publishport-opencli 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (317) hide show
  1. package/README.md +61 -61
  2. package/README.zh-CN.md +68 -68
  3. package/cli-manifest.json +502 -59
  4. package/clis/1688/shared.js +2 -2
  5. package/clis/36kr/news.js +1 -1
  6. package/clis/_atlassian/shared.js +1 -1
  7. package/clis/_shared/article/auth.js +1 -1
  8. package/clis/_shared/article/publish.js +1 -1
  9. package/clis/_shared/content-guard.js +1 -1
  10. package/clis/_shared/self-hosted-sites.js +2 -0
  11. package/clis/_shared/site-auth.js +1 -1
  12. package/clis/_shared/token-auth.js +2 -2
  13. package/clis/_shared/video-publish.js +1 -1
  14. package/clis/aibase/news.js +1 -1
  15. package/clis/amazon/shared.js +2 -2
  16. package/clis/antigravity/SKILL.md +12 -12
  17. package/clis/antigravity/storage.js +1 -1
  18. package/clis/apple-podcasts/episodes.js +1 -1
  19. package/clis/archive/item.js +1 -1
  20. package/clis/archive/search.js +1 -1
  21. package/clis/archive/snapshots.js +1 -1
  22. package/clis/archive/wayback.js +1 -1
  23. package/clis/arxiv/author.js +1 -1
  24. package/clis/baijiahao/publish.js +1 -1
  25. package/clis/bbc/utils.js +1 -1
  26. package/clis/bloomberg/businessweek.js +1 -1
  27. package/clis/bloomberg/utils.js +1 -1
  28. package/clis/booking/search.js +1 -1
  29. package/clis/boss/utils.js +1 -1
  30. package/clis/chatgpt/ask.js +1 -1
  31. package/clis/chatgpt/detail.js +1 -1
  32. package/clis/chatgpt/history.js +1 -1
  33. package/clis/chatgpt/project-file-add.js +1 -1
  34. package/clis/chatgpt/project-list.js +1 -1
  35. package/clis/chatgpt/utils.js +2 -2
  36. package/clis/chess/utils.js +1 -1
  37. package/clis/claude/ask.js +1 -1
  38. package/clis/claude/history.js +1 -1
  39. package/clis/claude/utils.js +1 -1
  40. package/clis/codex/extract-diff.js +1 -1
  41. package/clis/coingecko/coin.js +1 -1
  42. package/clis/crates/utils.js +1 -1
  43. package/clis/csdn/stats.js +1 -1
  44. package/clis/dblp/utils.js +1 -1
  45. package/clis/defillama/utils.js +1 -1
  46. package/clis/devto/publish.js +1 -1
  47. package/clis/discord-app/utils.js +1 -1
  48. package/clis/dockerhub/utils.js +1 -1
  49. package/clis/douban/utils.js +1 -1
  50. package/clis/douyin/_shared/tos-upload.js +1 -1
  51. package/clis/douyin/hashtag.js +1 -1
  52. package/clis/douyin/publish-image.js +1 -1
  53. package/clis/douyin/publish.js +1 -1
  54. package/clis/eastmoney/announcement.js +1 -1
  55. package/clis/eastmoney/rank.js +1 -1
  56. package/clis/eastmoney/sectors.js +1 -1
  57. package/clis/endoflife/utils.js +1 -1
  58. package/clis/flathub/utils.js +1 -1
  59. package/clis/gemini/detail.js +1 -1
  60. package/clis/gemini/read.js +1 -1
  61. package/clis/geogebra/add-circle.js +1 -1
  62. package/clis/geogebra/add-line.js +1 -1
  63. package/clis/geogebra/add-point.js +1 -1
  64. package/clis/geogebra/add-polygon.js +1 -1
  65. package/clis/geogebra/eval.js +1 -1
  66. package/clis/geogebra/hexagon.js +1 -1
  67. package/clis/geogebra/info.js +1 -1
  68. package/clis/geogebra/triangle.js +1 -1
  69. package/clis/ghost/login.js +1 -0
  70. package/clis/ghost/publish.js +1 -1
  71. package/clis/ghost/shared.js +24 -1
  72. package/clis/ghost/sites.js +1 -0
  73. package/clis/ghost/whoami.js +1 -1
  74. package/clis/gitee/user.js +2 -2
  75. package/clis/github-trending/repos.js +1 -1
  76. package/clis/goproxy/utils.js +1 -1
  77. package/clis/grok/export-all.js +1 -1
  78. package/clis/grok/export.js +1 -1
  79. package/clis/hashnode/publish.js +1 -1
  80. package/clis/hf/datasets.js +1 -1
  81. package/clis/hf/models.js +1 -1
  82. package/clis/hf/paper.js +1 -1
  83. package/clis/hf/spaces.js +1 -1
  84. package/clis/homebrew/utils.js +1 -1
  85. package/clis/instagram/note.js +1 -1
  86. package/clis/jimeng/generate.js +1 -1
  87. package/clis/juejin/utils.js +1 -1
  88. package/clis/kuaishou/publish.js +1 -1
  89. package/clis/lesswrong/tag.js +1 -1
  90. package/clis/lichess/utils.js +1 -1
  91. package/clis/linkedin/search.js +2 -2
  92. package/clis/linux-do/feed.js +2 -2
  93. package/clis/lobsters/domain.js +1 -1
  94. package/clis/mastodon/login.js +1 -0
  95. package/clis/mastodon/post.js +1 -1
  96. package/clis/mastodon/shared.js +10 -10
  97. package/clis/mastodon/sites.js +1 -0
  98. package/clis/mastodon/whoami.js +1 -1
  99. package/clis/maven/utils.js +1 -1
  100. package/clis/mdn/search.js +1 -1
  101. package/clis/medium/tag.js +1 -1
  102. package/clis/notebooklm/current.js +1 -1
  103. package/clis/notebooklm/get.js +1 -1
  104. package/clis/notebooklm/history.js +1 -1
  105. package/clis/notebooklm/note-list.js +1 -1
  106. package/clis/notebooklm/notes-get.js +1 -1
  107. package/clis/notebooklm/open.js +1 -1
  108. package/clis/notebooklm/source-fulltext.js +1 -1
  109. package/clis/notebooklm/source-get.js +1 -1
  110. package/clis/notebooklm/source-guide.js +1 -1
  111. package/clis/notebooklm/source-list.js +1 -1
  112. package/clis/notebooklm/summary.js +1 -1
  113. package/clis/notebooklm/utils.js +1 -1
  114. package/clis/npm/utils.js +1 -1
  115. package/clis/nuget/utils.js +1 -1
  116. package/clis/nvd/cve.js +1 -1
  117. package/clis/oeis/utils.js +1 -1
  118. package/clis/ones/common.js +2 -2
  119. package/clis/ones/me.js +1 -1
  120. package/clis/ones/tasks.js +1 -1
  121. package/clis/ones/token-info.js +1 -1
  122. package/clis/ones/worklog.js +1 -1
  123. package/clis/openalex/utils.js +1 -1
  124. package/clis/oschina/stats.js +1 -1
  125. package/clis/osv/utils.js +1 -1
  126. package/clis/packagist/utils.js +1 -1
  127. package/clis/pixiv/detail.js +1 -1
  128. package/clis/pixiv/user.js +1 -1
  129. package/clis/producthunt/utils.js +1 -1
  130. package/clis/pypi/utils.js +1 -1
  131. package/clis/qiita/gql.js +2 -2
  132. package/clis/rest-countries/utils.js +1 -1
  133. package/clis/rfc/utils.js +1 -1
  134. package/clis/rubygems/utils.js +1 -1
  135. package/clis/semanticscholar/utils.js +1 -1
  136. package/clis/slock/errors.js +1 -1
  137. package/clis/spotify/spotify.js +1 -1
  138. package/clis/spotify/utils.js +1 -1
  139. package/clis/stackoverflow/utils.js +1 -1
  140. package/clis/steam/utils.js +1 -1
  141. package/clis/substack/auth.js +2 -2
  142. package/clis/suno/generate.js +2 -2
  143. package/clis/suno/list.js +3 -3
  144. package/clis/telegram/send.js +1 -1
  145. package/clis/tiktok/creator-videos.js +1 -1
  146. package/clis/tiktok/publish.js +1 -1
  147. package/clis/tiktok/utils.js +1 -1
  148. package/clis/trae-cn/activity.js +1 -1
  149. package/clis/trae-cn/approve.js +1 -1
  150. package/clis/trae-cn/ask.js +1 -1
  151. package/clis/trae-cn/dump.js +1 -1
  152. package/clis/trae-cn/export.js +1 -1
  153. package/clis/trae-cn/model.js +1 -1
  154. package/clis/trae-cn/new.js +1 -1
  155. package/clis/trae-cn/read.js +1 -1
  156. package/clis/trae-cn/screenshot.js +1 -1
  157. package/clis/trae-cn/select-model.js +1 -1
  158. package/clis/trae-cn/send.js +1 -1
  159. package/clis/trae-cn/setup.js +1 -1
  160. package/clis/trae-cn/status.js +1 -1
  161. package/clis/trae-cn/targets.js +1 -1
  162. package/clis/trae-cn/watch.js +1 -1
  163. package/clis/trae-solo/state-fs.js +1 -1
  164. package/clis/tumblr/publish.js +1 -1
  165. package/clis/tvmaze/utils.js +1 -1
  166. package/clis/twitter/bookmark-folder.js +1 -1
  167. package/clis/twitter/download.js +1 -1
  168. package/clis/twitter/followers.js +2 -2
  169. package/clis/twitter/following.js +2 -2
  170. package/clis/twitter/likes.js +1 -1
  171. package/clis/twitter/list-add-batch.js +1 -1
  172. package/clis/twitter/list-add-core.js +1 -1
  173. package/clis/twitter/list-add.js +1 -1
  174. package/clis/twitter/list-create.js +1 -1
  175. package/clis/twitter/list-delete.js +3 -3
  176. package/clis/twitter/list-remove-batch.js +1 -1
  177. package/clis/twitter/list-remove.js +1 -1
  178. package/clis/twitter/list-tweets.js +1 -1
  179. package/clis/twitter/profile.js +1 -1
  180. package/clis/twitter/search.js +1 -1
  181. package/clis/twitter/shared.js +1 -1
  182. package/clis/twitter/tweets.js +1 -1
  183. package/clis/uisdc/news.js +1 -1
  184. package/clis/v2ex/daily.js +2 -2
  185. package/clis/v2ex/me.js +2 -2
  186. package/clis/v2ex/notifications.js +1 -1
  187. package/clis/wikidata/utils.js +1 -1
  188. package/clis/wikipedia/page.js +2 -2
  189. package/clis/wikipedia/summary.js +1 -1
  190. package/clis/wikipedia/utils.js +1 -1
  191. package/clis/wordpress/login.js +1 -1
  192. package/clis/wordpress/publish.js +1 -1
  193. package/clis/wordpress/shared.js +3 -3
  194. package/clis/wordpress/sites.js +1 -0
  195. package/clis/wordpress/whoami.js +1 -1
  196. package/clis/woshipm/stats.js +1 -1
  197. package/clis/xiaoe/content.js +1 -1
  198. package/clis/xiaohongshu/draft-delete.js +1 -1
  199. package/clis/xiaohongshu/draft-open.js +1 -1
  200. package/clis/xiaohongshu/draft-utils.js +2 -2
  201. package/clis/xiaohongshu/publish-video.js +1 -1
  202. package/clis/xiaohongshu/publish.js +1 -1
  203. package/clis/xiaoyuzhou/auth.js +1 -1
  204. package/clis/xiaoyuzhou/transcript.js +1 -1
  205. package/clis/yollomi/background.js +1 -1
  206. package/clis/yollomi/edit.js +1 -1
  207. package/clis/yollomi/generate.js +1 -1
  208. package/clis/yollomi/try-on.js +1 -1
  209. package/clis/youtube/publish.js +1 -1
  210. package/clis/zhihu/answer-comments.js +1 -1
  211. package/clis/zhihu/answer-detail.js +1 -1
  212. package/clis/zhihu/collection.js +2 -2
  213. package/clis/zhihu/collections.js +1 -1
  214. package/clis/zhihu/question.js +1 -1
  215. package/clis/zhihu/search.js +1 -1
  216. package/clis/zhihu/target.js +1 -1
  217. package/clis/zhihu/user-arg.js +1 -1
  218. package/dist/src/adapter-shadow.js +1 -1
  219. package/dist/src/browser/analyze.js +1 -1
  220. package/dist/src/browser/article-extract.d.ts +1 -1
  221. package/dist/src/browser/base-page.js +3 -3
  222. package/dist/src/browser/cdp.js +1 -1
  223. package/dist/src/browser/daemon-client.d.ts +1 -1
  224. package/dist/src/browser/daemon-lifecycle.js +6 -6
  225. package/dist/src/browser/daemon-version.js +1 -1
  226. package/dist/src/browser/dom-snapshot.d.ts +1 -1
  227. package/dist/src/browser/dom-snapshot.js +2 -2
  228. package/dist/src/browser/errors.js +2 -2
  229. package/dist/src/browser/network-cache.js +1 -1
  230. package/dist/src/browser/profile.js +1 -1
  231. package/dist/src/browser/target-resolver.js +4 -4
  232. package/dist/src/browser/verify-fixture.js +1 -1
  233. package/dist/src/browser-session-lock.js +1 -1
  234. package/dist/src/build-manifest.d.ts +1 -1
  235. package/dist/src/build-manifest.js +1 -1
  236. package/dist/src/cli-argv-preprocess.d.ts +4 -4
  237. package/dist/src/cli-argv-preprocess.js +1 -1
  238. package/dist/src/cli.d.ts +1 -1
  239. package/dist/src/cli.js +19 -19
  240. package/dist/src/commanderAdapter.js +1 -1
  241. package/dist/src/commands/auth.js +2 -2
  242. package/dist/src/commands/daemon.d.ts +4 -4
  243. package/dist/src/commands/daemon.js +1 -1
  244. package/dist/src/completion-shared.js +12 -12
  245. package/dist/src/completion.d.ts +2 -2
  246. package/dist/src/constants.d.ts +1 -1
  247. package/dist/src/constants.js +1 -1
  248. package/dist/src/daemon-utils.d.ts +1 -1
  249. package/dist/src/daemon-utils.js +1 -1
  250. package/dist/src/daemon.d.ts +2 -2
  251. package/dist/src/daemon.js +1 -1
  252. package/dist/src/discovery.d.ts +7 -7
  253. package/dist/src/discovery.js +2 -2
  254. package/dist/src/doctor.d.ts +1 -1
  255. package/dist/src/doctor.js +6 -6
  256. package/dist/src/electron-apps.d.ts +1 -1
  257. package/dist/src/electron-apps.js +1 -1
  258. package/dist/src/errors.d.ts +2 -2
  259. package/dist/src/errors.js +1 -1
  260. package/dist/src/execution.js +1 -1
  261. package/dist/src/external.d.ts +1 -1
  262. package/dist/src/external.js +1 -1
  263. package/dist/src/help.d.ts +1 -1
  264. package/dist/src/help.js +3 -3
  265. package/dist/src/hooks.d.ts +1 -1
  266. package/dist/src/launcher.js +2 -2
  267. package/dist/src/logger.d.ts +1 -1
  268. package/dist/src/main.d.ts +1 -1
  269. package/dist/src/main.js +1 -1
  270. package/dist/src/observation/artifact.js +2 -2
  271. package/dist/src/package-paths.js +1 -1
  272. package/dist/src/package-paths.test.d.ts +1 -0
  273. package/dist/src/plugin-manifest.d.ts +12 -3
  274. package/dist/src/plugin-manifest.js +1 -1
  275. package/dist/src/plugin-scaffold.d.ts +2 -2
  276. package/dist/src/plugin-scaffold.js +9 -9
  277. package/dist/src/plugin.d.ts +4 -4
  278. package/dist/src/plugin.js +10 -10
  279. package/dist/src/rate-limit.js +1 -1
  280. package/dist/src/registry-api.d.ts +1 -1
  281. package/dist/src/runtime-detect.d.ts +1 -1
  282. package/dist/src/serialization.js +1 -1
  283. package/dist/src/skills.js +6 -6
  284. package/dist/src/user-runtime.d.ts +8 -0
  285. package/dist/src/user-runtime.js +1 -0
  286. package/dist/src/user-runtime.test.d.ts +1 -0
  287. package/dist/src/validate.js +1 -1
  288. package/dist/src/validate.test.d.ts +1 -1
  289. package/docs/guide/browser-bridge.md +23 -23
  290. package/docs/guide/electron-app-cli.md +3 -3
  291. package/docs/guide/exit-codes.md +3 -3
  292. package/docs/guide/extending-opencli.md +37 -37
  293. package/docs/guide/getting-started.md +19 -19
  294. package/docs/guide/installation.md +6 -6
  295. package/docs/guide/plugins.md +32 -32
  296. package/docs/guide/remote-orchestration.md +5 -5
  297. package/docs/guide/troubleshooting.md +9 -9
  298. package/package.json +3 -2
  299. package/scripts/fetch-adapters.js +6 -6
  300. package/scripts/postinstall.js +14 -14
  301. package/skills/opencli-adapter-author/SKILL.md +24 -24
  302. package/skills/opencli-adapter-author/references/adapter-template.md +13 -13
  303. package/skills/opencli-adapter-author/references/api-discovery.md +25 -25
  304. package/skills/opencli-adapter-author/references/coverage-matrix.md +3 -3
  305. package/skills/opencli-adapter-author/references/field-decode-playbook.md +10 -10
  306. package/skills/opencli-adapter-author/references/jsdom-fixture-pattern.md +2 -2
  307. package/skills/opencli-adapter-author/references/site-memory.md +11 -11
  308. package/skills/opencli-adapter-author/references/site-recon.md +7 -7
  309. package/skills/opencli-adapter-author/references/strategy-selection.md +3 -3
  310. package/skills/opencli-adapter-author/references/success-rate-pitfalls.md +4 -4
  311. package/skills/opencli-adapter-author/references/typed-errors.md +2 -2
  312. package/skills/opencli-autofix/SKILL.md +29 -29
  313. package/skills/opencli-browser/SKILL.md +55 -55
  314. package/skills/opencli-browser-sitemap/SKILL.md +7 -7
  315. package/skills/opencli-sitemap-author/SKILL.md +11 -11
  316. package/skills/opencli-sitemap-author/references/sitemap-schema.md +19 -19
  317. package/skills/opencli-usage/SKILL.md +43 -43
package/dist/src/help.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import w from"js-yaml";import{fullName as N}from"./registry.js";import{formatCommandExample as k}from"./serialization.js";const I=[{flags:"-f, --format <fmt>",name:"format",help:"Output format: table, plain, json, yaml, md, csv",default:"table",choices:["table","plain","json","yaml","md","csv"]},{flags:"--trace <mode>",name:"trace",help:"Trace capture: off, on, retain-on-failure",default:"off",choices:["off","on","retain-on-failure"]},{flags:"-v, --verbose",name:"verbose",help:"Debug output",default:!1},{flags:"-h, --help",name:"help",help:"display help for command"}],E=[{flags:"--window <mode>",name:"window",help:"Browser window mode: foreground or background",choices:["foreground","background"]},{flags:"--site-session <mode>",name:"site-session",help:"Adapter site session lifecycle: ephemeral or persistent",choices:["ephemeral","persistent"]},{flags:"--keep-tab <bool>",name:"keep-tab",help:"Keep the browser tab lease after the command finishes",choices:["true","false"]}];function _(j){const y=j?.toLowerCase();if(y==="yaml"||y==="yml")return"yaml";if(y==="json")return"json";return}export function getRequestedHelpFormat(j=process.argv){for(let y=0;y<j.length;y++){const G=j[y];if(G==="-f"||G==="--format")return _(j[y+1]);if(G.startsWith("--format="))return _(G.slice(9));if(G.startsWith("-f")&&G.length>2)return _(G.slice(2))}return}export function renderStructuredHelp(j,y){if(y==="json")return`${JSON.stringify(j,null,2)}
2
2
  `;return w.dump(j,{sortKeys:!1,lineWidth:120,noRefs:!0})}export function wrapCommaList(j,y={}){const G=Math.max(y.width??process.stdout.columns??100,40),K=y.indent??" ",J=[...j].sort((Z,A)=>Z.localeCompare(A)),Q=[];let V=K;J.forEach((Z,A)=>{const D=`${Z}${A<J.length-1?",":""}`,q=V===K?"":" ";if(V.length+q.length+D.length>G&&V.trim()){Q.push(V);V=`${K}${D}`}else V+=`${q}${D}`});if(V.trim())Q.push(V);return Q.join(`
3
- `)}function h(j){if(j==="::1"||j==="[::1]")return!0;const y=j.split(".");if(y.length!==4)return!1;return y.every((G)=>/^\d+$/.test(G)&&Number(G)>=0&&Number(G)<=255)&&Number(y[0])===127}export function classifyAdapter(j){if(!j)return"site";if(h(j))return"app";return j.includes(".")?"site":"app"}function F(j,y){if(y.length===0)return[];return[`${j} (${y.length}):`,wrapCommaList(y),""]}export function formatRootAdapterHelpText(j){if(j.external.length+j.apps.length+j.sites.length===0)return"";const G=[""];G.push(...F("External CLIs",j.external.map((K)=>K.label)));G.push(...F("App adapters",j.apps));G.push(...F("Site adapters",j.sites));G.push("Run 'opencli list' for full command details, or 'opencli <site> --help' to inspect one site.");G.push("Agent tip: use 'opencli <site> --help -f yaml' for all command args/options in one structured response.");G.push("");return G.join(`
4
- `)}function H(j){return{name:j.name,...j.type&&j.type!=="string"?{type:j.type}:{},...j.positional?{positional:!0}:{},...j.required?{required:!0}:{},...j.valueRequired?{valueRequired:!0}:{},...j.default!==void 0?{default:j.default}:{},...j.choices?.length?{choices:j.choices}:{},...j.help?{help:j.help}:{}}}function $(j){return{name:j.name,flags:j.flags,help:j.help,..."default"in j?{default:j.default}:{},..."choices"in j?{choices:j.choices}:{}}}function x(j){return{name:j.name(),...j.required?{required:!0}:{},...j.variadic?{variadic:!0}:{},...j.description?{help:j.description}:{},...j.defaultValue!==void 0?{default:j.defaultValue}:{},...j.argChoices?.length?{choices:[...j.argChoices]}:{}}}function f(j){if(j.hidden)return null;return{name:j.attributeName(),flags:j.flags,...j.description?{help:j.description}:{},...j.required?{takes_value:"required"}:{},...j.optional?{takes_value:"optional"}:{},...j.mandatory?{required:!0}:{},...j.defaultValue!==void 0?{default:j.defaultValue}:{},...j.argChoices?.length?{choices:[...j.argChoices]}:{},...j.negate?{negate:!0}:{}}}function W(j){return j.map(f).filter((y)=>y!==null)}export function leadingPositionalFromUsage(j){const y=j._usage;if(!y)return null;const G=y.match(/^\s*(<[^>]+>)/);return G?G[1]:null}function X(j){const y=[];let G=j;while(G){const K=G.name();if(K){y.push(K);const J=leadingPositionalFromUsage(G);if(J&&y.length>1)y.splice(y.length-1,0,J)}G=G.parent}return y.reverse()}function Y(j,y){const G=X(j);return X(y).slice(G.length).filter((J)=>!/^<.+>$/.test(J))}function z(j){if(j.commands.length===0)return[j];return j.commands.flatMap((y)=>z(y))}function b(j){return j.commands.flatMap((y)=>[y,...b(y)])}function O(j){return j.map((y)=>{const G=`${y.name()}${y.variadic?"...":""}`;return y.required?`<${G}>`:`[${G}]`}).join(" ")}function C(j,y={}){const G=X(j).join(" "),K=O(j.registeredArguments),Q=W(j.options).length>0||(y.namespaceRoot?W(y.namespaceRoot.options).length>0:!1)||(y.globalCommand?W(y.globalCommand.options).length>0:!1)?" [options]":"";return`${G}${K?` ${K}`:""}${Q}`}function L(j,y,G={}){return{name:Y(j,y).join(" "),command:X(y).join(" "),usage:C(y,{namespaceRoot:j,globalCommand:G.globalCommand}),description:y.description(),...y.aliases().length?{aliases:y.aliases()}:{},positionals:y.registeredArguments.map(x),command_options:W(y.options)}}export function commanderNamespaceHelpData(j,y={}){const G=z(j).filter((V)=>V!==j).sort((V,Z)=>Y(j,V).join(" ").localeCompare(Y(j,Z).join(" "))),K=X(j).join(" "),J=j._usage,Q=J?`${K} ${J}`:`${K} <command> [args] [options]`;return{namespace:j.name(),command:K,usage:Q,description:y.description??j.description(),command_count:G.length,commands:G.map((V)=>L(j,V,y)),namespace_options:W(j.options),...y.globalCommand?{global_options:W(y.globalCommand.options)}:{},structured_help:{formats:["yaml","json"],usage:`${K} --help -f yaml`}}}export function commanderCommandHelpData(j,y,G={}){return{namespace:j.name(),...L(j,y,G),namespace_options:W(j.options),...G.globalCommand?{global_options:W(G.globalCommand.options)}:{},structured_help:{formats:["yaml","json"],usage:`${X(y).join(" ")} --help -f yaml`}}}export function commanderGroupHelpData(j,y,G={}){const K=z(y).filter((J)=>J!==y).sort((J,Q)=>Y(j,J).join(" ").localeCompare(Y(j,Q).join(" ")));return{namespace:j.name(),group:Y(j,y).join(" "),command:X(y).join(" "),usage:`${X(y).join(" ")} <command> [args] [options]`,description:y.description(),command_count:K.length,commands:K.map((J)=>L(j,J,G)),namespace_options:W(j.options),...G.globalCommand?{global_options:W(G.globalCommand.options)}:{},structured_help:{formats:["yaml","json"],usage:`${X(y).join(" ")} --help -f yaml`}}}export function installCommanderNamespaceStructuredHelp(j,y={}){installStructuredHelp(j,()=>commanderNamespaceHelpData(j,y));for(const G of b(j))if(G.commands.length>0)installStructuredHelp(G,()=>commanderGroupHelpData(j,G,y));else installStructuredHelp(G,()=>commanderCommandHelpData(j,G,y))}function U(j){return j.args.filter((y)=>y.positional)}function M(j){return j.args.filter((y)=>!y.positional)}function v(j){return j.map((y)=>y.required?`<${y.name}>`:`[${y.name}]`).join(" ")}function R(j){if(j.required||j.valueRequired)return`--${j.name} <value>`;return`--${j.name} [value]`}export function formatCommandListTerm(j){const y=v(U(j)),G=M(j).length>0?" [options]":"";return`${j.name}${y?` ${y}`:""}${G}`}function T(j){const y=v(U(j));return`opencli ${j.site} ${j.name}${y?` ${y}`:""} [options]`}function S(j){return{name:j.name,command:`opencli ${j.site} ${j.name}`,usage:T(j),access:j.access,description:j.description,browser:!!j.browser,...j.domain?{domain:j.domain}:{},...j.aliases?.length?{aliases:j.aliases}:{},positionals:U(j).map(H),command_options:M(j).map(H),...j.browser?{browser_common_options:E.map($)}:{},example:k(j),...j.siteSession?{siteSession:j.siteSession}:{},...j.defaultFormat?{defaultFormat:j.defaultFormat}:{},...j.columns?.length?{columns:j.columns}:{}}}export function rootHelpData(j,y){const G=new Set([...y.external.map((Q)=>Q.name),...y.apps,...y.sites]),K=j.commands.filter((Q)=>!G.has(Q.name())).map((Q)=>({name:Q.name(),description:Q.description()})),J=(Q,V)=>Q.localeCompare(V);return{name:j.name(),description:j.description(),commands:K,external_clis:{count:y.external.length,clis:y.external.map((Q)=>Q.name).sort(J),display:y.external.map((Q)=>Q.label).sort(J)},app_adapters:{count:y.apps.length,apps:[...y.apps].sort(J)},site_adapters:{count:y.sites.length,sites:[...y.sites].sort(J)},next:["opencli <site> --help -f yaml","opencli list -f yaml","opencli <site> <command> -f yaml"]}}export function siteHelpData(j,y){const G=[...new Map(y.map((K)=>[N(K),K])).values()].sort((K,J)=>K.name.localeCompare(J.name));return{site:j,command_count:G.length,commands:G.map((K)=>S(K)),common_options:I.map($),...G.some((K)=>K.browser)?{browser_common_options:E.map($)}:{},next:[`opencli ${j} <command> --help -f yaml`,`opencli ${j} <command> -f yaml`]}}export function commandHelpData(j){return{site:j.site,...S(j),common_options:I.map($),...j.browser?{browser_common_options:E.map($)}:{},output_formats:["table","plain","yaml","json","md","csv"]}}function B(j){if(j.length===0)return[];const y=Math.min(Math.max(...j.map(([G])=>G.length)),34);return j.map(([G,K])=>` ${G.padEnd(y+2)}${K}`)}function P(j){const y=[];if(j.help)y.push(j.help);if(j.default!==void 0)y.push(`default: ${j.default}`);if(j.choices?.length)y.push(`choices: ${j.choices.join(", ")}`);return y.join(" ")}export function formatCommonOptionsHelpText(){const j=I.map((y)=>{const G=[y.help];if("default"in y)G.push(`default: ${y.default}`);if("choices"in y)G.push(`choices: ${y.choices.join(", ")}`);return[y.flags,G.join(" ")]});return["Common options:",...B(j)].join(`
3
+ `)}function h(j){if(j==="::1"||j==="[::1]")return!0;const y=j.split(".");if(y.length!==4)return!1;return y.every((G)=>/^\d+$/.test(G)&&Number(G)>=0&&Number(G)<=255)&&Number(y[0])===127}export function classifyAdapter(j){if(!j)return"site";if(h(j))return"app";return j.includes(".")?"site":"app"}function F(j,y){if(y.length===0)return[];return[`${j} (${y.length}):`,wrapCommaList(y),""]}export function formatRootAdapterHelpText(j){if(j.external.length+j.apps.length+j.sites.length===0)return"";const G=[""];G.push(...F("External CLIs",j.external.map((K)=>K.label)));G.push(...F("App adapters",j.apps));G.push(...F("Site adapters",j.sites));G.push("Run 'ppcli list' for full command details, or 'ppcli <site> --help' to inspect one site.");G.push("Agent tip: use 'ppcli <site> --help -f yaml' for all command args/options in one structured response.");G.push("");return G.join(`
4
+ `)}function H(j){return{name:j.name,...j.type&&j.type!=="string"?{type:j.type}:{},...j.positional?{positional:!0}:{},...j.required?{required:!0}:{},...j.valueRequired?{valueRequired:!0}:{},...j.default!==void 0?{default:j.default}:{},...j.choices?.length?{choices:j.choices}:{},...j.help?{help:j.help}:{}}}function $(j){return{name:j.name,flags:j.flags,help:j.help,..."default"in j?{default:j.default}:{},..."choices"in j?{choices:j.choices}:{}}}function x(j){return{name:j.name(),...j.required?{required:!0}:{},...j.variadic?{variadic:!0}:{},...j.description?{help:j.description}:{},...j.defaultValue!==void 0?{default:j.defaultValue}:{},...j.argChoices?.length?{choices:[...j.argChoices]}:{}}}function f(j){if(j.hidden)return null;return{name:j.attributeName(),flags:j.flags,...j.description?{help:j.description}:{},...j.required?{takes_value:"required"}:{},...j.optional?{takes_value:"optional"}:{},...j.mandatory?{required:!0}:{},...j.defaultValue!==void 0?{default:j.defaultValue}:{},...j.argChoices?.length?{choices:[...j.argChoices]}:{},...j.negate?{negate:!0}:{}}}function W(j){return j.map(f).filter((y)=>y!==null)}export function leadingPositionalFromUsage(j){const y=j._usage;if(!y)return null;const G=y.match(/^\s*(<[^>]+>)/);return G?G[1]:null}function X(j){const y=[];let G=j;while(G){const K=G.name();if(K){y.push(K);const J=leadingPositionalFromUsage(G);if(J&&y.length>1)y.splice(y.length-1,0,J)}G=G.parent}return y.reverse()}function Y(j,y){const G=X(j);return X(y).slice(G.length).filter((J)=>!/^<.+>$/.test(J))}function z(j){if(j.commands.length===0)return[j];return j.commands.flatMap((y)=>z(y))}function b(j){return j.commands.flatMap((y)=>[y,...b(y)])}function O(j){return j.map((y)=>{const G=`${y.name()}${y.variadic?"...":""}`;return y.required?`<${G}>`:`[${G}]`}).join(" ")}function C(j,y={}){const G=X(j).join(" "),K=O(j.registeredArguments),Q=W(j.options).length>0||(y.namespaceRoot?W(y.namespaceRoot.options).length>0:!1)||(y.globalCommand?W(y.globalCommand.options).length>0:!1)?" [options]":"";return`${G}${K?` ${K}`:""}${Q}`}function L(j,y,G={}){return{name:Y(j,y).join(" "),command:X(y).join(" "),usage:C(y,{namespaceRoot:j,globalCommand:G.globalCommand}),description:y.description(),...y.aliases().length?{aliases:y.aliases()}:{},positionals:y.registeredArguments.map(x),command_options:W(y.options)}}export function commanderNamespaceHelpData(j,y={}){const G=z(j).filter((V)=>V!==j).sort((V,Z)=>Y(j,V).join(" ").localeCompare(Y(j,Z).join(" "))),K=X(j).join(" "),J=j._usage,Q=J?`${K} ${J}`:`${K} <command> [args] [options]`;return{namespace:j.name(),command:K,usage:Q,description:y.description??j.description(),command_count:G.length,commands:G.map((V)=>L(j,V,y)),namespace_options:W(j.options),...y.globalCommand?{global_options:W(y.globalCommand.options)}:{},structured_help:{formats:["yaml","json"],usage:`${K} --help -f yaml`}}}export function commanderCommandHelpData(j,y,G={}){return{namespace:j.name(),...L(j,y,G),namespace_options:W(j.options),...G.globalCommand?{global_options:W(G.globalCommand.options)}:{},structured_help:{formats:["yaml","json"],usage:`${X(y).join(" ")} --help -f yaml`}}}export function commanderGroupHelpData(j,y,G={}){const K=z(y).filter((J)=>J!==y).sort((J,Q)=>Y(j,J).join(" ").localeCompare(Y(j,Q).join(" ")));return{namespace:j.name(),group:Y(j,y).join(" "),command:X(y).join(" "),usage:`${X(y).join(" ")} <command> [args] [options]`,description:y.description(),command_count:K.length,commands:K.map((J)=>L(j,J,G)),namespace_options:W(j.options),...G.globalCommand?{global_options:W(G.globalCommand.options)}:{},structured_help:{formats:["yaml","json"],usage:`${X(y).join(" ")} --help -f yaml`}}}export function installCommanderNamespaceStructuredHelp(j,y={}){installStructuredHelp(j,()=>commanderNamespaceHelpData(j,y));for(const G of b(j))if(G.commands.length>0)installStructuredHelp(G,()=>commanderGroupHelpData(j,G,y));else installStructuredHelp(G,()=>commanderCommandHelpData(j,G,y))}function U(j){return j.args.filter((y)=>y.positional)}function M(j){return j.args.filter((y)=>!y.positional)}function v(j){return j.map((y)=>y.required?`<${y.name}>`:`[${y.name}]`).join(" ")}function R(j){if(j.required||j.valueRequired)return`--${j.name} <value>`;return`--${j.name} [value]`}export function formatCommandListTerm(j){const y=v(U(j)),G=M(j).length>0?" [options]":"";return`${j.name}${y?` ${y}`:""}${G}`}function T(j){const y=v(U(j));return`ppcli ${j.site} ${j.name}${y?` ${y}`:""} [options]`}function S(j){return{name:j.name,command:`ppcli ${j.site} ${j.name}`,usage:T(j),access:j.access,description:j.description,browser:!!j.browser,...j.domain?{domain:j.domain}:{},...j.aliases?.length?{aliases:j.aliases}:{},positionals:U(j).map(H),command_options:M(j).map(H),...j.browser?{browser_common_options:E.map($)}:{},example:k(j),...j.siteSession?{siteSession:j.siteSession}:{},...j.defaultFormat?{defaultFormat:j.defaultFormat}:{},...j.columns?.length?{columns:j.columns}:{}}}export function rootHelpData(j,y){const G=new Set([...y.external.map((Q)=>Q.name),...y.apps,...y.sites]),K=j.commands.filter((Q)=>!G.has(Q.name())).map((Q)=>({name:Q.name(),description:Q.description()})),J=(Q,V)=>Q.localeCompare(V);return{name:j.name(),description:j.description(),commands:K,external_clis:{count:y.external.length,clis:y.external.map((Q)=>Q.name).sort(J),display:y.external.map((Q)=>Q.label).sort(J)},app_adapters:{count:y.apps.length,apps:[...y.apps].sort(J)},site_adapters:{count:y.sites.length,sites:[...y.sites].sort(J)},next:["ppcli <site> --help -f yaml","ppcli list -f yaml","ppcli <site> <command> -f yaml"]}}export function siteHelpData(j,y){const G=[...new Map(y.map((K)=>[N(K),K])).values()].sort((K,J)=>K.name.localeCompare(J.name));return{site:j,command_count:G.length,commands:G.map((K)=>S(K)),common_options:I.map($),...G.some((K)=>K.browser)?{browser_common_options:E.map($)}:{},next:[`ppcli ${j} <command> --help -f yaml`,`ppcli ${j} <command> -f yaml`]}}export function commandHelpData(j){return{site:j.site,...S(j),common_options:I.map($),...j.browser?{browser_common_options:E.map($)}:{},output_formats:["table","plain","yaml","json","md","csv"]}}function B(j){if(j.length===0)return[];const y=Math.min(Math.max(...j.map(([G])=>G.length)),34);return j.map(([G,K])=>` ${G.padEnd(y+2)}${K}`)}function P(j){const y=[];if(j.help)y.push(j.help);if(j.default!==void 0)y.push(`default: ${j.default}`);if(j.choices?.length)y.push(`choices: ${j.choices.join(", ")}`);return y.join(" ")}export function formatCommonOptionsHelpText(){const j=I.map((y)=>{const G=[y.help];if("default"in y)G.push(`default: ${y.default}`);if("choices"in y)G.push(`choices: ${y.choices.join(", ")}`);return[y.flags,G.join(" ")]});return["Common options:",...B(j)].join(`
5
5
  `)}export function formatBrowserCommonOptionsHelpText(){const j=E.map((y)=>{const G=[y.help];if("choices"in y)G.push(`choices: ${y.choices.join(", ")}`);return[y.flags,G.join(" ")]});return["Browser common options:",...B(j)].join(`
6
- `)}export function formatSiteHelpText(j,y){const G=[...new Map(y.map((J)=>[N(J),J])).values()].sort((J,Q)=>J.name.localeCompare(Q.name));return[`Usage: opencli ${j} <command> [args] [options]`,"",wrapCommaList(G.map((J)=>J.name),{indent:""}),"","Commands:",...B(G.map((J)=>[formatCommandListTerm(J),formatSiteCommandDescription(J)])),"",formatCommonOptionsHelpText(),...G.some((J)=>J.browser)?["",formatBrowserCommonOptionsHelpText()]:[],"",`Agent tip: use 'opencli ${j} --help -f yaml' to get all command args/options in one structured response.`,""].join(`
6
+ `)}export function formatSiteHelpText(j,y){const G=[...new Map(y.map((J)=>[N(J),J])).values()].sort((J,Q)=>J.name.localeCompare(Q.name));return[`Usage: ppcli ${j} <command> [args] [options]`,"",wrapCommaList(G.map((J)=>J.name),{indent:""}),"","Commands:",...B(G.map((J)=>[formatCommandListTerm(J),formatSiteCommandDescription(J)])),"",formatCommonOptionsHelpText(),...G.some((J)=>J.browser)?["",formatBrowserCommonOptionsHelpText()]:[],"",`Agent tip: use 'ppcli ${j} --help -f yaml' to get all command args/options in one structured response.`,""].join(`
7
7
  `)}export function formatCommandHelpText(j){const y=[`Usage: ${T(j)}`,"",j.description,""],G=U(j).map((Q)=>[Q.name,P(Q)]);if(G.length)y.push("Arguments:",...B(G),"");const K=M(j).map((Q)=>[R(Q),P(Q)]);if(K.length)y.push("Command options:",...B(K),"");y.push(formatCommonOptionsHelpText(),"");if(j.browser)y.push(formatBrowserCommonOptionsHelpText(),"");const J=[];J.push(`Access: ${j.access}`);J.push(`Browser: ${j.browser?"yes":"no"}`);if(j.domain)J.push(`Domain: ${j.domain}`);if(j.defaultFormat)J.push(`Default format: ${j.defaultFormat}`);if(j.aliases?.length)J.push(`Aliases: ${j.aliases.join(", ")}`);y.push(J.join(" | "));y.push(`Example: ${k(j)}`);if(j.columns?.length)y.push(`Output columns: ${j.columns.join(", ")}`);y.push("Agent tip: use '--help -f yaml' for structured args/options.");y.push("");return y.join(`
8
8
  `)}export function installStructuredHelp(j,y,G){const K=j.helpInformation.bind(j);j.helpInformation=(J)=>{const Q=getRequestedHelpFormat();if(Q)return renderStructuredHelp(y(),Q);const V=typeof G==="function"?G():G??"";return K(J)+V}}export function formatSiteCommandDescription(j){return`${j.access==="write"?"[write]":"[read]"} ${j.description}`}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Plugin lifecycle hooks: allows plugins to tap into opencli's execution lifecycle.
2
+ * Plugin lifecycle hooks: allows plugins to tap into ppcli's execution lifecycle.
3
3
  *
4
4
  * Hooks use globalThis (like the command registry) to guarantee a single shared
5
5
  * instance across all module copies — critical when TS plugins are loaded via
@@ -1,7 +1,7 @@
1
- import{execFileSync as G,spawn as w}from"node:child_process";import{request as D}from"node:http";import*as K from"node:path";import{getElectronApp as U}from"./electron-apps.js";import{confirmPrompt as q}from"./tui.js";import{CommandExecutionError as $}from"./errors.js";import{log as H}from"./logger.js";const F=500,V=15000,I=2000,R=3000;export function probeCDP(z,Q=I){return new Promise((J)=>{const W=D({hostname:"127.0.0.1",port:z,path:"/json",method:"GET",timeout:Q},(Y)=>{Y.resume();J(Y.statusCode!==void 0&&Y.statusCode>=200&&Y.statusCode<300)});W.on("error",()=>J(!1));W.on("timeout",()=>{W.destroy();J(!1)});W.end()})}export function detectProcess(z){if(process.platform==="win32")return!1;try{G("pgrep",["-x",z],{encoding:"utf-8",stdio:"pipe"});return!0}catch{return!1}}export async function killProcess(z){if(process.platform==="win32")return;try{G("pkill",["-x",z],{stdio:"pipe"})}catch{}const Q=Date.now()+R;while(Date.now()<Q){if(!detectProcess(z))return;await new Promise((J)=>setTimeout(J,200))}try{G("pkill",["-9","-x",z],{stdio:"pipe"})}catch{}}export function discoverAppPath(z){if(process.platform!=="darwin")return null;try{return G("osascript",["-e",`POSIX path of (path to application "${z}")`],{encoding:"utf-8",stdio:"pipe",timeout:5000}).trim().replace(/\/$/,"")}catch{return null}}function O(z,Q){return`${z}/Contents/MacOS/${Q}`}function T(z,Q){return z instanceof $&&z.message.startsWith(`Could not launch ${Q}: executable not found at `)}export function resolveExecutableCandidates(z,Q){const J=Q.executableNames?.length?Q.executableNames:[Q.processName];return[...new Set(J)].map((W)=>O(z,W))}export async function launchDetachedApp(z,Q,J,W){await new Promise((Y,X)=>{const Z=w(z,Q,{detached:!0,stdio:"ignore",...W?{env:{...process.env,...W}}:{}}),k=(B)=>{if(B.code==="ENOENT"){X(new $(`Could not launch ${J}: executable not found at ${z}`,`Install ${J}, reinstall it, or register a custom app path in ~/.opencli/apps.yaml`));return}X(new $(`Failed to launch ${J}`,B.message))};Z.once("error",k);Z.once("spawn",()=>{Z.off("error",k);Z.unref();Y()})})}export async function launchElectronApp(z,Q,J,W){const Y=resolveExecutableCandidates(z,Q);let X;for(const Z of Y){H.debug(`[launcher] Launching: ${Z} ${J.join(" ")}`);try{await launchDetachedApp(Z,J,W);return}catch(k){if(T(k,W)){X=k;continue}throw k}}if(Y.length>1)throw new $(`Could not launch ${W}: no compatible executable found in ${K.join(z,"Contents","MacOS")}`,`Tried: ${Y.map((Z)=>K.basename(Z)).join(", ")}. Install ${W}, reinstall it, or register a custom app path in ~/.opencli/apps.yaml`);throw X??new $(`Could not launch ${W}`,`Install ${W}, reinstall it, or register a custom app path in ~/.opencli/apps.yaml`)}export function electronLaunchArgs(z,Q=[]){return[`--remote-debugging-port=${z}`,"--remote-allow-origins=*",...Q]}function j(z,Q){return`Start ${z} manually with --remote-debugging-port=${Q} --remote-allow-origins=*, then either:`}async function y(z){const Q=Date.now()+V;while(Date.now()<Q){if(await probeCDP(z,1000))return;await new Promise((J)=>setTimeout(J,F))}throw new $(`App launched but CDP not available on port ${z} after ${V/1000}s`,"The app may be slow to start. Try running the command again.")}export async function resolveElectronEndpoint(z){const Q=U(z);if(!Q)throw new $(`No Electron app registered for site "${z}"`,"Register the app in ~/.opencli/apps.yaml or check the site name.");const{port:J,processName:W,displayName:Y}=Q,X=Y??W,Z=`http://127.0.0.1:${J}`;H.debug(`[launcher] Probing CDP on port ${J}...`);if(await probeCDP(J)){H.debug(`[launcher] CDP already available on port ${J}`);return Z}if(process.platform!=="darwin")throw new $(`${X} is not reachable on CDP port ${J}.`,`Auto-launch is not yet supported on ${process.platform}.
1
+ import{execFileSync as G,spawn as w}from"node:child_process";import{request as D}from"node:http";import*as K from"node:path";import{getElectronApp as U}from"./electron-apps.js";import{confirmPrompt as q}from"./tui.js";import{CommandExecutionError as $}from"./errors.js";import{log as H}from"./logger.js";const F=500,V=15000,I=2000,R=3000;export function probeCDP(z,Q=I){return new Promise((J)=>{const W=D({hostname:"127.0.0.1",port:z,path:"/json",method:"GET",timeout:Q},(Y)=>{Y.resume();J(Y.statusCode!==void 0&&Y.statusCode>=200&&Y.statusCode<300)});W.on("error",()=>J(!1));W.on("timeout",()=>{W.destroy();J(!1)});W.end()})}export function detectProcess(z){if(process.platform==="win32")return!1;try{G("pgrep",["-x",z],{encoding:"utf-8",stdio:"pipe"});return!0}catch{return!1}}export async function killProcess(z){if(process.platform==="win32")return;try{G("pkill",["-x",z],{stdio:"pipe"})}catch{}const Q=Date.now()+R;while(Date.now()<Q){if(!detectProcess(z))return;await new Promise((J)=>setTimeout(J,200))}try{G("pkill",["-9","-x",z],{stdio:"pipe"})}catch{}}export function discoverAppPath(z){if(process.platform!=="darwin")return null;try{return G("osascript",["-e",`POSIX path of (path to application "${z}")`],{encoding:"utf-8",stdio:"pipe",timeout:5000}).trim().replace(/\/$/,"")}catch{return null}}function O(z,Q){return`${z}/Contents/MacOS/${Q}`}function T(z,Q){return z instanceof $&&z.message.startsWith(`Could not launch ${Q}: executable not found at `)}export function resolveExecutableCandidates(z,Q){const J=Q.executableNames?.length?Q.executableNames:[Q.processName];return[...new Set(J)].map((W)=>O(z,W))}export async function launchDetachedApp(z,Q,J,W){await new Promise((Y,X)=>{const Z=w(z,Q,{detached:!0,stdio:"ignore",...W?{env:{...process.env,...W}}:{}}),k=(B)=>{if(B.code==="ENOENT"){X(new $(`Could not launch ${J}: executable not found at ${z}`,`Install ${J}, reinstall it, or register a custom app path in ~/.ppcli/apps.yaml`));return}X(new $(`Failed to launch ${J}`,B.message))};Z.once("error",k);Z.once("spawn",()=>{Z.off("error",k);Z.unref();Y()})})}export async function launchElectronApp(z,Q,J,W){const Y=resolveExecutableCandidates(z,Q);let X;for(const Z of Y){H.debug(`[launcher] Launching: ${Z} ${J.join(" ")}`);try{await launchDetachedApp(Z,J,W);return}catch(k){if(T(k,W)){X=k;continue}throw k}}if(Y.length>1)throw new $(`Could not launch ${W}: no compatible executable found in ${K.join(z,"Contents","MacOS")}`,`Tried: ${Y.map((Z)=>K.basename(Z)).join(", ")}. Install ${W}, reinstall it, or register a custom app path in ~/.ppcli/apps.yaml`);throw X??new $(`Could not launch ${W}`,`Install ${W}, reinstall it, or register a custom app path in ~/.ppcli/apps.yaml`)}export function electronLaunchArgs(z,Q=[]){return[`--remote-debugging-port=${z}`,"--remote-allow-origins=*",...Q]}function j(z,Q){return`Start ${z} manually with --remote-debugging-port=${Q} --remote-allow-origins=*, then either:`}async function y(z){const Q=Date.now()+V;while(Date.now()<Q){if(await probeCDP(z,1000))return;await new Promise((J)=>setTimeout(J,F))}throw new $(`App launched but CDP not available on port ${z} after ${V/1000}s`,"The app may be slow to start. Try running the command again.")}export async function resolveElectronEndpoint(z){const Q=U(z);if(!Q)throw new $(`No Electron app registered for site "${z}"`,"Register the app in ~/.ppcli/apps.yaml or check the site name.");const{port:J,processName:W,displayName:Y}=Q,X=Y??W,Z=`http://127.0.0.1:${J}`;H.debug(`[launcher] Probing CDP on port ${J}...`);if(await probeCDP(J)){H.debug(`[launcher] CDP already available on port ${J}`);return Z}if(process.platform!=="darwin")throw new $(`${X} is not reachable on CDP port ${J}.`,`Auto-launch is not yet supported on ${process.platform}.
2
2
  ${j(X,J)}
3
3
  `+` • Set OPENCLI_CDP_ENDPOINT=http://127.0.0.1:${J}
4
4
  `+` • Or just re-run the command once ${X} is listening on port ${J}.`);if(detectProcess(W)){H.debug(`[launcher] ${X} is running but CDP not available`);if(!await q(`${X} is running but CDP is not enabled. Restart with debug port?`,!0))throw new $(`${X} needs to be restarted with CDP enabled.`,`Manually restart: kill the app and relaunch with --remote-debugging-port=${J} --remote-allow-origins=*`);process.stderr.write(` Restarting ${X}...
5
- `);await killProcess(W)}const B=discoverAppPath(X);if(!B)throw new $(`Could not find ${X} on this machine.`,`Install ${X} or register a custom path in ~/.opencli/apps.yaml`);const f=electronLaunchArgs(J,Q.extraArgs??[]);await launchElectronApp(B,Q,f,X);process.stderr.write(` Waiting for ${X} on port ${J}...
5
+ `);await killProcess(W)}const B=discoverAppPath(X);if(!B)throw new $(`Could not find ${X} on this machine.`,`Install ${X} or register a custom path in ~/.ppcli/apps.yaml`);const f=electronLaunchArgs(J,Q.extraArgs??[]);await launchElectronApp(B,Q,f,X);process.stderr.write(` Waiting for ${X} on port ${J}...
6
6
  `);await y(J);process.stderr.write(` Connected to ${X} on port ${J}.
7
7
  `);return Z}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Unified logging for opencli.
2
+ * Unified logging for ppcli.
3
3
  *
4
4
  * All framework output (warnings, debug info, errors) should go through
5
5
  * this module so that verbosity levels are respected consistently.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * opencli — Make any website your CLI. AI-powered.
3
+ * ppcli — Make any website your CLI. AI-powered.
4
4
  */
5
5
  export {};
package/dist/src/main.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- if(process.platform!=="win32"){const q=["/usr/local/bin","/usr/bin","/bin","/usr/sbin","/sbin"],Q=new Set((process.env.PATH??"").split(":").filter(Boolean));for(const z of q)Q.add(z);process.env.PATH=[...Q].join(":")}import*as J from"node:fs";import*as B from"node:os";import*as K from"node:path";import{fileURLToPath as X}from"node:url";import{getCompletionsFromManifest as k,hasAllManifests as R,printCompletionScriptFast as M}from"./completion-fast.js";import{findPackageRoot as N,getCliManifestPath as y}from"./package-paths.js";import{PKG_VERSION as O}from"./version.js";import{EXIT_CODES as Z}from"./errors.js";import{isSupportedNodeVersion as j,MIN_SUPPORTED_NODE_MAJOR as D}from"./runtime-detect.js";import{unsupportedDaemonPortEnvMessage as U}from"./constants.js";const x=X(import.meta.url),d=K.dirname(x),G=K.join(N(x),"clis"),F=K.join(B.homedir(),".opencli","clis"),b=process.argv.slice(2);if(typeof globalThis.Bun>"u"&&!j(process.version)){process.stderr.write([`OpenCLI requires Node.js >= ${D}.0.0.`,`Current runtime: ${process.version}`,"Upgrade Node.js, then retry the same command.",""].join(`
2
+ if(process.platform!=="win32"){const q=["/usr/local/bin","/usr/bin","/bin","/usr/sbin","/sbin"],Q=new Set((process.env.PATH??"").split(":").filter(Boolean));for(const z of q)Q.add(z);process.env.PATH=[...Q].join(":")}import*as J from"node:fs";import*as B from"node:os";import*as K from"node:path";import{fileURLToPath as X}from"node:url";import{getCompletionsFromManifest as k,hasAllManifests as R,printCompletionScriptFast as M}from"./completion-fast.js";import{findPackageRoot as N,getCliManifestPath as y}from"./package-paths.js";import{PKG_VERSION as O}from"./version.js";import{EXIT_CODES as Z}from"./errors.js";import{isSupportedNodeVersion as j,MIN_SUPPORTED_NODE_MAJOR as D}from"./runtime-detect.js";import{unsupportedDaemonPortEnvMessage as U}from"./constants.js";const x=X(import.meta.url),d=K.dirname(x),G=K.join(N(x),"clis"),F=K.join(B.homedir(),".ppcli","clis"),b=process.argv.slice(2);if(typeof globalThis.Bun>"u"&&!j(process.version)){process.stderr.write([`ppcli requires Node.js >= ${D}.0.0.`,`Current runtime: ${process.version}`,"Upgrade Node.js, then retry the same command.",""].join(`
3
3
  `));process.exit(Z.CONFIG_ERROR)}if(process.env.OPENCLI_DAEMON_PORT){process.stderr.write(`error: ${U(process.env.OPENCLI_DAEMON_PORT)}
4
4
  `);process.exit(Z.CONFIG_ERROR)}if(b[0]==="--version"||b[0]==="-V"){process.stdout.write(O+`
5
5
  `);process.exit(Z.SUCCESS)}if(b[0]==="completion"&&b.length>=2){if(M(b[1]))process.exit(Z.SUCCESS)}const H=process.argv.indexOf("--get-completions");if(H!==-1){const q=[y(G)],Q=y(F);try{J.accessSync(Q);q.push(Q)}catch{}if(R(q)){const z=process.argv.slice(H+1);let $;const W=[];for(let Y=0;Y<z.length;Y++)if(z[Y]==="--cursor"&&Y+1<z.length){$=parseInt(z[Y+1],10);Y++}else W.push(z[Y]);if($===void 0)$=W.length;const A=k(W,$,q);process.stdout.write(A.join(`
@@ -1,8 +1,8 @@
1
- import*as c from"node:fs";import*as L from"node:os";import*as s from"node:path";import{redactValue as p}from"./redaction.js";import{pruneTraceArtifacts as R,traceExpiresAt as N}from"./retention.js";import{CliError as b,getErrorMessage as k}from"../errors.js";import{log as C}from"../logger.js";import{PKG_VERSION as T}from"../version.js";function V(){return process.env.OPENCLI_CONFIG_DIR||s.join(L.homedir(),".opencli")}function F(e){return(e||"default").replace(/[^a-zA-Z0-9_-]+/g,"_")||"default"}export function getTraceDirectory(e,n,t=V()){return s.join(t,"profiles",F(e),"traces",F(n))}export function exportObservationSession(e,n={}){const t=getTraceDirectory(e.scope.contextId,e.id,n.baseDir),r=n.status??(n.error===void 0?"success":"failure"),o=new Date().toISOString();c.mkdirSync(t,{recursive:!0});c.mkdirSync(s.join(t,"screenshots"),{recursive:!0});c.mkdirSync(s.join(t,"state"),{recursive:!0});const l=e.events(),h=l.map((f)=>J(f)),S=[],g=[],$=[];let i=0,d=0;for(let f=0;f<h.length;f++){const j=l[f],m=h[f],u={...m};if(m.stream==="screenshot"&&j.stream==="screenshot"&&typeof j.data==="string"){const y=m.format==="jpeg"?"jpg":"png",A=`screenshots/${String(++i).padStart(4,"0")}.${y}`;c.writeFileSync(s.join(t,A),j.data,"base64");u.path=A;delete u.data}if(m.stream==="state"&&u.snapshot!==void 0){const y=`state/${String(++d).padStart(4,"0")}.json`;c.writeFileSync(s.join(t,y),JSON.stringify(u.snapshot,null,2),"utf-8");u.snapshotPath=y;delete u.snapshot}const P=JSON.stringify(u);S.push(P);if(m.stream==="network")g.push(P);if(m.stream==="console")$.push(P)}c.writeFileSync(s.join(t,"trace.jsonl"),S.join(`
1
+ import*as c from"node:fs";import*as R from"node:os";import*as s from"node:path";import{redactValue as p}from"./redaction.js";import{pruneTraceArtifacts as V,traceExpiresAt as N}from"./retention.js";import{CliError as b,getErrorMessage as k}from"../errors.js";import{log as T}from"../logger.js";import{PKG_VERSION as C}from"../version.js";function J(){return process.env.OPENCLI_CONFIG_DIR||s.join(R.homedir(),".ppcli")}function F(e){return(e||"default").replace(/[^a-zA-Z0-9_-]+/g,"_")||"default"}export function getTraceDirectory(e,n,t=J()){return s.join(t,"profiles",F(e),"traces",F(n))}export function exportObservationSession(e,n={}){const t=getTraceDirectory(e.scope.contextId,e.id,n.baseDir),r=n.status??(n.error===void 0?"success":"failure"),o=new Date().toISOString();c.mkdirSync(t,{recursive:!0});c.mkdirSync(s.join(t,"screenshots"),{recursive:!0});c.mkdirSync(s.join(t,"state"),{recursive:!0});const l=e.events(),h=l.map((f)=>L(f)),S=[],g=[],$=[];let i=0,d=0;for(let f=0;f<h.length;f++){const j=l[f],m=h[f],u={...m};if(m.stream==="screenshot"&&j.stream==="screenshot"&&typeof j.data==="string"){const y=m.format==="jpeg"?"jpg":"png",I=`screenshots/${String(++i).padStart(4,"0")}.${y}`;c.writeFileSync(s.join(t,I),j.data,"base64");u.path=I;delete u.data}if(m.stream==="state"&&u.snapshot!==void 0){const y=`state/${String(++d).padStart(4,"0")}.json`;c.writeFileSync(s.join(t,y),JSON.stringify(u.snapshot,null,2),"utf-8");u.snapshotPath=y;delete u.snapshot}const P=JSON.stringify(u);S.push(P);if(m.stream==="network")g.push(P);if(m.stream==="console")$.push(P)}c.writeFileSync(s.join(t,"trace.jsonl"),S.join(`
2
2
  `)+(S.length?`
3
3
  `:""),"utf-8");c.writeFileSync(s.join(t,"network.jsonl"),g.join(`
4
4
  `)+(g.length?`
5
5
  `:""),"utf-8");c.writeFileSync(s.join(t,"console.jsonl"),$.join(`
6
6
  `)+($.length?`
7
- `:""),"utf-8");const E=s.join(t,"summary.md");c.writeFileSync(E,z(e,h,{error:n.error,status:r,dir:t,createdAt:o,retentionPolicy:n.retentionPolicy}),"utf-8");const O=s.join(t,"receipt.json"),x={traceId:e.id,dir:t,summaryPath:E,receiptPath:O},I=buildTraceReceipt(x,r,n.error,{createdAt:o,scope:e.scope,retentionPolicy:n.retentionPolicy});c.writeFileSync(O,JSON.stringify(I,null,2),"utf-8");_(s.dirname(t),t,n.retentionPolicy);return{...x,receipt:I}}function J(e){return p(e)}export function buildTraceReceipt(e,n,t,r={}){const o=t instanceof b?t:void 0,l=r.createdAt??new Date().toISOString();return{schemaVersion:1,opencliVersion:T,traceId:e.traceId,traceDir:e.dir,summaryPath:e.summaryPath,receiptPath:e.receiptPath,status:n,createdAt:l,expiresAt:N(l,r.retentionPolicy),...r.scope?{scope:r.scope}:{},...t===void 0?{}:{error:{...t instanceof Error?{name:t.name}:{},...o?{code:o.code,hint:o.hint,exitCode:o.exitCode}:{},message:String(p(k(t)))}}}}function _(e,n,t){try{R(e,{policy:t,protectedTraceDirs:[n],warn:(r)=>C.warn(`[trace] ${r}`)})}catch(r){C.warn(`[trace] Failed to prune trace artifacts: ${r instanceof Error?r.message:String(r)}`)}}function z(e,n,t){const r=n.reduce((i,d)=>{i[d.stream]=(i[d.stream]??0)+1;return i},{}),o=v(t.error),l=n.filter((i)=>i.stream==="error").slice(-20).reverse(),h=n.filter((i)=>i.stream==="network").filter((i)=>i.status===void 0||i.status===0||i.status>=400).slice(-20).reverse(),S=n.filter((i)=>i.stream==="console").filter((i)=>/^(error|warning|warn|assert)$/i.test(i.level)).slice(-20).reverse(),g=n.filter((i)=>i.stream==="action").slice(-30);return["---","schemaVersion: 1",`opencliVersion: ${a(T)}`,`traceId: ${a(e.id)}`,`status: ${t.status}`,`contextId: ${a(e.scope.contextId??"default")}`,`session: ${a(e.scope.session)}`,...e.scope.target?[`target: ${a(e.scope.target)}`]:[],...e.scope.site?[`site: ${a(e.scope.site)}`]:[],...e.scope.command?[`command: ${a(e.scope.command)}`]:[],...e.scope.adapterSourcePath?[`adapterSourcePath: ${a(e.scope.adapterSourcePath)}`]:[],...e.scope.adapterSourcePath?[`adapterSourcePathExists: ${c.existsSync(e.scope.adapterSourcePath)}`]:[],`traceDir: ${a(t.dir)}`,`startedAt: ${a(new Date(e.startedAt).toISOString())}`,`exportedAt: ${a(t.createdAt)}`,`expiresAt: ${a(N(t.createdAt,t.retentionPolicy))}`,...o?[`errorCode: ${a(o.code??"UNKNOWN")}`,`errorMessage: ${a(o.message)}`]:[],"---","","# OpenCLI Trace Summary","","## How To Use","","- Start with this summary, then inspect `trace.jsonl` only when the evidence below is insufficient.","- For adapter repair policy and retry limits, use the `opencli-autofix` skill.","- `adapterSourcePathExists: false` means the path is a best-effort hint, not a confirmed editable file.","","## Error","",...G(o,l),"","## Failed Network","",...K(h),"","## Suspicious Console","",...U(S),"","## Action Timeline","",...B(g),"","## Event Counts","",...Object.entries(r).map(([i,d])=>`- ${i}: ${d}`),"","## Artifact Files","","- `trace.jsonl`: full redacted event timeline","- `network.jsonl`: redacted network events","- `console.jsonl`: redacted console events","- `state/`: final state snapshots when available","- `screenshots/`: final screenshots when available",""].join(`
7
+ `:""),"utf-8");const E=s.join(t,"summary.md");c.writeFileSync(E,z(e,h,{error:n.error,status:r,dir:t,createdAt:o,retentionPolicy:n.retentionPolicy}),"utf-8");const x=s.join(t,"receipt.json"),O={traceId:e.id,dir:t,summaryPath:E,receiptPath:x},A=buildTraceReceipt(O,r,n.error,{createdAt:o,scope:e.scope,retentionPolicy:n.retentionPolicy});c.writeFileSync(x,JSON.stringify(A,null,2),"utf-8");_(s.dirname(t),t,n.retentionPolicy);return{...O,receipt:A}}function L(e){return p(e)}export function buildTraceReceipt(e,n,t,r={}){const o=t instanceof b?t:void 0,l=r.createdAt??new Date().toISOString();return{schemaVersion:1,opencliVersion:C,traceId:e.traceId,traceDir:e.dir,summaryPath:e.summaryPath,receiptPath:e.receiptPath,status:n,createdAt:l,expiresAt:N(l,r.retentionPolicy),...r.scope?{scope:r.scope}:{},...t===void 0?{}:{error:{...t instanceof Error?{name:t.name}:{},...o?{code:o.code,hint:o.hint,exitCode:o.exitCode}:{},message:String(p(k(t)))}}}}function _(e,n,t){try{V(e,{policy:t,protectedTraceDirs:[n],warn:(r)=>T.warn(`[trace] ${r}`)})}catch(r){T.warn(`[trace] Failed to prune trace artifacts: ${r instanceof Error?r.message:String(r)}`)}}function z(e,n,t){const r=n.reduce((i,d)=>{i[d.stream]=(i[d.stream]??0)+1;return i},{}),o=v(t.error),l=n.filter((i)=>i.stream==="error").slice(-20).reverse(),h=n.filter((i)=>i.stream==="network").filter((i)=>i.status===void 0||i.status===0||i.status>=400).slice(-20).reverse(),S=n.filter((i)=>i.stream==="console").filter((i)=>/^(error|warning|warn|assert)$/i.test(i.level)).slice(-20).reverse(),g=n.filter((i)=>i.stream==="action").slice(-30);return["---","schemaVersion: 1",`opencliVersion: ${a(C)}`,`traceId: ${a(e.id)}`,`status: ${t.status}`,`contextId: ${a(e.scope.contextId??"default")}`,`session: ${a(e.scope.session)}`,...e.scope.target?[`target: ${a(e.scope.target)}`]:[],...e.scope.site?[`site: ${a(e.scope.site)}`]:[],...e.scope.command?[`command: ${a(e.scope.command)}`]:[],...e.scope.adapterSourcePath?[`adapterSourcePath: ${a(e.scope.adapterSourcePath)}`]:[],...e.scope.adapterSourcePath?[`adapterSourcePathExists: ${c.existsSync(e.scope.adapterSourcePath)}`]:[],`traceDir: ${a(t.dir)}`,`startedAt: ${a(new Date(e.startedAt).toISOString())}`,`exportedAt: ${a(t.createdAt)}`,`expiresAt: ${a(N(t.createdAt,t.retentionPolicy))}`,...o?[`errorCode: ${a(o.code??"UNKNOWN")}`,`errorMessage: ${a(o.message)}`]:[],"---","","# ppcli Trace Summary","","## How To Use","","- Start with this summary, then inspect `trace.jsonl` only when the evidence below is insufficient.","- For adapter repair policy and retry limits, use the `opencli-autofix` skill.","- `adapterSourcePathExists: false` means the path is a best-effort hint, not a confirmed editable file.","","## Error","",...G(o,l),"","## Failed Network","",...K(h),"","## Suspicious Console","",...U(S),"","## Action Timeline","",...B(g),"","## Event Counts","",...Object.entries(r).map(([i,d])=>`- ${i}: ${d}`),"","## Artifact Files","","- `trace.jsonl`: full redacted event timeline","- `network.jsonl`: redacted network events","- `console.jsonl`: redacted console events","- `state/`: final state snapshots when available","- `screenshots/`: final screenshots when available",""].join(`
8
8
  `)}function v(e){if(e===void 0)return;if(e instanceof b)return{code:e.code,message:String(p(e.message)),...e.hint?{hint:String(p(e.hint))}:{}};return{message:String(p(k(e)))}}function a(e){return JSON.stringify(e)}function G(e,n){const t=[];if(e){t.push(`- ${e.code??"UNKNOWN"}: ${e.message}`);if(e.hint)t.push(`- hint: ${e.hint}`)}for(const r of n){if(r.stream!=="error")continue;t.push(`- ${w(r.ts)} ${r.code??"ERROR"}: ${r.message}`)}return t.length?t:["- none"]}function K(e){if(!e.length)return["- none"];return e.map((n)=>{const t=n.status??"unknown",r=n.method??"GET",o=n.contentType?` ${n.contentType}`:"";return`- ${w(n.ts)} ${t} ${r} ${n.url}${o}`})}function U(e){if(!e.length)return["- none"];return e.map((n)=>`- ${w(n.ts)} ${n.level}: ${D(n.text,240)}`)}function B(e){if(!e.length)return["- none"];return e.map((n)=>{const t=n.phase?` ${n.phase}`:"",r=n.data&&Object.keys(n.data).length?` ${D(JSON.stringify(p(n.data)),240)}`:"";return`- ${w(n.ts)} ${n.name}${t}${r}`})}function w(e){return new Date(e).toISOString()}function D(e,n){const t=e.replace(/\s+/g," ").trim();return t.length>n?`${t.slice(0,n)}...`:t}
@@ -1 +1 @@
1
- import*as A from"node:fs";import*as w from"node:path";export function findPackageRoot(j,z=A.existsSync){let q=w.dirname(j);while(!0){if(z(w.join(q,"package.json")))return q;const v=w.dirname(q);if(v===q)throw Error(`Could not find package.json above ${j}`);q=v}}export function getBuiltEntryCandidates(j,z=(q)=>A.readFileSync(q,"utf-8")){const q=[];try{const v=JSON.parse(z(w.join(j,"package.json")));if(typeof v.bin==="string")q.push(w.join(j,v.bin));else if(v.bin&&typeof v.bin==="object"&&typeof v.bin.opencli==="string")q.push(w.join(j,v.bin.opencli));if(typeof v.main==="string")q.push(w.join(j,v.main))}catch{}q.push(w.join(j,"dist","src","main.js"),w.join(j,"dist","main.js"));return[...new Set(q)]}export function getCliManifestPath(j){return w.resolve(j,"..","cli-manifest.json")}export function getFetchAdaptersScriptPath(j){return w.join(j,"scripts","fetch-adapters.js")}
1
+ import*as B from"node:fs";import*as v from"node:path";export function findPackageRoot(j,z=B.existsSync){let q=v.dirname(j);while(!0){if(z(v.join(q,"package.json")))return q;const w=v.dirname(q);if(w===q)throw Error(`Could not find package.json above ${j}`);q=w}}export function getBuiltEntryCandidates(j,z=(q)=>B.readFileSync(q,"utf-8")){const q=[];try{const w=JSON.parse(z(v.join(j,"package.json")));if(typeof w.bin==="string")q.push(v.join(j,w.bin));else if(w.bin&&typeof w.bin==="object")for(const G of["ppcli","opencli"]){const A=w.bin[G];if(typeof A==="string")q.push(v.join(j,A))}if(typeof w.main==="string")q.push(v.join(j,w.main))}catch{}q.push(v.join(j,"dist","src","main.js"),v.join(j,"dist","main.js"));return[...new Set(q)]}export function getCliManifestPath(j){return v.resolve(j,"..","cli-manifest.json")}export function getFetchAdaptersScriptPath(j){return v.join(j,"scripts","fetch-adapters.js")}
@@ -0,0 +1 @@
1
+ export {};
@@ -10,7 +10,9 @@ export interface SubPluginEntry {
10
10
  path: string;
11
11
  version?: string;
12
12
  description?: string;
13
- /** Semver range for opencli compatibility (overrides top-level). */
13
+ /** Semver range for ppcli compatibility (overrides top-level). */
14
+ ppcli?: string;
15
+ /** Legacy field accepted for plugins created before the ppcli rename. */
14
16
  opencli?: string;
15
17
  /** When true, this sub-plugin is skipped during install. */
16
18
  disabled?: boolean;
@@ -20,7 +22,9 @@ export interface PluginManifest {
20
22
  name?: string;
21
23
  /** Semantic version of the plugin (single-plugin mode). */
22
24
  version?: string;
23
- /** Semver range for opencli compatibility, e.g. ">=1.0.0". */
25
+ /** Semver range for ppcli compatibility, e.g. ">=1.0.0". */
26
+ ppcli?: string;
27
+ /** Legacy field accepted for plugins created before the ppcli rename. */
24
28
  opencli?: string;
25
29
  /** Human-readable description. */
26
30
  description?: string;
@@ -44,7 +48,7 @@ export declare function getEnabledPlugins(manifest: PluginManifest): Array<{
44
48
  entry: SubPluginEntry;
45
49
  }>;
46
50
  /**
47
- * Check if the current opencli version satisfies a semver range string.
51
+ * Check if the current ppcli version satisfies a semver range string.
48
52
  *
49
53
  * Supports a simplified subset of semver ranges:
50
54
  * ">=1.0.0" – greater than or equal
@@ -60,6 +64,11 @@ export declare function getEnabledPlugins(manifest: PluginManifest): Array<{
60
64
  * ranges (no constraint = always compatible).
61
65
  */
62
66
  export declare function checkCompatibility(range: string | undefined): boolean;
67
+ /** Prefer the new manifest key while keeping existing plugin manifests valid. */
68
+ export declare function getCompatibilityRange(value: {
69
+ ppcli?: string;
70
+ opencli?: string;
71
+ } | null | undefined): string | undefined;
63
72
  /** Parse a version string ("1.2.3") into [major, minor, patch]. */
64
73
  export declare function parseVersion(version: string): [number, number, number] | null;
65
74
  /**
@@ -1 +1 @@
1
- import*as U from"node:fs";import*as W from"node:path";import{PKG_VERSION as X}from"./version.js";export const MANIFEST_FILENAME="opencli-plugin.json";export function readPluginManifest(z){const D=W.join(z,MANIFEST_FILENAME);try{const q=U.readFileSync(D,"utf-8"),J=JSON.parse(q);if(typeof J!=="object"||J===null||Array.isArray(J))return null;return J}catch{return null}}export function isMonorepo(z){return z.plugins!==void 0&&z.plugins!==null&&typeof z.plugins==="object"&&Object.keys(z.plugins).length>0}export function getEnabledPlugins(z){if(!z.plugins)return[];return Object.entries(z.plugins).filter(([,D])=>!D.disabled).map(([D,q])=>({name:D,entry:q})).sort((D,q)=>D.name.localeCompare(q.name))}export function checkCompatibility(z){if(!z||z.trim()==="")return!0;return satisfiesRange(X,z)}export function parseVersion(z){const D=z.trim().match(/^(\d+)\.(\d+)\.(\d+)/);if(!D)return null;return[parseInt(D[1],10),parseInt(D[2],10),parseInt(D[3],10)]}function H(z,D){for(let q=0;q<3;q++){if(z[q]<D[q])return-1;if(z[q]>D[q])return 1}return 0}function Y(z,D){const q=D.trim();if(!q)return!0;if(q.startsWith("^")){const B=parseVersion(q.slice(1));if(!B)return!0;const Q=[B[0]+1,0,0];return H(z,B)>=0&&H(z,Q)<0}if(q.startsWith("~")){const B=parseVersion(q.slice(1));if(!B)return!0;const Q=[B[0],B[1]+1,0];return H(z,B)>=0&&H(z,Q)<0}if(q.startsWith(">=")){const B=parseVersion(q.slice(2));if(!B)return!0;return H(z,B)>=0}if(q.startsWith("<=")){const B=parseVersion(q.slice(2));if(!B)return!0;return H(z,B)<=0}if(q.startsWith(">")){const B=parseVersion(q.slice(1));if(!B)return!0;return H(z,B)>0}if(q.startsWith("<")){const B=parseVersion(q.slice(1));if(!B)return!0;return H(z,B)<0}if(q.startsWith("=")){const B=parseVersion(q.slice(1));if(!B)return!0;return H(z,B)===0}const J=parseVersion(q);if(!J)return!0;return H(z,J)===0}export function satisfiesRange(z,D){const q=parseVersion(z);if(!q)return!0;return D.trim().split(/\s+/).every((B)=>Y(q,B))}export{readPluginManifest as _readPluginManifest,isMonorepo as _isMonorepo,getEnabledPlugins as _getEnabledPlugins,checkCompatibility as _checkCompatibility,parseVersion as _parseVersion,satisfiesRange as _satisfiesRange};
1
+ import*as U from"node:fs";import*as W from"node:path";import{PKG_VERSION as X}from"./version.js";export const MANIFEST_FILENAME="opencli-plugin.json";export function readPluginManifest(q){const D=W.join(q,MANIFEST_FILENAME);try{const z=U.readFileSync(D,"utf-8"),J=JSON.parse(z);if(typeof J!=="object"||J===null||Array.isArray(J))return null;return J}catch{return null}}export function isMonorepo(q){return q.plugins!==void 0&&q.plugins!==null&&typeof q.plugins==="object"&&Object.keys(q.plugins).length>0}export function getEnabledPlugins(q){if(!q.plugins)return[];return Object.entries(q.plugins).filter(([,D])=>!D.disabled).map(([D,z])=>({name:D,entry:z})).sort((D,z)=>D.name.localeCompare(z.name))}export function checkCompatibility(q){if(!q||q.trim()==="")return!0;return satisfiesRange(X,q)}export function getCompatibilityRange(q){return q?.ppcli??q?.opencli}export function parseVersion(q){const D=q.trim().match(/^(\d+)\.(\d+)\.(\d+)/);if(!D)return null;return[parseInt(D[1],10),parseInt(D[2],10),parseInt(D[3],10)]}function H(q,D){for(let z=0;z<3;z++){if(q[z]<D[z])return-1;if(q[z]>D[z])return 1}return 0}function Y(q,D){const z=D.trim();if(!z)return!0;if(z.startsWith("^")){const B=parseVersion(z.slice(1));if(!B)return!0;const Q=[B[0]+1,0,0];return H(q,B)>=0&&H(q,Q)<0}if(z.startsWith("~")){const B=parseVersion(z.slice(1));if(!B)return!0;const Q=[B[0],B[1]+1,0];return H(q,B)>=0&&H(q,Q)<0}if(z.startsWith(">=")){const B=parseVersion(z.slice(2));if(!B)return!0;return H(q,B)>=0}if(z.startsWith("<=")){const B=parseVersion(z.slice(2));if(!B)return!0;return H(q,B)<=0}if(z.startsWith(">")){const B=parseVersion(z.slice(1));if(!B)return!0;return H(q,B)>0}if(z.startsWith("<")){const B=parseVersion(z.slice(1));if(!B)return!0;return H(q,B)<0}if(z.startsWith("=")){const B=parseVersion(z.slice(1));if(!B)return!0;return H(q,B)===0}const J=parseVersion(z);if(!J)return!0;return H(q,J)===0}export function satisfiesRange(q,D){const z=parseVersion(q);if(!z)return!0;return D.trim().split(/\s+/).every((B)=>Y(z,B))}export{readPluginManifest as _readPluginManifest,isMonorepo as _isMonorepo,getEnabledPlugins as _getEnabledPlugins,checkCompatibility as _checkCompatibility,parseVersion as _parseVersion,satisfiesRange as _satisfiesRange};
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * Plugin scaffold: generates a ready-to-develop plugin directory.
3
3
  *
4
- * Usage: opencli plugin create <name> [--dir <path>]
4
+ * Usage: ppcli plugin create <name> [--dir <path>]
5
5
  *
6
6
  * Creates:
7
7
  * <name>/
8
8
  * opencli-plugin.json — manifest with name, version, description
9
- * package.json — ESM package with opencli peer dependency
9
+ * package.json — ESM package with ppcli peer dependency
10
10
  * hello.ts — sample pipeline command
11
11
  * greet.ts — sample TS command using func()
12
12
  * README.md — basic documentation
@@ -1,5 +1,5 @@
1
- import*as H from"node:fs";import*as J from"node:path";import{PKG_VERSION as L}from"./version.js";export function createPluginScaffold(b,x={}){if(!/^[a-z][a-z0-9-]*$/.test(b))throw Error(`Invalid plugin name "${b}". Plugin names must start with a lowercase letter and contain only lowercase letters, digits, and hyphens.`);const q=x.dir?J.resolve(x.dir):J.resolve(b);if(H.existsSync(q)&&H.readdirSync(q).length>0)throw Error(`Directory "${q}" already exists and is not empty.`);H.mkdirSync(q,{recursive:!0});const z=[],Q={name:b,version:"0.1.0",description:x.description??`An opencli plugin: ${b}`,opencli:`>=${L}`};B(q,"opencli-plugin.json",JSON.stringify(Q,null,2)+`
2
- `);z.push("opencli-plugin.json");const U={name:`opencli-plugin-${b}`,version:"0.1.0",type:"module",description:x.description??`An opencli plugin: ${b}`,peerDependencies:{"@jackwener/opencli":`>=${L}`}};B(q,"package.json",JSON.stringify(U,null,2)+`
1
+ import*as H from"node:fs";import*as J from"node:path";import{PKG_VERSION as L}from"./version.js";export function createPluginScaffold(b,x={}){if(!/^[a-z][a-z0-9-]*$/.test(b))throw Error(`Invalid plugin name "${b}". Plugin names must start with a lowercase letter and contain only lowercase letters, digits, and hyphens.`);const q=x.dir?J.resolve(x.dir):J.resolve(b);if(H.existsSync(q)&&H.readdirSync(q).length>0)throw Error(`Directory "${q}" already exists and is not empty.`);H.mkdirSync(q,{recursive:!0});const z=[],Q={name:b,version:"0.1.0",description:x.description??`A ppcli plugin: ${b}`,ppcli:`>=${L}`};B(q,"opencli-plugin.json",JSON.stringify(Q,null,2)+`
2
+ `);z.push("opencli-plugin.json");const U={name:`opencli-plugin-${b}`,version:"0.1.0",type:"module",description:x.description??`A ppcli plugin: ${b}`,peerDependencies:{"@jackwener/opencli":`>=${L}`}};B(q,"package.json",JSON.stringify(U,null,2)+`
3
3
  `);z.push("package.json");const W=`/**
4
4
  * Sample pipeline command for ${b}.
5
5
  * Demonstrates the declarative pipeline API.
@@ -40,16 +40,16 @@ cli({
40
40
  });
41
41
  `;B(q,"greet.ts",X);z.push("greet.ts");const Y=`# opencli-plugin-${b}
42
42
 
43
- ${x.description??`An opencli plugin: ${b}`}
43
+ ${x.description??`A ppcli plugin: ${b}`}
44
44
 
45
45
  ## Install
46
46
 
47
47
  \`\`\`bash
48
48
  # From local development directory
49
- opencli plugin install file://${q}
49
+ ppcli plugin install file://${q}
50
50
 
51
51
  # From GitHub (after publishing)
52
- opencli plugin install github:<user>/opencli-plugin-${b}
52
+ ppcli plugin install github:<user>/opencli-plugin-${b}
53
53
  \`\`\`
54
54
 
55
55
  ## Commands
@@ -63,13 +63,13 @@ opencli plugin install github:<user>/opencli-plugin-${b}
63
63
 
64
64
  \`\`\`bash
65
65
  # Install locally for development (symlinked, changes reflect immediately)
66
- opencli plugin install file://${q}
66
+ ppcli plugin install file://${q}
67
67
 
68
68
  # Verify commands are registered
69
- opencli list | grep ${b}
69
+ ppcli list | grep ${b}
70
70
 
71
71
  # Run a command
72
- opencli ${b} hello
73
- opencli ${b} greet --name World
72
+ ppcli ${b} hello
73
+ ppcli ${b} greet --name World
74
74
  \`\`\`
75
75
  `;B(q,"README.md",Y);z.push("README.md");return{name:b,dir:q,files:z}}function B(b,x,q){H.writeFileSync(J.join(b,x),q)}
@@ -1,15 +1,15 @@
1
1
  /**
2
2
  * Plugin management: install, uninstall, and list plugins.
3
3
  *
4
- * Plugins live in ~/.opencli/plugins/<name>/.
5
- * Monorepo clones live in ~/.opencli/monorepos/<repo-name>/.
4
+ * Plugins live in ~/.ppcli/plugins/<name>/.
5
+ * Monorepo clones live in ~/.ppcli/monorepos/<repo-name>/.
6
6
  * Install source format: "github:user/repo", "github:user/repo/subplugin",
7
7
  * "https://github.com/user/repo", "file:///local/plugin", or a local directory path.
8
8
  */
9
9
  import * as fs from 'node:fs';
10
10
  /** Path to the lock file that tracks installed plugin versions. */
11
11
  export declare function getLockFilePath(): string;
12
- /** Monorepo clones directory: ~/.opencli/monorepos/ */
12
+ /** Monorepo clones directory: ~/.ppcli/monorepos/ */
13
13
  export declare function getMonoreposDir(): string;
14
14
  export type PluginSourceRecord = {
15
15
  kind: 'git';
@@ -56,7 +56,7 @@ declare function resolveStoredPluginSource(lockEntry: LockEntry | undefined, plu
56
56
  /**
57
57
  * Move a directory, with EXDEV fallback.
58
58
  * fs.renameSync fails when source and destination are on different
59
- * filesystems (e.g. /tmp → ~/.opencli). In that case we copy then remove.
59
+ * filesystems (e.g. /tmp → ~/.ppcli). In that case we copy then remove.
60
60
  */
61
61
  type MoveDirFsOps = Pick<typeof fs, 'renameSync' | 'cpSync' | 'rmSync'>;
62
62
  declare function moveDir(src: string, dest: string, fsOps?: MoveDirFsOps): void;
@@ -1,7 +1,7 @@
1
- import*as X from"node:fs";import*as u from"node:os";import*as B from"node:path";import{execSync as r,execFileSync as v}from"node:child_process";import{fileURLToPath as f}from"node:url";import{PLUGINS_DIR as x}from"./discovery.js";import{getErrorMessage as U,PluginError as G}from"./errors.js";import{log as T}from"./logger.js";import{isRecord as L}from"./utils.js";import{readPluginManifest as F,isMonorepo as b,getEnabledPlugins as i,checkCompatibility as O}from"./plugin-manifest.js";const I=process.platform==="win32",P="local:";function c(){return process.env.HOME||process.env.USERPROFILE||u.homedir()}export function getLockFilePath(){return B.join(c(),".opencli","plugins.lock.json")}export function getMonoreposDir(){return B.join(c(),".opencli","monorepos")}function k(q){if(!q)return;if(q.startsWith(P))return{kind:"local",path:B.resolve(q.slice(P.length))};return{kind:"git",url:q}}function isLocalPluginSource(q){return k(q)?.kind==="local"}function toStoredPluginSource(q){if(q.kind==="local")return`${P}${B.resolve(q.path)}`;return q.url}function toLocalPluginSource(q){return toStoredPluginSource({kind:"local",path:q})}function e(q){if(!L(q))return;if(typeof q.name!=="string"||typeof q.subPath!=="string")return;return{name:q.name,subPath:q.subPath}}function qq(q,K){if(typeof q==="string"){const Q=k(q);if(!Q)return;if(Q.kind==="git"&&K)return{kind:"monorepo",url:Q.url,repoName:K.name,subPath:K.subPath};return Q}if(!L(q)||typeof q.kind!=="string")return;switch(q.kind){case"git":return typeof q.url==="string"?{kind:"git",url:q.url}:void 0;case"local":return typeof q.path==="string"?{kind:"local",path:B.resolve(q.path)}:void 0;case"monorepo":return typeof q.url==="string"&&typeof q.repoName==="string"&&typeof q.subPath==="string"?{kind:"monorepo",url:q.url,repoName:q.repoName,subPath:q.subPath}:void 0;default:return}}function Kq(q){if(!L(q))return;const K=e(q.monorepo),Q=qq(q.source,K);if(!Q)return;if(typeof q.commitHash!=="string"||typeof q.installedAt!=="string")return;const V={source:Q,commitHash:q.commitHash,installedAt:q.installedAt};if(typeof q.updatedAt==="string")V.updatedAt=q.updatedAt;return V}function resolvePluginSource(q,K){if(q)return q.source;return k(Wq(K))}function resolveStoredPluginSource(q,K){const Q=resolvePluginSource(q,K);return Q?toStoredPluginSource(Q):void 0}function moveDir(q,K,Q=X){try{Q.renameSync(q,K)}catch(V){if(V.code==="EXDEV"){try{Q.cpSync(q,K,{recursive:!0})}catch(Y){try{Q.rmSync(K,{recursive:!0,force:!0})}catch{}throw Y}Q.rmSync(q,{recursive:!0,force:!0})}else throw V}}function S(q,K){const Q=`${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;return B.join(B.dirname(q),`.${B.basename(q)}.${K}-${Q}`)}function Qq(q){const K=B.join(u.tmpdir(),`opencli-clone-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`);try{v("git",["clone","--depth","1",q,K],{encoding:"utf-8",stdio:["pipe","pipe","pipe"]})}catch(Q){throw new G(`Failed to clone plugin: ${U(Q)}`,"Check the repository URL and your network connection.")}return K}function d(q,K){const Q=Qq(q);try{return K(Q)}finally{try{X.rmSync(Q,{recursive:!0,force:!0})}catch{}}}function Vq(q,K){const Q=resolvePluginSource(q,K);if(!Q||Q.kind==="local")throw Error(`Unable to determine remote source for plugin at ${K}`);return Q.url}function y(q){try{X.lstatSync(q);return!0}catch{return!1}}function C(q,K){const Q=B.resolve(q,K);if(!Q.startsWith(q+B.sep)&&Q!==q)throw new G(`Plugin path "${K}" escapes repo root.`);return Q}function D(q){try{if(X.lstatSync(q).isSymbolicLink()){X.unlinkSync(q);return}X.rmSync(q,{recursive:!0,force:!0})}catch{}}class t{#K=[];#q=!1;track(q){this.#K.push(q);return q}commit(){if(this.#q)return;this.#q=!0;for(const q of this.#K)q.finalize()}rollback(){if(this.#q)return;this.#q=!0;for(const q of[...this.#K].reverse())q.rollback()}}function p(q){const K=new t;try{const Q=q(K);K.commit();return Q}catch(Q){K.rollback();throw Q}}function a(q,K,Q=X){const V=Q.existsSync(K);Q.mkdirSync(B.dirname(K),{recursive:!0});const Y=S(K,"tmp"),Z=V?S(K,"bak"):null;let J=!1;try{moveDir(q,Y,Q);if(Z)Q.renameSync(K,Z);Q.renameSync(Y,K)}catch($){try{Q.rmSync(Y,{recursive:!0,force:!0})}catch{}if(Z&&!Q.existsSync(K))try{Q.renameSync(Z,K)}catch{}throw $}return{finalize(){if(J)return;J=!0;if(Z)try{Q.rmSync(Z,{recursive:!0,force:!0})}catch{}},rollback(){if(J)return;J=!0;try{Q.rmSync(K,{recursive:!0,force:!0})}catch{}if(Z)try{Q.renameSync(Z,K)}catch{}try{Q.rmSync(Y,{recursive:!0,force:!0})}catch{}}}}function Yq(q,K){const Q=y(K);if(Q&&!isSymlinkSync(K))throw Error(`Expected monorepo plugin link at ${K} to be a symlink`);X.mkdirSync(B.dirname(K),{recursive:!0});const V=S(K,"tmp"),Y=Q?S(K,"bak"):null,Z=I?"junction":"dir";let J=!1;try{X.symlinkSync(q,V,Z);if(Y)X.renameSync(K,Y);X.renameSync(V,K)}catch($){D(V);if(Y&&!y(K))try{X.renameSync(Y,K)}catch{}throw $}return{finalize(){if(J)return;J=!0;if(Y)D(Y)},rollback(){if(J)return;J=!0;D(K);if(Y&&!y(K))try{X.renameSync(Y,K)}catch{}D(V)}}}function readLockFileWithWriter(q=writeLockFile){try{const K=X.readFileSync(getLockFilePath(),"utf-8"),Q=JSON.parse(K);if(!L(Q))return{};const V={};let Y=!1;for(const[Z,J]of Object.entries(Q)){const $=Kq(J);if(!$){Y=!0;continue}V[Z]=$;if(JSON.stringify(J)!==JSON.stringify($))Y=!0}if(Y)try{q(V)}catch{}return V}catch{return{}}}export function readLockFile(){return readLockFileWithWriter(writeLockFile)}function writeLockFileWithFs(q,K=X){const Q=getLockFilePath();K.mkdirSync(B.dirname(Q),{recursive:!0});const V=S(Q,"tmp");try{K.writeFileSync(V,JSON.stringify(q,null,2)+`
2
- `);K.renameSync(V,Q)}catch(Y){try{K.rmSync(V,{force:!0})}catch{}throw Y}}export function writeLockFile(q){writeLockFileWithFs(q,X)}export function getCommitHash(q){try{return v("git",["rev-parse","HEAD"],{cwd:q,encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()}catch{return}}export function validatePluginStructure(q){const K=[];if(!X.existsSync(q))return{valid:!1,errors:["Plugin directory does not exist"]};const Q=X.readdirSync(q),V=Q.some((Z)=>Z.endsWith(".ts")&&!Z.endsWith(".d.ts")&&!Z.endsWith(".test.ts")),Y=Q.some((Z)=>Z.endsWith(".js")&&!Z.endsWith(".d.js"));if(!V&&!Y)K.push("No command files found in plugin directory. A plugin must contain at least one .ts or .js command file.");if(V){const Z=B.join(q,"package.json");if(!X.existsSync(Z))K.push('Plugin contains .ts files but no package.json. A package.json with "type": "module" and "@jackwener/opencli" peer dependency is required for TS plugins.');else try{if(JSON.parse(X.readFileSync(Z,"utf-8")).type!=="module")K.push('Plugin package.json must have "type": "module" for TypeScript plugins.')}catch{K.push("Plugin package.json is malformed or invalid JSON.")}}return{valid:K.length===0,errors:K}}function Zq(q){const K=B.join(q,"package.json");if(!X.existsSync(K))return!1;try{const Q=JSON.parse(X.readFileSync(K,"utf-8"));return Q.dependencies!=null&&Object.keys(Q.dependencies).length>0}catch{return!1}}function installDependencies(q){const K=B.join(q,"package.json");if(!X.existsSync(K))return;try{v("npm",["install","--omit=dev"],{cwd:q,encoding:"utf-8",stdio:["pipe","pipe","pipe"],...I&&{shell:!0}})}catch(Q){throw new G(`npm install failed in ${q}: ${U(Q)}`,"Check your network connection and npm configuration.")}}function h(q){Gq(q);_q(q)}function s(q){installDependencies(q);h(q)}function postInstallMonorepoLifecycle(q,K){installDependencies(q);for(const Q of K){if(Q!==q&&Zq(Q))installDependencies(Q);h(Q)}}function n(q){const K=validatePluginStructure(q);if(!K.valid)throw new G(`Invalid plugin structure:
1
+ import*as J from"node:fs";import*as p from"node:os";import*as B from"node:path";import{execSync as e,execFileSync as S}from"node:child_process";import{fileURLToPath as k}from"node:url";import{PLUGINS_DIR as T}from"./discovery.js";import{getErrorMessage as U,PluginError as x}from"./errors.js";import{log as w}from"./logger.js";import{isRecord as b}from"./utils.js";import{readPluginManifest as F,isMonorepo as y,getEnabledPlugins as qq,checkCompatibility as O,getCompatibilityRange as M}from"./plugin-manifest.js";const N=process.platform==="win32",d="local:";function a(){return process.env.HOME||process.env.USERPROFILE||p.homedir()}export function getLockFilePath(){return B.join(a(),".ppcli","plugins.lock.json")}export function getMonoreposDir(){return B.join(a(),".ppcli","monorepos")}function h(q){if(!q)return;if(q.startsWith(d))return{kind:"local",path:B.resolve(q.slice(d.length))};return{kind:"git",url:q}}function isLocalPluginSource(q){return h(q)?.kind==="local"}function toStoredPluginSource(q){if(q.kind==="local")return`${d}${B.resolve(q.path)}`;return q.url}function toLocalPluginSource(q){return toStoredPluginSource({kind:"local",path:q})}function Kq(q){if(!b(q))return;if(typeof q.name!=="string"||typeof q.subPath!=="string")return;return{name:q.name,subPath:q.subPath}}function Qq(q,K){if(typeof q==="string"){const Q=h(q);if(!Q)return;if(Q.kind==="git"&&K)return{kind:"monorepo",url:Q.url,repoName:K.name,subPath:K.subPath};return Q}if(!b(q)||typeof q.kind!=="string")return;switch(q.kind){case"git":return typeof q.url==="string"?{kind:"git",url:q.url}:void 0;case"local":return typeof q.path==="string"?{kind:"local",path:B.resolve(q.path)}:void 0;case"monorepo":return typeof q.url==="string"&&typeof q.repoName==="string"&&typeof q.subPath==="string"?{kind:"monorepo",url:q.url,repoName:q.repoName,subPath:q.subPath}:void 0;default:return}}function Vq(q){if(!b(q))return;const K=Kq(q.monorepo),Q=Qq(q.source,K);if(!Q)return;if(typeof q.commitHash!=="string"||typeof q.installedAt!=="string")return;const V={source:Q,commitHash:q.commitHash,installedAt:q.installedAt};if(typeof q.updatedAt==="string")V.updatedAt=q.updatedAt;return V}function resolvePluginSource(q,K){if(q)return q.source;return h(_q(K))}function resolveStoredPluginSource(q,K){const Q=resolvePluginSource(q,K);return Q?toStoredPluginSource(Q):void 0}function moveDir(q,K,Q=J){try{Q.renameSync(q,K)}catch(V){if(V.code==="EXDEV"){try{Q.cpSync(q,K,{recursive:!0})}catch(Y){try{Q.rmSync(K,{recursive:!0,force:!0})}catch{}throw Y}Q.rmSync(q,{recursive:!0,force:!0})}else throw V}}function L(q,K){const Q=`${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;return B.join(B.dirname(q),`.${B.basename(q)}.${K}-${Q}`)}function Yq(q){const K=B.join(p.tmpdir(),`opencli-clone-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`);try{S("git",["clone","--depth","1",q,K],{encoding:"utf-8",stdio:["pipe","pipe","pipe"]})}catch(Q){throw new x(`Failed to clone plugin: ${U(Q)}`,"Check the repository URL and your network connection.")}return K}function m(q,K){const Q=Yq(q);try{return K(Q)}finally{try{J.rmSync(Q,{recursive:!0,force:!0})}catch{}}}function Zq(q,K){const Q=resolvePluginSource(q,K);if(!Q||Q.kind==="local")throw Error(`Unable to determine remote source for plugin at ${K}`);return Q.url}function D(q){try{J.lstatSync(q);return!0}catch{return!1}}function C(q,K){const Q=B.resolve(q,K);if(!Q.startsWith(q+B.sep)&&Q!==q)throw new x(`Plugin path "${K}" escapes repo root.`);return Q}function f(q){try{if(J.lstatSync(q).isSymbolicLink()){J.unlinkSync(q);return}J.rmSync(q,{recursive:!0,force:!0})}catch{}}class i{#K=[];#q=!1;track(q){this.#K.push(q);return q}commit(){if(this.#q)return;this.#q=!0;for(const q of this.#K)q.finalize()}rollback(){if(this.#q)return;this.#q=!0;for(const q of[...this.#K].reverse())q.rollback()}}function s(q){const K=new i;try{const Q=q(K);K.commit();return Q}catch(Q){K.rollback();throw Q}}function n(q,K,Q=J){const V=Q.existsSync(K);Q.mkdirSync(B.dirname(K),{recursive:!0});const Y=L(K,"tmp"),Z=V?L(K,"bak"):null;let X=!1;try{moveDir(q,Y,Q);if(Z)Q.renameSync(K,Z);Q.renameSync(Y,K)}catch($){try{Q.rmSync(Y,{recursive:!0,force:!0})}catch{}if(Z&&!Q.existsSync(K))try{Q.renameSync(Z,K)}catch{}throw $}return{finalize(){if(X)return;X=!0;if(Z)try{Q.rmSync(Z,{recursive:!0,force:!0})}catch{}},rollback(){if(X)return;X=!0;try{Q.rmSync(K,{recursive:!0,force:!0})}catch{}if(Z)try{Q.renameSync(Z,K)}catch{}try{Q.rmSync(Y,{recursive:!0,force:!0})}catch{}}}}function $q(q,K){const Q=D(K);if(Q&&!isSymlinkSync(K))throw Error(`Expected monorepo plugin link at ${K} to be a symlink`);J.mkdirSync(B.dirname(K),{recursive:!0});const V=L(K,"tmp"),Y=Q?L(K,"bak"):null,Z=N?"junction":"dir";let X=!1;try{J.symlinkSync(q,V,Z);if(Y)J.renameSync(K,Y);J.renameSync(V,K)}catch($){f(V);if(Y&&!D(K))try{J.renameSync(Y,K)}catch{}throw $}return{finalize(){if(X)return;X=!0;if(Y)f(Y)},rollback(){if(X)return;X=!0;f(K);if(Y&&!D(K))try{J.renameSync(Y,K)}catch{}f(V)}}}function readLockFileWithWriter(q=writeLockFile){try{const K=J.readFileSync(getLockFilePath(),"utf-8"),Q=JSON.parse(K);if(!b(Q))return{};const V={};let Y=!1;for(const[Z,X]of Object.entries(Q)){const $=Vq(X);if(!$){Y=!0;continue}V[Z]=$;if(JSON.stringify(X)!==JSON.stringify($))Y=!0}if(Y)try{q(V)}catch{}return V}catch{return{}}}export function readLockFile(){return readLockFileWithWriter(writeLockFile)}function writeLockFileWithFs(q,K=J){const Q=getLockFilePath();K.mkdirSync(B.dirname(Q),{recursive:!0});const V=L(Q,"tmp");try{K.writeFileSync(V,JSON.stringify(q,null,2)+`
2
+ `);K.renameSync(V,Q)}catch(Y){try{K.rmSync(V,{force:!0})}catch{}throw Y}}export function writeLockFile(q){writeLockFileWithFs(q,J)}export function getCommitHash(q){try{return S("git",["rev-parse","HEAD"],{cwd:q,encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()}catch{return}}export function validatePluginStructure(q){const K=[];if(!J.existsSync(q))return{valid:!1,errors:["Plugin directory does not exist"]};const Q=J.readdirSync(q),V=Q.some((Z)=>Z.endsWith(".ts")&&!Z.endsWith(".d.ts")&&!Z.endsWith(".test.ts")),Y=Q.some((Z)=>Z.endsWith(".js")&&!Z.endsWith(".d.js"));if(!V&&!Y)K.push("No command files found in plugin directory. A plugin must contain at least one .ts or .js command file.");if(V){const Z=B.join(q,"package.json");if(!J.existsSync(Z))K.push('Plugin contains .ts files but no package.json. A package.json with "type": "module" and "@jackwener/opencli" peer dependency is required for TS plugins.');else try{if(JSON.parse(J.readFileSync(Z,"utf-8")).type!=="module")K.push('Plugin package.json must have "type": "module" for TypeScript plugins.')}catch{K.push("Plugin package.json is malformed or invalid JSON.")}}return{valid:K.length===0,errors:K}}function Xq(q){const K=B.join(q,"package.json");if(!J.existsSync(K))return!1;try{const Q=JSON.parse(J.readFileSync(K,"utf-8"));return Q.dependencies!=null&&Object.keys(Q.dependencies).length>0}catch{return!1}}function installDependencies(q){const K=B.join(q,"package.json");if(!J.existsSync(K))return;try{S("npm",["install","--omit=dev"],{cwd:q,encoding:"utf-8",stdio:["pipe","pipe","pipe"],...N&&{shell:!0}})}catch(Q){throw new x(`npm install failed in ${q}: ${U(Q)}`,"Check your network connection and npm configuration.")}}function g(q){xq(q);Tq(q)}function o(q){installDependencies(q);g(q)}function postInstallMonorepoLifecycle(q,K){installDependencies(q);for(const Q of K){if(Q!==q&&Xq(Q))installDependencies(Q);g(Q)}}function l(q){const K=validatePluginStructure(q);if(!K.valid)throw new x(`Invalid plugin structure:
3
3
  - ${K.errors.join(`
4
- - `)}`);s(q)}function M(q,K,Q){q[K]={...Q,installedAt:Q.installedAt??new Date().toISOString()}}function o(q,K,Q){p((V)=>{V.track(a(q,K));Q(getCommitHash(K))})}function l(q,K,Q,V,Y){p((Z)=>{if(V){X.mkdirSync(V.parentDir,{recursive:!0});Z.track(a(V.stagingDir,q))}const J=getCommitHash(q);for(const $ of Q){const A=B.join(K,$.name),z=C(q,$.subPath);Z.track(Yq(z,A))}Y?.(J)})}export function installPlugin(q){const K=parseSource(q);if(!K)throw Error(`Invalid plugin source: "${q}"
4
+ - `)}`);o(q)}function R(q,K,Q){q[K]={...Q,installedAt:Q.installedAt??new Date().toISOString()}}function r(q,K,Q){s((V)=>{V.track(n(q,K));Q(getCommitHash(K))})}function t(q,K,Q,V,Y){s((Z)=>{if(V){J.mkdirSync(V.parentDir,{recursive:!0});Z.track(n(V.stagingDir,q))}const X=getCommitHash(q);for(const $ of Q){const A=B.join(K,$.name),z=C(q,$.subPath);Z.track($q(z,A))}Y?.(X)})}export function installPlugin(q){const K=parseSource(q);if(!K)throw Error(`Invalid plugin source: "${q}"
5
5
  Supported formats:
6
6
  github:user/repo
7
7
  github:user/repo/subplugin
@@ -10,11 +10,11 @@ Supported formats:
10
10
  ssh://git@<host>/<path>/repo.git
11
11
  git@<host>:user/repo.git
12
12
  file:///absolute/path
13
- /absolute/path`);const{name:Q,subPlugin:V}=K;if(K.type==="local")return installLocalPlugin(K.localPath,Q);return d(K.cloneUrl,(Y)=>{const Z=F(Y);if(Z?.opencli&&!O(Z.opencli))throw Error(`Plugin requires opencli ${Z.opencli}, but current version is incompatible.`);if(Z&&b(Z))return Jq(Y,K.cloneUrl,Q,Z,V);return $q(Y,K.cloneUrl,Q,Z)})}function $q(q,K,Q,V){const Y=V?.name??Q,Z=B.join(x,Y);if(X.existsSync(Z))throw new G(`Plugin "${Y}" is already installed at ${Z}`,'Use "opencli plugin uninstall" first, or pick a different name.');n(q);o(q,Z,(J)=>{const $=readLockFile();if(J){M($,Y,{source:{kind:"git",url:K},commitHash:J});writeLockFile($)}});return Y}function installLocalPlugin(q,K){if(!X.existsSync(q))throw new G(`Local plugin path does not exist: ${q}`);if(!X.statSync(q).isDirectory())throw new G(`Local plugin path is not a directory: ${q}`);const V=F(q);if(V?.opencli&&!O(V.opencli))throw new G(`Plugin requires opencli ${V.opencli}, but current version is incompatible.`,"Upgrade opencli to a compatible version.");const Y=V?.name??K,Z=B.join(x,Y);if(X.existsSync(Z))throw new G(`Plugin "${Y}" is already installed at ${Z}`,'Use "opencli plugin uninstall" first, or pick a different name.');const J=validatePluginStructure(q);if(!J.valid)throw new G(`Invalid plugin structure:
14
- - ${J.errors.join(`
15
- - `)}`);X.mkdirSync(x,{recursive:!0});const $=B.resolve(q),A=I?"junction":"dir";X.symlinkSync($,Z,A);installDependencies(q);h(q);const z=readLockFile(),j=getCommitHash(q);M(z,Y,{source:{kind:"local",path:$},commitHash:j??"local"});writeLockFile(z);return Y}function Xq(q,K,Q,V){const Y=X.realpathSync(K),Z=validatePluginStructure(Y);if(!Z.valid)T.warn(`Plugin "${q}" structure invalid:
13
+ /absolute/path`);const{name:Q,subPlugin:V}=K;if(K.type==="local")return installLocalPlugin(K.localPath,Q);return m(K.cloneUrl,(Y)=>{const Z=F(Y),X=M(Z);if(X&&!O(X))throw Error(`Plugin requires ppcli ${X}, but current version is incompatible.`);if(Z&&y(Z))return Aq(Y,K.cloneUrl,Q,Z,V);return Jq(Y,K.cloneUrl,Q,Z)})}function Jq(q,K,Q,V){const Y=V?.name??Q,Z=B.join(T,Y);if(J.existsSync(Z))throw new x(`Plugin "${Y}" is already installed at ${Z}`,'Use "ppcli plugin uninstall" first, or pick a different name.');l(q);r(q,Z,(X)=>{const $=readLockFile();if(X){R($,Y,{source:{kind:"git",url:K},commitHash:X});writeLockFile($)}});return Y}function installLocalPlugin(q,K){if(!J.existsSync(q))throw new x(`Local plugin path does not exist: ${q}`);if(!J.statSync(q).isDirectory())throw new x(`Local plugin path is not a directory: ${q}`);const V=F(q),Y=M(V);if(Y&&!O(Y))throw new x(`Plugin requires ppcli ${Y}, but current version is incompatible.`,"Upgrade ppcli to a compatible version.");const Z=V?.name??K,X=B.join(T,Z);if(J.existsSync(X))throw new x(`Plugin "${Z}" is already installed at ${X}`,'Use "ppcli plugin uninstall" first, or pick a different name.');const $=validatePluginStructure(q);if(!$.valid)throw new x(`Invalid plugin structure:
14
+ - ${$.errors.join(`
15
+ - `)}`);J.mkdirSync(T,{recursive:!0});const A=B.resolve(q),z=N?"junction":"dir";J.symlinkSync(A,X,z);installDependencies(q);g(q);const j=readLockFile(),G=getCommitHash(q);R(j,Z,{source:{kind:"local",path:A},commitHash:G??"local"});writeLockFile(j);return Z}function Bq(q,K,Q,V){const Y=J.realpathSync(K),Z=validatePluginStructure(Y);if(!Z.valid)w.warn(`Plugin "${q}" structure invalid:
16
16
  - ${Z.errors.join(`
17
- - `)}`);s(Y);M(Q,q,{source:V?.source??{kind:"local",path:Y},commitHash:getCommitHash(Y)??"local",installedAt:V?.installedAt??new Date().toISOString(),updatedAt:new Date().toISOString()});writeLockFile(Q)}function Jq(q,K,Q,V,Y){const Z=getMonoreposDir(),J=B.join(Z,Q),$=X.existsSync(J),A=$?J:q,z=$?F(J):V;if(!z||!b(z))throw new G(`Monorepo manifest missing or invalid at ${A}`);let j=i(z);if(Y){j=j.filter((W)=>W.name===Y);if(j.length===0){if(z.plugins?.[Y])throw new G(`Sub-plugin "${Y}" is disabled in the manifest.`);throw new G(`Sub-plugin "${Y}" not found in monorepo. Available: ${Object.keys(z.plugins??{}).join(", ")}`)}}const w=[],H=readLockFile(),N=[];X.mkdirSync(x,{recursive:!0});for(const{name:W,entry:_}of j){if(_.opencli&&!O(_.opencli)){T.warn(`Skipping "${W}": requires opencli ${_.opencli}`);continue}let E;try{E=C(A,_.path)}catch{T.warn(`Skipping "${W}": path "${_.path}" escapes repo root.`);continue}if(!X.existsSync(E)){T.warn(`Skipping "${W}": path "${_.path}" not found in repo.`);continue}const m=validatePluginStructure(E);if(!m.valid){T.warn(`Skipping "${W}": invalid structure — ${m.errors.join(", ")}`);continue}const g=B.join(x,W);if(X.existsSync(g)){T.warn(`Skipping "${W}": already installed at ${g}`);continue}N.push({name:W,entry:_})}if(N.length===0)return w;const R=N.map(({name:W,entry:_})=>({name:W,subPath:_.path}));if($)postInstallMonorepoLifecycle(J,N.map((W)=>C(J,W.entry.path)));else postInstallMonorepoLifecycle(q,N.map((W)=>C(q,W.entry.path)));l(J,x,R,$?void 0:{stagingDir:q,parentDir:Z},(W)=>{for(const{name:_,entry:E}of N){if(W)M(H,_,{source:{kind:"monorepo",url:K,repoName:Q,subPath:E.path},commitHash:W});w.push(_)}writeLockFile(H)});return w}function Bq(q,K,Q,V,Y){const Z=[];for(const[J,$]of Object.entries(K)){if($.source.kind!=="monorepo"||$.source.repoName!==q)continue;const A=Q.plugins?.[J];if(!A||A.disabled)throw Error(`Installed sub-plugin "${J}" no longer exists in ${V}`);if(A.opencli&&!O(A.opencli))throw Error(`Sub-plugin "${J}" requires opencli ${A.opencli}`);const z=C(Y,A.path),j=validatePluginStructure(z);if(!j.valid)throw Error(`Updated sub-plugin "${J}" is invalid:
18
- - ${j.errors.join(`
19
- - `)}`);Z.push({name:J,lockEntry:$,manifestEntry:A})}return Z}function Aq(q,K,Q,V,Y){for(const Z of K){if(!Y)continue;M(q,Z.name,{...Z.lockEntry,source:{kind:"monorepo",url:Q,repoName:V,subPath:Z.manifestEntry.path},commitHash:Y,updatedAt:new Date().toISOString()})}}function zq(q,K,Q,V,Y){if(!Y)return;M(q,K,{source:{kind:"git",url:Q},commitHash:Y,installedAt:V?.installedAt??new Date().toISOString(),updatedAt:new Date().toISOString()})}export function uninstallPlugin(q){const K=B.join(x,q);if(!y(K))throw Error(`Plugin "${q}" is not installed.`);const Q=readLockFile(),V=Q[q];if(isSymlinkSync(K))X.unlinkSync(K);else X.rmSync(K,{recursive:!0,force:!0});if(V?.source.kind==="monorepo"){delete Q[q];const Z=V.source.repoName;if(!Object.values(Q).some(($)=>$.source.kind==="monorepo"&&$.source.repoName===Z)){const $=B.join(getMonoreposDir(),Z);try{X.rmSync($,{recursive:!0,force:!0})}catch{}}}else if(Q[q])delete Q[q];writeLockFile(Q)}function isSymlinkSync(q){try{return X.lstatSync(q).isSymbolicLink()}catch{return!1}}export function updatePlugin(q){const K=B.join(x,q);if(!X.existsSync(K))throw Error(`Plugin "${q}" is not installed.`);const Q=readLockFile(),V=Q[q],Y=resolvePluginSource(V,K);if(Y?.kind==="local"){Xq(q,K,Q,V);return}if(Y?.kind==="monorepo"){const J=B.join(getMonoreposDir(),Y.repoName),$=Y.repoName,A=Y.url;d(A,(z)=>{const j=F(z);if(!j||!b(j))throw Error(`Updated source is no longer a monorepo: ${A}`);if(j.opencli&&!O(j.opencli))throw Error(`Plugin requires opencli ${j.opencli}, but current version is incompatible.`);const w=Bq($,Q,j,A,z);if(w.length>0)postInstallMonorepoLifecycle(z,w.map((H)=>C(z,H.manifestEntry.path)));l(J,x,w.map((H)=>({name:H.name,subPath:H.manifestEntry.path})),{stagingDir:z,parentDir:B.dirname(J)},(H)=>{Aq(Q,w,A,$,H);writeLockFile(Q)})});return}const Z=Vq(V,K);d(Z,(J)=>{const $=F(J);if($&&b($))throw Error(`Updated source is now a monorepo: ${Z}`);if($?.opencli&&!O($.opencli))throw Error(`Plugin requires opencli ${$.opencli}, but current version is incompatible.`);n(J);o(J,K,(A)=>{zq(Q,q,Z,Q[q],A);if(A)writeLockFile(Q)})})}export function updateAllPlugins(){return listPlugins().map((q)=>{try{updatePlugin(q.name);return{name:q.name,success:!0}}catch(K){return{name:q.name,success:!1,error:U(K)}}})}export function listPlugins(){if(!X.existsSync(x))return[];const q=X.readdirSync(x,{withFileTypes:!0}),K=readLockFile(),Q=[];for(const V of q){const Y=B.join(x,V.name);if(!(V.isDirectory()||isSymlinkSync(Y)))continue;const J=jq(Y),$=K[V.name],A=F(Y);let z=A?.description,j=A?.version;if($?.source.kind==="monorepo"&&!z){const H=B.join(getMonoreposDir(),$.source.repoName),R=F(H)?.plugins?.[V.name];if(R){z=z??R.description;j=j??R.version}}const w=resolveStoredPluginSource($,Y);Q.push({name:V.name,path:Y,commands:J,source:w,version:j??$?.commitHash?.slice(0,7),installedAt:$?.installedAt,monorepoName:$?.source.kind==="monorepo"?$.source.repoName:void 0,description:z})}return Q}function jq(q){try{const K=X.readdirSync(q);return[...new Set(K.filter((V)=>V.endsWith(".ts")&&!V.endsWith(".d.ts")&&!V.endsWith(".test.ts")||V.endsWith(".js")&&!V.endsWith(".d.js")).map((V)=>B.basename(V,B.extname(V))))]}catch{return[]}}function Wq(q){try{return v("git",["config","--get","remote.origin.url"],{cwd:q,encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()}catch{return}}function parseSource(q){if(q.startsWith("file://"))try{const $=B.resolve(f(q));return{type:"local",localPath:$,name:B.basename($).replace(/^opencli-plugin-/,"")}}catch{return null}if(B.isAbsolute(q)){const $=B.resolve(q);return{type:"local",localPath:$,name:B.basename($).replace(/^opencli-plugin-/,"")}}const K=q.match(/^github:([\w.-]+)\/([\w.-]+)\/([\w.-]+)$/);if(K){const[,$,A,z]=K,j=A.replace(/^opencli-plugin-/,"");return{type:"git",cloneUrl:`https://github.com/${$}/${A}.git`,name:j,subPlugin:z}}const Q=q.match(/^github:([\w.-]+)\/([\w.-]+)$/);if(Q){const[,$,A]=Q,z=A.replace(/^opencli-plugin-/,"");return{type:"git",cloneUrl:`https://github.com/${$}/${A}.git`,name:z}}const V=q.match(/^https?:\/\/github\.com\/([\w.-]+)\/([\w.-]+?)(?:\.git)?$/);if(V){const[,$,A]=V,z=A.replace(/^opencli-plugin-/,"");return{type:"git",cloneUrl:`https://github.com/${$}/${A}.git`,name:z}}const Y=q.match(/^ssh:\/\/[^/]+\/(.*?)(?:\.git)?$/);if(Y){const j=Y[1].split("/").pop().replace(/^opencli-plugin-/,"");return{type:"git",cloneUrl:q,name:j}}const Z=q.match(/^git@[^:]+:(.+?)(?:\.git)?$/);if(Z){const j=Z[1].split("/").pop().replace(/^opencli-plugin-/,"");return{type:"git",cloneUrl:q,name:j}}const J=q.match(/^https?:\/\/[^/]+\/(.+?)(?:\.git)?$/);if(J){const j=J[1].split("/").pop().replace(/^opencli-plugin-/,"");return{type:"git",cloneUrl:q.endsWith(".git")?q:`${q}.git`,name:j}}return null}function Gq(q){try{const K=resolveHostOpencliRoot(),Q=B.join(q,"node_modules","@jackwener","opencli");if(X.existsSync(Q))X.rmSync(Q,{recursive:!0,force:!0});X.mkdirSync(B.dirname(Q),{recursive:!0});const V=I?"junction":"dir";X.symlinkSync(K,Q,V);T.debug(`Linked host opencli into plugin: ${Q} → ${K}`)}catch(K){T.warn(`Failed to link host opencli into plugin: ${U(K)}`)}}export function resolveEsbuildBin(){const q=resolveHostOpencliRoot();if(I){const Q=B.join(q,"node_modules",".bin","esbuild.cmd");if(X.existsSync(Q))return Q}if(!I)try{const Q=import.meta.resolve("esbuild/package.json");if(Q.startsWith("file://")){const V=f(Q),Y=X.readFileSync(V,"utf8"),Z=JSON.parse(Y);if(Z.bin&&typeof Z.bin==="object"&&Z.bin.esbuild){const J=B.resolve(B.dirname(V),Z.bin.esbuild);if(X.existsSync(J))return J}else if(typeof Z.bin==="string"){const J=B.resolve(B.dirname(V),Z.bin);if(X.existsSync(J))return J}}}catch{}const K=B.join(q,"node_modules",".bin","esbuild");if(X.existsSync(K))return K;try{const V=r(I?"where esbuild":"which esbuild",{encoding:"utf-8",stdio:"pipe"}).trim().split(`
20
- `)[0].trim();if(V&&X.existsSync(V))return V}catch{}return null}function resolveHostOpencliRoot(q=f(import.meta.url)){let K=B.dirname(q);while(!0){const Q=B.join(K,"package.json");if(X.existsSync(Q))try{if(JSON.parse(X.readFileSync(Q,"utf-8"))?.name==="@jackwener/opencli")return K}catch{}const V=B.dirname(K);if(V===K)break;K=V}return B.resolve(B.dirname(q),"..")}function _q(q){try{const K=resolveEsbuildBin();if(!K){T.warn("esbuild not found. TS plugin files will not be transpiled and may fail to load. Install esbuild (`npm i -g esbuild`) or ensure it is available in the opencli host node_modules.");return}const V=X.readdirSync(q).filter((Y)=>Y.endsWith(".ts")&&!Y.endsWith(".d.ts")&&!Y.endsWith(".test.ts"));for(const Y of V){const Z=Y.replace(/\.ts$/,".js"),J=B.join(q,Z);if(X.existsSync(J))continue;try{v(K,[Y,`--outfile=${Z}`,"--format=esm","--platform=node"],{cwd:q,encoding:"utf-8",stdio:["pipe","pipe","pipe"],...I&&{shell:!0}});T.debug(`Transpiled plugin file: ${Y} → ${Z}`)}catch($){T.warn(`Failed to transpile ${Y}: ${U($)}`)}}}catch(K){T.warn(`TS transpilation setup failed: ${U(K)}`)}}export{resolveHostOpencliRoot as _resolveHostOpencliRoot,resolveEsbuildBin as _resolveEsbuildBin,getCommitHash as _getCommitHash,installDependencies as _installDependencies,parseSource as _parseSource,postInstallMonorepoLifecycle as _postInstallMonorepoLifecycle,readLockFile as _readLockFile,readLockFileWithWriter as _readLockFileWithWriter,updateAllPlugins as _updateAllPlugins,validatePluginStructure as _validatePluginStructure,writeLockFile as _writeLockFile,writeLockFileWithFs as _writeLockFileWithFs,isSymlinkSync as _isSymlinkSync,getMonoreposDir as _getMonoreposDir,installLocalPlugin as _installLocalPlugin,isLocalPluginSource as _isLocalPluginSource,moveDir as _moveDir,resolvePluginSource as _resolvePluginSource,resolveStoredPluginSource as _resolveStoredPluginSource,toStoredPluginSource as _toStoredPluginSource,toLocalPluginSource as _toLocalPluginSource};
17
+ - `)}`);o(Y);R(Q,q,{source:V?.source??{kind:"local",path:Y},commitHash:getCommitHash(Y)??"local",installedAt:V?.installedAt??new Date().toISOString(),updatedAt:new Date().toISOString()});writeLockFile(Q)}function Aq(q,K,Q,V,Y){const Z=getMonoreposDir(),X=B.join(Z,Q),$=J.existsSync(X),A=$?X:q,z=$?F(X):V;if(!z||!y(z))throw new x(`Monorepo manifest missing or invalid at ${A}`);let j=qq(z);if(Y){j=j.filter((W)=>W.name===Y);if(j.length===0){if(z.plugins?.[Y])throw new x(`Sub-plugin "${Y}" is disabled in the manifest.`);throw new x(`Sub-plugin "${Y}" not found in monorepo. Available: ${Object.keys(z.plugins??{}).join(", ")}`)}}const G=[],I=readLockFile(),_=[];J.mkdirSync(T,{recursive:!0});for(const{name:W,entry:H}of j){const v=M(H);if(v&&!O(v)){w.warn(`Skipping "${W}": requires ppcli ${v}`);continue}let P;try{P=C(A,H.path)}catch{w.warn(`Skipping "${W}": path "${H.path}" escapes repo root.`);continue}if(!J.existsSync(P)){w.warn(`Skipping "${W}": path "${H.path}" not found in repo.`);continue}const u=validatePluginStructure(P);if(!u.valid){w.warn(`Skipping "${W}": invalid structure — ${u.errors.join(", ")}`);continue}const c=B.join(T,W);if(J.existsSync(c)){w.warn(`Skipping "${W}": already installed at ${c}`);continue}_.push({name:W,entry:H})}if(_.length===0)return G;const E=_.map(({name:W,entry:H})=>({name:W,subPath:H.path}));if($)postInstallMonorepoLifecycle(X,_.map((W)=>C(X,W.entry.path)));else postInstallMonorepoLifecycle(q,_.map((W)=>C(q,W.entry.path)));t(X,T,E,$?void 0:{stagingDir:q,parentDir:Z},(W)=>{for(const{name:H,entry:v}of _){if(W)R(I,H,{source:{kind:"monorepo",url:K,repoName:Q,subPath:v.path},commitHash:W});G.push(H)}writeLockFile(I)});return G}function zq(q,K,Q,V,Y){const Z=[];for(const[X,$]of Object.entries(K)){if($.source.kind!=="monorepo"||$.source.repoName!==q)continue;const A=Q.plugins?.[X];if(!A||A.disabled)throw Error(`Installed sub-plugin "${X}" no longer exists in ${V}`);const z=M(A);if(z&&!O(z))throw Error(`Sub-plugin "${X}" requires ppcli ${z}`);const j=C(Y,A.path),G=validatePluginStructure(j);if(!G.valid)throw Error(`Updated sub-plugin "${X}" is invalid:
18
+ - ${G.errors.join(`
19
+ - `)}`);Z.push({name:X,lockEntry:$,manifestEntry:A})}return Z}function jq(q,K,Q,V,Y){for(const Z of K){if(!Y)continue;R(q,Z.name,{...Z.lockEntry,source:{kind:"monorepo",url:Q,repoName:V,subPath:Z.manifestEntry.path},commitHash:Y,updatedAt:new Date().toISOString()})}}function Wq(q,K,Q,V,Y){if(!Y)return;R(q,K,{source:{kind:"git",url:Q},commitHash:Y,installedAt:V?.installedAt??new Date().toISOString(),updatedAt:new Date().toISOString()})}export function uninstallPlugin(q){const K=B.join(T,q);if(!D(K))throw Error(`Plugin "${q}" is not installed.`);const Q=readLockFile(),V=Q[q];if(isSymlinkSync(K))J.unlinkSync(K);else J.rmSync(K,{recursive:!0,force:!0});if(V?.source.kind==="monorepo"){delete Q[q];const Z=V.source.repoName;if(!Object.values(Q).some(($)=>$.source.kind==="monorepo"&&$.source.repoName===Z)){const $=B.join(getMonoreposDir(),Z);try{J.rmSync($,{recursive:!0,force:!0})}catch{}}}else if(Q[q])delete Q[q];writeLockFile(Q)}function isSymlinkSync(q){try{return J.lstatSync(q).isSymbolicLink()}catch{return!1}}export function updatePlugin(q){const K=B.join(T,q);if(!J.existsSync(K))throw Error(`Plugin "${q}" is not installed.`);const Q=readLockFile(),V=Q[q],Y=resolvePluginSource(V,K);if(Y?.kind==="local"){Bq(q,K,Q,V);return}if(Y?.kind==="monorepo"){const X=B.join(getMonoreposDir(),Y.repoName),$=Y.repoName,A=Y.url;m(A,(z)=>{const j=F(z);if(!j||!y(j))throw Error(`Updated source is no longer a monorepo: ${A}`);const G=M(j);if(G&&!O(G))throw Error(`Plugin requires ppcli ${G}, but current version is incompatible.`);const I=zq($,Q,j,A,z);if(I.length>0)postInstallMonorepoLifecycle(z,I.map((_)=>C(z,_.manifestEntry.path)));t(X,T,I.map((_)=>({name:_.name,subPath:_.manifestEntry.path})),{stagingDir:z,parentDir:B.dirname(X)},(_)=>{jq(Q,I,A,$,_);writeLockFile(Q)})});return}const Z=Zq(V,K);m(Z,(X)=>{const $=F(X);if($&&y($))throw Error(`Updated source is now a monorepo: ${Z}`);const A=M($);if(A&&!O(A))throw Error(`Plugin requires ppcli ${A}, but current version is incompatible.`);l(X);r(X,K,(z)=>{Wq(Q,q,Z,Q[q],z);if(z)writeLockFile(Q)})})}export function updateAllPlugins(){return listPlugins().map((q)=>{try{updatePlugin(q.name);return{name:q.name,success:!0}}catch(K){return{name:q.name,success:!1,error:U(K)}}})}export function listPlugins(){if(!J.existsSync(T))return[];const q=J.readdirSync(T,{withFileTypes:!0}),K=readLockFile(),Q=[];for(const V of q){const Y=B.join(T,V.name);if(!(V.isDirectory()||isSymlinkSync(Y)))continue;const X=Gq(Y),$=K[V.name],A=F(Y);let z=A?.description,j=A?.version;if($?.source.kind==="monorepo"&&!z){const I=B.join(getMonoreposDir(),$.source.repoName),E=F(I)?.plugins?.[V.name];if(E){z=z??E.description;j=j??E.version}}const G=resolveStoredPluginSource($,Y);Q.push({name:V.name,path:Y,commands:X,source:G,version:j??$?.commitHash?.slice(0,7),installedAt:$?.installedAt,monorepoName:$?.source.kind==="monorepo"?$.source.repoName:void 0,description:z})}return Q}function Gq(q){try{const K=J.readdirSync(q);return[...new Set(K.filter((V)=>V.endsWith(".ts")&&!V.endsWith(".d.ts")&&!V.endsWith(".test.ts")||V.endsWith(".js")&&!V.endsWith(".d.js")).map((V)=>B.basename(V,B.extname(V))))]}catch{return[]}}function _q(q){try{return S("git",["config","--get","remote.origin.url"],{cwd:q,encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()}catch{return}}function parseSource(q){if(q.startsWith("file://"))try{const $=B.resolve(k(q));return{type:"local",localPath:$,name:B.basename($).replace(/^opencli-plugin-/,"")}}catch{return null}if(B.isAbsolute(q)){const $=B.resolve(q);return{type:"local",localPath:$,name:B.basename($).replace(/^opencli-plugin-/,"")}}const K=q.match(/^github:([\w.-]+)\/([\w.-]+)\/([\w.-]+)$/);if(K){const[,$,A,z]=K,j=A.replace(/^opencli-plugin-/,"");return{type:"git",cloneUrl:`https://github.com/${$}/${A}.git`,name:j,subPlugin:z}}const Q=q.match(/^github:([\w.-]+)\/([\w.-]+)$/);if(Q){const[,$,A]=Q,z=A.replace(/^opencli-plugin-/,"");return{type:"git",cloneUrl:`https://github.com/${$}/${A}.git`,name:z}}const V=q.match(/^https?:\/\/github\.com\/([\w.-]+)\/([\w.-]+?)(?:\.git)?$/);if(V){const[,$,A]=V,z=A.replace(/^opencli-plugin-/,"");return{type:"git",cloneUrl:`https://github.com/${$}/${A}.git`,name:z}}const Y=q.match(/^ssh:\/\/[^/]+\/(.*?)(?:\.git)?$/);if(Y){const j=Y[1].split("/").pop().replace(/^opencli-plugin-/,"");return{type:"git",cloneUrl:q,name:j}}const Z=q.match(/^git@[^:]+:(.+?)(?:\.git)?$/);if(Z){const j=Z[1].split("/").pop().replace(/^opencli-plugin-/,"");return{type:"git",cloneUrl:q,name:j}}const X=q.match(/^https?:\/\/[^/]+\/(.+?)(?:\.git)?$/);if(X){const j=X[1].split("/").pop().replace(/^opencli-plugin-/,"");return{type:"git",cloneUrl:q.endsWith(".git")?q:`${q}.git`,name:j}}return null}function xq(q){try{const K=resolveHostOpencliRoot(),Q=B.join(q,"node_modules","@jackwener","opencli");if(J.existsSync(Q))J.rmSync(Q,{recursive:!0,force:!0});J.mkdirSync(B.dirname(Q),{recursive:!0});const V=N?"junction":"dir";J.symlinkSync(K,Q,V);w.debug(`Linked host ppcli into plugin: ${Q} → ${K}`)}catch(K){w.warn(`Failed to link host ppcli into plugin: ${U(K)}`)}}export function resolveEsbuildBin(){const q=resolveHostOpencliRoot();if(N){const Q=B.join(q,"node_modules",".bin","esbuild.cmd");if(J.existsSync(Q))return Q}if(!N)try{const Q=import.meta.resolve("esbuild/package.json");if(Q.startsWith("file://")){const V=k(Q),Y=J.readFileSync(V,"utf8"),Z=JSON.parse(Y);if(Z.bin&&typeof Z.bin==="object"&&Z.bin.esbuild){const X=B.resolve(B.dirname(V),Z.bin.esbuild);if(J.existsSync(X))return X}else if(typeof Z.bin==="string"){const X=B.resolve(B.dirname(V),Z.bin);if(J.existsSync(X))return X}}}catch{}const K=B.join(q,"node_modules",".bin","esbuild");if(J.existsSync(K))return K;try{const V=e(N?"where esbuild":"which esbuild",{encoding:"utf-8",stdio:"pipe"}).trim().split(`
20
+ `)[0].trim();if(V&&J.existsSync(V))return V}catch{}return null}function resolveHostOpencliRoot(q=k(import.meta.url)){let K=B.dirname(q);while(!0){const Q=B.join(K,"package.json");if(J.existsSync(Q))try{if(JSON.parse(J.readFileSync(Q,"utf-8"))?.name==="@jackwener/opencli")return K}catch{}const V=B.dirname(K);if(V===K)break;K=V}return B.resolve(B.dirname(q),"..")}function Tq(q){try{const K=resolveEsbuildBin();if(!K){w.warn("esbuild not found. TS plugin files will not be transpiled and may fail to load. Install esbuild (`npm i -g esbuild`) or ensure it is available in the ppcli host node_modules.");return}const V=J.readdirSync(q).filter((Y)=>Y.endsWith(".ts")&&!Y.endsWith(".d.ts")&&!Y.endsWith(".test.ts"));for(const Y of V){const Z=Y.replace(/\.ts$/,".js"),X=B.join(q,Z);if(J.existsSync(X))continue;try{S(K,[Y,`--outfile=${Z}`,"--format=esm","--platform=node"],{cwd:q,encoding:"utf-8",stdio:["pipe","pipe","pipe"],...N&&{shell:!0}});w.debug(`Transpiled plugin file: ${Y} → ${Z}`)}catch($){w.warn(`Failed to transpile ${Y}: ${U($)}`)}}}catch(K){w.warn(`TS transpilation setup failed: ${U(K)}`)}}export{resolveHostOpencliRoot as _resolveHostOpencliRoot,resolveEsbuildBin as _resolveEsbuildBin,getCommitHash as _getCommitHash,installDependencies as _installDependencies,parseSource as _parseSource,postInstallMonorepoLifecycle as _postInstallMonorepoLifecycle,readLockFile as _readLockFile,readLockFileWithWriter as _readLockFileWithWriter,updateAllPlugins as _updateAllPlugins,validatePluginStructure as _validatePluginStructure,writeLockFile as _writeLockFile,writeLockFileWithFs as _writeLockFileWithFs,isSymlinkSync as _isSymlinkSync,getMonoreposDir as _getMonoreposDir,installLocalPlugin as _installLocalPlugin,isLocalPluginSource as _isLocalPluginSource,moveDir as _moveDir,resolvePluginSource as _resolvePluginSource,resolveStoredPluginSource as _resolveStoredPluginSource,toStoredPluginSource as _toStoredPluginSource,toLocalPluginSource as _toLocalPluginSource};
@@ -1,2 +1,2 @@
1
- import*as m from"node:fs";import*as p from"node:os";import*as f from"node:path";import $ from"js-yaml";import{ConfigError as s,RateLimitError as v,getErrorMessage as S}from"./errors.js";import{isRecord as l}from"./utils.js";const g="rate-limits.yaml",x="rate-limit-state.json";export function getRateLimitConfigPath(t=p.homedir()){return f.join(t,".opencli",g)}export function getRateLimitStatePath(t=p.homedir()){return f.join(t,".opencli",x)}export function enforceRateLimit(t,e={}){const r=e.configPath??getRateLimitConfigPath(),n=findRateLimitRule(t,r);if(!n)return;const o=e.statePath??getRateLimitStatePath(),i=e.now??Date.now(),a=M(o),c=Array.isArray(a.commands[t])?a.commands[t]:[],h=i-n.windowMs,u=c.filter((d)=>Number.isFinite(d)&&d>h);if(u.length>=n.limit){const d=Math.min(...u),b=Math.max(0,d+n.windowMs-i);a.commands[t]=u;y(o,a);throw new v(t,b,r)}a.commands[t]=[...u,i];y(o,a)}export function findRateLimitRule(t,e=getRateLimitConfigPath()){return loadRateLimitRules(e).find((n)=>n.command===t)??null}export function loadRateLimitRules(t=getRateLimitConfigPath()){if(!m.existsSync(t))return[];let e;try{e=$.load(m.readFileSync(t,"utf-8"))??{}}catch(n){throw new s(`Failed to parse ${t}: ${S(n)}`,"Fix the YAML syntax or remove the file to disable command rate limits.")}if(!l(e))throw new s(`Invalid ${t}: root value must be an object with a rules array.`);const r=e.rules;if(r===void 0)return[];if(!Array.isArray(r))throw new s(`Invalid ${t}: rules must be an array.`);return r.map((n,o)=>F(n,o,t))}function F(t,e,r){const n=`${r} rules[${e}]`;if(!l(t))throw new s(`Invalid ${n}: rule must be an object.`);const o=typeof t.command==="string"?t.command.trim():"";if(!o)throw new s(`Invalid ${n}: command must be a non-empty string.`);const i=Number(t.limit);if(!Number.isInteger(i)||i<=0)throw new s(`Invalid ${n}: limit must be a positive integer.`);if(typeof t.window!=="string"&&typeof t.window!=="number")throw new s(`Invalid ${n}: window must be a duration like 10m, 1h, or 500ms.`);return{command:o,limit:i,windowMs:I(t.window,`${n}.window`)}}function I(t,e){const r=String(t).trim(),n=/^(\d+(?:\.\d+)?)(ms|s|m|h)?$/.exec(r);if(!n)throw new s(`Invalid ${e}: expected a duration like 10m, 1h, or 500ms.`);const o=Number.parseFloat(n[1]),i=n[2]??"ms",c=Math.round(o*(i==="h"?3600000:i==="m"?60000:i==="s"?1000:1));if(!Number.isFinite(c)||c<=0)throw new s(`Invalid ${e}: duration must be greater than zero.`);return c}function M(t){if(!m.existsSync(t))return w();try{const e=JSON.parse(m.readFileSync(t,"utf-8"));if(!l(e)||!l(e.commands))return w();const r={};for(const[n,o]of Object.entries(e.commands)){if(!Array.isArray(o))continue;r[n]=o.filter((i)=>typeof i==="number"&&Number.isFinite(i))}return{version:1,commands:r}}catch{return w()}}function y(t,e){m.mkdirSync(f.dirname(t),{recursive:!0});const r=`${t}.${process.pid}.${Date.now()}.tmp`;m.writeFileSync(r,JSON.stringify(e,null,2)+`
1
+ import*as m from"node:fs";import*as p from"node:os";import*as f from"node:path";import $ from"js-yaml";import{ConfigError as s,RateLimitError as v,getErrorMessage as S}from"./errors.js";import{isRecord as l}from"./utils.js";const g="rate-limits.yaml",x="rate-limit-state.json";export function getRateLimitConfigPath(t=p.homedir()){return f.join(t,".ppcli",g)}export function getRateLimitStatePath(t=p.homedir()){return f.join(t,".ppcli",x)}export function enforceRateLimit(t,e={}){const r=e.configPath??getRateLimitConfigPath(),n=findRateLimitRule(t,r);if(!n)return;const o=e.statePath??getRateLimitStatePath(),i=e.now??Date.now(),a=M(o),c=Array.isArray(a.commands[t])?a.commands[t]:[],h=i-n.windowMs,u=c.filter((d)=>Number.isFinite(d)&&d>h);if(u.length>=n.limit){const d=Math.min(...u),b=Math.max(0,d+n.windowMs-i);a.commands[t]=u;y(o,a);throw new v(t,b,r)}a.commands[t]=[...u,i];y(o,a)}export function findRateLimitRule(t,e=getRateLimitConfigPath()){return loadRateLimitRules(e).find((n)=>n.command===t)??null}export function loadRateLimitRules(t=getRateLimitConfigPath()){if(!m.existsSync(t))return[];let e;try{e=$.load(m.readFileSync(t,"utf-8"))??{}}catch(n){throw new s(`Failed to parse ${t}: ${S(n)}`,"Fix the YAML syntax or remove the file to disable command rate limits.")}if(!l(e))throw new s(`Invalid ${t}: root value must be an object with a rules array.`);const r=e.rules;if(r===void 0)return[];if(!Array.isArray(r))throw new s(`Invalid ${t}: rules must be an array.`);return r.map((n,o)=>F(n,o,t))}function F(t,e,r){const n=`${r} rules[${e}]`;if(!l(t))throw new s(`Invalid ${n}: rule must be an object.`);const o=typeof t.command==="string"?t.command.trim():"";if(!o)throw new s(`Invalid ${n}: command must be a non-empty string.`);const i=Number(t.limit);if(!Number.isInteger(i)||i<=0)throw new s(`Invalid ${n}: limit must be a positive integer.`);if(typeof t.window!=="string"&&typeof t.window!=="number")throw new s(`Invalid ${n}: window must be a duration like 10m, 1h, or 500ms.`);return{command:o,limit:i,windowMs:I(t.window,`${n}.window`)}}function I(t,e){const r=String(t).trim(),n=/^(\d+(?:\.\d+)?)(ms|s|m|h)?$/.exec(r);if(!n)throw new s(`Invalid ${e}: expected a duration like 10m, 1h, or 500ms.`);const o=Number.parseFloat(n[1]),i=n[2]??"ms",c=Math.round(o*(i==="h"?3600000:i==="m"?60000:i==="s"?1000:1));if(!Number.isFinite(c)||c<=0)throw new s(`Invalid ${e}: duration must be greater than zero.`);return c}function M(t){if(!m.existsSync(t))return w();try{const e=JSON.parse(m.readFileSync(t,"utf-8"));if(!l(e)||!l(e.commands))return w();const r={};for(const[n,o]of Object.entries(e.commands)){if(!Array.isArray(o))continue;r[n]=o.filter((i)=>typeof i==="number"&&Number.isFinite(i))}return{version:1,commands:r}}catch{return w()}}function y(t,e){m.mkdirSync(f.dirname(t),{recursive:!0});const r=`${t}.${process.pid}.${Date.now()}.tmp`;m.writeFileSync(r,JSON.stringify(e,null,2)+`
2
2
  `,"utf-8");m.renameSync(r,t)}function w(){return{version:1,commands:{}}}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Public API for opencli plugins.
2
+ * Public API for ppcli plugins.
3
3
  *
4
4
  * TS plugins should import from '@jackwener/opencli/registry' which resolves to
5
5
  * this file. It re-exports ONLY the core registration API — no serialization,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Runtime detection — identify whether opencli is running under Node.js or Bun.
2
+ * Runtime detection — identify whether ppcli is running under Node.js or Bun.
3
3
  *
4
4
  * Bun injects `globalThis.Bun` at startup, making detection trivial.
5
5
  * This module centralises the check so other code can adapt behaviour
@@ -1,4 +1,4 @@
1
- import{fullName as J,strategyLabel as K}from"./registry.js";export function serializeArg(k){return{name:k.name,type:k.type??"string",required:!!k.required,valueRequired:!!k.valueRequired,positional:!!k.positional,choices:k.choices??[],default:k.default??null,help:k.help??""}}export function serializeCommand(k){return{command:J(k),site:k.site,name:k.name,aliases:k.aliases??[],description:k.description,access:k.access,strategy:K(k),browser:!!k.browser,args:k.args.map(serializeArg),columns:k.columns??[],domain:k.domain??null,example:formatCommandExample(k),defaultFormat:k.defaultFormat??null,siteSession:k.siteSession??null}}export function formatArgSummary(k){return k.map((q)=>{if(q.positional)return q.required?`<${q.name}>`:`[${q.name}]`;return q.required?`--${q.name}`:`[--${q.name}]`}).join(" ")}function M(k){if(k.length<=4)return k.join(", ");return`${k.slice(0,4).join(", ")}, ... (+${k.length-4} more)`}function F(k){return`<${k}>`}export function formatCommandExample(k){if(k.example?.trim())return k.example.trim();const q=["opencli",k.site,k.name];for(const v of k.args)if(v.positional&&v.required)q.push(F(v.name));for(const v of k.args){if(v.positional||!v.required)continue;q.push(`--${v.name}`);if(v.type!=="bool"&&v.type!=="boolean")q.push(F(v.name))}q.push("-f","yaml");return q.join(" ")}export function formatRegistryHelpText(k){const q=[],v=k.args.filter((B)=>B.choices?.length);for(const B of v){const H=B.positional?`<${B.name}>`:`--${B.name}`,I=B.default!=null?` (default: ${B.default})`:"";q.push(` ${H}: ${M(B.choices)}${I}`)}const D=[];D.push(`Access: ${k.access}`);D.push(`Browser: ${k.browser?"yes":"no"}`);if(k.domain)D.push(`Domain: ${k.domain}`);if(k.defaultFormat)D.push(`Default format: ${k.defaultFormat}`);if(k.aliases?.length)D.push(`Aliases: ${k.aliases.join(", ")}`);q.push(D.join(" | "));q.push(`Example: ${formatCommandExample(k)}`);if(k.columns?.length)q.push(`Output columns: ${k.columns.join(", ")}`);return`
1
+ import{fullName as J,strategyLabel as K}from"./registry.js";export function serializeArg(k){return{name:k.name,type:k.type??"string",required:!!k.required,valueRequired:!!k.valueRequired,positional:!!k.positional,choices:k.choices??[],default:k.default??null,help:k.help??""}}export function serializeCommand(k){return{command:J(k),site:k.site,name:k.name,aliases:k.aliases??[],description:k.description,access:k.access,strategy:K(k),browser:!!k.browser,args:k.args.map(serializeArg),columns:k.columns??[],domain:k.domain??null,example:formatCommandExample(k),defaultFormat:k.defaultFormat??null,siteSession:k.siteSession??null}}export function formatArgSummary(k){return k.map((q)=>{if(q.positional)return q.required?`<${q.name}>`:`[${q.name}]`;return q.required?`--${q.name}`:`[--${q.name}]`}).join(" ")}function M(k){if(k.length<=4)return k.join(", ");return`${k.slice(0,4).join(", ")}, ... (+${k.length-4} more)`}function F(k){return`<${k}>`}export function formatCommandExample(k){if(k.example?.trim())return k.example.trim();const q=["ppcli",k.site,k.name];for(const v of k.args)if(v.positional&&v.required)q.push(F(v.name));for(const v of k.args){if(v.positional||!v.required)continue;q.push(`--${v.name}`);if(v.type!=="bool"&&v.type!=="boolean")q.push(F(v.name))}q.push("-f","yaml");return q.join(" ")}export function formatRegistryHelpText(k){const q=[],v=k.args.filter((B)=>B.choices?.length);for(const B of v){const H=B.positional?`<${B.name}>`:`--${B.name}`,I=B.default!=null?` (default: ${B.default})`:"";q.push(` ${H}: ${M(B.choices)}${I}`)}const D=[];D.push(`Access: ${k.access}`);D.push(`Browser: ${k.browser?"yes":"no"}`);if(k.domain)D.push(`Domain: ${k.domain}`);if(k.defaultFormat)D.push(`Default format: ${k.defaultFormat}`);if(k.aliases?.length)D.push(`Aliases: ${k.aliases.join(", ")}`);q.push(D.join(" | "));q.push(`Example: ${formatCommandExample(k)}`);if(k.columns?.length)q.push(`Output columns: ${k.columns.join(", ")}`);return`
2
2
  `+q.join(`
3
3
  `)+`
4
4
  `}
@@ -1,6 +1,6 @@
1
- import*as s from"node:fs";import*as o from"node:path";import{fileURLToPath as d}from"node:url";import h from"js-yaml";import{ArgumentError as l}from"./errors.js";import{findPackageRoot as k}from"./package-paths.js";const y=d(import.meta.url);export function getSkillsRoot(e=k(y)){return o.join(e,"skills")}export function listOpenCliSkills(e){const n=getSkillsRoot(e);if(!s.existsSync(n))return[];return s.readdirSync(n,{withFileTypes:!0}).filter((t)=>t.isDirectory()&&t.name.startsWith("opencli-")).map((t)=>S(n,t.name)).filter((t)=>t!==null).sort((t,i)=>t.name.localeCompare(i.name))}export function readOpenCliSkill(e,n="",t){const{name:i,pathInSkill:r}=L(e,n);if(!i.startsWith("opencli-"))throw new l(`Unknown OpenCLI skill: ${i}`,'Run "opencli skills list" to see available OpenCLI skills.');const f=getSkillsRoot(t),a=o.join(f,i);if(!w(a)||!s.existsSync(o.join(a,"SKILL.md")))throw new l(`Unknown OpenCLI skill: ${i}`,'Run "opencli skills list" to see available OpenCLI skills.');const c=u(r||"SKILL.md"),p=o.resolve(a,c),m=o.relative(a,p);if(m.startsWith("..")||o.isAbsolute(m))throw new l(`Invalid skill path: ${c}`,"Skill paths must stay inside the selected OpenCLI skill.");if(!s.existsSync(p)||!s.statSync(p).isFile())throw new l(`Skill file not found: ${i}/${c}`,'Run "opencli skills list <skill>" is not supported yet; read SKILL.md or a known references/... file.');return{skill:i,path:c,content:s.readFileSync(p,"utf8")}}function S(e,n){const t=o.join(e,n,"SKILL.md");if(!s.existsSync(t))return null;const i=s.readFileSync(t,"utf8"),r=I(i);return{name:typeof r.name==="string"&&r.name?r.name:n,description:typeof r.description==="string"?r.description:v(i),version:typeof r.version==="string"||typeof r.version==="number"?String(r.version):"",path:`${n}/SKILL.md`}}function L(e,n){const t=u(e);if(n)return{name:t,pathInSkill:n};const i=t.indexOf("/");if(i===-1)return{name:t,pathInSkill:""};return{name:t.slice(0,i),pathInSkill:t.slice(i+1)}}function u(e){const n=e.trim().replace(/\\/g,"/");if(!n||n.includes("\x00"))throw new l("Skill path must be non-empty.");if(n.startsWith("/")||n.split("/").some((t)=>t===".."))throw new l(`Invalid skill path: ${e}`,"Use a path relative to an OpenCLI skill directory.");return o.posix.normalize(n)}function I(e){if(!e.startsWith(`---
2
- `))return{};const n=e.indexOf(`
3
- ---`,4);if(n<0)return{};try{const t=h.load(e.slice(4,n));return t&&typeof t==="object"?t:{}}catch{return g(e.slice(4,n))}}function g(e){const n={};for(const t of e.split(`
4
- `)){const i=/^([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*)$/.exec(t);if(!i)continue;const[,r,f]=i;if(!["name","description","version"].includes(r))continue;n[r]=f.trim().replace(/^['"]|['"]$/g,"")}return n}function v(e){return(e.startsWith(`---
5
- `)?e.slice(Math.max(e.indexOf(`
6
- ---`,4)+4,0)):e).split(/\n\s*\n/).map((i)=>i.replace(/^#+\s*/gm,"").trim()).find(Boolean)??""}function w(e){try{return s.statSync(e).isDirectory()}catch{return!1}}
1
+ import*as s from"node:fs";import*as l from"node:path";import{fileURLToPath as d}from"node:url";import h from"js-yaml";import{ArgumentError as o}from"./errors.js";import{findPackageRoot as k}from"./package-paths.js";const y=d(import.meta.url);export function getSkillsRoot(i=k(y)){return l.join(i,"skills")}export function listOpenCliSkills(i){const e=getSkillsRoot(i);if(!s.existsSync(e))return[];return s.readdirSync(e,{withFileTypes:!0}).filter((t)=>t.isDirectory()&&t.name.startsWith("opencli-")).map((t)=>S(e,t.name)).filter((t)=>t!==null).sort((t,n)=>t.name.localeCompare(n.name))}export function readOpenCliSkill(i,e="",t){const{name:n,pathInSkill:r}=g(i,e);if(!n.startsWith("opencli-"))throw new o(`Unknown ppcli skill: ${n}`,'Run "ppcli skills list" to see available ppcli skills.');const f=getSkillsRoot(t),a=l.join(f,n);if(!x(a)||!s.existsSync(l.join(a,"SKILL.md")))throw new o(`Unknown ppcli skill: ${n}`,'Run "ppcli skills list" to see available ppcli skills.');const c=u(r||"SKILL.md"),p=l.resolve(a,c),m=l.relative(a,p);if(m.startsWith("..")||l.isAbsolute(m))throw new o(`Invalid skill path: ${c}`,"Skill paths must stay inside the selected ppcli skill.");if(!s.existsSync(p)||!s.statSync(p).isFile())throw new o(`Skill file not found: ${n}/${c}`,'Run "ppcli skills list <skill>" is not supported yet; read SKILL.md or a known references/... file.');return{skill:n,path:c,content:s.readFileSync(p,"utf8")}}function S(i,e){const t=l.join(i,e,"SKILL.md");if(!s.existsSync(t))return null;const n=s.readFileSync(t,"utf8"),r=v(n);return{name:typeof r.name==="string"&&r.name?r.name:e,description:typeof r.description==="string"?r.description:L(n),version:typeof r.version==="string"||typeof r.version==="number"?String(r.version):"",path:`${e}/SKILL.md`}}function g(i,e){const t=u(i);if(e)return{name:t,pathInSkill:e};const n=t.indexOf("/");if(n===-1)return{name:t,pathInSkill:""};return{name:t.slice(0,n),pathInSkill:t.slice(n+1)}}function u(i){const e=i.trim().replace(/\\/g,"/");if(!e||e.includes("\x00"))throw new o("Skill path must be non-empty.");if(e.startsWith("/")||e.split("/").some((t)=>t===".."))throw new o(`Invalid skill path: ${i}`,"Use a path relative to a ppcli skill directory.");return l.posix.normalize(e)}function v(i){if(!i.startsWith(`---
2
+ `))return{};const e=i.indexOf(`
3
+ ---`,4);if(e<0)return{};try{const t=h.load(i.slice(4,e));return t&&typeof t==="object"?t:{}}catch{return w(i.slice(4,e))}}function w(i){const e={};for(const t of i.split(`
4
+ `)){const n=/^([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*)$/.exec(t);if(!n)continue;const[,r,f]=n;if(!["name","description","version"].includes(r))continue;e[r]=f.trim().replace(/^['"]|['"]$/g,"")}return e}function L(i){return(i.startsWith(`---
5
+ `)?i.slice(Math.max(i.indexOf(`
6
+ ---`,4)+4,0)):i).split(/\n\s*\n/).map((n)=>n.replace(/^#+\s*/gm,"").trim()).find(Boolean)??""}function x(i){try{return s.statSync(i).isDirectory()}catch{return!1}}
@@ -0,0 +1,8 @@
1
+ export declare const LEGACY_USER_DIR_NAME = ".opencli";
2
+ export declare const USER_DIR_NAME = ".ppcli";
3
+ /**
4
+ * Migrate legacy ~/.opencli state into ~/.ppcli once, idempotently.
5
+ * Existing ppcli files always win, so a partial prior migration is safe to
6
+ * resume and user changes in the new directory are never rolled back.
7
+ */
8
+ export declare function migrateLegacyUserRuntime(homeDir?: string): number;
@@ -0,0 +1 @@
1
+ import*as j from"node:fs";import*as O from"node:os";import*as F from"node:path";export const LEGACY_USER_DIR_NAME=".opencli",USER_DIR_NAME=".ppcli";function K(z,q){const B=j.lstatSync(z);if(B.isDirectory()){let H=0;if(!j.existsSync(q)){j.mkdirSync(q,{recursive:!0,mode:B.mode});H++}else if(!j.lstatSync(q).isDirectory())return 0;for(const J of j.readdirSync(z))H+=K(F.join(z,J),F.join(q,J));return H}if(j.existsSync(q))return 0;j.mkdirSync(F.dirname(q),{recursive:!0});if(B.isSymbolicLink())j.symlinkSync(j.readlinkSync(z),q,process.platform==="win32"?"junction":void 0);else if(B.isFile()){j.copyFileSync(z,q,j.constants.COPYFILE_EXCL);j.chmodSync(q,B.mode)}else return 0;return 1}export function migrateLegacyUserRuntime(z=O.homedir()){const q=F.join(z,LEGACY_USER_DIR_NAME);if(!j.existsSync(q))return 0;return K(q,F.join(z,USER_DIR_NAME))}
@@ -0,0 +1 @@
1
+ export {};