@shardflux/cli 0.25.0 → 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 (69) hide show
  1. package/CHANGELOG.md +130 -1
  2. package/README.md +145 -34
  3. package/dist/account-pool.d.ts +11 -1
  4. package/dist/account-pool.js +48 -0
  5. package/dist/agent-commands.js +4 -3
  6. package/dist/agent-models.d.ts +31 -1
  7. package/dist/agent-models.js +27 -4
  8. package/dist/args.d.ts +1 -1
  9. package/dist/args.js +67 -12
  10. package/dist/board-args.js +8 -3
  11. package/dist/board-commands.d.ts +7 -3
  12. package/dist/board-commands.js +22 -12
  13. package/dist/brain-commands.d.ts +2 -0
  14. package/dist/brain-commands.js +78 -0
  15. package/dist/client-settings.d.ts +2 -1
  16. package/dist/client-settings.js +14 -2
  17. package/dist/codex-cloud.d.ts +2 -0
  18. package/dist/codex-cloud.js +14 -11
  19. package/dist/commands.js +8 -0
  20. package/dist/conversation-commands.d.ts +5 -1
  21. package/dist/conversation-commands.js +58 -6
  22. package/dist/conversation-terminal.js +2 -2
  23. package/dist/desktop-linux.d.ts +12 -1
  24. package/dist/desktop-linux.js +47 -18
  25. package/dist/device-approvals.js +24 -2
  26. package/dist/device-bridge.d.ts +21 -4
  27. package/dist/device-bridge.js +173 -7
  28. package/dist/device-folders.d.ts +6 -1
  29. package/dist/device-folders.js +6 -2
  30. package/dist/device-helper.d.ts +8 -0
  31. package/dist/device-helper.js +71 -12
  32. package/dist/device-mode.d.ts +44 -0
  33. package/dist/device-mode.js +119 -0
  34. package/dist/drive-commands.d.ts +2 -2
  35. package/dist/drive-commands.js +1 -1
  36. package/dist/handlers.js +4 -2
  37. package/dist/land.d.ts +9 -4
  38. package/dist/land.js +52 -6
  39. package/dist/mac-app.d.ts +11 -0
  40. package/dist/mac-app.js +11 -0
  41. package/dist/main.js +2 -0
  42. package/dist/menubar.d.ts +18 -13
  43. package/dist/menubar.js +60 -50
  44. package/dist/orchestrator-args.d.ts +7 -0
  45. package/dist/orchestrator-args.js +64 -0
  46. package/dist/orchestrator-commands.d.ts +43 -0
  47. package/dist/orchestrator-commands.js +262 -0
  48. package/dist/person-scope.d.ts +20 -0
  49. package/dist/person-scope.js +61 -0
  50. package/dist/project-hub-seed.d.ts +20 -0
  51. package/dist/project-hub-seed.js +59 -0
  52. package/dist/registry.d.ts +1 -1
  53. package/dist/registry.js +2 -1
  54. package/dist/router-commands.d.ts +12 -21
  55. package/dist/router-commands.js +5 -59
  56. package/dist/t3-launch.js +2 -2
  57. package/dist/task-land-hub.py +16 -1
  58. package/dist/this-computer.d.ts +4 -2
  59. package/dist/this-computer.js +20 -2
  60. package/dist/thread-on-computer.js +2 -2
  61. package/dist/timing.js +3 -1
  62. package/dist/transcribe-commands.js +6 -2
  63. package/dist/update.js +4 -2
  64. package/dist/wait-status.d.ts +1 -1
  65. package/dist/wait-status.js +2 -0
  66. package/dist/worktree-twin-engine.js +1 -1
  67. package/dist/worktree-twin.d.ts +6 -0
  68. package/dist/worktree-twin.js +8 -1
  69. package/package.json +6 -5
@@ -4,8 +4,10 @@ export declare const yourComputer: (platform?: NodeJS.Platform) => string;
4
4
  export declare const LINUX_DEFAULT_SHORTCUT = "<Alt><Shift>space";
5
5
  /** A GTK accelerator ("<Control><Shift>space") as the Linux app shows it ("Ctrl+Shift+Space"); shortcutLabel in its core.mjs. */
6
6
  export declare function acceleratorLabel(accelerator: string): string;
7
+ /** An Electron accelerator ("Alt+Shift+Space", the one app's desktop-app.json) as the person reads it. */
8
+ export declare function electronAcceleratorLabel(accelerator: string, platform: NodeJS.Platform): string;
7
9
  /**
8
- * The launcher's shortcut as the person presses it: ⌥⇧Space on macOS (the menu bar's), the Linux app's configured one
9
- * on Linux, undefined elsewhere.
10
+ * The launcher's shortcut as the person presses it: the one app's (desktop-app.json in the config directory, 0.27.0+),
11
+ * else ⌥⇧Space on macOS (the menu bar app's) and the Linux app's configured one on Linux; undefined elsewhere.
10
12
  */
11
13
  export declare function launcherShortcut(configDir: string, platform?: NodeJS.Platform): string | undefined;
@@ -16,11 +16,29 @@ export function acceleratorLabel(accelerator) {
16
16
  const key = accelerator.replace(/<\w+>/g, '');
17
17
  return [...mods, key.length === 1 ? key.toUpperCase() : key.charAt(0).toUpperCase() + key.slice(1)].join('+');
18
18
  }
19
+ /** An Electron accelerator ("Alt+Shift+Space", the one app's desktop-app.json) as the person reads it. */
20
+ export function electronAcceleratorLabel(accelerator, platform) {
21
+ const parts = accelerator.split('+');
22
+ const key = parts.pop() ?? '';
23
+ if (platform === 'darwin') {
24
+ const symbols = { Control: '⌃', Ctrl: '⌃', Alt: '⌥', Option: '⌥', Shift: '⇧', Command: '⌘', Cmd: '⌘', CommandOrControl: '⌘', CmdOrCtrl: '⌘' };
25
+ const order = ['⌃', '⌥', '⇧', '⌘'];
26
+ return parts.map(p => symbols[p] ?? p).sort((a, b) => order.indexOf(a) - order.indexOf(b)).join('') + key;
27
+ }
28
+ const names = { Control: 'Ctrl', CommandOrControl: 'Ctrl', CmdOrCtrl: 'Ctrl', Command: 'Super', Cmd: 'Super', Super: 'Super', Meta: 'Super' };
29
+ return [...parts.map(p => names[p] ?? p), key].join('+');
30
+ }
19
31
  /**
20
- * The launcher's shortcut as the person presses it: ⌥⇧Space on macOS (the menu bar's), the Linux app's configured one
21
- * on Linux, undefined elsewhere.
32
+ * The launcher's shortcut as the person presses it: the one app's (desktop-app.json in the config directory, 0.27.0+),
33
+ * else ⌥⇧Space on macOS (the menu bar app's) and the Linux app's configured one on Linux; undefined elsewhere.
22
34
  */
23
35
  export function launcherShortcut(configDir, platform = process.platform) {
36
+ try {
37
+ const app = JSON.parse(readFileSync(join(configDir, 'desktop-app.json'), 'utf8'));
38
+ if (typeof app.launcher_shortcut === 'string' && /^([A-Za-z]+\+)+\S+$/.test(app.launcher_shortcut) && (platform === 'darwin' || platform === 'linux'))
39
+ return electronAcceleratorLabel(app.launcher_shortcut, platform);
40
+ }
41
+ catch { /* the app has not written it (an older app, or none) */ }
24
42
  if (platform === 'darwin')
25
43
  return '⌥⇧Space';
26
44
  if (platform !== 'linux')
@@ -19,7 +19,7 @@ import { cloudClaudeLogin, loginClaudeInWorkspace, requireClaudeTerminal } from
19
19
  import { promptWithImages, readInitialImages, readInitialPrompt, uploadInitialImages } from "./initial-prompt.js";
20
20
  import { recordSeedSnapshot } from "./files-mirror.js";
21
21
  import { ensureCodexCloud, printCloudThread, submitCloudInitialPrompt } from "./codex-cloud.js";
22
- import { modelChoice } from "./agent-models.js";
22
+ import { liveModels, modelChoice } from "./agent-models.js";
23
23
  import { storedTitlePrefix } from "./client-settings.js";
24
24
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
25
25
  const SHA = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
@@ -143,7 +143,7 @@ export async function startThreadOn(ctx, values, path, explicitApp) {
143
143
  const review = values.review === true;
144
144
  if (review && app === 'codex')
145
145
  throw new UsageError('--review starts a read-only Claude reviewer. A Codex thread on a computer works on its own branch; omit --review.');
146
- const choice = modelChoice(values, app);
146
+ const choice = modelChoice(values, app, await liveModels(ctx, values, app));
147
147
  const name = str(values.name);
148
148
  if (name !== undefined && !/^[a-z0-9][a-z0-9-]{0,47}$/.test(name))
149
149
  throw new UsageError('--name must match ^[a-z0-9][a-z0-9-]{0,47}$');
package/dist/timing.js CHANGED
@@ -53,13 +53,15 @@ export class TimingRecorder {
53
53
  return own && own.action !== 'token' ? own : this.last;
54
54
  }
55
55
  }
56
- /** A resume that booted the saved disk: files as of the suspend, every process restarted (never a null memory_restored). */
56
+ /** A resume that booted its disk: files kept, every process restarted (never a null memory_restored). */
57
57
  function restartedProcesses(t) {
58
58
  return t.server?.resumePath === 'cold_boot' && t.server.memoryRestored === false;
59
59
  }
60
60
  /** Cold boot reasons the notice says in words (0.8.1+); any other reason is printed as the API sent it. */
61
61
  const COLD_BOOT_WORDS = {
62
62
  host_lost: 'the machine it ran on failed',
63
+ guest_unresponsive: 'the guest did not become ready after bounded restore attempts',
64
+ guest_filesystem: 'the filesystem was repaired offline',
63
65
  };
64
66
  /**
65
67
  * The one-line stderr notice after a command whose resume booted the workspace's saved disk (0.6.0+), with or without
@@ -5,10 +5,10 @@
5
5
  */
6
6
  import { readFile } from 'node:fs/promises';
7
7
  import { extname } from 'node:path';
8
- import { audioFormatOf } from '@shardflux/sdk';
8
+ import { ShardfluxApiError, audioFormatOf } from '@shardflux/sdk';
9
9
  import { out, str } from "./commands.js";
10
10
  import { EXIT, UsageError } from "./errors.js";
11
- import { inScope } from "./router-commands.js";
11
+ import { inScope } from "./person-scope.js";
12
12
  /** What one transcription holds (the API's bound; past it the rest is not transcribed and `truncated` says so). */
13
13
  export const TRANSCRIBE_MAX_BYTES = 6 * 1024 * 1024;
14
14
  const FORMATS = ['webm', 'wav', 'pcm16'];
@@ -74,6 +74,10 @@ export const TRANSCRIBE_HANDLERS = {
74
74
  catch (err) {
75
75
  if (ctx.signal.aborted)
76
76
  return EXIT.interrupted;
77
+ if (err instanceof ShardfluxApiError && err.reason === 'cloud_transcription_off') {
78
+ ctx.io.stderr.write('Cloud transcription is not on for this organization (cloud_transcription_off).\n');
79
+ return EXIT.error;
80
+ }
77
81
  throw err;
78
82
  }
79
83
  },
package/dist/update.js CHANGED
@@ -18,12 +18,14 @@ import { AgentStateStore } from "./agent-state.js";
18
18
  import { writePrivateFile } from "./credentials.js";
19
19
  import { CodedError } from "./errors.js";
20
20
  import { json } from "./format.js";
21
+ import { macAppPackage, ONE_APP_ON_MAC } from "./mac-app.js";
21
22
  import { compareAppVersions, MenubarManager, packagedAppPath } from "./menubar.js";
22
23
  export const INSTALL_FILE = 'install.json';
23
24
  export const LAST_UPDATE_FILE = 'last-update.json';
24
25
  const LOCK_FILE = 'update.lock';
25
26
  const MCP = '@shardflux/mcp';
26
- const MENUBAR = '@shardflux/menubar-darwin';
27
+ /** The npm package of the macOS app on this Mac: the menu bar app, or the one Shardflux app once it takes over. */
28
+ const MENUBAR = macAppPackage();
27
29
  /** Finds the package directory of `pkg` above `script` and how it was installed. */
28
30
  export function detectInstall(script, pkg, platform = process.platform) {
29
31
  let real = script;
@@ -100,7 +102,7 @@ export function displayCommand(install, specs) {
100
102
  }
101
103
  /** Prefixes can contain spaces, quotes or shell substitutions. The actual install still uses argv. */
102
104
  const shellQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
103
- const LABELS = { shardflux: 'shardflux', '@shardflux/cli': 'shard', '@shardflux/sdk': 'SDK', [MENUBAR]: 'menu bar', [MCP]: 'MCP server' };
105
+ const LABELS = { shardflux: 'shardflux', '@shardflux/cli': 'shard', '@shardflux/sdk': 'SDK', [MENUBAR]: ONE_APP_ON_MAC ? 'Shardflux app' : 'menu bar', [MCP]: 'MCP server' };
104
106
  /** Every local piece and its status against the catalog. */
105
107
  export async function components(ctx, install, catalog, menubar = menubarManager(ctx)) {
106
108
  const local = [[ctx.distribution.package, ctx.distribution.version]];
@@ -14,7 +14,7 @@ export declare function queueLine(e: Pick<Extract<ProgressEvent, {
14
14
  type: 'phase';
15
15
  }>, 'queuePosition' | 'scaleOut' | 'expectedReadyBy' | 'deadlineAt'>): string;
16
16
  /** A call waiting to be admitted, in words. */
17
- export declare function admissionLine(r: Pick<RetryRecord, 'reason' | 'queuePosition'>): string;
17
+ export declare function admissionLine(r: Pick<RetryRecord, 'reason' | 'queuePosition' | 'retryAfterSeconds'>): string;
18
18
  /**
19
19
  * The client listener that prints where a wait stands. Silent with `json`. A phase other than capacity_pending ends
20
20
  * the queue, so a later queue prints again.
@@ -17,6 +17,8 @@ export function queueLine(e) {
17
17
  /** A call waiting to be admitted, in words. */
18
18
  export function admissionLine(r) {
19
19
  const position = r.queuePosition ? ` (position ${r.queuePosition})` : '';
20
+ if (r.reason === 'admission_queued')
21
+ return `queued for admission${position}${r.retryAfterSeconds != null ? `; retrying in ${r.retryAfterSeconds} s` : ''}`;
20
22
  if (r.reason === 'host_capacity')
21
23
  return `waiting for room on the workspace's host${position}`;
22
24
  if (r.reason === 'concurrent_workspaces')
@@ -624,7 +624,7 @@ export const TWIN_HANDLERS = {
624
624
  ledger.setMode(project, action);
625
625
  }
626
626
  const mode = ledger.mode(project);
627
- const text = mode === 'auto' ? 'Finished conversations land in your local branch automatically.' : mode === 'pr' ? 'Finished conversations open a pull request instead of landing locally.' : 'Automatic landing is off for this project.';
627
+ const text = !ledger.autoLand() ? 'Automatic landing is off on this computer: shard settings --auto-land on turns it back on.' : mode === 'auto' ? 'Finished conversations land in your local branch automatically.' : mode === 'pr' ? 'Finished conversations open a pull request instead of landing locally.' : 'Automatic landing is off for this project.';
628
628
  out(ctx, `${text}\n`, { project_id: project, landing: mode });
629
629
  return 0;
630
630
  },
@@ -265,7 +265,9 @@ export interface TwinConversationState {
265
265
  last_error?: string;
266
266
  latency_ms?: number;
267
267
  }
268
+ /** `auto_land: false` (`shard settings --auto-land off`, the desktop app's Settings switch): nothing lands on this computer. */
268
269
  interface TwinDoc {
270
+ auto_land?: boolean;
269
271
  landing?: Record<string, LandingMode>;
270
272
  conversations?: Record<string, TwinConversationState>;
271
273
  }
@@ -273,8 +275,12 @@ export declare class TwinLedger {
273
275
  readonly store: AgentStateStore;
274
276
  constructor(dir: string);
275
277
  read(): TwinDoc;
278
+ /** The project's landing, 'off' everywhere while this computer's switch is off. */
276
279
  mode(project: string): LandingMode;
277
280
  setMode(project: string, mode: LandingMode): void;
281
+ /** This computer's switch for every project (on unless turned off). */
282
+ autoLand(): boolean;
283
+ setAutoLand(on: boolean): void;
278
284
  conversation(id: string): TwinConversationState;
279
285
  patch(id: string, change: Partial<TwinConversationState>): void;
280
286
  forget(id: string): void;
@@ -726,8 +726,15 @@ export class TwinLedger {
726
726
  store;
727
727
  constructor(dir) { this.store = new AgentStateStore(dir); }
728
728
  read() { return this.store.read().worktree_twin ?? {}; }
729
- mode(project) { return this.read().landing?.[project] ?? 'auto'; }
729
+ /** The project's landing, 'off' everywhere while this computer's switch is off. */
730
+ mode(project) { const doc = this.read(); return doc.auto_land === false ? 'off' : doc.landing?.[project] ?? 'auto'; }
730
731
  setMode(project, mode) { this.update(doc => { doc.landing = { ...doc.landing, [project]: mode }; }); }
732
+ /** This computer's switch for every project (on unless turned off). */
733
+ autoLand() { return this.read().auto_land !== false; }
734
+ setAutoLand(on) { this.update(doc => { if (on)
735
+ delete doc.auto_land;
736
+ else
737
+ doc.auto_land = false; }); }
731
738
  conversation(id) { return this.read().conversations?.[id] ?? {}; }
732
739
  patch(id, change) {
733
740
  this.update(doc => { doc.conversations = { ...doc.conversations, [id]: { ...doc.conversations?.[id], ...change } }; });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shardflux/cli",
3
- "version": "0.25.0",
3
+ "version": "0.28.0",
4
4
  "type": "module",
5
5
  "description": "shard: the Shardflux command line. Create an account, sign in, set up keys and billing, then open, run commands in, move files to, suspend, resume and fork serverless agent workspaces.",
6
6
  "license": "Apache-2.0",
@@ -48,7 +48,7 @@
48
48
  "snappyjs": "0.7.0",
49
49
  "ws": "8.22.0",
50
50
  "yaml": "2.9.1",
51
- "@shardflux/sdk": "^0.28.0"
51
+ "@shardflux/sdk": "^0.30.0"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@eslint/js": "10.0.1",
@@ -60,9 +60,10 @@
60
60
  "typescript-eslint": "8.70.1"
61
61
  },
62
62
  "optionalDependencies": {
63
- "@shardflux/desktop-linux-arm64": "0.1.12",
64
- "@shardflux/desktop-linux-x64": "0.1.12",
65
- "@shardflux/menubar-darwin": "^0.8.6"
63
+ "@shardflux/desktop-darwin-arm64": "0.9.1",
64
+ "@shardflux/desktop-darwin-x64": "0.9.1",
65
+ "@shardflux/desktop-linux-arm64": "0.9.1",
66
+ "@shardflux/desktop-linux-x64": "0.9.1"
66
67
  },
67
68
  "scripts": {
68
69
  "build": "node scripts/build.mjs",