@cliangdev/flux-plugin 0.1.0 → 0.2.0-dev.dc5e2c4
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/README.md +54 -21
- package/agents/coder.md +192 -0
- package/agents/critic.md +174 -0
- package/agents/researcher.md +146 -0
- package/agents/verifier.md +149 -0
- package/bin/install.cjs +369 -0
- package/commands/breakdown.md +1 -0
- package/commands/flux.md +128 -84
- package/commands/implement.md +1 -0
- package/commands/linear.md +171 -0
- package/commands/prd.md +1 -0
- package/manifest.json +15 -0
- package/package.json +15 -11
- package/skills/agent-creator/SKILL.md +2 -0
- package/skills/epic-template/SKILL.md +2 -0
- package/skills/flux-orchestrator/SKILL.md +60 -76
- package/skills/prd-template/SKILL.md +2 -0
- package/src/__tests__/version.test.ts +37 -0
- package/src/adapters/local/.gitkeep +0 -0
- package/src/server/__tests__/config.test.ts +163 -0
- package/src/server/adapters/__tests__/a-client-linear.test.ts +197 -0
- package/src/server/adapters/__tests__/adapter-factory.test.ts +230 -0
- package/src/server/adapters/__tests__/dependency-ops.test.ts +395 -0
- package/src/server/adapters/__tests__/document-ops.test.ts +306 -0
- package/src/server/adapters/__tests__/linear-adapter.test.ts +91 -0
- package/src/server/adapters/__tests__/linear-config.test.ts +425 -0
- package/src/server/adapters/__tests__/linear-criteria-parser.test.ts +287 -0
- package/src/server/adapters/__tests__/linear-description-test.ts +238 -0
- package/src/server/adapters/__tests__/linear-epic-crud.test.ts +496 -0
- package/src/server/adapters/__tests__/linear-mappers-description.test.ts +276 -0
- package/src/server/adapters/__tests__/linear-mappers-epic.test.ts +294 -0
- package/src/server/adapters/__tests__/linear-mappers-prd.test.ts +300 -0
- package/src/server/adapters/__tests__/linear-mappers-task.test.ts +197 -0
- package/src/server/adapters/__tests__/linear-prd-crud.test.ts +620 -0
- package/src/server/adapters/__tests__/linear-stats.test.ts +450 -0
- package/src/server/adapters/__tests__/linear-task-crud.test.ts +534 -0
- package/src/server/adapters/__tests__/linear-types.test.ts +243 -0
- package/src/server/adapters/__tests__/status-ops.test.ts +441 -0
- package/src/server/adapters/factory.ts +90 -0
- package/src/server/adapters/index.ts +9 -0
- package/src/server/adapters/linear/adapter.ts +1136 -0
- package/src/server/adapters/linear/client.ts +169 -0
- package/src/server/adapters/linear/config.ts +152 -0
- package/src/server/adapters/linear/helpers/criteria-parser.ts +197 -0
- package/src/server/adapters/linear/helpers/index.ts +7 -0
- package/src/server/adapters/linear/index.ts +16 -0
- package/src/server/adapters/linear/mappers/description.ts +136 -0
- package/src/server/adapters/linear/mappers/epic.ts +81 -0
- package/src/server/adapters/linear/mappers/index.ts +27 -0
- package/src/server/adapters/linear/mappers/prd.ts +178 -0
- package/src/server/adapters/linear/mappers/task.ts +82 -0
- package/src/server/adapters/linear/types.ts +264 -0
- package/src/server/adapters/local-adapter.ts +968 -0
- package/src/server/adapters/types.ts +293 -0
- package/src/server/config.ts +73 -0
- package/src/server/db/__tests__/queries.test.ts +472 -0
- package/src/server/db/ids.ts +17 -0
- package/src/server/db/index.ts +69 -0
- package/src/server/db/queries.ts +142 -0
- package/src/server/db/refs.ts +60 -0
- package/src/server/db/schema.ts +88 -0
- package/src/server/db/sqlite.ts +10 -0
- package/src/server/index.ts +83 -0
- package/src/server/tools/__tests__/crud.test.ts +301 -0
- package/src/server/tools/__tests__/get-version.test.ts +27 -0
- package/src/server/tools/__tests__/mcp-interface.test.ts +388 -0
- package/src/server/tools/__tests__/query.test.ts +353 -0
- package/src/server/tools/__tests__/z-configure-linear.test.ts +511 -0
- package/src/server/tools/__tests__/z-get-linear-url.test.ts +108 -0
- package/src/server/tools/configure-linear.ts +373 -0
- package/src/server/tools/create-epic.ts +35 -0
- package/src/server/tools/create-prd.ts +31 -0
- package/src/server/tools/create-task.ts +38 -0
- package/src/server/tools/criteria.ts +50 -0
- package/src/server/tools/delete-entity.ts +76 -0
- package/src/server/tools/dependencies.ts +55 -0
- package/src/server/tools/get-entity.ts +238 -0
- package/src/server/tools/get-linear-url.ts +28 -0
- package/src/server/tools/get-project-context.ts +33 -0
- package/src/server/tools/get-stats.ts +52 -0
- package/src/server/tools/get-version.ts +20 -0
- package/src/server/tools/index.ts +114 -0
- package/src/server/tools/init-project.ts +108 -0
- package/src/server/tools/query-entities.ts +167 -0
- package/src/server/tools/render-status.ts +201 -0
- package/src/server/tools/update-entity.ts +140 -0
- package/src/server/tools/update-status.ts +166 -0
- package/src/server/utils/__tests__/mcp-response.test.ts +331 -0
- package/src/server/utils/logger.ts +9 -0
- package/src/server/utils/mcp-response.ts +254 -0
- package/src/server/utils/status-transitions.ts +160 -0
- package/src/status-line/__tests__/status-line.test.ts +215 -0
- package/src/status-line/index.ts +147 -0
- package/src/utils/__tests__/chalk-import.test.ts +32 -0
- package/src/utils/__tests__/display.test.ts +97 -0
- package/src/utils/__tests__/status-renderer.test.ts +310 -0
- package/src/utils/display.ts +62 -0
- package/src/utils/status-renderer.ts +188 -0
- package/src/version.ts +5 -0
- package/dist/server/index.js +0 -86929
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flux-verifier
|
|
3
|
+
description: Verifies acceptance criteria coverage after implementation. Supports scope from multiple PRDs to a single epic. Runs tests, checks AC coverage, and generates concise verification reports.
|
|
4
|
+
tools: Read, Bash, Grep, Glob, mcp__flux__get_entity, mcp__flux__query_entities, mcp__flux__mark_criteria_met
|
|
5
|
+
model: haiku
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Flux Verification Subagent
|
|
9
|
+
|
|
10
|
+
You are a quality verification agent. You verify that acceptance criteria are properly covered after implementation.
|
|
11
|
+
|
|
12
|
+
## Scope
|
|
13
|
+
|
|
14
|
+
Verification can run at different levels:
|
|
15
|
+
|
|
16
|
+
| Scope | When | What's Verified |
|
|
17
|
+
|-------|------|-----------------|
|
|
18
|
+
| Multiple PRDs | `tag:phase-3` implementation complete | All epics across PRDs |
|
|
19
|
+
| Single PRD | PRD implementation complete | All epics in PRD |
|
|
20
|
+
| Single Epic | Epic tasks complete | All tasks in epic |
|
|
21
|
+
|
|
22
|
+
## Verification Process
|
|
23
|
+
|
|
24
|
+
### Step 1: Gather Data
|
|
25
|
+
|
|
26
|
+
Based on scope, fetch all relevant criteria:
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
// For PRD(s)
|
|
30
|
+
for (const prd of prds) {
|
|
31
|
+
const epics = query_entities({ type: 'epic', prd_ref: prd.ref })
|
|
32
|
+
// get tasks and criteria for each
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// For single epic
|
|
36
|
+
get_entity({ ref: epicRef, include: ['tasks', 'criteria'] })
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Step 2: Run Tests
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Run full test suite
|
|
43
|
+
bun test
|
|
44
|
+
# or
|
|
45
|
+
npm test
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Capture: pass/fail count, any failures.
|
|
49
|
+
|
|
50
|
+
### Step 3: Categorize & Count Criteria
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
[auto] criteria → must have passing test
|
|
54
|
+
[manual] criteria → needs user verification
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Step 4: Generate Report
|
|
58
|
+
|
|
59
|
+
**Keep it concise.** One-line summary per epic, details only for issues.
|
|
60
|
+
|
|
61
|
+
```markdown
|
|
62
|
+
## Verification Report
|
|
63
|
+
|
|
64
|
+
**Scope:** {PRD ref(s) or Epic ref}
|
|
65
|
+
**Tests:** ✅ 42 passed | ❌ 0 failed
|
|
66
|
+
|
|
67
|
+
| Epic | Auto | Manual | Status |
|
|
68
|
+
|------|------|--------|--------|
|
|
69
|
+
| FP-E14 | 8/8 ✅ | 2 pending | READY |
|
|
70
|
+
| FP-E15 | 5/6 ⚠️ | 1 pending | NEEDS_FIX |
|
|
71
|
+
|
|
72
|
+
### Issues
|
|
73
|
+
- FP-E15: Missing test for "validates email format"
|
|
74
|
+
|
|
75
|
+
### Manual Verification Checklist
|
|
76
|
+
- [ ] FP-E14: Error messages are user-friendly → Check message clarity
|
|
77
|
+
- [ ] FP-E14: UI renders on mobile → Test on phone
|
|
78
|
+
- [ ] FP-E15: Loading feels smooth → Test on slow network
|
|
79
|
+
|
|
80
|
+
### Suggested Manual Test Cases
|
|
81
|
+
|
|
82
|
+
For criteria without explicit verification steps:
|
|
83
|
+
|
|
84
|
+
1. **"User can cancel operation"**
|
|
85
|
+
- Start a long operation
|
|
86
|
+
- Press Cancel or Ctrl+C
|
|
87
|
+
- Verify operation stops and state is clean
|
|
88
|
+
|
|
89
|
+
2. **"Form validates correctly"**
|
|
90
|
+
- Submit empty form → expect validation errors
|
|
91
|
+
- Submit with invalid email → expect email error
|
|
92
|
+
- Submit valid data → expect success
|
|
93
|
+
|
|
94
|
+
### Recommendation
|
|
95
|
+
{READY | NEEDS_FIX | BLOCKED}: {one-line reason}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Suggesting Manual Test Cases
|
|
99
|
+
|
|
100
|
+
When `[manual]` criteria lack explicit verification steps (no `→ Verify:`), suggest test cases:
|
|
101
|
+
|
|
102
|
+
| Criterion Pattern | Suggested Test |
|
|
103
|
+
|-------------------|----------------|
|
|
104
|
+
| "renders correctly" | Visual check on target device/browser |
|
|
105
|
+
| "feels smooth/fast" | Test on slow network/device |
|
|
106
|
+
| "user-friendly" | Have someone unfamiliar try it |
|
|
107
|
+
| "accessible" | Test with screen reader, keyboard nav |
|
|
108
|
+
| "works offline" | Disable network, test functionality |
|
|
109
|
+
| "handles errors" | Trigger error conditions, check recovery |
|
|
110
|
+
|
|
111
|
+
## Marking Criteria Met
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// Only auto-mark [auto] criteria when tests pass
|
|
115
|
+
mark_criteria_met({ criteria_id: criterionId })
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Leave `[manual]` criteria for user to confirm after verification.
|
|
119
|
+
|
|
120
|
+
## Output to Orchestrator
|
|
121
|
+
|
|
122
|
+
**Concise format:**
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
## Verification: {PASSED | NEEDS_FIX | BLOCKED}
|
|
126
|
+
|
|
127
|
+
Tests: 42/42 ✅
|
|
128
|
+
Auto AC: 15/16 (1 missing test)
|
|
129
|
+
Manual AC: 4 pending
|
|
130
|
+
|
|
131
|
+
Issues:
|
|
132
|
+
- {issue 1}
|
|
133
|
+
|
|
134
|
+
Manual Checklist:
|
|
135
|
+
- [ ] {item 1}
|
|
136
|
+
- [ ] {item 2}
|
|
137
|
+
|
|
138
|
+
Suggested Tests:
|
|
139
|
+
- {suggestion if no explicit steps}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Boundaries
|
|
143
|
+
|
|
144
|
+
- **DO** run tests and report results
|
|
145
|
+
- **DO** keep reports concise
|
|
146
|
+
- **DO** suggest manual test cases when steps are missing
|
|
147
|
+
- **DON'T** mark manual criteria as met
|
|
148
|
+
- **DON'T** write new tests or modify code
|
|
149
|
+
- **DON'T** generate verbose reports - be brief
|
package/bin/install.cjs
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
const readline = require("readline");
|
|
7
|
+
const { execSync, spawn } = require("child_process");
|
|
8
|
+
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
|
|
11
|
+
if (args[0] === "serve") {
|
|
12
|
+
const serverSrc = path.join(__dirname, "..", "src", "server", "index.ts");
|
|
13
|
+
const bunPath = getBunPath();
|
|
14
|
+
if (!bunPath) {
|
|
15
|
+
console.error("Failed to start Flux MCP server: Bun is required but not found");
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
const child = spawn(bunPath, ["run", serverSrc], { stdio: "inherit" });
|
|
19
|
+
child.on("error", (err) => {
|
|
20
|
+
console.error("Failed to start Flux MCP server:", err.message);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
});
|
|
23
|
+
child.on("close", (code) => process.exit(code || 0));
|
|
24
|
+
} else {
|
|
25
|
+
runInstaller();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getBunPath() {
|
|
29
|
+
const bunDir = path.join(os.homedir(), ".bun", "bin");
|
|
30
|
+
const bunBinary = process.platform === "win32" ? "bun.exe" : "bun";
|
|
31
|
+
const localBunPath = path.join(bunDir, bunBinary);
|
|
32
|
+
if (fs.existsSync(localBunPath)) return localBunPath;
|
|
33
|
+
try {
|
|
34
|
+
execSync("bun --version", { stdio: "ignore" });
|
|
35
|
+
return "bun";
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function runInstaller() {
|
|
42
|
+
const cyan = "\x1b[36m";
|
|
43
|
+
const green = "\x1b[32m";
|
|
44
|
+
const yellow = "\x1b[33m";
|
|
45
|
+
const red = "\x1b[31m";
|
|
46
|
+
const dim = "\x1b[2m";
|
|
47
|
+
const reset = "\x1b[0m";
|
|
48
|
+
const pkg = require("../package.json");
|
|
49
|
+
|
|
50
|
+
const banner = `
|
|
51
|
+
${cyan} ███████╗██╗ ██╗ ██╗██╗ ██╗
|
|
52
|
+
██╔════╝██║ ██║ ██║╚██╗██╔╝
|
|
53
|
+
█████╗ ██║ ██║ ██║ ╚███╔╝
|
|
54
|
+
██╔══╝ ██║ ██║ ██║ ██╔██╗
|
|
55
|
+
██║ ███████╗╚██████╔╝██╔╝ ██╗
|
|
56
|
+
╚═╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝${reset}
|
|
57
|
+
|
|
58
|
+
Flux Plugin ${dim}v${pkg.version}${reset}
|
|
59
|
+
AI-first workflow orchestration for Claude Code
|
|
60
|
+
`;
|
|
61
|
+
|
|
62
|
+
const hasGlobal = args.includes("--global") || args.includes("-g");
|
|
63
|
+
const hasLocal = args.includes("--local") || args.includes("-l");
|
|
64
|
+
const hasHelp = args.includes("--help") || args.includes("-h");
|
|
65
|
+
|
|
66
|
+
console.log(banner);
|
|
67
|
+
|
|
68
|
+
if (hasHelp) {
|
|
69
|
+
console.log(` ${yellow}Usage:${reset} bunx @cliangdev/flux-plugin [options]
|
|
70
|
+
|
|
71
|
+
${yellow}Options:${reset}
|
|
72
|
+
${cyan}-g, --global${reset} Install globally (to ~/.claude)
|
|
73
|
+
${cyan}-l, --local${reset} Install locally (to ./.claude in current directory)
|
|
74
|
+
${cyan}-h, --help${reset} Show this help message
|
|
75
|
+
|
|
76
|
+
${yellow}Examples:${reset}
|
|
77
|
+
${dim}# Interactive installation${reset}
|
|
78
|
+
bunx @cliangdev/flux-plugin
|
|
79
|
+
|
|
80
|
+
${dim}# Install globally (all projects)${reset}
|
|
81
|
+
bunx @cliangdev/flux-plugin --global
|
|
82
|
+
|
|
83
|
+
${dim}# Install locally (current project only)${reset}
|
|
84
|
+
bunx @cliangdev/flux-plugin --local
|
|
85
|
+
|
|
86
|
+
${yellow}Note:${reset} This plugin requires Bun. Install from https://bun.sh
|
|
87
|
+
`);
|
|
88
|
+
process.exit(0);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function isBunInstalled() {
|
|
92
|
+
const bunDir = path.join(os.homedir(), ".bun", "bin");
|
|
93
|
+
const envPath = process.env.PATH || "";
|
|
94
|
+
const pathWithBun = envPath.includes(bunDir)
|
|
95
|
+
? envPath
|
|
96
|
+
: `${bunDir}${path.delimiter}${envPath}`;
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
execSync("bun --version", {
|
|
100
|
+
stdio: "ignore",
|
|
101
|
+
env: { ...process.env, PATH: pathWithBun },
|
|
102
|
+
});
|
|
103
|
+
return true;
|
|
104
|
+
} catch {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function installBun() {
|
|
110
|
+
return new Promise((resolve, reject) => {
|
|
111
|
+
const platform = os.platform();
|
|
112
|
+
const installCmd = platform === "win32" ? "powershell" : "/bin/sh";
|
|
113
|
+
const installArgs = platform === "win32"
|
|
114
|
+
? ["-c", "irm bun.sh/install.ps1 | iex"]
|
|
115
|
+
: ["-c", "curl -fsSL https://bun.sh/install | bash"];
|
|
116
|
+
|
|
117
|
+
console.log(`\n ${cyan}Installing Bun...${reset}\n`);
|
|
118
|
+
|
|
119
|
+
const child = spawn(installCmd, installArgs, {
|
|
120
|
+
stdio: "inherit",
|
|
121
|
+
shell: false,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
child.on("close", (code) => {
|
|
125
|
+
if (code === 0) {
|
|
126
|
+
console.log(`\n ${green}✓${reset} Bun installed successfully\n`);
|
|
127
|
+
resolve(true);
|
|
128
|
+
} else {
|
|
129
|
+
reject(new Error(`Installation exited with code ${code}`));
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
child.on("error", (err) => {
|
|
134
|
+
reject(err);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function showBunInstallInstructions() {
|
|
140
|
+
console.log(`
|
|
141
|
+
${yellow}Bun is required but not installed.${reset}
|
|
142
|
+
|
|
143
|
+
Install Bun manually:
|
|
144
|
+
|
|
145
|
+
${cyan}macOS/Linux:${reset}
|
|
146
|
+
curl -fsSL https://bun.sh/install | bash
|
|
147
|
+
|
|
148
|
+
${cyan}Windows:${reset}
|
|
149
|
+
powershell -c "irm bun.sh/install.ps1 | iex"
|
|
150
|
+
|
|
151
|
+
Then restart your terminal and run this installer again.
|
|
152
|
+
|
|
153
|
+
${dim}Learn more: https://bun.sh${reset}
|
|
154
|
+
`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function checkBunAndContinue(callback) {
|
|
158
|
+
if (isBunInstalled()) {
|
|
159
|
+
callback();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const rl = readline.createInterface({
|
|
164
|
+
input: process.stdin,
|
|
165
|
+
output: process.stdout,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
console.log(` ${yellow}Bun is required but not installed.${reset}\n`);
|
|
169
|
+
|
|
170
|
+
rl.question(` Install Bun now? ${dim}[Y/n]${reset}: `, async (answer) => {
|
|
171
|
+
rl.close();
|
|
172
|
+
const shouldInstall = answer.trim().toLowerCase() !== "n";
|
|
173
|
+
|
|
174
|
+
if (shouldInstall) {
|
|
175
|
+
try {
|
|
176
|
+
await installBun();
|
|
177
|
+
if (isBunInstalled()) {
|
|
178
|
+
callback();
|
|
179
|
+
} else {
|
|
180
|
+
console.log(` ${yellow}Please restart your terminal to use Bun, then run the installer again.${reset}\n`);
|
|
181
|
+
process.exit(0);
|
|
182
|
+
}
|
|
183
|
+
} catch (err) {
|
|
184
|
+
console.log(`\n ${red}Failed to install Bun:${reset} ${err.message}\n`);
|
|
185
|
+
showBunInstallInstructions();
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
showBunInstallInstructions();
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function copyDir(src, dest) {
|
|
196
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
197
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
198
|
+
|
|
199
|
+
for (const entry of entries) {
|
|
200
|
+
const srcPath = path.join(src, entry.name);
|
|
201
|
+
const destPath = path.join(dest, entry.name);
|
|
202
|
+
|
|
203
|
+
if (entry.isDirectory()) {
|
|
204
|
+
copyDir(srcPath, destPath);
|
|
205
|
+
} else {
|
|
206
|
+
fs.copyFileSync(srcPath, destPath);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function readJson(filePath) {
|
|
212
|
+
if (fs.existsSync(filePath)) {
|
|
213
|
+
try {
|
|
214
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
215
|
+
} catch {
|
|
216
|
+
return {};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return {};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function writeJson(filePath, data) {
|
|
223
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function install(isGlobal) {
|
|
227
|
+
const src = path.join(__dirname, "..");
|
|
228
|
+
const claudeDir = isGlobal
|
|
229
|
+
? path.join(os.homedir(), ".claude")
|
|
230
|
+
: path.join(process.cwd(), ".claude");
|
|
231
|
+
const locationLabel = isGlobal ? "~/.claude" : "./.claude";
|
|
232
|
+
|
|
233
|
+
console.log(` Installing to ${cyan}${locationLabel}${reset}\n`);
|
|
234
|
+
|
|
235
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
236
|
+
|
|
237
|
+
const commandsSrc = path.join(src, "commands");
|
|
238
|
+
if (fs.existsSync(commandsSrc)) {
|
|
239
|
+
const commandsDest = path.join(claudeDir, "commands");
|
|
240
|
+
const fluxSubDir = path.join(commandsDest, "flux");
|
|
241
|
+
fs.mkdirSync(fluxSubDir, { recursive: true });
|
|
242
|
+
|
|
243
|
+
const commandFiles = fs.readdirSync(commandsSrc);
|
|
244
|
+
for (const file of commandFiles) {
|
|
245
|
+
if (file.endsWith(".md")) {
|
|
246
|
+
const name = file.replace(".md", "");
|
|
247
|
+
if (name === "flux") {
|
|
248
|
+
fs.copyFileSync(
|
|
249
|
+
path.join(commandsSrc, file),
|
|
250
|
+
path.join(commandsDest, file)
|
|
251
|
+
);
|
|
252
|
+
console.log(` ${green}✓${reset} Installed command: /flux`);
|
|
253
|
+
} else {
|
|
254
|
+
fs.copyFileSync(
|
|
255
|
+
path.join(commandsSrc, file),
|
|
256
|
+
path.join(fluxSubDir, file)
|
|
257
|
+
);
|
|
258
|
+
console.log(` ${green}✓${reset} Installed command: /flux:${name}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const skillsSrc = path.join(src, "skills");
|
|
265
|
+
if (fs.existsSync(skillsSrc)) {
|
|
266
|
+
const skillsDest = path.join(claudeDir, "skills");
|
|
267
|
+
fs.mkdirSync(skillsDest, { recursive: true });
|
|
268
|
+
|
|
269
|
+
const skillDirs = fs.readdirSync(skillsSrc, { withFileTypes: true });
|
|
270
|
+
for (const dir of skillDirs) {
|
|
271
|
+
if (dir.isDirectory()) {
|
|
272
|
+
copyDir(
|
|
273
|
+
path.join(skillsSrc, dir.name),
|
|
274
|
+
path.join(skillsDest, dir.name)
|
|
275
|
+
);
|
|
276
|
+
console.log(` ${green}✓${reset} Installed skill: ${dir.name}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const agentsSrc = path.join(src, "agents");
|
|
282
|
+
if (fs.existsSync(agentsSrc)) {
|
|
283
|
+
const agentsDest = path.join(claudeDir, "agents");
|
|
284
|
+
fs.mkdirSync(agentsDest, { recursive: true });
|
|
285
|
+
|
|
286
|
+
const agentFiles = fs.readdirSync(agentsSrc);
|
|
287
|
+
for (const file of agentFiles) {
|
|
288
|
+
if (file.endsWith(".md")) {
|
|
289
|
+
fs.copyFileSync(
|
|
290
|
+
path.join(agentsSrc, file),
|
|
291
|
+
path.join(agentsDest, file)
|
|
292
|
+
);
|
|
293
|
+
const name = file.replace(".md", "");
|
|
294
|
+
console.log(` ${green}✓${reset} Installed agent: ${name}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const mcpConfigPath = isGlobal
|
|
300
|
+
? path.join(os.homedir(), ".claude.json")
|
|
301
|
+
: path.join(process.cwd(), ".mcp.json");
|
|
302
|
+
|
|
303
|
+
const mcpConfig = readJson(mcpConfigPath);
|
|
304
|
+
|
|
305
|
+
if (!mcpConfig.mcpServers) {
|
|
306
|
+
mcpConfig.mcpServers = {};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const versionTag = pkg.version.includes("-dev.") ? "latest" : pkg.version;
|
|
310
|
+
mcpConfig.mcpServers.flux = {
|
|
311
|
+
command: "bunx",
|
|
312
|
+
args: [`@cliangdev/flux-plugin@${versionTag}`, "serve"],
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
writeJson(mcpConfigPath, mcpConfig);
|
|
316
|
+
console.log(
|
|
317
|
+
` ${green}✓${reset} Configured MCP server in ${isGlobal ? "~/.claude.json" : "./.mcp.json"}`
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
const versionFile = path.join(claudeDir, "flux-version");
|
|
321
|
+
fs.writeFileSync(versionFile, pkg.version);
|
|
322
|
+
|
|
323
|
+
console.log(`
|
|
324
|
+
${green}Done!${reset} Restart Claude Code and run ${cyan}/flux${reset} to get started.
|
|
325
|
+
|
|
326
|
+
${dim}Commands available:${reset}
|
|
327
|
+
/flux - Project status and guidance
|
|
328
|
+
/flux:prd - Create or refine PRDs
|
|
329
|
+
/flux:breakdown - Break PRDs into epics and tasks
|
|
330
|
+
/flux:implement - Implement tasks with TDD
|
|
331
|
+
|
|
332
|
+
${dim}Learn more:${reset} https://github.com/cliangdev/flux-plugin
|
|
333
|
+
`);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function promptLocation() {
|
|
337
|
+
const rl = readline.createInterface({
|
|
338
|
+
input: process.stdin,
|
|
339
|
+
output: process.stdout,
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
console.log(` ${yellow}Where would you like to install?${reset}
|
|
343
|
+
|
|
344
|
+
${cyan}1${reset}) Global ${dim}(~/.claude)${reset} - available in all projects
|
|
345
|
+
${cyan}2${reset}) Local ${dim}(./.claude)${reset} - this project only
|
|
346
|
+
`);
|
|
347
|
+
|
|
348
|
+
rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
|
|
349
|
+
rl.close();
|
|
350
|
+
const choice = answer.trim() || "1";
|
|
351
|
+
install(choice !== "2");
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function startInstallation() {
|
|
356
|
+
if (hasGlobal && hasLocal) {
|
|
357
|
+
console.error(` ${yellow}Cannot specify both --global and --local${reset}`);
|
|
358
|
+
process.exit(1);
|
|
359
|
+
} else if (hasGlobal) {
|
|
360
|
+
install(true);
|
|
361
|
+
} else if (hasLocal) {
|
|
362
|
+
install(false);
|
|
363
|
+
} else {
|
|
364
|
+
promptLocation();
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
checkBunAndContinue(startInstallation);
|
|
369
|
+
}
|