@cliangdev/flux-plugin 0.1.0 → 0.2.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/README.md +50 -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 +235 -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/dist/server/index.js +163 -29
- package/manifest.json +15 -0
- package/package.json +14 -6
- 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
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flux:linear
|
|
3
|
+
description: Connect Flux project to Linear for issue tracking
|
|
4
|
+
allowed-tools: mcp__plugin_flux_flux__*, AskUserQuestion
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Linear Integration Setup
|
|
8
|
+
|
|
9
|
+
Connect a Flux project to Linear using the interactive configuration flow.
|
|
10
|
+
|
|
11
|
+
## How Interactive Mode Works
|
|
12
|
+
|
|
13
|
+
The `configure_linear` tool supports progressive discovery:
|
|
14
|
+
|
|
15
|
+
| Call | Response |
|
|
16
|
+
|------|----------|
|
|
17
|
+
| `{interactive: true}` | Returns `{step: "select_team", teams: [...], user: {...}}` |
|
|
18
|
+
| `{interactive: true, teamId: "xxx"}` | Returns `{step: "select_project", projects: [...], team: {...}}` |
|
|
19
|
+
| `{teamId: "xxx", projectName: "..."}` | Creates new project and configures |
|
|
20
|
+
| `{teamId: "xxx", existingProjectId: "..."}` | Uses existing project and configures |
|
|
21
|
+
|
|
22
|
+
## Flow
|
|
23
|
+
|
|
24
|
+
### Step 1: Verify Project
|
|
25
|
+
|
|
26
|
+
Call `get_project_context`.
|
|
27
|
+
|
|
28
|
+
- If `initialized: false` → Tell user to run `/flux` first, then exit.
|
|
29
|
+
- If `adapter.type === "linear"` and config exists → Already configured, show info and exit.
|
|
30
|
+
- Otherwise → Continue to Step 2.
|
|
31
|
+
|
|
32
|
+
### Step 2: Fetch Teams
|
|
33
|
+
|
|
34
|
+
**IMPORTANT**: Call `configure_linear` with ONLY `interactive: true`. Do NOT pass teamId, projectName, or any other params.
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{"interactive": true}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
This is the ONLY parameter needed for the first call. The tool will return available teams.
|
|
41
|
+
|
|
42
|
+
**If error** (e.g., "Linear API key not found"):
|
|
43
|
+
```
|
|
44
|
+
Linear API key required.
|
|
45
|
+
|
|
46
|
+
1. Get your key: Linear → Settings → API → Personal API keys
|
|
47
|
+
2. Set it: export LINEAR_API_KEY=lin_api_xxx
|
|
48
|
+
3. Run /flux:linear again
|
|
49
|
+
```
|
|
50
|
+
Then exit.
|
|
51
|
+
|
|
52
|
+
**If success**, response will be:
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"step": "select_team",
|
|
56
|
+
"user": {"name": "...", "email": "..."},
|
|
57
|
+
"teams": [
|
|
58
|
+
{"id": "team-abc", "name": "Engineering", "key": "ENG"},
|
|
59
|
+
{"id": "team-def", "name": "Product", "key": "PROD"}
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Display: "Connected as {user.name} ({user.email})"
|
|
65
|
+
|
|
66
|
+
Use AskUserQuestion to let user select a team:
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"questions": [{
|
|
70
|
+
"question": "Which Linear team should Flux use?",
|
|
71
|
+
"header": "Team",
|
|
72
|
+
"options": [
|
|
73
|
+
{"label": "Engineering (ENG)", "description": "team-abc"},
|
|
74
|
+
{"label": "Product (PROD)", "description": "team-def"}
|
|
75
|
+
],
|
|
76
|
+
"multiSelect": false
|
|
77
|
+
}]
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Note: Put the team ID in the description field so you can retrieve it from the response.
|
|
82
|
+
|
|
83
|
+
### Step 3: Fetch Projects
|
|
84
|
+
|
|
85
|
+
Call `configure_linear` with:
|
|
86
|
+
```json
|
|
87
|
+
{"interactive": true, "teamId": "<selected_team_id>"}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Response will be:
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"step": "select_project",
|
|
94
|
+
"team": {"id": "...", "name": "...", "key": "..."},
|
|
95
|
+
"projects": [
|
|
96
|
+
{"id": "proj-123", "name": "Q1 Sprint", "state": "started"},
|
|
97
|
+
{"id": "proj-456", "name": "Backlog", "state": "planned"}
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Use AskUserQuestion:
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"questions": [{
|
|
106
|
+
"question": "Which project should Flux sync to?",
|
|
107
|
+
"header": "Project",
|
|
108
|
+
"options": [
|
|
109
|
+
{"label": "Create New Project", "description": "new"},
|
|
110
|
+
{"label": "Q1 Sprint", "description": "proj-123"},
|
|
111
|
+
{"label": "Backlog", "description": "proj-456"}
|
|
112
|
+
],
|
|
113
|
+
"multiSelect": false
|
|
114
|
+
}]
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Step 4: Configure
|
|
119
|
+
|
|
120
|
+
**If user selected "Create New Project":**
|
|
121
|
+
|
|
122
|
+
Ask for project name, then call:
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"teamId": "<team_id>",
|
|
126
|
+
"projectName": "<user_provided_name>"
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**If user selected existing project:**
|
|
131
|
+
|
|
132
|
+
Call:
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"teamId": "<team_id>",
|
|
136
|
+
"existingProjectId": "<project_id>"
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Step 5: Success
|
|
141
|
+
|
|
142
|
+
Response will include:
|
|
143
|
+
```json
|
|
144
|
+
{
|
|
145
|
+
"success": true,
|
|
146
|
+
"team": "Engineering",
|
|
147
|
+
"project": {"id": "...", "name": "..."},
|
|
148
|
+
"labels": {...},
|
|
149
|
+
"view": {"created": "...", "setup_hint": "..."}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Display:
|
|
154
|
+
```
|
|
155
|
+
Linear connected!
|
|
156
|
+
|
|
157
|
+
Team: {team}
|
|
158
|
+
Project: {project.name}
|
|
159
|
+
|
|
160
|
+
All PRDs, epics, and tasks will sync to Linear.
|
|
161
|
+
Run /flux:prd to create your first PRD.
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
If `view.setup_hint` exists, show it as a tip.
|
|
165
|
+
|
|
166
|
+
## Key Points
|
|
167
|
+
|
|
168
|
+
- Always use `{"interactive": true}` (boolean) not a string
|
|
169
|
+
- The response `step` field tells you what stage you're at
|
|
170
|
+
- Use AskUserQuestion for team/project selection
|
|
171
|
+
- Store the selected IDs from previous responses to use in next calls
|
package/commands/prd.md
CHANGED
package/dist/server/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
import { createRequire } from "node:module";
|
|
3
2
|
var __create = Object.create;
|
|
4
3
|
var __getProtoOf = Object.getPrototypeOf;
|
|
@@ -22481,7 +22480,6 @@ var config2 = {
|
|
|
22481
22480
|
};
|
|
22482
22481
|
|
|
22483
22482
|
// src/server/db/index.ts
|
|
22484
|
-
import { Database } from "bun:sqlite";
|
|
22485
22483
|
import { existsSync as existsSync2, mkdirSync } from "node:fs";
|
|
22486
22484
|
|
|
22487
22485
|
// src/server/utils/logger.ts
|
|
@@ -22584,6 +22582,44 @@ CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
|
|
|
22584
22582
|
CREATE INDEX IF NOT EXISTS idx_criteria_parent ON acceptance_criteria(parent_type, parent_id);
|
|
22585
22583
|
`;
|
|
22586
22584
|
|
|
22585
|
+
// src/server/db/sqlite.ts
|
|
22586
|
+
var isBun = typeof process !== "undefined" && !!process.versions?.bun;
|
|
22587
|
+
var DatabaseImpl;
|
|
22588
|
+
if (isBun) {
|
|
22589
|
+
const bunSqlite = "bun:" + "sqlite";
|
|
22590
|
+
const { Database: BunDatabase } = await import(bunSqlite);
|
|
22591
|
+
DatabaseImpl = BunDatabase;
|
|
22592
|
+
} else {
|
|
22593
|
+
const BetterSqlite3 = (await import("better-sqlite3")).default;
|
|
22594
|
+
|
|
22595
|
+
class BetterSqlite3Wrapper {
|
|
22596
|
+
db;
|
|
22597
|
+
constructor(path) {
|
|
22598
|
+
this.db = new BetterSqlite3(path);
|
|
22599
|
+
}
|
|
22600
|
+
query(sql) {
|
|
22601
|
+
const stmt = this.db.prepare(sql);
|
|
22602
|
+
return {
|
|
22603
|
+
get: (...params) => stmt.get(...params),
|
|
22604
|
+
all: (...params) => stmt.all(...params),
|
|
22605
|
+
run: (...params) => {
|
|
22606
|
+
stmt.run(...params);
|
|
22607
|
+
}
|
|
22608
|
+
};
|
|
22609
|
+
}
|
|
22610
|
+
exec(sql) {
|
|
22611
|
+
this.db.exec(sql);
|
|
22612
|
+
}
|
|
22613
|
+
run(sql) {
|
|
22614
|
+
this.db.exec(sql);
|
|
22615
|
+
}
|
|
22616
|
+
close() {
|
|
22617
|
+
this.db.close();
|
|
22618
|
+
}
|
|
22619
|
+
}
|
|
22620
|
+
DatabaseImpl = BetterSqlite3Wrapper;
|
|
22621
|
+
}
|
|
22622
|
+
|
|
22587
22623
|
// src/server/db/ids.ts
|
|
22588
22624
|
function generateId(prefix = "") {
|
|
22589
22625
|
const timestamp = Date.now().toString(36);
|
|
@@ -22686,7 +22722,7 @@ function initDb() {
|
|
|
22686
22722
|
mkdirSync(config2.fluxPath, { recursive: true });
|
|
22687
22723
|
}
|
|
22688
22724
|
logger.info(`Opening database: ${config2.dbPath}`);
|
|
22689
|
-
db = new
|
|
22725
|
+
db = new DatabaseImpl(config2.dbPath);
|
|
22690
22726
|
db.run("PRAGMA foreign_keys = ON");
|
|
22691
22727
|
db.exec(SCHEMA);
|
|
22692
22728
|
logger.info("Database initialized successfully");
|
|
@@ -83874,6 +83910,18 @@ var PRD_STATUSES_WITH_LABELS = {
|
|
|
83874
83910
|
COMPLETED: null,
|
|
83875
83911
|
ARCHIVED: null
|
|
83876
83912
|
};
|
|
83913
|
+
var FLUX_MILESTONE_LABEL_PREFIX = "flux:milestone:";
|
|
83914
|
+
function getMilestoneLabel(tag) {
|
|
83915
|
+
return `${FLUX_MILESTONE_LABEL_PREFIX}${tag}`;
|
|
83916
|
+
}
|
|
83917
|
+
function extractTagFromLabels(labels) {
|
|
83918
|
+
for (const label of labels) {
|
|
83919
|
+
if (label.startsWith(FLUX_MILESTONE_LABEL_PREFIX)) {
|
|
83920
|
+
return label.substring(FLUX_MILESTONE_LABEL_PREFIX.length);
|
|
83921
|
+
}
|
|
83922
|
+
}
|
|
83923
|
+
return;
|
|
83924
|
+
}
|
|
83877
83925
|
|
|
83878
83926
|
// src/server/adapters/linear/mappers/epic.ts
|
|
83879
83927
|
function toLinearEpicState(status) {
|
|
@@ -84025,6 +84073,7 @@ class LinearAdapter {
|
|
|
84025
84073
|
title: issue2.title,
|
|
84026
84074
|
description: issue2.description,
|
|
84027
84075
|
status: toFluxPrdStatusFromIssue(issue2.stateName, issue2.labels),
|
|
84076
|
+
tag: extractTagFromLabels(issue2.labels),
|
|
84028
84077
|
createdAt: issue2.createdAt.toISOString(),
|
|
84029
84078
|
updatedAt: issue2.updatedAt.toISOString()
|
|
84030
84079
|
};
|
|
@@ -84064,13 +84113,17 @@ class LinearAdapter {
|
|
|
84064
84113
|
return issue2.labels.includes(this.config.defaultLabels.task);
|
|
84065
84114
|
}
|
|
84066
84115
|
async createPrd(input) {
|
|
84067
|
-
const
|
|
84116
|
+
const labelIds = [await this.getLabelId(this.config.defaultLabels.prd)];
|
|
84117
|
+
if (input.tag) {
|
|
84118
|
+
const tagLabelId = await this.getOrCreateLabel(getMilestoneLabel(input.tag));
|
|
84119
|
+
labelIds.push(tagLabelId);
|
|
84120
|
+
}
|
|
84068
84121
|
const createResult = await this.client.execute(() => this.client.client.createIssue({
|
|
84069
84122
|
title: input.title,
|
|
84070
84123
|
description: input.description,
|
|
84071
84124
|
teamId: this.config.teamId,
|
|
84072
84125
|
projectId: this.config.projectId,
|
|
84073
|
-
labelIds
|
|
84126
|
+
labelIds
|
|
84074
84127
|
}));
|
|
84075
84128
|
const rawIssue = await this.client.execute(() => createResult.issue);
|
|
84076
84129
|
if (!rawIssue) {
|
|
@@ -84092,7 +84145,10 @@ class LinearAdapter {
|
|
|
84092
84145
|
updatePayload.description = input.description;
|
|
84093
84146
|
if (input.status !== undefined) {
|
|
84094
84147
|
updatePayload.stateId = await this.getStateId(toLinearPrdIssueState(input.status));
|
|
84095
|
-
|
|
84148
|
+
}
|
|
84149
|
+
if (input.status !== undefined || input.tag !== undefined) {
|
|
84150
|
+
const currentStatus = toFluxPrdStatusFromIssue(issue2.stateName, issue2.labels);
|
|
84151
|
+
updatePayload.labelIds = await this.buildPrdLabelIdsWithTag(issue2.labels, input.status ?? currentStatus, input.tag !== undefined ? input.tag : extractTagFromLabels(issue2.labels));
|
|
84096
84152
|
}
|
|
84097
84153
|
await this.client.execute(() => issue2._raw.update(updatePayload));
|
|
84098
84154
|
const updated = await this.fetchIssue(ref);
|
|
@@ -84114,6 +84170,23 @@ class LinearAdapter {
|
|
|
84114
84170
|
}
|
|
84115
84171
|
return labelIds;
|
|
84116
84172
|
}
|
|
84173
|
+
async buildPrdLabelIdsWithTag(currentLabels, newStatus, newTag) {
|
|
84174
|
+
const statusLabels = getAllStatusLabels();
|
|
84175
|
+
const newStatusLabel = getStatusLabelForPrdStatus(newStatus);
|
|
84176
|
+
const labelsToKeep = currentLabels.filter((l) => !statusLabels.includes(l) && !l.startsWith(FLUX_MILESTONE_LABEL_PREFIX));
|
|
84177
|
+
if (newStatusLabel) {
|
|
84178
|
+
labelsToKeep.push(newStatusLabel);
|
|
84179
|
+
}
|
|
84180
|
+
if (newTag) {
|
|
84181
|
+
labelsToKeep.push(getMilestoneLabel(newTag));
|
|
84182
|
+
}
|
|
84183
|
+
const labelIds = [];
|
|
84184
|
+
for (const labelName of labelsToKeep) {
|
|
84185
|
+
const id = await this.getOrCreateLabel(labelName);
|
|
84186
|
+
labelIds.push(id);
|
|
84187
|
+
}
|
|
84188
|
+
return labelIds;
|
|
84189
|
+
}
|
|
84117
84190
|
async getOrCreateLabel(labelName) {
|
|
84118
84191
|
try {
|
|
84119
84192
|
return await this.getLabelId(labelName);
|
|
@@ -84139,9 +84212,18 @@ class LinearAdapter {
|
|
|
84139
84212
|
const limit = pagination?.limit ?? 50;
|
|
84140
84213
|
const offset = pagination?.offset ?? 0;
|
|
84141
84214
|
const linearFilter = {
|
|
84142
|
-
labels: { name: { eq: this.config.defaultLabels.prd } },
|
|
84143
84215
|
project: { id: { eq: this.config.projectId } }
|
|
84144
84216
|
};
|
|
84217
|
+
if (filters?.tag) {
|
|
84218
|
+
linearFilter.labels = {
|
|
84219
|
+
and: [
|
|
84220
|
+
{ name: { eq: this.config.defaultLabels.prd } },
|
|
84221
|
+
{ name: { eq: getMilestoneLabel(filters.tag) } }
|
|
84222
|
+
]
|
|
84223
|
+
};
|
|
84224
|
+
} else {
|
|
84225
|
+
linearFilter.labels = { name: { eq: this.config.defaultLabels.prd } };
|
|
84226
|
+
}
|
|
84145
84227
|
if (filters?.status) {
|
|
84146
84228
|
linearFilter.state = {
|
|
84147
84229
|
name: { eq: toLinearPrdIssueState(filters.status) }
|
|
@@ -85431,14 +85513,20 @@ function clearAdapterCache() {
|
|
|
85431
85513
|
var LINEAR_API_KEY_ENV = "LINEAR_API_KEY";
|
|
85432
85514
|
var inputSchema = exports_external.object({
|
|
85433
85515
|
apiKey: exports_external.string().optional(),
|
|
85434
|
-
teamId: exports_external.string().
|
|
85516
|
+
teamId: exports_external.string().optional(),
|
|
85435
85517
|
projectName: exports_external.string().optional(),
|
|
85436
85518
|
existingProjectId: exports_external.string().optional(),
|
|
85437
85519
|
prdLabel: exports_external.string().optional().default("prd"),
|
|
85438
85520
|
epicLabel: exports_external.string().optional().default("epic"),
|
|
85439
|
-
taskLabel: exports_external.string().optional().default("task")
|
|
85440
|
-
|
|
85441
|
-
|
|
85521
|
+
taskLabel: exports_external.string().optional().default("task"),
|
|
85522
|
+
interactive: exports_external.boolean().optional().default(false)
|
|
85523
|
+
}).refine((data) => {
|
|
85524
|
+
if (data.interactive) {
|
|
85525
|
+
return true;
|
|
85526
|
+
}
|
|
85527
|
+
return data.teamId && data.teamId.length > 0 && (data.projectName || data.existingProjectId);
|
|
85528
|
+
}, {
|
|
85529
|
+
message: "teamId and either projectName or existingProjectId are required (unless interactive mode)"
|
|
85442
85530
|
});
|
|
85443
85531
|
function resolveApiKey(inputApiKey) {
|
|
85444
85532
|
if (inputApiKey) {
|
|
@@ -85516,19 +85604,72 @@ async function createDefaultView(client, teamId, projectId, projectName, labels)
|
|
|
85516
85604
|
};
|
|
85517
85605
|
}
|
|
85518
85606
|
}
|
|
85607
|
+
async function listTeams(client) {
|
|
85608
|
+
const teamsResult = await client.teams();
|
|
85609
|
+
return teamsResult.nodes.map((team) => ({
|
|
85610
|
+
id: team.id,
|
|
85611
|
+
name: team.name,
|
|
85612
|
+
key: team.key
|
|
85613
|
+
}));
|
|
85614
|
+
}
|
|
85615
|
+
async function listProjects(client, teamId) {
|
|
85616
|
+
const projectsResult = await client.projects({
|
|
85617
|
+
filter: {
|
|
85618
|
+
accessibleTeams: { some: { id: { eq: teamId } } },
|
|
85619
|
+
state: { nin: ["canceled"] }
|
|
85620
|
+
}
|
|
85621
|
+
});
|
|
85622
|
+
return projectsResult.nodes.map((project) => ({
|
|
85623
|
+
id: project.id,
|
|
85624
|
+
name: project.name,
|
|
85625
|
+
description: project.description ?? null,
|
|
85626
|
+
state: project.state,
|
|
85627
|
+
updatedAt: project.updatedAt.toISOString()
|
|
85628
|
+
}));
|
|
85629
|
+
}
|
|
85519
85630
|
async function handler(input) {
|
|
85520
85631
|
const parsed = inputSchema.parse(input);
|
|
85521
85632
|
const { apiKey, source: apiKeySource } = resolveApiKey(parsed.apiKey);
|
|
85522
85633
|
const client = new LinearClient({ apiKey });
|
|
85634
|
+
let viewerName;
|
|
85635
|
+
let viewerEmail;
|
|
85523
85636
|
try {
|
|
85524
85637
|
const viewer = await client.viewer;
|
|
85525
85638
|
if (!viewer) {
|
|
85526
85639
|
throw new Error("Invalid API key");
|
|
85527
85640
|
}
|
|
85641
|
+
viewerName = viewer.name;
|
|
85642
|
+
viewerEmail = viewer.email;
|
|
85528
85643
|
} catch (error48) {
|
|
85529
85644
|
const message = error48 instanceof Error ? error48.message : String(error48);
|
|
85530
85645
|
throw new Error(`Invalid Linear API key (source: ${apiKeySource}): ${message}`);
|
|
85531
85646
|
}
|
|
85647
|
+
if (parsed.interactive && !parsed.teamId) {
|
|
85648
|
+
const teams = await listTeams(client);
|
|
85649
|
+
return {
|
|
85650
|
+
step: "select_team",
|
|
85651
|
+
user: { name: viewerName, email: viewerEmail },
|
|
85652
|
+
api_key_source: apiKeySource,
|
|
85653
|
+
teams
|
|
85654
|
+
};
|
|
85655
|
+
}
|
|
85656
|
+
if (parsed.interactive && parsed.teamId && !parsed.projectName && !parsed.existingProjectId) {
|
|
85657
|
+
const team2 = await client.team(parsed.teamId);
|
|
85658
|
+
if (!team2) {
|
|
85659
|
+
throw new Error(`Team ${parsed.teamId} not found`);
|
|
85660
|
+
}
|
|
85661
|
+
const projects = await listProjects(client, parsed.teamId);
|
|
85662
|
+
return {
|
|
85663
|
+
step: "select_project",
|
|
85664
|
+
user: { name: viewerName, email: viewerEmail },
|
|
85665
|
+
api_key_source: apiKeySource,
|
|
85666
|
+
team: { id: team2.id, name: team2.name, key: team2.key },
|
|
85667
|
+
projects
|
|
85668
|
+
};
|
|
85669
|
+
}
|
|
85670
|
+
if (!parsed.teamId) {
|
|
85671
|
+
throw new Error("teamId is required");
|
|
85672
|
+
}
|
|
85532
85673
|
const team = await client.team(parsed.teamId);
|
|
85533
85674
|
if (!team) {
|
|
85534
85675
|
throw new Error(`Team ${parsed.teamId} not found`);
|
|
@@ -85605,7 +85746,7 @@ async function handler(input) {
|
|
|
85605
85746
|
}
|
|
85606
85747
|
var configureLinearTool = {
|
|
85607
85748
|
name: "configure_linear",
|
|
85608
|
-
description: "Configure Linear integration for the Flux project. " + "API Key Resolution (priority order): 1) LINEAR_API_KEY environment variable (recommended), 2) Existing .flux/linear-config.json, 3) apiKey parameter (not recommended - visible in logs). " + "Required: teamId (Linear team ID), and either projectName (to create new) or existingProjectId (to use existing). " + "Optional: apiKey, prdLabel (default 'prd'), epicLabel (default 'epic'), taskLabel (default 'task'). " + "Creates a Linear Project, ensures labels exist, creates 'Flux' custom view for new projects, saves config to .flux/linear-config.json. " + "Returns {success, message, api_key_source, team, project, labels, view}. " + "Setup: export LINEAR_API_KEY=lin_api_xxx before running.",
|
|
85749
|
+
description: "Configure Linear integration for the Flux project. " + "Use interactive mode for guided setup: " + "1) Call with {interactive: true} to get list of teams. " + "2) Call with {interactive: true, teamId: '...'} to get list of projects. " + "3) Call with full params to configure. " + "API Key Resolution (priority order): 1) LINEAR_API_KEY environment variable (recommended), 2) Existing .flux/linear-config.json, 3) apiKey parameter (not recommended - visible in logs). " + "Required (non-interactive): teamId (Linear team ID), and either projectName (to create new) or existingProjectId (to use existing). " + "Optional: apiKey, prdLabel (default 'prd'), epicLabel (default 'epic'), taskLabel (default 'task'). " + "Creates a Linear Project, ensures labels exist, creates 'Flux' custom view for new projects, saves config to .flux/linear-config.json. " + "Returns {success, message, api_key_source, team, project, labels, view} on final configuration. " + "Setup: export LINEAR_API_KEY=lin_api_xxx before running.",
|
|
85609
85750
|
inputSchema,
|
|
85610
85751
|
handler
|
|
85611
85752
|
};
|
|
@@ -86184,14 +86325,7 @@ var getStatsTool = {
|
|
|
86184
86325
|
handler: handler8
|
|
86185
86326
|
};
|
|
86186
86327
|
// src/version.ts
|
|
86187
|
-
|
|
86188
|
-
import { dirname as dirname2, join as join2 } from "node:path";
|
|
86189
|
-
import { fileURLToPath } from "node:url";
|
|
86190
|
-
var __filename2 = fileURLToPath(import.meta.url);
|
|
86191
|
-
var __dirname2 = dirname2(__filename2);
|
|
86192
|
-
var packageJsonPath = join2(__dirname2, "..", "package.json");
|
|
86193
|
-
var packageJson = JSON.parse(readFileSync6(packageJsonPath, "utf-8"));
|
|
86194
|
-
var VERSION = packageJson.version;
|
|
86328
|
+
var VERSION = "0.1.0";
|
|
86195
86329
|
|
|
86196
86330
|
// src/server/tools/get-version.ts
|
|
86197
86331
|
var inputSchema10 = exports_external.object({});
|
|
@@ -86208,15 +86342,15 @@ var getVersionTool = {
|
|
|
86208
86342
|
handler: handler9
|
|
86209
86343
|
};
|
|
86210
86344
|
// src/server/tools/init-project.ts
|
|
86211
|
-
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as
|
|
86212
|
-
import { dirname as
|
|
86345
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
86346
|
+
import { dirname as dirname2, join as join2 } from "node:path";
|
|
86213
86347
|
function getStatusLineBinaryPath() {
|
|
86214
|
-
const thisDir =
|
|
86215
|
-
return
|
|
86348
|
+
const thisDir = dirname2(new URL(import.meta.url).pathname);
|
|
86349
|
+
return join2(thisDir, "..", "..", "..", "bin", "flux-status");
|
|
86216
86350
|
}
|
|
86217
86351
|
function setupClaudeSettings(projectRoot) {
|
|
86218
|
-
const claudeDir =
|
|
86219
|
-
const settingsPath =
|
|
86352
|
+
const claudeDir = join2(projectRoot, ".claude");
|
|
86353
|
+
const settingsPath = join2(claudeDir, "settings.local.json");
|
|
86220
86354
|
const statusBinaryPath = getStatusLineBinaryPath();
|
|
86221
86355
|
if (!existsSync8(statusBinaryPath)) {
|
|
86222
86356
|
return;
|
|
@@ -86227,7 +86361,7 @@ function setupClaudeSettings(projectRoot) {
|
|
|
86227
86361
|
let settings = {};
|
|
86228
86362
|
if (existsSync8(settingsPath)) {
|
|
86229
86363
|
try {
|
|
86230
|
-
settings = JSON.parse(
|
|
86364
|
+
settings = JSON.parse(readFileSync6(settingsPath, "utf-8"));
|
|
86231
86365
|
} catch {
|
|
86232
86366
|
settings = {};
|
|
86233
86367
|
}
|
|
@@ -86393,7 +86527,7 @@ var queryEntitiesTool = {
|
|
|
86393
86527
|
handler: handler11
|
|
86394
86528
|
};
|
|
86395
86529
|
// src/server/tools/render-status.ts
|
|
86396
|
-
import { existsSync as existsSync9, readFileSync as
|
|
86530
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "node:fs";
|
|
86397
86531
|
|
|
86398
86532
|
// src/utils/display.ts
|
|
86399
86533
|
function renderProgressBar(percentage, width) {
|
|
@@ -86506,7 +86640,7 @@ function getProjectInfo() {
|
|
|
86506
86640
|
return null;
|
|
86507
86641
|
}
|
|
86508
86642
|
try {
|
|
86509
|
-
const content =
|
|
86643
|
+
const content = readFileSync7(config2.projectJsonPath, "utf-8");
|
|
86510
86644
|
const project = JSON.parse(content);
|
|
86511
86645
|
return {
|
|
86512
86646
|
name: project.name || "Unnamed Project",
|
package/manifest.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./manifest.schema.json",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"structure": {
|
|
5
|
+
"commands": ["flux.md", "prd.md", "breakdown.md", "implement.md"],
|
|
6
|
+
"skills": [
|
|
7
|
+
"agent-creator",
|
|
8
|
+
"epic-template",
|
|
9
|
+
"flux-orchestrator",
|
|
10
|
+
"prd-template"
|
|
11
|
+
],
|
|
12
|
+
"agents": ["coder.md", "critic.md", "researcher.md", "verifier.md"],
|
|
13
|
+
"hooks": []
|
|
14
|
+
}
|
|
15
|
+
}
|
package/package.json
CHANGED
|
@@ -1,32 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cliangdev/flux-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Claude Code plugin for AI-first workflow orchestration with MCP server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/server/index.js",
|
|
7
7
|
"bin": {
|
|
8
|
-
"flux-plugin": "./
|
|
8
|
+
"flux-plugin": "./bin/install.cjs"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
|
+
"bin/",
|
|
11
12
|
"dist/",
|
|
12
13
|
"skills/",
|
|
13
|
-
"commands/"
|
|
14
|
+
"commands/",
|
|
15
|
+
"agents/",
|
|
16
|
+
"manifest.json"
|
|
14
17
|
],
|
|
15
18
|
"scripts": {
|
|
16
19
|
"dev": "bun run src/server/index.ts",
|
|
17
|
-
"build": "bun build src/server/index.ts --outdir dist/server --target node",
|
|
20
|
+
"build": "bun build src/server/index.ts --outdir dist/server --target node --external better-sqlite3",
|
|
18
21
|
"postbuild": "node -e \"const fs=require('fs');const f='dist/server/index.js';const c=fs.readFileSync(f,'utf-8');if(!c.startsWith('#!/usr/bin/env node')){fs.writeFileSync(f,'#!/usr/bin/env node\\n'+c)}\"",
|
|
19
22
|
"build:compile": "bun build --compile --outfile bin/flux-server src/server/index.ts && bun build --compile --outfile bin/flux-status src/status-line/index.ts",
|
|
20
23
|
"build:compile:server": "bun build --compile --outfile bin/flux-server src/server/index.ts",
|
|
21
24
|
"build:compile:status": "bun build --compile --outfile bin/flux-status src/status-line/index.ts",
|
|
22
|
-
"
|
|
25
|
+
"validate": "node scripts/validate-structure.cjs",
|
|
26
|
+
"test:integration": "bun test scripts/__tests__/integration.test.ts --timeout 120000",
|
|
27
|
+
"prepublishOnly": "bun run validate && bun run build && bun run test:integration",
|
|
23
28
|
"test": "bun test",
|
|
24
29
|
"test:linear-description": "bun run src/server/adapters/__tests__/linear-description-test.ts",
|
|
25
30
|
"typecheck": "tsc --noEmit",
|
|
26
31
|
"lint": "biome check .",
|
|
27
32
|
"lint:fix": "biome check --write .",
|
|
28
33
|
"format": "biome format --write .",
|
|
29
|
-
"verify-release": "bun run scripts/verify-release.ts"
|
|
34
|
+
"verify-release": "bun run scripts/verify-release.ts",
|
|
35
|
+
"release": "./scripts/release.sh"
|
|
30
36
|
},
|
|
31
37
|
"repository": {
|
|
32
38
|
"type": "git",
|
|
@@ -47,12 +53,14 @@
|
|
|
47
53
|
"license": "MIT",
|
|
48
54
|
"devDependencies": {
|
|
49
55
|
"@biomejs/biome": "^2.3.11",
|
|
56
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
50
57
|
"@types/bun": "^1.3.6",
|
|
51
58
|
"typescript": "^5.0.0"
|
|
52
59
|
},
|
|
53
60
|
"dependencies": {
|
|
54
61
|
"@linear/sdk": "^70.0.0",
|
|
55
62
|
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
63
|
+
"better-sqlite3": "^12.6.2",
|
|
56
64
|
"chalk": "^5.4.1",
|
|
57
65
|
"zod": "^4.3.5"
|
|
58
66
|
},
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
+
name: flux:agent-creator
|
|
2
3
|
description: Guide for creating effective subagents. Use when users want to create a new agent that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.
|
|
4
|
+
user-invocable: false
|
|
3
5
|
---
|
|
4
6
|
|
|
5
7
|
# Agent Creator Skill
|