sidekick-shared 0.19.3 → 0.21.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.
@@ -37,6 +37,8 @@ exports.getSystemCodexHome = getSystemCodexHome;
37
37
  exports.getCodexMonitoringHomes = getCodexMonitoringHomes;
38
38
  exports.getCodexProfilesDir = getCodexProfilesDir;
39
39
  exports.getCodexProfileHome = getCodexProfileHome;
40
+ exports.readCodexAccountMetadata = readCodexAccountMetadata;
41
+ exports.isCodexProfileAuthenticated = isCodexProfileAuthenticated;
40
42
  exports.listCodexAccounts = listCodexAccounts;
41
43
  exports.getActiveCodexAccount = getActiveCodexAccount;
42
44
  exports.resolveSidekickCodexHome = resolveSidekickCodexHome;
@@ -378,7 +380,7 @@ function prepareCodexAccount(label) {
378
380
  needsLogin: true,
379
381
  };
380
382
  }
381
- function finalizeCodexAccount(profileId) {
383
+ function finalizeCodexAccount(profileId, opts = {}) {
382
384
  const pending = readPendingProfile(profileId);
383
385
  if (!pending) {
384
386
  return { success: false, error: `Codex profile ${profileId} was not prepared.` };
@@ -397,6 +399,9 @@ function finalizeCodexAccount(profileId) {
397
399
  metadata,
398
400
  };
399
401
  (0, accountRegistry_1.upsertSavedAccountProfile)(profile);
402
+ if (opts.activate === false) {
403
+ return { success: true };
404
+ }
400
405
  const hasCredentialFiles = fs.existsSync(path.join(codexHome, 'auth.json')) ||
401
406
  fs.existsSync(path.join(codexHome, '.credentials.json'));
402
407
  if (!hasCredentialFiles) {
@@ -8,6 +8,7 @@
8
8
  * Both accounts.ts and credentials.ts consume this module so that all
9
9
  * credential access is platform-correct in one place.
10
10
  */
11
+ export declare const KEYCHAIN_SERVICE = "Claude Code-credentials";
11
12
  /**
12
13
  * Read the active Claude Code credentials as a parsed object.
13
14
  *
@@ -16,7 +17,7 @@
16
17
  *
17
18
  * Returns `null` when credentials are absent or unreadable.
18
19
  */
19
- export declare function readActiveCredentials(): unknown;
20
+ export declare function readActiveCredentials(configDir?: string): unknown;
20
21
  /**
21
22
  * Write credentials to the active Claude Code credential store.
22
23
  *
@@ -25,4 +26,4 @@ export declare function readActiveCredentials(): unknown;
25
26
  *
26
27
  * Throws on failure.
27
28
  */
28
- export declare function writeActiveCredentials(credentials: unknown): void;
29
+ export declare function writeActiveCredentials(credentials: unknown, configDir?: string): void;
@@ -43,15 +43,17 @@ var __importStar = (this && this.__importStar) || (function () {
43
43
  };
44
44
  })();
45
45
  Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.KEYCHAIN_SERVICE = void 0;
46
47
  exports.readActiveCredentials = readActiveCredentials;
47
48
  exports.writeActiveCredentials = writeActiveCredentials;
48
49
  const fs = __importStar(require("fs"));
49
50
  const path = __importStar(require("path"));
50
51
  const os = __importStar(require("os"));
51
52
  const child_process_1 = require("child_process");
52
- const KEYCHAIN_SERVICE = 'Claude Code-credentials';
53
- function getCredentialsFilePath() {
54
- return path.join(os.homedir(), '.claude', '.credentials.json');
53
+ const claudeProfiles_1 = require("./claudeProfiles");
54
+ exports.KEYCHAIN_SERVICE = 'Claude Code-credentials';
55
+ function getCredentialsFilePath(configDir) {
56
+ return path.join(configDir ?? path.join(os.homedir(), '.claude'), '.credentials.json');
55
57
  }
56
58
  /**
57
59
  * Read the active Claude Code credentials as a parsed object.
@@ -61,11 +63,11 @@ function getCredentialsFilePath() {
61
63
  *
62
64
  * Returns `null` when credentials are absent or unreadable.
63
65
  */
64
- function readActiveCredentials() {
66
+ function readActiveCredentials(configDir) {
65
67
  if (process.platform === 'darwin') {
66
68
  try {
67
69
  const raw = (0, child_process_1.execFileSync)('security', [
68
- 'find-generic-password', '-s', KEYCHAIN_SERVICE, '-w',
70
+ 'find-generic-password', '-s', (0, claudeProfiles_1.claudeKeychainService)(configDir), '-w',
69
71
  ], { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
70
72
  return JSON.parse(raw.trim());
71
73
  }
@@ -75,7 +77,7 @@ function readActiveCredentials() {
75
77
  }
76
78
  // Linux / WSL / Windows — file-based
77
79
  try {
78
- return JSON.parse(fs.readFileSync(getCredentialsFilePath(), 'utf8'));
80
+ return JSON.parse(fs.readFileSync(getCredentialsFilePath(configDir), 'utf8'));
79
81
  }
80
82
  catch {
81
83
  return null;
@@ -89,20 +91,20 @@ function readActiveCredentials() {
89
91
  *
90
92
  * Throws on failure.
91
93
  */
92
- function writeActiveCredentials(credentials) {
94
+ function writeActiveCredentials(credentials, configDir) {
93
95
  const json = JSON.stringify(credentials);
94
96
  JSON.parse(json); // validate round-trip
95
97
  if (process.platform === 'darwin') {
96
98
  (0, child_process_1.execFileSync)('security', [
97
99
  'add-generic-password', '-U',
98
- '-s', KEYCHAIN_SERVICE,
100
+ '-s', (0, claudeProfiles_1.claudeKeychainService)(configDir),
99
101
  '-a', process.env.USER || 'user',
100
102
  '-w', json,
101
103
  ], { stdio: ['pipe', 'pipe', 'pipe'] });
102
104
  return;
103
105
  }
104
106
  // Linux / WSL / Windows — file-based
105
- const credPath = getCredentialsFilePath();
107
+ const credPath = getCredentialsFilePath(configDir);
106
108
  const tmp = credPath + '.tmp';
107
109
  fs.writeFileSync(tmp, json, { encoding: 'utf8', mode: 0o600 });
108
110
  fs.renameSync(tmp, credPath);
@@ -96,6 +96,12 @@ function ensureDefaultCodexAccount(options) {
96
96
  async function ensureDefaultAccounts(options) {
97
97
  const claude = await ensureDefaultClaudeAccount(options);
98
98
  const codex = ensureDefaultCodexAccount(options);
99
+ try {
100
+ (0, accounts_1.reconcileClaudeAuthState)();
101
+ }
102
+ catch (error) {
103
+ logFailure(options, 'Claude auth reconciliation failed.', error);
104
+ }
99
105
  try {
100
106
  (0, codexProfiles_1.reconcileCodexAuthState)();
101
107
  }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Aggregate actionable assets for exactly one working directory.
3
+ *
4
+ * Portions adapted from `trawl` by Juan Fourie (B33pBeeps), MIT licensed:
5
+ * https://github.com/B33pBeeps/trawl
6
+ *
7
+ * @module extractors/gatherAssets
8
+ */
9
+ import { type AssetAgent, type ExtractedAssets, type ExtractedAssetType } from './sessionAssets';
10
+ export type { AssetAgent } from './sessionAssets';
11
+ export interface GatherAssetsOptions {
12
+ cwd?: string;
13
+ agents?: AssetAgent[];
14
+ sessionsPerAgent?: number;
15
+ caps?: Partial<Record<ExtractedAssetType, number>>;
16
+ }
17
+ export interface GatherAssetsResult extends ExtractedAssets {
18
+ inChat: boolean;
19
+ }
20
+ export declare function gatherAssetsForCwd(options?: GatherAssetsOptions): GatherAssetsResult;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ /**
3
+ * Aggregate actionable assets for exactly one working directory.
4
+ *
5
+ * Portions adapted from `trawl` by Juan Fourie (B33pBeeps), MIT licensed:
6
+ * https://github.com/B33pBeeps/trawl
7
+ *
8
+ * @module extractors/gatherAssets
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.gatherAssetsForCwd = gatherAssetsForCwd;
12
+ const node_path_1 = require("node:path");
13
+ const claudeAssets_1 = require("./sources/claudeAssets");
14
+ const codexAssets_1 = require("./sources/codexAssets");
15
+ const sessionAssets_1 = require("./sessionAssets");
16
+ const ALL_AGENTS = ['claude', 'codex'];
17
+ function gatherAssetsForCwd(options = {}) {
18
+ const cwd = (0, node_path_1.resolve)(options.cwd || process.cwd());
19
+ const sessionLimit = options.sessionsPerAgent ?? 3;
20
+ const agents = options.agents ?? ALL_AGENTS;
21
+ const caps = { ...sessionAssets_1.DEFAULT_CAPS, ...(options.caps || {}) };
22
+ const sources = [];
23
+ if (agents.includes('claude'))
24
+ sources.push((0, claudeAssets_1.readClaudeAssets)(cwd, sessionLimit));
25
+ if (agents.includes('codex'))
26
+ sources.push((0, codexAssets_1.readCodexAssets)(cwd, sessionLimit));
27
+ const merge = (key, cap) => (0, sessionAssets_1.capped)((0, sessionAssets_1.dedupeAssets)(sources.flatMap((source) => source[key]).sort(sessionAssets_1.byTimestampDesc)), cap);
28
+ return {
29
+ urls: merge('urls', caps.url),
30
+ paths: merge('paths', caps.path),
31
+ commands: merge('commands', caps.command),
32
+ plans: (0, sessionAssets_1.capped)((0, sessionAssets_1.dedupePlansByTitle)(sources.flatMap((source) => source.plans).sort(sessionAssets_1.byTimestampDesc)), caps.plan),
33
+ inChat: sources.some((source) => source.hadSession),
34
+ };
35
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Extract actionable assets from agent session transcript text.
3
+ *
4
+ * Portions adapted from `trawl` by Juan Fourie (B33pBeeps), MIT licensed:
5
+ * https://github.com/B33pBeeps/trawl
6
+ *
7
+ * @module extractors/sessionAssets
8
+ */
9
+ export type ExtractedAssetType = 'url' | 'path' | 'command' | 'plan';
10
+ export type AssetAgent = 'claude' | 'codex';
11
+ export interface ExtractedAssetProvenance {
12
+ agent?: AssetAgent;
13
+ sessionPath?: string;
14
+ source?: string;
15
+ }
16
+ export interface ExtractedAsset extends ExtractedAssetProvenance {
17
+ type: ExtractedAssetType;
18
+ text: string;
19
+ display: string;
20
+ timestamp?: string;
21
+ }
22
+ export interface ExtractedAssets {
23
+ urls: ExtractedAsset[];
24
+ paths: ExtractedAsset[];
25
+ commands: ExtractedAsset[];
26
+ plans: ExtractedAsset[];
27
+ }
28
+ export interface SourceAssets extends ExtractedAssets {
29
+ hadSession: boolean;
30
+ }
31
+ export declare const DEFAULT_CAPS: Record<ExtractedAssetType, number>;
32
+ export declare function extractUrls(text: unknown): string[];
33
+ export declare function extractFilePaths(text: unknown, cwd?: string): Array<{
34
+ file: string;
35
+ line?: number;
36
+ }>;
37
+ export declare function extractCommands(text: unknown): string[];
38
+ export declare function urlAsset(url: string, timestamp?: string, provenance?: ExtractedAssetProvenance): ExtractedAsset;
39
+ export declare function commandAsset(command: string, timestamp?: string, provenance?: ExtractedAssetProvenance): ExtractedAsset;
40
+ export declare function pathAsset(path: {
41
+ file: string;
42
+ line?: number;
43
+ }, timestamp?: string, provenance?: ExtractedAssetProvenance): ExtractedAsset;
44
+ export declare function planAsset(markdown: string, timestamp?: string, provenance?: ExtractedAssetProvenance): ExtractedAsset;
45
+ export declare function flat(value: string): string;
46
+ export declare function planTitle(markdown: string): string;
47
+ export declare function isExistingFile(filePath: string): boolean;
48
+ export declare function readPlanFile(filePath: string | undefined): string | undefined;
49
+ export declare function byTimestampDesc(a: ExtractedAsset, b: ExtractedAsset): number;
50
+ export declare function dedupeAssets(assets: ExtractedAsset[]): ExtractedAsset[];
51
+ export declare function dedupePlansByTitle(plans: ExtractedAsset[]): ExtractedAsset[];
52
+ export declare function capped(assets: ExtractedAsset[], cap: number): ExtractedAsset[];
@@ -0,0 +1,219 @@
1
+ "use strict";
2
+ /**
3
+ * Extract actionable assets from agent session transcript text.
4
+ *
5
+ * Portions adapted from `trawl` by Juan Fourie (B33pBeeps), MIT licensed:
6
+ * https://github.com/B33pBeeps/trawl
7
+ *
8
+ * @module extractors/sessionAssets
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.DEFAULT_CAPS = void 0;
45
+ exports.extractUrls = extractUrls;
46
+ exports.extractFilePaths = extractFilePaths;
47
+ exports.extractCommands = extractCommands;
48
+ exports.urlAsset = urlAsset;
49
+ exports.commandAsset = commandAsset;
50
+ exports.pathAsset = pathAsset;
51
+ exports.planAsset = planAsset;
52
+ exports.flat = flat;
53
+ exports.planTitle = planTitle;
54
+ exports.isExistingFile = isExistingFile;
55
+ exports.readPlanFile = readPlanFile;
56
+ exports.byTimestampDesc = byTimestampDesc;
57
+ exports.dedupeAssets = dedupeAssets;
58
+ exports.dedupePlansByTitle = dedupePlansByTitle;
59
+ exports.capped = capped;
60
+ const fs = __importStar(require("node:fs"));
61
+ const os = __importStar(require("node:os"));
62
+ const node_path_1 = require("node:path");
63
+ const URL_RE = /(?:https?|file):\/\/[^\s"'<>)\]`\\]+/g;
64
+ const PATH_RE = /(?:~|\.{0,2}\/)?[A-Za-z0-9_@.-]*(?:\/[A-Za-z0-9_@.-]+)+(?::\d+){0,2}|[A-Za-z0-9_@-]+\.[A-Za-z0-9]{1,8}(?::\d+){0,2}/g;
65
+ const SHELL_TAGS = new Set(['sh', 'bash', 'shell', 'zsh', 'console', 'shellscript', 'shell-session']);
66
+ exports.DEFAULT_CAPS = {
67
+ command: 60,
68
+ path: 60,
69
+ url: 40,
70
+ plan: 25,
71
+ };
72
+ function extractUrls(text) {
73
+ if (!text)
74
+ return [];
75
+ const urls = [];
76
+ for (const match of String(text).matchAll(URL_RE)) {
77
+ urls.push(match[0].replace(/[.,;:!?'"`]+$/, ''));
78
+ }
79
+ return urls;
80
+ }
81
+ function extractFilePaths(text, cwd) {
82
+ if (!text)
83
+ return [];
84
+ const home = os.homedir();
85
+ const base = cwd ? (0, node_path_1.resolve)(cwd) : process.cwd();
86
+ const paths = [];
87
+ for (const match of String(text).matchAll(PATH_RE)) {
88
+ let token = match[0].replace(/[.,;:)>"]+$/, '');
89
+ let line;
90
+ const lineMatch = /^(.+?):(\d+)(?::\d+)?$/.exec(token);
91
+ if (lineMatch) {
92
+ token = lineMatch[1];
93
+ line = Number(lineMatch[2]);
94
+ }
95
+ let file = token.startsWith('~') ? token.replace(/^~/, home) : token;
96
+ if (!(0, node_path_1.isAbsolute)(file))
97
+ file = (0, node_path_1.resolve)(base, file);
98
+ if (isExistingFile(file)) {
99
+ paths.push(line !== undefined ? { file, line } : { file });
100
+ }
101
+ }
102
+ return paths;
103
+ }
104
+ function extractCommands(text) {
105
+ if (!text)
106
+ return [];
107
+ const commands = [];
108
+ const source = String(text);
109
+ const fence = /```([\w.-]*)[ \t]*\r?\n([\s\S]*?)```/g;
110
+ const fencedSpans = [];
111
+ let match;
112
+ while ((match = fence.exec(source))) {
113
+ fencedSpans.push([match.index, fence.lastIndex]);
114
+ const shellTagged = SHELL_TAGS.has(match[1].toLowerCase());
115
+ let continuation = '';
116
+ for (const rawLine of match[2].split('\n')) {
117
+ const promptLine = /^\s*\$\s+(.+)$/.exec(rawLine);
118
+ if (promptLine) {
119
+ commands.push(promptLine[1].trim());
120
+ continue;
121
+ }
122
+ if (!shellTagged)
123
+ continue;
124
+ let line = rawLine.trim();
125
+ if (!line || line.startsWith('#'))
126
+ continue;
127
+ if (continuation) {
128
+ line = `${continuation} ${line}`;
129
+ continuation = '';
130
+ }
131
+ if (line.endsWith('\\')) {
132
+ continuation = line.slice(0, -1).trim();
133
+ continue;
134
+ }
135
+ commands.push(line);
136
+ }
137
+ }
138
+ for (const lineMatch of source.matchAll(/^[ \t]*\$[ \t]+(.+)$/gm)) {
139
+ const index = lineMatch.index ?? 0;
140
+ if (fencedSpans.some(([start, end]) => index >= start && index < end))
141
+ continue;
142
+ commands.push(lineMatch[1].trim());
143
+ }
144
+ return commands;
145
+ }
146
+ function urlAsset(url, timestamp, provenance = {}) {
147
+ return { type: 'url', text: url, display: url, timestamp, ...provenance };
148
+ }
149
+ function commandAsset(command, timestamp, provenance = {}) {
150
+ return { type: 'command', text: command, display: flat(command), timestamp, ...provenance };
151
+ }
152
+ function pathAsset(path, timestamp, provenance = {}) {
153
+ const text = path.line !== undefined ? `${path.file}:${path.line}` : path.file;
154
+ return { type: 'path', text, display: text, timestamp, ...provenance };
155
+ }
156
+ function planAsset(markdown, timestamp, provenance = {}) {
157
+ return { type: 'plan', text: markdown, display: planTitle(markdown), timestamp, ...provenance };
158
+ }
159
+ function flat(value) {
160
+ return value.replace(/\s*\n\s*/g, ' ').trim();
161
+ }
162
+ function planTitle(markdown) {
163
+ for (const rawLine of markdown.split('\n')) {
164
+ const line = rawLine.trim();
165
+ if (line)
166
+ return line.replace(/^#{1,6}\s*/, '');
167
+ }
168
+ return 'Plan';
169
+ }
170
+ function isExistingFile(filePath) {
171
+ try {
172
+ return fs.statSync(filePath).isFile();
173
+ }
174
+ catch {
175
+ return false;
176
+ }
177
+ }
178
+ function readPlanFile(filePath) {
179
+ if (!filePath)
180
+ return undefined;
181
+ try {
182
+ return fs.readFileSync(filePath, 'utf8');
183
+ }
184
+ catch {
185
+ return undefined;
186
+ }
187
+ }
188
+ function byTimestampDesc(a, b) {
189
+ return (b.timestamp || '').localeCompare(a.timestamp || '');
190
+ }
191
+ function dedupeAssets(assets) {
192
+ const seen = new Set();
193
+ const deduped = [];
194
+ for (const asset of assets) {
195
+ if (!asset.text)
196
+ continue;
197
+ const key = `${asset.type}\0${asset.text}`;
198
+ if (seen.has(key))
199
+ continue;
200
+ seen.add(key);
201
+ deduped.push(asset);
202
+ }
203
+ return deduped;
204
+ }
205
+ function dedupePlansByTitle(plans) {
206
+ const seen = new Set();
207
+ const deduped = [];
208
+ for (const plan of plans) {
209
+ const key = (plan.display || plan.text).trim().toLowerCase();
210
+ if (!key || seen.has(key))
211
+ continue;
212
+ seen.add(key);
213
+ deduped.push(plan);
214
+ }
215
+ return deduped;
216
+ }
217
+ function capped(assets, cap) {
218
+ return cap > 0 ? assets.slice(0, cap) : assets;
219
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Claude Code transcript reader for actionable asset extraction.
3
+ *
4
+ * Portions adapted from `trawl` by Juan Fourie (B33pBeeps), MIT licensed:
5
+ * https://github.com/B33pBeeps/trawl
6
+ *
7
+ * @module extractors/sources/claudeAssets
8
+ */
9
+ import { type SourceAssets } from '../sessionAssets';
10
+ export declare function claudeSessions(cwd: string, limit?: number): string[];
11
+ export declare function readClaudeAssets(cwd: string, limit?: number): SourceAssets;
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ /**
3
+ * Claude Code transcript reader for actionable asset extraction.
4
+ *
5
+ * Portions adapted from `trawl` by Juan Fourie (B33pBeeps), MIT licensed:
6
+ * https://github.com/B33pBeeps/trawl
7
+ *
8
+ * @module extractors/sources/claudeAssets
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.claudeSessions = claudeSessions;
12
+ exports.readClaudeAssets = readClaudeAssets;
13
+ const node_path_1 = require("node:path");
14
+ const node_fs_1 = require("node:fs");
15
+ const sessionPathResolver_1 = require("../../parsers/sessionPathResolver");
16
+ const sessionAssets_1 = require("../sessionAssets");
17
+ const PATH_TOOLS = new Set(['Read', 'Write', 'Edit', 'NotebookEdit']);
18
+ function dirExists(path) {
19
+ try {
20
+ return (0, node_fs_1.statSync)(path).isDirectory();
21
+ }
22
+ catch {
23
+ return false;
24
+ }
25
+ }
26
+ function filesByMtimeDesc(dir, filter) {
27
+ let names;
28
+ try {
29
+ names = (0, node_fs_1.readdirSync)(dir);
30
+ }
31
+ catch {
32
+ return [];
33
+ }
34
+ return names
35
+ .filter(filter)
36
+ .map((name) => (0, node_path_1.join)(dir, name))
37
+ .map((filePath) => {
38
+ try {
39
+ return { path: filePath, mtime: (0, node_fs_1.statSync)(filePath).mtimeMs };
40
+ }
41
+ catch {
42
+ return null;
43
+ }
44
+ })
45
+ .filter((entry) => entry !== null)
46
+ .sort((a, b) => b.mtime - a.mtime)
47
+ .map((entry) => entry.path);
48
+ }
49
+ function readJsonl(filePath) {
50
+ let raw;
51
+ try {
52
+ raw = (0, node_fs_1.readFileSync)(filePath, 'utf8');
53
+ }
54
+ catch {
55
+ return [];
56
+ }
57
+ const lines = [];
58
+ for (const line of raw.split('\n')) {
59
+ const trimmed = line.trim();
60
+ if (!trimmed)
61
+ continue;
62
+ try {
63
+ lines.push(JSON.parse(trimmed));
64
+ }
65
+ catch {
66
+ // Ignore partial or malformed JSONL lines.
67
+ }
68
+ }
69
+ return lines;
70
+ }
71
+ function claudeSessions(cwd, limit = 3) {
72
+ const dir = (0, sessionPathResolver_1.getSessionDirectory)(cwd);
73
+ if (!dirExists(dir))
74
+ return [];
75
+ return filesByMtimeDesc(dir, (name) => name.endsWith('.jsonl')).slice(0, limit);
76
+ }
77
+ function asString(value) {
78
+ return typeof value === 'string' ? value : undefined;
79
+ }
80
+ function provenance(sessionPath, source) {
81
+ return { agent: 'claude', sessionPath, source };
82
+ }
83
+ function addTextAssets(acc, text, cwd, timestamp, meta) {
84
+ for (const url of (0, sessionAssets_1.extractUrls)(text))
85
+ acc.urls.push((0, sessionAssets_1.urlAsset)(url, timestamp, meta));
86
+ for (const filePath of (0, sessionAssets_1.extractFilePaths)(text, cwd))
87
+ acc.paths.push((0, sessionAssets_1.pathAsset)(filePath, timestamp, meta));
88
+ for (const command of (0, sessionAssets_1.extractCommands)(text))
89
+ acc.commands.push((0, sessionAssets_1.commandAsset)(command, timestamp, meta));
90
+ }
91
+ function addToolPathAssets(acc, value, cwd, timestamp, meta) {
92
+ for (const filePath of (0, sessionAssets_1.extractFilePaths)(value, cwd)) {
93
+ acc.paths.push((0, sessionAssets_1.pathAsset)(filePath, timestamp, meta));
94
+ }
95
+ }
96
+ function accumClaude(filePath, cwd, acc) {
97
+ for (const line of readJsonl(filePath)) {
98
+ const timestamp = asString(line.timestamp);
99
+ if (line.type === 'attachment') {
100
+ const attachment = line.attachment;
101
+ const planFilePath = asString(attachment?.planFilePath);
102
+ if (planFilePath && (0, sessionAssets_1.isExistingFile)(planFilePath)) {
103
+ const text = (0, sessionAssets_1.readPlanFile)(planFilePath);
104
+ if (text?.trim())
105
+ acc.plans.push((0, sessionAssets_1.planAsset)(text, timestamp, provenance(filePath, 'attachment:plan')));
106
+ }
107
+ continue;
108
+ }
109
+ const message = line.message;
110
+ const content = message?.content;
111
+ if (typeof content === 'string') {
112
+ addTextAssets(acc, content, cwd, timestamp, provenance(filePath, 'message'));
113
+ continue;
114
+ }
115
+ if (!Array.isArray(content))
116
+ continue;
117
+ for (const block of content) {
118
+ const typedBlock = block;
119
+ if (typedBlock.type === 'text') {
120
+ addTextAssets(acc, typedBlock.text, cwd, timestamp, provenance(filePath, 'message'));
121
+ continue;
122
+ }
123
+ if (typedBlock.type !== 'tool_use')
124
+ continue;
125
+ const name = asString(typedBlock.name);
126
+ const input = typedBlock.input || {};
127
+ const toolSource = name ? `tool:${name}` : 'tool';
128
+ const toolMeta = provenance(filePath, toolSource);
129
+ if (name === 'Bash') {
130
+ for (const url of (0, sessionAssets_1.extractUrls)(input.command))
131
+ acc.urls.push((0, sessionAssets_1.urlAsset)(url, timestamp, toolMeta));
132
+ addToolPathAssets(acc, input.command, cwd, timestamp, toolMeta);
133
+ }
134
+ else if (name && PATH_TOOLS.has(name)) {
135
+ addToolPathAssets(acc, input.file_path, cwd, timestamp, toolMeta);
136
+ }
137
+ else if (name === 'WebFetch' || name === 'WebSearch') {
138
+ for (const url of (0, sessionAssets_1.extractUrls)(JSON.stringify(input)))
139
+ acc.urls.push((0, sessionAssets_1.urlAsset)(url, timestamp, toolMeta));
140
+ }
141
+ else if (name === 'ExitPlanMode') {
142
+ const inline = asString(input.plan);
143
+ const markdown = inline?.trim() ? inline : (0, sessionAssets_1.readPlanFile)(asString(input.planFilePath));
144
+ if (markdown?.trim())
145
+ acc.plans.push((0, sessionAssets_1.planAsset)(markdown, timestamp, toolMeta));
146
+ }
147
+ }
148
+ }
149
+ }
150
+ function readClaudeAssets(cwd, limit = 3) {
151
+ const sessions = claudeSessions(cwd, limit);
152
+ const acc = { urls: [], paths: [], commands: [], plans: [], hadSession: sessions.length > 0 };
153
+ for (const session of sessions)
154
+ accumClaude(session, cwd, acc);
155
+ return acc;
156
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Codex CLI rollout reader for actionable asset extraction.
3
+ *
4
+ * Portions adapted from `trawl` by Juan Fourie (B33pBeeps), MIT licensed:
5
+ * https://github.com/B33pBeeps/trawl
6
+ *
7
+ * @module extractors/sources/codexAssets
8
+ */
9
+ import { type SourceAssets } from '../sessionAssets';
10
+ export declare function codexSessions(cwd: string, limit?: number): string[];
11
+ export declare function readCodexAssets(cwd: string, limit?: number): SourceAssets;