@codemoot/cli 0.2.9 → 0.2.11

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CodeMoot Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.js CHANGED
@@ -538,6 +538,7 @@ import {
538
538
  preflightTokenCheck
539
539
  } from "@codemoot/core";
540
540
  import chalk4 from "chalk";
541
+ import { writeFileSync } from "fs";
541
542
  async function debateStartCommand(topic, options) {
542
543
  let db;
543
544
  try {
@@ -560,7 +561,8 @@ async function debateStartCommand(topic, options) {
560
561
  status: "running",
561
562
  sessionIds: {},
562
563
  resumeStats: { attempted: 0, succeeded: 0, fallbacks: 0 },
563
- maxRounds: options.maxRounds ?? 5
564
+ maxRounds: options.maxRounds ?? 5,
565
+ defaultTimeout: options.timeout
564
566
  });
565
567
  const output = {
566
568
  debateId,
@@ -605,9 +607,10 @@ async function debateTurnCommand(debateId, prompt, options) {
605
607
  }
606
608
  const rawRound = options.round ?? criticRow.round + 1;
607
609
  const newRound = Number.isFinite(rawRound) && rawRound > 0 ? rawRound : criticRow.round + 1;
608
- const rawTimeout = options.timeout ?? 600;
609
- const timeout = (Number.isFinite(rawTimeout) && rawTimeout > 0 ? rawTimeout : 600) * 1e3;
610
610
  const proposerStateForLimit = store.loadState(debateId, "proposer");
611
+ const storedTimeout = proposerStateForLimit?.defaultTimeout;
612
+ const rawTimeout = options.timeout ?? storedTimeout ?? 600;
613
+ const timeout = (Number.isFinite(rawTimeout) && rawTimeout > 0 ? rawTimeout : 600) * 1e3;
611
614
  const rawMax = proposerStateForLimit?.maxRounds ?? 5;
612
615
  const maxRounds = Number.isFinite(rawMax) && rawMax > 0 ? rawMax : 5;
613
616
  if (newRound > maxRounds) {
@@ -626,10 +629,16 @@ async function debateTurnCommand(debateId, prompt, options) {
626
629
  status: "active"
627
630
  });
628
631
  }
632
+ if (options.output && existing.responseText) {
633
+ writeFileSync(options.output, existing.responseText, "utf-8");
634
+ console.error(chalk4.dim(`Full response written to ${options.output}`));
635
+ }
629
636
  const output = {
630
637
  debateId,
631
638
  round: newRound,
632
639
  response: existing.responseText?.slice(0, 2e3) ?? "",
640
+ responseTruncated: (existing.responseText?.length ?? 0) > 2e3,
641
+ responseFile: options.output ?? void 0,
633
642
  sessionId: existing.sessionId,
634
643
  resumed: false,
635
644
  cached: true,
@@ -671,10 +680,16 @@ async function debateTurnCommand(debateId, prompt, options) {
671
680
  if (!msgStore.markRunning(msgId)) {
672
681
  const recheckRow = msgStore.getByRound(debateId, newRound, "critic");
673
682
  if (recheckRow?.status === "completed") {
683
+ if (options.output && recheckRow.responseText) {
684
+ writeFileSync(options.output, recheckRow.responseText, "utf-8");
685
+ console.error(chalk4.dim(`Full response written to ${options.output}`));
686
+ }
674
687
  const output = {
675
688
  debateId,
676
689
  round: newRound,
677
690
  response: recheckRow.responseText?.slice(0, 2e3) ?? "",
691
+ responseTruncated: (recheckRow.responseText?.length ?? 0) > 2e3,
692
+ responseFile: options.output ?? void 0,
678
693
  sessionId: recheckRow.sessionId,
679
694
  resumed: false,
680
695
  cached: true,
@@ -702,14 +717,20 @@ async function debateTurnCommand(debateId, prompt, options) {
702
717
  const completedHistory = msgStore.getHistory(debateId).filter((m) => m.status === "completed");
703
718
  const maxContext = adapter.capabilities.maxContextTokens;
704
719
  const preflight = preflightTokenCheck(completedHistory, prompt, maxContext);
705
- if (preflight.shouldStop) {
706
- console.error(chalk4.yellow(` Token budget at ${Math.round(preflight.utilizationRatio * 100)}% (${preflight.totalTokensUsed}/${maxContext}). Consider completing this debate.`));
720
+ if (preflight.shouldStop && !options.force) {
721
+ console.error(chalk4.red(`Token budget at ${Math.round(preflight.utilizationRatio * 100)}% (${preflight.totalTokensUsed}/${maxContext}). Debate blocked \u2014 use --force to override.`));
722
+ db.close();
723
+ process.exit(1);
724
+ } else if (preflight.shouldStop && options.force) {
725
+ console.error(chalk4.yellow(` Token budget at ${Math.round(preflight.utilizationRatio * 100)}% (${preflight.totalTokensUsed}/${maxContext}) \u2014 forced past budget limit.`));
707
726
  } else if (preflight.shouldSummarize) {
708
727
  console.error(chalk4.yellow(` Token budget at ${Math.round(preflight.utilizationRatio * 100)}%. Older rounds will be summarized on resume failure.`));
709
728
  }
710
729
  const overflowCheck = sessionMgr.preCallOverflowCheck(unifiedSession.id);
711
730
  if (overflowCheck.rolled) {
712
731
  console.error(chalk4.yellow(` ${overflowCheck.message}`));
732
+ console.error(chalk4.yellow(" Warning: Session rolled over \u2014 previous conversation context has been lost."));
733
+ sessionMgr.updateThreadId(unifiedSession.id, null);
713
734
  existingSessionId = void 0;
714
735
  }
715
736
  try {
@@ -781,11 +802,16 @@ async function debateTurnCommand(debateId, prompt, options) {
781
802
  proposerState.resumeStats = stats;
782
803
  store.saveState(debateId, "proposer", proposerState);
783
804
  }
805
+ if (options.output) {
806
+ writeFileSync(options.output, result.text, "utf-8");
807
+ console.error(chalk4.dim(`Full response written to ${options.output}`));
808
+ }
784
809
  const output = {
785
810
  debateId,
786
811
  round: newRound,
787
812
  response: result.text.slice(0, 2e3),
788
813
  responseTruncated: result.text.length > 2e3,
814
+ responseFile: options.output ?? void 0,
789
815
  sessionId: result.sessionId,
790
816
  resumed,
791
817
  cached: false,
@@ -800,7 +826,7 @@ Round ${newRound} \u2014 Stance: ${verdict.stance}`));
800
826
  for (const line of previewLines) {
801
827
  console.error(chalk4.dim(` ${line.trim().slice(0, 120)}`));
802
828
  }
803
- console.error(chalk4.dim(`Duration: ${(result.durationMs / 1e3).toFixed(1)}s | Tokens: ${result.usage?.totalTokens ?? "?"} | Resumed: ${resumed}`));
829
+ console.error(chalk4.dim(`Duration: ${(result.durationMs / 1e3).toFixed(1)}s | Output: ${result.usage?.outputTokens ?? "?"} tokens | Input: ${result.usage?.inputTokens ?? "?"} (includes sandbox) | Resumed: ${resumed}`));
804
830
  console.log(JSON.stringify(output, null, 2));
805
831
  } catch (error) {
806
832
  msgStore.markFailed(msgId, error instanceof Error ? error.message : String(error));
@@ -813,6 +839,10 @@ Round ${newRound} \u2014 Stance: ${verdict.stance}`));
813
839
  process.exit(1);
814
840
  }
815
841
  }
842
+ async function debateNextCommand(debateId, options) {
843
+ const prompt = "Continue your analysis. Respond to the previous round's arguments and refine your position.";
844
+ return debateTurnCommand(debateId, prompt, options);
845
+ }
816
846
  async function debateStatusCommand(debateId) {
817
847
  let db;
818
848
  try {
@@ -893,7 +923,7 @@ async function debateListCommand(options) {
893
923
  process.exit(1);
894
924
  }
895
925
  }
896
- async function debateHistoryCommand(debateId) {
926
+ async function debateHistoryCommand(debateId, options = {}) {
897
927
  let db;
898
928
  try {
899
929
  const dbPath = getDbPath();
@@ -937,6 +967,29 @@ async function debateHistoryCommand(debateId) {
937
967
  completedAt: m.completedAt ? new Date(m.completedAt).toISOString() : null
938
968
  }))
939
969
  };
970
+ if (options.output) {
971
+ const fullOutput = {
972
+ ...output,
973
+ messages: history.map((m) => ({
974
+ round: m.round,
975
+ role: m.role,
976
+ bridge: m.bridge,
977
+ model: m.model,
978
+ status: m.status,
979
+ stance: m.stance,
980
+ confidence: m.confidence,
981
+ durationMs: m.durationMs,
982
+ sessionId: m.sessionId,
983
+ promptText: m.promptText,
984
+ responseText: m.responseText ?? null,
985
+ error: m.error,
986
+ createdAt: new Date(m.createdAt).toISOString(),
987
+ completedAt: m.completedAt ? new Date(m.completedAt).toISOString() : null
988
+ }))
989
+ };
990
+ writeFileSync(options.output, JSON.stringify(fullOutput, null, 2), "utf-8");
991
+ console.error(chalk4.dim(`Full history written to ${options.output}`));
992
+ }
940
993
  console.log(JSON.stringify(output, null, 2));
941
994
  db.close();
942
995
  } catch (error) {
@@ -1199,8 +1252,8 @@ Scan complete in ${(durationMs / 1e3).toFixed(1)}s`));
1199
1252
  durationMs
1200
1253
  });
1201
1254
  if (options.output) {
1202
- const { writeFileSync: writeFileSync4 } = await import("fs");
1203
- writeFileSync4(options.output, JSON.stringify(report, null, 2), "utf-8");
1255
+ const { writeFileSync: writeFileSync5 } = await import("fs");
1256
+ writeFileSync5(options.output, JSON.stringify(report, null, 2), "utf-8");
1204
1257
  console.error(chalk5.green(` Findings written to ${options.output}`));
1205
1258
  }
1206
1259
  const reportJson = JSON.stringify(report, null, 2);
@@ -1618,7 +1671,7 @@ async function eventsCommand(options) {
1618
1671
 
1619
1672
  // src/commands/fix.ts
1620
1673
  import { execFileSync as execFileSync2, execSync as execSync3 } from "child_process";
1621
- import { readFileSync as readFileSync2, writeFileSync } from "fs";
1674
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
1622
1675
  import { resolve } from "path";
1623
1676
  import {
1624
1677
  DEFAULT_RULES,
@@ -1666,7 +1719,7 @@ function applyFix(fix, projectDir) {
1666
1719
  const trimmedOld = fix.oldCode.trim();
1667
1720
  if (content.includes(trimmedOld)) {
1668
1721
  const updated = content.replace(trimmedOld, fix.newCode.trim());
1669
- writeFileSync(filePath, updated, "utf-8");
1722
+ writeFileSync2(filePath, updated, "utf-8");
1670
1723
  return true;
1671
1724
  }
1672
1725
  return false;
@@ -1676,7 +1729,7 @@ function applyFix(fix, projectDir) {
1676
1729
  const newLines = fix.newCode.trim().split("\n");
1677
1730
  const oldLineCount = Math.max(1, newLines.length);
1678
1731
  lines.splice(lineIdx, oldLineCount, ...newLines);
1679
- writeFileSync(filePath, lines.join("\n"), "utf-8");
1732
+ writeFileSync2(filePath, lines.join("\n"), "utf-8");
1680
1733
  return true;
1681
1734
  }
1682
1735
  async function fixCommand(fileOrGlob, options) {
@@ -1999,7 +2052,7 @@ Initialized with '${presetName}' preset`));
1999
2052
  }
2000
2053
 
2001
2054
  // src/commands/install-skills.ts
2002
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
2055
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
2003
2056
  import { dirname, join as join5 } from "path";
2004
2057
  import chalk11 from "chalk";
2005
2058
  var SKILLS = [
@@ -2449,7 +2502,7 @@ async function installSkillsCommand(options) {
2449
2502
  continue;
2450
2503
  }
2451
2504
  mkdirSync2(dir, { recursive: true });
2452
- writeFileSync2(fullPath, skill.content, "utf-8");
2505
+ writeFileSync3(fullPath, skill.content, "utf-8");
2453
2506
  console.error(chalk11.green(` OK ${skill.path}`));
2454
2507
  installed++;
2455
2508
  }
@@ -2467,7 +2520,7 @@ async function installSkillsCommand(options) {
2467
2520
  const afterMarker = sectionEnd >= 0 ? existing.slice(sectionEnd + CLAUDE_MD_SECTION.trimEnd().length) : existing.slice(markerIdx + marker.length);
2468
2521
  const nextHeadingMatch = sectionEnd >= 0 ? null : afterMarker.match(/\n#{1,6} (?!CodeMoot)/);
2469
2522
  const after = nextHeadingMatch ? afterMarker.slice(nextHeadingMatch.index) : "";
2470
- writeFileSync2(claudeMdPath, before.trimEnd() + "\n" + CLAUDE_MD_SECTION + after, "utf-8");
2523
+ writeFileSync3(claudeMdPath, before.trimEnd() + "\n" + CLAUDE_MD_SECTION + after, "utf-8");
2471
2524
  console.error(chalk11.green(" OK CLAUDE.md (updated CodeMoot section)"));
2472
2525
  installed++;
2473
2526
  } else {
@@ -2475,12 +2528,12 @@ async function installSkillsCommand(options) {
2475
2528
  skipped++;
2476
2529
  }
2477
2530
  } else {
2478
- writeFileSync2(claudeMdPath, existing.trimEnd() + "\n" + CLAUDE_MD_SECTION, "utf-8");
2531
+ writeFileSync3(claudeMdPath, existing.trimEnd() + "\n" + CLAUDE_MD_SECTION, "utf-8");
2479
2532
  console.error(chalk11.green(" OK CLAUDE.md (appended CodeMoot section)"));
2480
2533
  installed++;
2481
2534
  }
2482
2535
  } else {
2483
- writeFileSync2(claudeMdPath, `# Project Instructions
2536
+ writeFileSync3(claudeMdPath, `# Project Instructions
2484
2537
  ${CLAUDE_MD_SECTION}`, "utf-8");
2485
2538
  console.error(chalk11.green(" OK CLAUDE.md (created with CodeMoot section)"));
2486
2539
  installed++;
@@ -2502,7 +2555,7 @@ ${CLAUDE_MD_SECTION}`, "utf-8");
2502
2555
  ...existing.hooks,
2503
2556
  PostToolUse: [...otherHooks, ...HOOKS_CONFIG.hooks.PostToolUse]
2504
2557
  };
2505
- writeFileSync2(settingsPath, JSON.stringify(existing, null, 2), "utf-8");
2558
+ writeFileSync3(settingsPath, JSON.stringify(existing, null, 2), "utf-8");
2506
2559
  console.error(chalk11.green(" OK .claude/settings.json (added post-commit hint hook)"));
2507
2560
  installed++;
2508
2561
  }
@@ -2513,7 +2566,7 @@ ${CLAUDE_MD_SECTION}`, "utf-8");
2513
2566
  }
2514
2567
  } else {
2515
2568
  mkdirSync2(settingsDir, { recursive: true });
2516
- writeFileSync2(settingsPath, JSON.stringify(HOOKS_CONFIG, null, 2), "utf-8");
2569
+ writeFileSync3(settingsPath, JSON.stringify(HOOKS_CONFIG, null, 2), "utf-8");
2517
2570
  console.error(chalk11.green(" OK .claude/settings.json (created with post-commit hook)"));
2518
2571
  installed++;
2519
2572
  }
@@ -2811,7 +2864,7 @@ async function jobsStatusCommand(jobId) {
2811
2864
  }
2812
2865
 
2813
2866
  // src/commands/plan.ts
2814
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
2867
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
2815
2868
  import {
2816
2869
  ModelRegistry as ModelRegistry4,
2817
2870
  Orchestrator,
@@ -2928,7 +2981,7 @@ async function planGenerateCommand(task, options) {
2928
2981
  maxRounds: options.rounds
2929
2982
  });
2930
2983
  if (options.output) {
2931
- writeFileSync3(options.output, result.finalOutput, "utf-8");
2984
+ writeFileSync4(options.output, result.finalOutput, "utf-8");
2932
2985
  console.error(chalk15.green(`Plan saved to ${options.output}`));
2933
2986
  }
2934
2987
  printSessionSummary(result);
@@ -3079,7 +3132,7 @@ Verdict: ${verdict.toUpperCase()} (${score ?? "?"}/10)`));
3079
3132
  };
3080
3133
  console.log(JSON.stringify(output, null, 2));
3081
3134
  if (options.output) {
3082
- writeFileSync3(options.output, JSON.stringify(output, null, 2), "utf-8");
3135
+ writeFileSync4(options.output, JSON.stringify(output, null, 2), "utf-8");
3083
3136
  console.error(chalk15.green(`Review saved to ${options.output}`));
3084
3137
  }
3085
3138
  db.close();
@@ -3985,11 +4038,17 @@ var plan = program.command("plan").description("Plan generation and review \u201
3985
4038
  plan.command("generate").description("Generate a plan using architect + reviewer loop").argument("<task>", "Task to plan").option("--rounds <n>", "Max plan-review rounds", (v) => Number.parseInt(v, 10), 3).option("--output <file>", "Save plan to file").action(planGenerateCommand);
3986
4039
  plan.command("review").description("Send a host-authored plan to codex for review").argument("<plan-file>", "Plan file to review (use - for stdin)").option("--build <id>", "Link review to a build ID").option("--phase <id>", 'Phase identifier (e.g. "1", "setup")').option("--timeout <seconds>", "Review timeout", (v) => Number.parseInt(v, 10), 300).option("--output <file>", "Save review result to file").action(planReviewCommand);
3987
4040
  var debate = program.command("debate").description("Multi-model debate with session persistence");
3988
- debate.command("start").description("Start a new debate").argument("<topic>", "Debate topic or question").option("--max-rounds <n>", "Max debate rounds", (v) => Number.parseInt(v, 10), 5).action(debateStartCommand);
3989
- debate.command("turn").description("Send a prompt to GPT and get critique (with session resume)").argument("<debate-id>", "Debate ID from start command").argument("<prompt>", "Prompt to send to GPT").option("--round <n>", "Round number", (v) => Number.parseInt(v, 10)).option("--timeout <seconds>", "Timeout in seconds", (v) => Number.parseInt(v, 10), 600).action(debateTurnCommand);
4041
+ debate.command("start").description("Start a new debate").argument("<topic>", "Debate topic or question").option("--max-rounds <n>", "Max debate rounds", (v) => Number.parseInt(v, 10), 5).option("--timeout <seconds>", "Default timeout for debate turns in seconds", (v) => {
4042
+ if (!/^\d+$/.test(v)) throw new InvalidArgumentError("Timeout must be a positive integer");
4043
+ const n = Number.parseInt(v, 10);
4044
+ if (n <= 0) throw new InvalidArgumentError("Timeout must be a positive integer");
4045
+ return n;
4046
+ }).action(debateStartCommand);
4047
+ debate.command("turn").description("Send a prompt to GPT and get critique (with session resume)").argument("<debate-id>", "Debate ID from start command").argument("<prompt>", "Prompt to send to GPT").option("--round <n>", "Round number", (v) => Number.parseInt(v, 10)).option("--timeout <seconds>", "Timeout in seconds", (v) => Number.parseInt(v, 10)).option("--output <file>", "Write full untruncated response to file").option("--force", "Continue past token budget limit", false).action(debateTurnCommand);
4048
+ debate.command("next").description("Continue debate with auto-generated prompt").argument("<debate-id>", "Debate ID").option("--timeout <seconds>", "Timeout in seconds", (v) => Number.parseInt(v, 10)).option("--output <file>", "Write full untruncated response to file").option("--force", "Continue past token budget limit", false).action(debateNextCommand);
3990
4049
  debate.command("status").description("Show debate status and session info").argument("<debate-id>", "Debate ID").action(debateStatusCommand);
3991
4050
  debate.command("list").description("List all debates").option("--status <status>", "Filter by status (active|completed|stale)").option("--limit <n>", "Max results", (v) => Number.parseInt(v, 10), 20).action(debateListCommand);
3992
- debate.command("history").description("Show full message history with token budget").argument("<debate-id>", "Debate ID").action(debateHistoryCommand);
4051
+ debate.command("history").description("Show full message history with token budget").argument("<debate-id>", "Debate ID").option("--output <file>", "Write full untruncated history to file").action(debateHistoryCommand);
3993
4052
  debate.command("complete").description("Mark a debate as completed").argument("<debate-id>", "Debate ID").action(debateCompleteCommand);
3994
4053
  var build = program.command("build").description("Automated build loop: debate \u2192 plan \u2192 implement \u2192 review \u2192 fix");
3995
4054
  build.command("start").description("Start a new build session").argument("<task>", "Task description").option("--max-rounds <n>", "Max debate rounds", (v) => Number.parseInt(v, 10), 5).option("--allow-dirty", "Allow starting with dirty working tree (auto-stashes)").action(buildStartCommand);