@guildai/cli 0.9.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/commands/agent/chat.js +10 -7
  2. package/dist/commands/agent/clone.js +2 -0
  3. package/dist/commands/agent/fork.js +3 -0
  4. package/dist/commands/agent/init.js +58 -44
  5. package/dist/commands/agent/list.js +5 -4
  6. package/dist/commands/agent/logs.d.ts +3 -0
  7. package/dist/commands/agent/logs.js +62 -0
  8. package/dist/commands/agent/owners.js +3 -3
  9. package/dist/commands/agent/pull.js +8 -12
  10. package/dist/commands/agent/save.js +5 -6
  11. package/dist/commands/agent/search.js +5 -4
  12. package/dist/commands/agent/test.js +9 -6
  13. package/dist/commands/agent/update.js +9 -1
  14. package/dist/commands/agent/versions.js +5 -4
  15. package/dist/commands/agent/workspaces.js +5 -4
  16. package/dist/commands/auth/login.js +1 -3
  17. package/dist/commands/chat.d.ts +9 -0
  18. package/dist/commands/chat.js +136 -32
  19. package/dist/commands/config/get.js +4 -4
  20. package/dist/commands/config/list.js +2 -3
  21. package/dist/commands/config/path.js +2 -3
  22. package/dist/commands/config/set.js +12 -12
  23. package/dist/commands/credentials/endpoint-list.js +5 -4
  24. package/dist/commands/credentials/list.js +5 -4
  25. package/dist/commands/credentials/policy-list.js +5 -4
  26. package/dist/commands/doctor.js +5 -5
  27. package/dist/commands/integration/connect.js +2 -2
  28. package/dist/commands/integration/create.js +2 -2
  29. package/dist/commands/integration/get.js +2 -2
  30. package/dist/commands/integration/list.js +5 -4
  31. package/dist/commands/integration/operation/create.js +4 -4
  32. package/dist/commands/integration/operation/list.js +5 -4
  33. package/dist/commands/integration/update.js +2 -2
  34. package/dist/commands/integration/version/build.js +2 -2
  35. package/dist/commands/integration/version/create.js +2 -2
  36. package/dist/commands/integration/version/get.js +2 -2
  37. package/dist/commands/integration/version/list.js +5 -4
  38. package/dist/commands/integration/version/publish.js +2 -2
  39. package/dist/commands/integration/version/test.js +2 -2
  40. package/dist/commands/job/get.js +2 -2
  41. package/dist/commands/session/create.js +1 -1
  42. package/dist/commands/session/events.js +3 -2
  43. package/dist/commands/session/list.js +5 -4
  44. package/dist/commands/session/tasks.js +5 -4
  45. package/dist/commands/setup.d.ts +16 -0
  46. package/dist/commands/setup.js +76 -46
  47. package/dist/commands/trigger/list.js +5 -4
  48. package/dist/commands/trigger/sessions.js +3 -2
  49. package/dist/commands/workspace/agent/list.js +5 -4
  50. package/dist/commands/workspace/context/list.js +5 -4
  51. package/dist/commands/workspace/list.js +5 -4
  52. package/dist/index.js +15 -4
  53. package/dist/lib/api-types.d.ts +4 -0
  54. package/dist/lib/api-types.js +4 -0
  55. package/dist/lib/auth.d.ts +1 -1
  56. package/dist/lib/auth.js +2 -2
  57. package/dist/lib/output-mode.d.ts +9 -2
  58. package/dist/lib/output-mode.js +23 -2
  59. package/dist/lib/output.d.ts +7 -1
  60. package/dist/lib/output.js +36 -5
  61. package/dist/lib/owner-helpers.d.ts +3 -0
  62. package/dist/lib/owner-helpers.js +17 -10
  63. package/dist/lib/session-events.d.ts +13 -2
  64. package/dist/lib/session-events.js +15 -1
  65. package/dist/lib/session-polling.js +9 -3
  66. package/dist/lib/session-resume.d.ts +15 -1
  67. package/dist/lib/session-resume.js +149 -16
  68. package/dist/lib/splash.js +3 -2
  69. package/dist/lib/stdin.d.ts +5 -1
  70. package/dist/lib/stdin.js +8 -1
  71. package/dist/lib/version-helpers.js +24 -8
  72. package/package.json +1 -1
@@ -6,14 +6,15 @@ import { getGuildcoreUrl } from '../../lib/config.js';
6
6
  import { handleAxiosError, ErrorCodes } from '../../lib/errors.js';
7
7
  import { getAgentId, resolveAgentRef } from '../../lib/agent-helpers.js';
8
8
  import { createOutputWriter, formatWorkspaceTable } from '../../lib/output.js';
9
- import { getOutputMode } from '../../lib/output-mode.js';
9
+ import { isMachineReadable } from '../../lib/output-mode.js';
10
+ import { DEFAULT_PAGE_LIMIT } from '../../lib/api-types.js';
10
11
  export function createAgentWorkspacesCommand() {
11
12
  const cmd = new Command('workspaces');
12
13
  cmd
13
14
  .description('List workspaces that use an agent')
14
15
  .argument('[identifier]', 'Agent ID or full name (e.g., owner~agent-name)')
15
- .option('--limit <number>', 'Maximum number of workspaces to return', '20')
16
- .option('--offset <number>', 'Number of workspaces to skip', '0')
16
+ .option('--limit <number>', `Maximum number of workspaces to return (default: ${DEFAULT_PAGE_LIMIT})`, String(DEFAULT_PAGE_LIMIT))
17
+ .option('--offset <number>', 'Number of workspaces to skip (default: 0)', '0')
17
18
  .action(async (agentIdArg, options) => {
18
19
  const output = createOutputWriter();
19
20
  const { agentId } = await getAgentId(agentIdArg);
@@ -24,7 +25,7 @@ export function createAgentWorkspacesCommand() {
24
25
  try {
25
26
  const resolvedId = await resolveAgentRef(client, agentId);
26
27
  const result = await client.get(`/agents/${resolvedId}/workspaces?limit=${limit}&offset=${offset}`);
27
- if (getOutputMode() === 'json') {
28
+ if (isMachineReadable()) {
28
29
  output.data(result);
29
30
  }
30
31
  else {
@@ -11,11 +11,9 @@ export function createAuthLoginCommand() {
11
11
  .description('Login to Guild.ai')
12
12
  .option('--return-url <url>', 'Custom URL to redirect to after authentication')
13
13
  .option('--return-label <text>', 'Friendly label for return button (e.g., "VSCode")')
14
- .option('--non-interactive', 'Skip interactive prompts (for use with coding agents)')
15
14
  .action(async (options) => {
16
15
  const output = createOutputWriter();
17
- const nonInteractive = options.nonInteractive || false;
18
- const success = await login(options.returnUrl, options.returnLabel, nonInteractive);
16
+ const success = await login(options.returnUrl, options.returnLabel);
19
17
  if (success) {
20
18
  try {
21
19
  await configureNpmrc();
@@ -2,6 +2,15 @@ import React from 'react';
2
2
  import { Command } from 'commander';
3
3
  import { GuildAPIClient } from '../lib/api-client.js';
4
4
  import { SessionEvent, Session } from '../lib/session-events.js';
5
+ /** Thrown when no workspace is configured (no --workspace flag, no config). */
6
+ export declare class WorkspaceNotConfiguredError extends Error {
7
+ constructor();
8
+ }
9
+ /** Thrown when the specified workspace ID is not found in the backend. */
10
+ export declare class WorkspaceNotFoundError extends Error {
11
+ readonly workspaceId: string;
12
+ constructor(workspaceId: string);
13
+ }
5
14
  /**
6
15
  * ChatApp wrapper component that shows splash animation during connection
7
16
  * and then transitions to ChatUIWithConnection once connected.
@@ -2,7 +2,7 @@
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
  import React, { useState, useEffect, useRef } from 'react';
4
4
  import { Box, Text, Static, render, useInput, useApp } from 'ink';
5
- import { Command } from 'commander';
5
+ import { Command, Option } from 'commander';
6
6
  import { getAuthToken } from '../lib/auth.js';
7
7
  import { GuildAPIClient } from '../lib/api-client.js';
8
8
  import { handleAxiosError, ErrorCodes, debug, isDebugMode, retry, } from '../lib/errors.js';
@@ -13,8 +13,8 @@ import chalk from 'chalk';
13
13
  import { readFileSync } from 'fs';
14
14
  import path from 'path';
15
15
  import { fileURLToPath } from 'url';
16
- import { isUnfulfilledAgentInstallRequest, isFilteredTaskName, getTaskDisplayName, getAgentName, getAgentNotificationText, isDoneResponseStreamEvent, isResponseStreamEvent, isRootTaskEvent, } from '../lib/session-events.js';
17
- import { printResumeHint, fetchSession, fetchSessionEvents, eventsToDisplayMessages, } from '../lib/session-resume.js';
16
+ import { isUnfulfilledAgentInstallRequest, isFilteredTaskName, getTaskDisplayName, getAgentName, getAgentNotificationText, applyResponseStreamText, isDoneResponseStreamEvent, isResponseStreamEvent, isRootTaskEvent, } from '../lib/session-events.js';
17
+ import { printResumeHint, fetchSession, fetchSessionEvents, prepareSessionResumeDisplay, } from '../lib/session-resume.js';
18
18
  import { DEFAULT_EVENT_TYPES, parseEventFilter, shouldShowEvent, } from '../lib/event-filter.js';
19
19
  import { fetchEvents, fetchTasks } from '../lib/session-events-fetch.js';
20
20
  import { AgentInstallPrompt } from '../components/AgentInstallPrompt.js';
@@ -31,6 +31,28 @@ import { getOutputMode, isQuietMode } from '../lib/output-mode.js';
31
31
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
32
32
  // Read version from package.json
33
33
  const packageJson = JSON.parse(readFileSync(path.join(__dirname, '../../package.json'), 'utf-8'));
34
+ // ---------------------------------------------------------------------------
35
+ // Workspace error types
36
+ // ---------------------------------------------------------------------------
37
+ /** Thrown when no workspace is configured (no --workspace flag, no config). */
38
+ export class WorkspaceNotConfiguredError extends Error {
39
+ constructor() {
40
+ super('No workspace configured.');
41
+ this.name = 'WorkspaceNotConfiguredError';
42
+ }
43
+ }
44
+ /** Thrown when the specified workspace ID is not found in the backend. */
45
+ export class WorkspaceNotFoundError extends Error {
46
+ workspaceId;
47
+ constructor(workspaceId) {
48
+ super(`Workspace ${workspaceId} not found.`);
49
+ this.name = 'WorkspaceNotFoundError';
50
+ this.workspaceId = workspaceId;
51
+ }
52
+ }
53
+ /** User-facing error messages for workspace resolution failures. */
54
+ const WORKSPACE_NOT_CONFIGURED_MSG = 'No workspace configured. Pass a --workspace <id_or_name> argument or run guild workspace select';
55
+ const WORKSPACE_NOT_FOUND_MSG = "The workspace doesn't exist.";
34
56
  // Configure marked for terminal
35
57
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
58
  marked.use(markedTerminal({}, { theme: {} }));
@@ -90,6 +112,11 @@ function renderAssistantMessage(text, taskName) {
90
112
  const rendered = fixListItemMarkdown(marked.parse(text));
91
113
  return `${chalk.green('●')} ${chalk.bold(taskName)}\n${rendered.trim()}`;
92
114
  }
115
+ function applyResponseStreamContentsInSequence(contents) {
116
+ return [...contents]
117
+ .sort((left, right) => left.sequence - right.sequence)
118
+ .reduce((currentText, content) => applyResponseStreamText(currentText, content), '');
119
+ }
93
120
  /**
94
121
  * Output the result of a --once mode session.
95
122
  * Handles both JSON and human-readable output formats.
@@ -102,9 +129,18 @@ async function outputOnceResult(sessionId, events, mode) {
102
129
  const finalAgentMessages = events.filter((e) => e.type === 'agent_notification_message' &&
103
130
  isRootTaskEvent(e) &&
104
131
  !isResponseStreamEvent(e));
105
- const streamFallbacks = events.filter((e) => e.type === 'agent_notification_message' &&
106
- isRootTaskEvent(e) &&
107
- isDoneResponseStreamEvent(e));
132
+ const responseStreamContents = new Map();
133
+ const doneStreamIds = [];
134
+ for (const event of events) {
135
+ if (!isResponseStreamEvent(event) || !isRootTaskEvent(event))
136
+ continue;
137
+ const streamContents = responseStreamContents.get(event.content.stream_id) ?? [];
138
+ streamContents.push(event.content);
139
+ responseStreamContents.set(event.content.stream_id, streamContents);
140
+ if (isDoneResponseStreamEvent(event)) {
141
+ doneStreamIds.push(event.content.stream_id);
142
+ }
143
+ }
108
144
  if (finalAgentMessages.length > 0) {
109
145
  const messageContent = extractMessageText(getAgentNotificationText(finalAgentMessages[finalAgentMessages.length - 1]));
110
146
  const rendered = fixListItemMarkdown(await marked(messageContent));
@@ -120,9 +156,12 @@ async function outputOnceResult(sessionId, events, mode) {
120
156
  return;
121
157
  }
122
158
  }
123
- if (streamFallbacks.length > 0) {
124
- const messageContent = extractMessageText(getAgentNotificationText(streamFallbacks[streamFallbacks.length - 1]));
125
- const rendered = fixListItemMarkdown(await marked(messageContent));
159
+ const lastDoneStreamId = doneStreamIds[doneStreamIds.length - 1];
160
+ const streamFallbackText = lastDoneStreamId !== undefined
161
+ ? applyResponseStreamContentsInSequence(responseStreamContents.get(lastDoneStreamId) ?? [])
162
+ : null;
163
+ if (streamFallbackText !== null) {
164
+ const rendered = fixListItemMarkdown(await marked(streamFallbackText));
126
165
  console.log(rendered.trim());
127
166
  }
128
167
  }
@@ -264,8 +303,11 @@ export function ChatApp({ initialPrompt, version, workspaceId, versionId, agentN
264
303
  formattedError.code === ErrorCodes.AUTH_TOKEN_INVALID) {
265
304
  format.error('Not authenticated. Run: guild auth login');
266
305
  }
267
- else if (details.includes('workspace')) {
268
- format.error('Workspace not found. Run: guild workspace select');
306
+ else if (error instanceof WorkspaceNotConfiguredError) {
307
+ format.error(WORKSPACE_NOT_CONFIGURED_MSG);
308
+ }
309
+ else if (error instanceof WorkspaceNotFoundError) {
310
+ format.error(WORKSPACE_NOT_FOUND_MSG);
269
311
  }
270
312
  else if (details.includes('agent')) {
271
313
  format.error('Agent not found in workspace.');
@@ -278,6 +320,7 @@ export function ChatApp({ initialPrompt, version, workspaceId, versionId, agentN
278
320
  };
279
321
  connect();
280
322
  }, [workspaceId, initialPrompt, versionId]);
323
+ const chatInstanceKey = `${connectedSession?.id ?? 'pending'}:${resumeEvents?.[resumeEvents.length - 1]?.id ?? 'live'}`;
281
324
  // Render both splash and chat, but only show one at a time
282
325
  // ChatUIWithConnection is always mounted (when connected) so it can stream events in background
283
326
  return (React.createElement(React.Fragment, null,
@@ -291,7 +334,7 @@ export function ChatApp({ initialPrompt, version, workspaceId, versionId, agentN
291
334
  }
292
335
  // If not connected yet, ignore escape (let connection complete)
293
336
  } })),
294
- phase === 'chat' && (React.createElement(ChatUIWithConnection, { initialPrompt: initialPrompt, version: version, versionId: versionId, agentName: agentName, client: connectedClient, session: connectedSession, onFirstMessage: () => setFirstMessageReceived(true), resumeEvents: resumeEvents, resumeCommand: resumeCommand, eventFilter: eventFilter })),
337
+ phase === 'chat' && (React.createElement(ChatUIWithConnection, { key: chatInstanceKey, initialPrompt: initialPrompt, version: version, versionId: versionId, agentName: agentName, client: connectedClient, session: connectedSession, onFirstMessage: () => setFirstMessageReceived(true), resumeEvents: resumeEvents, resumeCommand: resumeCommand, eventFilter: eventFilter })),
295
338
  (phase === 'splash' || phase === 'finalizing') &&
296
339
  connectedSession &&
297
340
  connectedClient && (React.createElement(Box, { display: "none" },
@@ -311,9 +354,9 @@ function ChatUIWithConnection({ initialPrompt, version: _version, versionId: _ve
311
354
  // Only include initial prompt when active (not during splash)
312
355
  // Static component writes to stdout even with display="none"
313
356
  // When resuming, show past events instead of initial prompt
314
- const resumeDisplayMessages = resumeEvents
315
- ? eventsToDisplayMessages(resumeEvents)
316
- : null;
357
+ const [resumeDisplay] = useState(() => resumeEvents ? prepareSessionResumeDisplay(resumeEvents) : null);
358
+ const resumeDisplayMessages = resumeDisplay?.displayMessages ?? null;
359
+ const responseStreamResumeState = resumeDisplay?.responseStreamState ?? null;
317
360
  const sessionLinkMessage = isActive && preConnectedSession?.session_url
318
361
  ? [
319
362
  {
@@ -477,23 +520,53 @@ function ChatUIWithConnection({ initialPrompt, version: _version, versionId: _ve
477
520
  const isPolling = useRef(false);
478
521
  const receivedResponseSinceLastInput = useRef(false);
479
522
  const firstMessageNotified = useRef(!!resumeEvents);
480
- const responseStreamKeys = useRef(new Map());
481
- const responseStreamTimestamps = useRef(new Map());
482
- const responseStreamKeysByTask = useRef(new Map());
483
- const clearResponseStreamsForTask = (taskId) => {
523
+ const responseStreamKeys = useRef(responseStreamResumeState?.keys ?? new Map());
524
+ const responseStreamContents = useRef(responseStreamResumeState?.contents ?? new Map());
525
+ const responseStreamTexts = useRef(responseStreamResumeState?.texts ?? new Map());
526
+ const responseStreamTimestamps = useRef(responseStreamResumeState?.timestamps ?? new Map());
527
+ const responseStreamStatuses = useRef(responseStreamResumeState?.statuses ??
528
+ new Map());
529
+ const responseStreamKeysByTask = useRef(responseStreamResumeState?.keysByTask ?? new Map());
530
+ const clearResponseStreamsForTask = (taskId, options = {}) => {
484
531
  if (!taskId)
485
532
  return;
486
533
  const keys = responseStreamKeysByTask.current.get(taskId);
487
534
  if (!keys?.size)
488
535
  return;
536
+ // Removed keys disappear from the transcript; detached keys stay rendered
537
+ // but are no longer tracked as active streams for future cleanup.
538
+ const removedKeys = new Set();
539
+ const detachedKeys = new Set();
489
540
  for (const [streamId, key] of responseStreamKeys.current.entries()) {
490
541
  if (keys.has(key)) {
542
+ if (options.preserveContinued &&
543
+ responseStreamStatuses.current.get(streamId) === 'continued' &&
544
+ responseStreamTexts.current.get(streamId) !== options.finalText) {
545
+ responseStreamKeys.current.delete(streamId);
546
+ responseStreamContents.current.delete(streamId);
547
+ responseStreamTexts.current.delete(streamId);
548
+ responseStreamTimestamps.current.delete(streamId);
549
+ responseStreamStatuses.current.delete(streamId);
550
+ detachedKeys.add(key);
551
+ continue;
552
+ }
491
553
  responseStreamKeys.current.delete(streamId);
554
+ responseStreamContents.current.delete(streamId);
555
+ responseStreamTexts.current.delete(streamId);
492
556
  responseStreamTimestamps.current.delete(streamId);
557
+ responseStreamStatuses.current.delete(streamId);
558
+ removedKeys.add(key);
493
559
  }
494
560
  }
495
- responseStreamKeysByTask.current.delete(taskId);
496
- setMessages((prev) => prev.filter((message) => !keys.has(message.key)));
561
+ const inactiveKeys = new Set([...removedKeys, ...detachedKeys]);
562
+ const remainingKeys = new Set([...keys].filter((key) => !inactiveKeys.has(key)));
563
+ if (remainingKeys.size > 0) {
564
+ responseStreamKeysByTask.current.set(taskId, remainingKeys);
565
+ }
566
+ else {
567
+ responseStreamKeysByTask.current.delete(taskId);
568
+ }
569
+ setMessages((prev) => prev.filter((message) => !removedKeys.has(message.key)));
497
570
  };
498
571
  const upsertResponseStreamMessage = (event) => {
499
572
  if (!isResponseStreamEvent(event))
@@ -504,9 +577,12 @@ function ChatUIWithConnection({ initialPrompt, version: _version, versionId: _ve
504
577
  const taskId = event.task?.id;
505
578
  const existingKey = responseStreamKeys.current.get(streamId);
506
579
  if (event.content.status === 'aborted') {
580
+ responseStreamContents.current.delete(streamId);
581
+ responseStreamTexts.current.delete(streamId);
582
+ responseStreamTimestamps.current.delete(streamId);
583
+ responseStreamStatuses.current.delete(streamId);
507
584
  if (existingKey) {
508
585
  responseStreamKeys.current.delete(streamId);
509
- responseStreamTimestamps.current.delete(streamId);
510
586
  if (taskId) {
511
587
  const keys = responseStreamKeysByTask.current.get(taskId);
512
588
  keys?.delete(existingKey);
@@ -517,11 +593,22 @@ function ChatUIWithConnection({ initialPrompt, version: _version, versionId: _ve
517
593
  }
518
594
  return;
519
595
  }
520
- const text = event.content.text;
596
+ const streamContents = responseStreamContents.current.get(streamId) ?? [];
597
+ const existingContentIndex = streamContents.findIndex((content) => content.sequence === event.content.sequence);
598
+ if (existingContentIndex === -1) {
599
+ streamContents.push(event.content);
600
+ }
601
+ else {
602
+ streamContents[existingContentIndex] = event.content;
603
+ }
604
+ responseStreamContents.current.set(streamId, streamContents);
605
+ const text = applyResponseStreamContentsInSequence(streamContents);
521
606
  if (!text.trim())
522
607
  return;
523
608
  const key = existingKey ?? `response-stream-${streamId}`;
524
609
  responseStreamKeys.current.set(streamId, key);
610
+ responseStreamTexts.current.set(streamId, text);
611
+ responseStreamStatuses.current.set(streamId, event.content.status);
525
612
  if (taskId) {
526
613
  const keys = responseStreamKeysByTask.current.get(taskId) ?? new Set();
527
614
  keys.add(key);
@@ -773,7 +860,10 @@ function ChatUIWithConnection({ initialPrompt, version: _version, versionId: _ve
773
860
  }
774
861
  const text = extractMessageText(getAgentNotificationText(event));
775
862
  if (text.trim()) {
776
- clearResponseStreamsForTask(taskInfo?.id);
863
+ clearResponseStreamsForTask(taskInfo?.id, {
864
+ preserveContinued: true,
865
+ finalText: text,
866
+ });
777
867
  const taskName = agentName || 'assistant';
778
868
  const messageContent = renderAssistantMessage(text, taskName);
779
869
  setMessages((prev) => [
@@ -842,13 +932,16 @@ function ChatUIWithConnection({ initialPrompt, version: _version, versionId: _ve
842
932
  event.content !== undefined &&
843
933
  taskInfo &&
844
934
  'agent' in taskInfo) {
845
- clearResponseStreamsForTask(taskInfo.id);
846
935
  // One-shot agents may complete with runtime_done without sending
847
936
  // agent_notification_message. Display the output if we haven't
848
937
  // already shown a response for this input cycle.
849
938
  const contentStr = typeof event.content === 'string'
850
939
  ? event.content
851
940
  : JSON.stringify(event.content);
941
+ clearResponseStreamsForTask(taskInfo.id, {
942
+ preserveContinued: true,
943
+ finalText: contentStr,
944
+ });
852
945
  if (contentStr && contentStr !== '{}' && contentStr !== 'null') {
853
946
  const rendered = fixListItemMarkdown(marked.parse(contentStr));
854
947
  const taskName = agentName || 'assistant';
@@ -1108,7 +1201,7 @@ export async function createSession(client, workspaceId, initialPrompt, versionI
1108
1201
  }
1109
1202
  }
1110
1203
  if (!workspaceId) {
1111
- throw new Error('No workspace configured. Run: guild workspace select, or pass --workspace <id>');
1204
+ throw new WorkspaceNotConfiguredError();
1112
1205
  }
1113
1206
  progress('Creating session');
1114
1207
  const sessionData = {
@@ -1127,7 +1220,7 @@ export async function createSession(client, workspaceId, initialPrompt, versionI
1127
1220
  catch (error) {
1128
1221
  const err = handleAxiosError(error);
1129
1222
  if (err.code === ErrorCodes.NOT_FOUND) {
1130
- throw new Error(`Workspace ${workspaceId} not found. Run: guild workspace select`);
1223
+ throw new WorkspaceNotFoundError(workspaceId);
1131
1224
  }
1132
1225
  throw error;
1133
1226
  }
@@ -1145,11 +1238,13 @@ export function createChatCommand() {
1145
1238
  .argument('[prompt...]', 'Optional initial prompt (multiple words)')
1146
1239
  .option('--agent <identifier>', 'Agent ID or full name, e.g., foo~bar (default: assistant)')
1147
1240
  .option('--once', 'One-shot mode: send message, wait for response, exit (non-interactive)')
1148
- .option('--mode <format>', 'Machine-readable output format: json or jsonl')
1149
1241
  .option('--workspace <identifier>', 'Workspace ID or full name (e.g., owner/workspace-name)')
1150
1242
  .option('--no-splash', 'Skip the splash screen animation')
1151
1243
  .option('--resume <session-id>', 'Resume an existing session')
1152
1244
  .option('--events <types>', 'Event types to show (default: user). Shorthands: none, user, system, all, or comma-separated type names (e.g. agent_console,llm_start)')
1245
+ // Accept --mode so `guild chat --mode json` works when re-parsed.
1246
+ // The actual value is read from process.argv by getOutputMode().
1247
+ .addOption(new Option('--mode <format>').hideHelp())
1153
1248
  .addHelpText('after', '\nTo chat with a local agent under development: guild agent chat')
1154
1249
  .action(async (promptArgs, options) => {
1155
1250
  const initialPrompt = promptArgs.length > 0 ? promptArgs.join(' ') : 'Hello';
@@ -1220,7 +1315,8 @@ export function createChatCommand() {
1220
1315
  if (hasRootTaskError) {
1221
1316
  debug('Found error event from root agent, exiting --once mode');
1222
1317
  const errorEvents = allEvents.filter((e) => e.type === 'runtime_error' || e.type === 'agent_notification_error');
1223
- if (errorEvents.length > 0 && !options.mode) {
1318
+ const outputMode = getOutputMode();
1319
+ if (errorEvents.length > 0 && outputMode === 'interactive') {
1224
1320
  const lastError = errorEvents[errorEvents.length - 1];
1225
1321
  const content = lastError.content;
1226
1322
  if (content?.data) {
@@ -1230,7 +1326,7 @@ export function createChatCommand() {
1230
1326
  console.error(chalk.red('Agent failed to start'));
1231
1327
  }
1232
1328
  }
1233
- else if (options.mode === 'json') {
1329
+ else if (outputMode === 'json') {
1234
1330
  console.log(JSON.stringify({
1235
1331
  session_id: session.id,
1236
1332
  events: allEvents,
@@ -1241,14 +1337,14 @@ export function createChatCommand() {
1241
1337
  }
1242
1338
  if (hasRootTaskDone || hasAgentMessage || hasUIPromptMessage) {
1243
1339
  debug('Found completion event from root agent, exiting --once mode');
1244
- await outputOnceResult(session.id, allEvents, options.mode);
1340
+ await outputOnceResult(session.id, allEvents, getOutputMode());
1245
1341
  process.exit(0);
1246
1342
  }
1247
1343
  // Timeout if no activity for too long
1248
1344
  if (inactivityCounter >= maxInactivityAttempts) {
1249
1345
  debug(`Inactivity timeout reached (${maxInactivityAttempts} attempts with no new events)`);
1250
1346
  debug(`Exiting with ${allEvents.length} events total`);
1251
- await outputOnceResult(session.id, allEvents, options.mode);
1347
+ await outputOnceResult(session.id, allEvents, getOutputMode());
1252
1348
  process.exit(0);
1253
1349
  }
1254
1350
  }
@@ -1256,6 +1352,14 @@ export function createChatCommand() {
1256
1352
  catch (error) {
1257
1353
  spinner.fail('Connection failed');
1258
1354
  console.error('');
1355
+ if (error instanceof WorkspaceNotConfiguredError) {
1356
+ format.error(WORKSPACE_NOT_CONFIGURED_MSG);
1357
+ process.exit(1);
1358
+ }
1359
+ if (error instanceof WorkspaceNotFoundError) {
1360
+ format.error(WORKSPACE_NOT_FOUND_MSG);
1361
+ process.exit(1);
1362
+ }
1259
1363
  const formattedError = handleAxiosError(error);
1260
1364
  console.error(`Error: ${formattedError.error}`);
1261
1365
  console.error(formattedError.details);
@@ -2,7 +2,7 @@
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
  import { Command } from 'commander';
4
4
  import { loadConfig, } from '../../lib/guild-config.js';
5
- import { getOutputMode } from '../../lib/output-mode.js';
5
+ import { isMachineReadable } from '../../lib/output-mode.js';
6
6
  import { createOutputWriter } from '../../lib/output.js';
7
7
  /**
8
8
  * All valid config keys across global and local configs.
@@ -25,7 +25,7 @@ export function createConfigGetCommand() {
25
25
  .action(async (key) => {
26
26
  const output = createOutputWriter();
27
27
  const config = await loadConfig();
28
- const mode = getOutputMode();
28
+ const jsonMode = isMachineReadable();
29
29
  if (!ALL_VALID_KEYS.includes(key)) {
30
30
  output.error(`Unknown config key: ${key}\n\nValid keys:\n Global: ${VALID_GLOBAL_KEYS.join(', ')}\n Local: ${VALID_LOCAL_KEYS.join(', ')}`);
31
31
  process.exit(1);
@@ -44,7 +44,7 @@ export function createConfigGetCommand() {
44
44
  source = 'local';
45
45
  }
46
46
  if (value === undefined) {
47
- if (mode === 'json') {
47
+ if (jsonMode) {
48
48
  output.data({ key, value: null, source: null });
49
49
  }
50
50
  else {
@@ -52,7 +52,7 @@ export function createConfigGetCommand() {
52
52
  }
53
53
  process.exit(1);
54
54
  }
55
- if (mode === 'json') {
55
+ if (jsonMode) {
56
56
  output.data({ key, value, source });
57
57
  }
58
58
  else {
@@ -3,15 +3,14 @@
3
3
  import { Command } from 'commander';
4
4
  import chalk from 'chalk';
5
5
  import { loadConfig } from '../../lib/guild-config.js';
6
- import { getOutputMode } from '../../lib/output-mode.js';
6
+ import { isMachineReadable } from '../../lib/output-mode.js';
7
7
  import { createOutputWriter } from '../../lib/output.js';
8
8
  export function createConfigListCommand() {
9
9
  const cmd = new Command('list');
10
10
  cmd.description('Show all configuration values').action(async () => {
11
11
  const output = createOutputWriter();
12
12
  const config = await loadConfig();
13
- const mode = getOutputMode();
14
- if (mode === 'json') {
13
+ if (isMachineReadable()) {
15
14
  output.data({
16
15
  global: config.global || null,
17
16
  local: config.local || null,
@@ -4,7 +4,7 @@ import { Command } from 'commander';
4
4
  import chalk from 'chalk';
5
5
  import * as fs from 'fs/promises';
6
6
  import { getGlobalConfigPath, getLocalConfigPath } from '../../lib/guild-config.js';
7
- import { getOutputMode } from '../../lib/output-mode.js';
7
+ import { isMachineReadable } from '../../lib/output-mode.js';
8
8
  import { createOutputWriter } from '../../lib/output.js';
9
9
  async function fileExists(filePath) {
10
10
  return fs
@@ -18,10 +18,9 @@ export function createConfigPathCommand() {
18
18
  const output = createOutputWriter();
19
19
  const globalPath = getGlobalConfigPath();
20
20
  const localPath = getLocalConfigPath();
21
- const mode = getOutputMode();
22
21
  const globalExists = await fileExists(globalPath);
23
22
  const localExists = await fileExists(localPath);
24
- if (mode === 'json') {
23
+ if (isMachineReadable()) {
25
24
  output.data({
26
25
  global: { path: globalPath, exists: globalExists },
27
26
  local: { path: localPath, exists: localExists },
@@ -3,7 +3,7 @@
3
3
  import { Command } from 'commander';
4
4
  import chalk from 'chalk';
5
5
  import { saveGlobalConfig } from '../../lib/guild-config.js';
6
- import { getOutputMode } from '../../lib/output-mode.js';
6
+ import { isMachineReadable } from '../../lib/output-mode.js';
7
7
  import { createOutputWriter } from '../../lib/output.js';
8
8
  import { GuildAPIClient } from '../../lib/api-client.js';
9
9
  import { debug } from '../../lib/errors.js';
@@ -46,8 +46,8 @@ async function resolveWorkspaceName(workspaceId) {
46
46
  return undefined;
47
47
  }
48
48
  }
49
- function printResult(key, value, mode, output) {
50
- if (mode === 'json') {
49
+ function printResult(key, value, jsonMode, output) {
50
+ if (jsonMode) {
51
51
  output.data({ key, value });
52
52
  }
53
53
  else {
@@ -62,7 +62,7 @@ export function createConfigSetCommand() {
62
62
  .argument('<value>', 'Value to set')
63
63
  .action(async (key, value) => {
64
64
  const output = createOutputWriter();
65
- const mode = getOutputMode();
65
+ const jsonMode = isMachineReadable();
66
66
  if (!VALID_GLOBAL_KEYS.includes(key)) {
67
67
  output.error(`Unknown config key: ${key}\n\nValid keys:\n${VALID_GLOBAL_KEYS.map((k) => ` ${k}`).join('\n')}`);
68
68
  process.exit(1);
@@ -71,7 +71,7 @@ export function createConfigSetCommand() {
71
71
  if (BOOLEAN_KEYS.has(typedKey)) {
72
72
  const boolValue = parseBoolean(value, key, output);
73
73
  await saveGlobalConfig({ [typedKey]: boolValue });
74
- printResult(key, boolValue, mode, output);
74
+ printResult(key, boolValue, jsonMode, output);
75
75
  return;
76
76
  }
77
77
  if (typedKey === 'default_workspace') {
@@ -80,14 +80,14 @@ export function createConfigSetCommand() {
80
80
  default_workspace: value,
81
81
  default_workspace_name: name,
82
82
  });
83
- printResult(key, value, mode, output);
83
+ printResult(key, value, jsonMode, output);
84
84
  if (name) {
85
- if (mode !== 'json') {
85
+ if (!jsonMode) {
86
86
  output.progress(` Workspace name: ${name}`);
87
87
  }
88
88
  }
89
89
  else {
90
- if (mode !== 'json') {
90
+ if (!jsonMode) {
91
91
  output.error('Could not resolve workspace name (not authenticated?)');
92
92
  }
93
93
  }
@@ -111,21 +111,21 @@ export function createConfigSetCommand() {
111
111
  default_owner: ownerId,
112
112
  default_owner_name: ownerName,
113
113
  });
114
- printResult(key, ownerId, mode, output);
114
+ printResult(key, ownerId, jsonMode, output);
115
115
  if (ownerName) {
116
- if (mode !== 'json') {
116
+ if (!jsonMode) {
117
117
  output.progress(` Owner name: ${ownerName}`);
118
118
  }
119
119
  }
120
120
  else {
121
- if (mode !== 'json') {
121
+ if (!jsonMode) {
122
122
  output.error('Could not resolve owner name (not authenticated?)');
123
123
  }
124
124
  }
125
125
  return;
126
126
  }
127
127
  await saveGlobalConfig({ [typedKey]: value });
128
- printResult(key, value, mode, output);
128
+ printResult(key, value, jsonMode, output);
129
129
  });
130
130
  return cmd;
131
131
  }
@@ -5,8 +5,9 @@ import chalk from 'chalk';
5
5
  import { GuildAPIClient } from '../../lib/api-client.js';
6
6
  import { getAuthToken } from '../../lib/auth.js';
7
7
  import { handleAxiosError } from '../../lib/errors.js';
8
- import { getOutputMode } from '../../lib/output-mode.js';
8
+ import { isMachineReadable } from '../../lib/output-mode.js';
9
9
  import { createOutputWriter } from '../../lib/output.js';
10
+ import { DEFAULT_PAGE_LIMIT } from '../../lib/api-types.js';
10
11
  import { Table } from '../../lib/table.js';
11
12
  export function createCredentialsEndpointListCommand() {
12
13
  const cmd = new Command('list');
@@ -15,8 +16,8 @@ export function createCredentialsEndpointListCommand() {
15
16
  .argument('<credential-id>', 'Credential ID')
16
17
  .option('--include-previous-versions', 'Include endpoints from previous versions')
17
18
  .option('--search <query>', 'Search by operation name')
18
- .option('--limit <number>', 'Number of results to return', '20')
19
- .option('--offset <number>', 'Offset for pagination', '0')
19
+ .option('--limit <number>', `Number of results to return (default: ${DEFAULT_PAGE_LIMIT})`, String(DEFAULT_PAGE_LIMIT))
20
+ .option('--offset <number>', 'Offset for pagination (default: 0)', '0')
20
21
  .action(async (credentialId, options) => {
21
22
  const output = createOutputWriter();
22
23
  try {
@@ -36,7 +37,7 @@ export function createCredentialsEndpointListCommand() {
36
37
  params.append('search', options.search);
37
38
  }
38
39
  const response = await client.get(`/credentials/${credentialId}/endpoints?${params.toString()}`);
39
- if (getOutputMode() === 'json') {
40
+ if (isMachineReadable()) {
40
41
  console.log(JSON.stringify(response, null, 2));
41
42
  }
42
43
  else {
@@ -4,16 +4,17 @@ import { Command } from 'commander';
4
4
  import { GuildAPIClient } from '../../lib/api-client.js';
5
5
  import { getAuthToken } from '../../lib/auth.js';
6
6
  import { handleAxiosError } from '../../lib/errors.js';
7
- import { getOutputMode } from '../../lib/output-mode.js';
7
+ import { isMachineReadable } from '../../lib/output-mode.js';
8
8
  import { createOutputWriter, formatCredentialsTable } from '../../lib/output.js';
9
+ import { DEFAULT_PAGE_LIMIT } from '../../lib/api-types.js';
9
10
  export function createCredentialsListCommand() {
10
11
  const cmd = new Command('list');
11
12
  cmd
12
13
  .description('List credentials for an account')
13
14
  .requiredOption('--owner <account>', 'Account name or ID')
14
15
  .option('--search <query>', 'Filter by integration name')
15
- .option('--limit <number>', 'Number of results to return', '20')
16
- .option('--offset <number>', 'Offset for pagination', '0')
16
+ .option('--limit <number>', `Number of results to return (default: ${DEFAULT_PAGE_LIMIT})`, String(DEFAULT_PAGE_LIMIT))
17
+ .option('--offset <number>', 'Offset for pagination (default: 0)', '0')
17
18
  .action(async (options) => {
18
19
  const output = createOutputWriter();
19
20
  try {
@@ -31,7 +32,7 @@ export function createCredentialsListCommand() {
31
32
  params.append('search', options.search);
32
33
  }
33
34
  const response = await client.get(`/accounts/${accountId}/credentials?${params.toString()}`);
34
- if (getOutputMode() === 'json') {
35
+ if (isMachineReadable()) {
35
36
  console.log(JSON.stringify(response, null, 2));
36
37
  }
37
38
  else {