agentel 0.2.0 → 0.2.2
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 +161 -63
- package/agentlog-spec.md +42 -35
- package/bin/agentlog-recall.js +2 -0
- package/bin/agentlog.js +12 -0
- package/docs/code-reference.md +120 -34
- package/docs/history-source-handling.md +236 -81
- package/docs/release.md +8 -8
- package/package.json +5 -4
- package/src/archive.js +278 -20
- package/src/cli.js +3457 -511
- package/src/config.js +42 -1
- package/src/doctor.js +167 -10
- package/src/importers/gemini.js +369 -7
- package/src/importers.js +1837 -135
- package/src/mcp.js +4 -1
- package/src/parser-versions.js +37 -22
- package/src/paths.js +4 -2
- package/src/redaction.js +140 -17
- package/src/search.js +671 -52
- package/src/supervisor.js +206 -57
- package/src/sync.js +459 -12
package/src/config.js
CHANGED
|
@@ -194,12 +194,24 @@ function setConfigKey(key, value, env = process.env) {
|
|
|
194
194
|
const cfg = loadConfig(env);
|
|
195
195
|
const parts = key.split(".").filter(Boolean);
|
|
196
196
|
if (parts.length === 0) throw new Error("config key is required");
|
|
197
|
+
if (key === "device.name") {
|
|
198
|
+
const deviceName = String(value || "").trim();
|
|
199
|
+
if (!deviceName) throw new Error("device.name cannot be empty");
|
|
200
|
+
cfg.device = {
|
|
201
|
+
...(cfg.device || {}),
|
|
202
|
+
name: deviceName,
|
|
203
|
+
slug: slugifyDeviceName(deviceName)
|
|
204
|
+
};
|
|
205
|
+
cfg.updatedAt = new Date().toISOString();
|
|
206
|
+
saveConfig(cfg, env);
|
|
207
|
+
return cfg;
|
|
208
|
+
}
|
|
197
209
|
let cursor = cfg;
|
|
198
210
|
for (const part of parts.slice(0, -1)) {
|
|
199
211
|
if (!cursor[part] || typeof cursor[part] !== "object") cursor[part] = {};
|
|
200
212
|
cursor = cursor[part];
|
|
201
213
|
}
|
|
202
|
-
cursor[parts[parts.length - 1]] = parseConfigValue(value);
|
|
214
|
+
cursor[parts[parts.length - 1]] = normalizeConfigKeyValue(key, parseConfigValue(value));
|
|
203
215
|
cfg.updatedAt = new Date().toISOString();
|
|
204
216
|
saveConfig(cfg, env);
|
|
205
217
|
return cfg;
|
|
@@ -228,6 +240,35 @@ function parseConfigValue(value) {
|
|
|
228
240
|
}
|
|
229
241
|
}
|
|
230
242
|
|
|
243
|
+
function normalizeConfigKeyValue(key, value) {
|
|
244
|
+
if (key === "imports.sources") return normalizeImportSources(value);
|
|
245
|
+
if (key === "imports.defaultSinceDays") return normalizeNonNegativeInteger(value, 30);
|
|
246
|
+
if (key === "sync.intervalMinutes" || key === "index.intervalMinutes" || key === "index.batteryIntervalMinutes") {
|
|
247
|
+
return normalizeSyncInterval(value);
|
|
248
|
+
}
|
|
249
|
+
if (key === "storage.root") return path.resolve(String(value || ""));
|
|
250
|
+
return value;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function normalizeImportSources(value) {
|
|
254
|
+
const raw = Array.isArray(value)
|
|
255
|
+
? value
|
|
256
|
+
: String(value || "")
|
|
257
|
+
.split(/[,\s]+/)
|
|
258
|
+
.map((item) => item.trim())
|
|
259
|
+
.filter(Boolean);
|
|
260
|
+
const selected = new Set(raw);
|
|
261
|
+
const ordered = IMPORT_SOURCE_ORDER.filter((source) => selected.has(source));
|
|
262
|
+
const extras = raw.filter((source) => !ordered.includes(source));
|
|
263
|
+
return enabledImportSources([...ordered, ...extras]);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function normalizeNonNegativeInteger(value, fallback) {
|
|
267
|
+
const number = Number(value);
|
|
268
|
+
if (!Number.isFinite(number) || number < 0) return fallback;
|
|
269
|
+
return Math.round(number);
|
|
270
|
+
}
|
|
271
|
+
|
|
231
272
|
module.exports = {
|
|
232
273
|
defaultConfig,
|
|
233
274
|
effectiveImportSources,
|
package/src/doctor.js
CHANGED
|
@@ -1,34 +1,42 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const fs = require("fs");
|
|
4
|
-
const os = require("os");
|
|
5
|
-
const path = require("path");
|
|
6
4
|
const { spawnSync } = require("child_process");
|
|
7
|
-
const { archiveRoot, countSessions } = require("./archive");
|
|
5
|
+
const { archiveRoot, countSessions, listSessions } = require("./archive");
|
|
8
6
|
const { autostartStatus, recallInvocation } = require("./autostart");
|
|
9
7
|
const { loadConfig } = require("./config");
|
|
10
8
|
const { discoverCliHistory } = require("./importers");
|
|
9
|
+
const { canonicalSourceType, parserVersionForSource } = require("./parser-versions");
|
|
11
10
|
const { paths, readJson } = require("./paths");
|
|
12
11
|
const { hasRemoteTarget } = require("./sync");
|
|
13
12
|
|
|
14
|
-
function runDoctor(env = process.env) {
|
|
13
|
+
function runDoctor(env = process.env, options = {}) {
|
|
14
|
+
reportProgress(options, "Doctor", "checking configuration", 1, 5);
|
|
15
15
|
const cfg = loadConfig(env);
|
|
16
16
|
const checks = [];
|
|
17
17
|
add(checks, "config", fs.existsSync(paths(env).config), paths(env).config, "Run `agentlog init` to create config.");
|
|
18
18
|
add(checks, "archive", fs.existsSync(archiveRoot(env)), archiveRoot(env), "Run `agentlog import --source all --since 30d` to create the archive.");
|
|
19
|
-
|
|
19
|
+
reportProgress(options, "Doctor", "checking local tools", 2, 5);
|
|
20
|
+
add(checks, "rg", commandExists("rg"), "ripgrep search", "Install ripgrep with `brew install ripgrep` for faster `agentlog history` search.");
|
|
20
21
|
add(checks, "sqlite3", commandExists("sqlite3"), "Codex, Devin, and Cursor SQLite imports", "Install sqlite3 for Codex, Devin, and Cursor state database imports.");
|
|
22
|
+
add(checks, "search index", !cfg.index?.paused, cfg.index?.paused ? "paused" : "active", "Run `agentlog index resume` to restart search indexing.");
|
|
21
23
|
add(checks, "collector", Boolean(cfg.collector?.enabled), cfg.collector?.otlpEndpoint || "", "Enable collector with `agentlog config set collector.enabled true`.");
|
|
22
24
|
add(checks, "remote sync", hasRemoteTarget(cfg, env), remoteTargetLabel(cfg), "Configure with `agentlog sync --endpoint ... --bucket ...`.");
|
|
25
|
+
reportProgress(options, "Doctor", "checking watcher and integrations", 3, 5);
|
|
23
26
|
const auto = autostartStatus();
|
|
24
|
-
add(checks, "autostart", Boolean(auto.enabled), auto.file || auto.note || "", "Run `agentlog
|
|
27
|
+
add(checks, "autostart", Boolean(auto.enabled), auto.file || auto.note || "", "Run `agentlog watcher login enable` to start at login.");
|
|
25
28
|
const invocation = recallInvocation();
|
|
26
|
-
add(checks, "recall command", Boolean(invocation.command), `${invocation.command} ${invocation.args.join(" ")}`, "Install recall with `agentlog
|
|
29
|
+
add(checks, "recall command", Boolean(invocation.command), `${invocation.command} ${invocation.args.join(" ")}`, "Install recall with `agentlog integrations add-to codex` or `agentlog init`.");
|
|
30
|
+
reportProgress(options, "Doctor", "checking parser versions", 4, 5);
|
|
31
|
+
const parsers = parserVersionHealth(env);
|
|
32
|
+
add(checks, "parser versions", parsers.outdated.length === 0, parserVersionSummary(parsers), parserVersionRemediation(parsers));
|
|
27
33
|
|
|
28
|
-
|
|
34
|
+
reportProgress(options, "Doctor", "discovering local sources", 5, 5);
|
|
35
|
+
const discovery = discoverCliHistory(env, { onProgress: options.onProgress });
|
|
29
36
|
const coverage = sourceCoverage(discovery, cfg);
|
|
30
37
|
const sessions = countSessions(env);
|
|
31
38
|
const importState = readJson(paths(env).imports, { files: {}, sessions: {} });
|
|
39
|
+
const recommendations = buildRecommendations(checks, coverage, parsers);
|
|
32
40
|
return {
|
|
33
41
|
ok: checks.every((check) => check.status === "ok"),
|
|
34
42
|
home: paths(env).home,
|
|
@@ -40,7 +48,9 @@ function runDoctor(env = process.env) {
|
|
|
40
48
|
trackedSessions: Object.keys(importState.sessions || {}).length,
|
|
41
49
|
trackedFiles: Object.keys(importState.files || {}).length
|
|
42
50
|
},
|
|
43
|
-
coverage
|
|
51
|
+
coverage,
|
|
52
|
+
parsers,
|
|
53
|
+
recommendations
|
|
44
54
|
};
|
|
45
55
|
}
|
|
46
56
|
|
|
@@ -56,7 +66,10 @@ function sourceCoverage(discovery, cfg) {
|
|
|
56
66
|
coverageRow("gemini-cli", "Gemini CLI", discovery.geminiCli, configured),
|
|
57
67
|
coverageRow("antigravity", "Antigravity", discovery.antigravity, configured),
|
|
58
68
|
coverageRow("devin-cli", "Devin CLI", discovery.devinCli, configured),
|
|
59
|
-
coverageRow("cursor", "Cursor", discovery.cursor, configured)
|
|
69
|
+
coverageRow("cursor", "Cursor", discovery.cursor, configured),
|
|
70
|
+
coverageRow("cline", "Cline", discovery.cline, configured),
|
|
71
|
+
coverageRow("opencode", "OpenCode", discovery.opencode, configured),
|
|
72
|
+
coverageRow("aider", "Aider", discovery.aider, configured)
|
|
60
73
|
];
|
|
61
74
|
return rows.map((row) => ({
|
|
62
75
|
...row,
|
|
@@ -76,6 +89,139 @@ function coverageRow(source, label, result = {}, configured) {
|
|
|
76
89
|
};
|
|
77
90
|
}
|
|
78
91
|
|
|
92
|
+
function parserVersionHealth(env = process.env) {
|
|
93
|
+
const bySource = new Map();
|
|
94
|
+
for (const session of listSessions(env)) {
|
|
95
|
+
for (const [sourceType, archivedVersion] of parserVersionEntries(session)) {
|
|
96
|
+
const source = canonicalSourceType(sourceType);
|
|
97
|
+
const currentVersion = parserVersionForSource(source);
|
|
98
|
+
if (!currentVersion || !archivedVersion || compareVersions(archivedVersion, currentVersion) >= 0) continue;
|
|
99
|
+
const existing = bySource.get(source) || {
|
|
100
|
+
sourceType: source,
|
|
101
|
+
archivedVersion,
|
|
102
|
+
currentVersion,
|
|
103
|
+
sessions: 0,
|
|
104
|
+
command: parserUpdateCommand(source)
|
|
105
|
+
};
|
|
106
|
+
if (compareVersions(archivedVersion, existing.archivedVersion) < 0) existing.archivedVersion = archivedVersion;
|
|
107
|
+
existing.sessions++;
|
|
108
|
+
bySource.set(source, existing);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
outdated: [...bySource.values()].sort((a, b) => a.sourceType.localeCompare(b.sourceType))
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function parserVersionEntries(session = {}) {
|
|
117
|
+
const entries = [];
|
|
118
|
+
if (session.parserVersions && typeof session.parserVersions === "object") {
|
|
119
|
+
entries.push(...Object.entries(session.parserVersions));
|
|
120
|
+
}
|
|
121
|
+
if (session.sourceType && session.parserVersion) {
|
|
122
|
+
entries.push([session.sourceType, session.parserVersion]);
|
|
123
|
+
}
|
|
124
|
+
return entries.filter(([sourceType, version]) => sourceType && version);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function parserVersionSummary(parsers) {
|
|
128
|
+
const outdated = parsers.outdated || [];
|
|
129
|
+
if (!outdated.length) return "all archived sessions match current parser versions";
|
|
130
|
+
const sessions = outdated.reduce((sum, item) => sum + item.sessions, 0);
|
|
131
|
+
return `${outdated.length} parser${outdated.length === 1 ? "" : "s"} behind current code across ${sessions} session${sessions === 1 ? "" : "s"}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function parserVersionRemediation(parsers) {
|
|
135
|
+
const commands = (parsers.outdated || []).map((item) => item.command).filter(Boolean);
|
|
136
|
+
if (!commands.length) return "Re-run the affected importer, usually `agentlog import --source all --since all`.";
|
|
137
|
+
const unique = [...new Set(commands)];
|
|
138
|
+
if (unique.length === 1) return `Run \`${unique[0]}\` to refresh affected archives.`;
|
|
139
|
+
return "Run the affected import commands listed in Recommendations.";
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function buildRecommendations(checks, coverage, parsers) {
|
|
143
|
+
const recommendations = [];
|
|
144
|
+
for (const check of checks) {
|
|
145
|
+
if (check.status !== "warn" || !check.remediation) continue;
|
|
146
|
+
if (check.name === "parser versions") continue;
|
|
147
|
+
recommendations.push(recommendationFromRemediation(check.name, check.remediation));
|
|
148
|
+
}
|
|
149
|
+
for (const row of coverage) {
|
|
150
|
+
if (row.status !== "found but disabled") continue;
|
|
151
|
+
recommendations.push({
|
|
152
|
+
label: row.label,
|
|
153
|
+
detail: `${row.sessions} importable session${row.sessions === 1 ? "" : "s"} found, but ${row.source} is not in config.json imports.sources.`,
|
|
154
|
+
commands: [`agentlog config sources add ${row.source}`, `agentlog import --source ${row.source} --since all`]
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
for (const item of parsers.outdated || []) {
|
|
158
|
+
recommendations.push({
|
|
159
|
+
label: `Parser ${item.sourceType}`,
|
|
160
|
+
detail: `${item.sessions} archived session${item.sessions === 1 ? "" : "s"} use parser ${item.archivedVersion}; current parser is ${item.currentVersion}.`,
|
|
161
|
+
commands: item.command ? [item.command] : ["agentlog import --source all --since all"]
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
return dedupeRecommendations(recommendations);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function recommendationFromRemediation(label, remediation) {
|
|
168
|
+
const commands = [...String(remediation).matchAll(/`([^`]+)`/g)].map((match) => match[1]).filter(Boolean);
|
|
169
|
+
return {
|
|
170
|
+
label,
|
|
171
|
+
detail: String(remediation).replace(/`/g, ""),
|
|
172
|
+
commands
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function dedupeRecommendations(recommendations) {
|
|
177
|
+
const seen = new Set();
|
|
178
|
+
const result = [];
|
|
179
|
+
for (const item of recommendations) {
|
|
180
|
+
const key = `${item.label}\n${item.detail}\n${(item.commands || []).join("\n")}`;
|
|
181
|
+
if (seen.has(key)) continue;
|
|
182
|
+
seen.add(key);
|
|
183
|
+
result.push(item);
|
|
184
|
+
}
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function parserUpdateCommand(sourceType) {
|
|
189
|
+
const source = {
|
|
190
|
+
"codex-cli-history": "codex-cli",
|
|
191
|
+
"codex-desktop-history": "codex-desktop",
|
|
192
|
+
"cli-history": "claude",
|
|
193
|
+
"claude-sdk-history": "claude-sdk",
|
|
194
|
+
"claude-code-desktop-metadata": "claude-code-desktop",
|
|
195
|
+
"claude-workspace-desktop": "claude-workspace",
|
|
196
|
+
"cursor-workspace-sqlite": "cursor",
|
|
197
|
+
"cursor-global-sqlite": "cursor",
|
|
198
|
+
"cursor-raw-sqlite-salvage": "cursor",
|
|
199
|
+
"cursor-agent-transcripts": "cursor",
|
|
200
|
+
"devin-cli-history": "devin-cli",
|
|
201
|
+
"gemini-cli-history": "gemini-cli",
|
|
202
|
+
"cline-task-history": "cline",
|
|
203
|
+
"opencode-history": "opencode",
|
|
204
|
+
"aider-chat-history": "aider",
|
|
205
|
+
"antigravity-history": "antigravity"
|
|
206
|
+
}[canonicalSourceType(sourceType)];
|
|
207
|
+
return source ? `agentlog import --source ${source} --since all` : "";
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function compareVersions(a, b) {
|
|
211
|
+
const left = String(a || "0").split(".").map((part) => Number(part) || 0);
|
|
212
|
+
const right = String(b || "0").split(".").map((part) => Number(part) || 0);
|
|
213
|
+
if (left.length !== right.length) {
|
|
214
|
+
if (left.length === 3 && right.length === 4) return -1;
|
|
215
|
+
if (left.length === 4 && right.length === 3) return 1;
|
|
216
|
+
}
|
|
217
|
+
const length = Math.max(left.length, right.length);
|
|
218
|
+
for (let index = 0; index < length; index++) {
|
|
219
|
+
const diff = (left[index] || 0) - (right[index] || 0);
|
|
220
|
+
if (diff !== 0) return diff;
|
|
221
|
+
}
|
|
222
|
+
return 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
79
225
|
function add(checks, name, ok, detail, remediation) {
|
|
80
226
|
checks.push({
|
|
81
227
|
name,
|
|
@@ -97,6 +243,17 @@ function remoteTargetLabel(cfg) {
|
|
|
97
243
|
return `${remote.type || cfg.storage?.backend || "remote"} ${remote.endpoint || remote.url}${remote.bucket ? `/${remote.bucket}` : ""}${device}`;
|
|
98
244
|
}
|
|
99
245
|
|
|
246
|
+
function reportProgress(options, provider, message, current, total) {
|
|
247
|
+
if (typeof options.onProgress !== "function") return;
|
|
248
|
+
options.onProgress({
|
|
249
|
+
kind: "discovery",
|
|
250
|
+
provider,
|
|
251
|
+
current,
|
|
252
|
+
total,
|
|
253
|
+
message
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
100
257
|
module.exports = {
|
|
101
258
|
runDoctor
|
|
102
259
|
};
|