vibe-forge 0.8.1 → 0.8.2
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/.claude/commands/configure-vcs.md +102 -102
- package/.claude/commands/forge.md +218 -218
- package/.claude/hooks/worker-loop.js +220 -217
- package/.claude/settings.json +89 -89
- package/README.md +149 -191
- package/agents/aegis/personality.md +303 -303
- package/agents/anvil/personality.md +278 -278
- package/agents/architect/personality.md +260 -260
- package/agents/crucible/personality.md +362 -362
- package/agents/crucible-x/personality.md +210 -210
- package/agents/ember/personality.md +293 -293
- package/agents/flux/personality.md +248 -248
- package/agents/furnace/personality.md +342 -342
- package/agents/herald/personality.md +249 -249
- package/agents/oracle/personality.md +284 -284
- package/agents/pixel/personality.md +140 -140
- package/agents/planning-hub/personality.md +473 -473
- package/agents/scribe/personality.md +253 -253
- package/agents/slag/personality.md +268 -268
- package/agents/temper/personality.md +270 -270
- package/bin/cli.js +372 -372
- package/bin/forge-daemon.sh +477 -477
- package/bin/forge-setup.sh +662 -661
- package/bin/forge-spawn.sh +164 -164
- package/bin/forge.sh +566 -566
- package/docs/commands.md +8 -8
- package/package.json +77 -77
- package/{bin → src}/lib/agents.sh +177 -177
- package/{bin → src}/lib/check-aliases.js +50 -50
- package/{bin → src}/lib/colors.sh +45 -44
- package/{bin → src}/lib/config.sh +347 -347
- package/{bin → src}/lib/constants.sh +241 -241
- package/{bin → src}/lib/daemon/budgets.sh +107 -107
- package/{bin → src}/lib/daemon/dependencies.sh +146 -146
- package/{bin → src}/lib/daemon/display.sh +128 -128
- package/{bin → src}/lib/daemon/notifications.sh +273 -273
- package/{bin → src}/lib/daemon/routing.sh +93 -93
- package/{bin → src}/lib/daemon/state.sh +163 -163
- package/{bin → src}/lib/daemon/sync.sh +103 -103
- package/{bin → src}/lib/database.sh +357 -357
- package/{bin → src}/lib/frontmatter.js +106 -106
- package/{bin → src}/lib/heimdall-setup.js +113 -113
- package/{bin → src}/lib/heimdall.js +265 -265
- package/src/lib/index.sh +25 -0
- package/{bin → src}/lib/json.sh +264 -264
- package/{bin → src}/lib/terminal.js +452 -452
- package/{bin → src}/lib/util.sh +126 -126
- package/{bin → src}/lib/vcs.js +349 -349
- package/{context → templates}/project-context-template.md +122 -122
- package/config/task-template.md +0 -159
- package/config/templates/handoff-template.md +0 -40
package/bin/cli.js
CHANGED
|
@@ -1,372 +1,372 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Vibe Forge CLI
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* npx
|
|
8
|
-
* npx
|
|
9
|
-
* npx
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
const { execSync, spawn } = require('child_process');
|
|
13
|
-
const fs = require('fs');
|
|
14
|
-
const path = require('path');
|
|
15
|
-
const os = require('os');
|
|
16
|
-
|
|
17
|
-
// Read version from package.json (single source of truth)
|
|
18
|
-
const packageJson = require(path.join(__dirname, '..', 'package.json'));
|
|
19
|
-
const VERSION = packageJson.version;
|
|
20
|
-
const REPO_URL = 'https://github.com/sugar-crash-studios/vibe-forge.git';
|
|
21
|
-
const FORGE_DIR = '_vibe-forge';
|
|
22
|
-
|
|
23
|
-
// Colors for terminal output
|
|
24
|
-
// NOTE: Intentionally duplicated from
|
|
25
|
-
// This is by design: cli.js runs standalone via npx before the rest of Vibe Forge
|
|
26
|
-
// is installed, so it cannot import colors.sh. If you update colors here,
|
|
27
|
-
// also update
|
|
28
|
-
const colors = {
|
|
29
|
-
reset: '\x1b[0m',
|
|
30
|
-
red: '\x1b[31m',
|
|
31
|
-
green: '\x1b[32m',
|
|
32
|
-
yellow: '\x1b[33m',
|
|
33
|
-
blue: '\x1b[34m',
|
|
34
|
-
cyan: '\x1b[36m',
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
function log(message, color = colors.reset) {
|
|
38
|
-
console.log(`${color}${message}${colors.reset}`);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function logError(message) {
|
|
42
|
-
log(`Error: ${message}`, colors.red);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function logSuccess(message) {
|
|
46
|
-
log(message, colors.green);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function logInfo(message) {
|
|
50
|
-
log(message, colors.blue);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function showBanner() {
|
|
54
|
-
console.log(`
|
|
55
|
-
${colors.yellow}🔥 Vibe Forge${colors.reset}
|
|
56
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
57
|
-
Multi-agent development orchestration
|
|
58
|
-
`);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function showHelp() {
|
|
62
|
-
showBanner();
|
|
63
|
-
console.log(`Usage: npx
|
|
64
|
-
|
|
65
|
-
Commands:
|
|
66
|
-
init Initialize Vibe Forge in the current project
|
|
67
|
-
update Update Vibe Forge to the latest version
|
|
68
|
-
agents List all agents, roles, and aliases
|
|
69
|
-
version Show version information
|
|
70
|
-
help Show this help message
|
|
71
|
-
|
|
72
|
-
Examples:
|
|
73
|
-
npx
|
|
74
|
-
npx
|
|
75
|
-
npx
|
|
76
|
-
`);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function showAgents() {
|
|
80
|
-
const agentsFile = path.join(__dirname, '..', 'config', 'agents.json');
|
|
81
|
-
let agents = {};
|
|
82
|
-
try {
|
|
83
|
-
const data = JSON.parse(fs.readFileSync(agentsFile, 'utf8'));
|
|
84
|
-
agents = data.agents || {};
|
|
85
|
-
} catch (e) {
|
|
86
|
-
logError('Could not read agents.json');
|
|
87
|
-
logInfo('Run from inside a Vibe Forge installation, or run: npx
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
showBanner();
|
|
92
|
-
console.log(`${colors.yellow}Agent Roster${colors.reset}`);
|
|
93
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
94
|
-
console.log('');
|
|
95
|
-
|
|
96
|
-
const typeOrder = ['core', 'worker', 'specialist', 'advisor'];
|
|
97
|
-
const typeLabels = {
|
|
98
|
-
core: 'Core (Always Running)',
|
|
99
|
-
worker: 'Workers (Per-Task)',
|
|
100
|
-
specialist: 'Specialists (On-Demand)',
|
|
101
|
-
advisor: 'Advisors',
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
for (const type of typeOrder) {
|
|
105
|
-
const group = Object.entries(agents).filter(([, a]) => a.type === type);
|
|
106
|
-
if (group.length === 0) continue;
|
|
107
|
-
console.log(` ${colors.cyan}${typeLabels[type] || type}${colors.reset}`);
|
|
108
|
-
for (const [name, agent] of group) {
|
|
109
|
-
console.log(` ${agent.icon || ' '} ${colors.yellow}${name}${colors.reset} — ${agent.role}`);
|
|
110
|
-
if (agent.aliases && agent.aliases.length > 0) {
|
|
111
|
-
console.log(` aliases: ${agent.aliases.join(', ')}`);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
console.log('');
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
console.log(`Spawn an agent:`);
|
|
118
|
-
console.log(` ${colors.yellow}./bin/forge.sh spawn <agent-name-or-alias>${colors.reset}`);
|
|
119
|
-
console.log('');
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function showVersion() {
|
|
123
|
-
console.log(`Vibe Forge v${VERSION}`);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function checkPrerequisites() {
|
|
127
|
-
// Check for git
|
|
128
|
-
try {
|
|
129
|
-
execSync('git --version', { stdio: 'pipe' });
|
|
130
|
-
} catch {
|
|
131
|
-
logError('Git is not installed. Please install Git first.');
|
|
132
|
-
logInfo(' Windows: winget install Git.Git');
|
|
133
|
-
logInfo(' macOS: brew install git');
|
|
134
|
-
logInfo(' Linux: sudo apt install git');
|
|
135
|
-
process.exit(1);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Check for Claude Code
|
|
139
|
-
try {
|
|
140
|
-
execSync('claude --version', { stdio: 'pipe' });
|
|
141
|
-
} catch {
|
|
142
|
-
logError('Claude Code CLI is not installed.');
|
|
143
|
-
logInfo(' Install from: https://claude.ai/download');
|
|
144
|
-
process.exit(1);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function isWindows() {
|
|
149
|
-
return os.platform() === 'win32';
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function getBashPath() {
|
|
153
|
-
if (!isWindows()) {
|
|
154
|
-
return 'bash';
|
|
155
|
-
}
|
|
156
|
-
// Delegate to shared terminal utility for Windows Git Bash detection
|
|
157
|
-
const { findGitBash } = require('
|
|
158
|
-
return findGitBash() || null;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function runBashScript(scriptPath, args = []) {
|
|
162
|
-
const bashPath = getBashPath();
|
|
163
|
-
|
|
164
|
-
if (!bashPath) {
|
|
165
|
-
logError('Could not find bash. Please install Git Bash on Windows.');
|
|
166
|
-
process.exit(1);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return new Promise((resolve, reject) => {
|
|
170
|
-
const child = spawn(bashPath, [scriptPath, ...args], {
|
|
171
|
-
stdio: 'inherit',
|
|
172
|
-
cwd: process.cwd(),
|
|
173
|
-
env: {
|
|
174
|
-
...process.env,
|
|
175
|
-
CLAUDE_CODE_GIT_BASH_PATH: bashPath,
|
|
176
|
-
},
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
child.on('close', (code) => {
|
|
180
|
-
if (code === 0) {
|
|
181
|
-
resolve();
|
|
182
|
-
} else {
|
|
183
|
-
reject(new Error(`Script exited with code ${code}`));
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
child.on('error', (err) => {
|
|
188
|
-
reject(err);
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
async function initCommand() {
|
|
194
|
-
showBanner();
|
|
195
|
-
|
|
196
|
-
const targetDir = path.join(process.cwd(), FORGE_DIR);
|
|
197
|
-
|
|
198
|
-
// Check if already initialized
|
|
199
|
-
if (fs.existsSync(targetDir)) {
|
|
200
|
-
logInfo(`Vibe Forge already exists at ${FORGE_DIR}/`);
|
|
201
|
-
log('');
|
|
202
|
-
log('To update, run: npx
|
|
203
|
-
log('To reinitialize, delete the _vibe-forge folder first.');
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
logInfo('Checking prerequisites...');
|
|
208
|
-
checkPrerequisites();
|
|
209
|
-
logSuccess('Prerequisites OK');
|
|
210
|
-
log('');
|
|
211
|
-
|
|
212
|
-
// Clone the repository
|
|
213
|
-
logInfo(`Cloning Vibe Forge into ${FORGE_DIR}/...`);
|
|
214
|
-
try {
|
|
215
|
-
execSync(`git clone --depth 1 ${REPO_URL} ${FORGE_DIR}`, {
|
|
216
|
-
stdio: 'inherit',
|
|
217
|
-
cwd: process.cwd(),
|
|
218
|
-
});
|
|
219
|
-
logSuccess('Clone complete');
|
|
220
|
-
} catch {
|
|
221
|
-
logError('Failed to clone repository');
|
|
222
|
-
process.exit(1);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
log('');
|
|
226
|
-
|
|
227
|
-
// Validate agents.json (alias collisions)
|
|
228
|
-
validateAgentsConfig(targetDir);
|
|
229
|
-
|
|
230
|
-
// Update project's .gitignore to ignore tool internals but keep project data
|
|
231
|
-
updateGitignore();
|
|
232
|
-
|
|
233
|
-
// Run the setup script
|
|
234
|
-
logInfo('Running setup...');
|
|
235
|
-
log('');
|
|
236
|
-
|
|
237
|
-
try {
|
|
238
|
-
const setupScript = path.join(targetDir, 'bin', 'forge-setup.sh');
|
|
239
|
-
await runBashScript(setupScript);
|
|
240
|
-
} catch (err) {
|
|
241
|
-
logError(`Setup failed: ${err.message}`);
|
|
242
|
-
process.exit(1);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function validateAgentsConfig(forgeDir) {
|
|
247
|
-
const checkScript = path.join(forgeDir, 'bin', 'lib', 'check-aliases.js');
|
|
248
|
-
if (!fs.existsSync(checkScript)) return;
|
|
249
|
-
|
|
250
|
-
try {
|
|
251
|
-
const agentsFile = path.join(forgeDir, 'config', 'agents.json');
|
|
252
|
-
execSync(`node "${checkScript}" "${agentsFile}"`, { stdio: 'pipe' });
|
|
253
|
-
const config = JSON.parse(fs.readFileSync(agentsFile, 'utf8'));
|
|
254
|
-
const agentCount = Object.keys(config.agents || {}).length;
|
|
255
|
-
logSuccess(`Agent config valid (${agentCount} agents, no alias collisions)`);
|
|
256
|
-
} catch (err) {
|
|
257
|
-
logError('Agent alias collisions detected in config/agents.json');
|
|
258
|
-
logInfo(err.stderr ? err.stderr.toString().trim() : 'Run node
|
|
259
|
-
logError('Fix collisions before using Vibe Forge.');
|
|
260
|
-
process.exit(1);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function updateGitignore() {
|
|
265
|
-
const gitignorePath = path.join(process.cwd(), '.gitignore');
|
|
266
|
-
const forgeIgnoreMarker = '# Vibe Forge';
|
|
267
|
-
const forgeIgnoreBlock = `
|
|
268
|
-
# Vibe Forge
|
|
269
|
-
# Tool internals (regenerated on update)
|
|
270
|
-
_vibe-forge/.git/
|
|
271
|
-
_vibe-forge/bin/
|
|
272
|
-
_vibe-forge/agents/
|
|
273
|
-
_vibe-forge/config/
|
|
274
|
-
_vibe-forge/docs/
|
|
275
|
-
_vibe-forge/tests/
|
|
276
|
-
_vibe-forge/src/
|
|
277
|
-
_vibe-forge/node_modules/
|
|
278
|
-
_vibe-forge/package*.json
|
|
279
|
-
_vibe-forge/*.md
|
|
280
|
-
_vibe-forge/LICENSE
|
|
281
|
-
_vibe-forge/.github/
|
|
282
|
-
|
|
283
|
-
# Keep project data (tasks, context) - these ARE committed
|
|
284
|
-
# !_vibe-forge/tasks/
|
|
285
|
-
# !_vibe-forge/context/
|
|
286
|
-
# !_vibe-forge/.claude/
|
|
287
|
-
`;
|
|
288
|
-
|
|
289
|
-
try {
|
|
290
|
-
let content = '';
|
|
291
|
-
if (fs.existsSync(gitignorePath)) {
|
|
292
|
-
content = fs.readFileSync(gitignorePath, 'utf8');
|
|
293
|
-
// Check if already has forge entries
|
|
294
|
-
if (content.includes(forgeIgnoreMarker)) {
|
|
295
|
-
return; // Already configured
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// Append forge ignore block
|
|
300
|
-
const newContent = content.trimEnd() + '\n' + forgeIgnoreBlock;
|
|
301
|
-
fs.writeFileSync(gitignorePath, newContent);
|
|
302
|
-
logSuccess('Updated .gitignore for Vibe Forge');
|
|
303
|
-
} catch (err) {
|
|
304
|
-
logError(`Warning: Could not update .gitignore: ${err.message}`);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
async function updateCommand() {
|
|
309
|
-
showBanner();
|
|
310
|
-
|
|
311
|
-
const targetDir = path.join(process.cwd(), FORGE_DIR);
|
|
312
|
-
|
|
313
|
-
// Check if initialized
|
|
314
|
-
if (!fs.existsSync(targetDir)) {
|
|
315
|
-
logError('Vibe Forge is not initialized in this project.');
|
|
316
|
-
log('');
|
|
317
|
-
log('Run: npx
|
|
318
|
-
process.exit(1);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
logInfo('Updating Vibe Forge...');
|
|
322
|
-
|
|
323
|
-
try {
|
|
324
|
-
// Fetch and reset to origin/main - works even without tracking branch
|
|
325
|
-
execSync('git fetch origin', {
|
|
326
|
-
stdio: 'inherit',
|
|
327
|
-
cwd: targetDir,
|
|
328
|
-
});
|
|
329
|
-
execSync('git reset --hard origin/main', {
|
|
330
|
-
stdio: 'inherit',
|
|
331
|
-
cwd: targetDir,
|
|
332
|
-
});
|
|
333
|
-
logSuccess('Update complete');
|
|
334
|
-
} catch {
|
|
335
|
-
logError('Failed to update. Check your network connection or try deleting _vibe-forge and running init again.');
|
|
336
|
-
process.exit(1);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// Main entry point
|
|
341
|
-
async function main() {
|
|
342
|
-
const args = process.argv.slice(2);
|
|
343
|
-
const command = args[0] || 'help';
|
|
344
|
-
|
|
345
|
-
switch (command) {
|
|
346
|
-
case 'init':
|
|
347
|
-
await initCommand();
|
|
348
|
-
break;
|
|
349
|
-
case 'update':
|
|
350
|
-
await updateCommand();
|
|
351
|
-
break;
|
|
352
|
-
case 'agents':
|
|
353
|
-
showAgents();
|
|
354
|
-
break;
|
|
355
|
-
case 'version':
|
|
356
|
-
case '--version':
|
|
357
|
-
case '-v':
|
|
358
|
-
showVersion();
|
|
359
|
-
break;
|
|
360
|
-
case 'help':
|
|
361
|
-
case '--help':
|
|
362
|
-
case '-h':
|
|
363
|
-
default:
|
|
364
|
-
showHelp();
|
|
365
|
-
break;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
main().catch((err) => {
|
|
370
|
-
logError(err.message);
|
|
371
|
-
process.exit(1);
|
|
372
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Vibe Forge CLI
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx vibe-forge init Initialize Forge in current project
|
|
8
|
+
* npx vibe-forge update Update Forge to latest version
|
|
9
|
+
* npx vibe-forge --help Show help
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { execSync, spawn } = require('child_process');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
|
|
17
|
+
// Read version from package.json (single source of truth)
|
|
18
|
+
const packageJson = require(path.join(__dirname, '..', 'package.json'));
|
|
19
|
+
const VERSION = packageJson.version;
|
|
20
|
+
const REPO_URL = 'https://github.com/sugar-crash-studios/vibe-forge.git';
|
|
21
|
+
const FORGE_DIR = '_vibe-forge';
|
|
22
|
+
|
|
23
|
+
// Colors for terminal output
|
|
24
|
+
// NOTE: Intentionally duplicated from src/lib/colors.sh
|
|
25
|
+
// This is by design: cli.js runs standalone via npx before the rest of Vibe Forge
|
|
26
|
+
// is installed, so it cannot import colors.sh. If you update colors here,
|
|
27
|
+
// also update src/lib/colors.sh to keep them in sync.
|
|
28
|
+
const colors = {
|
|
29
|
+
reset: '\x1b[0m',
|
|
30
|
+
red: '\x1b[31m',
|
|
31
|
+
green: '\x1b[32m',
|
|
32
|
+
yellow: '\x1b[33m',
|
|
33
|
+
blue: '\x1b[34m',
|
|
34
|
+
cyan: '\x1b[36m',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function log(message, color = colors.reset) {
|
|
38
|
+
console.log(`${color}${message}${colors.reset}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function logError(message) {
|
|
42
|
+
log(`Error: ${message}`, colors.red);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function logSuccess(message) {
|
|
46
|
+
log(message, colors.green);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function logInfo(message) {
|
|
50
|
+
log(message, colors.blue);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function showBanner() {
|
|
54
|
+
console.log(`
|
|
55
|
+
${colors.yellow}🔥 Vibe Forge${colors.reset}
|
|
56
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
57
|
+
Multi-agent development orchestration
|
|
58
|
+
`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function showHelp() {
|
|
62
|
+
showBanner();
|
|
63
|
+
console.log(`Usage: npx vibe-forge <command>
|
|
64
|
+
|
|
65
|
+
Commands:
|
|
66
|
+
init Initialize Vibe Forge in the current project
|
|
67
|
+
update Update Vibe Forge to the latest version
|
|
68
|
+
agents List all agents, roles, and aliases
|
|
69
|
+
version Show version information
|
|
70
|
+
help Show this help message
|
|
71
|
+
|
|
72
|
+
Examples:
|
|
73
|
+
npx vibe-forge init # Set up Forge in your project
|
|
74
|
+
npx vibe-forge update # Update to latest version
|
|
75
|
+
npx vibe-forge agents # Show agent roster
|
|
76
|
+
`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function showAgents() {
|
|
80
|
+
const agentsFile = path.join(__dirname, '..', 'config', 'agents.json');
|
|
81
|
+
let agents = {};
|
|
82
|
+
try {
|
|
83
|
+
const data = JSON.parse(fs.readFileSync(agentsFile, 'utf8'));
|
|
84
|
+
agents = data.agents || {};
|
|
85
|
+
} catch (e) {
|
|
86
|
+
logError('Could not read agents.json');
|
|
87
|
+
logInfo('Run from inside a Vibe Forge installation, or run: npx vibe-forge init');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
showBanner();
|
|
92
|
+
console.log(`${colors.yellow}Agent Roster${colors.reset}`);
|
|
93
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
94
|
+
console.log('');
|
|
95
|
+
|
|
96
|
+
const typeOrder = ['core', 'worker', 'specialist', 'advisor'];
|
|
97
|
+
const typeLabels = {
|
|
98
|
+
core: 'Core (Always Running)',
|
|
99
|
+
worker: 'Workers (Per-Task)',
|
|
100
|
+
specialist: 'Specialists (On-Demand)',
|
|
101
|
+
advisor: 'Advisors',
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
for (const type of typeOrder) {
|
|
105
|
+
const group = Object.entries(agents).filter(([, a]) => a.type === type);
|
|
106
|
+
if (group.length === 0) continue;
|
|
107
|
+
console.log(` ${colors.cyan}${typeLabels[type] || type}${colors.reset}`);
|
|
108
|
+
for (const [name, agent] of group) {
|
|
109
|
+
console.log(` ${agent.icon || ' '} ${colors.yellow}${name}${colors.reset} — ${agent.role}`);
|
|
110
|
+
if (agent.aliases && agent.aliases.length > 0) {
|
|
111
|
+
console.log(` aliases: ${agent.aliases.join(', ')}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
console.log('');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
console.log(`Spawn an agent:`);
|
|
118
|
+
console.log(` ${colors.yellow}./bin/forge.sh spawn <agent-name-or-alias>${colors.reset}`);
|
|
119
|
+
console.log('');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function showVersion() {
|
|
123
|
+
console.log(`Vibe Forge v${VERSION}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function checkPrerequisites() {
|
|
127
|
+
// Check for git
|
|
128
|
+
try {
|
|
129
|
+
execSync('git --version', { stdio: 'pipe' });
|
|
130
|
+
} catch {
|
|
131
|
+
logError('Git is not installed. Please install Git first.');
|
|
132
|
+
logInfo(' Windows: winget install Git.Git');
|
|
133
|
+
logInfo(' macOS: brew install git');
|
|
134
|
+
logInfo(' Linux: sudo apt install git');
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Check for Claude Code
|
|
139
|
+
try {
|
|
140
|
+
execSync('claude --version', { stdio: 'pipe' });
|
|
141
|
+
} catch {
|
|
142
|
+
logError('Claude Code CLI is not installed.');
|
|
143
|
+
logInfo(' Install from: https://claude.ai/download');
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function isWindows() {
|
|
149
|
+
return os.platform() === 'win32';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function getBashPath() {
|
|
153
|
+
if (!isWindows()) {
|
|
154
|
+
return 'bash';
|
|
155
|
+
}
|
|
156
|
+
// Delegate to shared terminal utility for Windows Git Bash detection
|
|
157
|
+
const { findGitBash } = require('../src/lib/terminal.js');
|
|
158
|
+
return findGitBash() || null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function runBashScript(scriptPath, args = []) {
|
|
162
|
+
const bashPath = getBashPath();
|
|
163
|
+
|
|
164
|
+
if (!bashPath) {
|
|
165
|
+
logError('Could not find bash. Please install Git Bash on Windows.');
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return new Promise((resolve, reject) => {
|
|
170
|
+
const child = spawn(bashPath, [scriptPath, ...args], {
|
|
171
|
+
stdio: 'inherit',
|
|
172
|
+
cwd: process.cwd(),
|
|
173
|
+
env: {
|
|
174
|
+
...process.env,
|
|
175
|
+
CLAUDE_CODE_GIT_BASH_PATH: bashPath,
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
child.on('close', (code) => {
|
|
180
|
+
if (code === 0) {
|
|
181
|
+
resolve();
|
|
182
|
+
} else {
|
|
183
|
+
reject(new Error(`Script exited with code ${code}`));
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
child.on('error', (err) => {
|
|
188
|
+
reject(err);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function initCommand() {
|
|
194
|
+
showBanner();
|
|
195
|
+
|
|
196
|
+
const targetDir = path.join(process.cwd(), FORGE_DIR);
|
|
197
|
+
|
|
198
|
+
// Check if already initialized
|
|
199
|
+
if (fs.existsSync(targetDir)) {
|
|
200
|
+
logInfo(`Vibe Forge already exists at ${FORGE_DIR}/`);
|
|
201
|
+
log('');
|
|
202
|
+
log('To update, run: npx vibe-forge update');
|
|
203
|
+
log('To reinitialize, delete the _vibe-forge folder first.');
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
logInfo('Checking prerequisites...');
|
|
208
|
+
checkPrerequisites();
|
|
209
|
+
logSuccess('Prerequisites OK');
|
|
210
|
+
log('');
|
|
211
|
+
|
|
212
|
+
// Clone the repository
|
|
213
|
+
logInfo(`Cloning Vibe Forge into ${FORGE_DIR}/...`);
|
|
214
|
+
try {
|
|
215
|
+
execSync(`git clone --depth 1 ${REPO_URL} ${FORGE_DIR}`, {
|
|
216
|
+
stdio: 'inherit',
|
|
217
|
+
cwd: process.cwd(),
|
|
218
|
+
});
|
|
219
|
+
logSuccess('Clone complete');
|
|
220
|
+
} catch {
|
|
221
|
+
logError('Failed to clone repository');
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
log('');
|
|
226
|
+
|
|
227
|
+
// Validate agents.json (alias collisions)
|
|
228
|
+
validateAgentsConfig(targetDir);
|
|
229
|
+
|
|
230
|
+
// Update project's .gitignore to ignore tool internals but keep project data
|
|
231
|
+
updateGitignore();
|
|
232
|
+
|
|
233
|
+
// Run the setup script
|
|
234
|
+
logInfo('Running setup...');
|
|
235
|
+
log('');
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
const setupScript = path.join(targetDir, 'bin', 'forge-setup.sh');
|
|
239
|
+
await runBashScript(setupScript);
|
|
240
|
+
} catch (err) {
|
|
241
|
+
logError(`Setup failed: ${err.message}`);
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function validateAgentsConfig(forgeDir) {
|
|
247
|
+
const checkScript = path.join(forgeDir, 'bin', 'lib', 'check-aliases.js');
|
|
248
|
+
if (!fs.existsSync(checkScript)) return;
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
const agentsFile = path.join(forgeDir, 'config', 'agents.json');
|
|
252
|
+
execSync(`node "${checkScript}" "${agentsFile}"`, { stdio: 'pipe' });
|
|
253
|
+
const config = JSON.parse(fs.readFileSync(agentsFile, 'utf8'));
|
|
254
|
+
const agentCount = Object.keys(config.agents || {}).length;
|
|
255
|
+
logSuccess(`Agent config valid (${agentCount} agents, no alias collisions)`);
|
|
256
|
+
} catch (err) {
|
|
257
|
+
logError('Agent alias collisions detected in config/agents.json');
|
|
258
|
+
logInfo(err.stderr ? err.stderr.toString().trim() : 'Run node src/lib/check-aliases.js for details');
|
|
259
|
+
logError('Fix collisions before using Vibe Forge.');
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function updateGitignore() {
|
|
265
|
+
const gitignorePath = path.join(process.cwd(), '.gitignore');
|
|
266
|
+
const forgeIgnoreMarker = '# Vibe Forge';
|
|
267
|
+
const forgeIgnoreBlock = `
|
|
268
|
+
# Vibe Forge
|
|
269
|
+
# Tool internals (regenerated on update)
|
|
270
|
+
_vibe-forge/.git/
|
|
271
|
+
_vibe-forge/bin/
|
|
272
|
+
_vibe-forge/agents/
|
|
273
|
+
_vibe-forge/config/
|
|
274
|
+
_vibe-forge/docs/
|
|
275
|
+
_vibe-forge/tests/
|
|
276
|
+
_vibe-forge/src/
|
|
277
|
+
_vibe-forge/node_modules/
|
|
278
|
+
_vibe-forge/package*.json
|
|
279
|
+
_vibe-forge/*.md
|
|
280
|
+
_vibe-forge/LICENSE
|
|
281
|
+
_vibe-forge/.github/
|
|
282
|
+
|
|
283
|
+
# Keep project data (tasks, context) - these ARE committed
|
|
284
|
+
# !_vibe-forge/tasks/
|
|
285
|
+
# !_vibe-forge/context/
|
|
286
|
+
# !_vibe-forge/.claude/
|
|
287
|
+
`;
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
let content = '';
|
|
291
|
+
if (fs.existsSync(gitignorePath)) {
|
|
292
|
+
content = fs.readFileSync(gitignorePath, 'utf8');
|
|
293
|
+
// Check if already has forge entries
|
|
294
|
+
if (content.includes(forgeIgnoreMarker)) {
|
|
295
|
+
return; // Already configured
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Append forge ignore block
|
|
300
|
+
const newContent = content.trimEnd() + '\n' + forgeIgnoreBlock;
|
|
301
|
+
fs.writeFileSync(gitignorePath, newContent);
|
|
302
|
+
logSuccess('Updated .gitignore for Vibe Forge');
|
|
303
|
+
} catch (err) {
|
|
304
|
+
logError(`Warning: Could not update .gitignore: ${err.message}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function updateCommand() {
|
|
309
|
+
showBanner();
|
|
310
|
+
|
|
311
|
+
const targetDir = path.join(process.cwd(), FORGE_DIR);
|
|
312
|
+
|
|
313
|
+
// Check if initialized
|
|
314
|
+
if (!fs.existsSync(targetDir)) {
|
|
315
|
+
logError('Vibe Forge is not initialized in this project.');
|
|
316
|
+
log('');
|
|
317
|
+
log('Run: npx vibe-forge init');
|
|
318
|
+
process.exit(1);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
logInfo('Updating Vibe Forge...');
|
|
322
|
+
|
|
323
|
+
try {
|
|
324
|
+
// Fetch and reset to origin/main - works even without tracking branch
|
|
325
|
+
execSync('git fetch origin', {
|
|
326
|
+
stdio: 'inherit',
|
|
327
|
+
cwd: targetDir,
|
|
328
|
+
});
|
|
329
|
+
execSync('git reset --hard origin/main', {
|
|
330
|
+
stdio: 'inherit',
|
|
331
|
+
cwd: targetDir,
|
|
332
|
+
});
|
|
333
|
+
logSuccess('Update complete');
|
|
334
|
+
} catch {
|
|
335
|
+
logError('Failed to update. Check your network connection or try deleting _vibe-forge and running init again.');
|
|
336
|
+
process.exit(1);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Main entry point
|
|
341
|
+
async function main() {
|
|
342
|
+
const args = process.argv.slice(2);
|
|
343
|
+
const command = args[0] || 'help';
|
|
344
|
+
|
|
345
|
+
switch (command) {
|
|
346
|
+
case 'init':
|
|
347
|
+
await initCommand();
|
|
348
|
+
break;
|
|
349
|
+
case 'update':
|
|
350
|
+
await updateCommand();
|
|
351
|
+
break;
|
|
352
|
+
case 'agents':
|
|
353
|
+
showAgents();
|
|
354
|
+
break;
|
|
355
|
+
case 'version':
|
|
356
|
+
case '--version':
|
|
357
|
+
case '-v':
|
|
358
|
+
showVersion();
|
|
359
|
+
break;
|
|
360
|
+
case 'help':
|
|
361
|
+
case '--help':
|
|
362
|
+
case '-h':
|
|
363
|
+
default:
|
|
364
|
+
showHelp();
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
main().catch((err) => {
|
|
370
|
+
logError(err.message);
|
|
371
|
+
process.exit(1);
|
|
372
|
+
});
|