@guildai/cli 0.5.2 → 0.5.3
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/pull.js +63 -4
- package/dist/commands/auth/status.js +21 -4
- package/dist/commands/chat.js +13 -1
- package/dist/lib/api-types.d.ts +1 -0
- package/dist/lib/colors.d.ts +11 -0
- package/dist/lib/colors.js +19 -0
- package/dist/lib/config.d.ts +7 -0
- package/dist/lib/config.js +9 -0
- package/dist/lib/output.js +22 -10
- package/dist/lib/session-polling.js +4 -0
- package/docs/CLI_WORKFLOW.md +1 -0
- package/docs/getting-started.md +2 -0
- package/docs/output-format.md +9 -0
- package/package.json +1 -1
|
@@ -45,6 +45,7 @@ export function createAgentPullCommand() {
|
|
|
45
45
|
const { stdout: branchName } = await runGit(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd });
|
|
46
46
|
const currentBranch = branchName.trim();
|
|
47
47
|
// Pull remote changes with rebase
|
|
48
|
+
let gitPulledNewCommits = false;
|
|
48
49
|
try {
|
|
49
50
|
const { stdout: pullOutput } = await runGit(['pull', '--rebase', authenticatedUrl, currentBranch], { cwd });
|
|
50
51
|
// Update remote-tracking ref so git status stays consistent
|
|
@@ -57,23 +58,21 @@ export function createAgentPullCommand() {
|
|
|
57
58
|
if (pullOutput.includes('Already up to date') ||
|
|
58
59
|
pullOutput.includes('Current branch') // "Current branch X is up to date."
|
|
59
60
|
) {
|
|
60
|
-
|
|
61
|
-
output.data({ success: true, message: 'Already up to date' });
|
|
61
|
+
// Don't report yet — version check below may have more to say
|
|
62
62
|
}
|
|
63
63
|
else {
|
|
64
|
+
gitPulledNewCommits = true;
|
|
64
65
|
// Count commits pulled
|
|
65
66
|
const updateMatch = pullOutput.match(/Updating\s+[a-f0-9]+\.\.[a-f0-9]+/);
|
|
66
67
|
const fastForward = pullOutput.includes('Fast-forward');
|
|
67
68
|
let message = 'Pulled remote changes';
|
|
68
69
|
if (fastForward || updateMatch) {
|
|
69
|
-
// Try to count files changed from pull output
|
|
70
70
|
const filesMatch = pullOutput.match(/(\d+)\s+files?\s+changed/);
|
|
71
71
|
if (filesMatch) {
|
|
72
72
|
message = `Pulled remote changes (${filesMatch[1]} files changed)`;
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
output.progress(`✓ ${message}`);
|
|
76
|
-
output.data({ success: true, message });
|
|
77
76
|
}
|
|
78
77
|
}
|
|
79
78
|
catch (pullError) {
|
|
@@ -99,6 +98,66 @@ export function createAgentPullCommand() {
|
|
|
99
98
|
// Unknown git error
|
|
100
99
|
throw pullError;
|
|
101
100
|
}
|
|
101
|
+
// Version check: compare local state against backend
|
|
102
|
+
const versions = await client.get(`/agents/${guildConfig.agent_id}/versions?limit=1`);
|
|
103
|
+
if (versions.items.length > 0) {
|
|
104
|
+
const latest = versions.items[0];
|
|
105
|
+
const { stdout: headSha } = await runGit(['rev-parse', 'HEAD'], { cwd });
|
|
106
|
+
const localHead = headSha.trim();
|
|
107
|
+
if (latest.sha && latest.sha === localHead) {
|
|
108
|
+
// Truly up to date — git pull + version match
|
|
109
|
+
if (!gitPulledNewCommits) {
|
|
110
|
+
output.progress('✓ Already up to date');
|
|
111
|
+
output.data({ success: true, message: 'Already up to date' });
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
output.data({ success: true, message: 'Pulled remote changes' });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else if (latest.sha && latest.sha !== localHead) {
|
|
118
|
+
// SHA mismatch — warn user
|
|
119
|
+
output.progress(`⚠ Remote has a newer version (${latest.sha.slice(0, 7)}) not on this branch`);
|
|
120
|
+
output.progress(' Try: git fetch origin && git log --oneline origin/main');
|
|
121
|
+
output.data({
|
|
122
|
+
success: true,
|
|
123
|
+
message: 'SHA mismatch with latest version',
|
|
124
|
+
latest_sha: latest.sha,
|
|
125
|
+
local_sha: localHead,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
// Ephemeral version — download from API
|
|
130
|
+
output.progress('Remote has unpublished changes from the web editor');
|
|
131
|
+
// Check for local uncommitted changes
|
|
132
|
+
const { stdout: status } = await runGit(['status', '--porcelain'], { cwd });
|
|
133
|
+
if (status.trim()) {
|
|
134
|
+
output.error('Cannot download remote changes — you have uncommitted local changes', 'Commit or stash your changes first, then run guild agent pull again.');
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
const files = await client.get(`/agents/${guildConfig.agent_id}/code?include_unpublished=1`);
|
|
138
|
+
for (const file of files) {
|
|
139
|
+
const filePath = path.join(cwd, file.path);
|
|
140
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
141
|
+
await fs.writeFile(filePath, file.content, 'utf-8');
|
|
142
|
+
}
|
|
143
|
+
output.progress(`✓ Downloaded ${files.length} files from latest draft version`);
|
|
144
|
+
output.data({
|
|
145
|
+
success: true,
|
|
146
|
+
message: `Downloaded ${files.length} files from draft version`,
|
|
147
|
+
files_updated: files.length,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
// No versions exist yet — just report git pull result
|
|
153
|
+
if (!gitPulledNewCommits) {
|
|
154
|
+
output.progress('✓ Already up to date');
|
|
155
|
+
output.data({ success: true, message: 'Already up to date' });
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
output.data({ success: true, message: 'Pulled remote changes' });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
102
161
|
}
|
|
103
162
|
catch (error) {
|
|
104
163
|
if (error instanceof GitError) {
|
|
@@ -2,16 +2,33 @@
|
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import { getAuthStatus } from '../../lib/auth.js';
|
|
4
4
|
import { createOutputWriter } from '../../lib/output.js';
|
|
5
|
+
import { GuildAPIClient } from '../../lib/api-client.js';
|
|
6
|
+
import { GuildCLIError, ErrorCodes } from '../../lib/errors.js';
|
|
5
7
|
export function createAuthStatusCommand() {
|
|
6
8
|
const cmd = new Command('status');
|
|
7
9
|
cmd.description('Check authentication status').action(async () => {
|
|
8
10
|
const output = createOutputWriter();
|
|
9
11
|
const { authenticated } = await getAuthStatus();
|
|
10
|
-
if (authenticated) {
|
|
11
|
-
output.success('Authenticated');
|
|
12
|
-
}
|
|
13
|
-
else {
|
|
12
|
+
if (!authenticated) {
|
|
14
13
|
output.error('Not authenticated');
|
|
14
|
+
process.exit(0);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
// Try to fetch user profile for richer output
|
|
18
|
+
try {
|
|
19
|
+
const client = new GuildAPIClient();
|
|
20
|
+
const me = await client.get('/me');
|
|
21
|
+
output.success(`Authenticated as ${me.name} (${me.id})`);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (error instanceof GuildCLIError &&
|
|
25
|
+
error.code === ErrorCodes.AUTH_TOKEN_INVALID) {
|
|
26
|
+
output.error('Not authenticated (token expired)', 'Run: guild auth login');
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
// Offline or other network error — fall back to simple message
|
|
30
|
+
output.success('Authenticated');
|
|
31
|
+
}
|
|
15
32
|
}
|
|
16
33
|
process.exit(0);
|
|
17
34
|
});
|
package/dist/commands/chat.js
CHANGED
|
@@ -4,7 +4,7 @@ import { Box, Text, Static, render, useInput, useApp } from 'ink';
|
|
|
4
4
|
import { Command } from 'commander';
|
|
5
5
|
import { getAuthToken, showBetaGuidance } from '../lib/auth.js';
|
|
6
6
|
import { GuildAPIClient } from '../lib/api-client.js';
|
|
7
|
-
import { handleAxiosError, ErrorCodes, debug, retry } from '../lib/errors.js';
|
|
7
|
+
import { handleAxiosError, ErrorCodes, debug, isDebugMode, retry, } from '../lib/errors.js';
|
|
8
8
|
import { createSpinner, format } from '../lib/progress.js';
|
|
9
9
|
import { marked } from 'marked';
|
|
10
10
|
import { markedTerminal } from 'marked-terminal';
|
|
@@ -567,6 +567,18 @@ function ChatUIWithConnection({ initialPrompt, version: _version, versionId: _ve
|
|
|
567
567
|
]);
|
|
568
568
|
setCurrentOperation('');
|
|
569
569
|
}
|
|
570
|
+
else if (event.type === 'agent_console' && isDebugMode()) {
|
|
571
|
+
// Show console logs when debug mode is enabled (matches www debug checkbox behavior)
|
|
572
|
+
const content = typeof event.content === 'string' ? event.content : '';
|
|
573
|
+
setMessages((prev) => [
|
|
574
|
+
...prev,
|
|
575
|
+
{
|
|
576
|
+
key: `console-${Date.now()}-${Math.random()}`,
|
|
577
|
+
content: chalk.dim(`[console.${event.level}] ${content}`),
|
|
578
|
+
type: 'assistant',
|
|
579
|
+
},
|
|
580
|
+
]);
|
|
581
|
+
}
|
|
570
582
|
else if (event.type === 'runtime_done' &&
|
|
571
583
|
!receivedResponseSinceLastInput.current &&
|
|
572
584
|
event.content !== undefined &&
|
package/dist/lib/api-types.d.ts
CHANGED
package/dist/lib/colors.d.ts
CHANGED
|
@@ -17,4 +17,15 @@ export declare const warn: import("chalk").ChalkInstance;
|
|
|
17
17
|
* Replaces chalk.yellow for code highlighting.
|
|
18
18
|
*/
|
|
19
19
|
export declare const code: import("chalk").ChalkInstance;
|
|
20
|
+
/**
|
|
21
|
+
* Wrap text in an OSC8 terminal hyperlink.
|
|
22
|
+
*
|
|
23
|
+
* In supported terminals (iTerm2, VS Code, Windows Terminal, etc.),
|
|
24
|
+
* the text becomes clickable. Falls back to plain text when:
|
|
25
|
+
* - Output is piped (non-TTY)
|
|
26
|
+
* - FORCE_HYPERLINK=0
|
|
27
|
+
*
|
|
28
|
+
* Set FORCE_HYPERLINK=1 to force hyperlinks even in non-TTY.
|
|
29
|
+
*/
|
|
30
|
+
export declare function hyperlink(text: string, url: string): string;
|
|
20
31
|
//# sourceMappingURL=colors.d.ts.map
|
package/dist/lib/colors.js
CHANGED
|
@@ -23,4 +23,23 @@ export const warn = chalk.hex('#b8860b');
|
|
|
23
23
|
* Replaces chalk.yellow for code highlighting.
|
|
24
24
|
*/
|
|
25
25
|
export const code = chalk.hex('#b8860b');
|
|
26
|
+
/**
|
|
27
|
+
* Wrap text in an OSC8 terminal hyperlink.
|
|
28
|
+
*
|
|
29
|
+
* In supported terminals (iTerm2, VS Code, Windows Terminal, etc.),
|
|
30
|
+
* the text becomes clickable. Falls back to plain text when:
|
|
31
|
+
* - Output is piped (non-TTY)
|
|
32
|
+
* - FORCE_HYPERLINK=0
|
|
33
|
+
*
|
|
34
|
+
* Set FORCE_HYPERLINK=1 to force hyperlinks even in non-TTY.
|
|
35
|
+
*/
|
|
36
|
+
export function hyperlink(text, url) {
|
|
37
|
+
const force = process.env.FORCE_HYPERLINK;
|
|
38
|
+
if (force === '0')
|
|
39
|
+
return text;
|
|
40
|
+
const supported = force === '1' || process.stderr.isTTY;
|
|
41
|
+
if (!supported)
|
|
42
|
+
return text;
|
|
43
|
+
return `\x1b]8;;${url}\x1b\\${text}\x1b]8;;\x1b\\`;
|
|
44
|
+
}
|
|
26
45
|
//# sourceMappingURL=colors.js.map
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -38,6 +38,13 @@ export declare const IAP_SERVICE_ACCOUNT: string;
|
|
|
38
38
|
* @returns Base URL for Guildcore API (e.g., "https://app.guild.ai/api")
|
|
39
39
|
*/
|
|
40
40
|
export declare function getGuildcoreUrl(): string;
|
|
41
|
+
/**
|
|
42
|
+
* Get the dashboard (frontend) base URL
|
|
43
|
+
*
|
|
44
|
+
* Derives the frontend URL from the guildcore API URL by stripping /api.
|
|
45
|
+
* Used to construct clickable links to dashboard pages.
|
|
46
|
+
*/
|
|
47
|
+
export declare function getDashboardUrl(): string;
|
|
41
48
|
/**
|
|
42
49
|
* Check if the given URL is an IAP-protected host
|
|
43
50
|
*
|
package/dist/lib/config.js
CHANGED
|
@@ -96,6 +96,15 @@ export function getGuildcoreUrl() {
|
|
|
96
96
|
// Default to production
|
|
97
97
|
return DEFAULT_GUILDCORE_URL;
|
|
98
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Get the dashboard (frontend) base URL
|
|
101
|
+
*
|
|
102
|
+
* Derives the frontend URL from the guildcore API URL by stripping /api.
|
|
103
|
+
* Used to construct clickable links to dashboard pages.
|
|
104
|
+
*/
|
|
105
|
+
export function getDashboardUrl() {
|
|
106
|
+
return getGuildcoreUrl().replace(/\/api$/, '');
|
|
107
|
+
}
|
|
99
108
|
/**
|
|
100
109
|
* Check if the given URL is an IAP-protected host
|
|
101
110
|
*
|
package/dist/lib/output.js
CHANGED
|
@@ -3,7 +3,8 @@ import chalk from 'chalk';
|
|
|
3
3
|
import { Table } from 'console-table-printer';
|
|
4
4
|
import { getOutputMode, isQuietMode } from './output-mode.js';
|
|
5
5
|
import { createSpinner } from './progress.js';
|
|
6
|
-
import { brand } from './colors.js';
|
|
6
|
+
import { brand, hyperlink } from './colors.js';
|
|
7
|
+
import { getDashboardUrl } from './config.js';
|
|
7
8
|
/**
|
|
8
9
|
* Format a relative timestamp (e.g. "2d ago", "3mo ago")
|
|
9
10
|
*/
|
|
@@ -57,9 +58,12 @@ export function formatAgentTable(agents, pagination) {
|
|
|
57
58
|
{ name: 'updated', title: 'UPDATED', alignment: 'left' },
|
|
58
59
|
],
|
|
59
60
|
});
|
|
61
|
+
const base = getDashboardUrl();
|
|
60
62
|
agents.forEach((agent) => {
|
|
63
|
+
const name = agent.full_name || agent.name;
|
|
64
|
+
const agentUrl = agent.full_name ? `${base}/agents/${agent.full_name}` : '';
|
|
61
65
|
table.addRow({
|
|
62
|
-
name:
|
|
66
|
+
name: agentUrl ? hyperlink(name, agentUrl) : name,
|
|
63
67
|
owner: agent.owner?.name || '',
|
|
64
68
|
description: truncate(agent.description || '', 40),
|
|
65
69
|
updated: agent.updated_at ? formatRelativeTime(agent.updated_at) : '',
|
|
@@ -87,7 +91,7 @@ export function formatVersionTable(versions, pagination) {
|
|
|
87
91
|
}
|
|
88
92
|
const table = new Table({
|
|
89
93
|
columns: [
|
|
90
|
-
{ name: '
|
|
94
|
+
{ name: 'id', title: 'ID', alignment: 'left' },
|
|
91
95
|
{ name: 'version', title: 'VERSION', alignment: 'left', color: 'cyan' },
|
|
92
96
|
{ name: 'status', title: 'STATUS', alignment: 'left' },
|
|
93
97
|
{ name: 'validation', title: 'VALIDATION', alignment: 'left' },
|
|
@@ -102,7 +106,7 @@ export function formatVersionTable(versions, pagination) {
|
|
|
102
106
|
? chalk.red
|
|
103
107
|
: chalk.dim;
|
|
104
108
|
table.addRow({
|
|
105
|
-
|
|
109
|
+
id: v.id.substring(0, 8),
|
|
106
110
|
version: v.version_number || '-',
|
|
107
111
|
status: v.status,
|
|
108
112
|
validation: validationColor(v.validation_status || '-'),
|
|
@@ -173,11 +177,13 @@ export function formatWorkspaceAgentTable(agents) {
|
|
|
173
177
|
{ name: 'auto_update', title: 'AUTO-UPDATE', alignment: 'left' },
|
|
174
178
|
],
|
|
175
179
|
});
|
|
180
|
+
const base = getDashboardUrl();
|
|
176
181
|
agents.forEach((wa) => {
|
|
182
|
+
const name = wa.agent.full_name || wa.agent.name;
|
|
183
|
+
const agentUrl = wa.agent.full_name ? `${base}/agents/${wa.agent.full_name}` : '';
|
|
177
184
|
table.addRow({
|
|
178
|
-
name:
|
|
179
|
-
version: wa.agent_version.version_number ||
|
|
180
|
-
(wa.agent_version.sha ? wa.agent_version.sha.slice(0, 7) : '-'),
|
|
185
|
+
name: agentUrl ? hyperlink(name, agentUrl) : name,
|
|
186
|
+
version: wa.agent_version.version_number || wa.agent_version.id.substring(0, 8),
|
|
181
187
|
auto_update: wa.should_autoupdate ? chalk.green('yes') : chalk.dim('no'),
|
|
182
188
|
});
|
|
183
189
|
});
|
|
@@ -237,9 +243,10 @@ export function formatSessionTable(sessions, pagination) {
|
|
|
237
243
|
{ name: 'created', title: 'CREATED', alignment: 'left' },
|
|
238
244
|
],
|
|
239
245
|
});
|
|
246
|
+
const base = getDashboardUrl();
|
|
240
247
|
sessions.forEach((session) => {
|
|
241
248
|
table.addRow({
|
|
242
|
-
id: session.id,
|
|
249
|
+
id: hyperlink(session.id, `${base}/sessions/${session.id}`),
|
|
243
250
|
type: session.session_type,
|
|
244
251
|
name: session.name ? truncate(session.name, 30) : '-',
|
|
245
252
|
workspace: session.workspace?.name || '',
|
|
@@ -275,16 +282,21 @@ export function formatTriggerTable(triggers, pagination) {
|
|
|
275
282
|
{ name: 'created', title: 'CREATED', alignment: 'left' },
|
|
276
283
|
],
|
|
277
284
|
});
|
|
285
|
+
const base = getDashboardUrl();
|
|
278
286
|
triggers.forEach((trigger) => {
|
|
279
287
|
const status = trigger.disabled_reason
|
|
280
288
|
? chalk.yellow('disabled')
|
|
281
289
|
: trigger.deactivated_at
|
|
282
290
|
? chalk.dim('inactive')
|
|
283
291
|
: chalk.green('active');
|
|
292
|
+
const agentName = trigger.agent?.full_name || trigger.agent?.name || '';
|
|
293
|
+
const agentUrl = trigger.agent?.full_name
|
|
294
|
+
? `${base}/agents/${trigger.agent.full_name}`
|
|
295
|
+
: '';
|
|
284
296
|
table.addRow({
|
|
285
|
-
id: trigger.id,
|
|
297
|
+
id: hyperlink(trigger.id, `${base}/triggers/${trigger.id}/sessions`),
|
|
286
298
|
type: trigger.type,
|
|
287
|
-
agent:
|
|
299
|
+
agent: agentUrl ? hyperlink(agentName, agentUrl) : agentName,
|
|
288
300
|
status,
|
|
289
301
|
created: trigger.created_at ? formatRelativeTime(trigger.created_at) : '',
|
|
290
302
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Copyright (c) 2026 Guild.ai All Rights Reserved
|
|
2
|
+
import { isDebugMode } from './errors.js';
|
|
2
3
|
/**
|
|
3
4
|
* Poll for agent response after a certain event count.
|
|
4
5
|
*
|
|
@@ -30,6 +31,9 @@ export async function pollForResponse(client, sessionId, afterEventCount, maxWai
|
|
|
30
31
|
if (event.type === 'runtime_error' && event.task && 'agent' in event.task) {
|
|
31
32
|
return JSON.stringify({ error: event.content });
|
|
32
33
|
}
|
|
34
|
+
if (event.type === 'agent_console' && isDebugMode()) {
|
|
35
|
+
process.stderr.write(`[console.${event.level}] ${event.content}\n`);
|
|
36
|
+
}
|
|
33
37
|
}
|
|
34
38
|
if (lastAgentRuntimeDone !== null) {
|
|
35
39
|
return lastAgentRuntimeDone;
|
package/docs/CLI_WORKFLOW.md
CHANGED
package/docs/getting-started.md
CHANGED
|
@@ -278,6 +278,8 @@ If others are working on the same agent, pull their changes first:
|
|
|
278
278
|
guild agent pull
|
|
279
279
|
```
|
|
280
280
|
|
|
281
|
+
This pulls git commits and also checks for unpublished changes made via the web editor. If someone edited the agent in the UI, pull downloads those files automatically.
|
|
282
|
+
|
|
281
283
|
### 2. Test locally
|
|
282
284
|
|
|
283
285
|
```bash
|
package/docs/output-format.md
CHANGED
|
@@ -115,3 +115,12 @@ guild agent create my-agent --quiet
|
|
|
115
115
|
**JSON by default for data**: `agent list`, `workspace list`, etc. output JSON. The `guild chat` interface provides human-readable interaction.
|
|
116
116
|
|
|
117
117
|
**Quiet mode independent**: Works with both human and JSON modes. Suppresses progress, keeps errors.
|
|
118
|
+
|
|
119
|
+
## Hyperlinks
|
|
120
|
+
|
|
121
|
+
Table cells for entity names and IDs are wrapped in [OSC8 terminal hyperlinks](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda) that link to the corresponding dashboard pages. In supported terminals (iTerm2, VS Code Terminal, Windows Terminal), these cells are clickable.
|
|
122
|
+
|
|
123
|
+
**Environment controls:**
|
|
124
|
+
|
|
125
|
+
- `FORCE_HYPERLINK=1` — Force hyperlinks in non-TTY environments
|
|
126
|
+
- `FORCE_HYPERLINK=0` — Disable hyperlinks entirely
|