@junwu168/openshell 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +10 -0
- package/README.md +63 -0
- package/bun.lock +368 -0
- package/dist/cli/openshell.d.ts +13 -0
- package/dist/cli/openshell.js +41 -0
- package/dist/cli/server-registry.d.ts +22 -0
- package/dist/cli/server-registry.js +349 -0
- package/dist/core/audit/git-audit-repo.d.ts +10 -0
- package/dist/core/audit/git-audit-repo.js +38 -0
- package/dist/core/audit/log-store.d.ts +4 -0
- package/dist/core/audit/log-store.js +17 -0
- package/dist/core/audit/redact.d.ts +1 -0
- package/dist/core/audit/redact.js +3 -0
- package/dist/core/contracts.d.ts +28 -0
- package/dist/core/contracts.js +1 -0
- package/dist/core/orchestrator.d.ts +110 -0
- package/dist/core/orchestrator.js +825 -0
- package/dist/core/patch.d.ts +1 -0
- package/dist/core/patch.js +8 -0
- package/dist/core/paths.d.ts +26 -0
- package/dist/core/paths.js +26 -0
- package/dist/core/policy.d.ts +16 -0
- package/dist/core/policy.js +29 -0
- package/dist/core/registry/server-registry.d.ts +59 -0
- package/dist/core/registry/server-registry.js +350 -0
- package/dist/core/result.d.ts +4 -0
- package/dist/core/result.js +12 -0
- package/dist/core/ssh/ssh-runtime.d.ts +31 -0
- package/dist/core/ssh/ssh-runtime.js +240 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/opencode/plugin.d.ts +10 -0
- package/dist/opencode/plugin.js +183 -0
- package/dist/product/install.d.ts +12 -0
- package/dist/product/install.js +25 -0
- package/dist/product/opencode-config.d.ts +15 -0
- package/dist/product/opencode-config.js +93 -0
- package/dist/product/uninstall.d.ts +12 -0
- package/dist/product/uninstall.js +27 -0
- package/dist/product/workspace-tracker.d.ts +11 -0
- package/dist/product/workspace-tracker.js +48 -0
- package/docs/superpowers/notes/2026-03-25-opencode-remote-tools-handoff.md +81 -0
- package/docs/superpowers/notes/2026-03-26-openshell-pre-release-review.md +174 -0
- package/docs/superpowers/plans/2026-03-25-opencode-remote-tools.md +1656 -0
- package/docs/superpowers/plans/2026-03-25-server-registry-cli.md +54 -0
- package/docs/superpowers/plans/2026-03-26-config-backed-credential-registry.md +494 -0
- package/docs/superpowers/plans/2026-03-26-openshell-release-prep.md +639 -0
- package/docs/superpowers/specs/2026-03-25-opencode-remote-tools-design.md +378 -0
- package/docs/superpowers/specs/2026-03-26-config-backed-credential-registry-design.md +272 -0
- package/docs/superpowers/specs/2026-03-26-openshell-release-prep-design.md +197 -0
- package/examples/opencode-local/opencode.json +19 -0
- package/package.json +33 -0
- package/scripts/openshell.ts +3 -0
- package/scripts/server-registry.ts +3 -0
- package/src/cli/openshell.ts +59 -0
- package/src/cli/server-registry.ts +470 -0
- package/src/core/audit/git-audit-repo.ts +42 -0
- package/src/core/audit/log-store.ts +20 -0
- package/src/core/audit/redact.ts +4 -0
- package/src/core/contracts.ts +51 -0
- package/src/core/orchestrator.ts +1082 -0
- package/src/core/patch.ts +11 -0
- package/src/core/paths.ts +32 -0
- package/src/core/policy.ts +30 -0
- package/src/core/registry/server-registry.ts +505 -0
- package/src/core/result.ts +16 -0
- package/src/core/ssh/ssh-runtime.ts +355 -0
- package/src/index.ts +3 -0
- package/src/opencode/plugin.ts +242 -0
- package/src/product/install.ts +43 -0
- package/src/product/opencode-config.ts +118 -0
- package/src/product/uninstall.ts +47 -0
- package/src/product/workspace-tracker.ts +69 -0
- package/tests/integration/fake-ssh-server.ts +97 -0
- package/tests/integration/install-lifecycle.test.ts +85 -0
- package/tests/integration/orchestrator.test.ts +767 -0
- package/tests/integration/ssh-runtime.test.ts +122 -0
- package/tests/unit/audit.test.ts +221 -0
- package/tests/unit/build-layout.test.ts +28 -0
- package/tests/unit/opencode-config.test.ts +100 -0
- package/tests/unit/opencode-plugin.test.ts +358 -0
- package/tests/unit/openshell-cli.test.ts +60 -0
- package/tests/unit/paths.test.ts +64 -0
- package/tests/unit/plugin-export.test.ts +10 -0
- package/tests/unit/policy.test.ts +53 -0
- package/tests/unit/release-docs.test.ts +31 -0
- package/tests/unit/result.test.ts +28 -0
- package/tests/unit/server-registry-cli.test.ts +673 -0
- package/tests/unit/server-registry.test.ts +452 -0
- package/tests/unit/workspace-tracker.test.ts +57 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,825 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, resolve } from "node:path";
|
|
3
|
+
import { applyUnifiedPatch } from "./patch";
|
|
4
|
+
import { classifyRemoteExec } from "./policy";
|
|
5
|
+
import { errorResult, okResult, partialFailureResult } from "./result";
|
|
6
|
+
const quoteShell = (value) => `'${value.replaceAll("'", `'\"'\"'`)}'`;
|
|
7
|
+
const authError = (tool, server, code, message) => errorResult({
|
|
8
|
+
tool,
|
|
9
|
+
server: server.id,
|
|
10
|
+
code,
|
|
11
|
+
message,
|
|
12
|
+
execution: { attempted: false, completed: false },
|
|
13
|
+
audit: { logWritten: false, snapshotStatus: "not-applicable" },
|
|
14
|
+
});
|
|
15
|
+
const resolveAuthPath = (server, pathValue) => {
|
|
16
|
+
if (isAbsolute(pathValue)) {
|
|
17
|
+
return { path: pathValue };
|
|
18
|
+
}
|
|
19
|
+
if (server.scope !== "workspace" || !server.workspaceRoot) {
|
|
20
|
+
return {
|
|
21
|
+
code: "AUTH_PATH_INVALID",
|
|
22
|
+
message: `Relative auth paths are only allowed for workspace-scoped records: ${pathValue}`,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
path: resolve(server.workspaceRoot, pathValue),
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
const readAuthFile = (server, tool, pathValue, missingCode) => {
|
|
30
|
+
const resolved = resolveAuthPath(server, pathValue);
|
|
31
|
+
if ("code" in resolved) {
|
|
32
|
+
return authError(tool, server, resolved.code, resolved.message);
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
return { content: readFileSync(resolved.path, "utf8") };
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
const errno = error.code;
|
|
39
|
+
if (errno === "ENOENT") {
|
|
40
|
+
return authError(tool, server, missingCode, `Auth file not found: ${resolved.path}`);
|
|
41
|
+
}
|
|
42
|
+
return authError(tool, server, "AUTH_PATH_UNREADABLE", `Auth file is unreadable: ${resolved.path}`);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const toConnectConfig = (tool, server) => {
|
|
46
|
+
const base = {
|
|
47
|
+
host: server.host,
|
|
48
|
+
port: server.port,
|
|
49
|
+
username: server.username,
|
|
50
|
+
};
|
|
51
|
+
switch (server.auth.kind) {
|
|
52
|
+
case "password":
|
|
53
|
+
return {
|
|
54
|
+
...base,
|
|
55
|
+
password: server.auth.secret,
|
|
56
|
+
};
|
|
57
|
+
case "privateKey": {
|
|
58
|
+
const privateKey = readAuthFile(server, tool, server.auth.privateKeyPath, "KEY_PATH_NOT_FOUND");
|
|
59
|
+
if ("status" in privateKey) {
|
|
60
|
+
return privateKey;
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
...base,
|
|
64
|
+
privateKey: privateKey.content,
|
|
65
|
+
...(server.auth.passphrase ? { passphrase: server.auth.passphrase } : {}),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
case "certificate": {
|
|
69
|
+
const certificate = readAuthFile(server, tool, server.auth.certificatePath, "CERTIFICATE_PATH_NOT_FOUND");
|
|
70
|
+
if ("status" in certificate) {
|
|
71
|
+
return certificate;
|
|
72
|
+
}
|
|
73
|
+
const privateKey = readAuthFile(server, tool, server.auth.privateKeyPath, "KEY_PATH_NOT_FOUND");
|
|
74
|
+
if ("status" in privateKey) {
|
|
75
|
+
return privateKey;
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
...base,
|
|
79
|
+
privateKey: privateKey.content,
|
|
80
|
+
...(server.auth.passphrase ? { passphrase: server.auth.passphrase } : {}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
const withAuditFlag = (status, payload, logWritten) => {
|
|
86
|
+
const next = {
|
|
87
|
+
...payload,
|
|
88
|
+
audit: {
|
|
89
|
+
logWritten,
|
|
90
|
+
snapshotStatus: payload.audit?.snapshotStatus ?? "not-applicable",
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
if (status === "ok") {
|
|
94
|
+
return okResult(next);
|
|
95
|
+
}
|
|
96
|
+
if (status === "partial_failure") {
|
|
97
|
+
return partialFailureResult(next);
|
|
98
|
+
}
|
|
99
|
+
return errorResult(next);
|
|
100
|
+
};
|
|
101
|
+
const byteLength = (value) => Buffer.byteLength(value);
|
|
102
|
+
const clampLimit = (value, fallback) => Math.max(1, Math.trunc(value ?? fallback));
|
|
103
|
+
export const createOrchestrator = ({ registry, ssh, audit, policy = { classifyRemoteExec } }) => {
|
|
104
|
+
const appendLogSafe = async (entry) => {
|
|
105
|
+
try {
|
|
106
|
+
await audit.appendLog(entry);
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
const isRegistryValidationError = (error) => typeof error === "object" &&
|
|
114
|
+
error !== null &&
|
|
115
|
+
error.code === "REGISTRY_RECORD_INVALID" &&
|
|
116
|
+
typeof error.message === "string";
|
|
117
|
+
const preflightLog = async (tool, server) => {
|
|
118
|
+
try {
|
|
119
|
+
await audit.preflightLog();
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
return errorResult({
|
|
124
|
+
tool,
|
|
125
|
+
server,
|
|
126
|
+
code: "AUDIT_LOG_PREFLIGHT_FAILED",
|
|
127
|
+
message: error.message,
|
|
128
|
+
execution: { attempted: false, completed: false },
|
|
129
|
+
audit: { logWritten: false, snapshotStatus: "not-applicable" },
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
const logAuthFailure = async (tool, approvalStatus, logEntry, result) => {
|
|
134
|
+
const logWritten = await appendLogSafe({
|
|
135
|
+
...logEntry,
|
|
136
|
+
tool,
|
|
137
|
+
server: result.server,
|
|
138
|
+
approvalStatus,
|
|
139
|
+
code: result.code,
|
|
140
|
+
message: result.message,
|
|
141
|
+
});
|
|
142
|
+
return withAuditFlag("error", result, logWritten);
|
|
143
|
+
};
|
|
144
|
+
const resolveServer = async (tool, serverId, logEntry, approvalStatus) => {
|
|
145
|
+
let server;
|
|
146
|
+
try {
|
|
147
|
+
server = await registry.resolve(serverId);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
if (isRegistryValidationError(error)) {
|
|
151
|
+
const payload = {
|
|
152
|
+
tool,
|
|
153
|
+
server: serverId,
|
|
154
|
+
code: error.code,
|
|
155
|
+
message: error.message,
|
|
156
|
+
execution: { attempted: false, completed: false },
|
|
157
|
+
audit: { logWritten: false, snapshotStatus: "not-applicable" },
|
|
158
|
+
};
|
|
159
|
+
const logWritten = await appendLogSafe({
|
|
160
|
+
...logEntry,
|
|
161
|
+
tool,
|
|
162
|
+
server: serverId,
|
|
163
|
+
approvalStatus,
|
|
164
|
+
code: error.code,
|
|
165
|
+
message: error.message,
|
|
166
|
+
});
|
|
167
|
+
return {
|
|
168
|
+
result: withAuditFlag("error", payload, logWritten),
|
|
169
|
+
server: null,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const payload = {
|
|
173
|
+
tool,
|
|
174
|
+
server: serverId,
|
|
175
|
+
code: "SERVER_RESOLVE_FAILED",
|
|
176
|
+
message: error.message,
|
|
177
|
+
execution: { attempted: false, completed: false },
|
|
178
|
+
audit: { logWritten: false, snapshotStatus: "not-applicable" },
|
|
179
|
+
};
|
|
180
|
+
const logWritten = await appendLogSafe({
|
|
181
|
+
...logEntry,
|
|
182
|
+
tool,
|
|
183
|
+
server: serverId,
|
|
184
|
+
approvalStatus,
|
|
185
|
+
code: "SERVER_RESOLVE_FAILED",
|
|
186
|
+
message: payload.message,
|
|
187
|
+
});
|
|
188
|
+
return {
|
|
189
|
+
result: withAuditFlag("error", payload, logWritten),
|
|
190
|
+
server: null,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
if (!server) {
|
|
194
|
+
const payload = {
|
|
195
|
+
tool,
|
|
196
|
+
server: serverId,
|
|
197
|
+
code: "SERVER_NOT_FOUND",
|
|
198
|
+
execution: { attempted: false, completed: false },
|
|
199
|
+
audit: { logWritten: false, snapshotStatus: "not-applicable" },
|
|
200
|
+
};
|
|
201
|
+
const logWritten = await appendLogSafe({
|
|
202
|
+
...logEntry,
|
|
203
|
+
tool,
|
|
204
|
+
server: serverId,
|
|
205
|
+
approvalStatus,
|
|
206
|
+
code: "SERVER_NOT_FOUND",
|
|
207
|
+
});
|
|
208
|
+
return {
|
|
209
|
+
result: withAuditFlag("error", payload, logWritten),
|
|
210
|
+
server: null,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
result: null,
|
|
215
|
+
server,
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
const listServers = async () => {
|
|
219
|
+
const logReady = await preflightLog("list_servers");
|
|
220
|
+
if (logReady) {
|
|
221
|
+
return logReady;
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
const servers = await registry.list();
|
|
225
|
+
const data = servers.map(({ auth: _auth, workspaceRoot: _workspaceRoot, ...server }) => server);
|
|
226
|
+
const payload = {
|
|
227
|
+
tool: "list_servers",
|
|
228
|
+
data,
|
|
229
|
+
execution: { attempted: true, completed: true },
|
|
230
|
+
audit: { logWritten: false, snapshotStatus: "not-applicable" },
|
|
231
|
+
};
|
|
232
|
+
const logWritten = await appendLogSafe({
|
|
233
|
+
tool: "list_servers",
|
|
234
|
+
approvalStatus: "not-required",
|
|
235
|
+
count: data.length,
|
|
236
|
+
});
|
|
237
|
+
return withAuditFlag("ok", payload, logWritten);
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
if (isRegistryValidationError(error)) {
|
|
241
|
+
const payload = {
|
|
242
|
+
tool: "list_servers",
|
|
243
|
+
code: error.code,
|
|
244
|
+
message: error.message,
|
|
245
|
+
execution: { attempted: false, completed: false },
|
|
246
|
+
audit: { logWritten: false, snapshotStatus: "not-applicable" },
|
|
247
|
+
};
|
|
248
|
+
const logWritten = await appendLogSafe({
|
|
249
|
+
tool: "list_servers",
|
|
250
|
+
approvalStatus: "not-required",
|
|
251
|
+
code: error.code,
|
|
252
|
+
message: payload.message,
|
|
253
|
+
});
|
|
254
|
+
return withAuditFlag("error", payload, logWritten);
|
|
255
|
+
}
|
|
256
|
+
const payload = {
|
|
257
|
+
tool: "list_servers",
|
|
258
|
+
code: "REGISTRY_LIST_FAILED",
|
|
259
|
+
message: error.message,
|
|
260
|
+
execution: { attempted: false, completed: false },
|
|
261
|
+
audit: { logWritten: false, snapshotStatus: "not-applicable" },
|
|
262
|
+
};
|
|
263
|
+
const logWritten = await appendLogSafe({
|
|
264
|
+
tool: "list_servers",
|
|
265
|
+
approvalStatus: "not-required",
|
|
266
|
+
code: "REGISTRY_LIST_FAILED",
|
|
267
|
+
message: payload.message,
|
|
268
|
+
});
|
|
269
|
+
return withAuditFlag("error", payload, logWritten);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
const remoteExec = async (input) => {
|
|
273
|
+
const logReady = await preflightLog("remote_exec", input.server);
|
|
274
|
+
if (logReady) {
|
|
275
|
+
return logReady;
|
|
276
|
+
}
|
|
277
|
+
const classification = policy.classifyRemoteExec(input.command);
|
|
278
|
+
const approvalStatus = classification.decision === "approval-required" ? "host-managed-required" : "not-required";
|
|
279
|
+
const resolved = await resolveServer("remote_exec", input.server, { command: input.command, cwd: input.cwd, timeout: input.timeout }, "unknown");
|
|
280
|
+
if (resolved.result) {
|
|
281
|
+
return resolved.result;
|
|
282
|
+
}
|
|
283
|
+
if (classification.decision === "reject") {
|
|
284
|
+
const payload = {
|
|
285
|
+
tool: "remote_exec",
|
|
286
|
+
server: input.server,
|
|
287
|
+
code: "POLICY_REJECTED",
|
|
288
|
+
message: classification.reason,
|
|
289
|
+
execution: { attempted: false, completed: false },
|
|
290
|
+
audit: { logWritten: false, snapshotStatus: "not-applicable" },
|
|
291
|
+
};
|
|
292
|
+
const logWritten = await appendLogSafe({
|
|
293
|
+
tool: "remote_exec",
|
|
294
|
+
server: input.server,
|
|
295
|
+
command: input.command,
|
|
296
|
+
cwd: input.cwd,
|
|
297
|
+
timeout: input.timeout,
|
|
298
|
+
approvalStatus: "not-required",
|
|
299
|
+
code: "POLICY_REJECTED",
|
|
300
|
+
message: classification.reason,
|
|
301
|
+
});
|
|
302
|
+
return withAuditFlag("error", payload, logWritten);
|
|
303
|
+
}
|
|
304
|
+
try {
|
|
305
|
+
const connection = toConnectConfig("remote_exec", resolved.server);
|
|
306
|
+
if ("status" in connection) {
|
|
307
|
+
return logAuthFailure("remote_exec", approvalStatus, { command: input.command, cwd: input.cwd, timeout: input.timeout }, connection);
|
|
308
|
+
}
|
|
309
|
+
const executed = await ssh.exec(connection, input.command, {
|
|
310
|
+
cwd: input.cwd,
|
|
311
|
+
timeout: input.timeout,
|
|
312
|
+
});
|
|
313
|
+
const payload = {
|
|
314
|
+
tool: "remote_exec",
|
|
315
|
+
server: input.server,
|
|
316
|
+
data: executed,
|
|
317
|
+
execution: {
|
|
318
|
+
attempted: true,
|
|
319
|
+
completed: true,
|
|
320
|
+
exitCode: executed.exitCode,
|
|
321
|
+
stdoutBytes: byteLength(executed.stdout),
|
|
322
|
+
stderrBytes: byteLength(executed.stderr),
|
|
323
|
+
},
|
|
324
|
+
audit: { logWritten: false, snapshotStatus: "not-applicable" },
|
|
325
|
+
};
|
|
326
|
+
const logWritten = await appendLogSafe({
|
|
327
|
+
tool: "remote_exec",
|
|
328
|
+
server: input.server,
|
|
329
|
+
command: input.command,
|
|
330
|
+
cwd: input.cwd,
|
|
331
|
+
timeout: input.timeout,
|
|
332
|
+
approvalStatus,
|
|
333
|
+
policyDecision: classification.decision,
|
|
334
|
+
approvalRequired: classification.decision === "approval-required",
|
|
335
|
+
...executed,
|
|
336
|
+
});
|
|
337
|
+
return withAuditFlag("ok", payload, logWritten);
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
const payload = {
|
|
341
|
+
tool: "remote_exec",
|
|
342
|
+
server: input.server,
|
|
343
|
+
code: "SSH_EXEC_FAILED",
|
|
344
|
+
message: error.message,
|
|
345
|
+
execution: { attempted: true, completed: false },
|
|
346
|
+
audit: { logWritten: false, snapshotStatus: "not-applicable" },
|
|
347
|
+
};
|
|
348
|
+
const logWritten = await appendLogSafe({
|
|
349
|
+
tool: "remote_exec",
|
|
350
|
+
server: input.server,
|
|
351
|
+
command: input.command,
|
|
352
|
+
cwd: input.cwd,
|
|
353
|
+
timeout: input.timeout,
|
|
354
|
+
approvalStatus,
|
|
355
|
+
code: "SSH_EXEC_FAILED",
|
|
356
|
+
message: payload.message,
|
|
357
|
+
});
|
|
358
|
+
return withAuditFlag("error", payload, logWritten);
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
const remoteReadFile = async (input) => {
|
|
362
|
+
const logReady = await preflightLog("remote_read_file", input.server);
|
|
363
|
+
if (logReady) {
|
|
364
|
+
return logReady;
|
|
365
|
+
}
|
|
366
|
+
const resolved = await resolveServer("remote_read_file", input.server, { path: input.path, offset: input.offset, length: input.length }, "not-required");
|
|
367
|
+
if (resolved.result) {
|
|
368
|
+
return resolved.result;
|
|
369
|
+
}
|
|
370
|
+
try {
|
|
371
|
+
const connection = toConnectConfig("remote_read_file", resolved.server);
|
|
372
|
+
if ("status" in connection) {
|
|
373
|
+
return logAuthFailure("remote_read_file", "not-required", { path: input.path, offset: input.offset, length: input.length }, connection);
|
|
374
|
+
}
|
|
375
|
+
const body = await ssh.readFile(connection, input.path);
|
|
376
|
+
const offset = input.offset ?? 0;
|
|
377
|
+
const content = body.slice(offset, input.length ? offset + input.length : undefined);
|
|
378
|
+
const payload = {
|
|
379
|
+
tool: "remote_read_file",
|
|
380
|
+
server: input.server,
|
|
381
|
+
data: { content },
|
|
382
|
+
execution: { attempted: true, completed: true },
|
|
383
|
+
audit: { logWritten: false, snapshotStatus: "not-applicable" },
|
|
384
|
+
};
|
|
385
|
+
const logWritten = await appendLogSafe({
|
|
386
|
+
tool: "remote_read_file",
|
|
387
|
+
server: input.server,
|
|
388
|
+
path: input.path,
|
|
389
|
+
offset: input.offset,
|
|
390
|
+
length: input.length,
|
|
391
|
+
approvalStatus: "not-required",
|
|
392
|
+
});
|
|
393
|
+
return withAuditFlag("ok", payload, logWritten);
|
|
394
|
+
}
|
|
395
|
+
catch (error) {
|
|
396
|
+
const payload = {
|
|
397
|
+
tool: "remote_read_file",
|
|
398
|
+
server: input.server,
|
|
399
|
+
code: "SSH_READ_FAILED",
|
|
400
|
+
message: error.message,
|
|
401
|
+
execution: { attempted: true, completed: false },
|
|
402
|
+
audit: { logWritten: false, snapshotStatus: "not-applicable" },
|
|
403
|
+
};
|
|
404
|
+
const logWritten = await appendLogSafe({
|
|
405
|
+
tool: "remote_read_file",
|
|
406
|
+
server: input.server,
|
|
407
|
+
path: input.path,
|
|
408
|
+
approvalStatus: "not-required",
|
|
409
|
+
code: "SSH_READ_FAILED",
|
|
410
|
+
message: payload.message,
|
|
411
|
+
});
|
|
412
|
+
return withAuditFlag("error", payload, logWritten);
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
const remoteWriteFile = async (input) => {
|
|
416
|
+
const logReady = await preflightLog("remote_write_file", input.server);
|
|
417
|
+
if (logReady) {
|
|
418
|
+
return logReady;
|
|
419
|
+
}
|
|
420
|
+
const resolved = await resolveServer("remote_write_file", input.server, { path: input.path, mode: input.mode }, "host-managed-required");
|
|
421
|
+
if (resolved.result) {
|
|
422
|
+
return resolved.result;
|
|
423
|
+
}
|
|
424
|
+
const connection = toConnectConfig("remote_write_file", resolved.server);
|
|
425
|
+
if ("status" in connection) {
|
|
426
|
+
return logAuthFailure("remote_write_file", "host-managed-required", { path: input.path, mode: input.mode }, connection);
|
|
427
|
+
}
|
|
428
|
+
try {
|
|
429
|
+
await (audit.preflightSnapshots?.() ?? Promise.resolve());
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
const payload = {
|
|
433
|
+
tool: "remote_write_file",
|
|
434
|
+
server: input.server,
|
|
435
|
+
code: "AUDIT_SNAPSHOT_PREFLIGHT_FAILED",
|
|
436
|
+
message: error.message,
|
|
437
|
+
execution: { attempted: false, completed: false },
|
|
438
|
+
audit: { logWritten: false, snapshotStatus: "not-applicable" },
|
|
439
|
+
};
|
|
440
|
+
const logWritten = await appendLogSafe({
|
|
441
|
+
tool: "remote_write_file",
|
|
442
|
+
server: input.server,
|
|
443
|
+
path: input.path,
|
|
444
|
+
mode: input.mode,
|
|
445
|
+
approvalStatus: "host-managed-required",
|
|
446
|
+
code: "AUDIT_SNAPSHOT_PREFLIGHT_FAILED",
|
|
447
|
+
message: payload.message,
|
|
448
|
+
});
|
|
449
|
+
return withAuditFlag("error", payload, logWritten);
|
|
450
|
+
}
|
|
451
|
+
const before = await ssh.readFile(connection, input.path).catch(() => "");
|
|
452
|
+
try {
|
|
453
|
+
await ssh.writeFile(connection, input.path, input.content, input.mode);
|
|
454
|
+
}
|
|
455
|
+
catch (error) {
|
|
456
|
+
const payload = {
|
|
457
|
+
tool: "remote_write_file",
|
|
458
|
+
server: input.server,
|
|
459
|
+
code: "SSH_WRITE_FAILED",
|
|
460
|
+
message: error.message,
|
|
461
|
+
execution: { attempted: true, completed: false },
|
|
462
|
+
audit: { logWritten: false, snapshotStatus: "not-applicable" },
|
|
463
|
+
};
|
|
464
|
+
const logWritten = await appendLogSafe({
|
|
465
|
+
tool: "remote_write_file",
|
|
466
|
+
server: input.server,
|
|
467
|
+
path: input.path,
|
|
468
|
+
mode: input.mode,
|
|
469
|
+
approvalStatus: "host-managed-required",
|
|
470
|
+
approvalRequired: true,
|
|
471
|
+
code: "SSH_WRITE_FAILED",
|
|
472
|
+
message: payload.message,
|
|
473
|
+
});
|
|
474
|
+
return withAuditFlag("error", payload, logWritten);
|
|
475
|
+
}
|
|
476
|
+
const after = await ssh.readFile(connection, input.path).catch(() => input.content);
|
|
477
|
+
const logWritten = await appendLogSafe({
|
|
478
|
+
tool: "remote_write_file",
|
|
479
|
+
server: input.server,
|
|
480
|
+
path: input.path,
|
|
481
|
+
mode: input.mode,
|
|
482
|
+
changedPath: input.path,
|
|
483
|
+
approvalStatus: "host-managed-required",
|
|
484
|
+
approvalRequired: true,
|
|
485
|
+
});
|
|
486
|
+
try {
|
|
487
|
+
await (audit.captureSnapshots?.({ server: input.server, path: input.path, before, after }) ?? Promise.resolve());
|
|
488
|
+
const payload = {
|
|
489
|
+
tool: "remote_write_file",
|
|
490
|
+
server: input.server,
|
|
491
|
+
execution: { attempted: true, completed: true },
|
|
492
|
+
audit: { logWritten: false, snapshotStatus: "written" },
|
|
493
|
+
};
|
|
494
|
+
if (!logWritten) {
|
|
495
|
+
return withAuditFlag("partial_failure", {
|
|
496
|
+
...payload,
|
|
497
|
+
message: "remote write succeeded but audit log write failed",
|
|
498
|
+
audit: { logWritten: false, snapshotStatus: "written" },
|
|
499
|
+
}, false);
|
|
500
|
+
}
|
|
501
|
+
return withAuditFlag("ok", payload, true);
|
|
502
|
+
}
|
|
503
|
+
catch (error) {
|
|
504
|
+
return withAuditFlag("partial_failure", {
|
|
505
|
+
tool: "remote_write_file",
|
|
506
|
+
server: input.server,
|
|
507
|
+
message: `remote write succeeded but audit finalization failed: ${error.message}`,
|
|
508
|
+
execution: { attempted: true, completed: true },
|
|
509
|
+
audit: { logWritten: false, snapshotStatus: "partial-failure" },
|
|
510
|
+
}, logWritten);
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
const remotePatchFile = async (input) => {
|
|
514
|
+
const logReady = await preflightLog("remote_patch_file", input.server);
|
|
515
|
+
if (logReady) {
|
|
516
|
+
return logReady;
|
|
517
|
+
}
|
|
518
|
+
const resolved = await resolveServer("remote_patch_file", input.server, { path: input.path }, "host-managed-required");
|
|
519
|
+
if (resolved.result) {
|
|
520
|
+
return resolved.result;
|
|
521
|
+
}
|
|
522
|
+
const connection = toConnectConfig("remote_patch_file", resolved.server);
|
|
523
|
+
if ("status" in connection) {
|
|
524
|
+
return logAuthFailure("remote_patch_file", "host-managed-required", { path: input.path }, connection);
|
|
525
|
+
}
|
|
526
|
+
let before;
|
|
527
|
+
try {
|
|
528
|
+
before = await ssh.readFile(connection, input.path);
|
|
529
|
+
}
|
|
530
|
+
catch (error) {
|
|
531
|
+
const payload = {
|
|
532
|
+
tool: "remote_patch_file",
|
|
533
|
+
server: input.server,
|
|
534
|
+
code: "SSH_READ_FAILED",
|
|
535
|
+
message: error.message,
|
|
536
|
+
execution: { attempted: true, completed: false },
|
|
537
|
+
audit: { logWritten: false, snapshotStatus: "not-applicable" },
|
|
538
|
+
};
|
|
539
|
+
const logWritten = await appendLogSafe({
|
|
540
|
+
tool: "remote_patch_file",
|
|
541
|
+
server: input.server,
|
|
542
|
+
path: input.path,
|
|
543
|
+
approvalStatus: "host-managed-required",
|
|
544
|
+
code: "SSH_READ_FAILED",
|
|
545
|
+
message: payload.message,
|
|
546
|
+
});
|
|
547
|
+
return withAuditFlag("error", payload, logWritten);
|
|
548
|
+
}
|
|
549
|
+
let after;
|
|
550
|
+
try {
|
|
551
|
+
after = applyUnifiedPatch(before, input.patch);
|
|
552
|
+
}
|
|
553
|
+
catch (error) {
|
|
554
|
+
const payload = {
|
|
555
|
+
tool: "remote_patch_file",
|
|
556
|
+
server: input.server,
|
|
557
|
+
code: "PATCH_APPLY_FAILED",
|
|
558
|
+
message: error.message,
|
|
559
|
+
execution: { attempted: false, completed: false },
|
|
560
|
+
audit: { logWritten: false, snapshotStatus: "not-applicable" },
|
|
561
|
+
};
|
|
562
|
+
const logWritten = await appendLogSafe({
|
|
563
|
+
tool: "remote_patch_file",
|
|
564
|
+
server: input.server,
|
|
565
|
+
path: input.path,
|
|
566
|
+
approvalStatus: "host-managed-required",
|
|
567
|
+
code: "PATCH_APPLY_FAILED",
|
|
568
|
+
message: payload.message,
|
|
569
|
+
});
|
|
570
|
+
return withAuditFlag("error", payload, logWritten);
|
|
571
|
+
}
|
|
572
|
+
try {
|
|
573
|
+
await (audit.preflightSnapshots?.() ?? Promise.resolve());
|
|
574
|
+
}
|
|
575
|
+
catch (error) {
|
|
576
|
+
const payload = {
|
|
577
|
+
tool: "remote_patch_file",
|
|
578
|
+
server: input.server,
|
|
579
|
+
code: "AUDIT_SNAPSHOT_PREFLIGHT_FAILED",
|
|
580
|
+
message: error.message,
|
|
581
|
+
execution: { attempted: false, completed: false },
|
|
582
|
+
audit: { logWritten: false, snapshotStatus: "not-applicable" },
|
|
583
|
+
};
|
|
584
|
+
const logWritten = await appendLogSafe({
|
|
585
|
+
tool: "remote_patch_file",
|
|
586
|
+
server: input.server,
|
|
587
|
+
path: input.path,
|
|
588
|
+
approvalStatus: "host-managed-required",
|
|
589
|
+
code: "AUDIT_SNAPSHOT_PREFLIGHT_FAILED",
|
|
590
|
+
message: payload.message,
|
|
591
|
+
});
|
|
592
|
+
return withAuditFlag("error", payload, logWritten);
|
|
593
|
+
}
|
|
594
|
+
try {
|
|
595
|
+
await ssh.writeFile(connection, input.path, after);
|
|
596
|
+
}
|
|
597
|
+
catch (error) {
|
|
598
|
+
const payload = {
|
|
599
|
+
tool: "remote_patch_file",
|
|
600
|
+
server: input.server,
|
|
601
|
+
code: "SSH_WRITE_FAILED",
|
|
602
|
+
message: error.message,
|
|
603
|
+
execution: { attempted: true, completed: false },
|
|
604
|
+
audit: { logWritten: false, snapshotStatus: "not-applicable" },
|
|
605
|
+
};
|
|
606
|
+
const logWritten = await appendLogSafe({
|
|
607
|
+
tool: "remote_patch_file",
|
|
608
|
+
server: input.server,
|
|
609
|
+
path: input.path,
|
|
610
|
+
approvalStatus: "host-managed-required",
|
|
611
|
+
approvalRequired: true,
|
|
612
|
+
code: "SSH_WRITE_FAILED",
|
|
613
|
+
message: payload.message,
|
|
614
|
+
});
|
|
615
|
+
return withAuditFlag("error", payload, logWritten);
|
|
616
|
+
}
|
|
617
|
+
const logWritten = await appendLogSafe({
|
|
618
|
+
tool: "remote_patch_file",
|
|
619
|
+
server: input.server,
|
|
620
|
+
path: input.path,
|
|
621
|
+
changedPath: input.path,
|
|
622
|
+
approvalStatus: "host-managed-required",
|
|
623
|
+
approvalRequired: true,
|
|
624
|
+
});
|
|
625
|
+
try {
|
|
626
|
+
await (audit.captureSnapshots?.({ server: input.server, path: input.path, before, after }) ?? Promise.resolve());
|
|
627
|
+
const payload = {
|
|
628
|
+
tool: "remote_patch_file",
|
|
629
|
+
server: input.server,
|
|
630
|
+
execution: { attempted: true, completed: true },
|
|
631
|
+
audit: { logWritten: false, snapshotStatus: "written" },
|
|
632
|
+
};
|
|
633
|
+
if (!logWritten) {
|
|
634
|
+
return withAuditFlag("partial_failure", {
|
|
635
|
+
...payload,
|
|
636
|
+
message: "remote patch succeeded but audit log write failed",
|
|
637
|
+
audit: { logWritten: false, snapshotStatus: "written" },
|
|
638
|
+
}, false);
|
|
639
|
+
}
|
|
640
|
+
return withAuditFlag("ok", payload, true);
|
|
641
|
+
}
|
|
642
|
+
catch (error) {
|
|
643
|
+
return withAuditFlag("partial_failure", {
|
|
644
|
+
tool: "remote_patch_file",
|
|
645
|
+
server: input.server,
|
|
646
|
+
message: `remote patch succeeded but audit finalization failed: ${error.message}`,
|
|
647
|
+
execution: { attempted: true, completed: true },
|
|
648
|
+
audit: { logWritten: false, snapshotStatus: "partial-failure" },
|
|
649
|
+
}, logWritten);
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
const remoteListDir = async (input) => {
|
|
653
|
+
const logReady = await preflightLog("remote_list_dir", input.server);
|
|
654
|
+
if (logReady) {
|
|
655
|
+
return logReady;
|
|
656
|
+
}
|
|
657
|
+
const resolved = await resolveServer("remote_list_dir", input.server, { path: input.path, recursive: input.recursive, limit: input.limit }, "not-required");
|
|
658
|
+
if (resolved.result) {
|
|
659
|
+
return resolved.result;
|
|
660
|
+
}
|
|
661
|
+
try {
|
|
662
|
+
const connection = toConnectConfig("remote_list_dir", resolved.server);
|
|
663
|
+
if ("status" in connection) {
|
|
664
|
+
return logAuthFailure("remote_list_dir", "not-required", { path: input.path, recursive: input.recursive ?? false, limit: input.limit ?? 200 }, connection);
|
|
665
|
+
}
|
|
666
|
+
const entries = await ssh.listDir(connection, input.path, input.recursive ?? false, input.limit ?? 200);
|
|
667
|
+
const payload = {
|
|
668
|
+
tool: "remote_list_dir",
|
|
669
|
+
server: input.server,
|
|
670
|
+
data: entries,
|
|
671
|
+
execution: { attempted: true, completed: true },
|
|
672
|
+
audit: { logWritten: false, snapshotStatus: "not-applicable" },
|
|
673
|
+
};
|
|
674
|
+
const logWritten = await appendLogSafe({
|
|
675
|
+
tool: "remote_list_dir",
|
|
676
|
+
server: input.server,
|
|
677
|
+
path: input.path,
|
|
678
|
+
recursive: input.recursive ?? false,
|
|
679
|
+
limit: input.limit ?? 200,
|
|
680
|
+
approvalStatus: "not-required",
|
|
681
|
+
});
|
|
682
|
+
return withAuditFlag("ok", payload, logWritten);
|
|
683
|
+
}
|
|
684
|
+
catch (error) {
|
|
685
|
+
const payload = {
|
|
686
|
+
tool: "remote_list_dir",
|
|
687
|
+
server: input.server,
|
|
688
|
+
code: "SSH_LIST_FAILED",
|
|
689
|
+
message: error.message,
|
|
690
|
+
execution: { attempted: true, completed: false },
|
|
691
|
+
audit: { logWritten: false, snapshotStatus: "not-applicable" },
|
|
692
|
+
};
|
|
693
|
+
const logWritten = await appendLogSafe({
|
|
694
|
+
tool: "remote_list_dir",
|
|
695
|
+
server: input.server,
|
|
696
|
+
path: input.path,
|
|
697
|
+
approvalStatus: "not-required",
|
|
698
|
+
code: "SSH_LIST_FAILED",
|
|
699
|
+
message: payload.message,
|
|
700
|
+
});
|
|
701
|
+
return withAuditFlag("error", payload, logWritten);
|
|
702
|
+
}
|
|
703
|
+
};
|
|
704
|
+
const remoteStat = async (input) => {
|
|
705
|
+
const logReady = await preflightLog("remote_stat", input.server);
|
|
706
|
+
if (logReady) {
|
|
707
|
+
return logReady;
|
|
708
|
+
}
|
|
709
|
+
const resolved = await resolveServer("remote_stat", input.server, { path: input.path }, "not-required");
|
|
710
|
+
if (resolved.result) {
|
|
711
|
+
return resolved.result;
|
|
712
|
+
}
|
|
713
|
+
try {
|
|
714
|
+
const connection = toConnectConfig("remote_stat", resolved.server);
|
|
715
|
+
if ("status" in connection) {
|
|
716
|
+
return logAuthFailure("remote_stat", "not-required", { path: input.path }, connection);
|
|
717
|
+
}
|
|
718
|
+
const stat = await ssh.stat(connection, input.path);
|
|
719
|
+
const payload = {
|
|
720
|
+
tool: "remote_stat",
|
|
721
|
+
server: input.server,
|
|
722
|
+
data: stat,
|
|
723
|
+
execution: { attempted: true, completed: true },
|
|
724
|
+
audit: { logWritten: false, snapshotStatus: "not-applicable" },
|
|
725
|
+
};
|
|
726
|
+
const logWritten = await appendLogSafe({
|
|
727
|
+
tool: "remote_stat",
|
|
728
|
+
server: input.server,
|
|
729
|
+
path: input.path,
|
|
730
|
+
approvalStatus: "not-required",
|
|
731
|
+
});
|
|
732
|
+
return withAuditFlag("ok", payload, logWritten);
|
|
733
|
+
}
|
|
734
|
+
catch (error) {
|
|
735
|
+
const payload = {
|
|
736
|
+
tool: "remote_stat",
|
|
737
|
+
server: input.server,
|
|
738
|
+
code: "SSH_STAT_FAILED",
|
|
739
|
+
message: error.message,
|
|
740
|
+
execution: { attempted: true, completed: false },
|
|
741
|
+
audit: { logWritten: false, snapshotStatus: "not-applicable" },
|
|
742
|
+
};
|
|
743
|
+
const logWritten = await appendLogSafe({
|
|
744
|
+
tool: "remote_stat",
|
|
745
|
+
server: input.server,
|
|
746
|
+
path: input.path,
|
|
747
|
+
approvalStatus: "not-required",
|
|
748
|
+
code: "SSH_STAT_FAILED",
|
|
749
|
+
message: payload.message,
|
|
750
|
+
});
|
|
751
|
+
return withAuditFlag("error", payload, logWritten);
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
const remoteFind = async (input) => {
|
|
755
|
+
const logReady = await preflightLog("remote_find", input.server);
|
|
756
|
+
if (logReady) {
|
|
757
|
+
return logReady;
|
|
758
|
+
}
|
|
759
|
+
const resolved = await resolveServer("remote_find", input.server, { path: input.path, pattern: input.pattern, glob: input.glob, limit: input.limit }, "not-required");
|
|
760
|
+
if (resolved.result) {
|
|
761
|
+
return resolved.result;
|
|
762
|
+
}
|
|
763
|
+
const limit = clampLimit(input.limit, 200);
|
|
764
|
+
const command = input.glob
|
|
765
|
+
? `find ${quoteShell(input.path)} -name ${quoteShell(input.glob)} | head -n ${limit}`
|
|
766
|
+
: `grep -R -n ${quoteShell(input.pattern)} ${quoteShell(input.path)} | head -n ${limit}`;
|
|
767
|
+
try {
|
|
768
|
+
const connection = toConnectConfig("remote_find", resolved.server);
|
|
769
|
+
if ("status" in connection) {
|
|
770
|
+
return logAuthFailure("remote_find", "not-required", { path: input.path, pattern: input.pattern, glob: input.glob, limit: input.limit }, connection);
|
|
771
|
+
}
|
|
772
|
+
const executed = await ssh.exec(connection, command);
|
|
773
|
+
const payload = {
|
|
774
|
+
tool: "remote_find",
|
|
775
|
+
server: input.server,
|
|
776
|
+
data: executed,
|
|
777
|
+
execution: {
|
|
778
|
+
attempted: true,
|
|
779
|
+
completed: true,
|
|
780
|
+
exitCode: executed.exitCode,
|
|
781
|
+
stdoutBytes: byteLength(executed.stdout),
|
|
782
|
+
stderrBytes: byteLength(executed.stderr),
|
|
783
|
+
},
|
|
784
|
+
audit: { logWritten: false, snapshotStatus: "not-applicable" },
|
|
785
|
+
};
|
|
786
|
+
const logWritten = await appendLogSafe({
|
|
787
|
+
tool: "remote_find",
|
|
788
|
+
server: input.server,
|
|
789
|
+
command,
|
|
790
|
+
approvalStatus: "not-required",
|
|
791
|
+
...executed,
|
|
792
|
+
});
|
|
793
|
+
return withAuditFlag("ok", payload, logWritten);
|
|
794
|
+
}
|
|
795
|
+
catch (error) {
|
|
796
|
+
const payload = {
|
|
797
|
+
tool: "remote_find",
|
|
798
|
+
server: input.server,
|
|
799
|
+
code: "SSH_FIND_FAILED",
|
|
800
|
+
message: error.message,
|
|
801
|
+
execution: { attempted: true, completed: false },
|
|
802
|
+
audit: { logWritten: false, snapshotStatus: "not-applicable" },
|
|
803
|
+
};
|
|
804
|
+
const logWritten = await appendLogSafe({
|
|
805
|
+
tool: "remote_find",
|
|
806
|
+
server: input.server,
|
|
807
|
+
command,
|
|
808
|
+
approvalStatus: "not-required",
|
|
809
|
+
code: "SSH_FIND_FAILED",
|
|
810
|
+
message: payload.message,
|
|
811
|
+
});
|
|
812
|
+
return withAuditFlag("error", payload, logWritten);
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
return {
|
|
816
|
+
listServers,
|
|
817
|
+
remoteExec,
|
|
818
|
+
remoteReadFile,
|
|
819
|
+
remoteWriteFile,
|
|
820
|
+
remotePatchFile,
|
|
821
|
+
remoteListDir,
|
|
822
|
+
remoteStat,
|
|
823
|
+
remoteFind,
|
|
824
|
+
};
|
|
825
|
+
};
|