@shardflux/cli 0.20.0 → 0.23.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 (124) hide show
  1. package/CHANGELOG.md +312 -1
  2. package/README.md +242 -75
  3. package/dist/account-args.js +7 -2
  4. package/dist/account-commands.js +56 -106
  5. package/dist/account-pool.d.ts +19 -15
  6. package/dist/account-pool.js +47 -96
  7. package/dist/account-scope.d.ts +65 -0
  8. package/dist/account-scope.js +246 -0
  9. package/dist/acp-journal.js +58 -6
  10. package/dist/acp-login.d.ts +13 -5
  11. package/dist/acp-login.js +37 -8
  12. package/dist/acp-models.d.ts +6 -1
  13. package/dist/acp-models.js +29 -19
  14. package/dist/acp.js +120 -14
  15. package/dist/agent-commands.d.ts +4 -1
  16. package/dist/agent-commands.js +110 -42
  17. package/dist/agent-config-return.d.ts +46 -2
  18. package/dist/agent-config-return.js +110 -4
  19. package/dist/agent-credentials.js +7 -3
  20. package/dist/agent-repository.d.ts +16 -3
  21. package/dist/agent-repository.js +149 -74
  22. package/dist/agent-setup.d.ts +14 -3
  23. package/dist/agent-setup.js +78 -36
  24. package/dist/agent-target-commands.d.ts +20 -1
  25. package/dist/agent-target-commands.js +73 -31
  26. package/dist/agent-targets.js +6 -1
  27. package/dist/args.d.ts +1 -1
  28. package/dist/args.js +95 -54
  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-hub.d.ts +61 -25
  35. package/dist/cli-proxy-hub.js +202 -54
  36. package/dist/cli-proxy.d.ts +44 -1
  37. package/dist/cli-proxy.js +177 -28
  38. package/dist/client-settings.d.ts +39 -0
  39. package/dist/client-settings.js +91 -0
  40. package/dist/codex-cloud.d.ts +14 -4
  41. package/dist/codex-cloud.js +135 -31
  42. package/dist/codex-linux.d.ts +14 -0
  43. package/dist/codex-linux.js +69 -0
  44. package/dist/command-center-actions.js +18 -5
  45. package/dist/command-center-model.d.ts +2 -0
  46. package/dist/command-center-model.js +6 -1
  47. package/dist/command-center.js +3 -3
  48. package/dist/commands.js +173 -24
  49. package/dist/conversation-commands.d.ts +5 -0
  50. package/dist/conversation-commands.js +8 -5
  51. package/dist/conversation-terminal.d.ts +10 -2
  52. package/dist/conversation-terminal.js +78 -13
  53. package/dist/conversations.d.ts +86 -1
  54. package/dist/conversations.js +306 -23
  55. package/dist/desktop-linux.js +16 -3
  56. package/dist/device-bridge.d.ts +1 -1
  57. package/dist/device-bridge.js +6 -4
  58. package/dist/device-helper.js +43 -22
  59. package/dist/errors.d.ts +29 -2
  60. package/dist/errors.js +50 -7
  61. package/dist/filesystem-commands.d.ts +16 -9
  62. package/dist/filesystem-commands.js +133 -57
  63. package/dist/format.d.ts +2 -1
  64. package/dist/format.js +10 -1
  65. package/dist/handlers.js +3 -1
  66. package/dist/initial-prompt.d.ts +8 -0
  67. package/dist/initial-prompt.js +43 -1
  68. package/dist/large-media.d.ts +14 -0
  69. package/dist/large-media.js +100 -0
  70. package/dist/linux-terminal.d.ts +5 -3
  71. package/dist/linux-terminal.js +6 -20
  72. package/dist/local-sessions.d.ts +26 -2
  73. package/dist/local-sessions.js +113 -24
  74. package/dist/machine-setup.d.ts +11 -1
  75. package/dist/machine-setup.js +69 -22
  76. package/dist/machine-status.d.ts +67 -0
  77. package/dist/machine-status.js +140 -0
  78. package/dist/main.js +11 -13
  79. package/dist/menubar.d.ts +6 -5
  80. package/dist/menubar.js +38 -28
  81. package/dist/move-worktrees-restore.py +144 -0
  82. package/dist/move-worktrees.d.ts +69 -0
  83. package/dist/move-worktrees.js +252 -0
  84. package/dist/move.d.ts +10 -1
  85. package/dist/move.js +111 -16
  86. package/dist/onboarding.d.ts +11 -7
  87. package/dist/onboarding.js +63 -29
  88. package/dist/prepared-repository.d.ts +204 -0
  89. package/dist/prepared-repository.js +860 -0
  90. package/dist/prepared-repository.py +1307 -0
  91. package/dist/project-preparation.d.ts +62 -0
  92. package/dist/project-preparation.js +316 -0
  93. package/dist/project-sync-manifest.d.ts +4 -0
  94. package/dist/project-sync-manifest.js +5 -1
  95. package/dist/project-sync-transfer.js +1 -1
  96. package/dist/project-sync.d.ts +22 -4
  97. package/dist/project-sync.js +34 -8
  98. package/dist/project-template-setup.d.ts +33 -1
  99. package/dist/project-template-setup.js +59 -2
  100. package/dist/secret-ask.d.ts +72 -0
  101. package/dist/secret-ask.js +351 -0
  102. package/dist/seed-phases.d.ts +8 -1
  103. package/dist/seed-phases.js +17 -2
  104. package/dist/setup-cache.d.ts +7 -0
  105. package/dist/setup-cache.js +32 -0
  106. package/dist/t3-launch.d.ts +2 -1
  107. package/dist/t3-launch.js +12 -6
  108. package/dist/t3-mode.d.ts +8 -2
  109. package/dist/t3-mode.js +19 -4
  110. package/dist/t3-phone.d.ts +4 -2
  111. package/dist/t3-phone.js +12 -4
  112. package/dist/t3-proxy.d.ts +81 -0
  113. package/dist/t3-proxy.js +325 -0
  114. package/dist/terminal-apps.d.ts +168 -0
  115. package/dist/terminal-apps.js +574 -0
  116. package/dist/terminal-onboarding.js +6 -1
  117. package/dist/this-computer.d.ts +11 -0
  118. package/dist/this-computer.js +36 -0
  119. package/dist/thread-on-computer.js +4 -1
  120. package/dist/update.d.ts +2 -0
  121. package/dist/update.js +43 -9
  122. package/dist/worktree-twin.d.ts +10 -2
  123. package/dist/worktree-twin.js +27 -9
  124. package/package.json +5 -5
@@ -1,15 +1,21 @@
1
+ import { StringDecoder } from 'node:string_decoder';
1
2
  import { isDeepStrictEqual } from 'node:util';
2
3
  import { ShardfluxApiError } from '@shardflux/sdk';
3
- import { UsageError } from "./errors.js";
4
+ import { CodedError, UsageError } from "./errors.js";
4
5
  import { openUrl } from "./browser.js";
5
- import { attachTerminal, cellConnector, PtyOutputCursor, workspaceAsleep } from "./agent-terminal.js";
6
+ import { attachTerminal, cellConnector, PtyOutputCursor, reattachDelayMs, retryableAttach, workspaceAsleep } from "./agent-terminal.js";
6
7
  import { codingAgentShell } from "./agent-setup.js";
7
- const NEED_TERMINAL = 'Claude sign-in needed: run shard agents credentials login claude in a terminal, once for the account (a claude.ai link, then its code).';
8
- const NEED_TERMINAL_HINT = 'a coding agent asks its user to run it in their own terminal, then checks it with shard setup --inspect --json --apps claude';
8
+ import { announce, askInBrowser, openInBrowser } from "./secret-ask.js";
9
+ const NEED_TERMINAL = 'Claude sign-in needed: run shard agents credentials login claude, once for the account (it opens the claude.ai sign-in and a page for its code).';
10
+ const NEED_TERMINAL_HINT = 'a coding agent runs shard agents credentials login claude --json itself; its user signs in and pastes the code into the page it opens';
9
11
  const VERSION = '2.1.289';
10
- export function requireClaudeTerminal(ctx) {
12
+ /** Someone types into this terminal: not --json, a TTY with raw mode, and not a coding agent's shell. */
13
+ export function claudeTerminal(ctx) {
11
14
  // A coding agent's shell (0.17.1+) may be a PTY, but the person who reads the code is not typing into it.
12
- if (ctx.json || !ctx.io.stdin.isTTY || !ctx.io.stdin.setRawMode || !ctx.io.stdin.on || codingAgentShell(ctx.io.env))
15
+ return !ctx.json && ctx.io.stdin.isTTY === true && typeof ctx.io.stdin.setRawMode === 'function' && typeof ctx.io.stdin.on === 'function' && !codingAgentShell(ctx.io.env);
16
+ }
17
+ export function requireClaudeTerminal(ctx) {
18
+ if (!claudeTerminal(ctx))
13
19
  throw new UsageError(NEED_TERMINAL, NEED_TERMINAL_HINT);
14
20
  }
15
21
  export async function cloudClaudeLogin(ctx) {
@@ -109,7 +115,7 @@ export async function runWorkspaceLogin(ctx, ws, cell, login) {
109
115
  for (const url of login.urls(output))
110
116
  if (!opened.has(url)) {
111
117
  opened.add(url);
112
- openUrl(url, ctx.io.env);
118
+ void openUrl(url, ctx.io.env);
113
119
  }
114
120
  }
115
121
  yield message;
@@ -121,9 +127,141 @@ export async function runWorkspaceLogin(ctx, ws, cell, login) {
121
127
  const ended = await cell.pty.get(login.session);
122
128
  return code === 0 && ended.state === 'exited' && ended.exit_code === 0;
123
129
  }
124
- /** A transient Linux profile holds the initial pair; it is removed before an agent starts. */
125
- export async function loginClaudeInWorkspace(ctx, ws) {
126
- requireClaudeTerminal(ctx);
130
+ /** How long Claude may take to exchange the pasted code and exit. */
131
+ const CODE_EXCHANGE_MS = 60_000;
132
+ /**
133
+ * The same sign-in without an attached terminal (0.20.1+: a coding agent's shell, --json, no TTY). The workspace PTY
134
+ * runs the login exactly as runWorkspaceLogin does; shard reads its output, opens the claude.ai link it prints, and
135
+ * opens a page on this computer where the person pastes the code claude.ai shows (the agent never sees it). The code
136
+ * and Enter go into the PTY; the page says "Signed in" once the login exited 0. True when it did.
137
+ */
138
+ export async function runWorkspaceLoginHeadless(ctx, cell, login, timeoutMs) {
139
+ await cell.pty.open({ session_id: login.session, argv: login.argv, env: login.env, cwd: login.cwd, rows: 24, cols: 100 });
140
+ const deadline = Date.now() + timeoutMs;
141
+ const stop = new AbortController();
142
+ const signal = AbortSignal.any([ctx.signal, stop.signal]);
143
+ // The PTY ended (or could not be read) before the code went in: the page closes with that.
144
+ const ended = new AbortController();
145
+ const connect = cellConnector(cell, login.session);
146
+ const cursor = new PtyOutputCursor();
147
+ const decoder = new StringDecoder('utf8');
148
+ const waiters = new Set();
149
+ const notify = () => { for (const wake of waiters)
150
+ wake(); waiters.clear(); };
151
+ let output = '';
152
+ let exit;
153
+ let failure;
154
+ let reading = true;
155
+ let codeSent = false;
156
+ const early = () => new CodedError('claude_sign_in_ended', 'The Claude sign-in ended before its code arrived.', 'run shard agents credentials login claude again');
157
+ const reader = (async () => {
158
+ let failures = 0;
159
+ while (!signal.aborted && exit === undefined) {
160
+ let connection;
161
+ try {
162
+ connection = await connect(cursor.offset, signal);
163
+ for await (const message of connection.messages()) {
164
+ failures = 0;
165
+ const bytes = cursor.consume(message);
166
+ if (bytes?.length)
167
+ output = (output + decoder.write(bytes)).slice(-16384);
168
+ if (message.type === 'exit') {
169
+ exit = message.session?.exit_code ?? null;
170
+ if (!codeSent)
171
+ ended.abort(early());
172
+ }
173
+ notify();
174
+ if (exit !== undefined || signal.aborted)
175
+ return;
176
+ }
177
+ }
178
+ catch (error) {
179
+ if (signal.aborted)
180
+ return;
181
+ if (!retryableAttach(error))
182
+ throw error;
183
+ failures += 1;
184
+ await new Promise((resolve) => {
185
+ const done = () => { clearTimeout(timer); signal.removeEventListener('abort', done); resolve(); };
186
+ const timer = setTimeout(done, reattachDelayMs(error, failures));
187
+ signal.addEventListener('abort', done, { once: true });
188
+ });
189
+ }
190
+ finally {
191
+ await connection?.close().catch(() => undefined);
192
+ }
193
+ }
194
+ })().catch((error) => {
195
+ failure = error;
196
+ if (!codeSent)
197
+ ended.abort(error instanceof Error ? error : early());
198
+ }).finally(() => { reading = false; notify(); });
199
+ /** Until `ready()`, at most `ms`: false when it did not happen (time out, or the reader stopped). */
200
+ const until = async (ready, ms) => {
201
+ const end = Date.now() + ms;
202
+ for (;;) {
203
+ if (ready())
204
+ return true;
205
+ if (failure !== undefined)
206
+ throw failure instanceof Error ? failure : new Error('The Claude sign-in terminal failed.');
207
+ if (!reading || Date.now() >= end)
208
+ return false;
209
+ await new Promise((resolve) => {
210
+ const timer = setTimeout(wake, end - Date.now());
211
+ function wake() { clearTimeout(timer); waiters.delete(wake); resolve(); }
212
+ waiters.add(wake);
213
+ });
214
+ }
215
+ };
216
+ try {
217
+ await until(() => login.urls(output).length > 0 || exit !== undefined, deadline - Date.now());
218
+ const url = login.urls(output)[0];
219
+ if (url === undefined)
220
+ return false;
221
+ const opened = await openInBrowser(ctx, url);
222
+ announce(ctx, { event: 'open', url, opened }, opened ? `Opened the Claude sign-in in your browser (or open: ${url})` : `Open the Claude sign-in: ${url}`);
223
+ await askInBrowser(ctx, {
224
+ kind: 'code',
225
+ title: 'Sign in to Claude',
226
+ purpose: 'Paste the code claude.ai shows after you sign in. Your coding agent never sees it.',
227
+ label: 'Code',
228
+ submit: 'Sign in',
229
+ done: 'Signed in to Claude',
230
+ failed: 'Not signed in',
231
+ link: { href: url, text: 'Open the Claude sign-in again' },
232
+ doing: 'to paste the Claude code',
233
+ event: 'ask',
234
+ maxBytes: 4096,
235
+ timeoutMs: Math.max(1_000, deadline - Date.now()),
236
+ signal: ended.signal,
237
+ // Once the code went in, the login either finishes or ends: there is nothing to retry on the page.
238
+ retry: () => false,
239
+ use: async (code) => {
240
+ if (exit !== undefined)
241
+ throw early();
242
+ codeSent = true;
243
+ await cell.pty.input(login.session, `${code}\r`);
244
+ if (!await until(() => exit !== undefined, CODE_EXCHANGE_MS))
245
+ throw new CodedError('claude_sign_in_timeout', 'Claude did not finish the sign-in within a minute of the code.', 'run shard agents credentials login claude again');
246
+ if (exit !== 0)
247
+ throw new CodedError('claude_code_refused', `Claude did not accept the code (exit ${exit ?? 'by signal'}).`, 'run shard agents credentials login claude again for a new code');
248
+ },
249
+ });
250
+ }
251
+ finally {
252
+ stop.abort();
253
+ await reader;
254
+ }
255
+ const session = await cell.pty.get(login.session);
256
+ return session.state === 'exited' && session.exit_code === 0;
257
+ }
258
+ /**
259
+ * A transient Linux profile holds the initial pair; it is removed before an agent starts. `headless` (0.20.1+): the
260
+ * sign-in runs through the pages shard opens (runWorkspaceLoginHeadless) instead of an attached terminal.
261
+ */
262
+ export async function loginClaudeInWorkspace(ctx, ws, opts = {}) {
263
+ if (!opts.headless)
264
+ requireClaudeTerminal(ctx);
127
265
  const api = ctx.cloud().agentCredentials;
128
266
  let claim;
129
267
  try {
@@ -147,7 +285,9 @@ export async function loginClaudeInWorkspace(ctx, ws) {
147
285
  const version = await cell.exec.run(['claude', '--version'], { env, cwd: root, signal: ctx.signal, timeoutMs: 10_000 });
148
286
  if (version.exitCode !== 0 || version.stdout.trim() !== `${VERSION} (Claude Code)`)
149
287
  throw new UsageError(`Cloud Claude sign-in requires Claude Code ${VERSION} in the workspace image.`);
150
- if (!await runWorkspaceLogin(ctx, ws, cell, { session, argv: ['claude', 'auth', 'login', '--claudeai'], env, cwd: root, urls: claudeLoginUrls }))
288
+ const login = { session, argv: ['claude', 'auth', 'login', '--claudeai'], env, cwd: root, urls: claudeLoginUrls };
289
+ const signedIn = opts.headless ? await runWorkspaceLoginHeadless(ctx, cell, login, opts.headless.timeoutMs) : await runWorkspaceLogin(ctx, ws, cell, login);
290
+ if (!signedIn)
151
291
  throw new UsageError('Claude cloud sign-in was interrupted. Run shard agents credentials login claude again.');
152
292
  const credential = JSON.parse(await cell.files.readText(`${root}/.claude/.credentials.json`));
153
293
  const config = JSON.parse(await cell.files.readText(`${root}/.claude/.claude.json`));
@@ -32,24 +32,14 @@ export declare const HUB_TEMPLATE = "codex-connector";
32
32
  /**
33
33
  * The codex-connector version a hub runs at least: the first with the hub's boot service and an sf-agent that serves
34
34
  * the VM port (cli-proxy-filter). New hubs are created on it; an older hub is upgraded in place (its home, and so its
35
- * logins and settings, stay) before VMs get the hub's address.
35
+ * logins and settings, stay) before VMs get the hub's address. A hub on a newer version keeps it (never downgraded).
36
36
  */
37
37
  export declare const HUB_TEMPLATE_VERSION = 4;
38
- /**
39
- * The codex-connector version whose sf-agent runs the account pool router: a hub set up for pooled accounts is created
40
- * on it, or upgraded to it. HUB_TEMPLATE_VERSION moves to it once it is registered in every environment.
41
- */
42
- export declare const POOL_HUB_TEMPLATE_VERSION = 5;
43
38
  export declare const HUB_CAPS: Caps;
44
39
  export declare const HUB_INFO_FILE = "cli-proxy.json";
45
40
  /** Paths in the hub workspace. */
46
41
  export declare const HUB_DIR = "/home/user/.cli-proxy-hub";
47
42
  export declare const HUB_AUTH_DIR = "/home/user/.cli-proxy-api";
48
- /** Pool mode: the router reads the API URL and the hub token (`sfh_…`) from these hub files (0600). */
49
- export declare const HUB_POOL_FILES: {
50
- readonly apiUrl: "/home/user/.cli-proxy-hub/api-url";
51
- readonly apiToken: "/home/user/.cli-proxy-hub/api-token";
52
- };
53
43
  export interface HubInfo {
54
44
  version: 1;
55
45
  api_url: string;
@@ -112,7 +102,10 @@ export interface HubWorkspace {
112
102
  write(path: string, data: string, mode: '0600' | '0700'): Promise<void>;
113
103
  /** Names in a directory, or null when it does not exist. */
114
104
  list(dir: string): Promise<string[] | null>;
115
- /** The hub's auth files, as paths relative to its auth directory (subdirectories included, as CLIProxyAPI reads them). */
105
+ /**
106
+ * The hub's auth files, as paths relative to its auth directory (subdirectories included, as CLIProxyAPI reads them),
107
+ * oldest first: by modification time, then by path.
108
+ */
116
109
  authFiles(): Promise<string[]>;
117
110
  /** Exposes the port (idempotent): when its callback URL was created, null without one. */
118
111
  exposePort(port: number): Promise<{
@@ -167,6 +160,8 @@ export declare function sdkHubCloud(cloud: Shardflux, opts?: {
167
160
  agentLabel?: string;
168
161
  homeProject?: (organizationId: string) => Promise<HomeProject | null>;
169
162
  }): HubCloud;
163
+ /** The hub's auth files, oldest first (modification time, then path in byte order), one relative path per line. */
164
+ export declare const AUTH_FILES_SCRIPT = "set -euo pipefail\n[ -d /home/user/.cli-proxy-api ] || exit 0; cd /home/user/.cli-proxy-api\nfind . -type f -name '*.json' -printf '%T@ %P\\n' | LC_ALL=C sort -t ' ' -k1,1n -k2 | cut -d ' ' -f 2-\n";
170
165
  /** Installs the pinned release once: download, verify its SHA-256, then move the binary into place. */
171
166
  export declare const INSTALL_SCRIPT: string;
172
167
  /**
@@ -228,15 +223,6 @@ export interface EnsureOptions {
228
223
  progress?: (message: string) => void;
229
224
  /** Random management key (tests). */
230
225
  randomKey?: () => string;
231
- /**
232
- * Pool mode (account pool): the hub's router gets the API URL and a hub token, and CLIProxyAPI passes the providers'
233
- * rate-limit headers through so the router reads usage on every answer. `hubToken` mints a token for the hub
234
- * workspace (`POST /v1/agent-pool/hub-token`); it is called when the hub has none, or always with `renew`.
235
- */
236
- pool?: {
237
- hubToken: (workspaceId: string) => Promise<string>;
238
- renew?: boolean;
239
- };
240
226
  }
241
227
  export interface EnsureResult {
242
228
  info: HubInfo;
@@ -246,6 +232,8 @@ export interface EnsureResult {
246
232
  callbackReplaced: boolean;
247
233
  /** The config written now; null when the hub kept its own. */
248
234
  config: HubConfig | null;
235
+ /** The hub's logins, each enabled or disabled as the user left it. */
236
+ logins: HubLogin[];
249
237
  }
250
238
  /** The hub could not be made ready (exit 1, code `cli_proxy_hub`). */
251
239
  export declare class HubError extends CodedError {
@@ -258,11 +246,59 @@ export declare class HubError extends CodedError {
258
246
  */
259
247
  export declare function ensureCliProxyHub(cloud: Shardflux | HubCloud, opts: EnsureOptions): Promise<EnsureResult>;
260
248
  /**
261
- * The hub config with the providers' rate-limit headers passed through (`requests.passthrough-headers`, or the legacy
262
- * top-level key): CLIProxyAPI 8.0.23 records them per login only for non-streaming answers, so the pool router reads
263
- * them on its own answers. Returns the text unchanged when it already passes them.
249
+ * A login in the hub: one auth file. Only its provider, email and state are ever shown, never its tokens.
250
+ *
251
+ * The hub is the user's own CLIProxyAPI: it routes requests across the enabled logins exactly as its config says
252
+ * (round-robin, fill-first, session affinity, retries), the user's routing and their choice. A login with
253
+ * `"disabled": true` stays in the hub and is never used (CLIProxyAPI 8.0.23); `shard cli-proxy use` switches logins on
254
+ * and off.
255
+ */
256
+ export interface HubLogin {
257
+ /** Path relative to the auth directory. */
258
+ file: string;
259
+ /** The auth file's `type` (claude, codex, gemini…), lowercase; null when it has none. */
260
+ provider: string | null;
261
+ email: string | null;
262
+ /** The file carries `"disabled": true`. */
263
+ disabled: boolean;
264
+ /** It serves requests: enabled, with a provider, and the user's (not a file the retired account pool router left). */
265
+ active: boolean;
266
+ }
267
+ /** The hub's logins, oldest first, as they are (nothing changes). Files the retired account pool router left are not listed. */
268
+ export declare function listHubLogins(ws: HubWorkspace): Promise<HubLogin[]>;
269
+ /** The auth file's text with `disabled` set (true) or removed (false), every other field kept; null when unchanged. */
270
+ export declare function withDisabled(text: string, disabled: boolean): string | null;
271
+ /**
272
+ * Replaces one auth file atomically (0600, a temporary file in its folder then rename), only while it still holds what
273
+ * the device read ($2, its SHA-256): CLIProxyAPI rewrites a file when it refreshes the login, and a newer token must
274
+ * never be overwritten. The modification time stays, so the order of the hub's logins (oldest first) does not change.
275
+ * $1 is the path relative to the auth directory, the new content comes on standard input. Prints `written`, `changed`
276
+ * or `gone`.
277
+ */
278
+ export declare const SET_AUTH_SCRIPT = "set -euo pipefail\na=/home/user/.cli-proxy-api; rel=$1; want=$2\ncase \"$rel\" in ''|/*|*..*) echo \"not a path inside the auth folder\" >&2; exit 2 ;; esac\nf=\"$a/$rel\"\n[ -f \"$f\" ] || { echo gone; exit 0; }\ntmp=$(mktemp \"$(dirname \"$f\")/.writing.XXXXXX\"); trap 'rm -f \"$tmp\"' EXIT\nchmod 0600 \"$tmp\"; cat > \"$tmp\"; touch -r \"$f\" \"$tmp\"\nif [ \"$(sha256sum < \"$f\" | cut -d' ' -f1)\" != \"$want\" ]; then echo changed; exit 0; fi\nmv -f \"$tmp\" \"$f\"; trap - EXIT\necho written\n";
279
+ /**
280
+ * Under the hub's `logins` lock: the retired account pool router's files stay disabled (they were never the user's);
281
+ * every other login keeps the state its owner gave it. Returns the logins as they are now, oldest first.
282
+ */
283
+ export declare function settleHubLogins(ws: HubWorkspace): Promise<HubLogin[]>;
284
+ /**
285
+ * The user's choice of which logins serve requests (`shard cli-proxy use`; call it under the hub's `logins` lock).
286
+ * `only[provider]` names the one login of that provider that stays enabled (it is enabled first, then the provider's
287
+ * other logins are disabled, so the provider is never without one); `all` enables every login of the hub, which then
288
+ * routes across them as its config says. Providers the choice does not name are left as they are. Returns the logins
289
+ * as they are now, oldest first.
264
290
  */
265
- export declare function poolHubConfig(text: string): string;
291
+ export declare function useHubLogins(ws: HubWorkspace, choice: {
292
+ only?: Record<string, string>;
293
+ all?: boolean;
294
+ }): Promise<HubLogin[]>;
295
+ /** Where CLI 0.22.0 reads each provider's one login ({"<provider>": "<auth file>"}); written atomically (0600). */
296
+ export declare const LEGACY_ACTIVE_FILE = "/home/user/.cli-proxy-hub/active.json";
297
+ export declare const LEGACY_ACTIVE_SCRIPT = "set -euo pipefail\ndir=/home/user/.cli-proxy-hub; mkdir -p \"$dir\"; umask 077\ntmp=$(mktemp \"$dir/.active.XXXXXX\"); trap 'rm -f \"$tmp\"' EXIT\ncat > \"$tmp\"; mv -f \"$tmp\" \"$dir/active.json\"; trap - EXIT\necho written\n";
298
+ /** Removes the router's files; prints `retired` when its hub token was there (its sf-agent then starts again without it). */
299
+ export declare const RETIRE_POOL_SCRIPT: string;
300
+ /** The config without the router's own client key (`api-keys`, or v8 `access.api-keys`); the text itself when absent. */
301
+ export declare function withoutClientKey(text: string, key: string): string;
266
302
  /** The callback URL as the hub stores it (`callback.json`, 0600): its secret is shown only when it is created. */
267
303
  export interface StoredCallback {
268
304
  callback_url: string;