continuous-improvement 3.1.0 → 3.9.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 (136) hide show
  1. package/.claude-plugin/marketplace.json +66 -0
  2. package/CHANGELOG.md +216 -0
  3. package/QUICKSTART.md +79 -12
  4. package/README.md +229 -349
  5. package/SKILL.md +87 -9
  6. package/action.yml +1 -1
  7. package/bin/analyze.sh +9 -1
  8. package/bin/backfill.mjs +172 -0
  9. package/bin/check-doc-runtime-claims.mjs +120 -0
  10. package/bin/check-docs-substrings.mjs +333 -0
  11. package/bin/check-everything-mirror.mjs +145 -0
  12. package/bin/check-routing-targets.mjs +151 -0
  13. package/bin/check-skill-law-tag.mjs +128 -0
  14. package/bin/check-skill-mirror.mjs +119 -0
  15. package/bin/check-skill-tiers.mjs +116 -0
  16. package/bin/check-third-party-shape.mjs +202 -0
  17. package/bin/generate-plugin-manifests.mjs +171 -0
  18. package/bin/harvest-friction.mjs +279 -0
  19. package/bin/hook-stats.mjs +258 -0
  20. package/bin/install.mjs +407 -499
  21. package/bin/lint-transcript.mjs +182 -210
  22. package/bin/mcp-server.mjs +834 -608
  23. package/bin/observe.mjs +148 -0
  24. package/bin/pre-commit-block-strays.sh +49 -0
  25. package/bin/refresh-third-party.mjs +416 -0
  26. package/bin/unified-cli.mjs +533 -0
  27. package/commands/continuous-improvement.md +43 -2
  28. package/commands/discipline.md +14 -0
  29. package/commands/harvest.md +76 -0
  30. package/commands/learn-eval.md +117 -0
  31. package/commands/planning-with-files.md +66 -0
  32. package/commands/proceed-with-the-recommendation.md +62 -0
  33. package/commands/ralph.md +103 -0
  34. package/commands/release-train.md +81 -0
  35. package/commands/seven-laws.md +16 -0
  36. package/commands/superpowers.md +180 -0
  37. package/commands/swarm.md +101 -0
  38. package/commands/workspace-surface-audit.md +77 -0
  39. package/hooks/gateguard.mjs +172 -0
  40. package/hooks/observe.sh +42 -4
  41. package/hooks/session.sh +3 -3
  42. package/hooks/three-section-close.mjs +181 -0
  43. package/instinct-packs/meta.json +16 -0
  44. package/lib/cli-anything.mjs +401 -0
  45. package/lib/compound-engineering.mjs +831 -0
  46. package/lib/gateguard-state.mjs +85 -0
  47. package/lib/observe-event.mjs +128 -0
  48. package/lib/plugin-metadata.mjs +435 -0
  49. package/lib/pm-marketplace.mjs +61 -0
  50. package/lib/pm-skills.mjs +1274 -0
  51. package/lib/resolve-home-dir.mjs +43 -0
  52. package/lib/skill-tiers.mjs +137 -0
  53. package/lib/unified-plugin.mjs +924 -0
  54. package/llms.txt +32 -7
  55. package/package.json +29 -19
  56. package/plugins/beginner.json +17 -6
  57. package/plugins/continuous-improvement/.claude-plugin/marketplace.json +20 -0
  58. package/plugins/continuous-improvement/.claude-plugin/plugin.json +26 -0
  59. package/plugins/continuous-improvement/LICENSE +21 -0
  60. package/plugins/continuous-improvement/README.md +57 -0
  61. package/plugins/continuous-improvement/agents/README.md +120 -0
  62. package/plugins/continuous-improvement/agents/code-reviewer.md +97 -0
  63. package/plugins/continuous-improvement/agents/security-auditor.md +101 -0
  64. package/plugins/continuous-improvement/agents/test-engineer.md +95 -0
  65. package/plugins/continuous-improvement/bin/backfill.mjs +172 -0
  66. package/plugins/continuous-improvement/bin/mcp-server.mjs +889 -0
  67. package/plugins/continuous-improvement/bin/observe.mjs +148 -0
  68. package/plugins/continuous-improvement/commands/continuous-improvement.md +115 -0
  69. package/plugins/continuous-improvement/commands/dashboard.md +56 -0
  70. package/plugins/continuous-improvement/commands/discipline.md +51 -0
  71. package/plugins/continuous-improvement/commands/harvest.md +76 -0
  72. package/plugins/continuous-improvement/commands/learn-eval.md +117 -0
  73. package/plugins/continuous-improvement/commands/planning-with-files.md +66 -0
  74. package/plugins/continuous-improvement/commands/proceed-with-the-recommendation.md +62 -0
  75. package/plugins/continuous-improvement/commands/ralph.md +103 -0
  76. package/plugins/continuous-improvement/commands/release-train.md +81 -0
  77. package/plugins/continuous-improvement/commands/seven-laws.md +16 -0
  78. package/plugins/continuous-improvement/commands/superpowers.md +180 -0
  79. package/plugins/continuous-improvement/commands/swarm.md +101 -0
  80. package/plugins/continuous-improvement/commands/workspace-surface-audit.md +77 -0
  81. package/plugins/continuous-improvement/hooks/gateguard.mjs +172 -0
  82. package/plugins/continuous-improvement/hooks/hooks.json +65 -0
  83. package/plugins/continuous-improvement/hooks/observe.sh +172 -0
  84. package/plugins/continuous-improvement/hooks/session.sh +106 -0
  85. package/plugins/continuous-improvement/hooks/three-section-close.mjs +181 -0
  86. package/plugins/continuous-improvement/instinct-packs/go.json +58 -0
  87. package/plugins/continuous-improvement/instinct-packs/meta.json +16 -0
  88. package/plugins/continuous-improvement/instinct-packs/python.json +58 -0
  89. package/plugins/continuous-improvement/instinct-packs/react.json +58 -0
  90. package/plugins/continuous-improvement/lib/observe-event.mjs +128 -0
  91. package/plugins/continuous-improvement/lib/plugin-metadata.mjs +435 -0
  92. package/plugins/continuous-improvement/lib/resolve-home-dir.mjs +43 -0
  93. package/plugins/continuous-improvement/skills/README.md +37 -0
  94. package/plugins/continuous-improvement/skills/continuous-improvement/SKILL.md +249 -0
  95. package/plugins/continuous-improvement/skills/deploy-receipt/SKILL.md +178 -0
  96. package/plugins/continuous-improvement/skills/gateguard/SKILL.md +163 -0
  97. package/plugins/continuous-improvement/skills/para-memory-files/SKILL.md +108 -0
  98. package/plugins/continuous-improvement/skills/proceed-with-the-recommendation/SKILL.md +447 -0
  99. package/plugins/continuous-improvement/skills/ralph/SKILL.md +221 -0
  100. package/plugins/continuous-improvement/skills/recovery-classification/SKILL.md +73 -0
  101. package/plugins/continuous-improvement/skills/safety-guard/SKILL.md +76 -0
  102. package/plugins/continuous-improvement/skills/state-reconciliation/SKILL.md +63 -0
  103. package/plugins/continuous-improvement/skills/strategic-compact/SKILL.md +104 -0
  104. package/plugins/continuous-improvement/skills/superpowers/SKILL.md +219 -0
  105. package/plugins/continuous-improvement/skills/tdd-workflow/SKILL.md +411 -0
  106. package/plugins/continuous-improvement/skills/token-budget-advisor/SKILL.md +136 -0
  107. package/plugins/continuous-improvement/skills/verification-loop/SKILL.md +224 -0
  108. package/plugins/continuous-improvement/skills/wild-risa-balance/SKILL.md +191 -0
  109. package/plugins/continuous-improvement/skills/workspace-surface-audit/SKILL.md +147 -0
  110. package/plugins/continuous-improvement/skills/worktree-safety/SKILL.md +66 -0
  111. package/plugins/continuous-improvement/templates/planning-with-files/findings.md +8 -0
  112. package/plugins/continuous-improvement/templates/planning-with-files/progress.md +7 -0
  113. package/plugins/continuous-improvement/templates/planning-with-files/task_plan.md +23 -0
  114. package/plugins/expert.json +26 -5
  115. package/skills/README.md +79 -0
  116. package/skills/deploy-receipt.md +178 -0
  117. package/skills/gateguard.md +163 -0
  118. package/skills/para-memory-files.md +108 -0
  119. package/skills/proceed-with-the-recommendation.md +447 -0
  120. package/skills/ralph.md +221 -0
  121. package/skills/recovery-classification.md +73 -0
  122. package/skills/safety-guard.md +76 -0
  123. package/skills/state-reconciliation.md +63 -0
  124. package/skills/strategic-compact.md +104 -0
  125. package/skills/superpowers.md +219 -0
  126. package/skills/tdd-workflow.md +411 -0
  127. package/skills/token-budget-advisor.md +136 -0
  128. package/skills/verification-loop.md +224 -0
  129. package/skills/wild-risa-balance.md +191 -0
  130. package/skills/workspace-surface-audit.md +147 -0
  131. package/skills/worktree-safety.md +66 -0
  132. package/templates/insights-claude-md.md +91 -0
  133. package/templates/planning-with-files/findings.md +8 -0
  134. package/templates/planning-with-files/progress.md +7 -0
  135. package/templates/planning-with-files/task_plan.md +23 -0
  136. package/templates/verify-ladder.example.json +60 -0
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env node
2
+ // three-section-close.mjs — Stop hook that requires every substantive
3
+ // assistant reply to close with three named sections:
4
+ // "What has been done", "What is next", "Recommendation".
5
+ //
6
+ // Implements Law 4 + Law 5 in-band: verify before reporting, reflect at
7
+ // the end. See SKILL.md for the discipline rationale.
8
+ //
9
+ // Behavior:
10
+ // - exit 0 to allow the response to stand
11
+ // - emit {"decision":"block","reason":"..."} on stdout to re-prompt
12
+ // - never block on internal failure (parse error, missing transcript,
13
+ // unexpected message shape) — fail open
14
+ //
15
+ // Heuristic: skip messages shorter than 600 chars. Pure clarifying replies
16
+ // don't need the close; only substantive technical responses (work
17
+ // summaries, plan responses, post-execution reports) get gated.
18
+ //
19
+ // Telemetry: best-effort JSONL line per gated invocation written to
20
+ // ~/.claude/hook-telemetry/<project-hash>.jsonl. Never throws, never
21
+ // blocks the response. No network. No transcript content recorded.
22
+
23
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
24
+ import { createHash } from "node:crypto";
25
+ import { platform } from "node:os";
26
+ import { dirname, join } from "node:path";
27
+ import { performance } from "node:perf_hooks";
28
+
29
+ import { resolveHomeDir } from "../lib/resolve-home-dir.mjs";
30
+
31
+ const MIN_LENGTH_TO_GATE = 600;
32
+
33
+ const REQUIRED_SECTIONS = [
34
+ {
35
+ heading: "What has been done",
36
+ hint: "concrete summary of changes/findings from this turn",
37
+ pattern: /^#+ +What has been done(?:\s|$)/m,
38
+ },
39
+ {
40
+ heading: "What is next",
41
+ hint: "minimum 5 specific, actionable bullets",
42
+ pattern: /^#+ +What is next(?:\s|$)/m,
43
+ },
44
+ {
45
+ heading: "Recommendation",
46
+ hint: "ranked descending by impact, or write \"no\" if there is no recommendation",
47
+ pattern: /^#+ +Recommendation(?:\s|$)/m,
48
+ },
49
+ ];
50
+
51
+ function readStdinSync() {
52
+ try {
53
+ return readFileSync(0, "utf8");
54
+ } catch {
55
+ return "";
56
+ }
57
+ }
58
+
59
+ function safeJsonParse(text) {
60
+ try {
61
+ return JSON.parse(text);
62
+ } catch {
63
+ return null;
64
+ }
65
+ }
66
+
67
+ function extractLastAssistantText(transcriptPath) {
68
+ const raw = readFileSync(transcriptPath, "utf8");
69
+ const lines = raw.split(/\r?\n/);
70
+ for (let i = lines.length - 1; i >= 0; i--) {
71
+ const line = lines[i].trim();
72
+ if (!line) continue;
73
+ const obj = safeJsonParse(line);
74
+ if (!obj || obj.type !== "assistant") continue;
75
+ const blocks = obj.message?.content;
76
+ if (!Array.isArray(blocks)) continue;
77
+ const texts = blocks
78
+ .filter((b) => b && b.type === "text" && typeof b.text === "string")
79
+ .map((b) => b.text);
80
+ if (texts.length === 0) continue;
81
+ return texts.join("\n");
82
+ }
83
+ return null;
84
+ }
85
+
86
+ function emitBlock(missing) {
87
+ const bullets = missing
88
+ .map(
89
+ (s) =>
90
+ `- "## ${s.heading}" — ${s.hint}`,
91
+ )
92
+ .join("\n");
93
+ const reason =
94
+ `Your reply is missing the required 3-section close. Add these sections at the end of the reply with these exact headings:\n${bullets}`;
95
+ process.stdout.write(JSON.stringify({ decision: "block", reason }) + "\n");
96
+ }
97
+
98
+ function projectHashFor(transcriptPath) {
99
+ const dir = dirname(transcriptPath);
100
+ let normalized = dir.split("\\").join("/");
101
+ if (platform() === "win32") normalized = normalized.toLowerCase();
102
+ return createHash("sha256").update(normalized).digest("hex").slice(0, 12);
103
+ }
104
+
105
+ function recordTelemetry(entry, transcriptPath) {
106
+ try {
107
+ const home = resolveHomeDir();
108
+ if (!home) return;
109
+ const telemetryDir = join(home, ".claude", "hook-telemetry");
110
+ try {
111
+ mkdirSync(telemetryDir, { recursive: true });
112
+ } catch {
113
+ return;
114
+ }
115
+ const file = join(telemetryDir, `${projectHashFor(transcriptPath)}.jsonl`);
116
+ try {
117
+ appendFileSync(file, JSON.stringify(entry) + "\n", "utf8");
118
+ } catch {
119
+ return;
120
+ }
121
+ } catch {
122
+ // belt-and-suspenders: telemetry must never throw
123
+ }
124
+ }
125
+
126
+ function main() {
127
+ // Per-operator opt-out. When CLAUDE_THREE_SECTION_CLOSE_DISABLED=1, the hook
128
+ // short-circuits entirely: no enforcement, no telemetry. Public default is
129
+ // unchanged (rule still fires for everyone else). See feedback memory
130
+ // 2026-05-04 for rationale: an audit found 22,876 observations produced
131
+ // only 2 graduated instincts, so visible end-of-turn reflection earns its
132
+ // keep poorly enough that operators may want to run it as internal thinking.
133
+ if (process.env.CLAUDE_THREE_SECTION_CLOSE_DISABLED === "1") return;
134
+ const startedAt = performance.now();
135
+ const stdin = readStdinSync();
136
+ if (!stdin) return;
137
+
138
+ const payload = safeJsonParse(stdin);
139
+ if (!payload || typeof payload.transcript_path !== "string") return;
140
+ if (!existsSync(payload.transcript_path)) return;
141
+
142
+ let lastText;
143
+ try {
144
+ lastText = extractLastAssistantText(payload.transcript_path);
145
+ } catch {
146
+ return; // fail open on read error
147
+ }
148
+ if (!lastText) return;
149
+
150
+ const textLength = lastText.length;
151
+ let action;
152
+ let missing = [];
153
+
154
+ if (textLength < MIN_LENGTH_TO_GATE) {
155
+ action = "skip-short";
156
+ } else {
157
+ missing = REQUIRED_SECTIONS.filter((s) => !s.pattern.test(lastText));
158
+ if (missing.length === 0) {
159
+ action = "pass";
160
+ } else {
161
+ action = "block";
162
+ emitBlock(missing);
163
+ }
164
+ }
165
+
166
+ const entry = {
167
+ ts: new Date().toISOString(),
168
+ hook: "three-section-close",
169
+ action,
170
+ textLength,
171
+ missing: missing.map((s) => s.heading),
172
+ durationMs: Math.round((performance.now() - startedAt) * 1000) / 1000,
173
+ };
174
+ recordTelemetry(entry, payload.transcript_path);
175
+ }
176
+
177
+ try {
178
+ main();
179
+ } catch {
180
+ // fail open — never block the session due to a hook bug
181
+ }
@@ -0,0 +1,16 @@
1
+ [
2
+ {
3
+ "id": "skip-thin-observation-schema",
4
+ "trigger": "when running /continuous-improvement and observations.jsonl entries contain only tool_start events with no inputs/outputs/errors",
5
+ "body": "Skip instinct fabrication and report the schema gap instead. With only ts/event/session/tool/project_id/project_name fields, the stream cannot distinguish a `git status` Bash call from a `rm -rf` Bash call. Tool-name bigrams (Bash→Bash, Edit→Edit, Read→Edit) reflect parallel-call artifacts and harness-enforced read-before-edit, not learned preferences. Recommend enhancing PostToolUse/PreToolUse hooks to log tool inputs (Bash command head, Skill name) and tool_end with success flag before retrying analysis.",
6
+ "confidence": 0.6,
7
+ "domain": "meta"
8
+ },
9
+ {
10
+ "id": "parallelize-independent-tool-calls",
11
+ "trigger": "when issuing 2+ tool calls with no data dependency between them in the same turn",
12
+ "body": "Batch independent tool calls into a single message instead of issuing them sequentially. Independent reads, independent greps, independent git inspections, independent file edits in unrelated files belong in one message with multiple tool blocks. Dependent calls (output of A determines input of B) stay sequential. Exception: when a single check would short-circuit the whole task (e.g., `git status` returning empty), one call first is fine. Sequential issuing of independent calls wastes wall-clock time and burns context per round-trip.",
13
+ "confidence": 0.6,
14
+ "domain": "workflow"
15
+ }
16
+ ]
@@ -0,0 +1,401 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI-Anything: Turn open-source software into agent-native CLIs.
4
+ *
5
+ * Analyzes a repository and generates an agent-friendly CLI wrapper that
6
+ * language models can drive automatically.
7
+ */
8
+ import { existsSync } from "node:fs";
9
+ import { access, chmod, mkdir, readFile, writeFile } from "node:fs/promises";
10
+ import { join } from "node:path";
11
+ const DEFAULT_OPTIONS = {
12
+ outputDir: "./generated-clis",
13
+ verbose: false,
14
+ };
15
+ function getErrorMessage(error) {
16
+ return error instanceof Error ? error.message : String(error);
17
+ }
18
+ export default class CLIAnything {
19
+ options;
20
+ constructor(options = {}) {
21
+ this.options = { ...DEFAULT_OPTIONS, ...options };
22
+ }
23
+ async generateCLI(repoPath, options = {}) {
24
+ try {
25
+ this.log(`Analyzing repository: ${repoPath}`);
26
+ const repoInfo = await this.analyzeRepository(repoPath);
27
+ const commands = await this.extractCommands(repoPath, repoInfo);
28
+ const cliWrapper = await this.generateCLIWrapper(repoInfo, commands, options);
29
+ const outputPath = await this.saveCLI(repoInfo, cliWrapper);
30
+ this.log(`✅ CLI generated: ${outputPath}`);
31
+ return { outputPath, repoInfo, commands };
32
+ }
33
+ catch (error) {
34
+ throw new Error(`Failed to generate CLI: ${getErrorMessage(error)}`);
35
+ }
36
+ }
37
+ async analyzeRepository(repoPath) {
38
+ const packageJsonPath = join(repoPath, "package.json");
39
+ const readmePath = join(repoPath, "README.md");
40
+ let packageJson = {};
41
+ let readme = "";
42
+ try {
43
+ const packageContent = await readFile(packageJsonPath, "utf8");
44
+ packageJson = JSON.parse(packageContent);
45
+ }
46
+ catch {
47
+ // Not a Node.js project or no package.json.
48
+ }
49
+ try {
50
+ readme = await readFile(readmePath, "utf8");
51
+ }
52
+ catch {
53
+ // No README.
54
+ }
55
+ const projectType = this.detectProjectType(repoPath, packageJson);
56
+ const existingCLI = this.extractExistingCLI(packageJson);
57
+ const hasBin = !!packageJson.bin &&
58
+ (typeof packageJson.bin === "string" ||
59
+ Object.keys(packageJson.bin).length > 0);
60
+ return {
61
+ name: packageJson.name ?? this.extractRepoName(repoPath),
62
+ version: packageJson.version ?? "1.0.0",
63
+ description: packageJson.description ?? "",
64
+ projectType,
65
+ existingCLI,
66
+ hasBin,
67
+ scripts: packageJson.scripts ?? {},
68
+ dependencies: packageJson.dependencies ?? {},
69
+ readme,
70
+ };
71
+ }
72
+ detectProjectType(repoPath, packageJson) {
73
+ const { dependencies = {}, devDependencies = {} } = packageJson;
74
+ const allDeps = { ...dependencies, ...devDependencies };
75
+ if (allDeps.express || allDeps.koa || allDeps.fastify)
76
+ return "node-server";
77
+ if (allDeps.react || allDeps["react-dom"])
78
+ return "react-app";
79
+ if (allDeps.vue)
80
+ return "vue-app";
81
+ if (allDeps.angular)
82
+ return "angular-app";
83
+ if (allDeps.next)
84
+ return "next-app";
85
+ if (allDeps.gatsby)
86
+ return "gatsby-app";
87
+ if (allDeps.python || allDeps["python-shell"])
88
+ return "python-wrapper";
89
+ if (allDeps.docker || allDeps["dockerode"])
90
+ return "docker-tool";
91
+ if (existsSync(join(repoPath, "Dockerfile")))
92
+ return "docker-project";
93
+ if (existsSync(join(repoPath, "requirements.txt")) ||
94
+ existsSync(join(repoPath, "setup.py")) ||
95
+ existsSync(join(repoPath, "pyproject.toml"))) {
96
+ return "python-project";
97
+ }
98
+ if (existsSync(join(repoPath, "Cargo.toml")))
99
+ return "rust-project";
100
+ if (existsSync(join(repoPath, "go.mod")))
101
+ return "go-project";
102
+ return "generic";
103
+ }
104
+ extractExistingCLI(packageJson) {
105
+ const cli = {
106
+ bin: packageJson.bin ?? {},
107
+ scripts: {},
108
+ mainCommand: null,
109
+ };
110
+ if (packageJson.scripts) {
111
+ for (const [name, script] of Object.entries(packageJson.scripts)) {
112
+ if (name.includes("start") ||
113
+ name.includes("build") ||
114
+ name.includes("test") ||
115
+ name.includes("dev") ||
116
+ name.includes("serve") ||
117
+ name.includes("run")) {
118
+ cli.scripts[name] = script;
119
+ }
120
+ }
121
+ }
122
+ if (typeof cli.bin === "object") {
123
+ const keys = Object.keys(cli.bin);
124
+ if (keys.length > 0)
125
+ cli.mainCommand = keys[0] ?? null;
126
+ }
127
+ else if (typeof cli.bin === "string") {
128
+ cli.mainCommand = "main";
129
+ }
130
+ return cli;
131
+ }
132
+ async extractCommands(_repoPath, repoInfo) {
133
+ const commands = [];
134
+ for (const [name, script] of Object.entries(repoInfo.scripts)) {
135
+ commands.push({
136
+ type: "script",
137
+ name,
138
+ command: script,
139
+ description: this.generateScriptDescription(name),
140
+ category: this.categorizeScript(name),
141
+ });
142
+ }
143
+ const { bin } = repoInfo.existingCLI;
144
+ if (typeof bin === "object") {
145
+ for (const [name, path] of Object.entries(bin)) {
146
+ commands.push({
147
+ type: "binary",
148
+ name,
149
+ path,
150
+ description: `Execute ${name} command`,
151
+ category: "core",
152
+ });
153
+ }
154
+ }
155
+ commands.push(...this.generateProjectTypeCommands(repoInfo));
156
+ return commands;
157
+ }
158
+ generateProjectTypeCommands(repoInfo) {
159
+ const commands = [];
160
+ switch (repoInfo.projectType) {
161
+ case "node-server":
162
+ commands.push({ type: "generated", name: "start", command: "npm start", description: "Start the server", category: "lifecycle" }, { type: "generated", name: "dev", command: "npm run dev", description: "Start in development mode", category: "development" }, { type: "generated", name: "test", command: "npm test", description: "Run tests", category: "testing" });
163
+ break;
164
+ case "react-app":
165
+ case "next-app":
166
+ commands.push({ type: "generated", name: "dev", command: "npm run dev", description: "Start development server", category: "development" }, { type: "generated", name: "build", command: "npm run build", description: "Build for production", category: "build" }, { type: "generated", name: "test", command: "npm test", description: "Run tests", category: "testing" }, { type: "generated", name: "lint", command: "npm run lint", description: "Run linting", category: "quality" });
167
+ break;
168
+ case "python-project":
169
+ commands.push({ type: "generated", name: "install", command: "pip install -r requirements.txt", description: "Install dependencies", category: "setup" }, { type: "generated", name: "run", command: "python main.py", description: "Run main script", category: "core" }, { type: "generated", name: "test", command: "python -m pytest", description: "Run tests", category: "testing" });
170
+ break;
171
+ case "docker-project":
172
+ commands.push({ type: "generated", name: "build", command: "docker build -t app .", description: "Build Docker image", category: "build" }, { type: "generated", name: "run", command: "docker run -p 3000:3000 app", description: "Run Docker container", category: "core" });
173
+ break;
174
+ default:
175
+ break;
176
+ }
177
+ return commands;
178
+ }
179
+ async generateCLIWrapper(repoInfo, commands, options = {}) {
180
+ const template = await this.getTemplate(options.template ?? "default");
181
+ const className = repoInfo.name.replace(/[^a-zA-Z0-9]/g, "");
182
+ return template
183
+ .replace(/{{NAME}}/g, className)
184
+ .replace(/{{VERSION}}/g, repoInfo.version)
185
+ .replace(/{{DESCRIPTION}}/g, repoInfo.description)
186
+ .replace(/{{COMMANDS}}/g, this.generateCommandsCode(commands))
187
+ .replace(/{{PROJECT_TYPE}}/g, repoInfo.projectType)
188
+ .replace(/{{REPO_INFO}}/g, JSON.stringify(repoInfo, null, 2));
189
+ }
190
+ generateCommandsCode(commands) {
191
+ const commandGroups = this.groupCommands(commands);
192
+ let code = "";
193
+ for (const [category, categoryCommands] of Object.entries(commandGroups)) {
194
+ code += ` // ${category.toUpperCase()} COMMANDS\n`;
195
+ for (const cmd of categoryCommands) {
196
+ const safeName = cmd.name.replace(/[^a-zA-Z0-9]/g, "_");
197
+ code += ` ${safeName}: {
198
+ description: '${cmd.description}',
199
+ execute: async (args = []) => {
200
+ return await this.runCommand('${cmd.command ?? ""}', args);
201
+ }
202
+ },\n`;
203
+ }
204
+ code += "\n";
205
+ }
206
+ return code.trim();
207
+ }
208
+ groupCommands(commands) {
209
+ return commands.reduce((groups, cmd) => {
210
+ const category = cmd.category || "general";
211
+ if (!groups[category])
212
+ groups[category] = [];
213
+ groups[category].push(cmd);
214
+ return groups;
215
+ }, {});
216
+ }
217
+ async getTemplate(templateName) {
218
+ const templates = {
219
+ default: `#!/usr/bin/env node
220
+
221
+ /**
222
+ * Auto-generated CLI wrapper for {{NAME}}
223
+ * Generated by CLI-Anything tool
224
+ */
225
+
226
+ import { spawn } from 'node:child_process';
227
+
228
+ const repoInfo = {{REPO_INFO}};
229
+
230
+ class {{NAME}}CLI {
231
+ constructor() {
232
+ this.commands = {
233
+ {{COMMANDS}}
234
+ };
235
+ }
236
+
237
+ async runCommand(command, args = []) {
238
+ return new Promise((resolve, reject) => {
239
+ const process = spawn(command, args, {
240
+ stdio: 'inherit',
241
+ shell: true
242
+ });
243
+
244
+ process.on('close', (code) => {
245
+ if (code === 0) {
246
+ resolve({ success: true, code });
247
+ } else {
248
+ reject(new Error(\`Command failed with code \${code}\`));
249
+ }
250
+ });
251
+
252
+ process.on('error', reject);
253
+ });
254
+ }
255
+
256
+ async execute(commandName, args = []) {
257
+ if (!this.commands[commandName]) {
258
+ throw new Error(\`Unknown command: \${commandName}\`);
259
+ }
260
+
261
+ const command = this.commands[commandName];
262
+ console.log(\`Executing: \${command.description}\`);
263
+
264
+ try {
265
+ const result = await command.execute(args);
266
+ console.log(\`✅ \${command.description} completed successfully\`);
267
+ return result;
268
+ } catch (error) {
269
+ console.error(\`❌ \${command.description} failed:\`, error.message);
270
+ throw error;
271
+ }
272
+ }
273
+
274
+ listCommands() {
275
+ console.log('Available commands for {{NAME}}:');
276
+ Object.entries(this.commands).forEach(([name, cmd]) => {
277
+ console.log(\` \${name.padEnd(15)} - \${cmd.description}\`);
278
+ });
279
+ }
280
+ }
281
+
282
+ if (import.meta.url === \`file://\${process.argv[1]}\`) {
283
+ const cli = new {{NAME}}CLI();
284
+ const [,, command, ...args] = process.argv;
285
+
286
+ if (!command || command === '--help' || command === '-h') {
287
+ cli.listCommands();
288
+ process.exit(0);
289
+ }
290
+
291
+ cli.execute(command, args).catch(error => {
292
+ console.error(error);
293
+ process.exit(1);
294
+ });
295
+ }
296
+
297
+ export default {{NAME}}CLI;`,
298
+ };
299
+ return templates[templateName] ?? templates.default;
300
+ }
301
+ async saveCLI(repoInfo, cliWrapper) {
302
+ const outputDir = this.options.outputDir;
303
+ const fileName = `${repoInfo.name.toLowerCase().replace(/[^a-z0-9]/g, "-")}-cli.mjs`;
304
+ const outputPath = join(outputDir, fileName);
305
+ try {
306
+ await access(outputDir);
307
+ }
308
+ catch {
309
+ await mkdir(outputDir, { recursive: true });
310
+ }
311
+ await writeFile(outputPath, cliWrapper, "utf8");
312
+ try {
313
+ await chmod(outputPath, 0o755);
314
+ }
315
+ catch {
316
+ // Ignore on Windows.
317
+ }
318
+ return outputPath;
319
+ }
320
+ extractRepoName(repoPath) {
321
+ const segments = repoPath.split(/[\\/]/);
322
+ const last = segments[segments.length - 1] ?? repoPath;
323
+ return last.replace(/[^a-zA-Z0-9]/g, "-");
324
+ }
325
+ generateScriptDescription(name) {
326
+ const descriptions = {
327
+ start: "Start the application",
328
+ dev: "Start in development mode",
329
+ build: "Build the application",
330
+ test: "Run tests",
331
+ lint: "Run linting",
332
+ serve: "Start the server",
333
+ deploy: "Deploy the application",
334
+ };
335
+ return descriptions[name] ?? `Execute script: ${name}`;
336
+ }
337
+ categorizeScript(name) {
338
+ if (["start", "dev", "serve"].includes(name))
339
+ return "lifecycle";
340
+ if (["build", "compile"].includes(name))
341
+ return "build";
342
+ if (["test", "spec"].includes(name))
343
+ return "testing";
344
+ if (["lint", "format", "type-check"].includes(name))
345
+ return "quality";
346
+ if (["deploy", "publish"].includes(name))
347
+ return "deployment";
348
+ return "general";
349
+ }
350
+ log(message) {
351
+ if (this.options.verbose) {
352
+ console.log(`[CLI-Anything] ${message}`);
353
+ }
354
+ }
355
+ }
356
+ if (import.meta.url === `file://${process.argv[1]}`) {
357
+ const [, , repoPath, ...args] = process.argv;
358
+ if (!repoPath || repoPath === "--help" || repoPath === "-h") {
359
+ console.log(`
360
+ Usage: cli-anything <repository-path> [options]
361
+
362
+ Options:
363
+ --output-dir <dir> Output directory for generated CLIs (default: ./generated-clis)
364
+ --verbose Enable verbose logging
365
+ --template <name> Template to use (default: default)
366
+ --help Show this help
367
+
368
+ Examples:
369
+ cli-anything ./my-project
370
+ cli-anything ./my-project --output-dir ./tools --verbose
371
+ `);
372
+ process.exit(0);
373
+ }
374
+ const options = {};
375
+ for (let i = 0; i < args.length; i++) {
376
+ const flag = args[i];
377
+ if (flag === "--output-dir" && args[i + 1]) {
378
+ options.outputDir = args[i + 1];
379
+ i++;
380
+ }
381
+ else if (flag === "--verbose") {
382
+ options.verbose = true;
383
+ }
384
+ else if (flag === "--template" && args[i + 1]) {
385
+ options.template = args[i + 1];
386
+ i++;
387
+ }
388
+ }
389
+ const cliAnything = new CLIAnything(options);
390
+ cliAnything
391
+ .generateCLI(repoPath, { template: options.template })
392
+ .then((result) => {
393
+ console.log(`✅ CLI generated successfully!`);
394
+ console.log(`📁 Output: ${result.outputPath}`);
395
+ console.log(`📊 Commands found: ${result.commands.length}`);
396
+ })
397
+ .catch((error) => {
398
+ console.error(`❌ Error: ${getErrorMessage(error)}`);
399
+ process.exit(1);
400
+ });
401
+ }