@guildai/cli 0.6.2 → 0.7.1

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 (55) hide show
  1. package/README.md +3 -1
  2. package/dist/commands/agent/chat.js +41 -99
  3. package/dist/commands/agent/clone.js +1 -1
  4. package/dist/commands/agent/code.js +1 -1
  5. package/dist/commands/agent/fork.js +2 -2
  6. package/dist/commands/agent/get.js +1 -1
  7. package/dist/commands/agent/grep.js +2 -2
  8. package/dist/commands/agent/init.js +10 -3
  9. package/dist/commands/agent/list.js +9 -1
  10. package/dist/commands/agent/publish.js +1 -1
  11. package/dist/commands/agent/revalidate.js +1 -1
  12. package/dist/commands/agent/save.js +23 -2
  13. package/dist/commands/agent/test.js +70 -130
  14. package/dist/commands/agent/unpublish.js +1 -1
  15. package/dist/commands/agent/update.js +1 -1
  16. package/dist/commands/agent/versions.js +1 -1
  17. package/dist/commands/agent/workspaces.js +1 -1
  18. package/dist/commands/chat.d.ts +2 -1
  19. package/dist/commands/chat.js +189 -88
  20. package/dist/commands/config/list.js +2 -2
  21. package/dist/commands/integration/operation/create.js +2 -2
  22. package/dist/commands/integration/operation/list.js +2 -2
  23. package/dist/commands/integration/update.js +1 -1
  24. package/dist/commands/integration/version/get.js +2 -2
  25. package/dist/commands/integration/version/publish.js +2 -2
  26. package/dist/commands/integration/version/test.js +2 -2
  27. package/dist/commands/session/events.js +7 -3
  28. package/dist/commands/session/interrupt.d.ts +3 -0
  29. package/dist/commands/session/interrupt.js +33 -0
  30. package/dist/commands/setup.js +70 -11
  31. package/dist/commands/workspace/get.js +1 -1
  32. package/dist/commands/workspace/list.js +28 -6
  33. package/dist/commands/workspace/select.js +40 -9
  34. package/dist/components/TaskView.js +2 -2
  35. package/dist/index.js +2 -0
  36. package/dist/lib/agent-helpers.d.ts +59 -2
  37. package/dist/lib/agent-helpers.js +153 -8
  38. package/dist/lib/alternate-screen.js +2 -0
  39. package/dist/lib/api-client.js +2 -1
  40. package/dist/lib/config.d.ts +3 -0
  41. package/dist/lib/config.js +33 -0
  42. package/dist/lib/event-filter.d.ts +50 -0
  43. package/dist/lib/event-filter.js +91 -0
  44. package/dist/lib/generated-types.d.ts +2 -0
  45. package/dist/lib/generated-types.js +20 -0
  46. package/dist/lib/session-events.d.ts +27 -2
  47. package/dist/lib/session-events.js +5 -3
  48. package/dist/lib/session-polling.d.ts +8 -0
  49. package/dist/lib/session-polling.js +49 -0
  50. package/dist/lib/spinners.js +4 -1
  51. package/docs/CLI_WORKFLOW.md +7 -1
  52. package/docs/DESIGN.md +1 -1
  53. package/docs/skills/codex-agent-dev.md +155 -0
  54. package/docs/skills/integrations.md +338 -0
  55. package/package.json +1 -1
package/README.md CHANGED
@@ -229,6 +229,8 @@ guild version # Show version info
229
229
  ```bash
230
230
  guild setup # Install skills + configure MCP server
231
231
  guild setup --claude-md # Also create a CLAUDE.md template
232
+ guild setup --codex # Install Codex skills + configure MCP server
233
+ guild setup --codex --agents-md # Also create an AGENTS.md template
232
234
  guild setup --no-mcp # Install skills only, skip MCP configuration
233
235
  guild mcp # Start MCP server (used by Claude Code, etc.)
234
236
  ```
@@ -243,7 +245,7 @@ The CLI includes an [MCP](https://modelcontextprotocol.io/) server that exposes
243
245
  guild setup
244
246
  ```
245
247
 
246
- This installs Claude Code skills and adds a `guild` entry to `.mcp.json` in your project. Claude Code (and other MCP-compatible tools) will automatically connect when they detect it.
248
+ This installs Claude Code skills and adds a `guild` entry to `.mcp.json` in your project. Use `guild setup --codex` to install Codex skills instead. Claude Code and other MCP-compatible tools will automatically connect when they detect `.mcp.json`.
247
249
 
248
250
  **What it provides:**
249
251
 
@@ -5,7 +5,7 @@ import { render } from 'ink';
5
5
  import React from 'react';
6
6
  import open from 'open';
7
7
  import { hyperlink } from '../../lib/colors.js';
8
- import { getAgentId, readAgentFiles } from '../../lib/agent-helpers.js';
8
+ import { getAgentId, readAgentFiles, buildEphemeralVersion, BuildTimeoutError, BuildFailedError, } from '../../lib/agent-helpers.js';
9
9
  import { ChatApp, createSession, ensureAuthenticated } from '../chat.js';
10
10
  import { readFileSync } from 'fs';
11
11
  import path from 'path';
@@ -15,8 +15,6 @@ import { isQuietMode, getOutputMode } from '../../lib/output-mode.js';
15
15
  import { handleAxiosError, ErrorCodes } from '../../lib/errors.js';
16
16
  import { format } from '../../lib/progress.js';
17
17
  import { showBetaGuidance } from '../../lib/auth.js';
18
- import { pollUntilComplete } from '../../lib/polling.js';
19
- import { runGit } from '../../lib/git.js';
20
18
  import { suppressScrollbackClear } from '../../lib/alternate-screen.js';
21
19
  import * as readline from 'readline';
22
20
  import { pollForResponse } from '../../lib/session-polling.js';
@@ -32,13 +30,14 @@ export function createAgentChatCommand() {
32
30
  .description('Chat with the agent in current directory')
33
31
  .argument('[prompt...]', 'Optional initial prompt for the agent')
34
32
  .option('--path <dir>', 'Path to agent directory (defaults to current directory)')
35
- .option('--workspace <identifier>', 'Workspace ID or full name (e.g., owner/workspace-name)')
33
+ .option('--workspace <identifier>', 'Workspace ID or full name (e.g., owner~workspace-name)')
36
34
  .option('--mode <format>', 'Input mode: json (one-shot) or jsonl (line-by-line)')
37
- .option('--ephemeral', 'Chat using unsaved changes (no guild agent save required)')
35
+ .option('--agent-version <id>', 'Chat with a specific version (UUID or version number)')
38
36
  .option('--no-splash', 'Skip the splash screen animation')
39
37
  .option('--resume <session-id>', 'Resume an existing session')
40
38
  .option('--open', 'Open session in web dashboard')
41
- .addHelpText('after', '\nTo chat with a published agent by name: guild chat --agent owner/agent-name')
39
+ .option('--no-cache', 'Skip ephemeral build cache (force a fresh build)')
40
+ .addHelpText('after', '\nTo chat with a published agent by name: guild chat --agent owner~agent-name')
42
41
  .action(async (promptArgs, options) => {
43
42
  try {
44
43
  // Get agent ID from guild.json in the specified path
@@ -67,106 +66,36 @@ export function createAgentChatCommand() {
67
66
  const quiet = isQuietMode();
68
67
  const resolvedPath = path.resolve(agentPath);
69
68
  // Resolve the version to chat with.
70
- // Default: use the latest saved version (requires clean working tree).
71
- // --ephemeral: read local files and create an ephemeral version.
69
+ // Default: create ephemeral version from working directory.
70
+ // --version: use a specific existing version.
72
71
  let version;
73
- if (options.ephemeral) {
74
- if (!quiet) {
75
- console.error('Building agent...');
76
- }
77
- const agentFiles = await readAgentFiles(resolvedPath);
78
- version = (await client.post(`/agents/${agentId}/versions`, {
79
- files: agentFiles,
80
- summary: '[Chat] Ephemeral development version',
81
- version_type: 'EPHEMERAL',
82
- }));
83
- }
84
- else {
85
- // Require clean working tree
86
- const { stdout: statusOutput } = await runGit(['status', '--porcelain'], {
87
- cwd: resolvedPath,
88
- });
89
- if (statusOutput.trim().length > 0) {
90
- console.error('Error: Working tree has unsaved changes');
91
- console.error('');
92
- console.error('Options:');
93
- console.error(' 1. Save your changes first:');
94
- console.error(' guild agent save --message "your changes"');
95
- console.error('');
96
- console.error(' 2. Chat with unsaved changes:');
97
- console.error(' guild agent chat --ephemeral');
98
- process.exit(1);
99
- }
100
- // Get commit SHA and look for an existing version
101
- const { stdout: shaOutput } = await runGit(['rev-parse', 'HEAD'], {
102
- cwd: resolvedPath,
103
- });
104
- const commitSha = shaOutput.trim();
72
+ let ephemeralCached = false;
73
+ if (options.agentVersion) {
74
+ // Explicit version: look it up by ID or version number
105
75
  const existingVersions = (await client.get(`/agents/${agentId}/versions`));
106
- const existing = existingVersions.items.find((v) => v.sha === commitSha);
107
- if (existing) {
108
- version = existing;
109
- }
110
- else {
111
- if (!quiet) {
112
- console.error('Building agent...');
113
- }
114
- version = (await client.post(`/agents/${agentId}/versions`, {
115
- commit_sha: commitSha,
116
- summary: '[Chat] Development version',
117
- version_type: 'COMMITTED',
118
- }));
119
- }
120
- }
121
- // Wait for build to complete (skip if version already validated)
122
- if (version.validation_status === 'PENDING' ||
123
- version.validation_status === 'RUNNING') {
124
- const pollResult = await pollUntilComplete({
125
- resourceId: version.id,
126
- endpoint: `/versions/${version.id}`,
127
- isComplete: (response) => response.validation_status !== 'PENDING' &&
128
- response.validation_status !== 'RUNNING',
129
- message: 'Building...',
130
- successMessage: 'Build finished',
131
- timeoutMessage: 'Build timed out',
132
- maxAttempts: 120,
133
- delayMs: 1000,
134
- });
135
- if (!pollResult.success || !pollResult.response) {
136
- console.error('Error: Agent build did not complete');
76
+ const match = existingVersions.items.find((v) => v.id === options.agentVersion ||
77
+ v.version_number === options.agentVersion);
78
+ if (!match) {
79
+ console.error(`Error: Version not found: ${options.agentVersion}`);
80
+ console.error('');
81
+ console.error('List available versions:');
82
+ console.error(` guild agent versions ${agentId}`);
137
83
  process.exit(1);
138
84
  }
139
- version = pollResult.response;
85
+ version = match;
140
86
  }
141
- if (version.validation_status === 'FAILED') {
142
- console.error('Error: Agent build failed');
143
- console.error('');
144
- // Fetch validation steps to show the actual error
145
- try {
146
- const stepsResponse = await client.get(`/versions/${version.id}/validation/steps`);
147
- const failedSteps = stepsResponse.steps.filter((step) => step.status === 'FAILED');
148
- if (failedSteps.length > 0) {
149
- for (const step of failedSteps) {
150
- console.error(`Step "${step.name}" failed:`);
151
- if (step.content) {
152
- console.error(step.content);
153
- }
154
- console.error('');
155
- }
156
- }
157
- else {
158
- console.error('No failed steps found. Check validation logs for details.');
159
- console.error('');
160
- }
87
+ else {
88
+ if (!quiet) {
89
+ console.error('Building agent...');
161
90
  }
162
- catch {
163
- // If we can't fetch steps, just show generic message
164
- console.error('Could not fetch validation details.');
165
- console.error('');
91
+ const agentFiles = await readAgentFiles(resolvedPath);
92
+ const noCache = options.cache === false;
93
+ const result = await buildEphemeralVersion(client, agentId, agentFiles, resolvedPath, '[Chat] Ephemeral development version', { noCache });
94
+ version = result.version;
95
+ ephemeralCached = result.cached;
96
+ if (ephemeralCached && !quiet) {
97
+ console.error('No changes detected, reusing cached build.');
166
98
  }
167
- console.error('Fix the issues and retry:');
168
- console.error(` guild agent chat ${agentId}`);
169
- process.exit(1);
170
99
  }
171
100
  // Branch: JSON/JSONL modes vs interactive
172
101
  if (options.mode === 'json' || options.mode === 'jsonl') {
@@ -299,6 +228,19 @@ export function createAgentChatCommand() {
299
228
  }
300
229
  }
301
230
  catch (error) {
231
+ if (error instanceof BuildTimeoutError) {
232
+ console.error('Error: Build did not complete');
233
+ console.error('');
234
+ console.error(error.message);
235
+ console.error('');
236
+ console.error('Check build status:');
237
+ console.error(' guild agent versions');
238
+ process.exit(1);
239
+ }
240
+ if (error instanceof BuildFailedError) {
241
+ console.error(`Error: ${error.message}`);
242
+ process.exit(1);
243
+ }
302
244
  const formattedError = handleAxiosError(error);
303
245
  if (formattedError.code === ErrorCodes.AUTH_REQUIRED ||
304
246
  formattedError.code === ErrorCodes.AUTH_TOKEN_INVALID) {
@@ -23,7 +23,7 @@ export function createAgentCloneCommand() {
23
23
  const cmd = new Command('clone');
24
24
  cmd
25
25
  .description('Clone an existing agent repository')
26
- .argument('<identifier>', 'Agent ID or full name to clone (e.g., owner/agent-name)')
26
+ .argument('<identifier>', 'Agent ID or full name to clone (e.g., owner~agent-name)')
27
27
  .option('--directory <path>', 'Target directory for clone')
28
28
  .option('--force', 'Clone even if directory is not empty', false)
29
29
  .action(async (agentId, options) => {
@@ -11,7 +11,7 @@ export function createAgentCodeCommand() {
11
11
  const cmd = new Command('code');
12
12
  cmd
13
13
  .description('Fetch the latest code for an agent')
14
- .argument('[identifier]', 'Agent ID or full name (e.g., owner/agent-name)')
14
+ .argument('[identifier]', 'Agent ID or full name (e.g., owner~agent-name)')
15
15
  .option('--draft', 'Include draft versions (default: only published)', false)
16
16
  .option('--output <directory>', 'Write files to directory instead of printing JSON')
17
17
  .action(async (agentIdArg, options) => {
@@ -48,7 +48,7 @@ export function createAgentForkCommand() {
48
48
  const cmd = new Command('fork');
49
49
  cmd
50
50
  .description('Fork an existing agent version to create a new agent')
51
- .argument('[identifier]', 'Agent ID, full name, or agent:version (e.g., owner/agent-name:version_xyz)')
51
+ .argument('[identifier]', 'Agent ID, full name, or agent:version (e.g., owner~agent-name:version_xyz)')
52
52
  .option('--name <name>', 'Name for the forked agent')
53
53
  .option('--description <desc>', 'Description for the forked agent')
54
54
  .option('--directory <path>', 'Target directory for clone')
@@ -65,7 +65,7 @@ export function createAgentForkCommand() {
65
65
  const agentPart = identifierArg.substring(0, colonIndex);
66
66
  sourceVersionId = identifierArg.substring(colonIndex + 1);
67
67
  if (!agentPart || !sourceVersionId) {
68
- output.error('Error: Invalid argument format', 'Expected: [identifier] or [identifier]:[version-id]\nExample: guild agent fork owner/agent-name:version_xyz\n\nTo find versions:\n guild agent versions <agent-id>');
68
+ output.error('Error: Invalid argument format', 'Expected: [identifier] or [identifier]:[version-id]\nExample: guild agent fork owner~agent-name:version_xyz\n\nTo find versions:\n guild agent versions <agent-id>');
69
69
  process.exit(1);
70
70
  }
71
71
  const resolved = await getAgentId(agentPart);
@@ -10,7 +10,7 @@ export function createAgentGetCommand() {
10
10
  const cmd = new Command('get');
11
11
  cmd
12
12
  .description('Get agent details')
13
- .argument('[identifier]', 'Agent ID or full name (e.g., owner/agent-name)')
13
+ .argument('[identifier]', 'Agent ID or full name (e.g., owner~agent-name)')
14
14
  .action(async (idArg) => {
15
15
  const output = createOutputWriter();
16
16
  try {
@@ -94,10 +94,10 @@ async function grepAllAgents(client, patternRE, publishedOnly, output) {
94
94
  }
95
95
  catch (ex) {
96
96
  const formattedError = handleAxiosError(ex);
97
- output.error(`${agent.owner?.name}/${agent.name}: ${formattedError.details}`);
97
+ output.error(`${agent.owner?.name}~${agent.name}: ${formattedError.details}`);
98
98
  return;
99
99
  }
100
- searchFiles(files, patternRE, `${agent.owner?.name}/${agent.name}/`, output);
100
+ searchFiles(files, patternRE, `${agent.owner?.name}~${agent.name}/`, output);
101
101
  }));
102
102
  offset += response.pagination.limit;
103
103
  if (!response.pagination.has_more)
@@ -319,17 +319,24 @@ export function createAgentInitCommand() {
319
319
  };
320
320
  await fs.writeFile(guildJsonPath, JSON.stringify(guildConfig, null, 2) + '\n', 'utf-8');
321
321
  steps.succeed('Create guild.json');
322
- // Add guild.json to .gitignore if not already present
322
+ // Add guild.json and .guild/cache/ to .gitignore if not already present
323
323
  const gitignorePath = path.join(targetDir, '.gitignore');
324
324
  try {
325
325
  const gitignoreContent = await fs.readFile(gitignorePath, 'utf-8');
326
+ let additions = '';
326
327
  if (!gitignoreContent.includes('guild.json')) {
327
- await fs.appendFile(gitignorePath, '\nguild.json\n');
328
+ additions += '\nguild.json\n';
329
+ }
330
+ if (!gitignoreContent.includes('.guild/cache/')) {
331
+ additions += '.guild/cache/\n';
332
+ }
333
+ if (additions) {
334
+ await fs.appendFile(gitignorePath, additions);
328
335
  }
329
336
  }
330
337
  catch {
331
338
  // .gitignore doesn't exist (backend should have created it), create it
332
- await fs.writeFile(gitignorePath, 'guild.json\n');
339
+ await fs.writeFile(gitignorePath, 'guild.json\n.guild/cache/\n');
333
340
  }
334
341
  // Install pre-push hook to block direct git push
335
342
  await installPrePushHook(targetDir);
@@ -3,7 +3,7 @@
3
3
  import { Command } from 'commander';
4
4
  import { GuildAPIClient } from '../../lib/api-client.js';
5
5
  import { getAuthToken } from '../../lib/auth.js';
6
- import { handleAxiosError } from '../../lib/errors.js';
6
+ import { handleAxiosError, ErrorCodes } from '../../lib/errors.js';
7
7
  import { getOutputMode } from '../../lib/output-mode.js';
8
8
  import { createOutputWriter, formatAgentTable } from '../../lib/output.js';
9
9
  const SORT_MAP = {
@@ -19,6 +19,7 @@ export function createAgentListCommand() {
19
19
  .option('--search <query>', 'Search agents by name or description')
20
20
  .option('--sort <field>', 'Sort by: updated, newest, name, popular (default: updated)', 'updated')
21
21
  .option('--published', 'Only show published agents')
22
+ .option('--workspace <id>', 'Filter agents by workspace ID or name')
22
23
  .option('--limit <number>', 'Number of results to return', '20')
23
24
  .option('--offset <number>', 'Offset for pagination', '0')
24
25
  .action(async (options) => {
@@ -39,6 +40,9 @@ export function createAgentListCommand() {
39
40
  if (options.published) {
40
41
  params.append('published_only', 'true');
41
42
  }
43
+ if (options.workspace) {
44
+ params.append('for_workspace', options.workspace);
45
+ }
42
46
  const sortField = SORT_MAP[options.sort];
43
47
  if (sortField) {
44
48
  params.append('sort_by', sortField);
@@ -53,6 +57,10 @@ export function createAgentListCommand() {
53
57
  }
54
58
  catch (error) {
55
59
  const formattedError = handleAxiosError(error);
60
+ if (formattedError.code === ErrorCodes.NOT_FOUND) {
61
+ output.error('Workspace not found');
62
+ process.exit(1);
63
+ }
56
64
  output.error(`Failed to list agents: ${formattedError.details}`);
57
65
  process.exit(1);
58
66
  }
@@ -10,7 +10,7 @@ export function createAgentPublishCommand() {
10
10
  const cmd = new Command('publish');
11
11
  cmd
12
12
  .description('Publish the latest draft version of an agent')
13
- .argument('[identifier]', 'Agent ID or full name (e.g., owner/agent-name)')
13
+ .argument('[identifier]', 'Agent ID or full name (e.g., owner~agent-name)')
14
14
  .option('--wait', 'Wait for validation to complete before publishing')
15
15
  .action(async (agentIdArg, options) => {
16
16
  const output = createOutputWriter();
@@ -10,7 +10,7 @@ export function createAgentRevalidateCommand() {
10
10
  const cmd = new Command('revalidate');
11
11
  cmd
12
12
  .description('Revalidate an agent version')
13
- .argument('[identifier]', 'Agent ID or full name (e.g., owner/agent-name)')
13
+ .argument('[identifier]', 'Agent ID or full name (e.g., owner~agent-name)')
14
14
  .argument('[version-id]', 'ID of the version to revalidate (uses latest if omitted)')
15
15
  .action(async (agentIdArg, versionIdArg) => {
16
16
  const output = createOutputWriter();
@@ -44,6 +44,7 @@ export function createAgentSaveCommand() {
44
44
  output.error('Commit message is required with --all', 'Provide a message describing your changes:\n guild agent save -A --message "Add new feature"');
45
45
  process.exit(1);
46
46
  }
47
+ let versionNumber;
47
48
  try {
48
49
  // Check for guild.json
49
50
  const guildJsonPath = path.join(cwd, 'guild.json');
@@ -88,8 +89,22 @@ export function createAgentSaveCommand() {
88
89
  }
89
90
  packageJson.version = newVersion;
90
91
  await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
92
+ versionNumber = newVersion;
91
93
  output.progress(`✓ Bumped version: ${currentVersion} → ${newVersion}`);
92
94
  }
95
+ else {
96
+ // No bump — read version from package.json if it exists
97
+ const packageJsonPath = path.join(cwd, 'package.json');
98
+ try {
99
+ const pkg = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
100
+ if (pkg.version) {
101
+ versionNumber = pkg.version;
102
+ }
103
+ }
104
+ catch {
105
+ // No package.json or unreadable — version stays undefined
106
+ }
107
+ }
93
108
  // Check for uncommitted changes and unpushed commits
94
109
  const { stdout: statusOutput } = await runGit(['status', '--porcelain'], {
95
110
  cwd,
@@ -249,13 +264,19 @@ export function createAgentSaveCommand() {
249
264
  const commitSha = shaOutput.trim();
250
265
  // Create version in guildcore (always as DRAFT, publish happens separately)
251
266
  const client = new GuildAPIClient();
252
- let version = await client.post(`/agents/${guildConfig.agent_id}/versions`, {
267
+ const agentId = guildConfig?.agent_id;
268
+ if (!agentId) {
269
+ output.error('Not in an agent directory');
270
+ process.exit(1);
271
+ }
272
+ let version = await client.post(`/agents/${agentId}/versions`, {
253
273
  commit_sha: commitSha,
254
274
  summary: versionMessage,
255
275
  version_type: 'COMMITTED',
276
+ ...(versionNumber ? { version_number: versionNumber } : {}),
256
277
  });
257
278
  output.progress(`✓ Created version (${version.id})`);
258
- if (options.wait) {
279
+ if (options.wait || options.publish) {
259
280
  version = await waitForValidation(version.id, output);
260
281
  }
261
282
  if (options.publish) {