@shardflux/cli 0.19.1 → 0.22.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 (115) hide show
  1. package/CHANGELOG.md +278 -1
  2. package/README.md +225 -73
  3. package/dist/account-args.js +7 -2
  4. package/dist/account-commands.js +56 -106
  5. package/dist/account-pool.d.ts +20 -15
  6. package/dist/account-pool.js +51 -97
  7. package/dist/account-scope.d.ts +65 -0
  8. package/dist/account-scope.js +246 -0
  9. package/dist/acp-login.d.ts +4 -3
  10. package/dist/acp-login.js +7 -5
  11. package/dist/acp-models.js +20 -15
  12. package/dist/acp.js +66 -13
  13. package/dist/agent-commands.d.ts +4 -1
  14. package/dist/agent-commands.js +86 -32
  15. package/dist/agent-config-files.d.ts +19 -0
  16. package/dist/agent-config-files.js +70 -1
  17. package/dist/agent-config-return.d.ts +194 -0
  18. package/dist/agent-config-return.js +724 -0
  19. package/dist/agent-config-sync.d.ts +10 -2
  20. package/dist/agent-config-sync.js +45 -51
  21. package/dist/agent-credentials.js +7 -3
  22. package/dist/agent-repository.d.ts +16 -3
  23. package/dist/agent-repository.js +149 -74
  24. package/dist/agent-setup.d.ts +14 -3
  25. package/dist/agent-setup.js +78 -36
  26. package/dist/agent-target-commands.js +24 -13
  27. package/dist/args.d.ts +1 -1
  28. package/dist/args.js +104 -55
  29. package/dist/bin.js +5 -1
  30. package/dist/browser.d.ts +13 -2
  31. package/dist/browser.js +30 -14
  32. package/dist/claude-cloud-login.d.ts +24 -2
  33. package/dist/claude-cloud-login.js +151 -11
  34. package/dist/cli-proxy-forward.d.ts +2 -0
  35. package/dist/cli-proxy-forward.js +9 -2
  36. package/dist/cli-proxy-hub.d.ts +69 -25
  37. package/dist/cli-proxy-hub.js +231 -54
  38. package/dist/cli-proxy.d.ts +70 -1
  39. package/dist/cli-proxy.js +298 -32
  40. package/dist/client-settings.d.ts +39 -0
  41. package/dist/client-settings.js +91 -0
  42. package/dist/codex-cloud.d.ts +5 -0
  43. package/dist/codex-cloud.js +88 -17
  44. package/dist/command-center-actions.js +5 -1
  45. package/dist/commands.js +171 -22
  46. package/dist/conversation-commands.d.ts +5 -0
  47. package/dist/conversation-commands.js +8 -5
  48. package/dist/conversation-terminal.d.ts +10 -2
  49. package/dist/conversation-terminal.js +81 -15
  50. package/dist/conversations.d.ts +77 -1
  51. package/dist/conversations.js +257 -23
  52. package/dist/device-bridge.d.ts +1 -1
  53. package/dist/device-bridge.js +6 -4
  54. package/dist/device-helper.js +52 -4
  55. package/dist/errors.d.ts +34 -1
  56. package/dist/errors.js +56 -5
  57. package/dist/filesystem-commands.d.ts +22 -3
  58. package/dist/filesystem-commands.js +132 -16
  59. package/dist/format.d.ts +2 -1
  60. package/dist/format.js +10 -1
  61. package/dist/handlers.js +4 -1
  62. package/dist/initial-prompt.d.ts +8 -0
  63. package/dist/initial-prompt.js +43 -1
  64. package/dist/large-media.d.ts +14 -0
  65. package/dist/large-media.js +100 -0
  66. package/dist/linux-terminal.d.ts +5 -3
  67. package/dist/linux-terminal.js +6 -20
  68. package/dist/local-sessions.d.ts +26 -2
  69. package/dist/local-sessions.js +113 -24
  70. package/dist/machine-setup.d.ts +190 -0
  71. package/dist/machine-setup.js +849 -0
  72. package/dist/machine-status.d.ts +67 -0
  73. package/dist/machine-status.js +140 -0
  74. package/dist/main.js +23 -13
  75. package/dist/move-worktrees-restore.py +144 -0
  76. package/dist/move-worktrees.d.ts +63 -0
  77. package/dist/move-worktrees.js +201 -0
  78. package/dist/move.d.ts +10 -1
  79. package/dist/move.js +116 -16
  80. package/dist/onboarding.d.ts +11 -7
  81. package/dist/onboarding.js +59 -27
  82. package/dist/prepared-repository.d.ts +202 -0
  83. package/dist/prepared-repository.js +860 -0
  84. package/dist/prepared-repository.py +1306 -0
  85. package/dist/project-preparation.d.ts +48 -0
  86. package/dist/project-preparation.js +304 -0
  87. package/dist/project-sync-manifest.d.ts +4 -0
  88. package/dist/project-sync-manifest.js +5 -1
  89. package/dist/project-sync-transfer.js +1 -1
  90. package/dist/project-sync.d.ts +19 -4
  91. package/dist/project-sync.js +31 -8
  92. package/dist/project-template-setup.d.ts +33 -1
  93. package/dist/project-template-setup.js +59 -2
  94. package/dist/secret-ask.d.ts +72 -0
  95. package/dist/secret-ask.js +351 -0
  96. package/dist/seed-phases.d.ts +13 -3
  97. package/dist/seed-phases.js +21 -3
  98. package/dist/setup-cache.d.ts +7 -0
  99. package/dist/setup-cache.js +32 -0
  100. package/dist/t3-launch.js +1 -4
  101. package/dist/t3-mode.d.ts +8 -2
  102. package/dist/t3-mode.js +19 -4
  103. package/dist/t3-phone.d.ts +4 -2
  104. package/dist/t3-phone.js +12 -4
  105. package/dist/terminal-apps.d.ts +157 -0
  106. package/dist/terminal-apps.js +561 -0
  107. package/dist/terminal-onboarding.d.ts +2 -0
  108. package/dist/terminal-onboarding.js +11 -1
  109. package/dist/this-computer.d.ts +11 -0
  110. package/dist/this-computer.js +36 -0
  111. package/dist/thread-on-computer.js +4 -1
  112. package/dist/update.js +11 -3
  113. package/dist/worktree-twin.d.ts +10 -2
  114. package/dist/worktree-twin.js +27 -9
  115. package/package.json +5 -5
@@ -9,7 +9,7 @@ import { getConversation } from "./conversations.js";
9
9
  import { out, resolveWorkspace } from "./commands.js";
10
10
  import { MODEL_CHOICES } from "./agent-models.js";
11
11
  import { AgentTargets, installedTargets, targetLabel } from "./agent-targets.js";
12
- import { join, relative } from 'node:path';
12
+ import { join, relative, resolve } from 'node:path';
13
13
  import { projectIdentity } from "./project-identity.js";
14
14
  import { projectWorkspacePath } from "./agent-repository.js";
15
15
  import { UsageError, describeError } from "./errors.js";
@@ -20,9 +20,10 @@ import { agentMachineName, prepareAgentMachine } from "./agent-machine.js";
20
20
  import { prewarmT3 } from "./t3-runtime.js";
21
21
  import { AgentStateStore } from "./agent-state.js";
22
22
  import { workspaceRepositoryPath } from "./agent-repository.js";
23
- import { openInChoices, openT3, saveOpenIn, startSshThread, t3Install, t3NotInstalled, t3OpenFailed, t3Status } from "./t3-mode.js";
23
+ import { openInChoices, openInTargets, openT3, saveOpenIn, startSshThread, t3Install, t3NotInstalled, t3OpenFailed, t3Status } from "./t3-mode.js";
24
24
  import { openT3Conversation, t3Fields, withT3Context } from "./t3-launch.js";
25
- import { conversationPooled, pooledNeedsT3, targetsAccounts } from "./account-pool.js";
25
+ import { targetsAccounts } from "./account-pool.js";
26
+ import { detectTerminals, openInTerminal, parseTerminalChoice, reportTerminalOpen, saveTerminalChoice, terminalName, terminalReport } from "./terminal-apps.js";
26
27
  const missing = (e) => e instanceof ShardfluxApiError && e.status === 404;
27
28
  const keyOf = (ref) => ref.includes('/') ? ref : `${projectIdentity('./').slug}/${ref}`;
28
29
  /** How long opening a Claude conversation this computer just started waits for Claude to publish its Remote Control URL. */
@@ -230,12 +231,6 @@ const openTarget = (target) => async (ctx, values, [ref = 'main']) => {
230
231
  return openT3Conversation(ctx, ref, values);
231
232
  // A conversation id opens that conversation's own session on its computer (claude.ai, the desktop apps).
232
233
  let conversation = /^conv_[a-z0-9]{26}$/.test(ref) ? await getConversation(ctx, ref) : undefined;
233
- // A pooled thread (0.18.0+) has no provider login on its session: it opens remotely in T3 Code.
234
- if (conversation && conversationPooled(conversation)) {
235
- if (!detected.targets.includes('t3'))
236
- throw pooledNeedsT3(ref);
237
- return openT3Conversation(ctx, ref, values);
238
- }
239
234
  // A conversation whose VM was removed restores into a fresh one first (0.18.1+).
240
235
  let ws;
241
236
  if (conversation) {
@@ -257,7 +252,7 @@ const openTarget = (target) => async (ctx, values, [ref = 'main']) => {
257
252
  await ensureCloudClaude(ctx, ws);
258
253
  if (target === 'web') {
259
254
  const url = await remoteControlUrl(ctx, ws, conversation);
260
- if (!ctx.json && !openUrl(url, ctx.io.env))
255
+ if (!ctx.json && !(await openUrl(url, ctx.io.env)))
261
256
  throw new Error(`could not open ${url}`);
262
257
  out(ctx, `${url}\n`, { target, workspace: ws.data, url, ...(conversation ? { conversation_id: conversation.id } : {}) });
263
258
  return 0;
@@ -327,7 +322,7 @@ export const T3_CONNECTIONS_PAGE = 't3code://app/settings/connections';
327
322
  */
328
323
  async function linkT3Phone(ctx, ws) {
329
324
  try {
330
- const { link, signIn, needsLogin } = await t3PhoneLink(ctx, ws.id);
325
+ const { link, signIn, needsLogin } = await t3PhoneLink(ctx, ws.id, ws.data.project_id);
331
326
  return { line: t3LinkLine(link, signIn, needsLogin), link: signIn ? { ...link, sign_in: { ...link.sign_in, ...signIn } } : needsLogin ? { ...link, needs_login: true } : link };
332
327
  }
333
328
  catch (error) {
@@ -410,9 +405,18 @@ export const TARGET_HANDLERS = {
410
405
  const accounts = ctx.json ? targetsAccounts(ctx) : undefined;
411
406
  const project = targetsProject(ref, typeof values.path === 'string' ? values.path : undefined);
412
407
  const t3 = t3Status(ctx, { project, lastUsed: true });
408
+ // Terminals (0.20.1+): the installed ones, the one this runs in, and the app terminal conversations open in.
409
+ const terminals = detectTerminals(ctx.io.env, process.platform, { spotlight: true });
410
+ const terminal = typeof values.terminal === 'string' ? parseTerminalChoice(values.terminal, terminals) : undefined;
413
411
  if (typeof values['open-in'] === 'string')
414
412
  saveOpenIn(ctx, values['open-in'], t3, found.clients);
415
- const detected = { ...found, t3, open_in: openInChoices(ctx, project, { clients: found.clients }), ...(accounts ? { accounts: await accounts } : {}) };
413
+ if (terminal !== undefined)
414
+ saveTerminalChoice(ctx.store.dir, terminal);
415
+ const terminalFields = terminalReport(ctx.store.dir, ctx.io.env, terminals);
416
+ // open_in_targets (0.21.1+): every place each app's conversations can open from this computer, so a client offers
417
+ // exactly what --open-in takes.
418
+ const detected = { ...found, t3, open_in: openInChoices(ctx, project, { clients: found.clients }),
419
+ open_in_targets: { claude: [...openInTargets('claude')], codex: [...openInTargets('codex')] }, ...terminalFields, ...(accounts ? { accounts: await accounts } : {}) };
416
420
  const targets = new AgentTargets(ctx.io.env, ctx.store.dir);
417
421
  const key = ref ? keyOf(ref) : undefined;
418
422
  const before = values.remove === true ? await targets.configure(undefined, ctx.apiUrl, [], false, ctx.signal) : [];
@@ -421,7 +425,8 @@ export const TARGET_HANDLERS = {
421
425
  : [];
422
426
  const detection = `${detected.clients.join(', ') || 'No installed agent clients'}\nOpen targets: ${detected.targets.join(', ')}\n` +
423
427
  (t3.installed ? `T3 Code ${t3.version ?? ''}: ${t3.mode === 'acp' ? 'Shardflux provider' : 'SSH environments'}, ${t3.connected ? 'connected' : 'not connected'}\n`.replace(' :', ':') : '') +
424
- `New conversations open in: Claude → ${detected.open_in.claude}, Codex → ${detected.open_in.codex}\n`;
428
+ `New conversations open in: Claude → ${detected.open_in.claude}, Codex → ${detected.open_in.codex}\n` +
429
+ (terminalFields.terminal ? `Terminal conversations open in: ${terminalName(terminalFields.terminal.id)} (${terminalFields.terminal.mode === 'tab' ? 'a new tab' : 'a new window'}${terminalFields.terminal.saved ? '' : ', automatic'})\n` : '');
425
430
  if (values.remove === true) {
426
431
  const removed = before.filter(e => !entries.some(n => n.host === e.host));
427
432
  out(ctx, removed.length ? `Removed ${removed.map(e => `${e.workspace} (${e.host})`).join(', ')} from ${targetFiles(targets, removed).map(f => tilde(targets, f)).join(', ')}\n` : 'No managed targets to remove.\n', { ...detected, entries, removed, models: MODEL_CHOICES });
@@ -438,6 +443,12 @@ export const TARGET_HANDLERS = {
438
443
  ['claude-desktop', 'codex-app', 't3'].filter(t => t === 't3' ? !t3Installed : !entry.targets.includes(t)).map(t => ` ${{ 'claude-desktop': 'Claude Desktop', 'codex-app': 'Codex app', t3: 'T3 Code' }[t].padEnd(15)} not installed on this computer\n`).join(''), { ...detected, entries, configured: configured.map(({ target, file, name }) => ({ target, file, name })), models: MODEL_CHOICES });
439
444
  return Promise.resolve(0);
440
445
  },
446
+ /** An app's own commands (sign-ins, the command center) in the person's terminal app (0.20.1+, the menu bar). */
447
+ async 'agents terminal-run'(ctx, values, argv) {
448
+ const cwd = typeof values.cwd === 'string' ? resolve(values.cwd) : process.cwd() !== '/' ? process.cwd() : ctx.io.env.HOME || process.cwd();
449
+ reportTerminalOpen(ctx, await openInTerminal(ctx.store.dir, ctx.io.env, argv, { cwd, window: values.window === true }));
450
+ return 0;
451
+ },
441
452
  'agents open claude-desktop': openTarget('claude-desktop'),
442
453
  'agents open codex-app': openTarget('codex-app'),
443
454
  'agents open t3': openTarget('t3'),
package/dist/args.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const CLI_VERSION = "0.19.1";
1
+ export declare const CLI_VERSION = "0.22.0";
2
2
  export interface OptionSpec {
3
3
  type: 'string' | 'boolean';
4
4
  short?: string;
package/dist/args.js CHANGED
@@ -13,7 +13,7 @@ import { parseArgs } from 'node:util';
13
13
  import { ACCOUNT_COMMANDS } from "./account-args.js";
14
14
  import { UsageError } from "./errors.js";
15
15
  import { DEFAULT_CHOICE, MODEL_CHOICES } from "./agent-models.js";
16
- export const CLI_VERSION = '0.19.1';
16
+ export const CLI_VERSION = '0.22.0';
17
17
  export const GLOBAL_OPTIONS = {
18
18
  'api-url': { type: 'string', value: '<url>', description: 'API base URL (default $SHARDFLUX_API_URL, else https://api.shardflux.dev).' },
19
19
  json: { type: 'boolean', description: 'Print machine-readable JSON on stdout (errors as JSON on stderr).' },
@@ -110,18 +110,14 @@ const FEEDBACK_HELP = [
110
110
  ];
111
111
  /**
112
112
  * Why `shard feedback` exists, for the coding agents that drive shard: the same words as the top-level help and the
113
- * MCP server's send_feedback tool. It has to make an agent actually send feedback while it works.
113
+ * MCP server's send_feedback tool. Reporting is user-directed and requires approval of the message.
114
114
  */
115
115
  const FEEDBACK_WHY = [
116
- 'Sends your message straight to the Shardflux team, who read every one. Agents: use it actively while you work,',
117
- 'not once at the end. Send a short note the moment:',
118
- '- something fails unexpectedly, an error or doc is unclear, an option is missing, or you needed a workaround;',
119
- '- your user asks for a capability, an option or a smoother workflow: paraphrase it, with what they were trying',
120
- ' to do;',
121
- '- your user has an idea for how Shardflux could fit their work better.',
122
- 'Short and frequent beats polished; include the request id and error code (a failed shard command prints a',
123
- 'ready-made "feedback:" line to fill in). No need to ask first, but tell your user you sent it. Leave out private',
124
- 'data about the user and secrets or code they did not mean to share; paraphrase, never paste transcripts.',
116
+ 'Previews a message for the Shardflux team without sending it. Use --yes to send only after approval.',
117
+ 'Agents: report only when your user asks you to send feedback. Show the exact message and context first,',
118
+ 'then obtain explicit authorization before using --yes. A failure or request for a feature is not consent.',
119
+ 'Include the request id and error code. Leave out private data, secrets and code the user did not mean to',
120
+ 'share; paraphrase, never paste transcripts. Tell your user what was sent.',
125
121
  ];
126
122
  const LIFETIME_FILTER = {
127
123
  lifetime: { type: 'string', value: '<persistent|session|any>', description: 'Lifetime filter (default persistent: sessions are hidden).' },
@@ -132,8 +128,12 @@ const SECRET_SCOPE = {
132
128
  org: { type: 'string', value: '<organization-id>', description: 'Organization-wide secrets of this organization (owners/admins; project API keys get 403). Default: the API key’s project.' },
133
129
  };
134
130
  const SECRET_VALUE = {
135
- 'from-file': { type: 'string', value: '<file>', description: 'Read the value from this file. Default: standard input (pipe it in). One trailing newline is removed. Values are never accepted as arguments.' },
131
+ 'from-file': { type: 'string', value: '<file>', description: 'Read the value from this file. Default: standard input (pipe it in), a hidden prompt on a terminal, or the --ask page in a coding agent’s shell. One trailing newline is removed. Values are never accepted as arguments.' },
132
+ ask: { type: 'boolean', description: 'Your user types the value in a page shard opens in the browser; it goes straight to the API, never through the coding agent (0.20.1+). The default in a coding agent’s shell when nothing is piped.' },
133
+ timeout: { type: 'string', value: '<duration>', description: 'How long the --ask page waits (default 10m).' },
136
134
  };
135
+ /** What --ask prints (0.20.1+), for the help of each command that asks. */
136
+ const ASK_EVENTS = 'The page’s link is printed too ("Opened a page in your browser to type …"); with --json the first line is {"event":"open","url":…,"opened":true|false,"expires_in":600}, then the result once the page was submitted.';
137
137
  const SECRET_PERMISSIONS = {
138
138
  tool: { type: 'string', multiple: true, value: '<tool>', description: 'Tool that receives the value at session start (repeatable; exec, pty, files, process, git, browser). Default exec and pty.' },
139
139
  'allow-workspace': { type: 'string', multiple: true, value: '<workspace-id>', description: 'Only these workspaces may use it (repeatable). Default: any workspace of the allowed projects.' },
@@ -142,8 +142,8 @@ const SECRET_PERMISSIONS = {
142
142
  };
143
143
  // Cloud agents: a VM is the coding template, then the machine, then the project template.
144
144
  const LAYER_HELP = {
145
- machine: 'Your machine is what every cloud agent starts with: tools, credentials, dotfiles, browser logins, git identity, Claude/Codex settings and skills. "shard machine" opens its VM; change it there (or in any agent’s VM), then run "shard machine save". It is saved from an agent’s VM, so it always keeps Claude Code, Codex and the agent runtime.',
146
- template: 'The project template is this repository’s layer: toolchains, dependencies, services and project secrets. Setup after "shard ./" saves it; "shard template save" saves the current agent VM’s project changes. It is saved from an agent’s VM; "shard templates" builds templates for your own app’s workspaces instead.',
145
+ machine: 'Your machine is what every cloud agent starts with: tools, credentials, dotfiles, browser logins, git identity, Claude/Codex settings and skills. On your computer, "shard machine open --json" opens its source VM; change that VM, then run "shard machine save --wait". The computer’s CLI selects the machine VM, regardless of your current folder or conversation. Inside a VM, save uses that VM and requires a machine source; a conversation or unmarked fork returns filesystem_source_mismatch. To keep changes from another VM, copy the intended files or repeat the install in the machine VM, then save it. A machine source keeps Claude Code, Codex and the agent runtime.',
146
+ template: 'The project template is this repository’s layer: toolchains, dependencies, services and project secrets. Setup after "shard ./" saves it; "shard template save" saves the current agent VM’s project changes, and "shard template delete" starts the project over from your machine. It is saved from an agent’s VM; "shard templates" builds templates for your own app’s workspaces instead.',
147
147
  };
148
148
  const KEY_COMMANDS = [
149
149
  { path: ['conversations', 'list'], summary: 'List project conversations', positionals: [], options: {
@@ -162,20 +162,33 @@ const KEY_COMMANDS = [
162
162
  { path: ['conversations', 'landing'], summary: 'Show or set how finished conversations reach this checkout', positionals: [{ name: 'mode' }, { name: 'backup' }], options: {
163
163
  path: { type: 'string', value: '<path>', description: 'Local project checkout (default the current directory).' }
164
164
  },
165
- description: 'While the macOS app runs, a conversation whose turn ends with committed work lands in the local branch it started from: the branch moves to its commits and the changed files are written, keeping your uncommitted edits (auto, the default). pr opens a pull request instead; off leaves conversations in the cloud. Every file a landing rewrites is backed up first: backups lists them, restore <backup> puts the files back.',
165
+ description: 'While the Shardflux app runs (the menu bar on macOS, the background helper on Linux), a conversation whose turn ends with committed work lands in the local branch it started from: the branch moves to its commits and the changed files are written, keeping your uncommitted edits (auto, the default). pr opens a pull request instead; off leaves conversations in the cloud. Every file a landing rewrites is backed up first: backups lists them, restore <backup> puts the files back.',
166
166
  examples: ['shard conversations landing', 'shard conversations landing pr', 'shard conversations landing backups', 'shard conversations landing restore 2026-10-09T12-00-00-000Z-fix-login-3fa2c1'] },
167
167
  ...['machine', 'template'].flatMap((level) => [
168
168
  { path: [level], summary: level === 'machine' ? 'Open your machine agent, or show its saved filesystem with --json' : 'Show this project’s template filesystem', positionals: [], options: {},
169
169
  description: LAYER_HELP[level] },
170
170
  { path: [level, 'save'], summary: `Save the ${level} filesystem as a new private version`, description: LAYER_HELP[level], positionals: [], options: {
171
- wait: { type: 'boolean', description: 'Return when the new version is ready (or failed), printing progress on stderr.' },
171
+ wait: { type: 'boolean', description: 'Return when the new version is ready (or failed), printing each phase and its progress on stderr (0.20.2+; with --json as {"event":"save_progress"} lines, and `phases` in the result).' },
172
172
  timeout: { type: 'string', value: '<duration>', description: 'With --wait: give up waiting after this long (default 10m). The save continues server side.' },
173
173
  } },
174
174
  { path: [level, 'rollback'], summary: `Restore a saved ${level} version`, positionals: [], options: { to: { type: 'string', value: '<version>', description: 'Version to restore (default previous saved version).' } } },
175
+ ...(level === 'template' ? [{ path: [level, 'delete'], summary: 'Delete this project’s template and its saved versions (0.20.1+)', positionals: [], options: {
176
+ yes: { type: 'boolean', description: 'Skip the confirmation.' },
177
+ }, description: 'Deletes the project template of this folder’s repository: its saved versions, binding and setup state. It is no longer rebuilt when your machine or the platform changes; new conversations start from your machine until setup after "shard ./" (or "shard template save") saves a new one. VMs running on it keep their files, and builds still running for it are stopped. In a terminal it asks first; elsewhere it needs --yes.',
178
+ examples: ['shard template delete', 'shard template delete --yes --json'] }] : []),
175
179
  ]),
176
180
  { path: ['machine', 'open'], summary: 'Create or resume your machine VM without attaching to it, and print its workspace key (0.17.1+)', positionals: [], options: {},
177
- description: 'For an agent building your machine from a terminal it does not attach to: install with "shard ws exec <key> -- bash -lc ...", write dotfiles and secret files with "shard files write <key> <path> --from -" (contents on standard input), then "shard machine save --wait". The first open copies your Claude and Codex settings into it, as "shard machine" does.',
181
+ description: 'For an agent building your machine from a terminal it does not attach to: install with "shard ws exec <key> -- bash -lc ...", write dotfiles with "shard files write <key> <path> --from -" (contents on standard input) and secret files with --ask (your user types them in a page shard opens; 0.20.1+), then "shard machine save --wait". The first open copies your Claude and Codex settings into it, as "shard machine" does.',
178
182
  examples: ['shard machine open --json', "shard ws exec \"$(shard machine open)\" -- bash -lc 'sudo apt-get install -y ripgrep'", 'shard machine save --wait --json'] },
183
+ { path: ['machine', 'setup'], summary: 'Let your own Claude Code or Codex set up your machine from this computer (0.20.0+)', positionals: [], options: {
184
+ 'from-this-computer': { type: 'boolean', description: 'Recreate this computer’s development setup on your machine: its CLIs and tools, language runtimes, shell config and aliases, git identity and dotfiles. Required.' },
185
+ 'if-needed': { type: 'boolean', description: 'Only when your machine was never set up from this computer (what the apps run after onboarding).' },
186
+ status: { type: 'boolean', description: 'Show the last setup from this computer instead of running one.' },
187
+ agent: { type: 'string', value: '<claude|codex|cloud>', description: 'Which agent sets it up (default: this computer’s Claude Code, else its Codex, else your cloud Claude in the machine VM).' },
188
+ timeout: { type: 'string', value: '<duration>', description: 'Time box for the agent (default 10m).' },
189
+ },
190
+ description: 'Your own coding agent does it, on your subscription, with no question asked: shard starts this computer’s Claude Code (claude -p) or Codex (codex exec) with a fixed task. On this computer it may only read (no edits, no ~/.ssh, keychain, token or credential files) and run shard on your machine VM; files go over standard input. Without Claude Code or Codex here, your cloud Claude does it inside the machine VM from a list of this computer’s tools and versions. Then shard copies your Claude and Codex config and saves the machine (shard machine save --wait). Progress goes to stderr, one line per step.',
191
+ examples: ['shard machine setup --from-this-computer', 'shard machine setup --from-this-computer --if-needed --json', 'shard machine setup --status --json'] },
179
192
  { path: ['agents', 'list'], summary: 'List coding agent sessions.', positionals: [], options: {
180
193
  ...PAGE,
181
194
  state: { type: 'string', value: '<working|waiting|exited>', description: 'Agent state filter.' },
@@ -191,13 +204,13 @@ const KEY_COMMANDS = [
191
204
  { path: ['whoami'], summary: 'Show the principal behind the project API key (SHARDFLUX_API_KEY, else the saved one)', positionals: [], options: {} },
192
205
  {
193
206
  path: ['update'],
194
- summary: 'Update shard, the menu bar app and a global MCP server to their latest versions',
207
+ summary: 'Update shard, the desktop app and a global MCP server to their latest versions',
195
208
  description: [
196
209
  'Checks every local Shardflux piece against GET /v1/client-versions: shard (the shardflux package or @shardflux/cli),',
197
- 'its SDK, the macOS menu bar app and a globally installed @shardflux/mcp. When one is behind, runs one install of',
198
- 'the latest versions with the package manager that installed shard (npm, pnpm, bun or Volta), then restarts the',
199
- 'menu bar on its new app. --check only reports. Projects that depend on @shardflux/sdk or shardflux (Python) keep',
200
- 'their own versions.',
210
+ 'its SDK, the desktop app (the macOS menu bar, or the Linux app that installs with shard) and a globally installed',
211
+ '@shardflux/mcp. When one is behind, runs one install of the latest versions with the package manager that installed',
212
+ 'shard (npm, pnpm, bun or Volta), then restarts the macOS menu bar on its new app. --check only reports. Projects',
213
+ 'that depend on @shardflux/sdk or shardflux (Python) keep their own versions.',
201
214
  ].join('\n'),
202
215
  positionals: [],
203
216
  options: { check: { type: 'boolean', description: 'Report what is behind without installing anything.' } },
@@ -211,7 +224,7 @@ const KEY_COMMANDS = [
211
224
  },
212
225
  {
213
226
  path: ['feedback'],
214
- summary: 'Tell the Shardflux team what failed, confused you or is missing (agents: as you work)',
227
+ summary: 'Preview feedback; send to the Shardflux team only with explicit approval',
215
228
  description: [
216
229
  ...FEEDBACK_WHY,
217
230
  '',
@@ -225,6 +238,7 @@ const KEY_COMMANDS = [
225
238
  ].join('\n'),
226
239
  positionals: [{ name: 'message', variadic: true, words: true }],
227
240
  options: {
241
+ yes: { type: 'boolean', description: 'Send this message and context after reviewing the preview and obtaining explicit user authorization (0.21.0+).' },
228
242
  category: { type: 'string', value: '<bug|confusing|missing|idea|praise|other>', description: 'Kind of feedback (default other; see Categories).' },
229
243
  workspace: { type: 'string', value: '<id|key>', description: 'The workspace it is about.' },
230
244
  'request-id': { type: 'string', value: '<id>', description: 'request_id from the error, so the team can find the logs.' },
@@ -244,7 +258,7 @@ const KEY_COMMANDS = [
244
258
  description: 'Default: the summary (meters, allowances, caps). --estimate: the subscription fee, usage charges and projected allowance use at period end. --series: usage over time from the ledger (hour: at most 31 days, day: at most 400 days per request). --workspace: the same series for one workspace (the dashboard’s per-workspace usage).',
245
259
  positionals: [],
246
260
  options: {
247
- org: { type: 'string', value: '<organization-id>', description: 'Organization (default: the API key’s organization).' },
261
+ org: { type: 'string', value: '<id|slug>', description: 'Organization (default: the API key’s organization). Another of your organizations uses your signed-in session; slugs require sign-in (0.20.2+).' },
248
262
  estimate: { type: 'boolean', description: 'Show the period estimate instead of the summary.' },
249
263
  series: { type: 'boolean', description: 'Show usage over time instead of the summary.' },
250
264
  workspace: { type: 'string', value: '<id|key>', description: 'Show usage over time of this workspace.' },
@@ -806,12 +820,15 @@ const KEY_COMMANDS = [
806
820
  positionals: [REF, { name: 'path', required: true }],
807
821
  options: {
808
822
  from: { type: 'string', value: '<file|->', description: 'Local file to upload, or - for standard input (default -).' },
823
+ ask: { type: 'boolean', description: 'Your user types or pastes the contents in a page shard opens in the browser, for secret files such as ~/.npmrc; the coding agent never sees them, and a new file gets mode 0600 (0.20.1+).' },
824
+ timeout: { type: 'string', value: '<duration>', description: 'How long the --ask page waits (default 10m).' },
809
825
  append: { type: 'boolean', description: 'Append instead of replacing.' },
810
826
  parents: { type: 'boolean', description: 'Create missing parent directories (default; --no-parents to refuse).' },
811
827
  ...IF_REVISION,
812
828
  ...WAKE_TIMING,
813
829
  },
814
- examples: ['shard files write acme/demo /home/user/notes.txt --from notes.txt', 'printf "x = 1\\n" | shard files write acme/agent-7 /home/user/app/config.py --if-revision 4'],
830
+ description: `--ask (0.20.1+) asks your user for a secret file’s contents on a page on this computer; the page says Saved once the file is written. ${ASK_EVENTS}`,
831
+ examples: ['shard files write acme/demo /home/user/notes.txt --from notes.txt', 'printf "x = 1\\n" | shard files write acme/agent-7 /home/user/app/config.py --if-revision 4', 'shard files write "$(shard machine open)" /home/user/.npmrc --ask --json'],
815
832
  },
816
833
  {
817
834
  path: ['files', 'ls'],
@@ -863,7 +880,7 @@ const KEY_COMMANDS = [
863
880
  {
864
881
  path: ['files', 'pull'],
865
882
  summary: 'Download a large file a conversation committed into ~/Shardflux',
866
- description: 'Each conversation’s committed changes appear in ~/Shardflux/<repository>/<base branch>/<conversation>/ while the macOS app runs. Files above the size limit are listed there as <file>.placeholder.txt; this downloads one in its place. <conversation> is its name, id or folder name.',
883
+ description: 'Each conversation’s committed changes appear in ~/Shardflux/<repository>/<base branch>/<conversation>/ while the Shardflux app runs (the menu bar on macOS, the background helper on Linux). Files above the size limit are listed there as <file>.placeholder.txt; this downloads one in its place. <conversation> is its name, id or folder name.',
867
884
  positionals: [{ name: 'conversation', required: true }, { name: 'path', required: true }],
868
885
  options: {},
869
886
  examples: ['shard files pull hype-video out/hype.mp4'],
@@ -879,6 +896,14 @@ const KEY_COMMANDS = [
879
896
  },
880
897
  },
881
898
  { path: ['operations', 'get'], summary: 'Show one operation', positionals: [{ name: 'operation-id', required: true }], options: {} },
899
+ { path: ['operations', 'progress'], summary: 'Read suspension preparation and capture progress without waking the workspace',
900
+ positionals: [{ name: 'operation-id', required: true }], options: {} },
901
+ { path: ['operations', 'cancel'], summary: 'Cancel suspension preparation before capture and retain the original workspace',
902
+ description: 'Only host-acknowledged pre-capture cancellation is accepted. Without --wait this is a request; poll the same operation for recovery. After capture starts, wait for the suspension outcome and resume. No reset or replacement.',
903
+ positionals: [{ name: 'operation-id', required: true }], options: {
904
+ wait: { type: 'boolean', description: 'Wait for confirmed original-workspace recovery (suspend_canceled).' },
905
+ timeout: { type: 'string', value: '<duration>', description: 'With --wait, give up after this long (default 5m); recovery continues server side.' },
906
+ } },
882
907
  {
883
908
  path: ['executions', 'get'],
884
909
  summary: 'Fetch the result of a file-first workspace’s execution (exit: its command’s code; 6 failed/lost; 5 still running)',
@@ -911,11 +936,11 @@ const KEY_COMMANDS = [
911
936
  },
912
937
  {
913
938
  path: ['secrets', 'create'],
914
- summary: 'Create a secret; the value is read from standard input or --from-file, never from arguments',
915
- description: 'Project secret of the API key’s project by default, or an organization-wide secret with --org. The value (UTF-8 text, up to 64 KiB) is encrypted and never shown again. Bind it to workspaces with "shard ws open --secret NAME" or "shard ws secrets <ws> --set NAME".',
939
+ summary: 'Create a secret (the value from the --ask page, standard input or --from-file; never from arguments)',
940
+ description: `Project secret of the API key’s project by default, or an organization-wide secret with --org. The value (UTF-8 text, up to 64 KiB) is encrypted and never shown again. Bind it to workspaces with "shard ws open --secret NAME" or "shard ws secrets <ws> --set NAME". With --ask (0.20.1+; the default in a coding agent’s shell when nothing is piped) your user types the value in a page on this computer and the coding agent never sees it; the page says Saved once the API stored it. ${ASK_EVENTS}`,
916
941
  positionals: [{ name: 'name', required: true }],
917
942
  options: { ...SECRET_SCOPE, ...SECRET_VALUE, description: { type: 'string', value: '<text>', description: 'Description (up to 500 characters).' }, ...SECRET_PERMISSIONS },
918
- examples: ['printf %s "$OPENAI_API_KEY" | shard secrets create OPENAI_API_KEY', 'shard secrets create DATABASE_URL --from-file ./database-url.txt --description "Staging database"'],
943
+ examples: ['shard secrets create GITHUB_TOKEN --ask --json', 'printf %s "$OPENAI_API_KEY" | shard secrets create OPENAI_API_KEY', 'shard secrets create DATABASE_URL --from-file ./database-url.txt --description "Staging database"'],
919
944
  },
920
945
  {
921
946
  path: ['secrets', 'list'],
@@ -944,10 +969,11 @@ const KEY_COMMANDS = [
944
969
  },
945
970
  {
946
971
  path: ['secrets', 'rotate'],
947
- summary: 'Store a new value as the next version (standard input or --from-file); older values are erased',
972
+ summary: 'Store a new value as the next version (the --ask page, standard input or --from-file); older values are erased',
973
+ description: `With --ask (0.20.1+; the default in a coding agent’s shell when nothing is piped) your user types the new value in a page on this computer and the coding agent never sees it. ${ASK_EVENTS}`,
948
974
  positionals: [SECRET_REF],
949
975
  options: { ...SECRET_SCOPE, ...SECRET_VALUE },
950
- examples: ['printf %s "$NEW_TOKEN" | shard secrets rotate API_TOKEN'],
976
+ examples: ['shard secrets rotate GITHUB_TOKEN --ask --json', 'printf %s "$NEW_TOKEN" | shard secrets rotate API_TOKEN'],
951
977
  },
952
978
  {
953
979
  path: ['secrets', 'versions'],
@@ -1182,24 +1208,28 @@ const KEY_COMMANDS = [
1182
1208
  /** Copied sessions are project-key commands; --json is the normal global output option. */
1183
1209
  const AGENT_CREDENTIAL_COMMANDS = [
1184
1210
  ...['sync', 'status', 'forget'].map((action) => ({ path: ['agents', 'credentials', action], summary: `${action} Codex copies and the account's cloud Claude login (contents never printed)`, positionals: [], options: {} })),
1185
- { path: ['agents', 'credentials', 'login'], summary: 'Sign in once to cloud Claude for this account', positionals: [{ name: 'app', required: true }], options: { workspace: { type: 'string', value: '<id|key>', description: 'Use this workspace for the cloud sign-in.' } } },
1211
+ { path: ['agents', 'credentials', 'login'], summary: 'Sign in once to cloud Claude for this account', positionals: [{ name: 'app', required: true }], options: {
1212
+ workspace: { type: 'string', value: '<id|key>', description: 'Use this workspace for the cloud sign-in.' },
1213
+ ask: { type: 'boolean', description: 'Use the browser pages even on a terminal (0.20.1+).' },
1214
+ timeout: { type: 'string', value: '<duration>', description: 'How long the sign-in waits for the code (default 10m).' },
1215
+ },
1216
+ description: 'On a terminal the Claude sign-in runs attached to it: open the claude.ai link, then paste its code. In a coding agent’s shell, with --json or without a terminal (0.20.1+) shard opens the claude.ai sign-in and a page on this computer where your user pastes the code, so the coding agent runs it itself and never sees the code. The links are printed too; with --json the first lines are {"event":"open","url":…} (claude.ai) and {"event":"ask","url":…} (the page), then the login.',
1217
+ examples: ['shard agents credentials login claude --json'] },
1186
1218
  ];
1187
1219
  const ACCOUNT_PROVIDER = { type: 'string', value: '<claude|codex>', description: 'Only this provider’s accounts.' };
1188
- /** Claude and ChatGPT/Codex accounts with their usage windows, resets and threads (0.18.0+). */
1220
+ /** Claude and ChatGPT/Codex accounts with their usage windows and resets, and the active one of each provider (0.18.0+). */
1189
1221
  const AGENT_ACCOUNT_COMMANDS = [
1190
- { path: ['agents', 'accounts'], summary: 'Claude and ChatGPT/Codex accounts with usage, resets and status (0.18.0+)', positionals: [], options: { provider: ACCOUNT_PROVIDER },
1191
- examples: ['shard agents accounts', 'shard agents accounts usage', 'shard agents accounts add claude', 'pbpaste | shard agents accounts add codex --api-key-stdin'] },
1192
- { path: ['agents', 'accounts', 'list'], summary: 'List the accounts: status, usage windows and when they reset (0.18.0+)', positionals: [], options: { provider: ACCOUNT_PROVIDER } },
1222
+ { path: ['agents', 'accounts'], summary: 'Claude and ChatGPT/Codex accounts: which one is active, usage, resets and status (0.18.0+)', positionals: [], options: { provider: ACCOUNT_PROVIDER },
1223
+ examples: ['shard agents accounts', 'shard agents accounts usage', 'shard agents accounts add claude', 'shard agents accounts use claude:2'] },
1224
+ { path: ['agents', 'accounts', 'list'], summary: 'List the accounts: which one is active, status, usage windows and when they reset (0.18.0+)', positionals: [], options: { provider: ACCOUNT_PROVIDER } },
1193
1225
  { path: ['agents', 'accounts', 'usage'], summary: 'Every usage window of every account, with its reset time (0.18.0+)', positionals: [], options: { provider: ACCOUNT_PROVIDER } },
1194
- { path: ['agents', 'accounts', 'add'], summary: 'Sign in a Claude or ChatGPT account, or add an API key (0.18.0+)', positionals: [{ name: 'provider', required: true }], options: {
1195
- 'api-key-stdin': { type: 'boolean', description: 'Add an API key read from standard input instead of signing in.' },
1226
+ { path: ['agents', 'accounts', 'add'], summary: 'Sign in a Claude or ChatGPT account (0.18.0+)', positionals: [{ name: 'provider', required: true }], options: {
1196
1227
  label: { type: 'string', value: '<name>', description: 'The account’s name in lists (default its email).' },
1197
- scope: { type: 'string', value: '<personal|team>', description: 'personal: only your threads use it; team: any member’s (default: team for team plans and API keys, else personal).' },
1198
1228
  workspace: { type: 'string', value: '<id|key>', description: 'Run the sign-in in this workspace (default your machine VM, from any folder).' },
1199
1229
  } },
1230
+ { path: ['agents', 'accounts', 'use'], summary: 'Make an account the active one of its provider: new conversations start with it and running ones switch to it', positionals: [{ name: 'account', required: true }], options: {},
1231
+ examples: ['shard agents accounts use claude:2', 'shard agents accounts use marc@example.com --json'] },
1200
1232
  { path: ['agents', 'accounts', 'remove'], summary: 'Remove an account and its login (0.18.0+)', positionals: [{ name: 'account', required: true }], options: {} },
1201
- { path: ['agents', 'accounts', 'move'], summary: 'Move an account up or down in its provider’s order (0.18.0+)', positionals: [{ name: 'account', required: true }, { name: 'direction', required: true }], options: {} },
1202
- { path: ['agents', 'accounts', 'pool'], hidden: true, summary: 'Pool accounts: each new thread on the account with the most room', positionals: [{ name: 'state', required: true }], options: {} },
1203
1233
  ];
1204
1234
  /** `shard accounts …`: the same commands, as the menu bar and the design name them. */
1205
1235
  const ACCOUNT_ALIASES = AGENT_ACCOUNT_COMMANDS.map(c => ({ ...c, path: c.path.slice(1), hidden: true }));
@@ -1230,7 +1260,7 @@ const ON_OPTIONS = {
1230
1260
  };
1231
1261
  export const PROJECT_COMMAND = {
1232
1262
  path: ['<path>'], summary: 'Start a conversation in its own cloud VM',
1233
- description: 'Opens Claude Code or Codex directly. --resume continues a project conversation. With --json it starts detached and prints the session. Detach with Ctrl-] then d.',
1263
+ description: 'Opens Claude Code or Codex directly. --resume continues a project conversation. With --json it starts detached and prints the session. Detach with Ctrl-] then d. From a shell with no terminal of its own (a coding agent\'s) it opens in a new tab of your terminal app, the one agents targets --terminal chose (0.20.1+).',
1234
1264
  positionals: [{ name: 'path', required: true }, { name: 'claude|codex' }], options: {
1235
1265
  ...AGENT_OPTIONS,
1236
1266
  ...MODEL_OPTIONS,
@@ -1258,6 +1288,9 @@ const AGENT_COMMANDS = [
1258
1288
  last: { type: 'boolean', description: 'This project’s most recent session.' },
1259
1289
  session: { type: 'string', value: '<id>', description: 'The session to move: a Claude Code session id or Codex thread id.' },
1260
1290
  path: { type: 'string', value: '<path>', description: 'The project folder (default: the current folder).' },
1291
+ plan: { type: 'boolean', description: 'Inspect all registered worktree paths, reference hits and file bytes before any upload.' },
1292
+ 'preserve-all-worktrees': { type: 'boolean', description: 'Preserve every registered worktree, all local refs, indices and ignored files. Reports paths and bytes; keeps a durable recovery archive.' },
1293
+ 'this-worktree-only': { type: 'boolean', description: 'Explicitly leave other registered worktrees and their local refs on this computer.' },
1261
1294
  claude: { type: 'boolean', description: 'With --last or --list: Claude Code sessions only.' },
1262
1295
  codex: { type: 'boolean', description: 'With --last or --list: Codex sessions only.' },
1263
1296
  list: { type: 'boolean', description: 'List this project’s sessions on this computer, and where the moved ones continue.' },
@@ -1268,7 +1301,8 @@ const AGENT_COMMANDS = [
1268
1301
  { path: ['conversations', 'try'], summary: 'Try a conversation in a synced local worktree', positionals: [{ name: 'conversation', required: true }], options: { path: { type: 'string', value: '<path>', description: 'Local project checkout.' }, stop: { type: 'boolean', description: 'Stop the device helper’s local sync and ports.' } } },
1269
1302
  { path: ['agents', 'targets'], summary: 'Detect apps or configure managed desktop connections', positionals: [{ name: 'agent' }], options: {
1270
1303
  remove: { type: 'boolean', description: 'Remove this agent’s managed entries, or all entries for this API URL.' },
1271
- 'open-in': { type: 'string', value: '<app=target,…>', description: 'Where new conversations open (0.17.0+): claude=terminal|claude-desktop|web|t3, codex=codex-app|t3, e.g. claude=t3,codex=t3.' },
1304
+ 'open-in': { type: 'string', value: '<target|app=target,…>', description: 'Where new conversations open (0.17.0+): claude=terminal|claude-desktop|web|t3, codex=codex-app|t3, e.g. claude=t3,codex=t3; a bare target sets every app that opens there, e.g. terminal or t3 (0.20.1+).' },
1305
+ terminal: { type: 'string', value: '<app>[:tab|:window]', description: 'The terminal app terminal conversations open in (0.20.1+): ghostty, iterm, terminal, wezterm, kitty, alacritty or warp, a new tab in its front window (:window for a window); auto clears the choice.' },
1272
1306
  path: { type: 'string', value: '<path>', description: 'Local project folder whose T3 Code state and open_in to report, as agents new --path names it (default the current folder; the agent argument wins) (0.17.0+).' },
1273
1307
  } },
1274
1308
  ...['claude-desktop', 'codex-app', 'web', 't3'].map(target => ({ path: ['agents', 'open', target], summary: `Open the agent in ${target}`, positionals: [{ name: 'agent' }], options: { reachable: AGENT_OPTIONS.reachable } })),
@@ -1282,7 +1316,12 @@ const AGENT_COMMANDS = [
1282
1316
  },
1283
1317
  })),
1284
1318
  ...['status', 'enable', 'disable'].map(action => ({ path: ['agents', 'codex', action], summary: `${action} the account's billed Codex Cloud connector`, positionals: [], options: {} })),
1285
- { path: ['agents', 'open'], summary: 'Open a conversation (Codex Cloud or the native Claude session)', description: 'Codex opens its existing Cloud thread without waking its VM. Claude attaches its native session.', positionals: [{ name: 'agent', required: true }], options: { ...AGENT_OPTIONS, app: { type: 'string', value: '<claude|codex>', description: 'Agent app (default remembered choice).' }, in: { type: 'string', value: '<terminal|auto|claude-desktop|codex-app|web|t3>', description: 'Where to open it. auto (0.15.1+): the saved target, else a Claude conversation in Claude Desktop when it is installed, else in this terminal; web opens claude.ai.' } } },
1319
+ { path: ['agents', 'open'], summary: 'Open a conversation (Codex Cloud or the native Claude session)', description: 'Codex opens its existing Cloud thread without waking its VM. Claude attaches its native session.', positionals: [{ name: 'agent', required: true }], options: { ...AGENT_OPTIONS, app: { type: 'string', value: '<claude|codex>', description: 'Agent app (default remembered choice).' }, in: { type: 'string', value: '<terminal|auto|claude-desktop|codex-app|web|t3>', description: 'Where to open it. auto (0.15.1+): the saved target, else a Claude conversation in Claude Desktop when it is installed, else in this terminal; web opens claude.ai. terminal (0.20.1+): a new tab of the terminal app agents targets --terminal chose, or this terminal when it is that one.' },
1320
+ window: { type: 'boolean', description: 'With --in terminal: a new window of the terminal app instead of a tab (0.20.1+).' } } },
1321
+ { path: ['agents', 'terminal-run'], hidden: true, summary: 'Run a command in the terminal app agents targets --terminal chose (0.20.1+)', positionals: [{ name: 'command', required: true, variadic: true }], options: {
1322
+ cwd: { type: 'string', value: '<path>', description: 'The folder the new tab starts in (default the current folder).' },
1323
+ window: { type: 'boolean', description: 'A new window instead of a tab.' },
1324
+ } },
1286
1325
  { path: ['agents', 'computers'], summary: 'List the project’s computers a new thread can start on with --on, and the threads on each (0.16.0+)', positionals: [], options: {
1287
1326
  path: { type: 'string', value: '<path>', description: 'Local project path (default the current directory).' },
1288
1327
  } },
@@ -1299,7 +1338,7 @@ const AGENT_COMMANDS = [
1299
1338
  } },
1300
1339
  { path: ['agents', 'fork'], summary: 'Fork an agent’s memory and disk', positionals: [{ name: 'agent', required: true }, { name: 'name', required: true }], options: WAIT },
1301
1340
  { path: ['agents', 'park'], summary: 'Suspend an agent when idle', positionals: [{ name: 'agent', required: true }], options: { 'when-idle': { type: 'string', value: '<duration>', description: 'Idle grace before suspend (default 1s).' } } },
1302
- { path: ['agents', 'config-sync'], hidden: true, summary: 'Copy this computer’s Claude and Codex config into a conversation VM (started in the background after a start, 0.19.0+)', positionals: [{ name: 'workspace', required: true }], options: {} },
1341
+ { path: ['agents', 'config-sync'], hidden: true, summary: 'Copy this computer’s Claude and Codex config into a conversation VM (started in the background after a start, 0.19.0+)', positionals: [{ name: 'workspace', required: true }], options: { 'memory-only': { type: 'boolean', description: 'Only the project memory (an account with a saved machine, 0.20.0+).' } } },
1303
1342
  { path: ['agents', 'logs'], summary: 'Tail an agent’s terminal from its output offset: a conversation id (0.18.1+) or an agent workspace key', positionals: [{ name: 'agent', required: true }], options: {
1304
1343
  app: { type: 'string', value: '<claude|codex>', description: 'Agent app (default remembered choice; a conversation uses its own).' },
1305
1344
  offset: { type: 'string', value: '<bytes>', description: 'Read from this output byte offset (default last 64 KiB).' },
@@ -1314,7 +1353,10 @@ const CLI_PROXY_COMMANDS = [
1314
1353
  { path: ['cli-proxy', 'import'], summary: 'Move CLIProxyAPI from this computer into your account, reachable at 127.0.0.1:8317 everywhere', description: `${CLI_PROXY_HELP} Import brings your config and logins over, stops the local copy (one copy refreshes each login), and connects this computer and your machine template. On a second computer it connects that computer.`, positionals: [], options: {
1315
1354
  config: { type: 'string', value: '<path>', description: 'Your CLIProxyAPI config file (default: the one the running CLIProxyAPI uses, else the usual locations).' },
1316
1355
  yes: { type: 'boolean', description: 'Stop the local CLIProxyAPI without asking.' },
1356
+ use: { type: 'string', multiple: true, value: '<email|file>', description: 'The login that is active for its provider when this computer has several (repeatable, one per provider). Default: the hub’s active login, else the oldest.' },
1317
1357
  }, examples: ['shard cli-proxy import', 'shard cli-proxy'] },
1358
+ { path: ['cli-proxy', 'use'], summary: 'Make a login its provider’s active one in your hosted CLIProxyAPI', positionals: [{ name: 'login', required: true }], options: {},
1359
+ examples: ['shard cli-proxy use ana@example.com'] },
1318
1360
  { path: ['cli-proxy', 'export'], summary: 'Bring your logins back to this computer and stop the hosted CLIProxyAPI', description: 'The hosted copy stops first, so only this computer refreshes your logins from then on. Its logins, as last refreshed, go to the auth folder the import took them from (0600), this computer stops forwarding 127.0.0.1:8317, and the command that starts your local CLIProxyAPI is shown. shard cli-proxy import moves them back.', positionals: [], options: {
1319
1361
  yes: { type: 'boolean', description: 'Do it without asking.' },
1320
1362
  } },
@@ -1325,7 +1367,14 @@ const MENUBAR_COMMANDS = ['start', 'stop', 'status'].map(action => ({
1325
1367
  path: ['menubar', action], summary: `${action === 'start' ? 'Install and start' : action === 'stop' ? 'Stop' : 'Show'} the macOS menu bar app`,
1326
1368
  positionals: [], options: { login: { type: 'string', value: '<on|off>', description: 'Enable or disable launch at login.' } },
1327
1369
  }));
1328
- export const COMMANDS = [...ACCOUNT_COMMANDS.filter((c) => c.path[0] !== 'templates'), ...AGENT_COMMANDS, ...AGENT_CREDENTIAL_COMMANDS, ...AGENT_ACCOUNT_COMMANDS, ...ACCOUNT_ALIASES, ...CLI_PROXY_COMMANDS, ...MENUBAR_COMMANDS, ...KEY_COMMANDS, ...ACCOUNT_COMMANDS.filter((c) => c.path[0] === 'templates')];
1370
+ const SETTINGS_COMMANDS = [{
1371
+ path: ['settings'], summary: 'Show or change this computer’s settings for cloud conversations (0.20.1+)', positionals: [], options: {
1372
+ 'title-prefix': { type: 'string', value: '<text|none|default>', description: 'The marker your cloud conversations’ Claude session titles start with in claude.ai and the Claude app ("☁ Fix login bug · todo-app"): up to 16 characters on one line; none for no marker, default for ☁. New conversations use it.' },
1373
+ },
1374
+ description: 'Shows the title marker, where new conversations open and the terminal app (change those two with "shard agents targets --open-in" and "--terminal"). The menu bar and desktop apps change the same settings.',
1375
+ examples: ['shard settings', 'shard settings --title-prefix SF', 'shard settings --title-prefix none', 'shard settings --title-prefix default --json'],
1376
+ }];
1377
+ export const COMMANDS = [...ACCOUNT_COMMANDS.filter((c) => c.path[0] !== 'templates'), ...AGENT_COMMANDS, ...AGENT_CREDENTIAL_COMMANDS, ...AGENT_ACCOUNT_COMMANDS, ...ACCOUNT_ALIASES, ...CLI_PROXY_COMMANDS, ...MENUBAR_COMMANDS, ...SETTINGS_COMMANDS, ...KEY_COMMANDS, ...ACCOUNT_COMMANDS.filter((c) => c.path[0] === 'templates')];
1329
1378
  const GROUPS = {
1330
1379
  device: 'Local device approvals',
1331
1380
  'cli-proxy': 'Your hosted CLIProxyAPI, reachable at http://127.0.0.1:8317 on this computer and in every VM',
@@ -1349,7 +1398,7 @@ const GROUPS = {
1349
1398
  workspaces: 'Open, inspect and manage workspaces',
1350
1399
  'workspaces port': 'Serve ports of a workspace at private HTTPS URLs: expose, list, close, tokens, browser links and callback URLs (0.9.0+)',
1351
1400
  files: 'Read, write, list, search and patch files in a workspace',
1352
- operations: 'Inspect and wait for lifecycle operations',
1401
+ operations: 'Inspect lifecycle operations, wait for outcomes and cancel suspension preparation',
1353
1402
  executions: 'Fetch the results of file-first executions (the commands "shard ws exec" runs in file-first workspaces)',
1354
1403
  credentials: 'Approve browser login requests from your vault',
1355
1404
  secrets: 'Manage secrets (values are write-only) and bind them to workspaces',
@@ -1501,8 +1550,9 @@ function toParseArgsOptions(opts) {
1501
1550
  * returns help text for --help, bare groups and non-TTY bare `shard`.
1502
1551
  */
1503
1552
  export function parseCommandLine(argv, options = {}) {
1504
- // `acp --login` is the ACP terminal sign-in an editor runs (ACP appends the method's args to `shard acp`).
1505
- if (argv.length === 2 && argv[0] === 'acp' && argv[1] === '--login')
1553
+ // The ACP terminal sign-in an editor runs: T3 Code appends the method's args to `shard acp` (`acp --login`); clients
1554
+ // following the ACP registry replace the agent's args with them (`--login`, 0.21.1+).
1555
+ if ((argv.length === 2 && argv[0] === 'acp' && argv[1] === '--login') || (argv.length === 1 && argv[0] === '--login'))
1506
1556
  return { kind: 'run', command: { path: ['acp'], summary: '', positionals: [], options: {} }, values: { login: true }, positionals: [], global: { apiUrl: undefined, json: false, agentLabel: undefined, wake: undefined, wakeTimeout: undefined } };
1507
1557
  // `menubar refresh` (0.15.2+, not in help): the background step each command may start (menubar.ts).
1508
1558
  if (argv.length === 2 && argv[0] === 'menubar' && argv[1] === 'refresh')
@@ -1715,7 +1765,7 @@ export function groupHelp(prefix) {
1715
1765
  const group = words.join(' ');
1716
1766
  const cmds = COMMANDS.filter((c) => !c.hidden && words.every((w, i) => c.path[i] === w));
1717
1767
  const w = Math.max(...cmds.map((c) => usageLine(c).length));
1718
- return [`Usage: shard ${group} <command> [options]`, '', `${GROUPS[group] ?? GROUPS[words[0]] ?? ''}.`, '', 'Commands:', ...cmds.map((c) => ` ${usageLine(c).padEnd(w)} ${c.summary}`), ...(group === 'workspaces' ? ['', 'Also: shard workspaces files <read|write|ls|stat|search|patch> ..., shard workspaces operations <list|get|wait> ... and shard workspaces executions get ...'] : []), '', `Run "shard ${group} <command> --help" for details.`].join('\n') + '\n';
1768
+ return [`Usage: shard ${group} <command> [options]`, '', `${GROUPS[group] ?? GROUPS[words[0]] ?? ''}.`, '', 'Commands:', ...cmds.map((c) => ` ${usageLine(c).padEnd(w)} ${c.summary}`), ...(group === 'workspaces' ? ['', 'Also: shard workspaces files <read|write|ls|stat|search|patch> ..., shard workspaces operations <list|get|wait|progress|cancel> ... and shard workspaces executions get ...'] : []), '', `Run "shard ${group} <command> --help" for details.`].join('\n') + '\n';
1719
1769
  }
1720
1770
  export const EXIT_CODE_HELP = [
1721
1771
  ' 0 success',
@@ -1731,7 +1781,7 @@ export const EXIT_CODE_HELP = [
1731
1781
  /** The top help's sections: every command's group (first word) is in exactly one (args.test.ts checks). */
1732
1782
  export const HELP_SECTIONS = [
1733
1783
  { title: 'Account (a user session: shard signup or shard auth login)', groups: ['signup', 'verify', 'auth', 'setup', 'env', 'orgs', 'projects', 'api-keys', 'members', 'invitations', 'billing', 'audit', 'account'] },
1734
- { title: 'Cloud agents (shard ./ starts one; a project API key)', groups: ['conversations', 'move', 'machine', 'template', 'agents', 'accounts', 'device', 'cli-proxy', 'ssh-proxy'] },
1784
+ { title: 'Cloud agents (shard ./ starts one; a project API key)', groups: ['conversations', 'move', 'machine', 'template', 'agents', 'settings', 'accounts', 'device', 'cli-proxy', 'ssh-proxy'] },
1735
1785
  { title: 'Workspaces for your app (a project API key: SHARDFLUX_API_KEY, else the one shard setup saved)', groups: ['login', 'whoami', 'status', 'usage', 'workspaces', 'files', 'operations', 'executions', 'secrets', 'credentials', 'volumes', 'egress'] },
1736
1786
  { title: 'Templates for your app’s workspaces (not for cloud agents; publish and archive use the session, the rest the API key)', groups: ['templates'] },
1737
1787
  { title: 'Other', groups: ['menubar', 'update', 'feedback', 'version'] },
@@ -1746,10 +1796,8 @@ export function topHelp() {
1746
1796
  ' shard <path> [claude|codex] [--approvals]',
1747
1797
  'Terminal: detach with Ctrl-] then d; /exit ends the agent session.',
1748
1798
  '',
1749
- 'Setting Shardflux up with a coding agent? Ask your user one question first: is it for a SaaS platform (their',
1750
- 'product runs VMs for its own customers through the API, SDK or MCP) or for their personal setup (their own cloud',
1751
- 'coding agents: each Claude or Codex conversation gets its own VM)? "shard setup --guide" prints the steps of both',
1752
- '(0.17.1+; --json for a list); so does "shard" in a shell without a terminal.',
1799
+ 'Setting Shardflux up for someone as their coding agent? "shard setup --guide" prints how (0.17.1+; --json for a',
1800
+ 'list; so does "shard" in a shell without a terminal): you run every step, they answer multiple-choice questions.',
1753
1801
  '',
1754
1802
  'Feedback: shard feedback [options] <message...>',
1755
1803
  ...FEEDBACK_WHY.map((l) => ` ${l}`),
@@ -1770,6 +1818,7 @@ export function topHelp() {
1770
1818
  ' shard ./ # a conversation on this folder’s repository: Claude Code in its own VM (shard ./ codex: Codex; --resume: a saved one)',
1771
1819
  ' shard machine save # what every agent starts with: tools, logins, dotfiles, Claude/Codex config',
1772
1820
  ' shard template save # this project: toolchains, dependencies, services, project secrets',
1821
+ ' shard template delete # start this project over from your machine; setup saves a new one',
1773
1822
  ' A VM is the coding template, then your machine, then the project template. Setup after shard ./ saves the',
1774
1823
  ' project template; machine and project templates are always saved from an agent’s VM, so they keep Claude Code,',
1775
1824
  ' Codex and the agent runtime. "shard templates" is for your own app’s workspaces, not for agents: a repository',
package/dist/bin.js CHANGED
@@ -4,5 +4,9 @@
4
4
  * waits/streams cleanly (exit 130); a second Ctrl-C exits at once. Output is flushed before exiting (stdout/stderr
5
5
  * pipes are asynchronous on macOS). The `shardflux` npm bundle calls main() with its own identity instead.
6
6
  */
7
- import { main } from "./main.js";
7
+ import { enableCompileCache } from 'node:module';
8
+ // V8's compiled code for the CLI's modules is kept on disk (the OS temp directory) and reused: ~0.1 s off each
9
+ // command's start (0.21.1+). Best effort: without a writable cache the CLI compiles as before.
10
+ enableCompileCache?.();
11
+ const { main } = await import("./main.js");
8
12
  await main();
package/dist/browser.d.ts CHANGED
@@ -3,5 +3,16 @@ export declare function openerFor(url: string, env: Record<string, string | unde
3
3
  command: string;
4
4
  args: string[];
5
5
  };
6
- /** Starts the browser detached and returns whether it could be started; only http(s) URLs are opened. */
7
- export declare function openUrl(url: string, env: Record<string, string | undefined>): boolean;
6
+ /** How long an opener that has not exited counts as a browser that started in the foreground ($BROWSER=firefox, or
7
+ * xdg-open running one): opened. xdg-open hands a link to the desktop and exits well within it. */
8
+ export declare const OPENER_SETTLE_MS = 3000;
9
+ /**
10
+ * Starts the browser detached and resolves whether it took the URL: false when the opener cannot start (no xdg-open:
11
+ * ENOENT) or exits with a failure (xdg-open exits 3 or 4 when nothing on the desktop opens the link, `open` 1), so the
12
+ * caller says to open the printed link; true when it exits 0 or is still running after `settleMs`. Only http(s) URLs
13
+ * are opened.
14
+ */
15
+ export declare function openUrl(url: string, env: Record<string, string | undefined>, options?: {
16
+ platform?: NodeJS.Platform;
17
+ settleMs?: number;
18
+ }): Promise<boolean>;