remotelink 0.3.4 → 0.3.6
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 +41 -13
- package/dist/index.js +343 -61
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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,41 @@ npx remotelink --help Show help
|
|
|
49
49
|
|
|
50
50
|
The package also exposes the aliases `remote-link` and `remote-arc`.
|
|
51
51
|
|
|
52
|
-
##
|
|
52
|
+
## Capability model
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
file reading, file metadata, and process listing.
|
|
54
|
+
Newly paired devices start with only read-oriented skills enabled:
|
|
55
|
+
directory listing, file reading, file metadata, and process listing.
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
Use the Remote Arc dashboard to enable or disable individual skills per device.
|
|
58
|
+
The Safe preset keeps access read-only, Developer adds file editing, and Full
|
|
59
|
+
adds terminal execution.
|
|
59
60
|
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
By default the local CLI exposes the capabilities that the dashboard may grant,
|
|
62
|
+
while the relay enforces the saved per-device policy before forwarding a call.
|
|
63
|
+
Use `--safe` when you want an additional local hard cap that prevents write
|
|
64
|
+
skills from running even if they are enabled in the dashboard.
|
|
65
|
+
|
|
66
|
+
## Local Undo
|
|
67
|
+
|
|
68
|
+
Remote Arc snapshots the previous local file state before supported
|
|
69
|
+
`write_file` and `edit_block` operations. These snapshots stay under
|
|
70
|
+
`~/.remotearc/undo` on the device and are never uploaded to Remote Arc Cloud.
|
|
71
|
+
|
|
72
|
+
The `undo_last_change` skill restores the newest reversible Remote Arc file
|
|
73
|
+
change. Snapshots expire after 7 days, use at most 200 MB in total, and files
|
|
74
|
+
larger than 20 MB are not snapshotted.
|
|
75
|
+
|
|
76
|
+
Local Undo covers Remote Arc file writes only. It cannot reverse external side
|
|
77
|
+
effects such as publishing a package, deploying cloud infrastructure, sending a
|
|
78
|
+
request to another service, or mutating a remote database.
|
|
79
|
+
|
|
80
|
+
## Safety Guard
|
|
81
|
+
|
|
82
|
+
Terminal access stays useful for normal development workflows. The local CLI
|
|
83
|
+
only blocks a narrow set of catastrophic commands such as root/home recursive
|
|
84
|
+
deletion, disk formatting or raw-disk overwrite, fork bombs, and machine
|
|
85
|
+
shutdown/reboot. These checks happen on the device before the command reaches
|
|
86
|
+
the local execution core.
|
|
62
87
|
|
|
63
88
|
## Security
|
|
64
89
|
|
|
@@ -68,9 +93,12 @@ will not forward a tool the device did not advertise.
|
|
|
68
93
|
- MCP access uses OAuth 2.1 + PKCE.
|
|
69
94
|
- Device access is scoped to the authenticated Remote Arc account.
|
|
70
95
|
- You can revoke paired devices from the Remote Arc dashboard.
|
|
96
|
+
- Supported file changes can be rolled back from local-only snapshots.
|
|
71
97
|
|
|
72
|
-
Remote computer control can modify files and execute commands.
|
|
73
|
-
|
|
98
|
+
Remote computer control can modify files and execute commands. Remote Arc
|
|
99
|
+
starts new devices read-only; enable additional skills only when you want the
|
|
100
|
+
connected AI to use them. Full terminal access should be used only on computers
|
|
101
|
+
you control.
|
|
74
102
|
|
|
75
103
|
## Requirements
|
|
76
104
|
|
package/dist/index.js
CHANGED
|
@@ -1,19 +1,247 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
4
|
+
import os2 from "os";
|
|
5
|
+
import path2 from "path";
|
|
6
|
+
import fs2 from "fs/promises";
|
|
7
7
|
import { spawn } from "child_process";
|
|
8
|
-
import
|
|
8
|
+
import process2 from "process";
|
|
9
9
|
import { Client } from "@modelcontextprotocol/client";
|
|
10
10
|
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
|
11
11
|
import WebSocket from "ws";
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
// src/local-safety.ts
|
|
14
|
+
import crypto from "crypto";
|
|
15
|
+
import { createReadStream } from "fs";
|
|
16
|
+
import fs from "fs/promises";
|
|
17
|
+
import os from "os";
|
|
18
|
+
import path from "path";
|
|
19
|
+
var UNDO_ROOT = path.join(os.homedir(), ".remotearc", "undo");
|
|
20
|
+
var RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
21
|
+
var MAX_SNAPSHOT_BYTES = 20 * 1024 * 1024;
|
|
22
|
+
var MAX_UNDO_BYTES = 200 * 1024 * 1024;
|
|
23
|
+
async function ensureUndoRoot() {
|
|
24
|
+
await fs.mkdir(UNDO_ROOT, { recursive: true, mode: 448 });
|
|
25
|
+
}
|
|
26
|
+
async function readManifest(directory) {
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(
|
|
29
|
+
await fs.readFile(path.join(directory, "manifest.json"), "utf8")
|
|
30
|
+
);
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function snapshotDirectories() {
|
|
36
|
+
await ensureUndoRoot();
|
|
37
|
+
const entries = await fs.readdir(UNDO_ROOT, { withFileTypes: true });
|
|
38
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => path.join(UNDO_ROOT, entry.name));
|
|
39
|
+
}
|
|
40
|
+
async function cleanupUndoStore() {
|
|
41
|
+
const now = Date.now();
|
|
42
|
+
const snapshots = [];
|
|
43
|
+
for (const directory of await snapshotDirectories()) {
|
|
44
|
+
const manifest = await readManifest(directory);
|
|
45
|
+
if (!manifest) {
|
|
46
|
+
await fs.rm(directory, { recursive: true, force: true }).catch(() => void 0);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const created = Date.parse(manifest.createdAt);
|
|
50
|
+
if (!Number.isFinite(created) || now - created > RETENTION_MS) {
|
|
51
|
+
await fs.rm(directory, { recursive: true, force: true }).catch(() => void 0);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
snapshots.push({ directory, manifest });
|
|
55
|
+
}
|
|
56
|
+
snapshots.sort(
|
|
57
|
+
(a, b) => Date.parse(a.manifest.createdAt) - Date.parse(b.manifest.createdAt)
|
|
58
|
+
);
|
|
59
|
+
let total = snapshots.reduce((sum, item) => sum + item.manifest.bytes, 0);
|
|
60
|
+
for (const item of snapshots) {
|
|
61
|
+
if (total <= MAX_UNDO_BYTES) break;
|
|
62
|
+
await fs.rm(item.directory, { recursive: true, force: true }).catch(() => void 0);
|
|
63
|
+
total -= item.manifest.bytes;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function mutationPath(tool, args) {
|
|
67
|
+
const candidate = tool === "write_file" ? args.path : args.file_path;
|
|
68
|
+
return typeof candidate === "string" && candidate.trim() ? candidate : null;
|
|
69
|
+
}
|
|
70
|
+
async function createUndoSnapshot(tool, args) {
|
|
71
|
+
const targetPath = mutationPath(tool, args);
|
|
72
|
+
if (!targetPath) return null;
|
|
73
|
+
await cleanupUndoStore();
|
|
74
|
+
let existed = false;
|
|
75
|
+
let mode;
|
|
76
|
+
let bytes = 0;
|
|
77
|
+
let original = null;
|
|
78
|
+
try {
|
|
79
|
+
const stat = await fs.stat(targetPath);
|
|
80
|
+
if (!stat.isFile()) return null;
|
|
81
|
+
existed = true;
|
|
82
|
+
mode = stat.mode;
|
|
83
|
+
bytes = stat.size;
|
|
84
|
+
if (bytes > MAX_SNAPSHOT_BYTES) return null;
|
|
85
|
+
original = await fs.readFile(targetPath);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
|
|
88
|
+
if (code !== "ENOENT") throw error;
|
|
89
|
+
}
|
|
90
|
+
const id = `${Date.now()}-${crypto.randomUUID().slice(0, 8)}`;
|
|
91
|
+
const directory = path.join(UNDO_ROOT, id);
|
|
92
|
+
await fs.mkdir(directory, { recursive: true, mode: 448 });
|
|
93
|
+
if (original) {
|
|
94
|
+
await fs.writeFile(path.join(directory, "content.bin"), original, { mode: 384 });
|
|
95
|
+
}
|
|
96
|
+
const manifest = {
|
|
97
|
+
id,
|
|
98
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
99
|
+
tool,
|
|
100
|
+
targetPath,
|
|
101
|
+
existed,
|
|
102
|
+
...mode !== void 0 ? { mode } : {},
|
|
103
|
+
bytes
|
|
104
|
+
};
|
|
105
|
+
await fs.writeFile(
|
|
106
|
+
path.join(directory, "manifest.json"),
|
|
107
|
+
JSON.stringify(manifest, null, 2) + "\n",
|
|
108
|
+
{ mode: 384 }
|
|
109
|
+
);
|
|
110
|
+
await cleanupUndoStore();
|
|
111
|
+
return { id, directory, manifest };
|
|
112
|
+
}
|
|
113
|
+
async function hashFile(filePath) {
|
|
114
|
+
const hash = crypto.createHash("sha256");
|
|
115
|
+
for await (const chunk of createReadStream(filePath)) {
|
|
116
|
+
hash.update(chunk);
|
|
117
|
+
}
|
|
118
|
+
return hash.digest("hex");
|
|
119
|
+
}
|
|
120
|
+
async function finalizeUndoSnapshot(snapshot) {
|
|
121
|
+
if (!snapshot) return false;
|
|
122
|
+
try {
|
|
123
|
+
const postChangeHash = await hashFile(snapshot.manifest.targetPath);
|
|
124
|
+
snapshot.manifest.postChangeHash = postChangeHash;
|
|
125
|
+
await fs.writeFile(
|
|
126
|
+
path.join(snapshot.directory, "manifest.json"),
|
|
127
|
+
JSON.stringify(snapshot.manifest, null, 2) + "\n",
|
|
128
|
+
{ mode: 384 }
|
|
129
|
+
);
|
|
130
|
+
return true;
|
|
131
|
+
} catch {
|
|
132
|
+
await discardUndoSnapshot(snapshot);
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async function discardUndoSnapshot(snapshot) {
|
|
137
|
+
if (!snapshot) return;
|
|
138
|
+
await fs.rm(snapshot.directory, { recursive: true, force: true }).catch(() => void 0);
|
|
139
|
+
}
|
|
140
|
+
async function undoLastChange() {
|
|
141
|
+
const candidates = [];
|
|
142
|
+
for (const directory of await snapshotDirectories()) {
|
|
143
|
+
const manifest = await readManifest(directory);
|
|
144
|
+
if (manifest) candidates.push({ directory, manifest });
|
|
145
|
+
}
|
|
146
|
+
candidates.sort(
|
|
147
|
+
(a, b) => Date.parse(b.manifest.createdAt) - Date.parse(a.manifest.createdAt)
|
|
148
|
+
);
|
|
149
|
+
const latest = candidates[0];
|
|
150
|
+
if (!latest) {
|
|
151
|
+
throw new Error("No reversible Remote Arc file change is available on this device.");
|
|
152
|
+
}
|
|
153
|
+
if (!latest.manifest.postChangeHash) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
"This snapshot predates conflict-safe Local Undo and cannot be restored automatically."
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
let currentHash;
|
|
159
|
+
try {
|
|
160
|
+
currentHash = await hashFile(latest.manifest.targetPath);
|
|
161
|
+
} catch {
|
|
162
|
+
throw new Error(
|
|
163
|
+
"The target file changed or disappeared after the Remote Arc edit. Automatic undo was refused to avoid overwriting newer work."
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
if (currentHash !== latest.manifest.postChangeHash) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
"The target file changed again after the Remote Arc edit. Automatic undo was refused to avoid overwriting newer work."
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
if (latest.manifest.existed) {
|
|
172
|
+
const original = await fs.readFile(path.join(latest.directory, "content.bin"));
|
|
173
|
+
await fs.mkdir(path.dirname(latest.manifest.targetPath), { recursive: true });
|
|
174
|
+
await fs.writeFile(latest.manifest.targetPath, original);
|
|
175
|
+
if (latest.manifest.mode !== void 0) {
|
|
176
|
+
await fs.chmod(latest.manifest.targetPath, latest.manifest.mode).catch(() => void 0);
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
await fs.rm(latest.manifest.targetPath, { force: true });
|
|
180
|
+
}
|
|
181
|
+
await fs.rm(latest.directory, { recursive: true, force: true });
|
|
182
|
+
return {
|
|
183
|
+
restored: true,
|
|
184
|
+
action_id: latest.manifest.id,
|
|
185
|
+
tool: latest.manifest.tool,
|
|
186
|
+
path: latest.manifest.targetPath,
|
|
187
|
+
snapshot_location: "local-device-only"
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
var DESTRUCTIVE_COMMAND_RULES = [
|
|
191
|
+
{
|
|
192
|
+
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,
|
|
193
|
+
reason: "recursive deletion of a root or home path"
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
pattern: /\b(?:mkfs(?:\.\w+)?|wipefs|fdisk|parted)\b/i,
|
|
197
|
+
reason: "disk or filesystem destructive operation"
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
pattern: /\bdd\b[^\n]*\bof=\/dev\/(?:sd|nvme|vd|xvd|mmcblk)/i,
|
|
201
|
+
reason: "raw disk overwrite"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
pattern: /\bdiskutil\s+erase(?:disk|volume)\b/i,
|
|
205
|
+
reason: "disk erase"
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
pattern: /\b(?:format(?:\.com)?\s+[a-z]:|diskpart\b|clear-disk\b|initialize-disk\b)/i,
|
|
209
|
+
reason: "Windows disk destructive operation"
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
pattern: /\b(?:shutdown|reboot|halt|poweroff)\b/i,
|
|
213
|
+
reason: "system shutdown or reboot"
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
pattern: /\bremove-item\b[^\n]*(?:-recurse[^\n]*-force|-force[^\n]*-recurse)[^\n]*(?:[a-z]:\\(?:\*|\s|$)|\$env:systemdrive)/i,
|
|
217
|
+
reason: "recursive forced deletion of a drive root"
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
pattern: /\b(?:del|erase|rd|rmdir)\b[^\n]*\/s[^\n]*\/q[^\n]*[a-z]:\\/i,
|
|
221
|
+
reason: "recursive Windows drive deletion"
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
pattern: /:\(\)\s*\{\s*:\|:&\s*\};\s*:/,
|
|
225
|
+
reason: "fork bomb"
|
|
226
|
+
}
|
|
227
|
+
];
|
|
228
|
+
function assertCommandAllowed(command) {
|
|
229
|
+
if (process.env.REMOTEARC_ALLOW_DESTRUCTIVE === "1") return;
|
|
230
|
+
for (const rule of DESTRUCTIVE_COMMAND_RULES) {
|
|
231
|
+
if (rule.pattern.test(command)) {
|
|
232
|
+
throw new Error(
|
|
233
|
+
"Blocked by Remote Arc Safety Guard: " + rule.reason + ". Run this action manually on the device if you intentionally need it."
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/index.ts
|
|
240
|
+
var VERSION = "0.3.6";
|
|
13
241
|
var DEFAULT_ORIGIN = "https://mcp.remotearc.app";
|
|
14
|
-
var CONFIG_DIR =
|
|
15
|
-
var CONFIG_PATH =
|
|
16
|
-
var LEGACY_CONFIG_PATH =
|
|
242
|
+
var CONFIG_DIR = path2.join(os2.homedir(), ".remotearc");
|
|
243
|
+
var CONFIG_PATH = path2.join(CONFIG_DIR, "config.json");
|
|
244
|
+
var LEGACY_CONFIG_PATH = path2.join(os2.homedir(), ".remote-link", "config.json");
|
|
17
245
|
var SAFE_TOOLS = /* @__PURE__ */ new Set([
|
|
18
246
|
"list_directory",
|
|
19
247
|
"read_file",
|
|
@@ -24,10 +252,11 @@ var DEVELOPER_TOOLS = /* @__PURE__ */ new Set([
|
|
|
24
252
|
...SAFE_TOOLS,
|
|
25
253
|
"start_process",
|
|
26
254
|
"write_file",
|
|
27
|
-
"edit_block"
|
|
255
|
+
"edit_block",
|
|
256
|
+
"undo_last_change"
|
|
28
257
|
]);
|
|
29
258
|
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
30
|
-
var supportsColor =
|
|
259
|
+
var supportsColor = process2.stdout.isTTY && !process2.env.NO_COLOR;
|
|
31
260
|
var paint = (code, value) => supportsColor ? `\x1B[${code}m${value}\x1B[0m` : value;
|
|
32
261
|
var dim = (value) => paint("2", value);
|
|
33
262
|
var cyan = (value) => paint("36", value);
|
|
@@ -45,28 +274,34 @@ function nowTime() {
|
|
|
45
274
|
}
|
|
46
275
|
function logLine(level, message) {
|
|
47
276
|
const icon = level === "success" ? green("\u2713") : level === "warn" ? yellow("!") : level === "error" ? red("\xD7") : level === "event" ? cyan("\u2192") : cyan("\u2022");
|
|
48
|
-
|
|
277
|
+
process2.stdout.write(`${dim(nowTime())} ${icon} ${message}
|
|
49
278
|
`);
|
|
50
279
|
}
|
|
51
280
|
function banner() {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
281
|
+
process2.stdout.write("\n");
|
|
282
|
+
process2.stdout.write(bold("Remote Arc") + " " + dim(`v${VERSION}`) + "\n");
|
|
283
|
+
process2.stdout.write(dim("Secure remote MCP bridge") + "\n\n");
|
|
55
284
|
}
|
|
56
285
|
function argFlag(name) {
|
|
57
|
-
return
|
|
286
|
+
return process2.argv.includes(name);
|
|
58
287
|
}
|
|
59
288
|
function selectedMode() {
|
|
60
289
|
if (argFlag("--safe")) return "safe";
|
|
61
|
-
return "
|
|
290
|
+
return "managed";
|
|
291
|
+
}
|
|
292
|
+
function isLocalSafeMode(mode) {
|
|
293
|
+
return mode === "safe";
|
|
294
|
+
}
|
|
295
|
+
function modeLabel(mode) {
|
|
296
|
+
return isLocalSafeMode(mode) ? "safe local cap" : "dashboard-managed";
|
|
62
297
|
}
|
|
63
298
|
async function readConfig() {
|
|
64
299
|
try {
|
|
65
|
-
return JSON.parse(await
|
|
300
|
+
return JSON.parse(await fs2.readFile(CONFIG_PATH, "utf8"));
|
|
66
301
|
} catch {
|
|
67
302
|
try {
|
|
68
303
|
const legacy = JSON.parse(
|
|
69
|
-
await
|
|
304
|
+
await fs2.readFile(LEGACY_CONFIG_PATH, "utf8")
|
|
70
305
|
);
|
|
71
306
|
await writeConfig(legacy);
|
|
72
307
|
logLine("success", "Migrated existing Remote Link pairing to Remote Arc.");
|
|
@@ -77,21 +312,21 @@ async function readConfig() {
|
|
|
77
312
|
}
|
|
78
313
|
}
|
|
79
314
|
async function writeConfig(config) {
|
|
80
|
-
await
|
|
81
|
-
await
|
|
315
|
+
await fs2.mkdir(CONFIG_DIR, { recursive: true });
|
|
316
|
+
await fs2.writeFile(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", {
|
|
82
317
|
mode: 384
|
|
83
318
|
});
|
|
84
319
|
}
|
|
85
320
|
async function resetConfig() {
|
|
86
321
|
await Promise.all([
|
|
87
|
-
|
|
88
|
-
|
|
322
|
+
fs2.rm(CONFIG_PATH, { force: true }),
|
|
323
|
+
fs2.rm(LEGACY_CONFIG_PATH, { force: true })
|
|
89
324
|
]);
|
|
90
325
|
logLine("success", "Removed saved device credentials.");
|
|
91
326
|
}
|
|
92
327
|
function openBrowser(url) {
|
|
93
|
-
const command =
|
|
94
|
-
const args =
|
|
328
|
+
const command = process2.platform === "win32" ? "cmd" : process2.platform === "darwin" ? "open" : "xdg-open";
|
|
329
|
+
const args = process2.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
95
330
|
const child = spawn(command, args, {
|
|
96
331
|
detached: true,
|
|
97
332
|
stdio: "ignore"
|
|
@@ -99,15 +334,15 @@ function openBrowser(url) {
|
|
|
99
334
|
child.unref();
|
|
100
335
|
}
|
|
101
336
|
async function pair(origin, mode) {
|
|
102
|
-
const deviceName =
|
|
337
|
+
const deviceName = os2.hostname();
|
|
103
338
|
const response = await fetch(origin + "/api/device/start", {
|
|
104
339
|
method: "POST",
|
|
105
340
|
headers: { "content-type": "application/json" },
|
|
106
341
|
body: JSON.stringify({
|
|
107
342
|
device_name: deviceName,
|
|
108
|
-
platform:
|
|
109
|
-
arch:
|
|
110
|
-
hostname:
|
|
343
|
+
platform: process2.platform,
|
|
344
|
+
arch: process2.arch,
|
|
345
|
+
hostname: os2.hostname()
|
|
111
346
|
})
|
|
112
347
|
});
|
|
113
348
|
if (!response.ok) {
|
|
@@ -115,10 +350,10 @@ async function pair(origin, mode) {
|
|
|
115
350
|
}
|
|
116
351
|
const pairing = await response.json();
|
|
117
352
|
banner();
|
|
118
|
-
logLine("info", `Device: ${bold(deviceName)} \xB7 ${
|
|
353
|
+
logLine("info", `Device: ${bold(deviceName)} \xB7 ${process2.platform}/${process2.arch} \xB7 ${modeLabel(mode)}`);
|
|
119
354
|
logLine("event", "Pairing required \u2014 opening secure browser approval.");
|
|
120
|
-
|
|
121
|
-
|
|
355
|
+
process2.stdout.write("\n Pairing code " + bold(cyan(pairing.user_code)) + "\n");
|
|
356
|
+
process2.stdout.write(" " + dim(pairing.verification_uri_complete) + "\n\n");
|
|
122
357
|
openBrowser(pairing.verification_uri_complete);
|
|
123
358
|
logLine("info", "Waiting for authorization\u2026");
|
|
124
359
|
const startedAt = Date.now();
|
|
@@ -133,7 +368,7 @@ async function pair(origin, mode) {
|
|
|
133
368
|
})
|
|
134
369
|
});
|
|
135
370
|
if (tokenResponse.status === 428) {
|
|
136
|
-
if (
|
|
371
|
+
if (process2.stdout.isTTY) process2.stdout.write(dim("\xB7"));
|
|
137
372
|
continue;
|
|
138
373
|
}
|
|
139
374
|
if (!tokenResponse.ok) {
|
|
@@ -141,7 +376,7 @@ async function pair(origin, mode) {
|
|
|
141
376
|
throw new Error("Pairing failed: " + body);
|
|
142
377
|
}
|
|
143
378
|
const result = await tokenResponse.json();
|
|
144
|
-
|
|
379
|
+
process2.stdout.write("\n");
|
|
145
380
|
logLine("success", "Device authorized.");
|
|
146
381
|
const config = {
|
|
147
382
|
deviceId: result.device_id,
|
|
@@ -167,7 +402,7 @@ var ExecutionCore = class {
|
|
|
167
402
|
name: "remotearc-cli-core",
|
|
168
403
|
version: VERSION
|
|
169
404
|
});
|
|
170
|
-
const command =
|
|
405
|
+
const command = process2.platform === "win32" ? "npx.cmd" : "npx";
|
|
171
406
|
const transport = new StdioClientTransport({
|
|
172
407
|
command,
|
|
173
408
|
args: ["-y", "@wonderwhy-er/desktop-commander@latest"],
|
|
@@ -181,18 +416,62 @@ var ExecutionCore = class {
|
|
|
181
416
|
}
|
|
182
417
|
async tools() {
|
|
183
418
|
const list = await (await this.connect()).listTools();
|
|
184
|
-
const
|
|
185
|
-
|
|
419
|
+
const locallyAvailable = isLocalSafeMode(this.mode) ? SAFE_TOOLS : DEVELOPER_TOOLS;
|
|
420
|
+
const tools = list.tools.filter((tool) => locallyAvailable.has(tool.name));
|
|
421
|
+
if (!isLocalSafeMode(this.mode)) {
|
|
422
|
+
tools.push({
|
|
423
|
+
name: "undo_last_change",
|
|
424
|
+
description: "Undo the most recent reversible file change made through Remote Arc on this device. Snapshots stay on the device and expire automatically.",
|
|
425
|
+
inputSchema: {
|
|
426
|
+
type: "object",
|
|
427
|
+
properties: {},
|
|
428
|
+
additionalProperties: false
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
return tools;
|
|
186
433
|
}
|
|
187
434
|
async call(name, args) {
|
|
188
|
-
const
|
|
189
|
-
if (!
|
|
190
|
-
throw new Error("Tool blocked by local
|
|
435
|
+
const locallyAvailable = isLocalSafeMode(this.mode) ? SAFE_TOOLS : DEVELOPER_TOOLS;
|
|
436
|
+
if (!locallyAvailable.has(name)) {
|
|
437
|
+
throw new Error("Tool blocked by local safety cap: " + name);
|
|
438
|
+
}
|
|
439
|
+
if (name === "undo_last_change") {
|
|
440
|
+
const result = await undoLastChange();
|
|
441
|
+
logLine("success", "Local Undo restored the latest reversible file change.");
|
|
442
|
+
return {
|
|
443
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
if (name === "start_process") {
|
|
447
|
+
assertCommandAllowed(String(args.command || ""));
|
|
448
|
+
}
|
|
449
|
+
const reversibleTool = name === "write_file" || name === "edit_block" ? name : null;
|
|
450
|
+
const snapshot = reversibleTool ? await createUndoSnapshot(reversibleTool, args) : null;
|
|
451
|
+
if (reversibleTool && !snapshot) {
|
|
452
|
+
logLine("warn", "Local Undo is unavailable for this file change.");
|
|
453
|
+
}
|
|
454
|
+
try {
|
|
455
|
+
const result = await (await this.connect()).callTool({
|
|
456
|
+
name,
|
|
457
|
+
arguments: args
|
|
458
|
+
});
|
|
459
|
+
if (snapshot) {
|
|
460
|
+
const finalized = await finalizeUndoSnapshot(snapshot);
|
|
461
|
+
if (finalized) {
|
|
462
|
+
logLine(
|
|
463
|
+
"info",
|
|
464
|
+
`Local Undo ready \xB7 ${dim(snapshot.id)} \xB7 kept on this device only`
|
|
465
|
+
);
|
|
466
|
+
} else {
|
|
467
|
+
logLine("warn", "Local Undo snapshot could not be finalized.");
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return result;
|
|
471
|
+
} catch (error) {
|
|
472
|
+
await discardUndoSnapshot(snapshot);
|
|
473
|
+
throw error;
|
|
191
474
|
}
|
|
192
|
-
return (await this.connect()).callTool({
|
|
193
|
-
name,
|
|
194
|
-
arguments: args
|
|
195
|
-
});
|
|
196
475
|
}
|
|
197
476
|
async close() {
|
|
198
477
|
if (!this.client) return;
|
|
@@ -203,21 +482,24 @@ var ExecutionCore = class {
|
|
|
203
482
|
async function connectAgent(config) {
|
|
204
483
|
const core = new ExecutionCore(config.mode);
|
|
205
484
|
banner();
|
|
206
|
-
logLine("info", `Device: ${bold(config.deviceName)} \xB7 ${
|
|
207
|
-
logLine(
|
|
485
|
+
logLine("info", `Device: ${bold(config.deviceName)} \xB7 ${process2.platform}/${process2.arch}`);
|
|
486
|
+
logLine(
|
|
487
|
+
"info",
|
|
488
|
+
`Permission profile: ${isLocalSafeMode(config.mode) ? green("safe local cap") : cyan("dashboard-managed")}`
|
|
489
|
+
);
|
|
208
490
|
const tools = await core.tools();
|
|
209
491
|
logLine("success", `Local tools ready: ${tools.length} exposed`);
|
|
210
|
-
|
|
492
|
+
process2.stdout.write(" " + dim(tools.map((tool) => tool.name).join(" \xB7 ")) + "\n");
|
|
211
493
|
const wsUrl = new URL("/agent", config.origin.replace(/^http/, "ws"));
|
|
212
494
|
let stopped = false;
|
|
213
495
|
let backoff = 1e3;
|
|
214
496
|
const shutdown = async () => {
|
|
215
497
|
stopped = true;
|
|
216
498
|
await core.close().catch(() => void 0);
|
|
217
|
-
|
|
499
|
+
process2.exit(0);
|
|
218
500
|
};
|
|
219
|
-
|
|
220
|
-
|
|
501
|
+
process2.on("SIGINT", shutdown);
|
|
502
|
+
process2.on("SIGTERM", shutdown);
|
|
221
503
|
while (!stopped) {
|
|
222
504
|
logLine("event", `Connecting relay ${dim(wsUrl.host)}\u2026`);
|
|
223
505
|
const ws = new WebSocket(wsUrl, {
|
|
@@ -237,9 +519,9 @@ async function connectAgent(config) {
|
|
|
237
519
|
device: {
|
|
238
520
|
id: config.deviceId,
|
|
239
521
|
name: config.deviceName,
|
|
240
|
-
platform:
|
|
241
|
-
arch:
|
|
242
|
-
hostname:
|
|
522
|
+
platform: process2.platform,
|
|
523
|
+
arch: process2.arch,
|
|
524
|
+
hostname: os2.hostname(),
|
|
243
525
|
agentVersion: VERSION
|
|
244
526
|
},
|
|
245
527
|
tools: tools.map((tool) => tool.name)
|
|
@@ -247,8 +529,8 @@ async function connectAgent(config) {
|
|
|
247
529
|
);
|
|
248
530
|
logLine("success", `Relay connected \xB7 ${bold(config.deviceName)} \xB7 ${config.mode} mode`);
|
|
249
531
|
logLine("success", "Device presence published.");
|
|
250
|
-
|
|
251
|
-
|
|
532
|
+
process2.stdout.write(" " + dim(config.origin) + "\n");
|
|
533
|
+
process2.stdout.write(" " + dim("Ctrl+C to disconnect") + "\n\n");
|
|
252
534
|
const sendHeartbeat = async () => {
|
|
253
535
|
try {
|
|
254
536
|
await fetch(new URL("/api/device/heartbeat", config.origin), {
|
|
@@ -322,7 +604,7 @@ async function connectAgent(config) {
|
|
|
322
604
|
}
|
|
323
605
|
async function main() {
|
|
324
606
|
if (argFlag("--help") || argFlag("-h")) {
|
|
325
|
-
|
|
607
|
+
process2.stdout.write(
|
|
326
608
|
[
|
|
327
609
|
"Remote Arc",
|
|
328
610
|
"",
|
|
@@ -333,8 +615,8 @@ async function main() {
|
|
|
333
615
|
" npx remotelink",
|
|
334
616
|
"",
|
|
335
617
|
"Options:",
|
|
336
|
-
" --safe
|
|
337
|
-
" --developer
|
|
618
|
+
" --safe Hard local read-only cap; dashboard cannot enable write tools",
|
|
619
|
+
" --developer Legacy alias for dashboard-managed capabilities",
|
|
338
620
|
" --reset Remove this computer's saved pairing",
|
|
339
621
|
" --version Print CLI version",
|
|
340
622
|
" --help Show this help",
|
|
@@ -344,14 +626,14 @@ async function main() {
|
|
|
344
626
|
return;
|
|
345
627
|
}
|
|
346
628
|
if (argFlag("--version") || argFlag("-v")) {
|
|
347
|
-
|
|
629
|
+
process2.stdout.write(VERSION + "\n");
|
|
348
630
|
return;
|
|
349
631
|
}
|
|
350
632
|
if (argFlag("--reset")) {
|
|
351
633
|
await resetConfig();
|
|
352
634
|
return;
|
|
353
635
|
}
|
|
354
|
-
const origin =
|
|
636
|
+
const origin = process2.env.REMOTEARC_ORIGIN || process2.env.REMOTE_LINK_ORIGIN || process2.argv.find((value) => value.startsWith("--origin="))?.slice(9) || DEFAULT_ORIGIN;
|
|
355
637
|
let config = await readConfig();
|
|
356
638
|
if (config && ["https://remote.samyao.me", "https://remotearc.app"].includes(config.origin)) {
|
|
357
639
|
config.origin = DEFAULT_ORIGIN;
|
|
@@ -361,7 +643,7 @@ async function main() {
|
|
|
361
643
|
if (!config) {
|
|
362
644
|
config = await pair(origin, selectedMode());
|
|
363
645
|
} else if (argFlag("--safe") || argFlag("--developer")) {
|
|
364
|
-
config.mode =
|
|
646
|
+
config.mode = argFlag("--safe") ? "safe" : "managed";
|
|
365
647
|
await writeConfig(config);
|
|
366
648
|
}
|
|
367
649
|
if (config) {
|
|
@@ -374,6 +656,6 @@ main().catch((error) => {
|
|
|
374
656
|
"error",
|
|
375
657
|
"Fatal: " + (error instanceof Error ? error.message : String(error))
|
|
376
658
|
);
|
|
377
|
-
|
|
659
|
+
process2.exit(1);
|
|
378
660
|
});
|
|
379
661
|
//# 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","../src/local-safety.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\";\nimport {\n assertCommandAllowed,\n createUndoSnapshot,\n discardUndoSnapshot,\n finalizeUndoSnapshot,\n undoLastChange,\n} from \"./local-safety.js\";\n\nconst VERSION = \"0.3.6\";\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 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 \"undo_last_change\",\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 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 locallyAvailable = isLocalSafeMode(this.mode) ? SAFE_TOOLS : DEVELOPER_TOOLS;\n const tools = list.tools.filter((tool) => locallyAvailable.has(tool.name));\n\n if (!isLocalSafeMode(this.mode)) {\n tools.push({\n name: \"undo_last_change\",\n description:\n \"Undo the most recent reversible file change made through Remote Arc on this device. Snapshots stay on the device and expire automatically.\",\n inputSchema: {\n type: \"object\",\n properties: {},\n additionalProperties: false,\n },\n });\n }\n\n return tools;\n }\n\n async call(name: string, args: Record<string, unknown>) {\n const locallyAvailable = isLocalSafeMode(this.mode) ? SAFE_TOOLS : DEVELOPER_TOOLS;\n if (!locallyAvailable.has(name)) {\n throw new Error(\"Tool blocked by local safety cap: \" + name);\n }\n\n if (name === \"undo_last_change\") {\n const result = await undoLastChange();\n logLine(\"success\", \"Local Undo restored the latest reversible file change.\");\n return {\n content: [{ type: \"text\" as const, text: JSON.stringify(result, null, 2) }],\n };\n }\n\n if (name === \"start_process\") {\n assertCommandAllowed(String(args.command || \"\"));\n }\n\n const reversibleTool =\n name === \"write_file\" || name === \"edit_block\"\n ? (name as \"write_file\" | \"edit_block\")\n : null;\n const snapshot = reversibleTool\n ? await createUndoSnapshot(reversibleTool, args)\n : null;\n\n if (reversibleTool && !snapshot) {\n logLine(\"warn\", \"Local Undo is unavailable for this file change.\");\n }\n\n try {\n const result = await (await this.connect()).callTool({\n name,\n arguments: args,\n });\n if (snapshot) {\n const finalized = await finalizeUndoSnapshot(snapshot);\n if (finalized) {\n logLine(\n \"info\",\n `Local Undo ready · ${dim(snapshot.id)} · kept on this device only`,\n );\n } else {\n logLine(\"warn\", \"Local Undo snapshot could not be finalized.\");\n }\n }\n return result;\n } catch (error) {\n await discardUndoSnapshot(snapshot);\n throw error;\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(\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(() => 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 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 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 UNDO_ROOT = path.join(os.homedir(), \".remotearc\", \"undo\");\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\nasync function ensureUndoRoot() {\n await fs.mkdir(UNDO_ROOT, { 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(UNDO_ROOT, { withFileTypes: true });\n return entries\n .filter((entry) => entry.isDirectory())\n .map((entry) => path.join(UNDO_ROOT, 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(UNDO_ROOT, 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)) {\n hash.update(chunk);\n }\n return hash.digest(\"hex\");\n}\n\nexport async function finalizeUndoSnapshot(snapshot: UndoSnapshot | null) {\n if (!snapshot) return false;\n try {\n const postChangeHash = await hashFile(snapshot.manifest.targetPath);\n snapshot.manifest.postChangeHash = postChangeHash;\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 = {\n pattern: RegExp;\n reason: string;\n};\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"],"mappings":"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","names":["os","path","fs","process","path","os","process","fs"]}
|