@unbrained/pm-cli 2026.3.9

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.
Files changed (245) hide show
  1. package/.pi/extensions/pm-cli/index.ts +778 -0
  2. package/AGENTS.md +475 -0
  3. package/LICENSE +21 -0
  4. package/PRD.md +1390 -0
  5. package/README.md +655 -0
  6. package/dist/cli/commands/activity.d.ts +14 -0
  7. package/dist/cli/commands/activity.js +80 -0
  8. package/dist/cli/commands/activity.js.map +1 -0
  9. package/dist/cli/commands/append.d.ts +13 -0
  10. package/dist/cli/commands/append.js +46 -0
  11. package/dist/cli/commands/append.js.map +1 -0
  12. package/dist/cli/commands/beads.d.ts +15 -0
  13. package/dist/cli/commands/beads.js +475 -0
  14. package/dist/cli/commands/beads.js.map +1 -0
  15. package/dist/cli/commands/claim.d.ts +19 -0
  16. package/dist/cli/commands/claim.js +79 -0
  17. package/dist/cli/commands/claim.js.map +1 -0
  18. package/dist/cli/commands/close.d.ts +12 -0
  19. package/dist/cli/commands/close.js +58 -0
  20. package/dist/cli/commands/close.js.map +1 -0
  21. package/dist/cli/commands/comments.d.ts +15 -0
  22. package/dist/cli/commands/comments.js +80 -0
  23. package/dist/cli/commands/comments.js.map +1 -0
  24. package/dist/cli/commands/completion.d.ts +10 -0
  25. package/dist/cli/commands/completion.js +469 -0
  26. package/dist/cli/commands/completion.js.map +1 -0
  27. package/dist/cli/commands/config.d.ts +15 -0
  28. package/dist/cli/commands/config.js +72 -0
  29. package/dist/cli/commands/config.js.map +1 -0
  30. package/dist/cli/commands/create.d.ts +60 -0
  31. package/dist/cli/commands/create.js +456 -0
  32. package/dist/cli/commands/create.js.map +1 -0
  33. package/dist/cli/commands/delete.d.ts +12 -0
  34. package/dist/cli/commands/delete.js +33 -0
  35. package/dist/cli/commands/delete.js.map +1 -0
  36. package/dist/cli/commands/docs.d.ts +16 -0
  37. package/dist/cli/commands/docs.js +113 -0
  38. package/dist/cli/commands/docs.js.map +1 -0
  39. package/dist/cli/commands/files.d.ts +17 -0
  40. package/dist/cli/commands/files.js +113 -0
  41. package/dist/cli/commands/files.js.map +1 -0
  42. package/dist/cli/commands/gc.d.ts +9 -0
  43. package/dist/cli/commands/gc.js +80 -0
  44. package/dist/cli/commands/gc.js.map +1 -0
  45. package/dist/cli/commands/get.d.ts +12 -0
  46. package/dist/cli/commands/get.js +28 -0
  47. package/dist/cli/commands/get.js.map +1 -0
  48. package/dist/cli/commands/health.d.ts +15 -0
  49. package/dist/cli/commands/health.js +288 -0
  50. package/dist/cli/commands/health.js.map +1 -0
  51. package/dist/cli/commands/history.d.ts +13 -0
  52. package/dist/cli/commands/history.js +72 -0
  53. package/dist/cli/commands/history.js.map +1 -0
  54. package/dist/cli/commands/index.d.ts +26 -0
  55. package/dist/cli/commands/index.js +27 -0
  56. package/dist/cli/commands/index.js.map +1 -0
  57. package/dist/cli/commands/init.d.ts +10 -0
  58. package/dist/cli/commands/init.js +59 -0
  59. package/dist/cli/commands/init.js.map +1 -0
  60. package/dist/cli/commands/install.d.ts +18 -0
  61. package/dist/cli/commands/install.js +87 -0
  62. package/dist/cli/commands/install.js.map +1 -0
  63. package/dist/cli/commands/list.d.ts +21 -0
  64. package/dist/cli/commands/list.js +137 -0
  65. package/dist/cli/commands/list.js.map +1 -0
  66. package/dist/cli/commands/reindex.d.ts +16 -0
  67. package/dist/cli/commands/reindex.js +154 -0
  68. package/dist/cli/commands/reindex.js.map +1 -0
  69. package/dist/cli/commands/restore.d.ts +20 -0
  70. package/dist/cli/commands/restore.js +208 -0
  71. package/dist/cli/commands/restore.js.map +1 -0
  72. package/dist/cli/commands/search.d.ts +45 -0
  73. package/dist/cli/commands/search.js +531 -0
  74. package/dist/cli/commands/search.js.map +1 -0
  75. package/dist/cli/commands/stats.d.ts +13 -0
  76. package/dist/cli/commands/stats.js +88 -0
  77. package/dist/cli/commands/stats.js.map +1 -0
  78. package/dist/cli/commands/test-all.d.ts +30 -0
  79. package/dist/cli/commands/test-all.js +157 -0
  80. package/dist/cli/commands/test-all.js.map +1 -0
  81. package/dist/cli/commands/test.d.ts +29 -0
  82. package/dist/cli/commands/test.js +492 -0
  83. package/dist/cli/commands/test.js.map +1 -0
  84. package/dist/cli/commands/update.d.ts +52 -0
  85. package/dist/cli/commands/update.js +467 -0
  86. package/dist/cli/commands/update.js.map +1 -0
  87. package/dist/cli/extension-command-options.d.ts +1 -0
  88. package/dist/cli/extension-command-options.js +76 -0
  89. package/dist/cli/extension-command-options.js.map +1 -0
  90. package/dist/cli/main.d.ts +2 -0
  91. package/dist/cli/main.js +1494 -0
  92. package/dist/cli/main.js.map +1 -0
  93. package/dist/cli.d.ts +2 -0
  94. package/dist/cli.js +3 -0
  95. package/dist/cli.js.map +1 -0
  96. package/dist/command-types.d.ts +1 -0
  97. package/dist/command-types.js +2 -0
  98. package/dist/command-types.js.map +1 -0
  99. package/dist/constants.d.ts +1 -0
  100. package/dist/constants.js +2 -0
  101. package/dist/constants.js.map +1 -0
  102. package/dist/core/extensions/builtins.d.ts +3 -0
  103. package/dist/core/extensions/builtins.js +47 -0
  104. package/dist/core/extensions/builtins.js.map +1 -0
  105. package/dist/core/extensions/index.d.ts +13 -0
  106. package/dist/core/extensions/index.js +88 -0
  107. package/dist/core/extensions/index.js.map +1 -0
  108. package/dist/core/extensions/loader.d.ts +301 -0
  109. package/dist/core/extensions/loader.js +917 -0
  110. package/dist/core/extensions/loader.js.map +1 -0
  111. package/dist/core/fs/fs-utils.d.ts +6 -0
  112. package/dist/core/fs/fs-utils.js +58 -0
  113. package/dist/core/fs/fs-utils.js.map +1 -0
  114. package/dist/core/fs/index.d.ts +1 -0
  115. package/dist/core/fs/index.js +2 -0
  116. package/dist/core/fs/index.js.map +1 -0
  117. package/dist/core/history/history.d.ts +12 -0
  118. package/dist/core/history/history.js +44 -0
  119. package/dist/core/history/history.js.map +1 -0
  120. package/dist/core/history/index.d.ts +1 -0
  121. package/dist/core/history/index.js +2 -0
  122. package/dist/core/history/index.js.map +1 -0
  123. package/dist/core/item/id.d.ts +3 -0
  124. package/dist/core/item/id.js +54 -0
  125. package/dist/core/item/id.js.map +1 -0
  126. package/dist/core/item/index.d.ts +3 -0
  127. package/dist/core/item/index.js +4 -0
  128. package/dist/core/item/index.js.map +1 -0
  129. package/dist/core/item/item-format.d.ts +9 -0
  130. package/dist/core/item/item-format.js +363 -0
  131. package/dist/core/item/item-format.js.map +1 -0
  132. package/dist/core/item/parse.d.ts +3 -0
  133. package/dist/core/item/parse.js +72 -0
  134. package/dist/core/item/parse.js.map +1 -0
  135. package/dist/core/lock/index.d.ts +1 -0
  136. package/dist/core/lock/index.js +2 -0
  137. package/dist/core/lock/index.js.map +1 -0
  138. package/dist/core/lock/lock.d.ts +1 -0
  139. package/dist/core/lock/lock.js +100 -0
  140. package/dist/core/lock/lock.js.map +1 -0
  141. package/dist/core/output/output.d.ts +7 -0
  142. package/dist/core/output/output.js +79 -0
  143. package/dist/core/output/output.js.map +1 -0
  144. package/dist/core/search/cache.d.ts +17 -0
  145. package/dist/core/search/cache.js +212 -0
  146. package/dist/core/search/cache.js.map +1 -0
  147. package/dist/core/search/embedding-batches.d.ts +7 -0
  148. package/dist/core/search/embedding-batches.js +54 -0
  149. package/dist/core/search/embedding-batches.js.map +1 -0
  150. package/dist/core/search/providers.d.ts +59 -0
  151. package/dist/core/search/providers.js +265 -0
  152. package/dist/core/search/providers.js.map +1 -0
  153. package/dist/core/search/vector-stores.d.ts +89 -0
  154. package/dist/core/search/vector-stores.js +546 -0
  155. package/dist/core/search/vector-stores.js.map +1 -0
  156. package/dist/core/shared/command-types.d.ts +7 -0
  157. package/dist/core/shared/command-types.js +2 -0
  158. package/dist/core/shared/command-types.js.map +1 -0
  159. package/dist/core/shared/constants.d.ts +19 -0
  160. package/dist/core/shared/constants.js +134 -0
  161. package/dist/core/shared/constants.js.map +1 -0
  162. package/dist/core/shared/errors.d.ts +4 -0
  163. package/dist/core/shared/errors.js +9 -0
  164. package/dist/core/shared/errors.js.map +1 -0
  165. package/dist/core/shared/index.d.ts +3 -0
  166. package/dist/core/shared/index.js +4 -0
  167. package/dist/core/shared/index.js.map +1 -0
  168. package/dist/core/shared/serialization.d.ts +3 -0
  169. package/dist/core/shared/serialization.js +70 -0
  170. package/dist/core/shared/serialization.js.map +1 -0
  171. package/dist/core/shared/time.d.ts +3 -0
  172. package/dist/core/shared/time.js +28 -0
  173. package/dist/core/shared/time.js.map +1 -0
  174. package/dist/core/store/index.d.ts +3 -0
  175. package/dist/core/store/index.js +4 -0
  176. package/dist/core/store/index.js.map +1 -0
  177. package/dist/core/store/item-store.d.ts +42 -0
  178. package/dist/core/store/item-store.js +186 -0
  179. package/dist/core/store/item-store.js.map +1 -0
  180. package/dist/core/store/paths.d.ts +8 -0
  181. package/dist/core/store/paths.js +29 -0
  182. package/dist/core/store/paths.js.map +1 -0
  183. package/dist/core/store/settings.d.ts +4 -0
  184. package/dist/core/store/settings.js +148 -0
  185. package/dist/core/store/settings.js.map +1 -0
  186. package/dist/errors.d.ts +1 -0
  187. package/dist/errors.js +2 -0
  188. package/dist/errors.js.map +1 -0
  189. package/dist/extensions/builtins/beads/index.d.ts +8 -0
  190. package/dist/extensions/builtins/beads/index.js +29 -0
  191. package/dist/extensions/builtins/beads/index.js.map +1 -0
  192. package/dist/extensions/builtins/todos/import-export.d.ts +26 -0
  193. package/dist/extensions/builtins/todos/import-export.js +460 -0
  194. package/dist/extensions/builtins/todos/import-export.js.map +1 -0
  195. package/dist/extensions/builtins/todos/index.d.ts +8 -0
  196. package/dist/extensions/builtins/todos/index.js +38 -0
  197. package/dist/extensions/builtins/todos/index.js.map +1 -0
  198. package/dist/fs-utils.d.ts +1 -0
  199. package/dist/fs-utils.js +2 -0
  200. package/dist/fs-utils.js.map +1 -0
  201. package/dist/history.d.ts +1 -0
  202. package/dist/history.js +2 -0
  203. package/dist/history.js.map +1 -0
  204. package/dist/id.d.ts +1 -0
  205. package/dist/id.js +2 -0
  206. package/dist/id.js.map +1 -0
  207. package/dist/item-format.d.ts +1 -0
  208. package/dist/item-format.js +2 -0
  209. package/dist/item-format.js.map +1 -0
  210. package/dist/item-store.d.ts +1 -0
  211. package/dist/item-store.js +2 -0
  212. package/dist/item-store.js.map +1 -0
  213. package/dist/lock.d.ts +1 -0
  214. package/dist/lock.js +2 -0
  215. package/dist/lock.js.map +1 -0
  216. package/dist/output.d.ts +1 -0
  217. package/dist/output.js +2 -0
  218. package/dist/output.js.map +1 -0
  219. package/dist/parse.d.ts +1 -0
  220. package/dist/parse.js +2 -0
  221. package/dist/parse.js.map +1 -0
  222. package/dist/paths.d.ts +1 -0
  223. package/dist/paths.js +2 -0
  224. package/dist/paths.js.map +1 -0
  225. package/dist/serialization.d.ts +1 -0
  226. package/dist/serialization.js +2 -0
  227. package/dist/serialization.js.map +1 -0
  228. package/dist/settings.d.ts +1 -0
  229. package/dist/settings.js +2 -0
  230. package/dist/settings.js.map +1 -0
  231. package/dist/time.d.ts +1 -0
  232. package/dist/time.js +2 -0
  233. package/dist/time.js.map +1 -0
  234. package/dist/types/index.d.ts +1 -0
  235. package/dist/types/index.js +2 -0
  236. package/dist/types/index.js.map +1 -0
  237. package/dist/types.d.ts +179 -0
  238. package/dist/types.js +21 -0
  239. package/dist/types.js.map +1 -0
  240. package/docs/ARCHITECTURE.md +246 -0
  241. package/docs/EXTENSIONS.md +329 -0
  242. package/docs/RELEASING.md +65 -0
  243. package/package.json +79 -0
  244. package/scripts/install.ps1 +112 -0
  245. package/scripts/install.sh +113 -0
@@ -0,0 +1,778 @@
1
+ import { fileURLToPath } from "node:url";
2
+
3
+ export const PM_TOOL_ACTIONS = [
4
+ "init",
5
+ "config",
6
+ "create",
7
+ "list",
8
+ "list-all",
9
+ "list-draft",
10
+ "list-open",
11
+ "list-in-progress",
12
+ "list-blocked",
13
+ "list-closed",
14
+ "list-canceled",
15
+ "get",
16
+ "search",
17
+ "reindex",
18
+ "history",
19
+ "activity",
20
+ "restore",
21
+ "update",
22
+ "close",
23
+ "delete",
24
+ "append",
25
+ "comments",
26
+ "files",
27
+ "docs",
28
+ "test",
29
+ "test-all",
30
+ "stats",
31
+ "health",
32
+ "gc",
33
+ "completion",
34
+ "claim",
35
+ "release",
36
+ "beads-import",
37
+ "todos-import",
38
+ "todos-export",
39
+ "start-task",
40
+ "pause-task",
41
+ "close-task",
42
+ ] as const;
43
+
44
+ export type PmToolAction = (typeof PM_TOOL_ACTIONS)[number];
45
+ type NumericFlagInput = string | number;
46
+ type BooleanFlagInput = string | number | boolean;
47
+
48
+ const NODE_FALLBACK_CLI_PATH = fileURLToPath(new URL("../../../dist/cli.js", import.meta.url));
49
+
50
+ export interface PmToolParameters {
51
+ action: string;
52
+ json?: boolean;
53
+ quiet?: boolean;
54
+ profile?: boolean;
55
+ noExtensions?: boolean;
56
+ path?: string;
57
+ pmExecutable?: string;
58
+ timeoutMs?: number;
59
+ id?: string;
60
+ target?: string;
61
+ query?: string;
62
+ keywords?: string;
63
+ prefix?: string;
64
+ scope?: string;
65
+ configAction?: string;
66
+ key?: string;
67
+ title?: string;
68
+ description?: string;
69
+ type?: string;
70
+ status?: string;
71
+ priority?: NumericFlagInput;
72
+ tags?: string;
73
+ body?: string;
74
+ deadline?: string;
75
+ estimate?: NumericFlagInput;
76
+ acceptanceCriteria?: string;
77
+ author?: string;
78
+ message?: string;
79
+ assignee?: string;
80
+ parent?: string;
81
+ reviewer?: string;
82
+ risk?: string;
83
+ confidence?: NumericFlagInput | string;
84
+ sprint?: string;
85
+ release?: string;
86
+ blockedBy?: string;
87
+ blockedReason?: string;
88
+ unblockNote?: string;
89
+ reporter?: string;
90
+ severity?: string;
91
+ environment?: string;
92
+ reproSteps?: string;
93
+ resolution?: string;
94
+ expectedResult?: string;
95
+ actualResult?: string;
96
+ affectedVersion?: string;
97
+ fixedVersion?: string;
98
+ component?: string;
99
+ regression?: BooleanFlagInput;
100
+ customerImpact?: string;
101
+ definitionOfReady?: string;
102
+ order?: NumericFlagInput;
103
+ goal?: string;
104
+ objective?: string;
105
+ value?: string;
106
+ impact?: string;
107
+ outcome?: string;
108
+ whyNow?: string;
109
+ mode?: string;
110
+ includeLinked?: boolean;
111
+ tag?: string;
112
+ deadlineBefore?: string;
113
+ deadlineAfter?: string;
114
+ limit?: NumericFlagInput;
115
+ timeout?: NumericFlagInput;
116
+ force?: boolean;
117
+ run?: boolean;
118
+ shell?: string;
119
+ file?: string;
120
+ folder?: string;
121
+ text?: string;
122
+ add?: string[];
123
+ remove?: string[];
124
+ dep?: string[];
125
+ comment?: string[];
126
+ note?: string[];
127
+ learning?: string[];
128
+ linkedFile?: string[];
129
+ linkedTest?: string[];
130
+ doc?: string[];
131
+ criterion?: string[];
132
+ }
133
+
134
+ export interface PiExecResult {
135
+ stdout?: string;
136
+ stderr?: string;
137
+ code?: number | null;
138
+ killed?: boolean;
139
+ }
140
+
141
+ export interface PiToolResultEnvelope {
142
+ content: Array<{ type: "text"; text: string }>;
143
+ details: Record<string, unknown>;
144
+ isError?: boolean;
145
+ }
146
+
147
+ export interface PiToolDefinition {
148
+ name: string;
149
+ label: string;
150
+ description: string;
151
+ parameters: Record<string, unknown>;
152
+ execute: (
153
+ toolCallId: string,
154
+ params: PmToolParameters,
155
+ signal?: AbortSignal,
156
+ onUpdate?: (update: { content?: Array<{ type: "text"; text: string }>; details?: unknown }) => void,
157
+ ) => Promise<PiToolResultEnvelope>;
158
+ }
159
+
160
+ export interface PiExtensionApi {
161
+ registerTool(definition: PiToolDefinition): void;
162
+ exec(command: string, args: string[], options?: { signal?: AbortSignal; timeout?: number }): Promise<PiExecResult>;
163
+ }
164
+
165
+ export const PM_TOOL_PARAMETERS_SCHEMA: Record<string, unknown> = {
166
+ type: "object",
167
+ additionalProperties: false,
168
+ required: ["action"],
169
+ properties: {
170
+ action: { type: "string", enum: [...PM_TOOL_ACTIONS] },
171
+ json: { type: "boolean", default: true },
172
+ quiet: { type: "boolean" },
173
+ profile: { type: "boolean" },
174
+ noExtensions: { type: "boolean" },
175
+ path: { type: "string" },
176
+ pmExecutable: { type: "string" },
177
+ timeoutMs: { type: "number" },
178
+ id: { type: "string" },
179
+ target: { type: "string" },
180
+ query: { type: "string" },
181
+ keywords: { type: "string" },
182
+ prefix: { type: "string" },
183
+ scope: { type: "string" },
184
+ configAction: { type: "string" },
185
+ key: { type: "string" },
186
+ title: { type: "string" },
187
+ description: { type: "string" },
188
+ type: { type: "string" },
189
+ status: { type: "string" },
190
+ priority: { anyOf: [{ type: "string" }, { type: "number" }] },
191
+ tags: { type: "string" },
192
+ body: { type: "string" },
193
+ deadline: { type: "string" },
194
+ estimate: { anyOf: [{ type: "string" }, { type: "number" }] },
195
+ acceptanceCriteria: { type: "string" },
196
+ author: { type: "string" },
197
+ message: { type: "string" },
198
+ assignee: { type: "string" },
199
+ parent: { type: "string" },
200
+ reviewer: { type: "string" },
201
+ risk: { type: "string" },
202
+ confidence: { anyOf: [{ type: "string" }, { type: "number" }] },
203
+ sprint: { type: "string" },
204
+ release: { type: "string" },
205
+ blockedBy: { type: "string" },
206
+ blockedReason: { type: "string" },
207
+ unblockNote: { type: "string" },
208
+ reporter: { type: "string" },
209
+ severity: { type: "string" },
210
+ environment: { type: "string" },
211
+ reproSteps: { type: "string" },
212
+ resolution: { type: "string" },
213
+ expectedResult: { type: "string" },
214
+ actualResult: { type: "string" },
215
+ affectedVersion: { type: "string" },
216
+ fixedVersion: { type: "string" },
217
+ component: { type: "string" },
218
+ regression: { anyOf: [{ type: "boolean" }, { type: "string" }, { type: "number" }] },
219
+ customerImpact: { type: "string" },
220
+ definitionOfReady: { type: "string" },
221
+ order: { anyOf: [{ type: "string" }, { type: "number" }] },
222
+ goal: { type: "string" },
223
+ objective: { type: "string" },
224
+ value: { type: "string" },
225
+ impact: { type: "string" },
226
+ outcome: { type: "string" },
227
+ whyNow: { type: "string" },
228
+ mode: { type: "string" },
229
+ includeLinked: { type: "boolean" },
230
+ tag: { type: "string" },
231
+ deadlineBefore: { type: "string" },
232
+ deadlineAfter: { type: "string" },
233
+ limit: { anyOf: [{ type: "string" }, { type: "number" }] },
234
+ timeout: { anyOf: [{ type: "string" }, { type: "number" }] },
235
+ force: { type: "boolean" },
236
+ run: { type: "boolean" },
237
+ shell: { type: "string" },
238
+ file: { type: "string" },
239
+ folder: { type: "string" },
240
+ text: { type: "string" },
241
+ add: { type: "array", items: { type: "string" } },
242
+ remove: { type: "array", items: { type: "string" } },
243
+ dep: { type: "array", items: { type: "string" } },
244
+ comment: { type: "array", items: { type: "string" } },
245
+ note: { type: "array", items: { type: "string" } },
246
+ learning: { type: "array", items: { type: "string" } },
247
+ linkedFile: { type: "array", items: { type: "string" } },
248
+ linkedTest: { type: "array", items: { type: "string" } },
249
+ doc: { type: "array", items: { type: "string" } },
250
+ criterion: { type: "array", items: { type: "string" } },
251
+ },
252
+ };
253
+
254
+ function isPmToolAction(action: string): action is PmToolAction {
255
+ return (PM_TOOL_ACTIONS as readonly string[]).includes(action);
256
+ }
257
+
258
+ function pushOption(args: string[], flag: string, value: string | number | undefined, allowEmpty = false): void {
259
+ if (typeof value === "number") {
260
+ if (!Number.isFinite(value)) {
261
+ return;
262
+ }
263
+ args.push(flag, String(value));
264
+ return;
265
+ }
266
+ if (typeof value !== "string") {
267
+ return;
268
+ }
269
+ if (!allowEmpty && value.length === 0) {
270
+ return;
271
+ }
272
+ args.push(flag, value);
273
+ }
274
+
275
+ function pushRepeatable(args: string[], flag: string, values: string[] | undefined): void {
276
+ if (!Array.isArray(values)) {
277
+ return;
278
+ }
279
+ for (const value of values) {
280
+ if (typeof value === "string" && value.length > 0) {
281
+ args.push(flag, value);
282
+ }
283
+ }
284
+ }
285
+
286
+ function pushBooleanishOption(args: string[], flag: string, value: BooleanFlagInput | undefined): void {
287
+ if (typeof value === "boolean") {
288
+ args.push(flag, value ? "true" : "false");
289
+ return;
290
+ }
291
+ pushOption(args, flag, value);
292
+ }
293
+
294
+ function pushRepeatableOrNone(args: string[], flag: string, values: string[] | undefined): void {
295
+ const validValues = Array.isArray(values) ? values.filter((value) => typeof value === "string" && value.length > 0) : [];
296
+ if (validValues.length === 0) {
297
+ args.push(flag, "none");
298
+ return;
299
+ }
300
+ for (const value of validValues) {
301
+ args.push(flag, value);
302
+ }
303
+ }
304
+
305
+ function requireString(value: string | undefined, name: string, action: PmToolAction): string {
306
+ if (typeof value !== "string" || value.length === 0) {
307
+ throw new Error(`Action "${action}" requires "${name}".`);
308
+ }
309
+ return value;
310
+ }
311
+
312
+ function addListFilters(args: string[], params: PmToolParameters): void {
313
+ pushOption(args, "--type", params.type);
314
+ pushOption(args, "--tag", params.tag);
315
+ pushOption(args, "--priority", params.priority);
316
+ pushOption(args, "--deadline-before", params.deadlineBefore);
317
+ pushOption(args, "--deadline-after", params.deadlineAfter);
318
+ pushOption(args, "--limit", params.limit);
319
+ }
320
+
321
+ function addCreateFlags(args: string[], params: PmToolParameters): void {
322
+ pushOption(args, "--title", params.title);
323
+ pushOption(args, "--description", params.description, true);
324
+ pushOption(args, "--type", params.type);
325
+ pushOption(args, "--status", params.status);
326
+ pushOption(args, "--priority", params.priority);
327
+ pushOption(args, "--tags", params.tags, true);
328
+ pushOption(args, "--body", params.body, true);
329
+ pushOption(args, "--deadline", params.deadline);
330
+ pushOption(args, "--estimate", params.estimate);
331
+ pushOption(args, "--acceptance-criteria", params.acceptanceCriteria, true);
332
+ pushOption(args, "--author", params.author);
333
+ pushOption(args, "--message", params.message, true);
334
+ const assignee = typeof params.assignee === "string" && params.assignee.length > 0 ? params.assignee : "none";
335
+ pushOption(args, "--assignee", assignee);
336
+ addSharedCreateUpdateFlags(args, params);
337
+ pushRepeatableOrNone(args, "--dep", params.dep);
338
+ pushRepeatableOrNone(args, "--comment", params.comment);
339
+ pushRepeatableOrNone(args, "--note", params.note);
340
+ pushRepeatableOrNone(args, "--learning", params.learning);
341
+ pushRepeatableOrNone(args, "--file", params.linkedFile);
342
+ pushRepeatableOrNone(args, "--test", params.linkedTest);
343
+ pushRepeatableOrNone(args, "--doc", params.doc);
344
+ }
345
+
346
+ function addUpdateFlags(args: string[], params: PmToolParameters): void {
347
+ pushOption(args, "--title", params.title);
348
+ pushOption(args, "--description", params.description, true);
349
+ pushOption(args, "--status", params.status);
350
+ pushOption(args, "--priority", params.priority);
351
+ pushOption(args, "--type", params.type);
352
+ pushOption(args, "--tags", params.tags, true);
353
+ pushOption(args, "--deadline", params.deadline);
354
+ pushOption(args, "--estimate", params.estimate);
355
+ pushOption(args, "--acceptance-criteria", params.acceptanceCriteria, true);
356
+ pushOption(args, "--author", params.author);
357
+ pushOption(args, "--message", params.message, true);
358
+ pushOption(args, "--assignee", params.assignee);
359
+ addSharedCreateUpdateFlags(args, params);
360
+ if (params.force) {
361
+ args.push("--force");
362
+ }
363
+ }
364
+
365
+ function addSharedCreateUpdateFlags(args: string[], params: PmToolParameters): void {
366
+ pushOption(args, "--parent", params.parent);
367
+ pushOption(args, "--reviewer", params.reviewer);
368
+ pushOption(args, "--risk", params.risk);
369
+ pushOption(args, "--confidence", params.confidence);
370
+ pushOption(args, "--sprint", params.sprint);
371
+ pushOption(args, "--release", params.release);
372
+ pushOption(args, "--blocked-by", params.blockedBy);
373
+ pushOption(args, "--blocked-reason", params.blockedReason);
374
+ pushOption(args, "--unblock-note", params.unblockNote);
375
+ pushOption(args, "--reporter", params.reporter);
376
+ pushOption(args, "--severity", params.severity);
377
+ pushOption(args, "--environment", params.environment);
378
+ pushOption(args, "--repro-steps", params.reproSteps);
379
+ pushOption(args, "--resolution", params.resolution);
380
+ pushOption(args, "--expected-result", params.expectedResult);
381
+ pushOption(args, "--actual-result", params.actualResult);
382
+ pushOption(args, "--affected-version", params.affectedVersion);
383
+ pushOption(args, "--fixed-version", params.fixedVersion);
384
+ pushOption(args, "--component", params.component);
385
+ pushBooleanishOption(args, "--regression", params.regression);
386
+ pushOption(args, "--customer-impact", params.customerImpact);
387
+ pushOption(args, "--definition-of-ready", params.definitionOfReady, true);
388
+ pushOption(args, "--order", params.order);
389
+ pushOption(args, "--goal", params.goal);
390
+ pushOption(args, "--objective", params.objective);
391
+ pushOption(args, "--value", params.value);
392
+ pushOption(args, "--impact", params.impact);
393
+ pushOption(args, "--outcome", params.outcome);
394
+ pushOption(args, "--why-now", params.whyNow);
395
+ }
396
+
397
+ function addAuthorMessageForceFlags(args: string[], params: PmToolParameters): void {
398
+ pushOption(args, "--author", params.author);
399
+ pushOption(args, "--message", params.message, true);
400
+ if (params.force) {
401
+ args.push("--force");
402
+ }
403
+ }
404
+
405
+ function addGlobalFlags(args: string[], params: PmToolParameters): void {
406
+ if (params.json !== false) {
407
+ args.push("--json");
408
+ }
409
+ if (params.quiet) {
410
+ args.push("--quiet");
411
+ }
412
+ if (params.profile) {
413
+ args.push("--profile");
414
+ }
415
+ if (params.noExtensions) {
416
+ args.push("--no-extensions");
417
+ }
418
+ pushOption(args, "--path", params.path);
419
+ }
420
+
421
+ export function buildPmCliSequences(params: PmToolParameters): string[][] {
422
+ const action = params.action.trim().toLowerCase();
423
+ if (action === "start-task") {
424
+ const globalArgs: string[] = [];
425
+ addGlobalFlags(globalArgs, params);
426
+ const id = requireString(params.id, "id", action);
427
+ const claimArgs = ["claim", id];
428
+ addAuthorMessageForceFlags(claimArgs, params);
429
+ const updateArgs = ["update", id, "--status", "in_progress"];
430
+ addAuthorMessageForceFlags(updateArgs, params);
431
+ return [[...globalArgs, ...claimArgs], [...globalArgs, ...updateArgs]];
432
+ }
433
+ if (action === "pause-task") {
434
+ const globalArgs: string[] = [];
435
+ addGlobalFlags(globalArgs, params);
436
+ const id = requireString(params.id, "id", action);
437
+ const updateArgs = ["update", id, "--status", "open"];
438
+ addAuthorMessageForceFlags(updateArgs, params);
439
+ const releaseArgs = ["release", id];
440
+ addAuthorMessageForceFlags(releaseArgs, params);
441
+ return [[...globalArgs, ...updateArgs], [...globalArgs, ...releaseArgs]];
442
+ }
443
+ if (action === "close-task") {
444
+ const globalArgs: string[] = [];
445
+ addGlobalFlags(globalArgs, params);
446
+ const id = requireString(params.id, "id", action);
447
+ const closeArgs = ["close", id, requireString(params.text, "text", action)];
448
+ addAuthorMessageForceFlags(closeArgs, params);
449
+ const releaseArgs = ["release", id];
450
+ addAuthorMessageForceFlags(releaseArgs, params);
451
+ return [[...globalArgs, ...closeArgs], [...globalArgs, ...releaseArgs]];
452
+ }
453
+ return [buildPmCliArgs(params)];
454
+ }
455
+
456
+ export function buildPmCliArgs(params: PmToolParameters): string[] {
457
+ const action = params.action.trim().toLowerCase();
458
+ if (!isPmToolAction(action)) {
459
+ throw new Error(`Unsupported action "${params.action}".`);
460
+ }
461
+
462
+ const args: string[] = [];
463
+ addGlobalFlags(args, params);
464
+
465
+ switch (action) {
466
+ case "init":
467
+ args.push("init");
468
+ if (params.prefix) {
469
+ args.push(params.prefix);
470
+ }
471
+ return args;
472
+ case "config":
473
+ args.push(
474
+ "config",
475
+ requireString(params.scope, "scope", action),
476
+ requireString(params.configAction, "configAction", action),
477
+ requireString(params.key, "key", action),
478
+ );
479
+ pushRepeatable(args, "--criterion", params.criterion);
480
+ return args;
481
+ case "create":
482
+ args.push("create");
483
+ addCreateFlags(args, params);
484
+ return args;
485
+ case "list":
486
+ case "list-all":
487
+ case "list-draft":
488
+ case "list-open":
489
+ case "list-in-progress":
490
+ case "list-blocked":
491
+ case "list-closed":
492
+ case "list-canceled":
493
+ args.push(action);
494
+ addListFilters(args, params);
495
+ return args;
496
+ case "get":
497
+ args.push("get", requireString(params.id, "id", action));
498
+ return args;
499
+ case "search":
500
+ args.push("search", requireString(params.query ?? params.keywords, "query", action));
501
+ pushOption(args, "--mode", params.mode);
502
+ if (params.includeLinked) {
503
+ args.push("--include-linked");
504
+ }
505
+ addListFilters(args, params);
506
+ return args;
507
+ case "reindex":
508
+ args.push("reindex");
509
+ pushOption(args, "--mode", params.mode);
510
+ return args;
511
+ case "history":
512
+ args.push("history", requireString(params.id, "id", action));
513
+ pushOption(args, "--limit", params.limit);
514
+ return args;
515
+ case "activity":
516
+ args.push("activity");
517
+ pushOption(args, "--limit", params.limit);
518
+ return args;
519
+ case "restore":
520
+ args.push("restore", requireString(params.id, "id", action), requireString(params.target, "target", action));
521
+ addAuthorMessageForceFlags(args, params);
522
+ return args;
523
+ case "update":
524
+ args.push("update", requireString(params.id, "id", action));
525
+ addUpdateFlags(args, params);
526
+ return args;
527
+ case "close":
528
+ args.push("close", requireString(params.id, "id", action), requireString(params.text, "text", action));
529
+ addAuthorMessageForceFlags(args, params);
530
+ return args;
531
+ case "delete":
532
+ args.push("delete", requireString(params.id, "id", action));
533
+ addAuthorMessageForceFlags(args, params);
534
+ return args;
535
+ case "append":
536
+ args.push("append", requireString(params.id, "id", action));
537
+ pushOption(args, "--body", params.body, true);
538
+ addAuthorMessageForceFlags(args, params);
539
+ return args;
540
+ case "comments":
541
+ args.push("comments", requireString(params.id, "id", action));
542
+ pushOption(args, "--add", params.text ?? params.add?.[0]);
543
+ pushOption(args, "--limit", params.limit);
544
+ addAuthorMessageForceFlags(args, params);
545
+ return args;
546
+ case "files":
547
+ args.push("files", requireString(params.id, "id", action));
548
+ pushRepeatable(args, "--add", params.add);
549
+ pushRepeatable(args, "--remove", params.remove);
550
+ addAuthorMessageForceFlags(args, params);
551
+ return args;
552
+ case "docs":
553
+ args.push("docs", requireString(params.id, "id", action));
554
+ pushRepeatable(args, "--add", params.add);
555
+ pushRepeatable(args, "--remove", params.remove);
556
+ addAuthorMessageForceFlags(args, params);
557
+ return args;
558
+ case "test":
559
+ args.push("test", requireString(params.id, "id", action));
560
+ pushRepeatable(args, "--add", params.add);
561
+ pushRepeatable(args, "--remove", params.remove);
562
+ if (params.run) {
563
+ args.push("--run");
564
+ }
565
+ pushOption(args, "--timeout", params.timeout);
566
+ addAuthorMessageForceFlags(args, params);
567
+ return args;
568
+ case "test-all":
569
+ args.push("test-all");
570
+ pushOption(args, "--status", params.status);
571
+ pushOption(args, "--timeout", params.timeout);
572
+ return args;
573
+ case "stats":
574
+ case "health":
575
+ case "gc":
576
+ args.push(action);
577
+ return args;
578
+ case "completion":
579
+ args.push("completion", requireString(params.shell, "shell", action));
580
+ return args;
581
+ case "claim":
582
+ case "release":
583
+ args.push(action, requireString(params.id, "id", action));
584
+ addAuthorMessageForceFlags(args, params);
585
+ return args;
586
+ case "start-task":
587
+ case "pause-task":
588
+ case "close-task":
589
+ throw new Error(`Action "${action}" is a workflow preset and must be built via buildPmCliSequences().`);
590
+ case "beads-import":
591
+ args.push("beads", "import");
592
+ pushOption(args, "--file", params.file);
593
+ pushOption(args, "--author", params.author);
594
+ pushOption(args, "--message", params.message);
595
+ return args;
596
+ case "todos-import":
597
+ args.push("todos", "import");
598
+ pushOption(args, "--folder", params.folder);
599
+ pushOption(args, "--author", params.author);
600
+ pushOption(args, "--message", params.message);
601
+ return args;
602
+ case "todos-export":
603
+ args.push("todos", "export");
604
+ pushOption(args, "--folder", params.folder);
605
+ return args;
606
+ }
607
+ }
608
+
609
+ function looksLikeCommandNotFound(stderr: string, command: string, code: number | null | undefined): boolean {
610
+ if (code === 127) {
611
+ return true;
612
+ }
613
+ const normalized = stderr.toLowerCase();
614
+ return (
615
+ normalized.includes("not found") ||
616
+ normalized.includes("enoent") ||
617
+ normalized.includes(`${command.toLowerCase()}: command not found`) ||
618
+ normalized.includes(`'${command.toLowerCase()}' is not recognized`)
619
+ );
620
+ }
621
+
622
+ function parseJsonOutput(text: string): unknown {
623
+ try {
624
+ return JSON.parse(text);
625
+ } catch {
626
+ return null;
627
+ }
628
+ }
629
+
630
+ function renderContentText(stdout: string, stderr: string, code: number | null | undefined): string {
631
+ if (stdout.length > 0) {
632
+ return stdout;
633
+ }
634
+ if (stderr.length > 0) {
635
+ return stderr;
636
+ }
637
+ return `pm command exited with code ${code ?? "unknown"}.`;
638
+ }
639
+
640
+ function commandToDisplay(command: string, args: string[]): string {
641
+ return [command, ...args].join(" ").trim();
642
+ }
643
+
644
+ export async function runPmToolAction(
645
+ pi: PiExtensionApi,
646
+ params: PmToolParameters,
647
+ signal?: AbortSignal,
648
+ ): Promise<PiToolResultEnvelope> {
649
+ const sequences = buildPmCliSequences(params);
650
+ const timeout = typeof params.timeoutMs === "number" && Number.isFinite(params.timeoutMs) ? params.timeoutMs : undefined;
651
+
652
+ let lastResult: PiExecResult = {
653
+ code: 1,
654
+ stdout: "",
655
+ stderr: "No pm invocation attempts were configured.",
656
+ };
657
+ let selectedAttempt: { command: string; args: string[]; display: string } | undefined;
658
+ const tried: string[] = [];
659
+ const allStdout: string[] = [];
660
+ const allStderr: string[] = [];
661
+ let parsed: unknown = null;
662
+ let exitCode = 1;
663
+
664
+ for (const cliArgs of sequences) {
665
+ const attempts: Array<{ command: string; args: string[]; display: string }> = [];
666
+ if (typeof params.pmExecutable === "string" && params.pmExecutable.length > 0) {
667
+ attempts.push({
668
+ command: params.pmExecutable,
669
+ args: cliArgs,
670
+ display: commandToDisplay(params.pmExecutable, cliArgs),
671
+ });
672
+ } else {
673
+ attempts.push(
674
+ {
675
+ command: "pm",
676
+ args: cliArgs,
677
+ display: commandToDisplay("pm", cliArgs),
678
+ },
679
+ {
680
+ command: "node",
681
+ args: [NODE_FALLBACK_CLI_PATH, ...cliArgs],
682
+ display: commandToDisplay("node", [NODE_FALLBACK_CLI_PATH, ...cliArgs]),
683
+ },
684
+ );
685
+ }
686
+
687
+ let sequenceResult: PiExecResult = {
688
+ code: 1,
689
+ stdout: "",
690
+ stderr: "No pm invocation attempts were configured.",
691
+ };
692
+
693
+ for (let index = 0; index < attempts.length; index += 1) {
694
+ const attempt = attempts[index];
695
+ selectedAttempt = attempt;
696
+ tried.push(attempt.display);
697
+ try {
698
+ sequenceResult = await pi.exec(attempt.command, attempt.args, { signal, timeout });
699
+ } catch (error: unknown) {
700
+ const message = error instanceof Error ? error.message : String(error);
701
+ sequenceResult = {
702
+ code: 1,
703
+ stdout: "",
704
+ stderr: message,
705
+ };
706
+ }
707
+
708
+ const code = sequenceResult.code ?? 1;
709
+ const stderr = (sequenceResult.stderr ?? "").trim();
710
+ if (code === 0) {
711
+ break;
712
+ }
713
+
714
+ if (!looksLikeCommandNotFound(stderr, attempt.command, code) || index === attempts.length - 1) {
715
+ break;
716
+ }
717
+ }
718
+
719
+ lastResult = sequenceResult;
720
+ exitCode = lastResult.code ?? 1;
721
+
722
+ const currentStdout = (lastResult.stdout ?? "").trim();
723
+ const currentStderr = (lastResult.stderr ?? "").trim();
724
+ if (currentStdout) allStdout.push(currentStdout);
725
+ if (currentStderr) allStderr.push(currentStderr);
726
+ parsed = parseJsonOutput(currentStdout);
727
+
728
+ if (exitCode !== 0) {
729
+ break;
730
+ }
731
+ }
732
+
733
+ const stdout = allStdout.join("\n\n");
734
+ const stderr = allStderr.join("\n\n");
735
+ const contentText = renderContentText(stdout, stderr, exitCode);
736
+
737
+ return {
738
+ content: [{ type: "text", text: contentText }],
739
+ details: {
740
+ action: params.action,
741
+ invocation: selectedAttempt ? {
742
+ command: selectedAttempt.command,
743
+ args: selectedAttempt.args,
744
+ display: selectedAttempt.display,
745
+ } : { command: "unknown", args: [], display: "unknown" },
746
+ tried,
747
+ exit_code: exitCode,
748
+ ok: exitCode === 0,
749
+ stdout,
750
+ stderr,
751
+ parsed,
752
+ },
753
+ isError: exitCode !== 0,
754
+ };
755
+ }
756
+
757
+ export function createPmToolDefinition(pi: PiExtensionApi): PiToolDefinition {
758
+ return {
759
+ name: "pm",
760
+ label: "pm",
761
+ description: "Run pm-cli actions through a Pi tool wrapper.",
762
+ parameters: PM_TOOL_PARAMETERS_SCHEMA,
763
+ async execute(_toolCallId, params, signal, onUpdate) {
764
+ onUpdate?.({
765
+ content: [{ type: "text", text: `Running pm action: ${params.action}` }],
766
+ });
767
+ return runPmToolAction(pi, params, signal);
768
+ },
769
+ };
770
+ }
771
+
772
+ export function registerPmTool(pi: PiExtensionApi): void {
773
+ pi.registerTool(createPmToolDefinition(pi));
774
+ }
775
+
776
+ export default function pmPiExtension(pi: PiExtensionApi): void {
777
+ registerPmTool(pi);
778
+ }