@rubytech/taskmaster 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,7 +14,7 @@ taskmaster provision
14
14
  Or one command on a fresh device:
15
15
 
16
16
  ```bash
17
- curl -fsSL https://taskmaster.bot/install.sh | bash
17
+ curl -fsSL https://taskmaster.bot/install.sh | sudo bash
18
18
  ```
19
19
 
20
20
  After install, open the setup wizard in your browser:
@@ -122,7 +122,7 @@ export function buildAgentSystemPrompt(params) {
122
122
  browser: "Control web browser",
123
123
  canvas: "Present/eval/snapshot the Canvas",
124
124
  nodes: "List/describe/notify/camera/screen on paired nodes",
125
- cron: "Manage cron jobs and wake events (use for reminders; when scheduling a reminder, write the systemEvent text as something that will read like a reminder when it fires, and mention that it is a reminder depending on the time gap between setting and firing; include recent context in reminder text if appropriate)",
125
+ cron: "Manage scheduled events and wake events (use for reminders; when scheduling a reminder, write the systemEvent text as something that will read like a reminder when it fires, and mention that it is a reminder depending on the time gap between setting and firing; include recent context in reminder text if appropriate)",
126
126
  message: "Send messages and channel actions",
127
127
  gateway: "Restart, apply config, or run updates on the running Taskmaster process",
128
128
  agents_list: "List agent ids allowed for sessions_spawn",
@@ -263,7 +263,7 @@ export function buildAgentSystemPrompt(params) {
263
263
  "- browser: control Taskmaster's dedicated browser",
264
264
  "- canvas: present/eval/snapshot the Canvas",
265
265
  "- nodes: list/describe/notify/camera/screen on paired nodes",
266
- "- cron: manage cron jobs and wake events (use for reminders; when scheduling a reminder, write the systemEvent text as something that will read like a reminder when it fires, and mention that it is a reminder depending on the time gap between setting and firing; include recent context in reminder text if appropriate)",
266
+ "- cron: manage scheduled events and wake events (use for reminders; when scheduling a reminder, write the systemEvent text as something that will read like a reminder when it fires, and mention that it is a reminder depending on the time gap between setting and firing; include recent context in reminder text if appropriate)",
267
267
  "- sessions_list: list sessions",
268
268
  "- sessions_history: fetch session history",
269
269
  "- sessions_send: send to another session",
@@ -145,7 +145,7 @@
145
145
  },
146
146
  "cron": {
147
147
  "emoji": "⏰",
148
- "title": "Cron",
148
+ "title": "Events",
149
149
  "actions": {
150
150
  "status": { "label": "status" },
151
151
  "list": { "label": "list" },
@@ -113,21 +113,21 @@ async function buildReminderContextLines(params) {
113
113
  }
114
114
  export function createCronTool(opts) {
115
115
  return {
116
- label: "Cron",
116
+ label: "Events",
117
117
  name: "cron",
118
- description: `Manage Gateway cron jobs (status/list/add/update/remove/run/runs) and send wake events.
118
+ description: `Manage scheduled events (status/list/add/update/remove/run/runs) and send wake events.
119
119
 
120
120
  ACTIONS:
121
- - status: Check cron scheduler status
122
- - list: List jobs (use includeDisabled:true to include disabled)
123
- - add: Create job (requires job object, see schema below)
124
- - update: Modify job (requires jobId + patch object)
125
- - remove: Delete job (requires jobId)
126
- - run: Trigger job immediately (requires jobId)
127
- - runs: Get job run history (requires jobId)
121
+ - status: Check event scheduler status
122
+ - list: List events (use includeDisabled:true to include disabled). Returns all events for your account.
123
+ - add: Create event (requires job object, see schema below)
124
+ - update: Modify event (requires jobId + patch object)
125
+ - remove: Delete event (requires jobId)
126
+ - run: Trigger event immediately (requires jobId)
127
+ - runs: Get event run history (requires jobId)
128
128
  - wake: Send wake event (requires text, optional mode)
129
129
 
130
- JOB SCHEMA (for add action):
130
+ EVENT SCHEMA (for add action):
131
131
  {
132
132
  "name": "string (optional)",
133
133
  "schedule": { ... }, // Required: when to run
@@ -158,7 +158,7 @@ WAKE MODES (for wake action):
158
158
  - "next-heartbeat" (default): Wake on next heartbeat
159
159
  - "now": Wake immediately
160
160
 
161
- Use jobId as the canonical identifier; id is accepted for compatibility. Use contextMessages (0-10) to add previous messages as context to the job text.`,
161
+ Use jobId as the canonical identifier; id is accepted for compatibility. Use contextMessages (0-10) to add previous messages as context to the event text.`,
162
162
  parameters: CronToolSchema,
163
163
  execute: async (_toolCallId, args) => {
164
164
  const params = args;
@@ -171,10 +171,15 @@ Use jobId as the canonical identifier; id is accepted for compatibility. Use con
171
171
  switch (action) {
172
172
  case "status":
173
173
  return jsonResult(await callGatewayTool("cron.status", gatewayOpts, {}));
174
- case "list":
175
- return jsonResult(await callGatewayTool("cron.list", gatewayOpts, {
174
+ case "list": {
175
+ const listParams = {
176
176
  includeDisabled: Boolean(params.includeDisabled),
177
- }));
177
+ };
178
+ const accountId = opts?.agentAccountId;
179
+ if (accountId)
180
+ listParams.accountId = accountId;
181
+ return jsonResult(await callGatewayTool("cron.list", gatewayOpts, listParams));
182
+ }
178
183
  case "add": {
179
184
  if (!params.job || typeof params.job !== "object") {
180
185
  throw new Error("job required");
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.0.4",
3
- "commit": "a317fdb599ad59d1366f5d5352244c8dff8f44a8",
4
- "builtAt": "2026-02-14T19:55:48.406Z"
2
+ "version": "1.0.5",
3
+ "commit": "0d0cd3f87e0115e7ae887f96fefa33d8fdedbddb",
4
+ "builtAt": "2026-02-15T07:57:32.812Z"
5
5
  }
@@ -87,7 +87,11 @@ export function buildDefaultAgentList(workspaceRoot) {
87
87
  ],
88
88
  },
89
89
  write: {
90
- include: ["memory/users/{peer}/**", "memory/groups/{peer}/**"],
90
+ include: [
91
+ "memory/users/{peer}/**",
92
+ "memory/groups/{peer}/**",
93
+ "memory/shared/events/**",
94
+ ],
91
95
  },
92
96
  },
93
97
  },
@@ -13,11 +13,21 @@ export function parseTaskmasterVersion(raw) {
13
13
  revision: revision ? Number.parseInt(revision, 10) : 0,
14
14
  };
15
15
  }
16
+ /**
17
+ * Date-based versions (e.g. 2026.1.25) have major >= 2000.
18
+ * Semver versions (e.g. 1.0.4) have major < 100.
19
+ * Comparing across formats is meaningless — return null.
20
+ */
21
+ function isDateVersion(v) {
22
+ return v.major >= 2000;
23
+ }
16
24
  export function compareTaskmasterVersions(a, b) {
17
25
  const parsedA = parseTaskmasterVersion(a);
18
26
  const parsedB = parseTaskmasterVersion(b);
19
27
  if (!parsedA || !parsedB)
20
28
  return null;
29
+ if (isDateVersion(parsedA) !== isDateVersion(parsedB))
30
+ return null;
21
31
  if (parsedA.major !== parsedB.major)
22
32
  return parsedA.major < parsedB.major ? -1 : 1;
23
33
  if (parsedA.minor !== parsedB.minor)
@@ -66,6 +66,7 @@ export const CronJobStateSchema = Type.Object({
66
66
  export const CronJobSchema = Type.Object({
67
67
  id: NonEmptyString,
68
68
  agentId: Type.Optional(NonEmptyString),
69
+ accountId: Type.Optional(NonEmptyString),
69
70
  name: NonEmptyString,
70
71
  description: Type.Optional(Type.String()),
71
72
  enabled: Type.Boolean(),
@@ -82,11 +83,13 @@ export const CronJobSchema = Type.Object({
82
83
  export const CronListParamsSchema = Type.Object({
83
84
  includeDisabled: Type.Optional(Type.Boolean()),
84
85
  agentIds: Type.Optional(Type.Array(Type.String())),
86
+ accountId: Type.Optional(Type.String()),
85
87
  }, { additionalProperties: false });
86
88
  export const CronStatusParamsSchema = Type.Object({}, { additionalProperties: false });
87
89
  export const CronAddParamsSchema = Type.Object({
88
90
  name: NonEmptyString,
89
91
  agentId: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
92
+ accountId: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
90
93
  description: Type.Optional(Type.String()),
91
94
  enabled: Type.Optional(Type.Boolean()),
92
95
  deleteAfterRun: Type.Optional(Type.Boolean()),
@@ -99,6 +102,7 @@ export const CronAddParamsSchema = Type.Object({
99
102
  export const CronJobPatchSchema = Type.Object({
100
103
  name: Type.Optional(NonEmptyString),
101
104
  agentId: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
105
+ accountId: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
102
106
  description: Type.Optional(Type.String()),
103
107
  enabled: Type.Optional(Type.Boolean()),
104
108
  deleteAfterRun: Type.Optional(Type.Boolean()),
@@ -28,6 +28,10 @@ export const cronHandlers = {
28
28
  return jobAgent !== undefined && normalized.includes(jobAgent);
29
29
  });
30
30
  }
31
+ if (typeof p.accountId === "string" && p.accountId.trim()) {
32
+ const target = p.accountId.trim();
33
+ jobs = jobs.filter((job) => job.accountId === target);
34
+ }
31
35
  respond(true, { jobs }, undefined);
32
36
  },
33
37
  "cron.status": async ({ params, respond, context }) => {
@@ -5,6 +5,7 @@ import { listChannelPlugins } from "../channels/plugins/index.js";
5
5
  import { createDefaultDeps } from "../cli/deps.js";
6
6
  import { formatCliCommand } from "../cli/command-format.js";
7
7
  import { CONFIG_PATH_TASKMASTER, isNixMode, loadConfig, migrateLegacyConfig, readConfigFileSnapshot, writeConfigFile, } from "../config/config.js";
8
+ import { VERSION } from "../version.js";
8
9
  import { isDiagnosticsEnabled } from "../infra/diagnostic-events.js";
9
10
  import { logAcceptedEnvOption } from "../infra/env.js";
10
11
  import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js";
@@ -116,6 +117,17 @@ export async function startGatewayServer(port = 18789, opts = {}) {
116
117
  log.warn(`gateway: failed to persist plugin auto-enable changes: ${String(err)}`);
117
118
  }
118
119
  }
120
+ // Stamp config with running version on startup so upgrades keep the stamp current.
121
+ const storedVersion = configSnapshot.config.meta?.lastTouchedVersion;
122
+ if (configSnapshot.exists && storedVersion !== VERSION) {
123
+ try {
124
+ await writeConfigFile(configSnapshot.config);
125
+ log.info(`gateway: updated config version stamp from ${storedVersion ?? "(none)"} to ${VERSION}`);
126
+ }
127
+ catch (err) {
128
+ log.warn(`gateway: failed to update config version stamp: ${String(err)}`);
129
+ }
130
+ }
119
131
  const cfgAtStart = loadConfig();
120
132
  // License check — gateway always starts (so setup UI is reachable),
121
133
  // but pages other than /setup will redirect until a license is activated.
@@ -26,20 +26,32 @@ function attachExternalTransport(logger, transport) {
26
26
  }
27
27
  });
28
28
  }
29
+ let resolvingSettings = false;
29
30
  function resolveSettings() {
30
- let cfg = loggingState.overrideSettings ?? readLoggingConfig();
31
- if (!cfg) {
32
- try {
33
- const loaded = requireConfig("../config/config.js");
34
- cfg = loaded.loadConfig?.().logging;
35
- }
36
- catch {
37
- cfg = undefined;
31
+ // Guard against recursion: loadConfig() may trigger logging during
32
+ // validation, which calls getLogger() → resolveSettings() again.
33
+ if (resolvingSettings) {
34
+ return { level: "info", file: defaultRollingPathForToday() };
35
+ }
36
+ resolvingSettings = true;
37
+ try {
38
+ let cfg = loggingState.overrideSettings ?? readLoggingConfig();
39
+ if (!cfg) {
40
+ try {
41
+ const loaded = requireConfig("../config/config.js");
42
+ cfg = loaded.loadConfig?.().logging;
43
+ }
44
+ catch {
45
+ cfg = undefined;
46
+ }
38
47
  }
48
+ const level = normalizeLogLevel(cfg?.level, "info");
49
+ const file = cfg?.file ?? defaultRollingPathForToday();
50
+ return { level, file };
51
+ }
52
+ finally {
53
+ resolvingSettings = false;
39
54
  }
40
- const level = normalizeLogLevel(cfg?.level, "info");
41
- const file = cfg?.file ?? defaultRollingPathForToday();
42
- return { level, file };
43
55
  }
44
56
  function settingsChanged(a, b) {
45
57
  if (!a)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/taskmaster",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "AI-powered business assistant for small businesses",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -4,10 +4,10 @@ set -euo pipefail
4
4
  # Taskmaster — one-command install for fresh devices (Pi or Mac).
5
5
  #
6
6
  # Usage:
7
- # curl -fsSL https://taskmaster.bot/install.sh | bash
7
+ # curl -fsSL https://taskmaster.bot/install.sh | sudo bash
8
8
  #
9
9
  # With custom port:
10
- # curl -fsSL https://taskmaster.bot/install.sh | bash -s -- --port 19000
10
+ # curl -fsSL https://taskmaster.bot/install.sh | sudo bash -s -- --port 19000
11
11
 
12
12
  PORT=""
13
13
  for arg in "$@"; do
@@ -2,7 +2,13 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { spawnSync } from "node:child_process";
4
4
  import { fileURLToPath } from "node:url";
5
- import { setupGitHooks } from "./setup-git-hooks.js";
5
+ let setupGitHooks = () => {};
6
+ try {
7
+ const mod = await import("./setup-git-hooks.js");
8
+ setupGitHooks = mod.setupGitHooks;
9
+ } catch {
10
+ // setup-git-hooks.js is not shipped in the npm package — skip in production
11
+ }
6
12
 
7
13
  function detectPackageManager(ua = process.env.npm_config_user_agent ?? "") {
8
14
  // Examples:
@@ -214,7 +214,7 @@ Always ask the business owner before reshuffling. Never move a job without appro
214
214
  **Real-time location (GPS):**
215
215
  If the business owner's phone is paired as a node, you can query their GPS location on demand using `location.get`. Use this for:
216
216
  - **Customer asks "where is he?"** → Check GPS, compare to next appointment location, give an ETA: "[name] left his last job about 15 minutes ago. He should be with you in about 20 minutes."
217
- - **Late detection** → If you have a cron check 10 minutes before each appointment, query GPS. If the business owner is still far away, proactively message the customer: "Hi! [name]'s running about 15 minutes late — he's on his way. Sorry for the wait!" This turns a complaint into a professional courtesy.
217
+ - **Late detection** → If you have a scheduled event check 10 minutes before each appointment, query GPS. If the business owner is still far away, proactively message the customer: "Hi! [name]'s running about 15 minutes late — he's on his way. Sorry for the wait!" This turns a complaint into a professional courtesy.
218
218
  - **Business owner or their partner asks "where is [name]?"** → Report current location and next job ETA.
219
219
  - **End-of-day** → If the business owner is near their home postcode, stop offering same-day appointments.
220
220
  - Do NOT track location continuously. Only query when you have a specific reason.
@@ -339,7 +339,7 @@ Customers can always request to speak to the business owner directly. The agent
339
339
 
340
340
  ## Morning Briefing
341
341
 
342
- If a cron job is set up for morning briefings, send a daily summary. **Group jobs by area, not just time.** Include postcodes, estimated travel, and total driving time. See Schedule Optimisation section for full format.
342
+ If a scheduled event is set up for morning briefings, send a daily summary. **Group jobs by area, not just time.** Include postcodes, estimated travel, and total driving time. See Schedule Optimisation section for full format.
343
343
 
344
344
  > **Morning [name]! Here's your Tuesday:**
345
345
  >
@@ -0,0 +1,15 @@
1
+ # Event Management
2
+
3
+ Applies when handling anything time-bound: appointments, meetings, reminders, follow-ups, callbacks, deadlines — any commitment or scheduled action between one or more parties.
4
+
5
+ ## When to activate
6
+
7
+ - Customer requests or confirms an appointment
8
+ - Business owner asks to schedule, reschedule, or cancel something
9
+ - A reminder, follow-up, or callback needs to be recorded
10
+ - Someone asks "what's on [day/week]?" or "show me the schedule"
11
+ - A deadline or time-sensitive commitment is mentioned
12
+
13
+ ## References
14
+
15
+ Load `references/events.md` for the standard event template, file naming, dual-write pattern, and calendar query instructions.
@@ -0,0 +1,120 @@
1
+ # Events
2
+
3
+ Events are any time-bound information: appointments, meetings, reminders, follow-ups, callbacks, deadlines. Anything that represents a commitment, agreement, or scheduled action between one or more parties.
4
+
5
+ ---
6
+
7
+ ## Standard Event Format
8
+
9
+ Every event file follows this structure. All fields above the `## Notes` section are required unless marked optional.
10
+
11
+ ```markdown
12
+ # Event: [Short descriptive title]
13
+
14
+ - **Date:** YYYY-MM-DD
15
+ - **Time:** HH:MM (24h, local timezone)
16
+ - **Duration:** [number] min
17
+ - **Customer:** [Name] ([phone])
18
+ - **Service:** [what is being done]
19
+ - **Location:** [address or "Remote" / "Phone"]
20
+ - **Status:** confirmed | tentative | cancelled | completed
21
+ - **Created by:** [agent id — e.g. public, admin]
22
+ - **Created:** YYYY-MM-DDTHH:MM:SSZ
23
+
24
+ ## Notes
25
+ [Free text — special instructions, context, history of changes]
26
+ ```
27
+
28
+ ### Field guidance
29
+
30
+ - **Date/Time** — always local timezone. If the business owner's timezone is known, use it consistently.
31
+ - **Duration** — estimated in minutes. If unknown, use a reasonable default for the service type and note the assumption.
32
+ - **Customer** — full name and phone in international format. For internal events (admin meetings, personal reminders) use the owner's name and number.
33
+ - **Status** — `confirmed` when both parties have agreed. `tentative` when proposed but not confirmed. `cancelled` when called off. `completed` after the event has passed and the work is done.
34
+ - **Service** — brief description of the work or purpose. Not a category — use plain language ("boiler service", "quote for kitchen extension", "team meeting").
35
+
36
+ ---
37
+
38
+ ## File Naming
39
+
40
+ Files are named for sort order and human readability:
41
+
42
+ ```
43
+ YYYY-MM-DD-slug.md
44
+ ```
45
+
46
+ Examples:
47
+ - `2026-02-20-boiler-service-john-smith.md`
48
+ - `2026-02-21-quote-mrs-jenkins.md`
49
+ - `2026-03-01-team-meeting.md`
50
+
51
+ The slug should be lowercase, hyphenated, and include enough context to identify the event without opening the file.
52
+
53
+ ---
54
+
55
+ ## Where to Write
56
+
57
+ Every event must be written to **two** locations:
58
+
59
+ 1. **Per-customer record:** `memory/users/{phone}/events/YYYY-MM-DD-slug.md`
60
+ - Scoped to the customer. Visible when reviewing that customer's history.
61
+ 2. **Shared calendar:** `memory/shared/events/YYYY-MM-DD-slug.md`
62
+ - Visible to all agents in the account. The admin agent uses this for calendar queries.
63
+
64
+ Both files have identical content. Write to both in the same operation.
65
+
66
+ For events that have no external customer (internal meetings, personal reminders), write only to `memory/shared/events/`.
67
+
68
+ ---
69
+
70
+ ## Updating Events
71
+
72
+ When an event changes (rescheduled, cancelled, details updated):
73
+
74
+ 1. Update **both** copies — the per-customer file and the shared calendar file.
75
+ 2. Change the **Status** field appropriately.
76
+ 3. Add a note in the `## Notes` section explaining the change and when it happened.
77
+ 4. If the date changes, rename both files to reflect the new date. Delete the old files.
78
+
79
+ ---
80
+
81
+ ## Querying the Calendar
82
+
83
+ To answer "what's on [day/week]?" or "show me the schedule":
84
+
85
+ 1. List files in `memory/shared/events/` — filenames are date-prefixed, so lexicographic order is chronological order.
86
+ 2. Filter by date prefix (e.g., `2026-02-20` for a specific day, `2026-02-` for a month).
87
+ 3. Read the matching files for details.
88
+
89
+ This is a directory listing + read operation, not a search. It is fast and complete.
90
+
91
+ ---
92
+
93
+ ## Linking Events to Scheduled Reminders
94
+
95
+ Events (calendar data) and scheduled reminders (the `cron` tool) serve different purposes:
96
+
97
+ - **Event file** — the record of what is happening, when, and with whom. Source of truth for the calendar.
98
+ - **Scheduled reminder** (`cron` tool) — an automated trigger that fires at a specific time to notify or act. Optional.
99
+
100
+ When you create an event that also needs a reminder (e.g., "remind me 30 minutes before"), create both:
101
+ 1. The event file (in both locations, as above)
102
+ 2. A scheduled reminder via the `cron` tool, referencing the event
103
+
104
+ The reminder's payload text should reference the event clearly so the recipient has full context when it fires.
105
+
106
+ ---
107
+
108
+ ## Event Types
109
+
110
+ This format covers all time-bound data. Examples:
111
+
112
+ | Type | Customer field | Location | Notes |
113
+ |------|---------------|----------|-------|
114
+ | Customer appointment | Customer name + phone | Job site address | Service details, access instructions |
115
+ | Quote visit | Customer name + phone | Site address | What to quote, measurements needed |
116
+ | Callback | Customer name + phone | "Phone" | Why they're expecting a call, promised window |
117
+ | Follow-up | Customer name + phone | n/a | What to follow up on, last interaction context |
118
+ | Internal meeting | Owner name + phone | Meeting link or office | Agenda, attendees |
119
+ | Personal reminder | Owner name + phone | n/a | What to remember, deadline context |
120
+ | Deadline | Owner or customer | n/a | What's due, consequences of missing it |
@@ -39,7 +39,7 @@ You'll need a monitor, keyboard, and mouse connected to the Pi.
39
39
  2. Run:
40
40
 
41
41
  ```
42
- curl -fsSL https://taskmaster.bot/install.sh | bash
42
+ curl -fsSL https://taskmaster.bot/install.sh | sudo bash
43
43
  ```
44
44
 
45
45
  3. Wait for it to finish (a few minutes — it installs Node.js if needed)
@@ -54,7 +54,7 @@ You can also access the setup page from any other device on the same network at
54
54
  Open **Terminal** (search for "Terminal" in Spotlight) and run:
55
55
 
56
56
  ```
57
- curl -fsSL https://taskmaster.bot/install.sh | bash
57
+ curl -fsSL https://taskmaster.bot/install.sh | sudo bash
58
58
  ```
59
59
 
60
60
  This installs Node.js (if needed), Taskmaster, and sets up the background service. Once finished, open your browser and go to: **http://localhost:18789/setup**
@@ -62,7 +62,7 @@ This installs Node.js (if needed), Taskmaster, and sets up the background servic
62
62
  **Custom port:** If you're running multiple Taskmaster instances (e.g., one on a Pi and one on a Mac), give each a different port:
63
63
 
64
64
  ```
65
- curl -fsSL https://taskmaster.bot/install.sh | bash -s -- --port 19000
65
+ curl -fsSL https://taskmaster.bot/install.sh | sudo bash -s -- --port 19000
66
66
  ```
67
67
 
68
68
  ### Option D: Install from a package file
@@ -142,11 +142,11 @@ Keep it brief. Bullet points. Only what matters.
142
142
 
143
143
  ---
144
144
 
145
- ## Reminders (Cron Tool)
145
+ ## Reminders (Events Tool)
146
146
 
147
- When [OWNER_NAME] asks you to remind them about something, use the `cron` tool correctly.
147
+ When [OWNER_NAME] asks you to remind them about something, use the `cron` tool (named "Events" in the UI) correctly.
148
148
 
149
- ### CRITICAL: Two Types of Cron Jobs
149
+ ### CRITICAL: Two Types of Scheduled Events
150
150
 
151
151
  | Type | When to Use | What Happens |
152
152
  |------|-------------|--------------|
@@ -197,11 +197,32 @@ This does NOT send a message! It just adds text to the session that [OWNER_NAME]
197
197
  - **Recurring:** `{ "kind": "every", "everyMs": <interval> }` — repeats at interval
198
198
  - **Cron expr:** `{ "kind": "cron", "expr": "0 9 * * 1-5" }` — standard cron syntax
199
199
 
200
- ### When a Cron Job Fires (Isolated Agent)
200
+ ### When a Scheduled Event Fires (Isolated Agent)
201
201
 
202
- When YOU are running as an isolated cron agent:
202
+ When YOU are running as an isolated event agent:
203
203
  - **DO NOT call the `message` tool** — delivery is handled automatically by the framework
204
204
  - Just output the text you want to send
205
- - The `deliver: true` + `to` + `channel` from the job config handles delivery
205
+ - The `deliver: true` + `to` + `channel` from the event config handles delivery
206
206
 
207
207
  **Why:** If you call `message` directly, you might use the wrong recipient. Let the framework deliver.
208
+
209
+ ---
210
+
211
+ ## Shared Calendar
212
+
213
+ You have access to a shared calendar at `memory/shared/events/`. This contains all scheduled events across all agents in your account.
214
+
215
+ ### Querying the Calendar
216
+
217
+ When [OWNER_NAME] asks about upcoming appointments, schedules, or "what's on":
218
+ 1. List files in `memory/shared/events/` — each file is one event, named `YYYY-MM-DD-slug.md`
219
+ 2. Read the relevant files for details
220
+ 3. Files are sorted by date via filename — no search needed
221
+
222
+ ### Event Format
223
+
224
+ All calendar events follow a standard format. When creating events, write to **both** locations:
225
+ 1. `memory/users/{phone}/events/YYYY-MM-DD-slug.md` — per-customer record
226
+ 2. `memory/shared/events/YYYY-MM-DD-slug.md` — shared calendar
227
+
228
+ See `skills/event-management/references/events.md` for the full event template and guidelines.
@@ -0,0 +1,80 @@
1
+ # Business Assistant Skill
2
+
3
+ <description>
4
+ Handle customer enquiries, scheduling, and day-to-day business communication for a small business.
5
+ </description>
6
+
7
+ <triggers>
8
+ - Customer enquiries about services, availability, pricing
9
+ - Scheduling and appointment requests
10
+ - Follow-ups on outstanding requests
11
+ - General business questions
12
+ </triggers>
13
+
14
+ ---
15
+
16
+ ## Your Role
17
+
18
+ You're the virtual receptionist. You handle messages while the business owner is busy. Look up the owner's name and business name from memory so you can use them naturally in conversation.
19
+
20
+ ## What You Can Do
21
+
22
+ ### Answer Enquiries
23
+ - "What do you offer?" → Check memory for services and business info
24
+ - "Are you available [date]?" → Check schedule, offer to book
25
+ - "Where are you based?" → Check memory for location
26
+
27
+ ### Handle Scheduling
28
+ 1. Get the details (what they need, when)
29
+ 2. Get contact details (name, phone)
30
+ 3. Confirm with customer
31
+ 4. Add to schedule (or flag for the business owner to confirm)
32
+
33
+ ### Handle Follow-ups
34
+ - Check if the request was addressed
35
+ - Gentle reminder if something is outstanding
36
+
37
+ ### After Hours
38
+ - Acknowledge the message
39
+ - Say the business owner will respond during working hours
40
+ - If urgent, provide contact info from memory
41
+
42
+ ## What You Can't Do
43
+
44
+ - **Give specialised professional advice** — "I'll have [owner] get back to you on that directly."
45
+ - **Quote specific prices** unless you have them in memory — "Let me check and get back to you on pricing."
46
+ - **Make commitments** — "Let me check with [owner] and confirm."
47
+
48
+ (Use the owner's actual name — look it up from memory/shared/business.md.)
49
+
50
+ ## Message Style
51
+
52
+ - Short, friendly, professional
53
+ - WhatsApp-native (no walls of text)
54
+ - Use customer's name if known
55
+ - Sign off warmly: "Thanks! / Cheers! / Speak soon!"
56
+
57
+ ## Example Conversations
58
+
59
+ ### New Enquiry
60
+ > Customer: "Hi, I saw your business online. Can you help me with...?"
61
+ >
62
+ > You: "Hi! Thanks for reaching out. Yes, we can help with that. Would you like me to arrange a time to discuss the details?"
63
+
64
+ ### Scheduling
65
+ > Customer: "Can I book something for this week?"
66
+ >
67
+ > You: "Of course! What day works best for you? I'll check availability."
68
+
69
+ ### After Hours
70
+ > Customer: [Message at 10pm]
71
+ >
72
+ > You: "Thanks for your message! We'll get back to you tomorrow morning."
73
+
74
+ ## Escalation
75
+
76
+ Pass to the business owner when:
77
+ - Customer is upset or has a complaint
78
+ - Question you can't answer confidently
79
+ - Pricing negotiation or custom request
80
+ - Anything unusual