@guildai/cli 0.8.0 → 0.9.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.
- package/dist/commands/agent/chat.js +0 -2
- package/dist/commands/agent/init.js +9 -7
- package/dist/commands/agent/list.js +46 -7
- package/dist/commands/agent/owners.js +6 -8
- package/dist/commands/agent/test.js +6 -14
- package/dist/commands/chat.js +3 -10
- package/dist/commands/job/get-step.d.ts +3 -0
- package/dist/commands/job/{step-get.js → get-step.js} +3 -3
- package/dist/commands/mcp.js +1 -1
- package/dist/commands/session/create.js +2 -2
- package/dist/commands/session/list.js +1 -1
- package/dist/commands/trigger/create.js +41 -17
- package/dist/commands/trigger/update.js +33 -10
- package/dist/commands/workspace/agent/add.js +3 -3
- package/dist/commands/workspace/agent/remove.js +1 -1
- package/dist/index.js +5 -2
- package/dist/lib/api-client.js +12 -5
- package/dist/lib/api-types.d.ts +25 -0
- package/dist/lib/auth.d.ts +0 -4
- package/dist/lib/auth.js +0 -11
- package/dist/lib/errors.d.ts +5 -4
- package/dist/lib/errors.js +18 -11
- package/dist/lib/output.d.ts +1 -1
- package/dist/lib/output.js +35 -103
- package/dist/lib/polling.d.ts +7 -24
- package/dist/lib/polling.js +12 -29
- package/dist/lib/version-helpers.js +38 -0
- package/dist/mcp/tools.js +2 -2
- package/docs/CLI_WORKFLOW.md +1 -1
- package/docs/skills/agent-dev.md +302 -1007
- package/docs/skills/integrations.md +1 -1
- package/package.json +2 -2
- package/dist/commands/job/step-get.d.ts +0 -3
- package/dist/lib/lottie-renderer.d.ts +0 -61
- package/dist/lib/lottie-renderer.js +0 -238
- package/dist/lib/svg-renderer.d.ts +0 -110
- package/dist/lib/svg-renderer.js +0 -858
|
@@ -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}`);
|
|
@@ -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
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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 (!
|
|
331
|
-
|
|
331
|
+
if (!existingLines.includes('.guild/cache/')) {
|
|
332
|
+
lines.push('.guild/cache/');
|
|
332
333
|
}
|
|
333
|
-
if (
|
|
334
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
+
output.error(`Failed to list owners: ${formattedError.details}`);
|
|
71
69
|
process.exit(1);
|
|
72
70
|
}
|
|
73
71
|
});
|
|
@@ -12,11 +12,10 @@ import { hyperlink } from '../../lib/colors.js';
|
|
|
12
12
|
import { GuildAPIClient } from '../../lib/api-client.js';
|
|
13
13
|
import { handleAxiosError, ErrorCodes } from '../../lib/errors.js';
|
|
14
14
|
import { format } from '../../lib/progress.js';
|
|
15
|
-
import { showBetaGuidance } from '../../lib/auth.js';
|
|
16
15
|
import * as readline from 'readline';
|
|
17
16
|
import { parseEventFilter } from '../../lib/event-filter.js';
|
|
18
17
|
import { isQuietMode, getOutputMode } from '../../lib/output-mode.js';
|
|
19
|
-
import {
|
|
18
|
+
import { pollForResponseWithEvents } from '../../lib/session-polling.js';
|
|
20
19
|
import { readStdinAsJSON, ensureInteractiveStdin } from '../../lib/stdin.js';
|
|
21
20
|
import { loadLocalConfig, getWorkspaceId } from '../../lib/guild-config.js';
|
|
22
21
|
import { GitError, formatGitError } from '../../lib/git.js';
|
|
@@ -37,15 +36,13 @@ export function createAgentTestCommand() {
|
|
|
37
36
|
.option('--agent-version <id>', 'Test a specific version (UUID or version number)')
|
|
38
37
|
.option('--resume <session-id>', 'Resume an existing test session')
|
|
39
38
|
.option('--open', 'Open session in web dashboard')
|
|
40
|
-
.option('--events <types>', 'Event types to stream (default:
|
|
39
|
+
.option('--events <types>', 'Event types to stream (default: all). Shorthands: none, user, system, all, or comma-separated type names')
|
|
41
40
|
.option('--bundle <file>', 'Path to a pre-built gzip+base64 bundle file')
|
|
42
41
|
.option('--no-cache', 'Skip ephemeral build cache (force a fresh build)')
|
|
43
42
|
.action(async (options) => {
|
|
44
43
|
const cwd = process.cwd();
|
|
45
|
-
// Parse --events filter once, before any branching
|
|
46
|
-
const eventFilter = options.events
|
|
47
|
-
? parseEventFilter(options.events)
|
|
48
|
-
: undefined;
|
|
44
|
+
// Parse --events filter once, before any branching (default: all)
|
|
45
|
+
const eventFilter = parseEventFilter(options.events ?? 'all');
|
|
49
46
|
try {
|
|
50
47
|
// Handle --resume: skip build, fetch existing session, hand off to ChatApp
|
|
51
48
|
if (options.resume) {
|
|
@@ -273,9 +270,7 @@ export function createAgentTestCommand() {
|
|
|
273
270
|
});
|
|
274
271
|
// Poll for response (starting from beginning)
|
|
275
272
|
// 3 minutes - allow time for agents that use LLM calls for input parsing
|
|
276
|
-
const { response } = eventFilter
|
|
277
|
-
? await pollForResponseWithEvents(client, session.id, eventFilter, undefined, 180000)
|
|
278
|
-
: await pollForResponse(client, session.id, undefined, 180000);
|
|
273
|
+
const { response } = await pollForResponseWithEvents(client, session.id, eventFilter, undefined, 180000);
|
|
279
274
|
if (!response) {
|
|
280
275
|
console.error('Error: No response received from agent within timeout');
|
|
281
276
|
console.error('');
|
|
@@ -351,9 +346,7 @@ export function createAgentTestCommand() {
|
|
|
351
346
|
content: jsonInput,
|
|
352
347
|
});
|
|
353
348
|
// Wait for response (looking for events after last seen)
|
|
354
|
-
const result = eventFilter
|
|
355
|
-
? await pollForResponseWithEvents(client, session.id, eventFilter, lastEventId, 180000)
|
|
356
|
-
: await pollForResponse(client, session.id, lastEventId, 180000);
|
|
349
|
+
const result = await pollForResponseWithEvents(client, session.id, eventFilter, lastEventId, 180000);
|
|
357
350
|
lastEventId = result.lastEventId;
|
|
358
351
|
if (!result.response) {
|
|
359
352
|
console.error(`Timeout: No response for line ${lineNumber}`);
|
|
@@ -417,7 +410,6 @@ export function createAgentTestCommand() {
|
|
|
417
410
|
if (formattedError.code === ErrorCodes.AUTH_REQUIRED ||
|
|
418
411
|
formattedError.code === ErrorCodes.AUTH_TOKEN_INVALID) {
|
|
419
412
|
format.error('Not authenticated. Run: guild auth login');
|
|
420
|
-
showBetaGuidance();
|
|
421
413
|
process.exit(1);
|
|
422
414
|
}
|
|
423
415
|
console.error(`Error: ${formattedError.details}`);
|
package/dist/commands/chat.js
CHANGED
|
@@ -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
|
|
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>', '
|
|
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
|
}
|
|
@@ -5,8 +5,8 @@ import { GuildAPIClient } from '../../lib/api-client.js';
|
|
|
5
5
|
import { getAuthToken } from '../../lib/auth.js';
|
|
6
6
|
import { handleAxiosError } from '../../lib/errors.js';
|
|
7
7
|
import { createOutputWriter } from '../../lib/output.js';
|
|
8
|
-
export function
|
|
9
|
-
const cmd = new Command('step
|
|
8
|
+
export function createJobGetStepCommand() {
|
|
9
|
+
const cmd = new Command('get-step');
|
|
10
10
|
cmd
|
|
11
11
|
.description('Get details of a step in a job')
|
|
12
12
|
.argument('<job-id>', 'Job ID')
|
|
@@ -37,4 +37,4 @@ export function createJobStepGetCommand() {
|
|
|
37
37
|
});
|
|
38
38
|
return cmd;
|
|
39
39
|
}
|
|
40
|
-
//# sourceMappingURL=step
|
|
40
|
+
//# sourceMappingURL=get-step.js.map
|
package/dist/commands/mcp.js
CHANGED
|
@@ -6,7 +6,7 @@ export function createMcpCommand() {
|
|
|
6
6
|
const cmd = new Command('mcp');
|
|
7
7
|
cmd
|
|
8
8
|
.description('Start an MCP server over stdio for use with Claude Code, Cursor, and other MCP hosts')
|
|
9
|
-
.option('--workspace <id>', 'Workspace ID (defaults to configured workspace)')
|
|
9
|
+
.option('--workspace <id>', 'Workspace ID or name (defaults to configured workspace)')
|
|
10
10
|
.option('--debug', 'Enable debug logging to stderr')
|
|
11
11
|
.addHelpText('after', `
|
|
12
12
|
Configuration:
|
|
@@ -10,10 +10,10 @@ export function createSessionCreateCommand() {
|
|
|
10
10
|
const cmd = new Command('create');
|
|
11
11
|
cmd
|
|
12
12
|
.description('Create a new session')
|
|
13
|
-
.option('--workspace <id>', 'Workspace ID or name')
|
|
13
|
+
.option('--workspace <id>', 'Workspace ID or name (e.g., owner~workspace-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
|
|
16
|
+
.option('--agent <identifier>', 'Agent identifier, e.g., owner~agent-name')
|
|
17
17
|
.action(async (options) => {
|
|
18
18
|
const output = createOutputWriter();
|
|
19
19
|
try {
|
|
@@ -11,7 +11,7 @@ export function createSessionListCommand() {
|
|
|
11
11
|
const cmd = new Command('list');
|
|
12
12
|
cmd
|
|
13
13
|
.description('List sessions in a workspace')
|
|
14
|
-
.option('--workspace <id>', 'Workspace ID or name')
|
|
14
|
+
.option('--workspace <id>', 'Workspace ID or name (e.g., owner~workspace-name)')
|
|
15
15
|
.option('--type <type>', 'Filter by session type: chat, webhook, time, agent_test')
|
|
16
16
|
.option('--limit <number>', 'Number of results to return', '20')
|
|
17
17
|
.option('--offset <number>', 'Offset for pagination', '0')
|
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
62
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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();
|
package/dist/index.js
CHANGED
|
@@ -61,7 +61,7 @@ import { createSessionCreateCommand } from './commands/session/create.js';
|
|
|
61
61
|
import { createSessionSendCommand } from './commands/session/send.js';
|
|
62
62
|
import { createSessionInterruptCommand } from './commands/session/interrupt.js';
|
|
63
63
|
import { createJobGetCommand } from './commands/job/get.js';
|
|
64
|
-
import {
|
|
64
|
+
import { createJobGetStepCommand } from './commands/job/get-step.js';
|
|
65
65
|
import { createConfigListCommand } from './commands/config/list.js';
|
|
66
66
|
import { createConfigGetCommand } from './commands/config/get.js';
|
|
67
67
|
import { createConfigSetCommand } from './commands/config/set.js';
|
|
@@ -281,7 +281,10 @@ sessionCmd.addCommand(createSessionInterruptCommand());
|
|
|
281
281
|
// Job command group
|
|
282
282
|
const jobCmd = program.command('job').description('Job management');
|
|
283
283
|
jobCmd.addCommand(createJobGetCommand());
|
|
284
|
-
jobCmd.addCommand(
|
|
284
|
+
jobCmd.addCommand(createJobGetStepCommand());
|
|
285
|
+
const stepGetAlias = createJobGetStepCommand();
|
|
286
|
+
stepGetAlias.name('step-get');
|
|
287
|
+
jobCmd.addCommand(stepGetAlias, { hidden: true });
|
|
285
288
|
// Config command group
|
|
286
289
|
const configCmd = program
|
|
287
290
|
.command('config')
|
package/dist/lib/api-client.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
import axios from 'axios';
|
|
4
4
|
import { getAuthToken, clearAuthToken } from './auth.js';
|
|
5
|
-
import { retry, debug, GuildCLIError, ErrorCodes } from './errors.js';
|
|
5
|
+
import { retry, debug, GuildCLIError, ErrorCodes, shouldClearAuthToken, } from './errors.js';
|
|
6
6
|
import { getUserAgent, getGuildcoreUrl } from './config.js';
|
|
7
7
|
import { getIapHeaders } from './iap.js';
|
|
8
8
|
/**
|
|
@@ -69,14 +69,21 @@ export class GuildAPIClient {
|
|
|
69
69
|
return response.data;
|
|
70
70
|
}
|
|
71
71
|
catch (error) {
|
|
72
|
-
// Handle invalid/expired token - clear it and give clear error
|
|
73
72
|
if (error &&
|
|
74
73
|
typeof error === 'object' &&
|
|
75
74
|
'response' in error &&
|
|
76
75
|
error.response?.status === 401) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
const resp = error.response;
|
|
77
|
+
const data = resp?.data;
|
|
78
|
+
const message = typeof data === 'object' && data && 'message' in data
|
|
79
|
+
? data.message
|
|
80
|
+
: undefined;
|
|
81
|
+
if (shouldClearAuthToken(message)) {
|
|
82
|
+
debug('Guild auth failure (401), clearing token');
|
|
83
|
+
await clearAuthToken();
|
|
84
|
+
throw new GuildCLIError(ErrorCodes.AUTH_TOKEN_INVALID, 'Your authentication token is invalid or expired. Please run: guild auth login');
|
|
85
|
+
}
|
|
86
|
+
debug(`Non-auth 401: ${message ?? 'no message'}`);
|
|
80
87
|
}
|
|
81
88
|
throw error;
|
|
82
89
|
}
|