@shardflux/cli 0.25.0 → 0.28.1
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 +154 -1
- package/README.md +212 -36
- package/dist/account-commands.d.ts +2 -4
- package/dist/account-commands.js +2 -6
- package/dist/account-pool.d.ts +11 -1
- package/dist/account-pool.js +48 -0
- package/dist/agent-commands.js +4 -3
- package/dist/agent-git-commands.d.ts +1 -2
- package/dist/agent-git-commands.js +2 -21
- package/dist/agent-models.d.ts +31 -1
- package/dist/agent-models.js +27 -4
- 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 +67 -12
- package/dist/board-args.js +8 -3
- package/dist/board-commands.d.ts +7 -3
- package/dist/board-commands.js +22 -12
- package/dist/brain-commands.d.ts +2 -0
- package/dist/brain-commands.js +78 -0
- package/dist/cli-proxy.d.ts +1 -13
- package/dist/cli-proxy.js +2 -27
- package/dist/client-settings.d.ts +2 -1
- package/dist/client-settings.js +14 -2
- package/dist/codex-cloud.d.ts +2 -0
- package/dist/codex-cloud.js +17 -12
- package/dist/commands.js +8 -0
- package/dist/conversation-commands.d.ts +5 -1
- package/dist/conversation-commands.js +58 -6
- package/dist/conversation-terminal.js +2 -2
- package/dist/desktop-linux.d.ts +12 -1
- package/dist/desktop-linux.js +47 -18
- package/dist/device-approvals.js +24 -2
- package/dist/device-bridge.d.ts +24 -4
- package/dist/device-bridge.js +181 -8
- package/dist/device-folders.d.ts +6 -1
- package/dist/device-folders.js +6 -2
- package/dist/device-helper.d.ts +8 -0
- package/dist/device-helper.js +71 -12
- package/dist/device-mode.d.ts +44 -0
- package/dist/device-mode.js +119 -0
- package/dist/drive-commands.d.ts +2 -2
- package/dist/drive-commands.js +1 -1
- 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/handlers.js +4 -2
- package/dist/land.d.ts +10 -5
- package/dist/land.js +56 -9
- package/dist/linux-helper.js +1 -2
- package/dist/mac-app.d.ts +11 -0
- package/dist/mac-app.js +11 -0
- 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 +19 -16
- package/dist/menubar.d.ts +18 -13
- package/dist/menubar.js +60 -50
- package/dist/orchestrator-args.d.ts +7 -0
- package/dist/orchestrator-args.js +64 -0
- package/dist/orchestrator-commands.d.ts +43 -0
- package/dist/orchestrator-commands.js +260 -0
- package/dist/person-scope.d.ts +20 -0
- package/dist/person-scope.js +61 -0
- package/dist/project-hub-seed.d.ts +20 -0
- package/dist/project-hub-seed.js +59 -0
- package/dist/registry.d.ts +1 -1
- package/dist/registry.js +2 -1
- package/dist/router-commands.d.ts +12 -21
- package/dist/router-commands.js +5 -59
- package/dist/startup-housekeeping.d.ts +5 -0
- package/dist/startup-housekeeping.js +30 -0
- package/dist/t3-launch.js +2 -2
- package/dist/t3-provider.js +4 -2
- package/dist/task-land-hub.py +16 -1
- package/dist/this-computer.d.ts +4 -2
- package/dist/this-computer.js +20 -2
- package/dist/thread-on-computer.js +2 -2
- package/dist/timing.js +3 -1
- package/dist/transcribe-commands.js +6 -2
- package/dist/update.js +4 -2
- package/dist/wait-status.d.ts +1 -1
- package/dist/wait-status.js +2 -0
- package/dist/worktree-twin-engine.d.ts +1 -1
- package/dist/worktree-twin-engine.js +10 -7
- package/dist/worktree-twin.d.ts +6 -0
- package/dist/worktree-twin.js +8 -1
- package/package.json +12 -6
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How this computer answers its own agents (0.27.0+): the person chooses once, per device, whether their agents run
|
|
3
|
+
* things here without asking each time. Kept in `<config>/device-settings.json`; changed in the app's Settings or with
|
|
4
|
+
* `shard device approvals auto|read|ask`. Requests that ran without asking are kept in `<config>/device-activity.jsonl`
|
|
5
|
+
* (what ran, for which conversation, and how it ended), shown by the app and `shard device approvals`.
|
|
6
|
+
*
|
|
7
|
+
* - `ask`: every request asks (Allow once, or Always where a grant applies).
|
|
8
|
+
* - `read`: listing a folder and copying a file to a conversation run without asking; anything that changes this
|
|
9
|
+
* computer (commands, files saved here, ports, linking a folder) asks. Commands are never classified by their text.
|
|
10
|
+
* - `auto`: every request from this person's own agents runs without asking.
|
|
11
|
+
*
|
|
12
|
+
* Only this person's own conversations run without asking (`ownConversation`): a teammate's request always asks.
|
|
13
|
+
*/
|
|
14
|
+
import { appendFileSync, lstatSync, readFileSync, statSync } from 'node:fs';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import { writePrivateFile, ensurePrivateDir } from "./credentials.js";
|
|
17
|
+
import { UsageError } from "./errors.js";
|
|
18
|
+
import { thisComputer } from "./this-computer.js";
|
|
19
|
+
export const DEVICE_APPROVAL_MODES = ['auto', 'read', 'ask'];
|
|
20
|
+
/**
|
|
21
|
+
* The mode before the person chooses one. `undefined`: nothing is decided for them; the first request asks which they
|
|
22
|
+
* want (the app's prompt, or one line from `shard`), and the app's onboarding asks too. 'auto' or 'ask' here chooses
|
|
23
|
+
* for everyone who has not.
|
|
24
|
+
*/
|
|
25
|
+
export const DEFAULT_DEVICE_APPROVALS = undefined;
|
|
26
|
+
/** The kinds that only read this computer: a folder listing, and a file copied to the conversation. */
|
|
27
|
+
export const READ_KINDS = new Set(['dir_list', 'files_pull']);
|
|
28
|
+
export const MODE_LABELS = {
|
|
29
|
+
auto: 'Allow everything from my agents',
|
|
30
|
+
read: 'Allow reading, ask before changes',
|
|
31
|
+
ask: 'Ask every time',
|
|
32
|
+
};
|
|
33
|
+
/** The one line a person without the app reads when a request needs the choice ("this Mac" on macOS). */
|
|
34
|
+
export const chooseLine = (platform = process.platform) => `Let your agents run things on ${thisComputer(platform)} without asking each time? Allow everything: shard device approvals auto · Ask each time: shard device approvals ask`;
|
|
35
|
+
const SETTINGS = 'device-settings.json', ACTIVITY = 'device-activity.jsonl';
|
|
36
|
+
export function readDeviceSettings(dir) {
|
|
37
|
+
const file = join(dir, SETTINGS);
|
|
38
|
+
try {
|
|
39
|
+
if (lstatSync(file).isSymbolicLink() || (statSync(file).mode & 0o077))
|
|
40
|
+
return {};
|
|
41
|
+
const value = JSON.parse(readFileSync(file, 'utf8'));
|
|
42
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
43
|
+
return {};
|
|
44
|
+
return DEVICE_APPROVAL_MODES.includes(value.approvals) ? value : { ...value, approvals: undefined };
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return {};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** This person's mode on this computer; undefined until they choose one (and DEFAULT_DEVICE_APPROVALS is unset). */
|
|
51
|
+
export function deviceApprovalMode(dir, userId) {
|
|
52
|
+
const s = readDeviceSettings(dir);
|
|
53
|
+
if (s.approvals && !(s.user_id && userId && s.user_id !== userId))
|
|
54
|
+
return s.approvals;
|
|
55
|
+
return DEFAULT_DEVICE_APPROVALS;
|
|
56
|
+
}
|
|
57
|
+
export function setDeviceApprovalMode(dir, mode, userId) {
|
|
58
|
+
if (!DEVICE_APPROVAL_MODES.includes(mode))
|
|
59
|
+
throw new UsageError(`Unknown approval mode "${mode}": auto, read or ask.`);
|
|
60
|
+
const next = { ...readDeviceSettings(dir), approvals: mode, chosen_at: new Date().toISOString() };
|
|
61
|
+
if (userId)
|
|
62
|
+
next.user_id = userId;
|
|
63
|
+
else
|
|
64
|
+
delete next.user_id;
|
|
65
|
+
writePrivateFile(dir, join(dir, SETTINGS), JSON.stringify(next, null, 2) + '\n');
|
|
66
|
+
return next;
|
|
67
|
+
}
|
|
68
|
+
/** Whether a request of this kind runs without asking under this mode (for the person's own agents). */
|
|
69
|
+
export function runsWithoutAsking(mode, kind) {
|
|
70
|
+
return mode === 'auto' || (mode === 'read' && READ_KINDS.has(kind));
|
|
71
|
+
}
|
|
72
|
+
const ACTIVITY_MAX_BYTES = 512 << 10, ACTIVITY_KEEP = 400;
|
|
73
|
+
export function recordDeviceActivity(dir, entry) {
|
|
74
|
+
const file = join(dir, ACTIVITY);
|
|
75
|
+
try {
|
|
76
|
+
ensurePrivateDir(dir);
|
|
77
|
+
let size = 0;
|
|
78
|
+
try {
|
|
79
|
+
size = statSync(file).size;
|
|
80
|
+
}
|
|
81
|
+
catch { /* first entry */ }
|
|
82
|
+
if (size > ACTIVITY_MAX_BYTES) {
|
|
83
|
+
const keep = readFileSync(file, 'utf8').split('\n').filter(Boolean).slice(-ACTIVITY_KEEP);
|
|
84
|
+
writePrivateFile(dir, file, keep.join('\n') + '\n');
|
|
85
|
+
}
|
|
86
|
+
appendFileSync(file, JSON.stringify(entry) + '\n', { mode: 0o600 });
|
|
87
|
+
}
|
|
88
|
+
catch { /* the request's own result still reaches its conversation */ }
|
|
89
|
+
}
|
|
90
|
+
/** What ran without asking, newest first: one entry per request with its latest state. */
|
|
91
|
+
export function deviceActivity(dir, limit = 20) {
|
|
92
|
+
let text;
|
|
93
|
+
try {
|
|
94
|
+
if (lstatSync(join(dir, ACTIVITY)).isSymbolicLink())
|
|
95
|
+
return [];
|
|
96
|
+
text = readFileSync(join(dir, ACTIVITY), 'utf8');
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
const byId = new Map();
|
|
102
|
+
for (const line of text.split('\n')) {
|
|
103
|
+
if (!line)
|
|
104
|
+
continue;
|
|
105
|
+
let e;
|
|
106
|
+
try {
|
|
107
|
+
e = JSON.parse(line);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (!e || typeof e.request_id !== 'string')
|
|
113
|
+
continue;
|
|
114
|
+
const had = byId.get(e.request_id);
|
|
115
|
+
byId.delete(e.request_id);
|
|
116
|
+
byId.set(e.request_id, had ? { ...had, ...e, at: had.at, ...(e.state !== 'running' ? { finished_at: e.at } : {}) } : e);
|
|
117
|
+
}
|
|
118
|
+
return [...byId.values()].reverse().slice(0, limit);
|
|
119
|
+
}
|
package/dist/drive-commands.d.ts
CHANGED
|
@@ -14,8 +14,8 @@ export interface ProjectScope {
|
|
|
14
14
|
* `needId` false leaves the key's project unnamed (the API defaults to it), saving a lookup.
|
|
15
15
|
*/
|
|
16
16
|
export declare function projectScope(ctx: Ctx, values: Values, needId?: boolean): Promise<ProjectScope>;
|
|
17
|
-
/** `drive ls` rows: size, saved time, type and path (the path last, as `files ls` puts the name). */
|
|
18
|
-
export declare function driveTable(rows: readonly DriveFile[]): string;
|
|
17
|
+
/** `drive ls` and `brain ls` rows: size, saved time, type and path (the path last, as `files ls` puts the name). */
|
|
18
|
+
export declare function driveTable(rows: readonly Pick<DriveFile, 'size' | 'created_at' | 'content_type' | 'path'>[]): string;
|
|
19
19
|
/**
|
|
20
20
|
* The model template as people read it: the primary provider and why, the connected accounts, then one row per role
|
|
21
21
|
* with what it runs and, for a role whose provider is out of usage, until when it waits and what the other provider
|
package/dist/drive-commands.js
CHANGED
|
@@ -51,7 +51,7 @@ function drivePath(path, what = 'drive path') {
|
|
|
51
51
|
}
|
|
52
52
|
return path;
|
|
53
53
|
}
|
|
54
|
-
/** `drive ls` rows: size, saved time, type and path (the path last, as `files ls` puts the name). */
|
|
54
|
+
/** `drive ls` and `brain ls` rows: size, saved time, type and path (the path last, as `files ls` puts the name). */
|
|
55
55
|
export function driveTable(rows) {
|
|
56
56
|
return table(['SIZE', 'SAVED', 'TYPE', 'PATH'], rows.map((f) => [humanBytes(f.size), time(f.created_at), f.content_type, f.path]));
|
|
57
57
|
}
|
package/dist/format.js
CHANGED
|
@@ -83,6 +83,8 @@ export function workspaceState(w) {
|
|
|
83
83
|
tags.push('session');
|
|
84
84
|
if (w.deleted_at)
|
|
85
85
|
tags.push(w.ended_reason ? `ended: ${w.ended_reason}` : 'deleted');
|
|
86
|
+
if (w.storage_recovery)
|
|
87
|
+
tags.push('files stored');
|
|
86
88
|
return tags.length ? `${w.observed_state} (${tags.join(', ')})` : w.observed_state;
|
|
87
89
|
}
|
|
88
90
|
export function workspaceRow(w) {
|
|
@@ -230,6 +232,7 @@ export function workspaceDetail(w) {
|
|
|
230
232
|
['id', w.id],
|
|
231
233
|
['key', w.workspace_key],
|
|
232
234
|
['state', `${w.observed_state} (desired ${w.desired_state})`],
|
|
235
|
+
...(w.storage_recovery ? [['storage', w.storage_recovery.message]] : []),
|
|
233
236
|
['ready', isReady(w) ? 'yes' : 'no'],
|
|
234
237
|
['template', `${w.template.slug} v${w.template.version} (${w.template.version_id})`],
|
|
235
238
|
// Immutable paths (0.8.0): the template version whose immutable paths the workspace has mounted, when it has one.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ProgramRunner } from './cli-proxy.js';
|
|
2
|
+
export declare const FORWARD_LABEL = "dev.shardflux.cli-proxy-forward";
|
|
3
|
+
export declare function forwarderPlistPath(home: string): string;
|
|
4
|
+
/** The shard version the launchd forwarder runs (refreshForwarderAgent compares it with this one). */
|
|
5
|
+
export declare function recordForwarder(dir: string): void;
|
|
6
|
+
/**
|
|
7
|
+
* After `shard` is updated, the launchd forwarder still runs the program it started with. Called on every shard run
|
|
8
|
+
* (one file read on macOS): an installed forwarder agent recorded for another shard version is started again, on this
|
|
9
|
+
* one. Returns whether it was.
|
|
10
|
+
*/
|
|
11
|
+
export declare function refreshForwarderAgent(env: Record<string, string | undefined>, dir: string, deps?: {
|
|
12
|
+
run?: ProgramRunner;
|
|
13
|
+
uid?: number;
|
|
14
|
+
platform?: NodeJS.Platform;
|
|
15
|
+
}): Promise<boolean>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { homedir, userInfo } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { AgentStateStore } from "./agent-state.js";
|
|
5
|
+
import { CLI_VERSION } from "./args.js";
|
|
6
|
+
export const FORWARD_LABEL = 'dev.shardflux.cli-proxy-forward';
|
|
7
|
+
export function forwarderPlistPath(home) {
|
|
8
|
+
return join(home, 'Library', 'LaunchAgents', `${FORWARD_LABEL}.plist`);
|
|
9
|
+
}
|
|
10
|
+
/** The shard version the launchd forwarder runs (refreshForwarderAgent compares it with this one). */
|
|
11
|
+
export function recordForwarder(dir) {
|
|
12
|
+
new AgentStateStore(dir).update((doc) => { doc.cli_proxy_forwarder = { version: CLI_VERSION }; });
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* After `shard` is updated, the launchd forwarder still runs the program it started with. Called on every shard run
|
|
16
|
+
* (one file read on macOS): an installed forwarder agent recorded for another shard version is started again, on this
|
|
17
|
+
* one. Returns whether it was.
|
|
18
|
+
*/
|
|
19
|
+
export async function refreshForwarderAgent(env, dir, deps = {}) {
|
|
20
|
+
if ((deps.platform ?? process.platform) !== 'darwin')
|
|
21
|
+
return false;
|
|
22
|
+
if (!existsSync(forwarderPlistPath(env.HOME || homedir())))
|
|
23
|
+
return false;
|
|
24
|
+
const store = new AgentStateStore(dir);
|
|
25
|
+
const record = store.read().cli_proxy_forwarder;
|
|
26
|
+
if (record?.version === CLI_VERSION)
|
|
27
|
+
return false;
|
|
28
|
+
// Recorded first: a failed restart is not retried on every command (the next import installs the agent again).
|
|
29
|
+
recordForwarder(dir);
|
|
30
|
+
await (deps.run ?? (await import("./cli-proxy.js")).cliProxyTools.run)('launchctl', ['kickstart', '-k', `gui/${deps.uid ?? userInfo().uid}/${FORWARD_LABEL}`]);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
@@ -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/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 { ORCHESTRATOR_HANDLERS } from "./orchestrator-commands.js";
|
|
8
9
|
import { TRANSCRIBE_HANDLERS } from "./transcribe-commands.js";
|
|
9
10
|
/**
|
|
10
11
|
* Every command's handler, keyed by its path ("workspaces open", "auth login"): the project-key commands of
|
|
@@ -25,11 +26,12 @@ import { TWIN_HANDLERS } from "./worktree-twin-engine.js";
|
|
|
25
26
|
import { CODEX_CLOUD_HANDLERS } from "./codex-cloud.js";
|
|
26
27
|
import { THREAD_HANDLERS } from "./thread-on-computer.js";
|
|
27
28
|
import { CLI_PROXY_HANDLERS } from "./cli-proxy.js";
|
|
28
|
-
import { ACCOUNT_POOL_HANDLERS } from "./account-pool.js";
|
|
29
|
+
import { ACCOUNT_POOL_HANDLERS, AGENT_MODELS_HANDLERS } from "./account-pool.js";
|
|
29
30
|
import { MOVE_HANDLERS } from "./move.js";
|
|
30
31
|
import { AGENT_CONFIG_HANDLERS } from "./agent-config-sync.js";
|
|
31
32
|
import { MACHINE_SETUP_HANDLERS } from "./machine-setup.js";
|
|
32
33
|
import { SETTINGS_HANDLERS } from "./client-settings.js";
|
|
33
34
|
import { PROJECT_PREPARATION_HANDLERS } from "./project-preparation.js";
|
|
34
35
|
import { DRIVE_HANDLERS } from "./drive-commands.js";
|
|
35
|
-
|
|
36
|
+
import { BRAIN_HANDLERS } from "./brain-commands.js";
|
|
37
|
+
export const HANDLERS = { ...PROJECT_PREPARATION_HANDLERS, ...PROJECT_LINK_HANDLERS, ...MOVE_HANDLERS, ...DEVICE_HANDLERS, ...CODEX_CLOUD_HANDLERS, ...CLI_PROXY_HANDLERS, ...THREAD_HANDLERS, ...CONVERSATION_HANDLERS, ...ORCHESTRATOR_HANDLERS, ...BOARD_HANDLERS, ...TRANSCRIPT_HANDLERS, ...LOCAL_CONVERSATION_HANDLERS, ...TWIN_HANDLERS, ...CREDENTIAL_HANDLERS, ...AGENT_CREDENTIAL_HANDLERS, ...ACCOUNT_POOL_HANDLERS, ...AGENT_MODELS_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, ...BRAIN_HANDLERS };
|
package/dist/land.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { TwinCell } from './worktree-twin.js';
|
|
|
4
4
|
/** The hub side of a landing, shipped next to this module (src in the monorepo, dist when published). */
|
|
5
5
|
export declare function hubScript(): string;
|
|
6
6
|
export interface HubLanding {
|
|
7
|
-
state: 'landed' | 'conflict' | 'missing' | 'raced' | 'error';
|
|
7
|
+
state: 'landed' | 'conflict' | 'unrelated' | 'missing' | 'raced' | 'error';
|
|
8
8
|
into: string;
|
|
9
9
|
before?: string | null;
|
|
10
10
|
after?: string;
|
|
@@ -61,8 +61,13 @@ export declare function folderBranch(folder: string, fallback: string): Promise<
|
|
|
61
61
|
* edits stay, and the landed commit is checked to be on the branch.
|
|
62
62
|
*/
|
|
63
63
|
export declare function landInFolder(cell: TwinCell, f: FolderInput): Promise<FolderLanding>;
|
|
64
|
-
/** The
|
|
65
|
-
export declare function
|
|
64
|
+
/** The repository a folder holds, as conversations started from it name it (its origin, else the folder itself). */
|
|
65
|
+
export declare function folderRepository(folder: string): string | null;
|
|
66
|
+
/**
|
|
67
|
+
* The person's folder for a project on this computer: a linked folder (shard projects link) or where it was started.
|
|
68
|
+
* With `repository` (a project holds every folder its people linked), only a folder of that repository.
|
|
69
|
+
*/
|
|
70
|
+
export declare function linkedFolder(ctx: Pick<Ctx, 'store' | 'apiUrl'>, projectId: string, conversationId?: string, repository?: string): string | null;
|
|
66
71
|
export declare const displayPath: (path: string) => string;
|
|
67
72
|
export interface LandOutcome {
|
|
68
73
|
landed: true;
|
|
@@ -92,11 +97,11 @@ export interface LandDeps {
|
|
|
92
97
|
tasks: Pick<ReturnType<typeof conversationClient>['tasks'], 'get' | 'recordLanding' | 'steer'>;
|
|
93
98
|
conversations: Pick<ReturnType<typeof conversationClient>['conversations'], 'get' | 'land'>;
|
|
94
99
|
};
|
|
95
|
-
hubCell(projectId: string): Promise<{
|
|
100
|
+
hubCell(projectId: string, workspaceIds?: readonly string[]): Promise<{
|
|
96
101
|
cell: TwinCell;
|
|
97
102
|
close(): void;
|
|
98
103
|
}>;
|
|
99
|
-
folder(projectId: string, conversationId?: string): string | null;
|
|
104
|
+
folder(projectId: string, conversationId?: string, repository?: string): string | null;
|
|
100
105
|
device: string;
|
|
101
106
|
stateDir: string;
|
|
102
107
|
hub?: string;
|
package/dist/land.js
CHANGED
|
@@ -21,6 +21,7 @@ import { conversationClient } from "./conversation-commands.js";
|
|
|
21
21
|
import { localConversations } from "./conversations.js";
|
|
22
22
|
import { CodedError } from "./errors.js";
|
|
23
23
|
import { selectedProjects } from "./project-preparation.js";
|
|
24
|
+
import { projectIdentity } from "./project-identity.js";
|
|
24
25
|
import { HUB_PATH, backupRoot, deviceId, git, gitText, inspectWorktree, landingRound, listWorktrees } from "./worktree-twin.js";
|
|
25
26
|
import { projectHubCell } from "./worktree-twin-engine.js";
|
|
26
27
|
let script;
|
|
@@ -84,9 +85,22 @@ export async function landInFolder(cell, f) {
|
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
}
|
|
87
|
-
/** The
|
|
88
|
-
export function
|
|
89
|
-
|
|
88
|
+
/** The repository a folder holds, as conversations started from it name it (its origin, else the folder itself). */
|
|
89
|
+
export function folderRepository(folder) {
|
|
90
|
+
try {
|
|
91
|
+
const id = projectIdentity(folder);
|
|
92
|
+
return id.remote ?? `file://${id.folder}`;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The person's folder for a project on this computer: a linked folder (shard projects link) or where it was started.
|
|
100
|
+
* With `repository` (a project holds every folder its people linked), only a folder of that repository.
|
|
101
|
+
*/
|
|
102
|
+
export function linkedFolder(ctx, projectId, conversationId, repository) {
|
|
103
|
+
const repo = (folder) => folder && existsSync(join(folder, '.git')) && (!repository || folderRepository(folder) === repository) ? folder : null;
|
|
90
104
|
const records = localConversations(ctx);
|
|
91
105
|
const own = conversationId ? repo(records[conversationId]?.folder) : null;
|
|
92
106
|
if (own)
|
|
@@ -113,11 +127,11 @@ function message(o) {
|
|
|
113
127
|
return hub;
|
|
114
128
|
}
|
|
115
129
|
export function landDeps(ctx) {
|
|
116
|
-
return { client: conversationClient(ctx), hubCell: p => projectHubCell(ctx, p, ctx.signal, 'shard-land'), folder: (p, c) => linkedFolder(ctx, p, c),
|
|
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),
|
|
117
131
|
device: deviceId(ctx.store.dir), stateDir: ctx.store.dir, signal: ctx.signal };
|
|
118
132
|
}
|
|
119
133
|
async function land(d, l, onConflict) {
|
|
120
|
-
const vm = await d.hubCell(l.projectId);
|
|
134
|
+
const vm = await d.hubCell(l.projectId, l.workspaceIds);
|
|
121
135
|
try {
|
|
122
136
|
const input = { result: l.result, sources: l.sources.map(s => s.branch), title: l.title, ...(d.hub ? { hub: d.hub } : {}) };
|
|
123
137
|
let hub = await landOnHub(vm.cell, input, d.signal);
|
|
@@ -127,6 +141,19 @@ async function land(d, l, onConflict) {
|
|
|
127
141
|
await onConflict(hub);
|
|
128
142
|
throw new CodedError('land_conflict', `Not landed: ${list(hub.conflicts ?? [])} changed on both sides (${hub.stage === 'integrate' ? `${hub.source} and ${l.result}` : `${l.result} and ${hub.into}`}).`, 'its master merges them and asks to land again', { stage: hub.stage, into: hub.into, conflicts: hub.conflicts ?? [] });
|
|
129
143
|
}
|
|
144
|
+
// The hub's default branch is another repository's (a project holds every folder its people linked): a result
|
|
145
|
+
// whose repository has its folder here lands in that folder, and the hub's default branch stays as it is.
|
|
146
|
+
// Awaited here: the computer that reads the hub closes in `finally` (staging 2026-10-17: returned unawaited, the
|
|
147
|
+
// landing was cut off mid-way, "Interrupted.").
|
|
148
|
+
if (hub.state === 'unrelated' && hub.stage === 'default' && l.repository && l.folder && hub.result)
|
|
149
|
+
return await ownFolder(d, vm.cell, l, hub);
|
|
150
|
+
// No history in common (started from another repository than the branch it lands in): it never clears by trying
|
|
151
|
+
// again, so it says what to do instead.
|
|
152
|
+
if (hub.state === 'unrelated') {
|
|
153
|
+
throw new CodedError('land_unrelated', hub.stage === 'integrate'
|
|
154
|
+
? `Not landed: ${hub.source} and ${l.result} have no history in common; they were started from different repositories.`
|
|
155
|
+
: `Not landed: ${l.result} has no history in common with ${hub.into} on the project hub; it was started from another repository than the one ${hub.into} holds.`, 'land it from the folder of the repository it was started from, or start the task again from this project’s own folder', { stage: hub.stage, into: hub.into });
|
|
156
|
+
}
|
|
130
157
|
if (hub.state === 'missing')
|
|
131
158
|
throw new CodedError('land_nothing', `Nothing to land: the project hub has no ${l.result} yet.`, 'land after the work is done', { branch: l.result });
|
|
132
159
|
if (hub.state !== 'landed')
|
|
@@ -149,6 +176,23 @@ async function land(d, l, onConflict) {
|
|
|
149
176
|
vm.close();
|
|
150
177
|
}
|
|
151
178
|
}
|
|
179
|
+
/** A result into the folder of its own repository only (the hub's default branch holds another repository). */
|
|
180
|
+
async function ownFolder(d, cell, l, hub) {
|
|
181
|
+
const folder = l.folder, into = await folderBranch(folder, 'main');
|
|
182
|
+
const contained = l.sources.filter(s => hub.contained?.includes(s.branch)).map(s => s.conversation_id);
|
|
183
|
+
if (!into)
|
|
184
|
+
return { title: l.title, result: { branch: l.result, sha: hub.result }, hub: { into: '', sha: hub.result, method: 'folder' }, contained,
|
|
185
|
+
folder: { path: folder, display: displayPath(folder), into: '', state: 'skipped', reason: 'detached' } };
|
|
186
|
+
const f = await landInFolder(cell, { folder, into, name: l.name, title: l.title, branch: l.result, base: l.base ?? hub.result, device: d.device, stateDir: d.stateDir,
|
|
187
|
+
...(d.hub ? { hub: d.hub } : {}), ...(d.quietMs !== undefined ? { quietMs: d.quietMs } : {}), ...(d.signal ? { signal: d.signal } : {}) });
|
|
188
|
+
if (f.state !== 'landed' && f.state !== 'already') {
|
|
189
|
+
// Nothing else holds it: a folder that could not take it yet is not a landing.
|
|
190
|
+
throw new CodedError('land_failed', f.state === 'conflict'
|
|
191
|
+
? `Not landed yet: ${list(f.conflicts ?? [])} changed in ${displayPath(folder)} too since ${l.result} started.`
|
|
192
|
+
: `Not landed yet: ${displayPath(folder)} is busy (${f.reason ?? 'busy'}).`, f.state === 'conflict' ? 'commit or put aside those changes, and it lands' : 'it lands once the folder is quiet', { into, folder: displayPath(folder) });
|
|
193
|
+
}
|
|
194
|
+
return { title: l.title, result: { branch: l.result, sha: hub.result }, hub: { into, sha: f.sha ?? hub.result, method: 'folder' }, folder: { ...f, display: displayPath(f.path) }, contained };
|
|
195
|
+
}
|
|
152
196
|
/** What a landing did, for people: the line, then what was not recorded. */
|
|
153
197
|
export function landedText(o) {
|
|
154
198
|
return `${o.message}\n${o.recorded ? '' : `Not recorded on the board: ${o.record_error ?? 'unknown'}.\n`}`;
|
|
@@ -180,9 +224,11 @@ export async function landTask(d, taskId, opts = {}) {
|
|
|
180
224
|
if (w.join_mode !== 'reviewer' && (w.changes?.commits ?? 0) > 0 && w.landed?.how !== 'discarded')
|
|
181
225
|
workers.push(w);
|
|
182
226
|
}
|
|
183
|
-
|
|
227
|
+
// The repository the result is of: its workers' (or its master's), so it lands only in a folder of that repository.
|
|
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
|
+
const folder = opts.path ?? d.folder(task.project_id, undefined, repository ?? undefined);
|
|
184
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 })),
|
|
185
|
-
base: workers.find(w => w.base_commit)?.base_commit ?? null, folder }, 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) => {
|
|
186
232
|
const what = hub.stage === 'integrate' ? `${hub.source} into ${resultBranch(taskId)}` : `${resultBranch(taskId)} into ${hub.into}`;
|
|
187
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);
|
|
188
234
|
});
|
|
@@ -221,9 +267,10 @@ export async function landConversation(d, id, opts = {}) {
|
|
|
221
267
|
const c = await client.conversations.get(id);
|
|
222
268
|
if (c.changes?.uncommitted)
|
|
223
269
|
throw new CodedError('uncommitted_changes', 'The conversation has uncommitted changes.', 'ask its agent to commit them, then land');
|
|
224
|
-
const
|
|
270
|
+
const repository = c.repo_remote ?? null;
|
|
271
|
+
const folder = opts.path ?? d.folder(c.project_id, c.id, repository ?? undefined);
|
|
225
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: [],
|
|
226
|
-
base: c.base_commit ?? null, folder }, () => Promise.resolve());
|
|
273
|
+
base: c.base_commit ?? null, folder, repository, workspaceIds: [c.workspace_id] }, () => Promise.resolve());
|
|
227
274
|
let recorded = false, record_error;
|
|
228
275
|
try {
|
|
229
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,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which macOS app this CLI installs and updates. On (0.27.0+): the one Shardflux app from
|
|
3
|
+
* @shardflux/desktop-darwin-<arch>, which replaces the menu bar app (@shardflux/menubar-darwin) in place. The CLI's
|
|
4
|
+
* optionalDependencies carry the same packages (test/one-app.test.ts keeps the two in step): with the desktop package
|
|
5
|
+
* in the install, `shard`, its refresh and the running menu bar app's self-update replace the menu bar app with it.
|
|
6
|
+
*/
|
|
7
|
+
export declare const ONE_APP_ON_MAC = true;
|
|
8
|
+
/** The npm package that carries the one Shardflux app for this Mac's CPU (it replaces @shardflux/menubar-darwin). */
|
|
9
|
+
export declare const desktopDarwinPackage: (arch?: string) => string;
|
|
10
|
+
/** The npm package that carries the macOS app this CLI installs and updates (see ONE_APP_ON_MAC). */
|
|
11
|
+
export declare const macAppPackage: (arch?: string) => string;
|
package/dist/mac-app.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which macOS app this CLI installs and updates. On (0.27.0+): the one Shardflux app from
|
|
3
|
+
* @shardflux/desktop-darwin-<arch>, which replaces the menu bar app (@shardflux/menubar-darwin) in place. The CLI's
|
|
4
|
+
* optionalDependencies carry the same packages (test/one-app.test.ts keeps the two in step): with the desktop package
|
|
5
|
+
* in the install, `shard`, its refresh and the running menu bar app's self-update replace the menu bar app with it.
|
|
6
|
+
*/
|
|
7
|
+
export const ONE_APP_ON_MAC = true;
|
|
8
|
+
/** The npm package that carries the one Shardflux app for this Mac's CPU (it replaces @shardflux/menubar-darwin). */
|
|
9
|
+
export const desktopDarwinPackage = (arch = process.arch) => `@shardflux/desktop-darwin-${arch === 'x64' ? 'x64' : 'arm64'}`;
|
|
10
|
+
/** The npm package that carries the macOS app this CLI installs and updates (see ONE_APP_ON_MAC). */
|
|
11
|
+
export const macAppPackage = (arch = process.arch) => ONE_APP_ON_MAC ? desktopDarwinPackage(arch) : '@shardflux/menubar-darwin';
|
|
@@ -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)
|