@c0x12c/ai-toolkit 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +16 -0
- package/.claude-plugin/plugin.json +12 -0
- package/README.md +439 -0
- package/VERSION +1 -0
- package/agents/design-critic.md +127 -0
- package/agents/idea-killer.md +72 -0
- package/agents/infrastructure-expert.md +49 -0
- package/agents/micronaut-backend-expert.md +45 -0
- package/agents/phase-reviewer.md +150 -0
- package/agents/research-planner.md +70 -0
- package/agents/solution-architect-cto.md +49 -0
- package/agents/sre-architect.md +49 -0
- package/agents/team-coordinator.md +111 -0
- package/bin/cli.js +780 -0
- package/claude-md/00-header.md +39 -0
- package/claude-md/01-core.md +105 -0
- package/claude-md/05-database.md +20 -0
- package/claude-md/11-backend-micronaut.md +19 -0
- package/claude-md/20-frontend-react.md +44 -0
- package/claude-md/25-ux-design.md +56 -0
- package/claude-md/30-infrastructure.md +24 -0
- package/claude-md/30-project-mgmt.md +119 -0
- package/claude-md/40-product.md +39 -0
- package/claude-md/50-ops.md +34 -0
- package/claude-md/60-research.md +27 -0
- package/claude-md/90-footer.md +21 -0
- package/commands/spartan/brainstorm.md +134 -0
- package/commands/spartan/brownfield.md +157 -0
- package/commands/spartan/build.md +435 -0
- package/commands/spartan/careful.md +94 -0
- package/commands/spartan/commit-message.md +112 -0
- package/commands/spartan/content.md +17 -0
- package/commands/spartan/context-save.md +161 -0
- package/commands/spartan/contribute.md +140 -0
- package/commands/spartan/daily.md +42 -0
- package/commands/spartan/debug.md +308 -0
- package/commands/spartan/deep-dive.md +55 -0
- package/commands/spartan/deploy.md +207 -0
- package/commands/spartan/e2e.md +264 -0
- package/commands/spartan/env-setup.md +166 -0
- package/commands/spartan/epic.md +199 -0
- package/commands/spartan/fe-review.md +181 -0
- package/commands/spartan/figma-to-code.md +260 -0
- package/commands/spartan/forensics.md +46 -0
- package/commands/spartan/freeze.md +84 -0
- package/commands/spartan/fundraise.md +53 -0
- package/commands/spartan/gate-review.md +229 -0
- package/commands/spartan/gsd-upgrade.md +376 -0
- package/commands/spartan/guard.md +42 -0
- package/commands/spartan/init-project.md +178 -0
- package/commands/spartan/init-rules.md +298 -0
- package/commands/spartan/interview.md +154 -0
- package/commands/spartan/kickoff.md +73 -0
- package/commands/spartan/kotlin-service.md +109 -0
- package/commands/spartan/lean-canvas.md +222 -0
- package/commands/spartan/lint-rules.md +122 -0
- package/commands/spartan/map-codebase.md +124 -0
- package/commands/spartan/migration.md +82 -0
- package/commands/spartan/next-app.md +317 -0
- package/commands/spartan/next-feature.md +212 -0
- package/commands/spartan/onboard.md +326 -0
- package/commands/spartan/outreach.md +16 -0
- package/commands/spartan/phase.md +142 -0
- package/commands/spartan/pitch.md +18 -0
- package/commands/spartan/plan.md +210 -0
- package/commands/spartan/pr-ready.md +202 -0
- package/commands/spartan/project.md +106 -0
- package/commands/spartan/qa.md +222 -0
- package/commands/spartan/research.md +254 -0
- package/commands/spartan/review.md +132 -0
- package/commands/spartan/scan-rules.md +173 -0
- package/commands/spartan/sessions.md +143 -0
- package/commands/spartan/spec.md +131 -0
- package/commands/spartan/startup.md +257 -0
- package/commands/spartan/team.md +570 -0
- package/commands/spartan/teardown.md +161 -0
- package/commands/spartan/testcontainer.md +97 -0
- package/commands/spartan/tf-cost.md +123 -0
- package/commands/spartan/tf-deploy.md +116 -0
- package/commands/spartan/tf-drift.md +100 -0
- package/commands/spartan/tf-import.md +107 -0
- package/commands/spartan/tf-module.md +121 -0
- package/commands/spartan/tf-plan.md +100 -0
- package/commands/spartan/tf-review.md +106 -0
- package/commands/spartan/tf-scaffold.md +109 -0
- package/commands/spartan/tf-security.md +147 -0
- package/commands/spartan/think.md +221 -0
- package/commands/spartan/unfreeze.md +13 -0
- package/commands/spartan/update.md +134 -0
- package/commands/spartan/ux.md +1233 -0
- package/commands/spartan/validate.md +193 -0
- package/commands/spartan/web-to-prd.md +706 -0
- package/commands/spartan/workstreams.md +109 -0
- package/commands/spartan/write.md +16 -0
- package/commands/spartan.md +386 -0
- package/frameworks/00-framework-comparison-guide.md +317 -0
- package/frameworks/01-lean-canvas.md +196 -0
- package/frameworks/02-design-sprint.md +304 -0
- package/frameworks/03-foundation-sprint.md +337 -0
- package/frameworks/04-business-model-canvas.md +391 -0
- package/frameworks/05-customer-development.md +426 -0
- package/frameworks/06-jobs-to-be-done.md +358 -0
- package/frameworks/07-mom-test.md +392 -0
- package/frameworks/08-value-proposition-canvas.md +488 -0
- package/frameworks/09-javelin-board.md +428 -0
- package/frameworks/10-build-measure-learn.md +467 -0
- package/frameworks/11-mvp-approaches.md +533 -0
- package/frameworks/think-before-build.md +593 -0
- package/lib/assembler.js +197 -0
- package/lib/assembler.test.js +159 -0
- package/lib/detector.js +166 -0
- package/lib/detector.test.js +221 -0
- package/lib/packs.js +16 -0
- package/lib/resolver.js +272 -0
- package/lib/resolver.test.js +298 -0
- package/lib/worktree.sh +104 -0
- package/package.json +50 -0
- package/packs/backend-micronaut.yaml +35 -0
- package/packs/backend-nodejs.yaml +15 -0
- package/packs/backend-python.yaml +15 -0
- package/packs/core.yaml +37 -0
- package/packs/database.yaml +21 -0
- package/packs/frontend-react.yaml +24 -0
- package/packs/infrastructure.yaml +40 -0
- package/packs/ops.yaml +16 -0
- package/packs/packs.compiled.json +371 -0
- package/packs/product.yaml +22 -0
- package/packs/project-mgmt.yaml +24 -0
- package/packs/research.yaml +39 -0
- package/packs/shared-backend.yaml +14 -0
- package/packs/ux-design.yaml +21 -0
- package/rules/backend-micronaut/API_DESIGN.md +313 -0
- package/rules/backend-micronaut/BATCH_PROCESSING.md +92 -0
- package/rules/backend-micronaut/CONTROLLERS.md +388 -0
- package/rules/backend-micronaut/KOTLIN.md +414 -0
- package/rules/backend-micronaut/RETROFIT_PLACEMENT.md +290 -0
- package/rules/backend-micronaut/SERVICES_AND_BEANS.md +325 -0
- package/rules/core/NAMING_CONVENTIONS.md +208 -0
- package/rules/core/SKILL_AUTHORING.md +174 -0
- package/rules/core/TIMEZONE.md +316 -0
- package/rules/database/ORM_AND_REPO.md +289 -0
- package/rules/database/SCHEMA.md +146 -0
- package/rules/database/TRANSACTIONS.md +311 -0
- package/rules/frontend-react/FRONTEND.md +344 -0
- package/rules/infrastructure/MODULES.md +260 -0
- package/rules/infrastructure/NAMING.md +196 -0
- package/rules/infrastructure/PROVIDERS.md +309 -0
- package/rules/infrastructure/SECURITY.md +310 -0
- package/rules/infrastructure/STATE_AND_BACKEND.md +237 -0
- package/rules/infrastructure/STRUCTURE.md +234 -0
- package/rules/infrastructure/VARIABLES.md +285 -0
- package/rules/shared-backend/ARCHITECTURE.md +46 -0
- package/rules/ux-design/DESIGN_PROCESS.md +176 -0
- package/skills/api-endpoint-creator/SKILL.md +455 -0
- package/skills/api-endpoint-creator/error-handling-guide.md +244 -0
- package/skills/api-endpoint-creator/examples.md +522 -0
- package/skills/api-endpoint-creator/testing-patterns.md +302 -0
- package/skills/article-writing/SKILL.md +109 -0
- package/skills/article-writing/examples.md +59 -0
- package/skills/backend-api-design/SKILL.md +84 -0
- package/skills/backend-api-design/code-patterns.md +138 -0
- package/skills/brainstorm/SKILL.md +95 -0
- package/skills/browser-qa/SKILL.md +87 -0
- package/skills/browser-qa/playwright-snippets.md +110 -0
- package/skills/ci-cd-patterns/SKILL.md +108 -0
- package/skills/ci-cd-patterns/workflows.md +149 -0
- package/skills/competitive-teardown/SKILL.md +93 -0
- package/skills/competitive-teardown/example-analysis.md +50 -0
- package/skills/content-engine/SKILL.md +131 -0
- package/skills/content-engine/examples.md +72 -0
- package/skills/database-patterns/SKILL.md +72 -0
- package/skills/database-patterns/code-templates.md +114 -0
- package/skills/database-table-creator/SKILL.md +141 -0
- package/skills/database-table-creator/examples.md +552 -0
- package/skills/database-table-creator/kotlin-templates.md +400 -0
- package/skills/database-table-creator/migration-template.sql +68 -0
- package/skills/database-table-creator/validation-checklist.md +337 -0
- package/skills/deep-research/SKILL.md +80 -0
- package/skills/design-intelligence/SKILL.md +268 -0
- package/skills/design-workflow/SKILL.md +127 -0
- package/skills/design-workflow/checklists.md +45 -0
- package/skills/idea-validation/SKILL.md +129 -0
- package/skills/idea-validation/example-report.md +50 -0
- package/skills/investor-materials/SKILL.md +122 -0
- package/skills/investor-materials/example-outline.md +70 -0
- package/skills/investor-outreach/SKILL.md +112 -0
- package/skills/investor-outreach/examples.md +76 -0
- package/skills/kotlin-best-practices/SKILL.md +58 -0
- package/skills/kotlin-best-practices/code-patterns.md +132 -0
- package/skills/market-research/SKILL.md +99 -0
- package/skills/security-checklist/SKILL.md +65 -0
- package/skills/security-checklist/audit-reference.md +95 -0
- package/skills/service-debugging/SKILL.md +116 -0
- package/skills/service-debugging/common-issues.md +65 -0
- package/skills/startup-pipeline/SKILL.md +152 -0
- package/skills/terraform-best-practices/SKILL.md +244 -0
- package/skills/terraform-module-creator/SKILL.md +284 -0
- package/skills/terraform-review/SKILL.md +222 -0
- package/skills/terraform-security-audit/SKILL.md +280 -0
- package/skills/terraform-service-scaffold/SKILL.md +574 -0
- package/skills/testing-strategies/SKILL.md +116 -0
- package/skills/testing-strategies/examples.md +103 -0
- package/skills/testing-strategies/integration-test-setup.md +71 -0
- package/skills/ui-ux-pro-max/SKILL.md +238 -0
- package/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/skills/ui-ux-pro-max/python-setup.md +146 -0
- package/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/skills/ui-ux-pro-max/scripts/search.py +114 -0
- package/skills/web-to-prd/SKILL.md +478 -0
- package/templates/build-config.yaml +44 -0
- package/templates/commands-config.yaml +55 -0
- package/templates/competitor-analysis.md +60 -0
- package/templates/content/AGENT_TEMPLATE.md +47 -0
- package/templates/content/COMMAND_TEMPLATE.md +27 -0
- package/templates/content/RULE_TEMPLATE.md +40 -0
- package/templates/content/SKILL_TEMPLATE.md +41 -0
- package/templates/design-config.md +105 -0
- package/templates/design-doc.md +207 -0
- package/templates/epic.md +100 -0
- package/templates/feature-spec.md +181 -0
- package/templates/idea-canvas.md +47 -0
- package/templates/implementation-plan.md +159 -0
- package/templates/prd-template.md +86 -0
- package/templates/preamble.md +89 -0
- package/templates/project-readme.md +35 -0
- package/templates/quality-gates.md +230 -0
- package/templates/spartan-config.yaml +164 -0
- package/templates/user-interview.md +69 -0
- package/templates/validation-checklist.md +108 -0
- package/templates/workflow-backend-micronaut.md +409 -0
- package/templates/workflow-frontend-react.md +233 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,780 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Spartan AI Toolkit — npx installer
|
|
4
|
+
// Usage:
|
|
5
|
+
// npx @c0x12c/ai-toolkit@latest
|
|
6
|
+
// npx @c0x12c/ai-toolkit@latest --agent=cursor
|
|
7
|
+
// npx @c0x12c/ai-toolkit@latest --packs=backend-micronaut,product
|
|
8
|
+
// npx @c0x12c/ai-toolkit@latest --all
|
|
9
|
+
// npx @c0x12c/ai-toolkit@latest --local
|
|
10
|
+
|
|
11
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, cpSync, readdirSync, copyFileSync } from 'node:fs';
|
|
12
|
+
import { join, dirname, resolve as pathResolve } from 'node:path';
|
|
13
|
+
import { createInterface } from 'node:readline';
|
|
14
|
+
import { homedir } from 'node:os';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
|
|
17
|
+
// ── Resolve package root (works from npx temp dir) ──────────────
|
|
18
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
+
const __dirname = dirname(__filename);
|
|
20
|
+
const PKG_ROOT = pathResolve(__dirname, '..');
|
|
21
|
+
|
|
22
|
+
// ── Toolkit source paths ────────────────────────────────────────
|
|
23
|
+
const SRC = {
|
|
24
|
+
commands: join(PKG_ROOT, 'commands'),
|
|
25
|
+
commandsSub: join(PKG_ROOT, 'commands', 'spartan'),
|
|
26
|
+
router: join(PKG_ROOT, 'commands', 'spartan.md'),
|
|
27
|
+
rules: join(PKG_ROOT, 'rules'),
|
|
28
|
+
skills: join(PKG_ROOT, 'skills'),
|
|
29
|
+
agents: join(PKG_ROOT, 'agents'),
|
|
30
|
+
profiles: join(PKG_ROOT, 'profiles'),
|
|
31
|
+
claudeMd: join(PKG_ROOT, 'claude-md'),
|
|
32
|
+
version: join(PKG_ROOT, 'VERSION'),
|
|
33
|
+
claudePlugin: join(PKG_ROOT, '.claude-plugin'),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// ── Colors ──────────────────────────────────────────────────────
|
|
37
|
+
const C = {
|
|
38
|
+
blue: '\x1b[34m', green: '\x1b[32m', yellow: '\x1b[33m',
|
|
39
|
+
red: '\x1b[31m', cyan: '\x1b[36m', bold: '\x1b[1m',
|
|
40
|
+
dim: '\x1b[2m', reset: '\x1b[0m',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
function bold(s) { return `${C.bold}${s}${C.reset}`; }
|
|
44
|
+
function green(s) { return `${C.green}${s}${C.reset}`; }
|
|
45
|
+
function yellow(s) { return `${C.yellow}${s}${C.reset}`; }
|
|
46
|
+
function cyan(s) { return `${C.cyan}${s}${C.reset}`; }
|
|
47
|
+
function dim(s) { return `${C.dim}${s}${C.reset}`; }
|
|
48
|
+
function blue(s) { return `${C.blue}${s}${C.reset}`; }
|
|
49
|
+
|
|
50
|
+
// ── Pack definitions (loaded from YAML manifests) ───────────────
|
|
51
|
+
import { PACKS, PACK_ORDER } from '../lib/packs.js';
|
|
52
|
+
import { assembleCLAUDEmd, assembleAGENTSmd } from '../lib/assembler.js';
|
|
53
|
+
import { resolve as resolveDeps, resolveAliases, loadManifests, loadExternalPacks } from '../lib/resolver.js';
|
|
54
|
+
import { detectStacks } from '../lib/detector.js';
|
|
55
|
+
|
|
56
|
+
const manifests = loadManifests(join(PKG_ROOT, 'packs'));
|
|
57
|
+
|
|
58
|
+
// Maps community pack names to their source directory (for file resolution)
|
|
59
|
+
const externalPackSources = {};
|
|
60
|
+
|
|
61
|
+
// ── Parse args ──────────────────────────────────────────────────
|
|
62
|
+
const args = process.argv.slice(2);
|
|
63
|
+
|
|
64
|
+
let agent = 'claude-code';
|
|
65
|
+
let packsArg = '';
|
|
66
|
+
let installAll = false;
|
|
67
|
+
let mode = 'global'; // default for claude-code
|
|
68
|
+
let showHelp = false;
|
|
69
|
+
let format = ''; // '' = default, 'agents-md' = export AGENTS.md
|
|
70
|
+
let autoDetect = false;
|
|
71
|
+
let packDirArg = ''; // external community pack directory
|
|
72
|
+
|
|
73
|
+
for (const arg of args) {
|
|
74
|
+
if (arg === '--help' || arg === '-h') showHelp = true;
|
|
75
|
+
else if (arg.startsWith('--agent=')) agent = arg.split('=')[1];
|
|
76
|
+
else if (arg.startsWith('--packs=')) packsArg = arg.split('=')[1];
|
|
77
|
+
else if (arg.startsWith('--format=')) format = arg.split('=')[1];
|
|
78
|
+
else if (arg.startsWith('--pack-dir=')) packDirArg = arg.split('=')[1];
|
|
79
|
+
else if (arg === '--all') installAll = true;
|
|
80
|
+
else if (arg === '--auto') autoDetect = true;
|
|
81
|
+
else if (arg === '--global') mode = 'global';
|
|
82
|
+
else if (arg === '--local') mode = 'local';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (showHelp) {
|
|
86
|
+
// Group packs by category for help display
|
|
87
|
+
const categories = {};
|
|
88
|
+
for (const name of PACK_ORDER) {
|
|
89
|
+
const p = PACKS[name];
|
|
90
|
+
if (p.hidden) continue;
|
|
91
|
+
const cat = p.category || 'Other';
|
|
92
|
+
if (!categories[cat]) categories[cat] = [];
|
|
93
|
+
const items = [
|
|
94
|
+
p.commands.length ? `${p.commands.length} commands` : '',
|
|
95
|
+
p.rules.length ? `${p.rules.length} rules` : '',
|
|
96
|
+
p.skills.length ? `${p.skills.length} skills` : '',
|
|
97
|
+
p.agents.length ? `${p.agents.length} agents` : '',
|
|
98
|
+
].filter(Boolean).join(', ');
|
|
99
|
+
const tag = p.comingSoon ? ` ${dim('(coming soon)')}` : '';
|
|
100
|
+
categories[cat].push(` ${bold(name.padEnd(20))} ${p.description}${tag}${items ? dim(` (${items})`) : ''}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
console.log(`
|
|
104
|
+
${bold('Spartan AI Toolkit')} — installer
|
|
105
|
+
|
|
106
|
+
${bold('Usage:')}
|
|
107
|
+
npx @c0x12c/ai-toolkit@latest [options]
|
|
108
|
+
|
|
109
|
+
${bold('Options:')}
|
|
110
|
+
--agent=NAME Agent to set up for (default: claude-code)
|
|
111
|
+
Choices: claude-code, cursor, windsurf, codex, copilot
|
|
112
|
+
--packs=LIST Comma-separated packs (claude-code only)
|
|
113
|
+
Example: --packs=backend-micronaut,product
|
|
114
|
+
--auto Auto-detect tech stack and suggest packs (no menu)
|
|
115
|
+
--pack-dir=DIR Load community packs from an external directory
|
|
116
|
+
--format=NAME Output format: agents-md (exports AGENTS.md for cross-tool use)
|
|
117
|
+
--all Install all packs
|
|
118
|
+
--global Install to home dir (default for claude-code/codex)
|
|
119
|
+
--local Install to current project dir
|
|
120
|
+
--help Show this help
|
|
121
|
+
|
|
122
|
+
${bold('Packs:')}
|
|
123
|
+
${Object.entries(categories).map(([cat, lines]) => `\n ${bold(cat + ':')}
|
|
124
|
+
${lines.join('\n')}`).join('\n')}
|
|
125
|
+
|
|
126
|
+
${bold('Examples:')}
|
|
127
|
+
${cyan('npx @c0x12c/ai-toolkit@latest')}
|
|
128
|
+
Interactive — pick agent and packs from menu
|
|
129
|
+
|
|
130
|
+
${cyan('npx @c0x12c/ai-toolkit@latest --packs=frontend-react,product')}
|
|
131
|
+
Next.js app with product thinking tools
|
|
132
|
+
|
|
133
|
+
${cyan('npx @c0x12c/ai-toolkit@latest --packs=backend-micronaut,project-mgmt')}
|
|
134
|
+
Kotlin APIs with full project lifecycle
|
|
135
|
+
|
|
136
|
+
${cyan('npx @c0x12c/ai-toolkit@latest --all')}
|
|
137
|
+
Everything installed
|
|
138
|
+
|
|
139
|
+
${cyan('npx @c0x12c/ai-toolkit@latest --agent=cursor')}
|
|
140
|
+
Install rules for Cursor (rules + AGENTS.md only)
|
|
141
|
+
|
|
142
|
+
${cyan('npx @c0x12c/ai-toolkit@latest --auto')}
|
|
143
|
+
Auto-detect your tech stack and install matching packs
|
|
144
|
+
|
|
145
|
+
${cyan('npx @c0x12c/ai-toolkit@latest --format=agents-md --packs=backend-micronaut')}
|
|
146
|
+
Export AGENTS.md for any AI coding tool
|
|
147
|
+
`);
|
|
148
|
+
process.exit(0);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── Readline helper ─────────────────────────────────────────────
|
|
152
|
+
let rl;
|
|
153
|
+
|
|
154
|
+
function getRL() {
|
|
155
|
+
if (!rl) {
|
|
156
|
+
rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
157
|
+
}
|
|
158
|
+
return rl;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function ask(question) {
|
|
162
|
+
return new Promise(resolve => {
|
|
163
|
+
getRL().question(question, answer => resolve(answer.trim()));
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function closeRL() {
|
|
168
|
+
if (rl) { rl.close(); rl = null; }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ── Version ─────────────────────────────────────────────────────
|
|
172
|
+
const VERSION = existsSync(SRC.version)
|
|
173
|
+
? readFileSync(SRC.version, 'utf-8').trim()
|
|
174
|
+
: '4.2.0';
|
|
175
|
+
|
|
176
|
+
// ── Target directories based on agent + mode ────────────────────
|
|
177
|
+
function getTargets() {
|
|
178
|
+
const home = homedir();
|
|
179
|
+
const cwd = process.cwd();
|
|
180
|
+
|
|
181
|
+
if (agent === 'claude-code') {
|
|
182
|
+
const base = mode === 'global' ? join(home, '.claude') : join(cwd, '.claude');
|
|
183
|
+
return {
|
|
184
|
+
base,
|
|
185
|
+
commands: join(base, 'commands', 'spartan'),
|
|
186
|
+
router: join(base, 'commands', 'spartan.md'),
|
|
187
|
+
rules: join(base, 'rules'),
|
|
188
|
+
skills: join(base, 'skills'),
|
|
189
|
+
agents: join(base, 'agents'),
|
|
190
|
+
claudeMd: mode === 'global' ? join(home, '.claude', 'CLAUDE.md') : join(cwd, 'CLAUDE.md'),
|
|
191
|
+
packsFile: join(base, '.spartan-packs'),
|
|
192
|
+
versionFile: join(base, '.spartan-version'),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (agent === 'codex') {
|
|
197
|
+
const base = mode === 'global' ? join(home, '.codex') : join(cwd, '.codex');
|
|
198
|
+
return {
|
|
199
|
+
base,
|
|
200
|
+
commands: join(base, 'commands', 'spartan'),
|
|
201
|
+
router: join(base, 'commands', 'spartan.md'),
|
|
202
|
+
rules: join(base, 'rules'),
|
|
203
|
+
skills: join(base, 'skills'),
|
|
204
|
+
agents: join(base, 'agents'),
|
|
205
|
+
claudeMd: mode === 'global' ? join(home, '.codex', 'CLAUDE.md') : join(cwd, 'CLAUDE.md'),
|
|
206
|
+
packsFile: join(base, '.spartan-packs'),
|
|
207
|
+
versionFile: join(base, '.spartan-version'),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// cursor, windsurf, copilot — project-local only
|
|
212
|
+
const rulesDir = {
|
|
213
|
+
cursor: join(cwd, '.cursor', 'rules'),
|
|
214
|
+
windsurf: join(cwd, '.windsurf', 'rules'),
|
|
215
|
+
copilot: join(cwd, '.github', 'instructions'),
|
|
216
|
+
}[agent];
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
base: cwd,
|
|
220
|
+
rules: rulesDir,
|
|
221
|
+
agentsMd: join(cwd, 'AGENTS.md'),
|
|
222
|
+
packsFile: join(cwd, '.spartan-packs'),
|
|
223
|
+
versionFile: null,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ── Helpers ─────────────────────────────────────────────────────
|
|
228
|
+
function ensureDir(dir) {
|
|
229
|
+
mkdirSync(dir, { recursive: true });
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function copyFile(src, dest) {
|
|
233
|
+
ensureDir(dirname(dest));
|
|
234
|
+
const content = readFileSync(src);
|
|
235
|
+
writeFileSync(dest, content);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function copyDir(src, dest) {
|
|
239
|
+
ensureDir(dest);
|
|
240
|
+
cpSync(src, dest, { recursive: true });
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Get the source root for a pack (built-in uses PKG_ROOT, community uses pack-dir). */
|
|
244
|
+
function getPackSource(packName) {
|
|
245
|
+
return externalPackSources[packName] || PKG_ROOT;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Get all items for a category across selected packs, deduplicated. */
|
|
249
|
+
function gatherItems(selectedPacks, category) {
|
|
250
|
+
const seen = new Set();
|
|
251
|
+
const result = [];
|
|
252
|
+
for (const pack of selectedPacks) {
|
|
253
|
+
const def = PACKS[pack];
|
|
254
|
+
if (!def) continue;
|
|
255
|
+
for (const item of def[category]) {
|
|
256
|
+
if (!seen.has(item)) {
|
|
257
|
+
seen.add(item);
|
|
258
|
+
result.push(item);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Like gatherItems but includes the source root for each item (for community pack support). */
|
|
266
|
+
function gatherItemsWithSource(selectedPacks, category) {
|
|
267
|
+
const seen = new Set();
|
|
268
|
+
const result = [];
|
|
269
|
+
for (const pack of selectedPacks) {
|
|
270
|
+
const def = PACKS[pack];
|
|
271
|
+
if (!def) continue;
|
|
272
|
+
const srcRoot = getPackSource(pack);
|
|
273
|
+
for (const item of def[category]) {
|
|
274
|
+
if (!seen.has(item)) {
|
|
275
|
+
seen.add(item);
|
|
276
|
+
result.push({ item, srcRoot });
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ── Pack selection (grouped menu) ───────────────────────────────
|
|
284
|
+
async function selectPacks(targets) {
|
|
285
|
+
// --all flag
|
|
286
|
+
if (installAll) {
|
|
287
|
+
const all = PACK_ORDER.filter(p => !PACKS[p].hidden && !PACKS[p].comingSoon);
|
|
288
|
+
return resolveDeps(all, manifests);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// --packs flag
|
|
292
|
+
if (packsArg) {
|
|
293
|
+
let requested = packsArg.split(',').map(s => s.trim()).filter(Boolean);
|
|
294
|
+
const { resolved: aliased, warnings } = resolveAliases(requested);
|
|
295
|
+
for (const w of warnings) console.log(` ${yellow('!')} ${w}`);
|
|
296
|
+
return resolveDeps(aliased, manifests);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// --auto flag: detect tech stack
|
|
300
|
+
if (autoDetect) {
|
|
301
|
+
const cwd = process.cwd();
|
|
302
|
+
console.log(`\n ${blue('Scanning')} ${dim(cwd)} ${blue('for tech stack...')}\n`);
|
|
303
|
+
const { detected, comingSoon } = detectStacks(cwd);
|
|
304
|
+
|
|
305
|
+
if (detected.length > 0) {
|
|
306
|
+
console.log(` ${bold('Detected stacks:')}`);
|
|
307
|
+
for (const d of detected) {
|
|
308
|
+
console.log(` ${green('✓')} ${bold(d.pack)} ${dim(`(${d.reason})`)}`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (comingSoon.length > 0) {
|
|
312
|
+
console.log('');
|
|
313
|
+
for (const d of comingSoon) {
|
|
314
|
+
console.log(` ${yellow('~')} ${d.pack} ${dim(`(${d.reason})`)} ${dim('— coming soon, skipped')}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
console.log('');
|
|
318
|
+
|
|
319
|
+
const packNames = detected.map(d => d.pack);
|
|
320
|
+
const confirm = await ask(` Install ${bold(packNames.join(' + '))}? [Y/n]: `);
|
|
321
|
+
if (confirm !== 'n' && confirm !== 'N') {
|
|
322
|
+
return resolveDeps(packNames, manifests);
|
|
323
|
+
}
|
|
324
|
+
// User said no — fall through to interactive menu
|
|
325
|
+
console.log('');
|
|
326
|
+
} else {
|
|
327
|
+
console.log(` ${dim('No stacks detected.')}`);
|
|
328
|
+
if (comingSoon.length > 0) {
|
|
329
|
+
for (const d of comingSoon) {
|
|
330
|
+
console.log(` ${yellow('~')} ${d.pack} ${dim(`(${d.reason})`)} ${dim('— coming soon')}`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
console.log(` ${dim('Falling back to interactive menu...')}\n`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Check saved packs
|
|
338
|
+
if (existsSync(targets.packsFile)) {
|
|
339
|
+
let saved = readFileSync(targets.packsFile, 'utf-8').trim().split('\n').filter(Boolean);
|
|
340
|
+
const { resolved: aliased, warnings } = resolveAliases(saved);
|
|
341
|
+
for (const w of warnings) console.log(` ${yellow('!')} ${w}`);
|
|
342
|
+
saved = aliased;
|
|
343
|
+
|
|
344
|
+
if (saved.length > 0) {
|
|
345
|
+
console.log(`\n ${cyan('Previously installed packs:')} ${saved.filter(p => !PACKS[p]?.hidden).join(', ')}`);
|
|
346
|
+
const reuse = await ask(' Re-install same packs? [Y/n]: ');
|
|
347
|
+
if (reuse !== 'n' && reuse !== 'N') {
|
|
348
|
+
return resolveDeps(saved, manifests);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Interactive grouped menu
|
|
354
|
+
console.log(`\n ${bold('Choose your packs:')}\n`);
|
|
355
|
+
|
|
356
|
+
const categories = {};
|
|
357
|
+
const selectablePacks = [];
|
|
358
|
+
for (const name of PACK_ORDER) {
|
|
359
|
+
const p = PACKS[name];
|
|
360
|
+
if (p.hidden) continue;
|
|
361
|
+
const cat = p.category || 'Other';
|
|
362
|
+
if (!categories[cat]) categories[cat] = [];
|
|
363
|
+
categories[cat].push(name);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
for (const [cat, packs] of Object.entries(categories)) {
|
|
367
|
+
console.log(` ${bold(cat + ':')}`);
|
|
368
|
+
for (const name of packs) {
|
|
369
|
+
const p = PACKS[name];
|
|
370
|
+
const tag = name === 'core' ? ` ${green('(always included)')}`
|
|
371
|
+
: p.comingSoon ? ` ${dim('(coming soon)')}`
|
|
372
|
+
: '';
|
|
373
|
+
console.log(` ${bold(name)} — ${p.description}${tag}`);
|
|
374
|
+
if (!p.comingSoon && name !== 'core') {
|
|
375
|
+
selectablePacks.push(name);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
console.log('');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const allChoice = await ask(' Install all packs? [Y/n]: ');
|
|
382
|
+
|
|
383
|
+
if (allChoice !== 'n' && allChoice !== 'N') {
|
|
384
|
+
return resolveDeps(selectablePacks, manifests);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Ask about each optional pack
|
|
388
|
+
const selected = [];
|
|
389
|
+
for (const pack of selectablePacks) {
|
|
390
|
+
const def = PACKS[pack];
|
|
391
|
+
const choice = await ask(` ${bold(pack)} — ${def.description}? [y/N]: `);
|
|
392
|
+
if (choice === 'y' || choice === 'Y') {
|
|
393
|
+
selected.push(pack);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return resolveDeps(selected, manifests);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// ── Install for claude-code / codex ─────────────────────────────
|
|
401
|
+
async function installFull() {
|
|
402
|
+
const targets = getTargets();
|
|
403
|
+
const selectedPacks = await selectPacks(targets);
|
|
404
|
+
|
|
405
|
+
// Show user-facing packs (hide hidden deps)
|
|
406
|
+
const userPacks = selectedPacks.filter(p => !PACKS[p]?.hidden);
|
|
407
|
+
const hiddenPacks = selectedPacks.filter(p => PACKS[p]?.hidden);
|
|
408
|
+
console.log(`\n ${green('Selected:')} ${bold(userPacks.join(' '))}`);
|
|
409
|
+
if (hiddenPacks.length > 0) {
|
|
410
|
+
console.log(` ${dim('Auto-included:')} ${dim(hiddenPacks.join(' '))}`);
|
|
411
|
+
}
|
|
412
|
+
console.log('');
|
|
413
|
+
|
|
414
|
+
// 1) Assemble & install CLAUDE.md
|
|
415
|
+
console.log(`${blue('[1/6]')} ${bold('Assembling CLAUDE.md...')}`);
|
|
416
|
+
|
|
417
|
+
if (existsSync(targets.claudeMd)) {
|
|
418
|
+
const backupPath = `${targets.claudeMd}.${Date.now()}.bak`;
|
|
419
|
+
copyFile(targets.claudeMd, backupPath);
|
|
420
|
+
console.log(` ${dim('Backed up existing CLAUDE.md')}`);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const assembled = assembleCLAUDEmd(SRC.claudeMd, selectedPacks, PACKS);
|
|
424
|
+
ensureDir(dirname(targets.claudeMd));
|
|
425
|
+
writeFileSync(targets.claudeMd, assembled, 'utf-8');
|
|
426
|
+
|
|
427
|
+
const sectionCount = 2 + 1 + gatherItems(selectedPacks, 'claudeSections').length;
|
|
428
|
+
console.log(` ${green('+')} CLAUDE.md assembled (${sectionCount} sections)\n`);
|
|
429
|
+
|
|
430
|
+
// 2) Commands
|
|
431
|
+
console.log(`${blue('[2/6]')} ${bold('Installing commands...')}`);
|
|
432
|
+
ensureDir(targets.commands);
|
|
433
|
+
let cmdCount = 0;
|
|
434
|
+
|
|
435
|
+
// Smart router (always)
|
|
436
|
+
if (existsSync(SRC.router)) {
|
|
437
|
+
copyFile(SRC.router, targets.router);
|
|
438
|
+
console.log(` ${green('+')} /spartan (smart router)`);
|
|
439
|
+
cmdCount++;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const selectedCommands = gatherItemsWithSource(selectedPacks, 'commands');
|
|
443
|
+
for (const { item: cmd, srcRoot } of selectedCommands) {
|
|
444
|
+
const src = join(srcRoot, 'commands', 'spartan', `${cmd}.md`);
|
|
445
|
+
if (existsSync(src)) {
|
|
446
|
+
copyFile(src, join(targets.commands, `${cmd}.md`));
|
|
447
|
+
console.log(` ${green('+')} /spartan:${cmd}`);
|
|
448
|
+
cmdCount++;
|
|
449
|
+
} else {
|
|
450
|
+
console.log(` ${yellow('!')} /spartan:${cmd} (not found, skipped)`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
console.log(` ${bold(cmdCount + ' commands')} installed\n`);
|
|
454
|
+
|
|
455
|
+
// 3) Rules (now with subdirectory structure)
|
|
456
|
+
const rulesWithSource = gatherItemsWithSource(selectedPacks, 'rules');
|
|
457
|
+
if (rulesWithSource.length > 0) {
|
|
458
|
+
console.log(`${blue('[3/6]')} ${bold('Installing rules...')}`);
|
|
459
|
+
let ruleCount = 0;
|
|
460
|
+
|
|
461
|
+
for (const { item: rule, srcRoot } of rulesWithSource) {
|
|
462
|
+
const src = join(srcRoot, 'rules', rule);
|
|
463
|
+
const dest = join(targets.rules, rule);
|
|
464
|
+
if (existsSync(src)) {
|
|
465
|
+
copyFile(src, dest);
|
|
466
|
+
console.log(` ${green('+')} ${rule}`);
|
|
467
|
+
ruleCount++;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
console.log(` ${bold(ruleCount + ' rules')} installed\n`);
|
|
471
|
+
} else {
|
|
472
|
+
console.log(`${blue('[3/6]')} ${bold('Rules')} — ${dim('no rule packs selected, skipping')}\n`);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// 4) Skills
|
|
476
|
+
const skillsWithSource = gatherItemsWithSource(selectedPacks, 'skills');
|
|
477
|
+
if (skillsWithSource.length > 0) {
|
|
478
|
+
console.log(`${blue('[4/6]')} ${bold('Installing skills...')}`);
|
|
479
|
+
ensureDir(targets.skills);
|
|
480
|
+
let skillCount = 0;
|
|
481
|
+
|
|
482
|
+
for (const { item: skill, srcRoot } of skillsWithSource) {
|
|
483
|
+
const src = join(srcRoot, 'skills', skill);
|
|
484
|
+
if (existsSync(src)) {
|
|
485
|
+
copyDir(src, join(targets.skills, skill));
|
|
486
|
+
console.log(` ${green('+')} ${skill}`);
|
|
487
|
+
skillCount++;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
console.log(` ${bold(skillCount + ' skills')} installed\n`);
|
|
491
|
+
} else {
|
|
492
|
+
console.log(`${blue('[4/6]')} ${bold('Skills')} — ${dim('no skill packs selected, skipping')}\n`);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// 5) Agents
|
|
496
|
+
const agentsWithSource = gatherItemsWithSource(selectedPacks, 'agents');
|
|
497
|
+
if (agentsWithSource.length > 0) {
|
|
498
|
+
console.log(`${blue('[5/6]')} ${bold('Installing agents...')}`);
|
|
499
|
+
ensureDir(targets.agents);
|
|
500
|
+
let agentCount = 0;
|
|
501
|
+
|
|
502
|
+
for (const { item: agentFile, srcRoot } of agentsWithSource) {
|
|
503
|
+
const src = join(srcRoot, 'agents', agentFile);
|
|
504
|
+
if (existsSync(src)) {
|
|
505
|
+
copyFile(src, join(targets.agents, agentFile));
|
|
506
|
+
console.log(` ${green('+')} ${agentFile.replace('.md', '')}`);
|
|
507
|
+
agentCount++;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
console.log(` ${bold(agentCount + ' agents')} installed\n`);
|
|
511
|
+
} else {
|
|
512
|
+
console.log(`${blue('[5/6]')} ${bold('Agents')} — ${dim('no agent packs selected, skipping')}\n`);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// 6) Generate .spartan/config.yaml from best matching profile
|
|
516
|
+
const configDir = mode === 'global'
|
|
517
|
+
? join(homedir(), '.spartan')
|
|
518
|
+
: join(process.cwd(), '.spartan');
|
|
519
|
+
const configPath = join(configDir, 'config.yaml');
|
|
520
|
+
|
|
521
|
+
if (!existsSync(configPath)) {
|
|
522
|
+
const profile = pickProfile(selectedPacks);
|
|
523
|
+
const profileSrc = join(SRC.profiles, `${profile}.yaml`);
|
|
524
|
+
|
|
525
|
+
if (existsSync(profileSrc)) {
|
|
526
|
+
ensureDir(configDir);
|
|
527
|
+
copyFile(profileSrc, configPath);
|
|
528
|
+
console.log(`${blue('[6/6]')} ${bold('Generated .spartan/config.yaml')} from ${cyan(profile)} profile`);
|
|
529
|
+
console.log(` ${dim('Edit this file to customize rules, review stages, and build commands')}\n`);
|
|
530
|
+
}
|
|
531
|
+
} else {
|
|
532
|
+
console.log(`${blue('[6/6]')} ${bold('.spartan/config.yaml')} — ${dim('already exists, kept as-is')}\n`);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Save pack selection + version
|
|
536
|
+
writeFileSync(targets.packsFile, selectedPacks.join('\n') + '\n', 'utf-8');
|
|
537
|
+
if (targets.versionFile) {
|
|
538
|
+
writeFileSync(targets.versionFile, VERSION + '\n', 'utf-8');
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return selectedPacks;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// ── Profile picker ─────────────────────────────────────────────
|
|
545
|
+
// Maps selected packs to the best matching stack profile.
|
|
546
|
+
function pickProfile(selectedPacks) {
|
|
547
|
+
const packSet = new Set(selectedPacks);
|
|
548
|
+
|
|
549
|
+
// Check for specific stack packs first
|
|
550
|
+
if (packSet.has('backend-micronaut') && packSet.has('frontend-react')) {
|
|
551
|
+
return 'kotlin-micronaut'; // full-stack defaults to backend profile
|
|
552
|
+
}
|
|
553
|
+
if (packSet.has('backend-micronaut')) return 'kotlin-micronaut';
|
|
554
|
+
if (packSet.has('frontend-react')) return 'react-nextjs';
|
|
555
|
+
if (packSet.has('backend-nodejs')) return 'typescript-node';
|
|
556
|
+
if (packSet.has('backend-python')) return 'python-fastapi';
|
|
557
|
+
|
|
558
|
+
// No stack-specific pack → use custom
|
|
559
|
+
return 'custom';
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// ── Install for cursor / windsurf / copilot ─────────────────────
|
|
563
|
+
async function installRulesOnly() {
|
|
564
|
+
const targets = getTargets();
|
|
565
|
+
|
|
566
|
+
console.log(`\n Agent: ${bold(agent)}`);
|
|
567
|
+
console.log(` This installs ${bold('rules + AGENTS.md')} only.`);
|
|
568
|
+
console.log(` Slash commands are Claude Code specific.\n`);
|
|
569
|
+
|
|
570
|
+
// Ask which rule packs
|
|
571
|
+
console.log(` ${bold('Which rule packs do you need?')}\n`);
|
|
572
|
+
|
|
573
|
+
const rulePacks = PACK_ORDER.filter(p => PACKS[p].rules.length > 0 && !PACKS[p].hidden);
|
|
574
|
+
for (const pack of rulePacks) {
|
|
575
|
+
const ruleNames = PACKS[pack].rules.map(r => r.replace(/.*\//, '').replace('.md', '')).join(', ');
|
|
576
|
+
console.log(` ${bold(pack)} — ${dim(ruleNames)}`);
|
|
577
|
+
}
|
|
578
|
+
console.log('');
|
|
579
|
+
|
|
580
|
+
const allChoice = await ask(' Install all rule packs? [Y/n]: ');
|
|
581
|
+
let selectedPacks;
|
|
582
|
+
|
|
583
|
+
if (allChoice !== 'n' && allChoice !== 'N') {
|
|
584
|
+
selectedPacks = resolveDeps(rulePacks, manifests);
|
|
585
|
+
} else {
|
|
586
|
+
const picks = [];
|
|
587
|
+
for (const pack of rulePacks) {
|
|
588
|
+
const choice = await ask(` ${bold(pack)}? [y/N]: `);
|
|
589
|
+
if (choice === 'y' || choice === 'Y') {
|
|
590
|
+
picks.push(pack);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
selectedPacks = resolveDeps(picks, manifests);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Install rules
|
|
597
|
+
const selectedRules = gatherItems(selectedPacks, 'rules');
|
|
598
|
+
if (selectedRules.length > 0) {
|
|
599
|
+
console.log(`\n${blue('[1/2]')} ${bold('Installing rules...')}`);
|
|
600
|
+
ensureDir(targets.rules);
|
|
601
|
+
let count = 0;
|
|
602
|
+
for (const rule of selectedRules) {
|
|
603
|
+
const src = join(SRC.rules, rule);
|
|
604
|
+
const dest = join(targets.rules, rule);
|
|
605
|
+
if (existsSync(src)) {
|
|
606
|
+
copyFile(src, dest);
|
|
607
|
+
console.log(` ${green('+')} ${rule}`);
|
|
608
|
+
count++;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
console.log(` ${bold(count + ' rules')} installed\n`);
|
|
612
|
+
} else {
|
|
613
|
+
console.log(`\n${blue('[1/2]')} ${bold('Rules')} — ${dim('no rule packs selected')}\n`);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Install AGENTS.md — assembled from pack sections + agents
|
|
617
|
+
console.log(`${blue('[2/2]')} ${bold('Installing AGENTS.md...')}`);
|
|
618
|
+
|
|
619
|
+
if (targets.agentsMd) {
|
|
620
|
+
const agentsContent = assembleAGENTSmd(SRC.claudeMd, SRC.agents, selectedPacks, PACKS);
|
|
621
|
+
writeFileSync(targets.agentsMd, agentsContent, 'utf-8');
|
|
622
|
+
console.log(` ${green('+')} AGENTS.md\n`);
|
|
623
|
+
} else {
|
|
624
|
+
console.log(` ${dim('No AGENTS.md target')}\n`);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Save selection
|
|
628
|
+
if (targets.packsFile) {
|
|
629
|
+
writeFileSync(targets.packsFile, selectedPacks.join('\n') + '\n', 'utf-8');
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
return selectedPacks;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// ── Main ────────────────────────────────────────────────────────
|
|
636
|
+
async function main() {
|
|
637
|
+
console.log('');
|
|
638
|
+
console.log(`${bold('================================================')}`);
|
|
639
|
+
console.log(`${bold(' Spartan AI Toolkit')} ${dim(`v${VERSION}`)}`);
|
|
640
|
+
console.log(`${bold('================================================')}`);
|
|
641
|
+
console.log('');
|
|
642
|
+
console.log(` Agent: ${bold(agent)} Mode: ${bold(mode)}`);
|
|
643
|
+
|
|
644
|
+
// Check Node version
|
|
645
|
+
const nodeVer = parseInt(process.versions.node.split('.')[0], 10);
|
|
646
|
+
if (nodeVer < 18) {
|
|
647
|
+
console.error(`\n ${C.red}Node.js ${process.versions.node} is too old. Need >= 18.${C.reset}\n`);
|
|
648
|
+
process.exit(1);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// Load community packs if --pack-dir is set
|
|
652
|
+
if (packDirArg) {
|
|
653
|
+
const packDirPath = pathResolve(process.cwd(), packDirArg);
|
|
654
|
+
const builtinNames = new Set(manifests.keys());
|
|
655
|
+
console.log(`\n ${blue('Loading community packs from')} ${dim(packDirPath)}`);
|
|
656
|
+
const { loaded, errors } = loadExternalPacks(packDirPath, builtinNames);
|
|
657
|
+
|
|
658
|
+
if (errors.length > 0) {
|
|
659
|
+
for (const err of errors) {
|
|
660
|
+
console.log(` ${yellow('!')} ${err}`);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
if (loaded.size > 0) {
|
|
665
|
+
for (const [name, manifest] of loaded) {
|
|
666
|
+
manifests.set(name, manifest);
|
|
667
|
+
externalPackSources[name] = packDirPath;
|
|
668
|
+
// Add to PACKS and PACK_ORDER so menus and install work
|
|
669
|
+
PACKS[name] = {
|
|
670
|
+
description: manifest.description,
|
|
671
|
+
category: manifest.category || 'Community',
|
|
672
|
+
priority: manifest.priority ?? 500,
|
|
673
|
+
hidden: manifest.hidden || false,
|
|
674
|
+
comingSoon: manifest['coming-soon'] || false,
|
|
675
|
+
depends: manifest.depends || [],
|
|
676
|
+
commands: manifest.commands || [],
|
|
677
|
+
rules: manifest.rules || [],
|
|
678
|
+
skills: manifest.skills || [],
|
|
679
|
+
agents: manifest.agents || [],
|
|
680
|
+
claudeSections: manifest['claude-sections'] || [],
|
|
681
|
+
};
|
|
682
|
+
PACK_ORDER.push(name);
|
|
683
|
+
}
|
|
684
|
+
// Re-sort PACK_ORDER by priority
|
|
685
|
+
PACK_ORDER.sort((a, b) => (PACKS[a]?.priority ?? 999) - (PACKS[b]?.priority ?? 999));
|
|
686
|
+
|
|
687
|
+
const names = [...loaded.keys()].join(', ');
|
|
688
|
+
console.log(` ${green('+')} Loaded: ${bold(names)}\n`);
|
|
689
|
+
} else {
|
|
690
|
+
console.log(` ${dim('No valid community packs found')}\n`);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
let selectedPacks;
|
|
695
|
+
|
|
696
|
+
try {
|
|
697
|
+
if (agent === 'claude-code' || agent === 'codex') {
|
|
698
|
+
selectedPacks = await installFull();
|
|
699
|
+
} else if (['cursor', 'windsurf', 'copilot'].includes(agent)) {
|
|
700
|
+
selectedPacks = await installRulesOnly();
|
|
701
|
+
} else {
|
|
702
|
+
console.error(`\n ${C.red}Unknown agent: ${agent}${C.reset}`);
|
|
703
|
+
console.error(` Supported: claude-code, cursor, windsurf, codex, copilot\n`);
|
|
704
|
+
process.exit(1);
|
|
705
|
+
}
|
|
706
|
+
} finally {
|
|
707
|
+
closeRL();
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Generate Codex-compatible skills for codex agent
|
|
711
|
+
if (agent === 'codex') {
|
|
712
|
+
console.log(`${blue('[+]')} ${bold('Generating Codex skills...')}`);
|
|
713
|
+
try {
|
|
714
|
+
const { execSync } = await import('child_process');
|
|
715
|
+
const repoRoot = join(PKG_ROOT, '..');
|
|
716
|
+
execSync('node toolkit/scripts/gen-codex-skills.js', { cwd: repoRoot, stdio: 'pipe' });
|
|
717
|
+
const srcAgents = join(repoRoot, '.agents', 'skills');
|
|
718
|
+
const targets = getTargets();
|
|
719
|
+
const destBase = join(targets.base, '..', '.agents', 'skills');
|
|
720
|
+
if (existsSync(srcAgents)) {
|
|
721
|
+
mkdirSync(destBase, { recursive: true });
|
|
722
|
+
const dirs = readdirSync(srcAgents, { withFileTypes: true }).filter(d => d.isDirectory());
|
|
723
|
+
for (const d of dirs) {
|
|
724
|
+
const destAgentsDir = join(destBase, d.name, 'agents');
|
|
725
|
+
mkdirSync(destAgentsDir, { recursive: true });
|
|
726
|
+
const skillSrc = join(srcAgents, d.name, 'SKILL.md');
|
|
727
|
+
const yamlSrc = join(srcAgents, d.name, 'agents', 'openai.yaml');
|
|
728
|
+
if (existsSync(skillSrc)) copyFileSync(skillSrc, join(destBase, d.name, 'SKILL.md'));
|
|
729
|
+
if (existsSync(yamlSrc)) copyFileSync(yamlSrc, join(destAgentsDir, 'openai.yaml'));
|
|
730
|
+
}
|
|
731
|
+
console.log(` ${green('+')} ${dirs.length} Codex skills generated in .agents/skills/\n`);
|
|
732
|
+
}
|
|
733
|
+
} catch (e) {
|
|
734
|
+
console.log(` ${C.yellow}Warning: Codex skill generation failed — ${e.message}${C.reset}\n`);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// Export AGENTS.md alongside normal install when --format=agents-md
|
|
739
|
+
if (format === 'agents-md' && (agent === 'claude-code' || agent === 'codex')) {
|
|
740
|
+
const cwd = process.cwd();
|
|
741
|
+
const agentsMdPath = join(cwd, 'AGENTS.md');
|
|
742
|
+
console.log(`${blue('[+]')} ${bold('Exporting AGENTS.md for cross-tool use...')}`);
|
|
743
|
+
const agentsContent = assembleAGENTSmd(SRC.claudeMd, SRC.agents, selectedPacks, PACKS);
|
|
744
|
+
writeFileSync(agentsMdPath, agentsContent, 'utf-8');
|
|
745
|
+
console.log(` ${green('+')} AGENTS.md (works with Cursor, Copilot, Windsurf, Codex, and 20+ tools)\n`);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// Success
|
|
749
|
+
const userPacks = selectedPacks.filter(p => !PACKS[p]?.hidden);
|
|
750
|
+
console.log(`${bold(green('================================================'))}`);
|
|
751
|
+
console.log(`${bold(green(' Setup done!'))} ${dim(`v${VERSION}`)}`);
|
|
752
|
+
console.log(`${bold(green('================================================'))}`);
|
|
753
|
+
console.log('');
|
|
754
|
+
console.log(` Installed packs: ${cyan(userPacks.join(', '))}`);
|
|
755
|
+
console.log('');
|
|
756
|
+
|
|
757
|
+
if (agent === 'claude-code' || agent === 'codex') {
|
|
758
|
+
console.log(` ${bold('Next steps:')}`);
|
|
759
|
+
console.log('');
|
|
760
|
+
console.log(` 1. Open any project folder and type:`);
|
|
761
|
+
console.log(` ${cyan('/spartan')}`);
|
|
762
|
+
console.log('');
|
|
763
|
+
console.log(` 2. To change packs later, run again:`);
|
|
764
|
+
console.log(` ${cyan('npx @c0x12c/ai-toolkit@latest')}`);
|
|
765
|
+
console.log('');
|
|
766
|
+
} else {
|
|
767
|
+
console.log(` ${bold('Next steps:')}`);
|
|
768
|
+
console.log('');
|
|
769
|
+
console.log(` 1. Rules are in your ${agent} rules folder.`);
|
|
770
|
+
console.log(` 2. AGENTS.md is in your project root.`);
|
|
771
|
+
console.log(` 3. Slash commands need Claude Code — they don't work in ${agent}.`);
|
|
772
|
+
console.log('');
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
main().catch(err => {
|
|
777
|
+
console.error(`\n ${C.red}Error: ${err.message}${C.reset}\n`);
|
|
778
|
+
closeRL();
|
|
779
|
+
process.exit(1);
|
|
780
|
+
});
|