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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gbos",
3
- "version": "1.3.23",
3
+ "version": "1.4.1",
4
4
  "description": "GBOS - Command line interface for GBOS services",
5
5
  "main": "src/index.js",
6
6
  "bin": {
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();
@@ -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://gitlab.com';
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 session = config.loadSession();
48
- return session?.gitlab_token || process.env.GITLAB_TOKEN || null;
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';