sidekick-shared 0.26.5 → 0.27.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 (68) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +98 -59
  3. package/dist/accountChanges.js +21 -2
  4. package/dist/accountHealth.d.ts +72 -0
  5. package/dist/accountHealth.js +286 -0
  6. package/dist/accountLaunch.d.ts +30 -0
  7. package/dist/accountLaunch.js +124 -0
  8. package/dist/accountManager.d.ts +33 -8
  9. package/dist/accountManager.js +183 -50
  10. package/dist/accountRegistry.d.ts +10 -0
  11. package/dist/accountRegistry.js +42 -22
  12. package/dist/accountSwitch.d.ts +43 -0
  13. package/dist/accountSwitch.js +115 -0
  14. package/dist/accountSync.d.ts +30 -0
  15. package/dist/accountSync.js +180 -0
  16. package/dist/accountSyncTypes.d.ts +25 -0
  17. package/dist/accountSyncTypes.js +7 -0
  18. package/dist/accounts.d.ts +58 -6
  19. package/dist/accounts.js +408 -189
  20. package/dist/claudeCredentials.d.ts +38 -0
  21. package/dist/claudeCredentials.js +74 -0
  22. package/dist/claudeProfiles.d.ts +40 -0
  23. package/dist/claudeProfiles.js +102 -10
  24. package/dist/codexAuth.d.ts +32 -0
  25. package/dist/codexAuth.js +127 -0
  26. package/dist/codexPaths.d.ts +21 -0
  27. package/dist/codexPaths.js +117 -0
  28. package/dist/codexProfiles.d.ts +58 -5
  29. package/dist/codexProfiles.js +574 -289
  30. package/dist/credentialIO.d.ts +12 -5
  31. package/dist/credentialIO.js +63 -29
  32. package/dist/credentials.d.ts +8 -0
  33. package/dist/credentials.js +28 -32
  34. package/dist/ensureDefaultAccounts.d.ts +8 -0
  35. package/dist/ensureDefaultAccounts.js +42 -82
  36. package/dist/humanPrompt.d.ts +27 -0
  37. package/dist/humanPrompt.js +93 -0
  38. package/dist/index.d.ts +31 -10
  39. package/dist/index.js +72 -13
  40. package/dist/keepAlive.d.ts +44 -0
  41. package/dist/keepAlive.js +106 -0
  42. package/dist/modelContext.js +4 -0
  43. package/dist/modelInfo.js +26 -1
  44. package/dist/node.d.ts +4 -0
  45. package/dist/node.js +9 -2
  46. package/dist/processDetection.d.ts +32 -0
  47. package/dist/processDetection.js +210 -0
  48. package/dist/promptHistory.d.ts +108 -0
  49. package/dist/promptHistory.js +616 -0
  50. package/dist/schemas/accountManager.d.ts +283 -1
  51. package/dist/schemas/accountManager.js +122 -1
  52. package/dist/schemas/assistantTurn.d.ts +2 -2
  53. package/dist/schemas/index.d.ts +6 -1
  54. package/dist/schemas/index.js +14 -1
  55. package/dist/schemas/observedSessionV1.d.ts +21 -21
  56. package/dist/schemas/quota.d.ts +18 -18
  57. package/dist/schemas/quotaHistory.d.ts +1 -1
  58. package/dist/schemas/sessionEvent.d.ts +25 -0
  59. package/dist/schemas/sessionEvent.js +3 -0
  60. package/dist/schemas/stateFile.d.ts +5 -5
  61. package/dist/terminalSync.d.ts +5 -5
  62. package/dist/terminalSync.js +7 -112
  63. package/dist/transcript.d.ts +6 -0
  64. package/dist/transcript.js +3 -0
  65. package/dist/types/sessionEvent.d.ts +8 -0
  66. package/dist/writers/atomic.d.ts +2 -0
  67. package/dist/writers/atomic.js +49 -2
  68. package/package.json +1 -1
@@ -2,8 +2,10 @@
2
2
  * Platform-aware credential I/O for Claude Code.
3
3
  *
4
4
  * On macOS, Claude Code stores OAuth credentials in the system Keychain
5
- * (service "Claude Code-credentials"). On Linux/WSL/Windows, credentials
6
- * live in ~/.claude/.credentials.json.
5
+ * (service "Claude Code-credentials", suffixed per config directory). On
6
+ * Linux/WSL/Windows, credentials live in `<home>/.credentials.json`. When the
7
+ * Keychain is unavailable (locked, SSH) Claude Code falls back to the file on
8
+ * macOS too, so reads consult the file after a Keychain miss.
7
9
  *
8
10
  * Both accounts.ts and credentials.ts consume this module so that all
9
11
  * credential access is platform-correct in one place.
@@ -12,8 +14,8 @@ export declare const KEYCHAIN_SERVICE = "Claude Code-credentials";
12
14
  /**
13
15
  * Read the active Claude Code credentials as a parsed object.
14
16
  *
15
- * - macOS: reads from system Keychain via the `security` CLI
16
- * - Linux / WSL / Windows: reads `~/.claude/.credentials.json`
17
+ * - macOS: reads from system Keychain via the `security` CLI, then the file
18
+ * - Linux / WSL / Windows: reads `<home>/.credentials.json`
17
19
  *
18
20
  * Returns `null` when credentials are absent or unreadable.
19
21
  */
@@ -22,8 +24,13 @@ export declare function readActiveCredentials(configDir?: string): unknown;
22
24
  * Write credentials to the active Claude Code credential store.
23
25
  *
24
26
  * - macOS: writes to system Keychain via the `security` CLI
25
- * - Linux / WSL / Windows: atomic-writes `~/.claude/.credentials.json`
27
+ * - Linux / WSL / Windows: atomic-writes `<home>/.credentials.json`
26
28
  *
27
29
  * Throws on failure.
28
30
  */
29
31
  export declare function writeActiveCredentials(credentials: unknown, configDir?: string): void;
32
+ /**
33
+ * Remove the credential store of a config directory (an abandoned isolated
34
+ * login, a removed profile). Best effort: never throws.
35
+ */
36
+ export declare function deleteStoredCredentials(configDir: string): void;
@@ -3,8 +3,10 @@
3
3
  * Platform-aware credential I/O for Claude Code.
4
4
  *
5
5
  * On macOS, Claude Code stores OAuth credentials in the system Keychain
6
- * (service "Claude Code-credentials"). On Linux/WSL/Windows, credentials
7
- * live in ~/.claude/.credentials.json.
6
+ * (service "Claude Code-credentials", suffixed per config directory). On
7
+ * Linux/WSL/Windows, credentials live in `<home>/.credentials.json`. When the
8
+ * Keychain is unavailable (locked, SSH) Claude Code falls back to the file on
9
+ * macOS too, so reads consult the file after a Keychain miss.
8
10
  *
9
11
  * Both accounts.ts and credentials.ts consume this module so that all
10
12
  * credential access is platform-correct in one place.
@@ -46,12 +48,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
46
48
  exports.KEYCHAIN_SERVICE = void 0;
47
49
  exports.readActiveCredentials = readActiveCredentials;
48
50
  exports.writeActiveCredentials = writeActiveCredentials;
51
+ exports.deleteStoredCredentials = deleteStoredCredentials;
49
52
  const fs = __importStar(require("fs"));
50
53
  const path = __importStar(require("path"));
51
- const os = __importStar(require("os"));
52
54
  const child_process_1 = require("child_process");
53
55
  const claudeProfiles_1 = require("./claudeProfiles");
56
+ const atomic_1 = require("./writers/atomic");
54
57
  exports.KEYCHAIN_SERVICE = 'Claude Code-credentials';
58
+ const SECURITY_TIMEOUT_MS = 4000;
55
59
  function quoteSecurityInteractiveArg(value) {
56
60
  // `security -i` is not a POSIX shell: adjacent quoted segments do NOT
57
61
  // concatenate (so the shell-style '\'' splice splits the token), and
@@ -59,39 +63,49 @@ function quoteSecurityInteractiveArg(value) {
59
63
  return `'${value.replace(/\\/g, '\\\\').replace(/'/g, `\\'`)}'`;
60
64
  }
61
65
  function getCredentialsFilePath(configDir) {
62
- return path.join(configDir ?? path.join(os.homedir(), '.claude'), '.credentials.json');
66
+ return path.join(configDir ?? (0, claudeProfiles_1.getLiveClaudeHome)(), '.credentials.json');
67
+ }
68
+ function readCredentialsFile(configDir) {
69
+ try {
70
+ return JSON.parse(fs.readFileSync(getCredentialsFilePath(configDir), 'utf8'));
71
+ }
72
+ catch {
73
+ return null;
74
+ }
75
+ }
76
+ function readKeychainItem(service) {
77
+ try {
78
+ const raw = (0, child_process_1.execFileSync)('security', ['find-generic-password', '-s', service, '-w'], {
79
+ encoding: 'utf8',
80
+ stdio: ['pipe', 'pipe', 'pipe'],
81
+ timeout: SECURITY_TIMEOUT_MS,
82
+ killSignal: 'SIGKILL',
83
+ });
84
+ return JSON.parse(raw.trim());
85
+ }
86
+ catch {
87
+ return null;
88
+ }
63
89
  }
64
90
  /**
65
91
  * Read the active Claude Code credentials as a parsed object.
66
92
  *
67
- * - macOS: reads from system Keychain via the `security` CLI
68
- * - Linux / WSL / Windows: reads `~/.claude/.credentials.json`
93
+ * - macOS: reads from system Keychain via the `security` CLI, then the file
94
+ * - Linux / WSL / Windows: reads `<home>/.credentials.json`
69
95
  *
70
96
  * Returns `null` when credentials are absent or unreadable.
71
97
  */
72
98
  function readActiveCredentials(configDir) {
73
99
  if (process.platform === 'darwin') {
74
- try {
75
- const raw = (0, child_process_1.execFileSync)('security', ['find-generic-password', '-s', (0, claudeProfiles_1.claudeKeychainService)(configDir), '-w'], { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 4000, killSignal: 'SIGKILL' });
76
- return JSON.parse(raw.trim());
77
- }
78
- catch {
79
- return null;
80
- }
81
- }
82
- // Linux / WSL / Windows — file-based
83
- try {
84
- return JSON.parse(fs.readFileSync(getCredentialsFilePath(configDir), 'utf8'));
85
- }
86
- catch {
87
- return null;
100
+ return readKeychainItem((0, claudeProfiles_1.claudeKeychainService)(configDir)) ?? readCredentialsFile(configDir);
88
101
  }
102
+ return readCredentialsFile(configDir);
89
103
  }
90
104
  /**
91
105
  * Write credentials to the active Claude Code credential store.
92
106
  *
93
107
  * - macOS: writes to system Keychain via the `security` CLI
94
- * - Linux / WSL / Windows: atomic-writes `~/.claude/.credentials.json`
108
+ * - Linux / WSL / Windows: atomic-writes `<home>/.credentials.json`
95
109
  *
96
110
  * Throws on failure.
97
111
  */
@@ -100,28 +114,48 @@ function writeActiveCredentials(credentials, configDir) {
100
114
  JSON.parse(json); // validate round-trip
101
115
  if (process.platform === 'darwin') {
102
116
  const service = (0, claudeProfiles_1.claudeKeychainService)(configDir);
103
- const account = process.env.USER || 'user';
104
117
  const command = [
105
118
  'add-generic-password',
106
119
  '-U',
107
120
  '-s',
108
121
  quoteSecurityInteractiveArg(service),
109
122
  '-a',
110
- quoteSecurityInteractiveArg(account),
123
+ quoteSecurityInteractiveArg((0, claudeProfiles_1.claudeKeychainAccountName)()),
111
124
  '-w',
112
125
  quoteSecurityInteractiveArg(json),
113
126
  ].join(' ');
114
127
  (0, child_process_1.execFileSync)('security', ['-i'], {
115
128
  input: command + '\n',
116
129
  stdio: ['pipe', 'pipe', 'pipe'],
117
- timeout: 4000,
130
+ timeout: SECURITY_TIMEOUT_MS,
118
131
  killSignal: 'SIGKILL',
119
132
  });
120
133
  return;
121
134
  }
122
- // Linux / WSL / Windows — file-based
123
- const credPath = getCredentialsFilePath(configDir);
124
- const tmp = credPath + '.tmp';
125
- fs.writeFileSync(tmp, json, { encoding: 'utf8', mode: 0o600 });
126
- fs.renameSync(tmp, credPath);
135
+ // Linux / WSL / Windows — file-based (tmp + fsync + rename, retried on Windows)
136
+ (0, atomic_1.atomicWriteFileSync)(getCredentialsFilePath(configDir), json, 0o600);
137
+ }
138
+ /**
139
+ * Remove the credential store of a config directory (an abandoned isolated
140
+ * login, a removed profile). Best effort: never throws.
141
+ */
142
+ function deleteStoredCredentials(configDir) {
143
+ if (process.platform === 'darwin') {
144
+ try {
145
+ (0, child_process_1.execFileSync)('security', ['delete-generic-password', '-s', (0, claudeProfiles_1.claudeKeychainService)(configDir)], {
146
+ stdio: ['ignore', 'ignore', 'ignore'],
147
+ timeout: SECURITY_TIMEOUT_MS,
148
+ killSignal: 'SIGKILL',
149
+ });
150
+ }
151
+ catch {
152
+ /* no item, or keychain unavailable */
153
+ }
154
+ }
155
+ try {
156
+ fs.rmSync(getCredentialsFilePath(configDir), { force: true });
157
+ }
158
+ catch {
159
+ /* best effort */
160
+ }
127
161
  }
@@ -7,8 +7,10 @@ export interface ClaudeMaxCredentials {
7
7
  accessToken: string;
8
8
  refreshToken?: string;
9
9
  expiresAt?: number;
10
+ refreshTokenExpiresAt?: number;
10
11
  scopes?: string[];
11
12
  subscriptionType?: string;
13
+ rateLimitTier?: string;
12
14
  }
13
15
  /**
14
16
  * Reads Claude Max OAuth credentials.
@@ -18,6 +20,12 @@ export interface ClaudeMaxCredentials {
18
20
  * absent, the token is missing, or the token is expired. Never throws.
19
21
  */
20
22
  export declare function readClaudeMaxCredentials(): Promise<ClaudeMaxCredentials | null>;
23
+ /**
24
+ * Like {@link readClaudeMaxCredentials} but returns the stored credential even
25
+ * when the access token has lapsed: the CLI refreshes it on next use, so an
26
+ * expired access token is still a valid login (until the refresh token expires).
27
+ */
28
+ export declare function readClaudeMaxCredentialsRaw(): ClaudeMaxCredentials | null;
21
29
  /**
22
30
  * Synchronous convenience — returns just the access token or `null`.
23
31
  */
@@ -6,8 +6,18 @@
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.readClaudeMaxCredentials = readClaudeMaxCredentials;
9
+ exports.readClaudeMaxCredentialsRaw = readClaudeMaxCredentialsRaw;
9
10
  exports.readClaudeMaxAccessTokenSync = readClaudeMaxAccessTokenSync;
11
+ const claudeCredentials_1 = require("./claudeCredentials");
10
12
  const credentialIO_1 = require("./credentialIO");
13
+ function readParsed() {
14
+ try {
15
+ return (0, claudeCredentials_1.parseClaudeCredentialBlob)((0, credentialIO_1.readActiveCredentials)());
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ }
11
21
  /**
12
22
  * Reads Claude Max OAuth credentials.
13
23
  *
@@ -16,43 +26,29 @@ const credentialIO_1 = require("./credentialIO");
16
26
  * absent, the token is missing, or the token is expired. Never throws.
17
27
  */
18
28
  async function readClaudeMaxCredentials() {
19
- try {
20
- const raw = (0, credentialIO_1.readActiveCredentials)();
21
- if (!raw)
22
- return null;
23
- const oauth = raw.claudeAiOauth;
24
- if (!oauth?.accessToken)
25
- return null;
26
- if (oauth.expiresAt && Date.now() > oauth.expiresAt)
27
- return null;
28
- return {
29
- accessToken: oauth.accessToken,
30
- refreshToken: oauth.refreshToken,
31
- expiresAt: oauth.expiresAt,
32
- scopes: oauth.scopes,
33
- subscriptionType: oauth.subscriptionType,
34
- };
35
- }
36
- catch {
29
+ const parsed = readParsed();
30
+ if (!parsed)
37
31
  return null;
38
- }
32
+ if (parsed.expiresAt && Date.now() > parsed.expiresAt)
33
+ return null;
34
+ return parsed;
35
+ }
36
+ /**
37
+ * Like {@link readClaudeMaxCredentials} but returns the stored credential even
38
+ * when the access token has lapsed: the CLI refreshes it on next use, so an
39
+ * expired access token is still a valid login (until the refresh token expires).
40
+ */
41
+ function readClaudeMaxCredentialsRaw() {
42
+ return readParsed();
39
43
  }
40
44
  /**
41
45
  * Synchronous convenience — returns just the access token or `null`.
42
46
  */
43
47
  function readClaudeMaxAccessTokenSync() {
44
- try {
45
- const raw = (0, credentialIO_1.readActiveCredentials)();
46
- if (!raw)
47
- return null;
48
- const oauth = raw.claudeAiOauth;
49
- if (!oauth?.accessToken)
50
- return null;
51
- if (oauth.expiresAt && Date.now() > oauth.expiresAt)
52
- return null;
53
- return oauth.accessToken;
54
- }
55
- catch {
48
+ const parsed = readParsed();
49
+ if (!parsed)
56
50
  return null;
57
- }
51
+ if (parsed.expiresAt && Date.now() > parsed.expiresAt)
52
+ return null;
53
+ return parsed.accessToken;
58
54
  }
@@ -1,9 +1,17 @@
1
+ import type { SyncReport } from './accountSyncTypes';
1
2
  export type EnsureDefaultAccountStatus = 'registered' | 'skipped' | 'error';
2
3
  export interface EnsureDefaultAccountsResult {
3
4
  claude: EnsureDefaultAccountStatus;
4
5
  codex: EnsureDefaultAccountStatus;
6
+ /** The full sync report behind the two statuses. */
7
+ sync?: SyncReport;
5
8
  }
6
9
  export interface EnsureDefaultAccountsOptions {
7
10
  logger?: (message: string, error?: unknown) => void;
8
11
  }
12
+ /**
13
+ * Host startup hook: run the one-time on-disk migrations, drop abandoned
14
+ * isolated logins, then reconcile the live logins with the saved profiles
15
+ * (registering any login sidekick has never seen).
16
+ */
9
17
  export declare function ensureDefaultAccounts(options?: EnsureDefaultAccountsOptions): Promise<EnsureDefaultAccountsResult>;
@@ -1,45 +1,9 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  Object.defineProperty(exports, "__esModule", { value: true });
36
3
  exports.ensureDefaultAccounts = ensureDefaultAccounts;
37
- const fs = __importStar(require("fs"));
38
- const path = __importStar(require("path"));
39
4
  const accounts_1 = require("./accounts");
40
- const accountRegistry_1 = require("./accountRegistry");
41
5
  const codexProfiles_1 = require("./codexProfiles");
42
- const credentials_1 = require("./credentials");
6
+ const accountSync_1 = require("./accountSync");
43
7
  function logFailure(options, message, error) {
44
8
  try {
45
9
  options?.logger?.(message, error);
@@ -48,65 +12,61 @@ function logFailure(options, message, error) {
48
12
  // Logging must never make account bootstrap fail.
49
13
  }
50
14
  }
51
- async function ensureDefaultClaudeAccount(options) {
52
- try {
53
- if ((0, accountRegistry_1.getActiveSavedAccount)('claude-code'))
54
- return 'skipped';
55
- const active = (0, accounts_1.readActiveClaudeAccount)();
56
- if (!active)
57
- return 'skipped';
58
- const credentials = await (0, credentials_1.readClaudeMaxCredentials)();
59
- if (!credentials)
60
- return 'skipped';
61
- const result = (0, accounts_1.addCurrentAccount)('Default');
62
- if (result.success)
63
- return 'registered';
64
- logFailure(options, 'Claude default account registration failed.', result.error ?? 'unknown error');
15
+ function statusFromReport(report) {
16
+ if (report.registered)
17
+ return 'registered';
18
+ // A keyring-mode Codex login is a steady state sidekick cannot act on, not a failure.
19
+ if (report.skipped === 'keyring')
20
+ return 'skipped';
21
+ if (report.warnings.length > 0)
65
22
  return 'error';
23
+ return 'skipped';
24
+ }
25
+ /**
26
+ * Host startup hook: run the one-time on-disk migrations, drop abandoned
27
+ * isolated logins, then reconcile the live logins with the saved profiles
28
+ * (registering any login sidekick has never seen).
29
+ */
30
+ async function ensureDefaultAccounts(options) {
31
+ try {
32
+ (0, accounts_1.reconcileClaudeAuthState)();
66
33
  }
67
34
  catch (error) {
68
- logFailure(options, 'Claude default account registration failed.', error);
69
- return 'error';
35
+ logFailure(options, 'Claude auth reconciliation failed.', error);
70
36
  }
71
- }
72
- function cleanupPendingCodexProfile(profileId) {
73
- fs.rmSync(path.join((0, codexProfiles_1.getCodexProfilesDir)(), profileId), { recursive: true, force: true });
74
- }
75
- async function ensureDefaultCodexAccount(options) {
76
37
  try {
77
- if ((0, codexProfiles_1.getActiveCodexAccount)())
78
- return 'skipped';
79
- const systemAuthPath = path.join((0, codexProfiles_1.getSystemCodexHome)(), 'auth.json');
80
- if (!fs.existsSync(systemAuthPath))
81
- return 'skipped';
82
- const result = await (0, codexProfiles_1.prepareCodexAccountAsync)('Default');
83
- if (result.success && !result.needsLogin)
84
- return 'registered';
85
- if (result.profileId) {
86
- cleanupPendingCodexProfile(result.profileId);
87
- }
88
- logFailure(options, 'Codex default account registration failed.', result.error ?? 'Codex auth could not be finalized.');
89
- return 'error';
38
+ await (0, codexProfiles_1.reconcileCodexAuthStateAsync)();
90
39
  }
91
40
  catch (error) {
92
- logFailure(options, 'Codex default account registration failed.', error);
93
- return 'error';
41
+ logFailure(options, 'Codex auth reconciliation failed.', error);
94
42
  }
95
- }
96
- async function ensureDefaultAccounts(options) {
97
- const claude = await ensureDefaultClaudeAccount(options);
98
- const codex = await ensureDefaultCodexAccount(options);
99
43
  try {
100
- (0, accounts_1.reconcileClaudeAuthState)();
44
+ (0, accountSync_1.cleanupAbandonedLogins)();
101
45
  }
102
46
  catch (error) {
103
- logFailure(options, 'Claude auth reconciliation failed.', error);
47
+ logFailure(options, 'Abandoned login cleanup failed.', error);
104
48
  }
49
+ let sync;
105
50
  try {
106
- await (0, codexProfiles_1.reconcileCodexAuthStateAsync)();
51
+ sync = await (0, accountSync_1.syncLiveAccountState)({ reason: 'startup' });
107
52
  }
108
53
  catch (error) {
109
- logFailure(options, 'Codex auth reconciliation failed.', error);
54
+ logFailure(options, 'Account sync failed.', error);
55
+ return { claude: 'error', codex: 'error' };
110
56
  }
111
- return { claude, codex };
57
+ for (const [provider, report] of [
58
+ ['Claude', sync.claude],
59
+ ['Codex', sync.codex],
60
+ ]) {
61
+ if (report.skipped === 'keyring')
62
+ continue;
63
+ for (const warning of report.warnings) {
64
+ logFailure(options, `${provider} account sync: ${warning}`, undefined);
65
+ }
66
+ }
67
+ return {
68
+ claude: statusFromReport(sync.claude),
69
+ codex: statusFromReport(sync.codex),
70
+ sync,
71
+ };
112
72
  }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * One shared definition of "a prompt a person typed" for Claude Code and
3
+ * Codex transcripts. Pure: no filesystem access, so it can classify messages
4
+ * from `readSessionTranscript()` or from `collectPromptHistory()` alike.
5
+ */
6
+ import type { CanonicalTranscriptMessage } from './transcript';
7
+ export type HumanPromptProvider = 'claude-code' | 'codex';
8
+ /**
9
+ * Claude Code `entrypoint` values treated as a person at the keyboard:
10
+ * the interactive CLI, Claude Desktop's Code surface, and `claude -p`.
11
+ * `sdk-ts` / `sdk-py` (Agent SDK programs) are excluded.
12
+ */
13
+ export declare const HUMAN_CLAUDE_ENTRYPOINTS: readonly string[];
14
+ /**
15
+ * Codex `session_meta.source` values whose sessions carry typed prompts.
16
+ * Subagent sessions (`{ subagent: … }`) are excluded; they replay parent
17
+ * history. This is a session-level rule applied by `collectPromptHistory()`.
18
+ */
19
+ export declare const HUMAN_CODEX_SOURCES: readonly string[];
20
+ /** Whether a canonical transcript message is a prompt a person typed. */
21
+ export declare function isHumanPrompt(message: CanonicalTranscriptMessage, provider: HumanPromptProvider): boolean;
22
+ /**
23
+ * The visible text of a human prompt, or `null` when the message is not one.
24
+ * Claude slash commands are returned as typed (`/review foo`); Codex image
25
+ * wrappers and injected context blocks are dropped. Text is never truncated.
26
+ */
27
+ export declare function humanPromptText(message: CanonicalTranscriptMessage, provider: HumanPromptProvider): string | null;
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ /**
3
+ * One shared definition of "a prompt a person typed" for Claude Code and
4
+ * Codex transcripts. Pure: no filesystem access, so it can classify messages
5
+ * from `readSessionTranscript()` or from `collectPromptHistory()` alike.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.HUMAN_CODEX_SOURCES = exports.HUMAN_CLAUDE_ENTRYPOINTS = void 0;
9
+ exports.isHumanPrompt = isHumanPrompt;
10
+ exports.humanPromptText = humanPromptText;
11
+ /**
12
+ * Claude Code `entrypoint` values treated as a person at the keyboard:
13
+ * the interactive CLI, Claude Desktop's Code surface, and `claude -p`.
14
+ * `sdk-ts` / `sdk-py` (Agent SDK programs) are excluded.
15
+ */
16
+ exports.HUMAN_CLAUDE_ENTRYPOINTS = Object.freeze([
17
+ 'cli',
18
+ 'claude-desktop',
19
+ 'sdk-cli',
20
+ ]);
21
+ /**
22
+ * Codex `session_meta.source` values whose sessions carry typed prompts.
23
+ * Subagent sessions (`{ subagent: … }`) are excluded; they replay parent
24
+ * history. This is a session-level rule applied by `collectPromptHistory()`.
25
+ */
26
+ exports.HUMAN_CODEX_SOURCES = Object.freeze(['cli', 'vscode', 'exec']);
27
+ /** Claude user text that is harness output rather than something typed. */
28
+ const CLAUDE_INJECTED_PREFIX = /^(?:<(?:local-command-stdout|local-command-stderr|local-command-caveat|task-notification|system-reminder|bash-input|bash-stdout|bash-stderr|user-prompt-submit-hook)\b|\[Request interrupted\b|This session is being continued from a previous conversation)/;
29
+ const COMMAND_NAME = /<command-name>\s*([\s\S]*?)\s*<\/command-name>/;
30
+ const COMMAND_ARGS = /<command-args>\s*([\s\S]*?)\s*<\/command-args>/;
31
+ /** Codex user-role blocks injected by the harness (context, instructions, markers). */
32
+ const CODEX_INJECTED_PREFIX = /^(?:# AGENTS\.md instructions\b|<(?:environment_context|user_instructions|recommended_plugins|turn_aborted|skill|subagent_notification|codex_internal_context|user_shell_command|permissions instructions|collaboration_mode)\b)/;
33
+ /** Codex wraps attached images as `<image name=…>` / `</image>` text blocks. */
34
+ const CODEX_IMAGE_WRAPPER = /^<\/?image\b[^>]*>$/;
35
+ /** Whether a canonical transcript message is a prompt a person typed. */
36
+ function isHumanPrompt(message, provider) {
37
+ return humanPromptText(message, provider) !== null;
38
+ }
39
+ /**
40
+ * The visible text of a human prompt, or `null` when the message is not one.
41
+ * Claude slash commands are returned as typed (`/review foo`); Codex image
42
+ * wrappers and injected context blocks are dropped. Text is never truncated.
43
+ */
44
+ function humanPromptText(message, provider) {
45
+ if (message.role !== 'user')
46
+ return null;
47
+ if ((message.source.originalRole ?? 'user') !== 'user')
48
+ return null;
49
+ return provider === 'claude-code' ? claudePromptText(message) : codexPromptText(message);
50
+ }
51
+ function claudePromptText(message) {
52
+ const source = message.source;
53
+ if (!source.entrypoint || !exports.HUMAN_CLAUDE_ENTRYPOINTS.includes(source.entrypoint))
54
+ return null;
55
+ if (source.isMeta === true || source.isSidechain === true)
56
+ return null;
57
+ if (source.isCompactSummary === true)
58
+ return null;
59
+ if (source.originKind !== undefined && source.originKind !== 'human')
60
+ return null;
61
+ if (source.promptSource === 'system')
62
+ return null;
63
+ const text = textBlocks(message).join('\n');
64
+ const trimmed = text.trim();
65
+ if (!trimmed)
66
+ return null;
67
+ if (CLAUDE_INJECTED_PREFIX.test(trimmed))
68
+ return null;
69
+ const command = trimmed.match(COMMAND_NAME);
70
+ if (command && /^<command-(?:name|message)>/.test(trimmed)) {
71
+ const name = command[1].trim();
72
+ if (!name)
73
+ return null;
74
+ const args = trimmed.match(COMMAND_ARGS)?.[1].trim() ?? '';
75
+ const visible = name.startsWith('/') ? name : `/${name}`;
76
+ return args ? `${visible} ${args}` : visible;
77
+ }
78
+ return text;
79
+ }
80
+ function codexPromptText(message) {
81
+ const typed = textBlocks(message).filter((text) => {
82
+ const trimmed = text.trim();
83
+ return (trimmed.length > 0 &&
84
+ !CODEX_INJECTED_PREFIX.test(trimmed) &&
85
+ !CODEX_IMAGE_WRAPPER.test(trimmed));
86
+ });
87
+ return typed.length > 0 ? typed.join('\n') : null;
88
+ }
89
+ function textBlocks(message) {
90
+ return message.content
91
+ .filter((block) => block.type === 'text' && typeof block.text === 'string')
92
+ .map((block) => block.text);
93
+ }