@guildai/cli 0.9.1 → 0.11.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.
Files changed (72) hide show
  1. package/dist/commands/agent/chat.js +10 -7
  2. package/dist/commands/agent/clone.js +2 -0
  3. package/dist/commands/agent/fork.js +3 -0
  4. package/dist/commands/agent/init.js +58 -44
  5. package/dist/commands/agent/list.js +5 -4
  6. package/dist/commands/agent/logs.d.ts +3 -0
  7. package/dist/commands/agent/logs.js +62 -0
  8. package/dist/commands/agent/owners.js +3 -3
  9. package/dist/commands/agent/pull.js +8 -12
  10. package/dist/commands/agent/save.js +5 -6
  11. package/dist/commands/agent/search.js +5 -4
  12. package/dist/commands/agent/test.js +9 -6
  13. package/dist/commands/agent/update.js +9 -1
  14. package/dist/commands/agent/versions.js +5 -4
  15. package/dist/commands/agent/workspaces.js +5 -4
  16. package/dist/commands/auth/login.js +1 -3
  17. package/dist/commands/chat.d.ts +9 -0
  18. package/dist/commands/chat.js +136 -32
  19. package/dist/commands/config/get.js +4 -4
  20. package/dist/commands/config/list.js +2 -3
  21. package/dist/commands/config/path.js +2 -3
  22. package/dist/commands/config/set.js +12 -12
  23. package/dist/commands/credentials/endpoint-list.js +5 -4
  24. package/dist/commands/credentials/list.js +5 -4
  25. package/dist/commands/credentials/policy-list.js +5 -4
  26. package/dist/commands/doctor.js +5 -5
  27. package/dist/commands/integration/connect.js +2 -2
  28. package/dist/commands/integration/create.js +2 -2
  29. package/dist/commands/integration/get.js +2 -2
  30. package/dist/commands/integration/list.js +5 -4
  31. package/dist/commands/integration/operation/create.js +4 -4
  32. package/dist/commands/integration/operation/list.js +5 -4
  33. package/dist/commands/integration/update.js +2 -2
  34. package/dist/commands/integration/version/build.js +2 -2
  35. package/dist/commands/integration/version/create.js +2 -2
  36. package/dist/commands/integration/version/get.js +2 -2
  37. package/dist/commands/integration/version/list.js +5 -4
  38. package/dist/commands/integration/version/publish.js +2 -2
  39. package/dist/commands/integration/version/test.js +2 -2
  40. package/dist/commands/job/get.js +2 -2
  41. package/dist/commands/session/create.js +1 -1
  42. package/dist/commands/session/events.js +3 -2
  43. package/dist/commands/session/list.js +5 -4
  44. package/dist/commands/session/tasks.js +5 -4
  45. package/dist/commands/setup.d.ts +16 -0
  46. package/dist/commands/setup.js +76 -46
  47. package/dist/commands/trigger/list.js +5 -4
  48. package/dist/commands/trigger/sessions.js +3 -2
  49. package/dist/commands/workspace/agent/list.js +5 -4
  50. package/dist/commands/workspace/context/list.js +5 -4
  51. package/dist/commands/workspace/list.js +5 -4
  52. package/dist/index.js +15 -4
  53. package/dist/lib/api-types.d.ts +4 -0
  54. package/dist/lib/api-types.js +4 -0
  55. package/dist/lib/auth.d.ts +1 -1
  56. package/dist/lib/auth.js +2 -2
  57. package/dist/lib/output-mode.d.ts +9 -2
  58. package/dist/lib/output-mode.js +23 -2
  59. package/dist/lib/output.d.ts +7 -1
  60. package/dist/lib/output.js +36 -5
  61. package/dist/lib/owner-helpers.d.ts +3 -0
  62. package/dist/lib/owner-helpers.js +17 -10
  63. package/dist/lib/session-events.d.ts +13 -2
  64. package/dist/lib/session-events.js +15 -1
  65. package/dist/lib/session-polling.js +9 -3
  66. package/dist/lib/session-resume.d.ts +15 -1
  67. package/dist/lib/session-resume.js +149 -16
  68. package/dist/lib/splash.js +3 -2
  69. package/dist/lib/stdin.d.ts +5 -1
  70. package/dist/lib/stdin.js +8 -1
  71. package/dist/lib/version-helpers.js +24 -8
  72. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  // Copyright 2026 Guild.ai
2
2
  // SPDX-License-Identifier: Apache-2.0
3
- import { Command } from 'commander';
3
+ import { Command, Option } from 'commander';
4
4
  import { render } from 'ink';
5
5
  import React from 'react';
6
6
  import open from 'open';
@@ -31,12 +31,14 @@ export function createAgentChatCommand() {
31
31
  .argument('[prompt...]', 'Optional initial prompt for the agent')
32
32
  .option('--path <dir>', 'Path to agent directory (defaults to current directory)')
33
33
  .option('--workspace <identifier>', 'Workspace ID or full name (e.g., owner~workspace-name)')
34
- .option('--mode <format>', 'Input mode: json (one-shot) or jsonl (line-by-line)')
35
34
  .option('--agent-version <id>', 'Chat with a specific version (UUID or version number)')
36
35
  .option('--no-splash', 'Skip the splash screen animation')
37
36
  .option('--resume <session-id>', 'Resume an existing session')
38
37
  .option('--open', 'Open session in web dashboard')
39
38
  .option('--no-cache', 'Skip ephemeral build cache (force a fresh build)')
39
+ // Accept --mode so `guild agent chat --mode json` works when re-parsed.
40
+ // The actual value is read from process.argv by getOutputMode().
41
+ .addOption(new Option('--mode <format>').hideHelp())
40
42
  .addHelpText('after', '\nTo chat with a published agent by name: guild chat --agent owner~agent-name')
41
43
  .action(async (promptArgs, options) => {
42
44
  try {
@@ -45,8 +47,9 @@ export function createAgentChatCommand() {
45
47
  const { agentId, config } = await getAgentId(undefined, agentPath);
46
48
  const initialPrompt = promptArgs.length > 0 ? promptArgs.join(' ') : 'Hello';
47
49
  // If using JSON input, read it early (before auth) for fast failure on bad input
50
+ const outputMode = getOutputMode();
48
51
  let inputData;
49
- if (options.mode === 'json') {
52
+ if (outputMode === 'json') {
50
53
  try {
51
54
  inputData = await readStdinAsJSON();
52
55
  }
@@ -98,10 +101,10 @@ export function createAgentChatCommand() {
98
101
  }
99
102
  }
100
103
  // Branch: JSON/JSONL modes vs interactive
101
- if (options.mode === 'json' || options.mode === 'jsonl') {
104
+ if (outputMode === 'json' || outputMode === 'jsonl') {
102
105
  // For JSON mode with piped input, use the piped data as the initial prompt
103
106
  // so the agent receives it as its input (not the default 'Hello')
104
- const sessionPrompt = options.mode === 'json' && inputData
107
+ const sessionPrompt = outputMode === 'json' && inputData
105
108
  ? JSON.stringify(inputData)
106
109
  : initialPrompt;
107
110
  // Pre-resolve workspace so we can show the source label in output.
@@ -133,7 +136,7 @@ export function createAgentChatCommand() {
133
136
  if (options.open && session.session_url) {
134
137
  await open(session.session_url);
135
138
  }
136
- if (options.mode === 'json' && inputData) {
139
+ if (outputMode === 'json' && inputData) {
137
140
  // JSON one-shot mode — input was passed as initial_prompt during
138
141
  // session creation, so the agent already has it. Just poll for the response.
139
142
  try {
@@ -158,7 +161,7 @@ export function createAgentChatCommand() {
158
161
  process.exit(1);
159
162
  }
160
163
  }
161
- else if (options.mode === 'jsonl') {
164
+ else if (outputMode === 'jsonl') {
162
165
  // JSONL line-by-line mode
163
166
  const rl = readline.createInterface({
164
167
  input: process.stdin,
@@ -88,6 +88,8 @@ export function createAgentCloneCommand() {
88
88
  output.progress(' 3. git add . && git commit -m "your changes"');
89
89
  output.progress(" 4. Run 'guild agent save' to push and create a version");
90
90
  output.progress(` 5. Run 'guild agent test' to test your changes`);
91
+ output.progress('');
92
+ output.progress(`Tip: Using a coding agent? Run 'guild setup' to install skills for Claude Code, Codex, etc.`);
91
93
  }
92
94
  catch (error) {
93
95
  if (error instanceof GitError) {
@@ -143,6 +143,7 @@ export function createAgentForkCommand() {
143
143
  ownerFlag: options.owner,
144
144
  client,
145
145
  interactive: isInteractive(),
146
+ requireExplicitOwner: true,
146
147
  });
147
148
  // Create forked agent
148
149
  output.progress(`✓ Forking agent '${agentName}'...`);
@@ -189,6 +190,8 @@ export function createAgentForkCommand() {
189
190
  output.progress(' 2. Make your changes to the code');
190
191
  output.progress(` 3. Run 'guild agent save --message "your changes"'`);
191
192
  output.progress(` 4. Run 'guild agent test' to test your changes`);
193
+ output.progress('');
194
+ output.progress(`Tip: Using a coding agent? Run 'guild setup' to install skills for Claude Code, Codex, etc.`);
192
195
  }
193
196
  catch (error) {
194
197
  if (error instanceof GitError) {
@@ -59,6 +59,20 @@ async function promptForName(defaultName) {
59
59
  });
60
60
  return ask();
61
61
  }
62
+ async function confirmCreation(ownerName, agentName) {
63
+ const rl = readline.createInterface({
64
+ input: process.stdin,
65
+ output: process.stdout,
66
+ });
67
+ return new Promise((resolve) => {
68
+ rl.question(`\nYou are about to create agent "${ownerName}~${agentName}".\n` +
69
+ 'Warning: Owner and name cannot be changed after creation, and agents cannot be deleted.\n\n' +
70
+ 'Continue? [y/N] ', (answer) => {
71
+ rl.close();
72
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
73
+ });
74
+ });
75
+ }
62
76
  async function promptForTemplate() {
63
77
  const { default: inquirer } = await import('inquirer');
64
78
  const { template } = await inquirer.prompt([
@@ -80,17 +94,37 @@ export function createAgentInitCommand() {
80
94
  .option('--template <template>', 'Agent template (LLM, AUTO_MANAGED_STATE, BLANK)')
81
95
  .option('--fork <agent-id>', 'Fork from existing agent')
82
96
  .option('--owner <owner>', 'Owner (name or ID)')
83
- .option('--directory <path>', 'Directory to initialize (created if needed)')
97
+ .option('--directory <path>', 'Directory to initialize (default: ./<name>, created if needed)')
84
98
  .option('--force', 'Overwrite existing guild.json', false)
85
99
  .action(async (options) => {
86
- // Resolve target directory
100
+ // Get agent name first (needed to determine default directory)
101
+ let agentName = options.name;
102
+ if (!agentName) {
103
+ if (isInteractive()) {
104
+ const dirName = path.basename(process.cwd());
105
+ const defaultName = slugify(dirName);
106
+ agentName = await promptForName(defaultName);
107
+ if (!agentName) {
108
+ console.error('Error: Agent name cannot be empty');
109
+ process.exit(1);
110
+ }
111
+ }
112
+ else {
113
+ console.error('Error: Agent name is required');
114
+ console.error('');
115
+ console.error('Provide a name:');
116
+ console.error(' guild agent init --name my-agent');
117
+ console.error('');
118
+ console.error('Or run interactively to be prompted for a name.');
119
+ process.exit(1);
120
+ }
121
+ }
122
+ // Resolve target directory: --directory overrides, otherwise ./<name>
87
123
  const targetDir = options.directory
88
124
  ? path.resolve(process.cwd(), options.directory)
89
- : process.cwd();
125
+ : path.resolve(process.cwd(), agentName);
90
126
  // Create directory if it doesn't exist
91
- if (options.directory) {
92
- await fs.mkdir(targetDir, { recursive: true });
93
- }
127
+ await fs.mkdir(targetDir, { recursive: true });
94
128
  const guildJsonPath = path.join(targetDir, 'guild.json');
95
129
  try {
96
130
  // Check if already initialized
@@ -107,29 +141,6 @@ export function createAgentInitCommand() {
107
141
  console.error('To reinitialize, use: guild agent init --force');
108
142
  process.exit(1);
109
143
  }
110
- // Get agent name
111
- let agentName = options.name;
112
- if (!agentName) {
113
- if (isInteractive()) {
114
- // Use slugified directory name as default
115
- const dirName = path.basename(targetDir);
116
- const defaultName = slugify(dirName);
117
- agentName = await promptForName(defaultName);
118
- if (!agentName) {
119
- console.error('Error: Agent name cannot be empty');
120
- process.exit(1);
121
- }
122
- }
123
- else {
124
- console.error('Error: Agent name is required');
125
- console.error('');
126
- console.error('Provide a name:');
127
- console.error(' guild agent init --name my-agent');
128
- console.error('');
129
- console.error('Or run interactively to be prompted for a name.');
130
- process.exit(1);
131
- }
132
- }
133
144
  // Validate name (catches --name flag and non-interactive paths)
134
145
  if (!isValidAgentName(agentName)) {
135
146
  console.error(`Error: Invalid agent name "${agentName}"`);
@@ -176,14 +187,15 @@ export function createAgentInitCommand() {
176
187
  process.exit(1);
177
188
  }
178
189
  // Create progress tracker for initialization steps
179
- const steps = createSteps([
190
+ const stepNames = [
180
191
  'Create agent in backend',
181
192
  'Initialize git repository',
182
193
  'Configure git remote',
183
194
  'Wait for backend initialization',
184
195
  'Pull scaffolding from remote',
185
196
  'Create guild.json',
186
- ]);
197
+ ];
198
+ const steps = createSteps(stepNames);
187
199
  steps.start();
188
200
  // Step 1: Create agent in backend
189
201
  const client = new GuildAPIClient();
@@ -192,7 +204,15 @@ export function createAgentInitCommand() {
192
204
  ownerFlag: options.owner,
193
205
  client,
194
206
  interactive: isInteractive(),
207
+ requireExplicitOwner: true,
195
208
  });
209
+ if (isInteractive()) {
210
+ const confirmed = await confirmCreation(owner.name, agentName);
211
+ if (!confirmed) {
212
+ console.log('Cancelled.');
213
+ process.exit(0);
214
+ }
215
+ }
196
216
  const agent = await client.post('/agents', {
197
217
  name: agentName,
198
218
  description: `Agent created via CLI`,
@@ -346,19 +366,13 @@ export function createAgentInitCommand() {
346
366
  steps.complete('Agent initialized successfully');
347
367
  // Display next steps
348
368
  format.section('Next steps:');
349
- if (options.directory) {
350
- format.detail(`1. cd ${targetDir}`);
351
- format.detail('2. Edit agent.ts to implement your agent logic');
352
- format.detail('3. git add . && git commit -m "Initial implementation"');
353
- format.detail("4. Run 'guild agent save' to push and create a version");
354
- format.detail(`5. Run 'guild agent test' to test your agent`);
355
- }
356
- else {
357
- format.detail('1. Edit agent.ts to implement your agent logic');
358
- format.detail('2. git add . && git commit -m "Initial implementation"');
359
- format.detail("3. Run 'guild agent save' to push and create a version");
360
- format.detail(`4. Run 'guild agent test' to test your agent`);
361
- }
369
+ format.detail(`1. cd ${targetDir}`);
370
+ format.detail('2. Edit agent.ts to implement your agent logic');
371
+ format.detail('3. git add . && git commit -m "Initial implementation"');
372
+ format.detail("4. Run 'guild agent save' to push and create a version");
373
+ format.detail(`5. Run 'guild agent test' to test your agent`);
374
+ format.detail('');
375
+ format.detail(`Tip: Using a coding agent? Run 'guild setup' to install skills for Claude Code, Codex, etc.`);
362
376
  }
363
377
  catch (error) {
364
378
  if (error instanceof GitError) {
@@ -4,8 +4,9 @@ import { Command } from 'commander';
4
4
  import { GuildAPIClient } from '../../lib/api-client.js';
5
5
  import { getAuthToken } from '../../lib/auth.js';
6
6
  import { handleAxiosError, ErrorCodes } from '../../lib/errors.js';
7
- import { getOutputMode } from '../../lib/output-mode.js';
7
+ import { isMachineReadable } from '../../lib/output-mode.js';
8
8
  import { createOutputWriter, formatAgentTable } from '../../lib/output.js';
9
+ import { DEFAULT_PAGE_LIMIT } from '../../lib/api-types.js';
9
10
  const SORT_MAP = {
10
11
  updated: 'updated_at',
11
12
  newest: 'created_at',
@@ -23,8 +24,8 @@ export function createAgentListCommand() {
23
24
  .option('--all', 'Show all agents including archived')
24
25
  .option('--owner <name>', 'Filter by owner (user or org name). Without this flag, lists your own agents')
25
26
  .option('--workspace <id>', 'Filter agents by workspace ID or name')
26
- .option('--limit <number>', 'Number of results to return', '20')
27
- .option('--offset <number>', 'Offset for pagination', '0')
27
+ .option('--limit <number>', `Number of results to return (default: ${DEFAULT_PAGE_LIMIT})`, String(DEFAULT_PAGE_LIMIT))
28
+ .option('--offset <number>', 'Offset for pagination (default: 0)', '0')
28
29
  .action(async (options) => {
29
30
  const output = createOutputWriter();
30
31
  if (options.archived && options.all) {
@@ -79,7 +80,7 @@ export function createAgentListCommand() {
79
80
  if (options.archived) {
80
81
  response.items = response.items.filter((a) => a.is_archived);
81
82
  }
82
- if (getOutputMode() === 'json') {
83
+ if (isMachineReadable()) {
83
84
  output.data(response);
84
85
  }
85
86
  else {
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createAgentLogsCommand(): Command;
3
+ //# sourceMappingURL=logs.d.ts.map
@@ -0,0 +1,62 @@
1
+ // Copyright 2026 Guild.ai
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { Command } from 'commander';
4
+ import { GuildAPIClient } from '../../lib/api-client.js';
5
+ import { getGuildcoreUrl } from '../../lib/config.js';
6
+ import { handleAxiosError, ErrorCodes } from '../../lib/errors.js';
7
+ import { getAgentId, resolveAgentRef } from '../../lib/agent-helpers.js';
8
+ import { isMachineReadable } from '../../lib/output-mode.js';
9
+ import { createOutputWriter, formatValidationLogs } from '../../lib/output.js';
10
+ export function createAgentLogsCommand() {
11
+ const cmd = new Command('logs');
12
+ cmd
13
+ .description('Show build/validation logs for an agent version')
14
+ .argument('[identifier]', 'Agent ID or full name (e.g., "owner~agent-name")')
15
+ .argument('[version-id]', 'ID of the version to show logs for (uses latest if omitted)')
16
+ .action(async (agentIdArg, versionIdArg) => {
17
+ const output = createOutputWriter();
18
+ // Get agent ID from argument or guild.json
19
+ const { agentId } = await getAgentId(agentIdArg);
20
+ const baseUrl = getGuildcoreUrl();
21
+ const client = new GuildAPIClient({ baseUrl });
22
+ try {
23
+ const resolvedId = await resolveAgentRef(client, agentId);
24
+ let versionId = versionIdArg;
25
+ // If no version ID provided, get the latest version
26
+ if (!versionId) {
27
+ const versions = await client.get(`/agents/${resolvedId}/versions?limit=1&offset=0`);
28
+ if (!versions.items || versions.items.length === 0) {
29
+ output.error('No versions found for this agent.', `The agent may still be initializing. Check status:\n guild agent get ${agentId}`);
30
+ process.exit(1);
31
+ }
32
+ versionId = versions.items[0].id;
33
+ }
34
+ // Fetch validation steps for the version
35
+ const stepsResponse = await client.get(`/versions/${versionId}/validation/steps`);
36
+ if (isMachineReadable()) {
37
+ output.data(stepsResponse);
38
+ }
39
+ else {
40
+ formatValidationLogs(stepsResponse.steps);
41
+ }
42
+ }
43
+ catch (error) {
44
+ const formattedError = handleAxiosError(error);
45
+ if (formattedError.code === ErrorCodes.AUTH_REQUIRED) {
46
+ output.error('Not authenticated.', 'Please authenticate first:\n guild auth login');
47
+ }
48
+ else if (formattedError.code === ErrorCodes.CONN_REFUSED) {
49
+ output.error('Cannot connect to Guild servers');
50
+ }
51
+ else if (formattedError.code === ErrorCodes.NOT_FOUND) {
52
+ output.error('Agent or version not found', `Check the agent and version IDs:\n guild agent list\n guild agent versions ${agentId}`);
53
+ }
54
+ else {
55
+ output.error(`Failed to retrieve logs: ${formattedError.details}`);
56
+ }
57
+ process.exit(1);
58
+ }
59
+ });
60
+ return cmd;
61
+ }
62
+ //# sourceMappingURL=logs.js.map
@@ -4,7 +4,7 @@ import { Command } from 'commander';
4
4
  import chalk from 'chalk';
5
5
  import { GuildAPIClient } from '../../lib/api-client.js';
6
6
  import { handleAxiosError, ErrorCodes } from '../../lib/errors.js';
7
- import { getOutputMode } from '../../lib/output-mode.js';
7
+ import { isMachineReadable } from '../../lib/output-mode.js';
8
8
  import { createOutputWriter } from '../../lib/output.js';
9
9
  import { loadGlobalConfig } from '../../lib/guild-config.js';
10
10
  export function createAgentOwnersCommand() {
@@ -12,7 +12,7 @@ export function createAgentOwnersCommand() {
12
12
  cmd.description('List accounts that can own agents').action(async () => {
13
13
  const output = createOutputWriter();
14
14
  try {
15
- const mode = getOutputMode();
15
+ const mode = isMachineReadable();
16
16
  const client = new GuildAPIClient();
17
17
  const [me, orgs, globalConfig] = await Promise.all([
18
18
  client.get('/me'),
@@ -34,7 +34,7 @@ export function createAgentOwnersCommand() {
34
34
  is_default: defaultOwnerId === org.id,
35
35
  })),
36
36
  ];
37
- if (mode === 'json') {
37
+ if (mode) {
38
38
  output.data({ owners });
39
39
  return;
40
40
  }
@@ -3,7 +3,7 @@
3
3
  import { Command } from 'commander';
4
4
  import { GuildAPIClient } from '../../lib/api-client.js';
5
5
  import { handleAxiosError, ErrorCodes } from '../../lib/errors.js';
6
- import { getOutputMode } from '../../lib/output-mode.js';
6
+ import { isMachineReadable } from '../../lib/output-mode.js';
7
7
  import { createOutputWriter } from '../../lib/output.js';
8
8
  import * as fs from 'fs/promises';
9
9
  import * as path from 'path';
@@ -11,10 +11,7 @@ import { getAuthenticatedUrl } from '../../lib/auth.js';
11
11
  import { runGit, GitError, formatGitError } from '../../lib/git.js';
12
12
  export function createAgentPullCommand() {
13
13
  const cmd = new Command('pull');
14
- cmd
15
- .description('Pull remote changes into local agent directory')
16
- .option('--json', 'Output JSON only (no progress messages)', false)
17
- .action(async (_options) => {
14
+ cmd.description('Pull remote changes into local agent directory').action(async () => {
18
15
  const cwd = process.cwd();
19
16
  const output = createOutputWriter();
20
17
  try {
@@ -82,8 +79,7 @@ export function createAgentPullCommand() {
82
79
  ? pullError.stderr || pullError.stdout
83
80
  : String(pullError);
84
81
  // Check for rebase conflicts
85
- if (errMessage.includes('CONFLICT') ||
86
- errMessage.includes('could not apply')) {
82
+ if (errMessage.includes('CONFLICT') || errMessage.includes('could not apply')) {
87
83
  output.error('Merge conflict detected', 'Your changes conflict with remote changes.\n\nTo resolve:\n 1. Fix conflicts in the listed files\n 2. git add <resolved-files>\n 3. git rebase --continue\n\nOr abort the rebase:\n git rebase --abort');
88
84
  process.exit(1);
89
85
  }
@@ -91,7 +87,7 @@ export function createAgentPullCommand() {
91
87
  if (errMessage.includes('no tracking information') ||
92
88
  errMessage.includes("couldn't find remote ref")) {
93
89
  output.progress('✓ Already up to date (no remote branch yet)');
94
- if (getOutputMode() === 'json') {
90
+ if (isMachineReadable()) {
95
91
  output.data({
96
92
  success: true,
97
93
  message: 'Already up to date (no remote branch yet)',
@@ -113,7 +109,7 @@ export function createAgentPullCommand() {
113
109
  if (!gitPulledNewCommits) {
114
110
  output.progress('✓ Already up to date');
115
111
  }
116
- if (getOutputMode() === 'json') {
112
+ if (isMachineReadable()) {
117
113
  output.data({
118
114
  success: true,
119
115
  message: gitPulledNewCommits
@@ -126,7 +122,7 @@ export function createAgentPullCommand() {
126
122
  // SHA mismatch — warn user
127
123
  output.progress(`⚠ Remote has a newer version (${latest.sha.slice(0, 7)}) not on this branch`);
128
124
  output.progress(' Try: git fetch origin && git log --oneline origin/main');
129
- if (getOutputMode() === 'json') {
125
+ if (isMachineReadable()) {
130
126
  output.data({
131
127
  success: true,
132
128
  message: 'SHA mismatch with latest version',
@@ -151,7 +147,7 @@ export function createAgentPullCommand() {
151
147
  await fs.writeFile(filePath, file.content, 'utf-8');
152
148
  }
153
149
  output.progress(`✓ Downloaded ${files.length} files from latest draft version`);
154
- if (getOutputMode() === 'json') {
150
+ if (isMachineReadable()) {
155
151
  output.data({
156
152
  success: true,
157
153
  message: `Downloaded ${files.length} files from draft version`,
@@ -165,7 +161,7 @@ export function createAgentPullCommand() {
165
161
  if (!gitPulledNewCommits) {
166
162
  output.progress('✓ Already up to date');
167
163
  }
168
- if (getOutputMode() === 'json') {
164
+ if (isMachineReadable()) {
169
165
  output.data({
170
166
  success: true,
171
167
  message: gitPulledNewCommits
@@ -3,7 +3,7 @@
3
3
  import { Command } from 'commander';
4
4
  import { GuildAPIClient } from '../../lib/api-client.js';
5
5
  import { handleAxiosError, ErrorCodes } from '../../lib/errors.js';
6
- import { getOutputMode } from '../../lib/output-mode.js';
6
+ import { isMachineReadable } from '../../lib/output-mode.js';
7
7
  import { createOutputWriter } from '../../lib/output.js';
8
8
  import * as fs from 'fs/promises';
9
9
  import * as path from 'path';
@@ -16,11 +16,10 @@ export function createAgentSaveCommand() {
16
16
  .description('Commit, push, and create a new agent version')
17
17
  .option('-A, --all', 'Stage all changes and commit before pushing', false)
18
18
  .option('-m, --message <text>', 'Commit message (required with --all)')
19
- .option('--wait', 'Wait for validation to complete before returning', false)
20
- .option('--publish', 'Publish after validation passes (implies --wait)', false)
21
- .option('--bump [level]', 'Bump package.json version before saving (patch, or minor/major)', 'patch')
19
+ .option('--wait', 'Wait for validation to complete before returning (default: returns immediately)', false)
20
+ .option('--publish', 'Publish after validation passes (implies --wait) (default: does not publish)', false)
21
+ .option('--bump [level]', 'Bump package.json version before saving (patch, or minor/major) (default: patch)', 'patch')
22
22
  .option('--no-bump', 'Skip automatic version bump')
23
- .option('--json', 'Output JSON only (no progress messages)', false)
24
23
  .action(async (options) => {
25
24
  const cwd = process.cwd();
26
25
  let guildConfig = null;
@@ -314,7 +313,7 @@ export function createAgentSaveCommand() {
314
313
  // Output JSON to stdout only in --json mode.
315
314
  // In interactive mode the progress messages above already
316
315
  // show version details; dumping raw JSON is noise.
317
- if (getOutputMode() === 'json') {
316
+ if (isMachineReadable()) {
318
317
  output.data({ version });
319
318
  }
320
319
  }
@@ -4,8 +4,9 @@ import { Command } from 'commander';
4
4
  import { GuildAPIClient } from '../../lib/api-client.js';
5
5
  import { getAuthToken } from '../../lib/auth.js';
6
6
  import { handleAxiosError } from '../../lib/errors.js';
7
- import { getOutputMode } from '../../lib/output-mode.js';
7
+ import { isMachineReadable } from '../../lib/output-mode.js';
8
8
  import { createOutputWriter, formatAgentTable } from '../../lib/output.js';
9
+ import { DEFAULT_PAGE_LIMIT } from '../../lib/api-types.js';
9
10
  const SORT_MAP = {
10
11
  updated: 'updated_at',
11
12
  newest: 'created_at',
@@ -19,8 +20,8 @@ export function createAgentSearchCommand() {
19
20
  .argument('<query>', 'Search query')
20
21
  .option('--sort <field>', 'Sort by: updated, newest, name, popular (default: updated)', 'updated')
21
22
  .option('--published', 'Only show published agents')
22
- .option('--limit <number>', 'Number of results to return', '20')
23
- .option('--offset <number>', 'Offset for pagination', '0')
23
+ .option('--limit <number>', `Number of results to return (default: ${DEFAULT_PAGE_LIMIT})`, String(DEFAULT_PAGE_LIMIT))
24
+ .option('--offset <number>', 'Offset for pagination (default: 0)', '0')
24
25
  .action(async (query, options) => {
25
26
  const output = createOutputWriter();
26
27
  try {
@@ -42,7 +43,7 @@ export function createAgentSearchCommand() {
42
43
  params.append('sort_by', sortField);
43
44
  }
44
45
  const response = await client.get(`/agents?${params.toString()}`);
45
- if (getOutputMode() === 'json') {
46
+ if (isMachineReadable()) {
46
47
  output.data(response);
47
48
  }
48
49
  else {
@@ -1,6 +1,6 @@
1
1
  // Copyright 2026 Guild.ai
2
2
  // SPDX-License-Identifier: Apache-2.0
3
- import { Command } from 'commander';
3
+ import { Command, Option } from 'commander';
4
4
  import { render } from 'ink';
5
5
  import React from 'react';
6
6
  import { readFileSync } from 'fs';
@@ -32,13 +32,15 @@ export function createAgentTestCommand() {
32
32
  cmd
33
33
  .description('Test agent in interactive REPL session')
34
34
  .option('--workspace <identifier>', 'Workspace ID or full name (e.g., owner/workspace-name)')
35
- .option('--mode <format>', 'Input mode: json (one-shot) or jsonl (line-by-line)')
36
35
  .option('--agent-version <id>', 'Test a specific version (UUID or version number)')
37
36
  .option('--resume <session-id>', 'Resume an existing test session')
38
37
  .option('--open', 'Open session in web dashboard')
39
38
  .option('--events <types>', 'Event types to stream (default: user). Shorthands: none, user, system, all, or comma-separated type names')
40
39
  .option('--bundle <file>', 'Path to a pre-built gzip+base64 bundle file')
41
40
  .option('--no-cache', 'Skip ephemeral build cache (force a fresh build)')
41
+ // Accept --mode so `guild agent test --mode json` works when re-parsed.
42
+ // The actual value is read from process.argv by getOutputMode().
43
+ .addOption(new Option('--mode <format>').hideHelp())
42
44
  .action(async (options) => {
43
45
  const cwd = process.cwd();
44
46
  // Parse --events filter once, before any branching
@@ -85,9 +87,10 @@ export function createAgentTestCommand() {
85
87
  }
86
88
  // If using JSON/JSONL input, read and validate it early (before auth/session creation)
87
89
  // This provides better UX - fail fast on bad input without needing auth
90
+ const outputMode = getOutputMode();
88
91
  let inputData;
89
92
  let jsonlInputs;
90
- if (options.mode === 'json') {
93
+ if (outputMode === 'json') {
91
94
  try {
92
95
  inputData = await readStdinAsJSON();
93
96
  }
@@ -101,7 +104,7 @@ export function createAgentTestCommand() {
101
104
  process.exit(1);
102
105
  }
103
106
  }
104
- else if (options.mode === 'jsonl') {
107
+ else if (outputMode === 'jsonl') {
105
108
  try {
106
109
  const stdinContent = await readStdinAsText();
107
110
  jsonlInputs = [];
@@ -321,7 +324,7 @@ export function createAgentTestCommand() {
321
324
  await open(session.session_url);
322
325
  }
323
326
  // Branch: JSON input mode vs interactive REPL
324
- if (options.mode === 'json' && inputData) {
327
+ if (outputMode === 'json' && inputData) {
325
328
  // JSON input mode: one-shot test
326
329
  try {
327
330
  // Send JSON as event content (inputData already read earlier)
@@ -384,7 +387,7 @@ export function createAgentTestCommand() {
384
387
  process.exit(1);
385
388
  }
386
389
  }
387
- else if (options.mode === 'jsonl' && jsonlInputs) {
390
+ else if (outputMode === 'jsonl' && jsonlInputs) {
388
391
  // JSONL input mode: line-by-line processing (inputs already parsed and validated)
389
392
  let processedCount = 0;
390
393
  let lastEventId;
@@ -6,6 +6,7 @@ import { GuildAPIClient } from '../../lib/api-client.js';
6
6
  import { getAgentId, resolveAgentRef } from '../../lib/agent-helpers.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
  async function confirmVisibilityChange(agentName, makePublic) {
10
11
  const rl = readline.createInterface({
11
12
  input: process.stdin,
@@ -13,7 +14,7 @@ async function confirmVisibilityChange(agentName, makePublic) {
13
14
  });
14
15
  const action = makePublic ? 'PUBLIC' : 'PRIVATE';
15
16
  const warning = makePublic
16
- ? 'This will make the agent visible to everyone.'
17
+ ? 'This will make the agent visible to everyone.\nWarning: Once public, an agent cannot be made private again.'
17
18
  : 'This will hide the agent from public discovery.';
18
19
  return new Promise((resolve) => {
19
20
  rl.question(`\nAre you sure you want to make "${agentName}" ${action}?\n${warning}\n\nContinue? [y/N] `, (answer) => {
@@ -59,6 +60,13 @@ export function createAgentUpdateCommand() {
59
60
  }
60
61
  // Confirm unless --yes is specified
61
62
  if (!options.yes) {
63
+ if (!isInteractive()) {
64
+ output.error('Confirmation required for visibility changes.', 'Use --yes to skip confirmation in non-interactive mode:\n guild agent update ' +
65
+ (identifierArg ?? '') +
66
+ (makePublic ? ' --public' : ' --private') +
67
+ ' --yes');
68
+ process.exit(1);
69
+ }
62
70
  const confirmed = await confirmVisibilityChange(agent.full_name, makePublic);
63
71
  if (!confirmed) {
64
72
  output.progress('Cancelled.');
@@ -5,15 +5,16 @@ import { GuildAPIClient } from '../../lib/api-client.js';
5
5
  import { getGuildcoreUrl } from '../../lib/config.js';
6
6
  import { handleAxiosError, ErrorCodes } from '../../lib/errors.js';
7
7
  import { getAgentId, resolveAgentRef } from '../../lib/agent-helpers.js';
8
- import { getOutputMode } from '../../lib/output-mode.js';
8
+ import { isMachineReadable } from '../../lib/output-mode.js';
9
9
  import { createOutputWriter, formatVersionTable } from '../../lib/output.js';
10
+ import { DEFAULT_PAGE_LIMIT } from '../../lib/api-types.js';
10
11
  export function createAgentVersionsCommand() {
11
12
  const cmd = new Command('versions');
12
13
  cmd
13
14
  .description('List all versions of an agent')
14
15
  .argument('[identifier]', 'Agent ID or full name (e.g., owner~agent-name)')
15
- .option('--limit <number>', 'Maximum number of versions to return', '20')
16
- .option('--offset <number>', 'Number of versions to skip', '0')
16
+ .option('--limit <number>', `Maximum number of versions to return (default: ${DEFAULT_PAGE_LIMIT})`, String(DEFAULT_PAGE_LIMIT))
17
+ .option('--offset <number>', 'Number of versions to skip (default: 0)', '0')
17
18
  .action(async (agentIdArg, options) => {
18
19
  const output = createOutputWriter();
19
20
  // Get agent ID from argument or guild.json
@@ -25,7 +26,7 @@ export function createAgentVersionsCommand() {
25
26
  try {
26
27
  const resolvedId = await resolveAgentRef(client, agentId);
27
28
  const result = await client.get(`/agents/${resolvedId}/versions?limit=${limit}&offset=${offset}`);
28
- if (getOutputMode() === 'json') {
29
+ if (isMachineReadable()) {
29
30
  output.data(result);
30
31
  }
31
32
  else {