epismo 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,8 @@ 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
+ async function resolveContext() {
32
+ const context = await resolveExecutionContext();
35
33
  if (context.warning) {
36
34
  printWarning({
37
35
  warning: { code: WARNING_ENV_TOKEN_WORKSPACE_IGNORED, message: context.warning }
@@ -51,11 +49,12 @@ export function buildProgram(version) {
51
49
  .exitOverride();
52
50
  program.addHelpText("after", `
53
51
  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.
52
+ All commands resolve workspace in this order:
53
+ 1. Saved default (epismo workspace use)
54
+ 2. Personal space (fallback)
55
+ When EPISMO_TOKEN is set, the saved default is skipped — the workspace is
56
+ resolved from the token itself. Use epismo token create --workspace-id to
57
+ issue a workspace-scoped token for CI/CD.
59
58
 
60
59
  Examples:
61
60
  epismo login
@@ -108,16 +107,14 @@ Examples:
108
107
  program
109
108
  .command("whoami")
110
109
  .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 });
110
+ .action(async () => {
111
+ const context = await resolveContext();
114
112
  printJson(await whoami({ workspaceId: context.workspaceId, auth: context.auth }));
115
113
  });
116
114
  program.commands.at(-1)?.addHelpText("after", `
117
115
  Examples:
118
116
  epismo whoami
119
- EPISMO_TOKEN=<access-token> epismo whoami
120
- epismo whoami --workspace-id <workspace-id>`);
117
+ EPISMO_TOKEN=<access-token> epismo whoami`);
121
118
  const workspace = program
122
119
  .command("workspace")
123
120
  .description("manage workspaces and the saved default workspace");
@@ -159,33 +156,63 @@ Example:
159
156
  Example:
160
157
  epismo workspace clear`);
161
158
  workspace
162
- .command("upsert")
163
- .description("create a new workspace or update one you can access")
159
+ .command("create")
160
+ .description("create a new workspace")
164
161
  .option("--input <input>", "JSON object, @file, or - for stdin")
165
- .option("--id <id>", "existing workspace id")
166
162
  .option("--name <name>", "workspace name")
167
163
  .addOption(new Option("--plan <plan>", "basic | pro").choices(["basic", "pro"]))
168
164
  .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));
165
+ const payload = await resolveInput({ input: options.input }, { name: options.name, plan: options.plan });
166
+ const context = await resolveContext();
167
+ const created = await createWorkspace(context, payload);
168
+ const workspaceId = created.workspace?.id;
169
+ if (workspaceId) {
170
+ const checkout = await getWorkspaceCheckout(context, workspaceId);
171
+ printJson({ ...created, ...checkout });
172
+ }
173
+ else {
174
+ printJson(created);
175
+ }
176
+ });
177
+ workspace.commands.at(-1)?.addHelpText("after", `
178
+ Examples:
179
+ epismo workspace create --name my-team --plan basic
180
+ epismo workspace create --input @workspace.json`);
181
+ workspace
182
+ .command("checkout")
183
+ .description("get the billing URL for a workspace (to complete or retry payment)")
184
+ .requiredOption("--id <id>", "workspace id")
185
+ .action(async (options) => {
186
+ const context = await resolveContext();
187
+ printJson(await getWorkspaceCheckout(context, options.id));
188
+ });
189
+ workspace.commands.at(-1)?.addHelpText("after", `
190
+ Examples:
191
+ epismo workspace checkout --id <workspace-id>`);
192
+ workspace
193
+ .command("update")
194
+ .description("update a workspace you own (PATCH — omitted fields are unchanged)")
195
+ .option("--input <input>", "JSON object, @file, or - for stdin")
196
+ .requiredOption("--id <id>", "workspace id to update")
197
+ .option("--name <name>", "updated workspace name")
198
+ .addOption(new Option("--plan <plan>", "basic | pro").choices(["basic", "pro"]))
199
+ .action(async (options) => {
200
+ const payload = await resolveInput({ input: options.input }, { name: options.name, plan: options.plan });
201
+ const context = await resolveContext();
202
+ printJson(await updateWorkspace(context, options.id, payload));
172
203
  });
173
204
  const workspaceMember = workspace.command("member").description("manage workspace members");
174
205
  workspaceMember
175
206
  .command("list")
176
207
  .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
- }));
208
+ .action(async () => {
209
+ const context = await resolveContext();
210
+ printJson(await listWorkspaceMembers(context, { workspaceId: context.workspaceId }));
183
211
  });
184
212
  workspaceMember
185
213
  .command("upsert")
186
214
  .description("add a workspace member or update the member role")
187
215
  .option("--input <input>", "JSON object, @file, or - for stdin")
188
- .option("--workspace-id <workspaceId>", "workspace id")
189
216
  .option("--user-id <userId>", "user id")
190
217
  .addOption(new Option("--role <role>", "owner | admin | member").choices([
191
218
  "owner",
@@ -193,65 +220,74 @@ Example:
193
220
  "member"
194
221
  ]))
195
222
  .action(async (options) => {
223
+ const context = await resolveContext();
196
224
  const payload = await resolveInput({ input: options.input }, {
197
- workspaceId: options.workspaceId,
225
+ workspaceId: context.workspaceId,
198
226
  userId: options.userId,
199
227
  role: options.role
200
228
  });
201
- const context = await resolveContext({ workspaceId: options.workspaceId });
202
229
  printJson(await upsertWorkspaceMember(context, payload));
203
230
  });
204
231
  workspaceMember
205
232
  .command("delete")
206
233
  .description("remove a workspace member")
207
234
  .option("--input <input>", "JSON object, @file, or - for stdin")
208
- .option("--workspace-id <workspaceId>", "workspace id")
209
235
  .option("--user-id <userId>", "user id")
210
236
  .action(async (options) => {
237
+ const context = await resolveContext();
211
238
  const payload = await resolveInput({ input: options.input }, {
212
- workspaceId: options.workspaceId,
239
+ workspaceId: context.workspaceId,
213
240
  userId: options.userId
214
241
  });
215
- const context = await resolveContext({ workspaceId: options.workspaceId });
216
242
  printJson(await deleteWorkspaceMember(context, payload));
217
243
  });
218
244
  const project = program.command("project").description("manage workspace projects");
219
245
  project
220
246
  .command("list")
221
247
  .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 });
248
+ .action(async () => {
249
+ const context = await resolveContext();
225
250
  printJson(await listProjects(context));
226
251
  });
227
252
  project
228
- .command("upsert")
229
- .description("create a project in the selected workspace or update one you can access")
253
+ .command("create")
254
+ .description("create a project in the selected workspace")
230
255
  .option("--input <input>", "JSON object, @file, or - for stdin")
231
- .option("--id <id>", "existing project id")
232
256
  .option("--name <name>", "project name")
233
257
  .option("--description <description>", "project description")
234
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
235
258
  .action(async (options) => {
236
259
  const payload = await resolveInput(options, {
237
- id: options.id,
238
260
  name: options.name,
239
261
  description: options.description
240
262
  });
241
- const context = await resolveContext({ workspaceId: options.workspaceId });
242
- printJson(await upsertProject(context, payload));
263
+ const context = await resolveContext();
264
+ printJson(await createProject(context, payload));
265
+ });
266
+ project
267
+ .command("update")
268
+ .description("update a project you can access (PATCH — omitted fields are unchanged)")
269
+ .option("--input <input>", "JSON object, @file, or - for stdin")
270
+ .requiredOption("--id <id>", "project id to update")
271
+ .option("--name <name>", "updated project name")
272
+ .option("--description <description>", "updated project description")
273
+ .action(async (options) => {
274
+ const payload = await resolveInput(options, {
275
+ name: options.name,
276
+ description: options.description
277
+ });
278
+ const context = await resolveContext();
279
+ printJson(await updateProject(context, options.id, payload));
243
280
  });
244
281
  project
245
282
  .command("delete")
246
283
  .description("delete a project you can access in the selected workspace")
247
284
  .option("--input <input>", "JSON object, @file, or - for stdin")
248
285
  .option("--id <id>", "project id")
249
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
250
286
  .action(async (options) => {
251
287
  const payload = await resolveInput(options, {
252
288
  id: options.id
253
289
  });
254
- const context = await resolveContext({ workspaceId: options.workspaceId });
290
+ const context = await resolveContext();
255
291
  printJson(await deleteProject(context, payload));
256
292
  });
257
293
  const projectMember = project.command("member").description("manage project members");
@@ -260,12 +296,11 @@ Example:
260
296
  .description("list members across one or more projects")
261
297
  .option("--input <input>", "JSON object, @file, or - for stdin")
262
298
  .option("--project-ids <projectIds>", "JSON array or comma-separated project ids")
263
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
264
299
  .action(async (options) => {
265
300
  const payload = await resolveInput(options, {
266
301
  projectIds: parseStringArrayInput(options.projectIds, "--project-ids")
267
302
  });
268
- const context = await resolveContext({ workspaceId: options.workspaceId });
303
+ const context = await resolveContext();
269
304
  printJson(await listProjectMembers(context, payload));
270
305
  });
271
306
  projectMember
@@ -274,13 +309,12 @@ Example:
274
309
  .option("--input <input>", "JSON object, @file, or - for stdin")
275
310
  .option("--project-id <projectId>", "project id")
276
311
  .option("--user-id <userId>", "user id")
277
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
278
312
  .action(async (options) => {
279
313
  const payload = await resolveInput(options, {
280
314
  projectId: options.projectId,
281
315
  userId: options.userId
282
316
  });
283
- const context = await resolveContext({ workspaceId: options.workspaceId });
317
+ const context = await resolveContext();
284
318
  printJson(await addProjectMember(context, payload));
285
319
  });
286
320
  projectMember
@@ -289,60 +323,84 @@ Example:
289
323
  .option("--input <input>", "JSON object, @file, or - for stdin")
290
324
  .option("--project-id <projectId>", "project id")
291
325
  .option("--user-id <userId>", "user id")
292
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
293
326
  .action(async (options) => {
294
327
  const payload = await resolveInput(options, {
295
328
  projectId: options.projectId,
296
329
  userId: options.userId
297
330
  });
298
- const context = await resolveContext({ workspaceId: options.workspaceId });
331
+ const context = await resolveContext();
299
332
  printJson(await deleteProjectMember(context, payload));
300
333
  });
301
334
  const track = program.command("track").description("manage project tracks (tasks and goals)");
302
335
  track
303
- .command("upsert")
304
- .description("create or update a project track (task or goal)")
336
+ .command("create")
337
+ .description("create a new project track (task or goal)")
305
338
  .option("--input <input>", "JSON object, @file, or - for stdin")
306
339
  .addOption(new Option("--type <type>", "task | goal").choices(TRACK_TYPES))
307
- .option("--id <id>", "existing item id")
308
340
  .option("--title <title>", "title")
309
341
  .option("--content <content>", "markdown content")
310
342
  .option("--projects <projects>", "JSON array or comma-separated project ids")
311
343
  .option("--task <task>", 'task-specific fields as JSON object e.g. \'{"status":"todo","dueDate":"2025-12-31"}\'')
312
344
  .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
345
  .action(async (options) => {
315
346
  const payload = await resolveInput(options, {
316
347
  type: options.type,
317
- id: options.id,
318
348
  title: options.title,
319
349
  content: options.content,
320
350
  projects: parseStringArrayInput(options.projects, "--projects"),
321
351
  task: parseJsonObjectOption(options.task, "--task"),
322
352
  goal: parseJsonObjectOption(options.goal, "--goal")
323
353
  });
324
- const context = await resolveContext({ workspaceId: options.workspaceId });
325
- printJson(await upsertTrack(context, payload));
354
+ const context = await resolveContext();
355
+ printJson(await createTrack(context, payload));
326
356
  });
327
357
  track.commands.at(-1)?.addHelpText("after", `
328
358
  Notes:
329
359
  Use --task or --goal to pass type-specific fields.
330
360
 
331
361
  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"}'`);
362
+ epismo track create --type task --title "Fix bug" --content "Details..."
363
+ epismo track create --input @task.json
364
+ epismo track create --type goal --title "Launch" --goal '{"status":"on_track"}'`);
365
+ track
366
+ .command("update")
367
+ .description("update an existing project track (PATCH — omitted fields are unchanged)")
368
+ .option("--input <input>", "JSON object, @file, or - for stdin")
369
+ .requiredOption("--id <id>", "track id to update")
370
+ .option("--title <title>", "updated title")
371
+ .option("--content <content>", "updated markdown content")
372
+ .option("--projects <projects>", "JSON array or comma-separated project ids")
373
+ .option("--task <task>", 'task-specific fields as partial JSON object e.g. \'{"status":"done"}\'')
374
+ .option("--goal <goal>", 'goal-specific fields as partial JSON object e.g. \'{"progress":50}\'')
375
+ .action(async (options) => {
376
+ const payload = await resolveInput(options, {
377
+ title: options.title,
378
+ content: options.content,
379
+ projects: parseStringArrayInput(options.projects, "--projects"),
380
+ task: parseJsonObjectOption(options.task, "--task"),
381
+ goal: parseJsonObjectOption(options.goal, "--goal")
382
+ });
383
+ const context = await resolveContext();
384
+ printJson(await updateTrack(context, options.id, payload));
385
+ });
386
+ track.commands.at(-1)?.addHelpText("after", `
387
+ Notes:
388
+ Omitted fields keep their existing value.
389
+
390
+ Examples:
391
+ epismo track update --id <id> --title "Updated title"
392
+ epismo track update --id <id> --task '{"status":"done"}'
393
+ epismo track update --id <id> --goal '{"progress":75}'`);
335
394
  track
336
395
  .command("get")
337
396
  .description("fetch one project track")
338
397
  .option("--input <input>", "JSON object, @file, or - for stdin")
339
398
  .option("--id <id>", "item id")
340
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
341
399
  .action(async (options) => {
342
400
  const payload = await resolveInput(options, {
343
401
  id: options.id
344
402
  });
345
- const context = await resolveContext({ workspaceId: options.workspaceId });
403
+ const context = await resolveContext();
346
404
  printJson(await getTrack(context, payload));
347
405
  });
348
406
  track.commands.at(-1)?.addHelpText("after", `
@@ -357,7 +415,6 @@ Example:
357
415
  .option("--page <page>", "1-based page number")
358
416
  .option("--projects <projects>", "JSON array or comma-separated project ids")
359
417
  .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
418
  .action(async (options, command) => {
362
419
  const payload = await resolveInput(options, {
363
420
  type: getExplicitOptionOverride(command, "type", options.type),
@@ -366,7 +423,7 @@ Example:
366
423
  projects: getExplicitOptionOverride(command, "projects", parseStringArrayInput(options.projects, "--projects")),
367
424
  filter: getExplicitOptionOverride(command, "filter", parseJsonObjectOption(options.filter, "--filter"))
368
425
  });
369
- const context = await resolveContext({ workspaceId: options.workspaceId });
426
+ const context = await resolveContext();
370
427
  printJson(await searchTracks(context, payload));
371
428
  });
372
429
  track.commands.at(-1)?.addHelpText("after", `
@@ -379,23 +436,43 @@ Examples:
379
436
  .description("delete one project track")
380
437
  .option("--input <input>", "JSON object, @file, or - for stdin")
381
438
  .option("--id <id>", "item id")
382
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
383
439
  .action(async (options) => {
384
440
  const payload = await resolveInput(options, {
385
441
  id: options.id
386
442
  });
387
- const context = await resolveContext({ workspaceId: options.workspaceId });
443
+ const context = await resolveContext();
388
444
  printJson(await deleteTrack(context, payload));
389
445
  });
390
446
  track.commands.at(-1)?.addHelpText("after", `
391
447
  Example:
392
448
  epismo track delete --id <id>`);
449
+ track
450
+ .command("apply")
451
+ .description("create, update, and delete multiple tracks in a single request")
452
+ .option("--input <input>", "JSON object, @file, or - for stdin")
453
+ .option("--projects <projects>", "JSON array or comma-separated project ids")
454
+ .action(async (options) => {
455
+ const payload = await resolveInput(options, {
456
+ projects: parseStringArrayInput(options.projects, "--projects")
457
+ });
458
+ const context = await resolveContext();
459
+ printJson(await applyTracks(context, payload));
460
+ });
461
+ track.commands.at(-1)?.addHelpText("after", `
462
+ Notes:
463
+ updateDrafts: use a non-UUID string as id (e.g. "t001") to create a new track, or a UUID to update an existing one.
464
+ Labels in task.parentId, task.dependsOn, and task.goalId are resolved by the server, so you can wire
465
+ up dependencies between new entries in the same request.
466
+ deleteDrafts: provide UUIDs of tracks to delete.
467
+
468
+ Examples:
469
+ epismo track apply --input @batch.json
470
+ epismo track apply --input '{"updateDrafts":[{"id":"t001","title":"Task A","task":{"status":"todo"}},{"id":"t002","title":"Task B","task":{"status":"todo","dependsOn":["t001"]}}]}'`);
393
471
  const pack = program.command("pack").description("manage agent packs (workflows and contexts)");
394
- pack.command("upsert")
395
- .description("create or update an agent pack")
472
+ pack.command("create")
473
+ .description("create a new agent pack")
396
474
  .option("--input <input>", "JSON object, @file, or - for stdin")
397
475
  .addOption(new Option("--type <type>", "workflow | context").choices(PACK_TYPES))
398
- .option("--id <id>", "existing pack id")
399
476
  .option("--title <title>", "title")
400
477
  .option("--content <content>", "markdown content")
401
478
  .option("--category <category>", "pack category")
@@ -403,11 +480,9 @@ Example:
403
480
  .option("--projects <projects>", "JSON array or comma-separated project ids")
404
481
  .option("--steps <steps>", "workflow steps as JSON array (use --input @file.json for complex payloads)")
405
482
  .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
483
  .action(async (options) => {
408
484
  const payload = await resolveInput(options, {
409
485
  type: options.type,
410
- id: options.id,
411
486
  title: options.title,
412
487
  content: options.content,
413
488
  category: options.category,
@@ -416,15 +491,53 @@ Example:
416
491
  steps: parseJsonArrayOption(options.steps, "--steps"),
417
492
  blocks: parseJsonArrayOption(options.blocks, "--blocks")
418
493
  });
419
- const context = await resolveContext({ workspaceId: options.workspaceId });
420
- printJson(await upsertPack(context, payload));
494
+ const context = await resolveContext();
495
+ printJson(await createPack(context, payload));
421
496
  });
422
497
  pack.commands.at(-1)?.addHelpText("after", `
423
498
  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":"..."}]'`);
499
+ epismo pack create --type workflow --title "My workflow" --input @workflow.json
500
+ epismo pack create --type workflow --title "My workflow" --steps '[{"title":"Step 1","content":"..."}]'
501
+ epismo pack create --type context --title "My context" --input @context.json
502
+ epismo pack create --type context --title "My context" --blocks '[{"title":"Block 1","content":"..."}]'`);
503
+ pack.command("update")
504
+ .description("update an existing agent pack (PATCH semantics — omitted fields are unchanged)")
505
+ .requiredOption("--id <id>", "pack id to update")
506
+ .option("--input <input>", "JSON object, @file, or - for stdin")
507
+ .option("--title <title>", "updated title")
508
+ .option("--content <content>", "updated markdown content")
509
+ .option("--category <category>", "updated pack category")
510
+ .addOption(new Option("--visibility <visibility>", "public | private").choices(PACK_VISIBILITIES))
511
+ .option("--projects <projects>", "JSON array or comma-separated project ids")
512
+ .option("--steps <steps>", 'step operations as JSON array with op field: [{"op":"add","title":"..."},{"op":"update","id":"s001","content":"..."},{"op":"remove","id":"s002"}]')
513
+ .option("--blocks <blocks>", 'block operations as JSON array with op field: [{"op":"add","title":"..."},{"op":"update","id":"b001","content":"..."},{"op":"remove","id":"b002"}]')
514
+ .action(async (options) => {
515
+ const payload = await resolveInput(options, {
516
+ title: options.title,
517
+ content: options.content,
518
+ category: options.category,
519
+ visibility: options.visibility,
520
+ projects: parseStringArrayInput(options.projects, "--projects"),
521
+ steps: parseJsonArrayOption(options.steps, "--steps"),
522
+ blocks: parseJsonArrayOption(options.blocks, "--blocks")
523
+ });
524
+ const context = await resolveContext();
525
+ printJson(await updatePack(context, options.id, payload));
526
+ });
527
+ pack.commands.at(-1)?.addHelpText("after", `
528
+ Notes:
529
+ Omitted fields keep their existing value.
530
+ steps/blocks use operation objects with an "op" field (add | update | remove).
531
+ Omitting steps/blocks keeps them unchanged. Passing an empty array [] is a no-op.
532
+ To remove all items, send a remove op for each existing item ID.
533
+
534
+ Examples:
535
+ epismo pack update --id <id> --category ""
536
+ epismo pack update --id <id> --visibility public --category productivity
537
+ epismo pack update --id <id> --blocks '[{"op":"add","title":"New Block","content":"..."}]'
538
+ epismo pack update --id <id> --blocks '[{"op":"update","id":"b001","content":"updated..."}]'
539
+ epismo pack update --id <id> --blocks '[{"op":"remove","id":"b002"}]'
540
+ epismo pack update --id <id> --input @changes.json`);
428
541
  pack.command("get")
429
542
  .description("fetch one agent pack")
430
543
  .option("--input <input>", "JSON object, @file, or - for stdin")
@@ -433,14 +546,13 @@ Examples:
433
546
  .option("--full", "include nested item content")
434
547
  .option("--block-id <blockId>", "context block id to extract (type=context only)")
435
548
  .option("--step-id <stepId>", "workflow step id to extract (type=workflow only)")
436
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
437
549
  .action(async (options) => {
438
550
  const payload = await resolveInput(options, {
439
551
  id: options.id,
440
552
  alias: options.alias,
441
553
  full: options.full ? true : undefined
442
554
  });
443
- const context = await resolveContext({ workspaceId: options.workspaceId });
555
+ const context = await resolveContext();
444
556
  printJson(await getPack(context, payload, {
445
557
  blockId: options.blockId,
446
558
  stepId: options.stepId
@@ -462,7 +574,6 @@ Examples:
462
574
  .option("--page <page>", "1-based page number")
463
575
  .option("--projects <projects>", "JSON array or comma-separated project ids")
464
576
  .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
577
  .action(async (options, command) => {
467
578
  const payload = await resolveInput(options, {
468
579
  type: getExplicitOptionOverride(command, "type", options.type),
@@ -471,7 +582,7 @@ Examples:
471
582
  projects: getExplicitOptionOverride(command, "projects", parseStringArrayInput(options.projects, "--projects")),
472
583
  filter: getExplicitOptionOverride(command, "filter", parseJsonObjectOption(options.filter, "--filter"))
473
584
  });
474
- const context = await resolveContext({ workspaceId: options.workspaceId });
585
+ const context = await resolveContext();
475
586
  printJson(await searchPacks(context, payload));
476
587
  });
477
588
  pack.commands.at(-1)?.addHelpText("after", `
@@ -485,7 +596,6 @@ Examples:
485
596
  .option("--id <id>", "pack id")
486
597
  .option("--liked", "mark as liked")
487
598
  .option("--no-liked", "remove like")
488
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
489
599
  .action(async (options) => {
490
600
  const payload = await resolveInput(options, {
491
601
  id: options.id,
@@ -498,7 +608,7 @@ Examples:
498
608
  hint: "Use --liked to add a like, or --no-liked to remove one."
499
609
  });
500
610
  }
501
- const context = await resolveContext({ workspaceId: options.workspaceId });
611
+ const context = await resolveContext();
502
612
  printJson(await likePack(context, payload));
503
613
  });
504
614
  pack.commands.at(-1)?.addHelpText("after", `
@@ -509,41 +619,37 @@ Examples:
509
619
  .description("delete one agent pack and any of your aliases that point to it")
510
620
  .option("--input <input>", "JSON object, @file, or - for stdin")
511
621
  .option("--id <id>", "pack id")
512
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
513
622
  .action(async (options) => {
514
623
  const payload = await resolveInput(options, {
515
624
  id: options.id
516
625
  });
517
- const context = await resolveContext({ workspaceId: options.workspaceId });
626
+ const context = await resolveContext();
518
627
  printJson(await deletePack(context, payload));
519
628
  });
520
629
  pack.commands.at(-1)?.addHelpText("after", `
521
630
  Examples:
522
631
  epismo pack delete --id <id> # also removes your aliases for that pack`);
523
- const agent = program.command("agent").description("manage marketplace agent availability");
632
+ const agent = program.command("agent").description("manage agent availability");
524
633
  agent
525
634
  .command("list")
526
635
  .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 });
636
+ .action(async () => {
637
+ const context = await resolveContext();
530
638
  printJson(await listAgents(context));
531
639
  });
532
640
  agent.commands.at(-1)?.addHelpText("after", `
533
641
  Examples:
534
- epismo agent list
535
- epismo agent list --workspace-id <workspace-id>`);
642
+ epismo agent list`);
536
643
  agent
537
644
  .command("add")
538
645
  .description("add AI teammates to the assignee roster")
539
646
  .option("--input <input>", "JSON object, @file, or - for stdin")
540
647
  .option("--agent-ids <agentIds>", "JSON array or comma-separated agent ids")
541
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
542
648
  .action(async (options) => {
543
649
  const payload = await resolveInput(options, {
544
650
  agentIds: parseStringArrayInput(options.agentIds, "--agent-ids")
545
651
  });
546
- const context = await resolveContext({ workspaceId: options.workspaceId });
652
+ const context = await resolveContext();
547
653
  printJson(await addAgents(context, payload));
548
654
  });
549
655
  agent.commands.at(-1)?.addHelpText("after", `
@@ -555,12 +661,11 @@ Examples:
555
661
  .description("remove AI teammates from the assignee roster")
556
662
  .option("--input <input>", "JSON object, @file, or - for stdin")
557
663
  .option("--agent-ids <agentIds>", "JSON array or comma-separated agent ids")
558
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
559
664
  .action(async (options) => {
560
665
  const payload = await resolveInput(options, {
561
666
  agentIds: parseStringArrayInput(options.agentIds, "--agent-ids")
562
667
  });
563
- const context = await resolveContext({ workspaceId: options.workspaceId });
668
+ const context = await resolveContext();
564
669
  printJson(await removeAgents(context, payload));
565
670
  });
566
671
  agent.commands.at(-1)?.addHelpText("after", `
@@ -571,26 +676,23 @@ Examples:
571
676
  credit
572
677
  .command("balance")
573
678
  .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 });
679
+ .action(async () => {
680
+ const context = await resolveContext();
577
681
  printJson(await creditBalance(context));
578
682
  });
579
683
  credit.commands.at(-1)?.addHelpText("after", `
580
684
  Examples:
581
- epismo credit balance
582
- epismo credit balance --workspace-id <workspace-id>`);
685
+ epismo credit balance`);
583
686
  credit
584
687
  .command("checkout")
585
688
  .description("start a credit purchase and get a checkout URL")
586
689
  .option("--input <input>", "JSON object, @file, or - for stdin")
587
690
  .option("--allocations <allocations>", 'JSON array of { userId, quantity } e.g. \'[{"userId":"u_1","quantity":10}]\'')
588
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
589
691
  .action(async (options) => {
590
692
  const payload = await resolveInput(options, {
591
693
  allocations: parseJsonArrayOption(options.allocations, "--allocations")
592
694
  });
593
- const context = await resolveContext({ workspaceId: options.workspaceId });
695
+ const context = await resolveContext();
594
696
  printJson(await creditCheckout(context, payload));
595
697
  });
596
698
  credit.commands.at(-1)?.addHelpText("after", `
@@ -604,14 +706,13 @@ Example:
604
706
  .addOption(new Option("--type <type>", "workflow | context").choices(PACK_TYPES))
605
707
  .option("--id <id>", "target pack id")
606
708
  .option("--alias <alias>", "alias name")
607
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
608
709
  .action(async (options) => {
609
710
  const payload = await resolveInput(options, {
610
711
  type: options.type,
611
712
  id: options.id,
612
713
  alias: options.alias
613
714
  });
614
- const context = await resolveContext({ workspaceId: options.workspaceId });
715
+ const context = await resolveContext();
615
716
  printJson(await upsertAlias(context, payload));
616
717
  });
617
718
  alias.commands.at(-1)?.addHelpText("after", `
@@ -623,12 +724,11 @@ Examples:
623
724
  .description("resolve one alias")
624
725
  .option("--input <input>", "JSON object, @file, or - for stdin")
625
726
  .option("--alias <alias>", "alias reference (`alias` or `@handle/alias`)")
626
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
627
727
  .action(async (options) => {
628
728
  const payload = await resolveInput(options, {
629
729
  alias: options.alias
630
730
  });
631
- const context = await resolveContext({ workspaceId: options.workspaceId });
731
+ const context = await resolveContext();
632
732
  printJson(await getAlias(context, payload));
633
733
  });
634
734
  alias.commands.at(-1)?.addHelpText("after", `
@@ -639,9 +739,8 @@ Examples:
639
739
  .command("list")
640
740
  .description("list your aliases")
641
741
  .addOption(new Option("--type <type>", "workflow | context").choices(PACK_TYPES))
642
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
643
742
  .action(async (options) => {
644
- const context = await resolveContext({ workspaceId: options.workspaceId });
743
+ const context = await resolveContext();
645
744
  printJson(await listAliases(context, { type: options.type }));
646
745
  });
647
746
  alias.commands.at(-1)?.addHelpText("after", `
@@ -653,17 +752,36 @@ Example:
653
752
  .description("delete one alias")
654
753
  .option("--input <input>", "JSON object, @file, or - for stdin")
655
754
  .option("--alias <alias>", "alias name")
656
- .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
657
755
  .action(async (options) => {
658
756
  const payload = await resolveInput(options, {
659
757
  alias: options.alias
660
758
  });
661
- const context = await resolveContext({ workspaceId: options.workspaceId });
759
+ const context = await resolveContext();
662
760
  printJson(await deleteAlias(context, payload));
663
761
  });
664
762
  alias.commands.at(-1)?.addHelpText("after", `
665
763
  Example:
666
764
  epismo alias delete --alias myproject`);
765
+ const token = program.command("token").description("manage CLI tokens for CI/CD");
766
+ token
767
+ .command("create")
768
+ .description("issue a workspace-scoped CLI token for use in CI/CD pipelines")
769
+ .option("--workspace-id <workspaceId>", "workspace to scope the token to (omit for personal space)")
770
+ .action(async (options) => {
771
+ const context = await resolveContext();
772
+ printJson(await createToken(context, options.workspaceId ?? context.workspaceId));
773
+ });
774
+ token.commands.at(-1)?.addHelpText("after", `
775
+ Notes:
776
+ The issued token has the same scopes as a normal CLI token (read, write, offline_access)
777
+ but carries the workspace ID embedded. Use it via the EPISMO_TOKEN environment variable
778
+ in CI/CD so no --workspace-id flag is needed at runtime.
779
+ If --workspace-id is omitted, the saved default workspace is used (epismo workspace use),
780
+ falling back to personal space when no default is set.
781
+
782
+ Examples:
783
+ epismo token create --workspace-id <workspace-id>
784
+ epismo token create # uses saved default workspace, or personal space if none`);
667
785
  return program;
668
786
  }
669
787
  //# sourceMappingURL=program.js.map