@junwu168/openshell 0.1.2 → 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.
Files changed (61) hide show
  1. package/dist/cli/openshell.js +4 -0
  2. package/dist/core/audit/log-store.js +1 -1
  3. package/dist/core/orchestrator.d.ts +2 -2
  4. package/dist/core/orchestrator.js +3 -3
  5. package/dist/core/result.d.ts +1 -1
  6. package/dist/index.d.ts +3 -3
  7. package/dist/index.js +3 -3
  8. package/dist/opencode/plugin.d.ts +1 -1
  9. package/dist/opencode/plugin.js +8 -8
  10. package/package.json +6 -1
  11. package/.claude/settings.local.json +0 -15
  12. package/bun.lock +0 -368
  13. package/docs/superpowers/notes/2026-03-25-opencode-remote-tools-handoff.md +0 -81
  14. package/docs/superpowers/notes/2026-03-26-openshell-pre-release-review.md +0 -174
  15. package/docs/superpowers/plans/2026-03-25-opencode-remote-tools.md +0 -1656
  16. package/docs/superpowers/plans/2026-03-25-server-registry-cli.md +0 -54
  17. package/docs/superpowers/plans/2026-03-26-config-backed-credential-registry.md +0 -494
  18. package/docs/superpowers/plans/2026-03-26-openshell-release-prep.md +0 -639
  19. package/docs/superpowers/specs/2026-03-25-opencode-remote-tools-design.md +0 -378
  20. package/docs/superpowers/specs/2026-03-26-config-backed-credential-registry-design.md +0 -272
  21. package/docs/superpowers/specs/2026-03-26-openshell-release-prep-design.md +0 -197
  22. package/examples/opencode-local/opencode.json +0 -19
  23. package/scripts/openshell.ts +0 -3
  24. package/scripts/server-registry.ts +0 -3
  25. package/src/cli/openshell.ts +0 -60
  26. package/src/cli/server-registry.ts +0 -476
  27. package/src/core/audit/git-audit-repo.ts +0 -42
  28. package/src/core/audit/log-store.ts +0 -20
  29. package/src/core/audit/redact.ts +0 -4
  30. package/src/core/contracts.ts +0 -51
  31. package/src/core/orchestrator.ts +0 -1082
  32. package/src/core/patch.ts +0 -11
  33. package/src/core/paths.ts +0 -32
  34. package/src/core/policy.ts +0 -30
  35. package/src/core/registry/server-registry.ts +0 -505
  36. package/src/core/result.ts +0 -16
  37. package/src/core/ssh/ssh-runtime.ts +0 -355
  38. package/src/index.ts +0 -3
  39. package/src/opencode/plugin.ts +0 -242
  40. package/src/product/install.ts +0 -43
  41. package/src/product/opencode-config.ts +0 -118
  42. package/src/product/uninstall.ts +0 -47
  43. package/src/product/workspace-tracker.ts +0 -69
  44. package/tests/integration/fake-ssh-server.ts +0 -97
  45. package/tests/integration/install-lifecycle.test.ts +0 -85
  46. package/tests/integration/orchestrator.test.ts +0 -767
  47. package/tests/integration/ssh-runtime.test.ts +0 -122
  48. package/tests/unit/audit.test.ts +0 -221
  49. package/tests/unit/build-layout.test.ts +0 -28
  50. package/tests/unit/opencode-config.test.ts +0 -100
  51. package/tests/unit/opencode-plugin.test.ts +0 -358
  52. package/tests/unit/openshell-cli.test.ts +0 -60
  53. package/tests/unit/paths.test.ts +0 -64
  54. package/tests/unit/plugin-export.test.ts +0 -10
  55. package/tests/unit/policy.test.ts +0 -53
  56. package/tests/unit/release-docs.test.ts +0 -31
  57. package/tests/unit/result.test.ts +0 -28
  58. package/tests/unit/server-registry-cli.test.ts +0 -673
  59. package/tests/unit/server-registry.test.ts +0 -452
  60. package/tests/unit/workspace-tracker.test.ts +0 -57
  61. package/tsconfig.json +0 -14
@@ -1,476 +0,0 @@
1
- import { access } from "node:fs/promises"
2
- import { createInterface } from "node:readline/promises"
3
- import { stderr, stdin, stdout } from "node:process"
4
- import { ensureRuntimeDirs, runtimePaths, workspaceRegistryFile } from "../core/paths.js"
5
- import {
6
- createServerRegistry,
7
- type RegistryScope,
8
- type ResolvedServerRecord,
9
- type ServerRecord,
10
- type ServerRegistry,
11
- } from "../core/registry/server-registry.js"
12
- import { createWorkspaceTracker, type WorkspaceTracker } from "../product/workspace-tracker.js"
13
-
14
- type PromptAdapter = {
15
- text(message: string, defaultValue?: string): Promise<string>
16
- password(message: string): Promise<string>
17
- confirm(message: string, defaultValue?: boolean): Promise<boolean>
18
- close?(): Promise<void> | void
19
- }
20
-
21
- type WritableLike = {
22
- write(chunk: string): void
23
- }
24
-
25
- type CliDeps = {
26
- registry: Pick<ServerRegistry, "list" | "resolve" | "listRaw" | "upsert" | "remove">
27
- workspaceTracker?: Pick<WorkspaceTracker, "record" | "remove">
28
- prompt: PromptAdapter
29
- stdout: WritableLike
30
- stderr: WritableLike
31
- workspaceRoot: string
32
- }
33
-
34
- const usage = [
35
- "Usage: bun run server-registry <add|list|remove>",
36
- "",
37
- "Commands:",
38
- " add interactively add or update a server across workspace/global scopes",
39
- " list print configured servers with scope metadata",
40
- " remove remove a configured server by id and scope",
41
- ].join("\n")
42
-
43
- const parseList = (input: string) => {
44
- const values = input
45
- .split(",")
46
- .map((value) => value.trim())
47
- .filter(Boolean)
48
-
49
- return values.length === 0 ? undefined : values
50
- }
51
-
52
- const describeServer = (record: Pick<ServerRecord, "id" | "host" | "port" | "username">) =>
53
- `${record.id} (${record.host}:${record.port} as ${record.username})`
54
-
55
- const createConsolePrompt = (): PromptAdapter => {
56
- const askText = async (message: string) => {
57
- const rl = createInterface({ input: stdin, output: stdout })
58
- try {
59
- return await rl.question(message)
60
- } finally {
61
- rl.close()
62
- }
63
- }
64
-
65
- return {
66
- async text(message, defaultValue) {
67
- const suffix = defaultValue === undefined ? "" : ` [${defaultValue}]`
68
- const answer = (await askText(`${message}${suffix}: `)).trim()
69
- return answer === "" ? (defaultValue ?? "") : answer
70
- },
71
- async password(message) {
72
- if (!stdin.isTTY || typeof stdin.setRawMode !== "function") {
73
- return askText(`${message}: `)
74
- }
75
-
76
- stdout.write(`${message}: `)
77
- stdin.resume()
78
- stdin.setEncoding("utf8")
79
- stdin.setRawMode(true)
80
-
81
- return await new Promise<string>((resolve, reject) => {
82
- let value = ""
83
-
84
- const cleanup = () => {
85
- stdin.off("data", onData)
86
- stdin.setRawMode(false)
87
- stdin.pause()
88
- stdout.write("\n")
89
- }
90
-
91
- const onData = (chunk: string | Buffer) => {
92
- const char = chunk.toString("utf8")
93
- if (char === "\u0003") {
94
- cleanup()
95
- reject(new Error("prompt cancelled"))
96
- return
97
- }
98
-
99
- if (char === "\r" || char === "\n") {
100
- cleanup()
101
- resolve(value)
102
- return
103
- }
104
-
105
- if (char === "\u007f" || char === "\b") {
106
- value = value.slice(0, -1)
107
- return
108
- }
109
-
110
- value += char
111
- }
112
-
113
- stdin.on("data", onData)
114
- })
115
- },
116
- async confirm(message, defaultValue = false) {
117
- const suffix = defaultValue ? " [Y/n]" : " [y/N]"
118
-
119
- while (true) {
120
- const answer = (await askText(`${message}${suffix}: `)).trim().toLowerCase()
121
- if (answer === "") {
122
- return defaultValue
123
- }
124
- if (answer === "y" || answer === "yes") {
125
- return true
126
- }
127
- if (answer === "n" || answer === "no") {
128
- return false
129
- }
130
- }
131
- },
132
- }
133
- }
134
-
135
- const createDefaultDeps = async (): Promise<CliDeps> => {
136
- await ensureRuntimeDirs()
137
- const workspaceRoot = process.cwd()
138
-
139
- return {
140
- registry: createServerRegistry({
141
- globalRegistryFile: runtimePaths.globalRegistryFile,
142
- workspaceRegistryFile: workspaceRegistryFile(workspaceRoot),
143
- workspaceRoot,
144
- }),
145
- workspaceTracker: createWorkspaceTracker(runtimePaths.workspaceTrackerFile),
146
- prompt: createConsolePrompt(),
147
- stdout: { write: (chunk) => stdout.write(chunk) },
148
- stderr: { write: (chunk) => stderr.write(chunk) },
149
- workspaceRoot,
150
- }
151
- }
152
-
153
- const getRawRecord = async (registry: CliDeps["registry"], scope: RegistryScope, id: string) =>
154
- (await registry.listRaw(scope)).find((record) => record.id === id) ?? null
155
-
156
- const workspaceScopeExists = async (workspaceRoot: string) => {
157
- try {
158
- await access(workspaceRegistryFile(workspaceRoot))
159
- return true
160
- } catch (error: unknown) {
161
- if ((error as NodeJS.ErrnoException).code === "ENOENT") {
162
- return false
163
- }
164
-
165
- throw error
166
- }
167
- }
168
-
169
- const promptScope = async (
170
- deps: CliDeps,
171
- message: string,
172
- defaultScope: RegistryScope,
173
- ): Promise<RegistryScope> => {
174
- while (true) {
175
- const answer = (await deps.prompt.text(message, defaultScope)).trim().toLowerCase()
176
- if (answer === "") {
177
- return defaultScope
178
- }
179
-
180
- if (answer === "global" || answer === "g") {
181
- return "global"
182
- }
183
-
184
- if (answer === "workspace" || answer === "w") {
185
- return "workspace"
186
- }
187
-
188
- deps.stderr.write(`Invalid scope: ${answer}\n`)
189
- }
190
- }
191
-
192
- const promptAuthKind = async (
193
- deps: CliDeps,
194
- defaultKind: ServerRecord["auth"]["kind"] = "password",
195
- ): Promise<ServerRecord["auth"]["kind"]> => {
196
- while (true) {
197
- const answer = (await deps.prompt.text("Auth kind (password/privateKey/certificate)", defaultKind)).trim()
198
- if (answer === "") {
199
- return defaultKind
200
- }
201
-
202
- switch (answer.toLowerCase()) {
203
- case "password":
204
- return "password"
205
- case "privatekey":
206
- return "privateKey"
207
- case "certificate":
208
- return "certificate"
209
- default:
210
- deps.stderr.write(`Invalid auth kind: ${answer}\n`)
211
- }
212
- }
213
- }
214
-
215
- const promptAuth = async (
216
- deps: CliDeps,
217
- kind: ServerRecord["auth"]["kind"],
218
- existingAuth?: ServerRecord["auth"],
219
- ): Promise<ServerRecord["auth"] | null> => {
220
- if (kind === "password") {
221
- const secret = await deps.prompt.password("Password")
222
- if (!secret) {
223
- deps.stderr.write("Password is required.\n")
224
- return null
225
- }
226
-
227
- deps.stdout.write("Warning: plain-text password will be stored as-is.\n")
228
- return { kind, secret }
229
- }
230
-
231
- if (kind === "privateKey") {
232
- const privateKeyPath = await deps.prompt.text("Private key path", existingAuth?.kind === "privateKey" ? existingAuth.privateKeyPath : undefined)
233
- if (!privateKeyPath) {
234
- deps.stderr.write("Private key path is required.\n")
235
- return null
236
- }
237
-
238
- const passphrase = (await deps.prompt.text(
239
- "Passphrase (optional)",
240
- existingAuth && "passphrase" in existingAuth ? existingAuth.passphrase ?? "" : "",
241
- )).trim()
242
-
243
- return {
244
- kind,
245
- privateKeyPath,
246
- ...(passphrase ? { passphrase } : {}),
247
- } as ServerRecord["auth"]
248
- }
249
-
250
- const certificatePath = await deps.prompt.text(
251
- "Certificate path",
252
- existingAuth?.kind === "certificate" ? existingAuth.certificatePath : undefined,
253
- )
254
- if (!certificatePath) {
255
- deps.stderr.write("Certificate path is required.\n")
256
- return null
257
- }
258
-
259
- const privateKeyPath = await deps.prompt.text(
260
- "Private key path",
261
- existingAuth?.kind === "certificate" ? existingAuth.privateKeyPath : undefined,
262
- )
263
- if (!privateKeyPath) {
264
- deps.stderr.write("Private key path is required.\n")
265
- return null
266
- }
267
-
268
- const passphrase = (await deps.prompt.text(
269
- "Passphrase (optional)",
270
- existingAuth && "passphrase" in existingAuth ? existingAuth.passphrase ?? "" : "",
271
- )).trim()
272
-
273
- return {
274
- kind,
275
- certificatePath,
276
- privateKeyPath,
277
- ...(passphrase ? { passphrase } : {}),
278
- } as ServerRecord["auth"]
279
- }
280
-
281
- const handleAdd = async (deps: CliDeps, idArg?: string) => {
282
- const id = await deps.prompt.text("Server id", idArg)
283
- if (!id) {
284
- deps.stderr.write("Server id is required.\n")
285
- return 1
286
- }
287
-
288
- const defaultScope = (await workspaceScopeExists(deps.workspaceRoot)) ? "workspace" : "global"
289
- const scope = await promptScope(deps, "Server scope (global/workspace)", defaultScope)
290
-
291
- const existing = await getRawRecord(deps.registry, scope, id)
292
- const resolvedExisting = await deps.registry.resolve(id)
293
-
294
- if (existing) {
295
- const overwrite = await deps.prompt.confirm(`Overwrite existing server ${describeServer(existing)}?`)
296
- if (!overwrite) {
297
- deps.stdout.write("Cancelled.\n")
298
- return 0
299
- }
300
- }
301
-
302
- if (scope === "workspace" && resolvedExisting?.scope === "global") {
303
- deps.stdout.write(
304
- `Warning: workspace record ${id} will override global entry ${describeServer(resolvedExisting)}.\n`,
305
- )
306
- }
307
-
308
- const host = await deps.prompt.text("Host", existing?.host ?? resolvedExisting?.host)
309
- if (!host) {
310
- deps.stderr.write("Host is required.\n")
311
- return 1
312
- }
313
-
314
- const portRaw = await deps.prompt.text("Port", String(existing?.port ?? resolvedExisting?.port ?? 22))
315
- const port = Number.parseInt(portRaw, 10)
316
- if (!Number.isInteger(port) || port <= 0) {
317
- deps.stderr.write(`Invalid port: ${portRaw}\n`)
318
- return 1
319
- }
320
-
321
- const username = await deps.prompt.text("Username", existing?.username ?? resolvedExisting?.username)
322
- if (!username) {
323
- deps.stderr.write("Username is required.\n")
324
- return 1
325
- }
326
-
327
- const labels = parseList(
328
- await deps.prompt.text("Labels (comma-separated)", existing?.labels?.join(",") ?? resolvedExisting?.labels?.join(",") ?? ""),
329
- )
330
- const groups = parseList(
331
- await deps.prompt.text("Groups (comma-separated)", existing?.groups?.join(",") ?? resolvedExisting?.groups?.join(",") ?? ""),
332
- )
333
-
334
- const authKind = await promptAuthKind(deps, existing?.auth.kind ?? resolvedExisting?.auth.kind ?? "password")
335
- const auth = await promptAuth(deps, authKind, existing?.auth ?? resolvedExisting?.auth)
336
- if (!auth) {
337
- return 1
338
- }
339
-
340
- await deps.registry.upsert(scope, {
341
- id,
342
- host,
343
- port,
344
- username,
345
- ...(labels ? { labels } : {}),
346
- ...(groups ? { groups } : {}),
347
- auth,
348
- } as ServerRecord)
349
-
350
- if (scope === "workspace") {
351
- await deps.workspaceTracker?.record({
352
- workspaceRoot: deps.workspaceRoot,
353
- managedPath: `${deps.workspaceRoot}/.open-code`,
354
- })
355
- }
356
-
357
- deps.stdout.write(`Saved server ${id} (${host}:${port}).\n`)
358
- return 0
359
- }
360
-
361
- const handleList = async (deps: CliDeps) => {
362
- const records = await deps.registry.list()
363
- if (records.length === 0) {
364
- deps.stdout.write("No servers configured.\n")
365
- return 0
366
- }
367
-
368
- deps.stdout.write("ID\tSCOPE\tSTATUS\tHOST\tPORT\tUSERNAME\tLABELS\tGROUPS\n")
369
- for (const record of records as ResolvedServerRecord[]) {
370
- deps.stdout.write(
371
- [
372
- record.id,
373
- record.scope,
374
- record.shadowingGlobal ? "shadowing global" : "",
375
- record.host,
376
- String(record.port),
377
- record.username,
378
- (record.labels ?? []).join(","),
379
- (record.groups ?? []).join(","),
380
- ].join("\t") + "\n",
381
- )
382
- }
383
-
384
- return 0
385
- }
386
-
387
- const handleRemove = async (deps: CliDeps, idArg?: string) => {
388
- const [globalRecords, workspaceRecords] = await Promise.all([
389
- deps.registry.listRaw("global"),
390
- deps.registry.listRaw("workspace"),
391
- ])
392
-
393
- if (globalRecords.length === 0 && workspaceRecords.length === 0) {
394
- deps.stdout.write("No servers configured.\n")
395
- return 0
396
- }
397
-
398
- const id = idArg ?? (await deps.prompt.text("Server id to remove"))
399
- if (!id) {
400
- deps.stderr.write("Server id is required.\n")
401
- return 1
402
- }
403
-
404
- const globalRecord = globalRecords.find((record) => record.id === id) ?? null
405
- const workspaceRecord = workspaceRecords.find((record) => record.id === id) ?? null
406
-
407
- if (!globalRecord && !workspaceRecord) {
408
- deps.stderr.write(`Server ${id} not found.\n`)
409
- return 1
410
- }
411
-
412
- let scope: RegistryScope
413
- if (globalRecord && workspaceRecord) {
414
- scope = await promptScope(deps, "Remove from which scope (global/workspace)", "workspace")
415
- } else {
416
- scope = workspaceRecord ? "workspace" : "global"
417
- }
418
-
419
- const existing = scope === "workspace" ? workspaceRecord : globalRecord
420
- if (!existing) {
421
- deps.stderr.write(`Server ${id} not found in ${scope}.\n`)
422
- return 1
423
- }
424
-
425
- const confirmed = await deps.prompt.confirm(`Remove server ${describeServer(existing)}?`)
426
- if (!confirmed) {
427
- deps.stdout.write("Cancelled.\n")
428
- return 0
429
- }
430
-
431
- const removed = await deps.registry.remove(scope, id)
432
- if (!removed) {
433
- deps.stderr.write(`Server ${id} not found in ${scope}.\n`)
434
- return 1
435
- }
436
-
437
- if (scope === "workspace") {
438
- const remainingWorkspaceRecords = await deps.registry.listRaw("workspace")
439
- if (remainingWorkspaceRecords.length === 0) {
440
- await deps.workspaceTracker?.remove(deps.workspaceRoot)
441
- }
442
- }
443
-
444
- deps.stdout.write(`Removed server ${id} from ${scope}.\n`)
445
- return 0
446
- }
447
-
448
- export const runServerRegistryCli = async (argv: string[], deps?: CliDeps) => {
449
- let activeDeps: CliDeps
450
- try {
451
- activeDeps = deps ?? (await createDefaultDeps())
452
- } catch (err) {
453
- console.error(`Failed to initialize: ${err instanceof Error ? err.message : err}`)
454
- return 1
455
- }
456
-
457
- try {
458
- const [command, arg] = argv
459
-
460
- switch (command) {
461
- case "add":
462
- return await handleAdd(activeDeps, arg)
463
- case "list":
464
- return await handleList(activeDeps)
465
- case "remove":
466
- return await handleRemove(activeDeps, arg)
467
- default:
468
- activeDeps.stderr.write(`${usage}\n`)
469
- return 1
470
- }
471
- } finally {
472
- await activeDeps.prompt.close?.()
473
- }
474
- }
475
-
476
- export const main = async (argv: string[] = process.argv.slice(2)) => runServerRegistryCli(argv)
@@ -1,42 +0,0 @@
1
- import { mkdir, writeFile } from "node:fs/promises"
2
- import { dirname, join } from "node:path"
3
-
4
- const run = async (cwd: string, args: string[]) => {
5
- const proc = Bun.spawn(["git", ...args], { cwd, stderr: "pipe", stdout: "pipe" })
6
- const exitCode = await proc.exited
7
- if (exitCode !== 0) throw new Error(await new Response(proc.stderr).text())
8
- }
9
-
10
- const encodeSegment = (segment: string) =>
11
- `s-${Buffer.from(segment, "utf8").toString("base64url")}`
12
-
13
- const snapshotParts = (server: string, path: string) => [
14
- encodeSegment(server),
15
- ...path
16
- .split("/")
17
- .filter((segment) => segment.length > 0)
18
- .map(encodeSegment),
19
- ]
20
-
21
- export const createGitAuditRepo = (repoDir: string) => ({
22
- async preflight() {
23
- await mkdir(repoDir, { recursive: true })
24
- await run(repoDir, ["init"])
25
- await run(repoDir, ["config", "user.name", "Open Code"])
26
- await run(repoDir, ["config", "user.email", "open-code@local"])
27
- },
28
- async captureChange(input: { server: string; path: string; before: string; after: string }) {
29
- const parts = snapshotParts(input.server, input.path)
30
- const relativeBase = parts.join("/")
31
- const base = join(repoDir, ...parts)
32
- await mkdir(dirname(base), { recursive: true })
33
- await writeFile(`${base}.before`, input.before)
34
- await writeFile(`${base}.after`, input.after)
35
- await run(repoDir, ["add", "--", `${relativeBase}.before`, `${relativeBase}.after`])
36
- await run(repoDir, ["commit", "--allow-empty", "-m", `audit: ${input.server} ${input.path}`])
37
- },
38
- async lastCommitMessage() {
39
- const proc = Bun.spawn(["git", "log", "-1", "--pretty=%s"], { cwd: repoDir, stdout: "pipe" })
40
- return (await new Response(proc.stdout).text()).trim()
41
- },
42
- })
@@ -1,20 +0,0 @@
1
- import { appendFile, mkdir } from "node:fs/promises"
2
- import { dirname } from "node:path"
3
- import { redactSecrets } from "./redact"
4
-
5
- export const createAuditLogStore = (file: string) => ({
6
- async preflight() {
7
- await mkdir(dirname(file), { recursive: true })
8
- await appendFile(file, "")
9
- },
10
- async append(entry: Record<string, unknown>) {
11
- const stamped = {
12
- ...entry,
13
- timestamp: new Date().toISOString(),
14
- }
15
- const json = JSON.stringify(stamped, (_key, value) =>
16
- typeof value === "string" ? redactSecrets(value) : value,
17
- )
18
- await appendFile(file, `${json}\n`)
19
- },
20
- })
@@ -1,4 +0,0 @@
1
- export const redactSecrets = (value: string) =>
2
- value
3
- .replace(/:\/\/([^:\s]+):([^@\s]+)@/g, "://$1:[REDACTED]@")
4
- .replace(/(password|secret|token)=([^\s&]+)/gi, "$1=[REDACTED]")
@@ -1,51 +0,0 @@
1
- export type ServerID = string
2
-
3
- export type ApprovalDecision = "allow" | "deny"
4
- export type PolicyDecision = "auto-allow" | "approval-required" | "reject"
5
- export type ToolErrorCode =
6
- | "APPROVAL_REJECTED"
7
- | "AUDIT_LOG_PREFLIGHT_FAILED"
8
- | "AUDIT_SNAPSHOT_PREFLIGHT_FAILED"
9
- | "AUTH_PATH_INVALID"
10
- | "AUTH_PATH_UNREADABLE"
11
- | "CERTIFICATE_PATH_NOT_FOUND"
12
- | "KEY_PATH_NOT_FOUND"
13
- | "PATCH_APPLY_FAILED"
14
- | "POLICY_REJECTED"
15
- | "REGISTRY_RECORD_INVALID"
16
- | "REGISTRY_LIST_FAILED"
17
- | "SERVER_NOT_FOUND"
18
- | "SERVER_RESOLVE_FAILED"
19
- | "SSH_EXEC_FAILED"
20
- | "SSH_FIND_FAILED"
21
- | "SSH_LIST_FAILED"
22
- | "SSH_READ_FAILED"
23
- | "SSH_STAT_FAILED"
24
- | "SSH_WRITE_FAILED"
25
-
26
- export type ToolStatus = "ok" | "partial_failure" | "error"
27
-
28
- export interface ToolPayload<TData = unknown> {
29
- tool: string
30
- server?: ServerID
31
- data?: TData
32
- message?: string
33
- code?: ToolErrorCode
34
- execution?: {
35
- attempted: boolean
36
- completed: boolean
37
- exitCode?: number
38
- stdoutBytes?: number
39
- stderrBytes?: number
40
- stdoutTruncated?: boolean
41
- stderrTruncated?: boolean
42
- }
43
- audit?: {
44
- logWritten: boolean
45
- snapshotStatus: "not-applicable" | "written" | "partial-failure"
46
- }
47
- }
48
-
49
- export interface ToolResult<TData = unknown> extends ToolPayload<TData> {
50
- status: ToolStatus
51
- }