@shardflux/cli 0.24.1 → 0.28.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 (76) hide show
  1. package/CHANGELOG.md +159 -1
  2. package/README.md +182 -35
  3. package/dist/account-pool.d.ts +11 -1
  4. package/dist/account-pool.js +48 -0
  5. package/dist/acp-journal.js +23 -1
  6. package/dist/agent-commands.js +4 -3
  7. package/dist/agent-models.d.ts +31 -1
  8. package/dist/agent-models.js +27 -4
  9. package/dist/args.d.ts +1 -1
  10. package/dist/args.js +97 -14
  11. package/dist/board-args.js +26 -4
  12. package/dist/board-commands.d.ts +9 -5
  13. package/dist/board-commands.js +43 -16
  14. package/dist/brain-commands.d.ts +2 -0
  15. package/dist/brain-commands.js +78 -0
  16. package/dist/client-settings.d.ts +2 -1
  17. package/dist/client-settings.js +14 -2
  18. package/dist/codex-cloud.d.ts +2 -0
  19. package/dist/codex-cloud.js +14 -11
  20. package/dist/commands.js +8 -0
  21. package/dist/conversation-commands.d.ts +5 -1
  22. package/dist/conversation-commands.js +76 -8
  23. package/dist/conversation-terminal.js +2 -2
  24. package/dist/conversations.d.ts +2 -1
  25. package/dist/desktop-linux.d.ts +12 -1
  26. package/dist/desktop-linux.js +47 -18
  27. package/dist/device-approvals.js +24 -2
  28. package/dist/device-bridge.d.ts +21 -4
  29. package/dist/device-bridge.js +173 -7
  30. package/dist/device-folders.d.ts +14 -1
  31. package/dist/device-folders.js +20 -8
  32. package/dist/device-helper.d.ts +8 -0
  33. package/dist/device-helper.js +83 -13
  34. package/dist/device-mode.d.ts +44 -0
  35. package/dist/device-mode.js +119 -0
  36. package/dist/drive-commands.d.ts +2 -2
  37. package/dist/drive-commands.js +1 -1
  38. package/dist/errors.js +2 -1
  39. package/dist/format.js +6 -2
  40. package/dist/handlers.js +5 -2
  41. package/dist/land.d.ts +121 -0
  42. package/dist/land.js +284 -0
  43. package/dist/mac-app.d.ts +11 -0
  44. package/dist/mac-app.js +11 -0
  45. package/dist/main.js +9 -1
  46. package/dist/menubar.d.ts +18 -13
  47. package/dist/menubar.js +60 -50
  48. package/dist/onboarding.d.ts +4 -4
  49. package/dist/orchestrator-args.d.ts +7 -0
  50. package/dist/orchestrator-args.js +64 -0
  51. package/dist/orchestrator-commands.d.ts +43 -0
  52. package/dist/orchestrator-commands.js +262 -0
  53. package/dist/person-scope.d.ts +20 -0
  54. package/dist/person-scope.js +61 -0
  55. package/dist/project-hub-seed.d.ts +20 -0
  56. package/dist/project-hub-seed.js +59 -0
  57. package/dist/registry.d.ts +1 -1
  58. package/dist/registry.js +2 -1
  59. package/dist/router-commands.d.ts +12 -10
  60. package/dist/router-commands.js +5 -59
  61. package/dist/t3-launch.js +2 -2
  62. package/dist/task-land-hub.py +165 -0
  63. package/dist/this-computer.d.ts +4 -2
  64. package/dist/this-computer.js +20 -2
  65. package/dist/thread-on-computer.js +2 -2
  66. package/dist/timing.js +3 -1
  67. package/dist/transcribe-commands.d.ts +7 -0
  68. package/dist/transcribe-commands.js +84 -0
  69. package/dist/update.js +4 -2
  70. package/dist/wait-status.d.ts +1 -1
  71. package/dist/wait-status.js +2 -0
  72. package/dist/worktree-twin-engine.d.ts +8 -0
  73. package/dist/worktree-twin-engine.js +29 -23
  74. package/dist/worktree-twin.d.ts +6 -0
  75. package/dist/worktree-twin.js +8 -1
  76. package/package.json +6 -5
@@ -6,7 +6,7 @@
6
6
  * With --json every write prints the updated `{accounts, pool}` block (the menu bar redraws from it), plus the account
7
7
  * it changed. Sign-ins run the provider's own login in a workspace, as `shard agents credentials login claude` does.
8
8
  */
9
- import type { AgentAccount, AgentAccountList, AgentAccountWindow } from '@shardflux/sdk';
9
+ import type { AgentAccount, AgentAccountList, AgentAccountModels, AgentAccountWindow } from '@shardflux/sdk';
10
10
  import type { Ctx, Handler } from './commands.js';
11
11
  /**
12
12
  * Codex names its windows primary and secondary; their length says which is the 5-hour and which the weekly one (a
@@ -31,6 +31,16 @@ export declare function formatAccounts(list: AgentAccountList, now: number): str
31
31
  export declare function formatUsage(list: AgentAccountList, now: number): string;
32
32
  /** Codex's device sign-in page, as Codex 0.160.0 prints it (test/fixtures/cloud-agents/codex-login-device-auth-0.160.0.json). */
33
33
  export declare function codexLoginUrls(output: string): string[];
34
+ /**
35
+ * `shard agents models` (0.27.0+): each account's models as its provider lists them for that account, the way Claude
36
+ * Code and Codex ask for their pickers. The default effort is starred; the harness's default model is marked.
37
+ */
38
+ export declare function formatModels(entries: Array<AgentAccountModels & {
39
+ label?: string;
40
+ active?: boolean;
41
+ }>, now: number): string;
42
+ /** `shard agents models` (no `shard models` alias: that one is the model template). */
43
+ export declare const AGENT_MODELS_HANDLERS: Record<string, Handler>;
34
44
  export declare const ACCOUNT_POOL_HANDLERS: Record<string, Handler>;
35
45
  /** The `accounts` block of `shard agents targets --json` (0.18.0+): the account list for the menu bar; null when it cannot be read. */
36
46
  export declare function targetsAccounts(ctx: Ctx): Promise<AgentAccountList | null>;
@@ -207,6 +207,54 @@ async function signIn(ctx, values, app) {
207
207
  await api.cancelSignIn(claim.sign_in_id).catch(() => undefined);
208
208
  }
209
209
  }
210
+ const REASONS = { disabled: 'turned off for this account', not_entitled: 'not included for this account', not_in_plan: 'not in this plan' };
211
+ /** "3m ago", "2h ago". */
212
+ const ago = (at, now) => { const m = Math.max(0, Math.round((now - Date.parse(at)) / 60_000)); return m < 60 ? `${m}m ago` : `${Math.round(m / 60)}h ago`; };
213
+ /**
214
+ * `shard agents models` (0.27.0+): each account's models as its provider lists them for that account, the way Claude
215
+ * Code and Codex ask for their pickers. The default effort is starred; the harness's default model is marked.
216
+ */
217
+ export function formatModels(entries, now) {
218
+ if (!entries.length)
219
+ return 'No Claude or ChatGPT/Codex account is signed in. Add one with shard agents accounts add claude|codex.\n';
220
+ const lines = [];
221
+ for (const e of entries) {
222
+ const freshness = e.source === 'builtin' ? 'the harness’s own list until the provider answers' : e.stale && e.fetched_at ? `as of ${ago(e.fetched_at, now)}` : '';
223
+ lines.push(`${PROVIDERS[e.provider]}${e.label ? ` · ${e.label}` : ''}${e.active ? ' (active)' : ''}${freshness ? ` · ${freshness}` : ''}`);
224
+ const width = Math.max(...e.models.map(m => m.id.length), 8) + 2, nameWidth = Math.max(...e.models.map(m => m.name.length), 4) + 2;
225
+ const row = (m) => ` ${m.id.padEnd(width)}${m.name.padEnd(nameWidth)}${m.available
226
+ ? `${m.efforts.map(x => x === m.default_effort ? `${x}*` : x).join(' ') || 'no effort levels'}${m.id === e.default_model ? ' (default)' : ''}`
227
+ : `unavailable: ${m.reason_text ?? (m.reason ? REASONS[m.reason] : 'not offered')}`}`;
228
+ const main = e.models.filter(m => m.section === 'main'), more = e.models.filter(m => m.section === 'more');
229
+ lines.push(...main.map(row));
230
+ if (more.length)
231
+ lines.push(' More models', ...more.map(row));
232
+ lines.push('');
233
+ }
234
+ lines.push('Start with one: shard agents new <claude|codex> --model <id> --effort <level> (* the model’s default effort).');
235
+ return `${lines.join('\n')}\n`;
236
+ }
237
+ /** `shard agents models` (no `shard models` alias: that one is the model template). */
238
+ export const AGENT_MODELS_HANDLERS = {
239
+ async 'agents models'(ctx, values) {
240
+ const api = ctx.cloud().agentAccounts;
241
+ const refresh = values.refresh === true;
242
+ if (values.account !== undefined) {
243
+ if (typeof values.account !== 'string' || !values.account.trim())
244
+ throw new UsageError('--account needs an account: its id, claude:<n>, codex:<n>, email or label.');
245
+ const account = findAccount(await api.list(), values.account.trim());
246
+ if (account.kind !== 'oauth')
247
+ throw new UsageError(`${name(account)} is an API key: it has no model list. Sign in with shard agents accounts add ${account.provider}.`);
248
+ const models = await api.models(account.id, { refresh });
249
+ out(ctx, formatModels([{ ...models, label: name(account), active: isActive(account) }], Date.now()), models);
250
+ return 0;
251
+ }
252
+ const all = await api.allModels({ provider: optionalProvider(values.provider), refresh });
253
+ const shown = values.all === true ? all.accounts : all.accounts.filter(a => a.active);
254
+ out(ctx, formatModels(shown, Date.now()), { accounts: shown });
255
+ return 0;
256
+ },
257
+ };
210
258
  export const ACCOUNT_POOL_HANDLERS = {
211
259
  async 'agents accounts'(ctx, values) {
212
260
  const list = await ctx.cloud().agentAccounts.list({ provider: optionalProvider(values.provider) });
@@ -90,6 +90,28 @@ export class AcpJournal {
90
90
  }
91
91
  #wanted() { return !this.#signal.aborted && !this.rpc.closed && this.#paused === 0 && (this.rpc.busy || this.#cursor.prompt_id !== undefined); }
92
92
  async #deliver(inputId, message) {
93
+ let failures = 0;
94
+ const started = performance.now();
95
+ while (true) {
96
+ this.#signal.throwIfAborted();
97
+ try {
98
+ return await this.#deliverOnce(inputId, message);
99
+ }
100
+ catch (error) {
101
+ // Guest restarts can refuse a new input command while its native
102
+ // supervisor keeps running. Its persisted receipt deduplicates this
103
+ // exact input id, including a command accepted before the refusal.
104
+ // An unverified receipt or a permanent error still stops delivery.
105
+ if (!(error instanceof ShardfluxApiError) || !error.retryable)
106
+ throw error;
107
+ const remaining = RECONNECT_BUDGET_MS - (performance.now() - started);
108
+ if (remaining <= 0)
109
+ throw new Error('ACP input reconnect timed out; load this session to continue.', { cause: error });
110
+ await delay(Math.min(reattachDelayMs(error, ++failures), remaining), undefined, { signal: this.#signal });
111
+ }
112
+ }
113
+ }
114
+ async #deliverOnce(inputId, message) {
93
115
  const dir = `/home/user/.local/share/shardflux/acp/${this.#conversation}`;
94
116
  const payload = Buffer.from(`{"mode":"input","input_id":${JSON.stringify(inputId)},"message":${message}}\n`);
95
117
  if (payload.length <= ONE_SHOT_INPUT) {
@@ -133,7 +155,7 @@ export class AcpJournal {
133
155
  finally {
134
156
  await this.#cell.exec.cancel(session.session_id, 0).catch(() => undefined);
135
157
  }
136
- // Never resend an uncertain mutation. Its stable input identity is fenced by CVB before provider stdin.
158
+ // Retries retain inputId: CVB persists its receipt before provider stdin.
137
159
  }
138
160
  start() {
139
161
  this.#initial ??= this.#replay().then(() => { this.#ready = true; this.#follow(); }).catch(error => { this.rpc.close(error instanceof Error ? error : new Error(String(error))); throw error; });
@@ -33,7 +33,7 @@ import { guestFirstPrompt, INBOX_MAX_CHARS, submitInitialPrompt, prepareInitialC
33
33
  import { conversationForOpen, conversationWorkspace, conversationsUnavailable, getConversation, listConversations, resumeConversation } from "./conversations.js";
34
34
  import { conversationClient } from "./conversation-commands.js";
35
35
  import { conversationTerminal, reopenConversation } from "./conversation-terminal.js";
36
- import { CHOICE_PENDING, choiceReport, hasChoice, launchChoice, modelChoice, runFlags } from "./agent-models.js";
36
+ import { CHOICE_PENDING, choiceReport, hasChoice, launchChoice, liveModels, modelChoice, runFlags } from "./agent-models.js";
37
37
  const active = (session) => session.state === 'running' || session.state === 'starting';
38
38
  /** `<slug>-<6 hex of sha256(identity)>`: the same repository gets the same key on every machine. */
39
39
  export function disambiguatedSlug(slug, identity) {
@@ -135,7 +135,7 @@ export async function terminal(ctx, ws, app, agent, project, values, setupHash,
135
135
  });
136
136
  // This command's --model/--effort, else the conversation's own (a relaunch keeps its model). A computer whose guest
137
137
  // predates the choice starts the agent with its defaults, and this command says so once.
138
- const help = runHelp(cell, app, ctx.signal), explicit = modelChoice(values, app);
138
+ const help = runHelp(cell, app, ctx.signal), explicit = modelChoice(values, app, await liveModels(ctx, values, app));
139
139
  const { choice, applied } = await launchChoice(explicit, conversation?.choice, !!conversation, help);
140
140
  if (hasChoice(explicit) && !applied && !ctx.json)
141
141
  ctx.io.stderr.write(`${CHOICE_PENDING}\n`);
@@ -242,7 +242,8 @@ export async function launchProjectWith(ctx, values, [path = './', explicitApp],
242
242
  throw new UsageError(`${target} is not an installed desktop target`);
243
243
  const app = explicitApp ?? store.app(projectKey, like?.labels.app === 'codex' ? 'codex' : 'claude');
244
244
  // A typo in --model or --effort is refused before anything starts; T3 Code reads the same values (0.17.0+).
245
- const choice = modelChoice(values, target === 'codex-app' ? 'codex' : app);
245
+ const choiceApp = target === 'codex-app' ? 'codex' : app;
246
+ const choice = modelChoice(values, choiceApp, await liveModels(ctx, values, choiceApp));
246
247
  if (target === 'codex-app' || (app === 'codex' && !like && values.in === undefined && !linuxTerminalSession(ctx, values)))
247
248
  return launchCodexCloud(ctx, values, path);
248
249
  if (target === 'claude-desktop' && hasChoice(choice))
@@ -30,13 +30,42 @@ export interface AppModels {
30
30
  export declare const DEFAULT_CHOICE = "default";
31
31
  /** Claude Code `--model <alias>` and `--effort <level>`; Codex `-m <model>` and `-c model_reasoning_effort=<level>`. */
32
32
  export declare const MODEL_CHOICES: Readonly<Record<ModelApp, AppModels>>;
33
+ /** A model of the active account's live list (`shard agents models`, 0.27.0+): its id and the efforts it takes. */
34
+ export interface LiveModel {
35
+ id: string;
36
+ efforts: string[];
37
+ }
38
+ interface ModelsSource {
39
+ cloud(): {
40
+ agentAccounts: {
41
+ allModels(opts: {
42
+ provider?: ModelApp;
43
+ }): Promise<{
44
+ accounts: Array<{
45
+ active: boolean;
46
+ models: Array<{
47
+ id: string;
48
+ efforts: string[];
49
+ available: boolean;
50
+ }>;
51
+ }>;
52
+ }>;
53
+ };
54
+ };
55
+ }
56
+ /**
57
+ * The active account's available models (0.27.0+), asked only when `--model` is not one of the aliases above: a full
58
+ * model id is accepted when the account's provider lists it (GET /v1/agent-accounts/models). Null when the API cannot
59
+ * say (an API before the route, or a failure): then only the aliases are accepted.
60
+ */
61
+ export declare function liveModels(ctx: ModelsSource, values: Values, app: ModelApp): Promise<LiveModel[] | null>;
33
62
  /** What a thread starts with: absent fields are the agent's own default (no flag). */
34
63
  export interface ModelChoice {
35
64
  model?: string;
36
65
  effort?: string;
37
66
  }
38
67
  /** `--model` and `--effort` for this app, checked against its list. Throws a usage error for anything else. */
39
- export declare function modelChoice(values: Values, app: ModelApp): ModelChoice;
68
+ export declare function modelChoice(values: Values, app: ModelApp, live?: readonly LiveModel[] | null): ModelChoice;
40
69
  export declare const hasChoice: (c: ModelChoice | undefined) => c is ModelChoice;
41
70
  /** The guest's `sf-agent run` flags for a choice (sf-agent maps them to the agent's own flags). */
42
71
  export declare function runFlags(c: ModelChoice | undefined): string[];
@@ -59,3 +88,4 @@ export declare function choiceReport(c: ModelChoice, applied: boolean): {
59
88
  effort: string;
60
89
  applied: boolean;
61
90
  };
91
+ export {};
@@ -25,10 +25,31 @@ export const MODEL_CHOICES = {
25
25
  default: { model: 'gpt-6.1-sol', effort: null },
26
26
  },
27
27
  };
28
+ /**
29
+ * The active account's available models (0.27.0+), asked only when `--model` is not one of the aliases above: a full
30
+ * model id is accepted when the account's provider lists it (GET /v1/agent-accounts/models). Null when the API cannot
31
+ * say (an API before the route, or a failure): then only the aliases are accepted.
32
+ */
33
+ export async function liveModels(ctx, values, app) {
34
+ const model = typeof values.model === 'string' ? values.model.trim().toLowerCase() : '';
35
+ if (!model || MODEL_CHOICES[app].models.some(m => m.id === model))
36
+ return null;
37
+ // Another provider's model is refused without a request: Claude ids are claude-…; a Codex id is anything else.
38
+ const claudeId = model.startsWith('claude-') || MODEL_CHOICES.claude.models.some(m => m.id === model);
39
+ if (claudeId !== (app === 'claude'))
40
+ return null;
41
+ try {
42
+ const active = (await ctx.cloud().agentAccounts.allModels({ provider: app })).accounts.find(a => a.active);
43
+ return active ? active.models.filter(m => m.available).map(m => ({ id: m.id, efforts: m.efforts })) : null;
44
+ }
45
+ catch {
46
+ return null;
47
+ }
48
+ }
28
49
  const list = (ids) => ids.length > 1 ? `${ids.slice(0, -1).join(', ')} or ${ids.at(-1)}` : ids[0];
29
50
  const label = (app) => app === 'claude' ? 'Claude' : 'Codex';
30
51
  /** `--model` and `--effort` for this app, checked against its list. Throws a usage error for anything else. */
31
- export function modelChoice(values, app) {
52
+ export function modelChoice(values, app, live = null) {
32
53
  const raw = (name) => {
33
54
  const value = values[name];
34
55
  if (value === undefined)
@@ -39,9 +60,11 @@ export function modelChoice(values, app) {
39
60
  };
40
61
  const model = raw('model'), effort = raw('effort'), choices = MODEL_CHOICES[app];
41
62
  const option = model === undefined ? undefined : choices.models.find(m => m.id === model);
42
- if (model !== undefined && !option)
43
- throw new UsageError(`${label(app)} models: ${list(choices.models.map(m => m.id))}.`);
44
- const allowed = option?.efforts ?? choices.efforts.map(e => e.id);
63
+ const listed = model === undefined || option ? undefined : live?.find(m => m.id === model);
64
+ if (model !== undefined && !option && !listed) {
65
+ throw new UsageError(`${label(app)} models: ${list(choices.models.map(m => m.id))}${live?.length ? `, or one this account has: ${live.map(m => m.id).join(', ')}` : ', or a model id from shard agents models'}.`);
66
+ }
67
+ const allowed = option?.efforts ?? (listed ? [DEFAULT_CHOICE, ...listed.efforts] : choices.efforts.map(e => e.id));
45
68
  if (effort !== undefined && !allowed.includes(effort)) {
46
69
  throw new UsageError(`${option?.efforts ? option.label : label(app)} effort levels: ${list(allowed)}.`);
47
70
  }
package/dist/args.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const CLI_VERSION = "0.24.1";
1
+ export declare const CLI_VERSION = "0.28.0";
2
2
  export interface OptionSpec {
3
3
  type: 'string' | 'boolean';
4
4
  short?: string;
package/dist/args.js CHANGED
@@ -12,9 +12,10 @@
12
12
  import { parseArgs } from 'node:util';
13
13
  import { ACCOUNT_COMMANDS } from "./account-args.js";
14
14
  import { BOARD_COMMANDS } from "./board-args.js";
15
+ import { ORCHESTRATOR_COMMANDS } from "./orchestrator-args.js";
15
16
  import { UsageError } from "./errors.js";
16
17
  import { DEFAULT_CHOICE, MODEL_CHOICES } from "./agent-models.js";
17
- export const CLI_VERSION = '0.24.1';
18
+ export const CLI_VERSION = '0.28.0';
18
19
  export const GLOBAL_OPTIONS = {
19
20
  'api-url': { type: 'string', value: '<url>', description: 'API base URL (default $SHARDFLUX_API_URL, else https://api.shardflux.dev).' },
20
21
  json: { type: 'boolean', description: 'Print machine-readable JSON on stdout (errors as JSON on stderr).' },
@@ -156,7 +157,7 @@ const KEY_COMMANDS = [
156
157
  { path: ['conversations', 'feed'], summary: 'Print project conversations as they change', positionals: [], options: {
157
158
  project: { type: 'string', value: '<id>', description: 'Project ID for a user session.' }, org: { type: 'string', value: '<id|slug>', description: 'Signed in: every project of this organization you work in (0.25.0+).' }, cursor: { type: 'string', value: '<cursor>', description: 'Continue after this feed cursor (--json prints it with each change).' }
158
159
  },
159
- description: 'Prints each conversation the moment it changes, until interrupted: "<id> <title> <state>", and why it needs you when it does; --json prints one {conversation, cursor} object per line. With an API key: its project. Signed in without a key: every project you work in, on one cursor (0.25.0+); --org follows one organization’s projects and uses your sign-in when you have one.',
160
+ description: 'Prints each conversation the moment it changes, until interrupted: "<id> <title> <state>", and why it needs you when it does; a deleted conversation shows once as "<id> Deleted conversation deleted" (0.25.0+); --json prints one {conversation, cursor} object per line. With an API key: its project. Signed in without a key: every project you work in, on one cursor (0.25.0+); --org follows one organization’s projects and uses your sign-in when you have one.',
160
161
  examples: ['shard c feed', 'shard c feed --org acme --json'] },
161
162
  { path: ['conversations', 'resume'], summary: 'Resume a saved conversation in a fresh VM', positionals: [{ name: 'id', required: true }], options: {} },
162
163
  { path: ['conversations', 'reader'], summary: 'Open a read-only project space reader', positionals: [{ name: 'project_id', required: true }], options: {} },
@@ -165,11 +166,12 @@ const KEY_COMMANDS = [
165
166
  { path: ['conversations', 'watch'], summary: 'Watch a conversation’s terminal live, read-only', positionals: [{ name: 'id', required: true }], options: {},
166
167
  description: 'Shows the terminal the conversation’s agent runs in (its pty_id, terminal conversations) as the agent works: its last 256 KiB, then live output, until the agent’s session exits or you press Ctrl-C. Nothing you type reaches it. Watching never keeps the computer awake while the agent is idle, and ends when the computer sleeps. The output is the terminal’s own bytes, so there is no --json form; shard agents logs <id> --json returns recent output as JSON.',
167
168
  examples: ['shard conversations watch conv_a7k2m4q6s3t5v7x2z4b6d3f5h7'] },
168
- { path: ['conversations', 'send'], summary: 'Send the next user turn', positionals: [{ name: 'id', required: true }, { name: 'text', variadic: true, words: true }], options: {
169
- file: { type: 'string', multiple: true, value: '<path>', description: 'Attach a file (repeatable, up to 20 of 64 MiB each): it lands in the conversation’s computer before the message, which lists its path there (0.25.0+).' }
169
+ { path: ['conversations', 'send'], summary: 'Send a message to a conversation’s agent', positionals: [{ name: 'id', required: true }, { name: 'text', variadic: true, words: true }], options: {
170
+ file: { type: 'string', multiple: true, value: '<path>', description: 'Attach a file (repeatable, up to 20 of 64 MiB each): it lands in the conversation’s computer before the message, which lists its path there (0.25.0+).' },
171
+ wait: { type: 'boolean', description: 'Wait up to 60 s for the agent to have the message and print what happened to it (0.27.0+).' }
170
172
  },
171
- description: 'Text and files reach the conversation’s agent as its next turn. A conversation that ended is restored into a fresh computer first. Attached files keep their names under /home/user/.shardflux/attachments/<conversation id>/; each stored path is printed.',
172
- examples: ['shard conversations send conv_6qz2dmu4kd3xa7v5l2hgmfnpjy "Fix the failing export test"', 'shard conversations send conv_6qz2dmu4kd3xa7v5l2hgmfnpjy "Why does this page break?" --file screenshot.png --file console.log'] },
173
+ description: 'A working agent takes the message into its running turn; an idle one starts its next turn with it. --wait (0.27.0+) waits up to 60 s and prints which: "Steered into the running turn.", "Started the next turn." or "Not delivered yet."; --json adds the delivery (state, delivered_at, steered). A conversation that ended is restored into a fresh computer first. Attached files keep their names under /home/user/.shardflux/attachments/<conversation id>/; each stored path is printed.',
174
+ examples: ['shard conversations send conv_6qz2dmu4kd3xa7v5l2hgmfnpjy "Fix the failing export test"', 'shard conversations send conv_6qz2dmu4kd3xa7v5l2hgmfnpjy "Use the staging database instead" --wait', 'shard conversations send conv_6qz2dmu4kd3xa7v5l2hgmfnpjy "Why does this page break?" --file screenshot.png --file console.log'] },
173
175
  { path: ['conversations', 'wait'], summary: 'Wait for a result or question', positionals: [{ name: 'id', required: true }], options: {
174
176
  until: { type: 'string', value: '<states>', description: 'Comma-separated conversation states.' }, timeout: { type: 'string', value: '<seconds>', description: 'Wait for 0..900 seconds (default 900).' }
175
177
  } },
@@ -194,8 +196,27 @@ const KEY_COMMANDS = [
194
196
  { path: ['conversations', 'events'], summary: 'Follow a conversation live: state, transcript, reply text and tool calls (0.25.0+)', positionals: [{ name: 'id', required: true }], options: {},
195
197
  description: 'Streams the conversation until Ctrl-C, one line per event: state changes, new transcript lines and tool calls, with the reply written as the agent streams it. With --json: one event per line, {"type":"conversation"|"transcript"|"delta","id":…} with the conversation, the transcript lines or the ACP session updates.',
196
198
  examples: ['shard c events conv_k3m5q7s2u4w6y3b5d7f2h4j6ln', 'shard c events conv_k3m5q7s2u4w6y3b5d7f2h4j6ln --json | jq -c \'select(.type=="delta")\''] },
197
- { path: ['conversations', 'land'], summary: 'Record the landing action performed by the agent', positionals: [{ name: 'id', required: true }, { name: 'action', required: true }], options: { into: { type: 'string', value: '<ref>', description: 'Destination branch or PR URL.' } } },
199
+ { path: ['conversations', 'land'], summary: 'Land a conversation’s work: merge lands it for real; pr, keep and discard record the decision', positionals: [{ name: 'id', required: true }, { name: 'action', required: true }], options: {
200
+ into: { type: 'string', value: '<ref>', description: 'pr: the PR URL; keep: the branch (merge always lands in the project’s default branch).' },
201
+ path: { type: 'string', value: '<folder>', description: 'merge: the folder to land in (default: where the conversation started on this computer).' }
202
+ },
203
+ description: 'merge (0.25.0+) merges the conversation’s branch into the project’s default branch on the project hub (a fast-forward, or a merge commit when it is clean), brings it into your folder on this computer the way finished conversations land (your uncommitted edits stay), checks the commit is in the branch, and only then records it. pr, keep and discard record what was decided.',
204
+ examples: ['shard conversations land conv_k3m5q7s2u4w6y3b5d7f2h4j6ln merge', 'shard conversations land conv_k3m5q7s2u4w6y3b5d7f2h4j6ln discard'] },
198
205
  { path: ['conversations', 'move'], summary: 'Move at the end of the turn', positionals: [{ name: 'id', required: true }, { name: 'to', required: true }], options: {} },
206
+ { path: ['conversations', 'archive'], summary: 'Archive conversations from your inbox, on every device (0.28.0+)', positionals: [{ name: 'id', required: true, variadic: true, words: true }], options: {},
207
+ description: 'Archives them from your own inbox, the same in the Shardflux app, the CLI and every client you use: each one carries your dismissed_at until you unarchive it. The conversations, their agents and computers carry on, and everyone else keeps their own inbox. Name up to 200 ids at once. Prints "Archived <id> <title>" for each, and "Not found: <id>" on stderr for an id you cannot read (exit 3 when none was found). --json prints {data, skipped}: the conversations with your seen_at and dismissed_at, and the ids not found.',
208
+ examples: ['shard c archive conv_k3m5q7s2u4w6y3b5d7f2h4j6ln', 'shard conversations archive conv_k3m5q7s2u4w6y3b5d7f2h4j6ln conv_a7k2m4q6s3t5v7x2z4b6d3f5h7 --json'] },
209
+ { path: ['conversations', 'unarchive'], summary: 'Bring archived conversations back to your inbox, on every device (0.28.0+)', positionals: [{ name: 'id', required: true, variadic: true, words: true }], options: {},
210
+ description: 'Brings them back to your own inbox everywhere: their dismissed_at reads null again. Name up to 200 ids at once. Prints "Unarchived <id> <title>" for each, and "Not found: <id>" on stderr for an id you cannot read (exit 3 when none was found). --json prints {data, skipped}.',
211
+ examples: ['shard c unarchive conv_k3m5q7s2u4w6y3b5d7f2h4j6ln'] },
212
+ { path: ['conversations', 'seen'], summary: 'Mark conversations seen: you opened them now, on every device (0.28.0+)', positionals: [{ name: 'id', required: true, variadic: true, words: true }], options: {},
213
+ description: 'Records that you opened them now, the same on every device: their seen_at becomes now, so a result that finished before then is no longer new to you anywhere. Name up to 200 ids at once. Prints "Marked seen <id> <title>" for each, and "Not found: <id>" on stderr for an id you cannot read (exit 3 when none was found). --json prints {data, skipped}.',
214
+ examples: ['shard c seen conv_k3m5q7s2u4w6y3b5d7f2h4j6ln conv_a7k2m4q6s3t5v7x2z4b6d3f5h7'] },
215
+ { path: ['conversations', 'delete'], summary: 'Delete a conversation everywhere: its computer, messages, transcript and branch (0.25.0+)', positionals: [{ name: 'id', required: true }], options: {
216
+ yes: { type: 'boolean', description: 'Required: confirm the deletion.' }
217
+ },
218
+ description: 'Deletes the conversation on every device at once: its agent stops and its computer is deleted (a computer other conversations share stays for them), its messages, transcript and permission requests go with it, and its branch and transcript copy leave the project space. From then on it leaves every list and reads not found. The person who started it (or created the API key that did) or an organization owner or admin deletes it; deleting it again answers the same. --json prints {id, deleted, deleted_at, operation}: operation is the deletion of its computer, null when other conversations share it.',
219
+ examples: ['shard c delete conv_k3m5q7s2u4w6y3b5d7f2h4j6ln --yes', 'shard conversations delete conv_k3m5q7s2u4w6y3b5d7f2h4j6ln --yes --json'] },
199
220
  { path: ['conversations', 'landing'], summary: 'Show or set how finished conversations reach this checkout', positionals: [{ name: 'mode' }, { name: 'backup' }], options: {
200
221
  path: { type: 'string', value: '<path>', description: 'Local project checkout (default the current directory).' }
201
222
  },
@@ -867,6 +888,12 @@ const KEY_COMMANDS = [
867
888
  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}`,
868
889
  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'],
869
890
  },
891
+ {
892
+ path: ['files', 'delete'],
893
+ summary: 'Delete a file or empty directory in a workspace',
894
+ positionals: [REF, { name: 'path', required: true }],
895
+ options: { recursive: { type: 'boolean', description: 'Delete a directory and its contents (default false).' }, ...IF_REVISION, ...WAKE_TIMING },
896
+ },
870
897
  {
871
898
  path: ['files', 'ls'],
872
899
  summary: 'List a directory in a workspace',
@@ -1268,6 +1295,17 @@ const AGENT_ACCOUNT_COMMANDS = [
1268
1295
  examples: ['shard agents accounts use claude:2', 'shard agents accounts use marc@example.com --json'] },
1269
1296
  { path: ['agents', 'accounts', 'remove'], summary: 'Remove an account and its login (0.18.0+)', positionals: [{ name: 'account', required: true }], options: {} },
1270
1297
  ];
1298
+ /** The models each account can use (0.27.0+); not aliased under `shard models` (the model template). */
1299
+ const AGENT_MODELS_COMMANDS = [
1300
+ { path: ['agents', 'models'], summary: 'The models each account can use, as Claude and ChatGPT list them for it: efforts, default, what the plan does not include (0.27.0+)', positionals: [], options: {
1301
+ account: { type: 'string', value: '<account>', description: 'This account’s models (its id, claude:<n>, codex:<n>, email or label) instead of the active accounts’.' },
1302
+ provider: ACCOUNT_PROVIDER,
1303
+ all: { type: 'boolean', description: 'Every account, not only the active one of each provider.' },
1304
+ refresh: { type: 'boolean', description: 'Ask the provider again (lists are kept for an hour).' },
1305
+ },
1306
+ description: 'Asked server side with each account’s own sign-in, the way Claude Code and Codex fill their model pickers; never starts a computer. Pass an id to shard agents new --model. --json prints {accounts: [...]} (one account with --account).',
1307
+ examples: ['shard agents models', 'shard agents models --account claude:2', 'shard agents models --provider codex --json'] },
1308
+ ];
1271
1309
  /** `shard accounts …`: the same commands, as the menu bar and the design name them. */
1272
1310
  const ACCOUNT_ALIASES = AGENT_ACCOUNT_COMMANDS.map(c => ({ ...c, path: c.path.slice(1), hidden: true }));
1273
1311
  const COMPUTER_OPTION = { type: 'boolean', description: 'Give the new conversation a desktop with a browser its agent drives (computer use).' };
@@ -1334,11 +1372,14 @@ const AGENT_COMMANDS = [
1334
1372
  detach: { type: 'boolean', description: 'Start it in the cloud without opening it here.' },
1335
1373
  }, examples: ['shard move --last', 'shard move --list', 'shard move --session 3d085e35-aa66-4866-9a53-f09719d6767c --detach --json'] },
1336
1374
  { path: ['device', 'requests'], summary: 'Review local device requests waiting for approval', positionals: [], options: {} },
1375
+ { path: ['device', 'approvals'], summary: 'How this computer answers your agents: auto (allow everything), read (allow reading, ask before changes) or ask (0.27.0+)',
1376
+ description: 'Without a mode: the current one and what ran without asking. Only your own agents run without asking; a teammate\'s request always asks.',
1377
+ positionals: [{ name: 'mode', required: false }], options: {}, examples: ['shard device approvals auto', 'shard device approvals ask'] },
1337
1378
  ...['approve', 'deny'].map((action) => ({ path: ['device', action], summary: `${action} a local device request`, positionals: [{ name: 'id', required: true }], options: action === 'approve' ? { always: { type: 'boolean', description: 'Allow matching requests for this project, or for a folder request (0.25.0+) that folder and what is inside it; never for other access outside the conversation folder.' } } : {} })),
1338
1379
  { path: ['conversations', 'start'], summary: 'Start a conversation from the project or on your machine, no folder needed (0.25.0+)', positionals: [], options: {
1339
1380
  project: { type: 'string', value: '<id>', description: 'The project (default: the API key’s, or this computer’s selected project).' },
1340
1381
  home: { type: 'boolean', description: 'Start on your machine’s home, without a repository: the agent runs in /home/user and starts threads in the project’s repositories.' },
1341
- repo: { type: 'string', value: '<remote>', description: 'The repository (default: the project’s most recently active one).' },
1382
+ repo: { type: 'string', value: '<remote>', description: 'The repository (default: the project’s only one; a project with several asks you to name it).' },
1342
1383
  branch: { type: 'string', value: '<name>', description: 'Start from this branch’s newest saved point in the project (default main, then master).' },
1343
1384
  app: { type: 'string', value: '<claude|codex>', description: 'Agent app (default claude).' },
1344
1385
  title: { type: 'string', value: '<text>', description: 'The conversation’s title (default: named by its first message).' },
@@ -1420,9 +1461,10 @@ const MENUBAR_COMMANDS = [{ path: ['menubar', 'account'], hidden: true, summary:
1420
1461
  const SETTINGS_COMMANDS = [{
1421
1462
  path: ['settings'], summary: 'Show or change this computer’s settings for cloud conversations (0.20.1+)', positionals: [], options: {
1422
1463
  '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.' },
1464
+ 'auto-land': { type: 'string', value: '<on|off>', description: 'Bring finished cloud work (conversations and board tasks) into your folders on this computer automatically (0.27.0+, default on).' },
1423
1465
  },
1424
- 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.',
1425
- examples: ['shard settings', 'shard settings --title-prefix SF', 'shard settings --title-prefix none', 'shard settings --title-prefix default --json'],
1466
+ description: 'Shows the title marker, where new conversations open, the terminal app (change those two with "shard agents targets --open-in" and "--terminal") and auto land. The menu bar and desktop apps change the same settings.',
1467
+ examples: ['shard settings', 'shard settings --title-prefix SF', 'shard settings --title-prefix none', 'shard settings --title-prefix default --json', 'shard settings --auto-land off'],
1426
1468
  }];
1427
1469
  /** The board's input bar from a terminal (0.25.0+): route a line of input, ask a quick question about the work. */
1428
1470
  const BOARD_SCOPE = {
@@ -1433,8 +1475,9 @@ const BOARD_SCOPE_HELP = 'Reads your organization’s projects and open tasks wi
1433
1475
  const ROUTER_COMMANDS = [
1434
1476
  {
1435
1477
  path: ['route'],
1436
- summary: 'Route input like the board’s input bar: its project, task, question or steer, split per task (0.25.0+)',
1478
+ summary: 'Deprecated: route input like the board’s input bar (use shard orchestrator say) (0.25.0+)',
1437
1479
  description: [
1480
+ 'Deprecated: shard orchestrator say <words> sends your input to your orchestrator, which routes it, answers it or files its tasks.',
1438
1481
  'Decides which project the input belongs to (or your home, for personal work), whether it is a new task, a question or a steer for an open task, and splits input that asks for several tasks into your own words per task, each with a title. "?" or "quick question" at the start makes it a question.',
1439
1482
  BOARD_SCOPE_HELP,
1440
1483
  'Prints the kind and where it goes, then each part. With --json: the RouterResult (kind, parts with text, title, project_id and home, task_ref, confidence, uncertain, forced, routed_by, latency_ms).',
@@ -1448,8 +1491,9 @@ const ROUTER_COMMANDS = [
1448
1491
  },
1449
1492
  {
1450
1493
  path: ['ask'],
1451
- summary: 'Ask a quick question about your work, answered from the board as it is written (0.25.0+)',
1494
+ summary: 'Deprecated: ask a quick question about your work (use shard orchestrator say) (0.25.0+)',
1452
1495
  description: [
1496
+ 'Deprecated: shard orchestrator say --wait <question> asks your orchestrator and prints its answer.',
1453
1497
  'Answers from your tasks, their plans and states, sealed reports, the conversations behind them and what their agents last wrote. The answer streams to standard output as it is written; one that needs work ends with "Make it a task?".',
1454
1498
  BOARD_SCOPE_HELP,
1455
1499
  'With --json: the whole answer as one object (text, needs_work, sources, answered_by, first_token_ms, total_ms).',
@@ -1461,6 +1505,24 @@ const ROUTER_COMMANDS = [
1461
1505
  },
1462
1506
  examples: ['shard ask what is the healthz work doing right now', 'shard ask --task task_healthzhealthzhealthzhealt "is it done?"', 'shard ask "what is blocking the pricing page?" --json'],
1463
1507
  },
1508
+ {
1509
+ path: ['transcribe'],
1510
+ summary: 'Turn a recording into text, with each word’s start and end (0.25.0+)',
1511
+ description: [
1512
+ 'Sends the audio to Shardflux speech to text and prints what it says. WebM/Opus (a browser recording) and PCM WAV are recognized from the file; raw 16-bit mono PCM needs --format pcm16 and --sample-rate. Up to 120 s of audio.',
1513
+ 'Uses your signed-in session, or SHARDFLUX_API_KEY when it is set. The audio is not kept.',
1514
+ 'Prints the text; --words adds one line per word with its start and end. With --json: the transcript (text, words with w, start and end in seconds, language, duration, truncated).',
1515
+ ].join('\n\n'),
1516
+ positionals: [{ name: 'file', required: true }],
1517
+ options: {
1518
+ format: { type: 'string', value: '<webm|wav|pcm16>', description: 'The audio format (default: from the file).' },
1519
+ 'sample-rate': { type: 'string', value: '<hz>', description: 'Samples per second of raw pcm16 audio (8000 to 48000).' },
1520
+ language: { type: 'string', value: '<code>', description: 'ISO 639-1 code of the language spoken (default: detected).' },
1521
+ words: { type: 'boolean', description: 'Print each word with its start and end.' },
1522
+ org: { type: 'string', value: '<id|slug>', description: 'Organization to transcribe in when you are in several (default: the saved one, shard orgs use, else your only organization).' },
1523
+ },
1524
+ examples: ['shard transcribe note.webm', 'shard transcribe meeting.wav --words', 'shard transcribe mic.pcm --format pcm16 --sample-rate 16000 --json'],
1525
+ },
1464
1526
  ];
1465
1527
  /** The project a drive or models command acts in (0.25.0+). */
1466
1528
  const PROJECT_SCOPE = {
@@ -1490,6 +1552,26 @@ const DRIVE_COMMANDS = [
1490
1552
  description: `${DRIVE_HELP} After rm the path no longer lists or reads; shard drive get <path> --sha256 <hex> still reads each of its versions.`,
1491
1553
  examples: ['shard drive rm tasks/t_ab12/notes/draft.md'] },
1492
1554
  ];
1555
+ /** Whose brain a brain command reads (0.27.0+): the person's in this organization. */
1556
+ const BRAIN_SCOPE = {
1557
+ org: { type: 'string', value: '<id|slug>', description: 'Organization whose brain it is (default: the saved one, shard orgs use, else your only organization). Slugs need your signed-in session.' },
1558
+ };
1559
+ const BRAIN_HELP = 'Your brain is your archive in an organization, kept by your orchestrator: every answer it publishes is filed as reports/<YYYY-MM-DD>/<folder>/report.md with the answer’s media next to it (image thumbnails under .thumbs/), with its own notes and files and an index.md of everything, newest first. Your orchestrator writes it; you read it from any device, with every computer asleep. Paths are relative to the brain’s root, the names ~/brain shows on your orchestrator’s computer. Uses your signed-in session, or SHARDFLUX_API_KEY when it is set (a key reads the brain of the member who created it).';
1560
+ const BRAIN_COMMANDS = [
1561
+ { path: ['brain', 'ls'], summary: 'List your brain’s files: size, saved time, type and path (0.27.0+)', positionals: [{ name: 'prefix' }], options: {
1562
+ ...BRAIN_SCOPE,
1563
+ all: { type: 'boolean', description: 'Fetch every page.' },
1564
+ limit: { type: 'string', value: '<n>', description: 'Page size (1 to 1000, default 200).' },
1565
+ cursor: { type: 'string', value: '<cursor>', description: 'Continue from a previous page.' },
1566
+ }, description: `${BRAIN_HELP} Lists the current version of each file, by path; a prefix such as reports/2026-10-17/ lists only that folder. --json returns data (each file with its sha256), next_cursor and the brain’s version, which moves whenever anything in it changes.`,
1567
+ examples: ['shard brain ls', 'shard brain ls reports/2026-10-17/', 'shard brain ls --all --json'] },
1568
+ { path: ['brain', 'get'], summary: 'Read a file from your brain: its bytes to standard output, or to a file with -o (0.27.0+)', positionals: [{ name: 'path', required: true }], options: {
1569
+ ...BRAIN_SCOPE,
1570
+ sha256: { type: 'string', value: '<hex>', description: 'Read exactly this version (the current one or an earlier one).' },
1571
+ out: { type: 'string', short: 'o', value: '<file>', description: 'Write the bytes to this file.' },
1572
+ }, description: `${BRAIN_HELP} The bytes are checked against their SHA-256 before they are written. --json without -o returns the file as content_base64 with its sha256, size and content_type.`,
1573
+ examples: ['shard brain get index.md', 'shard brain get reports/2026-10-17/training-progress-ab12cd34/report.md', 'shard brain get reports/2026-10-17/training-progress-ab12cd34/loss.png -o loss.png'] },
1574
+ ];
1493
1575
  const MODELS_HELP = 'The model template: which agent app (harness), model and effort each role of a task runs with, from the Claude and Codex accounts your organization connected. master plans and coordinates, builder does the work, hard takes the hardest parts, light the small ones, and reviewer reviews with a fresh context. The primary provider runs master, builder and hard; the other one, light and reviewer. A role whose account waits for its next usage window shows until when, and what the other provider would run now.';
1494
1576
  const MODEL_COMMANDS = [
1495
1577
  { path: ['models'], summary: 'Show which harness, model and effort each role of a task runs with (0.25.0+)', positionals: [], options: {
@@ -1504,7 +1586,7 @@ const MODEL_COMMANDS = [
1504
1586
  }, description: `${MODELS_HELP} The primary is this project’s choice when it has one, else the organization’s, else the provider connected first; clear removes the choice. Prints the template with the new primary.`,
1505
1587
  examples: ['shard models primary codex', 'shard models primary claude --org acme', 'shard models primary clear --project web'] },
1506
1588
  ];
1507
- export const COMMANDS = [...ACCOUNT_COMMANDS.filter((c) => c.path[0] !== 'templates'), ...AGENT_COMMANDS, ...ROUTER_COMMANDS, ...AGENT_CREDENTIAL_COMMANDS, ...AGENT_ACCOUNT_COMMANDS, ...ACCOUNT_ALIASES, ...CLI_PROXY_COMMANDS, ...MENUBAR_COMMANDS, ...SETTINGS_COMMANDS, ...KEY_COMMANDS, ...BOARD_COMMANDS, ...DRIVE_COMMANDS, ...MODEL_COMMANDS, ...ACCOUNT_COMMANDS.filter((c) => c.path[0] === 'templates')];
1589
+ export const COMMANDS = [...ACCOUNT_COMMANDS.filter((c) => c.path[0] !== 'templates'), ...AGENT_COMMANDS, ...ROUTER_COMMANDS, ...AGENT_CREDENTIAL_COMMANDS, ...AGENT_ACCOUNT_COMMANDS, ...AGENT_MODELS_COMMANDS, ...ACCOUNT_ALIASES, ...CLI_PROXY_COMMANDS, ...MENUBAR_COMMANDS, ...SETTINGS_COMMANDS, ...KEY_COMMANDS, ...ORCHESTRATOR_COMMANDS, ...BOARD_COMMANDS, ...DRIVE_COMMANDS, ...BRAIN_COMMANDS, ...MODEL_COMMANDS, ...ACCOUNT_COMMANDS.filter((c) => c.path[0] === 'templates')];
1508
1590
  const GROUPS = {
1509
1591
  device: 'Local device approvals',
1510
1592
  'cli-proxy': 'Your hosted CLIProxyAPI, reachable at http://127.0.0.1:8317 on this computer and in every VM',
@@ -1514,6 +1596,7 @@ const GROUPS = {
1514
1596
  'needs-you': 'Everything your agents wait on, oldest first, and one answer command for each (0.25.0+)',
1515
1597
  approvals: 'The approval mode and always-allow rules your agents work under (0.25.0+)',
1516
1598
  drive: 'The project drive: files every agent, phone and person of the project reads and writes, each version kept by its SHA-256 (0.25.0+)',
1599
+ brain: 'Your brain: your orchestrator’s answers, reports and notes, read from any device (0.27.0+)',
1517
1600
  models: 'The model template: which harness, model and effort each role of a task runs with, and the primary provider (0.25.0+)',
1518
1601
  agents: 'Cloud agent sessions and copied credentials',
1519
1602
  auth: 'Create an account and sign in (the session is saved in the credentials file); passwords, email, two-factor authentication and sessions',
@@ -1927,7 +2010,7 @@ export const EXIT_CODE_HELP = [
1927
2010
  /** The top help's sections: every command's group (first word) is in exactly one (args.test.ts checks). */
1928
2011
  export const HELP_SECTIONS = [
1929
2012
  { 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'] },
1930
- { title: 'Cloud agents (shard ./ starts one; a project API key)', groups: ['conversations', 'board', 'task', 'needs-you', 'approvals', 'route', 'ask', 'drive', 'models', 'move', 'machine', 'template', 'agents', 'settings', 'accounts', 'device', 'cli-proxy', 'ssh-proxy'] },
2013
+ { title: 'Cloud agents (shard ./ starts one; a project API key)', groups: ['conversations', 'orchestrator', 'brain', 'board', 'task', 'needs-you', 'approvals', 'route', 'ask', 'transcribe', 'drive', 'models', 'move', 'machine', 'template', 'agents', 'settings', 'accounts', 'device', 'cli-proxy', 'ssh-proxy'] },
1931
2014
  { 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'] },
1932
2015
  { title: 'Templates for your app’s workspaces (not for cloud agents; publish and archive use the session, the rest the API key)', groups: ['templates'] },
1933
2016
  { title: 'Other', groups: ['menubar', 'update', 'feedback', 'version'] },
@@ -80,6 +80,22 @@ export const BOARD_COMMANDS = [
80
80
  description: 'Your words go to the agent that runs the task, which adjusts its plan. The task keeps its own words.',
81
81
  examples: ['shard task steer task_h7g4d7tqyv2pz3a2npyo4rpgzq use postgres instead'],
82
82
  },
83
+ {
84
+ path: ['task', 'land'],
85
+ summary: 'Land a finished task: its result into your folder and the project’s default branch (0.25.0+)',
86
+ positionals: [{ name: 'id', required: true }],
87
+ options: { path: { type: 'string', value: '<folder>', description: 'The folder to land in (default: the project’s folder on this computer, if any).' } },
88
+ description: [
89
+ 'A task has one result: the branch shard/<task id>, where its master merged its workers’ branches. Landing merges it',
90
+ 'into the project’s default branch on the project hub (a fast-forward, or a merge commit when the merge is clean),',
91
+ 'then brings it into your folder on this computer the way finished conversations land there: your branch moves to the',
92
+ 'new commits, your uncommitted edits stay, and every file it rewrites is backed up first (shard conversations landing',
93
+ 'backups). Only after the commit is checked to be in the branch is the landing recorded on the board, so the task',
94
+ 'reads shipped. A conflict changes nothing: the task’s master is asked to merge and the task asks to land again.',
95
+ '--json returns {landed, result, hub, folder, recorded, message}.',
96
+ ].join('\n'),
97
+ examples: ['shard task land task_h7g4d7tqyv2pz3a2npyo4rpgzq', 'shard task land task_h7g4d7tqyv2pz3a2npyo4rpgzq --path ~/code/best-todo --json'],
98
+ },
83
99
  {
84
100
  path: ['task', 'clear-done'],
85
101
  summary: 'Clear every finished task from the board (0.25.0+)',
@@ -93,9 +109,11 @@ export const BOARD_COMMANDS = [
93
109
  positionals: [],
94
110
  options: PROJECT_FILTER,
95
111
  description: [
96
- 'Everything your agents wait on, across the board: permission requests (perm_…), questions (cq_…, ask_…),',
97
- 'credential requests (cred_…), finished work to land (land_…) and Later tasks to keep or clear (prune_…). Each line is',
98
- 'the id, what is asked, and the command, options or branch behind it. Answer with shard needs-you answer <id>.',
112
+ 'Everything your agents wait on, across the board: permission requests (perm_…), questions (cq_…, ask_…, and your',
113
+ 'orchestrator’s oq_…), credential requests (cred_…), finished work to land (land_…), Later tasks to keep or clear',
114
+ '(prune_…) and what your orchestrator asked your computer while it was offline (dev_…, "Waiting for your computer:',
115
+ 'list ~/code"). Each line is the id, what is asked, and the command, options or branch behind it. Answer with shard',
116
+ 'needs-you answer <id>.',
99
117
  '--json returns {data}.',
100
118
  ].join('\n'),
101
119
  examples: ['shard needs-you', 'shard needs-you --json'],
@@ -116,9 +134,13 @@ export const BOARD_COMMANDS = [
116
134
  ' project) or deny; an agent’s question takes --pick',
117
135
  ' cq_… --text <answer>: the agent gets it as its next message',
118
136
  ' ask_… --text <answer>: the task’s master gets it',
137
+ ' oq_… --text <answer>: the orchestrator thread that asked gets it (0.27.0+)',
119
138
  ' cred_… deny (the value goes through the credential request, sealed for its computer)',
120
- ' land_… land (the worker merges its branch and pushes it), keep (the branch stays) or discard',
139
+ ' land_… land (as shard task land: the result into your folder and the default branch), keep (the',
140
+ ' branch stays) or discard',
121
141
  ' prune_… --keep <task-id> for each task to keep, keep for all of them, or discard to clear them',
142
+ ' dev_… cancel: drops what your orchestrator asked your computer (it runs by itself when your computer is',
143
+ ' back) (0.27.0+)',
122
144
  'The agent goes on as soon as you answer.',
123
145
  ].join('\n'),
124
146
  examples: ['shard needs-you answer perm_01a1300c-0000-7000-8000-000000000001 always', 'shard needs-you answer ask_rep_66cbevz65m72ij7mbiyrw4xvdy --text "eu-north-1"', 'shard needs-you answer land_conv_id4zts6jxsqraju2pggr55n3tq land'],
@@ -10,11 +10,14 @@ import type { Handler } from './commands.js';
10
10
  /** A task as people read it: its title once it has one, else your words, on one line of at most `max` characters. */
11
11
  export declare function taskLabel(t: Pick<Task, 'title' | 'text'>, max?: number): string;
12
12
  /**
13
- * One task as `shard board` lists it: id, state (with why it waits to start: "queued (Claude usage resets 15:00)"),
14
- * title or words (a follow-up ↳, a suggestion marked), project.
13
+ * One task as `shard board` lists it: id, state (with why it waits to start, "queued (Getting its computer ready)", or
14
+ * what it does now, "working (Fixing the login cookie)"), title or words (a follow-up ↳, a suggestion marked), project.
15
15
  */
16
16
  export declare function taskLine(t: Task): string;
17
- /** One item that waits on you: its id, what is asked, then the command, the suggested answers or the branch behind it. */
17
+ /**
18
+ * One item that waits on you: its id, what is asked, then the command, the suggested answers or the branch behind it. A
19
+ * request your orchestrator made to your computer while it was offline reads "Waiting for your computer: list ~/code".
20
+ */
18
21
  export declare function needsLine(n: NeedsYouItem): string;
19
22
  /** A sealed report as --follow prints it: id, milestone, title, then its evidence labels. */
20
23
  export declare function reportLine(r: TaskReport): string;
@@ -38,9 +41,10 @@ export declare class BoardFollower {
38
41
  }
39
42
  /** A feed answer as `board --follow --json` prints it: the API's fields (with `board` after a reset). */
40
43
  export declare function feedJson(u: BoardUpdate): Record<string, unknown>;
41
- /** `shard task show`: the task, its plan and conversations, then every report oldest first with its evidence. */
44
+ /** `shard task show`: the task, its plan and conversations, then every report oldest first with its evidence. Every
45
+ * title, step and outcome prints whole (0.27.0+), however long the agent wrote it; the terminal wraps it. */
42
46
  export declare function taskDetailText(d: TaskDetail): string;
43
- /** An item's kind from its id (perm_, cq_, ask_, cred_, land_, prune_); undefined for another id. */
47
+ /** An item's kind from its id (perm_, cq_, ask_, oq_, cred_, land_, prune_, dev_); undefined for another id. */
44
48
  export declare function itemKind(id: string): NeedsYouItem['kind'] | undefined;
45
49
  export interface AnswerInput {
46
50
  word?: string | undefined;