@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 +8 -3
- package/dist/commands/agent/chat.js +2 -1
- package/dist/commands/agent/clone.js +6 -3
- package/dist/commands/agent/init.js +40 -16
- package/dist/commands/agent/save.js +53 -35
- package/dist/commands/agent/test.js +2 -1
- package/dist/commands/chat.js +2 -0
- package/dist/commands/workspace/context/publish.js +1 -1
- package/dist/index.js +3 -0
- package/dist/lib/did-you-mean.d.ts +25 -0
- package/dist/lib/did-you-mean.js +143 -0
- package/dist/lib/generated-types.d.ts +1 -1
- package/dist/lib/generated-types.js +1 -0
- package/dist/lib/git.d.ts +9 -0
- package/dist/lib/git.js +24 -1
- package/dist/lib/stdin.d.ts +6 -0
- package/dist/lib/stdin.js +18 -0
- package/docs/CLI_WORKFLOW.md +45 -36
- package/docs/getting-started.md +21 -14
- package/docs/skills/agent-dev.md +76 -28
- package/package.json +1 -1
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
|
-
|
|
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
|
|
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(
|
|
84
|
-
output.progress(
|
|
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
|
|
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
|
-
|
|
75
|
-
const
|
|
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(
|
|
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(
|
|
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], {
|
|
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], {
|
|
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 ${
|
|
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(
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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('
|
|
16
|
-
.option('--
|
|
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
|
-
//
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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:
|
|
53
|
-
|
|
54
|
-
|
|
59
|
+
const { stdout: logOutput } = await runGit(['log', '--oneline', '-1'], {
|
|
60
|
+
cwd,
|
|
61
|
+
});
|
|
62
|
+
hasUnpushedCommits = logOutput.trim().length > 0;
|
|
55
63
|
}
|
|
56
64
|
catch {
|
|
57
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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:
|
|
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,
|
|
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) {
|
package/dist/commands/chat.js
CHANGED
|
@@ -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(`/
|
|
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];
|
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
|
package/dist/lib/stdin.d.ts
CHANGED
|
@@ -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
|
*/
|
package/docs/CLI_WORKFLOW.md
CHANGED
|
@@ -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
|
|
16
|
+
guild agent create my-agent --template LLM
|
|
12
17
|
|
|
13
18
|
# Clone an existing agent
|
|
14
|
-
guild agent clone guildai/
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
56
|
-
guild agent
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
guild agent
|
|
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
|
|
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/
|
|
72
|
-
cd
|
|
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
|
-
#
|
|
92
|
-
|
|
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
|
|
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
|
|
145
|
-
|
|
146
|
-
You already committed. Just push:
|
|
162
|
+
### "No changes to save"
|
|
147
163
|
|
|
148
|
-
|
|
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
|
|
171
|
+
- Run `guild agent create` to create one
|
|
158
172
|
|
|
159
173
|
### Validation Failed
|
|
160
174
|
|
|
161
|
-
Check the error
|
|
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`
|
package/docs/getting-started.md
CHANGED
|
@@ -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
|
|
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
|
-
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/docs/skills/agent-dev.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: Guild Agent Development
|
|
3
|
-
description:
|
|
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
|
|
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
|
|
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
|
-
#
|
|
61
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
93
|
+
### For Creating and Modifying
|
|
86
94
|
|
|
87
95
|
- ✅ `guild agent create`, `guild agent init`, `guild agent clone`
|
|
88
|
-
- ✅ `
|
|
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
|
-
-
|
|
92
|
-
- ❌ NEVER use `
|
|
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
|
|
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({
|
|
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
|
-
**
|
|
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**
|
|
662
|
-
2. **Validating**
|
|
663
|
-
3. **Published**
|
|
664
|
-
4. **Failed**
|
|
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
|
|
744
|
+
### "No changes to save"
|
|
697
745
|
|
|
698
|
-
|
|
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
|
```
|