@floless/app 0.74.0 → 0.76.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5489,7 +5489,7 @@ var require_thread_stream = __commonJS({
5489
5489
  var { version } = require_package();
5490
5490
  var { EventEmitter: EventEmitter2 } = require("events");
5491
5491
  var { Worker: Worker2 } = require("worker_threads");
5492
- var { join: join32 } = require("path");
5492
+ var { join: join34 } = require("path");
5493
5493
  var { pathToFileURL } = require("url");
5494
5494
  var { wait } = require_wait();
5495
5495
  var {
@@ -5540,7 +5540,7 @@ var require_thread_stream = __commonJS({
5540
5540
  function createWorker(stream, opts) {
5541
5541
  const { filename, workerData } = opts;
5542
5542
  const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
5543
- const toExecute = bundlerOverrides["thread-stream-worker"] || join32(__dirname, "lib", "worker.js");
5543
+ const toExecute = bundlerOverrides["thread-stream-worker"] || join34(__dirname, "lib", "worker.js");
5544
5544
  const worker = new Worker2(toExecute, {
5545
5545
  ...opts.workerOpts,
5546
5546
  name: opts.workerOpts?.name || "thread-stream",
@@ -6006,9 +6006,9 @@ var require_transport = __commonJS({
6006
6006
  "node_modules/pino/lib/transport.js"(exports2, module2) {
6007
6007
  "use strict";
6008
6008
  var { createRequire: createRequire5 } = require("module");
6009
- var { existsSync: existsSync28 } = require("node:fs");
6009
+ var { existsSync: existsSync29 } = require("node:fs");
6010
6010
  var getCallers = require_caller();
6011
- var { join: join32, isAbsolute: isAbsolute2, sep: sep4 } = require("node:path");
6011
+ var { join: join34, isAbsolute: isAbsolute2, sep: sep4 } = require("node:path");
6012
6012
  var { fileURLToPath: fileURLToPath6 } = require("node:url");
6013
6013
  var sleep = require_atomic_sleep();
6014
6014
  var onExit = require_on_exit_leak_free();
@@ -6080,7 +6080,7 @@ var require_transport = __commonJS({
6080
6080
  return false;
6081
6081
  }
6082
6082
  }
6083
- return isAbsolute2(path) && !existsSync28(path);
6083
+ return isAbsolute2(path) && !existsSync29(path);
6084
6084
  }
6085
6085
  function stripQuotes(value) {
6086
6086
  const first = value[0];
@@ -6161,7 +6161,7 @@ var require_transport = __commonJS({
6161
6161
  throw new Error("only one of target or targets can be specified");
6162
6162
  }
6163
6163
  if (targets) {
6164
- target = bundlerOverrides["pino-worker"] || join32(__dirname, "worker.js");
6164
+ target = bundlerOverrides["pino-worker"] || join34(__dirname, "worker.js");
6165
6165
  options.targets = targets.filter((dest) => dest.target).map((dest) => {
6166
6166
  return {
6167
6167
  ...dest,
@@ -6179,7 +6179,7 @@ var require_transport = __commonJS({
6179
6179
  });
6180
6180
  });
6181
6181
  } else if (pipeline2) {
6182
- target = bundlerOverrides["pino-worker"] || join32(__dirname, "worker.js");
6182
+ target = bundlerOverrides["pino-worker"] || join34(__dirname, "worker.js");
6183
6183
  options.pipelines = [pipeline2.map((dest) => {
6184
6184
  return {
6185
6185
  ...dest,
@@ -6202,7 +6202,7 @@ var require_transport = __commonJS({
6202
6202
  return origin;
6203
6203
  }
6204
6204
  if (origin === "pino/file") {
6205
- return join32(__dirname, "..", "file.js");
6205
+ return join34(__dirname, "..", "file.js");
6206
6206
  }
6207
6207
  let fixTarget2;
6208
6208
  for (const filePath of callers) {
@@ -7182,7 +7182,7 @@ var require_safe_stable_stringify = __commonJS({
7182
7182
  return circularValue;
7183
7183
  }
7184
7184
  let res = "";
7185
- let join32 = ",";
7185
+ let join34 = ",";
7186
7186
  const originalIndentation = indentation;
7187
7187
  if (Array.isArray(value)) {
7188
7188
  if (value.length === 0) {
@@ -7196,7 +7196,7 @@ var require_safe_stable_stringify = __commonJS({
7196
7196
  indentation += spacer;
7197
7197
  res += `
7198
7198
  ${indentation}`;
7199
- join32 = `,
7199
+ join34 = `,
7200
7200
  ${indentation}`;
7201
7201
  }
7202
7202
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
@@ -7204,13 +7204,13 @@ ${indentation}`;
7204
7204
  for (; i < maximumValuesToStringify - 1; i++) {
7205
7205
  const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
7206
7206
  res += tmp2 !== void 0 ? tmp2 : "null";
7207
- res += join32;
7207
+ res += join34;
7208
7208
  }
7209
7209
  const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
7210
7210
  res += tmp !== void 0 ? tmp : "null";
7211
7211
  if (value.length - 1 > maximumBreadth) {
7212
7212
  const removedKeys = value.length - maximumBreadth - 1;
7213
- res += `${join32}"... ${getItemCount(removedKeys)} not stringified"`;
7213
+ res += `${join34}"... ${getItemCount(removedKeys)} not stringified"`;
7214
7214
  }
7215
7215
  if (spacer !== "") {
7216
7216
  res += `
@@ -7231,7 +7231,7 @@ ${originalIndentation}`;
7231
7231
  let separator = "";
7232
7232
  if (spacer !== "") {
7233
7233
  indentation += spacer;
7234
- join32 = `,
7234
+ join34 = `,
7235
7235
  ${indentation}`;
7236
7236
  whitespace = " ";
7237
7237
  }
@@ -7245,13 +7245,13 @@ ${indentation}`;
7245
7245
  const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
7246
7246
  if (tmp !== void 0) {
7247
7247
  res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
7248
- separator = join32;
7248
+ separator = join34;
7249
7249
  }
7250
7250
  }
7251
7251
  if (keyLength > maximumBreadth) {
7252
7252
  const removedKeys = keyLength - maximumBreadth;
7253
7253
  res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
7254
- separator = join32;
7254
+ separator = join34;
7255
7255
  }
7256
7256
  if (spacer !== "" && separator.length > 1) {
7257
7257
  res = `
@@ -7292,7 +7292,7 @@ ${originalIndentation}`;
7292
7292
  }
7293
7293
  const originalIndentation = indentation;
7294
7294
  let res = "";
7295
- let join32 = ",";
7295
+ let join34 = ",";
7296
7296
  if (Array.isArray(value)) {
7297
7297
  if (value.length === 0) {
7298
7298
  return "[]";
@@ -7305,7 +7305,7 @@ ${originalIndentation}`;
7305
7305
  indentation += spacer;
7306
7306
  res += `
7307
7307
  ${indentation}`;
7308
- join32 = `,
7308
+ join34 = `,
7309
7309
  ${indentation}`;
7310
7310
  }
7311
7311
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
@@ -7313,13 +7313,13 @@ ${indentation}`;
7313
7313
  for (; i < maximumValuesToStringify - 1; i++) {
7314
7314
  const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
7315
7315
  res += tmp2 !== void 0 ? tmp2 : "null";
7316
- res += join32;
7316
+ res += join34;
7317
7317
  }
7318
7318
  const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
7319
7319
  res += tmp !== void 0 ? tmp : "null";
7320
7320
  if (value.length - 1 > maximumBreadth) {
7321
7321
  const removedKeys = value.length - maximumBreadth - 1;
7322
- res += `${join32}"... ${getItemCount(removedKeys)} not stringified"`;
7322
+ res += `${join34}"... ${getItemCount(removedKeys)} not stringified"`;
7323
7323
  }
7324
7324
  if (spacer !== "") {
7325
7325
  res += `
@@ -7332,7 +7332,7 @@ ${originalIndentation}`;
7332
7332
  let whitespace = "";
7333
7333
  if (spacer !== "") {
7334
7334
  indentation += spacer;
7335
- join32 = `,
7335
+ join34 = `,
7336
7336
  ${indentation}`;
7337
7337
  whitespace = " ";
7338
7338
  }
@@ -7341,7 +7341,7 @@ ${indentation}`;
7341
7341
  const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
7342
7342
  if (tmp !== void 0) {
7343
7343
  res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
7344
- separator = join32;
7344
+ separator = join34;
7345
7345
  }
7346
7346
  }
7347
7347
  if (spacer !== "" && separator.length > 1) {
@@ -7399,20 +7399,20 @@ ${originalIndentation}`;
7399
7399
  indentation += spacer;
7400
7400
  let res2 = `
7401
7401
  ${indentation}`;
7402
- const join33 = `,
7402
+ const join35 = `,
7403
7403
  ${indentation}`;
7404
7404
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
7405
7405
  let i = 0;
7406
7406
  for (; i < maximumValuesToStringify - 1; i++) {
7407
7407
  const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
7408
7408
  res2 += tmp2 !== void 0 ? tmp2 : "null";
7409
- res2 += join33;
7409
+ res2 += join35;
7410
7410
  }
7411
7411
  const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
7412
7412
  res2 += tmp !== void 0 ? tmp : "null";
7413
7413
  if (value.length - 1 > maximumBreadth) {
7414
7414
  const removedKeys = value.length - maximumBreadth - 1;
7415
- res2 += `${join33}"... ${getItemCount(removedKeys)} not stringified"`;
7415
+ res2 += `${join35}"... ${getItemCount(removedKeys)} not stringified"`;
7416
7416
  }
7417
7417
  res2 += `
7418
7418
  ${originalIndentation}`;
@@ -7428,16 +7428,16 @@ ${originalIndentation}`;
7428
7428
  return '"[Object]"';
7429
7429
  }
7430
7430
  indentation += spacer;
7431
- const join32 = `,
7431
+ const join34 = `,
7432
7432
  ${indentation}`;
7433
7433
  let res = "";
7434
7434
  let separator = "";
7435
7435
  let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
7436
7436
  if (isTypedArrayWithEntries(value)) {
7437
- res += stringifyTypedArray(value, join32, maximumBreadth);
7437
+ res += stringifyTypedArray(value, join34, maximumBreadth);
7438
7438
  keys = keys.slice(value.length);
7439
7439
  maximumPropertiesToStringify -= value.length;
7440
- separator = join32;
7440
+ separator = join34;
7441
7441
  }
7442
7442
  if (deterministic) {
7443
7443
  keys = sort(keys, comparator);
@@ -7448,13 +7448,13 @@ ${indentation}`;
7448
7448
  const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
7449
7449
  if (tmp !== void 0) {
7450
7450
  res += `${separator}${strEscape(key2)}: ${tmp}`;
7451
- separator = join32;
7451
+ separator = join34;
7452
7452
  }
7453
7453
  }
7454
7454
  if (keyLength > maximumBreadth) {
7455
7455
  const removedKeys = keyLength - maximumBreadth;
7456
7456
  res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
7457
- separator = join32;
7457
+ separator = join34;
7458
7458
  }
7459
7459
  if (separator !== "") {
7460
7460
  res = `
@@ -41991,7 +41991,7 @@ var require_send = __commonJS({
41991
41991
  var { parseTokenList } = require_parseTokenList();
41992
41992
  var { createHttpError } = require_createHttpError();
41993
41993
  var extname3 = path.extname;
41994
- var join32 = path.join;
41994
+ var join34 = path.join;
41995
41995
  var normalize2 = path.normalize;
41996
41996
  var resolve6 = path.resolve;
41997
41997
  var sep4 = path.sep;
@@ -42078,7 +42078,7 @@ var require_send = __commonJS({
42078
42078
  return { statusCode: 403 };
42079
42079
  }
42080
42080
  parts = path2.split(sep4);
42081
- path2 = normalize2(join32(root, path2));
42081
+ path2 = normalize2(join34(root, path2));
42082
42082
  } else {
42083
42083
  if (UP_PATH_REGEXP.test(path2)) {
42084
42084
  debug('malicious path "%s"', path2);
@@ -42361,7 +42361,7 @@ var require_send = __commonJS({
42361
42361
  let err2;
42362
42362
  for (let i = 0; i < options.index.length; i++) {
42363
42363
  const index = options.index[i];
42364
- const p = join32(path2, index);
42364
+ const p = join34(path2, index);
42365
42365
  const { error, stat: stat4 } = await tryStat(p);
42366
42366
  if (error) {
42367
42367
  err2 = error;
@@ -50827,9 +50827,9 @@ var import_node_readline2 = require("node:readline");
50827
50827
 
50828
50828
  // index.ts
50829
50829
  var import_node_url5 = require("node:url");
50830
- var import_node_path31 = require("node:path");
50830
+ var import_node_path33 = require("node:path");
50831
50831
  var import_node_os21 = require("node:os");
50832
- var import_node_fs33 = require("node:fs");
50832
+ var import_node_fs35 = require("node:fs");
50833
50833
  var import_node_child_process8 = require("node:child_process");
50834
50834
 
50835
50835
  // log.mjs
@@ -50948,6 +50948,48 @@ function storeVisualInput(appId, dataUrl) {
50948
50948
  }
50949
50949
  return { path, ext, bytes: (0, import_node_fs3.statSync)(path).size, sha256: sha2562 };
50950
50950
  }
50951
+ var MAX_IFC_BYTES = 64 * 1024 * 1024;
50952
+ function sniffIfc(buf) {
50953
+ let off = 0;
50954
+ if (buf.length >= 3 && buf[0] === 239 && buf[1] === 187 && buf[2] === 191) off = 3;
50955
+ if (buf.length >= off + 12 && buf.toString("ascii", off, off + 12) === "ISO-10303-21") return "ifc";
50956
+ if (buf.length >= 4 && buf[0] === 80 && buf[1] === 75 && (buf[2] === 3 || buf[2] === 5 || buf[2] === 7)) return "ifczip";
50957
+ return null;
50958
+ }
50959
+ function storeIfcInput(appId, dataUrl) {
50960
+ if (!safeSegment(appId)) throw new VisualInputError("invalid app id");
50961
+ const m = /^data:([\w/+.-]*);base64,(.*)$/s.exec(dataUrl || "");
50962
+ if (!m) throw new VisualInputError("IFC must be a base64 data URL");
50963
+ const buf = Buffer.from(m[2] ?? "", "base64");
50964
+ if (buf.length === 0) throw new VisualInputError("empty IFC file");
50965
+ if (buf.length > MAX_IFC_BYTES) throw new VisualInputError("IFC file too large (max 64 MB)");
50966
+ const ext = sniffIfc(buf);
50967
+ if (!ext) throw new VisualInputError("that file is not an IFC (.ifc / .ifczip)");
50968
+ const sha2562 = (0, import_node_crypto.createHash)("sha256").update(buf).digest("hex");
50969
+ const dir = (0, import_node_path2.join)(inputsDir(), appId);
50970
+ const path = (0, import_node_path2.join)(dir, `${sha2562}.${ext}`);
50971
+ const root = (0, import_node_path2.resolve)(inputsDir());
50972
+ const target = (0, import_node_path2.resolve)(path);
50973
+ if (target !== root && !target.startsWith(root + import_node_path2.sep)) {
50974
+ throw new VisualInputError("refusing to write outside the inputs root");
50975
+ }
50976
+ (0, import_node_fs3.mkdirSync)(dir, { recursive: true });
50977
+ if (!(0, import_node_fs3.existsSync)(path)) {
50978
+ const tmp = `${path}.tmp`;
50979
+ (0, import_node_fs3.writeFileSync)(tmp, buf);
50980
+ (0, import_node_fs3.renameSync)(tmp, path);
50981
+ }
50982
+ return { path, sha256: sha2562, bytes: (0, import_node_fs3.statSync)(path).size };
50983
+ }
50984
+ function ifcInputPath(appId, sha2562) {
50985
+ if (!safeSegment(appId) || !safeSegment(sha2562) || !/^[a-f0-9]{64}$/i.test(sha2562)) return null;
50986
+ const root = (0, import_node_path2.resolve)(inputsDir());
50987
+ for (const ext of ["ifc", "ifczip"]) {
50988
+ const path = (0, import_node_path2.resolve)((0, import_node_path2.join)(inputsDir(), appId, `${sha2562}.${ext}`));
50989
+ if ((path === root || path.startsWith(root + import_node_path2.sep)) && (0, import_node_fs3.existsSync)(path)) return path;
50990
+ }
50991
+ return null;
50992
+ }
50951
50993
  function renameVisualInputs(oldId, newId) {
50952
50994
  if (!safeSegment(oldId) || !safeSegment(newId)) throw new VisualInputError("invalid app id");
50953
50995
  const from = (0, import_node_path2.join)(inputsDir(), oldId);
@@ -51567,6 +51609,10 @@ function resolveTeklaBridge() {
51567
51609
  ].filter((p) => !!p);
51568
51610
  return candidates.find((p) => (0, import_node_fs6.existsSync)(p)) ?? null;
51569
51611
  }
51612
+ function resolveConnectionReaderBridge() {
51613
+ const p = (0, import_node_path5.join)((0, import_node_os5.homedir)(), ".aware", "bridges", "aware-connection-reader.exe");
51614
+ return (0, import_node_fs6.existsSync)(p) ? p : null;
51615
+ }
51570
51616
  function injectDebugger(code) {
51571
51617
  const lines = code.split(/\r?\n/);
51572
51618
  let lastUsing = -1;
@@ -51889,6 +51935,9 @@ function agentInstallArgv(id) {
51889
51935
  function agentUpdateArgv(id) {
51890
51936
  return ["agent", "update", assertId(id)];
51891
51937
  }
51938
+ function sidecarInstallArgv(host) {
51939
+ return ["sidecar", "install", assertId(host)];
51940
+ }
51892
51941
  function parseAgentInstallOutput(stdout) {
51893
51942
  const m = stdout.match(/✓\s+(?:installed|updated)\s+(\S+)/);
51894
51943
  return { installed: m?.[1] ?? null };
@@ -52235,6 +52284,28 @@ var aware = {
52235
52284
  if (isAgentInstalled(agents, id)) return { installed: id, output: "already installed" };
52236
52285
  return this.agentInstall(id);
52237
52286
  },
52287
+ /**
52288
+ * Install a cli-transport agent's host bridge (`aware sidecar install <host>`). Downloads a
52289
+ * platform zip (~30 MB observed) from the matching GitHub release into ~/.aware/bridges/ — well
52290
+ * over runRaw's 60s default — so use the generous timeout. NOT idempotent-safe to assume: prefer
52291
+ * ensureConnectionReaderBridge for the reader (checks the bridge is on disk first).
52292
+ */
52293
+ async sidecarInstall(host) {
52294
+ const { code, stdout, stderr } = await runRawWithEnv(sidecarInstallArgv(host), void 0, 6e5);
52295
+ if (code !== 0) throw new AwareError(`aware sidecar install ${host} failed (exit ${code})`, { stdout, stderr });
52296
+ return { output: stdout.trim() };
52297
+ },
52298
+ /**
52299
+ * Ensure the connection-reader bridge (the web-ifc Node SEA) is present, idempotently: if it's
52300
+ * already in ~/.aware/bridges/ this is a cheap no-op; otherwise fetch it via `sidecar install`.
52301
+ * `aware app run` on a connection-reader node needs this bridge (the tessellator is web-ifc, not
52302
+ * Rust) — the reader agent's manifest alone isn't enough.
52303
+ */
52304
+ async ensureConnectionReaderBridge() {
52305
+ if (resolveConnectionReaderBridge()) return { present: true, output: "already installed" };
52306
+ const r = await this.sidecarInstall("connection-reader");
52307
+ return { present: !!resolveConnectionReaderBridge(), output: r.output };
52308
+ },
52238
52309
  /**
52239
52310
  * Update an installed agent to the latest registry version (`aware agent update
52240
52311
  * <id>`). Unlike `ensureAgentInstalled`, this always re-pulls — it is the Agent
@@ -53022,7 +53093,7 @@ function appVersion() {
53022
53093
  return resolveVersion({
53023
53094
  isSea: isSea2(),
53024
53095
  sqVersionXml: readSqVersionXml(),
53025
- define: true ? "0.74.0" : void 0,
53096
+ define: true ? "0.76.0" : void 0,
53026
53097
  pkgVersion: readPkgVersion()
53027
53098
  });
53028
53099
  }
@@ -53032,7 +53103,7 @@ function resolveChannel(s) {
53032
53103
  return "dev";
53033
53104
  }
53034
53105
  function appChannel() {
53035
- return resolveChannel({ isSea: isSea2(), define: true ? "0.74.0" : void 0 });
53106
+ return resolveChannel({ isSea: isSea2(), define: true ? "0.76.0" : void 0 });
53036
53107
  }
53037
53108
 
53038
53109
  // workflow-update.ts
@@ -53397,7 +53468,7 @@ function extractSavedPath(events) {
53397
53468
  var aisc_shapes_default = { "C10X15.3": 2.6, C10X20: 2.74, C10X25: 2.89, C10X30: 3.03, "C12X20.7": 2.94, C12X25: 3.05, C12X30: 3.17, "C15X33.9": 3.4, C15X40: 3.52, C15X50: 3.72, "C3X3.5": 1.37, "C3X4.1": 1.41, C3X5: 1.5, C3X6: 1.6, "C4X4.5": 1.52, "C4X5.4": 1.58, "C4X6.25": 1.65, "C4X7.25": 1.72, "C5X6.7": 1.75, C5X9: 1.89, "C6X10.5": 2.03, C6X13: 2.16, "C6X8.2": 1.92, "C7X12.25": 2.19, "C7X14.75": 2.3, "C7X9.8": 2.09, "C8X11.5": 2.26, "C8X13.75": 2.34, "C8X18.75": 2.53, "C9X13.4": 2.43, C9X15: 2.49, C9X20: 2.65, HP10X42: 10.1, HP10X57: 10.2, HP12X53: 12, HP12X63: 12.1, HP12X74: 12.2, HP12X84: 12.3, HP12X89: 12.3, HP14X102: 14.8, HP14X117: 14.9, HP14X73: 14.6, HP14X89: 14.7, HP16X101: 15.8, HP16X121: 15.9, HP16X141: 16, HP16X162: 16.1, HP16X183: 16.3, HP16X88: 15.7, HP18X135: 17.8, HP18X157: 17.9, HP18X181: 18, HP18X204: 18.1, HP8X36: 8.16, "M10X7.5": 2.69, M10X8: 2.69, M10X9: 2.69, "M12.5X11.6": 3.5, "M12.5X12.4": 3.75, M12X10: 3.25, "M12X10.8": 3.07, "M12X11.8": 3.07, "M3X2.9": 2.25, "M4X3.2": 2.25, "M4X3.45": 2.25, "M4X4.08": 2.25, M4X6: 3.8, "M5X18.9": 5, "M6X3.7": 2, "M6X4.4": 1.84, "M8X6.2": 2.28, "M8X6.5": 2.28, MC10X22: 3.32, MC10X25: 3.41, "MC10X28.5": 3.95, "MC10X33.6": 4.1, "MC10X41.1": 4.32, "MC10X6.5": 1.17, "MC10X8.4": 1.5, "MC12X10.6": 1.5, "MC12X14.3": 2.12, MC12X31: 3.67, MC12X35: 3.77, MC12X40: 3.89, MC12X45: 4.01, MC12X50: 4.14, "MC13X31.8": 4, MC13X35: 4.07, MC13X40: 4.19, MC13X50: 4.41, "MC18X42.7": 3.95, "MC18X45.8": 4, "MC18X51.9": 4.1, MC18X58: 4.2, "MC3X7.1": 1.94, "MC4X13.8": 2.5, MC6X12: 2.5, "MC6X15.1": 2.94, "MC6X15.3": 3.5, "MC6X16.3": 3, MC6X18: 3.5, "MC6X6.5": 1.85, MC6X7: 1.88, "MC7X19.1": 3.45, "MC7X22.7": 3.6, "MC8X18.7": 2.98, MC8X20: 3.03, "MC8X21.4": 3.45, "MC8X22.8": 3.5, "MC8X8.5": 1.87, "MC9X23.9": 3.45, "MC9X25.4": 3.5, "MT2.5X9.45": 5, MT2X3: 3.8, "MT3X1.85": 2, "MT3X2.2": 1.84, "MT4X3.1": 2.28, "MT4X3.25": 2.28, "MT5X3.75": 2.69, MT5X4: 2.69, "MT5X4.5": 2.69, "MT6.25X5.8": 3.5, "MT6.25X6.2": 3.75, MT6X5: 3.25, "MT6X5.4": 3.07, "MT6X5.9": 3.07, "S10X25.4": 4.66, S10X35: 4.94, "S12X31.8": 5, S12X35: 5.08, "S12X40.8": 5.25, S12X50: 5.48, "S15X42.9": 5.5, S15X50: 5.64, "S18X54.7": 6, S18X70: 6.25, S20X66: 6.26, S20X75: 6.39, S20X86: 7.06, S20X96: 7.2, S24X100: 7.25, S24X106: 7.87, S24X121: 8.05, S24X80: 7, S24X90: 7.13, "S3X5.7": 2.33, "S3X7.5": 2.51, "S4X7.7": 2.66, "S4X9.5": 2.8, S5X10: 3, "S6X12.5": 3.33, "S6X17.25": 3.57, "S8X18.4": 4, S8X23: 4.17, "ST1.5X2.85": 2.33, "ST1.5X3.75": 2.51, ST10X33: 6.26, "ST10X37.5": 6.39, ST10X43: 7.06, ST10X48: 7.2, ST12X40: 7, ST12X45: 7.13, ST12X50: 7.25, ST12X53: 7.87, "ST12X60.5": 8.05, "ST2.5X5": 3, "ST2X3.85": 2.66, "ST2X4.75": 2.8, "ST3X6.25": 3.33, "ST3X8.6": 3.57, "ST4X11.5": 4.17, "ST4X9.2": 4, "ST5X12.7": 4.66, "ST5X17.5": 4.94, "ST6X15.9": 5, "ST6X17.5": 5.08, "ST6X20.4": 5.25, ST6X25: 5.48, "ST7.5X21.45": 5.5, "ST7.5X25": 5.64, "ST9X27.35": 6, ST9X35: 6.25, W10X100: 10.3, W10X112: 10.4, W10X12: 3.96, W10X15: 4, W10X17: 4.01, W10X19: 4.02, W10X22: 5.75, W10X26: 5.77, W10X30: 5.81, W10X33: 7.96, W10X39: 7.99, W10X45: 8.02, W10X49: 10, W10X54: 10, W10X60: 10.1, W10X68: 10.1, W10X77: 10.2, W10X88: 10.3, W12X106: 12.2, W12X120: 12.3, W12X136: 12.4, W12X14: 3.97, W12X152: 12.5, W12X16: 3.99, W12X170: 12.6, W12X19: 4.01, W12X190: 12.7, W12X210: 12.8, W12X22: 4.03, W12X230: 12.9, W12X252: 13, W12X26: 6.49, W12X279: 13.1, W12X30: 6.52, W12X305: 13.2, W12X336: 13.4, W12X35: 6.56, W12X40: 8.01, W12X45: 8.05, W12X50: 8.08, W12X53: 10, W12X58: 10, W12X65: 12, W12X72: 12, W12X79: 12.1, W12X87: 12.1, W12X96: 12.2, W14X109: 14.6, W14X120: 14.7, W14X132: 14.7, W14X145: 15.5, W14X159: 15.6, W14X176: 15.7, W14X193: 15.7, W14X211: 15.8, W14X22: 5, W14X233: 15.9, W14X257: 16, W14X26: 5.03, W14X283: 16.1, W14X30: 6.73, W14X311: 16.2, W14X34: 6.75, W14X342: 16.4, W14X370: 16.5, W14X38: 6.77, W14X398: 16.6, W14X426: 16.7, W14X43: 8, W14X455: 16.8, W14X48: 8.03, W14X500: 17, W14X53: 8.06, W14X550: 17.2, W14X605: 17.4, W14X61: 10, W14X665: 17.7, W14X68: 10, W14X730: 17.9, W14X74: 10.1, W14X808: 18.6, W14X82: 10.1, W14X873: 18.8, W14X90: 14.5, W14X99: 14.6, W16X100: 10.4, W16X26: 5.5, W16X31: 5.53, W16X36: 6.99, W16X40: 7, W16X45: 7.04, W16X50: 7.07, W16X57: 7.12, W16X67: 10.2, W16X77: 10.3, W16X89: 10.4, W18X106: 11.2, W18X119: 11.3, W18X130: 11.2, W18X143: 11.2, W18X158: 11.3, W18X175: 11.4, W18X192: 11.5, W18X211: 11.6, W18X234: 11.7, W18X258: 11.8, W18X283: 11.9, W18X311: 12, W18X35: 6, W18X40: 6.02, W18X46: 6.06, W18X50: 7.5, W18X55: 7.53, W18X60: 7.56, W18X65: 7.59, W18X71: 7.64, W18X76: 11, W18X86: 11.1, W18X97: 11.1, W21X101: 12.3, W21X111: 12.3, W21X122: 12.4, W21X132: 12.4, W21X147: 12.5, W21X166: 12.4, W21X182: 12.5, W21X201: 12.6, W21X223: 12.7, W21X248: 12.8, W21X275: 12.9, W21X44: 6.5, W21X48: 8.14, W21X50: 6.53, W21X55: 8.22, W21X57: 6.56, W21X62: 8.24, W21X68: 8.27, W21X73: 8.3, W21X83: 8.36, W21X93: 8.42, W24X103: 9, W24X104: 12.8, W24X117: 12.8, W24X131: 12.9, W24X146: 12.9, W24X162: 13, W24X176: 12.9, W24X192: 13, W24X207: 13, W24X229: 13.1, W24X250: 13.2, W24X279: 13.3, W24X306: 13.4, W24X335: 13.5, W24X370: 13.7, W24X55: 7.01, W24X62: 7.04, W24X68: 8.97, W24X76: 8.99, W24X84: 9.02, W24X94: 9.07, W27X102: 10, W27X114: 10.1, W27X129: 10, W27X146: 14, W27X161: 14, W27X178: 14.1, W27X194: 14, W27X217: 14.1, W27X235: 14.2, W27X258: 14.3, W27X281: 14.4, W27X307: 14.4, W27X336: 14.6, W27X368: 14.7, W27X539: 15.3, W27X84: 10, W27X94: 10, W30X108: 10.5, W30X116: 10.5, W30X124: 10.5, W30X132: 10.5, W30X148: 10.5, W30X173: 15, W30X191: 15, W30X211: 15.1, W30X235: 15.1, W30X261: 15.2, W30X292: 15.3, W30X326: 15.4, W30X357: 15.5, W30X391: 15.6, W30X90: 10.4, W30X99: 10.5, W33X118: 11.5, W33X130: 11.5, W33X141: 11.5, W33X152: 11.6, W33X169: 11.5, W33X201: 15.7, W33X221: 15.8, W33X241: 15.9, W33X263: 15.8, W33X291: 15.9, W33X318: 16, W33X354: 16.1, W33X387: 16.2, W36X135: 12, W36X150: 12, W36X160: 12, W36X170: 12, W36X182: 12.1, W36X194: 12.1, W36X210: 12.2, W36X231: 16.5, W36X232: 12.1, W36X247: 16.5, W36X256: 12.2, W36X262: 16.6, W36X282: 16.6, W36X302: 16.7, W36X330: 16.6, W36X361: 16.7, W36X395: 16.8, W36X441: 17, W36X487: 17.1, W36X529: 17.2, W36X652: 17.6, W36X723: 17.8, W36X802: 18, W36X853: 18.2, W36X925: 18.6, W40X149: 11.8, W40X167: 11.8, W40X183: 11.8, W40X199: 15.8, W40X211: 11.8, W40X215: 15.8, W40X235: 11.9, W40X249: 15.8, W40X264: 11.9, W40X277: 15.8, W40X278: 12, W40X294: 12, W40X297: 15.8, W40X324: 15.9, W40X327: 12.1, W40X331: 12.2, W40X362: 16, W40X372: 16.1, W40X392: 12.4, W40X397: 16.1, W40X431: 16.2, W40X503: 16.4, W40X593: 16.7, W40X655: 16.9, W44X230: 15.8, W44X262: 15.8, W44X290: 15.8, W44X335: 15.9, W4X13: 4.06, W5X16: 5, W5X19: 5.03, W6X12: 4, W6X15: 5.99, W6X16: 4.03, W6X20: 6.02, W6X25: 6.08, "W6X8.5": 3.94, W6X9: 3.94, W8X10: 3.94, W8X13: 4, W8X15: 4.02, W8X18: 5.25, W8X21: 5.27, W8X24: 6.5, W8X28: 6.54, W8X31: 8, W8X35: 8.02, W8X40: 8.07, W8X48: 8.11, W8X58: 8.22, W8X67: 8.28, "WT10.5X100.5": 12.6, "WT10.5X111.5": 12.7, "WT10.5X124": 12.8, "WT10.5X137.5": 12.9, "WT10.5X22": 6.5, "WT10.5X24": 8.14, "WT10.5X25": 6.53, "WT10.5X27.5": 8.22, "WT10.5X28.5": 6.56, "WT10.5X31": 8.24, "WT10.5X34": 8.27, "WT10.5X36.5": 8.3, "WT10.5X41.5": 8.36, "WT10.5X46.5": 8.42, "WT10.5X50.5": 12.3, "WT10.5X55.5": 12.3, "WT10.5X61": 12.4, "WT10.5X66": 12.4, "WT10.5X73.5": 12.5, "WT10.5X83": 12.4, "WT10.5X91": 12.5, "WT12X103.5": 13, "WT12X114.5": 13.1, WT12X125: 13.2, "WT12X139.5": 13.3, WT12X153: 13.4, "WT12X167.5": 13.5, WT12X185: 13.7, "WT12X27.5": 7.01, WT12X31: 7.04, WT12X34: 8.97, WT12X38: 8.99, WT12X42: 9.02, WT12X47: 9.07, "WT12X51.5": 9, WT12X52: 12.8, "WT12X58.5": 12.8, "WT12X65.5": 12.9, WT12X73: 12.9, WT12X81: 13, WT12X88: 12.9, WT12X96: 13, "WT13.5X108.5": 14.1, "WT13.5X117.5": 14.2, "WT13.5X129": 14.3, "WT13.5X140.5": 14.4, "WT13.5X153.5": 14.4, "WT13.5X168": 14.6, "WT13.5X184": 14.7, "WT13.5X269.5": 15.3, "WT13.5X42": 10, "WT13.5X47": 10, "WT13.5X51": 10, "WT13.5X57": 10.1, "WT13.5X64.5": 10, "WT13.5X73": 14, "WT13.5X80.5": 14, "WT13.5X89": 14.1, "WT13.5X97": 14, "WT15X105.5": 15.1, "WT15X117.5": 15.1, "WT15X130.5": 15.2, WT15X146: 15.3, WT15X163: 15.4, "WT15X178.5": 15.5, "WT15X195.5": 15.6, WT15X45: 10.4, "WT15X49.5": 10.5, WT15X54: 10.5, WT15X58: 10.5, WT15X62: 10.5, WT15X66: 10.5, WT15X74: 10.5, "WT15X86.5": 15, "WT15X95.5": 15, "WT16.5X100.5": 15.7, "WT16.5X110.5": 15.8, "WT16.5X120.5": 15.9, "WT16.5X131.5": 15.8, "WT16.5X145.5": 15.9, "WT16.5X159": 16, "WT16.5X177": 16.1, "WT16.5X193.5": 16.2, "WT16.5X59": 11.5, "WT16.5X65": 11.5, "WT16.5X70.5": 11.5, "WT16.5X76": 11.6, "WT16.5X84.5": 11.5, WT18X105: 12.2, "WT18X115.5": 16.5, WT18X116: 12.1, "WT18X123.5": 16.5, WT18X128: 12.2, WT18X131: 16.6, WT18X141: 16.6, WT18X151: 16.7, WT18X165: 16.6, "WT18X180.5": 16.7, "WT18X197.5": 16.8, "WT18X220.5": 17, "WT18X243.5": 17.1, "WT18X264.5": 17.2, WT18X326: 17.6, "WT18X361.5": 17.8, WT18X401: 18, "WT18X426.5": 18.2, "WT18X462.5": 18.6, "WT18X67.5": 12, WT18X75: 12, WT18X80: 12, WT18X85: 12, WT18X91: 12.1, WT18X97: 12.1, "WT2.5X8": 5, "WT2.5X9.5": 5.03, "WT20X105.5": 11.8, "WT20X107.5": 15.8, "WT20X117.5": 11.9, "WT20X124.5": 15.8, WT20X132: 11.9, "WT20X138.5": 15.8, WT20X139: 12, WT20X147: 12, "WT20X148.5": 15.8, WT20X162: 15.9, "WT20X163.5": 12.1, "WT20X165.5": 12.2, WT20X181: 16, WT20X186: 16.1, WT20X196: 12.4, "WT20X198.5": 16.1, "WT20X215.5": 16.2, "WT20X251.5": 16.4, "WT20X296.5": 16.7, "WT20X327.5": 16.9, "WT20X74.5": 11.8, "WT20X83.5": 11.8, "WT20X91.5": 11.8, "WT20X99.5": 15.8, WT22X115: 15.8, WT22X131: 15.8, WT22X145: 15.8, "WT22X167.5": 15.9, "WT2X6.5": 4.06, WT3X10: 6.02, "WT3X12.5": 6.08, "WT3X4.25": 3.94, "WT3X4.5": 3.94, WT3X6: 4, "WT3X7.5": 5.99, WT3X8: 4.03, "WT4X10.5": 5.27, WT4X12: 6.5, WT4X14: 6.54, "WT4X15.5": 8, "WT4X17.5": 8.02, WT4X20: 8.07, WT4X24: 8.11, WT4X29: 8.22, "WT4X33.5": 8.28, WT4X5: 3.94, "WT4X6.5": 4, "WT4X7.5": 4.02, WT4X9: 5.25, WT5X11: 5.75, WT5X13: 5.77, WT5X15: 5.81, "WT5X16.5": 7.96, "WT5X19.5": 7.99, "WT5X22.5": 8.02, "WT5X24.5": 10, WT5X27: 10, WT5X30: 10.1, WT5X34: 10.1, "WT5X38.5": 10.2, WT5X44: 10.3, WT5X50: 10.3, WT5X56: 10.4, WT5X6: 3.96, "WT5X7.5": 4, "WT5X8.5": 4.01, "WT5X9.5": 4.02, WT6X105: 12.8, WT6X11: 4.03, WT6X115: 12.9, WT6X126: 13, WT6X13: 6.49, "WT6X139.5": 13.1, WT6X15: 6.52, "WT6X152.5": 13.2, WT6X168: 13.4, "WT6X17.5": 6.56, WT6X20: 8.01, "WT6X22.5": 8.05, WT6X25: 8.08, "WT6X26.5": 10, WT6X29: 10, "WT6X32.5": 12, WT6X36: 12, "WT6X39.5": 12.1, "WT6X43.5": 12.1, WT6X48: 12.2, WT6X53: 12.2, WT6X60: 12.3, WT6X68: 12.4, WT6X7: 3.97, WT6X76: 12.5, WT6X8: 3.99, WT6X85: 12.6, "WT6X9.5": 4.01, WT6X95: 12.7, "WT7X105.5": 15.8, WT7X11: 5, "WT7X116.5": 15.9, "WT7X128.5": 16, WT7X13: 5.03, "WT7X141.5": 16.1, WT7X15: 6.73, "WT7X155.5": 16.2, WT7X17: 6.75, WT7X171: 16.4, WT7X185: 16.5, WT7X19: 6.77, WT7X199: 16.6, "WT7X21.5": 8, WT7X213: 16.7, "WT7X227.5": 16.8, WT7X24: 8.03, WT7X250: 17, "WT7X26.5": 8.06, WT7X275: 17.2, "WT7X30.5": 10, "WT7X302.5": 17.4, "WT7X332.5": 17.7, WT7X34: 10, WT7X365: 17.9, WT7X37: 10.1, WT7X404: 18.6, WT7X41: 10.1, "WT7X436.5": 18.8, WT7X45: 14.5, "WT7X49.5": 14.6, "WT7X54.5": 14.6, WT7X60: 14.7, WT7X66: 14.7, "WT7X72.5": 15.5, "WT7X79.5": 15.6, WT7X88: 15.7, "WT7X96.5": 15.7, WT8X13: 5.5, "WT8X15.5": 5.53, WT8X18: 6.99, WT8X20: 7, "WT8X22.5": 7.04, WT8X25: 7.07, "WT8X28.5": 7.12, "WT8X33.5": 10.2, "WT8X38.5": 10.3, "WT8X44.5": 10.4, WT8X50: 10.4, "WT9X105.5": 11.6, WT9X117: 11.7, WT9X129: 11.8, "WT9X141.5": 11.9, "WT9X155.5": 12, "WT9X17.5": 6, WT9X20: 6.02, WT9X23: 6.06, WT9X25: 7.5, "WT9X27.5": 7.53, WT9X30: 7.56, "WT9X32.5": 7.59, "WT9X35.5": 7.64, WT9X38: 11, WT9X43: 11.1, "WT9X48.5": 11.1, WT9X53: 11.2, "WT9X59.5": 11.3, WT9X65: 11.2, "WT9X71.5": 11.2, WT9X79: 11.3, "WT9X87.5": 11.4, WT9X96: 11.5 };
53398
53469
 
53399
53470
  // index.ts
53400
- var import_node_crypto9 = require("node:crypto");
53471
+ var import_node_crypto10 = require("node:crypto");
53401
53472
 
53402
53473
  // graft.ts
53403
53474
  var TEKLA_MARKER = "Recipe: Tekla model plug-in";
@@ -53694,6 +53765,29 @@ function deleteRequest(id) {
53694
53765
  (0, import_node_fs16.rmSync)((0, import_node_path14.join)(REQUESTS_DIR, file));
53695
53766
  return true;
53696
53767
  }
53768
+ function getRequest(id) {
53769
+ if (!(0, import_node_fs16.existsSync)(REQUESTS_DIR)) return null;
53770
+ const file = (0, import_node_fs16.readdirSync)(REQUESTS_DIR).find((f) => f.endsWith(`__${id}.json`));
53771
+ if (!file) return null;
53772
+ try {
53773
+ return JSON.parse((0, import_node_fs16.readFileSync)((0, import_node_path14.join)(REQUESTS_DIR, file), "utf8"));
53774
+ } catch {
53775
+ return null;
53776
+ }
53777
+ }
53778
+ function patchRequest(id, patch2) {
53779
+ if (!(0, import_node_fs16.existsSync)(REQUESTS_DIR)) return null;
53780
+ const file = (0, import_node_fs16.readdirSync)(REQUESTS_DIR).find((f) => f.endsWith(`__${id}.json`));
53781
+ if (!file) return null;
53782
+ try {
53783
+ const cur = JSON.parse((0, import_node_fs16.readFileSync)((0, import_node_path14.join)(REQUESTS_DIR, file), "utf8"));
53784
+ const next = { ...cur, ...patch2, id: cur.id, createdAt: cur.createdAt };
53785
+ (0, import_node_fs16.writeFileSync)((0, import_node_path14.join)(REQUESTS_DIR, file), JSON.stringify(next, null, 2));
53786
+ return next;
53787
+ } catch {
53788
+ return null;
53789
+ }
53790
+ }
53697
53791
  function clearRequests() {
53698
53792
  if (!(0, import_node_fs16.existsSync)(REQUESTS_DIR)) return 0;
53699
53793
  const allFiles = (0, import_node_fs16.readdirSync)(REQUESTS_DIR);
@@ -53746,14 +53840,190 @@ function decodeSnapshots(inputs) {
53746
53840
  });
53747
53841
  }
53748
53842
 
53843
+ // connection-import.ts
53844
+ var import_node_fs17 = require("node:fs");
53845
+ var import_node_path15 = require("node:path");
53846
+ var AGENT = "connection-reader";
53847
+ var MAX_PARTS = 5e3;
53848
+ var MAX_VERTICES = 3e6;
53849
+ var readerChain = Promise.resolve();
53850
+ var ConnectionImportError = class extends Error {
53851
+ };
53852
+ async function ensureConnectionReader() {
53853
+ try {
53854
+ await aware.ensureAgentInstalled(AGENT);
53855
+ await aware.ensureConnectionReaderBridge();
53856
+ } catch (e) {
53857
+ throw new ConnectionImportError(`could not set up the connection reader \u2014 check your network and try again${e instanceof Error && e.message ? ` (${e.message})` : ""}`);
53858
+ }
53859
+ }
53860
+ function writeReaderApp(companionId, command, ifcPath, id) {
53861
+ const dir = appPath(companionId);
53862
+ (0, import_node_fs17.mkdirSync)(dir, { recursive: true });
53863
+ const flo = (0, import_node_path15.join)(dir, `${companionId}.flo`);
53864
+ const lines = [
53865
+ `app: ${companionId}`,
53866
+ "version: 0.1.0",
53867
+ `display-name: Connection reader (${command})`,
53868
+ `description: ${command === "list" ? "List the steel connections found in an IFC file \u2014 the candidates a user picks from before importing one." : "Read one steel connection from an IFC file as tessellated mesh geometry, for import into the model."}`,
53869
+ "exposes-as-agent: false",
53870
+ "requires:",
53871
+ ` - ${AGENT}@0.1.x`,
53872
+ "layout: linear",
53873
+ "nodes:",
53874
+ ` - id: ${command}`,
53875
+ ` agent: ${AGENT}`,
53876
+ ` command: ${command}`,
53877
+ " config:",
53878
+ ` ifc-path: ${JSON.stringify(ifcPath)}`
53879
+ ];
53880
+ if (command === "extract") lines.push(` id: ${JSON.stringify(id ?? "")}`);
53881
+ lines.push("");
53882
+ (0, import_node_fs17.writeFileSync)(flo, lines.join("\n"));
53883
+ return flo;
53884
+ }
53885
+ function nodeOutput(traceText, node) {
53886
+ const events = traceText ? parseTrace(traceText) : [];
53887
+ const ev = [...events].reverse().find((e) => e.kind === "node-output" && e.node === node);
53888
+ const data = ev ? ev.data : null;
53889
+ return data && typeof data === "object" ? data : null;
53890
+ }
53891
+ async function runReader(companionId, command, ifcPath, id) {
53892
+ const prev = readerChain;
53893
+ let release;
53894
+ readerChain = new Promise((r) => release = r);
53895
+ await prev.catch(() => {
53896
+ });
53897
+ try {
53898
+ const flo = writeReaderApp(companionId, command, ifcPath, id);
53899
+ try {
53900
+ await aware.compile(flo);
53901
+ } catch (e) {
53902
+ throw new ConnectionImportError(`could not compile the connection reader: ${e instanceof Error ? e.message : "compile error"}`);
53903
+ }
53904
+ let traceText;
53905
+ try {
53906
+ ({ traceText } = await aware.run(companionId, {}));
53907
+ } catch (e) {
53908
+ const reason = e instanceof AwareError ? String(e.detail?.stderr ?? "").trim() : "";
53909
+ throw new ConnectionImportError(reason || (e instanceof Error ? e.message : "the connection reader failed"));
53910
+ }
53911
+ const out = nodeOutput(traceText, command);
53912
+ if (!out) throw new ConnectionImportError("the connection reader returned no result");
53913
+ return out;
53914
+ } finally {
53915
+ release();
53916
+ }
53917
+ }
53918
+ async function listConnections(companionId, ifcPath) {
53919
+ await ensureConnectionReader();
53920
+ const out = await runReader(companionId, "list", ifcPath);
53921
+ if (!Array.isArray(out.connections)) {
53922
+ throw new ConnectionImportError(typeof out.message === "string" ? out.message : "no connections found in that IFC file");
53923
+ }
53924
+ return out.connections.filter((c) => !!c && typeof c === "object");
53925
+ }
53926
+ function bboxCenter(parts) {
53927
+ let mnx = Infinity, mny = Infinity, mnz = Infinity, mxx = -Infinity, mxy = -Infinity, mxz = -Infinity;
53928
+ for (const p of parts) {
53929
+ for (let i = 0; i + 2 < p.positions.length; i += 3) {
53930
+ const x = p.positions[i], y = p.positions[i + 1], z = p.positions[i + 2];
53931
+ if (x < mnx) mnx = x;
53932
+ if (x > mxx) mxx = x;
53933
+ if (y < mny) mny = y;
53934
+ if (y > mxy) mxy = y;
53935
+ if (z < mnz) mnz = z;
53936
+ if (z > mxz) mxz = z;
53937
+ }
53938
+ }
53939
+ if (!isFinite(mnx)) return [0, 0, 0];
53940
+ return [(mnx + mxx) / 2, (mny + mxy) / 2, (mnz + mxz) / 2];
53941
+ }
53942
+ function shiftPositions(positions, [dx, dy, dz]) {
53943
+ const out = new Array(positions.length);
53944
+ for (let i = 0; i + 2 < positions.length; i += 3) {
53945
+ out[i] = positions[i] - dx;
53946
+ out[i + 1] = positions[i + 1] - dy;
53947
+ out[i + 2] = positions[i + 2] - dz;
53948
+ }
53949
+ return out;
53950
+ }
53951
+ function validPart(p) {
53952
+ const q = p;
53953
+ return !!q && Array.isArray(q.positions) && Array.isArray(q.indices) && q.positions.length >= 9 && q.indices.length >= 3 && q.positions.length % 3 === 0;
53954
+ }
53955
+ function toCustomConnection(raw, fallbackId) {
53956
+ const conn = raw.connection;
53957
+ if (!conn || typeof conn !== "object") {
53958
+ throw new ConnectionImportError(typeof raw.message === "string" ? raw.message : "could not read that connection");
53959
+ }
53960
+ const c = conn;
53961
+ const valid = (Array.isArray(c.parts) ? c.parts : []).filter(validPart);
53962
+ if (valid.length > MAX_PARTS) throw new ConnectionImportError(`that connection has too many parts (${valid.length}) to import`);
53963
+ let totalVerts = 0;
53964
+ for (const p of valid) totalVerts += p.positions.length / 3;
53965
+ if (totalVerts > MAX_VERTICES) throw new ConnectionImportError(`that connection is too large to import (${Math.round(totalVerts / 1e3)}k vertices)`);
53966
+ const anchor = bboxCenter(valid);
53967
+ const geometry = valid.map((p) => ({
53968
+ ...typeof p.id === "string" ? { id: p.id } : {},
53969
+ ...typeof p.role === "string" ? { role: p.role } : {},
53970
+ positions: shiftPositions(p.positions, anchor),
53971
+ indices: p.indices.slice()
53972
+ }));
53973
+ const recipe = sanitizeRecipe(c.recipe);
53974
+ return {
53975
+ id: typeof c.id === "string" ? c.id : fallbackId,
53976
+ name: typeof c.name === "string" ? c.name : "Imported connection",
53977
+ type: typeof c.type === "string" ? c.type : null,
53978
+ members: Array.isArray(c.members) ? c.members.filter((m) => typeof m === "string") : [],
53979
+ geometry,
53980
+ ...recipe ? { recipe } : {}
53981
+ };
53982
+ }
53983
+ var BASE_PLATE_PARAMS = {
53984
+ thickness: [1, 500],
53985
+ plateWidth: [1, 5e3],
53986
+ plateDepth: [1, 5e3],
53987
+ boltDia: [1, 200],
53988
+ boltCols: [1, 20],
53989
+ boltRows: [1, 20],
53990
+ edgeDist: [0, 5e3],
53991
+ weldLeg: [0, 200]
53992
+ };
53993
+ function sanitizeRecipe(raw) {
53994
+ if (!raw || typeof raw !== "object") return void 0;
53995
+ const r = raw;
53996
+ if (typeof r.kind !== "string" || !r.kind || !r.params || typeof r.params !== "object") return void 0;
53997
+ const num2 = {};
53998
+ for (const [k, v] of Object.entries(r.params)) {
53999
+ if (typeof v === "number" && isFinite(v)) num2[k] = v;
54000
+ }
54001
+ if (r.kind === "base-plate") {
54002
+ const params = {};
54003
+ for (const [k, [lo, hi]] of Object.entries(BASE_PLATE_PARAMS)) {
54004
+ if (k in num2) {
54005
+ if (!(num2[k] >= lo && num2[k] <= hi)) return void 0;
54006
+ params[k] = num2[k];
54007
+ }
54008
+ }
54009
+ return Object.keys(params).length ? { kind: r.kind, params } : void 0;
54010
+ }
54011
+ return Object.keys(num2).length ? { kind: r.kind, params: num2 } : void 0;
54012
+ }
54013
+ async function extractConnection(companionId, ifcPath, id) {
54014
+ await ensureConnectionReader();
54015
+ const out = await runReader(companionId, "extract", ifcPath, id);
54016
+ return toCustomConnection(out, id);
54017
+ }
54018
+
53749
54019
  // contract-store.ts
53750
- var import_node_fs18 = require("node:fs");
53751
- var import_node_path16 = require("node:path");
54020
+ var import_node_fs19 = require("node:fs");
54021
+ var import_node_path17 = require("node:path");
53752
54022
  var import_node_os12 = require("node:os");
53753
54023
 
53754
54024
  // contract-schema.ts
53755
- var import_node_fs17 = require("node:fs");
53756
- var import_node_path15 = require("node:path");
54025
+ var import_node_fs18 = require("node:fs");
54026
+ var import_node_path16 = require("node:path");
53757
54027
  var import_node_url2 = require("node:url");
53758
54028
  function validate(doc, schema) {
53759
54029
  const errors = [];
@@ -53855,16 +54125,16 @@ var _cache = /* @__PURE__ */ new Map();
53855
54125
  function loadContractSchema(file) {
53856
54126
  const hit = _cache.get(file);
53857
54127
  if (hit) return hit;
53858
- const here2 = (0, import_node_path15.dirname)((0, import_node_url2.fileURLToPath)(__import_meta_url));
54128
+ const here2 = (0, import_node_path16.dirname)((0, import_node_url2.fileURLToPath)(__import_meta_url));
53859
54129
  const candidates = [
53860
- (0, import_node_path15.join)(here2, "..", "schemas", file),
54130
+ (0, import_node_path16.join)(here2, "..", "schemas", file),
53861
54131
  // dev: server/ next to schemas/
53862
- (0, import_node_path15.join)(here2, "schemas", file)
54132
+ (0, import_node_path16.join)(here2, "schemas", file)
53863
54133
  // bundled: dist/ holds ./schemas
53864
54134
  ];
53865
54135
  for (const p of candidates) {
53866
54136
  try {
53867
- const parsed = JSON.parse((0, import_node_fs17.readFileSync)(p, "utf8"));
54137
+ const parsed = JSON.parse((0, import_node_fs18.readFileSync)(p, "utf8"));
53868
54138
  _cache.set(file, parsed);
53869
54139
  return parsed;
53870
54140
  } catch (err2) {
@@ -53894,8 +54164,8 @@ function validateContract(doc) {
53894
54164
  // contract-store.ts
53895
54165
  var ContractError = class extends Error {
53896
54166
  };
53897
- var ROOT3 = process.env.FLOLESS_HOME ?? (0, import_node_path16.join)((0, import_node_os12.homedir)(), ".floless");
53898
- var DIR = (0, import_node_path16.join)(ROOT3, "contracts");
54167
+ var ROOT3 = process.env.FLOLESS_HOME ?? (0, import_node_path17.join)((0, import_node_os12.homedir)(), ".floless");
54168
+ var DIR = (0, import_node_path17.join)(ROOT3, "contracts");
53899
54169
  function safeId(appId) {
53900
54170
  if (!/^[a-z0-9][a-z0-9._-]*$/i.test(appId)) throw new ContractError(`invalid appId: ${appId}`);
53901
54171
  return appId;
@@ -53905,14 +54175,14 @@ function safeProjectSeg(projectId) {
53905
54175
  return projectId;
53906
54176
  }
53907
54177
  function contractPath(appId, projectId) {
53908
- if (projectId) return (0, import_node_path16.join)(ROOT3, "projects", safeProjectSeg(projectId), "contract.json");
53909
- return (0, import_node_path16.join)(DIR, `${safeId(appId)}.json`);
54178
+ if (projectId) return (0, import_node_path17.join)(ROOT3, "projects", safeProjectSeg(projectId), "contract.json");
54179
+ return (0, import_node_path17.join)(DIR, `${safeId(appId)}.json`);
53910
54180
  }
53911
54181
  function readContract(appId, projectId) {
53912
54182
  const p = contractPath(appId, projectId);
53913
- if (!(0, import_node_fs18.existsSync)(p)) return null;
54183
+ if (!(0, import_node_fs19.existsSync)(p)) return null;
53914
54184
  try {
53915
- return JSON.parse((0, import_node_fs18.readFileSync)(p, "utf8"));
54185
+ return JSON.parse((0, import_node_fs19.readFileSync)(p, "utf8"));
53916
54186
  } catch (e) {
53917
54187
  console.warn(`readContract: ignoring unreadable contract at ${p}: ${e instanceof Error ? e.message : e}`);
53918
54188
  return null;
@@ -53926,11 +54196,11 @@ function writeContract(appId, doc, projectId) {
53926
54196
  throw new ContractError(`contract failed schema validation \u2014 ${first}`);
53927
54197
  }
53928
54198
  if (projectId) {
53929
- if (!(0, import_node_fs18.existsSync)((0, import_node_path16.dirname)(p))) throw new ContractError(`unknown project: ${projectId}`);
53930
- } else if (!(0, import_node_fs18.existsSync)(DIR)) {
53931
- (0, import_node_fs18.mkdirSync)(DIR, { recursive: true });
54199
+ if (!(0, import_node_fs19.existsSync)((0, import_node_path17.dirname)(p))) throw new ContractError(`unknown project: ${projectId}`);
54200
+ } else if (!(0, import_node_fs19.existsSync)(DIR)) {
54201
+ (0, import_node_fs19.mkdirSync)(DIR, { recursive: true });
53932
54202
  }
53933
- (0, import_node_fs18.writeFileSync)(p, JSON.stringify(doc));
54203
+ (0, import_node_fs19.writeFileSync)(p, JSON.stringify(doc));
53934
54204
  }
53935
54205
 
53936
54206
  // contract-resolve.ts
@@ -53953,15 +54223,15 @@ function readContractForApp(appId, readAppFn = readApp) {
53953
54223
  }
53954
54224
 
53955
54225
  // projects-store.ts
53956
- var import_node_fs19 = require("node:fs");
53957
- var import_node_path17 = require("node:path");
54226
+ var import_node_fs20 = require("node:fs");
54227
+ var import_node_path18 = require("node:path");
53958
54228
  var import_node_os13 = require("node:os");
53959
54229
  var import_node_crypto6 = require("node:crypto");
53960
54230
  var ProjectError = class extends Error {
53961
54231
  };
53962
- var ROOT4 = process.env.FLOLESS_HOME ?? (0, import_node_path17.join)((0, import_node_os13.homedir)(), ".floless");
53963
- var DIR2 = (0, import_node_path17.join)(ROOT4, "projects");
53964
- var ARCHIVE = (0, import_node_path17.join)(DIR2, ".archive");
54232
+ var ROOT4 = process.env.FLOLESS_HOME ?? (0, import_node_path18.join)((0, import_node_os13.homedir)(), ".floless");
54233
+ var DIR2 = (0, import_node_path18.join)(ROOT4, "projects");
54234
+ var ARCHIVE = (0, import_node_path18.join)(DIR2, ".archive");
53965
54235
  function safeProjectId(id) {
53966
54236
  if (typeof id !== "string" || !/^[a-z0-9][a-z0-9-]*$/.test(id)) {
53967
54237
  throw new ProjectError(`invalid project id: ${String(id)}`);
@@ -53973,28 +54243,28 @@ function slugify(name) {
53973
54243
  return s || "project";
53974
54244
  }
53975
54245
  function projectDir(id) {
53976
- return (0, import_node_path17.join)(DIR2, safeProjectId(id));
54246
+ return (0, import_node_path18.join)(DIR2, safeProjectId(id));
53977
54247
  }
53978
54248
  function projectExportsDir(id) {
53979
- return (0, import_node_path17.join)(projectDir(id), "exports");
54249
+ return (0, import_node_path18.join)(projectDir(id), "exports");
53980
54250
  }
53981
- var metaPath = (id) => (0, import_node_path17.join)(projectDir(id), "project.json");
54251
+ var metaPath = (id) => (0, import_node_path18.join)(projectDir(id), "project.json");
53982
54252
  function readMeta(id) {
53983
54253
  const p = metaPath(id);
53984
- if (!(0, import_node_fs19.existsSync)(p)) return null;
54254
+ if (!(0, import_node_fs20.existsSync)(p)) return null;
53985
54255
  try {
53986
- const meta = JSON.parse((0, import_node_fs19.readFileSync)(p, "utf8"));
54256
+ const meta = JSON.parse((0, import_node_fs20.readFileSync)(p, "utf8"));
53987
54257
  return meta && meta.id === id ? meta : null;
53988
54258
  } catch (e) {
53989
54259
  console.warn(`projects-store: ignoring unreadable project.json at ${p}: ${e instanceof Error ? e.message : e}`);
53990
54260
  return null;
53991
54261
  }
53992
54262
  }
53993
- var writeMeta = (meta) => (0, import_node_fs19.writeFileSync)(metaPath(meta.id), JSON.stringify(meta, null, 2));
54263
+ var writeMeta = (meta) => (0, import_node_fs20.writeFileSync)(metaPath(meta.id), JSON.stringify(meta, null, 2));
53994
54264
  function listProjects() {
53995
- if (!(0, import_node_fs19.existsSync)(DIR2)) return [];
54265
+ if (!(0, import_node_fs20.existsSync)(DIR2)) return [];
53996
54266
  const out = [];
53997
- for (const entry2 of (0, import_node_fs19.readdirSync)(DIR2, { withFileTypes: true })) {
54267
+ for (const entry2 of (0, import_node_fs20.readdirSync)(DIR2, { withFileTypes: true })) {
53998
54268
  if (!entry2.isDirectory() || entry2.name.startsWith(".")) continue;
53999
54269
  const meta = readMeta(entry2.name);
54000
54270
  if (meta) out.push(meta);
@@ -54010,8 +54280,8 @@ function createProject(input) {
54010
54280
  if (!name) throw new ProjectError("project name required");
54011
54281
  if (!/^[a-z0-9][a-z0-9._-]*$/i.test(app)) throw new ProjectError(`invalid app id: ${app || "(empty)"}`);
54012
54282
  let id = `${slugify(name)}-${(0, import_node_crypto6.randomBytes)(3).toString("hex")}`;
54013
- while ((0, import_node_fs19.existsSync)(projectDir(id))) id = `${slugify(name)}-${(0, import_node_crypto6.randomBytes)(3).toString("hex")}`;
54014
- (0, import_node_fs19.mkdirSync)(projectDir(id), { recursive: true });
54283
+ while ((0, import_node_fs20.existsSync)(projectDir(id))) id = `${slugify(name)}-${(0, import_node_crypto6.randomBytes)(3).toString("hex")}`;
54284
+ (0, import_node_fs20.mkdirSync)(projectDir(id), { recursive: true });
54015
54285
  const now = (/* @__PURE__ */ new Date()).toISOString();
54016
54286
  const meta = { id, name, app, createdAt: now, updatedAt: now };
54017
54287
  writeMeta(meta);
@@ -54046,7 +54316,7 @@ function duplicateProject(id, name) {
54046
54316
  name: typeof name === "string" && name.trim() ? name.trim() : `${src.name} (copy)`,
54047
54317
  app: src.app
54048
54318
  });
54049
- (0, import_node_fs19.cpSync)(projectDir(src.id), projectDir(copy.id), {
54319
+ (0, import_node_fs20.cpSync)(projectDir(src.id), projectDir(copy.id), {
54050
54320
  recursive: true,
54051
54321
  filter: (p) => !p.endsWith("project.json")
54052
54322
  });
@@ -54054,36 +54324,42 @@ function duplicateProject(id, name) {
54054
54324
  }
54055
54325
  function archiveProject(id) {
54056
54326
  const dir = projectDir(id);
54057
- if (!(0, import_node_fs19.existsSync)(dir)) throw new ProjectError(`unknown project: ${id}`);
54058
- (0, import_node_fs19.mkdirSync)(ARCHIVE, { recursive: true });
54059
- const dest = (0, import_node_path17.join)(ARCHIVE, id);
54060
- if ((0, import_node_fs19.existsSync)(dest)) throw new ProjectError(`already archived: ${id}`);
54061
- (0, import_node_fs19.renameSync)(dir, dest);
54327
+ if (!(0, import_node_fs20.existsSync)(dir)) throw new ProjectError(`unknown project: ${id}`);
54328
+ (0, import_node_fs20.mkdirSync)(ARCHIVE, { recursive: true });
54329
+ const dest = (0, import_node_path18.join)(ARCHIVE, id);
54330
+ if ((0, import_node_fs20.existsSync)(dest)) throw new ProjectError(`already archived: ${id}`);
54331
+ (0, import_node_fs20.renameSync)(dir, dest);
54062
54332
  }
54063
54333
 
54064
54334
  // project-versions-store.ts
54065
54335
  var import_node_crypto7 = require("node:crypto");
54066
- var import_node_fs20 = require("node:fs");
54067
- var import_node_path18 = require("node:path");
54336
+ var import_node_fs21 = require("node:fs");
54337
+ var import_node_path19 = require("node:path");
54068
54338
  var BLOB_MIN = 8192;
54069
54339
  var BLOB_KEY = "__blobRef__";
54070
54340
  var HASH_RE = /^[0-9a-f]{64}$/;
54071
- var versionsDir = (id) => (0, import_node_path18.join)(projectDir(id), "versions");
54072
- var objectsDir = (id) => (0, import_node_path18.join)(projectDir(id), "objects");
54073
- var logPath = (id) => (0, import_node_path18.join)(versionsDir(id), "log.json");
54341
+ var versionsDir = (id) => (0, import_node_path19.join)(projectDir(id), "versions");
54342
+ var objectsDir = (id) => (0, import_node_path19.join)(projectDir(id), "objects");
54343
+ var logPath = (id) => (0, import_node_path19.join)(versionsDir(id), "log.json");
54074
54344
  function writeAtomic(path, text) {
54075
54345
  const tmp = `${path}.${process.pid}.tmp`;
54076
- (0, import_node_fs20.writeFileSync)(tmp, text);
54077
- (0, import_node_fs20.renameSync)(tmp, path);
54346
+ (0, import_node_fs21.writeFileSync)(tmp, text);
54347
+ (0, import_node_fs21.renameSync)(tmp, path);
54078
54348
  }
54079
54349
  function isVersionMeta(v) {
54080
54350
  if (!v || typeof v !== "object") return false;
54081
54351
  const r = v;
54082
- return Number.isInteger(r.n) && typeof r.ts === "string" && typeof r.author === "string" && typeof r.message === "string" && (r.gate === "model" || r.gate === null) && (r.kind === "approve" || r.kind === "rollback" || r.kind === "rebake");
54352
+ return Number.isInteger(r.n) && typeof r.ts === "string" && typeof r.author === "string" && typeof r.message === "string" && (r.gate === "model" || r.gate === null) && (r.kind === "approve" || r.kind === "rollback" || r.kind === "rebake" || r.kind === "revision-read") && isValidSource(r.source);
54353
+ }
54354
+ function isValidSource(s) {
54355
+ if (s === void 0) return true;
54356
+ if (!s || typeof s !== "object") return false;
54357
+ const files2 = s.files;
54358
+ return Array.isArray(files2) && files2.every((f) => !!f && typeof f === "object" && typeof f.sha256 === "string");
54083
54359
  }
54084
54360
  function loadLog(id) {
54085
54361
  try {
54086
- const parsed = JSON.parse((0, import_node_fs20.readFileSync)(logPath(id), "utf8"));
54362
+ const parsed = JSON.parse((0, import_node_fs21.readFileSync)(logPath(id), "utf8"));
54087
54363
  const raw = parsed && typeof parsed === "object" && Array.isArray(parsed.versions) ? parsed.versions : [];
54088
54364
  return { versions: raw.filter(isVersionMeta) };
54089
54365
  } catch {
@@ -54114,7 +54390,7 @@ function rehydrate(node, id) {
54114
54390
  const keys = Object.keys(obj);
54115
54391
  const ref = obj[BLOB_KEY];
54116
54392
  if (keys.length === 1 && keys[0] === BLOB_KEY && typeof ref === "string" && HASH_RE.test(ref)) {
54117
- return (0, import_node_fs20.readFileSync)((0, import_node_path18.join)(objectsDir(id), ref), "utf8");
54393
+ return (0, import_node_fs21.readFileSync)((0, import_node_path19.join)(objectsDir(id), ref), "utf8");
54118
54394
  }
54119
54395
  const out = {};
54120
54396
  for (const [k, v] of Object.entries(obj)) out[k] = rehydrate(v, id);
@@ -54123,13 +54399,13 @@ function rehydrate(node, id) {
54123
54399
  return node;
54124
54400
  }
54125
54401
  function createVersion(id, contract, meta) {
54126
- (0, import_node_fs20.mkdirSync)(versionsDir(id), { recursive: true });
54127
- (0, import_node_fs20.mkdirSync)(objectsDir(id), { recursive: true });
54402
+ (0, import_node_fs21.mkdirSync)(versionsDir(id), { recursive: true });
54403
+ (0, import_node_fs21.mkdirSync)(objectsDir(id), { recursive: true });
54128
54404
  const blobs = /* @__PURE__ */ new Map();
54129
54405
  const skeleton = dehydrate(contract, blobs);
54130
54406
  for (const [hash, text] of blobs) {
54131
- const p = (0, import_node_path18.join)(objectsDir(id), hash);
54132
- if (!(0, import_node_fs20.existsSync)(p)) writeAtomic(p, text);
54407
+ const p = (0, import_node_path19.join)(objectsDir(id), hash);
54408
+ if (!(0, import_node_fs21.existsSync)(p)) writeAtomic(p, text);
54133
54409
  }
54134
54410
  const log2 = loadLog(id);
54135
54411
  const n = (log2.versions.at(-1)?.n ?? 0) + 1;
@@ -54139,9 +54415,11 @@ function createVersion(id, contract, meta) {
54139
54415
  author: meta.author,
54140
54416
  message: meta.message,
54141
54417
  gate: meta.gate,
54142
- kind: meta.kind
54418
+ kind: meta.kind,
54419
+ ...meta.source ? { source: meta.source } : {}
54420
+ // revision-read provenance (small metadata; not dehydrated)
54143
54421
  };
54144
- writeAtomic((0, import_node_path18.join)(versionsDir(id), `${n}.json`), JSON.stringify({ meta: row, contract: skeleton }, null, 2));
54422
+ writeAtomic((0, import_node_path19.join)(versionsDir(id), `${n}.json`), JSON.stringify({ meta: row, contract: skeleton }, null, 2));
54145
54423
  log2.versions.push(row);
54146
54424
  writeAtomic(logPath(id), JSON.stringify(log2, null, 2));
54147
54425
  return row;
@@ -54153,18 +54431,38 @@ function listVersions(id) {
54153
54431
  }
54154
54432
  function readVersion(id, n) {
54155
54433
  try {
54156
- const snap = JSON.parse((0, import_node_fs20.readFileSync)((0, import_node_path18.join)(versionsDir(id), `${n}.json`), "utf8"));
54434
+ const snap = JSON.parse((0, import_node_fs21.readFileSync)((0, import_node_path19.join)(versionsDir(id), `${n}.json`), "utf8"));
54157
54435
  return rehydrate(snap.contract, id);
54158
54436
  } catch {
54159
54437
  return null;
54160
54438
  }
54161
54439
  }
54162
54440
 
54441
+ // project-sources-store.ts
54442
+ var import_node_crypto8 = require("node:crypto");
54443
+ var import_node_fs22 = require("node:fs");
54444
+ var import_node_path20 = require("node:path");
54445
+ var sourcesDir = (id) => (0, import_node_path20.join)(projectDir(id), "sources");
54446
+ var safeExt = (ext) => typeof ext === "string" && /^[a-z0-9]{1,8}$/i.test(ext) ? ext.toLowerCase() : "bin";
54447
+ function writeAtomic2(path, buf) {
54448
+ const tmp = `${path}.${process.pid}.tmp`;
54449
+ (0, import_node_fs22.writeFileSync)(tmp, buf);
54450
+ (0, import_node_fs22.renameSync)(tmp, path);
54451
+ }
54452
+ function saveSource(id, file) {
54453
+ (0, import_node_fs22.mkdirSync)(sourcesDir(id), { recursive: true });
54454
+ const sha2562 = (0, import_node_crypto8.createHash)("sha256").update(file.buf).digest("hex");
54455
+ const ext = safeExt(file.ext);
54456
+ const p = (0, import_node_path20.join)(sourcesDir(id), `${sha2562}.${ext}`);
54457
+ if (!(0, import_node_fs22.existsSync)(p)) writeAtomic2(p, file.buf);
54458
+ return { name: String(file.name), sha256: sha2562, ext, bytes: file.buf.length };
54459
+ }
54460
+
54163
54461
  // contract-bake.ts
54164
- var import_node_fs21 = require("node:fs");
54462
+ var import_node_fs23 = require("node:fs");
54165
54463
  var import_yaml5 = __toESM(require_dist6(), 1);
54166
54464
  function bakeContractIntoApp(sourcePath, contract) {
54167
- const doc = (0, import_yaml5.parseDocument)((0, import_node_fs21.readFileSync)(sourcePath, "utf8"));
54465
+ const doc = (0, import_yaml5.parseDocument)((0, import_node_fs23.readFileSync)(sourcePath, "utf8"));
54168
54466
  if (doc.errors.length > 0) {
54169
54467
  throw new Error(`contract bake: source is not valid YAML: ${doc.errors[0]?.message ?? "parse error"}`);
54170
54468
  }
@@ -54214,7 +54512,7 @@ function bakeContractIntoApp(sourcePath, contract) {
54214
54512
  );
54215
54513
  }
54216
54514
  doc.setIn(["nodes", idx, "config", "takeoff"], baked);
54217
- (0, import_node_fs21.writeFileSync)(sourcePath, doc.toString());
54515
+ (0, import_node_fs23.writeFileSync)(sourcePath, doc.toString());
54218
54516
  }
54219
54517
 
54220
54518
  // vectorize.ts
@@ -54537,8 +54835,12 @@ var GROUPS = {
54537
54835
  bolt: { key: "bolt", label: "Bolts", color: "#b0c4d8" },
54538
54836
  // cool zinc-grey (matches the anchor fasteners)
54539
54837
  stiffener: { key: "stiffener", label: "Stiffeners", color: "#9aafc3" },
54540
- cope: { key: "cope", label: "Copes", color: "#c2703a" }
54838
+ cope: { key: "cope", label: "Copes", color: "#c2703a" },
54541
54839
  // a muted cut/burn amber — the removed corner (subtractive; rendered as the notch, not a swatch mesh)
54840
+ // Imported (custom) connection — one neutral steel band for the whole opaque assembly, so it reads
54841
+ // as a single imported unit (not scattered across the recipe-part legend rows). Roles (plate/bolt/
54842
+ // weld) are kept on each part's meta for labels, but share this one legend group + colour.
54843
+ custom: { key: "custom", label: "Imported connections", color: "#8a97a8" }
54542
54844
  };
54543
54845
  var EMBED = 1;
54544
54846
  function num(params, key, def) {
@@ -54897,17 +55199,48 @@ function expandShearPlate(joint, beam, support) {
54897
55199
  if (cope.bottom) pushCope("bottom", cope.bottom);
54898
55200
  return parts;
54899
55201
  }
55202
+ function expandCustom(joint) {
55203
+ const place = finite3(joint.place) ? joint.place : [0, 0, 0];
55204
+ const [dx, dy, dz] = place;
55205
+ const out = [];
55206
+ (joint.geometry ?? []).forEach((g, i) => {
55207
+ if (!g || !Array.isArray(g.positions) || !Array.isArray(g.indices)) return;
55208
+ if (g.positions.length < 9 || g.indices.length < 3 || g.positions.length % 3 !== 0) return;
55209
+ const positions = new Array(g.positions.length);
55210
+ for (let k = 0; k < g.positions.length; k += 3) {
55211
+ positions[k] = g.positions[k] + dx;
55212
+ positions[k + 1] = g.positions[k + 1] + dy;
55213
+ positions[k + 2] = g.positions[k + 2] + dz;
55214
+ }
55215
+ const role = typeof g.role === "string" ? g.role : void 0;
55216
+ const label = role ? role.charAt(0).toUpperCase() + role.slice(1) : "Imported part";
55217
+ out.push({ id: `${joint.id}:${g.id || `m${i}`}`, group: "custom", kind: "mesh", positions, indices: g.indices.slice(), meta: { label, role } });
55218
+ });
55219
+ return out;
55220
+ }
54900
55221
  function expandJoints(joints, memberGeo) {
54901
55222
  const elements = [];
54902
55223
  const skipped = [];
54903
55224
  const usedGroups = /* @__PURE__ */ new Set();
54904
55225
  (joints ?? []).forEach((j, i) => {
54905
- if (!j || !j.id || !j.main) {
54906
- skipped.push(j?.id || `joint#${i}`);
55226
+ if (!j || !j.id) {
55227
+ skipped.push(`joint#${i}`);
54907
55228
  return;
54908
55229
  }
54909
- if (j.kind === "base-plate") {
54910
- const col = memberGeo.get(j.main);
55230
+ if (j.kind === "custom") {
55231
+ const parts = expandCustom(j);
55232
+ if (!parts.length) {
55233
+ skipped.push(j.id);
55234
+ return;
55235
+ }
55236
+ for (const part of parts) {
55237
+ part.conn = j.id;
55238
+ part.connKind = j.kind;
55239
+ elements.push(part);
55240
+ usedGroups.add(part.group);
55241
+ }
55242
+ } else if (j.kind === "base-plate") {
55243
+ const col = j.main ? memberGeo.get(j.main) : void 0;
54911
55244
  if (!col || col.role !== "column") {
54912
55245
  skipped.push(j.id);
54913
55246
  return;
@@ -54923,7 +55256,7 @@ function expandJoints(joints, memberGeo) {
54923
55256
  usedGroups.add(part.group);
54924
55257
  }
54925
55258
  } else if (j.kind === "shear-plate") {
54926
- const beam = memberGeo.get(j.main);
55259
+ const beam = j.main ? memberGeo.get(j.main) : void 0;
54927
55260
  if (!beam || beam.role !== "beam") {
54928
55261
  skipped.push(j.id);
54929
55262
  return;
@@ -55145,10 +55478,10 @@ function contractToBom(contractInput) {
55145
55478
 
55146
55479
  // bom-export.ts
55147
55480
  var import_node_os14 = require("node:os");
55148
- var import_node_path19 = require("node:path");
55481
+ var import_node_path21 = require("node:path");
55149
55482
 
55150
55483
  // node_modules/write-excel-file/modules/export/writeXlsxFileNode.js
55151
- var import_node_fs22 = __toESM(require("node:fs"), 1);
55484
+ var import_node_fs24 = __toESM(require("node:fs"), 1);
55152
55485
 
55153
55486
  // node_modules/write-excel-file/modules/xlsx/helpers/features/getAdditionalContent.js
55154
55487
  function _createForOfIteratorHelperLoose(r, e) {
@@ -59320,7 +59653,7 @@ function writeXlsxFile(arg1, arg2, arg3) {
59320
59653
  },
59321
59654
  toFile: function toFile(filePath) {
59322
59655
  return createReadableStream().then(function(readableStream) {
59323
- return pipe(readableStream, import_node_fs22.default.createWriteStream(filePath));
59656
+ return pipe(readableStream, import_node_fs24.default.createWriteStream(filePath));
59324
59657
  });
59325
59658
  }
59326
59659
  };
@@ -59345,8 +59678,8 @@ function pipe(readableStream, writableStream) {
59345
59678
 
59346
59679
  // bom-export.ts
59347
59680
  function bomExportPath(appId, format) {
59348
- const root = process.env.FLOLESS_HOME ?? (0, import_node_path19.join)((0, import_node_os14.homedir)(), ".floless");
59349
- return (0, import_node_path19.join)(root, "exports", appId, `${appId}-bom.${format}`);
59681
+ const root = process.env.FLOLESS_HOME ?? (0, import_node_path21.join)((0, import_node_os14.homedir)(), ".floless");
59682
+ return (0, import_node_path21.join)(root, "exports", appId, `${appId}-bom.${format}`);
59350
59683
  }
59351
59684
  function csvField(v) {
59352
59685
  let s = String(v);
@@ -59374,11 +59707,11 @@ async function bomToXlsx(bom) {
59374
59707
  }
59375
59708
 
59376
59709
  // scene-bake.ts
59377
- var import_node_fs23 = require("node:fs");
59378
- var import_node_path20 = require("node:path");
59710
+ var import_node_fs25 = require("node:fs");
59711
+ var import_node_path22 = require("node:path");
59379
59712
  var import_yaml6 = __toESM(require_dist6(), 1);
59380
59713
  function bakeSceneIntoApp(sourcePath, scene, agent = "viewer-3d") {
59381
- const doc = (0, import_yaml6.parseDocument)((0, import_node_fs23.readFileSync)(sourcePath, "utf8"));
59714
+ const doc = (0, import_yaml6.parseDocument)((0, import_node_fs25.readFileSync)(sourcePath, "utf8"));
59382
59715
  if (doc.errors.length > 0) {
59383
59716
  throw new Error(`scene bake: source is not valid YAML: ${doc.errors[0]?.message ?? "parse error"}`);
59384
59717
  }
@@ -59387,10 +59720,10 @@ function bakeSceneIntoApp(sourcePath, scene, agent = "viewer-3d") {
59387
59720
  const idx = items.findIndex((it) => ((0, import_yaml6.isMap)(it) ? it.toJSON() : null)?.agent === agent);
59388
59721
  if (idx < 0) throw new Error(`no ${agent} render node to bake the scene into`);
59389
59722
  doc.setIn(["nodes", idx, "config", "scene"], scene);
59390
- (0, import_node_fs23.writeFileSync)(sourcePath, doc.toString());
59723
+ (0, import_node_fs25.writeFileSync)(sourcePath, doc.toString());
59391
59724
  }
59392
59725
  function bakeNodeConfigById(sourcePath, nodeId, patch2) {
59393
- const doc = (0, import_yaml6.parseDocument)((0, import_node_fs23.readFileSync)(sourcePath, "utf8"));
59726
+ const doc = (0, import_yaml6.parseDocument)((0, import_node_fs25.readFileSync)(sourcePath, "utf8"));
59394
59727
  if (doc.errors.length > 0) {
59395
59728
  throw new Error(`node bake: source is not valid YAML: ${doc.errors[0]?.message ?? "parse error"}`);
59396
59729
  }
@@ -59399,12 +59732,12 @@ function bakeNodeConfigById(sourcePath, nodeId, patch2) {
59399
59732
  const idx = items.findIndex((it) => ((0, import_yaml6.isMap)(it) ? it.toJSON() : null)?.id === nodeId);
59400
59733
  if (idx < 0) throw new Error(`no node with id "${nodeId}"`);
59401
59734
  for (const [key, value] of Object.entries(patch2)) doc.setIn(["nodes", idx, "config", key], value);
59402
- (0, import_node_fs23.writeFileSync)(sourcePath, doc.toString());
59735
+ (0, import_node_fs25.writeFileSync)(sourcePath, doc.toString());
59403
59736
  }
59404
59737
  function writeViewer3dApp(dir, appId, scene) {
59405
- (0, import_node_fs23.mkdirSync)(dir, { recursive: true });
59406
- const flo = (0, import_node_path20.join)(dir, `${appId}.flo`);
59407
- (0, import_node_fs23.writeFileSync)(flo, [
59738
+ (0, import_node_fs25.mkdirSync)(dir, { recursive: true });
59739
+ const flo = (0, import_node_path22.join)(dir, `${appId}.flo`);
59740
+ (0, import_node_fs25.writeFileSync)(flo, [
59408
59741
  `app: ${appId}`,
59409
59742
  "version: 0.1.0",
59410
59743
  "display-name: Steel 3D",
@@ -59425,11 +59758,11 @@ function writeViewer3dApp(dir, appId, scene) {
59425
59758
  return flo;
59426
59759
  }
59427
59760
  function writeIfcApp(dir, appId, scene, outputPath, opts = {}) {
59428
- (0, import_node_fs23.mkdirSync)(dir, { recursive: true });
59429
- const flo = (0, import_node_path20.join)(dir, `${appId}.flo`);
59761
+ (0, import_node_fs25.mkdirSync)(dir, { recursive: true });
59762
+ const flo = (0, import_node_path22.join)(dir, `${appId}.flo`);
59430
59763
  const displayName = opts.displayName ?? "Steel IFC";
59431
59764
  const description = opts.description ?? "IFC file written from an approved steel.takeoff/v1 contract (a companion export; the contract is the source of truth).";
59432
- (0, import_node_fs23.writeFileSync)(flo, [
59765
+ (0, import_node_fs25.writeFileSync)(flo, [
59433
59766
  `app: ${appId}`,
59434
59767
  "version: 0.1.0",
59435
59768
  `display-name: ${displayName}`,
@@ -59452,9 +59785,9 @@ function writeIfcApp(dir, appId, scene, outputPath, opts = {}) {
59452
59785
  return flo;
59453
59786
  }
59454
59787
  function writeTeklaApp(dir, appId, scene, teklaVersion = "2026.0") {
59455
- (0, import_node_fs23.mkdirSync)(dir, { recursive: true });
59456
- const flo = (0, import_node_path20.join)(dir, `${appId}.flo`);
59457
- (0, import_node_fs23.writeFileSync)(flo, [
59788
+ (0, import_node_fs25.mkdirSync)(dir, { recursive: true });
59789
+ const flo = (0, import_node_path22.join)(dir, `${appId}.flo`);
59790
+ (0, import_node_fs25.writeFileSync)(flo, [
59458
59791
  `app: ${appId}`,
59459
59792
  "version: 0.1.0",
59460
59793
  "display-name: Steel to Tekla",
@@ -59862,13 +60195,13 @@ function scoreContract2(contractInput) {
59862
60195
  }
59863
60196
 
59864
60197
  // app-lifecycle.ts
59865
- var import_node_fs26 = require("node:fs");
60198
+ var import_node_fs28 = require("node:fs");
59866
60199
  var import_node_os16 = require("node:os");
59867
- var import_node_path23 = require("node:path");
60200
+ var import_node_path25 = require("node:path");
59868
60201
 
59869
60202
  // routines.ts
59870
- var import_node_fs25 = require("node:fs");
59871
- var import_node_path22 = require("node:path");
60203
+ var import_node_fs27 = require("node:fs");
60204
+ var import_node_path24 = require("node:path");
59872
60205
 
59873
60206
  // sse.ts
59874
60207
  var clients = /* @__PURE__ */ new Set();
@@ -59905,11 +60238,11 @@ function clientCount() {
59905
60238
  }
59906
60239
 
59907
60240
  // trigger-sessions.ts
59908
- var import_node_fs24 = require("node:fs");
60241
+ var import_node_fs26 = require("node:fs");
59909
60242
  var import_node_os15 = require("node:os");
59910
- var import_node_path21 = require("node:path");
60243
+ var import_node_path23 = require("node:path");
59911
60244
  var import_yaml7 = __toESM(require_dist6(), 1);
59912
- var AGENTS_DIR2 = process.env.AWARE_HOME ? (0, import_node_path21.join)(process.env.AWARE_HOME, "agents") : (0, import_node_path21.join)((0, import_node_os15.homedir)(), ".aware", "agents");
60245
+ var AGENTS_DIR2 = process.env.AWARE_HOME ? (0, import_node_path23.join)(process.env.AWARE_HOME, "agents") : (0, import_node_path23.join)((0, import_node_os15.homedir)(), ".aware", "agents");
59913
60246
  function summarizeFire(data) {
59914
60247
  if (!data) return "event";
59915
60248
  const type = typeof data.type === "string" ? data.type : "";
@@ -59926,10 +60259,10 @@ function mapTriggerState(phase) {
59926
60259
  function isHostBacked(agent, agentsDir = AGENTS_DIR2) {
59927
60260
  const safe = (n) => !n.includes("/") && !n.includes("\\") && !n.includes("..");
59928
60261
  if (!safe(agent)) return false;
59929
- const manifestPath = (0, import_node_path21.join)(agentsDir, agent, "manifest.yaml");
59930
- if (!(0, import_node_fs24.existsSync)(manifestPath)) return false;
60262
+ const manifestPath = (0, import_node_path23.join)(agentsDir, agent, "manifest.yaml");
60263
+ if (!(0, import_node_fs26.existsSync)(manifestPath)) return false;
59931
60264
  try {
59932
- const doc = (0, import_yaml7.parse)((0, import_node_fs24.readFileSync)(manifestPath, "utf8"));
60265
+ const doc = (0, import_yaml7.parse)((0, import_node_fs26.readFileSync)(manifestPath, "utf8"));
59933
60266
  const transport = doc?.transport;
59934
60267
  return !!(transport && typeof transport === "object" && "cli" in transport);
59935
60268
  } catch {
@@ -60214,7 +60547,7 @@ var RoutineError = class extends Error {
60214
60547
  }
60215
60548
  status;
60216
60549
  };
60217
- var ROUTINES_FILE = (0, import_node_path22.join)(flolessRoot, "routines.json");
60550
+ var ROUTINES_FILE = (0, import_node_path24.join)(flolessRoot, "routines.json");
60218
60551
  var routines = [];
60219
60552
  var loaded = false;
60220
60553
  function ensureLoaded() {
@@ -60250,10 +60583,10 @@ function sanitizeRoutine(raw) {
60250
60583
  };
60251
60584
  }
60252
60585
  function loadFromDisk() {
60253
- if (!(0, import_node_fs25.existsSync)(ROUTINES_FILE)) return [];
60586
+ if (!(0, import_node_fs27.existsSync)(ROUTINES_FILE)) return [];
60254
60587
  let text;
60255
60588
  try {
60256
- text = (0, import_node_fs25.readFileSync)(ROUTINES_FILE, "utf8");
60589
+ text = (0, import_node_fs27.readFileSync)(ROUTINES_FILE, "utf8");
60257
60590
  } catch {
60258
60591
  return [];
60259
60592
  }
@@ -60262,17 +60595,17 @@ function loadFromDisk() {
60262
60595
  return Array.isArray(parsed) ? parsed.map(sanitizeRoutine).filter((r) => r !== null) : [];
60263
60596
  } catch {
60264
60597
  try {
60265
- (0, import_node_fs25.renameSync)(ROUTINES_FILE, `${ROUTINES_FILE}.corrupt-${Date.now()}`);
60598
+ (0, import_node_fs27.renameSync)(ROUTINES_FILE, `${ROUTINES_FILE}.corrupt-${Date.now()}`);
60266
60599
  } catch {
60267
60600
  }
60268
60601
  return [];
60269
60602
  }
60270
60603
  }
60271
60604
  function saveRoutines() {
60272
- if (!(0, import_node_fs25.existsSync)(flolessRoot)) (0, import_node_fs25.mkdirSync)(flolessRoot, { recursive: true });
60605
+ if (!(0, import_node_fs27.existsSync)(flolessRoot)) (0, import_node_fs27.mkdirSync)(flolessRoot, { recursive: true });
60273
60606
  const tmp = `${ROUTINES_FILE}.${process.pid}.tmp`;
60274
- (0, import_node_fs25.writeFileSync)(tmp, JSON.stringify(routines, null, 2));
60275
- (0, import_node_fs25.renameSync)(tmp, ROUTINES_FILE);
60607
+ (0, import_node_fs27.writeFileSync)(tmp, JSON.stringify(routines, null, 2));
60608
+ (0, import_node_fs27.renameSync)(tmp, ROUTINES_FILE);
60276
60609
  }
60277
60610
  function listRoutines() {
60278
60611
  ensureLoaded();
@@ -60680,13 +61013,13 @@ var AppLifecycleError = class extends Error {
60680
61013
  }
60681
61014
  };
60682
61015
  function appsDir() {
60683
- return process.env.AWARE_HOME ? (0, import_node_path23.join)(process.env.AWARE_HOME, "apps") : (0, import_node_path23.join)((0, import_node_os16.homedir)(), ".aware", "apps");
61016
+ return process.env.AWARE_HOME ? (0, import_node_path25.join)(process.env.AWARE_HOME, "apps") : (0, import_node_path25.join)((0, import_node_os16.homedir)(), ".aware", "apps");
60684
61017
  }
60685
61018
  function appDirPath(id) {
60686
- return (0, import_node_path23.join)(appsDir(), id);
61019
+ return (0, import_node_path25.join)(appsDir(), id);
60687
61020
  }
60688
61021
  function appInstalled(id) {
60689
- return APP_ID3.test(id) && (0, import_node_fs26.existsSync)(appDirPath(id));
61022
+ return APP_ID3.test(id) && (0, import_node_fs28.existsSync)(appDirPath(id));
60690
61023
  }
60691
61024
  function logCascade(what, e) {
60692
61025
  console.error(`[app-lifecycle] cascade step failed (${what}):`, e instanceof Error ? e.message : e);
@@ -60802,9 +61135,9 @@ function isGatedAwareRoute(url, method) {
60802
61135
 
60803
61136
  // autostart.mjs
60804
61137
  var import_node_child_process5 = require("node:child_process");
60805
- var import_node_fs27 = require("node:fs");
61138
+ var import_node_fs29 = require("node:fs");
60806
61139
  var import_node_os17 = require("node:os");
60807
- var import_node_path24 = require("node:path");
61140
+ var import_node_path26 = require("node:path");
60808
61141
 
60809
61142
  // teardown.mjs
60810
61143
  var RUN_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
@@ -60916,8 +61249,8 @@ function removeLegacyRunKey() {
60916
61249
  }
60917
61250
  function logLine(msg) {
60918
61251
  try {
60919
- (0, import_node_fs27.mkdirSync)(logDir(), { recursive: true });
60920
- (0, import_node_fs27.appendFileSync)(logFilePath(), `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
61252
+ (0, import_node_fs29.mkdirSync)(logDir(), { recursive: true });
61253
+ (0, import_node_fs29.appendFileSync)(logFilePath(), `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
60921
61254
  `);
60922
61255
  } catch {
60923
61256
  }
@@ -60925,8 +61258,8 @@ function logLine(msg) {
60925
61258
  function registerAutostart(exePath) {
60926
61259
  if (!isWin) return;
60927
61260
  const xml = buildAutostartTaskXml(exePath, currentUserId());
60928
- const tmp = (0, import_node_path24.join)((0, import_node_os17.tmpdir)(), `floless-autostart-${process.pid}-${Date.now()}.xml`);
60929
- (0, import_node_fs27.writeFileSync)(tmp, "\uFEFF" + xml, { encoding: "utf16le" });
61261
+ const tmp = (0, import_node_path26.join)((0, import_node_os17.tmpdir)(), `floless-autostart-${process.pid}-${Date.now()}.xml`);
61262
+ (0, import_node_fs29.writeFileSync)(tmp, "\uFEFF" + xml, { encoding: "utf16le" });
60930
61263
  try {
60931
61264
  (0, import_node_child_process5.execFileSync)("schtasks", ["/Create", "/TN", TASK_NAME, "/XML", tmp, "/F"], {
60932
61265
  stdio: ["ignore", "ignore", "ignore"],
@@ -60937,7 +61270,7 @@ function registerAutostart(exePath) {
60937
61270
  throw err2;
60938
61271
  } finally {
60939
61272
  try {
60940
- (0, import_node_fs27.rmSync)(tmp, { force: true });
61273
+ (0, import_node_fs29.rmSync)(tmp, { force: true });
60941
61274
  } catch {
60942
61275
  }
60943
61276
  }
@@ -60971,27 +61304,27 @@ function unregisterAutostart() {
60971
61304
 
60972
61305
  // updater.ts
60973
61306
  var import_node_child_process6 = require("node:child_process");
60974
- var import_node_crypto8 = require("node:crypto");
60975
- var import_node_fs29 = require("node:fs");
61307
+ var import_node_crypto9 = require("node:crypto");
61308
+ var import_node_fs31 = require("node:fs");
60976
61309
  var import_node_stream3 = require("node:stream");
60977
61310
  var import_promises = require("node:stream/promises");
60978
- var import_node_path26 = require("node:path");
61311
+ var import_node_path28 = require("node:path");
60979
61312
 
60980
61313
  // post-update-marker.mjs
60981
- var import_node_fs28 = require("node:fs");
61314
+ var import_node_fs30 = require("node:fs");
60982
61315
  var import_node_os18 = require("node:os");
60983
- var import_node_path25 = require("node:path");
61316
+ var import_node_path27 = require("node:path");
60984
61317
  var FRESH_MS = 12e4;
60985
61318
  function markerPath() {
60986
61319
  const override = (process.env.FLOLESS_POST_UPDATE_MARKER ?? "").trim();
60987
61320
  if (override) return override;
60988
- const root = process.env.FLOLESS_HOME ?? (0, import_node_path25.join)((0, import_node_os18.homedir)(), ".floless");
60989
- return (0, import_node_path25.join)(root, ".post-update");
61321
+ const root = process.env.FLOLESS_HOME ?? (0, import_node_path27.join)((0, import_node_os18.homedir)(), ".floless");
61322
+ return (0, import_node_path27.join)(root, ".post-update");
60990
61323
  }
60991
61324
  function legacyMarkerPath() {
60992
61325
  if ((process.env.FLOLESS_POST_UPDATE_MARKER ?? "").trim()) return null;
60993
61326
  try {
60994
- return (0, import_node_path25.join)((0, import_node_path25.dirname)((0, import_node_path25.dirname)(process.execPath)), ".floless-post-update");
61327
+ return (0, import_node_path27.join)((0, import_node_path27.dirname)((0, import_node_path27.dirname)(process.execPath)), ".floless-post-update");
60995
61328
  } catch {
60996
61329
  return null;
60997
61330
  }
@@ -61001,7 +61334,7 @@ function writePostUpdateMarker() {
61001
61334
  for (const p of [markerPath(), legacyMarkerPath()]) {
61002
61335
  if (!p) continue;
61003
61336
  try {
61004
- (0, import_node_fs28.writeFileSync)(p, (/* @__PURE__ */ new Date()).toISOString());
61337
+ (0, import_node_fs30.writeFileSync)(p, (/* @__PURE__ */ new Date()).toISOString());
61005
61338
  wrote = true;
61006
61339
  } catch {
61007
61340
  }
@@ -61013,9 +61346,9 @@ function consumePostUpdateMarker() {
61013
61346
  for (const p of [markerPath(), legacyMarkerPath()]) {
61014
61347
  if (!p) continue;
61015
61348
  try {
61016
- if (!(0, import_node_fs28.existsSync)(p)) continue;
61017
- const ageMs = Date.now() - (0, import_node_fs28.statSync)(p).mtimeMs;
61018
- (0, import_node_fs28.rmSync)(p, { force: true });
61349
+ if (!(0, import_node_fs30.existsSync)(p)) continue;
61350
+ const ageMs = Date.now() - (0, import_node_fs30.statSync)(p).mtimeMs;
61351
+ (0, import_node_fs30.rmSync)(p, { force: true });
61019
61352
  if (ageMs < FRESH_MS) fresh = true;
61020
61353
  } catch {
61021
61354
  }
@@ -61032,13 +61365,13 @@ function currentVersion() {
61032
61365
  return appVersion();
61033
61366
  }
61034
61367
  function installRoot() {
61035
- return (0, import_node_path26.dirname)((0, import_node_path26.dirname)(process.execPath));
61368
+ return (0, import_node_path28.dirname)((0, import_node_path28.dirname)(process.execPath));
61036
61369
  }
61037
61370
  function updateExePath() {
61038
- return (0, import_node_path26.join)(installRoot(), "Update.exe");
61371
+ return (0, import_node_path28.join)(installRoot(), "Update.exe");
61039
61372
  }
61040
61373
  function packagesDir() {
61041
- return (0, import_node_path26.join)(installRoot(), "packages");
61374
+ return (0, import_node_path28.join)(installRoot(), "packages");
61042
61375
  }
61043
61376
  function feedUrl() {
61044
61377
  const env2 = (process.env.FLOLESS_UPDATE_URL ?? "").trim().replace(/\/+$/, "");
@@ -61130,23 +61463,23 @@ async function checkForUpdate() {
61130
61463
  return { supported: true, currentVersion: cur, updateAvailable: true, targetVersion: latest.Version, asset: latest };
61131
61464
  }
61132
61465
  async function sha1OfFile(path) {
61133
- const hash = (0, import_node_crypto8.createHash)("sha1");
61134
- await (0, import_promises.pipeline)((0, import_node_fs29.createReadStream)(path), hash);
61466
+ const hash = (0, import_node_crypto9.createHash)("sha1");
61467
+ await (0, import_promises.pipeline)((0, import_node_fs31.createReadStream)(path), hash);
61135
61468
  return hash.digest("hex").toUpperCase();
61136
61469
  }
61137
61470
  async function downloadPackage(asset) {
61138
61471
  if (!NUPKG_NAME.test(asset.FileName)) throw new Error(`refusing suspicious package name: ${asset.FileName}`);
61139
61472
  const want = asset.SHA1.toUpperCase();
61140
61473
  const dir = packagesDir();
61141
- (0, import_node_fs29.mkdirSync)(dir, { recursive: true });
61142
- const dest = (0, import_node_path26.join)(dir, asset.FileName);
61143
- if ((0, import_node_fs29.existsSync)(dest) && await sha1OfFile(dest) === want) return dest;
61474
+ (0, import_node_fs31.mkdirSync)(dir, { recursive: true });
61475
+ const dest = (0, import_node_path28.join)(dir, asset.FileName);
61476
+ if ((0, import_node_fs31.existsSync)(dest) && await sha1OfFile(dest) === want) return dest;
61144
61477
  const res = await authedFetch(`${feedUrl()}/${encodeURIComponent(asset.FileName)}`, {
61145
61478
  redirect: "follow",
61146
61479
  signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS)
61147
61480
  });
61148
61481
  if (!res.ok || !res.body) throw new Error(`download failed: HTTP ${res.status} for ${asset.FileName}`);
61149
- await (0, import_promises.pipeline)(import_node_stream3.Readable.fromWeb(res.body), (0, import_node_fs29.createWriteStream)(dest));
61482
+ await (0, import_promises.pipeline)(import_node_stream3.Readable.fromWeb(res.body), (0, import_node_fs31.createWriteStream)(dest));
61150
61483
  const got = await sha1OfFile(dest);
61151
61484
  if (got !== want) throw new Error(`SHA1 mismatch for ${asset.FileName}: feed=${want} got=${got}`);
61152
61485
  return dest;
@@ -61155,7 +61488,7 @@ async function applyUpdate(check, opts) {
61155
61488
  if (!check.supported) return { applied: false, message: check.reason ?? "auto-update not supported in this runtime" };
61156
61489
  if (!check.updateAvailable || !check.asset) return { applied: false, message: check.reason ?? "no update available" };
61157
61490
  const exe = updateExePath();
61158
- if (!(0, import_node_fs29.existsSync)(exe)) {
61491
+ if (!(0, import_node_fs31.existsSync)(exe)) {
61159
61492
  return { applied: false, message: `Update.exe not found at ${exe} \u2014 is this a Velopack install?` };
61160
61493
  }
61161
61494
  const pkg = await downloadPackage(check.asset);
@@ -61395,12 +61728,12 @@ function isTraceCorrupt(events) {
61395
61728
 
61396
61729
  // launch.mjs
61397
61730
  var import_node_child_process7 = require("node:child_process");
61398
- var import_node_path27 = require("node:path");
61731
+ var import_node_path29 = require("node:path");
61399
61732
  var import_node_url3 = require("node:url");
61400
- var import_node_fs30 = require("node:fs");
61733
+ var import_node_fs32 = require("node:fs");
61401
61734
  var import_node_http = __toESM(require("node:http"), 1);
61402
61735
  var import_node_readline = require("node:readline");
61403
- var __dirname2 = (0, import_node_path27.dirname)((0, import_node_url3.fileURLToPath)(__import_meta_url));
61736
+ var __dirname2 = (0, import_node_path29.dirname)((0, import_node_url3.fileURLToPath)(__import_meta_url));
61404
61737
  var PORT = Number(process.env.PORT ?? 4317);
61405
61738
  var HEALTH_URL = `http://127.0.0.1:${PORT}/api/health`;
61406
61739
  var BROWSER_URL = `http://floless.localhost:${PORT}`;
@@ -61472,8 +61805,8 @@ async function waitHealthy(timeoutMs = 3e4) {
61472
61805
  function resolveServerStart() {
61473
61806
  const packaged = /flolessapp\.exe$/i.test(process.execPath);
61474
61807
  if (packaged) return { cmd: process.execPath, args: ["--serve"], shell: false };
61475
- const bundle = (0, import_node_path27.join)(__dirname2, "dist", "floless-server.cjs");
61476
- if ((0, import_node_fs30.existsSync)(bundle)) return { cmd: process.execPath, args: [bundle, "--serve"], shell: false };
61808
+ const bundle = (0, import_node_path29.join)(__dirname2, "dist", "floless-server.cjs");
61809
+ if ((0, import_node_fs32.existsSync)(bundle)) return { cmd: process.execPath, args: [bundle, "--serve"], shell: false };
61477
61810
  return { cmd: "npm", args: ["run", "start"], shell: isWin2 };
61478
61811
  }
61479
61812
  function startServerDetached() {
@@ -61626,8 +61959,8 @@ function taskkillArgs(pid, { tree = true } = {}) {
61626
61959
  }
61627
61960
  function killSupervisor({ tree = true } = {}) {
61628
61961
  if (!isWin2) return;
61629
- const isNpmChannel = /^node(\.exe)?$/i.test((0, import_node_path27.basename)(process.execPath));
61630
- const scriptMatch = isNpmChannel ? (0, import_node_path27.basename)((0, import_node_url3.fileURLToPath)(__import_meta_url)) : void 0;
61962
+ const isNpmChannel = /^node(\.exe)?$/i.test((0, import_node_path29.basename)(process.execPath));
61963
+ const scriptMatch = isNpmChannel ? (0, import_node_path29.basename)((0, import_node_url3.fileURLToPath)(__import_meta_url)) : void 0;
61631
61964
  const realExe = resolveRealInstallExe(process.execPath);
61632
61965
  const exeMatch = realExe === process.execPath ? process.execPath : [process.execPath, realExe];
61633
61966
  const pids = supervisorPidsToKill(enumerateProcesses(), process.pid, exeMatch, scriptMatch);
@@ -61792,7 +62125,7 @@ async function runAction(arg, flagArgv = [], selfVersion = null) {
61792
62125
  }
61793
62126
  await action(parseTeardownFlags(flagArgv));
61794
62127
  }
61795
- var entry = (0, import_node_path27.basename)(process.argv[1] ?? "").toLowerCase();
62128
+ var entry = (0, import_node_path29.basename)(process.argv[1] ?? "").toLowerCase();
61796
62129
  if (entry === "launch.mjs") {
61797
62130
  runAction(process.argv[2], process.argv.slice(3)).catch((e) => {
61798
62131
  log(`error: ${e?.message ?? e}`);
@@ -61866,9 +62199,9 @@ function awareUpgradeBlockReason(s) {
61866
62199
  }
61867
62200
 
61868
62201
  // skill-sync.ts
61869
- var import_node_fs31 = require("node:fs");
62202
+ var import_node_fs33 = require("node:fs");
61870
62203
  var import_node_os19 = require("node:os");
61871
- var import_node_path28 = require("node:path");
62204
+ var import_node_path30 = require("node:path");
61872
62205
  var import_node_url4 = require("node:url");
61873
62206
  var import_yaml8 = __toESM(require_dist6(), 1);
61874
62207
 
@@ -61911,14 +62244,14 @@ function selectShippedSkillNames(names) {
61911
62244
  }
61912
62245
 
61913
62246
  // skill-sync.ts
61914
- var __dirname3 = (0, import_node_path28.dirname)((0, import_node_url4.fileURLToPath)(__import_meta_url));
62247
+ var __dirname3 = (0, import_node_path30.dirname)((0, import_node_url4.fileURLToPath)(__import_meta_url));
61915
62248
  function bundledSkillsRoot() {
61916
62249
  const candidates = [
61917
- (0, import_node_path28.join)(__dirname3, "skills"),
61918
- (0, import_node_path28.join)((0, import_node_path28.dirname)(process.execPath), "skills"),
61919
- (0, import_node_path28.join)(__dirname3, "..", ".claude", "skills")
62250
+ (0, import_node_path30.join)(__dirname3, "skills"),
62251
+ (0, import_node_path30.join)((0, import_node_path30.dirname)(process.execPath), "skills"),
62252
+ (0, import_node_path30.join)(__dirname3, "..", ".claude", "skills")
61920
62253
  ];
61921
- return candidates.find((p) => (0, import_node_fs31.existsSync)(p)) ?? null;
62254
+ return candidates.find((p) => (0, import_node_fs33.existsSync)(p)) ?? null;
61922
62255
  }
61923
62256
  function targetConfigDirs() {
61924
62257
  const override = process.env.FLOLESS_SKILL_TARGETS;
@@ -61927,14 +62260,14 @@ function targetConfigDirs() {
61927
62260
  }
61928
62261
  const home = (0, import_node_os19.homedir)();
61929
62262
  return [
61930
- { runtime: "claude", dir: (0, import_node_path28.join)(home, ".claude") },
61931
- { runtime: "codex", dir: (0, import_node_path28.join)(home, ".codex") },
61932
- { runtime: "opencode", dir: (0, import_node_path28.join)(home, ".opencode") }
62263
+ { runtime: "claude", dir: (0, import_node_path30.join)(home, ".claude") },
62264
+ { runtime: "codex", dir: (0, import_node_path30.join)(home, ".codex") },
62265
+ { runtime: "opencode", dir: (0, import_node_path30.join)(home, ".opencode") }
61933
62266
  ];
61934
62267
  }
61935
62268
  function skillVersion(skillMdPath) {
61936
62269
  try {
61937
- const text = (0, import_node_fs31.readFileSync)(skillMdPath, "utf8");
62270
+ const text = (0, import_node_fs33.readFileSync)(skillMdPath, "utf8");
61938
62271
  const m = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text);
61939
62272
  if (!m || m[1] === void 0) return null;
61940
62273
  const fm = (0, import_yaml8.parse)(m[1]);
@@ -61974,21 +62307,21 @@ function decideAction(installed, bundled) {
61974
62307
  function bundledSkills(root) {
61975
62308
  let entries = [];
61976
62309
  try {
61977
- entries = selectShippedSkillNames((0, import_node_fs31.readdirSync)(root));
62310
+ entries = selectShippedSkillNames((0, import_node_fs33.readdirSync)(root));
61978
62311
  } catch {
61979
62312
  return [];
61980
62313
  }
61981
62314
  const out = [];
61982
62315
  for (const name of entries) {
61983
- const dir = (0, import_node_path28.join)(root, name);
62316
+ const dir = (0, import_node_path30.join)(root, name);
61984
62317
  let isDir = false;
61985
62318
  try {
61986
- isDir = (0, import_node_fs31.statSync)(dir).isDirectory();
62319
+ isDir = (0, import_node_fs33.statSync)(dir).isDirectory();
61987
62320
  } catch {
61988
62321
  isDir = false;
61989
62322
  }
61990
62323
  if (!isDir) continue;
61991
- const v = skillVersion((0, import_node_path28.join)(dir, "SKILL.md"));
62324
+ const v = skillVersion((0, import_node_path30.join)(dir, "SKILL.md"));
61992
62325
  if (!v) continue;
61993
62326
  out.push({ name, dir, version: v });
61994
62327
  }
@@ -62001,17 +62334,17 @@ function syncSkills() {
62001
62334
  const skills = bundledSkills(root);
62002
62335
  if (!skills.length) return results;
62003
62336
  for (const { runtime, dir: cfg } of targetConfigDirs()) {
62004
- if (!(0, import_node_fs31.existsSync)(cfg)) continue;
62005
- const skillsDir = (0, import_node_path28.join)(cfg, "skills");
62337
+ if (!(0, import_node_fs33.existsSync)(cfg)) continue;
62338
+ const skillsDir = (0, import_node_path30.join)(cfg, "skills");
62006
62339
  for (const s of skills) {
62007
- const dest = (0, import_node_path28.join)(skillsDir, s.name);
62008
- const installedMd = (0, import_node_path28.join)(dest, "SKILL.md");
62009
- const installed = (0, import_node_fs31.existsSync)(installedMd) ? skillVersion(installedMd) : null;
62340
+ const dest = (0, import_node_path30.join)(skillsDir, s.name);
62341
+ const installedMd = (0, import_node_path30.join)(dest, "SKILL.md");
62342
+ const installed = (0, import_node_fs33.existsSync)(installedMd) ? skillVersion(installedMd) : null;
62010
62343
  const action = decideAction(installed, s.version);
62011
62344
  if (action === "installed" || action === "updated") {
62012
62345
  try {
62013
- if (action === "updated") (0, import_node_fs31.rmSync)(dest, { recursive: true, force: true });
62014
- (0, import_node_fs31.cpSync)(s.dir, dest, { recursive: true });
62346
+ if (action === "updated") (0, import_node_fs33.rmSync)(dest, { recursive: true, force: true });
62347
+ (0, import_node_fs33.cpSync)(s.dir, dest, { recursive: true });
62015
62348
  results.push({ runtime, skill: s.name, action, from: installed, to: s.version });
62016
62349
  } catch {
62017
62350
  }
@@ -62025,8 +62358,8 @@ function syncSkills() {
62025
62358
 
62026
62359
  // watch.ts
62027
62360
  var import_node_os20 = require("node:os");
62028
- var import_node_path30 = require("node:path");
62029
- var import_node_fs32 = require("node:fs");
62361
+ var import_node_path32 = require("node:path");
62362
+ var import_node_fs34 = require("node:fs");
62030
62363
 
62031
62364
  // node_modules/chokidar/esm/index.js
62032
62365
  var import_fs2 = require("fs");
@@ -62037,7 +62370,7 @@ var sysPath2 = __toESM(require("path"), 1);
62037
62370
  // node_modules/readdirp/esm/index.js
62038
62371
  var import_promises2 = require("node:fs/promises");
62039
62372
  var import_node_stream4 = require("node:stream");
62040
- var import_node_path29 = require("node:path");
62373
+ var import_node_path31 = require("node:path");
62041
62374
  var EntryTypes = {
62042
62375
  FILE_TYPE: "files",
62043
62376
  DIR_TYPE: "directories",
@@ -62112,7 +62445,7 @@ var ReaddirpStream = class extends import_node_stream4.Readable {
62112
62445
  this._wantsDir = type ? DIR_TYPES.has(type) : false;
62113
62446
  this._wantsFile = type ? FILE_TYPES.has(type) : false;
62114
62447
  this._wantsEverything = type === EntryTypes.EVERYTHING_TYPE;
62115
- this._root = (0, import_node_path29.resolve)(root);
62448
+ this._root = (0, import_node_path31.resolve)(root);
62116
62449
  this._isDirent = !opts.alwaysStat;
62117
62450
  this._statsProp = this._isDirent ? "dirent" : "stats";
62118
62451
  this._rdOptions = { encoding: "utf8", withFileTypes: this._isDirent };
@@ -62183,8 +62516,8 @@ var ReaddirpStream = class extends import_node_stream4.Readable {
62183
62516
  let entry2;
62184
62517
  const basename5 = this._isDirent ? dirent.name : dirent;
62185
62518
  try {
62186
- const fullPath = (0, import_node_path29.resolve)((0, import_node_path29.join)(path, basename5));
62187
- entry2 = { path: (0, import_node_path29.relative)(this._root, fullPath), fullPath, basename: basename5 };
62519
+ const fullPath = (0, import_node_path31.resolve)((0, import_node_path31.join)(path, basename5));
62520
+ entry2 = { path: (0, import_node_path31.relative)(this._root, fullPath), fullPath, basename: basename5 };
62188
62521
  entry2[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
62189
62522
  } catch (err2) {
62190
62523
  this._onError(err2);
@@ -62218,7 +62551,7 @@ var ReaddirpStream = class extends import_node_stream4.Readable {
62218
62551
  }
62219
62552
  if (entryRealPathStats.isDirectory()) {
62220
62553
  const len2 = entryRealPath.length;
62221
- if (full.startsWith(entryRealPath) && full.substr(len2, 1) === import_node_path29.sep) {
62554
+ if (full.startsWith(entryRealPath) && full.substr(len2, 1) === import_node_path31.sep) {
62222
62555
  const recursiveError = new Error(`Circular symlink detected: "${full}" points to "${entryRealPath}"`);
62223
62556
  recursiveError.code = RECURSIVE_ERROR_CODE;
62224
62557
  return this._onError(recursiveError);
@@ -63726,33 +64059,33 @@ function appIdFromLogPath(path) {
63726
64059
  return i >= 0 && parts[i + 1] ? parts[i + 1] : null;
63727
64060
  }
63728
64061
  function samePath(a, b) {
63729
- const ra = (0, import_node_path30.resolve)(a);
63730
- const rb = (0, import_node_path30.resolve)(b);
64062
+ const ra = (0, import_node_path32.resolve)(a);
64063
+ const rb = (0, import_node_path32.resolve)(b);
63731
64064
  return process.platform === "win32" ? ra.toLowerCase() === rb.toLowerCase() : ra === rb;
63732
64065
  }
63733
64066
  function underDir(path, dir) {
63734
- const rp = (0, import_node_path30.resolve)(path);
63735
- const rd = (0, import_node_path30.resolve)(dir);
64067
+ const rp = (0, import_node_path32.resolve)(path);
64068
+ const rd = (0, import_node_path32.resolve)(dir);
63736
64069
  const [p, d] = process.platform === "win32" ? [rp.toLowerCase(), rd.toLowerCase()] : [rp, rd];
63737
- return p === d || p.startsWith(d + import_node_path30.sep);
64070
+ return p === d || p.startsWith(d + import_node_path32.sep);
63738
64071
  }
63739
64072
  function startWatcher() {
63740
- const awareDir = process.env.AWARE_HOME ?? (0, import_node_path30.join)((0, import_node_os20.homedir)(), ".aware");
63741
- const credentialsDir = (0, import_node_path30.join)(awareDir, "credentials");
63742
- if (!(0, import_node_fs32.existsSync)(credentialsDir)) {
64073
+ const awareDir = process.env.AWARE_HOME ?? (0, import_node_path32.join)((0, import_node_os20.homedir)(), ".aware");
64074
+ const credentialsDir = (0, import_node_path32.join)(awareDir, "credentials");
64075
+ if (!(0, import_node_fs34.existsSync)(credentialsDir)) {
63743
64076
  try {
63744
- (0, import_node_fs32.mkdirSync)(credentialsDir, { recursive: true });
64077
+ (0, import_node_fs34.mkdirSync)(credentialsDir, { recursive: true });
63745
64078
  } catch {
63746
64079
  }
63747
64080
  }
63748
- if (!(0, import_node_fs32.existsSync)(uiDir)) {
64081
+ if (!(0, import_node_fs34.existsSync)(uiDir)) {
63749
64082
  try {
63750
- (0, import_node_fs32.mkdirSync)(uiDir, { recursive: true });
64083
+ (0, import_node_fs34.mkdirSync)(uiDir, { recursive: true });
63751
64084
  } catch {
63752
64085
  }
63753
64086
  }
63754
- const targets = ["apps", "logs", "credentials"].map((d) => (0, import_node_path30.join)(awareDir, d)).filter((p) => (0, import_node_fs32.existsSync)(p));
63755
- if ((0, import_node_fs32.existsSync)(uiDir)) targets.push(uiDir);
64087
+ const targets = ["apps", "logs", "credentials"].map((d) => (0, import_node_path32.join)(awareDir, d)).filter((p) => (0, import_node_fs34.existsSync)(p));
64088
+ if ((0, import_node_fs34.existsSync)(uiDir)) targets.push(uiDir);
63756
64089
  if (targets.length === 0) {
63757
64090
  return null;
63758
64091
  }
@@ -63781,11 +64114,11 @@ function startWatcher() {
63781
64114
  const isCredential = path.split(/[\\/]/).includes("credentials");
63782
64115
  const kind = isCredential ? "credential" : path.endsWith(".jsonl") ? "trace" : path.endsWith(".lock") ? "lock" : path.endsWith(".flo") || path.endsWith(".app") ? "source" : "file";
63783
64116
  broadcast({ type: "fs-change", kind, event, path });
63784
- if (kind === "trace" && event !== "unlink" && (0, import_node_fs32.existsSync)(path)) {
64117
+ if (kind === "trace" && event !== "unlink" && (0, import_node_fs34.existsSync)(path)) {
63785
64118
  const id = appIdFromLogPath(path);
63786
64119
  if (!id) return;
63787
64120
  try {
63788
- broadcast({ type: "trace-file", id, runId: path.split(import_node_path30.sep).pop()?.replace(/\.jsonl$/, "") ?? null, events: parseTrace((0, import_node_fs32.readFileSync)(path, "utf8")) });
64121
+ broadcast({ type: "trace-file", id, runId: path.split(import_node_path32.sep).pop()?.replace(/\.jsonl$/, "") ?? null, events: parseTrace((0, import_node_fs34.readFileSync)(path, "utf8")) });
63789
64122
  } catch {
63790
64123
  }
63791
64124
  }
@@ -63794,10 +64127,10 @@ function startWatcher() {
63794
64127
  }
63795
64128
 
63796
64129
  // index.ts
63797
- var __dirname4 = (0, import_node_path31.dirname)((0, import_node_url5.fileURLToPath)(__import_meta_url));
63798
- var WEB_ROOT = [(0, import_node_path31.join)(__dirname4, "web"), (0, import_node_path31.join)((0, import_node_path31.dirname)(process.execPath), "web"), (0, import_node_path31.join)(__dirname4, "..", "web")].find(
63799
- (p) => (0, import_node_fs33.existsSync)(p)
63800
- ) ?? (0, import_node_path31.join)(__dirname4, "..", "web");
64130
+ var __dirname4 = (0, import_node_path33.dirname)((0, import_node_url5.fileURLToPath)(__import_meta_url));
64131
+ var WEB_ROOT = [(0, import_node_path33.join)(__dirname4, "web"), (0, import_node_path33.join)((0, import_node_path33.dirname)(process.execPath), "web"), (0, import_node_path33.join)(__dirname4, "..", "web")].find(
64132
+ (p) => (0, import_node_fs35.existsSync)(p)
64133
+ ) ?? (0, import_node_path33.join)(__dirname4, "..", "web");
63801
64134
  var PORT2 = Number(process.env.PORT ?? 4317);
63802
64135
  var HOST = "127.0.0.1";
63803
64136
  var crashHandlersInstalled = false;
@@ -63813,7 +64146,7 @@ function installCrashHandlers() {
63813
64146
  ${stack}
63814
64147
  `;
63815
64148
  try {
63816
- (0, import_node_fs33.appendFileSync)(logFilePath(), line);
64149
+ (0, import_node_fs35.appendFileSync)(logFilePath(), line);
63817
64150
  } catch {
63818
64151
  }
63819
64152
  if (process.stderr.isTTY) process.stderr.write(line);
@@ -64110,9 +64443,9 @@ async function startServer() {
64110
64443
  const { id, templateId, templatePath, srcPath, appDirPath: appDirPath2, fromVersion, contract, verb } = opts;
64111
64444
  const bdir = backupDir(id, fromVersion, Date.now());
64112
64445
  try {
64113
- (0, import_node_fs33.mkdirSync)((0, import_node_path31.dirname)(bdir), { recursive: true });
64114
- (0, import_node_fs33.cpSync)(appDirPath2, bdir, { recursive: true });
64115
- (0, import_node_fs33.cpSync)(templatePath, srcPath);
64446
+ (0, import_node_fs35.mkdirSync)((0, import_node_path33.dirname)(bdir), { recursive: true });
64447
+ (0, import_node_fs35.cpSync)(appDirPath2, bdir, { recursive: true });
64448
+ (0, import_node_fs35.cpSync)(templatePath, srcPath);
64116
64449
  if (id !== templateId) stampSourceAppId(srcPath, id);
64117
64450
  if (contract != null) {
64118
64451
  bakeContractIntoApp(srcPath, contract);
@@ -64123,8 +64456,8 @@ async function startServer() {
64123
64456
  } catch (e) {
64124
64457
  const msg = e instanceof Error ? e.message : String(e);
64125
64458
  try {
64126
- (0, import_node_fs33.rmSync)(appDirPath2, { recursive: true, force: true });
64127
- (0, import_node_fs33.cpSync)(bdir, appDirPath2, { recursive: true });
64459
+ (0, import_node_fs35.rmSync)(appDirPath2, { recursive: true, force: true });
64460
+ (0, import_node_fs35.cpSync)(bdir, appDirPath2, { recursive: true });
64128
64461
  } catch (re) {
64129
64462
  const rmsg = re instanceof Error ? re.message : String(re);
64130
64463
  throw new TemplateSwapError(`${verb} failed (${msg}) AND rollback failed (${rmsg}) \u2014 restore manually from ${bdir}`, "rollback-failed", bdir);
@@ -64155,7 +64488,7 @@ async function startServer() {
64155
64488
  return reply.status(409).send({ ok: false, error: `"${id}" is already at ${fromVersion}`, code: "up-to-date" });
64156
64489
  }
64157
64490
  const contract = readContract(id);
64158
- if (contract == null && (0, import_node_fs33.existsSync)(contractPath(id))) {
64491
+ if (contract == null && (0, import_node_fs35.existsSync)(contractPath(id))) {
64159
64492
  return reply.status(409).send({ ok: false, error: `"${id}"'s saved data is unreadable \u2014 open it in the editor and re-save before updating`, code: "contract-corrupt" });
64160
64493
  }
64161
64494
  if (contract != null) {
@@ -64360,6 +64693,92 @@ async function startServer() {
64360
64693
  broadcast({ type: "contract-changed", appId: project.app, project: project.id });
64361
64694
  return { ok: true, version: row };
64362
64695
  });
64696
+ app.post(
64697
+ "/api/projects/:id/revision-requests",
64698
+ { bodyLimit: 96 * 1024 * 1024 },
64699
+ // a revised SET = multiple structural PDFs (matches /api/rebake)
64700
+ async (req, reply) => {
64701
+ const project = getProject(req.params.id);
64702
+ if (!project) return reply.status(404).send({ ok: false, error: `unknown project: ${req.params.id}` });
64703
+ const appId = typeof req.body?.appId === "string" ? req.body.appId : project.app;
64704
+ if (appId !== project.app) {
64705
+ return reply.status(404).send({ ok: false, error: `project "${project.id}" belongs to app "${project.app}", not "${appId}"` });
64706
+ }
64707
+ let decoded;
64708
+ try {
64709
+ decoded = decodeSnapshots(req.body?.snapshots);
64710
+ } catch (e) {
64711
+ return reply.status(400).send({ ok: false, error: e instanceof Error ? e.message : "bad snapshot" });
64712
+ }
64713
+ if (!decoded.length) return reply.status(400).send({ ok: false, error: "attach at least one drawing file" });
64714
+ const inputs = req.body?.snapshots ?? [];
64715
+ const sourceRefs = decoded.map((d, i) => {
64716
+ const nm = inputs[i]?.name;
64717
+ return saveSource(project.id, { name: typeof nm === "string" && nm || `sheet-${i + 1}.${d.ext}`, buf: d.buf, ext: d.ext });
64718
+ });
64719
+ const baseVersion = listVersions(project.id)[0]?.n ?? 0;
64720
+ const request = addRequest(
64721
+ {
64722
+ type: "revision-read",
64723
+ appId: project.app,
64724
+ project: project.id,
64725
+ baseVersion,
64726
+ sourceRefs,
64727
+ ...typeof req.body?.message === "string" && req.body.message.trim() ? { instruction: req.body.message.trim() } : {}
64728
+ },
64729
+ decoded
64730
+ );
64731
+ broadcast({ type: "request-added", request });
64732
+ return { ok: true, request };
64733
+ }
64734
+ );
64735
+ app.post(
64736
+ "/api/projects/:id/revision-read",
64737
+ { bodyLimit: 25 * 1024 * 1024 },
64738
+ // the contract embeds rasters (matches the contract PUT)
64739
+ async (req, reply) => {
64740
+ const project = getProject(req.params.id);
64741
+ if (!project) return reply.status(404).send({ ok: false, error: `unknown project: ${req.params.id}` });
64742
+ if (req.body?.contract == null) return reply.status(400).send({ ok: false, error: "contract required" });
64743
+ const requestId = typeof req.body?.requestId === "string" ? req.body.requestId : "";
64744
+ const request = requestId ? getRequest(requestId) : null;
64745
+ if (!request || request.type !== "revision-read" || request.project !== project.id) {
64746
+ return reply.status(400).send({ ok: false, error: "a valid queued revision-read requestId for this project is required" });
64747
+ }
64748
+ const head = listVersions(project.id)[0]?.n ?? 0;
64749
+ if ((request.baseVersion ?? 0) !== head) {
64750
+ return reply.status(409).send({ ok: false, error: `project changed since this read started (base v${request.baseVersion ?? 0}, now v${head}) \u2014 re-read the current version` });
64751
+ }
64752
+ try {
64753
+ writeContract(project.app, req.body.contract, project.id);
64754
+ } catch (e) {
64755
+ if (e instanceof ContractError) return reply.status(400).send({ ok: false, error: e.message });
64756
+ throw e;
64757
+ }
64758
+ clearApproval(project.id);
64759
+ const message = typeof req.body?.message === "string" && req.body.message.trim() ? req.body.message.trim() : "Re-read a revised drawing set";
64760
+ const source = request.sourceRefs && request.sourceRefs.length ? { files: request.sourceRefs, requestId: request.id } : void 0;
64761
+ const version = createVersion(project.id, req.body.contract, { author: "AI", message, gate: null, kind: "revision-read", source });
64762
+ broadcast({ type: "contract-changed", appId: project.app, project: project.id });
64763
+ deleteRequest(request.id);
64764
+ return { ok: true, version };
64765
+ }
64766
+ );
64767
+ app.post(
64768
+ "/api/projects/:id/revision-requests/:reqId/fail",
64769
+ async (req, reply) => {
64770
+ const project = getProject(req.params.id);
64771
+ if (!project) return reply.status(404).send({ ok: false, error: `unknown project: ${req.params.id}` });
64772
+ const existing = getRequest(req.params.reqId);
64773
+ if (!existing || existing.type !== "revision-read" || existing.project !== project.id) {
64774
+ return reply.status(404).send({ ok: false, error: `unknown revision request for this project: ${req.params.reqId}` });
64775
+ }
64776
+ const error = typeof req.body?.error === "string" && req.body.error.trim() ? req.body.error.trim().slice(0, 2e3) : "the read could not be completed";
64777
+ const request = patchRequest(existing.id, { status: "failed", error });
64778
+ broadcast({ type: "request-added", request });
64779
+ return { ok: true, request };
64780
+ }
64781
+ );
64363
64782
  function projectAppMismatch(appId, project) {
64364
64783
  const p = getProject(project);
64365
64784
  if (!p) return `unknown project: ${project}`;
@@ -64517,10 +64936,10 @@ async function startServer() {
64517
64936
  if (bad) return reply.status(404).send({ ok: false, error: bad });
64518
64937
  const dir = projectExportsDir(project);
64519
64938
  const exports2 = projectExportFiles(req.params.appId).map(({ kind, filename }) => {
64520
- const path = (0, import_node_path31.join)(dir, filename);
64939
+ const path = (0, import_node_path33.join)(dir, filename);
64521
64940
  let exportedAt = null;
64522
64941
  try {
64523
- const st = (0, import_node_fs33.statSync)(path);
64942
+ const st = (0, import_node_fs35.statSync)(path);
64524
64943
  if (st.isFile()) exportedAt = st.mtime.toISOString();
64525
64944
  } catch {
64526
64945
  }
@@ -64604,8 +65023,8 @@ async function startServer() {
64604
65023
  }
64605
65024
  const companionId = `${req.params.appId}-ifc`;
64606
65025
  const filename = `${req.params.appId}.ifc`;
64607
- const outPath = project ? (0, import_node_path31.join)(projectExportsDir(project), filename) : (0, import_node_path31.join)(appPath(companionId), filename);
64608
- if (project) (0, import_node_fs33.mkdirSync)((0, import_node_path31.dirname)(outPath), { recursive: true });
65026
+ const outPath = project ? (0, import_node_path33.join)(projectExportsDir(project), filename) : (0, import_node_path33.join)(appPath(companionId), filename);
65027
+ if (project) (0, import_node_fs35.mkdirSync)((0, import_node_path33.dirname)(outPath), { recursive: true });
64609
65028
  let flo;
64610
65029
  try {
64611
65030
  flo = writeIfcApp(appPath(companionId), companionId, scene, outPath, writeOpts);
@@ -64628,8 +65047,8 @@ async function startServer() {
64628
65047
  }
64629
65048
  throw e;
64630
65049
  }
64631
- if (!(0, import_node_fs33.existsSync)(outPath)) return reply.send({ ok: false, error: "the IFC export produced no file" });
64632
- const content = (0, import_node_fs33.readFileSync)(outPath, "utf8");
65050
+ if (!(0, import_node_fs35.existsSync)(outPath)) return reply.send({ ok: false, error: "the IFC export produced no file" });
65051
+ const content = (0, import_node_fs35.readFileSync)(outPath, "utf8");
64633
65052
  broadcast({ type: "apps-changed" });
64634
65053
  return { ok: true, filename, savedTo: outPath, content, bytes: Buffer.byteLength(content), skipped, ...extrusionsSkipped ? { extrusionsSkipped } : {} };
64635
65054
  });
@@ -64718,17 +65137,17 @@ async function startServer() {
64718
65137
  return reply.status(422).send({ ok: false, error: "no priced members to export \u2014 every member is an RFI (no AISC size / weight yet)" });
64719
65138
  }
64720
65139
  const filename = `${appId}-bom.${format}`;
64721
- const outPath = project ? (0, import_node_path31.join)(projectExportsDir(project), filename) : bomExportPath(appId, format);
64722
- const dir = (0, import_node_path31.dirname)(outPath);
65140
+ const outPath = project ? (0, import_node_path33.join)(projectExportsDir(project), filename) : bomExportPath(appId, format);
65141
+ const dir = (0, import_node_path33.dirname)(outPath);
64723
65142
  try {
64724
- (0, import_node_fs33.mkdirSync)(dir, { recursive: true });
65143
+ (0, import_node_fs35.mkdirSync)(dir, { recursive: true });
64725
65144
  if (format === "csv") {
64726
65145
  const text = bomToCsv(bom);
64727
- (0, import_node_fs33.writeFileSync)(outPath, text, "utf8");
65146
+ (0, import_node_fs35.writeFileSync)(outPath, text, "utf8");
64728
65147
  return { ok: true, filename, encoding: "utf8", content: text, bytes: Buffer.byteLength(text), savedTo: outPath };
64729
65148
  }
64730
65149
  const buf = await bomToXlsx(bom);
64731
- (0, import_node_fs33.writeFileSync)(outPath, buf);
65150
+ (0, import_node_fs35.writeFileSync)(outPath, buf);
64732
65151
  return { ok: true, filename, encoding: "base64", content: buf.toString("base64"), bytes: buf.length, savedTo: outPath };
64733
65152
  } catch (e) {
64734
65153
  app.log.error({ appId, format, err: e instanceof Error ? e.message : e }, "export-bom: generation failed");
@@ -64798,7 +65217,7 @@ async function startServer() {
64798
65217
  return reply.status(404).send({ ok: false, error: `"${id}" is not installed`, code: "not-installed" });
64799
65218
  }
64800
65219
  const contract = readContract(id);
64801
- if (contract == null && (0, import_node_fs33.existsSync)(contractPath(id))) {
65220
+ if (contract == null && (0, import_node_fs35.existsSync)(contractPath(id))) {
64802
65221
  return reply.status(409).send({ ok: false, error: `"${id}"'s saved data is unreadable \u2014 open it in the editor and re-save before restoring`, code: "contract-corrupt" });
64803
65222
  }
64804
65223
  if (contract != null) {
@@ -64876,11 +65295,11 @@ async function startServer() {
64876
65295
  if (appExists(id)) {
64877
65296
  return reply.status(409).send({ ok: false, error: `a workflow named "${id}" is already installed \u2014 remove it first, or rename the file's app: id`, code: "exists", id });
64878
65297
  }
64879
- const stageRoot = (0, import_node_fs33.mkdtempSync)((0, import_node_path31.join)((0, import_node_os21.tmpdir)(), "floless-import-"));
65298
+ const stageRoot = (0, import_node_fs35.mkdtempSync)((0, import_node_path33.join)((0, import_node_os21.tmpdir)(), "floless-import-"));
64880
65299
  try {
64881
- const stageDir = (0, import_node_path31.join)(stageRoot, id);
64882
- (0, import_node_fs33.mkdirSync)(stageDir);
64883
- (0, import_node_fs33.writeFileSync)((0, import_node_path31.join)(stageDir, `${id}.flo`), content);
65300
+ const stageDir = (0, import_node_path33.join)(stageRoot, id);
65301
+ (0, import_node_fs35.mkdirSync)(stageDir);
65302
+ (0, import_node_fs35.writeFileSync)((0, import_node_path33.join)(stageDir, `${id}.flo`), content);
64884
65303
  await aware.install(stageDir);
64885
65304
  } catch (err2) {
64886
65305
  try {
@@ -64890,7 +65309,7 @@ async function startServer() {
64890
65309
  const msg = err2 instanceof AwareError ? err2.message : String(err2?.message ?? err2);
64891
65310
  return reply.status(502).send({ ok: false, error: `import failed: ${msg}` });
64892
65311
  } finally {
64893
- (0, import_node_fs33.rmSync)(stageRoot, { recursive: true, force: true });
65312
+ (0, import_node_fs35.rmSync)(stageRoot, { recursive: true, force: true });
64894
65313
  }
64895
65314
  broadcast({ type: "apps-changed", id });
64896
65315
  return { ok: true, id };
@@ -64904,13 +65323,13 @@ async function startServer() {
64904
65323
  }
64905
65324
  const inputs = appData.inputs.map((i) => ({ name: i.name, type: i.type }));
64906
65325
  const baked = bakeFloSource(appData.source.text, inputs);
64907
- const tmpRoot = (0, import_node_fs33.mkdtempSync)((0, import_node_path31.join)((0, import_node_os21.tmpdir)(), "floless-bake-"));
64908
- const backupDir2 = (0, import_node_path31.join)(tmpRoot, `${id}-backup`);
64909
- const bakeDir = (0, import_node_path31.join)(tmpRoot, id);
64910
- (0, import_node_fs33.cpSync)(appDir(id), backupDir2, { recursive: true });
64911
- (0, import_node_fs33.cpSync)(appDir(id), bakeDir, { recursive: true });
65326
+ const tmpRoot = (0, import_node_fs35.mkdtempSync)((0, import_node_path33.join)((0, import_node_os21.tmpdir)(), "floless-bake-"));
65327
+ const backupDir2 = (0, import_node_path33.join)(tmpRoot, `${id}-backup`);
65328
+ const bakeDir = (0, import_node_path33.join)(tmpRoot, id);
65329
+ (0, import_node_fs35.cpSync)(appDir(id), backupDir2, { recursive: true });
65330
+ (0, import_node_fs35.cpSync)(appDir(id), bakeDir, { recursive: true });
64912
65331
  const floName = appData.source.path.split(/[\\/]/).pop();
64913
- (0, import_node_fs33.writeFileSync)((0, import_node_path31.join)(bakeDir, floName), baked);
65332
+ (0, import_node_fs35.writeFileSync)((0, import_node_path33.join)(bakeDir, floName), baked);
64914
65333
  let appInstalled2 = true;
64915
65334
  try {
64916
65335
  await aware.uninstall(id);
@@ -64932,17 +65351,17 @@ async function startServer() {
64932
65351
  throw installErr;
64933
65352
  }
64934
65353
  try {
64935
- await aware.compile((0, import_node_path31.join)(appDir(id), floName));
65354
+ await aware.compile((0, import_node_path33.join)(appDir(id), floName));
64936
65355
  } catch (compileErr) {
64937
65356
  app.log.warn({ id, compileErr: String(compileErr) }, "bake: post-install recompile failed (app baked but may need a manual Compile)");
64938
65357
  }
64939
65358
  broadcast({ type: "baked", id });
64940
65359
  return { ok: true, id, agent: id, inputs };
64941
65360
  } finally {
64942
- if (appInstalled2) (0, import_node_fs33.rmSync)(tmpRoot, { recursive: true, force: true });
65361
+ if (appInstalled2) (0, import_node_fs35.rmSync)(tmpRoot, { recursive: true, force: true });
64943
65362
  }
64944
65363
  });
64945
- const graftAgentsDir = () => (0, import_node_path31.join)((0, import_node_os21.homedir)(), ".aware", "agents");
65364
+ const graftAgentsDir = () => (0, import_node_path33.join)((0, import_node_os21.homedir)(), ".aware", "agents");
64946
65365
  app.post("/api/graft/match", async (req, reply) => {
64947
65366
  const { glob } = req.body ?? {};
64948
65367
  if (!glob) return reply.status(400).send({ ok: false, error: "glob required" });
@@ -64959,7 +65378,7 @@ async function startServer() {
64959
65378
  if (!sourceKind || !sourceRef) {
64960
65379
  return reply.status(400).send({ ok: false, error: "sourceKind and sourceRef required" });
64961
65380
  }
64962
- const tempHome = (0, import_node_fs33.mkdtempSync)((0, import_node_path31.join)((0, import_node_os21.tmpdir)(), "floless-graft-"));
65381
+ const tempHome = (0, import_node_fs35.mkdtempSync)((0, import_node_path33.join)((0, import_node_os21.tmpdir)(), "floless-graft-"));
64963
65382
  let result;
64964
65383
  try {
64965
65384
  result = await aware.build({
@@ -64972,19 +65391,19 @@ async function startServer() {
64972
65391
  awareHome: tempHome
64973
65392
  });
64974
65393
  } catch (err2) {
64975
- (0, import_node_fs33.rmSync)(tempHome, { recursive: true, force: true });
65394
+ (0, import_node_fs35.rmSync)(tempHome, { recursive: true, force: true });
64976
65395
  const msg = err2 instanceof AwareError ? err2.message : String(err2?.message ?? err2);
64977
65396
  return reply.status(422).send({ ok: false, error: msg });
64978
65397
  }
64979
65398
  const manifest = readStagedManifest(result.agentDir);
64980
65399
  if (!manifest) {
64981
- (0, import_node_fs33.rmSync)(tempHome, { recursive: true, force: true });
65400
+ (0, import_node_fs35.rmSync)(tempHome, { recursive: true, force: true });
64982
65401
  return reply.status(502).send({ ok: false, error: `build produced output at ${result.agentDir} but no manifest.yaml` });
64983
65402
  }
64984
- const token = (0, import_node_crypto9.randomUUID)();
65403
+ const token = (0, import_node_crypto10.randomUUID)();
64985
65404
  registerStage(token, tempHome, result.agentId);
64986
65405
  const preview = buildPreview(manifest, sourceKind, sourceRef, token);
64987
- if ((0, import_node_fs33.existsSync)((0, import_node_path31.join)(graftAgentsDir(), result.agentId))) {
65406
+ if ((0, import_node_fs35.existsSync)((0, import_node_path33.join)(graftAgentsDir(), result.agentId))) {
64988
65407
  preview.warnings.unshift(`An agent named "${result.agentId}" is already installed \u2014 creating it will overwrite it.`);
64989
65408
  }
64990
65409
  return { ok: true, preview };
@@ -65003,7 +65422,7 @@ async function startServer() {
65003
65422
  registerStage(stagedRef, stage.tempDir, stage.agentId);
65004
65423
  return reply.status(409).send({ ok: false, error: err2.message, agentId: stage.agentId, collision: true });
65005
65424
  }
65006
- (0, import_node_fs33.rmSync)(stage.tempDir, { recursive: true, force: true });
65425
+ (0, import_node_fs35.rmSync)(stage.tempDir, { recursive: true, force: true });
65007
65426
  throw err2;
65008
65427
  }
65009
65428
  broadcast({ type: "grafted", id: stage.agentId });
@@ -65274,11 +65693,11 @@ async function startServer() {
65274
65693
  app.get("/api/requests/:id/snapshot/:n", async (req, reply) => {
65275
65694
  const n = Number.parseInt(req.params.n, 10);
65276
65695
  const p = Number.isInteger(n) ? snapshotPathFor(req.params.id, n) : null;
65277
- if (!p || !(0, import_node_fs33.existsSync)(p)) return reply.status(404).send({ ok: false, error: "snapshot not found" });
65696
+ if (!p || !(0, import_node_fs35.existsSync)(p)) return reply.status(404).send({ ok: false, error: "snapshot not found" });
65278
65697
  const ext = p.split(".").pop().toLowerCase();
65279
65698
  reply.header("Content-Type", ext === "png" ? "image/png" : ext === "webp" ? "image/webp" : ext === "pdf" ? "application/pdf" : "image/jpeg");
65280
65699
  reply.header("Cache-Control", "no-store");
65281
- return (0, import_node_fs33.readFileSync)(p);
65700
+ return (0, import_node_fs35.readFileSync)(p);
65282
65701
  });
65283
65702
  app.post(
65284
65703
  "/api/tweak",
@@ -65360,6 +65779,63 @@ async function startServer() {
65360
65779
  }
65361
65780
  }
65362
65781
  );
65782
+ const CONN_APPID = /^[a-z0-9][a-z0-9._-]*$/i;
65783
+ app.post(
65784
+ "/api/import-connection/list",
65785
+ { bodyLimit: 96 * 1024 * 1024 },
65786
+ // a base64 IFC inflates ~4/3; 96 MB covers the 64 MB stash cap
65787
+ async (req, reply) => {
65788
+ const { appId, dataUrl } = req.body ?? {};
65789
+ if (!appId || !CONN_APPID.test(appId)) return reply.status(400).send({ ok: false, error: "a valid appId is required" });
65790
+ if (!dataUrl) return reply.status(400).send({ ok: false, error: "an IFC file is required" });
65791
+ let stored;
65792
+ try {
65793
+ stored = storeIfcInput(appId, dataUrl);
65794
+ } catch (e) {
65795
+ if (e instanceof VisualInputError) return reply.status(400).send({ ok: false, error: e.message });
65796
+ throw e;
65797
+ }
65798
+ let done = false;
65799
+ req.raw.on("close", () => {
65800
+ if (!done && isRunActive()) cancelActiveRun();
65801
+ });
65802
+ try {
65803
+ const connections = await listConnections(`${appId}-connimport`, stored.path);
65804
+ return { ok: true, sha: stored.sha256, connections };
65805
+ } catch (e) {
65806
+ if (e instanceof ConnectionImportError) return reply.send({ ok: false, error: e.message });
65807
+ app.log.error({ appId, err: e instanceof Error ? e.message : e }, "import-connection list failed");
65808
+ return reply.status(500).send({ ok: false, error: "could not read the IFC file" });
65809
+ } finally {
65810
+ done = true;
65811
+ }
65812
+ }
65813
+ );
65814
+ app.post(
65815
+ "/api/import-connection/extract",
65816
+ async (req, reply) => {
65817
+ const { appId, sha, id } = req.body ?? {};
65818
+ if (!appId || !CONN_APPID.test(appId)) return reply.status(400).send({ ok: false, error: "a valid appId is required" });
65819
+ if (!sha || !id) return reply.status(400).send({ ok: false, error: "sha and connection id are required" });
65820
+ const path = ifcInputPath(appId, sha);
65821
+ if (!path) return reply.status(404).send({ ok: false, error: "the uploaded IFC is no longer available \u2014 drop the file again" });
65822
+ let done = false;
65823
+ req.raw.on("close", () => {
65824
+ if (!done && isRunActive()) cancelActiveRun();
65825
+ });
65826
+ try {
65827
+ const connection = await extractConnection(`${appId}-connimport`, path, id);
65828
+ if (!connection.geometry.length) return reply.send({ ok: false, error: "that connection has no importable geometry" });
65829
+ return { ok: true, connection };
65830
+ } catch (e) {
65831
+ if (e instanceof ConnectionImportError) return reply.send({ ok: false, error: e.message });
65832
+ app.log.error({ appId, id, err: e instanceof Error ? e.message : e }, "import-connection extract failed");
65833
+ return reply.status(500).send({ ok: false, error: "could not read that connection" });
65834
+ } finally {
65835
+ done = true;
65836
+ }
65837
+ }
65838
+ );
65363
65839
  app.post("/api/use-template", async (req, reply) => {
65364
65840
  const { appId, templateId } = req.body ?? {};
65365
65841
  if (!appId || !templateId) return reply.status(400).send({ ok: false, error: "appId and templateId required" });
@@ -65447,7 +65923,7 @@ async function startServer() {
65447
65923
  warn(`last-run-status:${ref} \u2014 trace exists but couldn't be parsed (corrupt/truncated)`);
65448
65924
  let finishedAt2 = null;
65449
65925
  try {
65450
- finishedAt2 = (0, import_node_fs33.statSync)(latest.path).mtime.toISOString();
65926
+ finishedAt2 = (0, import_node_fs35.statSync)(latest.path).mtime.toISOString();
65451
65927
  } catch {
65452
65928
  finishedAt2 = null;
65453
65929
  }
@@ -65457,7 +65933,7 @@ async function startServer() {
65457
65933
  let finishedAt = typeof runEnd?.ts === "string" ? runEnd.ts : null;
65458
65934
  if (!finishedAt) {
65459
65935
  try {
65460
- finishedAt = (0, import_node_fs33.statSync)(latest.path).mtime.toISOString();
65936
+ finishedAt = (0, import_node_fs35.statSync)(latest.path).mtime.toISOString();
65461
65937
  } catch {
65462
65938
  finishedAt = null;
65463
65939
  }