coding-friend-cli 1.40.1 → 1.40.3

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 (29) hide show
  1. package/dist/{chunk-B44ROTV6.js → chunk-2M42TEZC.js} +4 -4
  2. package/dist/{chunk-CZW36H3D.js → chunk-BQ4E66ZV.js} +1 -1
  3. package/dist/{chunk-E4NBTQXR.js → chunk-CGZNFAOB.js} +3 -2
  4. package/dist/{chunk-Q2QDSYQT.js → chunk-FK4SXIF7.js} +1 -1
  5. package/dist/{chunk-KTUPGMIV.js → chunk-FSQCAHWF.js} +35 -9
  6. package/dist/{chunk-TFF47F7P.js → chunk-HEVK3OYL.js} +1 -1
  7. package/dist/{chunk-3WIZA5OZ.js → chunk-ITI7MXLA.js} +2 -0
  8. package/dist/{chunk-KG6HYBGD.js → chunk-PIXAQLXQ.js} +43 -6
  9. package/dist/{chunk-Z5XTSF7L.js → chunk-XLB2UAKL.js} +40 -2
  10. package/dist/{clean-PKSSS662.js → clean-S57BNTWM.js} +2 -2
  11. package/dist/{config-WHEUK2LC.js → config-OFGL545F.js} +74 -13
  12. package/dist/{dev-HWKNWOZY.js → dev-GCVXE5TW.js} +7 -19
  13. package/dist/{disable-DCGVIHKG.js → disable-CN6GAG4I.js} +1 -1
  14. package/dist/{enable-YYWJDOYB.js → enable-MRLKTJRB.js} +1 -1
  15. package/dist/{host-GZJSMJSF.js → host-7XKH5HLB.js} +2 -2
  16. package/dist/index.js +34 -34
  17. package/dist/{init-TUBWQCBQ.js → init-XAWQUS2X.js} +364 -269
  18. package/dist/{install-45O5ICBA.js → install-B3646W7P.js} +5 -5
  19. package/dist/{learn-AJT7Z6F7.js → learn-Q5YHKHQH.js} +2 -2
  20. package/dist/{mcp-JGBZMNWL.js → mcp-ZG56IYLD.js} +6 -6
  21. package/dist/{mcp-serve-IS4T24CV.js → mcp-serve-7RWXY3J4.js} +2 -2
  22. package/dist/{memory-4NVLWOFS.js → memory-LBVPVWJQ.js} +6 -6
  23. package/dist/{permission-DDBMBFJV.js → permission-W3NM7HOL.js} +3 -3
  24. package/dist/{session-GBHL5TH3.js → session-WU5344ZD.js} +2 -2
  25. package/dist/{status-KQQJNSEZ.js → status-GQ3ZDYZ4.js} +8 -8
  26. package/dist/{statusline-B7PXP3AQ.js → statusline-CO4ZOUXH.js} +2 -2
  27. package/dist/{uninstall-6QKNGLKK.js → uninstall-S5FSXBYX.js} +2 -2
  28. package/dist/{update-YJ363TBU.js → update-HGEF46T5.js} +7 -5
  29. package/package.json +1 -1
@@ -7,14 +7,14 @@ import {
7
7
  getExistingRules,
8
8
  getMissingRules,
9
9
  logPluginScriptWarning
10
- } from "./chunk-KG6HYBGD.js";
10
+ } from "./chunk-PIXAQLXQ.js";
11
11
  import {
12
12
  ensureMemoryBuilt,
13
13
  isMemoryInitialized,
14
14
  memoryInitWizard
15
- } from "./chunk-B44ROTV6.js";
16
- import "./chunk-TFF47F7P.js";
17
- import "./chunk-E4NBTQXR.js";
15
+ } from "./chunk-2M42TEZC.js";
16
+ import "./chunk-HEVK3OYL.js";
17
+ import "./chunk-CGZNFAOB.js";
18
18
  import {
19
19
  findStatuslineHookPath,
20
20
  getCurrentAccountEmail,
@@ -25,10 +25,10 @@ import {
25
25
  saveStatuslineConfig,
26
26
  selectStatuslineComponents,
27
27
  writeStatuslineSettings
28
- } from "./chunk-Q2QDSYQT.js";
28
+ } from "./chunk-FK4SXIF7.js";
29
29
  import {
30
30
  DEFAULT_CONFIG
31
- } from "./chunk-3WIZA5OZ.js";
31
+ } from "./chunk-ITI7MXLA.js";
32
32
  import {
33
33
  isLearnMcpRegistered,
34
34
  registerLearnMcp
@@ -41,7 +41,7 @@ import {
41
41
  isMemoryMcpRegistered,
42
42
  memoryConfigMenu,
43
43
  registerMemoryMcp
44
- } from "./chunk-CZW36H3D.js";
44
+ } from "./chunk-BQ4E66ZV.js";
45
45
  import {
46
46
  deployCodexAgents,
47
47
  findCodexAgentSourceDir,
@@ -55,16 +55,22 @@ import {
55
55
  } from "./chunk-KCBMCHSA.js";
56
56
  import {
57
57
  BACK,
58
+ BackError,
58
59
  applyDocsDirChange,
59
60
  askScope,
60
61
  ensureDocsFolders,
62
+ escCheckbox,
63
+ escConfirm,
64
+ escInput,
65
+ escSelect,
61
66
  formatScopeLabel,
62
67
  getMergedValue,
63
68
  getScopeLabel,
64
69
  injectBackChoice,
65
70
  resolveHostFlags,
71
+ setEscArmed,
66
72
  showConfigHint
67
- } from "./chunk-Z5XTSF7L.js";
73
+ } from "./chunk-XLB2UAKL.js";
68
74
  import {
69
75
  getLibPath
70
76
  } from "./chunk-TN3Y75WG.js";
@@ -94,7 +100,6 @@ import {
94
100
  } from "./chunk-NREZK463.js";
95
101
 
96
102
  // src/commands/init.ts
97
- import { checkbox, confirm, input, select } from "@inquirer/prompts";
98
103
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
99
104
  import { homedir } from "os";
100
105
  import { join } from "path";
@@ -118,6 +123,33 @@ function printStepHeader(label, description) {
118
123
  console.log(` ${chalk.dim(description)}`);
119
124
  }
120
125
  console.log(`${line}`);
126
+ console.log(chalk.dim(" (esc: back to previous step)"));
127
+ }
128
+ async function runSteps(steps) {
129
+ setEscArmed(Boolean(process.stdin.isTTY));
130
+ try {
131
+ let i = 0;
132
+ while (i < steps.length) {
133
+ _stepIndex = i;
134
+ try {
135
+ await steps[i]();
136
+ i++;
137
+ } catch (err) {
138
+ if (!(err instanceof BackError)) throw err;
139
+ console.log();
140
+ if (i === 0) log.dim("Already at the first step.");
141
+ else i--;
142
+ }
143
+ }
144
+ } finally {
145
+ setEscArmed(false);
146
+ }
147
+ }
148
+ function readCfgs() {
149
+ return {
150
+ globalCfg: readJson(globalConfigPath()),
151
+ localCfg: readJson(localConfigPath())
152
+ };
121
153
  }
122
154
  function isGitRepo() {
123
155
  return run("git", ["rev-parse", "--is-inside-work-tree"]) === "true";
@@ -235,7 +267,7 @@ function getDocsDir(globalCfg, localCfg) {
235
267
  return localCfg?.docsDir ?? globalCfg?.docsDir ?? DEFAULT_CONFIG.docsDir;
236
268
  }
237
269
  async function offerGlobalShortcut(globalDisplay) {
238
- const choice = await select({
270
+ const choice = await escSelect({
239
271
  message: "How to configure?",
240
272
  choices: injectBackChoice(
241
273
  [
@@ -275,7 +307,7 @@ async function stepDocsDir(globalCfg, localCfg) {
275
307
  ensureDocsFolders(globalValue, DOCS_SUBFOLDERS2);
276
308
  return;
277
309
  }
278
- const value = await input({
310
+ const value = await escInput({
279
311
  message: "Docs folder name:",
280
312
  default: currentValue ?? DEFAULT_CONFIG.docsDir,
281
313
  validate: (val) => {
@@ -315,7 +347,7 @@ async function stepGitignore(docsDir) {
315
347
  "Configure .gitignore",
316
348
  "Keeps Coding Friend's AI-generated docs and config out of your git history. You can edit it later in .gitignore."
317
349
  );
318
- const choice = await select({
350
+ const choice = await escSelect({
319
351
  message: "Add coding-friend artifacts to .gitignore?",
320
352
  choices: injectBackChoice(
321
353
  [
@@ -343,7 +375,7 @@ async function stepGitignore(docsDir) {
343
375
  ];
344
376
  let entries = allEntries;
345
377
  if (choice === "partial") {
346
- entries = await checkbox({
378
+ entries = await escCheckbox({
347
379
  message: "Which folders to ignore?",
348
380
  choices: allEntries.map((e) => ({ name: e, value: e }))
349
381
  });
@@ -393,7 +425,7 @@ async function stepDocsLanguage(globalCfg, localCfg) {
393
425
  writeToScope(scope, { language: lang });
394
426
  }
395
427
  async function selectLanguage(message) {
396
- const choice = await select({
428
+ const choice = await escSelect({
397
429
  message,
398
430
  choices: injectBackChoice(
399
431
  [
@@ -406,7 +438,7 @@ async function selectLanguage(message) {
406
438
  });
407
439
  handleBack(choice);
408
440
  if (choice === "_other") {
409
- const lang = await input({ message: "Enter language name:" });
441
+ const lang = await escInput({ message: "Enter language name:" });
410
442
  return lang || "en";
411
443
  }
412
444
  return choice;
@@ -435,7 +467,7 @@ async function stepLearnConfig(globalCfg) {
435
467
  value: "default"
436
468
  });
437
469
  locationChoices.push({ name: "Custom path\u2026", value: "custom" });
438
- const locationChoice = await select({
470
+ const locationChoice = await escSelect({
439
471
  message: `Current learn folder: ${currentOutputDir}`,
440
472
  choices: injectBackChoice(locationChoices, "Cancel init")
441
473
  });
@@ -444,7 +476,7 @@ async function stepLearnConfig(globalCfg) {
444
476
  if (locationChoice === "current") {
445
477
  outputDir = currentOutputDir;
446
478
  } else if (locationChoice === "custom") {
447
- outputDir = await input({
479
+ outputDir = await escInput({
448
480
  message: "Enter path (absolute or ~/...):",
449
481
  default: currentOutputDir !== defaultLearnDir ? currentOutputDir : void 0,
450
482
  validate: (val) => {
@@ -456,7 +488,7 @@ async function stepLearnConfig(globalCfg) {
456
488
  });
457
489
  const resolved = resolvePath(outputDir);
458
490
  if (!existsSync(resolved)) {
459
- const create = await confirm({
491
+ const create = await escConfirm({
460
492
  message: `Folder ${resolved} doesn't exist. Create it?`,
461
493
  default: true
462
494
  });
@@ -480,7 +512,7 @@ async function stepLearnConfig(globalCfg) {
480
512
  });
481
513
  }
482
514
  catChoices.push({ name: "Customize", value: "custom" });
483
- const catChoice = await select({
515
+ const catChoice = await escSelect({
484
516
  message: "Categories for organizing learning docs?",
485
517
  choices: injectBackChoice(catChoices, "Cancel init")
486
518
  });
@@ -507,7 +539,7 @@ async function stepLearnConfig(globalCfg) {
507
539
  const customCats = [];
508
540
  let keepGoing = true;
509
541
  while (keepGoing) {
510
- const line = await input({
542
+ const line = await escInput({
511
543
  message: `Category ${customCats.length + 1}:`
512
544
  });
513
545
  if (!line) {
@@ -532,12 +564,12 @@ async function stepLearnConfig(globalCfg) {
532
564
  ]);
533
565
  const learnDirIsGit = gitInLearnDir !== null;
534
566
  if (learnDirIsGit) {
535
- autoCommit = await confirm({
567
+ autoCommit = await escConfirm({
536
568
  message: "Auto-commit learning docs to git after each /cf-learn?",
537
569
  default: globalLearn?.autoCommit ?? false
538
570
  });
539
571
  }
540
- const indexChoice = await select({
572
+ const indexChoice = await escSelect({
541
573
  message: "How should learning docs be indexed?",
542
574
  choices: injectBackChoice(
543
575
  [
@@ -644,7 +676,7 @@ async function stepMemory(docsDir, opts) {
644
676
  "CF Memory",
645
677
  "Persistent project knowledge across sessions \u2014 search tier, embeddings, MCP."
646
678
  );
647
- const choice = await select({
679
+ const choice = await escSelect({
648
680
  message: "Set up CF Memory?",
649
681
  choices: injectBackChoice(
650
682
  [
@@ -682,7 +714,7 @@ async function stepTdd(globalCfg, localCfg) {
682
714
  `TDD ${formatScopeLabel(scopeLabel)}${currentValue !== void 0 ? ` (${currentValue})` : ""}`,
683
715
  "Enable TDD (RED\u2192GREEN\u2192REFACTOR) by default for all implementations."
684
716
  );
685
- const tddChoice = await confirm({
717
+ const tddChoice = await escConfirm({
686
718
  message: "Enable TDD by default? (writes failing tests before code \u2014 RED \u2192 GREEN \u2192 REFACTOR)",
687
719
  default: currentValue ?? false
688
720
  });
@@ -702,7 +734,7 @@ async function stepAutoApprove(globalCfg, localCfg) {
702
734
  `Auto-approve ${formatScopeLabel(scopeLabel)}${currentValue !== void 0 ? ` (${currentValue})` : ""}`,
703
735
  "Same `autoApprove` key for Claude (LLM classifier), Antigravity, and Codex (deterministic)."
704
736
  );
705
- const autoApproveChoice = await confirm({
737
+ const autoApproveChoice = await escConfirm({
706
738
  message: "Enable auto-approve? (Claude: LLM classifier; Antigravity/Codex: deterministic rules, no LLM)",
707
739
  default: currentValue ?? false
708
740
  });
@@ -717,7 +749,7 @@ async function stepAutoApprove(globalCfg, localCfg) {
717
749
  if (autoApproveChoice) {
718
750
  await afterAutoApproveEnabled(
719
751
  log,
720
- (message) => confirm({ message, default: true })
752
+ (message) => escConfirm({ message, default: true })
721
753
  );
722
754
  }
723
755
  }
@@ -733,13 +765,13 @@ async function stepPlanDocs(globalCfg, localCfg) {
733
765
  `Plan docs ${formatScopeLabel(scopeLabel)}`,
734
766
  "The human overview doc /cf-plan writes next to the agent plan \u2014 enable/disable and choose format."
735
767
  );
736
- const disableChoice = await confirm({
768
+ const disableChoice = await escConfirm({
737
769
  message: "Disable the /cf-plan human overview doc? (off by default)",
738
770
  default: currentDisable ?? true
739
771
  });
740
772
  let formatChoice;
741
773
  if (!disableChoice) {
742
- const fmt = await select({
774
+ const fmt = await escSelect({
743
775
  message: "Format for the human overview doc?",
744
776
  choices: injectBackChoice(
745
777
  [
@@ -769,7 +801,7 @@ async function stepPlanDocs(globalCfg, localCfg) {
769
801
  log.success(`Saved to ${targetPath}`);
770
802
  }
771
803
  async function stepClaudePermissions(externalLearnDir, autoCommit) {
772
- const scope = await select({
804
+ const scope = await escSelect({
773
805
  message: "Where should permissions be saved?",
774
806
  choices: [
775
807
  {
@@ -813,7 +845,7 @@ async function stepClaudePermissions(externalLearnDir, autoCommit) {
813
845
  logPluginScriptWarning(log, chalk);
814
846
  }
815
847
  console.log();
816
- const ok = await confirm({
848
+ const ok = await escConfirm({
817
849
  message: `Add all ${missing.length} recommended rules?`,
818
850
  default: true
819
851
  });
@@ -933,7 +965,7 @@ async function initMenu(gitAvailable) {
933
965
  description: " Grant Coding Friend skills/hooks the permissions they need"
934
966
  }
935
967
  ];
936
- const choice = await select({
968
+ const choice = await escSelect({
937
969
  message: "Which setting to configure?",
938
970
  choices: injectBackChoice(choices, "Exit")
939
971
  });
@@ -942,104 +974,113 @@ async function initMenu(gitAvailable) {
942
974
  return;
943
975
  }
944
976
  _suppressStepHeaders = true;
945
- switch (choice) {
946
- case "docsDir":
947
- await stepDocsDir(globalCfg, localCfg);
948
- break;
949
- case "gitignore":
950
- await stepGitignore(docsDir);
951
- break;
952
- case "language":
953
- await stepDocsLanguage(globalCfg, localCfg);
954
- break;
955
- case "learn":
956
- await stepLearnConfig(globalCfg);
957
- break;
958
- case "completion":
959
- await stepShellCompletion();
960
- break;
961
- case "statusline":
962
- await stepStatusline();
963
- break;
964
- case "memory":
965
- await stepMemory(docsDir, { menuMode: true });
966
- break;
967
- case "tdd": {
968
- const tddChoice = await confirm({
969
- message: "Enable TDD by default? (writes failing tests before code \u2014 RED \u2192 GREEN \u2192 REFACTOR)",
970
- default: tddVal ?? false
971
- });
972
- const tddTargetScope = await askScope();
973
- if (tddTargetScope !== "back") {
974
- const targetPath = tddTargetScope === "global" ? globalConfigPath() : localConfigPath();
975
- mergeJson(targetPath, { tdd: tddChoice });
976
- log.success(`Saved to ${targetPath}`);
977
- }
978
- break;
979
- }
980
- case "autoApprove": {
981
- const autoApproveChoice = await confirm({
982
- message: "Enable auto-approve? (Claude: LLM classifier; Antigravity/Codex: deterministic rules, no LLM)",
983
- default: autoApproveVal ?? false
984
- });
985
- const autoApproveTargetScope = await askScope();
986
- if (autoApproveTargetScope !== "back") {
987
- const targetPath = autoApproveTargetScope === "global" ? globalConfigPath() : localConfigPath();
988
- mergeJson(targetPath, { autoApprove: autoApproveChoice });
989
- log.success(`Saved to ${targetPath}`);
990
- }
991
- if (autoApproveChoice) {
992
- await afterAutoApproveEnabled(
993
- log,
994
- (message) => confirm({ message, default: true })
995
- );
977
+ setEscArmed(Boolean(process.stdin.isTTY));
978
+ try {
979
+ switch (choice) {
980
+ case "docsDir":
981
+ await stepDocsDir(globalCfg, localCfg);
982
+ break;
983
+ case "gitignore":
984
+ await stepGitignore(docsDir);
985
+ break;
986
+ case "language":
987
+ await stepDocsLanguage(globalCfg, localCfg);
988
+ break;
989
+ case "learn":
990
+ await stepLearnConfig(globalCfg);
991
+ break;
992
+ case "completion":
993
+ await stepShellCompletion();
994
+ break;
995
+ case "statusline":
996
+ await stepStatusline();
997
+ break;
998
+ case "memory":
999
+ await stepMemory(docsDir, { menuMode: true });
1000
+ break;
1001
+ case "tdd": {
1002
+ const tddChoice = await escConfirm({
1003
+ message: "Enable TDD by default? (writes failing tests before code \u2014 RED \u2192 GREEN \u2192 REFACTOR)",
1004
+ default: tddVal ?? false
1005
+ });
1006
+ const tddTargetScope = await askScope();
1007
+ if (tddTargetScope !== "back") {
1008
+ const targetPath = tddTargetScope === "global" ? globalConfigPath() : localConfigPath();
1009
+ mergeJson(targetPath, { tdd: tddChoice });
1010
+ log.success(`Saved to ${targetPath}`);
1011
+ }
1012
+ break;
996
1013
  }
997
- break;
998
- }
999
- case "planDocs": {
1000
- const planDocsDisableChoice = await confirm({
1001
- message: "Disable the /cf-plan human overview doc? (off by default)",
1002
- default: planDocsDisableVal ?? true
1003
- });
1004
- let planDocsFormatChoice;
1005
- if (!planDocsDisableChoice) {
1006
- const fmt = await select({
1007
- message: "Format for the human overview doc?",
1008
- choices: injectBackChoice(
1009
- [
1010
- { name: "HTML", value: "html" },
1011
- { name: "Markdown", value: "md" }
1012
- ],
1013
- "Skip plan docs config"
1014
- ),
1015
- default: planDocsFormatVal ?? "html"
1014
+ case "autoApprove": {
1015
+ const autoApproveChoice = await escConfirm({
1016
+ message: "Enable auto-approve? (Claude: LLM classifier; Antigravity/Codex: deterministic rules, no LLM)",
1017
+ default: autoApproveVal ?? false
1016
1018
  });
1017
- if (fmt === BACK) {
1018
- log.dim("Skipped plan docs config.");
1019
- break;
1019
+ const autoApproveTargetScope = await askScope();
1020
+ if (autoApproveTargetScope !== "back") {
1021
+ const targetPath = autoApproveTargetScope === "global" ? globalConfigPath() : localConfigPath();
1022
+ mergeJson(targetPath, { autoApprove: autoApproveChoice });
1023
+ log.success(`Saved to ${targetPath}`);
1024
+ }
1025
+ if (autoApproveChoice) {
1026
+ await afterAutoApproveEnabled(
1027
+ log,
1028
+ (message) => escConfirm({ message, default: true })
1029
+ );
1020
1030
  }
1021
- planDocsFormatChoice = fmt;
1031
+ break;
1022
1032
  }
1023
- const planDocsTargetScope = await askScope();
1024
- if (planDocsTargetScope !== "back") {
1025
- const targetPath = planDocsTargetScope === "global" ? globalConfigPath() : localConfigPath();
1026
- mergeJson(targetPath, {
1027
- disableGUIPlan: planDocsDisableChoice,
1028
- ...planDocsFormatChoice !== void 0 ? { guiPlanFormat: planDocsFormatChoice } : {}
1033
+ case "planDocs": {
1034
+ const planDocsDisableChoice = await escConfirm({
1035
+ message: "Disable the /cf-plan human overview doc? (off by default)",
1036
+ default: planDocsDisableVal ?? true
1029
1037
  });
1030
- log.success(`Saved to ${targetPath}`);
1038
+ let planDocsFormatChoice;
1039
+ if (!planDocsDisableChoice) {
1040
+ const fmt = await escSelect({
1041
+ message: "Format for the human overview doc?",
1042
+ choices: injectBackChoice(
1043
+ [
1044
+ { name: "HTML", value: "html" },
1045
+ { name: "Markdown", value: "md" }
1046
+ ],
1047
+ "Skip plan docs config"
1048
+ ),
1049
+ default: planDocsFormatVal ?? "html"
1050
+ });
1051
+ if (fmt === BACK) {
1052
+ log.dim("Skipped plan docs config.");
1053
+ break;
1054
+ }
1055
+ planDocsFormatChoice = fmt;
1056
+ }
1057
+ const planDocsTargetScope = await askScope();
1058
+ if (planDocsTargetScope !== "back") {
1059
+ const targetPath = planDocsTargetScope === "global" ? globalConfigPath() : localConfigPath();
1060
+ mergeJson(targetPath, {
1061
+ disableGUIPlan: planDocsDisableChoice,
1062
+ ...planDocsFormatChoice !== void 0 ? { guiPlanFormat: planDocsFormatChoice } : {}
1063
+ });
1064
+ log.success(`Saved to ${targetPath}`);
1065
+ }
1066
+ break;
1067
+ }
1068
+ case "permissions": {
1069
+ const learnCfg = globalCfg?.learn;
1070
+ const learnOutputDir = learnCfg?.outputDir ?? DEFAULT_CONFIG.learn.outputDir;
1071
+ const learnAutoCommit = learnCfg?.autoCommit || false;
1072
+ await stepClaudePermissions(learnOutputDir, learnAutoCommit);
1073
+ break;
1031
1074
  }
1032
- break;
1033
- }
1034
- case "permissions": {
1035
- const learnCfg = globalCfg?.learn;
1036
- const learnOutputDir = learnCfg?.outputDir ?? DEFAULT_CONFIG.learn.outputDir;
1037
- const learnAutoCommit = learnCfg?.autoCommit || false;
1038
- await stepClaudePermissions(learnOutputDir, learnAutoCommit);
1039
- break;
1040
1075
  }
1076
+ } catch (err) {
1077
+ if (!(err instanceof BackError)) throw err;
1078
+ console.log();
1079
+ log.dim("Back to menu.");
1080
+ } finally {
1081
+ setEscArmed(false);
1082
+ _suppressStepHeaders = false;
1041
1083
  }
1042
- _suppressStepHeaders = false;
1043
1084
  console.log();
1044
1085
  }
1045
1086
  }
@@ -1115,7 +1156,7 @@ async function initCommand(opts = {}) {
1115
1156
  }
1116
1157
  log.success("All settings configured!");
1117
1158
  console.log();
1118
- const proceed = await confirm({
1159
+ const proceed = await escConfirm({
1119
1160
  message: "Modify settings?",
1120
1161
  default: false
1121
1162
  });
@@ -1138,7 +1179,7 @@ async function initCommand(opts = {}) {
1138
1179
  console.log(` ${chalk.yellow("\u26A0")} ${parts.join(" \xB7 ")}`);
1139
1180
  console.log();
1140
1181
  }
1141
- const proceed = await confirm({
1182
+ const proceed = await escConfirm({
1142
1183
  message: "Run setup wizard?",
1143
1184
  default: true
1144
1185
  });
@@ -1147,67 +1188,100 @@ async function initCommand(opts = {}) {
1147
1188
  return;
1148
1189
  }
1149
1190
  }
1150
- await stepDocsDir(globalCfg, localCfg);
1151
- const updatedGlobal = readJson(globalConfigPath());
1152
- const updatedLocal = readJson(localConfigPath());
1153
- const docsDir = getDocsDir(updatedGlobal, updatedLocal);
1154
- if (gitAvailable) {
1155
- await stepGitignore(docsDir);
1156
- } else {
1157
- printStepHeader(
1158
- `Configure .gitignore ${chalk.dim("[skipped]")}`,
1159
- "Keeps AI-generated docs and config out of your git history."
1160
- );
1161
- log.dim("Skipped \u2014 not inside a git repo.");
1162
- }
1163
- await stepDocsLanguage(globalCfg, localCfg);
1164
- const { outputDir, autoCommit } = await stepLearnConfig(updatedGlobal);
1165
- if (isLearnMcpRegistered()) {
1166
- log.dim("coding-friend-learn: already registered");
1167
- log.dim(
1168
- " (If it points to an old path, run: claude mcp remove --scope user coding-friend-learn && cf mcp)"
1169
- );
1170
- } else {
1171
- const registered = registerLearnMcp(outputDir);
1172
- if (registered) {
1173
- log.success(
1174
- "Registered CF Learn MCP (user scope). Restart Claude Code to activate."
1175
- );
1176
- }
1177
- }
1178
- if (isMemoryMcpRegistered()) {
1179
- log.dim("coding-friend-memory: already registered (user scope)");
1180
- log.dim(
1181
- " (If it points to an old path, run: claude mcp remove --scope user coding-friend-memory && cf mcp)"
1182
- );
1183
- } else {
1184
- const registered = registerMemoryMcp();
1185
- if (registered) {
1186
- log.success(
1187
- "Registered coding-friend-memory (user scope). Restart Claude Code to activate."
1191
+ const learnCfg = globalCfg?.learn;
1192
+ let learn = {
1193
+ outputDir: learnCfg?.outputDir ?? DEFAULT_CONFIG.learn.outputDir,
1194
+ autoCommit: learnCfg?.autoCommit ?? false
1195
+ };
1196
+ await runSteps([
1197
+ // Step 1: docsDir
1198
+ async () => {
1199
+ const { globalCfg: g, localCfg: l } = readCfgs();
1200
+ await stepDocsDir(g, l);
1201
+ },
1202
+ // Step 2: .gitignore (only in git repos)
1203
+ async () => {
1204
+ if (!gitAvailable) {
1205
+ printStepHeader(
1206
+ `Configure .gitignore ${chalk.dim("[skipped]")}`,
1207
+ "Keeps AI-generated docs and config out of your git history."
1208
+ );
1209
+ log.dim("Skipped \u2014 not inside a git repo.");
1210
+ return;
1211
+ }
1212
+ const { globalCfg: g, localCfg: l } = readCfgs();
1213
+ await stepGitignore(getDocsDir(g, l));
1214
+ },
1215
+ // Step 3: Docs language
1216
+ async () => {
1217
+ const { globalCfg: g, localCfg: l } = readCfgs();
1218
+ await stepDocsLanguage(g, l);
1219
+ },
1220
+ // Step 4: /cf-learn config + MCP registration
1221
+ async () => {
1222
+ const { globalCfg: g } = readCfgs();
1223
+ learn = await stepLearnConfig(g);
1224
+ if (isLearnMcpRegistered()) {
1225
+ log.dim("coding-friend-learn: already registered");
1226
+ log.dim(
1227
+ " (If it points to an old path, run: claude mcp remove --scope user coding-friend-learn && cf mcp)"
1228
+ );
1229
+ } else if (registerLearnMcp(learn.outputDir)) {
1230
+ log.success(
1231
+ "Registered CF Learn MCP (user scope). Restart Claude Code to activate."
1232
+ );
1233
+ }
1234
+ if (isMemoryMcpRegistered()) {
1235
+ log.dim("coding-friend-memory: already registered (user scope)");
1236
+ log.dim(
1237
+ " (If it points to an old path, run: claude mcp remove --scope user coding-friend-memory && cf mcp)"
1238
+ );
1239
+ } else if (registerMemoryMcp()) {
1240
+ log.success(
1241
+ "Registered coding-friend-memory (user scope). Restart Claude Code to activate."
1242
+ );
1243
+ }
1244
+ },
1245
+ // Step 5: Shell completion
1246
+ stepShellCompletion,
1247
+ // Step 6: Statusline
1248
+ stepStatusline,
1249
+ // Step 7: CF Memory
1250
+ async () => {
1251
+ const { globalCfg: g, localCfg: l } = readCfgs();
1252
+ await stepMemory(getDocsDir(g, l));
1253
+ },
1254
+ // Step 8: TDD
1255
+ async () => {
1256
+ const { globalCfg: g, localCfg: l } = readCfgs();
1257
+ await stepTdd(g, l);
1258
+ },
1259
+ // Step 9: Auto-approve
1260
+ async () => {
1261
+ const { globalCfg: g, localCfg: l } = readCfgs();
1262
+ await stepAutoApprove(g, l);
1263
+ },
1264
+ // Step 10: Plan docs
1265
+ async () => {
1266
+ const { globalCfg: g, localCfg: l } = readCfgs();
1267
+ await stepPlanDocs(g, l);
1268
+ },
1269
+ // Step 11: Claude permissions
1270
+ async () => {
1271
+ printStepHeader(
1272
+ "Configure Claude permissions",
1273
+ "Grants Coding Friend skills/hooks the permissions they need, so you get fewer prompts."
1188
1274
  );
1275
+ await stepClaudePermissions(learn.outputDir, learn.autoCommit);
1189
1276
  }
1190
- }
1191
- await stepShellCompletion();
1192
- await stepStatusline();
1193
- await stepMemory(docsDir);
1194
- const finalGlobal = readJson(globalConfigPath());
1195
- const finalLocal = readJson(localConfigPath());
1196
- await stepTdd(finalGlobal, finalLocal);
1197
- await stepAutoApprove(finalGlobal, finalLocal);
1198
- await stepPlanDocs(finalGlobal, finalLocal);
1199
- printStepHeader(
1200
- "Configure Claude permissions",
1201
- "Grants Coding Friend skills/hooks the permissions they need, so you get fewer prompts."
1202
- );
1203
- await stepClaudePermissions(outputDir, autoCommit);
1277
+ ]);
1204
1278
  if (!existsSync(localConfigPath())) {
1205
1279
  writeJson(localConfigPath(), {});
1206
1280
  }
1207
1281
  console.log();
1208
1282
  log.congrats("Setup complete!");
1209
1283
  log.dim(
1210
- "Available commands: /cf-ask, /cf-plan, /cf-later-do, /cf-design, /cf-fix, /cf-commit, /cf-review, /cf-review-out, /cf-review-in, /cf-ship, /cf-optimize, /cf-scan, /cf-remember, /cf-learn, /cf-teach, /cf-research, /cf-session, /cf-warm, /cf-checkpoint, /cf-checkpoint-from, /cf-help"
1284
+ "Available commands: /cf-ask, /cf-plan, /cf-plan-review, /cf-later-do, /cf-design, /cf-fix, /cf-commit, /cf-review, /cf-review-out, /cf-review-in, /cf-ship, /cf-optimize, /cf-scan, /cf-remember, /cf-learn, /cf-teach, /cf-research, /cf-session, /cf-warm, /cf-checkpoint, /cf-checkpoint-from, /cf-help"
1211
1285
  );
1212
1286
  }
1213
1287
  async function initCodexCommand(opts) {
@@ -1221,7 +1295,7 @@ async function initCodexCommand(opts) {
1221
1295
  log.warn("Not inside a git repo -- git-related steps will be skipped.");
1222
1296
  console.log();
1223
1297
  }
1224
- const proceed = await confirm({
1298
+ const proceed = await escConfirm({
1225
1299
  message: "Run Codex setup wizard?",
1226
1300
  default: true
1227
1301
  });
@@ -1229,42 +1303,53 @@ async function initCodexCommand(opts) {
1229
1303
  log.dim("Init cancelled. Run `cf init --agent codex` anytime to resume.");
1230
1304
  return;
1231
1305
  }
1232
- let globalCfg = readJson(globalConfigPath());
1233
- let localCfg = readJson(localConfigPath());
1234
- await stepDocsDir(globalCfg, localCfg);
1235
- globalCfg = readJson(globalConfigPath());
1236
- localCfg = readJson(localConfigPath());
1237
- const docsDir = getDocsDir(globalCfg, localCfg);
1238
- ensureDocsFolders(docsDir, [
1239
- "plans",
1240
- "memory",
1241
- "research",
1242
- "sessions",
1243
- "reviews",
1244
- "warm"
1306
+ await runSteps([
1307
+ async () => {
1308
+ const { globalCfg, localCfg } = readCfgs();
1309
+ await stepDocsDir(globalCfg, localCfg);
1310
+ const cfgs = readCfgs();
1311
+ ensureDocsFolders(getDocsDir(cfgs.globalCfg, cfgs.localCfg), [
1312
+ "plans",
1313
+ "memory",
1314
+ "research",
1315
+ "sessions",
1316
+ "reviews",
1317
+ "warm"
1318
+ ]);
1319
+ },
1320
+ async () => {
1321
+ if (!gitAvailable) {
1322
+ printStepHeader(
1323
+ `Configure .gitignore ${chalk.dim("[skipped]")}`,
1324
+ "Keeps AI-generated docs and config out of your git history."
1325
+ );
1326
+ log.dim("Skipped \u2014 not inside a git repo.");
1327
+ return;
1328
+ }
1329
+ const { globalCfg, localCfg } = readCfgs();
1330
+ await stepGitignore(getDocsDir(globalCfg, localCfg));
1331
+ },
1332
+ async () => {
1333
+ const { globalCfg, localCfg } = readCfgs();
1334
+ await stepDocsLanguage(globalCfg, localCfg);
1335
+ },
1336
+ async () => {
1337
+ await stepLearnConfig(readCfgs().globalCfg);
1338
+ log.dim(
1339
+ "Learn MCP is not registered into Codex config (no dedicated Codex writer). Settings are saved to Coding Friend config."
1340
+ );
1341
+ },
1342
+ async () => {
1343
+ const { globalCfg, localCfg } = readCfgs();
1344
+ await stepAutoApprove(globalCfg, localCfg);
1345
+ },
1346
+ async () => {
1347
+ const { globalCfg, localCfg } = readCfgs();
1348
+ await stepPrivacyBlock(globalCfg, localCfg);
1349
+ }
1245
1350
  ]);
1246
- if (gitAvailable) {
1247
- await stepGitignore(docsDir);
1248
- } else {
1249
- printStepHeader(
1250
- `Configure .gitignore ${chalk.dim("[skipped]")}`,
1251
- "Keeps AI-generated docs and config out of your git history."
1252
- );
1253
- log.dim("Skipped \u2014 not inside a git repo.");
1254
- }
1255
- await stepDocsLanguage(globalCfg, localCfg);
1256
- globalCfg = readJson(globalConfigPath());
1257
- localCfg = readJson(localConfigPath());
1258
- await stepLearnConfig(globalCfg);
1259
- log.dim(
1260
- "Learn MCP is not registered into Codex config (no dedicated Codex writer). Settings are saved to Coding Friend config."
1261
- );
1262
- globalCfg = readJson(globalConfigPath());
1263
- localCfg = readJson(localConfigPath());
1264
- await stepAutoApprove(globalCfg, localCfg);
1265
- globalCfg = readJson(globalConfigPath());
1266
- localCfg = readJson(localConfigPath());
1267
- await stepPrivacyBlock(globalCfg, localCfg);
1351
+ const codexCfgs = readCfgs();
1352
+ const docsDir = getDocsDir(codexCfgs.globalCfg, codexCfgs.localCfg);
1268
1353
  if (!existsSync(localConfigPath())) {
1269
1354
  writeJson(localConfigPath(), {});
1270
1355
  }
@@ -1329,7 +1414,7 @@ async function initAgyCommand() {
1329
1414
  log.warn("Not inside a git repo -- git-related steps will be skipped.");
1330
1415
  console.log();
1331
1416
  }
1332
- const proceed = await confirm({
1417
+ const proceed = await escConfirm({
1333
1418
  message: "Run Antigravity setup wizard?",
1334
1419
  default: true
1335
1420
  });
@@ -1337,65 +1422,74 @@ async function initAgyCommand() {
1337
1422
  log.dim("Init cancelled. Run `cf init --agent agy` anytime to resume.");
1338
1423
  return;
1339
1424
  }
1340
- let globalCfg = readJson(globalConfigPath());
1341
- let localCfg = readJson(localConfigPath());
1342
- await stepDocsDir(globalCfg, localCfg);
1343
- globalCfg = readJson(globalConfigPath());
1344
- localCfg = readJson(localConfigPath());
1345
- const docsDir = getDocsDir(globalCfg, localCfg);
1346
- ensureDocsFolders(docsDir, [
1347
- "plans",
1348
- "memory",
1349
- "research",
1350
- "sessions",
1351
- "reviews",
1352
- "warm"
1353
- ]);
1354
- if (gitAvailable) {
1355
- await stepGitignore(docsDir);
1356
- } else {
1357
- printStepHeader(
1358
- `Configure .gitignore ${chalk.dim("[skipped]")}`,
1359
- "Keeps AI-generated docs and config out of your git history."
1360
- );
1361
- log.dim("Skipped \u2014 not inside a git repo.");
1362
- }
1363
- await stepDocsLanguage(globalCfg, localCfg);
1364
- globalCfg = readJson(globalConfigPath());
1365
- localCfg = readJson(localConfigPath());
1366
- const { outputDir } = await stepLearnConfig(globalCfg);
1367
- if (isAgyPluginInstalled()) {
1368
- if (isLearnMcpRegistered("agy")) {
1369
- log.dim(
1370
- "coding-friend-learn: already registered in plugin mcp_config.json"
1371
- );
1372
- } else {
1373
- const registered = registerLearnMcp(outputDir, "agy");
1374
- if (registered) {
1375
- log.success(
1376
- "Registered CF Learn MCP in the Antigravity plugin mcp_config.json."
1425
+ await runSteps([
1426
+ async () => {
1427
+ const { globalCfg, localCfg } = readCfgs();
1428
+ await stepDocsDir(globalCfg, localCfg);
1429
+ const cfgs = readCfgs();
1430
+ ensureDocsFolders(getDocsDir(cfgs.globalCfg, cfgs.localCfg), [
1431
+ "plans",
1432
+ "memory",
1433
+ "research",
1434
+ "sessions",
1435
+ "reviews",
1436
+ "warm"
1437
+ ]);
1438
+ },
1439
+ async () => {
1440
+ if (!gitAvailable) {
1441
+ printStepHeader(
1442
+ `Configure .gitignore ${chalk.dim("[skipped]")}`,
1443
+ "Keeps AI-generated docs and config out of your git history."
1377
1444
  );
1445
+ log.dim("Skipped \u2014 not inside a git repo.");
1446
+ return;
1378
1447
  }
1379
- }
1380
- if (isMemoryMcpRegistered("agy")) {
1381
- log.dim(
1382
- "coding-friend-memory: already registered in plugin mcp_config.json"
1383
- );
1384
- } else {
1385
- const registered = registerMemoryMcp("agy");
1386
- if (registered) {
1387
- log.success(
1388
- "Registered coding-friend-memory in the Antigravity plugin mcp_config.json."
1389
- );
1448
+ const { globalCfg, localCfg } = readCfgs();
1449
+ await stepGitignore(getDocsDir(globalCfg, localCfg));
1450
+ },
1451
+ async () => {
1452
+ const { globalCfg, localCfg } = readCfgs();
1453
+ await stepDocsLanguage(globalCfg, localCfg);
1454
+ },
1455
+ async () => {
1456
+ const { outputDir } = await stepLearnConfig(readCfgs().globalCfg);
1457
+ if (isAgyPluginInstalled()) {
1458
+ if (isLearnMcpRegistered("agy")) {
1459
+ log.dim(
1460
+ "coding-friend-learn: already registered in plugin mcp_config.json"
1461
+ );
1462
+ } else {
1463
+ const registered = registerLearnMcp(outputDir, "agy");
1464
+ if (registered) {
1465
+ log.success(
1466
+ "Registered CF Learn MCP in the Antigravity plugin mcp_config.json."
1467
+ );
1468
+ }
1469
+ }
1470
+ if (isMemoryMcpRegistered("agy")) {
1471
+ log.dim(
1472
+ "coding-friend-memory: already registered in plugin mcp_config.json"
1473
+ );
1474
+ } else {
1475
+ const registered = registerMemoryMcp("agy");
1476
+ if (registered) {
1477
+ log.success(
1478
+ "Registered coding-friend-memory in the Antigravity plugin mcp_config.json."
1479
+ );
1480
+ }
1481
+ }
1390
1482
  }
1483
+ },
1484
+ async () => {
1485
+ const { globalCfg, localCfg } = readCfgs();
1486
+ await stepAutoApprove(globalCfg, localCfg);
1487
+ },
1488
+ async () => {
1489
+ const { globalCfg, localCfg } = readCfgs();
1490
+ await stepPrivacyBlock(globalCfg, localCfg);
1391
1491
  }
1392
- }
1393
- globalCfg = readJson(globalConfigPath());
1394
- localCfg = readJson(localConfigPath());
1395
- await stepAutoApprove(globalCfg, localCfg);
1396
- globalCfg = readJson(globalConfigPath());
1397
- localCfg = readJson(localConfigPath());
1398
- await stepPrivacyBlock(globalCfg, localCfg);
1492
+ ]);
1399
1493
  if (!existsSync(localConfigPath())) {
1400
1494
  writeJson(localConfigPath(), {});
1401
1495
  }
@@ -1425,7 +1519,7 @@ async function stepPrivacyBlock(globalCfg, localCfg) {
1425
1519
  `Privacy-block ${formatScopeLabel(scopeLabel)}${currentValue !== void 0 ? ` (${currentValue})` : ""}`,
1426
1520
  "Deny reads of .env, keys, and similar secrets on PreToolUse."
1427
1521
  );
1428
- const enabled = await confirm({
1522
+ const enabled = await escConfirm({
1429
1523
  message: "Keep privacy-block enabled? (deny .env / credentials / keys)",
1430
1524
  default: enabledByDefault
1431
1525
  });
@@ -1465,6 +1559,7 @@ function initOmpCommand(_opts) {
1465
1559
  var AGENTS_MD_SKILLS = [
1466
1560
  "cf-ask",
1467
1561
  "cf-plan",
1562
+ "cf-plan-review",
1468
1563
  "cf-later-do",
1469
1564
  "cf-review",
1470
1565
  "cf-review-out",