@shardflux/cli 0.19.1 → 0.22.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 (115) hide show
  1. package/CHANGELOG.md +278 -1
  2. package/README.md +225 -73
  3. package/dist/account-args.js +7 -2
  4. package/dist/account-commands.js +56 -106
  5. package/dist/account-pool.d.ts +20 -15
  6. package/dist/account-pool.js +51 -97
  7. package/dist/account-scope.d.ts +65 -0
  8. package/dist/account-scope.js +246 -0
  9. package/dist/acp-login.d.ts +4 -3
  10. package/dist/acp-login.js +7 -5
  11. package/dist/acp-models.js +20 -15
  12. package/dist/acp.js +66 -13
  13. package/dist/agent-commands.d.ts +4 -1
  14. package/dist/agent-commands.js +86 -32
  15. package/dist/agent-config-files.d.ts +19 -0
  16. package/dist/agent-config-files.js +70 -1
  17. package/dist/agent-config-return.d.ts +194 -0
  18. package/dist/agent-config-return.js +724 -0
  19. package/dist/agent-config-sync.d.ts +10 -2
  20. package/dist/agent-config-sync.js +45 -51
  21. package/dist/agent-credentials.js +7 -3
  22. package/dist/agent-repository.d.ts +16 -3
  23. package/dist/agent-repository.js +149 -74
  24. package/dist/agent-setup.d.ts +14 -3
  25. package/dist/agent-setup.js +78 -36
  26. package/dist/agent-target-commands.js +24 -13
  27. package/dist/args.d.ts +1 -1
  28. package/dist/args.js +104 -55
  29. package/dist/bin.js +5 -1
  30. package/dist/browser.d.ts +13 -2
  31. package/dist/browser.js +30 -14
  32. package/dist/claude-cloud-login.d.ts +24 -2
  33. package/dist/claude-cloud-login.js +151 -11
  34. package/dist/cli-proxy-forward.d.ts +2 -0
  35. package/dist/cli-proxy-forward.js +9 -2
  36. package/dist/cli-proxy-hub.d.ts +69 -25
  37. package/dist/cli-proxy-hub.js +231 -54
  38. package/dist/cli-proxy.d.ts +70 -1
  39. package/dist/cli-proxy.js +298 -32
  40. package/dist/client-settings.d.ts +39 -0
  41. package/dist/client-settings.js +91 -0
  42. package/dist/codex-cloud.d.ts +5 -0
  43. package/dist/codex-cloud.js +88 -17
  44. package/dist/command-center-actions.js +5 -1
  45. package/dist/commands.js +171 -22
  46. package/dist/conversation-commands.d.ts +5 -0
  47. package/dist/conversation-commands.js +8 -5
  48. package/dist/conversation-terminal.d.ts +10 -2
  49. package/dist/conversation-terminal.js +81 -15
  50. package/dist/conversations.d.ts +77 -1
  51. package/dist/conversations.js +257 -23
  52. package/dist/device-bridge.d.ts +1 -1
  53. package/dist/device-bridge.js +6 -4
  54. package/dist/device-helper.js +52 -4
  55. package/dist/errors.d.ts +34 -1
  56. package/dist/errors.js +56 -5
  57. package/dist/filesystem-commands.d.ts +22 -3
  58. package/dist/filesystem-commands.js +132 -16
  59. package/dist/format.d.ts +2 -1
  60. package/dist/format.js +10 -1
  61. package/dist/handlers.js +4 -1
  62. package/dist/initial-prompt.d.ts +8 -0
  63. package/dist/initial-prompt.js +43 -1
  64. package/dist/large-media.d.ts +14 -0
  65. package/dist/large-media.js +100 -0
  66. package/dist/linux-terminal.d.ts +5 -3
  67. package/dist/linux-terminal.js +6 -20
  68. package/dist/local-sessions.d.ts +26 -2
  69. package/dist/local-sessions.js +113 -24
  70. package/dist/machine-setup.d.ts +190 -0
  71. package/dist/machine-setup.js +849 -0
  72. package/dist/machine-status.d.ts +67 -0
  73. package/dist/machine-status.js +140 -0
  74. package/dist/main.js +23 -13
  75. package/dist/move-worktrees-restore.py +144 -0
  76. package/dist/move-worktrees.d.ts +63 -0
  77. package/dist/move-worktrees.js +201 -0
  78. package/dist/move.d.ts +10 -1
  79. package/dist/move.js +116 -16
  80. package/dist/onboarding.d.ts +11 -7
  81. package/dist/onboarding.js +59 -27
  82. package/dist/prepared-repository.d.ts +202 -0
  83. package/dist/prepared-repository.js +860 -0
  84. package/dist/prepared-repository.py +1306 -0
  85. package/dist/project-preparation.d.ts +48 -0
  86. package/dist/project-preparation.js +304 -0
  87. package/dist/project-sync-manifest.d.ts +4 -0
  88. package/dist/project-sync-manifest.js +5 -1
  89. package/dist/project-sync-transfer.js +1 -1
  90. package/dist/project-sync.d.ts +19 -4
  91. package/dist/project-sync.js +31 -8
  92. package/dist/project-template-setup.d.ts +33 -1
  93. package/dist/project-template-setup.js +59 -2
  94. package/dist/secret-ask.d.ts +72 -0
  95. package/dist/secret-ask.js +351 -0
  96. package/dist/seed-phases.d.ts +13 -3
  97. package/dist/seed-phases.js +21 -3
  98. package/dist/setup-cache.d.ts +7 -0
  99. package/dist/setup-cache.js +32 -0
  100. package/dist/t3-launch.js +1 -4
  101. package/dist/t3-mode.d.ts +8 -2
  102. package/dist/t3-mode.js +19 -4
  103. package/dist/t3-phone.d.ts +4 -2
  104. package/dist/t3-phone.js +12 -4
  105. package/dist/terminal-apps.d.ts +157 -0
  106. package/dist/terminal-apps.js +561 -0
  107. package/dist/terminal-onboarding.d.ts +2 -0
  108. package/dist/terminal-onboarding.js +11 -1
  109. package/dist/this-computer.d.ts +11 -0
  110. package/dist/this-computer.js +36 -0
  111. package/dist/thread-on-computer.js +4 -1
  112. package/dist/update.js +11 -3
  113. package/dist/worktree-twin.d.ts +10 -2
  114. package/dist/worktree-twin.js +27 -9
  115. package/package.json +5 -5
@@ -6,12 +6,15 @@ import { resolveWorkspace } from "./commands.js";
6
6
  import { AgentStateStore } from "./agent-state.js";
7
7
  import { describeError } from "./errors.js";
8
8
  import { t3PhoneLink } from "./t3-phone.js";
9
+ import { t3NeedsProject, t3SignInProject } from "./account-scope.js";
9
10
  import { readAgentCredentials, syncAgentCredentials } from "./agent-credentials.js";
10
11
  import { getConversation } from "./conversations.js";
11
12
  import { codexHealth, readCodexConnector, restartCodexConnector } from "./codex-cloud.js";
12
13
  import { applicationDirs } from "./agent-targets.js";
13
14
  import { configureT3Provider } from "./t3-provider.js";
14
15
  import { t3Install } from "./t3-mode.js";
16
+ import { inspectMachineRow, rowText } from "./machine-status.js";
17
+ import { launcherShortcut } from "./this-computer.js";
15
18
  /**
16
19
  * The first-run demo's task: it shows the agent's own computer at work. The user copies it and pastes it into the
17
20
  * launcher themselves; Daylight's own CLAUDE.md and AGENTS.md say how to run it and test it in the desktop browser.
@@ -37,7 +40,7 @@ export function installDirs(home) {
37
40
  return [join(home, '.local', 'bin'), join(home, '.claude', 'local'), join(home, '.npm-global', 'bin'), join(home, '.bun', 'bin'),
38
41
  join(home, '.volta', 'bin'), join(home, 'Library', 'pnpm')];
39
42
  }
40
- /** Claude Code or Codex is on this Mac: its home folder (~/.claude, ~/.codex) or its command on PATH or in an install folder. */
43
+ /** Claude Code or Codex is on this computer: its home folder (~/.claude, ~/.codex) or its command on PATH or in an install folder. */
41
44
  export async function foundApps(env) {
42
45
  const home = env.HOME ?? homedir();
43
46
  const onPath = async (name) => {
@@ -69,7 +72,7 @@ export function parseApps(value) {
69
72
  * appears in T3 Code and the T3 app by itself. `startSignIn` (`--t3-connect`) starts that sign-in. Null when T3 Code
70
73
  * is not installed.
71
74
  */
72
- export async function t3Connection(ctx, signedIn, appDirs = applicationDirs(ctx.io.env), platform = process.platform, startSignIn = false) {
75
+ export async function t3Connection(ctx, signedIn, appDirs = applicationDirs(ctx.io.env), platform = process.platform, startSignIn = false, scope = {}) {
73
76
  // The same install every T3 command uses (macOS and Linux): a build with ACP providers first.
74
77
  const app = t3Install(ctx.io.env, platform, appDirs);
75
78
  if (!app)
@@ -90,37 +93,52 @@ export async function t3Connection(ctx, signedIn, appDirs = applicationDirs(ctx.
90
93
  // Stable T3: one T3 Connect sign-in per person (the API's, per account), then each project appears by itself. `next`
91
94
  // is for scripts: a project is set up for T3 Code with it.
92
95
  const ssh = { ...base, mode: 'ssh', next: shard(['agents', 'new', '--in', 't3', '--path', '<project>']) };
96
+ const selection = ['org', 'project'].flatMap(key => typeof scope[key] === 'string' ? [`--${key}`, scope[key]] : []);
93
97
  const offer = { ...ssh, state: 'action', fact: 'Your projects appear in T3 Code and the T3 app once connected.',
94
- action: { title: 'Connect T3 Code', command: shard(['setup', '--inspect', '--t3-connect', '--json']) } };
98
+ action: { title: 'Connect T3 Code', command: shard(['setup', '--inspect', '--t3-connect', '--json', ...selection]) } };
95
99
  if (!signedIn)
96
100
  return offer;
97
- const cloud = ctx.cloud();
101
+ let projectId;
98
102
  let status;
99
103
  try {
100
- status = await cloud.t3.status();
104
+ // Status and sign-in must use the same person and organization. The key may belong to another organization
105
+ // (or another member); its connected state cannot decide whether this person's selected sign-in is connected.
106
+ if (ctx.sessionSource() !== null) {
107
+ projectId = await t3SignInProject(ctx, scope);
108
+ status = await ctx.account().t3.status(projectId ? { projectId } : {});
109
+ }
110
+ else
111
+ status = await ctx.cloud().t3.status();
101
112
  }
102
113
  catch {
103
114
  return { ...ssh, state: 'unknown', fact: 'T3 Connect could not be checked just now.', action: { title: 'Retry', retry: true } };
104
115
  }
105
116
  // Remembered for `agents targets`: with the sign-in, every project can start in T3 Code (t3Status).
106
- try {
107
- new AgentStateStore(ctx.store.dir).saveT3Connected(ctx.apiUrl, status.connected);
117
+ // A one-off --org/--project check must not replace the default target's remembered status.
118
+ if (selection.length === 0) {
119
+ try {
120
+ new AgentStateStore(ctx.store.dir).saveT3Connected(ctx.apiUrl, status.connected);
121
+ }
122
+ catch { /* the next check writes it */ }
108
123
  }
109
- catch { /* the next check writes it */ }
110
124
  if (!status.connected) {
111
125
  if (!startSignIn)
112
126
  return offer;
113
- const signIn = await startT3SignIn(ctx);
127
+ const signIn = await startT3SignIn(ctx, projectId);
114
128
  if (signIn === 'connected')
115
- return t3Connection(ctx, signedIn, appDirs, platform, false);
129
+ return t3Connection(ctx, signedIn, appDirs, platform, false, scope);
116
130
  if (signIn.needs_login)
117
131
  return { ...offer, fact: 'Sign in to Shardflux on this computer to connect T3 Code.', t3_link: signIn };
118
132
  return { ...offer, fact: 'Confirm the code on the page that opened. This updates on its own.', t3_link: signIn,
119
133
  action: { title: 'Connect T3 Code', url: signIn.sign_in.verification_uri } };
120
134
  }
121
135
  const connected = { ...ssh, state: 'verified', fact: 'Your projects appear in T3 Code and the T3 app.' };
136
+ // Explicit account selection is a status check, not consent to relink the default computer's last workspace.
137
+ if (selection.length > 0)
138
+ return connected;
122
139
  // The newest project set up for T3 Code from this computer (agents.json; a targets.json entry from an older CLI still
123
- // counts): where it is. One set up before T3 Connect, or whose link failed, is linked now: it is the person's choice.
140
+ // counts): where it is. One set up before T3 Connect is linked now. An existing error stays visible until the
141
+ // person explicitly requests another link; a periodic inspection must not restart permanent refusals.
124
142
  const recorded = new AgentStateStore(ctx.store.dir).t3Environments(ctx.apiUrl).at(-1);
125
143
  const manifest = await jsonFile(join(ctx.store.dir, 'targets.json'));
126
144
  const entries = Array.isArray(manifest?.entries) ? manifest.entries : [];
@@ -131,7 +149,9 @@ export async function t3Connection(ctx, signedIn, appDirs = applicationDirs(ctx.
131
149
  try {
132
150
  const ws = await resolveWorkspace(ctx, key);
133
151
  const known = status.links.find(l => l.workspace_id === ws.id);
134
- const link = !known || known.status === 'error' || known.status === 'needs_sign_in' ? (await t3PhoneLink(ctx, ws.id)).link : known;
152
+ if (known?.status === 'error')
153
+ return { ...ssh, state: 'info', fact: known.last_error ?? 'T3 Connect could not link this workspace. Check T3 Connect, then open this project in T3 Code again to retry.' };
154
+ const link = !known || known.status === 'needs_sign_in' ? (await t3PhoneLink(ctx, ws.id, ws.data.project_id)).link : known;
135
155
  // Linked to the person's T3 account: T3 Code and the T3 app list it once they are signed in to that account.
136
156
  if (link.status === 'linked')
137
157
  return { ...connected, fact: `${link.label} is linked to your T3 account.` };
@@ -149,19 +169,21 @@ export async function t3Connection(ctx, signedIn, appDirs = applicationDirs(ctx.
149
169
  }
150
170
  /**
151
171
  * Starts the person's T3 Connect sign-in (a pending one comes back again). It needs this computer's Shardflux session,
152
- * not only an API key. `connected` when the account already has one (409 `t3_signed_in`).
172
+ * not only an API key. `connected` when the account already has one (409 `t3_signed_in`). The sign-in is per
173
+ * organization: it is scoped by --project/--org, else the API key's project, else the saved default (0.20.2+; before,
174
+ * a person in two organizations got 422 project_id is required).
153
175
  */
154
- async function startT3SignIn(ctx) {
155
- if (ctx.store.profile(ctx.apiUrl).session?.state !== 'active')
176
+ async function startT3SignIn(ctx, projectId) {
177
+ if (ctx.sessionSource() === null)
156
178
  return { status: 'needs_sign_in', label: '', needs_login: true };
157
179
  try {
158
- const flow = await ctx.account().t3.signIn();
180
+ const flow = await ctx.account().t3.signIn(projectId ? { projectId } : {});
159
181
  return { status: 'needs_sign_in', label: '', sign_in: { verification_uri: flow.verification_uri, user_code: flow.user_code, expires_at: flow.expires_at } };
160
182
  }
161
183
  catch (error) {
162
184
  if (error instanceof ShardfluxApiError && describeError(error).code === 't3_signed_in')
163
185
  return 'connected';
164
- throw error;
186
+ throw t3NeedsProject(error) ?? error;
165
187
  }
166
188
  }
167
189
  const shard = (words) => ['shard', ...words];
@@ -176,7 +198,7 @@ export function claudeConnection(row) {
176
198
  return { state: 'action', fact: 'Claude no longer accepts this sign-in.', action: { ...signIn, title: 'Sign in to Claude again' } };
177
199
  return { state: 'verified', fact: 'Signed in. Works in every conversation, on web and phone.' };
178
200
  }
179
- /** Codex: this Mac's Codex login is copied and the account's Codex remote device (one executor) is connected. */
201
+ /** Codex: this computer's Codex login is copied and the account's Codex remote device (one executor) is connected. */
180
202
  export function codexConnection(local, synced, connector, health) {
181
203
  const enable = { title: 'Connect Codex', command: shard(['agents', 'codex', 'enable']) };
182
204
  if (!synced) {
@@ -198,14 +220,14 @@ export function codexConnection(local, synced, connector, health) {
198
220
  /**
199
221
  * The onboarding report, for an agent setting itself up from chat and for the menu bar app. It verifies every
200
222
  * connection live: the Shardflux account, Claude (Claude itself accepts the login) and Codex (the login is copied and
201
- * the account's Codex remote device is connected), for the apps picked (--apps, else saved, else found on this Mac).
223
+ * the account's Codex remote device is connected), for the apps picked (--apps, else saved, else found on this computer).
202
224
  * With --connect it also does what needs no sign-in: copies the Codex login and enables the Codex remote device.
203
225
  */
204
- export async function onboardingReport(ctx, values) {
226
+ export async function onboardingReport(ctx, values, platform = process.platform) {
205
227
  const saved = await jsonFile(resolve(ctx.store.dir, 'onboarding.json'));
206
228
  const state = saved?.api_url === ctx.apiUrl ? saved : undefined;
207
229
  const found = await foundApps(ctx.io.env);
208
- // An empty saved pick means nothing was chosen yet: what is found on this Mac is pre-ticked.
230
+ // An empty saved pick means nothing was chosen yet: what is found on this computer is pre-ticked.
209
231
  const savedApps = Array.isArray(state?.apps) && state.apps.length ? parseApps(state.apps.filter(v => typeof v === 'string')) : undefined;
210
232
  const apps = parseApps(values.apps) ?? savedApps ?? found;
211
233
  const connect = values.connect === true;
@@ -256,7 +278,7 @@ export async function onboardingReport(ctx, values) {
256
278
  let read = await readCodexConnector(ctx);
257
279
  health = codexHealth(read.connector, read.workspaceState);
258
280
  const synced = credentials.some(row => row.app === 'codex');
259
- // One executor per account, enabled as `shard setup` does when this Mac is signed in to Codex. --retry-codex starts
281
+ // One executor per account, enabled as `shard setup` does when this computer is signed in to Codex. --retry-codex starts
260
282
  // a failed or stuck one again, once.
261
283
  if (connect && synced && (health === 'missing' || health === 'disabled')) {
262
284
  read = { connector: await ctx.cloud().codex.enable({ signal: ctx.signal }), workspaceState: null };
@@ -281,22 +303,29 @@ export async function onboardingReport(ctx, values) {
281
303
  const codex = { id: 'codex', picked: apps.includes('codex'), found: found.includes('codex'),
282
304
  ...(!connected ? signIn : (credentialError ?? codexError) !== undefined ? failure(credentialError ?? codexError)
283
305
  : codexConnection(localCodex, credentials.some(row => row.app === 'codex'), connector, health)) };
284
- const t3 = await t3Connection(ctx, connected, undefined, undefined, values['t3-connect'] === true);
306
+ const t3 = await t3Connection(ctx, connected || ctx.sessionSource() !== null, undefined, undefined, values['t3-connect'] === true, values);
285
307
  const connections = [account, claude, codex, ...(t3 ? [t3] : [])];
308
+ // The cloud machine (0.20.2+), the same row `shard machine setup --status` reports: whether what is installed carries
309
+ // over to new conversations. It never decides `ready`: conversations start without a saved machine too.
310
+ const machine = connected ? await inspectMachineRow(ctx) : null;
311
+ const shortcut = launcherShortcut(ctx.store.dir, platform);
312
+ const press = (what) => (shortcut ? `Press ${shortcut}, ${what}` : `Open a new conversation, ${what}`);
286
313
  // Ready: signed in and at least one agent app connected; the other can be connected later (Settings, or this again).
287
314
  const ready = account.state === 'verified' && [claude, codex].some(c => c.state === 'verified');
288
315
  const done = (value) => value === true || (typeof value === 'string' && value.length > 0);
289
316
  return {
290
- version: 3, api_url: ctx.apiUrl, apps, found_apps: found, connections, ready, account_error: accountError,
317
+ version: 3, api_url: ctx.apiUrl, apps, found_apps: found, connections, ready, connections_ready: ready,
318
+ machine_ready: machine && !['unknown', 'unavailable'].includes(machine.state) ? machine.state === 'ready' && machine.version !== null && machine.version > 0 : null,
319
+ account_error: accountError, machine,
291
320
  demo: { prompt: DEMO_PROMPT, project: 'Daylight (sample)', prepare: shard(['setup', '--prepare-sample', '--json']) },
292
321
  activity: values.activity === true && typeof values.workspace === 'string' ? await liveActivity(ctx, values.workspace) : null,
293
322
  steps: [
294
323
  { id: 'account', title: 'Sign in to Shardflux', status: connected ? 'complete' : 'available', command: shard(['signup']) },
295
324
  { id: 'connect', title: 'Connect Claude, Codex or both', status: ready ? 'complete' : 'available' },
296
325
  { id: 'demo', title: 'Try it once: your agent’s own computer', status: done(state?.demo_finished) ? 'complete' : 'available',
297
- action: 'Copy the task and press ⌥⇧Space. Paste the task, then Start. The agent installs what it needs, starts it and tests it in its own browser.' },
326
+ action: `Copy the task. ${press('paste the task, then Start.')} The agent installs what it needs, starts it and tests it in its own browser.` },
298
327
  { id: 'own', title: 'Your own project', status: done(state?.own_key) ? 'complete' : 'available',
299
- action: 'Press ⌥⇧Space, pick your project and describe what you want.' },
328
+ action: press('pick your project and describe what you want.') },
300
329
  ],
301
330
  };
302
331
  }
@@ -305,7 +334,10 @@ export function onboardingText(report) {
305
334
  const names = { account: 'Shardflux', claude: 'Claude', codex: 'Codex', t3: 'T3 Code' };
306
335
  const mark = { verified: '✓', action: '!', pending: '…', unknown: '?', info: '·' };
307
336
  const lines = report.connections.filter(c => c.id === 'account' || c.picked || c.id === 't3').map(c => `${mark[c.state]} ${names[c.id]}: ${c.fact}${c.action?.command ? ` (${c.action.command.join(' ')})` : ''}`);
308
- return `${lines.join('\n')}\n${report.ready ? `Ready. Try it once: ${report.demo.prompt}` : 'Not ready yet: finish the steps above, then run this again.'}\n`;
337
+ const machineMark = { ready: '✓', saving: '…', failed: '!', none: '·', unknown: '?', unavailable: '?' };
338
+ if (report.machine)
339
+ lines.push(`${machineMark[report.machine.state]} ${rowText(report.machine)}`);
340
+ return `${lines.join('\n')}\n${report.ready ? `Agent connections ready. Try it once: ${report.demo.prompt}` : 'Connections not ready yet: finish the steps above, then run this again.'}\n`;
309
341
  }
310
342
  /** What a person recognises, most telling first: installs, then how the app is run, then what it runs. */
311
343
  const RANKS = [/^(sudo|apt|apt-get|dpkg)$/, /^(npm|npx|pnpm|yarn|bun)$/, /^(node|python[0-9.]*|uv|vite|next|deno|docker|cargo|go|make)$/, /^(pip[0-9.]*|inotifywait)$/];
@@ -0,0 +1,202 @@
1
+ import type { SyncCell } from './project-sync-transfer.js';
2
+ import type { OutsideFile } from './large-media.js';
3
+ /** One upload part. */
4
+ export declare const PART_BYTES: number;
5
+ /** Blobs of S from this size on leave the thin pack: stored when incompressible, compressed apart otherwise. */
6
+ export declare const LARGE_BLOB_BYTES: number;
7
+ /** Large media above this follow the first prompt when the caller allows it. */
8
+ export declare const DEFER_BYTES: number;
9
+ /** Parts in flight at once, and their bytes. */
10
+ export declare const UPLOAD_WINDOW = 4;
11
+ export declare const UPLOAD_BUDGET_BYTES: number;
12
+ /** The history the laptop sends before the cache fetches it from origin instead. */
13
+ export declare const ORIGIN_FIRST_BYTES: number;
14
+ /** The history the laptop sends before the cache takes the VM's own fetch of the branch (its boot prefetch) instead. */
15
+ export declare const PREFETCH_ADOPT_BYTES: number;
16
+ /** The local spool of generated packs (for resuming), pruned past this or a day. */
17
+ export declare const SPOOL_BYTES: number;
18
+ export interface PreparedExtra {
19
+ path: string;
20
+ sha256: string;
21
+ size: number;
22
+ mode: number;
23
+ symlink: boolean;
24
+ link_target: string;
25
+ }
26
+ export interface PreparedFile {
27
+ path: string;
28
+ oid: string;
29
+ size: number;
30
+ mode: number;
31
+ }
32
+ export interface PreparedManifest {
33
+ version: 2;
34
+ format: 'sha1' | 'sha256';
35
+ head: string;
36
+ seed: string;
37
+ /** The user's branch and its upstream, recreated in the VM as on this computer. */
38
+ branch?: string;
39
+ upstream?: {
40
+ ref: string;
41
+ oid: string;
42
+ };
43
+ origin: string | null;
44
+ repository: string;
45
+ worktree: string;
46
+ deps_hash?: string;
47
+ /** What Git does not hold: .env files (never in a Git object) and files whose filters change their bytes. */
48
+ extras: PreparedExtra[];
49
+ extras_key?: string;
50
+ /** Large untracked media, outside S: Git blobs the VM writes as files its Git ignores. */
51
+ files: PreparedFile[];
52
+ /** Deleted files S still names (only when S is HEAD: the twin snapshot was off or paused). */
53
+ remove: string[];
54
+ /** A shallow clone's boundary commits: the cache and the worktree take the same boundary. */
55
+ shallow?: string[];
56
+ }
57
+ export interface PreparedSnapshot {
58
+ revision: string;
59
+ manifest: PreparedManifest;
60
+ directory: string;
61
+ }
62
+ export interface PreparedTransfer {
63
+ revision: string;
64
+ /** S, its archive and every media file are in the cache. Otherwise `pending` names the media still to send. */
65
+ complete: boolean;
66
+ pending: string[];
67
+ /** Bytes of the packs generated for this transfer: history and small files; large blobs; media; the archive. */
68
+ history_bytes: number;
69
+ large_bytes: number;
70
+ media_bytes: number;
71
+ extras_bytes: number;
72
+ /** Part bytes sent, and bytes the VM already held. */
73
+ uploaded_bytes: number;
74
+ reused_bytes: number;
75
+ puts: number;
76
+ execs: number;
77
+ max_in_flight_bytes: number;
78
+ origin_fetched: boolean;
79
+ /** Media that changed on this computer after the start captured them: they never arrive as captured. */
80
+ changed: string[];
81
+ timings: Record<string, number>;
82
+ /** The checkout the probe made (options.apply) when the cache already held the revision. */
83
+ applied?: PreparedApply;
84
+ }
85
+ export interface PreparedGitConfig {
86
+ origin_url?: string;
87
+ user?: {
88
+ name?: string;
89
+ email?: string;
90
+ };
91
+ tracking?: {
92
+ branch: string;
93
+ merge: string;
94
+ };
95
+ excludes?: string;
96
+ }
97
+ export interface PreparedApply {
98
+ revision: string;
99
+ mode: 'fresh' | 'inplace' | 'existing';
100
+ /** Media not in the cache yet, and media landing right after the reply (Git ignores both). */
101
+ pending: number;
102
+ landing: number;
103
+ dependencies: number;
104
+ timings: Record<string, number>;
105
+ }
106
+ export declare function preparedCachePath(remote: string): string;
107
+ export declare const preparedDirectory: (stateDir: string, folder: string) => string;
108
+ /** The manifest's identity bytes: sorted keys, no spaces, UTF-8 (prepared-repository.py `canonical`). */
109
+ export declare function canonicalJson(value: unknown): string;
110
+ export declare const manifestRevision: (m: PreparedManifest) => string;
111
+ /** A refusal the VM half reports by reason (missing or corrupt parts, an owned worktree, ...). */
112
+ export declare class PreparedRefusal extends Error {
113
+ readonly reason: string;
114
+ readonly chunks: string[];
115
+ constructor(reason: string, message: string, chunks?: string[]);
116
+ }
117
+ /**
118
+ * The prepared revision of a worktree's state: S (the twin seed, already made), its large media and its extras. S's
119
+ * objects stay in the local repository under refs/shard/prepared/<revision> until the start that uses it is done, so a
120
+ * start accepted before a deadline resumes later with exactly the state it began from, whatever the user edits.
121
+ */
122
+ export declare function capturePreparedSnapshot(stateDir: string, folder: string, seed: {
123
+ commit: string;
124
+ outside?: OutsideFile[];
125
+ }, origin: string | null, signal?: AbortSignal, options?: {
126
+ reachable?: (url: string) => boolean;
127
+ depsHash?: string;
128
+ }): Promise<PreparedSnapshot>;
129
+ /** The local pin of a revision's objects, once its start no longer needs them. */
130
+ export declare function releaseLocalSnapshot(folder: string, revision: string): Promise<void>;
131
+ export declare function readPreparedSnapshot(stateDir: string, folder: string, revision: string): Promise<PreparedSnapshot>;
132
+ /** Where a conversation start's parts go: the VM's own disk, indexed there; the cache receives them after the reply. */
133
+ export declare const PARTS_ROOT = "/home/user/.cache/shardflux/prepared-parts";
134
+ /**
135
+ * Sends the revision, only what the project cache lacks. Without `apply` it goes into the cache (a preparation). With
136
+ * `apply` (a conversation start) it is checked out at that target: in the probe's own exec when the cache already
137
+ * holds the revision (a warm start is one exec), otherwise from parts uploaded to the VM's disk and indexed there, with
138
+ * no Git work on the shared cache before the reply; the cache receives them after it. With `defer`, media beyond
139
+ * DEFER_BYTES wait for a later call: the result names them in `pending`, and the worktree starts without them.
140
+ */
141
+ export declare function transferPreparedSnapshot(cell: SyncCell, folder: string, cache: string, snapshot: PreparedSnapshot, options?: {
142
+ signal?: AbortSignal;
143
+ defer?: boolean;
144
+ pin?: string;
145
+ originFirstBytes?: number;
146
+ apply?: PreparedApplyTarget;
147
+ partsDir?: string;
148
+ }): Promise<PreparedTransfer>;
149
+ /** Git settings the VM repository copies from this one: origin, identity, the branch's upstream, exclude rules. */
150
+ export declare function preparedGitConfig(folder: string, manifest: PreparedManifest, signal?: AbortSignal): Promise<PreparedGitConfig>;
151
+ export interface PreparedApplyOptions {
152
+ signal?: AbortSignal;
153
+ git?: PreparedGitConfig;
154
+ preparation?: boolean;
155
+ replacePreparation?: boolean;
156
+ dependencySource?: string;
157
+ /** A new VM: files its image left at the worktree path (outside any repository) are checked out over. */
158
+ adopt?: boolean;
159
+ /** false keeps borrowing the cache's objects; 'now' copies them before returning (a template must not borrow). */
160
+ dissociate?: boolean | 'now';
161
+ background?: boolean;
162
+ releasePin?: string;
163
+ /** A shell script the VM runs, detached, once the worktree is ready (the seed's copy to the project hub). */
164
+ after?: {
165
+ script: string;
166
+ args: string[];
167
+ };
168
+ /** The conversation's setup intent (recordSetupIntent), written in the same exec before its repository appears. */
169
+ setupIntent?: {
170
+ conversation: string;
171
+ depsHash: string;
172
+ };
173
+ /** The VM's workspace: a checkout another VM's conversation made (and a template image brought) is taken over. */
174
+ workspace?: string;
175
+ }
176
+ /** Where and how a revision is checked out: the VM half's apply request. */
177
+ export interface PreparedApplyTarget {
178
+ root: string;
179
+ branch: string;
180
+ options?: PreparedApplyOptions;
181
+ }
182
+ /**
183
+ * Checks out the revision as `branch` at `root`, in one exec. Media not in the cache yet stay pending
184
+ * (landDeferredFiles). A root holding the same revision is left as it is; one holding another is refused, unless it is
185
+ * a project template's checkout or `replacePreparation`.
186
+ */
187
+ export declare function applyPreparedSnapshot(cell: SyncCell, cache: string, revision: string, root: string, branch: string, options?: PreparedApplyOptions): Promise<PreparedApply>;
188
+ /** Lands media that arrived after the start (where nothing is yet: the agent's own file stands); `drop` gives up on blobs that changed. */
189
+ export declare function landDeferredFiles(cell: SyncCell, cache: string, revision: string, root: string, options?: {
190
+ signal?: AbortSignal;
191
+ drop?: string[];
192
+ }): Promise<{
193
+ landed: number;
194
+ kept: number;
195
+ pending: number;
196
+ }>;
197
+ export declare function prunePreparedCache(cell: SyncCell, cache: string, signal?: AbortSignal): Promise<void>;
198
+ /**
199
+ * The local spool stays bounded: a finished transfer's parts go at once; other parts and plans after a day or past
200
+ * the budget, oldest first; a revision's manifest and files archive (what a pending start resumes from) after a week.
201
+ */
202
+ export declare function pruneLocalPrepared(stateDir: string, limit?: number, every?: number): Promise<void>;