@zenalexa/unicli 0.211.2 → 0.213.0-beta.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/AGENTS.md +180 -138
- package/README.md +195 -771
- package/dist/cli.d.ts +8 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +74 -218
- package/dist/cli.js.map +1 -1
- package/dist/commands/acp.d.ts +14 -0
- package/dist/commands/acp.d.ts.map +1 -0
- package/dist/commands/acp.js +41 -0
- package/dist/commands/acp.js.map +1 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js.map +1 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +8 -3
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/dispatch.d.ts +14 -0
- package/dist/commands/dispatch.d.ts.map +1 -0
- package/dist/commands/dispatch.js +258 -0
- package/dist/commands/dispatch.js.map +1 -0
- package/dist/commands/ext.d.ts.map +1 -1
- package/dist/commands/ext.js +7 -5
- package/dist/commands/ext.js.map +1 -1
- package/dist/commands/health.d.ts.map +1 -1
- package/dist/commands/health.js +33 -22
- package/dist/commands/health.js.map +1 -1
- package/dist/commands/lint.d.ts +33 -0
- package/dist/commands/lint.d.ts.map +1 -0
- package/dist/commands/lint.js +332 -0
- package/dist/commands/lint.js.map +1 -0
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/migrate-schema.d.ts +56 -0
- package/dist/commands/migrate-schema.d.ts.map +1 -0
- package/dist/commands/migrate-schema.js +540 -0
- package/dist/commands/migrate-schema.js.map +1 -0
- package/dist/commands/migrate.d.ts +54 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +270 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/search.d.ts.map +1 -1
- package/dist/commands/search.js +7 -1
- package/dist/commands/search.js.map +1 -1
- package/dist/commands/skills.d.ts.map +1 -1
- package/dist/commands/skills.js +76 -0
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/usage.d.ts.map +1 -1
- package/dist/commands/usage.js +20 -19
- package/dist/commands/usage.js.map +1 -1
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +2 -0
- package/dist/constants.js.map +1 -1
- package/dist/core/envelope.d.ts +87 -0
- package/dist/core/envelope.d.ts.map +1 -0
- package/dist/core/envelope.js +94 -0
- package/dist/core/envelope.js.map +1 -0
- package/dist/core/index.d.ts +12 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +12 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/registry.d.ts +63 -0
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/core/registry.js +62 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/schema-v2.d.ts +119 -0
- package/dist/core/schema-v2.d.ts.map +1 -0
- package/dist/core/schema-v2.js +130 -0
- package/dist/core/schema-v2.js.map +1 -0
- package/dist/core/types.d.ts +24 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +15 -0
- package/dist/core/types.js.map +1 -0
- package/dist/discovery/aliases.d.ts +5 -0
- package/dist/discovery/aliases.d.ts.map +1 -1
- package/dist/discovery/aliases.js +45 -2
- package/dist/discovery/aliases.js.map +1 -1
- package/dist/discovery/loader.d.ts.map +1 -1
- package/dist/discovery/loader.js +83 -5
- package/dist/discovery/loader.js.map +1 -1
- package/dist/discovery/search.d.ts.map +1 -1
- package/dist/discovery/search.js +42 -5
- package/dist/discovery/search.js.map +1 -1
- package/dist/engine/executor.d.ts +77 -0
- package/dist/engine/executor.d.ts.map +1 -0
- package/dist/engine/executor.js +189 -0
- package/dist/engine/executor.js.map +1 -0
- package/dist/engine/repair/logger.d.ts.map +1 -1
- package/dist/engine/repair/logger.js +11 -1
- package/dist/engine/repair/logger.js.map +1 -1
- package/dist/engine/runtime.d.ts +23 -0
- package/dist/engine/runtime.d.ts.map +1 -0
- package/dist/engine/runtime.js +167 -0
- package/dist/engine/runtime.js.map +1 -0
- package/dist/engine/ssrf.d.ts +18 -0
- package/dist/engine/ssrf.d.ts.map +1 -0
- package/dist/engine/ssrf.js +55 -0
- package/dist/engine/ssrf.js.map +1 -0
- package/dist/engine/step-registry.d.ts +16 -0
- package/dist/engine/step-registry.d.ts.map +1 -0
- package/dist/engine/step-registry.js +25 -0
- package/dist/engine/step-registry.js.map +1 -0
- package/dist/engine/steps/append.d.ts +3 -0
- package/dist/engine/steps/append.d.ts.map +1 -0
- package/dist/engine/steps/append.js +20 -0
- package/dist/engine/steps/append.js.map +1 -0
- package/dist/engine/steps/assert.d.ts +10 -0
- package/dist/engine/steps/assert.d.ts.map +1 -0
- package/dist/engine/steps/assert.js +58 -0
- package/dist/engine/steps/assert.js.map +1 -0
- package/dist/engine/steps/browser-helpers.d.ts +13 -0
- package/dist/engine/steps/browser-helpers.d.ts.map +1 -0
- package/dist/engine/steps/browser-helpers.js +85 -0
- package/dist/engine/steps/browser-helpers.js.map +1 -0
- package/dist/engine/steps/click.d.ts +9 -0
- package/dist/engine/steps/click.d.ts.map +1 -0
- package/dist/engine/steps/click.js +32 -0
- package/dist/engine/steps/click.js.map +1 -0
- package/dist/engine/steps/cua.d.ts +41 -0
- package/dist/engine/steps/cua.d.ts.map +1 -0
- package/dist/engine/steps/cua.js +59 -0
- package/dist/engine/steps/cua.js.map +1 -0
- package/dist/engine/steps/desktop-ax.d.ts +31 -0
- package/dist/engine/steps/desktop-ax.d.ts.map +1 -0
- package/dist/engine/steps/desktop-ax.js +42 -0
- package/dist/engine/steps/desktop-ax.js.map +1 -0
- package/dist/engine/steps/download.d.ts +13 -0
- package/dist/engine/steps/download.d.ts.map +1 -0
- package/dist/engine/steps/download.js +59 -0
- package/dist/engine/steps/download.js.map +1 -0
- package/dist/engine/steps/each.d.ts +9 -0
- package/dist/engine/steps/each.d.ts.map +1 -0
- package/dist/engine/steps/each.js +44 -0
- package/dist/engine/steps/each.js.map +1 -0
- package/dist/engine/steps/evaluate.d.ts +6 -0
- package/dist/engine/steps/evaluate.d.ts.map +1 -0
- package/dist/engine/steps/evaluate.js +13 -0
- package/dist/engine/steps/evaluate.js.map +1 -0
- package/dist/engine/steps/exec.d.ts +12 -0
- package/dist/engine/steps/exec.d.ts.map +1 -0
- package/dist/engine/steps/exec.js +161 -0
- package/dist/engine/steps/exec.js.map +1 -0
- package/dist/engine/steps/extract.d.ts +14 -0
- package/dist/engine/steps/extract.d.ts.map +1 -0
- package/dist/engine/steps/extract.js +51 -0
- package/dist/engine/steps/extract.js.map +1 -0
- package/dist/engine/steps/fetch-text.d.ts +4 -0
- package/dist/engine/steps/fetch-text.d.ts.map +1 -0
- package/dist/engine/steps/fetch-text.js +53 -0
- package/dist/engine/steps/fetch-text.js.map +1 -0
- package/dist/engine/steps/fetch.d.ts +13 -0
- package/dist/engine/steps/fetch.d.ts.map +1 -0
- package/dist/engine/steps/fetch.js +186 -0
- package/dist/engine/steps/fetch.js.map +1 -0
- package/dist/engine/steps/filter.d.ts +3 -0
- package/dist/engine/steps/filter.d.ts.map +1 -0
- package/dist/engine/steps/filter.js +14 -0
- package/dist/engine/steps/filter.js.map +1 -0
- package/dist/engine/steps/html-to-md.d.ts +3 -0
- package/dist/engine/steps/html-to-md.d.ts.map +1 -0
- package/dist/engine/steps/html-to-md.js +13 -0
- package/dist/engine/steps/html-to-md.js.map +1 -0
- package/dist/engine/steps/if.d.ts +8 -0
- package/dist/engine/steps/if.d.ts.map +1 -0
- package/dist/engine/steps/if.js +36 -0
- package/dist/engine/steps/if.js.map +1 -0
- package/dist/engine/steps/index.d.ts +38 -0
- package/dist/engine/steps/index.d.ts.map +1 -0
- package/dist/engine/steps/index.js +38 -0
- package/dist/engine/steps/index.js.map +1 -0
- package/dist/engine/steps/intercept.d.ts +12 -0
- package/dist/engine/steps/intercept.d.ts.map +1 -0
- package/dist/engine/steps/intercept.js +67 -0
- package/dist/engine/steps/intercept.js.map +1 -0
- package/dist/engine/steps/limit.d.ts +3 -0
- package/dist/engine/steps/limit.d.ts.map +1 -0
- package/dist/engine/steps/limit.js +17 -0
- package/dist/engine/steps/limit.js.map +1 -0
- package/dist/engine/steps/map.d.ts +3 -0
- package/dist/engine/steps/map.d.ts.map +1 -0
- package/dist/engine/steps/map.js +20 -0
- package/dist/engine/steps/map.js.map +1 -0
- package/dist/engine/steps/navigate.d.ts +8 -0
- package/dist/engine/steps/navigate.d.ts.map +1 -0
- package/dist/engine/steps/navigate.js +15 -0
- package/dist/engine/steps/navigate.js.map +1 -0
- package/dist/engine/steps/parallel.d.ts +4 -0
- package/dist/engine/steps/parallel.d.ts.map +1 -0
- package/dist/engine/steps/parallel.js +56 -0
- package/dist/engine/steps/parallel.js.map +1 -0
- package/dist/engine/steps/parse-rss.d.ts +6 -0
- package/dist/engine/steps/parse-rss.d.ts.map +1 -0
- package/dist/engine/steps/parse-rss.js +57 -0
- package/dist/engine/steps/parse-rss.js.map +1 -0
- package/dist/engine/steps/press.d.ts +3 -0
- package/dist/engine/steps/press.d.ts.map +1 -0
- package/dist/engine/steps/press.js +22 -0
- package/dist/engine/steps/press.js.map +1 -0
- package/dist/engine/steps/scroll.d.ts +3 -0
- package/dist/engine/steps/scroll.d.ts.map +1 -0
- package/dist/engine/steps/scroll.js +26 -0
- package/dist/engine/steps/scroll.js.map +1 -0
- package/dist/engine/steps/select.d.ts +3 -0
- package/dist/engine/steps/select.d.ts.map +1 -0
- package/dist/engine/steps/select.js +21 -0
- package/dist/engine/steps/select.js.map +1 -0
- package/dist/engine/steps/set.d.ts +3 -0
- package/dist/engine/steps/set.d.ts.map +1 -0
- package/dist/engine/steps/set.js +13 -0
- package/dist/engine/steps/set.js.map +1 -0
- package/dist/engine/steps/snapshot.d.ts +3 -0
- package/dist/engine/steps/snapshot.d.ts.map +1 -0
- package/dist/engine/steps/snapshot.js +17 -0
- package/dist/engine/steps/snapshot.js.map +1 -0
- package/dist/engine/steps/sort.d.ts +7 -0
- package/dist/engine/steps/sort.d.ts.map +1 -0
- package/dist/engine/steps/sort.js +21 -0
- package/dist/engine/steps/sort.js.map +1 -0
- package/dist/engine/steps/tap.d.ts +12 -0
- package/dist/engine/steps/tap.d.ts.map +1 -0
- package/dist/engine/steps/tap.js +110 -0
- package/dist/engine/steps/tap.js.map +1 -0
- package/dist/engine/steps/type.d.ts +8 -0
- package/dist/engine/steps/type.d.ts.map +1 -0
- package/dist/engine/steps/type.js +20 -0
- package/dist/engine/steps/type.js.map +1 -0
- package/dist/engine/steps/wait.d.ts +8 -0
- package/dist/engine/steps/wait.d.ts.map +1 -0
- package/dist/engine/steps/wait.js +17 -0
- package/dist/engine/steps/wait.js.map +1 -0
- package/dist/engine/steps/websocket.d.ts +4 -0
- package/dist/engine/steps/websocket.d.ts.map +1 -0
- package/dist/engine/steps/websocket.js +14 -0
- package/dist/engine/steps/websocket.js.map +1 -0
- package/dist/engine/steps/write-temp.d.ts +7 -0
- package/dist/engine/steps/write-temp.d.ts.map +1 -0
- package/dist/engine/steps/write-temp.js +19 -0
- package/dist/engine/steps/write-temp.js.map +1 -0
- package/dist/engine/template.d.ts +42 -0
- package/dist/engine/template.d.ts.map +1 -0
- package/dist/engine/template.js +378 -0
- package/dist/engine/template.js.map +1 -0
- package/dist/engine/yaml-runner.d.ts +9 -100
- package/dist/engine/yaml-runner.d.ts.map +1 -1
- package/dist/engine/yaml-runner.js +15 -2025
- package/dist/engine/yaml-runner.js.map +1 -1
- package/dist/errors.d.ts +17 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +16 -0
- package/dist/errors.js.map +1 -0
- package/dist/manifest-compact.txt +1 -1
- package/dist/manifest-search.json +1 -1
- package/dist/manifest.json +98 -1
- package/dist/mcp/elicitation.d.ts +56 -0
- package/dist/mcp/elicitation.d.ts.map +1 -0
- package/dist/mcp/elicitation.js +62 -0
- package/dist/mcp/elicitation.js.map +1 -0
- package/dist/mcp/oauth.d.ts.map +1 -1
- package/dist/mcp/oauth.js +80 -9
- package/dist/mcp/oauth.js.map +1 -1
- package/dist/mcp/server.d.ts +5 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +73 -67
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/streamable-http.d.ts +36 -11
- package/dist/mcp/streamable-http.d.ts.map +1 -1
- package/dist/mcp/streamable-http.js +277 -23
- package/dist/mcp/streamable-http.js.map +1 -1
- package/dist/output/envelope.d.ts +83 -0
- package/dist/output/envelope.d.ts.map +1 -0
- package/dist/output/envelope.js +96 -0
- package/dist/output/envelope.js.map +1 -0
- package/dist/output/formatter.d.ts +33 -5
- package/dist/output/formatter.d.ts.map +1 -1
- package/dist/output/formatter.js +153 -61
- package/dist/output/formatter.js.map +1 -1
- package/dist/output/md.d.ts +20 -0
- package/dist/output/md.d.ts.map +1 -0
- package/dist/output/md.js +300 -0
- package/dist/output/md.js.map +1 -0
- package/dist/protocol/acp.d.ts +126 -0
- package/dist/protocol/acp.d.ts.map +1 -0
- package/dist/protocol/acp.js +588 -0
- package/dist/protocol/acp.js.map +1 -0
- package/dist/protocol/skill.d.ts +106 -0
- package/dist/protocol/skill.d.ts.map +1 -0
- package/dist/protocol/skill.js +276 -0
- package/dist/protocol/skill.js.map +1 -0
- package/dist/registry.d.ts +2 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +2 -0
- package/dist/registry.js.map +1 -1
- package/dist/transport/adapters/cdp-browser.d.ts +44 -0
- package/dist/transport/adapters/cdp-browser.d.ts.map +1 -0
- package/dist/transport/adapters/cdp-browser.js +240 -0
- package/dist/transport/adapters/cdp-browser.js.map +1 -0
- package/dist/transport/adapters/cua.d.ts +240 -0
- package/dist/transport/adapters/cua.d.ts.map +1 -0
- package/dist/transport/adapters/cua.js +663 -0
- package/dist/transport/adapters/cua.js.map +1 -0
- package/dist/transport/adapters/desktop-atspi.d.ts +27 -0
- package/dist/transport/adapters/desktop-atspi.d.ts.map +1 -0
- package/dist/transport/adapters/desktop-atspi.js +65 -0
- package/dist/transport/adapters/desktop-atspi.js.map +1 -0
- package/dist/transport/adapters/desktop-ax.d.ts +59 -0
- package/dist/transport/adapters/desktop-ax.d.ts.map +1 -0
- package/dist/transport/adapters/desktop-ax.js +311 -0
- package/dist/transport/adapters/desktop-ax.js.map +1 -0
- package/dist/transport/adapters/desktop-uia.d.ts +27 -0
- package/dist/transport/adapters/desktop-uia.d.ts.map +1 -0
- package/dist/transport/adapters/desktop-uia.js +66 -0
- package/dist/transport/adapters/desktop-uia.js.map +1 -0
- package/dist/transport/adapters/http.d.ts +40 -0
- package/dist/transport/adapters/http.d.ts.map +1 -0
- package/dist/transport/adapters/http.js +353 -0
- package/dist/transport/adapters/http.js.map +1 -0
- package/dist/transport/adapters/subprocess.d.ts +27 -0
- package/dist/transport/adapters/subprocess.d.ts.map +1 -0
- package/dist/transport/adapters/subprocess.js +254 -0
- package/dist/transport/adapters/subprocess.js.map +1 -0
- package/dist/transport/bus.d.ts +74 -0
- package/dist/transport/bus.d.ts.map +1 -0
- package/dist/transport/bus.js +180 -0
- package/dist/transport/bus.js.map +1 -0
- package/dist/transport/capability.d.ts +38 -0
- package/dist/transport/capability.d.ts.map +1 -0
- package/dist/transport/capability.js +254 -0
- package/dist/transport/capability.js.map +1 -0
- package/dist/transport/types.d.ts +148 -0
- package/dist/transport/types.d.ts.map +1 -0
- package/dist/transport/types.js +19 -0
- package/dist/transport/types.js.map +1 -0
- package/dist/types.d.ts +26 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +126 -6
- package/src/adapters/1688/item.yaml +10 -0
- package/src/adapters/1688/search.yaml +10 -0
- package/src/adapters/1688/store.yaml +10 -0
- package/src/adapters/36kr/article.yaml +10 -0
- package/src/adapters/36kr/hot.yaml +10 -0
- package/src/adapters/36kr/latest.yaml +11 -0
- package/src/adapters/36kr/news.yaml +10 -0
- package/src/adapters/36kr/search.yaml +10 -0
- package/src/adapters/adguardhome/add-rule.yaml +10 -0
- package/src/adapters/adguardhome/rules.yaml +11 -0
- package/src/adapters/adguardhome/stats.yaml +11 -0
- package/src/adapters/adguardhome/status.yaml +11 -0
- package/src/adapters/adguardhome/toggle.yaml +10 -0
- package/src/adapters/amazon/bestsellers.yaml +10 -0
- package/src/adapters/amazon/discussion.yaml +10 -0
- package/src/adapters/amazon/movers-shakers.yaml +10 -0
- package/src/adapters/amazon/new-releases.yaml +10 -0
- package/src/adapters/amazon/offer.yaml +10 -0
- package/src/adapters/amazon/product.yaml +10 -0
- package/src/adapters/amazon/rankings.yaml +10 -0
- package/src/adapters/amazon/search.yaml +10 -0
- package/src/adapters/apple-music/rate-album.yaml +76 -0
- package/src/adapters/apple-notes/list.yaml +55 -0
- package/src/adapters/apple-notes/read.yaml +47 -0
- package/src/adapters/apple-notes/search.yaml +46 -0
- package/src/adapters/apple-podcasts/episodes.yaml +10 -0
- package/src/adapters/apple-podcasts/search.yaml +10 -0
- package/src/adapters/apple-podcasts/top.yaml +11 -0
- package/src/adapters/arxiv/paper.test.ts +15 -0
- package/src/adapters/arxiv/paper.yaml +10 -0
- package/src/adapters/arxiv/search.test.ts +15 -0
- package/src/adapters/arxiv/search.yaml +10 -0
- package/src/adapters/arxiv/trending.yaml +11 -0
- package/src/adapters/audacity/convert.yaml +10 -0
- package/src/adapters/audacity/effects.yaml +10 -0
- package/src/adapters/audacity/info.yaml +10 -0
- package/src/adapters/audacity/mix.yaml +10 -0
- package/src/adapters/audacity/normalize.yaml +10 -0
- package/src/adapters/audacity/spectrogram.yaml +10 -0
- package/src/adapters/audacity/split-channels.yaml +10 -0
- package/src/adapters/audacity/trim.yaml +10 -0
- package/src/adapters/autoagent/eval-run.yaml +10 -0
- package/src/adapters/aws/s3-ls.yaml +10 -0
- package/src/adapters/az/account.yaml +11 -0
- package/src/adapters/baidu/hot.yaml +10 -0
- package/src/adapters/baidu/search.yaml +10 -0
- package/src/adapters/band/bands.yaml +10 -0
- package/src/adapters/band/mentions.yaml +10 -0
- package/src/adapters/band/post.yaml +10 -0
- package/src/adapters/band/posts.yaml +10 -0
- package/src/adapters/barchart/flow.yaml +10 -0
- package/src/adapters/barchart/greeks.yaml +10 -0
- package/src/adapters/barchart/options.yaml +10 -0
- package/src/adapters/barchart/quote.yaml +10 -0
- package/src/adapters/bbc/news.yaml +10 -0
- package/src/adapters/bbc/technology.yaml +10 -0
- package/src/adapters/bbc/top.yaml +10 -0
- package/src/adapters/bbc/world.yaml +10 -0
- package/src/adapters/bilibili/coin.yaml +11 -0
- package/src/adapters/bilibili/dynamic.test.ts +15 -0
- package/src/adapters/bilibili/dynamic.yaml +10 -0
- package/src/adapters/bilibili/favorites.test.ts +15 -0
- package/src/adapters/bilibili/favorites.yaml +10 -0
- package/src/adapters/bilibili/feed.yaml +11 -0
- package/src/adapters/bilibili/following.test.ts +15 -0
- package/src/adapters/bilibili/following.yaml +10 -0
- package/src/adapters/bilibili/history.yaml +11 -0
- package/src/adapters/bilibili/hot.test.ts +15 -0
- package/src/adapters/bilibili/hot.yaml +10 -0
- package/src/adapters/bilibili/later.yaml +11 -0
- package/src/adapters/bilibili/live.yaml +11 -0
- package/src/adapters/bilibili/me.test.ts +15 -0
- package/src/adapters/bilibili/me.yaml +10 -0
- package/src/adapters/bilibili/ranking.yaml +11 -0
- package/src/adapters/bilibili/trending.test.ts +15 -0
- package/src/adapters/bilibili/trending.yaml +10 -0
- package/src/adapters/binance/hot.yaml +10 -0
- package/src/adapters/binance/kline.yaml +10 -0
- package/src/adapters/binance/ticker.yaml +10 -0
- package/src/adapters/blender/animation.yaml +10 -0
- package/src/adapters/blender/camera.yaml +10 -0
- package/src/adapters/blender/convert.yaml +10 -0
- package/src/adapters/blender/export.yaml +10 -0
- package/src/adapters/blender/import.yaml +10 -0
- package/src/adapters/blender/info.yaml +10 -0
- package/src/adapters/blender/lighting.yaml +10 -0
- package/src/adapters/blender/materials.yaml +10 -0
- package/src/adapters/blender/objects.yaml +10 -0
- package/src/adapters/blender/render.yaml +10 -0
- package/src/adapters/blender/scene.yaml +10 -0
- package/src/adapters/blender/screenshot.yaml +10 -0
- package/src/adapters/blender/script.yaml +10 -0
- package/src/adapters/bloomberg/businessweek.yaml +10 -0
- package/src/adapters/bloomberg/economics.yaml +10 -0
- package/src/adapters/bloomberg/feeds.yaml +10 -0
- package/src/adapters/bloomberg/industries.yaml +10 -0
- package/src/adapters/bloomberg/main.yaml +10 -0
- package/src/adapters/bloomberg/markets.yaml +10 -0
- package/src/adapters/bloomberg/news.yaml +10 -0
- package/src/adapters/bloomberg/opinions.yaml +10 -0
- package/src/adapters/bloomberg/politics.yaml +10 -0
- package/src/adapters/bloomberg/tech.yaml +10 -0
- package/src/adapters/bluesky/feeds.test.ts +15 -0
- package/src/adapters/bluesky/feeds.yaml +10 -0
- package/src/adapters/bluesky/followers.test.ts +15 -0
- package/src/adapters/bluesky/followers.yaml +10 -0
- package/src/adapters/bluesky/following.test.ts +15 -0
- package/src/adapters/bluesky/following.yaml +10 -0
- package/src/adapters/bluesky/likes.test.ts +15 -0
- package/src/adapters/bluesky/likes.yaml +10 -0
- package/src/adapters/bluesky/notifications.test.ts +15 -0
- package/src/adapters/bluesky/notifications.yaml +10 -0
- package/src/adapters/bluesky/post.yaml +10 -0
- package/src/adapters/bluesky/profile.yaml +10 -0
- package/src/adapters/bluesky/search.test.ts +15 -0
- package/src/adapters/bluesky/search.yaml +10 -0
- package/src/adapters/bluesky/starter-packs.test.ts +15 -0
- package/src/adapters/bluesky/starter-packs.yaml +10 -0
- package/src/adapters/bluesky/thread.test.ts +15 -0
- package/src/adapters/bluesky/thread.yaml +10 -0
- package/src/adapters/bluesky/trending.test.ts +15 -0
- package/src/adapters/bluesky/trending.yaml +10 -0
- package/src/adapters/bluesky/user.test.ts +15 -0
- package/src/adapters/bluesky/user.yaml +10 -0
- package/src/adapters/boss/batchgreet.yaml +10 -0
- package/src/adapters/boss/chatlist.yaml +10 -0
- package/src/adapters/boss/chatmsg.yaml +10 -0
- package/src/adapters/boss/detail.yaml +10 -0
- package/src/adapters/boss/exchange.yaml +10 -0
- package/src/adapters/boss/greet.yaml +10 -0
- package/src/adapters/boss/invite.yaml +10 -0
- package/src/adapters/boss/joblist.yaml +10 -0
- package/src/adapters/boss/mark.yaml +10 -0
- package/src/adapters/boss/recommend.yaml +10 -0
- package/src/adapters/boss/resume.yaml +10 -0
- package/src/adapters/boss/search.yaml +10 -0
- package/src/adapters/boss/send.yaml +10 -0
- package/src/adapters/boss/stats.yaml +10 -0
- package/src/adapters/chaoxing/assignments.yaml +10 -0
- package/src/adapters/chaoxing/exams.yaml +10 -0
- package/src/adapters/chrome/bookmarks.yaml +10 -0
- package/src/adapters/chrome/tabs.yaml +10 -0
- package/src/adapters/claude-code/version.yaml +10 -0
- package/src/adapters/cloudcompare/compare.yaml +10 -0
- package/src/adapters/cloudcompare/convert.yaml +10 -0
- package/src/adapters/cloudcompare/info.yaml +10 -0
- package/src/adapters/cloudcompare/subsample.yaml +10 -0
- package/src/adapters/cnki/search.yaml +10 -0
- package/src/adapters/cnn/technology.yaml +10 -0
- package/src/adapters/cnn/top.yaml +10 -0
- package/src/adapters/cocoapods/info.yaml +10 -0
- package/src/adapters/cocoapods/search.yaml +10 -0
- package/src/adapters/codex-cli/version.yaml +10 -0
- package/src/adapters/coinbase/prices.yaml +10 -0
- package/src/adapters/coinbase/rates.yaml +10 -0
- package/src/adapters/comfyui/generate.yaml +10 -0
- package/src/adapters/comfyui/history.yaml +10 -0
- package/src/adapters/comfyui/nodes.yaml +10 -0
- package/src/adapters/comfyui/status.yaml +10 -0
- package/src/adapters/coupang/add-to-cart.yaml +10 -0
- package/src/adapters/coupang/hot.yaml +10 -0
- package/src/adapters/coupang/search.yaml +10 -0
- package/src/adapters/crates-io/info.yaml +10 -0
- package/src/adapters/crates-io/search.yaml +10 -0
- package/src/adapters/crates-io/versions.yaml +10 -0
- package/src/adapters/ctrip/hot.yaml +10 -0
- package/src/adapters/ctrip/search.yaml +10 -0
- package/src/adapters/cua/bench-list.yaml +10 -0
- package/src/adapters/cua/bench-run.yaml +10 -0
- package/src/adapters/dangdang/hot.yaml +10 -0
- package/src/adapters/dangdang/search.yaml +10 -0
- package/src/adapters/deepseek/chat.yaml +10 -0
- package/src/adapters/deepseek/models.yaml +10 -0
- package/src/adapters/devto/latest.yaml +10 -0
- package/src/adapters/devto/search.yaml +10 -0
- package/src/adapters/devto/tag.yaml +10 -0
- package/src/adapters/devto/top.yaml +10 -0
- package/src/adapters/devto/user.yaml +10 -0
- package/src/adapters/dianping/hot.yaml +10 -0
- package/src/adapters/dianping/search.yaml +10 -0
- package/src/adapters/dictionary/examples.yaml +10 -0
- package/src/adapters/dictionary/search.yaml +10 -0
- package/src/adapters/dictionary/synonyms.yaml +10 -0
- package/src/adapters/dingtalk/version.yaml +10 -0
- package/src/adapters/docker/build.yaml +10 -0
- package/src/adapters/docker/images.yaml +10 -0
- package/src/adapters/docker/logs.yaml +10 -0
- package/src/adapters/docker/networks.yaml +10 -0
- package/src/adapters/docker/ps.yaml +10 -0
- package/src/adapters/docker/run.yaml +10 -0
- package/src/adapters/docker/volumes.yaml +10 -0
- package/src/adapters/docker-hub/info.yaml +10 -0
- package/src/adapters/docker-hub/search.yaml +10 -0
- package/src/adapters/docker-hub/tags.yaml +10 -0
- package/src/adapters/doctl/droplets.yaml +10 -0
- package/src/adapters/douban/book-hot.yaml +11 -0
- package/src/adapters/douban/download.yaml +10 -0
- package/src/adapters/douban/group-hot.yaml +11 -0
- package/src/adapters/douban/marks.yaml +10 -0
- package/src/adapters/douban/movie-hot.test.ts +15 -0
- package/src/adapters/douban/movie-hot.yaml +10 -0
- package/src/adapters/douban/new-movies.test.ts +15 -0
- package/src/adapters/douban/new-movies.yaml +10 -0
- package/src/adapters/douban/photos.yaml +10 -0
- package/src/adapters/douban/reviews.yaml +10 -0
- package/src/adapters/douban/search.test.ts +15 -0
- package/src/adapters/douban/search.yaml +10 -0
- package/src/adapters/douban/subject.yaml +10 -0
- package/src/adapters/douban/top250.yaml +11 -0
- package/src/adapters/douban/tv-hot.test.ts +15 -0
- package/src/adapters/douban/tv-hot.yaml +10 -0
- package/src/adapters/doubao/ask.yaml +10 -0
- package/src/adapters/doubao/new.yaml +10 -0
- package/src/adapters/doubao/status.yaml +10 -0
- package/src/adapters/doubao-web/ask.yaml +10 -0
- package/src/adapters/doubao-web/detail.yaml +10 -0
- package/src/adapters/doubao-web/history.yaml +10 -0
- package/src/adapters/doubao-web/meeting-summary.yaml +10 -0
- package/src/adapters/doubao-web/meeting-transcript.yaml +10 -0
- package/src/adapters/doubao-web/new.yaml +10 -0
- package/src/adapters/doubao-web/read.yaml +10 -0
- package/src/adapters/doubao-web/send.yaml +10 -0
- package/src/adapters/doubao-web/status.yaml +10 -0
- package/src/adapters/douyu/hot.yaml +10 -0
- package/src/adapters/douyu/search.yaml +10 -0
- package/src/adapters/drawio/export.yaml +10 -0
- package/src/adapters/eastmoney/fund.yaml +10 -0
- package/src/adapters/eastmoney/hot.yaml +11 -0
- package/src/adapters/eastmoney/market.yaml +11 -0
- package/src/adapters/eastmoney/search.yaml +10 -0
- package/src/adapters/ele/hot.yaml +11 -0
- package/src/adapters/ele/search.yaml +11 -0
- package/src/adapters/exchangerate/convert.yaml +10 -0
- package/src/adapters/exchangerate/list.yaml +11 -0
- package/src/adapters/facebook/add-friend.yaml +10 -0
- package/src/adapters/facebook/events.yaml +10 -0
- package/src/adapters/facebook/feed.yaml +10 -0
- package/src/adapters/facebook/friends.yaml +10 -0
- package/src/adapters/facebook/groups.yaml +10 -0
- package/src/adapters/facebook/join-group.yaml +10 -0
- package/src/adapters/facebook/marketplace.yaml +10 -0
- package/src/adapters/facebook/memories.yaml +10 -0
- package/src/adapters/facebook/notifications.yaml +10 -0
- package/src/adapters/facebook/post.yaml +10 -0
- package/src/adapters/facebook/profile.yaml +10 -0
- package/src/adapters/facebook/search.yaml +10 -0
- package/src/adapters/feishu/calendar.yaml +10 -0
- package/src/adapters/feishu/docs.yaml +10 -0
- package/src/adapters/feishu/send.yaml +10 -0
- package/src/adapters/feishu/tasks.yaml +10 -0
- package/src/adapters/ffmpeg/compress.yaml +10 -0
- package/src/adapters/ffmpeg/concat.yaml +10 -0
- package/src/adapters/ffmpeg/convert.yaml +10 -0
- package/src/adapters/ffmpeg/extract-audio.yaml +10 -0
- package/src/adapters/ffmpeg/gif.yaml +10 -0
- package/src/adapters/ffmpeg/normalize.yaml +10 -0
- package/src/adapters/ffmpeg/probe.yaml +10 -0
- package/src/adapters/ffmpeg/resize.yaml +10 -0
- package/src/adapters/ffmpeg/subtitles.yaml +10 -0
- package/src/adapters/ffmpeg/thumbnail.yaml +10 -0
- package/src/adapters/ffmpeg/trim.yaml +10 -0
- package/src/adapters/figma/export-selected.yaml +72 -0
- package/src/adapters/flyctl/apps.yaml +10 -0
- package/src/adapters/freecad/assembly.yaml +10 -0
- package/src/adapters/freecad/bom.yaml +10 -0
- package/src/adapters/freecad/boolean.yaml +10 -0
- package/src/adapters/freecad/check.yaml +10 -0
- package/src/adapters/freecad/convert.yaml +10 -0
- package/src/adapters/freecad/export-stl.yaml +10 -0
- package/src/adapters/freecad/import.yaml +10 -0
- package/src/adapters/freecad/info.yaml +10 -0
- package/src/adapters/freecad/macro.yaml +10 -0
- package/src/adapters/freecad/measure.yaml +10 -0
- package/src/adapters/freecad/mesh.yaml +10 -0
- package/src/adapters/freecad/properties.yaml +10 -0
- package/src/adapters/freecad/render.yaml +10 -0
- package/src/adapters/freecad/section.yaml +10 -0
- package/src/adapters/freecad/sketch.yaml +10 -0
- package/src/adapters/futu/hot.yaml +10 -0
- package/src/adapters/futu/quote.yaml +10 -0
- package/src/adapters/gcloud/projects.yaml +11 -0
- package/src/adapters/gemini/ask.yaml +10 -0
- package/src/adapters/gemini/deep-research-result.yaml +10 -0
- package/src/adapters/gemini/deep-research.yaml +10 -0
- package/src/adapters/gemini/image.yaml +10 -0
- package/src/adapters/gemini/new.yaml +10 -0
- package/src/adapters/gh/issue.yaml +10 -0
- package/src/adapters/gh/pr.yaml +10 -0
- package/src/adapters/gh/release.yaml +10 -0
- package/src/adapters/gh/repo.yaml +10 -0
- package/src/adapters/gh/run.yaml +10 -0
- package/src/adapters/gimp/adjust.yaml +10 -0
- package/src/adapters/gimp/batch.yaml +10 -0
- package/src/adapters/gimp/convert.yaml +10 -0
- package/src/adapters/gimp/crop.yaml +10 -0
- package/src/adapters/gimp/filter.yaml +10 -0
- package/src/adapters/gimp/flip.yaml +10 -0
- package/src/adapters/gimp/info.yaml +10 -0
- package/src/adapters/gimp/layers.yaml +10 -0
- package/src/adapters/gimp/merge-layers.yaml +10 -0
- package/src/adapters/gimp/resize.yaml +10 -0
- package/src/adapters/gimp/rotate.yaml +10 -0
- package/src/adapters/gimp/text.yaml +10 -0
- package/src/adapters/gitee/repos.yaml +10 -0
- package/src/adapters/gitee/search.yaml +10 -0
- package/src/adapters/gitee/trending.yaml +11 -0
- package/src/adapters/github-trending/daily.test.ts +15 -0
- package/src/adapters/github-trending/daily.yaml +10 -0
- package/src/adapters/github-trending/developers.yaml +11 -0
- package/src/adapters/github-trending/weekly.yaml +11 -0
- package/src/adapters/gitlab/projects.yaml +10 -0
- package/src/adapters/gitlab/search.yaml +10 -0
- package/src/adapters/gitlab/trending.yaml +10 -0
- package/src/adapters/godot/project-run.yaml +10 -0
- package/src/adapters/godot/scene-export.yaml +10 -0
- package/src/adapters/google/news.yaml +10 -0
- package/src/adapters/google/search.yaml +10 -0
- package/src/adapters/google/suggest.yaml +10 -0
- package/src/adapters/google/trends.yaml +10 -0
- package/src/adapters/grok/ask.yaml +10 -0
- package/src/adapters/hackernews/ask.test.ts +15 -0
- package/src/adapters/hackernews/ask.yaml +10 -0
- package/src/adapters/hackernews/best.test.ts +15 -0
- package/src/adapters/hackernews/best.yaml +10 -0
- package/src/adapters/hackernews/comments.test.ts +15 -0
- package/src/adapters/hackernews/comments.yaml +10 -0
- package/src/adapters/hackernews/item.yaml +10 -0
- package/src/adapters/hackernews/jobs.test.ts +15 -0
- package/src/adapters/hackernews/jobs.yaml +10 -0
- package/src/adapters/hackernews/new.test.ts +15 -0
- package/src/adapters/hackernews/new.yaml +10 -0
- package/src/adapters/hackernews/search.test.ts +15 -0
- package/src/adapters/hackernews/search.yaml +10 -0
- package/src/adapters/hackernews/show.test.ts +15 -0
- package/src/adapters/hackernews/show.yaml +10 -0
- package/src/adapters/hackernews/top.test.ts +15 -0
- package/src/adapters/hackernews/top.yaml +10 -0
- package/src/adapters/hackernews/user.yaml +10 -0
- package/src/adapters/hermes/sessions-search.yaml +10 -0
- package/src/adapters/hermes/skills-list.yaml +10 -0
- package/src/adapters/hermes/skills-read.yaml +10 -0
- package/src/adapters/hf/datasets.yaml +10 -0
- package/src/adapters/hf/models.yaml +10 -0
- package/src/adapters/hf/spaces.yaml +10 -0
- package/src/adapters/hf/top.yaml +10 -0
- package/src/adapters/homebrew/info.yaml +10 -0
- package/src/adapters/homebrew/search.yaml +10 -0
- package/src/adapters/huggingface-papers/daily.yaml +10 -0
- package/src/adapters/huggingface-papers/search.yaml +10 -0
- package/src/adapters/hupu/detail.yaml +10 -0
- package/src/adapters/hupu/hot.yaml +10 -0
- package/src/adapters/hupu/like.yaml +10 -0
- package/src/adapters/hupu/mentions.yaml +10 -0
- package/src/adapters/hupu/reply.yaml +10 -0
- package/src/adapters/hupu/search.yaml +10 -0
- package/src/adapters/hupu/unlike.yaml +10 -0
- package/src/adapters/imagemagick/compare.yaml +10 -0
- package/src/adapters/imagemagick/composite.yaml +10 -0
- package/src/adapters/imagemagick/convert.yaml +10 -0
- package/src/adapters/imagemagick/identify.yaml +10 -0
- package/src/adapters/imagemagick/montage.yaml +10 -0
- package/src/adapters/imagemagick/resize.yaml +10 -0
- package/src/adapters/imdb/box-office.yaml +10 -0
- package/src/adapters/imdb/person.yaml +10 -0
- package/src/adapters/imdb/reviews.yaml +10 -0
- package/src/adapters/imdb/search.yaml +10 -0
- package/src/adapters/imdb/title.yaml +10 -0
- package/src/adapters/imdb/top.yaml +11 -0
- package/src/adapters/imdb/trending.yaml +10 -0
- package/src/adapters/imessage/contact.yaml +76 -0
- package/src/adapters/imessage/recent.yaml +69 -0
- package/src/adapters/imessage/search.yaml +78 -0
- package/src/adapters/infoq/articles.yaml +10 -0
- package/src/adapters/infoq/latest.yaml +10 -0
- package/src/adapters/inkscape/convert.yaml +10 -0
- package/src/adapters/inkscape/export.yaml +10 -0
- package/src/adapters/inkscape/optimize.yaml +10 -0
- package/src/adapters/instagram/activity.yaml +10 -0
- package/src/adapters/instagram/comment.yaml +10 -0
- package/src/adapters/instagram/explore.yaml +10 -0
- package/src/adapters/instagram/follow.yaml +10 -0
- package/src/adapters/instagram/followers.yaml +10 -0
- package/src/adapters/instagram/following.yaml +10 -0
- package/src/adapters/instagram/highlights.yaml +10 -0
- package/src/adapters/instagram/like.yaml +10 -0
- package/src/adapters/instagram/profile.yaml +10 -0
- package/src/adapters/instagram/reels-trending.yaml +10 -0
- package/src/adapters/instagram/reels.yaml +10 -0
- package/src/adapters/instagram/save.yaml +10 -0
- package/src/adapters/instagram/saved.yaml +10 -0
- package/src/adapters/instagram/search.yaml +10 -0
- package/src/adapters/instagram/stories.yaml +10 -0
- package/src/adapters/instagram/suggested.yaml +10 -0
- package/src/adapters/instagram/tags.yaml +10 -0
- package/src/adapters/instagram/unfollow.yaml +10 -0
- package/src/adapters/instagram/unlike.yaml +10 -0
- package/src/adapters/instagram/unsave.yaml +10 -0
- package/src/adapters/instagram/user.yaml +10 -0
- package/src/adapters/ip-info/lookup.yaml +10 -0
- package/src/adapters/itch-io/popular.yaml +11 -0
- package/src/adapters/itch-io/search.yaml +10 -0
- package/src/adapters/itch-io/top.yaml +11 -0
- package/src/adapters/ithome/hot.yaml +11 -0
- package/src/adapters/ithome/latest.yaml +10 -0
- package/src/adapters/ithome/news.yaml +10 -0
- package/src/adapters/jd/hot.yaml +11 -0
- package/src/adapters/jd/item.yaml +10 -0
- package/src/adapters/jd/search.yaml +10 -0
- package/src/adapters/jianyu/search.yaml +10 -0
- package/src/adapters/jike/feed.yaml +10 -0
- package/src/adapters/jike/notifications.yaml +10 -0
- package/src/adapters/jike/post.yaml +10 -0
- package/src/adapters/jike/search.yaml +10 -0
- package/src/adapters/jike/topic.yaml +10 -0
- package/src/adapters/jike/user.yaml +10 -0
- package/src/adapters/jimeng/generate.yaml +10 -0
- package/src/adapters/jimeng/history.yaml +10 -0
- package/src/adapters/jq/format.yaml +10 -0
- package/src/adapters/jq/query.yaml +10 -0
- package/src/adapters/kdenlive/effects.yaml +10 -0
- package/src/adapters/kdenlive/info.yaml +10 -0
- package/src/adapters/kdenlive/render.yaml +10 -0
- package/src/adapters/ke/ershoufang.yaml +10 -0
- package/src/adapters/ke/xiaoqu.yaml +10 -0
- package/src/adapters/krita/batch.yaml +10 -0
- package/src/adapters/krita/convert.yaml +10 -0
- package/src/adapters/krita/export.yaml +10 -0
- package/src/adapters/krita/info.yaml +10 -0
- package/src/adapters/kuaishou/hot.yaml +11 -0
- package/src/adapters/kuaishou/search.yaml +11 -0
- package/src/adapters/lark/version.yaml +10 -0
- package/src/adapters/lesswrong/comments.test.ts +15 -0
- package/src/adapters/lesswrong/comments.yaml +10 -0
- package/src/adapters/lesswrong/curated.test.ts +15 -0
- package/src/adapters/lesswrong/curated.yaml +10 -0
- package/src/adapters/lesswrong/frontpage.test.ts +15 -0
- package/src/adapters/lesswrong/frontpage.yaml +10 -0
- package/src/adapters/lesswrong/new.test.ts +15 -0
- package/src/adapters/lesswrong/new.yaml +10 -0
- package/src/adapters/lesswrong/read.test.ts +23 -0
- package/src/adapters/lesswrong/read.yaml +10 -0
- package/src/adapters/lesswrong/sequences.test.ts +15 -0
- package/src/adapters/lesswrong/sequences.yaml +10 -0
- package/src/adapters/lesswrong/shortform.test.ts +15 -0
- package/src/adapters/lesswrong/shortform.yaml +10 -0
- package/src/adapters/lesswrong/tag.yaml +10 -0
- package/src/adapters/lesswrong/tags.test.ts +15 -0
- package/src/adapters/lesswrong/tags.yaml +10 -0
- package/src/adapters/lesswrong/top-month.test.ts +15 -0
- package/src/adapters/lesswrong/top-month.yaml +10 -0
- package/src/adapters/lesswrong/top-week.test.ts +15 -0
- package/src/adapters/lesswrong/top-week.yaml +10 -0
- package/src/adapters/lesswrong/top-year.test.ts +15 -0
- package/src/adapters/lesswrong/top-year.yaml +10 -0
- package/src/adapters/lesswrong/top.test.ts +15 -0
- package/src/adapters/lesswrong/top.yaml +10 -0
- package/src/adapters/lesswrong/user-posts.yaml +10 -0
- package/src/adapters/lesswrong/user.yaml +10 -0
- package/src/adapters/libreoffice/convert.yaml +10 -0
- package/src/adapters/libreoffice/print.yaml +10 -0
- package/src/adapters/linear/issue-create.test.ts +15 -0
- package/src/adapters/linear/issue-create.yaml +101 -0
- package/src/adapters/linear/issue-list.yaml +90 -0
- package/src/adapters/linear/issue-update.test.ts +15 -0
- package/src/adapters/linear/issue-update.yaml +103 -0
- package/src/adapters/linkedin/jobs.yaml +10 -0
- package/src/adapters/linkedin/profile.yaml +10 -0
- package/src/adapters/linkedin/search.yaml +10 -0
- package/src/adapters/linkedin/timeline.yaml +10 -0
- package/src/adapters/linux-do/categories.yaml +10 -0
- package/src/adapters/linux-do/category.yaml +10 -0
- package/src/adapters/linux-do/feed.yaml +10 -0
- package/src/adapters/linux-do/hot.yaml +10 -0
- package/src/adapters/linux-do/latest.yaml +10 -0
- package/src/adapters/linux-do/search.yaml +10 -0
- package/src/adapters/linux-do/tags.yaml +10 -0
- package/src/adapters/linux-do/topic.yaml +10 -0
- package/src/adapters/linux-do/user-posts.yaml +10 -0
- package/src/adapters/linux-do/user-topics.yaml +10 -0
- package/src/adapters/lobsters/active.yaml +10 -0
- package/src/adapters/lobsters/hot.yaml +10 -0
- package/src/adapters/lobsters/newest.yaml +10 -0
- package/src/adapters/lobsters/search.yaml +10 -0
- package/src/adapters/lobsters/tag.yaml +10 -0
- package/src/adapters/macos/active-app.yaml +10 -0
- package/src/adapters/macos/apps-list.yaml +10 -0
- package/src/adapters/macos/apps.yaml +10 -0
- package/src/adapters/macos/battery.yaml +10 -0
- package/src/adapters/macos/bluetooth.yaml +10 -0
- package/src/adapters/macos/brightness.yaml +10 -0
- package/src/adapters/macos/caffeinate.yaml +10 -0
- package/src/adapters/macos/calendar-create.yaml +10 -0
- package/src/adapters/macos/calendar-list.yaml +10 -0
- package/src/adapters/macos/calendar-today.yaml +10 -0
- package/src/adapters/macos/clipboard.yaml +10 -0
- package/src/adapters/macos/contacts-search.yaml +10 -0
- package/src/adapters/macos/dark-mode.yaml +10 -0
- package/src/adapters/macos/disk-info.yaml +10 -0
- package/src/adapters/macos/disk-usage.yaml +10 -0
- package/src/adapters/macos/do-not-disturb.yaml +10 -0
- package/src/adapters/macos/empty-trash.yaml +10 -0
- package/src/adapters/macos/finder-copy.yaml +10 -0
- package/src/adapters/macos/finder-move.yaml +10 -0
- package/src/adapters/macos/finder-new-folder.yaml +10 -0
- package/src/adapters/macos/finder-recent.yaml +10 -0
- package/src/adapters/macos/finder-selection.yaml +10 -0
- package/src/adapters/macos/finder-tags.yaml +10 -0
- package/src/adapters/macos/lock-screen.yaml +10 -0
- package/src/adapters/macos/mail-send.yaml +10 -0
- package/src/adapters/macos/mail-status.yaml +10 -0
- package/src/adapters/macos/messages-send.yaml +10 -0
- package/src/adapters/macos/music-control.yaml +10 -0
- package/src/adapters/macos/music-now.yaml +10 -0
- package/src/adapters/macos/notes-list.yaml +10 -0
- package/src/adapters/macos/notes-search.yaml +10 -0
- package/src/adapters/macos/notification.yaml +10 -0
- package/src/adapters/macos/notify.yaml +10 -0
- package/src/adapters/macos/open-app.yaml +10 -0
- package/src/adapters/macos/open.yaml +10 -0
- package/src/adapters/macos/photos-search.yaml +10 -0
- package/src/adapters/macos/processes.yaml +10 -0
- package/src/adapters/macos/reminder-create.yaml +10 -0
- package/src/adapters/macos/reminders-complete.yaml +10 -0
- package/src/adapters/macos/reminders-list.yaml +10 -0
- package/src/adapters/macos/safari-history.yaml +10 -0
- package/src/adapters/macos/safari-tabs.yaml +10 -0
- package/src/adapters/macos/safari-url.yaml +10 -0
- package/src/adapters/macos/say.yaml +10 -0
- package/src/adapters/macos/screen-lock.yaml +10 -0
- package/src/adapters/macos/screen-recording.yaml +10 -0
- package/src/adapters/macos/screenshot.yaml +10 -0
- package/src/adapters/macos/shortcuts-list.yaml +10 -0
- package/src/adapters/macos/shortcuts-run.yaml +10 -0
- package/src/adapters/macos/sleep.yaml +10 -0
- package/src/adapters/macos/spotlight.yaml +10 -0
- package/src/adapters/macos/system-info.yaml +10 -0
- package/src/adapters/macos/trash.yaml +10 -0
- package/src/adapters/macos/uptime.yaml +10 -0
- package/src/adapters/macos/volume.yaml +10 -0
- package/src/adapters/macos/wallpaper.yaml +10 -0
- package/src/adapters/macos/wifi-info.yaml +10 -0
- package/src/adapters/macos/wifi.yaml +10 -0
- package/src/adapters/maimai/search.yaml +10 -0
- package/src/adapters/maoyan/hot.yaml +10 -0
- package/src/adapters/maoyan/search.yaml +10 -0
- package/src/adapters/mastodon/search.yaml +10 -0
- package/src/adapters/mastodon/timeline.yaml +11 -0
- package/src/adapters/mastodon/trending.yaml +10 -0
- package/src/adapters/mastodon/user.yaml +10 -0
- package/src/adapters/medium/article.yaml +10 -0
- package/src/adapters/medium/feed.yaml +10 -0
- package/src/adapters/medium/search.yaml +10 -0
- package/src/adapters/medium/trending.yaml +10 -0
- package/src/adapters/medium/user.yaml +10 -0
- package/src/adapters/meituan/hot.yaml +10 -0
- package/src/adapters/meituan/search.yaml +10 -0
- package/src/adapters/mermaid/render.yaml +10 -0
- package/src/adapters/minimax/chat.yaml +10 -0
- package/src/adapters/minimax/models.yaml +10 -0
- package/src/adapters/minimax/tts.yaml +10 -0
- package/src/adapters/motion-studio/component-get.yaml +10 -0
- package/src/adapters/mubu/list.yaml +10 -0
- package/src/adapters/mubu/search.yaml +17 -0
- package/src/adapters/musescore/convert.yaml +10 -0
- package/src/adapters/musescore/export.yaml +10 -0
- package/src/adapters/musescore/info.yaml +10 -0
- package/src/adapters/musescore/instruments.yaml +10 -0
- package/src/adapters/musescore/transpose.yaml +10 -0
- package/src/adapters/neonctl/projects.yaml +10 -0
- package/src/adapters/netease-music/hot.yaml +11 -0
- package/src/adapters/netease-music/playlist.yaml +10 -0
- package/src/adapters/netease-music/search.yaml +10 -0
- package/src/adapters/netease-music/top.yaml +11 -0
- package/src/adapters/netlify/sites.yaml +10 -0
- package/src/adapters/notebooklm/current.yaml +10 -0
- package/src/adapters/notebooklm/get.yaml +10 -0
- package/src/adapters/notebooklm/history.yaml +10 -0
- package/src/adapters/notebooklm/list.yaml +10 -0
- package/src/adapters/notebooklm/note-list.yaml +10 -0
- package/src/adapters/notebooklm/notes-get.yaml +10 -0
- package/src/adapters/notebooklm/open.yaml +10 -0
- package/src/adapters/notebooklm/rpc.yaml +10 -0
- package/src/adapters/notebooklm/shared.yaml +10 -0
- package/src/adapters/notebooklm/source-fulltext.yaml +10 -0
- package/src/adapters/notebooklm/source-get.yaml +10 -0
- package/src/adapters/notebooklm/source-guide.yaml +10 -0
- package/src/adapters/notebooklm/source-list.yaml +10 -0
- package/src/adapters/notebooklm/status.yaml +10 -0
- package/src/adapters/notebooklm/summary.yaml +10 -0
- package/src/adapters/notion/databases.yaml +10 -0
- package/src/adapters/notion/pages.yaml +10 -0
- package/src/adapters/notion/search.yaml +10 -0
- package/src/adapters/novita/generate.yaml +10 -0
- package/src/adapters/novita/models.yaml +10 -0
- package/src/adapters/novita/status.yaml +10 -0
- package/src/adapters/npm/downloads.yaml +10 -0
- package/src/adapters/npm/info.yaml +10 -0
- package/src/adapters/npm/search.yaml +10 -0
- package/src/adapters/npm/versions.yaml +10 -0
- package/src/adapters/npm-trends/compare.yaml +10 -0
- package/src/adapters/npm-trends/trending.yaml +10 -0
- package/src/adapters/nytimes/search.yaml +10 -0
- package/src/adapters/nytimes/top.yaml +10 -0
- package/src/adapters/obs/record-start.yaml +10 -0
- package/src/adapters/obs/record-stop.yaml +10 -0
- package/src/adapters/obs/scenes.yaml +10 -0
- package/src/adapters/obs/screenshot.yaml +10 -0
- package/src/adapters/obs/sources.yaml +10 -0
- package/src/adapters/obs/status.yaml +10 -0
- package/src/adapters/obs/stream-start.yaml +10 -0
- package/src/adapters/obs/stream-stop.yaml +10 -0
- package/src/adapters/obsidian/daily.yaml +10 -0
- package/src/adapters/obsidian/open.yaml +10 -0
- package/src/adapters/obsidian/search.yaml +10 -0
- package/src/adapters/ollama/generate.yaml +10 -0
- package/src/adapters/ollama/list.yaml +10 -0
- package/src/adapters/ollama/models.yaml +11 -0
- package/src/adapters/ollama/ps.yaml +10 -0
- package/src/adapters/ones/enrich-tasks.yaml +10 -0
- package/src/adapters/ones/login.yaml +10 -0
- package/src/adapters/ones/logout.yaml +10 -0
- package/src/adapters/ones/me.yaml +10 -0
- package/src/adapters/ones/my-tasks.yaml +10 -0
- package/src/adapters/ones/resolve-labels.yaml +10 -0
- package/src/adapters/ones/task-helpers.yaml +10 -0
- package/src/adapters/ones/task.yaml +10 -0
- package/src/adapters/ones/tasks.yaml +10 -0
- package/src/adapters/ones/token-info.yaml +10 -0
- package/src/adapters/ones/worklog.yaml +10 -0
- package/src/adapters/opencode/version.yaml +10 -0
- package/src/adapters/openharness/memory-read.yaml +10 -0
- package/src/adapters/openharness/skills-list.yaml +10 -0
- package/src/adapters/openrouter/models.yaml +10 -0
- package/src/adapters/openrouter/search.yaml +10 -0
- package/src/adapters/pandoc/convert.yaml +10 -0
- package/src/adapters/paperreview/feedback.yaml +10 -0
- package/src/adapters/paperreview/review.yaml +10 -0
- package/src/adapters/paperreview/submit.yaml +10 -0
- package/src/adapters/perplexity/ask.yaml +10 -0
- package/src/adapters/pexels/curated.yaml +10 -0
- package/src/adapters/pexels/search.yaml +10 -0
- package/src/adapters/pinduoduo/hot.yaml +10 -0
- package/src/adapters/pinduoduo/search.yaml +10 -0
- package/src/adapters/pixiv/detail.yaml +10 -0
- package/src/adapters/pixiv/download.yaml +10 -0
- package/src/adapters/pixiv/illusts.yaml +10 -0
- package/src/adapters/pixiv/ranking.yaml +10 -0
- package/src/adapters/pixiv/search.yaml +10 -0
- package/src/adapters/pixiv/user.yaml +10 -0
- package/src/adapters/producthunt/browse.yaml +10 -0
- package/src/adapters/producthunt/hot.yaml +10 -0
- package/src/adapters/producthunt/posts.yaml +10 -0
- package/src/adapters/producthunt/search.yaml +10 -0
- package/src/adapters/producthunt/today.yaml +10 -0
- package/src/adapters/pscale/databases.yaml +10 -0
- package/src/adapters/pypi/info.yaml +10 -0
- package/src/adapters/pypi/search.yaml +10 -0
- package/src/adapters/pypi/versions.yaml +10 -0
- package/src/adapters/quark/ls.yaml +10 -0
- package/src/adapters/quark/search.yaml +10 -0
- package/src/adapters/qweather/forecast.yaml +10 -0
- package/src/adapters/qweather/now.yaml +10 -0
- package/src/adapters/railway/deploy.yaml +10 -0
- package/src/adapters/reddit/comment.yaml +10 -0
- package/src/adapters/reddit/comments.test.ts +15 -0
- package/src/adapters/reddit/comments.yaml +10 -0
- package/src/adapters/reddit/frontpage.test.ts +15 -0
- package/src/adapters/reddit/frontpage.yaml +10 -0
- package/src/adapters/reddit/hot.test.ts +15 -0
- package/src/adapters/reddit/hot.yaml +10 -0
- package/src/adapters/reddit/new.test.ts +15 -0
- package/src/adapters/reddit/new.yaml +10 -0
- package/src/adapters/reddit/popular.test.ts +15 -0
- package/src/adapters/reddit/popular.yaml +10 -0
- package/src/adapters/reddit/read.test.ts +15 -0
- package/src/adapters/reddit/read.yaml +10 -0
- package/src/adapters/reddit/rising.test.ts +15 -0
- package/src/adapters/reddit/rising.yaml +10 -0
- package/src/adapters/reddit/save.yaml +10 -0
- package/src/adapters/reddit/saved.test.ts +15 -0
- package/src/adapters/reddit/saved.yaml +10 -0
- package/src/adapters/reddit/search.test.ts +15 -0
- package/src/adapters/reddit/search.yaml +10 -0
- package/src/adapters/reddit/subreddit.test.ts +15 -0
- package/src/adapters/reddit/subreddit.yaml +10 -0
- package/src/adapters/reddit/subscribe.yaml +10 -0
- package/src/adapters/reddit/top.test.ts +15 -0
- package/src/adapters/reddit/top.yaml +10 -0
- package/src/adapters/reddit/trending.test.ts +15 -0
- package/src/adapters/reddit/trending.yaml +10 -0
- package/src/adapters/reddit/upvote.yaml +10 -0
- package/src/adapters/reddit/upvoted.test.ts +15 -0
- package/src/adapters/reddit/upvoted.yaml +10 -0
- package/src/adapters/reddit/user-comments.test.ts +15 -0
- package/src/adapters/reddit/user-comments.yaml +10 -0
- package/src/adapters/reddit/user-posts.test.ts +15 -0
- package/src/adapters/reddit/user-posts.yaml +10 -0
- package/src/adapters/reddit/user.test.ts +21 -0
- package/src/adapters/reddit/user.yaml +10 -0
- package/src/adapters/renderdoc/capture-list.yaml +10 -0
- package/src/adapters/renderdoc/frame-export.yaml +10 -0
- package/src/adapters/replicate/run.yaml +10 -0
- package/src/adapters/replicate/search.yaml +10 -0
- package/src/adapters/replicate/trending.yaml +11 -0
- package/src/adapters/reuters/article.yaml +10 -0
- package/src/adapters/reuters/latest.yaml +11 -0
- package/src/adapters/reuters/search.yaml +10 -0
- package/src/adapters/reuters/top.yaml +11 -0
- package/src/adapters/shotcut/effects.yaml +10 -0
- package/src/adapters/shotcut/info.yaml +10 -0
- package/src/adapters/shotcut/render.yaml +10 -0
- package/src/adapters/sinablog/article.yaml +10 -0
- package/src/adapters/sinablog/hot.yaml +10 -0
- package/src/adapters/sinablog/search.yaml +10 -0
- package/src/adapters/sinablog/user.yaml +10 -0
- package/src/adapters/sinafinance/market.yaml +10 -0
- package/src/adapters/sinafinance/news.yaml +10 -0
- package/src/adapters/sinafinance/rolling-news.yaml +10 -0
- package/src/adapters/sinafinance/stock-rank.yaml +10 -0
- package/src/adapters/sinafinance/stock.yaml +10 -0
- package/src/adapters/sketch/artboards.yaml +10 -0
- package/src/adapters/sketch/export.yaml +10 -0
- package/src/adapters/sketch/symbols.yaml +10 -0
- package/src/adapters/slack/channels.yaml +10 -0
- package/src/adapters/slack/messages.yaml +10 -0
- package/src/adapters/slack/post.yaml +10 -0
- package/src/adapters/slack/search.yaml +10 -0
- package/src/adapters/slack/send.yaml +10 -0
- package/src/adapters/slack/status.yaml +10 -0
- package/src/adapters/slack/users.yaml +10 -0
- package/src/adapters/slay-the-spire-ii/deck.yaml +10 -0
- package/src/adapters/slay-the-spire-ii/end-turn.yaml +10 -0
- package/src/adapters/slay-the-spire-ii/map.yaml +10 -0
- package/src/adapters/slay-the-spire-ii/play-card.yaml +10 -0
- package/src/adapters/slay-the-spire-ii/status.yaml +10 -0
- package/src/adapters/slay-the-spire-ii/use-potion.yaml +10 -0
- package/src/adapters/slock/servers.yaml +10 -0
- package/src/adapters/smzdm/article.yaml +10 -0
- package/src/adapters/smzdm/hot.yaml +10 -0
- package/src/adapters/smzdm/search.yaml +10 -0
- package/src/adapters/spotify/now-playing.yaml +10 -0
- package/src/adapters/spotify/playlists.yaml +10 -0
- package/src/adapters/spotify/search.yaml +10 -0
- package/src/adapters/spotify/top-tracks.yaml +10 -0
- package/src/adapters/sspai/hot.yaml +10 -0
- package/src/adapters/sspai/latest.yaml +10 -0
- package/src/adapters/stackoverflow/bounties.yaml +10 -0
- package/src/adapters/stackoverflow/hot.yaml +10 -0
- package/src/adapters/stackoverflow/question.yaml +10 -0
- package/src/adapters/stackoverflow/search.yaml +10 -0
- package/src/adapters/stackoverflow/tags.yaml +10 -0
- package/src/adapters/stackoverflow/unanswered.yaml +10 -0
- package/src/adapters/stagehand/wrap-observe.yaml +10 -0
- package/src/adapters/steam/app-details.yaml +10 -0
- package/src/adapters/steam/new-releases.yaml +10 -0
- package/src/adapters/steam/search.yaml +10 -0
- package/src/adapters/steam/specials.yaml +10 -0
- package/src/adapters/steam/top-sellers.yaml +10 -0
- package/src/adapters/steam/wishlist.yaml +10 -0
- package/src/adapters/substack/feed.yaml +10 -0
- package/src/adapters/substack/publication.yaml +10 -0
- package/src/adapters/substack/search.yaml +10 -0
- package/src/adapters/substack/trending.yaml +10 -0
- package/src/adapters/supabase/projects.yaml +10 -0
- package/src/adapters/taobao/hot.yaml +10 -0
- package/src/adapters/taobao/search.yaml +10 -0
- package/src/adapters/techcrunch/latest.yaml +10 -0
- package/src/adapters/techcrunch/search.yaml +10 -0
- package/src/adapters/theverge/latest.yaml +10 -0
- package/src/adapters/theverge/search.yaml +10 -0
- package/src/adapters/threads/hot.yaml +11 -0
- package/src/adapters/threads/search.yaml +11 -0
- package/src/adapters/tieba/hot.yaml +11 -0
- package/src/adapters/tieba/posts.yaml +10 -0
- package/src/adapters/tieba/read.yaml +10 -0
- package/src/adapters/tieba/search.yaml +10 -0
- package/src/adapters/tiktok/comment.yaml +10 -0
- package/src/adapters/tiktok/explore.yaml +10 -0
- package/src/adapters/tiktok/follow.yaml +10 -0
- package/src/adapters/tiktok/following.yaml +10 -0
- package/src/adapters/tiktok/friends.yaml +10 -0
- package/src/adapters/tiktok/like.yaml +10 -0
- package/src/adapters/tiktok/live.yaml +10 -0
- package/src/adapters/tiktok/notifications.yaml +10 -0
- package/src/adapters/tiktok/profile.yaml +10 -0
- package/src/adapters/tiktok/save.yaml +10 -0
- package/src/adapters/tiktok/search.yaml +10 -0
- package/src/adapters/tiktok/trending.yaml +10 -0
- package/src/adapters/tiktok/unfollow.yaml +10 -0
- package/src/adapters/tiktok/unlike.yaml +10 -0
- package/src/adapters/tiktok/unsave.yaml +10 -0
- package/src/adapters/tiktok/user.yaml +10 -0
- package/src/adapters/toutiao/hot.yaml +10 -0
- package/src/adapters/toutiao/search.yaml +11 -0
- package/src/adapters/twitch/games.yaml +10 -0
- package/src/adapters/twitch/search.yaml +10 -0
- package/src/adapters/twitch/streams.yaml +10 -0
- package/src/adapters/twitch/top.yaml +10 -0
- package/src/adapters/twitter/lists.yaml +10 -0
- package/src/adapters/twitter/media.yaml +10 -0
- package/src/adapters/twitter/mentions.yaml +10 -0
- package/src/adapters/twitter/mute.yaml +10 -0
- package/src/adapters/twitter/pin.yaml +10 -0
- package/src/adapters/twitter/quotes.yaml +10 -0
- package/src/adapters/twitter/retweets.yaml +10 -0
- package/src/adapters/twitter/spaces.yaml +10 -0
- package/src/adapters/twitter/unmute.yaml +10 -0
- package/src/adapters/unsplash/random.yaml +10 -0
- package/src/adapters/unsplash/search.yaml +10 -0
- package/src/adapters/v2ex/daily.yaml +10 -0
- package/src/adapters/v2ex/hot.yaml +10 -0
- package/src/adapters/v2ex/latest.yaml +10 -0
- package/src/adapters/v2ex/me.test.ts +15 -0
- package/src/adapters/v2ex/me.yaml +10 -0
- package/src/adapters/v2ex/member.yaml +10 -0
- package/src/adapters/v2ex/node.yaml +10 -0
- package/src/adapters/v2ex/nodes.yaml +10 -0
- package/src/adapters/v2ex/notifications.test.ts +15 -0
- package/src/adapters/v2ex/notifications.yaml +10 -0
- package/src/adapters/v2ex/replies.yaml +10 -0
- package/src/adapters/v2ex/search.test.ts +15 -0
- package/src/adapters/v2ex/search.yaml +10 -0
- package/src/adapters/v2ex/topic.yaml +10 -0
- package/src/adapters/v2ex/user.yaml +10 -0
- package/src/adapters/vercel/list.yaml +10 -0
- package/src/adapters/vscode/extensions.yaml +10 -0
- package/src/adapters/vscode/install-ext.yaml +10 -0
- package/src/adapters/vscode/open.yaml +10 -0
- package/src/adapters/web/read.yaml +10 -0
- package/src/adapters/wechat-channels/hot.yaml +11 -0
- package/src/adapters/wechat-channels/search.yaml +18 -0
- package/src/adapters/weibo/comments.yaml +10 -0
- package/src/adapters/weibo/feed.yaml +10 -0
- package/src/adapters/weibo/hot.yaml +10 -0
- package/src/adapters/weibo/me.yaml +10 -0
- package/src/adapters/weibo/post.yaml +10 -0
- package/src/adapters/weibo/profile.yaml +10 -0
- package/src/adapters/weibo/search.yaml +10 -0
- package/src/adapters/weibo/timeline.yaml +10 -0
- package/src/adapters/weibo/trending.yaml +10 -0
- package/src/adapters/weibo/user.yaml +10 -0
- package/src/adapters/weixin/article.yaml +10 -0
- package/src/adapters/weixin/download.yaml +10 -0
- package/src/adapters/weixin/hot.yaml +10 -0
- package/src/adapters/weixin/search.yaml +10 -0
- package/src/adapters/weread/book.yaml +10 -0
- package/src/adapters/weread/highlights.yaml +10 -0
- package/src/adapters/weread/notebooks.yaml +10 -0
- package/src/adapters/weread/notes.yaml +10 -0
- package/src/adapters/weread/ranking.yaml +10 -0
- package/src/adapters/weread/search.yaml +10 -0
- package/src/adapters/weread/shelf.yaml +10 -0
- package/src/adapters/wikipedia/random.yaml +10 -0
- package/src/adapters/wikipedia/search.yaml +10 -0
- package/src/adapters/wikipedia/summary.yaml +10 -0
- package/src/adapters/wikipedia/today.yaml +11 -0
- package/src/adapters/wikipedia/trending.yaml +10 -0
- package/src/adapters/wiremock/create-stub.yaml +10 -0
- package/src/adapters/wiremock/delete-stub.yaml +10 -0
- package/src/adapters/wiremock/reset.yaml +10 -0
- package/src/adapters/wiremock/stubs.yaml +10 -0
- package/src/adapters/wiremock/verify.yaml +10 -0
- package/src/adapters/wrangler/list.yaml +10 -0
- package/src/adapters/xianyu/chat.yaml +10 -0
- package/src/adapters/xianyu/item.yaml +10 -0
- package/src/adapters/xianyu/search.yaml +11 -0
- package/src/adapters/xiaoe/catalog.yaml +10 -0
- package/src/adapters/xiaoe/content.yaml +10 -0
- package/src/adapters/xiaoe/courses.yaml +10 -0
- package/src/adapters/xiaoe/detail.yaml +10 -0
- package/src/adapters/xiaoe/play-url.yaml +10 -0
- package/src/adapters/xiaohongshu/feed.yaml +10 -0
- package/src/adapters/xiaohongshu/follow.yaml +10 -0
- package/src/adapters/xiaohongshu/hashtag.yaml +10 -0
- package/src/adapters/xiaohongshu/hot.yaml +10 -0
- package/src/adapters/xiaohongshu/like.yaml +10 -0
- package/src/adapters/xiaohongshu/notifications.yaml +10 -0
- package/src/adapters/xiaohongshu/profile.yaml +10 -0
- package/src/adapters/xiaohongshu/save.yaml +10 -0
- package/src/adapters/xiaohongshu/suggest.yaml +10 -0
- package/src/adapters/xiaohongshu/trending.yaml +10 -0
- package/src/adapters/xiaohongshu/unfollow.yaml +10 -0
- package/src/adapters/xiaoyuzhou/episode.yaml +10 -0
- package/src/adapters/xiaoyuzhou/podcast-episodes.yaml +10 -0
- package/src/adapters/xiaoyuzhou/podcast.yaml +10 -0
- package/src/adapters/xueqiu/comments.yaml +10 -0
- package/src/adapters/xueqiu/earnings-date.yaml +10 -0
- package/src/adapters/xueqiu/feed.yaml +10 -0
- package/src/adapters/xueqiu/fund-holdings.yaml +10 -0
- package/src/adapters/xueqiu/fund-snapshot.yaml +10 -0
- package/src/adapters/xueqiu/hot-stock.yaml +10 -0
- package/src/adapters/xueqiu/hot.yaml +10 -0
- package/src/adapters/xueqiu/market.yaml +10 -0
- package/src/adapters/xueqiu/quote.yaml +10 -0
- package/src/adapters/xueqiu/search.yaml +10 -0
- package/src/adapters/xueqiu/stock.yaml +10 -0
- package/src/adapters/xueqiu/watchlist.yaml +10 -0
- package/src/adapters/yahoo-finance/quote.yaml +10 -0
- package/src/adapters/yahoo-finance/search.yaml +10 -0
- package/src/adapters/yahoo-finance/trending.yaml +11 -0
- package/src/adapters/ycombinator/launches.yaml +10 -0
- package/src/adapters/yollomi/background.yaml +10 -0
- package/src/adapters/yollomi/edit.yaml +10 -0
- package/src/adapters/yollomi/face-swap.yaml +10 -0
- package/src/adapters/yollomi/generate.yaml +10 -0
- package/src/adapters/yollomi/models.yaml +10 -0
- package/src/adapters/yollomi/object-remover.yaml +10 -0
- package/src/adapters/yollomi/remove-bg.yaml +10 -0
- package/src/adapters/yollomi/restore.yaml +10 -0
- package/src/adapters/yollomi/try-on.yaml +10 -0
- package/src/adapters/yollomi/upload.yaml +10 -0
- package/src/adapters/yollomi/upscale.yaml +10 -0
- package/src/adapters/yollomi/video.yaml +10 -0
- package/src/adapters/youtube/playlist.yaml +10 -0
- package/src/adapters/youtube/shorts.yaml +10 -0
- package/src/adapters/youtube/trending.yaml +10 -0
- package/src/adapters/yt-dlp/download.yaml +10 -0
- package/src/adapters/yt-dlp/extract-audio.yaml +10 -0
- package/src/adapters/yt-dlp/info.yaml +10 -0
- package/src/adapters/yt-dlp/search.yaml +10 -0
- package/src/adapters/yuanbao/ask.yaml +10 -0
- package/src/adapters/yuanbao/new.yaml +10 -0
- package/src/adapters/yuanbao/shared.yaml +10 -0
- package/src/adapters/zhihu/answer.yaml +10 -0
- package/src/adapters/zhihu/answers.test.ts +15 -0
- package/src/adapters/zhihu/answers.yaml +10 -0
- package/src/adapters/zhihu/article.yaml +10 -0
- package/src/adapters/zhihu/articles.test.ts +15 -0
- package/src/adapters/zhihu/articles.yaml +10 -0
- package/src/adapters/zhihu/collections.test.ts +15 -0
- package/src/adapters/zhihu/collections.yaml +10 -0
- package/src/adapters/zhihu/columns.test.ts +15 -0
- package/src/adapters/zhihu/columns.yaml +10 -0
- package/src/adapters/zhihu/comment.test.ts +15 -0
- package/src/adapters/zhihu/comment.yaml +10 -0
- package/src/adapters/zhihu/download.yaml +10 -0
- package/src/adapters/zhihu/feed.test.ts +15 -0
- package/src/adapters/zhihu/feed.yaml +10 -0
- package/src/adapters/zhihu/followers.test.ts +15 -0
- package/src/adapters/zhihu/followers.yaml +10 -0
- package/src/adapters/zhihu/following.test.ts +15 -0
- package/src/adapters/zhihu/following.yaml +10 -0
- package/src/adapters/zhihu/hot.test.ts +15 -0
- package/src/adapters/zhihu/hot.yaml +10 -0
- package/src/adapters/zhihu/me.yaml +10 -0
- package/src/adapters/zhihu/notifications.test.ts +15 -0
- package/src/adapters/zhihu/notifications.yaml +10 -0
- package/src/adapters/zhihu/pins.test.ts +15 -0
- package/src/adapters/zhihu/pins.yaml +10 -0
- package/src/adapters/zhihu/question.test.ts +15 -0
- package/src/adapters/zhihu/question.yaml +10 -0
- package/src/adapters/zhihu/search.test.ts +15 -0
- package/src/adapters/zhihu/search.yaml +10 -0
- package/src/adapters/zhihu/topic.test.ts +15 -0
- package/src/adapters/zhihu/topic.yaml +10 -0
- package/src/adapters/zhihu/topics.test.ts +15 -0
- package/src/adapters/zhihu/topics.yaml +10 -0
- package/src/adapters/zhihu/trending.test.ts +15 -0
- package/src/adapters/zhihu/trending.yaml +10 -0
- package/src/adapters/zhihu/user.yaml +10 -0
- package/src/adapters/zoom/join.yaml +10 -0
- package/src/adapters/zoom/start.yaml +10 -0
- package/src/adapters/zoom/toggle-mute.yaml +40 -0
- package/src/adapters/zotero/add-note.yaml +10 -0
- package/src/adapters/zotero/add-tag.yaml +10 -0
- package/src/adapters/zotero/collections.yaml +10 -0
- package/src/adapters/zotero/export.yaml +10 -0
- package/src/adapters/zotero/items.yaml +10 -0
- package/src/adapters/zotero/notes.yaml +10 -0
- package/src/adapters/zotero/search.yaml +10 -0
- package/src/adapters/zotero/tags.yaml +10 -0
- package/src/adapters/zsxq/dynamics.yaml +10 -0
- package/src/adapters/zsxq/groups.yaml +10 -0
- package/src/adapters/zsxq/search.yaml +10 -0
- package/src/adapters/zsxq/topic.yaml +10 -0
- package/src/adapters/zsxq/topics.yaml +10 -0
|
@@ -1,2028 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
import { execFile } from "node:child_process";
|
|
18
|
-
import { existsSync, mkdirSync, writeFileSync, rmSync, readFileSync, } from "node:fs";
|
|
19
|
-
import { stat } from "node:fs/promises";
|
|
20
|
-
import { join, resolve } from "node:path";
|
|
21
|
-
import { tmpdir, homedir } from "node:os";
|
|
22
|
-
import { randomBytes, createHash } from "node:crypto";
|
|
23
|
-
import { promisify } from "node:util";
|
|
24
|
-
import { runInNewContext } from "node:vm";
|
|
25
|
-
import TurndownService from "turndown";
|
|
26
|
-
import { USER_AGENT } from "../constants.js";
|
|
27
|
-
import { formatCookieHeader, loadCookiesWithCDP } from "./cookies.js";
|
|
28
|
-
import { matchSensitivePathRealpath, buildSensitivePathDenial, } from "../permissions/sensitive-paths.js";
|
|
29
|
-
import { generateInterceptorJs, generateReadInterceptedJs, } from "./interceptor.js";
|
|
30
|
-
import { httpDownload, ytdlpDownload, requiresYtdlp, sanitizeFilename, generateFilename, mapConcurrent, } from "./download.js";
|
|
31
|
-
import { executeWebsocket } from "./websocket.js";
|
|
32
|
-
import { getProxyAgent } from "./proxy.js";
|
|
33
|
-
const execFileAsync = promisify(execFile);
|
|
34
|
-
/**
|
|
35
|
-
* Structured pipeline error — designed for AI agent consumption.
|
|
36
|
-
* An agent receiving this error can read the adapter YAML, understand
|
|
37
|
-
* exactly what failed, and edit the file to fix it.
|
|
38
|
-
*/
|
|
39
|
-
export class PipelineError extends Error {
|
|
40
|
-
detail;
|
|
41
|
-
constructor(message, detail) {
|
|
42
|
-
super(message);
|
|
43
|
-
this.detail = detail;
|
|
44
|
-
this.name = "PipelineError";
|
|
45
|
-
}
|
|
46
|
-
/** JSON output for AI agents — includes everything needed to self-repair */
|
|
47
|
-
toAgentJSON(adapterPath) {
|
|
48
|
-
return {
|
|
49
|
-
error: this.message,
|
|
50
|
-
adapter: adapterPath,
|
|
51
|
-
...this.detail,
|
|
52
|
-
retryable: this.detail.retryable ?? false,
|
|
53
|
-
alternatives: this.detail.alternatives ?? [],
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
/** Reserved sibling keys that are not step action names */
|
|
58
|
-
const SIBLING_KEYS = new Set([
|
|
59
|
-
"fallback",
|
|
60
|
-
"then",
|
|
61
|
-
"else",
|
|
62
|
-
"merge",
|
|
63
|
-
"retry",
|
|
64
|
-
"backoff",
|
|
65
|
-
]);
|
|
66
|
-
function getActionEntry(step) {
|
|
67
|
-
const entries = Object.entries(step);
|
|
68
|
-
return (entries.find(([k]) => !SIBLING_KEYS.has(k)) ?? entries[0]);
|
|
69
|
-
}
|
|
70
|
-
/** Dispatch a single pipeline step by action name. */
|
|
71
|
-
async function executeStep(ctx, action, config, stepIndex, fullStep, depth) {
|
|
72
|
-
switch (action) {
|
|
73
|
-
case "fetch":
|
|
74
|
-
return stepFetch(ctx, config);
|
|
75
|
-
case "fetch_text":
|
|
76
|
-
return stepFetchText(ctx, config);
|
|
77
|
-
case "parse_rss":
|
|
78
|
-
return stepParseRss(ctx, config);
|
|
79
|
-
case "select":
|
|
80
|
-
return stepSelect(ctx, config, stepIndex);
|
|
81
|
-
case "map":
|
|
82
|
-
return stepMap(ctx, config);
|
|
83
|
-
case "filter":
|
|
84
|
-
return stepFilter(ctx, config);
|
|
85
|
-
case "sort":
|
|
86
|
-
return stepSort(ctx, config);
|
|
87
|
-
case "limit":
|
|
88
|
-
return stepLimit(ctx, config);
|
|
89
|
-
case "exec":
|
|
90
|
-
return stepExec(ctx, config);
|
|
91
|
-
case "html_to_md":
|
|
92
|
-
return stepHtmlToMd(ctx);
|
|
93
|
-
case "write_temp":
|
|
94
|
-
return stepWriteTemp(ctx, config);
|
|
95
|
-
case "navigate":
|
|
96
|
-
return stepNavigate(ctx, config);
|
|
97
|
-
case "evaluate":
|
|
98
|
-
return stepEvaluate(ctx, config);
|
|
99
|
-
case "click":
|
|
100
|
-
return stepClick(ctx, config);
|
|
101
|
-
case "type":
|
|
102
|
-
return stepType(ctx, config);
|
|
103
|
-
case "wait":
|
|
104
|
-
return stepWaitBrowser(ctx, config);
|
|
105
|
-
case "intercept":
|
|
106
|
-
return stepIntercept(ctx, config);
|
|
107
|
-
case "press":
|
|
108
|
-
return stepPress(ctx, config);
|
|
109
|
-
case "scroll":
|
|
110
|
-
return stepScroll(ctx, config);
|
|
111
|
-
case "snapshot":
|
|
112
|
-
return stepSnapshot(ctx, config);
|
|
113
|
-
case "tap":
|
|
114
|
-
return stepTap(ctx, config);
|
|
115
|
-
case "download":
|
|
116
|
-
return stepDownload(ctx, config);
|
|
117
|
-
case "websocket":
|
|
118
|
-
return stepWebsocket(ctx, config);
|
|
119
|
-
case "rate_limit": {
|
|
120
|
-
const rlConfig = config;
|
|
121
|
-
const { waitForToken } = await import("./rate-limiter.js");
|
|
122
|
-
await waitForToken(rlConfig.domain, rlConfig.rpm ?? 60);
|
|
123
|
-
return ctx;
|
|
124
|
-
}
|
|
125
|
-
case "assert":
|
|
126
|
-
return stepAssert(ctx, config, stepIndex);
|
|
127
|
-
case "set":
|
|
128
|
-
return stepSet(ctx, config);
|
|
129
|
-
case "append":
|
|
130
|
-
return stepAppend(ctx, config);
|
|
131
|
-
case "if": {
|
|
132
|
-
const ifStep = (fullStep ?? { if: config });
|
|
133
|
-
return stepIf(ctx, ifStep, stepIndex, (depth ?? 0) + 1);
|
|
134
|
-
}
|
|
135
|
-
case "each":
|
|
136
|
-
return stepEach(ctx, config, stepIndex, depth ?? 0);
|
|
137
|
-
case "parallel": {
|
|
138
|
-
const mergeStrategy = fullStep?.merge ?? "concat";
|
|
139
|
-
return stepParallel(ctx, config, mergeStrategy, stepIndex, depth ?? 0);
|
|
140
|
-
}
|
|
141
|
-
case "extract":
|
|
142
|
-
return stepExtract(ctx, config);
|
|
143
|
-
default: {
|
|
144
|
-
// Check plugin custom step registry before giving up
|
|
145
|
-
const { getCustomStep } = await import("../plugin/step-registry.js");
|
|
146
|
-
const customHandler = getCustomStep(action);
|
|
147
|
-
if (customHandler) {
|
|
148
|
-
const pluginCtx = {
|
|
149
|
-
data: ctx.data,
|
|
150
|
-
args: ctx.args,
|
|
151
|
-
vars: ctx.vars,
|
|
152
|
-
base: ctx.base,
|
|
153
|
-
cookieHeader: ctx.cookieHeader,
|
|
154
|
-
};
|
|
155
|
-
const result = await customHandler(pluginCtx, config);
|
|
156
|
-
return { ...ctx, data: result.data, vars: result.vars };
|
|
157
|
-
}
|
|
158
|
-
return ctx;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
export async function runPipeline(steps, args, base, options) {
|
|
163
|
-
// Load cookies for cookie/header strategy (disk first, CDP fallback)
|
|
164
|
-
let cookieHeader;
|
|
165
|
-
if ((options?.strategy === "cookie" || options?.strategy === "header") &&
|
|
166
|
-
options?.site) {
|
|
167
|
-
const cookies = await loadCookiesWithCDP(options.site);
|
|
168
|
-
if (!cookies) {
|
|
169
|
-
throw new PipelineError(`No cookies found for "${options.site}". Run: unicli auth setup ${options.site}`, {
|
|
170
|
-
step: -1,
|
|
171
|
-
action: "auth",
|
|
172
|
-
config: { site: options.site, strategy: options.strategy },
|
|
173
|
-
errorType: "http_error",
|
|
174
|
-
suggestion: `Either start Chrome with "unicli browser start" and login to ${options.site}, or create cookie file at ~/.unicli/cookies/${options.site}.json`,
|
|
175
|
-
retryable: false,
|
|
176
|
-
alternatives: [`unicli auth setup ${options.site}`],
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
cookieHeader = formatCookieHeader(cookies);
|
|
180
|
-
}
|
|
181
|
-
let ctx = { data: null, args, vars: {}, base, cookieHeader };
|
|
182
|
-
let tempDir;
|
|
183
|
-
try {
|
|
184
|
-
for (let i = 0; i < steps.length; i++) {
|
|
185
|
-
const step = steps[i];
|
|
186
|
-
const [action, config] = getActionEntry(step);
|
|
187
|
-
// --- Fallback extraction ---
|
|
188
|
-
// Fallback can live inside the step config (object configs like fetch)
|
|
189
|
-
// or as a sibling key in the step object (string configs like select).
|
|
190
|
-
let stepConfig = config;
|
|
191
|
-
let fallbacks;
|
|
192
|
-
if (stepConfig &&
|
|
193
|
-
typeof stepConfig === "object" &&
|
|
194
|
-
!Array.isArray(stepConfig)) {
|
|
195
|
-
const configObj = stepConfig;
|
|
196
|
-
if ("fallback" in configObj) {
|
|
197
|
-
fallbacks = Array.isArray(configObj.fallback)
|
|
198
|
-
? configObj.fallback
|
|
199
|
-
: [configObj.fallback];
|
|
200
|
-
const { fallback: _, ...rest } = configObj;
|
|
201
|
-
stepConfig = rest;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
if (!fallbacks) {
|
|
205
|
-
const stepObj = step;
|
|
206
|
-
if ("fallback" in stepObj) {
|
|
207
|
-
const fb = stepObj.fallback;
|
|
208
|
-
fallbacks = Array.isArray(fb) ? fb : [fb];
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
// Filter out null/undefined fallback entries (e.g. `fallback:` with no value in YAML)
|
|
212
|
-
if (fallbacks) {
|
|
213
|
-
fallbacks = fallbacks.filter((fb) => fb != null);
|
|
214
|
-
if (fallbacks.length === 0)
|
|
215
|
-
fallbacks = undefined;
|
|
216
|
-
}
|
|
217
|
-
// --- Retry configuration ---
|
|
218
|
-
const retryCount = getRetryCount(step, stepConfig);
|
|
219
|
-
const backoffMs = getBackoffMs(step, stepConfig);
|
|
220
|
-
// Strip retry/backoff from stepConfig to avoid passing them to step implementations
|
|
221
|
-
if (stepConfig &&
|
|
222
|
-
typeof stepConfig === "object" &&
|
|
223
|
-
!Array.isArray(stepConfig)) {
|
|
224
|
-
const cfgObj = stepConfig;
|
|
225
|
-
if ("retry" in cfgObj || "backoff" in cfgObj) {
|
|
226
|
-
const { retry: _r, backoff: _b, ...rest } = cfgObj;
|
|
227
|
-
stepConfig = rest;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
try {
|
|
231
|
-
if (retryCount > 0) {
|
|
232
|
-
// Retry-aware execution: wraps primary + fallback
|
|
233
|
-
let lastRetryErr;
|
|
234
|
-
let retrySucceeded = false;
|
|
235
|
-
for (let attempt = 0; attempt <= retryCount; attempt++) {
|
|
236
|
-
try {
|
|
237
|
-
// Inner: primary step + fallback
|
|
238
|
-
try {
|
|
239
|
-
ctx = await executeStep(ctx, action, stepConfig, i, step);
|
|
240
|
-
}
|
|
241
|
-
catch (primaryErr) {
|
|
242
|
-
if (!fallbacks || fallbacks.length === 0)
|
|
243
|
-
throw primaryErr;
|
|
244
|
-
let lastErr = primaryErr;
|
|
245
|
-
let fbOk = false;
|
|
246
|
-
for (const fb of fallbacks) {
|
|
247
|
-
try {
|
|
248
|
-
ctx = await executeStep(ctx, action, fb, i, step);
|
|
249
|
-
fbOk = true;
|
|
250
|
-
break;
|
|
251
|
-
}
|
|
252
|
-
catch (fbErr) {
|
|
253
|
-
lastErr = fbErr;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
if (!fbOk)
|
|
257
|
-
throw lastErr;
|
|
258
|
-
}
|
|
259
|
-
retrySucceeded = true;
|
|
260
|
-
break;
|
|
261
|
-
}
|
|
262
|
-
catch (retryErr) {
|
|
263
|
-
lastRetryErr = retryErr;
|
|
264
|
-
if (attempt < retryCount) {
|
|
265
|
-
const delay = backoffMs * Math.pow(2, attempt);
|
|
266
|
-
await new Promise((r) => setTimeout(r, delay));
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
if (!retrySucceeded && lastRetryErr)
|
|
271
|
-
throw lastRetryErr;
|
|
272
|
-
}
|
|
273
|
-
else {
|
|
274
|
-
// Original execution path (no retry): primary + fallback
|
|
275
|
-
try {
|
|
276
|
-
ctx = await executeStep(ctx, action, stepConfig, i, step);
|
|
277
|
-
}
|
|
278
|
-
catch (primaryErr) {
|
|
279
|
-
if (!fallbacks || fallbacks.length === 0)
|
|
280
|
-
throw primaryErr;
|
|
281
|
-
let lastErr = primaryErr;
|
|
282
|
-
let succeeded = false;
|
|
283
|
-
for (const fb of fallbacks) {
|
|
284
|
-
try {
|
|
285
|
-
ctx = await executeStep(ctx, action, fb, i, step);
|
|
286
|
-
succeeded = true;
|
|
287
|
-
break;
|
|
288
|
-
}
|
|
289
|
-
catch (fbErr) {
|
|
290
|
-
lastErr = fbErr;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
if (!succeeded)
|
|
294
|
-
throw lastErr;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
catch (err) {
|
|
299
|
-
// Auto-fix: try alternative select paths when selector_miss
|
|
300
|
-
if (action === "select" &&
|
|
301
|
-
err instanceof PipelineError &&
|
|
302
|
-
err.detail.errorType === "selector_miss" &&
|
|
303
|
-
options?.site) {
|
|
304
|
-
try {
|
|
305
|
-
const { suggestSelectFix } = await import("./auto-fix.js");
|
|
306
|
-
const suggestions = suggestSelectFix(ctx.data, stepConfig);
|
|
307
|
-
let fixed = false;
|
|
308
|
-
for (const suggestion of suggestions) {
|
|
309
|
-
try {
|
|
310
|
-
ctx = stepSelect(ctx, suggestion, i);
|
|
311
|
-
process.stderr.write(`[auto-fix] ${options.site}: select path changed "${String(stepConfig)}" → "${suggestion}"\n`);
|
|
312
|
-
fixed = true;
|
|
313
|
-
break;
|
|
314
|
-
}
|
|
315
|
-
catch {
|
|
316
|
-
// Try next suggestion
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
if (fixed)
|
|
320
|
-
continue;
|
|
321
|
-
}
|
|
322
|
-
catch {
|
|
323
|
-
// Auto-fix module not available
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
// Emit diagnostic context for agent self-repair
|
|
327
|
-
if (process.env.UNICLI_DIAGNOSTIC === "1") {
|
|
328
|
-
try {
|
|
329
|
-
const { buildRepairContext, emitRepairContext } = await import("./diagnostic.js");
|
|
330
|
-
const repairCtx = await buildRepairContext({
|
|
331
|
-
error: err instanceof Error ? err : new Error(String(err)),
|
|
332
|
-
site: options?.site ?? "unknown",
|
|
333
|
-
command: "unknown",
|
|
334
|
-
page: ctx.page,
|
|
335
|
-
});
|
|
336
|
-
emitRepairContext(repairCtx);
|
|
337
|
-
}
|
|
338
|
-
catch {
|
|
339
|
-
// Diagnostic collection failure should never mask the original error
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
// Smart cookie refresh on auth failure
|
|
343
|
-
if (err instanceof PipelineError &&
|
|
344
|
-
(err.detail.statusCode === 401 || err.detail.statusCode === 403) &&
|
|
345
|
-
(options?.strategy === "cookie" || options?.strategy === "header") &&
|
|
346
|
-
options?.site) {
|
|
347
|
-
try {
|
|
348
|
-
const { refreshCookies } = await import("./cookie-refresh.js");
|
|
349
|
-
const refreshed = await refreshCookies(options.site);
|
|
350
|
-
if (refreshed) {
|
|
351
|
-
process.stderr.write(`[cookie-refresh] Cookies refreshed for ${options.site}, retry the command.\n`);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
catch {
|
|
355
|
-
// Cookie refresh failure is non-fatal
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
if (err instanceof PipelineError)
|
|
359
|
-
throw err;
|
|
360
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
361
|
-
const isTransient = /timeout|ETIMEDOUT|ECONNREFUSED|ECONNRESET|socket hang up/i.test(errMsg);
|
|
362
|
-
throw new PipelineError(`Step ${i} (${action}) failed: ${errMsg}`, {
|
|
363
|
-
step: i,
|
|
364
|
-
action,
|
|
365
|
-
config,
|
|
366
|
-
errorType: isTransient ? "timeout" : "parse_error",
|
|
367
|
-
suggestion: `Check the ${action} step at index ${i} in the adapter YAML. The expression or configuration may be invalid.`,
|
|
368
|
-
retryable: isTransient,
|
|
369
|
-
alternatives: [],
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
if (ctx.tempDir)
|
|
373
|
-
tempDir = ctx.tempDir;
|
|
374
|
-
}
|
|
375
|
-
const result = ctx.data;
|
|
376
|
-
if (Array.isArray(result))
|
|
377
|
-
return result;
|
|
378
|
-
if (result !== null && result !== undefined)
|
|
379
|
-
return [result];
|
|
380
|
-
return [];
|
|
381
|
-
}
|
|
382
|
-
finally {
|
|
383
|
-
if (tempDir) {
|
|
384
|
-
try {
|
|
385
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
386
|
-
}
|
|
387
|
-
catch {
|
|
388
|
-
// Best-effort cleanup
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
if (ctx.page) {
|
|
392
|
-
try {
|
|
393
|
-
await ctx.page.close();
|
|
394
|
-
}
|
|
395
|
-
catch {
|
|
396
|
-
// Best-effort cleanup
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
// --- Retry helpers ---
|
|
402
|
-
function getRetryCount(step, config) {
|
|
403
|
-
const stepObj = step;
|
|
404
|
-
if (typeof stepObj.retry === "number")
|
|
405
|
-
return stepObj.retry;
|
|
406
|
-
if (config &&
|
|
407
|
-
typeof config === "object" &&
|
|
408
|
-
!Array.isArray(config) &&
|
|
409
|
-
"retry" in config) {
|
|
410
|
-
return Number(config.retry) || 0;
|
|
411
|
-
}
|
|
412
|
-
return 0;
|
|
413
|
-
}
|
|
414
|
-
function getBackoffMs(step, config) {
|
|
415
|
-
const stepObj = step;
|
|
416
|
-
if (typeof stepObj.backoff === "number")
|
|
417
|
-
return stepObj.backoff;
|
|
418
|
-
if (config &&
|
|
419
|
-
typeof config === "object" &&
|
|
420
|
-
!Array.isArray(config) &&
|
|
421
|
-
"backoff" in config) {
|
|
422
|
-
return Number(config.backoff) || 1000;
|
|
423
|
-
}
|
|
424
|
-
return 1000;
|
|
425
|
-
}
|
|
426
|
-
async function stepAssert(ctx, config, stepIndex) {
|
|
427
|
-
const page = ctx.page ? ctx.page : undefined;
|
|
428
|
-
// URL assertion
|
|
429
|
-
if (config.url) {
|
|
430
|
-
if (!page)
|
|
431
|
-
throw assertionError("url assertion requires a browser page", config, stepIndex);
|
|
432
|
-
const currentUrl = await page.url();
|
|
433
|
-
const expected = evalTemplate(config.url, ctx);
|
|
434
|
-
if (!currentUrl.includes(expected)) {
|
|
435
|
-
throw assertionError(`URL mismatch: expected "${expected}" in "${currentUrl}"`, config, stepIndex);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
// Selector assertion
|
|
439
|
-
if (config.selector) {
|
|
440
|
-
if (!page)
|
|
441
|
-
throw assertionError("selector assertion requires a browser page", config, stepIndex);
|
|
442
|
-
const selector = evalTemplate(config.selector, ctx);
|
|
443
|
-
const exists = await page.evaluate(`!!document.querySelector(${JSON.stringify(selector)})`);
|
|
444
|
-
if (!exists) {
|
|
445
|
-
throw assertionError(`Element not found: ${selector}`, config, stepIndex);
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
// Text assertion
|
|
449
|
-
if (config.text) {
|
|
450
|
-
if (!page)
|
|
451
|
-
throw assertionError("text assertion requires a browser page", config, stepIndex);
|
|
452
|
-
const expected = evalTemplate(config.text, ctx);
|
|
453
|
-
const bodyText = (await page.evaluate("document.body?.innerText || ''"));
|
|
454
|
-
if (!bodyText.includes(expected)) {
|
|
455
|
-
throw assertionError(`Text not found: "${expected}"`, config, stepIndex);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
// Condition assertion (works without browser)
|
|
459
|
-
if (config.condition) {
|
|
460
|
-
const expr = evalTemplate(config.condition, ctx);
|
|
461
|
-
const result = evalExpression(expr, {
|
|
462
|
-
data: ctx.data,
|
|
463
|
-
args: ctx.args,
|
|
464
|
-
vars: ctx.vars,
|
|
465
|
-
});
|
|
466
|
-
if (!result) {
|
|
467
|
-
throw assertionError(`Condition failed: ${expr}`, config, stepIndex);
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
return ctx;
|
|
471
|
-
}
|
|
472
|
-
function assertionError(message, config, stepIndex) {
|
|
473
|
-
return new PipelineError(config.message ?? message, {
|
|
474
|
-
step: stepIndex,
|
|
475
|
-
action: "assert",
|
|
476
|
-
config,
|
|
477
|
-
errorType: "assertion_failed",
|
|
478
|
-
suggestion: "Check the assertion conditions in the adapter YAML. The page state may not match expectations.",
|
|
479
|
-
retryable: false,
|
|
480
|
-
alternatives: [],
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
async function stepFetch(ctx, config) {
|
|
484
|
-
let url = evalTemplate(config.url, ctx);
|
|
485
|
-
// If data is an array of items with IDs, fetch each one (fan-out with concurrency limit)
|
|
486
|
-
if (Array.isArray(ctx.data)) {
|
|
487
|
-
const items = ctx.data;
|
|
488
|
-
const concurrency = config
|
|
489
|
-
.concurrency
|
|
490
|
-
? Number(config.concurrency)
|
|
491
|
-
: 5;
|
|
492
|
-
const results = await mapConcurrent(items, concurrency, async (item) => {
|
|
493
|
-
const itemCtx = { ...ctx, data: item };
|
|
494
|
-
const itemUrl = evalTemplate(config.url, itemCtx);
|
|
495
|
-
const resolvedConfig = config.body
|
|
496
|
-
? { ...config, body: resolveTemplateDeep(config.body, itemCtx) }
|
|
497
|
-
: config;
|
|
498
|
-
return fetchJson(itemUrl, resolvedConfig, ctx.cookieHeader);
|
|
499
|
-
});
|
|
500
|
-
return { ...ctx, data: results };
|
|
501
|
-
}
|
|
502
|
-
// Append query params
|
|
503
|
-
if (config.params) {
|
|
504
|
-
const params = new URLSearchParams();
|
|
505
|
-
for (const [k, v] of Object.entries(config.params)) {
|
|
506
|
-
const val = evalTemplate(String(v), ctx);
|
|
507
|
-
params.set(k, val);
|
|
508
|
-
}
|
|
509
|
-
url += (url.includes("?") ? "&" : "?") + params.toString();
|
|
510
|
-
}
|
|
511
|
-
const resolvedConfig = config.body
|
|
512
|
-
? { ...config, body: resolveTemplateDeep(config.body, ctx) }
|
|
513
|
-
: config;
|
|
514
|
-
// Strategy fallback: if no cookie and fetch returns 401/403, try with cookies
|
|
515
|
-
try {
|
|
516
|
-
const data = await fetchJson(url, resolvedConfig, ctx.cookieHeader);
|
|
517
|
-
return { ...ctx, data };
|
|
518
|
-
}
|
|
519
|
-
catch (err) {
|
|
520
|
-
if (err instanceof PipelineError &&
|
|
521
|
-
(err.detail.statusCode === 401 || err.detail.statusCode === 403) &&
|
|
522
|
-
!ctx.cookieHeader) {
|
|
523
|
-
// Attempt cookie fallback — try loading cookies for the domain
|
|
524
|
-
try {
|
|
525
|
-
const hostname = new URL(url).hostname;
|
|
526
|
-
const siteName = hostname
|
|
527
|
-
.replace(/^www\./, "")
|
|
528
|
-
.split(".")
|
|
529
|
-
.slice(0, -1)
|
|
530
|
-
.join("-");
|
|
531
|
-
const cookies = await loadCookiesWithCDP(siteName);
|
|
532
|
-
if (cookies) {
|
|
533
|
-
const fallbackCookie = formatCookieHeader(cookies);
|
|
534
|
-
const data = await fetchJson(url, resolvedConfig, fallbackCookie);
|
|
535
|
-
return { ...ctx, data, cookieHeader: fallbackCookie };
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
catch {
|
|
539
|
-
// Cookie fallback also failed — throw original
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
throw err;
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
// --- Fetch response cache ---
|
|
546
|
-
const CACHE_DIR = join(homedir(), ".unicli", "cache");
|
|
547
|
-
function fetchCacheKey(url, method) {
|
|
548
|
-
return createHash("sha256")
|
|
549
|
-
.update(`${method}:${url}`)
|
|
550
|
-
.digest("hex")
|
|
551
|
-
.slice(0, 16);
|
|
552
|
-
}
|
|
553
|
-
function readFetchCache(url, method, ttlSeconds) {
|
|
554
|
-
const key = fetchCacheKey(url, method);
|
|
555
|
-
const filePath = join(CACHE_DIR, `${key}.json`);
|
|
556
|
-
if (!existsSync(filePath))
|
|
557
|
-
return null;
|
|
558
|
-
try {
|
|
559
|
-
const raw = readFileSync(filePath, "utf-8");
|
|
560
|
-
const entry = JSON.parse(raw);
|
|
561
|
-
if (Date.now() - entry.ts > ttlSeconds * 1000)
|
|
562
|
-
return null;
|
|
563
|
-
return entry.data;
|
|
564
|
-
}
|
|
565
|
-
catch {
|
|
566
|
-
return null;
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
const MAX_CACHE_ENTRY_BYTES = 10 * 1024 * 1024; // 10MB per entry
|
|
570
|
-
function writeFetchCache(url, method, data) {
|
|
571
|
-
try {
|
|
572
|
-
const payload = JSON.stringify({ ts: Date.now(), url, data });
|
|
573
|
-
if (payload.length > MAX_CACHE_ENTRY_BYTES)
|
|
574
|
-
return; // reject oversized responses
|
|
575
|
-
mkdirSync(CACHE_DIR, { recursive: true });
|
|
576
|
-
const key = fetchCacheKey(url, method);
|
|
577
|
-
writeFileSync(join(CACHE_DIR, `${key}.json`), payload);
|
|
578
|
-
}
|
|
579
|
-
catch {
|
|
580
|
-
/* cache write failure is non-fatal */
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
async function fetchJson(url, config, cookieHeader) {
|
|
584
|
-
const method = config.method ?? "GET";
|
|
585
|
-
// Check cache before making network request
|
|
586
|
-
if (config.cache && config.cache > 0) {
|
|
587
|
-
const cached = readFetchCache(url, method, config.cache);
|
|
588
|
-
if (cached !== null)
|
|
589
|
-
return cached;
|
|
590
|
-
}
|
|
591
|
-
const headers = {
|
|
592
|
-
Accept: "application/json",
|
|
593
|
-
"User-Agent": USER_AGENT,
|
|
594
|
-
...config.headers,
|
|
595
|
-
};
|
|
596
|
-
if (cookieHeader) {
|
|
597
|
-
headers["Cookie"] = cookieHeader;
|
|
598
|
-
}
|
|
599
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- dispatcher from undici not in standard RequestInit
|
|
600
|
-
const init = { method, headers };
|
|
601
|
-
if (config.body && method !== "GET") {
|
|
602
|
-
headers["Content-Type"] = "application/json";
|
|
603
|
-
init.body = JSON.stringify(config.body);
|
|
604
|
-
}
|
|
605
|
-
const proxyAgent = getProxyAgent();
|
|
606
|
-
if (proxyAgent)
|
|
607
|
-
init.dispatcher = proxyAgent;
|
|
608
|
-
const maxAttempts = config.retry ?? 1;
|
|
609
|
-
const baseDelay = config.backoff ?? 1000;
|
|
610
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
611
|
-
const resp = await fetch(url, init);
|
|
612
|
-
if (resp.ok) {
|
|
613
|
-
const data = await resp.json();
|
|
614
|
-
if (config.cache && config.cache > 0)
|
|
615
|
-
writeFetchCache(url, method, data);
|
|
616
|
-
return data;
|
|
617
|
-
}
|
|
618
|
-
const isRetryable = resp.status === 429 || resp.status >= 500;
|
|
619
|
-
const isLastAttempt = attempt === maxAttempts;
|
|
620
|
-
if (isRetryable && !isLastAttempt) {
|
|
621
|
-
await new Promise((r) => setTimeout(r, baseDelay * 2 ** (attempt - 1)));
|
|
622
|
-
continue;
|
|
623
|
-
}
|
|
624
|
-
// Non-retryable error or last attempt — throw
|
|
625
|
-
let preview = "";
|
|
626
|
-
try {
|
|
627
|
-
preview = (await resp.text()).slice(0, 200);
|
|
628
|
-
}
|
|
629
|
-
catch {
|
|
630
|
-
/* ignore */
|
|
631
|
-
}
|
|
632
|
-
const isRetryableStatus = resp.status === 429 ||
|
|
633
|
-
resp.status === 500 ||
|
|
634
|
-
resp.status === 502 ||
|
|
635
|
-
resp.status === 503;
|
|
636
|
-
throw new PipelineError(`HTTP ${resp.status} ${resp.statusText} from ${url}`, {
|
|
637
|
-
step: -1, // will be overwritten by caller
|
|
638
|
-
action: "fetch",
|
|
639
|
-
config: { url, method },
|
|
640
|
-
errorType: "http_error",
|
|
641
|
-
url,
|
|
642
|
-
statusCode: resp.status,
|
|
643
|
-
responsePreview: preview,
|
|
644
|
-
suggestion: resp.status === 403
|
|
645
|
-
? "The API is blocking requests. The endpoint may require authentication (cookie strategy) or the User-Agent may need updating."
|
|
646
|
-
: resp.status === 404
|
|
647
|
-
? "The API endpoint was not found. The URL path may have changed — check the target site for the current API."
|
|
648
|
-
: resp.status === 429
|
|
649
|
-
? "Rate limited. Add a delay between requests or reduce the limit parameter."
|
|
650
|
-
: `HTTP ${resp.status} error. Check if the API endpoint is still valid.`,
|
|
651
|
-
retryable: isRetryableStatus,
|
|
652
|
-
alternatives: resp.status === 401 || resp.status === 403
|
|
653
|
-
? ["unicli auth setup <site>"]
|
|
654
|
-
: [],
|
|
655
|
-
});
|
|
656
|
-
}
|
|
657
|
-
// Unreachable — loop always returns or throws — but satisfies TypeScript
|
|
658
|
-
throw new Error("fetchJson: unreachable");
|
|
659
|
-
}
|
|
660
|
-
function stepSelect(ctx, path, stepIndex) {
|
|
661
|
-
const resolved = evalTemplate(path, ctx);
|
|
662
|
-
const data = getNestedValue(ctx.data, resolved);
|
|
663
|
-
if (data === undefined || data === null) {
|
|
664
|
-
throw new PipelineError(`Select "${resolved}" returned nothing — the response structure may have changed`, {
|
|
665
|
-
step: stepIndex,
|
|
666
|
-
action: "select",
|
|
667
|
-
config: path,
|
|
668
|
-
errorType: "selector_miss",
|
|
669
|
-
suggestion: `The path "${resolved}" does not exist in the API response. Inspect the actual response JSON to find the correct path, then update the "select" step in the adapter YAML.`,
|
|
670
|
-
retryable: false,
|
|
671
|
-
alternatives: [],
|
|
672
|
-
});
|
|
673
|
-
}
|
|
674
|
-
return { ...ctx, data };
|
|
675
|
-
}
|
|
676
|
-
function stepMap(ctx, template) {
|
|
677
|
-
if (!Array.isArray(ctx.data))
|
|
678
|
-
return ctx;
|
|
679
|
-
const items = ctx.data;
|
|
680
|
-
const mapped = items.map((item, index) => {
|
|
681
|
-
const row = {};
|
|
682
|
-
for (const [key, expr] of Object.entries(template)) {
|
|
683
|
-
row[key] = evalTemplate(String(expr), {
|
|
684
|
-
...ctx,
|
|
685
|
-
data: { item, index },
|
|
686
|
-
});
|
|
687
|
-
}
|
|
688
|
-
return row;
|
|
689
|
-
});
|
|
690
|
-
return { ...ctx, data: mapped };
|
|
691
|
-
}
|
|
692
|
-
function stepFilter(ctx, expr) {
|
|
693
|
-
if (!Array.isArray(ctx.data))
|
|
694
|
-
return ctx;
|
|
695
|
-
const items = ctx.data;
|
|
696
|
-
const filtered = items.filter((item, index) => {
|
|
697
|
-
const result = evalExpression(expr, { item, index, args: ctx.args });
|
|
698
|
-
return Boolean(result);
|
|
699
|
-
});
|
|
700
|
-
return { ...ctx, data: filtered };
|
|
701
|
-
}
|
|
702
|
-
function stepLimit(ctx, config) {
|
|
703
|
-
if (!Array.isArray(ctx.data))
|
|
704
|
-
return ctx;
|
|
705
|
-
let n;
|
|
706
|
-
if (typeof config === "number") {
|
|
707
|
-
n = config;
|
|
708
|
-
}
|
|
709
|
-
else {
|
|
710
|
-
const val = evalTemplate(String(config), ctx);
|
|
711
|
-
n = parseInt(val, 10) || 20;
|
|
712
|
-
}
|
|
713
|
-
return { ...ctx, data: ctx.data.slice(0, n) };
|
|
714
|
-
}
|
|
715
|
-
// --- fetch_text: like fetch but returns raw text (for XML/RSS/HTML) ---
|
|
716
|
-
async function stepFetchText(ctx, config) {
|
|
717
|
-
let url = evalTemplate(config.url, ctx);
|
|
718
|
-
if (config.params) {
|
|
719
|
-
const params = new URLSearchParams();
|
|
720
|
-
for (const [k, v] of Object.entries(config.params)) {
|
|
721
|
-
params.set(k, evalTemplate(String(v), ctx));
|
|
722
|
-
}
|
|
723
|
-
url += (url.includes("?") ? "&" : "?") + params.toString();
|
|
724
|
-
}
|
|
725
|
-
const method = config.method ?? "GET";
|
|
726
|
-
const headers = {
|
|
727
|
-
"User-Agent": USER_AGENT,
|
|
728
|
-
...config.headers,
|
|
729
|
-
};
|
|
730
|
-
if (ctx.cookieHeader) {
|
|
731
|
-
headers["Cookie"] = ctx.cookieHeader;
|
|
732
|
-
}
|
|
733
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- dispatcher from undici not in standard RequestInit
|
|
734
|
-
const fetchInit = { method, headers };
|
|
735
|
-
const ftAgent = getProxyAgent();
|
|
736
|
-
if (ftAgent)
|
|
737
|
-
fetchInit.dispatcher = ftAgent;
|
|
738
|
-
const resp = await fetch(url, fetchInit);
|
|
739
|
-
if (!resp.ok) {
|
|
740
|
-
throw new PipelineError(`HTTP ${resp.status} ${resp.statusText} from ${url}`, {
|
|
741
|
-
step: -1,
|
|
742
|
-
action: "fetch_text",
|
|
743
|
-
config: { url, method },
|
|
744
|
-
errorType: "http_error",
|
|
745
|
-
url,
|
|
746
|
-
statusCode: resp.status,
|
|
747
|
-
suggestion: `Check if the URL is still valid: ${url}`,
|
|
748
|
-
retryable: resp.status === 429 ||
|
|
749
|
-
resp.status === 500 ||
|
|
750
|
-
resp.status === 502 ||
|
|
751
|
-
resp.status === 503,
|
|
752
|
-
alternatives: resp.status === 401 || resp.status === 403
|
|
753
|
-
? ["unicli auth setup <site>"]
|
|
754
|
-
: [],
|
|
755
|
-
});
|
|
756
|
-
}
|
|
757
|
-
const text = await resp.text();
|
|
758
|
-
return { ...ctx, data: text };
|
|
759
|
-
}
|
|
760
|
-
function stepParseRss(ctx, config) {
|
|
761
|
-
const xml = String(ctx.data ?? "");
|
|
762
|
-
const items = [];
|
|
763
|
-
// Support both RSS 2.0 (<item>) and Atom (<entry>) formats
|
|
764
|
-
const isAtom = xml.includes("<entry>");
|
|
765
|
-
const itemRegex = isAtom
|
|
766
|
-
? /<entry>([\s\S]*?)<\/entry>/g
|
|
767
|
-
: /<item>([\s\S]*?)<\/item>/g;
|
|
768
|
-
let match;
|
|
769
|
-
while ((match = itemRegex.exec(xml)) !== null) {
|
|
770
|
-
const block = match[1];
|
|
771
|
-
if (config?.fields) {
|
|
772
|
-
const row = {};
|
|
773
|
-
for (const [key, tag] of Object.entries(config.fields)) {
|
|
774
|
-
row[key] = extractXmlTag(block, tag);
|
|
775
|
-
}
|
|
776
|
-
items.push(row);
|
|
777
|
-
}
|
|
778
|
-
else if (isAtom) {
|
|
779
|
-
// Atom format: <title>, <link href="...">, <published>, <summary>/<content>
|
|
780
|
-
const linkMatch = block.match(/<link[^>]*rel=["']alternate["'][^>]*href=["']([^"']+)["']/);
|
|
781
|
-
const linkHref = linkMatch?.[1] ??
|
|
782
|
-
block.match(/<link[^>]*href=["']([^"']+)["']/)?.[1] ??
|
|
783
|
-
"";
|
|
784
|
-
items.push({
|
|
785
|
-
title: extractXmlCdata(block, "title"),
|
|
786
|
-
description: extractXmlCdata(block, "content") ||
|
|
787
|
-
extractXmlCdata(block, "summary"),
|
|
788
|
-
link: linkHref,
|
|
789
|
-
pubDate: extractXmlTag(block, "published") || extractXmlTag(block, "updated"),
|
|
790
|
-
guid: extractXmlTag(block, "id"),
|
|
791
|
-
});
|
|
792
|
-
}
|
|
793
|
-
else {
|
|
794
|
-
items.push({
|
|
795
|
-
title: extractXmlCdata(block, "title"),
|
|
796
|
-
description: extractXmlCdata(block, "description"),
|
|
797
|
-
link: extractXmlTag(block, "link"),
|
|
798
|
-
pubDate: extractXmlTag(block, "pubDate"),
|
|
799
|
-
guid: extractXmlTag(block, "guid"),
|
|
800
|
-
});
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
return { ...ctx, data: items };
|
|
804
|
-
}
|
|
805
|
-
function extractXmlCdata(xml, tag) {
|
|
806
|
-
const cdataMatch = xml.match(new RegExp(`<${tag}><!\\[CDATA\\[([\\s\\S]*?)\\]\\]></${tag}>`));
|
|
807
|
-
if (cdataMatch)
|
|
808
|
-
return cdataMatch[1].trim();
|
|
809
|
-
return extractXmlTag(xml, tag);
|
|
810
|
-
}
|
|
811
|
-
function extractXmlTag(xml, tag) {
|
|
812
|
-
const m = xml.match(new RegExp(`<${tag}[^>]*>([\\s\\S]*?)</${tag}>`));
|
|
813
|
-
return m ? m[1].trim() : "";
|
|
814
|
-
}
|
|
815
|
-
function stepSort(ctx, config) {
|
|
816
|
-
if (!Array.isArray(ctx.data))
|
|
817
|
-
return ctx;
|
|
818
|
-
const items = [...ctx.data];
|
|
819
|
-
const desc = config.order === "desc";
|
|
820
|
-
items.sort((a, b) => {
|
|
821
|
-
const va = a[config.by];
|
|
822
|
-
const vb = b[config.by];
|
|
823
|
-
const na = Number(va);
|
|
824
|
-
const nb = Number(vb);
|
|
825
|
-
if (!isNaN(na) && !isNaN(nb))
|
|
826
|
-
return desc ? nb - na : na - nb;
|
|
827
|
-
return desc
|
|
828
|
-
? String(vb ?? "").localeCompare(String(va ?? ""))
|
|
829
|
-
: String(va ?? "").localeCompare(String(vb ?? ""));
|
|
830
|
-
});
|
|
831
|
-
return { ...ctx, data: items };
|
|
832
|
-
}
|
|
833
|
-
async function stepExec(ctx, config) {
|
|
834
|
-
const cmd = evalTemplate(config.command, ctx);
|
|
835
|
-
const execArgs = (config.args ?? []).map((a) => evalTemplate(String(a), ctx));
|
|
836
|
-
const timeout = config.timeout ?? 30000;
|
|
837
|
-
// Sensitive-path deny list — scan every arg that looks like a path before
|
|
838
|
-
// touching subprocess. Cannot be overridden by permission mode. Defends
|
|
839
|
-
// against prompt-injection that smuggles a credential path into args.
|
|
840
|
-
// Uses the realpath-aware variant so `ln -s ~/.ssh/id_rsa /tmp/x.txt` is
|
|
841
|
-
// still blocked.
|
|
842
|
-
for (const arg of execArgs) {
|
|
843
|
-
if (typeof arg !== "string" || arg.length === 0)
|
|
844
|
-
continue;
|
|
845
|
-
if (!arg.startsWith("/") && !arg.startsWith("~/"))
|
|
846
|
-
continue;
|
|
847
|
-
const expanded = arg.startsWith("~/") ? join(homedir(), arg.slice(2)) : arg;
|
|
848
|
-
const matched = matchSensitivePathRealpath(expanded);
|
|
849
|
-
if (matched) {
|
|
850
|
-
const denial = buildSensitivePathDenial(expanded);
|
|
851
|
-
// The error message is the canonical `sensitive_path_denied` string so
|
|
852
|
-
// agents can pattern-match the same identifier regardless of whether
|
|
853
|
-
// the block fires in operate upload or the exec pipeline step. The
|
|
854
|
-
// full denial payload (path, pattern, hint) is inlined into `config`
|
|
855
|
-
// for `toAgentJSON()` to surface.
|
|
856
|
-
throw new PipelineError("sensitive_path_denied", {
|
|
857
|
-
step: -1,
|
|
858
|
-
action: "exec",
|
|
859
|
-
config: {
|
|
860
|
-
command: cmd,
|
|
861
|
-
args: execArgs,
|
|
862
|
-
denial_path: denial.path,
|
|
863
|
-
denial_pattern: denial.pattern,
|
|
864
|
-
},
|
|
865
|
-
errorType: "assertion_failed",
|
|
866
|
-
suggestion: denial.hint,
|
|
867
|
-
retryable: false,
|
|
868
|
-
alternatives: [],
|
|
869
|
-
});
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
// Resolve env vars (merge with process.env)
|
|
873
|
-
let envOption;
|
|
874
|
-
if (config.env) {
|
|
875
|
-
const resolved = {};
|
|
876
|
-
for (const [k, v] of Object.entries(config.env)) {
|
|
877
|
-
resolved[k] = evalTemplate(String(v), ctx);
|
|
878
|
-
}
|
|
879
|
-
envOption = { ...process.env, ...resolved };
|
|
880
|
-
}
|
|
881
|
-
// Resolve stdin content
|
|
882
|
-
const stdinContent = config.stdin
|
|
883
|
-
? evalTemplate(config.stdin, ctx)
|
|
884
|
-
: undefined;
|
|
885
|
-
// Resolve output_file path
|
|
886
|
-
const outputFile = config.output_file
|
|
887
|
-
? evalTemplate(config.output_file, ctx)
|
|
888
|
-
: undefined;
|
|
889
|
-
try {
|
|
890
|
-
let stdout;
|
|
891
|
-
if (stdinContent !== undefined) {
|
|
892
|
-
// Use spawn to pipe stdin
|
|
893
|
-
const { spawn } = await import("node:child_process");
|
|
894
|
-
stdout = await new Promise((resolve, reject) => {
|
|
895
|
-
const child = spawn(cmd, execArgs, {
|
|
896
|
-
timeout,
|
|
897
|
-
env: envOption,
|
|
898
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
899
|
-
});
|
|
900
|
-
const chunks = [];
|
|
901
|
-
const errChunks = [];
|
|
902
|
-
child.stdout.on("data", (c) => chunks.push(c));
|
|
903
|
-
child.stderr.on("data", (c) => errChunks.push(c));
|
|
904
|
-
child.on("error", (err) => reject(err));
|
|
905
|
-
child.on("close", (code) => {
|
|
906
|
-
if (code !== 0) {
|
|
907
|
-
const stderr = Buffer.concat(errChunks).toString("utf8");
|
|
908
|
-
reject(new Error(`Process exited with code ${code}${stderr ? `: ${stderr}` : ""}`));
|
|
909
|
-
}
|
|
910
|
-
else {
|
|
911
|
-
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
912
|
-
}
|
|
913
|
-
});
|
|
914
|
-
child.stdin.write(stdinContent);
|
|
915
|
-
child.stdin.end();
|
|
916
|
-
});
|
|
917
|
-
}
|
|
918
|
-
else {
|
|
919
|
-
// Use execFileAsync (original path) with optional env
|
|
920
|
-
const opts = {
|
|
921
|
-
timeout,
|
|
922
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
923
|
-
};
|
|
924
|
-
if (envOption)
|
|
925
|
-
opts.env = envOption;
|
|
926
|
-
({ stdout } = await execFileAsync(cmd, execArgs, opts));
|
|
927
|
-
}
|
|
928
|
-
// If output_file is specified, return file info instead of stdout
|
|
929
|
-
if (outputFile) {
|
|
930
|
-
const { stat } = await import("node:fs/promises");
|
|
931
|
-
try {
|
|
932
|
-
const info = await stat(outputFile);
|
|
933
|
-
return { ...ctx, data: { file: outputFile, size: info.size } };
|
|
934
|
-
}
|
|
935
|
-
catch {
|
|
936
|
-
throw new PipelineError(`exec "${cmd}" did not produce expected output file: ${outputFile}`, {
|
|
937
|
-
step: -1,
|
|
938
|
-
action: "exec",
|
|
939
|
-
config: { command: cmd, args: execArgs },
|
|
940
|
-
errorType: "parse_error",
|
|
941
|
-
suggestion: `Check that the command writes to "${outputFile}". Verify the path is correct.`,
|
|
942
|
-
retryable: false,
|
|
943
|
-
alternatives: [],
|
|
944
|
-
});
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
let data;
|
|
948
|
-
switch (config.parse ?? "lines") {
|
|
949
|
-
case "json":
|
|
950
|
-
data = JSON.parse(stdout);
|
|
951
|
-
break;
|
|
952
|
-
case "lines":
|
|
953
|
-
data = stdout
|
|
954
|
-
.split("\n")
|
|
955
|
-
.filter(Boolean)
|
|
956
|
-
.map((line) => ({ line }));
|
|
957
|
-
break;
|
|
958
|
-
case "csv": {
|
|
959
|
-
const lines = stdout.split("\n").filter(Boolean);
|
|
960
|
-
if (lines.length < 2) {
|
|
961
|
-
data = [];
|
|
962
|
-
break;
|
|
963
|
-
}
|
|
964
|
-
const headers = lines[0].split(",").map((h) => h.trim());
|
|
965
|
-
data = lines.slice(1).map((line) => {
|
|
966
|
-
const vals = line.split(",");
|
|
967
|
-
const row = {};
|
|
968
|
-
headers.forEach((h, i) => {
|
|
969
|
-
row[h] = (vals[i] ?? "").trim();
|
|
970
|
-
});
|
|
971
|
-
return row;
|
|
972
|
-
});
|
|
973
|
-
break;
|
|
974
|
-
}
|
|
975
|
-
case "text":
|
|
976
|
-
default:
|
|
977
|
-
data = stdout;
|
|
978
|
-
}
|
|
979
|
-
return { ...ctx, data };
|
|
980
|
-
}
|
|
981
|
-
catch (err) {
|
|
982
|
-
if (err instanceof PipelineError)
|
|
983
|
-
throw err;
|
|
984
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
985
|
-
const isExecTransient = /timeout|ETIMEDOUT|ECONNREFUSED|ECONNRESET/i.test(msg);
|
|
986
|
-
throw new PipelineError(`exec "${cmd}" failed: ${msg}`, {
|
|
987
|
-
step: -1,
|
|
988
|
-
action: "exec",
|
|
989
|
-
config: { command: cmd, args: execArgs },
|
|
990
|
-
errorType: isExecTransient ? "timeout" : "parse_error",
|
|
991
|
-
suggestion: `Check that "${cmd}" is installed and accessible. Run: which ${cmd}`,
|
|
992
|
-
retryable: isExecTransient,
|
|
993
|
-
alternatives: [],
|
|
994
|
-
});
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
// --- HTML to Markdown ---
|
|
998
|
-
function stepHtmlToMd(ctx) {
|
|
999
|
-
const html = String(ctx.data ?? "");
|
|
1000
|
-
const turndown = new TurndownService({
|
|
1001
|
-
headingStyle: "atx",
|
|
1002
|
-
codeBlockStyle: "fenced",
|
|
1003
|
-
});
|
|
1004
|
-
const md = turndown.turndown(html);
|
|
1005
|
-
return { ...ctx, data: md };
|
|
1006
|
-
}
|
|
1007
|
-
// --- Template engine ---
|
|
1008
|
-
/**
|
|
1009
|
-
* Evaluate ${{ expression }} templates in a string.
|
|
1010
|
-
* Returns the raw value if the entire string is a single expression,
|
|
1011
|
-
* otherwise returns a string with interpolated values.
|
|
1012
|
-
*/
|
|
1013
|
-
function evalTemplate(template, ctx) {
|
|
1014
|
-
const fullMatch = template.match(/^\$\{\{\s*(.+?)\s*\}\}$/);
|
|
1015
|
-
if (fullMatch) {
|
|
1016
|
-
const result = evalExpression(fullMatch[1], buildScope(ctx));
|
|
1017
|
-
return String(result ?? "");
|
|
1018
|
-
}
|
|
1019
|
-
return template.replace(/\$\{\{\s*(.+?)\s*\}\}/g, (_match, expr) => {
|
|
1020
|
-
const result = evalExpression(expr, buildScope(ctx));
|
|
1021
|
-
return String(result ?? "");
|
|
1022
|
-
});
|
|
1023
|
-
}
|
|
1024
|
-
function buildScope(ctx) {
|
|
1025
|
-
const scope = {
|
|
1026
|
-
args: ctx.args,
|
|
1027
|
-
vars: ctx.vars ?? {},
|
|
1028
|
-
base: ctx.base,
|
|
1029
|
-
temp: ctx.temp ?? {},
|
|
1030
|
-
};
|
|
1031
|
-
if (ctx.data &&
|
|
1032
|
-
typeof ctx.data === "object" &&
|
|
1033
|
-
"item" in ctx.data) {
|
|
1034
|
-
const d = ctx.data;
|
|
1035
|
-
scope.item = d.item;
|
|
1036
|
-
scope.index = d.index;
|
|
1037
|
-
}
|
|
1038
|
-
else {
|
|
1039
|
-
scope.item = ctx.data;
|
|
1040
|
-
}
|
|
1041
|
-
return scope;
|
|
1042
|
-
}
|
|
1043
|
-
// --- Set step (store pipeline variables) ---
|
|
1044
|
-
function stepSet(ctx, config) {
|
|
1045
|
-
if (!config || typeof config !== "object" || Array.isArray(config))
|
|
1046
|
-
return ctx;
|
|
1047
|
-
const resolved = {};
|
|
1048
|
-
for (const [key, value] of Object.entries(config)) {
|
|
1049
|
-
resolved[key] = resolveTemplateDeep(value, ctx);
|
|
1050
|
-
}
|
|
1051
|
-
return { ...ctx, vars: { ...ctx.vars, ...resolved } };
|
|
1052
|
-
}
|
|
1053
|
-
// --- Append step (accumulate data into vars array) ---
|
|
1054
|
-
function stepAppend(ctx, key) {
|
|
1055
|
-
if (typeof key !== "string" || !key)
|
|
1056
|
-
return ctx;
|
|
1057
|
-
const existing = ctx.vars[key];
|
|
1058
|
-
const arr = Array.isArray(existing)
|
|
1059
|
-
? [...existing]
|
|
1060
|
-
: existing !== undefined
|
|
1061
|
-
? [existing]
|
|
1062
|
-
: [];
|
|
1063
|
-
if (Array.isArray(ctx.data)) {
|
|
1064
|
-
arr.push(...ctx.data);
|
|
1065
|
-
}
|
|
1066
|
-
else if (ctx.data !== null && ctx.data !== undefined) {
|
|
1067
|
-
arr.push(ctx.data);
|
|
1068
|
-
}
|
|
1069
|
-
return { ...ctx, vars: { ...ctx.vars, [key]: arr } };
|
|
1070
|
-
}
|
|
1071
|
-
// --- If/else step (conditional branching) ---
|
|
1072
|
-
async function stepIf(ctx, config, stepIndex, depth = 0) {
|
|
1073
|
-
if (depth > 10) {
|
|
1074
|
-
throw new PipelineError("if step recursion depth exceeded (max 10)", {
|
|
1075
|
-
step: stepIndex,
|
|
1076
|
-
action: "if",
|
|
1077
|
-
config,
|
|
1078
|
-
errorType: "parse_error",
|
|
1079
|
-
suggestion: "Reduce nesting depth of if/else steps. Maximum is 10 levels.",
|
|
1080
|
-
retryable: false,
|
|
1081
|
-
alternatives: [],
|
|
1082
|
-
});
|
|
1083
|
-
}
|
|
1084
|
-
const conditionStr = typeof config.if === "string" ? config.if : String(config.if);
|
|
1085
|
-
// Strip ${{ }} wrapper if present
|
|
1086
|
-
const exprMatch = conditionStr.match(/^\$\{\{\s*(.+?)\s*\}\}$/);
|
|
1087
|
-
const expr = exprMatch ? exprMatch[1] : conditionStr;
|
|
1088
|
-
const result = evalExpression(expr, buildScope(ctx));
|
|
1089
|
-
const branch = result ? config.then : config.else;
|
|
1090
|
-
if (!branch || !Array.isArray(branch) || branch.length === 0)
|
|
1091
|
-
return ctx;
|
|
1092
|
-
// Execute sub-pipeline steps sequentially
|
|
1093
|
-
for (let j = 0; j < branch.length; j++) {
|
|
1094
|
-
const subStep = branch[j];
|
|
1095
|
-
const [subAction, subConfig] = getActionEntry(subStep);
|
|
1096
|
-
ctx = await executeStep(ctx, subAction, subConfig, stepIndex, subStep, depth);
|
|
1097
|
-
}
|
|
1098
|
-
return ctx;
|
|
1099
|
-
}
|
|
1100
|
-
async function stepEach(ctx, config, stepIndex, depth) {
|
|
1101
|
-
if (depth > 10) {
|
|
1102
|
-
throw new PipelineError("each step recursion depth exceeded (max 10)", {
|
|
1103
|
-
step: stepIndex,
|
|
1104
|
-
action: "each",
|
|
1105
|
-
config,
|
|
1106
|
-
errorType: "parse_error",
|
|
1107
|
-
suggestion: "Reduce nesting depth of loop steps. Maximum is 10 levels.",
|
|
1108
|
-
retryable: false,
|
|
1109
|
-
alternatives: [],
|
|
1110
|
-
});
|
|
1111
|
-
}
|
|
1112
|
-
const maxIterations = Math.max(config.max ?? 100, 1);
|
|
1113
|
-
const body = config.do;
|
|
1114
|
-
if (!body || !Array.isArray(body) || body.length === 0)
|
|
1115
|
-
return ctx;
|
|
1116
|
-
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
|
1117
|
-
// Reset data at start of each iteration to prevent fetch fan-out
|
|
1118
|
-
// from previous iteration's array data. State is carried via ctx.vars.
|
|
1119
|
-
ctx = { ...ctx, data: null };
|
|
1120
|
-
// Execute body sub-pipeline
|
|
1121
|
-
for (const subStep of body) {
|
|
1122
|
-
const [subAction, subConfig] = getActionEntry(subStep);
|
|
1123
|
-
ctx = await executeStep(ctx, subAction, subConfig, stepIndex, subStep, depth + 1);
|
|
1124
|
-
}
|
|
1125
|
-
// Check until condition (after body execution — do-while semantics)
|
|
1126
|
-
if (config.until) {
|
|
1127
|
-
const condStr = typeof config.until === "string" ? config.until : String(config.until);
|
|
1128
|
-
// Strip ${{ }} wrapper if present
|
|
1129
|
-
const exprMatch = condStr.match(/^\$\{\{\s*(.+?)\s*\}\}$/);
|
|
1130
|
-
const expr = exprMatch ? exprMatch[1] : condStr;
|
|
1131
|
-
// Build scope with data alias for until condition evaluation
|
|
1132
|
-
const scope = buildScope(ctx);
|
|
1133
|
-
scope.data = ctx.data;
|
|
1134
|
-
const result = evalExpression(expr, scope);
|
|
1135
|
-
if (result)
|
|
1136
|
-
break;
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
return ctx;
|
|
1140
|
-
}
|
|
1141
|
-
// --- Parallel step (concurrent branch execution with merge strategies) ---
|
|
1142
|
-
async function stepParallel(ctx, branches, merge, stepIndex, depth) {
|
|
1143
|
-
if (!Array.isArray(branches) || branches.length === 0)
|
|
1144
|
-
return ctx;
|
|
1145
|
-
if (depth > 10) {
|
|
1146
|
-
throw new PipelineError("parallel step recursion depth exceeded (max 10)", {
|
|
1147
|
-
step: stepIndex,
|
|
1148
|
-
action: "parallel",
|
|
1149
|
-
config: branches,
|
|
1150
|
-
errorType: "parse_error",
|
|
1151
|
-
suggestion: "Reduce nesting depth of parallel steps. Maximum is 10 levels.",
|
|
1152
|
-
retryable: false,
|
|
1153
|
-
alternatives: [],
|
|
1154
|
-
});
|
|
1155
|
-
}
|
|
1156
|
-
const results = await Promise.all(branches.map(async (branch) => {
|
|
1157
|
-
const branchCtx = {
|
|
1158
|
-
...ctx,
|
|
1159
|
-
vars: { ...ctx.vars },
|
|
1160
|
-
};
|
|
1161
|
-
const [action, config] = getActionEntry(branch);
|
|
1162
|
-
const result = await executeStep(branchCtx, action, config, stepIndex, branch, depth + 1);
|
|
1163
|
-
return result.data;
|
|
1164
|
-
}));
|
|
1165
|
-
let merged;
|
|
1166
|
-
switch (merge) {
|
|
1167
|
-
case "zip": {
|
|
1168
|
-
const first = results[0];
|
|
1169
|
-
if (Array.isArray(first)) {
|
|
1170
|
-
merged = first.map((_, i) => results.map((r) => (Array.isArray(r) ? r[i] : r)));
|
|
1171
|
-
}
|
|
1172
|
-
else {
|
|
1173
|
-
merged = results;
|
|
1174
|
-
}
|
|
1175
|
-
break;
|
|
1176
|
-
}
|
|
1177
|
-
case "object":
|
|
1178
|
-
merged = Object.fromEntries(results.map((r, i) => [String(i), r]));
|
|
1179
|
-
break;
|
|
1180
|
-
case "concat":
|
|
1181
|
-
default:
|
|
1182
|
-
merged = results.flatMap((r) => (Array.isArray(r) ? r : [r]));
|
|
1183
|
-
break;
|
|
1184
|
-
}
|
|
1185
|
-
return { ...ctx, data: merged };
|
|
1186
|
-
}
|
|
1187
|
-
/**
|
|
1188
|
-
* Recursively resolve ${{ }} templates in nested objects, arrays, and strings.
|
|
1189
|
-
* Non-string primitives (numbers, booleans, null) pass through unchanged.
|
|
1190
|
-
*/
|
|
1191
|
-
function resolveTemplateDeep(value, ctx) {
|
|
1192
|
-
if (typeof value === "string") {
|
|
1193
|
-
return evalTemplate(value, ctx);
|
|
1194
|
-
}
|
|
1195
|
-
if (Array.isArray(value)) {
|
|
1196
|
-
return value.map((v) => resolveTemplateDeep(v, ctx));
|
|
1197
|
-
}
|
|
1198
|
-
if (value !== null && typeof value === "object") {
|
|
1199
|
-
const result = {};
|
|
1200
|
-
for (const [k, v] of Object.entries(value)) {
|
|
1201
|
-
result[k] = resolveTemplateDeep(v, ctx);
|
|
1202
|
-
}
|
|
1203
|
-
return result;
|
|
1204
|
-
}
|
|
1205
|
-
return value;
|
|
1206
|
-
}
|
|
1207
|
-
/**
|
|
1208
|
-
* Built-in pipe filters — used in template expressions like:
|
|
1209
|
-
* ${{ item.tags | join(', ') }}
|
|
1210
|
-
* ${{ args.word | urlencode }}
|
|
1211
|
-
* ${{ item.text | slice(0, 200) }}
|
|
1212
|
-
*/
|
|
1213
|
-
const PIPE_FILTERS = {
|
|
1214
|
-
join: (val, sep) => Array.isArray(val) ? val.join(String(sep ?? ", ")) : String(val ?? ""),
|
|
1215
|
-
urlencode: (val) => encodeURIComponent(String(val ?? "")),
|
|
1216
|
-
slice: (val, start, end) => {
|
|
1217
|
-
const s = String(val ?? "");
|
|
1218
|
-
return s.slice(Number(start) || 0, end !== undefined ? Number(end) : undefined);
|
|
1219
|
-
},
|
|
1220
|
-
replace: (val, search, replacement) => String(val ?? "").replace(new RegExp(String(search), "g"), String(replacement ?? "")),
|
|
1221
|
-
lowercase: (val) => String(val ?? "").toLowerCase(),
|
|
1222
|
-
uppercase: (val) => String(val ?? "").toUpperCase(),
|
|
1223
|
-
trim: (val) => String(val ?? "").trim(),
|
|
1224
|
-
default: (val, fallback) => val == null || val === "" ? fallback : val,
|
|
1225
|
-
split: (val, sep) => String(val ?? "").split(String(sep ?? ",")),
|
|
1226
|
-
first: (val) => (Array.isArray(val) ? val[0] : val),
|
|
1227
|
-
last: (val) => (Array.isArray(val) ? val[val.length - 1] : val),
|
|
1228
|
-
length: (val) => Array.isArray(val) ? val.length : String(val ?? "").length,
|
|
1229
|
-
strip_html: (val) => String(val ?? "").replace(/<[^>]+>/g, ""),
|
|
1230
|
-
truncate: (val, max) => {
|
|
1231
|
-
const s = String(val ?? "");
|
|
1232
|
-
const n = Number(max) || 100;
|
|
1233
|
-
return s.length > n ? s.slice(0, n) + "..." : s;
|
|
1234
|
-
},
|
|
1235
|
-
slugify: (val) => {
|
|
1236
|
-
return String(val ?? "")
|
|
1237
|
-
.normalize("NFKD")
|
|
1238
|
-
.replace(/[\u0300-\u036f]/g, "")
|
|
1239
|
-
.toLowerCase()
|
|
1240
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
1241
|
-
.replace(/^-+|-+$/g, "");
|
|
1242
|
-
},
|
|
1243
|
-
sanitize: (val) => String(val ?? "")
|
|
1244
|
-
.replace(/[<>:"/\\|?*\x00-\x1f]/g, "_")
|
|
1245
|
-
.replace(/^\.+/, "")
|
|
1246
|
-
.trim() || "download",
|
|
1247
|
-
ext: (val) => {
|
|
1248
|
-
try {
|
|
1249
|
-
const pathname = new URL(String(val)).pathname;
|
|
1250
|
-
const dot = pathname.lastIndexOf(".");
|
|
1251
|
-
return dot > 0 ? pathname.slice(dot + 1) : "";
|
|
1252
|
-
}
|
|
1253
|
-
catch {
|
|
1254
|
-
const s = String(val ?? "");
|
|
1255
|
-
const dot = s.lastIndexOf(".");
|
|
1256
|
-
return dot > 0 ? s.slice(dot + 1).split(/[?#]/)[0] : "";
|
|
1257
|
-
}
|
|
1258
|
-
},
|
|
1259
|
-
basename: (val) => {
|
|
1260
|
-
try {
|
|
1261
|
-
const pathname = new URL(String(val)).pathname;
|
|
1262
|
-
return pathname.split("/").pop() ?? "";
|
|
1263
|
-
}
|
|
1264
|
-
catch {
|
|
1265
|
-
return (String(val ?? "")
|
|
1266
|
-
.split("/")
|
|
1267
|
-
.pop() ?? "");
|
|
1268
|
-
}
|
|
1269
|
-
},
|
|
1270
|
-
keys: (val) => val && typeof val === "object" && !Array.isArray(val)
|
|
1271
|
-
? Object.keys(val)
|
|
1272
|
-
: [],
|
|
1273
|
-
json: (val) => JSON.stringify(val),
|
|
1274
|
-
abs: (val) => Math.abs(Number(val) || 0),
|
|
1275
|
-
round: (val) => Math.round(Number(val) || 0),
|
|
1276
|
-
ceil: (val) => Math.ceil(Number(val) || 0),
|
|
1277
|
-
floor: (val) => Math.floor(Number(val) || 0),
|
|
1278
|
-
int: (val) => parseInt(String(val), 10) || 0,
|
|
1279
|
-
float: (val) => parseFloat(String(val)) || 0,
|
|
1280
|
-
str: (val) => String(val ?? ""),
|
|
1281
|
-
reverse: (val) => Array.isArray(val)
|
|
1282
|
-
? [...val].reverse()
|
|
1283
|
-
: String(val ?? "")
|
|
1284
|
-
.split("")
|
|
1285
|
-
.reverse()
|
|
1286
|
-
.join(""),
|
|
1287
|
-
unique: (val) => (Array.isArray(val) ? [...new Set(val)] : val),
|
|
1288
|
-
};
|
|
1289
|
-
/**
|
|
1290
|
-
* Parse pipe filters from expression: "expr | filter1(arg) | filter2"
|
|
1291
|
-
* Returns { baseExpr, filters: [{ name, args }] }
|
|
1292
|
-
*/
|
|
1293
|
-
function parsePipes(expr) {
|
|
1294
|
-
// Only split on | that is NOT inside parentheses, quotes, or array syntax
|
|
1295
|
-
const parts = [];
|
|
1296
|
-
let current = "";
|
|
1297
|
-
let depth = 0;
|
|
1298
|
-
let inStr = null;
|
|
1299
|
-
for (let i = 0; i < expr.length; i++) {
|
|
1300
|
-
const ch = expr[i];
|
|
1301
|
-
if (inStr) {
|
|
1302
|
-
current += ch;
|
|
1303
|
-
if (ch === inStr && expr[i - 1] !== "\\")
|
|
1304
|
-
inStr = null;
|
|
1305
|
-
continue;
|
|
1306
|
-
}
|
|
1307
|
-
if (ch === '"' || ch === "'") {
|
|
1308
|
-
inStr = ch;
|
|
1309
|
-
current += ch;
|
|
1310
|
-
continue;
|
|
1311
|
-
}
|
|
1312
|
-
if (ch === "(" || ch === "[") {
|
|
1313
|
-
depth++;
|
|
1314
|
-
current += ch;
|
|
1315
|
-
continue;
|
|
1316
|
-
}
|
|
1317
|
-
if (ch === ")" || ch === "]") {
|
|
1318
|
-
depth--;
|
|
1319
|
-
current += ch;
|
|
1320
|
-
continue;
|
|
1321
|
-
}
|
|
1322
|
-
if (ch === "|" && depth === 0 && expr[i + 1] !== "|") {
|
|
1323
|
-
// Check it's not || (logical OR)
|
|
1324
|
-
parts.push(current.trim());
|
|
1325
|
-
current = "";
|
|
1326
|
-
continue;
|
|
1327
|
-
}
|
|
1328
|
-
current += ch;
|
|
1329
|
-
}
|
|
1330
|
-
parts.push(current.trim());
|
|
1331
|
-
if (parts.length <= 1)
|
|
1332
|
-
return { baseExpr: expr, filters: [] };
|
|
1333
|
-
const baseExpr = parts[0];
|
|
1334
|
-
const filters = parts.slice(1).map((f) => {
|
|
1335
|
-
const m = f.match(/^(\w+)\((.*)\)$/s);
|
|
1336
|
-
if (m) {
|
|
1337
|
-
// Parse args — simple comma split respecting strings
|
|
1338
|
-
const rawArgs = m[2].trim();
|
|
1339
|
-
const args = rawArgs ? splitFilterArgs(rawArgs) : [];
|
|
1340
|
-
return { name: m[1], args };
|
|
1341
|
-
}
|
|
1342
|
-
return { name: f.trim(), args: [] };
|
|
1343
|
-
});
|
|
1344
|
-
return { baseExpr, filters };
|
|
1345
|
-
}
|
|
1346
|
-
function splitFilterArgs(raw) {
|
|
1347
|
-
const args = [];
|
|
1348
|
-
let current = "";
|
|
1349
|
-
let inStr = null;
|
|
1350
|
-
let depth = 0;
|
|
1351
|
-
for (const ch of raw) {
|
|
1352
|
-
if (inStr) {
|
|
1353
|
-
current += ch;
|
|
1354
|
-
if (ch === inStr)
|
|
1355
|
-
inStr = null;
|
|
1356
|
-
continue;
|
|
1357
|
-
}
|
|
1358
|
-
if (ch === '"' || ch === "'") {
|
|
1359
|
-
inStr = ch;
|
|
1360
|
-
current += ch;
|
|
1361
|
-
continue;
|
|
1362
|
-
}
|
|
1363
|
-
if (ch === "(") {
|
|
1364
|
-
depth++;
|
|
1365
|
-
current += ch;
|
|
1366
|
-
continue;
|
|
1367
|
-
}
|
|
1368
|
-
if (ch === ")") {
|
|
1369
|
-
depth--;
|
|
1370
|
-
current += ch;
|
|
1371
|
-
continue;
|
|
1372
|
-
}
|
|
1373
|
-
if (ch === "," && depth === 0) {
|
|
1374
|
-
args.push(current.trim());
|
|
1375
|
-
current = "";
|
|
1376
|
-
continue;
|
|
1377
|
-
}
|
|
1378
|
-
current += ch;
|
|
1379
|
-
}
|
|
1380
|
-
args.push(current.trim());
|
|
1381
|
-
return args;
|
|
1382
|
-
}
|
|
1383
|
-
/** Patterns that must never appear in evaluated expressions. */
|
|
1384
|
-
const FORBIDDEN_EXPR = /constructor|__proto__|prototype|globalThis|process|require|import\s*\(|eval\s*\(/;
|
|
1385
|
-
/**
|
|
1386
|
-
* Safe expression evaluator using Node.js VM sandbox.
|
|
1387
|
-
* Provides stronger isolation than `new Function()` with a 50ms timeout
|
|
1388
|
-
* to prevent DoS. Simple dotted access (the most common case) uses a
|
|
1389
|
-
* fast path that avoids the VM overhead entirely.
|
|
1390
|
-
*
|
|
1391
|
-
* Supports pipe filters: ${{ expr | join(', ') | slice(0, 100) }}
|
|
1392
|
-
*/
|
|
1393
|
-
function evalExpression(expr, scope) {
|
|
1394
|
-
try {
|
|
1395
|
-
// Security: reject dangerous patterns
|
|
1396
|
-
if (FORBIDDEN_EXPR.test(expr))
|
|
1397
|
-
return undefined;
|
|
1398
|
-
const { baseExpr, filters } = parsePipes(expr);
|
|
1399
|
-
// Fast path: simple dotted access like "item.title" or "args.query"
|
|
1400
|
-
if (/^[a-zA-Z_][\w.]*(\[\d+\])?$/.test(baseExpr)) {
|
|
1401
|
-
let result = resolveDottedPath(baseExpr, scope);
|
|
1402
|
-
for (const filter of filters) {
|
|
1403
|
-
const filterFn = PIPE_FILTERS[filter.name];
|
|
1404
|
-
if (!filterFn)
|
|
1405
|
-
continue;
|
|
1406
|
-
const evaledArgs = filter.args.map((a) => resolveFilterArg(a, scope));
|
|
1407
|
-
result = filterFn(result, ...evaledArgs);
|
|
1408
|
-
}
|
|
1409
|
-
return result;
|
|
1410
|
-
}
|
|
1411
|
-
// VM sandbox evaluation with 50ms timeout.
|
|
1412
|
-
// SECURITY: Create a null-prototype sandbox to prevent prototype chain escape.
|
|
1413
|
-
// Node.js vm is NOT a security boundary — host objects leak constructors.
|
|
1414
|
-
// We mitigate by: (1) null-prototype sandbox, (2) frozen copies of built-ins,
|
|
1415
|
-
// (3) contextCodeGeneration restriction, (4) FORBIDDEN_EXPR pre-check.
|
|
1416
|
-
const sandbox = Object.create(null);
|
|
1417
|
-
// Copy scope values (args, item, index, etc.) — shallow copy with null prototype
|
|
1418
|
-
for (const [k, v] of Object.entries(scope)) {
|
|
1419
|
-
sandbox[k] = v;
|
|
1420
|
-
}
|
|
1421
|
-
// Add safe built-ins as frozen copies (prevents constructor chain traversal)
|
|
1422
|
-
sandbox.encodeURIComponent = encodeURIComponent;
|
|
1423
|
-
sandbox.decodeURIComponent = decodeURIComponent;
|
|
1424
|
-
sandbox.JSON = { parse: JSON.parse, stringify: JSON.stringify };
|
|
1425
|
-
sandbox.Math = Object.freeze({ ...Math });
|
|
1426
|
-
sandbox.parseInt = parseInt;
|
|
1427
|
-
sandbox.parseFloat = parseFloat;
|
|
1428
|
-
sandbox.isNaN = isNaN;
|
|
1429
|
-
sandbox.isFinite = isFinite;
|
|
1430
|
-
let result;
|
|
1431
|
-
try {
|
|
1432
|
-
result = runInNewContext(`(${baseExpr})`, sandbox, {
|
|
1433
|
-
timeout: 50,
|
|
1434
|
-
contextCodeGeneration: { strings: false, wasm: false },
|
|
1435
|
-
});
|
|
1436
|
-
}
|
|
1437
|
-
catch {
|
|
1438
|
-
return undefined;
|
|
1439
|
-
}
|
|
1440
|
-
// Apply pipe filters
|
|
1441
|
-
for (const filter of filters) {
|
|
1442
|
-
const filterFn = PIPE_FILTERS[filter.name];
|
|
1443
|
-
if (!filterFn)
|
|
1444
|
-
continue;
|
|
1445
|
-
const evaledArgs = filter.args.map((a) => resolveFilterArg(a, scope));
|
|
1446
|
-
result = filterFn(result, ...evaledArgs);
|
|
1447
|
-
}
|
|
1448
|
-
return result;
|
|
1449
|
-
}
|
|
1450
|
-
catch {
|
|
1451
|
-
return undefined;
|
|
1452
|
-
}
|
|
1453
|
-
}
|
|
1454
|
-
/** Resolve a dotted path like "item.tags[0]" against the scope object. */
|
|
1455
|
-
function resolveDottedPath(path, scope) {
|
|
1456
|
-
// Handle array index: "item.tags[0]"
|
|
1457
|
-
const cleanPath = path.replace(/\[(\d+)\]/g, ".$1");
|
|
1458
|
-
const parts = cleanPath.split(".");
|
|
1459
|
-
let current = scope[parts[0]];
|
|
1460
|
-
for (let i = 1; i < parts.length; i++) {
|
|
1461
|
-
if (current == null || typeof current !== "object")
|
|
1462
|
-
return undefined;
|
|
1463
|
-
current = current[parts[i]];
|
|
1464
|
-
}
|
|
1465
|
-
return current;
|
|
1466
|
-
}
|
|
1467
|
-
/** Resolve a single filter argument — string literal, number, or expression. */
|
|
1468
|
-
function resolveFilterArg(a, scope) {
|
|
1469
|
-
// String literal
|
|
1470
|
-
if ((a.startsWith("'") && a.endsWith("'")) ||
|
|
1471
|
-
(a.startsWith('"') && a.endsWith('"'))) {
|
|
1472
|
-
return a.slice(1, -1);
|
|
1473
|
-
}
|
|
1474
|
-
// Number
|
|
1475
|
-
if (/^-?\d+(\.\d+)?$/.test(a))
|
|
1476
|
-
return Number(a);
|
|
1477
|
-
// Security check
|
|
1478
|
-
if (FORBIDDEN_EXPR.test(a))
|
|
1479
|
-
return a;
|
|
1480
|
-
// Expression via VM (same hardened sandbox as evalExpression)
|
|
1481
|
-
try {
|
|
1482
|
-
const sandbox = Object.create(null);
|
|
1483
|
-
for (const [k, v] of Object.entries(scope))
|
|
1484
|
-
sandbox[k] = v;
|
|
1485
|
-
sandbox.JSON = { parse: JSON.parse, stringify: JSON.stringify };
|
|
1486
|
-
sandbox.Math = Object.freeze({ ...Math });
|
|
1487
|
-
sandbox.parseInt = parseInt;
|
|
1488
|
-
sandbox.parseFloat = parseFloat;
|
|
1489
|
-
return runInNewContext(`(${a})`, sandbox, {
|
|
1490
|
-
timeout: 50,
|
|
1491
|
-
contextCodeGeneration: { strings: false, wasm: false },
|
|
1492
|
-
});
|
|
1493
|
-
}
|
|
1494
|
-
catch {
|
|
1495
|
-
return a;
|
|
1496
|
-
}
|
|
1497
|
-
}
|
|
1498
|
-
function stepWriteTemp(ctx, config) {
|
|
1499
|
-
const td = ctx.tempDir ?? join(tmpdir(), `unicli-${randomBytes(6).toString("hex")}`);
|
|
1500
|
-
mkdirSync(td, { recursive: true });
|
|
1501
|
-
const filename = evalTemplate(config.filename, ctx);
|
|
1502
|
-
const content = evalTemplate(config.content, ctx);
|
|
1503
|
-
const filePath = join(td, filename);
|
|
1504
|
-
writeFileSync(filePath, content, "utf-8");
|
|
1505
|
-
const key = filename.replace(/[^a-zA-Z0-9]/g, "_");
|
|
1506
|
-
const temp = { ...(ctx.temp ?? {}), [key]: filePath };
|
|
1507
|
-
return { ...ctx, temp, tempDir: td };
|
|
1508
|
-
}
|
|
1509
|
-
// --- Browser step implementations ---
|
|
1510
|
-
/**
|
|
1511
|
-
* Lazily acquire a BrowserPage. Connects on first use and caches on ctx.
|
|
1512
|
-
*/
|
|
1513
|
-
async function acquirePage(ctx) {
|
|
1514
|
-
if (ctx.page)
|
|
1515
|
-
return ctx.page;
|
|
1516
|
-
let port = 9222;
|
|
1517
|
-
const rawPort = process.env.UNICLI_CDP_PORT;
|
|
1518
|
-
if (rawPort) {
|
|
1519
|
-
const p = parseInt(rawPort, 10);
|
|
1520
|
-
if (Number.isInteger(p) && p >= 1 && p <= 65535) {
|
|
1521
|
-
port = p;
|
|
1522
|
-
}
|
|
1523
|
-
}
|
|
1524
|
-
// 1. Try direct CDP first (fastest, no daemon overhead)
|
|
1525
|
-
try {
|
|
1526
|
-
const { BrowserPage: BP } = await import("../browser/page.js");
|
|
1527
|
-
const { injectStealth } = await import("../browser/stealth.js");
|
|
1528
|
-
const page = await BP.connect(port);
|
|
1529
|
-
await injectStealth(page.sendCDP.bind(page));
|
|
1530
|
-
return page;
|
|
1531
|
-
}
|
|
1532
|
-
catch {
|
|
1533
|
-
// CDP not available — try daemon
|
|
1534
|
-
}
|
|
1535
|
-
// 2. Fallback: daemon (reuses Chrome login sessions via extension)
|
|
1536
|
-
try {
|
|
1537
|
-
const { checkDaemonStatus } = await import("../browser/discover.js");
|
|
1538
|
-
const status = await checkDaemonStatus({ timeout: 300 });
|
|
1539
|
-
if (status.running && status.extensionConnected) {
|
|
1540
|
-
const { BrowserBridge } = await import("../browser/bridge.js");
|
|
1541
|
-
const bridge = new BrowserBridge();
|
|
1542
|
-
const page = await bridge.connect({ timeout: 5000 });
|
|
1543
|
-
return page;
|
|
1544
|
-
}
|
|
1545
|
-
}
|
|
1546
|
-
catch {
|
|
1547
|
-
// Daemon not available either
|
|
1548
|
-
}
|
|
1549
|
-
// 3. Last resort: auto-launch Chrome with debug port
|
|
1550
|
-
try {
|
|
1551
|
-
const { launchChrome } = await import("../browser/launcher.js");
|
|
1552
|
-
const { BrowserPage: BP } = await import("../browser/page.js");
|
|
1553
|
-
const { injectStealth } = await import("../browser/stealth.js");
|
|
1554
|
-
await launchChrome(port);
|
|
1555
|
-
// Poll for connection (5 attempts, 500ms intervals)
|
|
1556
|
-
let page;
|
|
1557
|
-
for (let attempt = 0; attempt < 5; attempt++) {
|
|
1558
|
-
try {
|
|
1559
|
-
page = await BP.connect(port);
|
|
1560
|
-
break;
|
|
1561
|
-
}
|
|
1562
|
-
catch {
|
|
1563
|
-
if (attempt < 4)
|
|
1564
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
1565
|
-
}
|
|
1566
|
-
}
|
|
1567
|
-
if (!page)
|
|
1568
|
-
throw new Error("Chrome launched but no page target available");
|
|
1569
|
-
await injectStealth(page.sendCDP.bind(page));
|
|
1570
|
-
return page;
|
|
1571
|
-
}
|
|
1572
|
-
catch (err) {
|
|
1573
|
-
throw new Error(`Cannot connect to Chrome. Run "unicli browser start" first. (${err instanceof Error ? err.message : String(err)})`);
|
|
1574
|
-
}
|
|
1575
|
-
}
|
|
1576
|
-
/**
|
|
1577
|
-
* Wait until no new network requests occur for quietMs.
|
|
1578
|
-
* Uses polling — checks page.networkRequests() count stability.
|
|
1579
|
-
*/
|
|
1580
|
-
async function waitForNetworkIdle(page, maxMs = 5000, quietMs = 500) {
|
|
1581
|
-
const start = Date.now();
|
|
1582
|
-
let lastCount = -1;
|
|
1583
|
-
let stableSince = Date.now();
|
|
1584
|
-
while (Date.now() - start < maxMs) {
|
|
1585
|
-
const requests = await page.networkRequests();
|
|
1586
|
-
const currentCount = requests.length;
|
|
1587
|
-
if (currentCount !== lastCount) {
|
|
1588
|
-
lastCount = currentCount;
|
|
1589
|
-
stableSince = Date.now();
|
|
1590
|
-
}
|
|
1591
|
-
else if (Date.now() - stableSince >= quietMs) {
|
|
1592
|
-
return;
|
|
1593
|
-
}
|
|
1594
|
-
await page.waitFor(100);
|
|
1595
|
-
}
|
|
1596
|
-
}
|
|
1597
|
-
async function stepNavigate(ctx, config) {
|
|
1598
|
-
const page = await acquirePage(ctx);
|
|
1599
|
-
const url = evalTemplate(config.url, ctx);
|
|
1600
|
-
const settleMs = config.settleMs ?? 0;
|
|
1601
|
-
await page.goto(url, { settleMs, waitUntil: config.waitUntil });
|
|
1602
|
-
if (config.waitUntil === "networkidle") {
|
|
1603
|
-
await waitForNetworkIdle(page, 5000, 500);
|
|
1604
|
-
}
|
|
1605
|
-
return { ...ctx, page };
|
|
1606
|
-
}
|
|
1607
|
-
async function stepEvaluate(ctx, config) {
|
|
1608
|
-
const page = await acquirePage(ctx);
|
|
1609
|
-
const expr = typeof config === "string"
|
|
1610
|
-
? evalTemplate(config, ctx)
|
|
1611
|
-
: evalTemplate(config.expression, ctx);
|
|
1612
|
-
const result = await page.evaluate(expr);
|
|
1613
|
-
return { ...ctx, data: result, page };
|
|
1614
|
-
}
|
|
1615
|
-
async function stepClick(ctx, config) {
|
|
1616
|
-
const page = await acquirePage(ctx);
|
|
1617
|
-
// String shorthand: just a CSS selector
|
|
1618
|
-
if (typeof config === "string") {
|
|
1619
|
-
const selector = evalTemplate(config, ctx);
|
|
1620
|
-
await page.click(selector);
|
|
1621
|
-
return { ...ctx, page };
|
|
1622
|
-
}
|
|
1623
|
-
// Coordinate-based click
|
|
1624
|
-
if (config.x !== undefined && config.y !== undefined) {
|
|
1625
|
-
await page.nativeClick(config.x, config.y);
|
|
1626
|
-
return { ...ctx, page };
|
|
1627
|
-
}
|
|
1628
|
-
// Selector-based click
|
|
1629
|
-
if (config.selector) {
|
|
1630
|
-
const selector = evalTemplate(config.selector, ctx);
|
|
1631
|
-
await page.click(selector);
|
|
1632
|
-
return { ...ctx, page };
|
|
1633
|
-
}
|
|
1634
|
-
throw new PipelineError("click step requires either selector or x/y coordinates", {
|
|
1635
|
-
step: -1,
|
|
1636
|
-
action: "click",
|
|
1637
|
-
config,
|
|
1638
|
-
errorType: "expression_error",
|
|
1639
|
-
suggestion: 'Provide either a CSS selector string, {selector: "..."}, or {x: N, y: N} for coordinate click.',
|
|
1640
|
-
retryable: false,
|
|
1641
|
-
alternatives: [],
|
|
1642
|
-
});
|
|
1643
|
-
}
|
|
1644
|
-
async function stepType(ctx, config) {
|
|
1645
|
-
const page = await acquirePage(ctx);
|
|
1646
|
-
const text = evalTemplate(config.text, ctx);
|
|
1647
|
-
if (config.selector) {
|
|
1648
|
-
const selector = evalTemplate(config.selector, ctx);
|
|
1649
|
-
await page.type(selector, text);
|
|
1650
|
-
}
|
|
1651
|
-
else {
|
|
1652
|
-
// No selector — type into currently focused element via CDP
|
|
1653
|
-
await page.sendCDP("Input.insertText", { text });
|
|
1654
|
-
}
|
|
1655
|
-
if (config.submit)
|
|
1656
|
-
await page.press("Enter");
|
|
1657
|
-
return { ...ctx, page };
|
|
1658
|
-
}
|
|
1659
|
-
async function stepWaitBrowser(ctx, config) {
|
|
1660
|
-
const page = await acquirePage(ctx);
|
|
1661
|
-
if (typeof config === "number") {
|
|
1662
|
-
await page.waitFor(config);
|
|
1663
|
-
}
|
|
1664
|
-
else if (config.selector) {
|
|
1665
|
-
await page.waitFor(config.selector, config.timeout ?? 10000);
|
|
1666
|
-
}
|
|
1667
|
-
else if (config.ms) {
|
|
1668
|
-
await page.waitFor(config.ms);
|
|
1669
|
-
}
|
|
1670
|
-
return { ...ctx, page };
|
|
1671
|
-
}
|
|
1672
|
-
async function stepIntercept(ctx, config) {
|
|
1673
|
-
const page = await acquirePage(ctx);
|
|
1674
|
-
const capturePattern = evalTemplate(config.capture, ctx);
|
|
1675
|
-
const timeout = config.timeout ?? 10000;
|
|
1676
|
-
// Install interceptor: patch fetch + XHR to capture matching responses
|
|
1677
|
-
await page.evaluate(generateInterceptorJs(capturePattern, {
|
|
1678
|
-
regex: config.regex,
|
|
1679
|
-
captureAll: config.all,
|
|
1680
|
-
captureText: config.captureText,
|
|
1681
|
-
}));
|
|
1682
|
-
// Execute trigger action
|
|
1683
|
-
const trigger = evalTemplate(config.trigger, ctx);
|
|
1684
|
-
if (trigger.startsWith("navigate:")) {
|
|
1685
|
-
await page.goto(trigger.slice(9), { settleMs: 2000 });
|
|
1686
|
-
}
|
|
1687
|
-
else if (trigger.startsWith("click:")) {
|
|
1688
|
-
await page.click(trigger.slice(6));
|
|
1689
|
-
}
|
|
1690
|
-
else if (trigger === "scroll") {
|
|
1691
|
-
await page.scroll("down");
|
|
1692
|
-
}
|
|
1693
|
-
else if (trigger.startsWith("evaluate:")) {
|
|
1694
|
-
await page.evaluate(trigger.slice(9));
|
|
1695
|
-
}
|
|
1696
|
-
// Poll for captured response
|
|
1697
|
-
const startTime = Date.now();
|
|
1698
|
-
let captured = null;
|
|
1699
|
-
while (Date.now() - startTime < timeout) {
|
|
1700
|
-
const result = await page.evaluate(generateReadInterceptedJs());
|
|
1701
|
-
const arr = JSON.parse(result);
|
|
1702
|
-
if (arr.length > 0) {
|
|
1703
|
-
if (config.all) {
|
|
1704
|
-
captured = arr.map((item) => item.data);
|
|
1705
|
-
}
|
|
1706
|
-
else {
|
|
1707
|
-
captured = arr[arr.length - 1].data;
|
|
1708
|
-
}
|
|
1709
|
-
break;
|
|
1710
|
-
}
|
|
1711
|
-
await page.waitFor(200);
|
|
1712
|
-
}
|
|
1713
|
-
if (!captured) {
|
|
1714
|
-
throw new PipelineError(`Intercept timeout: no request matching "${capturePattern}" captured within ${String(timeout)}ms`, {
|
|
1715
|
-
step: -1,
|
|
1716
|
-
action: "intercept",
|
|
1717
|
-
config: { capture: capturePattern, trigger },
|
|
1718
|
-
errorType: "timeout",
|
|
1719
|
-
suggestion: `No network request matching "${capturePattern}" was observed. Verify the capture pattern matches the target API URL and that the trigger action causes the request.`,
|
|
1720
|
-
retryable: true,
|
|
1721
|
-
alternatives: [],
|
|
1722
|
-
});
|
|
1723
|
-
}
|
|
1724
|
-
// Apply optional dot-path selector to captured data
|
|
1725
|
-
let data = captured;
|
|
1726
|
-
if (config.select) {
|
|
1727
|
-
const segments = config.select.split(".");
|
|
1728
|
-
for (const key of segments) {
|
|
1729
|
-
if (data !== null && data !== undefined && typeof data === "object") {
|
|
1730
|
-
data = data[key];
|
|
1731
|
-
}
|
|
1732
|
-
}
|
|
1733
|
-
}
|
|
1734
|
-
return { ...ctx, data, page };
|
|
1735
|
-
}
|
|
1736
|
-
// --- press: keyboard event dispatch ---
|
|
1737
|
-
async function stepPress(ctx, config) {
|
|
1738
|
-
const page = await acquirePage(ctx);
|
|
1739
|
-
if (typeof config === "string") {
|
|
1740
|
-
await page.press(evalTemplate(config, ctx));
|
|
1741
|
-
}
|
|
1742
|
-
else {
|
|
1743
|
-
const cfg = config;
|
|
1744
|
-
const key = evalTemplate(cfg.key, ctx);
|
|
1745
|
-
if (cfg.modifiers && cfg.modifiers.length > 0) {
|
|
1746
|
-
await page.nativeKeyPress(key, cfg.modifiers);
|
|
1747
|
-
}
|
|
1748
|
-
else {
|
|
1749
|
-
await page.press(key);
|
|
1750
|
-
}
|
|
1751
|
-
}
|
|
1752
|
-
return { ...ctx, page };
|
|
1753
|
-
}
|
|
1754
|
-
// --- scroll: page scrolling ---
|
|
1755
|
-
async function stepScroll(ctx, config) {
|
|
1756
|
-
const page = await acquirePage(ctx);
|
|
1757
|
-
if (typeof config === "string") {
|
|
1758
|
-
await page.scroll(config);
|
|
1759
|
-
}
|
|
1760
|
-
else {
|
|
1761
|
-
const cfg = config;
|
|
1762
|
-
if (cfg.auto) {
|
|
1763
|
-
await page.autoScroll({ maxScrolls: cfg.max, delay: cfg.delay });
|
|
1764
|
-
}
|
|
1765
|
-
else if (cfg.selector) {
|
|
1766
|
-
const sel = evalTemplate(cfg.selector, ctx);
|
|
1767
|
-
const escaped = sel.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
1768
|
-
await page.evaluate(`document.querySelector('${escaped}')?.scrollIntoView({ behavior: 'smooth', block: 'center' })`);
|
|
1769
|
-
}
|
|
1770
|
-
else if (cfg.to) {
|
|
1771
|
-
await page.scroll(cfg.to);
|
|
1772
|
-
}
|
|
1773
|
-
}
|
|
1774
|
-
return { ...ctx, page };
|
|
1775
|
-
}
|
|
1776
|
-
// --- snapshot: DOM accessibility tree ---
|
|
1777
|
-
async function stepSnapshot(ctx, config) {
|
|
1778
|
-
const page = await acquirePage(ctx);
|
|
1779
|
-
const opts = typeof config === "object" && config !== null
|
|
1780
|
-
? config
|
|
1781
|
-
: {};
|
|
1782
|
-
// Normalize max_depth to maxDepth for BrowserPage.snapshot
|
|
1783
|
-
const normalizedOpts = {
|
|
1784
|
-
interactive: opts.interactive,
|
|
1785
|
-
compact: opts.compact,
|
|
1786
|
-
maxDepth: opts.max_depth,
|
|
1787
|
-
raw: opts.raw,
|
|
1788
|
-
};
|
|
1789
|
-
const result = await page.snapshot(normalizedOpts);
|
|
1790
|
-
return { ...ctx, data: result, page };
|
|
1791
|
-
}
|
|
1792
|
-
async function stepTap(ctx, config) {
|
|
1793
|
-
const page = await acquirePage(ctx);
|
|
1794
|
-
const { generateTapInterceptorJs } = await import("./interceptor.js");
|
|
1795
|
-
const capturePattern = evalTemplate(config.capture, ctx);
|
|
1796
|
-
const timeout = (config.timeout ?? 5) * 1000;
|
|
1797
|
-
const storeName = evalTemplate(config.store, ctx);
|
|
1798
|
-
const actionName = evalTemplate(config.action, ctx);
|
|
1799
|
-
// Sanitize store/action names to prevent JS injection in page context
|
|
1800
|
-
if (!/^[a-zA-Z_$][\w$]*$/.test(storeName)) {
|
|
1801
|
-
throw new PipelineError(`Invalid store name: "${storeName}"`, {
|
|
1802
|
-
step: -1,
|
|
1803
|
-
action: "tap",
|
|
1804
|
-
config,
|
|
1805
|
-
errorType: "expression_error",
|
|
1806
|
-
suggestion: "Store name must be a valid JavaScript identifier.",
|
|
1807
|
-
retryable: false,
|
|
1808
|
-
alternatives: [],
|
|
1809
|
-
});
|
|
1810
|
-
}
|
|
1811
|
-
if (!/^[a-zA-Z_$][\w$]*$/.test(actionName)) {
|
|
1812
|
-
throw new PipelineError(`Invalid action name: "${actionName}"`, {
|
|
1813
|
-
step: -1,
|
|
1814
|
-
action: "tap",
|
|
1815
|
-
config,
|
|
1816
|
-
errorType: "expression_error",
|
|
1817
|
-
suggestion: "Action name must be a valid JavaScript identifier.",
|
|
1818
|
-
retryable: false,
|
|
1819
|
-
alternatives: [],
|
|
1820
|
-
});
|
|
1821
|
-
}
|
|
1822
|
-
const framework = config.framework ?? "auto";
|
|
1823
|
-
const actionArgs = config.args
|
|
1824
|
-
? config.args.map((a) => JSON.stringify(a)).join(", ")
|
|
1825
|
-
: "";
|
|
1826
|
-
const tap = generateTapInterceptorJs(capturePattern);
|
|
1827
|
-
// Build optional select chain (escape keys to prevent JS injection)
|
|
1828
|
-
const selectChain = config.select
|
|
1829
|
-
? config.select
|
|
1830
|
-
.split(".")
|
|
1831
|
-
.map((k) => `?.[${JSON.stringify(k)}]`)
|
|
1832
|
-
.join("")
|
|
1833
|
-
: "";
|
|
1834
|
-
// Store discovery based on framework
|
|
1835
|
-
const piniaDiscovery = `
|
|
1836
|
-
const pinia = document.querySelector('#app')?.__vue_app__?.config?.globalProperties?.$pinia;
|
|
1837
|
-
if (!pinia) throw new Error('Pinia not found');
|
|
1838
|
-
const store = pinia._s.get('${storeName}');
|
|
1839
|
-
if (!store) throw new Error('Store "${storeName}" not found');
|
|
1840
|
-
await store['${actionName}'](${actionArgs});
|
|
1841
|
-
`;
|
|
1842
|
-
const vuexDiscovery = `
|
|
1843
|
-
const vStore = document.querySelector('#app')?.__vue_app__?.config?.globalProperties?.$store;
|
|
1844
|
-
if (!vStore) throw new Error('Vuex store not found');
|
|
1845
|
-
await vStore.dispatch('${storeName}/${actionName}'${actionArgs ? ", " + actionArgs : ""});
|
|
1846
|
-
`;
|
|
1847
|
-
const autoDiscovery = `
|
|
1848
|
-
const app = document.querySelector('#app')?.__vue_app__;
|
|
1849
|
-
if (!app) throw new Error('No Vue app found');
|
|
1850
|
-
const pinia = app.config?.globalProperties?.$pinia;
|
|
1851
|
-
if (pinia && pinia._s.has('${storeName}')) {
|
|
1852
|
-
const store = pinia._s.get('${storeName}');
|
|
1853
|
-
await store['${actionName}'](${actionArgs});
|
|
1854
|
-
} else {
|
|
1855
|
-
const vStore = app.config?.globalProperties?.$store;
|
|
1856
|
-
if (vStore) {
|
|
1857
|
-
await vStore.dispatch('${storeName}/${actionName}'${actionArgs ? ", " + actionArgs : ""});
|
|
1858
|
-
} else {
|
|
1859
|
-
throw new Error('No Pinia or Vuex store found');
|
|
1860
|
-
}
|
|
1861
|
-
}
|
|
1862
|
-
`;
|
|
1863
|
-
const storeCode = framework === "pinia"
|
|
1864
|
-
? piniaDiscovery
|
|
1865
|
-
: framework === "vuex"
|
|
1866
|
-
? vuexDiscovery
|
|
1867
|
-
: autoDiscovery;
|
|
1868
|
-
const script = `(async () => {
|
|
1869
|
-
${tap.setupVar}
|
|
1870
|
-
${tap.fetchPatch}
|
|
1871
|
-
${tap.xhrPatch}
|
|
1872
|
-
try {
|
|
1873
|
-
${storeCode}
|
|
1874
|
-
const result = await Promise.race([
|
|
1875
|
-
${tap.promiseVar},
|
|
1876
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('tap timeout')), ${timeout})),
|
|
1877
|
-
]);
|
|
1878
|
-
return JSON.stringify(result${selectChain});
|
|
1879
|
-
} finally {
|
|
1880
|
-
${tap.restorePatch}
|
|
1881
|
-
}
|
|
1882
|
-
})()`;
|
|
1883
|
-
const raw = await page.evaluate(script);
|
|
1884
|
-
let data;
|
|
1885
|
-
if (typeof raw === "string") {
|
|
1886
|
-
try {
|
|
1887
|
-
data = JSON.parse(raw);
|
|
1888
|
-
}
|
|
1889
|
-
catch {
|
|
1890
|
-
data = raw;
|
|
1891
|
-
}
|
|
1892
|
-
}
|
|
1893
|
-
else {
|
|
1894
|
-
data = raw;
|
|
1895
|
-
}
|
|
1896
|
-
return { ...ctx, data, page };
|
|
1897
|
-
}
|
|
1898
|
-
/**
|
|
1899
|
-
* Navigate nested object by dot-path: "data.list[].title"
|
|
1900
|
-
*/
|
|
1901
|
-
function getNestedValue(obj, path) {
|
|
1902
|
-
const parts = path.split(".");
|
|
1903
|
-
let current = obj;
|
|
1904
|
-
for (const part of parts) {
|
|
1905
|
-
if (current == null)
|
|
1906
|
-
return undefined;
|
|
1907
|
-
if (part.endsWith("[]")) {
|
|
1908
|
-
const key = part.slice(0, -2);
|
|
1909
|
-
if (key) {
|
|
1910
|
-
current = current[key];
|
|
1911
|
-
}
|
|
1912
|
-
// current should now be an array — continue traversing
|
|
1913
|
-
}
|
|
1914
|
-
else {
|
|
1915
|
-
current = current[part];
|
|
1916
|
-
}
|
|
1917
|
-
}
|
|
1918
|
-
return current;
|
|
1919
|
-
}
|
|
1920
|
-
async function stepDownload(ctx, config) {
|
|
1921
|
-
const dir = resolve(config.dir ?? "./downloads");
|
|
1922
|
-
mkdirSync(dir, { recursive: true });
|
|
1923
|
-
const concurrency = config.concurrency ?? 3;
|
|
1924
|
-
const skipExisting = config.skip_existing !== false; // default true
|
|
1925
|
-
const cookieHeader = ctx.cookieHeader;
|
|
1926
|
-
async function downloadOne(item, index) {
|
|
1927
|
-
const itemCtx = { ...ctx, data: { item, index } };
|
|
1928
|
-
const url = evalTemplate(config.url, itemCtx);
|
|
1929
|
-
const filename = config.filename
|
|
1930
|
-
? evalTemplate(config.filename, itemCtx)
|
|
1931
|
-
: generateFilename(url, index);
|
|
1932
|
-
const destPath = join(dir, sanitizeFilename(filename));
|
|
1933
|
-
if (skipExisting && existsSync(destPath)) {
|
|
1934
|
-
return { ...item, _download: { status: "skipped", path: destPath } };
|
|
1935
|
-
}
|
|
1936
|
-
const useYtdlp = config.use_ytdlp ?? (config.type === "video" && requiresYtdlp(url));
|
|
1937
|
-
let result;
|
|
1938
|
-
if (config.type === "document" && config.content) {
|
|
1939
|
-
const content = evalTemplate(config.content, itemCtx);
|
|
1940
|
-
writeFileSync(destPath, content, "utf-8");
|
|
1941
|
-
const info = await stat(destPath);
|
|
1942
|
-
result = {
|
|
1943
|
-
status: "success",
|
|
1944
|
-
path: destPath,
|
|
1945
|
-
size: info.size,
|
|
1946
|
-
duration: 0,
|
|
1947
|
-
};
|
|
1948
|
-
}
|
|
1949
|
-
else if (useYtdlp) {
|
|
1950
|
-
result = await ytdlpDownload(url, dir);
|
|
1951
|
-
}
|
|
1952
|
-
else {
|
|
1953
|
-
const headers = {};
|
|
1954
|
-
if (cookieHeader)
|
|
1955
|
-
headers["Cookie"] = cookieHeader;
|
|
1956
|
-
result = await httpDownload(url, destPath, headers);
|
|
1957
|
-
}
|
|
1958
|
-
return { ...item, _download: result };
|
|
1959
|
-
}
|
|
1960
|
-
if (Array.isArray(ctx.data)) {
|
|
1961
|
-
const items = ctx.data;
|
|
1962
|
-
const results = await mapConcurrent(items, concurrency, downloadOne);
|
|
1963
|
-
return { ...ctx, data: results };
|
|
1964
|
-
}
|
|
1965
|
-
else {
|
|
1966
|
-
const item = (ctx.data ?? {});
|
|
1967
|
-
const result = await downloadOne(item, 0);
|
|
1968
|
-
return { ...ctx, data: [result] };
|
|
1969
|
-
}
|
|
1970
|
-
}
|
|
1971
|
-
async function stepWebsocket(ctx, config) {
|
|
1972
|
-
const resolvedConfig = {
|
|
1973
|
-
...config,
|
|
1974
|
-
url: evalTemplate(config.url, ctx),
|
|
1975
|
-
send: evalTemplate(config.send, ctx),
|
|
1976
|
-
};
|
|
1977
|
-
const data = await executeWebsocket(resolvedConfig);
|
|
1978
|
-
return { ...ctx, data };
|
|
1979
|
-
}
|
|
1980
|
-
async function stepExtract(ctx, config) {
|
|
1981
|
-
const page = await acquirePage(ctx);
|
|
1982
|
-
const containerSelector = evalTemplate(config.from, ctx);
|
|
1983
|
-
// Build a JS expression that extracts structured data
|
|
1984
|
-
const fieldEntries = Object.entries(config.fields);
|
|
1985
|
-
const fieldJs = fieldEntries
|
|
1986
|
-
.map(([key, def]) => {
|
|
1987
|
-
const sel = JSON.stringify(def.selector);
|
|
1988
|
-
const attr = def.attribute ? JSON.stringify(def.attribute) : null;
|
|
1989
|
-
const pattern = def.pattern ? JSON.stringify(def.pattern) : null;
|
|
1990
|
-
const type = def.type ?? "text";
|
|
1991
|
-
if (type === "attribute" || attr) {
|
|
1992
|
-
return `${JSON.stringify(key)}: (() => { const el = item.querySelector(${sel}); return el ? el.getAttribute(${attr ?? JSON.stringify("href")}) : null; })()`;
|
|
1993
|
-
}
|
|
1994
|
-
else if (type === "number") {
|
|
1995
|
-
return `${JSON.stringify(key)}: (() => { const el = item.querySelector(${sel}); if (!el) return null; const txt = el.textContent || ''; ${pattern ? `const m = txt.match(new RegExp(${pattern})); return m ? parseFloat(m[0]) : null;` : `return parseFloat(txt.replace(/[^\\d.-]/g, '')) || null;`} })()`;
|
|
1996
|
-
}
|
|
1997
|
-
else if (type === "html") {
|
|
1998
|
-
return `${JSON.stringify(key)}: (() => { const el = item.querySelector(${sel}); return el ? el.innerHTML : null; })()`;
|
|
1999
|
-
}
|
|
2000
|
-
else {
|
|
2001
|
-
// text (default)
|
|
2002
|
-
if (pattern) {
|
|
2003
|
-
return `${JSON.stringify(key)}: (() => { const el = item.querySelector(${sel}); if (!el) return null; const txt = el.textContent || ''; const m = txt.match(new RegExp(${pattern})); return m ? (m[1] || m[0]) : txt.trim(); })()`;
|
|
2004
|
-
}
|
|
2005
|
-
return `${JSON.stringify(key)}: (() => { const el = item.querySelector(${sel}); return el ? el.textContent.trim() : null; })()`;
|
|
2006
|
-
}
|
|
2007
|
-
})
|
|
2008
|
-
.join(",\n ");
|
|
2009
|
-
const extractJs = `
|
|
2010
|
-
JSON.stringify(
|
|
2011
|
-
Array.from(document.querySelectorAll(${JSON.stringify(containerSelector)})).map(item => ({
|
|
2012
|
-
${fieldJs}
|
|
2013
|
-
}))
|
|
2014
|
-
)
|
|
2015
|
-
`;
|
|
2016
|
-
const resultStr = (await page.evaluate(extractJs));
|
|
2017
|
-
let data;
|
|
2018
|
-
try {
|
|
2019
|
-
data = JSON.parse(resultStr);
|
|
2020
|
-
}
|
|
2021
|
-
catch {
|
|
2022
|
-
data = [];
|
|
2023
|
-
}
|
|
2024
|
-
return { ...ctx, data, page };
|
|
2
|
+
* @deprecated since v0.213 — import from ./executor.js (runPipeline,
|
|
3
|
+
* PipelineError, assertSafeRequestUrl), ./step-registry.js (registerStep,
|
|
4
|
+
* StepHandler), ./template.js (evalExpression, PIPE_FILTERS), or the
|
|
5
|
+
* per-step file under ./steps/. This shim is removed in v0.214.
|
|
6
|
+
*/
|
|
7
|
+
// prettier-ignore
|
|
8
|
+
export { runPipeline, PipelineError, assertSafeRequestUrl, _resetTransportBusForTests } from "./executor.js";
|
|
9
|
+
// prettier-ignore
|
|
10
|
+
export { registerStep, getStep, listSteps } from "./step-registry.js";
|
|
11
|
+
// prettier-ignore
|
|
12
|
+
export { PIPE_FILTERS, evalExpression, buildScope } from "./template.js";
|
|
13
|
+
// prettier-ignore
|
|
14
|
+
export { stepFetch, stepFetchText, stepParseRss, stepHtmlToMd, stepSelect, stepMap, stepFilter, stepSort, stepLimit, stepExec, stepWriteTemp, stepNavigate, stepEvaluate, stepClick, stepType, stepWaitBrowser, stepIntercept, stepPress, stepScroll, stepSnapshot, stepTap, stepExtract, stepSet, stepAppend, stepIf, stepEach, stepParallel, stepAssert, stepDownload, stepWebsocket } from "./steps/index.js";
|
|
15
|
+
if (process.env.UNICLI_DEBUG === "1") {
|
|
16
|
+
process.stderr.write("[unicli] yaml-runner.ts is deprecated; import from ./executor.js or ./steps/\n");
|
|
2025
17
|
}
|
|
2026
|
-
// Exported for unit testing — not part of public API
|
|
2027
|
-
export { PIPE_FILTERS, evalExpression, buildScope };
|
|
2028
18
|
//# sourceMappingURL=yaml-runner.js.map
|