@guildai/cli 0.3.15 → 0.3.17

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.
Files changed (61) hide show
  1. package/dist/commands/agent/clone.js +21 -40
  2. package/dist/commands/agent/code.js +12 -32
  3. package/dist/commands/agent/create.js +14 -30
  4. package/dist/commands/agent/fork.js +27 -73
  5. package/dist/commands/agent/get.js +5 -4
  6. package/dist/commands/agent/grep.js +7 -9
  7. package/dist/commands/agent/init.js +2 -0
  8. package/dist/commands/agent/list.js +5 -12
  9. package/dist/commands/agent/publish.js +27 -44
  10. package/dist/commands/agent/pull.js +8 -35
  11. package/dist/commands/agent/revalidate.js +8 -16
  12. package/dist/commands/agent/save.js +23 -74
  13. package/dist/commands/agent/search.js +5 -12
  14. package/dist/commands/agent/tags/add.js +14 -24
  15. package/dist/commands/agent/tags/list.js +12 -23
  16. package/dist/commands/agent/tags/remove.js +16 -27
  17. package/dist/commands/agent/tags/set.js +14 -19
  18. package/dist/commands/agent/unpublish.js +12 -17
  19. package/dist/commands/agent/update.js +12 -29
  20. package/dist/commands/agent/versions.js +7 -11
  21. package/dist/commands/auth/login.js +4 -2
  22. package/dist/commands/auth/logout.js +3 -1
  23. package/dist/commands/auth/status.js +4 -3
  24. package/dist/commands/auth/token.js +3 -2
  25. package/dist/commands/chat.js +85 -10
  26. package/dist/commands/config/get.js +7 -9
  27. package/dist/commands/config/list.js +13 -11
  28. package/dist/commands/config/path.js +6 -4
  29. package/dist/commands/config/set.js +17 -22
  30. package/dist/commands/doctor.js +9 -7
  31. package/dist/commands/session/create.js +7 -5
  32. package/dist/commands/session/events.js +5 -3
  33. package/dist/commands/session/get.js +5 -3
  34. package/dist/commands/session/list.js +5 -4
  35. package/dist/commands/session/send.js +7 -5
  36. package/dist/commands/session/tasks.js +5 -3
  37. package/dist/commands/setup.js +15 -14
  38. package/dist/commands/trigger/activate.js +7 -6
  39. package/dist/commands/trigger/create.js +16 -15
  40. package/dist/commands/trigger/deactivate.js +7 -6
  41. package/dist/commands/trigger/get.js +5 -4
  42. package/dist/commands/trigger/list.js +5 -5
  43. package/dist/commands/trigger/sessions.js +7 -6
  44. package/dist/commands/trigger/update.js +11 -10
  45. package/dist/commands/version.js +7 -5
  46. package/dist/commands/workspace/agent/add.js +16 -22
  47. package/dist/commands/workspace/agent/list.js +9 -30
  48. package/dist/commands/workspace/agent/remove.js +9 -15
  49. package/dist/commands/workspace/context/edit.js +13 -27
  50. package/dist/commands/workspace/context/get.js +8 -14
  51. package/dist/commands/workspace/context/list.js +7 -38
  52. package/dist/commands/workspace/context/publish.js +7 -11
  53. package/dist/commands/workspace/create.js +7 -11
  54. package/dist/commands/workspace/current.js +19 -31
  55. package/dist/commands/workspace/get.js +7 -11
  56. package/dist/commands/workspace/list.js +5 -8
  57. package/dist/commands/workspace/select.js +17 -22
  58. package/dist/components/TaskView.js +2 -0
  59. package/dist/lib/output.js +4 -4
  60. package/dist/lib/session-events.d.ts +7 -2
  61. package/package.json +1 -1
@@ -76,7 +76,11 @@ function CustomInput({ value, onChange, onSubmit, trackedTasksSize, setShowTaskP
76
76
  const renderedValue = value + chalk.inverse(' ');
77
77
  return React.createElement(Text, null, renderedValue);
78
78
  }
79
- function InputWrapper({ isReady, input, setInput, handleSubmit, trackedTasksSize, setShowTaskPanel, isActive, }) {
79
+ function InputWrapper({ isReady, isInterrupted, input, setInput, handleSubmit, trackedTasksSize, setShowTaskPanel, isActive, }) {
80
+ if (isInterrupted) {
81
+ return (React.createElement(Box, { height: 1 },
82
+ React.createElement(Text, { color: "gray" }, chalk.dim('Interrupted — this session cannot be resumed. Press Ctrl+C to exit.'))));
83
+ }
80
84
  return (React.createElement(Box, { height: 1 },
81
85
  React.createElement(Text, { color: isReady ? BRAND_COLOR : 'gray' }, "> "),
82
86
  isReady && isActive ? (React.createElement(CustomInput, { value: input, onChange: setInput, onSubmit: handleSubmit, trackedTasksSize: trackedTasksSize, setShowTaskPanel: setShowTaskPanel, isActive: isActive })) : isReady ? (React.createElement(Text, null, input)) : (React.createElement(Text, null, chalk.dim('(connecting...)')))));
@@ -229,11 +233,22 @@ function ChatUIWithConnection({ initialPrompt, version: _version, versionId: _ve
229
233
  ]
230
234
  : []);
231
235
  const [input, setInput] = useState('');
236
+ const inputTextRef = useRef('');
237
+ // Keep ref in sync so useInput handler can read current input text
238
+ const updateInput = (value) => {
239
+ inputTextRef.current = value;
240
+ setInput(value);
241
+ };
232
242
  const [currentOperation, setCurrentOperation] = useState(resumeEvents ? '' : 'Waiting for response...');
233
243
  const [exitHint, setExitHint] = useState(null);
244
+ const [isInterrupted, setIsInterrupted] = useState(false);
234
245
  // Double-tap exit tracking (shared for Ctrl+C and Ctrl+D)
235
246
  const exitKeyPressed = useRef(false);
236
247
  const exitKeyTimeout = useRef(null);
248
+ // Interrupt tracking - prevents duplicate interrupt requests
249
+ const isInterrupting = useRef(false);
250
+ // Debounce Escape after mount so splash-skip doesn't trigger interrupt
251
+ const mountedAt = useRef(Date.now());
237
252
  // Track terminal size for layout calculations
238
253
  // Debounce resize to prevent spam during rapid resize events
239
254
  const [terminalSize, setTerminalSize] = useState({
@@ -264,11 +279,55 @@ function ChatUIWithConnection({ initialPrompt, version: _version, versionId: _ve
264
279
  }, []);
265
280
  // Global keyboard shortcuts
266
281
  useInput((input, key) => {
267
- // Ctrl-C / Ctrl-D: Double-tap to exit
282
+ // Escape: Interrupt agent while processing
283
+ // Ignore Escape within 300ms of mount to prevent splash-skip from triggering interrupt
284
+ if (key.escape &&
285
+ client &&
286
+ session &&
287
+ currentOperation &&
288
+ !isInterrupting.current &&
289
+ Date.now() - mountedAt.current > 300) {
290
+ isInterrupting.current = true;
291
+ client
292
+ .post(`/sessions/${session.id}/interrupt`, {})
293
+ .then(() => {
294
+ debug('Session interrupted');
295
+ })
296
+ .catch((err) => {
297
+ debug('Interrupt failed (session may already be done):', err);
298
+ })
299
+ .finally(() => {
300
+ isInterrupting.current = false;
301
+ });
302
+ return;
303
+ }
304
+ // Ctrl-C: Clear input on first press (if text present), double-tap to exit
268
305
  const isCtrlC = input === '\x03' || (key.ctrl && input === 'c');
306
+ if (isCtrlC) {
307
+ // If there's text in the input, clear it instead of showing exit hint
308
+ if (inputTextRef.current && !exitKeyPressed.current) {
309
+ updateInput('');
310
+ return;
311
+ }
312
+ if (exitKeyPressed.current) {
313
+ if (preConnectedSession?.id && resumeCommand) {
314
+ printResumeHint(preConnectedSession.id, resumeCommand);
315
+ }
316
+ process.exit(0);
317
+ }
318
+ exitKeyPressed.current = true;
319
+ setExitHint('ctrl-c');
320
+ if (exitKeyTimeout.current)
321
+ clearTimeout(exitKeyTimeout.current);
322
+ exitKeyTimeout.current = setTimeout(() => {
323
+ exitKeyPressed.current = false;
324
+ setExitHint(null);
325
+ }, 2000);
326
+ return;
327
+ }
328
+ // Ctrl-D: Double-tap to exit
269
329
  const isCtrlD = input === '\x04' || (key.ctrl && input === 'd');
270
- if (isCtrlC || isCtrlD) {
271
- const keyName = isCtrlC ? 'ctrl-c' : 'ctrl-d';
330
+ if (isCtrlD) {
272
331
  if (exitKeyPressed.current) {
273
332
  if (preConnectedSession?.id && resumeCommand) {
274
333
  printResumeHint(preConnectedSession.id, resumeCommand);
@@ -276,7 +335,7 @@ function ChatUIWithConnection({ initialPrompt, version: _version, versionId: _ve
276
335
  process.exit(0);
277
336
  }
278
337
  exitKeyPressed.current = true;
279
- setExitHint(keyName);
338
+ setExitHint('ctrl-d');
280
339
  if (exitKeyTimeout.current)
281
340
  clearTimeout(exitKeyTimeout.current);
282
341
  exitKeyTimeout.current = setTimeout(() => {
@@ -539,6 +598,19 @@ function ChatUIWithConnection({ initialPrompt, version: _version, versionId: _ve
539
598
  setCurrentOperation('');
540
599
  }
541
600
  }
601
+ else if (event.type === 'interrupted') {
602
+ // Session was interrupted — interrupted sessions are terminal on the backend
603
+ setMessages((prev) => [
604
+ ...prev,
605
+ {
606
+ key: `interrupted-${Date.now()}`,
607
+ content: chalk.dim('⊘ Interrupted'),
608
+ type: 'assistant',
609
+ },
610
+ ]);
611
+ setCurrentOperation('');
612
+ setIsInterrupted(true);
613
+ }
542
614
  else if (isUnfulfilledAgentInstallRequest(event)) {
543
615
  // Check for agent install requests that need user approval
544
616
  if (!promptedEventIds.current.has(event.id) && !pendingInstallRequest) {
@@ -589,7 +661,7 @@ function ChatUIWithConnection({ initialPrompt, version: _version, versionId: _ve
589
661
  },
590
662
  ]);
591
663
  // Clear input right after showing the message
592
- setInput('');
664
+ updateInput('');
593
665
  try {
594
666
  await client.post(`/sessions/${session.id}/events`, {
595
667
  content: value,
@@ -657,18 +729,21 @@ function ChatUIWithConnection({ initialPrompt, version: _version, versionId: _ve
657
729
  const showTasksHintText = '[ctrl-t to show tasks]';
658
730
  const showTasksHint = `[${chalk.bold('ctrl-t')} to show tasks]`;
659
731
  // Build status line with right-aligned hint
660
- // Exit hint takes priority (temporary, 2s), otherwise show ctrl-t task hint
732
+ // Priority: exit hint > esc to interrupt > ctrl-t task hint
661
733
  // Strip ANSI codes to get visible length (statusLine contains spinner with color codes)
662
734
  const stripAnsi = (str) => str.replace(/\x1b\[[0-9;]*m/g, '');
663
735
  const statusWithHint = (() => {
664
- // Pick which hint to show: exit hint takes priority
665
736
  let hintText = null;
666
737
  let hint = null;
667
738
  if (exitHint) {
668
739
  hintText = `[${exitHint} again to exit]`;
669
740
  hint = `[${chalk.bold(exitHint)} again to exit]`;
670
741
  }
671
- else if (activeTaskCount > 0 && currentOperation) {
742
+ else if (currentOperation) {
743
+ hintText = '(esc to interrupt)';
744
+ hint = `(${chalk.bold('esc')} to interrupt)`;
745
+ }
746
+ if (!hintText && activeTaskCount > 0 && currentOperation) {
672
747
  hintText = showTaskPanel ? hideTasksHintText : showTasksHintText;
673
748
  hint = showTaskPanel ? hideTasksHint : showTasksHint;
674
749
  }
@@ -721,7 +796,7 @@ function ChatUIWithConnection({ initialPrompt, version: _version, versionId: _ve
721
796
  React.createElement(Text, null, statusWithHint)),
722
797
  React.createElement(Box, { height: 1 },
723
798
  React.createElement(Text, { color: "gray" }, '─'.repeat(Math.max(1, terminalWidth - 2)))),
724
- React.createElement(InputWrapper, { isReady: isReady, input: input, setInput: setInput, handleSubmit: handleSubmit, trackedTasksSize: tasks.length, setShowTaskPanel: setShowTaskPanel, isActive: isActive })));
799
+ React.createElement(InputWrapper, { isReady: isReady, isInterrupted: isInterrupted, input: input, setInput: updateInput, handleSubmit: handleSubmit, trackedTasksSize: tasks.length, setShowTaskPanel: setShowTaskPanel, isActive: isActive })));
725
800
  }
726
801
  export async function ensureAuthenticated() {
727
802
  const token = await getAuthToken();
@@ -2,6 +2,7 @@
2
2
  import { Command } from 'commander';
3
3
  import { loadConfig, } from '../../lib/guild-config.js';
4
4
  import { getOutputMode } from '../../lib/output-mode.js';
5
+ import { createOutputWriter } from '../../lib/output.js';
5
6
  /**
6
7
  * All valid config keys across global and local configs.
7
8
  * Note: default_workspace_name is internal (auto-resolved from API).
@@ -21,14 +22,11 @@ export function createConfigGetCommand() {
21
22
  .description('Get a configuration value')
22
23
  .argument('<key>', `Config key (${ALL_VALID_KEYS.join(', ')})`)
23
24
  .action(async (key) => {
25
+ const output = createOutputWriter();
24
26
  const config = await loadConfig();
25
27
  const mode = getOutputMode();
26
28
  if (!ALL_VALID_KEYS.includes(key)) {
27
- console.error(`Unknown config key: ${key}`);
28
- console.error('');
29
- console.error('Valid keys:');
30
- console.error(` Global: ${VALID_GLOBAL_KEYS.join(', ')}`);
31
- console.error(` Local: ${VALID_LOCAL_KEYS.join(', ')}`);
29
+ output.error(`Unknown config key: ${key}\n\nValid keys:\n Global: ${VALID_GLOBAL_KEYS.join(', ')}\n Local: ${VALID_LOCAL_KEYS.join(', ')}`);
32
30
  process.exit(1);
33
31
  }
34
32
  // Look up in global first, then local
@@ -46,18 +44,18 @@ export function createConfigGetCommand() {
46
44
  }
47
45
  if (value === undefined) {
48
46
  if (mode === 'json') {
49
- console.log(JSON.stringify({ key, value: null, source: null }));
47
+ output.data({ key, value: null, source: null });
50
48
  }
51
49
  else {
52
- console.error(`Key not found: ${key}`);
50
+ output.error(`Key not found: ${key}`);
53
51
  }
54
52
  process.exit(1);
55
53
  }
56
54
  if (mode === 'json') {
57
- console.log(JSON.stringify({ key, value, source }, null, 2));
55
+ output.data({ key, value, source });
58
56
  }
59
57
  else {
60
- console.log(value);
58
+ output.data(value);
61
59
  }
62
60
  });
63
61
  return cmd;
@@ -3,39 +3,41 @@ import { Command } from 'commander';
3
3
  import chalk from 'chalk';
4
4
  import { loadConfig } from '../../lib/guild-config.js';
5
5
  import { getOutputMode } from '../../lib/output-mode.js';
6
+ import { createOutputWriter } from '../../lib/output.js';
6
7
  export function createConfigListCommand() {
7
8
  const cmd = new Command('list');
8
9
  cmd.description('Show all configuration values').action(async () => {
10
+ const output = createOutputWriter();
9
11
  const config = await loadConfig();
10
12
  const mode = getOutputMode();
11
13
  if (mode === 'json') {
12
- console.log(JSON.stringify({
14
+ output.data({
13
15
  global: config.global || null,
14
16
  local: config.local || null,
15
- }, null, 2));
17
+ });
16
18
  return;
17
19
  }
18
20
  const hasGlobal = config.global && Object.keys(config.global).length > 0;
19
21
  const hasLocal = config.local && Object.keys(config.local).length > 0;
20
22
  if (!hasGlobal && !hasLocal) {
21
- console.log('No configuration found.');
22
- console.log('');
23
- console.log('Set a default workspace:');
24
- console.log(chalk.dim(' guild workspace select'));
23
+ output.progress('No configuration found.');
24
+ output.progress('');
25
+ output.progress('Set a default workspace:');
26
+ output.progress(chalk.dim(' guild workspace select'));
25
27
  return;
26
28
  }
27
29
  if (hasGlobal) {
28
- console.log(chalk.bold('Global config') + chalk.dim(' (~/.guild/config.json):'));
30
+ output.progress(chalk.bold('Global config') + chalk.dim(' (~/.guild/config.json):'));
29
31
  for (const [key, value] of Object.entries(config.global)) {
30
- console.log(` ${key}: ${chalk.cyan(String(value))}`);
32
+ output.progress(` ${key}: ${chalk.cyan(String(value))}`);
31
33
  }
32
34
  }
33
35
  if (hasLocal) {
34
36
  if (hasGlobal)
35
- console.log('');
36
- console.log(chalk.bold('Local config') + chalk.dim(' (guild.json):'));
37
+ output.progress('');
38
+ output.progress(chalk.bold('Local config') + chalk.dim(' (guild.json):'));
37
39
  for (const [key, value] of Object.entries(config.local)) {
38
- console.log(` ${key}: ${chalk.cyan(String(value))}`);
40
+ output.progress(` ${key}: ${chalk.cyan(String(value))}`);
39
41
  }
40
42
  }
41
43
  });
@@ -4,6 +4,7 @@ import chalk from 'chalk';
4
4
  import * as fs from 'fs/promises';
5
5
  import { getGlobalConfigPath, getLocalConfigPath } from '../../lib/guild-config.js';
6
6
  import { getOutputMode } from '../../lib/output-mode.js';
7
+ import { createOutputWriter } from '../../lib/output.js';
7
8
  async function fileExists(filePath) {
8
9
  return fs
9
10
  .access(filePath)
@@ -13,22 +14,23 @@ async function fileExists(filePath) {
13
14
  export function createConfigPathCommand() {
14
15
  const cmd = new Command('path');
15
16
  cmd.description('Show configuration file paths').action(async () => {
17
+ const output = createOutputWriter();
16
18
  const globalPath = getGlobalConfigPath();
17
19
  const localPath = getLocalConfigPath();
18
20
  const mode = getOutputMode();
19
21
  const globalExists = await fileExists(globalPath);
20
22
  const localExists = await fileExists(localPath);
21
23
  if (mode === 'json') {
22
- console.log(JSON.stringify({
24
+ output.data({
23
25
  global: { path: globalPath, exists: globalExists },
24
26
  local: { path: localPath, exists: localExists },
25
- }, null, 2));
27
+ });
26
28
  return;
27
29
  }
28
30
  const globalStatus = globalExists ? chalk.green('exists') : chalk.dim('not found');
29
31
  const localStatus = localExists ? chalk.green('exists') : chalk.dim('not found');
30
- console.log(`Global: ${globalPath} (${globalStatus})`);
31
- console.log(`Local: ${localPath} (${localStatus})`);
32
+ output.progress(`Global: ${globalPath} (${globalStatus})`);
33
+ output.progress(`Local: ${localPath} (${localStatus})`);
32
34
  });
33
35
  return cmd;
34
36
  }
@@ -3,6 +3,7 @@ import { Command } from 'commander';
3
3
  import chalk from 'chalk';
4
4
  import { saveGlobalConfig } from '../../lib/guild-config.js';
5
5
  import { getOutputMode } from '../../lib/output-mode.js';
6
+ import { createOutputWriter } from '../../lib/output.js';
6
7
  import { GuildAPIClient } from '../../lib/api-client.js';
7
8
  import { debug } from '../../lib/errors.js';
8
9
  /**
@@ -19,15 +20,13 @@ const VALID_GLOBAL_KEYS = [
19
20
  const BOOLEAN_KEYS = new Set(['debug', 'json', 'quiet']);
20
21
  const TRUTHY = new Set(['true', 'yes', 'on', '1']);
21
22
  const FALSY = new Set(['false', 'no', 'off', '0']);
22
- function parseBoolean(value, key) {
23
+ function parseBoolean(value, key, output) {
23
24
  const lower = value.toLowerCase();
24
25
  if (TRUTHY.has(lower))
25
26
  return true;
26
27
  if (FALSY.has(lower))
27
28
  return false;
28
- console.error(`Invalid boolean value for "${key}": "${value}"`);
29
- console.error('');
30
- console.error('Accepted values: true/false, yes/no, on/off, 1/0');
29
+ output.error(`Invalid boolean value for "${key}": "${value}"\n\nAccepted values: true/false, yes/no, on/off, 1/0`);
31
30
  process.exit(1);
32
31
  }
33
32
  /**
@@ -66,12 +65,12 @@ async function resolveOwnerName(ownerId) {
66
65
  return undefined;
67
66
  }
68
67
  }
69
- function printResult(key, value, mode) {
68
+ function printResult(key, value, mode, output) {
70
69
  if (mode === 'json') {
71
- console.log(JSON.stringify({ key, value }, null, 2));
70
+ output.data({ key, value });
72
71
  }
73
72
  else {
74
- console.log(`Set ${chalk.bold(key)} = ${chalk.cyan(String(value))}`);
73
+ output.success(`Set ${chalk.bold(key)} = ${chalk.cyan(String(value))}`);
75
74
  }
76
75
  }
77
76
  export function createConfigSetCommand() {
@@ -81,21 +80,17 @@ export function createConfigSetCommand() {
81
80
  .argument('<key>', `Config key (${VALID_GLOBAL_KEYS.join(', ')})`)
82
81
  .argument('<value>', 'Value to set')
83
82
  .action(async (key, value) => {
83
+ const output = createOutputWriter();
84
84
  const mode = getOutputMode();
85
85
  if (!VALID_GLOBAL_KEYS.includes(key)) {
86
- console.error(`Unknown config key: ${key}`);
87
- console.error('');
88
- console.error('Valid keys:');
89
- for (const k of VALID_GLOBAL_KEYS) {
90
- console.error(` ${k}`);
91
- }
86
+ output.error(`Unknown config key: ${key}\n\nValid keys:\n${VALID_GLOBAL_KEYS.map((k) => ` ${k}`).join('\n')}`);
92
87
  process.exit(1);
93
88
  }
94
89
  const typedKey = key;
95
90
  if (BOOLEAN_KEYS.has(typedKey)) {
96
- const boolValue = parseBoolean(value, key);
91
+ const boolValue = parseBoolean(value, key, output);
97
92
  await saveGlobalConfig({ [typedKey]: boolValue });
98
- printResult(key, boolValue, mode);
93
+ printResult(key, boolValue, mode, output);
99
94
  return;
100
95
  }
101
96
  if (typedKey === 'default_workspace') {
@@ -104,15 +99,15 @@ export function createConfigSetCommand() {
104
99
  default_workspace: value,
105
100
  default_workspace_name: name,
106
101
  });
107
- printResult(key, value, mode);
102
+ printResult(key, value, mode, output);
108
103
  if (name) {
109
104
  if (mode !== 'json') {
110
- console.log(chalk.dim(` Workspace name: ${name}`));
105
+ output.progress(` Workspace name: ${name}`);
111
106
  }
112
107
  }
113
108
  else {
114
109
  if (mode !== 'json') {
115
- console.error(chalk.dim(' Could not resolve workspace name (not authenticated?)'));
110
+ output.error('Could not resolve workspace name (not authenticated?)');
116
111
  }
117
112
  }
118
113
  return;
@@ -123,21 +118,21 @@ export function createConfigSetCommand() {
123
118
  default_owner: value,
124
119
  default_owner_name: name,
125
120
  });
126
- printResult(key, value, mode);
121
+ printResult(key, value, mode, output);
127
122
  if (name) {
128
123
  if (mode !== 'json') {
129
- console.log(chalk.dim(` Owner name: ${name}`));
124
+ output.progress(` Owner name: ${name}`);
130
125
  }
131
126
  }
132
127
  else {
133
128
  if (mode !== 'json') {
134
- console.error(chalk.dim(' Could not resolve owner name (not authenticated?)'));
129
+ output.error('Could not resolve owner name (not authenticated?)');
135
130
  }
136
131
  }
137
132
  return;
138
133
  }
139
134
  await saveGlobalConfig({ [typedKey]: value });
140
- printResult(key, value, mode);
135
+ printResult(key, value, mode, output);
141
136
  });
142
137
  return cmd;
143
138
  }
@@ -6,6 +6,7 @@ import { getAuthToken } from '../lib/auth.js';
6
6
  import { getGuildcoreUrl } from '../lib/config.js';
7
7
  import { loadGlobalConfig, isAgentDirectory, getGlobalConfigPath, loadLocalConfig, } from '../lib/guild-config.js';
8
8
  import { getOutputMode } from '../lib/output-mode.js';
9
+ import { createOutputWriter } from '../lib/output.js';
9
10
  async function checkAuth() {
10
11
  const token = await getAuthToken();
11
12
  if (token) {
@@ -175,10 +176,11 @@ async function checkGit() {
175
176
  export function createDoctorCommand() {
176
177
  const cmd = new Command('doctor');
177
178
  cmd.description('Check CLI setup and diagnose issues').action(async () => {
179
+ const output = createOutputWriter();
178
180
  const mode = getOutputMode();
179
181
  const checks = [];
180
182
  if (mode !== 'json') {
181
- console.log('\nChecking Guild CLI setup...\n');
183
+ output.progress('\nChecking Guild CLI setup...\n');
182
184
  }
183
185
  const runners = [
184
186
  checkAuth,
@@ -199,10 +201,10 @@ export function createDoctorCommand() {
199
201
  ? chalk.red('✗')
200
202
  : chalk.dim('-');
201
203
  const nameCol = result.name.padEnd(20);
202
- console.log(` ${icon} ${chalk.bold(nameCol)} ${result.message}`);
204
+ output.progress(` ${icon} ${chalk.bold(nameCol)} ${result.message}`);
203
205
  if (result.suggestion) {
204
- console.log(` ${chalk.dim(result.suggestion)}`);
205
- console.log('');
206
+ output.progress(` ${chalk.dim(result.suggestion)}`);
207
+ output.progress('');
206
208
  }
207
209
  }
208
210
  }
@@ -210,10 +212,10 @@ export function createDoctorCommand() {
210
212
  const failed = checks.filter((c) => c.status === 'fail').length;
211
213
  const skipped = checks.filter((c) => c.status === 'skip').length;
212
214
  if (mode === 'json') {
213
- console.log(JSON.stringify({ checks, passed, failed, skipped }, null, 2));
215
+ output.data({ checks, passed, failed, skipped });
214
216
  }
215
217
  else {
216
- console.log('');
218
+ output.progress('');
217
219
  const parts = [];
218
220
  if (passed > 0)
219
221
  parts.push(chalk.green(`${passed} passed`));
@@ -221,7 +223,7 @@ export function createDoctorCommand() {
221
223
  parts.push(chalk.red(`${failed} failed`));
222
224
  if (skipped > 0)
223
225
  parts.push(chalk.dim(`${skipped} skipped`));
224
- console.log(parts.join(', '));
226
+ output.progress(parts.join(', '));
225
227
  }
226
228
  process.exit(failed > 0 ? 1 : 0);
227
229
  });
@@ -4,6 +4,7 @@ import { GuildAPIClient } from '../../lib/api-client.js';
4
4
  import { getAuthToken } from '../../lib/auth.js';
5
5
  import { getWorkspaceId } from '../../lib/guild-config.js';
6
6
  import { handleAxiosError } from '../../lib/errors.js';
7
+ import { createOutputWriter } from '../../lib/output.js';
7
8
  export function createSessionCreateCommand() {
8
9
  const cmd = new Command('create');
9
10
  cmd
@@ -13,21 +14,22 @@ export function createSessionCreateCommand() {
13
14
  .option('--prompt <text>', 'Initial prompt (required for chat sessions)')
14
15
  .option('--agent <identifier>', 'Agent identifier, e.g., owner/agent-name')
15
16
  .action(async (options) => {
17
+ const output = createOutputWriter();
16
18
  try {
17
19
  const token = await getAuthToken();
18
20
  if (!token) {
19
- console.error('Not authenticated. Run: guild auth login');
21
+ output.error('Not authenticated. Run: guild auth login');
20
22
  process.exit(1);
21
23
  }
22
24
  if (options.type === 'chat' && !options.prompt) {
23
- console.error('Error: --prompt is required for chat sessions');
25
+ output.error('--prompt is required for chat sessions');
24
26
  process.exit(1);
25
27
  }
26
28
  let workspaceId = options.workspace;
27
29
  if (!workspaceId) {
28
30
  const resolved = await getWorkspaceId();
29
31
  if (!resolved) {
30
- console.error('No workspace specified. Run: guild workspace select');
32
+ output.error('No workspace specified. Run: guild workspace select');
31
33
  process.exit(1);
32
34
  }
33
35
  workspaceId = resolved.workspaceId;
@@ -43,11 +45,11 @@ export function createSessionCreateCommand() {
43
45
  body.agent_id = options.agent;
44
46
  }
45
47
  const response = await client.post(`/workspaces/${workspaceId}/sessions`, body);
46
- console.log(JSON.stringify(response, null, 2));
48
+ output.data(response);
47
49
  }
48
50
  catch (error) {
49
51
  const formattedError = handleAxiosError(error);
50
- console.error(`Failed to create session: ${formattedError.details}`);
52
+ output.error(`Failed to create session: ${formattedError.details}`);
51
53
  process.exit(1);
52
54
  }
53
55
  });
@@ -3,6 +3,7 @@ import { Command } from 'commander';
3
3
  import { GuildAPIClient } from '../../lib/api-client.js';
4
4
  import { getAuthToken } from '../../lib/auth.js';
5
5
  import { handleAxiosError } from '../../lib/errors.js';
6
+ import { createOutputWriter } from '../../lib/output.js';
6
7
  export function createSessionEventsCommand() {
7
8
  const cmd = new Command('events');
8
9
  cmd
@@ -12,10 +13,11 @@ export function createSessionEventsCommand() {
12
13
  .option('--limit <number>', 'Number of results to return', '100')
13
14
  .option('--offset <number>', 'Offset for pagination', '0')
14
15
  .action(async (sessionId, options) => {
16
+ const output = createOutputWriter();
15
17
  try {
16
18
  const token = await getAuthToken();
17
19
  if (!token) {
18
- console.error('Not authenticated. Run: guild auth login');
20
+ output.error('Not authenticated. Run: guild auth login');
19
21
  process.exit(1);
20
22
  }
21
23
  const client = new GuildAPIClient();
@@ -26,11 +28,11 @@ export function createSessionEventsCommand() {
26
28
  params.append('types', options.types);
27
29
  }
28
30
  const response = await client.get(`/sessions/${sessionId}/events?${params.toString()}`);
29
- console.log(JSON.stringify(response, null, 2));
31
+ output.data(response);
30
32
  }
31
33
  catch (error) {
32
34
  const formattedError = handleAxiosError(error);
33
- console.error(`Failed to get session events: ${formattedError.details}`);
35
+ output.error(`Failed to get session events: ${formattedError.details}`);
34
36
  process.exit(1);
35
37
  }
36
38
  });
@@ -3,25 +3,27 @@ import { Command } from 'commander';
3
3
  import { GuildAPIClient } from '../../lib/api-client.js';
4
4
  import { getAuthToken } from '../../lib/auth.js';
5
5
  import { handleAxiosError } from '../../lib/errors.js';
6
+ import { createOutputWriter } from '../../lib/output.js';
6
7
  export function createSessionGetCommand() {
7
8
  const cmd = new Command('get');
8
9
  cmd
9
10
  .description('Get session details')
10
11
  .argument('<session-id>', 'Session ID')
11
12
  .action(async (sessionId) => {
13
+ const output = createOutputWriter();
12
14
  try {
13
15
  const token = await getAuthToken();
14
16
  if (!token) {
15
- console.error('Not authenticated. Run: guild auth login');
17
+ output.error('Not authenticated. Run: guild auth login');
16
18
  process.exit(1);
17
19
  }
18
20
  const client = new GuildAPIClient();
19
21
  const response = await client.get(`/sessions/${sessionId}`);
20
- console.log(JSON.stringify(response, null, 2));
22
+ output.data(response);
21
23
  }
22
24
  catch (error) {
23
25
  const formattedError = handleAxiosError(error);
24
- console.error(`Failed to get session: ${formattedError.details}`);
26
+ output.error(`Failed to get session: ${formattedError.details}`);
25
27
  process.exit(1);
26
28
  }
27
29
  });
@@ -5,7 +5,7 @@ import { getAuthToken } from '../../lib/auth.js';
5
5
  import { getWorkspaceId } from '../../lib/guild-config.js';
6
6
  import { handleAxiosError } from '../../lib/errors.js';
7
7
  import { getOutputMode } from '../../lib/output-mode.js';
8
- import { formatSessionTable } from '../../lib/output.js';
8
+ import { createOutputWriter, formatSessionTable } from '../../lib/output.js';
9
9
  export function createSessionListCommand() {
10
10
  const cmd = new Command('list');
11
11
  cmd
@@ -15,17 +15,18 @@ export function createSessionListCommand() {
15
15
  .option('--limit <number>', 'Number of results to return', '20')
16
16
  .option('--offset <number>', 'Offset for pagination', '0')
17
17
  .action(async (options) => {
18
+ const output = createOutputWriter();
18
19
  try {
19
20
  const token = await getAuthToken();
20
21
  if (!token) {
21
- console.error('Not authenticated. Run: guild auth login');
22
+ output.error('Not authenticated. Run: guild auth login');
22
23
  process.exit(1);
23
24
  }
24
25
  let workspaceId = options.workspace;
25
26
  if (!workspaceId) {
26
27
  const resolved = await getWorkspaceId();
27
28
  if (!resolved) {
28
- console.error('No workspace specified. Run: guild workspace select');
29
+ output.error('No workspace specified. Run: guild workspace select');
29
30
  process.exit(1);
30
31
  }
31
32
  workspaceId = resolved.workspaceId;
@@ -47,7 +48,7 @@ export function createSessionListCommand() {
47
48
  }
48
49
  catch (error) {
49
50
  const formattedError = handleAxiosError(error);
50
- console.error(`Failed to list sessions: ${formattedError.details}`);
51
+ output.error(`Failed to list sessions: ${formattedError.details}`);
51
52
  process.exit(1);
52
53
  }
53
54
  });