epismo 0.4.0 → 0.4.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/dist/program.js CHANGED
@@ -3,21 +3,19 @@ import { clearWorkspace, getCurrentWorkspace, listAvailableWorkspaces, login, lo
3
3
  import { resolveExecutionContext } from "./context.js";
4
4
  import { mergeDefined, parseJsonArrayOption, parseJsonObjectOption, parseOptionalPositiveInteger, parseStringArrayInput, readJsonObjectInput } from "./input.js";
5
5
  import { printJson, printWarning } from "./output.js";
6
- import { deleteTrack, getTrack, searchTracks, upsertTrack } from "./tracks.js";
7
- import { deletePack, getPack, likePack, searchPacks, upsertPack } from "./packs.js";
6
+ import { applyTracks, createTrack, deleteTrack, getTrack, searchTracks, updateTrack } from "./tracks.js";
7
+ import { createPack, deletePack, getPack, likePack, searchPacks, updatePack } from "./packs.js";
8
8
  import { deleteAlias, getAlias, listAliases, upsertAlias } from "./aliases.js";
9
9
  import { creditBalance, creditCheckout } from "./credits.js";
10
10
  import { CliError } from "./errors.js";
11
11
  import { addAgents, listAgents, removeAgents } from "./agents.js";
12
- import { deleteWorkspaceMember, listWorkspaceMembers, upsertWorkspace, upsertWorkspaceMember } from "./workspaces.js";
13
- import { addProjectMember, deleteProject, deleteProjectMember, listProjectMembers, listProjects, upsertProject } from "./projects.js";
12
+ import { createWorkspace, deleteWorkspaceMember, getWorkspaceCheckout, listWorkspaceMembers, updateWorkspace, upsertWorkspaceMember } from "./workspaces.js";
13
+ import { addProjectMember, createProject, deleteProject, deleteProjectMember, listProjectMembers, listProjects, updateProject } from "./projects.js";
14
+ import { createToken } from "./token.js";
14
15
  const TRACK_TYPES = ["task", "goal"];
15
16
  const PACK_TYPES = ["workflow", "context"];
16
17
  const PACK_VISIBILITIES = ["public", "private"];
17
- // #1: centralize --workspace-id option so description is defined once
18
- const WORKSPACE_ID_OPT = "--workspace-id <workspaceId>";
19
- const WORKSPACE_ID_DESC = "workspace to use; resolved in order: CLI arg → saved default → personal space";
20
- // #2: centralize warning codes so wording is consistent everywhere
18
+ // centralize warning codes so wording is consistent everywhere
21
19
  const WARNING_ENV_TOKEN_WORKSPACE_IGNORED = "EPISMO_TOKEN_WORKSPACE_IGNORED";
22
20
  async function resolveInput(options, overrides) {
23
21
  const base = await readJsonObjectInput(options.input);
@@ -30,8 +28,42 @@ function getExplicitOptionOverride(command, optionName, value) {
30
28
  }
31
29
  return value;
32
30
  }
33
- async function resolveContext(options) {
34
- const context = await resolveExecutionContext(options);
31
+ function buildSharingTargets(command, options) {
32
+ const targets = mergeDefined({}, {
33
+ projectIds: getExplicitOptionOverride(command, "projectIds", parseStringArrayInput(options.projectIds, "--project-ids")),
34
+ userIds: getExplicitOptionOverride(command, "userIds", parseStringArrayInput(options.userIds, "--user-ids")),
35
+ emails: getExplicitOptionOverride(command, "emails", parseStringArrayInput(options.emails, "--emails"))
36
+ });
37
+ return Object.keys(targets).length > 0 ? targets : undefined;
38
+ }
39
+ function buildSearchTargets(command, options) {
40
+ const targets = mergeDefined({}, {
41
+ projectIds: getExplicitOptionOverride(command, "projectIds", parseStringArrayInput(options.projectIds, "--project-ids")),
42
+ self: getExplicitOptionOverride(command, "self", parseOptionalBooleanText(options.self, "--self"))
43
+ });
44
+ return Object.keys(targets).length > 0 ? targets : undefined;
45
+ }
46
+ function parseOptionalBooleanText(value, optionName) {
47
+ if (value === undefined) {
48
+ return undefined;
49
+ }
50
+ if (typeof value === "boolean") {
51
+ return value;
52
+ }
53
+ if (typeof value === "string") {
54
+ const normalized = value.trim().toLowerCase();
55
+ if (normalized === "true")
56
+ return true;
57
+ if (normalized === "false")
58
+ return false;
59
+ }
60
+ throw new CliError({
61
+ code: "INVALID_INPUT",
62
+ message: `Invalid ${optionName}: expected true or false.`
63
+ });
64
+ }
65
+ async function resolveContext() {
66
+ const context = await resolveExecutionContext();
35
67
  if (context.warning) {
36
68
  printWarning({
37
69
  warning: { code: WARNING_ENV_TOKEN_WORKSPACE_IGNORED, message: context.warning }
@@ -51,11 +83,12 @@ export function buildProgram(version) {
51
83
  .exitOverride();
52
84
  program.addHelpText("after", `
53
85
  Workspace resolution:
54
- Commands that accept --workspace-id resolve the workspace in this order:
55
- 1. --workspace-id flag
56
- 2. Saved default (epismo workspace use)
57
- 3. Personal space (fallback)
58
- When EPISMO_TOKEN is set, the saved default is skipped.
86
+ All commands resolve workspace in this order:
87
+ 1. Saved default (epismo workspace use)
88
+ 2. Personal space (fallback)
89
+ When EPISMO_TOKEN is set, the saved default is skipped — the workspace is
90
+ resolved from the token itself. Use epismo token create --workspace-id to
91
+ issue a workspace-scoped token for CI/CD.
59
92
 
60
93
  Examples:
61
94
  epismo login
@@ -108,16 +141,14 @@ Examples:
108
141
  program
109
142
  .command("whoami")
110
143
  .description("show the currently authenticated user")
111
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
112
- .action(async (options) => {
113
- const context = await resolveContext({ workspaceId: options.workspaceId });
144
+ .action(async () => {
145
+ const context = await resolveContext();
114
146
  printJson(await whoami({ workspaceId: context.workspaceId, auth: context.auth }));
115
147
  });
116
148
  program.commands.at(-1)?.addHelpText("after", `
117
149
  Examples:
118
150
  epismo whoami
119
- EPISMO_TOKEN=<access-token> epismo whoami
120
- epismo whoami --workspace-id <workspace-id>`);
151
+ EPISMO_TOKEN=<access-token> epismo whoami`);
121
152
  const workspace = program
122
153
  .command("workspace")
123
154
  .description("manage workspaces and the saved default workspace");
@@ -159,33 +190,63 @@ Example:
159
190
  Example:
160
191
  epismo workspace clear`);
161
192
  workspace
162
- .command("upsert")
163
- .description("create a new workspace or update one you can access")
193
+ .command("create")
194
+ .description("create a new workspace")
164
195
  .option("--input <input>", "JSON object, @file, or - for stdin")
165
- .option("--id <id>", "existing workspace id")
166
196
  .option("--name <name>", "workspace name")
167
197
  .addOption(new Option("--plan <plan>", "basic | pro").choices(["basic", "pro"]))
168
198
  .action(async (options) => {
169
- const payload = await resolveInput({ input: options.input }, { id: options.id, name: options.name, plan: options.plan });
170
- const context = await resolveContext({});
171
- printJson(await upsertWorkspace(context, payload));
199
+ const payload = await resolveInput({ input: options.input }, { name: options.name, plan: options.plan });
200
+ const context = await resolveContext();
201
+ const created = await createWorkspace(context, payload);
202
+ const workspaceId = created.workspace?.id;
203
+ if (workspaceId) {
204
+ const checkout = await getWorkspaceCheckout(context, workspaceId);
205
+ printJson({ ...created, ...checkout });
206
+ }
207
+ else {
208
+ printJson(created);
209
+ }
210
+ });
211
+ workspace.commands.at(-1)?.addHelpText("after", `
212
+ Examples:
213
+ epismo workspace create --name my-team --plan basic
214
+ epismo workspace create --input @workspace.json`);
215
+ workspace
216
+ .command("checkout")
217
+ .description("get the billing URL for a workspace (to complete or retry payment)")
218
+ .requiredOption("--id <id>", "workspace id")
219
+ .action(async (options) => {
220
+ const context = await resolveContext();
221
+ printJson(await getWorkspaceCheckout(context, options.id));
222
+ });
223
+ workspace.commands.at(-1)?.addHelpText("after", `
224
+ Examples:
225
+ epismo workspace checkout --id <workspace-id>`);
226
+ workspace
227
+ .command("update")
228
+ .description("update a workspace you own (PATCH — omitted fields are unchanged)")
229
+ .option("--input <input>", "JSON object, @file, or - for stdin")
230
+ .requiredOption("--id <id>", "workspace id to update")
231
+ .option("--name <name>", "updated workspace name")
232
+ .addOption(new Option("--plan <plan>", "basic | pro").choices(["basic", "pro"]))
233
+ .action(async (options) => {
234
+ const payload = await resolveInput({ input: options.input }, { name: options.name, plan: options.plan });
235
+ const context = await resolveContext();
236
+ printJson(await updateWorkspace(context, options.id, payload));
172
237
  });
173
238
  const workspaceMember = workspace.command("member").description("manage workspace members");
174
239
  workspaceMember
175
240
  .command("list")
176
241
  .description("list members in a workspace")
177
- .option("--workspace-id <workspaceId>", "workspace id")
178
- .action(async (options) => {
179
- const context = await resolveContext({ workspaceId: options.workspaceId });
180
- printJson(await listWorkspaceMembers(context, {
181
- workspaceId: options.workspaceId ?? context.workspaceId
182
- }));
242
+ .action(async () => {
243
+ const context = await resolveContext();
244
+ printJson(await listWorkspaceMembers(context, { workspaceId: context.workspaceId }));
183
245
  });
184
246
  workspaceMember
185
247
  .command("upsert")
186
248
  .description("add a workspace member or update the member role")
187
249
  .option("--input <input>", "JSON object, @file, or - for stdin")
188
- .option("--workspace-id <workspaceId>", "workspace id")
189
250
  .option("--user-id <userId>", "user id")
190
251
  .addOption(new Option("--role <role>", "owner | admin | member").choices([
191
252
  "owner",
@@ -193,65 +254,74 @@ Example:
193
254
  "member"
194
255
  ]))
195
256
  .action(async (options) => {
257
+ const context = await resolveContext();
196
258
  const payload = await resolveInput({ input: options.input }, {
197
- workspaceId: options.workspaceId,
259
+ workspaceId: context.workspaceId,
198
260
  userId: options.userId,
199
261
  role: options.role
200
262
  });
201
- const context = await resolveContext({ workspaceId: options.workspaceId });
202
263
  printJson(await upsertWorkspaceMember(context, payload));
203
264
  });
204
265
  workspaceMember
205
266
  .command("delete")
206
267
  .description("remove a workspace member")
207
268
  .option("--input <input>", "JSON object, @file, or - for stdin")
208
- .option("--workspace-id <workspaceId>", "workspace id")
209
269
  .option("--user-id <userId>", "user id")
210
270
  .action(async (options) => {
271
+ const context = await resolveContext();
211
272
  const payload = await resolveInput({ input: options.input }, {
212
- workspaceId: options.workspaceId,
273
+ workspaceId: context.workspaceId,
213
274
  userId: options.userId
214
275
  });
215
- const context = await resolveContext({ workspaceId: options.workspaceId });
216
276
  printJson(await deleteWorkspaceMember(context, payload));
217
277
  });
218
278
  const project = program.command("project").description("manage workspace projects");
219
279
  project
220
280
  .command("list")
221
281
  .description("list projects visible in the selected workspace")
222
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
223
- .action(async (options) => {
224
- const context = await resolveContext({ workspaceId: options.workspaceId });
282
+ .action(async () => {
283
+ const context = await resolveContext();
225
284
  printJson(await listProjects(context));
226
285
  });
227
286
  project
228
- .command("upsert")
229
- .description("create a project in the selected workspace or update one you can access")
287
+ .command("create")
288
+ .description("create a project in the selected workspace")
230
289
  .option("--input <input>", "JSON object, @file, or - for stdin")
231
- .option("--id <id>", "existing project id")
232
290
  .option("--name <name>", "project name")
233
291
  .option("--description <description>", "project description")
234
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
235
292
  .action(async (options) => {
236
293
  const payload = await resolveInput(options, {
237
- id: options.id,
238
294
  name: options.name,
239
295
  description: options.description
240
296
  });
241
- const context = await resolveContext({ workspaceId: options.workspaceId });
242
- printJson(await upsertProject(context, payload));
297
+ const context = await resolveContext();
298
+ printJson(await createProject(context, payload));
299
+ });
300
+ project
301
+ .command("update")
302
+ .description("update a project you can access (PATCH — omitted fields are unchanged)")
303
+ .option("--input <input>", "JSON object, @file, or - for stdin")
304
+ .requiredOption("--id <id>", "project id to update")
305
+ .option("--name <name>", "updated project name")
306
+ .option("--description <description>", "updated project description")
307
+ .action(async (options) => {
308
+ const payload = await resolveInput(options, {
309
+ name: options.name,
310
+ description: options.description
311
+ });
312
+ const context = await resolveContext();
313
+ printJson(await updateProject(context, options.id, payload));
243
314
  });
244
315
  project
245
316
  .command("delete")
246
317
  .description("delete a project you can access in the selected workspace")
247
318
  .option("--input <input>", "JSON object, @file, or - for stdin")
248
319
  .option("--id <id>", "project id")
249
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
250
320
  .action(async (options) => {
251
321
  const payload = await resolveInput(options, {
252
322
  id: options.id
253
323
  });
254
- const context = await resolveContext({ workspaceId: options.workspaceId });
324
+ const context = await resolveContext();
255
325
  printJson(await deleteProject(context, payload));
256
326
  });
257
327
  const projectMember = project.command("member").description("manage project members");
@@ -260,12 +330,11 @@ Example:
260
330
  .description("list members across one or more projects")
261
331
  .option("--input <input>", "JSON object, @file, or - for stdin")
262
332
  .option("--project-ids <projectIds>", "JSON array or comma-separated project ids")
263
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
264
333
  .action(async (options) => {
265
334
  const payload = await resolveInput(options, {
266
335
  projectIds: parseStringArrayInput(options.projectIds, "--project-ids")
267
336
  });
268
- const context = await resolveContext({ workspaceId: options.workspaceId });
337
+ const context = await resolveContext();
269
338
  printJson(await listProjectMembers(context, payload));
270
339
  });
271
340
  projectMember
@@ -274,13 +343,12 @@ Example:
274
343
  .option("--input <input>", "JSON object, @file, or - for stdin")
275
344
  .option("--project-id <projectId>", "project id")
276
345
  .option("--user-id <userId>", "user id")
277
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
278
346
  .action(async (options) => {
279
347
  const payload = await resolveInput(options, {
280
348
  projectId: options.projectId,
281
349
  userId: options.userId
282
350
  });
283
- const context = await resolveContext({ workspaceId: options.workspaceId });
351
+ const context = await resolveContext();
284
352
  printJson(await addProjectMember(context, payload));
285
353
  });
286
354
  projectMember
@@ -289,60 +357,88 @@ Example:
289
357
  .option("--input <input>", "JSON object, @file, or - for stdin")
290
358
  .option("--project-id <projectId>", "project id")
291
359
  .option("--user-id <userId>", "user id")
292
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
293
360
  .action(async (options) => {
294
361
  const payload = await resolveInput(options, {
295
362
  projectId: options.projectId,
296
363
  userId: options.userId
297
364
  });
298
- const context = await resolveContext({ workspaceId: options.workspaceId });
365
+ const context = await resolveContext();
299
366
  printJson(await deleteProjectMember(context, payload));
300
367
  });
301
368
  const track = program.command("track").description("manage project tracks (tasks and goals)");
302
369
  track
303
- .command("upsert")
304
- .description("create or update a project track (task or goal)")
370
+ .command("create")
371
+ .description("create a new project track (task or goal)")
305
372
  .option("--input <input>", "JSON object, @file, or - for stdin")
306
373
  .addOption(new Option("--type <type>", "task | goal").choices(TRACK_TYPES))
307
- .option("--id <id>", "existing item id")
308
374
  .option("--title <title>", "title")
309
375
  .option("--content <content>", "markdown content")
310
- .option("--projects <projects>", "JSON array or comma-separated project ids")
376
+ .option("--project-ids <projectIds>", "JSON array or comma-separated project ids")
377
+ .option("--user-ids <userIds>", "JSON array or comma-separated user ids")
378
+ .option("--emails <emails>", "JSON array or comma-separated email addresses")
311
379
  .option("--task <task>", 'task-specific fields as JSON object e.g. \'{"status":"todo","dueDate":"2025-12-31"}\'')
312
380
  .option("--goal <goal>", 'goal-specific fields as JSON object e.g. \'{"status":"on_track","dueDate":"2025-12-31"}\'')
313
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
314
- .action(async (options) => {
381
+ .action(async (options, command) => {
315
382
  const payload = await resolveInput(options, {
316
383
  type: options.type,
317
- id: options.id,
318
384
  title: options.title,
319
385
  content: options.content,
320
- projects: parseStringArrayInput(options.projects, "--projects"),
386
+ targets: buildSharingTargets(command, options),
321
387
  task: parseJsonObjectOption(options.task, "--task"),
322
388
  goal: parseJsonObjectOption(options.goal, "--goal")
323
389
  });
324
- const context = await resolveContext({ workspaceId: options.workspaceId });
325
- printJson(await upsertTrack(context, payload));
390
+ const context = await resolveContext();
391
+ printJson(await createTrack(context, payload));
326
392
  });
327
393
  track.commands.at(-1)?.addHelpText("after", `
328
394
  Notes:
329
395
  Use --task or --goal to pass type-specific fields.
330
396
 
331
397
  Examples:
332
- epismo track upsert --type task --title "Fix bug" --content "Details..."
333
- epismo track upsert --input @task.json
334
- epismo track upsert --type goal --title "Launch" --goal '{"status":"on_track"}'`);
398
+ epismo track create --type task --title "Fix bug" --content "Details..."
399
+ epismo track create --input @task.json
400
+ epismo track create --type goal --title "Launch" --goal '{"status":"on_track"}'`);
401
+ track
402
+ .command("update")
403
+ .description("update an existing project track (PATCH — omitted fields are unchanged)")
404
+ .option("--input <input>", "JSON object, @file, or - for stdin")
405
+ .requiredOption("--id <id>", "track id to update")
406
+ .option("--title <title>", "updated title")
407
+ .option("--content <content>", "updated markdown content")
408
+ .option("--project-ids <projectIds>", "JSON array or comma-separated project ids")
409
+ .option("--user-ids <userIds>", "JSON array or comma-separated user ids")
410
+ .option("--emails <emails>", "JSON array or comma-separated email addresses")
411
+ .option("--task <task>", 'task-specific fields as partial JSON object e.g. \'{"status":"done"}\'')
412
+ .option("--goal <goal>", 'goal-specific fields as partial JSON object e.g. \'{"progress":50}\'')
413
+ .action(async (options, command) => {
414
+ const payload = await resolveInput(options, {
415
+ title: options.title,
416
+ content: options.content,
417
+ targets: buildSharingTargets(command, options),
418
+ task: parseJsonObjectOption(options.task, "--task"),
419
+ goal: parseJsonObjectOption(options.goal, "--goal")
420
+ });
421
+ const context = await resolveContext();
422
+ printJson(await updateTrack(context, options.id, payload));
423
+ });
424
+ track.commands.at(-1)?.addHelpText("after", `
425
+ Notes:
426
+ Omitted fields keep their existing value.
427
+
428
+ Examples:
429
+ epismo track update --id <id> --title "Updated title"
430
+ epismo track update --id <id> --task '{"status":"done"}'
431
+ epismo track update --id <id> --goal '{"progress":75}'`);
335
432
  track
336
433
  .command("get")
337
434
  .description("fetch one project track")
338
435
  .option("--input <input>", "JSON object, @file, or - for stdin")
339
436
  .option("--id <id>", "item id")
340
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
341
437
  .action(async (options) => {
342
438
  const payload = await resolveInput(options, {
343
439
  id: options.id
344
440
  });
345
- const context = await resolveContext({ workspaceId: options.workspaceId });
441
+ const context = await resolveContext();
346
442
  printJson(await getTrack(context, payload));
347
443
  });
348
444
  track.commands.at(-1)?.addHelpText("after", `
@@ -355,18 +451,18 @@ Example:
355
451
  .addOption(new Option("--type <type>", "task | goal").choices(TRACK_TYPES))
356
452
  .option("--query <query>", "free-text query")
357
453
  .option("--page <page>", "1-based page number")
358
- .option("--projects <projects>", "JSON array or comma-separated project ids")
454
+ .option("--project-ids <projectIds>", "JSON array or comma-separated project ids")
455
+ .option("--self <self>", "whether to include items that target the current user directly")
359
456
  .option("--filter <filter>", 'filter as JSON object; each value is an array of accepted values e.g. \'{"status":["todo","in_progress"]}\' (use --input @file.json for complex filters)')
360
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
361
457
  .action(async (options, command) => {
362
458
  const payload = await resolveInput(options, {
363
459
  type: getExplicitOptionOverride(command, "type", options.type),
364
460
  query: getExplicitOptionOverride(command, "query", options.query),
365
461
  page: getExplicitOptionOverride(command, "page", parseOptionalPositiveInteger(options.page, "--page")),
366
- projects: getExplicitOptionOverride(command, "projects", parseStringArrayInput(options.projects, "--projects")),
462
+ targets: buildSearchTargets(command, options),
367
463
  filter: getExplicitOptionOverride(command, "filter", parseJsonObjectOption(options.filter, "--filter"))
368
464
  });
369
- const context = await resolveContext({ workspaceId: options.workspaceId });
465
+ const context = await resolveContext();
370
466
  printJson(await searchTracks(context, payload));
371
467
  });
372
468
  track.commands.at(-1)?.addHelpText("after", `
@@ -379,52 +475,125 @@ Examples:
379
475
  .description("delete one project track")
380
476
  .option("--input <input>", "JSON object, @file, or - for stdin")
381
477
  .option("--id <id>", "item id")
382
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
383
478
  .action(async (options) => {
384
479
  const payload = await resolveInput(options, {
385
480
  id: options.id
386
481
  });
387
- const context = await resolveContext({ workspaceId: options.workspaceId });
482
+ const context = await resolveContext();
388
483
  printJson(await deleteTrack(context, payload));
389
484
  });
390
485
  track.commands.at(-1)?.addHelpText("after", `
391
486
  Example:
392
487
  epismo track delete --id <id>`);
488
+ track
489
+ .command("apply")
490
+ .description("create, update, and delete multiple tracks in a single request")
491
+ .option("--input <input>", "JSON object, @file, or - for stdin")
492
+ .option("--project-ids <projectIds>", "JSON array or comma-separated project ids")
493
+ .option("--user-ids <userIds>", "JSON array or comma-separated user ids")
494
+ .option("--emails <emails>", "JSON array or comma-separated email addresses")
495
+ .action(async (options, command) => {
496
+ const payload = await resolveInput(options, {
497
+ targets: buildSharingTargets(command, options)
498
+ });
499
+ const context = await resolveContext();
500
+ printJson(await applyTracks(context, payload));
501
+ });
502
+ track.commands.at(-1)?.addHelpText("after", `
503
+ Notes:
504
+ updateDrafts: use a non-UUID string as id (e.g. "t001") to create a new track, or a UUID to update an existing one.
505
+ Labels in task.parentId, task.dependsOn, and task.goalId are resolved by the server, so you can wire
506
+ up dependencies between new entries in the same request.
507
+ deleteDrafts: provide UUIDs of tracks to delete.
508
+
509
+ Examples:
510
+ epismo track apply --input @batch.json
511
+ epismo track apply --input '{"updateDrafts":[{"id":"t001","title":"Task A","task":{"status":"todo"}},{"id":"t002","title":"Task B","task":{"status":"todo","dependsOn":["t001"]}}]}'`);
393
512
  const pack = program.command("pack").description("manage agent packs (workflows and contexts)");
394
- pack.command("upsert")
395
- .description("create or update an agent pack")
513
+ pack.command("create")
514
+ .description("create a new agent pack")
396
515
  .option("--input <input>", "JSON object, @file, or - for stdin")
397
- .addOption(new Option("--type <type>", "workflow | context").choices(PACK_TYPES))
398
- .option("--id <id>", "existing pack id")
516
+ .addOption(new Option("--type <type>", "workflow | context (required unless provided in --input)")
517
+ .choices(PACK_TYPES))
399
518
  .option("--title <title>", "title")
400
519
  .option("--content <content>", "markdown content")
401
520
  .option("--category <category>", "pack category")
402
521
  .addOption(new Option("--visibility <visibility>", "public | private").choices(PACK_VISIBILITIES))
403
- .option("--projects <projects>", "JSON array or comma-separated project ids")
522
+ .option("--project-ids <projectIds>", "JSON array or comma-separated project ids")
523
+ .option("--user-ids <userIds>", "JSON array or comma-separated user ids")
524
+ .option("--emails <emails>", "JSON array or comma-separated email addresses")
404
525
  .option("--steps <steps>", "workflow steps as JSON array (use --input @file.json for complex payloads)")
405
526
  .option("--blocks <blocks>", "context blocks as JSON array (for type=context; use --input @file.json for complex payloads)")
406
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
407
- .action(async (options) => {
527
+ .action(async (options, command) => {
408
528
  const payload = await resolveInput(options, {
409
529
  type: options.type,
410
- id: options.id,
411
530
  title: options.title,
412
531
  content: options.content,
413
532
  category: options.category,
414
533
  visibility: options.visibility,
415
- projects: parseStringArrayInput(options.projects, "--projects"),
534
+ targets: buildSharingTargets(command, options),
535
+ steps: parseJsonArrayOption(options.steps, "--steps"),
536
+ blocks: parseJsonArrayOption(options.blocks, "--blocks")
537
+ });
538
+ if (payload.type !== "workflow" && payload.type !== "context") {
539
+ throw new CliError({
540
+ code: "INVALID_INPUT",
541
+ message: "Pack type is required.",
542
+ hint: 'Pass --type workflow|context or include {"type":"workflow"} / {"type":"context"} in --input.'
543
+ });
544
+ }
545
+ const context = await resolveContext();
546
+ printJson(await createPack(context, payload));
547
+ });
548
+ pack.commands.at(-1)?.addHelpText("after", `
549
+ Notes:
550
+ type is required for pack create. Pass --type or include it in --input.
551
+
552
+ Examples:
553
+ epismo pack create --type workflow --title "My workflow" --input @workflow.json
554
+ epismo pack create --type workflow --title "My workflow" --steps '[{"title":"Step 1","content":"..."}]'
555
+ epismo pack create --type context --title "My context" --input @context.json
556
+ epismo pack create --type context --title "My context" --blocks '[{"title":"Block 1","content":"..."}]'`);
557
+ pack.command("update")
558
+ .description("update an existing agent pack (PATCH semantics — omitted fields are unchanged)")
559
+ .requiredOption("--id <id>", "pack id to update")
560
+ .option("--input <input>", "JSON object, @file, or - for stdin")
561
+ .option("--title <title>", "updated title")
562
+ .option("--content <content>", "updated markdown content")
563
+ .option("--category <category>", "updated pack category")
564
+ .addOption(new Option("--visibility <visibility>", "public | private").choices(PACK_VISIBILITIES))
565
+ .option("--project-ids <projectIds>", "JSON array or comma-separated project ids")
566
+ .option("--user-ids <userIds>", "JSON array or comma-separated user ids")
567
+ .option("--emails <emails>", "JSON array or comma-separated email addresses")
568
+ .option("--steps <steps>", 'step operations as JSON array with op field: [{"op":"add","title":"..."},{"op":"update","id":"s001","content":"..."},{"op":"remove","id":"s002"}]')
569
+ .option("--blocks <blocks>", 'block operations as JSON array with op field: [{"op":"add","title":"..."},{"op":"update","id":"b001","content":"..."},{"op":"remove","id":"b002"}]')
570
+ .action(async (options, command) => {
571
+ const payload = await resolveInput(options, {
572
+ title: options.title,
573
+ content: options.content,
574
+ category: options.category,
575
+ visibility: options.visibility,
576
+ targets: buildSharingTargets(command, options),
416
577
  steps: parseJsonArrayOption(options.steps, "--steps"),
417
578
  blocks: parseJsonArrayOption(options.blocks, "--blocks")
418
579
  });
419
- const context = await resolveContext({ workspaceId: options.workspaceId });
420
- printJson(await upsertPack(context, payload));
580
+ const context = await resolveContext();
581
+ printJson(await updatePack(context, options.id, payload));
421
582
  });
422
583
  pack.commands.at(-1)?.addHelpText("after", `
584
+ Notes:
585
+ Omitted fields keep their existing value.
586
+ steps/blocks use operation objects with an "op" field (add | update | remove).
587
+ Omitting steps/blocks keeps them unchanged. Passing an empty array [] is a no-op.
588
+ To remove all items, send a remove op for each existing item ID.
589
+
423
590
  Examples:
424
- epismo pack upsert --type workflow --title "My workflow" --input @workflow.json
425
- epismo pack upsert --type workflow --title "My workflow" --steps '[{"title":"Step 1","content":"..."}]'
426
- epismo pack upsert --type context --title "My context" --input @context.json
427
- epismo pack upsert --type context --title "My context" --blocks '[{"title":"Block 1","content":"..."}]'`);
591
+ epismo pack update --id <id> --category ""
592
+ epismo pack update --id <id> --visibility public --category productivity
593
+ epismo pack update --id <id> --blocks '[{"op":"add","title":"New Block","content":"..."}]'
594
+ epismo pack update --id <id> --blocks '[{"op":"update","id":"b001","content":"updated..."}]'
595
+ epismo pack update --id <id> --blocks '[{"op":"remove","id":"b002"}]'
596
+ epismo pack update --id <id> --input @changes.json`);
428
597
  pack.command("get")
429
598
  .description("fetch one agent pack")
430
599
  .option("--input <input>", "JSON object, @file, or - for stdin")
@@ -433,14 +602,13 @@ Examples:
433
602
  .option("--full", "include nested item content")
434
603
  .option("--block-id <blockId>", "context block id to extract (type=context only)")
435
604
  .option("--step-id <stepId>", "workflow step id to extract (type=workflow only)")
436
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
437
605
  .action(async (options) => {
438
606
  const payload = await resolveInput(options, {
439
607
  id: options.id,
440
608
  alias: options.alias,
441
609
  full: options.full ? true : undefined
442
610
  });
443
- const context = await resolveContext({ workspaceId: options.workspaceId });
611
+ const context = await resolveContext();
444
612
  printJson(await getPack(context, payload, {
445
613
  blockId: options.blockId,
446
614
  stepId: options.stepId
@@ -460,18 +628,18 @@ Examples:
460
628
  .addOption(new Option("--type <type>", "workflow | context").choices(PACK_TYPES))
461
629
  .option("--query <query>", "free-text query")
462
630
  .option("--page <page>", "1-based page number")
463
- .option("--projects <projects>", "JSON array or comma-separated project ids")
631
+ .option("--project-ids <projectIds>", "JSON array or comma-separated project ids")
632
+ .option("--self <self>", "whether to include packs that target the current user directly")
464
633
  .option("--filter <filter>", 'filter as JSON object; each value is an array of accepted values e.g. \'{"visibility":["public"]}\' (use --input @file.json for complex filters)')
465
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
466
634
  .action(async (options, command) => {
467
635
  const payload = await resolveInput(options, {
468
636
  type: getExplicitOptionOverride(command, "type", options.type),
469
637
  query: getExplicitOptionOverride(command, "query", options.query),
470
638
  page: getExplicitOptionOverride(command, "page", parseOptionalPositiveInteger(options.page, "--page")),
471
- projects: getExplicitOptionOverride(command, "projects", parseStringArrayInput(options.projects, "--projects")),
639
+ targets: buildSearchTargets(command, options),
472
640
  filter: getExplicitOptionOverride(command, "filter", parseJsonObjectOption(options.filter, "--filter"))
473
641
  });
474
- const context = await resolveContext({ workspaceId: options.workspaceId });
642
+ const context = await resolveContext();
475
643
  printJson(await searchPacks(context, payload));
476
644
  });
477
645
  pack.commands.at(-1)?.addHelpText("after", `
@@ -485,7 +653,6 @@ Examples:
485
653
  .option("--id <id>", "pack id")
486
654
  .option("--liked", "mark as liked")
487
655
  .option("--no-liked", "remove like")
488
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
489
656
  .action(async (options) => {
490
657
  const payload = await resolveInput(options, {
491
658
  id: options.id,
@@ -498,7 +665,7 @@ Examples:
498
665
  hint: "Use --liked to add a like, or --no-liked to remove one."
499
666
  });
500
667
  }
501
- const context = await resolveContext({ workspaceId: options.workspaceId });
668
+ const context = await resolveContext();
502
669
  printJson(await likePack(context, payload));
503
670
  });
504
671
  pack.commands.at(-1)?.addHelpText("after", `
@@ -509,12 +676,11 @@ Examples:
509
676
  .description("delete one agent pack and any of your aliases that point to it")
510
677
  .option("--input <input>", "JSON object, @file, or - for stdin")
511
678
  .option("--id <id>", "pack id")
512
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
513
679
  .action(async (options) => {
514
680
  const payload = await resolveInput(options, {
515
681
  id: options.id
516
682
  });
517
- const context = await resolveContext({ workspaceId: options.workspaceId });
683
+ const context = await resolveContext();
518
684
  printJson(await deletePack(context, payload));
519
685
  });
520
686
  pack.commands.at(-1)?.addHelpText("after", `
@@ -524,26 +690,23 @@ Examples:
524
690
  agent
525
691
  .command("list")
526
692
  .description("list AI teammates and whether they appear in the assignee roster")
527
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
528
- .action(async (options) => {
529
- const context = await resolveContext({ workspaceId: options.workspaceId });
693
+ .action(async () => {
694
+ const context = await resolveContext();
530
695
  printJson(await listAgents(context));
531
696
  });
532
697
  agent.commands.at(-1)?.addHelpText("after", `
533
698
  Examples:
534
- epismo agent list
535
- epismo agent list --workspace-id <workspace-id>`);
699
+ epismo agent list`);
536
700
  agent
537
701
  .command("add")
538
702
  .description("add AI teammates to the assignee roster")
539
703
  .option("--input <input>", "JSON object, @file, or - for stdin")
540
704
  .option("--agent-ids <agentIds>", "JSON array or comma-separated agent ids")
541
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
542
705
  .action(async (options) => {
543
706
  const payload = await resolveInput(options, {
544
707
  agentIds: parseStringArrayInput(options.agentIds, "--agent-ids")
545
708
  });
546
- const context = await resolveContext({ workspaceId: options.workspaceId });
709
+ const context = await resolveContext();
547
710
  printJson(await addAgents(context, payload));
548
711
  });
549
712
  agent.commands.at(-1)?.addHelpText("after", `
@@ -555,12 +718,11 @@ Examples:
555
718
  .description("remove AI teammates from the assignee roster")
556
719
  .option("--input <input>", "JSON object, @file, or - for stdin")
557
720
  .option("--agent-ids <agentIds>", "JSON array or comma-separated agent ids")
558
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
559
721
  .action(async (options) => {
560
722
  const payload = await resolveInput(options, {
561
723
  agentIds: parseStringArrayInput(options.agentIds, "--agent-ids")
562
724
  });
563
- const context = await resolveContext({ workspaceId: options.workspaceId });
725
+ const context = await resolveContext();
564
726
  printJson(await removeAgents(context, payload));
565
727
  });
566
728
  agent.commands.at(-1)?.addHelpText("after", `
@@ -571,26 +733,23 @@ Examples:
571
733
  credit
572
734
  .command("balance")
573
735
  .description("show current credit balance")
574
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
575
- .action(async (options) => {
576
- const context = await resolveContext({ workspaceId: options.workspaceId });
736
+ .action(async () => {
737
+ const context = await resolveContext();
577
738
  printJson(await creditBalance(context));
578
739
  });
579
740
  credit.commands.at(-1)?.addHelpText("after", `
580
741
  Examples:
581
- epismo credit balance
582
- epismo credit balance --workspace-id <workspace-id>`);
742
+ epismo credit balance`);
583
743
  credit
584
744
  .command("checkout")
585
745
  .description("start a credit purchase and get a checkout URL")
586
746
  .option("--input <input>", "JSON object, @file, or - for stdin")
587
747
  .option("--allocations <allocations>", 'JSON array of { userId, quantity } e.g. \'[{"userId":"u_1","quantity":10}]\'')
588
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
589
748
  .action(async (options) => {
590
749
  const payload = await resolveInput(options, {
591
750
  allocations: parseJsonArrayOption(options.allocations, "--allocations")
592
751
  });
593
- const context = await resolveContext({ workspaceId: options.workspaceId });
752
+ const context = await resolveContext();
594
753
  printJson(await creditCheckout(context, payload));
595
754
  });
596
755
  credit.commands.at(-1)?.addHelpText("after", `
@@ -604,14 +763,13 @@ Example:
604
763
  .addOption(new Option("--type <type>", "workflow | context").choices(PACK_TYPES))
605
764
  .option("--id <id>", "target pack id")
606
765
  .option("--alias <alias>", "alias name")
607
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
608
766
  .action(async (options) => {
609
767
  const payload = await resolveInput(options, {
610
768
  type: options.type,
611
769
  id: options.id,
612
770
  alias: options.alias
613
771
  });
614
- const context = await resolveContext({ workspaceId: options.workspaceId });
772
+ const context = await resolveContext();
615
773
  printJson(await upsertAlias(context, payload));
616
774
  });
617
775
  alias.commands.at(-1)?.addHelpText("after", `
@@ -623,12 +781,11 @@ Examples:
623
781
  .description("resolve one alias")
624
782
  .option("--input <input>", "JSON object, @file, or - for stdin")
625
783
  .option("--alias <alias>", "alias reference (`alias` or `@handle/alias`)")
626
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
627
784
  .action(async (options) => {
628
785
  const payload = await resolveInput(options, {
629
786
  alias: options.alias
630
787
  });
631
- const context = await resolveContext({ workspaceId: options.workspaceId });
788
+ const context = await resolveContext();
632
789
  printJson(await getAlias(context, payload));
633
790
  });
634
791
  alias.commands.at(-1)?.addHelpText("after", `
@@ -639,9 +796,8 @@ Examples:
639
796
  .command("list")
640
797
  .description("list your aliases")
641
798
  .addOption(new Option("--type <type>", "workflow | context").choices(PACK_TYPES))
642
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
643
799
  .action(async (options) => {
644
- const context = await resolveContext({ workspaceId: options.workspaceId });
800
+ const context = await resolveContext();
645
801
  printJson(await listAliases(context, { type: options.type }));
646
802
  });
647
803
  alias.commands.at(-1)?.addHelpText("after", `
@@ -653,17 +809,36 @@ Example:
653
809
  .description("delete one alias")
654
810
  .option("--input <input>", "JSON object, @file, or - for stdin")
655
811
  .option("--alias <alias>", "alias name")
656
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
657
812
  .action(async (options) => {
658
813
  const payload = await resolveInput(options, {
659
814
  alias: options.alias
660
815
  });
661
- const context = await resolveContext({ workspaceId: options.workspaceId });
816
+ const context = await resolveContext();
662
817
  printJson(await deleteAlias(context, payload));
663
818
  });
664
819
  alias.commands.at(-1)?.addHelpText("after", `
665
820
  Example:
666
821
  epismo alias delete --alias myproject`);
822
+ const token = program.command("token").description("manage CLI tokens for CI/CD");
823
+ token
824
+ .command("create")
825
+ .description("issue a workspace-scoped CLI token for use in CI/CD pipelines")
826
+ .option("--workspace-id <workspaceId>", "workspace to scope the token to (omit for personal space)")
827
+ .action(async (options) => {
828
+ const context = await resolveContext();
829
+ printJson(await createToken(context, options.workspaceId ?? context.workspaceId));
830
+ });
831
+ token.commands.at(-1)?.addHelpText("after", `
832
+ Notes:
833
+ The issued token has the same scopes as a normal CLI token (read, write, offline_access)
834
+ but carries the workspace ID embedded. Use it via the EPISMO_TOKEN environment variable
835
+ in CI/CD so no --workspace-id flag is needed at runtime.
836
+ If --workspace-id is omitted, the saved default workspace is used (epismo workspace use),
837
+ falling back to personal space when no default is set.
838
+
839
+ Examples:
840
+ epismo token create --workspace-id <workspace-id>
841
+ epismo token create # uses saved default workspace, or personal space if none`);
667
842
  return program;
668
843
  }
669
844
  //# sourceMappingURL=program.js.map