epismo 0.20.0 → 1.0.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
@@ -1,161 +1,20 @@
1
- import { Argument, Command, Option } from "commander";
1
+ import { Command, Option } from "commander";
2
2
  import { clearWorkspace, getCurrentWorkspace, listAvailableWorkspaces, login, logout, useWorkspace, whoami } from "./auth.js";
3
3
  import { resolveExecutionContext } from "./context.js";
4
- import { mergeDefined, parseJsonArrayOption, parseJsonObjectOption, parseOptionalPositiveInteger, parseStringArrayInput, readJsonObjectInput } from "./input.js";
4
+ import { mergeDefined, parseStringArrayInput, readJsonObjectInput } from "./input.js";
5
5
  import { printJson, printWarning } from "./output.js";
6
- import { applyTracks, createTrack, deleteTrack, getTrack, reviewTracks, searchTracks, updateTrack } from "./tracks.js";
7
- import { createLogForTrack, deleteLog, listLogs } from "./logs.js";
8
- import { createPack, deletePack, getPack, likePack, ratePack, runPack, searchPacks, updatePack } from "./packs.js";
9
- import { createSuggestion, getSuggestion, listSuggestions, resolveSuggestion, updateSuggestion } from "./suggestions.js";
10
- import { deleteAlias, getAlias, listAliases, upsertAlias } from "./aliases.js";
11
6
  import { creditBalance, creditCheckout } from "./credits.js";
12
7
  import { CliError } from "./errors.js";
13
- import { addAgents, listAgents, removeAgents } from "./agents.js";
14
8
  import { createWorkspace, deleteWorkspaceMembers, getWorkspaceCheckout, listWorkspaceMembers, updateWorkspace, upsertWorkspaceMembers } from "./workspaces.js";
15
9
  import { addProjectMembers, createProject, deleteProjectMembers, listProjectMembers, listProjects, updateProject } from "./projects.js";
16
10
  import { createToken } from "./token.js";
17
- const TRACK_TYPES = ["task", "goal"];
18
- const LOG_ORDERS = ["asc", "desc"];
19
- const PACK_TYPES = ["workflow", "context"];
20
- const ALIAS_NAMESPACES = ["personal", "workspace"];
21
- const PACK_VISIBILITIES = ["public", "private"];
22
- const SUGGESTION_STATUSES = ["open", "applied", "declined", "archived"];
11
+ import { registerPlaybookProgram } from "./playbook-program.js";
23
12
  // centralize warning codes so wording is consistent everywhere
24
13
  const WARNING_ENV_TOKEN_WORKSPACE_IGNORED = "EPISMO_TOKEN_WORKSPACE_IGNORED";
25
- function collectOption(value, previous = []) {
26
- return [...previous, value];
27
- }
28
14
  async function resolveInput(options, overrides) {
29
15
  const base = await readJsonObjectInput(options.input);
30
16
  return mergeDefined(base, overrides);
31
17
  }
32
- function getExplicitOptionOverride(command, optionName, value) {
33
- const source = command.getOptionValueSource(optionName);
34
- if (source === undefined || source === "default") {
35
- return undefined;
36
- }
37
- return value;
38
- }
39
- function buildScopeOption(command, options) {
40
- const personalExplicit = getExplicitOptionOverride(command, "personal", options.personal);
41
- const projectsExplicit = getExplicitOptionOverride(command, "projects", parseStringArrayInput(options.projects, "--projects"));
42
- if (personalExplicit !== undefined && projectsExplicit !== undefined) {
43
- throw new CliError({
44
- code: "INVALID_INPUT",
45
- message: "--personal and --projects cannot be combined."
46
- });
47
- }
48
- if (personalExplicit === true) {
49
- return { type: "personal" };
50
- }
51
- if (projectsExplicit !== undefined) {
52
- const ids = projectsExplicit;
53
- if (ids.length === 0) {
54
- throw new CliError({
55
- code: "INVALID_INPUT",
56
- message: "--projects requires at least one project id."
57
- });
58
- }
59
- return { type: "projects", ids };
60
- }
61
- return undefined;
62
- }
63
- function buildSharedWithOption(command, options) {
64
- const values = getExplicitOptionOverride(command, "shareWith", parseStringArrayInput(options.shareWith, "--share-with"));
65
- if (values === undefined) {
66
- return undefined;
67
- }
68
- const emails = [];
69
- const userIds = [];
70
- for (const value of values) {
71
- if (value.includes("@")) {
72
- emails.push(value);
73
- }
74
- else {
75
- userIds.push(value);
76
- }
77
- }
78
- const result = {};
79
- if (userIds.length > 0) {
80
- result.userIds = userIds;
81
- }
82
- if (emails.length > 0) {
83
- result.emails = emails;
84
- }
85
- return result;
86
- }
87
- function buildSearchScopes(command, options) {
88
- const personalExplicit = getExplicitOptionOverride(command, "personal", options.personal);
89
- const projectsExplicit = getExplicitOptionOverride(command, "projects", parseStringArrayInput(options.projects, "--projects"));
90
- const scopes = [];
91
- if (personalExplicit === true) {
92
- scopes.push({ type: "personal" });
93
- }
94
- if (projectsExplicit !== undefined) {
95
- const ids = projectsExplicit;
96
- if (ids.length === 0) {
97
- throw new CliError({
98
- code: "INVALID_INPUT",
99
- message: "--projects requires at least one project id."
100
- });
101
- }
102
- scopes.push({ type: "projects", ids });
103
- }
104
- return scopes.length > 0 ? scopes : undefined;
105
- }
106
- // TODO(remove-legacy-search-targets): delete this helper and the hidden
107
- // `--project-ids` / `--self` options once users have migrated to `--personal`
108
- // / `--projects`.
109
- function buildLegacySearchTargets(command, options) {
110
- const projectIdsExplicit = getExplicitOptionOverride(command, "projectIds", parseStringArrayInput(options.projectIds, "--project-ids"));
111
- const selfExplicit = getExplicitOptionOverride(command, "self", parseOptionalBooleanText(options.self, "--self"));
112
- const targets = {};
113
- if (projectIdsExplicit !== undefined) {
114
- targets.projectIds = projectIdsExplicit;
115
- }
116
- if (selfExplicit !== undefined) {
117
- targets.self = selfExplicit;
118
- }
119
- return Object.keys(targets).length > 0 ? targets : undefined;
120
- }
121
- // TODO(remove-legacy-search-targets): delete with the helpers above.
122
- function parseOptionalBooleanText(value, optionName) {
123
- if (value === undefined) {
124
- return undefined;
125
- }
126
- if (typeof value === "boolean") {
127
- return value;
128
- }
129
- if (typeof value === "string") {
130
- const normalized = value.trim().toLowerCase();
131
- if (normalized === "true")
132
- return true;
133
- if (normalized === "false")
134
- return false;
135
- }
136
- throw new CliError({
137
- code: "INVALID_INPUT",
138
- message: `Invalid ${optionName}: expected true or false.`
139
- });
140
- }
141
- // TODO(remove-legacy-search-targets): inline back into action handlers
142
- // once legacy `--project-ids` / `--self` are removed.
143
- function buildSearchSelection(command, options) {
144
- const scopes = buildSearchScopes(command, options);
145
- const targets = buildLegacySearchTargets(command, options);
146
- if (scopes !== undefined && targets !== undefined) {
147
- throw new CliError({
148
- code: "INVALID_INPUT",
149
- message: "--personal/--projects cannot be combined with the legacy --self/--project-ids flags."
150
- });
151
- }
152
- const result = {};
153
- if (scopes !== undefined)
154
- result.scopes = scopes;
155
- if (targets !== undefined)
156
- result.targets = targets;
157
- return result;
158
- }
159
18
  async function resolveContext() {
160
19
  const context = await resolveExecutionContext();
161
20
  if (context.warning) {
@@ -169,7 +28,7 @@ export function buildProgram(version) {
169
28
  const program = new Command();
170
29
  program
171
30
  .name("epismo")
172
- .description("Epismo CLI")
31
+ .description("Agent-first CLI for discovering, authoring, and coordinating reusable AI Playbooks.")
173
32
  .version(version)
174
33
  .showHelpAfterError(false)
175
34
  .showSuggestionAfterError(false)
@@ -187,15 +46,7 @@ Workspace resolution:
187
46
  Examples:
188
47
  epismo login
189
48
  epismo whoami
190
- epismo workspace list
191
- epismo alias list
192
- epismo track search --query "bug"
193
- epismo pack search --query "meeting notes"
194
- epismo suggestion list --owner --status open
195
- epismo suggestion create @market-research --title "Add review step" --content "Add a final human review."
196
- epismo pack get <id> --block-id b001
197
- epismo pack get @myproject
198
- epismo pack get @handle/myproject`);
49
+ epismo workspace list`);
199
50
  program
200
51
  .command("login")
201
52
  .description("log in through a browser, or use email with automatic SSO discovery")
@@ -329,7 +180,7 @@ Examples:
329
180
  .option("--input <input>", "JSON object, @file, or - for stdin")
330
181
  .option("--name <name>", "updated workspace name")
331
182
  .action(async (workspaceId, options) => {
332
- const payload = await resolveInput({ input: options.input }, { name: options.name });
183
+ const payload = await resolveInput(options, { name: options.name });
333
184
  const context = await resolveContext();
334
185
  printJson(await updateWorkspace(context, workspaceId, payload));
335
186
  });
@@ -456,646 +307,6 @@ Examples:
456
307
  const context = await resolveContext();
457
308
  printJson(await deleteProjectMembers(context, options.projectId, userIds));
458
309
  });
459
- const track = program.command("track").description("manage project tracks (tasks and goals)");
460
- track
461
- .command("create")
462
- .description("create a new project track (task or goal)")
463
- .option("--input <input>", "JSON object, @file, or - for stdin")
464
- .addOption(new Option("--type <type>", "task | goal").choices(TRACK_TYPES))
465
- .option("--title <title>", "title")
466
- .option("--content <content>", "markdown content")
467
- .option("--personal", "keep private to the current user (mutually exclusive with --projects)")
468
- .option("--projects <projects>", "JSON array or comma-separated project ids (mutually exclusive with --personal)")
469
- .option("--share-with <shareWith>", "JSON array or comma-separated list of user ids and/or emails (values containing '@' are emails)")
470
- .option("--sources <sources>", 'JSON array or comma-separated source references: bare entry_ids, or pack origins as "workflow:<id>" / "context:<id>"')
471
- .option("--task <task>", 'task-specific fields as JSON object e.g. \'{"status":"todo","dueDate":"2025-12-31"}\'')
472
- .option("--goal <goal>", 'goal-specific fields as JSON object e.g. \'{"status":"on_track","dueDate":"2025-12-31"}\'')
473
- .action(async (options, command) => {
474
- const payload = await resolveInput(options, {
475
- type: options.type,
476
- title: options.title,
477
- content: options.content,
478
- scope: buildScopeOption(command, options),
479
- sharedWith: buildSharedWithOption(command, options),
480
- sources: parseStringArrayInput(options.sources, "--sources"),
481
- task: parseJsonObjectOption(options.task, "--task"),
482
- goal: parseJsonObjectOption(options.goal, "--goal")
483
- });
484
- const context = await resolveContext();
485
- printJson(await createTrack(context, payload));
486
- });
487
- track.commands.at(-1)?.addHelpText("after", `
488
- Notes:
489
- Use --task or --goal to pass type-specific fields.
490
-
491
- Examples:
492
- epismo track create --type task --title "Fix bug" --content "Details..."
493
- epismo track create --input @task.json
494
- epismo track create --type goal --title "Launch" --goal '{"status":"on_track"}'`);
495
- track
496
- .command("update")
497
- .description("update an existing project track (PATCH — omitted fields are unchanged)")
498
- .argument("<reference>", "task/goal UUID or URL containing it")
499
- .option("--input <input>", "JSON object, @file, or - for stdin")
500
- .option("--title <title>", "updated title")
501
- .option("--content <content>", "updated markdown content")
502
- .option("--personal", "set scope to personal (mutually exclusive with --projects)")
503
- .option("--projects <projects>", "JSON array or comma-separated project ids (mutually exclusive with --personal)")
504
- .option("--share-with <shareWith>", "JSON array or comma-separated list of user ids and/or emails (values containing '@' are emails)")
505
- .option("--sources <sources>", 'JSON array or comma-separated source references: bare entry_ids, or pack origins as "workflow:<id>" / "context:<id>"')
506
- .option("--task <task>", 'task-specific fields as partial JSON object e.g. \'{"status":"done"}\'')
507
- .option("--goal <goal>", "goal-specific fields as partial JSON object e.g. '{\"progress\":50}'")
508
- .option("--note <note>", 'optional status note as JSON, e.g. \'{"kind":"review","content":"Tests passed"}\'')
509
- .action(async (reference, options, command) => {
510
- const payload = await resolveInput(options, {
511
- title: options.title,
512
- content: options.content,
513
- scope: buildScopeOption(command, options),
514
- sharedWith: buildSharedWithOption(command, options),
515
- sources: parseStringArrayInput(options.sources, "--sources"),
516
- task: parseJsonObjectOption(options.task, "--task"),
517
- goal: parseJsonObjectOption(options.goal, "--goal"),
518
- note: parseJsonObjectOption(options.note, "--note")
519
- });
520
- const context = await resolveContext();
521
- printJson(await updateTrack(context, reference, payload));
522
- });
523
- track.commands.at(-1)?.addHelpText("after", `
524
- Notes:
525
- Omitted fields keep their existing value.
526
- Completing a task or completing/postponing a goal may return review.recommendations
527
- for an outcome log or a detailed review.
528
-
529
- Examples:
530
- epismo track update <track-reference> --title "Updated title"
531
- epismo track update <track-reference> --task '{"status":"done"}'
532
- epismo track update <track-reference> --task '{"status":"done"}' --note '{"kind":"review","content":"Tests passed"}'
533
- epismo track update <track-reference> --goal '{"progress":75}'`);
534
- track
535
- .command("get")
536
- .description("fetch one project track")
537
- .argument("<reference>", "task/goal UUID or URL containing it")
538
- .option("--input <input>", "JSON object, @file, or - for stdin")
539
- .action(async (reference) => {
540
- const context = await resolveContext();
541
- printJson(await getTrack(context, reference));
542
- });
543
- track.commands.at(-1)?.addHelpText("after", `
544
- Example:
545
- epismo track get <track-reference>`);
546
- track
547
- .command("review")
548
- .description("generate a detailed review for one or more task/goal tracks")
549
- .argument("<references...>", "one or more task/goal UUIDs or URLs containing them")
550
- .option("--input <input>", "JSON object, @file, or - for stdin")
551
- .option("--instruction <instruction>", "optional instruction to steer the review")
552
- .action(async (references, options) => {
553
- const payload = await resolveInput(options, {
554
- targetIds: references,
555
- instruction: options.instruction
556
- });
557
- const context = await resolveContext();
558
- printJson(await reviewTracks(context, payload));
559
- });
560
- track.commands.at(-1)?.addHelpText("after", `
561
- Notes:
562
- Review output is displayed only; it does not create or update packs automatically.
563
-
564
- Example:
565
- epismo track review <track-reference>
566
- epismo track review <track-reference> <related-track-reference>`);
567
- track
568
- .command("search")
569
- .description("search project tracks")
570
- .option("--input <input>", "JSON object, @file, or - for stdin")
571
- .addOption(new Option("--type <type>", "task | goal").choices(TRACK_TYPES))
572
- .option("--query <query>", "free-text query")
573
- .addOption(new Option("--search-mode <mode>", "keyword (default) or semantic; semantic blends keyword and vector relevance").choices(["keyword", "semantic"]))
574
- .option("--page <page>", "1-based page number")
575
- .option("--personal", "search personal/private-to-user items")
576
- .option("--projects <projects>", "JSON array or comma-separated project ids to search")
577
- // TODO(remove-legacy-search-targets): drop these two hidden options.
578
- .addOption(new Option("--project-ids <projectIds>", "deprecated; use --projects").hideHelp())
579
- .addOption(new Option("--self <self>", "deprecated; use --personal").hideHelp())
580
- .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)')
581
- .action(async (options, command) => {
582
- const selection = buildSearchSelection(command, options);
583
- const payload = await resolveInput(options, {
584
- type: getExplicitOptionOverride(command, "type", options.type),
585
- query: getExplicitOptionOverride(command, "query", options.query),
586
- searchMode: getExplicitOptionOverride(command, "searchMode", options.searchMode),
587
- page: getExplicitOptionOverride(command, "page", parseOptionalPositiveInteger(options.page, "--page")),
588
- scopes: selection.scopes,
589
- targets: selection.targets,
590
- filter: getExplicitOptionOverride(command, "filter", parseJsonObjectOption(options.filter, "--filter"))
591
- });
592
- const context = await resolveContext();
593
- printJson(await searchTracks(context, payload));
594
- });
595
- track.commands.at(-1)?.addHelpText("after", `
596
- Examples:
597
- epismo track search --query "bug"
598
- epismo track search --type task --query "bug"
599
- epismo track search --query "auth rollout plan" --search-mode semantic
600
- epismo track search --type task --projects proj1
601
- epismo track search --type task --personal --projects proj1
602
- epismo track search --type task --filter '{"status":["todo"]}'
603
- epismo track search --type goal --query "roadmap"`);
604
- track
605
- .command("delete")
606
- .description("delete one project track")
607
- .argument("<reference>", "task/goal UUID or URL containing it")
608
- .action(async (reference) => {
609
- const context = await resolveContext();
610
- printJson(await deleteTrack(context, reference));
611
- });
612
- track.commands.at(-1)?.addHelpText("after", `
613
- Example:
614
- epismo track delete <track-reference>`);
615
- track
616
- .command("apply")
617
- .description("create, update, and delete multiple tracks in a single request")
618
- .option("--input <input>", "JSON object, @file, or - for stdin")
619
- .option("--personal", "set scope to personal (mutually exclusive with --projects)")
620
- .option("--projects <projects>", "JSON array or comma-separated project ids (mutually exclusive with --personal)")
621
- .option("--share-with <shareWith>", "JSON array or comma-separated list of user ids and/or emails (values containing '@' are emails)")
622
- .action(async (options, command) => {
623
- const payload = await resolveInput(options, {
624
- scope: buildScopeOption(command, options),
625
- sharedWith: buildSharedWithOption(command, options)
626
- });
627
- const context = await resolveContext();
628
- printJson(await applyTracks(context, payload));
629
- });
630
- track.commands.at(-1)?.addHelpText("after", `
631
- Notes:
632
- upserts: use a non-UUID string as id (e.g. "t001") to create a new track, or a UUID to update an existing one.
633
- Labels in task.parentId, task.dependsOn, and task.goalId are resolved by the server, so you can wire
634
- up dependencies between new entries in the same request.
635
- deletes: provide UUIDs of tracks to delete.
636
-
637
- Examples:
638
- epismo track apply --input @batch.json
639
- epismo track apply --input '{"upserts":[{"id":"t001","title":"Task A","task":{"status":"todo"}},{"id":"t002","title":"Task B","task":{"status":"todo","dependsOn":["t001"]}}]}'`);
640
- const log = program.command("log").description("manage track logs");
641
- log.command("create")
642
- .description("append a log/comment to a track")
643
- .argument("<reference>", "task/goal UUID or URL containing it")
644
- .option("--input <input>", "JSON object, @file, or - for stdin")
645
- .option("--kind <kind>", "comment | update | review (default: comment)")
646
- .option("--content <content>", "log content")
647
- .option("--metadata <metadata>", "machine-readable metadata as JSON object")
648
- .option("--idempotency-key <idempotencyKey>", "optional UUID for idempotent retry")
649
- .action(async (trackId, options, command) => {
650
- const payload = await resolveInput(options, {
651
- kind: getExplicitOptionOverride(command, "kind", options.kind),
652
- content: getExplicitOptionOverride(command, "content", options.content),
653
- metadata: getExplicitOptionOverride(command, "metadata", parseJsonObjectOption(options.metadata, "--metadata")),
654
- idempotencyKey: getExplicitOptionOverride(command, "idempotencyKey", options.idempotencyKey)
655
- });
656
- const context = await resolveContext();
657
- printJson(await createLogForTrack(context, trackId, payload));
658
- });
659
- log.commands.at(-1)?.addHelpText("after", `
660
- Examples:
661
- epismo log create <track-reference> --content "Evaluator failed because tests are missing."
662
- epismo log create <track-reference> --kind review --content "Tests failed" --metadata '{"verdict":"fail"}'`);
663
- log.command("list")
664
- .description("list logs — for one track, or across every track you can see")
665
- .argument("[reference]", "task/goal UUID or URL containing it (omit for the cross-track activity feed)")
666
- .option("--author-id <userId>", "narrow to logs authored by this user id")
667
- .addOption(new Option("--order <order>", "desc = newest first (default), asc = oldest first").choices(LOG_ORDERS))
668
- .option("--cursor <logId>", "continue past this log id in the chosen order (nextCursor)")
669
- .action(async (trackId, options) => {
670
- const context = await resolveContext();
671
- printJson(await listLogs(context, trackId, {
672
- authorId: options.authorId,
673
- order: options.order,
674
- cursor: options.cursor
675
- }));
676
- });
677
- log.commands.at(-1)?.addHelpText("after", `
678
- Examples:
679
- epismo log list <track-reference> # newest logs first for one track
680
- epismo log list <track-reference> --cursor <logId> # older history
681
- epismo log list <track-reference> --order asc # chronological from the start
682
- epismo log list # every log you can see, newest first
683
- epismo log list --author-id <userId> # only logs one user wrote`);
684
- log.command("delete")
685
- .description("delete one log")
686
- .argument("<logId>", "log id")
687
- .action(async (logId) => {
688
- const context = await resolveContext();
689
- printJson(await deleteLog(context, logId));
690
- });
691
- log.commands.at(-1)?.addHelpText("after", `
692
- Example:
693
- epismo log delete <logId>`);
694
- const pack = program.command("pack").description("manage agent packs (workflows and contexts)");
695
- pack.command("create")
696
- .description("create a new agent pack")
697
- .option("--input <input>", "JSON object, @file, or - for stdin")
698
- .addOption(new Option("--type <type>", "workflow | context (required unless provided in --input)").choices(PACK_TYPES))
699
- .option("--title <title>", "title")
700
- .option("--content <content>", "markdown content")
701
- .option("--category <category>", "pack category")
702
- .addOption(new Option("--visibility <visibility>", "public | private").choices(PACK_VISIBILITIES))
703
- .option("--personal", "keep private to the current user (mutually exclusive with --projects)")
704
- .option("--projects <projects>", "JSON array or comma-separated project ids (mutually exclusive with --personal)")
705
- .option("--share-with <shareWith>", "JSON array or comma-separated list of user ids and/or emails (values containing '@' are emails)")
706
- .option("--steps <steps>", "workflow steps as JSON array (use --input @file.json for complex payloads)")
707
- .option("--blocks <blocks>", "context blocks as JSON array (for type=context; use --input @file.json for complex payloads)")
708
- .action(async (options, command) => {
709
- const payload = await resolveInput(options, {
710
- type: options.type,
711
- title: options.title,
712
- content: options.content,
713
- category: options.category,
714
- visibility: options.visibility,
715
- scope: buildScopeOption(command, options),
716
- sharedWith: buildSharedWithOption(command, options),
717
- steps: parseJsonArrayOption(options.steps, "--steps"),
718
- blocks: parseJsonArrayOption(options.blocks, "--blocks")
719
- });
720
- if (payload.type !== "workflow" && payload.type !== "context") {
721
- throw new CliError({
722
- code: "INVALID_INPUT",
723
- message: "Pack type is required.",
724
- hint: 'Pass --type workflow|context or include {"type":"workflow"} / {"type":"context"} in --input.'
725
- });
726
- }
727
- const context = await resolveContext();
728
- printJson(await createPack(context, payload));
729
- });
730
- pack.commands.at(-1)?.addHelpText("after", `
731
- Notes:
732
- type is required for pack create. Pass --type or include it in --input.
733
-
734
- Examples:
735
- epismo pack create --type workflow --title "My workflow" --input @workflow.json
736
- epismo pack create --type workflow --title "My workflow" --steps '[{"title":"Step 1","content":"..."}]'
737
- epismo pack create --type context --title "My context" --input @context.json
738
- epismo pack create --type context --title "My context" --blocks '[{"title":"Block 1","content":"..."}]'`);
739
- pack.command("update")
740
- .description("update an existing agent pack (PATCH semantics — omitted fields are unchanged)")
741
- .argument("<reference>", "pack reference: id, alias, share URL, or hub URL")
742
- .option("--input <input>", "JSON object, @file, or - for stdin")
743
- .option("--title <title>", "updated title")
744
- .option("--content <content>", "updated markdown content")
745
- .option("--category <category>", "updated pack category")
746
- .addOption(new Option("--visibility <visibility>", "public | private").choices(PACK_VISIBILITIES))
747
- .option("--personal", "set scope to personal (mutually exclusive with --projects)")
748
- .option("--projects <projects>", "JSON array or comma-separated project ids (mutually exclusive with --personal)")
749
- .option("--share-with <shareWith>", "JSON array or comma-separated list of user ids and/or emails (values containing '@' are emails)")
750
- .option("--steps <steps>", 'step operations as JSON array with op field: [{"op":"add","title":"...","beforeId":"s002"},{"op":"move","id":"s003","afterId":"s001"},{"op":"update","id":"s001","content":"..."},{"op":"remove","id":"s002"}]')
751
- .option("--blocks <blocks>", 'block operations as JSON array with op field: [{"op":"add","title":"...","beforeId":"b002"},{"op":"move","id":"b003","afterId":"b001"},{"op":"update","id":"b001","content":"..."},{"op":"remove","id":"b002"}]')
752
- .action(async (reference, options, command) => {
753
- const payload = await resolveInput(options, {
754
- title: options.title,
755
- content: options.content,
756
- category: options.category,
757
- visibility: options.visibility,
758
- scope: buildScopeOption(command, options),
759
- sharedWith: buildSharedWithOption(command, options),
760
- steps: parseJsonArrayOption(options.steps, "--steps"),
761
- blocks: parseJsonArrayOption(options.blocks, "--blocks")
762
- });
763
- const context = await resolveContext();
764
- printJson(await updatePack(context, reference, payload));
765
- });
766
- pack.commands.at(-1)?.addHelpText("after", `
767
- Notes:
768
- Omitted fields keep their existing value.
769
- steps/blocks use operation objects with an "op" field (add | update | move | remove).
770
- add/move accept beforeId or afterId. Workflow move keeps dependsOn unchanged.
771
- Omitting steps/blocks keeps them unchanged. Passing an empty array [] is a no-op.
772
- To remove all items, send a remove op for each existing item ID.
773
-
774
- Examples:
775
- epismo pack update <id> --category ""
776
- epismo pack update @myproject --visibility public
777
- epismo pack update <id> --visibility public --category productivity
778
- epismo pack update <id> --steps '[{"op":"move","id":"s003","afterId":"s001"}]'
779
- epismo pack update <id> --blocks '[{"op":"add","title":"New Block","content":"..."}]'
780
- epismo pack update <id> --blocks '[{"op":"move","id":"b003","beforeId":"b001"}]'
781
- epismo pack update <id> --blocks '[{"op":"update","id":"b001","content":"updated..."}]'
782
- epismo pack update <id> --blocks '[{"op":"remove","id":"b002"}]'
783
- epismo pack update <id> --input @changes.json`);
784
- pack.command("run")
785
- .description("materialize a workflow pack into a track (a root goal plus one task per step); fetch the run later with track search by goal id")
786
- .argument("<reference>", "workflow pack reference: id, alias, share URL, or hub URL")
787
- .option("--title <title>", "objective for the run's root goal (defaults to the pack title)")
788
- .option("--content <content>", "root goal content in Markdown (defaults to the pack content)")
789
- .option("--due-date <date>", "YYYY-MM-DD absolute due date for the root goal")
790
- .option("--context <reference>", "context pack reference (id, alias, share URL, or hub URL) to record as a source on the goal; comma-separated or repeatable", collectOption)
791
- .option("--assignee <token=id>", "map a pack assignee token to a track assignee id, e.g. human=<user-id>; comma-separated or repeatable", collectOption)
792
- .option("--personal", "keep created tracks private to the current user (mutually exclusive with --projects)")
793
- .option("--projects <projects>", "JSON array or comma-separated project ids (mutually exclusive with --personal)")
794
- .option("--share-with <shareWith>", "JSON array or comma-separated list of user ids and/or emails (values containing '@' are emails)")
795
- .action(async (reference, options, command) => {
796
- const body = {
797
- reference,
798
- title: options.title,
799
- content: options.content,
800
- goalDueDate: options.dueDate,
801
- context: options.context,
802
- assignees: options.assignee,
803
- scope: buildScopeOption(command, options),
804
- sharedWith: buildSharedWithOption(command, options)
805
- };
806
- const context = await resolveContext();
807
- printJson(await runPack(context, body));
808
- });
809
- pack.commands.at(-1)?.addHelpText("after", `
810
- Notes:
811
- Creates the track immediately. Review the returned warnings; delete and re-run to fix mistakes.
812
- --assignee maps pack tokens to track assignees; map human to a user id (agent ids resolve as-is).
813
-
814
- Examples:
815
- epismo pack run @small-feature-ship
816
- epismo pack run @small-feature-ship --assignee human=<user-id>
817
- epismo pack run @small-feature-ship --title "Ship CSV export" --projects <project-id> --context <context-pack-id>`);
818
- pack.command("get")
819
- .description("fetch one agent pack")
820
- .argument("<reference>", "pack reference: id, alias, share URL, or hub URL")
821
- .option("--input <input>", "JSON object, @file, or - for stdin")
822
- .option("--full", "include nested item content")
823
- .option("--share-url", "create or return a share URL for this pack")
824
- .option("--block-id <blockId>", "context block id to extract; comma-separated or repeatable (type=context only)", collectOption)
825
- .option("--step-id <stepId>", "workflow step id to extract; comma-separated or repeatable (type=workflow only)", collectOption)
826
- .addOption(new Option("--namespace <namespace>", "resolve a bare/`@alias` reference in personal or workspace (omit to try personal first, then workspace)").choices(ALIAS_NAMESPACES))
827
- .action(async (reference, options) => {
828
- const payload = await resolveInput(options, {
829
- full: options.full ? true : undefined
830
- });
831
- const context = await resolveContext();
832
- printJson(await getPack(context, reference, payload, {
833
- blockIds: options.blockId,
834
- stepIds: options.stepId,
835
- namespace: options.namespace,
836
- shareUrl: options.shareUrl
837
- }));
838
- });
839
- pack.commands.at(-1)?.addHelpText("after", `
840
- Examples:
841
- epismo pack get <id>
842
- epismo pack get @myproject
843
- epismo pack get @handle/myproject
844
- epismo pack get https://epismo.ai/share/<token>
845
- epismo pack get https://epismo.ai/hub/workflows/<id>
846
- epismo pack get <id> --share-url
847
- epismo pack get <id> --full
848
- epismo pack get <id> --block-id b001
849
- epismo pack get <id> --block-id b001 --block-id b002
850
- epismo pack get <id> --step-id s001,s002`);
851
- pack.command("search")
852
- .description("search agent packs")
853
- .option("--input <input>", "JSON object, @file, or - for stdin")
854
- .addOption(new Option("--type <type>", "workflow | context").choices(PACK_TYPES))
855
- .option("--query <query>", "free-text query")
856
- .addOption(new Option("--search-mode <mode>", "keyword (default) or semantic; semantic blends keyword and vector relevance").choices(["keyword", "semantic"]))
857
- .option("--page <page>", "1-based page number")
858
- .option("--personal", "search personal/private-to-user packs")
859
- .option("--projects <projects>", "JSON array or comma-separated project ids to search")
860
- // TODO(remove-legacy-search-targets): drop these two hidden options.
861
- .addOption(new Option("--project-ids <projectIds>", "deprecated; use --projects").hideHelp())
862
- .addOption(new Option("--self <self>", "deprecated; use --personal").hideHelp())
863
- .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)')
864
- .action(async (options, command) => {
865
- const selection = buildSearchSelection(command, options);
866
- const payload = await resolveInput(options, {
867
- type: getExplicitOptionOverride(command, "type", options.type),
868
- query: getExplicitOptionOverride(command, "query", options.query),
869
- searchMode: getExplicitOptionOverride(command, "searchMode", options.searchMode),
870
- page: getExplicitOptionOverride(command, "page", parseOptionalPositiveInteger(options.page, "--page")),
871
- scopes: selection.scopes,
872
- targets: selection.targets,
873
- filter: getExplicitOptionOverride(command, "filter", parseJsonObjectOption(options.filter, "--filter"))
874
- });
875
- const context = await resolveContext();
876
- printJson(await searchPacks(context, payload));
877
- });
878
- pack.commands.at(-1)?.addHelpText("after", `
879
- Examples:
880
- epismo pack search --type workflow --query "onboarding"
881
- epismo pack search --query "meeting notes"
882
- epismo pack search --type workflow --projects proj1
883
- epismo pack search --query "customer onboarding ideas" --search-mode semantic
884
- epismo pack search --type workflow --personal --projects proj1
885
- epismo pack search --type workflow --filter '{"visibility":["public"]}'
886
- epismo pack search --type context --query "meeting notes"`);
887
- pack.command("like")
888
- .description("like or unlike an agent pack")
889
- .argument("<reference>", "pack reference: id, alias, share URL, or hub URL")
890
- .option("--input <input>", "JSON object, @file, or - for stdin")
891
- .option("--liked", "mark as liked")
892
- .option("--no-liked", "remove like")
893
- .action(async (reference, options) => {
894
- const payload = await resolveInput(options, {
895
- liked: options.liked
896
- });
897
- if (typeof payload.liked !== "boolean") {
898
- throw new CliError({
899
- code: "INVALID_INPUT",
900
- message: "Either --liked or --no-liked must be specified.",
901
- hint: "Use --liked to add a like, or --no-liked to remove one."
902
- });
903
- }
904
- const context = await resolveContext();
905
- printJson(await likePack(context, reference, payload));
906
- });
907
- pack.commands.at(-1)?.addHelpText("after", `
908
- Examples:
909
- epismo pack like <id> --liked
910
- epismo pack like @myproject --liked
911
- epismo pack like <id> --no-liked`);
912
- pack.command("rate")
913
- .description("rate whether a pack worked when you used it")
914
- .argument("<reference>", "pack reference: id, alias, share URL, or hub URL")
915
- .addArgument(new Argument("<outcome>", "success | failure").choices(["success", "failure"]))
916
- .action(async (reference, outcome) => {
917
- const context = await resolveContext();
918
- printJson(await ratePack(context, reference, { outcome }));
919
- });
920
- pack.commands.at(-1)?.addHelpText("after", `
921
- Examples:
922
- epismo pack rate <id> success
923
- epismo pack rate @myproject failure`);
924
- pack.command("delete")
925
- .description("delete one agent pack and any of your aliases that point to it")
926
- .argument("<reference>", "pack reference: id, alias, share URL, or hub URL")
927
- .action(async (reference) => {
928
- const context = await resolveContext();
929
- printJson(await deletePack(context, reference));
930
- });
931
- pack.commands.at(-1)?.addHelpText("after", `
932
- Examples:
933
- epismo pack delete <id> # also removes your aliases for that pack
934
- epismo pack delete @myproject
935
- epismo pack delete https://epismo.ai/share/<token>`);
936
- const suggestion = program.command("suggestion").description("manage improvement suggestions");
937
- suggestion
938
- .command("create")
939
- .description("send an improvement suggestion for a pack")
940
- .argument("<reference>", "pack reference: id, alias, share URL, or hub URL")
941
- .option("--input <input>", "JSON object, @file, or - for stdin")
942
- .option("--title <title>", "short summary")
943
- .option("--content <content>", "suggestion content")
944
- .action(async (reference, options) => {
945
- const payload = await resolveInput(options, {
946
- reference,
947
- title: options.title,
948
- content: options.content
949
- });
950
- const context = await resolveContext();
951
- printJson(await createSuggestion(context, payload));
952
- });
953
- suggestion.commands.at(-1)?.addHelpText("after", `
954
- Examples:
955
- epismo suggestion create @market-research --title "Add review step" --content "A final human review step would reduce hallucinated findings."
956
- epismo suggestion create <id> --input @suggestion.json`);
957
- suggestion
958
- .command("get")
959
- .description("fetch one suggestion")
960
- .argument("<id>", "suggestion id")
961
- .option("--input <input>", "JSON object, @file, or - for stdin")
962
- .option("--include-snapshot", "include the pack snapshot captured with the suggestion")
963
- .action(async (id, options) => {
964
- const payload = await resolveInput(options, {
965
- includeSnapshot: options.includeSnapshot ? true : undefined
966
- });
967
- const context = await resolveContext();
968
- printJson(await getSuggestion(context, id, payload));
969
- });
970
- suggestion.commands.at(-1)?.addHelpText("after", `
971
- Examples:
972
- epismo suggestion get <suggestion-id>
973
- epismo suggestion get <suggestion-id> --include-snapshot`);
974
- suggestion
975
- .command("list")
976
- .description("list suggestions")
977
- .option("--input <input>", "JSON object, @file, or - for stdin")
978
- .option("--reference <reference>", "filter by pack reference")
979
- .option("--author <author>", "filter by author account id")
980
- .option("--owner", "list suggestions for packs you own")
981
- .option("--status <status>", "comma-separated statuses or JSON array")
982
- .option("--page <page>", "1-based page number")
983
- .option("--include-snapshots", "include captured snapshots")
984
- .action(async (options, command) => {
985
- const payload = await resolveInput(options, {
986
- reference: getExplicitOptionOverride(command, "reference", options.reference),
987
- author: getExplicitOptionOverride(command, "author", options.author),
988
- owner: getExplicitOptionOverride(command, "owner", options.owner),
989
- statuses: getExplicitOptionOverride(command, "status", parseStringArrayInput(options.status, "--status")),
990
- page: getExplicitOptionOverride(command, "page", parseOptionalPositiveInteger(options.page, "--page")),
991
- includeSnapshots: options.includeSnapshots ? true : undefined
992
- });
993
- const statuses = payload.statuses;
994
- if (Array.isArray(statuses) &&
995
- statuses.some((status) => !SUGGESTION_STATUSES.includes(status))) {
996
- throw new CliError({
997
- code: "INVALID_INPUT",
998
- message: `Invalid --status: expected one of ${SUGGESTION_STATUSES.join(", ")}.`
999
- });
1000
- }
1001
- const context = await resolveContext();
1002
- printJson(await listSuggestions(context, payload));
1003
- });
1004
- suggestion.commands.at(-1)?.addHelpText("after", `
1005
- Examples:
1006
- epismo suggestion list --reference @market-research
1007
- epismo suggestion list --owner --status open
1008
- epismo suggestion list --status open,declined`);
1009
- suggestion
1010
- .command("resolve")
1011
- .description("update a suggestion status")
1012
- .argument("<id>", "suggestion id")
1013
- .option("--input <input>", "JSON object, @file, or - for stdin")
1014
- .addOption(new Option("--status <status>", "open | applied | declined | archived").choices(SUGGESTION_STATUSES))
1015
- .action(async (id, options) => {
1016
- const payload = await resolveInput(options, {
1017
- status: options.status
1018
- });
1019
- if (!payload.status) {
1020
- throw new CliError({
1021
- code: "INVALID_INPUT",
1022
- message: "--status is required."
1023
- });
1024
- }
1025
- const context = await resolveContext();
1026
- printJson(await resolveSuggestion(context, id, payload));
1027
- });
1028
- suggestion.commands.at(-1)?.addHelpText("after", `
1029
- Examples:
1030
- epismo suggestion resolve <suggestion-id> --status applied
1031
- epismo suggestion resolve <suggestion-id> --status declined`);
1032
- suggestion
1033
- .command("update")
1034
- .description("update a suggestion title or content")
1035
- .argument("<id>", "suggestion id")
1036
- .option("--input <input>", "JSON object, @file, or - for stdin")
1037
- .option("--title <title>", "updated suggestion title")
1038
- .option("--content <content>", "updated suggestion content")
1039
- .action(async (id, options) => {
1040
- const payload = await resolveInput(options, {
1041
- title: options.title,
1042
- content: options.content
1043
- });
1044
- if (!payload.title || !payload.content) {
1045
- throw new CliError({
1046
- code: "INVALID_INPUT",
1047
- message: "--title and --content are required."
1048
- });
1049
- }
1050
- const context = await resolveContext();
1051
- printJson(await updateSuggestion(context, id, payload));
1052
- });
1053
- suggestion.commands.at(-1)?.addHelpText("after", `
1054
- Examples:
1055
- epismo suggestion update <suggestion-id> --title "Add review step" --content "Please add a reviewer approval step."`);
1056
- const agent = program.command("agent").description("manage agent availability");
1057
- agent
1058
- .command("list")
1059
- .description("list AI teammates and whether they appear in the assignee roster")
1060
- .action(async () => {
1061
- const context = await resolveContext();
1062
- printJson(await listAgents(context));
1063
- });
1064
- agent.commands.at(-1)?.addHelpText("after", `
1065
- Examples:
1066
- epismo agent list`);
1067
- agent
1068
- .command("add")
1069
- .description("add AI teammates to the assignee roster")
1070
- .option("--input <input>", "JSON object, @file, or - for stdin")
1071
- .option("--agent-ids <agentIds>", "JSON array or comma-separated agent ids")
1072
- .action(async (options) => {
1073
- const payload = await resolveInput(options, {
1074
- agentIds: parseStringArrayInput(options.agentIds, "--agent-ids")
1075
- });
1076
- const context = await resolveContext();
1077
- printJson(await addAgents(context, payload));
1078
- });
1079
- agent.commands.at(-1)?.addHelpText("after", `
1080
- Examples:
1081
- epismo agent add --agent-ids agent_a
1082
- epismo agent add --agent-ids '["agent_a","agent_b"]'`);
1083
- agent
1084
- .command("remove")
1085
- .description("remove AI teammates from the assignee roster")
1086
- .option("--input <input>", "JSON object, @file, or - for stdin")
1087
- .option("--agent-ids <agentIds>", "JSON array or comma-separated agent ids")
1088
- .action(async (options) => {
1089
- const payload = await resolveInput(options, {
1090
- agentIds: parseStringArrayInput(options.agentIds, "--agent-ids")
1091
- });
1092
- const context = await resolveContext();
1093
- printJson(await removeAgents(context, payload));
1094
- });
1095
- agent.commands.at(-1)?.addHelpText("after", `
1096
- Examples:
1097
- epismo agent remove --agent-ids agent_a
1098
- epismo agent remove --agent-ids '["agent_a","agent_b"]'`);
1099
310
  const credit = program.command("credit").description("view balance and purchase credits");
1100
311
  credit
1101
312
  .command("balance")
@@ -1122,73 +333,6 @@ Examples:
1122
333
  credit.commands.at(-1)?.addHelpText("after", `
1123
334
  Example:
1124
335
  epismo credit checkout --quantity 500`);
1125
- const alias = program.command("alias").description("manage pack aliases");
1126
- alias
1127
- .command("upsert")
1128
- .description("create or update one alias")
1129
- .argument("<alias>", "alias name (`alias` or `@alias`; stored without `@`)")
1130
- .option("--input <input>", "JSON object, @file, or - for stdin")
1131
- .option("--reference <reference>", "target pack reference: id, alias, share URL, or hub URL")
1132
- .addOption(new Option("--namespace <namespace>", "personal (default) or workspace (shared with the active workspace)").choices(ALIAS_NAMESPACES))
1133
- .action(async (aliasName, options) => {
1134
- const payload = await resolveInput(options, {
1135
- reference: options.reference,
1136
- alias: aliasName,
1137
- namespace: options.namespace
1138
- });
1139
- if (typeof payload.reference !== "string" || !payload.reference.trim()) {
1140
- throw new CliError({
1141
- code: "INVALID_INPUT",
1142
- message: "`reference` is required.",
1143
- hint: "Pass --reference <reference> or include reference in --input."
1144
- });
1145
- }
1146
- const context = await resolveContext();
1147
- printJson(await upsertAlias(context, payload));
1148
- });
1149
- alias.commands.at(-1)?.addHelpText("after", `
1150
- Examples:
1151
- epismo alias upsert @myproject --reference <pack-reference>
1152
- epismo alias upsert @deploy --reference <pack-reference> --namespace workspace`);
1153
- alias
1154
- .command("get")
1155
- .description("resolve one alias")
1156
- .argument("<alias>", "alias reference (`@alias` or `@handle/alias`)")
1157
- .addOption(new Option("--namespace <namespace>", "resolve a bare/`@alias` reference in personal or workspace (omit to try personal first, then workspace)").choices(ALIAS_NAMESPACES))
1158
- .action(async (aliasRef, options) => {
1159
- const context = await resolveContext();
1160
- printJson(await getAlias(context, { alias: aliasRef, namespace: options.namespace }));
1161
- });
1162
- alias.commands.at(-1)?.addHelpText("after", `
1163
- Examples:
1164
- epismo alias get @myproject
1165
- epismo alias get @deploy --namespace workspace
1166
- epismo alias get @handle/myproject`);
1167
- alias
1168
- .command("list")
1169
- .description("list your aliases")
1170
- .addOption(new Option("--type <type>", "workflow | context").choices(PACK_TYPES))
1171
- .action(async (options) => {
1172
- const context = await resolveContext();
1173
- printJson(await listAliases(context, { type: options.type }));
1174
- });
1175
- alias.commands.at(-1)?.addHelpText("after", `
1176
- Example:
1177
- epismo alias list
1178
- epismo alias list --type workflow`);
1179
- alias
1180
- .command("delete")
1181
- .description("delete one alias")
1182
- .argument("<alias>", "alias name (`alias` or `@alias`; stored without `@`)")
1183
- .addOption(new Option("--namespace <namespace>", "personal (default) or workspace (the active workspace's shared alias)").choices(ALIAS_NAMESPACES))
1184
- .action(async (aliasName, options) => {
1185
- const context = await resolveContext();
1186
- printJson(await deleteAlias(context, { alias: aliasName, namespace: options.namespace }));
1187
- });
1188
- alias.commands.at(-1)?.addHelpText("after", `
1189
- Examples:
1190
- epismo alias delete @myproject
1191
- epismo alias delete @deploy --namespace workspace`);
1192
336
  const token = program.command("token").description("manage CLI tokens for CI/CD");
1193
337
  token
1194
338
  .command("create")
@@ -1209,6 +353,7 @@ Notes:
1209
353
  Examples:
1210
354
  epismo token create --workspace-id <workspace-id>
1211
355
  epismo token create # uses saved default workspace, or personal space if none`);
356
+ registerPlaybookProgram(program, { resolveInput, resolveContext });
1212
357
  return program;
1213
358
  }
1214
359
  //# sourceMappingURL=program.js.map