@guildai/cli 0.3.17 → 0.3.18

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.
@@ -4,25 +4,9 @@ import { GuildAPIClient } from '../../lib/api-client.js';
4
4
  import { handleAxiosError, ErrorCodes } from '../../lib/errors.js';
5
5
  import * as fs from 'fs/promises';
6
6
  import * as path from 'path';
7
- import * as readline from 'readline';
8
7
  import { getAuthenticatedUrl } from '../../lib/auth.js';
9
8
  import { runGit, GitError, formatGitError } from '../../lib/git.js';
10
9
  import { createOutputWriter } from '../../lib/output.js';
11
- async function promptForDirectory(suggestedDir) {
12
- const rl = readline.createInterface({
13
- input: process.stdin,
14
- output: process.stdout,
15
- });
16
- return new Promise((resolve) => {
17
- rl.question(`Target directory (default: ${suggestedDir}): `, (answer) => {
18
- rl.close();
19
- resolve(answer.trim() || suggestedDir);
20
- });
21
- });
22
- }
23
- function isInteractive() {
24
- return process.stdin.isTTY === true;
25
- }
26
10
  async function isDirectoryEmpty(dirPath) {
27
11
  try {
28
12
  const files = await fs.readdir(dirPath);
@@ -51,18 +35,8 @@ export function createAgentCloneCommand() {
51
35
  process.exit(1);
52
36
  }
53
37
  output.progress(`✓ Fetched agent '${agent.name}' (${agent.id})`);
54
- // Determine target directory
55
- let targetDir = options.directory;
56
- if (!targetDir) {
57
- if (isInteractive()) {
58
- const suggestedDir = `./${agent.name}`;
59
- targetDir = await promptForDirectory(suggestedDir);
60
- }
61
- else {
62
- output.error('Error: Target directory required in non-interactive mode', `Provide a directory:\n guild agent clone ${agentId} --directory ./my-agent\n\nOr run interactively to be prompted.`);
63
- process.exit(1);
64
- }
65
- }
38
+ // Determine target directory (default to agent name, like git clone)
39
+ const targetDir = options.directory || agent.name;
66
40
  // Check if directory exists and is not empty
67
41
  const dirExists = await fs
68
42
  .access(targetDir)
@@ -3,7 +3,8 @@ import { Command } from 'commander';
3
3
  import { GuildAPIClient } from '../../lib/api-client.js';
4
4
  import { getAuthToken } from '../../lib/auth.js';
5
5
  import { handleAxiosError } from '../../lib/errors.js';
6
- import { createOutputWriter } from '../../lib/output.js';
6
+ import { getOutputMode } from '../../lib/output-mode.js';
7
+ import { createOutputWriter, formatAgentTable } from '../../lib/output.js';
7
8
  const SORT_MAP = {
8
9
  updated: 'updated_at',
9
10
  newest: 'created_at',
@@ -42,7 +43,12 @@ export function createAgentListCommand() {
42
43
  params.append('sort_by', sortField);
43
44
  }
44
45
  const response = await client.get(`/agents?${params.toString()}`);
45
- output.data(response);
46
+ if (getOutputMode() === 'json') {
47
+ output.data(response);
48
+ }
49
+ else {
50
+ formatAgentTable(response.items, response.pagination);
51
+ }
46
52
  }
47
53
  catch (error) {
48
54
  const formattedError = handleAxiosError(error);
@@ -2,6 +2,7 @@
2
2
  import { Command } from 'commander';
3
3
  import { GuildAPIClient } from '../../lib/api-client.js';
4
4
  import { handleAxiosError, ErrorCodes } from '../../lib/errors.js';
5
+ import { getOutputMode } from '../../lib/output-mode.js';
5
6
  import { createOutputWriter } from '../../lib/output.js';
6
7
  import * as fs from 'fs/promises';
7
8
  import * as path from 'path';
@@ -254,8 +255,12 @@ export function createAgentSaveCommand() {
254
255
  output.progress('To publish this version:');
255
256
  output.progress(' guild agent save --message "..." --publish');
256
257
  }
257
- // Output JSON to stdout for programmatic use
258
- output.data({ version });
258
+ // Output JSON to stdout only in --json mode.
259
+ // In interactive mode the progress messages above already
260
+ // show version details; dumping raw JSON is noise.
261
+ if (getOutputMode() === 'json') {
262
+ output.data({ version });
263
+ }
259
264
  }
260
265
  catch (error) {
261
266
  if (error instanceof GitError) {
@@ -3,7 +3,8 @@ import { Command } from 'commander';
3
3
  import { GuildAPIClient } from '../../lib/api-client.js';
4
4
  import { getAuthToken } from '../../lib/auth.js';
5
5
  import { handleAxiosError } from '../../lib/errors.js';
6
- import { createOutputWriter } from '../../lib/output.js';
6
+ import { getOutputMode } from '../../lib/output-mode.js';
7
+ import { createOutputWriter, formatAgentTable } from '../../lib/output.js';
7
8
  const SORT_MAP = {
8
9
  updated: 'updated_at',
9
10
  newest: 'created_at',
@@ -40,7 +41,12 @@ export function createAgentSearchCommand() {
40
41
  params.append('sort_by', sortField);
41
42
  }
42
43
  const response = await client.get(`/agents?${params.toString()}`);
43
- output.data(response);
44
+ if (getOutputMode() === 'json') {
45
+ output.data(response);
46
+ }
47
+ else {
48
+ formatAgentTable(response.items, response.pagination);
49
+ }
44
50
  }
45
51
  catch (error) {
46
52
  const formattedError = handleAxiosError(error);
@@ -4,7 +4,8 @@ import { GuildAPIClient } from '../../lib/api-client.js';
4
4
  import { getGuildcoreUrl } from '../../lib/config.js';
5
5
  import { handleAxiosError, ErrorCodes } from '../../lib/errors.js';
6
6
  import { getAgentId } from '../../lib/agent-helpers.js';
7
- import { createOutputWriter } from '../../lib/output.js';
7
+ import { getOutputMode } from '../../lib/output-mode.js';
8
+ import { createOutputWriter, formatVersionTable } from '../../lib/output.js';
8
9
  export function createAgentVersionsCommand() {
9
10
  const cmd = new Command('versions');
10
11
  cmd
@@ -22,7 +23,12 @@ export function createAgentVersionsCommand() {
22
23
  const offset = parseInt(options.offset, 10);
23
24
  try {
24
25
  const result = await client.get(`/agents/${agentId}/versions?limit=${limit}&offset=${offset}`);
25
- output.data(result);
26
+ if (getOutputMode() === 'json') {
27
+ output.data(result);
28
+ }
29
+ else {
30
+ formatVersionTable(result.items, result.pagination);
31
+ }
26
32
  }
27
33
  catch (error) {
28
34
  const formattedError = handleAxiosError(error);
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createAgentWorkspacesCommand(): Command;
3
+ //# sourceMappingURL=workspaces.d.ts.map
@@ -0,0 +1,51 @@
1
+ // Copyright (c) 2026 Guild.ai All Rights Reserved
2
+ import { Command } from 'commander';
3
+ import { GuildAPIClient } from '../../lib/api-client.js';
4
+ import { getGuildcoreUrl } from '../../lib/config.js';
5
+ import { handleAxiosError, ErrorCodes } from '../../lib/errors.js';
6
+ import { getAgentId } from '../../lib/agent-helpers.js';
7
+ import { createOutputWriter, formatWorkspaceTable } from '../../lib/output.js';
8
+ import { getOutputMode } from '../../lib/output-mode.js';
9
+ export function createAgentWorkspacesCommand() {
10
+ const cmd = new Command('workspaces');
11
+ cmd
12
+ .description('List workspaces that use an agent')
13
+ .argument('[identifier]', 'Agent ID or full name (e.g., owner/agent-name)')
14
+ .option('--limit <number>', 'Maximum number of workspaces to return', '20')
15
+ .option('--offset <number>', 'Number of workspaces to skip', '0')
16
+ .action(async (agentIdArg, options) => {
17
+ const output = createOutputWriter();
18
+ const { agentId } = await getAgentId(agentIdArg);
19
+ const baseUrl = getGuildcoreUrl();
20
+ const client = new GuildAPIClient({ baseUrl });
21
+ const limit = parseInt(options.limit, 10);
22
+ const offset = parseInt(options.offset, 10);
23
+ try {
24
+ const result = await client.get(`/agents/${agentId}/workspaces?limit=${limit}&offset=${offset}`);
25
+ if (getOutputMode() === 'json') {
26
+ output.data(result);
27
+ }
28
+ else {
29
+ formatWorkspaceTable(result.items, result.pagination);
30
+ }
31
+ }
32
+ catch (error) {
33
+ const formattedError = handleAxiosError(error);
34
+ if (formattedError.code === ErrorCodes.AUTH_REQUIRED) {
35
+ output.error('Not logged in.', 'Please authenticate first:\n guild auth login');
36
+ }
37
+ else if (formattedError.code === ErrorCodes.CONN_REFUSED) {
38
+ output.error('Cannot connect to Guild servers');
39
+ }
40
+ else if (formattedError.code === ErrorCodes.NOT_FOUND) {
41
+ output.error(`Agent not found: ${agentId}`, 'Check the agent ID:\n guild agent list');
42
+ }
43
+ else {
44
+ output.error(`Failed to list workspaces: ${formattedError.details}`);
45
+ }
46
+ process.exit(1);
47
+ }
48
+ });
49
+ return cmd;
50
+ }
51
+ //# sourceMappingURL=workspaces.js.map
@@ -4,7 +4,8 @@ import { GuildAPIClient } from '../../../lib/api-client.js';
4
4
  import { getAuthToken } from '../../../lib/auth.js';
5
5
  import { getWorkspaceId } from '../../../lib/guild-config.js';
6
6
  import { handleAxiosError, ErrorCodes } from '../../../lib/errors.js';
7
- import { createOutputWriter } from '../../../lib/output.js';
7
+ import { createOutputWriter, formatWorkspaceAgentTable } from '../../../lib/output.js';
8
+ import { getOutputMode } from '../../../lib/output-mode.js';
8
9
  export function createWorkspaceAgentListCommand() {
9
10
  const cmd = new Command('list');
10
11
  cmd
@@ -36,12 +37,12 @@ export function createWorkspaceAgentListCommand() {
36
37
  params.append('limit', options.limit);
37
38
  params.append('offset', options.offset);
38
39
  const response = await client.get(`/workspaces/${workspaceId}/workspace_agents?${params.toString()}`);
39
- const agents = response.items;
40
- if (agents.length === 0) {
41
- output.data({ items: [], message: 'No agents installed in this workspace.' });
42
- return;
40
+ if (getOutputMode() === 'json') {
41
+ output.data(response);
42
+ }
43
+ else {
44
+ formatWorkspaceAgentTable(response.items);
43
45
  }
44
- output.data(response);
45
46
  }
46
47
  catch (error) {
47
48
  const formattedError = handleAxiosError(error);
@@ -2,7 +2,8 @@
2
2
  import { Command } from 'commander';
3
3
  import { GuildAPIClient } from '../../../lib/api-client.js';
4
4
  import { handleAxiosError, ErrorCodes } from '../../../lib/errors.js';
5
- import { createOutputWriter } from '../../../lib/output.js';
5
+ import { getOutputMode } from '../../../lib/output-mode.js';
6
+ import { createOutputWriter, formatContextTable } from '../../../lib/output.js';
6
7
  export function createWorkspaceContextListCommand() {
7
8
  const cmd = new Command('list');
8
9
  cmd
@@ -18,7 +19,12 @@ export function createWorkspaceContextListCommand() {
18
19
  params.append('limit', options.limit);
19
20
  params.append('offset', options.offset);
20
21
  const response = await client.get(`/workspaces/${workspaceId}/contexts?${params.toString()}`);
21
- output.data(response);
22
+ if (getOutputMode() === 'json') {
23
+ output.data(response);
24
+ }
25
+ else {
26
+ formatContextTable(response.items, response.pagination);
27
+ }
22
28
  }
23
29
  catch (error) {
24
30
  const formattedError = handleAxiosError(error);
package/dist/index.js CHANGED
@@ -28,6 +28,7 @@ import { createAgentUnpublishCommand } from './commands/agent/unpublish.js';
28
28
  import { createAgentRevalidateCommand } from './commands/agent/revalidate.js';
29
29
  import { createAgentSearchCommand } from './commands/agent/search.js';
30
30
  import { createAgentOwnersCommand } from './commands/agent/owners.js';
31
+ import { createAgentWorkspacesCommand } from './commands/agent/workspaces.js';
31
32
  import { createAgentTagsListCommand } from './commands/agent/tags/list.js';
32
33
  import { createAgentTagsAddCommand } from './commands/agent/tags/add.js';
33
34
  import { createAgentTagsRemoveCommand } from './commands/agent/tags/remove.js';
@@ -131,6 +132,7 @@ agentCmd.addCommand(createAgentUnpublishCommand());
131
132
  agentCmd.addCommand(createAgentRevalidateCommand());
132
133
  agentCmd.addCommand(createAgentSearchCommand());
133
134
  agentCmd.addCommand(createAgentOwnersCommand());
135
+ agentCmd.addCommand(createAgentWorkspacesCommand());
134
136
  // Agent tags subcommand group
135
137
  const tagsCmd = agentCmd.command('tags').description('Manage agent tags');
136
138
  tagsCmd.addCommand(createAgentTagsListCommand());
@@ -66,8 +66,8 @@ export async function readAgentFiles(cwd) {
66
66
  const content = await fs.readFile(fullPath, 'utf-8');
67
67
  files.push({ path: filePath, content });
68
68
  }
69
- const fileNames = files.map((f) => f.path);
70
- const missing = REQUIRED_AGENT_FILES.filter((f) => !fileNames.includes(f));
69
+ const filePaths = files.map((f) => f.path);
70
+ const missing = REQUIRED_AGENT_FILES.filter((req) => !filePaths.some((fp) => fp === req || fp.endsWith(`/${req}`)));
71
71
  if (missing.length > 0) {
72
72
  throw new Error(`Missing required files: ${missing.join(', ')}`);
73
73
  }
@@ -1,4 +1,4 @@
1
- export declare const WEBHOOK_SERVICES: readonly ["AZURE_DEVOPS", "BITBUCKET", "CYPRESS", "GITHUB", "GOOGLE_DOCS", "JIRA", "LINEAR", "NEWRELIC", "NOTION", "SLACK", "TESTRAIL", "ZENDESK"];
1
+ export declare const WEBHOOK_SERVICES: readonly ["AZURE_DEVOPS", "BITBUCKET", "CYPRESS", "GITHUB", "GOOGLE_DOCS", "GOOGLE_LOGGING", "JIRA", "LINEAR", "NEWRELIC", "NOTION", "SLACK", "TESTRAIL", "ZENDESK"];
2
2
  export type WebhookService = (typeof WEBHOOK_SERVICES)[number];
3
3
  export declare const TIME_TRIGGER_FREQUENCIES: readonly ["HOURLY", "DAILY", "WEEKLY", "MONTHLY"];
4
4
  export type TimeTriggerFrequency = (typeof TIME_TRIGGER_FREQUENCIES)[number];
@@ -13,6 +13,7 @@ export const WEBHOOK_SERVICES = [
13
13
  'CYPRESS',
14
14
  'GITHUB',
15
15
  'GOOGLE_DOCS',
16
+ 'GOOGLE_LOGGING',
16
17
  'JIRA',
17
18
  'LINEAR',
18
19
  'NEWRELIC',
package/dist/lib/npmrc.js CHANGED
@@ -17,6 +17,7 @@ export async function configureNpmrc() {
17
17
  }
18
18
  try {
19
19
  await execa('npm', [
20
+ '--workspaces=false',
20
21
  'config',
21
22
  'set',
22
23
  '--location',
@@ -36,7 +37,14 @@ export async function cleanupNpmrc() {
36
37
  const keys = [...SCOPES.map((s) => `${s}:registry`), `${scope}:_authToken`];
37
38
  for (const key of keys) {
38
39
  try {
39
- await execa('npm', ['config', 'delete', '--location', 'user', key]);
40
+ await execa('npm', [
41
+ '--workspaces=false',
42
+ 'config',
43
+ 'delete',
44
+ '--location',
45
+ 'user',
46
+ key,
47
+ ]);
40
48
  }
41
49
  catch {
42
50
  // Key may not exist, that's fine
@@ -1,5 +1,5 @@
1
1
  import { type Spinner } from './progress.js';
2
- import type { Agent, Pagination, Workspace, Trigger } from './api-types.js';
2
+ import type { Agent, AgentVersion, Context, Pagination, Workspace, WorkspaceAgent, Trigger } from './api-types.js';
3
3
  /**
4
4
  * Session type from API responses
5
5
  */
@@ -19,6 +19,21 @@ interface Session {
19
19
  * Shared by agent list and agent search commands.
20
20
  */
21
21
  export declare function formatAgentTable(agents: Agent[], pagination: Pagination): void;
22
+ /**
23
+ * Format an agent version list as a human-readable table.
24
+ * Used by agent versions command.
25
+ */
26
+ export declare function formatVersionTable(versions: AgentVersion[], pagination: Pagination): void;
27
+ /**
28
+ * Format a context list as a human-readable table.
29
+ * Used by workspace context list command.
30
+ */
31
+ export declare function formatContextTable(contexts: Context[], pagination: Pagination): void;
32
+ /**
33
+ * Format a workspace agent list as a human-readable table.
34
+ * Used by workspace agent list command.
35
+ */
36
+ export declare function formatWorkspaceAgentTable(agents: WorkspaceAgent[]): void;
22
37
  /**
23
38
  * Format a workspace list as a human-readable table.
24
39
  * Used by workspace list command.
@@ -38,7 +53,7 @@ export declare function formatTriggerTable(triggers: Trigger[], pagination: Pagi
38
53
  * Output writer interface for consistent CLI output
39
54
  *
40
55
  * Implementations:
41
- * - HumanOutputWriter: Colors, tables, spinners for interactive use
56
+ * - InteractiveOutputWriter: Colors, tables, spinners for interactive use
42
57
  * - JSONOutputWriter: Pure JSON for scripting and automation
43
58
  */
44
59
  export interface OutputWriter {
@@ -69,9 +84,8 @@ export interface OutputWriter {
69
84
  * Uses colors, symbols, and formatting for readability.
70
85
  * Progress goes to stderr.
71
86
  */
72
- export declare class HumanOutputWriter implements OutputWriter {
87
+ export declare class InteractiveOutputWriter implements OutputWriter {
73
88
  data(value: unknown): void;
74
- private isAgentListResponse;
75
89
  success(message: string, details?: Record<string, unknown>): void;
76
90
  error(message: string, details?: string): void;
77
91
  progress(message: string): void;
@@ -76,6 +76,112 @@ export function formatAgentTable(agents, pagination) {
76
76
  console.log(chalk.dim(`\nShowing ${showing} of ${pagination.total_count} agents`));
77
77
  }
78
78
  }
79
+ /**
80
+ * Format an agent version list as a human-readable table.
81
+ * Used by agent versions command.
82
+ */
83
+ export function formatVersionTable(versions, pagination) {
84
+ if (versions.length === 0) {
85
+ console.log(chalk.dim('No versions found'));
86
+ return;
87
+ }
88
+ const table = new Table({
89
+ columns: [
90
+ { name: 'sha', title: 'SHA', alignment: 'left' },
91
+ { name: 'version', title: 'VERSION', alignment: 'left', color: 'cyan' },
92
+ { name: 'status', title: 'STATUS', alignment: 'left' },
93
+ { name: 'validation', title: 'VALIDATION', alignment: 'left' },
94
+ { name: 'summary', title: 'SUMMARY', alignment: 'left' },
95
+ { name: 'created', title: 'CREATED', alignment: 'left' },
96
+ ],
97
+ });
98
+ versions.forEach((v) => {
99
+ const validationColor = v.validation_status === 'PASSED'
100
+ ? chalk.green
101
+ : v.validation_status === 'FAILED'
102
+ ? chalk.red
103
+ : chalk.dim;
104
+ table.addRow({
105
+ sha: v.sha.slice(0, 7),
106
+ version: v.version_number || '-',
107
+ status: v.status,
108
+ validation: validationColor(v.validation_status || '-'),
109
+ summary: truncate(v.summary || '', 30),
110
+ created: v.created_at ? formatRelativeTime(v.created_at) : '',
111
+ });
112
+ });
113
+ table.printTable();
114
+ const showing = Math.min(pagination.limit, versions.length);
115
+ if (pagination.has_more) {
116
+ const nextOffset = pagination.offset + pagination.limit;
117
+ console.log(`\nShowing ${showing} of ${pagination.total_count} versions. ` +
118
+ chalk.dim(`Use --offset ${nextOffset} to see more.`));
119
+ }
120
+ else if (pagination.total_count > showing) {
121
+ console.log(chalk.dim(`\nShowing ${showing} of ${pagination.total_count} versions`));
122
+ }
123
+ }
124
+ /**
125
+ * Format a context list as a human-readable table.
126
+ * Used by workspace context list command.
127
+ */
128
+ export function formatContextTable(contexts, pagination) {
129
+ if (contexts.length === 0) {
130
+ console.log(chalk.dim('No contexts found'));
131
+ return;
132
+ }
133
+ const table = new Table({
134
+ columns: [
135
+ { name: 'id', title: 'ID', alignment: 'left' },
136
+ { name: 'status', title: 'STATUS', alignment: 'left', color: 'cyan' },
137
+ { name: 'type', title: 'TYPE', alignment: 'left' },
138
+ { name: 'created', title: 'CREATED', alignment: 'left' },
139
+ ],
140
+ });
141
+ contexts.forEach((ctx) => {
142
+ table.addRow({
143
+ id: truncate(ctx.id, 12),
144
+ status: ctx.status,
145
+ type: ctx.type || '-',
146
+ created: ctx.created_at ? formatRelativeTime(ctx.created_at) : '',
147
+ });
148
+ });
149
+ table.printTable();
150
+ const showing = Math.min(pagination.limit, contexts.length);
151
+ if (pagination.has_more) {
152
+ const nextOffset = pagination.offset + pagination.limit;
153
+ console.log(`\nShowing ${showing} of ${pagination.total_count} contexts. ` +
154
+ chalk.dim(`Use --offset ${nextOffset} to see more.`));
155
+ }
156
+ else if (pagination.total_count > showing) {
157
+ console.log(chalk.dim(`\nShowing ${showing} of ${pagination.total_count} contexts`));
158
+ }
159
+ }
160
+ /**
161
+ * Format a workspace agent list as a human-readable table.
162
+ * Used by workspace agent list command.
163
+ */
164
+ export function formatWorkspaceAgentTable(agents) {
165
+ if (agents.length === 0) {
166
+ console.log(chalk.dim('No agents installed in this workspace'));
167
+ return;
168
+ }
169
+ const table = new Table({
170
+ columns: [
171
+ { name: 'name', title: 'NAME', alignment: 'left' },
172
+ { name: 'version', title: 'VERSION', alignment: 'left', color: 'cyan' },
173
+ { name: 'auto_update', title: 'AUTO-UPDATE', alignment: 'left' },
174
+ ],
175
+ });
176
+ agents.forEach((wa) => {
177
+ table.addRow({
178
+ name: wa.agent.full_name || wa.agent.name,
179
+ version: wa.agent_version.version_number || wa.agent_version.sha.slice(0, 7),
180
+ auto_update: wa.should_autoupdate ? chalk.green('yes') : chalk.dim('no'),
181
+ });
182
+ });
183
+ table.printTable();
184
+ }
79
185
  /**
80
186
  * Format a workspace list as a human-readable table.
81
187
  * Used by workspace list command.
@@ -199,23 +305,10 @@ export function formatTriggerTable(triggers, pagination) {
199
305
  * Uses colors, symbols, and formatting for readability.
200
306
  * Progress goes to stderr.
201
307
  */
202
- export class HumanOutputWriter {
308
+ export class InteractiveOutputWriter {
203
309
  data(value) {
204
- // Special handling for agent list responses
205
- if (this.isAgentListResponse(value)) {
206
- formatAgentTable(value.items, value.pagination);
207
- return;
208
- }
209
- // Fallback to pretty JSON for other data
210
310
  console.log(JSON.stringify(value, null, 2));
211
311
  }
212
- isAgentListResponse(value) {
213
- return (typeof value === 'object' &&
214
- value !== null &&
215
- 'items' in value &&
216
- 'pagination' in value &&
217
- Array.isArray(value.items));
218
- }
219
312
  success(message, details) {
220
313
  process.stderr.write(chalk.green('✓') + ' ' + message + '\n');
221
314
  if (details) {
@@ -273,7 +366,7 @@ export class JSONOutputWriter {
273
366
  */
274
367
  export function createOutputWriter() {
275
368
  const mode = getOutputMode();
276
- return mode === 'json' ? new JSONOutputWriter() : new HumanOutputWriter();
369
+ return mode === 'json' ? new JSONOutputWriter() : new InteractiveOutputWriter();
277
370
  }
278
371
  /**
279
372
  * Create a no-op spinner (for quiet/JSON modes)
@@ -316,7 +316,7 @@ guild agent code # View source of latest version
316
316
 
317
317
  ## Key Rules
318
318
 
319
- - Agent code lives at `agent.ts` in the project root.
319
+ - Agent code lives in `agent.ts` (typically at the project root, but can be in a subdirectory like `src/`).
320
320
  - 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
321
  - Always call tools through `task.tools.<name>(args)`. Never access services directly.
322
322
  - Always use `guild agent save` to commit and `guild agent pull` to sync. Don't use raw git commands.
@@ -46,7 +46,7 @@ interface OutputWriter {
46
46
 
47
47
  Two implementations:
48
48
 
49
- - `HumanOutputWriter` - Colors, symbols, formatted output
49
+ - `InteractiveOutputWriter` - Colors, symbols, formatted output
50
50
  - `JSONOutputWriter` - Pure JSON
51
51
 
52
52
  Commands use `createOutputWriter()` to get the right implementation based on flags.
@@ -489,10 +489,23 @@ async function onToolResults(
489
489
 
490
490
  ### Slack-Specific Patterns
491
491
 
492
- When posting to Slack, convert markdown to Slack's mrkdwn format:
492
+ When posting to Slack, convert markdown to Slack's mrkdwn format. Use an inline converter
493
+ (`slackify-markdown` is CJS and breaks in the ESM agent runtime):
493
494
 
494
495
  ```typescript
495
- import slackifyMarkdown from 'slackify-markdown';
496
+ // Simple markdown-to-Slack-mrkdwn converter (inline — do NOT use slackify-markdown)
497
+ function slackifyMarkdown(md: string): string {
498
+ return md
499
+ .replace(/\*\*(.+?)\*\*/g, '*$1*') // bold: **text** → *text*
500
+ .replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, '_$1_') // italic: *text* → _text_
501
+ .replace(/~~(.+?)~~/g, '~$1~') // strikethrough
502
+ .replace(/^### (.+)$/gm, '*$1*') // h3 → bold
503
+ .replace(/^## (.+)$/gm, '*$1*') // h2 → bold
504
+ .replace(/^# (.+)$/gm, '*$1*') // h1 → bold
505
+ .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<$2|$1>') // links
506
+ .replace(/^> (.+)$/gm, '> $1') // blockquotes (same syntax)
507
+ .replace(/`([^`]+)`/g, '`$1`'); // inline code (same syntax)
508
+ }
496
509
 
497
510
  // In your agent:
498
511
  const responseText = '## Summary\n- Item 1\n- Item 2';
@@ -504,16 +517,6 @@ await task.tools.slack_chat_post_message({
504
517
  });
505
518
  ```
506
519
 
507
- Add `slackify-markdown` to `package.json` dependencies:
508
-
509
- ```json
510
- {
511
- "dependencies": {
512
- "slackify-markdown": "^4.5.0"
513
- }
514
- }
515
- ```
516
-
517
520
  ---
518
521
 
519
522
  ## Anti-Hallucination Guide
@@ -620,9 +623,7 @@ export default agent({ run: async (input, task) => { ... } })
620
623
  "version": "1.0.0",
621
624
  "author": "Guild.ai",
622
625
  "type": "module",
623
- "dependencies": {
624
- "slackify-markdown": "^4.5.0"
625
- },
626
+ "dependencies": {},
626
627
  "devDependencies": {
627
628
  "typescript": "^5.0.0"
628
629
  }
@@ -632,7 +633,7 @@ export default agent({ run: async (input, task) => { ... } })
632
633
  **Important:**
633
634
 
634
635
  - Do NOT add `@guildai/agents-sdk`, `@guildai-services/*`, or `zod` to dependencies. The runtime provides them.
635
- - DO add third-party packages your agent uses (e.g., `slackify-markdown`) to `dependencies`.
636
+ - 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.
636
637
  - `devDependencies` is for build tools only.
637
638
 
638
639
  ## Versioning
@@ -649,7 +650,7 @@ After `guild agent create` + `guild agent init`:
649
650
  my-agent/
650
651
  ├── .git/ # Git repo (remote is Guild server)
651
652
  ├── .gitignore # Includes guild.json
652
- ├── agent.ts # Your agent code (at project root, NOT in src/)
653
+ ├── agent.ts # Your agent code (default location; can also be in src/)
653
654
  ├── package.json # Dependencies
654
655
  ├── tsconfig.json # TypeScript config
655
656
  └── guild.json # Agent ID (gitignored, local only)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guildai/cli",
3
- "version": "0.3.17",
3
+ "version": "0.3.18",
4
4
  "description": "Guild.ai CLI - Build, test, and deploy AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",