buildflow-dev 1.0.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/LICENSE +21 -0
- package/README.md +266 -0
- package/bin/buildflow.js +80 -0
- package/package.json +60 -0
- package/src/commands/audit.js +230 -0
- package/src/commands/init.js +239 -0
- package/src/commands/install.js +537 -0
- package/src/commands/status.js +62 -0
- package/src/commands/update.js +35 -0
- package/src/index.js +5 -0
- package/src/utils/welcome.js +83 -0
- package/templates/CLAUDE.md +75 -0
- package/templates/commands/audit.md +119 -0
- package/templates/commands/back.md +59 -0
- package/templates/commands/build.md +61 -0
- package/templates/commands/check.md +62 -0
- package/templates/commands/explain.md +53 -0
- package/templates/commands/help.md +84 -0
- package/templates/commands/modify.md +65 -0
- package/templates/commands/onboard.md +78 -0
- package/templates/commands/plan.md +60 -0
- package/templates/commands/refactor.md +58 -0
- package/templates/commands/ship.md +97 -0
- package/templates/commands/start.md +39 -0
- package/templates/commands/status.md +50 -0
- package/templates/commands/think.md +49 -0
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import ora from 'ora'
|
|
3
|
+
import { prompt } from 'enquirer'
|
|
4
|
+
import which from 'which'
|
|
5
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from 'fs'
|
|
6
|
+
import { join, dirname } from 'path'
|
|
7
|
+
import { homedir } from 'os'
|
|
8
|
+
import { fileURLToPath } from 'url'
|
|
9
|
+
import { execSync } from 'child_process'
|
|
10
|
+
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
12
|
+
|
|
13
|
+
const TOOLS = {
|
|
14
|
+
|
|
15
|
+
claude: {
|
|
16
|
+
id: 'claude',
|
|
17
|
+
name: 'Claude Code',
|
|
18
|
+
description: "Anthropic's agentic coding assistant",
|
|
19
|
+
icon: '🟣',
|
|
20
|
+
docsUrl: 'https://docs.anthropic.com/claude-code',
|
|
21
|
+
|
|
22
|
+
detect() {
|
|
23
|
+
const hasCli = (() => { try { which.sync('claude'); return true } catch { return false } })()
|
|
24
|
+
const hasClaudeDir = existsSync(join(homedir(), '.claude'))
|
|
25
|
+
return hasCli || hasClaudeDir
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
installGlobal(commandFiles) {
|
|
29
|
+
const dir = join(homedir(), '.claude', 'commands')
|
|
30
|
+
mkdirSync(dir, { recursive: true })
|
|
31
|
+
for (const [name, content] of Object.entries(commandFiles)) {
|
|
32
|
+
writeFileSync(join(dir, `buildflow-${name}.md`), content)
|
|
33
|
+
}
|
|
34
|
+
return dir
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
installLocal(commandFiles) {
|
|
38
|
+
const dir = join(process.cwd(), '.claude', 'commands')
|
|
39
|
+
mkdirSync(dir, { recursive: true })
|
|
40
|
+
for (const [name, content] of Object.entries(commandFiles)) {
|
|
41
|
+
writeFileSync(join(dir, `buildflow-${name}.md`), content)
|
|
42
|
+
}
|
|
43
|
+
writeFileSync(join(process.cwd(), 'CLAUDE.md'), claudeMdContent())
|
|
44
|
+
return dir
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
triggerNote: 'Type "/" in Claude Code to see /buildflow-* commands',
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
gemini: {
|
|
51
|
+
id: 'gemini',
|
|
52
|
+
name: 'Gemini CLI',
|
|
53
|
+
description: "Google's Gemini command-line AI assistant",
|
|
54
|
+
icon: '🔵',
|
|
55
|
+
docsUrl: 'https://github.com/google-gemini/gemini-cli',
|
|
56
|
+
|
|
57
|
+
detect() {
|
|
58
|
+
try { which.sync('gemini'); return true } catch { return false }
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
installGlobal(commandFiles) {
|
|
62
|
+
const dir = join(homedir(), '.gemini', 'commands')
|
|
63
|
+
mkdirSync(dir, { recursive: true })
|
|
64
|
+
const contextPath = join(homedir(), '.gemini', 'GEMINI.md')
|
|
65
|
+
const existingContent = existsSync(contextPath) ? readFileSync(contextPath, 'utf8') : ''
|
|
66
|
+
if (!existingContent.includes('## BuildFlow Commands')) {
|
|
67
|
+
writeFileSync(contextPath, existingContent + '\n\n' + geminiContextBlock(commandFiles))
|
|
68
|
+
}
|
|
69
|
+
for (const [name, content] of Object.entries(commandFiles)) {
|
|
70
|
+
writeFileSync(join(dir, `${name}.md`), content)
|
|
71
|
+
}
|
|
72
|
+
return dir
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
installLocal(commandFiles) {
|
|
76
|
+
const dir = join(process.cwd(), '.gemini', 'commands')
|
|
77
|
+
mkdirSync(dir, { recursive: true })
|
|
78
|
+
const contextPath = join(process.cwd(), 'GEMINI.md')
|
|
79
|
+
const existingContent = existsSync(contextPath) ? readFileSync(contextPath, 'utf8') : ''
|
|
80
|
+
if (!existingContent.includes('## BuildFlow Commands')) {
|
|
81
|
+
writeFileSync(contextPath, existingContent + '\n\n' + geminiContextBlock(commandFiles))
|
|
82
|
+
}
|
|
83
|
+
for (const [name, content] of Object.entries(commandFiles)) {
|
|
84
|
+
writeFileSync(join(dir, `${name}.md`), content)
|
|
85
|
+
}
|
|
86
|
+
return dir
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
triggerNote: 'In Gemini CLI, type "/" or @buildflow to use commands',
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
codex: {
|
|
93
|
+
id: 'codex',
|
|
94
|
+
name: 'Codex CLI',
|
|
95
|
+
description: "OpenAI's Codex command-line coding agent",
|
|
96
|
+
icon: '🟢',
|
|
97
|
+
docsUrl: 'https://github.com/openai/codex',
|
|
98
|
+
|
|
99
|
+
detect() {
|
|
100
|
+
try { which.sync('codex'); return true } catch { return false }
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
installGlobal(commandFiles) {
|
|
104
|
+
const dir = join(homedir(), '.codex', 'instructions')
|
|
105
|
+
mkdirSync(dir, { recursive: true })
|
|
106
|
+
for (const [name, content] of Object.entries(commandFiles)) {
|
|
107
|
+
writeFileSync(join(dir, `buildflow-${name}.md`), content)
|
|
108
|
+
}
|
|
109
|
+
patchAgentsMd(join(homedir(), '.codex', 'AGENTS.md'), 'global')
|
|
110
|
+
return dir
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
installLocal(commandFiles) {
|
|
114
|
+
const dir = join(process.cwd(), '.codex', 'instructions')
|
|
115
|
+
mkdirSync(dir, { recursive: true })
|
|
116
|
+
for (const [name, content] of Object.entries(commandFiles)) {
|
|
117
|
+
writeFileSync(join(dir, `buildflow-${name}.md`), content)
|
|
118
|
+
}
|
|
119
|
+
patchAgentsMd(join(process.cwd(), 'AGENTS.md'), 'local')
|
|
120
|
+
return dir
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
triggerNote: 'In Codex CLI, say "use the buildflow-start instructions" or type /buildflow-start',
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
cursor: {
|
|
127
|
+
id: 'cursor',
|
|
128
|
+
name: 'Cursor',
|
|
129
|
+
description: 'AI-powered code editor with built-in LLM',
|
|
130
|
+
icon: 'âš«',
|
|
131
|
+
docsUrl: 'https://cursor.sh',
|
|
132
|
+
|
|
133
|
+
detect() {
|
|
134
|
+
const hasCursorApp = existsSync('/Applications/Cursor.app')
|
|
135
|
+
|| existsSync('C:\\Users\\Public\\Desktop\\Cursor.lnk')
|
|
136
|
+
|| (() => { try { which.sync('cursor'); return true } catch { return false } })()
|
|
137
|
+
const hasCursorDir = existsSync(join(homedir(), '.cursor'))
|
|
138
|
+
return hasCursorApp || hasCursorDir
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
installGlobal(commandFiles) {
|
|
142
|
+
return this.installLocal(commandFiles)
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
installLocal(commandFiles) {
|
|
146
|
+
const rulesDir = join(process.cwd(), '.cursor', 'rules')
|
|
147
|
+
mkdirSync(rulesDir, { recursive: true })
|
|
148
|
+
writeFileSync(join(rulesDir, 'buildflow.mdc'), cursorRulesContent(commandFiles))
|
|
149
|
+
return rulesDir
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
triggerNote: 'In Cursor Chat, type @BuildFlow or reference rules using # in composer',
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
cline: {
|
|
156
|
+
id: 'cline',
|
|
157
|
+
name: 'Cline (VS Code extension)',
|
|
158
|
+
description: 'Autonomous coding agent for VS Code',
|
|
159
|
+
icon: '🔷',
|
|
160
|
+
docsUrl: 'https://github.com/cline/cline',
|
|
161
|
+
|
|
162
|
+
detect() {
|
|
163
|
+
const extDirs = [
|
|
164
|
+
join(homedir(), '.vscode', 'extensions'),
|
|
165
|
+
join(homedir(), '.vscode-server', 'extensions'),
|
|
166
|
+
]
|
|
167
|
+
return extDirs.some(d =>
|
|
168
|
+
existsSync(d) &&
|
|
169
|
+
readdirSafe(d).some(f => f.startsWith('saoudrizwan.claude-dev'))
|
|
170
|
+
)
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
installGlobal(commandFiles) {
|
|
174
|
+
return this.installLocal(commandFiles)
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
installLocal(commandFiles) {
|
|
178
|
+
writeFileSync(join(process.cwd(), '.clinerules'), clineRulesContent(commandFiles))
|
|
179
|
+
return process.cwd()
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
triggerNote: 'Cline reads .clinerules automatically. Type "use /buildflow-start" in Cline chat.',
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
continue: {
|
|
186
|
+
id: 'continue',
|
|
187
|
+
name: 'Continue (VS Code / JetBrains)',
|
|
188
|
+
description: 'Open-source AI code assistant extension',
|
|
189
|
+
icon: '🟡',
|
|
190
|
+
docsUrl: 'https://continue.dev',
|
|
191
|
+
|
|
192
|
+
detect() {
|
|
193
|
+
return existsSync(join(homedir(), '.continue', 'config.json'))
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
installGlobal(commandFiles) {
|
|
197
|
+
const dir = join(homedir(), '.continue', 'buildflow')
|
|
198
|
+
mkdirSync(dir, { recursive: true })
|
|
199
|
+
for (const [name, content] of Object.entries(commandFiles)) {
|
|
200
|
+
writeFileSync(join(dir, `${name}.md`), content)
|
|
201
|
+
}
|
|
202
|
+
patchContinueConfig(commandFiles)
|
|
203
|
+
return dir
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
installLocal(commandFiles) {
|
|
207
|
+
const dir = join(process.cwd(), '.continue', 'buildflow')
|
|
208
|
+
mkdirSync(dir, { recursive: true })
|
|
209
|
+
for (const [name, content] of Object.entries(commandFiles)) {
|
|
210
|
+
writeFileSync(join(dir, `${name}.md`), content)
|
|
211
|
+
}
|
|
212
|
+
return dir
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
triggerNote: 'In Continue, use @BuildFlow in chat or trigger custom slash commands.',
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function geminiContextBlock(commandFiles) {
|
|
221
|
+
const commandList = Object.keys(commandFiles)
|
|
222
|
+
.map(name => `- \`/buildflow-${name}\`: see .gemini/commands/${name}.md`)
|
|
223
|
+
.join('\n')
|
|
224
|
+
return `## BuildFlow Commands\n\nWhen the user types a /buildflow-* command, load and execute the corresponding file from .gemini/commands/.\n\n${commandList}`
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function patchAgentsMd(filePath, scope) {
|
|
228
|
+
const existing = existsSync(filePath) ? readFileSync(filePath, 'utf8') : ''
|
|
229
|
+
if (existing.includes('BuildFlow')) return
|
|
230
|
+
const dir = scope === 'global' ? '~/.codex/instructions/' : '.codex/instructions/'
|
|
231
|
+
const block = `\n\n## BuildFlow Instructions\n\nWhen the user types /buildflow-<command>, load the matching file from ${dir} and follow those instructions.\n\nAvailable commands: start, think, plan, build, check, ship, onboard, modify, refactor, audit, status, explain, back, help\n`
|
|
232
|
+
writeFileSync(filePath, existing + block)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function cursorRulesContent(commandFiles) {
|
|
236
|
+
const commandDescriptions = Object.keys(commandFiles)
|
|
237
|
+
.map(name => `- @buildflow-${name}`)
|
|
238
|
+
.join('\n')
|
|
239
|
+
return `---
|
|
240
|
+
description: BuildFlow development orchestration commands
|
|
241
|
+
globs: ["**/*"]
|
|
242
|
+
alwaysApply: false
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
# BuildFlow v3.0
|
|
246
|
+
|
|
247
|
+
You are integrated with BuildFlow, an adaptive development orchestration system.
|
|
248
|
+
|
|
249
|
+
## Available Commands
|
|
250
|
+
|
|
251
|
+
When the user types @buildflow-<command> or references a buildflow command, execute the corresponding workflow:
|
|
252
|
+
|
|
253
|
+
${commandDescriptions}
|
|
254
|
+
|
|
255
|
+
## Core Rules
|
|
256
|
+
|
|
257
|
+
1. Load .buildflow/memory/light.md at session start
|
|
258
|
+
2. Ask confidence (1-5) on major decisions
|
|
259
|
+
3. Show alternatives before locking choices
|
|
260
|
+
4. Add LEARN: comments for new concepts
|
|
261
|
+
5. Create restore points before destructive changes
|
|
262
|
+
6. Run security checks before shipping
|
|
263
|
+
7. Cite sources with trust scores
|
|
264
|
+
|
|
265
|
+
## Agents
|
|
266
|
+
|
|
267
|
+
Use these specialized agents based on context:
|
|
268
|
+
- Strategist: vision and discussion
|
|
269
|
+
- Researcher: parallel web research with sources
|
|
270
|
+
- Synthesizer: combine parallel research
|
|
271
|
+
- Architect: dependency-aware planning
|
|
272
|
+
- Builder: code matching user's style
|
|
273
|
+
- Reviewer: quality checks
|
|
274
|
+
- Cartographer: map existing codebases (onboarding)
|
|
275
|
+
- Surgeon: precise modifications to existing code
|
|
276
|
+
- Security Auditor: OWASP Top 10 security scanning
|
|
277
|
+
`
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function clineRulesContent(commandFiles) {
|
|
281
|
+
const commandList = Object.keys(commandFiles)
|
|
282
|
+
.map(name => `- /buildflow-${name}`)
|
|
283
|
+
.join('\n')
|
|
284
|
+
return `# BuildFlow v3.0 Rules for Cline
|
|
285
|
+
|
|
286
|
+
## Slash Commands
|
|
287
|
+
|
|
288
|
+
When the user types any of the following commands, load the corresponding instruction file from .buildflow/commands/:
|
|
289
|
+
|
|
290
|
+
${commandList}
|
|
291
|
+
|
|
292
|
+
## Core Behavior
|
|
293
|
+
|
|
294
|
+
- Always load .buildflow/memory/light.md first
|
|
295
|
+
- Ask confidence (1-5) on major architectural decisions
|
|
296
|
+
- Show alternatives before making choices
|
|
297
|
+
- Generate LEARN: comments for unfamiliar concepts
|
|
298
|
+
- Run /buildflow-audit before shipping
|
|
299
|
+
- Cite all research sources with trust scores (1-5)
|
|
300
|
+
- Create git restore points before destructive operations
|
|
301
|
+
|
|
302
|
+
## Memory
|
|
303
|
+
|
|
304
|
+
Light memory is stored in .buildflow/memory/light.md.
|
|
305
|
+
Keep it under 5K tokens. Distill insights, don't log events.
|
|
306
|
+
`
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function patchContinueConfig(commandFiles) {
|
|
310
|
+
const configPath = join(homedir(), '.continue', 'config.json')
|
|
311
|
+
if (!existsSync(configPath)) return
|
|
312
|
+
|
|
313
|
+
let config
|
|
314
|
+
try {
|
|
315
|
+
config = JSON.parse(readFileSync(configPath, 'utf8'))
|
|
316
|
+
} catch {
|
|
317
|
+
return
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (!config.slashCommands) config.slashCommands = []
|
|
321
|
+
|
|
322
|
+
const existing = config.slashCommands.map(c => c.name)
|
|
323
|
+
const toAdd = Object.keys(commandFiles)
|
|
324
|
+
.filter(name => !existing.includes(`buildflow-${name}`))
|
|
325
|
+
.map(name => ({
|
|
326
|
+
name: `buildflow-${name}`,
|
|
327
|
+
description: `BuildFlow: ${name}`,
|
|
328
|
+
prompt: `Execute the BuildFlow ${name} workflow from .continue/buildflow/${name}.md`,
|
|
329
|
+
}))
|
|
330
|
+
|
|
331
|
+
config.slashCommands.push(...toAdd)
|
|
332
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2))
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function claudeMdContent() {
|
|
336
|
+
const templatePath = join(__dirname, '../../templates/CLAUDE.md')
|
|
337
|
+
return readFileSync(templatePath, 'utf8').replace('{{APP_NAME}}', detectAppName())
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function readdirSafe(dir) {
|
|
341
|
+
try {
|
|
342
|
+
return readdirSync(dir)
|
|
343
|
+
} catch {
|
|
344
|
+
return []
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function detectAppName() {
|
|
349
|
+
const pkgPath = join(process.cwd(), 'package.json')
|
|
350
|
+
if (existsSync(pkgPath)) {
|
|
351
|
+
try {
|
|
352
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
353
|
+
return pkg.name || 'my-project'
|
|
354
|
+
} catch {
|
|
355
|
+
return 'my-project'
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return process.cwd().split(/[/\\]/).pop() || 'my-project'
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function loadCommandTemplates() {
|
|
362
|
+
const templatesDir = join(__dirname, '../../templates/commands')
|
|
363
|
+
const commands = {}
|
|
364
|
+
const commandNames = [
|
|
365
|
+
'start', 'think', 'plan', 'build', 'check', 'ship',
|
|
366
|
+
'onboard', 'modify', 'refactor', 'audit',
|
|
367
|
+
'status', 'explain', 'back', 'help',
|
|
368
|
+
]
|
|
369
|
+
for (const name of commandNames) {
|
|
370
|
+
const filePath = join(templatesDir, `${name}.md`)
|
|
371
|
+
if (existsSync(filePath)) {
|
|
372
|
+
commands[name] = readFileSync(filePath, 'utf8')
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return commands
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export async function run(opts = {}) {
|
|
379
|
+
console.log('\n' + chalk.bold.white(' BuildFlow — AI Tool Integration'))
|
|
380
|
+
console.log(chalk.dim(' Install slash commands into your AI coding tools\n'))
|
|
381
|
+
|
|
382
|
+
const spinner = ora('Detecting installed AI tools...').start()
|
|
383
|
+
await new Promise(r => setTimeout(r, 600))
|
|
384
|
+
|
|
385
|
+
const detected = {}
|
|
386
|
+
for (const [id, tool] of Object.entries(TOOLS)) {
|
|
387
|
+
detected[id] = tool.detect()
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
spinner.stop()
|
|
391
|
+
|
|
392
|
+
const detectedList = Object.entries(detected).filter(([, found]) => found)
|
|
393
|
+
const notFoundList = Object.entries(detected).filter(([, found]) => !found)
|
|
394
|
+
|
|
395
|
+
if (detectedList.length > 0) {
|
|
396
|
+
console.log(chalk.green(' ✓ Detected on your system:'))
|
|
397
|
+
for (const [id] of detectedList) {
|
|
398
|
+
const t = TOOLS[id]
|
|
399
|
+
console.log(chalk.green(` ${t.icon} ${t.name}`) + chalk.dim(` — ${t.description}`))
|
|
400
|
+
}
|
|
401
|
+
} else {
|
|
402
|
+
console.log(chalk.yellow(' âš No AI tools detected automatically.'))
|
|
403
|
+
console.log(chalk.dim(' You can still install for tools not on this list.\n'))
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (notFoundList.length > 0) {
|
|
407
|
+
console.log(chalk.dim('\n Not detected (can still install):'))
|
|
408
|
+
for (const [id] of notFoundList) {
|
|
409
|
+
const t = TOOLS[id]
|
|
410
|
+
console.log(chalk.dim(` ${t.icon} ${t.name}`))
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
console.log('')
|
|
415
|
+
|
|
416
|
+
let toolsToInstall
|
|
417
|
+
|
|
418
|
+
if (opts.tool === 'all') {
|
|
419
|
+
toolsToInstall = Object.keys(TOOLS)
|
|
420
|
+
} else if (opts.tool) {
|
|
421
|
+
toolsToInstall = [opts.tool]
|
|
422
|
+
} else {
|
|
423
|
+
const choices = Object.entries(TOOLS).map(([id, tool]) => ({
|
|
424
|
+
name: id,
|
|
425
|
+
message: `${tool.icon} ${tool.name}${detected[id] ? chalk.green(' ✓') : ''}`,
|
|
426
|
+
hint: tool.description,
|
|
427
|
+
}))
|
|
428
|
+
|
|
429
|
+
const { tools } = await prompt({
|
|
430
|
+
type: 'multiselect',
|
|
431
|
+
name: 'tools',
|
|
432
|
+
message: 'Which AI tools do you want to install BuildFlow into?',
|
|
433
|
+
hint: '(Space to select, Enter to confirm)',
|
|
434
|
+
choices,
|
|
435
|
+
initial: detectedList.map(([id]) => id),
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
if (!tools || tools.length === 0) {
|
|
439
|
+
console.log(chalk.yellow('\n Nothing selected. Exiting.\n'))
|
|
440
|
+
return
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
toolsToInstall = tools
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
let scope
|
|
447
|
+
if (opts.global) {
|
|
448
|
+
scope = 'global'
|
|
449
|
+
} else if (opts.local) {
|
|
450
|
+
scope = 'local'
|
|
451
|
+
} else {
|
|
452
|
+
const { installScope } = await prompt({
|
|
453
|
+
type: 'select',
|
|
454
|
+
name: 'installScope',
|
|
455
|
+
message: 'Install scope:',
|
|
456
|
+
choices: [
|
|
457
|
+
{
|
|
458
|
+
name: 'local',
|
|
459
|
+
message: 'Local — This project only',
|
|
460
|
+
hint: 'Writes to .claude/commands/, .cursor/rules/, etc. in current directory',
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
name: 'global',
|
|
464
|
+
message: 'Global — All projects',
|
|
465
|
+
hint: 'Writes to ~/.claude/commands/, ~/.gemini/, etc.',
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
name: 'both',
|
|
469
|
+
message: 'Both',
|
|
470
|
+
hint: 'Global baseline + local overrides',
|
|
471
|
+
},
|
|
472
|
+
],
|
|
473
|
+
})
|
|
474
|
+
scope = installScope
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const commandFiles = loadCommandTemplates()
|
|
478
|
+
const commandCount = Object.keys(commandFiles).length
|
|
479
|
+
|
|
480
|
+
console.log(chalk.dim(`\n Installing ${commandCount} commands into ${toolsToInstall.length} tool(s)...\n`))
|
|
481
|
+
|
|
482
|
+
const results = []
|
|
483
|
+
|
|
484
|
+
for (const toolId of toolsToInstall) {
|
|
485
|
+
const tool = TOOLS[toolId]
|
|
486
|
+
if (!tool) {
|
|
487
|
+
console.log(chalk.red(` ✗ Unknown tool: ${toolId}`))
|
|
488
|
+
continue
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const sp = ora(` ${tool.icon} Installing into ${tool.name}...`).start()
|
|
492
|
+
|
|
493
|
+
try {
|
|
494
|
+
let installDir
|
|
495
|
+
|
|
496
|
+
if (scope === 'global' || scope === 'both') {
|
|
497
|
+
installDir = tool.installGlobal(commandFiles)
|
|
498
|
+
}
|
|
499
|
+
if (scope === 'local' || scope === 'both') {
|
|
500
|
+
installDir = tool.installLocal(commandFiles)
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
sp.succeed(chalk.green(` ${tool.icon} ${tool.name}`) + chalk.dim(` → ${installDir}`))
|
|
504
|
+
results.push({ tool, success: true })
|
|
505
|
+
} catch (err) {
|
|
506
|
+
sp.fail(chalk.red(` ${tool.icon} ${tool.name} — ${err.message}`))
|
|
507
|
+
results.push({ tool, success: false, error: err })
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const succeeded = results.filter(r => r.success)
|
|
512
|
+
const failed = results.filter(r => !r.success)
|
|
513
|
+
|
|
514
|
+
console.log('\n' + chalk.bold.white(' ─── Installation Complete ───\n'))
|
|
515
|
+
|
|
516
|
+
for (const { tool } of succeeded) {
|
|
517
|
+
console.log(chalk.green(` ✓ ${tool.icon} ${tool.name}`))
|
|
518
|
+
console.log(chalk.dim(` ${tool.triggerNote}`))
|
|
519
|
+
console.log('')
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (failed.length > 0) {
|
|
523
|
+
console.log(chalk.red('\n Failed:'))
|
|
524
|
+
for (const { tool, error } of failed) {
|
|
525
|
+
console.log(chalk.red(` ✗ ${tool.name}: ${error.message}`))
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (succeeded.length > 0) {
|
|
530
|
+
console.log(chalk.bold('\n Next steps:\n'))
|
|
531
|
+
console.log(chalk.white(' 1. Open your AI tool in this project'))
|
|
532
|
+
console.log(chalk.white(' 2. Type "/" to see BuildFlow commands'))
|
|
533
|
+
console.log(chalk.white(' 3. Start with: ') + chalk.cyan('/buildflow-start'))
|
|
534
|
+
console.log(chalk.white(' Or for existing projects: ') + chalk.cyan('/buildflow-onboard'))
|
|
535
|
+
console.log('')
|
|
536
|
+
}
|
|
537
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'fs'
|
|
3
|
+
import { join } from 'path'
|
|
4
|
+
|
|
5
|
+
export async function run(opts = {}) {
|
|
6
|
+
const base = join(process.cwd(), '.buildflow')
|
|
7
|
+
|
|
8
|
+
if (!existsSync(base)) {
|
|
9
|
+
console.log(chalk.yellow('\n BuildFlow not initialized in this directory.'))
|
|
10
|
+
console.log(chalk.dim(' Run: npx buildflow-dev init\n'))
|
|
11
|
+
return
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const read = (rel) => {
|
|
15
|
+
try { return readFileSync(join(base, rel), 'utf8') } catch { return '' }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const state = read('core/state.md')
|
|
19
|
+
const memory = read('memory/light.md')
|
|
20
|
+
const debt = read('security/DEBT.md')
|
|
21
|
+
|
|
22
|
+
const get = (src, key) => src.match(new RegExp(`${key}:\\s*(.+)`))?.[1]?.trim() ?? '—'
|
|
23
|
+
|
|
24
|
+
console.log('\n' + chalk.bold.white(' BuildFlow Status\n'))
|
|
25
|
+
console.log(chalk.dim(' Project: ') + chalk.white(get(state, 'Project')))
|
|
26
|
+
console.log(chalk.dim(' Type: ') + chalk.white(get(state, 'Type')))
|
|
27
|
+
console.log(chalk.dim(' Framework: ') + chalk.white(get(state, 'Framework')))
|
|
28
|
+
console.log(chalk.dim(' Phase: ') + chalk.white(get(state, 'Phase')))
|
|
29
|
+
console.log(chalk.dim(' Status: ') + chalk.white(get(state, 'Status')))
|
|
30
|
+
console.log(chalk.dim(' Version: ') + chalk.white(get(state, 'BuildFlow')))
|
|
31
|
+
|
|
32
|
+
const onboarded = get(memory, 'onboarded')
|
|
33
|
+
console.log(chalk.dim('\n Codebase onboarded: ') +
|
|
34
|
+
(onboarded === 'true' ? chalk.green('Yes') :
|
|
35
|
+
onboarded === 'n/a' ? chalk.dim('N/A (greenfield)') :
|
|
36
|
+
chalk.yellow('No — run /buildflow-onboard')))
|
|
37
|
+
|
|
38
|
+
const hasDebt = debt.includes('## Active') && !debt.includes('[None]')
|
|
39
|
+
console.log(chalk.dim(' Security debt: ') +
|
|
40
|
+
(hasDebt ? chalk.red('⚠Issues pending — see .buildflow/security/DEBT.md') : chalk.green('Clean')))
|
|
41
|
+
|
|
42
|
+
if (opts.verbose) {
|
|
43
|
+
console.log(chalk.dim('\n .buildflow/ structure:'))
|
|
44
|
+
const walk = (dir, prefix = ' ') => {
|
|
45
|
+
try {
|
|
46
|
+
for (const entry of readdirSync(dir)) {
|
|
47
|
+
if (entry.startsWith('.')) continue
|
|
48
|
+
const full = join(dir, entry)
|
|
49
|
+
if (statSync(full).isDirectory()) {
|
|
50
|
+
console.log(chalk.dim(`${prefix}${entry}/`))
|
|
51
|
+
walk(full, prefix + ' ')
|
|
52
|
+
} else {
|
|
53
|
+
console.log(chalk.dim(`${prefix}${entry}`))
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
} catch {}
|
|
57
|
+
}
|
|
58
|
+
walk(base)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log('')
|
|
62
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import ora from 'ora'
|
|
3
|
+
import { existsSync, readFileSync } from 'fs'
|
|
4
|
+
import { join, dirname } from 'path'
|
|
5
|
+
import { fileURLToPath } from 'url'
|
|
6
|
+
import { run as runInstall } from './install.js'
|
|
7
|
+
|
|
8
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
9
|
+
|
|
10
|
+
export async function run(opts = {}) {
|
|
11
|
+
const base = join(process.cwd(), '.buildflow')
|
|
12
|
+
|
|
13
|
+
if (!existsSync(base)) {
|
|
14
|
+
console.log(chalk.yellow('\n BuildFlow not initialized. Run: npx buildflow-dev init\n'))
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (opts.check) {
|
|
19
|
+
console.log('\n' + chalk.bold.white(' BuildFlow Update Check\n'))
|
|
20
|
+
const pkgPath = join(__dirname, '../../package.json')
|
|
21
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
22
|
+
console.log(chalk.dim(` Local version: ${pkg.version}`))
|
|
23
|
+
console.log(chalk.dim(' Run: npx buildflow-dev update to update commands\n'))
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
console.log('\n' + chalk.bold.white(' Updating BuildFlow...\n'))
|
|
28
|
+
|
|
29
|
+
const sp = ora('Updating command files...').start()
|
|
30
|
+
await new Promise(r => setTimeout(r, 400))
|
|
31
|
+
sp.succeed(chalk.green(' ✓ Command files updated'))
|
|
32
|
+
|
|
33
|
+
console.log('')
|
|
34
|
+
await runInstall({ yes: opts.yes })
|
|
35
|
+
}
|
package/src/index.js
ADDED