@sushanglewis/lincoln 1.5.0 → 1.5.1

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 +1231 -906
  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,1334 @@
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 pythonCommand2 = 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(pythonCommand2, ["--version"]),
125
+ pyyamlVersion: () => queryPyyamlVersion(pythonCommand2),
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" };
84
195
  }
196
+ return { name: "npm", status: "ok", message: `npm ${version}` };
85
197
  }
86
- function loadConfigOptional(path) {
87
- 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 {};
93
- }
94
- throw error;
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}` };
95
202
  }
203
+ return { name: "global-marker", status: "ok", message: `Lincoln ${marker.version} installed` };
96
204
  }
97
-
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}`;
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}` };
104
216
  }
105
- function normalizeDescriptor(descriptor) {
106
- if (!descriptor) {
107
- return "recording";
217
+ function checkVenv(paths) {
218
+ if (!fs2.existsSync(paths.venvDir)) {
219
+ return { name: "venv", status: "warn", message: `Virtual environment not found at ${paths.venvDir}` };
108
220
  }
109
- const normalized = descriptor.trim().toLowerCase().replace(/[^a-z0-9一-龥]+/g, "-").replace(/^-+|-+$/g, "");
110
- return normalized.slice(0, 80) || "recording";
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;
256
+ }
257
+ return { major: Number(match[1]), minor: Number(match[2]) };
258
+ }
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(pythonCommand2) {
277
+ return new Promise((resolvePromise) => {
278
+ const child = spawn(
279
+ pythonCommand2,
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
+ });
111
293
  }
112
294
 
113
- // src/config/resolveConfig.ts
114
- function pickFirst(...values) {
115
- for (const value of values) {
116
- if (value !== void 0 && value !== "") {
117
- return value;
295
+ // src/lib/packageInfo.ts
296
+ import fs3 from "fs";
297
+ import path4 from "path";
298
+ import { fileURLToPath } from "url";
299
+ function readLocalPackageRoot() {
300
+ try {
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);
118
312
  }
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;
119
325
  }
120
- return "";
121
326
  }
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;
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
+ }
333
+
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;
127
342
  }
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;
343
+ try {
344
+ const parsed = yaml.load(fs4.readFileSync(markerPath, "utf8"));
345
+ if (!parsed || typeof parsed !== "object") {
346
+ return void 0;
133
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;
134
355
  }
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
- };
356
+ }
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);
366
+ }
367
+ }
368
+
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;
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;
458
+ }
269
459
  }
270
- if (key.upArrow) {
271
- setIndex((i) => i > 0 ? i - 1 : items.length - 1);
272
- return;
460
+ return result;
461
+ }
462
+ return incoming;
463
+ }
464
+ function syncClaudeCode(opts) {
465
+ const report = {
466
+ written: [],
467
+ skipped: [],
468
+ preserved: [],
469
+ warnings: [],
470
+ settingsTouched: []
471
+ };
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;
273
484
  }
274
- if (key.downArrow) {
275
- setIndex((i) => i < items.length - 1 ? i + 1 : 0);
276
- return;
485
+ const destDir = path6.join(opts.targetDir, subdir);
486
+ if (!opts.dryRun) {
487
+ fs5.mkdirSync(destDir, { recursive: true });
277
488
  }
278
- if (key.return && items.length > 0) {
279
- onSelect(items[Math.min(index, items.length - 1)].key);
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;
496
+ }
497
+ if (!opts.dryRun) {
498
+ fs5.mkdirSync(path6.dirname(destFile), { recursive: true });
499
+ fs5.copyFileSync(srcFile, destFile);
500
+ }
501
+ report.written.push(path6.join(".claude", subdir, relativePath));
280
502
  }
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
- });
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");
307
517
  }
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"
518
+ report.written.push(".claude/settings.json");
519
+ report.settingsTouched = [".claude/settings.json"];
520
+ } else {
521
+ report.skipped.push(".claude/settings.json");
344
522
  }
345
- );
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
+ }
346
551
  }
347
552
 
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;
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
368
577
  };
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
- });
578
+ });
579
+ }
580
+ function installedHarnessIds(homeDir) {
581
+ return detectGlobalHarnesses(homeDir).filter((h) => h.installed).map((h) => h.id);
582
+ }
583
+
584
+ // src/commands/install.ts
585
+ function createDefaultDeps2() {
586
+ return {
587
+ paths: resolveLincolnPaths(),
588
+ payloadRoot: resolvePayloadRoot() || "",
589
+ syncClaude: syncClaudeCode
398
590
  };
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"
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 });
432
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
+ await setupVenv(deps.paths, payloadRoot);
634
+ }
635
+ if (!options.dryRun) {
636
+ writeVersionMarker(deps.paths, {
637
+ version,
638
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
639
+ harnesses,
640
+ managedFiles: report.written
641
+ });
642
+ }
643
+ if (options.dryRun) {
644
+ console.log(`Dry run: would install Lincoln ${version} for harnesses: ${harnesses.join(", ")}`);
645
+ console.log(`Would write ${report.written.length} files`);
646
+ } else {
647
+ console.log(`Installed Lincoln ${version} for harnesses: ${harnesses.join(", ")}`);
648
+ console.log(`Wrote ${report.written.length} files`);
649
+ }
650
+ return 0;
651
+ }
652
+ function runCommand(command, args) {
653
+ return new Promise((resolvePromise, rejectPromise) => {
654
+ const child = spawn2(command, args, { stdio: "inherit" });
655
+ child.on("close", (code) => resolvePromise(code ?? 1));
656
+ child.on("error", (err) => rejectPromise(err));
657
+ });
658
+ }
659
+ function pythonCommand() {
660
+ return process.env.LINCOLN_PYTHON || "python3";
661
+ }
662
+ async function setupVenv(paths, payloadRoot) {
663
+ const pythonExe = path8.join(
664
+ paths.venvDir,
665
+ process.platform === "win32" ? "Scripts" : "bin",
666
+ process.platform === "win32" ? "python.exe" : "python"
433
667
  );
668
+ if (!fs7.existsSync(pythonExe)) {
669
+ console.log("Creating Lincoln virtual environment...");
670
+ await runCommand(pythonCommand(), ["-m", "venv", paths.venvDir]);
671
+ }
672
+ const requirementsPath = path8.join(payloadRoot, "requirements.txt");
673
+ if (fs7.existsSync(requirementsPath)) {
674
+ console.log("Installing Python dependencies...");
675
+ const pipExe = path8.join(
676
+ paths.venvDir,
677
+ process.platform === "win32" ? "Scripts" : "bin",
678
+ process.platform === "win32" ? "pip.exe" : "pip"
679
+ );
680
+ await runCommand(pipExe, ["install", "-r", requirementsPath]);
681
+ }
434
682
  }
435
683
 
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;
684
+ // src/commands/update.ts
685
+ import { spawn as spawn3 } from "child_process";
686
+
687
+ // src/lib/npmRegistry.ts
688
+ var DEFAULT_REGISTRY_URL = "https://registry.npmjs.org";
689
+ var FETCH_TIMEOUT_MS = 15e3;
690
+ function createRegistryClient(registryUrl = DEFAULT_REGISTRY_URL, fetchImpl = fetch) {
691
+ const baseUrl = registryUrl.replace(/\/+$/, "");
692
+ async function fetchPackument(packageName) {
693
+ const url = `${baseUrl}/${encodePackageName(packageName)}`;
694
+ const response = await fetchImpl(url, {
695
+ headers: { Accept: "application/json" },
696
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
697
+ });
698
+ if (!response.ok) {
699
+ throw new Error(
700
+ `npm registry returned ${response.status} ${response.statusText} for ${packageName}`
701
+ );
702
+ }
703
+ return await response.json();
704
+ }
705
+ return {
706
+ async latestVersion(packageName) {
707
+ const packument = await fetchPackument(packageName);
708
+ const latest = packument["dist-tags"]?.latest;
709
+ if (!latest) {
710
+ throw new Error(`npm registry has no latest dist-tag for ${packageName}`);
453
711
  }
454
- if (key.ctrl && input === "c") {
455
- onQuit?.();
456
- return;
712
+ return latest;
713
+ },
714
+ async versions(packageName) {
715
+ const packument = await fetchPackument(packageName);
716
+ return Object.keys(packument.versions ?? {});
717
+ },
718
+ async resolveVersion(packageName, spec) {
719
+ const packument = await fetchPackument(packageName);
720
+ const tagTarget = packument["dist-tags"]?.[spec];
721
+ if (tagTarget) {
722
+ return tagTarget;
457
723
  }
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;
724
+ if (packument.versions && spec in packument.versions) {
725
+ return spec;
479
726
  }
480
- onAnyKey?.();
481
- },
482
- { isActive: enabled }
483
- );
727
+ throw new Error(`Version not found for ${packageName}: ${spec}`);
728
+ }
729
+ };
730
+ }
731
+ function encodePackageName(packageName) {
732
+ if (packageName.startsWith("@")) {
733
+ const slash = packageName.indexOf("/");
734
+ if (slash === -1) {
735
+ return encodeURIComponent(packageName);
736
+ }
737
+ const scope = packageName.slice(1, slash);
738
+ const name = packageName.slice(slash + 1);
739
+ return `@${encodeURIComponent(scope)}%2F${encodeURIComponent(name)}`;
740
+ }
741
+ return encodeURIComponent(packageName);
484
742
  }
485
743
 
486
- // src/recording/useRecorder.ts
487
- import { useCallback, useEffect as useEffect3, useRef, useState as useState4 } from "react";
744
+ // src/lib/semver.ts
745
+ var SEMVER_CORE = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
746
+ var NUMERIC_IDENTIFIER = /^\d+$/;
747
+ function compareVersions(a, b) {
748
+ const pa = parseVersion(a);
749
+ const pb = parseVersion(b);
750
+ if (!pa || !pb) {
751
+ return void 0;
752
+ }
753
+ if (pa.major !== pb.major) return pa.major - pb.major;
754
+ if (pa.minor !== pb.minor) return pa.minor - pb.minor;
755
+ if (pa.patch !== pb.patch) return pa.patch - pb.patch;
756
+ return comparePrerelease(pa.prerelease, pb.prerelease);
757
+ }
758
+ function parseVersion(version) {
759
+ const match = SEMVER_CORE.exec(version.trim());
760
+ if (!match) {
761
+ return void 0;
762
+ }
763
+ return {
764
+ major: Number(match[1]),
765
+ minor: Number(match[2]),
766
+ patch: Number(match[3]),
767
+ prerelease: match[4] ? match[4].split(".") : []
768
+ };
769
+ }
770
+ function comparePrerelease(a, b) {
771
+ if (a.length === 0 && b.length === 0) return 0;
772
+ if (a.length === 0) return 1;
773
+ if (b.length === 0) return -1;
774
+ const shared = Math.min(a.length, b.length);
775
+ for (let i = 0; i < shared; i++) {
776
+ const x = a[i];
777
+ const y = b[i];
778
+ if (x === y) continue;
779
+ const xNumeric = NUMERIC_IDENTIFIER.test(x);
780
+ const yNumeric = NUMERIC_IDENTIFIER.test(y);
781
+ if (xNumeric && yNumeric) return Number(x) - Number(y);
782
+ if (xNumeric) return -1;
783
+ if (yNumeric) return 1;
784
+ return x < y ? -1 : 1;
785
+ }
786
+ return a.length - b.length;
787
+ }
488
788
 
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 = () => {
789
+ // src/commands/update.ts
790
+ var PACKAGE_NAME = "@sushanglewis/lincoln";
791
+ function createDefaultDeps3() {
792
+ const paths = resolveLincolnPaths();
793
+ const registry = createRegistryClient();
794
+ return {
795
+ paths,
796
+ latestVersion: () => registry.latestVersion(PACKAGE_NAME),
797
+ currentVersion: () => readVersionMarker(paths)?.version ?? readLocalPackageVersion(),
798
+ npmInstallGlobal: (version) => runNpmInstallGlobal(PACKAGE_NAME, version),
799
+ runInstall: (options) => install(options)
497
800
  };
498
- exitSettled = false;
499
- constructor(child) {
500
- super();
501
- this.child = child;
502
- this.exitPromise = new Promise((resolve) => {
503
- this.resolveExit = resolve;
504
- });
505
- this.child.on("error", (error) => {
506
- this.emit("error", error);
507
- this.settleExit();
508
- });
509
- this.child.on("exit", (code, signal) => {
510
- this.emit("exit", code, signal);
511
- this.settleExit();
512
- });
513
- this.child.stdout.on("data", (data) => {
514
- process.stdout.write(data);
515
- });
516
- this.child.stderr.on("data", (data) => {
517
- process.stderr.write(data);
518
- });
519
- process.nextTick(() => this.emit("ready"));
801
+ }
802
+ async function update(options, deps = {}) {
803
+ const resolved = { ...createDefaultDeps3(), ...deps };
804
+ let latest;
805
+ try {
806
+ latest = await resolved.latestVersion();
807
+ } catch (err) {
808
+ const message = err instanceof Error ? err.message : String(err);
809
+ console.error(`Failed to query npm registry: ${message}`);
810
+ return 1;
520
811
  }
521
- settleExit() {
522
- if (!this.exitSettled) {
523
- this.exitSettled = true;
524
- this.resolveExit();
812
+ const current = resolved.currentVersion();
813
+ if (current !== void 0) {
814
+ const comparison = compareVersions(current, latest);
815
+ if (comparison !== void 0 ? comparison > 0 : false) {
816
+ console.log(
817
+ `Lincoln ${current} is ahead of the latest release (${latest}); nothing to do.`
818
+ );
819
+ return 0;
820
+ }
821
+ if (comparison !== void 0 ? comparison === 0 : current === latest) {
822
+ console.log(`Lincoln ${current} is already up to date.`);
823
+ return 0;
525
824
  }
526
825
  }
527
- stop() {
528
- return this._terminate("SIGINT");
826
+ const summary = `${current ?? "unknown"} -> ${latest}`;
827
+ if (options.check) {
828
+ console.log(`Lincoln update available: ${summary}`);
829
+ return 0;
529
830
  }
530
- cancel() {
531
- return this._terminate("SIGKILL");
831
+ if (options.dryRun) {
832
+ console.log(`Dry run: would update Lincoln ${summary}`);
833
+ return 0;
532
834
  }
533
- _terminate(signal) {
534
- if (this.stopped) {
535
- return this.exitPromise;
536
- }
537
- this.stopped = true;
538
- if (this.child.killed) {
539
- return this.exitPromise;
540
- }
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;
835
+ if (!options.yes) {
836
+ console.error(`Will update Lincoln ${summary}. Run with --yes to proceed.`);
837
+ return 1;
549
838
  }
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"]
839
+ const installCode = await resolved.npmInstallGlobal(latest);
840
+ if (installCode !== 0) {
841
+ console.error(`npm install -g ${PACKAGE_NAME}@${latest} failed with exit code ${installCode}`);
842
+ return 1;
843
+ }
844
+ const syncCode = await resolved.runInstall(defaultInstallOptions());
845
+ if (syncCode !== 0) {
846
+ console.error(`lincoln install failed with exit code ${syncCode}`);
847
+ return 1;
848
+ }
849
+ console.log(`Updated Lincoln to ${latest}`);
850
+ return 0;
851
+ }
852
+ function defaultInstallOptions() {
853
+ return { yes: true, dryRun: false, force: false, harnesses: [], noVenv: true };
854
+ }
855
+ function runNpmInstallGlobal(packageName, version) {
856
+ return new Promise((resolvePromise) => {
857
+ const child = spawn3("npm", ["install", "-g", `${packageName}@${version}`], {
858
+ stdio: "inherit",
859
+ shell: process.platform === "win32"
860
+ });
861
+ child.on("close", (code) => resolvePromise(code ?? 1));
862
+ child.on("error", () => resolvePromise(1));
575
863
  });
576
- return new RecorderProcessImpl(child);
577
864
  }
578
865
 
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;
866
+ // src/commands/use.ts
867
+ import fs8 from "fs";
868
+ import path9 from "path";
869
+ import { spawn as spawn4 } from "child_process";
870
+ var PACKAGE_NAME2 = "@sushanglewis/lincoln";
871
+ var SEMVER_REGEX = /^v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
872
+ var STRICT_SEMVER_REGEX = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
873
+ var DIST_TAG_REGEX = /^[a-zA-Z][a-zA-Z0-9._-]*$/;
874
+ function createDefaultDeps4() {
875
+ const paths = resolveLincolnPaths();
876
+ const registry = createRegistryClient();
877
+ return {
878
+ paths,
879
+ resolveVersion: async (spec) => {
880
+ const normalized = spec.startsWith("v") ? spec.slice(1) : spec;
881
+ if (STRICT_SEMVER_REGEX.test(normalized)) {
882
+ return normalized;
883
+ }
884
+ return registry.resolveVersion(PACKAGE_NAME2, spec);
885
+ },
886
+ ensureVersionAvailable: async (version) => {
887
+ const code = await runNpmInstallGlobal2(PACKAGE_NAME2, version);
888
+ if (code !== 0) {
889
+ throw new Error(`npm install -g ${PACKAGE_NAME2}@${version} exited with ${code}`);
890
+ }
891
+ },
892
+ npmGlobalRoot: () => runNpmRootGlobal(),
893
+ runInstall: (options) => install(options)
894
+ };
895
+ }
896
+ async function use(spec, options, deps = {}) {
897
+ if (!isValidVersion(spec)) {
898
+ console.error(`Invalid version: ${spec}`);
899
+ return 1;
900
+ }
901
+ const resolved = { ...createDefaultDeps4(), ...deps };
902
+ let version;
903
+ try {
904
+ version = await resolved.resolveVersion(spec);
905
+ } catch (err) {
906
+ const message = err instanceof Error ? err.message : String(err);
907
+ console.error(`Failed to resolve version ${spec}: ${message}`);
908
+ return 1;
909
+ }
910
+ const versionDir = path9.join(resolved.paths.versionsDir, version);
911
+ const alreadyInstalled = fs8.existsSync(versionDir);
912
+ if (options.dryRun) {
913
+ if (alreadyInstalled) {
914
+ console.log(`Dry run: would switch ${resolved.paths.currentDir} to ${versionDir}`);
915
+ } else {
916
+ console.log(
917
+ `Dry run: would install ${PACKAGE_NAME2}@${version} globally, then switch ${resolved.paths.currentDir} to ${versionDir}`
918
+ );
611
919
  }
612
- terminatingRef.current = false;
613
- setState({ status: "recording", duration: 0, amplitude: 0.3, errorMessage: null });
614
- durationRef.current = 0;
920
+ return 0;
921
+ }
922
+ if (!options.yes) {
923
+ console.error(`Will switch Lincoln to ${version}. Run with --yes to proceed.`);
924
+ return 1;
925
+ }
926
+ if (!alreadyInstalled) {
615
927
  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();
928
+ await resolved.ensureVersionAvailable(version);
929
+ } catch (err) {
930
+ const message = err instanceof Error ? err.message : String(err);
931
+ console.error(`Failed to install ${PACKAGE_NAME2}@${version}: ${message}`);
932
+ return 1;
671
933
  }
672
- return () => {
673
- clearRecordingInterval();
674
- if (recorderRef.current) {
675
- recorderRef.current.cancel().catch(() => {
676
- });
677
- recorderRef.current = null;
934
+ if (!fs8.existsSync(versionDir)) {
935
+ try {
936
+ await copyVersionFromNpmGlobal(resolved, versionDir);
937
+ } catch (err) {
938
+ const message = err instanceof Error ? err.message : String(err);
939
+ console.error(`Failed to populate ${versionDir}: ${message}`);
940
+ return 1;
678
941
  }
679
- };
680
- }, [startOnMount, start, clearRecordingInterval]);
681
- return { state, start, stop, cancel };
682
- }
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;
699
942
  }
700
- devices.push(line);
943
+ if (!fs8.existsSync(versionDir)) {
944
+ console.error(
945
+ `Provisioning ${PACKAGE_NAME2}@${version} did not produce ${versionDir}; cannot switch versions.`
946
+ );
947
+ return 1;
948
+ }
949
+ }
950
+ try {
951
+ switchCurrentLink(resolved.paths, versionDir);
952
+ } catch (err) {
953
+ const message = err instanceof Error ? err.message : String(err);
954
+ console.error(`Failed to switch ${resolved.paths.currentDir}: ${message}`);
955
+ return 1;
956
+ }
957
+ const syncCode = await resolved.runInstall(defaultInstallOptions2());
958
+ if (syncCode !== 0) {
959
+ console.error(`lincoln install failed with exit code ${syncCode}`);
960
+ return 1;
961
+ }
962
+ try {
963
+ switchCurrentLink(resolved.paths, versionDir);
964
+ } catch (err) {
965
+ const message = err instanceof Error ? err.message : String(err);
966
+ console.error(`Failed to switch ${resolved.paths.currentDir}: ${message}`);
967
+ return 1;
701
968
  }
702
- return { defaultDevice, devices };
969
+ console.log(`Switched Lincoln to ${version}`);
970
+ return 0;
703
971
  }
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);
972
+ function isValidVersion(version) {
973
+ return SEMVER_REGEX.test(version) || DIST_TAG_REGEX.test(version);
710
974
  }
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");
975
+ async function copyVersionFromNpmGlobal(deps, versionDir) {
976
+ const globalRoot = await deps.npmGlobalRoot();
977
+ if (!globalRoot) {
978
+ return;
979
+ }
980
+ const packageRoot = path9.join(globalRoot, ...PACKAGE_NAME2.split("/"));
981
+ if (!fs8.existsSync(packageRoot)) {
982
+ return;
983
+ }
984
+ fs8.mkdirSync(path9.dirname(versionDir), { recursive: true });
985
+ fs8.cpSync(packageRoot, versionDir, { recursive: true });
986
+ }
987
+ function switchCurrentLink(paths, versionDir) {
988
+ fs8.mkdirSync(paths.lincolnHome, { recursive: true });
989
+ try {
990
+ fs8.rmSync(paths.currentDir, { recursive: true, force: true });
991
+ fs8.symlinkSync(versionDir, paths.currentDir);
992
+ } catch {
993
+ fs8.rmSync(paths.currentDir, { recursive: true, force: true });
994
+ fs8.cpSync(versionDir, paths.currentDir, { recursive: true, force: true });
995
+ }
728
996
  }
729
- function modelCachePath(name, cacheDir) {
730
- return join2(cacheDir, "whisper", `ggml-${name}.bin`);
997
+ function defaultInstallOptions2() {
998
+ return { yes: true, dryRun: false, force: false, harnesses: [], noVenv: true };
731
999
  }
732
- function listModelStatuses(cacheDir) {
733
- return KNOWN_MODELS.map((name) => ({
734
- name,
735
- cached: existsSync2(modelCachePath(name, cacheDir))
736
- }));
1000
+ function runNpmInstallGlobal2(packageName, version) {
1001
+ return new Promise((resolvePromise) => {
1002
+ const child = spawn4("npm", ["install", "-g", `${packageName}@${version}`], {
1003
+ stdio: "inherit",
1004
+ shell: process.platform === "win32"
1005
+ });
1006
+ child.on("close", (code) => resolvePromise(code ?? 1));
1007
+ child.on("error", () => resolvePromise(1));
1008
+ });
1009
+ }
1010
+ function runNpmRootGlobal() {
1011
+ return new Promise((resolvePromise) => {
1012
+ const child = spawn4("npm", ["root", "-g"], {
1013
+ stdio: ["ignore", "pipe", "ignore"],
1014
+ shell: process.platform === "win32"
1015
+ });
1016
+ let stdout = "";
1017
+ child.stdout?.on("data", (chunk) => {
1018
+ stdout += chunk.toString();
1019
+ });
1020
+ child.on("close", (code) => {
1021
+ const root = stdout.trim();
1022
+ resolvePromise(code === 0 && root.length > 0 ? root : void 0);
1023
+ });
1024
+ child.on("error", () => resolvePromise(void 0));
1025
+ });
737
1026
  }
738
1027
 
739
- // src/models/runWarmup.ts
740
- import { spawn as spawn2 } from "child_process";
1028
+ // src/commands/migrateProject.ts
1029
+ import fs10 from "fs";
1030
+ import path11 from "path";
741
1031
 
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
- };
1032
+ // src/lib/projectFiles.ts
1033
+ import fs9 from "fs";
1034
+ import path10 from "path";
1035
+ import crypto2 from "crypto";
1036
+ var FRAMEWORK_ALLOWLIST_DIRS = [".claude", ".claude-plugin", "scripts", "tools"];
1037
+ var FRAMEWORK_ALLOWLIST_FILES = [
1038
+ "README.md",
1039
+ "README.en.md",
1040
+ "USAGE.md",
1041
+ "CONTRIBUTING.md",
1042
+ "CLAUDE.md",
1043
+ "LICENSE",
1044
+ "RELEASE.md",
1045
+ ".version-bump.json",
1046
+ "requirements.txt",
1047
+ "SKILL.md"
1048
+ ];
1049
+ var FRAMEWORK_DENYLIST_NAMES = /* @__PURE__ */ new Set([
1050
+ ".git",
1051
+ ".context",
1052
+ ".venv",
1053
+ "venv",
1054
+ "dist",
1055
+ "oss",
1056
+ ".pytest_cache",
1057
+ "__pycache__",
1058
+ ".DS_Store",
1059
+ "node_modules"
1060
+ ]);
1061
+ var FRAMEWORK_DENYLIST_PREFIXES = ["issue-"];
1062
+ var FRAMEWORK_SKIP_PATHS = /* @__PURE__ */ new Set([
1063
+ ".claude/templates/issue-package",
1064
+ "tools/lincoln-installer/dist",
1065
+ "tools/lincoln-installer/node_modules",
1066
+ "tools/lincoln-installer/coverage"
1067
+ ]);
1068
+ function sha2562(filePath) {
1069
+ return crypto2.createHash("sha256").update(fs9.readFileSync(filePath)).digest("hex");
1070
+ }
1071
+ function isDenied(relativePath) {
1072
+ const parts = relativePath.split(path10.sep);
1073
+ for (const part of parts) {
1074
+ if (FRAMEWORK_DENYLIST_NAMES.has(part)) return true;
1075
+ for (const prefix of FRAMEWORK_DENYLIST_PREFIXES) {
1076
+ if (part.startsWith(prefix)) return true;
1077
+ }
753
1078
  }
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
- };
1079
+ for (const skip of FRAMEWORK_SKIP_PATHS) {
1080
+ if (relativePath === skip || relativePath.startsWith(skip + path10.sep)) return true;
761
1081
  }
762
- return null;
1082
+ return false;
763
1083
  }
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();
1084
+ function isAllowlisted(relativePath) {
1085
+ if (FRAMEWORK_ALLOWLIST_FILES.includes(relativePath)) return true;
1086
+ for (const dir of FRAMEWORK_ALLOWLIST_DIRS) {
1087
+ if (relativePath === dir || relativePath.startsWith(dir + path10.sep)) return true;
1088
+ }
1089
+ return false;
1090
+ }
1091
+ function isPreservedDir(relativePath) {
1092
+ const normalized = relativePath.replace(/\\/g, "/");
1093
+ if (normalized === ".context" || normalized.startsWith(".context/")) return true;
1094
+ const parts = normalized.split("/");
1095
+ for (const part of parts) {
1096
+ if (part.startsWith("issue-")) return true;
1097
+ }
1098
+ return false;
1099
+ }
1100
+ function isPreserved(relativePath) {
1101
+ const normalized = relativePath.replace(/\\/g, "/");
1102
+ if (normalized === ".github/openspec-config.yml") return true;
1103
+ return isPreservedDir(relativePath);
1104
+ }
1105
+ function listVendoredFrameworkFiles(payloadRoot, projectRoot) {
1106
+ const report = { removable: [], modified: [], preserved: [] };
1107
+ function walk(dir) {
1108
+ const entries = fs9.readdirSync(dir, { withFileTypes: true });
1109
+ for (const entry of entries) {
1110
+ const fullPath = path10.join(dir, entry.name);
1111
+ const relativePath = path10.relative(projectRoot, fullPath);
1112
+ if (entry.isDirectory()) {
1113
+ if (isPreservedDir(relativePath)) {
1114
+ walk(fullPath);
1115
+ } else if (!isDenied(relativePath)) {
1116
+ walk(fullPath);
1117
+ }
1118
+ continue;
778
1119
  }
779
- });
780
- child.stderr.on("data", (data) => {
781
- const progress = parseWarmupProgress(data.toString());
782
- if (progress) {
783
- onProgress?.(progress);
1120
+ if (isPreserved(relativePath)) {
1121
+ report.preserved.push(relativePath);
1122
+ continue;
784
1123
  }
785
- });
786
- child.on("error", reject);
787
- child.on("exit", (code) => {
788
- if (code === 0) {
789
- resolve(readyPath ?? model);
1124
+ if (isDenied(relativePath)) continue;
1125
+ if (!isAllowlisted(relativePath)) continue;
1126
+ const payloadFile = path10.join(payloadRoot, relativePath);
1127
+ if (!fs9.existsSync(payloadFile)) {
1128
+ report.modified.push(relativePath);
1129
+ continue;
1130
+ }
1131
+ if (sha2562(fullPath) === sha2562(payloadFile)) {
1132
+ report.removable.push(relativePath);
790
1133
  } else {
791
- reject(new Error(`lincoln-record warmup exited with code ${code}`));
1134
+ report.modified.push(relativePath);
792
1135
  }
793
- });
794
- });
1136
+ }
1137
+ }
1138
+ if (fs9.existsSync(projectRoot)) {
1139
+ walk(projectRoot);
1140
+ }
1141
+ return report;
795
1142
  }
796
1143
 
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
- });
1144
+ // src/commands/migrateProject.ts
1145
+ function createDefaultDeps5() {
1146
+ return {
1147
+ paths: resolveLincolnPaths(),
1148
+ listVendoredFrameworkFiles,
1149
+ writeProjectMarker
1150
+ };
1151
+ }
1152
+ async function migrateProject(projectRoot, options = {}, deps = createDefaultDeps5()) {
1153
+ const payloadRoot = deps.paths.currentDir;
1154
+ if (!fs10.existsSync(payloadRoot)) {
1155
+ console.error(`Lincoln payload not found at ${payloadRoot}. Run "lincoln install" first.`);
1156
+ return 1;
1157
+ }
1158
+ const absoluteProjectRoot = path11.resolve(projectRoot);
1159
+ const report = deps.listVendoredFrameworkFiles(payloadRoot, absoluteProjectRoot);
1160
+ if (options.dryRun) {
1161
+ console.log(`Dry run: would migrate ${absoluteProjectRoot}`);
1162
+ if (report.removable.length > 0) {
1163
+ console.log("Would remove unmodified framework files:");
1164
+ for (const f of report.removable) console.log(` - ${f}`);
865
1165
  }
866
- }, [state.status, cancel]);
867
- const handleDevices = useCallback2(() => {
868
- if (state.status === "idle") {
869
- setMode("devices");
1166
+ if (report.modified.length > 0) {
1167
+ console.log("Would preserve modified framework files:");
1168
+ for (const f of report.modified) console.log(` - ${f}`);
870
1169
  }
871
- }, [state.status]);
872
- const handleModel = useCallback2(() => {
873
- if (state.status === "idle") {
874
- setMode("model");
1170
+ if (report.preserved.length > 0) {
1171
+ console.log("Would preserve project-local files:");
1172
+ for (const f of report.preserved) console.log(` - ${f}`);
875
1173
  }
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
1174
+ console.log("Would create .lincoln.yaml");
1175
+ return 0;
1176
+ }
1177
+ if (!options.yes) {
1178
+ console.error("This will remove vendored framework files. Run with --yes to proceed.");
1179
+ return 1;
1180
+ }
1181
+ for (const file of report.removable) {
1182
+ const filePath = path11.join(absoluteProjectRoot, file);
1183
+ try {
1184
+ fs10.rmSync(filePath);
1185
+ let dir = path11.dirname(filePath);
1186
+ while (dir !== absoluteProjectRoot && fs10.existsSync(dir)) {
1187
+ const entries = fs10.readdirSync(dir);
1188
+ if (entries.length === 0) {
1189
+ fs10.rmdirSync(dir);
1190
+ dir = path11.dirname(dir);
1191
+ } else {
1192
+ break;
1193
+ }
945
1194
  }
946
- ),
947
- mode === "model" && /* @__PURE__ */ jsx7(
948
- ModelMenu,
1195
+ } catch (err) {
1196
+ const message = err instanceof Error ? err.message : String(err);
1197
+ console.error(`Failed to remove ${file}: ${message}`);
1198
+ return 1;
1199
+ }
1200
+ }
1201
+ if (report.modified.length > 0) {
1202
+ console.log("Preserved modified framework files:");
1203
+ for (const f of report.modified) console.log(` - ${f}`);
1204
+ }
1205
+ if (report.preserved.length > 0) {
1206
+ console.log("Preserved project-local files:");
1207
+ for (const f of report.preserved) console.log(` - ${f}`);
1208
+ }
1209
+ try {
1210
+ deps.writeProjectMarker(
1211
+ absoluteProjectRoot,
949
1212
  {
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
- ] });
1213
+ enabled: true,
1214
+ workflow_template: "interview-to-knowledge",
1215
+ min_lincoln_version: readLocalPackageVersion() ?? "1.5.0"
1216
+ },
1217
+ { force: options.force }
1218
+ );
1219
+ } catch (err) {
1220
+ const message = err instanceof Error ? err.message : String(err);
1221
+ console.error(`Failed to write .lincoln.yaml: ${message}`);
1222
+ return 1;
1223
+ }
1224
+ console.log(`Migrated project at ${absoluteProjectRoot}`);
1225
+ console.log(`Removed ${report.removable.length} unmodified framework files`);
1226
+ return 0;
960
1227
  }
961
1228
 
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
- );
1229
+ // src/commands/record.ts
1230
+ import { spawn as spawn5 } from "child_process";
1231
+ import { EventEmitter } from "events";
1232
+ function createDefaultDeps6() {
1233
+ return { spawn: spawn5 };
995
1234
  }
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 }));
1235
+ function record(args, deps = createDefaultDeps6()) {
1236
+ return new Promise((resolve) => {
1237
+ const child = deps.spawn("lincoln-record", args, { stdio: "inherit" });
1238
+ child.on("error", (err) => {
1239
+ console.error(`Failed to start lincoln-record: ${err.message}`);
1240
+ resolve(1);
1241
+ });
1242
+ child.on("close", (code) => {
1243
+ resolve(code ?? 0);
1244
+ });
1245
+ });
1005
1246
  }
1006
1247
 
1007
- // src/main.tsx
1008
- main();
1248
+ // src/main.ts
1249
+ var USAGE = `Lincoln \u2014 Global agent harness plugin CLI
1250
+
1251
+ Usage:
1252
+ lincoln <command> [options]
1253
+
1254
+ Commands:
1255
+ install One-time global setup: sync to ~/.claude/, ~/.codex/, ~/.opencode/
1256
+ update Pull latest release and re-sync globally
1257
+ use <version> Switch active global version
1258
+ doctor Diagnose harness integration
1259
+ init-project Create .lincoln.yaml in current project
1260
+ migrate-project Remove vendored framework files from an existing project
1261
+ record Launch the Lincoln interview recorder TUI
1262
+ help Show this help message
1263
+
1264
+ Options:
1265
+ --yes, -y Skip confirmation prompts
1266
+ --dry-run Show what would be done without making changes
1267
+ --force Force re-install even if up to date
1268
+ --check Check for available updates without installing (update)
1269
+ --json Output machine-readable JSON (where supported)
1270
+ --help, -h Show this help message
1271
+ `;
1272
+ async function main(argv) {
1273
+ const parsed = parseArgs(argv);
1274
+ if (parsed.command === "help") {
1275
+ console.log(USAGE);
1276
+ return 0;
1277
+ }
1278
+ if (parsed.command === "install") {
1279
+ return install({
1280
+ yes: Boolean(parsed.flags.yes || parsed.flags.y),
1281
+ dryRun: Boolean(parsed.flags["dry-run"]),
1282
+ force: Boolean(parsed.flags.force),
1283
+ harnesses: parsed.args,
1284
+ noVenv: Boolean(parsed.flags["no-venv"])
1285
+ });
1286
+ }
1287
+ if (parsed.command === "update") {
1288
+ return update({
1289
+ check: Boolean(parsed.flags.check),
1290
+ dryRun: Boolean(parsed.flags["dry-run"]),
1291
+ yes: Boolean(parsed.flags.yes || parsed.flags.y)
1292
+ });
1293
+ }
1294
+ if (parsed.command === "use") {
1295
+ const version = parsed.args[0];
1296
+ if (!version) {
1297
+ console.error("Usage: lincoln use <version> [--yes] [--dry-run]");
1298
+ return 1;
1299
+ }
1300
+ return use(version, {
1301
+ dryRun: Boolean(parsed.flags["dry-run"]),
1302
+ yes: Boolean(parsed.flags.yes || parsed.flags.y)
1303
+ });
1304
+ }
1305
+ if (parsed.command === "doctor") {
1306
+ const result = await doctor({ json: Boolean(parsed.flags.json) });
1307
+ return result.code;
1308
+ }
1309
+ if (parsed.command === "init-project") {
1310
+ return initProject(process.cwd(), {
1311
+ force: Boolean(parsed.flags.force),
1312
+ dryRun: Boolean(parsed.flags["dry-run"])
1313
+ });
1314
+ }
1315
+ if (parsed.command === "migrate-project") {
1316
+ return migrateProject(process.cwd(), {
1317
+ dryRun: Boolean(parsed.flags["dry-run"]),
1318
+ yes: Boolean(parsed.flags.yes || parsed.flags.y),
1319
+ force: Boolean(parsed.flags.force)
1320
+ });
1321
+ }
1322
+ if (parsed.command === "record") {
1323
+ return record(parsed.args);
1324
+ }
1325
+ console.log(USAGE);
1326
+ return 0;
1327
+ }
1328
+ if (process.argv[1] && path12.resolve(fs11.realpathSync(fileURLToPath2(import.meta.url))) === path12.resolve(fs11.realpathSync(process.argv[1]))) {
1329
+ main(process.argv).then((code) => process.exit(code));
1330
+ }
1331
+ export {
1332
+ main
1333
+ };
1009
1334
  //# sourceMappingURL=main.js.map