Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ members = [
"apps/reference",
"apps/news/host-service",
"apps/mail/host-service",
"apps/calendar/host-service",
"apps/ai-providers/config",
"apps/ai-providers/host-service",
"apps/appcard/module",
Expand Down Expand Up @@ -142,6 +143,7 @@ octosense-l0-chat = { path = "crates/l0-chat" }
octosense-llm-config = { path = "apps/ai-providers/config" }
octosense-llm-service = { path = "apps/ai-providers/host-service" }
octosense-mail-service = { path = "apps/mail/host-service" }
octosense-calendar-service = { path = "apps/calendar/host-service" }
octosense-news-service = { path = "apps/news/host-service" }
octos-app = { path = "apps/appcard/app/app", default-features = false }
octos-app-store = { path = "apps/appcard/app/crates/octos-app-store" }
Expand Down
15 changes: 15 additions & 0 deletions apps/calendar/bundle/main.splash
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Calendar: its assistant keeps the events (the `calendar` host service) and
// puts event and agenda cards on the glance screen. This window says how to
// reach it; listing the events here waits for App Hub's `calendar` capability.
let ink = #x1c1c1e
let secondary = #x8e8e93

SolidView{width: Fill height: Fill flow: Overlay draw_bg.color: #xffffff
View{width: Fill height: Fill flow: Down align: Align{x: 0.5 y: 0.5} spacing: 12 padding: 32
Label{text: "Calendar" draw_text.color: ink draw_text.text_style: theme.font_bold{font_size: 32}}
Label{width: Fill align: Align{x: 0.5} text: "Calendar's assistant keeps your events. Ask it to add one, to show an event on your glance screen, or to put your agenda there."
draw_text.color: secondary draw_text.text_style.font_size: 13}
Label{width: Fill align: Align{x: 0.5} text: "Ask Calendar from the bar while this window is in front, or ask the assistant (F8) to ask Calendar."
draw_text.color: secondary draw_text.text_style.font_size: 12}
}
}
13 changes: 13 additions & 0 deletions apps/calendar/bundle/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"schema": 1,
"id": "os.calendar",
"version": "1.0.0",
"name": "Calendar",
"integrity": { "bundle_blake3": "" },
"capabilities": ["storage", "glance"],
"agent": {
"profile": "read-only",
"tools": ["ask_user_question"],
"model": { "needs": ["tool_calling"] }
}
}
228 changes: 228 additions & 0 deletions apps/calendar/bundle/tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
{
"schema": 1,
"tools": [
{
"name": "calendar.events",
"description": "List the person's calendar events, soonest first: id, title, start, end, location and notes. Times are local, like 2026-10-02T15:00. Filter with from and to (a time or a date); limit caps the list.",
"input_schema": {
"type": "object",
"properties": {
"from": {
"type": "string",
"maxLength": 32
},
"to": {
"type": "string",
"maxLength": 32
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 200
}
},
"additionalProperties": false
},
"output_schema": {
"type": "object",
"properties": {
"events": {
"type": "array",
"items": {
"type": "object"
}
}
},
"required": [
"events"
]
},
"risk": "read",
"background": true,
"private_data": true,
"implemented_by": "host-service"
},
{
"name": "calendar.add_event",
"description": "Add an event to the person's calendar. start (and end) are local times like 2026-10-02T15:00, or a date like 2026-10-02 for the whole day. Answers the new event's id.",
"input_schema": {
"type": "object",
"properties": {
"title": {
"type": "string",
"minLength": 1,
"maxLength": 80
},
"start": {
"type": "string",
"minLength": 1,
"maxLength": 32
},
"end": {
"type": "string",
"maxLength": 32
},
"location": {
"type": "string",
"maxLength": 120
},
"notes": {
"type": "string",
"maxLength": 600
}
},
"required": [
"title",
"start"
],
"additionalProperties": false
},
"output_schema": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"start": {
"type": "string"
}
},
"required": [
"id"
]
},
"risk": "act",
"background": true,
"implemented_by": "host-service"
},
{
"name": "calendar.remove_event",
"description": "Remove an event from the person's calendar by its id (from calendar.events). It is gone for good, so the person approves it.",
"input_schema": {
"type": "object",
"properties": {
"id": {
"type": "string",
"minLength": 1,
"maxLength": 64
}
},
"required": [
"id"
],
"additionalProperties": false
},
"output_schema": {
"type": "object",
"properties": {
"removed": {
"type": "boolean"
}
},
"required": [
"removed"
]
},
"risk": "destructive",
"confirm": "host",
"background": true,
"implemented_by": "host-service"
},
{
"name": "calendar.notify",
"description": "Show an event on the person's glance screen as an event card, with a notification: name one of the calendar's events by its id (event), or give a title and when (a local time like 2026-10-02T15:00, or words), with a location and notes if any. Calendar draws the card. The same card_id replaces an earlier card.",
"input_schema": {
"type": "object",
"properties": {
"event": {
"type": "string",
"maxLength": 64
},
"title": {
"type": "string",
"maxLength": 80
},
"when": {
"type": "string",
"maxLength": 80
},
"location": {
"type": "string",
"maxLength": 120
},
"notes": {
"type": "string",
"maxLength": 600
},
"card_id": {
"type": "string",
"minLength": 1,
"maxLength": 64,
"pattern": "^[A-Za-z0-9._-]+$"
},
"priority": {
"type": "integer",
"minimum": 0,
"maximum": 100
}
},
"additionalProperties": false
},
"output_schema": {
"type": "object",
"properties": {
"card_id": {
"type": "string"
},
"replaced": {
"type": "boolean"
},
"expires_at": {
"type": "integer"
}
},
"required": [
"card_id"
]
},
"risk": "act",
"background": true,
"implemented_by": "host-service"
},
{
"name": "calendar.agenda",
"description": "Put the person's agenda on the glance screen: a card with the next three events within days (7 by default), with a notification. It replaces the previous agenda card.",
"input_schema": {
"type": "object",
"properties": {
"days": {
"type": "integer",
"minimum": 1,
"maximum": 60
}
},
"additionalProperties": false
},
"output_schema": {
"type": "object",
"properties": {
"card_id": {
"type": "string"
},
"replaced": {
"type": "boolean"
},
"expires_at": {
"type": "integer"
}
},
"required": [
"card_id"
]
},
"risk": "act",
"background": true,
"implemented_by": "host-service"
}
]
}
16 changes: 16 additions & 0 deletions apps/calendar/host-service/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# octosense-calendar-service — the `calendar` host service: Calendar's events,
# kept by the host, and the event and agenda cards its agent puts on the
# glance screen.
[package]
name = "octosense-calendar-service"
version = "0.1.0"
edition = "2021"
license = "Apache-2.0"
publish = false

[dependencies]
# The same App Hub rev as Mail and News: one appstore, one service registry.
octosense-appstore = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
chrono = { workspace = true }
36 changes: 36 additions & 0 deletions apps/calendar/host-service/resources/agenda.card
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# ledger calendar.agenda@0.1.0
# level: L0
# profile: ui/l0
#
# Calendar's agenda card (`calendar.agenda`): the next three events, as
# `os.calendar`. `day` is a `sys.dataset` record the service fills from the
# events (an event's title, and its time and place); the card states nothing
# else.

source day sys.dataset(fields: [title, as_of, pick1_title, pick1_body, pick2_title, pick2_body, pick3_title, pick3_body, summary])

copy app { class: vocabulary, en: "Calendar" }

view root Surface(pad: .tight) {
Col(gap: 8) {
Row(gap: 8, align: .center) {
Icon(name: .calendar, size: .row)
TextCaption(text: copy.app, width: .fill)
TextCaption(value: day.as_of)
}
TextTitle(text: day.title, width: .fill)
Row(gap: 8) {
TextRow(text: day.pick1_title, width: .fill)
TextCaption(text: day.pick1_body)
}
Row(gap: 8) {
TextRow(text: day.pick2_title, width: .fill)
TextCaption(text: day.pick2_body)
}
Row(gap: 8) {
TextRow(text: day.pick3_title, width: .fill)
TextCaption(text: day.pick3_body)
}
TextCaption(value: day.summary)
}
}
31 changes: 31 additions & 0 deletions apps/calendar/host-service/resources/event.card
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# ledger calendar.event@0.2.0
# level: L0
# profile: ui/l0
#
# Calendar's event card (`calendar.notify`): one event, as `os.calendar`.
# `ev` is a `sys.dataset` record the service fills from the event (its day
# and time on two tiles, its place, its notes); the card states nothing else.

source ev sys.dataset(fields: [title, as_of, metric1_label, metric1_value, metric2_label, metric2_value, subtitle, summary])

copy app { class: vocabulary, en: "Calendar" }

view root Surface(pad: .tight) {
Col(gap: 10) {
Row(gap: 8, align: .center) {
Icon(name: .calendar, size: .row)
TextCaption(text: copy.app, width: .fill)
TextCaption(value: ev.as_of)
}
TextTitle(text: ev.title, width: .fill)
Row(gap: 8) {
Tile(label: ev.metric1_label, value: ev.metric1_value)
Tile(label: ev.metric2_label, value: ev.metric2_value)
}
Row(gap: 8, align: .center) {
Icon(name: .location, size: .row)
TextRow(text: ev.subtitle, width: .fill)
}
TextBody(text: ev.summary, width: .fill)
}
}
Loading
Loading