epismo 0.3.0 → 0.4.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,19 +3,21 @@ 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 { applyTracks, createTrack, deleteTrack, getTrack, searchTracks, updateTrack } from "./tracks.js";
7
- import { createPack, deletePack, getPack, likePack, searchPacks, updatePack } from "./packs.js";
6
+ import { deleteTrack, getTrack, searchTracks, upsertTrack } from "./tracks.js";
7
+ import { deletePack, getPack, likePack, searchPacks, upsertPack } 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 { 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";
12
+ import { deleteWorkspaceMember, listWorkspaceMembers, upsertWorkspace, upsertWorkspaceMember } from "./workspaces.js";
13
+ import { addProjectMember, deleteProject, deleteProjectMember, listProjectMembers, listProjects, upsertProject } from "./projects.js";
15
14
  const TRACK_TYPES = ["task", "goal"];
16
15
  const PACK_TYPES = ["workflow", "context"];
17
16
  const PACK_VISIBILITIES = ["public", "private"];
18
- // centralize warning codes so wording is consistent everywhere
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
19
21
  const WARNING_ENV_TOKEN_WORKSPACE_IGNORED = "EPISMO_TOKEN_WORKSPACE_IGNORED";
20
22
  async function resolveInput(options, overrides) {
21
23
  const base = await readJsonObjectInput(options.input);
@@ -28,8 +30,8 @@ function getExplicitOptionOverride(command, optionName, value) {
28
30
  }
29
31
  return value;
30
32
  }
31
- async function resolveContext() {
32
- const context = await resolveExecutionContext();
33
+ async function resolveContext(options) {
34
+ const context = await resolveExecutionContext(options);
33
35
  if (context.warning) {
34
36
  printWarning({
35
37
  warning: { code: WARNING_ENV_TOKEN_WORKSPACE_IGNORED, message: context.warning }
@@ -49,12 +51,11 @@ export function buildProgram(version) {
49
51
  .exitOverride();
50
52
  program.addHelpText("after", `
51
53
  Workspace resolution:
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.
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.
58
59
 
59
60
  Examples:
60
61
  epismo login
@@ -107,14 +108,16 @@ Examples:
107
108
  program
108
109
  .command("whoami")
109
110
  .description("show the currently authenticated user")
110
- .action(async () => {
111
- const context = await resolveContext();
111
+ .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
112
+ .action(async (options) => {
113
+ const context = await resolveContext({ workspaceId: options.workspaceId });
112
114
  printJson(await whoami({ workspaceId: context.workspaceId, auth: context.auth }));
113
115
  });
114
116
  program.commands.at(-1)?.addHelpText("after", `
115
117
  Examples:
116
118
  epismo whoami
117
- EPISMO_TOKEN=<access-token> epismo whoami`);
119
+ EPISMO_TOKEN=<access-token> epismo whoami
120
+ epismo whoami --workspace-id <workspace-id>`);
118
121
  const workspace = program
119
122
  .command("workspace")
120
123
  .description("manage workspaces and the saved default workspace");
@@ -156,63 +159,33 @@ Example:
156
159
  Example:
157
160
  epismo workspace clear`);
158
161
  workspace
159
- .command("create")
160
- .description("create a new workspace")
162
+ .command("upsert")
163
+ .description("create a new workspace or update one you can access")
161
164
  .option("--input <input>", "JSON object, @file, or - for stdin")
165
+ .option("--id <id>", "existing workspace id")
162
166
  .option("--name <name>", "workspace name")
163
167
  .addOption(new Option("--plan <plan>", "basic | pro").choices(["basic", "pro"]))
164
168
  .action(async (options) => {
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));
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));
203
172
  });
204
173
  const workspaceMember = workspace.command("member").description("manage workspace members");
205
174
  workspaceMember
206
175
  .command("list")
207
176
  .description("list members in a workspace")
208
- .action(async () => {
209
- const context = await resolveContext();
210
- printJson(await listWorkspaceMembers(context, { workspaceId: context.workspaceId }));
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
+ }));
211
183
  });
212
184
  workspaceMember
213
185
  .command("upsert")
214
186
  .description("add a workspace member or update the member role")
215
187
  .option("--input <input>", "JSON object, @file, or - for stdin")
188
+ .option("--workspace-id <workspaceId>", "workspace id")
216
189
  .option("--user-id <userId>", "user id")
217
190
  .addOption(new Option("--role <role>", "owner | admin | member").choices([
218
191
  "owner",
@@ -220,74 +193,65 @@ Examples:
220
193
  "member"
221
194
  ]))
222
195
  .action(async (options) => {
223
- const context = await resolveContext();
224
196
  const payload = await resolveInput({ input: options.input }, {
225
- workspaceId: context.workspaceId,
197
+ workspaceId: options.workspaceId,
226
198
  userId: options.userId,
227
199
  role: options.role
228
200
  });
201
+ const context = await resolveContext({ workspaceId: options.workspaceId });
229
202
  printJson(await upsertWorkspaceMember(context, payload));
230
203
  });
231
204
  workspaceMember
232
205
  .command("delete")
233
206
  .description("remove a workspace member")
234
207
  .option("--input <input>", "JSON object, @file, or - for stdin")
208
+ .option("--workspace-id <workspaceId>", "workspace id")
235
209
  .option("--user-id <userId>", "user id")
236
210
  .action(async (options) => {
237
- const context = await resolveContext();
238
211
  const payload = await resolveInput({ input: options.input }, {
239
- workspaceId: context.workspaceId,
212
+ workspaceId: options.workspaceId,
240
213
  userId: options.userId
241
214
  });
215
+ const context = await resolveContext({ workspaceId: options.workspaceId });
242
216
  printJson(await deleteWorkspaceMember(context, payload));
243
217
  });
244
218
  const project = program.command("project").description("manage workspace projects");
245
219
  project
246
220
  .command("list")
247
221
  .description("list projects visible in the selected workspace")
248
- .action(async () => {
249
- const context = await resolveContext();
222
+ .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
223
+ .action(async (options) => {
224
+ const context = await resolveContext({ workspaceId: options.workspaceId });
250
225
  printJson(await listProjects(context));
251
226
  });
252
227
  project
253
- .command("create")
254
- .description("create a project in the selected workspace")
228
+ .command("upsert")
229
+ .description("create a project in the selected workspace or update one you can access")
255
230
  .option("--input <input>", "JSON object, @file, or - for stdin")
231
+ .option("--id <id>", "existing project id")
256
232
  .option("--name <name>", "project name")
257
233
  .option("--description <description>", "project description")
234
+ .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
258
235
  .action(async (options) => {
259
236
  const payload = await resolveInput(options, {
237
+ id: options.id,
260
238
  name: options.name,
261
239
  description: options.description
262
240
  });
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));
241
+ const context = await resolveContext({ workspaceId: options.workspaceId });
242
+ printJson(await upsertProject(context, payload));
280
243
  });
281
244
  project
282
245
  .command("delete")
283
246
  .description("delete a project you can access in the selected workspace")
284
247
  .option("--input <input>", "JSON object, @file, or - for stdin")
285
248
  .option("--id <id>", "project id")
249
+ .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
286
250
  .action(async (options) => {
287
251
  const payload = await resolveInput(options, {
288
252
  id: options.id
289
253
  });
290
- const context = await resolveContext();
254
+ const context = await resolveContext({ workspaceId: options.workspaceId });
291
255
  printJson(await deleteProject(context, payload));
292
256
  });
293
257
  const projectMember = project.command("member").description("manage project members");
@@ -296,11 +260,12 @@ Examples:
296
260
  .description("list members across one or more projects")
297
261
  .option("--input <input>", "JSON object, @file, or - for stdin")
298
262
  .option("--project-ids <projectIds>", "JSON array or comma-separated project ids")
263
+ .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
299
264
  .action(async (options) => {
300
265
  const payload = await resolveInput(options, {
301
266
  projectIds: parseStringArrayInput(options.projectIds, "--project-ids")
302
267
  });
303
- const context = await resolveContext();
268
+ const context = await resolveContext({ workspaceId: options.workspaceId });
304
269
  printJson(await listProjectMembers(context, payload));
305
270
  });
306
271
  projectMember
@@ -309,12 +274,13 @@ Examples:
309
274
  .option("--input <input>", "JSON object, @file, or - for stdin")
310
275
  .option("--project-id <projectId>", "project id")
311
276
  .option("--user-id <userId>", "user id")
277
+ .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
312
278
  .action(async (options) => {
313
279
  const payload = await resolveInput(options, {
314
280
  projectId: options.projectId,
315
281
  userId: options.userId
316
282
  });
317
- const context = await resolveContext();
283
+ const context = await resolveContext({ workspaceId: options.workspaceId });
318
284
  printJson(await addProjectMember(context, payload));
319
285
  });
320
286
  projectMember
@@ -323,84 +289,60 @@ Examples:
323
289
  .option("--input <input>", "JSON object, @file, or - for stdin")
324
290
  .option("--project-id <projectId>", "project id")
325
291
  .option("--user-id <userId>", "user id")
292
+ .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
326
293
  .action(async (options) => {
327
294
  const payload = await resolveInput(options, {
328
295
  projectId: options.projectId,
329
296
  userId: options.userId
330
297
  });
331
- const context = await resolveContext();
298
+ const context = await resolveContext({ workspaceId: options.workspaceId });
332
299
  printJson(await deleteProjectMember(context, payload));
333
300
  });
334
301
  const track = program.command("track").description("manage project tracks (tasks and goals)");
335
302
  track
336
- .command("create")
337
- .description("create a new project track (task or goal)")
303
+ .command("upsert")
304
+ .description("create or update a project track (task or goal)")
338
305
  .option("--input <input>", "JSON object, @file, or - for stdin")
339
306
  .addOption(new Option("--type <type>", "task | goal").choices(TRACK_TYPES))
307
+ .option("--id <id>", "existing item id")
340
308
  .option("--title <title>", "title")
341
309
  .option("--content <content>", "markdown content")
342
310
  .option("--projects <projects>", "JSON array or comma-separated project ids")
343
311
  .option("--task <task>", 'task-specific fields as JSON object e.g. \'{"status":"todo","dueDate":"2025-12-31"}\'')
344
312
  .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)
345
314
  .action(async (options) => {
346
315
  const payload = await resolveInput(options, {
347
316
  type: options.type,
317
+ id: options.id,
348
318
  title: options.title,
349
319
  content: options.content,
350
320
  projects: parseStringArrayInput(options.projects, "--projects"),
351
321
  task: parseJsonObjectOption(options.task, "--task"),
352
322
  goal: parseJsonObjectOption(options.goal, "--goal")
353
323
  });
354
- const context = await resolveContext();
355
- printJson(await createTrack(context, payload));
324
+ const context = await resolveContext({ workspaceId: options.workspaceId });
325
+ printJson(await upsertTrack(context, payload));
356
326
  });
357
327
  track.commands.at(-1)?.addHelpText("after", `
358
328
  Notes:
359
329
  Use --task or --goal to pass type-specific fields.
360
330
 
361
331
  Examples:
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}'`);
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"}'`);
394
335
  track
395
336
  .command("get")
396
337
  .description("fetch one project track")
397
338
  .option("--input <input>", "JSON object, @file, or - for stdin")
398
339
  .option("--id <id>", "item id")
340
+ .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
399
341
  .action(async (options) => {
400
342
  const payload = await resolveInput(options, {
401
343
  id: options.id
402
344
  });
403
- const context = await resolveContext();
345
+ const context = await resolveContext({ workspaceId: options.workspaceId });
404
346
  printJson(await getTrack(context, payload));
405
347
  });
406
348
  track.commands.at(-1)?.addHelpText("after", `
@@ -415,6 +357,7 @@ Example:
415
357
  .option("--page <page>", "1-based page number")
416
358
  .option("--projects <projects>", "JSON array or comma-separated project ids")
417
359
  .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)
418
361
  .action(async (options, command) => {
419
362
  const payload = await resolveInput(options, {
420
363
  type: getExplicitOptionOverride(command, "type", options.type),
@@ -423,7 +366,7 @@ Example:
423
366
  projects: getExplicitOptionOverride(command, "projects", parseStringArrayInput(options.projects, "--projects")),
424
367
  filter: getExplicitOptionOverride(command, "filter", parseJsonObjectOption(options.filter, "--filter"))
425
368
  });
426
- const context = await resolveContext();
369
+ const context = await resolveContext({ workspaceId: options.workspaceId });
427
370
  printJson(await searchTracks(context, payload));
428
371
  });
429
372
  track.commands.at(-1)?.addHelpText("after", `
@@ -436,43 +379,23 @@ Examples:
436
379
  .description("delete one project track")
437
380
  .option("--input <input>", "JSON object, @file, or - for stdin")
438
381
  .option("--id <id>", "item id")
382
+ .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
439
383
  .action(async (options) => {
440
384
  const payload = await resolveInput(options, {
441
385
  id: options.id
442
386
  });
443
- const context = await resolveContext();
387
+ const context = await resolveContext({ workspaceId: options.workspaceId });
444
388
  printJson(await deleteTrack(context, payload));
445
389
  });
446
390
  track.commands.at(-1)?.addHelpText("after", `
447
391
  Example:
448
392
  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"]}}]}'`);
471
393
  const pack = program.command("pack").description("manage agent packs (workflows and contexts)");
472
- pack.command("create")
473
- .description("create a new agent pack")
394
+ pack.command("upsert")
395
+ .description("create or update an agent pack")
474
396
  .option("--input <input>", "JSON object, @file, or - for stdin")
475
397
  .addOption(new Option("--type <type>", "workflow | context").choices(PACK_TYPES))
398
+ .option("--id <id>", "existing pack id")
476
399
  .option("--title <title>", "title")
477
400
  .option("--content <content>", "markdown content")
478
401
  .option("--category <category>", "pack category")
@@ -480,9 +403,11 @@ Examples:
480
403
  .option("--projects <projects>", "JSON array or comma-separated project ids")
481
404
  .option("--steps <steps>", "workflow steps as JSON array (use --input @file.json for complex payloads)")
482
405
  .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)
483
407
  .action(async (options) => {
484
408
  const payload = await resolveInput(options, {
485
409
  type: options.type,
410
+ id: options.id,
486
411
  title: options.title,
487
412
  content: options.content,
488
413
  category: options.category,
@@ -491,53 +416,15 @@ Examples:
491
416
  steps: parseJsonArrayOption(options.steps, "--steps"),
492
417
  blocks: parseJsonArrayOption(options.blocks, "--blocks")
493
418
  });
494
- const context = await resolveContext();
495
- printJson(await createPack(context, payload));
496
- });
497
- pack.commands.at(-1)?.addHelpText("after", `
498
- Examples:
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));
419
+ const context = await resolveContext({ workspaceId: options.workspaceId });
420
+ printJson(await upsertPack(context, payload));
526
421
  });
527
422
  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
423
  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`);
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":"..."}]'`);
541
428
  pack.command("get")
542
429
  .description("fetch one agent pack")
543
430
  .option("--input <input>", "JSON object, @file, or - for stdin")
@@ -546,13 +433,14 @@ Examples:
546
433
  .option("--full", "include nested item content")
547
434
  .option("--block-id <blockId>", "context block id to extract (type=context only)")
548
435
  .option("--step-id <stepId>", "workflow step id to extract (type=workflow only)")
436
+ .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
549
437
  .action(async (options) => {
550
438
  const payload = await resolveInput(options, {
551
439
  id: options.id,
552
440
  alias: options.alias,
553
441
  full: options.full ? true : undefined
554
442
  });
555
- const context = await resolveContext();
443
+ const context = await resolveContext({ workspaceId: options.workspaceId });
556
444
  printJson(await getPack(context, payload, {
557
445
  blockId: options.blockId,
558
446
  stepId: options.stepId
@@ -574,6 +462,7 @@ Examples:
574
462
  .option("--page <page>", "1-based page number")
575
463
  .option("--projects <projects>", "JSON array or comma-separated project ids")
576
464
  .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)
577
466
  .action(async (options, command) => {
578
467
  const payload = await resolveInput(options, {
579
468
  type: getExplicitOptionOverride(command, "type", options.type),
@@ -582,7 +471,7 @@ Examples:
582
471
  projects: getExplicitOptionOverride(command, "projects", parseStringArrayInput(options.projects, "--projects")),
583
472
  filter: getExplicitOptionOverride(command, "filter", parseJsonObjectOption(options.filter, "--filter"))
584
473
  });
585
- const context = await resolveContext();
474
+ const context = await resolveContext({ workspaceId: options.workspaceId });
586
475
  printJson(await searchPacks(context, payload));
587
476
  });
588
477
  pack.commands.at(-1)?.addHelpText("after", `
@@ -596,6 +485,7 @@ Examples:
596
485
  .option("--id <id>", "pack id")
597
486
  .option("--liked", "mark as liked")
598
487
  .option("--no-liked", "remove like")
488
+ .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
599
489
  .action(async (options) => {
600
490
  const payload = await resolveInput(options, {
601
491
  id: options.id,
@@ -608,7 +498,7 @@ Examples:
608
498
  hint: "Use --liked to add a like, or --no-liked to remove one."
609
499
  });
610
500
  }
611
- const context = await resolveContext();
501
+ const context = await resolveContext({ workspaceId: options.workspaceId });
612
502
  printJson(await likePack(context, payload));
613
503
  });
614
504
  pack.commands.at(-1)?.addHelpText("after", `
@@ -619,11 +509,12 @@ Examples:
619
509
  .description("delete one agent pack and any of your aliases that point to it")
620
510
  .option("--input <input>", "JSON object, @file, or - for stdin")
621
511
  .option("--id <id>", "pack id")
512
+ .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
622
513
  .action(async (options) => {
623
514
  const payload = await resolveInput(options, {
624
515
  id: options.id
625
516
  });
626
- const context = await resolveContext();
517
+ const context = await resolveContext({ workspaceId: options.workspaceId });
627
518
  printJson(await deletePack(context, payload));
628
519
  });
629
520
  pack.commands.at(-1)?.addHelpText("after", `
@@ -633,23 +524,26 @@ Examples:
633
524
  agent
634
525
  .command("list")
635
526
  .description("list AI teammates and whether they appear in the assignee roster")
636
- .action(async () => {
637
- const context = await resolveContext();
527
+ .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
528
+ .action(async (options) => {
529
+ const context = await resolveContext({ workspaceId: options.workspaceId });
638
530
  printJson(await listAgents(context));
639
531
  });
640
532
  agent.commands.at(-1)?.addHelpText("after", `
641
533
  Examples:
642
- epismo agent list`);
534
+ epismo agent list
535
+ epismo agent list --workspace-id <workspace-id>`);
643
536
  agent
644
537
  .command("add")
645
538
  .description("add AI teammates to the assignee roster")
646
539
  .option("--input <input>", "JSON object, @file, or - for stdin")
647
540
  .option("--agent-ids <agentIds>", "JSON array or comma-separated agent ids")
541
+ .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
648
542
  .action(async (options) => {
649
543
  const payload = await resolveInput(options, {
650
544
  agentIds: parseStringArrayInput(options.agentIds, "--agent-ids")
651
545
  });
652
- const context = await resolveContext();
546
+ const context = await resolveContext({ workspaceId: options.workspaceId });
653
547
  printJson(await addAgents(context, payload));
654
548
  });
655
549
  agent.commands.at(-1)?.addHelpText("after", `
@@ -661,11 +555,12 @@ Examples:
661
555
  .description("remove AI teammates from the assignee roster")
662
556
  .option("--input <input>", "JSON object, @file, or - for stdin")
663
557
  .option("--agent-ids <agentIds>", "JSON array or comma-separated agent ids")
558
+ .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
664
559
  .action(async (options) => {
665
560
  const payload = await resolveInput(options, {
666
561
  agentIds: parseStringArrayInput(options.agentIds, "--agent-ids")
667
562
  });
668
- const context = await resolveContext();
563
+ const context = await resolveContext({ workspaceId: options.workspaceId });
669
564
  printJson(await removeAgents(context, payload));
670
565
  });
671
566
  agent.commands.at(-1)?.addHelpText("after", `
@@ -676,23 +571,26 @@ Examples:
676
571
  credit
677
572
  .command("balance")
678
573
  .description("show current credit balance")
679
- .action(async () => {
680
- const context = await resolveContext();
574
+ .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
575
+ .action(async (options) => {
576
+ const context = await resolveContext({ workspaceId: options.workspaceId });
681
577
  printJson(await creditBalance(context));
682
578
  });
683
579
  credit.commands.at(-1)?.addHelpText("after", `
684
580
  Examples:
685
- epismo credit balance`);
581
+ epismo credit balance
582
+ epismo credit balance --workspace-id <workspace-id>`);
686
583
  credit
687
584
  .command("checkout")
688
585
  .description("start a credit purchase and get a checkout URL")
689
586
  .option("--input <input>", "JSON object, @file, or - for stdin")
690
587
  .option("--allocations <allocations>", 'JSON array of { userId, quantity } e.g. \'[{"userId":"u_1","quantity":10}]\'')
588
+ .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
691
589
  .action(async (options) => {
692
590
  const payload = await resolveInput(options, {
693
591
  allocations: parseJsonArrayOption(options.allocations, "--allocations")
694
592
  });
695
- const context = await resolveContext();
593
+ const context = await resolveContext({ workspaceId: options.workspaceId });
696
594
  printJson(await creditCheckout(context, payload));
697
595
  });
698
596
  credit.commands.at(-1)?.addHelpText("after", `
@@ -706,13 +604,14 @@ Example:
706
604
  .addOption(new Option("--type <type>", "workflow | context").choices(PACK_TYPES))
707
605
  .option("--id <id>", "target pack id")
708
606
  .option("--alias <alias>", "alias name")
607
+ .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
709
608
  .action(async (options) => {
710
609
  const payload = await resolveInput(options, {
711
610
  type: options.type,
712
611
  id: options.id,
713
612
  alias: options.alias
714
613
  });
715
- const context = await resolveContext();
614
+ const context = await resolveContext({ workspaceId: options.workspaceId });
716
615
  printJson(await upsertAlias(context, payload));
717
616
  });
718
617
  alias.commands.at(-1)?.addHelpText("after", `
@@ -724,11 +623,12 @@ Examples:
724
623
  .description("resolve one alias")
725
624
  .option("--input <input>", "JSON object, @file, or - for stdin")
726
625
  .option("--alias <alias>", "alias reference (`alias` or `@handle/alias`)")
626
+ .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
727
627
  .action(async (options) => {
728
628
  const payload = await resolveInput(options, {
729
629
  alias: options.alias
730
630
  });
731
- const context = await resolveContext();
631
+ const context = await resolveContext({ workspaceId: options.workspaceId });
732
632
  printJson(await getAlias(context, payload));
733
633
  });
734
634
  alias.commands.at(-1)?.addHelpText("after", `
@@ -739,8 +639,9 @@ Examples:
739
639
  .command("list")
740
640
  .description("list your aliases")
741
641
  .addOption(new Option("--type <type>", "workflow | context").choices(PACK_TYPES))
642
+ .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
742
643
  .action(async (options) => {
743
- const context = await resolveContext();
644
+ const context = await resolveContext({ workspaceId: options.workspaceId });
744
645
  printJson(await listAliases(context, { type: options.type }));
745
646
  });
746
647
  alias.commands.at(-1)?.addHelpText("after", `
@@ -752,36 +653,17 @@ Example:
752
653
  .description("delete one alias")
753
654
  .option("--input <input>", "JSON object, @file, or - for stdin")
754
655
  .option("--alias <alias>", "alias name")
656
+ .option(WORKSPACE_ID_OPT, WORKSPACE_ID_DESC)
755
657
  .action(async (options) => {
756
658
  const payload = await resolveInput(options, {
757
659
  alias: options.alias
758
660
  });
759
- const context = await resolveContext();
661
+ const context = await resolveContext({ workspaceId: options.workspaceId });
760
662
  printJson(await deleteAlias(context, payload));
761
663
  });
762
664
  alias.commands.at(-1)?.addHelpText("after", `
763
665
  Example:
764
666
  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`);
785
667
  return program;
786
668
  }
787
669
  //# sourceMappingURL=program.js.map