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.
- 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/clis/chatgpt/detail.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as V,Strategy as W}from"@jackwener/opencli/registry";import{EmptyResultError as q}from"@jackwener/opencli/errors";import{CHATGPT_DOMAIN as z,CHATGPT_URL as B,CONVERSATION_MESSAGE_SELECTOR as H,ensureChatGPTLogin as b,getChatGPTDetailRows as j,normalizeBooleanFlag as Q,parseChatGPTConversationId as D,requireNonNegativeInt as F,requirePositiveInt as v,waitForChatGPTDetailRows as L}from"./utils.js";export const detailCommand=V({site:"chatgpt",name:"detail",access:"read",description:"Open a ChatGPT web conversation by ID and read its messages",domain:z,strategy:W.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"id",positional:!0,required:!0,help:"Conversation ID or full /c/<id> URL"},{name:"markdown",type:"boolean",default:!1,help:"Emit assistant replies as markdown"},{name:"wait",type:"boolean",default:!1,help:"Wait until the conversation stops generating and stabilizes"},{name:"timeout",type:"int",default:120,help:"Max seconds to wait when --wait is true"},{name:"stable",type:"int",default:6,help:"Seconds the final messages must remain unchanged when --wait is true"}],columns:["Index","Role","Text","Generating","StableSeconds"],func:async(f,x)=>{const J=D(x.id),K=Q(x.markdown,!1),X=Q(x.wait,!1),Y=v(Number(x.timeout??120),"chatgpt detail --timeout","Example:
|
|
1
|
+
import{cli as V,Strategy as W}from"@jackwener/opencli/registry";import{EmptyResultError as q}from"@jackwener/opencli/errors";import{CHATGPT_DOMAIN as z,CHATGPT_URL as B,CONVERSATION_MESSAGE_SELECTOR as H,ensureChatGPTLogin as b,getChatGPTDetailRows as j,normalizeBooleanFlag as Q,parseChatGPTConversationId as D,requireNonNegativeInt as F,requirePositiveInt as v,waitForChatGPTDetailRows as L}from"./utils.js";export const detailCommand=V({site:"chatgpt",name:"detail",access:"read",description:"Open a ChatGPT web conversation by ID and read its messages",domain:z,strategy:W.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"id",positional:!0,required:!0,help:"Conversation ID or full /c/<id> URL"},{name:"markdown",type:"boolean",default:!1,help:"Emit assistant replies as markdown"},{name:"wait",type:"boolean",default:!1,help:"Wait until the conversation stops generating and stabilizes"},{name:"timeout",type:"int",default:120,help:"Max seconds to wait when --wait is true"},{name:"stable",type:"int",default:6,help:"Seconds the final messages must remain unchanged when --wait is true"}],columns:["Index","Role","Text","Generating","StableSeconds"],func:async(f,x)=>{const J=D(x.id),K=Q(x.markdown,!1),X=Q(x.wait,!1),Y=v(Number(x.timeout??120),"chatgpt detail --timeout","Example: ppcli chatgpt detail <id> --wait true --timeout 600"),Z=F(Number(x.stable??6),"chatgpt detail --stable","Example: ppcli chatgpt detail <id> --wait true --stable 6");await f.goto(`${B}/c/${J}`,{settleMs:2000});try{await f.wait({selector:H,timeout:10})}catch{}await b(f,"ChatGPT detail requires a logged-in ChatGPT session.");const{messages:$,rows:U}=X?await L(f,{wantMarkdown:K,timeoutSeconds:Y,stableSeconds:Z}):await j(f,{wantMarkdown:K});if(!$.length)throw new q("chatgpt detail",`No visible ChatGPT messages were found for conversation ${J}.`);return U}});
|
package/clis/chatgpt/history.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as r,Strategy as o}from"@jackwener/opencli/registry";import{EmptyResultError as
|
|
1
|
+
import{cli as r,Strategy as o}from"@jackwener/opencli/registry";import{EmptyResultError as a}from"@jackwener/opencli/errors";import{CHATGPT_DOMAIN as n,ensureChatGPTLogin as h,ensureOnChatGPT as l,getConversationList as m,requirePositiveInt as c}from"./utils.js";export const historyCommand=r({site:"chatgpt",name:"history",access:"read",description:"List visible ChatGPT web conversation history from the sidebar",domain:n,strategy:o.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"limit",type:"int",default:20,help:"Max conversations to show"}],columns:["Index","Id","Title","Url"],func:async(t,e)=>{const s=c(Number(e.limit??20),"chatgpt history --limit","Example: ppcli chatgpt history --limit 20");await l(t);await h(t,"ChatGPT history requires a logged-in ChatGPT session.");const i=await m(t);if(!i.length)throw new a("chatgpt history","No ChatGPT conversation links were visible in the sidebar.");return i.slice(0,s)}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as s,Strategy as d}from"@jackwener/opencli/registry";import{ArgumentError as p,CommandExecutionError as n}from"@jackwener/opencli/errors";import{CHATGPT_DOMAIN as c,CHATGPT_URL as f,parseChatGPTProjectId as u,uploadChatGPTProjectFiles as h}from"./utils.js";function l(r){if(Array.isArray(r))return r.flatMap((e)=>l(e));return String(r??"").split(",").map((e)=>e.trim()).filter(Boolean)}export const projectFileAddCommand=s({site:"chatgpt",name:"project-file-add",access:"write",description:"Upload files to a ChatGPT project as project knowledge (not just conversation attachments)",domain:c,strategy:d.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"file",positional:!0,required:!0,help:"Local file path(s) to upload; comma-separated paths are supported"},{name:"id",required:!0,help:"Project ID or /g/g-p-<id> URL"}],columns:["Status","File"],func:async(r,e)=>{const a=l(e.file);if(!a.length)throw new p("chatgpt project-file-add requires at least one file path","Example:
|
|
1
|
+
import{cli as s,Strategy as d}from"@jackwener/opencli/registry";import{ArgumentError as p,CommandExecutionError as n}from"@jackwener/opencli/errors";import{CHATGPT_DOMAIN as c,CHATGPT_URL as f,parseChatGPTProjectId as u,uploadChatGPTProjectFiles as h}from"./utils.js";function l(r){if(Array.isArray(r))return r.flatMap((e)=>l(e));return String(r??"").split(",").map((e)=>e.trim()).filter(Boolean)}export const projectFileAddCommand=s({site:"chatgpt",name:"project-file-add",access:"write",description:"Upload files to a ChatGPT project as project knowledge (not just conversation attachments)",domain:c,strategy:d.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"file",positional:!0,required:!0,help:"Local file path(s) to upload; comma-separated paths are supported"},{name:"id",required:!0,help:"Project ID or /g/g-p-<id> URL"}],columns:["Status","File"],func:async(r,e)=>{const a=l(e.file);if(!a.length)throw new p("chatgpt project-file-add requires at least one file path","Example: ppcli chatgpt project-file-add report.pdf --id 12345678");const i=u(e.id);let t;try{t=await h(r,i,a)}catch(o){throw new n(`Failed to upload file to ChatGPT project knowledge: ${o instanceof Error?o.message:String(o)}`)}if(t?.inputError)throw new p(t.reason||"Invalid project file path","Provide an existing local file path that ChatGPT project knowledge can upload.");if(!t?.ok)throw new n(t?.reason||"Failed to upload file to ChatGPT project knowledge",`Open ${f}/g/g-p-${i} and verify the project accepts file uploads. If your browser needs a proxy, configure it outside this command.`);return t.files.map((o)=>({Status:"\uD83D\uDCC4 uploaded to project knowledge",File:o}))}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as s,Strategy as o}from"@jackwener/opencli/registry";import{EmptyResultError as a}from"@jackwener/opencli/errors";import{CHATGPT_DOMAIN as c,ensureChatGPTLogin as
|
|
1
|
+
import{cli as s,Strategy as o}from"@jackwener/opencli/registry";import{EmptyResultError as a}from"@jackwener/opencli/errors";import{CHATGPT_DOMAIN as c,ensureChatGPTLogin as l,getProjectList as n,requirePositiveInt as p}from"./utils.js";export const projectListCommand=s({site:"chatgpt",name:"project-list",access:"read",description:"List visible ChatGPT projects from the sidebar",domain:c,strategy:o.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"limit",type:"int",default:20,help:"Max projects to show"}],columns:["Index","Id","Title","Url"],func:async(t,i)=>{const r=p(Number(i.limit??20),"chatgpt project-list --limit","Example: ppcli chatgpt project-list --limit 20");await l(t,"ChatGPT project-list requires a logged-in ChatGPT session.");const e=await n(t);if(!e.length)throw new a("chatgpt project-list","No ChatGPT project links were visible in the sidebar.");return e.slice(0,r)}});
|
package/clis/chatgpt/utils.js
CHANGED
|
@@ -28,7 +28,7 @@ import{htmlToMarkdown as i}from"@jackwener/opencli/utils";import{ArgumentError a
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
findComposer.toString = () => 'findComposer';
|
|
31
|
-
`}export function normalizeBooleanFlag(Q,X=!1){if(typeof Q==="boolean")return Q;if(Q==null||Q==="")return X;const Y=String(Q).trim().toLowerCase();return Y==="true"||Y==="1"||Y==="yes"||Y==="on"}export function requireNonEmptyPrompt(Q,X){const Y=String(Q??"").trim();if(!Y)throw new k(`${X} prompt cannot be empty`,`Example:
|
|
31
|
+
`}export function normalizeBooleanFlag(Q,X=!1){if(typeof Q==="boolean")return Q;if(Q==null||Q==="")return X;const Y=String(Q).trim().toLowerCase();return Y==="true"||Y==="1"||Y==="yes"||Y==="on"}export function requireNonEmptyPrompt(Q,X){const Y=String(Q??"").trim();if(!Y)throw new k(`${X} prompt cannot be empty`,`Example: ppcli ${X} "hello"`);return Y}export function requirePositiveInt(Q,X,Y){if(!Number.isInteger(Q)||Q<1)throw new k(`${X} must be a positive integer`,Y);return Q}export function requireNonNegativeInt(Q,X,Y){if(!Number.isInteger(Q)||Q<0)throw new k(`${X} must be a non-negative integer`,Y);return Q}export function unwrapEvaluateResult(Q){if(Q&&!Array.isArray(Q)&&typeof Q==="object"&&"session"in Q&&"data"in Q)return Q.data;return Q}export function requireArrayEvaluateResult(Q,X){if(!Array.isArray(Q)){if(Q&&typeof Q==="object"&&"error"in Q)throw new F(`${X}: ${String(Q.error)}`);throw new F(`${X} returned malformed extraction payload`)}return Q}export function requireObjectEvaluateResult(Q,X){if(!Q||Array.isArray(Q)||typeof Q!=="object")throw new F(`${X} returned malformed extraction payload`);return Q}export function requireBooleanEvaluateResult(Q,X){if(typeof Q!=="boolean")throw new F(`${X} returned malformed extraction payload`);return Q}function u(Q){return Q===CHATGPT_DOMAIN||Q.endsWith(`.${CHATGPT_DOMAIN}`)}function n(Q){const X=String(Q||"").match(/^\/g\/g-p-([a-f0-9]{8,})(?:[-/]|$)/i);return X?X[1].toLowerCase():""}function S(Q){try{const X=new URL(String(Q||""),CHATGPT_URL);if(X.protocol!=="https:"||!u(X.hostname))return"";return n(X.pathname)}catch{return""}}export function parseChatGPTConversationId(Q){const X=String(Q??"").trim();if(/^https?:\/\//i.test(X)){try{const Z=new URL(X);if(Z.protocol!=="https:"||Z.hostname!==CHATGPT_DOMAIN&&!Z.hostname.endsWith(`.${CHATGPT_DOMAIN}`))throw Error("off-domain");const $=Z.pathname.match(/^\/(?:g\/g-p-[^/]+\/)?c\/([A-Za-z0-9_-]{8,})$/);if($)return $[1]}catch{}throw new k("chatgpt detail requires a conversation id or chatgpt.com /c/<id> URL","Example: ppcli chatgpt detail https://chatgpt.com/c/123e4567-e89b-12d3-a456-426614174000")}const Y=X.match(/^\/(?:g\/g-p-[^/]+\/)?c\/([A-Za-z0-9_-]{8,})(?:[?#].*)?$/);if(Y)return Y[1];if(/^[A-Za-z0-9_-]{8,}$/.test(X))return X;throw new k("chatgpt detail requires a conversation id or chatgpt.com /c/<id> URL","Example: ppcli chatgpt detail 123e4567-e89b-12d3-a456-426614174000")}export async function currentChatGPTUrl(Q){const X=unwrapEvaluateResult(await Q.evaluate("window.location.href").catch(()=>""));return typeof X==="string"?X:""}export async function isOnChatGPT(Q){const X=await currentChatGPTUrl(Q);if(!X)return!1;try{const Y=new URL(X).hostname;return Y===CHATGPT_DOMAIN||Y.endsWith(`.${CHATGPT_DOMAIN}`)}catch{return!1}}const f='#prompt-textarea, [data-testid="prompt-textarea"]',R='a[href*="/c/"]',c='a[href*="/g/g-p-"]';export const CONVERSATION_MESSAGE_SELECTOR='[data-message-author-role], article[data-testid*="conversation-turn"]';export async function ensureOnChatGPT(Q){if(await isOnChatGPT(Q))return!1;await Q.goto(CHATGPT_URL,{settleMs:2000});try{await Q.wait({selector:f,timeout:8})}catch{}return!0}export async function startNewChat(Q){await Q.goto(`${CHATGPT_URL}/new`,{settleMs:2000});try{await Q.wait({selector:f,timeout:8})}catch{}}export async function openChatGPTConversation(Q,X){const Y=parseChatGPTConversationId(X);await Q.goto(`${CHATGPT_URL}/c/${Y}`,{settleMs:2000});try{await Q.wait({selector:f,timeout:8})}catch{}return Y}export async function getPageState(Q){return requireObjectEvaluateResult(unwrapEvaluateResult(await Q.evaluate(`(() => {
|
|
32
32
|
const isVisible = (el) => {
|
|
33
33
|
if (!(el instanceof HTMLElement)) return false;
|
|
34
34
|
const style = window.getComputedStyle(el);
|
|
@@ -333,7 +333,7 @@ import{htmlToMarkdown as i}from"@jackwener/opencli/utils";import{ArgumentError a
|
|
|
333
333
|
}
|
|
334
334
|
}
|
|
335
335
|
})()
|
|
336
|
-
`);await Q.wait(0.5)}export function parseChatGPTProjectId(Q){const X=String(Q??"").trim();if(/^https?:\/\//i.test(X)||X.startsWith("/")){const Z=S(X);if(Z)return Z;throw new k("chatgpt project commands require a chatgpt.com project id or /g/g-p-<id> URL","Example:
|
|
336
|
+
`);await Q.wait(0.5)}export function parseChatGPTProjectId(Q){const X=String(Q??"").trim();if(/^https?:\/\//i.test(X)||X.startsWith("/")){const Z=S(X);if(Z)return Z;throw new k("chatgpt project commands require a chatgpt.com project id or /g/g-p-<id> URL","Example: ppcli chatgpt project-file-add report.pdf --id 12345678")}const Y=X.match(/^g-p-([a-f0-9]{8,})/i);if(Y)return Y[1].toLowerCase();if(/^[a-f0-9]{8,}$/i.test(X))return X.toLowerCase();throw new k("chatgpt project commands require a project id or /g/g-p-<id> URL","Example: ppcli chatgpt project-file-add report.pdf --id 12345678")}export async function sendChatGPTMessage(Q,X){await Q.evaluate(`
|
|
337
337
|
(() => {
|
|
338
338
|
const labels = ${JSON.stringify(A)};
|
|
339
339
|
const closeBtn = Array.from(document.querySelectorAll('button')).find(b => labels.includes(b.getAttribute('aria-label') || ''));
|
package/clis/chess/utils.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ArgumentError as X,CommandExecutionError as K,EmptyResultError as B}from"@jackwener/opencli/errors";export const API_BASE="https://api.chess.com/pub",UA="Mozilla/5.0 (compatible;
|
|
1
|
+
import{ArgumentError as X,CommandExecutionError as K,EmptyResultError as B}from"@jackwener/opencli/errors";export const API_BASE="https://api.chess.com/pub",UA="Mozilla/5.0 (compatible; ppcli/1.0)";const D=/^[a-zA-Z0-9_-]{3,25}$/,G=/^https:\/\/www\.chess\.com\/game\/(live|daily)\/(\d+)/i;export function isPlainObject(q){return q!==null&&typeof q==="object"&&!Array.isArray(q)}function Y(q){return q===void 0||q===null||isPlainObject(q)}export function validateUsername(q){const H=String(q??"").trim().toLowerCase();if(!H)throw new X("<username> is required");if(!D.test(H))throw new X(`Invalid Chess.com username "${q}"`,"Usernames are 3-25 chars: a-z, 0-9, hyphen, underscore.");return H}export function parseGameUrl(q){const H=String(q??"").trim();if(!H)throw new X("<game-url> is required");const F=H.match(G);if(!F)throw new X(`Invalid Chess.com game URL: "${q}"`,"Expected https://www.chess.com/game/live/<id> or https://www.chess.com/game/daily/<id>.");return{kind:F[1].toLowerCase(),id:F[2]}}export async function chessApi(q,H=fetch){const F=q.startsWith("http")?q:`${API_BASE}${q}`;let J;try{J=await H(F,{headers:{"User-Agent":UA,accept:"application/json"}})}catch(Q){throw new K(`Failed to fetch Chess.com API ${F}: ${Q?.message||Q}`)}if(!J||typeof J!=="object")throw new K(`Chess.com API returned an invalid response object for ${F}`);if(J.status===404)throw new B(`Chess.com returned 404 for ${F}`);if(!J.ok)throw new K(`Chess.com API returned HTTP ${J.status} for ${F}`);let V;try{V=await J.json()}catch(Q){throw new K(`Chess.com API returned malformed JSON for ${F}: ${Q?.message||Q}`)}if(!isPlainObject(V))throw new K(`Chess.com API returned an unexpected payload shape for ${F}`);return V}export function summarizeStats(q,H){const F=q?.[H];if(!F)return null;if(!isPlainObject(F))throw new K(`Chess.com stats payload for ${H} is not an object`);if(!Y(F.last))throw new K(`Chess.com stats payload for ${H}.last is not an object`);if(!Y(F.best))throw new K(`Chess.com stats payload for ${H}.best is not an object`);if(!Y(F.record))throw new K(`Chess.com stats payload for ${H}.record is not an object`);const J=isPlainObject(F.record)?F.record:{};return{kind:H.replace(/^chess_/,""),rating_current:F.last?.rating??"",rating_best:F.best?.rating??"",wins:J.win??"",losses:J.loss??"",draws:J.draw??""}}export function formatDate(q){if(!q||typeof q!=="number")return"";return new Date(q*1000).toISOString().slice(0,10)}export function openingName(q){if(typeof q!=="string"||!q.startsWith("http"))return"";const H=q.replace(/\/+$/,"").split("/").pop()||"";if(!H)return"";const F=H.match(/^([^.]+?)(?:-\d|\.\.\.|$)/);return(F?F[1]:H).replace(/-/g," ").trim()}export function mapGameRow(q,H){if(!isPlainObject(q))throw new K("Chess.com game archive entry is not an object");if(typeof q.url!=="string"||!/^https:\/\/www\.chess\.com\/game\/(?:live|daily)\/\d+(?:$|[/?#])/i.test(q.url))throw new K("Chess.com game archive entry is missing a stable game URL");const F=q?.white||{},J=q?.black||{};if(!isPlainObject(F)||!isPlainObject(J))throw new K("Chess.com game archive entry has malformed player objects");if(typeof F.username!=="string"||!F.username.trim()||typeof J.username!=="string"||!J.username.trim())throw new K("Chess.com game archive entry is missing stable player identities");const V=String(H||"").toLowerCase(),Q=String(F.username||"").toLowerCase()===V,T=String(J.username||"").toLowerCase()===V;if(!Q&&!T)throw new K("Chess.com game archive entry does not include the requested player");const Z=Q?F:J,$=Q?J:F,z=q?.eco||"";return{date:formatDate(q?.end_time),time_class:q?.time_class||"",rated:q?.rated===!0,my_color:Q?"white":"black",my_rating:Z?.rating??"",my_result:Z?.result||"",opponent:$?.username||"",opponent_rating:$?.rating??"",accuracy_white:typeof q?.accuracies?.white==="number"?q.accuracies.white:"",accuracy_black:typeof q?.accuracies?.black==="number"?q.accuracies.black:"",eco:z,opening_name:openingName(z),url:q?.url||""}}export const __test__={validateUsername,parseGameUrl,isPlainObject,isOptionalPlainObject:Y,chessApi,summarizeStats,formatDate,mapGameRow,openingName};
|
package/clis/claude/ask.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{cli as v,Strategy as x}from"@jackwener/opencli/registry";import{ArgumentError as f,CommandExecutionError as Y,EmptyResultError as j}from"@jackwener/opencli/errors";import{CLAUDE_DOMAIN as F,CLAUDE_URL as L,COMPOSER_SELECTOR as T,MESSAGE_SELECTOR as b,ensureOnClaude as O,selectModel as A,setAdaptiveThinking as S,sendMessage as y,sendWithFile as M,getBubbleCount as D,waitForResponse as P,parseBoolFlag as U,withRetry as K,ensureClaudeComposer as h,requireNonEmptyPrompt as C,requirePositiveInt as c}from"./utils.js";export const askCommand=v({site:"claude",name:"ask",access:"write",description:"Send a prompt to Claude and get the response",domain:F,strategy:x.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"prompt",positional:!0,required:!0,help:"Prompt to send"},{name:"timeout",type:"int",default:120,help:"Max seconds to wait for response"},{name:"new",type:"boolean",default:!1,help:"Start a new chat before sending"},{name:"model",default:"sonnet",choices:["sonnet","opus","haiku"],help:"Model to use: sonnet, opus, or haiku"},{name:"think",type:"boolean",default:!1,help:"Enable Adaptive thinking"},{name:"file",help:"Attach a file (image, PDF, text) with the prompt"}],columns:["response"],func:async(z,H)=>{const V=C(H.prompt,"claude ask"),Z=c(Number(H.timeout??120),"claude ask --timeout",'Example:
|
|
1
|
+
import{cli as v,Strategy as x}from"@jackwener/opencli/registry";import{ArgumentError as f,CommandExecutionError as Y,EmptyResultError as j}from"@jackwener/opencli/errors";import{CLAUDE_DOMAIN as F,CLAUDE_URL as L,COMPOSER_SELECTOR as T,MESSAGE_SELECTOR as b,ensureOnClaude as O,selectModel as A,setAdaptiveThinking as S,sendMessage as y,sendWithFile as M,getBubbleCount as D,waitForResponse as P,parseBoolFlag as U,withRetry as K,ensureClaudeComposer as h,requireNonEmptyPrompt as C,requirePositiveInt as c}from"./utils.js";export const askCommand=v({site:"claude",name:"ask",access:"write",description:"Send a prompt to Claude and get the response",domain:F,strategy:x.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"prompt",positional:!0,required:!0,help:"Prompt to send"},{name:"timeout",type:"int",default:120,help:"Max seconds to wait for response"},{name:"new",type:"boolean",default:!1,help:"Start a new chat before sending"},{name:"model",default:"sonnet",choices:["sonnet","opus","haiku"],help:"Model to use: sonnet, opus, or haiku"},{name:"think",type:"boolean",default:!1,help:"Enable Adaptive thinking"},{name:"file",help:"Attach a file (image, PDF, text) with the prompt"}],columns:["response"],func:async(z,H)=>{const V=C(H.prompt,"claude ask"),Z=c(Number(H.timeout??120),"claude ask --timeout",'Example: ppcli claude ask "hello" --timeout 120'),$=Z*1000,G=U(H.think);if(U(H.new)){await z.goto(L);try{await z.wait({selector:T,timeout:8})}catch{}}else if(await O(z)){await z.evaluate(`(() => {
|
|
2
2
|
var link = document.querySelector('a[href*="/chat/"]');
|
|
3
3
|
if (link) link.click();
|
|
4
4
|
})()`);try{await z.wait({selector:b,timeout:5})}catch{}}await K(()=>h(z,"Claude ask requires a visible composer on the current page."));const W=(await z.evaluate("window.location.href")||"").includes("/chat/"),B=H.__opencliOptionSources?.model==="cli",X=H.model||"sonnet";if(W&&B)throw new f(`Cannot switch to ${X} model inside an existing conversation.`,"Re-run with --new to start a fresh chat before selecting a model.");if(!W){const Q=await K(()=>A(z,X));if(!Q?.ok){if(Q?.upgrade)throw new f(`${X} model requires a paid Claude plan.`,"Pick --model sonnet or --model haiku, or upgrade your account.");throw new Y(`Could not switch to ${X} model`)}}if(!(await K(()=>S(z,G)))?.ok&&G)throw new Y("Could not enable Adaptive thinking");if(H.file){const Q=await K(()=>D(z));try{const J=await M(z,H.file,V);if(J&&!J.ok)throw new Y(J.reason||"Failed to attach file")}catch(J){if(!String(J?.message||J).includes("Promise was collected"))throw J}const N=await P(z,Q,V,$);if(!N)throw new j("claude ask",`No Claude response appeared within ${Z}s. Re-run with a higher --timeout if the model is still generating.`);return[{response:N}]}const _=await K(()=>D(z)),q=await K(()=>y(z,V));if(!q?.ok)throw new Y(q?.reason||"Failed to send message");const I=await P(z,_,V,$);if(!I)throw new j("claude ask",`No Claude response appeared within ${Z}s. Re-run with a higher --timeout if the model is still generating.`);return[{response:I}]}});
|
package/clis/claude/history.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as o,Strategy as r}from"@jackwener/opencli/registry";import{EmptyResultError as n}from"@jackwener/opencli/errors";import{CLAUDE_DOMAIN as a,getConversationList as l,ensureClaudeLogin as c,requirePositiveInt as m}from"./utils.js";export const historyCommand=o({site:"claude",name:"history",access:"read",description:"List conversation history from Claude /recents",domain:a,strategy:r.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"limit",type:"int",default:20,help:"Max conversations to show"}],columns:["Index","Id","Title","Url"],func:async(e,i)=>{const s=m(Number(i.limit??20),"claude history --limit","Example:
|
|
1
|
+
import{cli as o,Strategy as r}from"@jackwener/opencli/registry";import{EmptyResultError as n}from"@jackwener/opencli/errors";import{CLAUDE_DOMAIN as a,getConversationList as l,ensureClaudeLogin as c,requirePositiveInt as m}from"./utils.js";export const historyCommand=o({site:"claude",name:"history",access:"read",description:"List conversation history from Claude /recents",domain:a,strategy:r.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"limit",type:"int",default:20,help:"Max conversations to show"}],columns:["Index","Id","Title","Url"],func:async(e,i)=>{const s=m(Number(i.limit??20),"claude history --limit","Example: ppcli claude history --limit 20"),t=await l(e);await c(e,"Claude history requires a logged-in Claude session.");if(t.length===0)throw new n("claude history","No Claude conversation history was visible on /recents.");return t.slice(0,s)}});
|
package/clis/claude/utils.js
CHANGED
|
@@ -7,7 +7,7 @@ import{ArgumentError as Y,AuthRequiredError as F,CommandExecutionError as W}from
|
|
|
7
7
|
hasComposer: !!composer,
|
|
8
8
|
isLoggedIn: !!userMenu,
|
|
9
9
|
};
|
|
10
|
-
})()`)}export async function ensureClaudeLogin(z,B="Claude requires a logged-in browser session."){const H=await getPageState(z);if(!H.isLoggedIn)throw new F(CLAUDE_DOMAIN,B);return H}export async function ensureClaudeComposer(z,B="Claude composer is not available on the current page."){const H=await ensureClaudeLogin(z,B);if(!H.hasComposer)throw new W(B);return H}export function requireNonEmptyPrompt(z,B){const H=String(z??"").trim();if(!H)throw new Y(`${B} prompt cannot be empty`,`Example:
|
|
10
|
+
})()`)}export async function ensureClaudeLogin(z,B="Claude requires a logged-in browser session."){const H=await getPageState(z);if(!H.isLoggedIn)throw new F(CLAUDE_DOMAIN,B);return H}export async function ensureClaudeComposer(z,B="Claude composer is not available on the current page."){const H=await ensureClaudeLogin(z,B);if(!H.hasComposer)throw new W(B);return H}export function requireNonEmptyPrompt(z,B){const H=String(z??"").trim();if(!H)throw new Y(`${B} prompt cannot be empty`,`Example: ppcli ${B} "hello"`);return H}export function requirePositiveInt(z,B,H){if(!Number.isInteger(z)||z<1)throw new Y(`${B} must be a positive integer`,H);return z}export function requireConversationId(z){const B=String(z??"").trim();if(!B)throw new Y("claude detail requires a conversation id","Example: ppcli claude detail 123e4567-e89b-12d3-a456-426614174000");return B}export async function getVisibleMessages(z){const B=await z.evaluate(`(() => {
|
|
11
11
|
var nodes = document.querySelectorAll('[data-testid="user-message"], ${MESSAGE_SELECTOR}');
|
|
12
12
|
var rows = [];
|
|
13
13
|
Array.from(nodes).forEach(function(el) {
|
|
@@ -25,4 +25,4 @@ import{cli as o,Strategy as r}from"@jackwener/opencli/registry";export const ext
|
|
|
25
25
|
|
|
26
26
|
return results;
|
|
27
27
|
})()
|
|
28
|
-
`);if(e.length===0)return[{File:"No diffs found",Diff:'Try running
|
|
28
|
+
`);if(e.length===0)return[{File:"No diffs found",Diff:'Try running ppcli codex send "/review" first'}];return e}});
|
package/clis/coingecko/coin.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as y,Strategy as k}from"@jackwener/opencli/registry";import{ArgumentError as u,CommandExecutionError as i,EmptyResultError as w}from"@jackwener/opencli/errors";y({site:"coingecko",name:"coin",access:"read",description:"Fetch a single cryptocurrency's market data by CoinGecko id (e.g. bitcoin, ethereum).",domain:"api.coingecko.com",strategy:k.PUBLIC,browser:!1,args:[{name:"id",positional:!0,required:!0,type:"string",help:"CoinGecko coin id (lowercase, e.g. bitcoin / ethereum / solana)."},{name:"currency",type:"string",default:"usd",help:"Quote currency (usd, cny, eur, jpy, ...)."}],columns:["id","symbol","name","rank","price","marketCap","volume24h","change24hPct","change7dPct","change30dPct","ath","athDate","atl","atlDate","circulatingSupply","totalSupply","maxSupply","genesisDate","homepage"],func:async(l)=>{const s=String(l.id??"").trim().toLowerCase();if(!s)throw new u("coingecko coin id cannot be empty","Example:
|
|
1
|
+
import{cli as y,Strategy as k}from"@jackwener/opencli/registry";import{ArgumentError as u,CommandExecutionError as i,EmptyResultError as w}from"@jackwener/opencli/errors";y({site:"coingecko",name:"coin",access:"read",description:"Fetch a single cryptocurrency's market data by CoinGecko id (e.g. bitcoin, ethereum).",domain:"api.coingecko.com",strategy:k.PUBLIC,browser:!1,args:[{name:"id",positional:!0,required:!0,type:"string",help:"CoinGecko coin id (lowercase, e.g. bitcoin / ethereum / solana)."},{name:"currency",type:"string",default:"usd",help:"Quote currency (usd, cny, eur, jpy, ...)."}],columns:["id","symbol","name","rank","price","marketCap","volume24h","change24hPct","change7dPct","change30dPct","ath","athDate","atl","atlDate","circulatingSupply","totalSupply","maxSupply","genesisDate","homepage"],func:async(l)=>{const s=String(l.id??"").trim().toLowerCase();if(!s)throw new u("coingecko coin id cannot be empty","Example: ppcli coingecko coin bitcoin");if(!/^[a-z0-9][a-z0-9-]*$/.test(s))throw new u(`coingecko coin id must look like a CoinGecko slug (got "${l.id}")`);const r=String(l.currency??"usd").trim().toLowerCase();if(!/^[a-z0-9-]{2,20}$/.test(r))throw new u(`coingecko currency must look like a currency slug (got "${l.currency}")`);const c=new URL(`https://api.coingecko.com/api/v3/coins/${s}`);c.searchParams.set("localization","false");c.searchParams.set("tickers","false");c.searchParams.set("market_data","true");c.searchParams.set("community_data","false");c.searchParams.set("developer_data","false");c.searchParams.set("sparkline","false");let o;try{o=await fetch(c,{headers:{"User-Agent":"Mozilla/5.0"}})}catch(t){throw new i(`coingecko coin request failed: ${t?.message||t}`)}if(o.status===404)throw new w("coingecko coin",`coingecko has no coin with id "${s}".`);if(o.status===429)throw new i("coingecko returned HTTP 429 (rate limited)","Free tier allows ~30 calls/min. Wait and retry.");if(!o.ok)throw new i(`coingecko coin failed: HTTP ${o.status}`);let a;try{a=await o.json()}catch(t){throw new i(`coingecko returned malformed JSON: ${t?.message||t}`)}if(a?.error)throw new i(`coingecko returned error: ${a.error}`);const e=a.market_data||{},n=(t,h)=>t&&t[h]!=null?t[h]:null,m=(t)=>t?String(t).slice(0,10):"",p=n(e.current_price,r),g=n(e.market_cap,r),d=n(e.total_volume,r);if(p==null&&g==null&&d==null)throw new i(`coingecko returned no market data for currency "${r}"`,"Use a CoinGecko-supported quote currency such as usd, cny, eur, or jpy.");return[{id:a.id||s,symbol:String(a.symbol||"").toUpperCase(),name:a.name||"",rank:a.market_cap_rank??null,price:p,marketCap:g,volume24h:d,change24hPct:e.price_change_percentage_24h??null,change7dPct:e.price_change_percentage_7d??null,change30dPct:e.price_change_percentage_30d??null,ath:n(e.ath,r),athDate:m(n(e.ath_date,r)),atl:n(e.atl,r),atlDate:m(n(e.atl_date,r)),circulatingSupply:e.circulating_supply??null,totalSupply:e.total_supply??null,maxSupply:e.max_supply??null,genesisDate:a.genesis_date||"",homepage:Array.isArray(a.links?.homepage)?a.links.homepage.find(Boolean)||"":""}]}});
|
package/clis/crates/utils.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ArgumentError as H,CommandExecutionError as J,EmptyResultError as K}from"@jackwener/opencli/errors";export const CRATES_BASE="https://crates.io";const L="opencli-crates-adapter (+https://github.com/jackwener/
|
|
1
|
+
import{ArgumentError as H,CommandExecutionError as J,EmptyResultError as K}from"@jackwener/opencli/errors";export const CRATES_BASE="https://crates.io";const L="opencli-crates-adapter (+https://github.com/jackwener/OpenCLI)",O=/^[A-Za-z][A-Za-z0-9_-]{0,63}$/;export function requireString(D,z){const B=String(D??"").trim();if(!B)throw new H(`crates ${z} cannot be empty`);return B}export function requireCrateName(D){const z=String(D??"").trim();if(!z)throw new H('crates crate name is required (e.g. "serde", "tokio")');if(!O.test(z))throw new H(`crates crate name "${D}" is not a valid crates.io name`,'Names start with an ASCII letter, then 0-63 chars of letters / digits / "_-".');return z}export function requireBoundedInt(D,z,B,G="limit"){const F=D??z,I=typeof F==="number"?F:Number(F);if(!Number.isInteger(I)||I<=0)throw new H(`crates ${G} must be a positive integer`);if(I>B)throw new H(`crates ${G} must be <= ${B}`);return I}export async function cratesFetch(D,z){let B;try{B=await fetch(D,{headers:{"user-agent":L,accept:"application/json"}})}catch(F){throw new J(`${z} request failed: ${F?.message??F}`,"Check that crates.io is reachable from this network.")}if(B.status===404)throw new K(z,`crates.io returned 404 for ${D}.`);if(B.status===429)throw new J(`${z} returned HTTP 429 (rate limited)`,"crates.io rate-limits unauthenticated traffic; wait a few seconds and retry.");if(!B.ok)throw new J(`${z} returned HTTP ${B.status}`);let G;try{G=await B.json()}catch(F){throw new J(`${z} returned malformed JSON: ${F?.message??F}`)}return G}
|
package/clis/csdn/stats.js
CHANGED
|
@@ -68,4 +68,4 @@ import{cli as $,Strategy as K}from"@jackwener/opencli/registry";import{EmptyResu
|
|
|
68
68
|
}
|
|
69
69
|
return false;
|
|
70
70
|
}
|
|
71
|
-
`;function D(q){if(q&&typeof q==="object"&&!Array.isArray(q)&&"session"in q&&"data"in q)return q.data;return q}$({site:"csdn",name:"stats",access:"read",description:"CSDN 文章数据(每篇阅读/点赞/评论/收藏等全量运营指标)",domain:"mp.csdn.net",strategy:K.COOKIE,browser:!0,args:[{name:"limit",type:"number",default:20,help:"返回条数"}],columns:["id","type","title","url","published_at","views","likes","comments","collects","shares","status","badges","edit_url"],func:async(q,Z)=>{const B=Number(Z.limit)||20;await q.goto(P);const x=D(await q.evaluate(h));if(x&&/passport\.csdn\.net/.test(String(x.href||"")))throw Error("未登录 CSDN:页面被重定向到登录页。请先在 Chrome 中登录 mp.csdn.net(可用
|
|
71
|
+
`;function D(q){if(q&&typeof q==="object"&&!Array.isArray(q)&&"session"in q&&"data"in q)return q.data;return q}$({site:"csdn",name:"stats",access:"read",description:"CSDN 文章数据(每篇阅读/点赞/评论/收藏等全量运营指标)",domain:"mp.csdn.net",strategy:K.COOKIE,browser:!0,args:[{name:"limit",type:"number",default:20,help:"返回条数"}],columns:["id","type","title","url","published_at","views","likes","comments","collects","shares","status","badges","edit_url"],func:async(q,Z)=>{const B=Number(Z.limit)||20;await q.goto(P);const x=D(await q.evaluate(h));if(x&&/passport\.csdn\.net/.test(String(x.href||"")))throw Error("未登录 CSDN:页面被重定向到登录页。请先在 Chrome 中登录 mp.csdn.net(可用 ppcli csdn login)。");const F=new Set,v=[];for(let f=1;f<=U&&v.length<B;f++){const H=D(await q.evaluate(b));let Q=0;for(const z of Array.isArray(H)?H:[]){const Y=z.id||`${z.title}@@${z.published_at}`;if(F.has(Y))continue;F.add(Y);v.push(z);Q++}if(v.length>=B)break;const V=D(await q.evaluate(j));if(!V)break;await q.wait(2);if(Q===0&&!V)break}if(v.length===0){if(String(x?.bodyHint||"").includes("登录")||!x?.hasRows)throw Error("未能读取 CSDN 文章列表:可能未登录或页面未加载出内容。请确认已在 Chrome 中登录 mp.csdn.net 后重试。");throw new O("csdn stats","内容管理页没有任何文章。")}return v.slice(0,B).map((f)=>({id:f.id,type:"文章",title:f.title,url:f.url,published_at:f.published_at,views:f.views,likes:f.likes,comments:f.comments,collects:f.collects,shares:"",status:f.status,badges:f.badges,edit_url:f.edit_url}))}});
|
package/clis/dblp/utils.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ArgumentError as j,CommandExecutionError as $,EmptyResultError as N}from"@jackwener/opencli/errors";export const DBLP_ORIGIN="https://dblp.org";const O=/^[a-z]+(?:\/[A-Za-z0-9_.-]+)+$/;async function q(Q,J,S){let U;try{U=await fetch(Q,{headers:{accept:S,"user-agent":"opencli-dblp/1.0 (+https://github.com/jackwener/
|
|
1
|
+
import{ArgumentError as j,CommandExecutionError as $,EmptyResultError as N}from"@jackwener/opencli/errors";export const DBLP_ORIGIN="https://dblp.org";const O=/^[a-z]+(?:\/[A-Za-z0-9_.-]+)+$/;async function q(Q,J,S){let U;try{U=await fetch(Q,{headers:{accept:S,"user-agent":"opencli-dblp/1.0 (+https://github.com/jackwener/OpenCLI)"}})}catch(W){throw new $(`${J} request failed: ${W?.message??W}`,"Check that dblp.org is reachable from this network.")}if(!U.ok){if(U.status===429)throw new $(`${J} returned HTTP 429 (rate limited)`,"dblp throttles clients that fetch too aggressively. Wait a few seconds and retry, or lower --limit.");if(U.status===404)throw new N(J,"dblp returned 404 — the requested record may not exist.");throw new $(`${J} returned HTTP ${U.status}`,"Inspect the response in a browser at the same URL for more context.")}return U}export async function dblpFetchJson(Q,J){const S=await q(`${DBLP_ORIGIN}${Q}`,J,"application/json");let U;try{U=await S.json()}catch(Z){throw new $(`${J} returned malformed JSON: ${Z?.message??Z}`)}const W=String(U?.result?.status?.["@code"]??"").trim();if(!W)throw new $(`${J} returned JSON without result.status.@code`,"dblp changed its JSON envelope or returned a partial error payload; inspect the raw response in a browser.");if(W!=="200"){const Z=String(U?.result?.status?.text??"").trim();throw new $(`${J} returned API status ${W}${Z?` (${Z})`:""}`,"dblp accepted the HTTP request but reported an API-level failure. Retry later or inspect the same query in a browser.")}return U}export async function dblpFetchXml(Q,J){return(await q(`${DBLP_ORIGIN}${Q}`,J,"application/xml")).text()}export function coerceInt(Q){if(Q===void 0||Q===null||Q==="")return NaN;const J=typeof Q==="number"?Q:Number(Q);return Number.isFinite(J)&&Number.isInteger(J)?J:NaN}export function requireBoundedInt(Q,J,S,U="limit"){const Z=coerceInt(Q??J);if(!Number.isInteger(Z)||Z<=0)throw new j(`dblp ${U} must be a positive integer`);if(Z>S)throw new j(`dblp ${U} must be <= ${S}`);return Z}export function requireQuery(Q,J="query"){const S=String(Q??"").trim();if(!S)throw new j(`dblp ${J} cannot be empty`);return S}export function requireRecordKey(Q){const J=String(Q??"").trim();if(!J)throw new j("dblp paper key is required");if(!O.test(J))throw new j(`dblp paper key "${Q}" is not a valid record key`,'Expected something like "conf/nips/VaswaniSPUJGKP17" — copy the `key` column from `dblp search`.');return J}export function decodeXmlEntities(Q){if(!Q)return"";return String(Q).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/'/g,"'").replace(/"/g,'"').replace(/&#x([0-9a-fA-F]+);/g,(J,S)=>String.fromCodePoint(parseInt(S,16))).replace(/&#(\d+);/g,(J,S)=>String.fromCodePoint(parseInt(S,10)))}function M(Q){return String(Q||"").replace(/\s+\d{4,}$/,"").trim()}export function normalizeAuthors(Q){if(!Q)return[];const J=Q?.author;if(!J)return[];return(Array.isArray(J)?J:[J]).map((U)=>{if(U&&typeof U==="object")return M(U.text||U["#text"]||"");return M(String(U))}).filter(Boolean)}export function searchHitToRow(Q,J){const S=Q?.info??{},U=normalizeAuthors(S.authors),W=String(S.key??"").trim();return{rank:J,key:W,title:K(decodeXmlEntities(S.title??"")).trim(),authors:U.join(", "),venue:decodeXmlEntities(S.venue??""),year:String(S.year??"").trim(),type:_(S.type),doi:String(S.doi??"").trim(),url:String(S.ee??S.url??"").trim()}}function K(Q){return String(Q||"").replace(/\.\s*$/,"")}function _(Q){if(!Q)return"";const J=String(Q);if(/Conference and Workshop/i.test(J))return"conf";if(/Journal Articles/i.test(J))return"journal";if(/Books and Theses/i.test(J))return"book";if(/Editorship/i.test(J))return"editorship";if(/Reference Works/i.test(J))return"reference";if(/Informal/i.test(J))return"preprint";return J.toLowerCase().split(/\s+/)[0]}export function extractFirst(Q,J){const S=String(Q||"").match(new RegExp(`<${J}\\b[^>]*>([\\s\\S]*?)<\\/${J}>`));return S?S[1]:""}export function extractAll(Q,J){const S=[],U=new RegExp(`<${J}\\b[^>]*>([\\s\\S]*?)<\\/${J}>`,"g");let W;while((W=U.exec(String(Q||"")))!==null)S.push(W[1]);return S}export function extractOpenAccessLink(Q){const J=String(Q||"").match(/<ee\b[^>]*type=["']oa["'][^>]*>([\s\S]*?)<\/ee>/);if(J)return J[1].trim();const S=String(Q||"").match(/<ee\b[^>]*>([\s\S]*?)<\/ee>/);return S?S[1].trim():""}export function extractRecordKey(Q){const J=String(Q||"").match(/<(?:article|inproceedings|incollection|proceedings|book|phdthesis|mastersthesis)\b[^>]*\bkey="([^"]+)"/);return J?J[1]:""}export function extractRecordType(Q){const J=String(Q||"").match(/<(article|inproceedings|incollection|proceedings|book|phdthesis|mastersthesis)\b/);if(!J)return"";switch(J[1]){case"inproceedings":return"conf";case"article":return"journal";case"incollection":return"incollection";case"proceedings":return"editorship";case"book":return"book";case"phdthesis":return"phdthesis";case"mastersthesis":return"mastersthesis";default:return J[1]}}export function recordXmlToRow(Q){const J=extractRecordKey(Q),S=extractRecordType(Q),U=K(decodeXmlEntities(extractFirst(Q,"title"))),W=extractAll(Q,"author").map((z)=>M(decodeXmlEntities(z))),Z=decodeXmlEntities(extractFirst(Q,"year")),B=decodeXmlEntities(extractFirst(Q,"pages")),G=S==="conf"?decodeXmlEntities(extractFirst(Q,"booktitle")):decodeXmlEntities(extractFirst(Q,"journal")),H=(()=>{const z=String(Q||"").match(/<ee\b[^>]*>([^<]*?(?:doi\.org\/|10\.[^"<]+))<\/ee>/i);if(!z)return"";const Y=z[1].replace(/^https?:\/\/(?:dx\.)?doi\.org\//i,"");return Y.startsWith("10.")?Y:""})();return{key:J,type:S,title:U,authors:W.join(", "),venue:G,year:Z,pages:B,doi:H,open_access_url:extractOpenAccessLink(Q),dblp_url:J?`${DBLP_ORIGIN}/rec/${J}.html`:""}}export const SEARCH_COLUMNS=["rank","key","title","authors","venue","year","type","doi","url"],PAPER_COLUMNS=["key","type","title","authors","venue","year","pages","doi","open_access_url","dblp_url"];
|
package/clis/defillama/utils.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ArgumentError as H,CommandExecutionError as I,EmptyResultError as K}from"@jackwener/opencli/errors";export const LLAMA_BASE="https://api.llama.fi";const M="opencli-defillama-adapter (+https://github.com/jackwener/
|
|
1
|
+
import{ArgumentError as H,CommandExecutionError as I,EmptyResultError as K}from"@jackwener/opencli/errors";export const LLAMA_BASE="https://api.llama.fi";const M="opencli-defillama-adapter (+https://github.com/jackwener/OpenCLI)",N=/^[a-z0-9][a-z0-9._-]{0,99}$/;export function requireString(B,q){const z=String(B??"").trim();if(!z)throw new H(`defillama ${q} cannot be empty`);return z}export function requireSlug(B,q="slug"){const z=String(B??"").trim();if(!z)throw new H(`defillama ${q} is required (e.g. "aave", "lido")`,"Use the protocol slug as it appears on https://defillama.com/protocol/<slug>.");if(!N.test(z))throw new H(`defillama ${q} "${B}" is not a valid DefiLlama slug`,'Slugs are lowercase ASCII (letters / digits / "._-"), e.g. "aave", "lido", "pancakeswap-amm".');return z}export function requireBoundedInt(B,q,z,F="limit"){const D=B??q,J=typeof D==="number"?D:Number(D);if(!Number.isInteger(J)||J<=0)throw new H(`defillama ${F} must be a positive integer`);if(J>z)throw new H(`defillama ${F} must be <= ${z}`);return J}export async function llamaFetch(B,q){let z;try{z=await fetch(B,{headers:{"user-agent":M,accept:"application/json"}})}catch(D){throw new I(`${q} request failed: ${D?.message??D}`,"Check that api.llama.fi is reachable from this network.")}if(z.status===404)throw new K(q,`DefiLlama returned 404 for ${B}.`);if(z.status===400){const D=await z.text().catch(()=>"");if(/not\s*found/i.test(D))throw new K(q,`DefiLlama: ${D.trim()||"not found"}.`);throw new I(`${q} returned HTTP 400: ${D.slice(0,200)}`)}if(z.status===429)throw new I(`${q} returned HTTP 429 (rate limited)`,"DefiLlama throttles unauthenticated traffic; wait a few seconds and retry.");if(!z.ok)throw new I(`${q} returned HTTP ${z.status}`);let F;try{F=await z.json()}catch(D){throw new I(`${q} returned malformed JSON: ${D?.message??D}`)}return F}export function unixToDate(B){if(B==null)return null;const q=typeof B==="number"?B:Number(B);if(!Number.isFinite(q)||q<=0)return null;const z=q>1000000000000?q:q*1000,F=new Date(z);if(Number.isNaN(F.getTime()))return null;return F.toISOString().slice(0,10)}
|
package/clis/devto/publish.js
CHANGED
|
@@ -14,4 +14,4 @@ import*as V from"node:fs";import{cli as q,Strategy as B}from"@jackwener/opencli/
|
|
|
14
14
|
if (!r.ok) return { kind: 'http', status: r.status, detail: text.slice(0, 300) };
|
|
15
15
|
return { kind: 'ok', data: data };
|
|
16
16
|
} catch (e) { return { kind: 'exception', detail: String(e && e.message || e) }; }
|
|
17
|
-
})()`);if(I?.kind==="auth")throw new U("dev.to",`Not logged in (${I.detail}). Run \`
|
|
17
|
+
})()`);if(I?.kind==="auth")throw new U("dev.to",`Not logged in (${I.detail}). Run \`ppcli devto login\`.`);if(I?.kind==="http")throw new O(`dev.to publish failed: HTTP ${I.status} ${I.detail}`);if(I?.kind==="exception")throw new O(`dev.to publish error: ${I.detail}`);if(I?.kind!=="ok"||!I.data)throw new O(`Unexpected dev.to response: ${JSON.stringify(I)}`);const J=I.data,Q=!!P.published;let N="";if(J.current_state_path)try{N=new URL(J.current_state_path,"https://dev.to").href}catch(G){N=String(J.current_state_path)}else if(J.url)N=String(J.url);return[{status:F?"updated":"created",id:J.id??"",url:Q?N:"",published:Q,slug:J.slug??""}]}});
|
|
@@ -223,4 +223,4 @@ import{ArgumentError as o,CommandExecutionError as u}from"@jackwener/opencli/err
|
|
|
223
223
|
has_header: !!document.querySelector('[class*="title"], [aria-label*="Channel"], [aria-label*="频道"]')
|
|
224
224
|
};
|
|
225
225
|
})()
|
|
226
|
-
`}async function p(e){const t=f(await e.evaluate(buildRouteStateScript()),"Discord route state");if(t&&t.route)return t.route;const r=typeof t?.url==="string"?t.url:"";return parseDiscordChannelUrl(r)}export async function listDiscordChannels(e){return g(m(await e.evaluate(buildListChannelsScript()),"Discord channel list"),["Channel","guild_id","channel_id","url"],"Discord channel list")}export async function listDiscordServers(e){return g(m(await e.evaluate(buildListServersScript()),"Discord server list"),["Server","guild_id","url"],"Discord server list")}function x(e,t){if(!t)return!1;if(String(e.channel_id||"")===t)return!0;return normalizeDiscordName(e.Channel)===normalizeDiscordName(t)}function w(e,t){if(!t)return!0;return String(e.guild_id||"")===t||normalizeDiscordName(e.Server)===normalizeDiscordName(t)}export async function resolveDiscordChannelTarget(e,t={},r={}){const n=c(t.url);if(n){const s=parseDiscordChannelUrl(n);if(!s||s.thread_id)throw new o("--url must be a Discord channel URL like https://discord.com/channels/<guild_id>/<channel_id>.");return s}const i=c(t.guild),a=c(t.channel);if(!i&&!a){if(r.required)throw new o("Pass --url or --guild/--channel to choose a Discord channel.");return null}if(!a)throw new o("Pass --channel with --guild, or use --url.");let l=i;if(i&&!isDiscordSnowflake(i)&&i!=="@me"){const s=(await listDiscordServers(e)).find((v)=>w(v,i));if(s?.guild_id)l=String(s.guild_id)}if(l&&isDiscordSnowflake(l)&&isDiscordSnowflake(a))return{guild_id:l,channel_id:a,url:buildDiscordChannelUrl({guildId:l,channelId:a})};const h=(await listDiscordChannels(e)).find((s)=>w(s,l)&&x(s,a));if(h)return{guild_id:String(h.guild_id),channel_id:String(h.channel_id),url:String(h.url||buildDiscordChannelUrl({guildId:h.guild_id,channelId:h.channel_id}))};if(!l&&isDiscordSnowflake(a)){const s=await p(e);if(s?.guild_id)return{guild_id:s.guild_id,channel_id:a,url:buildDiscordChannelUrl({guildId:s.guild_id,channelId:a})}}throw new o(`Could not resolve Discord channel "${a}".`,'Use "
|
|
226
|
+
`}async function p(e){const t=f(await e.evaluate(buildRouteStateScript()),"Discord route state");if(t&&t.route)return t.route;const r=typeof t?.url==="string"?t.url:"";return parseDiscordChannelUrl(r)}export async function listDiscordChannels(e){return g(m(await e.evaluate(buildListChannelsScript()),"Discord channel list"),["Channel","guild_id","channel_id","url"],"Discord channel list")}export async function listDiscordServers(e){return g(m(await e.evaluate(buildListServersScript()),"Discord server list"),["Server","guild_id","url"],"Discord server list")}function x(e,t){if(!t)return!1;if(String(e.channel_id||"")===t)return!0;return normalizeDiscordName(e.Channel)===normalizeDiscordName(t)}function w(e,t){if(!t)return!0;return String(e.guild_id||"")===t||normalizeDiscordName(e.Server)===normalizeDiscordName(t)}export async function resolveDiscordChannelTarget(e,t={},r={}){const n=c(t.url);if(n){const s=parseDiscordChannelUrl(n);if(!s||s.thread_id)throw new o("--url must be a Discord channel URL like https://discord.com/channels/<guild_id>/<channel_id>.");return s}const i=c(t.guild),a=c(t.channel);if(!i&&!a){if(r.required)throw new o("Pass --url or --guild/--channel to choose a Discord channel.");return null}if(!a)throw new o("Pass --channel with --guild, or use --url.");let l=i;if(i&&!isDiscordSnowflake(i)&&i!=="@me"){const s=(await listDiscordServers(e)).find((v)=>w(v,i));if(s?.guild_id)l=String(s.guild_id)}if(l&&isDiscordSnowflake(l)&&isDiscordSnowflake(a))return{guild_id:l,channel_id:a,url:buildDiscordChannelUrl({guildId:l,channelId:a})};const h=(await listDiscordChannels(e)).find((s)=>w(s,l)&&x(s,a));if(h)return{guild_id:String(h.guild_id),channel_id:String(h.channel_id),url:String(h.url||buildDiscordChannelUrl({guildId:h.guild_id,channelId:h.channel_id}))};if(!l&&isDiscordSnowflake(a)){const s=await p(e);if(s?.guild_id)return{guild_id:s.guild_id,channel_id:a,url:buildDiscordChannelUrl({guildId:s.guild_id,channelId:a})}}throw new o(`Could not resolve Discord channel "${a}".`,'Use "ppcli discord-app channels -f json" and retry with --url or numeric --guild/--channel ids.')}export async function waitForDiscordRoute(e,t,r={}){const n=r.timeoutMs??8000,i=r.intervalSeconds??0.5,a=Date.now();let l=null;while(Date.now()-a<=n){l=f(await e.evaluate(buildRouteStateScript()),"Discord route state");const d=l?.route;if(d&&String(d.guild_id)===String(t.guild_id)&&String(d.channel_id)===String(t.channel_id)){if(!t.thread_id||String(d.thread_id||"")===String(t.thread_id))return l}await e.wait(i)}throw new u("Discord did not finish navigating to the requested channel.",`Last observed URL: ${l?.url||"unknown"}`)}export async function waitForDiscordContent(e,t="messages",r={}){const n=r.timeoutMs??5000,i=r.intervalSeconds??0.5,a=Date.now();let l=null;while(Date.now()-a<=n){l=f(await e.evaluate(buildRouteStateScript()),"Discord route state");if(t==="messages"&&l?.has_messages)return l;if(t==="threads"&&l?.has_threads)return l;await e.wait(i)}return l}export async function navigateToDiscordTarget(e,t,r={}){await e.goto(t.url,{waitUntil:"none",settleMs:r.settleMs??1000});const n=await waitForDiscordRoute(e,t,r);if(r.waitForContent==="messages"||r.waitForContent==="threads")return waitForDiscordContent(e,r.waitForContent,{timeoutMs:r.contentTimeoutMs??5000,intervalSeconds:r.intervalSeconds??0.5});return n}export async function maybeNavigateToDiscordChannel(e,t={},r={}){if(!hasDiscordChannelTarget(t))return null;const n=await resolveDiscordChannelTarget(e,t,{required:!0});await navigateToDiscordTarget(e,n,r);return n}export async function readDiscordMessages(e,t){return m(await e.evaluate(buildReadMessagesScript(t)),"Discord message list")}export async function listDiscordThreads(e,t){return g(m(await e.evaluate(buildListThreadsScript(t)),"Discord thread list"),["Thread","guild_id","channel_id","thread_id","url"],"Discord thread list")}export function assertDiscordMessageRowsBelongToTarget(e,t,r="Discord read"){if(!t)return e;const n=String(t.thread_id||t.channel_id||"");if(!n)throw new u(`${r} target is missing a stable channel/thread id.`);for(const i of e){if(!i||typeof i!=="object"||Array.isArray(i))throw new u(`${r} returned malformed message rows.`);const a=i.channel_id==null||i.channel_id===""?"":String(i.channel_id);if(!a)throw new u(`${r} could not verify returned message target.`,`Expected channel/thread id ${n}, but the message row did not include channel_id.`);if(a&&a!==n)throw new u(`${r} returned messages from the wrong Discord target.`,`Expected channel/thread id ${n}, saw ${a}.`)}return e}export async function resolveDiscordThreadTarget(e,t={}){const r=c(t.url);if(r){const d=parseDiscordChannelUrl(r);if(!d||!d.thread_id)throw new o("--url must be a Discord thread/post URL like https://discord.com/channels/<guild_id>/<channel_id>/<thread_id>.");return d}const n=c(t.thread);if(!n)throw new o("Pass --thread <thread_id> or --url <thread_url>.");const i=parseDiscordChannelUrl(n);if(i?.thread_id)return i;const a=hasDiscordChannelTarget(t)?await resolveDiscordChannelTarget(e,t,{required:!0}):null;if(a)return{guild_id:a.guild_id,channel_id:a.channel_id,thread_id:n,url:buildDiscordChannelUrl({guildId:a.guild_id,channelId:a.channel_id,threadId:n})};const l=await p(e);if(l?.guild_id&&l?.channel_id)return{guild_id:l.guild_id,channel_id:l.channel_id,thread_id:n,url:buildDiscordChannelUrl({guildId:l.guild_id,channelId:l.channel_id,threadId:n})};throw new o(`Could not resolve Discord thread "${n}".`,"Open the parent forum/channel first, or pass --guild and --channel ids with --thread.")}
|
package/clis/dockerhub/utils.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ArgumentError as K,CommandExecutionError as N,EmptyResultError as P}from"@jackwener/opencli/errors";export const HUB_BASE="https://hub.docker.com/v2";const Q="opencli-dockerhub-adapter (+https://github.com/jackwener/
|
|
1
|
+
import{ArgumentError as K,CommandExecutionError as N,EmptyResultError as P}from"@jackwener/opencli/errors";export const HUB_BASE="https://hub.docker.com/v2";const Q="opencli-dockerhub-adapter (+https://github.com/jackwener/OpenCLI)",O=/^[a-z0-9][a-z0-9._-]*$/;export function requireString(I,F){const q=String(I??"").trim();if(!q)throw new K(`dockerhub ${F} cannot be empty`);return q}export function requireBoundedInt(I,F,q,J="limit"){const z=I??F,M=typeof z==="number"?z:Number(z);if(!Number.isInteger(M)||M<=0)throw new K(`dockerhub ${J} must be a positive integer`);if(M>q)throw new K(`dockerhub ${J} must be <= ${q}`);return M}export function parseImage(I){const F=String(I??"").trim().toLowerCase();if(!F)throw new K('dockerhub image name is required (e.g. "nginx", "library/nginx", "bitnami/redis")');const q=F.indexOf("/");let J,z;if(q>=0){J=F.slice(0,q);z=F.slice(q+1)}else{J="library";z=F}if(!O.test(J)||!O.test(z))throw new K(`dockerhub image "${I}" is not a valid repository slug`,'Use lowercase letters / digits / "._-", optionally prefixed with "<owner>/".');if(z.length<2||z.length>255)throw new K(`dockerhub image "${I}" name must be 2-255 chars`);return{owner:J,name:z}}export async function hubFetch(I,F){let q;try{q=await fetch(I,{headers:{"user-agent":Q,accept:"application/json"}})}catch(z){throw new N(`${F} request failed: ${z?.message??z}`,"Check that hub.docker.com is reachable from this network.")}if(q.status===404)throw new P(F,`Docker Hub returned 404 for ${I}.`);if(q.status===429)throw new N(`${F} returned HTTP 429 (rate limited)`,"Docker Hub throttles anonymous traffic; wait a few seconds and retry.");if(!q.ok)throw new N(`${F} returned HTTP ${q.status}`);let J;try{J=await q.json()}catch(z){throw new N(`${F} returned malformed JSON: ${z?.message??z}`)}return J}
|
package/clis/douban/utils.js
CHANGED
|
@@ -7,7 +7,7 @@ import{ArgumentError as V,CliError as W,EmptyResultError as K}from"@jackwener/op
|
|
|
7
7
|
const blocked = href.includes('sec.douban.com') || /登录跳转/.test(title) || /异常请求/.test(document.body?.innerText || '');
|
|
8
8
|
return { blocked, title, href };
|
|
9
9
|
})()
|
|
10
|
-
`))?.blocked)throw new W("AUTH_REQUIRED","Douban requires a logged-in browser session before these commands can load data.","Please sign in to douban.com in the browser that
|
|
10
|
+
`))?.blocked)throw new W("AUTH_REQUIRED","Douban requires a logged-in browser session before these commands can load data.","Please sign in to douban.com in the browser that ppcli reuses, then rerun the command.")}function s(j){const k=j instanceof Error?j.message:String(j||"");return/Detached while handling command|Debugger is not attached to the tab|Target closed|No tab with id/i.test(k)}async function J(j,k={}){const m=Math.max(1,k.attempts||2);let q;for(let v=0;v<m;v+=1)try{return await j()}catch(w){q=w;if(v>=m-1||!s(w))throw w}throw q}function y(j,k){const m=new URL(`https://search.douban.com/${encodeURIComponent(j)}/subject_search`);m.searchParams.set("search_text",String(k||""));if(String(j||"").trim()==="book")m.searchParams.set("cat","1001");return m.toString()}export function normalizeDoubanSubjectId(j){const k=String(j||"").trim();if(!/^\d+$/.test(k))throw new V(`Invalid Douban subject ID: ${j}`);return k}export function promoteDoubanPhotoUrl(j,k="l"){const m=String(j||"").trim();if(!m)return"";if(/^[a-z]+:/i.test(m)&&!/^https?:/i.test(m))return"";return m.replace(/\/view\/photo\/[^/]+\/public\//,`/view/photo/${k}/public/`)}export function resolveDoubanPhotoAssetUrl(j,k=""){for(const m of j){const q=String(m||"").trim();if(!q)continue;let v=q;try{v=k?new URL(q,k).toString():new URL(q).toString()}catch{v=q}if(/^https?:\/\//i.test(v))return v}return""}export function getDoubanPhotoExtension(j){const k=String(j||"").trim();if(!k)return".jpg";try{return new URL(k).pathname.match(/\.(jpe?g|png|gif|webp|avif|bmp)$/i)?.[0]||".jpg"}catch{const m=k.match(/\.(jpe?g|png|gif|webp|avif|bmp)(?:$|[?#])/i)?.[0];return m?m.replace(/[?#].*$/,""):".jpg"}}export function normalizeDoubanBookSubject(j){const k=$(j?.infoText),m=x([j?.title]),q=x([j?.subtitle,k["副标题"]]),v=x([j?.originalTitle,k["原作名"]]),w=M(x([k["作者"]])),z=M(x([k["译者"]])),B=x([k["出版社"],k["出品方"]]),F=x([k["出版年"]]),E=l(F),G=h(k["页数"]),N=x([k["装帧"]]),O=x([k["定价"]]),P=x([k["丛书"]]),H=x([k.ISBN]).replace(/[^\dxX]/g,""),Q=H.length===10?H:"",U=H.length===13?H:"";return{id:normalizeDoubanSubjectId(j?.id),type:"book",title:m,subtitle:q,originalTitle:v,authors:w,translators:z,publisher:B,publishDate:F,publishYear:E,pageCount:G,binding:N,price:O,series:P,isbn10:Q,isbn13:U,rating:b(j?.rating),ratingCount:f(j?.ratingCount),summary:A(j?.summary),cover:x([j?.cover]),url:x([j?.url])}}async function D(j,k){const m=normalizeDoubanSubjectId(k);return await J(async()=>{await j.goto(`https://movie.douban.com/subject/${m}/`,{waitUntil:"load",settleMs:1500});await C(j);await j.wait({selector:'span[property="v:itemreviewed"], #info',timeout:8}).catch(()=>{});return j.evaluate(`
|
|
11
11
|
(() => {
|
|
12
12
|
const id = ${JSON.stringify(m)};
|
|
13
13
|
const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import*as R from"node:crypto";import*as M from"node:fs";import*as P from"node:os";import*as U from"node:path";import{CommandExecutionError as _}from"@jackwener/opencli/errors";const PART_SIZE=5242880,RESUME_DIR=U.join(P.homedir(),".
|
|
1
|
+
import*as R from"node:crypto";import*as M from"node:fs";import*as P from"node:os";import*as U from"node:path";import{CommandExecutionError as _}from"@jackwener/opencli/errors";const PART_SIZE=5242880,RESUME_DIR=U.join(P.homedir(),".ppcli","douyin-resume");function getResumeFilePath(J){const G=R.createHash("sha256").update(J).digest("hex");return U.join(RESUME_DIR,`${G}.json`)}function loadResumeState(J,G){try{const Q=M.readFileSync(J,"utf8"),W=JSON.parse(Q);if(W.fileSize===G&&W.uploadId&&Array.isArray(W.parts))return W}catch{}return null}function saveResumeState(J,G){M.mkdirSync(U.dirname(J),{recursive:!0});M.writeFileSync(J,JSON.stringify(G,null,2),"utf8")}function deleteResumeState(J){try{M.unlinkSync(J)}catch{}}function v(J,G){return R.createHmac("sha256",J).update(G,"utf8").digest()}function I(J){const G=R.createHash("sha256");if(typeof J==="string")G.update(J,"utf8");else G.update(J);return G.digest("hex")}const S=new Uint32Array(256).map((J,G)=>{let Q=G;for(let W=0;W<8;W+=1)Q=Q&1?3988292384^Q>>>1:Q>>>1;return Q>>>0});function crc32Hex(J){let G=4294967295;for(const Q of J)G=S[(G^Q)&255]^G>>>8;return((G^4294967295)>>>0).toString(16).padStart(8,"0")}function gatewayBaseUrl(J){const G=new URL(J);return`https://${G.host}/upload/v1${G.pathname}`}function gatewayHeaders(J,G,Q=""){return{Authorization:J,"X-Storage-U":encodeURIComponent(Q),...G??{}}}function extractRegionFromHost(J){const G=J.match(/^tos-([^.]+)\./);if(G)return G[1];return"cn-north-1"}function computeAws4Headers(J){const{method:G,url:Q,credentials:W,service:Y,region:V,datetime:X}=J,K=X.slice(0,8),$=new URL(Q),D=$.pathname||"/",O=[...$.searchParams.entries()].sort(([q],[C])=>q.localeCompare(C)).map(([q,C])=>`${encodeURIComponent(q)}=${encodeURIComponent(C)}`).join("&"),Z=I(J.body),A={...J.headers,host:$.host,"x-amz-content-sha256":Z,"x-amz-date":X,"x-amz-security-token":W.session_token},w=Object.keys(A).sort((q,C)=>q.toLowerCase().localeCompare(C.toLowerCase())),T=w.map((q)=>`${q.toLowerCase()}:${A[q].trim()}`).join(`
|
|
2
2
|
`)+`
|
|
3
3
|
`,L=w.map((q)=>q.toLowerCase()).join(";"),E=[G.toUpperCase(),D,O,T,L,Z].join(`
|
|
4
4
|
`),z=`${K}/${V}/${Y}/aws4_request`,N=["AWS4-HMAC-SHA256",X,z,I(E)].join(`
|
package/clis/douyin/hashtag.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as w,Strategy as f}from"@jackwener/opencli/registry";import{browserFetch as u}from"./_shared/browser-fetch.js";import{ArgumentError as h,CommandExecutionError as c}from"@jackwener/opencli/errors";function l(e){return e!==null&&typeof e==="object"&&!Array.isArray(e)}function y(e,n,i){if(!l(e))throw new c(`douyin hashtag ${i}: API returned malformed payload`);const r=e[n];if(r===void 0||r===null)return[];if(!Array.isArray(r))throw new c(`douyin hashtag ${i}: API returned malformed "${n}"`);return r}function g(e){const n=e.action;if(n==="search"){if(!String(e.keyword??"").trim())throw new h("douyin hashtag search 需要 --keyword <关键词>","示例:
|
|
1
|
+
import{cli as w,Strategy as f}from"@jackwener/opencli/registry";import{browserFetch as u}from"./_shared/browser-fetch.js";import{ArgumentError as h,CommandExecutionError as c}from"@jackwener/opencli/errors";function l(e){return e!==null&&typeof e==="object"&&!Array.isArray(e)}function y(e,n,i){if(!l(e))throw new c(`douyin hashtag ${i}: API returned malformed payload`);const r=e[n];if(r===void 0||r===null)return[];if(!Array.isArray(r))throw new c(`douyin hashtag ${i}: API returned malformed "${n}"`);return r}function g(e){const n=e.action;if(n==="search"){if(!String(e.keyword??"").trim())throw new h("douyin hashtag search 需要 --keyword <关键词>","示例: ppcli douyin hashtag search --keyword 美食");return}if(n==="suggest"){if(!String(e.cover??"").trim())throw new h("douyin hashtag suggest 需要 --cover <cover_uri>","suggest 基于已上传的视频封面做 AI 推荐, 不是关键词搜索. 关键词搜索请用 `douyin hashtag search --keyword <词>`.")}}w({site:"douyin",name:"hashtag",access:"read",description:"话题搜索 / AI推荐 / 热点词",domain:"creator.douyin.com",strategy:f.COOKIE,args:[{name:"action",required:!0,positional:!0,choices:["search","suggest","hot"],help:"search=关键词搜索 (--keyword 必填), suggest=AI推荐 (--cover 必填), hot=热点词 (--keyword 可选)"},{name:"keyword",default:"",help:"搜索关键词. search 必填; hot 可选; suggest 不使用 (传 --cover)"},{name:"cover",default:"",help:"封面 URI (cover_uri). suggest 必填; 其它 action 不使用"},{name:"limit",type:"int",default:10}],columns:["name","id","view_count"],validateArgs:g,func:async(e,n)=>{g(n);const i=n.action;if(i==="search"){const r=String(n.keyword??"").trim(),d=`https://creator.douyin.com/aweme/v1/challenge/search/?keyword=${encodeURIComponent(r)}&count=${n.limit}&aid=1128`,s=await u(e,"GET",d),a=y(s,"challenge_list","search"),o=a.flatMap((m)=>{const t=m?.challenge_info;if(!l(t))return[];return[{name:t.cha_name,id:t.cid,view_count:t.view_count}]});if(a.length>0&&o.length===0)throw new c("douyin hashtag search: API returned challenges but none had stable challenge_info shape");return o}if(i==="suggest"){const r=String(n.cover??"").trim(),d=`https://creator.douyin.com/web/api/media/hashtag/rec/?cover_uri=${encodeURIComponent(r)}&aid=1128`,s=await u(e,"GET",d);return y(s,"hashtag_list","suggest").map((o)=>({name:o?.name??"",id:o?.id??"",view_count:o?.view_count??0}))}if(i==="hot"){const r=String(n.keyword??"").trim(),d=`https://creator.douyin.com/aweme/v1/hotspot/recommend/?${r?`keyword=${encodeURIComponent(r)}&`:""}aid=1128`,s=await u(e,"GET",d);if(!l(s))throw new c("douyin hashtag hot: API returned malformed payload");const{hotspot_list:a,all_sentences:o}=s;if(a!==void 0&&a!==null&&!Array.isArray(a))throw new c('douyin hashtag hot: API returned malformed "hotspot_list"');if(o!==void 0&&o!==null&&!Array.isArray(o))throw new c('douyin hashtag hot: API returned malformed "all_sentences"');return(Array.isArray(a)?a:Array.isArray(o)?o.map((t)=>({sentence:t?.word??"",hot_value:t?.hot_value,sentence_id:t?.sentence_id??""})):[]).slice(0,n.limit).map((t)=>({name:t?.sentence??"",id:t&&"sentence_id"in t?t.sentence_id:"",view_count:t?.hot_value??0}))}throw new h(`未知的 action: ${i}`)}});
|
|
@@ -49,7 +49,7 @@ import*as R from"node:fs";import*as G from"node:path";import{cli as P,Strategy a
|
|
|
49
49
|
}
|
|
50
50
|
return { ok: false, seen: btns.filter(isVisible).map((b) => norm(b.innerText || b.textContent)).filter(Boolean).slice(0, 12) };
|
|
51
51
|
})(${JSON.stringify(K)})
|
|
52
|
-
`)}P({site:"douyin",name:"publish-image",access:"write",description:"发布图文(图集)到抖音(UI 自动化,最多 35 张图;话题以 #词 写进正文)",domain:"creator.douyin.com",strategy:h.COOKIE,browser:!0,navigateBefore:!1,defaultWindowMode:"foreground",args:[{name:"content",required:!1,positional:!0,help:"正文(字面文本,不展开 @文件 引用;长正文用 --file)"},{name:"file",required:!1,help:"从本机文件读取正文(UTF-8),与位置参数 <content> 二选一"},{name:"title",required:!0,help:"作品标题(≤20字)"},{name:"images",required:!0,help:"图片路径,逗号分隔,最多35张 (jpg/png/bmp/webp/tif)"},{name:"topics",required:!1,help:"话题标签,逗号分隔,不含 # 号"},{name:"draft",type:"bool",default:!1,help:"暂存为草稿,不直接发布"}],columns:["status","detail"],func:async(q,J)=>{if(!q)throw Error("Browser page required");const K=String(J.title??"").trim(),V=J.file?String(J.file).trim():"";let z=String(J.content??"").trim();if(V){if(z)throw new $("正文位置参数和 --file 只能二选一");const Q=G.resolve(V);if(!R.existsSync(Q))throw new $(`--file 指向的文件不存在: ${Q}`);z=R.readFileSync(Q,"utf-8").trim();if(!z)throw new $(`--file 指向的文件内容为空: ${Q}`)}const Z=String(J.images??"").split(",").map((Q)=>Q.trim()).filter(Boolean),F=J.topics?String(J.topics).split(",").map((Q)=>Q.trim().replace(/^#/,"")).filter(Boolean):[],Y=Boolean(J.draft);if(!K)throw new $("--title 必填");if(K.length>S)throw new $(`标题 ${K.length} 字,需 ≤ ${S} 字`);if(!z)throw new $("缺少正文:给位置参数 <content>,或用 --file <本机文件路径>");T(z,{fileFlag:"--file"});if(z.length>M)throw new $(`正文 ${z.length} 字,需 ≤ ${M} 字`);if(Z.length===0)throw new $("至少提供 1 张图片(--images)");if(Z.length>w)throw new $(`图片过多: ${Z.length}(最多 ${w} 张)`);const O=n(Z),f=F.length?`${z} ${F.map((Q)=>`#${Q}`).join(" ")} `:z;await q.goto(m);let W="";for(let Q=0;Q<30;Q++){await q.wait({time:0.5});W=await q.evaluate("() => location.href");if(W.includes("creator.douyin.com/creator-micro/content"))break}if(!W.includes("creator.douyin.com")){await q.screenshot({path:"/tmp/douyin_image_redirect_debug.png"});throw new j(`被重定向离开创作者中心(当前 ${W})——登录态可能已过期。`+"用
|
|
52
|
+
`)}P({site:"douyin",name:"publish-image",access:"write",description:"发布图文(图集)到抖音(UI 自动化,最多 35 张图;话题以 #词 写进正文)",domain:"creator.douyin.com",strategy:h.COOKIE,browser:!0,navigateBefore:!1,defaultWindowMode:"foreground",args:[{name:"content",required:!1,positional:!0,help:"正文(字面文本,不展开 @文件 引用;长正文用 --file)"},{name:"file",required:!1,help:"从本机文件读取正文(UTF-8),与位置参数 <content> 二选一"},{name:"title",required:!0,help:"作品标题(≤20字)"},{name:"images",required:!0,help:"图片路径,逗号分隔,最多35张 (jpg/png/bmp/webp/tif)"},{name:"topics",required:!1,help:"话题标签,逗号分隔,不含 # 号"},{name:"draft",type:"bool",default:!1,help:"暂存为草稿,不直接发布"}],columns:["status","detail"],func:async(q,J)=>{if(!q)throw Error("Browser page required");const K=String(J.title??"").trim(),V=J.file?String(J.file).trim():"";let z=String(J.content??"").trim();if(V){if(z)throw new $("正文位置参数和 --file 只能二选一");const Q=G.resolve(V);if(!R.existsSync(Q))throw new $(`--file 指向的文件不存在: ${Q}`);z=R.readFileSync(Q,"utf-8").trim();if(!z)throw new $(`--file 指向的文件内容为空: ${Q}`)}const Z=String(J.images??"").split(",").map((Q)=>Q.trim()).filter(Boolean),F=J.topics?String(J.topics).split(",").map((Q)=>Q.trim().replace(/^#/,"")).filter(Boolean):[],Y=Boolean(J.draft);if(!K)throw new $("--title 必填");if(K.length>S)throw new $(`标题 ${K.length} 字,需 ≤ ${S} 字`);if(!z)throw new $("缺少正文:给位置参数 <content>,或用 --file <本机文件路径>");T(z,{fileFlag:"--file"});if(z.length>M)throw new $(`正文 ${z.length} 字,需 ≤ ${M} 字`);if(Z.length===0)throw new $("至少提供 1 张图片(--images)");if(Z.length>w)throw new $(`图片过多: ${Z.length}(最多 ${w} 张)`);const O=n(Z),f=F.length?`${z} ${F.map((Q)=>`#${Q}`).join(" ")} `:z;await q.goto(m);let W="";for(let Q=0;Q<30;Q++){await q.wait({time:0.5});W=await q.evaluate("() => location.href");if(W.includes("creator.douyin.com/creator-micro/content"))break}if(!W.includes("creator.douyin.com")){await q.screenshot({path:"/tmp/douyin_image_redirect_debug.png"});throw new j(`被重定向离开创作者中心(当前 ${W})——登录态可能已过期。`+"用 ppcli douyin login 重新登录。截图: /tmp/douyin_image_redirect_debug.png")}const k=await i(q,O);if(!k.ok){await q.screenshot({path:"/tmp/douyin_image_upload_debug.png"});throw new j(`图片上传失败: ${k.error??"unknown"}。截图: /tmp/douyin_image_upload_debug.png`)}if(!await p(q)){await q.screenshot({path:"/tmp/douyin_image_form_debug.png"});throw new j("上传后编辑页未就绪(标题输入框未出现),页面结构可能变化。截图: /tmp/douyin_image_form_debug.png")}await q.wait({time:E/1000});await s(q,K);await q.wait({time:0.4});await g(q,f);await q.wait({time:0.6});const I=await r(q,Y);if(!I?.ok){await q.screenshot({path:"/tmp/douyin_image_submit_debug.png"});throw new j(`找不到「${Y?"暂存离开":"发布"}」按钮。可见按钮: ${(I?.seen||[]).join(" | ")||"无"}。截图: /tmp/douyin_image_submit_debug.png`)}const d=Y?["暂存成功","保存成功","存入草稿"]:["发布成功","提交成功"];let B=W,X=!1,v="";for(let Q=0;Q<24;Q++){await q.wait({time:0.5});const H=await q.evaluate(`
|
|
53
53
|
((markers) => {
|
|
54
54
|
const url = location.href;
|
|
55
55
|
let toast = '';
|
package/clis/douyin/publish.js
CHANGED
|
@@ -11,4 +11,4 @@ import*as f from"node:fs";import*as M from"node:path";import{cli as a,Strategy a
|
|
|
11
11
|
`,t=k(await Z.evaluate(o),"抖音封面提交上传响应异常");h("抖音封面提交上传",t);j=N}if(!K.no_safety_check){const Q="https://creator.douyin.com/aweme/v1/post_assistant/fast_detect/pre_check",z={video_id:C,title:Y,desc:$},J=await n(Z,"POST",Q,{body:z});if(!J.ok)process.stderr.write(` 内容安全预检接口无响应,继续轮询检测结果。
|
|
12
12
|
`);const X="https://creator.douyin.com/aweme/v1/post_assistant/fast_detect/poll",L=Date.now()+30000;let P=!1,N=0;while(Date.now()<L){const T=await n(Z,"POST",X,{body:z});if(!T.ok){N+=1;if(!J.ok&&N>=3)break;await x(2000);continue}N=0;const W=T.value;if(W.status===0||W.has_done===!0&&W.detect_result?.reason_code===0&&(W.detect_list?.length??0)===0){P=!0;break}if(W.status===1)throw new G("内容安全检测不通过,请修改后重试","使用 --no_safety_check 跳过");await x(2000)}if(!P)if(!J.ok&&N>=3)process.stderr.write(` 内容安全预检持续无响应,跳过本地预检,交由 create_v2 后的平台审核。
|
|
13
13
|
`);else throw new G("内容安全检测超时(30s),请稍后重试","如确认要跳过本地预检,可使用 --no_safety_check;提交后仍会走抖音平台审核")}const d=LK($),y=[];let I=0;for(const Q of d){const z=$.indexOf(`#${Q}`,I);if(z===-1)continue;y.push({name:Q,id:0,start:z,end:z+Q.length+1});I=z+Q.length+1}const v=$?`${Y} ${$}`:Y,E=$?Y.length+1:0,g=JK(v,y.map((Q)=>({...Q,start:Q.start+E,end:Q.end+E}))),p={item:{common:{text:v,caption:$,item_title:Y,activity:JSON.stringify(K.activity?[K.activity]:[]),text_extra:JSON.stringify(g),challenges:"[]",mentions:"[]",hashtag_source:"",hot_sentence:K.hotspot||"",interaction_stickers:"[]",visibility_type:m,download:K.allow_download?1:0,timing:V,creation_id:zK(),media_type:4,video_id:C,music_source:0,music_id:null,...K.poi_id?{poi_id:K.poi_id,poi_name:K.poi_name}:{}},cover:{poster:j,custom_cover_image_height:b,custom_cover_image_width:_,poster_delay:0,cover_tools_info:GK,cover_tools_extend_info:"{}"},mix:K.collection?{mix_id:K.collection,mix_order:0}:{},chapter:{chapter:JSON.stringify({chapter_abstract:"",chapter_details:[],chapter_type:0})},anchor:{},sync:{should_sync:!1,sync_to_toutiao:K.sync_toutiao?1:0},open_platform:{},assistant:{is_preview:0,is_post_assistant:1},declare:{user_declare_info:"{}"}}},l=`https://creator.douyin.com/web/api/media/aweme/create_v2/?read_aid=2906&${WK}`;process.stderr.write(` 创建定时发布...
|
|
14
|
-
`);let F;try{F=await u(Z,"POST",l,{body:p})}catch(Q){if((Q instanceof Error?Q.message:String(Q)).includes("Failed to fetch"))throw new G("提交定时发布请求被浏览器侧拦截(疑似字节安全组件 secsdk)","不要直接重试:先用 `
|
|
14
|
+
`);let F;try{F=await u(Z,"POST",l,{body:p})}catch(Q){if((Q instanceof Error?Q.message:String(Q)).includes("Failed to fetch"))throw new G("提交定时发布请求被浏览器侧拦截(疑似字节安全组件 secsdk)","不要直接重试:先用 `ppcli douyin videos` / 创作者中心确认作品是否已创建,避免重复投稿。"+"确认未创建后,用 `--window foreground` 前台窗口重试(视频已上传成功的部分无需重传)。");throw Q}const R=F.aweme_id??F.item_id;if(!R)throw new G(`发布成功但未返回 aweme_id/item_id: ${JSON.stringify(F)}`);const i=`https://www.douyin.com/video/${R}`,s=new Date(V*1000).toLocaleString("zh-CN",{timeZone:"Asia/Tokyo"});return[{status:"✅ 定时发布成功!",aweme_id:R,url:i,publish_time:s}]}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as d,Strategy as y}from"@jackwener/opencli/registry";import{CliError as m}from"@jackwener/opencli/errors";d({site:"eastmoney",name:"announcement",access:"read",description:"上市公司公告(按交易所筛选)",domain:"np-anotice-stock.eastmoney.com",strategy:y.PUBLIC,browser:!1,args:[{name:"market",type:"string",default:"SHA,SZA,BJA",help:"交易所:SHA (沪) / SZA (深) / BJA (北) 可逗号分隔"},{name:"limit",type:"int",default:20,help:"返回数量 (max 100)"}],columns:["time","code","name","title","category","url"],func:async(s)=>{const i=String(s.market??"SHA,SZA,BJA").trim()||"SHA,SZA,BJA",
|
|
1
|
+
import{cli as d,Strategy as y}from"@jackwener/opencli/registry";import{CliError as m}from"@jackwener/opencli/errors";d({site:"eastmoney",name:"announcement",access:"read",description:"上市公司公告(按交易所筛选)",domain:"np-anotice-stock.eastmoney.com",strategy:y.PUBLIC,browser:!1,args:[{name:"market",type:"string",default:"SHA,SZA,BJA",help:"交易所:SHA (沪) / SZA (深) / BJA (北) 可逗号分隔"},{name:"limit",type:"int",default:20,help:"返回数量 (max 100)"}],columns:["time","code","name","title","category","url"],func:async(s)=>{const i=String(s.market??"SHA,SZA,BJA").trim()||"SHA,SZA,BJA",r=Math.max(1,Math.min(Number(s.limit)||20,100)),t=new URL("https://np-anotice-stock.eastmoney.com/api/security/ann");t.searchParams.set("page_size",String(r));t.searchParams.set("page_index","1");t.searchParams.set("ann_type",i);t.searchParams.set("client_source","web");t.searchParams.set("f_node","0");t.searchParams.set("s_node","0");const a=await fetch(t,{headers:{"User-Agent":"Mozilla/5.0"}});if(!a.ok)throw new m("HTTP_ERROR",`announcement failed: HTTP ${a.status}`);const o=await a.json(),c=Array.isArray(o?.data?.list)?o.data.list:[];if(c.length===0)throw new m("NO_DATA","eastmoney returned no announcement data");return c.slice(0,r).map((e)=>{const n=Array.isArray(e.codes)&&e.codes.length>0?e.codes[0]:{},l=Array.isArray(e.columns)&&e.columns.length>0?e.columns[0]?.column_name:"";return{time:String(e.notice_date||e.display_time||"").slice(0,19),code:n.stock_code||"",name:n.short_name||"",title:e.title||e.title_ch||"",category:l||"",url:`https://data.eastmoney.com/notices/detail/${n.stock_code||""}/${e.art_code||""}.html`}})}});
|
package/clis/eastmoney/rank.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as u,Strategy as k}from"@jackwener/opencli/registry";import{CliError as t}from"@jackwener/opencli/errors";const h={"hs-a":"m:0+t:6,m:0+t:80,m:1+t:2,m:1+t:23,m:0+t:81+s:2048","sh-a":"m:1+t:2,m:1+t:23","sz-a":"m:0+t:6,m:0+t:80","bj-a":"m:0+t:81+s:2048",cyb:"m:0+t:80",kcb:"m:1+t:23",hk:"m:116+t:3,m:116+t:4,m:116+t:1,m:116+t:2",us:"m:105,m:106,m:107"},
|
|
1
|
+
import{cli as u,Strategy as k}from"@jackwener/opencli/registry";import{CliError as t}from"@jackwener/opencli/errors";const h={"hs-a":"m:0+t:6,m:0+t:80,m:1+t:2,m:1+t:23,m:0+t:81+s:2048","sh-a":"m:1+t:2,m:1+t:23","sz-a":"m:0+t:6,m:0+t:80","bj-a":"m:0+t:81+s:2048",cyb:"m:0+t:80",kcb:"m:1+t:23",hk:"m:116+t:3,m:116+t:4,m:116+t:1,m:116+t:2",us:"m:105,m:106,m:107"},p={change:{fid:"f3",order:"desc"},drop:{fid:"f3",order:"asc"},turnover:{fid:"f6",order:"desc"},volume:{fid:"f5",order:"desc"},amplitude:{fid:"f7",order:"desc"},rate:{fid:"f8",order:"desc"}},y="f2,f3,f4,f5,f6,f7,f8,f9,f10,f12,f13,f14,f15,f16,f17,f18,f20,f21,f23";u({site:"eastmoney",name:"rank",access:"read",description:"东财市场涨跌/成交排行(沪深/北证/创/科/港/美)",domain:"push2.eastmoney.com",strategy:k.PUBLIC,browser:!1,args:[{name:"market",type:"string",default:"hs-a",help:"市场:hs-a / sh-a / sz-a / bj-a / cyb / kcb / hk / us"},{name:"sort",type:"string",default:"change",help:"排序:change / drop / turnover / volume / amplitude / rate"},{name:"limit",type:"int",default:20,help:"返回数量 (max 100)"}],columns:["rank","code","name","price","changePercent","change","turnover","volume","turnoverRate","peDynamic","marketCap"],func:async(r)=>{const s=String(r.market??"hs-a").toLowerCase(),n=String(r.sort??"change").toLowerCase(),c=Math.max(1,Math.min(Number(r.limit)||20,100)),f=h[s];if(!f)throw new t("INVALID_ARGUMENT",`Unknown market "${s}". Valid: ${Object.keys(h).join(", ")}`);const o=p[n];if(!o)throw new t("INVALID_ARGUMENT",`Unknown sort "${n}". Valid: ${Object.keys(p).join(", ")}`);const e=new URL("https://push2.eastmoney.com/api/qt/clist/get");e.searchParams.set("pn","1");e.searchParams.set("pz",String(c));e.searchParams.set("po",o.order==="desc"?"1":"0");e.searchParams.set("np","1");e.searchParams.set("fltt","2");e.searchParams.set("invt","2");e.searchParams.set("fid",o.fid);e.searchParams.set("fs",f);e.searchParams.set("fields",y);e.searchParams.set("ut","bd1d9ddb04089700cf9c27f6f7426281");const m=await fetch(e,{headers:{"User-Agent":"Mozilla/5.0",Accept:"application/json"}});if(!m.ok)throw new t("HTTP_ERROR",`eastmoney rank failed: HTTP ${m.status}`);const i=await m.json(),d=Array.isArray(i?.data?.diff)?i.data.diff:[];if(d.length===0)throw new t("NO_DATA","eastmoney returned no rank data",`market=${s} sort=${n}`);return d.slice(0,c).map((a,l)=>({rank:l+1,code:a.f12,name:a.f14,price:a.f2,changePercent:a.f3,change:a.f4,turnover:a.f6,volume:a.f5,turnoverRate:a.f8,peDynamic:a.f9,marketCap:a.f20}))}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as y,Strategy as u}from"@jackwener/opencli/registry";import{CliError as s}from"@jackwener/opencli/errors";const
|
|
1
|
+
import{cli as y,Strategy as u}from"@jackwener/opencli/registry";import{CliError as s}from"@jackwener/opencli/errors";const p={industry:"m:90+t:2",concept:"m:90+t:3",region:"m:90+t:1"},l={change:{fid:"f3",order:"desc"},drop:{fid:"f3",order:"asc"},"money-flow":{fid:"f62",order:"desc"},"out-flow":{fid:"f62",order:"asc"},turnover:{fid:"f6",order:"desc"}};y({site:"eastmoney",name:"sectors",access:"read",description:"板块排行(行业/概念/地域)按涨跌幅、主力资金或成交额排序",domain:"push2.eastmoney.com",strategy:u.PUBLIC,browser:!1,args:[{name:"type",type:"string",default:"industry",help:"板块类型:industry / concept / region"},{name:"sort",type:"string",default:"change",help:"排序:change / drop / money-flow / out-flow / turnover"},{name:"limit",type:"int",default:20,help:"返回数量 (max 100)"}],columns:["rank","code","name","price","changePercent","mainNet","leadStock","leadChangePercent","upCount","downCount"],func:async(r)=>{const o=String(r.type??"industry").toLowerCase(),c=p[o];if(!c)throw new s("INVALID_ARGUMENT",`Unknown sector type "${o}". Valid: ${Object.keys(p).join(", ")}`);const f=String(r.sort??"change").toLowerCase(),a=l[f];if(!a)throw new s("INVALID_ARGUMENT",`Unknown sort "${f}". Valid: ${Object.keys(l).join(", ")}`);const i=Math.max(1,Math.min(Number(r.limit)||20,100)),e=new URL("https://push2.eastmoney.com/api/qt/clist/get");e.searchParams.set("pn","1");e.searchParams.set("pz",String(i));e.searchParams.set("po",a.order==="desc"?"1":"0");e.searchParams.set("np","1");e.searchParams.set("fltt","2");e.searchParams.set("invt","2");e.searchParams.set("fid",a.fid);e.searchParams.set("fs",c);e.searchParams.set("fields","f12,f14,f2,f3,f62,f104,f105,f128,f136,f140,f141");e.searchParams.set("ut","b2884a393a59ad64002292a3e90d46a5");const n=await fetch(e,{headers:{"User-Agent":"Mozilla/5.0"}});if(!n.ok)throw new s("HTTP_ERROR",`sectors failed: HTTP ${n.status}`);const d=await n.json(),m=Array.isArray(d?.data?.diff)?d.data.diff:[];if(m.length===0)throw new s("NO_DATA","eastmoney returned no sector data");return m.slice(0,i).map((t,h)=>({rank:h+1,code:t.f12,name:t.f14,price:t.f2,changePercent:t.f3,mainNet:t.f62,leadStock:t.f128,leadChangePercent:t.f136,upCount:t.f104,downCount:t.f105}))}});
|
package/clis/endoflife/utils.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ArgumentError as I,CommandExecutionError as J,EmptyResultError as K}from"@jackwener/opencli/errors";export const EOL_BASE="https://endoflife.date/api";const M="opencli-endoflife-adapter (+https://github.com/jackwener/
|
|
1
|
+
import{ArgumentError as I,CommandExecutionError as J,EmptyResultError as K}from"@jackwener/opencli/errors";export const EOL_BASE="https://endoflife.date/api";const M="opencli-endoflife-adapter (+https://github.com/jackwener/OpenCLI)",N=/^[a-z0-9][a-z0-9._-]{0,79}$/;export function requireProduct(q){const z=String(q??"").trim().toLowerCase();if(!z)throw new I('endoflife product is required (e.g. "nodejs", "python", "ubuntu")',"Use the slug visible at https://endoflife.date/<product>.");if(!N.test(z))throw new I(`endoflife product "${q}" is not a valid endoflife.date slug`,'Slugs are lowercase ASCII letters/digits/"._-", e.g. "nodejs", "python", "ubuntu".');return z}export function requireBoundedInt(q,z,B,G="limit"){const F=q??z,H=typeof F==="number"?F:Number(F);if(!Number.isInteger(H)||H<=0)throw new I(`endoflife ${G} must be a positive integer`);if(H>B)throw new I(`endoflife ${G} must be <= ${B}`);return H}export async function eolFetch(q,z){let B;try{B=await fetch(q,{headers:{"user-agent":M,accept:"application/json"}})}catch(F){throw new J(`${z} request failed: ${F?.message??F}`,"Check that endoflife.date is reachable from this network.")}if(B.status===404)throw new K(z,`endoflife.date returned 404 for ${q}.`);if(B.status===429)throw new J(`${z} returned HTTP 429 (rate limited)`,"endoflife.date throttles unauthenticated traffic; wait a few seconds and retry.");if(!B.ok)throw new J(`${z} returned HTTP ${B.status}`);let G;try{G=await B.json()}catch(F){throw new J(`${z} returned malformed JSON: ${F?.message??F}`)}return G}export function normaliseDateOrFlag(q){if(q===!0)return"ongoing";if(q===!1||q==null)return null;if(typeof q==="string")return q.trim()||null;return null}
|
package/clis/flathub/utils.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ArgumentError as J,CommandExecutionError as K,EmptyResultError as Q}from"@jackwener/opencli/errors";export const FLATHUB_API_BASE="https://flathub.org/api/v2",FLATHUB_APP_BASE="https://flathub.org/apps";const W="opencli-flathub-adapter/1.0 (+https://github.com/jackwener/
|
|
1
|
+
import{ArgumentError as J,CommandExecutionError as K,EmptyResultError as Q}from"@jackwener/opencli/errors";export const FLATHUB_API_BASE="https://flathub.org/api/v2",FLATHUB_APP_BASE="https://flathub.org/apps";const W="opencli-flathub-adapter/1.0 (+https://github.com/jackwener/OpenCLI; mailto:ppcli@example.com)",X=/^[A-Za-z][A-Za-z0-9_-]*(?:\.[A-Za-z0-9_][A-Za-z0-9_-]*){1,}$/;export function requireString(h,z){const q=String(h??"").trim();if(!q)throw new J(`flathub ${z} cannot be empty`);return q}export function requireBoundedInt(h,z,q,G="limit"){const H=h??z,B=typeof H==="number"?H:Number(H);if(!Number.isInteger(B)||B<=0)throw new J(`flathub ${G} must be a positive integer`);if(B>q)throw new J(`flathub ${G} must be <= ${q}`);return B}export function requireAppId(h){const z=String(h??"").trim();if(!z)throw new J('flathub appId is required (e.g. "org.mozilla.firefox")');if(!X.test(z))throw new J(`flathub appId "${h}" is not a valid AppStream identifier`,'AppStream IDs use reverse-DNS like "org.mozilla.firefox" — letters/digits/`._-` with at least one dot.');return z}export async function flathubFetch(h,z,q){let G;try{G=await fetch(h,{method:q?.method??"GET",headers:{"user-agent":W,accept:"application/json",...q?.headers??{}},body:q?.body})}catch(B){throw new K(`${z} request failed: ${B?.message??B}`,"Check that flathub.org is reachable from this network.")}if(G.status===404)throw new Q(z,`Flathub returned 404 for ${h}.`);if(G.status===429)throw new K(`${z} returned HTTP 429 (rate limited)`);if(!G.ok)throw new K(`${z} returned HTTP ${G.status}`);let H;try{H=await G.json()}catch(B){throw new K(`${z} returned malformed JSON: ${B?.message??B}`)}return H}export function joinList(h,z=10){if(!Array.isArray(h))return"";const q=h.filter((G)=>typeof G==="string"&&G.trim());if(q.length===0)return"";if(q.length>z)return[...q.slice(0,z),`(+${q.length-z})`].join(", ");return q.join(", ")}function L(h){if(typeof h==="number"&&Number.isFinite(h))return h;if(typeof h==="string"&&/^\d+$/.test(h.trim()))return Number(h.trim());return 0}export function pickLatestRelease(h){if(!Array.isArray(h)||h.length===0)return{version:null,date:null};const q=[...h].filter((B)=>B&&typeof B==="object").sort((B,M)=>{return L(M?.timestamp)-L(B?.timestamp)})[0];if(!q)return{version:null,date:null};let G=null;const H=L(q.timestamp);if(H>0){const B=new Date(H*1000);if(!Number.isNaN(B.getTime()))G=B.toISOString().slice(0,10)}if(!G&&typeof q.date==="string"&&q.date.trim())G=q.date.trim();return{version:typeof q.version==="string"?q.version:null,date:G}}
|
package/clis/gemini/detail.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as X,Strategy as Y}from"@jackwener/opencli/registry";import{ArgumentError as Z,EmptyResultError as W}from"@jackwener/opencli/errors";import{GEMINI_APP_URL as $,GEMINI_DOMAIN as b,ensureGeminiPage as D,getGeminiConversationList as F,getGeminiVisibleTurns as O,resolveGeminiConversationForQuery as P}from"./utils.js";import{extractGeminiId as Q}from"./history.js";async function S(j,H){const f=String(H||"").trim();if(!f)throw new Z("id","must be a conversation id, /app/<id> URL, or sidebar title");const z=Q(f),B=f.startsWith("http")||f.startsWith("/app/")||/^[a-f0-9]{16,}$/i.test(f);if(z&&B)return`${$}/${z}`;const J=await F(j),K=P(J,f,"contains");if(!K||!K.Url)throw new W("gemini detail",`No sidebar conversation matched "${f}". Try the exact id from \`
|
|
1
|
+
import{cli as X,Strategy as Y}from"@jackwener/opencli/registry";import{ArgumentError as Z,EmptyResultError as W}from"@jackwener/opencli/errors";import{GEMINI_APP_URL as $,GEMINI_DOMAIN as b,ensureGeminiPage as D,getGeminiConversationList as F,getGeminiVisibleTurns as O,resolveGeminiConversationForQuery as P}from"./utils.js";import{extractGeminiId as Q}from"./history.js";async function S(j,H){const f=String(H||"").trim();if(!f)throw new Z("id","must be a conversation id, /app/<id> URL, or sidebar title");const z=Q(f),B=f.startsWith("http")||f.startsWith("/app/")||/^[a-f0-9]{16,}$/i.test(f);if(z&&B)return`${$}/${z}`;const J=await F(j),K=P(J,f,"contains");if(!K||!K.Url)throw new W("gemini detail",`No sidebar conversation matched "${f}". Try the exact id from \`ppcli gemini history\` instead.`);return K.Url}export const detailCommand=X({site:"gemini",name:"detail",access:"read",description:"Open a Gemini web conversation by id, URL, or sidebar title and read its turns",domain:b,strategy:Y.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"id",positional:!0,required:!0,help:"Conversation id, /app/<id> URL, or sidebar title"}],columns:["Index","Role","Text"],func:async(j,H)=>{await D(j);const f=await S(j,H?.id);await j.goto(f,{waitUntil:"load",settleMs:2500});const z=await O(j);if(!Array.isArray(z)||z.length===0)throw new W("gemini detail",`No turns were visible after navigating to ${f}.`);return z.map((B,J)=>({Index:J+1,Role:B.Role||"System",Text:B.Text||""}))}});
|
package/clis/gemini/read.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as n,Strategy as s}from"@jackwener/opencli/registry";import{EmptyResultError as a}from"@jackwener/opencli/errors";import{GEMINI_DOMAIN as o,ensureGeminiPage as m,getGeminiVisibleTurns as c}from"./utils.js";export const readCommand=n({site:"gemini",name:"read",access:"read",description:"Read the turns visible in the current Gemini web conversation",domain:o,strategy:s.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[],columns:["Index","Role","Text"],func:async(i)=>{await m(i);const e=await c(i);if(!Array.isArray(e)||e.length===0)throw new a("gemini read","No turns were visible. Open a Gemini conversation first or use `
|
|
1
|
+
import{cli as n,Strategy as s}from"@jackwener/opencli/registry";import{EmptyResultError as a}from"@jackwener/opencli/errors";import{GEMINI_DOMAIN as o,ensureGeminiPage as m,getGeminiVisibleTurns as c}from"./utils.js";export const readCommand=n({site:"gemini",name:"read",access:"read",description:"Read the turns visible in the current Gemini web conversation",domain:o,strategy:s.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[],columns:["Index","Role","Text"],func:async(i)=>{await m(i);const e=await c(i);if(!Array.isArray(e)||e.length===0)throw new a("gemini read","No turns were visible. Open a Gemini conversation first or use `ppcli gemini detail <id>` to navigate.");return e.map((r,t)=>({Index:t+1,Role:r.Role||"System",Text:r.Text||""}))}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as s,Strategy as d}from"@jackwener/opencli/registry";import{ArgumentError as c}from"@jackwener/opencli/errors";import{ensureApplet as p,ggbEval as m,normalizeLabel as a,normalizeNumber as b,requireGgbSuccess as f}from"./utils.js";s({site:"geogebra",name:"add-circle",access:"write",description:"Create a circle by center+radius or center+point",domain:"www.geogebra.org",strategy:d.PUBLIC,browser:!0,navigateBefore:!1,example:"
|
|
1
|
+
import{cli as s,Strategy as d}from"@jackwener/opencli/registry";import{ArgumentError as c}from"@jackwener/opencli/errors";import{ensureApplet as p,ggbEval as m,normalizeLabel as a,normalizeNumber as b,requireGgbSuccess as f}from"./utils.js";s({site:"geogebra",name:"add-circle",access:"write",description:"Create a circle by center+radius or center+point",domain:"www.geogebra.org",strategy:d.PUBLIC,browser:!0,navigateBefore:!1,example:"ppcli geogebra add-circle --center A --radius 3",args:[{name:"center",required:!0,help:"Center point label (e.g. A)"},{name:"radius",required:!1,help:"Radius value (number) or a point label on the circle"},{name:"point",required:!1,help:"Alternative: a point label on the circle (use instead of --radius for Circle(center,point))"}],columns:["label","center","radius"],func:async(l,e)=>{const r=a(e.center,"center");if(e.point&&e.radius!==void 0)throw new c("Use either --point or --radius, not both");const t=e.point?a(e.point,"point"):"",n=e.radius;let i;if(t)i=`Circle(${r},${t})`;else if(n!==void 0){const o=String(n).trim(),u=Number(o);i=Number.isFinite(u)?`Circle(${r},${b(o,"radius",{positive:!0})})`:`Circle(${r},${a(o,"radius point")})`}else throw new c("Provide --radius (number or point label) or --point (point on circle)");await p(l);return[{label:f(await m(l,i),`Failed to create circle: ${i}`).label,center:r,radius:t||n}]}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as i,Strategy as s}from"@jackwener/opencli/registry";import{ArgumentError as l}from"@jackwener/opencli/errors";import{ensureApplet as p,ggbEval as m,normalizeLabelList as g,requireGgbSuccess as c}from"./utils.js";i({site:"geogebra",name:"add-line",access:"write",description:"Create a line through two points or a segment between two points",domain:"www.geogebra.org",strategy:s.PUBLIC,browser:!0,navigateBefore:!1,example:"
|
|
1
|
+
import{cli as i,Strategy as s}from"@jackwener/opencli/registry";import{ArgumentError as l}from"@jackwener/opencli/errors";import{ensureApplet as p,ggbEval as m,normalizeLabelList as g,requireGgbSuccess as c}from"./utils.js";i({site:"geogebra",name:"add-line",access:"write",description:"Create a line through two points or a segment between two points",domain:"www.geogebra.org",strategy:s.PUBLIC,browser:!0,navigateBefore:!1,example:"ppcli geogebra add-line --points A,B --type segment",args:[{name:"points",required:!0,help:'Two point labels separated by comma (e.g. "A,B")'},{name:"type",required:!1,choices:["line","segment","ray"],default:"line",help:"Type: line, segment, or ray (default: line)"}],columns:["label","type","points"],func:async(a,o)=>{const[e,t]=g(o.points,"points",2,2),n=o.type||"line",r={line:`Line(${e},${t})`,segment:`Segment(${e},${t})`,ray:`Ray(${e},${t})`}[n];if(!r)throw new l("type must be one of: line, segment, ray");await p(a);return[{label:c(await m(a,r),`Failed to create ${n}: ${r}`).label,type:n,points:`${e},${t}`}]}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as i,Strategy as s}from"@jackwener/opencli/registry";import{ensureApplet as c,ggbEval as m,normalizeCoords as l,normalizeLabel as d,requireGgbSuccess as g}from"./utils.js";i({site:"geogebra",name:"add-point",access:"write",description:"Create a point with given label and coordinates",domain:"www.geogebra.org",strategy:s.PUBLIC,browser:!0,navigateBefore:!1,example:"
|
|
1
|
+
import{cli as i,Strategy as s}from"@jackwener/opencli/registry";import{ensureApplet as c,ggbEval as m,normalizeCoords as l,normalizeLabel as d,requireGgbSuccess as g}from"./utils.js";i({site:"geogebra",name:"add-point",access:"write",description:"Create a point with given label and coordinates",domain:"www.geogebra.org",strategy:s.PUBLIC,browser:!0,navigateBefore:!1,example:"ppcli geogebra add-point --name A --coords 1,2",args:[{name:"name",required:!0,help:"Point label (e.g. A, B, P1)"},{name:"coords",required:!0,help:'Coordinates as x,y (e.g. "1,2")'}],columns:["name","x","y"],func:async(e,a)=>{const r=d(a.name,"name"),[o,t]=l(a.coords),n=`${r}=(${o},${t})`;await c(e);const p=g(await m(e,n),`Failed to create point: ${n}`);return[{name:r,x:o,y:t}]}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as r,Strategy as s}from"@jackwener/opencli/registry";import{ensureApplet as i,ggbEval as l,normalizeLabelList as n,requireGgbSuccess as
|
|
1
|
+
import{cli as r,Strategy as s}from"@jackwener/opencli/registry";import{ensureApplet as i,ggbEval as l,normalizeLabelList as n,requireGgbSuccess as p}from"./utils.js";r({site:"geogebra",name:"add-polygon",access:"write",description:"Create a polygon from a list of point labels",domain:"www.geogebra.org",strategy:s.PUBLIC,browser:!0,navigateBefore:!1,example:"ppcli geogebra add-polygon --points A,B,C",args:[{name:"points",required:!0,help:'Comma-separated point labels (e.g. "A,B,C" or "A,B,C,D")'}],columns:["label","vertices"],func:async(e,t)=>{const o=n(t.points,"points",3,50),a=`Polygon(${o.join(",")})`;await i(e);return[{label:p(await l(e,a),`Failed to create polygon: ${a}`).label,vertices:o.join(",")}]}});
|