@shardflux/cli 0.24.1 → 0.25.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - Device requests reach the right computer (0.25.0+): the background helper tells the API which conversations are
6
+ local to this computer (started or opened here), so their requests come here first, then to the computer you used
7
+ last, never to whichever was seen last. A project link with a first task (`project_link` with `brief`) reads the
8
+ start's whole JSON output; before, it failed with "Unexpected token '}'" although the conversation started.
9
+
10
+ - `shard board` and `shard task show` show what a working task does now ("working (Fixing the login cookie)") and
11
+ what each working step does, next to the plain waiting line of a task that waits to start.
12
+ - `shard transcribe <file>` (0.25.0+, SDK 0.28.0) prints what a recording says (WebM/Opus or PCM WAV from the file;
13
+ raw PCM with `--format pcm16 --sample-rate`); `--words` adds each word's start and end, `--json` prints the
14
+ transcript.
5
15
  - Faster conversation starts (0.24.1): the project's dependency fingerprint is read from Git's own file list instead of walking the whole folder, so ignored and untracked trees beside the project (agent worktrees, build output) no longer hold a start on **Getting your project…**. A folder with 1.2 million such files took 32 s per start; it now takes 0.3 s, with the same fingerprint.
6
16
  - A folder without Git that `shard` makes a repository of (a first chat, `shard projects link`) starts with its files as
7
17
  the first commit (0.25.0+; `.gitignore` respected, large media stay untracked), so a conversation starts from the
@@ -18,6 +28,26 @@
18
28
  could no longer open. Key and sign-in stores (`~/.ssh`, `~/.aws`, `.env` files, private keys) stay here. Up to 64
19
29
  files, 64 MiB in all, 16 MiB each; the command names what it brings and what stays, and `--json` adds `files` and
20
30
  `files_left`.
31
+ - 0.25.0 (pending): Allowance hints respect unavailable overage; operation errors retain the service’s recovery message and retry delay.
32
+
33
+ - Landing that lands (0.25.0+): `shard task land <task>` merges a finished task's result (`shard/<task id>`, where its
34
+ master merged its workers' branches; when a master did not, their branches are merged into it here when they merge
35
+ cleanly) into the project's default branch on the project hub, a fast-forward or a clean merge commit, never by
36
+ force; brings it into your folder on this computer the way finished conversations land (your branch moves, your
37
+ uncommitted edits stay, rewritten files are backed up first); checks the commit is in the branch; and only then
38
+ records it on the board (`Landed in ~/code/best-todo · 3f2a1c9`). A conflict changes nothing and asks the task's
39
+ master to merge (`land_conflict`). `shard needs-you answer land_… land` does the same, and
40
+ `shard conversations land <id> merge` now lands the conversation's branch the same way before it records it (before,
41
+ both only recorded a landing). `--path <folder>` picks the folder; `--json` returns `{landed, result, hub, folder,
42
+ recorded, message}`.
43
+
44
+ - `shard conversations delete <id> --yes` (`shard c delete`, 0.25.0+, SDK 0.28.0) deletes a conversation everywhere at
45
+ once: its agent stops, its computer is deleted (a computer other conversations share stays for them), and its
46
+ messages, transcript and branch go with it, on every device. Without `--yes` it is refused before any request, as
47
+ every deletion is. `--json` prints the API's answer, `{id, deleted, deleted_at, operation}`; deleting it again answers
48
+ the same. The person who started it (or created the API key that did) and organization owners and admins delete
49
+ conversations. `shard conversations feed` shows a deleted conversation once as `<id> Deleted conversation deleted`.
50
+ - ACP input recovery (0.25.0): temporary guest-agent interruptions use bounded backoff with the same native input identity. The editor retains its running turn; permanent errors and unverified receipts still require loading the session.
21
51
 
22
52
  - Tunnel recovery (0.24.1, SDK 0.27.3): a foreground reverse tunnel replaces its guest helper automatically when replay is lost. Existing TCP peers reset; fresh connections reach the new listener. SSH and device port-forward connections close cleanly for a new connection instead of looping on an unrecoverable cursor.
23
53
  - Folders on this computer (0.25.0+): an agent can list a folder here (`dir_list`: names, kinds, sizes and Git
@@ -87,7 +117,6 @@
87
117
  `codex_sign_in_required` and that line. The Codex remote device is never restarted for a sign-in (its Cloud threads
88
118
  keep running), and one starting again with the new login reads as connecting.
89
119
 
90
-
91
120
  - `shard conversations start` starts a conversation without a folder: from the project hub (`--repo`, `--branch`), or with `--home` a coordinator on your machine that spawns threads in the project's repositories. `--app`, `--model`, `--effort`, `--title`, `--project`; `--prompt` sends the first message, with `--file` attachments. Prints the conversation's id, key and state (`--json`: the conversation, its workspace and the message).
92
121
 
93
122
  - `shard conversations send <id> <text> --file <path>` (repeatable, up to 20 files of 64 MiB each) puts each file in the conversation's computer before the message, which lists their paths, and prints `Attached <path>` for each. The text may be left out when files are attached, and unquoted words are sent as one message.
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @shardflux/cli: `shard`
2
2
 
3
+ For error codes, retry timing, and plan recovery, see the [error reference](https://docs.shardflux.dev/reference/errors).
4
+
3
5
  Historical template file lists: reads queue checksum-verified reconstruction when a cold list is absent from the SQL cache. TypeScript SDK 0.27.0+ / Python 0.23.0+ wait full Retry-After for retryable API 409 file_list_indexing on GET/HEAD within the admission timeout. Template objects and version history stay available.
4
6
 
5
7
  Plan-derived API budgets (0.23.0+, SDK 0.27.0): `--json` error output includes `rate_limit` and `retry_after_seconds`; the text hint names the shared scope and delay. API request pacing follows the SDK admission timeout and preserves the request body. Reuse workspace credentials and reduce status polling when the returned budget asks for a delay.
@@ -705,6 +707,7 @@ shows the status; `shard version --check` asks now (`--json`: `{"cli", "sdk", "u
705
707
  | `usage [--org <id>] [--estimate \| --series \| --workspace <id\|key>] [--from T] [--to T] [--granularity hour\|day] [--meter M]` | Usage summary for the current period: plan, meters and allowances. `--estimate`: the bill so far and projected allowance use; `--series`: usage over time; `--workspace`: one workspace's usage over time (0.5.0+). The summary includes the [overage](#usage-and-overage) block (0.5.1+). |
706
708
  | `feedback [--yes] [--category C] [--workspace W] [--request-id ID] [--error-code CODE] [--command TEXT] [--from-file F\|-] <message...>` | Preview feedback; `--yes` sends after explicit user authorization (0.22.0+). See [Feedback](#feedback-preview-and-user-approval). |
707
709
  | `route [--task ID] [--org O] [--project P] <text...>`, `ask [--task ID] [--org O] [--project P] <question...>` | The board's input bar (0.25.0+): where a line of input goes (its project or your home; a task, a question or a steer, split per task), and a quick question about your work, answered as it is written. |
710
+ | `conversations delete <id> --yes` (`c delete`) | Delete a conversation everywhere at once (0.25.0+): its agent stops, its computer is deleted, and its messages, transcript and branch go with it. `--json` prints `{id, deleted, deleted_at, operation}`. See [Conversations](#conversations). |
708
711
  | `workspaces open <key> --template <slug> [--secret NAME]... [--input NAME=value]... [--input-file F] [--lifetime persistent\|session] [--mode processful\|file-first] [--cpu-millis N --memory-mib N --disk-gib N] [--allocation-mode fixed\|elastic] [--memory-held N] [--no-wait] [--timeout 5m] [--timing]` | Open by key. Creates the workspace on first use and reconnects or resumes it afterwards, never resetting it. Waits until it is ready (the template's start commands and services included) unless you pass `--no-wait`. `--secret` binds secret names (replacing the binding of an existing workspace). `--input` (0.4.0+) passes the template's text inputs (replacing those of an existing workspace); `--input-file` reads `NAME=value` lines or a JSON object. `--lifetime session`: the workspace is discarded when the session ends (`workspaces close` or the idle timeout), and the key then opens a new workspace. `--mode file-first` (0.5.0+) opens a [file-first workspace](#file-first-workspaces); the default is processful for a new key and the stored mode for an existing one. `--allocation-mode elastic` (0.8.0+): [elastic memory](#elastic-memory-080). |
709
712
  | `workspaces list [--state S] [--prefix P] [--lifetime persistent\|session\|any] [--purpose standard\|template_draft\|template_test\|any] [--include-deleted] [--all] [--limit N] [--cursor C]` | One page, or every page with `--all`. The next cursor is printed on stderr. By default only persistent standard workspaces are listed. A file-first workspace's state reads `running (file-first)` (0.5.0+). |
710
713
  | `workspaces get <id\|key>` | One workspace (deleted ones included), with the startup of its template's start commands and services (0.4.0+): a failed step shows its exit code, reason and output tail. Its mode, and a file-first workspace's tree revision (0.5.0+). Its memory (0.8.0+): `fixed 4096 MiB`, or `elastic: promised …, held …, plugged +… MiB`. The template version whose [immutable paths](#immutable-paths-080) it has mounted (0.8.0+): `immutable paths v3 (mounted read-only)`. |
@@ -1620,6 +1623,17 @@ Conversation JSON includes `join_mode` and the worktree used by reviewers and wo
1620
1623
 
1621
1624
  `shard conversations resume <id>` continues a saved conversation in a fresh VM under the same identity (0.14.0+). `shard ./ --resume` uses this same path. `shard conversations reader <project-id>` opens a small read-only workspace for saved branches and transcripts; the Mac transcript mirror opens it automatically when no project VM is live. Adaptive idle reclaims the reader.
1622
1625
 
1626
+ `shard conversations delete <id> --yes` (0.25.0+) deletes a conversation everywhere at once: its agent stops, its
1627
+ computer is deleted (a computer other conversations share stays for them), and its messages, transcript and branch go
1628
+ with it, on every device. From then on it leaves every list and reads not found; `shard c feed` shows it once as
1629
+ `deleted`. The person who started it and organization owners and admins delete conversations. `--json` prints the
1630
+ API's answer: `{id, deleted, deleted_at, operation}`, where `operation` is the deletion of its computer.
1631
+
1632
+ ```sh
1633
+ shard c delete conv_k3m5q7s2u4w6y3b5d7f2h4j6ln --yes
1634
+ # Deleted: conv_k3m5q7s2u4w6y3b5d7f2h4j6ln
1635
+ ```
1636
+
1623
1637
  ### Route input and ask about your work (0.25.0+)
1624
1638
 
1625
1639
  `shard route` reads a line of input the way the board's input bar does: which project it belongs to (or your home),
@@ -1646,6 +1660,24 @@ prints the API's object: the route (`kind`, `parts`, `task_ref`, `uncertain`,
1646
1660
  `needs_work`, `sources`, …). An answer that needs work ends with "Make it a task?". Neither starts work or wakes a
1647
1661
  computer.
1648
1662
 
1663
+ ### Transcribe a recording (0.25.0+)
1664
+
1665
+ `shard transcribe <file>` prints what a recording says. `--words` adds each word with its start and end; `--json`
1666
+ prints the transcript (`text`, `words` with `w`, `start` and `end` in seconds, `language`, `duration`).
1667
+
1668
+ ```sh
1669
+ shard transcribe note.webm
1670
+ # Take this sidebar, and move it over here, then make the header a bit taller, and change that color to blue.
1671
+ shard transcribe hello.wav --words
1672
+ # Move this button over here, then make that panel a little wider.
1673
+ # 0:00.00 0:00.18 Move
1674
+ # 0:00.18 0:00.44 this
1675
+ # …
1676
+ ```
1677
+
1678
+ WebM/Opus and PCM WAV are read from the file; raw 16-bit mono PCM takes `--format pcm16 --sample-rate 16000`.
1679
+ `--language en` names the language when you know it. Up to 120 s of audio. Signed in, or with `SHARDFLUX_API_KEY`.
1680
+
1649
1681
  ### Permission requests (0.25.0+)
1650
1682
 
1651
1683
  When a conversation's agent asks before it runs a tool (Claude Code, Codex or an ACP agent), the conversation is
@@ -1731,11 +1763,15 @@ changes the words (`--text`, and the task's master hears it), the title (`--titl
1731
1763
  (`--clear`; `--no-clear` brings it back). `shard task steer <id> <words...>` sends steering words to the task's master,
1732
1764
  `shard task rm <id>` deletes the task, and `shard task clear-done` clears every finished one. `shard task show <id>`
1733
1765
  prints the task, its plan and each report: milestone, outcome and evidence (`tests 12/12 (pnpm test)`).
1766
+ `shard task land <id>` lands a finished task: its result (the branch `shard/<task id>`, where its master merged its
1767
+ workers' branches) goes into the project's default branch and into your folder on this computer the way finished
1768
+ conversations land there (your uncommitted edits stay), and the board shows it shipped once the commit is in the
1769
+ branch: `Landed in ~/code/best-todo · 3f2a1c9`.
1734
1770
 
1735
1771
  **Needs you.** `shard needs-you` lists permission requests, questions, credential requests, finished work to land and
1736
1772
  Later tasks to keep, oldest first. `shard needs-you answer <id>` answers any of them: `allow`, `always` (also adds the
1737
1773
  always-allow rule for the project; `--everywhere` for every project) or `deny` for a permission request, `--pick` for an
1738
- agent's question in one, `--text` for a question, `land`, `keep` or `discard` for finished work, and `--keep <task-id>`
1774
+ agent's question in one, `--text` for a question, `land` (as `shard task land`), `keep` or `discard` for finished work, and `--keep <task-id>`
1739
1775
  (or `keep`, `discard`) for Later tasks. The agent goes on as soon as you answer.
1740
1776
 
1741
1777
  **Approvals.** The approval mode answers your agents' permission requests before they reach you: `ask` sends every one
@@ -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; });
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.25.0";
2
2
  export interface OptionSpec {
3
3
  type: 'string' | 'boolean';
4
4
  short?: string;
package/dist/args.js CHANGED
@@ -14,7 +14,7 @@ import { ACCOUNT_COMMANDS } from "./account-args.js";
14
14
  import { BOARD_COMMANDS } from "./board-args.js";
15
15
  import { UsageError } from "./errors.js";
16
16
  import { DEFAULT_CHOICE, MODEL_CHOICES } from "./agent-models.js";
17
- export const CLI_VERSION = '0.24.1';
17
+ export const CLI_VERSION = '0.25.0';
18
18
  export const GLOBAL_OPTIONS = {
19
19
  'api-url': { type: 'string', value: '<url>', description: 'API base URL (default $SHARDFLUX_API_URL, else https://api.shardflux.dev).' },
20
20
  json: { type: 'boolean', description: 'Print machine-readable JSON on stdout (errors as JSON on stderr).' },
@@ -156,7 +156,7 @@ const KEY_COMMANDS = [
156
156
  { path: ['conversations', 'feed'], summary: 'Print project conversations as they change', positionals: [], options: {
157
157
  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
158
  },
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.',
159
+ 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
160
  examples: ['shard c feed', 'shard c feed --org acme --json'] },
161
161
  { path: ['conversations', 'resume'], summary: 'Resume a saved conversation in a fresh VM', positionals: [{ name: 'id', required: true }], options: {} },
162
162
  { path: ['conversations', 'reader'], summary: 'Open a read-only project space reader', positionals: [{ name: 'project_id', required: true }], options: {} },
@@ -194,8 +194,18 @@ const KEY_COMMANDS = [
194
194
  { 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
195
  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
196
  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.' } } },
197
+ { 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: {
198
+ into: { type: 'string', value: '<ref>', description: 'pr: the PR URL; keep: the branch (merge always lands in the project’s default branch).' },
199
+ path: { type: 'string', value: '<folder>', description: 'merge: the folder to land in (default: where the conversation started on this computer).' }
200
+ },
201
+ 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.',
202
+ examples: ['shard conversations land conv_k3m5q7s2u4w6y3b5d7f2h4j6ln merge', 'shard conversations land conv_k3m5q7s2u4w6y3b5d7f2h4j6ln discard'] },
198
203
  { path: ['conversations', 'move'], summary: 'Move at the end of the turn', positionals: [{ name: 'id', required: true }, { name: 'to', required: true }], options: {} },
204
+ { path: ['conversations', 'delete'], summary: 'Delete a conversation everywhere: its computer, messages, transcript and branch (0.25.0+)', positionals: [{ name: 'id', required: true }], options: {
205
+ yes: { type: 'boolean', description: 'Required: confirm the deletion.' }
206
+ },
207
+ 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.',
208
+ examples: ['shard c delete conv_k3m5q7s2u4w6y3b5d7f2h4j6ln --yes', 'shard conversations delete conv_k3m5q7s2u4w6y3b5d7f2h4j6ln --yes --json'] },
199
209
  { path: ['conversations', 'landing'], summary: 'Show or set how finished conversations reach this checkout', positionals: [{ name: 'mode' }, { name: 'backup' }], options: {
200
210
  path: { type: 'string', value: '<path>', description: 'Local project checkout (default the current directory).' }
201
211
  },
@@ -1461,6 +1471,24 @@ const ROUTER_COMMANDS = [
1461
1471
  },
1462
1472
  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
1473
  },
1474
+ {
1475
+ path: ['transcribe'],
1476
+ summary: 'Turn a recording into text, with each word’s start and end (0.25.0+)',
1477
+ description: [
1478
+ '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.',
1479
+ 'Uses your signed-in session, or SHARDFLUX_API_KEY when it is set. The audio is not kept.',
1480
+ '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).',
1481
+ ].join('\n\n'),
1482
+ positionals: [{ name: 'file', required: true }],
1483
+ options: {
1484
+ format: { type: 'string', value: '<webm|wav|pcm16>', description: 'The audio format (default: from the file).' },
1485
+ 'sample-rate': { type: 'string', value: '<hz>', description: 'Samples per second of raw pcm16 audio (8000 to 48000).' },
1486
+ language: { type: 'string', value: '<code>', description: 'ISO 639-1 code of the language spoken (default: detected).' },
1487
+ words: { type: 'boolean', description: 'Print each word with its start and end.' },
1488
+ 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).' },
1489
+ },
1490
+ examples: ['shard transcribe note.webm', 'shard transcribe meeting.wav --words', 'shard transcribe mic.pcm --format pcm16 --sample-rate 16000 --json'],
1491
+ },
1464
1492
  ];
1465
1493
  /** The project a drive or models command acts in (0.25.0+). */
1466
1494
  const PROJECT_SCOPE = {
@@ -1927,7 +1955,7 @@ export const EXIT_CODE_HELP = [
1927
1955
  /** The top help's sections: every command's group (first word) is in exactly one (args.test.ts checks). */
1928
1956
  export const HELP_SECTIONS = [
1929
1957
  { 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'] },
1958
+ { title: 'Cloud agents (shard ./ starts one; a project API key)', groups: ['conversations', 'board', 'task', 'needs-you', 'approvals', 'route', 'ask', 'transcribe', 'drive', 'models', 'move', 'machine', 'template', 'agents', 'settings', 'accounts', 'device', 'cli-proxy', 'ssh-proxy'] },
1931
1959
  { 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
1960
  { 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
1961
  { 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+)',
@@ -117,7 +133,8 @@ export const BOARD_COMMANDS = [
117
133
  ' cq_… --text <answer>: the agent gets it as its next message',
118
134
  ' ask_… --text <answer>: the task’s master gets it',
119
135
  ' 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',
136
+ ' land_… land (as shard task land: the result into your folder and the default branch), keep (the',
137
+ ' branch stays) or discard',
121
138
  ' prune_… --keep <task-id> for each task to keep, keep for all of them, or discard to clear them',
122
139
  'The agent goes on as soon as you answer.',
123
140
  ].join('\n'),
@@ -10,8 +10,8 @@ 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
17
  /** One item that waits on you: its id, what is asked, then the command, the suggested answers or the branch behind it. */
@@ -3,6 +3,7 @@ import { detailLine, pickAnswers } from "./conversation-commands.js";
3
3
  import { NotFoundError, UsageError } from "./errors.js";
4
4
  import { time } from "./format.js";
5
5
  import { progress } from "./progress.js";
6
+ import { landDeps, landTask, landedText } from "./land.js";
6
7
  const BUCKETS = ['now', 'next', 'later'];
7
8
  const BUCKET_TITLES = { now: 'Now', next: 'Next', later: 'Later' };
8
9
  const MODES = ['ask', 'routine', 'trusted'];
@@ -16,12 +17,17 @@ const oneLine = (s, max) => {
16
17
  export function taskLabel(t, max = 80) {
17
18
  return oneLine(t.title?.trim() || t.text, max);
18
19
  }
20
+ /** Why a task waits to start, or what a working one does now: "Getting its computer ready", "Fixing the login cookie". */
21
+ function stateNote(t, max) {
22
+ const note = t.waiting?.message.trim() || (t.state === 'working' ? t.activity?.trim() : '') || '';
23
+ return note ? ` (${oneLine(note, max)})` : '';
24
+ }
19
25
  /**
20
- * One task as `shard board` lists it: id, state (with why it waits to start: "queued (Claude usage resets 15:00)"),
21
- * title or words (a follow-up ↳, a suggestion marked), project.
26
+ * One task as `shard board` lists it: id, state (with why it waits to start, "queued (Getting its computer ready)", or
27
+ * what it does now, "working (Fixing the login cookie)"), title or words (a follow-up ↳, a suggestion marked), project.
22
28
  */
23
29
  export function taskLine(t) {
24
- const state = `${t.state}${t.waiting?.message.trim() ? ` (${oneLine(t.waiting.message, 60)})` : ''}`;
30
+ const state = `${t.state}${stateNote(t, 60)}`;
25
31
  const label = `${t.parent_task_id ? '↳ ' : ''}${taskLabel(t)}${t.suggested ? ' (suggested)' : ''}`;
26
32
  return `${t.id}\t${state}\t${label}\t${t.project_id}${t.home ? ' (home)' : ''}`;
27
33
  }
@@ -111,14 +117,14 @@ export function taskDetailText(d) {
111
117
  const t = d.task;
112
118
  const titled = Boolean(t.title?.trim());
113
119
  const lines = [titled ? oneLine(t.title, 200) : oneLine(t.text.split('\n').find((l) => l.trim()) ?? t.text, 200)];
114
- lines.push([`${t.state}${t.waiting?.message.trim() ? ` (${oneLine(t.waiting.message, 120)})` : ''}`, t.bucket, `${t.project_id}${t.home ? ' (home)' : ''}`, ...(t.suggested ? ['suggested'] : []), ...(t.deleted_at ? ['deleted'] : t.cleared_at ? ['cleared'] : [])].join('\t'));
120
+ lines.push([`${t.state}${stateNote(t, 120)}`, t.bucket, `${t.project_id}${t.home ? ' (home)' : ''}`, ...(t.suggested ? ['suggested'] : []), ...(t.deleted_at ? ['deleted'] : t.cleared_at ? ['cleared'] : [])].join('\t'));
115
121
  // Your words exactly, when the first line does not already show them whole.
116
122
  if (titled || t.text.trim() !== lines[0])
117
123
  lines.push('', t.text);
118
124
  if (t.plan) {
119
125
  lines.push('', `Plan: ${t.plan.summary}`);
120
126
  for (const s of t.plan.steps)
121
- lines.push(` ${s.id}\t${s.state}\t${oneLine(s.text, 100)}${s.evidence.length ? `\t${s.evidence.join(', ')}` : ''}`);
127
+ lines.push(` ${s.id}\t${s.state}\t${oneLine(s.text, 100)}${s.now?.trim() ? `\t(${oneLine(s.now, 80)})` : ''}${s.evidence.length ? `\t${s.evidence.join(', ')}` : ''}`);
122
128
  }
123
129
  if (t.conversations.length)
124
130
  lines.push('', `Conversations: ${t.conversations.map((c) => `${c.id} (${c.role}${c.step_id ? ` ${c.step_id}` : ''})`).join(', ')}`);
@@ -332,6 +338,11 @@ export const BOARD_HANDLERS = {
332
338
  out(ctx, `Steered: ${taskLabel(task)}\n`, { task });
333
339
  return 0;
334
340
  },
341
+ async 'task land'(ctx, values, [id]) {
342
+ const o = await landTask(landDeps(ctx), id, { path: str(values.path) });
343
+ out(ctx, landedText(o), o);
344
+ return 0;
345
+ },
335
346
  async 'task clear-done'(ctx, values) {
336
347
  const cleared = await boardClient(ctx).tasks.clearDone(projectScope(values), { signal: ctx.signal });
337
348
  out(ctx, cleared.length ? `Cleared ${cleared.length} finished task${cleared.length === 1 ? '' : 's'}.\n` : 'No finished tasks to clear.\n', { cleared });
@@ -345,6 +356,12 @@ export const BOARD_HANDLERS = {
345
356
  async 'needs-you answer'(ctx, values, [id, word]) {
346
357
  const input = { word, text: str(values.text), picks: strs(values.pick), keep: strs(values.keep), everywhere: values.everywhere === true };
347
358
  const cloud = boardClient(ctx);
359
+ // Land merges here (shard task land) and records itself after; the board only takes keep and discard.
360
+ if (itemKind(id) === 'land' && word?.toLowerCase() === 'land') {
361
+ const o = await landTask(landDeps(ctx), id.replace(/^land_/, ''), {});
362
+ out(ctx, landedText(o), o);
363
+ return 0;
364
+ }
348
365
  // Checked first, so a wrong combination is a usage error before any request.
349
366
  let answer = needsAnswer(id, input);
350
367
  if (answerNeedsItem(id, input)) {
@@ -9,6 +9,7 @@ import { modelChoice } from "./agent-models.js";
9
9
  import { storedTitlePrefix } from "./client-settings.js";
10
10
  import { PtyOutputCursor, passiveUnavailable, sleepingAttach } from "./agent-terminal.js";
11
11
  import { progress } from "./progress.js";
12
+ import { landConversation, landDeps, landedText } from "./land.js";
12
13
  import { UUID } from "./args.js";
13
14
  import { findOrg } from "./account-scope.js";
14
15
  export function conversationClient(ctx) {
@@ -224,8 +225,9 @@ export const CONVERSATION_HANDLERS = {
224
225
  const { client, organizationId } = await listScope(ctx, values);
225
226
  let cursor = str(values.cursor);
226
227
  for await (const c of client.conversations.watch({ projectId: str(values.project), organizationId, cursor, signal: ctx.signal, onCursor: next => { cursor = next; } })) {
227
- const q = questionLine(c);
228
- ctx.io.stdout.write(ctx.json ? JSON.stringify({ conversation: c, cursor }) + '\n' : `${c.id}\t${displayTitle(c)}\t${c.state}${q ? `\t${q}` : ''}\n`);
228
+ // A deleted conversation comes once, its content cleared: its line says deleted (0.25.0+).
229
+ const q = questionLine(c), line = c.deleted_at ? `${c.id}\tDeleted conversation\tdeleted` : `${c.id}\t${displayTitle(c)}\t${c.state}${q ? `\t${q}` : ''}`;
230
+ ctx.io.stdout.write(ctx.json ? JSON.stringify({ conversation: c, cursor }) + '\n' : `${line}\n`);
229
231
  }
230
232
  return 0;
231
233
  },
@@ -360,6 +362,12 @@ export const CONVERSATION_HANDLERS = {
360
362
  async 'conversations land'(ctx, values, [id, action]) {
361
363
  if (!['merge', 'pr', 'keep', 'discard'].includes(action))
362
364
  throw new UsageError('Choose merge, pr, keep or discard.');
365
+ // merge lands for real (land.ts) and records only after the commit is in the branch; the others record a decision.
366
+ if (action === 'merge') {
367
+ const o = await landConversation(landDeps(ctx), id, { path: str(values.path) });
368
+ out(ctx, landedText(o), o);
369
+ return 0;
370
+ }
363
371
  const r = await conversationClient(ctx).conversations.land(id, action, { into: str(values.into), signal: ctx.signal });
364
372
  out(ctx, `${displayTitle(r)}: ${r.landed.how}\n`, r);
365
373
  return 0;
@@ -370,4 +378,12 @@ export const CONVERSATION_HANDLERS = {
370
378
  return 0;
371
379
  },
372
380
  async 'conversations move'(ctx, _values, [id, to]) { const r = await conversationClient(ctx).conversations.move(id, to, { signal: ctx.signal }); out(ctx, `Move scheduled for the end of the turn.\n`, r); return 0; },
381
+ // Everywhere at once, its computer included (0.25.0+); refused without --yes, as every deletion is. --json: the API's answer.
382
+ async 'conversations delete'(ctx, values, [id]) {
383
+ if (values.yes !== true)
384
+ throw new UsageError('refusing to delete without --yes (the conversation, its computer, messages, transcript and branch are deleted everywhere)');
385
+ const r = await conversationClient(ctx).conversations.delete(id, { signal: ctx.signal });
386
+ out(ctx, `Deleted: ${r.id}\n`, r);
387
+ return 0;
388
+ },
373
389
  };
@@ -17,7 +17,8 @@ export interface Conversation {
17
17
  worktree_path: string;
18
18
  branch: string;
19
19
  base_commit: string;
20
- state: 'working' | 'needs_you' | 'unresolved' | 'done' | 'archived';
20
+ /** `deleted`: the one feed delivery of a deleted conversation (0.25.0+). */
21
+ state: 'working' | 'needs_you' | 'unresolved' | 'done' | 'archived' | 'deleted';
21
22
  created_at: string;
22
23
  ended_at: string | null;
23
24
  landed?: {
@@ -42,6 +42,14 @@ type Starter = (ctx: Ctx, folder: string, harness: 'claude' | 'codex', brief: st
42
42
  * prompt. The brief goes in on standard input, never in a command line another user can read.
43
43
  */
44
44
  export declare const startFromFolder: Starter;
45
+ /**
46
+ * The conversation `shard agents new --json` started, from its whole output: one pretty-printed JSON document
47
+ * (format.ts json), never its last line (staging 2026-10-16: the last line is `}`).
48
+ */
49
+ export declare function startedConversation(stdout: string): {
50
+ id: string;
51
+ project_id: string;
52
+ };
45
53
  /**
46
54
  * `folder` becomes one of your projects: the repository its Git origin names (else the folder itself), in your
47
55
  * project on this API. It is selected for background preparation, so its files are in the cloud before the next chat,
@@ -148,10 +148,11 @@ export const startFromFolder = (ctx, folder, harness, brief) => {
148
148
  env: { ...process.env, ...ctx.io.env, SHARDFLUX_CONFIG_DIR: ctx.store.dir, SHARDFLUX_API_URL: ctx.apiUrl, CI: '1' },
149
149
  });
150
150
  const out = [], err = [];
151
+ let outBytes = 0;
151
152
  const stop = () => child.kill('SIGTERM');
152
153
  const timer = setTimeout(stop, 300_000);
153
154
  ctx.signal.addEventListener('abort', stop, { once: true });
154
- child.stdout.on('data', (b) => { if (out.length < 256)
155
+ child.stdout.on('data', (b) => { if ((outBytes += b.length) <= 8 << 20)
155
156
  out.push(b); });
156
157
  child.stderr.on('data', (b) => { if (err.length < 64)
157
158
  err.push(b); });
@@ -163,11 +164,7 @@ export const startFromFolder = (ctx, folder, harness, brief) => {
163
164
  try {
164
165
  if (code !== 0)
165
166
  throw new Error(lastLine(Buffer.concat(err).toString('utf8')) || `The conversation did not start (exit ${code}).`);
166
- const started = JSON.parse(text.trim().split('\n').at(-1));
167
- const id = started.conversation?.id ?? started.workspace?.labels?.shard_conversation;
168
- if (!id)
169
- throw new Error('The conversation did not start.');
170
- resolveStart({ id, project_id: started.conversation?.project_id ?? started.workspace?.project_id ?? '' });
167
+ resolveStart(startedConversation(text));
171
168
  }
172
169
  catch (error) {
173
170
  reject(error instanceof Error ? error : new Error(String(error)));
@@ -176,6 +173,17 @@ export const startFromFolder = (ctx, folder, harness, brief) => {
176
173
  child.stdin.end(brief);
177
174
  });
178
175
  };
176
+ /**
177
+ * The conversation `shard agents new --json` started, from its whole output: one pretty-printed JSON document
178
+ * (format.ts json), never its last line (staging 2026-10-16: the last line is `}`).
179
+ */
180
+ export function startedConversation(stdout) {
181
+ const started = JSON.parse(stdout);
182
+ const id = started.conversation?.id ?? started.workspace?.labels?.shard_conversation;
183
+ if (!id)
184
+ throw new Error('The conversation did not start.');
185
+ return { id, project_id: started.conversation?.project_id ?? started.workspace?.project_id ?? '' };
186
+ }
179
187
  function lastLine(stderr) {
180
188
  const text = stderr.trim();
181
189
  try {
@@ -150,7 +150,18 @@ export async function runDeviceHelper(ctx, options = {}) {
150
150
  // Registered for every project an agent may ask from (deviceProjects); one poll covers them all where the API
151
151
  // lists by device (0.25.0+), else one per project.
152
152
  const registered = new Map();
153
- let all, deviceWide = true;
153
+ let all, deviceWide = true, links = true;
154
+ // This device's local conversations of a project (started here or opened here): their requests come here first.
155
+ const linked = (projectId) => Object.values(localConversations(ctx)).filter(r => !r.handed_off && (!projectId || r.project_id === projectId))
156
+ .sort((a, b) => (b.conversation.created_at ?? '').localeCompare(a.conversation.created_at ?? '')).slice(0, 500).map(r => r.conversation.id);
157
+ const beat = (projectId) => conversationClient(ctx).deviceRequests.heartbeat({ ...(projectId ? { projectId } : {}), deviceId, leaseSeconds: 30, ...(links ? { conversationIds: linked(projectId) } : {}) })
158
+ .catch((e) => {
159
+ // An API before device links (0.25.0+ clients) refuses the list: register without it from then on.
160
+ if (!links || !(e instanceof ShardfluxApiError && [400, 422].includes(e.status)))
161
+ throw e;
162
+ links = false;
163
+ return conversationClient(ctx).deviceRequests.heartbeat({ ...(projectId ? { projectId } : {}), deviceId, leaseSeconds: 30 });
164
+ });
154
165
  const take = (rows) => {
155
166
  for (const r of rows) {
156
167
  if (r.device_id !== deviceId || working.has(r.id) || working.size >= 8)
@@ -167,7 +178,7 @@ export async function runDeviceHelper(ctx, options = {}) {
167
178
  for (const projectId of projects) {
168
179
  if (Date.now() - (registered.get(projectId) ?? 0) < 10_000)
169
180
  continue;
170
- await conversationClient(ctx).deviceRequests.heartbeat({ ...(projectId ? { projectId } : {}), deviceId, leaseSeconds: 30 }).then(() => registered.set(projectId, Date.now()), (e) => {
181
+ await beat(projectId).then(() => registered.set(projectId, Date.now()), (e) => {
171
182
  // A project this sign-in no longer reaches is skipped until the next listing, never the others.
172
183
  if (!(e instanceof ShardfluxApiError && [403, 404].includes(e.status)))
173
184
  throw e;
package/dist/errors.js CHANGED
@@ -266,7 +266,8 @@ export function describeError(err) {
266
266
  // details.reason: the operation error's reason (e.g. the scheduler's no_ready_hosts), printed after the message.
267
267
  const opDetails = err.operation.error?.details;
268
268
  const reason = typeof opDetails === 'object' && opDetails !== null ? opDetails.reason : undefined;
269
- const details = { ...(err.errorCode ? { error_code: err.errorCode } : {}), ...(typeof reason === 'string' ? { reason } : {}) };
269
+ const retryAfter = typeof opDetails === 'object' && opDetails !== null ? opDetails.retry_after_seconds : undefined;
270
+ const details = { ...(err.errorCode ? { error_code: err.errorCode } : {}), ...(typeof reason === 'string' ? { reason } : {}), ...(typeof retryAfter === 'number' && Number.isFinite(retryAfter) && retryAfter > 0 ? { retry_after_seconds: retryAfter } : {}) };
270
271
  // A suspend-when-idle canceled workspace_active (0.7.0+): the workspace was in use and keeps running.
271
272
  const message = err.workspaceActive ? `${err.message}: the workspace was in use, so the suspend-when-idle was canceled; it keeps running` : err.message;
272
273
  return { code: 'operation_failed', message, operation_id: err.operationId, retryable: err.retryable, ...(Object.keys(details).length ? { details } : {}), operation: err.operation };
package/dist/format.js CHANGED
@@ -448,7 +448,7 @@ const hoursText = (h) => String(Math.round(h * 100) / 100);
448
448
  * while the GPU is stopped (cap_state gpu_blocked), why and what brings it back. Workspaces keep running meanwhile.
449
449
  * Nothing without `gpu` (plans without GPU-hours).
450
450
  */
451
- function gpuLines(gpu, resetsAt) {
451
+ function gpuLines(gpu, resetsAt, overageAvailable) {
452
452
  if (!isObject(gpu) || typeof gpu.used_hours !== 'number')
453
453
  return [];
454
454
  const included = typeof gpu.included_hours === 'number' ? gpu.included_hours : null;
@@ -470,6 +470,10 @@ function gpuLines(gpu, resetsAt) {
470
470
  rows.push(['GPU', `stopped (spend_cap_reached): workspaces keep running; raise the spend cap (shard billing overage cap <dollars>), or wait for the reset${resets}`]);
471
471
  break;
472
472
  default:
473
+ if (!overageAvailable) {
474
+ rows.push(['GPU', 'stopped (allowance_used): workspaces keep running; choose a paid plan (shard billing plans); overage is unavailable on the current plan']);
475
+ break;
476
+ }
473
477
  rows.push(['GPU', `stopped (allowance_used): workspaces keep running; turn on overage (shard billing overage on --cap <dollars>), upgrade (shard billing plans), or wait for the reset${resets}`]);
474
478
  }
475
479
  return rows;
@@ -507,7 +511,7 @@ export function usageText(summary) {
507
511
  head.push(['retained state', 'used up: new workspaces and forks are refused (delete workspaces you no longer need, or upgrade)']);
508
512
  }
509
513
  const resetsAt = isObject(s.period) ? [s.period.resets_at, s.period.end].find((t) => typeof t === 'string') : undefined;
510
- head.push(...gpuLines(s.gpu, resetsAt));
514
+ head.push(...gpuLines(s.gpu, resetsAt, !(isObject(s.spend_cap) && s.spend_cap.available === false)));
511
515
  for (const [k, v] of Object.entries(s))
512
516
  if (!USAGE_KNOWN.has(k) && (typeof v !== 'object' || v === null))
513
517
  head.push([k.replace(/_/g, ' '), scalar(v)]);
package/dist/handlers.js CHANGED
@@ -5,6 +5,7 @@ import { CONVERSATION_HANDLERS } from "./conversation-commands.js";
5
5
  import { BOARD_HANDLERS } from "./board-commands.js";
6
6
  import { TRANSCRIPT_HANDLERS } from "./conversation-transcript.js";
7
7
  import { ROUTER_HANDLERS } from "./router-commands.js";
8
+ import { TRANSCRIBE_HANDLERS } from "./transcribe-commands.js";
8
9
  /**
9
10
  * Every command's handler, keyed by its path ("workspaces open", "auth login"): the project-key commands of
10
11
  * commands.ts and the account plane of account-commands.ts (0.5.0). args.test.ts checks that it matches COMMANDS.
@@ -31,4 +32,4 @@ import { MACHINE_SETUP_HANDLERS } from "./machine-setup.js";
31
32
  import { SETTINGS_HANDLERS } from "./client-settings.js";
32
33
  import { PROJECT_PREPARATION_HANDLERS } from "./project-preparation.js";
33
34
  import { DRIVE_HANDLERS } from "./drive-commands.js";
34
- export const HANDLERS = { ...PROJECT_PREPARATION_HANDLERS, ...PROJECT_LINK_HANDLERS, ...MOVE_HANDLERS, ...DEVICE_HANDLERS, ...CODEX_CLOUD_HANDLERS, ...CLI_PROXY_HANDLERS, ...THREAD_HANDLERS, ...CONVERSATION_HANDLERS, ...BOARD_HANDLERS, ...TRANSCRIPT_HANDLERS, ...LOCAL_CONVERSATION_HANDLERS, ...TWIN_HANDLERS, ...CREDENTIAL_HANDLERS, ...AGENT_CREDENTIAL_HANDLERS, ...ACCOUNT_POOL_HANDLERS, ...KEY_HANDLERS, ...ACCOUNT_HANDLERS, ...AGENT_HANDLERS, ...CENTER_HANDLERS, ...TARGET_HANDLERS, ...AGENT_GIT_HANDLERS, ...MENUBAR_HANDLERS, ...FILESYSTEM_HANDLERS, ...MACHINE_SETUP_HANDLERS, ...SETTINGS_HANDLERS, ...UPDATE_HANDLERS, ...AGENT_CONFIG_HANDLERS, ...ROUTER_HANDLERS, ...DRIVE_HANDLERS };
35
+ export const HANDLERS = { ...PROJECT_PREPARATION_HANDLERS, ...PROJECT_LINK_HANDLERS, ...MOVE_HANDLERS, ...DEVICE_HANDLERS, ...CODEX_CLOUD_HANDLERS, ...CLI_PROXY_HANDLERS, ...THREAD_HANDLERS, ...CONVERSATION_HANDLERS, ...BOARD_HANDLERS, ...TRANSCRIPT_HANDLERS, ...LOCAL_CONVERSATION_HANDLERS, ...TWIN_HANDLERS, ...CREDENTIAL_HANDLERS, ...AGENT_CREDENTIAL_HANDLERS, ...ACCOUNT_POOL_HANDLERS, ...KEY_HANDLERS, ...ACCOUNT_HANDLERS, ...AGENT_HANDLERS, ...CENTER_HANDLERS, ...TARGET_HANDLERS, ...AGENT_GIT_HANDLERS, ...MENUBAR_HANDLERS, ...FILESYSTEM_HANDLERS, ...MACHINE_SETUP_HANDLERS, ...SETTINGS_HANDLERS, ...UPDATE_HANDLERS, ...AGENT_CONFIG_HANDLERS, ...ROUTER_HANDLERS, ...TRANSCRIBE_HANDLERS, ...DRIVE_HANDLERS };