acp-devtools 0.5.0 → 0.6.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
@@ -3755,21 +3755,21 @@ var require_fast_uri = __commonJS({
3755
3755
  normalizeString(uri, options);
3756
3756
  } else if (typeof uri === "object") {
3757
3757
  uri = /** @type {T} */
3758
- parse(serialize(uri, options), options);
3758
+ parse2(serialize(uri, options), options);
3759
3759
  }
3760
3760
  return uri;
3761
3761
  }
3762
3762
  function resolve2(baseURI, relativeURI, options) {
3763
3763
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3764
- const resolved = resolveComponent(parse(baseURI, schemelessOptions), parse(relativeURI, schemelessOptions), schemelessOptions, true);
3764
+ const resolved = resolveComponent(parse2(baseURI, schemelessOptions), parse2(relativeURI, schemelessOptions), schemelessOptions, true);
3765
3765
  schemelessOptions.skipEscape = true;
3766
3766
  return serialize(resolved, schemelessOptions);
3767
3767
  }
3768
3768
  function resolveComponent(base, relative, options, skipNormalization) {
3769
3769
  const target = {};
3770
3770
  if (!skipNormalization) {
3771
- base = parse(serialize(base, options), options);
3772
- relative = parse(serialize(relative, options), options);
3771
+ base = parse2(serialize(base, options), options);
3772
+ relative = parse2(serialize(relative, options), options);
3773
3773
  }
3774
3774
  options = options || {};
3775
3775
  if (!options.tolerant && relative.scheme) {
@@ -3992,7 +3992,7 @@ var require_fast_uri = __commonJS({
3992
3992
  }
3993
3993
  return { parsed, malformedAuthorityOrPort };
3994
3994
  }
3995
- function parse(uri, opts) {
3995
+ function parse2(uri, opts) {
3996
3996
  return parseWithStatus(uri, opts).parsed;
3997
3997
  }
3998
3998
  function normalizeString(uri, opts) {
@@ -4021,7 +4021,7 @@ var require_fast_uri = __commonJS({
4021
4021
  resolveComponent,
4022
4022
  equal,
4023
4023
  serialize,
4024
- parse
4024
+ parse: parse2
4025
4025
  };
4026
4026
  module.exports = fastUri;
4027
4027
  module.exports.default = fastUri;
@@ -8518,6 +8518,14 @@ var init_registry = __esm({
8518
8518
  command: "opencode",
8519
8519
  args: ["acp"],
8520
8520
  requiresExternalInstall: true
8521
+ },
8522
+ junie: {
8523
+ shortName: "junie",
8524
+ displayName: "Junie",
8525
+ description: "JetBrains Junie CLI \u2014 install separately, then this runs `junie --acp=true`",
8526
+ command: "junie",
8527
+ args: ["--acp=true"],
8528
+ requiresExternalInstall: true
8521
8529
  }
8522
8530
  };
8523
8531
  DEFAULT_AGENT = "claude-code";
@@ -10073,6 +10081,142 @@ var init_dist = __esm({
10073
10081
  }
10074
10082
  });
10075
10083
 
10084
+ // src/lib/style.ts
10085
+ function colorEnabled(stream = process.stdout, env = process.env) {
10086
+ if (env.FORCE_COLOR === "0" || env.FORCE_COLOR === "false") return false;
10087
+ if (env.FORCE_COLOR !== void 0) return true;
10088
+ if (env.NO_COLOR !== void 0) return false;
10089
+ return Boolean(stream.isTTY);
10090
+ }
10091
+ function paint(enabled, code, s) {
10092
+ if (!enabled) return s;
10093
+ return `${ESC}[${CODES[code]}m${s}${ESC}[${CODES.reset}m`;
10094
+ }
10095
+ function createStyler(enabled = colorEnabled()) {
10096
+ const wrap = (code) => (s) => paint(enabled, code, s);
10097
+ return {
10098
+ enabled,
10099
+ bold: wrap("bold"),
10100
+ dim: wrap("dim"),
10101
+ italic: wrap("italic"),
10102
+ underline: wrap("underline"),
10103
+ red: wrap("red"),
10104
+ green: wrap("green"),
10105
+ yellow: wrap("yellow"),
10106
+ blue: wrap("blue"),
10107
+ magenta: wrap("magenta"),
10108
+ cyan: wrap("cyan"),
10109
+ gray: wrap("gray")
10110
+ };
10111
+ }
10112
+ function stripAnsi(s) {
10113
+ return s.replace(ANSI_PATTERN, "");
10114
+ }
10115
+ function visibleWidth(s) {
10116
+ return stripAnsi(s).length;
10117
+ }
10118
+ var ESC, CODES, ANSI_PATTERN;
10119
+ var init_style = __esm({
10120
+ "src/lib/style.ts"() {
10121
+ "use strict";
10122
+ ESC = String.fromCharCode(27);
10123
+ CODES = {
10124
+ reset: 0,
10125
+ bold: 1,
10126
+ dim: 2,
10127
+ italic: 3,
10128
+ underline: 4,
10129
+ red: 31,
10130
+ green: 32,
10131
+ yellow: 33,
10132
+ blue: 34,
10133
+ magenta: 35,
10134
+ cyan: 36,
10135
+ gray: 90
10136
+ };
10137
+ ANSI_PATTERN = new RegExp(`${ESC}\\[[0-9;]*m`, "g");
10138
+ }
10139
+ });
10140
+
10141
+ // src/lib/table.ts
10142
+ function renderTable(s, columns, rows, opts = {}) {
10143
+ const indent = opts.indent ?? "";
10144
+ const gap = opts.gap ?? " ";
10145
+ const widths = columns.map(
10146
+ (col, c) => Math.max(visibleWidth(col.title), ...rows.map((r) => visibleWidth(r[c] ?? "")))
10147
+ );
10148
+ const pad2 = (text, width, align) => {
10149
+ const fill = " ".repeat(Math.max(0, width - visibleWidth(text)));
10150
+ return align === "right" ? fill + text : text + fill;
10151
+ };
10152
+ const lineFor = (cells, color) => {
10153
+ const rendered = columns.map((col, c) => {
10154
+ const align = col.align ?? "left";
10155
+ const text = cells[c] ?? "";
10156
+ const isLast = c === columns.length - 1;
10157
+ const cell = isLast && align === "left" ? text : pad2(text, widths[c], align);
10158
+ return color(cell);
10159
+ });
10160
+ return indent + rendered.join(gap);
10161
+ };
10162
+ const out = [];
10163
+ out.push(
10164
+ lineFor(
10165
+ columns.map((col) => col.title),
10166
+ (t) => s.dim(t)
10167
+ )
10168
+ );
10169
+ for (const row2 of rows) {
10170
+ out.push(lineFor(row2, (t) => t));
10171
+ }
10172
+ return out.map((l) => l + "\n").join("");
10173
+ }
10174
+ var init_table = __esm({
10175
+ "src/lib/table.ts"() {
10176
+ "use strict";
10177
+ init_style();
10178
+ }
10179
+ });
10180
+
10181
+ // src/commands/agents.ts
10182
+ function registerAgentsCommand(program) {
10183
+ program.command("agents").description("List the built-in agent shortcuts (use with proxy / setup / --agent)").option("--json", "emit machine-readable JSON instead of a table").action((opts) => {
10184
+ const agents = listAgents();
10185
+ if (opts.json) {
10186
+ process.stdout.write(JSON.stringify(agents, null, 2) + "\n");
10187
+ return;
10188
+ }
10189
+ const s = createStyler(colorEnabled(process.stdout));
10190
+ const rows = agents.map((a) => [
10191
+ s.green(a.shortName),
10192
+ a.displayName,
10193
+ [a.command, ...a.args].join(" "),
10194
+ a.requiresExternalInstall ? "install separately" : "auto via npx"
10195
+ ]);
10196
+ process.stdout.write(renderTable(s, COLUMNS, rows));
10197
+ process.stdout.write(
10198
+ s.dim(
10199
+ "\nUse a shortcut: acp-devtools <name> \xB7 proxy --agent <name> \xB7 setup <name> --editor <zed|jetbrains>\nAny other ACP agent: acp-devtools setup --editor <ed> -- <command\u2026>\n"
10200
+ )
10201
+ );
10202
+ });
10203
+ }
10204
+ var COLUMNS;
10205
+ var init_agents2 = __esm({
10206
+ "src/commands/agents.ts"() {
10207
+ "use strict";
10208
+ init_dist();
10209
+ init_style();
10210
+ init_table();
10211
+ COLUMNS = [
10212
+ { title: "SHORTCUT" },
10213
+ { title: "NAME" },
10214
+ { title: "RUNS" },
10215
+ { title: "INSTALL" }
10216
+ ];
10217
+ }
10218
+ });
10219
+
10076
10220
  // src/commands/backfill-metadata.ts
10077
10221
  function backfillSession(db, id) {
10078
10222
  let session;
@@ -10221,63 +10365,6 @@ var init_delete = __esm({
10221
10365
  }
10222
10366
  });
10223
10367
 
10224
- // src/lib/style.ts
10225
- function colorEnabled(stream = process.stdout, env = process.env) {
10226
- if (env.FORCE_COLOR === "0" || env.FORCE_COLOR === "false") return false;
10227
- if (env.FORCE_COLOR !== void 0) return true;
10228
- if (env.NO_COLOR !== void 0) return false;
10229
- return Boolean(stream.isTTY);
10230
- }
10231
- function paint(enabled, code, s) {
10232
- if (!enabled) return s;
10233
- return `${ESC}[${CODES[code]}m${s}${ESC}[${CODES.reset}m`;
10234
- }
10235
- function createStyler(enabled = colorEnabled()) {
10236
- const wrap = (code) => (s) => paint(enabled, code, s);
10237
- return {
10238
- enabled,
10239
- bold: wrap("bold"),
10240
- dim: wrap("dim"),
10241
- italic: wrap("italic"),
10242
- underline: wrap("underline"),
10243
- red: wrap("red"),
10244
- green: wrap("green"),
10245
- yellow: wrap("yellow"),
10246
- blue: wrap("blue"),
10247
- magenta: wrap("magenta"),
10248
- cyan: wrap("cyan"),
10249
- gray: wrap("gray")
10250
- };
10251
- }
10252
- function stripAnsi(s) {
10253
- return s.replace(ANSI_PATTERN, "");
10254
- }
10255
- function visibleWidth(s) {
10256
- return stripAnsi(s).length;
10257
- }
10258
- var ESC, CODES, ANSI_PATTERN;
10259
- var init_style = __esm({
10260
- "src/lib/style.ts"() {
10261
- "use strict";
10262
- ESC = String.fromCharCode(27);
10263
- CODES = {
10264
- reset: 0,
10265
- bold: 1,
10266
- dim: 2,
10267
- italic: 3,
10268
- underline: 4,
10269
- red: 31,
10270
- green: 32,
10271
- yellow: 33,
10272
- blue: 34,
10273
- magenta: 35,
10274
- cyan: 36,
10275
- gray: 90
10276
- };
10277
- ANSI_PATTERN = new RegExp(`${ESC}\\[[0-9;]*m`, "g");
10278
- }
10279
- });
10280
-
10281
10368
  // src/lib/palette.ts
10282
10369
  function colorKind(s, kind, text) {
10283
10370
  switch (kind) {
@@ -10311,46 +10398,6 @@ var init_palette = __esm({
10311
10398
  }
10312
10399
  });
10313
10400
 
10314
- // src/lib/table.ts
10315
- function renderTable(s, columns, rows, opts = {}) {
10316
- const indent = opts.indent ?? "";
10317
- const gap = opts.gap ?? " ";
10318
- const widths = columns.map(
10319
- (col, c) => Math.max(visibleWidth(col.title), ...rows.map((r) => visibleWidth(r[c] ?? "")))
10320
- );
10321
- const pad2 = (text, width, align) => {
10322
- const fill = " ".repeat(Math.max(0, width - visibleWidth(text)));
10323
- return align === "right" ? fill + text : text + fill;
10324
- };
10325
- const lineFor = (cells, color) => {
10326
- const rendered = columns.map((col, c) => {
10327
- const align = col.align ?? "left";
10328
- const text = cells[c] ?? "";
10329
- const isLast = c === columns.length - 1;
10330
- const cell = isLast && align === "left" ? text : pad2(text, widths[c], align);
10331
- return color(cell);
10332
- });
10333
- return indent + rendered.join(gap);
10334
- };
10335
- const out = [];
10336
- out.push(
10337
- lineFor(
10338
- columns.map((col) => col.title),
10339
- (t) => s.dim(t)
10340
- )
10341
- );
10342
- for (const row2 of rows) {
10343
- out.push(lineFor(row2, (t) => t));
10344
- }
10345
- return out.map((l) => l + "\n").join("");
10346
- }
10347
- var init_table = __esm({
10348
- "src/lib/table.ts"() {
10349
- "use strict";
10350
- init_style();
10351
- }
10352
- });
10353
-
10354
10401
  // src/commands/diff.ts
10355
10402
  function registerDiffCommand(program) {
10356
10403
  program.command("diff").description(
@@ -10634,10 +10681,130 @@ var init_diff = __esm({
10634
10681
  }
10635
10682
  });
10636
10683
 
10684
+ // src/lib/setup-config.ts
10685
+ import { homedir as homedir3, platform } from "os";
10686
+ import { join as join3 } from "path";
10687
+ import { applyEdits, modify, parse } from "jsonc-parser";
10688
+ function isEditor(value) {
10689
+ return EDITORS.includes(value);
10690
+ }
10691
+ function zedSettingsPath() {
10692
+ if (platform() === "win32") {
10693
+ return join3(process.env.APPDATA ?? "", "Zed", "settings.json");
10694
+ }
10695
+ if (platform() === "darwin") {
10696
+ return join3(homedir3(), ".config", "zed", "settings.json");
10697
+ }
10698
+ return join3(process.env.XDG_CONFIG_HOME ?? join3(homedir3(), ".config"), "zed", "settings.json");
10699
+ }
10700
+ function jetbrainsConfigPath() {
10701
+ return join3(homedir3(), ".jetbrains", "acp.json");
10702
+ }
10703
+ function editorInfo(editor) {
10704
+ if (editor === "zed") {
10705
+ return {
10706
+ id: "zed",
10707
+ label: "Zed",
10708
+ configPath: zedSettingsPath(),
10709
+ placement: "add the agent_servers block to Zed\u2019s settings.json (create the file or the key if missing)",
10710
+ restart: "reopen the agent panel so Zed re-reads the config",
10711
+ dedicatedFile: false
10712
+ };
10713
+ }
10714
+ return {
10715
+ id: "jetbrains",
10716
+ label: "JetBrains",
10717
+ configPath: jetbrainsConfigPath(),
10718
+ placement: "write this to the global JetBrains ACP config (create it if missing \u2014 every JetBrains IDE shares this one file)",
10719
+ restart: "restart the AI chat tool window (close and reopen it)",
10720
+ dedicatedFile: true
10721
+ };
10722
+ }
10723
+ function deriveCustomName(target) {
10724
+ const first = target[0] ?? "agent";
10725
+ if (first === "npx") {
10726
+ for (let i = 1; i < target.length; i++) {
10727
+ const token = target[i];
10728
+ if (!token.startsWith("-")) return token;
10729
+ }
10730
+ }
10731
+ return first.split(/[/\\]/).pop() || first;
10732
+ }
10733
+ function buildSetupPlan(input) {
10734
+ const target = input.target.length > 0 ? [...input.target] : [DEFAULT_AGENT];
10735
+ const single = target.length === 1 ? target[0] : null;
10736
+ let args;
10737
+ let displayName;
10738
+ let isCustom;
10739
+ let agentDescription;
10740
+ let requiresExternalInstall = false;
10741
+ if (single !== null && isAgentShortcut(single)) {
10742
+ const def = resolveAgent(single);
10743
+ args = [def.shortName];
10744
+ displayName = input.name ?? `${def.displayName} (via ACP Devtools)`;
10745
+ isCustom = false;
10746
+ agentDescription = def.description;
10747
+ requiresExternalInstall = def.requiresExternalInstall ?? false;
10748
+ } else {
10749
+ args = ["proxy", ...target];
10750
+ displayName = input.name ?? `${deriveCustomName(target)} (via ACP Devtools)`;
10751
+ isCustom = true;
10752
+ }
10753
+ return {
10754
+ editor: editorInfo(input.editor),
10755
+ displayName,
10756
+ entry: { type: "custom", command: input.selfPath, args },
10757
+ isCustom,
10758
+ agentDescription,
10759
+ requiresExternalInstall
10760
+ };
10761
+ }
10762
+ function renderConfigBlock(plan) {
10763
+ const entries = { [plan.displayName]: plan.entry };
10764
+ if (plan.editor.dedicatedFile) {
10765
+ return JSON.stringify({ agent_servers: entries }, null, 4);
10766
+ }
10767
+ return `"agent_servers": ${JSON.stringify(entries, null, 4)}`;
10768
+ }
10769
+ function detectFormatting(text) {
10770
+ const match = text.match(/\n([ \t]+)\S/);
10771
+ if (!match) return { insertSpaces: true, tabSize: 4 };
10772
+ const ws = match[1];
10773
+ if (ws.startsWith(" ")) return { insertSpaces: false, tabSize: 1 };
10774
+ return { insertSpaces: true, tabSize: ws.length };
10775
+ }
10776
+ function mergeIntoConfig(existing, name, entry) {
10777
+ const base = existing !== null && existing.trim().length > 0 ? existing : "{}";
10778
+ const errors = [];
10779
+ const root = parse(base, errors, { allowTrailingComma: true });
10780
+ if (errors.length > 0) {
10781
+ throw new Error("config is not valid JSON");
10782
+ }
10783
+ if (typeof root !== "object" || root === null || Array.isArray(root)) {
10784
+ throw new Error("config root is not a JSON object");
10785
+ }
10786
+ const servers = root.agent_servers;
10787
+ const replaced = typeof servers === "object" && servers !== null && !Array.isArray(servers) ? Object.prototype.hasOwnProperty.call(servers, name) : false;
10788
+ const edits = modify(base, ["agent_servers", name], entry, {
10789
+ formattingOptions: detectFormatting(base)
10790
+ });
10791
+ let text = applyEdits(base, edits);
10792
+ if (!text.endsWith("\n")) text += "\n";
10793
+ return { text, replaced };
10794
+ }
10795
+ var EDITORS;
10796
+ var init_setup_config = __esm({
10797
+ "src/lib/setup-config.ts"() {
10798
+ "use strict";
10799
+ init_dist();
10800
+ EDITORS = ["zed", "jetbrains"];
10801
+ }
10802
+ });
10803
+
10637
10804
  // src/commands/doctor.ts
10638
10805
  import { existsSync as existsSync6, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
10639
- import { homedir as homedir3, platform, release } from "os";
10640
- import { join as join3 } from "path";
10806
+ import { homedir as homedir4, platform as platform2, release } from "os";
10807
+ import { join as join4 } from "path";
10641
10808
  function colorStatus(s, status, text) {
10642
10809
  switch (status) {
10643
10810
  case "ok":
@@ -10660,7 +10827,7 @@ function checkEnvironment() {
10660
10827
  });
10661
10828
  results.push({
10662
10829
  status: "info",
10663
- label: `Platform: ${platform()} ${process.arch} (${release()})`
10830
+ label: `Platform: ${platform2()} ${process.arch} (${release()})`
10664
10831
  });
10665
10832
  const binary = process.argv[1] ?? "(unknown)";
10666
10833
  results.push({
@@ -10744,21 +10911,17 @@ function checkState() {
10744
10911
  return results;
10745
10912
  }
10746
10913
  function probeZed() {
10747
- const path = platform() === "win32" ? join3(process.env.APPDATA ?? "", "Zed", "settings.json") : platform() === "darwin" ? join3(homedir3(), ".config", "zed", "settings.json") : join3(
10748
- process.env.XDG_CONFIG_HOME ?? join3(homedir3(), ".config"),
10749
- "zed",
10750
- "settings.json"
10751
- );
10914
+ const path = zedSettingsPath();
10752
10915
  return { name: "Zed", path, exists: existsSync6(path) };
10753
10916
  }
10754
10917
  function probeJetBrains() {
10755
10918
  const baseDirs = [];
10756
- if (platform() === "darwin") {
10757
- baseDirs.push(join3(homedir3(), "Library", "Application Support", "JetBrains"));
10758
- } else if (platform() === "win32") {
10759
- baseDirs.push(join3(process.env.APPDATA ?? "", "JetBrains"));
10919
+ if (platform2() === "darwin") {
10920
+ baseDirs.push(join4(homedir4(), "Library", "Application Support", "JetBrains"));
10921
+ } else if (platform2() === "win32") {
10922
+ baseDirs.push(join4(process.env.APPDATA ?? "", "JetBrains"));
10760
10923
  } else {
10761
- baseDirs.push(join3(homedir3(), ".config", "JetBrains"));
10924
+ baseDirs.push(join4(homedir4(), ".config", "JetBrains"));
10762
10925
  }
10763
10926
  const probes = [];
10764
10927
  for (const base of baseDirs) {
@@ -10772,7 +10935,7 @@ function probeJetBrains() {
10772
10935
  for (const entry of entries) {
10773
10936
  if (entry.startsWith(".")) continue;
10774
10937
  if (!JETBRAINS_VERSIONED.test(entry) && !JETBRAINS_UNVERSIONED.has(entry)) continue;
10775
- const full = join3(base, entry);
10938
+ const full = join4(base, entry);
10776
10939
  try {
10777
10940
  if (!statSync2(full).isDirectory()) continue;
10778
10941
  } catch {
@@ -10871,6 +11034,7 @@ var init_doctor = __esm({
10871
11034
  "use strict";
10872
11035
  init_dist();
10873
11036
  init_style();
11037
+ init_setup_config();
10874
11038
  STATUS_MARK = {
10875
11039
  ok: "\u2713",
10876
11040
  warn: "!",
@@ -10887,7 +11051,7 @@ var CLI_VERSION;
10887
11051
  var init_version = __esm({
10888
11052
  "src/version.ts"() {
10889
11053
  "use strict";
10890
- CLI_VERSION = "0.5.0";
11054
+ CLI_VERSION = "0.6.0";
10891
11055
  }
10892
11056
  });
10893
11057
 
@@ -11356,9 +11520,9 @@ function formatTable(rows) {
11356
11520
  labelOf(r)
11357
11521
  ];
11358
11522
  });
11359
- return renderTable(s, COLUMNS, body);
11523
+ return renderTable(s, COLUMNS2, body);
11360
11524
  }
11361
- var COLUMNS;
11525
+ var COLUMNS2;
11362
11526
  var init_list = __esm({
11363
11527
  "src/commands/list.ts"() {
11364
11528
  "use strict";
@@ -11366,7 +11530,7 @@ var init_list = __esm({
11366
11530
  init_style();
11367
11531
  init_palette();
11368
11532
  init_table();
11369
- COLUMNS = [
11533
+ COLUMNS2 = [
11370
11534
  { title: "ID", align: "left" },
11371
11535
  { title: "AGE", align: "right" },
11372
11536
  { title: "KIND", align: "left" },
@@ -11623,7 +11787,9 @@ function buildMcpServer(opts) {
11623
11787
  description: "Fetch a single captured frame by (session_id, seq). Returns the full record: raw JSON bytes, parsed payload, parseError (if any), direction, kind, method, rpcId, timestamp. Use this after find_spec_violations / get_session_messages narrows you down to a specific seq.",
11624
11788
  inputSchema: {
11625
11789
  session_id: z.number().int().positive().describe("session id from list_sessions"),
11626
- seq: z.number().int().positive().describe("per-session sequence number from list_session_messages or find_spec_violations")
11790
+ seq: z.number().int().positive().describe(
11791
+ "per-session sequence number from list_session_messages or find_spec_violations"
11792
+ )
11627
11793
  },
11628
11794
  outputSchema: {
11629
11795
  session_id: z.number(),
@@ -11717,12 +11883,14 @@ function buildMcpServer(opts) {
11717
11883
  z.object({
11718
11884
  seq: z.number(),
11719
11885
  method: z.string().nullable(),
11720
- errors: z.array(z.looseObject({
11721
- keyword: z.string(),
11722
- instancePath: z.string().optional(),
11723
- schemaPath: z.string().optional(),
11724
- message: z.string().optional()
11725
- }))
11886
+ errors: z.array(
11887
+ z.looseObject({
11888
+ keyword: z.string(),
11889
+ instancePath: z.string().optional(),
11890
+ schemaPath: z.string().optional(),
11891
+ message: z.string().optional()
11892
+ })
11893
+ )
11726
11894
  }).loose()
11727
11895
  ),
11728
11896
  advisories: z.array(
@@ -11851,6 +12019,88 @@ function buildMcpServer(opts) {
11851
12019
  });
11852
12020
  }
11853
12021
  );
12022
+ server.registerTool(
12023
+ "list_agent_shortcuts",
12024
+ {
12025
+ description: "List the built-in agent shortcuts you can pass to generate_editor_config as `agent` (or that the CLI accepts as `acp-devtools <name>` / `proxy --agent <name>`). Each is a known ACP agent wired into the registry; anything else is still supported via generate_editor_config's `command` argument. Same data as the CLI `acp-devtools agents`.",
12026
+ inputSchema: {},
12027
+ outputSchema: {
12028
+ agents: z.array(
12029
+ z.object({
12030
+ shortName: z.string(),
12031
+ displayName: z.string(),
12032
+ command: z.string(),
12033
+ args: z.array(z.string()),
12034
+ requiresExternalInstall: z.boolean()
12035
+ })
12036
+ )
12037
+ },
12038
+ annotations: annotations({ title: "List agent shortcuts" })
12039
+ },
12040
+ async () => {
12041
+ const agents = listAgents().map((a) => ({
12042
+ shortName: a.shortName,
12043
+ displayName: a.displayName,
12044
+ command: a.command,
12045
+ args: [...a.args],
12046
+ requiresExternalInstall: a.requiresExternalInstall ?? false
12047
+ }));
12048
+ return structuredResult({ agents });
12049
+ }
12050
+ );
12051
+ server.registerTool(
12052
+ "generate_editor_config",
12053
+ {
12054
+ description: 'Generate the editor config (Zed or JetBrains) that runs an ACP agent through ACP Devtools, with the absolute acp-devtools binary path already filled in. Use this when the user asks to wire up / add an agent \u2014 e.g. "add junie for zed". Returns `block` (the JSON to add to the editor config), `configPath` (where it goes), and `writeCommand` (the equivalent `acp-devtools setup --write` to run instead). This tool only COMPUTES the config \u2014 it does not write any file; add `block` to `configPath` with your own file tools, or tell the user to run `writeCommand`. Pass `agent` for a registry shortcut (see list_agent_shortcuts) or `command` for any other agent (wrapped via `proxy`). Mirrors the CLI `acp-devtools setup`.',
12055
+ inputSchema: {
12056
+ editor: z.enum(["zed", "jetbrains"]).describe("target editor: zed or jetbrains"),
12057
+ agent: z.string().optional().describe(
12058
+ "registry shortcut: junie, goose, codex, opencode, or claude-code (the default when neither agent nor command is given)"
12059
+ ),
12060
+ command: z.array(z.string()).optional().describe(
12061
+ 'custom agent command tokens (e.g. ["npx","-y","@scope/acp"]), wrapped via proxy; ignored when `agent` is set'
12062
+ ),
12063
+ name: z.string().optional().describe("display name override for the agent entry")
12064
+ },
12065
+ outputSchema: {
12066
+ displayName: z.string(),
12067
+ editor: z.enum(["zed", "jetbrains"]),
12068
+ configPath: z.string(),
12069
+ dedicatedFile: z.boolean(),
12070
+ entry: z.object({
12071
+ type: z.literal("custom"),
12072
+ command: z.string(),
12073
+ args: z.array(z.string())
12074
+ }),
12075
+ block: z.string(),
12076
+ writeCommand: z.string(),
12077
+ requiresExternalInstall: z.boolean(),
12078
+ installHint: z.string().optional()
12079
+ },
12080
+ annotations: annotations({ title: "Generate editor config" })
12081
+ },
12082
+ async ({ editor, agent, command, name }) => {
12083
+ const target = command && command.length > 0 ? command : agent ? [agent] : [];
12084
+ const selfPath = process.argv[1] ?? "acp-devtools";
12085
+ const plan = buildSetupPlan({ target, editor, selfPath, name });
12086
+ const writeParts = ["acp-devtools setup", `--editor ${editor}`, "--write"];
12087
+ if (name) writeParts.push(`--name ${JSON.stringify(name)}`);
12088
+ if (target.length === 1) writeParts.push(target[0]);
12089
+ else if (target.length > 1) writeParts.push("--", ...target);
12090
+ const installHint = plan.isCustom ? void 0 : plan.requiresExternalInstall ? plan.agentDescription : "Installs automatically via npx on first run.";
12091
+ return structuredResult({
12092
+ displayName: plan.displayName,
12093
+ editor,
12094
+ configPath: plan.editor.configPath,
12095
+ dedicatedFile: plan.editor.dedicatedFile,
12096
+ entry: plan.entry,
12097
+ block: renderConfigBlock(plan),
12098
+ writeCommand: writeParts.join(" "),
12099
+ requiresExternalInstall: plan.requiresExternalInstall,
12100
+ ...installHint !== void 0 ? { installHint } : {}
12101
+ });
12102
+ }
12103
+ );
11854
12104
  registerPrompts(server);
11855
12105
  return server;
11856
12106
  }
@@ -11974,12 +12224,14 @@ var init_mcp = __esm({
11974
12224
  "use strict";
11975
12225
  init_dist();
11976
12226
  init_version();
12227
+ init_setup_config();
11977
12228
  SERVER_INSTRUCTIONS = `acp-devtools exposes a local SQLite store of captured
11978
12229
  Agent Client Protocol (ACP) sessions. ACP is the editor\u2194agent JSON-RPC
11979
12230
  protocol used by Claude Code, Goose, Codex, and other IDE agents.
11980
12231
 
11981
12232
  Every tool here is **read-only** \u2014 nothing in this server writes to the
11982
- database. Use it to investigate:
12233
+ database or to any file (\`generate_editor_config\` only returns the config
12234
+ text to write; you apply it). Use it to investigate:
11983
12235
 
11984
12236
  - a slow session (\`get_latency_stats\` returns per-method p50/p99/max
11985
12237
  plus auto-detected insights: hotspot, long-tail, outlier, busiest,
@@ -11996,6 +12248,12 @@ database. Use it to investigate:
11996
12248
  (\`diff_sessions\` aligns two captures and reports added / removed
11997
12249
  frames and field-level payload changes)
11998
12250
 
12251
+ It also helps wire agents up: \`list_agent_shortcuts\` enumerates the known
12252
+ agents, and \`generate_editor_config({editor, agent})\` returns the
12253
+ Zed/JetBrains config block to run one through the proxy (e.g. "add junie for
12254
+ zed") \u2014 apply \`block\` to \`configPath\` with your own file tools, or run the
12255
+ returned \`writeCommand\`.
12256
+
11999
12257
  A typical investigation:
12000
12258
  1. \`list_sessions\` to see what is captured
12001
12259
  2. \`get_session_summary({session_id})\` for the headline numbers and
@@ -12931,7 +13189,7 @@ function renderHits(s, hits, needle) {
12931
13189
  truncate2(h.method ?? "\u2014", METHOD_MAX2),
12932
13190
  highlight(s, h.snippet, needle)
12933
13191
  ]);
12934
- return renderTable(s, COLUMNS2, body);
13192
+ return renderTable(s, COLUMNS3, body);
12935
13193
  }
12936
13194
  function highlight(s, snippet, needle) {
12937
13195
  if (!s.enabled || needle.length === 0) return snippet;
@@ -12953,7 +13211,7 @@ function truncate2(s, max) {
12953
13211
  if (s.length <= max) return s;
12954
13212
  return s.slice(0, Math.max(1, max - 1)) + "\u2026";
12955
13213
  }
12956
- var SNIPPET_LEN, METHOD_MAX2, COLUMNS2;
13214
+ var SNIPPET_LEN, METHOD_MAX2, COLUMNS3;
12957
13215
  var init_search = __esm({
12958
13216
  "src/commands/search.ts"() {
12959
13217
  "use strict";
@@ -12962,7 +13220,7 @@ var init_search = __esm({
12962
13220
  init_table();
12963
13221
  SNIPPET_LEN = 80;
12964
13222
  METHOD_MAX2 = 32;
12965
- COLUMNS2 = [
13223
+ COLUMNS3 = [
12966
13224
  { title: "LOC", align: "left" },
12967
13225
  { title: "METHOD", align: "left" },
12968
13226
  { title: "MATCH", align: "left" }
@@ -13150,6 +13408,139 @@ var init_session_info = __esm({
13150
13408
  }
13151
13409
  });
13152
13410
 
13411
+ // src/commands/setup.ts
13412
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
13413
+ import { dirname as dirname5 } from "path";
13414
+ function fail(message, code) {
13415
+ process.stderr.write(`acp-devtools: ${message}
13416
+ `);
13417
+ process.exit(code);
13418
+ }
13419
+ function rerunCommand(editor, target, opts) {
13420
+ const parts = ["acp-devtools setup", `--editor ${editor}`, "--write"];
13421
+ if (opts.name) parts.push(`--name ${JSON.stringify(opts.name)}`);
13422
+ if (opts.configPath) parts.push(`--config-path ${opts.configPath}`);
13423
+ if (target.length === 1) {
13424
+ parts.push(target[0]);
13425
+ } else if (target.length > 1) {
13426
+ parts.push("--", ...target);
13427
+ }
13428
+ return parts.join(" ");
13429
+ }
13430
+ function printInstallHint(plan, s) {
13431
+ if (plan.isCustom) return;
13432
+ if (plan.requiresExternalInstall) {
13433
+ process.stdout.write(`
13434
+ ${s.yellow("!")} ${plan.agentDescription}
13435
+ `);
13436
+ } else {
13437
+ process.stdout.write(`
13438
+ ${s.dim("Installs automatically via npx on first run.")}
13439
+ `);
13440
+ }
13441
+ }
13442
+ function printGuidance(plan, target, opts, configPath, s) {
13443
+ const block = renderConfigBlock(plan);
13444
+ process.stdout.write(
13445
+ `
13446
+ ${s.bold(plan.displayName)} ${s.dim("\u2192")} ${s.bold(plan.editor.label)}
13447
+
13448
+ `
13449
+ );
13450
+ process.stdout.write(` ${plan.editor.placement}:
13451
+ `);
13452
+ process.stdout.write(` ${s.cyan(configPath)}
13453
+
13454
+ `);
13455
+ process.stdout.write(`${block}
13456
+ `);
13457
+ printInstallHint(plan, s);
13458
+ process.stdout.write(`
13459
+ Then ${plan.editor.restart}.
13460
+ `);
13461
+ process.stdout.write(
13462
+ `
13463
+ ${s.dim("Or let ACP Devtools edit the file for you:")}
13464
+ ${rerunCommand(opts.editor, target, opts)}
13465
+
13466
+ `
13467
+ );
13468
+ }
13469
+ function doWrite(plan, target, opts, configPath, s) {
13470
+ let existing = null;
13471
+ try {
13472
+ existing = readFileSync5(configPath, "utf8");
13473
+ } catch (err) {
13474
+ if (err.code !== "ENOENT") {
13475
+ fail(`cannot read ${configPath}: ${err.message}`, 1);
13476
+ }
13477
+ }
13478
+ let merged;
13479
+ try {
13480
+ merged = mergeIntoConfig(existing, plan.displayName, plan.entry);
13481
+ } catch {
13482
+ process.stderr.write(
13483
+ `${s.red("\u2717")} ${configPath} isn't valid JSON \u2014 leaving it untouched.
13484
+ Paste this block in by hand:
13485
+ `
13486
+ );
13487
+ process.stdout.write(`
13488
+ ${renderConfigBlock(plan)}
13489
+
13490
+ `);
13491
+ process.exit(1);
13492
+ }
13493
+ try {
13494
+ mkdirSync6(dirname5(configPath), { recursive: true });
13495
+ writeFileSync3(configPath, merged.text);
13496
+ } catch (err) {
13497
+ fail(`cannot write ${configPath}: ${err.message}`, 1);
13498
+ }
13499
+ const verb = merged.replaced ? "updated" : "wrote";
13500
+ process.stdout.write(
13501
+ `
13502
+ ${s.green("\u2713")} ${verb} ${s.bold(JSON.stringify(plan.displayName))} in ${s.cyan(configPath)}
13503
+ `
13504
+ );
13505
+ printInstallHint(plan, s);
13506
+ process.stdout.write(`
13507
+ Then ${plan.editor.restart}.
13508
+
13509
+ `);
13510
+ process.exit(0);
13511
+ }
13512
+ function registerSetupCommand(program) {
13513
+ program.command("setup [target...]").description("Generate an editor config that runs an agent through ACP Devtools").option("--editor <name>", `target editor (${EDITORS.join(" | ")})`).option("--name <label>", "display name for the agent entry").option("--config-path <path>", "override the editor config file location").option("--write", "merge the entry into the editor config instead of printing it", false).option("--no-color", "disable ANSI colour output").action((target, opts) => {
13514
+ if (!opts.editor) {
13515
+ fail(`--editor is required (${EDITORS.join(" | ")})`, 2);
13516
+ }
13517
+ if (!isEditor(opts.editor)) {
13518
+ fail(`unknown editor "${opts.editor}" (expected ${EDITORS.join(" | ")})`, 2);
13519
+ }
13520
+ const selfPath = process.argv[1] ?? "acp-devtools";
13521
+ const plan = buildSetupPlan({
13522
+ target,
13523
+ editor: opts.editor,
13524
+ selfPath,
13525
+ name: opts.name
13526
+ });
13527
+ const configPath = opts.configPath ?? editorInfo(opts.editor).configPath;
13528
+ const s = createStyler(opts.color && colorEnabled(process.stdout));
13529
+ if (opts.write) {
13530
+ doWrite(plan, target, opts, configPath, s);
13531
+ }
13532
+ printGuidance(plan, target, opts, configPath, s);
13533
+ process.exit(0);
13534
+ });
13535
+ }
13536
+ var init_setup = __esm({
13537
+ "src/commands/setup.ts"() {
13538
+ "use strict";
13539
+ init_style();
13540
+ init_setup_config();
13541
+ }
13542
+ });
13543
+
13153
13544
  // src/commands/stats.ts
13154
13545
  function registerStatsCommand(program) {
13155
13546
  program.command("stats").description(
@@ -13444,11 +13835,11 @@ var init_stats = __esm({
13444
13835
  // src/commands/ui.ts
13445
13836
  import { createReadStream, existsSync as existsSync7, statSync as statSync3 } from "fs";
13446
13837
  import { createServer } from "http";
13447
- import { dirname as dirname5, extname, join as join4, normalize, resolve, sep } from "path";
13838
+ import { dirname as dirname6, extname, join as join5, normalize, resolve, sep } from "path";
13448
13839
  import { fileURLToPath } from "url";
13449
13840
  import { spawn as spawn2 } from "child_process";
13450
13841
  function uiDistDir() {
13451
- const here = dirname5(fileURLToPath(import.meta.url));
13842
+ const here = dirname6(fileURLToPath(import.meta.url));
13452
13843
  return resolve(here, "ui");
13453
13844
  }
13454
13845
  function contentTypeFor(path) {
@@ -13462,7 +13853,7 @@ function serveStatic(req, res, rootDir) {
13462
13853
  }
13463
13854
  const urlPath = decodeURIComponent((req.url ?? "/").split("?")[0] ?? "/");
13464
13855
  const relative = urlPath === "/" || urlPath === "" ? "/index.html" : urlPath;
13465
- const candidate = normalize(join4(rootDir, relative));
13856
+ const candidate = normalize(join5(rootDir, relative));
13466
13857
  if (!candidate.startsWith(rootDir + sep) && candidate !== rootDir) {
13467
13858
  res.statusCode = 403;
13468
13859
  res.end();
@@ -13470,7 +13861,7 @@ function serveStatic(req, res, rootDir) {
13470
13861
  }
13471
13862
  let filePath = candidate;
13472
13863
  if (!existsSync7(filePath) || !statSync3(filePath).isFile()) {
13473
- filePath = join4(rootDir, "index.html");
13864
+ filePath = join5(rootDir, "index.html");
13474
13865
  if (!existsSync7(filePath)) {
13475
13866
  res.statusCode = 404;
13476
13867
  res.setHeader("content-type", "text/plain; charset=utf-8");
@@ -13513,7 +13904,7 @@ function registerUiCommand(program) {
13513
13904
  process.exit(2);
13514
13905
  }
13515
13906
  const rootDir = uiDistDir();
13516
- if (!existsSync7(join4(rootDir, "index.html"))) {
13907
+ if (!existsSync7(join5(rootDir, "index.html"))) {
13517
13908
  process.stderr.write(
13518
13909
  `acp-devtools: UI bundle missing at ${rootDir}
13519
13910
  Run \`npm run build:full\` in the monorepo, or reinstall the published package.
@@ -13726,7 +14117,7 @@ function renderViolations(s, violations) {
13726
14117
  s.cyan(truncate3(v.path, PATH_MAX)),
13727
14118
  s.red(v.message)
13728
14119
  ]);
13729
- return renderTable(s, COLUMNS3, body);
14120
+ return renderTable(s, COLUMNS4, body);
13730
14121
  }
13731
14122
  function renderAdvisories(s, advisories) {
13732
14123
  const heading5 = s.yellow(`${advisories.length} advisor${advisories.length === 1 ? "y" : "ies"}`) + s.dim(" \u2014 valid today, but a future ACP version will require these fields") + "\n";
@@ -13743,7 +14134,7 @@ function truncate3(s, max) {
13743
14134
  if (s.length <= max) return s;
13744
14135
  return s.slice(0, Math.max(1, max - 1)) + "\u2026";
13745
14136
  }
13746
- var METHOD_MAX4, SCHEMA_MAX, PATH_MAX, COLUMNS3, ADVISORY_COLUMNS;
14137
+ var METHOD_MAX4, SCHEMA_MAX, PATH_MAX, COLUMNS4, ADVISORY_COLUMNS;
13747
14138
  var init_validate2 = __esm({
13748
14139
  "src/commands/validate.ts"() {
13749
14140
  "use strict";
@@ -13754,7 +14145,7 @@ var init_validate2 = __esm({
13754
14145
  METHOD_MAX4 = 28;
13755
14146
  SCHEMA_MAX = 28;
13756
14147
  PATH_MAX = 38;
13757
- COLUMNS3 = [
14148
+ COLUMNS4 = [
13758
14149
  { title: "SEQ", align: "right" },
13759
14150
  { title: "METHOD", align: "left" },
13760
14151
  { title: "SCHEMA", align: "left" },
@@ -13772,8 +14163,8 @@ var init_validate2 = __esm({
13772
14163
  });
13773
14164
 
13774
14165
  // src/lib/help.ts
13775
- import { homedir as homedir4 } from "os";
13776
- function tidyHome(text, home = homedir4()) {
14166
+ import { homedir as homedir5 } from "os";
14167
+ function tidyHome(text, home = homedir5()) {
13777
14168
  if (!home) return text;
13778
14169
  const escapedHome = home.replace(/\\/g, "\\\\");
13779
14170
  const collapsed = text.split(escapedHome).join("~").split(home).join("~");
@@ -13935,7 +14326,7 @@ var init_help = __esm({
13935
14326
  },
13936
14327
  { title: "View", commands: ["ui", "replay"] },
13937
14328
  { title: "Manage", commands: ["export", "import", "delete", "backfill-metadata"] },
13938
- { title: "Setup", commands: ["doctor", "mcp"] }
14329
+ { title: "Setup", commands: ["setup", "agents", "doctor", "mcp"] }
13939
14330
  ];
13940
14331
  SUMMARIES = {
13941
14332
  proxy: "Capture an agent's traffic through a proxy",
@@ -13954,6 +14345,8 @@ var init_help = __esm({
13954
14345
  import: "Import a JSON export into the store",
13955
14346
  delete: "Delete saved sessions permanently",
13956
14347
  "backfill-metadata": "Recompute session metadata columns",
14348
+ setup: "Generate an editor config for an agent",
14349
+ agents: "List the built-in agent shortcuts",
13957
14350
  doctor: "Diagnose setup and report issues",
13958
14351
  mcp: "Expose captures to Claude over MCP"
13959
14352
  };
@@ -14012,6 +14405,17 @@ var init_help = __esm({
14012
14405
  replay: ["acp-devtools replay 12", "acp-devtools replay --file session-12.json"],
14013
14406
  validate: ["acp-devtools validate 12"],
14014
14407
  "session-info": ["acp-devtools session-info 12"],
14408
+ setup: [
14409
+ "# See which agent shortcuts exist",
14410
+ "acp-devtools agents",
14411
+ "",
14412
+ "# Print a Zed config block for a registry agent",
14413
+ "acp-devtools setup junie --editor zed",
14414
+ "",
14415
+ "# Wrap a custom agent and write it straight into the JetBrains config",
14416
+ "acp-devtools setup --editor jetbrains --write -- npx -y @your-scope/your-acp"
14417
+ ],
14418
+ agents: ["acp-devtools agents", "", "# Machine-readable", "acp-devtools agents --json"],
14015
14419
  doctor: ["acp-devtools doctor"],
14016
14420
  mcp: ["# Read-only MCP server over stdio (wire into .claude/mcp_servers.json)", "acp-devtools mcp"],
14017
14421
  "backfill-metadata": [
@@ -14048,6 +14452,8 @@ function buildProgram() {
14048
14452
  registerInspectCommand(program);
14049
14453
  registerSearchCommand(program);
14050
14454
  registerSessionInfoCommand(program);
14455
+ registerSetupCommand(program);
14456
+ registerAgentsCommand(program);
14051
14457
  registerStatsCommand(program);
14052
14458
  registerBackfillMetadataCommand(program);
14053
14459
  registerMcpCommand(program);
@@ -14059,6 +14465,7 @@ function buildProgram() {
14059
14465
  var init_program = __esm({
14060
14466
  "src/program.ts"() {
14061
14467
  "use strict";
14468
+ init_agents2();
14062
14469
  init_backfill_metadata();
14063
14470
  init_delete();
14064
14471
  init_diff();
@@ -14074,6 +14481,7 @@ var init_program = __esm({
14074
14481
  init_replay2();
14075
14482
  init_search();
14076
14483
  init_session_info();
14484
+ init_setup();
14077
14485
  init_stats();
14078
14486
  init_ui();
14079
14487
  init_validate2();
@@ -14178,6 +14586,8 @@ var KNOWN_SUBCOMMANDS = /* @__PURE__ */ new Set([
14178
14586
  "inspect",
14179
14587
  "search",
14180
14588
  "session-info",
14589
+ "setup",
14590
+ "agents",
14181
14591
  "stats",
14182
14592
  "validate",
14183
14593
  "backfill-metadata",