@opticell/workflow-companion 1.8.0 → 1.8.2
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 +87 -14
- package/index.js +166 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# WorkFlow Companion
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@opticell/workflow-companion)
|
|
4
|
+
[](https://registry.modelcontextprotocol.io/v0/servers?search=io.github.TeamDzX/workflow-companion)
|
|
5
|
+
|
|
6
|
+
> **This repository** is WorkFlow Companion's home: the source (`src/index.js`,
|
|
7
|
+
> MIT licensed), downloads (`WorkFlow.mcpb` for Claude Desktop) on the
|
|
8
|
+
> [Releases](https://github.com/TeamDzX/workflow-companion/releases) page,
|
|
9
|
+
> and bugs and requests in [Issues](https://github.com/TeamDzX/workflow-companion/issues).
|
|
10
|
+
> The package itself installs from npm. On a Mac, use the open-source
|
|
11
|
+
> [workflow-agent-bridge](https://github.com/TeamDzX/workflow-agent-bridge) instead.
|
|
12
|
+
> All of Opticell's free tools for Claude: [opticell-mcp](https://github.com/TeamDzX/opticell-mcp).
|
|
13
|
+
|
|
3
14
|
Lets **Claude on Windows or Linux** work in your WorkFlow tasks, notes and
|
|
4
15
|
projects: reading the queue, replying in threads, ticking checklists, filing
|
|
5
16
|
notes, updating documents. It works in Claude Code, Claude Desktop, or any MCP
|
|
@@ -28,12 +39,23 @@ many devices are on.
|
|
|
28
39
|
|
|
29
40
|
## Install
|
|
30
41
|
|
|
31
|
-
|
|
42
|
+
One line installs Node.js if needed, signs this computer in, adds the
|
|
43
|
+
companion to Claude Code and offers to let it start work on its own:
|
|
44
|
+
|
|
45
|
+
**Windows** (PowerShell):
|
|
32
46
|
|
|
33
|
-
|
|
47
|
+
```powershell
|
|
48
|
+
irm https://raw.githubusercontent.com/TeamDzX/workflow-companion/main/install.ps1 | iex
|
|
49
|
+
```
|
|
34
50
|
|
|
35
|
-
|
|
36
|
-
|
|
51
|
+
**Mac or Linux** (Terminal):
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
curl -fsSL https://raw.githubusercontent.com/TeamDzX/workflow-companion/main/install.sh | sh
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Switch the companion on in WorkFlow first (step 1 below). The steps that
|
|
58
|
+
follow are what the installer does, if you'd rather do them by hand.
|
|
37
59
|
|
|
38
60
|
## Set up
|
|
39
61
|
|
|
@@ -158,24 +180,60 @@ npx -y @opticell/workflow-companion wake-on
|
|
|
158
180
|
| `workflow-companion` | Runs the MCP server. This is what Claude starts. |
|
|
159
181
|
| `workflow-companion setup` | Signs this computer in. Takes `--web-token` to skip the prompt. |
|
|
160
182
|
| `workflow-companion status` | Which device answers, how fresh the snapshot is, any receipts that arrived late. |
|
|
183
|
+
| `workflow-companion doctor` | Checks everything on this computer and says how to fix what's wrong. Start here when something isn't working. |
|
|
161
184
|
| `workflow-companion keepalive` | Renews the sign-in. The scheduled job runs this. |
|
|
162
185
|
| `workflow-companion update` | Updates the scheduled jobs' copy to the latest release now, and shows how to keep Claude Code on `@latest`. |
|
|
163
186
|
| `workflow-companion auto-update on\|off` | Whether the scheduled jobs update themselves (on by default). |
|
|
164
187
|
| `workflow-companion wake-on` | Starts a Claude Code session on its own when work for its name arrives. See above. |
|
|
165
188
|
| `workflow-companion wake-off` | Stops that. Work waits until you ask Claude here. |
|
|
166
189
|
| `workflow-companion wake` | One check of the queue. The scheduled job runs this. |
|
|
190
|
+
| `workflow-companion monitor add\|list\|check\|remove` | Checks this computer runs without using Claude. See below. |
|
|
191
|
+
| `workflow-companion approvals on\|off` | Lets an assistant run one exact command after you reply "approve CODE" (off by default). |
|
|
192
|
+
| `workflow-companion inbox-on\|inbox-off` | Files dropped in a folder become tasks. |
|
|
193
|
+
| `workflow-companion notifications on\|off` | Notices on this computer (on by default). `notify-test` shows one. |
|
|
194
|
+
| `workflow-companion reports on\|off` | A short report attached to the task after each session (on by default). |
|
|
167
195
|
| `workflow-companion signout` | Deletes the sign-in, cached data and both scheduled jobs. |
|
|
168
196
|
|
|
169
197
|
## On this computer, by itself (1.8)
|
|
170
198
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
199
|
+
These run in the same 2-minute scheduled job as waking, and cost no Claude
|
|
200
|
+
usage.
|
|
201
|
+
|
|
202
|
+
**Monitors.** Checks that wake Claude only when something changes:
|
|
203
|
+
|
|
204
|
+
```sh
|
|
205
|
+
workflow-companion monitor add --name site --http https://example.com --task "Server status" --wake
|
|
206
|
+
workflow-companion monitor add --name disk --disk C:\ --min-gb 20 --task "Server status"
|
|
207
|
+
workflow-companion monitor add --name api --port 10.0.0.5:443 --every 2
|
|
208
|
+
workflow-companion monitor add --name backup --file D:\backups\latest.zip --task "Backups"
|
|
209
|
+
workflow-companion monitor add --name nightly --command "check-backups.cmd" --task "Backups" --wake
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
A change (down, back up, the file changed) is posted on the task, shows a
|
|
213
|
+
notification here, and with `--wake` starts a session about it. An assistant
|
|
214
|
+
can set up website, port, disk, process and file monitors itself
|
|
215
|
+
(`workflow_monitor`), but **only you can add a command monitor**, because it
|
|
216
|
+
runs a command every few minutes.
|
|
217
|
+
|
|
218
|
+
**Approvals.** An assistant can ask to run one exact command on this computer
|
|
219
|
+
(restart a service, install an update). It posts the command on the task with
|
|
220
|
+
a code; reply `approve K7Q2` (or `decline K7Q2`) from your phone. Only a
|
|
221
|
+
person's reply counts, never another assistant's; tasks in shared projects
|
|
222
|
+
can't ask; the command runs once, exactly as shown, within 24 hours. **Off
|
|
223
|
+
until you run `workflow-companion approvals on` on this computer.**
|
|
224
|
+
|
|
225
|
+
**Inbox folder.** `workflow-companion inbox-on` makes `~/WorkFlow Inbox`
|
|
226
|
+
(or `--folder`): drop a file in and it becomes a task with the file attached.
|
|
227
|
+
A subfolder named after a project files it into that project. Filed files
|
|
228
|
+
move to `Filed/<date>`. `--assign` puts them in this assistant's queue.
|
|
229
|
+
|
|
230
|
+
**Notifications.** A notice on this computer when work arrives, a monitor
|
|
231
|
+
changes, an approval is answered, a session needs attention, or the iCloud
|
|
232
|
+
sign-in has ended (with the command that fixes it).
|
|
233
|
+
|
|
234
|
+
**Session reports.** After each session that changed something, a short
|
|
235
|
+
report is attached to its task: what it was asked, what it changed, any
|
|
236
|
+
approved commands it ran, and its last words.
|
|
179
237
|
|
|
180
238
|
## Worth knowing
|
|
181
239
|
|
|
@@ -204,11 +262,26 @@ dropped files become tasks), **notifications** on the computer, and a
|
|
|
204
262
|
|
|
205
263
|
## Tools
|
|
206
264
|
|
|
207
|
-
|
|
265
|
+
36 tools, including:
|
|
208
266
|
`workflow_queue`, `workflow_get_task`, `workflow_comment`,
|
|
209
267
|
`workflow_set_status_line`, `workflow_check_subtasks`, `workflow_update_task`,
|
|
210
268
|
`workflow_create_task`, `workflow_list_notes`, `workflow_reply_note`,
|
|
211
269
|
`workflow_list_projects`, `workflow_list_documents`, `workflow_update_document`,
|
|
212
|
-
`workflow_draft_email` and
|
|
270
|
+
`workflow_draft_email`, `workflow_state`, and from 1.7 and 1.8
|
|
271
|
+
`workflow_hand_off`, `workflow_watch`, `workflow_house_rules`,
|
|
272
|
+
`workflow_monitor`, `workflow_request_approval`, `workflow_check_approval`
|
|
273
|
+
and `workflow_run_approved`.
|
|
213
274
|
|
|
214
275
|
© Opticell Limited · <https://www.opticell-limited.com/companion>
|
|
276
|
+
|
|
277
|
+
## Source and licence
|
|
278
|
+
|
|
279
|
+
`src/index.js` is the whole server: one file, Node 18 or later, no
|
|
280
|
+
dependencies. It is MIT licensed (see `LICENSE`). The published npm package
|
|
281
|
+
is the same file with WorkFlow's CloudKit API token filled in at build time
|
|
282
|
+
(the token is not a secret: it only lets a signed-in user reach their own
|
|
283
|
+
data). Run from source, `setup` asks for it with `--api-token`.
|
|
284
|
+
|
|
285
|
+
The `Dockerfile` runs the server over stdio. MCP directories use it to check
|
|
286
|
+
that the server starts and lists its tools, which it does without signing in.
|
|
287
|
+
|
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.
|
|
30
|
+
const SERVER_INFO = { name: "workflow", version: "1.8.2" };
|
|
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
|
-
|
|
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
|
}
|
|
@@ -1168,6 +1172,12 @@ const TOOLS = [
|
|
|
1168
1172
|
return `Ran ${x.code} on ${os.hostname()} (${result.seconds} s): exit ${result.exit}\n${result.output || "(no output)"}`;
|
|
1169
1173
|
},
|
|
1170
1174
|
},
|
|
1175
|
+
{
|
|
1176
|
+
name: "workflow_doctor",
|
|
1177
|
+
description: "Check the companion on this computer end to end (sign-in, WorkFlow publishing and answering, version, scheduled jobs, Claude Code, last session, monitors) and say how to fix anything wrong. Use it when something isn't working.",
|
|
1178
|
+
inputSchema: { type: "object", properties: {} },
|
|
1179
|
+
handler: async () => (await doctorReport({ fromTool: true })).text,
|
|
1180
|
+
},
|
|
1171
1181
|
{
|
|
1172
1182
|
name: "workflow_ping",
|
|
1173
1183
|
description: "Confirm the app is watching the bridge (round-trips one command).",
|
|
@@ -1878,17 +1888,20 @@ function notify(title, body, onceKey) {
|
|
|
1878
1888
|
if (process.env.WF_NOTIFY_LOG) { try { fs.appendFileSync(process.env.WF_NOTIFY_LOG, `${title} | ${body}\n`); } catch (_) {} return; }
|
|
1879
1889
|
const { spawn } = require("child_process");
|
|
1880
1890
|
const opts = { detached: true, stdio: "ignore", windowsHide: true, env: Object.assign({}, process.env, { WF_T: title, WF_B: body }) };
|
|
1891
|
+
// A missing notifier (no notify-send on a server) fails asynchronously:
|
|
1892
|
+
// without a listener that error would end the whole process.
|
|
1893
|
+
const quiet = child => { child.on("error", () => {}); child.unref(); };
|
|
1881
1894
|
try {
|
|
1882
1895
|
if (process.platform === "win32") {
|
|
1883
1896
|
const ps = "[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null;" +
|
|
1884
1897
|
"$t = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02);" +
|
|
1885
1898
|
"$x = $t.GetElementsByTagName('text'); $x.Item(0).AppendChild($t.CreateTextNode($env:WF_T)) > $null; $x.Item(1).AppendChild($t.CreateTextNode($env:WF_B)) > $null;" +
|
|
1886
1899
|
"[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\\WindowsPowerShell\\v1.0\\powershell.exe').Show([Windows.UI.Notifications.ToastNotification]::new($t))";
|
|
1887
|
-
spawn("powershell.exe", ["-NoProfile", "-NonInteractive", "-WindowStyle", "Hidden", "-Command", ps], opts)
|
|
1900
|
+
quiet(spawn("powershell.exe", ["-NoProfile", "-NonInteractive", "-WindowStyle", "Hidden", "-Command", ps], opts));
|
|
1888
1901
|
} else if (process.platform === "darwin") {
|
|
1889
|
-
spawn("osascript", ["-e", "display notification (system attribute \"WF_B\") with title (system attribute \"WF_T\")"], opts)
|
|
1902
|
+
quiet(spawn("osascript", ["-e", "display notification (system attribute \"WF_B\") with title (system attribute \"WF_T\")"], opts));
|
|
1890
1903
|
} else {
|
|
1891
|
-
spawn("notify-send", [title, body], opts)
|
|
1904
|
+
quiet(spawn("notify-send", [title, body], opts));
|
|
1892
1905
|
}
|
|
1893
1906
|
} catch (_) {}
|
|
1894
1907
|
}
|
|
@@ -2253,6 +2266,122 @@ async function inboxCLI(on) {
|
|
|
2253
2266
|
return 0;
|
|
2254
2267
|
}
|
|
2255
2268
|
|
|
2269
|
+
// ------------------------------------------------------------------ doctor (1.8.1)
|
|
2270
|
+
//
|
|
2271
|
+
// Checks the whole chain on this computer in one go and says how to fix
|
|
2272
|
+
// anything that's wrong: Node, the iCloud sign-in, whether WorkFlow is
|
|
2273
|
+
// publishing and answering, the name, the version, the scheduled jobs,
|
|
2274
|
+
// Claude Code, and what the last session did. `fromTool` skips the checks
|
|
2275
|
+
// that would be odd from inside a Claude session (asking Claude Code about
|
|
2276
|
+
// itself).
|
|
2277
|
+
async function doctorReport({ fromTool = false } = {}) {
|
|
2278
|
+
const npx = "npx -y @opticell/workflow-companion@latest";
|
|
2279
|
+
const lines = [`WorkFlow companion ${SERVER_INFO.version} on ${os.hostname()}, as "${AGENT.name}"`, ""];
|
|
2280
|
+
let problems = 0, warnings = 0;
|
|
2281
|
+
const ok = l => lines.push(` ✓ ${l}`);
|
|
2282
|
+
const bad = (l, fix) => { problems++; lines.push(` ✗ ${l}`); if (fix) lines.push(` fix: ${fix}`); };
|
|
2283
|
+
const warn = (l, fix) => { warnings++; lines.push(` ! ${l}`); if (fix) lines.push(` ${fix}`); };
|
|
2284
|
+
const done = () => {
|
|
2285
|
+
lines.push("", problems ? `${problems} problem(s)${warnings ? `, ${warnings} warning(s)` : ""}. Fix the ✗ lines first.`
|
|
2286
|
+
: warnings ? `Working, with ${warnings} warning(s).` : "All good.");
|
|
2287
|
+
return { text: lines.join("\n"), problems, warnings };
|
|
2288
|
+
};
|
|
2289
|
+
const ago = t => { const m = Math.round((Date.now() - t) / 60000); return m < 1 ? "just now" : m < 90 ? `${m} min ago` : m < 2880 ? `${Math.round(m / 60)} h ago` : `${Math.round(m / 1440)} days ago`; };
|
|
2290
|
+
const { spawnSync } = require("child_process");
|
|
2291
|
+
|
|
2292
|
+
const major = parseInt(process.versions.node, 10);
|
|
2293
|
+
major >= 18 ? ok(`Node.js ${process.versions.node}`) : bad(`Node.js ${process.versions.node} is too old (18 or later)`, "install the LTS from https://nodejs.org");
|
|
2294
|
+
if (!CLOUD) { warn("this is the Mac's own bridge, not a companion", "on the Mac, check Settings → Agent Bridge, or run: wf state"); return done(); }
|
|
2295
|
+
|
|
2296
|
+
const cfg = readJSON(CP.config, null);
|
|
2297
|
+
if (!cfg || !fs.existsSync(CP.token)) { bad("not signed in on this computer", `${npx} setup`); return done(); }
|
|
2298
|
+
try {
|
|
2299
|
+
await cloud.call("public/users/caller");
|
|
2300
|
+
ok(`iCloud sign-in works (${cfg.env || COMPANION.env})`);
|
|
2301
|
+
} catch (e) {
|
|
2302
|
+
if (e instanceof SignInNeeded) bad("the iCloud sign-in has ended (Apple ends one left unused for 9 to 14 hours)", `${npx} setup`);
|
|
2303
|
+
else bad(`can't reach iCloud: ${String(e.message).split("\n")[0]}`, "check this computer's internet connection, then run doctor again");
|
|
2304
|
+
return done();
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
try { await cloud.load(true); } catch (e) { bad(`couldn't read WorkFlow's snapshot: ${e.message}`); return done(); }
|
|
2308
|
+
if (!cloudSnap) {
|
|
2309
|
+
bad("WorkFlow hasn't published anything for the companion yet",
|
|
2310
|
+
"switch it on in WorkFlow: iPhone/iPad Settings → Windows Companion → Answer the Companion, or Mac Settings → Agent Bridge → Cloud Bridge");
|
|
2311
|
+
} else {
|
|
2312
|
+
const age = Date.now() - Date.parse(cloudSnap.generatedAt);
|
|
2313
|
+
const line = `${cloudSnap.tasks.length} tasks, published ${ago(Date.parse(cloudSnap.generatedAt))}; answering: ${cloud.macLine()}`;
|
|
2314
|
+
age < 6 * 3600 * 1000 ? ok(line) : warn(line, "nothing has changed in WorkFlow for a while, or the device that answers is asleep or closed: open WorkFlow on it");
|
|
2315
|
+
const clash = nameClash(AGENT.name);
|
|
2316
|
+
clash ? bad(`"${AGENT.name}" is also the name of Claude on ${clash.host}: both would act on @${AGENT.name}`, `${npx} setup --name <another name>`)
|
|
2317
|
+
: ok(`the name "${AGENT.name}" is this computer's alone`);
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
if (!fromTool) {
|
|
2321
|
+
const started = Date.now();
|
|
2322
|
+
try {
|
|
2323
|
+
const r = await send("ping", {}, 30000);
|
|
2324
|
+
r.status === "applied" ? ok(`a test command was applied by WorkFlow in ${Math.max(1, Math.round((Date.now() - started) / 1000))} s`)
|
|
2325
|
+
: warn("a test command got no answer within 30 s", "WorkFlow on the answering device may be asleep or closed. Commands wait safely and run when it next looks; an iPhone can take minutes");
|
|
2326
|
+
} catch (e) { warn(`couldn't send a test command: ${e.message}`); }
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
const rel = await latestRelease(true);
|
|
2330
|
+
if (!rel) warn("couldn't check npm for a newer release");
|
|
2331
|
+
else if (newerVersion(rel.version, SERVER_INFO.version)) warn(`version ${SERVER_INFO.version}; ${rel.version} is out`, `${npx} update (and restart Claude Code)`);
|
|
2332
|
+
else ok(`version ${SERVER_INFO.version} is the latest`);
|
|
2333
|
+
const saved = versionOfScript(SAVED_SCRIPT);
|
|
2334
|
+
if (saved && rel && newerVersion(rel.version, saved)) warn(`the scheduled jobs run ${saved}`, cfg.autoUpdate === false ? `auto-update is off: ${npx} update` : "they update themselves within 6 hours, or now with: " + `${npx} update`);
|
|
2335
|
+
|
|
2336
|
+
const scheduled = name => {
|
|
2337
|
+
if (process.platform === "win32") return spawnSync("schtasks", ["/Query", "/TN", name], { encoding: "utf8", windowsHide: true }).status === 0;
|
|
2338
|
+
const r = spawnSync("crontab", ["-l"], { encoding: "utf8" });
|
|
2339
|
+
return r.status === 0 && r.stdout.includes(name === TASK_NAME ? "workflow-companion keepalive" : "workflow-companion wake");
|
|
2340
|
+
};
|
|
2341
|
+
let lastKeepAlive = "";
|
|
2342
|
+
try { lastKeepAlive = fs.readFileSync(KEEPALIVE_LOG, "utf8").trim().split("\n").pop(); } catch (_) {}
|
|
2343
|
+
scheduled(TASK_NAME) ? ok(`keep-alive scheduled${lastKeepAlive ? `; last run ${lastKeepAlive.split(" ")[0].slice(0, 16).replace("T", " ")}: ${lastKeepAlive.split(" ").slice(1).join(" ")}` : ""}`)
|
|
2344
|
+
: warn("the keep-alive isn't scheduled, so the sign-in can end while Claude isn't running", `${npx} setup (it schedules it)`);
|
|
2345
|
+
|
|
2346
|
+
if (cfg.wakeEnabled) {
|
|
2347
|
+
scheduled(WAKE.taskName) ? ok("waking is on and scheduled every 2 minutes") : bad("waking is on but its scheduled job is missing", `${npx} wake-on`);
|
|
2348
|
+
const bin = cfg.claudeBin || "claude";
|
|
2349
|
+
const v = spawnSync(bin, ["--version"], { encoding: "utf8", timeout: 20000, windowsHide: true, shell: process.platform === "win32" && /\.(cmd|bat)$/i.test(bin) });
|
|
2350
|
+
v.status === 0 ? ok(`Claude Code runs: ${String(v.stdout).trim().split("\n")[0]}`)
|
|
2351
|
+
: bad(`Claude Code at ${bin} doesn't run`, `reinstall or sign in to Claude Code, then: ${npx} wake-on`);
|
|
2352
|
+
let log = [];
|
|
2353
|
+
try { log = fs.readFileSync(WAKE.log, "utf8").trim().split("\n"); } catch (_) {}
|
|
2354
|
+
const last = log.filter(l => / session exited /.test(l)).pop();
|
|
2355
|
+
if (!last) ok("no sessions yet");
|
|
2356
|
+
else {
|
|
2357
|
+
const when = Date.parse(last.split(" ")[0]);
|
|
2358
|
+
const code = (/session exited (-?\d+)/.exec(last) || [])[1];
|
|
2359
|
+
code === "0" && !/time limit/.test(last) ? ok(`last session ${ago(when)} finished normally`)
|
|
2360
|
+
: warn(`last session ${ago(when)} didn't finish normally: ${last.split(" ").slice(1).join(" ").slice(0, 160)}`, `see ${WAKE.log}`);
|
|
2361
|
+
}
|
|
2362
|
+
if (log.slice(-30).some(l => /SIGN-IN ENDED/.test(l))) warn("the sign-in ended recently (it works now)", "if this PC sleeps for long, leave it awake or check it each morning");
|
|
2363
|
+
} else {
|
|
2364
|
+
warn("waking is off: work waits until you ask Claude on this computer", `${npx} wake-on`);
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
if (!fromTool) {
|
|
2368
|
+
const bin = cfg.claudeBin || "claude";
|
|
2369
|
+
const list = spawnSync(bin, ["mcp", "list"], { encoding: "utf8", timeout: 60000, windowsHide: true, shell: process.platform === "win32" && /\.(cmd|bat)$/i.test(bin) });
|
|
2370
|
+
const text = String(list.stdout || "");
|
|
2371
|
+
if (list.status !== 0 && !text) warn("couldn't ask Claude Code which tools it has", `if it isn't installed: ${claudeCodeCommand()}`);
|
|
2372
|
+
else if (!/workflow/i.test(text)) bad("Claude Code doesn't have the companion", claudeCodeCommand());
|
|
2373
|
+
else if (!/@latest/.test(text.split("\n").find(l => /^workflow/i.test(l)) || "")) warn("Claude Code runs a pinned version of the companion", `claude mcp remove workflow -s user, then: ${claudeCodeCommand()}`);
|
|
2374
|
+
else ok("Claude Code has the companion, on @latest");
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
const failing = loadMonitors().filter(m => m.state && !m.state.ok);
|
|
2378
|
+
if (loadMonitors().length) failing.length ? warn(`monitors failing: ${failing.map(m => `${m.name} (${m.state.detail})`).join(", ")}`) : ok(`${loadMonitors().length} monitor(s), all OK`);
|
|
2379
|
+
const pending = loadApprovals().filter(a => a.status === "pending");
|
|
2380
|
+
if (pending.length) warn(`${pending.length} approval(s) waiting for you: ${pending.map(a => `${a.code} on “${a.taskTitle}”`).join(", ")}`);
|
|
2381
|
+
if (cfg.inboxFolder) fs.existsSync(cfg.inboxFolder) ? ok(`inbox folder: ${cfg.inboxFolder}`) : bad(`the inbox folder ${cfg.inboxFolder} is missing`, `${npx} inbox-on --folder "<path>"`);
|
|
2382
|
+
return done();
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2256
2385
|
// ------------------------------------------------------------------ versions (1.6.1)
|
|
2257
2386
|
|
|
2258
2387
|
// Each companion says which version it runs, and keeps itself current.
|
|
@@ -2479,6 +2608,7 @@ const HELP = `WorkFlow companion ${SERVER_INFO.version}
|
|
|
2479
2608
|
workflow-companion run the MCP server (what Claude starts)
|
|
2480
2609
|
workflow-companion setup sign this computer in to iCloud for WorkFlow (--name, --takeover, --no-browser)
|
|
2481
2610
|
workflow-companion status which device answers, how fresh is the snapshot
|
|
2611
|
+
workflow-companion doctor check everything on this computer and say how to fix what's wrong
|
|
2482
2612
|
workflow-companion keepalive renew the sign-in (what the scheduled job runs)
|
|
2483
2613
|
workflow-companion update update the scheduled jobs' copy now, and say how to keep Claude Code current
|
|
2484
2614
|
workflow-companion auto-update on|off whether the scheduled jobs update themselves (on by default)
|
|
@@ -2502,28 +2632,34 @@ Needs WorkFlow 5.2+ on an iPhone, iPad or Mac, with the companion switched on in
|
|
|
2502
2632
|
Install in one line: see https://github.com/TeamDzX/workflow-companion#install
|
|
2503
2633
|
`;
|
|
2504
2634
|
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
else
|
|
2509
|
-
|
|
2510
|
-
else if (verb === "
|
|
2511
|
-
else if (verb === "
|
|
2512
|
-
else if (verb === "
|
|
2513
|
-
else if (verb === "
|
|
2514
|
-
else if (verb === "
|
|
2515
|
-
else if (verb === "
|
|
2516
|
-
|
|
2517
|
-
"
|
|
2518
|
-
else if (verb === "
|
|
2519
|
-
else if (verb === "
|
|
2520
|
-
"
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
else if (verb === "
|
|
2524
|
-
else if (verb === "
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2635
|
+
// Loaded by the shared rule tests (Tools/agent-rules) rather than run.
|
|
2636
|
+
if (require.main !== module) {
|
|
2637
|
+
module.exports = { wakeItems, queueItems, addressingComment, assignedHandoff, agent: () => AGENT };
|
|
2638
|
+
} else {
|
|
2639
|
+
if (argv.includes("--print-config")) printConfig();
|
|
2640
|
+
else if (argv.includes("--help") || verb === "help") process.stdout.write(HELP);
|
|
2641
|
+
else if (verb === "setup") setup().then(c => { process.exitCode = c; }, e => { console.error(e.message || e); process.exitCode = 1; });
|
|
2642
|
+
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; });
|
|
2643
|
+
else if (verb === "status") cloudStateText().then(t => console.log(t), e => { console.error(e.message || e); process.exitCode = 1; });
|
|
2644
|
+
else if (verb === "keepalive") keepAlive().then(c => { process.exitCode = c; });
|
|
2645
|
+
else if (verb === "update") updateNow().then(c => { process.exitCode = c; }, e => { console.error(e.message || e); process.exitCode = 1; });
|
|
2646
|
+
else if (verb === "auto-update") process.exitCode = setAutoUpdate(argv[argv.indexOf("auto-update") + 1] !== "off");
|
|
2647
|
+
else if (verb === "wake") wake().then(c => { process.exitCode = c; }, e => { logWake(`failed: ${e.message || e}`); process.exitCode = 1; });
|
|
2648
|
+
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; });
|
|
2649
|
+
else if (verb === "monitor") monitorCLI().then(c => { process.exitCode = c; }, e => { console.error(e.message || e); process.exitCode = 1; });
|
|
2650
|
+
else if (verb === "approvals") process.exitCode = setFlag("approvalsEnabled", argv[argv.indexOf("approvals") + 1] === "on",
|
|
2651
|
+
"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",
|
|
2652
|
+
"Approvals are off. Requests are refused and approved ones won't run.");
|
|
2653
|
+
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; });
|
|
2654
|
+
else if (verb === "notifications") process.exitCode = setFlag("notifications", argv[argv.indexOf("notifications") + 1] !== "off",
|
|
2655
|
+
"Notifications are on.", "Notifications are off.");
|
|
2656
|
+
else if (verb === "reports") process.exitCode = setFlag("reports", argv[argv.indexOf("reports") + 1] !== "off",
|
|
2657
|
+
"Session reports are on: each session attaches a short report to its task.", "Session reports are off.");
|
|
2658
|
+
else if (verb === "notify-test") { notify("WorkFlow companion", `Notifications work on ${os.hostname()}.`); console.log("Sent a test notification."); }
|
|
2659
|
+
else if (verb === "signout") {
|
|
2660
|
+
removeSchedule();
|
|
2661
|
+
removeWakeSchedule();
|
|
2662
|
+
for (const f of Object.values(CP)) try { fs.unlinkSync(f); } catch (_) {}
|
|
2663
|
+
console.log(`Signed out: removed the token, the settings and the keep-alive from ${COMPANION.home}.`);
|
|
2664
|
+
} else serve();
|
|
2665
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opticell/workflow-companion",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.2",
|
|
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": [
|