remotelink 0.3.4 → 0.3.8
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/README.md +50 -13
- package/dist/index.js +713 -93
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ copy is required.
|
|
|
27
27
|
Remote MCP endpoint:
|
|
28
28
|
|
|
29
29
|
```text
|
|
30
|
-
https://remotearc.app/mcp
|
|
30
|
+
https://mcp.remotearc.app/mcp
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
Website and device dashboard:
|
|
@@ -39,9 +39,9 @@ https://remotearc.app
|
|
|
39
39
|
## Commands
|
|
40
40
|
|
|
41
41
|
```text
|
|
42
|
-
npx remotelink Connect
|
|
43
|
-
npx remotelink --safe
|
|
44
|
-
npx remotelink --developer
|
|
42
|
+
npx remotelink Connect with dashboard-managed skills
|
|
43
|
+
npx remotelink --safe Hard local read-only cap
|
|
44
|
+
npx remotelink --developer Legacy alias for dashboard-managed capabilities
|
|
45
45
|
npx remotelink --reset Remove local pairing credentials
|
|
46
46
|
npx remotelink --version Show the CLI version
|
|
47
47
|
npx remotelink --help Show help
|
|
@@ -49,16 +49,50 @@ npx remotelink --help Show help
|
|
|
49
49
|
|
|
50
50
|
The package also exposes the aliases `remote-link` and `remote-arc`.
|
|
51
51
|
|
|
52
|
-
##
|
|
52
|
+
## Native execution core
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
Remote Arc no longer shells out to a third-party computer-control MCP server.
|
|
55
|
+
The CLI bundles Remote Arc's own cross-platform execution core for filesystem,
|
|
56
|
+
process, terminal, Local Undo, and Safety Guard behavior.
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
MCP remains the interoperability protocol between AI clients and Remote Arc;
|
|
59
|
+
local OS execution is implemented and versioned by Remote Arc itself.
|
|
59
60
|
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
## Capability model
|
|
62
|
+
|
|
63
|
+
Newly paired devices start with only read-oriented skills enabled:
|
|
64
|
+
directory listing, file reading, file metadata, and process listing.
|
|
65
|
+
|
|
66
|
+
Use the Remote Arc dashboard to enable or disable individual skills per device.
|
|
67
|
+
The Safe preset keeps access read-only, Developer adds file editing, and Full
|
|
68
|
+
adds terminal execution.
|
|
69
|
+
|
|
70
|
+
By default the local CLI exposes the capabilities that the dashboard may grant,
|
|
71
|
+
while the relay enforces the saved per-device policy before forwarding a call.
|
|
72
|
+
Use `--safe` when you want an additional local hard cap that prevents write
|
|
73
|
+
skills from running even if they are enabled in the dashboard.
|
|
74
|
+
|
|
75
|
+
## Local Undo
|
|
76
|
+
|
|
77
|
+
Remote Arc snapshots the previous local file state before supported
|
|
78
|
+
`write_file` and `edit_block` operations. These snapshots stay under
|
|
79
|
+
`~/.remotearc/undo` on the device and are never uploaded to Remote Arc Cloud.
|
|
80
|
+
|
|
81
|
+
The `undo_last_change` skill restores the newest reversible Remote Arc file
|
|
82
|
+
change. Snapshots expire after 7 days, use at most 200 MB in total, and files
|
|
83
|
+
larger than 20 MB are not snapshotted.
|
|
84
|
+
|
|
85
|
+
Local Undo covers Remote Arc file writes only. It cannot reverse external side
|
|
86
|
+
effects such as publishing a package, deploying cloud infrastructure, sending a
|
|
87
|
+
request to another service, or mutating a remote database.
|
|
88
|
+
|
|
89
|
+
## Safety Guard
|
|
90
|
+
|
|
91
|
+
Terminal access stays useful for normal development workflows. The local CLI
|
|
92
|
+
only blocks a narrow set of catastrophic commands such as root/home recursive
|
|
93
|
+
deletion, disk formatting or raw-disk overwrite, fork bombs, and machine
|
|
94
|
+
shutdown/reboot. These checks happen on the device before the command reaches
|
|
95
|
+
the local execution core.
|
|
62
96
|
|
|
63
97
|
## Security
|
|
64
98
|
|
|
@@ -68,9 +102,12 @@ will not forward a tool the device did not advertise.
|
|
|
68
102
|
- MCP access uses OAuth 2.1 + PKCE.
|
|
69
103
|
- Device access is scoped to the authenticated Remote Arc account.
|
|
70
104
|
- You can revoke paired devices from the Remote Arc dashboard.
|
|
105
|
+
- Supported file changes can be rolled back from local-only snapshots.
|
|
71
106
|
|
|
72
|
-
Remote computer control can modify files and execute commands.
|
|
73
|
-
|
|
107
|
+
Remote computer control can modify files and execute commands. Remote Arc
|
|
108
|
+
starts new devices read-only; enable additional skills only when you want the
|
|
109
|
+
connected AI to use them. Full terminal access should be used only on computers
|
|
110
|
+
you control.
|
|
74
111
|
|
|
75
112
|
## Requirements
|
|
76
113
|
|
package/dist/index.js
CHANGED
|
@@ -1,19 +1,453 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import { spawn } from "child_process";
|
|
8
|
-
import
|
|
9
|
-
import { Client } from "@modelcontextprotocol/client";
|
|
10
|
-
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
|
4
|
+
import os2 from "os";
|
|
5
|
+
import path3 from "path";
|
|
6
|
+
import fs3 from "fs/promises";
|
|
7
|
+
import { spawn as spawn2 } from "child_process";
|
|
8
|
+
import process3 from "process";
|
|
11
9
|
import WebSocket from "ws";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
var
|
|
10
|
+
|
|
11
|
+
// ../execution-core/src/filesystem.ts
|
|
12
|
+
import fs from "fs/promises";
|
|
13
|
+
import path from "path";
|
|
14
|
+
var MAX_DIRECTORY_ENTRIES = 2e3;
|
|
15
|
+
var DEFAULT_READ_LINES = 240;
|
|
16
|
+
var MAX_TEXT_FILE_BYTES = 20 * 1024 * 1024;
|
|
17
|
+
var printableSize = (bytes) => {
|
|
18
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
19
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
20
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
21
|
+
};
|
|
22
|
+
async function listDirectory(targetPath, depth = 2) {
|
|
23
|
+
const maxDepth = Math.max(1, Math.min(10, Math.trunc(depth || 2)));
|
|
24
|
+
const lines = [];
|
|
25
|
+
let entriesSeen = 0;
|
|
26
|
+
async function walk(directory, level, prefix) {
|
|
27
|
+
if (entriesSeen >= MAX_DIRECTORY_ENTRIES) return;
|
|
28
|
+
const entries = await fs.readdir(directory, { withFileTypes: true });
|
|
29
|
+
entries.sort((a, b) => {
|
|
30
|
+
if (a.isDirectory() !== b.isDirectory()) return a.isDirectory() ? -1 : 1;
|
|
31
|
+
return a.name.localeCompare(b.name);
|
|
32
|
+
});
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
if (entriesSeen >= MAX_DIRECTORY_ENTRIES) break;
|
|
35
|
+
entriesSeen += 1;
|
|
36
|
+
const absolute = path.join(directory, entry.name);
|
|
37
|
+
const label = entry.isDirectory() ? "[DIR]" : entry.isSymbolicLink() ? "[LINK]" : "[FILE]";
|
|
38
|
+
lines.push(`${prefix}${label} ${entry.name}`);
|
|
39
|
+
if (entry.isDirectory() && level < maxDepth) {
|
|
40
|
+
await walk(absolute, level + 1, prefix + " ");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const stat = await fs.stat(targetPath);
|
|
45
|
+
if (!stat.isDirectory()) throw new Error("Path is not a directory: " + targetPath);
|
|
46
|
+
await walk(targetPath, 1, "");
|
|
47
|
+
if (entriesSeen >= MAX_DIRECTORY_ENTRIES) {
|
|
48
|
+
lines.push(`\u2026 truncated after ${MAX_DIRECTORY_ENTRIES} entries`);
|
|
49
|
+
}
|
|
50
|
+
return `Directory: ${targetPath}
|
|
51
|
+
Depth: ${maxDepth}
|
|
52
|
+
|
|
53
|
+
${lines.join("\n") || "(empty)"}`;
|
|
54
|
+
}
|
|
55
|
+
async function readTextFile(targetPath, offset, length) {
|
|
56
|
+
const stat = await fs.stat(targetPath);
|
|
57
|
+
if (!stat.isFile()) throw new Error("Path is not a file: " + targetPath);
|
|
58
|
+
if (stat.size > MAX_TEXT_FILE_BYTES) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`File is too large for read_file (${printableSize(stat.size)} > 20 MB). Use a terminal command or a more targeted tool instead.`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
const buffer = await fs.readFile(targetPath);
|
|
64
|
+
const sample = buffer.subarray(0, Math.min(buffer.length, 8192));
|
|
65
|
+
if (sample.includes(0)) {
|
|
66
|
+
throw new Error("Binary file detected. read_file currently supports text files only.");
|
|
67
|
+
}
|
|
68
|
+
const text = buffer.toString("utf8");
|
|
69
|
+
const lines = text.split(/\r?\n/);
|
|
70
|
+
const requestedOffset = Number.isFinite(offset) ? Math.trunc(offset) : 0;
|
|
71
|
+
const start = requestedOffset < 0 ? Math.max(0, lines.length + requestedOffset) : Math.min(lines.length, Math.max(0, requestedOffset));
|
|
72
|
+
const requestedLength = Number.isFinite(length) && length > 0 ? Math.trunc(length) : DEFAULT_READ_LINES;
|
|
73
|
+
const end = Math.min(lines.length, start + requestedLength);
|
|
74
|
+
const chunk = lines.slice(start, end).join("\n");
|
|
75
|
+
const location = start === 0 ? "start" : end === lines.length ? `line ${start + 1} to end` : `lines ${start + 1}-${end}`;
|
|
76
|
+
return `[Reading ${end - start} lines from ${location} (total: ${lines.length} lines, ${printableSize(stat.size)})]
|
|
77
|
+
|
|
78
|
+
${chunk}`;
|
|
79
|
+
}
|
|
80
|
+
async function getFileInfo(targetPath) {
|
|
81
|
+
const stat = await fs.lstat(targetPath);
|
|
82
|
+
return {
|
|
83
|
+
path: targetPath,
|
|
84
|
+
type: stat.isFile() ? "file" : stat.isDirectory() ? "directory" : stat.isSymbolicLink() ? "symlink" : "other",
|
|
85
|
+
size: stat.size,
|
|
86
|
+
size_human: printableSize(stat.size),
|
|
87
|
+
mode: stat.mode,
|
|
88
|
+
created_at: stat.birthtime.toISOString(),
|
|
89
|
+
modified_at: stat.mtime.toISOString(),
|
|
90
|
+
accessed_at: stat.atime.toISOString()
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
async function writeTextFile(targetPath, content, mode = "rewrite") {
|
|
94
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
95
|
+
if (mode === "append") {
|
|
96
|
+
await fs.appendFile(targetPath, content, "utf8");
|
|
97
|
+
} else {
|
|
98
|
+
await fs.writeFile(targetPath, content, "utf8");
|
|
99
|
+
}
|
|
100
|
+
const stat = await fs.stat(targetPath);
|
|
101
|
+
return {
|
|
102
|
+
path: targetPath,
|
|
103
|
+
mode,
|
|
104
|
+
bytes: Buffer.byteLength(content),
|
|
105
|
+
file_size: stat.size
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
async function editTextBlock(filePath, oldString, newString, expectedReplacements = 1) {
|
|
109
|
+
if (!oldString) throw new Error("old_string cannot be empty.");
|
|
110
|
+
const current = await fs.readFile(filePath, "utf8");
|
|
111
|
+
const parts = current.split(oldString);
|
|
112
|
+
const replacements = parts.length - 1;
|
|
113
|
+
if (replacements !== expectedReplacements) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`Expected ${expectedReplacements} replacement(s) in ${filePath}, found ${replacements}. No changes were written.`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
const next = parts.join(newString);
|
|
119
|
+
await fs.writeFile(filePath, next, "utf8");
|
|
120
|
+
return {
|
|
121
|
+
path: filePath,
|
|
122
|
+
replacements,
|
|
123
|
+
bytes_before: Buffer.byteLength(current),
|
|
124
|
+
bytes_after: Buffer.byteLength(next)
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ../execution-core/src/process.ts
|
|
129
|
+
import { execFile, spawn } from "child_process";
|
|
130
|
+
import process2 from "process";
|
|
131
|
+
var MAX_CAPTURE_BYTES = 2 * 1024 * 1024;
|
|
132
|
+
function appendCapped(current, chunk) {
|
|
133
|
+
if (Buffer.byteLength(current) >= MAX_CAPTURE_BYTES) return current;
|
|
134
|
+
const next = current + chunk.toString();
|
|
135
|
+
if (Buffer.byteLength(next) <= MAX_CAPTURE_BYTES) return next;
|
|
136
|
+
return next.slice(0, MAX_CAPTURE_BYTES) + "\n\u2026 output truncated \u2026";
|
|
137
|
+
}
|
|
138
|
+
async function listProcesses() {
|
|
139
|
+
const output = await new Promise((resolve, reject) => {
|
|
140
|
+
const command = process2.platform === "win32" ? "tasklist.exe" : "ps";
|
|
141
|
+
const args = process2.platform === "win32" ? ["/FO", "CSV", "/NH"] : ["-axo", "pid,ppid,user,%cpu,%mem,etime,command"];
|
|
142
|
+
execFile(command, args, { maxBuffer: MAX_CAPTURE_BYTES }, (error, stdout, stderr) => {
|
|
143
|
+
if (error) {
|
|
144
|
+
reject(new Error(`Could not list processes: ${stderr || error.message}`));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
resolve(stdout.trim());
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
return `Processes on ${process2.platform}:
|
|
151
|
+
|
|
152
|
+
${output}`;
|
|
153
|
+
}
|
|
154
|
+
async function terminateTree(child) {
|
|
155
|
+
if (!child.pid) return;
|
|
156
|
+
if (process2.platform === "win32") {
|
|
157
|
+
await new Promise((resolve) => {
|
|
158
|
+
const killer = spawn(
|
|
159
|
+
"taskkill.exe",
|
|
160
|
+
["/PID", String(child.pid), "/T", "/F"],
|
|
161
|
+
{ windowsHide: true, stdio: "ignore" }
|
|
162
|
+
);
|
|
163
|
+
killer.once("close", () => resolve());
|
|
164
|
+
killer.once("error", () => resolve());
|
|
165
|
+
});
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
process2.kill(-child.pid, "SIGTERM");
|
|
170
|
+
} catch {
|
|
171
|
+
child.kill("SIGTERM");
|
|
172
|
+
}
|
|
173
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
174
|
+
try {
|
|
175
|
+
process2.kill(-child.pid, "SIGKILL");
|
|
176
|
+
} catch {
|
|
177
|
+
if (!child.killed) child.kill("SIGKILL");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
async function runShellCommand(command, timeoutMs = 5e3) {
|
|
181
|
+
const timeout = Math.max(100, Math.min(12e4, Math.trunc(timeoutMs || 5e3)));
|
|
182
|
+
const startedAt = Date.now();
|
|
183
|
+
return await new Promise((resolve, reject) => {
|
|
184
|
+
const child = spawn(command, {
|
|
185
|
+
shell: true,
|
|
186
|
+
windowsHide: true,
|
|
187
|
+
detached: process2.platform !== "win32",
|
|
188
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
189
|
+
});
|
|
190
|
+
let stdout = "";
|
|
191
|
+
let stderr = "";
|
|
192
|
+
let timedOut = false;
|
|
193
|
+
let settled = false;
|
|
194
|
+
child.stdout?.on("data", (chunk) => {
|
|
195
|
+
stdout = appendCapped(stdout, chunk);
|
|
196
|
+
});
|
|
197
|
+
child.stderr?.on("data", (chunk) => {
|
|
198
|
+
stderr = appendCapped(stderr, chunk);
|
|
199
|
+
});
|
|
200
|
+
const timer = setTimeout(async () => {
|
|
201
|
+
timedOut = true;
|
|
202
|
+
await terminateTree(child);
|
|
203
|
+
}, timeout);
|
|
204
|
+
child.once("error", (error) => {
|
|
205
|
+
if (settled) return;
|
|
206
|
+
settled = true;
|
|
207
|
+
clearTimeout(timer);
|
|
208
|
+
reject(error);
|
|
209
|
+
});
|
|
210
|
+
child.once("close", (code, signal) => {
|
|
211
|
+
if (settled) return;
|
|
212
|
+
settled = true;
|
|
213
|
+
clearTimeout(timer);
|
|
214
|
+
resolve({
|
|
215
|
+
command,
|
|
216
|
+
exit_code: code,
|
|
217
|
+
signal,
|
|
218
|
+
stdout,
|
|
219
|
+
stderr,
|
|
220
|
+
duration_ms: Date.now() - startedAt,
|
|
221
|
+
timed_out: timedOut
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ../execution-core/src/safety.ts
|
|
228
|
+
import crypto from "crypto";
|
|
229
|
+
import { createReadStream } from "fs";
|
|
230
|
+
import fs2 from "fs/promises";
|
|
231
|
+
import os from "os";
|
|
232
|
+
import path2 from "path";
|
|
233
|
+
var RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
234
|
+
var MAX_SNAPSHOT_BYTES = 20 * 1024 * 1024;
|
|
235
|
+
var MAX_UNDO_BYTES = 200 * 1024 * 1024;
|
|
236
|
+
var undoRoot = () => process.env.REMOTEARC_UNDO_ROOT || path2.join(os.homedir(), ".remotearc", "undo");
|
|
237
|
+
async function ensureUndoRoot() {
|
|
238
|
+
await fs2.mkdir(undoRoot(), { recursive: true, mode: 448 });
|
|
239
|
+
}
|
|
240
|
+
async function readManifest(directory) {
|
|
241
|
+
try {
|
|
242
|
+
return JSON.parse(
|
|
243
|
+
await fs2.readFile(path2.join(directory, "manifest.json"), "utf8")
|
|
244
|
+
);
|
|
245
|
+
} catch {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
async function snapshotDirectories() {
|
|
250
|
+
await ensureUndoRoot();
|
|
251
|
+
const entries = await fs2.readdir(undoRoot(), { withFileTypes: true });
|
|
252
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => path2.join(undoRoot(), entry.name));
|
|
253
|
+
}
|
|
254
|
+
async function cleanupUndoStore() {
|
|
255
|
+
const now = Date.now();
|
|
256
|
+
const snapshots = [];
|
|
257
|
+
for (const directory of await snapshotDirectories()) {
|
|
258
|
+
const manifest = await readManifest(directory);
|
|
259
|
+
if (!manifest) {
|
|
260
|
+
await fs2.rm(directory, { recursive: true, force: true }).catch(() => void 0);
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
const created = Date.parse(manifest.createdAt);
|
|
264
|
+
if (!Number.isFinite(created) || now - created > RETENTION_MS) {
|
|
265
|
+
await fs2.rm(directory, { recursive: true, force: true }).catch(() => void 0);
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
snapshots.push({ directory, manifest });
|
|
269
|
+
}
|
|
270
|
+
snapshots.sort(
|
|
271
|
+
(a, b) => Date.parse(a.manifest.createdAt) - Date.parse(b.manifest.createdAt)
|
|
272
|
+
);
|
|
273
|
+
let total = snapshots.reduce((sum, item) => sum + item.manifest.bytes, 0);
|
|
274
|
+
for (const item of snapshots) {
|
|
275
|
+
if (total <= MAX_UNDO_BYTES) break;
|
|
276
|
+
await fs2.rm(item.directory, { recursive: true, force: true }).catch(() => void 0);
|
|
277
|
+
total -= item.manifest.bytes;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function mutationPath(tool, args) {
|
|
281
|
+
const candidate = tool === "write_file" ? args.path : args.file_path;
|
|
282
|
+
return typeof candidate === "string" && candidate.trim() ? candidate : null;
|
|
283
|
+
}
|
|
284
|
+
async function createUndoSnapshot(tool, args) {
|
|
285
|
+
const targetPath = mutationPath(tool, args);
|
|
286
|
+
if (!targetPath) return null;
|
|
287
|
+
await cleanupUndoStore();
|
|
288
|
+
let existed = false;
|
|
289
|
+
let mode;
|
|
290
|
+
let bytes = 0;
|
|
291
|
+
let original = null;
|
|
292
|
+
try {
|
|
293
|
+
const stat = await fs2.stat(targetPath);
|
|
294
|
+
if (!stat.isFile()) return null;
|
|
295
|
+
existed = true;
|
|
296
|
+
mode = stat.mode;
|
|
297
|
+
bytes = stat.size;
|
|
298
|
+
if (bytes > MAX_SNAPSHOT_BYTES) return null;
|
|
299
|
+
original = await fs2.readFile(targetPath);
|
|
300
|
+
} catch (error) {
|
|
301
|
+
const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
|
|
302
|
+
if (code !== "ENOENT") throw error;
|
|
303
|
+
}
|
|
304
|
+
const id = `${Date.now()}-${crypto.randomUUID().slice(0, 8)}`;
|
|
305
|
+
const directory = path2.join(undoRoot(), id);
|
|
306
|
+
await fs2.mkdir(directory, { recursive: true, mode: 448 });
|
|
307
|
+
if (original) {
|
|
308
|
+
await fs2.writeFile(path2.join(directory, "content.bin"), original, { mode: 384 });
|
|
309
|
+
}
|
|
310
|
+
const manifest = {
|
|
311
|
+
id,
|
|
312
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
313
|
+
tool,
|
|
314
|
+
targetPath,
|
|
315
|
+
existed,
|
|
316
|
+
...mode !== void 0 ? { mode } : {},
|
|
317
|
+
bytes
|
|
318
|
+
};
|
|
319
|
+
await fs2.writeFile(
|
|
320
|
+
path2.join(directory, "manifest.json"),
|
|
321
|
+
JSON.stringify(manifest, null, 2) + "\n",
|
|
322
|
+
{ mode: 384 }
|
|
323
|
+
);
|
|
324
|
+
await cleanupUndoStore();
|
|
325
|
+
return { id, directory, manifest };
|
|
326
|
+
}
|
|
327
|
+
async function hashFile(filePath) {
|
|
328
|
+
const hash = crypto.createHash("sha256");
|
|
329
|
+
for await (const chunk of createReadStream(filePath)) hash.update(chunk);
|
|
330
|
+
return hash.digest("hex");
|
|
331
|
+
}
|
|
332
|
+
async function finalizeUndoSnapshot(snapshot) {
|
|
333
|
+
if (!snapshot) return false;
|
|
334
|
+
try {
|
|
335
|
+
snapshot.manifest.postChangeHash = await hashFile(snapshot.manifest.targetPath);
|
|
336
|
+
await fs2.writeFile(
|
|
337
|
+
path2.join(snapshot.directory, "manifest.json"),
|
|
338
|
+
JSON.stringify(snapshot.manifest, null, 2) + "\n",
|
|
339
|
+
{ mode: 384 }
|
|
340
|
+
);
|
|
341
|
+
return true;
|
|
342
|
+
} catch {
|
|
343
|
+
await discardUndoSnapshot(snapshot);
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
async function discardUndoSnapshot(snapshot) {
|
|
348
|
+
if (!snapshot) return;
|
|
349
|
+
await fs2.rm(snapshot.directory, { recursive: true, force: true }).catch(() => void 0);
|
|
350
|
+
}
|
|
351
|
+
async function undoLastChange() {
|
|
352
|
+
const candidates = [];
|
|
353
|
+
for (const directory of await snapshotDirectories()) {
|
|
354
|
+
const manifest = await readManifest(directory);
|
|
355
|
+
if (manifest) candidates.push({ directory, manifest });
|
|
356
|
+
}
|
|
357
|
+
candidates.sort(
|
|
358
|
+
(a, b) => Date.parse(b.manifest.createdAt) - Date.parse(a.manifest.createdAt)
|
|
359
|
+
);
|
|
360
|
+
const latest = candidates[0];
|
|
361
|
+
if (!latest) {
|
|
362
|
+
throw new Error("No reversible Remote Arc file change is available on this device.");
|
|
363
|
+
}
|
|
364
|
+
if (!latest.manifest.postChangeHash) {
|
|
365
|
+
throw new Error(
|
|
366
|
+
"This snapshot predates conflict-safe Local Undo and cannot be restored automatically."
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
let currentHash;
|
|
370
|
+
try {
|
|
371
|
+
currentHash = await hashFile(latest.manifest.targetPath);
|
|
372
|
+
} catch {
|
|
373
|
+
throw new Error(
|
|
374
|
+
"The target file changed or disappeared after the Remote Arc edit. Automatic undo was refused to avoid overwriting newer work."
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
if (currentHash !== latest.manifest.postChangeHash) {
|
|
378
|
+
throw new Error(
|
|
379
|
+
"The target file changed again after the Remote Arc edit. Automatic undo was refused to avoid overwriting newer work."
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
if (latest.manifest.existed) {
|
|
383
|
+
const original = await fs2.readFile(path2.join(latest.directory, "content.bin"));
|
|
384
|
+
await fs2.mkdir(path2.dirname(latest.manifest.targetPath), { recursive: true });
|
|
385
|
+
await fs2.writeFile(latest.manifest.targetPath, original);
|
|
386
|
+
if (latest.manifest.mode !== void 0) {
|
|
387
|
+
await fs2.chmod(latest.manifest.targetPath, latest.manifest.mode).catch(() => void 0);
|
|
388
|
+
}
|
|
389
|
+
} else {
|
|
390
|
+
await fs2.rm(latest.manifest.targetPath, { force: true });
|
|
391
|
+
}
|
|
392
|
+
await fs2.rm(latest.directory, { recursive: true, force: true });
|
|
393
|
+
return {
|
|
394
|
+
restored: true,
|
|
395
|
+
action_id: latest.manifest.id,
|
|
396
|
+
tool: latest.manifest.tool,
|
|
397
|
+
path: latest.manifest.targetPath,
|
|
398
|
+
snapshot_location: "local-device-only"
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
var DESTRUCTIVE_COMMAND_RULES = [
|
|
402
|
+
{
|
|
403
|
+
pattern: /(?:^|[;&|]\s*)(?:sudo\s+)?rm\s+[^\n]*(?:-rf|-fr|-[a-z]*r[a-z]*f)[^\n]*(?:\s\/\s*$|\s\/\*|\s~(?:\s|$)|\s\$HOME(?:\s|$))/i,
|
|
404
|
+
reason: "recursive deletion of a root or home path"
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
pattern: /\b(?:mkfs(?:\.\w+)?|wipefs|fdisk|parted)\b/i,
|
|
408
|
+
reason: "disk or filesystem destructive operation"
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
pattern: /\bdd\b[^\n]*\bof=\/dev\/(?:sd|nvme|vd|xvd|mmcblk)/i,
|
|
412
|
+
reason: "raw disk overwrite"
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
pattern: /\bdiskutil\s+erase(?:disk|volume)\b/i,
|
|
416
|
+
reason: "disk erase"
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
pattern: /\b(?:format(?:\.com)?\s+[a-z]:|diskpart\b|clear-disk\b|initialize-disk\b)/i,
|
|
420
|
+
reason: "Windows disk destructive operation"
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
pattern: /\b(?:shutdown|reboot|halt|poweroff)\b/i,
|
|
424
|
+
reason: "system shutdown or reboot"
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
pattern: /\bremove-item\b[^\n]*(?:-recurse[^\n]*-force|-force[^\n]*-recurse)[^\n]*(?:[a-z]:\\(?:\*|\s|$)|\$env:systemdrive)/i,
|
|
428
|
+
reason: "recursive forced deletion of a drive root"
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
pattern: /\b(?:del|erase|rd|rmdir)\b[^\n]*\/s[^\n]*\/q[^\n]*[a-z]:\\/i,
|
|
432
|
+
reason: "recursive Windows drive deletion"
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
pattern: /:\(\)\s*\{\s*:\|:&\s*\};\s*:/,
|
|
436
|
+
reason: "fork bomb"
|
|
437
|
+
}
|
|
438
|
+
];
|
|
439
|
+
function assertCommandAllowed(command) {
|
|
440
|
+
if (process.env.REMOTEARC_ALLOW_DESTRUCTIVE === "1") return;
|
|
441
|
+
for (const rule of DESTRUCTIVE_COMMAND_RULES) {
|
|
442
|
+
if (rule.pattern.test(command)) {
|
|
443
|
+
throw new Error(
|
|
444
|
+
"Blocked by Remote Arc Safety Guard: " + rule.reason + ". Run this action manually on the device if you intentionally need it."
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// ../execution-core/src/core.ts
|
|
17
451
|
var SAFE_TOOLS = /* @__PURE__ */ new Set([
|
|
18
452
|
"list_directory",
|
|
19
453
|
"read_file",
|
|
@@ -22,12 +456,214 @@ var SAFE_TOOLS = /* @__PURE__ */ new Set([
|
|
|
22
456
|
]);
|
|
23
457
|
var DEVELOPER_TOOLS = /* @__PURE__ */ new Set([
|
|
24
458
|
...SAFE_TOOLS,
|
|
25
|
-
"start_process",
|
|
26
459
|
"write_file",
|
|
27
|
-
"edit_block"
|
|
460
|
+
"edit_block",
|
|
461
|
+
"undo_last_change"
|
|
28
462
|
]);
|
|
463
|
+
var FULL_TOOLS = /* @__PURE__ */ new Set([
|
|
464
|
+
...DEVELOPER_TOOLS,
|
|
465
|
+
"start_process"
|
|
466
|
+
]);
|
|
467
|
+
var DEFINITIONS = [
|
|
468
|
+
{
|
|
469
|
+
name: "list_directory",
|
|
470
|
+
description: "List files and directories on this computer without following directory symlinks.",
|
|
471
|
+
inputSchema: {
|
|
472
|
+
type: "object",
|
|
473
|
+
properties: {
|
|
474
|
+
path: { type: "string" },
|
|
475
|
+
depth: { type: "integer", minimum: 1, maximum: 10, default: 2 }
|
|
476
|
+
},
|
|
477
|
+
required: ["path"],
|
|
478
|
+
additionalProperties: false
|
|
479
|
+
}
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
name: "read_file",
|
|
483
|
+
description: "Read a text file by line range. Negative offsets read from the end.",
|
|
484
|
+
inputSchema: {
|
|
485
|
+
type: "object",
|
|
486
|
+
properties: {
|
|
487
|
+
path: { type: "string" },
|
|
488
|
+
offset: { type: "integer" },
|
|
489
|
+
length: { type: "integer", minimum: 1 }
|
|
490
|
+
},
|
|
491
|
+
required: ["path"],
|
|
492
|
+
additionalProperties: false
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
name: "get_file_info",
|
|
497
|
+
description: "Get local file or directory metadata.",
|
|
498
|
+
inputSchema: {
|
|
499
|
+
type: "object",
|
|
500
|
+
properties: { path: { type: "string" } },
|
|
501
|
+
required: ["path"],
|
|
502
|
+
additionalProperties: false
|
|
503
|
+
}
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
name: "list_processes",
|
|
507
|
+
description: "List processes running on this computer.",
|
|
508
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false }
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
name: "write_file",
|
|
512
|
+
description: "Write or append UTF-8 text. Remote Arc creates a local-only Undo snapshot first.",
|
|
513
|
+
inputSchema: {
|
|
514
|
+
type: "object",
|
|
515
|
+
properties: {
|
|
516
|
+
path: { type: "string" },
|
|
517
|
+
content: { type: "string" },
|
|
518
|
+
mode: { type: "string", enum: ["rewrite", "append"], default: "rewrite" }
|
|
519
|
+
},
|
|
520
|
+
required: ["path", "content"],
|
|
521
|
+
additionalProperties: false
|
|
522
|
+
}
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
name: "edit_block",
|
|
526
|
+
description: "Apply an exact text replacement with conflict-safe Local Undo.",
|
|
527
|
+
inputSchema: {
|
|
528
|
+
type: "object",
|
|
529
|
+
properties: {
|
|
530
|
+
file_path: { type: "string" },
|
|
531
|
+
old_string: { type: "string" },
|
|
532
|
+
new_string: { type: "string" },
|
|
533
|
+
expected_replacements: { type: "integer", minimum: 1, default: 1 }
|
|
534
|
+
},
|
|
535
|
+
required: ["file_path", "old_string", "new_string"],
|
|
536
|
+
additionalProperties: false
|
|
537
|
+
}
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
name: "undo_last_change",
|
|
541
|
+
description: "Undo the newest reversible Remote Arc file edit stored only on this device.",
|
|
542
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false }
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
name: "start_process",
|
|
546
|
+
description: "Run a shell command with timeout and output capture. Catastrophic command patterns are blocked locally.",
|
|
547
|
+
inputSchema: {
|
|
548
|
+
type: "object",
|
|
549
|
+
properties: {
|
|
550
|
+
command: { type: "string" },
|
|
551
|
+
timeout_ms: { type: "integer", minimum: 100, maximum: 12e4, default: 5e3 }
|
|
552
|
+
},
|
|
553
|
+
required: ["command"],
|
|
554
|
+
additionalProperties: false
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
];
|
|
558
|
+
var textResult = (value) => ({
|
|
559
|
+
content: [
|
|
560
|
+
{
|
|
561
|
+
type: "text",
|
|
562
|
+
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
563
|
+
}
|
|
564
|
+
]
|
|
565
|
+
});
|
|
566
|
+
function requiredString(args, key) {
|
|
567
|
+
const value = args[key];
|
|
568
|
+
if (typeof value !== "string") throw new Error(`${key} must be a string.`);
|
|
569
|
+
return value;
|
|
570
|
+
}
|
|
571
|
+
function optionalNumber(args, key) {
|
|
572
|
+
const value = args[key];
|
|
573
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
574
|
+
}
|
|
575
|
+
var RemoteArcExecutionCore = class {
|
|
576
|
+
constructor(mode = "managed") {
|
|
577
|
+
this.mode = mode;
|
|
578
|
+
}
|
|
579
|
+
mode;
|
|
580
|
+
allowedTools() {
|
|
581
|
+
if (this.mode === "safe") return SAFE_TOOLS;
|
|
582
|
+
if (this.mode === "developer") return DEVELOPER_TOOLS;
|
|
583
|
+
return FULL_TOOLS;
|
|
584
|
+
}
|
|
585
|
+
listTools() {
|
|
586
|
+
const allowed = this.allowedTools();
|
|
587
|
+
return DEFINITIONS.filter((tool) => allowed.has(tool.name));
|
|
588
|
+
}
|
|
589
|
+
async callTool(name, args = {}) {
|
|
590
|
+
const toolName = name;
|
|
591
|
+
if (!this.allowedTools().has(toolName)) {
|
|
592
|
+
throw new Error(`Tool blocked by local permission mode: ${name}`);
|
|
593
|
+
}
|
|
594
|
+
switch (toolName) {
|
|
595
|
+
case "list_directory":
|
|
596
|
+
return textResult(
|
|
597
|
+
await listDirectory(
|
|
598
|
+
requiredString(args, "path"),
|
|
599
|
+
optionalNumber(args, "depth") ?? 2
|
|
600
|
+
)
|
|
601
|
+
);
|
|
602
|
+
case "read_file":
|
|
603
|
+
return textResult(
|
|
604
|
+
await readTextFile(
|
|
605
|
+
requiredString(args, "path"),
|
|
606
|
+
optionalNumber(args, "offset"),
|
|
607
|
+
optionalNumber(args, "length")
|
|
608
|
+
)
|
|
609
|
+
);
|
|
610
|
+
case "get_file_info":
|
|
611
|
+
return textResult(await getFileInfo(requiredString(args, "path")));
|
|
612
|
+
case "list_processes":
|
|
613
|
+
return textResult(await listProcesses());
|
|
614
|
+
case "undo_last_change":
|
|
615
|
+
return textResult(await undoLastChange());
|
|
616
|
+
case "start_process": {
|
|
617
|
+
const command = requiredString(args, "command");
|
|
618
|
+
assertCommandAllowed(command);
|
|
619
|
+
return textResult(
|
|
620
|
+
await runShellCommand(
|
|
621
|
+
command,
|
|
622
|
+
optionalNumber(args, "timeout_ms") ?? 5e3
|
|
623
|
+
)
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
case "write_file":
|
|
627
|
+
case "edit_block": {
|
|
628
|
+
const snapshot = await createUndoSnapshot(toolName, args);
|
|
629
|
+
try {
|
|
630
|
+
const result = toolName === "write_file" ? await writeTextFile(
|
|
631
|
+
requiredString(args, "path"),
|
|
632
|
+
requiredString(args, "content"),
|
|
633
|
+
args.mode === "append" ? "append" : "rewrite"
|
|
634
|
+
) : await editTextBlock(
|
|
635
|
+
requiredString(args, "file_path"),
|
|
636
|
+
requiredString(args, "old_string"),
|
|
637
|
+
requiredString(args, "new_string"),
|
|
638
|
+
optionalNumber(args, "expected_replacements") ?? 1
|
|
639
|
+
);
|
|
640
|
+
const undoAvailable = await finalizeUndoSnapshot(snapshot);
|
|
641
|
+
return textResult({
|
|
642
|
+
...result,
|
|
643
|
+
undo_available: undoAvailable,
|
|
644
|
+
undo_storage: undoAvailable ? "local-device-only" : null
|
|
645
|
+
});
|
|
646
|
+
} catch (error) {
|
|
647
|
+
await discardUndoSnapshot(snapshot);
|
|
648
|
+
throw error;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
default:
|
|
652
|
+
throw new Error("Unknown Remote Arc tool: " + name);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
async close() {
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
// src/index.ts
|
|
660
|
+
var VERSION = "0.3.8";
|
|
661
|
+
var DEFAULT_ORIGIN = "https://mcp.remotearc.app";
|
|
662
|
+
var CONFIG_DIR = path3.join(os2.homedir(), ".remotearc");
|
|
663
|
+
var CONFIG_PATH = path3.join(CONFIG_DIR, "config.json");
|
|
664
|
+
var LEGACY_CONFIG_PATH = path3.join(os2.homedir(), ".remote-link", "config.json");
|
|
29
665
|
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
30
|
-
var supportsColor =
|
|
666
|
+
var supportsColor = process3.stdout.isTTY && !process3.env.NO_COLOR;
|
|
31
667
|
var paint = (code, value) => supportsColor ? `\x1B[${code}m${value}\x1B[0m` : value;
|
|
32
668
|
var dim = (value) => paint("2", value);
|
|
33
669
|
var cyan = (value) => paint("36", value);
|
|
@@ -45,28 +681,34 @@ function nowTime() {
|
|
|
45
681
|
}
|
|
46
682
|
function logLine(level, message) {
|
|
47
683
|
const icon = level === "success" ? green("\u2713") : level === "warn" ? yellow("!") : level === "error" ? red("\xD7") : level === "event" ? cyan("\u2192") : cyan("\u2022");
|
|
48
|
-
|
|
684
|
+
process3.stdout.write(`${dim(nowTime())} ${icon} ${message}
|
|
49
685
|
`);
|
|
50
686
|
}
|
|
51
687
|
function banner() {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
688
|
+
process3.stdout.write("\n");
|
|
689
|
+
process3.stdout.write(bold("Remote Arc") + " " + dim(`v${VERSION}`) + "\n");
|
|
690
|
+
process3.stdout.write(dim("Secure remote MCP bridge") + "\n\n");
|
|
55
691
|
}
|
|
56
692
|
function argFlag(name) {
|
|
57
|
-
return
|
|
693
|
+
return process3.argv.includes(name);
|
|
58
694
|
}
|
|
59
695
|
function selectedMode() {
|
|
60
696
|
if (argFlag("--safe")) return "safe";
|
|
61
|
-
return "
|
|
697
|
+
return "managed";
|
|
698
|
+
}
|
|
699
|
+
function isLocalSafeMode(mode) {
|
|
700
|
+
return mode === "safe";
|
|
701
|
+
}
|
|
702
|
+
function modeLabel(mode) {
|
|
703
|
+
return isLocalSafeMode(mode) ? "safe local cap" : "dashboard-managed";
|
|
62
704
|
}
|
|
63
705
|
async function readConfig() {
|
|
64
706
|
try {
|
|
65
|
-
return JSON.parse(await
|
|
707
|
+
return JSON.parse(await fs3.readFile(CONFIG_PATH, "utf8"));
|
|
66
708
|
} catch {
|
|
67
709
|
try {
|
|
68
710
|
const legacy = JSON.parse(
|
|
69
|
-
await
|
|
711
|
+
await fs3.readFile(LEGACY_CONFIG_PATH, "utf8")
|
|
70
712
|
);
|
|
71
713
|
await writeConfig(legacy);
|
|
72
714
|
logLine("success", "Migrated existing Remote Link pairing to Remote Arc.");
|
|
@@ -77,37 +719,37 @@ async function readConfig() {
|
|
|
77
719
|
}
|
|
78
720
|
}
|
|
79
721
|
async function writeConfig(config) {
|
|
80
|
-
await
|
|
81
|
-
await
|
|
722
|
+
await fs3.mkdir(CONFIG_DIR, { recursive: true });
|
|
723
|
+
await fs3.writeFile(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", {
|
|
82
724
|
mode: 384
|
|
83
725
|
});
|
|
84
726
|
}
|
|
85
727
|
async function resetConfig() {
|
|
86
728
|
await Promise.all([
|
|
87
|
-
|
|
88
|
-
|
|
729
|
+
fs3.rm(CONFIG_PATH, { force: true }),
|
|
730
|
+
fs3.rm(LEGACY_CONFIG_PATH, { force: true })
|
|
89
731
|
]);
|
|
90
732
|
logLine("success", "Removed saved device credentials.");
|
|
91
733
|
}
|
|
92
734
|
function openBrowser(url) {
|
|
93
|
-
const command =
|
|
94
|
-
const args =
|
|
95
|
-
const child =
|
|
735
|
+
const command = process3.platform === "win32" ? "cmd" : process3.platform === "darwin" ? "open" : "xdg-open";
|
|
736
|
+
const args = process3.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
737
|
+
const child = spawn2(command, args, {
|
|
96
738
|
detached: true,
|
|
97
739
|
stdio: "ignore"
|
|
98
740
|
});
|
|
99
741
|
child.unref();
|
|
100
742
|
}
|
|
101
743
|
async function pair(origin, mode) {
|
|
102
|
-
const deviceName =
|
|
744
|
+
const deviceName = os2.hostname();
|
|
103
745
|
const response = await fetch(origin + "/api/device/start", {
|
|
104
746
|
method: "POST",
|
|
105
747
|
headers: { "content-type": "application/json" },
|
|
106
748
|
body: JSON.stringify({
|
|
107
749
|
device_name: deviceName,
|
|
108
|
-
platform:
|
|
109
|
-
arch:
|
|
110
|
-
hostname:
|
|
750
|
+
platform: process3.platform,
|
|
751
|
+
arch: process3.arch,
|
|
752
|
+
hostname: os2.hostname()
|
|
111
753
|
})
|
|
112
754
|
});
|
|
113
755
|
if (!response.ok) {
|
|
@@ -115,10 +757,10 @@ async function pair(origin, mode) {
|
|
|
115
757
|
}
|
|
116
758
|
const pairing = await response.json();
|
|
117
759
|
banner();
|
|
118
|
-
logLine("info", `Device: ${bold(deviceName)} \xB7 ${
|
|
760
|
+
logLine("info", `Device: ${bold(deviceName)} \xB7 ${process3.platform}/${process3.arch} \xB7 ${modeLabel(mode)}`);
|
|
119
761
|
logLine("event", "Pairing required \u2014 opening secure browser approval.");
|
|
120
|
-
|
|
121
|
-
|
|
762
|
+
process3.stdout.write("\n Pairing code " + bold(cyan(pairing.user_code)) + "\n");
|
|
763
|
+
process3.stdout.write(" " + dim(pairing.verification_uri_complete) + "\n\n");
|
|
122
764
|
openBrowser(pairing.verification_uri_complete);
|
|
123
765
|
logLine("info", "Waiting for authorization\u2026");
|
|
124
766
|
const startedAt = Date.now();
|
|
@@ -133,7 +775,7 @@ async function pair(origin, mode) {
|
|
|
133
775
|
})
|
|
134
776
|
});
|
|
135
777
|
if (tokenResponse.status === 428) {
|
|
136
|
-
if (
|
|
778
|
+
if (process3.stdout.isTTY) process3.stdout.write(dim("\xB7"));
|
|
137
779
|
continue;
|
|
138
780
|
}
|
|
139
781
|
if (!tokenResponse.ok) {
|
|
@@ -141,7 +783,7 @@ async function pair(origin, mode) {
|
|
|
141
783
|
throw new Error("Pairing failed: " + body);
|
|
142
784
|
}
|
|
143
785
|
const result = await tokenResponse.json();
|
|
144
|
-
|
|
786
|
+
process3.stdout.write("\n");
|
|
145
787
|
logLine("success", "Device authorized.");
|
|
146
788
|
const config = {
|
|
147
789
|
deviceId: result.device_id,
|
|
@@ -156,68 +798,43 @@ async function pair(origin, mode) {
|
|
|
156
798
|
throw new Error("Pairing expired. Run Remote Arc again to retry.");
|
|
157
799
|
}
|
|
158
800
|
var ExecutionCore = class {
|
|
801
|
+
core;
|
|
159
802
|
constructor(mode) {
|
|
160
|
-
this.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
client = null;
|
|
164
|
-
async connect() {
|
|
165
|
-
if (this.client) return this.client;
|
|
166
|
-
const client = new Client({
|
|
167
|
-
name: "remotearc-cli-core",
|
|
168
|
-
version: VERSION
|
|
169
|
-
});
|
|
170
|
-
const command = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
171
|
-
const transport = new StdioClientTransport({
|
|
172
|
-
command,
|
|
173
|
-
args: ["-y", "@wonderwhy-er/desktop-commander@latest"],
|
|
174
|
-
stderr: "inherit"
|
|
175
|
-
});
|
|
176
|
-
logLine("info", "Starting local execution core\u2026");
|
|
177
|
-
await client.connect(transport);
|
|
178
|
-
this.client = client;
|
|
179
|
-
logLine("success", "Local execution core ready.");
|
|
180
|
-
return client;
|
|
803
|
+
this.core = new RemoteArcExecutionCore(
|
|
804
|
+
isLocalSafeMode(mode) ? "safe" : "managed"
|
|
805
|
+
);
|
|
181
806
|
}
|
|
182
807
|
async tools() {
|
|
183
|
-
|
|
184
|
-
const allowed = this.mode === "developer" ? DEVELOPER_TOOLS : SAFE_TOOLS;
|
|
185
|
-
return list.tools.filter((tool) => allowed.has(tool.name));
|
|
808
|
+
return this.core.listTools();
|
|
186
809
|
}
|
|
187
810
|
async call(name, args) {
|
|
188
|
-
|
|
189
|
-
if (!allowed.has(name)) {
|
|
190
|
-
throw new Error("Tool blocked by local permission mode: " + name);
|
|
191
|
-
}
|
|
192
|
-
return (await this.connect()).callTool({
|
|
193
|
-
name,
|
|
194
|
-
arguments: args
|
|
195
|
-
});
|
|
811
|
+
return this.core.callTool(name, args);
|
|
196
812
|
}
|
|
197
813
|
async close() {
|
|
198
|
-
|
|
199
|
-
await this.client.close();
|
|
200
|
-
this.client = null;
|
|
814
|
+
await this.core.close();
|
|
201
815
|
}
|
|
202
816
|
};
|
|
203
817
|
async function connectAgent(config) {
|
|
204
818
|
const core = new ExecutionCore(config.mode);
|
|
205
819
|
banner();
|
|
206
|
-
logLine("info", `Device: ${bold(config.deviceName)} \xB7 ${
|
|
207
|
-
logLine(
|
|
820
|
+
logLine("info", `Device: ${bold(config.deviceName)} \xB7 ${process3.platform}/${process3.arch}`);
|
|
821
|
+
logLine(
|
|
822
|
+
"info",
|
|
823
|
+
`Permission profile: ${isLocalSafeMode(config.mode) ? green("safe local cap") : cyan("dashboard-managed")}`
|
|
824
|
+
);
|
|
208
825
|
const tools = await core.tools();
|
|
209
826
|
logLine("success", `Local tools ready: ${tools.length} exposed`);
|
|
210
|
-
|
|
827
|
+
process3.stdout.write(" " + dim(tools.map((tool) => tool.name).join(" \xB7 ")) + "\n");
|
|
211
828
|
const wsUrl = new URL("/agent", config.origin.replace(/^http/, "ws"));
|
|
212
829
|
let stopped = false;
|
|
213
830
|
let backoff = 1e3;
|
|
214
831
|
const shutdown = async () => {
|
|
215
832
|
stopped = true;
|
|
216
833
|
await core.close().catch(() => void 0);
|
|
217
|
-
|
|
834
|
+
process3.exit(0);
|
|
218
835
|
};
|
|
219
|
-
|
|
220
|
-
|
|
836
|
+
process3.on("SIGINT", shutdown);
|
|
837
|
+
process3.on("SIGTERM", shutdown);
|
|
221
838
|
while (!stopped) {
|
|
222
839
|
logLine("event", `Connecting relay ${dim(wsUrl.host)}\u2026`);
|
|
223
840
|
const ws = new WebSocket(wsUrl, {
|
|
@@ -237,9 +854,9 @@ async function connectAgent(config) {
|
|
|
237
854
|
device: {
|
|
238
855
|
id: config.deviceId,
|
|
239
856
|
name: config.deviceName,
|
|
240
|
-
platform:
|
|
241
|
-
arch:
|
|
242
|
-
hostname:
|
|
857
|
+
platform: process3.platform,
|
|
858
|
+
arch: process3.arch,
|
|
859
|
+
hostname: os2.hostname(),
|
|
243
860
|
agentVersion: VERSION
|
|
244
861
|
},
|
|
245
862
|
tools: tools.map((tool) => tool.name)
|
|
@@ -247,8 +864,8 @@ async function connectAgent(config) {
|
|
|
247
864
|
);
|
|
248
865
|
logLine("success", `Relay connected \xB7 ${bold(config.deviceName)} \xB7 ${config.mode} mode`);
|
|
249
866
|
logLine("success", "Device presence published.");
|
|
250
|
-
|
|
251
|
-
|
|
867
|
+
process3.stdout.write(" " + dim(config.origin) + "\n");
|
|
868
|
+
process3.stdout.write(" " + dim("Ctrl+C to disconnect") + "\n\n");
|
|
252
869
|
const sendHeartbeat = async () => {
|
|
253
870
|
try {
|
|
254
871
|
await fetch(new URL("/api/device/heartbeat", config.origin), {
|
|
@@ -259,7 +876,10 @@ async function connectAgent(config) {
|
|
|
259
876
|
}
|
|
260
877
|
};
|
|
261
878
|
void sendHeartbeat();
|
|
262
|
-
const heartbeatTimer = setInterval(
|
|
879
|
+
const heartbeatTimer = setInterval(
|
|
880
|
+
() => void sendHeartbeat(),
|
|
881
|
+
15 * 6e4
|
|
882
|
+
);
|
|
263
883
|
await new Promise((resolve) => {
|
|
264
884
|
ws.on("message", async (raw) => {
|
|
265
885
|
let message;
|
|
@@ -322,7 +942,7 @@ async function connectAgent(config) {
|
|
|
322
942
|
}
|
|
323
943
|
async function main() {
|
|
324
944
|
if (argFlag("--help") || argFlag("-h")) {
|
|
325
|
-
|
|
945
|
+
process3.stdout.write(
|
|
326
946
|
[
|
|
327
947
|
"Remote Arc",
|
|
328
948
|
"",
|
|
@@ -333,8 +953,8 @@ async function main() {
|
|
|
333
953
|
" npx remotelink",
|
|
334
954
|
"",
|
|
335
955
|
"Options:",
|
|
336
|
-
" --safe
|
|
337
|
-
" --developer
|
|
956
|
+
" --safe Hard local read-only cap; dashboard cannot enable write tools",
|
|
957
|
+
" --developer Legacy alias for dashboard-managed capabilities",
|
|
338
958
|
" --reset Remove this computer's saved pairing",
|
|
339
959
|
" --version Print CLI version",
|
|
340
960
|
" --help Show this help",
|
|
@@ -344,14 +964,14 @@ async function main() {
|
|
|
344
964
|
return;
|
|
345
965
|
}
|
|
346
966
|
if (argFlag("--version") || argFlag("-v")) {
|
|
347
|
-
|
|
967
|
+
process3.stdout.write(VERSION + "\n");
|
|
348
968
|
return;
|
|
349
969
|
}
|
|
350
970
|
if (argFlag("--reset")) {
|
|
351
971
|
await resetConfig();
|
|
352
972
|
return;
|
|
353
973
|
}
|
|
354
|
-
const origin =
|
|
974
|
+
const origin = process3.env.REMOTEARC_ORIGIN || process3.env.REMOTE_LINK_ORIGIN || process3.argv.find((value) => value.startsWith("--origin="))?.slice(9) || DEFAULT_ORIGIN;
|
|
355
975
|
let config = await readConfig();
|
|
356
976
|
if (config && ["https://remote.samyao.me", "https://remotearc.app"].includes(config.origin)) {
|
|
357
977
|
config.origin = DEFAULT_ORIGIN;
|
|
@@ -361,7 +981,7 @@ async function main() {
|
|
|
361
981
|
if (!config) {
|
|
362
982
|
config = await pair(origin, selectedMode());
|
|
363
983
|
} else if (argFlag("--safe") || argFlag("--developer")) {
|
|
364
|
-
config.mode =
|
|
984
|
+
config.mode = argFlag("--safe") ? "safe" : "managed";
|
|
365
985
|
await writeConfig(config);
|
|
366
986
|
}
|
|
367
987
|
if (config) {
|
|
@@ -374,6 +994,6 @@ main().catch((error) => {
|
|
|
374
994
|
"error",
|
|
375
995
|
"Fatal: " + (error instanceof Error ? error.message : String(error))
|
|
376
996
|
);
|
|
377
|
-
|
|
997
|
+
process3.exit(1);
|
|
378
998
|
});
|
|
379
999
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport fs from \"node:fs/promises\";\nimport { spawn } from \"node:child_process\";\nimport process from \"node:process\";\nimport { Client } from \"@modelcontextprotocol/client\";\nimport { StdioClientTransport } from \"@modelcontextprotocol/client/stdio\";\nimport WebSocket from \"ws\";\n\nconst VERSION = \"0.3.4\";\nconst DEFAULT_ORIGIN = \"https://mcp.remotearc.app\";\nconst CONFIG_DIR = path.join(os.homedir(), \".remotearc\");\nconst CONFIG_PATH = path.join(CONFIG_DIR, \"config.json\");\nconst LEGACY_CONFIG_PATH = path.join(os.homedir(), \".remote-link\", \"config.json\");\n\ntype Mode = \"safe\" | \"developer\";\n\ntype SavedConfig = {\n deviceId: string;\n deviceToken: string;\n deviceName: string;\n origin: string;\n mode: Mode;\n};\n\ntype PairingStart = {\n device_code: string;\n device_secret: string;\n user_code: string;\n verification_uri: string;\n verification_uri_complete: string;\n expires_in: number;\n interval: number;\n};\n\ntype PairingToken = {\n device_id: string;\n device_token: string;\n relay_url: string;\n};\n\nconst SAFE_TOOLS = new Set([\n \"list_directory\",\n \"read_file\",\n \"get_file_info\",\n \"list_processes\",\n]);\n\nconst DEVELOPER_TOOLS = new Set([\n ...SAFE_TOOLS,\n \"start_process\",\n \"write_file\",\n \"edit_block\",\n]);\n\nconst sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));\n\nconst supportsColor = process.stdout.isTTY && !process.env.NO_COLOR;\nconst paint = (code: string, value: string) =>\n supportsColor ? `\\x1b[${code}m${value}\\x1b[0m` : value;\nconst dim = (value: string) => paint(\"2\", value);\nconst cyan = (value: string) => paint(\"36\", value);\nconst green = (value: string) => paint(\"32\", value);\nconst yellow = (value: string) => paint(\"33\", value);\nconst red = (value: string) => paint(\"31\", value);\nconst bold = (value: string) => paint(\"1\", value);\n\nfunction nowTime() {\n return new Date().toLocaleTimeString([], {\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n hour12: false,\n });\n}\n\nfunction logLine(\n level: \"info\" | \"success\" | \"warn\" | \"error\" | \"event\",\n message: string,\n) {\n const icon =\n level === \"success\" ? green(\"✓\") :\n level === \"warn\" ? yellow(\"!\") :\n level === \"error\" ? red(\"×\") :\n level === \"event\" ? cyan(\"→\") :\n cyan(\"•\");\n process.stdout.write(`${dim(nowTime())} ${icon} ${message}\\n`);\n}\n\nfunction banner() {\n process.stdout.write(\"\\n\");\n process.stdout.write(bold(\"Remote Arc\") + \" \" + dim(`v${VERSION}`) + \"\\n\");\n process.stdout.write(dim(\"Secure remote MCP bridge\") + \"\\n\\n\");\n}\n\nfunction argFlag(name: string) {\n return process.argv.includes(name);\n}\n\nfunction selectedMode(): Mode {\n if (argFlag(\"--safe\")) return \"safe\";\n return \"developer\";\n}\n\nasync function readConfig(): Promise<SavedConfig | null> {\n try {\n return JSON.parse(await fs.readFile(CONFIG_PATH, \"utf8\")) as SavedConfig;\n } catch {\n try {\n const legacy = JSON.parse(\n await fs.readFile(LEGACY_CONFIG_PATH, \"utf8\"),\n ) as SavedConfig;\n await writeConfig(legacy);\n logLine(\"success\", \"Migrated existing Remote Link pairing to Remote Arc.\");\n return legacy;\n } catch {\n return null;\n }\n }\n}\n\nasync function writeConfig(config: SavedConfig) {\n await fs.mkdir(CONFIG_DIR, { recursive: true });\n await fs.writeFile(CONFIG_PATH, JSON.stringify(config, null, 2) + \"\\n\", {\n mode: 0o600,\n });\n}\n\nasync function resetConfig() {\n await Promise.all([\n fs.rm(CONFIG_PATH, { force: true }),\n fs.rm(LEGACY_CONFIG_PATH, { force: true }),\n ]);\n logLine(\"success\", \"Removed saved device credentials.\");\n}\n\nfunction openBrowser(url: string) {\n const command =\n process.platform === \"win32\"\n ? \"cmd\"\n : process.platform === \"darwin\"\n ? \"open\"\n : \"xdg-open\";\n const args =\n process.platform === \"win32\"\n ? [\"/c\", \"start\", \"\", url]\n : [url];\n\n const child = spawn(command, args, {\n detached: true,\n stdio: \"ignore\",\n });\n child.unref();\n}\n\nasync function pair(origin: string, mode: Mode): Promise<SavedConfig> {\n const deviceName = os.hostname();\n const response = await fetch(origin + \"/api/device/start\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n device_name: deviceName,\n platform: process.platform,\n arch: process.arch,\n hostname: os.hostname(),\n }),\n });\n\n if (!response.ok) {\n throw new Error(\"Could not start device pairing: \" + response.status);\n }\n\n const pairing = (await response.json()) as PairingStart;\n\n banner();\n logLine(\"info\", `Device: ${bold(deviceName)} · ${process.platform}/${process.arch} · ${mode} mode`);\n logLine(\"event\", \"Pairing required — opening secure browser approval.\");\n process.stdout.write(\"\\n Pairing code \" + bold(cyan(pairing.user_code)) + \"\\n\");\n process.stdout.write(\" \" + dim(pairing.verification_uri_complete) + \"\\n\\n\");\n openBrowser(pairing.verification_uri_complete);\n logLine(\"info\", \"Waiting for authorization…\");\n\n const startedAt = Date.now();\n while (Date.now() - startedAt < pairing.expires_in * 1000) {\n await sleep(Math.max(pairing.interval, 2) * 1000);\n\n const tokenResponse = await fetch(origin + \"/api/device/token\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n device_code: pairing.device_code,\n device_secret: pairing.device_secret,\n }),\n });\n\n if (tokenResponse.status === 428) {\n if (process.stdout.isTTY) process.stdout.write(dim(\"·\"));\n continue;\n }\n\n if (!tokenResponse.ok) {\n const body = await tokenResponse.text();\n throw new Error(\"Pairing failed: \" + body);\n }\n\n const result = (await tokenResponse.json()) as PairingToken;\n process.stdout.write(\"\\n\");\n logLine(\"success\", \"Device authorized.\");\n\n const config: SavedConfig = {\n deviceId: result.device_id,\n deviceToken: result.device_token,\n deviceName,\n origin,\n mode,\n };\n await writeConfig(config);\n return config;\n }\n\n throw new Error(\"Pairing expired. Run Remote Arc again to retry.\");\n}\n\nclass ExecutionCore {\n private client: Client | null = null;\n\n constructor(private readonly mode: Mode) {}\n\n async connect() {\n if (this.client) return this.client;\n\n const client = new Client({\n name: \"remotearc-cli-core\",\n version: VERSION,\n });\n\n const command = process.platform === \"win32\" ? \"npx.cmd\" : \"npx\";\n const transport = new StdioClientTransport({\n command,\n args: [\"-y\", \"@wonderwhy-er/desktop-commander@latest\"],\n stderr: \"inherit\",\n });\n\n logLine(\"info\", \"Starting local execution core…\");\n await client.connect(transport);\n this.client = client;\n logLine(\"success\", \"Local execution core ready.\");\n return client;\n }\n\n async tools() {\n const list = await (await this.connect()).listTools();\n const allowed = this.mode === \"developer\" ? DEVELOPER_TOOLS : SAFE_TOOLS;\n return list.tools.filter((tool) => allowed.has(tool.name));\n }\n\n async call(name: string, args: Record<string, unknown>) {\n const allowed = this.mode === \"developer\" ? DEVELOPER_TOOLS : SAFE_TOOLS;\n if (!allowed.has(name)) {\n throw new Error(\"Tool blocked by local permission mode: \" + name);\n }\n\n return (await this.connect()).callTool({\n name,\n arguments: args,\n });\n }\n\n async close() {\n if (!this.client) return;\n await this.client.close();\n this.client = null;\n }\n}\n\nasync function connectAgent(config: SavedConfig) {\n const core = new ExecutionCore(config.mode);\n banner();\n logLine(\"info\", `Device: ${bold(config.deviceName)} · ${process.platform}/${process.arch}`);\n logLine(\"info\", `Permission profile: ${config.mode === \"developer\" ? yellow(\"developer\") : green(\"safe\")}`);\n\n const tools = await core.tools();\n logLine(\"success\", `Local tools ready: ${tools.length} exposed`);\n process.stdout.write(\" \" + dim(tools.map((tool) => tool.name).join(\" · \")) + \"\\n\");\n const wsUrl = new URL(\"/agent\", config.origin.replace(/^http/, \"ws\"));\n\n let stopped = false;\n let backoff = 1000;\n\n const shutdown = async () => {\n stopped = true;\n await core.close().catch(() => undefined);\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n while (!stopped) {\n logLine(\"event\", `Connecting relay ${dim(wsUrl.host)}…`);\n const ws = new WebSocket(wsUrl, {\n headers: {\n Authorization: \"Bearer \" + config.deviceToken,\n },\n });\n\n try {\n await new Promise<void>((resolve, reject) => {\n ws.once(\"open\", resolve);\n ws.once(\"error\", reject);\n });\n\n backoff = 1000;\n ws.send(\n JSON.stringify({\n type: \"hello\",\n device: {\n id: config.deviceId,\n name: config.deviceName,\n platform: process.platform,\n arch: process.arch,\n hostname: os.hostname(),\n agentVersion: VERSION,\n },\n tools: tools.map((tool) => tool.name),\n }),\n );\n\n logLine(\"success\", `Relay connected · ${bold(config.deviceName)} · ${config.mode} mode`);\n logLine(\"success\", \"Device presence published.\");\n process.stdout.write(\" \" + dim(config.origin) + \"\\n\");\n process.stdout.write(\" \" + dim(\"Ctrl+C to disconnect\") + \"\\n\\n\");\n\n const sendHeartbeat = async () => {\n try {\n await fetch(new URL(\"/api/device/heartbeat\", config.origin), {\n method: \"POST\",\n headers: { Authorization: \"Bearer \" + config.deviceToken },\n });\n } catch {\n // WebSocket reconnect logic remains the source of truth for connectivity.\n }\n };\n void sendHeartbeat();\n const heartbeatTimer = setInterval(() => void sendHeartbeat(), 60_000);\n\n await new Promise<void>((resolve) => {\n ws.on(\"message\", async (raw) => {\n let message: {\n type?: string;\n id?: string;\n tool?: string;\n arguments?: Record<string, unknown>;\n };\n\n try {\n message = JSON.parse(raw.toString());\n } catch {\n return;\n }\n\n if (\n message.type !== \"call\" ||\n !message.id ||\n !message.tool\n ) {\n return;\n }\n\n const callStarted = Date.now();\n logLine(\"event\", `tool.call ${bold(message.tool)} · ${dim(message.id.slice(0, 8))}`);\n try {\n const result = await core.call(\n message.tool,\n message.arguments || {},\n );\n logLine(\"success\", `tool.done ${message.tool} · ${Date.now() - callStarted}ms`);\n ws.send(\n JSON.stringify({\n type: \"result\",\n id: message.id,\n result,\n }),\n );\n } catch (error) {\n logLine(\"error\", `tool.fail ${message.tool} · ${error instanceof Error ? error.message : String(error)}`);\n ws.send(\n JSON.stringify({\n type: \"result\",\n id: message.id,\n error: error instanceof Error ? error.message : String(error),\n }),\n );\n }\n });\n\n ws.once(\"close\", resolve);\n ws.once(\"error\", resolve);\n });\n clearInterval(heartbeatTimer);\n } catch (error) {\n logLine(\n \"error\",\n \"Relay connection failed: \" +\n (error instanceof Error ? error.message : String(error)),\n );\n } finally {\n ws.removeAllListeners();\n ws.close();\n }\n\n if (!stopped) {\n logLine(\n \"warn\",\n \"Relay disconnected · retrying in \" + Math.round(backoff / 1000) + \"s\",\n );\n await sleep(backoff);\n backoff = Math.min(backoff * 2, 30_000);\n }\n }\n}\n\nasync function main() {\n if (argFlag(\"--help\") || argFlag(\"-h\")) {\n process.stdout.write(\n [\n \"Remote Arc\",\n \"\",\n \"Usage:\",\n \" npx remotelink\",\n \"\",\n \"After the npm release:\",\n \" npx remotelink\",\n \"\",\n \"Options:\",\n \" --safe Read-only local capability mode\",\n \" --developer Read/write/shell mode (default)\",\n \" --reset Remove this computer's saved pairing\",\n \" --version Print CLI version\",\n \" --help Show this help\",\n \"\",\n ].join(\"\\n\"),\n );\n return;\n }\n\n if (argFlag(\"--version\") || argFlag(\"-v\")) {\n process.stdout.write(VERSION + \"\\n\");\n return;\n }\n\n if (argFlag(\"--reset\")) {\n await resetConfig();\n return;\n }\n\n const origin =\n process.env.REMOTEARC_ORIGIN || process.env.REMOTE_LINK_ORIGIN ||\n process.argv.find((value) => value.startsWith(\"--origin=\"))?.slice(9) ||\n DEFAULT_ORIGIN;\n\n let config = await readConfig();\n if (config && [\"https://remote.samyao.me\", \"https://remotearc.app\"].includes(config.origin)) {\n config.origin = DEFAULT_ORIGIN;\n await writeConfig(config);\n logLine(\"info\", \"Migrated relay origin to \" + DEFAULT_ORIGIN);\n }\n if (!config) {\n config = await pair(origin, selectedMode());\n } else if (argFlag(\"--safe\") || argFlag(\"--developer\")) {\n config.mode = selectedMode();\n await writeConfig(config);\n }\n\n if (config) {\n logLine(\"info\", `Using paired device identity ${dim(config.deviceId.slice(0, 8))}…`);\n }\n await connectAgent(config);\n}\n\nmain().catch((error) => {\n logLine(\n \"error\",\n \"Fatal: \" + (error instanceof Error ? error.message : String(error)),\n );\n process.exit(1);\n});\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../../execution-core/src/filesystem.ts","../../execution-core/src/process.ts","../../execution-core/src/safety.ts","../../execution-core/src/core.ts"],"sourcesContent":["#!/usr/bin/env node\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport fs from \"node:fs/promises\";\nimport { spawn } from \"node:child_process\";\nimport process from \"node:process\";\nimport WebSocket from \"ws\";\nimport { RemoteArcExecutionCore } from \"@remotearc/execution-core\";\n\nconst VERSION = \"0.3.8\";\nconst DEFAULT_ORIGIN = \"https://mcp.remotearc.app\";\nconst CONFIG_DIR = path.join(os.homedir(), \".remotearc\");\nconst CONFIG_PATH = path.join(CONFIG_DIR, \"config.json\");\nconst LEGACY_CONFIG_PATH = path.join(os.homedir(), \".remote-link\", \"config.json\");\n\ntype Mode = \"managed\" | \"safe\" | \"developer\";\n\ntype SavedConfig = {\n deviceId: string;\n deviceToken: string;\n deviceName: string;\n origin: string;\n mode: Mode;\n};\n\ntype PairingStart = {\n device_code: string;\n device_secret: string;\n user_code: string;\n verification_uri: string;\n verification_uri_complete: string;\n expires_in: number;\n interval: number;\n};\n\ntype PairingToken = {\n device_id: string;\n device_token: string;\n relay_url: string;\n};\n\nconst sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));\n\nconst supportsColor = process.stdout.isTTY && !process.env.NO_COLOR;\nconst paint = (code: string, value: string) =>\n supportsColor ? `\\x1b[${code}m${value}\\x1b[0m` : value;\nconst dim = (value: string) => paint(\"2\", value);\nconst cyan = (value: string) => paint(\"36\", value);\nconst green = (value: string) => paint(\"32\", value);\nconst yellow = (value: string) => paint(\"33\", value);\nconst red = (value: string) => paint(\"31\", value);\nconst bold = (value: string) => paint(\"1\", value);\n\nfunction nowTime() {\n return new Date().toLocaleTimeString([], {\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n hour12: false,\n });\n}\n\nfunction logLine(\n level: \"info\" | \"success\" | \"warn\" | \"error\" | \"event\",\n message: string,\n) {\n const icon =\n level === \"success\" ? green(\"✓\") :\n level === \"warn\" ? yellow(\"!\") :\n level === \"error\" ? red(\"×\") :\n level === \"event\" ? cyan(\"→\") :\n cyan(\"•\");\n process.stdout.write(`${dim(nowTime())} ${icon} ${message}\\n`);\n}\n\nfunction banner() {\n process.stdout.write(\"\\n\");\n process.stdout.write(bold(\"Remote Arc\") + \" \" + dim(`v${VERSION}`) + \"\\n\");\n process.stdout.write(dim(\"Secure remote MCP bridge\") + \"\\n\\n\");\n}\n\nfunction argFlag(name: string) {\n return process.argv.includes(name);\n}\n\nfunction selectedMode(): Mode {\n if (argFlag(\"--safe\")) return \"safe\";\n return \"managed\";\n}\n\nfunction isLocalSafeMode(mode: Mode) {\n return mode === \"safe\";\n}\n\nfunction modeLabel(mode: Mode) {\n return isLocalSafeMode(mode) ? \"safe local cap\" : \"dashboard-managed\";\n}\n\nasync function readConfig(): Promise<SavedConfig | null> {\n try {\n return JSON.parse(await fs.readFile(CONFIG_PATH, \"utf8\")) as SavedConfig;\n } catch {\n try {\n const legacy = JSON.parse(\n await fs.readFile(LEGACY_CONFIG_PATH, \"utf8\"),\n ) as SavedConfig;\n await writeConfig(legacy);\n logLine(\"success\", \"Migrated existing Remote Link pairing to Remote Arc.\");\n return legacy;\n } catch {\n return null;\n }\n }\n}\n\nasync function writeConfig(config: SavedConfig) {\n await fs.mkdir(CONFIG_DIR, { recursive: true });\n await fs.writeFile(CONFIG_PATH, JSON.stringify(config, null, 2) + \"\\n\", {\n mode: 0o600,\n });\n}\n\nasync function resetConfig() {\n await Promise.all([\n fs.rm(CONFIG_PATH, { force: true }),\n fs.rm(LEGACY_CONFIG_PATH, { force: true }),\n ]);\n logLine(\"success\", \"Removed saved device credentials.\");\n}\n\nfunction openBrowser(url: string) {\n const command =\n process.platform === \"win32\"\n ? \"cmd\"\n : process.platform === \"darwin\"\n ? \"open\"\n : \"xdg-open\";\n const args =\n process.platform === \"win32\"\n ? [\"/c\", \"start\", \"\", url]\n : [url];\n\n const child = spawn(command, args, {\n detached: true,\n stdio: \"ignore\",\n });\n child.unref();\n}\n\nasync function pair(origin: string, mode: Mode): Promise<SavedConfig> {\n const deviceName = os.hostname();\n const response = await fetch(origin + \"/api/device/start\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n device_name: deviceName,\n platform: process.platform,\n arch: process.arch,\n hostname: os.hostname(),\n }),\n });\n\n if (!response.ok) {\n throw new Error(\"Could not start device pairing: \" + response.status);\n }\n\n const pairing = (await response.json()) as PairingStart;\n\n banner();\n logLine(\"info\", `Device: ${bold(deviceName)} · ${process.platform}/${process.arch} · ${modeLabel(mode)}`);\n logLine(\"event\", \"Pairing required — opening secure browser approval.\");\n process.stdout.write(\"\\n Pairing code \" + bold(cyan(pairing.user_code)) + \"\\n\");\n process.stdout.write(\" \" + dim(pairing.verification_uri_complete) + \"\\n\\n\");\n openBrowser(pairing.verification_uri_complete);\n logLine(\"info\", \"Waiting for authorization…\");\n\n const startedAt = Date.now();\n while (Date.now() - startedAt < pairing.expires_in * 1000) {\n await sleep(Math.max(pairing.interval, 2) * 1000);\n\n const tokenResponse = await fetch(origin + \"/api/device/token\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n device_code: pairing.device_code,\n device_secret: pairing.device_secret,\n }),\n });\n\n if (tokenResponse.status === 428) {\n if (process.stdout.isTTY) process.stdout.write(dim(\"·\"));\n continue;\n }\n\n if (!tokenResponse.ok) {\n const body = await tokenResponse.text();\n throw new Error(\"Pairing failed: \" + body);\n }\n\n const result = (await tokenResponse.json()) as PairingToken;\n process.stdout.write(\"\\n\");\n logLine(\"success\", \"Device authorized.\");\n\n const config: SavedConfig = {\n deviceId: result.device_id,\n deviceToken: result.device_token,\n deviceName,\n origin,\n mode,\n };\n await writeConfig(config);\n return config;\n }\n\n throw new Error(\"Pairing expired. Run Remote Arc again to retry.\");\n}\n\nclass ExecutionCore {\n private readonly core: RemoteArcExecutionCore;\n\n constructor(mode: Mode) {\n this.core = new RemoteArcExecutionCore(\n isLocalSafeMode(mode) ? \"safe\" : \"managed\",\n );\n }\n\n async tools() {\n return this.core.listTools();\n }\n\n async call(name: string, args: Record<string, unknown>) {\n return this.core.callTool(name, args);\n }\n\n async close() {\n await this.core.close();\n }\n}\n\nasync function connectAgent(config: SavedConfig) {\n const core = new ExecutionCore(config.mode);\n banner();\n logLine(\"info\", `Device: ${bold(config.deviceName)} · ${process.platform}/${process.arch}`);\n logLine(\n \"info\",\n `Permission profile: ${isLocalSafeMode(config.mode) ? green(\"safe local cap\") : cyan(\"dashboard-managed\")}`,\n );\n\n const tools = await core.tools();\n logLine(\"success\", `Local tools ready: ${tools.length} exposed`);\n process.stdout.write(\" \" + dim(tools.map((tool) => tool.name).join(\" · \")) + \"\\n\");\n const wsUrl = new URL(\"/agent\", config.origin.replace(/^http/, \"ws\"));\n\n let stopped = false;\n let backoff = 1000;\n\n const shutdown = async () => {\n stopped = true;\n await core.close().catch(() => undefined);\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n while (!stopped) {\n logLine(\"event\", `Connecting relay ${dim(wsUrl.host)}…`);\n const ws = new WebSocket(wsUrl, {\n headers: {\n Authorization: \"Bearer \" + config.deviceToken,\n },\n });\n\n try {\n await new Promise<void>((resolve, reject) => {\n ws.once(\"open\", resolve);\n ws.once(\"error\", reject);\n });\n\n backoff = 1000;\n ws.send(\n JSON.stringify({\n type: \"hello\",\n device: {\n id: config.deviceId,\n name: config.deviceName,\n platform: process.platform,\n arch: process.arch,\n hostname: os.hostname(),\n agentVersion: VERSION,\n },\n tools: tools.map((tool) => tool.name),\n }),\n );\n\n logLine(\"success\", `Relay connected · ${bold(config.deviceName)} · ${config.mode} mode`);\n logLine(\"success\", \"Device presence published.\");\n process.stdout.write(\" \" + dim(config.origin) + \"\\n\");\n process.stdout.write(\" \" + dim(\"Ctrl+C to disconnect\") + \"\\n\\n\");\n\n const sendHeartbeat = async () => {\n try {\n await fetch(new URL(\"/api/device/heartbeat\", config.origin), {\n method: \"POST\",\n headers: { Authorization: \"Bearer \" + config.deviceToken },\n });\n } catch {\n // WebSocket reconnect logic remains the source of truth for connectivity.\n }\n };\n void sendHeartbeat();\n const heartbeatTimer = setInterval(\n () => void sendHeartbeat(),\n 15 * 60_000,\n );\n\n await new Promise<void>((resolve) => {\n ws.on(\"message\", async (raw) => {\n let message: {\n type?: string;\n id?: string;\n tool?: string;\n arguments?: Record<string, unknown>;\n };\n\n try {\n message = JSON.parse(raw.toString());\n } catch {\n return;\n }\n\n if (\n message.type !== \"call\" ||\n !message.id ||\n !message.tool\n ) {\n return;\n }\n\n const callStarted = Date.now();\n logLine(\"event\", `tool.call ${bold(message.tool)} · ${dim(message.id.slice(0, 8))}`);\n try {\n const result = await core.call(\n message.tool,\n message.arguments || {},\n );\n logLine(\"success\", `tool.done ${message.tool} · ${Date.now() - callStarted}ms`);\n ws.send(\n JSON.stringify({\n type: \"result\",\n id: message.id,\n result,\n }),\n );\n } catch (error) {\n logLine(\"error\", `tool.fail ${message.tool} · ${error instanceof Error ? error.message : String(error)}`);\n ws.send(\n JSON.stringify({\n type: \"result\",\n id: message.id,\n error: error instanceof Error ? error.message : String(error),\n }),\n );\n }\n });\n\n ws.once(\"close\", resolve);\n ws.once(\"error\", resolve);\n });\n clearInterval(heartbeatTimer);\n } catch (error) {\n logLine(\n \"error\",\n \"Relay connection failed: \" +\n (error instanceof Error ? error.message : String(error)),\n );\n } finally {\n ws.removeAllListeners();\n ws.close();\n }\n\n if (!stopped) {\n logLine(\n \"warn\",\n \"Relay disconnected · retrying in \" + Math.round(backoff / 1000) + \"s\",\n );\n await sleep(backoff);\n backoff = Math.min(backoff * 2, 30_000);\n }\n }\n}\n\nasync function main() {\n if (argFlag(\"--help\") || argFlag(\"-h\")) {\n process.stdout.write(\n [\n \"Remote Arc\",\n \"\",\n \"Usage:\",\n \" npx remotelink\",\n \"\",\n \"After the npm release:\",\n \" npx remotelink\",\n \"\",\n \"Options:\",\n \" --safe Hard local read-only cap; dashboard cannot enable write tools\",\n \" --developer Legacy alias for dashboard-managed capabilities\",\n \" --reset Remove this computer's saved pairing\",\n \" --version Print CLI version\",\n \" --help Show this help\",\n \"\",\n ].join(\"\\n\"),\n );\n return;\n }\n\n if (argFlag(\"--version\") || argFlag(\"-v\")) {\n process.stdout.write(VERSION + \"\\n\");\n return;\n }\n\n if (argFlag(\"--reset\")) {\n await resetConfig();\n return;\n }\n\n const origin =\n process.env.REMOTEARC_ORIGIN || process.env.REMOTE_LINK_ORIGIN ||\n process.argv.find((value) => value.startsWith(\"--origin=\"))?.slice(9) ||\n DEFAULT_ORIGIN;\n\n let config = await readConfig();\n if (config && [\"https://remote.samyao.me\", \"https://remotearc.app\"].includes(config.origin)) {\n config.origin = DEFAULT_ORIGIN;\n await writeConfig(config);\n logLine(\"info\", \"Migrated relay origin to \" + DEFAULT_ORIGIN);\n }\n if (!config) {\n config = await pair(origin, selectedMode());\n } else if (argFlag(\"--safe\") || argFlag(\"--developer\")) {\n config.mode = argFlag(\"--safe\") ? \"safe\" : \"managed\";\n await writeConfig(config);\n }\n\n if (config) {\n logLine(\"info\", `Using paired device identity ${dim(config.deviceId.slice(0, 8))}…`);\n }\n await connectAgent(config);\n}\n\nmain().catch((error) => {\n logLine(\n \"error\",\n \"Fatal: \" + (error instanceof Error ? error.message : String(error)),\n );\n process.exit(1);\n});\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\nconst MAX_DIRECTORY_ENTRIES = 2000;\nconst DEFAULT_READ_LINES = 240;\nconst MAX_TEXT_FILE_BYTES = 20 * 1024 * 1024;\n\nconst printableSize = (bytes: number) => {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n};\n\nexport async function listDirectory(targetPath: string, depth = 2) {\n const maxDepth = Math.max(1, Math.min(10, Math.trunc(depth || 2)));\n const lines: string[] = [];\n let entriesSeen = 0;\n\n async function walk(directory: string, level: number, prefix: string) {\n if (entriesSeen >= MAX_DIRECTORY_ENTRIES) return;\n const entries = await fs.readdir(directory, { withFileTypes: true });\n entries.sort((a, b) => {\n if (a.isDirectory() !== b.isDirectory()) return a.isDirectory() ? -1 : 1;\n return a.name.localeCompare(b.name);\n });\n\n for (const entry of entries) {\n if (entriesSeen >= MAX_DIRECTORY_ENTRIES) break;\n entriesSeen += 1;\n const absolute = path.join(directory, entry.name);\n const label = entry.isDirectory()\n ? \"[DIR]\"\n : entry.isSymbolicLink()\n ? \"[LINK]\"\n : \"[FILE]\";\n lines.push(`${prefix}${label} ${entry.name}`);\n\n if (entry.isDirectory() && level < maxDepth) {\n await walk(absolute, level + 1, prefix + \" \");\n }\n }\n }\n\n const stat = await fs.stat(targetPath);\n if (!stat.isDirectory()) throw new Error(\"Path is not a directory: \" + targetPath);\n\n await walk(targetPath, 1, \"\");\n if (entriesSeen >= MAX_DIRECTORY_ENTRIES) {\n lines.push(`… truncated after ${MAX_DIRECTORY_ENTRIES} entries`);\n }\n\n return `Directory: ${targetPath}\\nDepth: ${maxDepth}\\n\\n${lines.join(\"\\n\") || \"(empty)\"}`;\n}\n\nexport async function readTextFile(\n targetPath: string,\n offset?: number,\n length?: number,\n) {\n const stat = await fs.stat(targetPath);\n if (!stat.isFile()) throw new Error(\"Path is not a file: \" + targetPath);\n if (stat.size > MAX_TEXT_FILE_BYTES) {\n throw new Error(\n `File is too large for read_file (${printableSize(stat.size)} > 20 MB). Use a terminal command or a more targeted tool instead.`,\n );\n }\n\n const buffer = await fs.readFile(targetPath);\n const sample = buffer.subarray(0, Math.min(buffer.length, 8192));\n if (sample.includes(0)) {\n throw new Error(\"Binary file detected. read_file currently supports text files only.\");\n }\n\n const text = buffer.toString(\"utf8\");\n const lines = text.split(/\\r?\\n/);\n const requestedOffset = Number.isFinite(offset) ? Math.trunc(offset as number) : 0;\n const start =\n requestedOffset < 0\n ? Math.max(0, lines.length + requestedOffset)\n : Math.min(lines.length, Math.max(0, requestedOffset));\n const requestedLength =\n Number.isFinite(length) && (length as number) > 0\n ? Math.trunc(length as number)\n : DEFAULT_READ_LINES;\n const end = Math.min(lines.length, start + requestedLength);\n const chunk = lines.slice(start, end).join(\"\\n\");\n\n const location =\n start === 0\n ? \"start\"\n : end === lines.length\n ? `line ${start + 1} to end`\n : `lines ${start + 1}-${end}`;\n\n return `[Reading ${end - start} lines from ${location} (total: ${lines.length} lines, ${printableSize(stat.size)})]\\n\\n${chunk}`;\n}\n\nexport async function getFileInfo(targetPath: string) {\n const stat = await fs.lstat(targetPath);\n return {\n path: targetPath,\n type: stat.isFile()\n ? \"file\"\n : stat.isDirectory()\n ? \"directory\"\n : stat.isSymbolicLink()\n ? \"symlink\"\n : \"other\",\n size: stat.size,\n size_human: printableSize(stat.size),\n mode: stat.mode,\n created_at: stat.birthtime.toISOString(),\n modified_at: stat.mtime.toISOString(),\n accessed_at: stat.atime.toISOString(),\n };\n}\n\nexport async function writeTextFile(\n targetPath: string,\n content: string,\n mode: \"rewrite\" | \"append\" = \"rewrite\",\n) {\n await fs.mkdir(path.dirname(targetPath), { recursive: true });\n if (mode === \"append\") {\n await fs.appendFile(targetPath, content, \"utf8\");\n } else {\n await fs.writeFile(targetPath, content, \"utf8\");\n }\n const stat = await fs.stat(targetPath);\n return {\n path: targetPath,\n mode,\n bytes: Buffer.byteLength(content),\n file_size: stat.size,\n };\n}\n\nexport async function editTextBlock(\n filePath: string,\n oldString: string,\n newString: string,\n expectedReplacements = 1,\n) {\n if (!oldString) throw new Error(\"old_string cannot be empty.\");\n const current = await fs.readFile(filePath, \"utf8\");\n const parts = current.split(oldString);\n const replacements = parts.length - 1;\n\n if (replacements !== expectedReplacements) {\n throw new Error(\n `Expected ${expectedReplacements} replacement(s) in ${filePath}, found ${replacements}. No changes were written.`,\n );\n }\n\n const next = parts.join(newString);\n await fs.writeFile(filePath, next, \"utf8\");\n return {\n path: filePath,\n replacements,\n bytes_before: Buffer.byteLength(current),\n bytes_after: Buffer.byteLength(next),\n };\n}\n","import { execFile, spawn } from \"node:child_process\";\nimport process from \"node:process\";\n\nconst MAX_CAPTURE_BYTES = 2 * 1024 * 1024;\n\nfunction appendCapped(current: string, chunk: Buffer | string) {\n if (Buffer.byteLength(current) >= MAX_CAPTURE_BYTES) return current;\n const next = current + chunk.toString();\n if (Buffer.byteLength(next) <= MAX_CAPTURE_BYTES) return next;\n return next.slice(0, MAX_CAPTURE_BYTES) + \"\\n… output truncated …\";\n}\n\nexport async function listProcesses() {\n const output = await new Promise<string>((resolve, reject) => {\n const command = process.platform === \"win32\" ? \"tasklist.exe\" : \"ps\";\n const args =\n process.platform === \"win32\"\n ? [\"/FO\", \"CSV\", \"/NH\"]\n : [\"-axo\", \"pid,ppid,user,%cpu,%mem,etime,command\"];\n execFile(command, args, { maxBuffer: MAX_CAPTURE_BYTES }, (error, stdout, stderr) => {\n if (error) {\n reject(new Error(`Could not list processes: ${stderr || error.message}`));\n return;\n }\n resolve(stdout.trim());\n });\n });\n\n return `Processes on ${process.platform}:\\n\\n${output}`;\n}\n\nasync function terminateTree(child: ReturnType<typeof spawn>) {\n if (!child.pid) return;\n if (process.platform === \"win32\") {\n await new Promise<void>((resolve) => {\n const killer = spawn(\n \"taskkill.exe\",\n [\"/PID\", String(child.pid), \"/T\", \"/F\"],\n { windowsHide: true, stdio: \"ignore\" },\n );\n killer.once(\"close\", () => resolve());\n killer.once(\"error\", () => resolve());\n });\n return;\n }\n\n try {\n process.kill(-child.pid, \"SIGTERM\");\n } catch {\n child.kill(\"SIGTERM\");\n }\n await new Promise((resolve) => setTimeout(resolve, 300));\n try {\n process.kill(-child.pid, \"SIGKILL\");\n } catch {\n if (!child.killed) child.kill(\"SIGKILL\");\n }\n}\n\nexport async function runShellCommand(command: string, timeoutMs = 5000) {\n const timeout = Math.max(100, Math.min(120_000, Math.trunc(timeoutMs || 5000)));\n const startedAt = Date.now();\n\n return await new Promise<{\n command: string;\n exit_code: number | null;\n signal: NodeJS.Signals | null;\n stdout: string;\n stderr: string;\n duration_ms: number;\n timed_out: boolean;\n }>((resolve, reject) => {\n const child = spawn(command, {\n shell: true,\n windowsHide: true,\n detached: process.platform !== \"win32\",\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let stdout = \"\";\n let stderr = \"\";\n let timedOut = false;\n let settled = false;\n\n child.stdout?.on(\"data\", (chunk) => {\n stdout = appendCapped(stdout, chunk);\n });\n child.stderr?.on(\"data\", (chunk) => {\n stderr = appendCapped(stderr, chunk);\n });\n\n const timer = setTimeout(async () => {\n timedOut = true;\n await terminateTree(child);\n }, timeout);\n\n child.once(\"error\", (error) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n reject(error);\n });\n\n child.once(\"close\", (code, signal) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve({\n command,\n exit_code: code,\n signal,\n stdout,\n stderr,\n duration_ms: Date.now() - startedAt,\n timed_out: timedOut,\n });\n });\n });\n}\n","import crypto from \"node:crypto\";\nimport { createReadStream } from \"node:fs\";\nimport fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nconst RETENTION_MS = 7 * 24 * 60 * 60 * 1000;\nconst MAX_SNAPSHOT_BYTES = 20 * 1024 * 1024;\nconst MAX_UNDO_BYTES = 200 * 1024 * 1024;\n\ntype UndoManifest = {\n id: string;\n createdAt: string;\n tool: \"write_file\" | \"edit_block\";\n targetPath: string;\n existed: boolean;\n mode?: number;\n bytes: number;\n postChangeHash?: string;\n};\n\nexport type UndoSnapshot = {\n id: string;\n directory: string;\n manifest: UndoManifest;\n};\n\nconst undoRoot = () =>\n process.env.REMOTEARC_UNDO_ROOT ||\n path.join(os.homedir(), \".remotearc\", \"undo\");\n\nasync function ensureUndoRoot() {\n await fs.mkdir(undoRoot(), { recursive: true, mode: 0o700 });\n}\n\nasync function readManifest(directory: string): Promise<UndoManifest | null> {\n try {\n return JSON.parse(\n await fs.readFile(path.join(directory, \"manifest.json\"), \"utf8\"),\n ) as UndoManifest;\n } catch {\n return null;\n }\n}\n\nasync function snapshotDirectories() {\n await ensureUndoRoot();\n const entries = await fs.readdir(undoRoot(), { withFileTypes: true });\n return entries\n .filter((entry) => entry.isDirectory())\n .map((entry) => path.join(undoRoot(), entry.name));\n}\n\nasync function cleanupUndoStore() {\n const now = Date.now();\n const snapshots: Array<{ directory: string; manifest: UndoManifest }> = [];\n\n for (const directory of await snapshotDirectories()) {\n const manifest = await readManifest(directory);\n if (!manifest) {\n await fs.rm(directory, { recursive: true, force: true }).catch(() => undefined);\n continue;\n }\n\n const created = Date.parse(manifest.createdAt);\n if (!Number.isFinite(created) || now - created > RETENTION_MS) {\n await fs.rm(directory, { recursive: true, force: true }).catch(() => undefined);\n continue;\n }\n\n snapshots.push({ directory, manifest });\n }\n\n snapshots.sort(\n (a, b) => Date.parse(a.manifest.createdAt) - Date.parse(b.manifest.createdAt),\n );\n\n let total = snapshots.reduce((sum, item) => sum + item.manifest.bytes, 0);\n for (const item of snapshots) {\n if (total <= MAX_UNDO_BYTES) break;\n await fs.rm(item.directory, { recursive: true, force: true }).catch(() => undefined);\n total -= item.manifest.bytes;\n }\n}\n\nfunction mutationPath(\n tool: \"write_file\" | \"edit_block\",\n args: Record<string, unknown>,\n) {\n const candidate = tool === \"write_file\" ? args.path : args.file_path;\n return typeof candidate === \"string\" && candidate.trim() ? candidate : null;\n}\n\nexport async function createUndoSnapshot(\n tool: \"write_file\" | \"edit_block\",\n args: Record<string, unknown>,\n): Promise<UndoSnapshot | null> {\n const targetPath = mutationPath(tool, args);\n if (!targetPath) return null;\n\n await cleanupUndoStore();\n\n let existed = false;\n let mode: number | undefined;\n let bytes = 0;\n let original: Buffer | null = null;\n\n try {\n const stat = await fs.stat(targetPath);\n if (!stat.isFile()) return null;\n existed = true;\n mode = stat.mode;\n bytes = stat.size;\n if (bytes > MAX_SNAPSHOT_BYTES) return null;\n original = await fs.readFile(targetPath);\n } catch (error) {\n const code =\n error && typeof error === \"object\" && \"code\" in error\n ? String((error as { code?: unknown }).code)\n : \"\";\n if (code !== \"ENOENT\") throw error;\n }\n\n const id = `${Date.now()}-${crypto.randomUUID().slice(0, 8)}`;\n const directory = path.join(undoRoot(), id);\n await fs.mkdir(directory, { recursive: true, mode: 0o700 });\n\n if (original) {\n await fs.writeFile(path.join(directory, \"content.bin\"), original, { mode: 0o600 });\n }\n\n const manifest: UndoManifest = {\n id,\n createdAt: new Date().toISOString(),\n tool,\n targetPath,\n existed,\n ...(mode !== undefined ? { mode } : {}),\n bytes,\n };\n\n await fs.writeFile(\n path.join(directory, \"manifest.json\"),\n JSON.stringify(manifest, null, 2) + \"\\n\",\n { mode: 0o600 },\n );\n\n await cleanupUndoStore();\n return { id, directory, manifest };\n}\n\nasync function hashFile(filePath: string) {\n const hash = crypto.createHash(\"sha256\");\n for await (const chunk of createReadStream(filePath)) hash.update(chunk);\n return hash.digest(\"hex\");\n}\n\nexport async function finalizeUndoSnapshot(snapshot: UndoSnapshot | null) {\n if (!snapshot) return false;\n try {\n snapshot.manifest.postChangeHash = await hashFile(snapshot.manifest.targetPath);\n await fs.writeFile(\n path.join(snapshot.directory, \"manifest.json\"),\n JSON.stringify(snapshot.manifest, null, 2) + \"\\n\",\n { mode: 0o600 },\n );\n return true;\n } catch {\n await discardUndoSnapshot(snapshot);\n return false;\n }\n}\n\nexport async function discardUndoSnapshot(snapshot: UndoSnapshot | null) {\n if (!snapshot) return;\n await fs.rm(snapshot.directory, { recursive: true, force: true }).catch(() => undefined);\n}\n\nexport async function undoLastChange() {\n const candidates: Array<{ directory: string; manifest: UndoManifest }> = [];\n for (const directory of await snapshotDirectories()) {\n const manifest = await readManifest(directory);\n if (manifest) candidates.push({ directory, manifest });\n }\n\n candidates.sort(\n (a, b) => Date.parse(b.manifest.createdAt) - Date.parse(a.manifest.createdAt),\n );\n const latest = candidates[0];\n if (!latest) {\n throw new Error(\"No reversible Remote Arc file change is available on this device.\");\n }\n\n if (!latest.manifest.postChangeHash) {\n throw new Error(\n \"This snapshot predates conflict-safe Local Undo and cannot be restored automatically.\",\n );\n }\n\n let currentHash: string;\n try {\n currentHash = await hashFile(latest.manifest.targetPath);\n } catch {\n throw new Error(\n \"The target file changed or disappeared after the Remote Arc edit. Automatic undo was refused to avoid overwriting newer work.\",\n );\n }\n\n if (currentHash !== latest.manifest.postChangeHash) {\n throw new Error(\n \"The target file changed again after the Remote Arc edit. Automatic undo was refused to avoid overwriting newer work.\",\n );\n }\n\n if (latest.manifest.existed) {\n const original = await fs.readFile(path.join(latest.directory, \"content.bin\"));\n await fs.mkdir(path.dirname(latest.manifest.targetPath), { recursive: true });\n await fs.writeFile(latest.manifest.targetPath, original);\n if (latest.manifest.mode !== undefined) {\n await fs.chmod(latest.manifest.targetPath, latest.manifest.mode).catch(() => undefined);\n }\n } else {\n await fs.rm(latest.manifest.targetPath, { force: true });\n }\n\n await fs.rm(latest.directory, { recursive: true, force: true });\n\n return {\n restored: true,\n action_id: latest.manifest.id,\n tool: latest.manifest.tool,\n path: latest.manifest.targetPath,\n snapshot_location: \"local-device-only\",\n };\n}\n\ntype GuardRule = { pattern: RegExp; reason: string };\n\nconst DESTRUCTIVE_COMMAND_RULES: GuardRule[] = [\n {\n pattern: /(?:^|[;&|]\\s*)(?:sudo\\s+)?rm\\s+[^\\n]*(?:-rf|-fr|-[a-z]*r[a-z]*f)[^\\n]*(?:\\s\\/\\s*$|\\s\\/\\*|\\s~(?:\\s|$)|\\s\\$HOME(?:\\s|$))/i,\n reason: \"recursive deletion of a root or home path\",\n },\n {\n pattern: /\\b(?:mkfs(?:\\.\\w+)?|wipefs|fdisk|parted)\\b/i,\n reason: \"disk or filesystem destructive operation\",\n },\n {\n pattern: /\\bdd\\b[^\\n]*\\bof=\\/dev\\/(?:sd|nvme|vd|xvd|mmcblk)/i,\n reason: \"raw disk overwrite\",\n },\n {\n pattern: /\\bdiskutil\\s+erase(?:disk|volume)\\b/i,\n reason: \"disk erase\",\n },\n {\n pattern: /\\b(?:format(?:\\.com)?\\s+[a-z]:|diskpart\\b|clear-disk\\b|initialize-disk\\b)/i,\n reason: \"Windows disk destructive operation\",\n },\n {\n pattern: /\\b(?:shutdown|reboot|halt|poweroff)\\b/i,\n reason: \"system shutdown or reboot\",\n },\n {\n pattern: /\\bremove-item\\b[^\\n]*(?:-recurse[^\\n]*-force|-force[^\\n]*-recurse)[^\\n]*(?:[a-z]:\\\\(?:\\*|\\s|$)|\\$env:systemdrive)/i,\n reason: \"recursive forced deletion of a drive root\",\n },\n {\n pattern: /\\b(?:del|erase|rd|rmdir)\\b[^\\n]*\\/s[^\\n]*\\/q[^\\n]*[a-z]:\\\\/i,\n reason: \"recursive Windows drive deletion\",\n },\n {\n pattern: /:\\(\\)\\s*\\{\\s*:\\|:&\\s*\\};\\s*:/,\n reason: \"fork bomb\",\n },\n];\n\nexport function assertCommandAllowed(command: string) {\n if (process.env.REMOTEARC_ALLOW_DESTRUCTIVE === \"1\") return;\n\n for (const rule of DESTRUCTIVE_COMMAND_RULES) {\n if (rule.pattern.test(command)) {\n throw new Error(\n \"Blocked by Remote Arc Safety Guard: \" +\n rule.reason +\n \". Run this action manually on the device if you intentionally need it.\",\n );\n }\n }\n}\n","import {\n editTextBlock,\n getFileInfo,\n listDirectory,\n readTextFile,\n writeTextFile,\n} from \"./filesystem.js\";\nimport { listProcesses, runShellCommand } from \"./process.js\";\nimport {\n assertCommandAllowed,\n createUndoSnapshot,\n discardUndoSnapshot,\n finalizeUndoSnapshot,\n undoLastChange,\n} from \"./safety.js\";\nimport type {\n ExecutionMode,\n ToolArguments,\n ToolDefinition,\n ToolName,\n ToolResult,\n} from \"./types.js\";\n\nconst SAFE_TOOLS = new Set<ToolName>([\n \"list_directory\",\n \"read_file\",\n \"get_file_info\",\n \"list_processes\",\n]);\n\nconst DEVELOPER_TOOLS = new Set<ToolName>([\n ...SAFE_TOOLS,\n \"write_file\",\n \"edit_block\",\n \"undo_last_change\",\n]);\n\nconst FULL_TOOLS = new Set<ToolName>([\n ...DEVELOPER_TOOLS,\n \"start_process\",\n]);\n\nconst DEFINITIONS: ToolDefinition[] = [\n {\n name: \"list_directory\",\n description: \"List files and directories on this computer without following directory symlinks.\",\n inputSchema: {\n type: \"object\",\n properties: {\n path: { type: \"string\" },\n depth: { type: \"integer\", minimum: 1, maximum: 10, default: 2 },\n },\n required: [\"path\"],\n additionalProperties: false,\n },\n },\n {\n name: \"read_file\",\n description: \"Read a text file by line range. Negative offsets read from the end.\",\n inputSchema: {\n type: \"object\",\n properties: {\n path: { type: \"string\" },\n offset: { type: \"integer\" },\n length: { type: \"integer\", minimum: 1 },\n },\n required: [\"path\"],\n additionalProperties: false,\n },\n },\n {\n name: \"get_file_info\",\n description: \"Get local file or directory metadata.\",\n inputSchema: {\n type: \"object\",\n properties: { path: { type: \"string\" } },\n required: [\"path\"],\n additionalProperties: false,\n },\n },\n {\n name: \"list_processes\",\n description: \"List processes running on this computer.\",\n inputSchema: { type: \"object\", properties: {}, additionalProperties: false },\n },\n {\n name: \"write_file\",\n description: \"Write or append UTF-8 text. Remote Arc creates a local-only Undo snapshot first.\",\n inputSchema: {\n type: \"object\",\n properties: {\n path: { type: \"string\" },\n content: { type: \"string\" },\n mode: { type: \"string\", enum: [\"rewrite\", \"append\"], default: \"rewrite\" },\n },\n required: [\"path\", \"content\"],\n additionalProperties: false,\n },\n },\n {\n name: \"edit_block\",\n description: \"Apply an exact text replacement with conflict-safe Local Undo.\",\n inputSchema: {\n type: \"object\",\n properties: {\n file_path: { type: \"string\" },\n old_string: { type: \"string\" },\n new_string: { type: \"string\" },\n expected_replacements: { type: \"integer\", minimum: 1, default: 1 },\n },\n required: [\"file_path\", \"old_string\", \"new_string\"],\n additionalProperties: false,\n },\n },\n {\n name: \"undo_last_change\",\n description: \"Undo the newest reversible Remote Arc file edit stored only on this device.\",\n inputSchema: { type: \"object\", properties: {}, additionalProperties: false },\n },\n {\n name: \"start_process\",\n description: \"Run a shell command with timeout and output capture. Catastrophic command patterns are blocked locally.\",\n inputSchema: {\n type: \"object\",\n properties: {\n command: { type: \"string\" },\n timeout_ms: { type: \"integer\", minimum: 100, maximum: 120000, default: 5000 },\n },\n required: [\"command\"],\n additionalProperties: false,\n },\n },\n];\n\nconst textResult = (value: unknown): ToolResult => ({\n content: [\n {\n type: \"text\",\n text: typeof value === \"string\" ? value : JSON.stringify(value, null, 2),\n },\n ],\n});\n\nfunction requiredString(args: ToolArguments, key: string) {\n const value = args[key];\n if (typeof value !== \"string\") throw new Error(`${key} must be a string.`);\n return value;\n}\n\nfunction optionalNumber(args: ToolArguments, key: string) {\n const value = args[key];\n return typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n}\n\nexport class RemoteArcExecutionCore {\n constructor(private readonly mode: ExecutionMode = \"managed\") {}\n\n private allowedTools() {\n if (this.mode === \"safe\") return SAFE_TOOLS;\n if (this.mode === \"developer\") return DEVELOPER_TOOLS;\n return FULL_TOOLS;\n }\n\n listTools() {\n const allowed = this.allowedTools();\n return DEFINITIONS.filter((tool) => allowed.has(tool.name));\n }\n\n async callTool(name: string, args: ToolArguments = {}): Promise<ToolResult> {\n const toolName = name as ToolName;\n if (!this.allowedTools().has(toolName)) {\n throw new Error(`Tool blocked by local permission mode: ${name}`);\n }\n\n switch (toolName) {\n case \"list_directory\":\n return textResult(\n await listDirectory(\n requiredString(args, \"path\"),\n optionalNumber(args, \"depth\") ?? 2,\n ),\n );\n case \"read_file\":\n return textResult(\n await readTextFile(\n requiredString(args, \"path\"),\n optionalNumber(args, \"offset\"),\n optionalNumber(args, \"length\"),\n ),\n );\n case \"get_file_info\":\n return textResult(await getFileInfo(requiredString(args, \"path\")));\n case \"list_processes\":\n return textResult(await listProcesses());\n case \"undo_last_change\":\n return textResult(await undoLastChange());\n case \"start_process\": {\n const command = requiredString(args, \"command\");\n assertCommandAllowed(command);\n return textResult(\n await runShellCommand(\n command,\n optionalNumber(args, \"timeout_ms\") ?? 5000,\n ),\n );\n }\n case \"write_file\":\n case \"edit_block\": {\n const snapshot = await createUndoSnapshot(toolName, args);\n try {\n const result =\n toolName === \"write_file\"\n ? await writeTextFile(\n requiredString(args, \"path\"),\n requiredString(args, \"content\"),\n args.mode === \"append\" ? \"append\" : \"rewrite\",\n )\n : await editTextBlock(\n requiredString(args, \"file_path\"),\n requiredString(args, \"old_string\"),\n requiredString(args, \"new_string\"),\n optionalNumber(args, \"expected_replacements\") ?? 1,\n );\n\n const undoAvailable = await finalizeUndoSnapshot(snapshot);\n return textResult({\n ...result,\n undo_available: undoAvailable,\n undo_storage: undoAvailable ? \"local-device-only\" : null,\n });\n } catch (error) {\n await discardUndoSnapshot(snapshot);\n throw error;\n }\n }\n default:\n throw new Error(\"Unknown Remote Arc tool: \" + name);\n }\n }\n\n async close() {\n // Native execution has no persistent child process to close.\n }\n}\n\nexport const toolDefinitions = DEFINITIONS;\n"],"mappings":"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","names":["os","path","fs","spawn","process","process","fs","path","path","os","process","fs","spawn"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "remotelink",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.8",
|
|
4
4
|
"description": "Connect a computer to Remote Arc with one command.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -17,15 +17,15 @@
|
|
|
17
17
|
"node": ">=20"
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
|
-
"build": "tsup
|
|
20
|
+
"build": "tsup",
|
|
21
21
|
"typecheck": "tsc --noEmit",
|
|
22
22
|
"prepublishOnly": "pnpm build"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@modelcontextprotocol/client": "^2.1.0",
|
|
26
25
|
"ws": "^8.18.3"
|
|
27
26
|
},
|
|
28
27
|
"devDependencies": {
|
|
28
|
+
"@remotearc/execution-core": "workspace:*",
|
|
29
29
|
"@types/node": "^24.13.6",
|
|
30
30
|
"@types/ws": "^8.18.1",
|
|
31
31
|
"tsup": "^8.5.0",
|