@herbertgao/pi-extensions 2026.8.2 → 2026.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -2
- package/examples/pi-footer.json +275 -0
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +6 -8
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +5 -7
- package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/hero.en.svg +101 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +1 -17
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +5 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/{utils/agent-summary.ts → feature/agent-summary/core.ts} +37 -35
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/{agent-summary.ts → agent-summary/index.ts} +2 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/{session-reference.ts → reference/index.ts} +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/{agent-autocomplete.ts → reference/subagent.ts} +19 -9
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/{pi-startup-header.ts → shell/startup-header.ts} +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/{working-message.ts → shell/working-message.ts} +5 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +32 -28
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +379 -85
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +727 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/index.ts +65 -775
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/{feature → renderer}/markdown-enhance.ts +35 -31
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{mouse-hover.ts → mouse/hover.ts} +5 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{mouse-interaction.ts → mouse/interaction.ts} +34 -35
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{mouse-layout.ts → mouse/layout.ts} +5 -21
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{mouse-packets.ts → mouse/packets.ts} +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{mouse-scroll.ts → mouse/scroll.ts} +48 -37
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{tool-diff → tool/diff}/ansi-utils.ts +56 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{tool-diff → tool/diff}/diff-renderer.ts +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{tool-diff → tool/diff}/line-width-safety.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/write-display-utils.ts +12 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{tool-diff → tool/diff}/write-execution.ts +1 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{tool-grouping.ts → tool/grouping.ts} +18 -8
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{tool-result.ts → tool/result.ts} +4 -13
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/fullscreen-detect.ts +0 -27
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +2 -2
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +1 -0
- package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +3 -1
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +33 -35
- package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +26 -35
- package/node_modules/@herbertgao/pi-subagents/src/prompts.ts +15 -1
- package/node_modules/pi-footer/CHANGELOG.md +145 -0
- package/node_modules/pi-footer/LICENSE +21 -0
- package/node_modules/pi-footer/README.md +354 -0
- package/node_modules/pi-footer/package.json +68 -0
- package/node_modules/pi-footer/src/cache.ts +106 -0
- package/node_modules/pi-footer/src/colors.ts +237 -0
- package/node_modules/pi-footer/src/config.ts +202 -0
- package/node_modules/pi-footer/src/event-widgets.ts +41 -0
- package/node_modules/pi-footer/src/extension-statuses.ts +102 -0
- package/node_modules/pi-footer/src/git.ts +135 -0
- package/node_modules/pi-footer/src/index.ts +268 -0
- package/node_modules/pi-footer/src/metrics.ts +141 -0
- package/node_modules/pi-footer/src/presets.ts +383 -0
- package/node_modules/pi-footer/src/render.ts +114 -0
- package/node_modules/pi-footer/src/separators.ts +52 -0
- package/node_modules/pi-footer/src/types.ts +122 -0
- package/node_modules/pi-footer/src/ui/color-level-confirm.ts +22 -0
- package/node_modules/pi-footer/src/ui/color-options.ts +195 -0
- package/node_modules/pi-footer/src/ui/config-lifecycle.ts +61 -0
- package/node_modules/pi-footer/src/ui/edit-colors.ts +13 -0
- package/node_modules/pi-footer/src/ui/events.ts +35 -0
- package/node_modules/pi-footer/src/ui/extension-status-picker.ts +60 -0
- package/node_modules/pi-footer/src/ui/extension-statuses.ts +73 -0
- package/node_modules/pi-footer/src/ui/fields.ts +252 -0
- package/node_modules/pi-footer/src/ui/global-menu.ts +126 -0
- package/node_modules/pi-footer/src/ui/helpers.ts +44 -0
- package/node_modules/pi-footer/src/ui/layout.ts +17 -0
- package/node_modules/pi-footer/src/ui/line-list.ts +19 -0
- package/node_modules/pi-footer/src/ui/model.ts +64 -0
- package/node_modules/pi-footer/src/ui/navigation.ts +29 -0
- package/node_modules/pi-footer/src/ui/option-edit.ts +93 -0
- package/node_modules/pi-footer/src/ui/overlay-render.ts +103 -0
- package/node_modules/pi-footer/src/ui/screen-context.ts +18 -0
- package/node_modules/pi-footer/src/ui/screen-controller.ts +27 -0
- package/node_modules/pi-footer/src/ui/screen-render.ts +32 -0
- package/node_modules/pi-footer/src/ui/screen-state.ts +20 -0
- package/node_modules/pi-footer/src/ui/screen.ts +243 -0
- package/node_modules/pi-footer/src/ui/screens/add-widget.ts +92 -0
- package/node_modules/pi-footer/src/ui/screens/confirm-exit.ts +52 -0
- package/node_modules/pi-footer/src/ui/screens/controller.ts +12 -0
- package/node_modules/pi-footer/src/ui/screens/edit-colors.ts +68 -0
- package/node_modules/pi-footer/src/ui/screens/edit-widget.ts +115 -0
- package/node_modules/pi-footer/src/ui/screens/extension-status-row.ts +61 -0
- package/node_modules/pi-footer/src/ui/screens/global.ts +75 -0
- package/node_modules/pi-footer/src/ui/screens/line-list.ts +108 -0
- package/node_modules/pi-footer/src/ui/screens/main.ts +99 -0
- package/node_modules/pi-footer/src/ui/screens/terminal.ts +121 -0
- package/node_modules/pi-footer/src/ui/screens/widget-list.ts +154 -0
- package/node_modules/pi-footer/src/ui/terminal-menu.ts +32 -0
- package/node_modules/pi-footer/src/ui/theme.ts +48 -0
- package/node_modules/pi-footer/src/ui/title-bar.ts +100 -0
- package/node_modules/pi-footer/src/ui/widget-actions.ts +76 -0
- package/node_modules/pi-footer/src/ui.ts +66 -0
- package/node_modules/pi-footer/src/widgets/context.ts +28 -0
- package/node_modules/pi-footer/src/widgets/core/active-tools.ts +17 -0
- package/node_modules/pi-footer/src/widgets/core/context-window.ts +24 -0
- package/node_modules/pi-footer/src/widgets/core/cwd-basename.ts +19 -0
- package/node_modules/pi-footer/src/widgets/core/cwd.ts +108 -0
- package/node_modules/pi-footer/src/widgets/core/event.ts +34 -0
- package/node_modules/pi-footer/src/widgets/core/external-status.ts +102 -0
- package/node_modules/pi-footer/src/widgets/core/model-provider.ts +20 -0
- package/node_modules/pi-footer/src/widgets/core/model.ts +35 -0
- package/node_modules/pi-footer/src/widgets/core/provider.ts +17 -0
- package/node_modules/pi-footer/src/widgets/core/session-name.ts +17 -0
- package/node_modules/pi-footer/src/widgets/core/text-verbosity.ts +17 -0
- package/node_modules/pi-footer/src/widgets/core/thinking-level.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/ahead-behind.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/branch.ts +81 -0
- package/node_modules/pi-footer/src/widgets/git/clean.ts +19 -0
- package/node_modules/pi-footer/src/widgets/git/deletions.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/diff.ts +38 -0
- package/node_modules/pi-footer/src/widgets/git/insertions.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/remote.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/root.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/sha.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/staged.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/status.ts +19 -0
- package/node_modules/pi-footer/src/widgets/git/unstaged.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/untracked.ts +17 -0
- package/node_modules/pi-footer/src/widgets/instance.ts +118 -0
- package/node_modules/pi-footer/src/widgets/layout/custom-text.ts +18 -0
- package/node_modules/pi-footer/src/widgets/layout/flex-separator.ts +32 -0
- package/node_modules/pi-footer/src/widgets/layout/separator.ts +57 -0
- package/node_modules/pi-footer/src/widgets/layout/spacer.ts +31 -0
- package/node_modules/pi-footer/src/widgets/options.ts +122 -0
- package/node_modules/pi-footer/src/widgets/project/runtime.ts +361 -0
- package/node_modules/pi-footer/src/widgets/registry.ts +223 -0
- package/node_modules/pi-footer/src/widgets/session/assistant-messages.ts +17 -0
- package/node_modules/pi-footer/src/widgets/session/compactions.ts +17 -0
- package/node_modules/pi-footer/src/widgets/session/elapsed.ts +18 -0
- package/node_modules/pi-footer/src/widgets/session/last-activity.ts +18 -0
- package/node_modules/pi-footer/src/widgets/session/messages.ts +19 -0
- package/node_modules/pi-footer/src/widgets/session/session-id.ts +17 -0
- package/node_modules/pi-footer/src/widgets/session/session-start.ts +18 -0
- package/node_modules/pi-footer/src/widgets/session/tool-results.ts +17 -0
- package/node_modules/pi-footer/src/widgets/session/total-messages.ts +19 -0
- package/node_modules/pi-footer/src/widgets/session/total-time.ts +18 -0
- package/node_modules/pi-footer/src/widgets/session/user-messages.ts +17 -0
- package/node_modules/pi-footer/src/widgets/store.ts +26 -0
- package/node_modules/pi-footer/src/widgets/tokens/cache-hit-rate.ts +55 -0
- package/node_modules/pi-footer/src/widgets/tokens/cache-read.ts +18 -0
- package/node_modules/pi-footer/src/widgets/tokens/cache-write.ts +18 -0
- package/node_modules/pi-footer/src/widgets/tokens/context-bar.ts +108 -0
- package/node_modules/pi-footer/src/widgets/tokens/context-length.ts +26 -0
- package/node_modules/pi-footer/src/widgets/tokens/context-remaining.ts +29 -0
- package/node_modules/pi-footer/src/widgets/tokens/context.ts +29 -0
- package/node_modules/pi-footer/src/widgets/tokens/cost.ts +51 -0
- package/node_modules/pi-footer/src/widgets/tokens/input-speed.ts +25 -0
- package/node_modules/pi-footer/src/widgets/tokens/input-tokens.ts +18 -0
- package/node_modules/pi-footer/src/widgets/tokens/output-speed.ts +25 -0
- package/node_modules/pi-footer/src/widgets/tokens/output-tokens.ts +18 -0
- package/node_modules/pi-footer/src/widgets/tokens/tokens.ts +20 -0
- package/node_modules/pi-footer/src/widgets/tokens/total-speed.ts +25 -0
- package/node_modules/pi-footer/src/widgets/tokens/total-tokens.ts +18 -0
- package/node_modules/pi-footer/src/widgets/types.ts +181 -0
- package/node_modules/pi-footer/src/widgets/utils/colors.ts +11 -0
- package/node_modules/pi-footer/src/widgets/utils/context.ts +112 -0
- package/node_modules/pi-footer/src/widgets/utils/session.ts +19 -0
- package/node_modules/pi-footer/src/widgets/utils/token-format.ts +78 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +19 -0
- package/node_modules/pi-mcp-adapter/README.md +19 -4
- package/node_modules/pi-mcp-adapter/agent-dir.ts +40 -0
- package/node_modules/pi-mcp-adapter/commands.ts +12 -2
- package/node_modules/pi-mcp-adapter/direct-tools.ts +6 -8
- package/node_modules/pi-mcp-adapter/index.ts +15 -3
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +87 -4
- package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +138 -61
- package/node_modules/pi-mcp-adapter/mcp-oauth-provider.ts +43 -4
- package/node_modules/pi-mcp-adapter/oauth.ts +48 -0
- package/node_modules/pi-mcp-adapter/package.json +8 -2
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +8 -10
- package/node_modules/pi-mcp-adapter/tool-registrar.ts +187 -3
- package/node_modules/pi-mcp-adapter/types.ts +2 -0
- package/node_modules/pi-web-access/CHANGELOG.md +45 -0
- package/node_modules/pi-web-access/README.md +104 -29
- package/node_modules/pi-web-access/bocha.ts +221 -0
- package/node_modules/pi-web-access/curator-page.ts +113 -12
- package/node_modules/pi-web-access/curator-server.ts +56 -20
- package/node_modules/pi-web-access/datalab-pdf-extract.ts +568 -0
- package/node_modules/pi-web-access/duckduckgo.ts +123 -0
- package/node_modules/pi-web-access/extract.ts +288 -143
- package/node_modules/pi-web-access/feature-config.ts +29 -0
- package/node_modules/pi-web-access/gemini-api.ts +4 -0
- package/node_modules/pi-web-access/gemini-search.ts +45 -12
- package/node_modules/pi-web-access/gemini-web.ts +68 -3
- package/node_modules/pi-web-access/index.ts +160 -71
- package/node_modules/pi-web-access/jina-search.ts +284 -0
- package/node_modules/pi-web-access/kagi.ts +12 -12
- package/node_modules/pi-web-access/openai-search.ts +12 -2
- package/node_modules/pi-web-access/package.json +4 -3
- package/node_modules/pi-web-access/pdf-extract.ts +327 -228
- package/node_modules/pi-web-access/storage.ts +440 -2
- package/node_modules/pi-web-access/summary-model-scope.ts +1 -1
- package/node_modules/pi-web-access/summary-review.ts +4 -2
- package/node_modules/pi-web-access/video-extract.ts +10 -8
- package/node_modules/pi-web-access/xai-search.ts +12 -2
- package/node_modules/pi-web-access/youtube-extract.ts +2 -1
- package/node_modules/remote-pi/LICENSE +21 -0
- package/node_modules/remote-pi/README.md +675 -0
- package/node_modules/remote-pi/dist/actions/handlers.d.ts +122 -0
- package/node_modules/remote-pi/dist/actions/handlers.js +160 -0
- package/node_modules/remote-pi/dist/actions/handlers.js.map +1 -0
- package/node_modules/remote-pi/dist/actions/registry.d.ts +39 -0
- package/node_modules/remote-pi/dist/actions/registry.js +58 -0
- package/node_modules/remote-pi/dist/actions/registry.js.map +1 -0
- package/node_modules/remote-pi/dist/bin/supervisord.d.ts +2 -0
- package/node_modules/remote-pi/dist/bin/supervisord.js +86 -0
- package/node_modules/remote-pi/dist/bin/supervisord.js.map +1 -0
- package/node_modules/remote-pi/dist/commands/builtin_mirror.d.ts +58 -0
- package/node_modules/remote-pi/dist/commands/builtin_mirror.js +71 -0
- package/node_modules/remote-pi/dist/commands/builtin_mirror.js.map +1 -0
- package/node_modules/remote-pi/dist/commands/list_commands.d.ts +60 -0
- package/node_modules/remote-pi/dist/commands/list_commands.js +73 -0
- package/node_modules/remote-pi/dist/commands/list_commands.js.map +1 -0
- package/node_modules/remote-pi/dist/config.d.ts +62 -0
- package/node_modules/remote-pi/dist/config.js +115 -0
- package/node_modules/remote-pi/dist/config.js.map +1 -0
- package/node_modules/remote-pi/dist/daemon/client.d.ts +20 -0
- package/node_modules/remote-pi/dist/daemon/client.js +131 -0
- package/node_modules/remote-pi/dist/daemon/client.js.map +1 -0
- package/node_modules/remote-pi/dist/daemon/control_protocol.d.ts +176 -0
- package/node_modules/remote-pi/dist/daemon/control_protocol.js +63 -0
- package/node_modules/remote-pi/dist/daemon/control_protocol.js.map +1 -0
- package/node_modules/remote-pi/dist/daemon/cron_log.d.ts +45 -0
- package/node_modules/remote-pi/dist/daemon/cron_log.js +72 -0
- package/node_modules/remote-pi/dist/daemon/cron_log.js.map +1 -0
- package/node_modules/remote-pi/dist/daemon/cron_registry.d.ts +80 -0
- package/node_modules/remote-pi/dist/daemon/cron_registry.js +194 -0
- package/node_modules/remote-pi/dist/daemon/cron_registry.js.map +1 -0
- package/node_modules/remote-pi/dist/daemon/id.d.ts +24 -0
- package/node_modules/remote-pi/dist/daemon/id.js +36 -0
- package/node_modules/remote-pi/dist/daemon/id.js.map +1 -0
- package/node_modules/remote-pi/dist/daemon/install.d.ts +170 -0
- package/node_modules/remote-pi/dist/daemon/install.js +547 -0
- package/node_modules/remote-pi/dist/daemon/install.js.map +1 -0
- package/node_modules/remote-pi/dist/daemon/registry.d.ts +63 -0
- package/node_modules/remote-pi/dist/daemon/registry.js +153 -0
- package/node_modules/remote-pi/dist/daemon/registry.js.map +1 -0
- package/node_modules/remote-pi/dist/daemon/rpc_child.d.ts +194 -0
- package/node_modules/remote-pi/dist/daemon/rpc_child.js +389 -0
- package/node_modules/remote-pi/dist/daemon/rpc_child.js.map +1 -0
- package/node_modules/remote-pi/dist/daemon/supervisor.d.ts +93 -0
- package/node_modules/remote-pi/dist/daemon/supervisor.js +596 -0
- package/node_modules/remote-pi/dist/daemon/supervisor.js.map +1 -0
- package/node_modules/remote-pi/dist/extension_ui_bridge.d.ts +26 -0
- package/node_modules/remote-pi/dist/extension_ui_bridge.js +341 -0
- package/node_modules/remote-pi/dist/extension_ui_bridge.js.map +1 -0
- package/node_modules/remote-pi/dist/index.d.ts +213 -0
- package/node_modules/remote-pi/dist/index.js +4996 -0
- package/node_modules/remote-pi/dist/index.js.map +1 -0
- package/node_modules/remote-pi/dist/mcp/mesh_result.d.ts +10 -0
- package/node_modules/remote-pi/dist/mcp/mesh_result.js +42 -0
- package/node_modules/remote-pi/dist/mcp/mesh_result.js.map +1 -0
- package/node_modules/remote-pi/dist/mcp/mesh_server.d.ts +16 -0
- package/node_modules/remote-pi/dist/mcp/mesh_server.js +327 -0
- package/node_modules/remote-pi/dist/mcp/mesh_server.js.map +1 -0
- package/node_modules/remote-pi/dist/mesh/canonical.d.ts +30 -0
- package/node_modules/remote-pi/dist/mesh/canonical.js +61 -0
- package/node_modules/remote-pi/dist/mesh/canonical.js.map +1 -0
- package/node_modules/remote-pi/dist/mesh/client.d.ts +18 -0
- package/node_modules/remote-pi/dist/mesh/client.js +125 -0
- package/node_modules/remote-pi/dist/mesh/client.js.map +1 -0
- package/node_modules/remote-pi/dist/mesh/encoding.d.ts +38 -0
- package/node_modules/remote-pi/dist/mesh/encoding.js +216 -0
- package/node_modules/remote-pi/dist/mesh/encoding.js.map +1 -0
- package/node_modules/remote-pi/dist/mesh/self_revoke.d.ts +73 -0
- package/node_modules/remote-pi/dist/mesh/self_revoke.js +401 -0
- package/node_modules/remote-pi/dist/mesh/self_revoke.js.map +1 -0
- package/node_modules/remote-pi/dist/mesh/siblings.d.ts +49 -0
- package/node_modules/remote-pi/dist/mesh/siblings.js +159 -0
- package/node_modules/remote-pi/dist/mesh/siblings.js.map +1 -0
- package/node_modules/remote-pi/dist/mesh/types.d.ts +34 -0
- package/node_modules/remote-pi/dist/mesh/types.js +11 -0
- package/node_modules/remote-pi/dist/mesh/types.js.map +1 -0
- package/node_modules/remote-pi/dist/mesh/verify.d.ts +17 -0
- package/node_modules/remote-pi/dist/mesh/verify.js +86 -0
- package/node_modules/remote-pi/dist/mesh/verify.js.map +1 -0
- package/node_modules/remote-pi/dist/pairing/crypto.d.ts +8 -0
- package/node_modules/remote-pi/dist/pairing/crypto.js +22 -0
- package/node_modules/remote-pi/dist/pairing/crypto.js.map +1 -0
- package/node_modules/remote-pi/dist/pairing/handshake.d.ts +28 -0
- package/node_modules/remote-pi/dist/pairing/handshake.js +113 -0
- package/node_modules/remote-pi/dist/pairing/handshake.js.map +1 -0
- package/node_modules/remote-pi/dist/pairing/noise-sha256.d.ts +16 -0
- package/node_modules/remote-pi/dist/pairing/noise-sha256.js +103 -0
- package/node_modules/remote-pi/dist/pairing/noise-sha256.js.map +1 -0
- package/node_modules/remote-pi/dist/pairing/qr.d.ts +59 -0
- package/node_modules/remote-pi/dist/pairing/qr.js +125 -0
- package/node_modules/remote-pi/dist/pairing/qr.js.map +1 -0
- package/node_modules/remote-pi/dist/pairing/storage.d.ts +120 -0
- package/node_modules/remote-pi/dist/pairing/storage.js +604 -0
- package/node_modules/remote-pi/dist/pairing/storage.js.map +1 -0
- package/node_modules/remote-pi/dist/protocol/codec.d.ts +7 -0
- package/node_modules/remote-pi/dist/protocol/codec.js +49 -0
- package/node_modules/remote-pi/dist/protocol/codec.js.map +1 -0
- package/node_modules/remote-pi/dist/protocol/types.d.ts +393 -0
- package/node_modules/remote-pi/dist/protocol/types.js +2 -0
- package/node_modules/remote-pi/dist/protocol/types.js.map +1 -0
- package/node_modules/remote-pi/dist/rooms.d.ts +29 -0
- package/node_modules/remote-pi/dist/rooms.js +57 -0
- package/node_modules/remote-pi/dist/rooms.js.map +1 -0
- package/node_modules/remote-pi/dist/session/address.d.ts +49 -0
- package/node_modules/remote-pi/dist/session/address.js +58 -0
- package/node_modules/remote-pi/dist/session/address.js.map +1 -0
- package/node_modules/remote-pi/dist/session/agent_bridge.d.ts +55 -0
- package/node_modules/remote-pi/dist/session/agent_bridge.js +146 -0
- package/node_modules/remote-pi/dist/session/agent_bridge.js.map +1 -0
- package/node_modules/remote-pi/dist/session/bridge.d.ts +37 -0
- package/node_modules/remote-pi/dist/session/bridge.js +130 -0
- package/node_modules/remote-pi/dist/session/bridge.js.map +1 -0
- package/node_modules/remote-pi/dist/session/broker.d.ts +189 -0
- package/node_modules/remote-pi/dist/session/broker.js +507 -0
- package/node_modules/remote-pi/dist/session/broker.js.map +1 -0
- package/node_modules/remote-pi/dist/session/broker_remote.d.ts +74 -0
- package/node_modules/remote-pi/dist/session/broker_remote.js +583 -0
- package/node_modules/remote-pi/dist/session/broker_remote.js.map +1 -0
- package/node_modules/remote-pi/dist/session/cwd_lock.d.ts +33 -0
- package/node_modules/remote-pi/dist/session/cwd_lock.js +126 -0
- package/node_modules/remote-pi/dist/session/cwd_lock.js.map +1 -0
- package/node_modules/remote-pi/dist/session/envelope.d.ts +33 -0
- package/node_modules/remote-pi/dist/session/envelope.js +117 -0
- package/node_modules/remote-pi/dist/session/envelope.js.map +1 -0
- package/node_modules/remote-pi/dist/session/global_config.d.ts +33 -0
- package/node_modules/remote-pi/dist/session/global_config.js +73 -0
- package/node_modules/remote-pi/dist/session/global_config.js.map +1 -0
- package/node_modules/remote-pi/dist/session/ipc.d.ts +27 -0
- package/node_modules/remote-pi/dist/session/ipc.js +22 -0
- package/node_modules/remote-pi/dist/session/ipc.js.map +1 -0
- package/node_modules/remote-pi/dist/session/leader_election.d.ts +32 -0
- package/node_modules/remote-pi/dist/session/leader_election.js +106 -0
- package/node_modules/remote-pi/dist/session/leader_election.js.map +1 -0
- package/node_modules/remote-pi/dist/session/local_config.d.ts +55 -0
- package/node_modules/remote-pi/dist/session/local_config.js +157 -0
- package/node_modules/remote-pi/dist/session/local_config.js.map +1 -0
- package/node_modules/remote-pi/dist/session/mesh_message.d.ts +20 -0
- package/node_modules/remote-pi/dist/session/mesh_message.js +28 -0
- package/node_modules/remote-pi/dist/session/mesh_message.js.map +1 -0
- package/node_modules/remote-pi/dist/session/mesh_node.d.ts +116 -0
- package/node_modules/remote-pi/dist/session/mesh_node.js +573 -0
- package/node_modules/remote-pi/dist/session/mesh_node.js.map +1 -0
- package/node_modules/remote-pi/dist/session/peer.d.ts +139 -0
- package/node_modules/remote-pi/dist/session/peer.js +451 -0
- package/node_modules/remote-pi/dist/session/peer.js.map +1 -0
- package/node_modules/remote-pi/dist/session/peer_inventory.d.ts +13 -0
- package/node_modules/remote-pi/dist/session/peer_inventory.js +48 -0
- package/node_modules/remote-pi/dist/session/peer_inventory.js.map +1 -0
- package/node_modules/remote-pi/dist/session/peer_limits.d.ts +9 -0
- package/node_modules/remote-pi/dist/session/peer_limits.js +21 -0
- package/node_modules/remote-pi/dist/session/peer_limits.js.map +1 -0
- package/node_modules/remote-pi/dist/session/setup_wizard.d.ts +39 -0
- package/node_modules/remote-pi/dist/session/setup_wizard.js +63 -0
- package/node_modules/remote-pi/dist/session/setup_wizard.js.map +1 -0
- package/node_modules/remote-pi/dist/session/tool_gate.d.ts +5 -0
- package/node_modules/remote-pi/dist/session/tool_gate.js +11 -0
- package/node_modules/remote-pi/dist/session/tool_gate.js.map +1 -0
- package/node_modules/remote-pi/dist/session/tools.d.ts +24 -0
- package/node_modules/remote-pi/dist/session/tools.js +252 -0
- package/node_modules/remote-pi/dist/session/tools.js.map +1 -0
- package/node_modules/remote-pi/dist/session/wizard.d.ts +13 -0
- package/node_modules/remote-pi/dist/session/wizard.js +20 -0
- package/node_modules/remote-pi/dist/session/wizard.js.map +1 -0
- package/node_modules/remote-pi/dist/settings.d.ts +15 -0
- package/node_modules/remote-pi/dist/settings.js +52 -0
- package/node_modules/remote-pi/dist/settings.js.map +1 -0
- package/node_modules/remote-pi/dist/transport/peer_channel.d.ts +37 -0
- package/node_modules/remote-pi/dist/transport/peer_channel.js +98 -0
- package/node_modules/remote-pi/dist/transport/peer_channel.js.map +1 -0
- package/node_modules/remote-pi/dist/transport/pi_forward_client.d.ts +29 -0
- package/node_modules/remote-pi/dist/transport/pi_forward_client.js +62 -0
- package/node_modules/remote-pi/dist/transport/pi_forward_client.js.map +1 -0
- package/node_modules/remote-pi/dist/transport/relay_client.d.ts +91 -0
- package/node_modules/remote-pi/dist/transport/relay_client.js +206 -0
- package/node_modules/remote-pi/dist/transport/relay_client.js.map +1 -0
- package/node_modules/remote-pi/dist/ui/footer.d.ts +32 -0
- package/node_modules/remote-pi/dist/ui/footer.js +34 -0
- package/node_modules/remote-pi/dist/ui/footer.js.map +1 -0
- package/node_modules/remote-pi/docs/daemon.md +289 -0
- package/node_modules/remote-pi/package.json +88 -0
- package/node_modules/remote-pi/service-templates/launchd.plist.template +35 -0
- package/node_modules/remote-pi/service-templates/systemd.service.template +19 -0
- package/node_modules/remote-pi/service-templates/task-launcher.vbs.template +10 -0
- package/node_modules/remote-pi/service-templates/task-scheduler.xml.template +38 -0
- package/node_modules/remote-pi/skills/agent-network/SKILL.md +310 -0
- package/package.json +25 -10
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool-diff/write-display-utils.ts +0 -29
- /package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/{session-reference-core.ts → reference/session.ts} +0 -0
- /package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/{pi-aliases.ts → shell/aliases.ts} +0 -0
- /package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{tool-diff → tool/diff}/ATTRIBUTION.md +0 -0
- /package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{tool-diff → tool/diff}/diff-presentation.ts +0 -0
- /package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{tool-diff → tool/diff}/index.ts +0 -0
- /package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{tool-diff → tool/diff}/render-utils.ts +0 -0
- /package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{tool-diff → tool/diff}/shiki-highlight.ts +0 -0
- /package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{tool-diff → tool/diff}/types.ts +0 -0
|
@@ -1,8 +1,34 @@
|
|
|
1
|
+
import { closeSync, constants, fchmodSync, fstatSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, type Stats, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { join } from "node:path";
|
|
1
4
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
5
|
import type { ExtractedContent } from "./extract.ts";
|
|
3
6
|
import type { SearchResult } from "./perplexity.ts";
|
|
7
|
+
import { getWebSearchConfigDir } from "./utils.ts";
|
|
4
8
|
|
|
5
9
|
const CACHE_TTL_MS = 60 * 60 * 1000;
|
|
10
|
+
const FETCH_CACHE_DIR = "web-search-cache";
|
|
11
|
+
const FETCH_CACHE_VERSION = 1;
|
|
12
|
+
const CACHE_KEY_PATTERN = /^[A-Za-z0-9_-]+\.json$/;
|
|
13
|
+
const CACHE_TMP_PATTERN = /^[A-Za-z0-9_-]+\.json\.\d+\.\d+(?:\.[a-f0-9]{32})?\.tmp$/;
|
|
14
|
+
const CACHE_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
15
|
+
const MAX_METADATA_TEXT = 8192;
|
|
16
|
+
const DEFAULT_CACHE_LIMITS = { maxEntries: 128, maxBytes: 128 * 1024 * 1024 };
|
|
17
|
+
const O_DIRECTORY = process.platform === "win32" ? 0 : (constants.O_DIRECTORY ?? 0);
|
|
18
|
+
const O_NOFOLLOW = process.platform === "win32" ? 0 : (constants.O_NOFOLLOW ?? 0);
|
|
19
|
+
|
|
20
|
+
interface FetchCacheLimits {
|
|
21
|
+
maxEntries: number;
|
|
22
|
+
maxBytes: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface CacheFile {
|
|
26
|
+
name: string;
|
|
27
|
+
size: number;
|
|
28
|
+
mtimeMs: number;
|
|
29
|
+
dev: number;
|
|
30
|
+
ino: number;
|
|
31
|
+
}
|
|
6
32
|
|
|
7
33
|
export interface QueryResultData {
|
|
8
34
|
query: string;
|
|
@@ -12,6 +38,22 @@ export interface QueryResultData {
|
|
|
12
38
|
provider?: string;
|
|
13
39
|
}
|
|
14
40
|
|
|
41
|
+
interface FetchCacheRef {
|
|
42
|
+
version: typeof FETCH_CACHE_VERSION;
|
|
43
|
+
key: string;
|
|
44
|
+
storedAt: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface StoredFetchUrlMetadata {
|
|
48
|
+
url: string;
|
|
49
|
+
title: string;
|
|
50
|
+
error: string | null;
|
|
51
|
+
contentLength: number;
|
|
52
|
+
mimeType?: string;
|
|
53
|
+
status?: number;
|
|
54
|
+
duration?: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
15
57
|
export interface StoredSearchData {
|
|
16
58
|
id: string;
|
|
17
59
|
type: "search" | "fetch" | "research";
|
|
@@ -19,6 +61,9 @@ export interface StoredSearchData {
|
|
|
19
61
|
queries?: QueryResultData[];
|
|
20
62
|
urls?: ExtractedContent[];
|
|
21
63
|
artifact?: unknown;
|
|
64
|
+
fetchCache?: FetchCacheRef;
|
|
65
|
+
urlMetadata?: StoredFetchUrlMetadata[];
|
|
66
|
+
fetchCacheError?: string;
|
|
22
67
|
}
|
|
23
68
|
|
|
24
69
|
const storedResults = new Map<string, StoredSearchData>();
|
|
@@ -27,12 +72,387 @@ export function generateId(): string {
|
|
|
27
72
|
return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
|
|
28
73
|
}
|
|
29
74
|
|
|
75
|
+
export function getFetchCacheDir(): string {
|
|
76
|
+
return join(getWebSearchConfigDir(), FETCH_CACHE_DIR);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function fetchCachePath(key: string): string | null {
|
|
80
|
+
if (!CACHE_KEY_PATTERN.test(key)) return null;
|
|
81
|
+
return join(getFetchCacheDir(), key);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function cacheKeyForId(id: string): string {
|
|
85
|
+
if (!CACHE_ID_PATTERN.test(id)) {
|
|
86
|
+
throw new Error(`Invalid fetched content cache id: ${id}`);
|
|
87
|
+
}
|
|
88
|
+
return `${id}.json`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function truncateMetadataText(value: string | undefined): string {
|
|
92
|
+
if (!value) return "";
|
|
93
|
+
return value.length > MAX_METADATA_TEXT ? `${value.slice(0, MAX_METADATA_TEXT)}...` : value;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function metadataForUrls(urls: ExtractedContent[]): StoredFetchUrlMetadata[] {
|
|
97
|
+
return urls.map((url) => ({
|
|
98
|
+
url: truncateMetadataText(url.url),
|
|
99
|
+
title: truncateMetadataText(url.title),
|
|
100
|
+
error: url.error ? truncateMetadataText(url.error) : null,
|
|
101
|
+
contentLength: url.content.length,
|
|
102
|
+
...(url.mimeType ? { mimeType: truncateMetadataText(url.mimeType) } : {}),
|
|
103
|
+
...(typeof url.status === "number" ? { status: url.status } : {}),
|
|
104
|
+
...(typeof url.duration === "number" ? { duration: url.duration } : {}),
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function isFetchCacheRef(value: unknown): value is FetchCacheRef {
|
|
109
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
110
|
+
const ref = value as Record<string, unknown>;
|
|
111
|
+
return ref.version === FETCH_CACHE_VERSION &&
|
|
112
|
+
typeof ref.key === "string" && CACHE_KEY_PATTERN.test(ref.key) &&
|
|
113
|
+
typeof ref.storedAt === "number" && Number.isFinite(ref.storedAt);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function isStoredFetchUrlMetadata(value: unknown): value is StoredFetchUrlMetadata {
|
|
117
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
118
|
+
const meta = value as Record<string, unknown>;
|
|
119
|
+
return typeof meta.url === "string" &&
|
|
120
|
+
typeof meta.title === "string" &&
|
|
121
|
+
(meta.error === null || typeof meta.error === "string") &&
|
|
122
|
+
typeof meta.contentLength === "number" && Number.isFinite(meta.contentLength) && meta.contentLength >= 0 &&
|
|
123
|
+
(meta.mimeType === undefined || typeof meta.mimeType === "string") &&
|
|
124
|
+
(meta.status === undefined || typeof meta.status === "number") &&
|
|
125
|
+
(meta.duration === undefined || typeof meta.duration === "number");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isInlineFetchedUrl(value: unknown): value is ExtractedContent {
|
|
129
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
130
|
+
const url = value as Record<string, unknown>;
|
|
131
|
+
return typeof url.url === "string" &&
|
|
132
|
+
typeof url.title === "string" &&
|
|
133
|
+
typeof url.content === "string" &&
|
|
134
|
+
(url.error === null || typeof url.error === "string");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function isInlineFetchData(data: StoredSearchData): data is StoredSearchData & { urls: ExtractedContent[] } {
|
|
138
|
+
return data.type === "fetch" && Array.isArray(data.urls) && data.urls.every(isInlineFetchedUrl);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function cacheLimits(limits?: Partial<FetchCacheLimits>): FetchCacheLimits {
|
|
142
|
+
const resolved = {
|
|
143
|
+
maxEntries: limits?.maxEntries ?? DEFAULT_CACHE_LIMITS.maxEntries,
|
|
144
|
+
maxBytes: limits?.maxBytes ?? DEFAULT_CACHE_LIMITS.maxBytes,
|
|
145
|
+
};
|
|
146
|
+
if (!Number.isFinite(resolved.maxEntries) || !Number.isInteger(resolved.maxEntries) || resolved.maxEntries <= 0 ||
|
|
147
|
+
!Number.isFinite(resolved.maxBytes) || !Number.isInteger(resolved.maxBytes) || resolved.maxBytes <= 0) {
|
|
148
|
+
throw new Error("Fetched content cache limits must be finite positive integers");
|
|
149
|
+
}
|
|
150
|
+
return resolved;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function enforceDirectoryMode(fd: number): void {
|
|
154
|
+
try {
|
|
155
|
+
fchmodSync(fd, 0o700);
|
|
156
|
+
} catch (err) {
|
|
157
|
+
if (process.platform !== "win32") throw err;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function enforceFileMode(fd: number): void {
|
|
162
|
+
try {
|
|
163
|
+
fchmodSync(fd, 0o600);
|
|
164
|
+
} catch (err) {
|
|
165
|
+
if (process.platform !== "win32") throw err;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function safeFetchCacheDir(create: true): string;
|
|
170
|
+
function safeFetchCacheDir(create: false): string | null;
|
|
171
|
+
function safeFetchCacheDir(create: boolean): string | null {
|
|
172
|
+
const dir = getFetchCacheDir();
|
|
173
|
+
if (create) mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
174
|
+
let before: Stats;
|
|
175
|
+
try {
|
|
176
|
+
before = lstatSync(dir);
|
|
177
|
+
} catch (err) {
|
|
178
|
+
if (!create && (err as NodeJS.ErrnoException).code === "ENOENT") return null;
|
|
179
|
+
throw err;
|
|
180
|
+
}
|
|
181
|
+
if (before.isSymbolicLink() || !before.isDirectory()) {
|
|
182
|
+
throw new Error("Fetched content cache path is not a safe directory");
|
|
183
|
+
}
|
|
184
|
+
if (process.platform === "win32") {
|
|
185
|
+
const after = lstatSync(dir);
|
|
186
|
+
if (after.isSymbolicLink() || !after.isDirectory() || after.dev !== before.dev || after.ino !== before.ino) {
|
|
187
|
+
throw new Error("Fetched content cache directory changed while securing it");
|
|
188
|
+
}
|
|
189
|
+
return dir;
|
|
190
|
+
}
|
|
191
|
+
let fd: number | null = null;
|
|
192
|
+
try {
|
|
193
|
+
fd = openSync(dir, constants.O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
|
|
194
|
+
const opened = fstatSync(fd);
|
|
195
|
+
if (!opened.isDirectory() || opened.dev !== before.dev || opened.ino !== before.ino) {
|
|
196
|
+
throw new Error("Fetched content cache directory changed while opening");
|
|
197
|
+
}
|
|
198
|
+
enforceDirectoryMode(fd);
|
|
199
|
+
closeSync(fd);
|
|
200
|
+
fd = null;
|
|
201
|
+
const after = lstatSync(dir);
|
|
202
|
+
if (after.isSymbolicLink() || !after.isDirectory() || after.dev !== before.dev || after.ino !== before.ino) {
|
|
203
|
+
throw new Error("Fetched content cache directory changed while securing it");
|
|
204
|
+
}
|
|
205
|
+
return dir;
|
|
206
|
+
} finally {
|
|
207
|
+
if (fd !== null) try { closeSync(fd); } catch {}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function openRegularFile(path: string): { fd: number; info: Stats } {
|
|
212
|
+
const before = lstatSync(path);
|
|
213
|
+
if (before.isSymbolicLink() || !before.isFile()) throw new Error("Fetched content cache entry is not a regular file");
|
|
214
|
+
const fd = openSync(path, constants.O_RDONLY | O_NOFOLLOW);
|
|
215
|
+
try {
|
|
216
|
+
const info = fstatSync(fd);
|
|
217
|
+
if (!info.isFile() || info.dev !== before.dev || info.ino !== before.ino) {
|
|
218
|
+
throw new Error("Fetched content cache entry changed while opening");
|
|
219
|
+
}
|
|
220
|
+
return { fd, info };
|
|
221
|
+
} catch (err) {
|
|
222
|
+
closeSync(fd);
|
|
223
|
+
throw err;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
type CacheUnlinkResult = "removed" | "missing" | "changed" | "error";
|
|
228
|
+
|
|
229
|
+
function unlinkCacheFile(dir: string, file: CacheFile): CacheUnlinkResult {
|
|
230
|
+
try {
|
|
231
|
+
const root = lstatSync(dir);
|
|
232
|
+
if (root.isSymbolicLink() || !root.isDirectory()) return "changed";
|
|
233
|
+
const path = join(dir, file.name);
|
|
234
|
+
let current: Stats;
|
|
235
|
+
try {
|
|
236
|
+
current = lstatSync(path);
|
|
237
|
+
} catch (err) {
|
|
238
|
+
return (err as NodeJS.ErrnoException).code === "ENOENT" ? "missing" : "error";
|
|
239
|
+
}
|
|
240
|
+
if (current.isSymbolicLink() || !current.isFile() || current.dev !== file.dev || current.ino !== file.ino) return "changed";
|
|
241
|
+
try {
|
|
242
|
+
unlinkSync(path);
|
|
243
|
+
return "removed";
|
|
244
|
+
} catch (err) {
|
|
245
|
+
return (err as NodeJS.ErrnoException).code === "ENOENT" ? "missing" : "error";
|
|
246
|
+
}
|
|
247
|
+
} catch {
|
|
248
|
+
return "error";
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function pruneFetchCache(now: number, limits: FetchCacheLimits, preferredKey?: string, reservation?: { key: string; bytes: number }): boolean {
|
|
253
|
+
let dir: string | null;
|
|
254
|
+
try {
|
|
255
|
+
dir = safeFetchCacheDir(false);
|
|
256
|
+
} catch {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
if (!dir) return true;
|
|
260
|
+
|
|
261
|
+
let entries: string[];
|
|
262
|
+
try {
|
|
263
|
+
entries = readdirSync(dir);
|
|
264
|
+
} catch {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const files: CacheFile[] = [];
|
|
269
|
+
for (const entry of entries) {
|
|
270
|
+
if (!CACHE_KEY_PATTERN.test(entry) && !CACHE_TMP_PATTERN.test(entry)) continue;
|
|
271
|
+
const path = join(dir, entry);
|
|
272
|
+
let opened: ReturnType<typeof openRegularFile>;
|
|
273
|
+
try {
|
|
274
|
+
opened = openRegularFile(path);
|
|
275
|
+
} catch {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
try {
|
|
279
|
+
enforceFileMode(opened.fd);
|
|
280
|
+
} catch {
|
|
281
|
+
closeSync(opened.fd);
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
closeSync(opened.fd);
|
|
285
|
+
const file = { name: entry, size: opened.info.size, mtimeMs: opened.info.mtimeMs, dev: opened.info.dev, ino: opened.info.ino };
|
|
286
|
+
if (now - file.mtimeMs >= CACHE_TTL_MS) {
|
|
287
|
+
const removed = unlinkCacheFile(dir, file);
|
|
288
|
+
if (removed !== "removed" && removed !== "missing") return false;
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (CACHE_KEY_PATTERN.test(entry)) files.push(file);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
files.sort((a, b) => a.mtimeMs - b.mtimeMs || a.name.localeCompare(b.name));
|
|
295
|
+
const projectedUsage = () => {
|
|
296
|
+
const replaced = reservation ? files.find((file) => file.name === reservation.key) : undefined;
|
|
297
|
+
return {
|
|
298
|
+
entries: files.length + (reservation && !replaced ? 1 : 0),
|
|
299
|
+
bytes: files.reduce((total, file) => total + file.size, 0) + (reservation ? reservation.bytes - (replaced?.size ?? 0) : 0),
|
|
300
|
+
};
|
|
301
|
+
};
|
|
302
|
+
const attempted = new Set<string>();
|
|
303
|
+
let usage = projectedUsage();
|
|
304
|
+
while (usage.entries > limits.maxEntries || usage.bytes > limits.maxBytes) {
|
|
305
|
+
const index = files.findIndex((file) => file.name !== preferredKey && !attempted.has(file.name));
|
|
306
|
+
if (index < 0) break;
|
|
307
|
+
const file = files[index];
|
|
308
|
+
attempted.add(file.name);
|
|
309
|
+
const removed = unlinkCacheFile(dir, file);
|
|
310
|
+
if (removed === "removed" || removed === "missing") files.splice(index, 1);
|
|
311
|
+
usage = projectedUsage();
|
|
312
|
+
}
|
|
313
|
+
return usage.entries <= limits.maxEntries && usage.bytes <= limits.maxBytes;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function writeFetchCache(data: StoredSearchData & { urls: ExtractedContent[] }): FetchCacheRef {
|
|
317
|
+
const limits = DEFAULT_CACHE_LIMITS;
|
|
318
|
+
const serialized = JSON.stringify(data);
|
|
319
|
+
const size = Buffer.byteLength(serialized);
|
|
320
|
+
if (size > limits.maxBytes) throw new Error(`Fetched content cache entry exceeds ${limits.maxBytes} bytes`);
|
|
321
|
+
|
|
322
|
+
const dir = safeFetchCacheDir(true);
|
|
323
|
+
const key = cacheKeyForId(data.id);
|
|
324
|
+
if (!pruneFetchCache(Date.now(), limits, key, { key, bytes: size })) {
|
|
325
|
+
throw new Error("Fetched content cache could not reserve space for a new entry");
|
|
326
|
+
}
|
|
327
|
+
const finalPath = join(dir, key);
|
|
328
|
+
const tmpName = `${key}.${process.pid}.${Date.now()}.${randomBytes(16).toString("hex")}.tmp`;
|
|
329
|
+
const tmpPath = join(dir, tmpName);
|
|
330
|
+
let fd: number | null = null;
|
|
331
|
+
let tmpFile: CacheFile | null = null;
|
|
332
|
+
let renamed = false;
|
|
333
|
+
try {
|
|
334
|
+
fd = openSync(tmpPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | O_NOFOLLOW, 0o600);
|
|
335
|
+
const tmpInfo = fstatSync(fd);
|
|
336
|
+
tmpFile = { name: tmpName, size: tmpInfo.size, mtimeMs: tmpInfo.mtimeMs, dev: tmpInfo.dev, ino: tmpInfo.ino };
|
|
337
|
+
enforceFileMode(fd);
|
|
338
|
+
writeFileSync(fd, serialized, "utf8");
|
|
339
|
+
fsyncSync(fd);
|
|
340
|
+
closeSync(fd);
|
|
341
|
+
fd = null;
|
|
342
|
+
safeFetchCacheDir(false);
|
|
343
|
+
renameSync(tmpPath, finalPath);
|
|
344
|
+
renamed = true;
|
|
345
|
+
const written = lstatSync(finalPath);
|
|
346
|
+
if (!written.isFile() || written.dev !== tmpFile.dev || written.ino !== tmpFile.ino) {
|
|
347
|
+
throw new Error("Fetched content cache entry changed after writing");
|
|
348
|
+
}
|
|
349
|
+
if (!pruneFetchCache(Date.now(), limits, key)) {
|
|
350
|
+
throw new Error("Fetched content cache could not meet its limits after writing");
|
|
351
|
+
}
|
|
352
|
+
} catch (err) {
|
|
353
|
+
if (fd !== null) try { closeSync(fd); } catch {}
|
|
354
|
+
if (tmpFile) unlinkCacheFile(dir, { ...tmpFile, name: renamed ? key : tmpName });
|
|
355
|
+
throw err;
|
|
356
|
+
}
|
|
357
|
+
return { version: FETCH_CACHE_VERSION, key, storedAt: Date.now() };
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function cacheWriteError(err: unknown): string {
|
|
361
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
362
|
+
return `Failed to write fetched content cache: ${message}`;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function createFetchSessionData(data: StoredSearchData & { urls: ExtractedContent[] }, ref: FetchCacheRef | null, cacheError?: string): StoredSearchData {
|
|
366
|
+
return {
|
|
367
|
+
id: data.id,
|
|
368
|
+
type: "fetch",
|
|
369
|
+
timestamp: data.timestamp,
|
|
370
|
+
urlMetadata: metadataForUrls(data.urls),
|
|
371
|
+
...(ref ? { fetchCache: ref } : {}),
|
|
372
|
+
...(cacheError ? { fetchCacheError: truncateMetadataText(cacheError) } : {}),
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function fetchUrlMetadata(data: StoredSearchData): StoredFetchUrlMetadata[] {
|
|
377
|
+
if (data.urlMetadata) return data.urlMetadata;
|
|
378
|
+
return isInlineFetchData(data) ? metadataForUrls(data.urls) : [];
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function unavailableFetchData(data: StoredSearchData, reason: string): StoredSearchData {
|
|
382
|
+
return {
|
|
383
|
+
...data,
|
|
384
|
+
urls: fetchUrlMetadata(data).map((meta) => ({
|
|
385
|
+
url: meta.url,
|
|
386
|
+
title: meta.title,
|
|
387
|
+
content: "",
|
|
388
|
+
error: reason,
|
|
389
|
+
...(meta.mimeType ? { mimeType: meta.mimeType } : {}),
|
|
390
|
+
...(typeof meta.status === "number" ? { status: meta.status } : {}),
|
|
391
|
+
...(typeof meta.duration === "number" ? { duration: meta.duration } : {}),
|
|
392
|
+
})),
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function readCachedFetchData(data: StoredSearchData, now = Date.now()): StoredSearchData {
|
|
397
|
+
if (data.type !== "fetch") return data;
|
|
398
|
+
if (now - data.timestamp >= CACHE_TTL_MS) {
|
|
399
|
+
return unavailableFetchData(data, "Cached fetched content is missing or expired");
|
|
400
|
+
}
|
|
401
|
+
if (isInlineFetchData(data)) return data;
|
|
402
|
+
if (!data.fetchCache) {
|
|
403
|
+
return unavailableFetchData(data, data.fetchCacheError ?? "Cached fetched content is unavailable");
|
|
404
|
+
}
|
|
405
|
+
const path = fetchCachePath(data.fetchCache.key);
|
|
406
|
+
if (!path) return unavailableFetchData(data, "Cached fetched content is missing or expired");
|
|
407
|
+
let fd: number | null = null;
|
|
408
|
+
try {
|
|
409
|
+
if (!safeFetchCacheDir(false)) return unavailableFetchData(data, "Cached fetched content is missing or expired");
|
|
410
|
+
const opened = openRegularFile(path);
|
|
411
|
+
fd = opened.fd;
|
|
412
|
+
enforceFileMode(fd);
|
|
413
|
+
const parsed: unknown = JSON.parse(readFileSync(fd, "utf8"));
|
|
414
|
+
if (!isValidStoredData(parsed) || parsed.type !== "fetch" || parsed.id !== data.id || !isInlineFetchData(parsed)) {
|
|
415
|
+
return unavailableFetchData(data, "Cached fetched content is invalid");
|
|
416
|
+
}
|
|
417
|
+
return { ...parsed, fetchCache: data.fetchCache, urlMetadata: data.urlMetadata };
|
|
418
|
+
} catch (err) {
|
|
419
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
420
|
+
return unavailableFetchData(data, "Cached fetched content is missing or expired");
|
|
421
|
+
}
|
|
422
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
423
|
+
return unavailableFetchData(data, `Cached fetched content could not be read: ${message}`);
|
|
424
|
+
} finally {
|
|
425
|
+
if (fd !== null) try { closeSync(fd); } catch {}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export function pruneExpiredFetchCache(now = Date.now(), requestedLimits?: Partial<FetchCacheLimits>): void {
|
|
430
|
+
const limits = cacheLimits(requestedLimits);
|
|
431
|
+
try { pruneFetchCache(now, limits); } catch {}
|
|
432
|
+
}
|
|
433
|
+
|
|
30
434
|
export function storeResult(id: string, data: StoredSearchData): void {
|
|
31
435
|
storedResults.set(id, data);
|
|
32
436
|
}
|
|
33
437
|
|
|
438
|
+
export function storeFetchedContentResult(id: string, data: StoredSearchData & { type: "fetch"; urls: ExtractedContent[] }): StoredSearchData {
|
|
439
|
+
let ref: FetchCacheRef | null = null;
|
|
440
|
+
let cacheError: string | undefined;
|
|
441
|
+
try {
|
|
442
|
+
ref = writeFetchCache(data);
|
|
443
|
+
} catch (err) {
|
|
444
|
+
cacheError = cacheWriteError(err);
|
|
445
|
+
}
|
|
446
|
+
storedResults.set(id, ref ? { ...data, fetchCache: ref, urlMetadata: metadataForUrls(data.urls) } : { ...data, fetchCacheError: cacheError });
|
|
447
|
+
return createFetchSessionData(data, ref, cacheError);
|
|
448
|
+
}
|
|
449
|
+
|
|
34
450
|
export function getResult(id: string): StoredSearchData | null {
|
|
35
|
-
|
|
451
|
+
const data = storedResults.get(id);
|
|
452
|
+
if (!data) return null;
|
|
453
|
+
const loaded = readCachedFetchData(data);
|
|
454
|
+
if (loaded !== data) storedResults.set(id, loaded);
|
|
455
|
+
return loaded;
|
|
36
456
|
}
|
|
37
457
|
|
|
38
458
|
export function getAllResults(): StoredSearchData[] {
|
|
@@ -40,6 +460,19 @@ export function getAllResults(): StoredSearchData[] {
|
|
|
40
460
|
}
|
|
41
461
|
|
|
42
462
|
export function deleteResult(id: string): boolean {
|
|
463
|
+
const data = storedResults.get(id);
|
|
464
|
+
if (data?.fetchCache) {
|
|
465
|
+
try {
|
|
466
|
+
const dir = safeFetchCacheDir(false);
|
|
467
|
+
const path = fetchCachePath(data.fetchCache.key);
|
|
468
|
+
if (dir && path) {
|
|
469
|
+
const info = lstatSync(path);
|
|
470
|
+
if (!info.isSymbolicLink() && info.isFile()) {
|
|
471
|
+
unlinkCacheFile(dir, { name: data.fetchCache.key, size: info.size, mtimeMs: info.mtimeMs, dev: info.dev, ino: info.ino });
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
} catch {}
|
|
475
|
+
}
|
|
43
476
|
return storedResults.delete(id);
|
|
44
477
|
}
|
|
45
478
|
|
|
@@ -54,7 +487,11 @@ function isValidStoredData(data: unknown): data is StoredSearchData {
|
|
|
54
487
|
if (d.type !== "search" && d.type !== "fetch" && d.type !== "research") return false;
|
|
55
488
|
if (typeof d.timestamp !== "number") return false;
|
|
56
489
|
if (d.type === "search" && !Array.isArray(d.queries)) return false;
|
|
57
|
-
if (d.type === "fetch"
|
|
490
|
+
if (d.type === "fetch") {
|
|
491
|
+
if (Array.isArray(d.urls)) return d.urls.every(isInlineFetchedUrl);
|
|
492
|
+
if (!Array.isArray(d.urlMetadata) || !d.urlMetadata.every(isStoredFetchUrlMetadata)) return false;
|
|
493
|
+
return d.fetchCache === undefined ? d.fetchCacheError === undefined || typeof d.fetchCacheError === "string" : isFetchCacheRef(d.fetchCache);
|
|
494
|
+
}
|
|
58
495
|
if (d.type === "research" && (!d.artifact || typeof d.artifact !== "object")) return false;
|
|
59
496
|
return true;
|
|
60
497
|
}
|
|
@@ -62,6 +499,7 @@ function isValidStoredData(data: unknown): data is StoredSearchData {
|
|
|
62
499
|
export function restoreFromSession(ctx: ExtensionContext): void {
|
|
63
500
|
storedResults.clear();
|
|
64
501
|
const now = Date.now();
|
|
502
|
+
pruneExpiredFetchCache(now);
|
|
65
503
|
|
|
66
504
|
for (const entry of ctx.sessionManager.getBranch()) {
|
|
67
505
|
if (entry.type === "custom" && entry.customType === "web-search-results") {
|
|
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
|
|
5
|
-
const THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh"]);
|
|
5
|
+
const THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
6
6
|
|
|
7
7
|
interface SummaryModelScopeContext {
|
|
8
8
|
cwd: string;
|
|
@@ -3,6 +3,8 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
|
3
3
|
import { findModelWithProviderRouting, loadEnabledModelPatterns, modelMatchesEnabledPatterns } from "./summary-model-scope.ts";
|
|
4
4
|
import type { QueryResultData } from "./storage.ts";
|
|
5
5
|
|
|
6
|
+
type ProviderHeaders = Record<string, string | null>;
|
|
7
|
+
|
|
6
8
|
const PREFERRED_SUMMARY_MODELS = [
|
|
7
9
|
{ provider: "anthropic", id: "claude-haiku-4-5" },
|
|
8
10
|
{ provider: "openai-codex", id: "gpt-5.3-codex-spark" },
|
|
@@ -196,14 +198,14 @@ function parseModelSelector(value: string): { provider: string; id: string } {
|
|
|
196
198
|
async function resolveSummaryModelCandidates(
|
|
197
199
|
ctx: SummaryGenerationContext,
|
|
198
200
|
modelOverride?: string,
|
|
199
|
-
): Promise<{ candidates: Array<{ model: Model<Api>; apiKey: string; headers?:
|
|
201
|
+
): Promise<{ candidates: Array<{ model: Model<Api>; apiKey: string; headers?: ProviderHeaders }>; errors: string[] }> {
|
|
200
202
|
const enabledModelPatterns = loadEnabledModelPatterns(ctx);
|
|
201
203
|
const specs: Array<{ provider: string; id: string }> = [];
|
|
202
204
|
const normalizedOverride = typeof modelOverride === "string" ? modelOverride.trim() : "";
|
|
203
205
|
if (normalizedOverride.length > 0) specs.push(parseModelSelector(normalizedOverride));
|
|
204
206
|
specs.push(...PREFERRED_SUMMARY_MODELS);
|
|
205
207
|
|
|
206
|
-
const candidates: Array<{ model: Model<Api>; apiKey: string; headers?:
|
|
208
|
+
const candidates: Array<{ model: Model<Api>; apiKey: string; headers?: ProviderHeaders }> = [];
|
|
207
209
|
const errors: string[] = [];
|
|
208
210
|
const seen = new Set<string>();
|
|
209
211
|
for (const spec of specs) {
|
|
@@ -3,13 +3,13 @@ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
import { resolve, extname, basename, join, dirname } from "node:path";
|
|
5
5
|
import { activityMonitor } from "./activity.ts";
|
|
6
|
+
import { canAttachImages } from "./feature-config.ts";
|
|
6
7
|
import { isGeminiWebAvailable, queryWithCookies } from "./gemini-web.ts";
|
|
7
|
-
import { queryGeminiApiWithVideo, getApiKey, fetchGeminiApi,
|
|
8
|
+
import { queryGeminiApiWithVideo, getApiKey, fetchGeminiApi, getVersionedApiBase, getUploadBase, redactGeminiApiResponse } from "./gemini-api.ts";
|
|
8
9
|
import { extractHeadingTitle, type ExtractedContent, type ExtractOptions, type FrameResult } from "./extract.ts";
|
|
9
10
|
import { readExecError, trimErrorText, mapFfmpegError, getWebSearchConfigPath } from "./utils.ts";
|
|
10
11
|
|
|
11
12
|
const CONFIG_PATH = getWebSearchConfigPath();
|
|
12
|
-
const UPLOAD_BASE = "https://generativelanguage.googleapis.com/upload/v1beta";
|
|
13
13
|
|
|
14
14
|
const DEFAULT_VIDEO_PROMPT = `Extract the complete content of this video. Include:
|
|
15
15
|
1. Video title (infer from content if not explicit), duration
|
|
@@ -182,9 +182,11 @@ export async function extractVideo(
|
|
|
182
182
|
?? await tryVideoGeminiWeb(info, effectivePrompt, effectiveModel, signal);
|
|
183
183
|
|
|
184
184
|
if (result) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
185
|
+
if (canAttachImages()) {
|
|
186
|
+
const thumbnail = await extractVideoFrame(info.absolutePath);
|
|
187
|
+
if (!("error" in thumbnail)) {
|
|
188
|
+
result.thumbnail = thumbnail;
|
|
189
|
+
}
|
|
188
190
|
}
|
|
189
191
|
activityMonitor.logComplete(activityId, 200);
|
|
190
192
|
return result;
|
|
@@ -311,7 +313,7 @@ async function uploadToFilesApi(
|
|
|
311
313
|
): Promise<{ name: string; uri: string }> {
|
|
312
314
|
const displayName = basename(info.absolutePath);
|
|
313
315
|
|
|
314
|
-
const initRes = await fetchGeminiApi(`${
|
|
316
|
+
const initRes = await fetchGeminiApi(`${getUploadBase()}/files`, {
|
|
315
317
|
method: "POST",
|
|
316
318
|
headers: {
|
|
317
319
|
"X-Goog-Upload-Protocol": "resumable",
|
|
@@ -364,7 +366,7 @@ async function pollFileState(
|
|
|
364
366
|
while (Date.now() < deadline) {
|
|
365
367
|
if (signal?.aborted) throw new Error("Aborted");
|
|
366
368
|
|
|
367
|
-
const res = await fetchGeminiApi(`${
|
|
369
|
+
const res = await fetchGeminiApi(`${getVersionedApiBase()}/${fileName}`, { signal }, apiKey);
|
|
368
370
|
if (!res.ok) throw new Error(`File state check failed: ${res.status}`);
|
|
369
371
|
|
|
370
372
|
const data = await res.json() as { state: string };
|
|
@@ -378,7 +380,7 @@ async function pollFileState(
|
|
|
378
380
|
}
|
|
379
381
|
|
|
380
382
|
function deleteGeminiFile(fileName: string, apiKey: string): void {
|
|
381
|
-
void fetchGeminiApi(`${
|
|
383
|
+
void fetchGeminiApi(`${getVersionedApiBase()}/${fileName}`, { method: "DELETE" }, apiKey).catch((err) => {
|
|
382
384
|
const message = err instanceof Error ? err.message : String(err);
|
|
383
385
|
console.error(`Failed to delete Gemini file ${fileName}: ${message}`);
|
|
384
386
|
});
|
|
@@ -36,10 +36,12 @@ interface WebSearchConfig {
|
|
|
36
36
|
xaiSearchModel?: unknown;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
type ProviderHeaders = Record<string, string | null>;
|
|
40
|
+
|
|
39
41
|
interface XaiAuth {
|
|
40
42
|
apiKey: string;
|
|
41
43
|
model: string;
|
|
42
|
-
headers:
|
|
44
|
+
headers: ProviderHeaders;
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
let cachedConfig: WebSearchConfig | null = null;
|
|
@@ -69,6 +71,14 @@ function resolveConfiguredSearchModel(value: unknown): string | undefined {
|
|
|
69
71
|
return value.trim();
|
|
70
72
|
}
|
|
71
73
|
|
|
74
|
+
function toRequestHeaders(headers: ProviderHeaders): Record<string, string> {
|
|
75
|
+
const requestHeaders: Record<string, string> = {};
|
|
76
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
77
|
+
if (value !== null) requestHeaders[name] = value;
|
|
78
|
+
}
|
|
79
|
+
return requestHeaders;
|
|
80
|
+
}
|
|
81
|
+
|
|
72
82
|
/**
|
|
73
83
|
* Resolve auth from pi's model registry first, so a Grok subscription pays for
|
|
74
84
|
* its own searches and no api key has to be configured at all. Mirrors how the
|
|
@@ -287,7 +297,7 @@ export async function searchWithXai(
|
|
|
287
297
|
const response = await fetch(XAI_RESPONSES_URL, {
|
|
288
298
|
method: "POST",
|
|
289
299
|
headers: {
|
|
290
|
-
...auth.headers,
|
|
300
|
+
...toRequestHeaders(auth.headers),
|
|
291
301
|
Authorization: `Bearer ${auth.apiKey}`,
|
|
292
302
|
"Content-Type": "application/json",
|
|
293
303
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { activityMonitor } from "./activity.ts";
|
|
4
|
+
import { canAttachImages } from "./feature-config.ts";
|
|
4
5
|
import { isGeminiWebAvailable, queryWithCookies } from "./gemini-web.ts";
|
|
5
6
|
import { isGeminiApiAvailable, queryGeminiApiWithVideo } from "./gemini-api.ts";
|
|
6
7
|
import { isPerplexityAvailable, searchWithPerplexity } from "./perplexity.ts";
|
|
@@ -116,7 +117,7 @@ export async function extractYouTube(
|
|
|
116
117
|
|
|
117
118
|
if (result) {
|
|
118
119
|
result.url = url;
|
|
119
|
-
if (!result.error && videoId) {
|
|
120
|
+
if (!result.error && videoId && canAttachImages()) {
|
|
120
121
|
const thumb = await fetchYouTubeThumbnail(videoId);
|
|
121
122
|
if (thumb) result.thumbnail = thumb;
|
|
122
123
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jacob Moura
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|