flowcollab 0.3.17 → 0.3.19

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/bin/_client.mjs CHANGED
@@ -9,6 +9,7 @@ import 'dotenv/config';
9
9
  import { homedir } from 'os';
10
10
  import { readFileSync, writeFileSync, mkdirSync, chmodSync, renameSync } from 'fs';
11
11
  import { join } from 'path';
12
+ import { get as httpsGet } from 'https';
12
13
 
13
14
  // On Windows, Node's bundled CA store doesn't include certs added by corporate/AV
14
15
  // TLS inspection software, causing fetch() to throw "fetch failed". Re-spawn with
@@ -44,21 +45,31 @@ process.on('exit', () => {
44
45
  });
45
46
 
46
47
  if (!_uc.checkedAt || (Date.now() - _uc.checkedAt) > 86_400_000) {
47
- // B21 (audit): AbortSignal.timeout keeps a 4s timer alive that pins the event loop open AFTER the
48
- // command is done (up to 4s of dead time on exit). Use a manual controller with an UNREF'd timer so
49
- // the process can exit as soon as the real work finishes the check is fire-and-forget either way.
50
- const _uctrl = new AbortController();
51
- const _utimer = setTimeout(() => _uctrl.abort(), 4000);
52
- _utimer.unref?.();
53
- fetch('https://registry.npmjs.org/flowcollab/latest', { signal: _uctrl.signal })
54
- .then(r => r.json())
55
- .then(({ version: latest }) => {
56
- mkdirSync(join(homedir(), '.flow'), { recursive: true });
57
- writeFileSync(_updateCache, JSON.stringify({ checkedAt: Date.now(), latest }));
58
- _uc = { checkedAt: Date.now(), latest };
59
- })
60
- .catch(() => {})
61
- .finally(() => clearTimeout(_utimer));
48
+ // B21/CLI-6 (audit): a pending network request pins the event loop open AFTER the command is done.
49
+ // fetch()'s undici socket can't be unref'd per-request, so use https.get + UNREF the socket the
50
+ // request never delays the CLI's exit. Stamp checkedAt SYNCHRONOUSLY first so the 24h gate holds
51
+ // even for instant commands that exit before the request finishes (else unref'ing would re-check
52
+ // every run); the request just updates `latest` in the background when it completes.
53
+ try {
54
+ mkdirSync(join(homedir(), '.flow'), { recursive: true });
55
+ writeFileSync(_updateCache, JSON.stringify({ checkedAt: Date.now(), latest: _uc.latest ?? null }));
56
+ } catch {}
57
+ try {
58
+ const _ureq = httpsGet('https://registry.npmjs.org/flowcollab/latest', { headers: { accept: 'application/json' } }, (res) => {
59
+ if (res.statusCode !== 200) { res.resume(); return; }
60
+ let buf = '';
61
+ res.on('data', d => { buf += d; });
62
+ res.on('end', () => {
63
+ try {
64
+ const { version: latest } = JSON.parse(buf);
65
+ if (latest) { writeFileSync(_updateCache, JSON.stringify({ checkedAt: Date.now(), latest })); _uc = { checkedAt: Date.now(), latest }; }
66
+ } catch {}
67
+ });
68
+ });
69
+ _ureq.on('socket', s => s.unref?.()); // don't let the update check hold the CLI's exit
70
+ _ureq.on('error', () => {});
71
+ _ureq.setTimeout(4000, () => _ureq.destroy());
72
+ } catch {}
62
73
  }
63
74
 
64
75
  function loadGlobalConfig() {
@@ -0,0 +1,75 @@
1
+ /* Single source of truth for the Flow CLI command catalog.
2
+ *
3
+ * Consumed by:
4
+ * - bin/flow.mjs → the `flow <cmd>` router + `flow --help` list
5
+ * - routes/flow.js → the "All CLI commands" block in GET /claudemd
6
+ * - test/commands.test.js → drift guard (every command here has a bin file,
7
+ * a package.json bin entry, and a ui/docs.html card)
8
+ *
9
+ * Add a command in ONE place (here) and every surface stays in sync.
10
+ * Fields:
11
+ * name — the subcommand (`flow <name>` / `flow-<name>`)
12
+ * file — the bin module it dispatches to
13
+ * summary — one line for `flow --help`
14
+ * usage — full signature + flags for the docs / CLAUDE.md reference
15
+ */
16
+ export const CLI_COMMANDS = [
17
+ { name: 'login', file: 'login.mjs', summary: 'Authorize the CLI via browser (device flow)',
18
+ usage: 'flow-login [--server=<url>] — browser device-flow auth; writes ~/.flow/config.json' },
19
+ { name: 'pull', file: 'pull.mjs', summary: 'Fetch board snapshot + @mentions (start of day)',
20
+ usage: 'flow-pull [--focus] [--milestone=<name>] [--sync-md] — board snapshot + mentions + handoffs; --sync-md rewrites CLAUDE.md' },
21
+ { name: 'claim', file: 'claim.mjs', summary: 'Claim a task and mark it in-progress',
22
+ usage: 'flow-claim <id> [--files=a,b] [--watch] — self-assign; warns on file conflicts; --watch retries on 409' },
23
+ { name: 'comment', file: 'comment.mjs', summary: 'Post a comment (--commit[=<sha>] links a clickable diff)',
24
+ usage: 'flow-comment <id> "..." [--commit[=<sha>]] — add a comment; --commit links a commit (or HEAD) → clickable diff' },
25
+ { name: 'close', file: 'close.mjs', summary: 'Mark a task done with a summary',
26
+ usage: 'flow-close <id> "summary" — mark task done' },
27
+ { name: 'create', file: 'create.mjs', summary: 'Create a new task',
28
+ usage: 'flow-create --title="..." [--type=] [--area=] [--priority=] [--due=] [--blocked-by=<uuid>] [--milestone=] [--from-issue=<num>] [--template=<id>]' },
29
+ { name: 'edit', file: 'edit.mjs', summary: 'Update task fields (title, priority, area, due…)',
30
+ usage: 'flow-edit <id> [--title=] [--priority=] [--area=] [--due=] [--milestone=] [--blocked-by=<uuid>|null]' },
31
+ { name: 'unblock', file: 'unblock.mjs', summary: "Clear a task's blocked-by dependency",
32
+ usage: 'flow-unblock <id> — clear blocked_by' },
33
+ { name: 'propose', file: 'propose.mjs', summary: 'Propose a sub-task for human approval',
34
+ usage: 'flow-propose --parent=<id> --title="..." --md="..." — propose work for approval' },
35
+ { name: 'approve', file: 'approve.mjs', summary: 'Approve a pending proposal',
36
+ usage: 'flow-approve <id> — approve a proposal (owner only)' },
37
+ { name: 'reject', file: 'reject.mjs', summary: 'Reject a pending proposal with a reason',
38
+ usage: 'flow-reject <id> "reason" — reject a proposal (owner only)' },
39
+ { name: 'assign', file: 'assign.mjs', summary: 'Assign a task to another actor',
40
+ usage: 'flow-assign <id> <actor> — reassign (owner only)' },
41
+ { name: 'decisions', file: 'decisions.mjs', summary: 'List pending proposals (--watch <id> blocks until resolved)',
42
+ usage: 'flow-decisions [--watch <id>] — list pending; --watch blocks until <id> resolves (exit 0/1/2/3/4)' },
43
+ { name: 'handoff', file: 'handoff.mjs', summary: 'Hand off a task to another agent',
44
+ usage: 'flow-handoff --task=<id> --to=<actor> --context="..." [--branch=] [--questions="q1|q2"] [--next-step="..."]' },
45
+ { name: 'review', file: 'review.mjs', summary: 'Request code review; moves task to in-review',
46
+ usage: 'flow-review <id> [--pr=<num>] [--reviewer=<actor>] [--context="..."]' },
47
+ { name: 'search', file: 'search.mjs', summary: 'Search tasks by text, status, area, or assignee',
48
+ usage: 'flow-search "query" [--status=] [--area=] [--assignee=]' },
49
+ { name: 'status', file: 'status.mjs', summary: 'Quick board summary',
50
+ usage: 'flow-status — board summary' },
51
+ { name: 'standup', file: 'standup.mjs', summary: 'Done/in-progress digest; --velocity for trend chart',
52
+ usage: 'flow-standup [--since=<hours>] [--velocity] — activity digest; --velocity adds a week-over-week chart' },
53
+ { name: 'sync', file: 'sync.mjs', summary: 'Delta sync since N minutes (default 60)',
54
+ usage: 'flow-sync [--since=<minutes>] — delta update' },
55
+ { name: 'log', file: 'log.mjs', summary: 'Tail recent timeline events',
56
+ usage: 'flow-log [--task=<id>] [--limit=20] — tail timeline events (board-wide without --task)' },
57
+ { name: 'heartbeat', file: 'heartbeat.mjs', summary: 'Send a presence ping (~60s while active)',
58
+ usage: 'flow-heartbeat [--task=<id>] — presence ping' },
59
+ { name: 'ping', file: 'ping.mjs', summary: 'Health check or send a direct agent message',
60
+ usage: 'flow-ping [<actor> "message"] — 0 args: health check; 2 args: message an actor' },
61
+ { name: 'pr', file: 'pr.mjs', summary: 'Record a PR link on a task timeline',
62
+ usage: 'flow-pr <id> <pr_url> — record a PR link on a task' },
63
+ { name: 'project', file: 'project.mjs', summary: 'List GitHub Projects v2 items',
64
+ usage: 'flow-project [--project=<node_id>] — list GitHub Projects v2 items' },
65
+ { name: 'archive', file: 'archive.mjs', summary: 'Archive or unarchive a task (--undo to restore)',
66
+ usage: 'flow-archive <id> [--undo] — archive/unarchive a task (owner only)' },
67
+ { name: 'scan', file: 'scan.mjs', summary: 'Audit for TODOs, untracked Issues, unlinked PRs, security patterns',
68
+ usage: 'flow-scan [--todos] [--issues] [--prs] [--security] — audit codebase for untracked work' },
69
+ { name: 'close-sprint', file: 'close-sprint.mjs', summary: 'Close a sprint milestone (owner only)',
70
+ usage: 'flow-close-sprint --milestone=<name> [--archive-done] — owner only' },
71
+ { name: 'whoami', file: 'whoami.mjs', summary: 'Verify token and show current identity',
72
+ usage: 'flow-whoami — verify token + show identity' },
73
+ { name: 'completion', file: 'completion.mjs', summary: 'Print shell completion script (bash/zsh/fish)',
74
+ usage: 'flow-completion <bash|zsh|fish> — print shell completion script' },
75
+ ];
package/bin/flow.mjs CHANGED
@@ -6,8 +6,7 @@
6
6
 
7
7
  import { spawnSync } from 'child_process';
8
8
  import { readFileSync } from 'fs';
9
- import { fileURLToPath } from 'url';
10
- import { dirname } from 'path';
9
+ import { CLI_COMMANDS } from './_commands.mjs';
11
10
 
12
11
  // Mirror the _client.mjs TLS re-spawn so the child sees --use-system-ca.
13
12
  // FLOW_TLS_RESPAWNED is a belt-and-suspenders guard against an infinite loop
@@ -24,42 +23,8 @@ if (process.platform === 'win32'
24
23
  process.exit(r.status ?? 1);
25
24
  }
26
25
 
27
- const __dir = dirname(fileURLToPath(import.meta.url));
28
-
29
- // ── Command registry ────────────────────────────────────────────────────────
30
- const CMDS = [
31
- ['login', 'login.mjs', 'Authorize the CLI via browser (device flow)'],
32
- ['pull', 'pull.mjs', 'Fetch board snapshot + @mentions (start of day)'],
33
- ['claim', 'claim.mjs', 'Claim a task and mark it in-progress'],
34
- ['comment', 'comment.mjs', 'Post a comment (--commit[=<sha>] links a clickable diff)'],
35
- ['close', 'close.mjs', 'Mark a task done with a summary'],
36
- ['create', 'create.mjs', 'Create a new task'],
37
- ['edit', 'edit.mjs', 'Update task fields (title, priority, area, due…)'],
38
- ['unblock', 'unblock.mjs', 'Clear a task\'s blocked-by dependency'],
39
- ['propose', 'propose.mjs', 'Propose a sub-task for human approval'],
40
- ['approve', 'approve.mjs', 'Approve a pending proposal'],
41
- ['reject', 'reject.mjs', 'Reject a pending proposal with a reason'],
42
- ['assign', 'assign.mjs', 'Assign a task to another actor'],
43
- ['decisions', 'decisions.mjs', 'List pending proposals (--watch <id> blocks until resolved)'],
44
- ['handoff', 'handoff.mjs', 'Hand off a task to another agent'],
45
- ['review', 'review.mjs', 'Request code review; moves task to in-review'],
46
- ['search', 'search.mjs', 'Search tasks by text, status, area, or assignee'],
47
- ['status', 'status.mjs', 'Quick board summary'],
48
- ['standup', 'standup.mjs', 'Done/in-progress digest; --velocity for trend chart'],
49
- ['sync', 'sync.mjs', 'Delta sync since N minutes (default 60)'],
50
- ['log', 'log.mjs', 'Tail recent timeline events'],
51
- ['heartbeat', 'heartbeat.mjs', 'Send a presence ping (~60s while active)'],
52
- ['ping', 'ping.mjs', 'Health check or send a direct agent message'],
53
- ['pr', 'pr.mjs', 'Record a PR link on a task timeline'],
54
- ['project', 'project.mjs', 'List GitHub Projects v2 items'],
55
- ['archive', 'archive.mjs', 'Archive or unarchive a task (--undo to restore)'],
56
- ['scan', 'scan.mjs', 'Audit for TODOs, untracked Issues, unlinked PRs, security patterns'],
57
- ['close-sprint', 'close-sprint.mjs', 'Close a sprint milestone (owner only)'],
58
- ['whoami', 'whoami.mjs', 'Verify token and show current identity'],
59
- ['completion', 'completion.mjs', 'Print shell completion script (bash/zsh/fish)'],
60
- ];
61
-
62
- const CMD_MAP = new Map(CMDS.map(([name, file]) => [name, file]));
26
+ // ── Command registry (single source of truth: bin/_commands.mjs) ─────────────
27
+ const CMD_MAP = new Map(CLI_COMMANDS.map((c) => [c.name, c.file]));
63
28
 
64
29
  // ── Help ────────────────────────────────────────────────────────────────────
65
30
  function printHelp() {
@@ -72,16 +37,16 @@ function printHelp() {
72
37
  process.stdout.write(`flow${version} — FlowCollab CLI\n\n`);
73
38
  process.stdout.write(`Usage: flow <command> [options]\n\n`);
74
39
  process.stdout.write(`Commands:\n`);
75
- const pad = Math.max(...CMDS.map(([n]) => n.length)) + 2;
76
- for (const [name, , desc] of CMDS) {
77
- process.stdout.write(` ${name.padEnd(pad)}${desc}\n`);
40
+ const pad = Math.max(...CLI_COMMANDS.map((c) => c.name.length)) + 2;
41
+ for (const { name, summary } of CLI_COMMANDS) {
42
+ process.stdout.write(` ${name.padEnd(pad)}${summary}\n`);
78
43
  }
79
44
  process.stdout.write(`\nRun 'flow <command> --help' for command-specific flags.\n`);
80
45
  process.stdout.write(`Hyphenated aliases (flow-login, flow-pull, …) remain supported.\n`);
81
46
  }
82
47
 
83
48
  // ── Dispatch ────────────────────────────────────────────────────────────────
84
- const [, , sub, ...rest] = process.argv;
49
+ const [, , sub] = process.argv;
85
50
 
86
51
  if (!sub || sub === '--help' || sub === '-h') {
87
52
  printHelp();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowcollab",
3
- "version": "0.3.17",
3
+ "version": "0.3.19",
4
4
  "description": "Multi-Claude coordination layer — shared task board + CLI for teams running Claude Code",
5
5
  "type": "module",
6
6
  "files": [