@guildai/cli 0.6.0 → 0.6.2

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 (59) hide show
  1. package/README.md +17 -0
  2. package/dist/commands/agent/chat.js +31 -31
  3. package/dist/commands/agent/fork.js +1 -1
  4. package/dist/commands/agent/init.js +1 -1
  5. package/dist/commands/agent/publish.js +13 -53
  6. package/dist/commands/agent/pull.js +36 -21
  7. package/dist/commands/agent/save.js +16 -52
  8. package/dist/commands/agent/test.js +8 -12
  9. package/dist/commands/auth/login.js +3 -3
  10. package/dist/commands/chat.js +68 -106
  11. package/dist/commands/credentials/endpoint-list.js +1 -1
  12. package/dist/commands/integration/connect.d.ts +3 -0
  13. package/dist/commands/integration/connect.js +76 -0
  14. package/dist/commands/integration/create.d.ts +3 -0
  15. package/dist/commands/integration/create.js +298 -0
  16. package/dist/commands/integration/get.d.ts +3 -0
  17. package/dist/commands/integration/get.js +95 -0
  18. package/dist/commands/integration/list.d.ts +3 -0
  19. package/dist/commands/integration/list.js +61 -0
  20. package/dist/commands/integration/operation/create.d.ts +3 -0
  21. package/dist/commands/integration/operation/create.js +163 -0
  22. package/dist/commands/integration/operation/list.d.ts +3 -0
  23. package/dist/commands/integration/operation/list.js +83 -0
  24. package/dist/commands/integration/update.d.ts +3 -0
  25. package/dist/commands/integration/update.js +139 -0
  26. package/dist/commands/integration/version/build.d.ts +3 -0
  27. package/dist/commands/integration/version/build.js +86 -0
  28. package/dist/commands/integration/version/create.d.ts +3 -0
  29. package/dist/commands/integration/version/create.js +45 -0
  30. package/dist/commands/integration/version/get.d.ts +3 -0
  31. package/dist/commands/integration/version/get.js +72 -0
  32. package/dist/commands/integration/version/list.d.ts +3 -0
  33. package/dist/commands/integration/version/list.js +44 -0
  34. package/dist/commands/integration/version/publish.d.ts +3 -0
  35. package/dist/commands/integration/version/publish.js +79 -0
  36. package/dist/commands/integration/version/test.d.ts +3 -0
  37. package/dist/commands/integration/version/test.js +104 -0
  38. package/dist/commands/workspace/create.js +10 -4
  39. package/dist/index.js +38 -0
  40. package/dist/lib/api-types.d.ts +69 -12
  41. package/dist/lib/auth.d.ts +1 -1
  42. package/dist/lib/auth.js +3 -2
  43. package/dist/lib/integration-helpers.d.ts +15 -0
  44. package/dist/lib/integration-helpers.js +38 -0
  45. package/dist/lib/output.d.ts +11 -1
  46. package/dist/lib/output.js +94 -0
  47. package/dist/lib/session-events-fetch.d.ts +27 -0
  48. package/dist/lib/session-events-fetch.js +25 -0
  49. package/dist/lib/session-polling.d.ts +7 -2
  50. package/dist/lib/session-polling.js +19 -12
  51. package/dist/lib/session-resume.js +2 -5
  52. package/dist/lib/table.d.ts +0 -1
  53. package/dist/lib/table.js +0 -1
  54. package/dist/lib/version-helpers.d.ts +15 -0
  55. package/dist/lib/version-helpers.js +83 -0
  56. package/dist/mcp/tools.js +6 -12
  57. package/docs/CLI_WORKFLOW.md +15 -0
  58. package/docs/skills/agent-dev.md +15 -0
  59. package/package.json +4 -3
@@ -0,0 +1,104 @@
1
+ // Copyright 2026 Guild.ai
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { Command } from 'commander';
4
+ import chalk from 'chalk';
5
+ import { GuildAPIClient } from '../../../lib/api-client.js';
6
+ import { getAuthToken } from '../../../lib/auth.js';
7
+ import { handleAxiosError } from '../../../lib/errors.js';
8
+ import { getOutputMode } from '../../../lib/output-mode.js';
9
+ import { createOutputWriter } from '../../../lib/output.js';
10
+ import { resolveVersionId } from '../../../lib/integration-helpers.js';
11
+ function parseJsonFlag(value, flagName) {
12
+ try {
13
+ return JSON.parse(value);
14
+ }
15
+ catch (e) {
16
+ const msg = e instanceof Error ? e.message : String(e);
17
+ throw new Error(`Invalid JSON for ${flagName}: ${msg}`);
18
+ }
19
+ }
20
+ export function createIntegrationVersionTestCommand() {
21
+ const cmd = new Command('test');
22
+ cmd
23
+ .description('Test an endpoint invocation')
24
+ .argument('<id_or_name>', 'Integration ID or name (owner~name)')
25
+ .option('--version <semver>', 'Specific version, e.g. 1.0.0')
26
+ .requiredOption('--operation <name>', 'Operation to test, e.g. list_users')
27
+ .option('--account <name>', 'Account name to resolve credential from')
28
+ .option('--input-path <json>', 'JSON object of path parameters')
29
+ .option('--input-query <json>', 'JSON object of query parameters')
30
+ .option('--input-body <json>', 'JSON object for request body')
31
+ .action(async (identifier, options) => {
32
+ const output = createOutputWriter();
33
+ try {
34
+ const token = await getAuthToken();
35
+ if (!token) {
36
+ output.error('Not authenticated. Please log in first.', 'Run: guild auth login');
37
+ process.exit(1);
38
+ }
39
+ // Validate JSON inputs before any API calls
40
+ const body = {
41
+ operation: options.operation,
42
+ };
43
+ if (options.inputPath) {
44
+ body.input_path = parseJsonFlag(options.inputPath, '--input-path');
45
+ }
46
+ if (options.inputQuery) {
47
+ body.input_query = parseJsonFlag(options.inputQuery, '--input-query');
48
+ }
49
+ if (options.inputBody) {
50
+ body.input_body = parseJsonFlag(options.inputBody, '--input-body');
51
+ }
52
+ const client = new GuildAPIClient();
53
+ const versionId = await resolveVersionId(client, identifier, options.version);
54
+ if (options.account) {
55
+ const creds = await client.get(`/accounts/${options.account}/credentials?integration=${encodeURIComponent(identifier)}`);
56
+ if (creds.items.length === 0) {
57
+ output.error(`Error: No credentials found for account '${options.account}' on integration '${identifier}'`);
58
+ process.exit(1);
59
+ }
60
+ body.credential_id = creds.items[0].id;
61
+ }
62
+ const response = await client.post(`/integration_versions/${versionId}/test`, body);
63
+ if (getOutputMode() === 'json') {
64
+ output.data(response);
65
+ }
66
+ else {
67
+ console.log(`Testing operation: ${options.operation}`);
68
+ console.log();
69
+ console.log(chalk.bold('Request'));
70
+ console.log(` ${'URL'.padEnd(9)}${response.request_url}`);
71
+ console.log();
72
+ console.log(chalk.bold('Response'));
73
+ console.log(` ${'Status'.padEnd(9)}${response.status_code}`);
74
+ console.log(` ${'Type'.padEnd(9)}${response.content_type || chalk.dim('—')}`);
75
+ console.log();
76
+ console.log(chalk.bold('Body'));
77
+ if (response.body !== null && response.body !== undefined) {
78
+ const bodyStr = typeof response.body === 'string'
79
+ ? response.body
80
+ : JSON.stringify(response.body, null, 2);
81
+ const indented = bodyStr
82
+ .split('\n')
83
+ .map((line) => ` ${line}`)
84
+ .join('\n');
85
+ console.log(indented);
86
+ }
87
+ else {
88
+ console.log(chalk.dim(' (empty)'));
89
+ }
90
+ }
91
+ }
92
+ catch (error) {
93
+ if (error instanceof Error && error.message.startsWith('Invalid JSON')) {
94
+ output.error(`Error: ${error.message}`);
95
+ process.exit(1);
96
+ }
97
+ const formattedError = handleAxiosError(error);
98
+ output.error(`Failed to test endpoint: ${formattedError.details}`);
99
+ process.exit(1);
100
+ }
101
+ });
102
+ return cmd;
103
+ }
104
+ //# sourceMappingURL=test.js.map
@@ -4,21 +4,27 @@ import { Command } from 'commander';
4
4
  import { GuildAPIClient } from '../../lib/api-client.js';
5
5
  import { handleAxiosError, ErrorCodes } from '../../lib/errors.js';
6
6
  import { createOutputWriter } from '../../lib/output.js';
7
+ import { resolveOwnerId } from '../../lib/owner-helpers.js';
8
+ import { isInteractive } from '../../lib/stdin.js';
7
9
  export function createWorkspaceCreateCommand() {
8
10
  const cmd = new Command('create');
9
11
  cmd
10
12
  .description('Create a new workspace')
11
13
  .argument('<name>', 'Workspace name')
12
- .action(async (name) => {
14
+ .option('--owner <owner>', 'Owner (name or ID)')
15
+ .action(async (name, options) => {
13
16
  const output = createOutputWriter();
14
17
  try {
15
18
  const client = new GuildAPIClient();
16
- // Get current user info to use as workspace owner
17
- const me = (await client.get('/me'));
19
+ const owner = await resolveOwnerId({
20
+ ownerFlag: options.owner,
21
+ client,
22
+ interactive: isInteractive(),
23
+ });
18
24
  // Create workspace
19
25
  const workspace = await client.post('/workspaces', {
20
26
  name,
21
- owner_id: me.id,
27
+ owner_id: owner.id,
22
28
  });
23
29
  output.data(workspace);
24
30
  }
package/dist/index.js CHANGED
@@ -74,6 +74,19 @@ import { createCredentialsPolicyUpdateCommand } from './commands/credentials/pol
74
74
  import { createDoctorCommand } from './commands/doctor.js';
75
75
  import { createSetupCommand } from './commands/setup.js';
76
76
  import { createMcpCommand } from './commands/mcp.js';
77
+ import { createIntegrationListCommand } from './commands/integration/list.js';
78
+ import { createIntegrationGetCommand } from './commands/integration/get.js';
79
+ import { createIntegrationCreateCommand } from './commands/integration/create.js';
80
+ import { createIntegrationUpdateCommand } from './commands/integration/update.js';
81
+ import { createIntegrationConnectCommand } from './commands/integration/connect.js';
82
+ import { createIntegrationVersionListCommand } from './commands/integration/version/list.js';
83
+ import { createIntegrationVersionCreateCommand } from './commands/integration/version/create.js';
84
+ import { createIntegrationVersionGetCommand } from './commands/integration/version/get.js';
85
+ import { createIntegrationVersionBuildCommand } from './commands/integration/version/build.js';
86
+ import { createIntegrationVersionPublishCommand } from './commands/integration/version/publish.js';
87
+ import { createIntegrationVersionTestCommand } from './commands/integration/version/test.js';
88
+ import { createIntegrationOperationListCommand } from './commands/integration/operation/list.js';
89
+ import { createIntegrationOperationCreateCommand } from './commands/integration/operation/create.js';
77
90
  import { showSplashScreen, shouldShowSplash } from './lib/splash.js';
78
91
  import { checkForUpdate } from './lib/update-check.js';
79
92
  import { setupUnknownCommandSuggestions } from './lib/did-you-mean.js';
@@ -153,6 +166,31 @@ tagsCmd.addCommand(createAgentTagsListCommand());
153
166
  tagsCmd.addCommand(createAgentTagsAddCommand());
154
167
  tagsCmd.addCommand(createAgentTagsRemoveCommand());
155
168
  tagsCmd.addCommand(createAgentTagsSetCommand());
169
+ // Integration command group
170
+ const integrationCmd = program
171
+ .command('integration')
172
+ .description('Manage integrations');
173
+ integrationCmd.addCommand(createIntegrationListCommand());
174
+ integrationCmd.addCommand(createIntegrationGetCommand());
175
+ integrationCmd.addCommand(createIntegrationCreateCommand());
176
+ integrationCmd.addCommand(createIntegrationUpdateCommand());
177
+ integrationCmd.addCommand(createIntegrationConnectCommand());
178
+ // Integration version subcommand group
179
+ const integrationVersionCmd = integrationCmd
180
+ .command('version')
181
+ .description('Manage integration versions');
182
+ integrationVersionCmd.addCommand(createIntegrationVersionListCommand());
183
+ integrationVersionCmd.addCommand(createIntegrationVersionCreateCommand());
184
+ integrationVersionCmd.addCommand(createIntegrationVersionGetCommand());
185
+ integrationVersionCmd.addCommand(createIntegrationVersionBuildCommand());
186
+ integrationVersionCmd.addCommand(createIntegrationVersionPublishCommand());
187
+ integrationVersionCmd.addCommand(createIntegrationVersionTestCommand());
188
+ // Integration operation subcommand group
189
+ const integrationOperationCmd = integrationCmd
190
+ .command('operation')
191
+ .description('Manage version operations');
192
+ integrationOperationCmd.addCommand(createIntegrationOperationListCommand());
193
+ integrationOperationCmd.addCommand(createIntegrationOperationCreateCommand());
156
194
  // Workspace command group
157
195
  const workspaceCmd = program.command('workspace').description('Workspace management');
158
196
  workspaceCmd.addCommand(createWorkspaceListCommand());
@@ -204,25 +204,17 @@ export interface AgentVersionDetail {
204
204
  description: string;
205
205
  };
206
206
  }
207
- /**
208
- * Webhook config nested in integration responses.
209
- * Used by: trigger create (to resolve webhook_config_id)
210
- */
211
- export interface IntegrationWebhookConfig {
207
+ export interface TriggerIntegrationWebhookConfig {
212
208
  id: string;
213
209
  enabled: boolean;
214
210
  events: Record<string, string[]>;
215
211
  }
216
- /**
217
- * Integration entity from the API.
218
- * Used by: trigger create (to resolve webhook_config_id from integration name)
219
- */
220
- export interface Integration {
212
+ export interface TriggerIntegration {
221
213
  id: string;
222
214
  name: string;
223
215
  full_name: string;
224
216
  service?: string | null;
225
- webhook_config: IntegrationWebhookConfig | null;
217
+ webhook_config: TriggerIntegrationWebhookConfig | null;
226
218
  }
227
219
  export type TriggerType = 'webhook' | 'time';
228
220
  import { WEBHOOK_SERVICES, type WebhookService, TIME_TRIGGER_FREQUENCIES, type TimeTriggerFrequency } from './generated-types.js';
@@ -267,7 +259,7 @@ export interface TriggerWebhook {
267
259
  project?: string;
268
260
  jql_filter?: string;
269
261
  } | Record<string, unknown>;
270
- integration: Integration;
262
+ integration: TriggerIntegration;
271
263
  created_at: string;
272
264
  deactivated_at: string | null;
273
265
  disabled_reason: TriggerDisabledReason | null;
@@ -424,6 +416,71 @@ export interface JobStepsResponse {
424
416
  job_id: string;
425
417
  steps: JobStep[];
426
418
  }
419
+ export interface IntegrationOwner {
420
+ id: string;
421
+ name: string;
422
+ }
423
+ export interface IntegrationAuthConfig {
424
+ id: string;
425
+ entity_type: string;
426
+ auth_scheme: 'API_KEY' | 'OAUTH';
427
+ token_header_template?: string;
428
+ install_url?: string;
429
+ token_url?: string;
430
+ client_id?: string;
431
+ scopes?: string[];
432
+ }
433
+ export interface IntegrationProtocolConfig {
434
+ protocol: 'REST';
435
+ base_url: string;
436
+ }
437
+ export interface IntegrationWebhookConfig {
438
+ enabled: boolean;
439
+ events?: string[];
440
+ }
441
+ export interface IntegrationPublishedVersion {
442
+ id: string;
443
+ version_number: string;
444
+ published_at: string;
445
+ }
446
+ export interface Integration {
447
+ id: string;
448
+ entity_type: string;
449
+ created_at: string;
450
+ updated_at: string;
451
+ name: string;
452
+ description: string | null;
453
+ is_public: boolean;
454
+ avatar_url: string | null;
455
+ owner: IntegrationOwner;
456
+ auth_config: IntegrationAuthConfig;
457
+ protocol_config: IntegrationProtocolConfig;
458
+ webhook_config: IntegrationWebhookConfig | null;
459
+ params: unknown[];
460
+ viewer_can_edit: boolean;
461
+ latest_published_version?: IntegrationPublishedVersion | null;
462
+ }
463
+ export type IntegrationListResponse = PaginatedResponse<Integration>;
464
+ export interface IntegrationVersion {
465
+ id: string;
466
+ entity_type: string;
467
+ created_at: string;
468
+ updated_at: string;
469
+ author: {
470
+ id: string;
471
+ name: string;
472
+ };
473
+ validation_status: string;
474
+ published_at: string | null;
475
+ publishing_started_at: string | null;
476
+ version_number: string | null;
477
+ integration?: {
478
+ id: string;
479
+ name: string;
480
+ description: string;
481
+ };
482
+ }
483
+ export type IntegrationVersionListResponse = PaginatedResponse<IntegrationVersion>;
427
484
  /**
428
485
  * Contents of guild.json file in agent directories.
429
486
  */
@@ -50,7 +50,7 @@ export declare function showBetaGuidance(): void;
50
50
  * @param returnLabel - Optional friendly label for return button (e.g., "VSCode")
51
51
  * @returns true if successful, false otherwise
52
52
  */
53
- export declare function login(returnUrl?: string, returnLabel?: string, noBrowser?: boolean): Promise<boolean>;
53
+ export declare function login(returnUrl?: string, returnLabel?: string, nonInteractive?: boolean): Promise<boolean>;
54
54
  /**
55
55
  * Perform logout
56
56
  */
package/dist/lib/auth.js CHANGED
@@ -10,6 +10,7 @@ import { createSpinner } from './progress.js';
10
10
  import { getGuildcoreUrl } from './config.js';
11
11
  import { getIapHeaders } from './iap.js';
12
12
  import { brand, warn } from './colors.js';
13
+ import { isInteractive } from './stdin.js';
13
14
  const SERVICE_NAME = 'guild-cli';
14
15
  /**
15
16
  * Get account name for keyring, keyed by host to support multiple backends
@@ -199,12 +200,12 @@ export function showBetaGuidance() {
199
200
  * @param returnLabel - Optional friendly label for return button (e.g., "VSCode")
200
201
  * @returns true if successful, false otherwise
201
202
  */
202
- export async function login(returnUrl, returnLabel, noBrowser) {
203
+ export async function login(returnUrl, returnLabel, nonInteractive) {
203
204
  const authUrl = getGuildcoreUrl();
204
205
  try {
205
206
  console.log(chalk.bold('\nGuild.ai Authentication'));
206
207
  const deviceCode = await startDeviceFlow(authUrl, returnUrl, returnLabel);
207
- const skipBrowser = noBrowser || process.stdin.isTTY !== true;
208
+ const skipBrowser = nonInteractive || !isInteractive();
208
209
  if (skipBrowser) {
209
210
  console.log(`\nVerification code: ${deviceCode.user_code}`);
210
211
  console.log(`Open to authenticate: ${deviceCode.verification_uri_complete}\n`);
@@ -0,0 +1,15 @@
1
+ import { GuildAPIClient } from './api-client.js';
2
+ /**
3
+ * Resolve a version ID from an integration identifier and optional semver.
4
+ *
5
+ * 1. GET /integrations/<identifier> to resolve the integration
6
+ * 2. GET /integrations/<id>/versions to list versions
7
+ * 3. If semver provided, find matching version_number; otherwise use latest (first)
8
+ */
9
+ export declare function resolveVersionId(client: GuildAPIClient, identifier: string, versionNumber?: string): Promise<string>;
10
+ /**
11
+ * Resolve the latest draft version ID for an integration.
12
+ * Used by the build command where --version is not applicable (drafts have no semver).
13
+ */
14
+ export declare function resolveLatestDraftId(client: GuildAPIClient, identifier: string): Promise<string>;
15
+ //# sourceMappingURL=integration-helpers.d.ts.map
@@ -0,0 +1,38 @@
1
+ // Copyright 2026 Guild.ai
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Resolve a version ID from an integration identifier and optional semver.
5
+ *
6
+ * 1. GET /integrations/<identifier> to resolve the integration
7
+ * 2. GET /integrations/<id>/versions to list versions
8
+ * 3. If semver provided, find matching version_number; otherwise use latest (first)
9
+ */
10
+ export async function resolveVersionId(client, identifier, versionNumber) {
11
+ const integration = await client.get(`/integrations/${identifier}`);
12
+ const versions = await client.get(`/integrations/${integration.id}/versions?limit=100`);
13
+ if (versions.items.length === 0) {
14
+ throw new Error('No versions found for this integration');
15
+ }
16
+ if (versionNumber) {
17
+ const match = versions.items.find((v) => v.version_number === versionNumber);
18
+ if (!match) {
19
+ throw new Error(`Version ${versionNumber} not found`);
20
+ }
21
+ return match.id;
22
+ }
23
+ return versions.items[0].id;
24
+ }
25
+ /**
26
+ * Resolve the latest draft version ID for an integration.
27
+ * Used by the build command where --version is not applicable (drafts have no semver).
28
+ */
29
+ export async function resolveLatestDraftId(client, identifier) {
30
+ const integration = await client.get(`/integrations/${identifier}`);
31
+ const versions = await client.get(`/integrations/${integration.id}/versions?limit=100`);
32
+ const draft = versions.items.find((v) => v.version_number === null);
33
+ if (!draft) {
34
+ throw new Error('No draft version found for this integration');
35
+ }
36
+ return draft.id;
37
+ }
38
+ //# sourceMappingURL=integration-helpers.js.map
@@ -1,10 +1,20 @@
1
1
  import { type Spinner } from './progress.js';
2
- import type { Agent, AgentVersion, Credentials, CredentialsPolicy, Context, JobStep, Pagination, Session, Workspace, WorkspaceAgent, Trigger } from './api-types.js';
2
+ import type { Agent, AgentVersion, Credentials, CredentialsPolicy, Context, Integration, IntegrationVersion, JobStep, Pagination, Session, Workspace, WorkspaceAgent, Trigger } from './api-types.js';
3
3
  /**
4
4
  * Format an agent list as a human-readable table.
5
5
  * Shared by agent list and agent search commands.
6
6
  */
7
7
  export declare function formatAgentTable(agents: Agent[], pagination: Pagination): void;
8
+ /**
9
+ * Format an integration list as a human-readable table.
10
+ * Used by integration list command.
11
+ */
12
+ export declare function formatIntegrationTable(integrations: Integration[], pagination: Pagination): void;
13
+ /**
14
+ * Format an integration version list as a human-readable table.
15
+ * Used by integration version list command.
16
+ */
17
+ export declare function formatIntegrationVersionTable(versions: IntegrationVersion[], pagination: Pagination, integrationName: string): void;
8
18
  /**
9
19
  * Format an agent version list as a human-readable table.
10
20
  * Used by agent versions command.
@@ -81,6 +81,100 @@ export function formatAgentTable(agents, pagination) {
81
81
  console.log(chalk.dim(`\nShowing ${showing} of ${pagination.total_count} agents`));
82
82
  }
83
83
  }
84
+ /**
85
+ * Format an integration list as a human-readable table.
86
+ * Used by integration list command.
87
+ */
88
+ export function formatIntegrationTable(integrations, pagination) {
89
+ if (integrations.length === 0) {
90
+ console.log(chalk.dim('No integrations found'));
91
+ return;
92
+ }
93
+ const table = new Table({
94
+ columns: [
95
+ { name: 'name', title: 'NAME', alignment: 'left' },
96
+ { name: 'description', title: 'DESCRIPTION', alignment: 'left' },
97
+ { name: 'visibility', title: 'VISIBILITY', alignment: 'left' },
98
+ { name: 'owner', title: 'OWNER', alignment: 'left', color: 'cyan' },
99
+ { name: 'updated', title: 'UPDATED', alignment: 'left' },
100
+ ],
101
+ });
102
+ integrations.forEach((integration) => {
103
+ table.addRow({
104
+ name: integration.name,
105
+ description: truncate(integration.description || '', 40),
106
+ visibility: integration.is_public ? 'public' : 'internal',
107
+ owner: integration.owner?.name || '',
108
+ updated: integration.updated_at ? formatRelativeTime(integration.updated_at) : '',
109
+ });
110
+ });
111
+ table.printTable();
112
+ const showing = Math.min(pagination.limit, integrations.length);
113
+ if (pagination.has_more) {
114
+ const nextOffset = pagination.offset + pagination.limit;
115
+ console.log(`\nShowing ${showing} of ${pagination.total_count} integrations. ` +
116
+ chalk.dim(`Use --offset ${nextOffset} to see more.`));
117
+ }
118
+ else if (pagination.total_count > showing) {
119
+ console.log(chalk.dim(`\nShowing ${showing} of ${pagination.total_count} integrations`));
120
+ }
121
+ }
122
+ /**
123
+ * Format an integration version list as a human-readable table.
124
+ * Used by integration version list command.
125
+ */
126
+ export function formatIntegrationVersionTable(versions, pagination, integrationName) {
127
+ if (versions.length === 0) {
128
+ console.log(chalk.dim('No versions found'));
129
+ return;
130
+ }
131
+ console.log(chalk.bold(`Versions for ${integrationName}`));
132
+ console.log();
133
+ const table = new Table({
134
+ columns: [
135
+ { name: 'id', title: 'ID', alignment: 'left' },
136
+ { name: 'version', title: 'VERSION', alignment: 'left' },
137
+ { name: 'author', title: 'AUTHOR', alignment: 'left', color: 'cyan' },
138
+ { name: 'status', title: 'STATUS', alignment: 'left' },
139
+ { name: 'published', title: 'PUBLISHED', alignment: 'left' },
140
+ { name: 'created', title: 'CREATED', alignment: 'left' },
141
+ ],
142
+ });
143
+ versions.forEach((v) => {
144
+ let statusLabel;
145
+ switch (v.validation_status) {
146
+ case 'PASSED':
147
+ statusLabel = chalk.green('Valid');
148
+ break;
149
+ case 'FAILED':
150
+ statusLabel = chalk.red('Failed');
151
+ break;
152
+ case 'RUNNING':
153
+ statusLabel = chalk.yellow('Building');
154
+ break;
155
+ default:
156
+ statusLabel = chalk.dim('—');
157
+ }
158
+ table.addRow({
159
+ id: v.id,
160
+ version: v.version_number || 'Draft',
161
+ author: v.author?.name || '',
162
+ status: statusLabel,
163
+ published: v.published_at ? formatRelativeTime(v.published_at) : chalk.dim('—'),
164
+ created: v.created_at ? formatRelativeTime(v.created_at) : '',
165
+ });
166
+ });
167
+ table.printTable();
168
+ const showing = Math.min(pagination.limit, versions.length);
169
+ if (pagination.has_more) {
170
+ const nextOffset = pagination.offset + pagination.limit;
171
+ console.log(`\nShowing ${showing} of ${pagination.total_count} versions. ` +
172
+ chalk.dim(`Use --offset ${nextOffset} to see more.`));
173
+ }
174
+ else if (pagination.total_count > showing) {
175
+ console.log(chalk.dim(`\nShowing ${showing} of ${pagination.total_count} versions`));
176
+ }
177
+ }
84
178
  /**
85
179
  * Format an agent version list as a human-readable table.
86
180
  * Used by agent versions command.
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Shared event/task fetching for session polling.
3
+ *
4
+ * Consolidates the duplicate response parsing that was scattered across
5
+ * chat.tsx, session-polling.ts, and session-resume.ts.
6
+ */
7
+ import { GuildAPIClient } from './api-client.js';
8
+ import { SessionEvent, SessionTask } from './session-events.js';
9
+ interface FetchOptions {
10
+ /** Only return events after this event ID (server-side cursor). */
11
+ fromId?: string;
12
+ /** Maximum number of events to return. Default 1000. */
13
+ limit?: number;
14
+ }
15
+ /**
16
+ * Fetch session events from the API.
17
+ *
18
+ * Uses from_id cursor so callers only fetch new events
19
+ * instead of re-fetching everything on every poll cycle.
20
+ */
21
+ export declare function fetchEvents(client: GuildAPIClient, sessionId: string, options?: FetchOptions): Promise<SessionEvent[]>;
22
+ /**
23
+ * Fetch session tasks from the API.
24
+ */
25
+ export declare function fetchTasks(client: GuildAPIClient, sessionId: string): Promise<SessionTask[]>;
26
+ export {};
27
+ //# sourceMappingURL=session-events-fetch.d.ts.map
@@ -0,0 +1,25 @@
1
+ // Copyright 2026 Guild.ai
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Fetch session events from the API.
5
+ *
6
+ * Uses from_id cursor so callers only fetch new events
7
+ * instead of re-fetching everything on every poll cycle.
8
+ */
9
+ export async function fetchEvents(client, sessionId, options = {}) {
10
+ const { fromId, limit = 1000 } = options;
11
+ let url = `/sessions/${sessionId}/events?limit=${limit}`;
12
+ if (fromId) {
13
+ url += `&from_id=${fromId}`;
14
+ }
15
+ const response = await client.get(url);
16
+ return response?.items || [];
17
+ }
18
+ /**
19
+ * Fetch session tasks from the API.
20
+ */
21
+ export async function fetchTasks(client, sessionId) {
22
+ const response = await client.get(`/sessions/${sessionId}/tasks?limit=1000`);
23
+ return response?.items || [];
24
+ }
25
+ //# sourceMappingURL=session-events-fetch.js.map
@@ -1,6 +1,11 @@
1
1
  import { GuildAPIClient } from './api-client.js';
2
+ export interface PollResult {
3
+ response: string | null;
4
+ /** Last event ID seen — pass as `afterEventId` on next call. */
5
+ lastEventId: string | undefined;
6
+ }
2
7
  /**
3
- * Poll for agent response after a certain event count.
8
+ * Poll for agent response using from_id cursor.
4
9
  *
5
10
  * Handles both multi-turn agents (which emit agent_notification_message)
6
11
  * and one-shot agents (which may only emit runtime_done with output content).
@@ -10,5 +15,5 @@ import { GuildAPIClient } from './api-client.js';
10
15
  * 2. runtime_done from agent tasks — fallback for one-shot agents
11
16
  * 3. runtime_error from agent tasks — fail fast
12
17
  */
13
- export declare function pollForResponse(client: GuildAPIClient, sessionId: string, afterEventCount: number, maxWaitTime?: number): Promise<string | null>;
18
+ export declare function pollForResponse(client: GuildAPIClient, sessionId: string, afterEventId: string | undefined, maxWaitTime?: number): Promise<PollResult>;
14
19
  //# sourceMappingURL=session-polling.d.ts.map
@@ -1,8 +1,9 @@
1
1
  // Copyright 2026 Guild.ai
2
2
  // SPDX-License-Identifier: Apache-2.0
3
- import { isDebugMode } from './errors.js';
3
+ import { debug, isDebugMode } from './errors.js';
4
+ import { fetchEvents } from './session-events-fetch.js';
4
5
  /**
5
- * Poll for agent response after a certain event count.
6
+ * Poll for agent response using from_id cursor.
6
7
  *
7
8
  * Handles both multi-turn agents (which emit agent_notification_message)
8
9
  * and one-shot agents (which may only emit runtime_done with output content).
@@ -12,16 +13,16 @@ import { isDebugMode } from './errors.js';
12
13
  * 2. runtime_done from agent tasks — fallback for one-shot agents
13
14
  * 3. runtime_error from agent tasks — fail fast
14
15
  */
15
- export async function pollForResponse(client, sessionId, afterEventCount, maxWaitTime = 60000) {
16
+ export async function pollForResponse(client, sessionId, afterEventId, maxWaitTime = 60000) {
16
17
  const startTime = Date.now();
18
+ let fromId = afterEventId;
17
19
  while (Date.now() - startTime < maxWaitTime) {
18
- const response = await client.get(`/sessions/${sessionId}/events?limit=1000`);
19
- const events = response?.items || [];
20
+ const events = await fetchEvents(client, sessionId, { fromId });
20
21
  let lastAgentRuntimeDone = null;
21
- for (let i = afterEventCount; i < events.length; i++) {
22
- const event = events[i];
22
+ for (const event of events) {
23
+ debug(`pollForResponse event: ${event.type}`);
23
24
  if (event.type === 'agent_notification_message') {
24
- return event.content.data;
25
+ return { response: event.content.data, lastEventId: event.id };
25
26
  }
26
27
  if (event.type === 'runtime_done' &&
27
28
  event.content !== undefined &&
@@ -30,17 +31,23 @@ export async function pollForResponse(client, sessionId, afterEventCount, maxWai
30
31
  lastAgentRuntimeDone = JSON.stringify(event.content);
31
32
  }
32
33
  if (event.type === 'runtime_error' && event.task && 'agent' in event.task) {
33
- return JSON.stringify({ error: event.content });
34
+ return {
35
+ response: JSON.stringify({ error: event.content }),
36
+ lastEventId: event.id,
37
+ };
34
38
  }
35
39
  if (event.type === 'agent_console' && isDebugMode()) {
36
40
  process.stderr.write(`[console.${event.level}] ${event.content}\n`);
37
41
  }
38
42
  }
43
+ if (events.length > 0) {
44
+ fromId = events[events.length - 1].id;
45
+ }
39
46
  if (lastAgentRuntimeDone !== null) {
40
- return lastAgentRuntimeDone;
47
+ return { response: lastAgentRuntimeDone, lastEventId: fromId };
41
48
  }
42
- await new Promise((resolve) => setTimeout(resolve, 500));
49
+ await new Promise((resolve) => setTimeout(resolve, 2000));
43
50
  }
44
- return null;
51
+ return { response: null, lastEventId: fromId };
45
52
  }
46
53
  //# sourceMappingURL=session-polling.js.map