flow-cc 0.6.0 → 0.7.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/CHANGELOG.md +21 -0
- package/README.md +229 -202
- package/VERSION +1 -1
- package/bin/install.js +352 -352
- package/package.json +1 -1
- package/skills/flow-done.md +5 -5
- package/skills/flow-intro.md +106 -104
- package/skills/flow-setup.md +196 -196
- package/skills/flow-spec.md +3 -3
- package/skills/flow-status.md +1 -1
- package/skills/flow-triage.md +11 -0
- package/templates/ROADMAP.md.template +18 -18
- package/templates/STATE.md.template +24 -24
- package/skills/flow-milestone.md +0 -78
package/bin/install.js
CHANGED
|
@@ -1,352 +1,352 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Flow plugin installer for Claude Code
|
|
3
|
-
// Usage: npx flow-cc [--uninstall] [--verify]
|
|
4
|
-
|
|
5
|
-
const fs = require('fs');
|
|
6
|
-
const path = require('path');
|
|
7
|
-
const os = require('os');
|
|
8
|
-
|
|
9
|
-
// ---------- Node.js version check ----------
|
|
10
|
-
const nodeMajor = parseInt(process.versions.node.split('.')[0], 10);
|
|
11
|
-
if (nodeMajor < 18) {
|
|
12
|
-
console.error('Flow requires Node.js 18 or later. You have ' + process.version);
|
|
13
|
-
process.exit(1);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const homeDir = os.homedir();
|
|
17
|
-
const claudeDir = path.join(homeDir, '.claude');
|
|
18
|
-
const commandsDir = path.join(claudeDir, 'commands', 'flow');
|
|
19
|
-
const hooksDir = path.join(claudeDir, 'hooks');
|
|
20
|
-
const cacheDir = path.join(claudeDir, 'cache');
|
|
21
|
-
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
22
|
-
|
|
23
|
-
// Source directories (relative to package root, one level up from bin/)
|
|
24
|
-
const pkgRoot = path.resolve(__dirname, '..');
|
|
25
|
-
const skillsDir = path.join(pkgRoot, 'skills');
|
|
26
|
-
const srcHooksDir = path.join(pkgRoot, 'hooks');
|
|
27
|
-
const templatesDir = path.join(pkgRoot, 'templates');
|
|
28
|
-
const versionFile = path.join(pkgRoot, 'VERSION');
|
|
29
|
-
|
|
30
|
-
const uninstall = process.argv.includes('--uninstall') || process.argv.includes('-u');
|
|
31
|
-
const verify = process.argv.includes('--verify') || process.argv.includes('-v');
|
|
32
|
-
const changelog = process.argv.includes('--changelog');
|
|
33
|
-
|
|
34
|
-
// ---------- Changelog ----------
|
|
35
|
-
if (changelog) {
|
|
36
|
-
const changelogPath = path.join(pkgRoot, 'CHANGELOG.md');
|
|
37
|
-
try {
|
|
38
|
-
console.log(fs.readFileSync(changelogPath, 'utf8'));
|
|
39
|
-
} catch (e) {
|
|
40
|
-
console.log('CHANGELOG not available');
|
|
41
|
-
}
|
|
42
|
-
process.exit(0);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// ---------- Verify ----------
|
|
46
|
-
if (verify) {
|
|
47
|
-
console.log('Flow install health check:\n');
|
|
48
|
-
|
|
49
|
-
let passed = 0;
|
|
50
|
-
const total = 5;
|
|
51
|
-
|
|
52
|
-
// 1. Skills installed
|
|
53
|
-
try {
|
|
54
|
-
const files = fs.existsSync(commandsDir)
|
|
55
|
-
? fs.readdirSync(commandsDir).filter(f => f.endsWith('.md'))
|
|
56
|
-
: [];
|
|
57
|
-
if (files.length > 0) {
|
|
58
|
-
console.log(` \u2713 Skills installed (${files.length} files)`);
|
|
59
|
-
passed++;
|
|
60
|
-
} else {
|
|
61
|
-
console.log(' \u2717 Skills not installed (0 .md files in commands/flow/)');
|
|
62
|
-
}
|
|
63
|
-
} catch (e) {
|
|
64
|
-
console.log(' \u2717 Skills not installed (cannot read commands/flow/)');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// 2. Hooks installed
|
|
68
|
-
try {
|
|
69
|
-
const hookFiles = ['flow-check-update.js', 'flow-statusline.js'];
|
|
70
|
-
const found = hookFiles.filter(h => fs.existsSync(path.join(hooksDir, h)));
|
|
71
|
-
if (found.length === 2) {
|
|
72
|
-
console.log(' \u2713 Hooks installed (2 files)');
|
|
73
|
-
passed++;
|
|
74
|
-
} else {
|
|
75
|
-
console.log(` \u2717 Hooks incomplete (${found.length}/2 files)`);
|
|
76
|
-
}
|
|
77
|
-
} catch (e) {
|
|
78
|
-
console.log(' \u2717 Hooks not installed');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// 3. VERSION file
|
|
82
|
-
try {
|
|
83
|
-
const vPath = path.join(commandsDir, 'VERSION');
|
|
84
|
-
if (fs.existsSync(vPath)) {
|
|
85
|
-
const ver = fs.readFileSync(vPath, 'utf8').trim();
|
|
86
|
-
console.log(` \u2713 VERSION file present (${ver})`);
|
|
87
|
-
passed++;
|
|
88
|
-
} else {
|
|
89
|
-
console.log(' \u2717 VERSION file missing');
|
|
90
|
-
}
|
|
91
|
-
} catch (e) {
|
|
92
|
-
console.log(' \u2717 VERSION file missing');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// 4. Templates installed
|
|
96
|
-
try {
|
|
97
|
-
const tDir = path.join(commandsDir, 'templates');
|
|
98
|
-
const files = fs.existsSync(tDir) ? fs.readdirSync(tDir) : [];
|
|
99
|
-
if (files.length > 0) {
|
|
100
|
-
console.log(` \u2713 Templates installed (${files.length} files)`);
|
|
101
|
-
passed++;
|
|
102
|
-
} else {
|
|
103
|
-
console.log(' \u2717 Templates not installed (0 files)');
|
|
104
|
-
}
|
|
105
|
-
} catch (e) {
|
|
106
|
-
console.log(' \u2717 Templates not installed');
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// 5. StatusLine configured
|
|
110
|
-
try {
|
|
111
|
-
if (fs.existsSync(settingsPath)) {
|
|
112
|
-
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
113
|
-
if (settings.statusLine && typeof settings.statusLine.command === 'string' &&
|
|
114
|
-
settings.statusLine.command.includes('flow-statusline')) {
|
|
115
|
-
console.log(' \u2713 StatusLine configured');
|
|
116
|
-
passed++;
|
|
117
|
-
} else {
|
|
118
|
-
console.log(' \u2717 StatusLine not configured');
|
|
119
|
-
}
|
|
120
|
-
} else {
|
|
121
|
-
console.log(' \u2717 StatusLine not configured (no settings.json)');
|
|
122
|
-
}
|
|
123
|
-
} catch (e) {
|
|
124
|
-
console.log(' \u2717 StatusLine not configured');
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
console.log(`\nResult: ${passed}/${total} checks passed`);
|
|
128
|
-
process.exit(passed === total ? 0 : 1);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// ---------- Uninstall ----------
|
|
132
|
-
if (uninstall) {
|
|
133
|
-
console.log('Uninstalling Flow plugin...\n');
|
|
134
|
-
|
|
135
|
-
// Remove flow commands directory
|
|
136
|
-
if (fs.existsSync(commandsDir)) {
|
|
137
|
-
fs.rmSync(commandsDir, { recursive: true });
|
|
138
|
-
console.log(' Removed ~/.claude/commands/flow/');
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Remove flow hooks
|
|
142
|
-
const hookFiles = ['flow-check-update.js', 'flow-statusline.js'];
|
|
143
|
-
for (const hook of hookFiles) {
|
|
144
|
-
const hookPath = path.join(hooksDir, hook);
|
|
145
|
-
if (fs.existsSync(hookPath)) {
|
|
146
|
-
fs.unlinkSync(hookPath);
|
|
147
|
-
console.log(` Removed ~/.claude/hooks/${hook}`);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Remove update cache
|
|
152
|
-
const cacheFile = path.join(cacheDir, 'flow-update-check.json');
|
|
153
|
-
if (fs.existsSync(cacheFile)) {
|
|
154
|
-
fs.unlinkSync(cacheFile);
|
|
155
|
-
console.log(' Removed ~/.claude/cache/flow-update-check.json');
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Remove statusLine from settings.json if it points to flow
|
|
159
|
-
if (fs.existsSync(settingsPath)) {
|
|
160
|
-
try {
|
|
161
|
-
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
162
|
-
if (settings.statusLine && typeof settings.statusLine.command === 'string' &&
|
|
163
|
-
settings.statusLine.command.includes('flow-statusline')) {
|
|
164
|
-
delete settings.statusLine;
|
|
165
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
166
|
-
console.log(' Removed statusLine from ~/.claude/settings.json');
|
|
167
|
-
}
|
|
168
|
-
} catch (e) {
|
|
169
|
-
// settings.json parse error -- leave it alone
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
console.log('\nFlow plugin uninstalled.');
|
|
174
|
-
process.exit(0);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// ---------- Install ----------
|
|
178
|
-
console.log('Installing Flow plugin...\n');
|
|
179
|
-
|
|
180
|
-
// ---------- Write permission check ----------
|
|
181
|
-
const permTestDir = fs.existsSync(claudeDir) ? claudeDir : path.dirname(claudeDir);
|
|
182
|
-
const permTestFile = path.join(permTestDir, '.flow-perm-test-' + process.pid);
|
|
183
|
-
try {
|
|
184
|
-
fs.writeFileSync(permTestFile, 'test');
|
|
185
|
-
fs.unlinkSync(permTestFile);
|
|
186
|
-
} catch (e) {
|
|
187
|
-
console.error('Cannot write to ' + claudeDir + ' \u2014 check permissions.');
|
|
188
|
-
process.exit(1);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// ---------- Rollback tracking ----------
|
|
192
|
-
const created = []; // paths created during install (files and dirs we created fresh)
|
|
193
|
-
|
|
194
|
-
function trackFile(filePath) {
|
|
195
|
-
created.push({ type: 'file', path: filePath });
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function trackDir(dirPath) {
|
|
199
|
-
// Only track if we actually created it (didn't exist before)
|
|
200
|
-
if (!fs.existsSync(dirPath)) {
|
|
201
|
-
created.push({ type: 'dir', path: dirPath });
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function rollback() {
|
|
206
|
-
console.error('\nRolling back partial install...');
|
|
207
|
-
for (let i = created.length - 1; i >= 0; i--) {
|
|
208
|
-
const item = created[i];
|
|
209
|
-
try {
|
|
210
|
-
if (item.type === 'file' && fs.existsSync(item.path)) {
|
|
211
|
-
fs.unlinkSync(item.path);
|
|
212
|
-
} else if (item.type === 'dir' && fs.existsSync(item.path)) {
|
|
213
|
-
fs.rmSync(item.path, { recursive: true });
|
|
214
|
-
}
|
|
215
|
-
} catch (e) {
|
|
216
|
-
// Best-effort cleanup
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
console.error('Rollback complete.');
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
try {
|
|
223
|
-
// 1. Create directories
|
|
224
|
-
for (const dir of [commandsDir, hooksDir, cacheDir]) {
|
|
225
|
-
trackDir(dir);
|
|
226
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// 2. Copy skills: skills/flow-*.md -> commands/flow/*.md (strip "flow-" prefix)
|
|
230
|
-
const skillFiles = fs.readdirSync(skillsDir).filter(f => f.startsWith('flow-') && f.endsWith('.md'));
|
|
231
|
-
for (const file of skillFiles) {
|
|
232
|
-
const dest = file.replace(/^flow-/, '');
|
|
233
|
-
const destPath = path.join(commandsDir, dest);
|
|
234
|
-
fs.copyFileSync(path.join(skillsDir, file), destPath);
|
|
235
|
-
trackFile(destPath);
|
|
236
|
-
}
|
|
237
|
-
console.log(` Installed ${skillFiles.length} skills \u2192 ~/.claude/commands/flow/`);
|
|
238
|
-
|
|
239
|
-
// 3. Copy hooks
|
|
240
|
-
const hookFiles = ['flow-check-update.js', 'flow-statusline.js'];
|
|
241
|
-
for (const hook of hookFiles) {
|
|
242
|
-
const src = path.join(srcHooksDir, hook);
|
|
243
|
-
if (fs.existsSync(src)) {
|
|
244
|
-
const destPath = path.join(hooksDir, hook);
|
|
245
|
-
fs.copyFileSync(src, destPath);
|
|
246
|
-
trackFile(destPath);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
console.log(' Installed hooks \u2192 ~/.claude/hooks/');
|
|
250
|
-
|
|
251
|
-
// 4. Copy VERSION
|
|
252
|
-
const versionDest = path.join(commandsDir, 'VERSION');
|
|
253
|
-
fs.copyFileSync(versionFile, versionDest);
|
|
254
|
-
trackFile(versionDest);
|
|
255
|
-
console.log(' Installed VERSION \u2192 ~/.claude/commands/flow/VERSION');
|
|
256
|
-
|
|
257
|
-
// 5. Copy templates
|
|
258
|
-
const destTemplatesDir = path.join(commandsDir, 'templates');
|
|
259
|
-
trackDir(destTemplatesDir);
|
|
260
|
-
fs.mkdirSync(destTemplatesDir, { recursive: true });
|
|
261
|
-
if (fs.existsSync(templatesDir)) {
|
|
262
|
-
const templateFiles = fs.readdirSync(templatesDir);
|
|
263
|
-
for (const file of templateFiles) {
|
|
264
|
-
const destPath = path.join(destTemplatesDir, file);
|
|
265
|
-
fs.copyFileSync(path.join(templatesDir, file), destPath);
|
|
266
|
-
trackFile(destPath);
|
|
267
|
-
}
|
|
268
|
-
console.log(` Installed ${templateFiles.length} templates \u2192 ~/.claude/commands/flow/templates/`);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// 6. Merge statusLine into settings.json
|
|
272
|
-
let settings = {};
|
|
273
|
-
if (fs.existsSync(settingsPath)) {
|
|
274
|
-
try {
|
|
275
|
-
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
276
|
-
} catch (e) {
|
|
277
|
-
// Corrupted settings -- start fresh but warn
|
|
278
|
-
console.log(' Warning: could not parse existing settings.json, preserving as backup');
|
|
279
|
-
fs.copyFileSync(settingsPath, settingsPath + '.bak');
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
settings.statusLine = {
|
|
283
|
-
type: 'command',
|
|
284
|
-
command: `node "${path.join(hooksDir, 'flow-statusline.js')}"`
|
|
285
|
-
};
|
|
286
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
287
|
-
trackFile(settingsPath);
|
|
288
|
-
console.log(' Configured statusLine in ~/.claude/settings.json');
|
|
289
|
-
|
|
290
|
-
// 7. Write .source breadcrumb (for dev/setup.sh compat)
|
|
291
|
-
const sourcePath = path.join(commandsDir, '.source');
|
|
292
|
-
fs.writeFileSync(sourcePath, pkgRoot + '\n');
|
|
293
|
-
trackFile(sourcePath);
|
|
294
|
-
|
|
295
|
-
// ---------- Post-install verification ----------
|
|
296
|
-
let warnings = 0;
|
|
297
|
-
|
|
298
|
-
// Check: at least 1 file in commandsDir
|
|
299
|
-
const installedSkills = fs.readdirSync(commandsDir).filter(f => f.endsWith('.md'));
|
|
300
|
-
if (installedSkills.length === 0) {
|
|
301
|
-
console.log(' Warning: no skill files found in commands/flow/');
|
|
302
|
-
warnings++;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// Check: both hook files
|
|
306
|
-
const installedHooks = hookFiles.filter(h => fs.existsSync(path.join(hooksDir, h)));
|
|
307
|
-
if (installedHooks.length < 2) {
|
|
308
|
-
console.log(` Warning: only ${installedHooks.length}/2 hook files installed`);
|
|
309
|
-
warnings++;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Check: VERSION file
|
|
313
|
-
if (!fs.existsSync(path.join(commandsDir, 'VERSION'))) {
|
|
314
|
-
console.log(' Warning: VERSION file not found after install');
|
|
315
|
-
warnings++;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// Check: templates directory with at least 1 file
|
|
319
|
-
const tplDir = path.join(commandsDir, 'templates');
|
|
320
|
-
const tplFiles = fs.existsSync(tplDir) ? fs.readdirSync(tplDir) : [];
|
|
321
|
-
if (tplFiles.length === 0) {
|
|
322
|
-
console.log(' Warning: no template files found after install');
|
|
323
|
-
warnings++;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
if (warnings > 0) {
|
|
327
|
-
console.log(` (${warnings} verification warning${warnings > 1 ? 's' : ''} — install may be incomplete)`);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Done
|
|
331
|
-
const version = fs.readFileSync(versionFile, 'utf8').trim();
|
|
332
|
-
console.log(`
|
|
333
|
-
Flow v${version} installed successfully!
|
|
334
|
-
|
|
335
|
-
Commands available:
|
|
336
|
-
/flow:intro \u2014 Learn the Flow workflow
|
|
337
|
-
/flow:setup \u2014 Set up a new project with full roadmap
|
|
338
|
-
/flow:
|
|
339
|
-
/flow:spec \u2014 Spec interview \u2192 executable PRD
|
|
340
|
-
/flow:go \u2014 Execute next phase with agent teams
|
|
341
|
-
/flow:done \u2014 Session-end documentation
|
|
342
|
-
/flow:status \u2014 Quick orientation
|
|
343
|
-
/flow:task \u2014 Lightweight task execution
|
|
344
|
-
/flow:update \u2014 Update Flow to latest version
|
|
345
|
-
|
|
346
|
-
Get started: run /flow:intro in any Claude Code session.
|
|
347
|
-
`);
|
|
348
|
-
} catch (err) {
|
|
349
|
-
rollback();
|
|
350
|
-
console.error('\nInstall failed: ' + err.message);
|
|
351
|
-
process.exit(1);
|
|
352
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Flow plugin installer for Claude Code
|
|
3
|
+
// Usage: npx flow-cc [--uninstall] [--verify]
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
|
|
9
|
+
// ---------- Node.js version check ----------
|
|
10
|
+
const nodeMajor = parseInt(process.versions.node.split('.')[0], 10);
|
|
11
|
+
if (nodeMajor < 18) {
|
|
12
|
+
console.error('Flow requires Node.js 18 or later. You have ' + process.version);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const homeDir = os.homedir();
|
|
17
|
+
const claudeDir = path.join(homeDir, '.claude');
|
|
18
|
+
const commandsDir = path.join(claudeDir, 'commands', 'flow');
|
|
19
|
+
const hooksDir = path.join(claudeDir, 'hooks');
|
|
20
|
+
const cacheDir = path.join(claudeDir, 'cache');
|
|
21
|
+
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
22
|
+
|
|
23
|
+
// Source directories (relative to package root, one level up from bin/)
|
|
24
|
+
const pkgRoot = path.resolve(__dirname, '..');
|
|
25
|
+
const skillsDir = path.join(pkgRoot, 'skills');
|
|
26
|
+
const srcHooksDir = path.join(pkgRoot, 'hooks');
|
|
27
|
+
const templatesDir = path.join(pkgRoot, 'templates');
|
|
28
|
+
const versionFile = path.join(pkgRoot, 'VERSION');
|
|
29
|
+
|
|
30
|
+
const uninstall = process.argv.includes('--uninstall') || process.argv.includes('-u');
|
|
31
|
+
const verify = process.argv.includes('--verify') || process.argv.includes('-v');
|
|
32
|
+
const changelog = process.argv.includes('--changelog');
|
|
33
|
+
|
|
34
|
+
// ---------- Changelog ----------
|
|
35
|
+
if (changelog) {
|
|
36
|
+
const changelogPath = path.join(pkgRoot, 'CHANGELOG.md');
|
|
37
|
+
try {
|
|
38
|
+
console.log(fs.readFileSync(changelogPath, 'utf8'));
|
|
39
|
+
} catch (e) {
|
|
40
|
+
console.log('CHANGELOG not available');
|
|
41
|
+
}
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ---------- Verify ----------
|
|
46
|
+
if (verify) {
|
|
47
|
+
console.log('Flow install health check:\n');
|
|
48
|
+
|
|
49
|
+
let passed = 0;
|
|
50
|
+
const total = 5;
|
|
51
|
+
|
|
52
|
+
// 1. Skills installed
|
|
53
|
+
try {
|
|
54
|
+
const files = fs.existsSync(commandsDir)
|
|
55
|
+
? fs.readdirSync(commandsDir).filter(f => f.endsWith('.md'))
|
|
56
|
+
: [];
|
|
57
|
+
if (files.length > 0) {
|
|
58
|
+
console.log(` \u2713 Skills installed (${files.length} files)`);
|
|
59
|
+
passed++;
|
|
60
|
+
} else {
|
|
61
|
+
console.log(' \u2717 Skills not installed (0 .md files in commands/flow/)');
|
|
62
|
+
}
|
|
63
|
+
} catch (e) {
|
|
64
|
+
console.log(' \u2717 Skills not installed (cannot read commands/flow/)');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 2. Hooks installed
|
|
68
|
+
try {
|
|
69
|
+
const hookFiles = ['flow-check-update.js', 'flow-statusline.js'];
|
|
70
|
+
const found = hookFiles.filter(h => fs.existsSync(path.join(hooksDir, h)));
|
|
71
|
+
if (found.length === 2) {
|
|
72
|
+
console.log(' \u2713 Hooks installed (2 files)');
|
|
73
|
+
passed++;
|
|
74
|
+
} else {
|
|
75
|
+
console.log(` \u2717 Hooks incomplete (${found.length}/2 files)`);
|
|
76
|
+
}
|
|
77
|
+
} catch (e) {
|
|
78
|
+
console.log(' \u2717 Hooks not installed');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 3. VERSION file
|
|
82
|
+
try {
|
|
83
|
+
const vPath = path.join(commandsDir, 'VERSION');
|
|
84
|
+
if (fs.existsSync(vPath)) {
|
|
85
|
+
const ver = fs.readFileSync(vPath, 'utf8').trim();
|
|
86
|
+
console.log(` \u2713 VERSION file present (${ver})`);
|
|
87
|
+
passed++;
|
|
88
|
+
} else {
|
|
89
|
+
console.log(' \u2717 VERSION file missing');
|
|
90
|
+
}
|
|
91
|
+
} catch (e) {
|
|
92
|
+
console.log(' \u2717 VERSION file missing');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 4. Templates installed
|
|
96
|
+
try {
|
|
97
|
+
const tDir = path.join(commandsDir, 'templates');
|
|
98
|
+
const files = fs.existsSync(tDir) ? fs.readdirSync(tDir) : [];
|
|
99
|
+
if (files.length > 0) {
|
|
100
|
+
console.log(` \u2713 Templates installed (${files.length} files)`);
|
|
101
|
+
passed++;
|
|
102
|
+
} else {
|
|
103
|
+
console.log(' \u2717 Templates not installed (0 files)');
|
|
104
|
+
}
|
|
105
|
+
} catch (e) {
|
|
106
|
+
console.log(' \u2717 Templates not installed');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 5. StatusLine configured
|
|
110
|
+
try {
|
|
111
|
+
if (fs.existsSync(settingsPath)) {
|
|
112
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
113
|
+
if (settings.statusLine && typeof settings.statusLine.command === 'string' &&
|
|
114
|
+
settings.statusLine.command.includes('flow-statusline')) {
|
|
115
|
+
console.log(' \u2713 StatusLine configured');
|
|
116
|
+
passed++;
|
|
117
|
+
} else {
|
|
118
|
+
console.log(' \u2717 StatusLine not configured');
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
console.log(' \u2717 StatusLine not configured (no settings.json)');
|
|
122
|
+
}
|
|
123
|
+
} catch (e) {
|
|
124
|
+
console.log(' \u2717 StatusLine not configured');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
console.log(`\nResult: ${passed}/${total} checks passed`);
|
|
128
|
+
process.exit(passed === total ? 0 : 1);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ---------- Uninstall ----------
|
|
132
|
+
if (uninstall) {
|
|
133
|
+
console.log('Uninstalling Flow plugin...\n');
|
|
134
|
+
|
|
135
|
+
// Remove flow commands directory
|
|
136
|
+
if (fs.existsSync(commandsDir)) {
|
|
137
|
+
fs.rmSync(commandsDir, { recursive: true });
|
|
138
|
+
console.log(' Removed ~/.claude/commands/flow/');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Remove flow hooks
|
|
142
|
+
const hookFiles = ['flow-check-update.js', 'flow-statusline.js'];
|
|
143
|
+
for (const hook of hookFiles) {
|
|
144
|
+
const hookPath = path.join(hooksDir, hook);
|
|
145
|
+
if (fs.existsSync(hookPath)) {
|
|
146
|
+
fs.unlinkSync(hookPath);
|
|
147
|
+
console.log(` Removed ~/.claude/hooks/${hook}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Remove update cache
|
|
152
|
+
const cacheFile = path.join(cacheDir, 'flow-update-check.json');
|
|
153
|
+
if (fs.existsSync(cacheFile)) {
|
|
154
|
+
fs.unlinkSync(cacheFile);
|
|
155
|
+
console.log(' Removed ~/.claude/cache/flow-update-check.json');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Remove statusLine from settings.json if it points to flow
|
|
159
|
+
if (fs.existsSync(settingsPath)) {
|
|
160
|
+
try {
|
|
161
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
162
|
+
if (settings.statusLine && typeof settings.statusLine.command === 'string' &&
|
|
163
|
+
settings.statusLine.command.includes('flow-statusline')) {
|
|
164
|
+
delete settings.statusLine;
|
|
165
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
166
|
+
console.log(' Removed statusLine from ~/.claude/settings.json');
|
|
167
|
+
}
|
|
168
|
+
} catch (e) {
|
|
169
|
+
// settings.json parse error -- leave it alone
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.log('\nFlow plugin uninstalled.');
|
|
174
|
+
process.exit(0);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ---------- Install ----------
|
|
178
|
+
console.log('Installing Flow plugin...\n');
|
|
179
|
+
|
|
180
|
+
// ---------- Write permission check ----------
|
|
181
|
+
const permTestDir = fs.existsSync(claudeDir) ? claudeDir : path.dirname(claudeDir);
|
|
182
|
+
const permTestFile = path.join(permTestDir, '.flow-perm-test-' + process.pid);
|
|
183
|
+
try {
|
|
184
|
+
fs.writeFileSync(permTestFile, 'test');
|
|
185
|
+
fs.unlinkSync(permTestFile);
|
|
186
|
+
} catch (e) {
|
|
187
|
+
console.error('Cannot write to ' + claudeDir + ' \u2014 check permissions.');
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ---------- Rollback tracking ----------
|
|
192
|
+
const created = []; // paths created during install (files and dirs we created fresh)
|
|
193
|
+
|
|
194
|
+
function trackFile(filePath) {
|
|
195
|
+
created.push({ type: 'file', path: filePath });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function trackDir(dirPath) {
|
|
199
|
+
// Only track if we actually created it (didn't exist before)
|
|
200
|
+
if (!fs.existsSync(dirPath)) {
|
|
201
|
+
created.push({ type: 'dir', path: dirPath });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function rollback() {
|
|
206
|
+
console.error('\nRolling back partial install...');
|
|
207
|
+
for (let i = created.length - 1; i >= 0; i--) {
|
|
208
|
+
const item = created[i];
|
|
209
|
+
try {
|
|
210
|
+
if (item.type === 'file' && fs.existsSync(item.path)) {
|
|
211
|
+
fs.unlinkSync(item.path);
|
|
212
|
+
} else if (item.type === 'dir' && fs.existsSync(item.path)) {
|
|
213
|
+
fs.rmSync(item.path, { recursive: true });
|
|
214
|
+
}
|
|
215
|
+
} catch (e) {
|
|
216
|
+
// Best-effort cleanup
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
console.error('Rollback complete.');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
// 1. Create directories
|
|
224
|
+
for (const dir of [commandsDir, hooksDir, cacheDir]) {
|
|
225
|
+
trackDir(dir);
|
|
226
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// 2. Copy skills: skills/flow-*.md -> commands/flow/*.md (strip "flow-" prefix)
|
|
230
|
+
const skillFiles = fs.readdirSync(skillsDir).filter(f => f.startsWith('flow-') && f.endsWith('.md'));
|
|
231
|
+
for (const file of skillFiles) {
|
|
232
|
+
const dest = file.replace(/^flow-/, '');
|
|
233
|
+
const destPath = path.join(commandsDir, dest);
|
|
234
|
+
fs.copyFileSync(path.join(skillsDir, file), destPath);
|
|
235
|
+
trackFile(destPath);
|
|
236
|
+
}
|
|
237
|
+
console.log(` Installed ${skillFiles.length} skills \u2192 ~/.claude/commands/flow/`);
|
|
238
|
+
|
|
239
|
+
// 3. Copy hooks
|
|
240
|
+
const hookFiles = ['flow-check-update.js', 'flow-statusline.js'];
|
|
241
|
+
for (const hook of hookFiles) {
|
|
242
|
+
const src = path.join(srcHooksDir, hook);
|
|
243
|
+
if (fs.existsSync(src)) {
|
|
244
|
+
const destPath = path.join(hooksDir, hook);
|
|
245
|
+
fs.copyFileSync(src, destPath);
|
|
246
|
+
trackFile(destPath);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
console.log(' Installed hooks \u2192 ~/.claude/hooks/');
|
|
250
|
+
|
|
251
|
+
// 4. Copy VERSION
|
|
252
|
+
const versionDest = path.join(commandsDir, 'VERSION');
|
|
253
|
+
fs.copyFileSync(versionFile, versionDest);
|
|
254
|
+
trackFile(versionDest);
|
|
255
|
+
console.log(' Installed VERSION \u2192 ~/.claude/commands/flow/VERSION');
|
|
256
|
+
|
|
257
|
+
// 5. Copy templates
|
|
258
|
+
const destTemplatesDir = path.join(commandsDir, 'templates');
|
|
259
|
+
trackDir(destTemplatesDir);
|
|
260
|
+
fs.mkdirSync(destTemplatesDir, { recursive: true });
|
|
261
|
+
if (fs.existsSync(templatesDir)) {
|
|
262
|
+
const templateFiles = fs.readdirSync(templatesDir);
|
|
263
|
+
for (const file of templateFiles) {
|
|
264
|
+
const destPath = path.join(destTemplatesDir, file);
|
|
265
|
+
fs.copyFileSync(path.join(templatesDir, file), destPath);
|
|
266
|
+
trackFile(destPath);
|
|
267
|
+
}
|
|
268
|
+
console.log(` Installed ${templateFiles.length} templates \u2192 ~/.claude/commands/flow/templates/`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// 6. Merge statusLine into settings.json
|
|
272
|
+
let settings = {};
|
|
273
|
+
if (fs.existsSync(settingsPath)) {
|
|
274
|
+
try {
|
|
275
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
276
|
+
} catch (e) {
|
|
277
|
+
// Corrupted settings -- start fresh but warn
|
|
278
|
+
console.log(' Warning: could not parse existing settings.json, preserving as backup');
|
|
279
|
+
fs.copyFileSync(settingsPath, settingsPath + '.bak');
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
settings.statusLine = {
|
|
283
|
+
type: 'command',
|
|
284
|
+
command: `node "${path.join(hooksDir, 'flow-statusline.js')}"`
|
|
285
|
+
};
|
|
286
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
287
|
+
trackFile(settingsPath);
|
|
288
|
+
console.log(' Configured statusLine in ~/.claude/settings.json');
|
|
289
|
+
|
|
290
|
+
// 7. Write .source breadcrumb (for dev/setup.sh compat)
|
|
291
|
+
const sourcePath = path.join(commandsDir, '.source');
|
|
292
|
+
fs.writeFileSync(sourcePath, pkgRoot + '\n');
|
|
293
|
+
trackFile(sourcePath);
|
|
294
|
+
|
|
295
|
+
// ---------- Post-install verification ----------
|
|
296
|
+
let warnings = 0;
|
|
297
|
+
|
|
298
|
+
// Check: at least 1 file in commandsDir
|
|
299
|
+
const installedSkills = fs.readdirSync(commandsDir).filter(f => f.endsWith('.md'));
|
|
300
|
+
if (installedSkills.length === 0) {
|
|
301
|
+
console.log(' Warning: no skill files found in commands/flow/');
|
|
302
|
+
warnings++;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Check: both hook files
|
|
306
|
+
const installedHooks = hookFiles.filter(h => fs.existsSync(path.join(hooksDir, h)));
|
|
307
|
+
if (installedHooks.length < 2) {
|
|
308
|
+
console.log(` Warning: only ${installedHooks.length}/2 hook files installed`);
|
|
309
|
+
warnings++;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Check: VERSION file
|
|
313
|
+
if (!fs.existsSync(path.join(commandsDir, 'VERSION'))) {
|
|
314
|
+
console.log(' Warning: VERSION file not found after install');
|
|
315
|
+
warnings++;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Check: templates directory with at least 1 file
|
|
319
|
+
const tplDir = path.join(commandsDir, 'templates');
|
|
320
|
+
const tplFiles = fs.existsSync(tplDir) ? fs.readdirSync(tplDir) : [];
|
|
321
|
+
if (tplFiles.length === 0) {
|
|
322
|
+
console.log(' Warning: no template files found after install');
|
|
323
|
+
warnings++;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (warnings > 0) {
|
|
327
|
+
console.log(` (${warnings} verification warning${warnings > 1 ? 's' : ''} — install may be incomplete)`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Done
|
|
331
|
+
const version = fs.readFileSync(versionFile, 'utf8').trim();
|
|
332
|
+
console.log(`
|
|
333
|
+
Flow v${version} installed successfully!
|
|
334
|
+
|
|
335
|
+
Commands available:
|
|
336
|
+
/flow:intro \u2014 Learn the Flow workflow
|
|
337
|
+
/flow:setup \u2014 Set up a new project with full roadmap
|
|
338
|
+
/flow:triage \u2014 Sort brain dump into issues, milestones, lessons
|
|
339
|
+
/flow:spec \u2014 Spec interview \u2192 executable PRD
|
|
340
|
+
/flow:go \u2014 Execute next phase with agent teams
|
|
341
|
+
/flow:done \u2014 Session-end documentation
|
|
342
|
+
/flow:status \u2014 Quick orientation
|
|
343
|
+
/flow:task \u2014 Lightweight task execution
|
|
344
|
+
/flow:update \u2014 Update Flow to latest version
|
|
345
|
+
|
|
346
|
+
Get started: run /flow:intro in any Claude Code session.
|
|
347
|
+
`);
|
|
348
|
+
} catch (err) {
|
|
349
|
+
rollback();
|
|
350
|
+
console.error('\nInstall failed: ' + err.message);
|
|
351
|
+
process.exit(1);
|
|
352
|
+
}
|