terminal-pilot 0.0.19 → 0.0.21

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.
Files changed (52) hide show
  1. package/dist/cli.js +1076 -496
  2. package/dist/cli.js.map +4 -4
  3. package/dist/commands/close-session.js.map +1 -1
  4. package/dist/commands/create-session.js.map +1 -1
  5. package/dist/commands/fill.js.map +1 -1
  6. package/dist/commands/get-session.js.map +1 -1
  7. package/dist/commands/index.js +481 -163
  8. package/dist/commands/index.js.map +4 -4
  9. package/dist/commands/install.js +448 -130
  10. package/dist/commands/install.js.map +4 -4
  11. package/dist/commands/installer.js +154 -60
  12. package/dist/commands/installer.js.map +4 -4
  13. package/dist/commands/list-sessions.js.map +1 -1
  14. package/dist/commands/press-key.js.map +1 -1
  15. package/dist/commands/read-history.js.map +1 -1
  16. package/dist/commands/read-screen.js.map +1 -1
  17. package/dist/commands/resize.js.map +1 -1
  18. package/dist/commands/runtime.js.map +1 -1
  19. package/dist/commands/screenshot.js.map +1 -1
  20. package/dist/commands/send-signal.js.map +1 -1
  21. package/dist/commands/type.js.map +1 -1
  22. package/dist/commands/uninstall.js +187 -93
  23. package/dist/commands/uninstall.js.map +4 -4
  24. package/dist/commands/wait-for-exit.js.map +1 -1
  25. package/dist/commands/wait-for.js.map +1 -1
  26. package/dist/testing/cli-repl.js +1072 -492
  27. package/dist/testing/cli-repl.js.map +4 -4
  28. package/dist/testing/qa-cli.js +1082 -502
  29. package/dist/testing/qa-cli.js.map +4 -4
  30. package/node_modules/@poe-code/agent-skill-config/README.md +103 -0
  31. package/node_modules/@poe-code/agent-skill-config/dist/apply.d.ts +25 -0
  32. package/node_modules/@poe-code/agent-skill-config/dist/apply.js +159 -0
  33. package/node_modules/@poe-code/agent-skill-config/dist/bridge-active-skills.d.ts +23 -0
  34. package/node_modules/@poe-code/agent-skill-config/dist/bridge-active-skills.js +341 -0
  35. package/node_modules/@poe-code/agent-skill-config/dist/configs.d.ts +16 -0
  36. package/node_modules/@poe-code/agent-skill-config/dist/configs.js +70 -0
  37. package/node_modules/@poe-code/agent-skill-config/dist/exports.compile-check.d.ts +1 -0
  38. package/node_modules/@poe-code/agent-skill-config/dist/exports.compile-check.js +1 -0
  39. package/node_modules/@poe-code/agent-skill-config/dist/git-exclude.d.ts +8 -0
  40. package/node_modules/@poe-code/agent-skill-config/dist/git-exclude.js +159 -0
  41. package/node_modules/@poe-code/agent-skill-config/dist/index.d.ts +11 -0
  42. package/node_modules/@poe-code/agent-skill-config/dist/index.js +6 -0
  43. package/node_modules/@poe-code/agent-skill-config/dist/resolve-skill-reference.d.ts +22 -0
  44. package/node_modules/@poe-code/agent-skill-config/dist/resolve-skill-reference.js +87 -0
  45. package/node_modules/@poe-code/agent-skill-config/dist/templates/poe-generate.md +47 -0
  46. package/node_modules/@poe-code/agent-skill-config/dist/templates/terminal-pilot.md +45 -0
  47. package/node_modules/@poe-code/agent-skill-config/dist/templates.d.ts +3 -0
  48. package/node_modules/@poe-code/agent-skill-config/dist/templates.js +63 -0
  49. package/node_modules/@poe-code/agent-skill-config/dist/types.d.ts +16 -0
  50. package/node_modules/@poe-code/agent-skill-config/dist/types.js +1 -0
  51. package/node_modules/@poe-code/agent-skill-config/package.json +24 -0
  52. package/package.json +4 -2
@@ -206,11 +206,11 @@ function resolveAgentSupport(input, registry = agentSkillConfigs) {
206
206
  if (!resolvedId) {
207
207
  return { status: "unknown", input };
208
208
  }
209
- const config = registry[resolvedId];
210
- if (!config) {
209
+ const config2 = registry[resolvedId];
210
+ if (!config2) {
211
211
  return { status: "unsupported", input, id: resolvedId };
212
212
  }
213
- return { status: "supported", input, id: resolvedId, config: { ...config } };
213
+ return { status: "supported", input, id: resolvedId, config: { ...config2 } };
214
214
  }
215
215
 
216
216
  // ../config-mutations/src/mutations/file-mutation.ts
@@ -307,7 +307,7 @@ var templateMutation = {
307
307
  // ../config-mutations/src/execution/apply-mutation.ts
308
308
  import path3 from "node:path";
309
309
 
310
- // ../design-system/src/internal/color-support.ts
310
+ // ../toolcraft-design/src/internal/color-support.ts
311
311
  function supportsColor(env = process.env, stream = process.stdout) {
312
312
  if (env.FORCE_COLOR !== void 0 && env.FORCE_COLOR !== "0") {
313
313
  return true;
@@ -321,7 +321,7 @@ function supportsColor(env = process.env, stream = process.stdout) {
321
321
  return typeof env.TERM === "string" && env.TERM.length > 0 && env.TERM !== "dumb";
322
322
  }
323
323
 
324
- // ../design-system/src/components/color.ts
324
+ // ../toolcraft-design/src/components/color.ts
325
325
  var reset = "\x1B[0m";
326
326
  var ansiStyles = {
327
327
  reset: { open: reset },
@@ -423,41 +423,122 @@ function createColor(styles = []) {
423
423
  }
424
424
  var color = createColor();
425
425
 
426
- // ../design-system/src/tokens/colors.ts
427
- var dark = {
428
- header: (text4) => color.magentaBright.bold(text4),
429
- divider: (text4) => color.dim(text4),
430
- prompt: (text4) => color.cyan(text4),
431
- number: (text4) => color.cyanBright(text4),
432
- intro: (text4) => color.bgMagenta.white(` Poe - ${text4} `),
433
- resolvedSymbol: color.magenta("\u25C7"),
434
- errorSymbol: color.red("\u25A0"),
435
- accent: (text4) => color.cyan(text4),
436
- muted: (text4) => color.dim(text4),
437
- success: (text4) => color.green(text4),
438
- warning: (text4) => color.yellow(text4),
439
- error: (text4) => color.red(text4),
440
- info: (text4) => color.magenta(text4),
441
- badge: (text4) => color.bgYellow.black(` ${text4} `)
426
+ // ../toolcraft-design/src/tokens/brand.ts
427
+ var brands = {
428
+ purple: { name: "purple", primary: "#a200ff" },
429
+ blue: { name: "blue", primary: "#2f6fed" },
430
+ green: { name: "green", primary: "#1f9d57" }
442
431
  };
443
- var light = {
444
- header: (text4) => color.hex("#a200ff").bold(text4),
445
- divider: (text4) => color.hex("#666666")(text4),
446
- prompt: (text4) => color.hex("#006699").bold(text4),
447
- number: (text4) => color.hex("#0077cc").bold(text4),
448
- intro: (text4) => color.bgHex("#a200ff").white(` Poe - ${text4} `),
449
- resolvedSymbol: color.hex("#a200ff")("\u25C7"),
450
- errorSymbol: color.hex("#cc0000")("\u25A0"),
451
- accent: (text4) => color.hex("#006699").bold(text4),
452
- muted: (text4) => color.hex("#666666")(text4),
453
- success: (text4) => color.hex("#008800")(text4),
454
- warning: (text4) => color.hex("#cc6600")(text4),
455
- error: (text4) => color.hex("#cc0000")(text4),
456
- info: (text4) => color.hex("#a200ff")(text4),
457
- badge: (text4) => color.bgHex("#cc6600").white(` ${text4} `)
432
+
433
+ // ../toolcraft-design/src/internal/theme-state.ts
434
+ var defaults = {
435
+ brand: "purple",
436
+ label: "Poe"
458
437
  };
438
+ var config = { ...defaults };
439
+ var revision = 0;
440
+ var brandConfigured = false;
441
+ function getThemeConfig() {
442
+ return { ...config };
443
+ }
444
+ function getThemeRevision() {
445
+ return revision;
446
+ }
447
+ function isThemeBrandConfigured() {
448
+ return brandConfigured;
449
+ }
459
450
 
460
- // ../design-system/src/internal/output-format.ts
451
+ // ../toolcraft-design/src/tokens/colors.ts
452
+ var brand = brands.purple.primary;
453
+ function withStyles(palette, styles) {
454
+ return Object.defineProperty(palette, "styles", {
455
+ value: styles,
456
+ enumerable: false
457
+ });
458
+ }
459
+ function brandColor(activeBrand, purple) {
460
+ return activeBrand.name === "purple" ? purple : color.hex(activeBrand.primary);
461
+ }
462
+ function brandBackground(activeBrand, purple) {
463
+ return activeBrand.name === "purple" ? purple : color.bgHex(activeBrand.primary);
464
+ }
465
+ function createPalette(activeBrand, mode) {
466
+ const isPurple = activeBrand.name === "purple";
467
+ if (mode === "light") {
468
+ const active2 = color.hex(activeBrand.primary);
469
+ const prompt2 = isPurple ? color.hex("#006699") : active2;
470
+ const number2 = isPurple ? color.hex("#0077cc") : active2;
471
+ return withStyles(
472
+ {
473
+ header: (text4) => active2.bold(text4),
474
+ divider: (text4) => color.hex("#666666")(text4),
475
+ prompt: (text4) => prompt2.bold(text4),
476
+ number: (text4) => number2.bold(text4),
477
+ intro: (text4) => color.bgHex(activeBrand.primary).white(` ${getThemeConfig().label} - ${text4} `),
478
+ get resolvedSymbol() {
479
+ return active2("\u25C7");
480
+ },
481
+ get errorSymbol() {
482
+ return color.hex("#cc0000")("\u25A0");
483
+ },
484
+ accent: (text4) => prompt2.bold(text4),
485
+ muted: (text4) => color.hex("#666666")(text4),
486
+ success: (text4) => color.hex("#008800")(text4),
487
+ warning: (text4) => color.hex("#cc6600")(text4),
488
+ error: (text4) => color.hex("#cc0000")(text4),
489
+ info: (text4) => active2(text4),
490
+ badge: (text4) => color.bgHex("#cc6600").white(` ${text4} `)
491
+ },
492
+ {
493
+ accent: { fg: isPurple ? "#006699" : activeBrand.primary, bold: true },
494
+ muted: { fg: "#666666" },
495
+ success: { fg: "#008800" },
496
+ warning: { fg: "#cc6600" },
497
+ error: { fg: "#cc0000" },
498
+ info: { fg: activeBrand.primary }
499
+ }
500
+ );
501
+ }
502
+ const active = brandColor(activeBrand, color.magenta);
503
+ const activeBright = brandColor(activeBrand, color.magentaBright);
504
+ const activeBackground = brandBackground(activeBrand, color.bgMagenta);
505
+ const prompt = isPurple ? color.cyan : active;
506
+ const number = isPurple ? color.cyanBright : active;
507
+ return withStyles(
508
+ {
509
+ header: (text4) => activeBright.bold(text4),
510
+ divider: (text4) => color.dim(text4),
511
+ prompt: (text4) => prompt(text4),
512
+ number: (text4) => number(text4),
513
+ intro: (text4) => activeBackground.white(` ${getThemeConfig().label} - ${text4} `),
514
+ get resolvedSymbol() {
515
+ return active("\u25C7");
516
+ },
517
+ get errorSymbol() {
518
+ return color.red("\u25A0");
519
+ },
520
+ accent: (text4) => prompt(text4),
521
+ muted: (text4) => color.dim(text4),
522
+ success: (text4) => color.green(text4),
523
+ warning: (text4) => color.yellow(text4),
524
+ error: (text4) => color.red(text4),
525
+ info: (text4) => active(text4),
526
+ badge: (text4) => color.bgYellow.black(` ${text4} `)
527
+ },
528
+ {
529
+ accent: { fg: isPurple ? "cyan" : activeBrand.primary, bold: true },
530
+ muted: { dim: true },
531
+ success: { fg: "green" },
532
+ warning: { fg: "yellow" },
533
+ error: { fg: "red" },
534
+ info: { fg: isPurple ? "magenta" : activeBrand.primary }
535
+ }
536
+ );
537
+ }
538
+ var dark = createPalette(brands.purple, "dark");
539
+ var light = createPalette(brands.purple, "light");
540
+
541
+ // ../toolcraft-design/src/internal/output-format.ts
461
542
  import { AsyncLocalStorage } from "node:async_hooks";
462
543
  var VALID_FORMATS = /* @__PURE__ */ new Set(["terminal", "markdown", "json"]);
463
544
  var formatStorage = new AsyncLocalStorage();
@@ -475,7 +556,7 @@ function resolveOutputFormat(env = process.env) {
475
556
  return cached;
476
557
  }
477
558
 
478
- // ../design-system/src/internal/theme-detect.ts
559
+ // ../toolcraft-design/src/internal/theme-detect.ts
479
560
  function detectThemeFromEnv(env) {
480
561
  const apple = env.APPLE_INTERFACE_STYLE;
481
562
  if (typeof apple === "string") {
@@ -512,17 +593,30 @@ function resolveThemeName(env = process.env) {
512
593
  }
513
594
  return "dark";
514
595
  }
515
- var cachedTheme;
596
+ var themeCache = /* @__PURE__ */ new Map();
597
+ var cachedRevision = -1;
516
598
  function getTheme(env) {
599
+ const themeName = resolveThemeName(env);
600
+ const config2 = getThemeConfig();
601
+ const requestedBrand = env?.POE_BRAND?.toLowerCase();
602
+ const activeBrandName = !isThemeBrandConfigured() && requestedBrand && Object.hasOwn(brands, requestedBrand) ? requestedBrand : config2.brand;
603
+ const revision2 = getThemeRevision();
604
+ if (revision2 !== cachedRevision) {
605
+ themeCache.clear();
606
+ cachedRevision = revision2;
607
+ }
608
+ const cacheKey = `${activeBrandName}:${themeName}`;
609
+ const cachedTheme = themeCache.get(cacheKey);
517
610
  if (cachedTheme) {
518
611
  return cachedTheme;
519
612
  }
520
- const themeName = resolveThemeName(env);
521
- cachedTheme = themeName === "light" ? light : dark;
522
- return cachedTheme;
613
+ const activeBrand = brands[activeBrandName];
614
+ const theme = activeBrandName === "purple" ? themeName === "light" ? light : dark : createPalette(activeBrand, themeName);
615
+ themeCache.set(cacheKey, theme);
616
+ return theme;
523
617
  }
524
618
 
525
- // ../design-system/src/components/symbols.ts
619
+ // ../toolcraft-design/src/components/symbols.ts
526
620
  var symbols = {
527
621
  get info() {
528
622
  const format = resolveOutputFormat();
@@ -576,12 +670,12 @@ var symbols = {
576
670
  }
577
671
  };
578
672
 
579
- // ../design-system/src/internal/strip-ansi.ts
673
+ // ../toolcraft-design/src/internal/strip-ansi.ts
580
674
  function stripAnsi(value) {
581
675
  return value.replace(/\u001b\[[0-9;]*m/g, "");
582
676
  }
583
677
 
584
- // ../design-system/src/prompts/primitives/log.ts
678
+ // ../toolcraft-design/src/prompts/primitives/log.ts
585
679
  function renderMarkdownInline(value) {
586
680
  return stripAnsi(value).replaceAll("\r\n", " ").replaceAll("\n", " ").replaceAll("\r", " ");
587
681
  }
@@ -708,7 +802,7 @@ var log = {
708
802
  error
709
803
  };
710
804
 
711
- // ../design-system/src/components/logger.ts
805
+ // ../toolcraft-design/src/components/logger.ts
712
806
  function createLogger(emitter) {
713
807
  const emit = (level, message2) => {
714
808
  if (emitter) {
@@ -769,13 +863,14 @@ function createLogger(emitter) {
769
863
  }
770
864
  var logger = createLogger();
771
865
 
772
- // ../design-system/src/components/help-formatter.ts
866
+ // ../toolcraft-design/src/components/help-formatter.ts
773
867
  var graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
774
868
 
775
- // ../design-system/src/components/table.ts
869
+ // ../toolcraft-design/src/components/table.ts
776
870
  var graphemeSegmenter2 = new Intl.Segmenter(void 0, { granularity: "grapheme" });
777
871
 
778
- // ../design-system/src/components/template.ts
872
+ // ../toolcraft-design/src/components/template.ts
873
+ var MAX_PARTIAL_DEPTH = 100;
779
874
  var HTML_ESCAPE = {
780
875
  "&": "&",
781
876
  "<": "&lt;",
@@ -787,14 +882,27 @@ var HTML_ESCAPE = {
787
882
  "=": "&#x3D;"
788
883
  };
789
884
  function renderTemplate(template, view, options = {}) {
790
- const prepared = options.yield === void 0 ? template : template.split("{{yield}}").join(options.yield);
885
+ const prepared = options.yield === void 0 ? template : resolveTemplatePartials(template, options.partials ?? {}).split("{{yield}}").join(options.yield);
791
886
  const tokens = parseTemplate(prepared);
792
- const escape = options.escape === "none" ? String : escapeHtml;
793
- const preserveMissing = options.yield !== void 0 && options.escape === "none";
794
- return renderTokens(tokens, { view }, prepared, escape, preserveMissing);
887
+ validatePartialReferences(tokens, options.partials ?? {}, []);
888
+ if (options.validate === true) {
889
+ const expanded = resolveTemplatePartials(prepared, options.partials ?? {});
890
+ validateVariables(parseTemplate(expanded), { view });
891
+ }
892
+ const state = {
893
+ escape: options.escape === "none" ? String : escapeHtml,
894
+ partials: options.partials ?? {},
895
+ partialStack: [],
896
+ preserveMissing: options.yield !== void 0 && options.escape === "none",
897
+ validate: options.validate === true
898
+ };
899
+ return renderTokens(tokens, { view }, prepared, state);
900
+ }
901
+ function resolveTemplatePartials(template, partials) {
902
+ return expandTemplatePartials(template, partials, []);
795
903
  }
796
- function renderTemplateInContext(template, context, escape, preserveMissing) {
797
- return renderTokens(parseTemplate(template), context, template, escape, preserveMissing);
904
+ function renderTemplateInContext(template, context, state) {
905
+ return renderTokens(parseTemplate(template), context, template, state);
798
906
  }
799
907
  function parseTemplate(template) {
800
908
  const root = [];
@@ -817,9 +925,6 @@ function parseTemplate(template) {
817
925
  index = standalone?.nextIndex ?? parsed.end;
818
926
  continue;
819
927
  }
820
- if (parsed.kind === "partial") {
821
- throw new Error(`Partials are not supported: "${parsed.name}"`);
822
- }
823
928
  if (parsed.kind === "delimiter") {
824
929
  throw new Error("Custom delimiters are not supported");
825
930
  }
@@ -837,6 +942,15 @@ function parseTemplate(template) {
837
942
  index = standalone?.nextIndex ?? parsed.end;
838
943
  continue;
839
944
  }
945
+ if (parsed.kind === "partial") {
946
+ tokens.push({
947
+ type: "partial",
948
+ name: parsed.name,
949
+ indent: standalone === void 0 ? "" : template.slice(standalone.lineStart, open)
950
+ });
951
+ index = standalone?.nextIndex ?? parsed.end;
952
+ continue;
953
+ }
840
954
  if (parsed.kind === "close") {
841
955
  const frame2 = stack.pop();
842
956
  if (frame2 === void 0) {
@@ -907,7 +1021,7 @@ function getStandalone(template, tagStart, tagEnd, kind) {
907
1021
  }
908
1022
  return void 0;
909
1023
  }
910
- function renderTokens(tokens, context, template, escape, preserveMissing) {
1024
+ function renderTokens(tokens, context, template, state) {
911
1025
  let output = "";
912
1026
  for (const token of tokens) {
913
1027
  switch (token.type) {
@@ -916,32 +1030,62 @@ function renderTokens(tokens, context, template, escape, preserveMissing) {
916
1030
  continue;
917
1031
  case "name":
918
1032
  case "unescaped": {
919
- const value = lookup2(context, token.name);
920
- if (value == null) {
921
- if (preserveMissing) {
1033
+ const result = lookup2(context, token.name);
1034
+ if (!result.hit || result.value == null) {
1035
+ if (state.validate) {
1036
+ throw new Error(`Template variable "${token.name}" not found.`);
1037
+ }
1038
+ if (state.preserveMissing) {
922
1039
  output += token.raw;
923
1040
  }
924
1041
  continue;
925
1042
  }
926
- const rendered = String(value);
927
- output += token.type === "name" ? escape(rendered) : rendered;
1043
+ const rendered = String(result.value);
1044
+ output += token.type === "name" ? state.escape(rendered) : rendered;
1045
+ continue;
1046
+ }
1047
+ case "partial": {
1048
+ if (!Object.hasOwn(state.partials, token.name)) {
1049
+ throw new Error(`Partial "${token.name}" not found.`);
1050
+ }
1051
+ if (state.partialStack.includes(token.name)) {
1052
+ throw new Error(
1053
+ `Circular partial reference detected: ${[...state.partialStack, token.name].join(" -> ")}.`
1054
+ );
1055
+ }
1056
+ if (state.partialStack.length >= MAX_PARTIAL_DEPTH) {
1057
+ throw new Error(`Maximum partial depth exceeded (${MAX_PARTIAL_DEPTH}).`);
1058
+ }
1059
+ const partial = indentPartial(state.partials[token.name], token.indent);
1060
+ output += renderTokens(parseTemplate(partial), context, partial, {
1061
+ ...state,
1062
+ partialStack: [...state.partialStack, token.name]
1063
+ });
928
1064
  continue;
929
1065
  }
930
1066
  case "inverted": {
931
- const value = lookup2(context, token.name);
1067
+ const result = lookup2(context, token.name);
1068
+ if (!result.hit && state.validate) {
1069
+ throw new Error(`Template variable "${token.name}" not found.`);
1070
+ }
1071
+ const value = result.value;
932
1072
  if (!value || Array.isArray(value) && value.length === 0) {
933
- output += renderTokens(token.children, context, template, escape, preserveMissing);
1073
+ output += renderTokens(token.children, context, template, state);
934
1074
  }
935
1075
  continue;
936
1076
  }
937
1077
  case "section": {
938
- const value = lookup2(context, token.name);
1078
+ const result = lookup2(context, token.name);
1079
+ if (!result.hit && state.validate) {
1080
+ throw new Error(`Template variable "${token.name}" not found.`);
1081
+ }
1082
+ const value = result.value;
939
1083
  if (!value) {
940
1084
  continue;
941
1085
  }
942
1086
  if (Array.isArray(value)) {
943
1087
  for (const item of value) {
944
- output += renderTokens(token.children, pushContext(context, item), template, escape, preserveMissing);
1088
+ output += renderTokens(token.children, pushContext(context, item), template, state);
945
1089
  }
946
1090
  continue;
947
1091
  }
@@ -950,7 +1094,7 @@ function renderTokens(tokens, context, template, escape, preserveMissing) {
950
1094
  const rendered = value.call(
951
1095
  context.view,
952
1096
  raw,
953
- (nextTemplate) => renderTemplateInContext(nextTemplate, context, escape, preserveMissing)
1097
+ (nextTemplate) => renderTemplateInContext(nextTemplate, context, state)
954
1098
  );
955
1099
  if (rendered != null) {
956
1100
  output += String(rendered);
@@ -958,10 +1102,10 @@ function renderTokens(tokens, context, template, escape, preserveMissing) {
958
1102
  continue;
959
1103
  }
960
1104
  if (typeof value === "object" || typeof value === "string" || typeof value === "number") {
961
- output += renderTokens(token.children, pushContext(context, value), template, escape, preserveMissing);
1105
+ output += renderTokens(token.children, pushContext(context, value), template, state);
962
1106
  continue;
963
1107
  }
964
- output += renderTokens(token.children, context, template, escape, preserveMissing);
1108
+ output += renderTokens(token.children, context, template, state);
965
1109
  }
966
1110
  }
967
1111
  }
@@ -969,17 +1113,115 @@ function renderTokens(tokens, context, template, escape, preserveMissing) {
969
1113
  }
970
1114
  function lookup2(context, name) {
971
1115
  if (name === ".") {
972
- return callLambda(context.view, context.view);
1116
+ return { hit: true, value: callLambda(context.view, context.view) };
973
1117
  }
974
1118
  let cursor = context;
975
1119
  while (cursor !== void 0) {
976
1120
  const result = name.includes(".") ? lookupDotted(cursor.view, name) : lookupName(cursor.view, name);
977
1121
  if (result.hit) {
978
- return callLambda(result.value, cursor.view);
1122
+ return { hit: true, value: callLambda(result.value, cursor.view) };
979
1123
  }
980
1124
  cursor = cursor.parent;
981
1125
  }
982
- return void 0;
1126
+ return { hit: false, value: void 0 };
1127
+ }
1128
+ function validateVariables(tokens, context) {
1129
+ for (const token of tokens) {
1130
+ if (token.type === "text" || token.type === "partial") {
1131
+ continue;
1132
+ }
1133
+ if (token.type === "name" || token.type === "unescaped") {
1134
+ if (!lookup2(context, token.name).hit) {
1135
+ throw new Error(`Template variable "${token.name}" not found.`);
1136
+ }
1137
+ continue;
1138
+ }
1139
+ if (token.type !== "section" && token.type !== "inverted") {
1140
+ continue;
1141
+ }
1142
+ const result = lookup2(context, token.name);
1143
+ if (!result.hit) {
1144
+ throw new Error(`Template variable "${token.name}" not found.`);
1145
+ }
1146
+ if (Array.isArray(result.value) && result.value.length > 0) {
1147
+ for (const item of result.value) {
1148
+ validateVariables(token.children, pushContext(context, item));
1149
+ }
1150
+ continue;
1151
+ }
1152
+ if (typeof result.value === "object" && result.value !== null || typeof result.value === "string" || typeof result.value === "number") {
1153
+ validateVariables(token.children, pushContext(context, result.value));
1154
+ continue;
1155
+ }
1156
+ validateVariables(token.children, context);
1157
+ }
1158
+ }
1159
+ function validatePartialReferences(tokens, partials, partialStack) {
1160
+ for (const token of tokens) {
1161
+ if (token.type === "section" || token.type === "inverted") {
1162
+ validatePartialReferences(token.children, partials, partialStack);
1163
+ continue;
1164
+ }
1165
+ if (token.type !== "partial") {
1166
+ continue;
1167
+ }
1168
+ if (!Object.hasOwn(partials, token.name)) {
1169
+ throw new Error(`Partial "${token.name}" not found.`);
1170
+ }
1171
+ if (partialStack.includes(token.name)) {
1172
+ throw new Error(
1173
+ `Circular partial reference detected: ${[...partialStack, token.name].join(" -> ")}.`
1174
+ );
1175
+ }
1176
+ if (partialStack.length >= MAX_PARTIAL_DEPTH) {
1177
+ throw new Error(`Maximum partial depth exceeded (${MAX_PARTIAL_DEPTH}).`);
1178
+ }
1179
+ validatePartialReferences(parseTemplate(partials[token.name]), partials, [
1180
+ ...partialStack,
1181
+ token.name
1182
+ ]);
1183
+ }
1184
+ }
1185
+ function indentPartial(partial, indent) {
1186
+ if (indent === "") {
1187
+ return partial;
1188
+ }
1189
+ return partial.split("\n").map((line) => line === "" ? "" : `${indent}${line}`).join("\n");
1190
+ }
1191
+ function expandTemplatePartials(template, partials, partialStack) {
1192
+ let output = "";
1193
+ let index = 0;
1194
+ while (index < template.length) {
1195
+ const open = template.indexOf("{{", index);
1196
+ if (open === -1) {
1197
+ output += template.slice(index);
1198
+ break;
1199
+ }
1200
+ const parsed = parseTag(template, open);
1201
+ if (parsed.kind !== "partial") {
1202
+ output += template.slice(index, parsed.end);
1203
+ index = parsed.end;
1204
+ continue;
1205
+ }
1206
+ if (!Object.hasOwn(partials, parsed.name)) {
1207
+ throw new Error(`Partial "${parsed.name}" not found.`);
1208
+ }
1209
+ if (partialStack.includes(parsed.name)) {
1210
+ throw new Error(
1211
+ `Circular partial reference detected: ${[...partialStack, parsed.name].join(" -> ")}.`
1212
+ );
1213
+ }
1214
+ if (partialStack.length >= MAX_PARTIAL_DEPTH) {
1215
+ throw new Error(`Maximum partial depth exceeded (${MAX_PARTIAL_DEPTH}).`);
1216
+ }
1217
+ const standalone = getStandalone(template, open, parsed.end, parsed.kind);
1218
+ const beforePartial = standalone === void 0 ? template.slice(index, open) : template.slice(index, standalone.lineStart);
1219
+ const indent = standalone === void 0 ? "" : template.slice(standalone.lineStart, open);
1220
+ const partial = indentPartial(partials[parsed.name], indent);
1221
+ output += beforePartial + expandTemplatePartials(partial, partials, [...partialStack, parsed.name]);
1222
+ index = standalone?.nextIndex ?? parsed.end;
1223
+ }
1224
+ return output;
983
1225
  }
984
1226
  function lookupName(view, name) {
985
1227
  if (!isPropertyContainer(view) || !hasProperty(view, name)) {
@@ -1039,22 +1281,22 @@ function hasProperty(value, key) {
1039
1281
  return Object.prototype.hasOwnProperty.call(value, key);
1040
1282
  }
1041
1283
 
1042
- // ../design-system/src/components/browser.ts
1284
+ // ../toolcraft-design/src/components/browser.ts
1043
1285
  import { spawn } from "node:child_process";
1044
1286
  import process2 from "node:process";
1045
1287
 
1046
- // ../design-system/src/acp/writer.ts
1288
+ // ../toolcraft-design/src/acp/writer.ts
1047
1289
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
1048
1290
  var storage = new AsyncLocalStorage2();
1049
1291
 
1050
- // ../design-system/src/dashboard/terminal-width.ts
1292
+ // ../toolcraft-design/src/dashboard/terminal-width.ts
1051
1293
  var graphemeSegmenter3 = new Intl.Segmenter(void 0, { granularity: "grapheme" });
1052
1294
 
1053
- // ../design-system/src/dashboard/terminal.ts
1295
+ // ../toolcraft-design/src/dashboard/terminal.ts
1054
1296
  import readline from "node:readline";
1055
1297
  import { PassThrough } from "node:stream";
1056
1298
 
1057
- // ../design-system/src/explorer/state.ts
1299
+ // ../toolcraft-design/src/explorer/state.ts
1058
1300
  var REGION_HEADER = 1 << 0;
1059
1301
  var REGION_LIST = 1 << 1;
1060
1302
  var REGION_DETAIL = 1 << 2;
@@ -1063,13 +1305,13 @@ var REGION_MODAL = 1 << 4;
1063
1305
  var REGION_TOAST = 1 << 5;
1064
1306
  var REGION_ALL = REGION_HEADER | REGION_LIST | REGION_DETAIL | REGION_FOOTER | REGION_MODAL | REGION_TOAST;
1065
1307
 
1066
- // ../design-system/src/prompts/index.ts
1308
+ // ../toolcraft-design/src/prompts/index.ts
1067
1309
  import * as clack from "@clack/prompts";
1068
1310
 
1069
- // ../design-system/src/prompts/primitives/cancel.ts
1311
+ // ../toolcraft-design/src/prompts/primitives/cancel.ts
1070
1312
  import { isCancel } from "@clack/prompts";
1071
1313
 
1072
- // ../design-system/src/static/spinner.ts
1314
+ // ../toolcraft-design/src/static/spinner.ts
1073
1315
  var SPINNER_FRAMES = Object.freeze(["\u25D2", "\u25D0", "\u25D3", "\u25D1"]);
1074
1316
 
1075
1317
  // ../config-mutations/src/formats/json.ts
@@ -1108,6 +1350,13 @@ function cloneConfigValue(value) {
1108
1350
  function isConfigObject(value) {
1109
1351
  return typeof value === "object" && value !== null && !Array.isArray(value);
1110
1352
  }
1353
+ function detectIndent(content) {
1354
+ const match = content.match(/^[\t ]+/m);
1355
+ if (match) {
1356
+ return match[0];
1357
+ }
1358
+ return " ";
1359
+ }
1111
1360
  function parse3(content) {
1112
1361
  if (!content || content.trim() === "") {
1113
1362
  return {};
@@ -1147,6 +1396,9 @@ function merge(base, patch) {
1147
1396
  }
1148
1397
  return result;
1149
1398
  }
1399
+ function configValuesEqual(left, right) {
1400
+ return JSON.stringify(left) === JSON.stringify(right);
1401
+ }
1150
1402
  function prune(obj, shape) {
1151
1403
  let changed = false;
1152
1404
  const result = cloneConfigObject(obj);
@@ -1183,9 +1435,56 @@ function prune(obj, shape) {
1183
1435
  }
1184
1436
  return { changed, result };
1185
1437
  }
1438
+ function modifyAtPath(content, path10, value) {
1439
+ const indent = detectIndent(content);
1440
+ const formattingOptions = {
1441
+ tabSize: indent === " " ? 1 : indent.length,
1442
+ insertSpaces: indent !== " ",
1443
+ eol: "\n"
1444
+ };
1445
+ const edits = jsonc.modify(content, path10, value, { formattingOptions });
1446
+ let result = jsonc.applyEdits(content, edits);
1447
+ if (!result.endsWith("\n")) {
1448
+ result += "\n";
1449
+ }
1450
+ return result;
1451
+ }
1452
+ function removeAtPath(content, path10) {
1453
+ return modifyAtPath(content, path10, void 0);
1454
+ }
1455
+ function serializeUpdate(content, current, next) {
1456
+ let result = content || "{}";
1457
+ result = applyObjectUpdate(result, [], current, next);
1458
+ if (!result.endsWith("\n")) {
1459
+ result += "\n";
1460
+ }
1461
+ return result;
1462
+ }
1463
+ function applyObjectUpdate(content, path10, current, next) {
1464
+ let result = content;
1465
+ for (const key of Object.keys(current)) {
1466
+ if (!hasConfigEntry(next, key)) {
1467
+ result = removeAtPath(result, [...path10, key]);
1468
+ }
1469
+ }
1470
+ for (const [key, nextValue] of Object.entries(next)) {
1471
+ const nextPath = [...path10, key];
1472
+ const hasCurrent = hasConfigEntry(current, key);
1473
+ const currentValue = hasCurrent ? current[key] : void 0;
1474
+ if (hasCurrent && isConfigObject(currentValue) && isConfigObject(nextValue)) {
1475
+ result = applyObjectUpdate(result, nextPath, currentValue, nextValue);
1476
+ continue;
1477
+ }
1478
+ if (!hasCurrent || !configValuesEqual(currentValue, nextValue)) {
1479
+ result = modifyAtPath(result, nextPath, nextValue);
1480
+ }
1481
+ }
1482
+ return result;
1483
+ }
1186
1484
  var jsonFormat = {
1187
1485
  parse: parse3,
1188
1486
  serialize,
1487
+ serializeUpdate,
1189
1488
  merge,
1190
1489
  prune
1191
1490
  };
@@ -1567,7 +1866,7 @@ function pruneKeysByPrefix(table, prefix) {
1567
1866
  const result = {};
1568
1867
  for (const [key, value] of Object.entries(table)) {
1569
1868
  if (!key.startsWith(prefix)) {
1570
- result[key] = value;
1869
+ setConfigEntry(result, key, value);
1571
1870
  }
1572
1871
  }
1573
1872
  return result;
@@ -1576,28 +1875,43 @@ function isConfigObject4(value) {
1576
1875
  return typeof value === "object" && value !== null && !Array.isArray(value);
1577
1876
  }
1578
1877
  function mergeWithPruneByPrefix(base, patch, pruneByPrefix) {
1579
- const result = { ...base };
1878
+ const result = cloneConfigObject(base);
1580
1879
  const prefixMap = pruneByPrefix ?? {};
1581
1880
  for (const [key, value] of Object.entries(patch)) {
1881
+ if (value === void 0) {
1882
+ continue;
1883
+ }
1582
1884
  const current = result[key];
1583
1885
  const prefix = prefixMap[key];
1584
1886
  if (isConfigObject4(current) && isConfigObject4(value)) {
1585
1887
  if (prefix) {
1586
1888
  const pruned = pruneKeysByPrefix(current, prefix);
1587
- result[key] = { ...pruned, ...value };
1889
+ setConfigEntry(result, key, mergePrunedConfigObject(pruned, value));
1588
1890
  } else {
1589
- result[key] = mergeWithPruneByPrefix(
1590
- current,
1591
- value,
1592
- prefixMap
1593
- );
1891
+ setConfigEntry(result, key, mergeWithPruneByPrefix(current, value, prefixMap));
1594
1892
  }
1595
1893
  continue;
1596
1894
  }
1597
- result[key] = value;
1895
+ setConfigEntry(result, key, value);
1896
+ }
1897
+ return result;
1898
+ }
1899
+ function mergePrunedConfigObject(base, patch) {
1900
+ const result = cloneConfigObject(base);
1901
+ for (const [key, value] of Object.entries(patch)) {
1902
+ if (value === void 0) {
1903
+ continue;
1904
+ }
1905
+ setConfigEntry(result, key, value);
1598
1906
  }
1599
1907
  return result;
1600
1908
  }
1909
+ function serializeConfigUpdate(format, rawContent, current, next) {
1910
+ if (rawContent !== null && format.serializeUpdate) {
1911
+ return format.serializeUpdate(rawContent, current, next);
1912
+ }
1913
+ return format.serialize(next);
1914
+ }
1601
1915
  async function applyMutation(mutation, context, options) {
1602
1916
  switch (mutation.kind) {
1603
1917
  case "ensureDirectory":
@@ -1910,6 +2224,7 @@ async function applyConfigMerge(mutation, context, options) {
1910
2224
  }
1911
2225
  const format = getConfigFormat(formatName);
1912
2226
  const rawContent = await readFileIfExists(context.fs, targetPath);
2227
+ let preserveContent = rawContent;
1913
2228
  let current;
1914
2229
  try {
1915
2230
  current = rawContent === null ? {} : format.parse(rawContent);
@@ -1918,6 +2233,7 @@ async function applyConfigMerge(mutation, context, options) {
1918
2233
  await backupInvalidDocument(context, targetPath, rawContent);
1919
2234
  }
1920
2235
  current = {};
2236
+ preserveContent = null;
1921
2237
  }
1922
2238
  const value = resolveValue(mutation.value, options);
1923
2239
  let merged;
@@ -1926,7 +2242,7 @@ async function applyConfigMerge(mutation, context, options) {
1926
2242
  } else {
1927
2243
  merged = format.merge(current, value);
1928
2244
  }
1929
- const serialized = format.serialize(merged);
2245
+ const serialized = serializeConfigUpdate(format, preserveContent, current, merged);
1930
2246
  const changed = serialized !== rawContent;
1931
2247
  if (changed && !context.dryRun) {
1932
2248
  await writeAtomically(context, targetPath, serialized);
@@ -1994,7 +2310,7 @@ async function applyConfigPrune(mutation, context, options) {
1994
2310
  details
1995
2311
  };
1996
2312
  }
1997
- const serialized = format.serialize(result);
2313
+ const serialized = serializeConfigUpdate(format, rawContent, current, result);
1998
2314
  if (!context.dryRun) {
1999
2315
  await writeAtomically(context, targetPath, serialized);
2000
2316
  }
@@ -2019,6 +2335,7 @@ async function applyConfigTransform(mutation, context, options) {
2019
2335
  }
2020
2336
  const format = getConfigFormat(formatName);
2021
2337
  const rawContent = await readFileIfExists(context.fs, targetPath);
2338
+ let preserveContent = rawContent;
2022
2339
  let current;
2023
2340
  try {
2024
2341
  current = rawContent === null ? {} : format.parse(rawContent);
@@ -2027,6 +2344,7 @@ async function applyConfigTransform(mutation, context, options) {
2027
2344
  await backupInvalidDocument(context, targetPath, rawContent);
2028
2345
  }
2029
2346
  current = {};
2347
+ preserveContent = null;
2030
2348
  }
2031
2349
  const { content: transformed, changed } = mutation.transform(current, options);
2032
2350
  if (!changed) {
@@ -2050,7 +2368,7 @@ async function applyConfigTransform(mutation, context, options) {
2050
2368
  details
2051
2369
  };
2052
2370
  }
2053
- const serialized = format.serialize(transformed);
2371
+ const serialized = serializeConfigUpdate(format, preserveContent, current, transformed);
2054
2372
  if (!context.dryRun) {
2055
2373
  await writeAtomically(context, targetPath, serialized);
2056
2374
  }
@@ -2227,14 +2545,14 @@ async function installSkill(agentId, skill, options) {
2227
2545
  throw new UnsupportedAgentError(agentId);
2228
2546
  }
2229
2547
  const scope = options.scope ?? "local";
2230
- const config = support.config;
2548
+ const config2 = support.config;
2231
2549
  if (skill.name.length === 0 || skill.name === "." || skill.name === ".." || skill.name.includes("/") || skill.name.includes("\\") || skill.name.includes("\n") || skill.name.includes("\r")) {
2232
2550
  throw new Error(`Invalid skill name: ${skill.name}`);
2233
2551
  }
2234
- const skillDir = scope === "global" ? config.globalSkillDir : toHomeRelative(config.localSkillDir);
2552
+ const skillDir = scope === "global" ? config2.globalSkillDir : toHomeRelative(config2.localSkillDir);
2235
2553
  const skillFolderPath = `${skillDir}/${skill.name}`;
2236
2554
  const skillFilePath = `${skillFolderPath}/SKILL.md`;
2237
- const displayPath = `${scope === "global" ? config.globalSkillDir : config.localSkillDir}/${skill.name}/SKILL.md`;
2555
+ const displayPath = `${scope === "global" ? config2.globalSkillDir : config2.localSkillDir}/${skill.name}/SKILL.md`;
2238
2556
  const absoluteSkillPath = `${scope === "global" ? options.homeDir : options.cwd}/${skillFilePath.slice(2)}`;
2239
2557
  if (await pathExists2(options.fs, absoluteSkillPath)) {
2240
2558
  throw new Error(`Skill already exists: ${displayPath}`);
@@ -2293,19 +2611,19 @@ var UserError = class extends Error {
2293
2611
  };
2294
2612
 
2295
2613
  // ../toolcraft/src/human-in-loop/config.ts
2296
- function validateHumanInLoopOnDefine(config) {
2297
- const label = Array.isArray(config.children) ? "group" : "command";
2298
- if (config.confirm === true && config.humanInLoop !== void 0 && config.humanInLoop !== null) {
2299
- throw new Error(`${label} '${config.name}': use either confirm or humanInLoop, not both`);
2614
+ function validateHumanInLoopOnDefine(config2) {
2615
+ const label = Array.isArray(config2.children) ? "group" : "command";
2616
+ if (config2.confirm === true && config2.humanInLoop !== void 0 && config2.humanInLoop !== null) {
2617
+ throw new Error(`${label} '${config2.name}': use either confirm or humanInLoop, not both`);
2300
2618
  }
2301
- if (config.humanInLoop === void 0 || config.humanInLoop === null) {
2619
+ if (config2.humanInLoop === void 0 || config2.humanInLoop === null) {
2302
2620
  return;
2303
2621
  }
2304
- if (config.humanInLoop.mode !== "sync" && config.humanInLoop.mode !== "async") {
2305
- throw new Error(`${label} '${config.name}': humanInLoop.mode must be "sync" or "async"`);
2622
+ if (config2.humanInLoop.mode !== "sync" && config2.humanInLoop.mode !== "async") {
2623
+ throw new Error(`${label} '${config2.name}': humanInLoop.mode must be "sync" or "async"`);
2306
2624
  }
2307
- if (typeof config.humanInLoop.message !== "function") {
2308
- throw new Error(`${label} '${config.name}': humanInLoop.message must be a function`);
2625
+ if (typeof config2.humanInLoop.message !== "function") {
2626
+ throw new Error(`${label} '${config2.name}': humanInLoop.message must be a function`);
2309
2627
  }
2310
2628
  }
2311
2629
  function mergeHumanInLoopFromGroup(groupHumanInLoop, childHumanInLoop) {
@@ -2335,12 +2653,12 @@ function assertValidBranches(branches, discriminator) {
2335
2653
  }
2336
2654
  }
2337
2655
  }
2338
- function OneOf(config) {
2339
- assertValidBranches(config.branches, config.discriminator);
2656
+ function OneOf(config2) {
2657
+ assertValidBranches(config2.branches, config2.discriminator);
2340
2658
  return {
2341
2659
  kind: "oneOf",
2342
- discriminator: config.discriminator,
2343
- branches: config.branches
2660
+ discriminator: config2.discriminator,
2661
+ branches: config2.branches
2344
2662
  };
2345
2663
  }
2346
2664
 
@@ -2620,28 +2938,28 @@ function mergeSecrets(parent, child) {
2620
2938
  function resolveCommandScope(ownScope, inheritedScope) {
2621
2939
  return cloneScope(ownScope ?? inheritedScope) ?? ["cli", "sdk"];
2622
2940
  }
2623
- function createBaseCommand(config) {
2941
+ function createBaseCommand(config2) {
2624
2942
  const command = {
2625
2943
  kind: "command",
2626
- name: config.name,
2627
- description: config.description,
2628
- aliases: [...config.aliases ?? []],
2629
- positional: [...config.positional ?? []],
2630
- params: config.params,
2631
- secrets: cloneSecrets(config.secrets),
2632
- scope: resolveCommandScope(config.scope, void 0),
2633
- confirm: config.confirm ?? false,
2634
- humanInLoop: config.humanInLoop,
2635
- requires: cloneRequires(config.requires),
2636
- handler: config.handler,
2637
- render: config.render
2944
+ name: config2.name,
2945
+ description: config2.description,
2946
+ aliases: [...config2.aliases ?? []],
2947
+ positional: [...config2.positional ?? []],
2948
+ params: config2.params,
2949
+ secrets: cloneSecrets(config2.secrets),
2950
+ scope: resolveCommandScope(config2.scope, void 0),
2951
+ confirm: config2.confirm ?? false,
2952
+ humanInLoop: config2.humanInLoop,
2953
+ requires: cloneRequires(config2.requires),
2954
+ handler: config2.handler,
2955
+ render: config2.render
2638
2956
  };
2639
2957
  Object.defineProperty(command, commandConfigSymbol, {
2640
2958
  value: {
2641
- scope: cloneScope(config.scope),
2642
- humanInLoop: config.humanInLoop,
2643
- secrets: cloneSecrets(config.secrets),
2644
- requires: cloneRequires(config.requires),
2959
+ scope: cloneScope(config2.scope),
2960
+ humanInLoop: config2.humanInLoop,
2961
+ secrets: cloneSecrets(config2.secrets),
2962
+ requires: cloneRequires(config2.requires),
2645
2963
  sourcePath: inferCommandSourcePath()
2646
2964
  }
2647
2965
  });
@@ -2681,10 +2999,10 @@ function materializeCommand(command, inherited) {
2681
2999
  });
2682
3000
  return materialized;
2683
3001
  }
2684
- function defineCommand(config) {
2685
- validateHumanInLoopOnDefine(config);
3002
+ function defineCommand(config2) {
3003
+ validateHumanInLoopOnDefine(config2);
2686
3004
  return materializeCommand(
2687
- createBaseCommand(config),
3005
+ createBaseCommand(config2),
2688
3006
  {
2689
3007
  scope: void 0,
2690
3008
  humanInLoop: void 0,