├── service ├── .gitignore ├── assistant │ ├── widgets │ │ ├── google1bit.png │ │ ├── google2bit.png │ │ ├── user-location-pixel.png │ │ ├── timer.go │ │ └── highlight.go │ ├── util │ │ ├── coords.go │ │ ├── poi.go │ │ ├── storage │ │ │ └── redis.go │ │ ├── languages.go │ │ └── redact │ │ │ └── honeycomb.go │ ├── functions │ │ ├── fixup.go │ │ ├── time.go │ │ ├── locations.go │ │ └── feedback.go │ ├── feedback │ │ └── show_report.go │ ├── quota │ │ └── user.go │ ├── config │ │ └── config.go │ └── persistence │ │ └── persistence.go ├── main.go └── go.mod ├── tools ├── pdc-sequencer │ ├── .gitignore │ ├── go.mod │ ├── pdc │ │ ├── testdata │ │ │ ├── cog.pdci │ │ │ └── sent.pdcs │ │ ├── readwrite_test.go │ │ └── types.go │ └── main.go └── json2sadvibe │ └── json2sadvibe.py ├── .gitignore ├── app ├── resources │ ├── vibes │ │ ├── haptic_feedback.vibe │ │ ├── mario.vibe │ │ ├── reveille.vibe │ │ ├── jackhammer.vibe │ │ ├── nudge_nudge.vibe │ │ ├── standard_long_high.vibe │ │ ├── haptic_feedback.json │ │ ├── standard_long_high.json │ │ ├── jackhammer.json │ │ ├── nudge_nudge.json │ │ ├── mario.json │ │ └── reveille.json │ ├── icons │ │ ├── cog.pdc │ │ ├── clock.pdc │ │ ├── skull.pdc │ │ ├── timer.pdc │ │ ├── lightbulb.pdc │ │ ├── more_icon.png │ │ ├── reminder.pdc │ │ ├── menu_about.png │ │ ├── menu_alarms.png │ │ ├── menu_legal.png │ │ ├── menu_quota.png │ │ ├── menu_timers.png │ │ ├── action_bar_x.png │ │ ├── dictation_icon.png │ │ ├── menu_about_bw.png │ │ ├── menu_alarms_bw.png │ │ ├── menu_feedback.png │ │ ├── menu_legal_bw.png │ │ ├── menu_quota_bw.png │ │ ├── menu_reminders.png │ │ ├── menu_timers_bw.png │ │ ├── question_icon.png │ │ ├── menu_feedback_bw.png │ │ ├── action_bar_snooze.png │ │ ├── menu_icon_default.png │ │ ├── menu_reminders_bw.png │ │ ├── timer.svg │ │ ├── lightbulb.svg │ │ ├── skull.svg │ │ ├── reminder.svg │ │ ├── cog.svg │ │ └── clock.svg │ ├── images │ │ ├── sent.pdc │ │ ├── skull.pdc │ │ ├── failed_pony.pdc │ │ ├── fence_pony_bw.png │ │ ├── sleeping_pony.pdc │ │ ├── fence_pony_color.png │ │ ├── root_screen │ │ │ ├── pony.pdc │ │ │ └── pony.svg │ │ ├── sleeping_pony.svg │ │ └── failed_pony.svg │ ├── button_indicator.png │ ├── weather │ │ ├── medium │ │ │ ├── sunny.pdc │ │ │ ├── cloudy.pdc │ │ │ ├── generic.pdc │ │ │ ├── heavy_rain.pdc │ │ │ ├── heavy_snow.pdc │ │ │ ├── light_rain.pdc │ │ │ ├── light_snow.pdc │ │ │ ├── partly_cloudy.pdc │ │ │ └── raining_and_snowing.pdc │ │ └── small │ │ │ ├── cloudy.pdc │ │ │ ├── sunny.pdc │ │ │ ├── generic.pdc │ │ │ ├── heavy_rain.pdc │ │ │ ├── heavy_snow.pdc │ │ │ ├── light_rain.pdc │ │ │ ├── light_snow.pdc │ │ │ ├── partly_cloudy.pdc │ │ │ └── raining_and_snowing.pdc │ ├── animations │ │ ├── tired_pony.pdcs │ │ └── running_pony.pdcs │ └── text │ │ ├── about.txt │ │ ├── llm_warning.txt │ │ ├── gemini_consent.txt │ │ ├── location_consent.txt │ │ ├── feedback_blurb.txt │ │ ├── report_blurb.txt │ │ ├── sample_prompts.txt │ │ ├── legal.txt │ │ └── changelog │ │ └── 1.4.txt ├── src │ ├── c │ │ ├── util │ │ │ ├── memory │ │ │ │ ├── malloc.h │ │ │ │ ├── pressure.h │ │ │ │ ├── sdk.h │ │ │ │ └── malloc.c │ │ │ ├── logging.h │ │ │ ├── style.c │ │ │ ├── debug_state.h │ │ │ ├── action_menu_crimes.h │ │ │ ├── strings.c │ │ │ ├── strings.h │ │ │ ├── result_window.h │ │ │ ├── time.h │ │ │ ├── action_menu_crimes.c │ │ │ ├── thinking_layer.h │ │ │ ├── style.h │ │ │ ├── vector_layer.h │ │ │ ├── formatted_text_layer.h │ │ │ ├── vector_sequence_layer.h │ │ │ ├── persist_keys.h │ │ │ ├── time.c │ │ │ ├── perimeter.h │ │ │ └── vector_layer.c │ │ ├── release_notes.h │ │ ├── menus │ │ │ ├── about_window.h │ │ │ ├── feedback_window.h │ │ │ ├── legal_window.h │ │ │ ├── root_menu.h │ │ │ ├── quota_window.h │ │ │ ├── reminders_menu.h │ │ │ ├── alarm_menu.h │ │ │ ├── usage_layer.h │ │ │ ├── usage_layer.c │ │ │ └── legal_window.c │ │ ├── vibes │ │ │ ├── haptic_feedback.h │ │ │ ├── sad_vibe_score.h │ │ │ ├── haptic_feedback.c │ │ │ └── sad_vibe_score.c │ │ ├── converse │ │ │ ├── report_window.h │ │ │ ├── session_window.h │ │ │ ├── segments │ │ │ │ ├── widgets │ │ │ │ │ ├── weather_util.h │ │ │ │ │ ├── map.h │ │ │ │ │ ├── timer.h │ │ │ │ │ ├── number.h │ │ │ │ │ ├── weather_current.h │ │ │ │ │ ├── weather_multi_day.h │ │ │ │ │ ├── weather_single_day.h │ │ │ │ │ └── weather_util.c │ │ │ │ ├── message_layer.h │ │ │ │ ├── info_layer.h │ │ │ │ └── segment_layer.h │ │ │ └── conversation_manager.h │ │ ├── alarms │ │ │ ├── alarm_window.h │ │ │ └── manager.h │ │ ├── consent │ │ │ └── consent.h │ │ ├── root_window.h │ │ ├── talking_horse_layer.h │ │ ├── version │ │ │ ├── version.h │ │ │ └── version.c │ │ ├── features.h │ │ ├── image_manager │ │ │ └── image_manager.h │ │ ├── settings │ │ │ ├── settings.h │ │ │ └── settings.c │ │ └── assistant.c │ └── pkjs │ │ ├── features.js │ │ ├── urls_override.js │ │ ├── emulator │ │ ├── emulator_session.js │ │ └── emulator_main.js │ │ ├── custom_config.js │ │ ├── config.js │ │ ├── widgets │ │ ├── highlights.js │ │ ├── timer.js │ │ ├── index.js │ │ └── map.js │ │ ├── urls.js │ │ ├── actions │ │ ├── feedback.js │ │ ├── index.js │ │ ├── timeline.js │ │ └── reminders.js │ │ ├── quota.js │ │ ├── location.js │ │ ├── lib │ │ └── image_transfer.js │ │ └── reminders.js ├── .gitignore ├── wscript └── CMakeLists.txt ├── docs └── screenshot.png ├── Dockerfile-service ├── README.md ├── .github └── workflows │ ├── docker-image.yaml │ └── build-pbw.yaml └── CONTRIBUTING.md /service/.gitignore: -------------------------------------------------------------------------------- 1 | service 2 | .env 3 | -------------------------------------------------------------------------------- /tools/pdc-sequencer/.gitignore: -------------------------------------------------------------------------------- 1 | pdc-sequencer 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | settings.json 4 | -------------------------------------------------------------------------------- /app/resources/vibes/haptic_feedback.vibe: -------------------------------------------------------------------------------- 1 | 0 -------------------------------------------------------------------------------- /docs/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/docs/screenshot.png -------------------------------------------------------------------------------- /tools/pdc-sequencer/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pebble-dev/bobby-assistant/tools/pdc-sequencer 2 | 3 | go 1.23 4 | -------------------------------------------------------------------------------- /app/resources/icons/cog.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/icons/cog.pdc -------------------------------------------------------------------------------- /app/resources/icons/clock.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/icons/clock.pdc -------------------------------------------------------------------------------- /app/resources/icons/skull.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/icons/skull.pdc -------------------------------------------------------------------------------- /app/resources/icons/timer.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/icons/timer.pdc -------------------------------------------------------------------------------- /app/resources/images/sent.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/images/sent.pdc -------------------------------------------------------------------------------- /app/resources/images/skull.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/images/skull.pdc -------------------------------------------------------------------------------- /app/resources/vibes/mario.vibe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/vibes/mario.vibe -------------------------------------------------------------------------------- /app/resources/icons/lightbulb.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/icons/lightbulb.pdc -------------------------------------------------------------------------------- /app/resources/icons/more_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/icons/more_icon.png -------------------------------------------------------------------------------- /app/resources/icons/reminder.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/icons/reminder.pdc -------------------------------------------------------------------------------- /app/resources/vibes/reveille.vibe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/vibes/reveille.vibe -------------------------------------------------------------------------------- /app/resources/button_indicator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/button_indicator.png -------------------------------------------------------------------------------- /app/resources/icons/menu_about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/icons/menu_about.png -------------------------------------------------------------------------------- /app/resources/icons/menu_alarms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/icons/menu_alarms.png -------------------------------------------------------------------------------- /app/resources/icons/menu_legal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/icons/menu_legal.png -------------------------------------------------------------------------------- /app/resources/icons/menu_quota.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/icons/menu_quota.png -------------------------------------------------------------------------------- /app/resources/icons/menu_timers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/icons/menu_timers.png -------------------------------------------------------------------------------- /app/resources/vibes/jackhammer.vibe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/vibes/jackhammer.vibe -------------------------------------------------------------------------------- /app/resources/icons/action_bar_x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/icons/action_bar_x.png -------------------------------------------------------------------------------- /app/resources/icons/dictation_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/icons/dictation_icon.png -------------------------------------------------------------------------------- /app/resources/icons/menu_about_bw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/icons/menu_about_bw.png -------------------------------------------------------------------------------- /app/resources/icons/menu_alarms_bw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/icons/menu_alarms_bw.png -------------------------------------------------------------------------------- /app/resources/icons/menu_feedback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/icons/menu_feedback.png -------------------------------------------------------------------------------- /app/resources/icons/menu_legal_bw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/icons/menu_legal_bw.png -------------------------------------------------------------------------------- /app/resources/icons/menu_quota_bw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/icons/menu_quota_bw.png -------------------------------------------------------------------------------- /app/resources/icons/menu_reminders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/icons/menu_reminders.png -------------------------------------------------------------------------------- /app/resources/icons/menu_timers_bw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/icons/menu_timers_bw.png -------------------------------------------------------------------------------- /app/resources/icons/question_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/icons/question_icon.png -------------------------------------------------------------------------------- /app/resources/images/failed_pony.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/images/failed_pony.pdc -------------------------------------------------------------------------------- /app/resources/images/fence_pony_bw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/images/fence_pony_bw.png -------------------------------------------------------------------------------- /app/resources/images/sleeping_pony.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/images/sleeping_pony.pdc -------------------------------------------------------------------------------- /app/resources/vibes/nudge_nudge.vibe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/vibes/nudge_nudge.vibe -------------------------------------------------------------------------------- /app/resources/weather/medium/sunny.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/weather/medium/sunny.pdc -------------------------------------------------------------------------------- /app/resources/weather/small/cloudy.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/weather/small/cloudy.pdc -------------------------------------------------------------------------------- /app/resources/weather/small/sunny.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/weather/small/sunny.pdc -------------------------------------------------------------------------------- /app/resources/animations/tired_pony.pdcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/animations/tired_pony.pdcs -------------------------------------------------------------------------------- /app/resources/icons/menu_feedback_bw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/icons/menu_feedback_bw.png -------------------------------------------------------------------------------- /app/resources/weather/medium/cloudy.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/weather/medium/cloudy.pdc -------------------------------------------------------------------------------- /app/resources/weather/medium/generic.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/weather/medium/generic.pdc -------------------------------------------------------------------------------- /app/resources/weather/small/generic.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/weather/small/generic.pdc -------------------------------------------------------------------------------- /service/assistant/widgets/google1bit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/service/assistant/widgets/google1bit.png -------------------------------------------------------------------------------- /service/assistant/widgets/google2bit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/service/assistant/widgets/google2bit.png -------------------------------------------------------------------------------- /app/resources/animations/running_pony.pdcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/animations/running_pony.pdcs -------------------------------------------------------------------------------- /app/resources/icons/action_bar_snooze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/icons/action_bar_snooze.png -------------------------------------------------------------------------------- /app/resources/icons/menu_icon_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/icons/menu_icon_default.png -------------------------------------------------------------------------------- /app/resources/icons/menu_reminders_bw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/icons/menu_reminders_bw.png -------------------------------------------------------------------------------- /app/resources/images/fence_pony_color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/images/fence_pony_color.png -------------------------------------------------------------------------------- /app/resources/images/root_screen/pony.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/images/root_screen/pony.pdc -------------------------------------------------------------------------------- /app/resources/vibes/standard_long_high.vibe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/vibes/standard_long_high.vibe -------------------------------------------------------------------------------- /app/resources/weather/medium/heavy_rain.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/weather/medium/heavy_rain.pdc -------------------------------------------------------------------------------- /app/resources/weather/medium/heavy_snow.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/weather/medium/heavy_snow.pdc -------------------------------------------------------------------------------- /app/resources/weather/medium/light_rain.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/weather/medium/light_rain.pdc -------------------------------------------------------------------------------- /app/resources/weather/medium/light_snow.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/weather/medium/light_snow.pdc -------------------------------------------------------------------------------- /app/resources/weather/small/heavy_rain.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/weather/small/heavy_rain.pdc -------------------------------------------------------------------------------- /app/resources/weather/small/heavy_snow.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/weather/small/heavy_snow.pdc -------------------------------------------------------------------------------- /app/resources/weather/small/light_rain.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/weather/small/light_rain.pdc -------------------------------------------------------------------------------- /app/resources/weather/small/light_snow.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/weather/small/light_snow.pdc -------------------------------------------------------------------------------- /tools/pdc-sequencer/pdc/testdata/cog.pdci: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/tools/pdc-sequencer/pdc/testdata/cog.pdci -------------------------------------------------------------------------------- /tools/pdc-sequencer/pdc/testdata/sent.pdcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/tools/pdc-sequencer/pdc/testdata/sent.pdcs -------------------------------------------------------------------------------- /app/resources/weather/small/partly_cloudy.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/weather/small/partly_cloudy.pdc -------------------------------------------------------------------------------- /app/resources/weather/medium/partly_cloudy.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/weather/medium/partly_cloudy.pdc -------------------------------------------------------------------------------- /service/assistant/widgets/user-location-pixel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/service/assistant/widgets/user-location-pixel.png -------------------------------------------------------------------------------- /app/resources/weather/medium/raining_and_snowing.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/weather/medium/raining_and_snowing.pdc -------------------------------------------------------------------------------- /app/resources/weather/small/raining_and_snowing.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-dev/bobby-assistant/HEAD/app/resources/weather/small/raining_and_snowing.pdc -------------------------------------------------------------------------------- /app/resources/text/about.txt: -------------------------------------------------------------------------------- 1 | # Bobby 2 | ## Version %d.%d 3 | The Rebble AI Assistant Pony 4 | 5 | ## Programming 6 | Katharine Berry 7 | ## Iconography 8 | Stasia Michalska -------------------------------------------------------------------------------- /app/src/c/util/memory/malloc.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Katharine Berry on 4/9/25. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | void *bmalloc(size_t size); 10 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | .lock-waf* 2 | node_modules/ 3 | build/ 4 | sdk-include/ 5 | cmake-build-debug/ 6 | *.pyc 7 | # npm rewrites this lockfile during the build so clearly it's worthless anyway 8 | package-lock.json 9 | -------------------------------------------------------------------------------- /app/resources/text/llm_warning.txt: -------------------------------------------------------------------------------- 1 | Bobby uses an AI language model to respond to your requests. Like all other AI language models, Bobby may lie, do the wrong thing, or make offensive or inappropriate comments. -------------------------------------------------------------------------------- /app/resources/text/gemini_consent.txt: -------------------------------------------------------------------------------- 1 | Bobby uses Google's Gemini API to process requests. To do this, all of your requests will be sent verbatim to Google. Google does not use these requests for training or for any other purpose. If you do not agree, you cannot use Bobby. -------------------------------------------------------------------------------- /app/resources/text/location_consent.txt: -------------------------------------------------------------------------------- 1 | In order to provide contextually relevant information, Bobby would like to use your precise location. The name of the city you are in will be sent to Google's Gemini along with your requests. This decision can be changed later in app's config page. 2 | 3 | Can Bobby access your location? -------------------------------------------------------------------------------- /app/resources/vibes/haptic_feedback.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": "Haptic Feedback", 3 | "notes": [ 4 | { 5 | "id": "medium_pulse", 6 | "vibe_duration_ms": 30, 7 | "brake_duration_ms": 18, 8 | "strength": 100 9 | } 10 | ], 11 | "pattern": ["medium_pulse"], 12 | "repeat_delay_ms": 0 13 | } 14 | -------------------------------------------------------------------------------- /app/resources/vibes/standard_long_high.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": "Standard (Long Pulse) - High", 3 | "notes": [ 4 | { 5 | "id": "long_pulse", 6 | "vibe_duration_ms": 500, 7 | "brake_duration_ms": 0, 8 | "strength": 100 9 | } 10 | ], 11 | "pattern": ["long_pulse"], 12 | "repeat_delay_ms": 1000 13 | } 14 | -------------------------------------------------------------------------------- /app/resources/text/feedback_blurb.txt: -------------------------------------------------------------------------------- 1 | # Feedback 2 | You can give us your feedback on Bobby by dictating a message here. Feedback is not anonymous, and is linked to your Rebble account. 3 | 4 | To give feedback during a conversation, you can hold select on the conversation screen to send the content to us. 5 | 6 | You can also give us feedback on the Rebble discord at rebble.io/discord. -------------------------------------------------------------------------------- /app/src/c/util/logging.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Katharine Berry on 4/9/25. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include "debug_state.h" 10 | 11 | #ifdef BOBBY_DEBUG_LEVEL 12 | #define BOBBY_LOG(level, ...) do {if (level <= BOBBY_DEBUG_LEVEL) APP_LOG(level, __VA_ARGS__);} while (0) 13 | #else 14 | #define BOBBY_LOG(level, ...) do {} while (0) 15 | #endif 16 | -------------------------------------------------------------------------------- /app/resources/text/report_blurb.txt: -------------------------------------------------------------------------------- 1 | # Report thread 2 | If something is wrong, you can report this conversation thread to Rebble. The entire conversation will be sent to Rebble, including your queries, Bobby's responses, and any internal tool usage. 3 | 4 | Reports are tied to your Rebble account. Consider whether this conversation contains personal information you would not wish to share with Rebble's developers before continuing. -------------------------------------------------------------------------------- /app/resources/icons/timer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/resources/icons/lightbulb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/resources/text/sample_prompts.txt: -------------------------------------------------------------------------------- 1 | What's the weather like? 2 | Set a baking timer for five minutes. 3 | Will I need an umbrella tomorrow? 4 | At 5pm, remind me to go grocery shopping. 5 | Who was the 22nd president of the United States? 6 | Set an alarm for 11am. 7 | What is 50 dollars in pounds? 8 | How do you say 'good morning' in German? 9 | How long would it take to drive to McDonald's? 10 | What is 47 squared? 11 | Where is the nearest bar that's open? 12 | What is the capital of Burkina Faso? 13 | What is the population of Germany? 14 | What is the speed of light? -------------------------------------------------------------------------------- /app/resources/icons/skull.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/resources/images/root_screen/pony.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/resources/icons/reminder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/resources/vibes/jackhammer.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": "Jackhammer", 3 | "notes": [ 4 | { 5 | "id": "forward_backward", 6 | "vibe_duration_ms": 50, 7 | "brake_duration_ms": 50, 8 | "strength": 100 9 | }, 10 | { 11 | "id": "sleep_500ms", 12 | "vibe_duration_ms": 500, 13 | "brake_duration_ms": 0, 14 | "strength": 0 15 | } 16 | ], 17 | "pattern": ["forward_backward", "forward_backward", "forward_backward", 18 | "forward_backward", "forward_backward", "forward_backward"], 19 | "repeat_delay_ms": 1000 20 | } 21 | -------------------------------------------------------------------------------- /app/resources/vibes/nudge_nudge.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": "Nudge Nudge", 3 | "notes": [ 4 | { 5 | "id": "medium_pulse", 6 | "vibe_duration_ms": 30, 7 | "brake_duration_ms": 18, 8 | "strength": 100 9 | }, 10 | { 11 | "id": "sleep_100ms", 12 | "vibe_duration_ms": 100, 13 | "brake_duration_ms": 0, 14 | "strength": 0 15 | }, 16 | { 17 | "id": "sleep_750ms", 18 | "vibe_duration_ms": 750, 19 | "brake_duration_ms": 0, 20 | "strength": 0 21 | } 22 | ], 23 | "pattern": ["medium_pulse", "sleep_100ms", "medium_pulse"], 24 | "repeat_delay_ms": 1000 25 | } 26 | -------------------------------------------------------------------------------- /app/src/c/util/style.c: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Katharine Berry on 3/5/25. 3 | // 4 | 5 | #include 6 | #include "style.h" 7 | 8 | void bobby_status_bar_config(StatusBarLayer *status_bar) { 9 | status_bar_layer_set_colors(status_bar, GColorWhite, GColorBlack); 10 | status_bar_layer_set_separator_mode(status_bar, StatusBarLayerSeparatorModeDotted); 11 | } 12 | 13 | void bobby_status_bar_result_pane_config(StatusBarLayer *status_bar) { 14 | status_bar_layer_set_colors(status_bar, COLOR_FALLBACK(ACCENT_COLOUR, GColorWhite), gcolor_legible_over(ACCENT_COLOUR)); 15 | status_bar_layer_set_separator_mode(status_bar, PBL_IF_COLOR_ELSE(StatusBarLayerSeparatorModeNone, StatusBarLayerSeparatorModeDotted)); 16 | } -------------------------------------------------------------------------------- /app/src/c/release_notes.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | 18 | void release_notes_maybe_push(); 19 | -------------------------------------------------------------------------------- /app/src/c/menus/about_window.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | void about_window_push(); 20 | -------------------------------------------------------------------------------- /app/src/c/menus/feedback_window.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | void feedback_window_push(); 20 | -------------------------------------------------------------------------------- /app/src/c/vibes/haptic_feedback.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | void vibe_haptic_feedback(); 20 | -------------------------------------------------------------------------------- /app/src/c/converse/report_window.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | void report_window_push(const char* thread_uuid); 20 | -------------------------------------------------------------------------------- /app/src/c/util/debug_state.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | #define BOBBY_DEBUG_LEVEL APP_LOG_LEVEL_ERROR 22 | -------------------------------------------------------------------------------- /app/src/c/menus/legal_window.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef CREDITS_H 18 | 19 | void legal_window_push(); 20 | 21 | #define CREDITS_H 22 | 23 | #endif //CREDITS_H 24 | -------------------------------------------------------------------------------- /service/assistant/util/coords.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | type Coords struct { 18 | Latitude float64 `json:"latitude"` 19 | Longitude float64 `json:"longitude"` 20 | } 21 | -------------------------------------------------------------------------------- /app/src/c/menus/root_menu.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef APP_ROOT_MENU_H 18 | #define APP_ROOT_MENU_H 19 | 20 | void root_menu_window_push(); 21 | 22 | #endif //APP_ROOT_MENU_H 23 | -------------------------------------------------------------------------------- /app/src/pkjs/features.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // IFTTT: if you change this, you need to update the corresponding feature in src/c/features.h. 18 | exports.FEATURE_MAP_WIDGET = true; 19 | -------------------------------------------------------------------------------- /app/src/pkjs/urls_override.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // You can set exports.QUERY_URL or exports.QUOTA_URL in this file to override them without 18 | // needing to edit the main urls.js. 19 | -------------------------------------------------------------------------------- /app/src/c/util/action_menu_crimes.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | void action_menu_level_set_separator_index(ActionMenuLevel *level, unsigned index); 21 | -------------------------------------------------------------------------------- /app/resources/icons/cog.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | -------------------------------------------------------------------------------- /app/src/c/menus/quota_window.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef BOBBY_APP_QUOTA_WINDOW_H 18 | #define BOBBY_APP_QUOTA_WINDOW_H 19 | 20 | void push_quota_window(); 21 | 22 | #endif //BOBBY_APP_QUOTA_WINDOW_H 23 | -------------------------------------------------------------------------------- /app/src/c/menus/reminders_menu.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef BOBBY_APP_REMINDERS_MENU_H 18 | #define BOBBY_APP_REMINDERS_MENU_H 19 | 20 | void reminders_menu_push(); 21 | 22 | #endif //BOBBY_APP_REMINDERS_MENU_H -------------------------------------------------------------------------------- /app/src/c/menus/alarm_menu.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef APP_ALARM_MENU_H 18 | #define APP_ALARM_MENU_H 19 | 20 | #include 21 | 22 | void alarm_menu_window_push(bool for_timers); 23 | 24 | #endif //APP_ALARM_MENU_H 25 | -------------------------------------------------------------------------------- /app/src/c/alarms/alarm_window.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef ALARMS_ALARM_WINDOW_H 18 | #define ALARMS_ALARM_WINDOW_H 19 | 20 | #include 21 | 22 | void alarm_window_push(time_t alarm_time, bool is_timer, char *name); 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /app/src/c/util/strings.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Steffan Robert William Donal 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | void strings_fix_android_bridge_bodge(char* str) { 18 | while (*str) { 19 | if (*str == '"') { 20 | *str = '\''; 21 | } 22 | str++; 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/c/consent/consent.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef APP_CONSENT_H 18 | #define APP_CONSENT_H 19 | 20 | #include 21 | 22 | bool must_present_consent(); 23 | void consent_window_push(); 24 | void consent_migrate(); 25 | 26 | #endif //APP_CONSENT_H 27 | -------------------------------------------------------------------------------- /app/src/c/util/strings.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Steffan Robert William Donal 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | // Makes the passed, null-terminated string compatible when sent to 20 | // the Android Pebble app as an app message. 21 | void strings_fix_android_bridge_bodge(char* str); 22 | -------------------------------------------------------------------------------- /app/src/c/util/result_window.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef RESULT_WINDOW_H 18 | #define RESULT_WINDOW_H 19 | 20 | #include 21 | 22 | void result_window_push(const char* title, const char* text, GDrawCommandImage *image, GColor background_color); 23 | 24 | #endif //RESULT_WINDOW_H 25 | -------------------------------------------------------------------------------- /app/src/c/util/time.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | void format_time(char *buffer, size_t size, struct tm *time); 22 | void format_time_ampm(char *buffer, size_t size, struct tm *time); 23 | void format_datetime(char *buffer, size_t size, time_t time); 24 | -------------------------------------------------------------------------------- /service/assistant/util/poi.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | type POI struct { 4 | Name string 5 | Address string 6 | Categories []string 7 | OpeningHours []string 8 | CurrentlyOpen bool 9 | PhoneNumber string 10 | PriceLevel string 11 | StarRating float64 12 | RatingCount int64 13 | DistanceKilometers float64 `json:"DistanceKilometers,omitempty"` 14 | DistanceMiles float64 `json:"DistanceMiles,omitempty"` 15 | Coordinates Coords `json:"Coordinates,omitempty"` 16 | } 17 | 18 | type POIQuery struct { 19 | Location string 20 | Query string 21 | LanguageCode string 22 | Units string 23 | } 24 | 25 | func (p *POIQuery) Equal(other *POIQuery) bool { 26 | if other == nil { 27 | return false 28 | } 29 | return p.Location == other.Location && 30 | p.Query == other.Query && 31 | p.LanguageCode == other.LanguageCode && 32 | p.Units == other.Units 33 | } 34 | -------------------------------------------------------------------------------- /app/src/c/converse/session_window.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef SESSION_WINDOW_H 18 | #define SESSION_WINDOW_H 19 | 20 | #include 21 | 22 | typedef struct SessionWindow SessionWindow; 23 | 24 | void session_window_push(int timeout, char *starting_prompt); 25 | void session_window_destroy(SessionWindow* window); 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /app/src/pkjs/emulator/emulator_session.js: -------------------------------------------------------------------------------- 1 | var messageQueue = require('../lib/message_queue').Queue; 2 | var prerecorded = require('./prerecorded'); 3 | 4 | function Session(prompt, threadId) { 5 | console.log('session'); 6 | this.prompt = prompt; 7 | this.threadId = threadId; 8 | this.randomResponseIndex = 0; 9 | } 10 | 11 | Session.prototype.run = function() { 12 | var response; 13 | console.log(this.prompt) 14 | if (this.prompt in prerecorded.specificResponses) { 15 | response = prerecorded.specificResponses[this.prompt]; 16 | } else { 17 | response = prerecorded.randomResponses[this.randomResponseIndex]; 18 | this.randomResponseIndex = (this.randomResponseIndex + 1) % prerecorded.randomResponses.length; 19 | } 20 | console.log("Sending response: " + response); 21 | for (var i = 0; i < response.length; i++) { 22 | messageQueue.enqueue(response[i]); 23 | } 24 | } 25 | 26 | exports.Session = Session; -------------------------------------------------------------------------------- /app/src/c/vibes/sad_vibe_score.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | #pragma once 19 | 20 | #include 21 | 22 | typedef struct SadVibeScore SadVibeScore; 23 | 24 | SadVibeScore* sad_vibe_score_create_with_resource(uint32_t resource_id); 25 | void sad_vibe_score_destroy(SadVibeScore* score); 26 | void sad_vibe_score_play(SadVibeScore* score); 27 | void sad_vibe_score_stop(); 28 | -------------------------------------------------------------------------------- /app/src/c/root_window.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef ROOT_WINDOW_H 18 | #define ROOT_WINDOW_H 19 | 20 | #include 21 | 22 | typedef struct RootWindow RootWindow; 23 | 24 | RootWindow* root_window_create(); 25 | Window* root_window_get_window(RootWindow *window); 26 | void root_window_destroy(RootWindow *window); 27 | void root_window_push(RootWindow *window); 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /app/resources/images/sleeping_pony.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/c/converse/segments/widgets/weather_util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef WEATHER_UTIL_H 18 | #define WEATHER_UTIL_H 19 | 20 | #include 21 | 22 | int weather_widget_get_medium_resource_for_condition(int condition); 23 | int weather_widget_get_small_resource_for_condition(int condition); 24 | GColor weather_widget_get_colour_for_condition(int condition); 25 | 26 | #endif //WEATHER_UTIL_H 27 | -------------------------------------------------------------------------------- /app/src/c/vibes/haptic_feedback.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "haptic_feedback.h" 18 | #include "sad_vibe_score.h" 19 | 20 | static SadVibeScore *s_haptic_feedback = NULL; 21 | 22 | void vibe_haptic_feedback() { 23 | if (!s_haptic_feedback) { 24 | s_haptic_feedback = sad_vibe_score_create_with_resource(RESOURCE_ID_VIBE_HAPTIC_FEEDBACK); 25 | } 26 | sad_vibe_score_play(s_haptic_feedback); 27 | } 28 | -------------------------------------------------------------------------------- /app/src/pkjs/custom_config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // This function will be serialised and sent into another context, so it cannot reference anything 18 | // that is not textually inside it. 19 | module.exports = function(minified) { 20 | var clayConfig = this; 21 | 22 | clayConfig.on(clayConfig.EVENTS.AFTER_BUILD, function() { 23 | // not actually anything to do here. 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /app/src/c/converse/segments/widgets/map.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include "../../conversation.h" 21 | 22 | typedef Layer MapWidget; 23 | 24 | MapWidget* map_widget_create(GRect rect, ConversationEntry* entry); 25 | ConversationEntry* map_widget_get_entry(MapWidget* layer); 26 | void map_widget_destroy(MapWidget* layer); 27 | void map_widget_update(MapWidget* layer); 28 | -------------------------------------------------------------------------------- /service/assistant/widgets/timer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package widgets 16 | 17 | import "context" 18 | 19 | type TimerWidget struct { 20 | TargetTime string `json:"target_time"` 21 | Name string `json:"name,omitempty"` 22 | } 23 | 24 | func timerWidget(ctx context.Context, targetTime string, name string) (*TimerWidget, error) { 25 | return &TimerWidget{ 26 | TargetTime: targetTime, 27 | Name: name, 28 | }, nil 29 | } 30 | -------------------------------------------------------------------------------- /app/src/c/converse/segments/widgets/timer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include "../../conversation.h" 21 | 22 | typedef Layer TimerWidget; 23 | 24 | TimerWidget* timer_widget_create(GRect rect, ConversationEntry* entry); 25 | ConversationEntry* timer_widget_get_entry(TimerWidget* layer); 26 | void timer_widget_destroy(TimerWidget* layer); 27 | void timer_widget_update(TimerWidget* layer); 28 | -------------------------------------------------------------------------------- /app/src/c/menus/usage_layer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef APP_USAGE_LAYER_H 18 | #define APP_USAGE_LAYER_H 19 | 20 | #include 21 | 22 | typedef Layer UsageLayer; 23 | 24 | #define PERCENTAGE_MAX 256 25 | 26 | UsageLayer* usage_layer_create(GRect frame); 27 | void usage_layer_destroy(UsageLayer* layer); 28 | void usage_layer_set_percentage(UsageLayer* layer, int16_t percentage); 29 | 30 | #endif //APP_USAGE_LAYER_H 31 | -------------------------------------------------------------------------------- /app/src/c/util/action_menu_crimes.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "action_menu_crimes.h" 18 | #include 19 | 20 | typedef struct { 21 | char padding[12]; 22 | unsigned separator_index; 23 | } ActionMenuLevelHack; 24 | 25 | void action_menu_level_set_separator_index(ActionMenuLevel *level, unsigned index) { 26 | ActionMenuLevelHack *hack = (ActionMenuLevelHack *)level; 27 | hack->separator_index = index; 28 | } 29 | -------------------------------------------------------------------------------- /Dockerfile-service: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | FROM --platform=$BUILDPLATFORM golang:1.23 AS build 16 | 17 | WORKDIR /go/src/app 18 | COPY service/go.mod service/go.sum . 19 | RUN go mod download 20 | ADD . /go/src/app 21 | ARG TARGETARCH TARGETOS 22 | RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -C service -buildvcs=true -o /go/bin/app . 23 | 24 | FROM gcr.io/distroless/static 25 | COPY --from=build /go/bin/app / 26 | WORKDIR / 27 | ENTRYPOINT ["/app"] 28 | -------------------------------------------------------------------------------- /app/src/c/converse/segments/widgets/number.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include "../../conversation.h" 21 | 22 | typedef Layer NumberWidget; 23 | 24 | NumberWidget* number_widget_create(GRect rect, ConversationEntry* entry); 25 | ConversationEntry* number_widget_get_entry(NumberWidget* layer); 26 | void number_widget_destroy(NumberWidget* layer); 27 | void number_widget_update(NumberWidget* layer); 28 | -------------------------------------------------------------------------------- /app/src/c/converse/segments/message_layer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef MESSAGE_LAYER_H 18 | #define MESSAGE_LAYER_H 19 | 20 | #include "../conversation.h" 21 | #include 22 | 23 | typedef Layer MessageLayer; 24 | 25 | MessageLayer* message_layer_create(GRect rect, ConversationEntry* entry); 26 | void message_layer_destroy(MessageLayer* layer); 27 | void message_layer_update(MessageLayer* layer); 28 | 29 | #endif //MESSAGE_LAYER_H 30 | -------------------------------------------------------------------------------- /app/src/c/talking_horse_layer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef TALKING_HORSE_LAYER_H 18 | #define TALKING_HORSE_LAYER_H 19 | 20 | #include 21 | 22 | typedef Layer TalkingHorseLayer; 23 | 24 | TalkingHorseLayer *talking_horse_layer_create(GRect frame); 25 | void talking_horse_layer_destroy(TalkingHorseLayer *layer); 26 | void talking_horse_layer_set_text(TalkingHorseLayer *layer, const char *text); 27 | 28 | #endif //TALKING_HORSE_LAYER_H 29 | -------------------------------------------------------------------------------- /app/src/c/util/memory/pressure.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | typedef bool (*MemoryPressureHandler)(void *context); 22 | 23 | void memory_pressure_init(); 24 | void memory_pressure_deinit(); 25 | void memory_pressure_register_callback(MemoryPressureHandler handler, int priority, void *context); 26 | void memory_pressure_unregister_callback(MemoryPressureHandler handler); 27 | bool memory_pressure_try_free(); 28 | -------------------------------------------------------------------------------- /app/src/c/util/thinking_layer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef THINKING_LAYER_H 18 | #define THINKING_LAYER_H 19 | 20 | #include 21 | 22 | // These are recommendations - it will scale to any size. 23 | #define THINKING_LAYER_WIDTH 48 24 | #define THINKING_LAYER_HEIGHT 15 25 | 26 | typedef Layer ThinkingLayer; 27 | 28 | 29 | ThinkingLayer* thinking_layer_create(GRect rect); 30 | void thinking_layer_destroy(ThinkingLayer *layer); 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /app/resources/text/legal.txt: -------------------------------------------------------------------------------- 1 | # Legal 2 | ## Gemini 3 | AI processing is provided by Google's Gemini under the terms at https://ai.google.dev/gemini-api/terms (Bobby is a 'paid service'). 4 | 5 | ## Weather 6 | Weather data provided by The Weather Channel. 7 | 8 | ## Wikipedia 9 | Some grounding information is fetched from Wikipedia during request processing. Wikipedia content is available under the Creative Commons Attribution-ShareAlike License. 10 | 11 | ## Currency Data 12 | Exchange rate data is provided by Exchange Rate API (https://www.exchangerate-api.com/). 13 | 14 | ## Disclaimers 15 | Bobby includes experimental technology and may sometimes provide inaccurate or offensive content that doesn't represent Rebble's views. 16 | Use discretion before relying on, publishing, or otherwise using content provided by Bobby. 17 | Don't rely on Bobby for medical, legal, financial, or other professional advice. Any content regarding those topics is provided for informational purposes only and is not a substitute for advice from a qualified professional. Content does not constitute medical treatment or diagnosis. -------------------------------------------------------------------------------- /app/src/c/converse/segments/info_layer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef INFO_LAYER_H 18 | #define INFO_LAYER_H 19 | 20 | #include "../conversation.h" 21 | 22 | #include 23 | 24 | typedef Layer InfoLayer; 25 | 26 | InfoLayer* info_layer_create(GRect rect, ConversationEntry* entry); 27 | ConversationEntry* info_layer_get_entry(InfoLayer* layer); 28 | void info_layer_destroy(InfoLayer* layer); 29 | void info_layer_update(InfoLayer* layer); 30 | 31 | #endif //INFO_LAYER_H 32 | -------------------------------------------------------------------------------- /app/src/pkjs/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | exports.getSettings = function() { 18 | return JSON.parse(localStorage.getItem('clay-settings')) || {}; 19 | } 20 | 21 | exports.setSetting = function(key, value) { 22 | var settings = exports.getSettings(); 23 | settings[key] = value; 24 | localStorage.setItem('clay-settings', JSON.stringify(settings)); 25 | } 26 | 27 | exports.isLocationEnabled = function() { 28 | return !!exports.getSettings()['LOCATION_ENABLED']; 29 | } 30 | -------------------------------------------------------------------------------- /app/src/c/util/style.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef COLOURS_H 18 | #define COLOURS_H 19 | 20 | #include 21 | 22 | #define ACCENT_COLOUR GColorShockingPink 23 | #define BRANDED_BACKGROUND_COLOUR COLOR_FALLBACK(ACCENT_COLOUR, GColorWhite) 24 | #define SELECTION_HIGHLIGHT_COLOUR COLOR_FALLBACK(ACCENT_COLOUR, GColorBlack) 25 | 26 | void bobby_status_bar_config(StatusBarLayer *status_bar); 27 | void bobby_status_bar_result_pane_config(StatusBarLayer *status_bar); 28 | 29 | #endif //COLOURS_H 30 | -------------------------------------------------------------------------------- /app/src/c/converse/segments/segment_layer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef SEGMENT_LAYER_H 18 | #define SEGMENT_LAYER_H 19 | 20 | #include "../conversation.h" 21 | 22 | #include 23 | 24 | typedef Layer SegmentLayer; 25 | 26 | SegmentLayer* segment_layer_create(GRect rect, ConversationEntry* entry, bool assistant_label); 27 | ConversationEntry* segment_layer_get_entry(SegmentLayer* layer); 28 | void segment_layer_destroy(SegmentLayer* layer); 29 | void segment_layer_update(SegmentLayer* layer); 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /service/assistant/widgets/highlight.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package widgets 16 | 17 | import ( 18 | "context" 19 | "strings" 20 | ) 21 | 22 | type NumberWidget struct { 23 | Number string `json:"number"` 24 | Unit string `json:"unit,omitempty"` 25 | } 26 | 27 | func numberWidget(ctx context.Context, number, unit string) (*NumberWidget, error) { 28 | unit = strings.TrimSpace(unit) 29 | if strings.EqualFold(unit, "none") { 30 | unit = "" 31 | } 32 | return &NumberWidget{ 33 | Number: number, 34 | Unit: unit, 35 | }, nil 36 | } 37 | -------------------------------------------------------------------------------- /app/src/pkjs/widgets/highlights.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | exports.number = function(session, params) { 18 | console.log(JSON.stringify(params)); 19 | var number = params['number']; 20 | var message = { 21 | HIGHLIGHT_WIDGET: 1, 22 | HIGHLIGHT_WIDGET_PRIMARY: number, 23 | }; 24 | var unit = params['unit']; 25 | if (unit && unit.length > 0) { 26 | message['HIGHLIGHT_WIDGET_SECONDARY'] = unit; 27 | } 28 | console.log(JSON.stringify(message)); 29 | session.enqueue(message); 30 | } 31 | -------------------------------------------------------------------------------- /app/src/c/version/version.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef VERSION_H 18 | #define VERSION_H 19 | 20 | #include 21 | 22 | typedef struct __attribute__((__packed__)) { 23 | uint8_t major; 24 | uint8_t minor; 25 | } VersionInfo; 26 | 27 | void version_init(); 28 | bool version_is_first_launch(); 29 | bool version_is_updated(); 30 | VersionInfo version_get_last_launch(); 31 | VersionInfo version_get_current(); 32 | int version_info_compare(VersionInfo a, VersionInfo b); 33 | const char* version_git_tag(); 34 | 35 | #endif //VERSION_H 36 | -------------------------------------------------------------------------------- /app/src/pkjs/widgets/timer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | exports.timer = function(session, params) { 18 | console.log(JSON.stringify(params)); 19 | var time = new Date(params['target_time']) 20 | var name = params['name'] || null; 21 | var message = { 22 | TIMER_WIDGET: 1, 23 | TIMER_WIDGET_TARGET_TIME: Math.round(time.getTime() / 1000), 24 | }; 25 | if (name && name.length > 0) { 26 | message['TIMER_WIDGET_NAME'] = name; 27 | } 28 | console.log(JSON.stringify(message)); 29 | session.enqueue(message); 30 | } 31 | -------------------------------------------------------------------------------- /app/src/c/util/vector_layer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef VECTOR_LAYER_H 18 | #define VECTOR_LAYER_H 19 | 20 | #include 21 | 22 | typedef Layer VectorLayer; 23 | 24 | VectorLayer* vector_layer_create(GRect frame); 25 | void vector_layer_destroy(VectorLayer *layer); 26 | Layer* vector_layer_get_layer(VectorLayer *layer); 27 | void vector_layer_set_vector(VectorLayer *layer, GDrawCommandImage *image); 28 | GDrawCommandImage *vector_layer_get_vector(VectorLayer *layer); 29 | void vector_layer_set_background_color(VectorLayer *layer, GColor color); 30 | 31 | #endif //VECTOR_LAYER_H 32 | -------------------------------------------------------------------------------- /app/src/c/converse/segments/widgets/weather_current.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef WEATHER_CURRENT_H 18 | #define WEATHER_CURRENT_H 19 | 20 | #include 21 | #include "../../conversation.h" 22 | 23 | typedef Layer WeatherCurrentWidget; 24 | 25 | WeatherCurrentWidget* weather_current_widget_create(GRect rect, ConversationEntry* entry); 26 | ConversationEntry* weather_current_widget_get_entry(WeatherCurrentWidget* layer); 27 | void weather_current_widget_destroy(WeatherCurrentWidget* layer); 28 | void weather_current_widget_update(WeatherCurrentWidget* layer); 29 | 30 | #endif //WEATHER_CURRENT_H 31 | -------------------------------------------------------------------------------- /app/src/c/converse/segments/widgets/weather_multi_day.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef WEATHER_MULTI_DAY_H 18 | #define WEATHER_MULTI_DAY_H 19 | 20 | #include 21 | #include "../../conversation.h" 22 | 23 | typedef Layer WeatherMultiDayWidget; 24 | 25 | WeatherMultiDayWidget* weather_multi_day_widget_create(GRect rect, ConversationEntry* entry); 26 | ConversationEntry* weather_multi_day_widget_get_entry(WeatherMultiDayWidget* layer); 27 | void weather_multi_day_widget_destroy(WeatherMultiDayWidget* layer); 28 | void weather_multi_day_widget_update(WeatherMultiDayWidget* layer); 29 | 30 | #endif //WEATHER_MULTI_DAY_H 31 | -------------------------------------------------------------------------------- /app/src/c/converse/segments/widgets/weather_single_day.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef WEATHER_SINGLE_DAY_H 18 | #define WEATHER_SINGLE_DAY_H 19 | 20 | #include 21 | #include "../../conversation.h" 22 | 23 | typedef Layer WeatherSingleDayWidget; 24 | 25 | WeatherSingleDayWidget* weather_single_day_widget_create(GRect rect, ConversationEntry* entry); 26 | ConversationEntry* weather_single_day_widget_get_entry(WeatherSingleDayWidget* layer); 27 | void weather_single_day_widget_destroy(WeatherSingleDayWidget* layer); 28 | void weather_single_day_widget_update(WeatherSingleDayWidget* layer); 29 | 30 | #endif //WEATHER_SINGLE_DAY_H 31 | -------------------------------------------------------------------------------- /app/src/c/features.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | // If true, instead of using dictation input, the app will just use a fixed prompt. 20 | #define ENABLE_FEATURE_FIXED_PROMPT 0 21 | 22 | // IFTTT: if you change this, you need to update the corresponding feature in src/pkjs/features.js. 23 | // If true, maps will be available. 24 | #define ENABLE_FEATURE_MAPS 1 25 | 26 | // If true, the image manager will be available (required for maps to function) 27 | #define ENABLE_FEATURE_IMAGE_MANAGER 1 28 | 29 | #if ENABLE_FEATURE_MAPS && !ENABLE_FEATURE_IMAGE_MANAGER 30 | #error "ENABLE_FEATURE_MAPS requires ENABLE_FEATURE_IMAGE_MANAGER to be enabled." 31 | #endif 32 | -------------------------------------------------------------------------------- /app/src/c/alarms/manager.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef ALARMS_MANAGER_H 18 | #define ALARMS_MANAGER_H 19 | 20 | #include 21 | 22 | typedef struct AlarmManager AlarmManager; 23 | typedef struct Alarm Alarm; 24 | 25 | void alarm_manager_init(); 26 | int alarm_manager_add_alarm(time_t when, bool is_timer, char* name, bool conversational); 27 | int alarm_manager_cancel_alarm(time_t when, bool is_timer); 28 | int alarm_manager_get_alarm_count(); 29 | Alarm* alarm_manager_get_alarm(int index); 30 | bool alarm_manager_maybe_alarm(); 31 | 32 | time_t alarm_get_time(Alarm* alarm); 33 | bool alarm_is_timer(Alarm* alarm); 34 | char* alarm_get_name(Alarm* alarm); 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /app/src/pkjs/urls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | exports.QUERY_URL = 'wss://bobby-api.rebble.io/query'; 19 | exports.QUOTA_URL = 'https://bobby-api.rebble.io/quota'; 20 | exports.FEEDBACK_URL = 'https://bobby-api.rebble.io/feedback'; 21 | exports.REPORT_URL = 'https://bobby-api.rebble.io/report'; 22 | 23 | var override = require('./urls_override'); 24 | 25 | if (override.QUERY_URL) { 26 | exports.QUERY_URL = override.QUERY_URL; 27 | } 28 | if (override.QUOTA_URL) { 29 | exports.QUOTA_URL = override.QUOTA_URL; 30 | } 31 | if (override.FEEDBACK_URL) { 32 | exports.FEEDBACK_URL = override.FEEDBACK_URL; 33 | } 34 | if (override.REPORT_URL) { 35 | exports.REPORT_URL = override.REPORT_URL; 36 | } 37 | -------------------------------------------------------------------------------- /app/resources/icons/clock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 34 | -------------------------------------------------------------------------------- /app/src/c/image_manager/image_manager.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | #include 19 | 20 | typedef enum { 21 | ImageStatusCreated, 22 | ImageStatusCompleted, 23 | ImageStatusDestroyed, 24 | } ImageStatus; 25 | 26 | typedef void (*ImageManagerCallback)(int image_id, ImageStatus status, void *context); 27 | 28 | void image_manager_init(); 29 | void image_manager_deinit(); 30 | void image_manager_register_callback(int image_id, ImageManagerCallback callback, void *context); 31 | void image_manager_unregister_callback(int image_id); 32 | GBitmap *image_manager_get_image(int image_id); 33 | GSize image_manager_get_size(int image_id); 34 | void image_manager_destroy_image(int image_id); 35 | void image_manager_destroy_all_images(); 36 | -------------------------------------------------------------------------------- /service/assistant/util/storage/redis.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package storage 16 | 17 | import ( 18 | "fmt" 19 | "sync" 20 | 21 | "github.com/redis/go-redis/v9" 22 | 23 | "github.com/pebble-dev/bobby-assistant/service/assistant/config" 24 | ) 25 | 26 | var onceRedis sync.Once 27 | var sharedRedis *redis.Client 28 | 29 | func GetRedis() *redis.Client { 30 | onceRedis.Do(func() { 31 | r, err := connectRedis() 32 | if err != nil { 33 | panic(fmt.Errorf("error connecting to redis: %v", err)) 34 | } 35 | sharedRedis = r 36 | }) 37 | return sharedRedis 38 | } 39 | 40 | func connectRedis() (*redis.Client, error) { 41 | opt, err := redis.ParseURL(config.GetConfig().RedisURL) 42 | if err != nil { 43 | return nil, err 44 | } 45 | return redis.NewClient(opt), nil 46 | } 47 | -------------------------------------------------------------------------------- /app/src/c/settings/settings.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | typedef enum { 22 | QuickLaunchBehaviourConverseWithTimeout = 1, 23 | QuickLaunchBehaviourConverseForever = 2, 24 | QuickLaunchBehaviourHomeScreen = 3, 25 | } QuickLaunchBehaviour; 26 | 27 | typedef enum { 28 | VibePatternSettingReveille = 1, 29 | VibePatternSettingMario = 2, 30 | VibePatternSettingNudgeNudge = 3, 31 | VibePatternSettingJackhammer = 4, 32 | VibePatternSettingStandard = 5, 33 | } VibePatternSetting; 34 | 35 | void settings_init(); 36 | void settings_deinit(); 37 | QuickLaunchBehaviour settings_get_quick_launch_behaviour(); 38 | VibePatternSetting settings_get_alarm_vibe_pattern(); 39 | VibePatternSetting settings_get_timer_vibe_pattern(); 40 | bool settings_get_should_confirm_transcripts(); 41 | -------------------------------------------------------------------------------- /app/src/pkjs/actions/feedback.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | var feedback = require('../lib/feedback'); 18 | 19 | exports.sendFeedback = function(session, message, callback) { 20 | var feedbackText = message['feedback']; 21 | var threadId = message['thread_id']; 22 | if (!feedbackText && !threadId) { 23 | callback({"error": "No feedback provided"}); 24 | } 25 | console.log("Sending feedback: '" + feedbackText + "' threadId: " + threadId); 26 | 27 | feedback.sendFeedback(feedbackText, threadId, function(success, status) { 28 | if (success) { 29 | session.enqueue({ACTION_FEEDBACK_SENT: 1}); 30 | callback({"status": "ok"}); 31 | } else { 32 | session.enqueue({WARNING: "Sending feedback failed"}); 33 | callback({"error": "Failed to send feedback: HTTP error code" + status}); 34 | } 35 | }); 36 | }; 37 | -------------------------------------------------------------------------------- /app/src/c/util/formatted_text_layer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | typedef Layer FormattedTextLayer; 22 | 23 | FormattedTextLayer* formatted_text_layer_create(GRect frame); 24 | Layer* formatted_text_layer_get_layer(FormattedTextLayer* layer); 25 | void formatted_text_layer_destroy(FormattedTextLayer* layer); 26 | void formatted_text_layer_set_text(FormattedTextLayer* layer, const char* text); 27 | void formatted_text_layer_set_title_font(FormattedTextLayer* layer, GFont font); 28 | void formatted_text_layer_set_subtitle_font(FormattedTextLayer* layer, GFont font); 29 | void formatted_text_layer_set_body_font(FormattedTextLayer* layer, GFont font); 30 | void formatted_text_layer_set_text_alignment(FormattedTextLayer* layer, GTextAlignment alignment); 31 | GSize formatted_text_layer_get_content_size(FormattedTextLayer* layer); 32 | -------------------------------------------------------------------------------- /app/src/c/util/vector_sequence_layer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef VECTOR_SEQUENCE_LAYER_H 18 | #define VECTOR_SEQUENCE_LAYER_H 19 | 20 | #include 21 | 22 | typedef Layer VectorSequenceLayer; 23 | 24 | VectorSequenceLayer* vector_sequence_layer_create(GRect frame); 25 | void vector_sequence_layer_destroy(VectorSequenceLayer *layer); 26 | Layer* vector_sequence_layer_get_layer(VectorSequenceLayer *layer); 27 | void vector_sequence_layer_set_sequence(VectorSequenceLayer *layer, GDrawCommandSequence *image); 28 | GDrawCommandSequence *vector_sequence_layer_get_sequence(VectorSequenceLayer *layer); 29 | void vector_sequence_layer_set_background_color(VectorSequenceLayer *layer, GColor color); 30 | void vector_sequence_layer_play(VectorSequenceLayer *layer); 31 | void vector_sequence_layer_stop(VectorSequenceLayer *layer); 32 | 33 | #endif //VECTOR_SEQUENCE_LAYER_H 34 | -------------------------------------------------------------------------------- /app/resources/images/failed_pony.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/resources/text/changelog/1.4.txt: -------------------------------------------------------------------------------- 1 | # New Features 2 | Bobby has been updated! We have some new features: 3 | ## Directions 4 | Bobby can now give you driving, walking, cycling, and transit directions. Try asking questions like "how long would it take to drive to McDonald's?" or "when is the next train to San Francisco?". You can get directions too, e.g. "give me walking directions to the library". 5 | ## Settings 6 | You can now ask Bobby to change settings verbally, e.g. "always use metric units", "use the Mario pattern for timers", or "set the response language to French". Every setting on the settings page except for location permissions can be changed verbally. 7 | ## Release notes 8 | You're reading them! Bobby will show you these release notes on the first launch after an update. 9 | Since this is the first batch, here are some other features we've added since 1.0... 10 | ## Places and maps 11 | Now when you ask Bobby about places (e.g. "where's a ramen place nearby?"), Bobby will now show up a map with the results on it. Bobby also now has more accurate location data in most regions (from Google Maps), and also knows the star ratings of each place - so you could, for instance, ask Bobby "which is the best?" 12 | Directions will show a map, too. 13 | ## Weather 14 | Bobby now knows the UV index, wind speed, and direction for current, hourly, and daily forecasts. 15 | ## Bug fixes 16 | We have some! Most notably, "double quotes" in your question now work - which can be helpful when, for instance, asking for translations. -------------------------------------------------------------------------------- /app/resources/vibes/mario.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": "Mario", 3 | "notes": [ 4 | { 5 | "id": "note_0", 6 | "vibe_duration_ms": 120, 7 | "brake_duration_ms": 0, 8 | "strength": 0 9 | }, 10 | { 11 | "id": "note_1", 12 | "vibe_duration_ms": 111, 13 | "brake_duration_ms": 9, 14 | "strength": 40 15 | }, 16 | { 17 | "id": "note_6", 18 | "vibe_duration_ms": 105, 19 | "brake_duration_ms": 15, 20 | "strength": 55 21 | }, 22 | { 23 | "id": "note_10", 24 | "vibe_duration_ms": 102, 25 | "brake_duration_ms": 18, 26 | "strength": 76 27 | }, 28 | { 29 | "id": "note_13", 30 | "vibe_duration_ms": 100, 31 | "brake_duration_ms": 20, 32 | "strength": 100 33 | }, 34 | { 35 | "id": "sleep_30ms", 36 | "vibe_duration_ms": 30, 37 | "brake_duration_ms": 0, 38 | "strength": 0 39 | } 40 | 41 | ], 42 | "pattern": ["note_10", "sleep_30ms", 43 | "note_10", "sleep_30ms", 44 | "note_0", "sleep_30ms", 45 | "note_10", "sleep_30ms", 46 | "note_0", "sleep_30ms", 47 | "note_6", "sleep_30ms", 48 | "note_10", "sleep_30ms", 49 | "note_0", "sleep_30ms", 50 | "note_13", "sleep_30ms", 51 | "note_0", "sleep_30ms", 52 | "note_0", "sleep_30ms", 53 | "note_0", "sleep_30ms", 54 | "note_1"], 55 | "repeat_delay_ms": 1000 56 | } 57 | -------------------------------------------------------------------------------- /app/src/c/util/memory/sdk.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | 19 | Layer *blayer_create(); 20 | Layer *blayer_create_with_data(GRect frame, size_t data_size); 21 | Window *bwindow_create(); 22 | ActionBarLayer *baction_bar_layer_create(); 23 | TextLayer *btext_layer_create(GRect frame); 24 | MenuLayer *bmenu_layer_create(GRect frame); 25 | SimpleMenuLayer *bsimple_menu_layer_create(GRect frame, Window *window, SimpleMenuSection *sections, int32_t num_sections, void *context); 26 | BitmapLayer *bbitmap_layer_create(GRect frame); 27 | ActionMenuLevel *baction_menu_level_create(int max_items); 28 | ScrollLayer *bscroll_layer_create(GRect frame); 29 | StatusBarLayer *bstatus_bar_layer_create(); 30 | GBitmap *bgbitmap_create_with_resource(uint32_t resource_id); 31 | GDrawCommandImage *bgdraw_command_image_create_with_resource(uint32_t resource_id); 32 | GDrawCommandSequence *bgdraw_command_sequence_create_with_resource(uint32_t resource_id); 33 | -------------------------------------------------------------------------------- /app/src/pkjs/widgets/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | var map = require('./map'); 18 | var weather = require('./weather'); 19 | var timer = require('./timer'); 20 | var highlights = require('./highlights'); 21 | var features = require('../features'); 22 | 23 | var widgetMap = { 24 | 'timer': timer.timer, 25 | 'number': highlights.number, 26 | 'weather-single-day': weather.singleDay, 27 | 'weather-current': weather.current, 28 | 'weather-multi-day': weather.multiDay 29 | } 30 | 31 | if (features.FEATURE_MAP_WIDGET) { 32 | widgetMap['map'] = map.map; 33 | } 34 | 35 | exports.handleWidget = function(session, widgetString) { 36 | var params = JSON.parse(widgetString); 37 | var name = params['type']; 38 | console.log("got a widget: ", widgetString); 39 | if (name in widgetMap) { 40 | widgetMap[name](session, params['content']); 41 | } else { 42 | console.log("Unknown widget '" + name + "'."); 43 | } 44 | } -------------------------------------------------------------------------------- /service/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "github.com/honeycombio/beeline-go" 19 | "github.com/honeycombio/beeline-go/wrappers/hnynethttp" 20 | "github.com/pebble-dev/bobby-assistant/service/assistant" 21 | "github.com/pebble-dev/bobby-assistant/service/assistant/config" 22 | "github.com/pebble-dev/bobby-assistant/service/assistant/util/redact" 23 | "github.com/pebble-dev/bobby-assistant/service/assistant/util/storage" 24 | "log" 25 | "net/http" 26 | ) 27 | 28 | func main() { 29 | beeline.Init(beeline.Config{ 30 | WriteKey: config.GetConfig().HoneycombKey, 31 | Dataset: "rws", 32 | ServiceName: "bobby", 33 | PresendHook: redact.CleanHoneycomb, 34 | }) 35 | defer beeline.Close() 36 | http.DefaultTransport = hnynethttp.WrapRoundTripper(http.DefaultTransport) 37 | service := assistant.NewService(storage.GetRedis()) 38 | addr := "0.0.0.0:8080" 39 | log.Printf("Listening on %s.", addr) 40 | log.Fatal(service.ListenAndServe(addr)) 41 | } 42 | -------------------------------------------------------------------------------- /service/assistant/util/languages.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import "strings" 18 | 19 | var languages = map[string]string{ 20 | "af": "Afrikaans", 21 | "cs": "Czech", 22 | "da": "Danish", 23 | "de": "German", 24 | "en": "English", 25 | "fi": "Finnish", 26 | "fil": "Filipino", 27 | "fr": "French", 28 | "gl": "Galician", 29 | "id": "Indonesian", 30 | "is": "Icelandic", 31 | "it": "Italian", 32 | "ko": "Korean", 33 | "lv": "Latvian", 34 | "lt": "Lithuanian", 35 | "hr": "Croatian", 36 | "hu": "Hungarian", 37 | "ms": "Malay", 38 | "nl": "Dutch", 39 | "no": "Norwegian", 40 | "pt": "Portuguese", 41 | "pl": "Polish", 42 | "ro": "Romanian", 43 | "ru": "Russian", 44 | "es": "Spanish", 45 | "sk": "Slovak", 46 | "sl": "Slovenian", 47 | "sv": "Swedish", 48 | "sw": "Swahili", 49 | "tr": "Turkish", 50 | "zu": "Zulu", 51 | } 52 | 53 | func GetLanguageName(code string) string { 54 | code = strings.SplitN(code, "_", 2)[0] 55 | code = strings.ToLower(code) 56 | if val, ok := languages[code]; ok { 57 | return val 58 | } 59 | return "" 60 | } 61 | -------------------------------------------------------------------------------- /app/src/pkjs/emulator/emulator_main.js: -------------------------------------------------------------------------------- 1 | var location = require("../location"); 2 | var reminders = require("../reminders"); 3 | var emulatorSession = require("./emulator_session"); 4 | var quota = require("../quota"); 5 | var config = require("../config"); 6 | var feedback = require("../lib/feedback"); 7 | 8 | function main() { 9 | location.update(); 10 | Pebble.addEventListener('appmessage', handleAppMessage); 11 | } 12 | 13 | 14 | function handleAppMessage(e) { 15 | console.log("Inbound app message!"); 16 | console.log(JSON.stringify(e)); 17 | var data = e.payload; 18 | if (data.PROMPT) { 19 | console.log("Starting a new Session..."); 20 | var s = new emulatorSession.Session(data.PROMPT, data.THREAD_ID); 21 | s.run(); 22 | return; 23 | } 24 | 25 | if (reminders.handleReminderMessage(data)) { 26 | return; 27 | } 28 | 29 | if (data.QUOTA_REQUEST) { 30 | console.log("Requesting quota..."); 31 | quota.handleQuotaRequest(); 32 | } 33 | if ('LOCATION_ENABLED' in data) { 34 | config.setSetting("LOCATION_ENABLED", !!data.LOCATION_ENABLED); 35 | console.log("Location enabled: " + config.isLocationEnabled()); 36 | // We need to confirm that we received this for the watch to proceed. 37 | Pebble.sendAppMessage({ 38 | LOCATION_ENABLED: data.LOCATION_ENABLED, 39 | }); 40 | } 41 | if ('FEEDBACK_TEXT' in data) { 42 | console.log("Handling feedback..."); 43 | feedback.handleFeedbackRequest(data); 44 | } 45 | if ('REPORT_THREAD_UUID' in data) { 46 | console.log("Handling report..."); 47 | feedback.handleReportRequest(data); 48 | } 49 | } 50 | 51 | exports.main = main; 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tiny Assistant 2 | 3 | Tiny Assistant is an LLM-based assistant that runs on your Pebble smartwatch, 4 | if you still have a smartwatch that ceased production in 2016 lying around. 5 | 6 | ![A screenshot from a Pebble smartwatch running the Tiny Assistant. The user asked for the time, the assistant responded that it was 3:59 PM.](./docs/screenshot.png) 7 | 8 | ## Usage 9 | 10 | ### Server 11 | 12 | To use Tiny Assistant, you will need to run the server in `service/` somewhere 13 | your phone can reach. 14 | 15 | You will also need to set a few environment variables: 16 | 17 | - `GEMINI_KEY` - a key for Google's Gemini - you can get one at the 18 | [Google AI Studio](https://aistudio.google.com) 19 | - `REDIS_URL` - a URL for a functioning Redis server. No data is persisted 20 | long-term, so a purely in-memory server is fine. 21 | - `USER_IDENTIFICATION_URL` - a URL pointing to an instance of 22 | [user-identifier](https://github.com/pebble-dev/user-identifier). 23 | - `MAPBOX_KEY` - an API key for [Mapbox](https://www.mapbox.com), which is 24 | used for geocoding. If no key is provided, geocoding will be unavailable. 25 | 26 | ### Client 27 | 28 | Update the URL in `app/src/pkjs/urls.js` to point at your instance of the 29 | server. 30 | 31 | Then you can simply build it using the Pebble SDK and install on your watch. 32 | 33 | ## Contributing 34 | 35 | See [`CONTRIBUTING.md`](CONTRIBUTING.md) for details. 36 | 37 | ## License 38 | 39 | Apache 2.0; see [`LICENSE`](LICENSE) for details. 40 | 41 | ## Disclaimer 42 | 43 | This project is not an official Google project. It is not supported by 44 | Google and Google specifically disclaims all warranties as to its quality, 45 | merchantability, or fitness for a particular purpose. 46 | 47 | -------------------------------------------------------------------------------- /service/assistant/functions/fixup.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package functions 16 | 17 | import ( 18 | "encoding/json" 19 | "log" 20 | "regexp" 21 | ) 22 | 23 | var valueRegexp = regexp.MustCompile(`(?m)^\s*".+?"\s*:\s*(.+?),?\s*$`) 24 | 25 | // FixupBrokenJson takes a string that is supposed to be JSON, but erroneously contains expressions where there 26 | // should be numbers, and evaluates those expressions. 27 | func FixupBrokenJson(j string) string { 28 | var throwaway any 29 | if err := json.Unmarshal([]byte(j), &throwaway); err == nil { 30 | return j 31 | } 32 | values := valueRegexp.FindAllStringSubmatchIndex(j, -1) 33 | newRequest := "" 34 | lastIndex := 0 35 | for _, v := range values { 36 | newRequest += j[lastIndex:v[2]] 37 | expression := j[v[2]:v[3]] 38 | err := json.Unmarshal([]byte(expression), &throwaway) 39 | if err != nil { 40 | replacement := evalExpression(expression) 41 | log.Printf("Replacing expression %q with its value %q", expression, replacement) 42 | expression = replacement 43 | } 44 | newRequest += expression 45 | lastIndex = v[3] 46 | } 47 | newRequest += j[lastIndex:] 48 | return newRequest 49 | } 50 | -------------------------------------------------------------------------------- /app/src/pkjs/quota.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | var session = require('./session'); 18 | 19 | var QUOTA_URL = require('./urls').QUOTA_URL; 20 | 21 | exports.fetchQuota = function(callback) { 22 | var url = QUOTA_URL; 23 | url += '?token=' + session.userToken; 24 | console.log("Fetching quota from " + url); 25 | var req = new XMLHttpRequest(); 26 | req.open('GET', url, true); 27 | req.onload = function(e) { 28 | if (req.readyState === 4) { 29 | if (req.status === 200) { 30 | console.log("Got quota response: " + req.responseText); 31 | var response = JSON.parse(req.responseText); 32 | callback(response); 33 | } else { 34 | console.log("Request returned error code " + req.status.toString()); 35 | } 36 | } 37 | } 38 | req.send(); 39 | } 40 | 41 | exports.handleQuotaRequest = function() { 42 | console.log("Requesting quota..."); 43 | exports.fetchQuota(function(response) { 44 | Pebble.sendAppMessage({ 45 | QUOTA_RESPONSE_USED: response.used, 46 | QUOTA_RESPONSE_REMAINING: response.remaining, 47 | QUOTA_HAS_SUBSCRIPTION: response.hasSubscription, 48 | }); 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /app/src/c/util/persist_keys.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef APP_PERSIST_KEYS_H 18 | #define APP_PERSIST_KEYS_H 19 | 20 | // These keys are stored centrally so we can avoid accidental collisions. 21 | // Remember: these numbers can *never* be changed. 22 | 23 | // next key: 14 24 | 25 | // We write the alarm count twice - once before doing any work, and once after. 26 | // If they disagree we assume the lower number is correct. 27 | #define PERSIST_KEY_ALARM_COUNT_ONE 1 28 | #define PERSIST_KEY_ALARM_COUNT_TWO 2 29 | #define PERSIST_KEY_ALARM_TIMES 3 30 | #define PERSIST_KEY_ALARM_WAKEUP_IDS 4 31 | #define PERSIST_KEY_ALARM_IS_TIMERS 5 32 | #define PERSIST_KEY_ALARM_NAMES 8 33 | 34 | // Store whether we have successfully requested location consent. 35 | #define PERSIST_KEY_LOCATION_ENABLED 6 36 | 37 | // Store whether the user has accepted the consents 38 | #define PERSIST_KEY_CONSENTS_COMPLETED 12 39 | 40 | // Contains the version we were running the last time we were launched 41 | #define PERSIST_KEY_VERSION 7 42 | 43 | // Persist keys for our settings 44 | #define PERSIST_KEY_QUICK_LAUNCH_BEHAVIOUR 9 45 | #define PERSIST_KEY_ALARM_VIBE_PATTERN 10 46 | #define PERSIST_KEY_TIMER_VIBE_PATTERN 11 47 | #define PERSIST_KEY_CONFIRM_TRANSCRIPTS 13 48 | 49 | #endif //APP_PERSIST_KEYS_H 50 | -------------------------------------------------------------------------------- /app/src/c/converse/conversation_manager.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef CONVERSATION_MANAGER_H 18 | #define CONVERSATION_MANAGER_H 19 | 20 | #include 21 | #include "conversation.h" 22 | 23 | typedef struct ConversationManager ConversationManager; 24 | typedef void (*ConversationManagerUpdateHandler)(bool entry_added, void* context); 25 | typedef void (*ConversationManagerEntryDeletedHandler)(int index, void* context); 26 | 27 | void conversation_manager_init(); 28 | ConversationManager* conversation_manager_create(); 29 | ConversationManager* conversation_manager_get_current(); 30 | void conversation_manager_destroy(ConversationManager* manager); 31 | void conversation_manager_set_handler(ConversationManager* manager, ConversationManagerUpdateHandler handler, void* context); 32 | void conversation_manager_set_deletion_handler(ConversationManager* manager, ConversationManagerEntryDeletedHandler handler); 33 | void conversation_manager_add_input(ConversationManager* manager, const char* input); 34 | void conversation_manager_add_action(ConversationManager* manager, ConversationAction* action); 35 | void conversation_manager_add_widget(ConversationManager* manager, ConversationWidget* widget); 36 | Conversation* conversation_manager_get_conversation(ConversationManager* manager); 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /app/src/pkjs/location.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | var cachedLon = undefined; 18 | var cachedLat = undefined; 19 | 20 | exports.update = function() { 21 | // start with whatever we knew before, if anything. 22 | var oldLon = localStorage.getItem('oldLon'); 23 | var oldLat = localStorage.getItem('oldLat'); 24 | if (oldLon && oldLat) { 25 | cachedLon = parseFloat(oldLon); 26 | cachedLat = parseFloat(oldLat); 27 | console.log("Cached location restored: (" + cachedLat + ", " + cachedLon + ")"); 28 | } 29 | 30 | navigator.geolocation.getCurrentPosition(function(pos) { 31 | cachedLat = pos.coords.latitude; 32 | cachedLon = pos.coords.longitude; 33 | console.log("position updated: (" + cachedLat + ", " + cachedLon + ")"); 34 | localStorage.setItem('oldLon', cachedLon); 35 | localStorage.setItem('oldLat', cachedLat); 36 | }, function (err) { 37 | console.log("Failed to update location: " + err); 38 | }, { 39 | enableHighAccuracy: true, 40 | maximumAge: 300000, 41 | timeout: 30000, 42 | }); 43 | } 44 | 45 | exports.isReady = function() { 46 | return !!(cachedLon && cachedLat); 47 | } 48 | 49 | exports.getPos = function() { 50 | return {lon: cachedLon, lat: cachedLat}; 51 | } 52 | -------------------------------------------------------------------------------- /service/assistant/feedback/show_report.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package feedback 16 | 17 | import ( 18 | _ "embed" 19 | "encoding/json" 20 | "github.com/pebble-dev/bobby-assistant/service/assistant/util/storage" 21 | "html/template" 22 | "log" 23 | "net/http" 24 | "path" 25 | ) 26 | 27 | //go:embed report.html 28 | var reportTemplateString string 29 | var funcMap = template.FuncMap{"jsonify": jsonify} 30 | var reportTemplate = template.Must(template.New("report").Funcs(funcMap).Parse(reportTemplateString)) 31 | 32 | func jsonify(v interface{}) interface{} { 33 | b, err := json.MarshalIndent(v, "", " ") 34 | if err != nil { 35 | return v 36 | } 37 | return string(b) 38 | } 39 | 40 | func HandleShowReport(rw http.ResponseWriter, r *http.Request) { 41 | ctx := r.Context() 42 | rd := storage.GetRedis() 43 | 44 | _, reportId := path.Split(r.URL.Path) 45 | 46 | reportedThread, err := loadReport(ctx, rd, reportId) 47 | if err != nil { 48 | log.Printf("Error getting reported thread: %v", err) 49 | http.Error(rw, err.Error(), http.StatusInternalServerError) 50 | return 51 | } 52 | 53 | if err := reportTemplate.ExecuteTemplate(rw, "report", reportedThread); err != nil { 54 | log.Printf("Error executing template: %v", err) 55 | http.Error(rw, err.Error(), http.StatusInternalServerError) 56 | return 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/c/menus/usage_layer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "usage_layer.h" 18 | #include "../util/memory/sdk.h" 19 | 20 | #include 21 | 22 | typedef struct { 23 | int16_t percentage; 24 | } UsageLayerData; 25 | 26 | static void prv_layer_update(Layer* layer, GContext* ctx); 27 | 28 | UsageLayer* usage_layer_create(GRect frame) { 29 | UsageLayer* layer = blayer_create_with_data(frame, sizeof(UsageLayerData)); 30 | UsageLayerData* data = layer_get_data(layer); 31 | layer_set_update_proc(layer, prv_layer_update); 32 | data->percentage = 0; 33 | return layer; 34 | } 35 | 36 | void usage_layer_destroy(UsageLayer* layer) { 37 | layer_destroy(layer); 38 | } 39 | 40 | void usage_layer_set_percentage(UsageLayer* layer, int16_t percentage) { 41 | UsageLayerData* data = layer_get_data(layer); 42 | data->percentage = percentage; 43 | layer_mark_dirty(layer); 44 | } 45 | 46 | static void prv_layer_update(Layer* layer, GContext* ctx) { 47 | UsageLayerData* data = layer_get_data(layer); 48 | GRect bounds = layer_get_bounds(layer); 49 | graphics_context_set_fill_color(ctx, GColorWhite); 50 | graphics_fill_rect(ctx, bounds, 0, GCornerNone); 51 | graphics_context_set_fill_color(ctx, GColorDarkGray); 52 | graphics_fill_rect(ctx, GRect(0, 0, (bounds.size.w * data->percentage) / PERCENTAGE_MAX, bounds.size.h), 0, GCornerNone); 53 | graphics_context_set_stroke_color(ctx, GColorBlack); 54 | graphics_draw_rect(ctx, bounds); 55 | } 56 | -------------------------------------------------------------------------------- /app/src/pkjs/actions/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | var reminders = require('./reminders'); 18 | var alarms = require('./alarms'); 19 | var feedback = require('./feedback'); 20 | var settings = require('./settings'); 21 | var config = require('../config.js'); 22 | 23 | var actionMap = { 24 | 'set_reminder': reminders.setReminder, 25 | 'get_reminders': reminders.getReminders, 26 | 'delete_reminder': reminders.deleteReminder, 27 | 'set_alarm': alarms.setAlarm, 28 | 'get_alarm': alarms.getAlarm, 29 | 'send_feedback': feedback.sendFeedback, 30 | 'update_settings': settings.updateSettings, 31 | }; 32 | 33 | var extraActions = ['named_alarms']; 34 | 35 | exports.handleAction = function(session, ws, actionParamString) { 36 | var params = JSON.parse(actionParamString); 37 | var name = params['action']; 38 | console.log("got an action: ", actionParamString); 39 | if (name in actionMap) { 40 | actionMap[name](session, params, function(result) { 41 | console.log("Sending websocket response..."); 42 | ws.send(JSON.stringify(result)); 43 | console.log("Send"); 44 | }) 45 | } else { 46 | ws.send(JSON.stringify({"error": "Unknown action '" + name + "'."})); 47 | } 48 | } 49 | 50 | exports.getSupportedActions = function() { 51 | var results = []; 52 | for (var name in actionMap) { 53 | results.push(name); 54 | } 55 | return results.concat(extraActions); 56 | } 57 | -------------------------------------------------------------------------------- /.github/workflows/docker-image.yaml: -------------------------------------------------------------------------------- 1 | name: Push image to Container Registry 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - 'service/**' 9 | 10 | jobs: 11 | build-and-push: 12 | runs-on: ubuntu-latest 13 | steps: 14 | # Check out the repository 15 | - uses: actions/checkout@v3 16 | 17 | # Set up QEMU for multi-platform builds (required for non-native architectures) 18 | - name: Set up QEMU 19 | uses: docker/setup-qemu-action@v2 20 | 21 | # Set up Docker Buildx for advanced multi-platform builds 22 | - name: Set up Docker Buildx 23 | uses: docker/setup-buildx-action@v2 24 | 25 | # Log in to GitHub Container Registry using the provided GITHUB_TOKEN 26 | - name: Log in to GitHub Container Registry 27 | uses: docker/login-action@v2 28 | with: 29 | registry: ghcr.io 30 | username: ${{ github.actor }} 31 | password: ${{ secrets.GITHUB_TOKEN }} 32 | 33 | - name: Set short git commit SHA 34 | id: vars 35 | run: | 36 | calculatedSha=$(git rev-parse --short ${{ github.sha }}) 37 | echo "COMMIT_SHORT_SHA=$calculatedSha" >> $GITHUB_ENV 38 | 39 | - name: Confirm git commit SHA output 40 | run: echo ${{ env.COMMIT_SHORT_SHA }} 41 | 42 | # Build and push the Docker image for both amd64 and arm64 architectures 43 | # The image is tagged as "latest" and with the short commit SHA. 44 | # It also includes a label pointing to the associated repository. 45 | - name: Build and Push Docker Image 46 | uses: docker/build-push-action@v4 47 | with: 48 | context: . 49 | file: Dockerfile-service 50 | push: true 51 | platforms: linux/amd64,linux/arm64 52 | tags: | 53 | ghcr.io/${{ github.repository }}:latest 54 | ghcr.io/${{ github.repository }}:g${{ env.COMMIT_SHORT_SHA }} 55 | labels: | 56 | org.opencontainers.image.source=https://github.com/${{ github.repository }} 57 | org.opencontainers.image.licenses=Apache-2.0 58 | -------------------------------------------------------------------------------- /app/src/c/util/time.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "time.h" 18 | #include 19 | 20 | void format_time(char *buffer, size_t size, struct tm *time) { 21 | if (clock_is_24h_style()) { 22 | strftime(buffer, size, "%H:%M", time); 23 | } else if (time->tm_hour > 0 && time->tm_hour < 10) { 24 | snprintf(buffer, size, "%d", time->tm_hour); 25 | strftime(buffer + 1, size - 1, ":%M", time); 26 | } else if (time->tm_hour > 12 && time->tm_hour < 22) { 27 | snprintf(buffer, 2, "%d", time->tm_hour - 12); 28 | strftime(buffer + 1, size - 1, ":%M", time); 29 | } else { 30 | strftime(buffer, size, "%I:%M", time); 31 | } 32 | } 33 | 34 | void format_time_ampm(char *buffer, size_t size, struct tm *time) { 35 | format_time(buffer, size, time); 36 | if (!clock_is_24h_style()) { 37 | size_t length = strlen(buffer); 38 | strftime(buffer + length, size - length, " %p", time); 39 | } 40 | } 41 | 42 | void format_datetime(char *buffer, size_t size, time_t time) { 43 | struct tm *timeinfo = localtime(&time); 44 | char* time_pointer = buffer; 45 | if (time < time_start_of_today() + 24*60*60) { 46 | strncpy(buffer, "Today, ", size); 47 | } else if (time < time_start_of_today() + 48*60*60) { 48 | strncpy(buffer, "Tomorrow, ", size); 49 | } else { 50 | strftime(buffer, size, "%a, %b %d, ", timeinfo); 51 | } 52 | time_pointer += strlen(buffer); 53 | size_t remaining_buffer = sizeof(buffer) - (time_pointer - buffer); 54 | format_time_ampm(time_pointer, remaining_buffer, timeinfo); 55 | } -------------------------------------------------------------------------------- /app/src/c/util/memory/malloc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "malloc.h" 18 | #include "pressure.h" 19 | #include "../logging.h" 20 | 21 | #include 22 | 23 | void *bmalloc(size_t size) { 24 | register uintptr_t lr __asm("lr"); 25 | const uintptr_t saved_lr = lr; 26 | int heap_size = heap_bytes_free(); 27 | BOBBY_LOG(APP_LOG_LEVEL_DEBUG, "malloc request: %d; free: %d", size, heap_size); 28 | while (true) { 29 | heap_size = heap_bytes_free(); 30 | if (heap_bytes_free() > 750) { 31 | void *ptr = malloc(size); 32 | if (ptr) { 33 | BOBBY_LOG(APP_LOG_LEVEL_DEBUG_VERBOSE, "malloc returned %p for caller %p", ptr, saved_lr); 34 | return ptr; 35 | } 36 | BOBBY_LOG(APP_LOG_LEVEL_WARNING, "Out of memory! Need to allocate %d bytes; %d bytes free.", size, heap_size); 37 | } else { 38 | BOBBY_LOG(APP_LOG_LEVEL_WARNING, "Low memory (%d byte free); trying to free some before allocating %d bytes.", heap_size, size); 39 | } 40 | if (!memory_pressure_try_free()) { 41 | BOBBY_LOG(APP_LOG_LEVEL_ERROR, "Failed to allocate memory: couldn't free enough heap."); 42 | void *tried = malloc(size); 43 | if (tried) { 44 | BOBBY_LOG(APP_LOG_LEVEL_DEBUG, "malloc returned %p for caller %p", tried, saved_lr); 45 | } 46 | return tried; 47 | } 48 | int new_heap_size = heap_bytes_free(); 49 | BOBBY_LOG(APP_LOG_LEVEL_INFO, "Freed %d bytes, heap size is now %d. Retrying allocation of %d bytes.", heap_size - new_heap_size, new_heap_size, size); 50 | } 51 | return NULL; 52 | } 53 | -------------------------------------------------------------------------------- /app/src/pkjs/lib/image_transfer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | var messageQueue = require('./message_queue').Queue; 18 | 19 | var CHUNK_SIZE = 200; 20 | 21 | function ImageManager() { 22 | this.nextImageId = 1; 23 | } 24 | 25 | ImageManager.prototype.sendImage = function(width, height, /* number[]*/ imageData) { 26 | var imageId = this.nextImageId++; 27 | var chunks = Math.ceil(imageData.length / CHUNK_SIZE); 28 | messageQueue.enqueue({ 29 | IMAGE_ID: imageId, 30 | IMAGE_START_BYTE_SIZE: imageData.length, 31 | IMAGE_WIDTH: width, 32 | IMAGE_HEIGHT: height, 33 | }); 34 | console.log("Sending image " + imageId + " with size " + imageData.length + " bytes"); 35 | setTimeout(function() { 36 | for (var i = 0; i < chunks; i++) { 37 | var start = i * CHUNK_SIZE; 38 | var end = Math.min(start + CHUNK_SIZE, imageData.length); 39 | var chunk = imageData.slice(start, end); 40 | console.log(chunk.length); 41 | console.log(JSON.stringify(chunk)); 42 | messageQueue.enqueue({ 43 | IMAGE_ID: imageId, 44 | IMAGE_CHUNK_OFFSET: start, 45 | IMAGE_CHUNK_DATA: chunk, 46 | }); 47 | } 48 | messageQueue.enqueue({ 49 | IMAGE_ID: imageId, 50 | IMAGE_COMPLETE: 1, 51 | }); 52 | console.log("Enqueued " + chunks + " chunks for image " + imageId); 53 | }, 50); 54 | return imageId; 55 | } 56 | 57 | exports.ImageManager = ImageManager; 58 | exports.sharedManager = new ImageManager(); 59 | -------------------------------------------------------------------------------- /service/assistant/util/redact/honeycomb.go: -------------------------------------------------------------------------------- 1 | package redact 2 | 3 | import ( 4 | "golang.org/x/exp/slices" 5 | "net/url" 6 | "regexp" 7 | ) 8 | 9 | var sensitiveQueryParams = []string{ 10 | "access_token", // our mapbox API key 11 | "prompt", // the user's prompt 12 | "threadId", // the thread the prompt belongs to, if any 13 | "lon", "lat", // user's location as sent to us 14 | "geocode", // the user's location as sent to the weather API 15 | "apiKey", // the API key for the weather service 16 | "proximity", // the target location for a POI lookup 17 | "tzOffset", // user's timezone offset as sent to us 18 | "token", // user's auth (timeline) token, identifies them uniquely. 19 | } 20 | var mapboxPathRegex = regexp.MustCompile(`^/geocoding/v5/mapbox\.places/.+?.json$`) 21 | 22 | func redactQuery(query string) string { 23 | values, err := url.ParseQuery(query) 24 | if err != nil { 25 | return "[parse error redacted for safety]" 26 | } 27 | newValues := url.Values{} 28 | for k, v := range values { 29 | if slices.Contains(sensitiveQueryParams, k) { 30 | newValues[k] = []string{"redacted"} 31 | } else { 32 | newValues[k] = v 33 | } 34 | } 35 | return newValues.Encode() 36 | } 37 | 38 | func cleanPath(path string) string { 39 | if mapboxPathRegex.MatchString(path) { 40 | return "/geocoding/v5/mapbox.places/[place].json" 41 | } 42 | return path 43 | } 44 | 45 | func cleanUrl(u string) string { 46 | parsedUrl, err := url.Parse(u) 47 | if err != nil { 48 | return "[parse error redacted for safety]" 49 | } 50 | parsedUrl.Path = cleanPath(parsedUrl.Path) 51 | parsedUrl.RawQuery = redactQuery(parsedUrl.RawQuery) 52 | return parsedUrl.String() 53 | } 54 | 55 | func CleanHoneycomb(data map[string]any) { 56 | // HTTP requests contain a bunch of PII. We don't want to send that to Honeycomb. 57 | if query, ok := data["request.query"]; ok { 58 | if queryStr, ok := query.(string); ok { 59 | data["request.query"] = redactQuery(queryStr) 60 | } 61 | } 62 | if path, ok := data["request.path"]; ok { 63 | if pathStr, ok := path.(string); ok { 64 | data["request.path"] = cleanPath(pathStr) 65 | } 66 | } 67 | if u, ok := data["request.url"]; ok { 68 | if urlStr, ok := u.(string); ok { 69 | data["request.url"] = cleanUrl(urlStr) 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/pkjs/actions/timeline.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // The timeline public URL root 18 | var API_URL_ROOT = 'https://timeline-api.rebble.io/'; 19 | 20 | function timelineRequest(pin, type, topics, apiKey, callback) { 21 | // User or shared? 22 | var url = API_URL_ROOT + 'v1/' + ((topics != null) ? 'shared/' : 'user/') + 'pins/' + pin.id; 23 | 24 | // Create XHR 25 | var xhr = new XMLHttpRequest(); 26 | xhr.onload = function () { 27 | console.log('timeline: response received: ' + this.responseText); 28 | if (callback) { 29 | callback(this.responseText); 30 | } 31 | }; 32 | xhr.open(type, url); 33 | 34 | // Set headers 35 | xhr.setRequestHeader('Content-Type', 'application/json'); 36 | if(topics != null) { 37 | xhr.setRequestHeader('X-Pin-Topics', '' + topics.join(',')); 38 | xhr.setRequestHeader('X-API-Key', '' + apiKey); 39 | } 40 | 41 | // Get token 42 | Pebble.getTimelineToken(function(token) { 43 | // Add headers 44 | xhr.setRequestHeader('X-User-Token', '' + token); 45 | 46 | // Send 47 | xhr.send(JSON.stringify(pin)); 48 | console.log('timeline: request sent.'); 49 | }, function(error) { console.log('timeline: error getting timeline token: ' + error); }); 50 | } 51 | 52 | // Insert a pin into the timeline 53 | exports.insertUserPin = function(pin, callback) { 54 | timelineRequest(pin, 'PUT', null, null, callback); 55 | }; 56 | 57 | // Delete a pin from the timeline 58 | exports.deleteUserPin = function(pinId, callback) { 59 | var pin = { "id": pinId }; 60 | timelineRequest(pin, 'DELETE', null, null, callback); 61 | }; 62 | -------------------------------------------------------------------------------- /tools/pdc-sequencer/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "flag" 19 | "fmt" 20 | "github.com/pebble-dev/bobby-assistant/tools/pdc-sequencer/sequence" 21 | "os" 22 | "strconv" 23 | "strings" 24 | ) 25 | 26 | type Options struct { 27 | Pattern string 28 | Output string 29 | DelayMs uint 30 | PlayCount uint 31 | } 32 | 33 | func parseFlags() Options { 34 | o := Options{} 35 | flag.StringVar(&o.Pattern, "pattern", ".", "Pattern containing PDC files to join") 36 | flag.StringVar(&o.Output, "output", "", "Output filename to write to") 37 | flag.UintVar(&o.DelayMs, "delay", 33, "Delay between playing each frame in milliseconds") 38 | var playCount string 39 | flag.StringVar(&playCount, "playcount", "1", "Number of times to play the animation, or 'forever'") 40 | flag.Parse() 41 | 42 | if playCount == "forever" { 43 | o.PlayCount = 0xFFFF 44 | } else { 45 | count, err := strconv.ParseUint(playCount, 10, 16) 46 | if err != nil { 47 | fmt.Fprintf(os.Stderr, "Invalid playcount: %q\n", playCount) 48 | os.Exit(1) 49 | } 50 | o.PlayCount = uint(count) 51 | } 52 | 53 | if o.Output == "" { 54 | fmt.Fprintf(os.Stderr, "Output filename must be specified\n") 55 | os.Exit(1) 56 | } 57 | return o 58 | } 59 | 60 | func main() { 61 | options := parseFlags() 62 | if strings.HasPrefix(options.Pattern, "~/") { 63 | options.Pattern = strings.Replace(options.Pattern, "~", os.Getenv("HOME"), 1) 64 | } 65 | 66 | err := sequence.ConstructSequence(options.Pattern, options.Output, uint16(options.DelayMs), uint16(options.PlayCount)) 67 | if err != nil { 68 | fmt.Fprintf(os.Stderr, "Failed to construct sequence: %v\n", err) 69 | os.Exit(1) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /service/assistant/quota/user.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package quota 16 | 17 | import ( 18 | "context" 19 | "encoding/json" 20 | "fmt" 21 | "github.com/honeycombio/beeline-go" 22 | "io" 23 | "log" 24 | "net/http" 25 | "net/url" 26 | "strings" 27 | 28 | "github.com/pebble-dev/bobby-assistant/service/assistant/config" 29 | ) 30 | 31 | type UserInfo struct { 32 | UserId int `json:"user_id"` 33 | HasSubscription bool `json:"has_subscription"` 34 | } 35 | 36 | func GetUserInfo(ctx context.Context, token string) (*UserInfo, error) { 37 | ctx, span := beeline.StartSpan(ctx, "get_user_info") 38 | defer span.Send() 39 | if token == "" { 40 | return nil, fmt.Errorf("no token provided") 41 | } 42 | values := &url.Values{} 43 | values.Set("token", token) 44 | s := values.Encode() 45 | r := strings.NewReader(s) 46 | req, err := http.NewRequestWithContext(ctx, http.MethodPost, config.GetConfig().UserIdentificationURL, r) 47 | if err != nil { 48 | log.Printf("Error creating user id request: %v", err) 49 | return nil, err 50 | } 51 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 52 | resp, err := http.DefaultClient.Do(req) 53 | if err != nil { 54 | log.Printf("Error getting user id: %v", err) 55 | return nil, err 56 | } 57 | defer resp.Body.Close() 58 | if resp.StatusCode != 200 { 59 | log.Printf("Error getting user id: %v", resp.Status) 60 | result, _ := io.ReadAll(resp.Body) 61 | return nil, fmt.Errorf("error from user id service: %s", string(result)) 62 | } 63 | var u UserInfo 64 | if err := json.NewDecoder(resp.Body).Decode(&u); err != nil { 65 | log.Printf("Error decoding user id response: %v", err) 66 | return nil, err 67 | } 68 | return &u, nil 69 | } 70 | -------------------------------------------------------------------------------- /app/wscript: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | import os.path 15 | import sys 16 | 17 | top = '.' 18 | out = 'build' 19 | 20 | 21 | def options(ctx): 22 | ctx.load('pebble_sdk') 23 | 24 | 25 | def configure(ctx): 26 | ctx.load('pebble_sdk') 27 | 28 | 29 | def build(ctx): 30 | ctx.load('pebble_sdk') 31 | 32 | build_worker = os.path.exists('worker_src') 33 | binaries = [] 34 | 35 | cached_env = ctx.env 36 | for platform in ctx.env.TARGET_PLATFORMS: 37 | ctx.env = ctx.all_envs[platform] 38 | ctx.set_group(ctx.env.PLATFORM_NAME) 39 | app_elf = '{}/pebble-app.elf'.format(ctx.env.BUILD_DIR) 40 | 41 | ctx.pbl_build( 42 | source=ctx.path.ant_glob(['src/c/**/*.c']), 43 | target=app_elf, 44 | bin_type='app') 45 | 46 | if build_worker: 47 | worker_elf = '{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR) 48 | binaries.append({'platform': platform, 'app_elf': app_elf, 'worker_elf': worker_elf}) 49 | ctx.pbl_build(source=ctx.path.ant_glob('worker_src/c/**/*.c'), 50 | target=worker_elf, 51 | bin_type='worker') 52 | else: 53 | binaries.append({'platform': platform, 'app_elf': app_elf}) 54 | ctx.env = cached_env 55 | 56 | ctx(features='subst', 57 | source='package.json', 58 | target='js/package.json', 59 | is_copy=True) 60 | 61 | ctx.set_group('bundle') 62 | ctx.pbl_bundle(binaries=binaries, 63 | js=ctx.path.ant_glob(['src/pkjs/**/*.js', 64 | 'src/pkjs/**/*.json', 65 | 'src/common/**/*.js']), 66 | js_entry_file='src/pkjs/index.js') 67 | -------------------------------------------------------------------------------- /service/assistant/config/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package config 16 | 17 | import ( 18 | "log" 19 | "os" 20 | 21 | "github.com/joho/godotenv" 22 | ) 23 | 24 | // TODO: something reasonable. 25 | 26 | type Config struct { 27 | BaseURL string 28 | GeminiKey string 29 | MapboxKey string 30 | IBMKey string 31 | ExchangeRateApiKey string 32 | RedisURL string 33 | UserIdentificationURL string 34 | HoneycombKey string 35 | DiscordFeedbackURL string 36 | GoogleMapsStaticKey string 37 | GoogleMapsStaticSecret string 38 | GoogleMapsStaticMapId string 39 | } 40 | 41 | var c Config 42 | 43 | func GetConfig() *Config { 44 | return &c 45 | } 46 | 47 | func init() { 48 | // Load .env file if it exists 49 | if err := godotenv.Load(); err != nil { 50 | // Only log if the file exists but couldn't be loaded 51 | if !os.IsNotExist(err) { 52 | log.Printf("Error loading .env file: %v", err) 53 | } 54 | } 55 | 56 | c = Config{ 57 | BaseURL: os.Getenv("BASE_URL"), 58 | GeminiKey: os.Getenv("GEMINI_KEY"), 59 | MapboxKey: os.Getenv("MAPBOX_KEY"), 60 | IBMKey: os.Getenv("IBM_KEY"), 61 | ExchangeRateApiKey: os.Getenv("EXCHANGE_RATE_API_KEY"), 62 | RedisURL: os.Getenv("REDIS_URL"), 63 | UserIdentificationURL: os.Getenv("USER_IDENTIFICATION_URL"), 64 | HoneycombKey: os.Getenv("HONEYCOMB_KEY"), 65 | DiscordFeedbackURL: os.Getenv("DISCORD_FEEDBACK_URL"), 66 | GoogleMapsStaticKey: os.Getenv("GOOGLE_MAPS_STATIC_KEY"), 67 | GoogleMapsStaticSecret: os.Getenv("GOOGLE_MAPS_STATIC_SECRET"), 68 | GoogleMapsStaticMapId: os.Getenv("GOOGLE_MAPS_STATIC_MAP_ID"), 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/c/util/perimeter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // These definitions are all copied from the firmware. They aren't exposed in the public SDK. 18 | 19 | #ifndef PERIMETER_H 20 | #define PERIMETER_H 21 | 22 | #include 23 | 24 | typedef struct GPerimeter GPerimeter; 25 | 26 | typedef struct GRangeHorizontal { 27 | int16_t origin_x; 28 | int16_t size_w; 29 | } GRangeHorizontal; 30 | 31 | typedef struct GRangeVertical { 32 | int16_t origin_y; 33 | int16_t size_h; 34 | } GRangeVertical; 35 | 36 | typedef GRangeHorizontal (*GPerimeterCallback)(const GPerimeter *perimeter, const GSize *ctx_size, 37 | GRangeVertical vertical_range, uint16_t inset); 38 | 39 | typedef struct GPerimeter { 40 | GPerimeterCallback callback; 41 | } GPerimeter; 42 | 43 | typedef struct { 44 | const GPerimeter *impl; 45 | uint8_t inset; 46 | } TextLayoutFlowDataPerimeter; 47 | 48 | typedef struct { 49 | GPoint origin_on_screen; 50 | GRangeVertical page_on_screen; 51 | } TextLayoutFlowDataPaging; 52 | 53 | typedef struct { 54 | TextLayoutFlowDataPerimeter perimeter; 55 | TextLayoutFlowDataPaging paging; 56 | } TextLayoutFlowData; 57 | 58 | struct GTextAttributes { 59 | //! Invalidate the cache if these parameters have changed 60 | uint32_t hash; 61 | GRect box; 62 | GFont font; 63 | GTextOverflowMode overflow_mode; 64 | GTextAlignment alignment; 65 | //! Cached parameters 66 | GSize max_used_size; //= menuReminders.length) return; 40 | 41 | var reminder = menuReminders[index]; 42 | Pebble.sendAppMessage({ 43 | 'REMINDER_TEXT': reminder.text, 44 | 'REMINDER_ID': reminder.id, 45 | 'REMINDER_TIME': Math.floor(reminder.time.getTime() / 1000) 46 | }, function() { 47 | // On success, send next reminder 48 | sendNextReminder(index + 1); 49 | }, function() { 50 | // On failure, retry this reminder 51 | console.error('Failed to send reminder:', reminder); 52 | sendNextReminder(index); 53 | }); 54 | } 55 | 56 | // Start sending reminders 57 | sendNextReminder(0); 58 | return true; 59 | } else if (data.REMINDER_DELETE) { 60 | var id = data.REMINDER_DELETE; 61 | try { 62 | reminders.deleteReminder(id); 63 | } catch (err) { 64 | console.error('Failed to delete reminder:', err); 65 | } 66 | return true; 67 | } 68 | return false; 69 | } 70 | 71 | module.exports = { 72 | handleReminderMessage: handleReminderMessage 73 | }; 74 | -------------------------------------------------------------------------------- /app/src/c/util/vector_layer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "vector_layer.h" 18 | #include 19 | 20 | #include "memory/sdk.h" 21 | 22 | typedef struct { 23 | GDrawCommandImage *vector; 24 | GColor background_color; 25 | } VectorLayerData; 26 | 27 | static void prv_layer_update(Layer *layer, GContext *ctx); 28 | 29 | VectorLayer* vector_layer_create(GRect frame) { 30 | Layer *layer = blayer_create_with_data(frame, sizeof(VectorLayerData)); 31 | VectorLayerData *vector_layer_data = layer_get_data(layer); 32 | vector_layer_data->vector = NULL; 33 | vector_layer_data->background_color = GColorClear; 34 | layer_set_update_proc(layer, prv_layer_update); 35 | return layer; 36 | } 37 | 38 | void vector_layer_destroy(VectorLayer *layer) { 39 | layer_destroy(vector_layer_get_layer(layer)); 40 | } 41 | 42 | Layer* vector_layer_get_layer(VectorLayer *layer) { 43 | return layer; 44 | } 45 | 46 | void vector_layer_set_vector(VectorLayer *layer, GDrawCommandImage *image) { 47 | VectorLayerData *data = layer_get_data(layer); 48 | data->vector = image; 49 | layer_mark_dirty(layer); 50 | } 51 | 52 | GDrawCommandImage* vector_layer_get_vector(VectorLayer *layer) { 53 | VectorLayerData *data = layer_get_data(layer); 54 | return data->vector; 55 | } 56 | 57 | void vector_layer_set_background_color(VectorLayer *layer, GColor color) { 58 | VectorLayerData *data = layer_get_data(layer); 59 | data->background_color = color; 60 | } 61 | 62 | 63 | static void prv_layer_update(Layer *layer, GContext *ctx) { 64 | VectorLayerData *data = layer_get_data(layer); 65 | GRect bounds = layer_get_bounds(layer); 66 | if (!gcolor_equal(data->background_color, GColorClear)) { 67 | graphics_context_set_fill_color(ctx, data->background_color); 68 | graphics_fill_rect(ctx, bounds, 0, GCornerNone); 69 | } 70 | gdraw_command_image_draw(ctx, data->vector, GPoint(0, 0)); 71 | } 72 | -------------------------------------------------------------------------------- /app/src/c/vibes/sad_vibe_score.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | #include "sad_vibe_score.h" 19 | #include "../util/memory/malloc.h" 20 | 21 | #include 22 | 23 | struct __attribute__((__packed__)) SadVibeScore { 24 | uint32_t pattern_duration; 25 | uint32_t repeat_delay_ms; 26 | uint16_t note_count; 27 | uint32_t *notes; 28 | }; 29 | 30 | SadVibeScore *s_active_vibe_score = NULL; 31 | AppTimer *s_timer = NULL; 32 | 33 | static void prv_vibe_timer_callback(void* context); 34 | 35 | SadVibeScore* sad_vibe_score_create_with_resource(uint32_t resource_id) { 36 | ResHandle res_handle = resource_get_handle(resource_id); 37 | SadVibeScore* score = bmalloc(sizeof(SadVibeScore)); 38 | resource_load_byte_range(res_handle, 0, (uint8_t*)score, 10); 39 | score->notes = bmalloc(score->note_count * sizeof(uint32_t)); 40 | resource_load_byte_range(res_handle, 10, (uint8_t*)score->notes, score->note_count * sizeof(uint32_t)); 41 | return score; 42 | } 43 | 44 | void sad_vibe_score_destroy(SadVibeScore* score) { 45 | if (s_active_vibe_score == score) { 46 | sad_vibe_score_stop(); 47 | } 48 | free(score->notes); 49 | free(score); 50 | } 51 | 52 | void sad_vibe_score_play(SadVibeScore* score) { 53 | sad_vibe_score_stop(); 54 | s_active_vibe_score = score; 55 | vibes_enqueue_custom_pattern((VibePattern) { 56 | .durations = score->notes, 57 | .num_segments = score->note_count, 58 | }); 59 | if (score->repeat_delay_ms) { 60 | s_timer = app_timer_register(score->pattern_duration + score->repeat_delay_ms, prv_vibe_timer_callback, NULL); 61 | } 62 | } 63 | 64 | void sad_vibe_score_stop() { 65 | if (s_active_vibe_score) { 66 | vibes_cancel(); 67 | s_active_vibe_score = NULL; 68 | } 69 | } 70 | 71 | static void prv_vibe_timer_callback(void* context) { 72 | if (s_active_vibe_score) { 73 | sad_vibe_score_play(s_active_vibe_score); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /service/assistant/functions/time.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "google.golang.org/genai" 7 | "strings" 8 | "time" 9 | 10 | "github.com/honeycombio/beeline-go" 11 | "github.com/pebble-dev/bobby-assistant/service/assistant/quota" 12 | ) 13 | 14 | type TimeResponse struct { 15 | Time string `json:"time"` 16 | } 17 | 18 | type GetTimeInput struct { 19 | // The timezone, e.g. 'America/Los_Angeles'. 20 | Timezone string `json:"timezone" jsonschema:"required"` 21 | // The number of seconds to add to the current time. 22 | Offset float64 `json:"offset"` 23 | } 24 | 25 | func init() { 26 | f := false 27 | registerFunction(Registration{ 28 | Definition: genai.FunctionDeclaration{ 29 | Name: "get_time_elsewhere", 30 | Description: "Get the current time in a given valid tzdb timezone. Not all cities have a tzdb entry - be sure to use one that exists. Call multiple times to find the time in multiple timezones.", 31 | Parameters: &genai.Schema{ 32 | Type: genai.TypeObject, 33 | Nullable: &f, 34 | Properties: map[string]*genai.Schema{ 35 | "timezone": { 36 | Type: genai.TypeString, 37 | Description: "The timezone, e.g. 'America/Los_Angeles'.", 38 | Nullable: &f, 39 | }, 40 | "offset": { 41 | Type: genai.TypeNumber, 42 | Description: "The number of seconds to add to the current time, if checking a different time. Omit or set to zero for current time.", 43 | Format: "double", 44 | }, 45 | }, 46 | Required: []string{"timezone"}, 47 | }, 48 | }, 49 | Fn: getTimeElsewhere, 50 | Thought: getTimeThought, 51 | InputType: GetTimeInput{}, 52 | }) 53 | } 54 | 55 | func getTimeThought(args any) string { 56 | arg := args.(*GetTimeInput) 57 | if arg.Timezone != "" { 58 | s := strings.Split(arg.Timezone, "/") 59 | place := strings.Replace(s[len(s)-1], "_", " ", -1) 60 | return "Checking the time in " + place 61 | } 62 | return "Checking the time" 63 | } 64 | 65 | func getTimeElsewhere(ctx context.Context, quotaTracker *quota.Tracker, args any) any { 66 | ctx, span := beeline.StartSpan(ctx, "get_time_elsewhere") 67 | defer span.Send() 68 | arg := args.(*GetTimeInput) 69 | utc := time.Now().UTC().Add(time.Duration(arg.Offset) * time.Second) 70 | loc, err := time.LoadLocation(arg.Timezone) 71 | if err != nil { 72 | return Error{fmt.Sprintf("The timezone %q is not valid", arg.Timezone)} 73 | } 74 | utc.In(loc) 75 | return TimeResponse{utc.In(loc).Format(time.RFC1123)} 76 | } 77 | -------------------------------------------------------------------------------- /tools/json2sadvibe/json2sadvibe.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright 2025 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import argparse 17 | import os 18 | import json 19 | 20 | from libpebble2.protocol.base import PebblePacket 21 | from libpebble2.protocol.base.types import * 22 | 23 | class Note(PebblePacket): 24 | time_on = Uint32() 25 | time_off = Uint32() 26 | 27 | class DisappointingVibeScore(PebblePacket): 28 | class Meta: 29 | endianness = '<' 30 | pattern_duration = Uint32() 31 | repeat_delay_ms = Uint32() 32 | note_count = Uint16() 33 | pattern = FixedList(Note) 34 | 35 | def serialize(json_data): 36 | note_dictionary = {note['id']: note for note in json_data['notes']} 37 | 38 | pattern = [ 39 | Note(time_on=note_dictionary[x]['vibe_duration_ms'], time_off=note_dictionary[x]['brake_duration_ms']) 40 | if note_dictionary[x]['strength'] > 50 41 | else Note(time_on=0, time_off=note_dictionary[x]['vibe_duration_ms'] + note_dictionary[x]['brake_duration_ms']) 42 | for x in json_data['pattern']] 43 | 44 | score = DisappointingVibeScore( 45 | pattern_duration=sum(x.time_on+x.time_off for x in pattern), 46 | repeat_delay_ms=json_data.get('repeat_delay_ms', 0), 47 | pattern=pattern, 48 | note_count=len(pattern)*2 49 | ) 50 | 51 | # do the dirty work 52 | return score.serialise() 53 | 54 | 55 | def convert(file_path, out_path=None): 56 | if out_path is None: 57 | out_path = os.path.splitext(file_path)[0] + ".vibe" 58 | 59 | with open(out_path, 'wb') as o: 60 | convert_to_file(file_path, o) 61 | 62 | 63 | def convert_to_file(input_file_path, output_file): 64 | with open(input_file_path, 'r') as f: 65 | data = json.loads(f.read()) 66 | 67 | output_file.write(serialize(data)) 68 | 69 | 70 | if __name__ == '__main__': 71 | parser = argparse.ArgumentParser() 72 | parser.add_argument('json_file') 73 | args = parser.parse_args() 74 | 75 | convert(args.json_file) 76 | -------------------------------------------------------------------------------- /app/src/pkjs/actions/reminders.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | var reminders = require('../lib/reminders'); 18 | 19 | exports.setReminder = function(session, message, callback) { 20 | var when = message['time']; 21 | var what = message['what']; 22 | 23 | try { 24 | reminders.addReminder(what, when); 25 | var unixTime = (new Date(when)).getTime() / 1000; 26 | session.enqueue({ACTION_REMINDER_WAS_SET: unixTime}); 27 | 28 | if (unixTime < (new Date()).getTime() / 1000 + 3600) { 29 | callback({"warning": "Your reminder was set. It is **critical** you warn the user: Due to timeline delays, reminders set in the near future may not appear on time."}); 30 | } else { 31 | callback({"status": "ok"}); 32 | } 33 | } catch (err) { 34 | callback({"error": "Failed to set reminder: " + err.message}); 35 | } 36 | }; 37 | 38 | exports.getReminders = function(session, message, callback) { 39 | try { 40 | var allReminders = reminders.getAllReminders(); 41 | for (var i = 0; i < allReminders.length; i++) { 42 | var reminder = allReminders[i]; 43 | reminder.time = new Date(reminder.time).toString(); 44 | } 45 | callback({ 46 | "status": "ok", 47 | "reminders": allReminders 48 | }); 49 | } catch (err) { 50 | callback({"error": "Failed to get reminders: " + err.message}); 51 | } 52 | }; 53 | 54 | exports.deleteReminder = function(session, message, callback) { 55 | var reminderId = message['id']; 56 | if (!reminderId) { 57 | callback({"error": "No reminder ID provided"}); 58 | } 59 | 60 | try { 61 | var success = reminders.deleteReminder(reminderId); 62 | if (!success) { 63 | callback({"error": "Reminder not found"}); 64 | } 65 | session.enqueue({ACTION_REMINDER_DELETED: 1}); 66 | callback({"status": "ok"}); 67 | } catch (err) { 68 | callback({"error": "Failed to delete reminder: " + err.message}); 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /tools/pdc-sequencer/pdc/readwrite_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package pdc 16 | 17 | import ( 18 | "bytes" 19 | "encoding/hex" 20 | "fmt" 21 | "io" 22 | "os" 23 | "slices" 24 | "testing" 25 | ) 26 | 27 | func TestRoundTripImage(t *testing.T) { 28 | f, err := os.Open("testdata/cog.pdci") 29 | if err != nil { 30 | t.Fatal("failed to open testdata/cog.pdci") 31 | } 32 | content, err := io.ReadAll(f) 33 | _ = f.Close() 34 | if err != nil { 35 | t.Fatalf("failed to read testdata/cog.pdci: %v", err) 36 | } 37 | r := bytes.NewReader(content) 38 | pdc, err := ParseImageFile(r) 39 | if err != nil { 40 | t.Fatalf("failed to parse image: %v", err) 41 | } 42 | if pdc == nil { 43 | t.Fatal("nil image") 44 | } 45 | fmt.Printf("Parsed image: %+v\n", pdc) 46 | w := new(bytes.Buffer) 47 | if err := WriteImageFile(w, pdc); err != nil { 48 | t.Fatalf("failed to write image: %v", err) 49 | } 50 | if !slices.Equal(content, w.Bytes()) { 51 | t.Fatalf("round trip failed: content does not match.\no: %s\nr: %s", hex.EncodeToString(content), hex.EncodeToString(w.Bytes())) 52 | } 53 | } 54 | 55 | func TestRoundTripSequence(t *testing.T) { 56 | f, err := os.Open("testdata/sent.pdcs") 57 | if err != nil { 58 | t.Fatal("failed to open testdata/sent.pdcs") 59 | } 60 | content, err := io.ReadAll(f) 61 | _ = f.Close() 62 | if err != nil { 63 | t.Fatalf("failed to read testdata/csent.pdcs: %v", err) 64 | } 65 | r := bytes.NewReader(content) 66 | pdc, err := ParseImageSequence(r) 67 | if err != nil { 68 | t.Fatalf("failed to parse image: %v", err) 69 | } 70 | if pdc == nil { 71 | t.Fatal("nil image") 72 | } 73 | fmt.Printf("Parsed image: %+v\n", pdc) 74 | w := new(bytes.Buffer) 75 | if err := WriteImageSequence(w, pdc); err != nil { 76 | t.Fatalf("failed to write image: %v", err) 77 | } 78 | if !slices.Equal(content, w.Bytes()) { 79 | t.Fatalf("round trip failed: content does not match.\no: %s\nr: %s", hex.EncodeToString(content), hex.EncodeToString(w.Bytes())) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /service/assistant/persistence/persistence.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package persistence 16 | 17 | import ( 18 | "context" 19 | "encoding/json" 20 | "github.com/google/uuid" 21 | "github.com/honeycombio/beeline-go" 22 | "github.com/pebble-dev/bobby-assistant/service/assistant/util" 23 | "github.com/redis/go-redis/v9" 24 | "google.golang.org/genai" 25 | "time" 26 | ) 27 | 28 | type SerializedMessage struct { 29 | Role string `json:"role"` 30 | Content string `json:"content"` 31 | FunctionCall *genai.FunctionCall `json:"functionCall,omitempty"` 32 | FunctionResponse *genai.FunctionResponse `json:"functionResponse,omitempty"` 33 | } 34 | 35 | type StoredContext struct { 36 | PoiQuery *util.POIQuery `json:"poiQuery"` 37 | POIs []util.POI `json:"pois"` 38 | LastRoute map[string]any `json:"lastRoute"` 39 | } 40 | 41 | type ThreadContext struct { 42 | ThreadId uuid.UUID `json:"threadId"` 43 | Messages []SerializedMessage `json:"messages"` 44 | ContextStorage StoredContext `json:"contextStorage"` 45 | } 46 | 47 | func NewContext() *ThreadContext { 48 | return &ThreadContext{} 49 | } 50 | 51 | func LoadThread(ctx context.Context, r *redis.Client, id string) (*ThreadContext, error) { 52 | ctx, span := beeline.StartSpan(ctx, "load_thread") 53 | defer span.Send() 54 | j, err := r.Get(ctx, "thread:"+id).Result() 55 | if err != nil { 56 | return nil, err 57 | } 58 | var threadContext ThreadContext 59 | if err := json.Unmarshal([]byte(j), &threadContext); err != nil { 60 | return nil, err 61 | } 62 | return &threadContext, nil 63 | } 64 | 65 | func StoreThread(ctx context.Context, r *redis.Client, thread *ThreadContext) error { 66 | ctx, span := beeline.StartSpan(ctx, "store_thread") 67 | defer span.Send() 68 | j, err := json.Marshal(thread) 69 | if err != nil { 70 | span.AddField("error", err) 71 | return err 72 | } 73 | r.Set(ctx, "thread:"+thread.ThreadId.String(), j, 10*time.Minute) 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /service/assistant/functions/locations.go: -------------------------------------------------------------------------------- 1 | package functions 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/pebble-dev/bobby-assistant/service/assistant/query" 7 | "github.com/pebble-dev/bobby-assistant/service/assistant/util/mapbox" 8 | "github.com/umahmood/haversine" 9 | "google.golang.org/genai" 10 | 11 | "github.com/honeycombio/beeline-go" 12 | "github.com/pebble-dev/bobby-assistant/service/assistant/quota" 13 | ) 14 | 15 | type LocationResponse struct { 16 | Latitude float64 `json:"latitude"` 17 | Longitude float64 `json:"longitude"` 18 | DistanceKilometers float64 `json:"distance_meters,omitempty"` 19 | DistanceMiles float64 `json:"distance_miles,omitempty"` 20 | } 21 | 22 | type GetLocationInput struct { 23 | // The name of a place to locate, e.g. "San Francisco, CA, USA" or "Paris, France". 24 | PlaceName string `json:"place_name"` 25 | } 26 | 27 | func init() { 28 | f := false 29 | registerFunction(Registration{ 30 | Definition: genai.FunctionDeclaration{ 31 | Name: "get_location", 32 | Description: "Get the latitude and longitude of a given location. If the user's location is available, also provides the distance from the user.", 33 | Parameters: &genai.Schema{ 34 | Type: genai.TypeObject, 35 | Nullable: &f, 36 | Properties: map[string]*genai.Schema{ 37 | "place_name": { 38 | Type: genai.TypeString, 39 | Description: `The name of a place to locate, e.g. "San Francisco, CA, USA" or "Paris, France".`, 40 | Nullable: &f, 41 | }, 42 | }, 43 | Required: []string{"place_name"}, 44 | }, 45 | }, 46 | Fn: getLocationImpl, 47 | Thought: getLocationThought, 48 | InputType: GetLocationInput{}, 49 | }) 50 | } 51 | 52 | func getLocationThought(args any) string { 53 | arg := args.(*GetLocationInput) 54 | return fmt.Sprintf("Locating %q", arg.PlaceName) 55 | } 56 | 57 | func getLocationImpl(ctx context.Context, quotaTracker *quota.Tracker, args any) any { 58 | ctx, span := beeline.StartSpan(ctx, "get_location") 59 | defer span.Send() 60 | arg := args.(*GetLocationInput) 61 | location, err := mapbox.GeocodeWithContext(ctx, arg.PlaceName) 62 | if err != nil { 63 | return fmt.Errorf("failed to geocode %q: %w", arg.PlaceName, err) 64 | } 65 | userLocation := query.LocationFromContext(ctx) 66 | lr := LocationResponse{ 67 | Latitude: location.Lat, 68 | Longitude: location.Lon, 69 | } 70 | if userLocation != nil { 71 | uh := haversine.Coord{Lat: userLocation.Lat, Lon: userLocation.Lon} 72 | lh := haversine.Coord{Lat: location.Lat, Lon: location.Lon} 73 | lr.DistanceMiles, lr.DistanceKilometers = haversine.Distance(uh, lh) 74 | } 75 | return lr 76 | } 77 | -------------------------------------------------------------------------------- /app/src/pkjs/widgets/map.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | var imageManager = require('../lib/image_transfer').sharedManager; 18 | 19 | var atob = window.atob; 20 | 21 | // We don't get atob on iOS, so provide our own implementation. 22 | // This code was generated by Google Search, in response to the query "atob in pure javascript". 23 | // It seems to work. 24 | if (!atob) { 25 | atob = function(encoded) { 26 | const base64 = 27 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 28 | var str = ""; 29 | var bytes = 0; 30 | var bits = 0; 31 | for (var i = 0; i < encoded.length; i++) { 32 | const char = encoded.charAt(i); 33 | if (char === "=") { 34 | break; 35 | } 36 | var value = base64.indexOf(char); 37 | if (value < 0) { 38 | continue; 39 | } 40 | bytes = (bytes << 6) | value; 41 | bits += 6; 42 | if (bits >= 8) { 43 | str += String.fromCharCode((bytes >>> (bits - 8)) & 255); 44 | bits -= 8; 45 | } 46 | } 47 | return str; 48 | } 49 | } 50 | 51 | exports.map = function(session, params) { 52 | console.log(JSON.stringify(params)); 53 | var base64pbi = params['image']; 54 | var width = params['width']; 55 | var height = params['height']; 56 | var pbi = atob(base64pbi); 57 | var imageData = new Array(pbi.length); 58 | for (var i = 0; i < pbi.length; i++) { 59 | imageData[i] = pbi.charCodeAt(i); 60 | } 61 | var imageId = imageManager.sendImage(width, height, imageData); 62 | var userLocation = 0; 63 | if (params['user_location_x'] && params['user_location_y']) { 64 | userLocation = (params['user_location_x'] << 16) | params['user_location_y']; 65 | } 66 | var message = { 67 | MAP_WIDGET: 1, 68 | MAP_WIDGET_IMAGE_ID: imageId, 69 | MAP_WIDGET_USER_LOCATION: userLocation 70 | }; 71 | console.log(JSON.stringify(message)); 72 | session.enqueue(message); 73 | } 74 | -------------------------------------------------------------------------------- /tools/pdc-sequencer/pdc/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package pdc 16 | 17 | type Point struct { 18 | X, Y int16 19 | } 20 | 21 | type ViewBox struct { 22 | Width, Height uint16 23 | } 24 | 25 | const ( 26 | DrawCommandTypeInvalid = 0 27 | DrawCommandTypePath = 1 28 | DrawCommandTypeCircle = 2 29 | DrawCommandTypePrecisePath = 3 30 | ) 31 | 32 | const ( 33 | DrawCommandFlagHidden = 1 << 0 34 | ) 35 | 36 | type DrawCommand struct { 37 | Type uint8 38 | Flags uint8 39 | StrokeColor uint8 40 | StrokeWidth uint8 41 | FillColor uint8 42 | Radius uint16 43 | Points []Point 44 | } 45 | 46 | type DrawCommandList struct { 47 | Commands []DrawCommand 48 | } 49 | 50 | type DrawCommandFrame struct { 51 | DurationMs uint16 52 | CommandList DrawCommandList 53 | } 54 | type DrawCommandImage struct { 55 | Version uint8 56 | Reserved uint8 57 | ViewBox ViewBox 58 | CommandList DrawCommandList 59 | } 60 | 61 | type DrawCommandSequence struct { 62 | Version uint8 63 | Reserved uint8 64 | ViewBox ViewBox 65 | PlayCount uint16 66 | Frames []DrawCommandFrame 67 | } 68 | 69 | func CommandListsEqual(a, b *DrawCommandList) bool { 70 | if len(a.Commands) != len(b.Commands) { 71 | return false 72 | } 73 | for i := range a.Commands { 74 | if !commandsEqual(&a.Commands[i], &b.Commands[i]) { 75 | return false 76 | } 77 | } 78 | return true 79 | } 80 | 81 | func commandsEqual(a, b *DrawCommand) bool { 82 | if a.Type != b.Type { 83 | return false 84 | } 85 | if a.Flags != b.Flags { 86 | return false 87 | } 88 | if a.StrokeColor != b.StrokeColor { 89 | return false 90 | } 91 | if a.StrokeWidth != b.StrokeWidth { 92 | return false 93 | } 94 | if a.FillColor != b.FillColor { 95 | return false 96 | } 97 | if a.Radius != b.Radius { 98 | return false 99 | } 100 | if len(a.Points) != len(b.Points) { 101 | return false 102 | } 103 | for i := range a.Points { 104 | if a.Points[i] != b.Points[i] { 105 | return false 106 | } 107 | } 108 | return true 109 | } 110 | -------------------------------------------------------------------------------- /app/src/c/converse/segments/widgets/weather_util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "weather_util.h" 18 | #include 19 | 20 | int weather_widget_get_medium_resource_for_condition(int condition) { 21 | switch (condition) { 22 | case 1: 23 | return RESOURCE_ID_WEATHER_MEDIUM_LIGHT_RAIN; 24 | case 2: 25 | return RESOURCE_ID_WEATHER_MEDIUM_HEAVY_RAIN; 26 | case 3: 27 | return RESOURCE_ID_WEATHER_MEDIUM_LIGHT_SNOW; 28 | case 4: 29 | return RESOURCE_ID_WEATHER_MEDIUM_HEAVY_SNOW; 30 | case 5: 31 | return RESOURCE_ID_WEATHER_MEDIUM_CLOUDY; 32 | case 6: 33 | return RESOURCE_ID_WEATHER_MEDIUM_GENERIC; 34 | case 7: 35 | return RESOURCE_ID_WEATHER_MEDIUM_PARTLY_CLOUDY; 36 | case 8: 37 | return RESOURCE_ID_WEATHER_MEDIUM_SUNNY; 38 | default: 39 | return RESOURCE_ID_WEATHER_MEDIUM_GENERIC; 40 | } 41 | } 42 | 43 | int weather_widget_get_small_resource_for_condition(int condition) { 44 | switch (condition) { 45 | case 1: 46 | return RESOURCE_ID_WEATHER_SMALL_LIGHT_RAIN; 47 | case 2: 48 | return RESOURCE_ID_WEATHER_SMALL_HEAVY_RAIN; 49 | case 3: 50 | return RESOURCE_ID_WEATHER_SMALL_LIGHT_SNOW; 51 | case 4: 52 | return RESOURCE_ID_WEATHER_SMALL_HEAVY_SNOW; 53 | case 5: 54 | return RESOURCE_ID_WEATHER_SMALL_CLOUDY; 55 | case 6: 56 | return RESOURCE_ID_WEATHER_SMALL_GENERIC; 57 | case 7: 58 | return RESOURCE_ID_WEATHER_SMALL_PARTLY_CLOUDY; 59 | case 8: 60 | return RESOURCE_ID_WEATHER_SMALL_SUNNY; 61 | default: 62 | return RESOURCE_ID_WEATHER_SMALL_GENERIC; 63 | } 64 | } 65 | 66 | GColor weather_widget_get_colour_for_condition(int condition) { 67 | switch (condition) { 68 | case 1: 69 | case 2: 70 | return GColorElectricUltramarine; 71 | case 3: 72 | case 4: 73 | return GColorWhite; 74 | case 5: 75 | return GColorLightGray; 76 | case 6: 77 | return GColorElectricUltramarine; 78 | case 7: 79 | return GColorLightGray; 80 | case 8: 81 | return GColorYellow; 82 | default: 83 | return GColorWhite; 84 | } 85 | } -------------------------------------------------------------------------------- /app/resources/vibes/reveille.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": "Reveille", 3 | "notes": [ 4 | { 5 | "id": "note_0", 6 | "vibe_duration_ms": 120, 7 | "brake_duration_ms": 0, 8 | "strength": 0 9 | }, 10 | { 11 | "id": "note_1", 12 | "vibe_duration_ms": 111, 13 | "brake_duration_ms": 9, 14 | "strength": 40 15 | }, 16 | { 17 | "id": "note_6", 18 | "vibe_duration_ms": 105, 19 | "brake_duration_ms": 15, 20 | "strength": 55 21 | }, 22 | { 23 | "id": "note_6___", 24 | "vibe_duration_ms": 465, 25 | "brake_duration_ms": 15, 26 | "strength": 55 27 | }, 28 | { 29 | "id": "note_10", 30 | "vibe_duration_ms": 102, 31 | "brake_duration_ms": 18, 32 | "strength": 76 33 | }, 34 | { 35 | "id": "note_10_", 36 | "vibe_duration_ms": 222, 37 | "brake_duration_ms": 18, 38 | "strength": 76 39 | }, 40 | { 41 | "id": "note_10___", 42 | "vibe_duration_ms": 462, 43 | "brake_duration_ms": 18, 44 | "strength": 76 45 | }, 46 | { 47 | "id": "note_13_", 48 | "vibe_duration_ms": 220, 49 | "brake_duration_ms": 20, 50 | "strength": 100 51 | } 52 | ], 53 | "pattern": ["note_1", "note_0", 54 | "note_6", "note_0", "note_10", "note_6", "note_1", "note_0", "note_10", "note_0", 55 | "note_6", "note_0", "note_10", "note_6", "note_1", "note_0", "note_10", "note_0", 56 | "note_6", "note_0", "note_10", "note_6", "note_1", "note_0", "note_6", "note_0", 57 | "note_10_", "note_0", "note_0", "note_6", "note_0", "note_1", "note_0", 58 | 59 | "note_6", "note_0", "note_10", "note_6", "note_1", "note_0", "note_10", "note_0", 60 | "note_6", "note_0", "note_10", "note_6", "note_1", "note_0", "note_10", "note_0", 61 | "note_6", "note_0", "note_10", "note_6", "note_1", "note_0", "note_1", "note_0", 62 | "note_6___", "note_0", "note_0", "note_10", "note_0", 63 | 64 | "note_10", "note_0", "note_10", "note_0", "note_10", "note_0", "note_10", "note_0", 65 | "note_13_", "note_0", "note_0", "note_10", "note_0", "note_10", "note_0", 66 | "note_10", "note_0", "note_6", "note_0", "note_10", "note_0", "note_6", "note_0", 67 | "note_10___", "note_0", "note_0", "note_10", "note_0", 68 | 69 | "note_10", "note_0", "note_10", "note_0", "note_10", "note_0", "note_10", "note_0", 70 | "note_13_", "note_0", "note_0", "note_10", "note_0", "note_10", "note_0", 71 | "note_6", "note_0", "note_10", "note_6", "note_1", "note_0", "note_1", "note_0", 72 | "note_6___", "note_0", "note_0", "note_0", "note_0"], 73 | 74 | "repeat_delay_ms": 1500 75 | } 76 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | ## Developer Certification of Origin (DCO) 4 | 5 | To make a good faith effort to ensure licensing criteria are met, this 6 | project requires the Developer Certificate of Origin (DCO) process to be 7 | followed. 8 | 9 | The DCO is an attestation attached to every contribution made by every 10 | developer. In the commit message of the contribution, (described more fully 11 | later in this document), the developer simply adds a `Signed-off-by` 12 | statement and thereby agrees to the DCO. 13 | 14 | When a developer submits a patch, it is a commitment that the contributor has 15 | the right to submit the patch per the license. The DCO agreement is shown 16 | below and at http://developercertificate.org/. 17 | 18 | ``` 19 | Developer's Certificate of Origin 1.1 20 | 21 | By making a contribution to this project, I certify that: 22 | 23 | (a) The contribution was created in whole or in part by me and I 24 | have the right to submit it under the open source license 25 | indicated in the file; or 26 | 27 | (b) The contribution is based upon previous work that, to the best 28 | of my knowledge, is covered under an appropriate open source 29 | license and I have the right under that license to submit that 30 | work with modifications, whether created in whole or in part 31 | by me, under the same open source license (unless I am 32 | permitted to submit under a different license), as indicated 33 | in the file; or 34 | 35 | (c) The contribution was provided directly to me by some other 36 | person who certified (a), (b) or (c) and I have not modified 37 | it. 38 | 39 | (d) I understand and agree that this project and the contribution 40 | are public and that a record of the contribution (including all 41 | personal information I submit with it, including my sign-off) is 42 | maintained indefinitely and may be redistributed consistent with 43 | this project or the open source license(s) involved. 44 | ``` 45 | 46 | ### DCO Sign-Off 47 | 48 | The "sign-off" in the DCO is a "Signed-off-by:" line in each commit's log 49 | message. The Signed-off-by: line must be in the following format: 50 | 51 | ``` 52 | Signed-off-by: Your Name 53 | ``` 54 | 55 | For your commits, replace: 56 | 57 | - `Your Name` with your real name (pseudonyms, hacker handles, and the 58 | names of groups are not allowed) 59 | 60 | - `your.email@example.com` with the same email address you are using to 61 | author the commit (CI will fail if there is no match) 62 | 63 | You can automatically add the Signed-off-by: line to your commit body using 64 | `git commit -s`. Use other commits in the repository as examples. 65 | 66 | Additional requirements: 67 | 68 | - If you are altering an existing commit created by someone else, you must add 69 | your Signed-off-by: line without removing the existing one. 70 | -------------------------------------------------------------------------------- /app/src/c/assistant.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "root_window.h" 18 | #include "release_notes.h" 19 | #include "consent/consent.h" 20 | #include "converse/session_window.h" 21 | #include "converse/conversation_manager.h" 22 | #include "image_manager/image_manager.h" 23 | #include "alarms/manager.h" 24 | #include "version/version.h" 25 | #include "settings/settings.h" 26 | 27 | #include 28 | #include 29 | 30 | #include "util/logging.h" 31 | #include "util/memory/pressure.h" 32 | 33 | 34 | #define QUICK_LAUNCH_TIMEOUT_MS 60000 35 | 36 | static RootWindow* s_root_window = NULL; 37 | 38 | static void prv_init(void) { 39 | memory_pressure_init(); 40 | version_init(); 41 | consent_migrate(); 42 | settings_init(); 43 | conversation_manager_init(); 44 | #if ENABLE_FEATURE_IMAGE_MANAGER 45 | image_manager_init(); 46 | #endif 47 | events_app_message_open(); 48 | alarm_manager_init(); 49 | } 50 | 51 | static void prv_deinit(void) { 52 | if (s_root_window) { 53 | root_window_destroy(s_root_window); 54 | } 55 | #ifdef ENABLE_FEATURE_IMAGE_MANAGER 56 | image_manager_deinit(); 57 | #endif 58 | } 59 | 60 | int main(void) { 61 | VersionInfo version_info = version_get_current(); 62 | BOBBY_LOG(APP_LOG_LEVEL_INFO, "Bobby %d.%d", version_info.major, version_info.minor); 63 | prv_init(); 64 | 65 | if (alarm_manager_maybe_alarm()) { 66 | // don't actually have anything to do here - the alarm manager already did it. 67 | } else { 68 | if (must_present_consent()) { 69 | consent_window_push(); 70 | } else { 71 | if (launch_reason() == APP_LAUNCH_QUICK_LAUNCH) { 72 | QuickLaunchBehaviour quick_launch_behaviour = settings_get_quick_launch_behaviour(); 73 | if (quick_launch_behaviour != QuickLaunchBehaviourHomeScreen) { 74 | session_window_push(quick_launch_behaviour == QuickLaunchBehaviourConverseWithTimeout ? QUICK_LAUNCH_TIMEOUT_MS : 0, NULL); 75 | } else { 76 | s_root_window = root_window_create(); 77 | root_window_push(s_root_window); 78 | } 79 | } else { 80 | s_root_window = root_window_create(); 81 | root_window_push(s_root_window); 82 | } 83 | release_notes_maybe_push(); 84 | } 85 | } 86 | 87 | 88 | app_event_loop(); 89 | prv_deinit(); 90 | } 91 | -------------------------------------------------------------------------------- /app/src/c/settings/settings.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "settings.h" 18 | #include 19 | #include 20 | 21 | #include "../util/persist_keys.h" 22 | 23 | static EventHandle s_event_handle; 24 | 25 | static void prv_app_message_handler(DictionaryIterator *iter, void *context); 26 | 27 | void settings_init() { 28 | s_event_handle = events_app_message_register_inbox_received(prv_app_message_handler, NULL); 29 | } 30 | 31 | void settings_deinit() { 32 | events_app_message_unsubscribe(s_event_handle); 33 | } 34 | 35 | QuickLaunchBehaviour settings_get_quick_launch_behaviour() { 36 | int result = persist_read_int(PERSIST_KEY_QUICK_LAUNCH_BEHAVIOUR); 37 | if (result == 0) { 38 | return QuickLaunchBehaviourConverseWithTimeout; 39 | } 40 | return result; 41 | } 42 | 43 | VibePatternSetting settings_get_alarm_vibe_pattern() { 44 | int result = persist_read_int(PERSIST_KEY_ALARM_VIBE_PATTERN); 45 | if (result == 0) { 46 | return VibePatternSettingStandard; 47 | } 48 | return result; 49 | } 50 | 51 | VibePatternSetting settings_get_timer_vibe_pattern() { 52 | int result = persist_read_int(PERSIST_KEY_TIMER_VIBE_PATTERN); 53 | if (result == 0) { 54 | return VibePatternSettingStandard; 55 | } 56 | return result; 57 | } 58 | 59 | bool settings_get_should_confirm_transcripts() { 60 | // the default is false, so we don't have to check whether it exists. 61 | return persist_read_bool(PERSIST_KEY_CONFIRM_TRANSCRIPTS); 62 | } 63 | 64 | static void prv_app_message_handler(DictionaryIterator *iter, void *context) { 65 | for (Tuple *tuple = dict_read_first(iter); tuple; tuple = dict_read_next(iter)) { 66 | if (tuple->key == MESSAGE_KEY_QUICK_LAUNCH_BEHAVIOUR) { 67 | int value = atoi(tuple->value->cstring); 68 | persist_write_int(PERSIST_KEY_QUICK_LAUNCH_BEHAVIOUR, value); 69 | } else if (tuple->key == MESSAGE_KEY_ALARM_VIBE_PATTERN) { 70 | persist_write_int(PERSIST_KEY_ALARM_VIBE_PATTERN, atoi(tuple->value->cstring)); 71 | } else if (tuple->key == MESSAGE_KEY_TIMER_VIBE_PATTERN) { 72 | persist_write_int(PERSIST_KEY_TIMER_VIBE_PATTERN, atoi(tuple->value->cstring)); 73 | } else if (tuple->key == MESSAGE_KEY_CONFIRM_TRANSCRIPTS) { 74 | persist_write_bool(PERSIST_KEY_CONFIRM_TRANSCRIPTS, tuple->value->int8); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /app/src/c/version/version.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "version.h" 18 | #include "../util/persist_keys.h" 19 | #include "../util/logging.h" 20 | 21 | #include 22 | #include "pebble_process_info.h" 23 | 24 | // The standard pebble app build process injects this structure. 25 | extern const PebbleProcessInfo __pbl_app_info; 26 | 27 | static bool s_is_first_launch = false; 28 | static bool s_is_update = false; 29 | static VersionInfo s_last_launch; 30 | 31 | VersionInfo prv_read_last_launch(); 32 | 33 | void version_init() { 34 | VersionInfo version_info = version_get_current(); 35 | s_last_launch = prv_read_last_launch(); 36 | s_is_first_launch = s_last_launch.major == 0 && s_last_launch.minor == 0; 37 | if (version_info_compare(version_info, s_last_launch) != 0) { 38 | s_is_update = true; 39 | int status = persist_write_data(PERSIST_KEY_VERSION, &version_info, sizeof(VersionInfo)); 40 | if (status < 0) { 41 | BOBBY_LOG(APP_LOG_LEVEL_WARNING, "Failed to write version info: %d", status); 42 | } else { 43 | BOBBY_LOG(APP_LOG_LEVEL_INFO, "Current version (v%d.%d) stored (previous: v%d.%d)", version_info.major, version_info.minor, s_last_launch.major, s_last_launch.minor); 44 | } 45 | } else { 46 | BOBBY_LOG(APP_LOG_LEVEL_DEBUG, "Version (v%d.%d) unchanged since last launch.", version_info.major, version_info.minor); 47 | } 48 | } 49 | 50 | bool version_is_first_launch() { 51 | return s_is_first_launch; 52 | } 53 | 54 | bool version_is_updated() { 55 | return s_is_update; 56 | } 57 | 58 | VersionInfo version_get_last_launch() { 59 | return s_last_launch; 60 | } 61 | 62 | VersionInfo version_get_current() { 63 | return (VersionInfo) { 64 | .major = __pbl_app_info.process_version.major, 65 | .minor = __pbl_app_info.process_version.minor, 66 | }; 67 | } 68 | 69 | int version_info_compare(VersionInfo a, VersionInfo b) { 70 | if (a.major < b.major) { 71 | return -1; 72 | } 73 | if (a.major > b.major) { 74 | return 1; 75 | } 76 | if (a.minor < b.minor) { 77 | return -1; 78 | } 79 | if (a.minor > b.minor) { 80 | return 1; 81 | } 82 | return 0; 83 | } 84 | 85 | VersionInfo prv_read_last_launch() { 86 | VersionInfo version_info; 87 | int status = persist_read_data(PERSIST_KEY_VERSION, &version_info, sizeof(VersionInfo)); 88 | if (status < 0) { 89 | BOBBY_LOG(APP_LOG_LEVEL_WARNING, "Failed to read version info: %d", status); 90 | return (VersionInfo) { 91 | .major = 0, 92 | .minor = 0, 93 | }; 94 | } 95 | return version_info; 96 | } 97 | -------------------------------------------------------------------------------- /service/assistant/functions/feedback.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package functions 16 | 17 | import ( 18 | "context" 19 | "github.com/pebble-dev/bobby-assistant/service/assistant/query" 20 | "github.com/pebble-dev/bobby-assistant/service/assistant/quota" 21 | "google.golang.org/genai" 22 | "log" 23 | ) 24 | 25 | type FeedbackInput struct { 26 | Feedback string `json:"feedback"` 27 | IncludeThread bool `json:"include_thread"` 28 | } 29 | 30 | func init() { 31 | f := false 32 | registerFunction(Registration{ 33 | Definition: genai.FunctionDeclaration{ 34 | Name: "send_feedback", 35 | Description: "Send feedback to the developers. Include the thread if you want to provide context for the feedback. Only call this if the user specifically asks to send feedback. Feedback text is optional but recommended if include_thread is true.", 36 | Parameters: &genai.Schema{ 37 | Type: genai.TypeObject, 38 | Nullable: &f, 39 | Properties: map[string]*genai.Schema{ 40 | "feedback": { 41 | Type: genai.TypeString, 42 | Description: "The feedback to send to the developers.", 43 | Nullable: &f, 44 | }, 45 | "include_thread": { 46 | Type: genai.TypeBoolean, 47 | Description: "Whether to include the thread as context in the feedback.", 48 | Nullable: &f, 49 | }, 50 | }, 51 | Required: []string{"include_thread"}, 52 | }, 53 | }, 54 | Cb: sendFeedbackImpl, 55 | Thought: sendFeedbackThought, 56 | InputType: FeedbackInput{}, 57 | Capability: "send_feedback", 58 | }) 59 | } 60 | 61 | func sendFeedbackImpl(ctx context.Context, quotaTracker *quota.Tracker, i any, requestChan chan<- map[string]any, responseChan <-chan map[string]any) any { 62 | args := i.(*FeedbackInput) 63 | if !args.IncludeThread && args.Feedback == "" { 64 | return Error{Error: "You need either set include_thread = true or include some feedback from the user."} 65 | } 66 | log.Printf("Asking phone to send feedback...") 67 | request := map[string]any{ 68 | "action": "send_feedback", 69 | "feedback": args.Feedback, 70 | } 71 | if args.IncludeThread { 72 | request["thread_id"] = query.ThreadIdFromContext(ctx) 73 | } 74 | requestChan <- request 75 | log.Printf("Waiting for response...") 76 | response := <-responseChan 77 | log.Printf("Got response: %v", response) 78 | return response 79 | } 80 | 81 | func sendFeedbackThought(i any) string { 82 | args := i.(*FeedbackInput) 83 | if args.IncludeThread && args.Feedback != "" { 84 | return "Sending conversation with feedback..." 85 | } else if args.IncludeThread { 86 | return "Sending conversation..." 87 | } else if args.Feedback != "" { 88 | return "Sending feedback..." 89 | } else { 90 | return "Doing nothing productive..." 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/c/menus/legal_window.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "legal_window.h" 18 | #include 19 | #include "../util/style.h" 20 | #include "../util/formatted_text_layer.h" 21 | #include "../util/memory/malloc.h" 22 | #include "../util/memory/sdk.h" 23 | 24 | typedef struct { 25 | char *legal_text; 26 | FormattedTextLayer *text_layer; 27 | ScrollLayer *scroll_layer; 28 | StatusBarLayer *status_bar; 29 | } CreditsWindowData; 30 | 31 | static void prv_window_load(Window* window); 32 | static void prv_window_unload(Window* window); 33 | 34 | void legal_window_push() { 35 | Window *window = bwindow_create(); 36 | CreditsWindowData *data = bmalloc(sizeof(CreditsWindowData)); 37 | memset(data, 0, sizeof(CreditsWindowData)); 38 | window_set_user_data(window, data); 39 | window_set_window_handlers(window, (WindowHandlers) { 40 | .load = prv_window_load, 41 | .unload = prv_window_unload, 42 | }); 43 | window_stack_push(window, true); 44 | } 45 | 46 | static void prv_window_load(Window* window) { 47 | CreditsWindowData *data = window_get_user_data(window); 48 | ResHandle res_handle = resource_get_handle(RESOURCE_ID_LEGAL_TEXT); 49 | size_t res_size = resource_size(res_handle); 50 | data->legal_text = bmalloc(res_size + 1); 51 | resource_load(res_handle, (uint8_t*)data->legal_text, res_size); 52 | data->legal_text[res_size] = '\0'; 53 | Layer *root_layer = window_get_root_layer(window); 54 | GRect window_bounds = layer_get_bounds(root_layer); 55 | data->status_bar = bstatus_bar_layer_create(); 56 | bobby_status_bar_config(data->status_bar); 57 | layer_add_child(root_layer, status_bar_layer_get_layer(data->status_bar)); 58 | data->scroll_layer = bscroll_layer_create(GRect(0, STATUS_BAR_LAYER_HEIGHT, window_bounds.size.w, window_bounds.size.h - STATUS_BAR_LAYER_HEIGHT)); 59 | scroll_layer_set_shadow_hidden(data->scroll_layer, true); 60 | scroll_layer_set_click_config_onto_window(data->scroll_layer, window); 61 | layer_add_child(root_layer, scroll_layer_get_layer(data->scroll_layer)); 62 | data->text_layer = formatted_text_layer_create(GRect(5, 0, window_bounds.size.w - 10, 10000)); 63 | formatted_text_layer_set_text(data->text_layer, data->legal_text); 64 | GSize text_size = formatted_text_layer_get_content_size(data->text_layer); 65 | scroll_layer_set_content_size(data->scroll_layer, GSize(window_bounds.size.w, text_size.h + 10)); 66 | scroll_layer_add_child(data->scroll_layer, formatted_text_layer_get_layer(data->text_layer)); 67 | } 68 | 69 | static void prv_window_unload(Window* window) { 70 | CreditsWindowData *data = window_get_user_data(window); 71 | free(data->legal_text); 72 | formatted_text_layer_destroy(data->text_layer); 73 | scroll_layer_destroy(data->scroll_layer); 74 | status_bar_layer_destroy(data->status_bar); 75 | free(data); 76 | window_destroy(window); 77 | } 78 | -------------------------------------------------------------------------------- /.github/workflows/build-pbw.yaml: -------------------------------------------------------------------------------- 1 | name: Build Pebble app 2 | 3 | on: 4 | # Triggers the workflow on push or pull request events for any branch (see commented out `branches` lines for master branch example) 5 | push: 6 | paths: 7 | - 'app/**' 8 | - '.github/workflows/build-pbw.yaml' 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-24.04 13 | container: 14 | # Pebble SDK needs Python 2.7 15 | image: python:2.7.18-buster 16 | 17 | steps: 18 | # Sanity checks for debugging, if/when problems occur 19 | - name: pyver 20 | run: python --version 21 | - name: whichpy 22 | run: which python 23 | - name: whichpy2 24 | run: which python2 25 | - name: whichpy3 26 | run: which python3 27 | 28 | - name: Clone Repository (Latest) 29 | uses: actions/checkout@v4 30 | with: 31 | fetch-depth: 0 32 | if: github.event.inputs.git-ref == '' 33 | - name: Clone Repository (Custom Ref) 34 | uses: actions/checkout@v4 35 | if: github.event.inputs.git-ref != '' 36 | with: 37 | ref: ${{ github.event.inputs.git-ref }} 38 | fetch-depth: 0 39 | - name: Set short git commit SHA 40 | id: vars 41 | run: | 42 | calculatedSha=$(git rev-parse --short ${{ github.sha }}) 43 | echo "COMMIT_SHORT_SHA=$calculatedSha" >> $GITHUB_ENV 44 | - uses: actions/setup-node@v4 45 | - name: apt-get 46 | run: apt-get update; apt-get -qq install -y git zip wget python-pip python2.7-dev libsdl1.2debian libfdt1 libpixman-1-0 nodejs npm libfreetype6 libx11-6 rlwrap 47 | - name: virtualenv 48 | run: pip install virtualenv 49 | - name: get sdk 50 | run: if [ ! -d ~/sdk ]; then mkdir ~/sdk && wget -q -O - https://developer.rebble.io/s3.amazonaws.com/assets.getpebble.com/pebble-tool/pebble-sdk-4.5-linux64.tar.bz2 | tar xj --strip-components=1 -C ~/sdk; fi 51 | - name: requirements 52 | run: if [ ! -d ~/sdk/.env ]; then cd ~/sdk && virtualenv .env && bash -c "source .env/bin/activate && pip install -r requirements.txt && deactivate" && cd -; fi 53 | - name: sdk-core 54 | run: if [ ! -f ~/sdk/sdk-core-4.3.tar.bz2 ]; then wget https://github.com/aveao/PebbleArchive/raw/master/SDKCores/sdk-core-4.3.tar.bz2 -O ~/sdk/sdk-core-4.3.tar.bz2; fi 55 | - name: install sdk 56 | run: if [ ! -d ~/.pebble-sdk ]; then mkdir -p ~/.pebble-sdk && touch ~/.pebble-sdk/NO_TRACKING && ~/sdk/bin/pebble sdk install ~/sdk/sdk-core-4.3.tar.bz2; fi 57 | - name: fix whatever this git nonsense is 58 | run: git config --global --add safe.directory /__w/bobby-assistant/bobby-assistant; 59 | - name: pebble-build 60 | run: cd app/ && yes | ~/sdk/bin/pebble build 61 | - name: rename-app 62 | run: cp app/build/app.pbw Bobby-g${{ env.COMMIT_SHORT_SHA }}.pbw 63 | - name: generate elf bundle 64 | run: mkdir elfs && cp app/build/basalt/pebble-app.elf elfs/pebble-app-basalt.elf && cp app/build/diorite/pebble-app.elf elfs/pebble-app-diorite.elf && zip -r elfs.zip elfs 65 | - name: Upload PBW 66 | uses: actions/upload-artifact@v4 67 | with: 68 | name: Bobby PBW 69 | path: Bobby-g${{ env.COMMIT_SHORT_SHA }}.pbw 70 | if-no-files-found: error 71 | - name: Upload ELFs 72 | uses: actions/upload-artifact@v4 73 | with: 74 | name: Debug ELFs 75 | path: elfs.zip 76 | -------------------------------------------------------------------------------- /service/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pebble-dev/bobby-assistant/service 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.3 6 | 7 | require ( 8 | cloud.google.com/go/maps v1.20.4 9 | github.com/google/uuid v1.6.0 10 | github.com/honeycombio/beeline-go v1.19.0 11 | github.com/joho/godotenv v1.5.1 12 | github.com/redis/go-redis/v9 v9.7.3 13 | github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26 14 | github.com/yuin/gopher-lua v1.1.1 15 | golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 16 | golang.org/x/text v0.24.0 17 | google.golang.org/api v0.230.0 18 | google.golang.org/genai v1.1.0 19 | google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb 20 | google.golang.org/grpc v1.72.0 21 | google.golang.org/protobuf v1.36.6 22 | googlemaps.github.io/maps v1.7.0 23 | nhooyr.io/websocket v1.8.10 24 | ) 25 | 26 | require ( 27 | cloud.google.com/go v0.120.1 // indirect 28 | cloud.google.com/go/auth v0.16.0 // indirect 29 | cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect 30 | cloud.google.com/go/compute/metadata v0.6.0 // indirect 31 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 32 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 33 | github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect 34 | github.com/facebookgo/limitgroup v0.0.0-20150612190941-6abd8d71ec01 // indirect 35 | github.com/facebookgo/muster v0.0.0-20150708232844-fd3d7953fd52 // indirect 36 | github.com/felixge/httpsnoop v1.0.4 // indirect 37 | github.com/go-logr/logr v1.4.2 // indirect 38 | github.com/go-logr/stdr v1.2.2 // indirect 39 | github.com/google/go-cmp v0.7.0 // indirect 40 | github.com/google/s2a-go v0.1.9 // indirect 41 | github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect 42 | github.com/googleapis/gax-go/v2 v2.14.1 // indirect 43 | github.com/gorilla/websocket v1.5.3 // indirect 44 | github.com/honeycombio/libhoney-go v1.25.0 // indirect 45 | github.com/klauspost/compress v1.18.0 // indirect 46 | github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect 47 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 48 | go.opencensus.io v0.24.0 // indirect 49 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 50 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect 51 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect 52 | go.opentelemetry.io/otel v1.35.0 // indirect 53 | go.opentelemetry.io/otel/metric v1.35.0 // indirect 54 | go.opentelemetry.io/otel/trace v1.35.0 // indirect 55 | golang.org/x/crypto v0.37.0 // indirect 56 | golang.org/x/net v0.39.0 // indirect 57 | golang.org/x/oauth2 v0.29.0 // indirect 58 | golang.org/x/sync v0.13.0 // indirect 59 | golang.org/x/sys v0.32.0 // indirect 60 | golang.org/x/time v0.11.0 // indirect 61 | google.golang.org/genproto/googleapis/api v0.0.0-20250425173222-7b384671a197 // indirect 62 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250425173222-7b384671a197 // indirect 63 | gopkg.in/alexcesaro/statsd.v2 v2.0.0 // indirect 64 | ) 65 | 66 | // In v1.8.11, nhooyr.io/websocket made a change that appears to break cleanly closing websockets, so we can't use it. 67 | exclude nhooyr.io/websocket v1.8.11 68 | 69 | // In v1.8.12, nhooyr.io/websocket moved to github.com/coder/websocket (which still hasn't fixed the closing issue) 70 | exclude nhooyr.io/websocket v1.8.12 71 | 72 | // Apparently this is now updating again but none of these are fixed either. 73 | exclude nhooyr.io/websocket v1.8.13 74 | 75 | exclude nhooyr.io/websocket v1.8.14 76 | 77 | exclude nhooyr.io/websocket v1.8.15 78 | 79 | exclude nhooyr.io/websocket v1.8.16 80 | 81 | exclude nhooyr.io/websocket v1.8.17 82 | -------------------------------------------------------------------------------- /app/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # This file *will not build anything useful*. It's just here so CLion can evaluate the project. 16 | # Use the standard pebble tooling to build. 17 | cmake_minimum_required(VERSION 3.26) 18 | set(CMAKE_SYSTEM_NAME Generic) 19 | project(tiny_assistant_app) 20 | 21 | set(CMAKE_CXX_STANDARD 11) 22 | set(CMAKE_INCLUDE_SYSTEM_DIRECTORIES FALSE) 23 | 24 | # The Pebble SDK (pebble.h, etc.) 25 | # This assumes an SDK include directory in the same directory as this project, which is a weird thing to do. 26 | # I did it anyway. 27 | include_directories(sdk-include) 28 | include_directories(sdk-include/libc) 29 | 30 | # Autogenerated output 31 | include_directories(build/include) 32 | include_directories(build/basalt) # this is only going to work for basalt, but what can you do? 33 | 34 | # Each of the Pebble Packages we install needs an entry like this. 35 | include_directories(node_modules/pebble-clay/dist/include) 36 | include_directories(node_modules/pebble-events/dist/include) 37 | include_directories(node_modules/@smallstoneapps/linked-list/dist/include) 38 | 39 | # Y'know, all the files. 40 | add_executable(tiny_assistant_app 41 | src/c/alarms/alarm_window.c 42 | src/c/alarms/manager.c 43 | src/c/converse/conversation.c 44 | src/c/converse/conversation_manager.c 45 | src/c/converse/segments/info_layer.c 46 | src/c/converse/segments/message_layer.c 47 | src/c/converse/segments/segment_layer.c 48 | src/c/converse/session_window.c 49 | src/c/util/thinking_layer.c 50 | src/c/assistant.c 51 | src/c/root_window.c 52 | src/c/menus/root_menu.c 53 | src/c/menus/quota_window.c 54 | src/c/menus/usage_layer.c 55 | src/c/menus/alarm_menu.c 56 | src/c/menus/alarm_menu.h 57 | src/c/menus/legal_window.c 58 | src/c/menus/reminders_menu.c 59 | src/c/consent/consent.c 60 | src/c/util/vector_layer.c 61 | src/c/util/style.c 62 | src/c/util/vector_sequence_layer.c 63 | src/c/util/result_window.c 64 | src/c/converse/segments/widgets/weather_single_day.c 65 | src/c/converse/segments/widgets/weather_current.c 66 | src/c/converse/segments/widgets/weather_util.c 67 | src/c/converse/segments/widgets/weather_multi_day.c 68 | src/c/talking_horse_layer.c 69 | src/c/version/version.c 70 | src/c/util/time.c 71 | src/c/converse/segments/widgets/timer.c 72 | src/c/converse/segments/widgets/number.c 73 | src/c/menus/feedback_window.c 74 | src/c/settings/settings.c 75 | src/c/vibes/sad_vibe_score.c 76 | src/c/util/formatted_text_layer.c 77 | src/c/menus/about_window.c 78 | src/c/converse/report_window.c 79 | src/c/vibes/haptic_feedback.c 80 | src/c/util/action_menu_crimes.c 81 | src/c/image_manager/image_manager.c 82 | src/c/converse/segments/widgets/map.c 83 | src/c/util/memory/malloc.c 84 | src/c/util/memory/pressure.c 85 | src/c/util/memory/sdk.c 86 | src/c/release_notes.c 87 | ) 88 | --------------------------------------------------------------------------------