@shardflux/cli 0.22.0 → 0.23.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/README.md +47 -14
  3. package/dist/acp-journal.js +58 -6
  4. package/dist/acp-login.d.ts +10 -3
  5. package/dist/acp-login.js +31 -4
  6. package/dist/acp-models.d.ts +6 -1
  7. package/dist/acp-models.js +21 -16
  8. package/dist/acp.js +60 -7
  9. package/dist/agent-commands.js +29 -15
  10. package/dist/agent-target-commands.d.ts +20 -1
  11. package/dist/agent-target-commands.js +49 -18
  12. package/dist/agent-targets.js +6 -1
  13. package/dist/args.d.ts +1 -1
  14. package/dist/args.js +14 -14
  15. package/dist/cli-proxy-forward.d.ts +0 -2
  16. package/dist/cli-proxy-forward.js +2 -9
  17. package/dist/cli-proxy-hub.d.ts +22 -30
  18. package/dist/cli-proxy-hub.js +65 -94
  19. package/dist/cli-proxy.d.ts +12 -38
  20. package/dist/cli-proxy.js +51 -168
  21. package/dist/codex-cloud.d.ts +9 -4
  22. package/dist/codex-cloud.js +47 -14
  23. package/dist/codex-linux.d.ts +14 -0
  24. package/dist/codex-linux.js +69 -0
  25. package/dist/command-center-actions.js +13 -4
  26. package/dist/command-center-model.d.ts +2 -0
  27. package/dist/command-center-model.js +6 -1
  28. package/dist/command-center.js +3 -3
  29. package/dist/commands.js +2 -2
  30. package/dist/conversation-commands.js +16 -2
  31. package/dist/conversation-terminal.js +3 -2
  32. package/dist/conversations.d.ts +14 -1
  33. package/dist/conversations.js +61 -7
  34. package/dist/desktop-account.d.ts +16 -0
  35. package/dist/desktop-account.js +29 -0
  36. package/dist/desktop-linux.js +16 -3
  37. package/dist/errors.d.ts +2 -1
  38. package/dist/errors.js +5 -2
  39. package/dist/filesystem-commands.js +25 -3
  40. package/dist/menubar.d.ts +6 -5
  41. package/dist/menubar.js +40 -28
  42. package/dist/move-worktrees.d.ts +13 -7
  43. package/dist/move-worktrees.js +68 -17
  44. package/dist/move.js +20 -25
  45. package/dist/onboarding.js +4 -2
  46. package/dist/prepared-repository.d.ts +2 -0
  47. package/dist/prepared-repository.js +1 -1
  48. package/dist/prepared-repository.py +3 -2
  49. package/dist/project-preparation.d.ts +14 -0
  50. package/dist/project-preparation.js +16 -4
  51. package/dist/project-sync.d.ts +3 -0
  52. package/dist/project-sync.js +4 -1
  53. package/dist/t3-launch.d.ts +2 -1
  54. package/dist/t3-launch.js +11 -2
  55. package/dist/t3-proxy.d.ts +81 -0
  56. package/dist/t3-proxy.js +325 -0
  57. package/dist/terminal-apps.d.ts +11 -0
  58. package/dist/terminal-apps.js +18 -5
  59. package/dist/update.d.ts +2 -0
  60. package/dist/update.js +32 -6
  61. package/package.json +5 -5
@@ -40,12 +40,6 @@ export declare const HUB_INFO_FILE = "cli-proxy.json";
40
40
  /** Paths in the hub workspace. */
41
41
  export declare const HUB_DIR = "/home/user/.cli-proxy-hub";
42
42
  export declare const HUB_AUTH_DIR = "/home/user/.cli-proxy-api";
43
- /**
44
- * Each provider's active login: `{ "<provider type>": "<auth file, relative to the auth directory>" }` (0600). The one
45
- * login of each provider that stays enabled in the hub; every other login of that provider is kept there with
46
- * `"disabled": true`. Written by the CLI and by the hub's sf-agent, both under the hub's `logins.lock`.
47
- */
48
- export declare const HUB_ACTIVE_FILE = "/home/user/.cli-proxy-hub/active.json";
49
43
  export interface HubInfo {
50
44
  version: 1;
51
45
  api_url: string;
@@ -238,7 +232,7 @@ export interface EnsureResult {
238
232
  callbackReplaced: boolean;
239
233
  /** The config written now; null when the hub kept its own. */
240
234
  config: HubConfig | null;
241
- /** The hub's logins, one active per provider (the others kept disabled). */
235
+ /** The hub's logins, each enabled or disabled as the user left it. */
242
236
  logins: HubLogin[];
243
237
  }
244
238
  /** The hub could not be made ready (exit 1, code `cli_proxy_hub`). */
@@ -254,10 +248,10 @@ export declare function ensureCliProxyHub(cloud: Shardflux | HubCloud, opts: Ens
254
248
  /**
255
249
  * A login in the hub: one auth file. Only its provider, email and state are ever shown, never its tokens.
256
250
  *
257
- * CLIProxyAPI spreads requests across every enabled login of a provider (round-robin, fill-first) and moves to another
258
- * one on a 429, so the hub keeps exactly one enabled per provider: the user's active login. Every other login of that
259
- * provider stays in the hub with `"disabled": true` (CLIProxyAPI 8.0.23 never uses a disabled auth file) until the
260
- * user makes it active (`shard cli-proxy use`).
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.
261
255
  */
262
256
  export interface HubLogin {
263
257
  /** Path relative to the auth directory. */
@@ -267,15 +261,10 @@ export interface HubLogin {
267
261
  email: string | null;
268
262
  /** The file carries `"disabled": true`. */
269
263
  disabled: boolean;
270
- /** The one login of its provider that serves requests. */
264
+ /** It serves requests: enabled, with a provider, and the user's (not a file the retired account pool router left). */
271
265
  active: boolean;
272
266
  }
273
- /** Parses active.json: provider type → auth file. Anything else (missing, not JSON, other values) counts as empty. */
274
- export declare function parseActive(text: string | null): Record<string, string>;
275
- /**
276
- * The hub's logins, oldest first, as they are (nothing changes): `active` marks the one active.json names while it is
277
- * enabled. Files the retired account pool router left are not listed (they stay disabled).
278
- */
267
+ /** The hub's logins, oldest first, as they are (nothing changes). Files the retired account pool router left are not listed. */
279
268
  export declare function listHubLogins(ws: HubWorkspace): Promise<HubLogin[]>;
280
269
  /** The auth file's text with `disabled` set (true) or removed (false), every other field kept; null when unchanged. */
281
270
  export declare function withDisabled(text: string, disabled: boolean): string | null;
@@ -287,22 +276,25 @@ export declare function withDisabled(text: string, disabled: boolean): string |
287
276
  * or `gone`.
288
277
  */
289
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";
290
- /** Writes active.json atomically (0600) from standard input. */
291
- export declare const 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";
292
279
  /**
293
- * Keeps exactly one login enabled per provider (call it under the hub's `logins` lock, so two computers do not race):
294
- * `prefer[provider]` when given, else the one active.json names while it is present and enabled, else the oldest.
295
- * active.json records the choice first, then every other login of that provider is disabled (all other fields kept), then
296
- * the chosen one is enabled. Files without a `type` are left alone; the retired pool router's files are
297
- * disabled and never chosen. Returns the logins as they are now, oldest first (without the pool router's).
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.
298
282
  */
299
- export declare function enforceActiveLogins(ws: HubWorkspace, prefer?: Record<string, string>): Promise<HubLogin[]>;
283
+ export declare function settleHubLogins(ws: HubWorkspace): Promise<HubLogin[]>;
300
284
  /**
301
- * One login per provider on a hub that may predate it, without waking the hub: a hub that is not running is left
302
- * alone (it is checked again later). The forwarder on every connected computer runs this at start and every 15
303
- * minutes, so a hub set up before this rule stops spreading requests across logins without anyone running a command.
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.
304
290
  */
305
- export declare function enforceHubLoginsIfRunning(hub: HubCloud, workspaceId: string): Promise<'enforced' | 'asleep' | 'missing'>;
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";
306
298
  /** Removes the router's files; prints `retired` when its hub token was there (its sf-agent then starts again without it). */
307
299
  export declare const RETIRE_POOL_SCRIPT: string;
308
300
  /** The config without the router's own client key (`api-keys`, or v8 `access.api-keys`); the text itself when absent. */
@@ -50,12 +50,6 @@ export const HUB_INFO_FILE = 'cli-proxy.json';
50
50
  /** Paths in the hub workspace. */
51
51
  export const HUB_DIR = '/home/user/.cli-proxy-hub';
52
52
  export const HUB_AUTH_DIR = '/home/user/.cli-proxy-api';
53
- /**
54
- * Each provider's active login: `{ "<provider type>": "<auth file, relative to the auth directory>" }` (0600). The one
55
- * login of each provider that stays enabled in the hub; every other login of that provider is kept there with
56
- * `"disabled": true`. Written by the CLI and by the hub's sf-agent, both under the hub's `logins.lock`.
57
- */
58
- export const HUB_ACTIVE_FILE = `${HUB_DIR}/active.json`;
59
53
  const CALLBACK_PATH = /^\/__shardflux\/callback\/sfcb_[A-Za-z0-9_-]+\/$/;
60
54
  function callbackUrlOk(cb) {
61
55
  try {
@@ -661,8 +655,8 @@ export async function ensureCliProxyHub(cloud, opts) {
661
655
  // A hub that once ran the retired account pool router: its files go, and its sf-agent starts again without it.
662
656
  if (await retirePool(ws))
663
657
  restart = true;
664
- // One login per provider serves requests: the rest stay in the hub, disabled.
665
- const logins = await ws.withLock('logins', () => enforceActiveLogins(ws));
658
+ // The user's logins stay as they are; only the retired account pool router's files are kept disabled.
659
+ const logins = await ws.withLock('logins', () => settleHubLogins(ws));
666
660
  const started = await ws.sh(START_SCRIPT, [restart ? '1' : '0'], { timeoutMs: 180_000 });
667
661
  const startLines = started.stdout.trim().split('\n');
668
662
  const state = startLines.find((l) => ['running', 'started', 'restarted'].includes(l));
@@ -684,23 +678,6 @@ export async function ensureCliProxyHub(cloud, opts) {
684
678
  writeHubInfo(opts.dir, info);
685
679
  return { info, created, process: state, callbackReplaced, config, logins };
686
680
  }
687
- /** Parses active.json: provider type → auth file. Anything else (missing, not JSON, other values) counts as empty. */
688
- export function parseActive(text) {
689
- let v;
690
- try {
691
- v = JSON.parse(text ?? '');
692
- }
693
- catch {
694
- return {};
695
- }
696
- if (!v || typeof v !== 'object' || Array.isArray(v))
697
- return {};
698
- const out = {};
699
- for (const [k, f] of Object.entries(v))
700
- if (typeof f === 'string' && f)
701
- out[k.toLowerCase()] = f;
702
- return out;
703
- }
704
681
  const docOf = (text) => {
705
682
  try {
706
683
  const v = JSON.parse(text ?? '');
@@ -710,16 +687,13 @@ const docOf = (text) => {
710
687
  return null;
711
688
  }
712
689
  };
713
- /**
714
- * The hub's logins, oldest first, as they are (nothing changes): `active` marks the one active.json names while it is
715
- * enabled. Files the retired account pool router left are not listed (they stay disabled).
716
- */
690
+ const publicLogins = (logins) => logins.filter((l) => !l.pool).map(({ file, provider, email, disabled, active }) => ({ file, provider, email, disabled, active }));
691
+ /** The hub's logins, oldest first, as they are (nothing changes). Files the retired account pool router left are not listed. */
717
692
  export async function listHubLogins(ws) {
718
- const logins = await readHubLogins(ws, parseActive(await ws.read(HUB_ACTIVE_FILE)));
719
- return logins.filter((l) => !l.pool).map(({ file, provider, email, disabled, active }) => ({ file, provider, email, disabled, active }));
693
+ return publicLogins(await readHubLogins(ws));
720
694
  }
721
695
  /** The hub's logins, oldest first (the order authFiles lists them in). */
722
- async function readHubLogins(ws, stored) {
696
+ async function readHubLogins(ws) {
723
697
  const files = await ws.authFiles();
724
698
  const texts = await Promise.all(files.map((f) => ws.read(`${HUB_AUTH_DIR}/${f}`)));
725
699
  return files.map((file, i) => {
@@ -727,10 +701,10 @@ async function readHubLogins(ws, stored) {
727
701
  const doc = docOf(text);
728
702
  const type = typeof doc?.type === 'string' && doc.type.trim() ? doc.type.trim().toLowerCase() : null;
729
703
  const email = typeof doc?.email === 'string' && doc.email ? doc.email : null;
730
- // The retired pool router's files (`sfa-<account id>.json`, `"prefix": "sfa-…"`) are never a user's active login.
704
+ // The retired pool router's files (`sfa-<account id>.json`, `"prefix": "sfa-…"`) were never the user's logins.
731
705
  const pool = /^sfa-[^/]+\.json$/.test(file) || (typeof doc?.prefix === 'string' && doc.prefix.startsWith('sfa-'));
732
706
  const disabled = doc?.disabled === true;
733
- return { file, provider: type, email, disabled, active: !pool && !disabled && type !== null && stored[type] === file, text, doc, pool };
707
+ return { file, provider: type, email, disabled, active: !pool && !disabled && type !== null, text, doc, pool };
734
708
  });
735
709
  }
736
710
  /** The auth file's text with `disabled` set (true) or removed (false), every other field kept; null when unchanged. */
@@ -762,13 +736,6 @@ if [ "$(sha256sum < "$f" | cut -d' ' -f1)" != "$want" ]; then echo changed; exit
762
736
  mv -f "$tmp" "$f"; trap - EXIT
763
737
  echo written
764
738
  `;
765
- /** Writes active.json atomically (0600) from standard input. */
766
- export const ACTIVE_SCRIPT = `set -euo pipefail
767
- dir=${HUB_DIR}; mkdir -p "$dir"; umask 077
768
- tmp=$(mktemp "$dir/.active.XXXXXX"); trap 'rm -f "$tmp"' EXIT
769
- cat > "$tmp"; mv -f "$tmp" "$dir/active.json"; trap - EXIT
770
- echo written
771
- `;
772
739
  const sha256 = (text) => createHash('sha256').update(text, 'utf8').digest('hex');
773
740
  /** Sets one login's `disabled` (true) or clears it (false), reading the file again when CLIProxyAPI changed it meanwhile. */
774
741
  async function setDisabled(ws, login, disabled) {
@@ -790,66 +757,70 @@ async function setDisabled(ws, login, disabled) {
790
757
  throw new HubError(`${login.file} kept changing in the hub while it was being ${disabled ? 'disabled' : 'enabled'}.`, 'run: shard cli-proxy import');
791
758
  }
792
759
  /**
793
- * Keeps exactly one login enabled per provider (call it under the hub's `logins` lock, so two computers do not race):
794
- * `prefer[provider]` when given, else the one active.json names while it is present and enabled, else the oldest.
795
- * active.json records the choice first, then every other login of that provider is disabled (all other fields kept), then
796
- * the chosen one is enabled. Files without a `type` are left alone; the retired pool router's files are
797
- * disabled and never chosen. Returns the logins as they are now, oldest first (without the pool router's).
760
+ * Under the hub's `logins` lock: the retired account pool router's files stay disabled (they were never the user's);
761
+ * every other login keeps the state its owner gave it. Returns the logins as they are now, oldest first.
798
762
  */
799
- export async function enforceActiveLogins(ws, prefer = {}) {
800
- const storedText = await ws.read(HUB_ACTIVE_FILE);
801
- const stored = parseActive(storedText);
802
- const logins = await readHubLogins(ws, stored);
803
- const chosen = {};
804
- for (const login of logins) {
805
- const p = login.provider;
806
- if (p === null || login.pool || chosen[p] !== undefined)
807
- continue;
808
- const mine = logins.filter((l) => l.provider === p && !l.pool);
809
- const wanted = prefer[p];
810
- if (wanted !== undefined && !mine.some((l) => l.file === wanted))
811
- throw new HubError(`The hub has no ${p} login ${wanted} any more.`, 'see the hub’s logins: shard cli-proxy');
812
- const current = mine.find((l) => l.file === stored[p] && !l.disabled);
813
- chosen[p] = wanted ?? current?.file ?? login.file;
814
- }
815
- // Record the choice first, then disable the rest, then enable the chosen login: two logins of one provider are never
816
- // enabled at the same moment (CLIProxyAPI would fail over between them), at the cost of an instant with none during a
817
- // switch. The hub's sf-agent keeps one enabled login per provider on every change (the one active.json names, else
818
- // the oldest enabled, never enabling one), so in this order it never disables the chosen login.
819
- const text = `${JSON.stringify(Object.fromEntries(Object.entries(chosen).sort(([a], [b]) => a.localeCompare(b))), null, 2)}\n`;
820
- if (text !== storedText && (storedText !== null || Object.keys(chosen).length > 0)) {
821
- const r = await ws.sh(ACTIVE_SCRIPT, [], { stdin: text });
822
- if (r.code !== 0)
823
- throw new HubError(`The hub did not record its active logins: ${r.stderr.trim().split('\n').at(-1) || `exit ${r.code}`}`, 'run: shard cli-proxy import');
824
- }
763
+ export async function settleHubLogins(ws) {
764
+ const logins = await readHubLogins(ws);
825
765
  for (const l of logins)
826
- if (l.provider !== null && chosen[l.provider] !== l.file && !l.disabled)
766
+ if (l.pool && !l.disabled)
827
767
  await setDisabled(ws, l, true);
828
- for (const l of logins)
829
- if (l.provider !== null && chosen[l.provider] === l.file && l.disabled)
830
- await setDisabled(ws, l, false);
831
- return logins.filter((l) => !l.pool).map(({ file, provider, email, disabled }) => {
832
- const active = provider !== null && chosen[provider] === file;
833
- return { file, provider, email, disabled: provider === null ? disabled : !active, active };
834
- });
768
+ return publicLogins(logins);
835
769
  }
836
770
  /**
837
- * One login per provider on a hub that may predate it, without waking the hub: a hub that is not running is left
838
- * alone (it is checked again later). The forwarder on every connected computer runs this at start and every 15
839
- * minutes, so a hub set up before this rule stops spreading requests across logins without anyone running a command.
771
+ * The user's choice of which logins serve requests (`shard cli-proxy use`; call it under the hub's `logins` lock).
772
+ * `only[provider]` names the one login of that provider that stays enabled (it is enabled first, then the provider's
773
+ * other logins are disabled, so the provider is never without one); `all` enables every login of the hub, which then
774
+ * routes across them as its config says. Providers the choice does not name are left as they are. Returns the logins
775
+ * as they are now, oldest first.
840
776
  */
841
- export async function enforceHubLoginsIfRunning(hub, workspaceId) {
842
- const state = await hub.state(workspaceId);
843
- if (state === null)
844
- return 'missing';
845
- if (state !== 'running')
846
- return 'asleep';
847
- const opened = await hub.open(false);
848
- if (!opened || opened.ws.id !== workspaceId)
849
- return 'missing';
850
- await opened.ws.withLock('logins', () => enforceActiveLogins(opened.ws));
851
- return 'enforced';
777
+ export async function useHubLogins(ws, choice) {
778
+ const logins = await readHubLogins(ws);
779
+ const only = choice.only ?? {};
780
+ for (const [p, file] of Object.entries(only)) {
781
+ if (!logins.some((l) => !l.pool && l.provider === p && l.file === file))
782
+ throw new HubError(`The hub has no ${p} login ${file} any more.`, 'see the hub’s logins: shard cli-proxy');
783
+ }
784
+ const wanted = (l) => {
785
+ if (l.pool || l.provider === null)
786
+ return null;
787
+ if (only[l.provider] !== undefined)
788
+ return only[l.provider] === l.file;
789
+ return choice.all ? true : null;
790
+ };
791
+ for (const l of logins)
792
+ if (wanted(l) === true && l.disabled)
793
+ await setDisabled(ws, l, false);
794
+ for (const l of logins)
795
+ if (wanted(l) === false && !l.disabled)
796
+ await setDisabled(ws, l, true);
797
+ // Computers still on CLI 0.22.0 keep the login active.json names for each provider: it names this choice too.
798
+ if (Object.keys(only).length) {
799
+ const text = await ws.read(LEGACY_ACTIVE_FILE);
800
+ let stored = {};
801
+ try {
802
+ const v = JSON.parse(text ?? '');
803
+ if (v && typeof v === 'object' && !Array.isArray(v))
804
+ stored = v;
805
+ }
806
+ catch { /* none yet */ }
807
+ const next = `${JSON.stringify({ ...stored, ...only }, null, 2)}\n`;
808
+ if (next !== text) {
809
+ const r = await ws.sh(LEGACY_ACTIVE_SCRIPT, [], { stdin: next });
810
+ if (r.code !== 0)
811
+ throw new HubError(`The hub did not record the choice: ${r.stderr.trim().split('\n').at(-1) || `exit ${r.code}`}`, 'run: shard cli-proxy use <email>');
812
+ }
813
+ }
814
+ return listHubLogins(ws);
852
815
  }
816
+ /** Where CLI 0.22.0 reads each provider's one login ({"<provider>": "<auth file>"}); written atomically (0600). */
817
+ export const LEGACY_ACTIVE_FILE = `${HUB_DIR}/active.json`;
818
+ export const LEGACY_ACTIVE_SCRIPT = `set -euo pipefail
819
+ dir=${HUB_DIR}; mkdir -p "$dir"; umask 077
820
+ tmp=$(mktemp "$dir/.active.XXXXXX"); trap 'rm -f "$tmp"' EXIT
821
+ cat > "$tmp"; mv -f "$tmp" "$dir/active.json"; trap - EXIT
822
+ echo written
823
+ `;
853
824
  /** What the retired account pool router kept in the hub (codex-connector 5 with pooled accounts). */
854
825
  const POOL_LEFTOVERS = ['api-token', 'api-url', 'pool.json', 'pool-creds.json', 'pool-status.json', 'pool-client.key', 'pool.lock'];
855
826
  /** Removes the router's files; prints `retired` when its hub token was there (its sf-agent then starts again without it). */
@@ -213,14 +213,9 @@ export interface HubAccount {
213
213
  disabled: boolean;
214
214
  /** The auth file, relative to the hub's auth directory (null when CLIProxyAPI does not name it). */
215
215
  file: string | null;
216
- /** The one login of its provider that serves requests (the others stay in the hub, disabled). */
216
+ /** It serves requests (enabled); a disabled login stays in the hub unused. */
217
217
  active: boolean;
218
218
  }
219
- /**
220
- * Marks each provider's active login: an enabled login whose file is one of `activeFiles` (the hub's active.json).
221
- * Without them (unreadable), a provider's only enabled login is its active one.
222
- */
223
- export declare function markActive(accounts: HubAccount[], activeFiles: ReadonlySet<string> | null): HubAccount[];
224
219
  /** The hub's logins from its management API (wakes the hub). */
225
220
  export declare function hubAccounts(info: HubInfo, timeoutMs?: number): Promise<HubAccount[]>;
226
221
  /** Whether the hub answers /healthz through its callback URL, and the edge's reason when it does not. */
@@ -253,8 +248,8 @@ export interface ImportResult {
253
248
  };
254
249
  /**
255
250
  * added: accounts this import gave the hub; already: accounts the hub had (its tokens stay); moved_aside: where this
256
- * computer's auth files went once the hub had them (`<dir>.moved-to-shardflux`). active: each provider's one login
257
- * that serves requests; disabled: the provider's other logins, kept in the hub (shard cli-proxy use switches).
251
+ * computer's auth files went once the hub had them (`<dir>.moved-to-shardflux`). active: the logins that serve
252
+ * requests; disabled: logins kept in the hub unused (as they were on the computer, or by --use).
258
253
  */
259
254
  logins: {
260
255
  uploaded: string[];
@@ -301,9 +296,9 @@ export declare function matchLogins<T extends {
301
296
  * one: the hub's copy may hold a newer refresh token), start the forwarder on 127.0.0.1:8317 and put the hub info into
302
297
  * the machine template.
303
298
  *
304
- * One login per provider serves requests. `--use <email|file>` (repeatable, one per provider) picks it; otherwise a
305
- * provider the hub already serves keeps its active login, and a provider new to the hub starts with this computer's
306
- * oldest login of it. Every other login is uploaded with `"disabled": true` and kept in the hub.
299
+ * Each login arrives as it is on this computer (enabled or disabled), and the hub routes across the enabled ones as
300
+ * the user's config says. `--use <email|file>` (repeatable, one per provider) makes that login the only one serving
301
+ * its provider (the provider's others are kept in the hub, disabled), as `shard cli-proxy use` does.
307
302
  */
308
303
  export declare function importCliProxy(ctx: Ctx, opts?: ImportOptions): Promise<ImportResult>;
309
304
  export interface ExportOptions {
@@ -335,46 +330,25 @@ export interface ExportResult {
335
330
  */
336
331
  export declare function exportCliProxy(ctx: Ctx, opts?: ExportOptions): Promise<ExportResult>;
337
332
  export interface UseResult {
338
- /** Each provider this command set, with the login it had and the one it has now. */
333
+ /** Each provider this command set to one login, with the login that served it alone before (null if not one) and the one now. */
339
334
  switched: Array<{
340
335
  provider: string;
341
336
  from: LoginRef | null;
342
337
  to: LoginRef;
343
338
  }>;
344
- /** Every provider's active login, and the other logins kept in the hub, disabled. */
339
+ /** The logins that serve requests now, and the ones kept in the hub, disabled. */
345
340
  active: LoginRef[];
346
341
  disabled: LoginRef[];
347
342
  }
348
343
  /**
349
- * `shard cli-proxy use <email|file>`: makes that login its provider's active one in the hub (an email with a Claude
350
- * and a Codex login sets both); the provider's other logins stay in the hub, disabled. Under the hub's logins lock.
344
+ * `shard cli-proxy use <email|file>`: that login alone serves its provider (an email with a Claude and a Codex login
345
+ * sets both); the provider's other logins stay in the hub, disabled. `--all`: every login serves again, and the hub
346
+ * routes across them as its config says. Under the hub's logins lock.
351
347
  */
352
348
  export declare function useCliProxyLogin(ctx: Ctx, spec: string, opts?: {
353
349
  hub?: HubCloud;
350
+ all?: boolean;
354
351
  }): Promise<UseResult>;
355
- /** How often the forwarder checks that the hub serves one login per provider while it runs. */
356
- export declare const HUB_LOGIN_CHECK_MS: number;
357
- /** How soon it looks again when the hub was asleep (a VM's request may wake it any moment; looking never wakes it). */
358
- export declare const HUB_LOGIN_ASLEEP_MS = 60000;
359
- /** The least time between two checks a request asks for (requests come in bursts). */
360
- export declare const HUB_LOGIN_NUDGE_MS = 10000;
361
- export interface HubLoginChecks {
362
- /** A request arrived: the hub is awake or waking. Checks now unless a check found it running and fixed recently. */
363
- nudge(): void;
364
- stop(): void;
365
- }
366
- /**
367
- * The forwarder's check that the hub serves one login per provider (enforceHubLoginsIfRunning), never waking the hub:
368
- * at start; then every HUB_LOGIN_CHECK_MS while it runs and every HUB_LOGIN_ASLEEP_MS while it sleeps; and at the first
369
- * request after it slept (nudge), so a hub set up before one login per provider cannot fail over between logins for
370
- * long after it wakes. One check at a time.
371
- */
372
- export declare function hubLoginChecks(ctx: Ctx, source: () => HubInfo | null, log: (line: string) => void, opts?: {
373
- hub?: HubCloud;
374
- everyMs?: number;
375
- asleepMs?: number;
376
- nudgeMs?: number;
377
- }): HubLoginChecks;
378
352
  /**
379
353
  * After the edge refused the hub's callback URL (`callback_invalid`: the API key that created it was revoked):
380
354
  * replaces the URLs the edge refuses (under the hub's lock), saves them locally and gives VMs the new VM address.