@guildai/cli 0.7.1 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,7 +14,6 @@ import { GuildAPIClient } from '../../lib/api-client.js';
14
14
  import { isQuietMode, getOutputMode } from '../../lib/output-mode.js';
15
15
  import { handleAxiosError, ErrorCodes } from '../../lib/errors.js';
16
16
  import { format } from '../../lib/progress.js';
17
- import { showBetaGuidance } from '../../lib/auth.js';
18
17
  import { suppressScrollbackClear } from '../../lib/alternate-screen.js';
19
18
  import * as readline from 'readline';
20
19
  import { pollForResponse } from '../../lib/session-polling.js';
@@ -245,7 +244,6 @@ export function createAgentChatCommand() {
245
244
  if (formattedError.code === ErrorCodes.AUTH_REQUIRED ||
246
245
  formattedError.code === ErrorCodes.AUTH_TOKEN_INVALID) {
247
246
  format.error('Not authenticated. Run: guild auth login');
248
- showBetaGuidance();
249
247
  process.exit(1);
250
248
  }
251
249
  console.error(`Error: ${formattedError.details}`);
@@ -210,7 +210,7 @@ export function createAgentInitCommand() {
210
210
  .then(() => true)
211
211
  .catch(() => false);
212
212
  if (!gitExists) {
213
- await runGit(['init'], { cwd: targetDir });
213
+ await runGit(['init', '-b', 'main'], { cwd: targetDir });
214
214
  steps.succeed('Initialize git repository');
215
215
  }
216
216
  else {
@@ -323,15 +323,17 @@ export function createAgentInitCommand() {
323
323
  const gitignorePath = path.join(targetDir, '.gitignore');
324
324
  try {
325
325
  const gitignoreContent = await fs.readFile(gitignorePath, 'utf-8');
326
- let additions = '';
327
- if (!gitignoreContent.includes('guild.json')) {
328
- additions += '\nguild.json\n';
326
+ const existingLines = gitignoreContent.split('\n').map((l) => l.trim());
327
+ const lines = [];
328
+ if (!existingLines.includes('guild.json')) {
329
+ lines.push('guild.json');
329
330
  }
330
- if (!gitignoreContent.includes('.guild/cache/')) {
331
- additions += '.guild/cache/\n';
331
+ if (!existingLines.includes('.guild/cache/')) {
332
+ lines.push('.guild/cache/');
332
333
  }
333
- if (additions) {
334
- await fs.appendFile(gitignorePath, additions);
334
+ if (lines.length > 0) {
335
+ const prefix = gitignoreContent.endsWith('\n') ? '' : '\n';
336
+ await fs.appendFile(gitignorePath, prefix + lines.join('\n') + '\n');
335
337
  }
336
338
  }
337
339
  catch {
@@ -17,13 +17,20 @@ export function createAgentListCommand() {
17
17
  cmd
18
18
  .description('List agents')
19
19
  .option('--search <query>', 'Search agents by name or description')
20
- .option('--sort <field>', 'Sort by: updated, newest, name, popular (default: updated)', 'updated')
20
+ .option('--sort <field>', 'Sort by: name, updated, newest, popular (default: name)', 'name')
21
21
  .option('--published', 'Only show published agents')
22
+ .option('--archived', 'Show only archived agents')
23
+ .option('--all', 'Show all agents including archived')
24
+ .option('--owner <name>', 'Filter by owner (user or org name). Without this flag, lists your own agents')
22
25
  .option('--workspace <id>', 'Filter agents by workspace ID or name')
23
26
  .option('--limit <number>', 'Number of results to return', '20')
24
27
  .option('--offset <number>', 'Offset for pagination', '0')
25
28
  .action(async (options) => {
26
29
  const output = createOutputWriter();
30
+ if (options.archived && options.all) {
31
+ output.error('--archived and --all are mutually exclusive', 'Use --archived to show only archived agents, or --all to show all agents including archived');
32
+ process.exit(1);
33
+ }
27
34
  try {
28
35
  const token = await getAuthToken();
29
36
  if (!token) {
@@ -40,25 +47,57 @@ export function createAgentListCommand() {
40
47
  if (options.published) {
41
48
  params.append('published_only', 'true');
42
49
  }
43
- if (options.workspace) {
44
- params.append('for_workspace', options.workspace);
45
- }
46
50
  const sortField = SORT_MAP[options.sort];
47
51
  if (sortField) {
48
52
  params.append('sort_by', sortField);
49
53
  }
50
- const response = await client.get(`/agents?${params.toString()}`);
54
+ // Determine the right endpoint:
55
+ // - --workspace → GET /agents?for_workspace=... (global, workspace-scoped)
56
+ // - --owner → GET /users/{owner}/agents or /organizations/{owner}/agents
57
+ // - default → GET /users/{me}/agents (same as web)
58
+ let endpoint;
59
+ const showArchived = options.all || options.archived;
60
+ if (options.workspace) {
61
+ // Workspace filter uses the global endpoint
62
+ params.append('for_workspace', options.workspace);
63
+ endpoint = `/agents?${params.toString()}`;
64
+ }
65
+ else {
66
+ // Use scoped endpoint (matches web frontend pattern)
67
+ if (options.archived) {
68
+ params.append('include_archived', 'true');
69
+ }
70
+ else if (options.all) {
71
+ params.append('include_archived', 'true');
72
+ }
73
+ const ownerName = options.owner ?? (await client.get('/me')).name;
74
+ endpoint = `/users/${encodeURIComponent(ownerName)}/agents?${params.toString()}`;
75
+ }
76
+ const response = await client.get(endpoint);
77
+ // When --archived is used, filter to only archived agents client-side
78
+ // (the backend only supports include_archived, not archived_only)
79
+ if (options.archived) {
80
+ response.items = response.items.filter((a) => a.is_archived);
81
+ }
51
82
  if (getOutputMode() === 'json') {
52
83
  output.data(response);
53
84
  }
54
85
  else {
55
- formatAgentTable(response.items, response.pagination);
86
+ formatAgentTable(response.items, response.pagination, showArchived);
56
87
  }
57
88
  }
58
89
  catch (error) {
59
90
  const formattedError = handleAxiosError(error);
60
91
  if (formattedError.code === ErrorCodes.NOT_FOUND) {
61
- output.error('Workspace not found');
92
+ if (options.workspace) {
93
+ output.error('Workspace not found');
94
+ }
95
+ else if (options.owner) {
96
+ output.error(`Owner not found: ${options.owner}`);
97
+ }
98
+ else {
99
+ output.error('Not found');
100
+ }
62
101
  process.exit(1);
63
102
  }
64
103
  output.error(`Failed to list agents: ${formattedError.details}`);
@@ -5,10 +5,12 @@ import chalk from 'chalk';
5
5
  import { GuildAPIClient } from '../../lib/api-client.js';
6
6
  import { handleAxiosError, ErrorCodes } from '../../lib/errors.js';
7
7
  import { getOutputMode } from '../../lib/output-mode.js';
8
+ import { createOutputWriter } from '../../lib/output.js';
8
9
  import { loadGlobalConfig } from '../../lib/guild-config.js';
9
10
  export function createAgentOwnersCommand() {
10
11
  const cmd = new Command('owners');
11
12
  cmd.description('List accounts that can own agents').action(async () => {
13
+ const output = createOutputWriter();
12
14
  try {
13
15
  const mode = getOutputMode();
14
16
  const client = new GuildAPIClient();
@@ -33,7 +35,7 @@ export function createAgentOwnersCommand() {
33
35
  })),
34
36
  ];
35
37
  if (mode === 'json') {
36
- console.log(JSON.stringify({ owners }, null, 2));
38
+ output.data({ owners });
37
39
  return;
38
40
  }
39
41
  // Table display
@@ -56,18 +58,14 @@ export function createAgentOwnersCommand() {
56
58
  catch (error) {
57
59
  const formattedError = handleAxiosError(error);
58
60
  if (formattedError.code === ErrorCodes.AUTH_REQUIRED) {
59
- console.error('Not authenticated. Please log in first.');
60
- console.error('');
61
- console.error('Run: guild auth login');
61
+ output.error('Not authenticated. Please log in first.', 'Run: guild auth login');
62
62
  process.exit(1);
63
63
  }
64
64
  if (formattedError.code === ErrorCodes.CONN_REFUSED) {
65
- console.error('Cannot connect to Guild servers.');
66
- console.error('');
67
- console.error('Please check your connection and try again.');
65
+ output.error('Cannot connect to Guild servers.', 'Please check your connection and try again.');
68
66
  process.exit(1);
69
67
  }
70
- console.error(`Failed to list owners: ${formattedError.details}`);
68
+ output.error(`Failed to list owners: ${formattedError.details}`);
71
69
  process.exit(1);
72
70
  }
73
71
  });
@@ -4,6 +4,7 @@ import { Command } from 'commander';
4
4
  import { render } from 'ink';
5
5
  import React from 'react';
6
6
  import { readFileSync } from 'fs';
7
+ import { access } from 'fs/promises';
7
8
  import path from 'path';
8
9
  import { fileURLToPath } from 'url';
9
10
  import open from 'open';
@@ -11,7 +12,6 @@ import { hyperlink } from '../../lib/colors.js';
11
12
  import { GuildAPIClient } from '../../lib/api-client.js';
12
13
  import { handleAxiosError, ErrorCodes } from '../../lib/errors.js';
13
14
  import { format } from '../../lib/progress.js';
14
- import { showBetaGuidance } from '../../lib/auth.js';
15
15
  import * as readline from 'readline';
16
16
  import { parseEventFilter } from '../../lib/event-filter.js';
17
17
  import { isQuietMode, getOutputMode } from '../../lib/output-mode.js';
@@ -19,7 +19,7 @@ import { pollForResponse, pollForResponseWithEvents, } from '../../lib/session-p
19
19
  import { readStdinAsJSON, ensureInteractiveStdin } from '../../lib/stdin.js';
20
20
  import { loadLocalConfig, getWorkspaceId } from '../../lib/guild-config.js';
21
21
  import { GitError, formatGitError } from '../../lib/git.js';
22
- import { readAgentFiles, buildEphemeralVersion, BuildTimeoutError, BuildFailedError, } from '../../lib/agent-helpers.js';
22
+ import { readAgentFiles, buildEphemeralVersion, buildBundledVersion, BundleNotFoundError, BuildTimeoutError, BuildFailedError, } from '../../lib/agent-helpers.js';
23
23
  import { fetchSession, fetchSessionEvents } from '../../lib/session-resume.js';
24
24
  import { ChatApp, ensureAuthenticated } from '../chat.js';
25
25
  import { suppressScrollbackClear } from '../../lib/alternate-screen.js';
@@ -37,6 +37,7 @@ export function createAgentTestCommand() {
37
37
  .option('--resume <session-id>', 'Resume an existing test session')
38
38
  .option('--open', 'Open session in web dashboard')
39
39
  .option('--events <types>', 'Event types to stream (default: user). Shorthands: none, user, system, all, or comma-separated type names')
40
+ .option('--bundle <file>', 'Path to a pre-built gzip+base64 bundle file')
40
41
  .option('--no-cache', 'Skip ephemeral build cache (force a fresh build)')
41
42
  .action(async (options) => {
42
43
  const cwd = process.cwd();
@@ -99,6 +100,21 @@ export function createAgentTestCommand() {
99
100
  process.exit(1);
100
101
  }
101
102
  }
103
+ // If using bundle, verify the file exists early (before auth/session creation)
104
+ // so we fail fast without needing auth if the path is wrong.
105
+ if (options.bundle) {
106
+ try {
107
+ await access(options.bundle);
108
+ }
109
+ catch {
110
+ console.error(`Error: Bundle file not found: ${options.bundle}`);
111
+ console.error('');
112
+ console.error('Ensure the bundle file exists and the path is correct:');
113
+ console.error(' npm run build');
114
+ console.error(' guild agent test --bundle agent.js.gz');
115
+ process.exit(1);
116
+ }
117
+ }
102
118
  // Determine workspace (priority: flag > local config > global config)
103
119
  let workspaceId = options.workspace;
104
120
  if (!workspaceId) {
@@ -139,6 +155,11 @@ export function createAgentTestCommand() {
139
155
  }
140
156
  version = match;
141
157
  }
158
+ else if (options.bundle) {
159
+ // Pre-built bundle: skip server-side compilation entirely.
160
+ const result = await buildBundledVersion(client, guildConfig.agent_id, options.bundle, cwd, '[Test] Pre-built bundle');
161
+ version = result.version;
162
+ }
142
163
  else {
143
164
  // Default: build ephemeral version from working directory,
144
165
  // reusing the last build if files haven't changed.
@@ -150,6 +171,14 @@ export function createAgentTestCommand() {
150
171
  }
151
172
  }
152
173
  catch (error) {
174
+ if (error instanceof BundleNotFoundError) {
175
+ console.error(`Error: ${error.message}`);
176
+ console.error('');
177
+ console.error('Ensure the bundle file exists and the path is correct:');
178
+ console.error(' npm run build');
179
+ console.error(' guild agent test --bundle agent.js.gz');
180
+ process.exit(1);
181
+ }
153
182
  if (error instanceof BuildTimeoutError) {
154
183
  console.error('Error: Build did not complete');
155
184
  console.error('');
@@ -216,9 +245,11 @@ export function createAgentTestCommand() {
216
245
  console.log(`✓ Agent: ${guildConfig.name} (${guildConfig.agent_id})`);
217
246
  const versionDisplay = options.agentVersion
218
247
  ? options.agentVersion
219
- : ephemeralCached
220
- ? 'ephemeral (cached, no changes)'
221
- : 'ephemeral (working directory)';
248
+ : options.bundle
249
+ ? `bundle (${path.basename(options.bundle)})`
250
+ : ephemeralCached
251
+ ? 'ephemeral (cached, no changes)'
252
+ : 'ephemeral (working directory)';
222
253
  console.log(`✓ Version: ${versionDisplay}`);
223
254
  console.log(`✓ Workspace: ${workspaceId}`);
224
255
  const sessionLink = session.session_url
@@ -385,7 +416,6 @@ export function createAgentTestCommand() {
385
416
  if (formattedError.code === ErrorCodes.AUTH_REQUIRED ||
386
417
  formattedError.code === ErrorCodes.AUTH_TOKEN_INVALID) {
387
418
  format.error('Not authenticated. Run: guild auth login');
388
- showBetaGuidance();
389
419
  process.exit(1);
390
420
  }
391
421
  console.error(`Error: ${formattedError.details}`);
@@ -3,7 +3,7 @@
3
3
  import React, { useState, useEffect, useRef } from 'react';
4
4
  import { Box, Text, Static, render, useInput, useApp } from 'ink';
5
5
  import { Command } from 'commander';
6
- import { getAuthToken, showBetaGuidance } from '../lib/auth.js';
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';
9
9
  import { createSpinner, format } from '../lib/progress.js';
@@ -213,7 +213,6 @@ export function ChatApp({ initialPrompt, version, workspaceId, versionId, agentN
213
213
  if (formattedError.code === ErrorCodes.AUTH_REQUIRED ||
214
214
  formattedError.code === ErrorCodes.AUTH_TOKEN_INVALID) {
215
215
  format.error('Not authenticated. Run: guild auth login');
216
- showBetaGuidance();
217
216
  }
218
217
  else if (details.includes('workspace')) {
219
218
  format.error('Workspace not found. Run: guild workspace select');
@@ -963,7 +962,6 @@ export async function ensureAuthenticated() {
963
962
  const token = await getAuthToken();
964
963
  if (!token) {
965
964
  format.error('Not authenticated. Run: guild auth login');
966
- showBetaGuidance();
967
965
  process.exit(1);
968
966
  }
969
967
  // Validate token against the server to catch expired/invalid tokens
@@ -977,7 +975,6 @@ export async function ensureAuthenticated() {
977
975
  const { clearAuthToken } = await import('../lib/auth.js');
978
976
  await clearAuthToken();
979
977
  format.error('Session expired. Run: guild auth login');
980
- showBetaGuidance();
981
978
  process.exit(1);
982
979
  }
983
980
  return token;
@@ -1028,9 +1025,9 @@ export function createChatCommand() {
1028
1025
  cmd
1029
1026
  .description('Chat with an agent (default: Guild assistant)')
1030
1027
  .argument('[prompt...]', 'Optional initial prompt (multiple words)')
1031
- .option('--agent <identifier>', 'Agent ID or full name (default: assistant)')
1028
+ .option('--agent <identifier>', 'Agent ID or full name, e.g., foo~bar (default: assistant)')
1032
1029
  .option('--once', 'One-shot mode: send message, wait for response, exit (non-interactive)')
1033
- .option('--mode <format>', 'Input/output format: json or jsonl (default: human-readable)')
1030
+ .option('--mode <format>', 'Machine-readable output format: json or jsonl')
1034
1031
  .option('--workspace <identifier>', 'Workspace ID or full name (e.g., owner/workspace-name)')
1035
1032
  .option('--no-splash', 'Skip the splash screen animation')
1036
1033
  .option('--resume <session-id>', 'Resume an existing session')
@@ -1144,10 +1141,6 @@ export function createChatCommand() {
1144
1141
  console.error(` • ${suggestion}`);
1145
1142
  });
1146
1143
  }
1147
- if (formattedError.code === ErrorCodes.AUTH_REQUIRED ||
1148
- formattedError.code === ErrorCodes.AUTH_TOKEN_INVALID) {
1149
- showBetaGuidance();
1150
- }
1151
1144
  process.exit(1);
1152
1145
  }
1153
1146
  }
@@ -13,7 +13,7 @@ export function createSessionCreateCommand() {
13
13
  .option('--workspace <id>', 'Workspace ID or name')
14
14
  .option('--type <type>', 'Session type: chat or agent_test', 'chat')
15
15
  .option('--prompt <text>', 'Initial prompt (required for chat sessions)')
16
- .option('--agent <identifier>', 'Agent identifier, e.g., owner/agent-name')
16
+ .option('--agent <identifier>', 'Agent identifier, e.g., owner~agent-name')
17
17
  .action(async (options) => {
18
18
  const output = createOutputWriter();
19
19
  try {
@@ -4,7 +4,8 @@ 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 { createOutputWriter } from '../../lib/output.js';
7
+ import { getOutputMode } from '../../lib/output-mode.js';
8
+ import { createOutputWriter, formatTaskTable } from '../../lib/output.js';
8
9
  export function createSessionTasksCommand() {
9
10
  const cmd = new Command('tasks');
10
11
  cmd
@@ -25,7 +26,12 @@ export function createSessionTasksCommand() {
25
26
  params.append('limit', options.limit);
26
27
  params.append('offset', options.offset);
27
28
  const response = await client.get(`/sessions/${sessionId}/tasks?${params.toString()}`);
28
- output.data(response);
29
+ if (getOutputMode() === 'json') {
30
+ console.log(JSON.stringify(response, null, 2));
31
+ }
32
+ else {
33
+ formatTaskTable(response.items, response.pagination);
34
+ }
29
35
  }
30
36
  catch (error) {
31
37
  const formattedError = handleAxiosError(error);
@@ -13,18 +13,20 @@ export function createTriggerCreateCommand() {
13
13
  .description('Create a new trigger')
14
14
  .option('--workspace <id>', 'Workspace ID or name')
15
15
  .requiredOption('--type <type>', 'Trigger type: webhook or time')
16
- .requiredOption('--agent <identifier>', 'Agent identifier (e.g., owner/agent-name)')
16
+ .requiredOption('--agent <identifier>', 'Agent identifier (e.g., owner~agent-name)')
17
17
  // Webhook options
18
18
  .option('--integration <name>', 'Integration name for webhook triggers (e.g., github, slack, jira)')
19
19
  .option('--event <event>', 'Event type (e.g., app_mention, issues)')
20
20
  .option('--action <action>', 'Event action (e.g., opened, created)')
21
21
  .option('--service-config <json>', 'Service-specific config as JSON')
22
22
  // Time options
23
- .option('--frequency <freq>', 'Time trigger frequency: HOURLY, DAILY, WEEKLY, MONTHLY')
23
+ .option('--frequency <freq>', 'Time trigger frequency: HOURLY, DAILY, WEEKLY, MONTHLY, CRON')
24
24
  .option('--time <time>', 'Time of day in HH:MM format')
25
25
  .option('--days-of-week <days>', 'Days of week (comma-separated: MONDAY,TUESDAY,...)')
26
26
  .option('--days-of-month <days>', 'Days of month (comma-separated: 1,15,-1)')
27
27
  .option('--minutes-of-hour <minutes>', 'Minutes of hour, comma-separated (0-59)')
28
+ .option('--cron-expression <expression>', '5-field cron expression (required with --frequency CRON)')
29
+ .option('--cron-timezone <timezone>', 'IANA timezone for --cron-expression (default: UTC)')
28
30
  .option('--input <json>', 'Agent input as JSON object')
29
31
  .action(async (options) => {
30
32
  const output = createOutputWriter();
@@ -41,7 +43,13 @@ export function createTriggerCreateCommand() {
41
43
  process.exit(1);
42
44
  }
43
45
  if (triggerType === 'time' && !options.frequency) {
44
- output.error(`--frequency is required for time triggers. Valid: ${TIME_TRIGGER_FREQUENCIES.join(', ')}`);
46
+ output.error(`--frequency is required for time triggers. Valid frequencies: ${TIME_TRIGGER_FREQUENCIES.join(', ')}`);
47
+ process.exit(1);
48
+ }
49
+ if (triggerType === 'time' &&
50
+ options.frequency?.toUpperCase() === 'CRON' &&
51
+ !options.cronExpression) {
52
+ output.error('--cron-expression is required when --frequency is CRON');
45
53
  process.exit(1);
46
54
  }
47
55
  const token = await getAuthToken();
@@ -116,12 +124,6 @@ export function createTriggerCreateCommand() {
116
124
  }
117
125
  else {
118
126
  // Time trigger
119
- const frequency = options.frequency.toUpperCase();
120
- if (!TIME_TRIGGER_FREQUENCIES.includes(frequency)) {
121
- output.error(`Invalid frequency "${options.frequency}". Valid: ${TIME_TRIGGER_FREQUENCIES.join(', ')}`);
122
- process.exit(1);
123
- }
124
- // Parse agent input
125
127
  let agentInput = {};
126
128
  if (options.input) {
127
129
  try {
@@ -132,24 +134,46 @@ export function createTriggerCreateCommand() {
132
134
  process.exit(1);
133
135
  }
134
136
  }
137
+ const frequency = options.frequency.toUpperCase();
138
+ if (!TIME_TRIGGER_FREQUENCIES.includes(frequency)) {
139
+ output.error(`Invalid frequency "${options.frequency}". Valid: ${TIME_TRIGGER_FREQUENCIES.join(', ')}`);
140
+ process.exit(1);
141
+ }
142
+ // Validate options match the frequency
143
+ const frequencyOptions = {
144
+ HOURLY: new Set(['minutesOfHour']),
145
+ DAILY: new Set(['time']),
146
+ WEEKLY: new Set(['time', 'daysOfWeek']),
147
+ MONTHLY: new Set(['time', 'daysOfMonth']),
148
+ CRON: new Set(['cronExpression', 'cronTimezone']),
149
+ };
150
+ const allOptions = new Set(Object.values(frequencyOptions).flatMap((s) => [...s]));
151
+ const allowed = frequencyOptions[frequency] ?? new Set();
152
+ const invalid = [...allOptions]
153
+ .filter((opt) => !allowed.has(opt) && options[opt])
154
+ .map((opt) => '--' + opt.replace(/[A-Z]/g, (c) => '-' + c.toLowerCase()));
155
+ if (invalid.length > 0) {
156
+ output.error(`${invalid.join(', ')} cannot be used with ${frequency} frequency`);
157
+ process.exit(1);
158
+ }
135
159
  body = {
136
160
  type: 'time',
137
161
  frequency: frequency,
138
162
  workspace_agent_id: workspaceAgent.id,
139
163
  agent_input: agentInput,
140
164
  };
141
- if (options.time) {
165
+ if (options.time)
142
166
  body.time_of_day = options.time;
143
- }
144
- if (options.daysOfWeek) {
167
+ if (options.daysOfWeek)
145
168
  body.days_of_week = options.daysOfWeek;
146
- }
147
- if (options.daysOfMonth) {
169
+ if (options.daysOfMonth)
148
170
  body.days_of_month = options.daysOfMonth;
149
- }
150
- if (options.minutesOfHour) {
171
+ if (options.minutesOfHour)
151
172
  body.minutes_of_hour = options.minutesOfHour;
152
- }
173
+ if (options.cronExpression)
174
+ body.cron_expression = options.cronExpression;
175
+ if (options.cronTimezone)
176
+ body.cron_timezone = options.cronTimezone;
153
177
  }
154
178
  const response = await client.post(`/workspaces/${workspaceId}/triggers`, body);
155
179
  output.data(response);
@@ -16,11 +16,13 @@ export function createTriggerUpdateCommand() {
16
16
  .option('--action <action>', 'Event action (for webhook triggers)')
17
17
  .option('--service-config <json>', 'Service-specific config as JSON (for webhook triggers)')
18
18
  // Time options
19
- .option('--frequency <freq>', 'Frequency: HOURLY, DAILY, WEEKLY, MONTHLY (for time triggers)')
19
+ .option('--frequency <freq>', 'Frequency: HOURLY, DAILY, WEEKLY, MONTHLY, CRON (for time triggers)')
20
20
  .option('--time <time>', 'Time of day in HH:MM format (for time triggers)')
21
21
  .option('--days-of-week <days>', 'Days of week, comma-separated (for time triggers)')
22
22
  .option('--days-of-month <days>', 'Days of month, comma-separated (for time triggers)')
23
23
  .option('--minutes-of-hour <minutes>', 'Minutes of hour, comma-separated (0-59, for HOURLY triggers)')
24
+ .option('--cron-expression <expression>', '5-field cron expression (for CRON triggers)')
25
+ .option('--cron-timezone <timezone>', 'IANA timezone for --cron-expression (default: UTC)')
24
26
  .option('--input <json>', 'Agent input as JSON object (for time triggers)')
25
27
  .action(async (triggerId, options) => {
26
28
  const output = createOutputWriter();
@@ -50,26 +52,47 @@ export function createTriggerUpdateCommand() {
50
52
  }
51
53
  }
52
54
  // Time options
55
+ let frequency;
53
56
  if (options.frequency !== undefined) {
54
- const frequency = options.frequency.toUpperCase();
57
+ frequency = options.frequency.toUpperCase();
55
58
  if (!TIME_TRIGGER_FREQUENCIES.includes(frequency)) {
56
59
  output.error(`Invalid frequency "${options.frequency}". Valid: ${TIME_TRIGGER_FREQUENCIES.join(', ')}`);
57
60
  process.exit(1);
58
61
  }
59
62
  body.frequency = frequency;
60
63
  }
61
- if (options.time !== undefined) {
62
- body.time_of_day = options.time;
64
+ // Validate options match the frequency (when frequency is being set)
65
+ if (frequency) {
66
+ const frequencyOptions = {
67
+ HOURLY: new Set(['minutesOfHour']),
68
+ DAILY: new Set(['time']),
69
+ WEEKLY: new Set(['time', 'daysOfWeek']),
70
+ MONTHLY: new Set(['time', 'daysOfMonth']),
71
+ CRON: new Set(['cronExpression', 'cronTimezone']),
72
+ };
73
+ const allOptions = new Set(Object.values(frequencyOptions).flatMap((s) => [...s]));
74
+ const allowed = frequencyOptions[frequency] ?? new Set();
75
+ const invalid = [...allOptions]
76
+ .filter((opt) => !allowed.has(opt) &&
77
+ options[opt] !== undefined)
78
+ .map((opt) => '--' + opt.replace(/[A-Z]/g, (c) => '-' + c.toLowerCase()));
79
+ if (invalid.length > 0) {
80
+ output.error(`${invalid.join(', ')} cannot be used with ${frequency} frequency`);
81
+ process.exit(1);
82
+ }
63
83
  }
64
- if (options.daysOfWeek !== undefined) {
84
+ if (options.time !== undefined)
85
+ body.time_of_day = options.time;
86
+ if (options.daysOfWeek !== undefined)
65
87
  body.days_of_week = options.daysOfWeek;
66
- }
67
- if (options.daysOfMonth !== undefined) {
88
+ if (options.daysOfMonth !== undefined)
68
89
  body.days_of_month = options.daysOfMonth;
69
- }
70
- if (options.minutesOfHour !== undefined) {
90
+ if (options.minutesOfHour !== undefined)
71
91
  body.minutes_of_hour = options.minutesOfHour;
72
- }
92
+ if (options.cronExpression !== undefined)
93
+ body.cron_expression = options.cronExpression;
94
+ if (options.cronTimezone !== undefined)
95
+ body.cron_timezone = options.cronTimezone;
73
96
  if (options.input !== undefined) {
74
97
  try {
75
98
  body.agent_input = JSON.parse(options.input);
@@ -10,7 +10,7 @@ export function createWorkspaceAgentAddCommand() {
10
10
  const cmd = new Command('add');
11
11
  cmd
12
12
  .description('Add an agent to a workspace')
13
- .argument('<agent>', 'Agent identifier (e.g., owner/agent-name or UUID)')
13
+ .argument('<agent>', 'Agent identifier (e.g., owner~agent-name or UUID)')
14
14
  .option('--workspace <id>', 'Target workspace ID or name')
15
15
  .option('--no-autoupdate', 'Disable automatic updates for this agent')
16
16
  .action(async (agentIdentifier, options) => {
@@ -32,7 +32,7 @@ export function createWorkspaceAgentAddCommand() {
32
32
  }
33
33
  workspaceId = resolved.workspaceId;
34
34
  }
35
- // Resolve agent - the backend accepts both UUID and owner/name format
35
+ // Resolve agent - the backend accepts both UUID and owner~agent-name format
36
36
  // First, look up the agent to get its ID and display info
37
37
  let agent;
38
38
  try {
@@ -46,7 +46,7 @@ export function createWorkspaceAgentAddCommand() {
46
46
  }
47
47
  if (formattedError.code === ErrorCodes.FORBIDDEN ||
48
48
  formattedError.code === ErrorCodes.AUTH_TOKEN_INVALID) {
49
- console.error('Cannot add agent: access denied. Ensure the agent is public or check your permissions.');
49
+ output.error('Cannot add agent: access denied. Ensure the agent is public or check your permissions.');
50
50
  process.exit(1);
51
51
  }
52
52
  throw error;
@@ -10,7 +10,7 @@ export function createWorkspaceAgentRemoveCommand() {
10
10
  const cmd = new Command('remove');
11
11
  cmd
12
12
  .description('Remove an agent from a workspace')
13
- .argument('<agent>', 'Agent identifier (e.g., owner/agent-name or UUID)')
13
+ .argument('<agent>', 'Agent identifier (e.g., owner~agent-name or UUID)')
14
14
  .option('--workspace <id>', 'Workspace ID or name')
15
15
  .action(async (agentIdentifier, options) => {
16
16
  const output = createOutputWriter();
@@ -84,4 +84,19 @@ export declare function buildEphemeralVersion(client: GuildAPIClient, agentId: s
84
84
  version: AgentVersion;
85
85
  cached: boolean;
86
86
  }>;
87
+ /** Thrown when the specified bundle file cannot be found on disk. */
88
+ export declare class BundleNotFoundError extends Error {
89
+ constructor(filePath: string);
90
+ }
91
+ /**
92
+ * Upload a pre-built bundle as an ephemeral version.
93
+ *
94
+ * The bundle file must be gzip+base64 encoded (the output of
95
+ * `esbuild ... | gzip | base64`). Source files are included for
96
+ * dashboard viewing, but the server skips its own build step because
97
+ * the ready-to-run artifact is already provided.
98
+ */
99
+ export declare function buildBundledVersion(client: GuildAPIClient, agentId: string, bundlePath: string, cwd: string, summary: string): Promise<{
100
+ version: AgentVersion;
101
+ }>;
87
102
  //# sourceMappingURL=agent-helpers.d.ts.map