@soederpop/luca 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +71 -0
- package/README.md +78 -0
- package/bun.lock +2928 -0
- package/bunfig.toml +3 -0
- package/commands/audit-docs.ts +740 -0
- package/commands/build-scaffolds.ts +154 -0
- package/commands/generate-api-docs.ts +114 -0
- package/commands/update-introspection.ts +67 -0
- package/docs/CLI.md +335 -0
- package/docs/README.md +88 -0
- package/docs/TABLE-OF-CONTENTS.md +157 -0
- package/docs/apis/clients/elevenlabs.md +84 -0
- package/docs/apis/clients/graph.md +56 -0
- package/docs/apis/clients/openai.md +69 -0
- package/docs/apis/clients/rest.md +41 -0
- package/docs/apis/clients/websocket.md +107 -0
- package/docs/apis/features/agi/assistant.md +471 -0
- package/docs/apis/features/agi/assistants-manager.md +154 -0
- package/docs/apis/features/agi/claude-code.md +602 -0
- package/docs/apis/features/agi/conversation-history.md +352 -0
- package/docs/apis/features/agi/conversation.md +333 -0
- package/docs/apis/features/agi/docs-reader.md +121 -0
- package/docs/apis/features/agi/openai-codex.md +318 -0
- package/docs/apis/features/agi/openapi.md +138 -0
- package/docs/apis/features/agi/semantic-search.md +387 -0
- package/docs/apis/features/agi/skills-library.md +216 -0
- package/docs/apis/features/node/container-link.md +133 -0
- package/docs/apis/features/node/content-db.md +313 -0
- package/docs/apis/features/node/disk-cache.md +379 -0
- package/docs/apis/features/node/dns.md +651 -0
- package/docs/apis/features/node/docker.md +705 -0
- package/docs/apis/features/node/downloader.md +81 -0
- package/docs/apis/features/node/esbuild.md +59 -0
- package/docs/apis/features/node/file-manager.md +182 -0
- package/docs/apis/features/node/fs.md +581 -0
- package/docs/apis/features/node/git.md +330 -0
- package/docs/apis/features/node/google-auth.md +174 -0
- package/docs/apis/features/node/google-calendar.md +187 -0
- package/docs/apis/features/node/google-docs.md +151 -0
- package/docs/apis/features/node/google-drive.md +225 -0
- package/docs/apis/features/node/google-sheets.md +179 -0
- package/docs/apis/features/node/grep.md +290 -0
- package/docs/apis/features/node/helpers.md +135 -0
- package/docs/apis/features/node/ink.md +334 -0
- package/docs/apis/features/node/ipc-socket.md +260 -0
- package/docs/apis/features/node/json-tree.md +86 -0
- package/docs/apis/features/node/launcher-app-command-listener.md +145 -0
- package/docs/apis/features/node/networking.md +281 -0
- package/docs/apis/features/node/nlp.md +133 -0
- package/docs/apis/features/node/opener.md +97 -0
- package/docs/apis/features/node/os.md +118 -0
- package/docs/apis/features/node/package-finder.md +402 -0
- package/docs/apis/features/node/postgres.md +212 -0
- package/docs/apis/features/node/proc.md +430 -0
- package/docs/apis/features/node/process-manager.md +210 -0
- package/docs/apis/features/node/python.md +278 -0
- package/docs/apis/features/node/repl.md +88 -0
- package/docs/apis/features/node/runpod.md +673 -0
- package/docs/apis/features/node/secure-shell.md +169 -0
- package/docs/apis/features/node/semantic-search.md +401 -0
- package/docs/apis/features/node/sqlite.md +211 -0
- package/docs/apis/features/node/telegram.md +254 -0
- package/docs/apis/features/node/tts.md +118 -0
- package/docs/apis/features/node/ui.md +703 -0
- package/docs/apis/features/node/vault.md +64 -0
- package/docs/apis/features/node/vm.md +84 -0
- package/docs/apis/features/node/window-manager.md +337 -0
- package/docs/apis/features/node/yaml-tree.md +85 -0
- package/docs/apis/features/node/yaml.md +176 -0
- package/docs/apis/features/web/asset-loader.md +47 -0
- package/docs/apis/features/web/container-link.md +133 -0
- package/docs/apis/features/web/esbuild.md +59 -0
- package/docs/apis/features/web/helpers.md +135 -0
- package/docs/apis/features/web/network.md +30 -0
- package/docs/apis/features/web/speech.md +55 -0
- package/docs/apis/features/web/vault.md +64 -0
- package/docs/apis/features/web/vm.md +84 -0
- package/docs/apis/features/web/voice.md +67 -0
- package/docs/apis/servers/express.md +127 -0
- package/docs/apis/servers/mcp.md +213 -0
- package/docs/apis/servers/websocket.md +99 -0
- package/docs/documentation-audit.md +134 -0
- package/docs/examples/content-db.md +77 -0
- package/docs/examples/disk-cache.md +83 -0
- package/docs/examples/docker.md +101 -0
- package/docs/examples/downloader.md +70 -0
- package/docs/examples/esbuild.md +80 -0
- package/docs/examples/file-manager.md +82 -0
- package/docs/examples/fs.md +83 -0
- package/docs/examples/git.md +85 -0
- package/docs/examples/google-auth.md +88 -0
- package/docs/examples/google-calendar.md +94 -0
- package/docs/examples/google-docs.md +82 -0
- package/docs/examples/google-drive.md +96 -0
- package/docs/examples/google-sheets.md +95 -0
- package/docs/examples/grep.md +85 -0
- package/docs/examples/ink-blocks.md +75 -0
- package/docs/examples/ink-renderer.md +41 -0
- package/docs/examples/ink.md +103 -0
- package/docs/examples/ipc-socket.md +103 -0
- package/docs/examples/json-tree.md +91 -0
- package/docs/examples/launcher-app-command-listener.md +120 -0
- package/docs/examples/networking.md +58 -0
- package/docs/examples/nlp.md +91 -0
- package/docs/examples/opener.md +78 -0
- package/docs/examples/os.md +72 -0
- package/docs/examples/package-finder.md +89 -0
- package/docs/examples/port-exposer.md +89 -0
- package/docs/examples/postgres.md +91 -0
- package/docs/examples/proc.md +81 -0
- package/docs/examples/process-manager.md +79 -0
- package/docs/examples/python.md +91 -0
- package/docs/examples/repl.md +93 -0
- package/docs/examples/runpod.md +119 -0
- package/docs/examples/secure-shell.md +92 -0
- package/docs/examples/sqlite.md +86 -0
- package/docs/examples/telegram.md +77 -0
- package/docs/examples/tts.md +86 -0
- package/docs/examples/ui.md +80 -0
- package/docs/examples/vault.md +70 -0
- package/docs/examples/vm.md +86 -0
- package/docs/examples/window-manager.md +125 -0
- package/docs/examples/yaml-tree.md +93 -0
- package/docs/examples/yaml.md +104 -0
- package/docs/ideas/class-registration-refactor-possibilities.md +197 -0
- package/docs/ideas/container-use-api.md +9 -0
- package/docs/ideas/easy-auth-for-express-servers-and-luca-serve.md +0 -0
- package/docs/ideas/feature-stacks.md +22 -0
- package/docs/ideas/luca-cli-self-sufficiency-demo.md +23 -0
- package/docs/ideas/mcp-design.md +9 -0
- package/docs/ideas/web-container-debugging-feature.md +13 -0
- package/docs/introspection-audit.md +49 -0
- package/docs/introspection.md +154 -0
- package/docs/mcp/readme.md +162 -0
- package/docs/models.ts +38 -0
- package/docs/philosophy.md +85 -0
- package/docs/principles.md +7 -0
- package/docs/prompts/audit-codebase-for-failures-to-use-the-container.md +34 -0
- package/docs/prompts/mcp-test-easy-command.md +27 -0
- package/docs/reports/assistant-bugs.md +38 -0
- package/docs/reports/attach-pattern-usage.md +18 -0
- package/docs/reports/code-audit-results.md +391 -0
- package/docs/reports/introspection-audit-tasks.md +378 -0
- package/docs/reports/luca-mcp-improvements.md +128 -0
- package/docs/scaffolds/client.md +140 -0
- package/docs/scaffolds/command.md +106 -0
- package/docs/scaffolds/endpoint.md +176 -0
- package/docs/scaffolds/feature.md +148 -0
- package/docs/scaffolds/server.md +187 -0
- package/docs/tasks/web-container-helper-discovery.md +71 -0
- package/docs/todos.md +1 -0
- package/docs/tutorials/01-getting-started.md +106 -0
- package/docs/tutorials/02-container.md +210 -0
- package/docs/tutorials/03-scripts.md +194 -0
- package/docs/tutorials/04-features-overview.md +196 -0
- package/docs/tutorials/05-state-and-events.md +171 -0
- package/docs/tutorials/06-servers.md +157 -0
- package/docs/tutorials/07-endpoints.md +198 -0
- package/docs/tutorials/08-commands.md +171 -0
- package/docs/tutorials/09-clients.md +162 -0
- package/docs/tutorials/10-creating-features.md +198 -0
- package/docs/tutorials/11-contentbase.md +191 -0
- package/docs/tutorials/12-assistants.md +215 -0
- package/docs/tutorials/13-introspection.md +147 -0
- package/docs/tutorials/14-type-system.md +174 -0
- package/docs/tutorials/15-project-patterns.md +222 -0
- package/docs/tutorials/16-google-features.md +534 -0
- package/docs/tutorials/17-tui-blocks.md +530 -0
- package/docs/tutorials/18-semantic-search.md +334 -0
- package/index.ts +1 -0
- package/luca.console.ts +9 -0
- package/main.py +6 -0
- package/package.json +154 -0
- package/pyproject.toml +7 -0
- package/scripts/animations/chrome-glitch.ts +55 -0
- package/scripts/animations/index.ts +16 -0
- package/scripts/animations/neon-pulse.ts +64 -0
- package/scripts/animations/types.ts +6 -0
- package/scripts/build-web.ts +28 -0
- package/scripts/examples/ask-luca-expert.ts +42 -0
- package/scripts/examples/assistant-questions.ts +12 -0
- package/scripts/examples/excalidraw-expert.ts +75 -0
- package/scripts/examples/expert-chat.ts +0 -0
- package/scripts/examples/file-manager.ts +14 -0
- package/scripts/examples/ideas.ts +12 -0
- package/scripts/examples/interactive-chat.ts +20 -0
- package/scripts/examples/openai-tool-calls.ts +113 -0
- package/scripts/examples/opening-a-web-browser.ts +5 -0
- package/scripts/examples/telegram-bot.ts +79 -0
- package/scripts/examples/telegram-ink-ui.ts +302 -0
- package/scripts/examples/using-assistant-with-mcp.ts +560 -0
- package/scripts/examples/using-claude-code.ts +10 -0
- package/scripts/examples/using-contentdb.ts +35 -0
- package/scripts/examples/using-conversations.ts +35 -0
- package/scripts/examples/using-disk-cache.ts +10 -0
- package/scripts/examples/using-docker-shell.ts +75 -0
- package/scripts/examples/using-elevenlabs.ts +25 -0
- package/scripts/examples/using-google-calendar.ts +57 -0
- package/scripts/examples/using-google-docs.ts +74 -0
- package/scripts/examples/using-google-drive.ts +74 -0
- package/scripts/examples/using-google-sheets.ts +89 -0
- package/scripts/examples/using-nlp.ts +55 -0
- package/scripts/examples/using-ollama.ts +10 -0
- package/scripts/examples/using-openai-codex.ts +23 -0
- package/scripts/examples/using-postgres.ts +55 -0
- package/scripts/examples/using-runpod.ts +32 -0
- package/scripts/examples/using-tts.ts +40 -0
- package/scripts/examples/vm-loading-esm-modules.ts +16 -0
- package/scripts/scaffold.ts +391 -0
- package/scripts/scratch.ts +15 -0
- package/scripts/test-command-listener.ts +123 -0
- package/scripts/test-window-manager-lifecycle.ts +86 -0
- package/scripts/test-window-manager.ts +43 -0
- package/scripts/update-introspection-data.ts +58 -0
- package/src/agi/README.md +14 -0
- package/src/agi/container.server.ts +114 -0
- package/src/agi/endpoints/ask.ts +60 -0
- package/src/agi/endpoints/conversations/[id].ts +45 -0
- package/src/agi/endpoints/conversations.ts +31 -0
- package/src/agi/endpoints/experts.ts +37 -0
- package/src/agi/features/assistant.ts +767 -0
- package/src/agi/features/assistants-manager.ts +260 -0
- package/src/agi/features/claude-code.ts +1111 -0
- package/src/agi/features/conversation-history.ts +497 -0
- package/src/agi/features/conversation.ts +799 -0
- package/src/agi/features/openai-codex.ts +631 -0
- package/src/agi/features/openapi.ts +438 -0
- package/src/agi/features/skills-library.ts +425 -0
- package/src/agi/index.ts +6 -0
- package/src/agi/lib/token-counter.ts +122 -0
- package/src/browser.ts +25 -0
- package/src/bus.ts +100 -0
- package/src/cli/cli.ts +70 -0
- package/src/client.ts +461 -0
- package/src/clients/civitai/index.ts +541 -0
- package/src/clients/client-template.ts +41 -0
- package/src/clients/comfyui/index.ts +597 -0
- package/src/clients/elevenlabs/index.ts +291 -0
- package/src/clients/openai/index.ts +451 -0
- package/src/clients/supabase/index.ts +366 -0
- package/src/command.ts +164 -0
- package/src/commands/chat.ts +182 -0
- package/src/commands/console.ts +192 -0
- package/src/commands/describe.ts +433 -0
- package/src/commands/eval.ts +116 -0
- package/src/commands/help.ts +214 -0
- package/src/commands/index.ts +14 -0
- package/src/commands/mcp.ts +64 -0
- package/src/commands/prompt.ts +807 -0
- package/src/commands/run.ts +257 -0
- package/src/commands/sandbox-mcp.ts +439 -0
- package/src/commands/scaffold.ts +79 -0
- package/src/commands/serve.ts +172 -0
- package/src/container.ts +781 -0
- package/src/endpoint.ts +340 -0
- package/src/feature.ts +75 -0
- package/src/hash-object.ts +97 -0
- package/src/helper.ts +543 -0
- package/src/introspection/generated.agi.ts +23388 -0
- package/src/introspection/generated.node.ts +18899 -0
- package/src/introspection/generated.web.ts +2021 -0
- package/src/introspection/index.ts +256 -0
- package/src/introspection/scan.ts +912 -0
- package/src/node/container.ts +354 -0
- package/src/node/feature.ts +13 -0
- package/src/node/features/container-link.ts +558 -0
- package/src/node/features/content-db.ts +475 -0
- package/src/node/features/disk-cache.ts +382 -0
- package/src/node/features/dns.ts +655 -0
- package/src/node/features/docker.ts +912 -0
- package/src/node/features/downloader.ts +92 -0
- package/src/node/features/esbuild.ts +68 -0
- package/src/node/features/file-manager.ts +357 -0
- package/src/node/features/fs.ts +534 -0
- package/src/node/features/git.ts +492 -0
- package/src/node/features/google-auth.ts +502 -0
- package/src/node/features/google-calendar.ts +300 -0
- package/src/node/features/google-docs.ts +404 -0
- package/src/node/features/google-drive.ts +339 -0
- package/src/node/features/google-sheets.ts +279 -0
- package/src/node/features/grep.ts +406 -0
- package/src/node/features/helpers.ts +374 -0
- package/src/node/features/ink.ts +490 -0
- package/src/node/features/ipc-socket.ts +459 -0
- package/src/node/features/json-tree.ts +188 -0
- package/src/node/features/launcher-app-command-listener.ts +388 -0
- package/src/node/features/networking.ts +925 -0
- package/src/node/features/nlp.ts +211 -0
- package/src/node/features/opener.ts +166 -0
- package/src/node/features/os.ts +157 -0
- package/src/node/features/package-finder.ts +539 -0
- package/src/node/features/port-exposer.ts +342 -0
- package/src/node/features/postgres.ts +273 -0
- package/src/node/features/proc.ts +502 -0
- package/src/node/features/process-manager.ts +542 -0
- package/src/node/features/python.ts +444 -0
- package/src/node/features/repl.ts +194 -0
- package/src/node/features/runpod.ts +802 -0
- package/src/node/features/secure-shell.ts +248 -0
- package/src/node/features/semantic-search.ts +924 -0
- package/src/node/features/sqlite.ts +289 -0
- package/src/node/features/telegram.ts +342 -0
- package/src/node/features/tts.ts +184 -0
- package/src/node/features/ui.ts +857 -0
- package/src/node/features/vault.ts +164 -0
- package/src/node/features/vm.ts +312 -0
- package/src/node/features/window-manager.ts +804 -0
- package/src/node/features/yaml-tree.ts +149 -0
- package/src/node/features/yaml.ts +132 -0
- package/src/node.ts +70 -0
- package/src/react/index.ts +175 -0
- package/src/registry.ts +199 -0
- package/src/scaffolds/generated.ts +1613 -0
- package/src/scaffolds/template.ts +37 -0
- package/src/schemas/base.ts +255 -0
- package/src/server.ts +135 -0
- package/src/servers/express.ts +209 -0
- package/src/servers/mcp.ts +805 -0
- package/src/servers/socket.ts +120 -0
- package/src/state.ts +101 -0
- package/src/web/clients/socket.ts +82 -0
- package/src/web/container.ts +74 -0
- package/src/web/extension.ts +30 -0
- package/src/web/feature.ts +12 -0
- package/src/web/features/asset-loader.ts +64 -0
- package/src/web/features/container-link.ts +385 -0
- package/src/web/features/esbuild.ts +79 -0
- package/src/web/features/helpers.ts +267 -0
- package/src/web/features/network.ts +61 -0
- package/src/web/features/speech.ts +87 -0
- package/src/web/features/vault.ts +189 -0
- package/src/web/features/vm.ts +78 -0
- package/src/web/features/voice-recognition.ts +129 -0
- package/src/web/shims/isomorphic-vm.ts +149 -0
- package/test/bus.test.ts +134 -0
- package/test/clients-servers.test.ts +216 -0
- package/test/container-link.test.ts +274 -0
- package/test/features.test.ts +160 -0
- package/test/integration.test.ts +787 -0
- package/test/node-container.test.ts +121 -0
- package/test/rate-limit.test.ts +272 -0
- package/test/semantic-search.test.ts +550 -0
- package/test/state.test.ts +121 -0
- package/test-integration/assistant.test.ts +138 -0
- package/test-integration/assistants-manager.test.ts +123 -0
- package/test-integration/claude-code.test.ts +98 -0
- package/test-integration/conversation-history.test.ts +205 -0
- package/test-integration/conversation.test.ts +137 -0
- package/test-integration/elevenlabs.test.ts +55 -0
- package/test-integration/google-services.test.ts +80 -0
- package/test-integration/helpers.ts +89 -0
- package/test-integration/openai-codex.test.ts +93 -0
- package/test-integration/runpod.test.ts +58 -0
- package/test-integration/server-endpoints.test.ts +97 -0
- package/test-integration/skills-library.test.ts +157 -0
- package/test-integration/telegram.test.ts +46 -0
- package/tsconfig.json +58 -0
- package/uv.lock +8 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { features, Feature } from '../feature.js'
|
|
2
|
+
import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
|
|
3
|
+
|
|
4
|
+
/** Shell-escape a string using single quotes (safe for all characters) */
|
|
5
|
+
function shellQuote(s: string): string {
|
|
6
|
+
return "'" + s.replace(/'/g, "'\\''") + "'"
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type GrepMatch = {
|
|
10
|
+
file: string
|
|
11
|
+
line: number
|
|
12
|
+
column?: number
|
|
13
|
+
content: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type GrepOptions = {
|
|
17
|
+
/** Pattern to search for (string or regex) */
|
|
18
|
+
pattern: string
|
|
19
|
+
/** Directory or file to search in (defaults to container cwd) */
|
|
20
|
+
path?: string
|
|
21
|
+
/** Glob patterns to include (e.g. '*.ts') */
|
|
22
|
+
include?: string | string[]
|
|
23
|
+
/** Glob patterns to exclude (e.g. 'node_modules') */
|
|
24
|
+
exclude?: string | string[]
|
|
25
|
+
/** Case insensitive search */
|
|
26
|
+
ignoreCase?: boolean
|
|
27
|
+
/** Treat pattern as a fixed string, not regex */
|
|
28
|
+
fixedStrings?: boolean
|
|
29
|
+
/** Search recursively (default: true) */
|
|
30
|
+
recursive?: boolean
|
|
31
|
+
/** Include hidden files */
|
|
32
|
+
hidden?: boolean
|
|
33
|
+
/** Max number of results to return */
|
|
34
|
+
maxResults?: number
|
|
35
|
+
/** Number of context lines before match */
|
|
36
|
+
before?: number
|
|
37
|
+
/** Number of context lines after match */
|
|
38
|
+
after?: number
|
|
39
|
+
/** Only return filenames, not match details */
|
|
40
|
+
filesOnly?: boolean
|
|
41
|
+
/** Invert match (return lines that don't match) */
|
|
42
|
+
invert?: boolean
|
|
43
|
+
/** Match whole words only */
|
|
44
|
+
wordMatch?: boolean
|
|
45
|
+
/** Additional raw flags to pass to grep/ripgrep */
|
|
46
|
+
rawFlags?: string[]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The Grep feature provides utilities for searching file contents using ripgrep (rg) or grep.
|
|
51
|
+
*
|
|
52
|
+
* Returns structured results as arrays of `{ file, line, column, content }` objects
|
|
53
|
+
* with paths relative to the container cwd. Also provides convenience methods for
|
|
54
|
+
* common search patterns.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* const grep = container.feature('grep')
|
|
59
|
+
*
|
|
60
|
+
* // Basic search
|
|
61
|
+
* const results = await grep.search({ pattern: 'TODO' })
|
|
62
|
+
* // [{ file: 'src/index.ts', line: 42, column: 5, content: '// TODO: fix this' }, ...]
|
|
63
|
+
*
|
|
64
|
+
* // Find all imports of a module
|
|
65
|
+
* const imports = await grep.imports('lodash')
|
|
66
|
+
*
|
|
67
|
+
* // Find function/class/variable definitions
|
|
68
|
+
* const defs = await grep.definitions('MyClass')
|
|
69
|
+
*
|
|
70
|
+
* // Just get filenames containing a pattern
|
|
71
|
+
* const files = await grep.filesContaining('API_KEY')
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* @extends Feature
|
|
75
|
+
*/
|
|
76
|
+
export class Grep extends Feature {
|
|
77
|
+
static override shortcut = 'features.grep' as const
|
|
78
|
+
static override stateSchema = FeatureStateSchema
|
|
79
|
+
static override optionsSchema = FeatureOptionsSchema
|
|
80
|
+
|
|
81
|
+
private _hasRipgrep: boolean | null = null
|
|
82
|
+
|
|
83
|
+
/** Whether ripgrep (rg) is available on this system */
|
|
84
|
+
get hasRipgrep(): boolean {
|
|
85
|
+
if (this._hasRipgrep !== null) return this._hasRipgrep
|
|
86
|
+
try {
|
|
87
|
+
this.container.feature('proc').exec('which rg')
|
|
88
|
+
this._hasRipgrep = true
|
|
89
|
+
} catch {
|
|
90
|
+
this._hasRipgrep = false
|
|
91
|
+
}
|
|
92
|
+
return this._hasRipgrep
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Search for a pattern in files and return structured results.
|
|
97
|
+
*
|
|
98
|
+
* @param {GrepOptions} options - Search options
|
|
99
|
+
* @returns {Promise<GrepMatch[]>} Array of match objects with relative file paths
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* // Search for a pattern in TypeScript files
|
|
104
|
+
* const results = await grep.search({
|
|
105
|
+
* pattern: 'useState',
|
|
106
|
+
* include: '*.tsx',
|
|
107
|
+
* exclude: 'node_modules'
|
|
108
|
+
* })
|
|
109
|
+
*
|
|
110
|
+
* // Case insensitive search with context
|
|
111
|
+
* const results = await grep.search({
|
|
112
|
+
* pattern: 'error',
|
|
113
|
+
* ignoreCase: true,
|
|
114
|
+
* before: 2,
|
|
115
|
+
* after: 2
|
|
116
|
+
* })
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
async search(options: GrepOptions): Promise<GrepMatch[]> {
|
|
120
|
+
const cmd = this.buildCommand(options)
|
|
121
|
+
const proc = this.container.feature('proc')
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const output = proc.exec(cmd, {
|
|
125
|
+
cwd: this.container.cwd,
|
|
126
|
+
maxBuffer: 1024 * 1024 * 50,
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
if (!output.length) return []
|
|
130
|
+
|
|
131
|
+
return this.parseResults(output, options)
|
|
132
|
+
} catch {
|
|
133
|
+
// grep returns exit code 1 when no matches found
|
|
134
|
+
return []
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Find files containing a pattern. Returns just the relative file paths.
|
|
140
|
+
*
|
|
141
|
+
* @param {string} pattern - The pattern to search for
|
|
142
|
+
* @param {Omit<GrepOptions, 'pattern' | 'filesOnly'>} [options] - Additional search options
|
|
143
|
+
* @returns {Promise<string[]>} Array of relative file paths
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```typescript
|
|
147
|
+
* const files = await grep.filesContaining('TODO')
|
|
148
|
+
* // ['src/index.ts', 'src/utils.ts']
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
async filesContaining(pattern: string, options: Omit<GrepOptions, 'pattern' | 'filesOnly'> = {}): Promise<string[]> {
|
|
152
|
+
const results = await this.search({ ...options, pattern, filesOnly: true })
|
|
153
|
+
return results.map(r => r.file)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Find import/require statements for a module or path.
|
|
158
|
+
*
|
|
159
|
+
* @param {string} moduleOrPath - The module name or path to search for in imports
|
|
160
|
+
* @param {Omit<GrepOptions, 'pattern'>} [options] - Additional search options
|
|
161
|
+
* @returns {Promise<GrepMatch[]>} Array of matches
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```typescript
|
|
165
|
+
* const lodashImports = await grep.imports('lodash')
|
|
166
|
+
* const localImports = await grep.imports('./utils')
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
async imports(moduleOrPath: string, options: Omit<GrepOptions, 'pattern'> = {}): Promise<GrepMatch[]> {
|
|
170
|
+
const escaped = moduleOrPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
171
|
+
const pattern = `(import|require).*['"\`]${escaped}[/'"\`]?`
|
|
172
|
+
return this.search({ ...options, pattern })
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Find function, class, type, or variable definitions matching a name.
|
|
177
|
+
*
|
|
178
|
+
* @param {string} name - The identifier name to search for definitions of
|
|
179
|
+
* @param {Omit<GrepOptions, 'pattern'>} [options] - Additional search options
|
|
180
|
+
* @returns {Promise<GrepMatch[]>} Array of matches
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```typescript
|
|
184
|
+
* const defs = await grep.definitions('MyComponent')
|
|
185
|
+
* const classDefs = await grep.definitions('UserService')
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
async definitions(name: string, options: Omit<GrepOptions, 'pattern'> = {}): Promise<GrepMatch[]> {
|
|
189
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
190
|
+
const pattern = `(function|class|const|let|var|type|interface|enum|export)\\s+(async\\s+)?${escaped}\\b`
|
|
191
|
+
return this.search({ ...options, pattern })
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Find TODO, FIXME, HACK, and XXX comments.
|
|
196
|
+
*
|
|
197
|
+
* @param {Omit<GrepOptions, 'pattern'>} [options] - Additional search options
|
|
198
|
+
* @returns {Promise<GrepMatch[]>} Array of matches
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```typescript
|
|
202
|
+
* const todos = await grep.todos()
|
|
203
|
+
* const fixmes = await grep.todos({ include: '*.ts' })
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
async todos(options: Omit<GrepOptions, 'pattern'> = {}): Promise<GrepMatch[]> {
|
|
207
|
+
return this.search({ ...options, pattern: '(TODO|FIXME|HACK|XXX)\\b' })
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Count the number of matches for a pattern.
|
|
212
|
+
*
|
|
213
|
+
* @param {string} pattern - The pattern to count
|
|
214
|
+
* @param {Omit<GrepOptions, 'pattern'>} [options] - Additional search options
|
|
215
|
+
* @returns {Promise<number>} Total number of matching lines
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* ```typescript
|
|
219
|
+
* const count = await grep.count('console.log')
|
|
220
|
+
* console.log(`Found ${count} console.log statements`)
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
async count(pattern: string, options: Omit<GrepOptions, 'pattern'> = {}): Promise<number> {
|
|
224
|
+
const results = await this.search({ ...options, pattern })
|
|
225
|
+
return results.length
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Search and replace across files. Returns the list of files that would be affected.
|
|
230
|
+
* Does NOT modify files — use the returned file list to do the replacement yourself.
|
|
231
|
+
*
|
|
232
|
+
* @param {string} pattern - The pattern to search for
|
|
233
|
+
* @param {Omit<GrepOptions, 'pattern'>} [options] - Additional search options
|
|
234
|
+
* @returns {Promise<{ file: string, matches: GrepMatch[] }[]>} Array of files with their matches, grouped by file
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* ```typescript
|
|
238
|
+
* const affected = await grep.findForReplace('oldFunctionName')
|
|
239
|
+
* // [{ file: 'src/a.ts', matches: [...] }, { file: 'src/b.ts', matches: [...] }]
|
|
240
|
+
* ```
|
|
241
|
+
*/
|
|
242
|
+
async findForReplace(pattern: string, options: Omit<GrepOptions, 'pattern'> = {}): Promise<{ file: string, matches: GrepMatch[] }[]> {
|
|
243
|
+
const results = await this.search({ ...options, pattern })
|
|
244
|
+
const grouped = new Map<string, GrepMatch[]>()
|
|
245
|
+
|
|
246
|
+
for (const match of results) {
|
|
247
|
+
if (!grouped.has(match.file)) grouped.set(match.file, [])
|
|
248
|
+
grouped.get(match.file)!.push(match)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return Array.from(grouped.entries()).map(([file, matches]) => ({ file, matches }))
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Build the grep/rg command from options */
|
|
255
|
+
private buildCommand(options: GrepOptions): string {
|
|
256
|
+
const {
|
|
257
|
+
pattern,
|
|
258
|
+
path,
|
|
259
|
+
include,
|
|
260
|
+
exclude,
|
|
261
|
+
ignoreCase = false,
|
|
262
|
+
fixedStrings = false,
|
|
263
|
+
recursive = true,
|
|
264
|
+
hidden = false,
|
|
265
|
+
maxResults,
|
|
266
|
+
before,
|
|
267
|
+
after,
|
|
268
|
+
filesOnly = false,
|
|
269
|
+
invert = false,
|
|
270
|
+
wordMatch = false,
|
|
271
|
+
rawFlags = [],
|
|
272
|
+
} = options
|
|
273
|
+
|
|
274
|
+
const useRg = this.hasRipgrep
|
|
275
|
+
const flags: string[] = []
|
|
276
|
+
|
|
277
|
+
if (useRg) {
|
|
278
|
+
// ripgrep mode
|
|
279
|
+
flags.push('--no-heading', '--line-number', '--column')
|
|
280
|
+
|
|
281
|
+
if (filesOnly) flags.push('--files-with-matches')
|
|
282
|
+
if (ignoreCase) flags.push('--ignore-case')
|
|
283
|
+
if (fixedStrings) flags.push('--fixed-strings')
|
|
284
|
+
if (hidden) flags.push('--hidden')
|
|
285
|
+
if (invert) flags.push('--invert-match')
|
|
286
|
+
if (wordMatch) flags.push('--word-regexp')
|
|
287
|
+
if (maxResults) flags.push(`--max-count=${maxResults}`)
|
|
288
|
+
if (before) flags.push(`--before-context=${before}`)
|
|
289
|
+
if (after) flags.push(`--after-context=${after}`)
|
|
290
|
+
|
|
291
|
+
const includes = Array.isArray(include) ? include : (include ? [include] : [])
|
|
292
|
+
for (const g of includes) {
|
|
293
|
+
flags.push(`--glob=${shellQuote(g)}`)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const excludes = Array.isArray(exclude) ? exclude : (exclude ? [exclude] : [])
|
|
297
|
+
for (const g of excludes) {
|
|
298
|
+
flags.push(`--glob=${shellQuote('!' + g)}`)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
flags.push(...rawFlags)
|
|
302
|
+
|
|
303
|
+
const searchPath = path || '.'
|
|
304
|
+
return `rg ${flags.join(' ')} -e ${shellQuote(pattern)} ${shellQuote(searchPath)}`
|
|
305
|
+
} else {
|
|
306
|
+
// fallback to grep — use -E for extended regex (supports ?, +, |, (), {})
|
|
307
|
+
flags.push('-r', '-n', '-E')
|
|
308
|
+
|
|
309
|
+
if (filesOnly) flags.push('-l')
|
|
310
|
+
if (ignoreCase) flags.push('-i')
|
|
311
|
+
if (fixedStrings) flags.push('-F')
|
|
312
|
+
if (invert) flags.push('-v')
|
|
313
|
+
if (wordMatch) flags.push('-w')
|
|
314
|
+
if (maxResults) flags.push(`-m ${maxResults}`)
|
|
315
|
+
if (before) flags.push(`-B ${before}`)
|
|
316
|
+
if (after) flags.push(`-A ${after}`)
|
|
317
|
+
|
|
318
|
+
const includes = Array.isArray(include) ? include : (include ? [include] : [])
|
|
319
|
+
for (const g of includes) {
|
|
320
|
+
flags.push(`--include=${shellQuote(g)}`)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const excludes = Array.isArray(exclude) ? exclude : (exclude ? [exclude] : [])
|
|
324
|
+
for (const g of excludes) {
|
|
325
|
+
flags.push(`--exclude-dir=${shellQuote(g)}`)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (!recursive) {
|
|
329
|
+
const idx = flags.indexOf('-r')
|
|
330
|
+
if (idx !== -1) flags.splice(idx, 1)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
flags.push(...rawFlags)
|
|
334
|
+
|
|
335
|
+
const searchPath = path || '.'
|
|
336
|
+
return `grep ${flags.join(' ')} -e ${shellQuote(pattern)} ${shellQuote(searchPath)}`
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** Parse raw grep/rg output into structured results */
|
|
341
|
+
private parseResults(output: string, options: GrepOptions): GrepMatch[] {
|
|
342
|
+
const cwd = this.container.cwd
|
|
343
|
+
const lines = output.split('\n').filter(l => l.length > 0)
|
|
344
|
+
const results: GrepMatch[] = []
|
|
345
|
+
|
|
346
|
+
if (options.filesOnly) {
|
|
347
|
+
for (const line of lines) {
|
|
348
|
+
const filePath = line.trim()
|
|
349
|
+
if (!filePath) continue
|
|
350
|
+
results.push({
|
|
351
|
+
file: this.relativize(filePath, cwd),
|
|
352
|
+
line: 0,
|
|
353
|
+
content: '',
|
|
354
|
+
})
|
|
355
|
+
}
|
|
356
|
+
return results
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Context lines (from -B/-A) are separated by -- and use - instead of :
|
|
360
|
+
// Skip separator lines
|
|
361
|
+
const useRg = this.hasRipgrep
|
|
362
|
+
|
|
363
|
+
for (const line of lines) {
|
|
364
|
+
if (line === '--') continue
|
|
365
|
+
|
|
366
|
+
// rg format: file:line:column:content
|
|
367
|
+
// grep format: file:line:content
|
|
368
|
+
let match: GrepMatch | null = null
|
|
369
|
+
|
|
370
|
+
if (useRg) {
|
|
371
|
+
const m = line.match(/^(.+?):(\d+):(\d+):(.*)$/)
|
|
372
|
+
if (m) {
|
|
373
|
+
match = {
|
|
374
|
+
file: this.relativize(m[1]!, cwd),
|
|
375
|
+
line: parseInt(m[2]!, 10),
|
|
376
|
+
column: parseInt(m[3]!, 10),
|
|
377
|
+
content: m[4]!,
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
} else {
|
|
381
|
+
const m = line.match(/^(.+?):(\d+):(.*)$/)
|
|
382
|
+
if (m) {
|
|
383
|
+
match = {
|
|
384
|
+
file: this.relativize(m[1]!, cwd),
|
|
385
|
+
line: parseInt(m[2]!, 10),
|
|
386
|
+
content: m[3]!,
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (match) results.push(match)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return results
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** Make a path relative to cwd */
|
|
398
|
+
private relativize(filePath: string, cwd: string): string {
|
|
399
|
+
// If already relative (starts with ./), just clean it
|
|
400
|
+
if (filePath.startsWith('./')) return filePath.slice(2)
|
|
401
|
+
if (filePath.startsWith('/')) return this.container.paths.relative(cwd, filePath)
|
|
402
|
+
return filePath
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export default features.register('grep', Grep)
|