epismo 0.20.0 → 1.0.1

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/README.md CHANGED
@@ -1,104 +1,111 @@
1
1
  # Epismo CLI
2
2
 
3
- GitHub made code reusable.
4
- Hugging Face made AI models reusable.
5
- Epismo makes AI work reusable.
6
-
7
- Capture how you got your best results — not just the output — and share them as executable workflows that agents and humans can discover, adapt, and run across projects.
8
-
9
- ## CLI Contract
10
-
11
- Epismo CLI is designed for agents first.
12
-
13
- - Success output is machine-readable JSON on `stdout`, in both interactive and non-interactive runs.
14
- - `stderr` carries prompts, browser-login guidance, warnings, and errors (all JSON-shaped).
15
- - Exit code `0` means success; any non-zero code means failure.
16
- - In non-interactive environments, commands that would need a prompt fail fast instead of waiting for input.
3
+ Agent-friendly CLI for Playbooks, Cases, Tasks, Records, Suggestions, Stars, and Aliases. Successful commands print JSON to stdout; warnings and failures are JSON-shaped on stderr.
17
4
 
18
5
  ## Install
19
6
 
20
7
  Node.js 22.12 or newer is required.
21
8
 
22
9
  ```bash
23
- npm install -g epismo # or run ad hoc with: npx epismo <command>
10
+ npm install -g epismo
11
+ # or
12
+ npx epismo <command>
24
13
  ```
25
14
 
26
- ## Authentication
15
+ ## Authentication and workspace selection
27
16
 
28
17
  ```bash
29
- epismo login # opens a browser for Google, SSO, or email sign-in
30
- epismo login --email you@example.com # company SSO in browser, otherwise code in terminal
31
- epismo logout # clear local credentials
32
- EPISMO_TOKEN=<access-token> epismo whoami # preferred for scripts
18
+ epismo login
19
+ epismo login --email you@example.com
20
+ epismo whoami
21
+ epismo logout
33
22
  ```
34
23
 
35
- If `EPISMO_TOKEN` is set, the CLI uses it instead of stored credentials and resolves the workspace from the token itself (the saved default workspace is ignored).
24
+ Commands use the workspace embedded in `EPISMO_TOKEN`, then the saved default from
25
+ `epismo workspace use`, then personal space. A workspace-scoped CI token can be issued
26
+ with `epismo token create --workspace-id <workspace-id>`.
36
27
 
37
28
  ## Commands
38
29
 
39
- Each command group maps to an Epismo resource.
40
- Run `epismo <command> --help` for the full flag set; the highlights:
30
+ ```text
31
+ epismo login | logout | whoami
41
32
 
42
- | Command | Purpose |
43
- | ------------ | ----------------------------------------------------------------------------- |
44
- | `whoami` | Show the current user, effective workspace, and accessible workspaces. |
45
- | `workspace` | List/create/update workspaces, manage members, and get billing checkout URLs. |
46
- | `project` | Manage projects within a workspace and their members. |
47
- | `agent` | Manage which AI teammates appear in the assignee roster. |
48
- | `track` | Create, update, search, and bulk-apply task/goal project tracks. |
49
- | `log` | Append, list, and delete activity/comment logs on tracks. |
50
- | `pack` | Create, update, fetch, search, like, rate, and delete packs. |
51
- | `suggestion` | Send and triage improvement suggestions against packs. |
52
- | `alias` | Manage pack aliases as a top-level resource. |
53
- | `credit` | Check the credit balance and start credit checkout. |
54
- | `token` | Issue workspace-scoped CLI tokens for CI/CD. |
33
+ epismo workspace list | current | use | clear | create | checkout | update
34
+ epismo workspace member list | upsert | delete
35
+ epismo project list | create | update
36
+ epismo project member list | add | delete
37
+ epismo credit balance | checkout
38
+ epismo token create
55
39
 
56
- A few group-specific notes that aren't obvious from `--help`:
40
+ epismo playbook search | create | get | acl | archive | star | unstar | starred
41
+ epismo playbook version list | get | publish
42
+ epismo playbook share create
43
+ epismo alias set | list | delete
57
44
 
58
- - **Bulk member ops** accept comma-separated IDs and use one backend transaction. Membership does not change Stripe billing quantity.
59
- - **`track apply`** creates, updates, and deletes multiple tracks in one request; use a non-UUID `id` to create, and cross-references between new entries are resolved server-side.
60
- - **`track review`** generates a read-only detailed review for one or more completed/postponed task or goal tracks. It returns JSON with `targets` and a `review` object; it does not create or update packs automatically.
61
- - **`log`** entries are immutable comments/activity notes attached to a track (`kind`: `comment`, `update`, `review`, or the reserved `system`). `log list <track-reference>` reads one track, newest first by default; pass a task/goal UUID or URL containing it. Omit `<track-reference>` for an ACL-scoped activity feed across every track you can access, optionally `--author-id`-filtered either way. `--order asc|desc` and `--cursor <logId>` (the previous response's `nextCursor`) control pagination. Appending a log is free; listing costs credits like other reads. Authors outside what the caller can see (e.g. someone who has since left) come back as `"unknown"`.
45
+ epismo case start | get | list | assign | acl | update | close | reopen
46
+ epismo task create | list | assign | update | close | reopen
47
+ epismo record append | list
62
48
 
63
- ### Pack references
64
-
65
- Any command that takes a pack accepts a bare UUID, an alias (`@alias` or `@handle/alias`), a share URL, or a hub URL interchangeably.
66
-
67
- ### Track references
49
+ epismo suggestion create | get | list | update | resolve
50
+ ```
68
51
 
69
- Any command that takes an existing track accepts a task/goal UUID or a URL containing that UUID. Tracks do not have aliases.
52
+ The Playbook, Case, Task, Record, Suggestion, Star, and Alias operations are also
53
+ available through MCP. MCP names mirror the CLI resource/verb hierarchy in snake_case;
54
+ for example, `epismo_playbook_version_list` maps to `epismo playbook version list`.
70
55
 
71
- ### Access scope
56
+ Run `epismo <command> --help` for flags and positional arguments.
72
57
 
73
- `track` and `pack` writes share one access model: `scope` is `{ "type": "personal" }` or `{ "type": "projects", "ids": [...] }` (required on create; omit on update to preserve existing access), with an optional `sharedWith`.
74
- The matching CLI flags are `--personal`, `--projects <id...>`, and `--share-with <userIdOrEmail...>`.
75
- Search uses the same selectors additively; omit them to use the default scope for the current context.
58
+ ## Structured input
76
59
 
77
- ## Workspace Selection
60
+ Commands that accept payloads support:
78
61
 
79
- There is no per-command `--workspace-id` flag — every command resolves a workspace, in this order:
62
+ ```bash
63
+ epismo playbook create --input @playbook.json
64
+ epismo record append <case-id> --input @record.json
65
+ epismo suggestion create --input - < suggestion.json
66
+ ```
80
67
 
81
- 1. The workspace embedded in `EPISMO_TOKEN` (issued via `epismo token create --workspace-id`).
82
- 2. The saved default from `epismo workspace use` (interactive sessions).
83
- 3. Personal space, when nothing else is configured.
68
+ `--input` accepts an inline JSON object, `@file`, or `-` for stdin. Explicit flags
69
+ override fields from `--input`. Complex Playbook Definitions and close-time Record
70
+ arrays are best supplied through files.
84
71
 
85
- For CI/CD, issue a scoped token once and export it:
72
+ `--input` always carries the whole request payload, so `epismo case start` takes the
73
+ Case's own input object through `--case-input` instead:
86
74
 
87
75
  ```bash
88
- epismo token create --workspace-id <workspace-id>
89
- export EPISMO_TOKEN=<accessToken> # all commands now use that workspace
76
+ epismo case start --version-id <version-id> --acl <account-id> \
77
+ --case-input '{"companyName":"Acme"}'
90
78
  ```
91
79
 
92
- ## Input / Output
80
+ Mutation commands accept `--idempotency-key <uuid>`. When omitted, the CLI generates
81
+ a UUID automatically. Reuse an explicit key when retrying a command whose first result
82
+ was uncertain; reusing one with different arguments is rejected.
83
+
84
+ Every mutation of an existing Case or Task requires `--lock-version`, taken from the
85
+ most recent object returned by the API: `case assign | acl | update | close | reopen`
86
+ and `task assign | update | close | reopen`. After a conflict, re-read the object and
87
+ decide again rather than resending with a bumped number. Updates are partial: Case
88
+ currently supports `title`, while Task supports `title` and `instructions`.
89
+
90
+ `epismo record list` is a cross-Case activity feed constrained by current Case ACLs.
91
+ Use `--case-id`, `--task-id`, `--created-by`, `--kinds`, `--origins`, or `--acl` to
92
+ narrow it; these filters never grant access. Results default to newest first, and
93
+ `--order asc` walks a timeline forward.
94
+
95
+ ## Playbook references
93
96
 
94
- - `--input <json>`, `--input @file.json`, or `--input -` (stdin) supply structured input; explicit flags override fields from `--input`, and enum-like flags are validated locally.
95
- - Success is JSON on `stdout`; errors and warnings are JSON on `stderr`:
97
+ The CLI's REST commands currently use UUIDs for Playbook path arguments. Aliases are
98
+ managed with `epismo alias`; MCP exposes the same alias set/list/delete operations and
99
+ also accepts `pb:alias` and `pb:handle/alias` in `epismo_playbook_get`.
100
+
101
+ ## Output contract
102
+
103
+ - Success: JSON on stdout and exit code `0`.
104
+ - Failure: JSON on stderr and a non-zero exit code.
105
+ - Non-interactive commands fail instead of waiting for an unavailable prompt.
96
106
 
97
107
  ```json
98
- { "error": { "code": "NOT_FOUND", "message": "...", "retryable": false, "hint": "..." } }
108
+ { "error": { "code": "NOT_FOUND", "message": "...", "retryable": false } }
99
109
  ```
100
110
 
101
- ## Update Checks
102
-
103
- Update checks run once per day in interactive sessions.
104
- Set `EPISMO_UPDATE_CHECK=0` to disable the background npm version check.
111
+ Set `EPISMO_UPDATE_CHECK=0` to disable the daily interactive npm update check.
package/dist/cases.js ADDED
@@ -0,0 +1,25 @@
1
+ import { requestJson, withWorkspaceQuery } from "./api.js";
2
+ import { resolveApiUrl } from "./config.js";
3
+ import { toObjectInput } from "./input.js";
4
+ import { withIdempotency, withQuery } from "./playbooks.js";
5
+ const part = (value) => encodeURIComponent(value);
6
+ async function call(context, pathname, method = "GET", body) {
7
+ return requestJson(resolveApiUrl(), withWorkspaceQuery(pathname, context.workspaceId), { method, authToken: context.auth.accessToken, ...(body === undefined ? {} : { body }) });
8
+ }
9
+ export const caseApi = {
10
+ start: (c, input) => call(c, "/v1/cases", "POST", withIdempotency(input)),
11
+ get: (c, id) => call(c, `/v1/cases/${part(id)}`),
12
+ list: (c, input) => call(c, withQuery("/v1/cases", input)),
13
+ assign: (c, id, input) => call(c, `/v1/cases/${part(id)}/assignee`, "PATCH", withIdempotency(input)),
14
+ updateAcl: (c, id, input) => call(c, `/v1/cases/${part(id)}/acl`, "PATCH", withIdempotency(input)),
15
+ update: (c, id, input) => call(c, `/v1/cases/${part(id)}`, "PATCH", withIdempotency(input)),
16
+ createTask: (c, id, input) => call(c, "/v1/tasks", "POST", withIdempotency({ ...toObjectInput(input), caseId: id })),
17
+ listTasks: (c, input) => call(c, withQuery("/v1/tasks", input)),
18
+ assignTask: (c, taskId, input) => call(c, `/v1/tasks/${part(taskId)}/assignee`, "PATCH", withIdempotency(input)),
19
+ closeTask: (c, taskId, input) => call(c, `/v1/tasks/${part(taskId)}/close`, "POST", withIdempotency(input)),
20
+ reopenTask: (c, taskId, input) => call(c, `/v1/tasks/${part(taskId)}/reopen`, "POST", withIdempotency(input)),
21
+ updateTask: (c, taskId, input) => call(c, `/v1/tasks/${part(taskId)}`, "PATCH", withIdempotency(input)),
22
+ close: (c, id, input) => call(c, `/v1/cases/${part(id)}/close`, "POST", withIdempotency(input)),
23
+ reopen: (c, id, input) => call(c, `/v1/cases/${part(id)}/reopen`, "POST", withIdempotency(input))
24
+ };
25
+ //# sourceMappingURL=cases.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cases.js","sourceRoot":"","sources":["../src/cases.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,393 @@
1
+ import { Option } from "commander";
2
+ import { parseJsonArrayOption, parseJsonObjectOption, parseStringArrayInput } from "./input.js";
3
+ import { printJson } from "./output.js";
4
+ import { playbookApi } from "./playbooks.js";
5
+ import { caseApi } from "./cases.js";
6
+ import { suggestionApi } from "./suggestions.js";
7
+ import { recordApi } from "./records.js";
8
+ const number = (value) => Number(value);
9
+ function input(command) {
10
+ return command.option("--input <input>", "JSON object, @file, or - for stdin");
11
+ }
12
+ function paging(command) {
13
+ return command
14
+ .option("--page-size <number>", "results per page (1-100)", number)
15
+ .option("--cursor <cursor>", "cursor returned by the previous page");
16
+ }
17
+ function mutation(command) {
18
+ return input(command).option("--idempotency-key <uuid>", "retry key; generated automatically when omitted");
19
+ }
20
+ async function execute(deps, options, overrides, action) {
21
+ const payload = await deps.resolveInput(options, overrides);
22
+ printJson(await action(await deps.resolveContext(), payload));
23
+ }
24
+ function commonPage(options) {
25
+ return { pageSize: options.pageSize, cursor: options.cursor };
26
+ }
27
+ export function registerPlaybookProgram(program, deps) {
28
+ const playbook = program.command("playbook").description("manage reusable Playbooks");
29
+ paging(input(playbook.command("search").description("search readable Playbooks")))
30
+ .option("--query <text>", "full-text query")
31
+ .addOption(new Option("--category <category>").choices([
32
+ "productivity",
33
+ "programming",
34
+ "design",
35
+ "sales",
36
+ "marketing",
37
+ "operations",
38
+ "learning"
39
+ ]))
40
+ .option("--filter-expression <expression>", "search projection filter")
41
+ .option("--aggregations <json>", "JSON array of aggregation options")
42
+ .action(async (options) => execute(deps, options, {
43
+ query: options.query,
44
+ category: options.category,
45
+ ...commonPage(options),
46
+ filterExpression: options.filterExpression,
47
+ aggregationOptions: parseJsonArrayOption(options.aggregations, "--aggregations")
48
+ }, playbookApi.search));
49
+ mutation(playbook.command("create").description("create a Playbook and its first Version"))
50
+ .option("--owner-id <uuid>", "owner Account ID")
51
+ .option("--acl <principals>", "comma-separated ACL principals")
52
+ .option("--definition <json>", "Playbook Definition JSON object")
53
+ .action(async (options) => execute(deps, options, {
54
+ ownerId: options.ownerId,
55
+ acl: parseStringArrayInput(options.acl, "--acl"),
56
+ definition: parseJsonObjectOption(options.definition, "--definition"),
57
+ idempotencyKey: options.idempotencyKey
58
+ }, playbookApi.create));
59
+ playbook
60
+ .command("get")
61
+ .description("get a Playbook and its latest immutable Version")
62
+ .argument("<playbook-id>")
63
+ .action(async (id) => printJson(await playbookApi.get(await deps.resolveContext(), id)));
64
+ const version = playbook.command("version").description("read and publish immutable Versions");
65
+ paging(version.command("list").argument("<playbook-id>").description("list Versions")).action(async (id, options) => execute(deps, options, commonPage(options), (context, payload) => playbookApi.listVersions(context, id, payload)));
66
+ version
67
+ .command("get")
68
+ .argument("<playbook-id>")
69
+ .argument("<version-id>")
70
+ .description("get one immutable Version")
71
+ .action(async (playbookId, versionId) => printJson(await playbookApi.getVersion(await deps.resolveContext(), playbookId, versionId)));
72
+ mutation(version.command("publish").argument("<playbook-id>").description("publish a Version"))
73
+ .option("--base-version-id <uuid>", "current latest Version ID")
74
+ .option("--definition <json>", "new Playbook Definition JSON object")
75
+ .action(async (id, options) => execute(deps, options, {
76
+ baseVersionId: options.baseVersionId,
77
+ definition: parseJsonObjectOption(options.definition, "--definition"),
78
+ idempotencyKey: options.idempotencyKey
79
+ }, (context, payload) => playbookApi.publish(context, id, payload)));
80
+ mutation(playbook.command("acl").argument("<playbook-id>").description("replace the ACL"))
81
+ .requiredOption("--acl <principals>", "comma-separated ACL principals")
82
+ .action(async (id, options) => execute(deps, options, {
83
+ acl: parseStringArrayInput(options.acl, "--acl"),
84
+ idempotencyKey: options.idempotencyKey
85
+ }, (context, payload) => playbookApi.updateAcl(context, id, payload)));
86
+ for (const [name, description, action] of [
87
+ ["archive", "archive a Playbook", playbookApi.archive],
88
+ ["star", "star a Playbook", playbookApi.star],
89
+ ["unstar", "remove a Playbook star", playbookApi.unstar]
90
+ ]) {
91
+ mutation(playbook.command(name).argument("<playbook-id>").description(description)).action(async (id, options) => execute(deps, options, { idempotencyKey: options.idempotencyKey }, (context, payload) => action(context, id, payload)));
92
+ }
93
+ paging(playbook.command("starred").description("list your starred Playbooks")).action(async (options) => execute(deps, options, commonPage(options), playbookApi.listStarred));
94
+ const share = playbook.command("share").description("create short share URLs");
95
+ mutation(share.command("create").argument("<playbook-id>").description("create a share token"))
96
+ .option("--expires-at <timestamp>", "optional ISO-8601 expiration")
97
+ .action(async (id, options) => execute(deps, options, { expiresAt: options.expiresAt, idempotencyKey: options.idempotencyKey }, (context, payload) => playbookApi.createShare(context, id, payload)));
98
+ registerAliasProgram(program, deps);
99
+ registerCaseProgram(program, deps);
100
+ registerTaskProgram(program, deps);
101
+ registerRecordProgram(program, deps);
102
+ registerSuggestionProgram(program, deps);
103
+ }
104
+ function registerAliasProgram(program, deps) {
105
+ const alias = program.command("alias").description("manage Playbook aliases");
106
+ mutation(alias
107
+ .command("set")
108
+ .argument("<alias>")
109
+ .argument("<playbook-id>")
110
+ .description("point an alias at a Playbook, replacing any existing target"))
111
+ .requiredOption("--owner-id <uuid>", "alias namespace owner Account ID")
112
+ .action(async (name, playbookId, options) => execute(deps, options, {
113
+ ownerId: options.ownerId,
114
+ alias: name,
115
+ playbookId,
116
+ idempotencyKey: options.idempotencyKey
117
+ }, playbookApi.setAlias));
118
+ paging(input(alias.command("list").description("list aliases by namespace or Playbook")))
119
+ .option("--owner-id <uuid>", "alias namespace owner Account ID")
120
+ .option("--playbook-id <uuid>", "only aliases pointing at this Playbook")
121
+ .action(async (options) => execute(deps, options, {
122
+ ownerId: options.ownerId,
123
+ playbookId: options.playbookId,
124
+ ...commonPage(options)
125
+ }, playbookApi.listAliases));
126
+ mutation(alias
127
+ .command("delete")
128
+ .argument("<alias>")
129
+ .description("delete an alias without touching the Playbook it pointed at"))
130
+ .requiredOption("--owner-id <uuid>", "alias namespace owner Account ID")
131
+ .action(async (name, options) => execute(deps, options, { ownerId: options.ownerId, idempotencyKey: options.idempotencyKey }, (context, payload) => playbookApi.deleteAlias(context, name, payload)));
132
+ }
133
+ function registerCaseProgram(program, deps) {
134
+ const cases = program.command("case").description("start, assign, and close Cases");
135
+ mutation(cases.command("start").description("start a Playbook or ad-hoc Case"))
136
+ .option("--version-id <uuid>")
137
+ .option("--title <title>")
138
+ .option("--case-input <json>", "Case input JSON object")
139
+ .option("--acl <principals>", "comma-separated ACL principals")
140
+ .action(async (options) => execute(deps, options, {
141
+ versionId: options.versionId,
142
+ title: options.title,
143
+ input: parseJsonObjectOption(options.caseInput, "--case-input"),
144
+ acl: parseStringArrayInput(options.acl, "--acl"),
145
+ idempotencyKey: options.idempotencyKey
146
+ }, caseApi.start));
147
+ cases
148
+ .command("get")
149
+ .argument("<case-id>")
150
+ .description("get a Case with its status, ACL, and lock version")
151
+ .action(async (id) => printJson(await caseApi.get(await deps.resolveContext(), id)));
152
+ paging(input(cases.command("list").description("list readable Cases")))
153
+ .option("--assigned-to <uuid>", "only Cases assigned to this account")
154
+ .addOption(new Option("--status <status>").choices(["open", "closed"]))
155
+ .action(async (options) => execute(deps, options, { assignedTo: options.assignedTo, status: options.status, ...commonPage(options) }, caseApi.list));
156
+ mutation(cases
157
+ .command("assign")
158
+ .argument("<case-id>")
159
+ .description("hand overall responsibility for a Case to a user the ACL already covers"))
160
+ .requiredOption("--assigned-to <uuid>", "account to make responsible for the Case")
161
+ .requiredOption("--lock-version <number>", "expected lock version", number)
162
+ .action(async (id, options) => execute(deps, options, {
163
+ assignedTo: options.assignedTo,
164
+ expectedLockVersion: options.lockVersion,
165
+ idempotencyKey: options.idempotencyKey
166
+ }, (context, payload) => caseApi.assign(context, id, payload)));
167
+ mutation(cases
168
+ .command("acl")
169
+ .argument("<case-id>")
170
+ .description("replace a Case ACL; it must still cover every current assignee"))
171
+ .requiredOption("--acl <principals>", "comma-separated ACL principals")
172
+ .requiredOption("--lock-version <number>", "expected lock version", number)
173
+ .action(async (id, options) => execute(deps, options, {
174
+ acl: parseStringArrayInput(options.acl, "--acl"),
175
+ expectedLockVersion: options.lockVersion,
176
+ idempotencyKey: options.idempotencyKey
177
+ }, (context, payload) => caseApi.updateAcl(context, id, payload)));
178
+ mutation(cases.command("update").argument("<case-id>").description("update editable Case fields"))
179
+ .option("--title <title>")
180
+ .requiredOption("--lock-version <number>", "expected lock version", number)
181
+ .action(async (id, options) => execute(deps, options, {
182
+ title: options.title,
183
+ expectedLockVersion: options.lockVersion,
184
+ idempotencyKey: options.idempotencyKey
185
+ }, (context, payload) => caseApi.update(context, id, payload)));
186
+ mutation(cases
187
+ .command("close")
188
+ .argument("<case-id>")
189
+ .description("close a Case; completed requires no open Tasks, cancelled and abandoned close the rest"))
190
+ .addOption(new Option("--outcome <outcome>").choices(["completed", "cancelled", "abandoned"]))
191
+ .requiredOption("--lock-version <number>", "expected lock version", number)
192
+ .option("--records <json>", "JSON array of final Records")
193
+ .action(async (id, options) => execute(deps, options, {
194
+ outcome: options.outcome,
195
+ expectedLockVersion: options.lockVersion,
196
+ records: parseJsonArrayOption(options.records, "--records"),
197
+ idempotencyKey: options.idempotencyKey
198
+ }, (context, payload) => caseApi.close(context, id, payload)));
199
+ mutation(cases
200
+ .command("reopen")
201
+ .argument("<case-id>")
202
+ .description("reopen a closed Case; its Tasks stay closed until reopened separately"))
203
+ .requiredOption("--lock-version <number>", "expected lock version", number)
204
+ .action(async (id, options) => execute(deps, options, {
205
+ expectedLockVersion: options.lockVersion,
206
+ idempotencyKey: options.idempotencyKey
207
+ }, (context, payload) => caseApi.reopen(context, id, payload)));
208
+ }
209
+ function registerRecordProgram(program, deps) {
210
+ const record = program
211
+ .command("record")
212
+ .description("append Records and browse the ACL-scoped activity feed");
213
+ mutation(record
214
+ .command("append")
215
+ .argument("<case-id>")
216
+ .description("append an immutable Record to an open Case"))
217
+ .option("--task-id <uuid>", "Task this Record belongs to")
218
+ .option("--source-step-id <id>", "four-character Step ID from the Case's pinned Version")
219
+ .option("--kind <kind>", "application-defined Record kind, 1-64 characters")
220
+ .option("--content <text>")
221
+ .option("--data <json>", "Record data JSON object")
222
+ .addOption(new Option("--origin <origin>").choices(["user", "agent"]))
223
+ .option("--client-name <name>")
224
+ .option("--client-version <version>")
225
+ .action(async (id, options) => execute(deps, options, {
226
+ taskId: options.taskId,
227
+ sourceStepId: options.sourceStepId,
228
+ kind: options.kind,
229
+ content: options.content,
230
+ data: parseJsonObjectOption(options.data, "--data"),
231
+ origin: options.origin,
232
+ clientName: options.clientName,
233
+ clientVersion: options.clientVersion,
234
+ idempotencyKey: options.idempotencyKey
235
+ }, (context, payload) => recordApi.append(context, id, payload)));
236
+ paging(input(record
237
+ .command("list")
238
+ .description("browse Records across every readable Case, newest first by default")))
239
+ .option("--case-id <uuid>")
240
+ .option("--task-id <uuid>")
241
+ .option("--created-by <uuid>")
242
+ .option("--kinds <kinds>", "comma-separated Record kinds")
243
+ .option("--origins <origins>", "comma-separated user,agent,system")
244
+ .option("--acl <principals>", "narrow to Cases overlapping these ACL principals")
245
+ .addOption(new Option("--order <order>").choices(["asc", "desc"]).default("desc"))
246
+ .action(async (options) => execute(deps, options, {
247
+ caseId: options.caseId,
248
+ taskId: options.taskId,
249
+ createdBy: options.createdBy,
250
+ kinds: parseStringArrayInput(options.kinds, "--kinds"),
251
+ origins: parseStringArrayInput(options.origins, "--origins"),
252
+ acl: parseStringArrayInput(options.acl, "--acl"),
253
+ order: options.order,
254
+ ...commonPage(options)
255
+ }, recordApi.list));
256
+ }
257
+ function registerTaskProgram(program, deps) {
258
+ const task = program.command("task").description("manage materialized Case Tasks");
259
+ mutation(task
260
+ .command("create")
261
+ .argument("<case-id>")
262
+ .description("create a work or review Task when the work needs its own owner"))
263
+ .addOption(new Option("--kind <kind>").choices(["work", "review"]))
264
+ .option("--title <title>")
265
+ .option("--instructions <text>")
266
+ .option("--source-step-id <id>", "four-character Step ID from the Case's pinned Version")
267
+ .option("--assigned-to <uuid>", "assignee the Case ACL already covers; required for review")
268
+ .option("--subject-record-id <uuid>", "Record under review; review Tasks only")
269
+ .action(async (id, options) => execute(deps, options, {
270
+ kind: options.kind,
271
+ title: options.title,
272
+ instructions: options.instructions,
273
+ sourceStepId: options.sourceStepId,
274
+ assignedTo: options.assignedTo,
275
+ subjectRecordId: options.subjectRecordId,
276
+ idempotencyKey: options.idempotencyKey
277
+ }, (context, payload) => caseApi.createTask(context, id, payload)));
278
+ paging(input(task
279
+ .command("list")
280
+ .description("list Tasks in a Case, or your Tasks across Cases with --assigned-to")))
281
+ .option("--case-id <uuid>")
282
+ .option("--assigned-to <uuid>", "required when --case-id is omitted")
283
+ .addOption(new Option("--status <status>").choices(["open", "closed"]))
284
+ .action(async (options) => execute(deps, options, {
285
+ caseId: options.caseId,
286
+ assignedTo: options.assignedTo,
287
+ status: options.status,
288
+ ...commonPage(options)
289
+ }, caseApi.listTasks));
290
+ mutation(task
291
+ .command("assign")
292
+ .argument("<task-id>")
293
+ .description("assign a Task to a user the Case ACL already covers"))
294
+ .option("--assigned-to <uuid>", "omit in --input to clear assignment")
295
+ .requiredOption("--lock-version <number>", "expected lock version", number)
296
+ .action(async (taskId, options) => execute(deps, options, {
297
+ assignedTo: options.assignedTo,
298
+ expectedLockVersion: options.lockVersion,
299
+ idempotencyKey: options.idempotencyKey
300
+ }, (context, payload) => caseApi.assignTask(context, taskId, payload)));
301
+ mutation(task
302
+ .command("update")
303
+ .argument("<task-id>")
304
+ .description("update editable Task fields while the Task and Case are open"))
305
+ .option("--title <title>")
306
+ .option("--instructions <text>")
307
+ .requiredOption("--lock-version <number>", "expected lock version", number)
308
+ .action(async (taskId, options) => execute(deps, options, {
309
+ title: options.title,
310
+ instructions: options.instructions,
311
+ expectedLockVersion: options.lockVersion,
312
+ idempotencyKey: options.idempotencyKey
313
+ }, (context, payload) => caseApi.updateTask(context, taskId, payload)));
314
+ mutation(task
315
+ .command("close")
316
+ .argument("<task-id>")
317
+ .description("close a Task with an outcome, optionally appending its final Records"))
318
+ .requiredOption("--outcome <outcome>", "free-text result of the Task")
319
+ .requiredOption("--lock-version <number>", "expected lock version", number)
320
+ .option("--records <json>", "JSON array of final Records")
321
+ .action(async (taskId, options) => execute(deps, options, {
322
+ outcome: options.outcome,
323
+ expectedLockVersion: options.lockVersion,
324
+ records: parseJsonArrayOption(options.records, "--records"),
325
+ idempotencyKey: options.idempotencyKey
326
+ }, (context, payload) => caseApi.closeTask(context, taskId, payload)));
327
+ mutation(task
328
+ .command("reopen")
329
+ .argument("<task-id>")
330
+ .description("reopen a closed Task while its parent Case is still open"))
331
+ .requiredOption("--lock-version <number>", "expected lock version", number)
332
+ .action(async (taskId, options) => execute(deps, options, {
333
+ expectedLockVersion: options.lockVersion,
334
+ idempotencyKey: options.idempotencyKey
335
+ }, (context, payload) => caseApi.reopenTask(context, taskId, payload)));
336
+ }
337
+ function registerSuggestionProgram(program, deps) {
338
+ const suggestion = program.command("suggestion").description("manage Playbook Suggestions");
339
+ mutation(suggestion
340
+ .command("create")
341
+ .description("propose a change against an immutable base Version"))
342
+ .option("--playbook-id <uuid>")
343
+ .option("--base-version-id <uuid>", "Version the proposal was written against")
344
+ .option("--target-step-id <id>", "four-character Step ID within the base Version")
345
+ .option("--title <title>")
346
+ .option("--content <text>")
347
+ .action(async (options) => execute(deps, options, {
348
+ playbookId: options.playbookId,
349
+ baseVersionId: options.baseVersionId,
350
+ targetStepId: options.targetStepId,
351
+ title: options.title,
352
+ content: options.content,
353
+ idempotencyKey: options.idempotencyKey
354
+ }, suggestionApi.create));
355
+ suggestion
356
+ .command("get")
357
+ .argument("<suggestion-id>")
358
+ .description("get a Suggestion and its resolution state")
359
+ .action(async (id) => printJson(await suggestionApi.get(await deps.resolveContext(), id)));
360
+ paging(input(suggestion.command("list").description("list readable Suggestions")))
361
+ .option("--playbook-id <uuid>")
362
+ .option("--author-id <uuid>")
363
+ .option("--statuses <statuses>", "comma-separated open,applied,declined,archived")
364
+ .action(async (options) => execute(deps, options, {
365
+ playbookId: options.playbookId,
366
+ authorId: options.authorId,
367
+ statuses: parseStringArrayInput(options.statuses, "--statuses"),
368
+ ...commonPage(options)
369
+ }, suggestionApi.list));
370
+ mutation(suggestion
371
+ .command("update")
372
+ .argument("<suggestion-id>")
373
+ .description("edit your own open Suggestion"))
374
+ .option("--title <title>")
375
+ .option("--content <text>")
376
+ .action(async (id, options) => execute(deps, options, {
377
+ title: options.title,
378
+ content: options.content,
379
+ idempotencyKey: options.idempotencyKey
380
+ }, (context, payload) => suggestionApi.update(context, id, payload)));
381
+ mutation(suggestion
382
+ .command("resolve")
383
+ .argument("<suggestion-id>")
384
+ .description("apply, decline, archive, or reopen a Suggestion"))
385
+ .addOption(new Option("--status <status>").choices(["open", "applied", "declined", "archived"]))
386
+ .option("--result-version-id <uuid>", "Version published from it; required for applied")
387
+ .action(async (id, options) => execute(deps, options, {
388
+ status: options.status,
389
+ resultVersionId: options.resultVersionId,
390
+ idempotencyKey: options.idempotencyKey
391
+ }, (context, payload) => suggestionApi.resolve(context, id, payload)));
392
+ }
393
+ //# sourceMappingURL=playbook-program.js.map