@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,329 @@
1
+ # pm-cli Extension Development Guide
2
+
3
+ Extensions let you add commands, renderers, importers, exporters, schema fields, search providers, and lifecycle hooks to `pm-cli` without modifying core.
4
+
5
+ ## Extension Locations
6
+
7
+ | Scope | Path |
8
+ |-------|------|
9
+ | Global | `~/.pm-cli/extensions/<name>/` (override: `PM_GLOBAL_PATH/extensions/<name>/`) |
10
+ | Project | `.agents/pm/extensions/<name>/` (override: `PM_PATH/extensions/<name>/`) |
11
+
12
+ **Load order:** core built-ins → global → project. Project-local extensions take precedence over global when they declare the same command name or renderer key.
13
+
14
+ ## Manifest
15
+
16
+ Every extension directory must contain a `manifest.json`:
17
+
18
+ ```json
19
+ {
20
+ "name": "pm-ext-example",
21
+ "version": "0.1.0",
22
+ "entry": "./dist/index.js",
23
+ "priority": 100,
24
+ "capabilities": [
25
+ "commands",
26
+ "renderers",
27
+ "hooks",
28
+ "schema",
29
+ "importers",
30
+ "search"
31
+ ]
32
+ }
33
+ ```
34
+
35
+ - `entry` must resolve inside the extension directory (no path traversal).
36
+ - `capabilities` declares what the extension will register. API calls that exceed declared capabilities fail activation deterministically.
37
+ - Unknown capability names are silently ignored for gating but emit discovery diagnostics.
38
+
39
+ ## Extension Module
40
+
41
+ The entry module must export an `activate` function:
42
+
43
+ ```ts
44
+ import type { ExtensionApi } from "pm-cli";
45
+
46
+ export function activate(api: ExtensionApi): void {
47
+ // register commands, hooks, renderers, etc.
48
+ }
49
+ ```
50
+
51
+ `activate` may be synchronous or return `Promise<void>`.
52
+
53
+ ## API Reference
54
+
55
+ ### `api.registerCommand(def)`
56
+
57
+ Register a new command or override an existing core command's result.
58
+
59
+ **New command path:**
60
+
61
+ ```ts
62
+ api.registerCommand({
63
+ name: "acme sync",
64
+ run: async (args, options, global) => {
65
+ // args: string[] — positional CLI arguments
66
+ // options: Record<string,unknown> — parsed flags
67
+ // global: GlobalOptions — --json, --quiet, --path, etc.
68
+ return { ok: true, synced: 42 };
69
+ },
70
+ });
71
+ ```
72
+
73
+ The command name is canonicalized (trimmed, lowercased, repeated whitespace collapsed). The handler receives cloned snapshots so mutation cannot leak into caller state.
74
+
75
+ **Override existing core command result:**
76
+
77
+ ```ts
78
+ api.registerCommand("list", (priorResult, args, options, global, pmRoot) => {
79
+ // priorResult: the core command's output object (cloned)
80
+ // return a modified result object, or undefined to use priorResult as-is
81
+ return { ...priorResult, _ext: "annotated" };
82
+ });
83
+ ```
84
+
85
+ ### `api.registerFlags(targetCommand, flags)`
86
+
87
+ Declare flags for a command (displayed in `--help` for dynamic extension commands):
88
+
89
+ ```ts
90
+ api.registerFlags("acme sync", [
91
+ { name: "--dry-run", description: "Simulate without writing" },
92
+ { name: "--org <name>", description: "Organization name" },
93
+ ]);
94
+ ```
95
+
96
+ ### `api.registerRenderer(format, renderer)`
97
+
98
+ Override TOON or JSON output for a command:
99
+
100
+ ```ts
101
+ api.registerRenderer("toon", (command, result, args, options, global, pmRoot) => {
102
+ if (command !== "stats") return undefined; // pass through
103
+ return customToonFormat(result);
104
+ });
105
+ ```
106
+
107
+ Return `undefined` to fall back to the built-in renderer.
108
+
109
+ ### `api.registerImporter(name, importer)`
110
+
111
+ Register an importer (also wires `<name> import` command path):
112
+
113
+ ```ts
114
+ api.registerImporter("jira", async (options, global) => {
115
+ // options: parsed flags from `pm jira import ...`
116
+ return { ok: true, imported: 5, skipped: 0, ids: ["pm-xxxx"], warnings: [] };
117
+ });
118
+ ```
119
+
120
+ ### `api.registerExporter(name, exporter)`
121
+
122
+ Register an exporter (also wires `<name> export` command path):
123
+
124
+ ```ts
125
+ api.registerExporter("jira", async (options, global) => {
126
+ return { ok: true, exported: 5, ids: ["pm-xxxx"], warnings: [] };
127
+ });
128
+ ```
129
+
130
+ ### `api.registerItemFields(fields)`
131
+
132
+ Declare additional front-matter fields for schema-awareness:
133
+
134
+ ```ts
135
+ api.registerItemFields([
136
+ { name: "acme_epic_id", type: "string", optional: true },
137
+ ]);
138
+ ```
139
+
140
+ ### `api.registerMigration(def)`
141
+
142
+ Declare a schema migration (tracked in `pm health`):
143
+
144
+ ```ts
145
+ api.registerMigration({
146
+ id: "add-acme-epic-id",
147
+ description: "Add acme_epic_id field to existing items",
148
+ mandatory: false,
149
+ status: "pending",
150
+ run: async (pmRoot) => {
151
+ // migrate items; update status to "applied" when done
152
+ },
153
+ });
154
+ ```
155
+
156
+ Migrations with `mandatory: true` and `status` not `"applied"` block write commands until resolved (bypass with `--force`).
157
+
158
+ ### `api.registerSearchProvider(provider)`
159
+
160
+ Register a custom search provider:
161
+
162
+ ```ts
163
+ api.registerSearchProvider({
164
+ name: "elastic",
165
+ query: async (query, options, settings) => {
166
+ return [{ id: "pm-xxxx", score: 0.95 }];
167
+ },
168
+ });
169
+ ```
170
+
171
+ ### `api.registerVectorStoreAdapter(adapter)`
172
+
173
+ Register a custom vector store:
174
+
175
+ ```ts
176
+ api.registerVectorStoreAdapter({
177
+ name: "pinecone",
178
+ upsert: async (records, settings) => { ... },
179
+ query: async (vector, topK, settings) => { ... },
180
+ delete: async (ids, settings) => { ... },
181
+ });
182
+ ```
183
+
184
+ ## Lifecycle Hooks
185
+
186
+ Hooks run for every applicable core operation. Hook handlers receive cloned context snapshots — mutations do not leak back into caller state.
187
+
188
+ ### `api.hooks.beforeCommand(hook)`
189
+
190
+ Runs before any command executes:
191
+
192
+ ```ts
193
+ api.hooks.beforeCommand((ctx) => {
194
+ // ctx.command: string
195
+ // ctx.args: string[]
196
+ // ctx.options: Record<string,unknown>
197
+ // ctx.global: GlobalOptions
198
+ // ctx.pm_root: string
199
+ console.log(`[ext] before: ${ctx.command}`);
200
+ });
201
+ ```
202
+
203
+ ### `api.hooks.afterCommand(hook)`
204
+
205
+ Runs after a command completes (even on failure):
206
+
207
+ ```ts
208
+ api.hooks.afterCommand((ctx) => {
209
+ // ctx.ok: boolean
210
+ // ctx.result?: unknown
211
+ // ctx.error?: unknown
212
+ // same fields as beforeCommand
213
+ });
214
+ ```
215
+
216
+ ### `api.hooks.onWrite(hook)`
217
+
218
+ Runs before each item file write:
219
+
220
+ ```ts
221
+ api.hooks.onWrite((ctx) => {
222
+ // ctx.path: string
223
+ // ctx.op: string (create, update, restore, etc.)
224
+ // ctx.item_id: string
225
+ });
226
+ ```
227
+
228
+ ### `api.hooks.onRead(hook)`
229
+
230
+ Runs after each item file read:
231
+
232
+ ```ts
233
+ api.hooks.onRead((ctx) => {
234
+ // ctx.path: string
235
+ // ctx.item_id: string
236
+ });
237
+ ```
238
+
239
+ ### `api.hooks.onIndex(hook)`
240
+
241
+ Runs during reindex/gc operations:
242
+
243
+ ```ts
244
+ api.hooks.onIndex((ctx) => {
245
+ // ctx.mode: "keyword" | "semantic" | "hybrid" | "gc"
246
+ // ctx.total?: number
247
+ });
248
+ ```
249
+
250
+ ## Health and Diagnostics
251
+
252
+ `pm health` probes all loaded extensions and surfaces:
253
+
254
+ - `extension_load_failed:<layer>:<name>` — manifest parse or module import error
255
+ - `extension_activate_failed:<layer>:<name>` — exception in `activate()`
256
+ - `extension_entry_outside_extension:<layer>:<name>` — entry path escapes directory
257
+ - `extension_capability_unknown:<layer>:<name>:<capability>` — unknown capability in manifest
258
+
259
+ Use `pm health --json` to parse diagnostics programmatically.
260
+
261
+ ## Disabling Extensions
262
+
263
+ ```bash
264
+ pm --no-extensions list-open # disable all extensions for this invocation
265
+ ```
266
+
267
+ Or configure per-project in `.agents/pm/settings.json`:
268
+
269
+ ```json
270
+ {
271
+ "extensions": {
272
+ "disabled": ["pm-ext-example"]
273
+ }
274
+ }
275
+ ```
276
+
277
+ ## Example: Minimal Custom Command
278
+
279
+ **`~/.pm-cli/extensions/hello/manifest.json`:**
280
+
281
+ ```json
282
+ {
283
+ "name": "hello",
284
+ "version": "0.1.0",
285
+ "entry": "./index.js",
286
+ "priority": 100,
287
+ "capabilities": ["commands"]
288
+ }
289
+ ```
290
+
291
+ **`~/.pm-cli/extensions/hello/index.js`:**
292
+
293
+ ```js
294
+ export function activate(api) {
295
+ api.registerCommand({
296
+ name: "hello",
297
+ run: async (_args, _options, _global) => {
298
+ return { message: "Hello from extension!" };
299
+ },
300
+ });
301
+ }
302
+ ```
303
+
304
+ ```bash
305
+ pm hello
306
+ # => message: Hello from extension!
307
+ ```
308
+
309
+ ## Built-in Extensions
310
+
311
+ `pm-cli` ships two built-in extensions compiled into the package:
312
+
313
+ | Extension | Commands | Purpose |
314
+ |-----------|----------|---------|
315
+ | `builtin-beads-import` | `pm beads import` | Import Beads JSONL records into pm items |
316
+ | `builtin-todos-import-export` | `pm todos import`, `pm todos export` | Round-trip todos markdown format |
317
+
318
+ Built-in extensions are loaded automatically and cannot be disabled via settings (use `--no-extensions` to disable all extensions including built-ins).
319
+
320
+ ## Pi Agent Extension
321
+
322
+ The bundled Pi tool wrapper lives at `.pi/extensions/pm-cli/index.ts` and is a Pi agent extension (not a pm-cli extension). Install it with:
323
+
324
+ ```bash
325
+ pm install pi # to current project .pi/extensions/pm-cli/index.ts
326
+ pm install pi --global # to PI_CODING_AGENT_DIR/extensions/pm-cli/index.ts
327
+ ```
328
+
329
+ See [AGENTS.md](../AGENTS.md) section 9 for full usage details.
@@ -0,0 +1,65 @@
1
+ # Releasing `@unbrained/pm-cli`
2
+
3
+ This repository uses a tag-driven GitHub Actions release pipeline that publishes to npm and creates a GitHub Release.
4
+
5
+ ## Version policy
6
+
7
+ `pm-cli` uses a calendar SemVer-compatible scheme:
8
+
9
+ - `YYYY.M.D` for the first release on a date
10
+ - `YYYY.M.D-N` for additional releases on the same date (`N >= 2`)
11
+
12
+ Examples:
13
+
14
+ - First release on 2026-03-09: `2026.3.9`
15
+ - Second release on 2026-03-09: `2026.3.9-2`
16
+
17
+ Use:
18
+
19
+ ```bash
20
+ pnpm version:next
21
+ ```
22
+
23
+ to compute the next expected release version from the npm registry.
24
+
25
+ ## One-time GitHub setup
26
+
27
+ - Create GitHub Environment `release`
28
+ - Add environment secret `NPM_TOKEN` (npm automation token with publish rights for `@unbrained/pm-cli`)
29
+
30
+ ## Release checklist
31
+
32
+ 1) Update `package.json` version and `CHANGELOG.md`
33
+
34
+ 2) Run release gates locally:
35
+
36
+ ```bash
37
+ pnpm install
38
+ pnpm build
39
+ pnpm typecheck
40
+ pnpm test
41
+ pnpm test:coverage
42
+ pnpm version:check
43
+ pnpm security:scan
44
+ pnpm smoke:npx
45
+ ```
46
+
47
+ 3) Commit and tag:
48
+
49
+ ```bash
50
+ git tag v<version>
51
+ git push origin main
52
+ git push origin v<version>
53
+ ```
54
+
55
+ 4) Verify Actions:
56
+
57
+ - `CI` passes for commit
58
+ - `Release` workflow passes for tag
59
+ - npm package published at `@unbrained/pm-cli`
60
+ - GitHub Release created with generated notes
61
+
62
+ ## Notes
63
+
64
+ - Do not run manual `npm publish`; publishing is owned by `.github/workflows/release.yml`.
65
+ - Release workflow enforces tag/version alignment and calendar version sequencing before publish.
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "@unbrained/pm-cli",
3
+ "version": "2026.3.9",
4
+ "description": "Git-native project management CLI for humans and agents.",
5
+ "type": "module",
6
+ "author": "unbrained",
7
+ "homepage": "https://github.com/unbraind/pm-cli#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/unbraind/pm-cli.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/unbraind/pm-cli/issues"
14
+ },
15
+ "bin": {
16
+ "pm": "dist/cli.js"
17
+ },
18
+ "files": [
19
+ "dist/**",
20
+ "README.md",
21
+ "LICENSE",
22
+ "AGENTS.md",
23
+ "PRD.md",
24
+ "docs/**",
25
+ ".pi/extensions/pm-cli/index.ts",
26
+ "scripts/install.sh",
27
+ "scripts/install.ps1"
28
+ ],
29
+ "scripts": {
30
+ "build": "tsc -p tsconfig.json",
31
+ "typecheck": "tsc --noEmit -p tsconfig.json",
32
+ "start": "node dist/cli.js",
33
+ "dev": "tsx src/cli.ts",
34
+ "test": "pnpm build && vitest run",
35
+ "test:coverage": "pnpm build && vitest run --coverage",
36
+ "version:check": "node scripts/release-version.mjs check",
37
+ "version:next": "node scripts/release-version.mjs next",
38
+ "security:scan": "node scripts/check-secrets.mjs",
39
+ "smoke:npx": "node scripts/smoke-npx-from-pack.mjs",
40
+ "prepublishOnly": "pnpm build"
41
+ },
42
+ "keywords": [
43
+ "cli",
44
+ "project-management",
45
+ "agents",
46
+ "ai",
47
+ "git-native",
48
+ "typescript",
49
+ "task-tracker",
50
+ "coding-agents"
51
+ ],
52
+ "license": "MIT",
53
+ "publishConfig": {
54
+ "access": "public"
55
+ },
56
+ "engines": {
57
+ "node": ">=20"
58
+ },
59
+ "dependencies": {
60
+ "@toon-format/toon": "latest",
61
+ "commander": "latest",
62
+ "fast-json-patch": "latest",
63
+ "undici": "latest",
64
+ "zod": "latest"
65
+ },
66
+ "devDependencies": {
67
+ "@types/node": "latest",
68
+ "@vitest/coverage-v8": "^4.0.18",
69
+ "c8": "^11.0.0",
70
+ "tsx": "latest",
71
+ "typescript": "latest",
72
+ "vitest": "^4.0.18"
73
+ },
74
+ "pnpm": {
75
+ "overrides": {
76
+ "rollup": ">=4.59.0"
77
+ }
78
+ }
79
+ }
@@ -0,0 +1,112 @@
1
+ param(
2
+ [string]$Version = "latest",
3
+ [string]$Prefix = "",
4
+ [string]$PackageName = ""
5
+ )
6
+
7
+ Set-StrictMode -Version Latest
8
+ $ErrorActionPreference = "Stop"
9
+
10
+ $envPackageName = $env:PM_CLI_PACKAGE
11
+ if ([string]::IsNullOrWhiteSpace($PackageName)) {
12
+ if ([string]::IsNullOrWhiteSpace($envPackageName)) {
13
+ $PackageName = "@unbrained/pm-cli"
14
+ } else {
15
+ $PackageName = $envPackageName
16
+ }
17
+ }
18
+
19
+ function Require-Command {
20
+ param([string]$Name)
21
+ if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) {
22
+ throw "Required command not found: $Name"
23
+ }
24
+ }
25
+
26
+ function Use-LiteralInstallSpec {
27
+ param([string]$Name)
28
+
29
+ if ([string]::IsNullOrWhiteSpace($Name)) {
30
+ return $false
31
+ }
32
+
33
+ if ($Name -match "^(file:|https?://|git\+|npm:)") {
34
+ return $true
35
+ }
36
+
37
+ if ($Name.StartsWith("./") -or $Name.StartsWith("../") -or $Name.StartsWith("/") -or $Name.StartsWith("~/")) {
38
+ return $true
39
+ }
40
+
41
+ if ($Name.Contains('\') -or $Name -match "^[A-Za-z]:[\\/]") {
42
+ return $true
43
+ }
44
+
45
+ if ($Name.EndsWith(".tgz") -or $Name.EndsWith(".tar.gz")) {
46
+ return $true
47
+ }
48
+
49
+ if ($Name.StartsWith("@")) {
50
+ return $Name -match "^@[^/]+/[^@]+@.+$"
51
+ }
52
+
53
+ return $Name.Contains("@")
54
+ }
55
+
56
+ Require-Command "node"
57
+ Require-Command "npm"
58
+
59
+ $installSpec = $null
60
+ if (Use-LiteralInstallSpec $PackageName) {
61
+ $installSpec = $PackageName
62
+ } else {
63
+ $installSpec = "$PackageName@$Version"
64
+ }
65
+ Write-Host "Installing or updating $installSpec..."
66
+ $npmArgs = @("install", "-g", $installSpec)
67
+ if ($Prefix -ne "") {
68
+ $npmArgs += @("--prefix", $Prefix)
69
+ }
70
+
71
+ & npm @npmArgs
72
+ if ($LASTEXITCODE -ne 0) {
73
+ throw "npm install failed with exit code $LASTEXITCODE"
74
+ }
75
+
76
+ $pmCommand = Get-Command "pm" -ErrorAction SilentlyContinue
77
+ if ($pmCommand) {
78
+ $pmExecutable = $pmCommand.Source
79
+ } else {
80
+ $candidates = @()
81
+ if ($Prefix -ne "") {
82
+ $candidates += (Join-Path $Prefix "pm.cmd")
83
+ $candidates += (Join-Path $Prefix "bin/pm.cmd")
84
+ $candidates += (Join-Path $Prefix "pm")
85
+ $candidates += (Join-Path $Prefix "bin/pm")
86
+ }
87
+
88
+ $resolved = $null
89
+ foreach ($candidate in $candidates) {
90
+ if (Test-Path $candidate) {
91
+ $resolved = $candidate
92
+ break
93
+ }
94
+ }
95
+
96
+ if (-not $resolved) {
97
+ $hint = ""
98
+ if ($Prefix -ne "") {
99
+ $hint = " Add '$Prefix' (or '$Prefix\bin') to PATH and retry."
100
+ }
101
+ throw "pm binary not found after install.$hint"
102
+ }
103
+ $pmExecutable = $resolved
104
+ }
105
+
106
+ $versionOutput = & $pmExecutable --version
107
+ if ($LASTEXITCODE -ne 0) {
108
+ throw "pm --version failed with exit code $LASTEXITCODE"
109
+ }
110
+
111
+ Write-Host "Installed pm version: $versionOutput"
112
+ Write-Host "Done."
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ PACKAGE_NAME="${PM_CLI_PACKAGE:-@unbrained/pm-cli}"
5
+ TARGET_VERSION="latest"
6
+ PREFIX=""
7
+
8
+ usage() {
9
+ cat <<'EOF'
10
+ Install or update @unbrained/pm-cli globally via npm.
11
+
12
+ Usage:
13
+ bash scripts/install.sh [--version <tag>] [--prefix <dir>]
14
+
15
+ Options:
16
+ --version <tag> Package tag/version to install (default: latest)
17
+ --prefix <dir> npm global prefix override
18
+ -h, --help Show this help message
19
+ EOF
20
+ }
21
+
22
+ require_command() {
23
+ local cmd="$1"
24
+ if ! command -v "$cmd" >/dev/null 2>&1; then
25
+ echo "error: required command not found: $cmd" >&2
26
+ exit 1
27
+ fi
28
+ }
29
+
30
+ is_literal_install_spec() {
31
+ local name="$1"
32
+
33
+ if [[ "$name" == file:* || "$name" == http://* || "$name" == https://* || "$name" == git+* || "$name" == npm:* ]]; then
34
+ return 0
35
+ fi
36
+
37
+ if [[ "$name" == ./* || "$name" == ../* || "$name" == /* || "$name" == ~/* || "$name" == *.tgz || "$name" == *.tar.gz || "$name" == *\\* ]]; then
38
+ return 0
39
+ fi
40
+
41
+ # Scoped names use @scope/pkg; only treat them as literal when explicitly versioned.
42
+ if [[ "$name" == @*/*@* ]]; then
43
+ return 0
44
+ fi
45
+
46
+ if [[ "$name" != @* && "$name" == *@* ]]; then
47
+ return 0
48
+ fi
49
+
50
+ return 1
51
+ }
52
+
53
+ while [[ $# -gt 0 ]]; do
54
+ case "$1" in
55
+ --version)
56
+ if [[ $# -lt 2 ]]; then
57
+ echo "error: --version requires a value" >&2
58
+ exit 2
59
+ fi
60
+ TARGET_VERSION="$2"
61
+ shift 2
62
+ ;;
63
+ --prefix)
64
+ if [[ $# -lt 2 ]]; then
65
+ echo "error: --prefix requires a value" >&2
66
+ exit 2
67
+ fi
68
+ PREFIX="$2"
69
+ shift 2
70
+ ;;
71
+ -h|--help)
72
+ usage
73
+ exit 0
74
+ ;;
75
+ *)
76
+ echo "error: unknown argument: $1" >&2
77
+ usage
78
+ exit 2
79
+ ;;
80
+ esac
81
+ done
82
+
83
+ require_command node
84
+ require_command npm
85
+
86
+ if is_literal_install_spec "$PACKAGE_NAME"; then
87
+ INSTALL_SPEC="$PACKAGE_NAME"
88
+ else
89
+ INSTALL_SPEC="${PACKAGE_NAME}@${TARGET_VERSION}"
90
+ fi
91
+ INSTALL_CMD=(npm install -g "$INSTALL_SPEC")
92
+ if [[ -n "$PREFIX" ]]; then
93
+ INSTALL_CMD+=(--prefix "$PREFIX")
94
+ fi
95
+
96
+ echo "Installing ${INSTALL_SPEC}..."
97
+ "${INSTALL_CMD[@]}"
98
+
99
+ PM_BIN="pm"
100
+ if ! command -v "$PM_BIN" >/dev/null 2>&1; then
101
+ if [[ -n "$PREFIX" && -x "${PREFIX}/bin/pm" ]]; then
102
+ PM_BIN="${PREFIX}/bin/pm"
103
+ else
104
+ echo "error: pm binary not found on PATH after install." >&2
105
+ if [[ -n "$PREFIX" ]]; then
106
+ echo "hint: add ${PREFIX}/bin to your PATH or rerun without --prefix." >&2
107
+ fi
108
+ exit 1
109
+ fi
110
+ fi
111
+
112
+ echo "Installed pm version: $($PM_BIN --version)"
113
+ echo "Done."