opencode-add-dir 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +86 -95
- package/bin/setup.mjs +86 -0
- package/dist/context.d.ts +2 -0
- package/dist/index.d.ts +2 -4
- package/dist/index.js +234 -123
- package/dist/permissions.d.ts +8 -0
- package/dist/plugin.d.ts +2 -0
- package/dist/state.d.ts +9 -0
- package/dist/types.d.ts +25 -12
- package/dist/validate.d.ts +8 -0
- package/package.json +47 -26
- package/command/add-dir.md +0 -5
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/utils.d.ts +0 -4
- package/dist/utils.d.ts.map +0 -1
- package/dist/utils.js +0 -67
- package/dist/utils.js.map +0 -1
- package/scripts/install.js +0 -58
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
|
-
#
|
|
1
|
+
# opencode-add-dir
|
|
2
2
|
|
|
3
|
-
Add
|
|
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
|
-
|
|
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
|
-
|
|
10
|
-
cd ~/.config/opencode
|
|
11
|
-
npm install opencode-add-dir
|
|
12
|
-
```
|
|
7
|
+
## Quick Start
|
|
13
8
|
|
|
14
|
-
|
|
9
|
+
Add to your `opencode.json`:
|
|
15
10
|
|
|
16
|
-
```
|
|
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
|
-
|
|
17
|
+
Restart OpenCode. Done.
|
|
26
18
|
|
|
27
|
-
|
|
19
|
+
<details>
|
|
20
|
+
<summary>Alternative: setup CLI</summary>
|
|
28
21
|
|
|
29
22
|
```bash
|
|
30
|
-
|
|
23
|
+
bunx opencode-add-dir-setup
|
|
31
24
|
```
|
|
32
25
|
|
|
33
|
-
|
|
26
|
+
Automatically adds the plugin to your global `opencode.json`.
|
|
34
27
|
|
|
35
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
42
|
+
</details>
|
|
50
43
|
|
|
51
|
-
|
|
52
|
-
# Add a project directory
|
|
53
|
-
/add-dir ~/projects/my-app
|
|
44
|
+
## Usage
|
|
54
45
|
|
|
55
|
-
|
|
56
|
-
/add-dir /path/to/external/code
|
|
46
|
+
### Slash Command
|
|
57
47
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
55
|
+
### LLM Tools
|
|
64
56
|
|
|
65
|
-
|
|
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
|
-
|
|
78
|
-
|
|
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
|
-
##
|
|
65
|
+
## How It Works
|
|
81
66
|
|
|
82
|
-
The plugin
|
|
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
|
-
|
|
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
|
-
|
|
76
|
+
### AGENTS.md Injection
|
|
90
77
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
88
|
+
These are loaded at startup and injected into agent permission rules via the config hook.
|
|
89
|
+
|
|
90
|
+
## Development
|
|
91
|
+
|
|
97
92
|
```bash
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
100
|
+
### Project Structure
|
|
109
101
|
|
|
110
|
-
|
|
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
|
-
|
|
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
|
-
|
|
118
|
-
bun install
|
|
114
|
+
Run OpenCode with logs:
|
|
119
115
|
|
|
120
|
-
|
|
121
|
-
|
|
116
|
+
```bash
|
|
117
|
+
opencode --print-logs 2>debug.log
|
|
118
|
+
```
|
|
122
119
|
|
|
123
|
-
|
|
124
|
-
npm link
|
|
125
|
-
cd ~/.config/opencode
|
|
126
|
-
npm link opencode-add-dir
|
|
120
|
+
Filter plugin logs:
|
|
127
121
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
npm publish
|
|
122
|
+
```bash
|
|
123
|
+
grep "\[add-dir\]" debug.log
|
|
131
124
|
```
|
|
132
125
|
|
|
133
|
-
##
|
|
126
|
+
## Limitations
|
|
134
127
|
|
|
135
|
-
- `
|
|
136
|
-
- `
|
|
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()
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,129 +1,240 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
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 {};
|
|
21
|
-
}
|
|
22
|
-
function writeSessions(sessions) {
|
|
23
|
-
fs.writeFileSync(SESSIONS_FILE, JSON.stringify(sessions, null, 2));
|
|
24
|
-
}
|
|
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 [];
|
|
1
|
+
// src/state.ts
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
function stateDir() {
|
|
5
|
+
return join(process.env["XDG_DATA_HOME"] || join(process.env["HOME"] || "~", ".local", "share"), "opencode", "add-dir");
|
|
37
6
|
}
|
|
38
|
-
function
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
7
|
+
function expandHome(p) {
|
|
8
|
+
return p.startsWith("~/") ? (process.env["HOME"] || "~") + p.slice(1) : p;
|
|
9
|
+
}
|
|
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;
|
|
20
|
+
}
|
|
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));
|
|
27
|
+
}
|
|
28
|
+
function isChildOf(parent, child) {
|
|
29
|
+
return child === parent || child.startsWith(parent + "/");
|
|
30
|
+
}
|
|
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;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/validate.ts
|
|
40
|
+
import { statSync } from "fs";
|
|
41
|
+
import { resolve } from "path";
|
|
42
|
+
function validateDir(input, worktree, existing) {
|
|
43
|
+
const trimmed = input.trim();
|
|
44
|
+
if (!trimmed)
|
|
45
|
+
return { ok: false, reason: "No directory path provided." };
|
|
46
|
+
const abs = resolve(expandHome(trimmed));
|
|
47
|
+
try {
|
|
48
|
+
if (!statSync(abs).isDirectory())
|
|
49
|
+
return { ok: false, reason: `${abs} is not a directory.` };
|
|
50
|
+
} catch (e) {
|
|
51
|
+
const code = e.code;
|
|
52
|
+
if (code && ["ENOENT", "ENOTDIR", "EACCES", "EPERM"].includes(code))
|
|
53
|
+
return { ok: false, reason: `Path ${abs} was not found.` };
|
|
54
|
+
throw e;
|
|
55
|
+
}
|
|
56
|
+
if (isChildOf(worktree, abs))
|
|
57
|
+
return { ok: false, reason: `${abs} is already within the project directory ${worktree}.` };
|
|
58
|
+
for (const dir of existing)
|
|
59
|
+
if (isChildOf(dir, abs))
|
|
60
|
+
return { ok: false, reason: `${abs} is already accessible within ${dir}.` };
|
|
61
|
+
return { ok: true, absolutePath: abs };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/permissions.ts
|
|
65
|
+
import { join as join2, resolve as resolve2 } from "path";
|
|
66
|
+
var FILE_TOOLS = new Set(["read", "write", "edit", "apply_patch", "multiedit", "glob", "grep", "list", "bash"]);
|
|
67
|
+
var grantedSessions = new Set;
|
|
68
|
+
function permissionGlob(dirPath) {
|
|
69
|
+
return join2(dirPath, "*");
|
|
70
|
+
}
|
|
71
|
+
function sendPrompt(sdk, sessionID, text, tools) {
|
|
72
|
+
const body = { noReply: true, ...tools && { tools }, parts: [{ type: "text", text }] };
|
|
73
|
+
return sdk.session.promptAsync({ path: { id: sessionID }, body })?.then?.(() => {})?.catch?.(() => {}) ?? Promise.resolve();
|
|
49
74
|
}
|
|
50
|
-
function
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
75
|
+
function notify(sdk, sessionID, text) {
|
|
76
|
+
sendPrompt(sdk, sessionID, text);
|
|
77
|
+
}
|
|
78
|
+
function grantSessionAsync(sdk, sessionID, text) {
|
|
79
|
+
grantedSessions.add(sessionID);
|
|
80
|
+
sendPrompt(sdk, sessionID, text, { external_directory: true });
|
|
81
|
+
}
|
|
82
|
+
async function grantSession(sdk, sessionID, text) {
|
|
83
|
+
if (grantedSessions.has(sessionID))
|
|
84
|
+
return;
|
|
85
|
+
grantedSessions.add(sessionID);
|
|
86
|
+
const body = { noReply: true, tools: { external_directory: true }, parts: [{ type: "text", text }] };
|
|
87
|
+
await sdk.session.prompt({ path: { id: sessionID }, body }).catch(() => {});
|
|
88
|
+
}
|
|
89
|
+
function shouldGrantBeforeTool(dirs, tool, args) {
|
|
90
|
+
if (!dirs.size || !FILE_TOOLS.has(tool))
|
|
91
|
+
return false;
|
|
92
|
+
const p = extractPath(tool, args);
|
|
93
|
+
return !!p && matchesDirs(dirs, resolve2(expandHome(p)));
|
|
94
|
+
}
|
|
95
|
+
async function autoApprovePermission(sdk, props, dirs) {
|
|
96
|
+
if (props.permission !== "external_directory")
|
|
97
|
+
return;
|
|
98
|
+
const meta = props.metadata;
|
|
99
|
+
const filepath = meta.filepath ?? "";
|
|
100
|
+
const parentDir = meta.parentDir ?? "";
|
|
101
|
+
const patterns = props.patterns ?? [];
|
|
102
|
+
const matches = matchesDirs(dirs, filepath) || matchesDirs(dirs, parentDir) || patterns.some((p) => matchesDirs(dirs, p.replace(/\/?\*$/, "")));
|
|
103
|
+
if (!matches || !props.id || !props.sessionID)
|
|
104
|
+
return;
|
|
105
|
+
await sdk.postSessionIdPermissionsPermissionId({
|
|
106
|
+
path: { id: props.sessionID, permissionID: props.id },
|
|
107
|
+
body: { response: "always" }
|
|
108
|
+
}).catch(() => {});
|
|
109
|
+
}
|
|
110
|
+
function extractPath(tool, args) {
|
|
111
|
+
if (!args)
|
|
112
|
+
return "";
|
|
113
|
+
if (tool === "bash")
|
|
114
|
+
return args.workdir || args.command || "";
|
|
115
|
+
return args.filePath || args.path || args.pattern || "";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/context.ts
|
|
119
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
120
|
+
import { join as join3 } from "path";
|
|
121
|
+
var CONTEXT_FILES = ["AGENTS.md", "CLAUDE.md", ".agents/AGENTS.md"];
|
|
122
|
+
function collectAgentContext(dirs) {
|
|
123
|
+
const sections = [];
|
|
124
|
+
for (const entry of dirs.values()) {
|
|
125
|
+
for (const name of CONTEXT_FILES) {
|
|
126
|
+
const fp = join3(entry.path, name);
|
|
127
|
+
try {
|
|
128
|
+
const content = readFileSync2(fp, "utf-8").trim();
|
|
129
|
+
if (content)
|
|
130
|
+
sections.push(`# Context from ${fp}
|
|
131
|
+
|
|
132
|
+
${content}`);
|
|
133
|
+
} catch {}
|
|
60
134
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
135
|
+
}
|
|
136
|
+
return sections;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/plugin.ts
|
|
140
|
+
var SENTINEL = Object.assign(new Error("__ADD_DIR_HANDLED__"), { stack: "" });
|
|
141
|
+
function log(msg, data) {
|
|
142
|
+
console.error(`[add-dir] ${msg}`, data !== undefined ? JSON.stringify(data) : "");
|
|
143
|
+
}
|
|
144
|
+
var AddDirPlugin = async ({ client, worktree, directory }) => {
|
|
145
|
+
const root = worktree || directory;
|
|
146
|
+
const dirs = loadDirs();
|
|
147
|
+
const sdk = client;
|
|
148
|
+
log("init", { root, persistedDirs: [...dirs.keys()] });
|
|
149
|
+
function add(dirPath, persist) {
|
|
150
|
+
const result = validateDir(dirPath, root, [...dirs.values()].map((d) => d.path));
|
|
151
|
+
if (!result.ok)
|
|
152
|
+
return { ok: false, message: result.reason };
|
|
153
|
+
dirs.set(result.absolutePath, { path: result.absolutePath, persist });
|
|
154
|
+
if (persist)
|
|
155
|
+
saveDirs(dirs);
|
|
156
|
+
const label = persist ? "persistent" : "session";
|
|
157
|
+
return { ok: true, message: `Added ${result.absolutePath} as a working directory (${label}).` };
|
|
158
|
+
}
|
|
159
|
+
function remove(path) {
|
|
160
|
+
if (!path?.trim())
|
|
161
|
+
return "Usage: /remove-dir <path>";
|
|
162
|
+
if (!dirs.has(path))
|
|
163
|
+
return `${path} is not in the directory list.`;
|
|
164
|
+
dirs.delete(path);
|
|
165
|
+
saveDirs(dirs);
|
|
166
|
+
return `Removed ${path} from working directories.`;
|
|
167
|
+
}
|
|
168
|
+
function list() {
|
|
169
|
+
if (!dirs.size)
|
|
170
|
+
return "No additional directories added.";
|
|
171
|
+
return [...dirs.values()].map((d) => `${d.path} (${d.persist ? "persistent" : "session"})`).join(`
|
|
172
|
+
`);
|
|
173
|
+
}
|
|
174
|
+
function handleAdd(args, sessionID) {
|
|
175
|
+
const tokens = args.trim().split(/\s+/);
|
|
176
|
+
const flags = new Set(tokens.filter((t) => t.startsWith("--")));
|
|
177
|
+
const pos = tokens.filter((t) => !t.startsWith("--"));
|
|
178
|
+
if (!pos[0])
|
|
179
|
+
return notify(sdk, sessionID, "Usage: /add-dir <path> [--remember]");
|
|
180
|
+
const result = add(pos[0], flags.has("--remember"));
|
|
181
|
+
if (result.ok)
|
|
182
|
+
grantSessionAsync(sdk, sessionID, result.message);
|
|
183
|
+
else
|
|
184
|
+
notify(sdk, sessionID, result.message);
|
|
185
|
+
}
|
|
186
|
+
const commands = {
|
|
187
|
+
"add-dir": (args, sid) => {
|
|
188
|
+
log("add-dir", { args, sid });
|
|
189
|
+
handleAdd(args, sid);
|
|
190
|
+
},
|
|
191
|
+
"list-dir": (_, sid) => {
|
|
192
|
+
log("list-dir", { sid });
|
|
193
|
+
notify(sdk, sid, list());
|
|
194
|
+
},
|
|
195
|
+
"remove-dir": (args, sid) => {
|
|
196
|
+
log("remove-dir", { args, sid });
|
|
197
|
+
notify(sdk, sid, remove(args));
|
|
64
198
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
199
|
+
};
|
|
200
|
+
return {
|
|
201
|
+
config: async (cfg) => {
|
|
202
|
+
cfg.command ??= {};
|
|
203
|
+
const cmd = cfg.command;
|
|
204
|
+
cmd["add-dir"] = { template: "/add-dir", description: "Add a working directory" };
|
|
205
|
+
cmd["list-dir"] = { template: "/list-dir", description: "List added working directories" };
|
|
206
|
+
cmd["remove-dir"] = { template: "/remove-dir", description: "Remove a working directory" };
|
|
207
|
+
if (!dirs.size)
|
|
208
|
+
return;
|
|
209
|
+
const perm = cfg.permission ??= {};
|
|
210
|
+
const extDir = perm.external_directory ??= {};
|
|
211
|
+
for (const entry of dirs.values())
|
|
212
|
+
extDir[permissionGlob(entry.path)] = "allow";
|
|
213
|
+
},
|
|
214
|
+
"command.execute.before": async (input) => {
|
|
215
|
+
const handler = commands[input.command];
|
|
216
|
+
if (!handler)
|
|
217
|
+
return;
|
|
218
|
+
handler(input.arguments || "", input.sessionID);
|
|
219
|
+
throw SENTINEL;
|
|
220
|
+
},
|
|
221
|
+
"tool.execute.before": async (input, output) => {
|
|
222
|
+
if (shouldGrantBeforeTool(dirs, input.tool, output.args))
|
|
223
|
+
await grantSession(sdk, input.sessionID, "Directory access granted by add-dir plugin.");
|
|
224
|
+
},
|
|
225
|
+
event: async ({ event }) => {
|
|
226
|
+
const e = event;
|
|
227
|
+
if (e.type === "permission.asked" && e.properties)
|
|
228
|
+
await autoApprovePermission(sdk, e.properties, dirs);
|
|
229
|
+
},
|
|
230
|
+
"experimental.chat.system.transform": async (_input, output) => {
|
|
231
|
+
output.system.push(...collectAgentContext(dirs));
|
|
80
232
|
}
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
}
|
|
113
|
-
},
|
|
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
|
-
}
|
|
125
|
-
}
|
|
126
|
-
};
|
|
233
|
+
};
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// src/index.ts
|
|
237
|
+
var src_default = AddDirPlugin;
|
|
238
|
+
export {
|
|
239
|
+
src_default as default
|
|
127
240
|
};
|
|
128
|
-
export default addDirPlugin;
|
|
129
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DirEntry } from "./state.js";
|
|
2
|
+
import type { SDK, PermissionEvent, ToolArgs } from "./types.js";
|
|
3
|
+
export declare function permissionGlob(dirPath: string): string;
|
|
4
|
+
export declare function notify(sdk: SDK, sessionID: string, text: string): void;
|
|
5
|
+
export declare function grantSessionAsync(sdk: SDK, sessionID: string, text: string): void;
|
|
6
|
+
export declare function grantSession(sdk: SDK, sessionID: string, text: string): Promise<void>;
|
|
7
|
+
export declare function shouldGrantBeforeTool(dirs: Map<string, DirEntry>, tool: string, args: ToolArgs): boolean;
|
|
8
|
+
export declare function autoApprovePermission(sdk: SDK, props: PermissionEvent, dirs: Map<string, DirEntry>): Promise<void>;
|
package/dist/plugin.d.ts
ADDED
package/dist/state.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface DirEntry {
|
|
2
|
+
path: string;
|
|
3
|
+
persist: boolean;
|
|
4
|
+
}
|
|
5
|
+
export declare function expandHome(p: string): string;
|
|
6
|
+
export declare function loadDirs(): Map<string, DirEntry>;
|
|
7
|
+
export declare function saveDirs(dirs: Map<string, DirEntry>): void;
|
|
8
|
+
export declare function isChildOf(parent: string, child: string): boolean;
|
|
9
|
+
export declare function matchesDirs(dirs: Map<string, DirEntry>, filepath: string): boolean;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import type { PluginInput } from "@opencode-ai/plugin";
|
|
2
|
+
export type SDK = PluginInput["client"];
|
|
3
|
+
export interface PromptBody {
|
|
4
|
+
noReply: true;
|
|
5
|
+
tools?: Record<string, boolean>;
|
|
6
|
+
parts: Array<{
|
|
7
|
+
type: "text";
|
|
8
|
+
text: string;
|
|
9
|
+
}>;
|
|
4
10
|
}
|
|
5
|
-
export interface
|
|
6
|
-
|
|
7
|
-
directories: AddedDirectory[];
|
|
11
|
+
export interface PermissionReplyBody {
|
|
12
|
+
response: "once" | "always" | "reject";
|
|
8
13
|
}
|
|
9
|
-
export interface
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
export interface PermissionEvent {
|
|
15
|
+
id: string;
|
|
16
|
+
sessionID: string;
|
|
17
|
+
permission: string;
|
|
18
|
+
patterns: string[];
|
|
19
|
+
metadata: Record<string, unknown>;
|
|
20
|
+
always: string[];
|
|
21
|
+
}
|
|
22
|
+
export interface ToolArgs {
|
|
23
|
+
filePath?: string;
|
|
24
|
+
path?: string;
|
|
25
|
+
pattern?: string;
|
|
26
|
+
workdir?: string;
|
|
27
|
+
command?: string;
|
|
14
28
|
}
|
|
15
|
-
//# sourceMappingURL=types.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,37 +1,58 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-add-dir",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.3.0",
|
|
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
|
-
"
|
|
7
|
-
|
|
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
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
19
|
+
"dist/",
|
|
20
|
+
"bin/",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE"
|
|
13
23
|
],
|
|
14
24
|
"scripts": {
|
|
15
|
-
"build": "tsc",
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
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-
|
|
33
|
-
"
|
|
33
|
+
"opencode-plugin",
|
|
34
|
+
"add-dir",
|
|
35
|
+
"working-directory",
|
|
36
|
+
"permissions"
|
|
34
37
|
],
|
|
35
|
-
"
|
|
36
|
-
|
|
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
|
}
|
package/command/add-dir.md
DELETED
package/dist/index.d.ts.map
DELETED
|
@@ -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.map
DELETED
|
@@ -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
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
package/dist/utils.d.ts.map
DELETED
|
@@ -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"}
|
package/scripts/install.js
DELETED
|
@@ -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
|
-
}
|