@oh-my-pi/pi-coding-agent 0.1.0
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/CHANGELOG.md +1629 -0
- package/README.md +1041 -0
- package/docs/compaction.md +403 -0
- package/docs/config-usage.md +113 -0
- package/docs/custom-tools.md +541 -0
- package/docs/extension-loading.md +1004 -0
- package/docs/hooks.md +867 -0
- package/docs/rpc.md +1040 -0
- package/docs/sdk.md +994 -0
- package/docs/session-tree-plan.md +441 -0
- package/docs/session.md +240 -0
- package/docs/skills.md +290 -0
- package/docs/theme.md +670 -0
- package/docs/tree.md +197 -0
- package/docs/tui.md +341 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +124 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/custom-tools/question/index.ts +84 -0
- package/examples/custom-tools/subagent/README.md +172 -0
- package/examples/custom-tools/subagent/agents/planner.md +37 -0
- package/examples/custom-tools/subagent/agents/scout.md +50 -0
- package/examples/custom-tools/subagent/agents/worker.md +24 -0
- package/examples/custom-tools/subagent/agents.ts +156 -0
- package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
- package/examples/custom-tools/subagent/commands/implement.md +10 -0
- package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
- package/examples/custom-tools/subagent/index.ts +1002 -0
- package/examples/custom-tools/todo/index.ts +212 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +49 -0
- package/examples/hooks/confirm-destructive.ts +59 -0
- package/examples/hooks/custom-compaction.ts +116 -0
- package/examples/hooks/dirty-repo-guard.ts +52 -0
- package/examples/hooks/file-trigger.ts +41 -0
- package/examples/hooks/git-checkpoint.ts +53 -0
- package/examples/hooks/handoff.ts +150 -0
- package/examples/hooks/permission-gate.ts +34 -0
- package/examples/hooks/protected-paths.ts +30 -0
- package/examples/hooks/qna.ts +119 -0
- package/examples/hooks/snake.ts +343 -0
- package/examples/hooks/status-line.ts +40 -0
- package/examples/sdk/01-minimal.ts +22 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +44 -0
- package/examples/sdk/04-skills.ts +44 -0
- package/examples/sdk/05-tools.ts +90 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +36 -0
- package/examples/sdk/08-slash-commands.ts +42 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
- package/examples/sdk/10-settings.ts +38 -0
- package/examples/sdk/11-sessions.ts +48 -0
- package/examples/sdk/12-full-control.ts +95 -0
- package/examples/sdk/README.md +154 -0
- package/package.json +89 -0
- package/src/bun-imports.d.ts +16 -0
- package/src/capability/context-file.ts +40 -0
- package/src/capability/extension.ts +48 -0
- package/src/capability/hook.ts +40 -0
- package/src/capability/index.ts +616 -0
- package/src/capability/instruction.ts +37 -0
- package/src/capability/mcp.ts +52 -0
- package/src/capability/prompt.ts +35 -0
- package/src/capability/rule.ts +56 -0
- package/src/capability/settings.ts +35 -0
- package/src/capability/skill.ts +49 -0
- package/src/capability/slash-command.ts +40 -0
- package/src/capability/system-prompt.ts +35 -0
- package/src/capability/tool.ts +38 -0
- package/src/capability/types.ts +166 -0
- package/src/cli/args.ts +259 -0
- package/src/cli/file-processor.ts +121 -0
- package/src/cli/list-models.ts +104 -0
- package/src/cli/plugin-cli.ts +661 -0
- package/src/cli/session-picker.ts +41 -0
- package/src/cli/update-cli.ts +274 -0
- package/src/cli.ts +10 -0
- package/src/config.ts +391 -0
- package/src/core/agent-session.ts +2178 -0
- package/src/core/auth-storage.ts +258 -0
- package/src/core/bash-executor.ts +197 -0
- package/src/core/compaction/branch-summarization.ts +315 -0
- package/src/core/compaction/compaction.ts +664 -0
- package/src/core/compaction/index.ts +7 -0
- package/src/core/compaction/utils.ts +153 -0
- package/src/core/custom-commands/bundled/review/index.ts +156 -0
- package/src/core/custom-commands/index.ts +15 -0
- package/src/core/custom-commands/loader.ts +226 -0
- package/src/core/custom-commands/types.ts +112 -0
- package/src/core/custom-tools/index.ts +22 -0
- package/src/core/custom-tools/loader.ts +248 -0
- package/src/core/custom-tools/types.ts +185 -0
- package/src/core/custom-tools/wrapper.ts +29 -0
- package/src/core/exec.ts +139 -0
- package/src/core/export-html/index.ts +159 -0
- package/src/core/export-html/template.css +774 -0
- package/src/core/export-html/template.generated.ts +2 -0
- package/src/core/export-html/template.html +45 -0
- package/src/core/export-html/template.js +1185 -0
- package/src/core/export-html/template.macro.ts +24 -0
- package/src/core/file-mentions.ts +54 -0
- package/src/core/hooks/index.ts +16 -0
- package/src/core/hooks/loader.ts +288 -0
- package/src/core/hooks/runner.ts +434 -0
- package/src/core/hooks/tool-wrapper.ts +98 -0
- package/src/core/hooks/types.ts +770 -0
- package/src/core/index.ts +53 -0
- package/src/core/logger.ts +112 -0
- package/src/core/mcp/client.ts +185 -0
- package/src/core/mcp/config.ts +248 -0
- package/src/core/mcp/index.ts +45 -0
- package/src/core/mcp/loader.ts +99 -0
- package/src/core/mcp/manager.ts +235 -0
- package/src/core/mcp/tool-bridge.ts +156 -0
- package/src/core/mcp/transports/http.ts +316 -0
- package/src/core/mcp/transports/index.ts +6 -0
- package/src/core/mcp/transports/stdio.ts +252 -0
- package/src/core/mcp/types.ts +228 -0
- package/src/core/messages.ts +211 -0
- package/src/core/model-registry.ts +334 -0
- package/src/core/model-resolver.ts +494 -0
- package/src/core/plugins/doctor.ts +67 -0
- package/src/core/plugins/index.ts +38 -0
- package/src/core/plugins/installer.ts +189 -0
- package/src/core/plugins/loader.ts +339 -0
- package/src/core/plugins/manager.ts +672 -0
- package/src/core/plugins/parser.ts +105 -0
- package/src/core/plugins/paths.ts +37 -0
- package/src/core/plugins/types.ts +190 -0
- package/src/core/sdk.ts +900 -0
- package/src/core/session-manager.ts +1837 -0
- package/src/core/settings-manager.ts +860 -0
- package/src/core/skills.ts +352 -0
- package/src/core/slash-commands.ts +132 -0
- package/src/core/system-prompt.ts +442 -0
- package/src/core/timings.ts +25 -0
- package/src/core/title-generator.ts +110 -0
- package/src/core/tools/ask.ts +193 -0
- package/src/core/tools/bash-interceptor.ts +120 -0
- package/src/core/tools/bash.ts +91 -0
- package/src/core/tools/context.ts +32 -0
- package/src/core/tools/edit-diff.ts +487 -0
- package/src/core/tools/edit.ts +140 -0
- package/src/core/tools/exa/company.ts +59 -0
- package/src/core/tools/exa/index.ts +63 -0
- package/src/core/tools/exa/linkedin.ts +59 -0
- package/src/core/tools/exa/mcp-client.ts +368 -0
- package/src/core/tools/exa/render.ts +200 -0
- package/src/core/tools/exa/researcher.ts +90 -0
- package/src/core/tools/exa/search.ts +338 -0
- package/src/core/tools/exa/types.ts +167 -0
- package/src/core/tools/exa/websets.ts +248 -0
- package/src/core/tools/find.ts +244 -0
- package/src/core/tools/grep.ts +584 -0
- package/src/core/tools/index.ts +283 -0
- package/src/core/tools/ls.ts +142 -0
- package/src/core/tools/lsp/client.ts +767 -0
- package/src/core/tools/lsp/clients/biome-client.ts +207 -0
- package/src/core/tools/lsp/clients/index.ts +49 -0
- package/src/core/tools/lsp/clients/lsp-linter-client.ts +98 -0
- package/src/core/tools/lsp/config.ts +845 -0
- package/src/core/tools/lsp/edits.ts +110 -0
- package/src/core/tools/lsp/index.ts +1364 -0
- package/src/core/tools/lsp/render.ts +560 -0
- package/src/core/tools/lsp/rust-analyzer.ts +145 -0
- package/src/core/tools/lsp/types.ts +495 -0
- package/src/core/tools/lsp/utils.ts +526 -0
- package/src/core/tools/notebook.ts +182 -0
- package/src/core/tools/output.ts +198 -0
- package/src/core/tools/path-utils.ts +61 -0
- package/src/core/tools/read.ts +507 -0
- package/src/core/tools/renderers.ts +820 -0
- package/src/core/tools/review.ts +275 -0
- package/src/core/tools/rulebook.ts +124 -0
- package/src/core/tools/task/agents.ts +158 -0
- package/src/core/tools/task/artifacts.ts +114 -0
- package/src/core/tools/task/commands.ts +157 -0
- package/src/core/tools/task/discovery.ts +217 -0
- package/src/core/tools/task/executor.ts +531 -0
- package/src/core/tools/task/index.ts +548 -0
- package/src/core/tools/task/model-resolver.ts +176 -0
- package/src/core/tools/task/parallel.ts +38 -0
- package/src/core/tools/task/render.ts +502 -0
- package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
- package/src/core/tools/task/types.ts +142 -0
- package/src/core/tools/truncate.ts +265 -0
- package/src/core/tools/web-fetch.ts +2511 -0
- package/src/core/tools/web-search/auth.ts +199 -0
- package/src/core/tools/web-search/index.ts +583 -0
- package/src/core/tools/web-search/providers/anthropic.ts +198 -0
- package/src/core/tools/web-search/providers/exa.ts +196 -0
- package/src/core/tools/web-search/providers/perplexity.ts +195 -0
- package/src/core/tools/web-search/render.ts +372 -0
- package/src/core/tools/web-search/types.ts +180 -0
- package/src/core/tools/write.ts +63 -0
- package/src/core/ttsr.ts +211 -0
- package/src/core/utils.ts +187 -0
- package/src/discovery/agents-md.ts +75 -0
- package/src/discovery/builtin.ts +647 -0
- package/src/discovery/claude.ts +623 -0
- package/src/discovery/cline.ts +104 -0
- package/src/discovery/codex.ts +571 -0
- package/src/discovery/cursor.ts +266 -0
- package/src/discovery/gemini.ts +368 -0
- package/src/discovery/github.ts +120 -0
- package/src/discovery/helpers.test.ts +127 -0
- package/src/discovery/helpers.ts +249 -0
- package/src/discovery/index.ts +84 -0
- package/src/discovery/mcp-json.ts +127 -0
- package/src/discovery/vscode.ts +99 -0
- package/src/discovery/windsurf.ts +219 -0
- package/src/index.ts +192 -0
- package/src/main.ts +507 -0
- package/src/migrations.ts +156 -0
- package/src/modes/cleanup.ts +23 -0
- package/src/modes/index.ts +48 -0
- package/src/modes/interactive/components/armin.ts +382 -0
- package/src/modes/interactive/components/assistant-message.ts +86 -0
- package/src/modes/interactive/components/bash-execution.ts +199 -0
- package/src/modes/interactive/components/bordered-loader.ts +41 -0
- package/src/modes/interactive/components/branch-summary-message.ts +42 -0
- package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
- package/src/modes/interactive/components/custom-editor.ts +122 -0
- package/src/modes/interactive/components/diff.ts +147 -0
- package/src/modes/interactive/components/dynamic-border.ts +25 -0
- package/src/modes/interactive/components/extensions/extension-dashboard.ts +296 -0
- package/src/modes/interactive/components/extensions/extension-list.ts +479 -0
- package/src/modes/interactive/components/extensions/index.ts +9 -0
- package/src/modes/interactive/components/extensions/inspector-panel.ts +313 -0
- package/src/modes/interactive/components/extensions/state-manager.ts +558 -0
- package/src/modes/interactive/components/extensions/types.ts +191 -0
- package/src/modes/interactive/components/hook-editor.ts +117 -0
- package/src/modes/interactive/components/hook-input.ts +64 -0
- package/src/modes/interactive/components/hook-message.ts +96 -0
- package/src/modes/interactive/components/hook-selector.ts +91 -0
- package/src/modes/interactive/components/model-selector.ts +560 -0
- package/src/modes/interactive/components/oauth-selector.ts +136 -0
- package/src/modes/interactive/components/plugin-settings.ts +481 -0
- package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
- package/src/modes/interactive/components/session-selector.ts +220 -0
- package/src/modes/interactive/components/settings-defs.ts +597 -0
- package/src/modes/interactive/components/settings-selector.ts +545 -0
- package/src/modes/interactive/components/show-images-selector.ts +45 -0
- package/src/modes/interactive/components/status-line/index.ts +4 -0
- package/src/modes/interactive/components/status-line/presets.ts +94 -0
- package/src/modes/interactive/components/status-line/segments.ts +350 -0
- package/src/modes/interactive/components/status-line/separators.ts +55 -0
- package/src/modes/interactive/components/status-line/types.ts +81 -0
- package/src/modes/interactive/components/status-line-segment-editor.ts +357 -0
- package/src/modes/interactive/components/status-line.ts +384 -0
- package/src/modes/interactive/components/theme-selector.ts +62 -0
- package/src/modes/interactive/components/thinking-selector.ts +64 -0
- package/src/modes/interactive/components/tool-execution.ts +946 -0
- package/src/modes/interactive/components/tree-selector.ts +877 -0
- package/src/modes/interactive/components/ttsr-notification.ts +82 -0
- package/src/modes/interactive/components/user-message-selector.ts +159 -0
- package/src/modes/interactive/components/user-message.ts +18 -0
- package/src/modes/interactive/components/visual-truncate.ts +50 -0
- package/src/modes/interactive/components/welcome.ts +228 -0
- package/src/modes/interactive/interactive-mode.ts +2669 -0
- package/src/modes/interactive/theme/dark.json +102 -0
- package/src/modes/interactive/theme/defaults/dark-arctic.json +111 -0
- package/src/modes/interactive/theme/defaults/dark-catppuccin.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-cyberpunk.json +109 -0
- package/src/modes/interactive/theme/defaults/dark-dracula.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-forest.json +103 -0
- package/src/modes/interactive/theme/defaults/dark-github.json +112 -0
- package/src/modes/interactive/theme/defaults/dark-gruvbox.json +119 -0
- package/src/modes/interactive/theme/defaults/dark-monochrome.json +101 -0
- package/src/modes/interactive/theme/defaults/dark-monokai.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-nord.json +104 -0
- package/src/modes/interactive/theme/defaults/dark-ocean.json +108 -0
- package/src/modes/interactive/theme/defaults/dark-one.json +107 -0
- package/src/modes/interactive/theme/defaults/dark-retro.json +99 -0
- package/src/modes/interactive/theme/defaults/dark-rose-pine.json +95 -0
- package/src/modes/interactive/theme/defaults/dark-solarized.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-synthwave.json +102 -0
- package/src/modes/interactive/theme/defaults/dark-tokyo-night.json +108 -0
- package/src/modes/interactive/theme/defaults/index.ts +67 -0
- package/src/modes/interactive/theme/defaults/light-arctic.json +106 -0
- package/src/modes/interactive/theme/defaults/light-catppuccin.json +105 -0
- package/src/modes/interactive/theme/defaults/light-cyberpunk.json +103 -0
- package/src/modes/interactive/theme/defaults/light-forest.json +107 -0
- package/src/modes/interactive/theme/defaults/light-github.json +114 -0
- package/src/modes/interactive/theme/defaults/light-gruvbox.json +115 -0
- package/src/modes/interactive/theme/defaults/light-monochrome.json +100 -0
- package/src/modes/interactive/theme/defaults/light-ocean.json +106 -0
- package/src/modes/interactive/theme/defaults/light-one.json +105 -0
- package/src/modes/interactive/theme/defaults/light-retro.json +105 -0
- package/src/modes/interactive/theme/defaults/light-solarized.json +101 -0
- package/src/modes/interactive/theme/defaults/light-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/light-synthwave.json +105 -0
- package/src/modes/interactive/theme/defaults/light-tokyo-night.json +118 -0
- package/src/modes/interactive/theme/light.json +99 -0
- package/src/modes/interactive/theme/theme-schema.json +424 -0
- package/src/modes/interactive/theme/theme.ts +2211 -0
- package/src/modes/print-mode.ts +163 -0
- package/src/modes/rpc/rpc-client.ts +527 -0
- package/src/modes/rpc/rpc-mode.ts +494 -0
- package/src/modes/rpc/rpc-types.ts +203 -0
- package/src/prompts/architect-plan.md +10 -0
- package/src/prompts/branch-summary-preamble.md +3 -0
- package/src/prompts/branch-summary.md +28 -0
- package/src/prompts/browser.md +71 -0
- package/src/prompts/compaction-summary.md +34 -0
- package/src/prompts/compaction-turn-prefix.md +16 -0
- package/src/prompts/compaction-update-summary.md +41 -0
- package/src/prompts/explore.md +82 -0
- package/src/prompts/implement-with-critic.md +11 -0
- package/src/prompts/implement.md +11 -0
- package/src/prompts/init.md +30 -0
- package/src/prompts/plan.md +54 -0
- package/src/prompts/reviewer.md +81 -0
- package/src/prompts/summarization-system.md +3 -0
- package/src/prompts/system-prompt.md +27 -0
- package/src/prompts/task.md +56 -0
- package/src/prompts/title-system.md +8 -0
- package/src/prompts/tools/ask.md +24 -0
- package/src/prompts/tools/bash.md +23 -0
- package/src/prompts/tools/edit.md +9 -0
- package/src/prompts/tools/find.md +6 -0
- package/src/prompts/tools/grep.md +12 -0
- package/src/prompts/tools/lsp.md +14 -0
- package/src/prompts/tools/output.md +23 -0
- package/src/prompts/tools/read.md +25 -0
- package/src/prompts/tools/web-fetch.md +8 -0
- package/src/prompts/tools/web-search.md +10 -0
- package/src/prompts/tools/write.md +10 -0
- package/src/utils/changelog.ts +99 -0
- package/src/utils/clipboard.ts +265 -0
- package/src/utils/fuzzy.ts +108 -0
- package/src/utils/mime.ts +30 -0
- package/src/utils/shell-snapshot.ts +218 -0
- package/src/utils/shell.ts +364 -0
- package/src/utils/tools-manager.ts +265 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Interact with Language Server Protocol (LSP) servers to get code intelligence features.
|
|
2
|
+
|
|
3
|
+
Standard operations:
|
|
4
|
+
- diagnostics: Get errors/warnings for a file
|
|
5
|
+
- workspace_diagnostics: Check entire project for errors (uses tsc, cargo check, go build, etc.)
|
|
6
|
+
- definition: Go to symbol definition
|
|
7
|
+
- references: Find all references to a symbol
|
|
8
|
+
- hover: Get type info and documentation
|
|
9
|
+
- symbols: List symbols in a file (functions, classes, etc.)
|
|
10
|
+
- workspace_symbols: Search for symbols across the project
|
|
11
|
+
- rename: Rename a symbol across the codebase
|
|
12
|
+
- actions: List and apply code actions (quick fixes, refactors)
|
|
13
|
+
- incoming_calls: Find all callers of a function
|
|
14
|
+
- outgoing_calls: Find all functions called by a function
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# TaskOutput
|
|
2
|
+
|
|
3
|
+
Retrieves complete output from background tasks spawned with the Task tool.
|
|
4
|
+
|
|
5
|
+
## When to Use
|
|
6
|
+
|
|
7
|
+
Use TaskOutput when:
|
|
8
|
+
- Task tool returns truncated preview with "Output truncated" message
|
|
9
|
+
- You need full output to debug errors or analyze detailed results
|
|
10
|
+
- Task tool's summary shows substantial line/character counts but preview is incomplete
|
|
11
|
+
- You're analyzing multi-step task output requiring full context
|
|
12
|
+
|
|
13
|
+
Do NOT use when:
|
|
14
|
+
- Task preview already shows complete output (no truncation indicator)
|
|
15
|
+
- Summary alone answers your question
|
|
16
|
+
|
|
17
|
+
## Parameters
|
|
18
|
+
|
|
19
|
+
- `ids`: Array of output IDs from Task results (e.g., `["reviewer_0", "explore_1"]`)
|
|
20
|
+
- `format` (optional):
|
|
21
|
+
- `"raw"` (default): Full output with ANSI codes preserved
|
|
22
|
+
- `"json"`: Structured object with metadata
|
|
23
|
+
- `"stripped"`: Plain text with ANSI codes removed for parsing
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Reads a file from the local filesystem. You can access any file directly by using this tool.
|
|
2
|
+
Assume this tool is able to read all files on the machine. If the User provides a path to a file assume that path is valid. It is okay to read a file that does not exist; an error will be returned.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
- By default, it reads up to {{DEFAULT_MAX_LINES}} lines starting from the beginning of the file
|
|
6
|
+
- You can optionally specify a line offset and limit (especially handy for long files), but it's recommended to read the whole file by not providing these parameters
|
|
7
|
+
- Any lines longer than 500 characters will be truncated
|
|
8
|
+
- Results are returned using cat -n format, with line numbers starting at 1
|
|
9
|
+
- This tool allows Claude Code to read images (eg PNG, JPG, etc). When reading an image file the contents are presented visually as Claude Code is a multimodal LLM.
|
|
10
|
+
- This tool can read PDF files (.pdf). PDFs are processed page by page, extracting both text and visual content for analysis.
|
|
11
|
+
- This tool can read Jupyter notebooks (.ipynb files) and returns all cells with their outputs, combining code, text, and visualizations.
|
|
12
|
+
- This tool can only read files, not directories. To read a directory, use an ls command via the bash tool.
|
|
13
|
+
- You can call multiple tools in a single response. It is always better to speculatively read multiple potentially useful files in parallel.
|
|
14
|
+
- You will regularly be asked to read screenshots. If the user provides a path to a screenshot, ALWAYS use this tool to view the file at the path. This tool will work with all temporary file paths.
|
|
15
|
+
- If you read a file that exists but has empty contents you will receive a system reminder warning in place of file contents.
|
|
16
|
+
|
|
17
|
+
Empty files trigger a warning. Directory paths return an ls-style listing. Missing files return an error with closest matches (gitignore respected).
|
|
18
|
+
|
|
19
|
+
## Best Practices
|
|
20
|
+
|
|
21
|
+
- Parallelize reads when exploring related files
|
|
22
|
+
- Read before editing (required in current session)
|
|
23
|
+
- Trust user-provided paths; attempt the read
|
|
24
|
+
- Screenshots: Read tool renders images visually
|
|
25
|
+
- Skip re-reading after edits (Edit/Write report errors)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Fetches and analyzes web content by retrieving a URL
|
|
2
|
+
|
|
3
|
+
Use this tool when you need to:
|
|
4
|
+
- Extract specific information from web pages (documentation, articles, API references)
|
|
5
|
+
- Analyze GitHub issues, pull requests, or repository content
|
|
6
|
+
- Retrieve information from Stack Overflow, Wikipedia, Reddit, NPM, arXiv, or technical blogs
|
|
7
|
+
- Access RSS/Atom feeds or JSON endpoints
|
|
8
|
+
- Read PDF or DOCX files hosted at a URL
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Allows OMP to search the web and use the results to inform responses
|
|
2
|
+
- Provides up-to-date information for current events and recent data
|
|
3
|
+
- Returns search result information formatted as search result blocks, including links as markdown hyperlinks
|
|
4
|
+
- Use this tool for accessing information beyond Claude's knowledge cutoff
|
|
5
|
+
- Searches are performed automatically within a single API call
|
|
6
|
+
|
|
7
|
+
Common: system_prompt (guides response style)
|
|
8
|
+
Anthropic-specific: max_tokens
|
|
9
|
+
Perplexity-specific: model (sonar/sonar-pro), search_recency_filter, search_domain_filter, search_context_size, return_related_questions
|
|
10
|
+
Exa-specific: num_results
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Creates or overwrites a file at the specified path.
|
|
2
|
+
|
|
3
|
+
When to use:
|
|
4
|
+
- Creating new files explicitly required by the task
|
|
5
|
+
- Replacing entire file contents when editing would be more complex
|
|
6
|
+
|
|
7
|
+
Critical requirements:
|
|
8
|
+
- Prefer Edit tool for modifying existing files (more precise, preserves formatting)
|
|
9
|
+
- Create documentation files (*.md, README) only when explicitly requested
|
|
10
|
+
- Include emojis only when explicitly requested
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
export interface ChangelogEntry {
|
|
4
|
+
major: number;
|
|
5
|
+
minor: number;
|
|
6
|
+
patch: number;
|
|
7
|
+
content: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Parse changelog entries from CHANGELOG.md
|
|
12
|
+
* Scans for ## lines and collects content until next ## or EOF
|
|
13
|
+
*/
|
|
14
|
+
export function parseChangelog(changelogPath: string): ChangelogEntry[] {
|
|
15
|
+
if (!existsSync(changelogPath)) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const content = readFileSync(changelogPath, "utf-8");
|
|
21
|
+
const lines = content.split("\n");
|
|
22
|
+
const entries: ChangelogEntry[] = [];
|
|
23
|
+
|
|
24
|
+
let currentLines: string[] = [];
|
|
25
|
+
let currentVersion: { major: number; minor: number; patch: number } | null = null;
|
|
26
|
+
|
|
27
|
+
for (const line of lines) {
|
|
28
|
+
// Check if this is a version header (## [x.y.z] ...)
|
|
29
|
+
if (line.startsWith("## ")) {
|
|
30
|
+
// Save previous entry if exists
|
|
31
|
+
if (currentVersion && currentLines.length > 0) {
|
|
32
|
+
entries.push({
|
|
33
|
+
...currentVersion,
|
|
34
|
+
content: currentLines.join("\n").trim(),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Try to parse version from this line
|
|
39
|
+
const versionMatch = line.match(/##\s+\[?(\d+)\.(\d+)\.(\d+)\]?/);
|
|
40
|
+
if (versionMatch) {
|
|
41
|
+
currentVersion = {
|
|
42
|
+
major: Number.parseInt(versionMatch[1], 10),
|
|
43
|
+
minor: Number.parseInt(versionMatch[2], 10),
|
|
44
|
+
patch: Number.parseInt(versionMatch[3], 10),
|
|
45
|
+
};
|
|
46
|
+
currentLines = [line];
|
|
47
|
+
} else {
|
|
48
|
+
// Reset if we can't parse version
|
|
49
|
+
currentVersion = null;
|
|
50
|
+
currentLines = [];
|
|
51
|
+
}
|
|
52
|
+
} else if (currentVersion) {
|
|
53
|
+
// Collect lines for current version
|
|
54
|
+
currentLines.push(line);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Save last entry
|
|
59
|
+
if (currentVersion && currentLines.length > 0) {
|
|
60
|
+
entries.push({
|
|
61
|
+
...currentVersion,
|
|
62
|
+
content: currentLines.join("\n").trim(),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return entries;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error(`Warning: Could not parse changelog: ${error}`);
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Compare versions. Returns: -1 if v1 < v2, 0 if v1 === v2, 1 if v1 > v2
|
|
75
|
+
*/
|
|
76
|
+
export function compareVersions(v1: ChangelogEntry, v2: ChangelogEntry): number {
|
|
77
|
+
if (v1.major !== v2.major) return v1.major - v2.major;
|
|
78
|
+
if (v1.minor !== v2.minor) return v1.minor - v2.minor;
|
|
79
|
+
return v1.patch - v2.patch;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get entries newer than lastVersion
|
|
84
|
+
*/
|
|
85
|
+
export function getNewEntries(entries: ChangelogEntry[], lastVersion: string): ChangelogEntry[] {
|
|
86
|
+
// Parse lastVersion
|
|
87
|
+
const parts = lastVersion.split(".").map(Number);
|
|
88
|
+
const last: ChangelogEntry = {
|
|
89
|
+
major: parts[0] || 0,
|
|
90
|
+
minor: parts[1] || 0,
|
|
91
|
+
patch: parts[2] || 0,
|
|
92
|
+
content: "",
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
return entries.filter((entry) => compareVersions(entry, last) > 0);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Re-export getChangelogPath from paths.ts for convenience
|
|
99
|
+
export { getChangelogPath } from "../config";
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { platform } from "node:os";
|
|
2
|
+
|
|
3
|
+
async function spawnWithTimeout(cmd: string[], input: string, timeoutMs: number): Promise<void> {
|
|
4
|
+
const proc = Bun.spawn(cmd, { stdin: "pipe" });
|
|
5
|
+
|
|
6
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
7
|
+
setTimeout(() => reject(new Error("Clipboard operation timed out")), timeoutMs);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
proc.stdin.write(input);
|
|
12
|
+
proc.stdin.end();
|
|
13
|
+
await Promise.race([proc.exited, timeoutPromise]);
|
|
14
|
+
|
|
15
|
+
if (proc.exitCode !== 0) {
|
|
16
|
+
throw new Error(`Command failed with exit code ${proc.exitCode}`);
|
|
17
|
+
}
|
|
18
|
+
} finally {
|
|
19
|
+
proc.kill();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function spawnAndRead(cmd: string[], timeoutMs: number): Promise<Buffer | null> {
|
|
24
|
+
const proc = Bun.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
25
|
+
|
|
26
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
27
|
+
setTimeout(() => reject(new Error("Clipboard operation timed out")), timeoutMs);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const [exitCode, stdout] = await Promise.race([
|
|
32
|
+
Promise.all([proc.exited, new Response(proc.stdout).arrayBuffer()]),
|
|
33
|
+
timeoutPromise,
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
if (exitCode !== 0) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return Buffer.from(stdout);
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
} finally {
|
|
44
|
+
proc.kill();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function copyToClipboard(text: string): Promise<void> {
|
|
49
|
+
const p = platform();
|
|
50
|
+
const timeout = 5000;
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
if (p === "darwin") {
|
|
54
|
+
await spawnWithTimeout(["pbcopy"], text, timeout);
|
|
55
|
+
} else if (p === "win32") {
|
|
56
|
+
await spawnWithTimeout(["clip"], text, timeout);
|
|
57
|
+
} else {
|
|
58
|
+
// Linux - try xclip first, fall back to xsel
|
|
59
|
+
try {
|
|
60
|
+
await spawnWithTimeout(["xclip", "-selection", "clipboard"], text, timeout);
|
|
61
|
+
} catch {
|
|
62
|
+
await spawnWithTimeout(["xsel", "--clipboard", "--input"], text, timeout);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} catch (error) {
|
|
66
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
67
|
+
if (p === "linux") {
|
|
68
|
+
throw new Error(`Failed to copy to clipboard. Install xclip or xsel: ${msg}`);
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`Failed to copy to clipboard: ${msg}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface ClipboardImage {
|
|
75
|
+
data: string; // base64 encoded
|
|
76
|
+
mimeType: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Read image from system clipboard if available.
|
|
81
|
+
* Returns null if no image is in clipboard or clipboard access fails.
|
|
82
|
+
*
|
|
83
|
+
* Supported platforms:
|
|
84
|
+
* - Linux: requires xclip
|
|
85
|
+
* - macOS: uses osascript + pbpaste
|
|
86
|
+
* - Windows: uses PowerShell
|
|
87
|
+
*/
|
|
88
|
+
export async function readImageFromClipboard(): Promise<ClipboardImage | null> {
|
|
89
|
+
const p = platform();
|
|
90
|
+
const timeout = 3000;
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
if (p === "linux") {
|
|
94
|
+
return await readImageLinux(timeout);
|
|
95
|
+
} else if (p === "darwin") {
|
|
96
|
+
return await readImageMacOS(timeout);
|
|
97
|
+
} else if (p === "win32") {
|
|
98
|
+
return await readImageWindows(timeout);
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
// Clipboard access failed silently
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function readImageLinux(timeout: number): Promise<ClipboardImage | null> {
|
|
108
|
+
// Try Wayland first (wl-paste), then X11 (xclip)
|
|
109
|
+
const wayland = await readImageWayland(timeout);
|
|
110
|
+
if (wayland) return wayland;
|
|
111
|
+
|
|
112
|
+
return await readImageX11(timeout);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function readImageWayland(timeout: number): Promise<ClipboardImage | null> {
|
|
116
|
+
// wl-paste --list-types shows available MIME types
|
|
117
|
+
const types = await spawnAndRead(["wl-paste", "--list-types"], timeout);
|
|
118
|
+
if (!types) return null;
|
|
119
|
+
|
|
120
|
+
const typeList = types.toString("utf-8");
|
|
121
|
+
|
|
122
|
+
// Try PNG first, then JPEG
|
|
123
|
+
const imageTypes = [
|
|
124
|
+
{ type: "image/png", mimeType: "image/png" },
|
|
125
|
+
{ type: "image/jpeg", mimeType: "image/jpeg" },
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
for (const { type, mimeType } of imageTypes) {
|
|
129
|
+
if (typeList.includes(type)) {
|
|
130
|
+
const imageData = await spawnAndRead(["wl-paste", "--type", type], timeout);
|
|
131
|
+
if (imageData && imageData.length > 0) {
|
|
132
|
+
return {
|
|
133
|
+
data: imageData.toString("base64"),
|
|
134
|
+
mimeType,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function readImageX11(timeout: number): Promise<ClipboardImage | null> {
|
|
144
|
+
// Check available targets in clipboard
|
|
145
|
+
const targets = await spawnAndRead(["xclip", "-selection", "clipboard", "-t", "TARGETS", "-o"], timeout);
|
|
146
|
+
if (!targets) return null;
|
|
147
|
+
|
|
148
|
+
const targetList = targets.toString("utf-8");
|
|
149
|
+
|
|
150
|
+
// Try PNG first (preferred), then JPEG
|
|
151
|
+
const imageTypes = [
|
|
152
|
+
{ target: "image/png", mimeType: "image/png" },
|
|
153
|
+
{ target: "image/jpeg", mimeType: "image/jpeg" },
|
|
154
|
+
{ target: "image/jpg", mimeType: "image/jpeg" },
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
for (const { target, mimeType } of imageTypes) {
|
|
158
|
+
if (targetList.includes(target)) {
|
|
159
|
+
const imageData = await spawnAndRead(["xclip", "-selection", "clipboard", "-t", target, "-o"], timeout);
|
|
160
|
+
if (imageData && imageData.length > 0) {
|
|
161
|
+
return {
|
|
162
|
+
data: imageData.toString("base64"),
|
|
163
|
+
mimeType,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function readImageMacOS(timeout: number): Promise<ClipboardImage | null> {
|
|
173
|
+
// Use osascript to check clipboard class and read PNG data
|
|
174
|
+
// First check if clipboard has image data
|
|
175
|
+
const checkScript = `
|
|
176
|
+
try
|
|
177
|
+
clipboard info for «class PNGf»
|
|
178
|
+
return "png"
|
|
179
|
+
on error
|
|
180
|
+
try
|
|
181
|
+
clipboard info for «class JPEG»
|
|
182
|
+
return "jpeg"
|
|
183
|
+
on error
|
|
184
|
+
return "none"
|
|
185
|
+
end try
|
|
186
|
+
end try
|
|
187
|
+
`;
|
|
188
|
+
|
|
189
|
+
const checkProc = Bun.spawn(["osascript", "-e", checkScript], { stdout: "pipe", stderr: "pipe" });
|
|
190
|
+
const checkResult = await Promise.race([
|
|
191
|
+
new Response(checkProc.stdout).text(),
|
|
192
|
+
new Promise<string>((_, reject) => setTimeout(() => reject(new Error("timeout")), timeout)),
|
|
193
|
+
]).catch(() => "none");
|
|
194
|
+
|
|
195
|
+
await checkProc.exited;
|
|
196
|
+
const imageType = checkResult.trim();
|
|
197
|
+
|
|
198
|
+
if (imageType === "none") return null;
|
|
199
|
+
|
|
200
|
+
// Read the actual image data using a temp file approach
|
|
201
|
+
// osascript can't output binary directly, so we write to a temp file
|
|
202
|
+
const tempFile = `/tmp/omp-clipboard-${Date.now()}.${imageType === "png" ? "png" : "jpg"}`;
|
|
203
|
+
const clipboardClass = imageType === "png" ? "«class PNGf»" : "«class JPEG»";
|
|
204
|
+
|
|
205
|
+
const readScript = `
|
|
206
|
+
set imageData to the clipboard as ${clipboardClass}
|
|
207
|
+
set filePath to POSIX file "${tempFile}"
|
|
208
|
+
set fileRef to open for access filePath with write permission
|
|
209
|
+
write imageData to fileRef
|
|
210
|
+
close access fileRef
|
|
211
|
+
`;
|
|
212
|
+
|
|
213
|
+
const writeProc = Bun.spawn(["osascript", "-e", readScript], { stdout: "pipe", stderr: "pipe" });
|
|
214
|
+
await Promise.race([
|
|
215
|
+
writeProc.exited,
|
|
216
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), timeout)),
|
|
217
|
+
]).catch(() => null);
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
const file = Bun.file(tempFile);
|
|
221
|
+
if (await file.exists()) {
|
|
222
|
+
const buffer = await file.arrayBuffer();
|
|
223
|
+
await Bun.write(tempFile, ""); // Clear file
|
|
224
|
+
const { unlink } = await import("fs/promises");
|
|
225
|
+
await unlink(tempFile).catch(() => {});
|
|
226
|
+
|
|
227
|
+
if (buffer.byteLength > 0) {
|
|
228
|
+
return {
|
|
229
|
+
data: Buffer.from(buffer).toString("base64"),
|
|
230
|
+
mimeType: imageType === "png" ? "image/png" : "image/jpeg",
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
} catch {
|
|
235
|
+
// File read failed
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function readImageWindows(timeout: number): Promise<ClipboardImage | null> {
|
|
242
|
+
// PowerShell script to read image from clipboard as base64
|
|
243
|
+
const script = `
|
|
244
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
245
|
+
$clipboard = [System.Windows.Forms.Clipboard]::GetImage()
|
|
246
|
+
if ($clipboard -ne $null) {
|
|
247
|
+
$ms = New-Object System.IO.MemoryStream
|
|
248
|
+
$clipboard.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png)
|
|
249
|
+
[Convert]::ToBase64String($ms.ToArray())
|
|
250
|
+
}
|
|
251
|
+
`;
|
|
252
|
+
|
|
253
|
+
const result = await spawnAndRead(["powershell", "-NoProfile", "-Command", script], timeout);
|
|
254
|
+
if (result && result.length > 0) {
|
|
255
|
+
const base64 = result.toString("utf-8").trim();
|
|
256
|
+
if (base64.length > 0) {
|
|
257
|
+
return {
|
|
258
|
+
data: base64,
|
|
259
|
+
mimeType: "image/png",
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Fuzzy search. Matches if all query characters appear in order (not necessarily consecutive).
|
|
2
|
+
// Lower score = better match.
|
|
3
|
+
|
|
4
|
+
export interface FuzzyMatch {
|
|
5
|
+
matches: boolean;
|
|
6
|
+
score: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function fuzzyMatch(query: string, text: string): FuzzyMatch {
|
|
10
|
+
const queryLower = query.toLowerCase();
|
|
11
|
+
const textLower = text.toLowerCase();
|
|
12
|
+
|
|
13
|
+
if (queryLower.length === 0) {
|
|
14
|
+
return { matches: true, score: 0 };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (queryLower.length > textLower.length) {
|
|
18
|
+
return { matches: false, score: 0 };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let queryIndex = 0;
|
|
22
|
+
let score = 0;
|
|
23
|
+
let lastMatchIndex = -1;
|
|
24
|
+
let consecutiveMatches = 0;
|
|
25
|
+
|
|
26
|
+
for (let i = 0; i < textLower.length && queryIndex < queryLower.length; i++) {
|
|
27
|
+
if (textLower[i] === queryLower[queryIndex]) {
|
|
28
|
+
const isWordBoundary = i === 0 || /[\s\-_./]/.test(textLower[i - 1]!);
|
|
29
|
+
|
|
30
|
+
// Reward consecutive character matches (e.g., typing "foo" matches "foobar" better than "f_o_o")
|
|
31
|
+
if (lastMatchIndex === i - 1) {
|
|
32
|
+
consecutiveMatches++;
|
|
33
|
+
score -= consecutiveMatches * 5;
|
|
34
|
+
} else {
|
|
35
|
+
consecutiveMatches = 0;
|
|
36
|
+
// Penalize gaps between matched characters
|
|
37
|
+
if (lastMatchIndex >= 0) {
|
|
38
|
+
score += (i - lastMatchIndex - 1) * 2;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Reward matches at word boundaries (start of words are more likely intentional targets)
|
|
43
|
+
if (isWordBoundary) {
|
|
44
|
+
score -= 10;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Slight penalty for matches later in the string (prefer earlier matches)
|
|
48
|
+
score += i * 0.1;
|
|
49
|
+
|
|
50
|
+
lastMatchIndex = i;
|
|
51
|
+
queryIndex++;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Not all query characters were found in order
|
|
56
|
+
if (queryIndex < queryLower.length) {
|
|
57
|
+
return { matches: false, score: 0 };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return { matches: true, score };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Filter and sort items by fuzzy match quality (best matches first)
|
|
64
|
+
// Supports space-separated tokens: all tokens must match, sorted by match count then score
|
|
65
|
+
export function fuzzyFilter<T>(items: T[], query: string, getText: (item: T) => string): T[] {
|
|
66
|
+
if (!query.trim()) {
|
|
67
|
+
return items;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Split query into tokens
|
|
71
|
+
const tokens = query
|
|
72
|
+
.trim()
|
|
73
|
+
.split(/\s+/)
|
|
74
|
+
.filter((t) => t.length > 0);
|
|
75
|
+
|
|
76
|
+
if (tokens.length === 0) {
|
|
77
|
+
return items;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const results: { item: T; totalScore: number }[] = [];
|
|
81
|
+
|
|
82
|
+
for (const item of items) {
|
|
83
|
+
const text = getText(item);
|
|
84
|
+
let totalScore = 0;
|
|
85
|
+
let allMatch = true;
|
|
86
|
+
|
|
87
|
+
// Check each token against the text - ALL must match
|
|
88
|
+
for (const token of tokens) {
|
|
89
|
+
const match = fuzzyMatch(token, text);
|
|
90
|
+
if (match.matches) {
|
|
91
|
+
totalScore += match.score;
|
|
92
|
+
} else {
|
|
93
|
+
allMatch = false;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Only include if all tokens match
|
|
99
|
+
if (allMatch) {
|
|
100
|
+
results.push({ item, totalScore });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Sort by score (asc, lower is better)
|
|
105
|
+
results.sort((a, b) => a.totalScore - b.totalScore);
|
|
106
|
+
|
|
107
|
+
return results.map((r) => r.item);
|
|
108
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { open } from "node:fs/promises";
|
|
2
|
+
import { fileTypeFromBuffer } from "file-type";
|
|
3
|
+
|
|
4
|
+
const IMAGE_MIME_TYPES = new Set(["image/jpeg", "image/png", "image/gif", "image/webp"]);
|
|
5
|
+
|
|
6
|
+
const FILE_TYPE_SNIFF_BYTES = 4100;
|
|
7
|
+
|
|
8
|
+
export async function detectSupportedImageMimeTypeFromFile(filePath: string): Promise<string | null> {
|
|
9
|
+
const fileHandle = await open(filePath, "r");
|
|
10
|
+
try {
|
|
11
|
+
const buffer = Buffer.alloc(FILE_TYPE_SNIFF_BYTES);
|
|
12
|
+
const { bytesRead } = await fileHandle.read(buffer, 0, FILE_TYPE_SNIFF_BYTES, 0);
|
|
13
|
+
if (bytesRead === 0) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const fileType = await fileTypeFromBuffer(buffer.subarray(0, bytesRead));
|
|
18
|
+
if (!fileType) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!IMAGE_MIME_TYPES.has(fileType.mime)) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return fileType.mime;
|
|
27
|
+
} finally {
|
|
28
|
+
await fileHandle.close();
|
|
29
|
+
}
|
|
30
|
+
}
|