@sushanglewis/lincoln 1.5.0 → 1.5.2

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 (305) hide show
  1. package/dist/main.d.ts +3 -0
  2. package/dist/main.js +1286 -916
  3. package/dist/main.js.map +1 -1
  4. package/dist/postinstall.d.ts +3 -0
  5. package/dist/postinstall.js +18 -0
  6. package/dist/postinstall.js.map +1 -0
  7. package/framework/.claude/agents/_contract.md +79 -0
  8. package/framework/.claude/agents/architect.md +31 -0
  9. package/framework/.claude/agents/default.md +151 -0
  10. package/framework/.claude/agents/designer.md +33 -0
  11. package/framework/.claude/agents/engineer.md +42 -0
  12. package/framework/.claude/agents/external/NOTICES.md +12 -0
  13. package/framework/.claude/agents/external/everything-claude-code.manifest.yaml +23 -0
  14. package/framework/.claude/agents/external/oh-my-claudecode.manifest.yaml +26 -0
  15. package/framework/.claude/agents/external/wshobson-agents.manifest.yaml +23 -0
  16. package/framework/.claude/agents/frontend-engineer.md +32 -0
  17. package/framework/.claude/agents/knowledge-curator.md +28 -0
  18. package/framework/.claude/agents/pm.md +38 -0
  19. package/framework/.claude/agents/qa.md +28 -0
  20. package/framework/.claude/agents/release-coordinator.md +25 -0
  21. package/framework/.claude/agents/researcher.md +41 -0
  22. package/framework/.claude/harnesses/claude-code.yaml +15 -0
  23. package/framework/.claude/harnesses/codex.yaml +40 -0
  24. package/framework/.claude/harnesses/command-map.yaml +255 -0
  25. package/framework/.claude/harnesses/opencode.yaml +32 -0
  26. package/framework/.claude/harnesses/scenarios.yaml +45 -0
  27. package/framework/.claude/hooks/on-pr-event.sh +69 -0
  28. package/framework/.claude/hooks/on-session-start.sh +427 -0
  29. package/framework/.claude/hooks/on-stop.sh +104 -0
  30. package/framework/.claude/hooks/post-tool-use.sh +161 -0
  31. package/framework/.claude/hooks/pre-tool-use.sh +246 -0
  32. package/framework/.claude/schemas/agent-definition.schema.json +35 -0
  33. package/framework/.claude/schemas/benchmark-report.schema.json +148 -0
  34. package/framework/.claude/schemas/pm-to-ux-handoff.schema.yaml +128 -0
  35. package/framework/.claude/schemas/stage-definition.schema.json +233 -0
  36. package/framework/.claude/schemas/workflow-stage.schema.json +86 -0
  37. package/framework/.claude/schemas/workflow-template.schema.json +88 -0
  38. package/framework/.claude/settings.json +14 -0
  39. package/framework/.claude/skills/build-product-prototype/SKILL.md +35 -0
  40. package/framework/.claude/skills/build-product-prototype/prompts/main.md +78 -0
  41. package/framework/.claude/skills/clarify-requirements/SKILL.md +32 -0
  42. package/framework/.claude/skills/clarify-requirements/prompts/main.md +81 -0
  43. package/framework/.claude/skills/dependencies.yaml +139 -0
  44. package/framework/.claude/skills/draft-product-design/SKILL.md +40 -0
  45. package/framework/.claude/skills/draft-product-design/prompts/main.md +47 -0
  46. package/framework/.claude/skills/lc-analyze-frameworks/SKILL.md +43 -0
  47. package/framework/.claude/skills/lc-analyze-frameworks/prompts/main.md +95 -0
  48. package/framework/.claude/skills/lc-benchmark/SKILL.md +61 -0
  49. package/framework/.claude/skills/lc-build-codebase-knowledge/SKILL.md +38 -0
  50. package/framework/.claude/skills/lc-build-codebase-knowledge/prompts/build-codebase-knowledge.md +61 -0
  51. package/framework/.claude/skills/lc-collect-intelligence/SKILL.md +43 -0
  52. package/framework/.claude/skills/lc-collect-intelligence/prompts/main.md +72 -0
  53. package/framework/.claude/skills/lc-competitive-analysis/SKILL.md +43 -0
  54. package/framework/.claude/skills/lc-competitive-analysis/prompts/main.md +70 -0
  55. package/framework/.claude/skills/lc-explore-opensource/SKILL.md +36 -0
  56. package/framework/.claude/skills/lc-explore-opensource/prompts/explore-opensource.md +77 -0
  57. package/framework/.claude/skills/lc-first-principles/SKILL.md +43 -0
  58. package/framework/.claude/skills/lc-first-principles/prompts/main.md +73 -0
  59. package/framework/.claude/skills/lc-handoff/SKILL.md +34 -0
  60. package/framework/.claude/skills/lc-init-branch/SKILL.md +52 -0
  61. package/framework/.claude/skills/lc-market-research/SKILL.md +43 -0
  62. package/framework/.claude/skills/lc-market-research/prompts/main.md +79 -0
  63. package/framework/.claude/skills/lc-product-research/SKILL.md +43 -0
  64. package/framework/.claude/skills/lc-product-research/prompts/main.md +80 -0
  65. package/framework/.claude/skills/lc-research-report/SKILL.md +43 -0
  66. package/framework/.claude/skills/lc-research-report/prompts/main.md +90 -0
  67. package/framework/.claude/skills/lc-research-scope/SKILL.md +43 -0
  68. package/framework/.claude/skills/lc-research-scope/prompts/main.md +82 -0
  69. package/framework/.claude/skills/lc-setup/SKILL.md +68 -0
  70. package/framework/.claude/skills/lc-setup/prompts/main.md +88 -0
  71. package/framework/.claude/skills/lc-stage/SKILL.md +45 -0
  72. package/framework/.claude/skills/lc-stakeholder-research/SKILL.md +43 -0
  73. package/framework/.claude/skills/lc-stakeholder-research/prompts/main.md +65 -0
  74. package/framework/.claude/skills/lc-status/SKILL.md +25 -0
  75. package/framework/.claude/skills/lc-storytelling/SKILL.md +43 -0
  76. package/framework/.claude/skills/lc-storytelling/prompts/main.md +77 -0
  77. package/framework/.claude/skills/lc-wf/SKILL.md +54 -0
  78. package/framework/.claude/skills/lc-workflow/SKILL.md +48 -0
  79. package/framework/.claude/skills/lc-workflow/validators/validate.py +755 -0
  80. package/framework/.claude/skills/lc-workflow-router/SKILL.md +41 -0
  81. package/framework/.claude/skills/lc-workflow-router/prompts/intake-prompt.md +91 -0
  82. package/framework/.claude/skills/lc-workflow-router/prompts/router-prompt.md +44 -0
  83. package/framework/.claude/skills/plan-tdd-development/SKILL.md +31 -0
  84. package/framework/.claude/skills/plan-tdd-development/prompts/main.md +44 -0
  85. package/framework/.claude/skills/process-interview/SKILL.md +32 -0
  86. package/framework/.claude/skills/process-interview/prompts/main.md +47 -0
  87. package/framework/.claude/skills/propose-with-openspec/SKILL.md +37 -0
  88. package/framework/.claude/skills/propose-with-openspec/prompts/main.md +53 -0
  89. package/framework/.claude/skills/split-to-github/SKILL.md +32 -0
  90. package/framework/.claude/skills/split-to-github/prompts/main.md +42 -0
  91. package/framework/.claude/skills/sync-to-knowledge/SKILL.md +35 -0
  92. package/framework/.claude/skills/sync-to-knowledge/prompts/main.md +54 -0
  93. package/framework/.claude/skills/workflow-continue/SKILL.md +24 -0
  94. package/framework/.claude/skills/workflow-continue/prompts/main.md +25 -0
  95. package/framework/.claude/stages/build-codebase-knowledge.yaml +42 -0
  96. package/framework/.claude/stages/clarify.yaml +59 -0
  97. package/framework/.claude/stages/explore-opensource.yaml +40 -0
  98. package/framework/.claude/stages/implement.yaml +60 -0
  99. package/framework/.claude/stages/ingest.yaml +40 -0
  100. package/framework/.claude/stages/lc-analyze-frameworks.yaml +37 -0
  101. package/framework/.claude/stages/lc-collect-intelligence.yaml +37 -0
  102. package/framework/.claude/stages/lc-competitive-analysis.yaml +37 -0
  103. package/framework/.claude/stages/lc-first-principles.yaml +37 -0
  104. package/framework/.claude/stages/lc-market-research.yaml +37 -0
  105. package/framework/.claude/stages/lc-product-research.yaml +37 -0
  106. package/framework/.claude/stages/lc-research-report.yaml +38 -0
  107. package/framework/.claude/stages/lc-research-scope.yaml +38 -0
  108. package/framework/.claude/stages/lc-stakeholder-research.yaml +37 -0
  109. package/framework/.claude/stages/lc-storytelling.yaml +37 -0
  110. package/framework/.claude/stages/product-design-docs.yaml +66 -0
  111. package/framework/.claude/stages/product-prototype.yaml +53 -0
  112. package/framework/.claude/stages/propose.yaml +45 -0
  113. package/framework/.claude/stages/split.yaml +41 -0
  114. package/framework/.claude/stages/sync-knowledge.yaml +49 -0
  115. package/framework/.claude/stages/tdd-development-plan.yaml +43 -0
  116. package/framework/.claude/stages/workflow-router.yaml +45 -0
  117. package/framework/.claude/templates/solo-workflow-context.yaml +23 -0
  118. package/framework/.claude/workflows/README.md +93 -0
  119. package/framework/.claude/workflows/bug-fix.yaml +81 -0
  120. package/framework/.claude/workflows/design-spike.yaml +55 -0
  121. package/framework/.claude/workflows/existing-project-iteration.yaml +106 -0
  122. package/framework/.claude/workflows/interview-to-knowledge.yaml +189 -0
  123. package/framework/.claude/workflows/oss-first-design.yaml +100 -0
  124. package/framework/.claude/workflows/pm-research.yaml +115 -0
  125. package/framework/.claude-plugin/hooks.json +6 -0
  126. package/framework/.claude-plugin/marketplace.json +32 -0
  127. package/framework/.claude-plugin/plugin.json +57 -0
  128. package/framework/.version-bump.json +43 -0
  129. package/framework/CLAUDE.md +140 -0
  130. package/framework/CONTRIBUTING.md +219 -0
  131. package/framework/LICENSE +21 -0
  132. package/framework/README.en.md +139 -0
  133. package/framework/README.md +139 -0
  134. package/framework/RELEASE.md +334 -0
  135. package/framework/SKILL.md +24 -0
  136. package/framework/USAGE.md +356 -0
  137. package/framework/manifest.json +1214 -0
  138. package/framework/requirements.txt +4 -0
  139. package/framework/scripts/__init__.py +0 -0
  140. package/framework/scripts/build_codebase_knowledge.py +76 -0
  141. package/framework/scripts/bump_version.py +181 -0
  142. package/framework/scripts/check-dependency-drift.py +69 -0
  143. package/framework/scripts/check-harness-drift.sh +57 -0
  144. package/framework/scripts/check-main-merge-hygiene.py +128 -0
  145. package/framework/scripts/check-skill-dependencies.sh +124 -0
  146. package/framework/scripts/init-lincoln-branch.sh +300 -0
  147. package/framework/scripts/init-project.sh +184 -0
  148. package/framework/scripts/lc-benchmark-cli.py +66 -0
  149. package/framework/scripts/lincoln-audit.py +288 -0
  150. package/framework/scripts/lincoln-setup.py +651 -0
  151. package/framework/scripts/lincoln-status.py +448 -0
  152. package/framework/scripts/lincoln_benchmark.py +224 -0
  153. package/framework/scripts/lincoln_benchmark_eval.py +104 -0
  154. package/framework/scripts/lincoln_benchmark_metrics.py +818 -0
  155. package/framework/scripts/lincoln_benchmark_report.py +141 -0
  156. package/framework/scripts/lincoln_command_map.py +289 -0
  157. package/framework/scripts/lincoln_dependency_manager.py +335 -0
  158. package/framework/scripts/lincoln_documents.py +175 -0
  159. package/framework/scripts/lincoln_harness_adapter.py +319 -0
  160. package/framework/scripts/lincoln_index.py +255 -0
  161. package/framework/scripts/lincoln_paths.py +198 -0
  162. package/framework/scripts/lincoln_prd.py +227 -0
  163. package/framework/scripts/lincoln_role.py +46 -0
  164. package/framework/scripts/lincoln_scenario.py +59 -0
  165. package/framework/scripts/lincoln_session_start_metrics.py +71 -0
  166. package/framework/scripts/lincoln_skill_prompt.py +48 -0
  167. package/framework/scripts/lincoln_trace.py +335 -0
  168. package/framework/scripts/lincoln_workflow.py +199 -0
  169. package/framework/scripts/list-active-lincoln-branches.sh +87 -0
  170. package/framework/scripts/package-lincoln-plugin.py +340 -0
  171. package/framework/scripts/run-infrastructure-tests.py +84 -0
  172. package/framework/scripts/stage_loader.py +1071 -0
  173. package/framework/scripts/static-check.sh +356 -0
  174. package/framework/scripts/sync-external-agents.sh +210 -0
  175. package/framework/scripts/sync-framework-package.mjs +218 -0
  176. package/framework/scripts/task_tool_guard.py +213 -0
  177. package/framework/scripts/track-artifacts.py +140 -0
  178. package/framework/scripts/validate_stage.py +350 -0
  179. package/{README.md → framework/tools/lincoln/README.md} +4 -4
  180. package/framework/tools/lincoln/package-lock.json +3349 -0
  181. package/framework/tools/lincoln/package.json +48 -0
  182. package/framework/tools/lincoln/src/cli.tsx +60 -0
  183. package/framework/tools/lincoln/src/components/AudioMeter.tsx +34 -0
  184. package/framework/tools/lincoln/src/components/CancelledScreen.tsx +11 -0
  185. package/framework/tools/lincoln/src/components/DevicesMenu.tsx +79 -0
  186. package/framework/tools/lincoln/src/components/HintBar.tsx +25 -0
  187. package/framework/tools/lincoln/src/components/LogView.tsx +36 -0
  188. package/framework/tools/lincoln/src/components/ModelMenu.tsx +122 -0
  189. package/framework/tools/lincoln/src/components/ReadyScreen.tsx +54 -0
  190. package/framework/tools/lincoln/src/components/RecordingApp.tsx +211 -0
  191. package/framework/tools/lincoln/src/components/RecordingScreen.tsx +41 -0
  192. package/framework/tools/lincoln/src/components/SelectMenu.tsx +53 -0
  193. package/framework/tools/lincoln/src/components/StatusPanel.tsx +52 -0
  194. package/framework/tools/lincoln/src/components/StopConfirmation.tsx +32 -0
  195. package/framework/tools/lincoln/src/config/args.ts +35 -0
  196. package/framework/tools/lincoln/src/config/loadConfig.ts +81 -0
  197. package/framework/tools/lincoln/src/config/resolveConfig.ts +85 -0
  198. package/framework/tools/lincoln/src/devices/listDevices.ts +11 -0
  199. package/framework/tools/lincoln/src/devices/parseDevices.ts +25 -0
  200. package/framework/tools/lincoln/src/hooks/useKeyHandler.ts +63 -0
  201. package/framework/tools/lincoln/src/main.tsx +4 -0
  202. package/framework/tools/lincoln/src/models/models.ts +34 -0
  203. package/framework/tools/lincoln/src/models/runWarmup.ts +46 -0
  204. package/framework/tools/lincoln/src/models/warmupProgress.ts +30 -0
  205. package/framework/tools/lincoln/src/recording/spawnRecorder.ts +127 -0
  206. package/framework/tools/lincoln/src/recording/useRecorder.ts +146 -0
  207. package/framework/tools/lincoln/src/utils/amplitudeToMeter.ts +4 -0
  208. package/framework/tools/lincoln/src/utils/formatDuration.ts +6 -0
  209. package/framework/tools/lincoln/src/utils/sessionId.ts +25 -0
  210. package/framework/tools/lincoln/tests/cli.test.tsx +52 -0
  211. package/framework/tools/lincoln/tests/components/AudioMeter.test.tsx +31 -0
  212. package/framework/tools/lincoln/tests/components/CancelledScreen.test.tsx +12 -0
  213. package/framework/tools/lincoln/tests/components/ReadyScreen.test.tsx +68 -0
  214. package/framework/tools/lincoln/tests/components/RecordingApp.menus.test.tsx +158 -0
  215. package/framework/tools/lincoln/tests/components/RecordingApp.test.tsx +248 -0
  216. package/framework/tools/lincoln/tests/components/RecordingScreen.test.tsx +37 -0
  217. package/framework/tools/lincoln/tests/components/SelectMenu.test.tsx +59 -0
  218. package/framework/tools/lincoln/tests/components/StopConfirmation.test.tsx +38 -0
  219. package/framework/tools/lincoln/tests/config/args.test.ts +49 -0
  220. package/framework/tools/lincoln/tests/config/loadConfig.test.ts +59 -0
  221. package/framework/tools/lincoln/tests/config/resolveConfig.test.ts +72 -0
  222. package/framework/tools/lincoln/tests/devices/parseDevices.test.ts +21 -0
  223. package/framework/tools/lincoln/tests/hooks/useKeyHandler.enabled.test.tsx +44 -0
  224. package/framework/tools/lincoln/tests/hooks/useKeyHandler.test.tsx +130 -0
  225. package/framework/tools/lincoln/tests/models/models.test.ts +27 -0
  226. package/framework/tools/lincoln/tests/models/warmupProgress.test.ts +25 -0
  227. package/framework/tools/lincoln/tests/recording/spawnRecorder.test.ts +85 -0
  228. package/framework/tools/lincoln/tests/recording/useRecorder.test.tsx +172 -0
  229. package/framework/tools/lincoln/tests/utils/amplitudeToMeter.test.ts +22 -0
  230. package/framework/tools/lincoln/tests/utils/formatDuration.test.ts +11 -0
  231. package/framework/tools/lincoln/tests/utils/sessionId.test.ts +30 -0
  232. package/framework/tools/lincoln/tsconfig.json +22 -0
  233. package/framework/tools/lincoln/tsconfig.tests.json +10 -0
  234. package/framework/tools/lincoln/tsup.config.ts +17 -0
  235. package/framework/tools/lincoln/vitest.config.ts +20 -0
  236. package/framework/tools/lincoln-installer/package-lock.json +3611 -0
  237. package/framework/tools/lincoln-installer/package.json +53 -0
  238. package/framework/tools/lincoln-installer/src/cli.tsx +63 -0
  239. package/framework/tools/lincoln-installer/src/components/HarnessSelectScreen.tsx +43 -0
  240. package/framework/tools/lincoln-installer/src/components/InstallerApp.tsx +98 -0
  241. package/framework/tools/lincoln-installer/src/components/OptionsScreen.tsx +56 -0
  242. package/framework/tools/lincoln-installer/src/components/ProgressScreen.tsx +25 -0
  243. package/framework/tools/lincoln-installer/src/components/ResultScreen.tsx +40 -0
  244. package/framework/tools/lincoln-installer/src/components/SelectMenu.tsx +73 -0
  245. package/framework/tools/lincoln-installer/src/components/SummaryScreen.tsx +49 -0
  246. package/framework/tools/lincoln-installer/src/components/UpdateApp.tsx +135 -0
  247. package/framework/tools/lincoln-installer/src/components/WelcomeScreen.tsx +22 -0
  248. package/framework/tools/lincoln-installer/src/config/args.ts +76 -0
  249. package/framework/tools/lincoln-installer/src/config/resolveConfig.ts +33 -0
  250. package/framework/tools/lincoln-installer/src/config/updateArgs.ts +67 -0
  251. package/framework/tools/lincoln-installer/src/hooks/useSetup.ts +149 -0
  252. package/framework/tools/lincoln-installer/src/main.tsx +11 -0
  253. package/framework/tools/lincoln-installer/src/types.ts +19 -0
  254. package/framework/tools/lincoln-installer/src/update-main.tsx +11 -0
  255. package/framework/tools/lincoln-installer/src/update.tsx +90 -0
  256. package/framework/tools/lincoln-installer/src/utils/detectHarness.ts +51 -0
  257. package/framework/tools/lincoln-installer/src/utils/runSetup.ts +62 -0
  258. package/framework/tools/lincoln-installer/src/utils/updateRelease.ts +454 -0
  259. package/framework/tools/lincoln-installer/tests/components/InstallerApp.test.tsx +88 -0
  260. package/framework/tools/lincoln-installer/tests/components/SelectMenu.test.tsx +26 -0
  261. package/framework/tools/lincoln-installer/tests/components/UpdateApp.test.tsx +170 -0
  262. package/framework/tools/lincoln-installer/tests/components/screens.test.tsx +310 -0
  263. package/framework/tools/lincoln-installer/tests/config/args.test.ts +66 -0
  264. package/framework/tools/lincoln-installer/tests/config/resolveConfig.test.ts +75 -0
  265. package/framework/tools/lincoln-installer/tests/config/updateArgs.test.ts +52 -0
  266. package/framework/tools/lincoln-installer/tests/hooks/useSetup.test.tsx +213 -0
  267. package/framework/tools/lincoln-installer/tests/update.test.tsx +108 -0
  268. package/framework/tools/lincoln-installer/tests/utils/detectHarness.test.ts +40 -0
  269. package/framework/tools/lincoln-installer/tests/utils/runSetup.test.ts +45 -0
  270. package/framework/tools/lincoln-installer/tests/utils/updateRelease.test.ts +559 -0
  271. package/framework/tools/lincoln-installer/tsconfig.json +22 -0
  272. package/framework/tools/lincoln-installer/tsconfig.tests.json +8 -0
  273. package/framework/tools/lincoln-installer/tsup.config.ts +17 -0
  274. package/framework/tools/lincoln-installer/vitest.config.ts +20 -0
  275. package/framework/tools/lincoln-record/Cargo.lock +1896 -0
  276. package/framework/tools/lincoln-record/Cargo.toml +24 -0
  277. package/framework/tools/lincoln-record/diarize/diarize.py +117 -0
  278. package/framework/tools/lincoln-record/diarize/merge.py +55 -0
  279. package/framework/tools/lincoln-record/src/audio/capture/microphone.rs +194 -0
  280. package/framework/tools/lincoln-record/src/audio/capture/mod.rs +1 -0
  281. package/framework/tools/lincoln-record/src/audio/devices.rs +20 -0
  282. package/framework/tools/lincoln-record/src/audio/mod.rs +5 -0
  283. package/framework/tools/lincoln-record/src/audio/resampler.rs +44 -0
  284. package/framework/tools/lincoln-record/src/audio/saver.rs +87 -0
  285. package/framework/tools/lincoln-record/src/audio/source.rs +104 -0
  286. package/framework/tools/lincoln-record/src/cli.rs +96 -0
  287. package/framework/tools/lincoln-record/src/config.rs +135 -0
  288. package/framework/tools/lincoln-record/src/lib.rs +6 -0
  289. package/framework/tools/lincoln-record/src/main.rs +242 -0
  290. package/framework/tools/lincoln-record/src/model/mod.rs +248 -0
  291. package/framework/tools/lincoln-record/src/output/metadata.rs +30 -0
  292. package/framework/tools/lincoln-record/src/output/mod.rs +2 -0
  293. package/framework/tools/lincoln-record/src/output/transcript.rs +42 -0
  294. package/framework/tools/lincoln-record/src/transcription/diarization.rs +64 -0
  295. package/framework/tools/lincoln-record/src/transcription/mod.rs +3 -0
  296. package/framework/tools/lincoln-record/src/transcription/provider.rs +43 -0
  297. package/framework/tools/lincoln-record/src/transcription/whisper.rs +176 -0
  298. package/framework/tools/lincoln-record/tests/cli_args.rs +103 -0
  299. package/framework/tools/lincoln-record/tests/config_test.rs +70 -0
  300. package/framework/tools/lincoln-record/tests/devices_test.rs +23 -0
  301. package/framework/tools/lincoln-record/tests/diarization_tests.rs +101 -0
  302. package/framework/tools/lincoln-record/tests/integration_tests.rs +44 -0
  303. package/framework/tools/lincoln-record/tests/model_test.rs +68 -0
  304. package/framework/tools/lincoln-record/tests/transcription_tests.rs +73 -0
  305. package/package.json +19 -13
package/dist/main.js CHANGED
@@ -1,1009 +1,1379 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // src/cli.tsx
4
- import { Box as Box8, Text as Text7 } from "ink";
3
+ // src/main.ts
4
+ import fs11 from "fs";
5
+ import path12 from "path";
6
+ import { fileURLToPath as fileURLToPath2 } from "url";
5
7
 
6
- // src/config/args.ts
7
- import { parseArgs as nodeParseArgs } from "util";
8
+ // src/cli.ts
9
+ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
10
+ "yes",
11
+ "y",
12
+ "dry-run",
13
+ "force",
14
+ "check",
15
+ "json",
16
+ "help",
17
+ "h",
18
+ "no-venv"
19
+ ]);
8
20
  function parseArgs(argv) {
9
- const { values } = nodeParseArgs({
10
- args: argv,
11
- options: {
12
- topic: { type: "string" },
13
- "design-id": { type: "string" },
14
- branch: { type: "string" },
15
- "session-id": { type: "string" },
16
- "no-tui": { type: "boolean", default: false },
17
- help: { type: "boolean", short: "h", default: false }
18
- },
19
- strict: false,
20
- allowPositionals: true
21
- });
21
+ const args = argv.slice(2);
22
+ const flags = {};
23
+ const positional = [];
24
+ for (let i = 0; i < args.length; i++) {
25
+ const arg = args[i];
26
+ if (arg.startsWith("--")) {
27
+ const key = arg.replace(/^--/, "");
28
+ const next = args[i + 1];
29
+ if (!BOOLEAN_FLAGS.has(key) && next && !next.startsWith("-")) {
30
+ flags[key] = next;
31
+ i++;
32
+ } else {
33
+ flags[key] = true;
34
+ }
35
+ } else if (arg.startsWith("-")) {
36
+ const key = arg.replace(/^-/, "");
37
+ const next = args[i + 1];
38
+ if (!BOOLEAN_FLAGS.has(key) && next && !next.startsWith("-")) {
39
+ flags[key] = next;
40
+ i++;
41
+ } else {
42
+ flags[key] = true;
43
+ }
44
+ } else {
45
+ positional.push(arg);
46
+ }
47
+ }
48
+ const command = positional[0] || "help";
49
+ const validCommands = /* @__PURE__ */ new Set([
50
+ "install",
51
+ "update",
52
+ "use",
53
+ "doctor",
54
+ "init-project",
55
+ "migrate-project",
56
+ "record",
57
+ "help",
58
+ "--help",
59
+ "-h"
60
+ ]);
61
+ if (command === "help" || command === "--help" || command === "-h") {
62
+ return { command: "help", args: positional.slice(1), flags };
63
+ }
64
+ if (!validCommands.has(command)) {
65
+ return { command: "help", args: positional, flags };
66
+ }
67
+ return { command, args: positional.slice(1), flags };
68
+ }
69
+
70
+ // src/commands/doctor.ts
71
+ import fs2 from "fs";
72
+ import path3 from "path";
73
+ import { spawn } from "child_process";
74
+
75
+ // src/lib/paths.ts
76
+ import path from "path";
77
+ import os from "os";
78
+ function resolveLincolnPaths(overrideHomeDir) {
79
+ const homeDir = overrideHomeDir ?? os.homedir();
80
+ const lincolnHome = process.env.LINCOLN_HOME ? path.resolve(process.env.LINCOLN_HOME) : path.join(homeDir, ".lincoln");
22
81
  return {
23
- topic: values.topic,
24
- designId: values["design-id"],
25
- branch: values.branch,
26
- sessionId: values["session-id"],
27
- noTui: values["no-tui"],
28
- help: values.help
82
+ homeDir,
83
+ lincolnHome,
84
+ versionsDir: path.join(lincolnHome, "versions"),
85
+ currentDir: path.join(lincolnHome, "current"),
86
+ versionMarker: path.join(lincolnHome, "marker.json"),
87
+ venvDir: path.join(lincolnHome, "venv"),
88
+ claudeDir: path.join(homeDir, ".claude"),
89
+ codexDir: path.join(homeDir, ".codex"),
90
+ opencodeDir: path.join(homeDir, ".opencode")
29
91
  };
30
92
  }
31
93
 
32
- // src/config/resolveConfig.ts
33
- import { existsSync } from "fs";
34
- import { homedir } from "os";
35
- import { join } from "path";
36
-
37
- // src/config/loadConfig.ts
38
- import { readFileSync } from "fs";
39
- import yaml from "js-yaml";
40
- function parseConfig(content, path) {
41
- const trimmed = content.trim();
42
- if (!trimmed) {
43
- return {};
44
- }
45
- const isJson = trimmed.startsWith("{") || trimmed.startsWith("[");
46
- const raw = isJson ? JSON.parse(trimmed) : yaml.load(trimmed);
47
- if (raw === null || typeof raw !== "object") {
48
- return {};
94
+ // src/lib/versionMarker.ts
95
+ import fs from "fs";
96
+ import path2 from "path";
97
+ function readVersionMarker(paths) {
98
+ try {
99
+ const raw = fs.readFileSync(paths.versionMarker, "utf8");
100
+ const parsed = JSON.parse(raw);
101
+ if (typeof parsed.version === "string" && typeof parsed.installedAt === "string" && Array.isArray(parsed.harnesses)) {
102
+ return parsed;
103
+ }
104
+ return void 0;
105
+ } catch {
106
+ return void 0;
49
107
  }
50
- const data = raw;
108
+ }
109
+ function writeVersionMarker(paths, marker) {
110
+ fs.mkdirSync(path2.dirname(paths.versionMarker), { recursive: true });
111
+ fs.writeFileSync(paths.versionMarker, JSON.stringify(marker, null, 2) + "\n");
112
+ }
113
+
114
+ // src/commands/doctor.ts
115
+ var MIN_NODE_MAJOR = 20;
116
+ var MIN_PYTHON_MAJOR = 3;
117
+ var MIN_PYTHON_MINOR = 10;
118
+ function createDefaultDeps() {
119
+ const paths = resolveLincolnPaths();
120
+ const pythonCommand = resolvePythonCommand(paths);
51
121
  return {
52
- defaultTopic: asString(data.default_topic),
53
- defaultDesignId: asString(data.default_design_id),
54
- defaultBranch: asString(data.default_branch),
55
- autoProcess: asBoolean(data.auto_process),
56
- showAudioMeter: asBoolean(data.show_audio_meter),
57
- audioMeterStyle: asMeterStyle(data.audio_meter_style)
122
+ paths,
123
+ nodeVersion: () => process.version,
124
+ pythonVersion: () => queryCommandVersion(pythonCommand, ["--version"]),
125
+ pyyamlVersion: () => queryPyyamlVersion(pythonCommand),
126
+ npmVersion: () => queryCommandVersion("npm", ["--version"]),
127
+ projectRoot: process.cwd()
58
128
  };
59
129
  }
60
- function asString(value) {
61
- return typeof value === "string" ? value : void 0;
130
+ async function doctor(options = {}, deps = createDefaultDeps()) {
131
+ const checks = [];
132
+ checks.push(checkNode(deps.nodeVersion()));
133
+ checks.push(await checkPython(deps.pythonVersion));
134
+ checks.push(await checkPyyaml(deps.pyyamlVersion));
135
+ checks.push(await checkNpm(deps.npmVersion));
136
+ checks.push(checkGlobalMarker(deps.paths));
137
+ checks.push(checkPayloadHooks(deps.paths));
138
+ checks.push(checkVenv(deps.paths));
139
+ checks.push(checkProjectMarker(deps.projectRoot));
140
+ const hasError = checks.some((c) => c.status === "error");
141
+ const result = { code: hasError ? 1 : 0, checks };
142
+ if (options.json) {
143
+ console.log(JSON.stringify({ checks }, null, 2));
144
+ } else {
145
+ for (const check of checks) {
146
+ const icon = check.status === "ok" ? "\u2713" : check.status === "error" ? "\u2717" : check.status === "warn" ? "!" : "-";
147
+ console.log(`${icon} ${check.name}: ${check.message}`);
148
+ }
149
+ if (hasError) {
150
+ console.error("\nOne or more checks failed.");
151
+ }
152
+ }
153
+ return result;
62
154
  }
63
- function asBoolean(value) {
64
- return typeof value === "boolean" ? value : void 0;
155
+ function checkNode(version) {
156
+ const major = parseNodeMajor(version);
157
+ if (major === void 0) {
158
+ return { name: "node", status: "warn", message: `Could not parse Node version: ${version}` };
159
+ }
160
+ if (major < MIN_NODE_MAJOR) {
161
+ return { name: "node", status: "error", message: `Node ${version} is below the minimum required v${MIN_NODE_MAJOR}` };
162
+ }
163
+ return { name: "node", status: "ok", message: `Node ${version}` };
65
164
  }
66
- function asMeterStyle(value) {
67
- if (value === "bar" || value === "dot" || value === "wave") {
68
- return value;
165
+ async function checkPython(query) {
166
+ const version = (await query())?.trim();
167
+ if (!version) {
168
+ return { name: "python", status: "error", message: "Python 3 is not available" };
69
169
  }
70
- return void 0;
170
+ const parsed = parsePythonVersion(version);
171
+ if (!parsed) {
172
+ return { name: "python", status: "warn", message: `Could not parse Python version: ${version}` };
173
+ }
174
+ if (parsed.major < MIN_PYTHON_MAJOR || parsed.major === MIN_PYTHON_MAJOR && parsed.minor < MIN_PYTHON_MINOR) {
175
+ return {
176
+ name: "python",
177
+ status: "error",
178
+ message: `Python ${version} is below the minimum required ${MIN_PYTHON_MAJOR}.${MIN_PYTHON_MINOR}`
179
+ };
180
+ }
181
+ const display = version.replace(/^Python\s+/, "");
182
+ return { name: "python", status: "ok", message: `Python ${display}` };
71
183
  }
72
- function loadConfig(path) {
73
- let content;
74
- try {
75
- content = readFileSync(path, "utf-8");
76
- } catch (error) {
77
- throw new Error(`Config file not found: ${String(path)}`);
184
+ async function checkPyyaml(query) {
185
+ const version = await query();
186
+ if (!version) {
187
+ return { name: "pyyaml", status: "error", message: "PyYAML is not installed for Python 3" };
78
188
  }
79
- try {
80
- return parseConfig(content, path);
81
- } catch (error) {
82
- const cause = error instanceof Error ? error.message : String(error);
83
- throw new Error(`Failed to parse config ${String(path)}: ${cause}`);
189
+ return { name: "pyyaml", status: "ok", message: `PyYAML ${version}` };
190
+ }
191
+ async function checkNpm(query) {
192
+ const version = await query();
193
+ if (!version) {
194
+ return { name: "npm", status: "warn", message: "npm is not available" };
195
+ }
196
+ return { name: "npm", status: "ok", message: `npm ${version}` };
197
+ }
198
+ function checkGlobalMarker(paths) {
199
+ const marker = readVersionMarker(paths);
200
+ if (!marker) {
201
+ return { name: "global-marker", status: "warn", message: `No version marker at ${paths.versionMarker}` };
202
+ }
203
+ return { name: "global-marker", status: "ok", message: `Lincoln ${marker.version} installed` };
204
+ }
205
+ function checkPayloadHooks(paths) {
206
+ const hooksDir = path3.join(paths.currentDir, ".claude", "hooks");
207
+ if (!fs2.existsSync(hooksDir)) {
208
+ return { name: "payload-hooks", status: "warn", message: `Payload hooks not found at ${hooksDir}` };
209
+ }
210
+ const required = ["on-session-start.sh"];
211
+ const missing = required.filter((f) => !fs2.existsSync(path3.join(hooksDir, f)));
212
+ if (missing.length > 0) {
213
+ return { name: "payload-hooks", status: "warn", message: `Missing hooks: ${missing.join(", ")}` };
214
+ }
215
+ return { name: "payload-hooks", status: "ok", message: `Payload hooks present at ${hooksDir}` };
216
+ }
217
+ function checkVenv(paths) {
218
+ if (!fs2.existsSync(paths.venvDir)) {
219
+ return { name: "venv", status: "warn", message: `Virtual environment not found at ${paths.venvDir}` };
220
+ }
221
+ const pythonExe = path3.join(paths.venvDir, process.platform === "win32" ? "Scripts" : "bin", "python");
222
+ if (!fs2.existsSync(pythonExe)) {
223
+ return { name: "venv", status: "warn", message: `Virtual environment exists but python executable is missing` };
224
+ }
225
+ return { name: "venv", status: "ok", message: `Virtual environment present at ${paths.venvDir}` };
226
+ }
227
+ function checkProjectMarker(projectRoot) {
228
+ const markerPath = path3.join(projectRoot, ".lincoln.yaml");
229
+ if (!fs2.existsSync(markerPath)) {
230
+ return { name: "project-marker", status: "skip", message: `No .lincoln.yaml in ${projectRoot}` };
231
+ }
232
+ return { name: "project-marker", status: "ok", message: `Project marker found at ${markerPath}` };
233
+ }
234
+ function resolvePythonCommand(paths) {
235
+ const venvPython = path3.join(
236
+ paths.venvDir,
237
+ process.platform === "win32" ? "Scripts" : "bin",
238
+ process.platform === "win32" ? "python.exe" : "python"
239
+ );
240
+ if (fs2.existsSync(venvPython)) {
241
+ return venvPython;
242
+ }
243
+ return "python3";
244
+ }
245
+ function parseNodeMajor(version) {
246
+ const match = /^v?(\d+)/.exec(version);
247
+ if (!match) {
248
+ return void 0;
249
+ }
250
+ return Number(match[1]);
251
+ }
252
+ function parsePythonVersion(version) {
253
+ const match = /(\d+)\.(\d+)/.exec(version);
254
+ if (!match) {
255
+ return void 0;
84
256
  }
257
+ return { major: Number(match[1]), minor: Number(match[2]) };
85
258
  }
86
- function loadConfigOptional(path) {
259
+ async function queryCommandVersion(command, args) {
260
+ return new Promise((resolvePromise) => {
261
+ const child = spawn(command, args, {
262
+ stdio: ["ignore", "pipe", "ignore"],
263
+ shell: process.platform === "win32"
264
+ });
265
+ let stdout = "";
266
+ child.stdout?.on("data", (chunk) => {
267
+ stdout += chunk.toString();
268
+ });
269
+ child.on("close", (code) => {
270
+ const text = stdout.trim();
271
+ resolvePromise(code === 0 && text.length > 0 ? text : void 0);
272
+ });
273
+ child.on("error", () => resolvePromise(void 0));
274
+ });
275
+ }
276
+ async function queryPyyamlVersion(pythonCommand) {
277
+ return new Promise((resolvePromise) => {
278
+ const child = spawn(
279
+ pythonCommand,
280
+ ["-c", "import yaml; print(yaml.__version__)"],
281
+ { stdio: ["ignore", "pipe", "ignore"], shell: process.platform === "win32" }
282
+ );
283
+ let stdout = "";
284
+ child.stdout?.on("data", (chunk) => {
285
+ stdout += chunk.toString();
286
+ });
287
+ child.on("close", (code) => {
288
+ const text = stdout.trim();
289
+ resolvePromise(code === 0 && text.length > 0 ? text : void 0);
290
+ });
291
+ child.on("error", () => resolvePromise(void 0));
292
+ });
293
+ }
294
+
295
+ // src/lib/packageInfo.ts
296
+ import fs3 from "fs";
297
+ import path4 from "path";
298
+ import { fileURLToPath } from "url";
299
+ function readLocalPackageRoot() {
87
300
  try {
88
- return loadConfig(path);
89
- } catch (error) {
90
- const message = error instanceof Error ? error.message : String(error);
91
- if (message.includes("Config file not found")) {
92
- return {};
301
+ let dir = path4.dirname(fileURLToPath(import.meta.url));
302
+ const root = path4.parse(dir).root;
303
+ while (dir !== root) {
304
+ const pkgPath = path4.join(dir, "package.json");
305
+ if (fs3.existsSync(pkgPath)) {
306
+ const parsed = JSON.parse(fs3.readFileSync(pkgPath, "utf8"));
307
+ if (parsed.name === "@sushanglewis/lincoln") {
308
+ return dir;
309
+ }
310
+ }
311
+ dir = path4.dirname(dir);
93
312
  }
94
- throw error;
313
+ } catch {
314
+ }
315
+ return void 0;
316
+ }
317
+ function readLocalPackageVersion() {
318
+ try {
319
+ const root = readLocalPackageRoot();
320
+ if (!root) return void 0;
321
+ const parsed = JSON.parse(fs3.readFileSync(path4.join(root, "package.json"), "utf8"));
322
+ return typeof parsed.version === "string" ? parsed.version : void 0;
323
+ } catch {
324
+ return void 0;
95
325
  }
96
326
  }
327
+ function resolvePayloadRoot() {
328
+ const root = readLocalPackageRoot();
329
+ if (!root) return void 0;
330
+ const payload = path4.join(root, "framework");
331
+ return fs3.existsSync(payload) ? payload : void 0;
332
+ }
97
333
 
98
- // src/utils/sessionId.ts
99
- function generateSessionId(options = {}) {
100
- const now = options.now ?? /* @__PURE__ */ new Date();
101
- const date = now.toISOString().slice(0, 10);
102
- const slug = normalizeDescriptor(options.descriptor);
103
- return `${date}-${slug}`;
334
+ // src/lib/marker.ts
335
+ import fs4 from "fs";
336
+ import path5 from "path";
337
+ import yaml from "js-yaml";
338
+ function readProjectMarker(projectRoot) {
339
+ const markerPath = path5.join(projectRoot, ".lincoln.yaml");
340
+ if (!fs4.existsSync(markerPath)) {
341
+ return void 0;
342
+ }
343
+ try {
344
+ const parsed = yaml.load(fs4.readFileSync(markerPath, "utf8"));
345
+ if (!parsed || typeof parsed !== "object") {
346
+ return void 0;
347
+ }
348
+ return {
349
+ enabled: Boolean(parsed.enabled),
350
+ workflow_template: typeof parsed.workflow_template === "string" ? parsed.workflow_template : void 0,
351
+ min_lincoln_version: typeof parsed.min_lincoln_version === "string" ? parsed.min_lincoln_version : void 0
352
+ };
353
+ } catch {
354
+ return void 0;
355
+ }
104
356
  }
105
- function normalizeDescriptor(descriptor) {
106
- if (!descriptor) {
107
- return "recording";
357
+ function writeProjectMarker(projectRoot, marker, opts = {}) {
358
+ const markerPath = path5.join(projectRoot, ".lincoln.yaml");
359
+ if (fs4.existsSync(markerPath) && !opts.force) {
360
+ throw new Error(`.lincoln.yaml already exists at ${markerPath}; use --force to overwrite`);
361
+ }
362
+ const content = yaml.dump(marker, { sortKeys: false, lineWidth: -1 });
363
+ if (!opts.dryRun) {
364
+ fs4.mkdirSync(projectRoot, { recursive: true });
365
+ fs4.writeFileSync(markerPath, content);
108
366
  }
109
- const normalized = descriptor.trim().toLowerCase().replace(/[^a-z0-9一-龥]+/g, "-").replace(/^-+|-+$/g, "");
110
- return normalized.slice(0, 80) || "recording";
111
367
  }
112
368
 
113
- // src/config/resolveConfig.ts
114
- function pickFirst(...values) {
115
- for (const value of values) {
116
- if (value !== void 0 && value !== "") {
117
- return value;
118
- }
119
- }
120
- return "";
121
- }
122
- function uniqueSessionId(workspaceRoot, now, descriptor) {
123
- const baseSessionId = generateSessionId({ now, descriptor });
124
- const recordingPath = join(workspaceRoot, "recordings", `${baseSessionId}.m4a`);
125
- if (!existsSync(recordingPath)) {
126
- return baseSessionId;
127
- }
128
- for (let counter = 1; counter <= 999; counter += 1) {
129
- const candidate = `${baseSessionId}-${String(counter).padStart(3, "0")}`;
130
- const candidatePath = join(workspaceRoot, "recordings", `${candidate}.m4a`);
131
- if (!existsSync(candidatePath)) {
132
- return candidate;
133
- }
134
- }
135
- return baseSessionId;
136
- }
137
- function resolveConfig(args, options = {}) {
138
- const cwd = options.cwd ?? process.cwd();
139
- const home = options.home ?? homedir();
140
- const now = options.now ?? /* @__PURE__ */ new Date();
141
- const globalConfig = loadConfigOptional(join(home, ".lincolnrc"));
142
- const localConfig = loadConfigOptional(join(cwd, ".lincolnrc"));
143
- const topic = pickFirst(args.topic, localConfig.defaultTopic, globalConfig.defaultTopic);
144
- const designId = pickFirst(args.designId, localConfig.defaultDesignId, globalConfig.defaultDesignId);
145
- const branch = pickFirst(args.branch, localConfig.defaultBranch, globalConfig.defaultBranch);
146
- const descriptor = designId || topic || void 0;
147
- const sessionId = args.sessionId || uniqueSessionId(cwd, now, descriptor);
148
- return {
149
- workspaceRoot: cwd,
150
- sessionId,
151
- topic,
152
- designId,
153
- branch,
154
- noTui: args.noTui ?? false,
155
- autoProcess: localConfig.autoProcess ?? globalConfig.autoProcess ?? false,
156
- showAudioMeter: localConfig.showAudioMeter ?? globalConfig.showAudioMeter ?? true,
157
- audioMeterStyle: localConfig.audioMeterStyle ?? globalConfig.audioMeterStyle ?? "bar"
158
- };
369
+ // src/commands/initProject.ts
370
+ function initProject(projectRoot, options = {}) {
371
+ if (!options.force && readProjectMarker(projectRoot) !== void 0) {
372
+ console.error(`Project already has a .lincoln.yaml. Use --force to overwrite.`);
373
+ return 1;
374
+ }
375
+ try {
376
+ writeProjectMarker(
377
+ projectRoot,
378
+ {
379
+ enabled: true,
380
+ workflow_template: "interview-to-knowledge",
381
+ min_lincoln_version: readLocalPackageVersion() ?? "1.5.0"
382
+ },
383
+ { force: options.force, dryRun: options.dryRun }
384
+ );
385
+ } catch (err) {
386
+ const message = err instanceof Error ? err.message : String(err);
387
+ console.error(`Failed to write .lincoln.yaml: ${message}`);
388
+ return 1;
389
+ }
390
+ if (options.dryRun) {
391
+ console.log(`Dry run: would create ${projectRoot}/.lincoln.yaml`);
392
+ } else {
393
+ console.log(`Created ${projectRoot}/.lincoln.yaml`);
394
+ }
395
+ return 0;
159
396
  }
160
397
 
161
- // src/components/RecordingApp.tsx
162
- import { Box as Box7, useApp } from "ink";
163
- import { useCallback as useCallback2, useEffect as useEffect4, useMemo, useState as useState5 } from "react";
398
+ // src/commands/install.ts
399
+ import fs7 from "fs";
400
+ import path8 from "path";
401
+ import { spawn as spawn2 } from "child_process";
164
402
 
165
- // src/components/StatusPanel.tsx
166
- import { Box, Text } from "ink";
403
+ // src/lib/syncClaude.ts
404
+ import fs5 from "fs";
405
+ import path6 from "path";
406
+ import crypto from "crypto";
167
407
 
168
- // src/utils/formatDuration.ts
169
- function formatDuration(totalSeconds) {
170
- const safeSeconds = Math.max(0, Math.floor(totalSeconds));
171
- const minutes = Math.floor(safeSeconds / 60);
172
- const seconds = safeSeconds % 60;
173
- return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
174
- }
408
+ // src/lib/claudeMdMerge.ts
409
+ var BEGIN = "<!-- lincoln:begin -->";
410
+ var END = "<!-- lincoln:end -->";
411
+ function mergeManagedBlock(existing, block) {
412
+ const wrapped = `${BEGIN}
413
+ ${block}
414
+ ${END}`;
415
+ const text = existing ?? "";
416
+ const beginIndex = text.indexOf(BEGIN);
417
+ const endIndex = text.indexOf(END);
418
+ if (beginIndex !== -1 && endIndex !== -1 && endIndex > beginIndex) {
419
+ return text.slice(0, beginIndex) + wrapped + text.slice(endIndex + END.length);
420
+ }
421
+ if (text.trim().length === 0) {
422
+ return wrapped;
423
+ }
424
+ return `${text.trimEnd()}
175
425
 
176
- // src/components/StatusPanel.tsx
177
- import { jsx, jsxs } from "react/jsx-runtime";
178
- function StatusPanel({
179
- sessionId,
180
- topic,
181
- designId,
182
- branch,
183
- status,
184
- duration
185
- }) {
186
- const statusLabel = {
187
- idle: "Ready",
188
- recording: `Recording ${formatDuration(duration)}`,
189
- stopped: "Stopped",
190
- cancelled: "Cancelled",
191
- error: "Error"
192
- }[status];
193
- const statusColor = status === "recording" ? "red" : status === "error" ? "red" : status === "cancelled" ? "yellow" : "green";
194
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
195
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", justifyContent: "space-between", alignItems: "center", children: [
196
- /* @__PURE__ */ jsx(Text, { bold: true, color: "white", children: "Lincoln Recorder" }),
197
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
198
- /* @__PURE__ */ jsx(Text, { color: statusColor, children: status === "recording" ? "\u25CF" : "\u25CB" }),
199
- /* @__PURE__ */ jsx(Text, { children: statusLabel })
200
- ] })
201
- ] }),
202
- /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingY: 1, children: [
203
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
204
- "Session: ",
205
- sessionId
206
- ] }),
207
- topic ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
208
- "Topic: ",
209
- topic
210
- ] }) : null,
211
- designId ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
212
- "Design: ",
213
- designId
214
- ] }) : null,
215
- branch ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
216
- "Branch: ",
217
- branch
218
- ] }) : null
219
- ] })
220
- ] });
426
+ ${wrapped}
427
+ `;
221
428
  }
222
429
 
223
- // src/components/LogView.tsx
224
- import { Box as Box2, Text as Text2 } from "ink";
225
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
226
- var TYPE_COLORS = {
227
- info: void 0,
228
- success: "green",
229
- error: "red",
230
- command: "cyan"
231
- };
232
- function LogView({ logs }) {
233
- const visible = logs.slice(-8);
234
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
235
- /* @__PURE__ */ jsx2(Text2, { underline: true, dimColor: true, children: "Log" }),
236
- visible.length === 0 ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "No events yet." }) : visible.map((log) => /* @__PURE__ */ jsx2(Text2, { color: TYPE_COLORS[log.type ?? "info"], children: log.message }, log.id))
237
- ] });
430
+ // src/lib/syncClaude.ts
431
+ var COPY_SUBDIRS = [
432
+ "agents",
433
+ "skills",
434
+ "workflows",
435
+ "stages",
436
+ "schemas",
437
+ "templates",
438
+ "harnesses"
439
+ ];
440
+ function sha256(filePath) {
441
+ const data = fs5.readFileSync(filePath);
442
+ return crypto.createHash("sha256").update(data).digest("hex");
238
443
  }
239
-
240
- // src/components/HintBar.tsx
241
- import { Box as Box3, Text as Text3 } from "ink";
242
- import { jsx as jsx3 } from "react/jsx-runtime";
243
- var HINTS = {
244
- idle: "[r] record [d] devices [m] model [q] quit",
245
- recording: "[s] stop [c] cancel",
246
- stopped: "[any key] exit",
247
- cancelled: "[q] exit",
248
- error: "[q] exit",
249
- menu: "[\u2191\u2193] select [enter] confirm [esc] back"
250
- };
251
- function HintBar({ mode }) {
252
- return /* @__PURE__ */ jsx3(Box3, { paddingX: 1, paddingY: 1, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: HINTS[mode] }) });
444
+ function sameContent(a, b) {
445
+ return sha256(a) === sha256(b);
253
446
  }
254
-
255
- // src/components/DevicesMenu.tsx
256
- import { Box as Box5, Text as Text5, useInput as useInput2 } from "ink";
257
- import { useEffect, useState as useState2 } from "react";
258
-
259
- // src/components/SelectMenu.tsx
260
- import { Box as Box4, Text as Text4, useInput } from "ink";
261
- import { useState } from "react";
262
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
263
- function SelectMenu({ title, items, onSelect, onClose, footer }) {
264
- const [index, setIndex] = useState(0);
265
- useInput((_input, key) => {
266
- if (key.escape) {
267
- onClose();
268
- return;
269
- }
270
- if (key.upArrow) {
271
- setIndex((i) => i > 0 ? i - 1 : items.length - 1);
272
- return;
273
- }
274
- if (key.downArrow) {
275
- setIndex((i) => i < items.length - 1 ? i + 1 : 0);
276
- return;
277
- }
278
- if (key.return && items.length > 0) {
279
- onSelect(items[Math.min(index, items.length - 1)].key);
280
- }
281
- });
282
- return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", paddingX: 1, children: [
283
- /* @__PURE__ */ jsx4(Text4, { bold: true, children: title }),
284
- items.length === 0 ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "(none)" }) : items.map((item, i) => /* @__PURE__ */ jsxs3(Text4, { color: i === index ? "cyan" : void 0, children: [
285
- i === index ? "\u203A" : " ",
286
- " ",
287
- item.label
288
- ] }, item.key)),
289
- footer ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: footer }) : null
290
- ] });
291
- }
292
-
293
- // src/components/DevicesMenu.tsx
294
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
295
- function DevicesMenu({ listDevicesFn, onSelect, onClose }) {
296
- const [loadState, setLoadState] = useState2({ status: "loading" });
297
- useEffect(() => {
298
- let cancelled = false;
299
- listDevicesFn().then((list2) => {
300
- if (!cancelled) setLoadState({ status: "loaded", list: list2 });
301
- }).catch((error) => {
302
- if (!cancelled) {
303
- setLoadState({
304
- status: "error",
305
- message: error instanceof Error ? error.message : String(error)
306
- });
447
+ function deepMergeSettings(existing, incoming) {
448
+ if (Array.isArray(existing) && Array.isArray(incoming)) {
449
+ return incoming;
450
+ }
451
+ if (typeof existing === "object" && existing !== null && typeof incoming === "object" && incoming !== null) {
452
+ const result = { ...existing };
453
+ for (const [key, value] of Object.entries(incoming)) {
454
+ if (key in result) {
455
+ result[key] = deepMergeSettings(result[key], value);
456
+ } else {
457
+ result[key] = value;
307
458
  }
308
- });
309
- return () => {
310
- cancelled = true;
311
- };
312
- }, [listDevicesFn]);
313
- useInput2(
314
- (_input, key) => {
315
- if (key.escape) onClose();
316
- },
317
- { isActive: loadState.status !== "loaded" }
318
- );
319
- if (loadState.status === "loading") {
320
- return /* @__PURE__ */ jsx5(Box5, { paddingX: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Loading devices\u2026" }) });
321
- }
322
- if (loadState.status === "error") {
323
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", paddingX: 1, children: [
324
- /* @__PURE__ */ jsxs4(Text5, { color: "red", children: [
325
- "Failed to list devices: ",
326
- loadState.message
327
- ] }),
328
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "[esc] back" })
329
- ] });
330
- }
331
- const { list } = loadState;
332
- const items = list.devices.map((name) => ({
333
- key: name,
334
- label: name === list.defaultDevice ? `${name} (default)` : name
335
- }));
336
- return /* @__PURE__ */ jsx5(
337
- SelectMenu,
338
- {
339
- title: "Devices",
340
- items,
341
- onSelect,
342
- onClose,
343
- footer: "[\u2191\u2193] select [enter] confirm [esc] back"
344
459
  }
345
- );
460
+ return result;
461
+ }
462
+ return incoming;
346
463
  }
347
-
348
- // src/components/ModelMenu.tsx
349
- import { Box as Box6, Text as Text6, useInput as useInput3 } from "ink";
350
- import { useEffect as useEffect2, useState as useState3 } from "react";
351
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
352
- function ModelMenu({ listModelStatusesFn, runWarmupFn, onSelect, onClose }) {
353
- const [menuState, setMenuState] = useState3({ status: "loading" });
354
- useEffect2(() => {
355
- let cancelled = false;
356
- listModelStatusesFn().then((models) => {
357
- if (!cancelled) setMenuState({ status: "loaded", models });
358
- }).catch((error) => {
359
- if (!cancelled) {
360
- setMenuState({
361
- status: "error",
362
- message: error instanceof Error ? error.message : String(error)
363
- });
364
- }
365
- });
366
- return () => {
367
- cancelled = true;
368
- };
369
- }, [listModelStatusesFn]);
370
- useInput3(
371
- (_input, key) => {
372
- if (key.escape) onClose();
373
- },
374
- { isActive: menuState.status === "error" }
375
- );
376
- const handleSelect = (name) => {
377
- if (menuState.status !== "loaded") return;
378
- const target = menuState.models.find((m) => m.name === name);
379
- if (!target) return;
380
- if (target.cached) {
381
- onSelect(name);
382
- return;
383
- }
384
- setMenuState({ status: "downloading", model: name, percent: null, downloaded: 0 });
385
- runWarmupFn(name, (progress) => {
386
- setMenuState({
387
- status: "downloading",
388
- model: name,
389
- percent: progress.percent,
390
- downloaded: progress.downloaded
391
- });
392
- }).then(() => onSelect(name)).catch((error) => {
393
- setMenuState({
394
- status: "error",
395
- message: error instanceof Error ? error.message : String(error)
396
- });
397
- });
464
+ function syncClaudeCode(opts) {
465
+ const report = {
466
+ written: [],
467
+ skipped: [],
468
+ preserved: [],
469
+ warnings: [],
470
+ settingsTouched: []
398
471
  };
399
- if (menuState.status === "loading") {
400
- return /* @__PURE__ */ jsx6(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Loading models\u2026" }) });
401
- }
402
- if (menuState.status === "error") {
403
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", paddingX: 1, children: [
404
- /* @__PURE__ */ jsx6(Text6, { color: "red", children: menuState.message }),
405
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "[esc] back" })
406
- ] });
407
- }
408
- if (menuState.status === "downloading") {
409
- const progressText = menuState.percent !== null ? `${menuState.percent.toFixed(1)}%` : `${menuState.downloaded} bytes`;
410
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", paddingX: 1, children: [
411
- /* @__PURE__ */ jsxs5(Text6, { children: [
412
- "Downloading model '",
413
- menuState.model,
414
- "'\u2026 ",
415
- progressText
416
- ] }),
417
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "please wait" })
418
- ] });
419
- }
420
- const items = menuState.models.map((m) => ({
421
- key: m.name,
422
- label: m.cached ? `\u2713 ${m.name}` : `\xB7 ${m.name} (not downloaded)`
423
- }));
424
- return /* @__PURE__ */ jsx6(
425
- SelectMenu,
426
- {
427
- title: "Model",
428
- items,
429
- onSelect: handleSelect,
430
- onClose,
431
- footer: "[\u2191\u2193] select [enter] confirm [esc] back"
472
+ const payloadClaude = path6.join(opts.payloadRoot, ".claude");
473
+ if (!fs5.existsSync(payloadClaude)) {
474
+ report.warnings.push(`payload .claude directory not found: ${payloadClaude}`);
475
+ return report;
476
+ }
477
+ if (!opts.dryRun) {
478
+ fs5.mkdirSync(opts.targetDir, { recursive: true });
479
+ }
480
+ for (const subdir of COPY_SUBDIRS) {
481
+ const srcDir = path6.join(payloadClaude, subdir);
482
+ if (!fs5.existsSync(srcDir)) {
483
+ continue;
432
484
  }
433
- );
434
- }
435
-
436
- // src/hooks/useKeyHandler.ts
437
- import { useInput as useInput4 } from "ink";
438
- function useKeyHandler({
439
- enabled = true,
440
- onRecord,
441
- onStop,
442
- onCancel,
443
- onQuit,
444
- onDevices,
445
- onModel,
446
- onAnyKey
447
- }) {
448
- useInput4(
449
- (input, key) => {
450
- if (key.escape || input === "q" || input === "Q") {
451
- onQuit?.();
452
- return;
485
+ const destDir = path6.join(opts.targetDir, subdir);
486
+ if (!opts.dryRun) {
487
+ fs5.mkdirSync(destDir, { recursive: true });
488
+ }
489
+ for (const entry of walkSync(srcDir)) {
490
+ const relativePath = path6.relative(srcDir, entry);
491
+ const srcFile = entry;
492
+ const destFile = path6.join(destDir, relativePath);
493
+ if (fs5.existsSync(destFile) && sameContent(srcFile, destFile)) {
494
+ report.skipped.push(path6.join(".claude", subdir, relativePath));
495
+ continue;
453
496
  }
454
- if (key.ctrl && input === "c") {
455
- onQuit?.();
456
- return;
497
+ if (!opts.dryRun) {
498
+ fs5.mkdirSync(path6.dirname(destFile), { recursive: true });
499
+ fs5.copyFileSync(srcFile, destFile);
457
500
  }
458
- switch (input) {
459
- case "r":
460
- case "R":
461
- onRecord?.();
462
- return;
463
- case "s":
464
- case "S":
465
- onStop?.();
466
- return;
467
- case "c":
468
- case "C":
469
- onCancel?.();
470
- return;
471
- case "d":
472
- case "D":
473
- onDevices?.();
474
- return;
475
- case "m":
476
- case "M":
477
- onModel?.();
478
- return;
501
+ report.written.push(path6.join(".claude", subdir, relativePath));
502
+ }
503
+ }
504
+ const payloadSettings = path6.join(payloadClaude, "settings.json");
505
+ const targetSettings = path6.join(opts.targetDir, "settings.json");
506
+ if (fs5.existsSync(payloadSettings)) {
507
+ const incoming = JSON.parse(fs5.readFileSync(payloadSettings, "utf8"));
508
+ let merged = incoming;
509
+ if (fs5.existsSync(targetSettings)) {
510
+ const existing = JSON.parse(fs5.readFileSync(targetSettings, "utf8"));
511
+ merged = deepMergeSettings(existing, incoming);
512
+ }
513
+ const wouldChange = !fs5.existsSync(targetSettings) || JSON.stringify(merged) !== JSON.stringify(JSON.parse(fs5.readFileSync(targetSettings, "utf8")));
514
+ if (wouldChange) {
515
+ if (!opts.dryRun) {
516
+ fs5.writeFileSync(targetSettings, JSON.stringify(merged, null, 2) + "\n");
479
517
  }
480
- onAnyKey?.();
481
- },
482
- { isActive: enabled }
483
- );
518
+ report.written.push(".claude/settings.json");
519
+ report.settingsTouched = [".claude/settings.json"];
520
+ } else {
521
+ report.skipped.push(".claude/settings.json");
522
+ }
523
+ }
524
+ const payloadClaudeMd = path6.join(opts.payloadRoot, "CLAUDE.md");
525
+ const targetClaudeMd = path6.join(path6.dirname(opts.targetDir), "CLAUDE.md");
526
+ if (fs5.existsSync(payloadClaudeMd)) {
527
+ const incomingBlock = fs5.readFileSync(payloadClaudeMd, "utf8");
528
+ const existing = fs5.existsSync(targetClaudeMd) ? fs5.readFileSync(targetClaudeMd, "utf8") : void 0;
529
+ const merged = mergeManagedBlock(existing, incomingBlock);
530
+ if (merged !== existing) {
531
+ if (!opts.dryRun) {
532
+ fs5.writeFileSync(targetClaudeMd, merged);
533
+ }
534
+ report.written.push("CLAUDE.md");
535
+ } else {
536
+ report.skipped.push("CLAUDE.md");
537
+ }
538
+ }
539
+ return report;
540
+ }
541
+ function* walkSync(dir) {
542
+ const entries = fs5.readdirSync(dir, { withFileTypes: true });
543
+ for (const entry of entries) {
544
+ const fullPath = path6.join(dir, entry.name);
545
+ if (entry.isDirectory()) {
546
+ yield* walkSync(fullPath);
547
+ } else {
548
+ yield fullPath;
549
+ }
550
+ }
484
551
  }
485
552
 
486
- // src/recording/useRecorder.ts
487
- import { useCallback, useEffect as useEffect3, useRef, useState as useState4 } from "react";
553
+ // src/lib/harnessDetect.ts
554
+ import fs6 from "fs";
555
+ import path7 from "path";
556
+ import os2 from "os";
557
+ var HARNESS_SPECS = [
558
+ {
559
+ id: "claude-code",
560
+ configDirName: ".claude",
561
+ pluginMarker: "plugins/lincoln"
562
+ },
563
+ { id: "codex", configDirName: ".codex" },
564
+ { id: "opencode", configDirName: ".opencode" }
565
+ ];
566
+ function detectGlobalHarnesses(overrideHomeDir) {
567
+ const homeDir = overrideHomeDir ?? os2.homedir();
568
+ return HARNESS_SPECS.map((spec) => {
569
+ const configDir = path7.join(homeDir, spec.configDirName);
570
+ const installed = fs6.existsSync(configDir);
571
+ const pluginManaged = spec.pluginMarker ? fs6.existsSync(path7.join(configDir, spec.pluginMarker)) : false;
572
+ return {
573
+ id: spec.id,
574
+ installed,
575
+ configDir,
576
+ pluginManaged
577
+ };
578
+ });
579
+ }
580
+ function installedHarnessIds(homeDir) {
581
+ return detectGlobalHarnesses(homeDir).filter((h) => h.installed).map((h) => h.id);
582
+ }
488
583
 
489
- // src/recording/spawnRecorder.ts
490
- import { spawn } from "child_process";
491
- import { EventEmitter } from "events";
492
- var RecorderProcessImpl = class extends EventEmitter {
493
- child;
494
- stopped = false;
495
- exitPromise;
496
- resolveExit = () => {
584
+ // src/commands/install.ts
585
+ function createDefaultDeps2() {
586
+ return {
587
+ paths: resolveLincolnPaths(),
588
+ payloadRoot: resolvePayloadRoot() || "",
589
+ syncClaude: syncClaudeCode
497
590
  };
498
- exitSettled = false;
499
- constructor(child) {
500
- super();
501
- this.child = child;
502
- this.exitPromise = new Promise((resolve) => {
503
- this.resolveExit = resolve;
591
+ }
592
+ async function install(options, deps = createDefaultDeps2()) {
593
+ if (!options.dryRun && !options.yes) {
594
+ console.error("This will install Lincoln globally. Run with --yes to proceed.");
595
+ return 1;
596
+ }
597
+ const version = readLocalPackageVersion();
598
+ if (!version) {
599
+ console.error("Could not determine Lincoln version");
600
+ return 1;
601
+ }
602
+ const harnesses = options.harnesses.length > 0 ? options.harnesses : installedHarnessIds(deps.paths.homeDir);
603
+ if (harnesses.length === 0) {
604
+ console.error("No agent harness detected. Install Claude Code, Codex, or OpenCode first.");
605
+ return 1;
606
+ }
607
+ const payloadRoot = deps.payloadRoot;
608
+ if (!payloadRoot || !fs7.existsSync(payloadRoot)) {
609
+ console.error(
610
+ `Lincoln payload not found. Run "npm install -g @sushanglewis/lincoln" first.`
611
+ );
612
+ return 1;
613
+ }
614
+ const versionDir = path8.join(deps.paths.versionsDir, version);
615
+ if (!options.dryRun) {
616
+ fs7.mkdirSync(versionDir, { recursive: true });
617
+ fs7.cpSync(payloadRoot, versionDir, { recursive: true, force: true });
618
+ try {
619
+ fs7.rmSync(deps.paths.currentDir, { recursive: true, force: true });
620
+ fs7.symlinkSync(versionDir, deps.paths.currentDir);
621
+ } catch {
622
+ fs7.rmSync(deps.paths.currentDir, { recursive: true, force: true });
623
+ fs7.cpSync(versionDir, deps.paths.currentDir, { recursive: true, force: true });
624
+ }
625
+ }
626
+ const report = deps.syncClaude({
627
+ payloadRoot: deps.paths.currentDir,
628
+ targetDir: deps.paths.claudeDir,
629
+ version,
630
+ dryRun: options.dryRun
631
+ });
632
+ if (!options.dryRun && !options.noVenv) {
633
+ const python = await resolvePythonForVenv();
634
+ if (!python) {
635
+ console.error(
636
+ "Python 3.10+ is required but was not found. Set LINCOLN_PYTHON to a compatible Python executable."
637
+ );
638
+ return 1;
639
+ }
640
+ await setupVenv(deps.paths, payloadRoot, python);
641
+ }
642
+ if (!options.dryRun) {
643
+ writeVersionMarker(deps.paths, {
644
+ version,
645
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
646
+ harnesses,
647
+ managedFiles: report.written
504
648
  });
505
- this.child.on("error", (error) => {
506
- this.emit("error", error);
507
- this.settleExit();
649
+ }
650
+ if (options.dryRun) {
651
+ console.log(`Dry run: would install Lincoln ${version} for harnesses: ${harnesses.join(", ")}`);
652
+ console.log(`Would write ${report.written.length} files`);
653
+ } else {
654
+ console.log(`Installed Lincoln ${version} for harnesses: ${harnesses.join(", ")}`);
655
+ console.log(`Wrote ${report.written.length} files`);
656
+ }
657
+ return 0;
658
+ }
659
+ function runCommand(command, args) {
660
+ return new Promise((resolvePromise, rejectPromise) => {
661
+ const child = spawn2(command, args, { stdio: "inherit" });
662
+ child.on("close", (code) => resolvePromise(code ?? 1));
663
+ child.on("error", (err) => rejectPromise(err));
664
+ });
665
+ }
666
+ var MIN_PYTHON_MAJOR2 = 3;
667
+ var MIN_PYTHON_MINOR2 = 10;
668
+ var DEFAULT_PYTHON_CANDIDATES = ["python3.12", "python3.11", "python3.10", "python3"];
669
+ function parsePythonVersion2(version) {
670
+ const match = /(\d+)\.(\d+)/.exec(version);
671
+ if (!match) {
672
+ return void 0;
673
+ }
674
+ return { major: Number(match[1]), minor: Number(match[2]) };
675
+ }
676
+ async function queryPythonVersion(command) {
677
+ return new Promise((resolvePromise) => {
678
+ const child = spawn2(command, ["--version"], {
679
+ stdio: ["ignore", "pipe", "ignore"],
680
+ shell: process.platform === "win32"
508
681
  });
509
- this.child.on("exit", (code, signal) => {
510
- this.emit("exit", code, signal);
511
- this.settleExit();
682
+ let stdout = "";
683
+ child.stdout?.on("data", (chunk) => {
684
+ stdout += chunk.toString();
512
685
  });
513
- this.child.stdout.on("data", (data) => {
514
- process.stdout.write(data);
686
+ child.on("close", (code) => {
687
+ const text = stdout.trim();
688
+ resolvePromise(code === 0 && text.length > 0 ? text : void 0);
515
689
  });
516
- this.child.stderr.on("data", (data) => {
517
- process.stderr.write(data);
690
+ child.on("error", () => resolvePromise(void 0));
691
+ });
692
+ }
693
+ async function resolvePythonForVenv(envOverride = process.env.LINCOLN_PYTHON, candidates = DEFAULT_PYTHON_CANDIDATES, queryVersion = queryPythonVersion) {
694
+ const list = envOverride ? [envOverride] : candidates;
695
+ for (const cmd of list) {
696
+ const version = await queryVersion(cmd);
697
+ if (!version) {
698
+ continue;
699
+ }
700
+ const parsed = parsePythonVersion2(version);
701
+ if (parsed && parsed.major >= MIN_PYTHON_MAJOR2 && (parsed.major > MIN_PYTHON_MAJOR2 || parsed.minor >= MIN_PYTHON_MINOR2)) {
702
+ return cmd;
703
+ }
704
+ }
705
+ return void 0;
706
+ }
707
+ async function setupVenv(paths, payloadRoot, pythonCommand) {
708
+ const pythonExe = path8.join(
709
+ paths.venvDir,
710
+ process.platform === "win32" ? "Scripts" : "bin",
711
+ process.platform === "win32" ? "python.exe" : "python"
712
+ );
713
+ if (!fs7.existsSync(pythonExe)) {
714
+ console.log("Creating Lincoln virtual environment...");
715
+ await runCommand(pythonCommand, ["-m", "venv", paths.venvDir]);
716
+ }
717
+ const requirementsPath = path8.join(payloadRoot, "requirements.txt");
718
+ if (fs7.existsSync(requirementsPath)) {
719
+ console.log("Installing Python dependencies...");
720
+ const pipExe = path8.join(
721
+ paths.venvDir,
722
+ process.platform === "win32" ? "Scripts" : "bin",
723
+ process.platform === "win32" ? "pip.exe" : "pip"
724
+ );
725
+ await runCommand(pipExe, ["install", "-r", requirementsPath]);
726
+ }
727
+ }
728
+
729
+ // src/commands/update.ts
730
+ import { spawn as spawn3 } from "child_process";
731
+
732
+ // src/lib/npmRegistry.ts
733
+ var DEFAULT_REGISTRY_URL = "https://registry.npmjs.org";
734
+ var FETCH_TIMEOUT_MS = 15e3;
735
+ function createRegistryClient(registryUrl = DEFAULT_REGISTRY_URL, fetchImpl = fetch) {
736
+ const baseUrl = registryUrl.replace(/\/+$/, "");
737
+ async function fetchPackument(packageName) {
738
+ const url = `${baseUrl}/${encodePackageName(packageName)}`;
739
+ const response = await fetchImpl(url, {
740
+ headers: { Accept: "application/json" },
741
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
518
742
  });
519
- process.nextTick(() => this.emit("ready"));
743
+ if (!response.ok) {
744
+ throw new Error(
745
+ `npm registry returned ${response.status} ${response.statusText} for ${packageName}`
746
+ );
747
+ }
748
+ return await response.json();
520
749
  }
521
- settleExit() {
522
- if (!this.exitSettled) {
523
- this.exitSettled = true;
524
- this.resolveExit();
750
+ return {
751
+ async latestVersion(packageName) {
752
+ const packument = await fetchPackument(packageName);
753
+ const latest = packument["dist-tags"]?.latest;
754
+ if (!latest) {
755
+ throw new Error(`npm registry has no latest dist-tag for ${packageName}`);
756
+ }
757
+ return latest;
758
+ },
759
+ async versions(packageName) {
760
+ const packument = await fetchPackument(packageName);
761
+ return Object.keys(packument.versions ?? {});
762
+ },
763
+ async resolveVersion(packageName, spec) {
764
+ const packument = await fetchPackument(packageName);
765
+ const tagTarget = packument["dist-tags"]?.[spec];
766
+ if (tagTarget) {
767
+ return tagTarget;
768
+ }
769
+ if (packument.versions && spec in packument.versions) {
770
+ return spec;
771
+ }
772
+ throw new Error(`Version not found for ${packageName}: ${spec}`);
773
+ }
774
+ };
775
+ }
776
+ function encodePackageName(packageName) {
777
+ if (packageName.startsWith("@")) {
778
+ const slash = packageName.indexOf("/");
779
+ if (slash === -1) {
780
+ return encodeURIComponent(packageName);
525
781
  }
782
+ const scope = packageName.slice(1, slash);
783
+ const name = packageName.slice(slash + 1);
784
+ return `@${encodeURIComponent(scope)}%2F${encodeURIComponent(name)}`;
785
+ }
786
+ return encodeURIComponent(packageName);
787
+ }
788
+
789
+ // src/lib/semver.ts
790
+ var SEMVER_CORE = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
791
+ var NUMERIC_IDENTIFIER = /^\d+$/;
792
+ function compareVersions(a, b) {
793
+ const pa = parseVersion(a);
794
+ const pb = parseVersion(b);
795
+ if (!pa || !pb) {
796
+ return void 0;
797
+ }
798
+ if (pa.major !== pb.major) return pa.major - pb.major;
799
+ if (pa.minor !== pb.minor) return pa.minor - pb.minor;
800
+ if (pa.patch !== pb.patch) return pa.patch - pb.patch;
801
+ return comparePrerelease(pa.prerelease, pb.prerelease);
802
+ }
803
+ function parseVersion(version) {
804
+ const match = SEMVER_CORE.exec(version.trim());
805
+ if (!match) {
806
+ return void 0;
526
807
  }
527
- stop() {
528
- return this._terminate("SIGINT");
808
+ return {
809
+ major: Number(match[1]),
810
+ minor: Number(match[2]),
811
+ patch: Number(match[3]),
812
+ prerelease: match[4] ? match[4].split(".") : []
813
+ };
814
+ }
815
+ function comparePrerelease(a, b) {
816
+ if (a.length === 0 && b.length === 0) return 0;
817
+ if (a.length === 0) return 1;
818
+ if (b.length === 0) return -1;
819
+ const shared = Math.min(a.length, b.length);
820
+ for (let i = 0; i < shared; i++) {
821
+ const x = a[i];
822
+ const y = b[i];
823
+ if (x === y) continue;
824
+ const xNumeric = NUMERIC_IDENTIFIER.test(x);
825
+ const yNumeric = NUMERIC_IDENTIFIER.test(y);
826
+ if (xNumeric && yNumeric) return Number(x) - Number(y);
827
+ if (xNumeric) return -1;
828
+ if (yNumeric) return 1;
829
+ return x < y ? -1 : 1;
529
830
  }
530
- cancel() {
531
- return this._terminate("SIGKILL");
831
+ return a.length - b.length;
832
+ }
833
+
834
+ // src/commands/update.ts
835
+ var PACKAGE_NAME = "@sushanglewis/lincoln";
836
+ function createDefaultDeps3() {
837
+ const paths = resolveLincolnPaths();
838
+ const registry = createRegistryClient();
839
+ return {
840
+ paths,
841
+ latestVersion: () => registry.latestVersion(PACKAGE_NAME),
842
+ currentVersion: () => readVersionMarker(paths)?.version ?? readLocalPackageVersion(),
843
+ npmInstallGlobal: (version) => runNpmInstallGlobal(PACKAGE_NAME, version),
844
+ runInstall: (options) => install(options)
845
+ };
846
+ }
847
+ async function update(options, deps = {}) {
848
+ const resolved = { ...createDefaultDeps3(), ...deps };
849
+ let latest;
850
+ try {
851
+ latest = await resolved.latestVersion();
852
+ } catch (err) {
853
+ const message = err instanceof Error ? err.message : String(err);
854
+ console.error(`Failed to query npm registry: ${message}`);
855
+ return 1;
532
856
  }
533
- _terminate(signal) {
534
- if (this.stopped) {
535
- return this.exitPromise;
857
+ const current = resolved.currentVersion();
858
+ if (current !== void 0) {
859
+ const comparison = compareVersions(current, latest);
860
+ if (comparison !== void 0 ? comparison > 0 : false) {
861
+ console.log(
862
+ `Lincoln ${current} is ahead of the latest release (${latest}); nothing to do.`
863
+ );
864
+ return 0;
536
865
  }
537
- this.stopped = true;
538
- if (this.child.killed) {
539
- return this.exitPromise;
866
+ if (comparison !== void 0 ? comparison === 0 : current === latest) {
867
+ console.log(`Lincoln ${current} is already up to date.`);
868
+ return 0;
540
869
  }
541
- this.child.kill(signal);
542
- const timeout = setTimeout(() => {
543
- if (!this.child.killed) {
544
- this.child.kill("SIGKILL");
545
- }
546
- }, 5e3);
547
- this.exitPromise.finally(() => clearTimeout(timeout));
548
- return this.exitPromise;
549
870
  }
550
- };
551
- function spawnRecorder(options) {
552
- const {
553
- workspaceRoot,
554
- sessionId,
555
- lincolnRecordPath = "lincoln-record",
556
- mic,
557
- model
558
- } = options;
559
- const args = [
560
- "record",
561
- "--session-id",
562
- sessionId,
563
- "--output",
564
- workspaceRoot
565
- ];
566
- if (mic) {
567
- args.push("--mic", mic);
568
- }
569
- if (model) {
570
- args.push("--model", model);
571
- }
572
- const child = spawn(lincolnRecordPath, args, {
573
- cwd: workspaceRoot,
574
- stdio: ["pipe", "pipe", "pipe"]
871
+ const summary = `${current ?? "unknown"} -> ${latest}`;
872
+ if (options.check) {
873
+ console.log(`Lincoln update available: ${summary}`);
874
+ return 0;
875
+ }
876
+ if (options.dryRun) {
877
+ console.log(`Dry run: would update Lincoln ${summary}`);
878
+ return 0;
879
+ }
880
+ if (!options.yes) {
881
+ console.error(`Will update Lincoln ${summary}. Run with --yes to proceed.`);
882
+ return 1;
883
+ }
884
+ const installCode = await resolved.npmInstallGlobal(latest);
885
+ if (installCode !== 0) {
886
+ console.error(`npm install -g ${PACKAGE_NAME}@${latest} failed with exit code ${installCode}`);
887
+ return 1;
888
+ }
889
+ const syncCode = await resolved.runInstall(defaultInstallOptions());
890
+ if (syncCode !== 0) {
891
+ console.error(`lincoln install failed with exit code ${syncCode}`);
892
+ return 1;
893
+ }
894
+ console.log(`Updated Lincoln to ${latest}`);
895
+ return 0;
896
+ }
897
+ function defaultInstallOptions() {
898
+ return { yes: true, dryRun: false, force: false, harnesses: [], noVenv: true };
899
+ }
900
+ function runNpmInstallGlobal(packageName, version) {
901
+ return new Promise((resolvePromise) => {
902
+ const child = spawn3("npm", ["install", "-g", `${packageName}@${version}`], {
903
+ stdio: "inherit",
904
+ shell: process.platform === "win32"
905
+ });
906
+ child.on("close", (code) => resolvePromise(code ?? 1));
907
+ child.on("error", () => resolvePromise(1));
575
908
  });
576
- return new RecorderProcessImpl(child);
577
909
  }
578
910
 
579
- // src/recording/useRecorder.ts
580
- function mockAmplitude(duration) {
581
- return 0.5 + 0.4 * Math.sin(duration * 0.8);
582
- }
583
- function useRecorder(options) {
584
- const {
585
- startOnMount = false,
586
- workspaceRoot,
587
- sessionId,
588
- lincolnRecordPath,
589
- mic,
590
- model
591
- } = options;
592
- const [state, setState] = useState4({
593
- status: "idle",
594
- duration: 0,
595
- amplitude: 0,
596
- errorMessage: null
597
- });
598
- const recorderRef = useRef(null);
599
- const intervalRef = useRef(null);
600
- const durationRef = useRef(0);
601
- const terminatingRef = useRef(false);
602
- const clearRecordingInterval = useCallback(() => {
603
- if (intervalRef.current) {
604
- clearInterval(intervalRef.current);
605
- intervalRef.current = null;
606
- }
607
- }, []);
608
- const start = useCallback(() => {
609
- if (recorderRef.current) {
610
- return;
611
- }
612
- terminatingRef.current = false;
613
- setState({ status: "recording", duration: 0, amplitude: 0.3, errorMessage: null });
614
- durationRef.current = 0;
615
- try {
616
- const recorder = spawnRecorder({
617
- workspaceRoot,
618
- sessionId,
619
- lincolnRecordPath,
620
- mic,
621
- model
622
- });
623
- recorderRef.current = recorder;
624
- recorder.on("ready", () => {
625
- });
626
- recorder.on("error", (error) => {
627
- setState((s) => ({ ...s, status: "error", errorMessage: error.message }));
628
- clearRecordingInterval();
629
- });
630
- recorder.on("exit", (code, signal) => {
631
- recorderRef.current = null;
632
- clearRecordingInterval();
633
- if (terminatingRef.current) {
634
- return;
635
- }
636
- const reason = code !== null && code !== void 0 ? `lincoln-record exited unexpectedly with code ${code}` : `lincoln-record exited unexpectedly with signal ${signal}`;
637
- setState((s) => ({ ...s, status: "error", errorMessage: reason }));
638
- });
639
- intervalRef.current = setInterval(() => {
640
- durationRef.current += 1;
641
- setState((s) => ({
642
- ...s,
643
- duration: durationRef.current,
644
- amplitude: mockAmplitude(durationRef.current)
645
- }));
646
- }, 1e3);
647
- } catch (error) {
648
- const message = error instanceof Error ? error.message : String(error);
649
- setState((s) => ({ ...s, status: "error", errorMessage: message }));
650
- }
651
- }, [workspaceRoot, sessionId, lincolnRecordPath, mic, model, clearRecordingInterval]);
652
- const stop = useCallback(async () => {
653
- const recorder = recorderRef.current;
654
- if (!recorder) return;
655
- terminatingRef.current = true;
656
- clearRecordingInterval();
657
- setState((s) => ({ ...s, status: "stopped" }));
658
- await recorder.stop();
659
- }, [clearRecordingInterval]);
660
- const cancel = useCallback(async () => {
661
- const recorder = recorderRef.current;
662
- if (!recorder) return;
663
- terminatingRef.current = true;
664
- clearRecordingInterval();
665
- setState((s) => ({ ...s, status: "cancelled" }));
666
- await recorder.cancel();
667
- }, [clearRecordingInterval]);
668
- useEffect3(() => {
669
- if (startOnMount) {
670
- start();
671
- }
672
- return () => {
673
- clearRecordingInterval();
674
- if (recorderRef.current) {
675
- recorderRef.current.cancel().catch(() => {
676
- });
677
- recorderRef.current = null;
911
+ // src/commands/use.ts
912
+ import fs8 from "fs";
913
+ import path9 from "path";
914
+ import { spawn as spawn4 } from "child_process";
915
+ var PACKAGE_NAME2 = "@sushanglewis/lincoln";
916
+ var SEMVER_REGEX = /^v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
917
+ var STRICT_SEMVER_REGEX = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
918
+ var DIST_TAG_REGEX = /^[a-zA-Z][a-zA-Z0-9._-]*$/;
919
+ function createDefaultDeps4() {
920
+ const paths = resolveLincolnPaths();
921
+ const registry = createRegistryClient();
922
+ return {
923
+ paths,
924
+ resolveVersion: async (spec) => {
925
+ const normalized = spec.startsWith("v") ? spec.slice(1) : spec;
926
+ if (STRICT_SEMVER_REGEX.test(normalized)) {
927
+ return normalized;
678
928
  }
679
- };
680
- }, [startOnMount, start, clearRecordingInterval]);
681
- return { state, start, stop, cancel };
929
+ return registry.resolveVersion(PACKAGE_NAME2, spec);
930
+ },
931
+ ensureVersionAvailable: async (version) => {
932
+ const code = await runNpmInstallGlobal2(PACKAGE_NAME2, version);
933
+ if (code !== 0) {
934
+ throw new Error(`npm install -g ${PACKAGE_NAME2}@${version} exited with ${code}`);
935
+ }
936
+ },
937
+ npmGlobalRoot: () => runNpmRootGlobal(),
938
+ runInstall: (options) => install(options)
939
+ };
682
940
  }
683
-
684
- // src/devices/listDevices.ts
685
- import { execFile } from "child_process";
686
- import { promisify } from "util";
687
-
688
- // src/devices/parseDevices.ts
689
- var DEFAULT_PREFIX = "Default input: ";
690
- function parseDevicesOutput(output) {
691
- let defaultDevice = null;
692
- const devices = [];
693
- for (const rawLine of output.split("\n")) {
694
- const line = rawLine.trim();
695
- if (!line) continue;
696
- if (line.startsWith(DEFAULT_PREFIX)) {
697
- defaultDevice = line.slice(DEFAULT_PREFIX.length).trim() || null;
698
- continue;
941
+ async function use(spec, options, deps = {}) {
942
+ if (!isValidVersion(spec)) {
943
+ console.error(`Invalid version: ${spec}`);
944
+ return 1;
945
+ }
946
+ const resolved = { ...createDefaultDeps4(), ...deps };
947
+ let version;
948
+ try {
949
+ version = await resolved.resolveVersion(spec);
950
+ } catch (err) {
951
+ const message = err instanceof Error ? err.message : String(err);
952
+ console.error(`Failed to resolve version ${spec}: ${message}`);
953
+ return 1;
954
+ }
955
+ const versionDir = path9.join(resolved.paths.versionsDir, version);
956
+ const alreadyInstalled = fs8.existsSync(versionDir);
957
+ if (options.dryRun) {
958
+ if (alreadyInstalled) {
959
+ console.log(`Dry run: would switch ${resolved.paths.currentDir} to ${versionDir}`);
960
+ } else {
961
+ console.log(
962
+ `Dry run: would install ${PACKAGE_NAME2}@${version} globally, then switch ${resolved.paths.currentDir} to ${versionDir}`
963
+ );
964
+ }
965
+ return 0;
966
+ }
967
+ if (!options.yes) {
968
+ console.error(`Will switch Lincoln to ${version}. Run with --yes to proceed.`);
969
+ return 1;
970
+ }
971
+ if (!alreadyInstalled) {
972
+ try {
973
+ await resolved.ensureVersionAvailable(version);
974
+ } catch (err) {
975
+ const message = err instanceof Error ? err.message : String(err);
976
+ console.error(`Failed to install ${PACKAGE_NAME2}@${version}: ${message}`);
977
+ return 1;
978
+ }
979
+ if (!fs8.existsSync(versionDir)) {
980
+ try {
981
+ await copyVersionFromNpmGlobal(resolved, versionDir);
982
+ } catch (err) {
983
+ const message = err instanceof Error ? err.message : String(err);
984
+ console.error(`Failed to populate ${versionDir}: ${message}`);
985
+ return 1;
986
+ }
699
987
  }
700
- devices.push(line);
988
+ if (!fs8.existsSync(versionDir)) {
989
+ console.error(
990
+ `Provisioning ${PACKAGE_NAME2}@${version} did not produce ${versionDir}; cannot switch versions.`
991
+ );
992
+ return 1;
993
+ }
994
+ }
995
+ try {
996
+ switchCurrentLink(resolved.paths, versionDir);
997
+ } catch (err) {
998
+ const message = err instanceof Error ? err.message : String(err);
999
+ console.error(`Failed to switch ${resolved.paths.currentDir}: ${message}`);
1000
+ return 1;
1001
+ }
1002
+ const syncCode = await resolved.runInstall(defaultInstallOptions2());
1003
+ if (syncCode !== 0) {
1004
+ console.error(`lincoln install failed with exit code ${syncCode}`);
1005
+ return 1;
1006
+ }
1007
+ try {
1008
+ switchCurrentLink(resolved.paths, versionDir);
1009
+ } catch (err) {
1010
+ const message = err instanceof Error ? err.message : String(err);
1011
+ console.error(`Failed to switch ${resolved.paths.currentDir}: ${message}`);
1012
+ return 1;
701
1013
  }
702
- return { defaultDevice, devices };
1014
+ console.log(`Switched Lincoln to ${version}`);
1015
+ return 0;
703
1016
  }
704
-
705
- // src/devices/listDevices.ts
706
- var execFileAsync = promisify(execFile);
707
- async function listDevices(lincolnRecordPath = "lincoln-record") {
708
- const { stdout } = await execFileAsync(lincolnRecordPath, ["devices"]);
709
- return parseDevicesOutput(stdout);
1017
+ function isValidVersion(version) {
1018
+ return SEMVER_REGEX.test(version) || DIST_TAG_REGEX.test(version);
710
1019
  }
711
-
712
- // src/models/models.ts
713
- import { existsSync as existsSync2 } from "fs";
714
- import { homedir as homedir2 } from "os";
715
- import { join as join2 } from "path";
716
- var KNOWN_MODELS = [
717
- "tiny",
718
- "base",
719
- "small",
720
- "medium",
721
- "large-v1",
722
- "large-v2",
723
- "large-v3",
724
- "large-v3-turbo"
725
- ];
726
- function defaultModelCacheDir() {
727
- return join2(homedir2(), ".cache", "lincoln", "models");
1020
+ async function copyVersionFromNpmGlobal(deps, versionDir) {
1021
+ const globalRoot = await deps.npmGlobalRoot();
1022
+ if (!globalRoot) {
1023
+ return;
1024
+ }
1025
+ const packageRoot = path9.join(globalRoot, ...PACKAGE_NAME2.split("/"));
1026
+ if (!fs8.existsSync(packageRoot)) {
1027
+ return;
1028
+ }
1029
+ fs8.mkdirSync(path9.dirname(versionDir), { recursive: true });
1030
+ fs8.cpSync(packageRoot, versionDir, { recursive: true });
1031
+ }
1032
+ function switchCurrentLink(paths, versionDir) {
1033
+ fs8.mkdirSync(paths.lincolnHome, { recursive: true });
1034
+ try {
1035
+ fs8.rmSync(paths.currentDir, { recursive: true, force: true });
1036
+ fs8.symlinkSync(versionDir, paths.currentDir);
1037
+ } catch {
1038
+ fs8.rmSync(paths.currentDir, { recursive: true, force: true });
1039
+ fs8.cpSync(versionDir, paths.currentDir, { recursive: true, force: true });
1040
+ }
728
1041
  }
729
- function modelCachePath(name, cacheDir) {
730
- return join2(cacheDir, "whisper", `ggml-${name}.bin`);
1042
+ function defaultInstallOptions2() {
1043
+ return { yes: true, dryRun: false, force: false, harnesses: [], noVenv: true };
731
1044
  }
732
- function listModelStatuses(cacheDir) {
733
- return KNOWN_MODELS.map((name) => ({
734
- name,
735
- cached: existsSync2(modelCachePath(name, cacheDir))
736
- }));
1045
+ function runNpmInstallGlobal2(packageName, version) {
1046
+ return new Promise((resolvePromise) => {
1047
+ const child = spawn4("npm", ["install", "-g", `${packageName}@${version}`], {
1048
+ stdio: "inherit",
1049
+ shell: process.platform === "win32"
1050
+ });
1051
+ child.on("close", (code) => resolvePromise(code ?? 1));
1052
+ child.on("error", () => resolvePromise(1));
1053
+ });
1054
+ }
1055
+ function runNpmRootGlobal() {
1056
+ return new Promise((resolvePromise) => {
1057
+ const child = spawn4("npm", ["root", "-g"], {
1058
+ stdio: ["ignore", "pipe", "ignore"],
1059
+ shell: process.platform === "win32"
1060
+ });
1061
+ let stdout = "";
1062
+ child.stdout?.on("data", (chunk) => {
1063
+ stdout += chunk.toString();
1064
+ });
1065
+ child.on("close", (code) => {
1066
+ const root = stdout.trim();
1067
+ resolvePromise(code === 0 && root.length > 0 ? root : void 0);
1068
+ });
1069
+ child.on("error", () => resolvePromise(void 0));
1070
+ });
737
1071
  }
738
1072
 
739
- // src/models/runWarmup.ts
740
- import { spawn as spawn2 } from "child_process";
1073
+ // src/commands/migrateProject.ts
1074
+ import fs10 from "fs";
1075
+ import path11 from "path";
741
1076
 
742
- // src/models/warmupProgress.ts
743
- var PROGRESS_WITH_TOTAL = /downloaded (\d+) \/ (\d+) bytes \(([\d.]+)%\)/;
744
- var PROGRESS_WITHOUT_TOTAL = /downloaded (\d+) bytes/;
745
- function parseWarmupProgress(chunk) {
746
- const withTotal = PROGRESS_WITH_TOTAL.exec(chunk);
747
- if (withTotal) {
748
- return {
749
- downloaded: Number(withTotal[1]),
750
- total: Number(withTotal[2]),
751
- percent: Number(withTotal[3])
752
- };
1077
+ // src/lib/projectFiles.ts
1078
+ import fs9 from "fs";
1079
+ import path10 from "path";
1080
+ import crypto2 from "crypto";
1081
+ var FRAMEWORK_ALLOWLIST_DIRS = [".claude", ".claude-plugin", "scripts", "tools"];
1082
+ var FRAMEWORK_ALLOWLIST_FILES = [
1083
+ "README.md",
1084
+ "README.en.md",
1085
+ "USAGE.md",
1086
+ "CONTRIBUTING.md",
1087
+ "CLAUDE.md",
1088
+ "LICENSE",
1089
+ "RELEASE.md",
1090
+ ".version-bump.json",
1091
+ "requirements.txt",
1092
+ "SKILL.md"
1093
+ ];
1094
+ var FRAMEWORK_DENYLIST_NAMES = /* @__PURE__ */ new Set([
1095
+ ".git",
1096
+ ".context",
1097
+ ".venv",
1098
+ "venv",
1099
+ "dist",
1100
+ "oss",
1101
+ ".pytest_cache",
1102
+ "__pycache__",
1103
+ ".DS_Store",
1104
+ "node_modules"
1105
+ ]);
1106
+ var FRAMEWORK_DENYLIST_PREFIXES = ["issue-"];
1107
+ var FRAMEWORK_SKIP_PATHS = /* @__PURE__ */ new Set([
1108
+ ".claude/templates/issue-package",
1109
+ "tools/lincoln-installer/dist",
1110
+ "tools/lincoln-installer/node_modules",
1111
+ "tools/lincoln-installer/coverage"
1112
+ ]);
1113
+ function sha2562(filePath) {
1114
+ return crypto2.createHash("sha256").update(fs9.readFileSync(filePath)).digest("hex");
1115
+ }
1116
+ function isDenied(relativePath) {
1117
+ const parts = relativePath.split(path10.sep);
1118
+ for (const part of parts) {
1119
+ if (FRAMEWORK_DENYLIST_NAMES.has(part)) return true;
1120
+ for (const prefix of FRAMEWORK_DENYLIST_PREFIXES) {
1121
+ if (part.startsWith(prefix)) return true;
1122
+ }
753
1123
  }
754
- const withoutTotal = PROGRESS_WITHOUT_TOTAL.exec(chunk);
755
- if (withoutTotal) {
756
- return {
757
- downloaded: Number(withoutTotal[1]),
758
- total: null,
759
- percent: null
760
- };
1124
+ for (const skip of FRAMEWORK_SKIP_PATHS) {
1125
+ if (relativePath === skip || relativePath.startsWith(skip + path10.sep)) return true;
761
1126
  }
762
- return null;
1127
+ return false;
763
1128
  }
764
-
765
- // src/models/runWarmup.ts
766
- var READY_LINE = /Model ready at (.+)/;
767
- function runWarmup(options) {
768
- const { model, lincolnRecordPath = "lincoln-record", onProgress } = options;
769
- return new Promise((resolve, reject) => {
770
- const child = spawn2(lincolnRecordPath, ["warmup", "--model", model], {
771
- stdio: ["ignore", "pipe", "pipe"]
772
- });
773
- let readyPath = null;
774
- child.stdout.on("data", (data) => {
775
- const match = READY_LINE.exec(data.toString());
776
- if (match) {
777
- readyPath = match[1].trim();
1129
+ function isAllowlisted(relativePath) {
1130
+ if (FRAMEWORK_ALLOWLIST_FILES.includes(relativePath)) return true;
1131
+ for (const dir of FRAMEWORK_ALLOWLIST_DIRS) {
1132
+ if (relativePath === dir || relativePath.startsWith(dir + path10.sep)) return true;
1133
+ }
1134
+ return false;
1135
+ }
1136
+ function isPreservedDir(relativePath) {
1137
+ const normalized = relativePath.replace(/\\/g, "/");
1138
+ if (normalized === ".context" || normalized.startsWith(".context/")) return true;
1139
+ const parts = normalized.split("/");
1140
+ for (const part of parts) {
1141
+ if (part.startsWith("issue-")) return true;
1142
+ }
1143
+ return false;
1144
+ }
1145
+ function isPreserved(relativePath) {
1146
+ const normalized = relativePath.replace(/\\/g, "/");
1147
+ if (normalized === ".github/openspec-config.yml") return true;
1148
+ return isPreservedDir(relativePath);
1149
+ }
1150
+ function listVendoredFrameworkFiles(payloadRoot, projectRoot) {
1151
+ const report = { removable: [], modified: [], preserved: [] };
1152
+ function walk(dir) {
1153
+ const entries = fs9.readdirSync(dir, { withFileTypes: true });
1154
+ for (const entry of entries) {
1155
+ const fullPath = path10.join(dir, entry.name);
1156
+ const relativePath = path10.relative(projectRoot, fullPath);
1157
+ if (entry.isDirectory()) {
1158
+ if (isPreservedDir(relativePath)) {
1159
+ walk(fullPath);
1160
+ } else if (!isDenied(relativePath)) {
1161
+ walk(fullPath);
1162
+ }
1163
+ continue;
778
1164
  }
779
- });
780
- child.stderr.on("data", (data) => {
781
- const progress = parseWarmupProgress(data.toString());
782
- if (progress) {
783
- onProgress?.(progress);
1165
+ if (isPreserved(relativePath)) {
1166
+ report.preserved.push(relativePath);
1167
+ continue;
784
1168
  }
785
- });
786
- child.on("error", reject);
787
- child.on("exit", (code) => {
788
- if (code === 0) {
789
- resolve(readyPath ?? model);
1169
+ if (isDenied(relativePath)) continue;
1170
+ if (!isAllowlisted(relativePath)) continue;
1171
+ const payloadFile = path10.join(payloadRoot, relativePath);
1172
+ if (!fs9.existsSync(payloadFile)) {
1173
+ report.modified.push(relativePath);
1174
+ continue;
1175
+ }
1176
+ if (sha2562(fullPath) === sha2562(payloadFile)) {
1177
+ report.removable.push(relativePath);
790
1178
  } else {
791
- reject(new Error(`lincoln-record warmup exited with code ${code}`));
1179
+ report.modified.push(relativePath);
792
1180
  }
793
- });
794
- });
1181
+ }
1182
+ }
1183
+ if (fs9.existsSync(projectRoot)) {
1184
+ walk(projectRoot);
1185
+ }
1186
+ return report;
795
1187
  }
796
1188
 
797
- // src/components/RecordingApp.tsx
798
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
799
- function createLogId() {
800
- return `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
801
- }
802
- function RecordingApp({
803
- workspaceRoot,
804
- sessionId,
805
- topic,
806
- designId,
807
- branch,
808
- audioMeterStyle = "bar",
809
- lincolnRecordPath,
810
- listDevicesFn,
811
- listModelStatusesFn,
812
- runWarmupFn
813
- }) {
814
- const { exit } = useApp();
815
- const [logs, setLogs] = useState5([]);
816
- const [mode, setMode] = useState5("main");
817
- const [mic, setMic] = useState5(null);
818
- const [model, setModel] = useState5(null);
819
- const addLog = useCallback2((message, type = "info") => {
820
- setLogs((prev) => [...prev, { id: createLogId(), message, type }]);
821
- }, []);
822
- const { state, start, stop, cancel } = useRecorder({
823
- workspaceRoot,
824
- sessionId,
825
- lincolnRecordPath,
826
- mic,
827
- model,
828
- startOnMount: false
829
- });
830
- useEffect4(() => {
831
- switch (state.status) {
832
- case "recording":
833
- addLog("Recording started.", "info");
834
- break;
835
- case "stopped":
836
- addLog(`Saved to ${workspaceRoot}/${sessionId}/audio.wav`, "success");
837
- addLog(`Run: claude process-interview ${sessionId}`, "command");
838
- break;
839
- case "cancelled":
840
- addLog("Recording cancelled. No files were saved.", "error");
841
- break;
842
- case "error":
843
- addLog(`Recording error: ${state.errorMessage ?? "unknown error"}`, "error");
844
- break;
845
- }
846
- }, [state.status, state.errorMessage, addLog, workspaceRoot, sessionId]);
847
- const handleQuit = useCallback2(() => {
848
- exit();
849
- }, [exit]);
850
- const handleRecord = useCallback2(() => {
851
- if (state.status === "idle") {
852
- start();
853
- }
854
- }, [state.status, start]);
855
- const handleStop = useCallback2(() => {
856
- if (state.status === "recording") {
857
- stop().catch(() => {
858
- });
859
- }
860
- }, [state.status, stop]);
861
- const handleCancel = useCallback2(() => {
862
- if (state.status === "recording") {
863
- cancel().catch(() => {
864
- });
865
- }
866
- }, [state.status, cancel]);
867
- const handleDevices = useCallback2(() => {
868
- if (state.status === "idle") {
869
- setMode("devices");
870
- }
871
- }, [state.status]);
872
- const handleModel = useCallback2(() => {
873
- if (state.status === "idle") {
874
- setMode("model");
875
- }
876
- }, [state.status]);
877
- const handleDeviceSelect = useCallback2(
878
- (device) => {
879
- setMic(device);
880
- addLog(`Input device: ${device}`, "success");
881
- setMode("main");
882
- },
883
- [addLog]
884
- );
885
- const handleModelSelect = useCallback2(
886
- (name) => {
887
- setModel(name);
888
- addLog(`Model: ${name}`, "success");
889
- setMode("main");
890
- },
891
- [addLog]
892
- );
893
- const handleMenuClose = useCallback2(() => {
894
- setMode("main");
895
- }, []);
896
- const isTerminal = state.status === "stopped" || state.status === "cancelled" || state.status === "error";
897
- useKeyHandler({
898
- enabled: mode === "main",
899
- onRecord: handleRecord,
900
- onStop: handleStop,
901
- onCancel: handleCancel,
902
- onQuit: handleQuit,
903
- onDevices: handleDevices,
904
- onModel: handleModel,
905
- onAnyKey: isTerminal ? handleQuit : void 0
906
- });
907
- const hintMode = useMemo(() => {
908
- if (mode !== "main") return "menu";
909
- if (state.status === "idle") return "idle";
910
- if (state.status === "recording") return "recording";
911
- if (state.status === "stopped") return "stopped";
912
- if (state.status === "cancelled") return "cancelled";
913
- return "error";
914
- }, [mode, state.status]);
915
- const devicesLoader = useCallback2(
916
- () => (listDevicesFn ?? (() => listDevices(lincolnRecordPath)))(),
917
- [listDevicesFn, lincolnRecordPath]
918
- );
919
- const modelsLoader = useCallback2(
920
- () => (listModelStatusesFn ?? (() => Promise.resolve(listModelStatuses(defaultModelCacheDir()))))(),
921
- [listModelStatusesFn]
922
- );
923
- const warmupRunner = useCallback2(
924
- (name, onProgress) => runWarmupFn ? runWarmupFn(name, onProgress) : runWarmup({ model: name, lincolnRecordPath, onProgress }),
925
- [runWarmupFn, lincolnRecordPath]
926
- );
927
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", borderStyle: "round", borderColor: "gray", height: 24, children: [
928
- /* @__PURE__ */ jsx7(
929
- StatusPanel,
930
- {
931
- sessionId,
932
- topic,
933
- designId,
934
- branch,
935
- status: state.status,
936
- duration: state.duration
937
- }
938
- ),
939
- mode === "devices" && /* @__PURE__ */ jsx7(
940
- DevicesMenu,
941
- {
942
- listDevicesFn: devicesLoader,
943
- onSelect: handleDeviceSelect,
944
- onClose: handleMenuClose
1189
+ // src/commands/migrateProject.ts
1190
+ function createDefaultDeps5() {
1191
+ return {
1192
+ paths: resolveLincolnPaths(),
1193
+ listVendoredFrameworkFiles,
1194
+ writeProjectMarker
1195
+ };
1196
+ }
1197
+ async function migrateProject(projectRoot, options = {}, deps = createDefaultDeps5()) {
1198
+ const payloadRoot = deps.paths.currentDir;
1199
+ if (!fs10.existsSync(payloadRoot)) {
1200
+ console.error(`Lincoln payload not found at ${payloadRoot}. Run "lincoln install" first.`);
1201
+ return 1;
1202
+ }
1203
+ const absoluteProjectRoot = path11.resolve(projectRoot);
1204
+ const report = deps.listVendoredFrameworkFiles(payloadRoot, absoluteProjectRoot);
1205
+ if (options.dryRun) {
1206
+ console.log(`Dry run: would migrate ${absoluteProjectRoot}`);
1207
+ if (report.removable.length > 0) {
1208
+ console.log("Would remove unmodified framework files:");
1209
+ for (const f of report.removable) console.log(` - ${f}`);
1210
+ }
1211
+ if (report.modified.length > 0) {
1212
+ console.log("Would preserve modified framework files:");
1213
+ for (const f of report.modified) console.log(` - ${f}`);
1214
+ }
1215
+ if (report.preserved.length > 0) {
1216
+ console.log("Would preserve project-local files:");
1217
+ for (const f of report.preserved) console.log(` - ${f}`);
1218
+ }
1219
+ console.log("Would create .lincoln.yaml");
1220
+ return 0;
1221
+ }
1222
+ if (!options.yes) {
1223
+ console.error("This will remove vendored framework files. Run with --yes to proceed.");
1224
+ return 1;
1225
+ }
1226
+ for (const file of report.removable) {
1227
+ const filePath = path11.join(absoluteProjectRoot, file);
1228
+ try {
1229
+ fs10.rmSync(filePath);
1230
+ let dir = path11.dirname(filePath);
1231
+ while (dir !== absoluteProjectRoot && fs10.existsSync(dir)) {
1232
+ const entries = fs10.readdirSync(dir);
1233
+ if (entries.length === 0) {
1234
+ fs10.rmdirSync(dir);
1235
+ dir = path11.dirname(dir);
1236
+ } else {
1237
+ break;
1238
+ }
945
1239
  }
946
- ),
947
- mode === "model" && /* @__PURE__ */ jsx7(
948
- ModelMenu,
1240
+ } catch (err) {
1241
+ const message = err instanceof Error ? err.message : String(err);
1242
+ console.error(`Failed to remove ${file}: ${message}`);
1243
+ return 1;
1244
+ }
1245
+ }
1246
+ if (report.modified.length > 0) {
1247
+ console.log("Preserved modified framework files:");
1248
+ for (const f of report.modified) console.log(` - ${f}`);
1249
+ }
1250
+ if (report.preserved.length > 0) {
1251
+ console.log("Preserved project-local files:");
1252
+ for (const f of report.preserved) console.log(` - ${f}`);
1253
+ }
1254
+ try {
1255
+ deps.writeProjectMarker(
1256
+ absoluteProjectRoot,
949
1257
  {
950
- listModelStatusesFn: modelsLoader,
951
- runWarmupFn: warmupRunner,
952
- onSelect: handleModelSelect,
953
- onClose: handleMenuClose
954
- }
955
- ),
956
- /* @__PURE__ */ jsx7(LogView, { logs }),
957
- /* @__PURE__ */ jsx7(Box7, { flexGrow: 1 }),
958
- /* @__PURE__ */ jsx7(HintBar, { mode: hintMode })
959
- ] });
1258
+ enabled: true,
1259
+ workflow_template: "interview-to-knowledge",
1260
+ min_lincoln_version: readLocalPackageVersion() ?? "1.5.0"
1261
+ },
1262
+ { force: options.force }
1263
+ );
1264
+ } catch (err) {
1265
+ const message = err instanceof Error ? err.message : String(err);
1266
+ console.error(`Failed to write .lincoln.yaml: ${message}`);
1267
+ return 1;
1268
+ }
1269
+ console.log(`Migrated project at ${absoluteProjectRoot}`);
1270
+ console.log(`Removed ${report.removable.length} unmodified framework files`);
1271
+ return 0;
960
1272
  }
961
1273
 
962
- // src/cli.tsx
963
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
964
- function App({ args, config }) {
965
- if (args.help) {
966
- return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", padding: 1, children: [
967
- /* @__PURE__ */ jsx8(Text7, { bold: true, children: "lincoln" }),
968
- /* @__PURE__ */ jsx8(Text7, { children: "Cross-tool TUI for Lincoln interview recording." }),
969
- /* @__PURE__ */ jsx8(Text7, {}),
970
- /* @__PURE__ */ jsx8(Text7, { bold: true, children: "Usage:" }),
971
- /* @__PURE__ */ jsx8(Text7, { children: " lincoln [options]" }),
972
- /* @__PURE__ */ jsx8(Text7, {}),
973
- /* @__PURE__ */ jsx8(Text7, { bold: true, children: "Options:" }),
974
- /* @__PURE__ */ jsx8(Text7, { children: " --topic Interview topic" }),
975
- /* @__PURE__ */ jsx8(Text7, { children: " --design-id Design ID for the session" }),
976
- /* @__PURE__ */ jsx8(Text7, { children: " --branch Current branch name" }),
977
- /* @__PURE__ */ jsx8(Text7, { children: " --session-id Explicit session ID" }),
978
- /* @__PURE__ */ jsx8(Text7, { children: " --no-tui Run without the terminal UI" }),
979
- /* @__PURE__ */ jsx8(Text7, { children: " --help, -h Show this help message" }),
980
- /* @__PURE__ */ jsx8(Text7, {}),
981
- /* @__PURE__ */ jsx8(Text7, { dimColor: true, children: "Configuration files: ~/.lincolnrc, ./.lincolnrc" })
982
- ] });
983
- }
984
- return /* @__PURE__ */ jsx8(
985
- RecordingApp,
986
- {
987
- workspaceRoot: config.workspaceRoot,
988
- sessionId: config.sessionId,
989
- topic: config.topic,
990
- designId: config.designId,
991
- branch: config.branch,
992
- audioMeterStyle: config.audioMeterStyle
993
- }
994
- );
1274
+ // src/commands/record.ts
1275
+ import { spawn as spawn5 } from "child_process";
1276
+ import { EventEmitter } from "events";
1277
+ function createDefaultDeps6() {
1278
+ return { spawn: spawn5 };
995
1279
  }
996
- async function main(argv = process.argv.slice(2)) {
997
- const args = parseArgs(argv);
998
- const config = resolveConfig(args);
999
- if (args.noTui) {
1000
- console.log(JSON.stringify(config, null, 2));
1001
- return;
1002
- }
1003
- const { render } = await import("ink");
1004
- render(/* @__PURE__ */ jsx8(App, { args, config }));
1280
+ function record(args, deps = createDefaultDeps6()) {
1281
+ return new Promise((resolve) => {
1282
+ const child = deps.spawn("lincoln-record", args, { stdio: "inherit" });
1283
+ child.on("error", (err) => {
1284
+ console.error(`Failed to start lincoln-record: ${err.message}`);
1285
+ resolve(1);
1286
+ });
1287
+ child.on("close", (code) => {
1288
+ resolve(code ?? 0);
1289
+ });
1290
+ });
1005
1291
  }
1006
1292
 
1007
- // src/main.tsx
1008
- main();
1293
+ // src/main.ts
1294
+ var USAGE = `Lincoln \u2014 Global agent harness plugin CLI
1295
+
1296
+ Usage:
1297
+ lincoln <command> [options]
1298
+
1299
+ Commands:
1300
+ install One-time global setup: sync to ~/.claude/, ~/.codex/, ~/.opencode/
1301
+ update Pull latest release and re-sync globally
1302
+ use <version> Switch active global version
1303
+ doctor Diagnose harness integration
1304
+ init-project Create .lincoln.yaml in current project
1305
+ migrate-project Remove vendored framework files from an existing project
1306
+ record Launch the Lincoln interview recorder TUI
1307
+ help Show this help message
1308
+
1309
+ Options:
1310
+ --yes, -y Skip confirmation prompts
1311
+ --dry-run Show what would be done without making changes
1312
+ --force Force re-install even if up to date
1313
+ --check Check for available updates without installing (update)
1314
+ --json Output machine-readable JSON (where supported)
1315
+ --help, -h Show this help message
1316
+ `;
1317
+ async function main(argv) {
1318
+ const parsed = parseArgs(argv);
1319
+ if (parsed.command === "help") {
1320
+ console.log(USAGE);
1321
+ return 0;
1322
+ }
1323
+ if (parsed.command === "install") {
1324
+ return install({
1325
+ yes: Boolean(parsed.flags.yes || parsed.flags.y),
1326
+ dryRun: Boolean(parsed.flags["dry-run"]),
1327
+ force: Boolean(parsed.flags.force),
1328
+ harnesses: parsed.args,
1329
+ noVenv: Boolean(parsed.flags["no-venv"])
1330
+ });
1331
+ }
1332
+ if (parsed.command === "update") {
1333
+ return update({
1334
+ check: Boolean(parsed.flags.check),
1335
+ dryRun: Boolean(parsed.flags["dry-run"]),
1336
+ yes: Boolean(parsed.flags.yes || parsed.flags.y)
1337
+ });
1338
+ }
1339
+ if (parsed.command === "use") {
1340
+ const version = parsed.args[0];
1341
+ if (!version) {
1342
+ console.error("Usage: lincoln use <version> [--yes] [--dry-run]");
1343
+ return 1;
1344
+ }
1345
+ return use(version, {
1346
+ dryRun: Boolean(parsed.flags["dry-run"]),
1347
+ yes: Boolean(parsed.flags.yes || parsed.flags.y)
1348
+ });
1349
+ }
1350
+ if (parsed.command === "doctor") {
1351
+ const result = await doctor({ json: Boolean(parsed.flags.json) });
1352
+ return result.code;
1353
+ }
1354
+ if (parsed.command === "init-project") {
1355
+ return initProject(process.cwd(), {
1356
+ force: Boolean(parsed.flags.force),
1357
+ dryRun: Boolean(parsed.flags["dry-run"])
1358
+ });
1359
+ }
1360
+ if (parsed.command === "migrate-project") {
1361
+ return migrateProject(process.cwd(), {
1362
+ dryRun: Boolean(parsed.flags["dry-run"]),
1363
+ yes: Boolean(parsed.flags.yes || parsed.flags.y),
1364
+ force: Boolean(parsed.flags.force)
1365
+ });
1366
+ }
1367
+ if (parsed.command === "record") {
1368
+ return record(parsed.args);
1369
+ }
1370
+ console.log(USAGE);
1371
+ return 0;
1372
+ }
1373
+ if (process.argv[1] && path12.resolve(fs11.realpathSync(fileURLToPath2(import.meta.url))) === path12.resolve(fs11.realpathSync(process.argv[1]))) {
1374
+ main(process.argv).then((code) => process.exit(code));
1375
+ }
1376
+ export {
1377
+ main
1378
+ };
1009
1379
  //# sourceMappingURL=main.js.map