@cliangdev/flux-plugin 0.1.0 → 0.2.0-dev.359209a
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 +55 -22
- package/agents/coder.md +317 -0
- package/agents/critic.md +174 -0
- package/agents/researcher.md +146 -0
- package/agents/verifier.md +149 -0
- package/bin/install.cjs +390 -0
- package/commands/breakdown.md +48 -10
- package/commands/dashboard.md +29 -0
- package/commands/flux.md +218 -94
- package/commands/implement.md +167 -17
- package/commands/linear.md +172 -0
- package/commands/prd.md +997 -82
- package/manifest.json +16 -0
- package/package.json +17 -11
- package/skills/agent-creator/SKILL.md +2 -0
- package/skills/epic-template/SKILL.md +2 -0
- package/skills/flux-orchestrator/SKILL.md +68 -76
- package/skills/prd-writer/SKILL.md +761 -0
- package/skills/ux-ui-design/SKILL.md +346 -0
- package/skills/ux-ui-design/references/design-tokens.md +359 -0
- package/src/__tests__/version.test.ts +37 -0
- package/src/adapters/local/.gitkeep +0 -0
- package/src/dashboard/__tests__/api.test.ts +211 -0
- package/src/dashboard/browser.ts +35 -0
- package/src/dashboard/public/app.js +869 -0
- package/src/dashboard/public/index.html +90 -0
- package/src/dashboard/public/styles.css +807 -0
- package/src/dashboard/public/vendor/highlight.css +10 -0
- package/src/dashboard/public/vendor/highlight.min.js +8422 -0
- package/src/dashboard/public/vendor/marked.min.js +2210 -0
- package/src/dashboard/server.ts +296 -0
- package/src/dashboard/watchers.ts +83 -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 +429 -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 +1141 -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 +1009 -0
- package/src/server/adapters/types.ts +293 -0
- package/src/server/config.ts +73 -0
- package/src/server/db/__tests__/queries.test.ts +473 -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 +97 -0
- package/src/server/db/sqlite.ts +10 -0
- package/src/server/index.ts +81 -0
- package/src/server/tools/__tests__/crud.test.ts +411 -0
- package/src/server/tools/__tests__/get-version.test.ts +27 -0
- package/src/server/tools/__tests__/mcp-interface.test.ts +479 -0
- package/src/server/tools/__tests__/query.test.ts +405 -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 +44 -0
- package/src/server/tools/create-prd.ts +40 -0
- package/src/server/tools/create-task.ts +47 -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 +240 -0
- package/src/server/tools/get-linear-url.ts +28 -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 +158 -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 +219 -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 +214 -0
- package/src/version.ts +5 -0
- package/dist/server/index.js +0 -86929
- package/skills/prd-template/SKILL.md +0 -240
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flux-researcher
|
|
3
|
+
description: Researches unfamiliar technologies, libraries, and APIs. Use proactively when user mentions tech that may need investigation during PRD interviews, or when explicitly asked to research something. Auto-triggers when confidence < 70%.
|
|
4
|
+
tools: WebFetch, WebSearch, Read, mcp__context7__resolve-library-id, mcp__context7__query-docs
|
|
5
|
+
model: sonnet
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Flux Research Subagent
|
|
9
|
+
|
|
10
|
+
You are a technology research specialist for the Flux workflow system. Your role is to gather accurate, up-to-date information about technologies mentioned during PRD creation and planning.
|
|
11
|
+
|
|
12
|
+
## When to Activate
|
|
13
|
+
|
|
14
|
+
Trigger research when:
|
|
15
|
+
- User mentions a library, framework, or API you're uncertain about
|
|
16
|
+
- User explicitly asks "research X" or "what is X?"
|
|
17
|
+
- User asks about comparisons ("X vs Y")
|
|
18
|
+
- During interview if user mentions unfamiliar tech stack
|
|
19
|
+
- Confidence in technical recommendation < 70%
|
|
20
|
+
|
|
21
|
+
## Research Process
|
|
22
|
+
|
|
23
|
+
### Step 1: Identify Questions
|
|
24
|
+
What do we need to know?
|
|
25
|
+
- What is this technology?
|
|
26
|
+
- What problems does it solve?
|
|
27
|
+
- Is it actively maintained?
|
|
28
|
+
- What are the alternatives?
|
|
29
|
+
- How does it fit the user's context?
|
|
30
|
+
|
|
31
|
+
### Step 2: Library Documentation (Context7)
|
|
32
|
+
For libraries and frameworks:
|
|
33
|
+
|
|
34
|
+
1. **Resolve library ID:**
|
|
35
|
+
```
|
|
36
|
+
mcp__context7__resolve-library-id
|
|
37
|
+
- libraryName: "{library name}"
|
|
38
|
+
- query: "{what the user is trying to accomplish}"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
2. **Query docs:**
|
|
42
|
+
```
|
|
43
|
+
mcp__context7__query-docs
|
|
44
|
+
- libraryId: "{resolved id}"
|
|
45
|
+
- query: "{specific question about usage}"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Context7 provides up-to-date documentation with code examples.
|
|
49
|
+
|
|
50
|
+
### Step 3: Web Research
|
|
51
|
+
For broader context:
|
|
52
|
+
|
|
53
|
+
1. **WebSearch** for:
|
|
54
|
+
- "{library} vs alternatives 2024"
|
|
55
|
+
- "{library} production use cases"
|
|
56
|
+
- "{library} getting started"
|
|
57
|
+
|
|
58
|
+
2. **WebFetch** on:
|
|
59
|
+
- Official documentation sites
|
|
60
|
+
- GitHub repository (check stars, recent commits)
|
|
61
|
+
- Comparison articles from reputable sources
|
|
62
|
+
|
|
63
|
+
### Step 4: Synthesize
|
|
64
|
+
Combine findings into actionable insights relevant to the user's project.
|
|
65
|
+
|
|
66
|
+
## Output Format
|
|
67
|
+
|
|
68
|
+
```markdown
|
|
69
|
+
## Research: {Technology Name}
|
|
70
|
+
|
|
71
|
+
### What It Is
|
|
72
|
+
{1-2 sentence description}
|
|
73
|
+
|
|
74
|
+
### Key Features
|
|
75
|
+
- {Feature 1}: {brief explanation}
|
|
76
|
+
- {Feature 2}: {brief explanation}
|
|
77
|
+
- {Feature 3}: {brief explanation}
|
|
78
|
+
|
|
79
|
+
### Pros
|
|
80
|
+
- {Advantage 1}
|
|
81
|
+
- {Advantage 2}
|
|
82
|
+
|
|
83
|
+
### Cons
|
|
84
|
+
- {Disadvantage 1}
|
|
85
|
+
- {Disadvantage 2}
|
|
86
|
+
|
|
87
|
+
### Alternatives
|
|
88
|
+
| Alternative | Comparison |
|
|
89
|
+
|-------------|------------|
|
|
90
|
+
| {Alt 1} | {How it differs} |
|
|
91
|
+
| {Alt 2} | {How it differs} |
|
|
92
|
+
|
|
93
|
+
### Recommendation
|
|
94
|
+
{Based on user's context, should they use this? Why or why not?}
|
|
95
|
+
|
|
96
|
+
### Quick Start
|
|
97
|
+
{If relevant, brief code example or getting started steps}
|
|
98
|
+
|
|
99
|
+
### Sources
|
|
100
|
+
- [{Source 1 title}]({url})
|
|
101
|
+
- [{Source 2 title}]({url})
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Context-Specific Research
|
|
105
|
+
|
|
106
|
+
### For Web Applications
|
|
107
|
+
Focus on:
|
|
108
|
+
- Frontend framework compatibility
|
|
109
|
+
- Bundle size considerations
|
|
110
|
+
- SSR/SSG support
|
|
111
|
+
- Developer experience
|
|
112
|
+
|
|
113
|
+
### For CLI Tools
|
|
114
|
+
Focus on:
|
|
115
|
+
- Runtime requirements (Node, Bun, etc.)
|
|
116
|
+
- Cross-platform support
|
|
117
|
+
- Dependency footprint
|
|
118
|
+
|
|
119
|
+
### For APIs/Backend
|
|
120
|
+
Focus on:
|
|
121
|
+
- Performance characteristics
|
|
122
|
+
- Database compatibility
|
|
123
|
+
- Authentication options
|
|
124
|
+
- Deployment requirements
|
|
125
|
+
|
|
126
|
+
### For Mobile Apps
|
|
127
|
+
Focus on:
|
|
128
|
+
- Native vs cross-platform
|
|
129
|
+
- Platform-specific limitations
|
|
130
|
+
- Performance on mobile devices
|
|
131
|
+
|
|
132
|
+
## Boundaries
|
|
133
|
+
|
|
134
|
+
- Do NOT make up information - if unsure, say so
|
|
135
|
+
- Do NOT recommend against something without evidence
|
|
136
|
+
- Do NOT fetch more than 5 web pages per research task
|
|
137
|
+
- If research is inconclusive, present what you found and ask for clarification
|
|
138
|
+
|
|
139
|
+
## Completion
|
|
140
|
+
|
|
141
|
+
Research is complete when:
|
|
142
|
+
- Core questions are answered
|
|
143
|
+
- User has enough info to make a decision
|
|
144
|
+
- Sources are cited
|
|
145
|
+
|
|
146
|
+
Output your findings in the format above, then offer to dive deeper on any aspect.
|
|
@@ -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,390 @@
|
|
|
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 if (args[0] === "dashboard") {
|
|
25
|
+
const dashboardSrc = path.join(__dirname, "..", "src", "dashboard", "server.ts");
|
|
26
|
+
const bunPath = getBunPath();
|
|
27
|
+
if (!bunPath) {
|
|
28
|
+
console.error("Failed to start Flux Dashboard: Bun is required but not found");
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
const child = spawn(bunPath, ["run", dashboardSrc], { stdio: "inherit" });
|
|
32
|
+
child.on("error", (err) => {
|
|
33
|
+
console.error("Failed to start Flux Dashboard:", err.message);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
});
|
|
36
|
+
child.on("close", (code) => process.exit(code || 0));
|
|
37
|
+
} else {
|
|
38
|
+
runInstaller();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getBunPath() {
|
|
42
|
+
const bunDir = path.join(os.homedir(), ".bun", "bin");
|
|
43
|
+
const bunBinary = process.platform === "win32" ? "bun.exe" : "bun";
|
|
44
|
+
const localBunPath = path.join(bunDir, bunBinary);
|
|
45
|
+
if (fs.existsSync(localBunPath)) return localBunPath;
|
|
46
|
+
try {
|
|
47
|
+
execSync("bun --version", { stdio: "ignore" });
|
|
48
|
+
return "bun";
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function runInstaller() {
|
|
55
|
+
const cyan = "\x1b[36m";
|
|
56
|
+
const green = "\x1b[32m";
|
|
57
|
+
const yellow = "\x1b[33m";
|
|
58
|
+
const red = "\x1b[31m";
|
|
59
|
+
const dim = "\x1b[2m";
|
|
60
|
+
const reset = "\x1b[0m";
|
|
61
|
+
const pkg = require("../package.json");
|
|
62
|
+
|
|
63
|
+
const banner = `
|
|
64
|
+
${cyan} ███████╗██╗ ██╗ ██╗██╗ ██╗
|
|
65
|
+
██╔════╝██║ ██║ ██║╚██╗██╔╝
|
|
66
|
+
█████╗ ██║ ██║ ██║ ╚███╔╝
|
|
67
|
+
██╔══╝ ██║ ██║ ██║ ██╔██╗
|
|
68
|
+
██║ ███████╗╚██████╔╝██╔╝ ██╗
|
|
69
|
+
╚═╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝${reset}
|
|
70
|
+
|
|
71
|
+
Flux Plugin ${dim}v${pkg.version}${reset}
|
|
72
|
+
AI-first workflow orchestration for Claude Code
|
|
73
|
+
`;
|
|
74
|
+
|
|
75
|
+
const hasGlobal = args.includes("--global") || args.includes("-g");
|
|
76
|
+
const hasLocal = args.includes("--local") || args.includes("-l");
|
|
77
|
+
const hasHelp = args.includes("--help") || args.includes("-h");
|
|
78
|
+
|
|
79
|
+
console.log(banner);
|
|
80
|
+
|
|
81
|
+
if (hasHelp) {
|
|
82
|
+
console.log(` ${yellow}Usage:${reset} bunx @cliangdev/flux-plugin [command] [options]
|
|
83
|
+
|
|
84
|
+
${yellow}Commands:${reset}
|
|
85
|
+
${cyan}(none)${reset} Run the installer (default)
|
|
86
|
+
${cyan}serve${reset} Start the MCP server
|
|
87
|
+
${cyan}dashboard${reset} Open the Flux Dashboard in browser
|
|
88
|
+
|
|
89
|
+
${yellow}Options:${reset}
|
|
90
|
+
${cyan}-g, --global${reset} Install globally (to ~/.claude)
|
|
91
|
+
${cyan}-l, --local${reset} Install locally (to ./.claude in current directory)
|
|
92
|
+
${cyan}-h, --help${reset} Show this help message
|
|
93
|
+
|
|
94
|
+
${yellow}Examples:${reset}
|
|
95
|
+
${dim}# Interactive installation${reset}
|
|
96
|
+
bunx @cliangdev/flux-plugin
|
|
97
|
+
|
|
98
|
+
${dim}# Install globally (all projects)${reset}
|
|
99
|
+
bunx @cliangdev/flux-plugin --global
|
|
100
|
+
|
|
101
|
+
${dim}# Install locally (current project only)${reset}
|
|
102
|
+
bunx @cliangdev/flux-plugin --local
|
|
103
|
+
|
|
104
|
+
${dim}# Open the dashboard${reset}
|
|
105
|
+
bunx @cliangdev/flux-plugin dashboard
|
|
106
|
+
|
|
107
|
+
${yellow}Note:${reset} This plugin requires Bun. Install from https://bun.sh
|
|
108
|
+
`);
|
|
109
|
+
process.exit(0);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function isBunInstalled() {
|
|
113
|
+
const bunDir = path.join(os.homedir(), ".bun", "bin");
|
|
114
|
+
const envPath = process.env.PATH || "";
|
|
115
|
+
const pathWithBun = envPath.includes(bunDir)
|
|
116
|
+
? envPath
|
|
117
|
+
: `${bunDir}${path.delimiter}${envPath}`;
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
execSync("bun --version", {
|
|
121
|
+
stdio: "ignore",
|
|
122
|
+
env: { ...process.env, PATH: pathWithBun },
|
|
123
|
+
});
|
|
124
|
+
return true;
|
|
125
|
+
} catch {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function installBun() {
|
|
131
|
+
return new Promise((resolve, reject) => {
|
|
132
|
+
const platform = os.platform();
|
|
133
|
+
const installCmd = platform === "win32" ? "powershell" : "/bin/sh";
|
|
134
|
+
const installArgs = platform === "win32"
|
|
135
|
+
? ["-c", "irm bun.sh/install.ps1 | iex"]
|
|
136
|
+
: ["-c", "curl -fsSL https://bun.sh/install | bash"];
|
|
137
|
+
|
|
138
|
+
console.log(`\n ${cyan}Installing Bun...${reset}\n`);
|
|
139
|
+
|
|
140
|
+
const child = spawn(installCmd, installArgs, {
|
|
141
|
+
stdio: "inherit",
|
|
142
|
+
shell: false,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
child.on("close", (code) => {
|
|
146
|
+
if (code === 0) {
|
|
147
|
+
console.log(`\n ${green}✓${reset} Bun installed successfully\n`);
|
|
148
|
+
resolve(true);
|
|
149
|
+
} else {
|
|
150
|
+
reject(new Error(`Installation exited with code ${code}`));
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
child.on("error", (err) => {
|
|
155
|
+
reject(err);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function showBunInstallInstructions() {
|
|
161
|
+
console.log(`
|
|
162
|
+
${yellow}Bun is required but not installed.${reset}
|
|
163
|
+
|
|
164
|
+
Install Bun manually:
|
|
165
|
+
|
|
166
|
+
${cyan}macOS/Linux:${reset}
|
|
167
|
+
curl -fsSL https://bun.sh/install | bash
|
|
168
|
+
|
|
169
|
+
${cyan}Windows:${reset}
|
|
170
|
+
powershell -c "irm bun.sh/install.ps1 | iex"
|
|
171
|
+
|
|
172
|
+
Then restart your terminal and run this installer again.
|
|
173
|
+
|
|
174
|
+
${dim}Learn more: https://bun.sh${reset}
|
|
175
|
+
`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function checkBunAndContinue(callback) {
|
|
179
|
+
if (isBunInstalled()) {
|
|
180
|
+
callback();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const rl = readline.createInterface({
|
|
185
|
+
input: process.stdin,
|
|
186
|
+
output: process.stdout,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
console.log(` ${yellow}Bun is required but not installed.${reset}\n`);
|
|
190
|
+
|
|
191
|
+
rl.question(` Install Bun now? ${dim}[Y/n]${reset}: `, async (answer) => {
|
|
192
|
+
rl.close();
|
|
193
|
+
const shouldInstall = answer.trim().toLowerCase() !== "n";
|
|
194
|
+
|
|
195
|
+
if (shouldInstall) {
|
|
196
|
+
try {
|
|
197
|
+
await installBun();
|
|
198
|
+
if (isBunInstalled()) {
|
|
199
|
+
callback();
|
|
200
|
+
} else {
|
|
201
|
+
console.log(` ${yellow}Please restart your terminal to use Bun, then run the installer again.${reset}\n`);
|
|
202
|
+
process.exit(0);
|
|
203
|
+
}
|
|
204
|
+
} catch (err) {
|
|
205
|
+
console.log(`\n ${red}Failed to install Bun:${reset} ${err.message}\n`);
|
|
206
|
+
showBunInstallInstructions();
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
showBunInstallInstructions();
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function copyDir(src, dest) {
|
|
217
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
218
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
219
|
+
|
|
220
|
+
for (const entry of entries) {
|
|
221
|
+
const srcPath = path.join(src, entry.name);
|
|
222
|
+
const destPath = path.join(dest, entry.name);
|
|
223
|
+
|
|
224
|
+
if (entry.isDirectory()) {
|
|
225
|
+
copyDir(srcPath, destPath);
|
|
226
|
+
} else {
|
|
227
|
+
fs.copyFileSync(srcPath, destPath);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function readJson(filePath) {
|
|
233
|
+
if (fs.existsSync(filePath)) {
|
|
234
|
+
try {
|
|
235
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
236
|
+
} catch {
|
|
237
|
+
return {};
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return {};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function writeJson(filePath, data) {
|
|
244
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function install(isGlobal) {
|
|
248
|
+
const src = path.join(__dirname, "..");
|
|
249
|
+
const claudeDir = isGlobal
|
|
250
|
+
? path.join(os.homedir(), ".claude")
|
|
251
|
+
: path.join(process.cwd(), ".claude");
|
|
252
|
+
const locationLabel = isGlobal ? "~/.claude" : "./.claude";
|
|
253
|
+
|
|
254
|
+
console.log(` Installing to ${cyan}${locationLabel}${reset}\n`);
|
|
255
|
+
|
|
256
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
257
|
+
|
|
258
|
+
const commandsSrc = path.join(src, "commands");
|
|
259
|
+
if (fs.existsSync(commandsSrc)) {
|
|
260
|
+
const commandsDest = path.join(claudeDir, "commands");
|
|
261
|
+
const fluxSubDir = path.join(commandsDest, "flux");
|
|
262
|
+
fs.mkdirSync(fluxSubDir, { recursive: true });
|
|
263
|
+
|
|
264
|
+
const commandFiles = fs.readdirSync(commandsSrc);
|
|
265
|
+
for (const file of commandFiles) {
|
|
266
|
+
if (file.endsWith(".md")) {
|
|
267
|
+
const name = file.replace(".md", "");
|
|
268
|
+
if (name === "flux") {
|
|
269
|
+
fs.copyFileSync(
|
|
270
|
+
path.join(commandsSrc, file),
|
|
271
|
+
path.join(commandsDest, file)
|
|
272
|
+
);
|
|
273
|
+
console.log(` ${green}✓${reset} Installed command: /flux`);
|
|
274
|
+
} else {
|
|
275
|
+
fs.copyFileSync(
|
|
276
|
+
path.join(commandsSrc, file),
|
|
277
|
+
path.join(fluxSubDir, file)
|
|
278
|
+
);
|
|
279
|
+
console.log(` ${green}✓${reset} Installed command: /flux:${name}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const skillsSrc = path.join(src, "skills");
|
|
286
|
+
if (fs.existsSync(skillsSrc)) {
|
|
287
|
+
const skillsDest = path.join(claudeDir, "skills");
|
|
288
|
+
fs.mkdirSync(skillsDest, { recursive: true });
|
|
289
|
+
|
|
290
|
+
const skillDirs = fs.readdirSync(skillsSrc, { withFileTypes: true });
|
|
291
|
+
for (const dir of skillDirs) {
|
|
292
|
+
if (dir.isDirectory()) {
|
|
293
|
+
copyDir(
|
|
294
|
+
path.join(skillsSrc, dir.name),
|
|
295
|
+
path.join(skillsDest, dir.name)
|
|
296
|
+
);
|
|
297
|
+
console.log(` ${green}✓${reset} Installed skill: ${dir.name}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const agentsSrc = path.join(src, "agents");
|
|
303
|
+
if (fs.existsSync(agentsSrc)) {
|
|
304
|
+
const agentsDest = path.join(claudeDir, "agents");
|
|
305
|
+
fs.mkdirSync(agentsDest, { recursive: true });
|
|
306
|
+
|
|
307
|
+
const agentFiles = fs.readdirSync(agentsSrc);
|
|
308
|
+
for (const file of agentFiles) {
|
|
309
|
+
if (file.endsWith(".md")) {
|
|
310
|
+
fs.copyFileSync(
|
|
311
|
+
path.join(agentsSrc, file),
|
|
312
|
+
path.join(agentsDest, file)
|
|
313
|
+
);
|
|
314
|
+
const name = file.replace(".md", "");
|
|
315
|
+
console.log(` ${green}✓${reset} Installed agent: ${name}`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const mcpConfigPath = isGlobal
|
|
321
|
+
? path.join(os.homedir(), ".claude.json")
|
|
322
|
+
: path.join(process.cwd(), ".mcp.json");
|
|
323
|
+
|
|
324
|
+
const mcpConfig = readJson(mcpConfigPath);
|
|
325
|
+
|
|
326
|
+
if (!mcpConfig.mcpServers) {
|
|
327
|
+
mcpConfig.mcpServers = {};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const versionTag = pkg.version.includes("-dev.") ? "latest" : pkg.version;
|
|
331
|
+
mcpConfig.mcpServers.flux = {
|
|
332
|
+
command: "bunx",
|
|
333
|
+
args: [`@cliangdev/flux-plugin@${versionTag}`, "serve"],
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
writeJson(mcpConfigPath, mcpConfig);
|
|
337
|
+
console.log(
|
|
338
|
+
` ${green}✓${reset} Configured MCP server in ${isGlobal ? "~/.claude.json" : "./.mcp.json"}`
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
const versionFile = path.join(claudeDir, "flux-version");
|
|
342
|
+
fs.writeFileSync(versionFile, pkg.version);
|
|
343
|
+
|
|
344
|
+
console.log(`
|
|
345
|
+
${green}Done!${reset} Restart Claude Code and run ${cyan}/flux${reset} to get started.
|
|
346
|
+
|
|
347
|
+
${dim}Commands available:${reset}
|
|
348
|
+
/flux - Project status and guidance
|
|
349
|
+
/flux:prd - Create or refine PRDs
|
|
350
|
+
/flux:breakdown - Break PRDs into epics and tasks
|
|
351
|
+
/flux:implement - Implement tasks with TDD
|
|
352
|
+
|
|
353
|
+
${dim}Learn more:${reset} https://github.com/cliangdev/flux-plugin
|
|
354
|
+
`);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function promptLocation() {
|
|
358
|
+
const rl = readline.createInterface({
|
|
359
|
+
input: process.stdin,
|
|
360
|
+
output: process.stdout,
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
console.log(` ${yellow}Where would you like to install?${reset}
|
|
364
|
+
|
|
365
|
+
${cyan}1${reset}) Global ${dim}(~/.claude)${reset} - available in all projects
|
|
366
|
+
${cyan}2${reset}) Local ${dim}(./.claude)${reset} - this project only
|
|
367
|
+
`);
|
|
368
|
+
|
|
369
|
+
rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
|
|
370
|
+
rl.close();
|
|
371
|
+
const choice = answer.trim() || "1";
|
|
372
|
+
install(choice !== "2");
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function startInstallation() {
|
|
377
|
+
if (hasGlobal && hasLocal) {
|
|
378
|
+
console.error(` ${yellow}Cannot specify both --global and --local${reset}`);
|
|
379
|
+
process.exit(1);
|
|
380
|
+
} else if (hasGlobal) {
|
|
381
|
+
install(true);
|
|
382
|
+
} else if (hasLocal) {
|
|
383
|
+
install(false);
|
|
384
|
+
} else {
|
|
385
|
+
promptLocation();
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
checkBunAndContinue(startInstallation);
|
|
390
|
+
}
|