openmates 0.15.0-alpha.97 → 0.15.0-alpha.99
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 +15 -6
- package/dist/{chunk-YRIKOMBJ.js → chunk-3ZTRVFOG.js} +875 -80
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +19 -0
- package/dist/index.js +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -144,14 +144,23 @@ review the spend confirmation before live runs.
|
|
|
144
144
|
### Local source bridge
|
|
145
145
|
|
|
146
146
|
```bash
|
|
147
|
-
openmates remote-access
|
|
148
|
-
openmates remote-access
|
|
149
|
-
openmates remote-access
|
|
147
|
+
openmates remote-access
|
|
148
|
+
openmates remote-access --path ./my-repo --path ../shared
|
|
149
|
+
openmates remote-access --path ./my-repo --json
|
|
150
150
|
```
|
|
151
151
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
The command discovers Git repositories below the current folder by default;
|
|
153
|
+
explicit `--path` values replace that scope. It remains in the foreground,
|
|
154
|
+
reconnects after temporary network loss, and marks sources offline when it
|
|
155
|
+
stops. OpenMates can request bounded directory listings, text search, and
|
|
156
|
+
selected text previews. The bridge is read-only, excludes ignored, protected,
|
|
157
|
+
binary, and out-of-root files, and never uploads a preview unless you explicitly
|
|
158
|
+
choose to upload it.
|
|
159
|
+
|
|
160
|
+
Project keys authenticate end-to-end encryption between trusted clients. The
|
|
161
|
+
backend receives routing metadata and opaque ciphertext, not filesystem paths,
|
|
162
|
+
queries, snippets, file contents, or encryption keys. Local source associations
|
|
163
|
+
are stored with owner-only permissions under `~/.openmates/remote-sources.json`.
|
|
155
164
|
|
|
156
165
|
## Self-Host Server Management
|
|
157
166
|
|
|
@@ -1165,8 +1165,8 @@ function buildSession(onDisk, masterKey, emailEncryptionKey) {
|
|
|
1165
1165
|
var SYNC_CACHE_FILE = "sync_cache.json";
|
|
1166
1166
|
var LOCAL_TEAM_KEYS_FILE = "team_keys.json";
|
|
1167
1167
|
function teamKeyStorageId(hashedEmail, teamId) {
|
|
1168
|
-
const
|
|
1169
|
-
return `${hashedEmail}:team:${
|
|
1168
|
+
const digest2 = createHash3("sha256").update(teamId).digest("hex").slice(0, 32);
|
|
1169
|
+
return `${hashedEmail}:team:${digest2}`;
|
|
1170
1170
|
}
|
|
1171
1171
|
function saveLocalTeamKey(hashedEmail, teamId, teamKeyB64) {
|
|
1172
1172
|
const storageId = teamKeyStorageId(hashedEmail, teamId);
|
|
@@ -1224,8 +1224,8 @@ function pruneLocalTeamArtifacts(hashedEmail, teamIds) {
|
|
|
1224
1224
|
}
|
|
1225
1225
|
function syncCacheFile(teamId) {
|
|
1226
1226
|
if (!teamId) return SYNC_CACHE_FILE;
|
|
1227
|
-
const
|
|
1228
|
-
return `sync_cache.team.${
|
|
1227
|
+
const digest2 = createHash3("sha256").update(teamId).digest("hex").slice(0, 32);
|
|
1228
|
+
return `sync_cache.team.${digest2}.json`;
|
|
1229
1229
|
}
|
|
1230
1230
|
function saveSyncCache(cache, teamId) {
|
|
1231
1231
|
const filePath = join(ensureStateDir(), syncCacheFile(teamId));
|
|
@@ -1567,6 +1567,27 @@ var OpenMatesWsClient = class {
|
|
|
1567
1567
|
this.socket.on("message", onMessage);
|
|
1568
1568
|
return () => this.socket.off("message", onMessage);
|
|
1569
1569
|
}
|
|
1570
|
+
onProjectRemoteAccessRequest(handler) {
|
|
1571
|
+
const onMessage = (rawData) => {
|
|
1572
|
+
try {
|
|
1573
|
+
const parsed = JSON.parse(rawData.toString());
|
|
1574
|
+
if (parsed.type !== "project_remote_access_request") return;
|
|
1575
|
+
const payload = parsed.payload ?? {};
|
|
1576
|
+
if (typeof payload.request_id !== "string" || typeof payload.project_id !== "string" || typeof payload.source_id !== "string" || typeof payload.source_session_id !== "string" || typeof payload.requesting_client_id !== "string" || !["list", "search", "read_text"].includes(String(payload.operation)) || typeof payload.key_epoch !== "number" || typeof payload.encrypted_envelope !== "string") return;
|
|
1577
|
+
void handler(payload);
|
|
1578
|
+
} catch {
|
|
1579
|
+
}
|
|
1580
|
+
};
|
|
1581
|
+
this.socket.on("message", onMessage);
|
|
1582
|
+
return () => this.socket.off("message", onMessage);
|
|
1583
|
+
}
|
|
1584
|
+
waitForClose() {
|
|
1585
|
+
return new Promise((resolve10) => {
|
|
1586
|
+
this.socket.once("close", (code, reason) => {
|
|
1587
|
+
resolve10({ code, reason: reason.toString("utf-8") });
|
|
1588
|
+
});
|
|
1589
|
+
});
|
|
1590
|
+
}
|
|
1570
1591
|
bufferPassiveTaskUpdateJobs(rawData) {
|
|
1571
1592
|
try {
|
|
1572
1593
|
const parsed = JSON.parse(rawData.toString());
|
|
@@ -4843,8 +4864,8 @@ function parseStringArray(value) {
|
|
|
4843
4864
|
function deriveShortId(record) {
|
|
4844
4865
|
const prefix = record.short_id_prefix || DEFAULT_STANDALONE_PREFIX;
|
|
4845
4866
|
const source = record.task_id || `${record.created_at ?? ""}-${record.position ?? ""}`;
|
|
4846
|
-
const
|
|
4847
|
-
return `${prefix}-${parseInt(
|
|
4867
|
+
const digest2 = createHash7("sha256").update(source).digest("hex").slice(0, 4).toUpperCase();
|
|
4868
|
+
return `${prefix}-${parseInt(digest2, 16) % 1e4}`;
|
|
4848
4869
|
}
|
|
4849
4870
|
function nowSeconds() {
|
|
4850
4871
|
return Math.floor(Date.now() / 1e3);
|
|
@@ -5842,8 +5863,8 @@ function parseEmbedContentObject(rawContent) {
|
|
|
5842
5863
|
}
|
|
5843
5864
|
async function encodeEmbedContentObject(content) {
|
|
5844
5865
|
try {
|
|
5845
|
-
const { encode } = await import("@toon-format/toon");
|
|
5846
|
-
return
|
|
5866
|
+
const { encode: encode2 } = await import("@toon-format/toon");
|
|
5867
|
+
return encode2(content);
|
|
5847
5868
|
} catch {
|
|
5848
5869
|
return JSON.stringify(content);
|
|
5849
5870
|
}
|
|
@@ -7271,7 +7292,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
7271
7292
|
session,
|
|
7272
7293
|
{ chatId, requestId: audioEmbed.embed.embedId }
|
|
7273
7294
|
);
|
|
7274
|
-
const
|
|
7295
|
+
const transcript2 = transcription.transcript_corrected ?? transcription.transcript ?? transcription.transcript_original ?? "";
|
|
7275
7296
|
audioEmbed.embed.content = toonEncodeContent({
|
|
7276
7297
|
app_id: "audio",
|
|
7277
7298
|
skill_id: "transcribe",
|
|
@@ -7302,7 +7323,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
7302
7323
|
const audioMarkdown = [
|
|
7303
7324
|
`Audio: ${audioEmbed.displayName}`,
|
|
7304
7325
|
referenceBlock,
|
|
7305
|
-
|
|
7326
|
+
transcript2 ? `Transcript: ${transcript2}` : null
|
|
7306
7327
|
].filter((line) => Boolean(line)).join("\n");
|
|
7307
7328
|
return await this.addIdeaBucketPreparedIdeas({
|
|
7308
7329
|
chatId,
|
|
@@ -7313,12 +7334,12 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
7313
7334
|
preparedEmbeds: [audioEmbed.embed],
|
|
7314
7335
|
ideas: [{
|
|
7315
7336
|
content: audioMarkdown,
|
|
7316
|
-
preview:
|
|
7337
|
+
preview: transcript2 || audioEmbed.displayName,
|
|
7317
7338
|
payload: {
|
|
7318
7339
|
type: "audio",
|
|
7319
7340
|
filename: audioEmbed.displayName,
|
|
7320
7341
|
embed_ref: embedRef,
|
|
7321
|
-
transcript,
|
|
7342
|
+
transcript: transcript2,
|
|
7322
7343
|
transcript_original: transcription.transcript_original,
|
|
7323
7344
|
transcript_corrected: transcription.transcript_corrected,
|
|
7324
7345
|
use_corrected: transcription.use_corrected,
|
|
@@ -10418,6 +10439,14 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
10418
10439
|
}
|
|
10419
10440
|
return response.data.source;
|
|
10420
10441
|
}
|
|
10442
|
+
async openProjectRemoteAccessWebSocket() {
|
|
10443
|
+
const opened = await this.openWsClient({ taskUpdateJobs: false });
|
|
10444
|
+
if (!opened.ownerId) {
|
|
10445
|
+
opened.ws.close();
|
|
10446
|
+
throw new Error("Authenticated user identity unavailable for remote access");
|
|
10447
|
+
}
|
|
10448
|
+
return { ws: opened.ws, ownerId: opened.ownerId };
|
|
10449
|
+
}
|
|
10421
10450
|
async createProjectItem(projectId, input) {
|
|
10422
10451
|
this.requireSession();
|
|
10423
10452
|
const response = await this.http.post(
|
|
@@ -18270,8 +18299,8 @@ function parseStringArray2(value) {
|
|
|
18270
18299
|
}
|
|
18271
18300
|
function deriveShortId2(record) {
|
|
18272
18301
|
const source = record.plan_id || `${record.created_at ?? ""}-${record.updated_at ?? ""}`;
|
|
18273
|
-
const
|
|
18274
|
-
return `${DEFAULT_PLAN_PREFIX}-${
|
|
18302
|
+
const digest2 = createHash10("sha256").update(source).digest("hex").slice(0, 6).toUpperCase();
|
|
18303
|
+
return `${DEFAULT_PLAN_PREFIX}-${digest2}`;
|
|
18275
18304
|
}
|
|
18276
18305
|
function nowSeconds2() {
|
|
18277
18306
|
return Math.floor(Date.now() / 1e3);
|
|
@@ -23201,8 +23230,8 @@ function renderVideoTranscriptFullscreen(c) {
|
|
|
23201
23230
|
if (url) process.stdout.write(`\x1B[2m${url}\x1B[0m
|
|
23202
23231
|
`);
|
|
23203
23232
|
console.log();
|
|
23204
|
-
const
|
|
23205
|
-
if (
|
|
23233
|
+
const transcript2 = str(c.transcript) ?? str(c.text) ?? "";
|
|
23234
|
+
if (transcript2) console.log(transcript2);
|
|
23206
23235
|
else console.log("No transcript available.");
|
|
23207
23236
|
}
|
|
23208
23237
|
async function renderHealthSearchPreview(c, ln, _client) {
|
|
@@ -67303,8 +67332,22 @@ function clamp(value, min, max) {
|
|
|
67303
67332
|
|
|
67304
67333
|
// src/remoteAccess.ts
|
|
67305
67334
|
import { homedir as homedir7 } from "os";
|
|
67306
|
-
import {
|
|
67307
|
-
|
|
67335
|
+
import {
|
|
67336
|
+
chmodSync as chmodSync4,
|
|
67337
|
+
closeSync as closeSync2,
|
|
67338
|
+
constants,
|
|
67339
|
+
existsSync as existsSync10,
|
|
67340
|
+
fstatSync,
|
|
67341
|
+
mkdirSync as mkdirSync6,
|
|
67342
|
+
openSync as openSync2,
|
|
67343
|
+
readFileSync as readFileSync8,
|
|
67344
|
+
readdirSync as readdirSync5,
|
|
67345
|
+
readSync as readSync2,
|
|
67346
|
+
realpathSync,
|
|
67347
|
+
statSync as statSync2,
|
|
67348
|
+
writeFileSync as writeFileSync6
|
|
67349
|
+
} from "fs";
|
|
67350
|
+
import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
|
|
67308
67351
|
import { join as join8, resolve as resolve8, relative as relative2 } from "path";
|
|
67309
67352
|
|
|
67310
67353
|
// src/projectFileRisk.ts
|
|
@@ -67354,9 +67397,492 @@ var BUILT_IN_PATTERNS = [
|
|
|
67354
67397
|
{ reason: "migration_schema_or_ci_file", patterns: MIGRATION_SCHEMA_CI_PATTERNS }
|
|
67355
67398
|
];
|
|
67356
67399
|
var PROJECT_HIGH_RISK_GLOBS = BUILT_IN_PATTERNS.flatMap((group) => group.patterns);
|
|
67400
|
+
function classifyProjectFileRisk(path, userProtectedPatterns = []) {
|
|
67401
|
+
const normalizedPath = normalizePath(path);
|
|
67402
|
+
const reasons = [];
|
|
67403
|
+
for (const group of BUILT_IN_PATTERNS) {
|
|
67404
|
+
if (group.patterns.some((pattern) => matchesPattern2(normalizedPath, pattern))) {
|
|
67405
|
+
reasons.push(group.reason);
|
|
67406
|
+
}
|
|
67407
|
+
}
|
|
67408
|
+
if (userProtectedPatterns.some((pattern) => matchesPattern2(normalizedPath, normalizePath(pattern)))) {
|
|
67409
|
+
reasons.push("user_protected_pattern");
|
|
67410
|
+
}
|
|
67411
|
+
return { isHighRisk: reasons.length > 0, reasons: [...new Set(reasons)] };
|
|
67412
|
+
}
|
|
67413
|
+
function normalizePath(path) {
|
|
67414
|
+
return path.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
67415
|
+
}
|
|
67416
|
+
function matchesPattern2(path, pattern) {
|
|
67417
|
+
const regex = new RegExp(`^${globToRegex(pattern)}$`);
|
|
67418
|
+
return regex.test(path);
|
|
67419
|
+
}
|
|
67420
|
+
function globToRegex(pattern) {
|
|
67421
|
+
let output = "";
|
|
67422
|
+
for (let index = 0; index < pattern.length; index += 1) {
|
|
67423
|
+
const char = pattern[index];
|
|
67424
|
+
const next = pattern[index + 1];
|
|
67425
|
+
if (char === "*" && next === "*") {
|
|
67426
|
+
output += ".*";
|
|
67427
|
+
index += 1;
|
|
67428
|
+
} else if (char === "*") {
|
|
67429
|
+
output += "[^/]*";
|
|
67430
|
+
} else {
|
|
67431
|
+
output += escapeRegex(char ?? "");
|
|
67432
|
+
}
|
|
67433
|
+
}
|
|
67434
|
+
return output;
|
|
67435
|
+
}
|
|
67436
|
+
function escapeRegex(value) {
|
|
67437
|
+
return value.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
67438
|
+
}
|
|
67439
|
+
|
|
67440
|
+
// src/remoteAccessCrypto.ts
|
|
67441
|
+
import { webcrypto as webcrypto6 } from "crypto";
|
|
67442
|
+
import nacl2 from "tweetnacl";
|
|
67443
|
+
var cryptoApi4 = globalThis.crypto ?? webcrypto6;
|
|
67444
|
+
var PROTOCOL_VERSION = 1;
|
|
67445
|
+
var KEY_BYTES = 32;
|
|
67446
|
+
var NONCE_BYTES = 12;
|
|
67447
|
+
var MAX_PAYLOAD_BYTES = 200 * 1024;
|
|
67448
|
+
async function createRemoteAccessHandshake(projectKey, identity, role) {
|
|
67449
|
+
assertProjectKey(projectKey);
|
|
67450
|
+
validateIdentity(identity);
|
|
67451
|
+
const privateKey = cryptoApi4.getRandomValues(new Uint8Array(KEY_BYTES));
|
|
67452
|
+
const publicKey = nacl2.scalarMult.base(privateKey);
|
|
67453
|
+
const publicKeyText = toBase64Url(publicKey);
|
|
67454
|
+
return {
|
|
67455
|
+
privateKey: toBase64Url(privateKey),
|
|
67456
|
+
handshake: {
|
|
67457
|
+
version: PROTOCOL_VERSION,
|
|
67458
|
+
role,
|
|
67459
|
+
publicKey: publicKeyText,
|
|
67460
|
+
authenticationTag: await authenticateHandshake(projectKey, identity, role, publicKeyText)
|
|
67461
|
+
}
|
|
67462
|
+
};
|
|
67463
|
+
}
|
|
67464
|
+
async function deriveRemoteAccessSessionKey(projectKey, identity, localRole, localPrivateKey, localHandshake, remoteHandshake) {
|
|
67465
|
+
assertProjectKey(projectKey);
|
|
67466
|
+
validateIdentity(identity);
|
|
67467
|
+
const remoteRole = localRole === "requester" ? "source" : "requester";
|
|
67468
|
+
if (localHandshake.role !== localRole || remoteHandshake.role !== remoteRole) {
|
|
67469
|
+
throw new Error("handshake authentication failed");
|
|
67470
|
+
}
|
|
67471
|
+
await verifyHandshake(projectKey, identity, localHandshake);
|
|
67472
|
+
await verifyHandshake(projectKey, identity, remoteHandshake);
|
|
67473
|
+
const privateKey = fromBase64Url(localPrivateKey, KEY_BYTES);
|
|
67474
|
+
const remotePublicKey = fromBase64Url(remoteHandshake.publicKey, KEY_BYTES);
|
|
67475
|
+
const sharedSecret = nacl2.scalarMult(privateKey, remotePublicKey);
|
|
67476
|
+
if (sharedSecret.every((value) => value === 0)) throw new Error("handshake shared secret invalid");
|
|
67477
|
+
const requesterPublicKey = localRole === "requester" ? localHandshake.publicKey : remoteHandshake.publicKey;
|
|
67478
|
+
const sourcePublicKey = localRole === "source" ? localHandshake.publicKey : remoteHandshake.publicKey;
|
|
67479
|
+
return hkdf(
|
|
67480
|
+
sharedSecret,
|
|
67481
|
+
new TextEncoder().encode("openmates:project-remote-access:v1"),
|
|
67482
|
+
await digest(transcript(identity, requesterPublicKey, sourcePublicKey))
|
|
67483
|
+
);
|
|
67484
|
+
}
|
|
67485
|
+
async function sealRemoteAccessEnvelope(sessionKey, identity, requestId, direction, plaintext) {
|
|
67486
|
+
assertSessionKey(sessionKey);
|
|
67487
|
+
validateIdentity(identity);
|
|
67488
|
+
if (!requestId || plaintext.length > MAX_PAYLOAD_BYTES) throw new Error("invalid remote-access envelope payload");
|
|
67489
|
+
const nonce = cryptoApi4.getRandomValues(new Uint8Array(NONCE_BYTES));
|
|
67490
|
+
const key = await cryptoApi4.subtle.importKey("raw", arrayBuffer(sessionKey), "AES-GCM", false, ["encrypt"]);
|
|
67491
|
+
const ciphertext = await cryptoApi4.subtle.encrypt(
|
|
67492
|
+
{ name: "AES-GCM", iv: arrayBuffer(nonce), additionalData: arrayBuffer(envelopeAad(identity, requestId, direction)) },
|
|
67493
|
+
key,
|
|
67494
|
+
arrayBuffer(plaintext)
|
|
67495
|
+
);
|
|
67496
|
+
return { version: PROTOCOL_VERSION, nonce: toBase64Url(nonce), ciphertext: toBase64Url(new Uint8Array(ciphertext)) };
|
|
67497
|
+
}
|
|
67498
|
+
async function authenticateHandshake(projectKey, identity, role, publicKey) {
|
|
67499
|
+
const key = await cryptoApi4.subtle.importKey("raw", arrayBuffer(projectKey), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
67500
|
+
const tag = await cryptoApi4.subtle.sign("HMAC", key, arrayBuffer(handshakeAad(identity, role, publicKey)));
|
|
67501
|
+
return toBase64Url(new Uint8Array(tag));
|
|
67502
|
+
}
|
|
67503
|
+
async function verifyHandshake(projectKey, identity, handshake) {
|
|
67504
|
+
if (handshake.version !== PROTOCOL_VERSION) throw new Error("handshake authentication failed");
|
|
67505
|
+
fromBase64Url(handshake.publicKey, KEY_BYTES);
|
|
67506
|
+
const expected = await authenticateHandshake(projectKey, identity, handshake.role, handshake.publicKey);
|
|
67507
|
+
const actualBytes = fromBase64Url(handshake.authenticationTag, KEY_BYTES);
|
|
67508
|
+
const expectedBytes = fromBase64Url(expected, KEY_BYTES);
|
|
67509
|
+
if (!constantTimeEqual(actualBytes, expectedBytes)) throw new Error("handshake authentication failed");
|
|
67510
|
+
}
|
|
67511
|
+
function handshakeAad(identity, role, publicKey) {
|
|
67512
|
+
return encode(["OMRA-HANDSHAKE-1", ...identityValues(identity), role, publicKey]);
|
|
67513
|
+
}
|
|
67514
|
+
function transcript(identity, requesterPublicKey, sourcePublicKey) {
|
|
67515
|
+
return encode(["OMRA-TRANSCRIPT-1", ...identityValues(identity), requesterPublicKey, sourcePublicKey]);
|
|
67516
|
+
}
|
|
67517
|
+
function envelopeAad(identity, requestId, direction) {
|
|
67518
|
+
return encode(["OMRA-ENVELOPE-1", ...identityValues(identity), requestId, direction]);
|
|
67519
|
+
}
|
|
67520
|
+
function identityValues(identity) {
|
|
67521
|
+
return [
|
|
67522
|
+
identity.ownerId,
|
|
67523
|
+
identity.projectId,
|
|
67524
|
+
identity.sourceId,
|
|
67525
|
+
identity.sourceSessionId,
|
|
67526
|
+
identity.requestingClientId,
|
|
67527
|
+
identity.keyEpoch
|
|
67528
|
+
];
|
|
67529
|
+
}
|
|
67530
|
+
function encode(values) {
|
|
67531
|
+
return new TextEncoder().encode(JSON.stringify(values));
|
|
67532
|
+
}
|
|
67533
|
+
async function hkdf(secret, salt, info) {
|
|
67534
|
+
const key = await cryptoApi4.subtle.importKey("raw", arrayBuffer(secret), "HKDF", false, ["deriveBits"]);
|
|
67535
|
+
const bits = await cryptoApi4.subtle.deriveBits(
|
|
67536
|
+
{ name: "HKDF", hash: "SHA-256", salt: arrayBuffer(salt), info: arrayBuffer(info) },
|
|
67537
|
+
key,
|
|
67538
|
+
256
|
|
67539
|
+
);
|
|
67540
|
+
return new Uint8Array(bits);
|
|
67541
|
+
}
|
|
67542
|
+
async function digest(value) {
|
|
67543
|
+
return new Uint8Array(await cryptoApi4.subtle.digest("SHA-256", arrayBuffer(value)));
|
|
67544
|
+
}
|
|
67545
|
+
function validateIdentity(identity) {
|
|
67546
|
+
const strings = [identity.ownerId, identity.projectId, identity.sourceId, identity.sourceSessionId, identity.requestingClientId];
|
|
67547
|
+
if (strings.some((value) => !value || value.length > 128) || !Number.isInteger(identity.keyEpoch) || identity.keyEpoch < 1) {
|
|
67548
|
+
throw new Error("invalid remote-access identity");
|
|
67549
|
+
}
|
|
67550
|
+
}
|
|
67551
|
+
function assertProjectKey(value) {
|
|
67552
|
+
if (value.length !== KEY_BYTES) throw new Error("Project key must be 32 bytes");
|
|
67553
|
+
}
|
|
67554
|
+
function assertSessionKey(value) {
|
|
67555
|
+
if (value.length !== KEY_BYTES) throw new Error("source-session key must be 32 bytes");
|
|
67556
|
+
}
|
|
67557
|
+
function toBase64Url(value) {
|
|
67558
|
+
return Buffer.from(value).toString("base64url");
|
|
67559
|
+
}
|
|
67560
|
+
function fromBase64Url(value, expectedLength) {
|
|
67561
|
+
if (!value || value.includes("=") || !/^[A-Za-z0-9_-]+$/.test(value)) {
|
|
67562
|
+
throw new Error("invalid remote-access key or envelope field");
|
|
67563
|
+
}
|
|
67564
|
+
const decoded = new Uint8Array(Buffer.from(value, "base64url"));
|
|
67565
|
+
if (toBase64Url(decoded) !== value || expectedLength !== void 0 && decoded.length !== expectedLength) {
|
|
67566
|
+
throw new Error("invalid remote-access key or envelope field");
|
|
67567
|
+
}
|
|
67568
|
+
return decoded;
|
|
67569
|
+
}
|
|
67570
|
+
function constantTimeEqual(left, right) {
|
|
67571
|
+
if (left.length !== right.length) return false;
|
|
67572
|
+
let difference = 0;
|
|
67573
|
+
for (let index = 0; index < left.length; index += 1) difference |= left[index] ^ right[index];
|
|
67574
|
+
return difference === 0;
|
|
67575
|
+
}
|
|
67576
|
+
function arrayBuffer(value) {
|
|
67577
|
+
return value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength);
|
|
67578
|
+
}
|
|
67357
67579
|
|
|
67358
67580
|
// src/remoteAccess.ts
|
|
67581
|
+
var DEFAULT_MAX_SEARCH_RESULTS = 20;
|
|
67582
|
+
var MAX_SEARCH_SNIPPET_CHARS = 500;
|
|
67583
|
+
var SEARCH_TIMEOUT_MS = 1e4;
|
|
67584
|
+
var MAX_APPROVED_ROOTS = 16;
|
|
67585
|
+
var DEFAULT_MAX_DIRECTORY_ENTRIES = 500;
|
|
67586
|
+
var DEFAULT_MAX_READ_BYTES = 200 * 1024;
|
|
67587
|
+
var DEFAULT_MAX_READ_LINES = 4e3;
|
|
67588
|
+
var BINARY_PROBE_BYTES = 8 * 1024;
|
|
67359
67589
|
var MAX_SOURCE_ID_LENGTH = 128;
|
|
67590
|
+
var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
67591
|
+
".png",
|
|
67592
|
+
".jpg",
|
|
67593
|
+
".jpeg",
|
|
67594
|
+
".gif",
|
|
67595
|
+
".webp",
|
|
67596
|
+
".pdf",
|
|
67597
|
+
".zip",
|
|
67598
|
+
".gz",
|
|
67599
|
+
".tar",
|
|
67600
|
+
".mp3",
|
|
67601
|
+
".mp4",
|
|
67602
|
+
".mov"
|
|
67603
|
+
]);
|
|
67604
|
+
function resolveRemoteAccessRoots(pathFlag, cwd = process.cwd()) {
|
|
67605
|
+
const requested = pathFlag === void 0 ? [cwd] : pathFlag.split("\n");
|
|
67606
|
+
if (requested.some((value) => !value)) throw new Error("--path requires a non-empty folder value");
|
|
67607
|
+
if (requested.length > MAX_APPROVED_ROOTS) {
|
|
67608
|
+
throw new Error(`remote-access accepts at most ${MAX_APPROVED_ROOTS} approved roots`);
|
|
67609
|
+
}
|
|
67610
|
+
const roots = [];
|
|
67611
|
+
for (const value of requested) {
|
|
67612
|
+
const candidate = resolve8(cwd, value);
|
|
67613
|
+
if (!existsSync10(candidate) || !statSync2(candidate).isDirectory()) {
|
|
67614
|
+
throw new Error(`Remote source path does not exist or is not a directory: ${candidate}`);
|
|
67615
|
+
}
|
|
67616
|
+
const canonical = realpathSync(candidate);
|
|
67617
|
+
if (!roots.includes(canonical)) roots.push(canonical);
|
|
67618
|
+
}
|
|
67619
|
+
return roots;
|
|
67620
|
+
}
|
|
67621
|
+
function discoverRemoteAccessRepositories(roots) {
|
|
67622
|
+
const repositoryRoots = /* @__PURE__ */ new Set();
|
|
67623
|
+
const permissionDenied = [];
|
|
67624
|
+
const visit = (directory, approvedRoot) => {
|
|
67625
|
+
let entries;
|
|
67626
|
+
try {
|
|
67627
|
+
entries = readdirSync5(directory, { withFileTypes: true });
|
|
67628
|
+
} catch (error) {
|
|
67629
|
+
const code = error.code;
|
|
67630
|
+
if (code === "EACCES" || code === "EPERM") {
|
|
67631
|
+
permissionDenied.push(relative2(approvedRoot, directory) || ".");
|
|
67632
|
+
return;
|
|
67633
|
+
}
|
|
67634
|
+
throw error;
|
|
67635
|
+
}
|
|
67636
|
+
if (entries.some((entry) => entry.name === ".git" && (entry.isDirectory() || entry.isFile()))) {
|
|
67637
|
+
repositoryRoots.add(realpathSync(directory));
|
|
67638
|
+
}
|
|
67639
|
+
for (const entry of entries) {
|
|
67640
|
+
if (entry.name === ".git" || entry.isSymbolicLink() || !entry.isDirectory()) continue;
|
|
67641
|
+
visit(join8(directory, entry.name), approvedRoot);
|
|
67642
|
+
}
|
|
67643
|
+
};
|
|
67644
|
+
for (const root of roots) visit(root, root);
|
|
67645
|
+
return {
|
|
67646
|
+
repositories: [...repositoryRoots].sort().map((rootPath) => ({ rootPath, displayName: rootPath.split(/[\\/]/).filter(Boolean).pop() ?? rootPath })),
|
|
67647
|
+
permissionDenied: permissionDenied.sort()
|
|
67648
|
+
};
|
|
67649
|
+
}
|
|
67650
|
+
function remoteAccessSourceType(rootPath) {
|
|
67651
|
+
const gitMarker = join8(rootPath, ".git");
|
|
67652
|
+
return existsSync10(gitMarker) ? "local_git_repository" : "local_folder";
|
|
67653
|
+
}
|
|
67654
|
+
function listRemoteAccessDirectory(options) {
|
|
67655
|
+
const root = realpathSync(options.sourceRoot);
|
|
67656
|
+
const directory = resolveApprovedPath(root, options.relativePath);
|
|
67657
|
+
if (!statSync2(directory).isDirectory()) throw new Error("Remote source path is not a directory");
|
|
67658
|
+
const maxEntries = options.maxEntries ?? DEFAULT_MAX_DIRECTORY_ENTRIES;
|
|
67659
|
+
if (!Number.isInteger(maxEntries) || maxEntries <= 0 || maxEntries > DEFAULT_MAX_DIRECTORY_ENTRIES) {
|
|
67660
|
+
throw new Error(`Remote directory entry limit must be between 1 and ${DEFAULT_MAX_DIRECTORY_ENTRIES}`);
|
|
67661
|
+
}
|
|
67662
|
+
const entries = [];
|
|
67663
|
+
let omitted = 0;
|
|
67664
|
+
let excluded = 0;
|
|
67665
|
+
for (const entry of readdirSync5(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
67666
|
+
const entryPath = relative2(root, join8(directory, entry.name)).replace(/\\/g, "/");
|
|
67667
|
+
if (entry.name === ".git" || entry.isSymbolicLink() || isGitIgnoredPath(root, entryPath) || classifyProjectFileRisk(entryPath, options.userProtectedPatterns ?? []).isHighRisk || entry.isFile() && isBinaryFile(join8(directory, entry.name))) {
|
|
67668
|
+
excluded += 1;
|
|
67669
|
+
continue;
|
|
67670
|
+
}
|
|
67671
|
+
if (!entry.isFile() && !entry.isDirectory()) {
|
|
67672
|
+
excluded += 1;
|
|
67673
|
+
continue;
|
|
67674
|
+
}
|
|
67675
|
+
if (entries.length >= maxEntries) {
|
|
67676
|
+
omitted += 1;
|
|
67677
|
+
continue;
|
|
67678
|
+
}
|
|
67679
|
+
entries.push({ path: entryPath, kind: entry.isDirectory() ? "directory" : "file" });
|
|
67680
|
+
}
|
|
67681
|
+
return { entries, omitted, excluded };
|
|
67682
|
+
}
|
|
67683
|
+
function readRemoteAccessTextFile(options) {
|
|
67684
|
+
const root = realpathSync(options.sourceRoot);
|
|
67685
|
+
const normalizedRelative = options.relativePath.replace(/\\/g, "/");
|
|
67686
|
+
if (classifyProjectFileRisk(normalizedRelative, options.userProtectedPatterns ?? []).isHighRisk) {
|
|
67687
|
+
throw new Error("Remote source file is protected");
|
|
67688
|
+
}
|
|
67689
|
+
if (isGitIgnoredPath(root, normalizedRelative)) throw new Error("Remote source file is ignored");
|
|
67690
|
+
const requested = resolveApprovedPath(root, normalizedRelative);
|
|
67691
|
+
options.beforeOpen?.();
|
|
67692
|
+
const maxBytes = normalizeBound(options.maxBytes, DEFAULT_MAX_READ_BYTES, "byte");
|
|
67693
|
+
const maxLines = normalizeBound(options.maxLines, DEFAULT_MAX_READ_LINES, "line");
|
|
67694
|
+
let descriptor;
|
|
67695
|
+
try {
|
|
67696
|
+
descriptor = openSync2(requested, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
67697
|
+
const openedPath = openedDescriptorPath(descriptor, requested);
|
|
67698
|
+
assertInsideRoot(root, openedPath);
|
|
67699
|
+
const stats = fstatSync(descriptor);
|
|
67700
|
+
if (!stats.isFile()) throw new Error("Remote source path is not a regular file");
|
|
67701
|
+
const bytesToRead = Math.min(stats.size, maxBytes + 1);
|
|
67702
|
+
const buffer = Buffer.alloc(bytesToRead);
|
|
67703
|
+
const bytesRead = readSync2(descriptor, buffer, 0, bytesToRead, 0);
|
|
67704
|
+
const bytes = new Uint8Array(buffer.subarray(0, bytesRead));
|
|
67705
|
+
if (bytes.subarray(0, BINARY_PROBE_BYTES).includes(0)) throw new Error("Remote source file is binary or unsupported");
|
|
67706
|
+
let decoded;
|
|
67707
|
+
try {
|
|
67708
|
+
decoded = new TextDecoder("utf-8", { fatal: true }).decode(bytes.subarray(0, Math.min(bytes.length, maxBytes)));
|
|
67709
|
+
} catch {
|
|
67710
|
+
throw new Error("Remote source file is binary or unsupported");
|
|
67711
|
+
}
|
|
67712
|
+
const lines = decoded.split(/(?<=\n)/);
|
|
67713
|
+
const content = lines.slice(0, maxLines).join("");
|
|
67714
|
+
return {
|
|
67715
|
+
content,
|
|
67716
|
+
truncated: stats.size > maxBytes || lines.length > maxLines,
|
|
67717
|
+
sizeBytes: stats.size,
|
|
67718
|
+
lineCount: decoded.length === 0 ? 0 : decoded.split("\n").length
|
|
67719
|
+
};
|
|
67720
|
+
} catch (error) {
|
|
67721
|
+
const code = error.code;
|
|
67722
|
+
if (code === "ELOOP") throw new Error("Remote source path is a symbolic link");
|
|
67723
|
+
throw error;
|
|
67724
|
+
} finally {
|
|
67725
|
+
if (descriptor !== void 0) closeSync2(descriptor);
|
|
67726
|
+
}
|
|
67727
|
+
}
|
|
67728
|
+
async function runRemoteAccessBridge(options) {
|
|
67729
|
+
let reconnectAttempt = 0;
|
|
67730
|
+
while (!options.signal.aborted) {
|
|
67731
|
+
options.onLifecycle?.({ state: reconnectAttempt === 0 ? "connecting" : "reconnecting", attempt: reconnectAttempt });
|
|
67732
|
+
let ws = null;
|
|
67733
|
+
let removeRequestListener = null;
|
|
67734
|
+
let heartbeatTimer = null;
|
|
67735
|
+
try {
|
|
67736
|
+
const opened = await options.client.openProjectRemoteAccessWebSocket();
|
|
67737
|
+
ws = opened.ws;
|
|
67738
|
+
await ws.sendAsync("project_remote_access_register", {
|
|
67739
|
+
source_session_id: options.sourceSessionId,
|
|
67740
|
+
confirmed_takeover: options.confirmedTakeover === true,
|
|
67741
|
+
bindings: options.bindings.map((binding) => ({
|
|
67742
|
+
project_id: binding.source.projectId,
|
|
67743
|
+
source_id: binding.source.sourceId,
|
|
67744
|
+
capabilities: ["read", "search", "import"],
|
|
67745
|
+
key_epoch: binding.keyEpoch
|
|
67746
|
+
}))
|
|
67747
|
+
});
|
|
67748
|
+
await ws.waitForMessage("project_remote_access_registered", void 0, 2e4);
|
|
67749
|
+
reconnectAttempt = 0;
|
|
67750
|
+
options.onLifecycle?.({ state: "connected" });
|
|
67751
|
+
removeRequestListener = ws.onProjectRemoteAccessRequest((frame) => {
|
|
67752
|
+
void handleLiveRemoteAccessRequest(ws, opened.ownerId, options.sourceSessionId, options.bindings, frame);
|
|
67753
|
+
});
|
|
67754
|
+
heartbeatTimer = setInterval(() => {
|
|
67755
|
+
void ws?.sendAsync("project_remote_access_heartbeat", { source_session_id: options.sourceSessionId });
|
|
67756
|
+
}, 15e3);
|
|
67757
|
+
await Promise.race([ws.waitForClose(), waitForAbort2(options.signal)]);
|
|
67758
|
+
if (options.signal.aborted) {
|
|
67759
|
+
await ws.sendAsync("project_remote_access_disconnect", { source_session_id: options.sourceSessionId }).catch(() => void 0);
|
|
67760
|
+
options.onLifecycle?.({ state: "disconnected" });
|
|
67761
|
+
return;
|
|
67762
|
+
}
|
|
67763
|
+
} catch (error) {
|
|
67764
|
+
if (error instanceof WebSocketProtocolError) throw error;
|
|
67765
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
67766
|
+
if (/session expired|invalid|not logged in/i.test(message)) throw error;
|
|
67767
|
+
} finally {
|
|
67768
|
+
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
67769
|
+
removeRequestListener?.();
|
|
67770
|
+
ws?.close();
|
|
67771
|
+
}
|
|
67772
|
+
reconnectAttempt += 1;
|
|
67773
|
+
const delayMs = reconnectDelayMs(reconnectAttempt);
|
|
67774
|
+
options.onLifecycle?.({ state: "reconnecting", attempt: reconnectAttempt, delayMs });
|
|
67775
|
+
await waitForAbort2(options.signal, delayMs);
|
|
67776
|
+
}
|
|
67777
|
+
}
|
|
67778
|
+
async function handleLiveRemoteAccessRequest(ws, ownerId, sourceSessionId, bindings, frame) {
|
|
67779
|
+
const binding = bindings.find(
|
|
67780
|
+
(item) => item.source.sourceId === frame.source_id && item.source.projectId === frame.project_id
|
|
67781
|
+
);
|
|
67782
|
+
if (!binding || frame.source_session_id !== sourceSessionId || frame.key_epoch !== binding.keyEpoch) return;
|
|
67783
|
+
const bootstrapText = await decryptWithAesGcmCombined(frame.encrypted_envelope, binding.projectKey);
|
|
67784
|
+
if (!bootstrapText) return;
|
|
67785
|
+
let bootstrap;
|
|
67786
|
+
try {
|
|
67787
|
+
bootstrap = JSON.parse(bootstrapText);
|
|
67788
|
+
} catch {
|
|
67789
|
+
return;
|
|
67790
|
+
}
|
|
67791
|
+
if (!bootstrap.requesting_client_id || bootstrap.requesting_client_id !== frame.requesting_client_id || !bootstrap.requester_handshake || bootstrap.operation !== frame.operation || !bootstrap.arguments) return;
|
|
67792
|
+
const identity = {
|
|
67793
|
+
ownerId,
|
|
67794
|
+
projectId: frame.project_id,
|
|
67795
|
+
sourceId: frame.source_id,
|
|
67796
|
+
sourceSessionId,
|
|
67797
|
+
requestingClientId: bootstrap.requesting_client_id,
|
|
67798
|
+
keyEpoch: binding.keyEpoch
|
|
67799
|
+
};
|
|
67800
|
+
try {
|
|
67801
|
+
const sourceHandshake = await createRemoteAccessHandshake(binding.projectKey, identity, "source");
|
|
67802
|
+
const sessionKey = await deriveRemoteAccessSessionKey(
|
|
67803
|
+
binding.projectKey,
|
|
67804
|
+
identity,
|
|
67805
|
+
"source",
|
|
67806
|
+
sourceHandshake.privateKey,
|
|
67807
|
+
sourceHandshake.handshake,
|
|
67808
|
+
bootstrap.requester_handshake
|
|
67809
|
+
);
|
|
67810
|
+
let response;
|
|
67811
|
+
try {
|
|
67812
|
+
response = {
|
|
67813
|
+
ok: true,
|
|
67814
|
+
result: await executeRemoteAccessOperation(binding.source.rootPath, frame.operation, bootstrap.arguments)
|
|
67815
|
+
};
|
|
67816
|
+
} catch (error) {
|
|
67817
|
+
response = { ok: false, error: remoteAccessOperationErrorCode(error) };
|
|
67818
|
+
}
|
|
67819
|
+
const envelope = await sealRemoteAccessEnvelope(
|
|
67820
|
+
sessionKey,
|
|
67821
|
+
identity,
|
|
67822
|
+
frame.request_id,
|
|
67823
|
+
"result",
|
|
67824
|
+
new TextEncoder().encode(JSON.stringify(response))
|
|
67825
|
+
);
|
|
67826
|
+
await ws.sendAsync("project_remote_access_complete", {
|
|
67827
|
+
source_session_id: sourceSessionId,
|
|
67828
|
+
project_id: frame.project_id,
|
|
67829
|
+
source_id: frame.source_id,
|
|
67830
|
+
request_id: frame.request_id,
|
|
67831
|
+
key_epoch: binding.keyEpoch,
|
|
67832
|
+
encrypted_envelope: JSON.stringify({ source_handshake: sourceHandshake.handshake, envelope })
|
|
67833
|
+
});
|
|
67834
|
+
} catch {
|
|
67835
|
+
}
|
|
67836
|
+
}
|
|
67837
|
+
async function executeRemoteAccessOperation(sourceRoot, operation, args) {
|
|
67838
|
+
const relativePath = typeof args.path === "string" ? args.path : ".";
|
|
67839
|
+
if (operation === "list") {
|
|
67840
|
+
return listRemoteAccessDirectory({ sourceRoot, relativePath });
|
|
67841
|
+
}
|
|
67842
|
+
if (operation === "read_text") {
|
|
67843
|
+
return readRemoteAccessTextFile({ sourceRoot, relativePath });
|
|
67844
|
+
}
|
|
67845
|
+
const query = typeof args.query === "string" ? args.query : "";
|
|
67846
|
+
if (!query) throw new Error("Search query is required");
|
|
67847
|
+
return searchRemoteSource({ query, sourceRoot, runRg: runRgCommand });
|
|
67848
|
+
}
|
|
67849
|
+
function remoteAccessOperationErrorCode(error) {
|
|
67850
|
+
const message = error instanceof Error ? error.message : "";
|
|
67851
|
+
if (/protected|ignored/i.test(message)) return "protected_path";
|
|
67852
|
+
if (/binary|unsupported/i.test(message)) return "unsupported_file";
|
|
67853
|
+
if (/symbolic link|approved source root|not a directory|not a regular file|ENOENT/i.test(message)) return "invalid_path";
|
|
67854
|
+
if (/Search query is required/i.test(message)) return "search_query_required";
|
|
67855
|
+
return "operation_failed";
|
|
67856
|
+
}
|
|
67857
|
+
function isGitIgnoredPath(sourceRoot, relativePath) {
|
|
67858
|
+
if (!existsSync10(join8(sourceRoot, ".git"))) return false;
|
|
67859
|
+
const result = spawnSync2("git", ["check-ignore", "--quiet", "--", relativePath], {
|
|
67860
|
+
cwd: sourceRoot,
|
|
67861
|
+
stdio: "ignore"
|
|
67862
|
+
});
|
|
67863
|
+
if (result.error) throw result.error;
|
|
67864
|
+
if (result.status === 0) return true;
|
|
67865
|
+
if (result.status === 1) return false;
|
|
67866
|
+
throw new Error("Could not evaluate Git ignored-file policy");
|
|
67867
|
+
}
|
|
67868
|
+
function reconnectDelayMs(attempt) {
|
|
67869
|
+
const base = Math.min(3e4, 1e3 * 2 ** Math.max(0, attempt - 1));
|
|
67870
|
+
return Math.round(base * (0.8 + Math.random() * 0.4));
|
|
67871
|
+
}
|
|
67872
|
+
function waitForAbort2(signal, timeoutMs) {
|
|
67873
|
+
if (signal.aborted) return Promise.resolve();
|
|
67874
|
+
return new Promise((resolvePromise) => {
|
|
67875
|
+
const onAbort = () => {
|
|
67876
|
+
if (timer) clearTimeout(timer);
|
|
67877
|
+
resolvePromise();
|
|
67878
|
+
};
|
|
67879
|
+
const timer = timeoutMs === void 0 ? void 0 : setTimeout(() => {
|
|
67880
|
+
signal.removeEventListener("abort", onAbort);
|
|
67881
|
+
resolvePromise();
|
|
67882
|
+
}, timeoutMs);
|
|
67883
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
67884
|
+
});
|
|
67885
|
+
}
|
|
67360
67886
|
function resolveRemoteCachePath(sourceId, homeDirectory = homedir7()) {
|
|
67361
67887
|
assertSafeSourceId(sourceId);
|
|
67362
67888
|
return join8(homeDirectory, ".openmates", "remote-cache", sourceId);
|
|
@@ -67377,7 +67903,7 @@ function startRemoteAccessSource(input) {
|
|
|
67377
67903
|
rootPath,
|
|
67378
67904
|
displayName: input.displayName ?? input.sourceId,
|
|
67379
67905
|
cachePath: resolveRemoteCachePath(input.sourceId, homeDirectory),
|
|
67380
|
-
status: "
|
|
67906
|
+
status: "offline",
|
|
67381
67907
|
createdAt: now,
|
|
67382
67908
|
updatedAt: now
|
|
67383
67909
|
};
|
|
@@ -67401,11 +67927,190 @@ function listRemoteAccessSources(homeDirectory = homedir7()) {
|
|
|
67401
67927
|
throw new Error(`Failed to read remote source store at ${filePath}: ${message}`);
|
|
67402
67928
|
}
|
|
67403
67929
|
}
|
|
67930
|
+
async function searchRemoteSource(options) {
|
|
67931
|
+
const sourceRoot = resolve8(options.sourceRoot);
|
|
67932
|
+
const maxResults = normalizeMaxResults(options.maxResults);
|
|
67933
|
+
const output = await options.runRg(
|
|
67934
|
+
buildRgSearchArgs(options.query, options.userProtectedPatterns ?? []),
|
|
67935
|
+
sourceRoot,
|
|
67936
|
+
maxResults + 1
|
|
67937
|
+
);
|
|
67938
|
+
const matches2 = [];
|
|
67939
|
+
let omitted = 0;
|
|
67940
|
+
let excluded = 0;
|
|
67941
|
+
for (const line of output.split("\n")) {
|
|
67942
|
+
if (!line.trim()) continue;
|
|
67943
|
+
const match = parseRgMatch(line);
|
|
67944
|
+
if (!match) continue;
|
|
67945
|
+
if (shouldExcludePath(sourceRoot, match.path, options.userProtectedPatterns ?? [])) {
|
|
67946
|
+
excluded += 1;
|
|
67947
|
+
continue;
|
|
67948
|
+
}
|
|
67949
|
+
if (matches2.length >= maxResults) {
|
|
67950
|
+
omitted += 1;
|
|
67951
|
+
continue;
|
|
67952
|
+
}
|
|
67953
|
+
matches2.push(match);
|
|
67954
|
+
}
|
|
67955
|
+
return { matches: matches2, omitted, excluded };
|
|
67956
|
+
}
|
|
67404
67957
|
function assertSafeSourceId(sourceId) {
|
|
67405
67958
|
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(sourceId) || sourceId.length > MAX_SOURCE_ID_LENGTH) {
|
|
67406
67959
|
throw new Error("Remote source ID must be 1-128 characters and contain only letters, numbers, dot, underscore, or hyphen");
|
|
67407
67960
|
}
|
|
67408
67961
|
}
|
|
67962
|
+
function normalizeMaxResults(value) {
|
|
67963
|
+
const maxResults = value ?? DEFAULT_MAX_SEARCH_RESULTS;
|
|
67964
|
+
if (!Number.isInteger(maxResults) || maxResults <= 0 || maxResults > DEFAULT_MAX_SEARCH_RESULTS) {
|
|
67965
|
+
throw new Error(`Remote source search limit must be between 1 and ${DEFAULT_MAX_SEARCH_RESULTS}`);
|
|
67966
|
+
}
|
|
67967
|
+
return maxResults;
|
|
67968
|
+
}
|
|
67969
|
+
function buildRgSearchArgs(query, userProtectedPatterns) {
|
|
67970
|
+
const args = ["--json", "--line-number"];
|
|
67971
|
+
for (const pattern of [...PROJECT_HIGH_RISK_GLOBS, ...binaryRgGlobs(), ...userProtectedPatterns]) {
|
|
67972
|
+
args.push("--glob", `!${pattern.replace(/\\/g, "/")}`);
|
|
67973
|
+
}
|
|
67974
|
+
args.push("--", query, ".");
|
|
67975
|
+
return args;
|
|
67976
|
+
}
|
|
67977
|
+
function binaryRgGlobs() {
|
|
67978
|
+
return [...BINARY_EXTENSIONS].flatMap((extension) => [`*${extension}`, `**/*${extension}`]);
|
|
67979
|
+
}
|
|
67980
|
+
async function runRgCommand(args, cwd, maxOutputMatches) {
|
|
67981
|
+
return new Promise((resolvePromise, reject) => {
|
|
67982
|
+
const child = spawn2("rg", args, {
|
|
67983
|
+
cwd,
|
|
67984
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
67985
|
+
});
|
|
67986
|
+
const stdoutLines = [];
|
|
67987
|
+
const stderrChunks = [];
|
|
67988
|
+
let pendingStdout = "";
|
|
67989
|
+
let matchCount = 0;
|
|
67990
|
+
let killedForCap = false;
|
|
67991
|
+
let timedOut = false;
|
|
67992
|
+
const timeout = setTimeout(() => {
|
|
67993
|
+
timedOut = true;
|
|
67994
|
+
child.kill();
|
|
67995
|
+
}, SEARCH_TIMEOUT_MS);
|
|
67996
|
+
child.stdout.setEncoding("utf-8");
|
|
67997
|
+
child.stdout.on("data", (chunk) => {
|
|
67998
|
+
pendingStdout += chunk;
|
|
67999
|
+
const lines = pendingStdout.split("\n");
|
|
68000
|
+
pendingStdout = lines.pop() ?? "";
|
|
68001
|
+
for (const line of lines) {
|
|
68002
|
+
if (!line) continue;
|
|
68003
|
+
stdoutLines.push(line);
|
|
68004
|
+
if (line.includes('"type":"match"')) {
|
|
68005
|
+
matchCount += 1;
|
|
68006
|
+
}
|
|
68007
|
+
if (maxOutputMatches !== void 0 && matchCount >= maxOutputMatches) {
|
|
68008
|
+
killedForCap = true;
|
|
68009
|
+
child.kill();
|
|
68010
|
+
break;
|
|
68011
|
+
}
|
|
68012
|
+
}
|
|
68013
|
+
});
|
|
68014
|
+
child.stderr.setEncoding("utf-8");
|
|
68015
|
+
child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
|
|
68016
|
+
child.on("error", (error) => {
|
|
68017
|
+
clearTimeout(timeout);
|
|
68018
|
+
reject(error);
|
|
68019
|
+
});
|
|
68020
|
+
child.on("close", (code) => {
|
|
68021
|
+
clearTimeout(timeout);
|
|
68022
|
+
if (pendingStdout) stdoutLines.push(pendingStdout);
|
|
68023
|
+
if (timedOut) {
|
|
68024
|
+
reject(new Error("Remote source search timed out"));
|
|
68025
|
+
return;
|
|
68026
|
+
}
|
|
68027
|
+
if (code === 0 || code === 1 || killedForCap) {
|
|
68028
|
+
resolvePromise(stdoutLines.join("\n"));
|
|
68029
|
+
return;
|
|
68030
|
+
}
|
|
68031
|
+
reject(new Error(`rg failed with exit code ${code}: ${stderrChunks.join("").trim()}`));
|
|
68032
|
+
});
|
|
68033
|
+
});
|
|
68034
|
+
}
|
|
68035
|
+
function shouldExcludePath(sourceRoot, relativePath, userProtectedPatterns) {
|
|
68036
|
+
if (!isPathInsideRoot(sourceRoot, relativePath)) return true;
|
|
68037
|
+
if (isBinaryPath(relativePath)) return true;
|
|
68038
|
+
return classifyProjectFileRisk(relativePath, userProtectedPatterns).isHighRisk;
|
|
68039
|
+
}
|
|
68040
|
+
function isPathInsideRoot(sourceRoot, relativePath) {
|
|
68041
|
+
const resolvedPath = resolve8(sourceRoot, relativePath);
|
|
68042
|
+
const relation = relative2(sourceRoot, resolvedPath);
|
|
68043
|
+
return relation === "" || !relation.startsWith("..") && !resolve8(relation).startsWith("/..");
|
|
68044
|
+
}
|
|
68045
|
+
function isBinaryPath(path) {
|
|
68046
|
+
const lowerPath = path.toLowerCase();
|
|
68047
|
+
for (const extension of BINARY_EXTENSIONS) {
|
|
68048
|
+
if (lowerPath.endsWith(extension)) return true;
|
|
68049
|
+
}
|
|
68050
|
+
return false;
|
|
68051
|
+
}
|
|
68052
|
+
function parseRgMatch(line) {
|
|
68053
|
+
try {
|
|
68054
|
+
const parsed = JSON.parse(line);
|
|
68055
|
+
if (parsed.type !== "match") return null;
|
|
68056
|
+
const path = parsed.data?.path?.text;
|
|
68057
|
+
const lineNumber = parsed.data?.line_number;
|
|
68058
|
+
const snippet = parsed.data?.lines?.text;
|
|
68059
|
+
if (typeof path !== "string" || typeof lineNumber !== "number" || typeof snippet !== "string") {
|
|
68060
|
+
return null;
|
|
68061
|
+
}
|
|
68062
|
+
return { path, line: lineNumber, snippet: snippet.slice(0, MAX_SEARCH_SNIPPET_CHARS) };
|
|
68063
|
+
} catch {
|
|
68064
|
+
return null;
|
|
68065
|
+
}
|
|
68066
|
+
}
|
|
68067
|
+
function resolveApprovedPath(sourceRoot, relativePath) {
|
|
68068
|
+
if (!relativePath || relativePath.includes("\0")) throw new Error("Remote source path is invalid");
|
|
68069
|
+
const lexical = resolve8(sourceRoot, relativePath);
|
|
68070
|
+
assertInsideRoot(sourceRoot, lexical);
|
|
68071
|
+
const canonical = realpathSync(lexical);
|
|
68072
|
+
assertInsideRoot(sourceRoot, canonical);
|
|
68073
|
+
return canonical;
|
|
68074
|
+
}
|
|
68075
|
+
function assertInsideRoot(sourceRoot, path) {
|
|
68076
|
+
const relation = relative2(sourceRoot, path);
|
|
68077
|
+
if (relation.startsWith("..") || resolve8(sourceRoot, relation) !== resolve8(path)) {
|
|
68078
|
+
throw new Error("Remote source path escapes the approved source root");
|
|
68079
|
+
}
|
|
68080
|
+
}
|
|
68081
|
+
function openedDescriptorPath(descriptor, requestedPath) {
|
|
68082
|
+
const procPath = `/proc/self/fd/${descriptor}`;
|
|
68083
|
+
if (existsSync10(procPath)) return realpathSync(procPath);
|
|
68084
|
+
return realpathSync(requestedPath);
|
|
68085
|
+
}
|
|
68086
|
+
function isBinaryFile(path) {
|
|
68087
|
+
let descriptor;
|
|
68088
|
+
try {
|
|
68089
|
+
descriptor = openSync2(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
68090
|
+
const stats = fstatSync(descriptor);
|
|
68091
|
+
const buffer = Buffer.alloc(Math.min(stats.size, BINARY_PROBE_BYTES));
|
|
68092
|
+
const bytesRead = readSync2(descriptor, buffer, 0, buffer.length, 0);
|
|
68093
|
+
const bytes = new Uint8Array(buffer.subarray(0, bytesRead));
|
|
68094
|
+
if (bytes.includes(0)) return true;
|
|
68095
|
+
try {
|
|
68096
|
+
new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
68097
|
+
return false;
|
|
68098
|
+
} catch {
|
|
68099
|
+
return true;
|
|
68100
|
+
}
|
|
68101
|
+
} catch {
|
|
68102
|
+
return true;
|
|
68103
|
+
} finally {
|
|
68104
|
+
if (descriptor !== void 0) closeSync2(descriptor);
|
|
68105
|
+
}
|
|
68106
|
+
}
|
|
68107
|
+
function normalizeBound(value, maximum, label) {
|
|
68108
|
+
const result = value ?? maximum;
|
|
68109
|
+
if (!Number.isInteger(result) || result <= 0 || result > maximum) {
|
|
68110
|
+
throw new Error(`Remote source ${label} limit must be between 1 and ${maximum}`);
|
|
68111
|
+
}
|
|
68112
|
+
return result;
|
|
68113
|
+
}
|
|
67409
68114
|
function remoteAccessStorePath(homeDirectory) {
|
|
67410
68115
|
return join8(homeDirectory, ".openmates", "remote-sources.json");
|
|
67411
68116
|
}
|
|
@@ -67434,7 +68139,7 @@ function assertRemoteAccessSourceRecord(value, index) {
|
|
|
67434
68139
|
}
|
|
67435
68140
|
|
|
67436
68141
|
// src/selfUpdate.ts
|
|
67437
|
-
import { spawnSync as
|
|
68142
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
67438
68143
|
import { readFileSync as readFileSync9 } from "fs";
|
|
67439
68144
|
var PACKAGE_NAME = "openmates";
|
|
67440
68145
|
var DEFAULT_TARGET = "latest";
|
|
@@ -67481,7 +68186,7 @@ function checkSelfUpdateStatus(plan) {
|
|
|
67481
68186
|
}
|
|
67482
68187
|
function runSelfUpdate(plan, options = {}) {
|
|
67483
68188
|
if (plan.dryRun) return;
|
|
67484
|
-
const result =
|
|
68189
|
+
const result = spawnSync3(plan.command, plan.args, {
|
|
67485
68190
|
encoding: "utf-8",
|
|
67486
68191
|
stdio: ["ignore", "pipe", "pipe"]
|
|
67487
68192
|
});
|
|
@@ -67503,7 +68208,7 @@ function resolveTargetVersion(target) {
|
|
|
67503
68208
|
if (/^\d+\.\d+\.\d+(?:[-+][a-zA-Z0-9._-]+)?$/.test(target)) {
|
|
67504
68209
|
return { version: target, error: null };
|
|
67505
68210
|
}
|
|
67506
|
-
const result =
|
|
68211
|
+
const result = spawnSync3(commandName("npm"), ["view", `${PACKAGE_NAME}@${target}`, "version"], {
|
|
67507
68212
|
encoding: "utf-8",
|
|
67508
68213
|
stdio: ["ignore", "pipe", "pipe"]
|
|
67509
68214
|
});
|
|
@@ -69940,34 +70645,62 @@ async function handleRemoteAccess(client, subcommand, rest, flags) {
|
|
|
69940
70645
|
printRemoteAccessHelp();
|
|
69941
70646
|
return;
|
|
69942
70647
|
}
|
|
69943
|
-
|
|
69944
|
-
const
|
|
69945
|
-
const
|
|
69946
|
-
|
|
69947
|
-
const
|
|
69948
|
-
const
|
|
69949
|
-
const
|
|
69950
|
-
|
|
69951
|
-
|
|
69952
|
-
|
|
69953
|
-
|
|
69954
|
-
|
|
69955
|
-
|
|
69956
|
-
|
|
70648
|
+
assertRemoteAccessPublicFlags(flags);
|
|
70649
|
+
const roots = resolveRemoteAccessRoots(typeof flags.path === "string" ? flags.path : void 0);
|
|
70650
|
+
const discovery = discoverRemoteAccessRepositories(roots);
|
|
70651
|
+
const candidateRoots = typeof flags.path === "string" ? roots : discovery.repositories.length > 0 ? discovery.repositories.map((candidate) => candidate.rootPath) : roots;
|
|
70652
|
+
const masterKey = client.getMasterKeyBytes();
|
|
70653
|
+
const projects = await loadProjects(client, masterKey, flags);
|
|
70654
|
+
const bindings = await resolveRemoteAccessBindings(client, masterKey, projects, candidateRoots, flags);
|
|
70655
|
+
if (bindings.length === 0) throw new Error("No Project folders were approved for remote access.");
|
|
70656
|
+
const sourceSessionId = randomUUID10();
|
|
70657
|
+
const controller = new AbortController();
|
|
70658
|
+
const stop = () => controller.abort();
|
|
70659
|
+
process.once("SIGINT", stop);
|
|
70660
|
+
process.once("SIGTERM", stop);
|
|
70661
|
+
if (flags.json !== true) {
|
|
70662
|
+
console.log(`Remote access connecting ${bindings.length} Project source${bindings.length === 1 ? "" : "s"}.`);
|
|
69957
70663
|
console.log("Keep this terminal open. For long-running work, start it inside zellij, tmux, or screen.");
|
|
70664
|
+
if (discovery.permissionDenied.length > 0) {
|
|
70665
|
+
console.warn(`Skipped ${discovery.permissionDenied.length} unreadable folder${discovery.permissionDenied.length === 1 ? "" : "s"}.`);
|
|
70666
|
+
}
|
|
70667
|
+
}
|
|
70668
|
+
try {
|
|
70669
|
+
let confirmedTakeover = false;
|
|
70670
|
+
while (!controller.signal.aborted) {
|
|
70671
|
+
try {
|
|
70672
|
+
await runRemoteAccessBridge({
|
|
70673
|
+
client,
|
|
70674
|
+
sourceSessionId,
|
|
70675
|
+
bindings,
|
|
70676
|
+
signal: controller.signal,
|
|
70677
|
+
confirmedTakeover,
|
|
70678
|
+
onLifecycle: (event) => {
|
|
70679
|
+
if (flags.json === true) printJson2({ type: "remote_access_lifecycle", ...event });
|
|
70680
|
+
else if (event.state === "connected") console.log("Remote access connected.");
|
|
70681
|
+
else if (event.state === "reconnecting") console.log(`Remote access reconnecting in ${event.delayMs ?? 0}ms.`);
|
|
70682
|
+
else if (event.state === "disconnected") console.log("Remote access disconnected.");
|
|
70683
|
+
}
|
|
70684
|
+
});
|
|
70685
|
+
break;
|
|
70686
|
+
} catch (error) {
|
|
70687
|
+
if (!(error instanceof WebSocketProtocolError) || error.code !== "takeover_confirmation_required") throw error;
|
|
70688
|
+
if (flags.json === true || !process.stdin.isTTY) {
|
|
70689
|
+
throw new Error("Another remote-access process owns one of these sources; rerun interactively to confirm takeover.");
|
|
70690
|
+
}
|
|
70691
|
+
const answer = (await promptLine2("Another process owns one of these sources. Take over? [y/N] ")).trim().toLowerCase();
|
|
70692
|
+
if (answer !== "y" && answer !== "yes") throw new Error("Remote access takeover cancelled.");
|
|
70693
|
+
confirmedTakeover = true;
|
|
70694
|
+
}
|
|
70695
|
+
}
|
|
70696
|
+
} finally {
|
|
70697
|
+
process.off("SIGINT", stop);
|
|
70698
|
+
process.off("SIGTERM", stop);
|
|
69958
70699
|
}
|
|
69959
70700
|
return;
|
|
69960
70701
|
}
|
|
69961
70702
|
throw new Error("openmates remote-access does not accept public subcommands. Run 'openmates remote-access --help'.");
|
|
69962
70703
|
}
|
|
69963
|
-
function parseRemoteAccessRootPath(flags) {
|
|
69964
|
-
const rootPath = flags.path;
|
|
69965
|
-
if (rootPath === void 0) return process.cwd();
|
|
69966
|
-
if (typeof rootPath !== "string") {
|
|
69967
|
-
throw new Error("--path requires a folder value.");
|
|
69968
|
-
}
|
|
69969
|
-
return rootPath;
|
|
69970
|
-
}
|
|
69971
70704
|
function parsePositiveIntegerFlag(value, flagName) {
|
|
69972
70705
|
if (value === void 0) return void 0;
|
|
69973
70706
|
if (typeof value !== "string") {
|
|
@@ -69983,38 +70716,101 @@ function parseResponseTimeoutMs2(flags) {
|
|
|
69983
70716
|
const seconds = parsePositiveIntegerFlag(flags["response-timeout-seconds"], "--response-timeout-seconds");
|
|
69984
70717
|
return seconds === void 0 ? void 0 : seconds * 1e3;
|
|
69985
70718
|
}
|
|
69986
|
-
function
|
|
69987
|
-
|
|
69988
|
-
|
|
69989
|
-
|
|
70719
|
+
function assertRemoteAccessPublicFlags(flags) {
|
|
70720
|
+
const internalFlags = ["source-id", "project", "type", "local-only", "encrypted-display-name", "encrypted-metadata"];
|
|
70721
|
+
const supplied = internalFlags.filter((name) => flags[name] !== void 0);
|
|
70722
|
+
if (supplied.length > 0) {
|
|
70723
|
+
throw new Error(`Unsupported remote-access option${supplied.length === 1 ? "" : "s"}: ${supplied.map((name) => `--${name}`).join(", ")}`);
|
|
69990
70724
|
}
|
|
69991
|
-
throw new Error("--type must be one of local_folder or local_git_repository for the local remote-access bridge");
|
|
69992
70725
|
}
|
|
69993
|
-
async function
|
|
69994
|
-
|
|
69995
|
-
const
|
|
69996
|
-
const
|
|
69997
|
-
|
|
69998
|
-
|
|
70726
|
+
async function resolveRemoteAccessBindings(client, masterKey, projects, candidateRoots, flags) {
|
|
70727
|
+
const stored = listRemoteAccessSources();
|
|
70728
|
+
const resolved = [];
|
|
70729
|
+
const unresolved = [];
|
|
70730
|
+
for (const rootPath of candidateRoots) {
|
|
70731
|
+
const existingSource = stored.find((source) => source.rootPath === rootPath && source.projectId);
|
|
70732
|
+
const project = existingSource ? projects.find((item) => item.projectId === existingSource.projectId) : void 0;
|
|
70733
|
+
if (existingSource && project) {
|
|
70734
|
+
resolved.push({ rootPath, project, sourceId: existingSource.sourceId });
|
|
70735
|
+
} else {
|
|
70736
|
+
unresolved.push(rootPath);
|
|
70737
|
+
}
|
|
70738
|
+
}
|
|
70739
|
+
if (unresolved.length > 0) {
|
|
70740
|
+
if (flags.json === true || !process.stdin.isTTY) {
|
|
70741
|
+
throw new Error(`Remote access needs interactive Project review for: ${unresolved.join(", ")}`);
|
|
70742
|
+
}
|
|
70743
|
+
console.log("The following folders need OpenMates Projects:");
|
|
70744
|
+
unresolved.forEach((rootPath) => console.log(` - ${rootPath}`));
|
|
70745
|
+
const answer = (await promptLine2(`Create ${unresolved.length} missing Project${unresolved.length === 1 ? "" : "s"}? [y/N] `)).trim().toLowerCase();
|
|
70746
|
+
if (answer !== "y" && answer !== "yes") throw new Error("Remote access Project creation cancelled.");
|
|
70747
|
+
for (const rootPath of unresolved) {
|
|
70748
|
+
const project = await createEncryptedRemoteAccessProject(client, masterKey, basename3(rootPath));
|
|
70749
|
+
resolved.push({ rootPath, project, sourceId: randomUUID10() });
|
|
70750
|
+
}
|
|
70751
|
+
}
|
|
70752
|
+
const bindings = [];
|
|
70753
|
+
for (const item of resolved) {
|
|
70754
|
+
const sourceType = remoteAccessSourceType(item.rootPath);
|
|
70755
|
+
const source = startRemoteAccessSource({
|
|
70756
|
+
sourceId: item.sourceId,
|
|
70757
|
+
projectId: item.project.projectId,
|
|
70758
|
+
rootPath: item.rootPath,
|
|
70759
|
+
sourceType,
|
|
70760
|
+
displayName: basename3(item.rootPath)
|
|
70761
|
+
});
|
|
70762
|
+
const remoteSources = await client.listProjectSources(item.project.projectId);
|
|
70763
|
+
const remoteSource = remoteSources.find((entry) => entry.source_id === source.sourceId);
|
|
70764
|
+
if (!remoteSource) {
|
|
70765
|
+
const timestamp = Math.floor(Date.now() / 1e3);
|
|
70766
|
+
await client.createProjectSource(item.project.projectId, {
|
|
70767
|
+
source_id: source.sourceId,
|
|
70768
|
+
source_type: sourceType,
|
|
70769
|
+
encrypted_display_name: await encryptWithAesGcmCombined(source.displayName, item.project.projectKey),
|
|
70770
|
+
encrypted_metadata: await encryptWithAesGcmCombined(
|
|
70771
|
+
JSON.stringify({ root: source.rootPath, binding_root: source.rootPath }),
|
|
70772
|
+
item.project.projectKey
|
|
70773
|
+
),
|
|
70774
|
+
capabilities: ["read", "search", "import"],
|
|
70775
|
+
status: "offline",
|
|
70776
|
+
created_at: timestamp,
|
|
70777
|
+
updated_at: timestamp
|
|
70778
|
+
});
|
|
70779
|
+
}
|
|
70780
|
+
bindings.push({ source, projectKey: item.project.projectKey, keyEpoch: 1 });
|
|
69999
70781
|
}
|
|
70782
|
+
return bindings;
|
|
70783
|
+
}
|
|
70784
|
+
async function createEncryptedRemoteAccessProject(client, masterKey, name) {
|
|
70785
|
+
const projectKey = randomBytes8(32);
|
|
70786
|
+
const projectId = randomUUID10();
|
|
70000
70787
|
const timestamp = Math.floor(Date.now() / 1e3);
|
|
70001
|
-
|
|
70002
|
-
|
|
70003
|
-
|
|
70004
|
-
|
|
70005
|
-
|
|
70006
|
-
|
|
70007
|
-
|
|
70788
|
+
const payload = {
|
|
70789
|
+
project_id: projectId,
|
|
70790
|
+
encrypted_project_key: await encryptBytesWithAesGcm(projectKey, masterKey),
|
|
70791
|
+
encrypted_name: await encryptWithAesGcmCombined(name, projectKey),
|
|
70792
|
+
encrypted_description: await encryptWithAesGcmCombined("", projectKey),
|
|
70793
|
+
encrypted_icon: await encryptWithAesGcmCombined("folder", projectKey),
|
|
70794
|
+
encrypted_color: await encryptWithAesGcmCombined("default", projectKey),
|
|
70795
|
+
pinned: false,
|
|
70796
|
+
archived: false,
|
|
70008
70797
|
created_at: timestamp,
|
|
70009
|
-
updated_at: timestamp
|
|
70010
|
-
|
|
70011
|
-
}
|
|
70012
|
-
|
|
70013
|
-
|
|
70014
|
-
|
|
70015
|
-
|
|
70016
|
-
|
|
70017
|
-
|
|
70798
|
+
updated_at: timestamp,
|
|
70799
|
+
last_opened_at: timestamp
|
|
70800
|
+
};
|
|
70801
|
+
await client.createProject(payload);
|
|
70802
|
+
return {
|
|
70803
|
+
projectId,
|
|
70804
|
+
name,
|
|
70805
|
+
description: "",
|
|
70806
|
+
icon: "folder",
|
|
70807
|
+
color: "default",
|
|
70808
|
+
pinned: false,
|
|
70809
|
+
archived: false,
|
|
70810
|
+
version: 1,
|
|
70811
|
+
projectKey,
|
|
70812
|
+
encrypted: payload
|
|
70813
|
+
};
|
|
70018
70814
|
}
|
|
70019
70815
|
function shouldInitializeRedactor(command, subcommand) {
|
|
70020
70816
|
return command === "chats" && ["new", "send", "answer-interactive", "incognito"].includes(subcommand ?? "");
|
|
@@ -76851,21 +77647,20 @@ Options:
|
|
|
76851
77647
|
}
|
|
76852
77648
|
function printRemoteAccessHelp() {
|
|
76853
77649
|
console.log(`Remote access command:
|
|
76854
|
-
openmates remote-access [--path <folder>] [--
|
|
77650
|
+
openmates remote-access [--path <folder>]... [--json]
|
|
76855
77651
|
|
|
76856
77652
|
Behavior:
|
|
76857
|
-
|
|
76858
|
-
--path
|
|
76859
|
-
|
|
76860
|
-
|
|
76861
|
-
Source types:
|
|
76862
|
-
local_folder, local_git_repository
|
|
77653
|
+
Discovers repository Projects below the current working directory by default.
|
|
77654
|
+
Repeated --path values replace default discovery. Missing Projects are created
|
|
77655
|
+
only after interactive review. The command remains connected in the foreground;
|
|
77656
|
+
keep the terminal open or use zellij, tmux, or screen.
|
|
76863
77657
|
|
|
76864
77658
|
Security:
|
|
76865
77659
|
Source metadata is stored locally under ~/.openmates/remote-sources.json.
|
|
76866
77660
|
Preview cache defaults to ~/.openmates/remote-cache/<source-id>.
|
|
76867
|
-
|
|
76868
|
-
|
|
77661
|
+
Project metadata and bridge payloads are encrypted automatically. List, search,
|
|
77662
|
+
and text preview stay inside approved roots and exclude protected, ignored,
|
|
77663
|
+
binary, symlink-escaped, and out-of-root paths.`);
|
|
76869
77664
|
}
|
|
76870
77665
|
function printConnectedAccountsHelp() {
|
|
76871
77666
|
console.log(`Connected account commands:
|
package/dist/cli.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -69,6 +69,16 @@ interface ForceLogoutPayload {
|
|
|
69
69
|
reason?: string;
|
|
70
70
|
revoked_session_id?: string | null;
|
|
71
71
|
}
|
|
72
|
+
interface ProjectRemoteAccessRequestFrame {
|
|
73
|
+
request_id: string;
|
|
74
|
+
project_id: string;
|
|
75
|
+
source_id: string;
|
|
76
|
+
source_session_id: string;
|
|
77
|
+
requesting_client_id: string;
|
|
78
|
+
operation: "list" | "search" | "read_text";
|
|
79
|
+
key_epoch: number;
|
|
80
|
+
encrypted_envelope: string;
|
|
81
|
+
}
|
|
72
82
|
/** Streaming event dispatched for each chunk or lifecycle event. */
|
|
73
83
|
interface StreamEvent {
|
|
74
84
|
/** Event type. */
|
|
@@ -198,6 +208,11 @@ declare class OpenMatesWsClient {
|
|
|
198
208
|
send(type: string, payload: unknown): void;
|
|
199
209
|
sendAsync(type: string, payload: unknown): Promise<void>;
|
|
200
210
|
onLocalConnectorRequest(handler: (payload: LocalConnectorRequestFrame) => void | Promise<void>): () => void;
|
|
211
|
+
onProjectRemoteAccessRequest(handler: (payload: ProjectRemoteAccessRequestFrame) => void | Promise<void>): () => void;
|
|
212
|
+
waitForClose(): Promise<{
|
|
213
|
+
code: number;
|
|
214
|
+
reason: string;
|
|
215
|
+
}>;
|
|
201
216
|
private bufferPassiveTaskUpdateJobs;
|
|
202
217
|
private handleForceLogout;
|
|
203
218
|
drainPassiveTaskUpdateJobs(): PendingTaskUpdateJobFrame[];
|
|
@@ -2173,6 +2188,10 @@ declare class OpenMatesClient {
|
|
|
2173
2188
|
}): Promise<Record<string, unknown>>;
|
|
2174
2189
|
listProjectSources(projectId: string): Promise<ProjectSourceRecord[]>;
|
|
2175
2190
|
createProjectSource(projectId: string, input: ProjectSourceCreateInput): Promise<ProjectSourceRecord>;
|
|
2191
|
+
openProjectRemoteAccessWebSocket(): Promise<{
|
|
2192
|
+
ws: OpenMatesWsClient;
|
|
2193
|
+
ownerId: string;
|
|
2194
|
+
}>;
|
|
2176
2195
|
createProjectItem(projectId: string, input: ProjectItemCreateInput): Promise<ProjectItemRecord>;
|
|
2177
2196
|
deleteProjectItemByTarget(projectId: string, itemType: "embed" | "chat" | "workflow", targetId: string): Promise<{
|
|
2178
2197
|
deleted: boolean;
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openmates",
|
|
3
|
-
"version": "0.15.0-alpha.
|
|
3
|
+
"version": "0.15.0-alpha.99",
|
|
4
4
|
"description": "OpenMates CLI and SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"test:unit:workflows": "node --test --experimental-strip-types --loader ./tests/loader.mjs tests/workflows.test.ts",
|
|
31
31
|
"test:unit:sdk-workflows": "node --test --experimental-strip-types --loader ./tests/loader.mjs tests/sdk-workflows.test.ts",
|
|
32
32
|
"test:unit:remote-access": "node --test --experimental-strip-types --loader ./tests/loader.mjs tests/remoteSources.test.ts tests/remoteAccess.test.ts",
|
|
33
|
+
"test:unit:remote-access-crypto": "node --test --experimental-strip-types --loader ./tests/loader.mjs tests/remoteAccessCrypto.test.ts",
|
|
33
34
|
"test:unit:teams": "node --test --experimental-strip-types --loader ./tests/loader.mjs tests/teams-permissions.test.ts",
|
|
34
35
|
"test:unit:proton-bridge-connector": "node --test --experimental-strip-types --loader ./tests/loader.mjs tests/protonBridgeConnector.test.ts",
|
|
35
36
|
"test:unit:proton-bridge-connector-integration": "node --test --experimental-strip-types --loader ./tests/loader.mjs tests/protonBridgeConnector.integration.test.ts",
|