@prismer/runtime 2.0.0 → 2.0.1
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/CHANGELOG.md +50 -0
- package/dist/cli.cjs +560 -31
- package/dist/cli.js +568 -39
- package/dist/index.cjs +562 -31
- package/dist/index.d.cts +142 -65
- package/dist/index.d.ts +142 -65
- package/dist/index.js +573 -43
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -443,7 +443,7 @@ var require_package = __commonJS({
|
|
|
443
443
|
"package.json"(exports, module) {
|
|
444
444
|
module.exports = {
|
|
445
445
|
name: "@prismer/runtime",
|
|
446
|
-
version: "2.0.
|
|
446
|
+
version: "2.0.1",
|
|
447
447
|
description: "Prismer Cloud daemon runtime \u2014 TS-only adapter host for hosted IM agents",
|
|
448
448
|
type: "module",
|
|
449
449
|
main: "dist/index.js",
|
|
@@ -480,6 +480,7 @@ var require_package = __commonJS({
|
|
|
480
480
|
"better-sqlite3": "^11.8.0",
|
|
481
481
|
commander: "^12.1.0",
|
|
482
482
|
qrcode: "^1.5.4",
|
|
483
|
+
undici: "^7.24.0",
|
|
483
484
|
ws: "^8.19.0",
|
|
484
485
|
yaml: "^2.8.2",
|
|
485
486
|
zod: "^3.23.8"
|
|
@@ -3912,8 +3913,11 @@ import { basename, dirname as dirname7, join as join10, relative } from "path";
|
|
|
3912
3913
|
|
|
3913
3914
|
// src/asset-cache.ts
|
|
3914
3915
|
import { createHash } from "crypto";
|
|
3916
|
+
import { promises as dnsp } from "dns";
|
|
3915
3917
|
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, renameSync, statSync as statSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
3918
|
+
import { isIP, isIPv4, isIPv6 } from "net";
|
|
3916
3919
|
import { dirname as dirname6, join as join7 } from "path";
|
|
3920
|
+
import { Agent as UndiciAgent } from "undici";
|
|
3917
3921
|
function rowToCached(row) {
|
|
3918
3922
|
return {
|
|
3919
3923
|
contentHash: row.content_hash,
|
|
@@ -4034,6 +4038,73 @@ var AssetCache = class {
|
|
|
4034
4038
|
pin: false
|
|
4035
4039
|
};
|
|
4036
4040
|
}
|
|
4041
|
+
/**
|
|
4042
|
+
* Fetch a public http(s) URL, content-hash dedup, cache locally.
|
|
4043
|
+
*
|
|
4044
|
+
* Mirrors `getOrFetch` (cache key = sha256 of body bytes), but the input
|
|
4045
|
+
* is a URL whose hash we don't know up front. The fetcher applies:
|
|
4046
|
+
*
|
|
4047
|
+
* - SSRF guard (resolve hostname, reject loopback / RFC1918 / link-local
|
|
4048
|
+
* / cloud-metadata / IPv6 ULA + link-local). Cross-host redirects
|
|
4049
|
+
* re-validate the new host before each hop.
|
|
4050
|
+
* - manual redirect handling (max 3 hops).
|
|
4051
|
+
* - hard timeout (default 15 s, override via env
|
|
4052
|
+
* `PRISMER_URL_FETCH_TIMEOUT_MS`).
|
|
4053
|
+
* - streaming body read with abort-at-limit (default 5 MiB, override
|
|
4054
|
+
* via env `PRISMER_URL_FETCH_MAX_BYTES`). Truncated bodies are NOT
|
|
4055
|
+
* cached.
|
|
4056
|
+
* - non-2xx → throws (caller should leave the URL in place and emit
|
|
4057
|
+
* an `error` observation).
|
|
4058
|
+
*
|
|
4059
|
+
* On success returns { cached, finalUrl, durationMs }; the caller can
|
|
4060
|
+
* pin / unpin the hash like any other asset.
|
|
4061
|
+
*/
|
|
4062
|
+
async getOrFetchUrl(url, opts) {
|
|
4063
|
+
const started = Date.now();
|
|
4064
|
+
const maxBytes = opts?.maxBytes ?? defaultMaxBytes();
|
|
4065
|
+
const timeoutMs = opts?.timeoutMs ?? defaultTimeoutMs();
|
|
4066
|
+
const maxRedirects = opts?.maxRedirects ?? 3;
|
|
4067
|
+
const userAgent = opts?.userAgent ?? "prismer-daemon";
|
|
4068
|
+
const { body, finalUrl, mime } = await fetchUrlWithGuards(url, {
|
|
4069
|
+
signal: opts?.signal,
|
|
4070
|
+
maxBytes,
|
|
4071
|
+
timeoutMs,
|
|
4072
|
+
maxRedirects,
|
|
4073
|
+
userAgent
|
|
4074
|
+
});
|
|
4075
|
+
const hash = sha256(body);
|
|
4076
|
+
const existing = this.get(hash);
|
|
4077
|
+
if (existing) {
|
|
4078
|
+
return { cached: existing, finalUrl, durationMs: Date.now() - started };
|
|
4079
|
+
}
|
|
4080
|
+
const localPath = this.pathFor(hash);
|
|
4081
|
+
if (!existsSync7(dirname6(localPath))) {
|
|
4082
|
+
mkdirSync5(dirname6(localPath), { recursive: true });
|
|
4083
|
+
}
|
|
4084
|
+
const tmpPath = `${localPath}.${process.pid}.${Date.now()}.tmp`;
|
|
4085
|
+
writeFileSync5(tmpPath, body);
|
|
4086
|
+
renameSync(tmpPath, localPath);
|
|
4087
|
+
const now = Date.now();
|
|
4088
|
+
this.db.prepare(
|
|
4089
|
+
`INSERT OR REPLACE INTO cached_assets
|
|
4090
|
+
(content_hash, size_bytes, mime, local_path, fetched_at, last_used_at, pin)
|
|
4091
|
+
VALUES (?, ?, ?, ?, ?, ?, 0)`
|
|
4092
|
+
).run(hash, body.length, mime, localPath, now, now);
|
|
4093
|
+
this.evictIfOver();
|
|
4094
|
+
return {
|
|
4095
|
+
cached: {
|
|
4096
|
+
contentHash: hash,
|
|
4097
|
+
sizeBytes: body.length,
|
|
4098
|
+
mime,
|
|
4099
|
+
localPath,
|
|
4100
|
+
fetchedAt: now,
|
|
4101
|
+
lastUsedAt: now,
|
|
4102
|
+
pin: false
|
|
4103
|
+
},
|
|
4104
|
+
finalUrl,
|
|
4105
|
+
durationMs: now - started
|
|
4106
|
+
};
|
|
4107
|
+
}
|
|
4037
4108
|
/** Insert an already-on-disk asset into the cache (e.g., asset just produced by adapter). */
|
|
4038
4109
|
registerLocal(hash, localPath, mime) {
|
|
4039
4110
|
const actualHash = sha256(readFileBuffer(localPath));
|
|
@@ -4085,6 +4156,184 @@ function sha256(buf) {
|
|
|
4085
4156
|
function readFileBuffer(path9) {
|
|
4086
4157
|
return readFileSync6(path9);
|
|
4087
4158
|
}
|
|
4159
|
+
var DEFAULT_URL_FETCH_MAX_BYTES = 5 * 1024 * 1024;
|
|
4160
|
+
var DEFAULT_URL_FETCH_TIMEOUT_MS = 15e3;
|
|
4161
|
+
function defaultMaxBytes() {
|
|
4162
|
+
const v = Number(process.env.PRISMER_URL_FETCH_MAX_BYTES);
|
|
4163
|
+
return Number.isFinite(v) && v > 0 ? v : DEFAULT_URL_FETCH_MAX_BYTES;
|
|
4164
|
+
}
|
|
4165
|
+
function defaultTimeoutMs() {
|
|
4166
|
+
const v = Number(process.env.PRISMER_URL_FETCH_TIMEOUT_MS);
|
|
4167
|
+
return Number.isFinite(v) && v > 0 ? v : DEFAULT_URL_FETCH_TIMEOUT_MS;
|
|
4168
|
+
}
|
|
4169
|
+
var urlFetchDeps = {};
|
|
4170
|
+
function activeFetch() {
|
|
4171
|
+
return urlFetchDeps.fetch ?? globalThis.fetch;
|
|
4172
|
+
}
|
|
4173
|
+
async function activeResolveHostname(host) {
|
|
4174
|
+
if (urlFetchDeps.resolveHostname) return urlFetchDeps.resolveHostname(host);
|
|
4175
|
+
if (isIP(host)) return [host];
|
|
4176
|
+
const res = await dnsp.lookup(host, { all: true, verbatim: true });
|
|
4177
|
+
return res.map((r) => r.address);
|
|
4178
|
+
}
|
|
4179
|
+
function isForbiddenIp(ip) {
|
|
4180
|
+
if (isIPv4(ip)) {
|
|
4181
|
+
const parts = ip.split(".").map((n) => Number(n));
|
|
4182
|
+
if (parts.length !== 4 || parts.some((p) => !Number.isFinite(p) || p < 0 || p > 255)) {
|
|
4183
|
+
return true;
|
|
4184
|
+
}
|
|
4185
|
+
const [a = 0, b = 0] = parts;
|
|
4186
|
+
if (a === 127) return true;
|
|
4187
|
+
if (a === 10) return true;
|
|
4188
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
4189
|
+
if (a === 192 && b === 168) return true;
|
|
4190
|
+
if (a === 169 && b === 254) return true;
|
|
4191
|
+
if (a === 0) return true;
|
|
4192
|
+
return false;
|
|
4193
|
+
}
|
|
4194
|
+
if (isIPv6(ip)) {
|
|
4195
|
+
const lower = ip.toLowerCase();
|
|
4196
|
+
if (lower === "::1" || lower === "0:0:0:0:0:0:0:1") return true;
|
|
4197
|
+
if (lower === "::" || lower === "0:0:0:0:0:0:0:0") return true;
|
|
4198
|
+
if (/^fe[89ab][0-9a-f]?:/.test(lower)) return true;
|
|
4199
|
+
if (/^f[cd][0-9a-f]{2}:/.test(lower)) return true;
|
|
4200
|
+
const mapped = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/i.exec(lower);
|
|
4201
|
+
if (mapped) return isForbiddenIp(mapped[1]);
|
|
4202
|
+
return false;
|
|
4203
|
+
}
|
|
4204
|
+
return true;
|
|
4205
|
+
}
|
|
4206
|
+
async function assertHostAllowed(hostname5) {
|
|
4207
|
+
let ips;
|
|
4208
|
+
try {
|
|
4209
|
+
ips = await activeResolveHostname(hostname5);
|
|
4210
|
+
} catch (err) {
|
|
4211
|
+
throw new Error(`hostname resolution failed for ${hostname5}: ${err.message}`);
|
|
4212
|
+
}
|
|
4213
|
+
if (ips.length === 0) {
|
|
4214
|
+
throw new Error(`no IPs resolved for ${hostname5}`);
|
|
4215
|
+
}
|
|
4216
|
+
for (const ip of ips) {
|
|
4217
|
+
if (isForbiddenIp(ip)) {
|
|
4218
|
+
throw new Error(`SSRF guard: ${hostname5} \u2192 ${ip} is in a forbidden range`);
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4221
|
+
return ips[0];
|
|
4222
|
+
}
|
|
4223
|
+
async function fetchUrlWithGuards(rawUrl, opts) {
|
|
4224
|
+
let currentUrl;
|
|
4225
|
+
try {
|
|
4226
|
+
currentUrl = new URL(rawUrl);
|
|
4227
|
+
} catch {
|
|
4228
|
+
throw new Error(`invalid URL: ${rawUrl}`);
|
|
4229
|
+
}
|
|
4230
|
+
for (let hop = 0; hop <= opts.maxRedirects; hop += 1) {
|
|
4231
|
+
if (currentUrl.protocol !== "http:" && currentUrl.protocol !== "https:") {
|
|
4232
|
+
throw new Error(`unsupported scheme: ${currentUrl.protocol}`);
|
|
4233
|
+
}
|
|
4234
|
+
const pinnedIp = await assertHostAllowed(currentUrl.hostname);
|
|
4235
|
+
const controller = new AbortController();
|
|
4236
|
+
const timer = setTimeout(() => controller.abort(new Error("timeout")), opts.timeoutMs);
|
|
4237
|
+
if (opts.signal) {
|
|
4238
|
+
if (opts.signal.aborted) controller.abort(opts.signal.reason);
|
|
4239
|
+
else opts.signal.addEventListener("abort", () => controller.abort(opts.signal.reason), { once: true });
|
|
4240
|
+
}
|
|
4241
|
+
const usingMockedFetch = urlFetchDeps.fetch !== void 0;
|
|
4242
|
+
const dispatcher = usingMockedFetch ? void 0 : new UndiciAgent({
|
|
4243
|
+
connect: {
|
|
4244
|
+
lookup: (_hostname, _options, cb) => {
|
|
4245
|
+
cb(null, pinnedIp, isIPv6(pinnedIp) ? 6 : 4);
|
|
4246
|
+
}
|
|
4247
|
+
}
|
|
4248
|
+
});
|
|
4249
|
+
try {
|
|
4250
|
+
let res;
|
|
4251
|
+
try {
|
|
4252
|
+
res = await activeFetch()(currentUrl, {
|
|
4253
|
+
method: "GET",
|
|
4254
|
+
redirect: "manual",
|
|
4255
|
+
headers: { "User-Agent": opts.userAgent, Accept: "*/*" },
|
|
4256
|
+
signal: controller.signal,
|
|
4257
|
+
// `dispatcher` is undici-specific; Node's built-in fetch (which
|
|
4258
|
+
// IS undici under the hood) accepts it via this option.
|
|
4259
|
+
...dispatcher ? { dispatcher } : {}
|
|
4260
|
+
});
|
|
4261
|
+
} catch (err) {
|
|
4262
|
+
clearTimeout(timer);
|
|
4263
|
+
throw new Error(`fetch failed: ${err.message}`);
|
|
4264
|
+
}
|
|
4265
|
+
if (res.status >= 300 && res.status < 400) {
|
|
4266
|
+
clearTimeout(timer);
|
|
4267
|
+
const loc = res.headers.get("location");
|
|
4268
|
+
if (!loc) throw new Error(`redirect ${res.status} with no Location header`);
|
|
4269
|
+
try {
|
|
4270
|
+
currentUrl = new URL(loc, currentUrl);
|
|
4271
|
+
} catch {
|
|
4272
|
+
throw new Error(`invalid redirect Location: ${loc}`);
|
|
4273
|
+
}
|
|
4274
|
+
try {
|
|
4275
|
+
await res.arrayBuffer();
|
|
4276
|
+
} catch {
|
|
4277
|
+
}
|
|
4278
|
+
if (hop === opts.maxRedirects) {
|
|
4279
|
+
throw new Error(`too many redirects (>${opts.maxRedirects})`);
|
|
4280
|
+
}
|
|
4281
|
+
continue;
|
|
4282
|
+
}
|
|
4283
|
+
if (!res.ok) {
|
|
4284
|
+
clearTimeout(timer);
|
|
4285
|
+
throw new Error(`HTTP ${res.status}`);
|
|
4286
|
+
}
|
|
4287
|
+
const contentLength = Number(res.headers.get("content-length"));
|
|
4288
|
+
if (Number.isFinite(contentLength) && contentLength > opts.maxBytes) {
|
|
4289
|
+
clearTimeout(timer);
|
|
4290
|
+
controller.abort(new Error("content-length exceeds limit"));
|
|
4291
|
+
throw new Error(`body too large: content-length=${contentLength} > ${opts.maxBytes}`);
|
|
4292
|
+
}
|
|
4293
|
+
const chunks = [];
|
|
4294
|
+
let total = 0;
|
|
4295
|
+
const reader = res.body?.getReader();
|
|
4296
|
+
try {
|
|
4297
|
+
if (reader) {
|
|
4298
|
+
for (; ; ) {
|
|
4299
|
+
const { done, value } = await reader.read();
|
|
4300
|
+
if (done) break;
|
|
4301
|
+
if (value) {
|
|
4302
|
+
total += value.byteLength;
|
|
4303
|
+
if (total > opts.maxBytes) {
|
|
4304
|
+
controller.abort(new Error("body exceeds limit"));
|
|
4305
|
+
try {
|
|
4306
|
+
await reader.cancel();
|
|
4307
|
+
} catch {
|
|
4308
|
+
}
|
|
4309
|
+
throw new Error(`body too large: read ${total} > ${opts.maxBytes}`);
|
|
4310
|
+
}
|
|
4311
|
+
chunks.push(value);
|
|
4312
|
+
}
|
|
4313
|
+
}
|
|
4314
|
+
} else {
|
|
4315
|
+
const ab = await res.arrayBuffer();
|
|
4316
|
+
if (ab.byteLength > opts.maxBytes) {
|
|
4317
|
+
throw new Error(`body too large: ${ab.byteLength} > ${opts.maxBytes}`);
|
|
4318
|
+
}
|
|
4319
|
+
chunks.push(new Uint8Array(ab));
|
|
4320
|
+
total = ab.byteLength;
|
|
4321
|
+
}
|
|
4322
|
+
} finally {
|
|
4323
|
+
clearTimeout(timer);
|
|
4324
|
+
}
|
|
4325
|
+
const body = Buffer.concat(chunks.map((c) => Buffer.from(c.buffer, c.byteOffset, c.byteLength)), total);
|
|
4326
|
+
return {
|
|
4327
|
+
body,
|
|
4328
|
+
finalUrl: currentUrl.toString(),
|
|
4329
|
+
mime: res.headers.get("content-type")
|
|
4330
|
+
};
|
|
4331
|
+
} finally {
|
|
4332
|
+
await dispatcher?.close().catch(() => void 0);
|
|
4333
|
+
}
|
|
4334
|
+
}
|
|
4335
|
+
throw new Error(`too many redirects (>${opts.maxRedirects})`);
|
|
4336
|
+
}
|
|
4088
4337
|
|
|
4089
4338
|
// src/daemon/asset/metadata-index.ts
|
|
4090
4339
|
import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
@@ -4446,11 +4695,11 @@ function buildAssetCommand() {
|
|
|
4446
4695
|
}
|
|
4447
4696
|
printAssetDetail("Asset", assetId, body);
|
|
4448
4697
|
}, { code: "asset_get_failed" }));
|
|
4449
|
-
cmd.command("by-hash <sha256>").description("Fetch asset metadata by content hash").option("--workspace-id <id>", "Workspace id (required by the cloud API)").option("--json", "Output machine-readable JSON").action(runAction(async (
|
|
4698
|
+
cmd.command("by-hash <sha256>").description("Fetch asset metadata by content hash").option("--workspace-id <id>", "Workspace id (required by the cloud API)").option("--json", "Output machine-readable JSON").action(runAction(async (sha2563, opts) => {
|
|
4450
4699
|
const cloud = mkCloud();
|
|
4451
4700
|
const query = new URLSearchParams();
|
|
4452
4701
|
if (opts.workspaceId) query.set("wsId", opts.workspaceId);
|
|
4453
|
-
const path9 = `/api/im/assets/by-hash/${encodeURIComponent(
|
|
4702
|
+
const path9 = `/api/im/assets/by-hash/${encodeURIComponent(sha2563)}${query.toString() ? `?${query.toString()}` : ""}`;
|
|
4454
4703
|
const res = await cloud.request("GET", path9);
|
|
4455
4704
|
if (!res.ok) exitWithError(`asset by-hash failed (${res.status}): ${res.error?.message ?? "request failed"}`, { code: res.error?.code ?? "asset_by_hash_failed" });
|
|
4456
4705
|
const body = res.data;
|
|
@@ -4461,7 +4710,7 @@ function buildAssetCommand() {
|
|
|
4461
4710
|
printJson(body ?? null);
|
|
4462
4711
|
return;
|
|
4463
4712
|
}
|
|
4464
|
-
printAssetDetail("Asset hash",
|
|
4713
|
+
printAssetDetail("Asset hash", sha2563, body);
|
|
4465
4714
|
}, { code: "asset_by_hash_failed" }));
|
|
4466
4715
|
return cmd;
|
|
4467
4716
|
}
|
|
@@ -5603,6 +5852,8 @@ var SyncWorker = class extends EventEmitter {
|
|
|
5603
5852
|
// src/uri-resolver.ts
|
|
5604
5853
|
var URI_REGEX_WORKSPACE = /prismer:\/\/workspace\/([^/\s]+)\/(asset|file)\/([^\s)\]]+)/g;
|
|
5605
5854
|
var URI_REGEX_LEGACY = /prismer:\/\/(?!workspace\/)([^/\s]+)\/(asset|file)\/([^\s)\]]+)/g;
|
|
5855
|
+
var HTTP_URL_REGEX = /https?:\/\/[^\s<>"'`]+/g;
|
|
5856
|
+
var URL_TRAILING_PUNCT_RE = /[,.;:!?)\]}'"]+$/;
|
|
5606
5857
|
function parseUris(text) {
|
|
5607
5858
|
if (!text) return [];
|
|
5608
5859
|
const out = [];
|
|
@@ -5665,12 +5916,20 @@ var UriResolver = class {
|
|
|
5665
5916
|
return cached.localPath;
|
|
5666
5917
|
}
|
|
5667
5918
|
/**
|
|
5668
|
-
* Walk a string, replace every `prismer://(asset|file)/...`
|
|
5669
|
-
*
|
|
5919
|
+
* Walk a string, replace every `prismer://(asset|file)/...` and
|
|
5920
|
+
* `https://…` / `http://…` URL with `file://<localPath>`.
|
|
5921
|
+
*
|
|
5922
|
+
* Unrecognized URIs pass through unchanged. Returns rewritten text, the
|
|
5923
|
+
* list of pinned hashes, and one observation per http(s) URL the resolver
|
|
5924
|
+
* attempted (success + error both surface) so the dispatch can include
|
|
5925
|
+
* them in `reply.assetObservability`.
|
|
5926
|
+
*
|
|
5927
|
+
* `urlCache` lets the caller dedupe URL fetches across multiple rewrite
|
|
5928
|
+
* calls within a single dispatch (e.g. prompt + each context entry).
|
|
5929
|
+
* Pass the same Map instance to every rewrite() / rewriteAll() call.
|
|
5670
5930
|
*/
|
|
5671
5931
|
async rewrite(text, opts) {
|
|
5672
5932
|
const uris = parseUris(text);
|
|
5673
|
-
if (uris.length === 0) return { text, resolvedHashes: [] };
|
|
5674
5933
|
const replacements = /* @__PURE__ */ new Map();
|
|
5675
5934
|
const resolvedHashes = [];
|
|
5676
5935
|
for (const u of uris) {
|
|
@@ -5693,6 +5952,46 @@ var UriResolver = class {
|
|
|
5693
5952
|
console.warn(`[uri-resolver] failed to resolve ${u.raw}: ${err.message}`);
|
|
5694
5953
|
}
|
|
5695
5954
|
}
|
|
5955
|
+
if (opts?.fetchUrls !== false) {
|
|
5956
|
+
const urlCache = opts?.urlCache;
|
|
5957
|
+
const urls = extractHttpUrls(text);
|
|
5958
|
+
for (const original of urls) {
|
|
5959
|
+
try {
|
|
5960
|
+
let resolution = urlCache?.get(original);
|
|
5961
|
+
if (!resolution) {
|
|
5962
|
+
const { cached, finalUrl, durationMs } = await this.assetCache.getOrFetchUrl(original, {
|
|
5963
|
+
signal: opts?.signal
|
|
5964
|
+
});
|
|
5965
|
+
resolution = { hash: cached.contentHash, localPath: cached.localPath, sizeBytes: cached.sizeBytes, mime: cached.mime, finalUrl, durationMs };
|
|
5966
|
+
urlCache?.set(original, resolution);
|
|
5967
|
+
opts?.urlObservations?.push({
|
|
5968
|
+
contentHash: cached.contentHash,
|
|
5969
|
+
mime: cached.mime,
|
|
5970
|
+
sizeBytes: cached.sizeBytes,
|
|
5971
|
+
strategy: "fetched-https",
|
|
5972
|
+
originalUrl: original,
|
|
5973
|
+
finalUrl,
|
|
5974
|
+
durationMs
|
|
5975
|
+
});
|
|
5976
|
+
}
|
|
5977
|
+
replacements.set(original, `file://${resolution.localPath}`);
|
|
5978
|
+
resolvedHashes.push(resolution.hash);
|
|
5979
|
+
if (opts?.pin) this.assetCache.pin(resolution.hash);
|
|
5980
|
+
} catch (err) {
|
|
5981
|
+
const message = err.message;
|
|
5982
|
+
console.warn(`[uri-resolver] failed to fetch ${original}: ${message}`);
|
|
5983
|
+
opts?.urlObservations?.push({
|
|
5984
|
+
contentHash: "",
|
|
5985
|
+
mime: null,
|
|
5986
|
+
sizeBytes: null,
|
|
5987
|
+
strategy: "error",
|
|
5988
|
+
originalUrl: original,
|
|
5989
|
+
error: message
|
|
5990
|
+
});
|
|
5991
|
+
}
|
|
5992
|
+
}
|
|
5993
|
+
}
|
|
5994
|
+
if (replacements.size === 0) return { text, resolvedHashes };
|
|
5696
5995
|
let rewritten = text;
|
|
5697
5996
|
for (const [raw, sub] of replacements) {
|
|
5698
5997
|
rewritten = rewritten.split(raw).join(sub);
|
|
@@ -5730,6 +6029,27 @@ var UriResolver = class {
|
|
|
5730
6029
|
function dedupe(xs) {
|
|
5731
6030
|
return Array.from(new Set(xs));
|
|
5732
6031
|
}
|
|
6032
|
+
function extractHttpUrls(text) {
|
|
6033
|
+
if (!text) return [];
|
|
6034
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6035
|
+
const out = [];
|
|
6036
|
+
for (const m of text.matchAll(HTTP_URL_REGEX)) {
|
|
6037
|
+
let url = m[0];
|
|
6038
|
+
const punct = URL_TRAILING_PUNCT_RE.exec(url);
|
|
6039
|
+
if (punct) url = url.slice(0, -punct[0].length);
|
|
6040
|
+
if (!url) continue;
|
|
6041
|
+
try {
|
|
6042
|
+
const parsed = new URL(url);
|
|
6043
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") continue;
|
|
6044
|
+
} catch {
|
|
6045
|
+
continue;
|
|
6046
|
+
}
|
|
6047
|
+
if (seen.has(url)) continue;
|
|
6048
|
+
seen.add(url);
|
|
6049
|
+
out.push(url);
|
|
6050
|
+
}
|
|
6051
|
+
return out;
|
|
6052
|
+
}
|
|
5733
6053
|
|
|
5734
6054
|
// src/lib/logger.ts
|
|
5735
6055
|
var LEVEL_ORDER = {
|
|
@@ -5801,7 +6121,7 @@ import * as path2 from "path";
|
|
|
5801
6121
|
|
|
5802
6122
|
// src/daemon/skill-sync.ts
|
|
5803
6123
|
import { promises as fsp2 } from "fs";
|
|
5804
|
-
import { join as join11 } from "path";
|
|
6124
|
+
import { dirname as dirname8, join as join11, relative as relative2, sep } from "path";
|
|
5805
6125
|
import { createHash as createHash3 } from "crypto";
|
|
5806
6126
|
async function syncInstalledSkillsForDispatch(profile, agentImUserId, cloud, signal) {
|
|
5807
6127
|
if (!agentImUserId) return { synced: 0, skipped: 0, unchanged: 0 };
|
|
@@ -5818,29 +6138,141 @@ async function syncInstalledSkillsForDispatch(profile, agentImUserId, cloud, sig
|
|
|
5818
6138
|
for (const entry of entries) {
|
|
5819
6139
|
const slug = sanitizeSlug(trimmedStringFrom(entry.skill?.slug) ?? trimmedStringFrom(entry.slug));
|
|
5820
6140
|
const skillId = trimmedStringFrom(entry.skill?.id);
|
|
5821
|
-
|
|
5822
|
-
if (!slug || !content) {
|
|
6141
|
+
if (!slug) {
|
|
5823
6142
|
skipped++;
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
6143
|
+
continue;
|
|
6144
|
+
}
|
|
6145
|
+
let files = null;
|
|
6146
|
+
const manifestRaw = entry.skill?.contentManifest;
|
|
6147
|
+
if (typeof manifestRaw === "string" && manifestRaw.trim()) {
|
|
6148
|
+
files = parseManifest(manifestRaw, slug);
|
|
6149
|
+
}
|
|
6150
|
+
if (!files) {
|
|
6151
|
+
const legacyContent = contentStringFrom(entry.skill?.content) ?? contentStringFrom(entry.content);
|
|
6152
|
+
if (legacyContent) {
|
|
6153
|
+
const buf = Buffer.from(legacyContent, "utf8");
|
|
6154
|
+
files = [
|
|
6155
|
+
{
|
|
6156
|
+
path: "SKILL.md",
|
|
6157
|
+
size: buf.byteLength,
|
|
6158
|
+
sha256: sha256Buffer(buf),
|
|
6159
|
+
inline: true,
|
|
6160
|
+
content: buf.toString("base64")
|
|
6161
|
+
}
|
|
6162
|
+
];
|
|
5828
6163
|
}
|
|
6164
|
+
}
|
|
6165
|
+
if (!files || files.length === 0) {
|
|
6166
|
+
skipped++;
|
|
6167
|
+
process.stderr.write(`[daemon] skill sync skipped ${slug}: no manifest or content
|
|
6168
|
+
`);
|
|
6169
|
+
await ackSkillSync(cloud, agentImUserId, { skillId, slug, error: "missing content" }, signal);
|
|
5829
6170
|
continue;
|
|
5830
6171
|
}
|
|
5831
|
-
const revision = sha2562(content);
|
|
5832
6172
|
const skillDir = join11(skillsRoot, slug);
|
|
5833
6173
|
await fsp2.mkdir(skillDir, { recursive: true });
|
|
5834
|
-
const
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
6174
|
+
const localFiles = await walkLocalDir(skillDir);
|
|
6175
|
+
let dirty = false;
|
|
6176
|
+
let perFileFailures = 0;
|
|
6177
|
+
for (const file of files) {
|
|
6178
|
+
if (!isSafeRelativePath(file.path)) {
|
|
6179
|
+
process.stderr.write(`[daemon] skill sync ${slug}: skipping suspicious path "${file.path}"
|
|
6180
|
+
`);
|
|
6181
|
+
perFileFailures++;
|
|
6182
|
+
continue;
|
|
6183
|
+
}
|
|
6184
|
+
const targetPath = join11(skillDir, file.path);
|
|
6185
|
+
const normalizedTarget = targetPath + (targetPath.endsWith(sep) ? "" : "");
|
|
6186
|
+
if (normalizedTarget !== skillDir && !normalizedTarget.startsWith(skillDir + sep)) {
|
|
6187
|
+
process.stderr.write(`[daemon] skill sync ${slug}: path escapes skillDir "${file.path}"
|
|
6188
|
+
`);
|
|
6189
|
+
perFileFailures++;
|
|
6190
|
+
continue;
|
|
6191
|
+
}
|
|
6192
|
+
const existingHash = localFiles.get(file.path);
|
|
6193
|
+
if (existingHash === file.sha256) {
|
|
6194
|
+
localFiles.delete(file.path);
|
|
6195
|
+
continue;
|
|
6196
|
+
}
|
|
6197
|
+
let bytes = null;
|
|
6198
|
+
try {
|
|
6199
|
+
if (file.inline !== false && typeof file.content === "string") {
|
|
6200
|
+
bytes = Buffer.from(file.content, "base64");
|
|
6201
|
+
} else if (typeof file.url === "string" && file.url) {
|
|
6202
|
+
bytes = await downloadUrl(file.url, signal);
|
|
6203
|
+
} else {
|
|
6204
|
+
process.stderr.write(
|
|
6205
|
+
`[daemon] skill sync ${slug}: file ${file.path} has neither inline content nor url
|
|
6206
|
+
`
|
|
6207
|
+
);
|
|
6208
|
+
perFileFailures++;
|
|
6209
|
+
continue;
|
|
6210
|
+
}
|
|
6211
|
+
} catch (err) {
|
|
6212
|
+
process.stderr.write(
|
|
6213
|
+
`[daemon] skill sync ${slug}: failed to fetch ${file.path}: ${err.message}
|
|
6214
|
+
`
|
|
6215
|
+
);
|
|
6216
|
+
perFileFailures++;
|
|
6217
|
+
continue;
|
|
6218
|
+
}
|
|
6219
|
+
const downloadedHash = sha256Buffer(bytes);
|
|
6220
|
+
if (downloadedHash !== file.sha256) {
|
|
6221
|
+
process.stderr.write(
|
|
6222
|
+
`[daemon] skill sync ${slug}: hash mismatch for ${file.path} (expected ${file.sha256}, got ${downloadedHash})
|
|
6223
|
+
`
|
|
6224
|
+
);
|
|
6225
|
+
perFileFailures++;
|
|
6226
|
+
continue;
|
|
6227
|
+
}
|
|
6228
|
+
await fsp2.mkdir(dirname8(targetPath), { recursive: true });
|
|
6229
|
+
await fsp2.writeFile(targetPath, bytes);
|
|
6230
|
+
localFiles.delete(file.path);
|
|
6231
|
+
dirty = true;
|
|
6232
|
+
}
|
|
6233
|
+
for (const orphan of localFiles.keys()) {
|
|
6234
|
+
try {
|
|
6235
|
+
await fsp2.unlink(join11(skillDir, orphan));
|
|
6236
|
+
dirty = true;
|
|
6237
|
+
} catch (err) {
|
|
6238
|
+
if (err.code !== "ENOENT") {
|
|
6239
|
+
process.stderr.write(
|
|
6240
|
+
`[daemon] skill sync ${slug}: failed to remove orphan ${orphan}: ${err.message}
|
|
6241
|
+
`
|
|
6242
|
+
);
|
|
6243
|
+
}
|
|
6244
|
+
}
|
|
6245
|
+
}
|
|
6246
|
+
if (perFileFailures > 0) {
|
|
6247
|
+
skipped++;
|
|
6248
|
+
await ackSkillSync(
|
|
6249
|
+
cloud,
|
|
6250
|
+
agentImUserId,
|
|
6251
|
+
{ skillId, slug, error: `${perFileFailures} file(s) failed` },
|
|
6252
|
+
signal
|
|
6253
|
+
);
|
|
5839
6254
|
continue;
|
|
5840
6255
|
}
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
6256
|
+
const localMerkle = computeMerkle(files);
|
|
6257
|
+
const declaredRevision = trimmedStringFrom(entry.skill?.contentManifestRevision);
|
|
6258
|
+
const revision = declaredRevision ?? localMerkle;
|
|
6259
|
+
if (declaredRevision && declaredRevision !== localMerkle) {
|
|
6260
|
+
process.stderr.write(
|
|
6261
|
+
`[daemon] skill sync ${slug}: declared revision ${declaredRevision} != computed ${localMerkle}; using computed
|
|
6262
|
+
`
|
|
6263
|
+
);
|
|
6264
|
+
}
|
|
6265
|
+
if (dirty) {
|
|
6266
|
+
synced++;
|
|
6267
|
+
} else {
|
|
6268
|
+
unchanged++;
|
|
6269
|
+
}
|
|
6270
|
+
await ackSkillSync(
|
|
6271
|
+
cloud,
|
|
6272
|
+
agentImUserId,
|
|
6273
|
+
{ skillId, slug, revision: declaredRevision === localMerkle ? declaredRevision : localMerkle },
|
|
6274
|
+
signal
|
|
6275
|
+
);
|
|
5844
6276
|
}
|
|
5845
6277
|
return { synced, skipped, unchanged };
|
|
5846
6278
|
}
|
|
@@ -5858,9 +6290,96 @@ function resolveSkillsRoot(profile) {
|
|
|
5858
6290
|
}
|
|
5859
6291
|
return null;
|
|
5860
6292
|
}
|
|
5861
|
-
function
|
|
6293
|
+
function sha256Buffer(value) {
|
|
5862
6294
|
return createHash3("sha256").update(value).digest("hex");
|
|
5863
6295
|
}
|
|
6296
|
+
function computeMerkle(files) {
|
|
6297
|
+
const sorted = [...files].sort((a, b) => a.path.localeCompare(b.path));
|
|
6298
|
+
const lines = sorted.map((f) => `${f.path}:${f.sha256}`).join("\n");
|
|
6299
|
+
return createHash3("sha256").update(lines).digest("hex");
|
|
6300
|
+
}
|
|
6301
|
+
function parseManifest(raw, slug) {
|
|
6302
|
+
try {
|
|
6303
|
+
const parsed = JSON.parse(raw);
|
|
6304
|
+
const arr = Array.isArray(parsed) ? parsed : parsed && typeof parsed === "object" && Array.isArray(parsed.files) ? parsed.files : null;
|
|
6305
|
+
if (!Array.isArray(arr)) {
|
|
6306
|
+
process.stderr.write(`[daemon] skill sync ${slug}: contentManifest is not an array
|
|
6307
|
+
`);
|
|
6308
|
+
return null;
|
|
6309
|
+
}
|
|
6310
|
+
const files = [];
|
|
6311
|
+
for (const item of arr) {
|
|
6312
|
+
if (!item || typeof item !== "object") continue;
|
|
6313
|
+
const rec = item;
|
|
6314
|
+
const path9 = typeof rec.path === "string" ? rec.path : null;
|
|
6315
|
+
const sha2563 = typeof rec.sha256 === "string" ? rec.sha256 : null;
|
|
6316
|
+
const size = typeof rec.size === "number" && Number.isFinite(rec.size) && rec.size >= 0 ? Math.floor(rec.size) : null;
|
|
6317
|
+
if (!path9 || !sha2563 || size === null) continue;
|
|
6318
|
+
const content = typeof rec.content === "string" ? rec.content : void 0;
|
|
6319
|
+
const url = typeof rec.url === "string" ? rec.url : void 0;
|
|
6320
|
+
const inline = typeof rec.inline === "boolean" ? rec.inline : content !== void 0 ? true : void 0;
|
|
6321
|
+
files.push({ path: path9, size, sha256: sha2563, inline, content, url });
|
|
6322
|
+
}
|
|
6323
|
+
return files;
|
|
6324
|
+
} catch (err) {
|
|
6325
|
+
process.stderr.write(
|
|
6326
|
+
`[daemon] skill sync ${slug}: invalid contentManifest JSON: ${err.message}
|
|
6327
|
+
`
|
|
6328
|
+
);
|
|
6329
|
+
return null;
|
|
6330
|
+
}
|
|
6331
|
+
}
|
|
6332
|
+
function isSafeRelativePath(p) {
|
|
6333
|
+
if (typeof p !== "string" || !p) return false;
|
|
6334
|
+
if (p.length > 512) return false;
|
|
6335
|
+
if (p.includes("\0")) return false;
|
|
6336
|
+
if (p.startsWith("/") || p.startsWith("\\")) return false;
|
|
6337
|
+
if (/^[A-Za-z]:[\\/]/.test(p)) return false;
|
|
6338
|
+
const segs = p.replace(/\\/g, "/").split("/");
|
|
6339
|
+
for (const seg of segs) {
|
|
6340
|
+
if (seg === "" || seg === "." || seg === "..") return false;
|
|
6341
|
+
}
|
|
6342
|
+
return true;
|
|
6343
|
+
}
|
|
6344
|
+
async function walkLocalDir(root) {
|
|
6345
|
+
const out = /* @__PURE__ */ new Map();
|
|
6346
|
+
async function walk2(dir) {
|
|
6347
|
+
let entries;
|
|
6348
|
+
try {
|
|
6349
|
+
entries = await fsp2.readdir(dir, { withFileTypes: true });
|
|
6350
|
+
} catch (err) {
|
|
6351
|
+
if (err.code === "ENOENT") return;
|
|
6352
|
+
throw err;
|
|
6353
|
+
}
|
|
6354
|
+
for (const ent of entries) {
|
|
6355
|
+
const full = join11(dir, ent.name);
|
|
6356
|
+
if (ent.isDirectory()) {
|
|
6357
|
+
await walk2(full);
|
|
6358
|
+
} else if (ent.isFile()) {
|
|
6359
|
+
try {
|
|
6360
|
+
const buf = await fsp2.readFile(full);
|
|
6361
|
+
const rel = relative2(root, full).split(sep).join("/");
|
|
6362
|
+
out.set(rel, sha256Buffer(buf));
|
|
6363
|
+
} catch {
|
|
6364
|
+
}
|
|
6365
|
+
}
|
|
6366
|
+
}
|
|
6367
|
+
}
|
|
6368
|
+
await walk2(root);
|
|
6369
|
+
return out;
|
|
6370
|
+
}
|
|
6371
|
+
async function downloadUrl(url, signal) {
|
|
6372
|
+
const lower = url.toLowerCase();
|
|
6373
|
+
if (!lower.startsWith("http://") && !lower.startsWith("https://")) {
|
|
6374
|
+
throw new Error(`unsupported url scheme: ${url.slice(0, 32)}`);
|
|
6375
|
+
}
|
|
6376
|
+
const res = await fetch(url, { signal });
|
|
6377
|
+
if (!res.ok) {
|
|
6378
|
+
throw new Error(`fetch ${url} failed: HTTP ${res.status}`);
|
|
6379
|
+
}
|
|
6380
|
+
const ab = await res.arrayBuffer();
|
|
6381
|
+
return Buffer.from(ab);
|
|
6382
|
+
}
|
|
5864
6383
|
function normalizeInstalledSkills(data) {
|
|
5865
6384
|
if (Array.isArray(data)) return data.filter(isInstalledSkillEntry);
|
|
5866
6385
|
if (!data || typeof data !== "object" || Array.isArray(data)) return [];
|
|
@@ -5980,12 +6499,22 @@ async function handleDispatch(payload, requestId, deps) {
|
|
|
5980
6499
|
payload.prompt = hashRefResult.text;
|
|
5981
6500
|
}
|
|
5982
6501
|
}
|
|
5983
|
-
const
|
|
6502
|
+
const urlCache = /* @__PURE__ */ new Map();
|
|
6503
|
+
const urlObservations = [];
|
|
6504
|
+
const rewrittenPrompt = await deps.uriResolver.rewrite(payload.prompt, {
|
|
6505
|
+
pin: true,
|
|
6506
|
+
urlCache,
|
|
6507
|
+
urlObservations
|
|
6508
|
+
});
|
|
5984
6509
|
resolvedHashes.push(...rewrittenPrompt.resolvedHashes);
|
|
5985
6510
|
let rewrittenContext = [];
|
|
5986
6511
|
if (payload.context && payload.context.length > 0) {
|
|
5987
6512
|
const contents = payload.context.map((e) => e.content);
|
|
5988
|
-
const r = await deps.uriResolver.rewriteAll(contents, {
|
|
6513
|
+
const r = await deps.uriResolver.rewriteAll(contents, {
|
|
6514
|
+
pin: true,
|
|
6515
|
+
urlCache,
|
|
6516
|
+
urlObservations
|
|
6517
|
+
});
|
|
5989
6518
|
resolvedHashes.push(...r.resolvedHashes);
|
|
5990
6519
|
rewrittenContext = payload.context.map((e, i) => ({ ...e, content: r.texts[i] }));
|
|
5991
6520
|
}
|
|
@@ -6105,7 +6634,7 @@ async function handleDispatch(payload, requestId, deps) {
|
|
|
6105
6634
|
} : result.error,
|
|
6106
6635
|
...collectedAssetIds.length > 0 ? { assetIds: collectedAssetIds } : {},
|
|
6107
6636
|
metrics: result.metrics,
|
|
6108
|
-
...assetResolution.observability.length > 0 ? { assetObservability: assetResolution.observability } : {}
|
|
6637
|
+
...assetResolution.observability.length > 0 || urlObservations.length > 0 ? { assetObservability: [...assetResolution.observability, ...urlObservations] } : {}
|
|
6109
6638
|
};
|
|
6110
6639
|
await writeBridgeMetadata(payload.taskId, deps.cloud, result.metadata, deps.signal);
|
|
6111
6640
|
await writeObservabilityMetadata(
|
|
@@ -7272,10 +7801,10 @@ async function walkAndDigest(root, current) {
|
|
|
7272
7801
|
}
|
|
7273
7802
|
if (!st.isFile()) continue;
|
|
7274
7803
|
const buf = await fs2.readFile(full);
|
|
7275
|
-
const
|
|
7804
|
+
const sha2563 = createHash4("sha256").update(buf).digest("hex");
|
|
7276
7805
|
out.push({
|
|
7277
7806
|
path: rel,
|
|
7278
|
-
sha256:
|
|
7807
|
+
sha256: sha2563,
|
|
7279
7808
|
sizeBytes: st.size,
|
|
7280
7809
|
mtime: Math.floor(st.mtimeMs)
|
|
7281
7810
|
});
|
|
@@ -7482,7 +8011,7 @@ var MemoryStore = class {
|
|
|
7482
8011
|
}
|
|
7483
8012
|
const db = this.requireDb();
|
|
7484
8013
|
const now = Date.now();
|
|
7485
|
-
const contentHash =
|
|
8014
|
+
const contentHash = sha2562(input.content);
|
|
7486
8015
|
const payload = sealPlaintext(input.content);
|
|
7487
8016
|
if (payload.kind !== "inline") {
|
|
7488
8017
|
throw new Error("MemoryStore.write: non-inline payload not yet supported in phase-0");
|
|
@@ -7676,7 +8205,7 @@ var MemoryStore = class {
|
|
|
7676
8205
|
};
|
|
7677
8206
|
}
|
|
7678
8207
|
};
|
|
7679
|
-
function
|
|
8208
|
+
function sha2562(s) {
|
|
7680
8209
|
return createHash5("sha256").update(s, "utf8").digest("hex");
|
|
7681
8210
|
}
|
|
7682
8211
|
|
|
@@ -12752,7 +13281,7 @@ import { Command as Command13 } from "commander";
|
|
|
12752
13281
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
12753
13282
|
import { existsSync as existsSync18, mkdirSync as mkdirSync10, rmSync, renameSync as renameSync2 } from "fs";
|
|
12754
13283
|
import { homedir as homedir9 } from "os";
|
|
12755
|
-
import { dirname as
|
|
13284
|
+
import { dirname as dirname11, join as join21 } from "path";
|
|
12756
13285
|
import { setTimeout as sleep4 } from "timers/promises";
|
|
12757
13286
|
init_util();
|
|
12758
13287
|
init_ui();
|
|
@@ -12806,8 +13335,8 @@ function buildResetCommand() {
|
|
|
12806
13335
|
rmSync(paths.root, { recursive: true, force: true });
|
|
12807
13336
|
} else {
|
|
12808
13337
|
if (!archivePath) throw new Error("internal: archivePath missing");
|
|
12809
|
-
if (!existsSync18(
|
|
12810
|
-
mkdirSync10(
|
|
13338
|
+
if (!existsSync18(dirname11(archivePath))) {
|
|
13339
|
+
mkdirSync10(dirname11(archivePath), { recursive: true });
|
|
12811
13340
|
}
|
|
12812
13341
|
renameSync2(paths.root, archivePath);
|
|
12813
13342
|
}
|
|
@@ -12817,8 +13346,8 @@ function buildResetCommand() {
|
|
|
12817
13346
|
rmSync(assetSyncRoot, { recursive: true, force: true });
|
|
12818
13347
|
} else {
|
|
12819
13348
|
if (!assetSyncArchivePath) throw new Error("internal: assetSyncArchivePath missing");
|
|
12820
|
-
if (!existsSync18(
|
|
12821
|
-
mkdirSync10(
|
|
13349
|
+
if (!existsSync18(dirname11(assetSyncArchivePath))) {
|
|
13350
|
+
mkdirSync10(dirname11(assetSyncArchivePath), { recursive: true });
|
|
12822
13351
|
}
|
|
12823
13352
|
renameSync2(assetSyncRoot, assetSyncArchivePath);
|
|
12824
13353
|
}
|
|
@@ -12828,8 +13357,8 @@ function buildResetCommand() {
|
|
|
12828
13357
|
rmSync(hermesHome, { recursive: true, force: true });
|
|
12829
13358
|
} else {
|
|
12830
13359
|
if (!hermesArchivePath) throw new Error("internal: hermesArchivePath missing");
|
|
12831
|
-
if (!existsSync18(
|
|
12832
|
-
mkdirSync10(
|
|
13360
|
+
if (!existsSync18(dirname11(hermesArchivePath))) {
|
|
13361
|
+
mkdirSync10(dirname11(hermesArchivePath), { recursive: true });
|
|
12833
13362
|
}
|
|
12834
13363
|
renameSync2(hermesHome, hermesArchivePath);
|
|
12835
13364
|
}
|
|
@@ -14161,7 +14690,7 @@ async function readResponseError(res) {
|
|
|
14161
14690
|
|
|
14162
14691
|
// src/cli/index.ts
|
|
14163
14692
|
init_ui();
|
|
14164
|
-
var VERSION = "2.0.
|
|
14693
|
+
var VERSION = "2.0.1";
|
|
14165
14694
|
function buildProgram() {
|
|
14166
14695
|
const program = new Command20("prismer").description("Prismer Cloud daemon CLI (TS-only).").version(VERSION);
|
|
14167
14696
|
program.addCommand(buildBannerCommand());
|