cli-link 0.0.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.
- package/README.md +271 -0
- package/bin/agentpilot.js +239 -0
- package/dist/client/assets/History-DR_K6WbO.js +3 -0
- package/dist/client/assets/MarkdownRenderer-D9IwexPM.js +1 -0
- package/dist/client/assets/PageTopBar-SnTIrSb5.js +1 -0
- package/dist/client/assets/Session-EFYFIC_X.js +11 -0
- package/dist/client/assets/Settings-DgmHC_Hw.js +1 -0
- package/dist/client/assets/Workspace-CJvVQVzU.js +8 -0
- package/dist/client/assets/WorkspaceLinkedText-D6hNg0T9.js +2 -0
- package/dist/client/assets/code-highlight-CEcsuMpw.js +1 -0
- package/dist/client/assets/index-Bk4_acsd.css +1 -0
- package/dist/client/assets/index-C89UCwGk.js +2 -0
- package/dist/client/assets/vendor-icons-S_ObYVVf.js +331 -0
- package/dist/client/assets/vendor-markdown-BDwu-Ux6.js +35 -0
- package/dist/client/assets/vendor-motion-n6Lx6G4a.js +9 -0
- package/dist/client/assets/vendor-react-DSV5aFEg.js +67 -0
- package/dist/client/assets/vendor-virtual-CcftJrIC.js +4 -0
- package/dist/client/favicon.svg +18 -0
- package/dist/client/icons/apple-touch-icon.png +0 -0
- package/dist/client/icons/icon-192.png +0 -0
- package/dist/client/icons/icon-512.png +0 -0
- package/dist/client/index.html +34 -0
- package/dist/client/manifest.webmanifest +59 -0
- package/dist/client/sw.js +143 -0
- package/dist/client//344/273/243/347/240/201/351/241/265/351/235/242.png +0 -0
- package/dist/client//345/216/206/345/217/262/350/256/260/345/275/225.png +0 -0
- package/dist/client//345/257/271/350/257/235/351/241/265/351/235/242.png +0 -0
- package/dist/client//350/256/276/347/275/256/351/241/265/351/235/242.png +0 -0
- package/dist/server/cli-manager.js +1532 -0
- package/dist/server/codex-history.js +280 -0
- package/dist/server/index.js +2097 -0
- package/dist/server/store.js +594 -0
- package/dist/server/terminal-qr.js +317 -0
- package/package.json +71 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { existsSync, readFileSync, statSync } from 'fs';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { join, resolve } from 'path';
|
|
5
|
+
import * as store from './store.js';
|
|
6
|
+
const DEFAULT_LIMIT = 500;
|
|
7
|
+
const MAX_ROLLOUT_BYTES = 25 * 1024 * 1024;
|
|
8
|
+
const MAX_MESSAGE_CHARS = 120_000;
|
|
9
|
+
const MAX_TOOL_CHARS = 180_000;
|
|
10
|
+
function resolveCodexHome(raw) {
|
|
11
|
+
if (!raw || raw === '~/.codex')
|
|
12
|
+
return join(homedir(), '.codex');
|
|
13
|
+
if (raw === '~')
|
|
14
|
+
return homedir();
|
|
15
|
+
if (raw.startsWith('~/'))
|
|
16
|
+
return join(homedir(), raw.slice(2));
|
|
17
|
+
return resolve(raw);
|
|
18
|
+
}
|
|
19
|
+
function normalizeLimit(value) {
|
|
20
|
+
const parsed = Number(value);
|
|
21
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
22
|
+
return DEFAULT_LIMIT;
|
|
23
|
+
return Math.min(2000, Math.floor(parsed));
|
|
24
|
+
}
|
|
25
|
+
function normalizeWorkDir(value) {
|
|
26
|
+
if (typeof value !== 'string' || !value.trim())
|
|
27
|
+
return null;
|
|
28
|
+
const trimmed = value.trim();
|
|
29
|
+
if (trimmed === '~')
|
|
30
|
+
return homedir();
|
|
31
|
+
if (trimmed.startsWith('~/'))
|
|
32
|
+
return join(homedir(), trimmed.slice(2));
|
|
33
|
+
return resolve(trimmed);
|
|
34
|
+
}
|
|
35
|
+
function safeString(value) {
|
|
36
|
+
if (typeof value === 'string')
|
|
37
|
+
return value;
|
|
38
|
+
if (value == null)
|
|
39
|
+
return '';
|
|
40
|
+
try {
|
|
41
|
+
return JSON.stringify(value);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return String(value);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function truncate(value, maxChars) {
|
|
48
|
+
const text = safeString(value);
|
|
49
|
+
if (text.length <= maxChars)
|
|
50
|
+
return text;
|
|
51
|
+
return `${text.slice(0, maxChars)}\n\n[truncated ${text.length - maxChars} chars]`;
|
|
52
|
+
}
|
|
53
|
+
function localTime(timestamp, fallbackMs) {
|
|
54
|
+
const ms = typeof timestamp === 'string'
|
|
55
|
+
? new Date(timestamp).getTime()
|
|
56
|
+
: Number(timestamp);
|
|
57
|
+
const date = Number.isFinite(ms) && ms > 0 ? new Date(ms) : new Date(fallbackMs || Date.now());
|
|
58
|
+
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
59
|
+
}
|
|
60
|
+
function cleanTitle(value) {
|
|
61
|
+
const normalized = safeString(value)
|
|
62
|
+
.replace(/```[\s\S]*?```/g, ' ')
|
|
63
|
+
.split(/\r?\n/)
|
|
64
|
+
.map(line => line.trim())
|
|
65
|
+
.filter(Boolean)
|
|
66
|
+
.join(' ')
|
|
67
|
+
.replace(/\s+/g, ' ')
|
|
68
|
+
.trim();
|
|
69
|
+
if (!normalized)
|
|
70
|
+
return 'Codex 会话记录';
|
|
71
|
+
return normalized.length > 60 ? `${normalized.slice(0, 60).trimEnd()}...` : normalized;
|
|
72
|
+
}
|
|
73
|
+
function extractContentText(content) {
|
|
74
|
+
if (typeof content === 'string')
|
|
75
|
+
return content;
|
|
76
|
+
if (!Array.isArray(content))
|
|
77
|
+
return safeString(content);
|
|
78
|
+
return content
|
|
79
|
+
.map((part) => {
|
|
80
|
+
if (typeof part === 'string')
|
|
81
|
+
return part;
|
|
82
|
+
if (!part || typeof part !== 'object')
|
|
83
|
+
return safeString(part);
|
|
84
|
+
const record = part;
|
|
85
|
+
return safeString(record.text || record.content || record.output_text || record.summary || '');
|
|
86
|
+
})
|
|
87
|
+
.filter(Boolean)
|
|
88
|
+
.join('\n');
|
|
89
|
+
}
|
|
90
|
+
function parseRolloutMessages(thread) {
|
|
91
|
+
const rolloutPath = thread.rollout_path;
|
|
92
|
+
if (!rolloutPath || !existsSync(rolloutPath))
|
|
93
|
+
return [];
|
|
94
|
+
if (statSync(rolloutPath).size > MAX_ROLLOUT_BYTES) {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
const startedAt = thread.created_at_ms || (thread.created_at || 0) * 1000 || Date.now();
|
|
98
|
+
const lines = readFileSync(rolloutPath, 'utf8').split(/\r?\n/).filter(Boolean);
|
|
99
|
+
const messages = [];
|
|
100
|
+
const toolByCallId = new Map();
|
|
101
|
+
const push = (message) => {
|
|
102
|
+
const next = {
|
|
103
|
+
id: message.id || `codex-${thread.id}-${messages.length + 1}`,
|
|
104
|
+
type: message.type,
|
|
105
|
+
content: message.content,
|
|
106
|
+
time: message.time,
|
|
107
|
+
status: message.status,
|
|
108
|
+
toolName: message.toolName,
|
|
109
|
+
toolDetails: message.toolDetails,
|
|
110
|
+
toolUseId: message.toolUseId,
|
|
111
|
+
toolResult: message.toolResult,
|
|
112
|
+
permission: message.permission,
|
|
113
|
+
details: message.details,
|
|
114
|
+
};
|
|
115
|
+
messages.push(next);
|
|
116
|
+
return next;
|
|
117
|
+
};
|
|
118
|
+
push({
|
|
119
|
+
type: 'system',
|
|
120
|
+
content: `Codex session synced (${thread.id.slice(0, 8)}...)`,
|
|
121
|
+
time: localTime(thread.created_at_ms || startedAt, startedAt),
|
|
122
|
+
details: {
|
|
123
|
+
subtype: 'init',
|
|
124
|
+
session_id: thread.id,
|
|
125
|
+
model: thread.model || undefined,
|
|
126
|
+
reasoningEffort: thread.reasoning_effort || undefined,
|
|
127
|
+
cwd: thread.cwd || undefined,
|
|
128
|
+
source: thread.source || undefined,
|
|
129
|
+
importedFrom: 'codex-client',
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
for (const line of lines) {
|
|
133
|
+
let entry;
|
|
134
|
+
try {
|
|
135
|
+
entry = JSON.parse(line);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const payload = entry?.payload;
|
|
141
|
+
const time = localTime(entry?.timestamp, startedAt);
|
|
142
|
+
if (!payload || typeof payload !== 'object')
|
|
143
|
+
continue;
|
|
144
|
+
if (entry.type === 'event_msg') {
|
|
145
|
+
if (payload.type === 'user_message') {
|
|
146
|
+
const content = truncate(payload.message, MAX_MESSAGE_CHARS).trim();
|
|
147
|
+
if (content)
|
|
148
|
+
push({ type: 'user_message', content, time });
|
|
149
|
+
}
|
|
150
|
+
else if (payload.type === 'agent_message') {
|
|
151
|
+
const content = truncate(payload.message, MAX_MESSAGE_CHARS).trim();
|
|
152
|
+
if (content)
|
|
153
|
+
push({ type: 'ai_message', content, time });
|
|
154
|
+
}
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (entry.type !== 'response_item')
|
|
158
|
+
continue;
|
|
159
|
+
if (payload.type === 'function_call') {
|
|
160
|
+
const callId = safeString(payload.call_id || payload.id || `call-${messages.length + 1}`);
|
|
161
|
+
const toolName = safeString(payload.name || 'tool');
|
|
162
|
+
const tool = push({
|
|
163
|
+
id: `codex-${thread.id}-tool-${callId}`,
|
|
164
|
+
type: 'tool_call',
|
|
165
|
+
content: toolName,
|
|
166
|
+
time,
|
|
167
|
+
status: 'running',
|
|
168
|
+
toolName,
|
|
169
|
+
toolDetails: truncate(payload.arguments || payload.input || '', MAX_TOOL_CHARS),
|
|
170
|
+
toolUseId: callId,
|
|
171
|
+
});
|
|
172
|
+
toolByCallId.set(callId, tool);
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (payload.type === 'function_call_output') {
|
|
176
|
+
const callId = safeString(payload.call_id || payload.id || '');
|
|
177
|
+
const output = truncate(payload.output || payload.result || payload.content || '', MAX_TOOL_CHARS);
|
|
178
|
+
const existing = callId ? toolByCallId.get(callId) : undefined;
|
|
179
|
+
if (existing) {
|
|
180
|
+
existing.status = 'success';
|
|
181
|
+
existing.toolResult = output;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
push({
|
|
185
|
+
id: `codex-${thread.id}-tool-output-${messages.length + 1}`,
|
|
186
|
+
type: 'tool_call',
|
|
187
|
+
content: 'tool',
|
|
188
|
+
time,
|
|
189
|
+
status: 'success',
|
|
190
|
+
toolName: 'tool',
|
|
191
|
+
toolUseId: callId || undefined,
|
|
192
|
+
toolResult: output,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (payload.type === 'message' && payload.role === 'assistant') {
|
|
198
|
+
const content = truncate(extractContentText(payload.content), MAX_MESSAGE_CHARS).trim();
|
|
199
|
+
const last = messages[messages.length - 1];
|
|
200
|
+
if (content && !(last?.type === 'ai_message' && last.content === content)) {
|
|
201
|
+
push({ type: 'ai_message', content, time });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (thread.tokens_used && thread.tokens_used > 0) {
|
|
206
|
+
push({
|
|
207
|
+
type: 'system',
|
|
208
|
+
content: 'Codex usage summary',
|
|
209
|
+
time: localTime(thread.updated_at_ms || Date.now(), Date.now()),
|
|
210
|
+
details: {
|
|
211
|
+
subtype: 'result',
|
|
212
|
+
session_id: thread.id,
|
|
213
|
+
model: thread.model || undefined,
|
|
214
|
+
inputTokens: thread.tokens_used,
|
|
215
|
+
outputTokens: 0,
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
return messages;
|
|
220
|
+
}
|
|
221
|
+
export function readCodexHistorySessions(options = {}) {
|
|
222
|
+
const codexHome = resolveCodexHome(options.codexHome);
|
|
223
|
+
const stateDbPath = join(codexHome, 'state_5.sqlite');
|
|
224
|
+
if (!existsSync(stateDbPath))
|
|
225
|
+
return [];
|
|
226
|
+
const limit = normalizeLimit(options.limit);
|
|
227
|
+
const workDir = normalizeWorkDir(options.workDir);
|
|
228
|
+
const db = new Database(stateDbPath, { readonly: true, fileMustExist: true });
|
|
229
|
+
try {
|
|
230
|
+
const query = `
|
|
231
|
+
SELECT id, rollout_path, created_at, updated_at, created_at_ms, updated_at_ms,
|
|
232
|
+
title, cwd, source, model, reasoning_effort, tokens_used, first_user_message
|
|
233
|
+
FROM threads
|
|
234
|
+
WHERE rollout_path IS NOT NULL
|
|
235
|
+
AND rollout_path != ''
|
|
236
|
+
AND has_user_event = 1
|
|
237
|
+
${workDir ? 'AND cwd = ?' : ''}
|
|
238
|
+
ORDER BY COALESCE(updated_at_ms, updated_at * 1000, created_at_ms, created_at * 1000) DESC
|
|
239
|
+
LIMIT ?
|
|
240
|
+
`;
|
|
241
|
+
const params = workDir ? [workDir, limit] : [limit];
|
|
242
|
+
const rows = db.prepare(query).all(...params);
|
|
243
|
+
return rows.map((thread) => {
|
|
244
|
+
const startedAt = thread.created_at_ms || (thread.created_at || 0) * 1000 || Date.now();
|
|
245
|
+
const endedAt = thread.updated_at_ms || (thread.updated_at || 0) * 1000 || startedAt;
|
|
246
|
+
const title = cleanTitle(thread.title || thread.first_user_message);
|
|
247
|
+
const messages = parseRolloutMessages(thread);
|
|
248
|
+
return {
|
|
249
|
+
sessionId: `codex-${thread.id}`,
|
|
250
|
+
taskId: `codex-task-${thread.id}`,
|
|
251
|
+
title,
|
|
252
|
+
workDir: thread.cwd || null,
|
|
253
|
+
startedAt,
|
|
254
|
+
endedAt,
|
|
255
|
+
createdAt: endedAt,
|
|
256
|
+
status: 'completed',
|
|
257
|
+
cliConfig: {
|
|
258
|
+
cliType: 'codex',
|
|
259
|
+
cliCommand: 'codex',
|
|
260
|
+
workDir: normalizeWorkDir(thread.cwd) || thread.cwd || process.cwd(),
|
|
261
|
+
confirmMode: 'key',
|
|
262
|
+
cliArgs: [],
|
|
263
|
+
runMode: 'print',
|
|
264
|
+
cliSessionId: thread.id,
|
|
265
|
+
model: thread.model || undefined,
|
|
266
|
+
reasoningEffort: thread.reasoning_effort || undefined,
|
|
267
|
+
importedFrom: 'codex-client',
|
|
268
|
+
},
|
|
269
|
+
messages,
|
|
270
|
+
};
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
finally {
|
|
274
|
+
db.close();
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
export function syncCodexHistory(options = {}) {
|
|
278
|
+
const sessions = readCodexHistorySessions(options);
|
|
279
|
+
return store.importCodexSessions(sessions);
|
|
280
|
+
}
|