gbos 1.3.23 → 1.4.1
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/package.json +1 -1
- package/src/cli.js +55 -2
- package/src/commands/gitlab.js +153 -8
- package/src/commands/orchestrator.js +329 -0
- package/src/lib/api.js +4 -0
- package/src/lib/gitlab.js +502 -0
- package/src/orchestrator/adapters/base-adapter.js +204 -0
- package/src/orchestrator/adapters/claude-adapter.js +197 -0
- package/src/orchestrator/adapters/codex-adapter.js +133 -0
- package/src/orchestrator/adapters/gemini-adapter.js +144 -0
- package/src/orchestrator/adapters/index.js +70 -0
- package/src/orchestrator/managers/git-manager.js +358 -0
- package/src/orchestrator/managers/verification-manager.js +398 -0
- package/src/orchestrator/managers/workspace-manager.js +462 -0
- package/src/orchestrator/orchestrator.js +595 -0
- package/src/orchestrator/runners/session-runner.js +379 -0
- package/src/orchestrator/state-machine.js +257 -0
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -7,8 +7,9 @@ const authCommand = require('./commands/auth');
|
|
|
7
7
|
const connectCommand = require('./commands/connect');
|
|
8
8
|
const logoutCommand = require('./commands/logout');
|
|
9
9
|
const { tasksCommand, nextTaskCommand, continueCommand, fallbackCommand, addTaskCommand, completedCommand } = require('./commands/tasks');
|
|
10
|
-
const { syncStartCommand, syncStopCommand, syncStatusCommand, syncNowCommand, repoCreateCommand, repoListCommand, repoCloneCommand } = require('./commands/gitlab');
|
|
10
|
+
const { syncStartCommand, syncStopCommand, syncStatusCommand, syncNowCommand, repoCreateCommand, repoListCommand, repoCloneCommand, authCommand: gitlabAuthCommand, authStatusCommand: gitlabAuthStatusCommand, authLogoutCommand: gitlabAuthLogoutCommand } = require('./commands/gitlab');
|
|
11
11
|
const { registryLoginCommand, registryImagesCommand, registryPushCommand, registryPullCommand } = require('./commands/registry');
|
|
12
|
+
const { startCommand, resumeCommand, stopCommand, runsCommand } = require('./commands/orchestrator');
|
|
12
13
|
const config = require('./lib/config');
|
|
13
14
|
const { displayStatus, printBanner } = require('./lib/display');
|
|
14
15
|
|
|
@@ -133,6 +134,40 @@ program
|
|
|
133
134
|
.description('Create a new task interactively')
|
|
134
135
|
.action(addTaskCommand);
|
|
135
136
|
|
|
137
|
+
// ==================== Orchestrator Commands ====================
|
|
138
|
+
|
|
139
|
+
program
|
|
140
|
+
.command('start')
|
|
141
|
+
.description('Start the GBOS orchestrator to automatically process tasks')
|
|
142
|
+
.option('-a, --agent <agent>', 'Agent to use (claude-code, codex, gemini)', 'claude-code')
|
|
143
|
+
.option('-d, --dir <directory>', 'Working directory')
|
|
144
|
+
.option('--auto-approve', 'Auto-approve agent actions')
|
|
145
|
+
.option('--no-mr', 'Skip merge request creation')
|
|
146
|
+
.option('-c, --continuous', 'Continuously process tasks')
|
|
147
|
+
.option('-n, --max-tasks <number>', 'Maximum tasks to process', '1')
|
|
148
|
+
.option('--show-prompt', 'Show the generated prompt')
|
|
149
|
+
.action(startCommand);
|
|
150
|
+
|
|
151
|
+
program
|
|
152
|
+
.command('resume')
|
|
153
|
+
.description('Resume a paused orchestrator run')
|
|
154
|
+
.option('-r, --run-id <runId>', 'Specific run ID to resume')
|
|
155
|
+
.option('--no-mr', 'Skip merge request creation')
|
|
156
|
+
.action(resumeCommand);
|
|
157
|
+
|
|
158
|
+
program
|
|
159
|
+
.command('stop')
|
|
160
|
+
.description('Stop an active orchestrator run')
|
|
161
|
+
.option('-r, --run-id <runId>', 'Specific run ID to stop')
|
|
162
|
+
.option('-f, --force', 'Force stop and mark as failed')
|
|
163
|
+
.action(stopCommand);
|
|
164
|
+
|
|
165
|
+
program
|
|
166
|
+
.command('runs')
|
|
167
|
+
.description('List recent orchestrator runs')
|
|
168
|
+
.option('-l, --limit <number>', 'Number of runs to show', '10')
|
|
169
|
+
.action(runsCommand);
|
|
170
|
+
|
|
136
171
|
program
|
|
137
172
|
.command('logout')
|
|
138
173
|
.description('Log out from GBOS services and clear credentials')
|
|
@@ -202,6 +237,24 @@ gitlabRepo
|
|
|
202
237
|
.option('-d, --dir <directory>', 'Target directory name')
|
|
203
238
|
.action(repoCloneCommand);
|
|
204
239
|
|
|
240
|
+
// GitLab Auth subcommands
|
|
241
|
+
gitlabCmd
|
|
242
|
+
.command('auth')
|
|
243
|
+
.description('Authenticate with GitLab (store token securely)')
|
|
244
|
+
.option('-t, --token <token>', 'GitLab Personal Access Token')
|
|
245
|
+
.option('-h, --host <host>', 'GitLab host (default: https://git.gbos.io)')
|
|
246
|
+
.action(gitlabAuthCommand);
|
|
247
|
+
|
|
248
|
+
gitlabCmd
|
|
249
|
+
.command('auth-status')
|
|
250
|
+
.description('Show GitLab authentication status')
|
|
251
|
+
.action(gitlabAuthStatusCommand);
|
|
252
|
+
|
|
253
|
+
gitlabCmd
|
|
254
|
+
.command('auth-logout')
|
|
255
|
+
.description('Remove GitLab credentials')
|
|
256
|
+
.action(gitlabAuthLogoutCommand);
|
|
257
|
+
|
|
205
258
|
// ==================== Registry Commands ====================
|
|
206
259
|
|
|
207
260
|
const registryCmd = program
|
|
@@ -244,7 +297,7 @@ program
|
|
|
244
297
|
cmd.outputHelp();
|
|
245
298
|
} else {
|
|
246
299
|
console.log(`Unknown command: ${command}`);
|
|
247
|
-
console.log('Available commands: auth, connect, disconnect, status, tasks, next, continue, completed, fallback, add_task, logout, gitlab, registry, help');
|
|
300
|
+
console.log('Available commands: auth, connect, disconnect, status, tasks, next, continue, completed, fallback, add_task, start, resume, stop, runs, logout, gitlab, registry, help');
|
|
248
301
|
}
|
|
249
302
|
} else {
|
|
250
303
|
program.outputHelp();
|
package/src/commands/gitlab.js
CHANGED
|
@@ -38,14 +38,25 @@ function saveGitLabConfig(config) {
|
|
|
38
38
|
|
|
39
39
|
// Get GitLab URL from session or config
|
|
40
40
|
function getGitLabUrl() {
|
|
41
|
+
// Check config file first
|
|
42
|
+
try {
|
|
43
|
+
if (fs.existsSync(GITLAB_CONFIG_FILE)) {
|
|
44
|
+
const gitlabConfig = JSON.parse(fs.readFileSync(GITLAB_CONFIG_FILE, 'utf8'));
|
|
45
|
+
if (gitlabConfig.host) return gitlabConfig.host;
|
|
46
|
+
}
|
|
47
|
+
} catch (e) {
|
|
48
|
+
// Ignore
|
|
49
|
+
}
|
|
50
|
+
|
|
41
51
|
const session = config.loadSession();
|
|
42
|
-
return session?.gitlab_url || process.env.GITLAB_URL || 'https://
|
|
52
|
+
return session?.gitlab_url || process.env.GITLAB_URL || 'https://git.gbos.io';
|
|
43
53
|
}
|
|
44
54
|
|
|
45
|
-
// Get GitLab token
|
|
46
|
-
function getGitLabToken() {
|
|
47
|
-
const
|
|
48
|
-
|
|
55
|
+
// Get GitLab token (checks all storage locations)
|
|
56
|
+
async function getGitLabToken() {
|
|
57
|
+
const { createGitLabService } = require('../lib/gitlab');
|
|
58
|
+
const gitlabService = createGitLabService();
|
|
59
|
+
return await gitlabService.getToken();
|
|
49
60
|
}
|
|
50
61
|
|
|
51
62
|
// Execute git command
|
|
@@ -291,7 +302,7 @@ async function syncNowCommand(options) {
|
|
|
291
302
|
|
|
292
303
|
// Create a new repository
|
|
293
304
|
async function repoCreateCommand(name, options) {
|
|
294
|
-
const token = getGitLabToken();
|
|
305
|
+
const token = await getGitLabToken();
|
|
295
306
|
const gitlabUrl = getGitLabUrl();
|
|
296
307
|
|
|
297
308
|
if (!token) {
|
|
@@ -354,7 +365,7 @@ async function repoCreateCommand(name, options) {
|
|
|
354
365
|
|
|
355
366
|
// List repositories
|
|
356
367
|
async function repoListCommand(options) {
|
|
357
|
-
const token = getGitLabToken();
|
|
368
|
+
const token = await getGitLabToken();
|
|
358
369
|
const gitlabUrl = getGitLabUrl();
|
|
359
370
|
|
|
360
371
|
if (!token) {
|
|
@@ -410,7 +421,7 @@ async function repoListCommand(options) {
|
|
|
410
421
|
|
|
411
422
|
// Clone a repository
|
|
412
423
|
async function repoCloneCommand(name, options) {
|
|
413
|
-
const token = getGitLabToken();
|
|
424
|
+
const token = await getGitLabToken();
|
|
414
425
|
const gitlabUrl = getGitLabUrl();
|
|
415
426
|
|
|
416
427
|
if (!token) {
|
|
@@ -459,6 +470,137 @@ async function repoCloneCommand(name, options) {
|
|
|
459
470
|
}
|
|
460
471
|
}
|
|
461
472
|
|
|
473
|
+
// ==================== AUTH COMMANDS ====================
|
|
474
|
+
|
|
475
|
+
// Authenticate with GitLab
|
|
476
|
+
async function authCommand(options) {
|
|
477
|
+
const { createGitLabService, GITLAB_HOST } = require('../lib/gitlab');
|
|
478
|
+
const readline = require('readline');
|
|
479
|
+
|
|
480
|
+
const termWidth = getTerminalWidth();
|
|
481
|
+
const tableWidth = Math.min(80, termWidth - 4);
|
|
482
|
+
|
|
483
|
+
console.log(`\n${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}`);
|
|
484
|
+
console.log(`${BOLD} GitLab Authentication${RESET}`);
|
|
485
|
+
console.log(`${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}\n`);
|
|
486
|
+
|
|
487
|
+
let token = options.token;
|
|
488
|
+
const host = options.host || GITLAB_HOST;
|
|
489
|
+
|
|
490
|
+
// Interactive prompt if no token provided
|
|
491
|
+
if (!token) {
|
|
492
|
+
const rl = readline.createInterface({
|
|
493
|
+
input: process.stdin,
|
|
494
|
+
output: process.stdout,
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
console.log(` ${DIM}GitLab Host: ${host}${RESET}`);
|
|
498
|
+
console.log(` ${DIM}Create a Personal Access Token with scopes:${RESET}`);
|
|
499
|
+
console.log(` ${DIM}- api${RESET}`);
|
|
500
|
+
console.log(` ${DIM}- read_repository${RESET}`);
|
|
501
|
+
console.log(` ${DIM}- write_repository${RESET}`);
|
|
502
|
+
console.log(` ${DIM}- read_registry${RESET}`);
|
|
503
|
+
console.log(` ${DIM}- write_registry${RESET}\n`);
|
|
504
|
+
|
|
505
|
+
token = await new Promise((resolve) => {
|
|
506
|
+
rl.question(` ${CYAN}?${RESET} Enter GitLab Personal Access Token: `, (answer) => {
|
|
507
|
+
rl.close();
|
|
508
|
+
resolve(answer.trim());
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
if (!token) {
|
|
513
|
+
console.log(`\n ${YELLOW}!${RESET} No token provided. Cancelled.\n`);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
console.log(`\n ${DIM}Validating token...${RESET}`);
|
|
519
|
+
|
|
520
|
+
const gitlabService = createGitLabService({ host });
|
|
521
|
+
|
|
522
|
+
try {
|
|
523
|
+
const user = await gitlabService.storeToken(token);
|
|
524
|
+
|
|
525
|
+
console.log(`\n${GREEN}✓${RESET} ${BOLD}GitLab authentication successful${RESET}`);
|
|
526
|
+
console.log(` ${DIM}User:${RESET} ${user.username} (${user.name || user.email})`);
|
|
527
|
+
console.log(` ${DIM}Host:${RESET} ${host}`);
|
|
528
|
+
console.log(` ${GREEN}✓${RESET} Token stored securely`);
|
|
529
|
+
console.log(` ${GREEN}✓${RESET} Git credentials configured\n`);
|
|
530
|
+
|
|
531
|
+
console.log(` ${DIM}You can now use:${RESET}`);
|
|
532
|
+
console.log(` ${DIM}- gbos gitlab repo create <name>${RESET}`);
|
|
533
|
+
console.log(` ${DIM}- gbos gitlab repo clone <name>${RESET}`);
|
|
534
|
+
console.log(` ${DIM}- gbos start (orchestrator with GitLab integration)${RESET}\n`);
|
|
535
|
+
|
|
536
|
+
} catch (error) {
|
|
537
|
+
console.log(`\n${RED}✗${RESET} ${BOLD}Authentication failed${RESET}`);
|
|
538
|
+
console.log(` ${DIM}${error.message}${RESET}\n`);
|
|
539
|
+
process.exit(1);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Show GitLab auth status
|
|
544
|
+
async function authStatusCommand() {
|
|
545
|
+
const { createGitLabService, GITLAB_HOST, GITLAB_CONFIG_FILE } = require('../lib/gitlab');
|
|
546
|
+
|
|
547
|
+
const termWidth = getTerminalWidth();
|
|
548
|
+
const tableWidth = Math.min(80, termWidth - 4);
|
|
549
|
+
|
|
550
|
+
console.log(`\n${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}`);
|
|
551
|
+
console.log(`${BOLD} GitLab Authentication Status${RESET}`);
|
|
552
|
+
console.log(`${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}\n`);
|
|
553
|
+
|
|
554
|
+
const gitlabService = createGitLabService();
|
|
555
|
+
|
|
556
|
+
try {
|
|
557
|
+
const token = await gitlabService.getToken();
|
|
558
|
+
|
|
559
|
+
if (!token) {
|
|
560
|
+
console.log(` ${YELLOW}!${RESET} Not authenticated with GitLab`);
|
|
561
|
+
console.log(` ${DIM}Run: gbos gitlab auth --token <your-token>${RESET}\n`);
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Validate token
|
|
566
|
+
gitlabService.token = token;
|
|
567
|
+
const user = await gitlabService.getCurrentUser();
|
|
568
|
+
|
|
569
|
+
console.log(` ${GREEN}●${RESET} ${BOLD}Authenticated${RESET}`);
|
|
570
|
+
console.log(` ${DIM}User:${RESET} ${user.username}`);
|
|
571
|
+
console.log(` ${DIM}Name:${RESET} ${user.name || 'N/A'}`);
|
|
572
|
+
console.log(` ${DIM}Email:${RESET} ${user.email || 'N/A'}`);
|
|
573
|
+
console.log(` ${DIM}Host:${RESET} ${GITLAB_HOST}\n`);
|
|
574
|
+
|
|
575
|
+
// Check if config file exists
|
|
576
|
+
if (fs.existsSync(GITLAB_CONFIG_FILE)) {
|
|
577
|
+
const gitlabConfig = JSON.parse(fs.readFileSync(GITLAB_CONFIG_FILE, 'utf8'));
|
|
578
|
+
if (gitlabConfig.storedAt) {
|
|
579
|
+
console.log(` ${DIM}Stored: ${new Date(gitlabConfig.storedAt).toLocaleString()}${RESET}\n`);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
} catch (error) {
|
|
584
|
+
console.log(` ${RED}●${RESET} ${BOLD}Invalid or expired token${RESET}`);
|
|
585
|
+
console.log(` ${DIM}Error: ${error.message}${RESET}`);
|
|
586
|
+
console.log(` ${DIM}Run: gbos gitlab auth --token <new-token>${RESET}\n`);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Logout from GitLab
|
|
591
|
+
async function authLogoutCommand() {
|
|
592
|
+
const { createGitLabService } = require('../lib/gitlab');
|
|
593
|
+
|
|
594
|
+
const gitlabService = createGitLabService();
|
|
595
|
+
|
|
596
|
+
try {
|
|
597
|
+
await gitlabService.deleteToken();
|
|
598
|
+
console.log(`\n${GREEN}✓${RESET} GitLab credentials removed.\n`);
|
|
599
|
+
} catch (error) {
|
|
600
|
+
console.log(`\n${YELLOW}!${RESET} ${error.message}\n`);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
462
604
|
module.exports = {
|
|
463
605
|
syncStartCommand,
|
|
464
606
|
syncStopCommand,
|
|
@@ -467,4 +609,7 @@ module.exports = {
|
|
|
467
609
|
repoCreateCommand,
|
|
468
610
|
repoListCommand,
|
|
469
611
|
repoCloneCommand,
|
|
612
|
+
authCommand,
|
|
613
|
+
authStatusCommand,
|
|
614
|
+
authLogoutCommand,
|
|
470
615
|
};
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orchestrator Commands
|
|
3
|
+
* CLI commands for start, resume, and stop
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const config = require('../lib/config');
|
|
7
|
+
const { displayMessageBox, fg, LOGO_PURPLE, RESET, BOLD, DIM, getTerminalWidth } = require('../lib/display');
|
|
8
|
+
const Orchestrator = require('../orchestrator/orchestrator');
|
|
9
|
+
const { StateMachine, STATES, RUNS_DIR } = require('../orchestrator/state-machine');
|
|
10
|
+
const { checkInstalledAdapters } = require('../orchestrator/adapters');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
// Colors
|
|
15
|
+
const GREEN = '\x1b[32m';
|
|
16
|
+
const YELLOW = '\x1b[33m';
|
|
17
|
+
const RED = '\x1b[31m';
|
|
18
|
+
const CYAN = '\x1b[36m';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* gbos start - Start the orchestrator
|
|
22
|
+
*/
|
|
23
|
+
async function startCommand(options) {
|
|
24
|
+
// Check authentication
|
|
25
|
+
if (!config.isAuthenticated()) {
|
|
26
|
+
displayMessageBox('Not Authenticated', 'Please run "gbos auth" first.', 'warning');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Check connection
|
|
31
|
+
const connection = config.getConnection();
|
|
32
|
+
if (!connection) {
|
|
33
|
+
displayMessageBox('Not Connected', 'Please run "gbos connect" first.', 'warning');
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const termWidth = getTerminalWidth();
|
|
38
|
+
const tableWidth = Math.min(80, termWidth - 4);
|
|
39
|
+
|
|
40
|
+
console.log(`\n${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}`);
|
|
41
|
+
console.log(`${BOLD} GBOS Orchestrator${RESET}`);
|
|
42
|
+
console.log(`${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}\n`);
|
|
43
|
+
|
|
44
|
+
// Check for existing active run
|
|
45
|
+
const activeRun = StateMachine.getActiveRun();
|
|
46
|
+
if (activeRun) {
|
|
47
|
+
console.log(` ${YELLOW}!${RESET} An active run exists: ${activeRun.runId}`);
|
|
48
|
+
console.log(` ${DIM}State: ${activeRun.state}${RESET}`);
|
|
49
|
+
console.log(` ${DIM}Use "gbos resume" to continue or "gbos stop" to cancel.${RESET}\n`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check agent availability
|
|
54
|
+
console.log(` ${DIM}Checking agent availability...${RESET}`);
|
|
55
|
+
const adapters = await checkInstalledAdapters();
|
|
56
|
+
const agentName = options.agent || 'claude-code';
|
|
57
|
+
const agentInfo = adapters[agentName] || adapters['claude-code'];
|
|
58
|
+
|
|
59
|
+
if (!agentInfo?.available) {
|
|
60
|
+
displayMessageBox('Agent Not Found', `Agent "${agentName}" is not installed.\n\nAvailable agents: ${Object.entries(adapters).filter(([_, v]) => v.available).map(([k]) => k).join(', ') || 'none'}`, 'error');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log(` ${GREEN}✓${RESET} Agent: ${agentName} (${agentInfo.version || 'installed'})`);
|
|
65
|
+
console.log(` ${DIM}Application: ${connection.application?.name || 'N/A'}${RESET}`);
|
|
66
|
+
console.log(` ${DIM}Node: ${connection.node?.name || 'N/A'}${RESET}`);
|
|
67
|
+
console.log('');
|
|
68
|
+
|
|
69
|
+
// Create orchestrator
|
|
70
|
+
const orchestrator = new Orchestrator({
|
|
71
|
+
agent: agentName,
|
|
72
|
+
autoApprove: options.autoApprove || false,
|
|
73
|
+
createMR: options.mr !== false,
|
|
74
|
+
continuous: options.continuous || false,
|
|
75
|
+
maxTasks: options.maxTasks ? parseInt(options.maxTasks) : 1,
|
|
76
|
+
workingDir: options.dir ? path.resolve(options.dir) : null,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Set up event handlers
|
|
80
|
+
orchestrator.on('started', ({ runId }) => {
|
|
81
|
+
console.log(` ${GREEN}✓${RESET} Run started: ${runId}\n`);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
orchestrator.on('stage', ({ stage }) => {
|
|
85
|
+
console.log(` ${CYAN}▸${RESET} ${stage.replace(/_/g, ' ')}`);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
orchestrator.on('log', ({ message }) => {
|
|
89
|
+
console.log(` ${DIM}${message}${RESET}`);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
orchestrator.on('prompt', ({ prompt }) => {
|
|
93
|
+
if (options.showPrompt) {
|
|
94
|
+
console.log(`\n${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}`);
|
|
95
|
+
console.log(`${BOLD} Task Prompt${RESET}`);
|
|
96
|
+
console.log(`${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}\n`);
|
|
97
|
+
console.log(prompt);
|
|
98
|
+
console.log(`${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}\n`);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
orchestrator.on('committed', (result) => {
|
|
103
|
+
if (result.commit) {
|
|
104
|
+
console.log(` ${GREEN}✓${RESET} Committed: ${result.commit.shortHash}`);
|
|
105
|
+
}
|
|
106
|
+
if (result.mergeRequest) {
|
|
107
|
+
console.log(` ${GREEN}✓${RESET} MR: ${result.mergeRequest.url}`);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
orchestrator.on('completed', ({ tasksCompleted }) => {
|
|
112
|
+
console.log(`\n${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}`);
|
|
113
|
+
console.log(`${GREEN}✓${RESET} ${BOLD}Orchestrator completed${RESET}`);
|
|
114
|
+
console.log(` ${DIM}Tasks completed: ${tasksCompleted}${RESET}`);
|
|
115
|
+
console.log(`${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}\n`);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
orchestrator.on('failed', ({ error }) => {
|
|
119
|
+
console.log(`\n${RED}✗${RESET} ${BOLD}Orchestrator failed${RESET}`);
|
|
120
|
+
console.log(` ${DIM}Error: ${error.message}${RESET}\n`);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Handle interrupts
|
|
124
|
+
process.on('SIGINT', async () => {
|
|
125
|
+
console.log(`\n\n ${YELLOW}!${RESET} Stopping orchestrator...`);
|
|
126
|
+
await orchestrator.stop();
|
|
127
|
+
console.log(` ${DIM}Run paused. Use "gbos resume" to continue.${RESET}\n`);
|
|
128
|
+
process.exit(0);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Start the orchestrator
|
|
132
|
+
try {
|
|
133
|
+
await orchestrator.start();
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.log(`\n${RED}✗${RESET} ${error.message}\n`);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* gbos resume - Resume a paused run
|
|
142
|
+
*/
|
|
143
|
+
async function resumeCommand(options) {
|
|
144
|
+
const termWidth = getTerminalWidth();
|
|
145
|
+
const tableWidth = Math.min(80, termWidth - 4);
|
|
146
|
+
|
|
147
|
+
console.log(`\n${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}`);
|
|
148
|
+
console.log(`${BOLD} Resume Orchestrator${RESET}`);
|
|
149
|
+
console.log(`${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}\n`);
|
|
150
|
+
|
|
151
|
+
// Find run to resume
|
|
152
|
+
let runId = options.runId;
|
|
153
|
+
let run;
|
|
154
|
+
|
|
155
|
+
if (runId) {
|
|
156
|
+
try {
|
|
157
|
+
run = StateMachine.loadRun(runId);
|
|
158
|
+
} catch (e) {
|
|
159
|
+
displayMessageBox('Run Not Found', `Run "${runId}" not found.`, 'error');
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
run = StateMachine.getActiveRun();
|
|
164
|
+
if (!run) {
|
|
165
|
+
console.log(` ${DIM}No active runs to resume.${RESET}`);
|
|
166
|
+
console.log(` ${DIM}Use "gbos start" to begin a new run.${RESET}\n`);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
runId = run.runId;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!run.isResumable()) {
|
|
173
|
+
console.log(` ${YELLOW}!${RESET} Run ${runId} is not resumable (state: ${run.state})`);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
console.log(` ${DIM}Resuming run: ${runId}${RESET}`);
|
|
178
|
+
console.log(` ${DIM}State: ${run.state}${RESET}`);
|
|
179
|
+
console.log(` ${DIM}Task: ${run.context.taskKey || run.context.taskId || 'N/A'}${RESET}\n`);
|
|
180
|
+
|
|
181
|
+
// Create orchestrator and resume
|
|
182
|
+
const orchestrator = new Orchestrator({
|
|
183
|
+
agent: run.context.agentVendor || 'claude-code',
|
|
184
|
+
createMR: options.mr !== false,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Set up event handlers (same as start)
|
|
188
|
+
orchestrator.on('stage', ({ stage }) => {
|
|
189
|
+
console.log(` ${CYAN}▸${RESET} ${stage.replace(/_/g, ' ')}`);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
orchestrator.on('log', ({ message }) => {
|
|
193
|
+
console.log(` ${DIM}${message}${RESET}`);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
orchestrator.on('completed', ({ tasksCompleted }) => {
|
|
197
|
+
console.log(`\n${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}`);
|
|
198
|
+
console.log(`${GREEN}✓${RESET} ${BOLD}Orchestrator completed${RESET}`);
|
|
199
|
+
console.log(` ${DIM}Tasks completed: ${tasksCompleted}${RESET}`);
|
|
200
|
+
console.log(`${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}\n`);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
orchestrator.on('failed', ({ error }) => {
|
|
204
|
+
console.log(`\n${RED}✗${RESET} ${BOLD}Orchestrator failed${RESET}`);
|
|
205
|
+
console.log(` ${DIM}Error: ${error.message}${RESET}\n`);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
process.on('SIGINT', async () => {
|
|
209
|
+
console.log(`\n\n ${YELLOW}!${RESET} Stopping orchestrator...`);
|
|
210
|
+
await orchestrator.stop();
|
|
211
|
+
process.exit(0);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
await orchestrator.resume(runId);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.log(`\n${RED}✗${RESET} ${error.message}\n`);
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* gbos stop - Stop an active run
|
|
224
|
+
*/
|
|
225
|
+
async function stopCommand(options) {
|
|
226
|
+
const termWidth = getTerminalWidth();
|
|
227
|
+
const tableWidth = Math.min(80, termWidth - 4);
|
|
228
|
+
|
|
229
|
+
console.log(`\n${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}`);
|
|
230
|
+
console.log(`${BOLD} Stop Orchestrator${RESET}`);
|
|
231
|
+
console.log(`${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}\n`);
|
|
232
|
+
|
|
233
|
+
// Find active run
|
|
234
|
+
let runId = options.runId;
|
|
235
|
+
let run;
|
|
236
|
+
|
|
237
|
+
if (runId) {
|
|
238
|
+
try {
|
|
239
|
+
run = StateMachine.loadRun(runId);
|
|
240
|
+
} catch (e) {
|
|
241
|
+
displayMessageBox('Run Not Found', `Run "${runId}" not found.`, 'error');
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
run = StateMachine.getActiveRun();
|
|
246
|
+
if (!run) {
|
|
247
|
+
console.log(` ${DIM}No active runs to stop.${RESET}\n`);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
runId = run.runId;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (run.state === STATES.COMPLETED || run.state === STATES.FAILED) {
|
|
254
|
+
console.log(` ${DIM}Run ${runId} is already ${run.state}.${RESET}\n`);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Transition to paused or failed
|
|
259
|
+
if (options.force) {
|
|
260
|
+
run.transition(STATES.FAILED, { reason: 'Forcefully stopped by user' });
|
|
261
|
+
console.log(` ${RED}✗${RESET} Run ${runId} forcefully stopped.\n`);
|
|
262
|
+
} else {
|
|
263
|
+
run.transition(STATES.PAUSED, { reason: 'Stopped by user' });
|
|
264
|
+
console.log(` ${YELLOW}!${RESET} Run ${runId} paused.`);
|
|
265
|
+
console.log(` ${DIM}Use "gbos resume" to continue.${RESET}\n`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* gbos runs - List recent runs
|
|
271
|
+
*/
|
|
272
|
+
async function runsCommand(options) {
|
|
273
|
+
const termWidth = getTerminalWidth();
|
|
274
|
+
const tableWidth = Math.min(100, termWidth - 4);
|
|
275
|
+
|
|
276
|
+
console.log(`\n${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}`);
|
|
277
|
+
console.log(`${BOLD} Orchestrator Runs${RESET}`);
|
|
278
|
+
console.log(`${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}\n`);
|
|
279
|
+
|
|
280
|
+
StateMachine.ensureRunsDir();
|
|
281
|
+
|
|
282
|
+
const files = fs.readdirSync(RUNS_DIR)
|
|
283
|
+
.filter(f => f.endsWith('.json'))
|
|
284
|
+
.sort()
|
|
285
|
+
.reverse()
|
|
286
|
+
.slice(0, options.limit ? parseInt(options.limit) : 10);
|
|
287
|
+
|
|
288
|
+
if (files.length === 0) {
|
|
289
|
+
console.log(` ${DIM}No runs found.${RESET}`);
|
|
290
|
+
console.log(` ${DIM}Use "gbos start" to begin a new run.${RESET}\n`);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
for (const file of files) {
|
|
295
|
+
try {
|
|
296
|
+
const runId = file.replace('.json', '');
|
|
297
|
+
const run = StateMachine.loadRun(runId);
|
|
298
|
+
const summary = run.getSummary();
|
|
299
|
+
|
|
300
|
+
const stateColors = {
|
|
301
|
+
[STATES.COMPLETED]: GREEN,
|
|
302
|
+
[STATES.FAILED]: RED,
|
|
303
|
+
[STATES.PAUSED]: YELLOW,
|
|
304
|
+
};
|
|
305
|
+
const stateColor = stateColors[summary.state] || CYAN;
|
|
306
|
+
|
|
307
|
+
console.log(` ${stateColor}●${RESET} ${BOLD}${summary.runId}${RESET}`);
|
|
308
|
+
console.log(` ${DIM}State: ${summary.state} | Agent: ${summary.agent}${RESET}`);
|
|
309
|
+
if (summary.taskKey) {
|
|
310
|
+
console.log(` ${DIM}Task: ${summary.taskKey}${RESET}`);
|
|
311
|
+
}
|
|
312
|
+
if (summary.startTime) {
|
|
313
|
+
console.log(` ${DIM}Started: ${new Date(summary.startTime).toLocaleString()}${RESET}`);
|
|
314
|
+
}
|
|
315
|
+
console.log('');
|
|
316
|
+
} catch (e) {
|
|
317
|
+
// Skip invalid files
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
console.log(`${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}\n`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
module.exports = {
|
|
325
|
+
startCommand,
|
|
326
|
+
resumeCommand,
|
|
327
|
+
stopCommand,
|
|
328
|
+
runsCommand,
|
|
329
|
+
};
|
package/src/lib/api.js
CHANGED
|
@@ -91,6 +91,10 @@ class GbosApiClient {
|
|
|
91
91
|
return this.request('/cli/applications', { method: 'GET' });
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
async getApplication(applicationId) {
|
|
95
|
+
return this.request(`/cli/applications/${applicationId}`, { method: 'GET' });
|
|
96
|
+
}
|
|
97
|
+
|
|
94
98
|
// Node endpoints
|
|
95
99
|
async listNodes(applicationId = null) {
|
|
96
100
|
let endpoint = '/cli/nodes';
|