acp-devtools 0.2.1 → 0.3.0

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/dist/index.js CHANGED
@@ -9495,9 +9495,22 @@ var init_paths = __esm({
9495
9495
  // ../core/dist/server/queries.js
9496
9496
  import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
9497
9497
  import { dirname } from "path";
9498
- function listSessionsSummary(dbPath, limit = 200) {
9498
+ function listSessionsSummary(dbPath, limit = 200, filters = {}) {
9499
9499
  if (!existsSync2(dbPath))
9500
9500
  return [];
9501
+ const where = [];
9502
+ const params = [];
9503
+ if (filters.imported === true)
9504
+ where.push("s.imported_at IS NOT NULL");
9505
+ if (filters.imported === false)
9506
+ where.push("s.imported_at IS NULL");
9507
+ if (filters.client) {
9508
+ where.push(`(instr(lower(coalesce(s.client_name, '')), ?)
9509
+ + instr(lower(coalesce(s.client_version, '')), ?)
9510
+ + instr(lower(coalesce(s.client_platform, '')), ?)) > 0`);
9511
+ const needle = filters.client.toLowerCase();
9512
+ params.push(needle, needle, needle);
9513
+ }
9501
9514
  const db = openDatabase(dbPath);
9502
9515
  try {
9503
9516
  return db.prepare(
@@ -9514,21 +9527,16 @@ function listSessionsSummary(dbPath, limit = 200) {
9514
9527
  s.current_mode, s.current_model,
9515
9528
  (SELECT COUNT(*) FROM messages WHERE session_id = s.id) AS message_count
9516
9529
  FROM sessions s
9530
+ ${where.length > 0 ? `WHERE ${where.join(" AND ")}` : ""}
9517
9531
  ORDER BY COALESCE(s.imported_at, s.started_at) DESC
9518
9532
  LIMIT ?`
9519
- ).all(limit);
9533
+ ).all(...params, limit);
9520
9534
  } finally {
9521
9535
  db.close();
9522
9536
  }
9523
9537
  }
9524
9538
  function findSessionsByClient(dbPath, needle, limit = 200) {
9525
- const pool = listSessionsSummary(dbPath, FIND_BY_CLIENT_POOL_SIZE);
9526
- const lower = needle.toLowerCase();
9527
- const matches = pool.filter((s) => {
9528
- const candidates = [s.client_name, s.client_version, s.client_platform].filter((v) => typeof v === "string");
9529
- return candidates.some((c) => c.toLowerCase().includes(lower));
9530
- });
9531
- return matches.slice(0, limit);
9539
+ return listSessionsSummary(dbPath, limit, { client: needle });
9532
9540
  }
9533
9541
  function insertImportedSession(dbPath, exp, options = {}) {
9534
9542
  mkdirSync2(dirname(dbPath), { recursive: true });
@@ -9568,13 +9576,11 @@ function deleteSession(dbPath, sessionId) {
9568
9576
  db.close();
9569
9577
  }
9570
9578
  }
9571
- var FIND_BY_CLIENT_POOL_SIZE;
9572
9579
  var init_queries = __esm({
9573
9580
  "../core/dist/server/queries.js"() {
9574
9581
  "use strict";
9575
9582
  init_session();
9576
9583
  init_sqlite();
9577
- FIND_BY_CLIENT_POOL_SIZE = 500;
9578
9584
  }
9579
9585
  });
9580
9586
 
@@ -9672,10 +9678,36 @@ var init_replay = __esm({
9672
9678
  // ../core/dist/server/http.js
9673
9679
  import { existsSync as existsSync4 } from "fs";
9674
9680
  import { WebSocketServer as WebSocketServer2 } from "ws";
9681
+ function stripPort(host) {
9682
+ if (host.startsWith("[")) {
9683
+ const end = host.indexOf("]");
9684
+ return end === -1 ? host : host.slice(1, end);
9685
+ }
9686
+ const colon = host.lastIndexOf(":");
9687
+ if (colon !== -1 && host.indexOf(":") === colon)
9688
+ return host.slice(0, colon);
9689
+ return host;
9690
+ }
9691
+ function isAllowedHost(hostHeader, allowedHosts = []) {
9692
+ if (allowedHosts.some((h) => WILDCARD_BINDS.has(h)))
9693
+ return true;
9694
+ if (typeof hostHeader !== "string" || hostHeader.length === 0)
9695
+ return false;
9696
+ const host = stripPort(hostHeader.trim()).toLowerCase();
9697
+ if (LOOPBACK_HOSTS.has(host))
9698
+ return true;
9699
+ return allowedHosts.some((h) => stripPort(h).toLowerCase() === host);
9700
+ }
9675
9701
  function createApiHandler(options) {
9676
9702
  return (req, res) => {
9677
9703
  const url = req.url ?? "";
9678
- const path = url.split("?")[0];
9704
+ const path = url.split("?")[0] ?? "";
9705
+ if (path.startsWith("/api/") && !isAllowedHost(req.headers.host, options.allowedHosts)) {
9706
+ res.statusCode = 403;
9707
+ res.setHeader("content-type", "application/json");
9708
+ res.end(JSON.stringify({ error: "forbidden host" }));
9709
+ return true;
9710
+ }
9679
9711
  if (path === "/api/active") {
9680
9712
  handleActive(req, res);
9681
9713
  return true;
@@ -9886,13 +9918,18 @@ function attachReplayUpgrade(httpServer, options) {
9886
9918
  const match = url.match(/^\/replay\/(\d+)/);
9887
9919
  if (!match)
9888
9920
  return;
9921
+ if (!isAllowedHost(req.headers.host, options.allowedHosts)) {
9922
+ socket.write("HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n");
9923
+ socket.destroy();
9924
+ return;
9925
+ }
9889
9926
  const sessionId = Number(match[1]);
9890
9927
  wss.handleUpgrade(req, socket, head, (ws) => {
9891
9928
  streamReplay(ws, sessionId, options.capturesDbPath);
9892
9929
  });
9893
9930
  });
9894
9931
  }
9895
- var IMPORT_MAX_BYTES;
9932
+ var LOOPBACK_HOSTS, WILDCARD_BINDS, IMPORT_MAX_BYTES;
9896
9933
  var init_http = __esm({
9897
9934
  "../core/dist/server/http.js"() {
9898
9935
  "use strict";
@@ -9902,6 +9939,8 @@ var init_http = __esm({
9902
9939
  init_sqlite();
9903
9940
  init_queries();
9904
9941
  init_replay();
9942
+ LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1"]);
9943
+ WILDCARD_BINDS = /* @__PURE__ */ new Set(["0.0.0.0", "::", "*"]);
9905
9944
  IMPORT_MAX_BYTES = 64 * 1024 * 1024;
9906
9945
  }
9907
9946
  });
@@ -10282,7 +10321,11 @@ var init_table = __esm({
10282
10321
  function registerDiffCommand(program) {
10283
10322
  program.command("diff").description(
10284
10323
  'Align two saved sessions and show what changed between them \u2014 added / removed frames and field-level payload differences. Useful for "worked yesterday, broke today" and A/B comparisons of two agents on the same prompt.'
10285
- ).argument("<a>", "baseline session id (left side)").argument("<b>", "new session id (right side)").option("--db <path>", "captures database", defaultCapturesDbPath()).option("--full", "print unchanged frames too (default collapses equal runs)").option("--json", "machine-readable JSON instead of human-readable text").action((rawA, rawB, opts) => {
10324
+ ).argument("<a>", "baseline session id (left side)").argument("<b>", "new session id (right side)").option("--db <path>", "captures database", defaultCapturesDbPath()).option("--full", "print unchanged frames too (default collapses equal runs)").option("--json", "machine-readable JSON instead of human-readable text").option(
10325
+ "--raw",
10326
+ "skip default redaction of auth headers / proxy tokens (use only when the output stays on YOUR machine)",
10327
+ false
10328
+ ).action((rawA, rawB, opts) => {
10286
10329
  const idA = parseId(rawA);
10287
10330
  const idB = parseId(rawB);
10288
10331
  let db;
@@ -10313,6 +10356,32 @@ function registerDiffCommand(program) {
10313
10356
  process.exit(1);
10314
10357
  }
10315
10358
  db.close();
10359
+ let fieldsRedacted = 0;
10360
+ let messagesAffected = 0;
10361
+ if (!opts.raw) {
10362
+ const redactAll = (msgs) => msgs.map((m) => {
10363
+ const r = redactMessage(m);
10364
+ if (r.count > 0) {
10365
+ messagesAffected += 1;
10366
+ fieldsRedacted += r.count;
10367
+ }
10368
+ return r.redacted;
10369
+ });
10370
+ msgsA = redactAll(msgsA);
10371
+ msgsB = redactAll(msgsB);
10372
+ }
10373
+ const writeRedactionNote = () => {
10374
+ if (opts.raw) {
10375
+ process.stderr.write(
10376
+ "acp-devtools: --raw was set; output may contain auth headers / proxy tokens \u2014 do not share publicly\n"
10377
+ );
10378
+ } else if (fieldsRedacted > 0) {
10379
+ process.stderr.write(
10380
+ `acp-devtools: redacted ${fieldsRedacted} field(s) across ${messagesAffected} message(s) \u2014 re-run with --raw to compare live values
10381
+ `
10382
+ );
10383
+ }
10384
+ };
10316
10385
  const diff = buildSessionDiff(msgsA, msgsB);
10317
10386
  const meta = buildMetadataDiff(msgsA, msgsB);
10318
10387
  const perf = buildMethodStatsDiff(msgsA, msgsB);
@@ -10331,12 +10400,14 @@ function registerDiffCommand(program) {
10331
10400
  2
10332
10401
  ) + "\n"
10333
10402
  );
10403
+ writeRedactionNote();
10334
10404
  return;
10335
10405
  }
10336
10406
  const st = createStyler(colorEnabled(process.stdout));
10337
10407
  process.stdout.write(
10338
10408
  renderDiff(infoA, infoB, msgsA.length, msgsB.length, diff, meta, perf, Boolean(opts.full), st)
10339
10409
  );
10410
+ writeRedactionNote();
10340
10411
  });
10341
10412
  }
10342
10413
  function heading(st, label) {
@@ -10782,7 +10853,7 @@ var CLI_VERSION;
10782
10853
  var init_version = __esm({
10783
10854
  "src/version.ts"() {
10784
10855
  "use strict";
10785
- CLI_VERSION = "0.2.1";
10856
+ CLI_VERSION = "0.3.0";
10786
10857
  }
10787
10858
  });
10788
10859
 
@@ -11201,18 +11272,17 @@ function registerListCommand(program) {
11201
11272
  `);
11202
11273
  process.exit(2);
11203
11274
  }
11204
- let rows = listSessionsSummary(opts.db, limit);
11205
- if (opts.imported) rows = rows.filter((r) => r.imported_at !== null);
11206
- if (opts.saved) rows = rows.filter((r) => r.imported_at === null);
11207
- if (opts.client) {
11208
- const needle = opts.client.toLowerCase();
11209
- rows = rows.filter((r) => {
11210
- const fields = [r.client_name, r.client_version, r.client_platform].filter(
11211
- (v) => typeof v === "string"
11212
- );
11213
- return fields.some((f) => f.toLowerCase().includes(needle));
11214
- });
11275
+ if (opts.imported && opts.saved) {
11276
+ process.stderr.write(
11277
+ "acp-devtools: --imported and --saved are mutually exclusive\n"
11278
+ );
11279
+ process.exit(2);
11215
11280
  }
11281
+ const filters = {};
11282
+ if (opts.imported) filters.imported = true;
11283
+ if (opts.saved) filters.imported = false;
11284
+ if (opts.client) filters.client = opts.client;
11285
+ const rows = listSessionsSummary(opts.db, limit, filters);
11216
11286
  if (opts.json) {
11217
11287
  process.stdout.write(JSON.stringify(rows, null, 2) + "\n");
11218
11288
  return;
@@ -11980,7 +12050,7 @@ import { readFileSync as readFileSync3 } from "fs";
11980
12050
  import { basename as basename2 } from "path";
11981
12051
  function loadPlaybackScript(opts) {
11982
12052
  if (opts.script && opts.session !== void 0) {
11983
- throw new Error("--script and --session are mutually exclusive");
12053
+ throw new PlaybackUsageError("--script and --session are mutually exclusive");
11984
12054
  }
11985
12055
  if (opts.script) {
11986
12056
  let text;
@@ -11999,7 +12069,7 @@ function loadPlaybackScript(opts) {
11999
12069
  if (opts.session !== void 0) {
12000
12070
  const id = Number(opts.session);
12001
12071
  if (!Number.isInteger(id) || id <= 0) {
12002
- throw new Error(`invalid --session "${opts.session}"`);
12072
+ throw new PlaybackUsageError(`invalid --session "${opts.session}"`);
12003
12073
  }
12004
12074
  session = Session.load(db, id);
12005
12075
  } else {
@@ -12019,10 +12089,17 @@ function loadPlaybackScript(opts) {
12019
12089
  db.close();
12020
12090
  }
12021
12091
  }
12092
+ var PlaybackUsageError;
12022
12093
  var init_playback_source = __esm({
12023
12094
  "src/commands/playback-source.ts"() {
12024
12095
  "use strict";
12025
12096
  init_dist();
12097
+ PlaybackUsageError = class extends Error {
12098
+ constructor(message) {
12099
+ super(message);
12100
+ this.name = "PlaybackUsageError";
12101
+ }
12102
+ };
12026
12103
  }
12027
12104
  });
12028
12105
 
@@ -12052,7 +12129,7 @@ function registerMockAgentCommand(program) {
12052
12129
  const msg = err instanceof Error ? err.message : String(err);
12053
12130
  process.stderr.write(`acp-devtools: ${msg}
12054
12131
  `);
12055
- process.exit(1);
12132
+ process.exit(err instanceof PlaybackUsageError ? 2 : 1);
12056
12133
  }
12057
12134
  const engine = new PlaybackEngine(loaded.messages, "agent");
12058
12135
  process.stderr.write(
@@ -12112,11 +12189,18 @@ function registerMockAgentCommand(program) {
12112
12189
  }
12113
12190
  };
12114
12191
  let pendingTimer = null;
12192
+ let stdinEnded = false;
12115
12193
  const emit = (line) => {
12116
12194
  process.stdout.write(line + "\n");
12117
12195
  log("\u2190", line);
12118
12196
  capture(line, "agent-to-editor");
12119
12197
  };
12198
+ const finishIfDrained = () => {
12199
+ if (stdinEnded && !pendingTimer) {
12200
+ closeSession();
12201
+ process.exit(0);
12202
+ }
12203
+ };
12120
12204
  const drain = () => {
12121
12205
  if (pendingTimer) return;
12122
12206
  while (true) {
@@ -12135,9 +12219,7 @@ function registerMockAgentCommand(program) {
12135
12219
  emit(step.line);
12136
12220
  continue;
12137
12221
  }
12138
- if (step.kind === "done") {
12139
- return;
12140
- }
12222
+ finishIfDrained();
12141
12223
  return;
12142
12224
  }
12143
12225
  };
@@ -12157,9 +12239,8 @@ function registerMockAgentCommand(program) {
12157
12239
  }
12158
12240
  });
12159
12241
  process.stdin.on("end", () => {
12242
+ stdinEnded = true;
12160
12243
  drain();
12161
- closeSession();
12162
- process.exit(0);
12163
12244
  });
12164
12245
  drain();
12165
12246
  });
@@ -12198,7 +12279,7 @@ function registerMockEditorCommand(program) {
12198
12279
  const msg = err instanceof Error ? err.message : String(err);
12199
12280
  process.stderr.write(`acp-devtools: ${msg}
12200
12281
  `);
12201
- process.exit(1);
12282
+ process.exit(err instanceof PlaybackUsageError ? 2 : 1);
12202
12283
  }
12203
12284
  const engine = new PlaybackEngine(loaded.messages, "editor");
12204
12285
  process.stderr.write(
@@ -12396,7 +12477,7 @@ function registerProxyCommand(program) {
12396
12477
  } else {
12397
12478
  process.stderr.write(
12398
12479
  `acp-devtools: no agent specified.
12399
- Pass an agent command (\`acp-devtools proxy npx -y @zed-industries/claude-code-acp\`)
12480
+ Pass an agent command (\`acp-devtools proxy npx -y @agentclientprotocol/claude-agent-acp\`)
12400
12481
  or use --agent <name> with one of: ${knownAgentList}.
12401
12482
  `
12402
12483
  );
@@ -12850,7 +12931,11 @@ var init_search = __esm({
12850
12931
  function registerSessionInfoCommand(program) {
12851
12932
  program.command("session-info").description(
12852
12933
  "Print derived client/agent metadata for a saved session \u2014 the terminal equivalent of the inspector's session info panel."
12853
- ).argument("<id>", "session id (see `acp-devtools list`)").option("--db <path>", "captures database", defaultCapturesDbPath()).option("--json", "machine-readable JSON instead of human-readable text").action((rawId, opts) => {
12934
+ ).argument("<id>", "session id (see `acp-devtools list`)").option("--db <path>", "captures database", defaultCapturesDbPath()).option("--json", "machine-readable JSON instead of human-readable text").option(
12935
+ "--raw",
12936
+ "skip default redaction of auth headers / proxy tokens (use only when the output stays on YOUR machine)",
12937
+ false
12938
+ ).action((rawId, opts) => {
12854
12939
  const id = Number(rawId);
12855
12940
  if (!Number.isInteger(id) || id <= 0) {
12856
12941
  process.stderr.write(`acp-devtools: invalid id "${rawId}"
@@ -12880,8 +12965,30 @@ function registerSessionInfoCommand(program) {
12880
12965
  }
12881
12966
  const messages = [];
12882
12967
  for (const m of session.messages()) messages.push(m);
12883
- const meta = extractSessionMetadata(messages);
12884
12968
  db.close();
12969
+ let fieldsRedacted = 0;
12970
+ let messagesAffected = 0;
12971
+ const display = opts.raw ? messages : messages.map((m) => {
12972
+ const r = redactMessage(m);
12973
+ if (r.count > 0) {
12974
+ messagesAffected += 1;
12975
+ fieldsRedacted += r.count;
12976
+ }
12977
+ return r.redacted;
12978
+ });
12979
+ const meta = extractSessionMetadata(display);
12980
+ const writeRedactionNote = () => {
12981
+ if (opts.raw) {
12982
+ process.stderr.write(
12983
+ "acp-devtools: --raw was set; output may contain auth headers / proxy tokens \u2014 do not share publicly\n"
12984
+ );
12985
+ } else if (fieldsRedacted > 0) {
12986
+ process.stderr.write(
12987
+ `acp-devtools: redacted ${fieldsRedacted} field(s) across ${messagesAffected} message(s) \u2014 re-run with --raw to print them
12988
+ `
12989
+ );
12990
+ }
12991
+ };
12885
12992
  if (opts.json) {
12886
12993
  process.stdout.write(
12887
12994
  JSON.stringify(
@@ -12901,9 +13008,11 @@ function registerSessionInfoCommand(program) {
12901
13008
  2
12902
13009
  ) + "\n"
12903
13010
  );
13011
+ writeRedactionNote();
12904
13012
  return;
12905
13013
  }
12906
13014
  renderText(session, meta, messages.length);
13015
+ writeRedactionNote();
12907
13016
  });
12908
13017
  }
12909
13018
  function renderText(session, meta, msgCount) {
@@ -12913,12 +13022,10 @@ function renderText(session, meta, msgCount) {
12913
13022
  `);
12914
13023
  w.write(s.dim("\u2500".repeat(48)) + "\n");
12915
13024
  const clientLabel = meta.client.title ?? meta.client.name ?? session.info.clientName ?? "\u2014";
12916
- const clientVer = meta.client.version ? ` v${meta.client.version}` : "";
12917
13025
  const clientPlatform = meta.client.platform ? ` (${meta.client.platform})` : "";
12918
- w.write(pad(s, "Client", `${clientLabel}${clientVer}${clientPlatform}`));
13026
+ w.write(pad(s, "Client", `${withVersion(clientLabel, meta.client.version)}${clientPlatform}`));
12919
13027
  const agentLabel = meta.agent.name ?? shortAgent(session.info.agentCommand) ?? "\u2014";
12920
- const agentVer = meta.agent.version ? ` v${meta.agent.version}` : "";
12921
- w.write(pad(s, "Agent", `${agentLabel}${agentVer}`));
13028
+ w.write(pad(s, "Agent", withVersion(agentLabel, meta.agent.version)));
12922
13029
  w.write(pad(s, "Protocol", meta.protocolVersion !== null ? `ACP v${meta.protocolVersion}` : "\u2014"));
12923
13030
  w.write(
12924
13031
  pad(
@@ -12966,6 +13073,10 @@ ${heading2(s, "JETBRAINS EXTENSIONS")}
12966
13073
  );
12967
13074
  }
12968
13075
  }
13076
+ function withVersion(label, version) {
13077
+ if (!version || label.includes(version)) return label;
13078
+ return `${label} v${version}`;
13079
+ }
12969
13080
  function heading2(s, label) {
12970
13081
  return s.bold(s.yellow(label));
12971
13082
  }
@@ -13368,15 +13479,17 @@ function registerUiCommand(program) {
13368
13479
  process.exit(1);
13369
13480
  }
13370
13481
  const capturesDbPath = opts.capturesDb ?? defaultCapturesDbPath();
13482
+ const allowedHosts = [opts.host];
13371
13483
  const apiHandler = createApiHandler({
13372
13484
  capturesDbPath,
13373
- binaryPath: process.argv[1] ?? null
13485
+ binaryPath: process.argv[1] ?? null,
13486
+ allowedHosts
13374
13487
  });
13375
13488
  const server = createServer((req, res) => {
13376
13489
  if (apiHandler(req, res)) return;
13377
13490
  serveStatic(req, res, rootDir);
13378
13491
  });
13379
- attachReplayUpgrade(server, { capturesDbPath });
13492
+ attachReplayUpgrade(server, { capturesDbPath, allowedHosts });
13380
13493
  await new Promise((resolveListen, rejectListen) => {
13381
13494
  server.once("error", rejectListen);
13382
13495
  server.listen(port, opts.host, () => {
@@ -13768,7 +13881,7 @@ var init_help = __esm({
13768
13881
  };
13769
13882
  ROOT_EXAMPLES = [
13770
13883
  "# Capture a Claude Code session (Zed adapter)",
13771
- "acp-devtools proxy npx -y @zed-industries/claude-code-acp",
13884
+ "acp-devtools proxy npx -y @agentclientprotocol/claude-agent-acp",
13772
13885
  "",
13773
13886
  "# Browse captured sessions in the web inspector",
13774
13887
  "acp-devtools ui",
@@ -13783,7 +13896,7 @@ var init_help = __esm({
13783
13896
  EXAMPLES = {
13784
13897
  proxy: [
13785
13898
  "# Wrap the Zed Claude Code adapter and capture every frame",
13786
- "acp-devtools proxy npx -y @zed-industries/claude-code-acp",
13899
+ "acp-devtools proxy npx -y @agentclientprotocol/claude-agent-acp",
13787
13900
  "",
13788
13901
  "# Capture headless, into a specific database",
13789
13902
  "acp-devtools proxy --no-ws --save-to /tmp/run.db node ./my-agent.js"
@@ -14013,7 +14126,7 @@ function expandArgv(rawArgs, defaultAgent, isAgentShortcut2) {
14013
14126
  if (isAgentShortcut2(first)) {
14014
14127
  return ["proxy", "--agent", first, ...rawArgs.slice(1)];
14015
14128
  }
14016
- return ["proxy", ...rawArgs];
14129
+ return rawArgs;
14017
14130
  }
14018
14131
  function reportFatal(err, prefix = "acp-devtools") {
14019
14132
  if (isNativeBindingError(err)) {
@@ -14056,4 +14169,7 @@ async function main() {
14056
14169
  }
14057
14170
  }
14058
14171
  main();
14172
+ export {
14173
+ expandArgv
14174
+ };
14059
14175
  //# sourceMappingURL=index.js.map