cogpit-memory 0.1.3 → 0.1.4

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 CHANGED
@@ -40,14 +40,14 @@ cogpit-memory search "authentication"
40
40
  cogpit-memory sessions # Recent sessions (last 7 days)
41
41
  cogpit-memory sessions --cwd /path/to/project # Filter by project
42
42
  cogpit-memory sessions --current --cwd /path/to/project # Most recent for a project
43
- cogpit-memory sessions --max-age 30d --limit 50 # Custom window
43
+ cogpit-memory sessions --max-age 90d --limit 50 # Custom window
44
44
  ```
45
45
 
46
46
  | Flag | Default | Description |
47
47
  |------|---------|-------------|
48
48
  | `--cwd` | all | Filter by working directory |
49
49
  | `--limit` | `20` | Max results |
50
- | `--max-age` | `7d` | Time window (`7d`, `12h`, `30d`) |
50
+ | `--max-age` | `7d` | Time window — any duration (`7d`, `12h`, `90d`, `365d`) |
51
51
  | `--current` | — | Most recent session for `--cwd` |
52
52
 
53
53
  ### `context` — Layered session drill-down
@@ -77,11 +77,11 @@ cogpit-memory search "AuthProvider" --case-sensitive # Case-sensitive
77
77
  | Flag | Default | Description |
78
78
  |------|---------|-------------|
79
79
  | `--session` | all | Scope to single session |
80
- | `--max-age` | `5d` | Time window |
80
+ | `--max-age` | `5d` | Time window — any duration (`5d`, `30d`, `365d`) |
81
81
  | `--limit` | `20` | Max returned hits |
82
82
  | `--case-sensitive` | `false` | Case sensitivity |
83
83
 
84
- Each hit includes a `location` string (e.g. `turn/3/assistantMessage`, `agent/a7f3bc2/toolCall/tc1/result`) that maps directly to L2/L3 drill-down commands.
84
+ Each result includes the `cwd` (working directory where the session ran) and an array of hits. Each hit includes a `location` string (e.g. `turn/3/assistantMessage`, `agent/a7f3bc2/toolCall/tc1/result`) that maps directly to L2/L3 drill-down commands.
85
85
 
86
86
  ### `index` — Manage the FTS5 search index
87
87
 
@@ -151,9 +151,11 @@ Browse at [skills.sh](https://skills.sh).
151
151
 
152
152
  ### Install via cogpit-memory CLI
153
153
 
154
- Installs into a single project's `.claude/skills/`:
155
-
156
154
  ```bash
155
+ # Install globally (all projects)
156
+ npx cogpit-memory install-skill -g
157
+
158
+ # Install into a single project's .claude/skills/
157
159
  npx cogpit-memory install-skill
158
160
 
159
161
  # Or specify a project directory
package/dist/cli.js CHANGED
@@ -812,7 +812,7 @@ var SearchIndex = class {
812
812
  const caseSensitive = opts?.caseSensitive ?? false;
813
813
  const ftsQuery = `"${query.replace(/"/g, '""')}"`;
814
814
  let sql = `
815
- SELECT sc.session_id, sc.location,
815
+ SELECT sc.session_id, sc.source_file, sc.location,
816
816
  snippet(search_content, 3, '', '', '...', 120) as snippet
817
817
  FROM search_content sc
818
818
  `;
@@ -835,6 +835,7 @@ var SearchIndex = class {
835
835
  const rows = this.db.prepare(sql).all(...params);
836
836
  let hits = rows.map((row) => ({
837
837
  sessionId: row.session_id,
838
+ filePath: row.source_file,
838
839
  location: row.location,
839
840
  snippet: row.snippet,
840
841
  matchCount: 1
@@ -1213,20 +1214,21 @@ async function searchSessions(query, opts, searchIndex) {
1213
1214
  });
1214
1215
  const grouped = /* @__PURE__ */ new Map();
1215
1216
  for (const hit of hits) {
1216
- let sessionHits = grouped.get(hit.sessionId);
1217
- if (!sessionHits) {
1218
- sessionHits = [];
1219
- grouped.set(hit.sessionId, sessionHits);
1217
+ let entry = grouped.get(hit.sessionId);
1218
+ if (!entry) {
1219
+ entry = { filePath: hit.filePath, hits: [] };
1220
+ grouped.set(hit.sessionId, entry);
1220
1221
  }
1221
- sessionHits.push({
1222
+ entry.hits.push({
1222
1223
  location: hit.location,
1223
1224
  snippet: hit.snippet,
1224
1225
  matchCount: hit.matchCount
1225
1226
  });
1226
1227
  }
1227
1228
  const results = [];
1228
- for (const [sid, sessionHits] of grouped) {
1229
- results.push({ sessionId: sid, hits: sessionHits });
1229
+ for (const [sid, entry] of grouped) {
1230
+ const cwd = await cwdFromFilePath(entry.filePath);
1231
+ results.push({ sessionId: sid, cwd, hits: entry.hits });
1230
1232
  }
1231
1233
  let totalHits = hits.length;
1232
1234
  let sessionsSearched = grouped.size;
@@ -1252,6 +1254,37 @@ async function searchSessions(query, opts, searchIndex) {
1252
1254
  }
1253
1255
  return rawScanSearch(query, opts.sessionId ?? null, maxAgeMs, limit, caseSensitive, depth);
1254
1256
  }
1257
+ var CWD_READ_BYTES = 4096;
1258
+ var cwdCache = /* @__PURE__ */ new Map();
1259
+ async function cwdFromFilePath(filePath) {
1260
+ const cached = cwdCache.get(filePath);
1261
+ if (cached !== void 0) return cached;
1262
+ try {
1263
+ const fh = await (0, import_promises2.open)(filePath, "r");
1264
+ try {
1265
+ const buf = Buffer.alloc(CWD_READ_BYTES);
1266
+ const { bytesRead } = await fh.read(buf, 0, CWD_READ_BYTES, 0);
1267
+ const head = buf.subarray(0, bytesRead).toString("utf-8");
1268
+ const lines = head.split("\n", 10);
1269
+ for (const line of lines) {
1270
+ if (!line) continue;
1271
+ try {
1272
+ const obj = JSON.parse(line);
1273
+ if (obj.cwd) {
1274
+ cwdCache.set(filePath, obj.cwd);
1275
+ return obj.cwd;
1276
+ }
1277
+ } catch {
1278
+ }
1279
+ }
1280
+ } finally {
1281
+ await fh.close();
1282
+ }
1283
+ } catch {
1284
+ }
1285
+ cwdCache.set(filePath, "");
1286
+ return "";
1287
+ }
1255
1288
  var SNIPPET_WINDOW = 150;
1256
1289
  function generateSnippet(text, matchIdx, queryLen) {
1257
1290
  if (matchIdx === -1) return text.slice(0, SNIPPET_WINDOW);
@@ -1450,6 +1483,7 @@ async function rawScanSearch(query, sessionId, maxAgeMs, limit, caseSensitive, d
1450
1483
  totalHits += allHits.length;
1451
1484
  const sessionResult = {
1452
1485
  sessionId: session.sessionId || (0, import_node_path4.basename)(file.path, ".jsonl"),
1486
+ cwd: session.cwd || "",
1453
1487
  hits: []
1454
1488
  };
1455
1489
  for (const hit of allHits) {
@@ -2093,9 +2127,9 @@ function findSkillContent() {
2093
2127
  }
2094
2128
  throw new Error("Could not find SKILL.md \u2014 try reinstalling cogpit-memory");
2095
2129
  }
2096
- function installSkill(cwd) {
2097
- const root = cwd ?? process.cwd();
2098
- const skillDir = (0, import_node_path8.join)(root, ".claude", "skills", "cogpit-memory");
2130
+ function installSkill(cwd, global) {
2131
+ const root = global ? (0, import_node_path8.join)(process.env.HOME ?? process.env.USERPROFILE ?? "~", ".claude") : (0, import_node_path8.join)(cwd ?? process.cwd(), ".claude");
2132
+ const skillDir = (0, import_node_path8.join)(root, "skills", "cogpit-memory");
2099
2133
  (0, import_node_fs4.mkdirSync)(skillDir, { recursive: true });
2100
2134
  const content = findSkillContent();
2101
2135
  const dest = (0, import_node_path8.join)(skillDir, "SKILL.md");
@@ -2171,6 +2205,10 @@ function parseArgs(argv) {
2171
2205
  case "--cwd":
2172
2206
  args.cwd = argv[++i];
2173
2207
  break;
2208
+ case "-g":
2209
+ case "--global":
2210
+ args.global = true;
2211
+ break;
2174
2212
  }
2175
2213
  }
2176
2214
  break;
@@ -2233,7 +2271,7 @@ async function main() {
2233
2271
  }
2234
2272
  break;
2235
2273
  case "install-skill":
2236
- result = installSkill(cmd.args.cwd);
2274
+ result = installSkill(cmd.args.cwd, cmd.args.global);
2237
2275
  break;
2238
2276
  default:
2239
2277
  console.error(JSON.stringify({ error: `Unknown command: ${cmd.command}` }));
@@ -2265,7 +2303,9 @@ Commands:
2265
2303
  index stats Show index stats
2266
2304
  index rebuild Rebuild full index
2267
2305
 
2268
- install-skill [--cwd path] Install Claude Code skill to .claude/skills/
2306
+ install-skill [options] Install Claude Code skill
2307
+ --cwd <path> Target project directory
2308
+ -g, --global Install to ~/.claude/skills/ (all projects)
2269
2309
  `);
2270
2310
  }
2271
2311
  var isBunCompiled = false;
package/dist/index.js CHANGED
@@ -898,7 +898,7 @@ var SearchIndex = class {
898
898
  const caseSensitive = opts?.caseSensitive ?? false;
899
899
  const ftsQuery = `"${query.replace(/"/g, '""')}"`;
900
900
  let sql = `
901
- SELECT sc.session_id, sc.location,
901
+ SELECT sc.session_id, sc.source_file, sc.location,
902
902
  snippet(search_content, 3, '', '', '...', 120) as snippet
903
903
  FROM search_content sc
904
904
  `;
@@ -921,6 +921,7 @@ var SearchIndex = class {
921
921
  const rows = this.db.prepare(sql).all(...params);
922
922
  let hits = rows.map((row) => ({
923
923
  sessionId: row.session_id,
924
+ filePath: row.source_file,
924
925
  location: row.location,
925
926
  snippet: row.snippet,
926
927
  matchCount: 1
@@ -1304,20 +1305,21 @@ async function searchSessions(query, opts, searchIndex) {
1304
1305
  });
1305
1306
  const grouped = /* @__PURE__ */ new Map();
1306
1307
  for (const hit of hits) {
1307
- let sessionHits = grouped.get(hit.sessionId);
1308
- if (!sessionHits) {
1309
- sessionHits = [];
1310
- grouped.set(hit.sessionId, sessionHits);
1308
+ let entry = grouped.get(hit.sessionId);
1309
+ if (!entry) {
1310
+ entry = { filePath: hit.filePath, hits: [] };
1311
+ grouped.set(hit.sessionId, entry);
1311
1312
  }
1312
- sessionHits.push({
1313
+ entry.hits.push({
1313
1314
  location: hit.location,
1314
1315
  snippet: hit.snippet,
1315
1316
  matchCount: hit.matchCount
1316
1317
  });
1317
1318
  }
1318
1319
  const results = [];
1319
- for (const [sid, sessionHits] of grouped) {
1320
- results.push({ sessionId: sid, hits: sessionHits });
1320
+ for (const [sid, entry] of grouped) {
1321
+ const cwd = await cwdFromFilePath(entry.filePath);
1322
+ results.push({ sessionId: sid, cwd, hits: entry.hits });
1321
1323
  }
1322
1324
  let totalHits = hits.length;
1323
1325
  let sessionsSearched = grouped.size;
@@ -1343,6 +1345,37 @@ async function searchSessions(query, opts, searchIndex) {
1343
1345
  }
1344
1346
  return rawScanSearch(query, opts.sessionId ?? null, maxAgeMs, limit, caseSensitive, depth);
1345
1347
  }
1348
+ var CWD_READ_BYTES = 4096;
1349
+ var cwdCache = /* @__PURE__ */ new Map();
1350
+ async function cwdFromFilePath(filePath) {
1351
+ const cached = cwdCache.get(filePath);
1352
+ if (cached !== void 0) return cached;
1353
+ try {
1354
+ const fh = await (0, import_promises2.open)(filePath, "r");
1355
+ try {
1356
+ const buf = Buffer.alloc(CWD_READ_BYTES);
1357
+ const { bytesRead } = await fh.read(buf, 0, CWD_READ_BYTES, 0);
1358
+ const head = buf.subarray(0, bytesRead).toString("utf-8");
1359
+ const lines = head.split("\n", 10);
1360
+ for (const line of lines) {
1361
+ if (!line) continue;
1362
+ try {
1363
+ const obj = JSON.parse(line);
1364
+ if (obj.cwd) {
1365
+ cwdCache.set(filePath, obj.cwd);
1366
+ return obj.cwd;
1367
+ }
1368
+ } catch {
1369
+ }
1370
+ }
1371
+ } finally {
1372
+ await fh.close();
1373
+ }
1374
+ } catch {
1375
+ }
1376
+ cwdCache.set(filePath, "");
1377
+ return "";
1378
+ }
1346
1379
  var SNIPPET_WINDOW = 150;
1347
1380
  function generateSnippet(text, matchIdx, queryLen) {
1348
1381
  if (matchIdx === -1) return text.slice(0, SNIPPET_WINDOW);
@@ -1541,6 +1574,7 @@ async function rawScanSearch(query, sessionId, maxAgeMs, limit, caseSensitive, d
1541
1574
  totalHits += allHits.length;
1542
1575
  const sessionResult = {
1543
1576
  sessionId: session.sessionId || (0, import_node_path4.basename)(file.path, ".jsonl"),
1577
+ cwd: session.cwd || "",
1544
1578
  hits: []
1545
1579
  };
1546
1580
  for (const hit of allHits) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cogpit-memory",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "CLI tool for Claude Code session introspection — search, browse, and drill into past sessions",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./src/index.ts",
package/skill/SKILL.md CHANGED
@@ -262,6 +262,7 @@ bunx cogpit-memory search "AuthProvider" --case-sensitive
262
262
  "results": [
263
263
  {
264
264
  "sessionId": "abc-123",
265
+ "cwd": "/path/to/project",
265
266
  "hits": [
266
267
  {
267
268
  "location": "turn/3/userMessage",