@guildai/cli 0.5.11 → 0.5.13

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.
@@ -8,6 +8,7 @@ import * as readline from 'readline';
8
8
  import { runGit, GitError, formatGitError } from '../../lib/git.js';
9
9
  import { createOutputWriter } from '../../lib/output.js';
10
10
  import { resolveOwnerId } from '../../lib/owner-helpers.js';
11
+ import { isInteractive } from '../../lib/stdin.js';
11
12
  async function promptForName() {
12
13
  const rl = readline.createInterface({
13
14
  input: process.stdin,
@@ -32,9 +33,6 @@ async function promptForDescription(defaultDescription) {
32
33
  });
33
34
  });
34
35
  }
35
- function isInteractive() {
36
- return process.stdin.isTTY === true;
37
- }
38
36
  async function isDirectoryEmpty(dirPath) {
39
37
  try {
40
38
  const files = await fs.readdir(dirPath);
@@ -11,6 +11,7 @@ import * as readline from 'readline';
11
11
  import { getAuthenticatedUrl } from '../../lib/auth.js';
12
12
  import { runGit, GitError, formatGitError, installPrePushHook } from '../../lib/git.js';
13
13
  import { resolveOwnerId } from '../../lib/owner-helpers.js';
14
+ import { isInteractive } from '../../lib/stdin.js';
14
15
  const TEMPLATE_CHOICES = [
15
16
  {
16
17
  name: 'LLM - Simple language model agent (recommended)',
@@ -71,9 +72,6 @@ async function promptForTemplate() {
71
72
  ]);
72
73
  return template;
73
74
  }
74
- function isInteractive() {
75
- return process.stdin.isTTY === true;
76
- }
77
75
  export function createAgentInitCommand() {
78
76
  const cmd = new Command('init');
79
77
  cmd
@@ -11,9 +11,11 @@ export function createAuthLoginCommand() {
11
11
  .description('Login to Guild.ai')
12
12
  .option('--return-url <url>', 'Custom URL to redirect to after authentication')
13
13
  .option('--return-label <text>', 'Friendly label for return button (e.g., "VSCode")')
14
+ .option('--no-browser', 'Skip opening the browser (print URL only)')
14
15
  .action(async (options) => {
15
16
  const output = createOutputWriter();
16
- const success = await login(options.returnUrl, options.returnLabel);
17
+ const noBrowser = options.browser === false;
18
+ const success = await login(options.returnUrl, options.returnLabel, noBrowser);
17
19
  if (success) {
18
20
  try {
19
21
  await configureNpmrc();
@@ -2,10 +2,11 @@
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
  import { Command } from 'commander';
4
4
  import * as fs from 'fs/promises';
5
- import * as readline from 'readline';
5
+ import search from '@inquirer/search';
6
6
  import { GuildAPIClient } from '../../lib/api-client.js';
7
7
  import { handleAxiosError, ErrorCodes } from '../../lib/errors.js';
8
8
  import { createOutputWriter } from '../../lib/output.js';
9
+ import { isInteractive } from '../../lib/stdin.js';
9
10
  import { isAgentDirectory, loadLocalConfig, getLocalConfigPath, getWorkspaceId, saveGlobalConfig, } from '../../lib/guild-config.js';
10
11
  /**
11
12
  * Format workspace for display with owner name.
@@ -68,44 +69,39 @@ export function createWorkspaceSelectCommand() {
68
69
  }
69
70
  return;
70
71
  }
71
- // Interactive mode: fetch workspaces for selection
72
- const workspaces = await client.get('/me/workspaces?filter=all');
73
- if (workspaces.items.length === 0) {
72
+ if (!isInteractive()) {
73
+ output.error('Interactive mode requires a terminal.', 'Provide a workspace argument:\n guild workspace select <name|id>');
74
+ process.exit(1);
75
+ }
76
+ // Interactive mode: fetch all workspaces across all pages
77
+ const workspaces = await client.fetchAll('/me/workspaces?filter=all');
78
+ if (workspaces.length === 0) {
74
79
  output.error('No workspaces found.', 'Create a workspace first:\n guild workspace create <name>');
75
80
  process.exit(1);
76
81
  }
77
82
  // Resolve the currently selected workspace (if any)
78
83
  const current = await getWorkspaceId();
79
- const currentIndex = current
80
- ? workspaces.items.findIndex((w) => w.id === current.workspaceId)
81
- : -1;
82
- // Show interactive selection
83
- console.log('Select a default workspace:\n');
84
- workspaces.items.forEach((workspace, index) => {
85
- const marker = index === currentIndex ? ' (current)' : '';
86
- console.log(` ${index + 1}. ${formatWorkspaceDisplay(workspace)}${marker}`);
87
- });
88
- console.log('');
89
- const rl = readline.createInterface({
90
- input: process.stdin,
91
- output: process.stdout,
92
- });
93
- const defaultHint = currentIndex >= 0 ? ` [${currentIndex + 1}]` : '';
94
- const answer = await new Promise((resolve) => {
95
- rl.question(`Enter number (1-${workspaces.items.length})${defaultHint}: `, resolve);
84
+ const currentId = current?.workspaceId;
85
+ // Interactive searchable selection
86
+ const selectedId = await search({
87
+ message: 'Select a workspace (type to filter)',
88
+ pageSize: 15,
89
+ default: currentId,
90
+ source: (input) => {
91
+ const term = input?.toLowerCase() ?? '';
92
+ const filtered = term
93
+ ? workspaces.filter((w) => w.name.toLowerCase().includes(term) ||
94
+ w.full_name?.toLowerCase().includes(term) ||
95
+ w.owner?.name.toLowerCase().includes(term))
96
+ : workspaces;
97
+ return filtered.map((w) => ({
98
+ name: formatWorkspaceDisplay(w) + (w.id === currentId ? ' (current)' : ''),
99
+ value: w.id,
100
+ short: w.name,
101
+ }));
102
+ },
96
103
  });
97
- rl.close();
98
- // Empty input keeps current selection
99
- if (answer.trim() === '' && currentIndex >= 0) {
100
- console.log('Workspace unchanged.');
101
- return;
102
- }
103
- const selection = parseInt(answer.trim(), 10);
104
- if (isNaN(selection) || selection < 1 || selection > workspaces.items.length) {
105
- output.error('Invalid selection');
106
- process.exit(1);
107
- }
108
- const selectedWorkspace = workspaces.items[selection - 1];
104
+ const selectedWorkspace = workspaces.find((w) => w.id === selectedId);
109
105
  const target = await saveWorkspaceConfig(selectedWorkspace.id, selectedWorkspace.name);
110
106
  if (target === 'local') {
111
107
  output.success(`Workspace set for this agent: ${formatWorkspaceDisplay(selectedWorkspace)}`);
package/dist/index.js CHANGED
@@ -11,7 +11,6 @@ import { createAuthLogoutCommand } from './commands/auth/logout.js';
11
11
  import { createAuthStatusCommand } from './commands/auth/status.js';
12
12
  import { createAuthTokenCommand } from './commands/auth/token.js';
13
13
  import { createAgentListCommand } from './commands/agent/list.js';
14
- import { createAgentCreateCommand } from './commands/agent/create.js';
15
14
  import { createAgentGetCommand } from './commands/agent/get.js';
16
15
  import { createAgentUpdateCommand } from './commands/agent/update.js';
17
16
  import { createAgentVersionsCommand } from './commands/agent/versions.js';
@@ -80,6 +79,7 @@ import { createMcpCommand } from './commands/mcp.js';
80
79
  import { showSplashScreen, shouldShowSplash } from './lib/splash.js';
81
80
  import { checkForUpdate } from './lib/update-check.js';
82
81
  import { setupUnknownCommandSuggestions } from './lib/did-you-mean.js';
82
+ import { getEnabledGKs, isGKEnabled } from './lib/gk.js';
83
83
  import chalk from 'chalk';
84
84
  // ESM equivalent of __dirname
85
85
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -132,7 +132,6 @@ authCmd.addCommand(createAuthTokenCommand());
132
132
  // Agent command group
133
133
  const agentCmd = program.command('agent').description('Agent management');
134
134
  agentCmd.addCommand(createAgentListCommand());
135
- agentCmd.addCommand(createAgentCreateCommand());
136
135
  agentCmd.addCommand(createAgentGetCommand());
137
136
  agentCmd.addCommand(createAgentUpdateCommand());
138
137
  agentCmd.addCommand(createAgentVersionsCommand());
@@ -218,20 +217,24 @@ triggerCmd.addCommand(createTriggerUpdateCommand());
218
217
  triggerCmd.addCommand(createTriggerActivateCommand());
219
218
  triggerCmd.addCommand(createTriggerDeactivateCommand());
220
219
  triggerCmd.addCommand(createTriggerSessionsCommand());
221
- // Container command group
222
- const containerCmd = program.command('container').description('Container management');
223
- containerCmd.addCommand(createContainerListCommand());
224
- containerCmd.addCommand(createContainerGetCommand());
225
- containerCmd.addCommand(createContainerEventsCommand());
226
- containerCmd.addCommand(createContainerExecCommand());
227
- containerCmd.addCommand(createContainerDestroyCommand());
228
- // Container image command group
229
- const containerImageCmd = program
230
- .command('container-image')
231
- .description('Container image management');
232
- containerImageCmd.addCommand(createContainerImageCreateCommand());
233
- containerImageCmd.addCommand(createContainerImageListCommand());
234
- containerImageCmd.addCommand(createContainerImageGetCommand());
220
+ // GK-gated commands: fetch enabled GKs and conditionally register command groups
221
+ const enabledGKs = await getEnabledGKs();
222
+ if (isGKEnabled('CONTAINERS', enabledGKs)) {
223
+ // Container command group
224
+ const containerCmd = program.command('container').description('Container management');
225
+ containerCmd.addCommand(createContainerListCommand());
226
+ containerCmd.addCommand(createContainerGetCommand());
227
+ containerCmd.addCommand(createContainerEventsCommand());
228
+ containerCmd.addCommand(createContainerExecCommand());
229
+ containerCmd.addCommand(createContainerDestroyCommand());
230
+ // Container image command group
231
+ const containerImageCmd = program
232
+ .command('container-image')
233
+ .description('Container image management');
234
+ containerImageCmd.addCommand(createContainerImageCreateCommand());
235
+ containerImageCmd.addCommand(createContainerImageListCommand());
236
+ containerImageCmd.addCommand(createContainerImageGetCommand());
237
+ }
235
238
  // Session command group
236
239
  const sessionCmd = program.command('session').description('Session management');
237
240
  sessionCmd.addCommand(createSessionListCommand());
@@ -50,7 +50,7 @@ export declare function showBetaGuidance(): void;
50
50
  * @param returnLabel - Optional friendly label for return button (e.g., "VSCode")
51
51
  * @returns true if successful, false otherwise
52
52
  */
53
- export declare function login(returnUrl?: string, returnLabel?: string): Promise<boolean>;
53
+ export declare function login(returnUrl?: string, returnLabel?: string, noBrowser?: boolean): Promise<boolean>;
54
54
  /**
55
55
  * Perform logout
56
56
  */
package/dist/lib/auth.js CHANGED
@@ -196,36 +196,42 @@ export function showBetaGuidance() {
196
196
  * @param returnLabel - Optional friendly label for return button (e.g., "VSCode")
197
197
  * @returns true if successful, false otherwise
198
198
  */
199
- export async function login(returnUrl, returnLabel) {
199
+ export async function login(returnUrl, returnLabel, noBrowser) {
200
200
  const authUrl = getGuildcoreUrl();
201
201
  try {
202
202
  console.log(chalk.bold('\nGuild.ai Authentication'));
203
203
  const deviceCode = await startDeviceFlow(authUrl, returnUrl, returnLabel);
204
204
  // Display styled verification code box
205
205
  displayVerificationCode(deviceCode.user_code, deviceCode.verification_uri_complete);
206
- // Wait for user to press Enter before opening browser
207
- // This gives them time to see and copy the code before browser opens
208
- // Critical for VSCode integration where browser may open full screen
209
- const { openBrowser } = await inquirer.prompt([
210
- {
211
- type: 'confirm',
212
- name: 'openBrowser',
213
- message: 'Press Enter to open browser in your default web browser...',
214
- default: true,
215
- },
216
- ]);
217
- if (!openBrowser) {
218
- console.error(chalk.red('✗ Authentication cancelled by user'));
219
- return false;
206
+ const skipBrowser = noBrowser || process.stdin.isTTY !== true;
207
+ if (skipBrowser) {
208
+ console.log(chalk.dim('Open the URL above in your browser to complete authentication.'));
220
209
  }
221
- // Open URL in default browser after user confirmation
222
- try {
223
- const open = (await import('open')).default;
224
- await open(deviceCode.verification_uri_complete);
225
- }
226
- catch {
227
- // Fallback if open fails - user can manually open URL
228
- console.log(chalk.red('✗ Could not open browser automatically. Please visit the URL manually.\n'));
210
+ else {
211
+ // Wait for user to press Enter before opening browser
212
+ // This gives them time to see and copy the code before browser opens
213
+ // Critical for VSCode integration where browser may open full screen
214
+ const { openBrowser } = await inquirer.prompt([
215
+ {
216
+ type: 'confirm',
217
+ name: 'openBrowser',
218
+ message: 'Press Enter to open browser in your default web browser...',
219
+ default: true,
220
+ },
221
+ ]);
222
+ if (!openBrowser) {
223
+ console.error(chalk.red('✗ Authentication cancelled by user'));
224
+ return false;
225
+ }
226
+ // Open URL in default browser after user confirmation
227
+ try {
228
+ const open = (await import('open')).default;
229
+ await open(deviceCode.verification_uri_complete);
230
+ }
231
+ catch {
232
+ // Fallback if open fails - user can manually open URL
233
+ console.log(chalk.red('✗ Could not open browser automatically. Please visit the URL manually.\n'));
234
+ }
229
235
  }
230
236
  const token = await pollForToken(authUrl, deviceCode.device_code, deviceCode.interval);
231
237
  if (token) {
@@ -46,10 +46,6 @@ export const IAP_CLIENT_ID = SHARED_IAP_CONFIG.clientId;
46
46
  export const IAP_SERVICE_ACCOUNT = SHARED_IAP_CONFIG.serviceAccount;
47
47
  /**
48
48
  * GuildCore URL constants
49
- *
50
- * TODO: Consider implementing proper feature flags (GK) for the CLI instead of
51
- * these hardcoded shortcuts. For now, --shared and --local are undocumented
52
- * developer conveniences.
53
49
  */
54
50
  const PROD_GUILDCORE_URL = 'https://app.guild.ai/api';
55
51
  const SHARED_GUILDCORE_URL = 'https://shared.guildai.dev/api';
@@ -1,4 +1,4 @@
1
- export declare const WEBHOOK_SERVICES: readonly ["AZURE_DEVOPS", "BITBUCKET", "CYPRESS", "GITHUB", "GOOGLE_DOCS", "GOOGLE_COMPUTE", "GOOGLE_LOGGING", "JIRA", "LINEAR", "NEWRELIC", "NOTION", "SLACK", "TESTRAIL", "ZENDESK"];
1
+ export declare const WEBHOOK_SERVICES: readonly ["AZURE_DEVOPS", "BITBUCKET", "CURL", "CYPRESS", "GITHUB", "GOOGLE_DOCS", "GOOGLE_COMPUTE", "GOOGLE_LOGGING", "JIRA", "LINEAR", "NEWRELIC", "NOTION", "SLACK", "TESTRAIL", "ZENDESK"];
2
2
  export type WebhookService = (typeof WEBHOOK_SERVICES)[number];
3
3
  export declare const TIME_TRIGGER_FREQUENCIES: readonly ["HOURLY", "DAILY", "WEEKLY", "MONTHLY", "CRON"];
4
4
  export type TimeTriggerFrequency = (typeof TIME_TRIGGER_FREQUENCIES)[number];
@@ -10,6 +10,7 @@
10
10
  export const WEBHOOK_SERVICES = [
11
11
  'AZURE_DEVOPS',
12
12
  'BITBUCKET',
13
+ 'CURL',
13
14
  'CYPRESS',
14
15
  'GITHUB',
15
16
  'GOOGLE_DOCS',
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Fetch the set of GK names enabled for the current user.
3
+ *
4
+ * Returns null when the enabled set cannot be determined (not authenticated,
5
+ * network error, etc.). Callers should treat null as "show everything" (fail-open).
6
+ */
7
+ export declare function getEnabledGKs(): Promise<Set<string> | null>;
8
+ /**
9
+ * Check whether a specific GK is enabled for the current user.
10
+ *
11
+ * When enabledGKs is null (could not be determined), returns true (fail-open)
12
+ * so that commands are not hidden due to transient errors.
13
+ */
14
+ export declare function isGKEnabled(gk: string, enabledGKs: Set<string> | null): boolean;
15
+ //# sourceMappingURL=gk.d.ts.map
package/dist/lib/gk.js ADDED
@@ -0,0 +1,91 @@
1
+ // Copyright 2026 Guild.ai
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import * as fs from 'fs/promises';
4
+ import * as path from 'path';
5
+ import axios from 'axios';
6
+ import { getGlobalConfigDir } from './guild-config.js';
7
+ import { getGuildcoreUrl } from './config.js';
8
+ import { getAuthToken } from './auth.js';
9
+ import { getIapHeaders } from './iap.js';
10
+ import { debug } from './errors.js';
11
+ const GK_CACHE_FILE = 'gk-cache.json';
12
+ const GK_CACHE_TTL_MS = 5 * 60 * 1000;
13
+ function getCachePath() {
14
+ return path.join(getGlobalConfigDir(), GK_CACHE_FILE);
15
+ }
16
+ async function readCache(host) {
17
+ try {
18
+ const content = await fs.readFile(getCachePath(), 'utf-8');
19
+ const entry = JSON.parse(content);
20
+ if (entry.host === host && Date.now() - entry.ts < GK_CACHE_TTL_MS) {
21
+ debug('GK cache hit for %s', host);
22
+ return entry.gks;
23
+ }
24
+ debug('GK cache stale or host mismatch');
25
+ }
26
+ catch {
27
+ debug('GK cache not found or unreadable');
28
+ }
29
+ return null;
30
+ }
31
+ async function writeCache(host, gks) {
32
+ const configDir = getGlobalConfigDir();
33
+ await fs.mkdir(configDir, { recursive: true });
34
+ await fs.writeFile(getCachePath(), JSON.stringify({ gks, ts: Date.now(), host }));
35
+ }
36
+ /**
37
+ * Fetch the set of GK names enabled for the current user.
38
+ *
39
+ * Returns null when the enabled set cannot be determined (not authenticated,
40
+ * network error, etc.). Callers should treat null as "show everything" (fail-open).
41
+ */
42
+ export async function getEnabledGKs() {
43
+ const token = await getAuthToken();
44
+ if (!token) {
45
+ debug('Not authenticated, skipping GK fetch');
46
+ return null;
47
+ }
48
+ const baseUrl = getGuildcoreUrl();
49
+ let host;
50
+ try {
51
+ host = new URL(baseUrl).host;
52
+ }
53
+ catch {
54
+ return null;
55
+ }
56
+ const cached = await readCache(host);
57
+ if (cached) {
58
+ return new Set(cached);
59
+ }
60
+ try {
61
+ const iapHeaders = await getIapHeaders(baseUrl);
62
+ const response = await axios.get(`${baseUrl}/gks/enabled`, {
63
+ headers: {
64
+ ...iapHeaders,
65
+ Authorization: `Bearer ${token}`,
66
+ Accept: 'application/json',
67
+ },
68
+ timeout: 5000,
69
+ });
70
+ const gkNames = response.data.gks.map((g) => g.name);
71
+ await writeCache(host, gkNames);
72
+ debug('Fetched %d enabled GKs', gkNames.length);
73
+ return new Set(gkNames);
74
+ }
75
+ catch (error) {
76
+ debug('Failed to fetch GKs: %s', error);
77
+ return null;
78
+ }
79
+ }
80
+ /**
81
+ * Check whether a specific GK is enabled for the current user.
82
+ *
83
+ * When enabledGKs is null (could not be determined), returns true (fail-open)
84
+ * so that commands are not hidden due to transient errors.
85
+ */
86
+ export function isGKEnabled(gk, enabledGKs) {
87
+ if (!enabledGKs)
88
+ return true;
89
+ return enabledGKs.has(gk);
90
+ }
91
+ //# sourceMappingURL=gk.js.map
@@ -1,3 +1,7 @@
1
+ /**
2
+ * Check if stdin is connected to an interactive terminal (TTY).
3
+ */
4
+ export declare function isInteractive(): boolean;
1
5
  /**
2
6
  * Exit with a helpful error when stdin is piped but no --mode flag was given.
3
7
  * Call this before rendering an interactive UI (Ink render()) so users who
package/dist/lib/stdin.js CHANGED
@@ -1,5 +1,11 @@
1
1
  // Copyright 2026 Guild.ai
2
2
  // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Check if stdin is connected to an interactive terminal (TTY).
5
+ */
6
+ export function isInteractive() {
7
+ return process.stdin.isTTY === true;
8
+ }
3
9
  /**
4
10
  * Exit with a helpful error when stdin is piped but no --mode flag was given.
5
11
  * Call this before rendering an interactive UI (Ink render()) so users who
@@ -11,7 +11,7 @@ For local agent development using the Guild CLI. This workflow manages agent cod
11
11
 
12
12
  If Guild MCP tools are available (check for tools prefixed with `guild_`), use them for **read operations**: searching agents, listing workspaces, reading contexts, checking sessions, viewing credentials. MCP tools are faster and don't require shell execution.
13
13
 
14
- Use the **CLI** (via Bash) for **local development operations**: `guild agent create`, `guild agent save`, `guild agent test`, `guild agent pull`, `guild agent clone`. These involve the local filesystem and git, which MCP can't do.
14
+ Use the **CLI** (via Bash) for **local development operations**: `guild agent init`, `guild agent save`, `guild agent test`, `guild agent pull`, `guild agent clone`. These involve the local filesystem and git, which MCP can't do.
15
15
 
16
16
  ## CRITICAL: Always Use the Guild CLI
17
17
 
@@ -19,7 +19,7 @@ Use the **CLI** (via Bash) for **local development operations**: `guild agent cr
19
19
 
20
20
  ```bash
21
21
  # Create a new agent
22
- guild agent create my-agent --template LLM
22
+ guild agent init --name my-agent --template LLM
23
23
 
24
24
  # Clone an existing agent
25
25
  guild agent clone guildai/dev-assistant
@@ -66,11 +66,11 @@ guild setup --claude-md
66
66
  ### Creating Agents
67
67
 
68
68
  ```bash
69
- # Create with template (interactive — prompts for template if omitted)
70
- guild agent create my-agent
71
- guild agent create my-agent --template LLM
72
- guild agent create my-agent --template AUTO_MANAGED_STATE
73
- guild agent create my-agent --template BLANK
69
+ # Create and initialize a new agent (interactive — prompts for name and template)
70
+ guild agent init
71
+ guild agent init --name my-agent --template LLM
72
+ guild agent init --name my-agent --template AUTO_MANAGED_STATE
73
+ guild agent init --name my-agent --template BLANK
74
74
 
75
75
  # Fork an existing agent
76
76
  guild agent init --fork owner/agent-name
@@ -145,7 +145,7 @@ guild agent chat "Hello, can you help me?"
145
145
 
146
146
  ## File Structure
147
147
 
148
- After `guild agent create`, you get:
148
+ After `guild agent init`, you get:
149
149
 
150
150
  ```
151
151
  my-agent/
@@ -175,7 +175,7 @@ Working tree is clean and there are no unpushed commits. Make a code change, com
175
175
  You're not in an agent directory. Either:
176
176
 
177
177
  - `cd` into the agent directory
178
- - Run `guild agent create` to create one
178
+ - Run `guild agent init` to create one
179
179
 
180
180
  ### Validation Failed
181
181
 
@@ -11,7 +11,7 @@ Build agents for Guild using the CLI. **Always use the Guild CLI for agent opera
11
11
 
12
12
  If Guild MCP tools are available (check for tools prefixed with `guild_`), use them for **read operations**: searching agents, listing workspaces, reading contexts, checking sessions, viewing credentials. MCP tools are faster and don't require shell execution.
13
13
 
14
- Use the **CLI** (via Bash) for **local development operations**: `guild agent create`, `guild agent save`, `guild agent test`, `guild agent pull`, `guild agent clone`. These involve the local filesystem and git, which MCP can't do.
14
+ Use the **CLI** (via Bash) for **local development operations**: `guild agent init`, `guild agent save`, `guild agent test`, `guild agent pull`, `guild agent clone`. These involve the local filesystem and git, which MCP can't do.
15
15
 
16
16
  ## When to Use This
17
17
 
@@ -39,17 +39,14 @@ guild setup --claude-md
39
39
  ### Creating Agents
40
40
 
41
41
  ```bash
42
- # Create from template (interactive - prompts for template)
43
- guild agent create my-agent
44
-
45
- # Create with specific template
46
- guild agent create my-agent --template LLM
47
- guild agent create my-agent --template AUTO_MANAGED_STATE
48
- guild agent create my-agent --template BLANK
49
-
50
- # Initialize local workspace for existing agent
42
+ # Create and initialize a new agent (interactive - prompts for name and template)
51
43
  guild agent init
52
44
 
45
+ # Create with specific name and template
46
+ guild agent init --name my-agent --template LLM
47
+ guild agent init --name my-agent --template AUTO_MANAGED_STATE
48
+ guild agent init --name my-agent --template BLANK
49
+
53
50
  # Initialize with fork of existing agent
54
51
  guild agent init --fork owner/agent-name
55
52
 
@@ -98,7 +95,7 @@ guild agent chat "Hello, can you help me?"
98
95
 
99
96
  ### For Creating and Modifying
100
97
 
101
- - ✅ `guild agent create`, `guild agent init`, `guild agent clone`
98
+ - ✅ `guild agent init`, `guild agent clone`
102
99
  - ✅ `git add`, `git commit` (manage your own working tree)
103
100
  - ✅ `guild agent save` (push commits and create a version)
104
101
  - ✅ `guild agent save -A --message "desc"` (stage+commit+push in one step)
@@ -154,16 +151,22 @@ import { ask, output, callTools } from '@guildai/agents-sdk';
154
151
  import { guildTools, userInterfaceTools } from '@guildai/agents-sdk';
155
152
 
156
153
  // Service tools (from separate packages - NOT from SDK)
154
+ import { azureDevOpsTools } from '@guildai-services/guildai~azure-devops';
155
+ import { bitbucketTools } from '@guildai-services/guildai~bitbucket';
156
+ import { confluenceTools } from '@guildai-services/guildai~confluence';
157
+ import { cypressTools } from '@guildai-services/guildai~cypress';
158
+ import { figmaTools } from '@guildai-services/guildai~figma';
157
159
  import { gitHubTools } from '@guildai-services/guildai~github';
158
- import { slackTools } from '@guildai-services/guildai~slack';
160
+ import { googleComputeTools } from '@guildai-services/guildai~google-compute';
161
+ import { googleLoggingTools } from '@guildai-services/guildai~google-logging';
159
162
  import { jiraTools } from '@guildai-services/guildai~jira';
160
- import { bitbucketTools } from '@guildai-services/guildai~bitbucket';
161
- import { azureDevOpsTools } from '@guildai-services/guildai~azure-devops';
162
163
  import { pipedreamTools } from '@guildai-services/guildai~pipedream';
163
- import { cypressTools } from '@guildai-services/guildai~cypress';
164
+ import { slackTools } from '@guildai-services/guildai~slack';
165
+ import { testrailTools } from '@guildai-services/guildai~testrail';
166
+ import { zendeskTools } from '@guildai-services/guildai~zendesk';
164
167
 
165
168
  // Utilities
166
- import { pick, progressLogNotifyEvent } from '@guildai/agents-sdk';
169
+ import { pick, omit, progressLogNotifyEvent } from '@guildai/agents-sdk';
167
170
 
168
171
  // Advanced (for compiled agents with LLM tool loops)
169
172
  import { delegatedCallsOf, asToolResultContent } from '@guildai/agents-sdk';
@@ -176,19 +179,25 @@ import { z } from 'zod';
176
179
 
177
180
  Service tools are in separate `@guildai-services/*` packages. The runtime resolves them automatically.
178
181
 
179
- | Service | Package | Export | Tool Name Prefix |
180
- | -------------- | ---------------------------------------- | -------------------- | ---------------- |
181
- | Azure DevOps | `@guildai-services/guildai~azure-devops` | `azureDevOpsTools` | `azure_devops_` |
182
- | Bitbucket | `@guildai-services/guildai~bitbucket` | `bitbucketTools` | `bitbucket_` |
183
- | Cypress | `@guildai-services/guildai~cypress` | `cypressTools` | `cypress_` |
184
- | GitHub | `@guildai-services/guildai~github` | `gitHubTools` | `github_` |
185
- | Guild | `@guildai/agents-sdk` | `guildTools` | `guild_` |
186
- | Jira | `@guildai-services/guildai~jira` | `jiraTools` | `jira_` |
187
- | Linear | `@guildai-services/guildai~linear` | `linearTools` | `linear_` |
188
- | NewRelic | `@guildai-services/guildai~newrelic` | `newrelicTools` | `newrelic_` |
189
- | Pipedream | `@guildai-services/guildai~pipedream` | `pipedreamTools` | `pipedream_` |
190
- | Slack | `@guildai-services/guildai~slack` | `slackTools` | `slack_` |
191
- | User Interface | `@guildai/agents-sdk` | `userInterfaceTools` | `ui_` |
182
+ | Service | Package | Export | Tool Name Prefix |
183
+ | -------------- | ------------------------------------------ | -------------------- | ----------------- |
184
+ | Azure DevOps | `@guildai-services/guildai~azure-devops` | `azureDevOpsTools` | `azure_devops_` |
185
+ | Bitbucket | `@guildai-services/guildai~bitbucket` | `bitbucketTools` | `bitbucket_` |
186
+ | Confluence | `@guildai-services/guildai~confluence` | `confluenceTools` | `confluence_` |
187
+ | Cypress | `@guildai-services/guildai~cypress` | `cypressTools` | `cypress_` |
188
+ | Figma | `@guildai-services/guildai~figma` | `figmaTools` | `figma_` |
189
+ | GitHub | `@guildai-services/guildai~github` | `gitHubTools` | `github_` |
190
+ | Google Compute | `@guildai-services/guildai~google-compute` | `googleComputeTools` | `google_compute_` |
191
+ | Google Logging | `@guildai-services/guildai~google-logging` | `googleLoggingTools` | `google_logging_` |
192
+ | Guild | `@guildai/agents-sdk` | `guildTools` | `guild_` |
193
+ | Jira | `@guildai-services/guildai~jira` | `jiraTools` | `jira_` |
194
+ | Linear | `@guildai-services/guildai~linear` | `linearTools` | `linear_` |
195
+ | NewRelic | `@guildai-services/guildai~newrelic` | `newrelicTools` | `newrelic_` |
196
+ | Pipedream | `@guildai-services/guildai~pipedream` | `pipedreamTools` | `pipedream_` |
197
+ | Slack | `@guildai-services/guildai~slack` | `slackTools` | `slack_` |
198
+ | TestRail | `@guildai-services/guildai~testrail` | `testrailTools` | `testrail_` |
199
+ | User Interface | `@guildai/agents-sdk` | `userInterfaceTools` | `ui_` |
200
+ | Zendesk | `@guildai-services/guildai~zendesk` | `zendeskTools` | `zendesk_` |
192
201
 
193
202
  ### Tool Access via `task.tools.*`
194
203
 
@@ -223,12 +232,16 @@ await task.tools.guild_credentials_request({ service: 'GITHUB' });
223
232
 
224
233
  ### Task Properties
225
234
 
226
- | Property | Description |
227
- | ---------------- | ------------------------------------------------------------------------------------ |
228
- | `task.sessionId` | Session ID for correlating operations |
229
- | `task.console` | Debug logging (`task.console.debug(...)`, `.info(...)`, `.warn(...)`, `.error(...)`) |
230
- | `task.tools` | Primary API for calling all tools |
231
- | `task.guild` | Guild service (available when **all** `guildTools` included see warning below) |
235
+ | Property | Description |
236
+ | ---------------- | ------------------------------------------------------------------------------------------- |
237
+ | `task.sessionId` | Session ID for correlating operations |
238
+ | `task.tools` | Primary API for calling all tools |
239
+ | `task.llm` | LLM service — call `task.llm.generateText({ messages, system, tools })` for AI model access |
240
+ | `task.console` | Debug logging (`task.console.debug(...)`, `.info(...)`, `.warn(...)`, `.error(...)`) |
241
+ | `task.save()` | Persist agent state (self-managed state agents only) |
242
+ | `task.restore()` | Retrieve previously saved state (self-managed state agents only) |
243
+ | `task.guild` | **Deprecated** — use `task.tools.guild_*` instead |
244
+ | `task.ui` | **Deprecated** — use `task.tools.ui_*` instead |
232
245
 
233
246
  ---
234
247
 
@@ -343,7 +356,7 @@ export default agent({
343
356
 
344
357
  - `run()` returns the OUTPUT directly (not wrapped in `{ type: "output", output: ... }`)
345
358
  - The runtime handles continuations — you can `await` tool calls inline
346
- - Requires `"use agent"` directive at top of file
359
+ - `"use agent"` directive at top of file is optional (the Babel compiler recognizes it but strips it)
347
360
  - No `identifier` field needed
348
361
 
349
362
  ### 3. Self-Managed State Agent (`start()`/`onToolResults()`)
@@ -477,7 +490,7 @@ import {
477
490
  } from '@guildai/agents-sdk';
478
491
  import { gitHubTools } from '@guildai-services/guildai~github';
479
492
  import { slackTools } from '@guildai-services/guildai~slack';
480
- import { generateText, type ModelMessage } from 'ai';
493
+ import type { ModelMessage } from 'ai';
481
494
  import { z } from 'zod';
482
495
 
483
496
  const tools = { ...gitHubTools, ...slackTools, ...userInterfaceTools };
@@ -488,15 +501,12 @@ const llmTools = { ...gitHubTools }; // LLM gets execute access to these
488
501
  const agentTools = { ...slackTools, ...userInterfaceTools }; // These get delegated
489
502
 
490
503
  async function start(input, task: Task<Tools>) {
491
- const model = await task.llm.model();
492
504
  const messages: ModelMessage[] = [{ role: 'user', content: input.text }];
493
505
 
494
- const result = await generateText({
495
- model,
506
+ const result = await task.llm.generateText({
496
507
  system: 'You are a helpful assistant.',
497
508
  messages,
498
509
  tools: llmTools, // Only give LLM the tools it can execute
499
- maxSteps: 10,
500
510
  });
501
511
 
502
512
  // Check for delegated (unexecuted) tool calls
@@ -528,6 +538,7 @@ async function onToolResults(
528
538
 
529
539
  **Key utilities:**
530
540
 
541
+ - `task.llm.generateText({ messages, system, tools })` — call the LLM with automatic authentication and provider selection. The runtime handles model selection and credential injection.
531
542
  - `delegatedCallsOf<Tools>(content)` — extracts unexecuted tool calls from `generateText` results that need runtime delegation
532
543
  - `asToolResultContent(results)` — converts `TypedToolResult[]` into LLM message format for conversation history
533
544
 
@@ -575,13 +586,20 @@ await task.tools.slack_chat_post_message({
575
586
 
576
587
  **CRITICAL: Only use tool names listed below.** If a tool isn't listed here, it doesn't exist. Do not guess tool names based on API patterns.
577
588
 
578
- Use `pick()` to select specific tools:
589
+ Use `pick()` to select specific tools, or `omit()` to exclude specific tools:
579
590
 
580
591
  ```typescript
592
+ // Include only specific tools
581
593
  const tools = {
582
594
  ...pick(gitHubTools, ['github_repos_get', 'github_pulls_list']),
583
595
  ...guildTools,
584
596
  };
597
+
598
+ // Include all tools except specific ones
599
+ const tools = {
600
+ ...omit(gitHubTools, ['github_repos_delete', 'github_repos_update']),
601
+ ...guildTools,
602
+ };
585
603
  ```
586
604
 
587
605
  #### Azure DevOps (`azure_devops_` prefix, 122 tools)
@@ -1676,12 +1694,12 @@ export default agent({ run: async (input, task) => { ... } })
1676
1694
  ## Versioning
1677
1695
 
1678
1696
  - Use semver: `1.0.0` → `1.0.1` (patch), `1.1.0` (minor), `2.0.0` (breaking)
1679
- - Keep `package.json` version in sync with your releases
1680
- - Bump version before `guild agent save --wait --publish`
1697
+ - Use `--bump [patch|minor|major]` with `guild agent save` to auto-bump `package.json` version
1698
+ - Or bump manually in `package.json` before saving
1681
1699
 
1682
1700
  ## File Structure
1683
1701
 
1684
- After `guild agent create` + `guild agent init`:
1702
+ After `guild agent init`:
1685
1703
 
1686
1704
  ```
1687
1705
  my-agent/
@@ -1705,23 +1723,28 @@ my-agent/
1705
1723
  ```bash
1706
1724
  guild setup # Install coding assistant skills
1707
1725
  guild setup --claude-md # Also create CLAUDE.md template
1708
- guild agent create <name> # Create new agent (prompts for template)
1709
- guild agent create <name> --template LLM # Create with specific template
1710
- guild agent init # Initialize local workspace
1726
+ guild agent init # Create and initialize a new agent
1727
+ guild agent init --name <name> --template LLM # Create with specific name and template
1711
1728
  guild agent init --fork <agent-id> # Fork existing agent
1712
1729
  guild agent pull # Pull remote changes
1713
1730
  guild agent save # Push commits and create a draft version
1714
1731
  guild agent save -A --message "description" # Stage+commit+push in one step
1715
1732
  guild agent save --message "v1.0" --wait --publish # Save + validate + publish
1733
+ guild agent save --bump minor --message "v1.1" # Auto-bump version before saving
1716
1734
  guild agent test # Interactive test
1717
1735
  guild agent test --ephemeral # Ephemeral test
1718
1736
  guild agent chat "Hello" # Test with input
1719
1737
  guild agent get [agent-id] # View agent info
1738
+ guild agent list # List agents
1739
+ guild agent list --search "github" --published # Search published agents
1740
+ guild agent search <query> # Search published agents
1720
1741
  guild agent versions [agent-id] # Version history
1721
1742
  guild agent clone <agent-id> # Clone existing agent
1743
+ guild agent fork <agent-id>:<version-id> # Fork a specific version as a new agent
1722
1744
  guild agent publish # Publish a version
1723
1745
  guild agent unpublish # Remove from catalog
1724
1746
  guild agent update # Update agent metadata
1747
+ guild agent workspaces [agent-id] # List workspaces using an agent
1725
1748
  guild agent tags list|add|remove|set # Manage agent tags
1726
1749
  guild agent revalidate # Re-run validation
1727
1750
  guild agent code [agent-id] # View agent source
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guildai/cli",
3
- "version": "0.5.11",
3
+ "version": "0.5.13",
4
4
  "description": "Guild.ai CLI - Build, test, and deploy AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -55,6 +55,7 @@
55
55
  "url": "https://docs.guild.ai/support"
56
56
  },
57
57
  "dependencies": {
58
+ "@inquirer/search": "^4.1.7",
58
59
  "@napi-rs/canvas": "^0.1.85",
59
60
  "@napi-rs/keyring": "^1.2.0",
60
61
  "axios": "^1.13.2",
@@ -1,3 +0,0 @@
1
- import { Command } from 'commander';
2
- export declare function createAgentCreateCommand(): Command;
3
- //# sourceMappingURL=create.d.ts.map
@@ -1,132 +0,0 @@
1
- // Copyright 2026 Guild.ai
2
- // SPDX-License-Identifier: Apache-2.0
3
- import { Command } from 'commander';
4
- import inquirer from 'inquirer';
5
- import { GuildAPIClient } from '../../lib/api-client.js';
6
- import { getGuildcoreUrl } from '../../lib/config.js';
7
- import { handleAxiosError, ErrorCodes } from '../../lib/errors.js';
8
- import { pollAgentStatus } from '../../lib/polling.js';
9
- import { createOutputWriter } from '../../lib/output.js';
10
- const TEMPLATE_CHOICES = [
11
- {
12
- name: 'LLM - Simple language model agent (recommended)',
13
- value: 'LLM',
14
- short: 'LLM',
15
- },
16
- {
17
- name: 'Auto-managed state - Agent with automatic state management',
18
- value: 'AUTO_MANAGED_STATE',
19
- short: 'Auto-managed state',
20
- },
21
- {
22
- name: 'Blank - Start from scratch',
23
- value: 'BLANK',
24
- short: 'Blank',
25
- },
26
- ];
27
- function isInteractive() {
28
- return process.stdin.isTTY === true;
29
- }
30
- async function promptForTemplate() {
31
- const { template } = await inquirer.prompt([
32
- {
33
- type: 'list',
34
- name: 'template',
35
- message: 'Select agent template:',
36
- choices: TEMPLATE_CHOICES,
37
- default: 'LLM',
38
- },
39
- ]);
40
- return template;
41
- }
42
- export function createAgentCreateCommand() {
43
- const cmd = new Command('create');
44
- cmd
45
- .description('Create a new agent')
46
- .argument('<name>', 'Agent name')
47
- .option('--description <text>', 'Agent description')
48
- .option('--owner-id <id>', 'Owner ID (user or organization, defaults to current user)')
49
- .option('--template <template>', 'Agent template (LLM, AUTO_MANAGED_STATE, BLANK)')
50
- .option('--no-wait', 'Return immediately without waiting for initialization')
51
- .action(async (name, options) => {
52
- const output = createOutputWriter();
53
- const baseUrl = getGuildcoreUrl();
54
- const client = new GuildAPIClient({ baseUrl });
55
- try {
56
- // Get owner_id: use --owner-id option or default to current user
57
- let ownerId = options.ownerId;
58
- if (!ownerId) {
59
- const me = (await client.get('/me'));
60
- ownerId = me.id;
61
- }
62
- // Determine template: use --template option, prompt if interactive, or error
63
- let template = options.template;
64
- if (!template) {
65
- if (isInteractive()) {
66
- template = await promptForTemplate();
67
- }
68
- else {
69
- output.error('Error: --template is required in non-interactive mode', `Provide a template:\n guild agent create ${name} --template LLM\n\nAvailable templates:\n • LLM - Simple language model agent (recommended)\n • AUTO_MANAGED_STATE - Agent with automatic state management\n • BLANK - Start from scratch`);
70
- process.exit(1);
71
- }
72
- }
73
- // Normalize template to uppercase for case-insensitive matching
74
- template = template.toUpperCase();
75
- // Validate template
76
- const validTemplates = [
77
- 'LLM',
78
- 'AUTO_MANAGED_STATE',
79
- 'BLANK',
80
- ];
81
- if (!validTemplates.includes(template)) {
82
- output.error(`Error: Invalid template '${template}'`, 'Valid templates:\n • LLM\n • AUTO_MANAGED_STATE\n • BLANK');
83
- process.exit(1);
84
- }
85
- const response = await client.post('/agents', {
86
- name,
87
- owner_id: ownerId,
88
- description: options.description || 'No description provided',
89
- template,
90
- });
91
- // By default, poll until agent is ready (unless --no-wait is passed)
92
- if (options.wait !== false) {
93
- const pollResult = await pollAgentStatus(response.id, 'READY');
94
- if (pollResult.success && pollResult.response) {
95
- // Display the updated response with READY status
96
- output.data(pollResult.response);
97
- }
98
- else {
99
- // Polling failed or timed out - this is an error
100
- output.error('Agent initialization failed or timed out', `Check status: guild agent get ${response.id}`);
101
- output.data(response);
102
- process.exit(1);
103
- }
104
- }
105
- else {
106
- // --no-wait: return immediately without polling
107
- output.data(response);
108
- }
109
- }
110
- catch (error) {
111
- const formattedError = handleAxiosError(error);
112
- // Provide specific error messages based on error code
113
- if (formattedError.code === ErrorCodes.AUTH_REQUIRED) {
114
- output.error('Not logged in.', 'Please authenticate first:\n guild auth login');
115
- }
116
- else if (formattedError.code === ErrorCodes.CONN_REFUSED) {
117
- output.error('Cannot connect to Guild servers');
118
- }
119
- else if (formattedError.code === ErrorCodes.API_ERROR &&
120
- formattedError.details.includes('already exists')) {
121
- output.error(`Agent name '${name}' already exists.`, `Try a different name:\n guild agent create ${name}-v2`);
122
- }
123
- else {
124
- // Generic error with details from server
125
- output.error(`Failed to create agent: ${formattedError.details}`);
126
- }
127
- process.exit(1);
128
- }
129
- });
130
- return cmd;
131
- }
132
- //# sourceMappingURL=create.js.map