@junwu168/openshell 0.1.3 → 0.1.4
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/dist/core/audit/log-store.js +1 -1
- package/dist/core/orchestrator.d.ts +2 -2
- package/dist/core/orchestrator.js +3 -3
- package/dist/core/result.d.ts +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/opencode/plugin.d.ts +1 -1
- package/dist/opencode/plugin.js +8 -8
- package/package.json +6 -1
- package/.claude/settings.local.json +0 -25
- package/bun.lock +0 -368
- package/docs/superpowers/notes/2026-03-25-opencode-remote-tools-handoff.md +0 -81
- package/docs/superpowers/notes/2026-03-26-openshell-pre-release-review.md +0 -174
- package/docs/superpowers/plans/2026-03-25-opencode-remote-tools.md +0 -1656
- package/docs/superpowers/plans/2026-03-25-server-registry-cli.md +0 -54
- package/docs/superpowers/plans/2026-03-26-config-backed-credential-registry.md +0 -494
- package/docs/superpowers/plans/2026-03-26-openshell-release-prep.md +0 -639
- package/docs/superpowers/specs/2026-03-25-opencode-remote-tools-design.md +0 -378
- package/docs/superpowers/specs/2026-03-26-config-backed-credential-registry-design.md +0 -272
- package/docs/superpowers/specs/2026-03-26-openshell-release-prep-design.md +0 -197
- package/examples/opencode-local/opencode.json +0 -19
- package/scripts/openshell.ts +0 -3
- package/scripts/server-registry.ts +0 -3
- package/src/cli/openshell.ts +0 -65
- package/src/cli/server-registry.ts +0 -476
- package/src/core/audit/git-audit-repo.ts +0 -42
- package/src/core/audit/log-store.ts +0 -20
- package/src/core/audit/redact.ts +0 -4
- package/src/core/contracts.ts +0 -51
- package/src/core/orchestrator.ts +0 -1082
- package/src/core/patch.ts +0 -11
- package/src/core/paths.ts +0 -32
- package/src/core/policy.ts +0 -30
- package/src/core/registry/server-registry.ts +0 -505
- package/src/core/result.ts +0 -16
- package/src/core/ssh/ssh-runtime.ts +0 -355
- package/src/index.ts +0 -3
- package/src/opencode/plugin.ts +0 -242
- package/src/product/install.ts +0 -43
- package/src/product/opencode-config.ts +0 -118
- package/src/product/uninstall.ts +0 -47
- package/src/product/workspace-tracker.ts +0 -69
- package/tests/integration/fake-ssh-server.ts +0 -97
- package/tests/integration/install-lifecycle.test.ts +0 -85
- package/tests/integration/orchestrator.test.ts +0 -767
- package/tests/integration/ssh-runtime.test.ts +0 -122
- package/tests/unit/audit.test.ts +0 -221
- package/tests/unit/build-layout.test.ts +0 -28
- package/tests/unit/opencode-config.test.ts +0 -100
- package/tests/unit/opencode-plugin.test.ts +0 -358
- package/tests/unit/openshell-cli.test.ts +0 -60
- package/tests/unit/paths.test.ts +0 -64
- package/tests/unit/plugin-export.test.ts +0 -10
- package/tests/unit/policy.test.ts +0 -53
- package/tests/unit/release-docs.test.ts +0 -31
- package/tests/unit/result.test.ts +0 -28
- package/tests/unit/server-registry-cli.test.ts +0 -673
- package/tests/unit/server-registry.test.ts +0 -452
- package/tests/unit/workspace-tracker.test.ts +0 -57
- package/tsconfig.json +0 -14
|
@@ -1,358 +0,0 @@
|
|
|
1
|
-
import { describe, expect, mock, test } from "bun:test"
|
|
2
|
-
import { access, mkdtemp, readFile, rm } from "node:fs/promises"
|
|
3
|
-
import { chdir, cwd } from "node:process"
|
|
4
|
-
import type { ToolContext } from "@opencode-ai/plugin"
|
|
5
|
-
import { join } from "node:path"
|
|
6
|
-
import { tmpdir } from "node:os"
|
|
7
|
-
|
|
8
|
-
const toolNames = [
|
|
9
|
-
"list_servers",
|
|
10
|
-
"remote_exec",
|
|
11
|
-
"remote_read_file",
|
|
12
|
-
"remote_write_file",
|
|
13
|
-
"remote_patch_file",
|
|
14
|
-
"remote_list_dir",
|
|
15
|
-
"remote_stat",
|
|
16
|
-
"remote_find",
|
|
17
|
-
]
|
|
18
|
-
|
|
19
|
-
const createRuntimeDependencies = () => ({
|
|
20
|
-
registry: {
|
|
21
|
-
list: async () => [],
|
|
22
|
-
resolve: async () => ({
|
|
23
|
-
id: "prod-a",
|
|
24
|
-
host: "prod-a.example",
|
|
25
|
-
port: 22,
|
|
26
|
-
username: "open",
|
|
27
|
-
auth: {
|
|
28
|
-
kind: "password" as const,
|
|
29
|
-
secret: "openpass",
|
|
30
|
-
},
|
|
31
|
-
}),
|
|
32
|
-
},
|
|
33
|
-
ssh: {
|
|
34
|
-
exec: async () => ({ stdout: "", stderr: "", exitCode: 0 }),
|
|
35
|
-
readFile: async () => "",
|
|
36
|
-
writeFile: async () => {},
|
|
37
|
-
listDir: async () => [],
|
|
38
|
-
stat: async () => ({ size: 0, mode: 0o644, isFile: true, isDirectory: false }),
|
|
39
|
-
},
|
|
40
|
-
audit: {
|
|
41
|
-
preflightLog: async () => {},
|
|
42
|
-
appendLog: async () => {},
|
|
43
|
-
preflightSnapshots: async () => {},
|
|
44
|
-
captureSnapshots: async () => {},
|
|
45
|
-
},
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
const createToolContext = (overrides: Partial<ToolContext> = {}): ToolContext => ({
|
|
49
|
-
sessionID: "session-1",
|
|
50
|
-
messageID: "message-1",
|
|
51
|
-
agent: "default",
|
|
52
|
-
directory: "/tmp/project",
|
|
53
|
-
worktree: "/tmp/project",
|
|
54
|
-
abort: new AbortController().signal,
|
|
55
|
-
metadata: () => {},
|
|
56
|
-
ask: async () => {},
|
|
57
|
-
...overrides,
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
const exists = async (path: URL) => {
|
|
61
|
-
try {
|
|
62
|
-
await access(path)
|
|
63
|
-
return true
|
|
64
|
-
} catch {
|
|
65
|
-
return false
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
describe("OpenCode plugin", () => {
|
|
70
|
-
test("registers explicit remote tools in plan order and serializes results", async () => {
|
|
71
|
-
const { OpenCodePlugin } = await import("../../src/index")
|
|
72
|
-
const { createOpenCodePlugin } = await import("../../src/opencode/plugin")
|
|
73
|
-
const plugin = createOpenCodePlugin({
|
|
74
|
-
ensureRuntimeDirs: async () => {},
|
|
75
|
-
createRuntimeDependencies,
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
expect(typeof OpenCodePlugin).toBe("function")
|
|
79
|
-
|
|
80
|
-
const hooks = await plugin({
|
|
81
|
-
client: {} as never,
|
|
82
|
-
project: {} as never,
|
|
83
|
-
directory: "/tmp/project",
|
|
84
|
-
worktree: "/tmp/project",
|
|
85
|
-
serverUrl: new URL("http://localhost"),
|
|
86
|
-
$: {} as never,
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
expect(Object.keys(hooks.tool ?? {})).toEqual(toolNames)
|
|
90
|
-
expect(typeof hooks.tool?.list_servers?.execute).toBe("function")
|
|
91
|
-
|
|
92
|
-
const serialized = await hooks.tool?.list_servers?.execute({}, {} as never)
|
|
93
|
-
expect(JSON.parse(serialized ?? "null")).toMatchObject({
|
|
94
|
-
status: "ok",
|
|
95
|
-
tool: "list_servers",
|
|
96
|
-
data: [],
|
|
97
|
-
execution: { attempted: true, completed: true },
|
|
98
|
-
audit: { logWritten: true, snapshotStatus: "not-applicable" },
|
|
99
|
-
})
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
test("builds runtime dependencies from the plugin worktree", async () => {
|
|
103
|
-
const { createOpenCodePlugin } = await import("../../src/opencode/plugin")
|
|
104
|
-
const tempDir = await mkdtemp(join(tmpdir(), "opencode-plugin-root-"))
|
|
105
|
-
const originalCwd = cwd()
|
|
106
|
-
const workspaceRoots: Array<string | undefined> = []
|
|
107
|
-
|
|
108
|
-
try {
|
|
109
|
-
chdir(tempDir)
|
|
110
|
-
|
|
111
|
-
const plugin = createOpenCodePlugin({
|
|
112
|
-
ensureRuntimeDirs: async () => {},
|
|
113
|
-
createRuntimeDependencies: (workspaceRoot) => {
|
|
114
|
-
workspaceRoots.push(workspaceRoot)
|
|
115
|
-
return createRuntimeDependencies()
|
|
116
|
-
},
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
await plugin({
|
|
120
|
-
client: {} as never,
|
|
121
|
-
project: {} as never,
|
|
122
|
-
directory: "/tmp/project",
|
|
123
|
-
worktree: "/tmp/project-worktree",
|
|
124
|
-
serverUrl: new URL("http://localhost"),
|
|
125
|
-
$: {} as never,
|
|
126
|
-
})
|
|
127
|
-
} finally {
|
|
128
|
-
chdir(originalCwd)
|
|
129
|
-
await rm(tempDir, { recursive: true, force: true })
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
expect(workspaceRoots).toEqual(["/tmp/project-worktree"])
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
test("derives runtime registry paths from the OpenCode worktree at call time", async () => {
|
|
136
|
-
const tempDir = await mkdtemp(join(tmpdir(), "opencode-plugin-env-"))
|
|
137
|
-
const firstConfigDir = join(tempDir, "config-a")
|
|
138
|
-
const firstDataDir = join(tempDir, "data-a")
|
|
139
|
-
const secondConfigDir = join(tempDir, "config-b")
|
|
140
|
-
const secondDataDir = join(tempDir, "data-b")
|
|
141
|
-
const registryCalls: Array<Record<string, unknown>> = []
|
|
142
|
-
const runtimePathCalls: string[] = []
|
|
143
|
-
|
|
144
|
-
try {
|
|
145
|
-
mock.module("../../src/core/paths", () => ({
|
|
146
|
-
createRuntimePaths: (workspaceRoot: string) => {
|
|
147
|
-
runtimePathCalls.push(workspaceRoot)
|
|
148
|
-
return {
|
|
149
|
-
configDir: secondConfigDir,
|
|
150
|
-
dataDir: secondDataDir,
|
|
151
|
-
globalRegistryFile: join(secondConfigDir, "servers.json"),
|
|
152
|
-
workspaceRegistryFile: join(workspaceRoot, ".open-code", "servers.json"),
|
|
153
|
-
auditLogFile: join(secondDataDir, "audit", "actions.jsonl"),
|
|
154
|
-
auditRepoDir: join(secondDataDir, "audit", "repo"),
|
|
155
|
-
}
|
|
156
|
-
},
|
|
157
|
-
runtimePaths: {
|
|
158
|
-
configDir: firstConfigDir,
|
|
159
|
-
dataDir: firstDataDir,
|
|
160
|
-
globalRegistryFile: join(firstConfigDir, "servers.json"),
|
|
161
|
-
workspaceRegistryFile: join(firstConfigDir, ".open-code", "servers.json"),
|
|
162
|
-
auditLogFile: join(firstDataDir, "audit", "actions.jsonl"),
|
|
163
|
-
auditRepoDir: join(firstDataDir, "audit", "repo"),
|
|
164
|
-
},
|
|
165
|
-
workspaceRegistryFile: (workspaceRoot: string) => join(workspaceRoot, ".open-code", "servers.json"),
|
|
166
|
-
ensureRuntimeDirs: async () => {},
|
|
167
|
-
}))
|
|
168
|
-
mock.module("../../src/core/registry/server-registry", () => ({
|
|
169
|
-
createServerRegistry: (options: Record<string, unknown>) => {
|
|
170
|
-
registryCalls.push(options)
|
|
171
|
-
return createRuntimeDependencies().registry
|
|
172
|
-
},
|
|
173
|
-
}))
|
|
174
|
-
mock.module("../../src/core/audit/log-store", () => ({
|
|
175
|
-
createAuditLogStore: () => ({
|
|
176
|
-
preflight: async () => {},
|
|
177
|
-
append: async () => {},
|
|
178
|
-
}),
|
|
179
|
-
}))
|
|
180
|
-
mock.module("../../src/core/audit/git-audit-repo", () => ({
|
|
181
|
-
createGitAuditRepo: () => ({
|
|
182
|
-
preflight: async () => {},
|
|
183
|
-
captureChange: async () => {},
|
|
184
|
-
}),
|
|
185
|
-
}))
|
|
186
|
-
mock.module("../../src/core/ssh/ssh-runtime", () => ({
|
|
187
|
-
createSshRuntime: () => createRuntimeDependencies().ssh,
|
|
188
|
-
}))
|
|
189
|
-
|
|
190
|
-
const { createOpenCodePlugin } = await import("../../src/opencode/plugin?runtime-path-check")
|
|
191
|
-
const plugin = createOpenCodePlugin({
|
|
192
|
-
ensureRuntimeDirs: async () => {},
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
await plugin({
|
|
196
|
-
client: {} as never,
|
|
197
|
-
project: {} as never,
|
|
198
|
-
directory: "/tmp/project",
|
|
199
|
-
worktree: "/tmp/project-worktree",
|
|
200
|
-
serverUrl: new URL("http://localhost"),
|
|
201
|
-
$: {} as never,
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
expect(runtimePathCalls).toEqual(["/tmp/project-worktree"])
|
|
205
|
-
expect(registryCalls).toHaveLength(1)
|
|
206
|
-
expect(registryCalls[0]).toMatchObject({
|
|
207
|
-
globalRegistryFile: join(secondConfigDir, "servers.json"),
|
|
208
|
-
workspaceRegistryFile: "/tmp/project-worktree/.open-code/servers.json",
|
|
209
|
-
workspaceRoot: "/tmp/project-worktree",
|
|
210
|
-
})
|
|
211
|
-
} finally {
|
|
212
|
-
mock.restore()
|
|
213
|
-
await rm(tempDir, { recursive: true, force: true })
|
|
214
|
-
}
|
|
215
|
-
})
|
|
216
|
-
|
|
217
|
-
test("does not ask for approval before safe remote exec commands", async () => {
|
|
218
|
-
const { createOpenCodePlugin } = await import("../../src/opencode/plugin")
|
|
219
|
-
const asks: Array<Record<string, unknown>> = []
|
|
220
|
-
const plugin = createOpenCodePlugin({
|
|
221
|
-
ensureRuntimeDirs: async () => {},
|
|
222
|
-
createRuntimeDependencies,
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
const hooks = await plugin({
|
|
226
|
-
client: {} as never,
|
|
227
|
-
project: {} as never,
|
|
228
|
-
directory: "/tmp/project",
|
|
229
|
-
worktree: "/tmp/project",
|
|
230
|
-
serverUrl: new URL("http://localhost"),
|
|
231
|
-
$: {} as never,
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
await hooks.tool?.remote_exec?.execute(
|
|
235
|
-
{
|
|
236
|
-
server: "prod-a",
|
|
237
|
-
command: "cat /etc/hosts",
|
|
238
|
-
},
|
|
239
|
-
createToolContext({
|
|
240
|
-
ask: async (request) => {
|
|
241
|
-
asks.push(request)
|
|
242
|
-
},
|
|
243
|
-
}),
|
|
244
|
-
)
|
|
245
|
-
|
|
246
|
-
expect(asks).toHaveLength(0)
|
|
247
|
-
})
|
|
248
|
-
|
|
249
|
-
test("asks OpenCode for bash approval before approval-required remote exec commands", async () => {
|
|
250
|
-
const { createOpenCodePlugin } = await import("../../src/opencode/plugin")
|
|
251
|
-
const asks: Array<Record<string, unknown>> = []
|
|
252
|
-
const plugin = createOpenCodePlugin({
|
|
253
|
-
ensureRuntimeDirs: async () => {},
|
|
254
|
-
createRuntimeDependencies,
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
const hooks = await plugin({
|
|
258
|
-
client: {} as never,
|
|
259
|
-
project: {} as never,
|
|
260
|
-
directory: "/tmp/project",
|
|
261
|
-
worktree: "/tmp/project",
|
|
262
|
-
serverUrl: new URL("http://localhost"),
|
|
263
|
-
$: {} as never,
|
|
264
|
-
})
|
|
265
|
-
|
|
266
|
-
await hooks.tool?.remote_exec?.execute(
|
|
267
|
-
{
|
|
268
|
-
server: "prod-a",
|
|
269
|
-
command: "kubectl get pods",
|
|
270
|
-
},
|
|
271
|
-
createToolContext({
|
|
272
|
-
ask: async (request) => {
|
|
273
|
-
asks.push(request)
|
|
274
|
-
},
|
|
275
|
-
}),
|
|
276
|
-
)
|
|
277
|
-
|
|
278
|
-
expect(asks).toEqual([
|
|
279
|
-
expect.objectContaining({
|
|
280
|
-
permission: "bash",
|
|
281
|
-
patterns: ["kubectl get pods"],
|
|
282
|
-
always: [],
|
|
283
|
-
metadata: expect.objectContaining({
|
|
284
|
-
tool: "remote_exec",
|
|
285
|
-
server: "prod-a",
|
|
286
|
-
command: "kubectl get pods",
|
|
287
|
-
}),
|
|
288
|
-
}),
|
|
289
|
-
])
|
|
290
|
-
})
|
|
291
|
-
|
|
292
|
-
test("asks OpenCode for edit approval before remote writes", async () => {
|
|
293
|
-
const { createOpenCodePlugin } = await import("../../src/opencode/plugin")
|
|
294
|
-
const asks: Array<Record<string, unknown>> = []
|
|
295
|
-
const plugin = createOpenCodePlugin({
|
|
296
|
-
ensureRuntimeDirs: async () => {},
|
|
297
|
-
createRuntimeDependencies,
|
|
298
|
-
})
|
|
299
|
-
|
|
300
|
-
const hooks = await plugin({
|
|
301
|
-
client: {} as never,
|
|
302
|
-
project: {} as never,
|
|
303
|
-
directory: "/tmp/project",
|
|
304
|
-
worktree: "/tmp/project",
|
|
305
|
-
serverUrl: new URL("http://localhost"),
|
|
306
|
-
$: {} as never,
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
await hooks.tool?.remote_write_file?.execute(
|
|
310
|
-
{
|
|
311
|
-
server: "prod-a",
|
|
312
|
-
path: "/tmp/open-code-smoke.txt",
|
|
313
|
-
content: "hello",
|
|
314
|
-
},
|
|
315
|
-
createToolContext({
|
|
316
|
-
ask: async (request) => {
|
|
317
|
-
asks.push(request)
|
|
318
|
-
},
|
|
319
|
-
}),
|
|
320
|
-
)
|
|
321
|
-
|
|
322
|
-
expect(asks).toEqual([
|
|
323
|
-
expect.objectContaining({
|
|
324
|
-
permission: "edit",
|
|
325
|
-
patterns: ["/tmp/open-code-smoke.txt"],
|
|
326
|
-
always: [],
|
|
327
|
-
metadata: expect.objectContaining({
|
|
328
|
-
tool: "remote_write_file",
|
|
329
|
-
server: "prod-a",
|
|
330
|
-
path: "/tmp/open-code-smoke.txt",
|
|
331
|
-
}),
|
|
332
|
-
}),
|
|
333
|
-
])
|
|
334
|
-
})
|
|
335
|
-
|
|
336
|
-
test("uses built-in bash and edit permission families in the local OpenCode example config", async () => {
|
|
337
|
-
const raw = await readFile(new URL("../../examples/opencode-local/opencode.json", import.meta.url), "utf8")
|
|
338
|
-
const config = JSON.parse(raw)
|
|
339
|
-
|
|
340
|
-
expect(config.permission.edit).toBe("ask")
|
|
341
|
-
expect(config.permission.bash).toMatchObject({
|
|
342
|
-
"*": "ask",
|
|
343
|
-
"cat *": "allow",
|
|
344
|
-
"systemctl status *": "allow",
|
|
345
|
-
})
|
|
346
|
-
expect(config.permission.remote_write_file).toBeUndefined()
|
|
347
|
-
expect(config.permission.remote_exec).toBeUndefined()
|
|
348
|
-
})
|
|
349
|
-
|
|
350
|
-
test("removes the checked-in local smoke package in favor of the global install flow", async () => {
|
|
351
|
-
expect(
|
|
352
|
-
await exists(new URL("../../examples/opencode-local/.opencode/package.json", import.meta.url)),
|
|
353
|
-
).toBe(false)
|
|
354
|
-
expect(
|
|
355
|
-
await exists(new URL("../../examples/opencode-local/.opencode/bun.lock", import.meta.url)),
|
|
356
|
-
).toBe(false)
|
|
357
|
-
})
|
|
358
|
-
})
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test"
|
|
2
|
-
|
|
3
|
-
const createWritable = () => {
|
|
4
|
-
let buffer = ""
|
|
5
|
-
|
|
6
|
-
return {
|
|
7
|
-
write(chunk: string) {
|
|
8
|
-
buffer += chunk
|
|
9
|
-
},
|
|
10
|
-
toString() {
|
|
11
|
-
return buffer
|
|
12
|
-
},
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
describe("openshell cli", () => {
|
|
17
|
-
test("routes server-registry subcommands", async () => {
|
|
18
|
-
const stdout = createWritable()
|
|
19
|
-
const stderr = createWritable()
|
|
20
|
-
const serverRegistryCalls: string[][] = []
|
|
21
|
-
const { runOpenShellCli } = await import("../../src/cli/openshell")
|
|
22
|
-
|
|
23
|
-
await expect(
|
|
24
|
-
runOpenShellCli(["server-registry", "list"], {
|
|
25
|
-
stdout,
|
|
26
|
-
stderr,
|
|
27
|
-
runServerRegistryCli: async (argv: string[]) => {
|
|
28
|
-
serverRegistryCalls.push(argv)
|
|
29
|
-
return 0
|
|
30
|
-
},
|
|
31
|
-
}),
|
|
32
|
-
).resolves.toBe(0)
|
|
33
|
-
|
|
34
|
-
expect(serverRegistryCalls).toEqual([["list"]])
|
|
35
|
-
expect(stdout.toString()).toBe("")
|
|
36
|
-
expect(stderr.toString()).toBe("")
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
test("prints top-level usage with no args", async () => {
|
|
40
|
-
const stdout = createWritable()
|
|
41
|
-
const stderr = createWritable()
|
|
42
|
-
const { runOpenShellCli } = await import("../../src/cli/openshell")
|
|
43
|
-
|
|
44
|
-
await expect(runOpenShellCli([], { stdout, stderr })).resolves.toBe(0)
|
|
45
|
-
|
|
46
|
-
expect(stdout.toString()).toContain("Usage: openshell")
|
|
47
|
-
expect(stderr.toString()).toBe("")
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
test("returns non-zero for unknown subcommands", async () => {
|
|
51
|
-
const stdout = createWritable()
|
|
52
|
-
const stderr = createWritable()
|
|
53
|
-
const { runOpenShellCli } = await import("../../src/cli/openshell")
|
|
54
|
-
|
|
55
|
-
await expect(runOpenShellCli(["wat"], { stdout, stderr })).resolves.toBe(1)
|
|
56
|
-
|
|
57
|
-
expect(stderr.toString()).toContain("Usage: openshell")
|
|
58
|
-
expect(stdout.toString()).toBe("")
|
|
59
|
-
})
|
|
60
|
-
})
|
package/tests/unit/paths.test.ts
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { describe, expect, mock, test } from "bun:test"
|
|
2
|
-
import { mkdtemp, rm, stat } from "node:fs/promises"
|
|
3
|
-
import { join } from "node:path"
|
|
4
|
-
import { tmpdir } from "node:os"
|
|
5
|
-
|
|
6
|
-
describe("runtime paths", () => {
|
|
7
|
-
test("runtime paths use openshell app directories and expose OpenCode config", async () => {
|
|
8
|
-
const tempRoot = await mkdtemp(join(tmpdir(), "opencode-paths-"))
|
|
9
|
-
try {
|
|
10
|
-
const openshellConfigDir = join(tempRoot, "openshell-config")
|
|
11
|
-
const openshellDataDir = join(tempRoot, "openshell-data")
|
|
12
|
-
const opencodeConfigDir = join(tempRoot, "opencode-config")
|
|
13
|
-
|
|
14
|
-
mock.module("env-paths", () => ({
|
|
15
|
-
default: (name: string) => {
|
|
16
|
-
if (name === "openshell") {
|
|
17
|
-
return { config: openshellConfigDir, data: openshellDataDir }
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (name === "opencode") {
|
|
21
|
-
return { config: opencodeConfigDir, data: join(tempRoot, "opencode-data") }
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
throw new Error(`unexpected env-paths name: ${name}`)
|
|
25
|
-
},
|
|
26
|
-
}))
|
|
27
|
-
|
|
28
|
-
const { createRuntimePaths, runtimePaths } = await import("../../src/core/paths?runtime-paths-test-1")
|
|
29
|
-
const runtime = createRuntimePaths("/repo")
|
|
30
|
-
|
|
31
|
-
expect(runtime.configDir).toBe(openshellConfigDir)
|
|
32
|
-
expect(runtime.dataDir).toBe(openshellDataDir)
|
|
33
|
-
expect(runtime.globalRegistryFile.endsWith("servers.json")).toBe(true)
|
|
34
|
-
expect(runtime.workspaceRegistryFile).toBe("/repo/.open-code/servers.json")
|
|
35
|
-
expect(runtime.opencodeConfigDir).toBe(opencodeConfigDir)
|
|
36
|
-
expect(runtime.opencodeConfigFile).toBe(join(opencodeConfigDir, "opencode.json"))
|
|
37
|
-
expect(runtimePaths.globalRegistryFile.endsWith("servers.json")).toBe(true)
|
|
38
|
-
} finally {
|
|
39
|
-
mock.restore()
|
|
40
|
-
await rm(tempRoot, { recursive: true, force: true })
|
|
41
|
-
}
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
test("ensureRuntimeDirs prepares the audit repo directory", async () => {
|
|
45
|
-
const tempRoot = await mkdtemp(join(tmpdir(), "opencode-paths-"))
|
|
46
|
-
try {
|
|
47
|
-
const configDir = join(tempRoot, "config")
|
|
48
|
-
const dataDir = join(tempRoot, "data")
|
|
49
|
-
|
|
50
|
-
mock.module("env-paths", () => ({
|
|
51
|
-
default: () => ({ config: configDir, data: dataDir }),
|
|
52
|
-
}))
|
|
53
|
-
|
|
54
|
-
const { ensureRuntimeDirs, runtimePaths } = await import("../../src/core/paths?runtime-paths-test-2")
|
|
55
|
-
await ensureRuntimeDirs()
|
|
56
|
-
|
|
57
|
-
const pathStat = await stat(runtimePaths.auditRepoDir)
|
|
58
|
-
expect(pathStat.isDirectory()).toBe(true)
|
|
59
|
-
} finally {
|
|
60
|
-
mock.restore()
|
|
61
|
-
await rm(tempRoot, { recursive: true, force: true })
|
|
62
|
-
}
|
|
63
|
-
})
|
|
64
|
-
})
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test"
|
|
2
|
-
import OpenShellPlugin, { OpenCodePlugin, OpenShellPlugin as NamedOpenShellPlugin } from "@junwu168/openshell"
|
|
3
|
-
|
|
4
|
-
describe("package entry", () => {
|
|
5
|
-
test("exports the OpenShell plugin factory", () => {
|
|
6
|
-
expect(typeof OpenShellPlugin).toBe("function")
|
|
7
|
-
expect(typeof NamedOpenShellPlugin).toBe("function")
|
|
8
|
-
expect(typeof OpenCodePlugin).toBe("function")
|
|
9
|
-
})
|
|
10
|
-
})
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test"
|
|
2
|
-
import { classifyRemoteExec } from "../../src/core/policy"
|
|
3
|
-
|
|
4
|
-
describe("remote exec policy", () => {
|
|
5
|
-
test("auto-allows simple linux inspection commands", () => {
|
|
6
|
-
expect(classifyRemoteExec("cat /etc/hosts")).toEqual({
|
|
7
|
-
decision: "auto-allow",
|
|
8
|
-
reason: "safe inspection command",
|
|
9
|
-
})
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
test("requires approval for middleware commands", () => {
|
|
13
|
-
expect(classifyRemoteExec("kubectl get pods -A")).toEqual({
|
|
14
|
-
decision: "approval-required",
|
|
15
|
-
reason: "middleware command",
|
|
16
|
-
})
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
test("requires approval for shell composition", () => {
|
|
20
|
-
expect(classifyRemoteExec("cat /etc/hosts | grep localhost")).toEqual({
|
|
21
|
-
decision: "approval-required",
|
|
22
|
-
reason: "shell composition",
|
|
23
|
-
})
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
test("rejects empty commands", () => {
|
|
27
|
-
expect(classifyRemoteExec(" ")).toEqual({
|
|
28
|
-
decision: "reject",
|
|
29
|
-
reason: "empty command",
|
|
30
|
-
})
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
test("auto-allows systemctl status inspection", () => {
|
|
34
|
-
expect(classifyRemoteExec("systemctl status nginx")).toEqual({
|
|
35
|
-
decision: "auto-allow",
|
|
36
|
-
reason: "safe inspection command",
|
|
37
|
-
})
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
test("does not auto-allow systemctl statusx", () => {
|
|
41
|
-
expect(classifyRemoteExec("systemctl statusx nginx")).toEqual({
|
|
42
|
-
decision: "approval-required",
|
|
43
|
-
reason: "unknown command",
|
|
44
|
-
})
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
test("requires approval for unknown commands", () => {
|
|
48
|
-
expect(classifyRemoteExec("uptime now")).toEqual({
|
|
49
|
-
decision: "approval-required",
|
|
50
|
-
reason: "unknown command",
|
|
51
|
-
})
|
|
52
|
-
})
|
|
53
|
-
})
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test"
|
|
2
|
-
import { access, readFile } from "node:fs/promises"
|
|
3
|
-
|
|
4
|
-
const exists = async (path: string) => {
|
|
5
|
-
try {
|
|
6
|
-
await access(path)
|
|
7
|
-
return true
|
|
8
|
-
} catch {
|
|
9
|
-
return false
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
describe("release docs", () => {
|
|
14
|
-
test("README documents the npm install and uninstall flow", async () => {
|
|
15
|
-
const readme = await readFile(new URL("../../README.md", import.meta.url), "utf8")
|
|
16
|
-
|
|
17
|
-
expect(readme).toContain("npm install -g @junwu168/openshell")
|
|
18
|
-
expect(readme).toContain("openshell install")
|
|
19
|
-
expect(readme).toContain("openshell server-registry add")
|
|
20
|
-
expect(readme).toContain("openshell uninstall")
|
|
21
|
-
expect(readme).toContain("opencode")
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
test("legacy local plugin shim is not part of the release example", async () => {
|
|
25
|
-
expect(
|
|
26
|
-
await exists(
|
|
27
|
-
new URL("../../examples/opencode-local/.opencode/plugins/open-code.ts", import.meta.url).pathname,
|
|
28
|
-
),
|
|
29
|
-
).toBe(false)
|
|
30
|
-
})
|
|
31
|
-
})
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test"
|
|
2
|
-
import { errorResult, okResult, partialFailureResult } from "../../src/core/result"
|
|
3
|
-
|
|
4
|
-
describe("tool result helpers", () => {
|
|
5
|
-
test("builds success payloads", () => {
|
|
6
|
-
expect(
|
|
7
|
-
okResult({
|
|
8
|
-
tool: "list_servers",
|
|
9
|
-
data: [],
|
|
10
|
-
execution: { attempted: true, completed: true },
|
|
11
|
-
audit: { logWritten: true, snapshotStatus: "not-applicable" },
|
|
12
|
-
}).status,
|
|
13
|
-
).toBe("ok")
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
test("builds partial-failure payloads", () => {
|
|
17
|
-
expect(
|
|
18
|
-
partialFailureResult({
|
|
19
|
-
tool: "remote_write_file",
|
|
20
|
-
message: "remote write succeeded but git commit failed",
|
|
21
|
-
}).status,
|
|
22
|
-
).toBe("partial_failure")
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
test("builds hard-error payloads", () => {
|
|
26
|
-
expect(errorResult({ tool: "remote_exec", code: "POLICY_REJECTED" }).status).toBe("error")
|
|
27
|
-
})
|
|
28
|
-
})
|