@agentuity/cli 2.0.6 → 2.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +11 -0
  2. package/dist/cmd/cloud/task/close.d.ts +3 -0
  3. package/dist/cmd/cloud/task/close.d.ts.map +1 -0
  4. package/dist/cmd/cloud/task/close.js +286 -0
  5. package/dist/cmd/cloud/task/close.js.map +1 -0
  6. package/dist/cmd/cloud/task/delete.d.ts +1 -5
  7. package/dist/cmd/cloud/task/delete.d.ts.map +1 -1
  8. package/dist/cmd/cloud/task/delete.js +15 -38
  9. package/dist/cmd/cloud/task/delete.js.map +1 -1
  10. package/dist/cmd/cloud/task/index.d.ts.map +1 -1
  11. package/dist/cmd/cloud/task/index.js +10 -0
  12. package/dist/cmd/cloud/task/index.js.map +1 -1
  13. package/dist/cmd/cloud/task/list.d.ts.map +1 -1
  14. package/dist/cmd/cloud/task/list.js +97 -3
  15. package/dist/cmd/cloud/task/list.js.map +1 -1
  16. package/dist/cmd/cloud/task/util.d.ts +10 -0
  17. package/dist/cmd/cloud/task/util.d.ts.map +1 -1
  18. package/dist/cmd/cloud/task/util.js +47 -3
  19. package/dist/cmd/cloud/task/util.js.map +1 -1
  20. package/dist/cmd/coder/config/index.d.ts +2 -0
  21. package/dist/cmd/coder/config/index.d.ts.map +1 -0
  22. package/dist/cmd/coder/config/index.js +20 -0
  23. package/dist/cmd/coder/config/index.js.map +1 -0
  24. package/dist/cmd/coder/config/set.d.ts +2 -0
  25. package/dist/cmd/coder/config/set.d.ts.map +1 -0
  26. package/dist/cmd/coder/config/set.js +100 -0
  27. package/dist/cmd/coder/config/set.js.map +1 -0
  28. package/dist/cmd/coder/hub-url.d.ts +21 -10
  29. package/dist/cmd/coder/hub-url.d.ts.map +1 -1
  30. package/dist/cmd/coder/hub-url.js +97 -55
  31. package/dist/cmd/coder/hub-url.js.map +1 -1
  32. package/dist/cmd/coder/index.d.ts.map +1 -1
  33. package/dist/cmd/coder/index.js +6 -1
  34. package/dist/cmd/coder/index.js.map +1 -1
  35. package/dist/cmd/coder/inspect.d.ts.map +1 -1
  36. package/dist/cmd/coder/inspect.js +15 -7
  37. package/dist/cmd/coder/inspect.js.map +1 -1
  38. package/dist/cmd/coder/list.d.ts.map +1 -1
  39. package/dist/cmd/coder/list.js +14 -7
  40. package/dist/cmd/coder/list.js.map +1 -1
  41. package/dist/cmd/coder/start.d.ts.map +1 -1
  42. package/dist/cmd/coder/start.js +38 -23
  43. package/dist/cmd/coder/start.js.map +1 -1
  44. package/dist/cmd/coder/tui-init.d.ts +4 -1
  45. package/dist/cmd/coder/tui-init.d.ts.map +1 -1
  46. package/dist/cmd/coder/tui-init.js +3 -2
  47. package/dist/cmd/coder/tui-init.js.map +1 -1
  48. package/dist/cmd/dev/sync.js +5 -5
  49. package/dist/cmd/dev/sync.js.map +1 -1
  50. package/dist/coder-config.d.ts +14 -0
  51. package/dist/coder-config.d.ts.map +1 -0
  52. package/dist/coder-config.js +119 -0
  53. package/dist/coder-config.js.map +1 -0
  54. package/dist/coder-hub-url.d.ts +3 -0
  55. package/dist/coder-hub-url.d.ts.map +1 -0
  56. package/dist/coder-hub-url.js +32 -0
  57. package/dist/coder-hub-url.js.map +1 -0
  58. package/dist/config.d.ts +1 -0
  59. package/dist/config.d.ts.map +1 -1
  60. package/dist/config.js +11 -0
  61. package/dist/config.js.map +1 -1
  62. package/dist/internal-logger.d.ts +4 -0
  63. package/dist/internal-logger.d.ts.map +1 -1
  64. package/dist/internal-logger.js +64 -2
  65. package/dist/internal-logger.js.map +1 -1
  66. package/dist/keychain.d.ts +3 -0
  67. package/dist/keychain.d.ts.map +1 -1
  68. package/dist/keychain.js +47 -28
  69. package/dist/keychain.js.map +1 -1
  70. package/dist/types.d.ts +4 -0
  71. package/dist/types.d.ts.map +1 -1
  72. package/dist/types.js +10 -0
  73. package/dist/types.js.map +1 -1
  74. package/package.json +6 -6
  75. package/src/cmd/cloud/task/close.ts +319 -0
  76. package/src/cmd/cloud/task/delete.ts +15 -43
  77. package/src/cmd/cloud/task/index.ts +10 -0
  78. package/src/cmd/cloud/task/list.ts +111 -4
  79. package/src/cmd/cloud/task/util.ts +59 -5
  80. package/src/cmd/coder/config/index.ts +20 -0
  81. package/src/cmd/coder/config/set.ts +112 -0
  82. package/src/cmd/coder/hub-url.ts +147 -53
  83. package/src/cmd/coder/index.ts +6 -1
  84. package/src/cmd/coder/inspect.ts +33 -10
  85. package/src/cmd/coder/list.ts +33 -10
  86. package/src/cmd/coder/start.ts +62 -26
  87. package/src/cmd/coder/tui-init.ts +7 -2
  88. package/src/cmd/dev/sync.ts +5 -5
  89. package/src/coder-config.ts +141 -0
  90. package/src/coder-hub-url.ts +32 -0
  91. package/src/config.ts +13 -0
  92. package/src/internal-logger.ts +83 -2
  93. package/src/keychain.ts +68 -39
  94. package/src/types.ts +10 -0
@@ -0,0 +1,319 @@
1
+ import { z } from 'zod';
2
+ import { createCommand } from '../../../types';
3
+ import * as tui from '../../../tui';
4
+ import { createStorageAdapter, resolveMeId, parseDuration, truncate } from './util';
5
+ import { getCommand } from '../../../command-prefix';
6
+ import { isDryRunMode, outputDryRun } from '../../../explain';
7
+ import type { TaskPriority, TaskStatus, TaskType, BatchClosedTask } from '@agentuity/core';
8
+
9
+ const TaskCloseResponseSchema = z.object({
10
+ success: z.boolean().describe('Whether the operation succeeded'),
11
+ closed: z
12
+ .array(
13
+ z.object({
14
+ id: z.string().describe('Closed task ID'),
15
+ title: z.string().describe('Closed task title'),
16
+ status: z.string().describe('Task status'),
17
+ closed_date: z.string().optional().describe('ISO 8601 closed date'),
18
+ })
19
+ )
20
+ .describe('List of closed tasks'),
21
+ count: z.number().describe('Number of tasks closed'),
22
+ durationMs: z.number().describe('Operation duration in milliseconds'),
23
+ dryRun: z.boolean().optional().describe('Whether this was a dry run'),
24
+ message: z.string().optional().describe('Status message'),
25
+ });
26
+
27
+ export const closeSubcommand = createCommand({
28
+ name: 'close',
29
+ aliases: ['done', 'complete'],
30
+ description: 'Close a task by ID or batch-close tasks by filter',
31
+ tags: ['mutating', 'slow', 'requires-auth'],
32
+ requires: { auth: true },
33
+ examples: [
34
+ {
35
+ command: getCommand('cloud task close task_abc123'),
36
+ description: 'Close a single task by ID',
37
+ },
38
+ {
39
+ command: getCommand('cloud task close --status in_progress --older-than 7d'),
40
+ description: 'Close in-progress tasks older than 7 days',
41
+ },
42
+ {
43
+ command: getCommand('cloud task close --status open --limit 10 --dry-run'),
44
+ description: 'Preview which open tasks would be closed (dry run)',
45
+ },
46
+ {
47
+ command: getCommand('cloud task close --created-id me --confirm'),
48
+ description: 'Close all tasks created by me without confirmation prompt',
49
+ },
50
+ ],
51
+ schema: {
52
+ args: z.object({
53
+ id: z.string().optional().describe('Task ID to close (for single close)'),
54
+ }),
55
+ options: z.object({
56
+ status: z
57
+ .enum(['open', 'in_progress', 'started', 'done', 'completed', 'closed', 'cancelled'])
58
+ .optional()
59
+ .describe('filter batch close by status'),
60
+ type: z
61
+ .enum(['epic', 'feature', 'enhancement', 'bug', 'task'])
62
+ .optional()
63
+ .describe('filter batch close by type'),
64
+ priority: z
65
+ .enum(['high', 'medium', 'low', 'none'])
66
+ .optional()
67
+ .describe('filter batch close by priority'),
68
+ olderThan: z
69
+ .string()
70
+ .optional()
71
+ .describe('filter batch close by age (e.g. 30s, 7d, 24h, 2w)'),
72
+ parentId: z.string().optional().describe('filter batch close by parent task ID'),
73
+ createdId: z
74
+ .string()
75
+ .optional()
76
+ .describe('filter batch close by creator ID (use "me" for current user)'),
77
+ assignedId: z.string().optional().describe('filter batch close by assigned user ID'),
78
+ projectId: z.string().optional().describe('filter batch close by project ID'),
79
+ tagId: z.string().optional().describe('filter batch close by tag ID'),
80
+ idsFile: z.string().optional().describe('path to JSON file containing task IDs to close'),
81
+ orgId: z.string().optional().describe('organization ID (uses default if not specified)'),
82
+ dryRun: z
83
+ .boolean()
84
+ .optional()
85
+ .default(false)
86
+ .describe('preview changes without executing'),
87
+ limit: z.coerce
88
+ .number()
89
+ .int()
90
+ .min(1)
91
+ .max(200)
92
+ .default(50)
93
+ .describe('max tasks to close in batch mode (default: 50, max: 200)'),
94
+ confirm: z.boolean().optional().default(false).describe('skip confirmation prompt'),
95
+ }),
96
+ response: TaskCloseResponseSchema,
97
+ },
98
+
99
+ async handler(ctx) {
100
+ const { args, opts, options } = ctx;
101
+ const started = Date.now();
102
+ const storage = await createStorageAdapter(ctx);
103
+
104
+ const isSingleClose = !!args.id;
105
+ const hasFilters =
106
+ opts.status ||
107
+ opts.type ||
108
+ opts.priority ||
109
+ opts.olderThan ||
110
+ opts.parentId ||
111
+ opts.createdId ||
112
+ opts.assignedId ||
113
+ opts.projectId ||
114
+ opts.tagId ||
115
+ opts.idsFile;
116
+
117
+ if (!isSingleClose && !hasFilters) {
118
+ tui.fatal(
119
+ 'Provide a task ID for single close, or use --status, --type, --priority, --older-than, --parent-id, --created-id, --assigned-id, --project-id, --tag-id, or --ids-file for batch close.'
120
+ );
121
+ }
122
+
123
+ if (isSingleClose && hasFilters) {
124
+ tui.fatal(
125
+ 'Cannot combine task ID with filter options. Use either single close (by ID) or batch close (by filters).'
126
+ );
127
+ }
128
+
129
+ if (isSingleClose) {
130
+ if (isDryRunMode(options)) {
131
+ outputDryRun(`Would close task: ${args.id}`, options);
132
+ return {
133
+ success: true,
134
+ closed: [{ id: args.id!, title: '(dry run)', status: 'done' }],
135
+ count: 1,
136
+ durationMs: Date.now() - started,
137
+ dryRun: true,
138
+ message: 'Dry run — no tasks were closed',
139
+ };
140
+ }
141
+
142
+ if (!opts.confirm) {
143
+ const confirmed = await tui.confirm(`Close task "${args.id}"?`, false);
144
+ if (!confirmed) {
145
+ if (!options.json) tui.info('Cancelled');
146
+ return {
147
+ success: false,
148
+ closed: [],
149
+ count: 0,
150
+ durationMs: Date.now() - started,
151
+ message: 'Cancelled',
152
+ };
153
+ }
154
+ }
155
+
156
+ const task = await storage.close(args.id!);
157
+ const durationMs = Date.now() - started;
158
+
159
+ if (!options.json) {
160
+ tui.success(`Closed task ${tui.bold(task.id)} (${task.title}) in ${durationMs}ms`);
161
+ }
162
+
163
+ return {
164
+ success: true,
165
+ closed: [
166
+ {
167
+ id: task.id,
168
+ title: task.title,
169
+ status: task.status,
170
+ closed_date: task.closed_date,
171
+ },
172
+ ],
173
+ count: 1,
174
+ durationMs,
175
+ };
176
+ }
177
+
178
+ // Batch close mode
179
+ if (opts.olderThan) {
180
+ parseDuration(opts.olderThan);
181
+ }
182
+
183
+ const createdId = resolveMeId(opts.createdId, ctx);
184
+ const assignedId = resolveMeId(opts.assignedId, ctx);
185
+
186
+ // Handle IDs file
187
+ let explicitIds: string[] | undefined;
188
+ if (opts.idsFile) {
189
+ const file = Bun.file(opts.idsFile);
190
+ if (!(await file.exists())) {
191
+ tui.fatal(`IDs file not found: ${opts.idsFile}`);
192
+ }
193
+ try {
194
+ const content = await file.json();
195
+ if (Array.isArray(content)) {
196
+ explicitIds = content.map((id) => String(id));
197
+ } else if (content && Array.isArray((content as { ids?: string[] }).ids)) {
198
+ explicitIds = (content as { ids: string[] }).ids;
199
+ } else {
200
+ tui.fatal(`Invalid IDs file format. Expected array of IDs or { ids: [...] }`);
201
+ }
202
+ } catch (err) {
203
+ tui.fatal(`Failed to parse IDs file: ${err}`);
204
+ }
205
+ }
206
+
207
+ const batchParams = {
208
+ status: opts.status as TaskStatus | undefined,
209
+ type: opts.type as TaskType | undefined,
210
+ priority: opts.priority as TaskPriority | undefined,
211
+ parent_id: opts.parentId,
212
+ created_id: createdId,
213
+ assigned_id: assignedId,
214
+ project_id: opts.projectId,
215
+ tag_id: opts.tagId,
216
+ older_than: opts.olderThan,
217
+ ids: explicitIds,
218
+ limit: opts.limit,
219
+ closed_id: ctx.auth.userId,
220
+ dry_run: isDryRunMode(options),
221
+ };
222
+
223
+ // For confirmation, run a dry-run first to preview
224
+ if (!isDryRunMode(options) && !opts.confirm) {
225
+ const preview = await storage.batchClose({ ...batchParams, dry_run: true });
226
+
227
+ if (preview.count === 0) {
228
+ if (!options.json) tui.info('No tasks match the given filters');
229
+ return {
230
+ success: true,
231
+ closed: [],
232
+ count: 0,
233
+ durationMs: Date.now() - started,
234
+ message: 'No matching tasks found',
235
+ };
236
+ }
237
+
238
+ if (!options.json) {
239
+ tui.warning(
240
+ `Found ${preview.count} ${tui.plural(preview.count, 'task', 'tasks')} to close:`
241
+ );
242
+ tui.newline();
243
+
244
+ const tableData = preview.closed.map((task: BatchClosedTask) => ({
245
+ ID: tui.muted(truncate(task.id, 28)),
246
+ Title: truncate(task.title, 40),
247
+ Status: task.status,
248
+ }));
249
+
250
+ tui.table(tableData, [
251
+ { name: 'ID', alignment: 'left' },
252
+ { name: 'Title', alignment: 'left' },
253
+ { name: 'Status', alignment: 'left' },
254
+ ]);
255
+ tui.newline();
256
+ }
257
+
258
+ const confirmed = await tui.confirm(
259
+ `Close ${preview.count} ${tui.plural(preview.count, 'task', 'tasks')}?`,
260
+ false
261
+ );
262
+ if (!confirmed) {
263
+ if (!options.json) tui.info('Cancelled');
264
+ return {
265
+ success: false,
266
+ closed: [],
267
+ count: 0,
268
+ durationMs: Date.now() - started,
269
+ message: 'Cancelled',
270
+ };
271
+ }
272
+ }
273
+
274
+ // Execute batch close
275
+ const result = await storage.batchClose(batchParams);
276
+ const durationMs = Date.now() - started;
277
+
278
+ if (!options.json) {
279
+ if (result.dry_run) {
280
+ if (result.count > 0) {
281
+ tui.info(
282
+ `Dry run: would close ${result.count} ${tui.plural(result.count, 'task', 'tasks')}`
283
+ );
284
+ } else {
285
+ tui.info('No tasks match the given filters');
286
+ }
287
+ } else if (result.count > 0) {
288
+ tui.success(
289
+ `Closed ${result.count} ${tui.plural(result.count, 'task', 'tasks')} in ${durationMs}ms`
290
+ );
291
+
292
+ // Show which tasks were closed
293
+ if (result.closed.length > 0) {
294
+ tui.newline();
295
+ const closedTable = result.closed.map((task) => ({
296
+ ID: tui.muted(truncate(task.id, 28)),
297
+ Title: truncate(task.title, 40),
298
+ }));
299
+ tui.table(closedTable, [
300
+ { name: 'ID', alignment: 'left' },
301
+ { name: 'Title', alignment: 'left' },
302
+ ]);
303
+ }
304
+ } else {
305
+ tui.info('No tasks matched the given filters');
306
+ }
307
+ }
308
+
309
+ return {
310
+ success: true,
311
+ closed: result.closed,
312
+ count: result.count,
313
+ durationMs,
314
+ dryRun: result.dry_run,
315
+ };
316
+ },
317
+ });
318
+
319
+ export default closeSubcommand;
@@ -1,46 +1,13 @@
1
1
  import { z } from 'zod';
2
2
  import { createCommand } from '../../../types';
3
3
  import * as tui from '../../../tui';
4
- import { createStorageAdapter } from './util';
4
+ import { createStorageAdapter, resolveMeId, parseDuration, truncate } from './util';
5
5
  import { getCommand } from '../../../command-prefix';
6
6
  import { isDryRunMode, outputDryRun } from '../../../explain';
7
7
  import type { TaskPriority, TaskStatus, TaskType, BatchDeletedTask } from '@agentuity/core';
8
8
 
9
- const DURATION_UNITS: Record<string, number> = {
10
- s: 1000,
11
- m: 60 * 1000,
12
- h: 60 * 60 * 1000,
13
- d: 24 * 60 * 60 * 1000,
14
- w: 7 * 24 * 60 * 60 * 1000,
15
- };
16
-
17
- /**
18
- * Parse a human-friendly duration string (e.g. "30s", "7d", "24h", "30m", "2w")
19
- * into milliseconds. Exported for testing.
20
- */
21
- export function parseDuration(duration: string): number {
22
- const match = duration.match(/^(\d+)([smhdw])$/);
23
- if (!match) {
24
- tui.fatal(
25
- `Invalid duration format: "${duration}". Use a number followed by s (seconds), m (minutes), h (hours), d (days), or w (weeks). Examples: 30s, 30m, 24h, 7d, 2w`
26
- );
27
- // tui.fatal exits, but TypeScript doesn't know that
28
- throw new Error('unreachable');
29
- }
30
- const value = parseInt(match[1]!, 10);
31
- const unit = match[2]!;
32
- const ms = DURATION_UNITS[unit];
33
- if (!ms) {
34
- tui.fatal(`Unknown duration unit: "${unit}"`);
35
- throw new Error('unreachable');
36
- }
37
- return value * ms;
38
- }
39
-
40
- function truncate(s: string, max: number): string {
41
- if (s.length <= max) return s;
42
- return `${s.slice(0, max - 1)}…`;
43
- }
9
+ // Re-export for testing
10
+ export { parseDuration } from './util';
44
11
 
45
12
  const TaskDeleteResponseSchema = z.object({
46
13
  success: z.boolean().describe('Whether the operation succeeded'),
@@ -104,7 +71,16 @@ export const deleteSubcommand = createCommand({
104
71
  .optional()
105
72
  .describe('filter batch delete by age (e.g. 30s, 7d, 24h, 2w)'),
106
73
  parentId: z.string().optional().describe('filter batch delete by parent task ID'),
107
- createdId: z.string().optional().describe('filter batch delete by creator ID'),
74
+ createdId: z
75
+ .string()
76
+ .optional()
77
+ .describe('filter batch delete by creator ID (use "me" for current user)'),
78
+ orgId: z.string().optional().describe('organization ID (uses default if not specified)'),
79
+ dryRun: z
80
+ .boolean()
81
+ .optional()
82
+ .default(false)
83
+ .describe('preview changes without executing'),
108
84
  limit: z.coerce
109
85
  .number()
110
86
  .int()
@@ -198,7 +174,7 @@ export const deleteSubcommand = createCommand({
198
174
  type: opts.type as TaskType | undefined,
199
175
  priority: opts.priority as TaskPriority | undefined,
200
176
  parent_id: opts.parentId,
201
- created_id: opts.createdId,
177
+ created_id: resolveMeId(opts.createdId, ctx),
202
178
  older_than: opts.olderThan,
203
179
  limit: opts.limit,
204
180
  };
@@ -212,6 +188,7 @@ export const deleteSubcommand = createCommand({
212
188
  type: batchParams.type,
213
189
  priority: batchParams.priority,
214
190
  parent_id: batchParams.parent_id,
191
+ created_id: batchParams.created_id,
215
192
  limit: batchParams.limit,
216
193
  sort: 'created_at',
217
194
  order: 'asc',
@@ -219,11 +196,6 @@ export const deleteSubcommand = createCommand({
219
196
 
220
197
  // Client-side filters for preview (server will apply these on actual delete)
221
198
  let candidates = preview.tasks;
222
- if (batchParams.created_id) {
223
- candidates = candidates.filter(
224
- (t: { created_id: string }) => t.created_id === batchParams.created_id
225
- );
226
- }
227
199
  if (opts.olderThan) {
228
200
  const durationMs = parseDuration(opts.olderThan);
229
201
  const cutoff = new Date(Date.now() - durationMs);
@@ -4,6 +4,7 @@ import { createSubcommand } from './create';
4
4
  import { updateSubcommand } from './update';
5
5
  import { listSubcommand } from './list';
6
6
  import { deleteSubcommand } from './delete';
7
+ import { closeSubcommand } from './close';
7
8
  import { statsSubcommand } from './stats';
8
9
  import { attachmentSubcommand } from './attachment';
9
10
  import { userSubcommand } from './user';
@@ -36,6 +37,14 @@ export const taskCommand = createCommand({
36
37
  command: getCommand('cloud task delete task_abc123'),
37
38
  description: 'Delete a task by ID',
38
39
  },
40
+ {
41
+ command: getCommand('cloud task close task_abc123'),
42
+ description: 'Close a task by ID',
43
+ },
44
+ {
45
+ command: getCommand('cloud task close --status done --older-than 7d'),
46
+ description: 'Batch close done tasks older than 7 days',
47
+ },
39
48
  {
40
49
  command: getCommand('cloud task delete --status done --older-than 7d'),
41
50
  description: 'Batch delete done tasks older than 7 days',
@@ -59,6 +68,7 @@ export const taskCommand = createCommand({
59
68
  updateSubcommand,
60
69
  listSubcommand,
61
70
  deleteSubcommand,
71
+ closeSubcommand,
62
72
  statsSubcommand,
63
73
  attachmentSubcommand,
64
74
  userSubcommand,
@@ -1,9 +1,9 @@
1
1
  import { z } from 'zod';
2
2
  import { createCommand } from '../../../types';
3
3
  import * as tui from '../../../tui';
4
- import { createStorageAdapter } from './util';
4
+ import { createStorageAdapter, resolveMeId } from './util';
5
5
  import { getCommand } from '../../../command-prefix';
6
- import type { TaskPriority, TaskStatus, TaskType, Task } from '@agentuity/core';
6
+ import type { TaskPriority, TaskStatus, TaskType, Task, TaskIncludeField } from '@agentuity/core';
7
7
 
8
8
  const TaskListResponseSchema = z.object({
9
9
  success: z.boolean().describe('Whether the operation succeeded'),
@@ -14,6 +14,12 @@ const TaskListResponseSchema = z.object({
14
14
  type: z.string(),
15
15
  status: z.string(),
16
16
  priority: z.string(),
17
+ description: z.string().optional(),
18
+ metadata: z.record(z.string(), z.unknown()).optional(),
19
+ tags: z.array(z.object({ id: z.string(), name: z.string() })).optional(),
20
+ subtask_count: z.number().optional(),
21
+ created_id: z.string().optional(),
22
+ deleted: z.boolean().optional(),
17
23
  creator: z
18
24
  .object({
19
25
  id: z.string(),
@@ -73,6 +79,38 @@ function truncate(s: string, max: number): string {
73
79
  return `${s.slice(0, max - 1)}…`;
74
80
  }
75
81
 
82
+ const VALID_INCLUDE_FIELDS = new Set<TaskIncludeField>([
83
+ 'description',
84
+ 'metadata',
85
+ 'tags',
86
+ 'subtask_count',
87
+ 'created_id',
88
+ 'deleted',
89
+ ]);
90
+
91
+ function parseIncludeParam(include: string | undefined): TaskIncludeField[] | undefined {
92
+ if (!include) return undefined;
93
+ const fields: TaskIncludeField[] = [];
94
+ for (const f of include.split(',')) {
95
+ const trimmed = f.trim() as TaskIncludeField;
96
+ if (VALID_INCLUDE_FIELDS.has(trimmed)) {
97
+ fields.push(trimmed);
98
+ } else {
99
+ tui.fatal(
100
+ `Invalid include field: "${trimmed}". Valid fields are: ${[...VALID_INCLUDE_FIELDS].join(', ')}`
101
+ );
102
+ }
103
+ }
104
+ return fields.length > 0 ? fields : undefined;
105
+ }
106
+
107
+ function hasIncludeField(
108
+ include: TaskIncludeField[] | undefined,
109
+ field: TaskIncludeField
110
+ ): boolean {
111
+ return include?.includes(field) ?? false;
112
+ }
113
+
76
114
  export const listSubcommand = createCommand({
77
115
  name: 'list',
78
116
  aliases: ['ls'],
@@ -103,6 +141,14 @@ export const listSubcommand = createCommand({
103
141
  command: getCommand('cloud task list --assigned-id agent_001 --limit 10'),
104
142
  description: 'List first 10 tasks assigned to an agent',
105
143
  },
144
+ {
145
+ command: getCommand('cloud task list --created-id me --include description,metadata,tags'),
146
+ description: 'List tasks created by me with full details',
147
+ },
148
+ {
149
+ command: getCommand('cloud task list --project-id proj_abc123'),
150
+ description: 'List tasks for a specific project',
151
+ },
106
152
  ],
107
153
  schema: {
108
154
  options: z.object({
@@ -118,8 +164,24 @@ export const listSubcommand = createCommand({
118
164
  .enum(['high', 'medium', 'low', 'none'])
119
165
  .optional()
120
166
  .describe('filter by priority'),
121
- assignedId: z.string().optional().describe('filter by assigned agent or user ID'),
167
+ assignedId: z
168
+ .string()
169
+ .optional()
170
+ .describe('filter by assigned agent or user ID (use "me" for current user)'),
171
+ createdId: z
172
+ .string()
173
+ .optional()
174
+ .describe('filter by creator ID (use "me" for current user)'),
122
175
  parentId: z.string().optional().describe('filter by parent task ID'),
176
+ projectId: z.string().optional().describe('filter by project ID'),
177
+ tagId: z.string().optional().describe('filter by tag ID'),
178
+ deleted: z.boolean().optional().describe('include soft-deleted tasks'),
179
+ include: z
180
+ .string()
181
+ .optional()
182
+ .describe(
183
+ 'comma-separated fields to include: description,metadata,tags,subtask_count,created_id,deleted'
184
+ ),
123
185
  sort: z
124
186
  .enum(['created_at', 'updated_at', 'priority'])
125
187
  .optional()
@@ -127,6 +189,7 @@ export const listSubcommand = createCommand({
127
189
  order: z.enum(['asc', 'desc']).optional().describe('sort order (default: desc)'),
128
190
  limit: z.coerce.number().optional().describe('max results to return (default: 50)'),
129
191
  offset: z.coerce.number().optional().describe('offset for pagination'),
192
+ orgId: z.string().optional().describe('organization ID (uses default if not specified)'),
130
193
  }),
131
194
  response: TaskListResponseSchema,
132
195
  },
@@ -136,12 +199,22 @@ export const listSubcommand = createCommand({
136
199
  const started = Date.now();
137
200
  const storage = await createStorageAdapter(ctx);
138
201
 
202
+ const createdId = resolveMeId(opts.createdId, ctx);
203
+ const assignedId = resolveMeId(opts.assignedId, ctx);
204
+
205
+ const includeFields = parseIncludeParam(opts.include);
206
+
139
207
  const result = await storage.list({
140
208
  status: opts.status as TaskStatus | undefined,
141
209
  type: opts.type as TaskType | undefined,
142
210
  priority: opts.priority as TaskPriority | undefined,
143
- assigned_id: opts.assignedId,
211
+ assigned_id: assignedId,
212
+ created_id: createdId,
144
213
  parent_id: opts.parentId,
214
+ project_id: opts.projectId,
215
+ tag_id: opts.tagId,
216
+ deleted: opts.deleted,
217
+ include: includeFields,
145
218
  sort: opts.sort,
146
219
  order: opts.order,
147
220
  limit: opts.limit,
@@ -154,6 +227,10 @@ export const listSubcommand = createCommand({
154
227
  if (result.tasks.length === 0) {
155
228
  tui.info('No tasks found');
156
229
  } else {
230
+ const showDescription = hasIncludeField(includeFields, 'description');
231
+ const showTags = hasIncludeField(includeFields, 'tags');
232
+ const showMetadata = hasIncludeField(includeFields, 'metadata');
233
+
157
234
  const tableData = result.tasks.map((task: Task) => ({
158
235
  ID: tui.muted(truncate(task.id, 28)),
159
236
  Title: truncate(task.title, 40),
@@ -176,6 +253,30 @@ export const listSubcommand = createCommand({
176
253
  { name: 'Updated', alignment: 'left' },
177
254
  ]);
178
255
 
256
+ // Show extra details for each task if included
257
+ if (showDescription || showTags || showMetadata) {
258
+ for (const task of result.tasks) {
259
+ const extras: string[] = [];
260
+ if (showDescription && task.description) {
261
+ extras.push(`${tui.muted('Desc:')} ${truncate(task.description, 80)}`);
262
+ }
263
+ if (showTags && task.tags && task.tags.length > 0) {
264
+ const tagList = task.tags.map((t) => t.name).join(', ');
265
+ extras.push(`${tui.muted('Tags:')} ${tagList}`);
266
+ }
267
+ if (showMetadata && task.metadata) {
268
+ const metaStr =
269
+ typeof task.metadata === 'object'
270
+ ? JSON.stringify(task.metadata)
271
+ : String(task.metadata);
272
+ extras.push(`${tui.muted('Meta:')} ${truncate(metaStr, 80)}`);
273
+ }
274
+ if (extras.length > 0) {
275
+ tui.output(` ${tui.muted(truncate(task.id, 28))} → ${extras.join(' | ')}`);
276
+ }
277
+ }
278
+ }
279
+
179
280
  tui.info(
180
281
  `Showing ${result.tasks.length} of ${result.total} ${tui.plural(result.total, 'task', 'tasks')} (${durationMs.toFixed(1)}ms)`
181
282
  );
@@ -190,6 +291,12 @@ export const listSubcommand = createCommand({
190
291
  type: task.type,
191
292
  status: task.status,
192
293
  priority: task.priority,
294
+ description: task.description,
295
+ metadata: task.metadata,
296
+ tags: task.tags,
297
+ subtask_count: task.subtask_count,
298
+ created_id: task.created_id,
299
+ deleted: task.deleted,
193
300
  creator: task.creator,
194
301
  assignee: task.assignee,
195
302
  project: task.project,