@guildai/cli 0.5.1 → 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/README.md CHANGED
@@ -58,17 +58,23 @@ guild auth status # Check authentication status
58
58
 
59
59
  ```bash
60
60
  guild agent init --name my-agent # Initialize a new agent
61
+ guild agent clone <agent-id> # Clone agent locally
61
62
  guild agent save # Push commits and create a version
62
63
  guild agent save -A --message "..." # Stage+commit+push in one step
63
- guild agent versions # List versions
64
- guild agent publish # Publish latest draft version
65
- guild agent unpublish # Unpublish latest published version
66
- guild agent test # Test agent
67
- guild agent chat # Chat with agent in current directory
68
- guild agent get <agent-id> # Get agent details
69
- guild agent code <agent-id> # Fetch latest published code
70
- guild agent clone <agent-id> # Clone agent locally
64
+ guild agent pull # Pull remote changes
65
+ guild agent test # Test agent interactively
66
+ guild agent test --ephemeral # Test unsaved changes
67
+ guild agent chat "message" # Send a single message
68
+ guild agent get [agent-id] # Get agent details
69
+ guild agent versions [agent-id] # List versions
70
+ guild agent code [agent-id] # View latest published source
71
71
  guild agent list # List all agents
72
+ guild agent grep <pattern> # Search agent source code
73
+ guild agent publish # Publish latest draft version
74
+ guild agent unpublish # Remove from catalog
75
+ guild agent revalidate # Re-run validation
76
+ guild agent update # Update agent metadata
77
+ guild agent owners # List accounts that can own agents
72
78
  guild agent tags list # List tags
73
79
  guild agent tags add <tag...> # Add tags
74
80
  guild agent tags remove <tag...> # Remove tags
@@ -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
- output.progress(' Already up to date');
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) {
@@ -258,7 +258,7 @@ export function createAgentSaveCommand() {
258
258
  output.progress('');
259
259
  output.progress('Version details:');
260
260
  output.progress(` ID: ${version.id}`);
261
- output.progress(` SHA: ${version.sha.substring(0, 12)}`);
261
+ output.progress(` SHA: ${version.sha ? version.sha.substring(0, 12) : '-'}`);
262
262
  output.progress(` Status: ${version.status}`);
263
263
  if (version.published_at) {
264
264
  output.progress(` Published at: ${version.published_at}`);
@@ -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
  });
@@ -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 &&
@@ -58,7 +58,7 @@ export function createWorkspaceAgentAddCommand() {
58
58
  // Display success
59
59
  const versionDisplay = response.agent_version.version_number
60
60
  ? `v${response.agent_version.version_number}`
61
- : response.agent_version.sha.slice(0, 7);
61
+ : (response.agent_version.sha?.slice(0, 7) ?? '-');
62
62
  const workspaceName = response.workspace?.name || 'workspace';
63
63
  output.success(`Added ${response.agent.full_name} to workspace "${workspaceName}"`, {
64
64
  Agent: response.agent.full_name,
@@ -18,6 +18,16 @@ function formatWorkspaceDisplay(workspace) {
18
18
  }
19
19
  return base;
20
20
  }
21
+ /**
22
+ * Match a workspace against a search argument (case-insensitive name, full_name, or exact ID).
23
+ */
24
+ function matchesWorkspaceArg(w, arg) {
25
+ return (w.name === arg ||
26
+ w.name.toLowerCase() === arg.toLowerCase() ||
27
+ w.full_name === arg ||
28
+ w.full_name?.toLowerCase() === arg.toLowerCase() ||
29
+ w.id === arg);
30
+ }
21
31
  export function createWorkspaceSelectCommand() {
22
32
  const cmd = new Command('select');
23
33
  cmd
@@ -27,24 +37,25 @@ export function createWorkspaceSelectCommand() {
27
37
  const output = createOutputWriter();
28
38
  try {
29
39
  const client = new GuildAPIClient();
30
- // Use filter=all to get workspaces from all orgs user is a member of
31
- const workspaces = await client.get('/me/workspaces?filter=all');
32
- if (workspaces.items.length === 0) {
33
- output.error('No workspaces found.', 'Create a workspace first:\n guild workspace create <name>');
34
- process.exit(1);
35
- }
36
- // If a workspace argument was provided, find and select it directly
40
+ // If a workspace argument was provided, use server-side search to find it
37
41
  if (workspaceArg) {
38
- const workspace = workspaces.items.find((w) => w.name === workspaceArg ||
39
- w.name.toLowerCase() === workspaceArg.toLowerCase() ||
40
- w.full_name === workspaceArg ||
41
- w.full_name?.toLowerCase() === workspaceArg.toLowerCase() ||
42
- w.id === workspaceArg);
42
+ // Use search param to narrow results server-side, then exact-match client-side.
43
+ // The backend searches only the "name" column via ILIKE, so full_name (owner/name)
44
+ // and ID lookups may not return results. Extract just the name part for search,
45
+ // and if still no match, fall back to fetching the full list.
46
+ const searchTerm = workspaceArg.includes('/')
47
+ ? workspaceArg.split('/').pop()
48
+ : workspaceArg;
49
+ const searchResults = await client.get(`/me/workspaces?filter=all&search=${encodeURIComponent(searchTerm)}&limit=100`);
50
+ let workspace = searchResults.items.find((w) => matchesWorkspaceArg(w, workspaceArg));
51
+ // Search didn't find it (could be an ID lookup, or search term didn't match).
52
+ // Fall back to fetching all workspaces via pagination.
43
53
  if (!workspace) {
44
- const available = workspaces.items
45
- .map((w) => ` - ${formatWorkspaceDisplay(w)}`)
46
- .join('\n');
47
- output.error(`Workspace "${workspaceArg}" not found.`, `Available workspaces:\n${available}`);
54
+ const allWorkspaces = await client.fetchAll('/me/workspaces?filter=all');
55
+ workspace = allWorkspaces.find((w) => matchesWorkspaceArg(w, workspaceArg));
56
+ }
57
+ if (!workspace) {
58
+ output.error(`Workspace "${workspaceArg}" not found.`, 'Run: guild workspace list');
48
59
  process.exit(1);
49
60
  }
50
61
  const target = await saveWorkspaceConfig(workspace.id, workspace.name);
@@ -56,6 +67,12 @@ export function createWorkspaceSelectCommand() {
56
67
  }
57
68
  return;
58
69
  }
70
+ // Interactive mode: fetch workspaces for selection
71
+ const workspaces = await client.get('/me/workspaces?filter=all');
72
+ if (workspaces.items.length === 0) {
73
+ output.error('No workspaces found.', 'Create a workspace first:\n guild workspace create <name>');
74
+ process.exit(1);
75
+ }
59
76
  // Resolve the currently selected workspace (if any)
60
77
  const current = await getWorkspaceId();
61
78
  const currentIndex = current
@@ -63,7 +63,7 @@ export interface WorkspaceAgent {
63
63
  };
64
64
  agent_version: {
65
65
  id: string;
66
- sha: string;
66
+ sha: string | null;
67
67
  version_number: string | null;
68
68
  };
69
69
  workspace?: {
@@ -83,11 +83,12 @@ export interface AgentVersion {
83
83
  id: string;
84
84
  agent_id?: string;
85
85
  author_id?: string;
86
- sha: string;
86
+ sha: string | null;
87
87
  summary: string;
88
88
  status: string;
89
89
  validation_status?: string;
90
90
  version_number?: string | null;
91
+ version_type?: 'COMMITTED' | 'EPHEMERAL';
91
92
  published_at?: string | null;
92
93
  created_at: string;
93
94
  updated_at?: string;
@@ -227,7 +228,7 @@ export interface TriggerWorkspaceAgent {
227
228
  };
228
229
  agent_version: {
229
230
  id: string;
230
- sha: string;
231
+ sha: string | null;
231
232
  version_number: string | null;
232
233
  };
233
234
  }
@@ -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
@@ -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
@@ -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
  *
@@ -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
  *
@@ -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: agent.full_name || agent.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: 'sha', title: 'SHA', alignment: 'left' },
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
- sha: v.sha.slice(0, 7),
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,10 +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: wa.agent.full_name || wa.agent.name,
179
- version: wa.agent_version.version_number || 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),
180
187
  auto_update: wa.should_autoupdate ? chalk.green('yes') : chalk.dim('no'),
181
188
  });
182
189
  });
@@ -236,9 +243,10 @@ export function formatSessionTable(sessions, pagination) {
236
243
  { name: 'created', title: 'CREATED', alignment: 'left' },
237
244
  ],
238
245
  });
246
+ const base = getDashboardUrl();
239
247
  sessions.forEach((session) => {
240
248
  table.addRow({
241
- id: truncate(session.id, 8),
249
+ id: hyperlink(session.id, `${base}/sessions/${session.id}`),
242
250
  type: session.session_type,
243
251
  name: session.name ? truncate(session.name, 30) : '-',
244
252
  workspace: session.workspace?.name || '',
@@ -274,16 +282,21 @@ export function formatTriggerTable(triggers, pagination) {
274
282
  { name: 'created', title: 'CREATED', alignment: 'left' },
275
283
  ],
276
284
  });
285
+ const base = getDashboardUrl();
277
286
  triggers.forEach((trigger) => {
278
287
  const status = trigger.disabled_reason
279
288
  ? chalk.yellow('disabled')
280
289
  : trigger.deactivated_at
281
290
  ? chalk.dim('inactive')
282
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
+ : '';
283
296
  table.addRow({
284
- id: truncate(trigger.id, 8),
297
+ id: hyperlink(trigger.id, `${base}/triggers/${trigger.id}/sessions`),
285
298
  type: trigger.type,
286
- agent: trigger.agent?.full_name || trigger.agent?.name || '',
299
+ agent: agentUrl ? hyperlink(agentName, agentUrl) : agentName,
287
300
  status,
288
301
  created: trigger.created_at ? formatRelativeTime(trigger.created_at) : '',
289
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;
@@ -81,6 +81,7 @@ guild agent clone guildai/dev-assistant
81
81
  cd dev-assistant
82
82
 
83
83
  # Pull remote changes (from collaborators or web edits)
84
+ # Also syncs unpublished changes made via the web editor
84
85
  guild agent pull
85
86
 
86
87
  # Check current version status
@@ -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
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guildai/cli",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "Guild.ai CLI - Build, test, and deploy AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",