@opticell/workflow-companion 1.8.1 → 1.9.0

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.
Files changed (3) hide show
  1. package/README.md +8 -0
  2. package/index.js +166 -32
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -181,6 +181,7 @@ npx -y @opticell/workflow-companion wake-on
181
181
  | `workflow-companion setup` | Signs this computer in. Takes `--web-token` to skip the prompt. |
182
182
  | `workflow-companion status` | Which device answers, how fresh the snapshot is, any receipts that arrived late. |
183
183
  | `workflow-companion doctor` | Checks everything on this computer and says how to fix what's wrong. Start here when something isn't working. |
184
+ | `workflow-companion canary` | Runs the daily self-test now. `canary on\|off`, `canary at HH:MM`. |
184
185
  | `workflow-companion keepalive` | Renews the sign-in. The scheduled job runs this. |
185
186
  | `workflow-companion update` | Updates the scheduled jobs' copy to the latest release now, and shows how to keep Claude Code on `@latest`. |
186
187
  | `workflow-companion auto-update on\|off` | Whether the scheduled jobs update themselves (on by default). |
@@ -231,6 +232,13 @@ move to `Filed/<date>`. `--assign` puts them in this assistant's queue.
231
232
  changes, an approval is answered, a session needs attention, or the iCloud
232
233
  sign-in has ended (with the command that fixes it).
233
234
 
235
+ **Daily self-test (1.9).** With waking on, once a day (07:30, or
236
+ `canary at HH:MM`) the companion proves the whole chain still works: WorkFlow
237
+ applies its commands, Claude Code runs and can write, and the change comes
238
+ back. A pass quietly updates its "⚙︎ <name> daily self-test" task; a failure
239
+ comments there, which reaches your phone, naming the step and the fix.
240
+ `workflow-companion canary` runs it now; `canary off` stops it.
241
+
234
242
  **Session reports.** After each session that changed something, a short
235
243
  report is attached to its task: what it was asked, what it changed, any
236
244
  approved commands it ran, and its last words.
package/index.js CHANGED
@@ -27,7 +27,7 @@ const path = require("path");
27
27
  const os = require("os");
28
28
 
29
29
  const PROTOCOL_VERSION = "2024-11-05";
30
- const SERVER_INFO = { name: "workflow", version: "1.8.1" };
30
+ const SERVER_INFO = { name: "workflow", version: "1.9.0" };
31
31
  // Who the agent is in WorkFlow: the assignee its queue follows, the @name
32
32
  // that calls it, the signature on what it writes. On the Mac it's Claude.
33
33
  // A companion goes by the name chosen at setup (default "Companion"), so a
@@ -629,7 +629,11 @@ function showTask(t) {
629
629
  function queueItems(snap) {
630
630
  const shared = new Set((snap.projects || []).filter(p => p.isShared).map(p => p.name));
631
631
  const mine = t => (t.assignee || "").toLowerCase() === AGENT.name.toLowerCase();
632
- const rows = (snap.tasks || []).filter(t => t.status !== "Completed" && !t.isHidden && (mine(t) || addressedToMe(t, shared) || watchOf(t)));
632
+ // Nothing in a shared project, however it got there: a collaborator can
633
+ // assign tasks and write comments there, so it isn't only the owner asking
634
+ // (the Mac's rule too; the shared cases in Tools/agent-rules check both).
635
+ const rows = (snap.tasks || []).filter(t => t.status !== "Completed" && !t.isHidden && !(t.project && shared.has(t.project))
636
+ && (mine(t) || addressedToMe(t, shared) || watchOf(t)));
633
637
  const notes = (snap.notes || []).filter(n => !n.isArchived && !n.convertedAt && mentionsMe(`${n.title || ""}\n${n.body || ""}`));
634
638
  return { rows, notes, mine };
635
639
  }
@@ -1595,7 +1599,7 @@ function killTree(pid) {
1595
1599
  else { try { process.kill(-pid, "SIGTERM"); } catch (_) { try { process.kill(pid, "SIGTERM"); } catch (_) {} } }
1596
1600
  }
1597
1601
 
1598
- function runSession(cfg, prompt) {
1602
+ function runSession(cfg, prompt, limitMs) {
1599
1603
  const { spawn } = require("child_process");
1600
1604
  const bin = cfg.claudeBin || "claude";
1601
1605
  const args = ["-p", "--allowedTools", cfg.wakeAllowedTools || "mcp__workflow"];
@@ -1605,7 +1609,7 @@ function runSession(cfg, prompt) {
1605
1609
  const shell = process.platform === "win32" && /\.(cmd|bat)$/i.test(bin);
1606
1610
  const cwd = cfg.wakeWorkDir && fs.existsSync(cfg.wakeWorkDir) ? cfg.wakeWorkDir : os.homedir();
1607
1611
  // WF_WAKE_MAX_SECONDS is for the tests.
1608
- const limit = process.env.WF_WAKE_MAX_SECONDS ? Number(process.env.WF_WAKE_MAX_SECONDS) * 1000 : (cfg.wakeMaxMinutes || WAKE.maxMinutes) * 60000;
1612
+ const limit = process.env.WF_WAKE_MAX_SECONDS ? Number(process.env.WF_WAKE_MAX_SECONDS) * 1000 : limitMs || (cfg.wakeMaxMinutes || WAKE.maxMinutes) * 60000;
1609
1613
  return new Promise(resolve => {
1610
1614
  let out = "", timedOut = false, child;
1611
1615
  try {
@@ -1671,7 +1675,12 @@ async function wake() {
1671
1675
  const live = new Set(items.map(i => i.key));
1672
1676
  for (const k of Object.keys(st.seen)) if (!live.has(k)) { delete st.seen[k]; delete st.attempts[k]; }
1673
1677
  const fresh = items.filter(i => st.seen[i.key] !== i.sig);
1674
- if (!fresh.length) { saveAtomic(WAKE.state, JSON.stringify(st)); return 0; }
1678
+ if (!fresh.length) {
1679
+ saveAtomic(WAKE.state, JSON.stringify(st));
1680
+ // 1.9: the daily self-test, when nothing else is waiting.
1681
+ if (canaryDue(readJSON(CP.config, cfg))) await runCanary(readJSON(CP.config, cfg));
1682
+ return 0;
1683
+ }
1675
1684
 
1676
1685
  // Marked handled BEFORE the session: re-running half-done work unattended
1677
1686
  // is worse than dropping it. A session that wrote nothing at all is the
@@ -2262,6 +2271,118 @@ async function inboxCLI(on) {
2262
2271
  return 0;
2263
2272
  }
2264
2273
 
2274
+ // ------------------------------------------------------------------ canary (1.9)
2275
+ //
2276
+ // A daily self-test of the whole chain, so a break shows up in the morning
2277
+ // rather than when something needs doing. Once a day (07:30 by default, only
2278
+ // with waking on), in the scheduled job:
2279
+ // 1. doctor's checks
2280
+ // 2. its own "⚙︎ <name> daily self-test" task exists: creating it proves
2281
+ // WorkFlow applies this companion's commands
2282
+ // 3. a tiny real Claude session writes a one-off code into that task's
2283
+ // status line: Claude Code is signed in, has the tools, and can write
2284
+ // 4. the code comes back in WorkFlow's published snapshot: the round trip
2285
+ // A pass only updates the status line (no comment, so no notification). A
2286
+ // failure posts one comment naming the step and the fix, which reaches the
2287
+ // phone; a recovery says so too.
2288
+
2289
+ const CANARY_DEFAULT_AT = "07:30";
2290
+ const localDay = (d = new Date()) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
2291
+ function canaryTitle() { return `⚙︎ ${AGENT.name} daily self-test`; }
2292
+
2293
+ function canaryDue(cfg, now = new Date()) {
2294
+ if (!cfg || !cfg.wakeEnabled || cfg.canary === false) return false;
2295
+ if (cfg.canaryLastDay === localDay(now)) return false;
2296
+ const [h, m] = String(cfg.canaryAt || CANARY_DEFAULT_AT).split(":").map(Number);
2297
+ return now.getHours() * 60 + now.getMinutes() >= (h || 0) * 60 + (m || 0);
2298
+ }
2299
+
2300
+ async function runCanary(cfg) {
2301
+ const started = Date.now();
2302
+ const host = os.hostname();
2303
+ updateConfig(c => { c.canaryLastDay = localDay(); }); // once a day, even if this dies part-way
2304
+ const fail = (step, detail, fix) => ({ ok: false, step, detail, fix });
2305
+ let result, taskID = null;
2306
+ try {
2307
+ result = await (async () => {
2308
+ // Found first, so any failure below can be reported on it.
2309
+ const existing = (cloudSnap && cloudSnap.tasks || []).find(t => t.title === canaryTitle());
2310
+ if (existing) taskID = existing.id;
2311
+ const health = await doctorReport({ fromTool: true, forCanary: true });
2312
+ if (health.problems) {
2313
+ const first = health.text.split("\n").findIndex(l => l.startsWith(" ✗"));
2314
+ const lines = health.text.split("\n");
2315
+ return fail("health check", lines[first].replace(/^\s*✗\s*/, ""), (lines[first + 1] || "").replace(/^\s*fix:\s*/, "") || "run: npx -y @opticell/workflow-companion@latest doctor");
2316
+ }
2317
+ if (!taskID) {
2318
+ const r = await send("create_task", { title: canaryTitle(), priority: "Low", hasDueDate: false,
2319
+ notes: `${AGENT.name} on ${host} checks itself here once a day: WorkFlow, iCloud and Claude Code, end to end. A pass updates the status line; a failure comments, so you hear about it. Leave this task be.` }, 60000);
2320
+ if (r.status !== "applied" || !r.taskID) return fail("WorkFlow applying commands", "WorkFlow didn't apply a command within a minute", "open WorkFlow on the Mac or iPhone that answers the companion (doctor shows which)");
2321
+ taskID = r.taskID;
2322
+ }
2323
+ const nonce = require("crypto").randomBytes(3).toString("hex");
2324
+ const t0 = Date.now();
2325
+ const r = await runSession(cfg, `This is ${AGENT.name}'s scheduled self-test on ${host}. Do exactly one thing: call workflow_set_status_line with task "${taskID}" and text "self-test ${nonce}". Don't comment, don't read or change anything else, then stop.`, 5 * 60000);
2326
+ if (r.timedOut) return fail("Claude session", "it didn't finish within 5 minutes", `a prompt may be waiting on ${host}; check Claude Code there`);
2327
+ if (r.code !== 0) return fail("Claude session", attentionReason(r.out, `Claude Code exited with ${r.code}`), "run claude on that computer and check it's signed in and has usage left");
2328
+ const answered = Math.round((Date.now() - t0) / 1000);
2329
+ for (let i = 0; i < 12; i++) {
2330
+ await cloud.load(true);
2331
+ const t = (cloudSnap.tasks || []).find(x => x.id === taskID);
2332
+ if (t && String(t.pinnedNote || "").includes(nonce)) return { ok: true, answered };
2333
+ await delay(15000);
2334
+ }
2335
+ return fail("the round trip", "Claude's change didn't come back through WorkFlow within 3 minutes", "WorkFlow on the answering device may be asleep or closed; run doctor");
2336
+ })();
2337
+ } catch (e) {
2338
+ result = fail(e instanceof SignInNeeded ? "iCloud sign-in" : "the self-test", String(e.message).split("\n")[0],
2339
+ e instanceof SignInNeeded ? "npx -y @opticell/workflow-companion@latest setup" : "run doctor on that computer");
2340
+ }
2341
+ const when = new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
2342
+ const wasFailing = cfg.canaryLastOk === false;
2343
+ updateConfig(c => { c.canaryLastOk = result.ok; c.canaryLast = Object.assign({ at: new Date().toISOString() }, result); });
2344
+ logWake(`self-test: ${result.ok ? `passed (Claude answered in ${result.answered} s)` : `FAILED at ${result.step}: ${result.detail}`}`);
2345
+ try {
2346
+ if (taskID) {
2347
+ await send("update_task", { taskID, pinnedNote: result.ok ? `✓ Self-test passed ${when} on ${host}: Claude answered in ${result.answered} s.` : `✗ Self-test failed ${when} on ${host}: ${result.step}.` });
2348
+ if (!result.ok) await send("add_comment", { taskID, text: `✗ My daily self-test failed on ${host} (${result.step}): ${result.detail}.\nFix: ${result.fix}.` });
2349
+ else if (wasFailing) await send("add_comment", { taskID, text: `✓ My daily self-test passes again on ${host}.` });
2350
+ }
2351
+ } catch (_) { /* reported on the PC below */ }
2352
+ if (!result.ok) {
2353
+ notify(`${AGENT.name}: self-test failed`, `${result.step}: ${result.detail}. Fix: ${result.fix}`);
2354
+ await reportActivity("attention", { reason: `self-test failed (${result.step}): ${result.detail}`.slice(0, 150) }).catch(() => {});
2355
+ }
2356
+ return result;
2357
+ }
2358
+
2359
+ async function canaryCLI() {
2360
+ const sub = argv[argv.indexOf("canary") + 1];
2361
+ const cfg = readJSON(CP.config, null);
2362
+ if (!cfg) { console.error(signInHelp("Not signed in yet.")); return 2; }
2363
+ if (sub === "off" || sub === "on") {
2364
+ updateConfig(c => { c.canary = sub === "on"; });
2365
+ console.log(sub === "on" ? `The daily self-test is on, at ${cfg.canaryAt || CANARY_DEFAULT_AT} (it needs waking on).` : "The daily self-test is off.");
2366
+ return 0;
2367
+ }
2368
+ if (sub === "at") {
2369
+ const at = argv[argv.indexOf("at") + 1] || "";
2370
+ if (!/^([01]?\d|2[0-3]):[0-5]\d$/.test(at)) { console.error("Give a time as HH:MM, e.g. canary at 07:30"); return 2; }
2371
+ updateConfig(c => { c.canaryAt = at; });
2372
+ console.log(`The daily self-test runs at ${at}.`);
2373
+ return 0;
2374
+ }
2375
+ if (!cfg.claudeBin) { console.error("The self-test runs Claude Code, so waking has to be set up first: workflow-companion wake-on"); return 2; }
2376
+ if (!takeWakeLock(cfg.wakeMaxMinutes || WAKE.maxMinutes)) { console.error("A session is running; try again when it has finished."); return 1; }
2377
+ try {
2378
+ await cloud.load(true);
2379
+ console.log("Running the self-test (up to about 8 minutes)…");
2380
+ const r = await runCanary(Object.assign({}, cfg, readJSON(CP.config, {})));
2381
+ console.log(r.ok ? `✓ Passed: Claude answered in ${r.answered} s, and its change came back through WorkFlow.` : `✗ Failed at ${r.step}: ${r.detail}\n fix: ${r.fix}`);
2382
+ return r.ok ? 0 : 1;
2383
+ } finally { try { fs.unlinkSync(WAKE.lock); } catch (_) {} }
2384
+ }
2385
+
2265
2386
  // ------------------------------------------------------------------ doctor (1.8.1)
2266
2387
  //
2267
2388
  // Checks the whole chain on this computer in one go and says how to fix
@@ -2270,7 +2391,7 @@ async function inboxCLI(on) {
2270
2391
  // Claude Code, and what the last session did. `fromTool` skips the checks
2271
2392
  // that would be odd from inside a Claude session (asking Claude Code about
2272
2393
  // itself).
2273
- async function doctorReport({ fromTool = false } = {}) {
2394
+ async function doctorReport({ fromTool = false, forCanary = false } = {}) {
2274
2395
  const npx = "npx -y @opticell/workflow-companion@latest";
2275
2396
  const lines = [`WorkFlow companion ${SERVER_INFO.version} on ${os.hostname()}, as "${AGENT.name}"`, ""];
2276
2397
  let problems = 0, warnings = 0;
@@ -2370,6 +2491,12 @@ async function doctorReport({ fromTool = false } = {}) {
2370
2491
  else ok("Claude Code has the companion, on @latest");
2371
2492
  }
2372
2493
 
2494
+ if (!forCanary && cfg.wakeEnabled && cfg.canary !== false) {
2495
+ const last = cfg.canaryLast;
2496
+ if (!last) ok(`daily self-test on, at ${cfg.canaryAt || CANARY_DEFAULT_AT}; it hasn't run yet`);
2497
+ else if (last.ok) ok(`last self-test passed ${ago(Date.parse(last.at))}`);
2498
+ else bad(`last self-test failed ${ago(Date.parse(last.at))} (${last.step}): ${last.detail}`, last.fix);
2499
+ }
2373
2500
  const failing = loadMonitors().filter(m => m.state && !m.state.ok);
2374
2501
  if (loadMonitors().length) failing.length ? warn(`monitors failing: ${failing.map(m => `${m.name} (${m.state.detail})`).join(", ")}`) : ok(`${loadMonitors().length} monitor(s), all OK`);
2375
2502
  const pending = loadApprovals().filter(a => a.status === "pending");
@@ -2605,6 +2732,7 @@ const HELP = `WorkFlow companion ${SERVER_INFO.version}
2605
2732
  workflow-companion setup sign this computer in to iCloud for WorkFlow (--name, --takeover, --no-browser)
2606
2733
  workflow-companion status which device answers, how fresh is the snapshot
2607
2734
  workflow-companion doctor check everything on this computer and say how to fix what's wrong
2735
+ workflow-companion canary run the daily self-test now (also: canary on|off, canary at HH:MM)
2608
2736
  workflow-companion keepalive renew the sign-in (what the scheduled job runs)
2609
2737
  workflow-companion update update the scheduled jobs' copy now, and say how to keep Claude Code current
2610
2738
  workflow-companion auto-update on|off whether the scheduled jobs update themselves (on by default)
@@ -2628,29 +2756,35 @@ Needs WorkFlow 5.2+ on an iPhone, iPad or Mac, with the companion switched on in
2628
2756
  Install in one line: see https://github.com/TeamDzX/workflow-companion#install
2629
2757
  `;
2630
2758
 
2631
- if (argv.includes("--print-config")) printConfig();
2632
- else if (argv.includes("--help") || verb === "help") process.stdout.write(HELP);
2633
- else if (verb === "setup") setup().then(c => { process.exitCode = c; }, e => { console.error(e.message || e); process.exitCode = 1; });
2634
- else if (verb === "doctor") doctorReport().then(r => { console.log(r.text); process.exitCode = r.problems ? 1 : 0; }, e => { console.error(e.message || e); process.exitCode = 1; });
2635
- else if (verb === "status") cloudStateText().then(t => console.log(t), e => { console.error(e.message || e); process.exitCode = 1; });
2636
- else if (verb === "keepalive") keepAlive().then(c => { process.exitCode = c; });
2637
- else if (verb === "update") updateNow().then(c => { process.exitCode = c; }, e => { console.error(e.message || e); process.exitCode = 1; });
2638
- else if (verb === "auto-update") process.exitCode = setAutoUpdate(argv[argv.indexOf("auto-update") + 1] !== "off");
2639
- else if (verb === "wake") wake().then(c => { process.exitCode = c; }, e => { logWake(`failed: ${e.message || e}`); process.exitCode = 1; });
2640
- else if (verb === "wake-on" || verb === "wake-off") wakeSetup(verb === "wake-on").then(c => { process.exitCode = c; }, e => { console.error(e.message || e); process.exitCode = 1; });
2641
- else if (verb === "monitor") monitorCLI().then(c => { process.exitCode = c; }, e => { console.error(e.message || e); process.exitCode = 1; });
2642
- else if (verb === "approvals") process.exitCode = setFlag("approvalsEnabled", argv[argv.indexOf("approvals") + 1] === "on",
2643
- "Approvals are on. An assistant can ask to run one exact command on this computer; it runs only after you reply \"approve CODE\" on the task in WorkFlow, once, within 24 hours. Turn off with: workflow-companion approvals off",
2644
- "Approvals are off. Requests are refused and approved ones won't run.");
2645
- else if (verb === "inbox-on" || verb === "inbox-off") inboxCLI(verb === "inbox-on").then(c => { process.exitCode = c; }, e => { console.error(e.message || e); process.exitCode = 1; });
2646
- else if (verb === "notifications") process.exitCode = setFlag("notifications", argv[argv.indexOf("notifications") + 1] !== "off",
2647
- "Notifications are on.", "Notifications are off.");
2648
- else if (verb === "reports") process.exitCode = setFlag("reports", argv[argv.indexOf("reports") + 1] !== "off",
2649
- "Session reports are on: each session attaches a short report to its task.", "Session reports are off.");
2650
- else if (verb === "notify-test") { notify("WorkFlow companion", `Notifications work on ${os.hostname()}.`); console.log("Sent a test notification."); }
2651
- else if (verb === "signout") {
2652
- removeSchedule();
2653
- removeWakeSchedule();
2654
- for (const f of Object.values(CP)) try { fs.unlinkSync(f); } catch (_) {}
2655
- console.log(`Signed out: removed the token, the settings and the keep-alive from ${COMPANION.home}.`);
2656
- } else serve();
2759
+ // Loaded by the shared rule tests (Tools/agent-rules) rather than run.
2760
+ if (require.main !== module) {
2761
+ module.exports = { wakeItems, queueItems, addressingComment, assignedHandoff, agent: () => AGENT };
2762
+ } else {
2763
+ if (argv.includes("--print-config")) printConfig();
2764
+ else if (argv.includes("--help") || verb === "help") process.stdout.write(HELP);
2765
+ else if (verb === "setup") setup().then(c => { process.exitCode = c; }, e => { console.error(e.message || e); process.exitCode = 1; });
2766
+ else if (verb === "canary") canaryCLI().then(c => { process.exitCode = c; }, e => { console.error(e.message || e); process.exitCode = 1; });
2767
+ else if (verb === "doctor") doctorReport().then(r => { console.log(r.text); process.exitCode = r.problems ? 1 : 0; }, e => { console.error(e.message || e); process.exitCode = 1; });
2768
+ else if (verb === "status") cloudStateText().then(t => console.log(t), e => { console.error(e.message || e); process.exitCode = 1; });
2769
+ else if (verb === "keepalive") keepAlive().then(c => { process.exitCode = c; });
2770
+ else if (verb === "update") updateNow().then(c => { process.exitCode = c; }, e => { console.error(e.message || e); process.exitCode = 1; });
2771
+ else if (verb === "auto-update") process.exitCode = setAutoUpdate(argv[argv.indexOf("auto-update") + 1] !== "off");
2772
+ else if (verb === "wake") wake().then(c => { process.exitCode = c; }, e => { logWake(`failed: ${e.message || e}`); process.exitCode = 1; });
2773
+ else if (verb === "wake-on" || verb === "wake-off") wakeSetup(verb === "wake-on").then(c => { process.exitCode = c; }, e => { console.error(e.message || e); process.exitCode = 1; });
2774
+ else if (verb === "monitor") monitorCLI().then(c => { process.exitCode = c; }, e => { console.error(e.message || e); process.exitCode = 1; });
2775
+ else if (verb === "approvals") process.exitCode = setFlag("approvalsEnabled", argv[argv.indexOf("approvals") + 1] === "on",
2776
+ "Approvals are on. An assistant can ask to run one exact command on this computer; it runs only after you reply \"approve CODE\" on the task in WorkFlow, once, within 24 hours. Turn off with: workflow-companion approvals off",
2777
+ "Approvals are off. Requests are refused and approved ones won't run.");
2778
+ else if (verb === "inbox-on" || verb === "inbox-off") inboxCLI(verb === "inbox-on").then(c => { process.exitCode = c; }, e => { console.error(e.message || e); process.exitCode = 1; });
2779
+ else if (verb === "notifications") process.exitCode = setFlag("notifications", argv[argv.indexOf("notifications") + 1] !== "off",
2780
+ "Notifications are on.", "Notifications are off.");
2781
+ else if (verb === "reports") process.exitCode = setFlag("reports", argv[argv.indexOf("reports") + 1] !== "off",
2782
+ "Session reports are on: each session attaches a short report to its task.", "Session reports are off.");
2783
+ else if (verb === "notify-test") { notify("WorkFlow companion", `Notifications work on ${os.hostname()}.`); console.log("Sent a test notification."); }
2784
+ else if (verb === "signout") {
2785
+ removeSchedule();
2786
+ removeWakeSchedule();
2787
+ for (const f of Object.values(CP)) try { fs.unlinkSync(f); } catch (_) {}
2788
+ console.log(`Signed out: removed the token, the settings and the keep-alive from ${COMPANION.home}.`);
2789
+ } else serve();
2790
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opticell/workflow-companion",
3
- "version": "1.8.1",
3
+ "version": "1.9.0",
4
4
  "mcpName": "io.github.TeamDzX/workflow-companion",
5
5
  "description": "Lets Claude on Windows or Linux work in your WorkFlow tasks, notes and projects, through WorkFlow for Mac and your own iCloud. An MCP server.",
6
6
  "keywords": [