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/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
- add(checks, "rg", commandExists("rg"), "ripgrep search", "`agentlog history` will fall back to a slower JS scan.");
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 autostart enable` to start at login.");
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 recall add-to codex` or `agentlog init`.");
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
- const discovery = discoverCliHistory(env);
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
  };