@guildai/cli 0.7.1 → 0.8.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/init.js +1 -1
- package/dist/commands/agent/test.js +36 -4
- package/dist/commands/session/tasks.js +8 -2
- package/dist/lib/agent-helpers.d.ts +15 -0
- package/dist/lib/agent-helpers.js +69 -0
- package/dist/lib/api-types.d.ts +37 -0
- package/dist/lib/output.d.ts +6 -1
- package/dist/lib/output.js +50 -0
- package/package.json +1 -1
|
@@ -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 {
|
|
@@ -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';
|
|
@@ -19,7 +20,7 @@ import { pollForResponse, pollForResponseWithEvents, } from '../../lib/session-p
|
|
|
19
20
|
import { readStdinAsJSON, ensureInteractiveStdin } from '../../lib/stdin.js';
|
|
20
21
|
import { loadLocalConfig, getWorkspaceId } from '../../lib/guild-config.js';
|
|
21
22
|
import { GitError, formatGitError } from '../../lib/git.js';
|
|
22
|
-
import { readAgentFiles, buildEphemeralVersion, BuildTimeoutError, BuildFailedError, } from '../../lib/agent-helpers.js';
|
|
23
|
+
import { readAgentFiles, buildEphemeralVersion, buildBundledVersion, BundleNotFoundError, BuildTimeoutError, BuildFailedError, } from '../../lib/agent-helpers.js';
|
|
23
24
|
import { fetchSession, fetchSessionEvents } from '../../lib/session-resume.js';
|
|
24
25
|
import { ChatApp, ensureAuthenticated } from '../chat.js';
|
|
25
26
|
import { suppressScrollbackClear } from '../../lib/alternate-screen.js';
|
|
@@ -37,6 +38,7 @@ export function createAgentTestCommand() {
|
|
|
37
38
|
.option('--resume <session-id>', 'Resume an existing test session')
|
|
38
39
|
.option('--open', 'Open session in web dashboard')
|
|
39
40
|
.option('--events <types>', 'Event types to stream (default: user). Shorthands: none, user, system, all, or comma-separated type names')
|
|
41
|
+
.option('--bundle <file>', 'Path to a pre-built gzip+base64 bundle file')
|
|
40
42
|
.option('--no-cache', 'Skip ephemeral build cache (force a fresh build)')
|
|
41
43
|
.action(async (options) => {
|
|
42
44
|
const cwd = process.cwd();
|
|
@@ -99,6 +101,21 @@ export function createAgentTestCommand() {
|
|
|
99
101
|
process.exit(1);
|
|
100
102
|
}
|
|
101
103
|
}
|
|
104
|
+
// If using bundle, verify the file exists early (before auth/session creation)
|
|
105
|
+
// so we fail fast without needing auth if the path is wrong.
|
|
106
|
+
if (options.bundle) {
|
|
107
|
+
try {
|
|
108
|
+
await access(options.bundle);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
console.error(`Error: Bundle file not found: ${options.bundle}`);
|
|
112
|
+
console.error('');
|
|
113
|
+
console.error('Ensure the bundle file exists and the path is correct:');
|
|
114
|
+
console.error(' npm run build');
|
|
115
|
+
console.error(' guild agent test --bundle agent.js.gz');
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
102
119
|
// Determine workspace (priority: flag > local config > global config)
|
|
103
120
|
let workspaceId = options.workspace;
|
|
104
121
|
if (!workspaceId) {
|
|
@@ -139,6 +156,11 @@ export function createAgentTestCommand() {
|
|
|
139
156
|
}
|
|
140
157
|
version = match;
|
|
141
158
|
}
|
|
159
|
+
else if (options.bundle) {
|
|
160
|
+
// Pre-built bundle: skip server-side compilation entirely.
|
|
161
|
+
const result = await buildBundledVersion(client, guildConfig.agent_id, options.bundle, cwd, '[Test] Pre-built bundle');
|
|
162
|
+
version = result.version;
|
|
163
|
+
}
|
|
142
164
|
else {
|
|
143
165
|
// Default: build ephemeral version from working directory,
|
|
144
166
|
// reusing the last build if files haven't changed.
|
|
@@ -150,6 +172,14 @@ export function createAgentTestCommand() {
|
|
|
150
172
|
}
|
|
151
173
|
}
|
|
152
174
|
catch (error) {
|
|
175
|
+
if (error instanceof BundleNotFoundError) {
|
|
176
|
+
console.error(`Error: ${error.message}`);
|
|
177
|
+
console.error('');
|
|
178
|
+
console.error('Ensure the bundle file exists and the path is correct:');
|
|
179
|
+
console.error(' npm run build');
|
|
180
|
+
console.error(' guild agent test --bundle agent.js.gz');
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
153
183
|
if (error instanceof BuildTimeoutError) {
|
|
154
184
|
console.error('Error: Build did not complete');
|
|
155
185
|
console.error('');
|
|
@@ -216,9 +246,11 @@ export function createAgentTestCommand() {
|
|
|
216
246
|
console.log(`✓ Agent: ${guildConfig.name} (${guildConfig.agent_id})`);
|
|
217
247
|
const versionDisplay = options.agentVersion
|
|
218
248
|
? options.agentVersion
|
|
219
|
-
:
|
|
220
|
-
?
|
|
221
|
-
:
|
|
249
|
+
: options.bundle
|
|
250
|
+
? `bundle (${path.basename(options.bundle)})`
|
|
251
|
+
: ephemeralCached
|
|
252
|
+
? 'ephemeral (cached, no changes)'
|
|
253
|
+
: 'ephemeral (working directory)';
|
|
222
254
|
console.log(`✓ Version: ${versionDisplay}`);
|
|
223
255
|
console.log(`✓ Workspace: ${workspaceId}`);
|
|
224
256
|
const sessionLink = session.session_url
|
|
@@ -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 {
|
|
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
|
-
|
|
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);
|
|
@@ -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
|
|
@@ -234,4 +234,73 @@ export async function buildEphemeralVersion(client, agentId, files, cwd, summary
|
|
|
234
234
|
}
|
|
235
235
|
return { version, cached: false };
|
|
236
236
|
}
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
// Bundle version
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
/** Thrown when the specified bundle file cannot be found on disk. */
|
|
241
|
+
export class BundleNotFoundError extends Error {
|
|
242
|
+
constructor(filePath) {
|
|
243
|
+
super(`Bundle file not found: ${filePath}`);
|
|
244
|
+
this.name = 'BundleNotFoundError';
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Upload a pre-built bundle as an ephemeral version.
|
|
249
|
+
*
|
|
250
|
+
* The bundle file must be gzip+base64 encoded (the output of
|
|
251
|
+
* `esbuild ... | gzip | base64`). Source files are included for
|
|
252
|
+
* dashboard viewing, but the server skips its own build step because
|
|
253
|
+
* the ready-to-run artifact is already provided.
|
|
254
|
+
*/
|
|
255
|
+
export async function buildBundledVersion(client, agentId, bundlePath, cwd, summary) {
|
|
256
|
+
try {
|
|
257
|
+
await fs.access(bundlePath);
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
throw new BundleNotFoundError(bundlePath);
|
|
261
|
+
}
|
|
262
|
+
const bundle = (await fs.readFile(bundlePath, 'utf-8')).trim();
|
|
263
|
+
let files = [];
|
|
264
|
+
try {
|
|
265
|
+
files = await readAgentFiles(cwd);
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
// No git or missing required files — proceed with bundle-only upload
|
|
269
|
+
}
|
|
270
|
+
const initial = (await client.post(`/agents/${agentId}/versions`, {
|
|
271
|
+
version_type: 'EPHEMERAL',
|
|
272
|
+
bundle,
|
|
273
|
+
encoding: 'gzip;base64',
|
|
274
|
+
files,
|
|
275
|
+
summary,
|
|
276
|
+
}));
|
|
277
|
+
const pollResult = await pollUntilComplete({
|
|
278
|
+
resourceId: initial.id,
|
|
279
|
+
endpoint: `/versions/${initial.id}`,
|
|
280
|
+
isComplete: (r) => r.validation_status !== 'PENDING' && r.validation_status !== 'RUNNING',
|
|
281
|
+
message: 'Validating bundle...',
|
|
282
|
+
successMessage: 'Bundle validated',
|
|
283
|
+
timeoutMessage: 'Bundle validation timed out',
|
|
284
|
+
maxAttempts: 120,
|
|
285
|
+
delayMs: 1000,
|
|
286
|
+
});
|
|
287
|
+
if (!pollResult.success || !pollResult.response) {
|
|
288
|
+
throw new BuildTimeoutError('Bundle validation timed out.');
|
|
289
|
+
}
|
|
290
|
+
const version = pollResult.response;
|
|
291
|
+
if (version.validation_status === 'FAILED') {
|
|
292
|
+
let failedSteps = [];
|
|
293
|
+
try {
|
|
294
|
+
const stepsResponse = await client.get(`/versions/${version.id}/validation/steps`);
|
|
295
|
+
failedSteps = stepsResponse.steps
|
|
296
|
+
.filter((step) => step.status === 'FAILED')
|
|
297
|
+
.map((step) => ({ name: step.name, content: step.content ?? undefined }));
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
// Could not fetch validation details — throw with empty steps
|
|
301
|
+
}
|
|
302
|
+
throw new BuildFailedError(failedSteps);
|
|
303
|
+
}
|
|
304
|
+
return { version };
|
|
305
|
+
}
|
|
237
306
|
//# sourceMappingURL=agent-helpers.js.map
|
package/dist/lib/api-types.d.ts
CHANGED
|
@@ -361,6 +361,43 @@ export interface CredentialsPolicy {
|
|
|
361
361
|
updated_at: string;
|
|
362
362
|
}
|
|
363
363
|
export type CredentialsPolicyListResponse = PaginatedResponse<CredentialsPolicy>;
|
|
364
|
+
export type TaskStatus = 'CREATED' | 'DISPATCHED' | 'STARTED' | 'RUNNING' | 'WAITING' | 'ERROR' | 'DONE' | 'INTERRUPTED';
|
|
365
|
+
export interface TokenUsage {
|
|
366
|
+
input_tokens: number;
|
|
367
|
+
output_tokens: number;
|
|
368
|
+
cache_read_tokens: number;
|
|
369
|
+
cache_write_tokens: number;
|
|
370
|
+
total_tokens: number;
|
|
371
|
+
llm_call_count: number;
|
|
372
|
+
}
|
|
373
|
+
interface TaskBase {
|
|
374
|
+
id: string;
|
|
375
|
+
entity_type: string;
|
|
376
|
+
status: TaskStatus;
|
|
377
|
+
token_usage: TokenUsage | null;
|
|
378
|
+
created_at: string;
|
|
379
|
+
updated_at: string;
|
|
380
|
+
}
|
|
381
|
+
export interface TaskAgent extends TaskBase {
|
|
382
|
+
entity_type: 'EntTaskAgent';
|
|
383
|
+
agent: {
|
|
384
|
+
name: string;
|
|
385
|
+
full_name?: string;
|
|
386
|
+
} | null;
|
|
387
|
+
version: {
|
|
388
|
+
id: string;
|
|
389
|
+
version_number?: string;
|
|
390
|
+
} | null;
|
|
391
|
+
}
|
|
392
|
+
export interface TaskTool extends TaskBase {
|
|
393
|
+
entity_type: 'EntTaskTool';
|
|
394
|
+
tool_name: string;
|
|
395
|
+
tool_call_id: string;
|
|
396
|
+
request_bytes: number;
|
|
397
|
+
response_bytes: number;
|
|
398
|
+
http_status_code: number;
|
|
399
|
+
}
|
|
400
|
+
export type Task = TaskAgent | TaskTool;
|
|
364
401
|
/**
|
|
365
402
|
* Session entity from API responses.
|
|
366
403
|
* Used by: session list
|
package/dist/lib/output.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Spinner } from './progress.js';
|
|
2
|
-
import type { Agent, AgentVersion, Credentials, CredentialsPolicy, Context, Integration, IntegrationVersion, JobStep, Pagination, Session, Workspace, WorkspaceAgent, Trigger } from './api-types.js';
|
|
2
|
+
import type { Agent, AgentVersion, Credentials, CredentialsPolicy, Context, Integration, IntegrationVersion, JobStep, Pagination, Session, Task, 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.
|
|
@@ -40,6 +40,11 @@ export declare function formatWorkspaceTable(workspaces: Workspace[], pagination
|
|
|
40
40
|
* Used by session list command.
|
|
41
41
|
*/
|
|
42
42
|
export declare function formatSessionTable(sessions: Session[], pagination: Pagination): void;
|
|
43
|
+
/**
|
|
44
|
+
* Format a task list as a human-readable table.
|
|
45
|
+
* Used by session tasks command.
|
|
46
|
+
*/
|
|
47
|
+
export declare function formatTaskTable(tasks: Task[], pagination: Pagination): void;
|
|
43
48
|
/**
|
|
44
49
|
* Format a trigger list as a human-readable table.
|
|
45
50
|
* Used by trigger list command.
|
package/dist/lib/output.js
CHANGED
|
@@ -359,6 +359,56 @@ export function formatSessionTable(sessions, pagination) {
|
|
|
359
359
|
console.log(chalk.dim(`\nShowing ${showing} of ${pagination.total_count} sessions`));
|
|
360
360
|
}
|
|
361
361
|
}
|
|
362
|
+
/**
|
|
363
|
+
* Format a task list as a human-readable table.
|
|
364
|
+
* Used by session tasks command.
|
|
365
|
+
*/
|
|
366
|
+
export function formatTaskTable(tasks, pagination) {
|
|
367
|
+
if (tasks.length === 0) {
|
|
368
|
+
console.log(chalk.dim('No tasks found'));
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const table = new Table({
|
|
372
|
+
columns: [
|
|
373
|
+
{ name: 'id', title: 'ID', alignment: 'left' },
|
|
374
|
+
{ name: 'name', title: 'NAME', alignment: 'left' },
|
|
375
|
+
{ name: 'status', title: 'STATUS', alignment: 'left' },
|
|
376
|
+
{ name: 'tokens', title: 'TOKENS', alignment: 'left' },
|
|
377
|
+
{ name: 'created', title: 'CREATED', alignment: 'left' },
|
|
378
|
+
],
|
|
379
|
+
});
|
|
380
|
+
tasks.forEach((task) => {
|
|
381
|
+
const name = task.entity_type === 'EntTaskAgent'
|
|
382
|
+
? task.agent?.full_name || task.agent?.name || '-'
|
|
383
|
+
: task.tool_name;
|
|
384
|
+
const statusColor = task.status === 'DONE'
|
|
385
|
+
? chalk.green
|
|
386
|
+
: task.status === 'ERROR'
|
|
387
|
+
? chalk.red
|
|
388
|
+
: task.status === 'RUNNING' ||
|
|
389
|
+
task.status === 'STARTED' ||
|
|
390
|
+
task.status === 'WAITING'
|
|
391
|
+
? chalk.yellow
|
|
392
|
+
: chalk.dim;
|
|
393
|
+
table.addRow({
|
|
394
|
+
id: truncate(task.id, 13),
|
|
395
|
+
name,
|
|
396
|
+
status: statusColor(task.status),
|
|
397
|
+
tokens: task.token_usage ? task.token_usage.total_tokens.toLocaleString() : '-',
|
|
398
|
+
created: task.created_at ? formatRelativeTime(task.created_at) : '',
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
table.printTable();
|
|
402
|
+
const showing = Math.min(pagination.limit, tasks.length);
|
|
403
|
+
if (pagination.has_more) {
|
|
404
|
+
const nextOffset = pagination.offset + pagination.limit;
|
|
405
|
+
console.log(`\nShowing ${showing} of ${pagination.total_count} tasks. ` +
|
|
406
|
+
chalk.dim(`Use --offset ${nextOffset} to see more.`));
|
|
407
|
+
}
|
|
408
|
+
else if (pagination.total_count > showing) {
|
|
409
|
+
console.log(chalk.dim(`\nShowing ${showing} of ${pagination.total_count} tasks`));
|
|
410
|
+
}
|
|
411
|
+
}
|
|
362
412
|
/**
|
|
363
413
|
* Format a trigger list as a human-readable table.
|
|
364
414
|
* Used by trigger list command.
|