@shardflux/cli 0.28.0 → 0.28.2
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 +54 -17
- package/README.md +91 -12
- package/dist/account-commands.d.ts +2 -4
- package/dist/account-commands.js +2 -6
- package/dist/agent-git-commands.d.ts +1 -2
- package/dist/agent-git-commands.js +2 -21
- package/dist/agent-setup.d.ts +4 -2
- package/dist/agent-setup.js +6 -4
- package/dist/agent-shell.d.ts +2 -0
- package/dist/agent-shell.js +5 -0
- package/dist/args.d.ts +1 -1
- package/dist/args.js +5 -1
- package/dist/board-args.js +7 -0
- package/dist/board-commands.d.ts +1 -1
- package/dist/board-commands.js +46 -6
- package/dist/cli-proxy.d.ts +1 -13
- package/dist/cli-proxy.js +2 -27
- package/dist/codex-cloud.js +3 -1
- package/dist/commands.d.ts +12 -0
- package/dist/commands.js +121 -4
- package/dist/conversations.d.ts +2 -0
- package/dist/conversations.js +1 -1
- package/dist/device-bridge.d.ts +4 -1
- package/dist/device-bridge.js +9 -2
- package/dist/device-folders.d.ts +2 -1
- package/dist/device-folders.js +3 -3
- package/dist/format.js +3 -0
- package/dist/forwarder-refresh.d.ts +15 -0
- package/dist/forwarder-refresh.js +32 -0
- package/dist/handler-loader.d.ts +3 -0
- package/dist/handler-loader.js +43 -0
- package/dist/land.d.ts +1 -1
- package/dist/land.js +6 -5
- package/dist/linux-helper.js +1 -2
- package/dist/machine-setup-guard.d.ts +9 -0
- package/dist/machine-setup-guard.js +22 -0
- package/dist/machine-setup.d.ts +1 -8
- package/dist/machine-setup.js +2 -22
- package/dist/main.js +17 -16
- package/dist/orchestrator-commands.js +1 -3
- package/dist/startup-housekeeping.d.ts +5 -0
- package/dist/startup-housekeeping.js +30 -0
- package/dist/t3-provider.js +4 -2
- package/dist/worktree-twin-engine.d.ts +1 -1
- package/dist/worktree-twin-engine.js +9 -6
- package/package.json +12 -7
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Exact command ownership, in the same override order as HANDLERS. The parity test catches additions
|
|
2
|
+
// or moves without loading every command module during an ordinary command.
|
|
3
|
+
const groups = [
|
|
4
|
+
[["projects sync list", "projects sync add", "projects sync pause", "projects sync remove", "projects sync run"], async () => (await import("./project-preparation.js")).PROJECT_PREPARATION_HANDLERS],
|
|
5
|
+
[["projects link"], async () => (await import("./device-folders.js")).PROJECT_LINK_HANDLERS],
|
|
6
|
+
[["move"], async () => (await import("./move.js")).MOVE_HANDLERS],
|
|
7
|
+
[["device approvals", "device requests", "device approve", "device deny"], async () => (await import("./device-approvals.js")).DEVICE_HANDLERS],
|
|
8
|
+
[["agents codex status", "agents codex enable", "agents codex disable"], async () => (await import("./codex-cloud.js")).CODEX_CLOUD_HANDLERS],
|
|
9
|
+
[["cli-proxy", "cli-proxy import", "cli-proxy export", "cli-proxy use", "cli-proxy forward"], async () => (await import("./cli-proxy.js")).CLI_PROXY_HANDLERS],
|
|
10
|
+
[["agents computers"], async () => (await import("./thread-on-computer.js")).THREAD_HANDLERS],
|
|
11
|
+
[["conversations resume", "conversations reader", "conversations list", "conversations feed", "conversations get", "conversations permissions", "conversations answer", "conversations watch", "conversations send", "conversations start", "conversations wait", "conversations land", "files pull", "conversations move", "conversations archive", "conversations unarchive", "conversations seen", "conversations delete"], async () => (await import("./conversation-commands.js")).CONVERSATION_HANDLERS],
|
|
12
|
+
[["orchestrator say", "orchestrator list", "orchestrator retry", "orchestrator dismiss", "orchestrator restore"], async () => (await import("./orchestrator-commands.js")).ORCHESTRATOR_HANDLERS],
|
|
13
|
+
[["board", "task add", "task show", "task edit", "task rm", "task steer", "task land", "task clear-done", "needs-you", "needs-you answer", "approvals", "approvals set", "approvals allow", "approvals remove"], async () => (await import("./board-commands.js")).BOARD_HANDLERS],
|
|
14
|
+
[["conversations transcript", "conversations events"], async () => (await import("./conversation-transcript.js")).TRANSCRIPT_HANDLERS],
|
|
15
|
+
[["conversations try"], async () => (await import("./conversation-local.js")).CONVERSATION_HANDLERS],
|
|
16
|
+
[["conversations landing"], async () => (await import("./worktree-twin-engine.js")).TWIN_HANDLERS],
|
|
17
|
+
[["secrets log", "credentials list", "credentials enable", "credentials approve", "credentials deny", "credentials forget"], async () => (await import("./credential-prompts.js")).CREDENTIAL_HANDLERS],
|
|
18
|
+
[["agents credentials login", "agents credentials sync", "agents credentials status", "agents credentials forget"], async () => (await import("./agent-credentials.js")).AGENT_CREDENTIAL_HANDLERS],
|
|
19
|
+
[["agents accounts", "agents accounts list", "agents accounts usage", "agents accounts add", "agents accounts remove", "agents accounts use", "accounts", "accounts list", "accounts usage", "accounts add", "accounts remove", "accounts use"], async () => (await import("./account-pool.js")).ACCOUNT_POOL_HANDLERS],
|
|
20
|
+
[["agents models"], async () => (await import("./account-pool.js")).AGENT_MODELS_HANDLERS],
|
|
21
|
+
[["agents list", "login", "whoami", "version", "feedback", "usage", "workspaces labels", "workspaces hint", "workspaces idle", "workspaces exec-start", "workspaces tunnel", "workspaces exec-read", "workspaces exec-cancel", "workspaces exec-input", "workspaces retention", "projects retention", "workspaces open", "workspaces list", "workspaces get", "workspaces inputs", "workspaces exec", "files read-many", "files read", "files write", "files delete", "files ls", "files stat", "files search", "files patch", "workspaces suspend", "workspaces resume", "workspaces delete", "workspaces close", "workspaces upgrade", "workspaces reset", "workspaces save-as-template", "workspaces changes", "templates list", "templates get", "templates init", "templates build", "templates export", "templates test", "templates builds list", "templates builds get", "templates builds log", "templates builds cancel", "templates languages", "templates packages search", "templates files", "templates diff", "templates draft open", "templates draft status", "templates draft state", "templates draft test", "templates draft publish", "templates draft discard", "workspaces fork", "workspaces resize", "workspaces sessions", "workspaces port expose", "workspaces port ls", "workspaces port close", "workspaces port token", "workspaces port link", "workspaces port callback", "workspaces computer status", "workspaces computer screenshot", "workspaces computer act", "workspaces computer click", "workspaces computer type", "workspaces computer paste-text", "workspaces computer key", "workspaces computer scroll", "workspaces computer move", "workspaces computer drag", "workspaces computer mouse-down", "workspaces computer mouse-up", "workspaces computer hold-key", "workspaces computer wait", "workspaces computer zoom", "workspaces computer cursor", "workspaces computer start", "workspaces computer stop", "workspaces computer stream", "workspaces computer enable", "workspaces computer disable", "workspaces computer inherit", "templates computer-use", "workspaces secrets", "secrets import", "secrets create", "secrets list", "secrets get", "secrets update", "secrets rotate", "secrets versions", "secrets delete", "secrets access-log", "operations list", "operations get", "operations progress", "operations cancel", "operations wait", "executions get"], async () => (await import("./commands.js")).KEY_HANDLERS],
|
|
22
|
+
[["auth register", "auth verify-email", "auth resend-verification", "auth login", "auth mfa", "auth status", "auth logout", "auth sessions", "auth step-up", "auth password change", "auth password reset", "auth password reset-confirm", "auth email change", "auth email confirm", "auth totp enroll", "auth totp confirm", "auth totp disable", "auth totp recovery-codes", "setup", "signup", "verify", "env", "orgs list", "orgs create", "orgs get", "orgs use", "orgs export", "orgs workspaces", "orgs deletion", "orgs delete", "projects list", "projects create", "projects get", "projects use", "api-keys list", "api-keys create", "api-keys revoke", "members list", "members set-role", "members remove", "invitations list", "invitations create", "invitations revoke", "invitations accept", "billing plans", "billing status", "billing upgrade", "billing portal", "billing invoices", "billing alerts", "billing overage", "audit list", "audit export", "account status", "account export", "account delete", "account cancel-deletion", "egress get", "egress set", "egress history", "egress clear", "volumes list", "status", "volumes open", "volumes create", "volumes get", "volumes delete", "volumes attachments", "volumes attach", "volumes detach", "templates publish", "templates archive"], async () => (await import("./account-commands.js")).ACCOUNT_HANDLERS],
|
|
23
|
+
[["agents open"], async () => (await import("./agent-commands.js")).AGENT_HANDLERS],
|
|
24
|
+
[["agents new", "agents fork", "agents park", "agents logs"], async () => (await import("./command-center-actions.js")).CENTER_HANDLERS],
|
|
25
|
+
[["agents targets", "agents terminal-run", "agents open claude-desktop", "agents open codex-app", "agents open t3", "agents open web", "ssh-proxy"], async () => (await import("./agent-target-commands.js")).TARGET_HANDLERS],
|
|
26
|
+
[["agents merge", "agents keep", "agents discard", "agents worktree", "agents restore"], async () => (await import("./agent-git-commands.js")).AGENT_GIT_HANDLERS],
|
|
27
|
+
[["menubar start", "menubar stop", "menubar status", "menubar account"], async () => (await import("./menubar.js")).MENUBAR_HANDLERS],
|
|
28
|
+
[["machine", "machine open", "machine save", "machine rollback", "template", "template save", "template rollback", "template delete"], async () => (await import("./filesystem-commands.js")).FILESYSTEM_HANDLERS],
|
|
29
|
+
[["machine setup"], async () => (await import("./machine-setup.js")).MACHINE_SETUP_HANDLERS],
|
|
30
|
+
[["settings"], async () => (await import("./client-settings.js")).SETTINGS_HANDLERS],
|
|
31
|
+
[["update"], async () => (await import("./update.js")).UPDATE_HANDLERS],
|
|
32
|
+
[["agents config-sync"], async () => (await import("./agent-config-sync.js")).AGENT_CONFIG_HANDLERS],
|
|
33
|
+
[["route", "ask"], async () => (await import("./router-commands.js")).ROUTER_HANDLERS],
|
|
34
|
+
[["transcribe"], async () => (await import("./transcribe-commands.js")).TRANSCRIBE_HANDLERS],
|
|
35
|
+
[["drive ls", "drive get", "drive put", "drive rm", "models", "models primary"], async () => (await import("./drive-commands.js")).DRIVE_HANDLERS],
|
|
36
|
+
[["brain ls", "brain get"], async () => (await import("./brain-commands.js")).BRAIN_HANDLERS],
|
|
37
|
+
];
|
|
38
|
+
const owners = new Map(groups.flatMap(([paths, load]) => paths.map(path => [path, load])));
|
|
39
|
+
export const HANDLER_PATHS = [...owners.keys()];
|
|
40
|
+
export async function handlerFor(path) {
|
|
41
|
+
const load = owners.get(path);
|
|
42
|
+
return load ? (await load())[path] : undefined;
|
|
43
|
+
}
|
package/dist/land.d.ts
CHANGED
|
@@ -97,7 +97,7 @@ export interface LandDeps {
|
|
|
97
97
|
tasks: Pick<ReturnType<typeof conversationClient>['tasks'], 'get' | 'recordLanding' | 'steer'>;
|
|
98
98
|
conversations: Pick<ReturnType<typeof conversationClient>['conversations'], 'get' | 'land'>;
|
|
99
99
|
};
|
|
100
|
-
hubCell(projectId: string): Promise<{
|
|
100
|
+
hubCell(projectId: string, workspaceIds?: readonly string[]): Promise<{
|
|
101
101
|
cell: TwinCell;
|
|
102
102
|
close(): void;
|
|
103
103
|
}>;
|
package/dist/land.js
CHANGED
|
@@ -127,11 +127,11 @@ function message(o) {
|
|
|
127
127
|
return hub;
|
|
128
128
|
}
|
|
129
129
|
export function landDeps(ctx) {
|
|
130
|
-
return { client: conversationClient(ctx), hubCell: p => projectHubCell(ctx, p, ctx.signal, 'shard-land'), folder: (p, c, r) => linkedFolder(ctx, p, c, r),
|
|
130
|
+
return { client: conversationClient(ctx), hubCell: (p, ids) => projectHubCell(ctx, p, ctx.signal, 'shard-land', ids), folder: (p, c, r) => linkedFolder(ctx, p, c, r),
|
|
131
131
|
device: deviceId(ctx.store.dir), stateDir: ctx.store.dir, signal: ctx.signal };
|
|
132
132
|
}
|
|
133
133
|
async function land(d, l, onConflict) {
|
|
134
|
-
const vm = await d.hubCell(l.projectId);
|
|
134
|
+
const vm = await d.hubCell(l.projectId, l.workspaceIds);
|
|
135
135
|
try {
|
|
136
136
|
const input = { result: l.result, sources: l.sources.map(s => s.branch), title: l.title, ...(d.hub ? { hub: d.hub } : {}) };
|
|
137
137
|
let hub = await landOnHub(vm.cell, input, d.signal);
|
|
@@ -228,7 +228,7 @@ export async function landTask(d, taskId, opts = {}) {
|
|
|
228
228
|
const repository = workers.find(w => w.repo_remote)?.repo_remote ?? (task.master_id ? (await client.conversations.get(task.master_id).catch(() => null))?.repo_remote : undefined) ?? null;
|
|
229
229
|
const folder = opts.path ?? d.folder(task.project_id, undefined, repository ?? undefined);
|
|
230
230
|
const done = await land(d, { projectId: task.project_id, title, name: taskId, result: resultBranch(taskId), sources: workers.map(w => ({ branch: w.branch, conversation_id: w.id })),
|
|
231
|
-
base: workers.find(w => w.base_commit)?.base_commit ?? null, folder, repository }, async (hub) => {
|
|
231
|
+
base: workers.find(w => w.base_commit)?.base_commit ?? null, folder, repository, workspaceIds: workers.map(w => w.workspace_id).filter(Boolean) }, async (hub) => {
|
|
232
232
|
const what = hub.stage === 'integrate' ? `${hub.source} into ${resultBranch(taskId)}` : `${resultBranch(taskId)} into ${hub.into}`;
|
|
233
233
|
await client.tasks.steer(taskId, `Land could not merge ${what}: ${list(hub.conflicts ?? [])} changed on both sides. Merge ${hub.into} from the project hub and every worker branch into ${resultBranch(taskId)}, resolve the conflicts so both changes work (ask the person only if the choice is theirs), run the tests, push ${resultBranch(taskId)} to the hub and seal done again.`, signal);
|
|
234
234
|
});
|
|
@@ -267,9 +267,10 @@ export async function landConversation(d, id, opts = {}) {
|
|
|
267
267
|
const c = await client.conversations.get(id);
|
|
268
268
|
if (c.changes?.uncommitted)
|
|
269
269
|
throw new CodedError('uncommitted_changes', 'The conversation has uncommitted changes.', 'ask its agent to commit them, then land');
|
|
270
|
-
const
|
|
270
|
+
const repository = c.repo_remote ?? null;
|
|
271
|
+
const folder = opts.path ?? d.folder(c.project_id, c.id, repository ?? undefined);
|
|
271
272
|
const done = await land(d, { projectId: c.project_id, title: c.title || c.branch, name: c.name ?? c.branch.replace(/^shard\//, ''), result: c.branch, sources: [],
|
|
272
|
-
base: c.base_commit ?? null, folder }, () => Promise.resolve());
|
|
273
|
+
base: c.base_commit ?? null, folder, repository, workspaceIds: [c.workspace_id] }, () => Promise.resolve());
|
|
273
274
|
let recorded = false, record_error;
|
|
274
275
|
try {
|
|
275
276
|
await client.conversations.land(id, 'merge', { into: done.hub.into, ...(d.signal ? { signal: d.signal } : {}) });
|
package/dist/linux-helper.js
CHANGED
|
@@ -4,7 +4,6 @@ import { readFileSync, realpathSync } from 'node:fs';
|
|
|
4
4
|
import { dirname, join } from 'node:path';
|
|
5
5
|
import { CLI_VERSION } from "./args.js";
|
|
6
6
|
import { writePrivateFile } from "./credentials.js";
|
|
7
|
-
import { runDeviceHelper } from "./device-helper.js";
|
|
8
7
|
export const HELPER_UNIT = 'shardflux-helper.service';
|
|
9
8
|
export const helperUnitPath = (home) => join(home, '.config/systemd/user', HELPER_UNIT);
|
|
10
9
|
const quote = (s) => '"' + s.replaceAll('\\', '\\\\').replaceAll('"', '\\"').replaceAll('%', '%%').replaceAll('\n', '\\n').replaceAll('\r', '\\r') + '"';
|
|
@@ -83,6 +82,6 @@ export function startLinuxHelper(ctx) {
|
|
|
83
82
|
writePrivateFile(ctx.store.dir, notice, '1\n');
|
|
84
83
|
}
|
|
85
84
|
const abort = new AbortController();
|
|
86
|
-
const work = runDeviceHelper({ ...ctx, signal: AbortSignal.any([ctx.signal, abort.signal]) }, { service: true }).catch(() => undefined);
|
|
85
|
+
const work = import("./device-helper.js").then(({ runDeviceHelper }) => runDeviceHelper({ ...ctx, signal: AbortSignal.any([ctx.signal, abort.signal]) }, { service: true })).catch(() => undefined);
|
|
87
86
|
return async () => { abort.abort(); await work; };
|
|
88
87
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Values } from './args.js';
|
|
2
|
+
type Env = Record<string, string | undefined>;
|
|
3
|
+
export declare const SETUP_VM_ENV = "SHARDFLUX_MACHINE_SETUP_VM";
|
|
4
|
+
/**
|
|
5
|
+
* While `SHARDFLUX_MACHINE_SETUP_VM` is set (the setup agent's own commands), shard only runs commands on that VM: exec,
|
|
6
|
+
* file writes from standard input, reads and listings. Returns the refusal, or undefined when the command may run.
|
|
7
|
+
*/
|
|
8
|
+
export declare function setupGuard(env: Env, command: string, values: Values, positionals: string[]): string | undefined;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const SETUP_VM_ENV = 'SHARDFLUX_MACHINE_SETUP_VM';
|
|
2
|
+
/**
|
|
3
|
+
* While `SHARDFLUX_MACHINE_SETUP_VM` is set (the setup agent's own commands), shard only runs commands on that VM: exec,
|
|
4
|
+
* file writes from standard input, reads and listings. Returns the refusal, or undefined when the command may run.
|
|
5
|
+
*/
|
|
6
|
+
export function setupGuard(env, command, values, positionals) {
|
|
7
|
+
const vm = env[SETUP_VM_ENV]?.trim();
|
|
8
|
+
if (!vm)
|
|
9
|
+
return undefined;
|
|
10
|
+
const allowed = ['workspaces exec', 'files write', 'files read', 'files ls', 'files stat', 'version'];
|
|
11
|
+
if (!allowed.includes(command))
|
|
12
|
+
return `machine setup runs only shard ws exec, files write and files read on ${vm}`;
|
|
13
|
+
if (command === 'version')
|
|
14
|
+
return undefined;
|
|
15
|
+
if (positionals[0] !== vm)
|
|
16
|
+
return `machine setup runs commands only on ${vm}`;
|
|
17
|
+
if (command === 'files write' && values.from !== undefined && values.from !== '-')
|
|
18
|
+
return 'machine setup writes files only from standard input (--from -)';
|
|
19
|
+
if (command === 'files read' && values.out !== undefined)
|
|
20
|
+
return 'machine setup reads files only to standard output';
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
package/dist/machine-setup.d.ts
CHANGED
|
@@ -4,8 +4,7 @@ import { AgentStateStore } from './agent-state.js';
|
|
|
4
4
|
import { type Values } from './args.js';
|
|
5
5
|
export type Runner = 'claude' | 'codex' | 'cloud';
|
|
6
6
|
type Env = Record<string, string | undefined>;
|
|
7
|
-
|
|
8
|
-
export declare const SETUP_VM_ENV = "SHARDFLUX_MACHINE_SETUP_VM";
|
|
7
|
+
export { SETUP_VM_ENV, setupGuard } from './machine-setup-guard.js';
|
|
9
8
|
export declare const DEFAULT_AGENT_TIMEOUT_MS: number;
|
|
10
9
|
export interface ChildResult {
|
|
11
10
|
code: number | null;
|
|
@@ -71,11 +70,6 @@ export declare const SECRET_PATHS: readonly ["~/.ssh/**", "~/.aws/**", "~/.azure
|
|
|
71
70
|
export declare function claudeArgs(key: string, help: string): string[];
|
|
72
71
|
/** `codex exec` argv: writes only in `workDir` (and temp folders), network on for `shard`, the task on standard input. */
|
|
73
72
|
export declare function codexArgs(workDir: string, help: string): string[];
|
|
74
|
-
/**
|
|
75
|
-
* While `SHARDFLUX_MACHINE_SETUP_VM` is set (the setup agent's own commands), shard only runs commands on that VM: exec,
|
|
76
|
-
* file writes from standard input, reads and listings. Returns the refusal, or undefined when the command may run.
|
|
77
|
-
*/
|
|
78
|
-
export declare function setupGuard(env: Env, command: string, values: Values, positionals: string[]): string | undefined;
|
|
79
73
|
export interface Inventory {
|
|
80
74
|
/** The home whose files were inventoried; a local agent's login shell can use a different home. */
|
|
81
75
|
source_home?: string;
|
|
@@ -187,4 +181,3 @@ export declare function machineSetup(ctx: Ctx, values: Values, deps?: SetupDeps)
|
|
|
187
181
|
*/
|
|
188
182
|
export declare function startMachineSetupInBackground(ctx: Ctx): boolean;
|
|
189
183
|
export declare const MACHINE_SETUP_HANDLERS: Record<string, Handler>;
|
|
190
|
-
export {};
|
package/dist/machine-setup.js
CHANGED
|
@@ -31,7 +31,8 @@ import { progress as stderrProgress } from "./progress.js";
|
|
|
31
31
|
import { writeProgress } from "./seed-phases.js";
|
|
32
32
|
import { parseDuration } from "./args.js";
|
|
33
33
|
/** Set for the local agent's processes: the `shard` it runs only reaches this machine VM (see `setupGuard`). */
|
|
34
|
-
|
|
34
|
+
import { SETUP_VM_ENV } from "./machine-setup-guard.js";
|
|
35
|
+
export { SETUP_VM_ENV, setupGuard } from "./machine-setup-guard.js";
|
|
35
36
|
export const DEFAULT_AGENT_TIMEOUT_MS = 10 * 60_000;
|
|
36
37
|
const SAVE_TIMEOUT_MS = 10 * 60_000;
|
|
37
38
|
/**
|
|
@@ -228,27 +229,6 @@ export function codexArgs(workDir, help) {
|
|
|
228
229
|
'-',
|
|
229
230
|
];
|
|
230
231
|
}
|
|
231
|
-
/**
|
|
232
|
-
* While `SHARDFLUX_MACHINE_SETUP_VM` is set (the setup agent's own commands), shard only runs commands on that VM: exec,
|
|
233
|
-
* file writes from standard input, reads and listings. Returns the refusal, or undefined when the command may run.
|
|
234
|
-
*/
|
|
235
|
-
export function setupGuard(env, command, values, positionals) {
|
|
236
|
-
const vm = env[SETUP_VM_ENV]?.trim();
|
|
237
|
-
if (!vm)
|
|
238
|
-
return undefined;
|
|
239
|
-
const allowed = ['workspaces exec', 'files write', 'files read', 'files ls', 'files stat', 'version'];
|
|
240
|
-
if (!allowed.includes(command))
|
|
241
|
-
return `machine setup runs only shard ws exec, files write and files read on ${vm}`;
|
|
242
|
-
if (command === 'version')
|
|
243
|
-
return undefined;
|
|
244
|
-
if (positionals[0] !== vm)
|
|
245
|
-
return `machine setup runs commands only on ${vm}`;
|
|
246
|
-
if (command === 'files write' && values.from !== undefined && values.from !== '-')
|
|
247
|
-
return 'machine setup writes files only from standard input (--from -)';
|
|
248
|
-
if (command === 'files read' && values.out !== undefined)
|
|
249
|
-
return 'machine setup reads files only to standard output';
|
|
250
|
-
return undefined;
|
|
251
|
-
}
|
|
252
232
|
function setupCapacity(ws) {
|
|
253
233
|
const disk = ws.grants?.disk_gib;
|
|
254
234
|
if (typeof disk !== 'number' || !Number.isFinite(disk) || disk <= 0)
|
package/dist/main.js
CHANGED
|
@@ -14,27 +14,17 @@ import { homedir, hostname } from 'node:os';
|
|
|
14
14
|
import { statSync } from 'node:fs';
|
|
15
15
|
import { SDK_VERSION, Shardflux, ShardfluxAccount, aboutSeconds, isSessionToken } from '@shardflux/sdk';
|
|
16
16
|
import { CLI_VERSION, CENTER_COMMAND, GUIDE_COMMAND, ONBOARDING_COMMAND, PROJECT_COMMAND, parseCommandLine, resolveApiUrl, resolveWake, resolveWakeTimeout } from "./args.js";
|
|
17
|
-
import { commandCenter } from "./command-center.js";
|
|
18
|
-
import { launchProjectCommand } from "./conversation-terminal.js";
|
|
19
|
-
import { runAcp } from "./acp.js";
|
|
20
17
|
import { registerInstalledT3 } from "./t3-provider.js";
|
|
21
|
-
import {
|
|
22
|
-
import { runAcpLogin } from "./acp-login.js";
|
|
23
|
-
import { terminalOnboarding } from "./terminal-onboarding.js";
|
|
24
|
-
import { codingAgentShell, setupGuideText } from "./agent-setup.js";
|
|
25
|
-
import { agentSetupGuide } from "./account-commands.js";
|
|
26
|
-
import { tryLinuxDesktop } from "./desktop-linux.js";
|
|
18
|
+
import { codingAgentShell } from "./agent-shell.js";
|
|
27
19
|
import { startLinuxHelper } from "./linux-helper.js";
|
|
28
|
-
import {
|
|
29
|
-
import { housekeeping, startupHousekeeping } from "./agent-git-commands.js";
|
|
20
|
+
import { housekeepingIfTracked, startupHousekeeping } from "./startup-housekeeping.js";
|
|
30
21
|
import { CredentialStore, configDir } from "./credentials.js";
|
|
31
22
|
import { recordInstall } from "./update.js";
|
|
32
23
|
import { refreshMenubarInBackground, runMenubarRefresh } from "./menubar.js";
|
|
33
24
|
import { NPM_CLIENTS, npmLatestAll, npmRegistry, withRegistryLatest } from "./registry.js";
|
|
34
25
|
import { AuthConfigError, EXIT, ExecutionPendingError, InterruptedError, UsageError, describeError, exitCodeFor, ownHint, redact } from "./errors.js";
|
|
35
26
|
import { errorText, feedbackCommand, json, time } from "./format.js";
|
|
36
|
-
import {
|
|
37
|
-
import { SETUP_VM_ENV, setupGuard } from "./machine-setup.js";
|
|
27
|
+
import { SETUP_VM_ENV, setupGuard } from "./machine-setup-guard.js";
|
|
38
28
|
import { makeFetch } from "./http.js";
|
|
39
29
|
import { Input } from "./input.js";
|
|
40
30
|
import { TimingRecorder, notices, timingJson, timingText } from "./timing.js";
|
|
@@ -726,6 +716,7 @@ export async function run(argv, io, opts = {}) {
|
|
|
726
716
|
};
|
|
727
717
|
// Bare shard without a terminal (0.17.1+): the setup guide, read from local state only (never waits).
|
|
728
718
|
if (parsed.command === GUIDE_COMMAND) {
|
|
719
|
+
const { setupGuideText, agentSetupGuide } = await import("./agent-setup.js");
|
|
729
720
|
io.stdout.write(setupGuideText(agentSetupGuide(ctx)));
|
|
730
721
|
return EXIT.ok;
|
|
731
722
|
}
|
|
@@ -733,14 +724,24 @@ export async function run(argv, io, opts = {}) {
|
|
|
733
724
|
const refused = setupGuard(io.env, command, parsed.values, parsed.positionals);
|
|
734
725
|
if (refused)
|
|
735
726
|
return report(new UsageError(refused));
|
|
736
|
-
|
|
727
|
+
// Load the selected interactive surface only after help/version and credential parsing. Plain commands do
|
|
728
|
+
// not need the terminal UI, ACP session machinery, device bridge or onboarding just to start.
|
|
729
|
+
const handler = parsed.command.path[0] === 'acp'
|
|
730
|
+
? async (ctx) => parsed.values.login === true ? (await import("./acp-login.js")).runAcpLogin(ctx) : (await import("./acp.js")).runAcp(ctx)
|
|
731
|
+
: parsed.command.path[0] === 'device-helper'
|
|
732
|
+
? async (ctx) => (await import("./device-helper.js")).runDeviceHelper(ctx, { service: parsed.values.service === true })
|
|
733
|
+
: command === 'menubar refresh' ? runMenubarRefresh
|
|
734
|
+
: parsed.command === PROJECT_COMMAND ? (await import("./conversation-terminal.js")).launchProjectCommand
|
|
735
|
+
: parsed.command === ONBOARDING_COMMAND ? async (ctx, values) => await (await import("./desktop-linux.js")).tryLinuxDesktop(ctx) ? 0 : (await import("./terminal-onboarding.js")).terminalOnboarding(ctx, values, { startHelper })
|
|
736
|
+
: parsed.command === CENTER_COMMAND ? (await import("./command-center.js")).commandCenter
|
|
737
|
+
: await (await import("./handler-loader.js")).handlerFor(parsed.command.path.join(' '));
|
|
737
738
|
if (!handler)
|
|
738
739
|
return report(new UsageError(`unknown command "${parsed.command.path.join(' ')}"`));
|
|
739
740
|
// Long-running helpers other programs start (an editor, launchd, the menu bar) skip the per-command housekeeping.
|
|
740
741
|
if (!['acp', 'device-helper'].includes(parsed.command.path[0]) && command !== 'menubar refresh' && command !== 'cli-proxy forward' && !io.env[SETUP_VM_ENV]) {
|
|
741
742
|
// An updated shard restarts this computer's CLIProxyAPI forwarder on itself (a no-op unless the version changed).
|
|
742
|
-
await refreshForwarderAgent(io.env, ctx.store.dir).catch(() => false);
|
|
743
|
-
await
|
|
743
|
+
await import("./forwarder-refresh.js").then(({ refreshForwarderAgent }) => refreshForwarderAgent(io.env, ctx.store.dir)).catch(() => false);
|
|
744
|
+
await housekeepingIfTracked(ctx);
|
|
744
745
|
startHelper();
|
|
745
746
|
}
|
|
746
747
|
const running = handler(ctx, parsed.values, parsed.positionals).then((code) => ({ code }), (err) => ({ err }));
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* a session) reaches the orchestrator of the member who created that key (person-scope.ts). Text output is one line
|
|
7
7
|
* per input, tab-separated, id first; --json prints the API's objects.
|
|
8
8
|
*/
|
|
9
|
-
import { inputSettled
|
|
9
|
+
import { inputSettled } from '@shardflux/sdk';
|
|
10
10
|
import { parseDuration } from "./args.js";
|
|
11
11
|
import { out, str } from "./commands.js";
|
|
12
12
|
import { EXIT, UsageError } from "./errors.js";
|
|
@@ -202,8 +202,6 @@ export const ORCHESTRATOR_HANDLERS = {
|
|
|
202
202
|
const text = words.join(' ');
|
|
203
203
|
if (!text.trim())
|
|
204
204
|
throw new UsageError('missing <words...> for "shard orchestrator say"');
|
|
205
|
-
if (text.length > ORCHESTRATOR_TEXT_MAX)
|
|
206
|
-
throw new UsageError(`your words are ${text.length} characters; one input takes up to ${ORCHESTRATOR_TEXT_MAX}`);
|
|
207
205
|
const selection = selectionOf(values);
|
|
208
206
|
const wait = values.wait === true;
|
|
209
207
|
const timeoutMs = str(values.timeout) === undefined ? SAY_WAIT_MS : parseDuration(str(values.timeout), 'timeout');
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Ctx } from './commands.js';
|
|
2
|
+
/** Help/version starts also clean saved work, without requiring credentials when nothing is due. */
|
|
3
|
+
export declare function startupHousekeeping(ctx: Pick<Ctx, 'io' | 'store' | 'signal'>): Promise<void>;
|
|
4
|
+
/** Keep the command's already selected auth behavior, loading Git machinery only for tracked repositories. */
|
|
5
|
+
export declare function housekeepingIfTracked(ctx: Pick<Ctx, 'io' | 'apiUrl' | 'signal' | 'store' | 'cloud'>): Promise<void>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Shardflux } from '@shardflux/sdk';
|
|
2
|
+
import { AgentStateStore } from "./agent-state.js";
|
|
3
|
+
import { resolveApiUrl } from "./args.js";
|
|
4
|
+
import { UsageError } from "./errors.js";
|
|
5
|
+
/** Help/version starts also clean saved work, without requiring credentials when nothing is due. */
|
|
6
|
+
export async function startupHousekeeping(ctx) {
|
|
7
|
+
try {
|
|
8
|
+
const state = new AgentStateStore(ctx.store.dir).read();
|
|
9
|
+
if (!state.git_repositories || !Object.keys(state.git_repositories).length)
|
|
10
|
+
return;
|
|
11
|
+
const apiUrl = resolveApiUrl(undefined, ctx.io.env.SHARDFLUX_API_URL ?? ctx.io.env.SHARDFLUX_BASE_URL);
|
|
12
|
+
let cloud;
|
|
13
|
+
await (await import("./agent-git-commands.js")).housekeeping({ ...ctx, apiUrl, cloud: () => {
|
|
14
|
+
const key = ctx.io.env.SHARDFLUX_API_KEY?.trim() || ctx.store.profile(apiUrl).api_key?.token;
|
|
15
|
+
if (!key)
|
|
16
|
+
throw new UsageError('saved agents need a project key for housekeeping; run shard setup');
|
|
17
|
+
return cloud ??= new Shardflux({ apiKey: key, baseUrl: apiUrl, versionCheck: false });
|
|
18
|
+
} });
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
ctx.io.stderr.write(`shard housekeeping: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** Keep the command's already selected auth behavior, loading Git machinery only for tracked repositories. */
|
|
25
|
+
export async function housekeepingIfTracked(ctx) {
|
|
26
|
+
const repos = new AgentStateStore(ctx.store.dir).read().git_repositories;
|
|
27
|
+
if (!repos || !Object.keys(repos).length)
|
|
28
|
+
return;
|
|
29
|
+
await (await import("./agent-git-commands.js")).housekeeping(ctx);
|
|
30
|
+
}
|
package/dist/t3-provider.js
CHANGED
|
@@ -3,13 +3,15 @@ import { homedir } from 'node:os';
|
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { writePrivateFile } from "./credentials.js";
|
|
5
5
|
import { withTargetLock } from "./target-lock.js";
|
|
6
|
-
import {
|
|
6
|
+
import { applicationDirs, t3Apps } from "./agent-targets.js";
|
|
7
7
|
import { t3ArchiveSupportsAcp } from "./t3-asar.js";
|
|
8
8
|
const ID = 'shardflux';
|
|
9
9
|
const provider = { driver: 'acpRegistry', displayName: 'Shardflux', enabled: true, config: { source: 'local', commandPath: 'shard', commandArgs: ['acp'] } };
|
|
10
10
|
/** Every shard invocation registers the one installed T3 provider without adding terminal noise (macOS and Linux). */
|
|
11
11
|
export async function registerInstalledT3(env, dir, signal, platform = process.platform) {
|
|
12
|
-
|
|
12
|
+
// Registration only needs T3 presence; probing other apps or choosing a
|
|
13
|
+
// preferred T3 version would inspect unrelated bundles on every command.
|
|
14
|
+
if (!t3Apps(applicationDirs(env, platform), platform, env).length)
|
|
13
15
|
return;
|
|
14
16
|
try {
|
|
15
17
|
await configureT3Provider(env, dir, false, signal);
|
|
@@ -123,7 +123,7 @@ export declare function runTwinLoop(deps: LoopDeps, signal: AbortSignal): Promis
|
|
|
123
123
|
* A VM of the project that reads the project hub: an awake one reads it without waking anything; else the project's
|
|
124
124
|
* small space reader (woken). The twin lands from it when a conversation's VM is gone; `shard task land` merges there.
|
|
125
125
|
*/
|
|
126
|
-
export declare function projectHubCell(ctx: Ctx, projectId: string, signal?: AbortSignal, agentLabel?: string): Promise<{
|
|
126
|
+
export declare function projectHubCell(ctx: Ctx, projectId: string, signal?: AbortSignal, agentLabel?: string, workspaceIds?: readonly string[]): Promise<{
|
|
127
127
|
cell: TwinCell;
|
|
128
128
|
close(): void;
|
|
129
129
|
}>;
|
|
@@ -508,14 +508,17 @@ const openCell = (ws, agentLabel) => {
|
|
|
508
508
|
* A VM of the project that reads the project hub: an awake one reads it without waking anything; else the project's
|
|
509
509
|
* small space reader (woken). The twin lands from it when a conversation's VM is gone; `shard task land` merges there.
|
|
510
510
|
*/
|
|
511
|
-
export async function projectHubCell(ctx, projectId, signal, agentLabel = 'shard-twin') {
|
|
511
|
+
export async function projectHubCell(ctx, projectId, signal, agentLabel = 'shard-twin', workspaceIds) {
|
|
512
512
|
const client = conversationClient(ctx);
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
513
|
+
// A manual landing uses its own result's computer. Borrowing an unrelated thread races that person's deletion:
|
|
514
|
+
// staging 2026-10-17 cut off a real landing when a newer home thread's VM was deleted. If the result's computers
|
|
515
|
+
// are gone or asleep, the persistent space reader owns the operation instead; do not wake a finished agent.
|
|
516
|
+
const ids = workspaceIds ?? (await listConversations(ctx, projectId)).filter(c => !ended(c))
|
|
517
|
+
.sort((a, b) => (b.last_activity_at ?? '').localeCompare(a.last_activity_at ?? '')).map(c => c.workspace_id);
|
|
518
|
+
for (const id of new Set(ids)) {
|
|
516
519
|
try {
|
|
517
|
-
const ws = await client.workspaces.get(
|
|
518
|
-
if (!ws.data.deleted_at && ws.ready)
|
|
520
|
+
const ws = await client.workspaces.get(id);
|
|
521
|
+
if (ws.data.project_id === projectId && !ws.data.deleted_at && ws.ready)
|
|
519
522
|
return openCell(ws, agentLabel);
|
|
520
523
|
}
|
|
521
524
|
catch { /* another may be awake */ }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shardflux/cli",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.2",
|
|
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",
|
|
@@ -31,7 +31,12 @@
|
|
|
31
31
|
"import": "./dist/index.js",
|
|
32
32
|
"default": "./dist/index.js"
|
|
33
33
|
},
|
|
34
|
-
"./bin": "./dist/bin.js"
|
|
34
|
+
"./bin": "./dist/bin.js",
|
|
35
|
+
"./main": {
|
|
36
|
+
"types": "./dist/main.d.ts",
|
|
37
|
+
"import": "./dist/main.js",
|
|
38
|
+
"default": "./dist/main.js"
|
|
39
|
+
}
|
|
35
40
|
},
|
|
36
41
|
"files": [
|
|
37
42
|
"dist",
|
|
@@ -48,7 +53,7 @@
|
|
|
48
53
|
"snappyjs": "0.7.0",
|
|
49
54
|
"ws": "8.22.0",
|
|
50
55
|
"yaml": "2.9.1",
|
|
51
|
-
"@shardflux/sdk": "^0.30.
|
|
56
|
+
"@shardflux/sdk": "^0.30.2"
|
|
52
57
|
},
|
|
53
58
|
"devDependencies": {
|
|
54
59
|
"@eslint/js": "10.0.1",
|
|
@@ -60,10 +65,10 @@
|
|
|
60
65
|
"typescript-eslint": "8.70.1"
|
|
61
66
|
},
|
|
62
67
|
"optionalDependencies": {
|
|
63
|
-
"@shardflux/desktop-darwin-arm64": "0.9.
|
|
64
|
-
"@shardflux/desktop-darwin-x64": "0.9.
|
|
65
|
-
"@shardflux/desktop-linux-arm64": "0.9.
|
|
66
|
-
"@shardflux/desktop-linux-x64": "0.9.
|
|
68
|
+
"@shardflux/desktop-darwin-arm64": "0.9.3",
|
|
69
|
+
"@shardflux/desktop-darwin-x64": "0.9.3",
|
|
70
|
+
"@shardflux/desktop-linux-arm64": "0.9.3",
|
|
71
|
+
"@shardflux/desktop-linux-x64": "0.9.3"
|
|
67
72
|
},
|
|
68
73
|
"scripts": {
|
|
69
74
|
"build": "node scripts/build.mjs",
|