sidekick-shared 0.27.5 → 0.27.6

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 (56) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +35 -34
  3. package/dist/accountHealth.d.ts +14 -0
  4. package/dist/accountHealth.js +61 -14
  5. package/dist/accountLaunch.d.ts +6 -0
  6. package/dist/accountLaunch.js +61 -4
  7. package/dist/accountLease.d.ts +5 -0
  8. package/dist/accountLease.js +98 -0
  9. package/dist/accountManager.d.ts +7 -0
  10. package/dist/accountManager.js +226 -89
  11. package/dist/accountSync.d.ts +1 -5
  12. package/dist/accountSync.js +22 -6
  13. package/dist/accountSyncTypes.d.ts +9 -0
  14. package/dist/accounts.d.ts +75 -18
  15. package/dist/accounts.js +712 -189
  16. package/dist/claudeCredentialDoctor.d.ts +50 -0
  17. package/dist/claudeCredentialDoctor.js +277 -0
  18. package/dist/claudeCredentialLedger.d.ts +15 -0
  19. package/dist/claudeCredentialLedger.js +130 -0
  20. package/dist/claudeCredentials.d.ts +20 -3
  21. package/dist/claudeCredentials.js +50 -3
  22. package/dist/claudeLiveGuard.d.ts +33 -0
  23. package/dist/claudeLiveGuard.js +46 -0
  24. package/dist/claudeProfiles.d.ts +14 -0
  25. package/dist/claudeProfiles.js +22 -0
  26. package/dist/claudeRefreshLock.d.ts +15 -0
  27. package/dist/claudeRefreshLock.js +145 -0
  28. package/dist/codexAuth.d.ts +4 -0
  29. package/dist/codexAuth.js +18 -0
  30. package/dist/codexCredentialDoctor.d.ts +33 -0
  31. package/dist/codexCredentialDoctor.js +302 -0
  32. package/dist/codexCredentialStore.d.ts +29 -0
  33. package/dist/codexCredentialStore.js +213 -0
  34. package/dist/codexKeepAlive.d.ts +2 -0
  35. package/dist/codexKeepAlive.js +116 -0
  36. package/dist/codexPaths.d.ts +8 -2
  37. package/dist/codexPaths.js +51 -7
  38. package/dist/codexProfiles.d.ts +37 -31
  39. package/dist/codexProfiles.js +842 -671
  40. package/dist/credentialIO.d.ts +67 -7
  41. package/dist/credentialIO.js +211 -47
  42. package/dist/ensureDefaultAccounts.js +1 -8
  43. package/dist/index.d.ts +14 -4
  44. package/dist/index.js +29 -6
  45. package/dist/keepAlive.d.ts +2 -1
  46. package/dist/keepAlive.js +116 -29
  47. package/dist/liveCredentialGuard.d.ts +28 -0
  48. package/dist/liveCredentialGuard.js +36 -0
  49. package/dist/processDetection.d.ts +10 -3
  50. package/dist/processDetection.js +44 -11
  51. package/dist/schemas/accountManager.d.ts +22 -0
  52. package/dist/schemas/accountManager.js +4 -0
  53. package/dist/schemas/assistantTurn.d.ts +2 -2
  54. package/dist/terminalSync.d.ts +12 -2
  55. package/dist/terminalSync.js +27 -2
  56. package/package.json +1 -1
@@ -39,6 +39,7 @@ export declare function keepAliveCommand(provider: AccountProviderId): {
39
39
  };
40
40
  /**
41
41
  * Serialised, bounded refresh of every inactive account that is `expiring`
42
- * (or `unknown` for Claude). Never touches the live account.
42
+ * (or `unknown` for Claude). Never touches the live account or any copy of
43
+ * its chain.
43
44
  */
44
45
  export declare function refreshInactiveAccounts(options?: RefreshInactiveAccountsOptions): Promise<RefreshInactiveAccountsResult>;
package/dist/keepAlive.js CHANGED
@@ -7,33 +7,50 @@ exports.refreshInactiveAccounts = refreshInactiveAccounts;
7
7
  * providers' OAuth endpoints itself: it runs the official CLI inside the
8
8
  * profile's isolated home and lets the CLI refresh (and rotate) the token
9
9
  * where it belongs, then re-reads the store to record the new expiry.
10
+ *
11
+ * A refresh rotates the refresh token, so keep-alive only runs where the
12
+ * profile home holds the one live copy of its chain: never the live account,
13
+ * never a copy whose refresh token equals live's, never a copy taken from the
14
+ * live store that no switch has handed back yet, and never a stale copy when
15
+ * the backup is newer. The check and a lease are taken under the swap lock, so
16
+ * no switch can install that account while its CLI is refreshing.
10
17
  */
18
+ const codexKeepAlive_1 = require("./codexKeepAlive");
19
+ const codexProfiles_1 = require("./codexProfiles");
20
+ const codexPaths_1 = require("./codexPaths");
21
+ const codexCredentialStore_1 = require("./codexCredentialStore");
22
+ const codexAuth_1 = require("./codexAuth");
11
23
  const child_process_1 = require("child_process");
12
24
  const accountHealth_1 = require("./accountHealth");
13
25
  const accountLaunch_1 = require("./accountLaunch");
26
+ const accounts_1 = require("./accounts");
14
27
  /** The command each CLI runs to load (and refresh) its stored login. */
15
28
  function keepAliveCommand(provider) {
16
29
  if (provider === 'codex')
17
- return { command: 'codex', args: ['login', 'status'] };
30
+ return { command: 'codex', args: ['app-server', '-c', 'cli_auth_credentials_store="file"'] };
18
31
  const override = process.env.SIDEKICK_CLAUDE_KEEPALIVE_ARGS?.trim();
19
32
  return { command: 'claude', args: override ? override.split(/\s+/) : ['auth', 'status'] };
20
33
  }
21
- const defaultRunner = (command, args, env, timeoutMs) => new Promise((resolve) => {
22
- try {
23
- (0, child_process_1.execFile)(command, args, { encoding: 'utf8', env, timeout: timeoutMs, killSignal: 'SIGKILL', windowsHide: true }, (error, stdout) => {
24
- const status = error
25
- ? (error.code ?? null)
26
- : 0;
27
- resolve({
28
- status: typeof status === 'number' ? status : null,
29
- stdout: String(stdout ?? ''),
34
+ /** How long a keep-alive lease outlives the CLI timeout before it lapses on its own. */
35
+ const LEASE_GRACE_MS = 30_000;
36
+ const defaultRunner = (command, args, env, timeoutMs) => command === 'codex'
37
+ ? (0, codexKeepAlive_1.runCodexKeepAlive)(command, args, env, timeoutMs)
38
+ : new Promise((resolve) => {
39
+ try {
40
+ (0, child_process_1.execFile)(command, args, { encoding: 'utf8', env, timeout: timeoutMs, killSignal: 'SIGKILL', windowsHide: true }, (error, stdout) => {
41
+ const status = error
42
+ ? (error.code ?? null)
43
+ : 0;
44
+ resolve({
45
+ status: typeof status === 'number' ? status : null,
46
+ stdout: String(stdout ?? ''),
47
+ });
30
48
  });
31
- });
32
- }
33
- catch {
34
- resolve({ status: null, stdout: '' });
35
- }
36
- });
49
+ }
50
+ catch {
51
+ resolve({ status: null, stdout: '' });
52
+ }
53
+ });
37
54
  function eligible(view, includeFresh) {
38
55
  if (view.health.isLive)
39
56
  return 'live account; the CLI refreshes it in normal use';
@@ -49,7 +66,8 @@ function eligible(view, includeFresh) {
49
66
  }
50
67
  /**
51
68
  * Serialised, bounded refresh of every inactive account that is `expiring`
52
- * (or `unknown` for Claude). Never touches the live account.
69
+ * (or `unknown` for Claude). Never touches the live account or any copy of
70
+ * its chain.
53
71
  */
54
72
  async function refreshInactiveAccounts(options = {}) {
55
73
  const result = { refreshed: [], skipped: [], failed: [] };
@@ -62,10 +80,15 @@ async function refreshInactiveAccounts(options = {}) {
62
80
  result.skipped.push({ id: view.id, providerId: view.providerId, reason });
63
81
  continue;
64
82
  }
65
- const launch = (0, accountLaunch_1.getAccountLaunchEnv)(view.providerId, view.id, { materialize: true });
66
- if (launch.error) {
67
- result.skipped.push({ id: view.id, providerId: view.providerId, reason: launch.error });
68
- continue;
83
+ const isClaude = view.providerId === 'claude-code';
84
+ const beginLease = isClaude ? accounts_1.beginClaudeKeepAlive : codexProfiles_1.beginCodexKeepAlive;
85
+ const finishLease = isClaude ? accounts_1.finishClaudeKeepAlive : codexProfiles_1.finishCodexKeepAlive;
86
+ if (options.dryRun) {
87
+ const blocker = (isClaude ? accounts_1.getClaudeKeepAliveBlocker : codexProfiles_1.getCodexKeepAliveBlocker)(view.id);
88
+ if (blocker) {
89
+ result.skipped.push({ id: view.id, providerId: view.providerId, reason: blocker });
90
+ continue;
91
+ }
69
92
  }
70
93
  const { command, args } = keepAliveCommand(view.providerId);
71
94
  if (options.dryRun) {
@@ -76,16 +99,71 @@ async function refreshInactiveAccounts(options = {}) {
76
99
  });
77
100
  continue;
78
101
  }
79
- const env = { ...process.env, ...launch.env };
80
- for (const name of launch.envUnset)
81
- delete env[name];
102
+ {
103
+ const begin = beginLease(view.id, timeoutMs + LEASE_GRACE_MS);
104
+ if (!begin.ok) {
105
+ result.skipped.push({ id: view.id, providerId: view.providerId, reason: begin.reason });
106
+ continue;
107
+ }
108
+ }
109
+ let released = false;
110
+ const release = () => {
111
+ if (!released) {
112
+ released = true;
113
+ finishLease(view.id);
114
+ }
115
+ };
82
116
  try {
83
- const run = await runner(command, args, env, timeoutMs);
117
+ const launch = (0, accountLaunch_1.getAccountLaunchEnv)(view.providerId, view.id, {
118
+ materialize: true,
119
+ restoreBackup: false,
120
+ });
121
+ if (launch.error) {
122
+ release();
123
+ result.skipped.push({ id: view.id, providerId: view.providerId, reason: launch.error });
124
+ continue;
125
+ }
126
+ if (!isClaude &&
127
+ (0, codexCredentialStore_1.canonicalCodexPath)(launch.home) === (0, codexCredentialStore_1.canonicalCodexPath)((0, codexPaths_1.getSystemCodexHome)())) {
128
+ result.skipped.push({
129
+ id: view.id,
130
+ providerId: view.providerId,
131
+ reason: 'the account became live before refresh',
132
+ });
133
+ continue;
134
+ }
135
+ const before = isClaude ? null : (0, codexCredentialStore_1.readCodexCredentialStore)(launch.home);
136
+ const env = { ...process.env, ...launch.env };
137
+ for (const name of launch.envUnset)
138
+ delete env[name];
139
+ if (!isClaude)
140
+ for (const name of [
141
+ 'OPENAI_API_KEY',
142
+ 'CODEX_API_KEY',
143
+ 'CODEX_ACCESS_TOKEN',
144
+ 'OPENAI_FEDERATION_RULE_ID',
145
+ 'OPENAI_IDENTITY_TOKEN_FILE',
146
+ 'OPENAI_WORKLOAD_IDENTITY_CONTEXT',
147
+ ])
148
+ delete env[name];
149
+ let run;
150
+ try {
151
+ run = await runner(command, args, env, timeoutMs);
152
+ }
153
+ finally {
154
+ release();
155
+ }
84
156
  const after = (0, accountHealth_1.getAccountHealth)(view.providerId, view.id, {
85
157
  probe: 'store',
86
158
  now: options.now,
87
159
  });
88
- if (after.state === 'fresh') {
160
+ const codexAfter = isClaude ? null : (0, codexCredentialStore_1.readCodexCredentialStore)(launch.home);
161
+ const advanced = isClaude ||
162
+ (before?.status === 'ok' &&
163
+ codexAfter?.status === 'ok' &&
164
+ (0, codexAuth_1.identitiesMatch)((0, codexAuth_1.readAuthIdentityFromRaw)(before.snapshot.authRaw), (0, codexAuth_1.readAuthIdentityFromRaw)(codexAfter.snapshot.authRaw)) &&
165
+ (0, codexAuth_1.compareCodexAuthFreshness)(codexAfter.snapshot.authRaw, before.snapshot.authRaw) > 0);
166
+ if (after.state === 'fresh' && advanced && (isClaude || run.status === 0)) {
89
167
  result.refreshed.push({ id: view.id, providerId: view.providerId });
90
168
  }
91
169
  else {
@@ -93,13 +171,22 @@ async function refreshInactiveAccounts(options = {}) {
93
171
  id: view.id,
94
172
  providerId: view.providerId,
95
173
  error: run.status === 0
96
- ? `${command} ran but the stored credential is still ${after.state}.`
97
- : `${command} exited with status ${run.status ?? 'unknown'}.`,
174
+ ? isClaude
175
+ ? `${command} ran but the stored credential is still ${after.state}.`
176
+ : `${command} ran but no verified credential advance was observed (health: ${after.state}).`
177
+ : `${command} exited with status ${run.status ?? 'unknown'}.${isClaude ? '' : ' Codex app-server refresh requires a compatible Codex CLI; update it and retry.'}`,
98
178
  });
99
179
  }
100
180
  }
101
181
  catch (err) {
102
- result.failed.push({ id: view.id, providerId: view.providerId, error: String(err) });
182
+ result.failed.push({
183
+ id: view.id,
184
+ providerId: view.providerId,
185
+ error: isClaude ? String(err) : 'Codex keep-alive could not complete safely.',
186
+ });
187
+ }
188
+ finally {
189
+ release();
103
190
  }
104
191
  }
105
192
  return result;
@@ -0,0 +1,28 @@
1
+ /** Pure policy shared by provider-specific live credential installers. */
2
+ export type LiveCredentialWriteIntent = 'switch' | 'relogin' | 'repair' | 'rollback';
3
+ export type LiveCredentialWriteRefusal = 'live-unreadable' | 'invalid-candidate' | 'same-account' | 'different-account' | 'same-link' | 'not-captured' | 'not-newer' | 'live-changed';
4
+ export type LiveCredentialWriteDecision = {
5
+ allow: true;
6
+ } | {
7
+ allow: false;
8
+ code: LiveCredentialWriteRefusal;
9
+ reason: string;
10
+ };
11
+ export interface LiveCredentialWriteFacts {
12
+ provider: string;
13
+ intent: LiveCredentialWriteIntent;
14
+ liveStatus: 'ok' | 'absent' | 'corrupt' | 'error';
15
+ candidateValid: boolean;
16
+ liveHasCredential: boolean;
17
+ liveAccountId: string | null;
18
+ candidateAccountId: string;
19
+ sameLink: boolean;
20
+ strictlyNewer: boolean;
21
+ capturedOutgoing?: boolean;
22
+ matchesWritten: boolean;
23
+ /** Compatibility with Claude's established missing/corrupt rollback policy. */
24
+ allowNonOkRollback?: boolean;
25
+ rejectCorrupt?: boolean;
26
+ requireDifferentLinkForAdvance?: boolean;
27
+ }
28
+ export declare function decideLiveCredentialWrite(f: LiveCredentialWriteFacts): LiveCredentialWriteDecision;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.decideLiveCredentialWrite = decideLiveCredentialWrite;
4
+ function decideLiveCredentialWrite(f) {
5
+ const refuse = (code, reason) => ({ allow: false, code, reason });
6
+ if (f.liveStatus === 'error' || (f.rejectCorrupt && f.liveStatus === 'corrupt')) {
7
+ return refuse('live-unreadable', `The live ${f.provider} login could not be read, so it was left untouched.`);
8
+ }
9
+ if (f.intent === 'rollback') {
10
+ return f.matchesWritten || (f.allowNonOkRollback && f.liveStatus !== 'ok')
11
+ ? { allow: true }
12
+ : refuse('live-changed', `The live ${f.provider} login changed after the switch wrote it; it was kept.`);
13
+ }
14
+ if (!f.candidateValid)
15
+ return refuse('invalid-candidate', `The saved credential is not a ${f.provider} login.`);
16
+ if (f.intent === 'switch') {
17
+ if (f.liveAccountId !== null && f.liveAccountId === f.candidateAccountId) {
18
+ return refuse('same-account', 'That account is already the live login.');
19
+ }
20
+ if (f.sameLink)
21
+ return refuse('same-link', 'The saved login is the same refresh token the live login holds; sign in to that account again.');
22
+ if (f.liveHasCredential && !f.capturedOutgoing) {
23
+ return refuse('not-captured', 'The outgoing live login was not saved first, so it was left untouched.');
24
+ }
25
+ return { allow: true };
26
+ }
27
+ if (f.liveAccountId !== null && f.liveAccountId !== f.candidateAccountId) {
28
+ return refuse('different-account', `The live ${f.provider} login belongs to another account; switch accounts instead.`);
29
+ }
30
+ if (!f.liveHasCredential)
31
+ return { allow: true };
32
+ if (!f.strictlyNewer || (f.requireDifferentLinkForAdvance && f.sameLink)) {
33
+ return refuse('not-newer', `The live ${f.provider} login is already at least as new; it was kept.`);
34
+ }
35
+ return { allow: true };
36
+ }
@@ -14,9 +14,6 @@ export interface RunningAccountConsumer {
14
14
  /** The user-facing sentence both UIs print for this consumer. */
15
15
  reachability: string;
16
16
  }
17
- /** Sync sibling of {@link findRunningProcesses}; blocks up to 4 s. */
18
- export declare function findRunningProcessesSync(names: string[]): RunningProcess[];
19
- export declare function findRunningProcesses(names: string[]): Promise<RunningProcess[]>;
20
17
  export declare const ACCOUNT_CONSUMER_REACHABILITY: Record<AccountConsumerKind, {
21
18
  switched: boolean;
22
19
  reachability: string;
@@ -30,3 +27,13 @@ export declare function detectRunningAccountConsumers(provider?: 'claude-code' |
30
27
  export declare function detectRunningAccountConsumersSync(provider?: 'claude-code' | 'codex'): RunningAccountConsumer[];
31
28
  /** Build a consumer entry for a kind the host detects itself (e.g. its own extension host). */
32
29
  export declare function describeAccountConsumer(kind: AccountConsumerKind, pids?: number[]): RunningAccountConsumer;
30
+ /** Compatibility wrappers keep the established best-effort list API. */
31
+ export declare function findRunningProcessesSync(names: string[]): RunningProcess[];
32
+ export declare function findRunningProcesses(names: string[]): Promise<RunningProcess[]>;
33
+ export interface AccountConsumerScan {
34
+ ok: boolean;
35
+ consumers: RunningAccountConsumer[];
36
+ }
37
+ /** Safety decisions must distinguish a failed process scan from an empty one. */
38
+ export declare function inspectRunningAccountConsumersSync(provider: 'claude-code' | 'codex'): AccountConsumerScan;
39
+ export declare function inspectRunningAccountConsumers(provider: 'claude-code' | 'codex'): Promise<AccountConsumerScan>;
@@ -34,11 +34,13 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.ACCOUNT_CONSUMER_REACHABILITY = void 0;
37
- exports.findRunningProcessesSync = findRunningProcessesSync;
38
- exports.findRunningProcesses = findRunningProcesses;
39
37
  exports.detectRunningAccountConsumers = detectRunningAccountConsumers;
40
38
  exports.detectRunningAccountConsumersSync = detectRunningAccountConsumersSync;
41
39
  exports.describeAccountConsumer = describeAccountConsumer;
40
+ exports.findRunningProcessesSync = findRunningProcessesSync;
41
+ exports.findRunningProcesses = findRunningProcesses;
42
+ exports.inspectRunningAccountConsumersSync = inspectRunningAccountConsumersSync;
43
+ exports.inspectRunningAccountConsumers = inspectRunningAccountConsumers;
42
44
  /**
43
45
  * Cross-platform detection of running CLI/app processes that hold provider
44
46
  * credentials in memory. Bounded, never throws, never spawns a shell.
@@ -47,6 +49,7 @@ const child_process_1 = require("child_process");
47
49
  const path = __importStar(require("path"));
48
50
  const PROBE_TIMEOUT_MS = 4000;
49
51
  const CLAUDE_NPM_ENTRY = /claude-code[\\/]cli\.js/;
52
+ const CODEX_NPM_ENTRY = /(?:@openai[\\/]codex|codex[\\/]bin)[\\/].*codex\.js/;
50
53
  function parseUnixTable(stdout) {
51
54
  const rows = [];
52
55
  for (const line of stdout.split('\n')) {
@@ -87,6 +90,15 @@ function matchRows(rows, names) {
87
90
  CLAUDE_NPM_ENTRY.test(row.args)) {
88
91
  name = 'claude';
89
92
  }
93
+ if (!name &&
94
+ wanted.has('codex') &&
95
+ /^node(\.exe)?$/i.test(base) &&
96
+ CODEX_NPM_ENTRY.test(row.args))
97
+ name = 'codex';
98
+ if (!name &&
99
+ wanted.has('Codex') &&
100
+ /(?:^|[\\/])Codex\.app[\\/]/.test(row.command + ' ' + row.args))
101
+ name = 'Codex';
90
102
  if (name)
91
103
  found.push({ pid: row.pid, name, command: row.command });
92
104
  }
@@ -101,9 +113,9 @@ function parseOutput(stdout) {
101
113
  return process.platform === 'win32' ? parseWindowsCsv(stdout) : parseUnixTable(stdout);
102
114
  }
103
115
  /** Sync sibling of {@link findRunningProcesses}; blocks up to 4 s. */
104
- function findRunningProcessesSync(names) {
116
+ function inspectProcessesSync(names) {
105
117
  if (names.length === 0)
106
- return [];
118
+ return { ok: true, processes: [] };
107
119
  try {
108
120
  const { command, args } = probeCommand();
109
121
  const result = (0, child_process_1.spawnSync)(command, args, {
@@ -114,16 +126,16 @@ function findRunningProcessesSync(names) {
114
126
  windowsHide: true,
115
127
  });
116
128
  if (result.error || result.status !== 0)
117
- return [];
118
- return matchRows(parseOutput(String(result.stdout ?? '')), names);
129
+ return { ok: false, processes: [] };
130
+ return { ok: true, processes: matchRows(parseOutput(String(result.stdout ?? '')), names) };
119
131
  }
120
132
  catch {
121
- return [];
133
+ return { ok: false, processes: [] };
122
134
  }
123
135
  }
124
- function findRunningProcesses(names) {
136
+ function inspectProcesses(names) {
125
137
  if (names.length === 0)
126
- return Promise.resolve([]);
138
+ return Promise.resolve({ ok: true, processes: [] });
127
139
  return new Promise((resolve) => {
128
140
  try {
129
141
  const { command, args } = probeCommand();
@@ -134,11 +146,14 @@ function findRunningProcesses(names) {
134
146
  windowsHide: true,
135
147
  maxBuffer: 16 * 1024 * 1024,
136
148
  }, (error, stdout) => {
137
- resolve(error ? [] : matchRows(parseOutput(String(stdout ?? '')), names));
149
+ resolve({
150
+ ok: !error,
151
+ processes: error ? [] : matchRows(parseOutput(String(stdout ?? '')), names),
152
+ });
138
153
  });
139
154
  }
140
155
  catch {
141
- resolve([]);
156
+ resolve({ ok: false, processes: [] });
142
157
  }
143
158
  });
144
159
  }
@@ -208,3 +223,21 @@ function detectRunningAccountConsumersSync(provider) {
208
223
  function describeAccountConsumer(kind, pids = []) {
209
224
  return toConsumer(kind, pids);
210
225
  }
226
+ /** Compatibility wrappers keep the established best-effort list API. */
227
+ function findRunningProcessesSync(names) {
228
+ return inspectProcessesSync(names).processes;
229
+ }
230
+ async function findRunningProcesses(names) {
231
+ return (await inspectProcesses(names)).processes;
232
+ }
233
+ /** Safety decisions must distinguish a failed process scan from an empty one. */
234
+ function inspectRunningAccountConsumersSync(provider) {
235
+ const kinds = kindsForProvider(provider);
236
+ const result = inspectProcessesSync(kinds.map((kind) => CONSUMER_PROCESS_NAMES[kind]));
237
+ return { ok: result.ok, consumers: groupConsumers(result.processes, kinds) };
238
+ }
239
+ async function inspectRunningAccountConsumers(provider) {
240
+ const kinds = kindsForProvider(provider);
241
+ const result = await inspectProcesses(kinds.map((kind) => CONSUMER_PROCESS_NAMES[kind]));
242
+ return { ok: result.ok, consumers: groupConsumers(result.processes, kinds) };
243
+ }
@@ -143,6 +143,7 @@ export declare const accountHealthSidecarSchema: z.ZodObject<{
143
143
  "api-key": "api-key";
144
144
  oauth: "oauth";
145
145
  }>>;
146
+ refreshFp: z.ZodOptional<z.ZodString>;
146
147
  source: z.ZodEnum<{
147
148
  login: "login";
148
149
  "live-sync": "live-sync";
@@ -310,6 +311,13 @@ export declare const providerSyncReportSchema: z.ZodObject<{
310
311
  folded: z.ZodOptional<z.ZodString>;
311
312
  repointed: z.ZodOptional<z.ZodString>;
312
313
  merged: z.ZodOptional<z.ZodArray<z.ZodString>>;
314
+ demoted: z.ZodOptional<z.ZodString>;
315
+ repaired: z.ZodOptional<z.ZodString>;
316
+ journal: z.ZodOptional<z.ZodEnum<{
317
+ finished: "finished";
318
+ "rolled-back": "rolled-back";
319
+ cleared: "cleared";
320
+ }>>;
313
321
  skipped: z.ZodOptional<z.ZodString>;
314
322
  warnings: z.ZodArray<z.ZodString>;
315
323
  }, z.core.$strip>;
@@ -322,6 +330,13 @@ export declare const syncReportSchema: z.ZodObject<{
322
330
  folded: z.ZodOptional<z.ZodString>;
323
331
  repointed: z.ZodOptional<z.ZodString>;
324
332
  merged: z.ZodOptional<z.ZodArray<z.ZodString>>;
333
+ demoted: z.ZodOptional<z.ZodString>;
334
+ repaired: z.ZodOptional<z.ZodString>;
335
+ journal: z.ZodOptional<z.ZodEnum<{
336
+ finished: "finished";
337
+ "rolled-back": "rolled-back";
338
+ cleared: "cleared";
339
+ }>>;
325
340
  skipped: z.ZodOptional<z.ZodString>;
326
341
  warnings: z.ZodArray<z.ZodString>;
327
342
  }, z.core.$strip>;
@@ -333,6 +348,13 @@ export declare const syncReportSchema: z.ZodObject<{
333
348
  folded: z.ZodOptional<z.ZodString>;
334
349
  repointed: z.ZodOptional<z.ZodString>;
335
350
  merged: z.ZodOptional<z.ZodArray<z.ZodString>>;
351
+ demoted: z.ZodOptional<z.ZodString>;
352
+ repaired: z.ZodOptional<z.ZodString>;
353
+ journal: z.ZodOptional<z.ZodEnum<{
354
+ finished: "finished";
355
+ "rolled-back": "rolled-back";
356
+ cleared: "cleared";
357
+ }>>;
336
358
  skipped: z.ZodOptional<z.ZodString>;
337
359
  warnings: z.ZodArray<z.ZodString>;
338
360
  }, z.core.$strip>;
@@ -107,6 +107,7 @@ exports.accountHealthSidecarSchema = zod_1.z.object({
107
107
  lastRefreshAt: zod_1.z.number().optional(),
108
108
  hasRefreshToken: zod_1.z.boolean().optional(),
109
109
  authMode: zod_1.z.enum(['chatgpt', 'api-key', 'oauth']).optional(),
110
+ refreshFp: zod_1.z.string().optional(),
110
111
  source: exports.accountHealthSourceSchema,
111
112
  });
112
113
  exports.accountHealthSchema = zod_1.z.object({
@@ -178,6 +179,9 @@ exports.providerSyncReportSchema = zod_1.z.object({
178
179
  folded: zod_1.z.string().optional(),
179
180
  repointed: zod_1.z.string().optional(),
180
181
  merged: zod_1.z.array(zod_1.z.string()).optional(),
182
+ demoted: zod_1.z.string().optional(),
183
+ repaired: zod_1.z.string().optional(),
184
+ journal: zod_1.z.enum(['finished', 'rolled-back', 'cleared']).optional(),
181
185
  skipped: zod_1.z.string().optional(),
182
186
  warnings: zod_1.z.array(zod_1.z.string()),
183
187
  });
@@ -7,8 +7,8 @@
7
7
  import { z } from 'zod';
8
8
  export declare const assistantTurnEventTypeSchema: z.ZodEnum<{
9
9
  error: "error";
10
- text: "text";
11
10
  status: "status";
11
+ text: "text";
12
12
  tool_use: "tool_use";
13
13
  tool_result: "tool_result";
14
14
  progress: "progress";
@@ -18,8 +18,8 @@ export declare const assistantTurnEventTypeSchema: z.ZodEnum<{
18
18
  export declare const assistantTurnEventSchema: z.ZodObject<{
19
19
  eventType: z.ZodEnum<{
20
20
  error: "error";
21
- text: "text";
22
21
  status: "status";
22
+ text: "text";
23
23
  tool_use: "tool_use";
24
24
  tool_result: "tool_result";
25
25
  progress: "progress";
@@ -1,7 +1,17 @@
1
1
  import type { AccountProviderId } from './accountRegistry';
2
2
  /**
3
- * Write a POSIX launcher script that runs the provider CLI against a profile
4
- * home. Unix only; on Windows use `sidekick accounts env --shell powershell`.
3
+ * Write a POSIX launcher script that runs the provider CLI as a saved account.
4
+ * The script defers to `sidekick accounts shell` at run time, which resolves
5
+ * the right home then: the account's profile home while it is inactive, the
6
+ * live home while it is the live login (a session pinned to the profile home
7
+ * would otherwise be a second copy of the live login). Unix only; on Windows
8
+ * use `sidekick accounts env --shell powershell`.
9
+ */
10
+ export declare function writeAccountLauncher(name: string, provider: AccountProviderId, accountId: string): void;
11
+ /**
12
+ * @deprecated Pins a launcher to one profile home; when that account becomes
13
+ * the live login the launcher runs a second copy of it. Use
14
+ * {@link writeAccountLauncher}.
5
15
  */
6
16
  export declare function writeLauncher(name: string, provider: AccountProviderId, profileHome: string): void;
7
17
  export declare function removeLauncher(name: string): void;
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.writeAccountLauncher = writeAccountLauncher;
36
37
  exports.writeLauncher = writeLauncher;
37
38
  exports.removeLauncher = removeLauncher;
38
39
  const fs = __importStar(require("fs"));
@@ -79,9 +80,33 @@ function assertNoLauncherCollision(name, targetPath) {
79
80
  }
80
81
  }
81
82
  }
83
+ function shellSingleQuote(value) {
84
+ return `'${value.replace(/'/g, `'\\''`)}'`;
85
+ }
86
+ /**
87
+ * Write a POSIX launcher script that runs the provider CLI as a saved account.
88
+ * The script defers to `sidekick accounts shell` at run time, which resolves
89
+ * the right home then: the account's profile home while it is inactive, the
90
+ * live home while it is the live login (a session pinned to the profile home
91
+ * would otherwise be a second copy of the live login). Unix only; on Windows
92
+ * use `sidekick accounts env --shell powershell`.
93
+ */
94
+ function writeAccountLauncher(name, provider, accountId) {
95
+ assertValidLauncherName(name);
96
+ const launcherPath = getLauncherPath(name);
97
+ assertNoLauncherCollision(name, launcherPath);
98
+ const script = [
99
+ '#!/bin/sh',
100
+ LAUNCHER_MARKER,
101
+ `exec sidekick accounts shell --provider ${provider} ${shellSingleQuote(accountId)} -- ${providerBinary(provider)} "$@"`,
102
+ '',
103
+ ].join('\n');
104
+ (0, atomic_1.atomicWriteFileSync)(launcherPath, script, 0o755);
105
+ }
82
106
  /**
83
- * Write a POSIX launcher script that runs the provider CLI against a profile
84
- * home. Unix only; on Windows use `sidekick accounts env --shell powershell`.
107
+ * @deprecated Pins a launcher to one profile home; when that account becomes
108
+ * the live login the launcher runs a second copy of it. Use
109
+ * {@link writeAccountLauncher}.
85
110
  */
86
111
  function writeLauncher(name, provider, profileHome) {
87
112
  assertValidLauncherName(name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sidekick-shared",
3
- "version": "0.27.5",
3
+ "version": "0.27.6",
4
4
  "description": "Shared data access layer for Sidekick — readers, types, providers, credentials, quota",
5
5
  "keywords": [
6
6
  "claude-code",