@danypops/papyrus 0.44.9 → 0.44.11
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/package.json +3 -2
- package/src/artifact/artifact.ts +23 -0
- package/src/cli/artifact-command.ts +210 -0
- package/src/cli/discuss-command.ts +256 -0
- package/src/cli/docs-command.ts +192 -0
- package/src/cli/gates-command.ts +46 -0
- package/src/cli/graph-command.ts +171 -0
- package/src/cli/graph-projection-command.ts +79 -0
- package/src/cli/log-command.ts +101 -0
- package/src/cli/migration-command.ts +40 -0
- package/src/cli/note-command.ts +186 -0
- package/src/cli/playbooks-command.ts +313 -0
- package/src/cli/rules-command.ts +220 -0
- package/src/cli/session-identity-command.ts +58 -0
- package/src/cli/shared.ts +5 -0
- package/src/cli/stricli-run.ts +30 -0
- package/src/cli/task-command.ts +892 -0
- package/src/cli.ts +30 -1912
- package/src/handlers/discuss.ts +22 -20
- package/src/handlers/docs.ts +4 -30
- package/src/handlers/notes.ts +2 -29
- package/src/handlers/playbooks.ts +27 -92
- package/src/handlers/rules.ts +4 -30
- package/src/handlers/shared.ts +92 -1
- package/src/handlers/tasks.ts +31 -118
- package/src/modules/discuss.ts +5 -26
- package/src/modules/docs.ts +6 -23
- package/src/modules/graph-projection.ts +1 -8
- package/src/modules/logs.ts +1 -22
- package/src/modules/notes.ts +1 -22
- package/src/modules/operation-input.ts +42 -0
- package/src/modules/playbooks.ts +6 -23
- package/src/modules/rules.ts +6 -23
- package/src/modules/session-identity.ts +1 -15
- package/src/modules/tasks.ts +5 -33
- package/src/service.ts +1 -28
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danypops/papyrus",
|
|
3
|
-
"version": "0.44.
|
|
3
|
+
"version": "0.44.11",
|
|
4
4
|
"description": "Daemon-backed graph artifacts, evidence-bearing tasks, rules, skills, and native TUI workflows for Pi",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": ["pi-package"],
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@danypops/vehicle-client": "^0.6.1",
|
|
38
38
|
"@danypops/vehicle-core": "^0.12.3",
|
|
39
|
-
"@danypops/vehicle-server": "^0.18.1"
|
|
39
|
+
"@danypops/vehicle-server": "^0.18.1",
|
|
40
|
+
"@stricli/core": "^1.3.0"
|
|
40
41
|
}
|
|
41
42
|
}
|
package/src/artifact/artifact.ts
CHANGED
|
@@ -20,6 +20,29 @@ export interface Artifact {
|
|
|
20
20
|
edges?: ArtifactEdge[];
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* A list operation's default projection: everything needed to identify, browse, and pick
|
|
25
|
+
* one artifact out of many, without its body/extra -- the same fields show()'s own full
|
|
26
|
+
* Artifact carries minus the two that make listing dozens of rows as expensive as showing
|
|
27
|
+
* each one individually (a Playbook's full runbook body, a Rule's condition/action text).
|
|
28
|
+
*/
|
|
29
|
+
export interface ArtifactSummary {
|
|
30
|
+
id: string;
|
|
31
|
+
kind: string;
|
|
32
|
+
title: string;
|
|
33
|
+
status: string;
|
|
34
|
+
subtype: string;
|
|
35
|
+
labels: string[];
|
|
36
|
+
created_at: string;
|
|
37
|
+
updated_at: string;
|
|
38
|
+
alias: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function summarizeArtifact(artifact: Artifact): ArtifactSummary {
|
|
42
|
+
const { id, kind, title, status, subtype, labels, created_at, updated_at, alias } = artifact;
|
|
43
|
+
return { id, kind, title, status, subtype, labels, created_at, updated_at, alias };
|
|
44
|
+
}
|
|
45
|
+
|
|
23
46
|
export interface CreateArtifactInput {
|
|
24
47
|
kind?: string;
|
|
25
48
|
title?: string;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import type { CommandContext } from "@stricli/core";
|
|
2
|
+
import { buildApplication, buildCommand, buildRouteMap, numberParser } from "@stricli/core";
|
|
3
|
+
import type { PapyrusClient } from "../client.ts";
|
|
4
|
+
import { artifactLabel, type CliArtifact } from "./shared.ts";
|
|
5
|
+
import { runStricliToString } from "./stricli-run.ts";
|
|
6
|
+
|
|
7
|
+
type ArtifactClient = Pick<PapyrusClient, "call">;
|
|
8
|
+
|
|
9
|
+
interface ArtifactContext extends CommandContext {
|
|
10
|
+
readonly client: ArtifactClient;
|
|
11
|
+
readonly json: boolean;
|
|
12
|
+
readonly projectRoot: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parseStringArray(value: string): string[] {
|
|
16
|
+
const parsed = JSON.parse(value) as unknown;
|
|
17
|
+
if (!Array.isArray(parsed) || parsed.some((entry) => typeof entry !== "string")) throw new Error("value must be a JSON string array");
|
|
18
|
+
return parsed as string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function parseObject(value: string): Record<string, unknown> {
|
|
22
|
+
const parsed = JSON.parse(value) as unknown;
|
|
23
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("value must be a JSON object");
|
|
24
|
+
return parsed as Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function render(this: ArtifactContext, result: unknown, human: string): void {
|
|
28
|
+
this.process.stdout.write(this.json ? JSON.stringify(result) : human);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const createCommand = buildCommand({
|
|
32
|
+
func: async function (
|
|
33
|
+
this: ArtifactContext,
|
|
34
|
+
flags: {
|
|
35
|
+
kind?: string;
|
|
36
|
+
title?: string;
|
|
37
|
+
body?: string;
|
|
38
|
+
status?: string;
|
|
39
|
+
subtype?: string;
|
|
40
|
+
labelsJson?: string[];
|
|
41
|
+
extraJson?: Record<string, unknown>;
|
|
42
|
+
templateId?: string;
|
|
43
|
+
},
|
|
44
|
+
) {
|
|
45
|
+
if (!flags.kind && !flags.templateId) throw new Error("artifact create requires --kind (or --template-id)");
|
|
46
|
+
const artifact = await this.client.call<Record<string, unknown>, CliArtifact>("artifact.create", {
|
|
47
|
+
kind: flags.kind,
|
|
48
|
+
title: flags.title,
|
|
49
|
+
body: flags.body,
|
|
50
|
+
status: flags.status,
|
|
51
|
+
subtype: flags.subtype,
|
|
52
|
+
labels: flags.labelsJson,
|
|
53
|
+
extra: flags.extraJson,
|
|
54
|
+
template_id: flags.templateId,
|
|
55
|
+
...(flags.kind === "task" ? { project_root: this.projectRoot } : {}),
|
|
56
|
+
});
|
|
57
|
+
render.call(this, artifact, `Created: ${artifactLabel(artifact)}`);
|
|
58
|
+
},
|
|
59
|
+
parameters: {
|
|
60
|
+
flags: {
|
|
61
|
+
kind: { brief: "Artifact kind", kind: "parsed", parse: String, placeholder: "kind", optional: true },
|
|
62
|
+
title: { brief: "Title", kind: "parsed", parse: String, placeholder: "text", optional: true },
|
|
63
|
+
body: { brief: "Body", kind: "parsed", parse: String, placeholder: "text", optional: true },
|
|
64
|
+
status: { brief: "Initial status", kind: "parsed", parse: String, placeholder: "status", optional: true },
|
|
65
|
+
subtype: { brief: "Subtype", kind: "parsed", parse: String, placeholder: "text", optional: true },
|
|
66
|
+
labelsJson: { brief: "JSON string array of labels", kind: "parsed", parse: parseStringArray, placeholder: "json", optional: true },
|
|
67
|
+
extraJson: { brief: "JSON object of extra fields", kind: "parsed", parse: parseObject, placeholder: "json", optional: true },
|
|
68
|
+
templateId: { brief: "Template to instantiate from", kind: "parsed", parse: String, placeholder: "id", optional: true },
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
docs: { brief: "Create an artifact of any kind" },
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const queryCommand = buildCommand({
|
|
75
|
+
func: async function (this: ArtifactContext, flags: { kind?: string; status?: string; text?: string; limit?: number }) {
|
|
76
|
+
const rows = await this.client.call<Record<string, unknown>, CliArtifact[]>("artifact.query", {
|
|
77
|
+
kind: flags.kind,
|
|
78
|
+
status: flags.status,
|
|
79
|
+
text: flags.text,
|
|
80
|
+
limit: flags.limit,
|
|
81
|
+
});
|
|
82
|
+
render.call(this, rows, rows.length === 0 ? "No artifacts found." : rows.map((row) => artifactLabel(row)).join("\n"));
|
|
83
|
+
},
|
|
84
|
+
parameters: {
|
|
85
|
+
flags: {
|
|
86
|
+
kind: { brief: "Filter by kind", kind: "parsed", parse: String, placeholder: "kind", optional: true },
|
|
87
|
+
status: { brief: "Filter by status", kind: "parsed", parse: String, placeholder: "status", optional: true },
|
|
88
|
+
text: { brief: "Substring match against title/body", kind: "parsed", parse: String, placeholder: "text", optional: true },
|
|
89
|
+
limit: { brief: "Maximum artifacts to return", kind: "parsed", parse: numberParser, optional: true },
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
docs: { brief: "Query artifacts across every kind" },
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const showCommand = buildCommand({
|
|
96
|
+
func: async function (this: ArtifactContext, flags: { depth?: number; maxNodes?: number }, id: string) {
|
|
97
|
+
const artifact = await this.client.call<Record<string, unknown>, CliArtifact>("artifact.show", {
|
|
98
|
+
id,
|
|
99
|
+
depth: flags.depth,
|
|
100
|
+
max_nodes: flags.maxNodes,
|
|
101
|
+
});
|
|
102
|
+
render.call(this, artifact, `${artifactLabel(artifact)}\n\n${artifact.body ?? ""}`);
|
|
103
|
+
},
|
|
104
|
+
parameters: {
|
|
105
|
+
flags: {
|
|
106
|
+
depth: { brief: "Edge traversal depth", kind: "parsed", parse: numberParser, optional: true },
|
|
107
|
+
maxNodes: { brief: "Maximum traversed nodes", kind: "parsed", parse: numberParser, optional: true },
|
|
108
|
+
},
|
|
109
|
+
positional: { kind: "tuple", parameters: [{ brief: "Artifact id", parse: String, placeholder: "id" }] },
|
|
110
|
+
},
|
|
111
|
+
docs: { brief: "Show one artifact by id, regardless of kind" },
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const removeCommand = buildCommand({
|
|
115
|
+
func: async function (this: ArtifactContext, flags: { reason?: string }, id: string) {
|
|
116
|
+
const record = await this.client.call<
|
|
117
|
+
Record<string, unknown>,
|
|
118
|
+
{ artifactId: string; trashedAt: string; purgeAfter: string; reason?: string }
|
|
119
|
+
>("artifact.remove", { id, reason: flags.reason });
|
|
120
|
+
render.call(this, record, `Trashed ${record.artifactId}: eligible for purge at ${record.purgeAfter}`);
|
|
121
|
+
},
|
|
122
|
+
parameters: {
|
|
123
|
+
flags: { reason: { brief: "Why this artifact was removed", kind: "parsed", parse: String, placeholder: "text", optional: true } },
|
|
124
|
+
positional: { kind: "tuple", parameters: [{ brief: "Artifact id", parse: String, placeholder: "id" }] },
|
|
125
|
+
},
|
|
126
|
+
docs: { brief: "Move an artifact to a time-gated trash" },
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const removeSubtreeCommand = buildCommand({
|
|
130
|
+
func: async function (this: ArtifactContext, flags: { reason?: string }, id: string) {
|
|
131
|
+
const outcome = await this.client.call<Record<string, unknown>, { removed: string[]; skipped: string[] }>("artifact.remove_subtree", {
|
|
132
|
+
id,
|
|
133
|
+
reason: flags.reason,
|
|
134
|
+
});
|
|
135
|
+
render.call(
|
|
136
|
+
this,
|
|
137
|
+
outcome,
|
|
138
|
+
`Trashed ${outcome.removed.length} artifact(s)${outcome.skipped.length > 0 ? `, skipped ${outcome.skipped.length} already-trashed` : ""}.`,
|
|
139
|
+
);
|
|
140
|
+
},
|
|
141
|
+
parameters: {
|
|
142
|
+
flags: { reason: { brief: "Why this subtree was removed", kind: "parsed", parse: String, placeholder: "text", optional: true } },
|
|
143
|
+
positional: { kind: "tuple", parameters: [{ brief: "Artifact id", parse: String, placeholder: "id" }] },
|
|
144
|
+
},
|
|
145
|
+
docs: { brief: "Trash an artifact and its whole contains subtree" },
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const restoreCommand = buildCommand({
|
|
149
|
+
func: async function (this: ArtifactContext, _flags: Record<string, never>, id: string) {
|
|
150
|
+
const outcome = await this.client.call<Record<string, unknown>, { restored: boolean }>("artifact.restore", { id });
|
|
151
|
+
render.call(this, outcome, outcome.restored ? `Restored ${id}` : `${id} was not trashed`);
|
|
152
|
+
},
|
|
153
|
+
parameters: { flags: {}, positional: { kind: "tuple", parameters: [{ brief: "Artifact id", parse: String, placeholder: "id" }] } },
|
|
154
|
+
docs: { brief: "Restore a trashed artifact" },
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const trashStatusCommand = buildCommand({
|
|
158
|
+
func: async function (this: ArtifactContext, _flags: Record<string, never>, id: string) {
|
|
159
|
+
const record = await this.client.call<
|
|
160
|
+
Record<string, unknown>,
|
|
161
|
+
{ artifactId: string; trashedAt: string; purgeAfter: string; reason?: string } | null
|
|
162
|
+
>("artifact.trash_status", { id });
|
|
163
|
+
render.call(
|
|
164
|
+
this,
|
|
165
|
+
record,
|
|
166
|
+
record ? `${record.artifactId}: trashed at ${record.trashedAt}, purge eligible at ${record.purgeAfter}` : `${id} is not trashed`,
|
|
167
|
+
);
|
|
168
|
+
},
|
|
169
|
+
parameters: { flags: {}, positional: { kind: "tuple", parameters: [{ brief: "Artifact id", parse: String, placeholder: "id" }] } },
|
|
170
|
+
docs: { brief: "Show one artifact's trash status" },
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const trashListCommand = buildCommand({
|
|
174
|
+
func: async function (this: ArtifactContext) {
|
|
175
|
+
const rows = await this.client.call<
|
|
176
|
+
Record<string, unknown>,
|
|
177
|
+
Array<{ artifactId: string; trashedAt: string; purgeAfter: string; reason?: string }>
|
|
178
|
+
>("artifact.trash_list", {});
|
|
179
|
+
render.call(
|
|
180
|
+
this,
|
|
181
|
+
rows,
|
|
182
|
+
rows.length === 0 ? "Trash is empty." : rows.map((row) => `${row.artifactId}: purge eligible at ${row.purgeAfter}`).join("\n"),
|
|
183
|
+
);
|
|
184
|
+
},
|
|
185
|
+
parameters: { flags: {} },
|
|
186
|
+
docs: { brief: "List every trashed artifact" },
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const app = buildApplication(
|
|
190
|
+
buildRouteMap({
|
|
191
|
+
routes: {
|
|
192
|
+
create: createCommand,
|
|
193
|
+
query: queryCommand,
|
|
194
|
+
show: showCommand,
|
|
195
|
+
remove: removeCommand,
|
|
196
|
+
"remove-subtree": removeSubtreeCommand,
|
|
197
|
+
restore: restoreCommand,
|
|
198
|
+
"trash-status": trashStatusCommand,
|
|
199
|
+
"trash-list": trashListCommand,
|
|
200
|
+
},
|
|
201
|
+
docs: { brief: "Artifact operations" },
|
|
202
|
+
}),
|
|
203
|
+
{ name: "artifact", scanner: { caseStyle: "allow-kebab-for-camel" } },
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
export async function runArtifactCli(args: string[], client: ArtifactClient, projectRoot: string = process.cwd()): Promise<string> {
|
|
207
|
+
const json = args.includes("--json");
|
|
208
|
+
const positional = args.filter((arg) => arg !== "--json");
|
|
209
|
+
return runStricliToString(app, positional, { client, json, projectRoot });
|
|
210
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import type { CommandContext } from "@stricli/core";
|
|
2
|
+
import { buildApplication, buildCommand, buildRouteMap, numberParser } from "@stricli/core";
|
|
3
|
+
import type { PapyrusClient } from "../client.ts";
|
|
4
|
+
import type { OperationName } from "../service.ts";
|
|
5
|
+
import { runStricliToString } from "./stricli-run.ts";
|
|
6
|
+
|
|
7
|
+
type DiscussClient = Pick<PapyrusClient, "call">;
|
|
8
|
+
|
|
9
|
+
interface DiscussContext extends CommandContext {
|
|
10
|
+
readonly client: DiscussClient;
|
|
11
|
+
readonly json: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function parseStringArray(value: string): string[] {
|
|
15
|
+
const parsed = JSON.parse(value) as unknown;
|
|
16
|
+
if (!Array.isArray(parsed) || parsed.some((entry) => typeof entry !== "string")) throw new Error("value must be a JSON string array");
|
|
17
|
+
return parsed as string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function render(this: DiscussContext, result: unknown): void {
|
|
21
|
+
this.process.stdout.write(this.json ? JSON.stringify(result) : JSON.stringify(result, null, 2));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function call(this: DiscussContext, operation: OperationName, input: Record<string, unknown>): Promise<void> {
|
|
25
|
+
render.call(this, await this.client.call<Record<string, unknown>, unknown>(operation, input));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const openCommand = buildCommand({
|
|
29
|
+
func: async function (
|
|
30
|
+
this: DiscussContext,
|
|
31
|
+
flags: {
|
|
32
|
+
title: string;
|
|
33
|
+
actor: string;
|
|
34
|
+
content: string;
|
|
35
|
+
body?: string;
|
|
36
|
+
labelsJson?: string[];
|
|
37
|
+
blocksJson?: string[];
|
|
38
|
+
optionsJson?: string[];
|
|
39
|
+
optionsMode?: string;
|
|
40
|
+
optionDescriptionsJson?: string[];
|
|
41
|
+
},
|
|
42
|
+
) {
|
|
43
|
+
await call.call(this, "discuss.open", {
|
|
44
|
+
title: flags.title,
|
|
45
|
+
actor: flags.actor,
|
|
46
|
+
content: flags.content,
|
|
47
|
+
body: flags.body,
|
|
48
|
+
labels: flags.labelsJson,
|
|
49
|
+
blocks_task_ids: flags.blocksJson,
|
|
50
|
+
options: flags.optionsJson,
|
|
51
|
+
options_mode: flags.optionsMode,
|
|
52
|
+
option_descriptions: flags.optionDescriptionsJson,
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
parameters: {
|
|
56
|
+
flags: {
|
|
57
|
+
title: { brief: "Discussion title", kind: "parsed", parse: String, placeholder: "text" },
|
|
58
|
+
actor: { brief: "Actor opening the discussion", kind: "parsed", parse: String, placeholder: "text" },
|
|
59
|
+
content: { brief: "First round's content", kind: "parsed", parse: String, placeholder: "text" },
|
|
60
|
+
body: { brief: "Longer body text", kind: "parsed", parse: String, placeholder: "text", optional: true },
|
|
61
|
+
labelsJson: { brief: "JSON string array of labels", kind: "parsed", parse: parseStringArray, placeholder: "json", optional: true },
|
|
62
|
+
blocksJson: {
|
|
63
|
+
brief: "JSON string array of task ids to block",
|
|
64
|
+
kind: "parsed",
|
|
65
|
+
parse: parseStringArray,
|
|
66
|
+
placeholder: "json",
|
|
67
|
+
optional: true,
|
|
68
|
+
},
|
|
69
|
+
optionsJson: {
|
|
70
|
+
brief: "JSON string array of posed options",
|
|
71
|
+
kind: "parsed",
|
|
72
|
+
parse: parseStringArray,
|
|
73
|
+
placeholder: "json",
|
|
74
|
+
optional: true,
|
|
75
|
+
},
|
|
76
|
+
optionsMode: { brief: "single or multi", kind: "parsed", parse: String, placeholder: "mode", optional: true },
|
|
77
|
+
optionDescriptionsJson: {
|
|
78
|
+
brief: "JSON string array of option descriptions",
|
|
79
|
+
kind: "parsed",
|
|
80
|
+
parse: parseStringArray,
|
|
81
|
+
placeholder: "json",
|
|
82
|
+
optional: true,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
docs: { brief: "Open a new discussion" },
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const replyCommand = buildCommand({
|
|
90
|
+
func: async function (
|
|
91
|
+
this: DiscussContext,
|
|
92
|
+
flags: {
|
|
93
|
+
actor: string;
|
|
94
|
+
content: string;
|
|
95
|
+
selectedJson?: string[];
|
|
96
|
+
optionsJson?: string[];
|
|
97
|
+
optionsMode?: string;
|
|
98
|
+
optionDescriptionsJson?: string[];
|
|
99
|
+
},
|
|
100
|
+
id: string,
|
|
101
|
+
) {
|
|
102
|
+
await call.call(this, "discuss.reply", {
|
|
103
|
+
id,
|
|
104
|
+
actor: flags.actor,
|
|
105
|
+
content: flags.content,
|
|
106
|
+
selected: flags.selectedJson,
|
|
107
|
+
options: flags.optionsJson,
|
|
108
|
+
options_mode: flags.optionsMode,
|
|
109
|
+
option_descriptions: flags.optionDescriptionsJson,
|
|
110
|
+
});
|
|
111
|
+
},
|
|
112
|
+
parameters: {
|
|
113
|
+
flags: {
|
|
114
|
+
actor: { brief: "Actor replying", kind: "parsed", parse: String, placeholder: "text" },
|
|
115
|
+
content: { brief: "Reply content", kind: "parsed", parse: String, placeholder: "text" },
|
|
116
|
+
selectedJson: {
|
|
117
|
+
brief: "JSON string array answering a pending posed choice",
|
|
118
|
+
kind: "parsed",
|
|
119
|
+
parse: parseStringArray,
|
|
120
|
+
placeholder: "json",
|
|
121
|
+
optional: true,
|
|
122
|
+
},
|
|
123
|
+
optionsJson: {
|
|
124
|
+
brief: "JSON string array of posed options",
|
|
125
|
+
kind: "parsed",
|
|
126
|
+
parse: parseStringArray,
|
|
127
|
+
placeholder: "json",
|
|
128
|
+
optional: true,
|
|
129
|
+
},
|
|
130
|
+
optionsMode: { brief: "single or multi", kind: "parsed", parse: String, placeholder: "mode", optional: true },
|
|
131
|
+
optionDescriptionsJson: {
|
|
132
|
+
brief: "JSON string array of option descriptions",
|
|
133
|
+
kind: "parsed",
|
|
134
|
+
parse: parseStringArray,
|
|
135
|
+
placeholder: "json",
|
|
136
|
+
optional: true,
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
positional: { kind: "tuple", parameters: [{ brief: "Discussion id", parse: String, placeholder: "id" }] },
|
|
140
|
+
},
|
|
141
|
+
docs: { brief: "Add a round to an existing discussion" },
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const deferCommand = buildCommand({
|
|
145
|
+
func: async function (this: DiscussContext, flags: { reason?: string }, id: string) {
|
|
146
|
+
await call.call(this, "discuss.defer", { id, reason: flags.reason });
|
|
147
|
+
},
|
|
148
|
+
parameters: {
|
|
149
|
+
flags: {
|
|
150
|
+
reason: { brief: "Why this discussion is being deferred", kind: "parsed", parse: String, placeholder: "text", optional: true },
|
|
151
|
+
},
|
|
152
|
+
positional: { kind: "tuple", parameters: [{ brief: "Discussion id", parse: String, placeholder: "id" }] },
|
|
153
|
+
},
|
|
154
|
+
docs: { brief: "Pause a discussion without settling it" },
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const resumeCommand = buildCommand({
|
|
158
|
+
func: async function (this: DiscussContext, _flags: Record<string, never>, id: string) {
|
|
159
|
+
await call.call(this, "discuss.resume", { id });
|
|
160
|
+
},
|
|
161
|
+
parameters: { flags: {}, positional: { kind: "tuple", parameters: [{ brief: "Discussion id", parse: String, placeholder: "id" }] } },
|
|
162
|
+
docs: { brief: "Resume a deferred discussion" },
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const settleCommand = buildCommand({
|
|
166
|
+
func: async function (this: DiscussContext, flags: { settlement: string }, id: string) {
|
|
167
|
+
await call.call(this, "discuss.settle", { id, settlement: flags.settlement });
|
|
168
|
+
},
|
|
169
|
+
parameters: {
|
|
170
|
+
flags: { settlement: { brief: "Final settlement text", kind: "parsed", parse: String, placeholder: "text" } },
|
|
171
|
+
positional: { kind: "tuple", parameters: [{ brief: "Discussion id", parse: String, placeholder: "id" }] },
|
|
172
|
+
},
|
|
173
|
+
docs: { brief: "Settle a discussion" },
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const blockCommand = buildCommand({
|
|
177
|
+
func: async function (this: DiscussContext, flags: { taskId: string }, id: string) {
|
|
178
|
+
await call.call(this, "discuss.block", { id, task_id: flags.taskId });
|
|
179
|
+
},
|
|
180
|
+
parameters: {
|
|
181
|
+
flags: { taskId: { brief: "Task id to block", kind: "parsed", parse: String, placeholder: "id" } },
|
|
182
|
+
positional: { kind: "tuple", parameters: [{ brief: "Discussion id", parse: String, placeholder: "id" }] },
|
|
183
|
+
},
|
|
184
|
+
docs: { brief: "Block a task's completion on this discussion" },
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const unblockCommand = buildCommand({
|
|
188
|
+
func: async function (this: DiscussContext, flags: { taskId: string }, id: string) {
|
|
189
|
+
await call.call(this, "discuss.unblock", { id, task_id: flags.taskId });
|
|
190
|
+
},
|
|
191
|
+
parameters: {
|
|
192
|
+
flags: { taskId: { brief: "Task id to unblock", kind: "parsed", parse: String, placeholder: "id" } },
|
|
193
|
+
positional: { kind: "tuple", parameters: [{ brief: "Discussion id", parse: String, placeholder: "id" }] },
|
|
194
|
+
},
|
|
195
|
+
docs: { brief: "Remove a discussion's block on a task" },
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const showCommand = buildCommand({
|
|
199
|
+
func: async function (this: DiscussContext, _flags: Record<string, never>, id: string) {
|
|
200
|
+
await call.call(this, "discuss.show", { id });
|
|
201
|
+
},
|
|
202
|
+
parameters: { flags: {}, positional: { kind: "tuple", parameters: [{ brief: "Discussion id", parse: String, placeholder: "id" }] } },
|
|
203
|
+
docs: { brief: "Show a discussion's full transcript" },
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const roundsCommand = buildCommand({
|
|
207
|
+
func: async function (this: DiscussContext, flags: { afterRound?: number; limit?: number }, id: string) {
|
|
208
|
+
await call.call(this, "discuss.rounds", { id, after_round: flags.afterRound, limit: flags.limit });
|
|
209
|
+
},
|
|
210
|
+
parameters: {
|
|
211
|
+
flags: {
|
|
212
|
+
afterRound: { brief: "Only rounds after this number", kind: "parsed", parse: numberParser, optional: true },
|
|
213
|
+
limit: { brief: "Maximum rounds to return", kind: "parsed", parse: numberParser, optional: true },
|
|
214
|
+
},
|
|
215
|
+
positional: { kind: "tuple", parameters: [{ brief: "Discussion id", parse: String, placeholder: "id" }] },
|
|
216
|
+
},
|
|
217
|
+
docs: { brief: "List a discussion's rounds" },
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const listCommand = buildCommand({
|
|
221
|
+
func: async function (this: DiscussContext, flags: { state?: string; limit?: number }) {
|
|
222
|
+
await call.call(this, "discuss.list", { state: flags.state, limit: flags.limit });
|
|
223
|
+
},
|
|
224
|
+
parameters: {
|
|
225
|
+
flags: {
|
|
226
|
+
state: { brief: "Filter to active|deferred|settled", kind: "parsed", parse: String, placeholder: "state", optional: true },
|
|
227
|
+
limit: { brief: "Maximum discussions to return", kind: "parsed", parse: numberParser, optional: true },
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
docs: { brief: "List discussions" },
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const app = buildApplication(
|
|
234
|
+
buildRouteMap({
|
|
235
|
+
routes: {
|
|
236
|
+
open: openCommand,
|
|
237
|
+
reply: replyCommand,
|
|
238
|
+
defer: deferCommand,
|
|
239
|
+
resume: resumeCommand,
|
|
240
|
+
settle: settleCommand,
|
|
241
|
+
block: blockCommand,
|
|
242
|
+
unblock: unblockCommand,
|
|
243
|
+
show: showCommand,
|
|
244
|
+
rounds: roundsCommand,
|
|
245
|
+
list: listCommand,
|
|
246
|
+
},
|
|
247
|
+
docs: { brief: "Discussion operations" },
|
|
248
|
+
}),
|
|
249
|
+
{ name: "discuss", scanner: { caseStyle: "allow-kebab-for-camel" } },
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
export async function runDiscussCli(args: string[], client: DiscussClient): Promise<string> {
|
|
253
|
+
const json = args.includes("--json");
|
|
254
|
+
const positional = args.filter((arg) => arg !== "--json");
|
|
255
|
+
return runStricliToString(app, positional, { client, json });
|
|
256
|
+
}
|