flowviant 0.102.2 → 0.104.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 (141) hide show
  1. package/bin/cli.mjs +87 -474
  2. package/bin/lib/admission.mjs +19 -3
  3. package/bin/lib/agentPlan.mjs +2 -2
  4. package/bin/lib/agentTaskKinds.mjs +93 -0
  5. package/bin/lib/agySessions.mjs +158 -0
  6. package/bin/lib/artifactPolicy.mjs +44 -0
  7. package/bin/lib/artifactPreview.mjs +52 -0
  8. package/bin/lib/artifactReporter.mjs +163 -0
  9. package/bin/lib/artifacts.mjs +47 -227
  10. package/bin/lib/authReject.mjs +16 -4
  11. package/bin/lib/authproxy.mjs +47 -304
  12. package/bin/lib/boxIdentity.mjs +245 -0
  13. package/bin/lib/childEnv.mjs +11 -9
  14. package/bin/lib/claude.mjs +14 -851
  15. package/bin/lib/claudeAuth.mjs +3 -3
  16. package/bin/lib/claudePosture.mjs +367 -0
  17. package/bin/lib/claudeSessions.mjs +411 -0
  18. package/bin/lib/claudeStream.mjs +207 -0
  19. package/bin/lib/cliLimit.mjs +34 -0
  20. package/bin/lib/commandSpecs.mjs +173 -0
  21. package/bin/lib/config.mjs +11 -42
  22. package/bin/lib/credentialDate.mjs +22 -0
  23. package/bin/lib/credentials.mjs +21 -26
  24. package/bin/lib/daemonLogging.mjs +55 -0
  25. package/bin/lib/daemonState.mjs +64 -0
  26. package/bin/lib/deploy.mjs +148 -647
  27. package/bin/lib/deployCheckout.mjs +265 -0
  28. package/bin/lib/deployConfig.mjs +142 -0
  29. package/bin/lib/deployRunner.mjs +311 -0
  30. package/bin/lib/deployWire.mjs +41 -0
  31. package/bin/lib/desktopContract.mjs +56 -105
  32. package/bin/lib/env.mjs +49 -484
  33. package/bin/lib/fleet.mjs +184 -2396
  34. package/bin/lib/fleetJobs.mjs +120 -0
  35. package/bin/lib/fleetPost.mjs +40 -0
  36. package/bin/lib/fleetReports.mjs +392 -0
  37. package/bin/lib/fleetRoster.mjs +470 -0
  38. package/bin/lib/fleetStartup.mjs +196 -0
  39. package/bin/lib/fleetWake.mjs +45 -0
  40. package/bin/lib/fleetWire.mjs +46 -0
  41. package/bin/lib/git.mjs +34 -3
  42. package/bin/lib/gitMergeIdentity.mjs +51 -0
  43. package/bin/lib/grant.mjs +5 -3
  44. package/bin/lib/help.mjs +3 -125
  45. package/bin/lib/holder.mjs +482 -0
  46. package/bin/lib/instance.mjs +124 -46
  47. package/bin/lib/knowledge.mjs +16 -799
  48. package/bin/lib/knowledgeFetch.mjs +38 -0
  49. package/bin/lib/knowledgeFileSync.mjs +112 -0
  50. package/bin/lib/knowledgeFiles.mjs +487 -0
  51. package/bin/lib/knowledgeLibrary.mjs +249 -0
  52. package/bin/lib/landed.mjs +2 -1
  53. package/bin/lib/listeners.mjs +99 -141
  54. package/bin/lib/localSessions.mjs +30 -555
  55. package/bin/lib/login.mjs +3 -2
  56. package/bin/lib/loginPath.mjs +3 -5
  57. package/bin/lib/machineDisconnect.mjs +203 -0
  58. package/bin/lib/machineEnv.mjs +22 -0
  59. package/bin/lib/machineLimits.mjs +61 -0
  60. package/bin/lib/machineListing.mjs +338 -0
  61. package/bin/lib/machines.mjs +78 -455
  62. package/bin/lib/machinesCommand.mjs +214 -0
  63. package/bin/lib/mcp-cli.mjs +2 -1
  64. package/bin/lib/patchRevert.mjs +60 -0
  65. package/bin/lib/prWorkflow.mjs +157 -0
  66. package/bin/lib/preflight.mjs +2 -1
  67. package/bin/lib/pressurePolicy.mjs +104 -0
  68. package/bin/lib/preview.mjs +2 -272
  69. package/bin/lib/previewFramePolicy.mjs +112 -0
  70. package/bin/lib/previewGatePolicy.mjs +236 -0
  71. package/bin/lib/previewRegistry.mjs +235 -0
  72. package/bin/lib/printable.mjs +2 -2
  73. package/bin/lib/procRegistry.mjs +28 -18
  74. package/bin/lib/procScan.mjs +146 -0
  75. package/bin/lib/processes.mjs +29 -37
  76. package/bin/lib/projectToolRuntimes.mjs +138 -0
  77. package/bin/lib/projectTools.mjs +13 -88
  78. package/bin/lib/prompts.mjs +28 -42
  79. package/bin/lib/repoPath.mjs +26 -0
  80. package/bin/lib/resources.mjs +7 -69
  81. package/bin/lib/resumeLost.mjs +107 -0
  82. package/bin/lib/runTurn.mjs +324 -0
  83. package/bin/lib/runtimeAntigravity.mjs +230 -0
  84. package/bin/lib/runtimeCapabilities.mjs +334 -0
  85. package/bin/lib/runtimeClaude.mjs +126 -0
  86. package/bin/lib/runtimeCodex.mjs +217 -0
  87. package/bin/lib/runtimeDetection.mjs +121 -0
  88. package/bin/lib/runtimeEvents.mjs +482 -0
  89. package/bin/lib/runtimeOfferings.mjs +4 -1
  90. package/bin/lib/runtimes.mjs +42 -1402
  91. package/bin/lib/safeFileName.mjs +74 -0
  92. package/bin/lib/shipMerge.mjs +33 -2
  93. package/bin/lib/standDownExit.mjs +181 -0
  94. package/bin/lib/standDownGate.mjs +48 -0
  95. package/bin/lib/startProject.mjs +279 -0
  96. package/bin/lib/trace.mjs +3 -3
  97. package/bin/lib/turnLock.mjs +68 -0
  98. package/bin/lib/turnProfile.mjs +77 -0
  99. package/bin/lib/uninstall.mjs +55 -45
  100. package/bin/lib/uninstallCopies.mjs +112 -0
  101. package/bin/lib/update.mjs +30 -17
  102. package/bin/lib/uplinkScrub.mjs +265 -0
  103. package/bin/lib/vault.mjs +22 -151
  104. package/bin/lib/vaultDiff.mjs +183 -0
  105. package/bin/lib/versionOrder.mjs +42 -0
  106. package/bin/lib/views.mjs +35 -12
  107. package/bin/lib/wikiRunner.mjs +630 -0
  108. package/bin/lib/work.mjs +188 -3037
  109. package/bin/lib/workAdoptCarry.mjs +94 -0
  110. package/bin/lib/workAgentCheck.mjs +278 -0
  111. package/bin/lib/workAgentMerges.mjs +54 -142
  112. package/bin/lib/workAgentPlans.mjs +14 -12
  113. package/bin/lib/workAgentPrecheck.mjs +393 -0
  114. package/bin/lib/workAgentPublish.mjs +177 -0
  115. package/bin/lib/workAgentReview.mjs +8 -666
  116. package/bin/lib/workAgentTurnBegun.mjs +109 -0
  117. package/bin/lib/workAgentTurnExecution.mjs +604 -0
  118. package/bin/lib/workAgentTurnOutcome.mjs +207 -0
  119. package/bin/lib/workAgentTurnReports.mjs +189 -0
  120. package/bin/lib/workAgentTurns.mjs +92 -897
  121. package/bin/lib/workAttachments.mjs +93 -0
  122. package/bin/lib/workBrain.mjs +53 -0
  123. package/bin/lib/workChildren.mjs +110 -0
  124. package/bin/lib/workCommandAudit.mjs +62 -0
  125. package/bin/lib/workDiffs.mjs +2 -1
  126. package/bin/lib/workNarration.mjs +152 -0
  127. package/bin/lib/workPlaceReports.mjs +426 -0
  128. package/bin/lib/workPlaces.mjs +228 -0
  129. package/bin/lib/workPreviews.mjs +31 -9
  130. package/bin/lib/workProcesses.mjs +4 -5
  131. package/bin/lib/workPullRequests.mjs +52 -126
  132. package/bin/lib/workReportQueue.mjs +254 -0
  133. package/bin/lib/workRetire.mjs +185 -0
  134. package/bin/lib/workSessionGroups.mjs +166 -0
  135. package/bin/lib/workSessionRuntime.mjs +164 -0
  136. package/bin/lib/workSessionTokens.mjs +68 -0
  137. package/bin/lib/workSessionTurns.mjs +892 -0
  138. package/bin/lib/workShip.mjs +9 -29
  139. package/bin/lib/workToolLog.mjs +76 -0
  140. package/package.json +3 -2
  141. package/bin/lib/patch.mjs +0 -363
@@ -0,0 +1,45 @@
1
+ /**
2
+ * THE RECONCILE WAIT — idle until the next poll deadline OR a push wake,
3
+ * whichever comes first.
4
+ *
5
+ * Split out of fleet.mjs (SOLID audit 2026-09-26, F038): a small piece of
6
+ * timing state (the pending resolver, and a wake that landed mid-reconcile)
7
+ * that the loop only ever touches through these two functions. The socket
8
+ * itself is stream.mjs's; this is what a wake DOES to the loop.
9
+ */
10
+
11
+ import { RECONCILE_SECONDS } from './config.mjs';
12
+
13
+ export function createReconcileWait() {
14
+ // ── Push channel: a server wake short-circuits the reconcile sleep so a job is
15
+ // picked up in ~a round trip instead of on the next poll. The socket only
16
+ // nudges — we still fetch the roster below — so it's pure latency, and the
17
+ // poll stays the fallback whenever the socket is down. `waitReconcile()`
18
+ // resolves on either a wake or the RECONCILE_SECONDS timeout, whichever first.
19
+ let wakeSignal = null; // { resolve, timer } while the loop is idling
20
+ let pendingWake = false; // a wake that landed mid-reconcile — honored next wait
21
+ const fireWake = () => {
22
+ if (wakeSignal) {
23
+ clearTimeout(wakeSignal.timer);
24
+ const { resolve } = wakeSignal;
25
+ wakeSignal = null;
26
+ resolve();
27
+ } else {
28
+ pendingWake = true; // not idling right now; don't lose the wake
29
+ }
30
+ };
31
+ const waitReconcile = () => {
32
+ if (pendingWake) {
33
+ pendingWake = false;
34
+ return Promise.resolve();
35
+ }
36
+ return new Promise((resolve) => {
37
+ const timer = setTimeout(() => {
38
+ wakeSignal = null;
39
+ resolve();
40
+ }, RECONCILE_SECONDS * 1000);
41
+ wakeSignal = { resolve, timer };
42
+ });
43
+ };
44
+ return { fireWake, waitReconcile };
45
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * EVERY `/fleet/*` ENDPOINT, derived from the one roster URL.
3
+ *
4
+ * One place configures the API base (`FLEET_URL`, overridable by
5
+ * `FLOWVIANT_FLEET_URL`, ending in `/fleet/agents`) and every other daemon
6
+ * endpoint is that URL with its final `agents` segment swapped — so a
7
+ * self-hosted server, or the `/api` → `/api/v2` alias, is honoured by every
8
+ * lane at once.
9
+ *
10
+ * ONE HOME (SOLID audit 2026-09-26, F166). The swap was spelled
11
+ * `FLEET_URL.replace(/\/agents\/?$/, '/…')` forty-nine times across eighteen
12
+ * files; a change to the roster URL's shape, or to what counts as a valid
13
+ * endpoint name, was forty-nine edits.
14
+ *
15
+ * WHAT IT VALIDATES, AND WHAT IT DELIBERATELY DOES NOT. An endpoint NAME that
16
+ * is not a plain lowercase path is a programming error and throws. A ROSTER
17
+ * URL that does not end in `/agents` is NOT refused here: endpoints are
18
+ * derived while modules load (fleetReports.mjs, landed.mjs, login.mjs), and a throw
19
+ * there took down every command — `help` and `projects` included — over a
20
+ * setting only the daemon's lanes use (measured while making this change). Such
21
+ * a URL comes back unchanged, exactly as the forty-nine copies answered, and
22
+ * the roster poll against it is the first thing to fail and say so.
23
+ *
24
+ * The push channel (`STREAM_URL` in config.mjs) is the one derivation kept
25
+ * apart: it switches the scheme to ws and keeps a trailing slash.
26
+ *
27
+ * A leaf on purpose: it takes the roster URL as an argument rather than
28
+ * importing `config.mjs`, so modules that are handed a `fleetUrl` (the
29
+ * artifact reporter, the knowledge fetcher, the tray's remote status) do not
30
+ * gain a config import to name a path.
31
+ */
32
+
33
+ const ROSTER_TAIL = /\/agents\/?$/;
34
+ const NAME = /^[a-z0-9-]+(\/[a-z0-9-]+)*$/;
35
+
36
+ /**
37
+ * The `/fleet/<name>` endpoint beside the roster URL `rosterUrl`
38
+ * (`https://api.flowviant.com/api/fleet/agents` → `…/api/fleet/<name>`). A
39
+ * trailing slash on the roster URL is accepted and not carried over.
40
+ */
41
+ export function fleetEndpoint(name, rosterUrl) {
42
+ if (typeof name !== 'string' || !NAME.test(name)) {
43
+ throw new TypeError(`fleetEndpoint: "${name}" is not a /fleet endpoint name`);
44
+ }
45
+ return String(rosterUrl).replace(ROSTER_TAIL, `/${name}`);
46
+ }
package/bin/lib/git.mjs CHANGED
@@ -250,12 +250,43 @@ export function detectBaseRef(repoRoot) {
250
250
  }
251
251
  }
252
252
  try {
253
- return `origin/${git(['rev-parse', '--abbrev-ref', 'HEAD'], repoRoot)}`;
253
+ return usableBaseRef(repoRoot, `origin/${git(['rev-parse', '--abbrev-ref', 'HEAD'], repoRoot)}`);
254
254
  } catch {
255
255
  return 'HEAD';
256
256
  }
257
257
  }
258
258
 
259
+ /**
260
+ * A base ref an agent's worktree can actually be cut from.
261
+ *
262
+ * `origin/<branch>` is the right base when the repo HAS an origin. A repo with
263
+ * no remote (a local-only project — the owner's "Death Note - Roblox",
264
+ * 2026-09-25) has no `origin/*` refs at all, and cutting a worktree from one
265
+ * fails; every agent turn then ended "nothing" in a second. So: the ref itself
266
+ * when it resolves to a commit, else the LOCAL branch of the same name, else
267
+ * the ref unchanged (and the worktree step says why in git's words).
268
+ */
269
+ export function usableBaseRef(repoRoot, ref) {
270
+ const resolves = (r) => {
271
+ try { git(['rev-parse', '--verify', '--quiet', `${r}^{commit}`], repoRoot); return true; } catch { return false; }
272
+ };
273
+ if (resolves(ref)) return ref;
274
+ const local = String(ref).replace(/^origin\//, '');
275
+ if (local !== ref && resolves(local)) return local;
276
+ return ref;
277
+ }
278
+
279
+ /** True when the repository has at least one commit (HEAD resolves). */
280
+ export function hasCommits(repoRoot) {
281
+ try { git(['rev-parse', '--verify', '--quiet', 'HEAD^{commit}'], repoRoot); return true; } catch { return false; }
282
+ }
283
+
284
+ /** The last line git wrote to stderr for a failed call, or its message. */
285
+ export function gitFailure(error) {
286
+ const stderr = String(error?.stderr ?? '').trim().split('\n').filter(Boolean);
287
+ return stderr.at(-1) ?? String(error?.message ?? error).split('\n')[0];
288
+ }
289
+
259
290
  /**
260
291
  * The BRANCH NAME behind a base ref.
261
292
  *
@@ -292,8 +323,8 @@ export function resetWorktree(wt, baseRef) {
292
323
  * the vault is what first needed it — materialized `.env` files had to be
293
324
  * untracked AND unstageable — but it is a plain `git info/exclude` helper with
294
325
  * no crypto, no bundle and no opinion about secrets, and the vault is deleted.
295
- * Its one surviving caller is `work.mjs`, which hides a tab's `.flowviant/`
296
- * upload directory with it.
326
+ * Its callers are the work manager (`work.mjs`, before a turn's artifacts)
327
+ * and `workAttachments.mjs`, which hide a tab's `.flowviant/` paths with it.
297
328
  *
298
329
  * WHY IT IS NOT `.git/worktrees/<name>/info/exclude`: it used to resolve the
299
330
  * worktree's own gitdir and write there, on the belief that the file "applies
@@ -0,0 +1,51 @@
1
+ /**
2
+ * THE IDENTITY A DAEMON MERGE COMMIT IS WRITTEN UNDER (2026-09-26, SOLID F165).
3
+ *
4
+ * A merge COMMIT needs a git identity and the machine may have none. Prefer the
5
+ * operator's own config; fall back to the daemon's, so a bare machine does not
6
+ * fail the fold with "Please tell me who you are".
7
+ *
8
+ * Split out because the session ship lane (workShip.mjs) and the agent merge
9
+ * lane (workAgentMerges.mjs) each carried a copy of the probe and the four
10
+ * fallback variables — an identity-policy change needed two edits. Both lanes
11
+ * now take their `gitMerge` from `gitMergeIn(repoRoot)`; their merge decisions
12
+ * are their own.
13
+ */
14
+
15
+ import { execFileSync } from 'node:child_process';
16
+ import { git } from './git.mjs';
17
+
18
+ export const DAEMON_GIT_IDENTITY = Object.freeze({
19
+ GIT_AUTHOR_NAME: 'Flowviant',
20
+ GIT_AUTHOR_EMAIL: 'daemon@flowviant.com',
21
+ GIT_COMMITTER_NAME: 'Flowviant',
22
+ GIT_COMMITTER_EMAIL: 'daemon@flowviant.com',
23
+ });
24
+
25
+ /** The env a merge in `repoRoot` needs: null when the operator's config names a
26
+ * user.email (git uses theirs), the daemon's identity when it names none. */
27
+ export function mergeIdentityEnv(repoRoot) {
28
+ try {
29
+ git(['config', 'user.email'], repoRoot);
30
+ return null;
31
+ } catch {
32
+ return DAEMON_GIT_IDENTITY;
33
+ }
34
+ }
35
+
36
+ /**
37
+ * A `gitMerge(args, cwd)` for a checkout rooted at `repoRoot`: runs `git args`
38
+ * in `cwd` (a throwaway worktree, usually) under the identity the probe finds.
39
+ * The probe runs per call, so an identity configured mid-job is heard.
40
+ */
41
+ export function gitMergeIn(repoRoot) {
42
+ return (args, cwd) => {
43
+ const idEnv = mergeIdentityEnv(repoRoot);
44
+ return execFileSync('git', args, {
45
+ cwd,
46
+ encoding: 'utf8',
47
+ stdio: ['ignore', 'pipe', 'pipe'],
48
+ ...(idEnv ? { env: { ...process.env, ...idEnv } } : {}),
49
+ });
50
+ };
51
+ }
package/bin/lib/grant.mjs CHANGED
@@ -46,8 +46,10 @@ export const GRANT_SKEW_MS = 60_000;
46
46
  const b64url = (buf) => buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
47
47
 
48
48
  /** Constant-time over sha256 digests, so length never leaks and a missing
49
- * value costs the same as a wrong one. Never `===` on a signature. */
50
- function sameDigest(a, b) {
49
+ * value costs the same as a wrong one. Never `===` on a signature or a
50
+ * password. The daemon's ONE secret comparison: the grant signature below and
51
+ * the gate's Basic password (`previewGatePolicy.mjs`) both ask it. */
52
+ export function sameSecret(a, b) {
51
53
  try {
52
54
  const da = createHash('sha256').update(String(a ?? '')).digest();
53
55
  const db = createHash('sha256').update(String(b ?? '')).digest();
@@ -80,7 +82,7 @@ export function verifyGrant(raw, { secret, shareId, nowMs, skewMs } = {}) {
80
82
  }
81
83
 
82
84
  const expected = b64url(createHmac('sha256', String(secret)).update(DOMAIN + body).digest());
83
- if (!sameDigest(sig, expected)) return { ok: false, reason: 'sig' };
85
+ if (!sameSecret(sig, expected)) return { ok: false, reason: 'sig' };
84
86
 
85
87
  let payload;
86
88
  try {
package/bin/lib/help.mjs CHANGED
@@ -9,127 +9,12 @@
9
9
  * and `<command> --help` print one entry, and an unknown word is refused with
10
10
  * the nearest command instead of being served.
11
11
  *
12
- * THE TABLE IS PINNED TO THE DISPATCH: help.test.mjs reads every
13
- * `process.argv[2] === '<name>'` in cli.mjs and fails if one is missing here
14
- * (or listed here and not dispatched). `hidden` rows are real commands a
15
- * person rarely types (an agent calls `shot`; `mcp` manages connectors from a
16
- * script); they answer `help <name>` but stay off the main screen.
12
+ * THE TABLE is commandSpecs.mjs (one list for help, the --dir checkout policy
13
+ * and cli.mjs's dispatch); this module renders it and answers a slip.
17
14
  */
18
15
 
19
16
  import { c } from './ui.mjs';
20
-
21
- export const COMMANDS = [
22
- {
23
- name: 'start',
24
- usage: 'flowviant [start] [--project <name|id>]',
25
- summary: "Serve this repo's project: run agents and Workbench tabs here. Plain `flowviant` does the same.",
26
- details: [
27
- 'Run it inside the repository you connected. The first run in a new repo asks you to approve this computer.',
28
- '--project <name|id> serve that stored project instead of the one this repo is bound to',
29
- ],
30
- group: 'run',
31
- },
32
- {
33
- name: 'login',
34
- usage: 'flowviant login [--no-start]',
35
- summary: 'Connect this repo to a project: approve the code in your browser, then start serving.',
36
- details: ['--no-start save the connection without starting the daemon'],
37
- group: 'run',
38
- },
39
- {
40
- name: 'stop',
41
- usage: 'flowviant stop [--project <name|id>]',
42
- summary: 'Stop every flowviant daemon on this computer (or just one project’s).',
43
- group: 'run',
44
- },
45
- {
46
- name: 'status',
47
- usage: 'flowviant status',
48
- summary: 'Each project connected here: its repo, whether it is running and serving, and its live agents.',
49
- details: ['--json the machine-readable form the Windows tray reads'],
50
- group: 'look',
51
- },
52
- {
53
- name: 'logs',
54
- usage: 'flowviant logs [-f] [--project <name|id>]',
55
- summary: "The daemon's log for this repo's project.",
56
- details: ['-f, --follow keep printing new lines as they are written', '-n <lines> how many lines to show first (default 80)'],
57
- group: 'look',
58
- },
59
- {
60
- name: 'open',
61
- usage: 'flowviant open [--project <name|id>]',
62
- summary: "Open this project's board in your browser (prints the link too).",
63
- group: 'look',
64
- },
65
- {
66
- name: 'projects',
67
- usage: 'flowviant projects',
68
- summary: 'Every project this computer has a login for, and the repo each one serves.',
69
- group: 'look',
70
- },
71
- {
72
- name: 'machines',
73
- usage: 'flowviant machines [--remove <id> | --forget <id>] [--yes]',
74
- summary: 'Every computer that serves your projects. On a terminal, a menu to disconnect or forget one here.',
75
- group: 'look',
76
- },
77
- {
78
- name: 'doctor',
79
- usage: 'flowviant doctor',
80
- summary: 'Check what this computer needs: git, a signed-in CLI, a connection to Flowviant, a running daemon.',
81
- group: 'look',
82
- },
83
- {
84
- name: 'update',
85
- usage: 'flowviant update',
86
- summary: 'Install the newest flowviant now. It also updates itself when idle.',
87
- group: 'manage',
88
- },
89
- {
90
- name: 'clean',
91
- usage: 'flowviant clean',
92
- summary: 'Delete the kept agent worktrees under ~/.flowviant/worktrees. Stop the daemon first.',
93
- group: 'manage',
94
- },
95
- {
96
- name: 'uninstall',
97
- usage: 'flowviant uninstall [--purge] [--yes]',
98
- summary: 'Remove every flowviant copy on this computer. Logins are kept unless you add --purge.',
99
- details: ['--purge also disconnect this computer from its projects and delete ~/.flowviant', '--yes do not ask first'],
100
- group: 'manage',
101
- },
102
- {
103
- name: 'gh-auth',
104
- usage: 'flowviant gh-auth',
105
- summary: 'Sign in the GitHub CLI flowviant uses to open pull requests.',
106
- group: 'manage',
107
- },
108
- {
109
- name: 'version',
110
- usage: 'flowviant --version',
111
- summary: 'Print the installed version.',
112
- group: 'manage',
113
- },
114
- {
115
- name: 'help',
116
- usage: 'flowviant help [command]',
117
- summary: 'This list, or one command in detail.',
118
- group: 'manage',
119
- },
120
- {
121
- name: 'shot',
122
- usage: 'flowviant shot <url> [--out <file>]',
123
- summary: 'Screenshot a running page with a headless browser (agents use it to see their change).',
124
- hidden: true,
125
- },
126
- {
127
- name: 'mcp',
128
- usage: 'flowviant mcp <subcommand>',
129
- summary: "Manage the project's MCP connectors from a script.",
130
- hidden: true,
131
- },
132
- ];
17
+ import { COMMANDS } from './commandSpecs.mjs';
133
18
 
134
19
  const GROUPS = [
135
20
  ['run', 'Run'],
@@ -137,13 +22,6 @@ const GROUPS = [
137
22
  ['manage', 'Manage'],
138
23
  ];
139
24
 
140
- const ALIASES = { '--version': 'version', '-v': 'version', '--help': 'help', '-h': 'help' };
141
-
142
- export function findCommand(name) {
143
- const key = ALIASES[name] ?? name;
144
- return COMMANDS.find((cmd) => cmd.name === key) ?? null;
145
- }
146
-
147
25
  /** The main screen: every visible command, grouped, one line each. */
148
26
  export function renderHelp(version) {
149
27
  const width = Math.max(...COMMANDS.filter((cmd) => !cmd.hidden).map((cmd) => cmd.name.length));