@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
@@ -11,9 +11,10 @@
11
11
  * 2. GUILD_OWNER_ID environment variable
12
12
  * 3. default_owner from ~/.guild/config.json
13
13
  * 4. Fetch user + orgs from API:
14
- * - No orgs → use current user
15
- * - Has orgs + interactive → prompt
16
- * - Has orgs + non-interactive → use current user
14
+ * - No orgs + non-interactive → use current user (single-account, stated explicitly)
15
+ * - No orgs + interactive → prompt with personal account as only choice
16
+ * - Has orgs + interactive → prompt to select from all accounts
17
+ * - Has orgs + non-interactive → error: require --owner flag
17
18
  */
18
19
  import inquirer from 'inquirer';
19
20
  import { loadGlobalConfig } from './guild-config.js';
@@ -35,7 +36,7 @@ export async function lookupOwner(client, val) {
35
36
  return undefined;
36
37
  }
37
38
  export async function resolveOwnerId(options) {
38
- const { ownerFlag, client, interactive } = options;
39
+ const { ownerFlag, client, interactive, requireExplicitOwner = false } = options;
39
40
  // Priority 1: --owner flag
40
41
  if (ownerFlag) {
41
42
  debug('Using owner from --owner flag: %s', ownerFlag);
@@ -71,17 +72,23 @@ export async function resolveOwnerId(options) {
71
72
  // Priority 4: Fetch user + orgs
72
73
  const me = await client.get('/me');
73
74
  const orgs = await client.fetchAll('/me/organizations');
74
- // No orgs → use current user
75
- if (orgs.length === 0) {
75
+ // No orgs + non-interactive → use current user (single account, stated explicitly by caller)
76
+ if (orgs.length === 0 && !interactive) {
76
77
  debug('No organizations found, using current user as owner');
77
78
  return { id: me.id, name: me.name, type: 'user' };
78
79
  }
79
- // Has orgs + non-interactive → use current user
80
- if (!interactive) {
81
- debug('Non-interactive mode with orgs available, using current user as owner');
80
+ // Has orgs + non-interactive
81
+ if (orgs.length > 0 && !interactive) {
82
+ if (requireExplicitOwner) {
83
+ debug('Non-interactive mode with multiple accounts available, requiring --owner');
84
+ throw new Error('Owner is required in non-interactive mode when multiple accounts are available.\n\n' +
85
+ 'Use --owner <name-or-id> to specify an owner, or set a default:\n' +
86
+ ' guild config set default_owner <name-or-id>');
87
+ }
88
+ debug('Multiple accounts available, defaulting to current user');
82
89
  return { id: me.id, name: me.name, type: 'user' };
83
90
  }
84
- // Has orgs + interactive prompt
91
+ // Interactive prompt (always, even with single account)
85
92
  const choices = [
86
93
  {
87
94
  name: `${me.name} (personal)`,
@@ -97,14 +97,23 @@ export interface TextContent {
97
97
  type: 'text';
98
98
  data: string;
99
99
  }
100
+ export interface UnknownContent {
101
+ type: string;
102
+ data?: unknown;
103
+ }
104
+ export interface MultipartContent {
105
+ type: 'multipart/mixed';
106
+ parts: UnknownContent[];
107
+ }
100
108
  export interface ResponseStreamContent {
101
109
  type: 'application/guild-response-stream';
102
110
  stream_id: string;
103
111
  sequence: number;
104
- status: 'streaming' | 'done' | 'aborted';
112
+ status: 'streaming' | 'done' | 'continued' | 'aborted';
105
113
  text: string;
114
+ is_delta?: boolean;
106
115
  }
107
- export type AgentNotificationMessageContent = TextContent | ResponseStreamContent;
116
+ export type AgentNotificationMessageContent = TextContent | MultipartContent | ResponseStreamContent;
108
117
  export interface AgentNotificationMessageEvent extends BaseEvent {
109
118
  type: 'agent_notification_message';
110
119
  content: AgentNotificationMessageContent;
@@ -181,6 +190,8 @@ export declare function isDoneResponseStreamEvent(event: SessionEvent): event is
181
190
  status: 'done';
182
191
  };
183
192
  };
193
+ /** Apply a response-stream payload to the accumulated display text. */
194
+ export declare function applyResponseStreamText(currentText: string, content: ResponseStreamContent): string;
184
195
  /** Extract display text from notification content. */
185
196
  export declare function getAgentNotificationText(event: AgentNotificationMessageEvent): string;
186
197
  /** Check if an event belongs to the root task or has no task context. */
@@ -96,9 +96,23 @@ export function isResponseStreamEvent(event) {
96
96
  export function isDoneResponseStreamEvent(event) {
97
97
  return isResponseStreamEvent(event) && event.content.status === 'done';
98
98
  }
99
+ /** Apply a response-stream payload to the accumulated display text. */
100
+ export function applyResponseStreamText(currentText, content) {
101
+ return content.is_delta ? currentText + content.text : content.text;
102
+ }
99
103
  /** Extract display text from notification content. */
100
104
  export function getAgentNotificationText(event) {
101
- return event.content.type === 'text' ? event.content.data : event.content.text;
105
+ if (event.content.type === 'text') {
106
+ return event.content.data;
107
+ }
108
+ if (event.content.type === 'application/guild-response-stream') {
109
+ return event.content.text;
110
+ }
111
+ if (event.content.type === 'multipart/mixed' && event.content.parts.length === 1) {
112
+ const part = event.content.parts[0];
113
+ return part.type === 'text' && typeof part.data === 'string' ? part.data : '';
114
+ }
115
+ return '';
102
116
  }
103
117
  /** Check if an event belongs to the root task or has no task context. */
104
118
  export function isRootTaskEvent(event) {
@@ -3,7 +3,7 @@
3
3
  import { debug, isDebugMode } from './errors.js';
4
4
  import { shouldShowEvent } from './event-filter.js';
5
5
  import { fetchEvents } from './session-events-fetch.js';
6
- import { getAgentNotificationText, isDoneResponseStreamEvent, isResponseStreamEvent, isRootTaskEvent, } from './session-events.js';
6
+ import { applyResponseStreamText, getAgentNotificationText, isDoneResponseStreamEvent, isResponseStreamEvent, isRootTaskEvent, } from './session-events.js';
7
7
  /**
8
8
  * Poll for agent response using from_id cursor.
9
9
  *
@@ -20,6 +20,7 @@ export async function pollForResponse(client, sessionId, afterEventId, maxWaitTi
20
20
  const startTime = Date.now();
21
21
  let fromId = afterEventId;
22
22
  let responseStreamDone = null;
23
+ const responseStreamTexts = new Map();
23
24
  while (Date.now() - startTime < maxWaitTime) {
24
25
  const events = await fetchEvents(client, sessionId, { fromId });
25
26
  let lastAgentRuntimeDone = null;
@@ -28,9 +29,11 @@ export async function pollForResponse(client, sessionId, afterEventId, maxWaitTi
28
29
  debug(`pollForResponse event: ${event.type}`);
29
30
  if (event.type === 'agent_notification_message') {
30
31
  if (isResponseStreamEvent(event)) {
32
+ const streamText = applyResponseStreamText(responseStreamTexts.get(event.content.stream_id) ?? '', event.content);
33
+ responseStreamTexts.set(event.content.stream_id, streamText);
31
34
  if (isRootTaskEvent(event) && isDoneResponseStreamEvent(event)) {
32
35
  responseStreamDone = {
33
- response: getAgentNotificationText(event),
36
+ response: streamText,
34
37
  lastEventId: event.id,
35
38
  };
36
39
  }
@@ -91,6 +94,7 @@ export async function pollForResponseWithEvents(client, sessionId, eventFilter,
91
94
  const startTime = Date.now();
92
95
  let fromId = afterEventId;
93
96
  let responseStreamDone = null;
97
+ const responseStreamTexts = new Map();
94
98
  while (Date.now() - startTime < maxWaitTime) {
95
99
  const events = await fetchEvents(client, sessionId, { fromId });
96
100
  let lastAgentRuntimeDone = null;
@@ -105,9 +109,11 @@ export async function pollForResponseWithEvents(client, sessionId, eventFilter,
105
109
  }
106
110
  if (event.type === 'agent_notification_message') {
107
111
  if (isResponseStreamEvent(event)) {
112
+ const streamText = applyResponseStreamText(responseStreamTexts.get(event.content.stream_id) ?? '', event.content);
113
+ responseStreamTexts.set(event.content.stream_id, streamText);
108
114
  if (isRootTaskEvent(event) && isDoneResponseStreamEvent(event)) {
109
115
  responseStreamDone = {
110
- response: getAgentNotificationText(event),
116
+ response: streamText,
111
117
  lastEventId: event.id,
112
118
  };
113
119
  }
@@ -1,5 +1,5 @@
1
1
  import { GuildAPIClient } from './api-client.js';
2
- import { SessionEvent, Session } from './session-events.js';
2
+ import { type ResponseStreamContent, SessionEvent, Session } from './session-events.js';
3
3
  /**
4
4
  * Print a dimmed resume hint to stderr on session exit.
5
5
  * Only call when a session ID is available.
@@ -19,6 +19,20 @@ export interface DisplayMessage {
19
19
  type: 'user' | 'assistant' | 'progress';
20
20
  timestamp?: string;
21
21
  }
22
+ export interface ResponseStreamResumeState {
23
+ keys: Map<string, string>;
24
+ contents: Map<string, ResponseStreamContent[]>;
25
+ texts: Map<string, string>;
26
+ timestamps: Map<string, string>;
27
+ statuses: Map<string, ResponseStreamContent['status']>;
28
+ keysByTask: Map<string, Set<string>>;
29
+ }
30
+ export interface SessionResumeDisplay {
31
+ displayMessages: DisplayMessage[];
32
+ responseStreamState: ResponseStreamResumeState;
33
+ }
34
+ export declare function responseStreamResumeStateFromEvents(events: SessionEvent[]): ResponseStreamResumeState;
35
+ export declare function prepareSessionResumeDisplay(events: SessionEvent[]): SessionResumeDisplay;
22
36
  /**
23
37
  * Convert session events to display messages for resume.
24
38
  * Maps user_message, agent_notification_message, and agent_notification_error
@@ -7,7 +7,7 @@
7
7
  import chalk from 'chalk';
8
8
  import { marked } from 'marked';
9
9
  import { markedTerminal } from 'marked-terminal';
10
- import { getAgentNotificationText, isResponseStreamEvent, } from './session-events.js';
10
+ import { applyResponseStreamText, getAgentNotificationText, isResponseStreamEvent, isRootTaskEvent, } from './session-events.js';
11
11
  import { fetchEvents } from './session-events-fetch.js';
12
12
  import { brand, code as codeColor } from './colors.js';
13
13
  // Configure marked for terminal rendering (same config as chat.tsx)
@@ -48,13 +48,127 @@ export async function fetchSession(client, sessionId) {
48
48
  const session = (await client.get(`/sessions/${sessionId}`));
49
49
  return session;
50
50
  }
51
- /**
52
- * Convert session events to display messages for resume.
53
- * Maps user_message, agent_notification_message, and agent_notification_error
54
- * to DisplayMessage objects. Skips progress, console, runtime events.
55
- */
56
- export function eventsToDisplayMessages(events) {
51
+ function renderAssistantText(key, text, timestamp) {
52
+ const rendered = renderMarkdown(text);
53
+ const messageContent = `${chalk.green('●')} ${chalk.bold('assistant')}\n${rendered.trim()}`;
54
+ return {
55
+ key,
56
+ content: messageContent,
57
+ type: 'assistant',
58
+ timestamp,
59
+ };
60
+ }
61
+ function textMatches(left, right) {
62
+ return left === right;
63
+ }
64
+ function formatDisplayTimestamp(timestamp) {
65
+ if (timestamp === undefined)
66
+ return undefined;
67
+ const date = new Date(timestamp);
68
+ if (Number.isNaN(date.getTime()))
69
+ return timestamp;
70
+ return date.toLocaleTimeString();
71
+ }
72
+ function upsertResponseStreamEvent(events, event) {
73
+ const existingIndex = events.findIndex((existing) => existing.content.sequence === event.content.sequence);
74
+ if (existingIndex === -1) {
75
+ events.push(event);
76
+ }
77
+ else {
78
+ events[existingIndex] = event;
79
+ }
80
+ }
81
+ function collectResponseStreamResumeEntries(events) {
82
+ const responseStreamGroups = new Map();
83
+ const finalMessagesByTask = new Map();
84
+ for (const [index, event] of events.entries()) {
85
+ if (event.type !== 'agent_notification_message')
86
+ continue;
87
+ if (!isRootTaskEvent(event))
88
+ continue;
89
+ if (isResponseStreamEvent(event)) {
90
+ const streamId = event.content.stream_id;
91
+ const current = responseStreamGroups.get(streamId);
92
+ if (current) {
93
+ upsertResponseStreamEvent(current.events, event);
94
+ // Insertion order and sequence can diverge if backfilled events arrive out
95
+ // of order; use insertion index only for "later final message" dedupe.
96
+ current.lastIndex = Math.max(current.lastIndex, index);
97
+ }
98
+ else {
99
+ responseStreamGroups.set(streamId, {
100
+ events: [event],
101
+ lastIndex: index,
102
+ });
103
+ }
104
+ continue;
105
+ }
106
+ const taskId = event.task?.id;
107
+ if (!taskId)
108
+ continue;
109
+ const text = getAgentNotificationText(event);
110
+ if (!text.trim())
111
+ continue;
112
+ const finalMessages = finalMessagesByTask.get(taskId) ?? [];
113
+ finalMessages.push({ index, text });
114
+ finalMessagesByTask.set(taskId, finalMessages);
115
+ }
116
+ const entries = [];
117
+ for (const [streamId, streamGroup] of responseStreamGroups.entries()) {
118
+ const streamEvents = [...streamGroup.events].sort((left, right) => left.content.sequence - right.content.sequence);
119
+ const latest = streamEvents[streamEvents.length - 1];
120
+ const text = streamEvents.reduce((currentText, streamEvent) => applyResponseStreamText(currentText, streamEvent.content), '');
121
+ const taskId = latest.task?.id;
122
+ const matchingLaterFinalMessage = taskId !== undefined &&
123
+ (finalMessagesByTask.get(taskId) ?? []).some((message) => message.index > streamGroup.lastIndex && textMatches(text, message.text));
124
+ if (latest.content.status !== 'aborted' &&
125
+ !matchingLaterFinalMessage &&
126
+ text.trim()) {
127
+ entries.push({
128
+ streamId,
129
+ key: `response-stream-${streamId}`,
130
+ contents: streamEvents.map((event) => event.content),
131
+ text,
132
+ status: latest.content.status,
133
+ timestamp: formatDisplayTimestamp(latest.created_at),
134
+ taskId,
135
+ });
136
+ }
137
+ }
138
+ return entries;
139
+ }
140
+ function responseStreamResumeStateFromEntries(entries) {
141
+ const state = {
142
+ keys: new Map(),
143
+ contents: new Map(),
144
+ texts: new Map(),
145
+ timestamps: new Map(),
146
+ statuses: new Map(),
147
+ keysByTask: new Map(),
148
+ };
149
+ for (const entry of entries) {
150
+ state.keys.set(entry.streamId, entry.key);
151
+ state.contents.set(entry.streamId, entry.contents);
152
+ state.texts.set(entry.streamId, entry.text);
153
+ state.statuses.set(entry.streamId, entry.status);
154
+ if (entry.timestamp !== undefined) {
155
+ state.timestamps.set(entry.streamId, entry.timestamp);
156
+ }
157
+ if (entry.taskId !== undefined) {
158
+ const keys = state.keysByTask.get(entry.taskId) ?? new Set();
159
+ keys.add(entry.key);
160
+ state.keysByTask.set(entry.taskId, keys);
161
+ }
162
+ }
163
+ return state;
164
+ }
165
+ export function responseStreamResumeStateFromEvents(events) {
166
+ return responseStreamResumeStateFromEntries(collectResponseStreamResumeEntries(events));
167
+ }
168
+ function eventsToDisplayMessagesWithResponseStreamEntries(events, responseStreamEntries) {
57
169
  const messages = [];
170
+ const responseStreamEntriesById = new Map(responseStreamEntries.map((entry) => [entry.streamId, entry]));
171
+ const renderedResponseStreamIds = new Set();
58
172
  for (const event of events) {
59
173
  if (event.type === 'user_message') {
60
174
  messages.push({
@@ -65,18 +179,22 @@ export function eventsToDisplayMessages(events) {
65
179
  });
66
180
  }
67
181
  else if (event.type === 'agent_notification_message') {
68
- if (isResponseStreamEvent(event))
182
+ if (isResponseStreamEvent(event)) {
183
+ if (!isRootTaskEvent(event))
184
+ continue;
185
+ const streamId = event.content.stream_id;
186
+ if (renderedResponseStreamIds.has(streamId))
187
+ continue;
188
+ const streamEntry = responseStreamEntriesById.get(streamId);
189
+ if (!streamEntry)
190
+ continue;
191
+ renderedResponseStreamIds.add(streamId);
192
+ messages.push(renderAssistantText(streamEntry.key, streamEntry.text, streamEntry.timestamp));
69
193
  continue;
194
+ }
70
195
  const text = getAgentNotificationText(event);
71
196
  if (text.trim()) {
72
- const rendered = renderMarkdown(text);
73
- const messageContent = `${chalk.green('●')} ${chalk.bold('assistant')}\n${rendered.trim()}`;
74
- messages.push({
75
- key: event.id,
76
- content: messageContent,
77
- type: 'assistant',
78
- timestamp: event.created_at,
79
- });
197
+ messages.push(renderAssistantText(event.id, text, event.created_at));
80
198
  }
81
199
  }
82
200
  else if (event.type === 'agent_notification_error') {
@@ -93,4 +211,19 @@ export function eventsToDisplayMessages(events) {
93
211
  }
94
212
  return messages;
95
213
  }
214
+ export function prepareSessionResumeDisplay(events) {
215
+ const responseStreamEntries = collectResponseStreamResumeEntries(events);
216
+ return {
217
+ displayMessages: eventsToDisplayMessagesWithResponseStreamEntries(events, responseStreamEntries),
218
+ responseStreamState: responseStreamResumeStateFromEntries(responseStreamEntries),
219
+ };
220
+ }
221
+ /**
222
+ * Convert session events to display messages for resume.
223
+ * Maps user_message, agent_notification_message, and agent_notification_error
224
+ * to DisplayMessage objects. Skips progress, console, runtime events.
225
+ */
226
+ export function eventsToDisplayMessages(events) {
227
+ return prepareSessionResumeDisplay(events).displayMessages;
228
+ }
96
229
  //# sourceMappingURL=session-resume.js.map
@@ -3,6 +3,7 @@
3
3
  import chalk from 'chalk';
4
4
  import LOGO from './logo.js';
5
5
  import { brand } from './colors.js';
6
+ import { isMachineReadable } from './output-mode.js';
6
7
  /**
7
8
  * Show splash screen with logo
8
9
  */
@@ -21,8 +22,8 @@ export function showSplashScreen(options) {
21
22
  * Never for: --json, data commands, action commands
22
23
  */
23
24
  export function shouldShowSplash() {
24
- // Never in JSON mode
25
- if (process.argv.includes('--json')) {
25
+ // Never in machine-readable mode (--mode json, --mode jsonl, --json)
26
+ if (isMachineReadable()) {
26
27
  return false;
27
28
  }
28
29
  const args = process.argv.slice(2);
@@ -1,5 +1,9 @@
1
1
  /**
2
- * Check if stdin is connected to an interactive terminal (TTY).
2
+ * Check if the CLI is running in interactive mode.
3
+ *
4
+ * Returns false when:
5
+ * - stdin is not a TTY (piped input, CI, cron)
6
+ * - the global --non-interactive flag is set
3
7
  */
4
8
  export declare function isInteractive(): boolean;
5
9
  /**
package/dist/lib/stdin.js CHANGED
@@ -1,9 +1,16 @@
1
1
  // Copyright 2026 Guild.ai
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
  /**
4
- * Check if stdin is connected to an interactive terminal (TTY).
4
+ * Check if the CLI is running in interactive mode.
5
+ *
6
+ * Returns false when:
7
+ * - stdin is not a TTY (piped input, CI, cron)
8
+ * - the global --non-interactive flag is set
5
9
  */
6
10
  export function isInteractive() {
11
+ if (process.argv.includes('--non-interactive')) {
12
+ return false;
13
+ }
7
14
  return process.stdin.isTTY === true;
8
15
  }
9
16
  /**
@@ -66,22 +66,38 @@ export async function waitForValidation(versionId, output) {
66
66
  }
67
67
  /**
68
68
  * Fetch validation step details for a failed version.
69
+ * Shows content from all steps (not just failed) so the full build output is visible.
69
70
  */
70
71
  async function fetchValidationFailureDetails(versionId) {
71
72
  const client = new GuildAPIClient();
72
73
  let failureDetails = '';
73
74
  try {
74
75
  const stepsResponse = await client.get(`/versions/${versionId}/validation/steps`);
75
- const failedSteps = stepsResponse.steps.filter((step) => step.status === 'FAILED');
76
- if (failedSteps.length > 0) {
77
- failureDetails = failedSteps
78
- .map((step) => step.content
79
- ? `Step "${step.name}" failed: ${step.content}`
80
- : `Step "${step.name}" failed`)
81
- .join('\n');
76
+ if (stepsResponse.steps.length > 0) {
77
+ failureDetails = stepsResponse.steps
78
+ .map((step) => {
79
+ let icon;
80
+ switch (step.status) {
81
+ case 'SUCCEEDED':
82
+ icon = '';
83
+ break;
84
+ case 'FAILED':
85
+ case 'ERRORED':
86
+ icon = '✗';
87
+ break;
88
+ case 'RUNNING':
89
+ icon = '⟳';
90
+ break;
91
+ default:
92
+ icon = '○';
93
+ }
94
+ const header = `${icon} ${step.name} [${step.status}]`;
95
+ return step.content ? `${header}\n${step.content}` : header;
96
+ })
97
+ .join('\n\n');
82
98
  }
83
99
  else {
84
- failureDetails = 'No failed steps found. Check validation logs for details.';
100
+ failureDetails = 'No steps found. Check validation logs for details.';
85
101
  }
86
102
  }
87
103
  catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guildai/cli",
3
- "version": "0.9.1",
3
+ "version": "0.11.0",
4
4
  "description": "Guild.ai CLI - Build, test, and deploy AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",