@guildai/cli 0.3.18 → 0.5.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/README.md CHANGED
@@ -28,8 +28,12 @@ guild agent init --name my-agent
28
28
  ### 3. Develop and Save
29
29
 
30
30
  ```bash
31
- # Edit agent.ts, then save
32
- guild agent save --message "Initial implementation"
31
+ # Edit agent.ts, then commit and save
32
+ git add . && git commit -m "Initial implementation"
33
+ guild agent save
34
+
35
+ # Or stage+commit+push in one step
36
+ guild agent save -A --message "Initial implementation"
33
37
  ```
34
38
 
35
39
  ### 4. Test Your Agent
@@ -54,7 +58,8 @@ guild auth status # Check authentication status
54
58
 
55
59
  ```bash
56
60
  guild agent init --name my-agent # Initialize a new agent
57
- guild agent save --message "..." # Save changes
61
+ guild agent save # Push commits and create a version
62
+ guild agent save -A --message "..." # Stage+commit+push in one step
58
63
  guild agent versions # List versions
59
64
  guild agent publish # Publish latest draft version
60
65
  guild agent unpublish # Unpublish latest published version
@@ -19,7 +19,7 @@ import { runGit } from '../../lib/git.js';
19
19
  import { suppressScrollbackClear } from '../../lib/alternate-screen.js';
20
20
  import * as readline from 'readline';
21
21
  import { pollForResponse } from '../../lib/session-polling.js';
22
- import { readStdinAsJSON } from '../../lib/stdin.js';
22
+ import { readStdinAsJSON, ensureInteractiveStdin } from '../../lib/stdin.js';
23
23
  import { fetchSession, fetchSessionEvents } from '../../lib/session-resume.js';
24
24
  // ESM equivalent of __dirname
25
25
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -266,6 +266,7 @@ export function createAgentChatCommand() {
266
266
  else {
267
267
  // Interactive mode: use shared ChatApp with full features
268
268
  // (splash animation, task panel, status line, etc.)
269
+ ensureInteractiveStdin('guild agent chat');
269
270
  const resumeCommand = 'guild agent chat';
270
271
  // Handle --resume: fetch existing session + events
271
272
  let resumeSession;
@@ -5,7 +5,7 @@ import { handleAxiosError, ErrorCodes } from '../../lib/errors.js';
5
5
  import * as fs from 'fs/promises';
6
6
  import * as path from 'path';
7
7
  import { getAuthenticatedUrl } from '../../lib/auth.js';
8
- import { runGit, GitError, formatGitError } from '../../lib/git.js';
8
+ import { runGit, GitError, formatGitError, installPrePushHook } from '../../lib/git.js';
9
9
  import { createOutputWriter } from '../../lib/output.js';
10
10
  async function isDirectoryEmpty(dirPath) {
11
11
  try {
@@ -57,6 +57,8 @@ export function createAgentCloneCommand() {
57
57
  }
58
58
  await runGit(['clone', cloneUrl, targetDir]);
59
59
  output.progress(`✓ Cloned repository to ${targetDir}`);
60
+ await installPrePushHook(targetDir);
61
+ output.progress('✓ Installed pre-push hook');
60
62
  // Create guild.json if it doesn't exist
61
63
  const guildJsonPath = path.join(targetDir, 'guild.json');
62
64
  const guildJsonExists = await fs
@@ -80,8 +82,9 @@ export function createAgentCloneCommand() {
80
82
  output.progress('Next steps:');
81
83
  output.progress(` 1. cd ${targetDir}`);
82
84
  output.progress(' 2. Make your changes to the code');
83
- output.progress(` 3. Run 'guild agent save --message "your changes"'`);
84
- output.progress(` 4. Run 'guild agent test' to test your changes`);
85
+ output.progress(' 3. git add . && git commit -m "your changes"');
86
+ output.progress(" 4. Run 'guild agent save' to push and create a version");
87
+ output.progress(` 5. Run 'guild agent test' to test your changes`);
85
88
  }
86
89
  catch (error) {
87
90
  if (error instanceof GitError) {
@@ -8,7 +8,7 @@ import * as fs from 'fs/promises';
8
8
  import * as path from 'path';
9
9
  import * as readline from 'readline';
10
10
  import { getAuthenticatedUrl } from '../../lib/auth.js';
11
- import { runGit, GitError, formatGitError } from '../../lib/git.js';
11
+ import { runGit, GitError, formatGitError, installPrePushHook } from '../../lib/git.js';
12
12
  import { resolveOwnerId } from '../../lib/owner-helpers.js';
13
13
  const TEMPLATE_CHOICES = [
14
14
  {
@@ -64,15 +64,23 @@ function isInteractive() {
64
64
  export function createAgentInitCommand() {
65
65
  const cmd = new Command('init');
66
66
  cmd
67
- .description('Initialize current directory as a Guild agent')
67
+ .description('Initialize a directory as a Guild agent')
68
68
  .option('--name <name>', 'Agent name')
69
69
  .option('--template <template>', 'Agent template (LLM, AUTO_MANAGED_STATE, BLANK)')
70
70
  .option('--fork <agent-id>', 'Fork from existing agent')
71
71
  .option('--owner <owner-id>', 'Owner account (user or organization ID)')
72
+ .option('--directory <path>', 'Directory to initialize (created if needed)')
72
73
  .option('--force', 'Overwrite existing guild.json', false)
73
74
  .action(async (options) => {
74
- const cwd = process.cwd();
75
- const guildJsonPath = path.join(cwd, 'guild.json');
75
+ // Resolve target directory
76
+ const targetDir = options.directory
77
+ ? path.resolve(process.cwd(), options.directory)
78
+ : process.cwd();
79
+ // Create directory if it doesn't exist
80
+ if (options.directory) {
81
+ await fs.mkdir(targetDir, { recursive: true });
82
+ }
83
+ const guildJsonPath = path.join(targetDir, 'guild.json');
76
84
  try {
77
85
  // Check if already initialized
78
86
  const guildJsonExists = await fs
@@ -93,7 +101,7 @@ export function createAgentInitCommand() {
93
101
  if (!agentName) {
94
102
  if (isInteractive()) {
95
103
  // Use slugified directory name as default
96
- const dirName = path.basename(cwd);
104
+ const dirName = path.basename(targetDir);
97
105
  const defaultName = slugify(dirName);
98
106
  agentName = await promptForName(defaultName);
99
107
  if (!agentName) {
@@ -176,13 +184,13 @@ export function createAgentInitCommand() {
176
184
  format.detail(`Agent: ${agent.name} (${agent.id})`);
177
185
  format.detail(`Owner: ${owner.name}`);
178
186
  // Step 2: Initialize git repository if needed
179
- const gitDir = path.join(cwd, '.git');
187
+ const gitDir = path.join(targetDir, '.git');
180
188
  const gitExists = await fs
181
189
  .access(gitDir)
182
190
  .then(() => true)
183
191
  .catch(() => false);
184
192
  if (!gitExists) {
185
- await runGit(['init'], { cwd });
193
+ await runGit(['init'], { cwd: targetDir });
186
194
  steps.succeed('Initialize git repository');
187
195
  }
188
196
  else {
@@ -192,13 +200,17 @@ export function createAgentInitCommand() {
192
200
  // Step 3: Configure git remote
193
201
  try {
194
202
  // Check if remote 'origin' exists
195
- await runGit(['remote', 'get-url', 'origin'], { cwd });
203
+ await runGit(['remote', 'get-url', 'origin'], { cwd: targetDir });
196
204
  // Remote exists, update it
197
- await runGit(['remote', 'set-url', 'origin', agent.git_url], { cwd });
205
+ await runGit(['remote', 'set-url', 'origin', agent.git_url], {
206
+ cwd: targetDir,
207
+ });
198
208
  }
199
209
  catch {
200
210
  // Remote doesn't exist, add it
201
- await runGit(['remote', 'add', 'origin', agent.git_url], { cwd });
211
+ await runGit(['remote', 'add', 'origin', agent.git_url], {
212
+ cwd: targetDir,
213
+ });
202
214
  }
203
215
  steps.succeed('Configure git remote');
204
216
  format.detail(`Remote: ${agent.git_url}`);
@@ -242,7 +254,7 @@ export function createAgentInitCommand() {
242
254
  console.error(` guild agent get ${agent.id}`);
243
255
  console.error('');
244
256
  console.error('Or wait and try pulling the scaffolding manually:');
245
- console.error(` cd ${cwd}`);
257
+ console.error(` cd ${targetDir}`);
246
258
  console.error(' git pull origin main');
247
259
  process.exit(1);
248
260
  }
@@ -258,7 +270,7 @@ export function createAgentInitCommand() {
258
270
  }
259
271
  for (let attempt = 1; attempt <= pullMaxAttempts; attempt++) {
260
272
  try {
261
- await runGit(['pull', authenticatedGitUrl, 'main'], { cwd });
273
+ await runGit(['pull', authenticatedGitUrl, 'main'], { cwd: targetDir });
262
274
  steps.succeed('Pull scaffolding from remote');
263
275
  break;
264
276
  }
@@ -288,7 +300,7 @@ export function createAgentInitCommand() {
288
300
  await fs.writeFile(guildJsonPath, JSON.stringify(guildConfig, null, 2) + '\n', 'utf-8');
289
301
  steps.succeed('Create guild.json');
290
302
  // Add guild.json to .gitignore if not already present
291
- const gitignorePath = path.join(cwd, '.gitignore');
303
+ const gitignorePath = path.join(targetDir, '.gitignore');
292
304
  try {
293
305
  const gitignoreContent = await fs.readFile(gitignorePath, 'utf-8');
294
306
  if (!gitignoreContent.includes('guild.json')) {
@@ -299,13 +311,25 @@ export function createAgentInitCommand() {
299
311
  // .gitignore doesn't exist (backend should have created it), create it
300
312
  await fs.writeFile(gitignorePath, 'guild.json\n');
301
313
  }
314
+ // Install pre-push hook to block direct git push
315
+ await installPrePushHook(targetDir);
302
316
  // Complete progress tracking
303
317
  steps.complete('Agent initialized successfully');
304
318
  // Display next steps
305
319
  format.section('Next steps:');
306
- format.detail('1. Edit agent.ts to implement your agent logic');
307
- format.detail(`2. Run 'guild agent save --message "Initial implementation"'`);
308
- format.detail(`3. Run 'guild agent test' to test your agent`);
320
+ if (options.directory) {
321
+ format.detail(`1. cd ${targetDir}`);
322
+ format.detail('2. Edit agent.ts to implement your agent logic');
323
+ format.detail('3. git add . && git commit -m "Initial implementation"');
324
+ format.detail("4. Run 'guild agent save' to push and create a version");
325
+ format.detail(`5. Run 'guild agent test' to test your agent`);
326
+ }
327
+ else {
328
+ format.detail('1. Edit agent.ts to implement your agent logic');
329
+ format.detail('2. git add . && git commit -m "Initial implementation"');
330
+ format.detail("3. Run 'guild agent save' to push and create a version");
331
+ format.detail(`4. Run 'guild agent test' to test your agent`);
332
+ }
309
333
  }
310
334
  catch (error) {
311
335
  if (error instanceof GitError) {
@@ -12,8 +12,9 @@ import { pollUntilComplete } from '../../lib/polling.js';
12
12
  export function createAgentSaveCommand() {
13
13
  const cmd = new Command('save');
14
14
  cmd
15
- .description('Save agent code by committing and syncing with guildcore')
16
- .option('--message <text>', 'Commit message describing the changes')
15
+ .description('Push committed changes to Guild and create a version')
16
+ .option('-A, --all', 'Stage all changes and commit before pushing', false)
17
+ .option('--message <text>', 'Commit message (required with --all)')
17
18
  .option('--wait', 'Wait for validation to complete before returning', false)
18
19
  .option('--publish', 'Publish after validation passes (implies --wait)', false)
19
20
  .option('--json', 'Output JSON only (no progress messages)', false)
@@ -21,9 +22,9 @@ export function createAgentSaveCommand() {
21
22
  const cwd = process.cwd();
22
23
  let guildConfig = null;
23
24
  const output = createOutputWriter();
24
- // Check for --message
25
- if (!options.message) {
26
- output.error('Commit message is required', 'Provide a message describing your changes:\n guild agent save --message "Add new feature"');
25
+ // With --all, a commit message is required
26
+ if (options.all && !options.message) {
27
+ output.error('Commit message is required with --all', 'Provide a message describing your changes:\n guild agent save -A --message "Add new feature"');
27
28
  process.exit(1);
28
29
  }
29
30
  try {
@@ -39,45 +40,60 @@ export function createAgentSaveCommand() {
39
40
  }
40
41
  // Read guild.json
41
42
  guildConfig = JSON.parse(await fs.readFile(guildJsonPath, 'utf-8'));
42
- // Check for uncommitted changes
43
+ // Check for uncommitted changes and unpushed commits
43
44
  const { stdout: statusOutput } = await runGit(['status', '--porcelain'], {
44
45
  cwd,
45
46
  });
46
47
  const hasUncommittedChanges = statusOutput.trim().length > 0;
47
48
  let hasUnpushedCommits = false;
48
- if (!hasUncommittedChanges) {
49
- // Working tree is clean — check for unpushed commits from a
50
- // previous save that committed locally but failed to push.
49
+ // Check for unpushed commits
50
+ try {
51
+ const { stdout: branchForCheck } = await runGit(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd });
52
+ const { stdout: unpushed } = await runGit(['log', `origin/${branchForCheck.trim()}..HEAD`, '--oneline'], { cwd });
53
+ hasUnpushedCommits = unpushed.trim().length > 0;
54
+ }
55
+ catch {
56
+ // No remote tracking branch yet — if HEAD has commits, treat
57
+ // as unpushed so the push flow can create the remote branch.
51
58
  try {
52
- const { stdout: branchForCheck } = await runGit(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd });
53
- const { stdout: unpushed } = await runGit(['log', `origin/${branchForCheck.trim()}..HEAD`, '--oneline'], { cwd });
54
- hasUnpushedCommits = unpushed.trim().length > 0;
59
+ const { stdout: logOutput } = await runGit(['log', '--oneline', '-1'], {
60
+ cwd,
61
+ });
62
+ hasUnpushedCommits = logOutput.trim().length > 0;
55
63
  }
56
64
  catch {
57
- // No remote tracking branch yet — if HEAD has commits, treat
58
- // as unpushed so the push flow can create the remote branch.
59
- try {
60
- const { stdout: logOutput } = await runGit(['log', '--oneline', '-1'], {
61
- cwd,
62
- });
63
- hasUnpushedCommits = logOutput.trim().length > 0;
64
- }
65
- catch {
66
- hasUnpushedCommits = false;
67
- }
65
+ hasUnpushedCommits = false;
68
66
  }
69
- if (!hasUnpushedCommits) {
70
- output.error('No changes to commit', 'Working tree is clean. Make changes to your code before saving.');
71
- process.exit(1);
67
+ }
68
+ if (options.all) {
69
+ // --all: stage and commit before pushing
70
+ if (hasUncommittedChanges) {
71
+ await runGit(['add', '-A'], { cwd });
72
+ output.progress('✓ Staged changes');
73
+ await runGit(['commit', '-m', options.message], { cwd });
74
+ output.progress('✓ Committed locally');
75
+ hasUnpushedCommits = true;
72
76
  }
73
- output.progress('✓ Found unpushed commits, resuming push...');
74
77
  }
75
78
  else {
76
- // Stage and commit new changes
77
- await runGit(['add', '.'], { cwd });
78
- output.progress(' Staged changes');
79
- await runGit(['commit', '-m', options.message], { cwd });
80
- output.progress('✓ Committed locally');
79
+ // Default: push-only, no staging or committing
80
+ if (!hasUnpushedCommits && hasUncommittedChanges) {
81
+ output.error('Uncommitted changes', 'You have uncommitted changes. Either commit them first:\n git add . && git commit -m "your message"\n\nOr use --all to stage and commit automatically:\n guild agent save -A --message "your message"');
82
+ process.exit(1);
83
+ }
84
+ }
85
+ if (!hasUnpushedCommits) {
86
+ output.error('No changes to save', 'Working tree is clean and there are no unpushed commits.');
87
+ process.exit(1);
88
+ }
89
+ // Resolve version summary: use --message if provided, otherwise
90
+ // extract the latest commit message.
91
+ let versionMessage = options.message;
92
+ if (!versionMessage) {
93
+ const { stdout: commitMsg } = await runGit(['log', '-1', '--pretty=%s'], {
94
+ cwd,
95
+ });
96
+ versionMessage = commitMsg.trim();
81
97
  }
82
98
  // Get remote URL with auth credentials
83
99
  const { stdout: remoteUrl } = await runGit(['remote', 'get-url', 'origin'], {
@@ -94,8 +110,9 @@ export function createAgentSaveCommand() {
94
110
  });
95
111
  const currentBranch = branchName.trim();
96
112
  // Pull remote changes first (rebase to avoid merge commits)
113
+ const gitEnv = { GUILD_AGENT_SAVE: '1' };
97
114
  try {
98
- const { stdout: pullOutput } = await runGit(['pull', '--rebase', authenticatedUrl, currentBranch], { cwd });
115
+ const { stdout: pullOutput } = await runGit(['pull', '--rebase', authenticatedUrl, currentBranch], { cwd, env: gitEnv });
99
116
  if (pullOutput.includes('Updating') ||
100
117
  pullOutput.includes('Fast-forward')) {
101
118
  output.progress('✓ Synced with remote');
@@ -132,6 +149,7 @@ export function createAgentSaveCommand() {
132
149
  try {
133
150
  await runGit(['push', authenticatedUrl, `HEAD:${currentBranch}`], {
134
151
  cwd,
152
+ env: gitEnv,
135
153
  });
136
154
  break;
137
155
  }
@@ -180,7 +198,7 @@ export function createAgentSaveCommand() {
180
198
  const client = new GuildAPIClient();
181
199
  let version = await client.post(`/agents/${guildConfig.agent_id}/versions`, {
182
200
  commit_sha: commitSha,
183
- summary: options.message,
201
+ summary: versionMessage,
184
202
  version_type: 'COMMITTED',
185
203
  });
186
204
  output.progress(`✓ Created version (${version.id})`);
@@ -226,7 +244,7 @@ export function createAgentSaveCommand() {
226
244
  failureDetails = 'Could not fetch validation details.';
227
245
  }
228
246
  failureDetails +=
229
- '\n\nFix the issues, then save a new version:\n guild agent save --message "Fix validation errors"';
247
+ '\n\nFix the issues, commit, and save a new version:\n git add . && git commit -m "Fix validation errors"\n guild agent save';
230
248
  output.error('Validation failed', failureDetails);
231
249
  process.exit(1);
232
250
  }
@@ -14,7 +14,7 @@ import { showBetaGuidance } from '../../lib/auth.js';
14
14
  import * as readline from 'readline';
15
15
  import { isQuietMode, getOutputMode } from '../../lib/output-mode.js';
16
16
  import { pollForResponse } from '../../lib/session-polling.js';
17
- import { readStdinAsJSON } from '../../lib/stdin.js';
17
+ import { readStdinAsJSON, ensureInteractiveStdin } from '../../lib/stdin.js';
18
18
  import { loadLocalConfig, getWorkspaceId } from '../../lib/guild-config.js';
19
19
  import { runGit, GitError, formatGitError } from '../../lib/git.js';
20
20
  import { pollUntilComplete } from '../../lib/polling.js';
@@ -415,6 +415,7 @@ export function createAgentTestCommand() {
415
415
  process.exit(0);
416
416
  }
417
417
  // Interactive mode: use shared ChatApp (spinner, progress, prompt)
418
+ ensureInteractiveStdin('guild agent test');
418
419
  const resumeCommand = 'guild agent test';
419
420
  const isInteractive = getOutputMode() === 'interactive' && !isQuietMode();
420
421
  if (isInteractive) {
@@ -16,6 +16,7 @@ import { isUnfulfilledAgentInstallRequest, isFilteredTaskName, getTaskDisplayNam
16
16
  import { printResumeHint, fetchSession, fetchSessionEvents, eventsToDisplayMessages, } from '../lib/session-resume.js';
17
17
  import { AgentInstallPrompt } from '../components/AgentInstallPrompt.js';
18
18
  import { getWorkspaceId } from '../lib/guild-config.js';
19
+ import { ensureInteractiveStdin } from '../lib/stdin.js';
19
20
  import { brand, BRAND_COLOR, code as codeColor } from '../lib/colors.js';
20
21
  import { SplashAnimation } from '../components/SplashAnimation.js';
21
22
  import { LOADING_TIMINGS } from '../lib/loading-messages.js';
@@ -1062,6 +1063,7 @@ export function createChatCommand() {
1062
1063
  }
1063
1064
  else {
1064
1065
  // Interactive mode - check auth first, then render UI with splash animation
1066
+ ensureInteractiveStdin('guild chat');
1065
1067
  await ensureAuthenticated();
1066
1068
  // Build the resume command string for exit hints
1067
1069
  let resumeCommand = 'guild chat';
@@ -13,7 +13,7 @@ export function createWorkspaceContextPublishCommand() {
13
13
  const output = createOutputWriter();
14
14
  try {
15
15
  const client = new GuildAPIClient();
16
- await client.patch(`/workspaces/${workspaceId}/contexts/${contextId}`, {
16
+ await client.patch(`/contexts/${contextId}`, {
17
17
  status: 'PUBLISHED',
18
18
  });
19
19
  output.success(`Context ${contextId} published successfully`);
package/dist/index.js CHANGED
@@ -67,6 +67,7 @@ import { createDoctorCommand } from './commands/doctor.js';
67
67
  import { createSetupCommand } from './commands/setup.js';
68
68
  import { showSplashScreen, shouldShowSplash } from './lib/splash.js';
69
69
  import { checkForUpdate } from './lib/update-check.js';
70
+ import { setupUnknownCommandSuggestions } from './lib/did-you-mean.js';
70
71
  import chalk from 'chalk';
71
72
  // ESM equivalent of __dirname
72
73
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -216,6 +217,8 @@ configCmd.addCommand(createConfigListCommand());
216
217
  configCmd.addCommand(createConfigGetCommand());
217
218
  configCmd.addCommand(createConfigSetCommand());
218
219
  configCmd.addCommand(createConfigPathCommand());
220
+ // Set up cross-level "Did you mean?" suggestions for unknown commands
221
+ setupUnknownCommandSuggestions(program);
219
222
  // Parse arguments
220
223
  program.parse(process.argv);
221
224
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,25 @@
1
+ import { Command } from 'commander';
2
+ interface CommandEntry {
3
+ path: string;
4
+ description: string;
5
+ leafName: string;
6
+ }
7
+ /**
8
+ * Recursively collects all commands from the command tree.
9
+ * Includes both leaf commands and command groups (parents with subcommands).
10
+ * Aliases are added as separate entries.
11
+ */
12
+ export declare function getAllCommands(cmd: Command, prefix?: string): CommandEntry[];
13
+ /**
14
+ * Standard Levenshtein distance between two strings.
15
+ */
16
+ export declare function levenshtein(a: string, b: string): number;
17
+ /**
18
+ * Sets up cross-level "Did you mean?" suggestions for unknown commands.
19
+ *
20
+ * Disables Commander's built-in same-level suggestions and intercepts
21
+ * "unknown command" errors to search the entire command tree.
22
+ */
23
+ export declare function setupUnknownCommandSuggestions(program: Command): void;
24
+ export {};
25
+ //# sourceMappingURL=did-you-mean.d.ts.map
@@ -0,0 +1,143 @@
1
+ // Copyright (c) 2026 Guild.ai All Rights Reserved
2
+ /**
3
+ * Recursively collects all commands from the command tree.
4
+ * Includes both leaf commands and command groups (parents with subcommands).
5
+ * Aliases are added as separate entries.
6
+ */
7
+ export function getAllCommands(cmd, prefix = '') {
8
+ const results = [];
9
+ for (const sub of cmd.commands) {
10
+ const name = sub.name();
11
+ const cmdPath = prefix ? `${prefix} ${name}` : name;
12
+ const description = sub.description();
13
+ const hasChildren = sub.commands.length > 0;
14
+ // Always include this command (even if it has subcommands)
15
+ results.push({ path: cmdPath, description, leafName: name });
16
+ // If this command has subcommands, also recurse into them
17
+ if (hasChildren) {
18
+ results.push(...getAllCommands(sub, cmdPath));
19
+ }
20
+ // Add aliases as separate entries
21
+ for (const alias of sub.aliases()) {
22
+ const aliasPath = prefix ? `${prefix} ${alias}` : alias;
23
+ results.push({ path: aliasPath, description, leafName: alias });
24
+ if (hasChildren) {
25
+ results.push(...getAllCommands(sub, aliasPath));
26
+ }
27
+ }
28
+ }
29
+ return results;
30
+ }
31
+ /**
32
+ * Standard Levenshtein distance between two strings.
33
+ */
34
+ export function levenshtein(a, b) {
35
+ const m = a.length;
36
+ const n = b.length;
37
+ // Use a single-row DP approach for space efficiency
38
+ const row = Array.from({ length: n + 1 }, (_, i) => i);
39
+ for (let i = 1; i <= m; i++) {
40
+ let prev = i - 1;
41
+ row[0] = i;
42
+ for (let j = 1; j <= n; j++) {
43
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
44
+ const val = Math.min(row[j] + 1, // deletion
45
+ row[j - 1] + 1, // insertion
46
+ prev + cost // substitution
47
+ );
48
+ prev = row[j];
49
+ row[j] = val;
50
+ }
51
+ }
52
+ return row[n];
53
+ }
54
+ /**
55
+ * Recursively disables Commander's built-in suggestion on all commands.
56
+ */
57
+ function disableBuiltinSuggestions(cmd) {
58
+ cmd.showSuggestionAfterError(false);
59
+ for (const sub of cmd.commands) {
60
+ disableBuiltinSuggestions(sub);
61
+ }
62
+ }
63
+ /**
64
+ * Creates the writeErr handler that intercepts "unknown command" errors
65
+ * and prints cross-level suggestions.
66
+ */
67
+ function createWriteErrHandler(program) {
68
+ return (str) => {
69
+ // Always write the original error
70
+ process.stderr.write(str);
71
+ // Check if this is an "unknown command" error
72
+ const match = /unknown command '([^']+)'/.exec(str);
73
+ if (!match) {
74
+ return;
75
+ }
76
+ const unknown = match[1];
77
+ const allCommands = getAllCommands(program);
78
+ // Determine the typed parent context from process.argv
79
+ // e.g., "guild agent trigger" → typed args are ["agent", "trigger"]
80
+ const typedArgs = process.argv.slice(2);
81
+ const typedParent = typedArgs.slice(0, -1).join(' ');
82
+ // Score each command
83
+ const scored = allCommands.map((entry) => {
84
+ const leafName = entry.leafName;
85
+ // Exact leaf name match gets score 0
86
+ let score = leafName === unknown ? 0 : levenshtein(unknown, leafName);
87
+ // Context bonus: if command's parent matches typed parent, reduce score
88
+ const entryParts = entry.path.split(' ');
89
+ const entryParent = entryParts.slice(0, -1).join(' ');
90
+ if (entryParent === typedParent && score > 0) {
91
+ score -= 0.5;
92
+ }
93
+ return { ...entry, score };
94
+ });
95
+ // Filter poor matches
96
+ const threshold = Math.max(2, unknown.length / 2);
97
+ const filtered = scored.filter((s) => s.score <= threshold);
98
+ if (filtered.length === 0) {
99
+ return;
100
+ }
101
+ // Sort by score, deduplicate by path
102
+ filtered.sort((a, b) => a.score - b.score);
103
+ const seen = new Set();
104
+ const unique = filtered.filter((entry) => {
105
+ if (seen.has(entry.path)) {
106
+ return false;
107
+ }
108
+ seen.add(entry.path);
109
+ return true;
110
+ });
111
+ // Print top 5 suggestions
112
+ const top = unique.slice(0, 5);
113
+ process.stderr.write('\nDid you mean?\n');
114
+ for (const entry of top) {
115
+ const cmd = `guild ${entry.path}`;
116
+ const padding = ' '.repeat(Math.max(2, 30 - cmd.length));
117
+ process.stderr.write(` ${cmd}${padding}${entry.description}\n`);
118
+ }
119
+ };
120
+ }
121
+ /**
122
+ * Recursively configures writeErr on a command and all its subcommands.
123
+ */
124
+ function configureOutputRecursively(cmd, writeErr) {
125
+ cmd.configureOutput({ writeErr });
126
+ for (const sub of cmd.commands) {
127
+ configureOutputRecursively(sub, writeErr);
128
+ }
129
+ }
130
+ /**
131
+ * Sets up cross-level "Did you mean?" suggestions for unknown commands.
132
+ *
133
+ * Disables Commander's built-in same-level suggestions and intercepts
134
+ * "unknown command" errors to search the entire command tree.
135
+ */
136
+ export function setupUnknownCommandSuggestions(program) {
137
+ // Disable Commander's built-in suggestion on all commands
138
+ disableBuiltinSuggestions(program);
139
+ // Create a shared writeErr handler and apply it to all commands
140
+ const writeErr = createWriteErrHandler(program);
141
+ configureOutputRecursively(program, writeErr);
142
+ }
143
+ //# sourceMappingURL=did-you-mean.js.map
@@ -1,4 +1,4 @@
1
- export declare const WEBHOOK_SERVICES: readonly ["AZURE_DEVOPS", "BITBUCKET", "CYPRESS", "GITHUB", "GOOGLE_DOCS", "GOOGLE_LOGGING", "JIRA", "LINEAR", "NEWRELIC", "NOTION", "SLACK", "TESTRAIL", "ZENDESK"];
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"];
2
2
  export type WebhookService = (typeof WEBHOOK_SERVICES)[number];
3
3
  export declare const TIME_TRIGGER_FREQUENCIES: readonly ["HOURLY", "DAILY", "WEEKLY", "MONTHLY"];
4
4
  export type TimeTriggerFrequency = (typeof TIME_TRIGGER_FREQUENCIES)[number];
@@ -13,6 +13,7 @@ export const WEBHOOK_SERVICES = [
13
13
  'CYPRESS',
14
14
  'GITHUB',
15
15
  'GOOGLE_DOCS',
16
+ 'GOOGLE_COMPUTE',
16
17
  'GOOGLE_LOGGING',
17
18
  'JIRA',
18
19
  'LINEAR',
package/dist/lib/git.d.ts CHANGED
@@ -6,6 +6,8 @@ export interface GitOptions {
6
6
  cwd?: string;
7
7
  /** Timeout in milliseconds (default: 30000 for most commands) */
8
8
  timeout?: number;
9
+ /** Extra environment variables to pass to the git subprocess */
10
+ env?: Record<string, string>;
9
11
  }
10
12
  /**
11
13
  * Result from a git command
@@ -57,4 +59,11 @@ export declare function runGit(args: string[], options?: GitOptions): Promise<Gi
57
59
  * Returns a user-friendly error message with the git output.
58
60
  */
59
61
  export declare function formatGitError(error: GitError): string;
62
+ /**
63
+ * Install a pre-push hook that blocks direct git push.
64
+ *
65
+ * The hook allows pushes only when the GUILD_AGENT_SAVE env var is set,
66
+ * which guild agent save sets automatically.
67
+ */
68
+ export declare function installPrePushHook(targetDir: string): Promise<void>;
60
69
  //# sourceMappingURL=git.d.ts.map
package/dist/lib/git.js CHANGED
@@ -6,6 +6,8 @@
6
6
  * by preventing interactive prompts and providing clear error messages.
7
7
  */
8
8
  import { execa } from 'execa';
9
+ import * as fs from 'fs/promises';
10
+ import * as path from 'path';
9
11
  import { debug } from './errors.js';
10
12
  /**
11
13
  * Error thrown when a git command fails
@@ -71,7 +73,7 @@ const NETWORK_COMMANDS = ['clone', 'push', 'pull', 'fetch'];
71
73
  * }
72
74
  */
73
75
  export async function runGit(args, options = {}) {
74
- const { cwd, timeout: customTimeout } = options;
76
+ const { cwd, timeout: customTimeout, env: extraEnv } = options;
75
77
  // Determine timeout based on command type
76
78
  const isNetworkCommand = args.length > 0 && NETWORK_COMMANDS.includes(args[0]);
77
79
  const timeout = customTimeout ?? (isNetworkCommand ? NETWORK_TIMEOUT : DEFAULT_TIMEOUT);
@@ -88,6 +90,7 @@ export async function runGit(args, options = {}) {
88
90
  GIT_TERMINAL_PROMPT: '0',
89
91
  // Prevent SSH from prompting for passwords/passphrases
90
92
  GIT_SSH_COMMAND: 'ssh -o BatchMode=yes',
93
+ ...extraEnv,
91
94
  },
92
95
  });
93
96
  }
@@ -149,4 +152,24 @@ export function formatGitError(error) {
149
152
  }
150
153
  return lines.join('\n');
151
154
  }
155
+ const PRE_PUSH_HOOK = `#!/bin/sh
156
+ if [ "$GUILD_AGENT_SAVE" != "1" ]; then
157
+ echo "Please use \\\`guild agent save\\\` to push."
158
+ exit 1
159
+ fi
160
+ exit 0
161
+ `;
162
+ /**
163
+ * Install a pre-push hook that blocks direct git push.
164
+ *
165
+ * The hook allows pushes only when the GUILD_AGENT_SAVE env var is set,
166
+ * which guild agent save sets automatically.
167
+ */
168
+ export async function installPrePushHook(targetDir) {
169
+ const hooksDir = path.join(targetDir, '.git', 'hooks');
170
+ await fs.mkdir(hooksDir, { recursive: true });
171
+ await fs.writeFile(path.join(hooksDir, 'pre-push'), PRE_PUSH_HOOK, {
172
+ mode: 0o755,
173
+ });
174
+ }
152
175
  //# sourceMappingURL=git.js.map
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Exit with a helpful error when stdin is piped but no --mode flag was given.
3
+ * Call this before rendering an interactive UI (Ink render()) so users who
4
+ * accidentally pipe input without --mode get clear guidance instead of a hang.
5
+ */
6
+ export declare function ensureInteractiveStdin(command: string): void;
1
7
  /**
2
8
  * Read JSON from stdin with a timeout.
3
9
  */
package/dist/lib/stdin.js CHANGED
@@ -1,4 +1,22 @@
1
1
  // Copyright (c) 2026 Guild.ai All Rights Reserved
2
+ /**
3
+ * Exit with a helpful error when stdin is piped but no --mode flag was given.
4
+ * Call this before rendering an interactive UI (Ink render()) so users who
5
+ * accidentally pipe input without --mode get clear guidance instead of a hang.
6
+ */
7
+ export function ensureInteractiveStdin(command) {
8
+ if (!process.stdin.isTTY) {
9
+ console.error('Error: stdin is piped but --mode was not specified');
10
+ console.error('');
11
+ console.error('When piping input, use --mode to set the input format:');
12
+ console.error(` echo '{"prompt":"hello"}' | ${command} --mode json`);
13
+ console.error(` cat inputs.jsonl | ${command} --mode jsonl`);
14
+ console.error('');
15
+ console.error('Or run interactively (no pipe):');
16
+ console.error(` ${command}`);
17
+ process.exit(1);
18
+ }
19
+ }
2
20
  /**
3
21
  * Read JSON from stdin with a timeout.
4
22
  */
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: Guild CLI Workflow
3
+ description: Agent development using the Guild CLI. Activated when user mentions guild agent commands, saving/publishing agents, clone/pull workflow, or agent testing. Covers CLI commands and common workflows.
4
+ ---
5
+
1
6
  # Guild CLI Agent Development Workflow
2
7
 
3
8
  For local agent development using the Guild CLI. This workflow manages agent code via the Guild git server.
@@ -8,13 +13,18 @@ For local agent development using the Guild CLI. This workflow manages agent cod
8
13
 
9
14
  ```bash
10
15
  # Create a new agent
11
- guild agent init --name my-agent --template LLM
16
+ guild agent create my-agent --template LLM
12
17
 
13
18
  # Clone an existing agent
14
- guild agent clone guildai/slack-assistant
19
+ guild agent clone guildai/dev-assistant
20
+ cd dev-assistant
21
+
22
+ # Save changes (pushes commits to Guild server)
23
+ git add . && git commit -m "Description of changes"
24
+ guild agent save
15
25
 
16
- # Save changes (commits and syncs to Guild server)
17
- guild agent save --message "Description of changes"
26
+ # Or stage+commit+push in one step
27
+ guild agent save -A --message "Description of changes"
18
28
 
19
29
  # Save and publish
20
30
  guild agent save --message "Description" --wait --publish
@@ -31,11 +41,9 @@ guild agent save --message "Description" --wait --publish
31
41
  ## NEVER Do These Things
32
42
 
33
43
  - ❌ Manually create `package.json`, `tsconfig.json`, or `guild.json`
34
- - ❌ Run `git push` directly (use `guild agent save`)
44
+ - ❌ Run `git push` directly (use `guild agent save` — a pre-push hook blocks direct pushes)
35
45
  - ❌ Run `git pull` directly (use `guild agent pull`)
36
- - ❌ Run `git commit` directly (use `guild agent save`)
37
46
  - ❌ Edit `guild.json` (it's generated and gitignored)
38
- - ❌ Push to GitHub (agents sync to Guild's git server)
39
47
 
40
48
  ## Common Commands
41
49
 
@@ -45,31 +53,32 @@ guild agent save --message "Description" --wait --publish
45
53
  # Install Guild CLI skills for coding assistants (Claude Code, etc.)
46
54
  guild setup
47
55
 
48
- # Install skills and create a CLAUDE.md template
56
+ # Also create a CLAUDE.md template
49
57
  guild setup --claude-md
50
58
  ```
51
59
 
52
60
  ### Creating Agents
53
61
 
54
62
  ```bash
55
- # Interactive creation
56
- guild agent init --name my-agent
57
-
58
- # With specific template
59
- guild agent init --name my-agent --template LLM
60
- guild agent init --name my-agent --template COMPILED
61
- guild agent init --name my-agent --template BLANK
63
+ # Create with template (interactive — prompts for template if omitted)
64
+ guild agent create my-agent
65
+ guild agent create my-agent --template LLM
66
+ guild agent create my-agent --template AUTO_MANAGED_STATE
67
+ guild agent create my-agent --template BLANK
62
68
 
63
69
  # Fork an existing agent
64
- guild agent init --fork guildai/slack-assistant
70
+ guild agent init --fork owner/agent-name
71
+
72
+ # Clone to work on an existing agent
73
+ guild agent clone owner/agent-name
65
74
  ```
66
75
 
67
76
  ### Working with Existing Agents
68
77
 
69
78
  ```bash
70
79
  # Clone to work on an agent
71
- guild agent clone guildai/slack-assistant
72
- cd slack-assistant
80
+ guild agent clone guildai/dev-assistant
81
+ cd dev-assistant
73
82
 
74
83
  # Pull remote changes (from collaborators or web edits)
75
84
  guild agent pull
@@ -87,15 +96,21 @@ guild agent grep "pattern" --published
87
96
 
88
97
  ### Saving Changes
89
98
 
99
+ Git owns the working tree, Guild owns the remote. Use normal git commands to stage and commit, then `guild agent save` to push and create a version.
100
+
90
101
  ```bash
91
- # Save without publishing (creates draft)
92
- guild agent save --message "WIP: still testing"
102
+ # Commit with git, then push via Guild (creates draft)
103
+ git add . && git commit -m "WIP: still testing"
104
+ guild agent save
105
+
106
+ # Or stage+commit+push in one step
107
+ guild agent save -A --message "WIP: still testing"
93
108
 
94
109
  # Save and wait for validation
95
110
  guild agent save --message "Fix bug" --wait
96
111
 
97
112
  # Save, validate, and publish
98
- guild agent save --message "Release v1.0" --wait --publish
113
+ guild agent save -A --message "Release v1.0" --wait --publish
99
114
  ```
100
115
 
101
116
  ### Publishing
@@ -114,19 +129,22 @@ guild agent versions --limit 1
114
129
  # Interactive test session
115
130
  guild agent test
116
131
 
132
+ # Test uncommitted changes without saving
133
+ guild agent test --ephemeral
134
+
117
135
  # Test with specific input
118
136
  guild agent chat "Hello, can you help me?"
119
137
  ```
120
138
 
121
139
  ## File Structure
122
140
 
123
- After `guild agent init`, you get:
141
+ After `guild agent create`, you get:
124
142
 
125
143
  ```
126
144
  my-agent/
127
145
  ├── .git/ # Git repo (remote is Guild server)
128
146
  ├── .gitignore # Includes guild.json
129
- ├── agent.ts # Your agent code
147
+ ├── agent.ts # Your agent code (default; can also be in src/)
130
148
  ├── package.json # Dependencies
131
149
  ├── tsconfig.json # TypeScript config
132
150
  └── guild.json # Agent ID (gitignored, local only)
@@ -141,30 +159,21 @@ my-agent/
141
159
 
142
160
  ## Troubleshooting
143
161
 
144
- ### "No changes to commit"
145
-
146
- You already committed. Just push:
162
+ ### "No changes to save"
147
163
 
148
- ```bash
149
- git push origin main
150
- ```
164
+ Working tree is clean and there are no unpushed commits. Make a code change, commit it, then run `guild agent save` again.
151
165
 
152
166
  ### "guild.json not found"
153
167
 
154
168
  You're not in an agent directory. Either:
155
169
 
156
170
  - `cd` into the agent directory
157
- - Run `guild agent init` to create one
171
+ - Run `guild agent create` to create one
158
172
 
159
173
  ### Validation Failed
160
174
 
161
- Check the error in `guild agent versions --limit 1`. Common issues:
175
+ Check the error with `guild agent versions --limit 1`. Common issues:
162
176
 
163
177
  - TypeScript compilation errors
164
178
  - Missing dependencies
165
179
  - Invalid schema
166
-
167
- ## See Also
168
-
169
- - [Agent Development Guide](https://github.com/agents-for-dev/guildai__agent-builder__019a8e0d-5280-726e-0000-b896bbbc2320/blob/main/docs/AGENT_DEVELOPMENT.md) - Comprehensive patterns and SDK usage
170
- - SDK: `@guildai/agents-sdk`
@@ -103,9 +103,10 @@ Press `Ctrl+C` to exit.
103
103
  ### 4. Save and publish
104
104
 
105
105
  ```bash
106
- guild agent save --message "First version" --wait --publish
106
+ guild agent save -A --message "First version" --wait --publish
107
107
  ```
108
108
 
109
+ - `-A` stages and commits all changes before pushing
109
110
  - `--wait` blocks until validation passes
110
111
  - `--publish` makes the agent available in the Guild catalog
111
112
 
@@ -288,16 +289,25 @@ guild agent chat "Hello" # Send a single message
288
289
 
289
290
  ### 3. Save your work
290
291
 
292
+ Commit with git, then push via Guild:
293
+
294
+ ```bash
295
+ git add . && git commit -m "Add Slack notifications"
296
+ guild agent save
297
+ ```
298
+
299
+ Or stage+commit+push in one step:
300
+
291
301
  ```bash
292
- guild agent save --message "Add Slack notifications"
302
+ guild agent save -A --message "Add Slack notifications"
293
303
  ```
294
304
 
295
- This commits your code and creates a new version in the Guild backend. Versions start as drafts.
305
+ This pushes your commits and creates a new version in the Guild backend. Versions start as drafts.
296
306
 
297
307
  ### 4. Publish
298
308
 
299
309
  ```bash
300
- guild agent save --message "Ready to ship" --wait --publish
310
+ guild agent save -A --message "Ready to ship" --wait --publish
301
311
  ```
302
312
 
303
313
  `--wait` blocks until validation passes. `--publish` makes the agent available in the catalog. You can also publish separately:
@@ -319,7 +329,7 @@ guild agent code # View source of latest version
319
329
  - Agent code lives in `agent.ts` (typically at the project root, but can be in a subdirectory like `src/`).
320
330
  - Don't add `@guildai/agents-sdk`, `zod`, or `@guildai-services/*` to `package.json`. The runtime provides them. Only add third-party packages you actually use.
321
331
  - Always call tools through `task.tools.<name>(args)`. Never access services directly.
322
- - Always use `guild agent save` to commit and `guild agent pull` to sync. Don't use raw git commands.
332
+ - Use `git add` and `git commit` to manage your working tree. Use `guild agent save` to push and create versions. Don't use `git push` directly (a pre-push hook blocks it).
323
333
  - Don't edit `guild.json` — it's managed by the CLI.
324
334
 
325
335
  ## Other Commands
@@ -396,13 +406,9 @@ guild agent get
396
406
  guild agent get <agent-id>
397
407
  ```
398
408
 
399
- ### "No changes to commit" after a failed save
400
-
401
- If a previous `guild agent save` committed locally but failed to push (e.g., network error), just run save again. It detects the unpushed commits and resumes:
409
+ ### "No changes to save"
402
410
 
403
- ```bash
404
- guild agent save --message "Retry"
405
- ```
411
+ Working tree is clean and there are no unpushed commits. Make a code change, commit it, then run `guild agent save` again. If a previous save committed locally but failed to push (e.g., network error), just run `guild agent save` again — it detects the unpushed commits and resumes.
406
412
 
407
413
  ### Validation failures
408
414
 
@@ -412,8 +418,9 @@ After saving, if validation fails:
412
418
  # Check the latest version for errors
413
419
  guild agent versions --limit 1
414
420
 
415
- # Fix the issue and save again
416
- guild agent save --message "Fix validation error" --wait
421
+ # Fix the issue, commit, and save again
422
+ git add . && git commit -m "Fix validation error"
423
+ guild agent save --wait
417
424
  ```
418
425
 
419
426
  ### Agent test not responding
@@ -421,7 +428,7 @@ guild agent save --message "Fix validation error" --wait
421
428
  If `guild agent test` hangs or produces no output:
422
429
 
423
430
  1. Check your agent code compiles: look for TypeScript errors in `agent.ts`
424
- 2. Make sure you've saved at least once: `guild agent save --message "initial"`
431
+ 2. Make sure you've saved at least once: `guild agent save -A --message "initial"`
425
432
  3. Try a single message instead: `guild agent chat "hello"`
426
433
 
427
434
  ## Next Steps
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: Guild Agent Development
3
- description: Agent development using the Guild CLI. Activated when user mentions creating agents, guild agent commands, saving/publishing agents, or agent development workflow. Handles proper CLI workflow and prevents direct git operations.
3
+ description: Local agent development using the Guild CLI. Activated when user mentions creating agents, guild agent commands, saving/publishing agents, or agent development workflow. Handles proper CLI workflow and prevents direct git operations.
4
4
  ---
5
5
 
6
6
  # Guild Agent Development
7
7
 
8
- Build agents for Guild using the CLI. **Always use the Guild CLI for agent operations never use raw git commands.**
8
+ Build agents for Guild using the CLI. **Always use the Guild CLI for agent operations - never use raw git commands.**
9
9
 
10
10
  ## When to Use This
11
11
 
@@ -33,7 +33,7 @@ guild setup --claude-md
33
33
  ### Creating Agents
34
34
 
35
35
  ```bash
36
- # Create from template (interactive prompts for template)
36
+ # Create from template (interactive - prompts for template)
37
37
  guild agent create my-agent
38
38
 
39
39
  # Create with specific template
@@ -53,18 +53,24 @@ guild agent clone owner/agent-name
53
53
 
54
54
  ### Syncing and Saving
55
55
 
56
+ Git owns the working tree, Guild owns the remote. Use normal git commands to stage and commit, then `guild agent save` to push and create a version.
57
+
56
58
  ```bash
57
59
  # Pull remote changes (e.g., edits from other collaborators)
58
60
  guild agent pull
59
61
 
60
- # Save without publishing (creates draft)
61
- guild agent save --message "Description of changes"
62
+ # Commit with git, then push via Guild (creates draft)
63
+ git add . && git commit -m "Description of changes"
64
+ guild agent save
65
+
66
+ # Or stage+commit+push in one step
67
+ guild agent save -A --message "Description of changes"
62
68
 
63
69
  # Save and wait for validation
64
70
  guild agent save --message "Fix bug" --wait
65
71
 
66
72
  # Save, validate, and publish
67
- guild agent save --message "Release v1.0" --wait --publish
73
+ guild agent save -A --message "Release v1.0" --wait --publish
68
74
  ```
69
75
 
70
76
  ### Testing
@@ -80,18 +86,41 @@ guild agent test --ephemeral
80
86
  guild agent chat "Hello, can you help me?"
81
87
  ```
82
88
 
83
- ## Use the Guild CLI for All Agent Operations
89
+ ## Guild CLI Is the ONLY Tool for Agent Operations
90
+
91
+ **ALL agent work — creating, saving, testing, debugging, investigating — goes through Guild CLI.**
84
92
 
85
- **All agent work creating, saving, testing, debugging — goes through Guild CLI.**
93
+ ### For Creating and Modifying
86
94
 
87
95
  - ✅ `guild agent create`, `guild agent init`, `guild agent clone`
88
- - ✅ `guild agent save --message "description"`
96
+ - ✅ `git add`, `git commit` (manage your own working tree)
97
+ - ✅ `guild agent save` (push commits and create a version)
98
+ - ✅ `guild agent save -A --message "desc"` (stage+commit+push in one step)
89
99
  - ✅ `guild agent pull` (sync remote changes into local directory)
90
100
  - ✅ `guild agent test`, `guild agent chat`
91
- - `guild agent get`, `guild agent versions`, `guild agent code`, `guild agent grep`
92
- - ❌ NEVER use `git commit`, `git push`, `git pull` for agent operations
101
+ - NEVER use `git push` directly (a pre-push hook blocks this — use `guild agent save`)
102
+ - ❌ NEVER use `gh repo` for agent operations
93
103
  - ❌ NEVER manually create `package.json`, `tsconfig.json`, or `guild.json`
94
104
 
105
+ ### For Investigating and Debugging
106
+
107
+ - ✅ `guild agent clone <id>` to get agent source locally
108
+ - ✅ `guild agent versions <id>` to check version history
109
+ - ✅ `guild agent code <id>` to view source
110
+ - ✅ `guild agent get <id>` to view agent info
111
+ - ✅ `guild agent grep <pattern>` to search across all agent code
112
+ - ✅ Read local clones created by `guild agent clone`
113
+ - ❌ NEVER use `git clone`, `gh repo`, or direct API calls for agent source — always use Guild CLI
114
+
115
+ ### If Guild CLI Can't Do Something
116
+
117
+ **STOP and tell the user:**
118
+
119
+ 1. What you need to do
120
+ 2. Why Guild CLI can't do it
121
+ 3. Why you think `gh`/`git` is needed
122
+ 4. Let the user decide — never reach for `gh`/`git` on your own
123
+
95
124
  ---
96
125
 
97
126
  ## SDK Reference
@@ -118,12 +147,14 @@ import { ask, output, callTools } from '@guildai/agents-sdk';
118
147
  // Platform tools (from SDK)
119
148
  import { guildTools, userInterfaceTools } from '@guildai/agents-sdk';
120
149
 
121
- // Service tools (from separate packages NOT from SDK)
150
+ // Service tools (from separate packages - NOT from SDK)
122
151
  import { gitHubTools } from '@guildai-services/guildai~github';
123
152
  import { slackTools } from '@guildai-services/guildai~slack';
124
153
  import { jiraTools } from '@guildai-services/guildai~jira';
125
154
  import { bitbucketTools } from '@guildai-services/guildai~bitbucket';
126
155
  import { azureDevOpsTools } from '@guildai-services/guildai~azure-devops';
156
+ import { pipedreamTools } from '@guildai-services/guildai~pipedream';
157
+ import { cypressTools } from '@guildai-services/guildai~cypress';
127
158
 
128
159
  // Utilities
129
160
  import { pick, progressLogNotifyEvent } from '@guildai/agents-sdk';
@@ -141,12 +172,16 @@ Service tools are in separate `@guildai-services/*` packages. The runtime resolv
141
172
 
142
173
  | Service | Package | Export | Tool Name Prefix |
143
174
  | -------------- | ---------------------------------------- | -------------------- | ---------------- |
144
- | GitHub | `@guildai-services/guildai~github` | `gitHubTools` | `github_` |
145
- | Slack | `@guildai-services/guildai~slack` | `slackTools` | `slack_` |
146
- | Jira | `@guildai-services/guildai~jira` | `jiraTools` | `jira_` |
147
- | Bitbucket | `@guildai-services/guildai~bitbucket` | `bitbucketTools` | `bitbucket_` |
148
175
  | Azure DevOps | `@guildai-services/guildai~azure-devops` | `azureDevOpsTools` | `azure_devops_` |
176
+ | Bitbucket | `@guildai-services/guildai~bitbucket` | `bitbucketTools` | `bitbucket_` |
177
+ | Cypress | `@guildai-services/guildai~cypress` | `cypressTools` | `cypress_` |
178
+ | GitHub | `@guildai-services/guildai~github` | `gitHubTools` | `github_` |
149
179
  | Guild | `@guildai/agents-sdk` | `guildTools` | `guild_` |
180
+ | Jira | `@guildai-services/guildai~jira` | `jiraTools` | `jira_` |
181
+ | Linear | `@guildai-services/guildai~linear` | `linearTools` | `linear_` |
182
+ | NewRelic | `@guildai-services/guildai~newrelic` | `newrelicTools` | `newrelic_` |
183
+ | Pipedream | `@guildai-services/guildai~pipedream` | `pipedreamTools` | `pipedream_` |
184
+ | Slack | `@guildai-services/guildai~slack` | `slackTools` | `slack_` |
150
185
  | User Interface | `@guildai/agents-sdk` | `userInterfaceTools` | `ui_` |
151
186
 
152
187
  ### Tool Access via `task.tools.*`
@@ -169,7 +204,10 @@ const issues = await task.tools.jira_search_and_reconsile_issues_using_jql({
169
204
  });
170
205
 
171
206
  // User interface
172
- const response = await task.tools.ui_prompt({ type: 'text', text: 'What repo?' });
207
+ const response = await task.tools.ui_prompt({
208
+ type: 'text',
209
+ text: 'What repo?',
210
+ });
173
211
  await task.tools.ui_notify(progressLogNotifyEvent('Processing...'));
174
212
 
175
213
  // Guild
@@ -525,7 +563,7 @@ await task.tools.slack_chat_post_message({
525
563
 
526
564
  ### NEVER `pick()` from `guildTools`
527
565
 
528
- **Always spread `guildTools` fully. NEVER use `pick(guildTools, [...])`.**
566
+ **CRITICAL: Always spread `guildTools` fully. NEVER use `pick(guildTools, [...])`.**
529
567
 
530
568
  The SDK's `Task` type conditionally provides `task.guild: GuildService` based on whether the **full** `guildTools` set is in the tools type. Using `pick()` creates a subset type that doesn't satisfy this constraint, causing:
531
569
 
@@ -630,7 +668,7 @@ export default agent({ run: async (input, task) => { ... } })
630
668
  }
631
669
  ```
632
670
 
633
- **Important:**
671
+ **CRITICAL:**
634
672
 
635
673
  - Do NOT add `@guildai/agents-sdk`, `@guildai-services/*`, or `zod` to dependencies. The runtime provides them.
636
674
  - DO add third-party ESM-compatible packages your agent uses to `dependencies`. Note: CJS-only packages (e.g., `slackify-markdown`) will break in the ESM agent runtime — use inline alternatives instead.
@@ -658,10 +696,10 @@ my-agent/
658
696
 
659
697
  ## Version Lifecycle
660
698
 
661
- 1. **Draft** After `guild agent save` (no `--publish`)
662
- 2. **Validating** After `--publish`, running validation
663
- 3. **Published** Validation passed, available for use
664
- 4. **Failed** Validation failed, check errors
699
+ 1. **Draft** - After `guild agent save` (no `--publish`)
700
+ 2. **Validating** - After `--publish`, running validation
701
+ 3. **Published** - Validation passed, available for use
702
+ 4. **Failed** - Validation failed, check errors
665
703
 
666
704
  ## CLI Commands
667
705
 
@@ -672,9 +710,10 @@ guild agent create <name> # Create new agent (prompts f
672
710
  guild agent create <name> --template LLM # Create with specific template
673
711
  guild agent init # Initialize local workspace
674
712
  guild agent init --fork <agent-id> # Fork existing agent
675
- guild agent save --message "description" # Save changes
676
- guild agent save --message "v1.0" --wait --publish # Save + validate + publish
677
713
  guild agent pull # Pull remote changes
714
+ guild agent save # Push commits and create a draft version
715
+ guild agent save -A --message "description" # Stage+commit+push in one step
716
+ guild agent save --message "v1.0" --wait --publish # Save + validate + publish
678
717
  guild agent test # Interactive test
679
718
  guild agent test --ephemeral # Ephemeral test
680
719
  guild agent chat "Hello" # Test with input
@@ -689,13 +728,22 @@ guild agent revalidate # Re-run validation
689
728
  guild agent code [agent-id] # View agent source
690
729
  guild agent grep <pattern> # Search agent code files for a regex pattern
691
730
  guild agent grep <pattern> --published # Search only published agents
731
+ guild agent owners # List accounts that can own agents
732
+ guild workspace select # Set default workspace (writes to guild.json if in agent dir)
733
+ ```
734
+
735
+ ### Environment Variable Overrides
736
+
737
+ ```bash
738
+ GUILD_WORKSPACE_ID=<id> guild agent test # Override workspace for this command
739
+ GUILD_OWNER_ID=<id> guild agent init --name my-agent # Override owner for agent creation
692
740
  ```
693
741
 
694
742
  ## Troubleshooting
695
743
 
696
- ### "No changes to commit"
744
+ ### "No changes to save"
697
745
 
698
- All changes are already committed. Make a code change first, then run `guild agent save` again.
746
+ Working tree is clean and there are no unpushed commits. Make a code change, commit it, then run `guild agent save` again.
699
747
 
700
748
  ### "guild.json not found"
701
749
 
@@ -719,5 +767,5 @@ If `guild.json` is tracked in git (it shouldn't be):
719
767
  ```bash
720
768
  echo "guild.json" >> .gitignore
721
769
  git rm --cached guild.json
722
- guild agent save --message "fix: Add guild.json to gitignore"
770
+ guild agent save -A --message "fix: Add guild.json to gitignore"
723
771
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guildai/cli",
3
- "version": "0.3.18",
3
+ "version": "0.5.0",
4
4
  "description": "Guild.ai CLI - Build, test, and deploy AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",