@guildai/cli 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -58,17 +58,23 @@ guild auth status # Check authentication status
58
58
 
59
59
  ```bash
60
60
  guild agent init --name my-agent # Initialize a new agent
61
+ guild agent clone <agent-id> # Clone agent locally
61
62
  guild agent save # Push commits and create a version
62
63
  guild agent save -A --message "..." # Stage+commit+push in one step
63
- guild agent versions # List versions
64
- guild agent publish # Publish latest draft version
65
- guild agent unpublish # Unpublish latest published version
66
- guild agent test # Test agent
67
- guild agent chat # Chat with agent in current directory
68
- guild agent get <agent-id> # Get agent details
69
- guild agent code <agent-id> # Fetch latest published code
70
- guild agent clone <agent-id> # Clone agent locally
64
+ guild agent pull # Pull remote changes
65
+ guild agent test # Test agent interactively
66
+ guild agent test --ephemeral # Test unsaved changes
67
+ guild agent chat "message" # Send a single message
68
+ guild agent get [agent-id] # Get agent details
69
+ guild agent versions [agent-id] # List versions
70
+ guild agent code [agent-id] # View latest published source
71
71
  guild agent list # List all agents
72
+ guild agent grep <pattern> # Search agent source code
73
+ guild agent publish # Publish latest draft version
74
+ guild agent unpublish # Remove from catalog
75
+ guild agent revalidate # Re-run validation
76
+ guild agent update # Update agent metadata
77
+ guild agent owners # List accounts that can own agents
72
78
  guild agent tags list # List tags
73
79
  guild agent tags add <tag...> # Add tags
74
80
  guild agent tags remove <tag...> # Remove tags
@@ -13,7 +13,7 @@ export function createAgentOwnersCommand() {
13
13
  const client = new GuildAPIClient();
14
14
  const [me, orgs, globalConfig] = await Promise.all([
15
15
  client.get('/me'),
16
- client.get('/me/organizations'),
16
+ client.fetchAll('/me/organizations'),
17
17
  loadGlobalConfig(),
18
18
  ]);
19
19
  const defaultOwnerId = globalConfig?.default_owner;
@@ -24,7 +24,7 @@ export function createAgentOwnersCommand() {
24
24
  type: 'user',
25
25
  is_default: defaultOwnerId === me.id,
26
26
  },
27
- ...orgs.items.map((org) => ({
27
+ ...orgs.map((org) => ({
28
28
  id: org.id,
29
29
  name: org.name,
30
30
  type: 'organization',
@@ -258,7 +258,7 @@ export function createAgentSaveCommand() {
258
258
  output.progress('');
259
259
  output.progress('Version details:');
260
260
  output.progress(` ID: ${version.id}`);
261
- output.progress(` SHA: ${version.sha.substring(0, 12)}`);
261
+ output.progress(` SHA: ${version.sha ? version.sha.substring(0, 12) : '-'}`);
262
262
  output.progress(` Status: ${version.status}`);
263
263
  if (version.published_at) {
264
264
  output.progress(` Published at: ${version.published_at}`);
@@ -56,8 +56,8 @@ async function resolveOwnerName(ownerId) {
56
56
  if (me.id === ownerId) {
57
57
  return me.name;
58
58
  }
59
- const orgs = await client.get('/me/organizations');
60
- const org = orgs.items.find((o) => o.id === ownerId);
59
+ const orgs = await client.fetchAll('/me/organizations');
60
+ const org = orgs.find((o) => o.id === ownerId);
61
61
  return org?.name;
62
62
  }
63
63
  catch (error) {
@@ -50,8 +50,8 @@ export function createTriggerCreateCommand() {
50
50
  }
51
51
  const client = new GuildAPIClient();
52
52
  // Find workspace_agent_id from agent identifier
53
- const workspaceAgentsResponse = await client.get(`/workspaces/${workspaceId}/workspace_agents`);
54
- const workspaceAgent = workspaceAgentsResponse.items.find((wa) => wa.agent.full_name === options.agent ||
53
+ const allWorkspaceAgents = await client.fetchAll(`/workspaces/${workspaceId}/workspace_agents`);
54
+ const workspaceAgent = allWorkspaceAgents.find((wa) => wa.agent.full_name === options.agent ||
55
55
  wa.agent.name === options.agent ||
56
56
  wa.agent.id === options.agent);
57
57
  if (!workspaceAgent) {
@@ -58,7 +58,7 @@ export function createWorkspaceAgentAddCommand() {
58
58
  // Display success
59
59
  const versionDisplay = response.agent_version.version_number
60
60
  ? `v${response.agent_version.version_number}`
61
- : response.agent_version.sha.slice(0, 7);
61
+ : (response.agent_version.sha?.slice(0, 7) ?? '-');
62
62
  const workspaceName = response.workspace?.name || 'workspace';
63
63
  output.success(`Added ${response.agent.full_name} to workspace "${workspaceName}"`, {
64
64
  Agent: response.agent.full_name,
@@ -30,10 +30,10 @@ export function createWorkspaceAgentRemoveCommand() {
30
30
  }
31
31
  workspaceId = resolved.workspaceId;
32
32
  }
33
- // List workspace agents to find the one to remove
34
- const response = await client.get(`/workspaces/${workspaceId}/workspace_agents`);
33
+ // List all workspace agents to find the one to remove
34
+ const allWorkspaceAgents = await client.fetchAll(`/workspaces/${workspaceId}/workspace_agents`);
35
35
  // Find the agent by identifier (match full_name, name, agent id, or workspace_agent id)
36
- const workspaceAgent = response.items.find((wa) => wa.id === agentIdentifier ||
36
+ const workspaceAgent = allWorkspaceAgents.find((wa) => wa.id === agentIdentifier ||
37
37
  wa.agent.full_name === agentIdentifier ||
38
38
  wa.agent.name === agentIdentifier ||
39
39
  wa.agent.id === agentIdentifier);
@@ -18,6 +18,16 @@ function formatWorkspaceDisplay(workspace) {
18
18
  }
19
19
  return base;
20
20
  }
21
+ /**
22
+ * Match a workspace against a search argument (case-insensitive name, full_name, or exact ID).
23
+ */
24
+ function matchesWorkspaceArg(w, arg) {
25
+ return (w.name === arg ||
26
+ w.name.toLowerCase() === arg.toLowerCase() ||
27
+ w.full_name === arg ||
28
+ w.full_name?.toLowerCase() === arg.toLowerCase() ||
29
+ w.id === arg);
30
+ }
21
31
  export function createWorkspaceSelectCommand() {
22
32
  const cmd = new Command('select');
23
33
  cmd
@@ -27,24 +37,25 @@ export function createWorkspaceSelectCommand() {
27
37
  const output = createOutputWriter();
28
38
  try {
29
39
  const client = new GuildAPIClient();
30
- // Use filter=all to get workspaces from all orgs user is a member of
31
- const workspaces = await client.get('/me/workspaces?filter=all');
32
- if (workspaces.items.length === 0) {
33
- output.error('No workspaces found.', 'Create a workspace first:\n guild workspace create <name>');
34
- process.exit(1);
35
- }
36
- // If a workspace argument was provided, find and select it directly
40
+ // If a workspace argument was provided, use server-side search to find it
37
41
  if (workspaceArg) {
38
- const workspace = workspaces.items.find((w) => w.name === workspaceArg ||
39
- w.name.toLowerCase() === workspaceArg.toLowerCase() ||
40
- w.full_name === workspaceArg ||
41
- w.full_name?.toLowerCase() === workspaceArg.toLowerCase() ||
42
- w.id === workspaceArg);
42
+ // Use search param to narrow results server-side, then exact-match client-side.
43
+ // The backend searches only the "name" column via ILIKE, so full_name (owner/name)
44
+ // and ID lookups may not return results. Extract just the name part for search,
45
+ // and if still no match, fall back to fetching the full list.
46
+ const searchTerm = workspaceArg.includes('/')
47
+ ? workspaceArg.split('/').pop()
48
+ : workspaceArg;
49
+ const searchResults = await client.get(`/me/workspaces?filter=all&search=${encodeURIComponent(searchTerm)}&limit=100`);
50
+ let workspace = searchResults.items.find((w) => matchesWorkspaceArg(w, workspaceArg));
51
+ // Search didn't find it (could be an ID lookup, or search term didn't match).
52
+ // Fall back to fetching all workspaces via pagination.
43
53
  if (!workspace) {
44
- const available = workspaces.items
45
- .map((w) => ` - ${formatWorkspaceDisplay(w)}`)
46
- .join('\n');
47
- output.error(`Workspace "${workspaceArg}" not found.`, `Available workspaces:\n${available}`);
54
+ const allWorkspaces = await client.fetchAll('/me/workspaces?filter=all');
55
+ workspace = allWorkspaces.find((w) => matchesWorkspaceArg(w, workspaceArg));
56
+ }
57
+ if (!workspace) {
58
+ output.error(`Workspace "${workspaceArg}" not found.`, 'Run: guild workspace list');
48
59
  process.exit(1);
49
60
  }
50
61
  const target = await saveWorkspaceConfig(workspace.id, workspace.name);
@@ -56,6 +67,12 @@ export function createWorkspaceSelectCommand() {
56
67
  }
57
68
  return;
58
69
  }
70
+ // Interactive mode: fetch workspaces for selection
71
+ const workspaces = await client.get('/me/workspaces?filter=all');
72
+ if (workspaces.items.length === 0) {
73
+ output.error('No workspaces found.', 'Create a workspace first:\n guild workspace create <name>');
74
+ process.exit(1);
75
+ }
59
76
  // Resolve the currently selected workspace (if any)
60
77
  const current = await getWorkspaceId();
61
78
  const currentIndex = current
@@ -50,5 +50,13 @@ export declare class GuildAPIClient {
50
50
  * Make a DELETE request
51
51
  */
52
52
  delete<T = unknown>(endpoint: string, config?: AxiosRequestConfig): Promise<T>;
53
+ /**
54
+ * Fetch all items from a paginated endpoint by paging through results.
55
+ * Use for display commands or ID-based lookups where you need the complete list.
56
+ *
57
+ * @param endpoint - API endpoint path (may include query params)
58
+ * @returns Flat array of all items across all pages
59
+ */
60
+ fetchAll<T>(endpoint: string): Promise<T[]>;
53
61
  }
54
62
  //# sourceMappingURL=api-client.d.ts.map
@@ -121,5 +121,27 @@ export class GuildAPIClient {
121
121
  async delete(endpoint, config) {
122
122
  return this.request('DELETE', endpoint, config);
123
123
  }
124
+ /**
125
+ * Fetch all items from a paginated endpoint by paging through results.
126
+ * Use for display commands or ID-based lookups where you need the complete list.
127
+ *
128
+ * @param endpoint - API endpoint path (may include query params)
129
+ * @returns Flat array of all items across all pages
130
+ */
131
+ async fetchAll(endpoint) {
132
+ const PAGE_SIZE = 100;
133
+ const allItems = [];
134
+ let offset = 0;
135
+ while (true) {
136
+ const separator = endpoint.includes('?') ? '&' : '?';
137
+ const url = `${endpoint}${separator}limit=${PAGE_SIZE}&offset=${offset}`;
138
+ const response = await this.get(url);
139
+ allItems.push(...response.items);
140
+ offset += response.pagination.limit;
141
+ if (!response.pagination.has_more)
142
+ break;
143
+ }
144
+ return allItems;
145
+ }
124
146
  }
125
147
  //# sourceMappingURL=api-client.js.map
@@ -63,7 +63,7 @@ export interface WorkspaceAgent {
63
63
  };
64
64
  agent_version: {
65
65
  id: string;
66
- sha: string;
66
+ sha: string | null;
67
67
  version_number: string | null;
68
68
  };
69
69
  workspace?: {
@@ -83,7 +83,7 @@ export interface AgentVersion {
83
83
  id: string;
84
84
  agent_id?: string;
85
85
  author_id?: string;
86
- sha: string;
86
+ sha: string | null;
87
87
  summary: string;
88
88
  status: string;
89
89
  validation_status?: string;
@@ -170,7 +170,7 @@ export type AgentListResponse = PaginatedResponse<Agent>;
170
170
  export type WorkspaceListResponse = PaginatedResponse<Workspace>;
171
171
  export type VersionListResponse = PaginatedResponse<AgentVersion>;
172
172
  export type ContextListResponse = PaginatedResponse<Context>;
173
- export type WorkspaceAgentListResponse = CountedResponse<WorkspaceAgent>;
173
+ export type WorkspaceAgentListResponse = PaginatedResponse<WorkspaceAgent>;
174
174
  /**
175
175
  * Response from creating a new agent.
176
176
  */
@@ -227,7 +227,7 @@ export interface TriggerWorkspaceAgent {
227
227
  };
228
228
  agent_version: {
229
229
  id: string;
230
- sha: string;
230
+ sha: string | null;
231
231
  version_number: string | null;
232
232
  };
233
233
  }
@@ -102,7 +102,7 @@ export function formatVersionTable(versions, pagination) {
102
102
  ? chalk.red
103
103
  : chalk.dim;
104
104
  table.addRow({
105
- sha: v.sha.slice(0, 7),
105
+ sha: v.sha ? v.sha.slice(0, 7) : '-',
106
106
  version: v.version_number || '-',
107
107
  status: v.status,
108
108
  validation: validationColor(v.validation_status || '-'),
@@ -176,7 +176,8 @@ export function formatWorkspaceAgentTable(agents) {
176
176
  agents.forEach((wa) => {
177
177
  table.addRow({
178
178
  name: wa.agent.full_name || wa.agent.name,
179
- version: wa.agent_version.version_number || wa.agent_version.sha.slice(0, 7),
179
+ version: wa.agent_version.version_number ||
180
+ (wa.agent_version.sha ? wa.agent_version.sha.slice(0, 7) : '-'),
180
181
  auto_update: wa.should_autoupdate ? chalk.green('yes') : chalk.dim('no'),
181
182
  });
182
183
  });
@@ -238,7 +239,7 @@ export function formatSessionTable(sessions, pagination) {
238
239
  });
239
240
  sessions.forEach((session) => {
240
241
  table.addRow({
241
- id: truncate(session.id, 8),
242
+ id: session.id,
242
243
  type: session.session_type,
243
244
  name: session.name ? truncate(session.name, 30) : '-',
244
245
  workspace: session.workspace?.name || '',
@@ -281,7 +282,7 @@ export function formatTriggerTable(triggers, pagination) {
281
282
  ? chalk.dim('inactive')
282
283
  : chalk.green('active');
283
284
  table.addRow({
284
- id: truncate(trigger.id, 8),
285
+ id: trigger.id,
285
286
  type: trigger.type,
286
287
  agent: trigger.agent?.full_name || trigger.agent?.name || '',
287
288
  status,
@@ -27,8 +27,8 @@ export async function resolveOwnerId(options) {
27
27
  if (me.id === ownerFlag) {
28
28
  return { id: me.id, name: me.name, type: 'user' };
29
29
  }
30
- const orgs = await client.get('/me/organizations');
31
- const org = orgs.items.find((o) => o.id === ownerFlag);
30
+ const orgs = await client.fetchAll('/me/organizations');
31
+ const org = orgs.find((o) => o.id === ownerFlag);
32
32
  if (org) {
33
33
  return { id: org.id, name: org.name, type: 'organization' };
34
34
  }
@@ -43,8 +43,8 @@ export async function resolveOwnerId(options) {
43
43
  if (me.id === envOwnerId) {
44
44
  return { id: me.id, name: me.name, type: 'user' };
45
45
  }
46
- const orgs = await client.get('/me/organizations');
47
- const org = orgs.items.find((o) => o.id === envOwnerId);
46
+ const orgs = await client.fetchAll('/me/organizations');
47
+ const org = orgs.find((o) => o.id === envOwnerId);
48
48
  if (org) {
49
49
  return { id: org.id, name: org.name, type: 'organization' };
50
50
  }
@@ -63,9 +63,9 @@ export async function resolveOwnerId(options) {
63
63
  }
64
64
  // Priority 4: Fetch user + orgs
65
65
  const me = await client.get('/me');
66
- const orgs = await client.get('/me/organizations');
66
+ const orgs = await client.fetchAll('/me/organizations');
67
67
  // No orgs → use current user
68
- if (orgs.items.length === 0) {
68
+ if (orgs.length === 0) {
69
69
  debug('No organizations found, using current user as owner');
70
70
  return { id: me.id, name: me.name, type: 'user' };
71
71
  }
@@ -80,7 +80,7 @@ export async function resolveOwnerId(options) {
80
80
  name: `${me.name} (personal)`,
81
81
  value: { id: me.id, name: me.name, type: 'user' },
82
82
  },
83
- ...orgs.items.map((org) => ({
83
+ ...orgs.map((org) => ({
84
84
  name: org.name,
85
85
  value: { id: org.id, name: org.name, type: 'organization' },
86
86
  })),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guildai/cli",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Guild.ai CLI - Build, test, and deploy AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",