opencode-add-dir 1.1.0 → 1.2.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Cristian Fonseca
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,142 +1,133 @@
1
- # OpenCode Add-Dir Plugin
1
+ # opencode-add-dir
2
2
 
3
- Add external directories to your OpenCode session context with automatic permission approval.
3
+ Add working directories to your [OpenCode](https://opencode.ai) session inspired by Claude Code's [`/add-dir`](https://docs.anthropic.com/en/docs/claude-code/cli-usage#add-dir) command.
4
4
 
5
- ## Quick Start
6
-
7
- 1. Install the plugin:
5
+ When you need an agent to read, edit, or search files outside the current project, this plugin grants access without permission popups.
8
6
 
9
- ```bash
10
- cd ~/.config/opencode
11
- npm install opencode-add-dir
12
- ```
7
+ ## Quick Start
13
8
 
14
- 2. Add `"opencode-add-dir"` to your `~/.config/opencode/opencode.jsonc` plugins array:
9
+ Add to your `opencode.json`:
15
10
 
16
- ```jsonc
11
+ ```json
17
12
  {
18
- "plugin": [
19
- "your-other-plugins",
20
- "opencode-add-dir"
21
- ]
13
+ "plugin": ["opencode-add-dir"]
22
14
  }
23
15
  ```
24
16
 
25
- 3. Restart OpenCode
17
+ Restart OpenCode. Done.
26
18
 
27
- 4. Use the command:
19
+ <details>
20
+ <summary>Alternative: setup CLI</summary>
28
21
 
29
22
  ```bash
30
- /add-dir /path/to/your/project
23
+ bunx opencode-add-dir-setup
31
24
  ```
32
25
 
33
- ## Features
26
+ Automatically adds the plugin to your global `opencode.json`.
34
27
 
35
- - **Zero Configuration**: Works immediately after installation
36
- - **Auto Permission**: No permission prompts for directories you add
37
- - **Smart Filtering**: Automatically skips node_modules, .git, binary files, etc.
38
- - **Recursive Scanning**: Reads entire directory structures
39
- - **File Limits**: 100KB per file, 500 files max (to prevent overwhelm)
40
- - **Full Access**: Read and write to added directories
28
+ </details>
41
29
 
42
- ## How It Works
30
+ <details>
31
+ <summary>Alternative: local file</summary>
32
+
33
+ ```bash
34
+ # Clone and build
35
+ git clone https://github.com/kuzeofficial/add-dir-opencode.git
36
+ cd add-dir-opencode
37
+ bun install && bun run deploy
38
+ ```
43
39
 
44
- 1. `/add-dir <path>` scans the directory and adds all files to context
45
- 2. Directory path is automatically registered for future access
46
- 3. Any operations on that directory skip permission prompts
47
- 4. Works recursively - all subdirectories are auto-approved
40
+ Bundles to `~/.config/opencode/plugins/add-dir.js`.
48
41
 
49
- ## Usage Examples
42
+ </details>
50
43
 
51
- ```bash
52
- # Add a project directory
53
- /add-dir ~/projects/my-app
44
+ ## Usage
54
45
 
55
- # Add any external directory
56
- /add-dir /path/to/external/code
46
+ ### Slash Command
57
47
 
58
- # Now you can read/write without permission prompts
59
- read ~/projects/my-app/src/index.js
60
- edit ~/projects/my-app/package.json
48
+ ```
49
+ /add-dir /path/to/directory # Session only
50
+ /add-dir /path/to/directory --remember # Persist across sessions
51
+ /add-dir list # Show added directories
52
+ /add-dir remove /path/to/directory # Remove a directory
61
53
  ```
62
54
 
63
- ## What Gets Filtered
55
+ ### LLM Tools
64
56
 
65
- ### Automatically Skipped Directories
66
- - `node_modules`
67
- - `.git`
68
- - `dist`, `build`
69
- - `.next`
70
- - `__pycache__`
71
- - Virtual environments (`.venv`, `venv`, `env`)
72
- - `.env`, `.env.*`
73
- - `coverage`
74
- - `.nuxt`, `.output`
75
- - `tmp`, `temp`, `.turbo`
57
+ The agent can also call these tools directly:
76
58
 
77
- ### Binary Files (Not Read)
78
- Images, PDFs, Office docs, archives, media files, executables, compiled files, etc.
59
+ | Tool | Description |
60
+ |------|-------------|
61
+ | `add_dir` | Add a directory (with optional `remember` flag) |
62
+ | `list_dirs` | List all added directories |
63
+ | `remove_dir` | Remove a directory |
79
64
 
80
- ## Installation Details
65
+ ## How It Works
81
66
 
82
- The plugin's postinstall script automatically:
83
- 1. Finds your OpenCode config directory (`~/.config/opencode/`)
84
- 2. Creates the `/add-dir` command file
85
- 3. Installs it to `~/.config/opencode/command/add-dir.md`
67
+ The plugin uses a layered approach to handle permissions across all sessions, including subagents:
86
68
 
87
- ### Troubleshooting
69
+ | Layer | When | Scope |
70
+ |-------|------|-------|
71
+ | **Config hook** | Startup | Injects `external_directory: "allow"` rules for persisted dirs into all agents |
72
+ | **Session permission** | `/add-dir` | Sets `external_directory: true` on the current session via `tools` field |
73
+ | **tool.execute.before** | Every file tool | Detects subagent sessions accessing added dirs, grants permission before execution |
74
+ | **Event auto-approve** | Permission popup | Catches any remaining `external_directory` requests and auto-approves via SDK |
88
75
 
89
- If the `/add-dir` command doesn't work after installation:
76
+ ### AGENTS.md Injection
90
77
 
91
- 1. Verify the command file exists:
92
- ```bash
93
- ls -la ~/.config/opencode/command/add-dir.md
78
+ If an added directory contains `AGENTS.md`, `CLAUDE.md`, or `.agents/AGENTS.md`, the content is automatically injected into the system prompt via `experimental.chat.system.transform`.
79
+
80
+ ## Persistence
81
+
82
+ Directories added with `--remember` are stored in:
83
+
84
+ ```
85
+ ~/.local/share/opencode/add-dir/directories.json
94
86
  ```
95
87
 
96
- 2. If missing, create it manually:
88
+ These are loaded at startup and injected into agent permission rules via the config hook.
89
+
90
+ ## Development
91
+
97
92
  ```bash
98
- mkdir -p ~/.config/opencode/command
99
- cat > ~/.config/opencode/command/add-dir.md << 'EOF'
100
- ---
101
- description: Add an external directory to the session context
102
- ---
103
- Add the directory at path $ARGUMENTS to this session's context.
104
- Use the add_dir tool to read all files from the specified directory.
105
- EOF
93
+ bun install
94
+ bun test # 17 tests
95
+ bun run typecheck # Type check
96
+ bun run build # Build npm package
97
+ bun run deploy # Bundle to ~/.config/opencode/plugins/
106
98
  ```
107
99
 
108
- ## Development
100
+ ### Project Structure
109
101
 
110
- For contributors and maintainers:
102
+ ```
103
+ src/
104
+ ├── index.ts # Entry point (default export)
105
+ ├── plugin.ts # Hooks + tools
106
+ ├── state.ts # Persistence
107
+ ├── validate.ts # Directory validation
108
+ ├── permissions.ts # Session grants + auto-approve
109
+ └── context.ts # AGENTS.md injection
110
+ ```
111
111
 
112
- ```bash
113
- # Clone the repository
114
- git clone https://github.com/kuzeofficial/add-dir-opencode.git
115
- cd opencode-add-dir
112
+ ## Debugging
116
113
 
117
- # Install dependencies
118
- bun install
114
+ Run OpenCode with logs:
119
115
 
120
- # Build the TypeScript source
121
- bun run build
116
+ ```bash
117
+ opencode --print-logs 2>debug.log
118
+ ```
122
119
 
123
- # Test locally
124
- npm link
125
- cd ~/.config/opencode
126
- npm link opencode-add-dir
120
+ Filter plugin logs:
127
121
 
128
- # Publish new version
129
- npm version patch # or minor/major
130
- npm publish
122
+ ```bash
123
+ grep "\[add-dir\]" debug.log
131
124
  ```
132
125
 
133
- ## Files
126
+ ## Limitations
134
127
 
135
- - `src/index.ts` - Main plugin with permission auto-approval
136
- - `command/add-dir.md` - Command definition (auto-installed)
137
- - `scripts/install.js` - Post-install script (auto-runs)
138
- - `package.json` - NPM package configuration
128
+ - Directories added mid-session (without `--remember`) rely on session-level permissions and the event hook auto-approve. The first access by a subagent may briefly show a permission popup before auto-dismissing.
129
+ - The `permission.ask` plugin hook is defined in the OpenCode SDK but [not invoked](https://github.com/sst/opencode/blob/main/packages/opencode/src/permission/index.ts) in the source — this plugin works around it using `tool.execute.before` and event-based auto-approval.
139
130
 
140
131
  ## License
141
132
 
142
- MIT
133
+ [MIT](LICENSE)
package/bin/setup.mjs ADDED
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs"
3
+ import { join } from "path"
4
+ import { homedir } from "os"
5
+
6
+ const PKG = "opencode-add-dir"
7
+ const args = process.argv.slice(2)
8
+ const isRemove = args.includes("--remove")
9
+
10
+ function configDir() {
11
+ if (process.env.XDG_CONFIG_HOME) return join(process.env.XDG_CONFIG_HOME, "opencode")
12
+ if (process.platform === "darwin") return join(homedir(), ".config", "opencode")
13
+ return join(homedir(), ".config", "opencode")
14
+ }
15
+
16
+ function findConfig() {
17
+ const dir = configDir()
18
+ for (const name of ["opencode.jsonc", "opencode.json"]) {
19
+ const p = join(dir, name)
20
+ if (existsSync(p)) return p
21
+ }
22
+ return join(dir, "opencode.json")
23
+ }
24
+
25
+ function stripJsonComments(text) {
26
+ let result = ""
27
+ let inString = false
28
+ let escape = false
29
+ for (let i = 0; i < text.length; i++) {
30
+ const ch = text[i]
31
+ if (escape) { result += ch; escape = false; continue }
32
+ if (ch === "\\" && inString) { result += ch; escape = true; continue }
33
+ if (ch === '"') { inString = !inString; result += ch; continue }
34
+ if (inString) { result += ch; continue }
35
+ if (ch === "/" && text[i + 1] === "/") { while (i < text.length && text[i] !== "\n") i++; continue }
36
+ if (ch === "/" && text[i + 1] === "*") { i += 2; while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) i++; i++; continue }
37
+ result += ch
38
+ }
39
+ return result
40
+ }
41
+
42
+ function run() {
43
+ const configPath = findConfig()
44
+ const dir = configDir()
45
+
46
+ let config = {}
47
+ if (existsSync(configPath)) {
48
+ const raw = readFileSync(configPath, "utf-8")
49
+ config = JSON.parse(stripJsonComments(raw))
50
+ } else {
51
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
52
+ }
53
+
54
+ config.plugin = config.plugin || []
55
+ const has = config.plugin.some((p) => {
56
+ const name = Array.isArray(p) ? p[0] : p
57
+ return name === PKG || name.startsWith(PKG + "@")
58
+ })
59
+
60
+ if (isRemove) {
61
+ if (!has) {
62
+ console.log(`${PKG} is not in ${configPath}`)
63
+ return
64
+ }
65
+ config.plugin = config.plugin.filter((p) => {
66
+ const name = Array.isArray(p) ? p[0] : p
67
+ return name !== PKG && !name.startsWith(PKG + "@")
68
+ })
69
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n")
70
+ console.log(`Removed ${PKG} from ${configPath}`)
71
+ return
72
+ }
73
+
74
+ if (has) {
75
+ console.log(`${PKG} is already in ${configPath}`)
76
+ return
77
+ }
78
+
79
+ config.plugin.push(PKG)
80
+ if (!config.$schema) config.$schema = "https://opencode.ai/config.json"
81
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n")
82
+ console.log(`Added ${PKG} to ${configPath}`)
83
+ console.log("Restart OpenCode to activate the plugin.")
84
+ }
85
+
86
+ run()
@@ -0,0 +1,2 @@
1
+ import type { DirEntry } from "./state.js";
2
+ export declare function collectAgentContext(dirs: Map<string, DirEntry>): string[];
package/dist/index.d.ts CHANGED
@@ -1,4 +1,2 @@
1
- import type { Plugin } from '@opencode-ai/plugin';
2
- declare const addDirPlugin: Plugin;
3
- export default addDirPlugin;
4
- //# sourceMappingURL=index.d.ts.map
1
+ import { AddDirPlugin } from "./plugin.js";
2
+ export default AddDirPlugin;
package/dist/index.js CHANGED
@@ -1,129 +1,251 @@
1
- import { tool } from '@opencode-ai/plugin/tool';
2
- import fs from 'fs';
3
- import path from 'path';
4
- import { validateDirectory, countFiles } from './utils.js';
5
- const SESSIONS_FILE = path.join(__dirname, '.sessions.json');
6
- const MAX_SESSIONS = 50;
7
- const SESSION_AGE_DAYS = 30;
8
- const CLEANUP_INTERVAL_MS = 3600000;
9
- const LAST_ACCESS_UPDATE_MS = 3600000;
10
- let lastCleaned = 0;
11
- function readSessions() {
12
- try {
13
- if (fs.existsSync(SESSIONS_FILE)) {
14
- return JSON.parse(fs.readFileSync(SESSIONS_FILE, 'utf-8'));
15
- }
16
- }
17
- catch (error) {
18
- console.error('Failed to read sessions:', error);
19
- }
20
- return {};
1
+ // src/plugin.ts
2
+ import { tool } from "@opencode-ai/plugin";
3
+
4
+ // src/state.ts
5
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
6
+ import { join } from "path";
7
+ function stateDir() {
8
+ return join(process.env["XDG_DATA_HOME"] || join(process.env["HOME"] || "~", ".local", "share"), "opencode", "add-dir");
21
9
  }
22
- function writeSessions(sessions) {
23
- fs.writeFileSync(SESSIONS_FILE, JSON.stringify(sessions, null, 2));
10
+ function loadDirs() {
11
+ const dirs = new Map;
12
+ const file = join(stateDir(), "directories.json");
13
+ if (!existsSync(file))
14
+ return dirs;
15
+ try {
16
+ for (const p of JSON.parse(readFileSync(file, "utf-8")))
17
+ dirs.set(p, { path: p, persist: true });
18
+ } catch {}
19
+ return dirs;
24
20
  }
25
- function getSessionDirs(sessionId) {
26
- const sessions = readSessions();
27
- const session = sessions[sessionId];
28
- if (session) {
29
- const now = Date.now();
30
- if (now - session.lastAccessed > LAST_ACCESS_UPDATE_MS) {
31
- session.lastAccessed = now;
32
- writeSessions(sessions);
33
- }
34
- return session.dirs;
35
- }
36
- return [];
21
+ function saveDirs(dirs) {
22
+ const list = [...dirs.values()].filter((d) => d.persist).map((d) => d.path);
23
+ const dir = stateDir();
24
+ if (!existsSync(dir))
25
+ mkdirSync(dir, { recursive: true });
26
+ writeFileSync(join(dir, "directories.json"), JSON.stringify(list, null, 2));
37
27
  }
38
- function addSessionDir(sessionId, dirPath) {
39
- const sessions = readSessions();
40
- const normalized = path.resolve(dirPath);
41
- if (!sessions[sessionId]) {
42
- sessions[sessionId] = { dirs: [], lastAccessed: Date.now() };
43
- }
44
- if (!sessions[sessionId].dirs.includes(normalized)) {
45
- sessions[sessionId].dirs.push(normalized);
46
- sessions[sessionId].lastAccessed = Date.now();
47
- writeSessions(sessions);
48
- }
28
+ function isChildOf(parent, child) {
29
+ return child === parent || child.startsWith(parent + "/");
49
30
  }
50
- function cleanOldSessions() {
51
- const sessions = readSessions();
52
- const now = Date.now();
53
- const maxAge = SESSION_AGE_DAYS * 24 * 60 * 60 * 1000;
54
- const entries = Object.entries(sessions);
55
- const recentSessions = entries.filter(([, data]) => now - data.lastAccessed < maxAge);
56
- if (recentSessions.length <= MAX_SESSIONS) {
57
- if (recentSessions.length === entries.length) {
58
- return;
59
- }
60
- }
61
- else {
62
- recentSessions.sort(([, a], [, b]) => b.lastAccessed - a.lastAccessed);
63
- recentSessions.splice(MAX_SESSIONS);
64
- }
65
- const cleanedSessions = {};
66
- recentSessions.forEach(([id, data]) => {
67
- cleanedSessions[id] = data;
68
- });
69
- writeSessions(cleanedSessions);
31
+ function matchesDirs(dirs, filepath) {
32
+ for (const entry of dirs.values()) {
33
+ if (isChildOf(entry.path, filepath))
34
+ return true;
35
+ }
36
+ return false;
70
37
  }
71
- function isInSessionDirs(filePath, dirs) {
72
- const normalizedFilePath = path.resolve(filePath);
73
- for (const dir of dirs) {
74
- const normalizedDir = path.resolve(dir);
75
- const relative = path.relative(normalizedDir, normalizedFilePath);
76
- const isInside = !relative.startsWith('..') && !path.isAbsolute(relative);
77
- if (isInside) {
78
- return true;
79
- }
80
- }
38
+
39
+ // src/validate.ts
40
+ import { statSync } from "fs";
41
+ import { resolve } from "path";
42
+ function expandHome(p) {
43
+ return p.startsWith("~/") ? (process.env["HOME"] || "~") + p.slice(1) : p;
44
+ }
45
+ function validateDir(input, worktree, existing) {
46
+ const trimmed = input.trim();
47
+ if (!trimmed)
48
+ return { ok: false, reason: "No directory path provided." };
49
+ const abs = resolve(expandHome(trimmed));
50
+ try {
51
+ if (!statSync(abs).isDirectory())
52
+ return { ok: false, reason: `${abs} is not a directory.` };
53
+ } catch (e) {
54
+ if ("ENOENT ENOTDIR EACCES EPERM".includes(e.code))
55
+ return { ok: false, reason: `Path ${abs} was not found.` };
56
+ throw e;
57
+ }
58
+ if (isChildOf(worktree, abs))
59
+ return { ok: false, reason: `${abs} is already within the project directory ${worktree}.` };
60
+ for (const dir of existing)
61
+ if (isChildOf(dir, abs))
62
+ return { ok: false, reason: `${abs} is already accessible within ${dir}.` };
63
+ return { ok: true, absolutePath: abs };
64
+ }
65
+
66
+ // src/permissions.ts
67
+ import { join as join2, resolve as resolve2 } from "path";
68
+ var FILE_TOOLS = new Set(["read", "write", "edit", "apply_patch", "multiedit", "glob", "grep", "list", "bash"]);
69
+ var grantedSessions = new Set;
70
+ function expandHome2(p) {
71
+ return p.startsWith("~/") ? (process.env["HOME"] || "~") + p.slice(1) : p;
72
+ }
73
+ function extractPath(tool, args) {
74
+ if (!args)
75
+ return "";
76
+ if (tool === "bash")
77
+ return args.workdir || args.command || "";
78
+ return args.filePath || args.path || args.pattern || "";
79
+ }
80
+ function permissionGlob(dirPath) {
81
+ return join2(dirPath, "*");
82
+ }
83
+ async function grantSession(sdk, sessionID, text) {
84
+ if (grantedSessions.has(sessionID))
85
+ return;
86
+ grantedSessions.add(sessionID);
87
+ await sdk.session.prompt({
88
+ path: { id: sessionID },
89
+ body: { noReply: true, tools: { external_directory: true }, parts: [{ type: "text", text }] }
90
+ }).catch(() => {});
91
+ }
92
+ function grantSessionAsync(sdk, sessionID, text) {
93
+ setTimeout(() => {
94
+ sdk.session.promptAsync({
95
+ path: { id: sessionID },
96
+ body: { noReply: true, tools: { external_directory: true }, parts: [{ type: "text", text }] }
97
+ })?.then?.(() => grantedSessions.add(sessionID))?.catch?.(() => {});
98
+ }, 150);
99
+ }
100
+ function shouldGrantBeforeTool(dirs, tool, args) {
101
+ if (!dirs.size || !FILE_TOOLS.has(tool))
81
102
  return false;
103
+ const p = extractPath(tool, args);
104
+ return !!p && matchesDirs(dirs, resolve2(expandHome2(p)));
82
105
  }
83
- const addDirPlugin = async () => {
84
- return {
85
- tool: {
86
- add_dir: tool({
87
- description: 'Add a directory to the workspace. Access is auto-approved for files in added directories.',
88
- args: {
89
- directory: tool.schema.string().describe('Absolute path to the directory')
90
- },
91
- execute: async ({ directory }, context) => {
92
- const sessionId = context.sessionID;
93
- const resolvedPath = path.resolve(directory);
94
- validateDirectory(resolvedPath);
95
- addSessionDir(sessionId, resolvedPath);
96
- const fileCount = countFiles(resolvedPath);
97
- return JSON.stringify({
98
- directory: resolvedPath,
99
- status: 'added',
100
- message: 'Directory added to workspace',
101
- fileCount
102
- }, null, 2);
103
- }
104
- })
105
- },
106
- 'chat.message': async (context) => {
107
- getSessionDirs(context.sessionID);
108
- const now = Date.now();
109
- if (now - lastCleaned > CLEANUP_INTERVAL_MS) {
110
- cleanOldSessions();
111
- lastCleaned = now;
112
- }
106
+ async function autoApprovePermission(sdk, props, dirs) {
107
+ if (props.permission !== "external_directory")
108
+ return;
109
+ const meta = props.metadata ?? {};
110
+ const filepath = meta.filepath ?? "";
111
+ const parentDir = meta.parentDir ?? "";
112
+ const patterns = props.patterns ?? [];
113
+ const matches = matchesDirs(dirs, filepath) || matchesDirs(dirs, parentDir) || patterns.some((p) => matchesDirs(dirs, p.replace(/\/?\*$/, "")));
114
+ if (!matches || !props.id || !props.sessionID)
115
+ return;
116
+ await sdk.postSessionIdPermissionsPermissionId({
117
+ path: { id: props.sessionID, permissionID: props.id },
118
+ body: { response: "always" }
119
+ }).catch(() => {});
120
+ }
121
+
122
+ // src/context.ts
123
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
124
+ import { join as join3 } from "path";
125
+ var CONTEXT_FILES = ["AGENTS.md", "CLAUDE.md", ".agents/AGENTS.md"];
126
+ function collectAgentContext(dirs) {
127
+ const sections = [];
128
+ for (const entry of dirs.values()) {
129
+ for (const name of CONTEXT_FILES) {
130
+ const fp = join3(entry.path, name);
131
+ if (!existsSync2(fp))
132
+ continue;
133
+ try {
134
+ const content = readFileSync2(fp, "utf-8").trim();
135
+ if (content)
136
+ sections.push(`# Context from ${fp}
137
+
138
+ ${content}`);
139
+ } catch {}
140
+ }
141
+ }
142
+ return sections;
143
+ }
144
+
145
+ // src/plugin.ts
146
+ var SENTINEL = "__ADD_DIR_HANDLED__";
147
+ var AddDirPlugin = async ({ client, worktree, directory }) => {
148
+ const root = worktree || directory;
149
+ const dirs = loadDirs();
150
+ const sdk = client;
151
+ function add(dirPath, persist, sessionID) {
152
+ const result = validateDir(dirPath, root, [...dirs.values()].map((d) => d.path));
153
+ if (!result.ok)
154
+ return result.reason;
155
+ dirs.set(result.absolutePath, { path: result.absolutePath, persist });
156
+ if (persist)
157
+ saveDirs(dirs);
158
+ const label = persist ? "persistent" : "session";
159
+ const msg = `Added ${result.absolutePath} as a working directory (${label}).`;
160
+ grantSessionAsync(sdk, sessionID, msg);
161
+ return msg;
162
+ }
163
+ function remove(path) {
164
+ if (!dirs.has(path))
165
+ return `${path} is not in the directory list.`;
166
+ dirs.delete(path);
167
+ saveDirs(dirs);
168
+ return `Removed ${path} from working directories.`;
169
+ }
170
+ function list() {
171
+ if (!dirs.size)
172
+ return "No additional directories added.";
173
+ return [...dirs.values()].map((d) => `${d.path} (${d.persist ? "persistent" : "session"})`).join(`
174
+ `);
175
+ }
176
+ function handleCommand(args, sessionID) {
177
+ const tokens = args.trim().split(/\s+/);
178
+ const flags = new Set(tokens.filter((t) => t.startsWith("--")));
179
+ const pos = tokens.filter((t) => !t.startsWith("--"));
180
+ if (pos[0] === "list")
181
+ return list();
182
+ if (pos[0] === "remove" && pos[1])
183
+ return remove(pos[1]);
184
+ if (!pos[0])
185
+ return `Usage: /add-dir <path> [--remember]
186
+ /add-dir list
187
+ /add-dir remove <path>`;
188
+ return add(pos[0], flags.has("--remember"), sessionID);
189
+ }
190
+ return {
191
+ config: async (cfg) => {
192
+ cfg.command ??= {};
193
+ cfg.command["add-dir"] = { template: "/add-dir", description: "Add a working directory for this session" };
194
+ if (!dirs.size)
195
+ return;
196
+ cfg.permission ??= {};
197
+ cfg.permission.external_directory ??= {};
198
+ for (const entry of dirs.values())
199
+ cfg.permission.external_directory[permissionGlob(entry.path)] = "allow";
200
+ },
201
+ "command.execute.before": async (input) => {
202
+ if (input.command !== "add-dir")
203
+ return;
204
+ handleCommand(input.arguments || "", input.sessionID);
205
+ throw new Error(SENTINEL);
206
+ },
207
+ "tool.execute.before": async (input, output) => {
208
+ if (shouldGrantBeforeTool(dirs, input.tool, output.args))
209
+ await grantSession(sdk, input.sessionID, "Directory access granted by add-dir plugin.");
210
+ },
211
+ event: async ({ event }) => {
212
+ if (event.type === "permission.asked" && event.properties)
213
+ await autoApprovePermission(sdk, event.properties, dirs);
214
+ },
215
+ "experimental.chat.system.transform": async (_, output) => {
216
+ output.system.push(...collectAgentContext(dirs));
217
+ },
218
+ tool: {
219
+ add_dir: tool({
220
+ description: "Add an external directory as a working directory. Files in added directories can be read and edited without permission prompts.",
221
+ args: {
222
+ path: tool.schema.string().describe("Absolute or relative path to directory"),
223
+ remember: tool.schema.boolean().optional().describe("Persist across sessions")
113
224
  },
114
- 'permission.ask': async (input, output) => {
115
- const dirs = getSessionDirs(input.sessionID);
116
- const check = (value) => typeof value === 'string' && isInSessionDirs(value, dirs);
117
- const approved = [
118
- check(input.title),
119
- (input.pattern ? (Array.isArray(input.pattern) ? input.pattern : [input.pattern]) : []).some(check),
120
- Object.values(input.metadata || {}).flat().some(check)
121
- ].some(Boolean);
122
- if (approved) {
123
- output.status = 'allow';
124
- }
225
+ async execute(args, ctx) {
226
+ return add(args.path, args.remember ?? false, ctx.sessionID);
227
+ }
228
+ }),
229
+ list_dirs: tool({
230
+ description: "List all added working directories.",
231
+ args: {},
232
+ async execute() {
233
+ return list();
125
234
  }
126
- };
235
+ }),
236
+ remove_dir: tool({
237
+ description: "Remove a previously added working directory.",
238
+ args: { path: tool.schema.string().describe("Path of directory to remove") },
239
+ async execute(args) {
240
+ return remove(args.path);
241
+ }
242
+ })
243
+ }
244
+ };
245
+ };
246
+
247
+ // src/index.ts
248
+ var src_default = AddDirPlugin;
249
+ export {
250
+ src_default as default
127
251
  };
128
- export default addDirPlugin;
129
- //# sourceMappingURL=index.js.map
@@ -0,0 +1,6 @@
1
+ import type { DirEntry } from "./state.js";
2
+ export declare function permissionGlob(dirPath: string): string;
3
+ export declare function grantSession(sdk: any, sessionID: string, text: string): Promise<void>;
4
+ export declare function grantSessionAsync(sdk: any, sessionID: string, text: string): void;
5
+ export declare function shouldGrantBeforeTool(dirs: Map<string, DirEntry>, tool: string, args: any): boolean;
6
+ export declare function autoApprovePermission(sdk: any, props: any, dirs: Map<string, DirEntry>): Promise<void>;
@@ -0,0 +1,2 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ export declare const AddDirPlugin: Plugin;
@@ -0,0 +1,8 @@
1
+ export interface DirEntry {
2
+ path: string;
3
+ persist: boolean;
4
+ }
5
+ export declare function loadDirs(): Map<string, DirEntry>;
6
+ export declare function saveDirs(dirs: Map<string, DirEntry>): void;
7
+ export declare function isChildOf(parent: string, child: string): boolean;
8
+ export declare function matchesDirs(dirs: Map<string, DirEntry>, filepath: string): boolean;
@@ -0,0 +1,8 @@
1
+ export type Result = {
2
+ ok: true;
3
+ absolutePath: string;
4
+ } | {
5
+ ok: false;
6
+ reason: string;
7
+ };
8
+ export declare function validateDir(input: string, worktree: string, existing: string[]): Result;
package/package.json CHANGED
@@ -1,37 +1,58 @@
1
1
  {
2
2
  "name": "opencode-add-dir",
3
- "version": "1.1.0",
4
- "description": "OpenCode plugin to add external directories to session context",
3
+ "version": "1.2.1",
4
+ "description": "Add working directories to your OpenCode session with auto-approved permissions",
5
+ "author": "Cristian Fonseca <cfonsecacomas@gmail.com>",
5
6
  "type": "module",
6
- "main": "dist/index.js",
7
- "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ }
12
+ },
13
+ "main": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "bin": {
16
+ "opencode-add-dir-setup": "./bin/setup.mjs"
17
+ },
8
18
  "files": [
9
- "dist",
10
- "command",
11
- "scripts",
12
- "README.md"
19
+ "dist/",
20
+ "bin/",
21
+ "README.md",
22
+ "LICENSE"
13
23
  ],
14
24
  "scripts": {
15
- "build": "tsc",
16
- "prepublishOnly": "npm run build",
17
- "postinstall": "node scripts/install.js"
18
- },
19
- "dependencies": {
20
- "@opencode-ai/plugin": "^1.0.0"
21
- },
22
- "devDependencies": {
23
- "@types/node": "^20.0.0",
24
- "typescript": "^5.0.0"
25
- },
26
- "peerDependencies": {
27
- "@opencode-ai/plugin": "^1.0.0"
25
+ "build": "bun build ./src/index.ts --outdir ./dist --target node --format esm --external @opencode-ai/plugin && bun x tsc --emitDeclarationOnly",
26
+ "deploy": "bun build ./src/plugin.ts --outfile ~/.config/opencode/plugins/add-dir.js --target node --format esm --external @opencode-ai/plugin",
27
+ "test": "bun test",
28
+ "typecheck": "bun x tsc --noEmit",
29
+ "prepublishOnly": "bun run typecheck && bun test && bun run build"
28
30
  },
29
31
  "keywords": [
30
32
  "opencode",
31
- "plugin",
32
- "add-directory",
33
- "context"
33
+ "opencode-plugin",
34
+ "add-dir",
35
+ "working-directory",
36
+ "permissions"
34
37
  ],
35
- "author": "Cristian Fonseca <cfonsecacomas@gmail.com>",
36
- "license": "MIT"
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/kuzeofficial/add-dir-opencode.git"
41
+ },
42
+ "license": "MIT",
43
+ "peerDependencies": {
44
+ "@opencode-ai/plugin": ">=1.0.0"
45
+ },
46
+ "devDependencies": {
47
+ "@opencode-ai/plugin": "^1.1.14",
48
+ "@semantic-release/changelog": "^6.0.3",
49
+ "@semantic-release/commit-analyzer": "^13.0.1",
50
+ "@semantic-release/git": "^10.0.1",
51
+ "@semantic-release/github": "^12.0.6",
52
+ "@semantic-release/npm": "^13.1.5",
53
+ "@semantic-release/release-notes-generator": "^14.1.0",
54
+ "@types/bun": "latest",
55
+ "semantic-release": "^25.0.3",
56
+ "typescript": "^5.8.3"
57
+ }
37
58
  }
@@ -1,5 +0,0 @@
1
- ---
2
- description: Add an external directory to the session context
3
- ---
4
- Add the directory at path $ARGUMENTS to this session's context.
5
- Use the add_dir tool to read all files from the specified directory.
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAgHlD,QAAA,MAAM,YAAY,EAAE,MAgDnB,CAAC;AAEF,eAAe,YAAY,CAAC"}
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts DELETED
@@ -1,15 +0,0 @@
1
- export interface AddedDirectory {
2
- directory: string;
3
- addedAt: number;
4
- }
5
- export interface SessionDirectories {
6
- sessionId: string;
7
- directories: AddedDirectory[];
8
- }
9
- export interface AddDirResult {
10
- directory: string;
11
- status: 'added' | 'error';
12
- message: string;
13
- fileCount?: number;
14
- }
15
- //# sourceMappingURL=types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,cAAc,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB"}
package/dist/types.js DELETED
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=types.js.map
package/dist/types.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/dist/utils.d.ts DELETED
@@ -1,4 +0,0 @@
1
- export declare function isIgnoredDirectory(dirName: string): boolean;
2
- export declare function countFiles(directory: string): number;
3
- export declare function validateDirectory(dirPath: string): void;
4
- //# sourceMappingURL=utils.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAsBA,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CA+BpD;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAgBvD"}
package/dist/utils.js DELETED
@@ -1,67 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- const IGNORED_DIRECTORIES = new Set([
4
- 'node_modules',
5
- '.git',
6
- 'dist',
7
- 'build',
8
- '.next',
9
- '__pycache__',
10
- '.venv',
11
- 'venv',
12
- 'env',
13
- '.env',
14
- 'coverage',
15
- '.nuxt',
16
- '.output',
17
- 'tmp',
18
- 'temp',
19
- '.turbo'
20
- ]);
21
- export function isIgnoredDirectory(dirName) {
22
- return IGNORED_DIRECTORIES.has(dirName);
23
- }
24
- export function countFiles(directory) {
25
- let fileCount = 0;
26
- function scanDir(dir) {
27
- try {
28
- const entries = fs.readdirSync(dir, { withFileTypes: true });
29
- for (const entry of entries) {
30
- if (isIgnoredDirectory(entry.name)) {
31
- continue;
32
- }
33
- if (entry.isSymbolicLink()) {
34
- continue;
35
- }
36
- const fullPath = path.join(dir, entry.name);
37
- if (entry.isDirectory()) {
38
- scanDir(fullPath);
39
- }
40
- else if (entry.isFile()) {
41
- fileCount++;
42
- }
43
- }
44
- }
45
- catch (error) {
46
- console.error(`Error scanning ${dir}:`, error);
47
- }
48
- }
49
- scanDir(directory);
50
- return fileCount;
51
- }
52
- export function validateDirectory(dirPath) {
53
- if (!fs.existsSync(dirPath)) {
54
- throw new Error(`Directory does not exist: ${dirPath}`);
55
- }
56
- const stats = fs.statSync(dirPath);
57
- if (!stats.isDirectory()) {
58
- throw new Error(`Path is not a directory: ${dirPath}`);
59
- }
60
- try {
61
- fs.accessSync(dirPath, fs.constants.R_OK);
62
- }
63
- catch (error) {
64
- throw new Error(`Permission denied: Cannot read directory ${dirPath}`);
65
- }
66
- }
67
- //# sourceMappingURL=utils.js.map
package/dist/utils.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC;IAClC,cAAc;IACd,MAAM;IACN,MAAM;IACN,OAAO;IACP,OAAO;IACP,aAAa;IACb,OAAO;IACP,MAAM;IACN,KAAK;IACL,MAAM;IACN,UAAU;IACV,OAAO;IACP,SAAS;IACT,KAAK;IACL,MAAM;IACN,QAAQ;CACT,CAAC,CAAC;AAEH,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,OAAO,mBAAmB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,SAAiB;IAC1C,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,SAAS,OAAO,CAAC,GAAW;QAC1B,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;YAE7D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAC5B,IAAI,kBAAkB,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;oBACnC,SAAS;gBACX,CAAC;gBAED,IAAI,KAAK,CAAC,cAAc,EAAE,EAAE,CAAC;oBAC3B,SAAS;gBACX,CAAC;gBAED,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;gBAE5C,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;oBACxB,OAAO,CAAC,QAAQ,CAAC,CAAC;gBACpB,CAAC;qBAAM,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;oBAC1B,SAAS,EAAE,CAAC;gBACd,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,kBAAkB,GAAG,GAAG,EAAE,KAAK,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;IAED,OAAO,CAAC,SAAS,CAAC,CAAC;IACnB,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC/C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,6BAA6B,OAAO,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,KAAK,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAEnC,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,4BAA4B,OAAO,EAAE,CAAC,CAAC;IACzD,CAAC;IAED,IAAI,CAAC;QACH,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAC5C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,4CAA4C,OAAO,EAAE,CAAC,CAAC;IACzE,CAAC;AACH,CAAC"}
@@ -1,58 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { fileURLToPath } from 'url';
4
- import os from 'os';
5
-
6
- const __filename = fileURLToPath(import.meta.url);
7
- const __dirname = path.dirname(__filename);
8
-
9
- const COMMAND_SOURCE = path.join(__dirname, '..', 'command', 'add-dir.md');
10
-
11
- function findOpencodeConfigDir(startDir = process.cwd()) {
12
- const configFiles = ['opencode.jsonc', 'opencode.json'];
13
- let currentDir = startDir;
14
-
15
- while (currentDir !== path.parse(currentDir).root) {
16
- for (const configFile of configFiles) {
17
- const configPath = path.join(currentDir, configFile);
18
- if (fs.existsSync(configPath)) {
19
- return currentDir;
20
- }
21
- }
22
- currentDir = path.dirname(currentDir);
23
- }
24
-
25
- const defaultConfigDir = path.join(os.homedir(), '.config', 'opencode');
26
- if (fs.existsSync(defaultConfigDir)) {
27
- return defaultConfigDir;
28
- }
29
-
30
- return null;
31
- }
32
-
33
- try {
34
- const opencodeDir = findOpencodeConfigDir();
35
-
36
- if (!opencodeDir) {
37
- console.log('ℹ opencode-add-dir: Could not find opencode config directory, skipping command file installation');
38
- process.exit(0);
39
- }
40
-
41
- const commandDir = path.join(opencodeDir, 'command');
42
- const commandDest = path.join(commandDir, 'add-dir.md');
43
-
44
- if (!fs.existsSync(commandDir)) {
45
- fs.mkdirSync(commandDir, { recursive: true });
46
- }
47
-
48
- if (fs.existsSync(COMMAND_SOURCE)) {
49
- fs.copyFileSync(COMMAND_SOURCE, commandDest);
50
- console.log('✓ opencode-add-dir: Command file installed to', commandDest);
51
- } else {
52
- console.error('✗ opencode-add-dir: Command source file not found at', COMMAND_SOURCE);
53
- process.exit(1);
54
- }
55
- } catch (error) {
56
- console.error('✗ opencode-add-dir: Failed to install command file:', error.message);
57
- process.exit(1);
58
- }