srift-transfer 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -20
- package/dist/daemon.js +349 -158
- package/dist/index.js +650 -426
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1289,12 +1289,13 @@ function withQuery(url, q) {
|
|
|
1289
1289
|
return `${url}${url.includes("?") ? "&" : "?"}${q}`;
|
|
1290
1290
|
}
|
|
1291
1291
|
function explainStatus(status, body) {
|
|
1292
|
-
|
|
1292
|
+
let msg = body.replace(/^SRIFT:\s*/, "").trim();
|
|
1293
|
+
if (/^\s*<(!doctype|html)/i.test(msg)) msg = status >= 500 ? "The SRIFT server or a proxy in front of it is temporarily unavailable \u2014 retry shortly" : "";
|
|
1294
|
+
if (status === 424 || status === 502 && /sender error: (the shared file|cannot read the shared file)/i.test(msg)) return new GetError(msg || "The sender can no longer serve this file.", "ESENDERFILE", status);
|
|
1293
1295
|
if (status === 404) return new GetError(msg || "Link not found \u2014 it expired, was revoked, or the sender went offline.", "ENOTFOUND_LINK", 404);
|
|
1294
1296
|
if (status === 410) return new GetError(msg || "Link expired or reached its download limit.", "EGONE", 410);
|
|
1295
1297
|
if (status === 503) return new GetError(msg || "Sender is offline \u2014 their daemon must be running for relay links.", "EOFFLINE", 503);
|
|
1296
1298
|
if (status === 409) return new GetError(msg || "The sender is still uploading \u2014 retry shortly.", "ENOTREADY", 409);
|
|
1297
|
-
if (status === 502 && /sender error: (the shared file|cannot read the shared file)/i.test(msg)) return new GetError(msg, "ESENDERFILE", 502);
|
|
1298
1299
|
return new GetError(`${msg || "Download failed"} (HTTP ${status})`, "EHTTP", status);
|
|
1299
1300
|
}
|
|
1300
1301
|
async function readSmallBody(res, max = 4096) {
|
|
@@ -1481,7 +1482,7 @@ async function getLink(input, opts = {}) {
|
|
|
1481
1482
|
}
|
|
1482
1483
|
const serverName = filenameFromDisposition(head["content-disposition"]) || "download.bin";
|
|
1483
1484
|
const mode = head["x-srift-mode"] ? String(head["x-srift-mode"]) : null;
|
|
1484
|
-
const total = parseInt(String(head["content-length"] || ""), 10) || 0;
|
|
1485
|
+
const total = parseInt(String(head["x-srift-size"] || head["content-length"] || ""), 10) || 0;
|
|
1485
1486
|
let received = 0;
|
|
1486
1487
|
let lastPrint = 0;
|
|
1487
1488
|
const progress = (n) => {
|
|
@@ -1744,10 +1745,10 @@ var init_skills_data = __esm({
|
|
|
1744
1745
|
"../../lib/mcp/skills-data.mjs"() {
|
|
1745
1746
|
"use strict";
|
|
1746
1747
|
SOURCES = [
|
|
1747
|
-
{ name: "encrypted-chat", content: "---\nname: encrypted-chat\ndescription: Send and read end-to-end encrypted chat messages and manage in-session file transfers within an active SRIFT session\n---\n\n# encrypted-chat\n\nSend and read end-to-end encrypted chat messages within an active SRIFT session, plus manage in-session file transfers, via the local MCP daemon.\n\n## Requirements\n\nAn active SRIFT session (see the `session-management` skill to open or join one first). No special credentials required.\n\n## Instructions\n\nAvailable MCP tools (see `/.well-known/mcp/server-card.json` for full schemas):\n\n- `srift_send_chat` \u2014 send an E2EE chat message to peers in the current session.\n- `srift_chat_history` \u2014 read decrypted chat history for the current session.\n- `srift_send_file` \u2014 offer a file to peers already inside the session (in-session transfer, distinct from `quick-share`'s standalone link flow).\n- `srift_accept_transfer` \u2014 accept and download an inbound file offer.\n- `srift_list_transfers` \u2014 list all active and recent transfers with progress.\n- `srift_read_state` \u2014 read the raw `.srift-state.json` snapshot for debugging/observability.\n\
|
|
1748
|
+
{ name: "encrypted-chat", content: "---\nname: encrypted-chat\ndescription: Send and read end-to-end encrypted chat messages and manage in-session file transfers within an active SRIFT session\n---\n\n# encrypted-chat\n\nSend and read end-to-end encrypted chat messages within an active SRIFT session, plus manage in-session file transfers, via the local MCP daemon.\n\n## Requirements\n\nAn active SRIFT session (see the `session-management` skill to open or join one first). No special credentials required.\n\n## Instructions\n\nAvailable MCP tools (see `/.well-known/mcp/server-card.json` for full schemas):\n\n- `srift_send_chat` \u2014 send an E2EE chat message to peers in the current session.\n- `srift_chat_history` \u2014 read decrypted chat history for the current session.\n- `srift_send_file` \u2014 offer a file to peers already inside the session (in-session transfer, distinct from `quick-share`'s standalone link flow).\n- `srift_accept_transfer` \u2014 accept and download an inbound file offer.\n- `srift_list_transfers` \u2014 list all active and recent transfers with progress.\n- `srift_read_state` \u2014 read the raw `.srift-state.json` snapshot for debugging/observability.\n\nYour local daemon (or the browser) encrypts messages and file chunks with AES-256-GCM before they leave the device, using keys derived from the session (plus the optional `roomSecret`); the relay server only forwards ciphertext and never receives the key. Without a `roomSecret` the key is derivable from the session ID (which the server sees); set one on both sides to make it participant-only.\n" },
|
|
1748
1749
|
{ name: "get-company-info", content: "---\nname: get-company-info\ndescription: Retrieve overview and core metadata for SRIFT and SRIPTO Corporation\n---\n\n# get-company-info\n\nRetrieve high-level overview and specifications about SRIFT and SRIPTO Corporation.\n\n## Requirements\n\nNo special credentials required.\n\n## Instructions\n\nRead the home page or `llms.txt` to find details about Srift (secure P2P transport), LetMeTeach, SRIX, and A\u039EI.\n" },
|
|
1749
1750
|
{ name: "quick-share", content: "---\nname: quick-share\ndescription: Zero-install file delivery: seed a file and get a public download URL, no session setup required on the recipient's end\n---\n\n# quick-share\n\nZero-install file delivery via SRIFT's local MCP server. Returns a public `https://srift.app/d/<token>` relay link that any recipient can open in a browser \u2014 no SRIFT install, no signup, no session-join UI required on their end. The bytes stream from the sender's machine through the srift.app relay on demand (pass-through, `Cache-Control: no-store`); nothing is stored on any server, so the link works while the sender's daemon (or process) runs.\n\n## Requirements\n\nNo credentials. The SRIFT local daemon binds to `127.0.0.1:3822` with zero authentication. Only outbound HTTPS/WSS on port 443 is needed.\n\n## Instructions\n\n1. Call the MCP tool `srift_quick_share` with `{ filePath: \"/absolute/path/to/file\" }` (single file) or `{ filePaths: [...] }` (up to 500 files/folders). Optional params: `bundle` (default: true = one .tar.gz link; false = one link per path, created in parallel), `bundleName`, `exclude` (string[] for glob patterns to skip), `sessionName`, `maxDownloads`, `ttlMs`, `encrypt`, `password`.\n2. The tool returns a `downloadUrl` (e.g. `https://srift.app/d/<token>`) when bundled, or `{ links: [], errors: [] }` when separate. With `encrypt: true` (or a `password`) each link is end-to-end encrypted with AES-256-GCM: the key rides in the `#k=` URL fragment, is never sent to a server, and the browser decrypts locally. Without it, links are protected by TLS in transit only \u2014 use `encrypt` for anything sensitive.\n3. Hand the URL(s) to the recipient. In order of preference, they can run:\n - `srift get <url> [<url> ...]` (or `npx -y srift-transfer get <url>`; proxy-aware, resumable, decrypts `#k=` links, supports multiple URLs with `--concurrency N`, works where curl is broken)\n - `wget --content-disposition <url>`\n - PowerShell `iwr <url> -OutFile <name>`\n - `curl -OJ <url>`\n - or just open it in any browser.\n4. Keep the sending side running until the recipient has downloaded. From a shell, `srift quick-share <file> [<file> ...] --wait` blocks until then (`--wait-timeout 30m` exits with code 3 on timeout); `--keep-alive` serves until the TTL expires.\n\n## Sandboxed and datacenter agents\n\nIf the background daemon cannot start (the sandbox forbids local servers, blocks detached spawns or loopback, or the port is taken), `srift mcp` and `srift quick-share` host the daemon inside their own process: no local port, outbound HTTPS/WSS on 443 only. The MCP server switches automatically, so this tool keeps working. From a shell, run quick-share in the background (it exits after the download) or with `--wait`; `--foreground` forces in-process serving. Blocked UDP never breaks links. Run `srift doctor` (or the `srift_net_diagnose` tool) to diagnose connectivity; `HTTPS_PROXY`/`NO_PROXY` and `NODE_EXTRA_CA_CERTS` are honoured.\n\nSee `/openapi.json` and `/.well-known/mcp/server-card.json` for the full tool contract.\n" },
|
|
1750
|
-
{ name: "session-management", content: '---\nname: session-management\ndescription: Create, join, monitor, and control SRIFT end-to-end encrypted P2P sessions via the local MCP daemon\n---\n\n# session-management\n\nCreate, join, monitor, and control SRIFT end-to-end encrypted P2P sessions ("rooms") via the local MCP daemon. Covers the full session lifecycle used for in-session file transfer and encrypted chat.\n\n## Requirements\n\nNo special credentials required. The SRIFT local daemon binds to `127.0.0.1:3822` and requires zero authentication.\n\n## Instructions\n\nAvailable MCP tools (see `/.well-known/mcp/server-card.json` for full schemas):\n\n- `srift_start_session` \u2014 open a new E2EE room as host.\n- `srift_join_session` \u2014 join an existing room by its 7-character session ID.\n- `srift_session_status` \u2014 read current session, role, connected peers, and pending join requests.\n- `srift_approve_join` / `srift_reject_join` \u2014 host-only: approve or reject a guest\'s join request.\n- `srift_kick_user` \u2014 host-only:
|
|
1751
|
+
{ name: "session-management", content: '---\nname: session-management\ndescription: Create, join, monitor, and control SRIFT end-to-end encrypted P2P sessions via the local MCP daemon\n---\n\n# session-management\n\nCreate, join, monitor, and control SRIFT end-to-end encrypted P2P sessions ("rooms") via the local MCP daemon. Covers the full session lifecycle used for in-session file transfer and encrypted chat.\n\n## Requirements\n\nNo special credentials required. The SRIFT local daemon binds to `127.0.0.1:3822` and requires zero authentication.\n\n## Instructions\n\nAvailable MCP tools (see `/.well-known/mcp/server-card.json` for full schemas):\n\n- `srift_start_session` \u2014 open a new E2EE room as host.\n- `srift_join_session` \u2014 join an existing room by its 7-character session ID.\n- `srift_session_status` \u2014 read current session, role, connected peers, and pending join requests.\n- `srift_approve_join` / `srift_reject_join` \u2014 host-only: approve or reject a guest\'s join request.\n- `srift_kick_user` \u2014 host-only: remove a peer. Get their `userId` from `srift_session_status` \u2192 `participants`.\n- `srift_close_session` \u2014 tear down the session and flush all encryption keys.\n\nSessions are ephemeral: session metadata and chat ciphertext are kept server-side only until the session is deleted (host close or 7 days of inactivity), and files are never stored. Keys are derived locally via PBKDF2-SHA256 from the session ID (plus the optional `roomSecret`) and are never sent to the server; without a `roomSecret` the key is derivable from the session ID, which the server sees, so pass one to `srift_start_session` / `srift_join_session` to make it participant-only.\n' }
|
|
1751
1752
|
];
|
|
1752
1753
|
MCP_SKILLS = SOURCES.map(({ name, content }) => {
|
|
1753
1754
|
const uri = `skill://${name}/SKILL.md`;
|
|
@@ -2006,7 +2007,7 @@ var init_core = __esm({
|
|
|
2006
2007
|
type: "object",
|
|
2007
2008
|
properties: {
|
|
2008
2009
|
sessionName: { type: "string", description: 'Human-readable session name (optional, e.g. "Project Collab")' },
|
|
2009
|
-
roomSecret: { type: "string", description: "Optional shared secret
|
|
2010
|
+
roomSecret: { type: "string", description: "Optional shared secret mixed into key derivation. Never sent to server. Without it the key is derivable from the session ID (which the server sees); set it to make the key participant-only." }
|
|
2010
2011
|
}
|
|
2011
2012
|
},
|
|
2012
2013
|
outputSchema: {
|
|
@@ -2057,15 +2058,37 @@ var init_core = __esm({
|
|
|
2057
2058
|
{
|
|
2058
2059
|
name: "srift_session_status",
|
|
2059
2060
|
title: "Get Session Status",
|
|
2060
|
-
description: "Get current session details: id, role (host/guest), connection state, pending join requests,
|
|
2061
|
+
description: "Get current session details: session { id, role (host/guest), connection state, your userId }, pending join requests (approve with srift_approve_join), and members with their userIds (remove one with srift_kick_user).",
|
|
2061
2062
|
inputSchema: { type: "object", properties: {} },
|
|
2062
2063
|
outputSchema: {
|
|
2063
2064
|
type: "object",
|
|
2064
2065
|
properties: {
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2066
|
+
session: {
|
|
2067
|
+
type: "object",
|
|
2068
|
+
description: "The active session (id is null when there is none)",
|
|
2069
|
+
properties: {
|
|
2070
|
+
id: { type: ["string", "null"], description: "Active session ID" },
|
|
2071
|
+
name: { type: ["string", "null"], description: "Session name" },
|
|
2072
|
+
role: { type: ["string", "null"], description: '"host", "guest", or null' },
|
|
2073
|
+
isConnected: { type: "boolean", description: "True if connected to the signaling server" },
|
|
2074
|
+
userId: { type: ["string", "null"], description: "Your user id in the session" },
|
|
2075
|
+
status: { type: "string", description: 'Guest only: "pending_approval" or "approved" (hosted endpoint)' }
|
|
2076
|
+
}
|
|
2077
|
+
},
|
|
2078
|
+
peerCount: { type: "number", description: "Number of connected sockets in the room (hosted endpoint)" },
|
|
2079
|
+
participants: {
|
|
2080
|
+
type: "array",
|
|
2081
|
+
description: "Session members (local daemon): pass userId to srift_kick_user to remove one",
|
|
2082
|
+
items: {
|
|
2083
|
+
type: "object",
|
|
2084
|
+
properties: {
|
|
2085
|
+
userId: { type: "string" },
|
|
2086
|
+
username: { type: "string" },
|
|
2087
|
+
isHost: { type: "boolean" },
|
|
2088
|
+
online: { type: "boolean" }
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
},
|
|
2069
2092
|
pendingJoins: {
|
|
2070
2093
|
type: "array",
|
|
2071
2094
|
description: "List of pending guest join requests waiting for host approval",
|
|
@@ -2159,7 +2182,7 @@ var init_core = __esm({
|
|
|
2159
2182
|
{
|
|
2160
2183
|
name: "srift_kick_user",
|
|
2161
2184
|
title: "Kick Peer",
|
|
2162
|
-
description: "Host-only. Disconnect and
|
|
2185
|
+
description: "Host-only. Disconnect and remove a peer from the active room. Get the peer's userId from srift_session_status (participants).",
|
|
2163
2186
|
inputSchema: {
|
|
2164
2187
|
type: "object",
|
|
2165
2188
|
properties: {
|
|
@@ -2427,6 +2450,8 @@ var init_core = __esm({
|
|
|
2427
2450
|
type: "object",
|
|
2428
2451
|
properties: {
|
|
2429
2452
|
ok: { type: "boolean", description: "True if the diagnostic ran successfully (independent of what it found)" },
|
|
2453
|
+
base: { type: "string", description: "The SRIFT server that was probed" },
|
|
2454
|
+
checkedAt: { type: "string", description: "When the probe ran (ISO 8601); cached results show an older time" },
|
|
2430
2455
|
verdict: { type: "string", description: '"ok", "degraded", or "blocked"' },
|
|
2431
2456
|
rungs: {
|
|
2432
2457
|
type: "array",
|
|
@@ -2572,8 +2597,13 @@ session-management, and get-company-info, \`skills/get\` to fetch one by URI, an
|
|
|
2572
2597
|
## Security model
|
|
2573
2598
|
- AES-256-GCM, 12-byte random IV per message.
|
|
2574
2599
|
- PBKDF2-SHA256, 100,000 iterations.
|
|
2575
|
-
- All keys derive locally from sessionId (+ optional roomSecret).
|
|
2576
|
-
|
|
2600
|
+
- All keys derive locally from sessionId (+ optional roomSecret). The key is never sent to the
|
|
2601
|
+
server, but the server sees the sessionId: without a roomSecret the key is derivable from it.
|
|
2602
|
+
Pass roomSecret to srift_start_session / srift_join_session to make the key participant-only.
|
|
2603
|
+
- Quick-share links are end-to-end encrypted only with encrypt: true (key in the #k= fragment);
|
|
2604
|
+
otherwise TLS in transit only, zero retention.
|
|
2605
|
+
- The CLI daemon moves file chunks over the AES-256-GCM WebSocket relay (browsers use WebRTC for
|
|
2606
|
+
files >5 MB, with the relay as fallback).
|
|
2577
2607
|
|
|
2578
2608
|
## Local daemon vs hosted MCP
|
|
2579
2609
|
If you are talking to the hosted endpoint (https://srift.app/mcp) instead of a local
|
|
@@ -2590,6 +2620,8 @@ for full file-transfer + chat capability.
|
|
|
2590
2620
|
|
|
2591
2621
|
// ../../cli/mcp.ts
|
|
2592
2622
|
import http3 from "http";
|
|
2623
|
+
import fs6 from "fs";
|
|
2624
|
+
import path5 from "path";
|
|
2593
2625
|
function callDaemon(endpoint, method, body) {
|
|
2594
2626
|
const emb = embeddedCall(method, endpoint, body);
|
|
2595
2627
|
if (emb) return emb.then((r) => {
|
|
@@ -2640,43 +2672,60 @@ async function callTool(name, args) {
|
|
|
2640
2672
|
sessionName: args.sessionName,
|
|
2641
2673
|
roomSecret: args.roomSecret
|
|
2642
2674
|
});
|
|
2643
|
-
|
|
2675
|
+
const joinUrl = res.joinUrl || `https://srift.app/join-session?id=${res.sessionId}`;
|
|
2676
|
+
return {
|
|
2677
|
+
text: `Session created.
|
|
2644
2678
|
Session ID: ${res.sessionId}
|
|
2645
|
-
Share URL:
|
|
2646
|
-
You are the HOST. Approve incoming joins with srift_approve_join
|
|
2679
|
+
Share URL: ${joinUrl}
|
|
2680
|
+
You are the HOST. Approve incoming joins with srift_approve_join.`,
|
|
2681
|
+
structured: { sessionId: res.sessionId, url: joinUrl, role: "host" }
|
|
2682
|
+
};
|
|
2647
2683
|
}
|
|
2648
2684
|
case "srift_join_session": {
|
|
2649
2685
|
const res = await callDaemon("/session/join", "POST", args);
|
|
2650
|
-
return
|
|
2686
|
+
return {
|
|
2687
|
+
text: `Join requested for session ${args.sessionId}. Status: ${res.success ? "awaiting host approval" : "failed"}.`,
|
|
2688
|
+
structured: { sessionId: args.sessionId, status: res.success ? "awaiting_approval" : "failed" }
|
|
2689
|
+
};
|
|
2651
2690
|
}
|
|
2652
2691
|
case "srift_session_status": {
|
|
2653
2692
|
const res = await callDaemon("/status", "GET");
|
|
2654
|
-
return JSON.stringify({ session: res.session, pendingJoins: res.pendingJoins }, null, 2);
|
|
2693
|
+
return JSON.stringify({ session: res.session, pendingJoins: res.pendingJoins, participants: res.participants || [] }, null, 2);
|
|
2655
2694
|
}
|
|
2656
2695
|
case "srift_close_session":
|
|
2657
2696
|
await callDaemon("/session/close", "POST");
|
|
2658
|
-
return "Session closed. Keys flushed.";
|
|
2697
|
+
return { text: "Session closed. Keys flushed.", structured: { success: true } };
|
|
2659
2698
|
case "srift_approve_join":
|
|
2660
2699
|
await callDaemon("/session/approve", "POST", args);
|
|
2661
|
-
return `Approved ${args.tempUserId}
|
|
2700
|
+
return { text: `Approved ${args.tempUserId}.`, structured: { success: true, tempUserId: args.tempUserId } };
|
|
2662
2701
|
case "srift_reject_join":
|
|
2663
2702
|
await callDaemon("/session/reject", "POST", args);
|
|
2664
|
-
return `Rejected ${args.tempUserId}
|
|
2703
|
+
return { text: `Rejected ${args.tempUserId}.`, structured: { success: true, tempUserId: args.tempUserId } };
|
|
2665
2704
|
case "srift_kick_user":
|
|
2666
2705
|
await callDaemon("/session/kick", "POST", args);
|
|
2667
|
-
return `Kicked ${args.userId}
|
|
2706
|
+
return { text: `Kicked ${args.userId}.`, structured: { success: true, userId: args.userId } };
|
|
2668
2707
|
case "srift_send_file": {
|
|
2669
2708
|
const res = await callDaemon("/send", "POST", args);
|
|
2670
|
-
return
|
|
2709
|
+
return {
|
|
2710
|
+
text: `File offered.
|
|
2671
2711
|
File ID: ${res.fileId}
|
|
2672
|
-
The peer must call srift_accept_transfer with this fileId
|
|
2712
|
+
The peer must call srift_accept_transfer with this fileId.`,
|
|
2713
|
+
structured: { fileId: res.fileId, fileName: path5.basename(String(args.filePath || "")), fileSize: (() => {
|
|
2714
|
+
try {
|
|
2715
|
+
return fs6.statSync(args.filePath).size;
|
|
2716
|
+
} catch {
|
|
2717
|
+
return void 0;
|
|
2718
|
+
}
|
|
2719
|
+
})() }
|
|
2720
|
+
};
|
|
2673
2721
|
}
|
|
2674
2722
|
case "srift_accept_transfer":
|
|
2675
2723
|
await callDaemon("/receive", "POST", args);
|
|
2676
|
-
return `Transfer ${args.fileId} accepted. Downloading\u2026
|
|
2724
|
+
return { text: `Transfer ${args.fileId} accepted. Downloading\u2026`, structured: { success: true, fileId: args.fileId } };
|
|
2677
2725
|
case "srift_list_transfers": {
|
|
2678
2726
|
const res = await callDaemon("/status", "GET");
|
|
2679
|
-
|
|
2727
|
+
const transfers = res.activeTransfers || [];
|
|
2728
|
+
return { text: JSON.stringify(transfers, null, 2), structured: { transfers } };
|
|
2680
2729
|
}
|
|
2681
2730
|
case "srift_quick_share": {
|
|
2682
2731
|
const multi = Array.isArray(args?.filePaths) && args.filePaths.length > 0;
|
|
@@ -2770,10 +2819,11 @@ The peer must call srift_accept_transfer with this fileId.`;
|
|
|
2770
2819
|
}
|
|
2771
2820
|
case "srift_send_chat":
|
|
2772
2821
|
await callDaemon("/chat/send", "POST", args);
|
|
2773
|
-
return `Sent (E2EE): "${args.message}"
|
|
2822
|
+
return { text: `Sent (E2EE): "${args.message}"`, structured: { success: true, timestamp: (/* @__PURE__ */ new Date()).toISOString() } };
|
|
2774
2823
|
case "srift_chat_history": {
|
|
2775
2824
|
const res = await callDaemon("/chat/history", "GET");
|
|
2776
|
-
|
|
2825
|
+
const messages = Array.isArray(res) ? res : res?.messages || [];
|
|
2826
|
+
return { text: JSON.stringify(res, null, 2), structured: { messages } };
|
|
2777
2827
|
}
|
|
2778
2828
|
case "srift_read_state": {
|
|
2779
2829
|
const res = await callDaemon("/state", "GET");
|
|
@@ -2846,7 +2896,7 @@ var init_mcp = __esm({
|
|
|
2846
2896
|
SERVER_INFO = {
|
|
2847
2897
|
name: "srift-mcp-server",
|
|
2848
2898
|
title: "SRIFT Secure P2P File Transfer",
|
|
2849
|
-
version: "4.
|
|
2899
|
+
version: "4.1.0"
|
|
2850
2900
|
};
|
|
2851
2901
|
localDaemonBackend = { callTool, readResource };
|
|
2852
2902
|
handleMcpMessage = createMcpHandler({
|
|
@@ -2858,8 +2908,8 @@ var init_mcp = __esm({
|
|
|
2858
2908
|
});
|
|
2859
2909
|
|
|
2860
2910
|
// ../../cli/pack.ts
|
|
2861
|
-
import
|
|
2862
|
-
import
|
|
2911
|
+
import fs7 from "fs";
|
|
2912
|
+
import path6 from "path";
|
|
2863
2913
|
import zlib from "zlib";
|
|
2864
2914
|
import { pipeline } from "stream/promises";
|
|
2865
2915
|
import { Readable } from "stream";
|
|
@@ -2883,16 +2933,16 @@ function normFileMode(m) {
|
|
|
2883
2933
|
}
|
|
2884
2934
|
function listFiles(root, excludes = DEFAULT_EXCLUDES) {
|
|
2885
2935
|
const pats = excludes.map(globToRegExp);
|
|
2886
|
-
const excluded = (rel) => pats.some((p) => p.test(rel) || p.test(
|
|
2936
|
+
const excluded = (rel) => pats.some((p) => p.test(rel) || p.test(path6.posix.basename(rel)));
|
|
2887
2937
|
const out = [];
|
|
2888
2938
|
const walk = (dirAbs, dirRel) => {
|
|
2889
|
-
const ents =
|
|
2939
|
+
const ents = fs7.readdirSync(dirAbs, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
2890
2940
|
for (const e of ents) {
|
|
2891
2941
|
const rel = dirRel ? `${dirRel}/${e.name}` : e.name;
|
|
2892
2942
|
if (excluded(rel)) continue;
|
|
2893
|
-
const abs =
|
|
2943
|
+
const abs = path6.join(dirAbs, e.name);
|
|
2894
2944
|
if (e.isSymbolicLink()) continue;
|
|
2895
|
-
const st =
|
|
2945
|
+
const st = fs7.lstatSync(abs);
|
|
2896
2946
|
if (e.isDirectory()) {
|
|
2897
2947
|
out.push({ abs, rel, size: 0, mode: 493, mtime: Math.floor(st.mtimeMs / 1e3), dir: true });
|
|
2898
2948
|
walk(abs, rel);
|
|
@@ -2954,7 +3004,7 @@ async function* tarStream(entries, prefix) {
|
|
|
2954
3004
|
yield* entryHeaders(e, prefix);
|
|
2955
3005
|
if (e.dir) continue;
|
|
2956
3006
|
let written = 0;
|
|
2957
|
-
const stream =
|
|
3007
|
+
const stream = fs7.createReadStream(e.abs);
|
|
2958
3008
|
stream.on("error", () => {
|
|
2959
3009
|
});
|
|
2960
3010
|
try {
|
|
@@ -2975,17 +3025,17 @@ async function* tarStream(entries, prefix) {
|
|
|
2975
3025
|
yield Buffer.alloc(BLOCK * 2);
|
|
2976
3026
|
}
|
|
2977
3027
|
async function packDirectory(dir, outFile, excludes = DEFAULT_EXCLUDES) {
|
|
2978
|
-
const root =
|
|
3028
|
+
const root = path6.resolve(dir);
|
|
2979
3029
|
const entries = listFiles(root, excludes);
|
|
2980
3030
|
const files = entries.filter((e) => !e.dir);
|
|
2981
3031
|
if (!files.length) throw new Error(`Nothing to share in ${root} (empty, or everything was excluded)`);
|
|
2982
|
-
const prefix =
|
|
2983
|
-
await pipeline(Readable.from(tarStream(entries, prefix)), zlib.createGzip({ level: 6 }),
|
|
3032
|
+
const prefix = path6.basename(root) || "folder";
|
|
3033
|
+
await pipeline(Readable.from(tarStream(entries, prefix)), zlib.createGzip({ level: 6 }), fs7.createWriteStream(outFile, { mode: 384 }));
|
|
2984
3034
|
return { files: files.length, bytes: files.reduce((a, e) => a + e.size, 0) };
|
|
2985
3035
|
}
|
|
2986
3036
|
function readable(p) {
|
|
2987
3037
|
try {
|
|
2988
|
-
|
|
3038
|
+
fs7.accessSync(p, fs7.constants.R_OK);
|
|
2989
3039
|
return null;
|
|
2990
3040
|
} catch (e) {
|
|
2991
3041
|
return e?.code || "unreadable";
|
|
@@ -2999,7 +3049,7 @@ function listPaths(paths, excludes = DEFAULT_EXCLUDES, skipped = []) {
|
|
|
2999
3049
|
used.add(name.toLowerCase());
|
|
3000
3050
|
return name;
|
|
3001
3051
|
}
|
|
3002
|
-
const ext =
|
|
3052
|
+
const ext = path6.extname(name);
|
|
3003
3053
|
const stem2 = name.slice(0, name.length - ext.length);
|
|
3004
3054
|
for (let i = 2; ; i++) {
|
|
3005
3055
|
const c = `${stem2} (${i})${ext}`;
|
|
@@ -3010,10 +3060,10 @@ function listPaths(paths, excludes = DEFAULT_EXCLUDES, skipped = []) {
|
|
|
3010
3060
|
}
|
|
3011
3061
|
};
|
|
3012
3062
|
for (const p of paths) {
|
|
3013
|
-
const abs =
|
|
3063
|
+
const abs = path6.resolve(p);
|
|
3014
3064
|
let st;
|
|
3015
3065
|
try {
|
|
3016
|
-
st =
|
|
3066
|
+
st = fs7.statSync(abs);
|
|
3017
3067
|
} catch (e) {
|
|
3018
3068
|
skipped.push({ path: abs, reason: e?.code === "ENOENT" ? "not found" : e?.code || "stat failed" });
|
|
3019
3069
|
continue;
|
|
@@ -3026,7 +3076,7 @@ function listPaths(paths, excludes = DEFAULT_EXCLUDES, skipped = []) {
|
|
|
3026
3076
|
skipped.push({ path: abs, reason: e?.code || "unreadable folder" });
|
|
3027
3077
|
continue;
|
|
3028
3078
|
}
|
|
3029
|
-
const name = unique(
|
|
3079
|
+
const name = unique(path6.basename(abs) || "item");
|
|
3030
3080
|
out.push({ abs, rel: name, size: 0, mode: 493, mtime: Math.floor(st.mtimeMs / 1e3), dir: true });
|
|
3031
3081
|
for (const e of inner) {
|
|
3032
3082
|
const why = e.dir ? null : readable(e.abs);
|
|
@@ -3042,7 +3092,7 @@ function listPaths(paths, excludes = DEFAULT_EXCLUDES, skipped = []) {
|
|
|
3042
3092
|
skipped.push({ path: abs, reason: why });
|
|
3043
3093
|
continue;
|
|
3044
3094
|
}
|
|
3045
|
-
out.push({ abs, rel: unique(
|
|
3095
|
+
out.push({ abs, rel: unique(path6.basename(abs) || "item"), size: st.size, mode: normFileMode(st.mode), mtime: Math.floor(st.mtimeMs / 1e3), dir: false });
|
|
3046
3096
|
} else {
|
|
3047
3097
|
skipped.push({ path: abs, reason: "not a regular file or folder" });
|
|
3048
3098
|
}
|
|
@@ -3059,7 +3109,7 @@ async function packPaths(paths, outFile, prefix, excludes = DEFAULT_EXCLUDES) {
|
|
|
3059
3109
|
throw new Error(`Nothing to share${skipped.length ? ` \u2014 skipped: ${skipped.map((x) => `${x.path} (${x.reason})`).join(", ")}` : " (empty folders, or everything was excluded)"}`);
|
|
3060
3110
|
}
|
|
3061
3111
|
try {
|
|
3062
|
-
await pipeline(Readable.from(tarStream(entries, prefix)), zlib.createGzip({ level: 6 }),
|
|
3112
|
+
await pipeline(Readable.from(tarStream(entries, prefix)), zlib.createGzip({ level: 6 }), fs7.createWriteStream(outFile, { mode: 384 }));
|
|
3063
3113
|
return { files: files.length, bytes: files.reduce((a, e) => a + e.size, 0), skipped };
|
|
3064
3114
|
} catch (err) {
|
|
3065
3115
|
const bad = err?.packEntry;
|
|
@@ -3100,10 +3150,10 @@ import express from "express";
|
|
|
3100
3150
|
import cors from "cors";
|
|
3101
3151
|
import { WebSocket as WebSocket2 } from "ws";
|
|
3102
3152
|
import { webcrypto } from "crypto";
|
|
3103
|
-
import
|
|
3153
|
+
import fs8 from "fs";
|
|
3104
3154
|
import http4 from "http";
|
|
3105
3155
|
import { Duplex } from "stream";
|
|
3106
|
-
import
|
|
3156
|
+
import path7 from "path";
|
|
3107
3157
|
import os4 from "os";
|
|
3108
3158
|
import { v4 as uuidv4 } from "uuid";
|
|
3109
3159
|
import dotenv from "dotenv";
|
|
@@ -3234,6 +3284,46 @@ async function decrypt(encB64) {
|
|
|
3234
3284
|
const decrypted = await crypto5.subtle.decrypt({ name: "AES-GCM", iv: iv2 }, encryptionKey, ciphertext);
|
|
3235
3285
|
return new TextDecoder().decode(decrypted);
|
|
3236
3286
|
}
|
|
3287
|
+
function relayKey() {
|
|
3288
|
+
if (!session.id) return Promise.reject(new Error("No active session"));
|
|
3289
|
+
if (relayKeyCache?.sessionId !== session.id) relayKeyCache = { sessionId: session.id, key: deriveKey(session.id) };
|
|
3290
|
+
return relayKeyCache.key;
|
|
3291
|
+
}
|
|
3292
|
+
function relayAad(fileId, chunkIndex, totalChunks) {
|
|
3293
|
+
return new TextEncoder().encode(`${RELAY_ENC}|${fileId}|${chunkIndex}|${totalChunks}`);
|
|
3294
|
+
}
|
|
3295
|
+
async function sealRelayChunk(plain, fileId, chunkIndex, totalChunks) {
|
|
3296
|
+
const iv2 = crypto5.getRandomValues(new Uint8Array(IV_LEN));
|
|
3297
|
+
const ct = new Uint8Array(await crypto5.subtle.encrypt(
|
|
3298
|
+
{ name: "AES-GCM", iv: iv2, additionalData: relayAad(fileId, chunkIndex, totalChunks) },
|
|
3299
|
+
await relayKey(),
|
|
3300
|
+
plain
|
|
3301
|
+
));
|
|
3302
|
+
const out = new Uint8Array(IV_LEN + ct.length);
|
|
3303
|
+
out.set(iv2);
|
|
3304
|
+
out.set(ct, IV_LEN);
|
|
3305
|
+
return Array.from(out);
|
|
3306
|
+
}
|
|
3307
|
+
async function openRelayChunk(p) {
|
|
3308
|
+
if (p.enc === void 0 || p.enc === null) {
|
|
3309
|
+
if (typeof p.chunk === "string") return Buffer.from(p.chunk, "base64");
|
|
3310
|
+
if (Array.isArray(p.chunk)) return Buffer.from(p.chunk);
|
|
3311
|
+
throw new Error("relay chunk is neither base64 nor a byte array");
|
|
3312
|
+
}
|
|
3313
|
+
if (p.enc !== RELAY_ENC) throw new Error(`unsupported relay encryption scheme: ${String(p.enc)}`);
|
|
3314
|
+
if (!Array.isArray(p.chunk)) throw new Error("sealed relay chunk is not a byte array");
|
|
3315
|
+
const data = Uint8Array.from(p.chunk);
|
|
3316
|
+
if (data.length < IV_LEN + 16) throw new Error("sealed relay chunk too short");
|
|
3317
|
+
try {
|
|
3318
|
+
return Buffer.from(await crypto5.subtle.decrypt(
|
|
3319
|
+
{ name: "AES-GCM", iv: data.slice(0, IV_LEN), additionalData: relayAad(p.fileId, p.chunkIndex, p.totalChunks) },
|
|
3320
|
+
await relayKey(),
|
|
3321
|
+
data.slice(IV_LEN)
|
|
3322
|
+
));
|
|
3323
|
+
} catch {
|
|
3324
|
+
throw new Error(`relay chunk ${p.chunkIndex} of ${p.fileId} failed authentication (wrong session key or tampered data)`);
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3237
3327
|
function getTrackers(sessionId) {
|
|
3238
3328
|
const list = [];
|
|
3239
3329
|
try {
|
|
@@ -3272,13 +3362,13 @@ function writeStateFile() {
|
|
|
3272
3362
|
})),
|
|
3273
3363
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
3274
3364
|
};
|
|
3275
|
-
|
|
3365
|
+
fs8.writeFileSync(STATE_PATH, JSON.stringify(stateData, null, 2), "utf-8");
|
|
3276
3366
|
}
|
|
3277
3367
|
function pruneStaleStateOnStartup() {
|
|
3278
|
-
if (!
|
|
3368
|
+
if (!fs8.existsSync(STATE_PATH)) return;
|
|
3279
3369
|
let prunedCount = 0;
|
|
3280
3370
|
try {
|
|
3281
|
-
const raw =
|
|
3371
|
+
const raw = fs8.readFileSync(STATE_PATH, "utf-8");
|
|
3282
3372
|
const parsed = JSON.parse(raw);
|
|
3283
3373
|
const now = Date.now();
|
|
3284
3374
|
const lastUpdatedMs = parsed?.lastUpdated ? Date.parse(parsed.lastUpdated) : NaN;
|
|
@@ -3297,7 +3387,7 @@ function pruneStaleStateOnStartup() {
|
|
|
3297
3387
|
if (sessionStale || freshTransfers.length !== transfers.length) {
|
|
3298
3388
|
parsed.activeTransfers = freshTransfers;
|
|
3299
3389
|
parsed.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
3300
|
-
|
|
3390
|
+
fs8.writeFileSync(STATE_PATH, JSON.stringify(parsed, null, 2), "utf-8");
|
|
3301
3391
|
}
|
|
3302
3392
|
if (prunedCount > 0) {
|
|
3303
3393
|
console.log(
|
|
@@ -3307,7 +3397,7 @@ function pruneStaleStateOnStartup() {
|
|
|
3307
3397
|
} catch (err) {
|
|
3308
3398
|
console.warn("[DAEMON] Stale state file was unreadable, removing it:", err?.message || err);
|
|
3309
3399
|
try {
|
|
3310
|
-
|
|
3400
|
+
fs8.unlinkSync(STATE_PATH);
|
|
3311
3401
|
} catch {
|
|
3312
3402
|
}
|
|
3313
3403
|
}
|
|
@@ -3328,8 +3418,11 @@ function connectWebSocket(wsTargetUrl) {
|
|
|
3328
3418
|
}
|
|
3329
3419
|
csrfToken = null;
|
|
3330
3420
|
console.log(`[DAEMON] Connecting to Signaler WS: ${wsTargetUrl}`);
|
|
3331
|
-
|
|
3332
|
-
wsConn
|
|
3421
|
+
const sock = new WebSocket2(wsTargetUrl, { agent: agentFor(wsTargetUrl) });
|
|
3422
|
+
wsConn = sock;
|
|
3423
|
+
const current = () => wsConn === sock;
|
|
3424
|
+
sock.on("open", () => {
|
|
3425
|
+
if (!current()) return;
|
|
3333
3426
|
console.log("[DAEMON] WebSocket connection open");
|
|
3334
3427
|
session.isConnected = true;
|
|
3335
3428
|
writeStateFile();
|
|
@@ -3367,7 +3460,8 @@ function connectWebSocket(wsTargetUrl) {
|
|
|
3367
3460
|
}
|
|
3368
3461
|
}, 3e4);
|
|
3369
3462
|
});
|
|
3370
|
-
|
|
3463
|
+
sock.on("message", async (dataStr) => {
|
|
3464
|
+
if (!current()) return;
|
|
3371
3465
|
try {
|
|
3372
3466
|
const msg = JSON.parse(dataStr);
|
|
3373
3467
|
const { type, payload } = msg;
|
|
@@ -3400,6 +3494,30 @@ function connectWebSocket(wsTargetUrl) {
|
|
|
3400
3494
|
case "heartbeat_ack":
|
|
3401
3495
|
lastHeartbeatAckAt = Date.now();
|
|
3402
3496
|
break;
|
|
3497
|
+
case "user_list": {
|
|
3498
|
+
const users = Array.isArray(payload?.users) ? payload.users : [];
|
|
3499
|
+
participants = users.map((u) => ({ userId: String(u.id), username: String(u.username || ""), isHost: !!u.is_host, online: !!u.isOnline }));
|
|
3500
|
+
broadcastSSE("participants", { participants });
|
|
3501
|
+
if (payload?.messageId) {
|
|
3502
|
+
try {
|
|
3503
|
+
sock.send(JSON.stringify({ type: "user_list_ack", payload: { messageId: payload.messageId } }));
|
|
3504
|
+
} catch {
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
break;
|
|
3508
|
+
}
|
|
3509
|
+
case "kicked_from_session":
|
|
3510
|
+
console.log(`[DAEMON] Kicked from session: ${payload?.reason || ""}`);
|
|
3511
|
+
sessionTerminated = true;
|
|
3512
|
+
terminationReason = payload?.reason || "You were removed from the session by the host";
|
|
3513
|
+
session.isConnected = false;
|
|
3514
|
+
session.userId = null;
|
|
3515
|
+
csrfToken = null;
|
|
3516
|
+
pendingJoins = [];
|
|
3517
|
+
participants = [];
|
|
3518
|
+
writeStateFile();
|
|
3519
|
+
broadcastSSE("session_terminated", { reason: terminationReason });
|
|
3520
|
+
break;
|
|
3403
3521
|
case "session_deleted":
|
|
3404
3522
|
console.log(`[DAEMON] Session deleted by host: ${payload.reason || ""}`);
|
|
3405
3523
|
sessionTerminated = true;
|
|
@@ -3407,6 +3525,7 @@ function connectWebSocket(wsTargetUrl) {
|
|
|
3407
3525
|
session = { id: null, name: null, role: null, isConnected: false, userId: null };
|
|
3408
3526
|
activeTransfers = [];
|
|
3409
3527
|
pendingJoins = [];
|
|
3528
|
+
participants = [];
|
|
3410
3529
|
encryptionKey = null;
|
|
3411
3530
|
writeStateFile();
|
|
3412
3531
|
broadcastSSE("session_terminated", { reason: terminationReason });
|
|
@@ -3423,6 +3542,7 @@ function connectWebSocket(wsTargetUrl) {
|
|
|
3423
3542
|
session.isConnected = false;
|
|
3424
3543
|
session.userId = null;
|
|
3425
3544
|
pendingJoins = [];
|
|
3545
|
+
participants = [];
|
|
3426
3546
|
writeStateFile();
|
|
3427
3547
|
broadcastSSE("session_terminated", { reason: terminationReason });
|
|
3428
3548
|
}
|
|
@@ -3477,14 +3597,41 @@ function connectWebSocket(wsTargetUrl) {
|
|
|
3477
3597
|
break;
|
|
3478
3598
|
case "file_accept":
|
|
3479
3599
|
console.log(`[DAEMON] File offer accepted by peer! ID: ${payload.fileId}`);
|
|
3600
|
+
{
|
|
3601
|
+
const acceptedTx = activeTransfers.find((t) => t.fileId === payload.fileId);
|
|
3602
|
+
if (acceptedTx) acceptedTx.peerRelayEnc = payload.relayEnc === RELAY_ENC ? RELAY_ENC : void 0;
|
|
3603
|
+
}
|
|
3480
3604
|
startUpload(payload.fileId, payload.userId);
|
|
3481
3605
|
break;
|
|
3482
3606
|
case "webtorrent_info_hash":
|
|
3483
3607
|
handleWebTorrentInfoHash(payload);
|
|
3484
3608
|
break;
|
|
3485
3609
|
case "file_chunk":
|
|
3486
|
-
handleIncomingChunk(payload)
|
|
3610
|
+
handleIncomingChunk(payload).catch((e) => {
|
|
3611
|
+
console.error(`[DAEMON] Relay chunk rejected: ${e?.message || e}`);
|
|
3612
|
+
const bad = activeTransfers.find((t) => t.fileId === payload?.fileId);
|
|
3613
|
+
if (bad) {
|
|
3614
|
+
bad.status = "error";
|
|
3615
|
+
writeStateFile();
|
|
3616
|
+
}
|
|
3617
|
+
try {
|
|
3618
|
+
sock.send(JSON.stringify({ type: "file_cancel_by_receiver", payload: { fileId: payload?.fileId } }));
|
|
3619
|
+
} catch {
|
|
3620
|
+
}
|
|
3621
|
+
});
|
|
3487
3622
|
break;
|
|
3623
|
+
case "file_cancelled_by_receiver":
|
|
3624
|
+
case "file_cancelled_by_sender":
|
|
3625
|
+
case "file_cancelled_all": {
|
|
3626
|
+
const gone = activeTransfers.find((t) => t.fileId === payload?.fileId);
|
|
3627
|
+
activeUploads.delete(payload?.fileId);
|
|
3628
|
+
if (gone && gone.status !== "completed") {
|
|
3629
|
+
gone.status = "cancelled";
|
|
3630
|
+
writeStateFile();
|
|
3631
|
+
broadcastSSE("transfer_progress", { fileId: gone.fileId, fileName: gone.name, size: gone.size, bytesTransferred: gone.bytesTransferred, progress: gone.progress, status: "cancelled" });
|
|
3632
|
+
}
|
|
3633
|
+
break;
|
|
3634
|
+
}
|
|
3488
3635
|
case "file_chunk_ack":
|
|
3489
3636
|
handleChunkAck(payload);
|
|
3490
3637
|
break;
|
|
@@ -3496,6 +3643,7 @@ function connectWebSocket(wsTargetUrl) {
|
|
|
3496
3643
|
if (entry) {
|
|
3497
3644
|
entry.token = payload.token;
|
|
3498
3645
|
entry.downloadUrl = payload.downloadUrl;
|
|
3646
|
+
if (typeof payload.claim === "string") entry.claim = payload.claim;
|
|
3499
3647
|
pubsharesByToken.set(payload.token, entry);
|
|
3500
3648
|
const resolver = pubshareRegResolvers.get(payload.fileId);
|
|
3501
3649
|
if (resolver) {
|
|
@@ -3529,7 +3677,8 @@ function connectWebSocket(wsTargetUrl) {
|
|
|
3529
3677
|
console.error("[DAEMON] Error processing WS message:", err);
|
|
3530
3678
|
}
|
|
3531
3679
|
});
|
|
3532
|
-
|
|
3680
|
+
sock.on("close", () => {
|
|
3681
|
+
if (!current()) return;
|
|
3533
3682
|
console.log("[DAEMON] WebSocket connection closed");
|
|
3534
3683
|
session.isConnected = false;
|
|
3535
3684
|
csrfToken = null;
|
|
@@ -3549,7 +3698,8 @@ function connectWebSocket(wsTargetUrl) {
|
|
|
3549
3698
|
}
|
|
3550
3699
|
}, 3e3);
|
|
3551
3700
|
});
|
|
3552
|
-
|
|
3701
|
+
sock.on("error", (err) => {
|
|
3702
|
+
if (!current()) return;
|
|
3553
3703
|
console.error("[DAEMON] WebSocket error:", err);
|
|
3554
3704
|
});
|
|
3555
3705
|
}
|
|
@@ -3687,7 +3837,7 @@ async function startWebSocketUpload(fileId, targetUserId) {
|
|
|
3687
3837
|
const tx = activeTransfers.find((t) => t.fileId === fileId);
|
|
3688
3838
|
if (!tx || !tx.filePath) return;
|
|
3689
3839
|
const chunkSize = 64 * 1024;
|
|
3690
|
-
const stat =
|
|
3840
|
+
const stat = fs8.statSync(tx.filePath);
|
|
3691
3841
|
const totalChunks = Math.max(1, Math.ceil(stat.size / chunkSize));
|
|
3692
3842
|
tx.status = "uploading";
|
|
3693
3843
|
tx.startTime = Date.now();
|
|
@@ -3705,6 +3855,17 @@ async function startWebSocketUpload(fileId, targetUserId) {
|
|
|
3705
3855
|
sendNextChunk(fileId);
|
|
3706
3856
|
}
|
|
3707
3857
|
function sendNextChunk(fileId) {
|
|
3858
|
+
sendNextChunkAsync(fileId).catch((e) => {
|
|
3859
|
+
console.error(`[DAEMON] Relay upload failed for ${fileId}: ${e?.message || e}`);
|
|
3860
|
+
activeUploads.delete(fileId);
|
|
3861
|
+
const failed = activeTransfers.find((t) => t.fileId === fileId);
|
|
3862
|
+
if (failed) {
|
|
3863
|
+
failed.status = "error";
|
|
3864
|
+
writeStateFile();
|
|
3865
|
+
}
|
|
3866
|
+
});
|
|
3867
|
+
}
|
|
3868
|
+
async function sendNextChunkAsync(fileId) {
|
|
3708
3869
|
const upload = activeUploads.get(fileId);
|
|
3709
3870
|
if (!upload) return;
|
|
3710
3871
|
const tx = activeTransfers.find((t) => t.fileId === fileId);
|
|
@@ -3724,18 +3885,18 @@ function sendNextChunk(fileId) {
|
|
|
3724
3885
|
return;
|
|
3725
3886
|
}
|
|
3726
3887
|
const start = upload.currentChunk * upload.chunkSize;
|
|
3727
|
-
const stat =
|
|
3888
|
+
const stat = fs8.statSync(upload.filePath);
|
|
3728
3889
|
const end = Math.min(start + upload.chunkSize, stat.size);
|
|
3729
3890
|
const buffer = Buffer.alloc(end - start);
|
|
3730
|
-
const fd =
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
const
|
|
3891
|
+
const fd = fs8.openSync(upload.filePath, "r");
|
|
3892
|
+
fs8.readSync(fd, buffer, 0, end - start, start);
|
|
3893
|
+
fs8.closeSync(fd);
|
|
3894
|
+
const frame = tx.peerRelayEnc === RELAY_ENC ? { chunk: await sealRelayChunk(buffer, fileId, upload.currentChunk, upload.totalChunks), enc: RELAY_ENC } : { chunk: buffer.toString("base64") };
|
|
3734
3895
|
wsConn?.send(JSON.stringify({
|
|
3735
3896
|
type: "file_chunk",
|
|
3736
3897
|
payload: {
|
|
3737
3898
|
fileId,
|
|
3738
|
-
|
|
3899
|
+
...frame,
|
|
3739
3900
|
chunkIndex: upload.currentChunk,
|
|
3740
3901
|
totalChunks: upload.totalChunks,
|
|
3741
3902
|
targetUserId: upload.targetUserId
|
|
@@ -3769,18 +3930,21 @@ function handleChunkAck(payload) {
|
|
|
3769
3930
|
sendNextChunk(fileId);
|
|
3770
3931
|
}
|
|
3771
3932
|
}
|
|
3772
|
-
function handleIncomingChunk(payload) {
|
|
3773
|
-
const { fileId,
|
|
3933
|
+
async function handleIncomingChunk(payload) {
|
|
3934
|
+
const { fileId, chunkIndex, totalChunks, from } = payload;
|
|
3774
3935
|
const tx = activeTransfers.find((t) => t.fileId === fileId);
|
|
3775
3936
|
if (!tx) return;
|
|
3937
|
+
const mode = payload.enc === void 0 || payload.enc === null ? "plain" : "sealed";
|
|
3938
|
+
if (tx.relayMode && tx.relayMode !== mode) throw new Error(`transfer ${fileId} mixed sealed and plaintext chunks`);
|
|
3939
|
+
tx.relayMode = mode;
|
|
3940
|
+
const chunkBuffer = await openRelayChunk(payload);
|
|
3776
3941
|
tx.status = "downloading";
|
|
3777
3942
|
tx.totalChunks = totalChunks;
|
|
3778
3943
|
tx.peerId = from;
|
|
3779
|
-
const chunkBuffer = Buffer.from(chunk, "base64");
|
|
3780
3944
|
tx.bytesTransferred += chunkBuffer.length;
|
|
3781
3945
|
tx.chunksCount++;
|
|
3782
|
-
const chunkPath =
|
|
3783
|
-
|
|
3946
|
+
const chunkPath = path7.join(TEMP_DIR, `${fileId}_chunk_${chunkIndex}`);
|
|
3947
|
+
fs8.writeFileSync(chunkPath, chunkBuffer);
|
|
3784
3948
|
tx.progress = tx.bytesTransferred / tx.size * 100;
|
|
3785
3949
|
const elapsed = (Date.now() - tx.startTime) / 1e3;
|
|
3786
3950
|
if (elapsed > 0) {
|
|
@@ -3808,10 +3972,10 @@ function handleFileComplete(payload) {
|
|
|
3808
3972
|
const tx = activeTransfers.find((t) => t.fileId === fileId);
|
|
3809
3973
|
if (!tx) return;
|
|
3810
3974
|
const destDir = tx.saveDir || process.cwd();
|
|
3811
|
-
const destPath =
|
|
3975
|
+
const destPath = path7.join(destDir, tx.name);
|
|
3812
3976
|
try {
|
|
3813
|
-
if (!
|
|
3814
|
-
|
|
3977
|
+
if (!fs8.existsSync(destDir)) {
|
|
3978
|
+
fs8.mkdirSync(destDir, { recursive: true });
|
|
3815
3979
|
}
|
|
3816
3980
|
} catch (err) {
|
|
3817
3981
|
console.error(`[DAEMON] Failed to create destination directory ${destDir}:`, err);
|
|
@@ -3819,7 +3983,7 @@ function handleFileComplete(payload) {
|
|
|
3819
3983
|
writeStateFile();
|
|
3820
3984
|
return;
|
|
3821
3985
|
}
|
|
3822
|
-
const writeStream =
|
|
3986
|
+
const writeStream = fs8.createWriteStream(destPath);
|
|
3823
3987
|
writeStream.on("error", (err) => {
|
|
3824
3988
|
console.error(`[DAEMON] Write stream error for ${destPath}:`, err);
|
|
3825
3989
|
tx.status = "error";
|
|
@@ -3854,12 +4018,12 @@ function handleFileComplete(payload) {
|
|
|
3854
4018
|
});
|
|
3855
4019
|
});
|
|
3856
4020
|
for (let i = 0; i < tx.totalChunks; i++) {
|
|
3857
|
-
const chunkPath =
|
|
3858
|
-
if (
|
|
4021
|
+
const chunkPath = path7.join(TEMP_DIR, `${fileId}_chunk_${i}`);
|
|
4022
|
+
if (fs8.existsSync(chunkPath)) {
|
|
3859
4023
|
try {
|
|
3860
|
-
const chunkBuf =
|
|
4024
|
+
const chunkBuf = fs8.readFileSync(chunkPath);
|
|
3861
4025
|
writeStream.write(chunkBuf);
|
|
3862
|
-
|
|
4026
|
+
fs8.unlinkSync(chunkPath);
|
|
3863
4027
|
} catch (err) {
|
|
3864
4028
|
console.error(`[DAEMON] Error reading/writing chunk ${i}:`, err);
|
|
3865
4029
|
writeStream.emit("error", err);
|
|
@@ -3869,6 +4033,20 @@ function handleFileComplete(payload) {
|
|
|
3869
4033
|
}
|
|
3870
4034
|
writeStream.end();
|
|
3871
4035
|
}
|
|
4036
|
+
async function waitSignalReady(maxMs = 8e3) {
|
|
4037
|
+
if (!session.id) return { status: 409, body: { success: false, error: "No active session. Call /session/start first." } };
|
|
4038
|
+
if (sessionTerminated) return { status: 410, body: { success: false, error: terminationReason || "The session ended." } };
|
|
4039
|
+
if (session.role === "guest" && !session.userId) {
|
|
4040
|
+
return { status: 409, body: { success: false, error: "Waiting for the host to approve your join request.", retryAfterMs: 2e3 } };
|
|
4041
|
+
}
|
|
4042
|
+
const ready = () => !!encryptionKey && !!csrfToken && wsConn?.readyState === WebSocket2.OPEN;
|
|
4043
|
+
for (let waited = 0; waited < maxMs && !ready() && session.id; waited += 100) {
|
|
4044
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
4045
|
+
}
|
|
4046
|
+
if (ready()) return null;
|
|
4047
|
+
if (!encryptionKey) return { status: 409, body: { success: false, error: "Encryption key not ready. Session still initialising." } };
|
|
4048
|
+
return { status: 503, body: { success: false, error: "Signaling connection not ready (WebSocket is connecting). Retry in 1\u20132 seconds.", retryAfterMs: 1500 } };
|
|
4049
|
+
}
|
|
3872
4050
|
async function registerPubshare(fileId, absPath, filename, size, mime, opts = {}, timeoutMs = 5e3) {
|
|
3873
4051
|
if (!wsConn || wsConn.readyState !== WebSocket2.OPEN) {
|
|
3874
4052
|
throw new Error("Signaling WebSocket not connected");
|
|
@@ -3891,7 +4069,8 @@ async function registerPubshare(fileId, absPath, filename, size, mime, opts = {}
|
|
|
3891
4069
|
linkKey: opts.linkKey,
|
|
3892
4070
|
tempFile: opts.tempFile,
|
|
3893
4071
|
displayName: opts.displayName || filename,
|
|
3894
|
-
prefixLen: opts.prefixLen
|
|
4072
|
+
prefixLen: opts.prefixLen,
|
|
4073
|
+
claim: opts.preferClaim
|
|
3895
4074
|
};
|
|
3896
4075
|
pubshares.set(fileId, entry);
|
|
3897
4076
|
const ackP = new Promise((resolve, reject) => {
|
|
@@ -3916,6 +4095,7 @@ async function registerPubshare(fileId, absPath, filename, size, mime, opts = {}
|
|
|
3916
4095
|
maxDownloads,
|
|
3917
4096
|
expiresAt,
|
|
3918
4097
|
preferToken: opts.preferToken || void 0,
|
|
4098
|
+
preferClaim: opts.preferToken && opts.preferClaim ? opts.preferClaim : void 0,
|
|
3919
4099
|
encrypted: opts.encrypted ? true : void 0,
|
|
3920
4100
|
prefixLen: opts.encrypted && opts.prefixLen ? opts.prefixLen : void 0
|
|
3921
4101
|
}
|
|
@@ -3932,7 +4112,7 @@ async function reregisterPubsharesAfterReconnect() {
|
|
|
3932
4112
|
pubsharesByToken.delete(entry.token);
|
|
3933
4113
|
if (entry.tempFile) {
|
|
3934
4114
|
try {
|
|
3935
|
-
|
|
4115
|
+
fs8.unlinkSync(entry.tempFile);
|
|
3936
4116
|
} catch {
|
|
3937
4117
|
}
|
|
3938
4118
|
}
|
|
@@ -3943,7 +4123,7 @@ async function reregisterPubsharesAfterReconnect() {
|
|
|
3943
4123
|
pubsharesByToken.delete(entry.token);
|
|
3944
4124
|
if (entry.tempFile) {
|
|
3945
4125
|
try {
|
|
3946
|
-
|
|
4126
|
+
fs8.unlinkSync(entry.tempFile);
|
|
3947
4127
|
} catch {
|
|
3948
4128
|
}
|
|
3949
4129
|
}
|
|
@@ -3962,6 +4142,7 @@ async function reregisterPubsharesAfterReconnect() {
|
|
|
3962
4142
|
maxDownloads: remainingMax,
|
|
3963
4143
|
ttlMs: remainingTtl,
|
|
3964
4144
|
preferToken: entry.token,
|
|
4145
|
+
preferClaim: entry.claim,
|
|
3965
4146
|
encrypted: entry.encrypted,
|
|
3966
4147
|
linkKey: entry.linkKey,
|
|
3967
4148
|
tempFile: entry.tempFile,
|
|
@@ -3987,16 +4168,16 @@ async function handlePubsharePull(payload) {
|
|
|
3987
4168
|
}
|
|
3988
4169
|
let fd;
|
|
3989
4170
|
try {
|
|
3990
|
-
fd =
|
|
4171
|
+
fd = fs8.openSync(entry.filePath, "r");
|
|
3991
4172
|
} catch (e) {
|
|
3992
4173
|
throw new Error(e?.code === "ENOENT" ? "the shared file no longer exists on the sender" : `cannot read the shared file (${e?.code || e?.message})`);
|
|
3993
4174
|
}
|
|
3994
4175
|
try {
|
|
3995
|
-
const size =
|
|
4176
|
+
const size = fs8.fstatSync(fd).size;
|
|
3996
4177
|
if (size !== entry.size) throw new Error("the shared file changed after the link was created");
|
|
3997
4178
|
} catch (e) {
|
|
3998
4179
|
try {
|
|
3999
|
-
|
|
4180
|
+
fs8.closeSync(fd);
|
|
4000
4181
|
} catch {
|
|
4001
4182
|
}
|
|
4002
4183
|
throw e;
|
|
@@ -4016,7 +4197,7 @@ async function handlePubsharePull(payload) {
|
|
|
4016
4197
|
}
|
|
4017
4198
|
const len = Math.min(CHUNK, end - pos + 1);
|
|
4018
4199
|
const buf = Buffer.alloc(len);
|
|
4019
|
-
const got =
|
|
4200
|
+
const got = fs8.readSync(fd, buf, 0, len, pos);
|
|
4020
4201
|
if (got !== len) throw new Error("the shared file changed while it was being sent");
|
|
4021
4202
|
const dataB64 = buf.toString("base64");
|
|
4022
4203
|
wsConn.send(JSON.stringify({
|
|
@@ -4040,7 +4221,7 @@ async function handlePubsharePull(payload) {
|
|
|
4040
4221
|
}
|
|
4041
4222
|
} finally {
|
|
4042
4223
|
try {
|
|
4043
|
-
|
|
4224
|
+
fs8.closeSync(fd);
|
|
4044
4225
|
} catch {
|
|
4045
4226
|
}
|
|
4046
4227
|
activePulls.delete(requestId);
|
|
@@ -4093,7 +4274,7 @@ async function ensureHostSessionOnce(sessionName) {
|
|
|
4093
4274
|
}
|
|
4094
4275
|
async function shareViaRelay(absPath, stat, reqBody, encrypt2) {
|
|
4095
4276
|
await ensureHostSession(reqBody.sessionName);
|
|
4096
|
-
const filename = reqBody.name ||
|
|
4277
|
+
const filename = reqBody.name || path7.basename(absPath);
|
|
4097
4278
|
const fileId = `cli_file_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
4098
4279
|
let servePath = absPath;
|
|
4099
4280
|
let serveSize = stat.size;
|
|
@@ -4103,13 +4284,13 @@ async function shareViaRelay(absPath, stat, reqBody, encrypt2) {
|
|
|
4103
4284
|
if (encrypt2) {
|
|
4104
4285
|
const key = newLinkKey();
|
|
4105
4286
|
linkKey = b64url(key);
|
|
4106
|
-
tempFile =
|
|
4287
|
+
tempFile = path7.join(TEMP_DIR, `${fileId}.sre1`);
|
|
4107
4288
|
const meta = { name: filename, size: stat.size, mime: "application/octet-stream" };
|
|
4108
|
-
const fd =
|
|
4289
|
+
const fd = fs8.openSync(tempFile, "w", 384);
|
|
4109
4290
|
try {
|
|
4110
|
-
for await (const part of encryptFile(absPath, key, meta, { password: reqBody.password }))
|
|
4291
|
+
for await (const part of encryptFile(absPath, key, meta, { password: reqBody.password })) fs8.writeSync(fd, part);
|
|
4111
4292
|
} finally {
|
|
4112
|
-
|
|
4293
|
+
fs8.closeSync(fd);
|
|
4113
4294
|
}
|
|
4114
4295
|
servePath = tempFile;
|
|
4115
4296
|
serveSize = encryptedSize(stat.size, meta);
|
|
@@ -4141,7 +4322,7 @@ async function shareViaRelay(absPath, stat, reqBody, encrypt2) {
|
|
|
4141
4322
|
if (reqBody._ownedPath) {
|
|
4142
4323
|
if (encrypt2) {
|
|
4143
4324
|
try {
|
|
4144
|
-
|
|
4325
|
+
fs8.unlinkSync(reqBody._ownedPath);
|
|
4145
4326
|
} catch {
|
|
4146
4327
|
}
|
|
4147
4328
|
} else tempFile = reqBody._ownedPath;
|
|
@@ -4180,7 +4361,7 @@ async function shareViaRelay(absPath, stat, reqBody, encrypt2) {
|
|
|
4180
4361
|
} catch (e) {
|
|
4181
4362
|
if (tempFile) {
|
|
4182
4363
|
try {
|
|
4183
|
-
|
|
4364
|
+
fs8.unlinkSync(tempFile);
|
|
4184
4365
|
} catch {
|
|
4185
4366
|
}
|
|
4186
4367
|
}
|
|
@@ -4212,26 +4393,26 @@ function safeArchiveBase(name, fallback) {
|
|
|
4212
4393
|
async function createShare(reqBody) {
|
|
4213
4394
|
if (!reqBody?.filePath) throw Object.assign(new Error("filePath (or filePaths) required"), { status: 400 });
|
|
4214
4395
|
assertRelayMode(reqBody);
|
|
4215
|
-
const absPath =
|
|
4396
|
+
const absPath = path7.resolve(reqBody.filePath);
|
|
4216
4397
|
let stat;
|
|
4217
4398
|
try {
|
|
4218
|
-
stat =
|
|
4399
|
+
stat = fs8.statSync(absPath);
|
|
4219
4400
|
} catch {
|
|
4220
4401
|
throw Object.assign(new Error(`File not found: ${absPath}`), { status: 404 });
|
|
4221
4402
|
}
|
|
4222
4403
|
if (stat.isDirectory()) {
|
|
4223
|
-
const base = safeArchiveBase(
|
|
4224
|
-
const out =
|
|
4404
|
+
const base = safeArchiveBase(path7.basename(absPath), "folder");
|
|
4405
|
+
const out = path7.join(TEMP_DIR, `${uuidv4()}-${base}.tar.gz`);
|
|
4225
4406
|
try {
|
|
4226
4407
|
await packDirectory(absPath, out, excludesFor(reqBody));
|
|
4227
4408
|
} catch (e) {
|
|
4228
4409
|
try {
|
|
4229
|
-
|
|
4410
|
+
fs8.unlinkSync(out);
|
|
4230
4411
|
} catch {
|
|
4231
4412
|
}
|
|
4232
4413
|
throw Object.assign(new Error(e?.message || String(e)), { status: 400 });
|
|
4233
4414
|
}
|
|
4234
|
-
return shareViaRelay(out,
|
|
4415
|
+
return shareViaRelay(out, fs8.statSync(out), { ...reqBody, name: reqBody.name || `${base}.tar.gz`, _ownedPath: out }, shareEncrypt(reqBody));
|
|
4235
4416
|
}
|
|
4236
4417
|
if (!stat.isFile()) throw Object.assign(new Error(`Not a regular file or folder: ${absPath}`), { status: 400 });
|
|
4237
4418
|
return shareViaRelay(absPath, stat, reqBody, shareEncrypt(reqBody));
|
|
@@ -4239,26 +4420,26 @@ async function createShare(reqBody) {
|
|
|
4239
4420
|
async function createMultiShare(reqBody) {
|
|
4240
4421
|
assertRelayMode(reqBody);
|
|
4241
4422
|
const raw = Array.isArray(reqBody.filePaths) ? reqBody.filePaths : [];
|
|
4242
|
-
const paths = Array.from(new Set(raw.filter((p) => typeof p === "string" && p.trim()).map((p) =>
|
|
4423
|
+
const paths = Array.from(new Set(raw.filter((p) => typeof p === "string" && p.trim()).map((p) => path7.resolve(p))));
|
|
4243
4424
|
if (!paths.length) throw Object.assign(new Error("filePaths must be a non-empty array of paths"), { status: 400 });
|
|
4244
4425
|
if (paths.length > MAX_MULTI_PATHS) throw Object.assign(new Error(`Too many paths (${paths.length}); max ${MAX_MULTI_PATHS}. Share a folder instead.`), { status: 400 });
|
|
4245
|
-
const missing = paths.filter((p) => !
|
|
4426
|
+
const missing = paths.filter((p) => !fs8.existsSync(p));
|
|
4246
4427
|
const bundle = reqBody.bundle !== false;
|
|
4247
4428
|
if (bundle) {
|
|
4248
4429
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace(/[-:T]/g, "");
|
|
4249
4430
|
const base = safeArchiveBase(reqBody.bundleName, `srift-${paths.length}-items-${stamp}`);
|
|
4250
|
-
const out =
|
|
4431
|
+
const out = path7.join(TEMP_DIR, `${uuidv4()}-${base}.tar.gz`);
|
|
4251
4432
|
let packed;
|
|
4252
4433
|
try {
|
|
4253
4434
|
packed = await packPaths(paths, out, base, excludesFor(reqBody));
|
|
4254
4435
|
} catch (e) {
|
|
4255
4436
|
try {
|
|
4256
|
-
|
|
4437
|
+
fs8.unlinkSync(out);
|
|
4257
4438
|
} catch {
|
|
4258
4439
|
}
|
|
4259
4440
|
throw Object.assign(new Error(e?.message || String(e)), { status: 400 });
|
|
4260
4441
|
}
|
|
4261
|
-
const r = await shareViaRelay(out,
|
|
4442
|
+
const r = await shareViaRelay(out, fs8.statSync(out), { ...reqBody, name: `${base}.tar.gz`, _ownedPath: out }, shareEncrypt(reqBody));
|
|
4262
4443
|
if (packed.skipped.length) console.warn(`[DAEMON] bundle skipped ${packed.skipped.length} item(s): ${packed.skipped.map((x) => x.path).join(", ")}`);
|
|
4263
4444
|
return { ...r, bundle: true, files: packed.files, bytes: packed.bytes, paths: paths.length, skipped: packed.skipped };
|
|
4264
4445
|
}
|
|
@@ -4327,7 +4508,7 @@ function embeddedDispatch(method, pathname, body) {
|
|
|
4327
4508
|
}
|
|
4328
4509
|
});
|
|
4329
4510
|
}
|
|
4330
|
-
var _WebTorrentCtor, _webTorrentLoadError, SRIFT_HOME, EMBEDDED, STATE_PATH, logPath, logStream, logMessage, fmtLogArg, crypto5, PORT, DEFAULT_SIGNALER_URL, activeSignalerUrl, lastDiag, session, activeTransfers, chatHistory, pendingJoins, sseClients, wsConn, wsUrl, csrfToken, heartbeatInterval, lastHeartbeatAckAt, encryptionKey, sessionTerminated, terminationReason, activeTorrents, wtClient, pubshares, pubsharesByToken, activePulls, pubshareRegResolvers, TEMP_DIR, KDF_ITERATIONS, IV_LEN, STATE_STALE_MS, activeUploads, app, LOCAL_HOSTS, DAEMON_START_TIME, PACKAGE_VERSION, hostSessionInflight, MAX_MULTI_PATHS, MULTI_CONCURRENCY, embeddedReady, httpServer;
|
|
4511
|
+
var _WebTorrentCtor, _webTorrentLoadError, SRIFT_HOME, EMBEDDED, STATE_PATH, logPath, logStream, logMessage, fmtLogArg, crypto5, PORT, DEFAULT_SIGNALER_URL, activeSignalerUrl, lastDiag, session, activeTransfers, chatHistory, pendingJoins, participants, sseClients, wsConn, wsUrl, csrfToken, heartbeatInterval, lastHeartbeatAckAt, encryptionKey, sessionTerminated, terminationReason, activeTorrents, wtClient, pubshares, pubsharesByToken, activePulls, pubshareRegResolvers, TEMP_DIR, KDF_ITERATIONS, IV_LEN, RELAY_ENC, relayKeyCache, STATE_STALE_MS, activeUploads, app, LOCAL_HOSTS, DAEMON_START_TIME, PACKAGE_VERSION, hostSessionInflight, MAX_MULTI_PATHS, MULTI_CONCURRENCY, embeddedReady, httpServer;
|
|
4331
4512
|
var init_daemon = __esm({
|
|
4332
4513
|
"../../cli/daemon.ts"() {
|
|
4333
4514
|
"use strict";
|
|
@@ -4339,25 +4520,25 @@ var init_daemon = __esm({
|
|
|
4339
4520
|
init_pack();
|
|
4340
4521
|
_WebTorrentCtor = null;
|
|
4341
4522
|
_webTorrentLoadError = null;
|
|
4342
|
-
SRIFT_HOME =
|
|
4523
|
+
SRIFT_HOME = path7.join(os4.homedir(), ".srift");
|
|
4343
4524
|
try {
|
|
4344
|
-
|
|
4525
|
+
fs8.mkdirSync(SRIFT_HOME, { recursive: true, mode: 448 });
|
|
4345
4526
|
try {
|
|
4346
|
-
|
|
4527
|
+
fs8.chmodSync(SRIFT_HOME, 448);
|
|
4347
4528
|
} catch {
|
|
4348
4529
|
}
|
|
4349
4530
|
} catch (e) {
|
|
4350
4531
|
}
|
|
4351
4532
|
EMBEDDED = process.env.SRIFT_DAEMON_EMBEDDED === "1";
|
|
4352
|
-
STATE_PATH =
|
|
4353
|
-
logPath =
|
|
4533
|
+
STATE_PATH = path7.join(SRIFT_HOME, EMBEDDED ? "state-embedded.json" : "state.json");
|
|
4534
|
+
logPath = path7.join(SRIFT_HOME, "daemon.log");
|
|
4354
4535
|
try {
|
|
4355
|
-
if (
|
|
4536
|
+
if (fs8.statSync(logPath).size > 5 * 1024 * 1024) fs8.renameSync(logPath, `${logPath}.1`);
|
|
4356
4537
|
} catch {
|
|
4357
4538
|
}
|
|
4358
|
-
logStream =
|
|
4539
|
+
logStream = fs8.createWriteStream(logPath, { flags: "a", mode: 384 });
|
|
4359
4540
|
try {
|
|
4360
|
-
|
|
4541
|
+
fs8.chmodSync(logPath, 384);
|
|
4361
4542
|
} catch {
|
|
4362
4543
|
}
|
|
4363
4544
|
logMessage = (level, message) => {
|
|
@@ -4404,6 +4585,7 @@ var init_daemon = __esm({
|
|
|
4404
4585
|
activeTransfers = [];
|
|
4405
4586
|
chatHistory = [];
|
|
4406
4587
|
pendingJoins = [];
|
|
4588
|
+
participants = [];
|
|
4407
4589
|
sseClients = [];
|
|
4408
4590
|
wsConn = null;
|
|
4409
4591
|
wsUrl = null;
|
|
@@ -4419,15 +4601,15 @@ var init_daemon = __esm({
|
|
|
4419
4601
|
pubsharesByToken = /* @__PURE__ */ new Map();
|
|
4420
4602
|
activePulls = /* @__PURE__ */ new Map();
|
|
4421
4603
|
pubshareRegResolvers = /* @__PURE__ */ new Map();
|
|
4422
|
-
TEMP_DIR =
|
|
4423
|
-
if (!
|
|
4424
|
-
|
|
4604
|
+
TEMP_DIR = path7.join(SRIFT_HOME, "tmp");
|
|
4605
|
+
if (!fs8.existsSync(TEMP_DIR)) {
|
|
4606
|
+
fs8.mkdirSync(TEMP_DIR, { recursive: true, mode: 448 });
|
|
4425
4607
|
}
|
|
4426
4608
|
try {
|
|
4427
|
-
for (const f of
|
|
4609
|
+
for (const f of fs8.readdirSync(TEMP_DIR)) {
|
|
4428
4610
|
if (f.endsWith(".sre1")) {
|
|
4429
4611
|
try {
|
|
4430
|
-
|
|
4612
|
+
fs8.unlinkSync(path7.join(TEMP_DIR, f));
|
|
4431
4613
|
} catch {
|
|
4432
4614
|
}
|
|
4433
4615
|
}
|
|
@@ -4436,6 +4618,8 @@ var init_daemon = __esm({
|
|
|
4436
4618
|
}
|
|
4437
4619
|
KDF_ITERATIONS = 1e5;
|
|
4438
4620
|
IV_LEN = 12;
|
|
4621
|
+
RELAY_ENC = "sgcm1";
|
|
4622
|
+
relayKeyCache = null;
|
|
4439
4623
|
STATE_STALE_MS = 24 * 60 * 60 * 1e3;
|
|
4440
4624
|
pruneStaleStateOnStartup();
|
|
4441
4625
|
activeUploads = /* @__PURE__ */ new Map();
|
|
@@ -4454,7 +4638,7 @@ var init_daemon = __esm({
|
|
|
4454
4638
|
app.use(cors({ origin: "*", maxAge: 600 }));
|
|
4455
4639
|
app.use(express.json());
|
|
4456
4640
|
DAEMON_START_TIME = Date.now();
|
|
4457
|
-
PACKAGE_VERSION = "4.
|
|
4641
|
+
PACKAGE_VERSION = "4.1.0";
|
|
4458
4642
|
app.get("/health", (req, res) => {
|
|
4459
4643
|
res.json({
|
|
4460
4644
|
ok: true,
|
|
@@ -4478,6 +4662,7 @@ var init_daemon = __esm({
|
|
|
4478
4662
|
},
|
|
4479
4663
|
activeTransfers,
|
|
4480
4664
|
pendingJoins,
|
|
4665
|
+
participants,
|
|
4481
4666
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
4482
4667
|
});
|
|
4483
4668
|
});
|
|
@@ -4505,7 +4690,7 @@ var init_daemon = __esm({
|
|
|
4505
4690
|
wsUrl = data.wsUrl || `${activeSignalerUrl.replace(/^http/, "ws")}/ws`;
|
|
4506
4691
|
if (wsUrl) connectWebSocket(wsUrl);
|
|
4507
4692
|
writeStateFile();
|
|
4508
|
-
res.json({ success: true, sessionId: data.sessionId });
|
|
4693
|
+
res.json({ success: true, sessionId: data.sessionId, joinUrl: `${activeSignalerUrl.replace(/\/+$/, "")}/join-session?id=${data.sessionId}` });
|
|
4509
4694
|
} catch (err) {
|
|
4510
4695
|
console.error("[DAEMON] Failed to start session:", err);
|
|
4511
4696
|
res.status(500).json({ success: false, error: err.message });
|
|
@@ -4550,7 +4735,7 @@ var init_daemon = __esm({
|
|
|
4550
4735
|
res.status(500).json({ success: false, error: err.message });
|
|
4551
4736
|
}
|
|
4552
4737
|
});
|
|
4553
|
-
app.post("/session/approve", (req, res) => {
|
|
4738
|
+
app.post("/session/approve", async (req, res) => {
|
|
4554
4739
|
const { tempUserId } = req.body;
|
|
4555
4740
|
if (!tempUserId) {
|
|
4556
4741
|
return res.status(400).json({ success: false, error: "tempUserId required" });
|
|
@@ -4568,7 +4753,13 @@ var init_daemon = __esm({
|
|
|
4568
4753
|
payload: { sessionId: session.id, tempUserId }
|
|
4569
4754
|
}));
|
|
4570
4755
|
pendingJoins = pendingJoins.filter((j) => j.tempUserId !== tempUserId);
|
|
4571
|
-
|
|
4756
|
+
const before = new Set(participants.filter((p) => p.online).map((p) => p.userId));
|
|
4757
|
+
const joined = () => participants.find((p) => p.online && !p.isHost && p.username === pending.username && !before.has(p.userId));
|
|
4758
|
+
for (let waited = 0; waited < 8e3 && !joined() && session.id; waited += 100) {
|
|
4759
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
4760
|
+
}
|
|
4761
|
+
const member = joined();
|
|
4762
|
+
res.json({ success: true, joined: !!member, userId: member?.userId ?? null, username: pending.username });
|
|
4572
4763
|
});
|
|
4573
4764
|
app.post("/session/reject", (req, res) => {
|
|
4574
4765
|
const { tempUserId, reason } = req.body;
|
|
@@ -4645,13 +4836,14 @@ var init_daemon = __esm({
|
|
|
4645
4836
|
activeTransfers = [];
|
|
4646
4837
|
chatHistory = [];
|
|
4647
4838
|
pendingJoins = [];
|
|
4839
|
+
participants = [];
|
|
4648
4840
|
csrfToken = null;
|
|
4649
4841
|
encryptionKey = null;
|
|
4650
4842
|
try {
|
|
4651
|
-
if (
|
|
4652
|
-
for (const f of
|
|
4843
|
+
if (fs8.existsSync(TEMP_DIR)) {
|
|
4844
|
+
for (const f of fs8.readdirSync(TEMP_DIR)) {
|
|
4653
4845
|
try {
|
|
4654
|
-
|
|
4846
|
+
fs8.unlinkSync(path7.join(TEMP_DIR, f));
|
|
4655
4847
|
} catch {
|
|
4656
4848
|
}
|
|
4657
4849
|
}
|
|
@@ -4667,14 +4859,16 @@ var init_daemon = __esm({
|
|
|
4667
4859
|
if (!filePath) {
|
|
4668
4860
|
return res.status(400).json({ success: false, error: "filePath is required" });
|
|
4669
4861
|
}
|
|
4670
|
-
const absPath =
|
|
4671
|
-
if (!
|
|
4862
|
+
const absPath = path7.resolve(filePath);
|
|
4863
|
+
if (!fs8.existsSync(absPath)) {
|
|
4672
4864
|
return res.status(404).json({ success: false, error: `File not found: ${absPath}` });
|
|
4673
4865
|
}
|
|
4674
|
-
const stat =
|
|
4675
|
-
const filename =
|
|
4866
|
+
const stat = fs8.statSync(absPath);
|
|
4867
|
+
const filename = path7.basename(absPath);
|
|
4676
4868
|
const fileId = `cli_file_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
4677
4869
|
const targetProtocol = webTorrentAllowed() && (protocol === "webtorrent" || stat.size > 10 * 1024 * 1024 && protocol !== "websocket") ? "webtorrent" : "websocket";
|
|
4870
|
+
const notReady = await waitSignalReady();
|
|
4871
|
+
if (notReady) return res.status(notReady.status).json(notReady.body);
|
|
4678
4872
|
const newTx = {
|
|
4679
4873
|
fileId,
|
|
4680
4874
|
name: filename,
|
|
@@ -4720,13 +4914,14 @@ var init_daemon = __esm({
|
|
|
4720
4914
|
if (!tx) {
|
|
4721
4915
|
return res.status(404).json({ success: false, error: "No offer found for this fileId" });
|
|
4722
4916
|
}
|
|
4723
|
-
tx.saveDir = saveDir ?
|
|
4917
|
+
tx.saveDir = saveDir ? path7.resolve(saveDir) : process.cwd();
|
|
4724
4918
|
tx.status = "downloading";
|
|
4725
4919
|
tx.startTime = Date.now();
|
|
4726
4920
|
console.log(`[DAEMON] Accepting file offer ${fileId}, saving to ${tx.saveDir}`);
|
|
4727
4921
|
wsConn?.send(JSON.stringify({
|
|
4728
4922
|
type: "file_accept",
|
|
4729
|
-
|
|
4923
|
+
// relayEnc: this daemon decrypts sealed relay chunks (see openRelayChunk)
|
|
4924
|
+
payload: { fileId, relayEnc: RELAY_ENC }
|
|
4730
4925
|
}));
|
|
4731
4926
|
res.json({ success: true });
|
|
4732
4927
|
});
|
|
@@ -4739,16 +4934,8 @@ var init_daemon = __esm({
|
|
|
4739
4934
|
if (!session.id) {
|
|
4740
4935
|
return res.status(409).json({ success: false, error: "No active session. Call /session/start first." });
|
|
4741
4936
|
}
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
}
|
|
4745
|
-
if (!wsConn || wsConn.readyState !== 1) {
|
|
4746
|
-
return res.status(503).json({
|
|
4747
|
-
success: false,
|
|
4748
|
-
error: "Signaling connection not ready (WebSocket is connecting). Retry in 1\u20132 seconds.",
|
|
4749
|
-
retryAfterMs: 1500
|
|
4750
|
-
});
|
|
4751
|
-
}
|
|
4937
|
+
const notReady = await waitSignalReady();
|
|
4938
|
+
if (notReady) return res.status(notReady.status).json(notReady.body);
|
|
4752
4939
|
const encContent = await encrypt(message);
|
|
4753
4940
|
wsConn.send(JSON.stringify({
|
|
4754
4941
|
type: "chat",
|
|
@@ -4771,8 +4958,8 @@ var init_daemon = __esm({
|
|
|
4771
4958
|
});
|
|
4772
4959
|
app.get("/state", (req, res) => {
|
|
4773
4960
|
try {
|
|
4774
|
-
if (
|
|
4775
|
-
const raw =
|
|
4961
|
+
if (fs8.existsSync(STATE_PATH)) {
|
|
4962
|
+
const raw = fs8.readFileSync(STATE_PATH, "utf-8");
|
|
4776
4963
|
res.type("application/json").send(raw);
|
|
4777
4964
|
} else {
|
|
4778
4965
|
res.json({ session: { id: null, isConnected: false }, activeTransfers: [], lastUpdated: null });
|
|
@@ -4852,7 +5039,7 @@ var init_daemon = __esm({
|
|
|
4852
5039
|
pubshares.delete(entry.fileId);
|
|
4853
5040
|
if (entry.tempFile) {
|
|
4854
5041
|
try {
|
|
4855
|
-
|
|
5042
|
+
fs8.unlinkSync(entry.tempFile);
|
|
4856
5043
|
} catch {
|
|
4857
5044
|
}
|
|
4858
5045
|
}
|
|
@@ -4964,7 +5151,7 @@ data: /mcp/messages
|
|
|
4964
5151
|
servers: [{ url: `http://127.0.0.1:${PORT}` }],
|
|
4965
5152
|
paths: {
|
|
4966
5153
|
"/health": { get: { summary: "Liveness probe", description: "Returns {ok,version,uptime_ms,mcp,webrtc,webtorrent}", responses: { "200": { description: "OK" } } } },
|
|
4967
|
-
"/status": { get: { summary: "Get session
|
|
5154
|
+
"/status": { get: { summary: "Get session, transfers, pending joins and participants (members with userId, for /session/kick)", responses: { "200": { description: "OK" } } } },
|
|
4968
5155
|
"/state": { get: { summary: "Workspace state snapshot (.srift-state.json)", responses: { "200": { description: "OK" } } } },
|
|
4969
5156
|
"/transfers": { get: { summary: "Live transfer list with speed and ETA. Optional ?fileId= to filter.", responses: { "200": { description: "OK" } } } },
|
|
4970
5157
|
"/transfers/{fileId}": { get: { summary: "Per-transfer drill-down", parameters: [{ name: "fileId", in: "path", required: true, schema: { type: "string" } }], responses: { "200": { description: "OK" }, "404": { description: "Transfer not found" } } } },
|
|
@@ -4972,9 +5159,11 @@ data: /mcp/messages
|
|
|
4972
5159
|
"/metrics": { get: { summary: "Prometheus-format counters (no auth)", responses: { "200": { description: "text/plain Prometheus format" } } } },
|
|
4973
5160
|
"/logs": { get: { summary: "NDJSON daemon log tail. ?lines=N (default 100)", responses: { "200": { description: "application/x-ndjson" } } } },
|
|
4974
5161
|
"/reset": { post: { summary: "Wipe all state and flush encryption keys", responses: { "200": { description: "{ok:true}" } } } },
|
|
4975
|
-
"/
|
|
5162
|
+
"/v1/diag": { get: { summary: "Network diagnosis (same as `srift doctor --json`); ?fresh=1 re-probes", responses: { "200": { description: "Diagnosis report" } } } },
|
|
5163
|
+
"/daemon/stop": { post: { summary: "Stop this daemon", responses: { "200": { description: "OK" } } } },
|
|
5164
|
+
"/session/start": { post: { summary: "Create new session (becomes host); returns sessionId and joinUrl", requestBody: { content: { "application/json": { schema: { type: "object", properties: { sessionName: { type: "string" }, roomSecret: { type: "string" } } } } } }, responses: { "200": { description: "{success:true,sessionId}" } } } },
|
|
4976
5165
|
"/session/join": { post: { summary: "Join existing session", requestBody: { content: { "application/json": { schema: { type: "object", required: ["sessionId"], properties: { sessionId: { type: "string" }, username: { type: "string" }, roomSecret: { type: "string" } } } } } }, responses: { "200": { description: "OK" }, "400": { description: "Missing sessionId" }, "404": { description: "Session not found" } } } },
|
|
4977
|
-
"/session/approve": { post: { summary: "Host approves a pending join request", requestBody: { content: { "application/json": { schema: { type: "object", required: ["tempUserId"], properties: { tempUserId: { type: "string" } } } } } }, responses: { "200": { description: "OK" }, "400": { description: "tempUserId required" }, "403": { description: "Not host" }, "404": { description: "No such pending request" } } } },
|
|
5166
|
+
"/session/approve": { post: { summary: "Host approves a pending join request; returns {success, joined, userId} once the guest is in the room (max 8 s)", requestBody: { content: { "application/json": { schema: { type: "object", required: ["tempUserId"], properties: { tempUserId: { type: "string" } } } } } }, responses: { "200": { description: "OK" }, "400": { description: "tempUserId required" }, "403": { description: "Not host" }, "404": { description: "No such pending request" } } } },
|
|
4978
5167
|
"/session/reject": { post: { summary: "Host rejects a pending join request", requestBody: { content: { "application/json": { schema: { type: "object", required: ["tempUserId"], properties: { tempUserId: { type: "string" }, reason: { type: "string" } } } } } }, responses: { "200": { description: "OK" }, "400": { description: "tempUserId required" }, "403": { description: "Not host" } } } },
|
|
4979
5168
|
"/session/kick": { post: { summary: "Host kicks a peer", requestBody: { content: { "application/json": { schema: { type: "object", required: ["userId"], properties: { userId: { type: "string" } } } } } }, responses: { "200": { description: "OK" }, "400": { description: "userId required" }, "403": { description: "Not host" } } } },
|
|
4980
5169
|
"/session/close": { post: { summary: "Tear down the session and flush keys", responses: { "200": { description: "OK" } } } },
|
|
@@ -5055,8 +5244,8 @@ data: /mcp/messages
|
|
|
5055
5244
|
app.get("/logs", (req, res) => {
|
|
5056
5245
|
const lines = parseInt(req.query.lines || "100", 10);
|
|
5057
5246
|
try {
|
|
5058
|
-
if (!
|
|
5059
|
-
const content =
|
|
5247
|
+
if (!fs8.existsSync(logPath)) return res.json([]);
|
|
5248
|
+
const content = fs8.readFileSync(logPath, "utf-8");
|
|
5060
5249
|
const all = content.split("\n").filter(Boolean);
|
|
5061
5250
|
const tail = all.slice(-Math.min(lines, all.length));
|
|
5062
5251
|
res.set("Content-Type", "application/x-ndjson");
|
|
@@ -5091,15 +5280,16 @@ data: /mcp/messages
|
|
|
5091
5280
|
activeTransfers = [];
|
|
5092
5281
|
chatHistory = [];
|
|
5093
5282
|
pendingJoins = [];
|
|
5283
|
+
participants = [];
|
|
5094
5284
|
csrfToken = null;
|
|
5095
5285
|
encryptionKey = null;
|
|
5096
5286
|
wsUrl = null;
|
|
5097
5287
|
let cleanedChunks = 0;
|
|
5098
5288
|
try {
|
|
5099
|
-
if (
|
|
5100
|
-
for (const f of
|
|
5289
|
+
if (fs8.existsSync(TEMP_DIR)) {
|
|
5290
|
+
for (const f of fs8.readdirSync(TEMP_DIR)) {
|
|
5101
5291
|
try {
|
|
5102
|
-
|
|
5292
|
+
fs8.unlinkSync(path7.join(TEMP_DIR, f));
|
|
5103
5293
|
cleanedChunks++;
|
|
5104
5294
|
} catch {
|
|
5105
5295
|
}
|
|
@@ -5161,16 +5351,17 @@ data: /mcp/messages
|
|
|
5161
5351
|
activeTransfers = [];
|
|
5162
5352
|
chatHistory = [];
|
|
5163
5353
|
pendingJoins = [];
|
|
5354
|
+
participants = [];
|
|
5164
5355
|
encryptionKey = null;
|
|
5165
5356
|
wsUrl = null;
|
|
5166
5357
|
writeStateFile();
|
|
5167
5358
|
} catch {
|
|
5168
5359
|
}
|
|
5169
5360
|
try {
|
|
5170
|
-
if (
|
|
5171
|
-
for (const f of
|
|
5361
|
+
if (fs8.existsSync(TEMP_DIR)) {
|
|
5362
|
+
for (const f of fs8.readdirSync(TEMP_DIR)) {
|
|
5172
5363
|
try {
|
|
5173
|
-
|
|
5364
|
+
fs8.unlinkSync(path7.join(TEMP_DIR, f));
|
|
5174
5365
|
} catch {
|
|
5175
5366
|
}
|
|
5176
5367
|
}
|
|
@@ -5185,7 +5376,7 @@ data: /mcp/messages
|
|
|
5185
5376
|
process.on("exit", () => {
|
|
5186
5377
|
for (const e of pubshares.values()) if (e.tempFile) {
|
|
5187
5378
|
try {
|
|
5188
|
-
|
|
5379
|
+
fs8.unlinkSync(e.tempFile);
|
|
5189
5380
|
} catch {
|
|
5190
5381
|
}
|
|
5191
5382
|
}
|
|
@@ -6837,12 +7028,12 @@ var init_card = __esm({
|
|
|
6837
7028
|
});
|
|
6838
7029
|
|
|
6839
7030
|
// ../../cli/agentnet/describe.ts
|
|
6840
|
-
import
|
|
7031
|
+
import fs10 from "node:fs";
|
|
6841
7032
|
import os6 from "node:os";
|
|
6842
|
-
import
|
|
7033
|
+
import path9 from "node:path";
|
|
6843
7034
|
function read(file2) {
|
|
6844
7035
|
try {
|
|
6845
|
-
return
|
|
7036
|
+
return fs10.readFileSync(file2, "utf8");
|
|
6846
7037
|
} catch {
|
|
6847
7038
|
return null;
|
|
6848
7039
|
}
|
|
@@ -6855,7 +7046,7 @@ function firstSentence(s) {
|
|
|
6855
7046
|
function detectProject(start = process.cwd()) {
|
|
6856
7047
|
let dir = start;
|
|
6857
7048
|
for (let i = 0; i < 4; i++) {
|
|
6858
|
-
const pkg = read(
|
|
7049
|
+
const pkg = read(path9.join(dir, "package.json"));
|
|
6859
7050
|
if (pkg) {
|
|
6860
7051
|
try {
|
|
6861
7052
|
const j = JSON.parse(pkg);
|
|
@@ -6864,7 +7055,7 @@ function detectProject(start = process.cwd()) {
|
|
|
6864
7055
|
} catch {
|
|
6865
7056
|
}
|
|
6866
7057
|
}
|
|
6867
|
-
const py = read(
|
|
7058
|
+
const py = read(path9.join(dir, "pyproject.toml"));
|
|
6868
7059
|
if (py) {
|
|
6869
7060
|
return {
|
|
6870
7061
|
name: py.match(/^\s*name\s*=\s*"([^"]+)"/m)?.[1],
|
|
@@ -6872,7 +7063,7 @@ function detectProject(start = process.cwd()) {
|
|
|
6872
7063
|
stack: "Python"
|
|
6873
7064
|
};
|
|
6874
7065
|
}
|
|
6875
|
-
const cargo = read(
|
|
7066
|
+
const cargo = read(path9.join(dir, "Cargo.toml"));
|
|
6876
7067
|
if (cargo) {
|
|
6877
7068
|
return {
|
|
6878
7069
|
name: cargo.match(/^\s*name\s*=\s*"([^"]+)"/m)?.[1],
|
|
@@ -6880,9 +7071,9 @@ function detectProject(start = process.cwd()) {
|
|
|
6880
7071
|
stack: "Rust"
|
|
6881
7072
|
};
|
|
6882
7073
|
}
|
|
6883
|
-
const gomod = read(
|
|
7074
|
+
const gomod = read(path9.join(dir, "go.mod"));
|
|
6884
7075
|
if (gomod) return { name: gomod.match(/^module\s+(\S+)/m)?.[1]?.split("/").pop(), stack: "Go" };
|
|
6885
|
-
const parent =
|
|
7076
|
+
const parent = path9.dirname(dir);
|
|
6886
7077
|
if (parent === dir) break;
|
|
6887
7078
|
dir = parent;
|
|
6888
7079
|
}
|
|
@@ -6890,7 +7081,7 @@ function detectProject(start = process.cwd()) {
|
|
|
6890
7081
|
}
|
|
6891
7082
|
function runtimeLabel() {
|
|
6892
7083
|
if (process.env.KUBERNETES_SERVICE_HOST) return "Kubernetes";
|
|
6893
|
-
if (
|
|
7084
|
+
if (fs10.existsSync("/.dockerenv")) return "container";
|
|
6894
7085
|
if (process.env.GITHUB_ACTIONS) return "GitHub Actions";
|
|
6895
7086
|
if (process.env.CODESPACES) return "Codespace";
|
|
6896
7087
|
if (process.env.AWS_LAMBDA_FUNCTION_NAME) return "AWS Lambda";
|
|
@@ -6926,20 +7117,20 @@ var init_describe = __esm({
|
|
|
6926
7117
|
|
|
6927
7118
|
// ../../cli/agentnet/local.ts
|
|
6928
7119
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6929
|
-
import
|
|
7120
|
+
import fs11 from "node:fs";
|
|
6930
7121
|
import os7 from "node:os";
|
|
6931
|
-
import
|
|
7122
|
+
import path10 from "node:path";
|
|
6932
7123
|
function inHome(home, fn) {
|
|
6933
7124
|
return homeCtx.run(home, fn);
|
|
6934
7125
|
}
|
|
6935
7126
|
function anDir() {
|
|
6936
|
-
return homeCtx.getStore() || process.env.SRIFT_AN_HOME ||
|
|
7127
|
+
return homeCtx.getStore() || process.env.SRIFT_AN_HOME || path10.join(os7.homedir(), ".srift", "agentnet");
|
|
6937
7128
|
}
|
|
6938
7129
|
function file(name) {
|
|
6939
|
-
return
|
|
7130
|
+
return path10.join(anDir(), name);
|
|
6940
7131
|
}
|
|
6941
7132
|
function ensureDir(dir = anDir()) {
|
|
6942
|
-
|
|
7133
|
+
fs11.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
6943
7134
|
}
|
|
6944
7135
|
function ramFor() {
|
|
6945
7136
|
const k = anDir();
|
|
@@ -6959,7 +7150,7 @@ function readJson(name, fallback) {
|
|
|
6959
7150
|
const v = ramFor().json.get(name);
|
|
6960
7151
|
return v === void 0 ? fallback : JSON.parse(v);
|
|
6961
7152
|
}
|
|
6962
|
-
return JSON.parse(
|
|
7153
|
+
return JSON.parse(fs11.readFileSync(file(name), "utf8"));
|
|
6963
7154
|
} catch {
|
|
6964
7155
|
return fallback;
|
|
6965
7156
|
}
|
|
@@ -6972,20 +7163,20 @@ function writeJson(name, data) {
|
|
|
6972
7163
|
ensureDir();
|
|
6973
7164
|
const target = file(name);
|
|
6974
7165
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
6975
|
-
|
|
7166
|
+
fs11.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
6976
7167
|
try {
|
|
6977
|
-
|
|
7168
|
+
fs11.renameSync(tmp, target);
|
|
6978
7169
|
} catch (e) {
|
|
6979
7170
|
for (let i = 0; i < 20; i++) {
|
|
6980
7171
|
sleepSync(25);
|
|
6981
7172
|
try {
|
|
6982
|
-
|
|
7173
|
+
fs11.renameSync(tmp, target);
|
|
6983
7174
|
return;
|
|
6984
7175
|
} catch {
|
|
6985
7176
|
}
|
|
6986
7177
|
}
|
|
6987
7178
|
try {
|
|
6988
|
-
|
|
7179
|
+
fs11.unlinkSync(tmp);
|
|
6989
7180
|
} catch {
|
|
6990
7181
|
}
|
|
6991
7182
|
throw e;
|
|
@@ -7002,15 +7193,15 @@ function withLock(name, fn) {
|
|
|
7002
7193
|
let fd = null;
|
|
7003
7194
|
while (fd === null) {
|
|
7004
7195
|
try {
|
|
7005
|
-
fd =
|
|
7006
|
-
|
|
7196
|
+
fd = fs11.openSync(lock, "wx");
|
|
7197
|
+
fs11.writeSync(fd, String(process.pid));
|
|
7007
7198
|
} catch (e) {
|
|
7008
7199
|
if (e?.code !== "EEXIST" && e?.code !== "EPERM" && e?.code !== "EACCES" && e?.code !== "EBUSY") throw e;
|
|
7009
7200
|
try {
|
|
7010
|
-
const holder = Number(
|
|
7011
|
-
const age = Date.now() -
|
|
7201
|
+
const holder = Number(fs11.readFileSync(lock, "utf8"));
|
|
7202
|
+
const age = Date.now() - fs11.statSync(lock).mtimeMs;
|
|
7012
7203
|
if (holder && holder !== process.pid && !pidAlive(holder) && age > 200 || age > 6e4) {
|
|
7013
|
-
|
|
7204
|
+
fs11.unlinkSync(lock);
|
|
7014
7205
|
continue;
|
|
7015
7206
|
}
|
|
7016
7207
|
} catch {
|
|
@@ -7023,11 +7214,11 @@ function withLock(name, fn) {
|
|
|
7023
7214
|
return fn();
|
|
7024
7215
|
} finally {
|
|
7025
7216
|
try {
|
|
7026
|
-
|
|
7217
|
+
fs11.closeSync(fd);
|
|
7027
7218
|
} catch {
|
|
7028
7219
|
}
|
|
7029
7220
|
try {
|
|
7030
|
-
|
|
7221
|
+
fs11.unlinkSync(lock);
|
|
7031
7222
|
} catch {
|
|
7032
7223
|
}
|
|
7033
7224
|
}
|
|
@@ -7040,13 +7231,13 @@ function mutateJson(name, fallback, fn) {
|
|
|
7040
7231
|
});
|
|
7041
7232
|
}
|
|
7042
7233
|
function hasIdentity() {
|
|
7043
|
-
return
|
|
7234
|
+
return fs11.existsSync(file("identity.json"));
|
|
7044
7235
|
}
|
|
7045
7236
|
function loadIdentity() {
|
|
7046
7237
|
const p = file("identity.json");
|
|
7047
7238
|
let text2;
|
|
7048
7239
|
try {
|
|
7049
|
-
text2 =
|
|
7240
|
+
text2 = fs11.readFileSync(p, "utf8");
|
|
7050
7241
|
} catch (e) {
|
|
7051
7242
|
if (e?.code === "ENOENT") return null;
|
|
7052
7243
|
throw new Error(`Cannot read ${p} (${e?.code || e?.message}). Refusing to continue so your address is never replaced.`);
|
|
@@ -7101,7 +7292,7 @@ function ensureIdentity() {
|
|
|
7101
7292
|
ensureDir();
|
|
7102
7293
|
let fd;
|
|
7103
7294
|
try {
|
|
7104
|
-
fd =
|
|
7295
|
+
fd = fs11.openSync(file("identity.json.creating"), "wx");
|
|
7105
7296
|
} catch {
|
|
7106
7297
|
for (let i = 0; i < 100; i++) {
|
|
7107
7298
|
sleepSync(20);
|
|
@@ -7109,7 +7300,7 @@ function ensureIdentity() {
|
|
|
7109
7300
|
if (again) return { identity: again, created: false };
|
|
7110
7301
|
}
|
|
7111
7302
|
try {
|
|
7112
|
-
|
|
7303
|
+
fs11.unlinkSync(file("identity.json.creating"));
|
|
7113
7304
|
} catch {
|
|
7114
7305
|
}
|
|
7115
7306
|
return ensureIdentity();
|
|
@@ -7121,9 +7312,9 @@ function ensureIdentity() {
|
|
|
7121
7312
|
saveIdentity(identity);
|
|
7122
7313
|
return { identity, created: true };
|
|
7123
7314
|
} finally {
|
|
7124
|
-
|
|
7315
|
+
fs11.closeSync(fd);
|
|
7125
7316
|
try {
|
|
7126
|
-
|
|
7317
|
+
fs11.unlinkSync(file("identity.json.creating"));
|
|
7127
7318
|
} catch {
|
|
7128
7319
|
}
|
|
7129
7320
|
}
|
|
@@ -7133,7 +7324,7 @@ function saveProfile(address, profile) {
|
|
|
7133
7324
|
withLock("identity.json", () => {
|
|
7134
7325
|
let raw;
|
|
7135
7326
|
try {
|
|
7136
|
-
raw = JSON.parse(
|
|
7327
|
+
raw = JSON.parse(fs11.readFileSync(file("identity.json"), "utf8").replace(/^\uFEFF/, ""));
|
|
7137
7328
|
} catch {
|
|
7138
7329
|
return;
|
|
7139
7330
|
}
|
|
@@ -7274,7 +7465,7 @@ function inboxPath() {
|
|
|
7274
7465
|
function readInbox() {
|
|
7275
7466
|
if (isEphemeral()) return ramFor().inbox.slice();
|
|
7276
7467
|
try {
|
|
7277
|
-
return
|
|
7468
|
+
return fs11.readFileSync(inboxPath(), "utf8").split("\n").filter(Boolean).map((l) => {
|
|
7278
7469
|
try {
|
|
7279
7470
|
return JSON.parse(l);
|
|
7280
7471
|
} catch {
|
|
@@ -7299,7 +7490,7 @@ function appendInbox(rec) {
|
|
|
7299
7490
|
if (r.inbox.length > 2e4) r.inbox.splice(0, r.inbox.length - 2e4);
|
|
7300
7491
|
} else {
|
|
7301
7492
|
ensureDir();
|
|
7302
|
-
withLock("inbox.jsonl", () =>
|
|
7493
|
+
withLock("inbox.jsonl", () => fs11.appendFileSync(inboxPath(), JSON.stringify(rec) + "\n", { mode: 384 }));
|
|
7303
7494
|
}
|
|
7304
7495
|
ids.add(rec.id);
|
|
7305
7496
|
if (ids.size > 2e4) recentIds.set(key, new Set([...ids].slice(-1e4)));
|
|
@@ -7322,14 +7513,14 @@ function outboxAdd(item) {
|
|
|
7322
7513
|
return;
|
|
7323
7514
|
}
|
|
7324
7515
|
ensureDir(outboxDir());
|
|
7325
|
-
|
|
7516
|
+
fs11.writeFileSync(path10.join(outboxDir(), `${item.id}.json`), JSON.stringify(item), { mode: 384 });
|
|
7326
7517
|
}
|
|
7327
7518
|
function outboxList() {
|
|
7328
7519
|
if (isEphemeral()) return [...ramFor().outbox.values()].map((v) => JSON.parse(v));
|
|
7329
7520
|
try {
|
|
7330
|
-
return
|
|
7521
|
+
return fs11.readdirSync(outboxDir()).filter((f) => f.endsWith(".json")).map((f) => {
|
|
7331
7522
|
try {
|
|
7332
|
-
return JSON.parse(
|
|
7523
|
+
return JSON.parse(fs11.readFileSync(path10.join(outboxDir(), f), "utf8"));
|
|
7333
7524
|
} catch {
|
|
7334
7525
|
return null;
|
|
7335
7526
|
}
|
|
@@ -7343,7 +7534,7 @@ function outboxUpdate(item) {
|
|
|
7343
7534
|
if (ramFor().outbox.has(item.id)) ramFor().outbox.set(item.id, JSON.stringify(item));
|
|
7344
7535
|
return;
|
|
7345
7536
|
}
|
|
7346
|
-
if (OUTBOX_ID.test(item.id) &&
|
|
7537
|
+
if (OUTBOX_ID.test(item.id) && fs11.existsSync(path10.join(outboxDir(), `${item.id}.json`))) outboxAdd(item);
|
|
7347
7538
|
}
|
|
7348
7539
|
function outboxRemove(id) {
|
|
7349
7540
|
if (isEphemeral()) {
|
|
@@ -7352,7 +7543,7 @@ function outboxRemove(id) {
|
|
|
7352
7543
|
}
|
|
7353
7544
|
if (!OUTBOX_ID.test(id)) return;
|
|
7354
7545
|
try {
|
|
7355
|
-
|
|
7546
|
+
fs11.unlinkSync(path10.join(outboxDir(), `${id}.json`));
|
|
7356
7547
|
} catch {
|
|
7357
7548
|
}
|
|
7358
7549
|
}
|
|
@@ -7365,14 +7556,14 @@ function readLock() {
|
|
|
7365
7556
|
const p = file("node.lock");
|
|
7366
7557
|
let l = null;
|
|
7367
7558
|
try {
|
|
7368
|
-
l = JSON.parse(
|
|
7559
|
+
l = JSON.parse(fs11.readFileSync(p, "utf8"));
|
|
7369
7560
|
} catch {
|
|
7370
7561
|
return null;
|
|
7371
7562
|
}
|
|
7372
7563
|
if (!l || !Number.isInteger(l.pid)) return null;
|
|
7373
7564
|
if (l.pid === process.pid && l.nonce && l.nonce === myLockNonce) return l;
|
|
7374
7565
|
try {
|
|
7375
|
-
if (Date.now() -
|
|
7566
|
+
if (Date.now() - fs11.statSync(p).mtimeMs > LOCK_STALE_MS) return null;
|
|
7376
7567
|
} catch {
|
|
7377
7568
|
return null;
|
|
7378
7569
|
}
|
|
@@ -7383,16 +7574,16 @@ function acquireLock() {
|
|
|
7383
7574
|
const p = file("node.lock");
|
|
7384
7575
|
for (let attempt2 = 0; attempt2 < 2; attempt2++) {
|
|
7385
7576
|
try {
|
|
7386
|
-
const fd =
|
|
7577
|
+
const fd = fs11.openSync(p, "wx", 384);
|
|
7387
7578
|
myLockNonce = randomToken(12);
|
|
7388
|
-
|
|
7389
|
-
|
|
7579
|
+
fs11.writeSync(fd, JSON.stringify({ pid: process.pid, startedAt: nowSec(), nonce: myLockNonce }));
|
|
7580
|
+
fs11.closeSync(fd);
|
|
7390
7581
|
return true;
|
|
7391
7582
|
} catch (e) {
|
|
7392
7583
|
if (e?.code !== "EEXIST") throw e;
|
|
7393
7584
|
if (readLock()) return false;
|
|
7394
7585
|
try {
|
|
7395
|
-
|
|
7586
|
+
fs11.unlinkSync(p);
|
|
7396
7587
|
} catch {
|
|
7397
7588
|
}
|
|
7398
7589
|
}
|
|
@@ -7403,20 +7594,20 @@ function heartbeatLock() {
|
|
|
7403
7594
|
if (!myLockNonce) return;
|
|
7404
7595
|
const now = /* @__PURE__ */ new Date();
|
|
7405
7596
|
try {
|
|
7406
|
-
|
|
7597
|
+
fs11.utimesSync(file("node.lock"), now, now);
|
|
7407
7598
|
} catch {
|
|
7408
7599
|
}
|
|
7409
7600
|
}
|
|
7410
7601
|
function requestStop() {
|
|
7411
7602
|
ensureDir();
|
|
7412
|
-
|
|
7603
|
+
fs11.writeFileSync(file("node.stop"), String(Date.now()), { mode: 384 });
|
|
7413
7604
|
}
|
|
7414
7605
|
function stopRequested() {
|
|
7415
|
-
return
|
|
7606
|
+
return fs11.existsSync(file("node.stop"));
|
|
7416
7607
|
}
|
|
7417
7608
|
function clearStopRequest() {
|
|
7418
7609
|
try {
|
|
7419
|
-
|
|
7610
|
+
fs11.unlinkSync(file("node.stop"));
|
|
7420
7611
|
} catch {
|
|
7421
7612
|
}
|
|
7422
7613
|
}
|
|
@@ -7425,7 +7616,7 @@ function clearLock() {
|
|
|
7425
7616
|
if (l && l.pid === process.pid && (!l.nonce || l.nonce === myLockNonce)) {
|
|
7426
7617
|
myLockNonce = null;
|
|
7427
7618
|
try {
|
|
7428
|
-
|
|
7619
|
+
fs11.unlinkSync(file("node.lock"));
|
|
7429
7620
|
} catch {
|
|
7430
7621
|
}
|
|
7431
7622
|
}
|
|
@@ -7471,12 +7662,12 @@ function appendSent(rec) {
|
|
|
7471
7662
|
return;
|
|
7472
7663
|
}
|
|
7473
7664
|
ensureDir();
|
|
7474
|
-
withLock("sent.jsonl", () =>
|
|
7665
|
+
withLock("sent.jsonl", () => fs11.appendFileSync(file("sent.jsonl"), JSON.stringify(rec) + "\n", { mode: 384 }));
|
|
7475
7666
|
}
|
|
7476
7667
|
function readSent() {
|
|
7477
7668
|
if (isEphemeral()) return ramFor().sent.slice();
|
|
7478
7669
|
try {
|
|
7479
|
-
return
|
|
7670
|
+
return fs11.readFileSync(file("sent.jsonl"), "utf8").split("\n").filter(Boolean).map((l) => {
|
|
7480
7671
|
try {
|
|
7481
7672
|
return JSON.parse(l);
|
|
7482
7673
|
} catch {
|
|
@@ -7498,11 +7689,11 @@ function pidAlive(pid) {
|
|
|
7498
7689
|
function cleanStaleEphemeral() {
|
|
7499
7690
|
let n = 0;
|
|
7500
7691
|
try {
|
|
7501
|
-
for (const d of
|
|
7692
|
+
for (const d of fs11.readdirSync(os7.tmpdir())) {
|
|
7502
7693
|
const m = d.match(/^srift-an-(\d+)-/);
|
|
7503
7694
|
if (m && Number(m[1]) !== process.pid && !pidAlive(Number(m[1]))) {
|
|
7504
7695
|
try {
|
|
7505
|
-
|
|
7696
|
+
fs11.rmSync(path10.join(os7.tmpdir(), d), { recursive: true, force: true });
|
|
7506
7697
|
n++;
|
|
7507
7698
|
} catch {
|
|
7508
7699
|
}
|
|
@@ -7518,23 +7709,23 @@ function filesRoot() {
|
|
|
7518
7709
|
let root = ephemeralRoots.get(key);
|
|
7519
7710
|
if (!root) {
|
|
7520
7711
|
cleanStaleEphemeral();
|
|
7521
|
-
root =
|
|
7712
|
+
root = fs11.mkdtempSync(path10.join(os7.tmpdir(), `srift-an-${process.pid}-`));
|
|
7522
7713
|
ephemeralRoots.set(key, root);
|
|
7523
7714
|
const r = root;
|
|
7524
7715
|
process.once("exit", () => {
|
|
7525
7716
|
try {
|
|
7526
|
-
|
|
7717
|
+
fs11.rmSync(r, { recursive: true, force: true });
|
|
7527
7718
|
} catch {
|
|
7528
7719
|
}
|
|
7529
7720
|
});
|
|
7530
7721
|
}
|
|
7531
7722
|
return root;
|
|
7532
7723
|
}
|
|
7533
|
-
return
|
|
7724
|
+
return path10.join(anDir(), "files");
|
|
7534
7725
|
}
|
|
7535
7726
|
function filesDir(...parts) {
|
|
7536
|
-
const d =
|
|
7537
|
-
|
|
7727
|
+
const d = path10.join(filesRoot(), ...parts);
|
|
7728
|
+
fs11.mkdirSync(d, { recursive: true, mode: 448 });
|
|
7538
7729
|
return d;
|
|
7539
7730
|
}
|
|
7540
7731
|
function rewriteJsonl(name, keep) {
|
|
@@ -7543,7 +7734,7 @@ function rewriteJsonl(name, keep) {
|
|
|
7543
7734
|
withLock(name, () => {
|
|
7544
7735
|
let lines;
|
|
7545
7736
|
try {
|
|
7546
|
-
lines =
|
|
7737
|
+
lines = fs11.readFileSync(p, "utf8").split("\n").filter(Boolean);
|
|
7547
7738
|
} catch {
|
|
7548
7739
|
return;
|
|
7549
7740
|
}
|
|
@@ -7557,8 +7748,8 @@ function rewriteJsonl(name, keep) {
|
|
|
7557
7748
|
});
|
|
7558
7749
|
if (!removed) return;
|
|
7559
7750
|
const tmp = `${p}.${process.pid}.tmp`;
|
|
7560
|
-
|
|
7561
|
-
|
|
7751
|
+
fs11.writeFileSync(tmp, kept.map((l) => l + "\n").join(""), { mode: 384 });
|
|
7752
|
+
fs11.renameSync(tmp, p);
|
|
7562
7753
|
});
|
|
7563
7754
|
if (removed) recentIds.delete("disk:" + anDir());
|
|
7564
7755
|
return removed;
|
|
@@ -7602,11 +7793,11 @@ function prune(cfg = loadConfig(), now = nowSec()) {
|
|
|
7602
7793
|
return k;
|
|
7603
7794
|
});
|
|
7604
7795
|
try {
|
|
7605
|
-
const inc =
|
|
7606
|
-
for (const f of
|
|
7607
|
-
const fp =
|
|
7608
|
-
if (Date.now() -
|
|
7609
|
-
|
|
7796
|
+
const inc = path10.join(filesRoot(), ".incoming");
|
|
7797
|
+
for (const f of fs11.readdirSync(inc)) {
|
|
7798
|
+
const fp = path10.join(inc, f);
|
|
7799
|
+
if (Date.now() - fs11.statSync(fp).mtimeMs > 36e5) {
|
|
7800
|
+
fs11.rmSync(fp, { force: true });
|
|
7610
7801
|
r.partials++;
|
|
7611
7802
|
}
|
|
7612
7803
|
}
|
|
@@ -7617,19 +7808,19 @@ function prune(cfg = loadConfig(), now = nowSec()) {
|
|
|
7617
7808
|
const walk = (d) => {
|
|
7618
7809
|
let entries = [];
|
|
7619
7810
|
try {
|
|
7620
|
-
entries =
|
|
7811
|
+
entries = fs11.readdirSync(d, { withFileTypes: true });
|
|
7621
7812
|
} catch {
|
|
7622
7813
|
return;
|
|
7623
7814
|
}
|
|
7624
7815
|
for (const e of entries) {
|
|
7625
|
-
const fp =
|
|
7816
|
+
const fp = path10.join(d, e.name);
|
|
7626
7817
|
if (e.isDirectory()) {
|
|
7627
7818
|
if (e.name !== ".incoming") walk(fp);
|
|
7628
7819
|
continue;
|
|
7629
7820
|
}
|
|
7630
7821
|
try {
|
|
7631
|
-
if (
|
|
7632
|
-
|
|
7822
|
+
if (fs11.statSync(fp).mtimeMs < limit) {
|
|
7823
|
+
fs11.rmSync(fp, { force: true });
|
|
7633
7824
|
r.files++;
|
|
7634
7825
|
}
|
|
7635
7826
|
} catch {
|
|
@@ -7644,12 +7835,12 @@ function prune(cfg = loadConfig(), now = nowSec()) {
|
|
|
7644
7835
|
function rotateLog(maxBytes = 5 * 1024 * 1024) {
|
|
7645
7836
|
const p = file("node.log");
|
|
7646
7837
|
try {
|
|
7647
|
-
if (
|
|
7838
|
+
if (fs11.statSync(p).size <= maxBytes) return false;
|
|
7648
7839
|
try {
|
|
7649
|
-
|
|
7840
|
+
fs11.rmSync(p + ".1", { force: true });
|
|
7650
7841
|
} catch {
|
|
7651
7842
|
}
|
|
7652
|
-
|
|
7843
|
+
fs11.renameSync(p, p + ".1");
|
|
7653
7844
|
return true;
|
|
7654
7845
|
} catch {
|
|
7655
7846
|
return false;
|
|
@@ -7663,14 +7854,14 @@ function wipe(all = false) {
|
|
|
7663
7854
|
if (all) names.push("identity.json", "contacts.json", "groups.json", "config.json", "node.lock");
|
|
7664
7855
|
for (const n of names) {
|
|
7665
7856
|
const p = file(n);
|
|
7666
|
-
if (
|
|
7667
|
-
|
|
7857
|
+
if (fs11.existsSync(p)) {
|
|
7858
|
+
fs11.rmSync(p, { recursive: true, force: true });
|
|
7668
7859
|
removed.push(n);
|
|
7669
7860
|
}
|
|
7670
7861
|
}
|
|
7671
7862
|
if (all) {
|
|
7672
7863
|
try {
|
|
7673
|
-
for (const f of
|
|
7864
|
+
for (const f of fs11.readdirSync(anDir())) if (f.endsWith(".lock") || f.endsWith(".tmp")) fs11.rmSync(path10.join(anDir(), f), { force: true });
|
|
7674
7865
|
} catch {
|
|
7675
7866
|
}
|
|
7676
7867
|
}
|
|
@@ -7821,8 +8012,8 @@ var init_group = __esm({
|
|
|
7821
8012
|
|
|
7822
8013
|
// ../../cli/agentnet/files.ts
|
|
7823
8014
|
import crypto7 from "node:crypto";
|
|
7824
|
-
import
|
|
7825
|
-
import
|
|
8015
|
+
import fs12 from "node:fs";
|
|
8016
|
+
import path11 from "node:path";
|
|
7826
8017
|
function safeFileName(name) {
|
|
7827
8018
|
const raw = String(name || "file").split(/[\\/]/).pop() || "file";
|
|
7828
8019
|
let base = raw.replace(/[\u0000-\u001f\u007f<>:"|?*]/g, "_").replace(/^\.+/, "_").replace(/[. ]+$/, "").trim();
|
|
@@ -7830,33 +8021,33 @@ function safeFileName(name) {
|
|
|
7830
8021
|
return (base || "file").slice(0, 180);
|
|
7831
8022
|
}
|
|
7832
8023
|
function uniquePath2(dir, name) {
|
|
7833
|
-
let p =
|
|
7834
|
-
if (!
|
|
7835
|
-
const ext =
|
|
8024
|
+
let p = path11.join(dir, name);
|
|
8025
|
+
if (!fs12.existsSync(p)) return p;
|
|
8026
|
+
const ext = path11.extname(name);
|
|
7836
8027
|
const stem2 = name.slice(0, name.length - ext.length);
|
|
7837
8028
|
for (let i = 1; ; i++) {
|
|
7838
|
-
p =
|
|
7839
|
-
if (!
|
|
8029
|
+
p = path11.join(dir, `${stem2} (${i})${ext}`);
|
|
8030
|
+
if (!fs12.existsSync(p)) return p;
|
|
7840
8031
|
}
|
|
7841
8032
|
}
|
|
7842
8033
|
function sha256File(p) {
|
|
7843
8034
|
const h = crypto7.createHash("sha256");
|
|
7844
|
-
const fd =
|
|
8035
|
+
const fd = fs12.openSync(p, "r");
|
|
7845
8036
|
try {
|
|
7846
8037
|
const buf = Buffer.alloc(1 << 20);
|
|
7847
8038
|
let n;
|
|
7848
|
-
while ((n =
|
|
8039
|
+
while ((n = fs12.readSync(fd, buf, 0, buf.length, null)) > 0) h.update(buf.subarray(0, n));
|
|
7849
8040
|
} finally {
|
|
7850
|
-
|
|
8041
|
+
fs12.closeSync(fd);
|
|
7851
8042
|
}
|
|
7852
8043
|
return h.digest("hex");
|
|
7853
8044
|
}
|
|
7854
8045
|
function makeOffer(filePath, groupId) {
|
|
7855
|
-
const st =
|
|
8046
|
+
const st = fs12.statSync(filePath);
|
|
7856
8047
|
if (!st.isFile()) throw new Error(`Not a file: ${filePath}`);
|
|
7857
8048
|
return {
|
|
7858
8049
|
fileId: newId(),
|
|
7859
|
-
name: safeFileName(
|
|
8050
|
+
name: safeFileName(path11.basename(filePath)),
|
|
7860
8051
|
size: st.size,
|
|
7861
8052
|
sha256: sha256File(filePath),
|
|
7862
8053
|
chunkSize: CHUNK_SIZE,
|
|
@@ -7867,7 +8058,7 @@ function makeOffer(filePath, groupId) {
|
|
|
7867
8058
|
function readChunk(fd, offer, index) {
|
|
7868
8059
|
const len = Math.min(offer.chunkSize, offer.size - index * offer.chunkSize);
|
|
7869
8060
|
const buf = Buffer.alloc(Math.max(0, len));
|
|
7870
|
-
if (len > 0)
|
|
8061
|
+
if (len > 0) fs12.readSync(fd, buf, 0, len, index * offer.chunkSize);
|
|
7871
8062
|
return buf.toString("base64");
|
|
7872
8063
|
}
|
|
7873
8064
|
var CHUNK_SIZE, FILE_WINDOW, IDLE_MS, MAX_PER_SENDER, MAX_TOTAL, MAX_TOTAL_BYTES, WIN_RESERVED2, FileReceiver;
|
|
@@ -7910,8 +8101,8 @@ var init_files = __esm({
|
|
|
7910
8101
|
}
|
|
7911
8102
|
begin(offer, from, fromName) {
|
|
7912
8103
|
const dir = filesDir(".incoming");
|
|
7913
|
-
const part =
|
|
7914
|
-
const fd =
|
|
8104
|
+
const part = path11.join(dir, `${offer.fileId}.part`);
|
|
8105
|
+
const fd = fs12.openSync(part, "w", 384);
|
|
7915
8106
|
this.incoming.set(offer.fileId, { ...offer, name: safeFileName(offer.name), from, fromName, part, fd, received: /* @__PURE__ */ new Set(), bytes: 0, lastAt: Date.now() });
|
|
7916
8107
|
}
|
|
7917
8108
|
/** Write one chunk. `complete` = every chunk is on disk (call finalize() after acking). */
|
|
@@ -7926,7 +8117,7 @@ var init_files = __esm({
|
|
|
7926
8117
|
const data = Buffer.from(body.data, "base64");
|
|
7927
8118
|
const expected = Math.min(t.chunkSize, t.size - index * t.chunkSize);
|
|
7928
8119
|
if (data.length !== Math.max(0, expected)) return { error: "chunk size mismatch" };
|
|
7929
|
-
if (data.length)
|
|
8120
|
+
if (data.length) fs12.writeSync(t.fd, data, 0, data.length, index * t.chunkSize);
|
|
7930
8121
|
t.received.add(index);
|
|
7931
8122
|
t.bytes += data.length;
|
|
7932
8123
|
t.lastAt = Date.now();
|
|
@@ -7937,7 +8128,7 @@ var init_files = __esm({
|
|
|
7937
8128
|
const t = this.incoming.get(fileId);
|
|
7938
8129
|
if (!t) return { error: "unknown transfer" };
|
|
7939
8130
|
try {
|
|
7940
|
-
|
|
8131
|
+
fs12.closeSync(t.fd);
|
|
7941
8132
|
} catch {
|
|
7942
8133
|
}
|
|
7943
8134
|
this.incoming.delete(t.fileId);
|
|
@@ -7947,26 +8138,26 @@ var init_files = __esm({
|
|
|
7947
8138
|
const actual = sha256File(t.part);
|
|
7948
8139
|
if (actual !== t.sha256) {
|
|
7949
8140
|
try {
|
|
7950
|
-
|
|
8141
|
+
fs12.unlinkSync(t.part);
|
|
7951
8142
|
} catch {
|
|
7952
8143
|
}
|
|
7953
8144
|
return { error: "hash mismatch" };
|
|
7954
8145
|
}
|
|
7955
8146
|
const dir = t.groupId ? filesDir("groups", t.groupId) : filesDir(compactAddress(t.from));
|
|
7956
8147
|
const dest = uniquePath2(dir, t.name);
|
|
7957
|
-
const root =
|
|
7958
|
-
if (!
|
|
8148
|
+
const root = path11.resolve(filesRoot()) + path11.sep;
|
|
8149
|
+
if (!path11.resolve(dest).startsWith(root)) {
|
|
7959
8150
|
try {
|
|
7960
|
-
|
|
8151
|
+
fs12.unlinkSync(t.part);
|
|
7961
8152
|
} catch {
|
|
7962
8153
|
}
|
|
7963
8154
|
return { error: "invalid destination" };
|
|
7964
8155
|
}
|
|
7965
|
-
|
|
7966
|
-
return { done: { fileId: t.fileId, from: t.from, fromName: t.fromName, name:
|
|
8156
|
+
fs12.renameSync(t.part, dest);
|
|
8157
|
+
return { done: { fileId: t.fileId, from: t.from, fromName: t.fromName, name: path11.basename(dest), size: t.size, sha256: actual, path: dest, groupId: t.groupId } };
|
|
7967
8158
|
} catch (e) {
|
|
7968
8159
|
try {
|
|
7969
|
-
|
|
8160
|
+
fs12.unlinkSync(t.part);
|
|
7970
8161
|
} catch {
|
|
7971
8162
|
}
|
|
7972
8163
|
return { error: `could not save file: ${e?.code || e?.message}` };
|
|
@@ -7976,11 +8167,11 @@ var init_files = __esm({
|
|
|
7976
8167
|
const t = this.incoming.get(fileId);
|
|
7977
8168
|
if (!t) return;
|
|
7978
8169
|
try {
|
|
7979
|
-
|
|
8170
|
+
fs12.closeSync(t.fd);
|
|
7980
8171
|
} catch {
|
|
7981
8172
|
}
|
|
7982
8173
|
try {
|
|
7983
|
-
|
|
8174
|
+
fs12.unlinkSync(t.part);
|
|
7984
8175
|
} catch {
|
|
7985
8176
|
}
|
|
7986
8177
|
this.incoming.delete(fileId);
|
|
@@ -8264,10 +8455,9 @@ var init_relay_client = __esm({
|
|
|
8264
8455
|
reject(new Error("closed before auth"));
|
|
8265
8456
|
return;
|
|
8266
8457
|
}
|
|
8267
|
-
if (this.ws
|
|
8268
|
-
|
|
8269
|
-
|
|
8270
|
-
}
|
|
8458
|
+
if (this.ws !== ws) return;
|
|
8459
|
+
this.ws = null;
|
|
8460
|
+
this.transport = null;
|
|
8271
8461
|
for (const [, cb] of this.waiting) cb({ type: "error", error: "connection closed" });
|
|
8272
8462
|
this.waiting.clear();
|
|
8273
8463
|
this.emit("disconnected");
|
|
@@ -8602,6 +8792,8 @@ async function resolveAll(query) {
|
|
|
8602
8792
|
}
|
|
8603
8793
|
const tag = q.startsWith("@") || /~[a-z2-7]{4,20}$/i.test(q) ? parseHandleTag(q) : null;
|
|
8604
8794
|
if (tag) {
|
|
8795
|
+
const known = Object.values(loadContacts()).filter((c) => c.policy !== "blocked" && cardMatchesTag({ ...c.card || {}, name: c.card?.name || c.name, address: c.address }, tag));
|
|
8796
|
+
if (known.length === 1) return [{ address: known[0].address, card: checked(known[0].card, known[0].address), source: "contacts" }];
|
|
8605
8797
|
const res = await liveSearch({ handle: tag.suffix ? `${tag.handle}~${tag.suffix}` : tag.handle, limit: 20 });
|
|
8606
8798
|
return res.filter((r) => r.card && cardMatchesTag(r.card, tag)).map((r) => ({ ...r, source: "handle" }));
|
|
8607
8799
|
}
|
|
@@ -8712,7 +8904,7 @@ var init_netpolicy = __esm({
|
|
|
8712
8904
|
|
|
8713
8905
|
// ../../cli/agentnet/node.ts
|
|
8714
8906
|
import { EventEmitter as EventEmitter2 } from "node:events";
|
|
8715
|
-
import
|
|
8907
|
+
import fs13 from "node:fs";
|
|
8716
8908
|
async function pool(items, n, fn) {
|
|
8717
8909
|
const out = new Array(items.length);
|
|
8718
8910
|
let i = 0;
|
|
@@ -9174,7 +9366,7 @@ var init_node = __esm({
|
|
|
9174
9366
|
if (!a) return { ...base, result: "no_answer", reason: "recipient did not answer the offer (is its node running?)" };
|
|
9175
9367
|
if (!a.body?.accepted) return { ...base, result: "rejected", reason: a.body?.reason };
|
|
9176
9368
|
const ack = this.waitFor((r) => r.type === "file_ack" && r.from === to2 && r.body?.fileId === offer.fileId, 12e4);
|
|
9177
|
-
const fd =
|
|
9369
|
+
const fd = fs13.openSync(filePath, "r");
|
|
9178
9370
|
let failed = null;
|
|
9179
9371
|
let sent = 0;
|
|
9180
9372
|
try {
|
|
@@ -9194,7 +9386,7 @@ var init_node = __esm({
|
|
|
9194
9386
|
failed = `chunk ${i} not delivered`;
|
|
9195
9387
|
});
|
|
9196
9388
|
} finally {
|
|
9197
|
-
|
|
9389
|
+
fs13.closeSync(fd);
|
|
9198
9390
|
}
|
|
9199
9391
|
if (failed) {
|
|
9200
9392
|
ack.cancel();
|
|
@@ -9363,9 +9555,11 @@ var init_node = __esm({
|
|
|
9363
9555
|
const targets = [...new Set(outboxList().map((i) => i.to))].filter((t) => !this.watchedTargets.has(t));
|
|
9364
9556
|
if (!targets.length) return;
|
|
9365
9557
|
for (const t of targets) this.watchedTargets.add(t);
|
|
9366
|
-
await this.watch(targets).catch(() => {
|
|
9558
|
+
const states = await this.watch(targets).catch(() => {
|
|
9367
9559
|
for (const t of targets) this.watchedTargets.delete(t);
|
|
9560
|
+
return {};
|
|
9368
9561
|
});
|
|
9562
|
+
for (const [addr, st] of Object.entries(states)) if (st === "online" && this.opts.recv) void this.flushOutboxFor(addr);
|
|
9369
9563
|
}
|
|
9370
9564
|
expired(i) {
|
|
9371
9565
|
return nowSec() - i.createdAt > this.cfg.outboxMaxAgeDays * 86400;
|
|
@@ -10874,8 +11068,8 @@ __export(mcp_exports, {
|
|
|
10874
11068
|
createAgentNetMcpHandler: () => createAgentNetMcpHandler,
|
|
10875
11069
|
startAgentNetMcp: () => startAgentNetMcp
|
|
10876
11070
|
});
|
|
10877
|
-
import
|
|
10878
|
-
import
|
|
11071
|
+
import fs14 from "node:fs";
|
|
11072
|
+
import path12 from "node:path";
|
|
10879
11073
|
function text(o, isError = false) {
|
|
10880
11074
|
return { content: [{ type: "text", text: typeof o === "string" ? o : JSON.stringify(o, null, 2) }], structuredContent: typeof o === "object" ? o : void 0, isError };
|
|
10881
11075
|
}
|
|
@@ -10897,16 +11091,16 @@ async function resolveTarget(t) {
|
|
|
10897
11091
|
const direct = normalizeAddress(t);
|
|
10898
11092
|
if (direct && t.replace(/^srift:/i, "").replace(/-/g, "").length === 20) return direct;
|
|
10899
11093
|
const r = await resolveAll(t);
|
|
10900
|
-
if (r.length > 1) throw new Error(`"${t}" is ambiguous (${r.length} online agents): ${r.map((x) => "@" + (x.tag || x.address)).join(", ")}. Use the full @name~
|
|
11094
|
+
if (r.length > 1) throw new Error(`"${t}" is ambiguous (${r.length} online agents): ${r.map((x) => "@" + (x.tag || x.address)).join(", ")}. Use the full @name~xxxxxxxx tag.`);
|
|
10901
11095
|
if (!r.length) throw new Error(`Could not resolve "${t}" (it must be online, or use an invite/address). Try srift_an_search.`);
|
|
10902
11096
|
return r[0].address;
|
|
10903
11097
|
}
|
|
10904
11098
|
function allowedFile(p) {
|
|
10905
|
-
const abs =
|
|
10906
|
-
const roots = (process.env.SRIFT_AN_FILE_ROOTS || process.cwd()).split(
|
|
10907
|
-
const inside = roots.some((r) => abs === r || abs.startsWith(r.endsWith(
|
|
11099
|
+
const abs = path12.resolve(p);
|
|
11100
|
+
const roots = (process.env.SRIFT_AN_FILE_ROOTS || process.cwd()).split(path12.delimiter).filter(Boolean).map((r) => path12.resolve(r));
|
|
11101
|
+
const inside = roots.some((r) => abs === r || abs.startsWith(r.endsWith(path12.sep) ? r : r + path12.sep));
|
|
10908
11102
|
if (!inside) throw new Error(`Refusing to send ${abs}: only files under ${roots.join(", ")} can be sent through MCP (set SRIFT_AN_FILE_ROOTS to allow more).`);
|
|
10909
|
-
const rel =
|
|
11103
|
+
const rel = path12.relative(roots.find((r) => abs.startsWith(r)) || roots[0], abs);
|
|
10910
11104
|
if (rel.split(/[\\/]/).some((seg) => seg.startsWith("."))) throw new Error(`Refusing to send hidden files or folders (${rel}) through MCP.`);
|
|
10911
11105
|
return abs;
|
|
10912
11106
|
}
|
|
@@ -11243,13 +11437,13 @@ async function startAgentNetMcp(opts = {}) {
|
|
|
11243
11437
|
hooks.onDiscovery = h.onDiscovery;
|
|
11244
11438
|
let lastSize = 0;
|
|
11245
11439
|
try {
|
|
11246
|
-
lastSize =
|
|
11440
|
+
lastSize = fs14.statSync(inboxPath()).size;
|
|
11247
11441
|
} catch {
|
|
11248
11442
|
}
|
|
11249
11443
|
setInterval(() => {
|
|
11250
11444
|
if (ownsLock) return;
|
|
11251
11445
|
try {
|
|
11252
|
-
const s =
|
|
11446
|
+
const s = fs14.statSync(inboxPath()).size;
|
|
11253
11447
|
if (s !== lastSize) {
|
|
11254
11448
|
lastSize = s;
|
|
11255
11449
|
hooks.onInbox();
|
|
@@ -11311,7 +11505,7 @@ var init_mcp2 = __esm({
|
|
|
11311
11505
|
LEGACY_VERSIONS = ["2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05"];
|
|
11312
11506
|
INBOX_URI = "srift://agentnet/inbox";
|
|
11313
11507
|
DISCOVERIES_URI = "srift://agentnet/discoveries";
|
|
11314
|
-
to = { type: "string", description: "Target: srift: address, @name~
|
|
11508
|
+
to = { type: "string", description: "Target: srift: address, @name~xxxxxxxx tag, @name, @owner/agent, contact name, name@domain, domain, or invite link" };
|
|
11315
11509
|
obj = (properties, required = []) => ({ type: "object", properties, required, additionalProperties: false });
|
|
11316
11510
|
AGENTNET_TOOLS = [
|
|
11317
11511
|
{ name: "srift_an_whoami", description: "This agent's permanent address (its 'phone number'), unique @tag, one-line description, discoverability and relays.", inputSchema: obj({}) },
|
|
@@ -11334,7 +11528,7 @@ var init_mcp2 = __esm({
|
|
|
11334
11528
|
},
|
|
11335
11529
|
{
|
|
11336
11530
|
name: "srift_an_find",
|
|
11337
|
-
description: "Resolve a specific agent: srift: address, @name~
|
|
11531
|
+
description: "Resolve a specific agent: srift: address, @name~xxxxxxxx, @name, @owner/agent, owner:@owner (all their agents), name@domain, domain, or invite link. Falls back to live search.",
|
|
11338
11532
|
inputSchema: obj({ query: { type: "string" }, limit: { type: "number" } }, ["query"])
|
|
11339
11533
|
},
|
|
11340
11534
|
{
|
|
@@ -11436,8 +11630,8 @@ __export(cli_exports, {
|
|
|
11436
11630
|
inboxPath: () => inboxPath,
|
|
11437
11631
|
run: () => run
|
|
11438
11632
|
});
|
|
11439
|
-
import
|
|
11440
|
-
import
|
|
11633
|
+
import fs15 from "node:fs";
|
|
11634
|
+
import path13 from "node:path";
|
|
11441
11635
|
import { spawn as spawn3 } from "node:child_process";
|
|
11442
11636
|
import readline from "node:readline";
|
|
11443
11637
|
function flag(args, name) {
|
|
@@ -11577,12 +11771,12 @@ ${r.map((x) => ` @${x.tag || (x.card ? handleTag(x.card) : x.address)} ${x.one
|
|
|
11577
11771
|
if (action === "export") {
|
|
11578
11772
|
const file2 = pos[2] || fail("Usage: srift an id export <file>");
|
|
11579
11773
|
const { identity: identity2 } = ensureIdentity();
|
|
11580
|
-
|
|
11774
|
+
fs15.writeFileSync(file2, JSON.stringify(identity2, null, 2), { mode: 384 });
|
|
11581
11775
|
return out(`Exported identity (PRIVATE KEYS) to ${file2}. Keep it secret.`, { exported: file2, address: identity2.address });
|
|
11582
11776
|
}
|
|
11583
11777
|
if (action === "import") {
|
|
11584
11778
|
const file2 = pos[2] || fail("Usage: srift an id import <file>");
|
|
11585
|
-
const idt = JSON.parse(
|
|
11779
|
+
const idt = JSON.parse(fs15.readFileSync(file2, "utf8"));
|
|
11586
11780
|
if (addressFromEdPub(idt.edPub) !== idt.address || !idt.edPriv || !idt.xPriv) fail("Not a valid AgentNet identity file.");
|
|
11587
11781
|
if (hasIdentity() && !has(args, "--force")) fail("An identity already exists. Re-run with --force to replace it.");
|
|
11588
11782
|
saveIdentity(idt);
|
|
@@ -11698,12 +11892,12 @@ Home: ${anDir()}`,
|
|
|
11698
11892
|
case "up": {
|
|
11699
11893
|
if (has(args, "--detach")) {
|
|
11700
11894
|
const childArgs = [...process.execArgv, process.argv[1], ...process.argv.slice(2).filter((a) => a !== "--detach")];
|
|
11701
|
-
|
|
11895
|
+
fs15.mkdirSync(anDir(), { recursive: true });
|
|
11702
11896
|
rotateLog();
|
|
11703
|
-
const logFd =
|
|
11897
|
+
const logFd = fs15.openSync(path13.join(anDir(), "node.log"), "a");
|
|
11704
11898
|
const child = spawn3(process.execPath, childArgs, { detached: true, stdio: ["ignore", logFd, logFd], windowsHide: true });
|
|
11705
11899
|
child.unref();
|
|
11706
|
-
return out(`AgentNet node started in background (pid ${child.pid}). Log: ${
|
|
11900
|
+
return out(`AgentNet node started in background (pid ${child.pid}). Log: ${path13.join(anDir(), "node.log")}`, { pid: child.pid });
|
|
11707
11901
|
}
|
|
11708
11902
|
const { identity } = ensureIdentity();
|
|
11709
11903
|
if (sub === "host" || has(args, "--discoverable")) {
|
|
@@ -11903,13 +12097,13 @@ Home: ${anDir()}`,
|
|
|
11903
12097
|
}
|
|
11904
12098
|
try {
|
|
11905
12099
|
if (t.startsWith("/file ")) {
|
|
11906
|
-
const fp =
|
|
12100
|
+
const fp = path13.resolve(t.slice(6).trim().replace(/^"|"$/g, ""));
|
|
11907
12101
|
if (g) {
|
|
11908
12102
|
const r = await n.groupSendFile(g.state.id, fp);
|
|
11909
|
-
console.log(` \u{1F4CE} ${
|
|
12103
|
+
console.log(` \u{1F4CE} ${path13.basename(fp)}: ${r.results.filter((x) => x.result === "delivered").length}/${r.results.length} members received it`);
|
|
11910
12104
|
} else {
|
|
11911
12105
|
const r = await n.sendFile(peer, fp);
|
|
11912
|
-
console.log(` \u{1F4CE} ${
|
|
12106
|
+
console.log(` \u{1F4CE} ${path13.basename(fp)}: ${r.result}${r.reason ? ` \u2014 ${r.reason}` : ""}`);
|
|
11913
12107
|
}
|
|
11914
12108
|
} else if (g) {
|
|
11915
12109
|
const r = await n.groupSend(g.state.id, t);
|
|
@@ -11940,8 +12134,8 @@ Home: ${anDir()}`,
|
|
|
11940
12134
|
case "file": {
|
|
11941
12135
|
const a = await target(pos[1]);
|
|
11942
12136
|
const fp = pos[2] || fail("Usage: srift an file <to> <path>");
|
|
11943
|
-
const r = await withNode(true, (n) => n.sendFile(a,
|
|
11944
|
-
if (!json) process.stderr.write(`\r[agentnet] sending ${
|
|
12137
|
+
const r = await withNode(true, (n) => n.sendFile(a, path13.resolve(fp), { onProgress: (p) => {
|
|
12138
|
+
if (!json) process.stderr.write(`\r[agentnet] sending ${path13.basename(fp)} ${Math.round(p * 100)}%`);
|
|
11945
12139
|
} }));
|
|
11946
12140
|
if (!json) process.stderr.write("\n");
|
|
11947
12141
|
out(r.result === "delivered" ? `Delivered ${r.name} (${r.size} bytes) to ${a} in ${((r.ms || 0) / 1e3).toFixed(1)}s \u2014 SHA-256 verified by the receiver.` : `Not delivered: ${r.result}${r.reason ? ` \u2014 ${r.reason}` : ""}`, r);
|
|
@@ -12023,9 +12217,9 @@ Chat: srift an chat ${r.group.id}`, r);
|
|
|
12023
12217
|
if (action === "file") {
|
|
12024
12218
|
const id = gid(pos[2]);
|
|
12025
12219
|
const fp = pos[3] || fail("Usage: srift an group file <group> <path>");
|
|
12026
|
-
const r = await withNode(true, (n) => n.groupSendFile(id,
|
|
12220
|
+
const r = await withNode(true, (n) => n.groupSendFile(id, path13.resolve(fp)));
|
|
12027
12221
|
const ok = r.results.filter((x) => x.result === "delivered").length;
|
|
12028
|
-
return out(`${
|
|
12222
|
+
return out(`${path13.basename(fp)} delivered to ${ok}/${r.results.length} members (SHA-256 verified).
|
|
12029
12223
|
${r.results.map((x) => ` ${x.to}: ${x.result}${x.reason ? ` \u2014 ${x.reason}` : ""}`).join("\n")}`, r);
|
|
12030
12224
|
}
|
|
12031
12225
|
if (action === "call") {
|
|
@@ -12071,8 +12265,8 @@ ${r.results.map((x) => ` ${x.to}: ${x.result}${x.reason ? ` \u2014 ${x.reason}`
|
|
|
12071
12265
|
if (file2 && !has(args, "--link")) {
|
|
12072
12266
|
const res = await withNode(true, async (n) => {
|
|
12073
12267
|
const m = textArg ? await n.send(a, "msg", body) : null;
|
|
12074
|
-
const f = await n.sendFile(a,
|
|
12075
|
-
if (!json) process.stderr.write(`\r[agentnet] sending ${
|
|
12268
|
+
const f = await n.sendFile(a, path13.resolve(file2), { onProgress: (p) => {
|
|
12269
|
+
if (!json) process.stderr.write(`\r[agentnet] sending ${path13.basename(file2)} ${Math.round(p * 100)}%`);
|
|
12076
12270
|
} });
|
|
12077
12271
|
return { message: m, file: f };
|
|
12078
12272
|
});
|
|
@@ -12084,9 +12278,9 @@ ${r.results.map((x) => ` ${x.to}: ${x.result}${x.reason ? ` \u2014 ${x.reason}`
|
|
|
12084
12278
|
if (file2) {
|
|
12085
12279
|
if (!helpers.callDaemon) fail("File attachments need the SRIFT daemon.");
|
|
12086
12280
|
await helpers.ensureDaemon?.();
|
|
12087
|
-
const share = await helpers.callDaemon("/quick-share", "POST", { filePath:
|
|
12281
|
+
const share = await helpers.callDaemon("/quick-share", "POST", { filePath: path13.resolve(file2), encrypt: true });
|
|
12088
12282
|
if (!share?.downloadUrl) fail(`quick-share failed: ${share?.error || "unknown error"}`);
|
|
12089
|
-
body.attachments = [{ name:
|
|
12283
|
+
body.attachments = [{ name: path13.basename(file2), url: share.downloadUrl, size: share.size }];
|
|
12090
12284
|
}
|
|
12091
12285
|
const queue = has(args, "--queue");
|
|
12092
12286
|
const r = await withNode(false, (n) => n.send(a, "msg", body, { queue }));
|
|
@@ -12412,7 +12606,7 @@ Hooks fire from the running node ('srift an up' / 'host' / 'mcp').`, { hooks: ma
|
|
|
12412
12606
|
sessions,
|
|
12413
12607
|
quickShare: helpers.callDaemon ? async (filePath) => {
|
|
12414
12608
|
await helpers.ensureDaemon?.();
|
|
12415
|
-
const r = await helpers.callDaemon("/quick-share", "POST", { filePath:
|
|
12609
|
+
const r = await helpers.callDaemon("/quick-share", "POST", { filePath: path13.resolve(filePath), encrypt: true });
|
|
12416
12610
|
if (!r?.downloadUrl) throw new Error(r?.error || "quick-share failed");
|
|
12417
12611
|
return { url: r.downloadUrl };
|
|
12418
12612
|
} : void 0
|
|
@@ -12441,7 +12635,7 @@ No database anywhere: relays keep only what is live (RAM) and route only to ONLI
|
|
|
12441
12635
|
|
|
12442
12636
|
Identity (your "phone number")
|
|
12443
12637
|
srift an id [--name N] [--description D] [--skills a,b] show/create your address + handle tag
|
|
12444
|
-
srift an id handle @name set a handle; your tag is name~
|
|
12638
|
+
srift an id handle @name set a handle; your tag is name~xxxxxxxx (key-derived, unforgeable)
|
|
12445
12639
|
srift an id owner <srift-own:token> | --clear accept an owner's signed claim
|
|
12446
12640
|
srift an id link-domain acme.com [--name support] print /.well-known/srift for your domain
|
|
12447
12641
|
srift an id export <file> | import <file> | rotate
|
|
@@ -12453,7 +12647,7 @@ Be found (live)
|
|
|
12453
12647
|
srift an up [--discoverable] [--poll] [--detach] go online; srift an down
|
|
12454
12648
|
Find & reach others
|
|
12455
12649
|
srift an search "what you need" [--watch] [--limit 10] live search; --watch notifies as matches come online
|
|
12456
|
-
srift an find <srift:addr|@name|@name~
|
|
12650
|
+
srift an find <srift:addr|@name|@name~xxxxxxxx|@owner/agent|owner:@owner|name@domain|domain|invite|"need">
|
|
12457
12651
|
srift an knock <to> "hey, it's me \u2014 \u2026" [--wait 60] ask to connect; they accept/reject with a note
|
|
12458
12652
|
srift an connect "what you need" [--max 5] [--wait 30] [--note "\u2026"] [--message "\u2026" | --no-message]
|
|
12459
12653
|
search \u2192 knock best matches \u2192 first accept \u2192 conversation starts
|
|
@@ -12522,8 +12716,8 @@ Add --json to any command for machine-readable output.`;
|
|
|
12522
12716
|
import { spawn as spawn4, execSync } from "child_process";
|
|
12523
12717
|
import http7 from "http";
|
|
12524
12718
|
import https3 from "https";
|
|
12525
|
-
import
|
|
12526
|
-
import
|
|
12719
|
+
import fs16 from "fs";
|
|
12720
|
+
import path14 from "path";
|
|
12527
12721
|
import os8 from "os";
|
|
12528
12722
|
import crypto8 from "crypto";
|
|
12529
12723
|
import net4 from "net";
|
|
@@ -12535,8 +12729,8 @@ init_history();
|
|
|
12535
12729
|
init_embedded();
|
|
12536
12730
|
import http5 from "http";
|
|
12537
12731
|
import https2 from "https";
|
|
12538
|
-
import
|
|
12539
|
-
import
|
|
12732
|
+
import fs9 from "fs";
|
|
12733
|
+
import path8 from "path";
|
|
12540
12734
|
import os5 from "os";
|
|
12541
12735
|
import { fileURLToPath } from "url";
|
|
12542
12736
|
var DAEMON_PORT2 = parseInt(process.env.SRIFT_DAEMON_PORT || "3822", 10);
|
|
@@ -12602,7 +12796,7 @@ async function handleSessionStart(name, roomSecret, isJson) {
|
|
|
12602
12796
|
} else {
|
|
12603
12797
|
console.log(`[SRIFT] Session created successfully!`);
|
|
12604
12798
|
console.log(`Session ID: ${res.sessionId}`);
|
|
12605
|
-
console.log(`Access Link: https://srift.app/join-session?id=${res.sessionId}`);
|
|
12799
|
+
console.log(`Access Link: ${res.joinUrl || `https://srift.app/join-session?id=${res.sessionId}`}`);
|
|
12606
12800
|
if (roomSecret) {
|
|
12607
12801
|
console.log(`Room Secret: ${roomSecret} (derived locally, never sent to server)`);
|
|
12608
12802
|
}
|
|
@@ -12649,6 +12843,14 @@ Pending Join Requests (${res.pendingJoins.length}):`);
|
|
|
12649
12843
|
console.log(` - ${j.username} (${j.tempUserId}) [Run 'srift approve ${j.tempUserId}' to let them in]`);
|
|
12650
12844
|
});
|
|
12651
12845
|
}
|
|
12846
|
+
if (res.participants && res.participants.length > 0) {
|
|
12847
|
+
console.log(`
|
|
12848
|
+
Members (${res.participants.length}):`);
|
|
12849
|
+
res.participants.forEach((p) => {
|
|
12850
|
+
const kick = s.role === "host" && !p.isHost ? ` [remove: 'srift kick ${p.userId}']` : "";
|
|
12851
|
+
console.log(` - ${p.username}${p.isHost ? " (host)" : ""} ${p.online ? "online" : "offline"} (${p.userId})${kick}`);
|
|
12852
|
+
});
|
|
12853
|
+
}
|
|
12652
12854
|
}
|
|
12653
12855
|
} catch (err) {
|
|
12654
12856
|
console.error(`Error: ${err.message}`);
|
|
@@ -13023,8 +13225,25 @@ async function handlePubshareAdd(filePath, isJson, opts = {}) {
|
|
|
13023
13225
|
process.exit(1);
|
|
13024
13226
|
}
|
|
13025
13227
|
}
|
|
13026
|
-
|
|
13228
|
+
var TERMINAL_STATUSES = ["completed", "error", "cancelled"];
|
|
13229
|
+
async function handleMonitorTransfer(fileId, jsonStream) {
|
|
13027
13230
|
const url = `${DAEMON_URL2}/api/v1/monitor/events`;
|
|
13231
|
+
try {
|
|
13232
|
+
const tx = ((await callDaemon2("/status", "GET")).activeTransfers || []).find((t) => t.fileId === fileId);
|
|
13233
|
+
if (!tx) {
|
|
13234
|
+
console.error(`[SRIFT] No transfer with id ${fileId}. See: srift list`);
|
|
13235
|
+
process.exit(1);
|
|
13236
|
+
}
|
|
13237
|
+
if (jsonStream) console.log(JSON.stringify({ fileId, fileName: tx.name, size: tx.size, progress: tx.progress, status: tx.status }));
|
|
13238
|
+
else renderProgressBar(tx.progress || 0, tx.speedKBps || 0, tx.etaSeconds || 0, tx.status);
|
|
13239
|
+
if (TERMINAL_STATUSES.includes(tx.status)) {
|
|
13240
|
+
if (!jsonStream) console.log("\nTransfer reached terminal state:", tx.status);
|
|
13241
|
+
process.exit(0);
|
|
13242
|
+
}
|
|
13243
|
+
} catch (err) {
|
|
13244
|
+
console.error(`[SRIFT] Monitor error: ${err.message}`);
|
|
13245
|
+
process.exit(1);
|
|
13246
|
+
}
|
|
13028
13247
|
const req = http5.request(url, (res) => {
|
|
13029
13248
|
res.on("data", (chunk) => {
|
|
13030
13249
|
const lines = chunk.toString().split("\n");
|
|
@@ -13038,6 +13257,7 @@ function handleMonitorTransfer(fileId, jsonStream) {
|
|
|
13038
13257
|
if (currentEvent === "transfer_progress" && dataJson.fileId === fileId) {
|
|
13039
13258
|
if (jsonStream) {
|
|
13040
13259
|
console.log(JSON.stringify(dataJson));
|
|
13260
|
+
if (TERMINAL_STATUSES.includes(dataJson.status)) process.exit(0);
|
|
13041
13261
|
} else {
|
|
13042
13262
|
renderProgressBar(
|
|
13043
13263
|
dataJson.progress,
|
|
@@ -13045,7 +13265,7 @@ function handleMonitorTransfer(fileId, jsonStream) {
|
|
|
13045
13265
|
dataJson.timeRemainingSeconds,
|
|
13046
13266
|
dataJson.status
|
|
13047
13267
|
);
|
|
13048
|
-
if (
|
|
13268
|
+
if (TERMINAL_STATUSES.includes(dataJson.status)) {
|
|
13049
13269
|
console.log("\nTransfer reached terminal state:", dataJson.status);
|
|
13050
13270
|
process.exit(0);
|
|
13051
13271
|
}
|
|
@@ -13137,16 +13357,16 @@ async function handleAutoInstallMcp(isJson) {
|
|
|
13137
13357
|
try {
|
|
13138
13358
|
let configPath = "";
|
|
13139
13359
|
if (process.platform === "win32") {
|
|
13140
|
-
configPath =
|
|
13360
|
+
configPath = path8.join(process.env.APPDATA || path8.join(os5.homedir(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
|
13141
13361
|
} else if (process.platform === "darwin") {
|
|
13142
|
-
configPath =
|
|
13362
|
+
configPath = path8.join(os5.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
13143
13363
|
} else {
|
|
13144
|
-
configPath =
|
|
13364
|
+
configPath = path8.join(os5.homedir(), ".config", "Claude", "claude_desktop_config.json");
|
|
13145
13365
|
}
|
|
13146
13366
|
let config = { mcpServers: {} };
|
|
13147
|
-
if (
|
|
13367
|
+
if (fs9.existsSync(configPath)) {
|
|
13148
13368
|
try {
|
|
13149
|
-
config = JSON.parse(
|
|
13369
|
+
config = JSON.parse(fs9.readFileSync(configPath, "utf8"));
|
|
13150
13370
|
} catch (err) {
|
|
13151
13371
|
throw new Error(`Failed to parse Claude Desktop config at ${configPath}: ${err.message}`);
|
|
13152
13372
|
}
|
|
@@ -13155,7 +13375,7 @@ async function handleAutoInstallMcp(isJson) {
|
|
|
13155
13375
|
config.mcpServers = {};
|
|
13156
13376
|
}
|
|
13157
13377
|
const execPath = process.execPath || "";
|
|
13158
|
-
const execBase =
|
|
13378
|
+
const execBase = path8.basename(execPath).toLowerCase();
|
|
13159
13379
|
const isCompiledBinary = execBase === "srift" || execBase === "srift.exe" || typeof process.isBun === "boolean" && process.isBun || process.versions && process.versions.bun !== void 0;
|
|
13160
13380
|
let command = "";
|
|
13161
13381
|
let mcpArgs = [];
|
|
@@ -13165,16 +13385,16 @@ async function handleAutoInstallMcp(isJson) {
|
|
|
13165
13385
|
} else {
|
|
13166
13386
|
command = "node";
|
|
13167
13387
|
const __filename2 = fileURLToPath(import.meta.url);
|
|
13168
|
-
const __dirname2 =
|
|
13169
|
-
const scriptPath =
|
|
13388
|
+
const __dirname2 = path8.dirname(__filename2);
|
|
13389
|
+
const scriptPath = path8.resolve(__dirname2, "index.ts").replace(/\\/g, "/");
|
|
13170
13390
|
mcpArgs = ["--experimental-strip-types", scriptPath, "mcp"];
|
|
13171
13391
|
}
|
|
13172
13392
|
config.mcpServers.srift = {
|
|
13173
13393
|
command,
|
|
13174
13394
|
args: mcpArgs
|
|
13175
13395
|
};
|
|
13176
|
-
|
|
13177
|
-
|
|
13396
|
+
fs9.mkdirSync(path8.dirname(configPath), { recursive: true });
|
|
13397
|
+
fs9.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
13178
13398
|
if (isJson) {
|
|
13179
13399
|
console.log(JSON.stringify({ success: true, configPath }));
|
|
13180
13400
|
} else {
|
|
@@ -13192,13 +13412,13 @@ IMPORTANT: Please restart Claude Desktop for the changes to take effect.`);
|
|
|
13192
13412
|
}
|
|
13193
13413
|
async function handleBootstrap(targetDirArg, opts = {}) {
|
|
13194
13414
|
try {
|
|
13195
|
-
const targetDir = targetDirArg ?
|
|
13415
|
+
const targetDir = targetDirArg ? path8.resolve(targetDirArg) : process.cwd();
|
|
13196
13416
|
const createCursorrules = opts.cursorrules || !opts.cursorrules && !opts.agents;
|
|
13197
13417
|
const createAgents = opts.agents || !opts.cursorrules && !opts.agents;
|
|
13198
13418
|
console.log(`[SRIFT] Bootstrapping AI Agent instructions in: ${targetDir}`);
|
|
13199
|
-
|
|
13419
|
+
fs9.mkdirSync(targetDir, { recursive: true });
|
|
13200
13420
|
if (createCursorrules) {
|
|
13201
|
-
const cursorrulesPath =
|
|
13421
|
+
const cursorrulesPath = path8.join(targetDir, ".cursorrules");
|
|
13202
13422
|
console.log(` Writing .cursorrules...`);
|
|
13203
13423
|
let content = DEFAULT_CURSORRULES;
|
|
13204
13424
|
try {
|
|
@@ -13206,11 +13426,11 @@ async function handleBootstrap(targetDirArg, opts = {}) {
|
|
|
13206
13426
|
} catch (err) {
|
|
13207
13427
|
console.log(` (Note: failed to fetch latest .cursorrules online: ${err.message}. Using offline template.)`);
|
|
13208
13428
|
}
|
|
13209
|
-
|
|
13429
|
+
fs9.writeFileSync(cursorrulesPath, content, "utf8");
|
|
13210
13430
|
console.log(` \u2705 .cursorrules written to ${cursorrulesPath}`);
|
|
13211
13431
|
}
|
|
13212
13432
|
if (createAgents) {
|
|
13213
|
-
const agentsPath =
|
|
13433
|
+
const agentsPath = path8.join(targetDir, "AGENTS.md");
|
|
13214
13434
|
console.log(` Writing AGENTS.md...`);
|
|
13215
13435
|
let content = DEFAULT_AGENTS_MD;
|
|
13216
13436
|
try {
|
|
@@ -13218,7 +13438,7 @@ async function handleBootstrap(targetDirArg, opts = {}) {
|
|
|
13218
13438
|
} catch (err) {
|
|
13219
13439
|
console.log(` (Note: failed to fetch latest AGENTS.md online: ${err.message}. Using offline template.)`);
|
|
13220
13440
|
}
|
|
13221
|
-
|
|
13441
|
+
fs9.writeFileSync(agentsPath, content, "utf8");
|
|
13222
13442
|
console.log(` \u2705 AGENTS.md written to ${agentsPath}`);
|
|
13223
13443
|
}
|
|
13224
13444
|
console.log(`
|
|
@@ -13290,10 +13510,10 @@ init_get();
|
|
|
13290
13510
|
init_pack();
|
|
13291
13511
|
init_history();
|
|
13292
13512
|
var __filename = fileURLToPath2(import.meta.url);
|
|
13293
|
-
var __dirname =
|
|
13513
|
+
var __dirname = path14.dirname(__filename);
|
|
13294
13514
|
var DAEMON_PORT3 = parseInt(process.env.SRIFT_DAEMON_PORT || "3822", 10);
|
|
13295
13515
|
var DAEMON_URL3 = `http://127.0.0.1:${DAEMON_PORT3}`;
|
|
13296
|
-
var CLI_VERSION = "4.
|
|
13516
|
+
var CLI_VERSION = "4.1.0";
|
|
13297
13517
|
function parseDuration(s) {
|
|
13298
13518
|
if (!s) return 0;
|
|
13299
13519
|
const m = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/i.exec(s.trim());
|
|
@@ -13358,36 +13578,36 @@ function flagValues(args, name) {
|
|
|
13358
13578
|
});
|
|
13359
13579
|
return out;
|
|
13360
13580
|
}
|
|
13361
|
-
var OUTBOX_DIR =
|
|
13581
|
+
var OUTBOX_DIR = path14.join(os8.homedir(), ".srift", "outbox");
|
|
13362
13582
|
function outboxPath(name) {
|
|
13363
|
-
|
|
13583
|
+
fs16.mkdirSync(OUTBOX_DIR, { recursive: true, mode: 448 });
|
|
13364
13584
|
try {
|
|
13365
13585
|
const cutoff = Date.now() - 30 * 864e5;
|
|
13366
|
-
for (const f of
|
|
13367
|
-
const p =
|
|
13586
|
+
for (const f of fs16.readdirSync(OUTBOX_DIR)) {
|
|
13587
|
+
const p = path14.join(OUTBOX_DIR, f);
|
|
13368
13588
|
try {
|
|
13369
|
-
if (
|
|
13589
|
+
if (fs16.statSync(p).mtimeMs < cutoff) fs16.rmSync(p, { recursive: true, force: true });
|
|
13370
13590
|
} catch {
|
|
13371
13591
|
}
|
|
13372
13592
|
}
|
|
13373
13593
|
} catch {
|
|
13374
13594
|
}
|
|
13375
|
-
const dir =
|
|
13376
|
-
|
|
13377
|
-
return
|
|
13595
|
+
const dir = path14.join(OUTBOX_DIR, `${Date.now()}-${crypto8.randomBytes(4).toString("hex")}`);
|
|
13596
|
+
fs16.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
13597
|
+
return path14.join(dir, name);
|
|
13378
13598
|
}
|
|
13379
13599
|
async function stdinToFile(dest) {
|
|
13380
13600
|
if (process.stdin.isTTY) throw new Error("Nothing on stdin. Pipe data in, e.g.: tar cz dir | srift quick-share - --filename dir.tgz");
|
|
13381
|
-
await pipeline2(process.stdin,
|
|
13382
|
-
return
|
|
13601
|
+
await pipeline2(process.stdin, fs16.createWriteStream(dest, { mode: 384 }));
|
|
13602
|
+
return fs16.statSync(dest).size;
|
|
13383
13603
|
}
|
|
13384
13604
|
var SRIFT_BASE_DL_URL = "https://srift.app/dl";
|
|
13385
|
-
var CONFIG_DIR =
|
|
13386
|
-
var CONFIG_FILE =
|
|
13605
|
+
var CONFIG_DIR = path14.join(os8.homedir(), ".srift");
|
|
13606
|
+
var CONFIG_FILE = path14.join(CONFIG_DIR, "config.json");
|
|
13387
13607
|
function readConfig() {
|
|
13388
13608
|
try {
|
|
13389
|
-
if (
|
|
13390
|
-
return JSON.parse(
|
|
13609
|
+
if (fs16.existsSync(CONFIG_FILE)) {
|
|
13610
|
+
return JSON.parse(fs16.readFileSync(CONFIG_FILE, "utf8"));
|
|
13391
13611
|
}
|
|
13392
13612
|
} catch {
|
|
13393
13613
|
}
|
|
@@ -13395,8 +13615,8 @@ function readConfig() {
|
|
|
13395
13615
|
}
|
|
13396
13616
|
function writeConfig(config) {
|
|
13397
13617
|
try {
|
|
13398
|
-
|
|
13399
|
-
|
|
13618
|
+
fs16.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
13619
|
+
fs16.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n");
|
|
13400
13620
|
} catch {
|
|
13401
13621
|
}
|
|
13402
13622
|
}
|
|
@@ -13507,9 +13727,9 @@ async function handleVersion(isJson) {
|
|
|
13507
13727
|
}
|
|
13508
13728
|
async function handleSelfUpdate(isJson) {
|
|
13509
13729
|
const isBinary = !__filename.endsWith(".ts") && !__filename.endsWith(".js");
|
|
13510
|
-
const installDir =
|
|
13730
|
+
const installDir = path14.join(os8.homedir(), ".srift", "bin");
|
|
13511
13731
|
const binName = process.platform === "win32" ? "srift.exe" : "srift";
|
|
13512
|
-
const binPath =
|
|
13732
|
+
const binPath = path14.join(installDir, binName);
|
|
13513
13733
|
console.log(`[srift] Checking for updates...`);
|
|
13514
13734
|
let data;
|
|
13515
13735
|
try {
|
|
@@ -13567,15 +13787,15 @@ async function handleSelfUpdate(isJson) {
|
|
|
13567
13787
|
const tmpPath = binPath + ".new";
|
|
13568
13788
|
const stampPath = binPath + ".new.stamp";
|
|
13569
13789
|
try {
|
|
13570
|
-
if (
|
|
13571
|
-
const prevUrl =
|
|
13572
|
-
if (prevUrl !== binaryUrl &&
|
|
13573
|
-
|
|
13790
|
+
if (fs16.existsSync(stampPath)) {
|
|
13791
|
+
const prevUrl = fs16.readFileSync(stampPath, "utf8").trim();
|
|
13792
|
+
if (prevUrl !== binaryUrl && fs16.existsSync(tmpPath)) {
|
|
13793
|
+
fs16.unlinkSync(tmpPath);
|
|
13574
13794
|
}
|
|
13575
|
-
} else if (
|
|
13576
|
-
|
|
13795
|
+
} else if (fs16.existsSync(tmpPath)) {
|
|
13796
|
+
fs16.unlinkSync(tmpPath);
|
|
13577
13797
|
}
|
|
13578
|
-
|
|
13798
|
+
fs16.writeFileSync(stampPath, binaryUrl);
|
|
13579
13799
|
} catch {
|
|
13580
13800
|
}
|
|
13581
13801
|
console.log(`[srift] Downloading ${binaryUrl} ...`);
|
|
@@ -13587,7 +13807,7 @@ async function handleSelfUpdate(isJson) {
|
|
|
13587
13807
|
process.exit(1);
|
|
13588
13808
|
} finally {
|
|
13589
13809
|
try {
|
|
13590
|
-
if (
|
|
13810
|
+
if (fs16.existsSync(stampPath)) fs16.unlinkSync(stampPath);
|
|
13591
13811
|
} catch {
|
|
13592
13812
|
}
|
|
13593
13813
|
}
|
|
@@ -13597,10 +13817,10 @@ async function handleSelfUpdate(isJson) {
|
|
|
13597
13817
|
const line = lines.find((l) => l.trim().endsWith(binName));
|
|
13598
13818
|
if (line) {
|
|
13599
13819
|
const expected = line.trim().split(/\s+/)[0].toLowerCase();
|
|
13600
|
-
const actual = crypto8.createHash("sha256").update(
|
|
13820
|
+
const actual = crypto8.createHash("sha256").update(fs16.readFileSync(tmpPath)).digest("hex");
|
|
13601
13821
|
if (actual !== expected) {
|
|
13602
13822
|
try {
|
|
13603
|
-
|
|
13823
|
+
fs16.unlinkSync(tmpPath);
|
|
13604
13824
|
} catch {
|
|
13605
13825
|
}
|
|
13606
13826
|
console.error(`[srift] Checksum mismatch! Aborting update.`);
|
|
@@ -13617,23 +13837,23 @@ async function handleSelfUpdate(isJson) {
|
|
|
13617
13837
|
console.log(`[srift] Could not download checksum (${err?.message || err}). Continuing without verification.`);
|
|
13618
13838
|
}
|
|
13619
13839
|
try {
|
|
13620
|
-
|
|
13840
|
+
fs16.chmodSync(tmpPath, 493);
|
|
13621
13841
|
} catch {
|
|
13622
13842
|
}
|
|
13623
13843
|
const bakPath = binPath + ".bak";
|
|
13624
13844
|
try {
|
|
13625
|
-
if (
|
|
13626
|
-
if (
|
|
13627
|
-
|
|
13845
|
+
if (fs16.existsSync(binPath)) {
|
|
13846
|
+
if (fs16.existsSync(bakPath)) fs16.unlinkSync(bakPath);
|
|
13847
|
+
fs16.renameSync(binPath, bakPath);
|
|
13628
13848
|
}
|
|
13629
|
-
|
|
13630
|
-
if (
|
|
13849
|
+
fs16.renameSync(tmpPath, binPath);
|
|
13850
|
+
if (fs16.existsSync(bakPath)) {
|
|
13631
13851
|
try {
|
|
13632
|
-
|
|
13852
|
+
fs16.unlinkSync(bakPath);
|
|
13633
13853
|
} catch {
|
|
13634
13854
|
}
|
|
13635
13855
|
}
|
|
13636
|
-
const others = findAllSriftBinaries().filter((p) =>
|
|
13856
|
+
const others = findAllSriftBinaries().filter((p) => path14.resolve(p) !== path14.resolve(binPath));
|
|
13637
13857
|
if (others.length) {
|
|
13638
13858
|
console.log(`[srift] Cleaning ${others.length} stale srift binar${others.length === 1 ? "y" : "ies"} elsewhere on the system...`);
|
|
13639
13859
|
for (const o of others) {
|
|
@@ -13645,7 +13865,7 @@ async function handleSelfUpdate(isJson) {
|
|
|
13645
13865
|
purgeSriftFromPath();
|
|
13646
13866
|
if (process.platform === "win32") {
|
|
13647
13867
|
try {
|
|
13648
|
-
const installDir2 =
|
|
13868
|
+
const installDir2 = path14.dirname(binPath);
|
|
13649
13869
|
const psCmd = [
|
|
13650
13870
|
"$p=[System.Environment]::GetEnvironmentVariable('Path','User')",
|
|
13651
13871
|
"if(-not $p){$p=''}",
|
|
@@ -13664,9 +13884,9 @@ async function handleSelfUpdate(isJson) {
|
|
|
13664
13884
|
try {
|
|
13665
13885
|
const winBin = binPath.replace(/\//g, "\\");
|
|
13666
13886
|
const winTmp = tmpPath.replace(/\//g, "\\");
|
|
13667
|
-
const batPath =
|
|
13887
|
+
const batPath = path14.join(os8.tmpdir(), `srift-update-${process.pid}-${Date.now()}.bat`);
|
|
13668
13888
|
const batBody = '@echo off\r\ntimeout /t 2 /nobreak >nul 2>&1\r\n:swap\r\ndel /f /q "' + winBin + '" >nul 2>&1\r\nif exist "' + winBin + '" (\r\n timeout /t 1 /nobreak >nul 2>&1\r\n goto :swap\r\n)\r\nmove /y "' + winTmp + '" "' + winBin + '" >nul 2>&1\r\ndel /f /q "%~f0" >nul 2>&1\r\n';
|
|
13669
|
-
|
|
13889
|
+
fs16.writeFileSync(batPath, batBody, "utf8");
|
|
13670
13890
|
spawn4("cmd.exe", ["/C", batPath], {
|
|
13671
13891
|
detached: true,
|
|
13672
13892
|
stdio: "ignore",
|
|
@@ -13712,7 +13932,7 @@ function _downloadAttempt(url, dest, resumeFrom, showProgress, socketIdleMs) {
|
|
|
13712
13932
|
let received = resumeFrom;
|
|
13713
13933
|
let lastPrint = 0;
|
|
13714
13934
|
let lastDataAt = Date.now();
|
|
13715
|
-
const file2 =
|
|
13935
|
+
const file2 = fs16.createWriteStream(dest, { flags: resumeFrom > 0 ? "a" : "w" });
|
|
13716
13936
|
const stallTimer = setInterval(() => {
|
|
13717
13937
|
if (Date.now() - lastDataAt > socketIdleMs) {
|
|
13718
13938
|
clearInterval(stallTimer);
|
|
@@ -13754,8 +13974,8 @@ function _downloadAttempt(url, dest, resumeFrom, showProgress, socketIdleMs) {
|
|
|
13754
13974
|
}
|
|
13755
13975
|
function _findCurl() {
|
|
13756
13976
|
if (process.platform === "win32") {
|
|
13757
|
-
const win =
|
|
13758
|
-
if (
|
|
13977
|
+
const win = path14.join(process.env.WINDIR || "C:\\Windows", "System32", "curl.exe");
|
|
13978
|
+
if (fs16.existsSync(win)) return win;
|
|
13759
13979
|
}
|
|
13760
13980
|
try {
|
|
13761
13981
|
const out = execSync(process.platform === "win32" ? "where curl.exe" : "which curl", {
|
|
@@ -13764,14 +13984,14 @@ function _findCurl() {
|
|
|
13764
13984
|
}).trim().split("\n").map((s) => s.trim()).filter(Boolean);
|
|
13765
13985
|
for (const p of out) {
|
|
13766
13986
|
if (/powershell/i.test(p)) continue;
|
|
13767
|
-
if (
|
|
13987
|
+
if (fs16.existsSync(p)) return p;
|
|
13768
13988
|
}
|
|
13769
13989
|
} catch {
|
|
13770
13990
|
}
|
|
13771
13991
|
return null;
|
|
13772
13992
|
}
|
|
13773
13993
|
async function _runCurl(curlPath, url, dest, showProgress) {
|
|
13774
|
-
|
|
13994
|
+
fs16.mkdirSync(path14.dirname(dest), { recursive: true });
|
|
13775
13995
|
const ua = `srift-cli/${CLI_VERSION} (${process.platform}; ${process.arch})`;
|
|
13776
13996
|
const args = [
|
|
13777
13997
|
"-fL",
|
|
@@ -13808,7 +14028,7 @@ async function _runCurl(curlPath, url, dest, showProgress) {
|
|
|
13808
14028
|
});
|
|
13809
14029
|
}
|
|
13810
14030
|
async function downloadFile(url, dest) {
|
|
13811
|
-
|
|
14031
|
+
fs16.mkdirSync(path14.dirname(dest), { recursive: true });
|
|
13812
14032
|
const curlPath = _findCurl();
|
|
13813
14033
|
if (curlPath) {
|
|
13814
14034
|
console.log(`[srift] Downloading via curl (${curlPath}) \u2014 handles 5xx + connection drops natively.`);
|
|
@@ -13819,7 +14039,7 @@ async function downloadFile(url, dest) {
|
|
|
13819
14039
|
/*showProgress*/
|
|
13820
14040
|
true
|
|
13821
14041
|
);
|
|
13822
|
-
if (ok &&
|
|
14042
|
+
if (ok && fs16.existsSync(dest) && fs16.statSync(dest).size > 1e6) {
|
|
13823
14043
|
console.log("");
|
|
13824
14044
|
return;
|
|
13825
14045
|
}
|
|
@@ -13830,7 +14050,7 @@ async function downloadFile(url, dest) {
|
|
|
13830
14050
|
const socketIdleMs = 3e4;
|
|
13831
14051
|
let lastErr = null;
|
|
13832
14052
|
for (let attempt2 = 1; attempt2 <= maxAttempts; attempt2++) {
|
|
13833
|
-
const resumeFrom =
|
|
14053
|
+
const resumeFrom = fs16.existsSync(dest) ? fs16.statSync(dest).size : 0;
|
|
13834
14054
|
try {
|
|
13835
14055
|
await _downloadAttempt(
|
|
13836
14056
|
url,
|
|
@@ -13843,7 +14063,7 @@ async function downloadFile(url, dest) {
|
|
|
13843
14063
|
return;
|
|
13844
14064
|
} catch (err) {
|
|
13845
14065
|
lastErr = err;
|
|
13846
|
-
const partial =
|
|
14066
|
+
const partial = fs16.existsSync(dest) ? fs16.statSync(dest).size : 0;
|
|
13847
14067
|
const partialMb = (partial / 1024 / 1024).toFixed(1);
|
|
13848
14068
|
if (attempt2 >= maxAttempts) break;
|
|
13849
14069
|
const wait = Math.min(2e3 * attempt2, 8e3);
|
|
@@ -14053,7 +14273,7 @@ async function ensureDaemon() {
|
|
|
14053
14273
|
void isMcp;
|
|
14054
14274
|
logFn("[SRIFT] Starting background transfer daemon...");
|
|
14055
14275
|
const isBunBinary = !!(process.versions && process.versions.bun) || typeof process.isBun === "boolean" && process.isBun;
|
|
14056
|
-
const execBase =
|
|
14276
|
+
const execBase = path14.basename(process.execPath || "").toLowerCase();
|
|
14057
14277
|
const isCompiledBinary = isBunBinary || execBase === "srift" || execBase === "srift.exe";
|
|
14058
14278
|
let execCmd;
|
|
14059
14279
|
let spawnArgs;
|
|
@@ -14063,7 +14283,7 @@ async function ensureDaemon() {
|
|
|
14063
14283
|
} else {
|
|
14064
14284
|
const isJs = __filename.endsWith(".js");
|
|
14065
14285
|
const daemonFile = isJs ? "daemon.js" : "daemon.ts";
|
|
14066
|
-
const daemonPath =
|
|
14286
|
+
const daemonPath = path14.join(__dirname, daemonFile);
|
|
14067
14287
|
execCmd = process.execPath;
|
|
14068
14288
|
spawnArgs = isJs ? [daemonPath] : ["--experimental-strip-types", daemonPath];
|
|
14069
14289
|
}
|
|
@@ -14319,21 +14539,21 @@ function findAllSriftBinaries() {
|
|
|
14319
14539
|
const add = (p) => {
|
|
14320
14540
|
if (!p) return;
|
|
14321
14541
|
try {
|
|
14322
|
-
const abs =
|
|
14323
|
-
if (
|
|
14542
|
+
const abs = path14.resolve(p);
|
|
14543
|
+
if (fs16.existsSync(abs) && fs16.statSync(abs).isFile()) found.add(abs);
|
|
14324
14544
|
} catch {
|
|
14325
14545
|
}
|
|
14326
14546
|
};
|
|
14327
|
-
const execBase =
|
|
14547
|
+
const execBase = path14.basename(process.execPath || "").toLowerCase();
|
|
14328
14548
|
if (execBase === "srift" || execBase === "srift.exe") add(process.execPath);
|
|
14329
|
-
add(
|
|
14549
|
+
add(path14.join(os8.homedir(), ".srift", "bin", binName));
|
|
14330
14550
|
try {
|
|
14331
14551
|
const cmd = process.platform === "win32" ? "where srift" : "which -a srift";
|
|
14332
14552
|
const out = execSync(cmd, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
14333
14553
|
for (const line of out.split("\n").map((s) => s.trim()).filter(Boolean)) add(line);
|
|
14334
14554
|
} catch {
|
|
14335
14555
|
}
|
|
14336
|
-
const pathDirs = (process.env.PATH || "").split(
|
|
14556
|
+
const pathDirs = (process.env.PATH || "").split(path14.delimiter).filter(Boolean);
|
|
14337
14557
|
if (process.platform === "win32") {
|
|
14338
14558
|
try {
|
|
14339
14559
|
const userPath = execSync(
|
|
@@ -14346,7 +14566,7 @@ function findAllSriftBinaries() {
|
|
|
14346
14566
|
} catch {
|
|
14347
14567
|
}
|
|
14348
14568
|
}
|
|
14349
|
-
for (const dir of pathDirs) add(
|
|
14569
|
+
for (const dir of pathDirs) add(path14.join(dir, binName));
|
|
14350
14570
|
return Array.from(found);
|
|
14351
14571
|
}
|
|
14352
14572
|
function purgeSriftFromPath() {
|
|
@@ -14369,19 +14589,19 @@ function purgeSriftFromPath() {
|
|
|
14369
14589
|
return;
|
|
14370
14590
|
}
|
|
14371
14591
|
const rcFiles = [
|
|
14372
|
-
|
|
14373
|
-
|
|
14374
|
-
|
|
14375
|
-
|
|
14376
|
-
|
|
14592
|
+
path14.join(os8.homedir(), ".bashrc"),
|
|
14593
|
+
path14.join(os8.homedir(), ".zshrc"),
|
|
14594
|
+
path14.join(os8.homedir(), ".profile"),
|
|
14595
|
+
path14.join(os8.homedir(), ".bash_profile"),
|
|
14596
|
+
path14.join(os8.homedir(), ".config", "fish", "config.fish")
|
|
14377
14597
|
];
|
|
14378
14598
|
for (const rc of rcFiles) {
|
|
14379
|
-
if (!
|
|
14599
|
+
if (!fs16.existsSync(rc)) continue;
|
|
14380
14600
|
try {
|
|
14381
|
-
const original =
|
|
14601
|
+
const original = fs16.readFileSync(rc, "utf8");
|
|
14382
14602
|
const filtered = original.split("\n").filter((l) => !/\.srift[\/\\]bin/.test(l) && l.trim() !== "# SRIFT" && !l.includes("fish_add_path ~/.srift")).join("\n");
|
|
14383
14603
|
if (filtered !== original) {
|
|
14384
|
-
|
|
14604
|
+
fs16.writeFileSync(rc, filtered);
|
|
14385
14605
|
console.log(`[srift] Cleaned srift PATH entry from: ${rc}`);
|
|
14386
14606
|
}
|
|
14387
14607
|
} catch {
|
|
@@ -14391,9 +14611,9 @@ function purgeSriftFromPath() {
|
|
|
14391
14611
|
function scheduleWindowsDelete(binaryPath) {
|
|
14392
14612
|
try {
|
|
14393
14613
|
const winBin = binaryPath.replace(/\//g, "\\");
|
|
14394
|
-
const batPath =
|
|
14614
|
+
const batPath = path14.join(os8.tmpdir(), `srift-uninstall-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.bat`);
|
|
14395
14615
|
const batBody = '@echo off\r\ntimeout /t 2 /nobreak >nul 2>&1\r\nif not exist "' + winBin + '" goto :selfDelete\r\n:retry\r\ndel /f /q "' + winBin + '" >nul 2>&1\r\nif exist "' + winBin + '" (\r\n timeout /t 1 /nobreak >nul 2>&1\r\n goto :retry\r\n)\r\n:selfDelete\r\ndel /f /q "%~f0" >nul 2>&1\r\n';
|
|
14396
|
-
|
|
14616
|
+
fs16.writeFileSync(batPath, batBody, "utf8");
|
|
14397
14617
|
spawn4("cmd.exe", ["/C", batPath], {
|
|
14398
14618
|
detached: true,
|
|
14399
14619
|
stdio: "ignore",
|
|
@@ -14407,13 +14627,13 @@ function scheduleWindowsDelete(binaryPath) {
|
|
|
14407
14627
|
}
|
|
14408
14628
|
function removeBinary(binaryPath) {
|
|
14409
14629
|
try {
|
|
14410
|
-
|
|
14630
|
+
fs16.unlinkSync(binaryPath);
|
|
14411
14631
|
return { ok: true, deferred: false };
|
|
14412
14632
|
} catch (e) {
|
|
14413
14633
|
if (process.platform === "win32" && (e?.code === "EPERM" || e?.code === "EBUSY" || e?.code === "EACCES" || e?.code === "ENOTEMPTY")) {
|
|
14414
14634
|
try {
|
|
14415
14635
|
const renamed = `${binaryPath}.old-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
14416
|
-
|
|
14636
|
+
fs16.renameSync(binaryPath, renamed);
|
|
14417
14637
|
scheduleWindowsDelete(renamed);
|
|
14418
14638
|
return { ok: true, deferred: true };
|
|
14419
14639
|
} catch {
|
|
@@ -14461,13 +14681,13 @@ async function handleUninstall(purge) {
|
|
|
14461
14681
|
console.error(`[srift] Could not remove ${failed.length}:`);
|
|
14462
14682
|
failed.forEach((f) => console.error(` ${f}`));
|
|
14463
14683
|
}
|
|
14464
|
-
const standardInstallDir =
|
|
14465
|
-
if (
|
|
14684
|
+
const standardInstallDir = path14.join(os8.homedir(), ".srift", "bin");
|
|
14685
|
+
if (fs16.existsSync(standardInstallDir)) {
|
|
14466
14686
|
try {
|
|
14467
|
-
for (const f of
|
|
14468
|
-
const fp =
|
|
14687
|
+
for (const f of fs16.readdirSync(standardInstallDir)) {
|
|
14688
|
+
const fp = path14.join(standardInstallDir, f);
|
|
14469
14689
|
try {
|
|
14470
|
-
|
|
14690
|
+
fs16.unlinkSync(fp);
|
|
14471
14691
|
} catch {
|
|
14472
14692
|
}
|
|
14473
14693
|
}
|
|
@@ -14475,18 +14695,18 @@ async function handleUninstall(purge) {
|
|
|
14475
14695
|
}
|
|
14476
14696
|
}
|
|
14477
14697
|
if (purge) {
|
|
14478
|
-
const sriftDir =
|
|
14479
|
-
if (
|
|
14698
|
+
const sriftDir = path14.join(os8.homedir(), ".srift");
|
|
14699
|
+
if (fs16.existsSync(sriftDir)) {
|
|
14480
14700
|
try {
|
|
14481
|
-
|
|
14701
|
+
fs16.rmSync(sriftDir, { recursive: true, force: true });
|
|
14482
14702
|
console.log(`[srift] Purged directory: ${sriftDir}`);
|
|
14483
14703
|
} catch (e) {
|
|
14484
14704
|
if (process.platform === "win32") {
|
|
14485
|
-
const batPath =
|
|
14705
|
+
const batPath = path14.join(os8.tmpdir(), `srift-purge-${process.pid}-${Date.now()}.bat`);
|
|
14486
14706
|
const winDir = sriftDir.replace(/\//g, "\\");
|
|
14487
14707
|
const batBody = '@echo off\r\ntimeout /t 3 /nobreak >nul 2>&1\r\n:retry\r\nrmdir /s /q "' + winDir + '" >nul 2>&1\r\nif exist "' + winDir + '" (\r\n timeout /t 1 /nobreak >nul 2>&1\r\n goto :retry\r\n)\r\ndel /f /q "%~f0" >nul 2>&1\r\n';
|
|
14488
14708
|
try {
|
|
14489
|
-
|
|
14709
|
+
fs16.writeFileSync(batPath, batBody, "utf8");
|
|
14490
14710
|
spawn4("cmd.exe", ["/C", batPath], { detached: true, stdio: "ignore", windowsHide: true, shell: false }).unref();
|
|
14491
14711
|
console.log(`[srift] Scheduled purge of ${sriftDir} (~3s after exit).`);
|
|
14492
14712
|
} catch (ee) {
|
|
@@ -14502,10 +14722,10 @@ async function handleUninstall(purge) {
|
|
|
14502
14722
|
if (process.platform === "win32") {
|
|
14503
14723
|
try {
|
|
14504
14724
|
const tmp = os8.tmpdir();
|
|
14505
|
-
for (const f of
|
|
14725
|
+
for (const f of fs16.readdirSync(tmp)) {
|
|
14506
14726
|
if (/^srift-(uninstall|update|purge)-.*\.bat$/.test(f)) {
|
|
14507
14727
|
try {
|
|
14508
|
-
|
|
14728
|
+
fs16.unlinkSync(path14.join(tmp, f));
|
|
14509
14729
|
} catch {
|
|
14510
14730
|
}
|
|
14511
14731
|
}
|
|
@@ -14554,7 +14774,10 @@ var ALL_COMMANDS = [
|
|
|
14554
14774
|
"status",
|
|
14555
14775
|
"reset",
|
|
14556
14776
|
"logs",
|
|
14557
|
-
"uninstall"
|
|
14777
|
+
"uninstall",
|
|
14778
|
+
"agentnet",
|
|
14779
|
+
"an",
|
|
14780
|
+
"bootstrap"
|
|
14558
14781
|
];
|
|
14559
14782
|
function suggestCommand(input) {
|
|
14560
14783
|
const prefixMatch = ALL_COMMANDS.find((c) => c.startsWith(input) || input.startsWith(c.slice(0, 3)));
|
|
@@ -14684,7 +14907,7 @@ async function main() {
|
|
|
14684
14907
|
}
|
|
14685
14908
|
const jsonStream = args.includes("--json-stream");
|
|
14686
14909
|
await ensureDaemon();
|
|
14687
|
-
handleMonitorTransfer(fileId, jsonStream);
|
|
14910
|
+
await handleMonitorTransfer(fileId, jsonStream);
|
|
14688
14911
|
break;
|
|
14689
14912
|
}
|
|
14690
14913
|
case "approve": {
|
|
@@ -14792,15 +15015,15 @@ async function main() {
|
|
|
14792
15015
|
const n = await stdinToFile(filePath);
|
|
14793
15016
|
if (!isJson) console.error(`[srift] Read ${n} bytes from stdin.`);
|
|
14794
15017
|
} else {
|
|
14795
|
-
const abs =
|
|
14796
|
-
if (!
|
|
14797
|
-
if (
|
|
14798
|
-
const base =
|
|
15018
|
+
const abs = path14.resolve(target);
|
|
15019
|
+
if (!fs16.existsSync(abs)) throw new Error(`File not found: ${abs}`);
|
|
15020
|
+
if (fs16.statSync(abs).isDirectory()) {
|
|
15021
|
+
const base = path14.basename(abs) || "folder";
|
|
14799
15022
|
displayName = displayName || `${base}.tar.gz`;
|
|
14800
15023
|
filePath = outboxPath(displayName.replace(/[\\/]/g, "_"));
|
|
14801
15024
|
const excludes = [...DEFAULT_EXCLUDES, ...flagValues(args, "--exclude")];
|
|
14802
15025
|
const r = await packDirectory(abs, filePath, excludes);
|
|
14803
|
-
if (!isJson) console.error(`[srift] Packed ${r.files} file(s), ${r.bytes} bytes \u2192 ${
|
|
15026
|
+
if (!isJson) console.error(`[srift] Packed ${r.files} file(s), ${r.bytes} bytes \u2192 ${path14.basename(filePath)} (excluded: ${excludes.join(", ")})`);
|
|
14804
15027
|
} else {
|
|
14805
15028
|
filePath = abs;
|
|
14806
15029
|
}
|
|
@@ -14832,17 +15055,17 @@ async function main() {
|
|
|
14832
15055
|
console.error("stdin (-) cannot be combined with other paths.");
|
|
14833
15056
|
process.exit(1);
|
|
14834
15057
|
}
|
|
14835
|
-
const missing = targets.filter((t) => !
|
|
15058
|
+
const missing = targets.filter((t) => !fs16.existsSync(path14.resolve(t)));
|
|
14836
15059
|
if (missing.length === targets.length) {
|
|
14837
|
-
const msg = `File not found: ${missing.map((m) =>
|
|
15060
|
+
const msg = `File not found: ${missing.map((m) => path14.resolve(m)).join(", ")}`;
|
|
14838
15061
|
if (isJson) console.log(JSON.stringify({ success: false, error: msg }));
|
|
14839
15062
|
else console.error(`Error: ${msg}`);
|
|
14840
15063
|
process.exit(1);
|
|
14841
15064
|
}
|
|
14842
|
-
if (missing.length && !isJson) console.error(`[srift] Warning: not found, will be skipped: ${missing.map((m) =>
|
|
15065
|
+
if (missing.length && !isJson) console.error(`[srift] Warning: not found, will be skipped: ${missing.map((m) => path14.resolve(m)).join(", ")}`);
|
|
14843
15066
|
try {
|
|
14844
15067
|
const where = await ensureDaemonOrEmbedded({ force: args.includes("--foreground") });
|
|
14845
|
-
const res = await handleQuickShareMany(targets.map((t) =>
|
|
15068
|
+
const res = await handleQuickShareMany(targets.map((t) => path14.resolve(t)), isJson, {
|
|
14846
15069
|
maxDownloads,
|
|
14847
15070
|
ttlMs,
|
|
14848
15071
|
encrypt: encrypt2,
|
|
@@ -14888,7 +15111,7 @@ async function main() {
|
|
|
14888
15111
|
console.error("Several links cannot all go to stdout; use -o <dir>.");
|
|
14889
15112
|
process.exit(64);
|
|
14890
15113
|
}
|
|
14891
|
-
|
|
15114
|
+
fs16.mkdirSync(outDir, { recursive: true });
|
|
14892
15115
|
const conc = Math.min(16, Math.max(1, parseInt(flagValue(args, "--concurrency") || "4", 10) || 4));
|
|
14893
15116
|
const results = await mapLimit(links, conc, async (l) => {
|
|
14894
15117
|
try {
|
|
@@ -14942,8 +15165,10 @@ async function main() {
|
|
|
14942
15165
|
break;
|
|
14943
15166
|
}
|
|
14944
15167
|
const items = readHistory().reverse();
|
|
15168
|
+
const limitArg = parseInt(flagValue(args, "--limit") || "", 10);
|
|
15169
|
+
const limit = Number.isFinite(limitArg) && limitArg > 0 ? limitArg : null;
|
|
14945
15170
|
if (isJson) {
|
|
14946
|
-
console.log(JSON.stringify({ items }));
|
|
15171
|
+
console.log(JSON.stringify({ items: limit ? items.slice(0, limit) : items }));
|
|
14947
15172
|
break;
|
|
14948
15173
|
}
|
|
14949
15174
|
if (!items.length) {
|
|
@@ -14951,8 +15176,7 @@ async function main() {
|
|
|
14951
15176
|
break;
|
|
14952
15177
|
}
|
|
14953
15178
|
const now = Date.now();
|
|
14954
|
-
const
|
|
14955
|
-
for (const it of items.slice(0, Number.isFinite(limit) && limit > 0 ? limit : 20)) {
|
|
15179
|
+
for (const it of items.slice(0, limit || 20)) {
|
|
14956
15180
|
const state = it.expiresAt && it.expiresAt <= now ? "expired" : it.expiresAt ? `expires ${new Date(it.expiresAt).toLocaleString()}` : "no expiry";
|
|
14957
15181
|
console.log(`${it.at.slice(0, 19).replace("T", " ")} ${it.mode.padEnd(5)} ${it.encrypted ? "e2ee " : "plain"} ${it.fileName} (${state})`);
|
|
14958
15182
|
console.log(` ${it.downloadUrl}`);
|
|
@@ -15181,7 +15405,7 @@ Env vars:
|
|
|
15181
15405
|
function printInstallMcpInstructions() {
|
|
15182
15406
|
const claudeWin = `${process.env.APPDATA || "%APPDATA%"}\\Claude\\claude_desktop_config.json`;
|
|
15183
15407
|
const execPath = process.execPath || "";
|
|
15184
|
-
const execBase =
|
|
15408
|
+
const execBase = path14.basename(execPath).toLowerCase();
|
|
15185
15409
|
const isCompiledBinary = execBase === "srift" || execBase === "srift.exe" || typeof process.isBun === "boolean" && process.isBun || process.versions && process.versions.bun !== void 0;
|
|
15186
15410
|
let cmd;
|
|
15187
15411
|
let argList;
|
|
@@ -15198,7 +15422,7 @@ function printInstallMcpInstructions() {
|
|
|
15198
15422
|
argList = `["mcp"]`;
|
|
15199
15423
|
cursorCmd = `srift mcp`;
|
|
15200
15424
|
} else {
|
|
15201
|
-
const scriptPath =
|
|
15425
|
+
const scriptPath = path14.resolve(__dirname, "index.ts").replace(/\\/g, "/");
|
|
15202
15426
|
cmd = `node`;
|
|
15203
15427
|
argList = `["--experimental-strip-types","${scriptPath}","mcp"]`;
|
|
15204
15428
|
cursorCmd = `node --experimental-strip-types ${scriptPath} mcp`;
|
|
@@ -15232,10 +15456,10 @@ Same command/args as above.
|
|
|
15232
15456
|
|
|
15233
15457
|
# Cloud / browser agents (ChatGPT, Claude.ai, n8n, Zapier, etc.)
|
|
15234
15458
|
# These run off your machine and CANNOT reach 127.0.0.1 \u2014 use the hosted endpoint:
|
|
15235
|
-
Hosted MCP: POST https://srift.app/mcp (no install,
|
|
15459
|
+
Hosted MCP: POST https://srift.app/mcp (no install, 9 session/control tools)
|
|
15236
15460
|
{ "mcpServers": { "srift": { "type": "streamable-http", "url": "https://srift.app/mcp" } } }
|
|
15237
15461
|
|
|
15238
|
-
# Local daemon surfaces (this machine only \u2014
|
|
15462
|
+
# Local daemon surfaces (this machine only \u2014 all 15 tools):
|
|
15239
15463
|
HTTP MCP endpoint: POST http://127.0.0.1:${DAEMON_PORT3}/mcp
|
|
15240
15464
|
OpenAPI spec: GET http://127.0.0.1:${DAEMON_PORT3}/openapi.json
|
|
15241
15465
|
AI Plugin: GET http://127.0.0.1:${DAEMON_PORT3}/.well-known/ai-plugin.json
|