0rrery 0.1.0 → 0.1.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/index.js CHANGED
@@ -1,4 +1,6 @@
1
- #!/usr/bin/env bun
1
+ #!/bin/sh
2
+ ':' //; command -v bun >/dev/null 2>&1 || { echo "0rrery runs on Bun (>= 1.1), and bun was not found on your PATH." >&2; echo "Install it from https://bun.sh then re-run this command." >&2; exit 1; }
3
+ ':' //; exec bun "$0" "$@"
2
4
  // @bun
3
5
  var __defProp = Object.defineProperty;
4
6
  var __returnValue = (v) => v;
@@ -16,9 +18,9 @@ var __export = (target, all) => {
16
18
  };
17
19
 
18
20
  // packages/cli/src/index.ts
19
- import { existsSync as existsSync6 } from "fs";
21
+ import { existsSync as existsSync7, openSync as openSync2, readSync as readSync2, closeSync as closeSync2 } from "fs";
20
22
  import { homedir as homedir3 } from "os";
21
- import { join as join8, resolve as resolve2 } from "path";
23
+ import { join as join9, resolve as resolve2 } from "path";
22
24
 
23
25
  // packages/server/src/server.ts
24
26
  import { appendFileSync, mkdirSync } from "fs";
@@ -4009,7 +4011,7 @@ var ts = exports_external.number().int().nonnegative();
4009
4011
  var SessionStartSchema = exports_external.object({
4010
4012
  op: exports_external.literal("session.start"),
4011
4013
  sessionId: exports_external.string().min(1),
4012
- source: exports_external.enum(["claude-code", "api"]),
4014
+ source: exports_external.enum(["claude-code", "api", "codex"]),
4013
4015
  project: exports_external.string().optional(),
4014
4016
  cwd: exports_external.string().optional(),
4015
4017
  gitBranch: exports_external.string().optional(),
@@ -4291,7 +4293,15 @@ function spendSeries(db, f) {
4291
4293
  FROM spans sp JOIN sessions se ON se.id = sp.session_id
4292
4294
  WHERE ${where}
4293
4295
  GROUP BY day, model, project ORDER BY day, model`).all(...params);
4294
- return rows.map((r) => ({ ...r, est_cost: estCost(r.model, r.tokens_in, r.tokens_out) }));
4296
+ return rows.map((r) => ({
4297
+ day: r.day,
4298
+ model: r.model,
4299
+ project: r.project,
4300
+ tokens_in: r.tokens_in,
4301
+ tokens_out: r.tokens_out,
4302
+ calls: r.calls,
4303
+ est_cost: estCost(r.model, r.tokens_in, r.tokens_out)
4304
+ }));
4295
4305
  }
4296
4306
  function toolHealth(db, f) {
4297
4307
  const { where, params } = spanWhere(f, "sp.kind IN ('tool', 'mcp')");
@@ -4350,10 +4360,11 @@ function searchSessions(db, f, opts) {
4350
4360
  const conds = ["1 = 1"];
4351
4361
  const params = [];
4352
4362
  if (f.q) {
4353
- conds.push(`(project LIKE '%' || ? || '%' OR EXISTS(
4363
+ const q = f.q.replaceAll(/[%_\\]/g, (m) => "\\" + m);
4364
+ conds.push(`(project LIKE '%' || ? || '%' ESCAPE '\\' OR EXISTS(
4354
4365
  SELECT 1 FROM events ev WHERE ev.session_id = sessions.id
4355
- AND ev.type = 'message.user' AND json_extract(ev.attrs, '$.preview') LIKE '%' || ? || '%'))`);
4356
- params.push(f.q, f.q);
4366
+ AND ev.type = 'message.user' AND json_extract(ev.attrs, '$.preview') LIKE '%' || ? || '%' ESCAPE '\\'))`);
4367
+ params.push(q, q);
4357
4368
  }
4358
4369
  if (f.project) {
4359
4370
  conds.push("project = ?");
@@ -4498,7 +4509,7 @@ function sessionSummary(db, id) {
4498
4509
  SUM(COALESCE(json_extract(attrs, '$.input_tokens'), 0)) tin,
4499
4510
  SUM(COALESCE(json_extract(attrs, '$.output_tokens'), 0)) tout
4500
4511
  FROM spans WHERE session_id = ? AND kind = 'llm'
4501
- GROUP BY name ORDER BY calls DESC, model`).all(id);
4512
+ GROUP BY name ORDER BY calls DESC, model LIMIT 20`).all(id);
4502
4513
  const top_tools = db.query(`SELECT name, kind, COUNT(*) calls, SUM(status = 'error') errors
4503
4514
  FROM spans WHERE session_id = ? AND kind IN ('tool', 'mcp')
4504
4515
  GROUP BY name, kind ORDER BY calls DESC LIMIT 10`).all(id);
@@ -4625,7 +4636,7 @@ class LiveBus {
4625
4636
  // packages/server/src/server.ts
4626
4637
  var json = (data, status = 200) => new Response(JSON.stringify(data), { status, headers: { "Content-Type": "application/json" } });
4627
4638
  var numParam = (raw) => {
4628
- if (raw === null)
4639
+ if (raw === null || raw === "")
4629
4640
  return;
4630
4641
  const n = Number(raw);
4631
4642
  return Number.isInteger(n) && n >= 0 ? n : undefined;
@@ -4685,9 +4696,9 @@ function startServer(config) {
4685
4696
  const bad = (name) => json({ error: `invalid ${name}` }, 400);
4686
4697
  const rawFrom = url.searchParams.get("from"), rawTo = url.searchParams.get("to");
4687
4698
  const from = numParam(rawFrom), to = numParam(rawTo);
4688
- if (rawFrom !== null && from === undefined)
4699
+ if (rawFrom !== null && rawFrom !== "" && from === undefined)
4689
4700
  return bad("from");
4690
- if (rawTo !== null && to === undefined)
4701
+ if (rawTo !== null && rawTo !== "" && to === undefined)
4691
4702
  return bad("to");
4692
4703
  const f = { project: url.searchParams.get("project") ?? undefined, from, to };
4693
4704
  switch (insightsMatch[1]) {
@@ -4708,9 +4719,9 @@ function startServer(config) {
4708
4719
  if (path === "/api/sessions" && req.method === "GET") {
4709
4720
  const rawFrom = url.searchParams.get("from"), rawTo = url.searchParams.get("to");
4710
4721
  const from = numParam(rawFrom), to = numParam(rawTo);
4711
- if (rawFrom !== null && from === undefined)
4722
+ if (rawFrom !== null && rawFrom !== "" && from === undefined)
4712
4723
  return json({ error: "invalid from" }, 400);
4713
- if (rawTo !== null && to === undefined)
4724
+ if (rawTo !== null && rawTo !== "" && to === undefined)
4714
4725
  return json({ error: "invalid to" }, 400);
4715
4726
  const q = url.searchParams.get("q") ?? undefined;
4716
4727
  const project = url.searchParams.get("project") ?? undefined;
@@ -5008,7 +5019,8 @@ function parseTranscriptLine(raw, state) {
5008
5019
  // packages/claude-code/src/importer.ts
5009
5020
  import { openSync, readSync, fstatSync, closeSync, readdirSync } from "fs";
5010
5021
  import { join as join3, dirname, basename } from "path";
5011
- async function importTranscript(path, url, fromByte = 0, state = newTranscriptState(), finalize = false) {
5022
+ var claudeParser = { parse: parseTranscriptLine };
5023
+ async function importTranscript(path, url, fromByte = 0, state = newTranscriptState(), finalize = false, parser = claudeParser) {
5012
5024
  const fd = openSync(path, "r");
5013
5025
  let text;
5014
5026
  try {
@@ -5027,19 +5039,24 @@ async function importTranscript(path, url, fromByte = 0, state = newTranscriptSt
5027
5039
  return { ops: 0, emitted: true, bytesRead: fromByte };
5028
5040
  const complete = text.slice(0, lastNewline);
5029
5041
  const consumedBytes = Buffer.byteLength(text.slice(0, lastNewline + 1));
5030
- const snapshot = { ...state, agentToolUseIds: new Set(state.agentToolUseIds) };
5042
+ const snapshot = { ...state };
5043
+ for (const k of Object.keys(snapshot))
5044
+ if (snapshot[k] instanceof Set)
5045
+ snapshot[k] = new Set(snapshot[k]);
5031
5046
  const ops = complete.split(`
5032
- `).filter(Boolean).flatMap((l) => parseTranscriptLine(l, state));
5047
+ `).filter(Boolean).flatMap((l) => parser.parse(l, state));
5033
5048
  if (ops.length > 0) {
5034
5049
  const maxTs = ops.reduce((max, o) => o.ts > max ? o.ts : max, 0);
5035
- if (state.agentId) {
5050
+ if ("agentId" in state && state.agentId && ops.length) {
5036
5051
  ops.push({ op: "span.end", id: `agent:${state.agentId}`, ts: maxTs, status: "ok" });
5037
5052
  }
5038
- if (finalize && !state.agentId) {
5053
+ if (finalize && !(("agentId" in state) && state.agentId)) {
5039
5054
  const sessionId = ops.find((o) => ("sessionId" in o))?.sessionId;
5040
5055
  if (sessionId)
5041
5056
  ops.push({ op: "session.end", sessionId, ts: maxTs });
5042
5057
  }
5058
+ if (finalize && parser.finalize)
5059
+ ops.push(...parser.finalize(state, maxTs));
5043
5060
  }
5044
5061
  const emitted = await emitOps(url, ops, 5000);
5045
5062
  if (!emitted) {
@@ -5049,23 +5066,27 @@ async function importTranscript(path, url, fromByte = 0, state = newTranscriptSt
5049
5066
  return { ops: ops.length, emitted, bytesRead: fromByte + consumedBytes };
5050
5067
  }
5051
5068
  async function importSession(path, url, opts = {}) {
5069
+ const newState = opts.newState ?? newTranscriptState;
5070
+ const parser = opts.parser ?? claudeParser;
5052
5071
  let files = 0, ops = 0, emitted = true;
5053
- const main = await importTranscript(path, url, 0, newTranscriptState(), opts.finalize ?? false);
5072
+ const main = await importTranscript(path, url, 0, newState(), opts.finalize ?? false, parser);
5054
5073
  files++;
5055
5074
  ops += main.ops;
5056
5075
  emitted = emitted && main.emitted;
5057
5076
  if (!main.emitted)
5058
5077
  return { files, ops, emitted: false };
5059
- const subDir = join3(dirname(path), basename(path, ".jsonl"), "subagents");
5060
- let subs = [];
5061
- try {
5062
- subs = readdirSync(subDir).filter((f) => f.endsWith(".jsonl"));
5063
- } catch {}
5064
- for (const f of subs) {
5065
- const r = await importTranscript(join3(subDir, f), url, 0, newTranscriptState());
5066
- files++;
5067
- ops += r.ops;
5068
- emitted = emitted && r.emitted;
5078
+ if (opts.parser === undefined) {
5079
+ const subDir = join3(dirname(path), basename(path, ".jsonl"), "subagents");
5080
+ let subs = [];
5081
+ try {
5082
+ subs = readdirSync(subDir).filter((f) => f.endsWith(".jsonl"));
5083
+ } catch {}
5084
+ for (const f of subs) {
5085
+ const r = await importTranscript(join3(subDir, f), url, 0, newTranscriptState());
5086
+ files++;
5087
+ ops += r.ops;
5088
+ emitted = emitted && r.emitted;
5089
+ }
5069
5090
  }
5070
5091
  return { files, ops, emitted };
5071
5092
  }
@@ -5090,7 +5111,7 @@ function reviveState(json2) {
5090
5111
  agentToolUseIds: Array.isArray(j.agentToolUseIds) ? new Set(j.agentToolUseIds.filter((x) => typeof x === "string")) : fresh.agentToolUseIds
5091
5112
  };
5092
5113
  }
5093
- function loadOffsets(path) {
5114
+ function loadOffsets(path, revive) {
5094
5115
  const out = new Map;
5095
5116
  let raw;
5096
5117
  try {
@@ -5106,7 +5127,7 @@ function loadOffsets(path) {
5106
5127
  if (!existsSync2(file))
5107
5128
  continue;
5108
5129
  const offset = Number.isInteger(entry?.offset) && entry.offset >= 0 ? entry.offset : 0;
5109
- out.set(file, { offset, state: reviveState(entry?.state) });
5130
+ out.set(file, { offset, state: revive(entry?.state) });
5110
5131
  }
5111
5132
  } catch (e) {
5112
5133
  console.error("0rrery: tailer offsets snapshot corrupt, starting fresh", e);
@@ -5118,7 +5139,11 @@ function saveOffsets(path, files) {
5118
5139
  try {
5119
5140
  const filesJson = {};
5120
5141
  for (const [file, { offset, state }] of files) {
5121
- filesJson[file] = { offset, state: { ...state, agentToolUseIds: [...state.agentToolUseIds] } };
5142
+ const serialized = { ...state };
5143
+ for (const k of Object.keys(serialized))
5144
+ if (serialized[k] instanceof Set)
5145
+ serialized[k] = [...serialized[k]];
5146
+ filesJson[file] = { offset, state: serialized };
5122
5147
  }
5123
5148
  writeFileSync(path + ".tmp", JSON.stringify({ version: VERSION, files: filesJson }));
5124
5149
  renameSync(path + ".tmp", path);
@@ -5129,7 +5154,7 @@ function saveOffsets(path, files) {
5129
5154
 
5130
5155
  // packages/claude-code/src/tailer.ts
5131
5156
  function startTailer(projectsDir, url, pollMs = 2000, offsetsPath) {
5132
- const files = offsetsPath ? loadOffsets(offsetsPath) : new Map;
5157
+ const files = offsetsPath ? loadOffsets(offsetsPath, reviveState) : new Map;
5133
5158
  let stopped = false;
5134
5159
  let dirty = false;
5135
5160
  async function scanFile(path) {
@@ -5200,14 +5225,246 @@ function startTailer(projectsDir, url, pollMs = 2000, offsetsPath) {
5200
5225
  stopped = true;
5201
5226
  } };
5202
5227
  }
5228
+ // packages/codex/src/codex.ts
5229
+ function newCodexState() {
5230
+ return { sessionId: null, project: null, model: null, openTurnId: null, turnIn: 0, turnOut: 0, threadId: null };
5231
+ }
5232
+ function reviveCodexState(json2) {
5233
+ const fresh = newCodexState();
5234
+ if (typeof json2 !== "object" || json2 === null)
5235
+ return fresh;
5236
+ const j = json2;
5237
+ const str = (v) => typeof v === "string" ? v : null;
5238
+ const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
5239
+ if (j.sessionId !== null && typeof j.sessionId !== "string")
5240
+ return fresh;
5241
+ return {
5242
+ sessionId: str(j.sessionId),
5243
+ project: str(j.project),
5244
+ model: str(j.model),
5245
+ openTurnId: str(j.openTurnId),
5246
+ turnIn: num(j.turnIn),
5247
+ turnOut: num(j.turnOut),
5248
+ threadId: str(j.threadId)
5249
+ };
5250
+ }
5251
+ function idSalt(state) {
5252
+ return state.threadId !== null && state.threadId !== state.sessionId ? `:${state.threadId}` : "";
5253
+ }
5254
+ var preview = (s) => s.slice(0, 200);
5255
+ function messageText(content) {
5256
+ if (!Array.isArray(content))
5257
+ return "";
5258
+ return content.map((c) => c?.text ?? "").join("").trim();
5259
+ }
5260
+ function parseCodexLine(raw, state) {
5261
+ let line;
5262
+ try {
5263
+ line = JSON.parse(raw);
5264
+ } catch {
5265
+ return [];
5266
+ }
5267
+ if (typeof line !== "object" || line === null)
5268
+ return [];
5269
+ const parsed = Date.parse(line.timestamp);
5270
+ const ts2 = Number.isNaN(parsed) ? Date.now() : parsed;
5271
+ const p = line.payload;
5272
+ if (typeof p !== "object" || p === null)
5273
+ return [];
5274
+ if (line.type === "session_meta") {
5275
+ state.sessionId = typeof p.session_id === "string" ? p.session_id : typeof p.id === "string" ? p.id : null;
5276
+ if (!state.sessionId)
5277
+ return [];
5278
+ state.threadId = typeof p.id === "string" ? p.id : state.sessionId;
5279
+ state.project = typeof p.cwd === "string" ? p.cwd.split("/").pop() ?? null : null;
5280
+ state.model = typeof p.model_provider === "string" ? p.model_provider : null;
5281
+ return [{
5282
+ op: "session.start",
5283
+ sessionId: state.sessionId,
5284
+ source: "codex",
5285
+ ts: ts2,
5286
+ project: state.project ?? undefined,
5287
+ cwd: typeof p.cwd === "string" ? p.cwd : undefined,
5288
+ meta: { model_provider: p.model_provider, cli_version: p.cli_version, originator: p.originator }
5289
+ }];
5290
+ }
5291
+ const sid = state.sessionId;
5292
+ if (!sid)
5293
+ return [];
5294
+ const ops = [];
5295
+ const closeTurn = (endTs) => {
5296
+ if (!state.openTurnId)
5297
+ return;
5298
+ ops.push({ op: "span.end", id: `llm:${state.openTurnId}`, ts: endTs, status: "ok" });
5299
+ state.openTurnId = null;
5300
+ state.turnIn = 0;
5301
+ state.turnOut = 0;
5302
+ };
5303
+ if (line.type === "turn_context") {
5304
+ closeTurn(ts2);
5305
+ const turnId = typeof p.turn_id === "string" ? p.turn_id : null;
5306
+ if (typeof p.model === "string")
5307
+ state.model = p.model;
5308
+ if (turnId) {
5309
+ state.openTurnId = turnId;
5310
+ ops.push({
5311
+ op: "span.start",
5312
+ id: `llm:${turnId}`,
5313
+ sessionId: sid,
5314
+ parentId: null,
5315
+ kind: "llm",
5316
+ name: state.model ?? "(model)",
5317
+ ts: ts2,
5318
+ attrs: {}
5319
+ });
5320
+ ops.push({ op: "event", id: `evt:turn:${turnId}`, sessionId: sid, type: "turn.context", ts: ts2, attrs: {} });
5321
+ }
5322
+ return ops;
5323
+ }
5324
+ if (line.type === "event_msg") {
5325
+ if (p.type === "task_complete") {
5326
+ closeTurn(ts2);
5327
+ ops.push({ op: "event", id: `evt:stop:${sid}${idSalt(state)}:${ts2}`, sessionId: sid, type: "turn.stop", ts: ts2, attrs: {} });
5328
+ return ops;
5329
+ }
5330
+ if (p.type === "token_count" && p.info && typeof p.info === "object" && state.openTurnId) {
5331
+ const u = p.info.last_token_usage;
5332
+ if (u && typeof u === "object") {
5333
+ state.turnIn += u.input_tokens ?? 0;
5334
+ state.turnOut += u.output_tokens ?? 0;
5335
+ ops.push({
5336
+ op: "span.start",
5337
+ id: `llm:${state.openTurnId}`,
5338
+ sessionId: sid,
5339
+ parentId: null,
5340
+ kind: "llm",
5341
+ name: state.model ?? "(model)",
5342
+ ts: ts2,
5343
+ attrs: { input_tokens: state.turnIn, output_tokens: state.turnOut }
5344
+ });
5345
+ }
5346
+ return ops;
5347
+ }
5348
+ return [];
5349
+ }
5350
+ if (line.type === "response_item") {
5351
+ if (p.type === "function_call" && typeof p.call_id === "string") {
5352
+ let input = p.arguments;
5353
+ try {
5354
+ input = JSON.parse(p.arguments);
5355
+ } catch {}
5356
+ const name = typeof p.name === "string" ? p.name : "(tool)";
5357
+ return [{
5358
+ op: "span.start",
5359
+ id: `tool:${p.call_id}`,
5360
+ sessionId: sid,
5361
+ parentId: state.openTurnId ? `llm:${state.openTurnId}` : null,
5362
+ kind: isMcpTool(name) ? "mcp" : "tool",
5363
+ name,
5364
+ ts: ts2,
5365
+ attrs: { input }
5366
+ }];
5367
+ }
5368
+ if (p.type === "function_call_output" && typeof p.call_id === "string") {
5369
+ const out = typeof p.output === "string" ? p.output : "";
5370
+ const status = /exited with code [1-9]/.test(out) ? "error" : "ok";
5371
+ return [{ op: "span.end", id: `tool:${p.call_id}`, ts: ts2, status, attrs: {} }];
5372
+ }
5373
+ if (p.type === "web_search_call" && typeof p.id === "string") {
5374
+ return [
5375
+ {
5376
+ op: "span.start",
5377
+ id: `tool:${p.id}`,
5378
+ sessionId: sid,
5379
+ parentId: state.openTurnId ? `llm:${state.openTurnId}` : null,
5380
+ kind: "tool",
5381
+ name: "web_search",
5382
+ ts: ts2,
5383
+ attrs: { input: { query: p.action?.query ?? "" } }
5384
+ },
5385
+ { op: "span.end", id: `tool:${p.id}`, ts: ts2, status: "ok", attrs: {} }
5386
+ ];
5387
+ }
5388
+ if (p.type === "message" && (p.role === "user" || p.role === "assistant")) {
5389
+ const text = messageText(p.content);
5390
+ if (!text)
5391
+ return [];
5392
+ return [{
5393
+ op: "event",
5394
+ id: `evt:msg:${sid}${idSalt(state)}:${ts2}:${p.role}`,
5395
+ sessionId: sid,
5396
+ type: p.role === "user" ? "message.user" : "message.assistant",
5397
+ ts: ts2,
5398
+ attrs: { preview: preview(text) }
5399
+ }];
5400
+ }
5401
+ return [];
5402
+ }
5403
+ return [];
5404
+ }
5405
+ var codexParser = {
5406
+ parse: parseCodexLine,
5407
+ finalize: (state, maxTs) => state.openTurnId ? [{ op: "span.end", id: `llm:${state.openTurnId}`, ts: maxTs, status: "ok" }] : []
5408
+ };
5409
+ // packages/codex/src/tailer.ts
5410
+ import { readdirSync as readdirSync3, statSync as statSync2 } from "fs";
5411
+ import { join as join5 } from "path";
5412
+ function startCodexTailer(rootDir, url, pollMs = 2000, offsetsPath) {
5413
+ const files = offsetsPath ? loadOffsets(offsetsPath, reviveCodexState) : new Map;
5414
+ let stopped = false;
5415
+ const pass = async () => {
5416
+ let dirty = false;
5417
+ let entries = [];
5418
+ try {
5419
+ entries = readdirSync3(rootDir, { recursive: true }).filter((e) => e.endsWith(".jsonl"));
5420
+ } catch {
5421
+ return;
5422
+ }
5423
+ for (const rel of entries) {
5424
+ const path = join5(rootDir, String(rel));
5425
+ try {
5426
+ let fs = files.get(path);
5427
+ if (!fs) {
5428
+ fs = { offset: 0, state: newCodexState() };
5429
+ files.set(path, fs);
5430
+ }
5431
+ const size = statSync2(path).size;
5432
+ if (size < fs.offset) {
5433
+ fs.offset = 0;
5434
+ fs.state = newCodexState();
5435
+ dirty = true;
5436
+ }
5437
+ if (size > fs.offset) {
5438
+ const r = await importTranscript(path, url, fs.offset, fs.state, false, codexParser);
5439
+ if (r.bytesRead !== fs.offset) {
5440
+ fs.offset = r.bytesRead;
5441
+ dirty = true;
5442
+ }
5443
+ }
5444
+ } catch {}
5445
+ }
5446
+ if (dirty && offsetsPath)
5447
+ saveOffsets(offsetsPath, files);
5448
+ };
5449
+ const loop = async () => {
5450
+ while (!stopped) {
5451
+ await pass();
5452
+ await Bun.sleep(pollMs);
5453
+ }
5454
+ };
5455
+ loop();
5456
+ return { stop() {
5457
+ stopped = true;
5458
+ } };
5459
+ }
5203
5460
  // packages/cli/src/install.ts
5204
5461
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
5205
- import { join as join5 } from "path";
5462
+ import { join as join6 } from "path";
5206
5463
  var HOOK_EVENTS = ["SessionStart", "SessionEnd", "PreToolUse", "PostToolUse", "Notification", "Stop", "SubagentStop", "PermissionRequest", "PermissionDenied"];
5207
5464
  var NEEDS_MATCHER = new Set(["PreToolUse", "PostToolUse"]);
5208
5465
  function installHooks(claudeDir, hookCommand) {
5209
5466
  mkdirSync2(claudeDir, { recursive: true });
5210
- const settingsPath = join5(claudeDir, "settings.json");
5467
+ const settingsPath = join6(claudeDir, "settings.json");
5211
5468
  let settings = {};
5212
5469
  if (existsSync3(settingsPath)) {
5213
5470
  try {
@@ -5239,34 +5496,58 @@ function installHooks(claudeDir, hookCommand) {
5239
5496
  }
5240
5497
 
5241
5498
  // packages/cli/src/sweep.ts
5242
- async function importAll(projectsDir, url) {
5243
- const files = Array.from(new Bun.Glob("*/*.jsonl").scanSync({ cwd: projectsDir, absolute: true })).sort();
5499
+ import { existsSync as existsSync4 } from "fs";
5500
+ async function importOne(path, url, opts, label) {
5501
+ try {
5502
+ const r = await importSession(path, url, opts);
5503
+ if (r.emitted) {
5504
+ console.log(` ${label}: ${r.ops} ops`);
5505
+ return "ok";
5506
+ }
5507
+ console.error(` ${label}: server unreachable at ${url} \u2014 aborting sweep`);
5508
+ return "unreachable";
5509
+ } catch (err) {
5510
+ console.error(` ${label}: ${err instanceof Error ? err.message : String(err)}`);
5511
+ return "failed";
5512
+ }
5513
+ }
5514
+ async function importAll(projectsDir, url, codexRoot) {
5515
+ const claudeFiles = existsSync4(projectsDir) ? Array.from(new Bun.Glob("*/*.jsonl").scanSync({ cwd: projectsDir, absolute: true })).sort() : [];
5244
5516
  let ok = 0;
5245
5517
  let failed = 0;
5246
- for (const f of files) {
5518
+ let aborted = false;
5519
+ for (const f of claudeFiles) {
5247
5520
  const name = f.split("/").slice(-2).join("/");
5248
- try {
5249
- const r = await importSession(f, url, { finalize: true });
5250
- if (r.emitted) {
5251
- ok++;
5252
- console.log(` ${name}: ${r.ops} ops`);
5253
- } else {
5254
- failed++;
5255
- console.error(` ${name}: server unreachable at ${url} \u2014 aborting sweep`);
5521
+ const result = await importOne(f, url, { finalize: true }, name);
5522
+ if (result === "ok") {
5523
+ ok++;
5524
+ } else {
5525
+ failed++;
5526
+ if (result === "unreachable") {
5527
+ aborted = true;
5256
5528
  break;
5257
5529
  }
5258
- } catch (err) {
5530
+ }
5531
+ }
5532
+ const codexFiles = !aborted && codexRoot && existsSync4(codexRoot) ? Array.from(new Bun.Glob("**/*.jsonl").scanSync({ cwd: codexRoot, absolute: true })).sort() : [];
5533
+ for (const f of codexFiles) {
5534
+ const name = f.split("/").slice(-2).join("/");
5535
+ const result = await importOne(f, url, { finalize: true, parser: codexParser, newState: newCodexState }, name);
5536
+ if (result === "ok") {
5537
+ ok++;
5538
+ } else {
5259
5539
  failed++;
5260
- console.error(` ${name}: ${err instanceof Error ? err.message : String(err)}`);
5540
+ if (result === "unreachable")
5541
+ break;
5261
5542
  }
5262
5543
  }
5263
- return { ok, failed, total: files.length };
5544
+ return { ok, failed, total: claudeFiles.length + codexFiles.length };
5264
5545
  }
5265
5546
 
5266
5547
  // packages/cli/src/service.ts
5267
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, realpathSync, rmSync, writeFileSync as writeFileSync3 } from "fs";
5548
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, realpathSync, rmSync, writeFileSync as writeFileSync3 } from "fs";
5268
5549
  import { homedir as homedir2 } from "os";
5269
- import { dirname as dirname2, join as join6 } from "path";
5550
+ import { dirname as dirname2, join as join7 } from "path";
5270
5551
  function systemdUnit(execStart) {
5271
5552
  return `[Unit]
5272
5553
  Description=0rrery - trace-first observability for AI agent workflows
@@ -5300,9 +5581,9 @@ ${items}
5300
5581
  }
5301
5582
  function servicePath(platform = process.platform) {
5302
5583
  if (platform === "linux")
5303
- return join6(homedir2(), ".config/systemd/user/0rrery.service");
5584
+ return join7(homedir2(), ".config/systemd/user/0rrery.service");
5304
5585
  if (platform === "darwin")
5305
- return join6(homedir2(), "Library/LaunchAgents/com.0pon.0rrery.plist");
5586
+ return join7(homedir2(), "Library/LaunchAgents/com.0pon.0rrery.plist");
5306
5587
  return null;
5307
5588
  }
5308
5589
  function resolveBin() {
@@ -5322,7 +5603,7 @@ function runService(sub, platform = process.platform, exec = run, file = service
5322
5603
  const linux = platform === "linux";
5323
5604
  if (sub === "install") {
5324
5605
  const bin = resolveBin();
5325
- if (!linux && existsSync4(file))
5606
+ if (!linux && existsSync5(file))
5326
5607
  exec(["launchctl", "unload", "-w", file]);
5327
5608
  mkdirSync3(dirname2(file), { recursive: true });
5328
5609
  writeFileSync3(file, linux ? systemdUnit([...bin, "serve"].join(" ")) : launchdPlist([...bin, "serve"]));
@@ -5331,11 +5612,12 @@ function runService(sub, platform = process.platform, exec = run, file = service
5331
5612
  return ok;
5332
5613
  }
5333
5614
  if (sub === "uninstall") {
5334
- if (!existsSync4(file)) {
5615
+ const existed = existsSync5(file);
5616
+ const ok = linux ? exec(["systemctl", "--user", "disable", "--now", "0rrery"]) : exec(["launchctl", "unload", "-w", file]);
5617
+ if (!existed) {
5335
5618
  console.log("no service installed");
5336
5619
  return true;
5337
5620
  }
5338
- const ok = linux ? exec(["systemctl", "--user", "disable", "--now", "0rrery"]) : exec(["launchctl", "unload", "-w", file]);
5339
5621
  rmSync(file, { force: true });
5340
5622
  if (linux)
5341
5623
  exec(["systemctl", "--user", "daemon-reload"]);
@@ -5343,21 +5625,23 @@ function runService(sub, platform = process.platform, exec = run, file = service
5343
5625
  return ok;
5344
5626
  }
5345
5627
  if (sub === "status") {
5346
- return linux ? exec(["systemctl", "--user", "status", "0rrery", "--no-pager"]) : exec(["launchctl", "list", "com.0pon.0rrery"]);
5628
+ const ok = linux ? exec(["systemctl", "--user", "status", "0rrery", "--no-pager"]) : exec(["launchctl", "list", "com.0pon.0rrery"]);
5629
+ console.log(`dashboard: http://localhost:${process.env.ORRERY_PORT ?? 7317}`);
5630
+ return ok;
5347
5631
  }
5348
5632
  console.error("usage: 0rrery service <install|uninstall|status>");
5349
5633
  return false;
5350
5634
  }
5351
5635
 
5352
5636
  // packages/cli/src/skill.ts
5353
- import { cpSync, existsSync as existsSync5, mkdirSync as mkdirSync4 } from "fs";
5354
- import { join as join7 } from "path";
5637
+ import { cpSync, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
5638
+ import { join as join8 } from "path";
5355
5639
  function skillSourceDir() {
5356
- const candidates = [join7(import.meta.dir, "skill"), join7(import.meta.dir, "../skill")];
5357
- return candidates.find(existsSync5) ?? null;
5640
+ const candidates = [join8(import.meta.dir, "skill"), join8(import.meta.dir, "../skill")];
5641
+ return candidates.find(existsSync6) ?? null;
5358
5642
  }
5359
5643
  function installSkill(claudeDir, srcDir) {
5360
- const dest = join7(claudeDir, "skills", "0rrery");
5644
+ const dest = join8(claudeDir, "skills", "0rrery");
5361
5645
  mkdirSync4(dest, { recursive: true });
5362
5646
  cpSync(srcDir, dest, { recursive: true });
5363
5647
  return dest;
@@ -5366,9 +5650,20 @@ function installSkill(claudeDir, srcDir) {
5366
5650
  // packages/cli/src/index.ts
5367
5651
  var [cmd, arg] = process.argv.slice(2);
5368
5652
  var url = process.env.ORRERY_URL ?? "http://localhost:7317";
5369
- var claudeDir = () => process.env.ORRERY_CLAUDE_DIR ?? join8(homedir3(), ".claude");
5653
+ var claudeDir = () => process.env.ORRERY_CLAUDE_DIR ?? join9(homedir3(), ".claude");
5654
+ var codexDir = () => process.env.ORRERY_CODEX_DIR ?? join9(homedir3(), ".codex", "sessions");
5655
+ function sniffHead(path) {
5656
+ const fd = openSync2(path, "r");
5657
+ try {
5658
+ const buf = Buffer.alloc(400);
5659
+ const n = readSync2(fd, buf, 0, buf.length, 0);
5660
+ return buf.subarray(0, n).toString("utf8");
5661
+ } finally {
5662
+ closeSync2(fd);
5663
+ }
5664
+ }
5370
5665
  function runInstall() {
5371
- if (!existsSync6(claudeDir())) {
5666
+ if (!existsSync7(claudeDir())) {
5372
5667
  console.warn(`${claudeDir()} not found \u2014 Claude Code not present, skipping hooks`);
5373
5668
  return false;
5374
5669
  }
@@ -5385,12 +5680,16 @@ switch (cmd) {
5385
5680
  case "serve": {
5386
5681
  const config = loadConfig();
5387
5682
  const srv = startServer(config);
5388
- const projectsDir = join8(claudeDir(), "projects");
5389
- const tailer = startTailer(projectsDir, srv.url, 2000, join8(config.dataDir, "tailer-offsets.json"));
5683
+ const projectsDir = join9(claudeDir(), "projects");
5684
+ const tailer = startTailer(projectsDir, srv.url, 2000, join9(config.dataDir, "tailer-offsets.json"));
5685
+ const cx = existsSync7(codexDir()) ? startCodexTailer(codexDir(), srv.url, 2000, join9(config.dataDir, "codex-offsets.json")) : null;
5390
5686
  console.log(`0rrery serving on ${srv.url} (db: ${config.dbPath})`);
5391
5687
  console.log(`tailing ${projectsDir}`);
5688
+ if (cx)
5689
+ console.log(`tailing ${codexDir()} (codex)`);
5392
5690
  process.on("SIGINT", () => {
5393
5691
  tailer.stop();
5692
+ cx?.stop();
5394
5693
  srv.stop();
5395
5694
  process.exit(0);
5396
5695
  });
@@ -5419,15 +5718,18 @@ switch (cmd) {
5419
5718
  process.exit(1);
5420
5719
  }
5421
5720
  if (arg === "--all") {
5422
- const projectsDir = join8(claudeDir(), "projects");
5423
- const r2 = await importAll(projectsDir, url);
5721
+ const projectsDir = join9(claudeDir(), "projects");
5722
+ const cxRoot = existsSync7(codexDir()) ? codexDir() : undefined;
5723
+ const r2 = await importAll(projectsDir, url, cxRoot);
5424
5724
  console.log(r2.total ? `imported ${r2.ok}/${r2.total} transcript(s)` : `no transcripts found under ${projectsDir}`);
5425
5725
  process.exit(r2.failed && !r2.ok ? 1 : 0);
5426
5726
  break;
5427
5727
  }
5428
5728
  let r;
5429
5729
  try {
5430
- r = await importSession(resolve2(arg), url, { finalize: true });
5730
+ const path = resolve2(arg);
5731
+ const isCodex = /"type"\s*:\s*"session_meta"/.test(sniffHead(path));
5732
+ r = isCodex ? await importSession(path, url, { finalize: true, parser: codexParser, newState: newCodexState }) : await importSession(path, url, { finalize: true });
5431
5733
  } catch (err) {
5432
5734
  console.error(`0rrery import: cannot read ${arg}: ${err instanceof Error ? err.message : String(err)}`);
5433
5735
  process.exit(1);
@@ -5441,7 +5743,12 @@ switch (cmd) {
5441
5743
  break;
5442
5744
  }
5443
5745
  case "init": {
5444
- const flags = new Set(process.argv.slice(3));
5746
+ const KNOWN_INIT_FLAGS = new Set(["--no-hooks", "--no-service", "--no-import", "--no-skill"]);
5747
+ const argv = process.argv.slice(3);
5748
+ const flags = new Set(argv);
5749
+ for (const f of argv)
5750
+ if (!KNOWN_INIT_FLAGS.has(f))
5751
+ console.warn(`unknown flag: ${f}`);
5445
5752
  let failed = false;
5446
5753
  if (!flags.has("--no-hooks")) {
5447
5754
  console.log("\u203A hooks");
@@ -5455,7 +5762,7 @@ switch (cmd) {
5455
5762
  if (!flags.has("--no-skill")) {
5456
5763
  console.log("\u203A skill");
5457
5764
  const src = skillSourceDir();
5458
- if (!existsSync6(claudeDir()))
5765
+ if (!existsSync7(claudeDir()))
5459
5766
  console.log(` ${claudeDir()} not found \u2014 skipping skill`);
5460
5767
  else if (!src)
5461
5768
  console.log(" skill assets not found \u2014 skipping");
@@ -5469,14 +5776,15 @@ switch (cmd) {
5469
5776
  }
5470
5777
  if (!flags.has("--no-import")) {
5471
5778
  console.log("\u203A importing history");
5472
- const projectsDir = join8(claudeDir(), "projects");
5473
- if (existsSync6(projectsDir)) {
5779
+ const projectsDir = join9(claudeDir(), "projects");
5780
+ const cxRoot = existsSync7(codexDir()) ? codexDir() : undefined;
5781
+ if (existsSync7(projectsDir) || cxRoot) {
5474
5782
  for (let i = 0;i < 30; i++) {
5475
5783
  if (await fetch(`${url}/api/sessions`).then((x) => x.ok).catch(() => false))
5476
5784
  break;
5477
5785
  await Bun.sleep(200);
5478
5786
  }
5479
- const r = await importAll(projectsDir, url);
5787
+ const r = await importAll(projectsDir, url, cxRoot);
5480
5788
  console.log(r.total ? ` imported ${r.ok}/${r.total} transcript(s)` : " no transcripts found");
5481
5789
  } else {
5482
5790
  console.log(` ${projectsDir} not found \u2014 nothing to import`);