@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/index.js
CHANGED
|
@@ -442,7 +442,7 @@ var require_package = __commonJS({
|
|
|
442
442
|
"package.json"(exports, module) {
|
|
443
443
|
module.exports = {
|
|
444
444
|
name: "@prismer/runtime",
|
|
445
|
-
version: "2.0.
|
|
445
|
+
version: "2.0.1",
|
|
446
446
|
description: "Prismer Cloud daemon runtime \u2014 TS-only adapter host for hosted IM agents",
|
|
447
447
|
type: "module",
|
|
448
448
|
main: "dist/index.js",
|
|
@@ -479,6 +479,7 @@ var require_package = __commonJS({
|
|
|
479
479
|
"better-sqlite3": "^11.8.0",
|
|
480
480
|
commander: "^12.1.0",
|
|
481
481
|
qrcode: "^1.5.4",
|
|
482
|
+
undici: "^7.24.0",
|
|
482
483
|
ws: "^8.19.0",
|
|
483
484
|
yaml: "^2.8.2",
|
|
484
485
|
zod: "^3.23.8"
|
|
@@ -2873,8 +2874,11 @@ function envelope(type, payload, requestId) {
|
|
|
2873
2874
|
|
|
2874
2875
|
// src/asset-cache.ts
|
|
2875
2876
|
import { createHash } from "crypto";
|
|
2877
|
+
import { promises as dnsp } from "dns";
|
|
2876
2878
|
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync3, renameSync, statSync, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
2879
|
+
import { isIP, isIPv4, isIPv6 } from "net";
|
|
2877
2880
|
import { dirname as dirname4, join as join5 } from "path";
|
|
2881
|
+
import { Agent as UndiciAgent } from "undici";
|
|
2878
2882
|
function rowToCached(row) {
|
|
2879
2883
|
return {
|
|
2880
2884
|
contentHash: row.content_hash,
|
|
@@ -2995,6 +2999,73 @@ var AssetCache = class {
|
|
|
2995
2999
|
pin: false
|
|
2996
3000
|
};
|
|
2997
3001
|
}
|
|
3002
|
+
/**
|
|
3003
|
+
* Fetch a public http(s) URL, content-hash dedup, cache locally.
|
|
3004
|
+
*
|
|
3005
|
+
* Mirrors `getOrFetch` (cache key = sha256 of body bytes), but the input
|
|
3006
|
+
* is a URL whose hash we don't know up front. The fetcher applies:
|
|
3007
|
+
*
|
|
3008
|
+
* - SSRF guard (resolve hostname, reject loopback / RFC1918 / link-local
|
|
3009
|
+
* / cloud-metadata / IPv6 ULA + link-local). Cross-host redirects
|
|
3010
|
+
* re-validate the new host before each hop.
|
|
3011
|
+
* - manual redirect handling (max 3 hops).
|
|
3012
|
+
* - hard timeout (default 15 s, override via env
|
|
3013
|
+
* `PRISMER_URL_FETCH_TIMEOUT_MS`).
|
|
3014
|
+
* - streaming body read with abort-at-limit (default 5 MiB, override
|
|
3015
|
+
* via env `PRISMER_URL_FETCH_MAX_BYTES`). Truncated bodies are NOT
|
|
3016
|
+
* cached.
|
|
3017
|
+
* - non-2xx → throws (caller should leave the URL in place and emit
|
|
3018
|
+
* an `error` observation).
|
|
3019
|
+
*
|
|
3020
|
+
* On success returns { cached, finalUrl, durationMs }; the caller can
|
|
3021
|
+
* pin / unpin the hash like any other asset.
|
|
3022
|
+
*/
|
|
3023
|
+
async getOrFetchUrl(url, opts) {
|
|
3024
|
+
const started = Date.now();
|
|
3025
|
+
const maxBytes = opts?.maxBytes ?? defaultMaxBytes();
|
|
3026
|
+
const timeoutMs = opts?.timeoutMs ?? defaultTimeoutMs();
|
|
3027
|
+
const maxRedirects = opts?.maxRedirects ?? 3;
|
|
3028
|
+
const userAgent = opts?.userAgent ?? "prismer-daemon";
|
|
3029
|
+
const { body, finalUrl, mime } = await fetchUrlWithGuards(url, {
|
|
3030
|
+
signal: opts?.signal,
|
|
3031
|
+
maxBytes,
|
|
3032
|
+
timeoutMs,
|
|
3033
|
+
maxRedirects,
|
|
3034
|
+
userAgent
|
|
3035
|
+
});
|
|
3036
|
+
const hash = sha256(body);
|
|
3037
|
+
const existing = this.get(hash);
|
|
3038
|
+
if (existing) {
|
|
3039
|
+
return { cached: existing, finalUrl, durationMs: Date.now() - started };
|
|
3040
|
+
}
|
|
3041
|
+
const localPath = this.pathFor(hash);
|
|
3042
|
+
if (!existsSync4(dirname4(localPath))) {
|
|
3043
|
+
mkdirSync4(dirname4(localPath), { recursive: true });
|
|
3044
|
+
}
|
|
3045
|
+
const tmpPath = `${localPath}.${process.pid}.${Date.now()}.tmp`;
|
|
3046
|
+
writeFileSync3(tmpPath, body);
|
|
3047
|
+
renameSync(tmpPath, localPath);
|
|
3048
|
+
const now = Date.now();
|
|
3049
|
+
this.db.prepare(
|
|
3050
|
+
`INSERT OR REPLACE INTO cached_assets
|
|
3051
|
+
(content_hash, size_bytes, mime, local_path, fetched_at, last_used_at, pin)
|
|
3052
|
+
VALUES (?, ?, ?, ?, ?, ?, 0)`
|
|
3053
|
+
).run(hash, body.length, mime, localPath, now, now);
|
|
3054
|
+
this.evictIfOver();
|
|
3055
|
+
return {
|
|
3056
|
+
cached: {
|
|
3057
|
+
contentHash: hash,
|
|
3058
|
+
sizeBytes: body.length,
|
|
3059
|
+
mime,
|
|
3060
|
+
localPath,
|
|
3061
|
+
fetchedAt: now,
|
|
3062
|
+
lastUsedAt: now,
|
|
3063
|
+
pin: false
|
|
3064
|
+
},
|
|
3065
|
+
finalUrl,
|
|
3066
|
+
durationMs: now - started
|
|
3067
|
+
};
|
|
3068
|
+
}
|
|
2998
3069
|
/** Insert an already-on-disk asset into the cache (e.g., asset just produced by adapter). */
|
|
2999
3070
|
registerLocal(hash, localPath, mime) {
|
|
3000
3071
|
const actualHash = sha256(readFileBuffer(localPath));
|
|
@@ -3046,6 +3117,184 @@ function sha256(buf) {
|
|
|
3046
3117
|
function readFileBuffer(path9) {
|
|
3047
3118
|
return readFileSync3(path9);
|
|
3048
3119
|
}
|
|
3120
|
+
var DEFAULT_URL_FETCH_MAX_BYTES = 5 * 1024 * 1024;
|
|
3121
|
+
var DEFAULT_URL_FETCH_TIMEOUT_MS = 15e3;
|
|
3122
|
+
function defaultMaxBytes() {
|
|
3123
|
+
const v = Number(process.env.PRISMER_URL_FETCH_MAX_BYTES);
|
|
3124
|
+
return Number.isFinite(v) && v > 0 ? v : DEFAULT_URL_FETCH_MAX_BYTES;
|
|
3125
|
+
}
|
|
3126
|
+
function defaultTimeoutMs() {
|
|
3127
|
+
const v = Number(process.env.PRISMER_URL_FETCH_TIMEOUT_MS);
|
|
3128
|
+
return Number.isFinite(v) && v > 0 ? v : DEFAULT_URL_FETCH_TIMEOUT_MS;
|
|
3129
|
+
}
|
|
3130
|
+
var urlFetchDeps = {};
|
|
3131
|
+
function activeFetch() {
|
|
3132
|
+
return urlFetchDeps.fetch ?? globalThis.fetch;
|
|
3133
|
+
}
|
|
3134
|
+
async function activeResolveHostname(host) {
|
|
3135
|
+
if (urlFetchDeps.resolveHostname) return urlFetchDeps.resolveHostname(host);
|
|
3136
|
+
if (isIP(host)) return [host];
|
|
3137
|
+
const res = await dnsp.lookup(host, { all: true, verbatim: true });
|
|
3138
|
+
return res.map((r) => r.address);
|
|
3139
|
+
}
|
|
3140
|
+
function isForbiddenIp(ip) {
|
|
3141
|
+
if (isIPv4(ip)) {
|
|
3142
|
+
const parts = ip.split(".").map((n) => Number(n));
|
|
3143
|
+
if (parts.length !== 4 || parts.some((p) => !Number.isFinite(p) || p < 0 || p > 255)) {
|
|
3144
|
+
return true;
|
|
3145
|
+
}
|
|
3146
|
+
const [a = 0, b = 0] = parts;
|
|
3147
|
+
if (a === 127) return true;
|
|
3148
|
+
if (a === 10) return true;
|
|
3149
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
3150
|
+
if (a === 192 && b === 168) return true;
|
|
3151
|
+
if (a === 169 && b === 254) return true;
|
|
3152
|
+
if (a === 0) return true;
|
|
3153
|
+
return false;
|
|
3154
|
+
}
|
|
3155
|
+
if (isIPv6(ip)) {
|
|
3156
|
+
const lower = ip.toLowerCase();
|
|
3157
|
+
if (lower === "::1" || lower === "0:0:0:0:0:0:0:1") return true;
|
|
3158
|
+
if (lower === "::" || lower === "0:0:0:0:0:0:0:0") return true;
|
|
3159
|
+
if (/^fe[89ab][0-9a-f]?:/.test(lower)) return true;
|
|
3160
|
+
if (/^f[cd][0-9a-f]{2}:/.test(lower)) return true;
|
|
3161
|
+
const mapped = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/i.exec(lower);
|
|
3162
|
+
if (mapped) return isForbiddenIp(mapped[1]);
|
|
3163
|
+
return false;
|
|
3164
|
+
}
|
|
3165
|
+
return true;
|
|
3166
|
+
}
|
|
3167
|
+
async function assertHostAllowed(hostname5) {
|
|
3168
|
+
let ips;
|
|
3169
|
+
try {
|
|
3170
|
+
ips = await activeResolveHostname(hostname5);
|
|
3171
|
+
} catch (err) {
|
|
3172
|
+
throw new Error(`hostname resolution failed for ${hostname5}: ${err.message}`);
|
|
3173
|
+
}
|
|
3174
|
+
if (ips.length === 0) {
|
|
3175
|
+
throw new Error(`no IPs resolved for ${hostname5}`);
|
|
3176
|
+
}
|
|
3177
|
+
for (const ip of ips) {
|
|
3178
|
+
if (isForbiddenIp(ip)) {
|
|
3179
|
+
throw new Error(`SSRF guard: ${hostname5} \u2192 ${ip} is in a forbidden range`);
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
return ips[0];
|
|
3183
|
+
}
|
|
3184
|
+
async function fetchUrlWithGuards(rawUrl, opts) {
|
|
3185
|
+
let currentUrl;
|
|
3186
|
+
try {
|
|
3187
|
+
currentUrl = new URL(rawUrl);
|
|
3188
|
+
} catch {
|
|
3189
|
+
throw new Error(`invalid URL: ${rawUrl}`);
|
|
3190
|
+
}
|
|
3191
|
+
for (let hop = 0; hop <= opts.maxRedirects; hop += 1) {
|
|
3192
|
+
if (currentUrl.protocol !== "http:" && currentUrl.protocol !== "https:") {
|
|
3193
|
+
throw new Error(`unsupported scheme: ${currentUrl.protocol}`);
|
|
3194
|
+
}
|
|
3195
|
+
const pinnedIp = await assertHostAllowed(currentUrl.hostname);
|
|
3196
|
+
const controller = new AbortController();
|
|
3197
|
+
const timer = setTimeout(() => controller.abort(new Error("timeout")), opts.timeoutMs);
|
|
3198
|
+
if (opts.signal) {
|
|
3199
|
+
if (opts.signal.aborted) controller.abort(opts.signal.reason);
|
|
3200
|
+
else opts.signal.addEventListener("abort", () => controller.abort(opts.signal.reason), { once: true });
|
|
3201
|
+
}
|
|
3202
|
+
const usingMockedFetch = urlFetchDeps.fetch !== void 0;
|
|
3203
|
+
const dispatcher = usingMockedFetch ? void 0 : new UndiciAgent({
|
|
3204
|
+
connect: {
|
|
3205
|
+
lookup: (_hostname, _options, cb) => {
|
|
3206
|
+
cb(null, pinnedIp, isIPv6(pinnedIp) ? 6 : 4);
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
});
|
|
3210
|
+
try {
|
|
3211
|
+
let res;
|
|
3212
|
+
try {
|
|
3213
|
+
res = await activeFetch()(currentUrl, {
|
|
3214
|
+
method: "GET",
|
|
3215
|
+
redirect: "manual",
|
|
3216
|
+
headers: { "User-Agent": opts.userAgent, Accept: "*/*" },
|
|
3217
|
+
signal: controller.signal,
|
|
3218
|
+
// `dispatcher` is undici-specific; Node's built-in fetch (which
|
|
3219
|
+
// IS undici under the hood) accepts it via this option.
|
|
3220
|
+
...dispatcher ? { dispatcher } : {}
|
|
3221
|
+
});
|
|
3222
|
+
} catch (err) {
|
|
3223
|
+
clearTimeout(timer);
|
|
3224
|
+
throw new Error(`fetch failed: ${err.message}`);
|
|
3225
|
+
}
|
|
3226
|
+
if (res.status >= 300 && res.status < 400) {
|
|
3227
|
+
clearTimeout(timer);
|
|
3228
|
+
const loc = res.headers.get("location");
|
|
3229
|
+
if (!loc) throw new Error(`redirect ${res.status} with no Location header`);
|
|
3230
|
+
try {
|
|
3231
|
+
currentUrl = new URL(loc, currentUrl);
|
|
3232
|
+
} catch {
|
|
3233
|
+
throw new Error(`invalid redirect Location: ${loc}`);
|
|
3234
|
+
}
|
|
3235
|
+
try {
|
|
3236
|
+
await res.arrayBuffer();
|
|
3237
|
+
} catch {
|
|
3238
|
+
}
|
|
3239
|
+
if (hop === opts.maxRedirects) {
|
|
3240
|
+
throw new Error(`too many redirects (>${opts.maxRedirects})`);
|
|
3241
|
+
}
|
|
3242
|
+
continue;
|
|
3243
|
+
}
|
|
3244
|
+
if (!res.ok) {
|
|
3245
|
+
clearTimeout(timer);
|
|
3246
|
+
throw new Error(`HTTP ${res.status}`);
|
|
3247
|
+
}
|
|
3248
|
+
const contentLength = Number(res.headers.get("content-length"));
|
|
3249
|
+
if (Number.isFinite(contentLength) && contentLength > opts.maxBytes) {
|
|
3250
|
+
clearTimeout(timer);
|
|
3251
|
+
controller.abort(new Error("content-length exceeds limit"));
|
|
3252
|
+
throw new Error(`body too large: content-length=${contentLength} > ${opts.maxBytes}`);
|
|
3253
|
+
}
|
|
3254
|
+
const chunks = [];
|
|
3255
|
+
let total = 0;
|
|
3256
|
+
const reader = res.body?.getReader();
|
|
3257
|
+
try {
|
|
3258
|
+
if (reader) {
|
|
3259
|
+
for (; ; ) {
|
|
3260
|
+
const { done, value } = await reader.read();
|
|
3261
|
+
if (done) break;
|
|
3262
|
+
if (value) {
|
|
3263
|
+
total += value.byteLength;
|
|
3264
|
+
if (total > opts.maxBytes) {
|
|
3265
|
+
controller.abort(new Error("body exceeds limit"));
|
|
3266
|
+
try {
|
|
3267
|
+
await reader.cancel();
|
|
3268
|
+
} catch {
|
|
3269
|
+
}
|
|
3270
|
+
throw new Error(`body too large: read ${total} > ${opts.maxBytes}`);
|
|
3271
|
+
}
|
|
3272
|
+
chunks.push(value);
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3275
|
+
} else {
|
|
3276
|
+
const ab = await res.arrayBuffer();
|
|
3277
|
+
if (ab.byteLength > opts.maxBytes) {
|
|
3278
|
+
throw new Error(`body too large: ${ab.byteLength} > ${opts.maxBytes}`);
|
|
3279
|
+
}
|
|
3280
|
+
chunks.push(new Uint8Array(ab));
|
|
3281
|
+
total = ab.byteLength;
|
|
3282
|
+
}
|
|
3283
|
+
} finally {
|
|
3284
|
+
clearTimeout(timer);
|
|
3285
|
+
}
|
|
3286
|
+
const body = Buffer.concat(chunks.map((c) => Buffer.from(c.buffer, c.byteOffset, c.byteLength)), total);
|
|
3287
|
+
return {
|
|
3288
|
+
body,
|
|
3289
|
+
finalUrl: currentUrl.toString(),
|
|
3290
|
+
mime: res.headers.get("content-type")
|
|
3291
|
+
};
|
|
3292
|
+
} finally {
|
|
3293
|
+
await dispatcher?.close().catch(() => void 0);
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
throw new Error(`too many redirects (>${opts.maxRedirects})`);
|
|
3297
|
+
}
|
|
3049
3298
|
|
|
3050
3299
|
// src/daemon/asset/mirror.ts
|
|
3051
3300
|
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
@@ -3298,6 +3547,8 @@ var ParseClaimController = class {
|
|
|
3298
3547
|
// src/uri-resolver.ts
|
|
3299
3548
|
var URI_REGEX_WORKSPACE = /prismer:\/\/workspace\/([^/\s]+)\/(asset|file)\/([^\s)\]]+)/g;
|
|
3300
3549
|
var URI_REGEX_LEGACY = /prismer:\/\/(?!workspace\/)([^/\s]+)\/(asset|file)\/([^\s)\]]+)/g;
|
|
3550
|
+
var HTTP_URL_REGEX = /https?:\/\/[^\s<>"'`]+/g;
|
|
3551
|
+
var URL_TRAILING_PUNCT_RE = /[,.;:!?)\]}'"]+$/;
|
|
3301
3552
|
function parseUris(text) {
|
|
3302
3553
|
if (!text) return [];
|
|
3303
3554
|
const out = [];
|
|
@@ -3360,12 +3611,20 @@ var UriResolver = class {
|
|
|
3360
3611
|
return cached.localPath;
|
|
3361
3612
|
}
|
|
3362
3613
|
/**
|
|
3363
|
-
* Walk a string, replace every `prismer://(asset|file)/...`
|
|
3364
|
-
*
|
|
3614
|
+
* Walk a string, replace every `prismer://(asset|file)/...` and
|
|
3615
|
+
* `https://…` / `http://…` URL with `file://<localPath>`.
|
|
3616
|
+
*
|
|
3617
|
+
* Unrecognized URIs pass through unchanged. Returns rewritten text, the
|
|
3618
|
+
* list of pinned hashes, and one observation per http(s) URL the resolver
|
|
3619
|
+
* attempted (success + error both surface) so the dispatch can include
|
|
3620
|
+
* them in `reply.assetObservability`.
|
|
3621
|
+
*
|
|
3622
|
+
* `urlCache` lets the caller dedupe URL fetches across multiple rewrite
|
|
3623
|
+
* calls within a single dispatch (e.g. prompt + each context entry).
|
|
3624
|
+
* Pass the same Map instance to every rewrite() / rewriteAll() call.
|
|
3365
3625
|
*/
|
|
3366
3626
|
async rewrite(text, opts) {
|
|
3367
3627
|
const uris = parseUris(text);
|
|
3368
|
-
if (uris.length === 0) return { text, resolvedHashes: [] };
|
|
3369
3628
|
const replacements = /* @__PURE__ */ new Map();
|
|
3370
3629
|
const resolvedHashes = [];
|
|
3371
3630
|
for (const u of uris) {
|
|
@@ -3388,6 +3647,46 @@ var UriResolver = class {
|
|
|
3388
3647
|
console.warn(`[uri-resolver] failed to resolve ${u.raw}: ${err.message}`);
|
|
3389
3648
|
}
|
|
3390
3649
|
}
|
|
3650
|
+
if (opts?.fetchUrls !== false) {
|
|
3651
|
+
const urlCache = opts?.urlCache;
|
|
3652
|
+
const urls = extractHttpUrls(text);
|
|
3653
|
+
for (const original of urls) {
|
|
3654
|
+
try {
|
|
3655
|
+
let resolution = urlCache?.get(original);
|
|
3656
|
+
if (!resolution) {
|
|
3657
|
+
const { cached, finalUrl, durationMs } = await this.assetCache.getOrFetchUrl(original, {
|
|
3658
|
+
signal: opts?.signal
|
|
3659
|
+
});
|
|
3660
|
+
resolution = { hash: cached.contentHash, localPath: cached.localPath, sizeBytes: cached.sizeBytes, mime: cached.mime, finalUrl, durationMs };
|
|
3661
|
+
urlCache?.set(original, resolution);
|
|
3662
|
+
opts?.urlObservations?.push({
|
|
3663
|
+
contentHash: cached.contentHash,
|
|
3664
|
+
mime: cached.mime,
|
|
3665
|
+
sizeBytes: cached.sizeBytes,
|
|
3666
|
+
strategy: "fetched-https",
|
|
3667
|
+
originalUrl: original,
|
|
3668
|
+
finalUrl,
|
|
3669
|
+
durationMs
|
|
3670
|
+
});
|
|
3671
|
+
}
|
|
3672
|
+
replacements.set(original, `file://${resolution.localPath}`);
|
|
3673
|
+
resolvedHashes.push(resolution.hash);
|
|
3674
|
+
if (opts?.pin) this.assetCache.pin(resolution.hash);
|
|
3675
|
+
} catch (err) {
|
|
3676
|
+
const message = err.message;
|
|
3677
|
+
console.warn(`[uri-resolver] failed to fetch ${original}: ${message}`);
|
|
3678
|
+
opts?.urlObservations?.push({
|
|
3679
|
+
contentHash: "",
|
|
3680
|
+
mime: null,
|
|
3681
|
+
sizeBytes: null,
|
|
3682
|
+
strategy: "error",
|
|
3683
|
+
originalUrl: original,
|
|
3684
|
+
error: message
|
|
3685
|
+
});
|
|
3686
|
+
}
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3689
|
+
if (replacements.size === 0) return { text, resolvedHashes };
|
|
3391
3690
|
let rewritten = text;
|
|
3392
3691
|
for (const [raw, sub] of replacements) {
|
|
3393
3692
|
rewritten = rewritten.split(raw).join(sub);
|
|
@@ -3425,6 +3724,27 @@ var UriResolver = class {
|
|
|
3425
3724
|
function dedupe(xs) {
|
|
3426
3725
|
return Array.from(new Set(xs));
|
|
3427
3726
|
}
|
|
3727
|
+
function extractHttpUrls(text) {
|
|
3728
|
+
if (!text) return [];
|
|
3729
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3730
|
+
const out = [];
|
|
3731
|
+
for (const m of text.matchAll(HTTP_URL_REGEX)) {
|
|
3732
|
+
let url = m[0];
|
|
3733
|
+
const punct = URL_TRAILING_PUNCT_RE.exec(url);
|
|
3734
|
+
if (punct) url = url.slice(0, -punct[0].length);
|
|
3735
|
+
if (!url) continue;
|
|
3736
|
+
try {
|
|
3737
|
+
const parsed = new URL(url);
|
|
3738
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") continue;
|
|
3739
|
+
} catch {
|
|
3740
|
+
continue;
|
|
3741
|
+
}
|
|
3742
|
+
if (seen.has(url)) continue;
|
|
3743
|
+
seen.add(url);
|
|
3744
|
+
out.push(url);
|
|
3745
|
+
}
|
|
3746
|
+
return out;
|
|
3747
|
+
}
|
|
3428
3748
|
|
|
3429
3749
|
// src/daemon/dispatch.ts
|
|
3430
3750
|
import { readFileSync as readFileSync5, promises as fsp3 } from "fs";
|
|
@@ -3432,7 +3752,7 @@ import * as path from "path";
|
|
|
3432
3752
|
|
|
3433
3753
|
// src/daemon/skill-sync.ts
|
|
3434
3754
|
import { promises as fsp2 } from "fs";
|
|
3435
|
-
import { join as join7 } from "path";
|
|
3755
|
+
import { dirname as dirname5, join as join7, relative, sep } from "path";
|
|
3436
3756
|
import { createHash as createHash2 } from "crypto";
|
|
3437
3757
|
async function syncInstalledSkillsForDispatch(profile, agentImUserId, cloud, signal) {
|
|
3438
3758
|
if (!agentImUserId) return { synced: 0, skipped: 0, unchanged: 0 };
|
|
@@ -3449,29 +3769,141 @@ async function syncInstalledSkillsForDispatch(profile, agentImUserId, cloud, sig
|
|
|
3449
3769
|
for (const entry of entries) {
|
|
3450
3770
|
const slug = sanitizeSlug(trimmedStringFrom(entry.skill?.slug) ?? trimmedStringFrom(entry.slug));
|
|
3451
3771
|
const skillId = trimmedStringFrom(entry.skill?.id);
|
|
3452
|
-
|
|
3453
|
-
if (!slug || !content) {
|
|
3772
|
+
if (!slug) {
|
|
3454
3773
|
skipped++;
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3774
|
+
continue;
|
|
3775
|
+
}
|
|
3776
|
+
let files = null;
|
|
3777
|
+
const manifestRaw = entry.skill?.contentManifest;
|
|
3778
|
+
if (typeof manifestRaw === "string" && manifestRaw.trim()) {
|
|
3779
|
+
files = parseManifest(manifestRaw, slug);
|
|
3780
|
+
}
|
|
3781
|
+
if (!files) {
|
|
3782
|
+
const legacyContent = contentStringFrom(entry.skill?.content) ?? contentStringFrom(entry.content);
|
|
3783
|
+
if (legacyContent) {
|
|
3784
|
+
const buf = Buffer.from(legacyContent, "utf8");
|
|
3785
|
+
files = [
|
|
3786
|
+
{
|
|
3787
|
+
path: "SKILL.md",
|
|
3788
|
+
size: buf.byteLength,
|
|
3789
|
+
sha256: sha256Buffer(buf),
|
|
3790
|
+
inline: true,
|
|
3791
|
+
content: buf.toString("base64")
|
|
3792
|
+
}
|
|
3793
|
+
];
|
|
3459
3794
|
}
|
|
3795
|
+
}
|
|
3796
|
+
if (!files || files.length === 0) {
|
|
3797
|
+
skipped++;
|
|
3798
|
+
process.stderr.write(`[daemon] skill sync skipped ${slug}: no manifest or content
|
|
3799
|
+
`);
|
|
3800
|
+
await ackSkillSync(cloud, agentImUserId, { skillId, slug, error: "missing content" }, signal);
|
|
3460
3801
|
continue;
|
|
3461
3802
|
}
|
|
3462
|
-
const revision = sha2562(content);
|
|
3463
3803
|
const skillDir = join7(skillsRoot, slug);
|
|
3464
3804
|
await fsp2.mkdir(skillDir, { recursive: true });
|
|
3465
|
-
const
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3805
|
+
const localFiles = await walkLocalDir(skillDir);
|
|
3806
|
+
let dirty = false;
|
|
3807
|
+
let perFileFailures = 0;
|
|
3808
|
+
for (const file of files) {
|
|
3809
|
+
if (!isSafeRelativePath(file.path)) {
|
|
3810
|
+
process.stderr.write(`[daemon] skill sync ${slug}: skipping suspicious path "${file.path}"
|
|
3811
|
+
`);
|
|
3812
|
+
perFileFailures++;
|
|
3813
|
+
continue;
|
|
3814
|
+
}
|
|
3815
|
+
const targetPath = join7(skillDir, file.path);
|
|
3816
|
+
const normalizedTarget = targetPath + (targetPath.endsWith(sep) ? "" : "");
|
|
3817
|
+
if (normalizedTarget !== skillDir && !normalizedTarget.startsWith(skillDir + sep)) {
|
|
3818
|
+
process.stderr.write(`[daemon] skill sync ${slug}: path escapes skillDir "${file.path}"
|
|
3819
|
+
`);
|
|
3820
|
+
perFileFailures++;
|
|
3821
|
+
continue;
|
|
3822
|
+
}
|
|
3823
|
+
const existingHash = localFiles.get(file.path);
|
|
3824
|
+
if (existingHash === file.sha256) {
|
|
3825
|
+
localFiles.delete(file.path);
|
|
3826
|
+
continue;
|
|
3827
|
+
}
|
|
3828
|
+
let bytes = null;
|
|
3829
|
+
try {
|
|
3830
|
+
if (file.inline !== false && typeof file.content === "string") {
|
|
3831
|
+
bytes = Buffer.from(file.content, "base64");
|
|
3832
|
+
} else if (typeof file.url === "string" && file.url) {
|
|
3833
|
+
bytes = await downloadUrl(file.url, signal);
|
|
3834
|
+
} else {
|
|
3835
|
+
process.stderr.write(
|
|
3836
|
+
`[daemon] skill sync ${slug}: file ${file.path} has neither inline content nor url
|
|
3837
|
+
`
|
|
3838
|
+
);
|
|
3839
|
+
perFileFailures++;
|
|
3840
|
+
continue;
|
|
3841
|
+
}
|
|
3842
|
+
} catch (err) {
|
|
3843
|
+
process.stderr.write(
|
|
3844
|
+
`[daemon] skill sync ${slug}: failed to fetch ${file.path}: ${err.message}
|
|
3845
|
+
`
|
|
3846
|
+
);
|
|
3847
|
+
perFileFailures++;
|
|
3848
|
+
continue;
|
|
3849
|
+
}
|
|
3850
|
+
const downloadedHash = sha256Buffer(bytes);
|
|
3851
|
+
if (downloadedHash !== file.sha256) {
|
|
3852
|
+
process.stderr.write(
|
|
3853
|
+
`[daemon] skill sync ${slug}: hash mismatch for ${file.path} (expected ${file.sha256}, got ${downloadedHash})
|
|
3854
|
+
`
|
|
3855
|
+
);
|
|
3856
|
+
perFileFailures++;
|
|
3857
|
+
continue;
|
|
3858
|
+
}
|
|
3859
|
+
await fsp2.mkdir(dirname5(targetPath), { recursive: true });
|
|
3860
|
+
await fsp2.writeFile(targetPath, bytes);
|
|
3861
|
+
localFiles.delete(file.path);
|
|
3862
|
+
dirty = true;
|
|
3863
|
+
}
|
|
3864
|
+
for (const orphan of localFiles.keys()) {
|
|
3865
|
+
try {
|
|
3866
|
+
await fsp2.unlink(join7(skillDir, orphan));
|
|
3867
|
+
dirty = true;
|
|
3868
|
+
} catch (err) {
|
|
3869
|
+
if (err.code !== "ENOENT") {
|
|
3870
|
+
process.stderr.write(
|
|
3871
|
+
`[daemon] skill sync ${slug}: failed to remove orphan ${orphan}: ${err.message}
|
|
3872
|
+
`
|
|
3873
|
+
);
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
if (perFileFailures > 0) {
|
|
3878
|
+
skipped++;
|
|
3879
|
+
await ackSkillSync(
|
|
3880
|
+
cloud,
|
|
3881
|
+
agentImUserId,
|
|
3882
|
+
{ skillId, slug, error: `${perFileFailures} file(s) failed` },
|
|
3883
|
+
signal
|
|
3884
|
+
);
|
|
3470
3885
|
continue;
|
|
3471
3886
|
}
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3887
|
+
const localMerkle = computeMerkle(files);
|
|
3888
|
+
const declaredRevision = trimmedStringFrom(entry.skill?.contentManifestRevision);
|
|
3889
|
+
const revision = declaredRevision ?? localMerkle;
|
|
3890
|
+
if (declaredRevision && declaredRevision !== localMerkle) {
|
|
3891
|
+
process.stderr.write(
|
|
3892
|
+
`[daemon] skill sync ${slug}: declared revision ${declaredRevision} != computed ${localMerkle}; using computed
|
|
3893
|
+
`
|
|
3894
|
+
);
|
|
3895
|
+
}
|
|
3896
|
+
if (dirty) {
|
|
3897
|
+
synced++;
|
|
3898
|
+
} else {
|
|
3899
|
+
unchanged++;
|
|
3900
|
+
}
|
|
3901
|
+
await ackSkillSync(
|
|
3902
|
+
cloud,
|
|
3903
|
+
agentImUserId,
|
|
3904
|
+
{ skillId, slug, revision: declaredRevision === localMerkle ? declaredRevision : localMerkle },
|
|
3905
|
+
signal
|
|
3906
|
+
);
|
|
3475
3907
|
}
|
|
3476
3908
|
return { synced, skipped, unchanged };
|
|
3477
3909
|
}
|
|
@@ -3489,9 +3921,96 @@ function resolveSkillsRoot(profile) {
|
|
|
3489
3921
|
}
|
|
3490
3922
|
return null;
|
|
3491
3923
|
}
|
|
3492
|
-
function
|
|
3924
|
+
function sha256Buffer(value) {
|
|
3493
3925
|
return createHash2("sha256").update(value).digest("hex");
|
|
3494
3926
|
}
|
|
3927
|
+
function computeMerkle(files) {
|
|
3928
|
+
const sorted = [...files].sort((a, b) => a.path.localeCompare(b.path));
|
|
3929
|
+
const lines = sorted.map((f) => `${f.path}:${f.sha256}`).join("\n");
|
|
3930
|
+
return createHash2("sha256").update(lines).digest("hex");
|
|
3931
|
+
}
|
|
3932
|
+
function parseManifest(raw, slug) {
|
|
3933
|
+
try {
|
|
3934
|
+
const parsed = JSON.parse(raw);
|
|
3935
|
+
const arr = Array.isArray(parsed) ? parsed : parsed && typeof parsed === "object" && Array.isArray(parsed.files) ? parsed.files : null;
|
|
3936
|
+
if (!Array.isArray(arr)) {
|
|
3937
|
+
process.stderr.write(`[daemon] skill sync ${slug}: contentManifest is not an array
|
|
3938
|
+
`);
|
|
3939
|
+
return null;
|
|
3940
|
+
}
|
|
3941
|
+
const files = [];
|
|
3942
|
+
for (const item of arr) {
|
|
3943
|
+
if (!item || typeof item !== "object") continue;
|
|
3944
|
+
const rec = item;
|
|
3945
|
+
const path9 = typeof rec.path === "string" ? rec.path : null;
|
|
3946
|
+
const sha2563 = typeof rec.sha256 === "string" ? rec.sha256 : null;
|
|
3947
|
+
const size = typeof rec.size === "number" && Number.isFinite(rec.size) && rec.size >= 0 ? Math.floor(rec.size) : null;
|
|
3948
|
+
if (!path9 || !sha2563 || size === null) continue;
|
|
3949
|
+
const content = typeof rec.content === "string" ? rec.content : void 0;
|
|
3950
|
+
const url = typeof rec.url === "string" ? rec.url : void 0;
|
|
3951
|
+
const inline = typeof rec.inline === "boolean" ? rec.inline : content !== void 0 ? true : void 0;
|
|
3952
|
+
files.push({ path: path9, size, sha256: sha2563, inline, content, url });
|
|
3953
|
+
}
|
|
3954
|
+
return files;
|
|
3955
|
+
} catch (err) {
|
|
3956
|
+
process.stderr.write(
|
|
3957
|
+
`[daemon] skill sync ${slug}: invalid contentManifest JSON: ${err.message}
|
|
3958
|
+
`
|
|
3959
|
+
);
|
|
3960
|
+
return null;
|
|
3961
|
+
}
|
|
3962
|
+
}
|
|
3963
|
+
function isSafeRelativePath(p) {
|
|
3964
|
+
if (typeof p !== "string" || !p) return false;
|
|
3965
|
+
if (p.length > 512) return false;
|
|
3966
|
+
if (p.includes("\0")) return false;
|
|
3967
|
+
if (p.startsWith("/") || p.startsWith("\\")) return false;
|
|
3968
|
+
if (/^[A-Za-z]:[\\/]/.test(p)) return false;
|
|
3969
|
+
const segs = p.replace(/\\/g, "/").split("/");
|
|
3970
|
+
for (const seg of segs) {
|
|
3971
|
+
if (seg === "" || seg === "." || seg === "..") return false;
|
|
3972
|
+
}
|
|
3973
|
+
return true;
|
|
3974
|
+
}
|
|
3975
|
+
async function walkLocalDir(root) {
|
|
3976
|
+
const out = /* @__PURE__ */ new Map();
|
|
3977
|
+
async function walk2(dir) {
|
|
3978
|
+
let entries;
|
|
3979
|
+
try {
|
|
3980
|
+
entries = await fsp2.readdir(dir, { withFileTypes: true });
|
|
3981
|
+
} catch (err) {
|
|
3982
|
+
if (err.code === "ENOENT") return;
|
|
3983
|
+
throw err;
|
|
3984
|
+
}
|
|
3985
|
+
for (const ent of entries) {
|
|
3986
|
+
const full = join7(dir, ent.name);
|
|
3987
|
+
if (ent.isDirectory()) {
|
|
3988
|
+
await walk2(full);
|
|
3989
|
+
} else if (ent.isFile()) {
|
|
3990
|
+
try {
|
|
3991
|
+
const buf = await fsp2.readFile(full);
|
|
3992
|
+
const rel = relative(root, full).split(sep).join("/");
|
|
3993
|
+
out.set(rel, sha256Buffer(buf));
|
|
3994
|
+
} catch {
|
|
3995
|
+
}
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
await walk2(root);
|
|
4000
|
+
return out;
|
|
4001
|
+
}
|
|
4002
|
+
async function downloadUrl(url, signal) {
|
|
4003
|
+
const lower = url.toLowerCase();
|
|
4004
|
+
if (!lower.startsWith("http://") && !lower.startsWith("https://")) {
|
|
4005
|
+
throw new Error(`unsupported url scheme: ${url.slice(0, 32)}`);
|
|
4006
|
+
}
|
|
4007
|
+
const res = await fetch(url, { signal });
|
|
4008
|
+
if (!res.ok) {
|
|
4009
|
+
throw new Error(`fetch ${url} failed: HTTP ${res.status}`);
|
|
4010
|
+
}
|
|
4011
|
+
const ab = await res.arrayBuffer();
|
|
4012
|
+
return Buffer.from(ab);
|
|
4013
|
+
}
|
|
3495
4014
|
function normalizeInstalledSkills(data) {
|
|
3496
4015
|
if (Array.isArray(data)) return data.filter(isInstalledSkillEntry);
|
|
3497
4016
|
if (!data || typeof data !== "object" || Array.isArray(data)) return [];
|
|
@@ -3611,12 +4130,22 @@ async function handleDispatch(payload, requestId, deps) {
|
|
|
3611
4130
|
payload.prompt = hashRefResult.text;
|
|
3612
4131
|
}
|
|
3613
4132
|
}
|
|
3614
|
-
const
|
|
4133
|
+
const urlCache = /* @__PURE__ */ new Map();
|
|
4134
|
+
const urlObservations = [];
|
|
4135
|
+
const rewrittenPrompt = await deps.uriResolver.rewrite(payload.prompt, {
|
|
4136
|
+
pin: true,
|
|
4137
|
+
urlCache,
|
|
4138
|
+
urlObservations
|
|
4139
|
+
});
|
|
3615
4140
|
resolvedHashes.push(...rewrittenPrompt.resolvedHashes);
|
|
3616
4141
|
let rewrittenContext = [];
|
|
3617
4142
|
if (payload.context && payload.context.length > 0) {
|
|
3618
4143
|
const contents = payload.context.map((e) => e.content);
|
|
3619
|
-
const r = await deps.uriResolver.rewriteAll(contents, {
|
|
4144
|
+
const r = await deps.uriResolver.rewriteAll(contents, {
|
|
4145
|
+
pin: true,
|
|
4146
|
+
urlCache,
|
|
4147
|
+
urlObservations
|
|
4148
|
+
});
|
|
3620
4149
|
resolvedHashes.push(...r.resolvedHashes);
|
|
3621
4150
|
rewrittenContext = payload.context.map((e, i) => ({ ...e, content: r.texts[i] }));
|
|
3622
4151
|
}
|
|
@@ -3736,7 +4265,7 @@ async function handleDispatch(payload, requestId, deps) {
|
|
|
3736
4265
|
} : result.error,
|
|
3737
4266
|
...collectedAssetIds.length > 0 ? { assetIds: collectedAssetIds } : {},
|
|
3738
4267
|
metrics: result.metrics,
|
|
3739
|
-
...assetResolution.observability.length > 0 ? { assetObservability: assetResolution.observability } : {}
|
|
4268
|
+
...assetResolution.observability.length > 0 || urlObservations.length > 0 ? { assetObservability: [...assetResolution.observability, ...urlObservations] } : {}
|
|
3740
4269
|
};
|
|
3741
4270
|
await writeBridgeMetadata(payload.taskId, deps.cloud, result.metadata, deps.signal);
|
|
3742
4271
|
await writeObservabilityMetadata(
|
|
@@ -4947,10 +5476,10 @@ async function walkAndDigest(root, current) {
|
|
|
4947
5476
|
}
|
|
4948
5477
|
if (!st.isFile()) continue;
|
|
4949
5478
|
const buf = await fs.readFile(full);
|
|
4950
|
-
const
|
|
5479
|
+
const sha2563 = createHash3("sha256").update(buf).digest("hex");
|
|
4951
5480
|
out.push({
|
|
4952
5481
|
path: rel,
|
|
4953
|
-
sha256:
|
|
5482
|
+
sha256: sha2563,
|
|
4954
5483
|
sizeBytes: st.size,
|
|
4955
5484
|
mtime: Math.floor(st.mtimeMs)
|
|
4956
5485
|
});
|
|
@@ -5715,7 +6244,7 @@ var MemoryStore = class {
|
|
|
5715
6244
|
}
|
|
5716
6245
|
const db = this.requireDb();
|
|
5717
6246
|
const now = Date.now();
|
|
5718
|
-
const contentHash =
|
|
6247
|
+
const contentHash = sha2562(input.content);
|
|
5719
6248
|
const payload = sealPlaintext(input.content);
|
|
5720
6249
|
if (payload.kind !== "inline") {
|
|
5721
6250
|
throw new Error("MemoryStore.write: non-inline payload not yet supported in phase-0");
|
|
@@ -5909,7 +6438,7 @@ var MemoryStore = class {
|
|
|
5909
6438
|
};
|
|
5910
6439
|
}
|
|
5911
6440
|
};
|
|
5912
|
-
function
|
|
6441
|
+
function sha2562(s) {
|
|
5913
6442
|
return createHash4("sha256").update(s, "utf8").digest("hex");
|
|
5914
6443
|
}
|
|
5915
6444
|
|
|
@@ -10174,7 +10703,7 @@ import {
|
|
|
10174
10703
|
writeFileSync as writeFileSync7
|
|
10175
10704
|
} from "fs";
|
|
10176
10705
|
import { homedir as homedir7 } from "os";
|
|
10177
|
-
import { dirname as
|
|
10706
|
+
import { dirname as dirname9, join as join16 } from "path";
|
|
10178
10707
|
import { Command } from "commander";
|
|
10179
10708
|
init_util();
|
|
10180
10709
|
init_ui();
|
|
@@ -10434,7 +10963,7 @@ function runHooks(spec, opts) {
|
|
|
10434
10963
|
backup = backupIfExists(markerPath);
|
|
10435
10964
|
writeFileSync7(markerPath, JSON.stringify(prismerHookMarker(spec), null, 2) + "\n", "utf8");
|
|
10436
10965
|
} else {
|
|
10437
|
-
mkdirSync8(
|
|
10966
|
+
mkdirSync8(dirname9(planned.path), { recursive: true });
|
|
10438
10967
|
backup = backupIfExists(planned.path);
|
|
10439
10968
|
const merged = mergeHookJson(planned.path, spec);
|
|
10440
10969
|
writeFileSync7(planned.path, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
@@ -11104,7 +11633,7 @@ function whichBinary2(bin) {
|
|
|
11104
11633
|
import { createHash as createHash7 } from "crypto";
|
|
11105
11634
|
import { Command as Command3 } from "commander";
|
|
11106
11635
|
import { existsSync as existsSync13, lstatSync, readdirSync, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "fs";
|
|
11107
|
-
import { basename as basename3, dirname as
|
|
11636
|
+
import { basename as basename3, dirname as dirname10, join as join17, relative as relative4 } from "path";
|
|
11108
11637
|
init_util();
|
|
11109
11638
|
init_ui();
|
|
11110
11639
|
function buildAssetCommand() {
|
|
@@ -11179,11 +11708,11 @@ function buildAssetCommand() {
|
|
|
11179
11708
|
}
|
|
11180
11709
|
printAssetDetail("Asset", assetId, body);
|
|
11181
11710
|
}, { code: "asset_get_failed" }));
|
|
11182
|
-
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 (
|
|
11711
|
+
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) => {
|
|
11183
11712
|
const cloud = mkCloud();
|
|
11184
11713
|
const query = new URLSearchParams();
|
|
11185
11714
|
if (opts.workspaceId) query.set("wsId", opts.workspaceId);
|
|
11186
|
-
const path9 = `/api/im/assets/by-hash/${encodeURIComponent(
|
|
11715
|
+
const path9 = `/api/im/assets/by-hash/${encodeURIComponent(sha2563)}${query.toString() ? `?${query.toString()}` : ""}`;
|
|
11187
11716
|
const res = await cloud.request("GET", path9);
|
|
11188
11717
|
if (!res.ok) exitWithError(`asset by-hash failed (${res.status}): ${res.error?.message ?? "request failed"}`, { code: res.error?.code ?? "asset_by_hash_failed" });
|
|
11189
11718
|
const body = res.data;
|
|
@@ -11194,7 +11723,7 @@ function buildAssetCommand() {
|
|
|
11194
11723
|
printJson(body ?? null);
|
|
11195
11724
|
return;
|
|
11196
11725
|
}
|
|
11197
|
-
printAssetDetail("Asset hash",
|
|
11726
|
+
printAssetDetail("Asset hash", sha2563, body);
|
|
11198
11727
|
}, { code: "asset_by_hash_failed" }));
|
|
11199
11728
|
return cmd;
|
|
11200
11729
|
}
|
|
@@ -11287,7 +11816,7 @@ async function uploadAssetPath(inputPath, opts) {
|
|
|
11287
11816
|
if (files.length === 0) exitWithError(`directory has no uploadable files: ${inputPath}`);
|
|
11288
11817
|
const items = [];
|
|
11289
11818
|
for (const file of files) {
|
|
11290
|
-
const relDir =
|
|
11819
|
+
const relDir = dirname10(relative4(inputPath, file));
|
|
11291
11820
|
const folderPath = combineFolderPath(opts.folderPath, relDir === "." ? "" : relDir);
|
|
11292
11821
|
const response = await uploadAsset(file, { ...opts, folderPath });
|
|
11293
11822
|
items.push({ file, folderPath: folderPath ?? null, response });
|
|
@@ -12874,7 +13403,7 @@ import { Command as Command13 } from "commander";
|
|
|
12874
13403
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
12875
13404
|
import { existsSync as existsSync18, mkdirSync as mkdirSync10, rmSync, renameSync as renameSync2 } from "fs";
|
|
12876
13405
|
import { homedir as homedir9 } from "os";
|
|
12877
|
-
import { dirname as
|
|
13406
|
+
import { dirname as dirname11, join as join21 } from "path";
|
|
12878
13407
|
import { setTimeout as sleep4 } from "timers/promises";
|
|
12879
13408
|
init_util();
|
|
12880
13409
|
init_ui();
|
|
@@ -12928,8 +13457,8 @@ function buildResetCommand() {
|
|
|
12928
13457
|
rmSync(paths.root, { recursive: true, force: true });
|
|
12929
13458
|
} else {
|
|
12930
13459
|
if (!archivePath) throw new Error("internal: archivePath missing");
|
|
12931
|
-
if (!existsSync18(
|
|
12932
|
-
mkdirSync10(
|
|
13460
|
+
if (!existsSync18(dirname11(archivePath))) {
|
|
13461
|
+
mkdirSync10(dirname11(archivePath), { recursive: true });
|
|
12933
13462
|
}
|
|
12934
13463
|
renameSync2(paths.root, archivePath);
|
|
12935
13464
|
}
|
|
@@ -12939,8 +13468,8 @@ function buildResetCommand() {
|
|
|
12939
13468
|
rmSync(assetSyncRoot, { recursive: true, force: true });
|
|
12940
13469
|
} else {
|
|
12941
13470
|
if (!assetSyncArchivePath) throw new Error("internal: assetSyncArchivePath missing");
|
|
12942
|
-
if (!existsSync18(
|
|
12943
|
-
mkdirSync10(
|
|
13471
|
+
if (!existsSync18(dirname11(assetSyncArchivePath))) {
|
|
13472
|
+
mkdirSync10(dirname11(assetSyncArchivePath), { recursive: true });
|
|
12944
13473
|
}
|
|
12945
13474
|
renameSync2(assetSyncRoot, assetSyncArchivePath);
|
|
12946
13475
|
}
|
|
@@ -12950,8 +13479,8 @@ function buildResetCommand() {
|
|
|
12950
13479
|
rmSync(hermesHome, { recursive: true, force: true });
|
|
12951
13480
|
} else {
|
|
12952
13481
|
if (!hermesArchivePath) throw new Error("internal: hermesArchivePath missing");
|
|
12953
|
-
if (!existsSync18(
|
|
12954
|
-
mkdirSync10(
|
|
13482
|
+
if (!existsSync18(dirname11(hermesArchivePath))) {
|
|
13483
|
+
mkdirSync10(dirname11(hermesArchivePath), { recursive: true });
|
|
12955
13484
|
}
|
|
12956
13485
|
renameSync2(hermesHome, hermesArchivePath);
|
|
12957
13486
|
}
|
|
@@ -14283,7 +14812,7 @@ async function readResponseError(res) {
|
|
|
14283
14812
|
|
|
14284
14813
|
// src/cli/index.ts
|
|
14285
14814
|
init_ui();
|
|
14286
|
-
var VERSION = "2.0.
|
|
14815
|
+
var VERSION = "2.0.1";
|
|
14287
14816
|
function buildProgram() {
|
|
14288
14817
|
const program = new Command20("prismer").description("Prismer Cloud daemon CLI (TS-only).").version(VERSION);
|
|
14289
14818
|
program.addCommand(buildBannerCommand());
|
|
@@ -14341,6 +14870,7 @@ export {
|
|
|
14341
14870
|
currentSchemaVersion,
|
|
14342
14871
|
deriveWsUrl,
|
|
14343
14872
|
envelope,
|
|
14873
|
+
extractHttpUrls,
|
|
14344
14874
|
getRoleTemplate,
|
|
14345
14875
|
handleAgentMessageDispatch,
|
|
14346
14876
|
handleDispatch,
|