@howaboua/opencode-chat 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.
Files changed (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +116 -0
  3. package/dist/config.d.ts +13 -0
  4. package/dist/config.d.ts.map +1 -0
  5. package/dist/config.js +65 -0
  6. package/dist/index.d.ts +8 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +44 -0
  9. package/dist/script/download-model.d.ts +2 -0
  10. package/dist/script/download-model.d.ts.map +1 -0
  11. package/dist/script/download-model.js +39 -0
  12. package/dist/script/semantic-index.d.ts +2 -0
  13. package/dist/script/semantic-index.d.ts.map +1 -0
  14. package/dist/script/semantic-index.js +63 -0
  15. package/dist/semantic/chunker.d.ts +8 -0
  16. package/dist/semantic/chunker.d.ts.map +1 -0
  17. package/dist/semantic/chunker.js +163 -0
  18. package/dist/semantic/embedder.d.ts +12 -0
  19. package/dist/semantic/embedder.d.ts.map +1 -0
  20. package/dist/semantic/embedder.js +54 -0
  21. package/dist/semantic/index.d.ts +41 -0
  22. package/dist/semantic/index.d.ts.map +1 -0
  23. package/dist/semantic/index.js +178 -0
  24. package/dist/system.d.ts +5 -0
  25. package/dist/system.d.ts.map +1 -0
  26. package/dist/system.js +93 -0
  27. package/dist/tools/bash.d.ts +22 -0
  28. package/dist/tools/bash.d.ts.map +1 -0
  29. package/dist/tools/bash.js +59 -0
  30. package/dist/tools/batch.d.ts +25 -0
  31. package/dist/tools/batch.d.ts.map +1 -0
  32. package/dist/tools/batch.js +49 -0
  33. package/dist/tools/edit.d.ts +25 -0
  34. package/dist/tools/edit.d.ts.map +1 -0
  35. package/dist/tools/edit.js +44 -0
  36. package/dist/tools/glob.d.ts +19 -0
  37. package/dist/tools/glob.d.ts.map +1 -0
  38. package/dist/tools/glob.js +54 -0
  39. package/dist/tools/grep.d.ts +22 -0
  40. package/dist/tools/grep.d.ts.map +1 -0
  41. package/dist/tools/grep.js +92 -0
  42. package/dist/tools/index.d.ts +12 -0
  43. package/dist/tools/index.d.ts.map +1 -0
  44. package/dist/tools/index.js +51 -0
  45. package/dist/tools/patch.d.ts +16 -0
  46. package/dist/tools/patch.d.ts.map +1 -0
  47. package/dist/tools/patch.js +87 -0
  48. package/dist/tools/read.d.ts +22 -0
  49. package/dist/tools/read.d.ts.map +1 -0
  50. package/dist/tools/read.js +70 -0
  51. package/dist/tools/remember.d.ts +16 -0
  52. package/dist/tools/remember.d.ts.map +1 -0
  53. package/dist/tools/remember.js +58 -0
  54. package/dist/tools/semantic-search.d.ts +19 -0
  55. package/dist/tools/semantic-search.d.ts.map +1 -0
  56. package/dist/tools/semantic-search.js +54 -0
  57. package/dist/tools/skill.d.ts +17 -0
  58. package/dist/tools/skill.d.ts.map +1 -0
  59. package/dist/tools/skill.js +106 -0
  60. package/dist/tools/todo.d.ts +62 -0
  61. package/dist/tools/todo.d.ts.map +1 -0
  62. package/dist/tools/todo.js +62 -0
  63. package/dist/tools/write.d.ts +19 -0
  64. package/dist/tools/write.d.ts.map +1 -0
  65. package/dist/tools/write.js +37 -0
  66. package/dist/util/constants.d.ts +16 -0
  67. package/dist/util/constants.d.ts.map +1 -0
  68. package/dist/util/constants.js +39 -0
  69. package/dist/util/patch.d.ts +32 -0
  70. package/dist/util/patch.d.ts.map +1 -0
  71. package/dist/util/patch.js +240 -0
  72. package/dist/util/paths.d.ts +6 -0
  73. package/dist/util/paths.d.ts.map +1 -0
  74. package/dist/util/paths.js +76 -0
  75. package/dist/util/text.d.ts +4 -0
  76. package/dist/util/text.d.ts.map +1 -0
  77. package/dist/util/text.js +37 -0
  78. package/dist/util/todo.d.ts +5 -0
  79. package/dist/util/todo.d.ts.map +1 -0
  80. package/dist/util/todo.js +48 -0
  81. package/dist/util/types.d.ts +22 -0
  82. package/dist/util/types.d.ts.map +1 -0
  83. package/dist/util/types.js +1 -0
  84. package/package.json +53 -0
@@ -0,0 +1,41 @@
1
+ export { ensureModel } from "./embedder";
2
+ type IndexProgress = {
3
+ total: number;
4
+ processed: number;
5
+ indexed: number;
6
+ skipped: number;
7
+ chunks: number;
8
+ currentPath?: string;
9
+ };
10
+ type IndexOptions = {
11
+ mode?: "changed" | "full";
12
+ onProgress?: (progress: IndexProgress) => void;
13
+ maxTargets?: number;
14
+ maxBytes?: number;
15
+ };
16
+ export declare function ensureSemanticIndex(worktree: string, options?: IndexOptions): Promise<{
17
+ total: number;
18
+ processed: number;
19
+ indexed: number;
20
+ skipped: number;
21
+ chunks: number;
22
+ mode: "changed" | "full";
23
+ skippedReason?: undefined;
24
+ } | {
25
+ total: number;
26
+ processed: number;
27
+ indexed: number;
28
+ skipped: number;
29
+ chunks: number;
30
+ mode: "changed" | "full";
31
+ skippedReason: string;
32
+ }>;
33
+ export declare function semanticSearch(worktree: string, query: string, limit: number): Promise<{
34
+ score: number;
35
+ path: string;
36
+ start_line: number;
37
+ end_line: number;
38
+ content: string;
39
+ embedding: Uint8Array;
40
+ }[]>;
41
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../semantic/index.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAoCxC,KAAK,aAAa,GAAG;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,KAAK,YAAY,GAAG;IAClB,IAAI,CAAC,EAAE,SAAS,GAAG,MAAM,CAAA;IACzB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,IAAI,CAAA;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAoDD,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB;;;;;;;;;;;;;;;;GAwGrF;AAED,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;;UAQzE,MAAM;gBACA,MAAM;cACR,MAAM;aACP,MAAM;eACJ,UAAU;KAQxB"}
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Semantic indexing and search orchestration.
3
+ * Coordinates chunking, embedding, and SQLite storage for vector search.
4
+ */
5
+ import * as path from "path";
6
+ import * as fs from "fs/promises";
7
+ import { Database } from "bun:sqlite";
8
+ import { chunkFile } from "./chunker";
9
+ import { getEmbedder, encodeEmbedding, decodeEmbedding, cosineSimilarity, getModelDir } from "./embedder";
10
+ export { ensureModel } from "./embedder";
11
+ const DB_FILENAME = "semantic.sqlite";
12
+ const MAX_FILE_BYTES = 1024 * 1024;
13
+ const MAX_CHUNKS_PER_FILE = 200;
14
+ const EMBED_BATCH_SIZE = 16;
15
+ const TEXT_EXTENSIONS = new Set([
16
+ ".md",
17
+ ".mdx",
18
+ ".txt",
19
+ ".ts",
20
+ ".tsx",
21
+ ".js",
22
+ ".jsx",
23
+ ".json",
24
+ ".yml",
25
+ ".yaml",
26
+ ".toml",
27
+ ".py",
28
+ ".go",
29
+ ".rs",
30
+ ".java",
31
+ ".c",
32
+ ".cpp",
33
+ ".h",
34
+ ".hpp",
35
+ ".css",
36
+ ".html",
37
+ ".sh",
38
+ ".bash",
39
+ ".zsh",
40
+ ]);
41
+ const SKIP_DIRS = [".git", "node_modules", ".opencode", "dist", "build", "out", ".next", ".turbo", "coverage"];
42
+ function getDb(dbPath) {
43
+ const db = new Database(dbPath);
44
+ db.run("PRAGMA journal_mode = WAL");
45
+ db.run("PRAGMA synchronous = NORMAL");
46
+ db.run("CREATE TABLE IF NOT EXISTS files (path TEXT PRIMARY KEY, mtime INTEGER)");
47
+ db.run("CREATE TABLE IF NOT EXISTS chunks (id INTEGER PRIMARY KEY, path TEXT, start_line INTEGER, end_line INTEGER, content TEXT, embedding BLOB)");
48
+ return db;
49
+ }
50
+ function isSkippedPath(filePath) {
51
+ const normalized = filePath.replace(/\\/g, "/");
52
+ return SKIP_DIRS.some((dir) => normalized.includes(`/${dir}/`));
53
+ }
54
+ function isTextFile(filePath) {
55
+ const ext = path.extname(filePath).toLowerCase();
56
+ return ext ? TEXT_EXTENSIONS.has(ext) : true;
57
+ }
58
+ async function collectTargets(worktree, mode, filesQuery) {
59
+ const glob = new Bun.Glob("**/*");
60
+ const targets = [];
61
+ const skipped = [];
62
+ let totalBytes = 0;
63
+ for await (const relPath of glob.scan({ cwd: worktree, absolute: false, onlyFiles: true, followSymlinks: false })) {
64
+ if (isSkippedPath(`/${relPath}`))
65
+ continue;
66
+ const absPath = path.join(worktree, relPath);
67
+ if (!isTextFile(absPath))
68
+ continue;
69
+ const stat = await fs.stat(absPath);
70
+ if (stat.size > MAX_FILE_BYTES)
71
+ continue;
72
+ if (mode === "changed") {
73
+ const existing = filesQuery.get(absPath);
74
+ if (existing && existing.mtime === Math.floor(stat.mtimeMs)) {
75
+ skipped.push(absPath);
76
+ continue;
77
+ }
78
+ }
79
+ targets.push({ absPath, stat });
80
+ totalBytes += stat.size;
81
+ }
82
+ return { targets, skipped, totalBytes };
83
+ }
84
+ export async function ensureSemanticIndex(worktree, options = {}) {
85
+ const dbDir = path.join(worktree, ".opencode", "chat");
86
+ await fs.mkdir(dbDir, { recursive: true });
87
+ const db = getDb(path.join(dbDir, DB_FILENAME));
88
+ const mode = options.mode ?? "changed";
89
+ const filesQuery = db.prepare("SELECT mtime FROM files WHERE path = ?");
90
+ const upsertFile = db.prepare("INSERT OR REPLACE INTO files (path, mtime) VALUES (?, ?)");
91
+ const deleteChunks = db.prepare("DELETE FROM chunks WHERE path = ?");
92
+ const insertChunk = db.prepare("INSERT INTO chunks (path, start_line, end_line, content, embedding) VALUES (?, ?, ?, ?, ?)");
93
+ if (mode === "full") {
94
+ db.run("DELETE FROM chunks");
95
+ db.run("DELETE FROM files");
96
+ }
97
+ const { targets, skipped, totalBytes } = await collectTargets(worktree, mode, filesQuery);
98
+ let processed = 0;
99
+ let indexed = 0;
100
+ let chunksTotal = 0;
101
+ const report = (currentPath) => {
102
+ options.onProgress?.({
103
+ total: targets.length,
104
+ processed,
105
+ indexed,
106
+ skipped: skipped.length,
107
+ chunks: chunksTotal,
108
+ currentPath,
109
+ });
110
+ };
111
+ if (targets.length === 0) {
112
+ report();
113
+ return { total: 0, processed: 0, indexed: 0, skipped: skipped.length, chunks: 0, mode };
114
+ }
115
+ if (typeof options.maxTargets === "number" && targets.length > options.maxTargets) {
116
+ report("skip:too-many-files");
117
+ return {
118
+ total: targets.length,
119
+ processed: 0,
120
+ indexed: 0,
121
+ skipped: skipped.length,
122
+ chunks: 0,
123
+ mode,
124
+ skippedReason: "too-many-files",
125
+ };
126
+ }
127
+ if (typeof options.maxBytes === "number" && totalBytes > options.maxBytes) {
128
+ report("skip:too-large");
129
+ return {
130
+ total: targets.length,
131
+ processed: 0,
132
+ indexed: 0,
133
+ skipped: skipped.length,
134
+ chunks: 0,
135
+ mode,
136
+ skippedReason: "too-large",
137
+ };
138
+ }
139
+ const model = await getEmbedder(getModelDir(dbDir));
140
+ report();
141
+ for (const target of targets) {
142
+ report(target.absPath);
143
+ const text = await Bun.file(target.absPath).text();
144
+ processed += 1;
145
+ if (!text.trim() || text.includes("\u0000"))
146
+ continue;
147
+ const chunks = chunkFile(target.absPath, text);
148
+ if (chunks.length === 0 || chunks.length > MAX_CHUNKS_PER_FILE)
149
+ continue;
150
+ deleteChunks.run(target.absPath);
151
+ const embeddings = [];
152
+ for await (const batch of model.passageEmbed(chunks.map((c) => c.content), EMBED_BATCH_SIZE)) {
153
+ embeddings.push(...batch);
154
+ }
155
+ db.run("BEGIN");
156
+ for (let i = 0; i < chunks.length; i++) {
157
+ const chunk = chunks[i];
158
+ insertChunk.run(chunk.path, chunk.startLine, chunk.endLine, chunk.content, encodeEmbedding(embeddings[i]));
159
+ }
160
+ upsertFile.run(target.absPath, Math.floor(target.stat.mtimeMs));
161
+ db.run("COMMIT");
162
+ indexed += 1;
163
+ chunksTotal += chunks.length;
164
+ report(target.absPath);
165
+ }
166
+ return { total: targets.length, processed, indexed, skipped: skipped.length, chunks: chunksTotal, mode };
167
+ }
168
+ export async function semanticSearch(worktree, query, limit) {
169
+ const dbDir = path.join(worktree, ".opencode", "chat");
170
+ const db = getDb(path.join(dbDir, DB_FILENAME));
171
+ const model = await getEmbedder(getModelDir(dbDir));
172
+ const queryVec = new Float32Array(await model.queryEmbed(query));
173
+ const rows = db.query("SELECT path, start_line, end_line, content, embedding FROM chunks").all();
174
+ const scored = rows
175
+ .map((row) => ({ ...row, score: cosineSimilarity(queryVec, decodeEmbedding(row.embedding)) }))
176
+ .sort((a, b) => b.score - a.score);
177
+ return scored.slice(0, limit);
178
+ }
@@ -0,0 +1,5 @@
1
+ export declare const CHATIFIER_MARKER = "---CHATIFIER_MODE_ACTIVE---";
2
+ export declare const UNIVERSAL_PROMPT = "You are Opencode, an interactive CLI-style assistant that helps users solve problems, explore ideas, and complete tasks efficiently. Respond conversationally, guide users step-by-step when needed, and use available tools to support their goals\u2014whether technical, analytical, or creative.\n\n# Communication Style\n- Be concise, direct, and to the point\n- Minimize output tokens while maintaining helpfulness\n- Answer directly without unnecessary preamble or postamble\n- Use clear, straightforward language\n- Avoid phrases like \"Here is...\" or \"The answer is...\"\n- Stop after working on a file unless explanation is requested\n- Focus on being helpful and accurate\n\n<example>\nuser: 2 + 2\nassistant: 4\n</example>\n\n<example>\nuser: what is 2+2?\nassistant: 4\n</example>\n\n<example>\nuser: is 11 a prime number?\nassistant: Yes\n</example>\n\n# CLI Interface\nYour output displays on a command line interface using GitHub-flavored markdown in a monospace font. All text outside tool use communicates with the user.\n\nWhen running bash commands that make changes, briefly explain what you're doing to ensure user understanding.\n\n# Task Management\nUse TodoWrite tools frequently to track tasks and provide visibility into your progress. This is essential for planning complex tasks and ensuring nothing is missed.\n\nMark tasks as completed immediately after finishing them. Don't batch completions.\n\nExample workflow:\n1. Create todo list for complex tasks\n2. Mark items as in_progress when starting\n3. Mark as completed immediately after finishing\n4. Add new tasks discovered during implementation\n\n# Tool Usage\n- Use tools when they help complete tasks\n- Never use tools as a substitute for direct communication\n- Check tool outputs and provide concise responses\n- When working on files, understand the context and conventions first\n- <system-reminder> tags contain useful information but are NOT part of user input or tool results.\n\n## Parallel Tool Execution (CRITICAL)\n**You MUST use parallel tool calling whenever possible:**\n\n- **ALWAYS** batch multiple independent tool calls in a single message\n- **NEVER** send separate messages for operations that can be done in parallel\n- **ESPECIALLY** for multiple file reads, bash commands, or API calls\n- **PRIORITY**: Parallel execution over sequential for all independent operations\n\n**When to use parallel calls:**\n- Reading multiple files at once\n- Running multiple independent bash commands\n- Checking status of multiple resources\n- Any operations that don't depend on each other\n\n**Example:**\nInstead of: `Read file A` \u2192 `Read file B` \u2192 `Read file C`\nUse: `Read file A + Read file B + Read file C` in single message \n\n# Safety & Security\n- Never introduce security vulnerabilities\n- Don't log or expose secrets/keys\n- Consider security implications before implementing features\n- Exercise extra caution with system operations that affect OS stability\n- Pay attention to commands that modify system files, services, or configurations\n- Be mindful of operations that could impact system performance or availability";
3
+ export declare const CHATIFIER_PROMPT: string;
4
+ export declare function replaceSystemPrompt(system: string[]): void;
5
+ //# sourceMappingURL=system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system.d.ts","sourceRoot":"","sources":["../system.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,gCAAgC,CAAA;AAC7D,eAAO,MAAM,gBAAgB,ukGAyEmD,CAAA;AAGhF,eAAO,MAAM,gBAAgB,QAA+C,CAAA;AAE5E,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,QAgBnD"}
package/dist/system.js ADDED
@@ -0,0 +1,93 @@
1
+ export const CHATIFIER_MARKER = "---CHATIFIER_MODE_ACTIVE---";
2
+ export const UNIVERSAL_PROMPT = `You are Opencode, an interactive CLI-style assistant that helps users solve problems, explore ideas, and complete tasks efficiently. Respond conversationally, guide users step-by-step when needed, and use available tools to support their goals—whether technical, analytical, or creative.
3
+
4
+ # Communication Style
5
+ - Be concise, direct, and to the point
6
+ - Minimize output tokens while maintaining helpfulness
7
+ - Answer directly without unnecessary preamble or postamble
8
+ - Use clear, straightforward language
9
+ - Avoid phrases like "Here is..." or "The answer is..."
10
+ - Stop after working on a file unless explanation is requested
11
+ - Focus on being helpful and accurate
12
+
13
+ <example>
14
+ user: 2 + 2
15
+ assistant: 4
16
+ </example>
17
+
18
+ <example>
19
+ user: what is 2+2?
20
+ assistant: 4
21
+ </example>
22
+
23
+ <example>
24
+ user: is 11 a prime number?
25
+ assistant: Yes
26
+ </example>
27
+
28
+ # CLI Interface
29
+ Your output displays on a command line interface using GitHub-flavored markdown in a monospace font. All text outside tool use communicates with the user.
30
+
31
+ When running bash commands that make changes, briefly explain what you're doing to ensure user understanding.
32
+
33
+ # Task Management
34
+ Use TodoWrite tools frequently to track tasks and provide visibility into your progress. This is essential for planning complex tasks and ensuring nothing is missed.
35
+
36
+ Mark tasks as completed immediately after finishing them. Don't batch completions.
37
+
38
+ Example workflow:
39
+ 1. Create todo list for complex tasks
40
+ 2. Mark items as in_progress when starting
41
+ 3. Mark as completed immediately after finishing
42
+ 4. Add new tasks discovered during implementation
43
+
44
+ # Tool Usage
45
+ - Use tools when they help complete tasks
46
+ - Never use tools as a substitute for direct communication
47
+ - Check tool outputs and provide concise responses
48
+ - When working on files, understand the context and conventions first
49
+ - <system-reminder> tags contain useful information but are NOT part of user input or tool results.
50
+
51
+ ## Parallel Tool Execution (CRITICAL)
52
+ **You MUST use parallel tool calling whenever possible:**
53
+
54
+ - **ALWAYS** batch multiple independent tool calls in a single message
55
+ - **NEVER** send separate messages for operations that can be done in parallel
56
+ - **ESPECIALLY** for multiple file reads, bash commands, or API calls
57
+ - **PRIORITY**: Parallel execution over sequential for all independent operations
58
+
59
+ **When to use parallel calls:**
60
+ - Reading multiple files at once
61
+ - Running multiple independent bash commands
62
+ - Checking status of multiple resources
63
+ - Any operations that don't depend on each other
64
+
65
+ **Example:**
66
+ Instead of: \`Read file A\` → \`Read file B\` → \`Read file C\`
67
+ Use: \`Read file A + Read file B + Read file C\` in single message
68
+
69
+ # Safety & Security
70
+ - Never introduce security vulnerabilities
71
+ - Don't log or expose secrets/keys
72
+ - Consider security implications before implementing features
73
+ - Exercise extra caution with system operations that affect OS stability
74
+ - Pay attention to commands that modify system files, services, or configurations
75
+ - Be mindful of operations that could impact system performance or availability`;
76
+ // Append marker to ensure detection works
77
+ export const CHATIFIER_PROMPT = UNIVERSAL_PROMPT + "\n\n" + CHATIFIER_MARKER;
78
+ export function replaceSystemPrompt(system) {
79
+ // console.log("[Chatifier] System prompt parts:", system.length)
80
+ // system.forEach((s, i) => console.log(`[Chatifier] Part ${i} preview:`, s.slice(0, 50)))
81
+ // Check if our marker is present in any part of the system prompt
82
+ const hasMarker = system.some((part) => part.includes(CHATIFIER_MARKER));
83
+ if (hasMarker) {
84
+ // console.log("[Chatifier] Marker found! Replacing system prompt.")
85
+ // Clear the ENTIRE system prompt (including the marker and any headers)
86
+ system.length = 0;
87
+ // Inject our universal prompt
88
+ system.push(UNIVERSAL_PROMPT);
89
+ }
90
+ else {
91
+ // console.log("[Chatifier] Marker NOT found. Skipping replacement.")
92
+ }
93
+ }
@@ -0,0 +1,22 @@
1
+ export declare function createChatBash(baseDir: string): {
2
+ id: string;
3
+ run: (args: {
4
+ command: string;
5
+ timeout?: number;
6
+ description: string;
7
+ }) => Promise<string>;
8
+ tool: {
9
+ description: string;
10
+ args: {
11
+ command: import("zod").ZodString;
12
+ timeout: import("zod").ZodOptional<import("zod").ZodNumber>;
13
+ description: import("zod").ZodString;
14
+ };
15
+ execute(args: {
16
+ command: string;
17
+ description: string;
18
+ timeout?: number | undefined;
19
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
20
+ };
21
+ };
22
+ //# sourceMappingURL=bash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../tools/bash.ts"],"names":[],"mappings":"AAQA,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM;;gBACnB;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE;;;;;;;;;;;;;;EAuDpF"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * chat_bash tool implementation.
3
+ * Runs shell commands with basic timeout and output truncation.
4
+ * Keeps output size bounded for predictable responses.
5
+ */
6
+ import { tool } from "@opencode-ai/plugin";
7
+ import { MAX_OUTPUT_LENGTH } from "../util/constants";
8
+ export function createChatBash(baseDir) {
9
+ const run = async (args) => {
10
+ if (!args.command) {
11
+ throw new Error("command is required");
12
+ }
13
+ const timeoutMs = args.timeout ?? 2 * 60 * 1000;
14
+ if (timeoutMs < 0) {
15
+ throw new Error(`Invalid timeout value: ${timeoutMs}. Timeout must be a positive number.`);
16
+ }
17
+ const cmd = process.platform === "win32" ? ["cmd", "/c", args.command] : ["bash", "-lc", args.command];
18
+ const proc = Bun.spawn(cmd, {
19
+ cwd: baseDir,
20
+ stdout: "pipe",
21
+ stderr: "pipe",
22
+ });
23
+ const outputPromise = Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text()]).then(([out, err]) => out + err);
24
+ const timeoutPromise = new Promise((resolve) => {
25
+ const timer = setTimeout(() => {
26
+ proc.kill();
27
+ clearTimeout(timer);
28
+ resolve(`\n\n<bash_metadata>\nCommand timed out after ${timeoutMs} ms\n</bash_metadata>`);
29
+ }, timeoutMs);
30
+ });
31
+ const output = await Promise.race([outputPromise, timeoutPromise]);
32
+ if (output.length <= MAX_OUTPUT_LENGTH)
33
+ return output;
34
+ return output.slice(0, MAX_OUTPUT_LENGTH) + "\n\n<bash_metadata>\nOutput truncated\n</bash_metadata>";
35
+ };
36
+ return {
37
+ id: "chat_bash",
38
+ run,
39
+ tool: tool({
40
+ description: `Run shell commands.
41
+
42
+ Usage:
43
+ - Study unfamiliar commands with -h or --help first
44
+ - Verify directories exist before creating files
45
+ - Quote paths with spaces: "path with spaces/file.txt"
46
+ - Output limited to 30,000 characters
47
+ - Use && to chain dependent commands, ; for independent ones
48
+ - Prefer absolute paths over cd when possible`,
49
+ args: {
50
+ command: tool.schema.string().describe("The command to execute"),
51
+ timeout: tool.schema.number().optional().describe("Optional timeout in milliseconds"),
52
+ description: tool.schema.string().describe("Brief description of command purpose (5-10 words)"),
53
+ },
54
+ async execute(args) {
55
+ return await run(args);
56
+ },
57
+ }),
58
+ };
59
+ }
@@ -0,0 +1,25 @@
1
+ import { ToolCall } from "../util/types";
2
+ type Runner = (params: Record<string, unknown>) => Promise<string>;
3
+ export declare function createChatBatch(runners: Record<string, Runner>, todoRead: () => Promise<string>): {
4
+ id: string;
5
+ run: (args: {
6
+ tool_calls: ToolCall[];
7
+ }) => Promise<string>;
8
+ tool: {
9
+ description: string;
10
+ args: {
11
+ tool_calls: import("zod").ZodArray<import("zod").ZodObject<{
12
+ tool: import("zod").ZodString;
13
+ parameters: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodUnknown>;
14
+ }, import("zod/v4/core").$strip>>;
15
+ };
16
+ execute(args: {
17
+ tool_calls: {
18
+ tool: string;
19
+ parameters: Record<string, unknown>;
20
+ }[];
21
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
22
+ };
23
+ };
24
+ export {};
25
+ //# sourceMappingURL=batch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../../tools/batch.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAExC,KAAK,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;AAElE,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC;;gBAoB1E;QAAE,UAAU,EAAE,QAAQ,EAAE,CAAA;KAAE;;;;;;;;;;;;;;;;EA2B/C"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * chat_batch tool implementation.
3
+ * Executes multiple chat_* tools with simple coordination.
4
+ * Limits tool calls to keep execution predictable.
5
+ */
6
+ import { tool } from "@opencode-ai/plugin";
7
+ export function createChatBatch(runners, todoRead) {
8
+ const allRunners = {
9
+ ...runners,
10
+ chat_todoread: async () => todoRead(),
11
+ };
12
+ const run = async (args) => {
13
+ const calls = args.tool_calls.slice(0, 10);
14
+ const results = await Promise.all(calls.map((call) => {
15
+ const runner = allRunners[call.tool];
16
+ if (!runner)
17
+ return Promise.resolve(`Unsupported tool: ${call.tool}`);
18
+ return runner(call.parameters);
19
+ }));
20
+ return results.join("\n\n");
21
+ };
22
+ return {
23
+ id: "chat_batch",
24
+ run: async (args) => run(args),
25
+ tool: tool({
26
+ description: `Run multiple tools at once.
27
+
28
+ Usage:
29
+ - 1-10 tool calls per batch
30
+ - All calls run in parallel
31
+ - Use for independent operations only
32
+ - Don't use when results depend on each other`,
33
+ args: {
34
+ tool_calls: tool.schema
35
+ .array(tool.schema.object({
36
+ tool: tool.schema.string().describe("Name of the tool to call"),
37
+ parameters: tool.schema
38
+ .record(tool.schema.string(), tool.schema.unknown())
39
+ .describe("Parameters for the tool"),
40
+ }))
41
+ .min(1)
42
+ .describe("Array of tool calls to execute"),
43
+ },
44
+ async execute(args) {
45
+ return await run({ tool_calls: args.tool_calls });
46
+ },
47
+ }),
48
+ };
49
+ }
@@ -0,0 +1,25 @@
1
+ export declare function createChatEdit(baseDir: string, repoRoot: string): {
2
+ id: string;
3
+ run: (args: {
4
+ filePath: string;
5
+ oldString: string;
6
+ newString: string;
7
+ replaceAll?: boolean;
8
+ }) => Promise<string>;
9
+ tool: {
10
+ description: string;
11
+ args: {
12
+ filePath: import("zod").ZodString;
13
+ oldString: import("zod").ZodString;
14
+ newString: import("zod").ZodString;
15
+ replaceAll: import("zod").ZodOptional<import("zod").ZodBoolean>;
16
+ };
17
+ execute(args: {
18
+ filePath: string;
19
+ oldString: string;
20
+ newString: string;
21
+ replaceAll?: boolean | undefined;
22
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
23
+ };
24
+ };
25
+ //# sourceMappingURL=edit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edit.d.ts","sourceRoot":"","sources":["../../tools/edit.ts"],"names":[],"mappings":"AAWA,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;gBACrC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE;;;;;;;;;;;;;;;;EAiC1G"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * chat_edit tool implementation.
3
+ * Performs targeted string replacements in files.
4
+ * Rejects ambiguous edits to prevent unintended changes.
5
+ */
6
+ import * as fs from "fs/promises";
7
+ import path from "path";
8
+ import { tool } from "@opencode-ai/plugin";
9
+ import { resolvePath } from "../util/paths";
10
+ import { replaceOnce } from "../util/text";
11
+ export function createChatEdit(baseDir, repoRoot) {
12
+ const run = async (args) => {
13
+ const filePath = resolvePath(baseDir, args.filePath);
14
+ const content = await fs.readFile(filePath, "utf-8").catch(() => {
15
+ throw new Error(`File not found: ${filePath}`);
16
+ });
17
+ const updated = replaceOnce(content, args.oldString, args.newString, args.replaceAll);
18
+ await fs.writeFile(filePath, updated, "utf-8");
19
+ const title = path.relative(repoRoot, filePath);
20
+ return `Updated ${title}`;
21
+ };
22
+ return {
23
+ id: "chat_edit",
24
+ run,
25
+ tool: tool({
26
+ description: `Replace text in files.
27
+
28
+ Usage:
29
+ - Read the file first before editing
30
+ - Preserve original formatting and indentation
31
+ - Fails if oldString not found or matches multiple times
32
+ - Use replaceAll for global replacements`,
33
+ args: {
34
+ filePath: tool.schema.string().describe("The absolute path to the file to modify"),
35
+ oldString: tool.schema.string().describe("The text to replace"),
36
+ newString: tool.schema.string().describe("The text to replace it with (must be different from oldString)"),
37
+ replaceAll: tool.schema.boolean().optional().describe("Replace all occurrences of oldString (default false)"),
38
+ },
39
+ async execute(args) {
40
+ return await run(args);
41
+ },
42
+ }),
43
+ };
44
+ }
@@ -0,0 +1,19 @@
1
+ export declare function createChatGlob(baseDir: string): {
2
+ id: string;
3
+ run: (args: {
4
+ pattern: string;
5
+ path?: string;
6
+ }) => Promise<string>;
7
+ tool: {
8
+ description: string;
9
+ args: {
10
+ pattern: import("zod").ZodString;
11
+ path: import("zod").ZodOptional<import("zod").ZodString>;
12
+ };
13
+ execute(args: {
14
+ pattern: string;
15
+ path?: string | undefined;
16
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
17
+ };
18
+ };
19
+ //# sourceMappingURL=glob.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glob.d.ts","sourceRoot":"","sources":["../../tools/glob.ts"],"names":[],"mappings":"AAQA,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM;;gBACnB;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE;;;;;;;;;;;;EA6C5D"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * chat_glob tool implementation.
3
+ * Finds files by glob pattern with modification-time ordering.
4
+ * Mirrors core behavior with truncation hints for large result sets.
5
+ */
6
+ import { tool } from "@opencode-ai/plugin";
7
+ import { resolvePath } from "../util/paths";
8
+ export function createChatGlob(baseDir) {
9
+ const run = async (args) => {
10
+ const searchRoot = resolvePath(baseDir, args.path ?? baseDir);
11
+ const glob = new Bun.Glob(args.pattern);
12
+ const files = [];
13
+ let truncated = false;
14
+ for await (const file of glob.scan({ cwd: searchRoot, absolute: true, onlyFiles: true })) {
15
+ if (files.length >= 100) {
16
+ truncated = true;
17
+ break;
18
+ }
19
+ const mtime = await Bun.file(file)
20
+ .stat()
21
+ .then((stat) => stat.mtime.getTime())
22
+ .catch(() => 0);
23
+ files.push({ path: file, mtime });
24
+ }
25
+ files.sort((a, b) => b.mtime - a.mtime);
26
+ if (files.length === 0)
27
+ return "No files found";
28
+ const output = files.map((file) => file.path);
29
+ if (truncated) {
30
+ output.push("");
31
+ output.push("(Results are truncated. Consider using a more specific path or pattern.)");
32
+ }
33
+ return output.join("\n");
34
+ };
35
+ return {
36
+ id: "chat_glob",
37
+ run,
38
+ tool: tool({
39
+ description: `Find files by pattern.
40
+
41
+ Usage:
42
+ - Supports glob patterns like "**/*.txt" or "docs/**/*.md"
43
+ - Returns files sorted by modification time
44
+ - Results truncated at 100 files`,
45
+ args: {
46
+ pattern: tool.schema.string().describe("The glob pattern to match files against"),
47
+ path: tool.schema.string().optional().describe("The directory to search in"),
48
+ },
49
+ async execute(args) {
50
+ return await run(args);
51
+ },
52
+ }),
53
+ };
54
+ }