@shardflux/cli 0.15.2 → 0.17.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 (77) hide show
  1. package/CHANGELOG.md +144 -14
  2. package/README.md +92 -7
  3. package/dist/acp-models.js +5 -1
  4. package/dist/agent-commands.d.ts +15 -1
  5. package/dist/agent-commands.js +84 -23
  6. package/dist/agent-models.d.ts +61 -0
  7. package/dist/agent-models.js +79 -0
  8. package/dist/agent-state.d.ts +10 -0
  9. package/dist/agent-state.js +13 -0
  10. package/dist/agent-target-commands.d.ts +13 -3
  11. package/dist/agent-target-commands.js +129 -23
  12. package/dist/agent-targets.d.ts +5 -0
  13. package/dist/agent-targets.js +14 -5
  14. package/dist/agent-terminal.d.ts +8 -0
  15. package/dist/agent-terminal.js +35 -2
  16. package/dist/args.d.ts +5 -1
  17. package/dist/args.js +74 -11
  18. package/dist/cli-proxy-forward.d.ts +45 -0
  19. package/dist/cli-proxy-forward.js +432 -0
  20. package/dist/cli-proxy-hub.d.ts +273 -0
  21. package/dist/cli-proxy-hub.js +749 -0
  22. package/dist/cli-proxy.d.ts +325 -0
  23. package/dist/cli-proxy.js +1016 -0
  24. package/dist/codex-cloud.d.ts +3 -0
  25. package/dist/codex-cloud.js +17 -5
  26. package/dist/command-center-actions.js +25 -1
  27. package/dist/commands.d.ts +22 -0
  28. package/dist/commands.js +54 -14
  29. package/dist/conversation-commands.js +6 -0
  30. package/dist/conversation-local.js +2 -2
  31. package/dist/conversation-stash.js +4 -3
  32. package/dist/conversation-terminal.d.ts +5 -0
  33. package/dist/conversation-terminal.js +54 -6
  34. package/dist/conversations.d.ts +31 -3
  35. package/dist/conversations.js +53 -10
  36. package/dist/device-approvals.d.ts +12 -0
  37. package/dist/device-approvals.js +73 -0
  38. package/dist/device-helper.d.ts +3 -1
  39. package/dist/device-helper.js +14 -4
  40. package/dist/errors.d.ts +7 -0
  41. package/dist/errors.js +12 -2
  42. package/dist/files-mirror.d.ts +164 -0
  43. package/dist/files-mirror.js +761 -0
  44. package/dist/handlers.js +5 -1
  45. package/dist/index.d.ts +4 -0
  46. package/dist/index.js +3 -0
  47. package/dist/linux-helper.d.ts +12 -0
  48. package/dist/linux-helper.js +88 -0
  49. package/dist/linux-terminal.d.ts +11 -0
  50. package/dist/linux-terminal.js +42 -0
  51. package/dist/main.js +38 -8
  52. package/dist/onboarding.d.ts +2 -0
  53. package/dist/onboarding.js +53 -5
  54. package/dist/registry.d.ts +2 -42
  55. package/dist/registry.js +6 -90
  56. package/dist/t3-launch.d.ts +37 -0
  57. package/dist/t3-launch.js +133 -0
  58. package/dist/t3-mode.d.ts +121 -0
  59. package/dist/t3-mode.js +654 -0
  60. package/dist/t3-phone.d.ts +17 -0
  61. package/dist/t3-phone.js +15 -0
  62. package/dist/t3-thread-script.d.ts +18 -0
  63. package/dist/t3-thread-script.js +245 -0
  64. package/dist/terminal-onboarding.d.ts +11 -0
  65. package/dist/terminal-onboarding.js +75 -0
  66. package/dist/thread-on-computer.d.ts +35 -0
  67. package/dist/thread-on-computer.js +206 -0
  68. package/dist/update.d.ts +1 -1
  69. package/dist/update.js +12 -1
  70. package/dist/wait-status.d.ts +22 -0
  71. package/dist/wait-status.js +59 -0
  72. package/dist/worktree-twin-engine.d.ts +125 -0
  73. package/dist/worktree-twin-engine.js +615 -0
  74. package/dist/worktree-twin-land.py +331 -0
  75. package/dist/worktree-twin.d.ts +313 -0
  76. package/dist/worktree-twin.js +843 -0
  77. package/package.json +3 -3
@@ -0,0 +1,133 @@
1
+ import { out, resolveWorkspace, str } from "./commands.js";
2
+ import { CodedError, NotFoundError, UsageError } from "./errors.js";
3
+ import { projectIdentity } from "./project-identity.js";
4
+ import { AgentStateStore } from "./agent-state.js";
5
+ import { readInitialImages, readInitialPrompt } from "./initial-prompt.js";
6
+ import { findAcpThread, openT3, startAcpThread, t3Install, t3NotInstalled, t3OpenFailed } from "./t3-mode.js";
7
+ import { agentReachability } from "./agent-reachability.js";
8
+ import { conversationWorkspace, getConversation } from "./conversations.js";
9
+ import { resolveOn } from "./thread-on-computer.js";
10
+ import { ensureCloudClaude } from "./claude-cloud-login.js";
11
+ import { modelChoice } from "./agent-models.js";
12
+ /**
13
+ * A `t3_*` refusal raised once the workspace, conversation or thread exists names them (details.workspace_key,
14
+ * conversation_id, t3_thread_id), so a client opens what exists instead of starting it again.
15
+ */
16
+ export function withT3Context(error, ref) {
17
+ if (!(error instanceof CodedError) || !error.code.startsWith('t3_'))
18
+ return error;
19
+ const details = { ...error.details, conversation_id: ref.conversation_id ?? error.details?.conversation_id ?? null, workspace_key: ref.workspace_key ?? error.details?.workspace_key ?? null,
20
+ t3_thread_id: ref.t3_thread_id ?? error.details?.t3_thread_id ?? null };
21
+ const next = new CodedError(error.code, error.message, error.hint, details);
22
+ next.stack = error.stack;
23
+ return next;
24
+ }
25
+ /** The thread to start, when the command carries a first message (`--prompt`, `--prompt-file`, `--image`, `--model`, `--effort`). */
26
+ export async function t3ThreadRequest(ctx, values, app) {
27
+ const prompt = await readInitialPrompt(ctx, values);
28
+ const images = await readInitialImages(ctx, values, prompt);
29
+ if (prompt === undefined) {
30
+ if (values.model !== undefined || values.effort !== undefined)
31
+ throw new UsageError('--model and --effort choose how the first message runs: add --prompt or --prompt-file.');
32
+ return undefined;
33
+ }
34
+ // The same choices as every other new thread (agent-models.ts); "default" leaves the agent's own.
35
+ const choice = modelChoice(values, app);
36
+ return { app, prompt, ...(choice.model ? { model: choice.model } : {}), ...(choice.effort ? { effort: choice.effort } : {}), ...(images.length ? { images } : {}) };
37
+ }
38
+ /**
39
+ * `agents new --in t3`: with a first message in ACP mode, the thread starts on T3's Shardflux provider (its own VM);
40
+ * otherwise the project VM is opened as T3's SSH environment (with its phone link), and a first message becomes a
41
+ * thread on the T3 server in that VM. `--on <ref>` uses that existing workspace instead of the project VM.
42
+ */
43
+ export async function launchT3(ctx, values, path, explicitApp) {
44
+ const install = t3Install(ctx.io.env);
45
+ if (!install)
46
+ throw t3NotInstalled();
47
+ if (explicitApp !== undefined && explicitApp !== 'claude' && explicitApp !== 'codex')
48
+ throw new UsageError('agent app must be claude or codex');
49
+ const project = projectIdentity(path);
50
+ const app = explicitApp ?? new AgentStateStore(ctx.store.dir).app(project.remote ?? project.folder);
51
+ const request = await t3ThreadRequest(ctx, values, app);
52
+ const on = str(values.on);
53
+ if (values.review === true)
54
+ throw new UsageError('--review starts a Claude reviewer in its terminal; leave out --in t3.');
55
+ if (on !== undefined) {
56
+ if (request && install.mode === 'acp') {
57
+ throw new CodedError('t3_on_not_supported', 'With the Shardflux provider in T3 Code, every thread runs in its own new VM, so it cannot start on an existing workspace.', 'Leave out --on to start the thread in its own VM, or open that workspace in T3 Code with shard agents open t3 <workspace>.', { workspace_key: on, conversation_id: null });
58
+ }
59
+ const { ws, conversation } = await onComputer(ctx, on, project.folder);
60
+ // The thread's agent runs in that VM with the account's logins, as `agents open t3` prepares them.
61
+ await ensureCloudClaude(ctx, ws);
62
+ const { openDesktopWorkspace } = await import("./agent-target-commands.js");
63
+ return openDesktopWorkspace(ctx, ws, 't3', install.version ?? undefined, false, request, install, conversation);
64
+ }
65
+ if (request && install.mode === 'acp')
66
+ return acpThreadOutput(ctx, install, project, request);
67
+ const { launchProjectWith } = await import("./agent-commands.js");
68
+ return launchProjectWith(ctx, { ...values, in: 't3' }, [path, app], request, install);
69
+ }
70
+ /**
71
+ * The computer `--on` names, as for any new thread (thread-on-computer.ts): a workspace key or id, else a conversation
72
+ * id, key or name (its VM). The T3 server in that VM runs the thread.
73
+ */
74
+ async function onComputer(ctx, ref, folder) {
75
+ if (!/^conv_[a-z0-9]{26}$/.test(ref)) {
76
+ try {
77
+ const ws = await resolveWorkspace(ctx, ref);
78
+ return { ws, conversation: ws.labels.shard_conversation || null };
79
+ }
80
+ catch (error) {
81
+ if (!(error instanceof NotFoundError))
82
+ throw error;
83
+ }
84
+ }
85
+ const target = await resolveOn(ctx, ref, folder);
86
+ return { ws: await conversationWorkspace(ctx, target), conversation: target.id };
87
+ }
88
+ async function acpThreadOutput(ctx, install, project, request) {
89
+ const started = await startAcpThread(ctx, install, project.folder, request);
90
+ new AgentStateStore(ctx.store.dir).saveTarget(project.remote ?? project.folder, 't3');
91
+ out(ctx, `Started in T3 Code: ${request.prompt.split('\n')[0].slice(0, 80)}${started.conversation_id ? ` (${started.conversation_id})` : ''}\n`, { target: 't3', mode: started.mode, project: project.slug, t3_thread_id: started.t3_thread_id, conversation_id: started.conversation_id, workspace: started.workspace,
92
+ opened: true, focused: false, model: started.model, effort: started.effort, ...(started.effort_reason ? { effort_reason: started.effort_reason } : {}), app_bundle_id: 'com.t3tools.t3code' });
93
+ return 0;
94
+ }
95
+ /** The T3 fields of an SSH-mode result (the thread, when one was started in the VM). */
96
+ export function t3Fields(ws, project, install, started, conversation) {
97
+ return { mode: install?.mode ?? 'ssh', project, t3_thread_id: started?.t3_thread_id ?? null, conversation_id: started?.conversation_id ?? conversation ?? (ws.labels.shard_conversation || null),
98
+ opened: true, focused: false, model: started?.model ?? null, effort: started?.effort ?? null, ...(started?.effort_reason ? { effort_reason: started.effort_reason } : {}) };
99
+ }
100
+ /**
101
+ * `agents open t3 <conversation>` for a conversation that runs on T3's Shardflux provider (ACP mode): T3 is brought to
102
+ * the front with that thread's id. A conversation of another route opens its VM as T3's SSH environment.
103
+ */
104
+ export async function openT3Conversation(ctx, conversationId, values = {}) {
105
+ const install = t3Install(ctx.io.env);
106
+ if (!install)
107
+ throw t3NotInstalled();
108
+ const conversation = await getConversation(ctx, conversationId);
109
+ if (install.mode === 'acp' && conversation.route === 'acp') {
110
+ let threadId;
111
+ try {
112
+ threadId = await findAcpThread(ctx, install, conversationId);
113
+ }
114
+ catch (error) {
115
+ throw withT3Context(error, { conversation_id: conversationId });
116
+ }
117
+ try {
118
+ await openT3(ctx.io.env, install.path);
119
+ }
120
+ catch (error) {
121
+ throw threadId ? t3OpenFailed(error, { t3_thread_id: threadId, conversation_id: conversationId }) : error;
122
+ }
123
+ out(ctx, threadId ? `Opened T3 Code (thread ${threadId}).\n` : 'Opened T3 Code.\n', { target: 't3', mode: 'acp', conversation_id: conversationId, t3_thread_id: threadId, opened: true, focused: false, app_bundle_id: 'com.t3tools.t3code' });
124
+ return 0;
125
+ }
126
+ // Its VM as T3's SSH environment, with the checks every `agents open t3` makes: --reachable and the cloud Claude login.
127
+ let ws = await conversationWorkspace(ctx, conversation);
128
+ if (values.reachable !== undefined)
129
+ ws = await ctx.cloud().workspaces.open({ key: ws.key, ...agentReachability(values), wait: false });
130
+ await ensureCloudClaude(ctx, ws);
131
+ const { openDesktopWorkspace } = await import("./agent-target-commands.js");
132
+ return openDesktopWorkspace(ctx, ws, 't3', install.version ?? undefined, false, undefined, install, conversationId);
133
+ }
@@ -0,0 +1,121 @@
1
+ import type { Workspace } from '@shardflux/sdk';
2
+ import type { Ctx } from './commands.js';
3
+ import { CodedError } from './errors.js';
4
+ import type { InitialImage } from './initial-prompt.js';
5
+ export declare const T3_BUNDLE_ID = "com.t3tools.t3code";
6
+ export type T3Mode = 'acp' | 'ssh';
7
+ export type AgentApp = 'claude' | 'codex';
8
+ export type OpenIn = 'terminal' | 'claude-desktop' | 'web' | 'codex-app' | 't3';
9
+ /** Where each app's new conversations can open: Claude in a terminal, Claude Desktop, claude.ai or T3; Codex in the Codex app or T3. */
10
+ export declare const OPEN_IN_TARGETS: Record<AgentApp, readonly OpenIn[]>;
11
+ export interface T3Install {
12
+ path: string;
13
+ version: string | null;
14
+ mode: T3Mode;
15
+ }
16
+ export interface T3Status {
17
+ installed: boolean;
18
+ mode: T3Mode | null;
19
+ connected: boolean;
20
+ version: string | null;
21
+ last_used_at: string | null;
22
+ }
23
+ export interface T3ThreadRequest {
24
+ app: AgentApp;
25
+ prompt: string;
26
+ model?: string;
27
+ effort?: string;
28
+ images?: readonly InitialImage[];
29
+ }
30
+ /** Why a requested `--effort` reached T3 as none: the model offers no such level, or its levels are not known yet. */
31
+ export type EffortReason = 'not_offered' | 'models_not_known';
32
+ export interface T3ThreadResult {
33
+ mode: T3Mode;
34
+ t3_thread_id: string;
35
+ conversation_id: string | null;
36
+ workspace: {
37
+ workspace_key: string;
38
+ id: string;
39
+ } | null;
40
+ model: string | null;
41
+ effort: string | null;
42
+ effort_reason?: EffortReason;
43
+ }
44
+ /** Native lookups that tests replace: Spotlight's last-used dates of app bundles, one `mdls` for all of them. */
45
+ export declare const t3Native: {
46
+ lastUsedMany(paths: readonly string[], env: Record<string, string | undefined>): Record<string, string | null>;
47
+ };
48
+ /** Spotlight takes about 40 ms per call, so the dates are kept for a few minutes (app-usage.json in the config dir). */
49
+ export declare const LAST_USED_TTL_MS: number;
50
+ export declare function lastUsedDates(ctx: Ctx, paths: readonly string[], now?: number): Record<string, string | null>;
51
+ /** The installed T3 Code, preferring a build with ACP providers when several channels are installed. */
52
+ export declare function t3Install(env: Record<string, string | undefined>, platform?: NodeJS.Platform): T3Install | undefined;
53
+ export declare function t3HomeDir(env: Record<string, string | undefined>): string;
54
+ /** The Shardflux provider instance in T3's server settings (read only; every `shard` run writes it, see t3-provider.ts). */
55
+ export declare function t3ProviderRegistered(env: Record<string, string | undefined>): boolean;
56
+ /**
57
+ * T3 is connected when a new thread can start in it now: in ACP mode the Shardflux provider is in T3's settings and
58
+ * this computer is signed in; in SSH mode this project has a T3 SSH environment set up from this computer (signed in).
59
+ * `last_used_at` is looked up only when asked for (`agents targets`).
60
+ */
61
+ export declare function t3Status(ctx: Ctx, opts?: {
62
+ project?: string | null;
63
+ install?: T3Install | null;
64
+ lastUsed?: boolean;
65
+ }): T3Status;
66
+ export interface OpenInChoice {
67
+ target: OpenIn;
68
+ saved: boolean;
69
+ }
70
+ /**
71
+ * Where each app's new conversations open for `project`: the choice saved with `agents targets --open-in` while it is
72
+ * available, else T3 when it is connected and the most recently used of the installed desktop apps, else a terminal
73
+ * (Claude) or the Codex app. Lookups run only when an answer needs them: none for a saved terminal, web or Codex app
74
+ * choice, and Spotlight only when T3 is installed and connected (cached, see lastUsedDates).
75
+ */
76
+ export declare function openInFor(ctx: Ctx, project: string | null, known?: {
77
+ clients?: readonly string[];
78
+ }): Record<AgentApp, OpenInChoice>;
79
+ export declare function openInChoices(ctx: Ctx, project: string | null, known?: {
80
+ clients?: readonly string[];
81
+ }): Record<AgentApp, OpenIn>;
82
+ /** `--open-in claude=t3,codex=t3`: validated against what is installed, then saved in agents.json. */
83
+ export declare function saveOpenIn(ctx: Ctx, spec: string, status: T3Status, clients: readonly string[]): void;
84
+ /** A T3 thread was started, then a later step failed: the error names the thread so a client opens it, never starts another. */
85
+ export declare function t3OpenFailed(error: unknown, ref: {
86
+ t3_thread_id: string;
87
+ conversation_id?: string | null;
88
+ workspace_key?: string | null;
89
+ }): CodedError;
90
+ export declare const t3NotInstalled: () => CodedError;
91
+ /** The first line of the prompt, as T3 titles a thread before it names it. */
92
+ export declare function t3ThreadTitle(prompt: string): string;
93
+ /**
94
+ * Opens T3 Code (or brings it to the front): the install `shard` chose (`open -a <path>`). Every T3 channel shares one
95
+ * bundle id, so `open -b` could start another installed channel.
96
+ */
97
+ export declare function openT3(env: Record<string, string | undefined>, appPath: string): Promise<void>;
98
+ /** Starts the thread in the VM's T3 server (T3_THREAD_SCRIPT); the prompt travels on stdin, never in argv. */
99
+ export declare function startSshThread(ctx: Ctx, ws: Workspace, version: string | undefined, projectPath: string, request: T3ThreadRequest): Promise<T3ThreadResult>;
100
+ interface Descriptor {
101
+ serverVersion?: string;
102
+ orchestrationProtocolVersion?: number;
103
+ }
104
+ export interface LocalT3Server {
105
+ origin: string;
106
+ descriptor: Descriptor;
107
+ base: string;
108
+ }
109
+ interface T3Session {
110
+ token: string;
111
+ sessionId: string;
112
+ }
113
+ /** The running desktop server; T3 is opened (which starts it) when none answers. */
114
+ export declare function localT3Server(ctx: Ctx, install: T3Install, waitMs?: number): Promise<LocalT3Server>;
115
+ /** One Effect RPC request over T3's WebSocket (JSON frames: Request → Exit), authorized by a ticket from the session. */
116
+ export declare function t3Rpc(server: LocalT3Server, session: T3Session, tag: string, payload: unknown, signal: AbortSignal, timeoutMs?: number): Promise<unknown>;
117
+ /** Starts a T3 thread on the Shardflux provider (ACP mode) and brings T3 to the front. */
118
+ export declare function startAcpThread(ctx: Ctx, install: T3Install, folder: string, request: T3ThreadRequest, waitMs?: number): Promise<T3ThreadResult>;
119
+ /** The T3 thread of a Shardflux conversation started in T3 (ACP mode): the one remembered, else a search of recent Shardflux threads. */
120
+ export declare function findAcpThread(ctx: Ctx, install: T3Install, conversationId: string): Promise<string | null>;
121
+ export {};