litclaude-ai 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/CHANGELOG.md +155 -0
  2. package/LICENSE +21 -0
  3. package/README.md +369 -0
  4. package/README_ko-KR.md +374 -0
  5. package/RELEASE_CHECKLIST.md +165 -0
  6. package/bin/litclaude-ai.js +643 -0
  7. package/cover.png +0 -0
  8. package/docs/agents.md +67 -0
  9. package/docs/hooks.md +134 -0
  10. package/docs/lsp.md +40 -0
  11. package/docs/migration.md +209 -0
  12. package/docs/workflow-compatibility-audit.md +119 -0
  13. package/generate_cover.py +123 -0
  14. package/package.json +48 -0
  15. package/plugins/litclaude/.claude-plugin/plugin.json +25 -0
  16. package/plugins/litclaude/.lsp.json +13 -0
  17. package/plugins/litclaude/.mcp.json +9 -0
  18. package/plugins/litclaude/agents/boulder-executor.md +12 -0
  19. package/plugins/litclaude/agents/librarian-researcher.md +15 -0
  20. package/plugins/litclaude/agents/oracle-verifier.md +16 -0
  21. package/plugins/litclaude/agents/prometheus-planner.md +13 -0
  22. package/plugins/litclaude/agents/qa-runner.md +16 -0
  23. package/plugins/litclaude/agents/quality-reviewer.md +17 -0
  24. package/plugins/litclaude/bin/litclaude-hook.js +110 -0
  25. package/plugins/litclaude/bin/litclaude-hud.js +271 -0
  26. package/plugins/litclaude/bin/litclaude-lsp-doctor.js +15 -0
  27. package/plugins/litclaude/bin/litclaude-mcp.js +70 -0
  28. package/plugins/litclaude/commands/deep-interview.md +21 -0
  29. package/plugins/litclaude/commands/dynamic-workflow.md +36 -0
  30. package/plugins/litclaude/commands/lit-loop.md +40 -0
  31. package/plugins/litclaude/commands/lit-plan.md +35 -0
  32. package/plugins/litclaude/commands/litgoal.md +30 -0
  33. package/plugins/litclaude/commands/review-work.md +35 -0
  34. package/plugins/litclaude/commands/start-work.md +36 -0
  35. package/plugins/litclaude/hooks/hooks.json +54 -0
  36. package/plugins/litclaude/lib/context-pressure.mjs +25 -0
  37. package/plugins/litclaude/lib/hud-accent-palette.mjs +58 -0
  38. package/plugins/litclaude/lib/litgoal/cli.mjs +266 -0
  39. package/plugins/litclaude/lib/litgoal/ledger.mjs +16 -0
  40. package/plugins/litclaude/lib/litgoal/paths.mjs +7 -0
  41. package/plugins/litclaude/lib/litgoal/state.mjs +67 -0
  42. package/plugins/litclaude/lib/mutated-file-paths.mjs +63 -0
  43. package/plugins/litclaude/lib/start-work-continuation.mjs +99 -0
  44. package/plugins/litclaude/lib/workflow-check.mjs +83 -0
  45. package/plugins/litclaude/skills/ai-slop-remover/SKILL.md +142 -0
  46. package/plugins/litclaude/skills/comment-checker/SKILL.md +55 -0
  47. package/plugins/litclaude/skills/debugging/SKILL.md +70 -0
  48. package/plugins/litclaude/skills/debugging/references/methodology/00-setup.md +108 -0
  49. package/plugins/litclaude/skills/debugging/references/methodology/02-investigate.md +126 -0
  50. package/plugins/litclaude/skills/debugging/references/methodology/04-oracle-triple.md +106 -0
  51. package/plugins/litclaude/skills/debugging/references/methodology/05-escalate.md +69 -0
  52. package/plugins/litclaude/skills/debugging/references/methodology/06-fix.md +116 -0
  53. package/plugins/litclaude/skills/debugging/references/methodology/08-qa.md +94 -0
  54. package/plugins/litclaude/skills/debugging/references/methodology/09-cleanup.md +164 -0
  55. package/plugins/litclaude/skills/debugging/references/methodology/partial-runtime-evidence.md +228 -0
  56. package/plugins/litclaude/skills/debugging/references/runtimes/bundled-js-binary.md +415 -0
  57. package/plugins/litclaude/skills/debugging/references/runtimes/go.md +252 -0
  58. package/plugins/litclaude/skills/debugging/references/runtimes/native-binary.md +484 -0
  59. package/plugins/litclaude/skills/debugging/references/runtimes/node.md +260 -0
  60. package/plugins/litclaude/skills/debugging/references/runtimes/python.md +248 -0
  61. package/plugins/litclaude/skills/debugging/references/runtimes/rust.md +234 -0
  62. package/plugins/litclaude/skills/debugging/references/tools/ghidra.md +212 -0
  63. package/plugins/litclaude/skills/debugging/references/tools/playwright-cli.md +194 -0
  64. package/plugins/litclaude/skills/debugging/references/tools/pwndbg.md +263 -0
  65. package/plugins/litclaude/skills/debugging/references/tools/pwntools.md +265 -0
  66. package/plugins/litclaude/skills/deep-interview/SKILL.md +323 -0
  67. package/plugins/litclaude/skills/deep-interview/scripts/render_progress.py +193 -0
  68. package/plugins/litclaude/skills/frontend-ui-ux/SKILL.md +62 -0
  69. package/plugins/litclaude/skills/lit-loop/SKILL.md +144 -0
  70. package/plugins/litclaude/skills/lit-plan/SKILL.md +125 -0
  71. package/plugins/litclaude/skills/litgoal/SKILL.md +219 -0
  72. package/plugins/litclaude/skills/lsp/SKILL.md +63 -0
  73. package/plugins/litclaude/skills/programming/SKILL.md +106 -0
  74. package/plugins/litclaude/skills/programming/references/go/README.md +90 -0
  75. package/plugins/litclaude/skills/programming/references/go/backend-stack.md +641 -0
  76. package/plugins/litclaude/skills/programming/references/go/bootstrap.md +328 -0
  77. package/plugins/litclaude/skills/programming/references/go/bubbletea-v2.md +360 -0
  78. package/plugins/litclaude/skills/programming/references/go/cobra-stack.md +468 -0
  79. package/plugins/litclaude/skills/programming/references/go/concurrency.md +362 -0
  80. package/plugins/litclaude/skills/programming/references/go/data-modeling.md +329 -0
  81. package/plugins/litclaude/skills/programming/references/go/error-handling.md +359 -0
  82. package/plugins/litclaude/skills/programming/references/go/golangci-strict.md +236 -0
  83. package/plugins/litclaude/skills/programming/references/go/grpc-connect.md +375 -0
  84. package/plugins/litclaude/skills/programming/references/go/libraries.md +337 -0
  85. package/plugins/litclaude/skills/programming/references/go/one-liners.md +202 -0
  86. package/plugins/litclaude/skills/programming/references/go/sqlc-pgx.md +471 -0
  87. package/plugins/litclaude/skills/programming/references/go/testing.md +467 -0
  88. package/plugins/litclaude/skills/programming/references/go/type-patterns.md +298 -0
  89. package/plugins/litclaude/skills/programming/references/python/README.md +314 -0
  90. package/plugins/litclaude/skills/programming/references/python/async-anyio.md +442 -0
  91. package/plugins/litclaude/skills/programming/references/python/data-modeling.md +233 -0
  92. package/plugins/litclaude/skills/programming/references/python/data-processing.md +133 -0
  93. package/plugins/litclaude/skills/programming/references/python/error-handling.md +218 -0
  94. package/plugins/litclaude/skills/programming/references/python/fastapi-stack.md +316 -0
  95. package/plugins/litclaude/skills/programming/references/python/httpx2-optimization.md +360 -0
  96. package/plugins/litclaude/skills/programming/references/python/libraries.md +307 -0
  97. package/plugins/litclaude/skills/programming/references/python/one-liners.md +268 -0
  98. package/plugins/litclaude/skills/programming/references/python/orjson-stack.md +378 -0
  99. package/plugins/litclaude/skills/programming/references/python/pydantic-ai.md +285 -0
  100. package/plugins/litclaude/skills/programming/references/python/pyproject-strict.md +232 -0
  101. package/plugins/litclaude/skills/programming/references/python/textual-tui.md +201 -0
  102. package/plugins/litclaude/skills/programming/references/python/type-patterns.md +176 -0
  103. package/plugins/litclaude/skills/programming/references/rust/README.md +317 -0
  104. package/plugins/litclaude/skills/programming/references/rust/async-tokio.md +299 -0
  105. package/plugins/litclaude/skills/programming/references/rust/axum-stack.md +467 -0
  106. package/plugins/litclaude/skills/programming/references/rust/cargo-strict.md +317 -0
  107. package/plugins/litclaude/skills/programming/references/rust/clap-stack.md +409 -0
  108. package/plugins/litclaude/skills/programming/references/rust/concurrency.md +375 -0
  109. package/plugins/litclaude/skills/programming/references/rust/libraries.md +439 -0
  110. package/plugins/litclaude/skills/programming/references/rust/one-liners.md +291 -0
  111. package/plugins/litclaude/skills/programming/references/rust/proptest-insta.md +429 -0
  112. package/plugins/litclaude/skills/programming/references/rust/type-state.md +354 -0
  113. package/plugins/litclaude/skills/programming/references/rust/unsafe-discipline.md +250 -0
  114. package/plugins/litclaude/skills/programming/references/rust/zero-cost-safety.md +527 -0
  115. package/plugins/litclaude/skills/programming/references/rust-ub/README.md +289 -0
  116. package/plugins/litclaude/skills/programming/references/rust-ub/miri-sanitizers-loom.md +411 -0
  117. package/plugins/litclaude/skills/programming/references/rust-ub/ub-taxonomy.md +269 -0
  118. package/plugins/litclaude/skills/programming/references/typescript/README.md +195 -0
  119. package/plugins/litclaude/skills/programming/references/typescript/backend-hono.md +672 -0
  120. package/plugins/litclaude/skills/programming/references/typescript/bootstrap.md +199 -0
  121. package/plugins/litclaude/skills/programming/references/typescript/data-modeling.md +202 -0
  122. package/plugins/litclaude/skills/programming/references/typescript/error-handling.md +169 -0
  123. package/plugins/litclaude/skills/programming/references/typescript/tsconfig-strict.md +152 -0
  124. package/plugins/litclaude/skills/programming/references/typescript/type-patterns.md +196 -0
  125. package/plugins/litclaude/skills/programming/scripts/go/check-no-excuse-rules.sh +173 -0
  126. package/plugins/litclaude/skills/programming/scripts/go/new-project.py +138 -0
  127. package/plugins/litclaude/skills/programming/scripts/go/templates/.editorconfig +13 -0
  128. package/plugins/litclaude/skills/programming/scripts/go/templates/.golangci.yml +95 -0
  129. package/plugins/litclaude/skills/programming/scripts/go/templates/AGENTS.md.tmpl +24 -0
  130. package/plugins/litclaude/skills/programming/scripts/go/templates/README.md.tmpl +12 -0
  131. package/plugins/litclaude/skills/programming/scripts/go/templates/Taskfile.yml +40 -0
  132. package/plugins/litclaude/skills/programming/scripts/go/templates/ci.yml +37 -0
  133. package/plugins/litclaude/skills/programming/scripts/go/templates/config.go +24 -0
  134. package/plugins/litclaude/skills/programming/scripts/go/templates/gitignore +15 -0
  135. package/plugins/litclaude/skills/programming/scripts/go/templates/main.go.tmpl +22 -0
  136. package/plugins/litclaude/skills/programming/scripts/go/templates/run.go +15 -0
  137. package/plugins/litclaude/skills/programming/scripts/python/check-no-excuse-rules.py +687 -0
  138. package/plugins/litclaude/skills/programming/scripts/python/new-project.py +172 -0
  139. package/plugins/litclaude/skills/programming/scripts/python/new-script.py +116 -0
  140. package/plugins/litclaude/skills/programming/scripts/rust/check-no-excuse-rules.py +296 -0
  141. package/plugins/litclaude/skills/programming/scripts/rust/check-no-excuse-rules.sh +158 -0
  142. package/plugins/litclaude/skills/programming/scripts/rust/new-project.py +175 -0
  143. package/plugins/litclaude/skills/programming/scripts/typescript/check-no-excuse-rules.ts +282 -0
  144. package/plugins/litclaude/skills/programming/scripts/typescript/new-project.ts +177 -0
  145. package/plugins/litclaude/skills/refactor/SKILL.md +73 -0
  146. package/plugins/litclaude/skills/remove-ai-slops/SKILL.md +52 -0
  147. package/plugins/litclaude/skills/review-work/SKILL.md +331 -0
  148. package/plugins/litclaude/skills/rules/SKILL.md +66 -0
  149. package/plugins/litclaude/skills/start-work/SKILL.md +132 -0
  150. package/scripts/audit-plan-checkboxes.mjs +37 -0
  151. package/scripts/doctor.mjs +41 -0
  152. package/scripts/inspect-agent-tools.mjs +27 -0
  153. package/scripts/postinstall.mjs +50 -0
  154. package/scripts/qa-claude-plugin-smoke.sh +60 -0
  155. package/scripts/qa-portable-install.sh +136 -0
  156. package/scripts/validate-plugin.mjs +72 -0
@@ -0,0 +1,643 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from "node:child_process";
4
+ import {
5
+ cpSync,
6
+ existsSync,
7
+ mkdirSync,
8
+ readFileSync,
9
+ rmSync,
10
+ symlinkSync,
11
+ writeFileSync,
12
+ } from "node:fs";
13
+ import { homedir } from "node:os";
14
+ import { dirname, join, resolve } from "node:path";
15
+ import { createInterface } from "node:readline/promises";
16
+ import { fileURLToPath } from "node:url";
17
+ import { HUD_ACCENT_THEMES, normalizeHudAccent, themeForAccent } from "../plugins/litclaude/lib/hud-accent-palette.mjs";
18
+ import { runStartWorkContinuationCli } from "../plugins/litclaude/lib/start-work-continuation.mjs";
19
+ import { runLitgoalCli } from "../plugins/litclaude/lib/litgoal/cli.mjs";
20
+ import { runWorkflowCheckCli } from "../plugins/litclaude/lib/workflow-check.mjs";
21
+
22
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
23
+ const packageJson = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
24
+ const version = packageJson.version;
25
+
26
+ const usage = `Usage: litclaude-ai [--dry-run] <install|doctor|path|run|update|uninstall|litgoal|workflow-check|start-work-next> [...args]
27
+ litclaude-ai --version
28
+
29
+ Commands:
30
+ install Register the packaged LitClaude plugin in Claude Code.
31
+ doctor Validate the installed LitClaude plugin path.
32
+ path Print the installed Claude plugin path.
33
+ run -- ... Run Claude Code after the global plugin install.
34
+ litgoal Manage litgoal runtime state and evidence.
35
+ workflow-check Verify Dynamic workflow, /goal, and subagent delegation readiness.
36
+ start-work-next Print the next active start-work continuation directive.
37
+ update Reinstall this package version and refresh the Claude plugin registry.
38
+ uninstall Remove LitClaude-managed install state.
39
+ `;
40
+
41
+ const parseArgs = (argv) => {
42
+ const args = [...argv];
43
+ const wantsVersion = args[0] === "--version" || args[0] === "-v";
44
+ if (wantsVersion) return { dryRun: false, command: "version", rest: [] };
45
+ const dryRun = args[0] === "--dry-run";
46
+ if (dryRun) args.shift();
47
+ return { dryRun, command: args[0], rest: args.slice(1) };
48
+ };
49
+
50
+ const litHome = () => resolve(process.env.LITCLAUDE_HOME ?? join(homedir(), ".litclaude"));
51
+ const claudeHome = () => resolve(process.env.CLAUDE_CONFIG_DIR ?? process.env.CLAUDE_HOME ?? join(homedir(), ".claude"));
52
+ const versionRoot = (home = litHome()) => join(home, "litclaude-ai", version);
53
+ const currentRoot = (home = litHome()) => join(home, "current");
54
+ const pluginPathForRoot = (installRoot) => join(installRoot, "plugins", "litclaude");
55
+ const claudePluginRoot = (home = claudeHome()) => join(home, "plugins", "cache", "litclaude-ai", "litclaude", version);
56
+ const installedPluginsPath = (home = claudeHome()) => join(home, "plugins", "installed_plugins.json");
57
+ const knownMarketplacesPath = (home = claudeHome()) => join(home, "plugins", "known_marketplaces.json");
58
+ const claudeSettingsPath = (home = claudeHome()) => join(home, "settings.json");
59
+ const pluginKey = "litclaude@litclaude-ai";
60
+ const marketplaceName = "litclaude-ai";
61
+ const intendedPluginPath = (home = claudeHome()) => claudePluginRoot(home);
62
+ const marketplaceRoot = (home = litHome()) => join(home, "marketplaces", marketplaceName);
63
+ const marketplacePluginPath = (home = litHome()) => join(marketplaceRoot(home), "plugins", "litclaude");
64
+ const sourcePluginPath = () => join(root, "plugins", "litclaude");
65
+ const litClaudeSettingsKey = "litclaude";
66
+ const knownExternalLspPlugins = {
67
+ "typescript-lsp@claude-plugins-official": ["typescript-language-server"],
68
+ "rust-analyzer-lsp@claude-plugins-official": ["rust-analyzer"],
69
+ "pyright-lsp@claude-plugins-official": ["pyright-langserver", "pyright"],
70
+ "gopls-lsp@claude-plugins-official": ["gopls"],
71
+ };
72
+ const spinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
73
+ const hudAccentThemes = HUD_ACCENT_THEMES;
74
+
75
+ const printUsage = () => {
76
+ process.stderr.write(usage);
77
+ };
78
+
79
+ const fail = (message, status = 1) => {
80
+ process.stderr.write(`${message}\n`);
81
+ process.exit(status);
82
+ };
83
+
84
+ const shouldAnimateInstall = () => {
85
+ if (process.env.LITCLAUDE_SPINNER === "1") return true;
86
+ if (process.env.LITCLAUDE_SPINNER === "0") return false;
87
+ return Boolean(process.stdout.isTTY && !process.env.CI);
88
+ };
89
+
90
+ const installStepTheme = (label) => {
91
+ if (/registry|HUD/iu.test(label)) return { code: 215, tag: "registry" };
92
+ if (/plugin/iu.test(label)) return { code: 141, tag: "plugin" };
93
+ if (/marketplace/iu.test(label)) return { code: 111, tag: "market" };
94
+ if (/cache/iu.test(label)) return { code: 179, tag: "cache" };
95
+ return { code: 81, tag: "setup" };
96
+ };
97
+
98
+ const color256 = (code, text) => `\x1b[38;5;${code}m${text}\x1b[0m`;
99
+
100
+ const formatInstallStep = (label) => {
101
+ const theme = installStepTheme(label);
102
+ return `${color256(theme.code, theme.tag)} \x1b[1m${label}\x1b[0m`;
103
+ };
104
+
105
+ const createInstallProgress = () => {
106
+ const animated = shouldAnimateInstall();
107
+ let frameIndex = 0;
108
+ return {
109
+ step(label) {
110
+ if (!animated) {
111
+ process.stdout.write(`INSTALL_STEP: ${label}\n`);
112
+ return;
113
+ }
114
+ const frame = spinnerFrames[frameIndex % spinnerFrames.length];
115
+ frameIndex += 1;
116
+ process.stdout.write(`${color256(81, frame)} ${formatInstallStep(label)}\n`);
117
+ },
118
+ done(label) {
119
+ if (!animated) {
120
+ process.stdout.write(`INSTALL_STEP: ${label}\n`);
121
+ return;
122
+ }
123
+ process.stdout.write(`${color256(119, "✔")} \x1b[1m${label}\x1b[0m\n`);
124
+ },
125
+ };
126
+ };
127
+
128
+ const currentExists = (home = claudeHome()) => existsSync(claudePluginRoot(home));
129
+
130
+ const readInstalledPlugins = (home = claudeHome()) => {
131
+ const registryPath = installedPluginsPath(home);
132
+ if (!existsSync(registryPath)) return { version: 2, plugins: {} };
133
+ const parsed = JSON.parse(readFileSync(registryPath, "utf8"));
134
+ return {
135
+ ...parsed,
136
+ version: parsed.version ?? 2,
137
+ plugins: parsed.plugins ?? {},
138
+ };
139
+ };
140
+
141
+ const writeInstalledPlugins = (registry, home = claudeHome()) => {
142
+ const registryPath = installedPluginsPath(home);
143
+ mkdirSync(dirname(registryPath), { recursive: true });
144
+ writeFileSync(registryPath, `${JSON.stringify(registry, null, 2)}\n`);
145
+ };
146
+
147
+ const readClaudeSettings = (home = claudeHome()) => {
148
+ const settingsPath = claudeSettingsPath(home);
149
+ if (!existsSync(settingsPath)) return {};
150
+ return JSON.parse(readFileSync(settingsPath, "utf8"));
151
+ };
152
+
153
+ const writeClaudeSettings = (settings, home = claudeHome()) => {
154
+ const settingsPath = claudeSettingsPath(home);
155
+ mkdirSync(dirname(settingsPath), { recursive: true });
156
+ writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
157
+ };
158
+
159
+ const hudAccentPreview = (theme) =>
160
+ `\x1b[1m\x1b[38;5;201m[🔥LITCLAUDE v${version}]\x1b[0m\x1b[38;5;${theme.code}m | O4.8 │ ctx [▌░░] 18%/1000k │ 5h [▏░] 4%\x1b[0m`;
161
+
162
+ const shouldPromptHudAccent = () => {
163
+ if (process.env.LITCLAUDE_HUD_ACCENT_PROMPT === "1") return true;
164
+ if (process.env.LITCLAUDE_HUD_ACCENT_PROMPT === "0") return false;
165
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY && !process.env.CI);
166
+ };
167
+
168
+ const chooseHudAccent = async () => {
169
+ const envAccent = process.env.LITCLAUDE_HUD_ACCENT;
170
+ if (envAccent && process.env.LITCLAUDE_HUD_ACCENT_PROMPT !== "1") {
171
+ return normalizeHudAccent(envAccent);
172
+ }
173
+ if (!shouldPromptHudAccent()) return normalizeHudAccent(envAccent);
174
+
175
+ process.stdout.write("Choose LitClaude HUD brand color:\n");
176
+ for (const [index, theme] of hudAccentThemes.entries()) {
177
+ process.stdout.write(` ${index + 1}) ${theme.name.padEnd(8)} ${hudAccentPreview(theme)} ${theme.mood}\n`);
178
+ }
179
+
180
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
181
+ try {
182
+ const answer = await rl.question("Color 1-10 or name [cyan]: ");
183
+ const trimmed = answer.trim().toLowerCase();
184
+ const byIndex = hudAccentThemes[Number.parseInt(trimmed, 10) - 1]?.name;
185
+ const selected = byIndex ?? normalizeHudAccent(trimmed);
186
+ if (trimmed && selected === "cyan" && trimmed !== "cyan" && byIndex === undefined) {
187
+ process.stdout.write("HUD_COLOR_WARNING: unknown color; using cyan\n");
188
+ }
189
+ process.stdout.write(`HUD_COLOR_SELECTED: ${selected} ${hudAccentPreview(themeForAccent(selected))}\n`);
190
+ return selected;
191
+ } finally {
192
+ rl.close();
193
+ }
194
+ };
195
+
196
+ const hudCommandForPlugin = (pluginPath, accent = "cyan") =>
197
+ `LITCLAUDE_HUD_ACCENT=${normalizeHudAccent(accent)} node "${join(pluginPath, "bin", "litclaude-hud.js")}"`;
198
+
199
+ const readKnownMarketplaces = (home = claudeHome()) => {
200
+ const path = knownMarketplacesPath(home);
201
+ if (!existsSync(path)) return {};
202
+ return JSON.parse(readFileSync(path, "utf8"));
203
+ };
204
+
205
+ const writeKnownMarketplaces = (knownMarketplaces, home = claudeHome()) => {
206
+ const path = knownMarketplacesPath(home);
207
+ mkdirSync(dirname(path), { recursive: true });
208
+ writeFileSync(path, `${JSON.stringify(knownMarketplaces, null, 2)}\n`);
209
+ };
210
+
211
+ const marketplaceSource = (marketplacePath) => ({
212
+ source: "directory",
213
+ path: marketplacePath,
214
+ });
215
+
216
+ const writeLocalMarketplace = (home = litHome()) => {
217
+ const rootPath = marketplaceRoot(home);
218
+ const targetPlugin = marketplacePluginPath(home);
219
+ rmSync(rootPath, { recursive: true, force: true });
220
+ mkdirSync(dirname(targetPlugin), { recursive: true });
221
+ cpSync(sourcePluginPath(), targetPlugin, { recursive: true });
222
+ const marketplaceManifestPath = join(rootPath, ".claude-plugin", "marketplace.json");
223
+ mkdirSync(dirname(marketplaceManifestPath), { recursive: true });
224
+ writeFileSync(
225
+ marketplaceManifestPath,
226
+ `${JSON.stringify(
227
+ {
228
+ $schema: "https://anthropic.com/claude-code/marketplace.schema.json",
229
+ name: marketplaceName,
230
+ description: "Local LitClaude npm-installed marketplace.",
231
+ owner: {
232
+ name: "LitClaude contributors",
233
+ },
234
+ plugins: [
235
+ {
236
+ name: "litclaude",
237
+ displayName: "LitClaude",
238
+ version,
239
+ description: packageJson.description,
240
+ author: {
241
+ name: "LitClaude contributors",
242
+ },
243
+ source: "./plugins/litclaude",
244
+ category: "development",
245
+ homepage: "https://github.com/wjgoarxiv/litclaude",
246
+ },
247
+ ],
248
+ },
249
+ null,
250
+ 2,
251
+ )}\n`,
252
+ );
253
+ return rootPath;
254
+ };
255
+
256
+ const registerMarketplace = (marketplacePath, home = claudeHome()) => {
257
+ const settings = readClaudeSettings(home);
258
+ settings.enabledPlugins = settings.enabledPlugins ?? {};
259
+ settings.enabledPlugins[pluginKey] = true;
260
+ settings.extraKnownMarketplaces = settings.extraKnownMarketplaces ?? {};
261
+ settings.extraKnownMarketplaces[marketplaceName] = {
262
+ source: marketplaceSource(marketplacePath),
263
+ };
264
+ writeClaudeSettings(settings, home);
265
+
266
+ const knownMarketplaces = readKnownMarketplaces(home);
267
+ knownMarketplaces[marketplaceName] = {
268
+ source: marketplaceSource(marketplacePath),
269
+ installLocation: marketplacePath,
270
+ lastUpdated: new Date().toISOString(),
271
+ };
272
+ writeKnownMarketplaces(knownMarketplaces, home);
273
+ };
274
+
275
+ const installHudStatusLine = (pluginPath, home = claudeHome(), accent = "cyan") => {
276
+ const settings = readClaudeSettings(home);
277
+ const hudAccent = normalizeHudAccent(accent);
278
+ const command = hudCommandForPlugin(pluginPath, hudAccent);
279
+ const existingLitClaude = settings[litClaudeSettingsKey] ?? {};
280
+ const now = new Date().toISOString();
281
+ const previousStatusLine =
282
+ existingLitClaude.statusLineManaged === true
283
+ ? existingLitClaude.previousStatusLine
284
+ : settings.statusLine;
285
+
286
+ settings.statusLine = {
287
+ type: "command",
288
+ command,
289
+ };
290
+ settings[litClaudeSettingsKey] = {
291
+ ...existingLitClaude,
292
+ statusLineManaged: true,
293
+ statusLineCommand: command,
294
+ statusLineVersion: version,
295
+ hudAccent,
296
+ statusLineInstalledAt: existingLitClaude.statusLineInstalledAt ?? now,
297
+ statusLineUpdatedAt: now,
298
+ previousStatusLine: previousStatusLine ?? null,
299
+ };
300
+ writeClaudeSettings(settings, home);
301
+ };
302
+
303
+ const uninstallHudStatusLine = (home = claudeHome()) => {
304
+ const settingsPath = claudeSettingsPath(home);
305
+ if (!existsSync(settingsPath)) return;
306
+
307
+ const settings = readClaudeSettings(home);
308
+ const litClaude = settings[litClaudeSettingsKey];
309
+ if (litClaude?.statusLineManaged !== true) return;
310
+
311
+ if (settings.statusLine?.command === litClaude.statusLineCommand) {
312
+ if (litClaude.previousStatusLine) {
313
+ settings.statusLine = litClaude.previousStatusLine;
314
+ } else {
315
+ delete settings.statusLine;
316
+ }
317
+ } else if (settings.statusLine?.command) {
318
+ process.stdout.write("HUD_WARNING: statusLine changed by user; leaving it untouched\n");
319
+ }
320
+
321
+ const remainingLitClaude = { ...litClaude };
322
+ for (const key of [
323
+ "statusLineManaged",
324
+ "statusLineCommand",
325
+ "statusLineVersion",
326
+ "hudAccent",
327
+ "statusLineInstalledAt",
328
+ "statusLineUpdatedAt",
329
+ "previousStatusLine",
330
+ ]) {
331
+ delete remainingLitClaude[key];
332
+ }
333
+
334
+ if (Object.keys(remainingLitClaude).length > 0) {
335
+ settings[litClaudeSettingsKey] = remainingLitClaude;
336
+ } else {
337
+ delete settings[litClaudeSettingsKey];
338
+ }
339
+ writeClaudeSettings(settings, home);
340
+ };
341
+
342
+ const unregisterMarketplace = (home = claudeHome()) => {
343
+ const settingsPath = claudeSettingsPath(home);
344
+ if (existsSync(settingsPath)) {
345
+ const settings = readClaudeSettings(home);
346
+ if (settings.enabledPlugins) delete settings.enabledPlugins[pluginKey];
347
+ if (settings.extraKnownMarketplaces) delete settings.extraKnownMarketplaces[marketplaceName];
348
+ writeClaudeSettings(settings, home);
349
+ }
350
+
351
+ const knownPath = knownMarketplacesPath(home);
352
+ if (existsSync(knownPath)) {
353
+ const knownMarketplaces = readKnownMarketplaces(home);
354
+ delete knownMarketplaces[marketplaceName];
355
+ writeKnownMarketplaces(knownMarketplaces, home);
356
+ }
357
+ };
358
+
359
+ const registerClaudePlugin = (installRoot, marketplacePath, home = claudeHome(), hudAccent = "cyan") => {
360
+ const registry = readInstalledPlugins(home);
361
+ const now = new Date().toISOString();
362
+ const existing = registry.plugins[pluginKey]?.[0] ?? {};
363
+ registry.plugins[pluginKey] = [
364
+ {
365
+ scope: "user",
366
+ installPath: installRoot,
367
+ version,
368
+ installedAt: existing.installedAt ?? now,
369
+ lastUpdated: now,
370
+ installedBy: "litclaude-ai",
371
+ enabled: true,
372
+ },
373
+ ];
374
+ writeInstalledPlugins(registry, home);
375
+ registerMarketplace(marketplacePath, home);
376
+ installHudStatusLine(installRoot, home, hudAccent);
377
+ };
378
+
379
+ const unregisterClaudePlugin = (home = claudeHome()) => {
380
+ const registryPath = installedPluginsPath(home);
381
+ if (!existsSync(registryPath)) return;
382
+ const registry = readInstalledPlugins(home);
383
+ delete registry.plugins[pluginKey];
384
+ writeInstalledPlugins(registry, home);
385
+ };
386
+
387
+ const requireInstalledPluginPath = (home = claudeHome()) => {
388
+ if (!currentExists(home)) {
389
+ fail("LitClaude is not installed. Run `litclaude install` first.");
390
+ }
391
+ const path = intendedPluginPath(home);
392
+ if (!existsSync(join(path, ".claude-plugin", "plugin.json"))) {
393
+ fail("LitClaude install is incomplete. Run `litclaude install` again.");
394
+ }
395
+ return path;
396
+ };
397
+
398
+ const commandExists = (command) => {
399
+ const result = spawnSync(command, ["--version"], {
400
+ encoding: "utf8",
401
+ stdio: "ignore",
402
+ });
403
+ return !result.error;
404
+ };
405
+
406
+ const printLspDiagnostics = (pluginPath, registry) => {
407
+ const lspPath = join(pluginPath, ".lsp.json");
408
+ const config = JSON.parse(readFileSync(lspPath, "utf8"));
409
+ const litClaudeServers = Object.keys(config);
410
+ process.stdout.write(`LITCLAUDE_LSP_SERVERS: ${litClaudeServers.join(", ") || "none"}\n`);
411
+
412
+ for (const server of litClaudeServers) {
413
+ const command = config[server]?.command?.[0];
414
+ if (command && !commandExists(command)) {
415
+ process.stdout.write(`LITCLAUDE_LSP_WARNING: ${server} requires ${command}; install it or remove that server from .lsp.json\n`);
416
+ }
417
+ }
418
+
419
+ const externalWarnings = [];
420
+ for (const [key, commands] of Object.entries(knownExternalLspPlugins)) {
421
+ if (!registry.plugins[key]?.length) continue;
422
+ if (commands.some(commandExists)) continue;
423
+ externalWarnings.push({ key, command: commands[0] });
424
+ process.stdout.write(`EXTERNAL_LSP_WARNING: ${key} requires ${commands[0]}\n`);
425
+ }
426
+
427
+ if (externalWarnings.length > 0) {
428
+ process.stdout.write("These warnings are not emitted by LitClaude's .lsp.json; they come from other installed Claude Code LSP plugins.\n");
429
+ }
430
+ };
431
+
432
+ const resetCurrentPointer = (home, target) => {
433
+ const current = currentRoot(home);
434
+ rmSync(current, { recursive: true, force: true });
435
+ try {
436
+ symlinkSync(target, current, "dir");
437
+ } catch {
438
+ mkdirSync(current, { recursive: true });
439
+ cpSync(target, current, { recursive: true });
440
+ }
441
+ };
442
+
443
+ const install = async ({ dryRun }) => {
444
+ const home = claudeHome();
445
+ const litClaudeHome = litHome();
446
+ const targetPlugin = claudePluginRoot(home);
447
+ const sourcePlugin = sourcePluginPath();
448
+ const targetMarketplace = marketplaceRoot(litClaudeHome);
449
+ const progress = createInstallProgress();
450
+
451
+ if (dryRun) {
452
+ process.stdout.write(`DRY_RUN: install LitClaude ${version}\n`);
453
+ process.stdout.write(`Would copy: ${sourcePlugin} -> ${targetPlugin}\n`);
454
+ process.stdout.write(`Would create local marketplace: ${targetMarketplace}\n`);
455
+ process.stdout.write(`Would register Claude plugin: ${pluginKey}\n`);
456
+ process.stdout.write(`Would set Claude statusLine HUD: ${hudCommandForPlugin(targetPlugin)}\n`);
457
+ process.stdout.write("Launch with: claude\n");
458
+ return;
459
+ }
460
+
461
+ if (!existsSync(sourcePlugin)) {
462
+ fail(`Packaged LitClaude plugin payload is missing: ${sourcePlugin}`);
463
+ }
464
+
465
+ const hudAccent = await chooseHudAccent();
466
+ progress.step(`Installing LitClaude ${version}`);
467
+ progress.step("Plugin payload: copying Claude Code plugin");
468
+ rmSync(targetPlugin, { recursive: true, force: true });
469
+ mkdirSync(dirname(targetPlugin), { recursive: true });
470
+ cpSync(sourcePlugin, targetPlugin, { recursive: true });
471
+ progress.step("Marketplace: writing local Claude marketplace");
472
+ const localMarketplace = writeLocalMarketplace(litClaudeHome);
473
+ progress.step("Claude registry: enabling plugin and HUD");
474
+ registerClaudePlugin(targetPlugin, localMarketplace, home, hudAccent);
475
+
476
+ progress.step("Compatibility cache: refreshing local current pointer");
477
+ const litRoot = versionRoot(litClaudeHome);
478
+ rmSync(litRoot, { recursive: true, force: true });
479
+ mkdirSync(dirname(pluginPathForRoot(litRoot)), { recursive: true });
480
+ cpSync(sourcePlugin, pluginPathForRoot(litRoot), { recursive: true });
481
+ resetCurrentPointer(litClaudeHome, litRoot);
482
+
483
+ progress.done("LitClaude install complete");
484
+ process.stdout.write(`INSTALL_PASS: LitClaude ${version} installed\n`);
485
+ process.stdout.write(`Claude plugin: ${pluginKey}\n`);
486
+ process.stdout.write(`Marketplace: ${localMarketplace}\n`);
487
+ process.stdout.write(`Plugin path: ${intendedPluginPath(home)}\n`);
488
+ process.stdout.write(`HUD: LitClaude statusLine installed (${hudAccent})\n`);
489
+ process.stdout.write("Launch with: claude\n");
490
+ };
491
+
492
+ const doctor = ({ dryRun }) => {
493
+ if (dryRun) {
494
+ const pluginPath = intendedPluginPath();
495
+ process.stdout.write("DRY_RUN: doctor LitClaude install\n");
496
+ process.stdout.write(`Would check plugin files under: ${pluginPath}\n`);
497
+ process.stdout.write(`Would run: claude plugin validate ${pluginPath}\n`);
498
+ process.stdout.write(`Would check Claude plugin registry: ${pluginKey}\n`);
499
+ process.stdout.write(`Would check Claude statusLine HUD: ${hudCommandForPlugin(pluginPath)}\n`);
500
+ return;
501
+ }
502
+
503
+ const pluginPath = requireInstalledPluginPath();
504
+ const registry = readInstalledPlugins();
505
+ const registryEntry = registry.plugins[pluginKey]?.[0];
506
+ if (!registryEntry) fail(`LitClaude is missing from Claude plugin registry: ${pluginKey}`);
507
+ const requiredFiles = [
508
+ ".claude-plugin/plugin.json",
509
+ ".mcp.json",
510
+ ".lsp.json",
511
+ "hooks/hooks.json",
512
+ "bin/litclaude-hook.js",
513
+ "bin/litclaude-mcp.js",
514
+ "skills/lit-loop/SKILL.md",
515
+ "skills/lit-plan/SKILL.md",
516
+ ];
517
+
518
+ for (const file of requiredFiles) {
519
+ const path = join(pluginPath, file);
520
+ if (!existsSync(path)) fail(`LitClaude install is missing ${file}. Run \`litclaude install\` again.`);
521
+ }
522
+
523
+ printLspDiagnostics(pluginPath, registry);
524
+
525
+ const claude = spawnSync("claude", ["--version"], { encoding: "utf8" });
526
+ if (claude.error) {
527
+ process.stdout.write("CLAUDE_WARNING: claude executable not found in PATH\n");
528
+ } else {
529
+ process.stdout.write(`CLAUDE_VERSION: ${(claude.stdout || claude.stderr).trim()}\n`);
530
+ const validation = spawnSync("claude", ["plugin", "validate", pluginPath], { encoding: "utf8" });
531
+ if (validation.status !== 0) {
532
+ if (validation.stdout) process.stderr.write(validation.stdout);
533
+ if (validation.stderr) process.stderr.write(validation.stderr);
534
+ fail("Claude plugin validation failed.");
535
+ }
536
+ process.stdout.write("CLAUDE_PLUGIN_VALIDATE_PASS\n");
537
+ const details = spawnSync("claude", ["plugin", "details", pluginKey], { encoding: "utf8" });
538
+ if (details.status !== 0) {
539
+ if (details.stdout) process.stderr.write(details.stdout);
540
+ if (details.stderr) process.stderr.write(details.stderr);
541
+ fail("Claude plugin details failed.");
542
+ }
543
+ process.stdout.write("CLAUDE_PLUGIN_DETAILS_PASS\n");
544
+ }
545
+
546
+ process.stdout.write(`Plugin path: ${pluginPath}\n`);
547
+ const settings = readClaudeSettings();
548
+ if (settings.statusLine?.command === hudCommandForPlugin(pluginPath, settings[litClaudeSettingsKey]?.hudAccent)) {
549
+ process.stdout.write("HUD_STATUSLINE_PASS\n");
550
+ } else {
551
+ process.stdout.write("HUD_STATUSLINE_WARNING: LitClaude HUD is not the active Claude statusLine\n");
552
+ }
553
+ process.stdout.write("Launch with: claude\n");
554
+ process.stdout.write("DOCTOR_PASS\n");
555
+ };
556
+
557
+ const printPath = () => {
558
+ process.stdout.write(`${requireInstalledPluginPath()}\n`);
559
+ };
560
+
561
+ const printVersion = () => {
562
+ process.stdout.write(`${version}\n`);
563
+ };
564
+
565
+ const runClaude = ({ dryRun, rest }) => {
566
+ const separatorIndex = rest.indexOf("--");
567
+ const claudeArgs = separatorIndex === -1 ? rest : rest.slice(separatorIndex + 1);
568
+ if (!dryRun) requireInstalledPluginPath();
569
+ const command = ["claude", ...claudeArgs];
570
+
571
+ if (dryRun) {
572
+ process.stdout.write(command.join(" "));
573
+ process.stdout.write("\n");
574
+ return;
575
+ }
576
+
577
+ const result = spawnSync(command[0], command.slice(1), { stdio: "inherit" });
578
+ if (result.error) fail(`failed to start claude: ${result.error.message}`);
579
+ process.exit(result.status ?? 1);
580
+ };
581
+
582
+ const uninstall = ({ dryRun }) => {
583
+ const home = litHome();
584
+ const pluginCacheRoot = join(claudeHome(), "plugins", "cache", "litclaude-ai");
585
+ if (dryRun) {
586
+ process.stdout.write(`DRY_RUN: remove ${join(home, "litclaude-ai")}, ${currentRoot(home)}, ${marketplaceRoot(home)}, ${pluginCacheRoot}, ${pluginKey}, and its marketplace/settings entries\n`);
587
+ return;
588
+ }
589
+ rmSync(currentRoot(home), { recursive: true, force: true });
590
+ rmSync(join(home, "litclaude-ai"), { recursive: true, force: true });
591
+ rmSync(marketplaceRoot(home), { recursive: true, force: true });
592
+ unregisterClaudePlugin();
593
+ unregisterMarketplace();
594
+ uninstallHudStatusLine();
595
+ rmSync(pluginCacheRoot, { recursive: true, force: true });
596
+ process.stdout.write("UNINSTALL_PASS\n");
597
+ };
598
+
599
+ const main = async () => {
600
+ const parsed = parseArgs(process.argv.slice(2));
601
+ const { command } = parsed;
602
+
603
+ if (!command) {
604
+ printUsage();
605
+ process.exit(64);
606
+ }
607
+
608
+ switch (command) {
609
+ case "install":
610
+ case "update":
611
+ await install(parsed);
612
+ break;
613
+ case "doctor":
614
+ doctor(parsed);
615
+ break;
616
+ case "path":
617
+ printPath(parsed);
618
+ break;
619
+ case "version":
620
+ printVersion();
621
+ break;
622
+ case "run":
623
+ runClaude(parsed);
624
+ break;
625
+ case "litgoal":
626
+ process.exit(runLitgoalCli(parsed.rest));
627
+ break;
628
+ case "workflow-check":
629
+ process.exit(runWorkflowCheckCli(root, version, parsed.rest));
630
+ break;
631
+ case "start-work-next":
632
+ process.exit(runStartWorkContinuationCli(root, parsed.rest));
633
+ break;
634
+ case "uninstall":
635
+ uninstall(parsed);
636
+ break;
637
+ default:
638
+ printUsage();
639
+ fail(`Unknown command: ${command}`, 64);
640
+ }
641
+ };
642
+
643
+ main().catch((error) => fail(error.message));
package/cover.png ADDED
Binary file