publishport-opencli 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -61
- package/README.zh-CN.md +68 -68
- package/cli-manifest.json +502 -59
- package/clis/1688/shared.js +2 -2
- package/clis/36kr/news.js +1 -1
- package/clis/_atlassian/shared.js +1 -1
- package/clis/_shared/article/auth.js +1 -1
- package/clis/_shared/article/publish.js +1 -1
- package/clis/_shared/content-guard.js +1 -1
- package/clis/_shared/self-hosted-sites.js +2 -0
- package/clis/_shared/site-auth.js +1 -1
- package/clis/_shared/token-auth.js +2 -2
- package/clis/_shared/video-publish.js +1 -1
- package/clis/aibase/news.js +1 -1
- package/clis/amazon/shared.js +2 -2
- package/clis/antigravity/SKILL.md +12 -12
- package/clis/antigravity/storage.js +1 -1
- package/clis/apple-podcasts/episodes.js +1 -1
- package/clis/archive/item.js +1 -1
- package/clis/archive/search.js +1 -1
- package/clis/archive/snapshots.js +1 -1
- package/clis/archive/wayback.js +1 -1
- package/clis/arxiv/author.js +1 -1
- package/clis/baijiahao/publish.js +1 -1
- package/clis/bbc/utils.js +1 -1
- package/clis/bloomberg/businessweek.js +1 -1
- package/clis/bloomberg/utils.js +1 -1
- package/clis/booking/search.js +1 -1
- package/clis/boss/utils.js +1 -1
- package/clis/chatgpt/ask.js +1 -1
- package/clis/chatgpt/detail.js +1 -1
- package/clis/chatgpt/history.js +1 -1
- package/clis/chatgpt/project-file-add.js +1 -1
- package/clis/chatgpt/project-list.js +1 -1
- package/clis/chatgpt/utils.js +2 -2
- package/clis/chess/utils.js +1 -1
- package/clis/claude/ask.js +1 -1
- package/clis/claude/history.js +1 -1
- package/clis/claude/utils.js +1 -1
- package/clis/codex/extract-diff.js +1 -1
- package/clis/coingecko/coin.js +1 -1
- package/clis/crates/utils.js +1 -1
- package/clis/csdn/stats.js +1 -1
- package/clis/dblp/utils.js +1 -1
- package/clis/defillama/utils.js +1 -1
- package/clis/devto/publish.js +1 -1
- package/clis/discord-app/utils.js +1 -1
- package/clis/dockerhub/utils.js +1 -1
- package/clis/douban/utils.js +1 -1
- package/clis/douyin/_shared/tos-upload.js +1 -1
- package/clis/douyin/hashtag.js +1 -1
- package/clis/douyin/publish-image.js +1 -1
- package/clis/douyin/publish.js +1 -1
- package/clis/eastmoney/announcement.js +1 -1
- package/clis/eastmoney/rank.js +1 -1
- package/clis/eastmoney/sectors.js +1 -1
- package/clis/endoflife/utils.js +1 -1
- package/clis/flathub/utils.js +1 -1
- package/clis/gemini/detail.js +1 -1
- package/clis/gemini/read.js +1 -1
- package/clis/geogebra/add-circle.js +1 -1
- package/clis/geogebra/add-line.js +1 -1
- package/clis/geogebra/add-point.js +1 -1
- package/clis/geogebra/add-polygon.js +1 -1
- package/clis/geogebra/eval.js +1 -1
- package/clis/geogebra/hexagon.js +1 -1
- package/clis/geogebra/info.js +1 -1
- package/clis/geogebra/triangle.js +1 -1
- package/clis/ghost/login.js +1 -0
- package/clis/ghost/publish.js +1 -1
- package/clis/ghost/shared.js +24 -1
- package/clis/ghost/sites.js +1 -0
- package/clis/ghost/whoami.js +1 -1
- package/clis/gitee/user.js +2 -2
- package/clis/github-trending/repos.js +1 -1
- package/clis/goproxy/utils.js +1 -1
- package/clis/grok/export-all.js +1 -1
- package/clis/grok/export.js +1 -1
- package/clis/hashnode/publish.js +1 -1
- package/clis/hf/datasets.js +1 -1
- package/clis/hf/models.js +1 -1
- package/clis/hf/paper.js +1 -1
- package/clis/hf/spaces.js +1 -1
- package/clis/homebrew/utils.js +1 -1
- package/clis/instagram/note.js +1 -1
- package/clis/jimeng/generate.js +1 -1
- package/clis/juejin/utils.js +1 -1
- package/clis/kuaishou/publish.js +1 -1
- package/clis/lesswrong/tag.js +1 -1
- package/clis/lichess/utils.js +1 -1
- package/clis/linkedin/search.js +2 -2
- package/clis/linux-do/feed.js +2 -2
- package/clis/lobsters/domain.js +1 -1
- package/clis/mastodon/login.js +1 -0
- package/clis/mastodon/post.js +1 -1
- package/clis/mastodon/shared.js +10 -10
- package/clis/mastodon/sites.js +1 -0
- package/clis/mastodon/whoami.js +1 -1
- package/clis/maven/utils.js +1 -1
- package/clis/mdn/search.js +1 -1
- package/clis/medium/tag.js +1 -1
- package/clis/notebooklm/current.js +1 -1
- package/clis/notebooklm/get.js +1 -1
- package/clis/notebooklm/history.js +1 -1
- package/clis/notebooklm/note-list.js +1 -1
- package/clis/notebooklm/notes-get.js +1 -1
- package/clis/notebooklm/open.js +1 -1
- package/clis/notebooklm/source-fulltext.js +1 -1
- package/clis/notebooklm/source-get.js +1 -1
- package/clis/notebooklm/source-guide.js +1 -1
- package/clis/notebooklm/source-list.js +1 -1
- package/clis/notebooklm/summary.js +1 -1
- package/clis/notebooklm/utils.js +1 -1
- package/clis/npm/utils.js +1 -1
- package/clis/nuget/utils.js +1 -1
- package/clis/nvd/cve.js +1 -1
- package/clis/oeis/utils.js +1 -1
- package/clis/ones/common.js +2 -2
- package/clis/ones/me.js +1 -1
- package/clis/ones/tasks.js +1 -1
- package/clis/ones/token-info.js +1 -1
- package/clis/ones/worklog.js +1 -1
- package/clis/openalex/utils.js +1 -1
- package/clis/oschina/stats.js +1 -1
- package/clis/osv/utils.js +1 -1
- package/clis/packagist/utils.js +1 -1
- package/clis/pixiv/detail.js +1 -1
- package/clis/pixiv/user.js +1 -1
- package/clis/producthunt/utils.js +1 -1
- package/clis/pypi/utils.js +1 -1
- package/clis/qiita/gql.js +2 -2
- package/clis/rest-countries/utils.js +1 -1
- package/clis/rfc/utils.js +1 -1
- package/clis/rubygems/utils.js +1 -1
- package/clis/semanticscholar/utils.js +1 -1
- package/clis/slock/errors.js +1 -1
- package/clis/spotify/spotify.js +1 -1
- package/clis/spotify/utils.js +1 -1
- package/clis/stackoverflow/utils.js +1 -1
- package/clis/steam/utils.js +1 -1
- package/clis/substack/auth.js +2 -2
- package/clis/suno/generate.js +2 -2
- package/clis/suno/list.js +3 -3
- package/clis/telegram/send.js +1 -1
- package/clis/tiktok/creator-videos.js +1 -1
- package/clis/tiktok/publish.js +1 -1
- package/clis/tiktok/utils.js +1 -1
- package/clis/trae-cn/activity.js +1 -1
- package/clis/trae-cn/approve.js +1 -1
- package/clis/trae-cn/ask.js +1 -1
- package/clis/trae-cn/dump.js +1 -1
- package/clis/trae-cn/export.js +1 -1
- package/clis/trae-cn/model.js +1 -1
- package/clis/trae-cn/new.js +1 -1
- package/clis/trae-cn/read.js +1 -1
- package/clis/trae-cn/screenshot.js +1 -1
- package/clis/trae-cn/select-model.js +1 -1
- package/clis/trae-cn/send.js +1 -1
- package/clis/trae-cn/setup.js +1 -1
- package/clis/trae-cn/status.js +1 -1
- package/clis/trae-cn/targets.js +1 -1
- package/clis/trae-cn/watch.js +1 -1
- package/clis/trae-solo/state-fs.js +1 -1
- package/clis/tumblr/publish.js +1 -1
- package/clis/tvmaze/utils.js +1 -1
- package/clis/twitter/bookmark-folder.js +1 -1
- package/clis/twitter/download.js +1 -1
- package/clis/twitter/followers.js +2 -2
- package/clis/twitter/following.js +2 -2
- package/clis/twitter/likes.js +1 -1
- package/clis/twitter/list-add-batch.js +1 -1
- package/clis/twitter/list-add-core.js +1 -1
- package/clis/twitter/list-add.js +1 -1
- package/clis/twitter/list-create.js +1 -1
- package/clis/twitter/list-delete.js +3 -3
- package/clis/twitter/list-remove-batch.js +1 -1
- package/clis/twitter/list-remove.js +1 -1
- package/clis/twitter/list-tweets.js +1 -1
- package/clis/twitter/profile.js +1 -1
- package/clis/twitter/search.js +1 -1
- package/clis/twitter/shared.js +1 -1
- package/clis/twitter/tweets.js +1 -1
- package/clis/uisdc/news.js +1 -1
- package/clis/v2ex/daily.js +2 -2
- package/clis/v2ex/me.js +2 -2
- package/clis/v2ex/notifications.js +1 -1
- package/clis/wikidata/utils.js +1 -1
- package/clis/wikipedia/page.js +2 -2
- package/clis/wikipedia/summary.js +1 -1
- package/clis/wikipedia/utils.js +1 -1
- package/clis/wordpress/login.js +1 -1
- package/clis/wordpress/publish.js +1 -1
- package/clis/wordpress/shared.js +3 -3
- package/clis/wordpress/sites.js +1 -0
- package/clis/wordpress/whoami.js +1 -1
- package/clis/woshipm/stats.js +1 -1
- package/clis/xiaoe/content.js +1 -1
- package/clis/xiaohongshu/draft-delete.js +1 -1
- package/clis/xiaohongshu/draft-open.js +1 -1
- package/clis/xiaohongshu/draft-utils.js +2 -2
- package/clis/xiaohongshu/publish-video.js +1 -1
- package/clis/xiaohongshu/publish.js +1 -1
- package/clis/xiaoyuzhou/auth.js +1 -1
- package/clis/xiaoyuzhou/transcript.js +1 -1
- package/clis/yollomi/background.js +1 -1
- package/clis/yollomi/edit.js +1 -1
- package/clis/yollomi/generate.js +1 -1
- package/clis/yollomi/try-on.js +1 -1
- package/clis/youtube/publish.js +1 -1
- package/clis/zhihu/answer-comments.js +1 -1
- package/clis/zhihu/answer-detail.js +1 -1
- package/clis/zhihu/collection.js +2 -2
- package/clis/zhihu/collections.js +1 -1
- package/clis/zhihu/question.js +1 -1
- package/clis/zhihu/search.js +1 -1
- package/clis/zhihu/target.js +1 -1
- package/clis/zhihu/user-arg.js +1 -1
- package/dist/src/adapter-shadow.js +1 -1
- package/dist/src/browser/analyze.js +1 -1
- package/dist/src/browser/article-extract.d.ts +1 -1
- package/dist/src/browser/base-page.js +3 -3
- package/dist/src/browser/cdp.js +1 -1
- package/dist/src/browser/daemon-client.d.ts +1 -1
- package/dist/src/browser/daemon-lifecycle.js +6 -6
- package/dist/src/browser/daemon-version.js +1 -1
- package/dist/src/browser/dom-snapshot.d.ts +1 -1
- package/dist/src/browser/dom-snapshot.js +2 -2
- package/dist/src/browser/errors.js +2 -2
- package/dist/src/browser/network-cache.js +1 -1
- package/dist/src/browser/profile.js +1 -1
- package/dist/src/browser/target-resolver.js +4 -4
- package/dist/src/browser/verify-fixture.js +1 -1
- package/dist/src/browser-session-lock.js +1 -1
- package/dist/src/build-manifest.d.ts +1 -1
- package/dist/src/build-manifest.js +1 -1
- package/dist/src/cli-argv-preprocess.d.ts +4 -4
- package/dist/src/cli-argv-preprocess.js +1 -1
- package/dist/src/cli.d.ts +1 -1
- package/dist/src/cli.js +19 -19
- package/dist/src/commanderAdapter.js +1 -1
- package/dist/src/commands/auth.js +2 -2
- package/dist/src/commands/daemon.d.ts +4 -4
- package/dist/src/commands/daemon.js +1 -1
- package/dist/src/completion-shared.js +12 -12
- package/dist/src/completion.d.ts +2 -2
- package/dist/src/constants.d.ts +1 -1
- package/dist/src/constants.js +1 -1
- package/dist/src/daemon-utils.d.ts +1 -1
- package/dist/src/daemon-utils.js +1 -1
- package/dist/src/daemon.d.ts +2 -2
- package/dist/src/daemon.js +1 -1
- package/dist/src/discovery.d.ts +7 -7
- package/dist/src/discovery.js +2 -2
- package/dist/src/doctor.d.ts +1 -1
- package/dist/src/doctor.js +6 -6
- package/dist/src/electron-apps.d.ts +1 -1
- package/dist/src/electron-apps.js +1 -1
- package/dist/src/errors.d.ts +2 -2
- package/dist/src/errors.js +1 -1
- package/dist/src/execution.js +1 -1
- package/dist/src/external.d.ts +1 -1
- package/dist/src/external.js +1 -1
- package/dist/src/help.d.ts +1 -1
- package/dist/src/help.js +3 -3
- package/dist/src/hooks.d.ts +1 -1
- package/dist/src/launcher.js +2 -2
- package/dist/src/logger.d.ts +1 -1
- package/dist/src/main.d.ts +1 -1
- package/dist/src/main.js +1 -1
- package/dist/src/observation/artifact.js +2 -2
- package/dist/src/package-paths.js +1 -1
- package/dist/src/package-paths.test.d.ts +1 -0
- package/dist/src/plugin-manifest.d.ts +12 -3
- package/dist/src/plugin-manifest.js +1 -1
- package/dist/src/plugin-scaffold.d.ts +2 -2
- package/dist/src/plugin-scaffold.js +9 -9
- package/dist/src/plugin.d.ts +4 -4
- package/dist/src/plugin.js +10 -10
- package/dist/src/rate-limit.js +1 -1
- package/dist/src/registry-api.d.ts +1 -1
- package/dist/src/runtime-detect.d.ts +1 -1
- package/dist/src/serialization.js +1 -1
- package/dist/src/skills.js +6 -6
- package/dist/src/user-runtime.d.ts +8 -0
- package/dist/src/user-runtime.js +1 -0
- package/dist/src/user-runtime.test.d.ts +1 -0
- package/dist/src/validate.js +1 -1
- package/dist/src/validate.test.d.ts +1 -1
- package/docs/guide/browser-bridge.md +23 -23
- package/docs/guide/electron-app-cli.md +3 -3
- package/docs/guide/exit-codes.md +3 -3
- package/docs/guide/extending-opencli.md +37 -37
- package/docs/guide/getting-started.md +19 -19
- package/docs/guide/installation.md +6 -6
- package/docs/guide/plugins.md +32 -32
- package/docs/guide/remote-orchestration.md +5 -5
- package/docs/guide/troubleshooting.md +9 -9
- package/package.json +3 -2
- package/scripts/fetch-adapters.js +6 -6
- package/scripts/postinstall.js +14 -14
- package/skills/opencli-adapter-author/SKILL.md +24 -24
- package/skills/opencli-adapter-author/references/adapter-template.md +13 -13
- package/skills/opencli-adapter-author/references/api-discovery.md +25 -25
- package/skills/opencli-adapter-author/references/coverage-matrix.md +3 -3
- package/skills/opencli-adapter-author/references/field-decode-playbook.md +10 -10
- package/skills/opencli-adapter-author/references/jsdom-fixture-pattern.md +2 -2
- package/skills/opencli-adapter-author/references/site-memory.md +11 -11
- package/skills/opencli-adapter-author/references/site-recon.md +7 -7
- package/skills/opencli-adapter-author/references/strategy-selection.md +3 -3
- package/skills/opencli-adapter-author/references/success-rate-pitfalls.md +4 -4
- package/skills/opencli-adapter-author/references/typed-errors.md +2 -2
- package/skills/opencli-autofix/SKILL.md +29 -29
- package/skills/opencli-browser/SKILL.md +55 -55
- package/skills/opencli-browser-sitemap/SKILL.md +7 -7
- package/skills/opencli-sitemap-author/SKILL.md +11 -11
- package/skills/opencli-sitemap-author/references/sitemap-schema.md +19 -19
- 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 '
|
|
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`
|
|
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:
|
|
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}`}
|
package/dist/src/hooks.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Plugin lifecycle hooks: allows plugins to tap into
|
|
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
|
package/dist/src/launcher.js
CHANGED
|
@@ -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 ~/.
|
|
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 ~/.
|
|
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}
|
package/dist/src/logger.d.ts
CHANGED
package/dist/src/main.d.ts
CHANGED
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(),".
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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:
|
|
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
|
|
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??`
|
|
2
|
-
`);z.push("opencli-plugin.json");const U={name:`opencli-plugin-${b}`,version:"0.1.0",type:"module",description:x.description??`
|
|
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??`
|
|
43
|
+
${x.description??`A ppcli plugin: ${b}`}
|
|
44
44
|
|
|
45
45
|
## Install
|
|
46
46
|
|
|
47
47
|
\`\`\`bash
|
|
48
48
|
# From local development directory
|
|
49
|
-
|
|
49
|
+
ppcli plugin install file://${q}
|
|
50
50
|
|
|
51
51
|
# From GitHub (after publishing)
|
|
52
|
-
|
|
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
|
-
|
|
66
|
+
ppcli plugin install file://${q}
|
|
67
67
|
|
|
68
68
|
# Verify commands are registered
|
|
69
|
-
|
|
69
|
+
ppcli list | grep ${b}
|
|
70
70
|
|
|
71
71
|
# Run a command
|
|
72
|
-
|
|
73
|
-
|
|
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)}
|
package/dist/src/plugin.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Plugin management: install, uninstall, and list plugins.
|
|
3
3
|
*
|
|
4
|
-
* Plugins live in ~/.
|
|
5
|
-
* Monorepo clones live in ~/.
|
|
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: ~/.
|
|
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 → ~/.
|
|
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;
|
package/dist/src/plugin.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import*as
|
|
2
|
-
`);K.renameSync(V,Q)}catch(Y){try{K.rmSync(V,{force:!0})}catch{}throw Y}}export function writeLockFile(q){writeLockFileWithFs(q,
|
|
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
|
-
- `)}`);
|
|
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
|
|
14
|
-
- ${
|
|
15
|
-
- `)}`);
|
|
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
|
-
- `)}`);
|
|
18
|
-
- ${
|
|
19
|
-
- `)}`);Z.push({name:
|
|
20
|
-
`)[0].trim();if(V&&
|
|
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};
|
package/dist/src/rate-limit.js
CHANGED
|
@@ -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,".
|
|
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
|
-
* Runtime detection — identify whether
|
|
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=["
|
|
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
|
`}
|
package/dist/src/skills.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import*as s from"node:fs";import*as
|
|
2
|
-
`))return{};const
|
|
3
|
-
---`,4);if(
|
|
4
|
-
`)){const
|
|
5
|
-
`)?
|
|
6
|
-
---`,4)+4,0)):
|
|
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 H from"node:os";import*as v from"node:path";export const LEGACY_USER_DIR_NAME=".opencli",USER_DIR_NAME=".ppcli";function B(k){try{j.lstatSync(k);return!0}catch{return!1}}function F(k,b){const q=j.lstatSync(k);if(q.isDirectory()){let w=0;if(!B(b)){j.mkdirSync(b,{recursive:!0,mode:q.mode});w++}else if(!j.lstatSync(b).isDirectory())return 0;for(const z of j.readdirSync(k))try{w+=F(v.join(k,z),v.join(b,z))}catch{}return w}if(B(b))return 0;j.mkdirSync(v.dirname(b),{recursive:!0});if(q.isSymbolicLink())j.symlinkSync(j.readlinkSync(k),b,process.platform==="win32"?"junction":void 0);else if(q.isFile()){j.copyFileSync(k,b,j.constants.COPYFILE_EXCL);j.chmodSync(b,q.mode)}else return 0;return 1}export function migrateLegacyUserRuntime(k=H.homedir()){try{const b=v.join(k,LEGACY_USER_DIR_NAME);if(!j.existsSync(b))return 0;return F(b,v.join(k,USER_DIR_NAME))}catch{return 0}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|