@floless/app 0.73.1 → 0.75.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: join31 } = require("path");
5492
+ var { join: join33 } = 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"] || join31(__dirname, "lib", "worker.js");
5543
+ const toExecute = bundlerOverrides["thread-stream-worker"] || join33(__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: existsSync27 } = require("node:fs");
6009
+ var { existsSync: existsSync28 } = require("node:fs");
6010
6010
  var getCallers = require_caller();
6011
- var { join: join31, isAbsolute: isAbsolute2, sep: sep4 } = require("node:path");
6011
+ var { join: join33, 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) && !existsSync27(path);
6083
+ return isAbsolute2(path) && !existsSync28(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"] || join31(__dirname, "worker.js");
6164
+ target = bundlerOverrides["pino-worker"] || join33(__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"] || join31(__dirname, "worker.js");
6182
+ target = bundlerOverrides["pino-worker"] || join33(__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 join31(__dirname, "..", "file.js");
6205
+ return join33(__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 join31 = ",";
7185
+ let join33 = ",";
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
- join31 = `,
7199
+ join33 = `,
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 += join31;
7207
+ res += join33;
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 += `${join31}"... ${getItemCount(removedKeys)} not stringified"`;
7213
+ res += `${join33}"... ${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
- join31 = `,
7234
+ join33 = `,
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 = join31;
7248
+ separator = join33;
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 = join31;
7254
+ separator = join33;
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 join31 = ",";
7295
+ let join33 = ",";
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
- join31 = `,
7308
+ join33 = `,
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 += join31;
7316
+ res += join33;
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 += `${join31}"... ${getItemCount(removedKeys)} not stringified"`;
7322
+ res += `${join33}"... ${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
- join31 = `,
7335
+ join33 = `,
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 = join31;
7344
+ separator = join33;
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 join32 = `,
7402
+ const join34 = `,
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 += join32;
7409
+ res2 += join34;
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 += `${join32}"... ${getItemCount(removedKeys)} not stringified"`;
7415
+ res2 += `${join34}"... ${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 join31 = `,
7431
+ const join33 = `,
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, join31, maximumBreadth);
7437
+ res += stringifyTypedArray(value, join33, maximumBreadth);
7438
7438
  keys = keys.slice(value.length);
7439
7439
  maximumPropertiesToStringify -= value.length;
7440
- separator = join31;
7440
+ separator = join33;
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 = join31;
7451
+ separator = join33;
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 = join31;
7457
+ separator = join33;
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 join31 = path.join;
41994
+ var join33 = 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(join31(root, path2));
42081
+ path2 = normalize2(join33(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 = join31(path2, index);
42364
+ const p = join33(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_path30 = require("node:path");
50830
+ var import_node_path32 = require("node:path");
50831
50831
  var import_node_os21 = require("node:os");
50832
- var import_node_fs32 = require("node:fs");
50832
+ var import_node_fs34 = 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.73.1" : void 0,
53096
+ define: true ? "0.75.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.73.1" : void 0 });
53106
+ return resolveChannel({ isSea: isSea2(), define: true ? "0.75.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_crypto8 = require("node:crypto");
53471
+ var import_node_crypto9 = require("node:crypto");
53401
53472
 
53402
53473
  // graft.ts
53403
53474
  var TEKLA_MARKER = "Recipe: Tekla model plug-in";
@@ -53746,14 +53817,158 @@ function decodeSnapshots(inputs) {
53746
53817
  });
53747
53818
  }
53748
53819
 
53820
+ // connection-import.ts
53821
+ var import_node_fs17 = require("node:fs");
53822
+ var import_node_path15 = require("node:path");
53823
+ var AGENT = "connection-reader";
53824
+ var MAX_PARTS = 5e3;
53825
+ var MAX_VERTICES = 3e6;
53826
+ var readerChain = Promise.resolve();
53827
+ var ConnectionImportError = class extends Error {
53828
+ };
53829
+ async function ensureConnectionReader() {
53830
+ try {
53831
+ await aware.ensureAgentInstalled(AGENT);
53832
+ await aware.ensureConnectionReaderBridge();
53833
+ } catch (e) {
53834
+ throw new ConnectionImportError(`could not set up the connection reader \u2014 check your network and try again${e instanceof Error && e.message ? ` (${e.message})` : ""}`);
53835
+ }
53836
+ }
53837
+ function writeReaderApp(companionId, command, ifcPath, id) {
53838
+ const dir = appPath(companionId);
53839
+ (0, import_node_fs17.mkdirSync)(dir, { recursive: true });
53840
+ const flo = (0, import_node_path15.join)(dir, `${companionId}.flo`);
53841
+ const lines = [
53842
+ `app: ${companionId}`,
53843
+ "version: 0.1.0",
53844
+ `display-name: Connection reader (${command})`,
53845
+ `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."}`,
53846
+ "exposes-as-agent: false",
53847
+ "requires:",
53848
+ ` - ${AGENT}@0.1.x`,
53849
+ "layout: linear",
53850
+ "nodes:",
53851
+ ` - id: ${command}`,
53852
+ ` agent: ${AGENT}`,
53853
+ ` command: ${command}`,
53854
+ " config:",
53855
+ ` ifc-path: ${JSON.stringify(ifcPath)}`
53856
+ ];
53857
+ if (command === "extract") lines.push(` id: ${JSON.stringify(id ?? "")}`);
53858
+ lines.push("");
53859
+ (0, import_node_fs17.writeFileSync)(flo, lines.join("\n"));
53860
+ return flo;
53861
+ }
53862
+ function nodeOutput(traceText, node) {
53863
+ const events = traceText ? parseTrace(traceText) : [];
53864
+ const ev = [...events].reverse().find((e) => e.kind === "node-output" && e.node === node);
53865
+ const data = ev ? ev.data : null;
53866
+ return data && typeof data === "object" ? data : null;
53867
+ }
53868
+ async function runReader(companionId, command, ifcPath, id) {
53869
+ const prev = readerChain;
53870
+ let release;
53871
+ readerChain = new Promise((r) => release = r);
53872
+ await prev.catch(() => {
53873
+ });
53874
+ try {
53875
+ const flo = writeReaderApp(companionId, command, ifcPath, id);
53876
+ try {
53877
+ await aware.compile(flo);
53878
+ } catch (e) {
53879
+ throw new ConnectionImportError(`could not compile the connection reader: ${e instanceof Error ? e.message : "compile error"}`);
53880
+ }
53881
+ let traceText;
53882
+ try {
53883
+ ({ traceText } = await aware.run(companionId, {}));
53884
+ } catch (e) {
53885
+ const reason = e instanceof AwareError ? String(e.detail?.stderr ?? "").trim() : "";
53886
+ throw new ConnectionImportError(reason || (e instanceof Error ? e.message : "the connection reader failed"));
53887
+ }
53888
+ const out = nodeOutput(traceText, command);
53889
+ if (!out) throw new ConnectionImportError("the connection reader returned no result");
53890
+ return out;
53891
+ } finally {
53892
+ release();
53893
+ }
53894
+ }
53895
+ async function listConnections(companionId, ifcPath) {
53896
+ await ensureConnectionReader();
53897
+ const out = await runReader(companionId, "list", ifcPath);
53898
+ if (!Array.isArray(out.connections)) {
53899
+ throw new ConnectionImportError(typeof out.message === "string" ? out.message : "no connections found in that IFC file");
53900
+ }
53901
+ return out.connections.filter((c) => !!c && typeof c === "object");
53902
+ }
53903
+ function bboxCenter(parts) {
53904
+ let mnx = Infinity, mny = Infinity, mnz = Infinity, mxx = -Infinity, mxy = -Infinity, mxz = -Infinity;
53905
+ for (const p of parts) {
53906
+ for (let i = 0; i + 2 < p.positions.length; i += 3) {
53907
+ const x = p.positions[i], y = p.positions[i + 1], z = p.positions[i + 2];
53908
+ if (x < mnx) mnx = x;
53909
+ if (x > mxx) mxx = x;
53910
+ if (y < mny) mny = y;
53911
+ if (y > mxy) mxy = y;
53912
+ if (z < mnz) mnz = z;
53913
+ if (z > mxz) mxz = z;
53914
+ }
53915
+ }
53916
+ if (!isFinite(mnx)) return [0, 0, 0];
53917
+ return [(mnx + mxx) / 2, (mny + mxy) / 2, (mnz + mxz) / 2];
53918
+ }
53919
+ function shiftPositions(positions, [dx, dy, dz]) {
53920
+ const out = new Array(positions.length);
53921
+ for (let i = 0; i + 2 < positions.length; i += 3) {
53922
+ out[i] = positions[i] - dx;
53923
+ out[i + 1] = positions[i + 1] - dy;
53924
+ out[i + 2] = positions[i + 2] - dz;
53925
+ }
53926
+ return out;
53927
+ }
53928
+ function validPart(p) {
53929
+ const q = p;
53930
+ return !!q && Array.isArray(q.positions) && Array.isArray(q.indices) && q.positions.length >= 9 && q.indices.length >= 3 && q.positions.length % 3 === 0;
53931
+ }
53932
+ function toCustomConnection(raw, fallbackId) {
53933
+ const conn = raw.connection;
53934
+ if (!conn || typeof conn !== "object") {
53935
+ throw new ConnectionImportError(typeof raw.message === "string" ? raw.message : "could not read that connection");
53936
+ }
53937
+ const c = conn;
53938
+ const valid = (Array.isArray(c.parts) ? c.parts : []).filter(validPart);
53939
+ if (valid.length > MAX_PARTS) throw new ConnectionImportError(`that connection has too many parts (${valid.length}) to import`);
53940
+ let totalVerts = 0;
53941
+ for (const p of valid) totalVerts += p.positions.length / 3;
53942
+ if (totalVerts > MAX_VERTICES) throw new ConnectionImportError(`that connection is too large to import (${Math.round(totalVerts / 1e3)}k vertices)`);
53943
+ const anchor = bboxCenter(valid);
53944
+ const geometry = valid.map((p) => ({
53945
+ ...typeof p.id === "string" ? { id: p.id } : {},
53946
+ ...typeof p.role === "string" ? { role: p.role } : {},
53947
+ positions: shiftPositions(p.positions, anchor),
53948
+ indices: p.indices.slice()
53949
+ }));
53950
+ return {
53951
+ id: typeof c.id === "string" ? c.id : fallbackId,
53952
+ name: typeof c.name === "string" ? c.name : "Imported connection",
53953
+ type: typeof c.type === "string" ? c.type : null,
53954
+ members: Array.isArray(c.members) ? c.members.filter((m) => typeof m === "string") : [],
53955
+ geometry
53956
+ };
53957
+ }
53958
+ async function extractConnection(companionId, ifcPath, id) {
53959
+ await ensureConnectionReader();
53960
+ const out = await runReader(companionId, "extract", ifcPath, id);
53961
+ return toCustomConnection(out, id);
53962
+ }
53963
+
53749
53964
  // contract-store.ts
53750
- var import_node_fs18 = require("node:fs");
53751
- var import_node_path16 = require("node:path");
53965
+ var import_node_fs19 = require("node:fs");
53966
+ var import_node_path17 = require("node:path");
53752
53967
  var import_node_os12 = require("node:os");
53753
53968
 
53754
53969
  // contract-schema.ts
53755
- var import_node_fs17 = require("node:fs");
53756
- var import_node_path15 = require("node:path");
53970
+ var import_node_fs18 = require("node:fs");
53971
+ var import_node_path16 = require("node:path");
53757
53972
  var import_node_url2 = require("node:url");
53758
53973
  function validate(doc, schema) {
53759
53974
  const errors = [];
@@ -53855,16 +54070,16 @@ var _cache = /* @__PURE__ */ new Map();
53855
54070
  function loadContractSchema(file) {
53856
54071
  const hit = _cache.get(file);
53857
54072
  if (hit) return hit;
53858
- const here2 = (0, import_node_path15.dirname)((0, import_node_url2.fileURLToPath)(__import_meta_url));
54073
+ const here2 = (0, import_node_path16.dirname)((0, import_node_url2.fileURLToPath)(__import_meta_url));
53859
54074
  const candidates = [
53860
- (0, import_node_path15.join)(here2, "..", "schemas", file),
54075
+ (0, import_node_path16.join)(here2, "..", "schemas", file),
53861
54076
  // dev: server/ next to schemas/
53862
- (0, import_node_path15.join)(here2, "schemas", file)
54077
+ (0, import_node_path16.join)(here2, "schemas", file)
53863
54078
  // bundled: dist/ holds ./schemas
53864
54079
  ];
53865
54080
  for (const p of candidates) {
53866
54081
  try {
53867
- const parsed = JSON.parse((0, import_node_fs17.readFileSync)(p, "utf8"));
54082
+ const parsed = JSON.parse((0, import_node_fs18.readFileSync)(p, "utf8"));
53868
54083
  _cache.set(file, parsed);
53869
54084
  return parsed;
53870
54085
  } catch (err2) {
@@ -53894,8 +54109,8 @@ function validateContract(doc) {
53894
54109
  // contract-store.ts
53895
54110
  var ContractError = class extends Error {
53896
54111
  };
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");
54112
+ var ROOT3 = process.env.FLOLESS_HOME ?? (0, import_node_path17.join)((0, import_node_os12.homedir)(), ".floless");
54113
+ var DIR = (0, import_node_path17.join)(ROOT3, "contracts");
53899
54114
  function safeId(appId) {
53900
54115
  if (!/^[a-z0-9][a-z0-9._-]*$/i.test(appId)) throw new ContractError(`invalid appId: ${appId}`);
53901
54116
  return appId;
@@ -53905,14 +54120,14 @@ function safeProjectSeg(projectId) {
53905
54120
  return projectId;
53906
54121
  }
53907
54122
  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`);
54123
+ if (projectId) return (0, import_node_path17.join)(ROOT3, "projects", safeProjectSeg(projectId), "contract.json");
54124
+ return (0, import_node_path17.join)(DIR, `${safeId(appId)}.json`);
53910
54125
  }
53911
54126
  function readContract(appId, projectId) {
53912
54127
  const p = contractPath(appId, projectId);
53913
- if (!(0, import_node_fs18.existsSync)(p)) return null;
54128
+ if (!(0, import_node_fs19.existsSync)(p)) return null;
53914
54129
  try {
53915
- return JSON.parse((0, import_node_fs18.readFileSync)(p, "utf8"));
54130
+ return JSON.parse((0, import_node_fs19.readFileSync)(p, "utf8"));
53916
54131
  } catch (e) {
53917
54132
  console.warn(`readContract: ignoring unreadable contract at ${p}: ${e instanceof Error ? e.message : e}`);
53918
54133
  return null;
@@ -53926,11 +54141,11 @@ function writeContract(appId, doc, projectId) {
53926
54141
  throw new ContractError(`contract failed schema validation \u2014 ${first}`);
53927
54142
  }
53928
54143
  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 });
54144
+ if (!(0, import_node_fs19.existsSync)((0, import_node_path17.dirname)(p))) throw new ContractError(`unknown project: ${projectId}`);
54145
+ } else if (!(0, import_node_fs19.existsSync)(DIR)) {
54146
+ (0, import_node_fs19.mkdirSync)(DIR, { recursive: true });
53932
54147
  }
53933
- (0, import_node_fs18.writeFileSync)(p, JSON.stringify(doc));
54148
+ (0, import_node_fs19.writeFileSync)(p, JSON.stringify(doc));
53934
54149
  }
53935
54150
 
53936
54151
  // contract-resolve.ts
@@ -53953,15 +54168,15 @@ function readContractForApp(appId, readAppFn = readApp) {
53953
54168
  }
53954
54169
 
53955
54170
  // projects-store.ts
53956
- var import_node_fs19 = require("node:fs");
53957
- var import_node_path17 = require("node:path");
54171
+ var import_node_fs20 = require("node:fs");
54172
+ var import_node_path18 = require("node:path");
53958
54173
  var import_node_os13 = require("node:os");
53959
54174
  var import_node_crypto6 = require("node:crypto");
53960
54175
  var ProjectError = class extends Error {
53961
54176
  };
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");
54177
+ var ROOT4 = process.env.FLOLESS_HOME ?? (0, import_node_path18.join)((0, import_node_os13.homedir)(), ".floless");
54178
+ var DIR2 = (0, import_node_path18.join)(ROOT4, "projects");
54179
+ var ARCHIVE = (0, import_node_path18.join)(DIR2, ".archive");
53965
54180
  function safeProjectId(id) {
53966
54181
  if (typeof id !== "string" || !/^[a-z0-9][a-z0-9-]*$/.test(id)) {
53967
54182
  throw new ProjectError(`invalid project id: ${String(id)}`);
@@ -53973,28 +54188,28 @@ function slugify(name) {
53973
54188
  return s || "project";
53974
54189
  }
53975
54190
  function projectDir(id) {
53976
- return (0, import_node_path17.join)(DIR2, safeProjectId(id));
54191
+ return (0, import_node_path18.join)(DIR2, safeProjectId(id));
53977
54192
  }
53978
54193
  function projectExportsDir(id) {
53979
- return (0, import_node_path17.join)(projectDir(id), "exports");
54194
+ return (0, import_node_path18.join)(projectDir(id), "exports");
53980
54195
  }
53981
- var metaPath = (id) => (0, import_node_path17.join)(projectDir(id), "project.json");
54196
+ var metaPath = (id) => (0, import_node_path18.join)(projectDir(id), "project.json");
53982
54197
  function readMeta(id) {
53983
54198
  const p = metaPath(id);
53984
- if (!(0, import_node_fs19.existsSync)(p)) return null;
54199
+ if (!(0, import_node_fs20.existsSync)(p)) return null;
53985
54200
  try {
53986
- const meta = JSON.parse((0, import_node_fs19.readFileSync)(p, "utf8"));
54201
+ const meta = JSON.parse((0, import_node_fs20.readFileSync)(p, "utf8"));
53987
54202
  return meta && meta.id === id ? meta : null;
53988
54203
  } catch (e) {
53989
54204
  console.warn(`projects-store: ignoring unreadable project.json at ${p}: ${e instanceof Error ? e.message : e}`);
53990
54205
  return null;
53991
54206
  }
53992
54207
  }
53993
- var writeMeta = (meta) => (0, import_node_fs19.writeFileSync)(metaPath(meta.id), JSON.stringify(meta, null, 2));
54208
+ var writeMeta = (meta) => (0, import_node_fs20.writeFileSync)(metaPath(meta.id), JSON.stringify(meta, null, 2));
53994
54209
  function listProjects() {
53995
- if (!(0, import_node_fs19.existsSync)(DIR2)) return [];
54210
+ if (!(0, import_node_fs20.existsSync)(DIR2)) return [];
53996
54211
  const out = [];
53997
- for (const entry2 of (0, import_node_fs19.readdirSync)(DIR2, { withFileTypes: true })) {
54212
+ for (const entry2 of (0, import_node_fs20.readdirSync)(DIR2, { withFileTypes: true })) {
53998
54213
  if (!entry2.isDirectory() || entry2.name.startsWith(".")) continue;
53999
54214
  const meta = readMeta(entry2.name);
54000
54215
  if (meta) out.push(meta);
@@ -54010,8 +54225,8 @@ function createProject(input) {
54010
54225
  if (!name) throw new ProjectError("project name required");
54011
54226
  if (!/^[a-z0-9][a-z0-9._-]*$/i.test(app)) throw new ProjectError(`invalid app id: ${app || "(empty)"}`);
54012
54227
  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 });
54228
+ while ((0, import_node_fs20.existsSync)(projectDir(id))) id = `${slugify(name)}-${(0, import_node_crypto6.randomBytes)(3).toString("hex")}`;
54229
+ (0, import_node_fs20.mkdirSync)(projectDir(id), { recursive: true });
54015
54230
  const now = (/* @__PURE__ */ new Date()).toISOString();
54016
54231
  const meta = { id, name, app, createdAt: now, updatedAt: now };
54017
54232
  writeMeta(meta);
@@ -54046,7 +54261,7 @@ function duplicateProject(id, name) {
54046
54261
  name: typeof name === "string" && name.trim() ? name.trim() : `${src.name} (copy)`,
54047
54262
  app: src.app
54048
54263
  });
54049
- (0, import_node_fs19.cpSync)(projectDir(src.id), projectDir(copy.id), {
54264
+ (0, import_node_fs20.cpSync)(projectDir(src.id), projectDir(copy.id), {
54050
54265
  recursive: true,
54051
54266
  filter: (p) => !p.endsWith("project.json")
54052
54267
  });
@@ -54054,18 +54269,117 @@ function duplicateProject(id, name) {
54054
54269
  }
54055
54270
  function archiveProject(id) {
54056
54271
  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);
54272
+ if (!(0, import_node_fs20.existsSync)(dir)) throw new ProjectError(`unknown project: ${id}`);
54273
+ (0, import_node_fs20.mkdirSync)(ARCHIVE, { recursive: true });
54274
+ const dest = (0, import_node_path18.join)(ARCHIVE, id);
54275
+ if ((0, import_node_fs20.existsSync)(dest)) throw new ProjectError(`already archived: ${id}`);
54276
+ (0, import_node_fs20.renameSync)(dir, dest);
54277
+ }
54278
+
54279
+ // project-versions-store.ts
54280
+ var import_node_crypto7 = require("node:crypto");
54281
+ var import_node_fs21 = require("node:fs");
54282
+ var import_node_path19 = require("node:path");
54283
+ var BLOB_MIN = 8192;
54284
+ var BLOB_KEY = "__blobRef__";
54285
+ var HASH_RE = /^[0-9a-f]{64}$/;
54286
+ var versionsDir = (id) => (0, import_node_path19.join)(projectDir(id), "versions");
54287
+ var objectsDir = (id) => (0, import_node_path19.join)(projectDir(id), "objects");
54288
+ var logPath = (id) => (0, import_node_path19.join)(versionsDir(id), "log.json");
54289
+ function writeAtomic(path, text) {
54290
+ const tmp = `${path}.${process.pid}.tmp`;
54291
+ (0, import_node_fs21.writeFileSync)(tmp, text);
54292
+ (0, import_node_fs21.renameSync)(tmp, path);
54293
+ }
54294
+ function isVersionMeta(v) {
54295
+ if (!v || typeof v !== "object") return false;
54296
+ const r = v;
54297
+ 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");
54298
+ }
54299
+ function loadLog(id) {
54300
+ try {
54301
+ const parsed = JSON.parse((0, import_node_fs21.readFileSync)(logPath(id), "utf8"));
54302
+ const raw = parsed && typeof parsed === "object" && Array.isArray(parsed.versions) ? parsed.versions : [];
54303
+ return { versions: raw.filter(isVersionMeta) };
54304
+ } catch {
54305
+ return { versions: [] };
54306
+ }
54307
+ }
54308
+ function dehydrate(node, blobs) {
54309
+ if (typeof node === "string") {
54310
+ if (node.length > BLOB_MIN) {
54311
+ const hash = (0, import_node_crypto7.createHash)("sha256").update(node).digest("hex");
54312
+ blobs.set(hash, node);
54313
+ return { [BLOB_KEY]: hash };
54314
+ }
54315
+ return node;
54316
+ }
54317
+ if (Array.isArray(node)) return node.map((x) => dehydrate(x, blobs));
54318
+ if (node && typeof node === "object") {
54319
+ const out = {};
54320
+ for (const [k, v] of Object.entries(node)) out[k] = dehydrate(v, blobs);
54321
+ return out;
54322
+ }
54323
+ return node;
54324
+ }
54325
+ function rehydrate(node, id) {
54326
+ if (Array.isArray(node)) return node.map((x) => rehydrate(x, id));
54327
+ if (node && typeof node === "object") {
54328
+ const obj = node;
54329
+ const keys = Object.keys(obj);
54330
+ const ref = obj[BLOB_KEY];
54331
+ if (keys.length === 1 && keys[0] === BLOB_KEY && typeof ref === "string" && HASH_RE.test(ref)) {
54332
+ return (0, import_node_fs21.readFileSync)((0, import_node_path19.join)(objectsDir(id), ref), "utf8");
54333
+ }
54334
+ const out = {};
54335
+ for (const [k, v] of Object.entries(obj)) out[k] = rehydrate(v, id);
54336
+ return out;
54337
+ }
54338
+ return node;
54339
+ }
54340
+ function createVersion(id, contract, meta) {
54341
+ (0, import_node_fs21.mkdirSync)(versionsDir(id), { recursive: true });
54342
+ (0, import_node_fs21.mkdirSync)(objectsDir(id), { recursive: true });
54343
+ const blobs = /* @__PURE__ */ new Map();
54344
+ const skeleton = dehydrate(contract, blobs);
54345
+ for (const [hash, text] of blobs) {
54346
+ const p = (0, import_node_path19.join)(objectsDir(id), hash);
54347
+ if (!(0, import_node_fs21.existsSync)(p)) writeAtomic(p, text);
54348
+ }
54349
+ const log2 = loadLog(id);
54350
+ const n = (log2.versions.at(-1)?.n ?? 0) + 1;
54351
+ const row = {
54352
+ n,
54353
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
54354
+ author: meta.author,
54355
+ message: meta.message,
54356
+ gate: meta.gate,
54357
+ kind: meta.kind
54358
+ };
54359
+ writeAtomic((0, import_node_path19.join)(versionsDir(id), `${n}.json`), JSON.stringify({ meta: row, contract: skeleton }, null, 2));
54360
+ log2.versions.push(row);
54361
+ writeAtomic(logPath(id), JSON.stringify(log2, null, 2));
54362
+ return row;
54363
+ }
54364
+ function listVersions(id) {
54365
+ const versions = loadLog(id).versions;
54366
+ const tip = versions.at(-1)?.n;
54367
+ return versions.map((v) => ({ ...v, current: v.n === tip })).reverse();
54368
+ }
54369
+ function readVersion(id, n) {
54370
+ try {
54371
+ const snap = JSON.parse((0, import_node_fs21.readFileSync)((0, import_node_path19.join)(versionsDir(id), `${n}.json`), "utf8"));
54372
+ return rehydrate(snap.contract, id);
54373
+ } catch {
54374
+ return null;
54375
+ }
54062
54376
  }
54063
54377
 
54064
54378
  // contract-bake.ts
54065
- var import_node_fs20 = require("node:fs");
54379
+ var import_node_fs22 = require("node:fs");
54066
54380
  var import_yaml5 = __toESM(require_dist6(), 1);
54067
54381
  function bakeContractIntoApp(sourcePath, contract) {
54068
- const doc = (0, import_yaml5.parseDocument)((0, import_node_fs20.readFileSync)(sourcePath, "utf8"));
54382
+ const doc = (0, import_yaml5.parseDocument)((0, import_node_fs22.readFileSync)(sourcePath, "utf8"));
54069
54383
  if (doc.errors.length > 0) {
54070
54384
  throw new Error(`contract bake: source is not valid YAML: ${doc.errors[0]?.message ?? "parse error"}`);
54071
54385
  }
@@ -54115,7 +54429,7 @@ function bakeContractIntoApp(sourcePath, contract) {
54115
54429
  );
54116
54430
  }
54117
54431
  doc.setIn(["nodes", idx, "config", "takeoff"], baked);
54118
- (0, import_node_fs20.writeFileSync)(sourcePath, doc.toString());
54432
+ (0, import_node_fs22.writeFileSync)(sourcePath, doc.toString());
54119
54433
  }
54120
54434
 
54121
54435
  // vectorize.ts
@@ -54438,8 +54752,12 @@ var GROUPS = {
54438
54752
  bolt: { key: "bolt", label: "Bolts", color: "#b0c4d8" },
54439
54753
  // cool zinc-grey (matches the anchor fasteners)
54440
54754
  stiffener: { key: "stiffener", label: "Stiffeners", color: "#9aafc3" },
54441
- cope: { key: "cope", label: "Copes", color: "#c2703a" }
54755
+ cope: { key: "cope", label: "Copes", color: "#c2703a" },
54442
54756
  // a muted cut/burn amber — the removed corner (subtractive; rendered as the notch, not a swatch mesh)
54757
+ // Imported (custom) connection — one neutral steel band for the whole opaque assembly, so it reads
54758
+ // as a single imported unit (not scattered across the recipe-part legend rows). Roles (plate/bolt/
54759
+ // weld) are kept on each part's meta for labels, but share this one legend group + colour.
54760
+ custom: { key: "custom", label: "Imported connections", color: "#8a97a8" }
54443
54761
  };
54444
54762
  var EMBED = 1;
54445
54763
  function num(params, key, def) {
@@ -54798,17 +55116,48 @@ function expandShearPlate(joint, beam, support) {
54798
55116
  if (cope.bottom) pushCope("bottom", cope.bottom);
54799
55117
  return parts;
54800
55118
  }
55119
+ function expandCustom(joint) {
55120
+ const place = finite3(joint.place) ? joint.place : [0, 0, 0];
55121
+ const [dx, dy, dz] = place;
55122
+ const out = [];
55123
+ (joint.geometry ?? []).forEach((g, i) => {
55124
+ if (!g || !Array.isArray(g.positions) || !Array.isArray(g.indices)) return;
55125
+ if (g.positions.length < 9 || g.indices.length < 3 || g.positions.length % 3 !== 0) return;
55126
+ const positions = new Array(g.positions.length);
55127
+ for (let k = 0; k < g.positions.length; k += 3) {
55128
+ positions[k] = g.positions[k] + dx;
55129
+ positions[k + 1] = g.positions[k + 1] + dy;
55130
+ positions[k + 2] = g.positions[k + 2] + dz;
55131
+ }
55132
+ const role = typeof g.role === "string" ? g.role : void 0;
55133
+ const label = role ? role.charAt(0).toUpperCase() + role.slice(1) : "Imported part";
55134
+ out.push({ id: `${joint.id}:${g.id || `m${i}`}`, group: "custom", kind: "mesh", positions, indices: g.indices.slice(), meta: { label, role } });
55135
+ });
55136
+ return out;
55137
+ }
54801
55138
  function expandJoints(joints, memberGeo) {
54802
55139
  const elements = [];
54803
55140
  const skipped = [];
54804
55141
  const usedGroups = /* @__PURE__ */ new Set();
54805
55142
  (joints ?? []).forEach((j, i) => {
54806
- if (!j || !j.id || !j.main) {
54807
- skipped.push(j?.id || `joint#${i}`);
55143
+ if (!j || !j.id) {
55144
+ skipped.push(`joint#${i}`);
54808
55145
  return;
54809
55146
  }
54810
- if (j.kind === "base-plate") {
54811
- const col = memberGeo.get(j.main);
55147
+ if (j.kind === "custom") {
55148
+ const parts = expandCustom(j);
55149
+ if (!parts.length) {
55150
+ skipped.push(j.id);
55151
+ return;
55152
+ }
55153
+ for (const part of parts) {
55154
+ part.conn = j.id;
55155
+ part.connKind = j.kind;
55156
+ elements.push(part);
55157
+ usedGroups.add(part.group);
55158
+ }
55159
+ } else if (j.kind === "base-plate") {
55160
+ const col = j.main ? memberGeo.get(j.main) : void 0;
54812
55161
  if (!col || col.role !== "column") {
54813
55162
  skipped.push(j.id);
54814
55163
  return;
@@ -54824,7 +55173,7 @@ function expandJoints(joints, memberGeo) {
54824
55173
  usedGroups.add(part.group);
54825
55174
  }
54826
55175
  } else if (j.kind === "shear-plate") {
54827
- const beam = memberGeo.get(j.main);
55176
+ const beam = j.main ? memberGeo.get(j.main) : void 0;
54828
55177
  if (!beam || beam.role !== "beam") {
54829
55178
  skipped.push(j.id);
54830
55179
  return;
@@ -55046,10 +55395,10 @@ function contractToBom(contractInput) {
55046
55395
 
55047
55396
  // bom-export.ts
55048
55397
  var import_node_os14 = require("node:os");
55049
- var import_node_path18 = require("node:path");
55398
+ var import_node_path20 = require("node:path");
55050
55399
 
55051
55400
  // node_modules/write-excel-file/modules/export/writeXlsxFileNode.js
55052
- var import_node_fs21 = __toESM(require("node:fs"), 1);
55401
+ var import_node_fs23 = __toESM(require("node:fs"), 1);
55053
55402
 
55054
55403
  // node_modules/write-excel-file/modules/xlsx/helpers/features/getAdditionalContent.js
55055
55404
  function _createForOfIteratorHelperLoose(r, e) {
@@ -59221,7 +59570,7 @@ function writeXlsxFile(arg1, arg2, arg3) {
59221
59570
  },
59222
59571
  toFile: function toFile(filePath) {
59223
59572
  return createReadableStream().then(function(readableStream) {
59224
- return pipe(readableStream, import_node_fs21.default.createWriteStream(filePath));
59573
+ return pipe(readableStream, import_node_fs23.default.createWriteStream(filePath));
59225
59574
  });
59226
59575
  }
59227
59576
  };
@@ -59246,8 +59595,8 @@ function pipe(readableStream, writableStream) {
59246
59595
 
59247
59596
  // bom-export.ts
59248
59597
  function bomExportPath(appId, format) {
59249
- const root = process.env.FLOLESS_HOME ?? (0, import_node_path18.join)((0, import_node_os14.homedir)(), ".floless");
59250
- return (0, import_node_path18.join)(root, "exports", appId, `${appId}-bom.${format}`);
59598
+ const root = process.env.FLOLESS_HOME ?? (0, import_node_path20.join)((0, import_node_os14.homedir)(), ".floless");
59599
+ return (0, import_node_path20.join)(root, "exports", appId, `${appId}-bom.${format}`);
59251
59600
  }
59252
59601
  function csvField(v) {
59253
59602
  let s = String(v);
@@ -59275,11 +59624,11 @@ async function bomToXlsx(bom) {
59275
59624
  }
59276
59625
 
59277
59626
  // scene-bake.ts
59278
- var import_node_fs22 = require("node:fs");
59279
- var import_node_path19 = require("node:path");
59627
+ var import_node_fs24 = require("node:fs");
59628
+ var import_node_path21 = require("node:path");
59280
59629
  var import_yaml6 = __toESM(require_dist6(), 1);
59281
59630
  function bakeSceneIntoApp(sourcePath, scene, agent = "viewer-3d") {
59282
- const doc = (0, import_yaml6.parseDocument)((0, import_node_fs22.readFileSync)(sourcePath, "utf8"));
59631
+ const doc = (0, import_yaml6.parseDocument)((0, import_node_fs24.readFileSync)(sourcePath, "utf8"));
59283
59632
  if (doc.errors.length > 0) {
59284
59633
  throw new Error(`scene bake: source is not valid YAML: ${doc.errors[0]?.message ?? "parse error"}`);
59285
59634
  }
@@ -59288,10 +59637,10 @@ function bakeSceneIntoApp(sourcePath, scene, agent = "viewer-3d") {
59288
59637
  const idx = items.findIndex((it) => ((0, import_yaml6.isMap)(it) ? it.toJSON() : null)?.agent === agent);
59289
59638
  if (idx < 0) throw new Error(`no ${agent} render node to bake the scene into`);
59290
59639
  doc.setIn(["nodes", idx, "config", "scene"], scene);
59291
- (0, import_node_fs22.writeFileSync)(sourcePath, doc.toString());
59640
+ (0, import_node_fs24.writeFileSync)(sourcePath, doc.toString());
59292
59641
  }
59293
59642
  function bakeNodeConfigById(sourcePath, nodeId, patch2) {
59294
- const doc = (0, import_yaml6.parseDocument)((0, import_node_fs22.readFileSync)(sourcePath, "utf8"));
59643
+ const doc = (0, import_yaml6.parseDocument)((0, import_node_fs24.readFileSync)(sourcePath, "utf8"));
59295
59644
  if (doc.errors.length > 0) {
59296
59645
  throw new Error(`node bake: source is not valid YAML: ${doc.errors[0]?.message ?? "parse error"}`);
59297
59646
  }
@@ -59300,12 +59649,12 @@ function bakeNodeConfigById(sourcePath, nodeId, patch2) {
59300
59649
  const idx = items.findIndex((it) => ((0, import_yaml6.isMap)(it) ? it.toJSON() : null)?.id === nodeId);
59301
59650
  if (idx < 0) throw new Error(`no node with id "${nodeId}"`);
59302
59651
  for (const [key, value] of Object.entries(patch2)) doc.setIn(["nodes", idx, "config", key], value);
59303
- (0, import_node_fs22.writeFileSync)(sourcePath, doc.toString());
59652
+ (0, import_node_fs24.writeFileSync)(sourcePath, doc.toString());
59304
59653
  }
59305
59654
  function writeViewer3dApp(dir, appId, scene) {
59306
- (0, import_node_fs22.mkdirSync)(dir, { recursive: true });
59307
- const flo = (0, import_node_path19.join)(dir, `${appId}.flo`);
59308
- (0, import_node_fs22.writeFileSync)(flo, [
59655
+ (0, import_node_fs24.mkdirSync)(dir, { recursive: true });
59656
+ const flo = (0, import_node_path21.join)(dir, `${appId}.flo`);
59657
+ (0, import_node_fs24.writeFileSync)(flo, [
59309
59658
  `app: ${appId}`,
59310
59659
  "version: 0.1.0",
59311
59660
  "display-name: Steel 3D",
@@ -59326,11 +59675,11 @@ function writeViewer3dApp(dir, appId, scene) {
59326
59675
  return flo;
59327
59676
  }
59328
59677
  function writeIfcApp(dir, appId, scene, outputPath, opts = {}) {
59329
- (0, import_node_fs22.mkdirSync)(dir, { recursive: true });
59330
- const flo = (0, import_node_path19.join)(dir, `${appId}.flo`);
59678
+ (0, import_node_fs24.mkdirSync)(dir, { recursive: true });
59679
+ const flo = (0, import_node_path21.join)(dir, `${appId}.flo`);
59331
59680
  const displayName = opts.displayName ?? "Steel IFC";
59332
59681
  const description = opts.description ?? "IFC file written from an approved steel.takeoff/v1 contract (a companion export; the contract is the source of truth).";
59333
- (0, import_node_fs22.writeFileSync)(flo, [
59682
+ (0, import_node_fs24.writeFileSync)(flo, [
59334
59683
  `app: ${appId}`,
59335
59684
  "version: 0.1.0",
59336
59685
  `display-name: ${displayName}`,
@@ -59353,9 +59702,9 @@ function writeIfcApp(dir, appId, scene, outputPath, opts = {}) {
59353
59702
  return flo;
59354
59703
  }
59355
59704
  function writeTeklaApp(dir, appId, scene, teklaVersion = "2026.0") {
59356
- (0, import_node_fs22.mkdirSync)(dir, { recursive: true });
59357
- const flo = (0, import_node_path19.join)(dir, `${appId}.flo`);
59358
- (0, import_node_fs22.writeFileSync)(flo, [
59705
+ (0, import_node_fs24.mkdirSync)(dir, { recursive: true });
59706
+ const flo = (0, import_node_path21.join)(dir, `${appId}.flo`);
59707
+ (0, import_node_fs24.writeFileSync)(flo, [
59359
59708
  `app: ${appId}`,
59360
59709
  "version: 0.1.0",
59361
59710
  "display-name: Steel to Tekla",
@@ -59763,13 +60112,13 @@ function scoreContract2(contractInput) {
59763
60112
  }
59764
60113
 
59765
60114
  // app-lifecycle.ts
59766
- var import_node_fs25 = require("node:fs");
60115
+ var import_node_fs27 = require("node:fs");
59767
60116
  var import_node_os16 = require("node:os");
59768
- var import_node_path22 = require("node:path");
60117
+ var import_node_path24 = require("node:path");
59769
60118
 
59770
60119
  // routines.ts
59771
- var import_node_fs24 = require("node:fs");
59772
- var import_node_path21 = require("node:path");
60120
+ var import_node_fs26 = require("node:fs");
60121
+ var import_node_path23 = require("node:path");
59773
60122
 
59774
60123
  // sse.ts
59775
60124
  var clients = /* @__PURE__ */ new Set();
@@ -59806,11 +60155,11 @@ function clientCount() {
59806
60155
  }
59807
60156
 
59808
60157
  // trigger-sessions.ts
59809
- var import_node_fs23 = require("node:fs");
60158
+ var import_node_fs25 = require("node:fs");
59810
60159
  var import_node_os15 = require("node:os");
59811
- var import_node_path20 = require("node:path");
60160
+ var import_node_path22 = require("node:path");
59812
60161
  var import_yaml7 = __toESM(require_dist6(), 1);
59813
- var AGENTS_DIR2 = process.env.AWARE_HOME ? (0, import_node_path20.join)(process.env.AWARE_HOME, "agents") : (0, import_node_path20.join)((0, import_node_os15.homedir)(), ".aware", "agents");
60162
+ var AGENTS_DIR2 = process.env.AWARE_HOME ? (0, import_node_path22.join)(process.env.AWARE_HOME, "agents") : (0, import_node_path22.join)((0, import_node_os15.homedir)(), ".aware", "agents");
59814
60163
  function summarizeFire(data) {
59815
60164
  if (!data) return "event";
59816
60165
  const type = typeof data.type === "string" ? data.type : "";
@@ -59827,10 +60176,10 @@ function mapTriggerState(phase) {
59827
60176
  function isHostBacked(agent, agentsDir = AGENTS_DIR2) {
59828
60177
  const safe = (n) => !n.includes("/") && !n.includes("\\") && !n.includes("..");
59829
60178
  if (!safe(agent)) return false;
59830
- const manifestPath = (0, import_node_path20.join)(agentsDir, agent, "manifest.yaml");
59831
- if (!(0, import_node_fs23.existsSync)(manifestPath)) return false;
60179
+ const manifestPath = (0, import_node_path22.join)(agentsDir, agent, "manifest.yaml");
60180
+ if (!(0, import_node_fs25.existsSync)(manifestPath)) return false;
59832
60181
  try {
59833
- const doc = (0, import_yaml7.parse)((0, import_node_fs23.readFileSync)(manifestPath, "utf8"));
60182
+ const doc = (0, import_yaml7.parse)((0, import_node_fs25.readFileSync)(manifestPath, "utf8"));
59834
60183
  const transport = doc?.transport;
59835
60184
  return !!(transport && typeof transport === "object" && "cli" in transport);
59836
60185
  } catch {
@@ -60115,7 +60464,7 @@ var RoutineError = class extends Error {
60115
60464
  }
60116
60465
  status;
60117
60466
  };
60118
- var ROUTINES_FILE = (0, import_node_path21.join)(flolessRoot, "routines.json");
60467
+ var ROUTINES_FILE = (0, import_node_path23.join)(flolessRoot, "routines.json");
60119
60468
  var routines = [];
60120
60469
  var loaded = false;
60121
60470
  function ensureLoaded() {
@@ -60151,10 +60500,10 @@ function sanitizeRoutine(raw) {
60151
60500
  };
60152
60501
  }
60153
60502
  function loadFromDisk() {
60154
- if (!(0, import_node_fs24.existsSync)(ROUTINES_FILE)) return [];
60503
+ if (!(0, import_node_fs26.existsSync)(ROUTINES_FILE)) return [];
60155
60504
  let text;
60156
60505
  try {
60157
- text = (0, import_node_fs24.readFileSync)(ROUTINES_FILE, "utf8");
60506
+ text = (0, import_node_fs26.readFileSync)(ROUTINES_FILE, "utf8");
60158
60507
  } catch {
60159
60508
  return [];
60160
60509
  }
@@ -60163,17 +60512,17 @@ function loadFromDisk() {
60163
60512
  return Array.isArray(parsed) ? parsed.map(sanitizeRoutine).filter((r) => r !== null) : [];
60164
60513
  } catch {
60165
60514
  try {
60166
- (0, import_node_fs24.renameSync)(ROUTINES_FILE, `${ROUTINES_FILE}.corrupt-${Date.now()}`);
60515
+ (0, import_node_fs26.renameSync)(ROUTINES_FILE, `${ROUTINES_FILE}.corrupt-${Date.now()}`);
60167
60516
  } catch {
60168
60517
  }
60169
60518
  return [];
60170
60519
  }
60171
60520
  }
60172
60521
  function saveRoutines() {
60173
- if (!(0, import_node_fs24.existsSync)(flolessRoot)) (0, import_node_fs24.mkdirSync)(flolessRoot, { recursive: true });
60522
+ if (!(0, import_node_fs26.existsSync)(flolessRoot)) (0, import_node_fs26.mkdirSync)(flolessRoot, { recursive: true });
60174
60523
  const tmp = `${ROUTINES_FILE}.${process.pid}.tmp`;
60175
- (0, import_node_fs24.writeFileSync)(tmp, JSON.stringify(routines, null, 2));
60176
- (0, import_node_fs24.renameSync)(tmp, ROUTINES_FILE);
60524
+ (0, import_node_fs26.writeFileSync)(tmp, JSON.stringify(routines, null, 2));
60525
+ (0, import_node_fs26.renameSync)(tmp, ROUTINES_FILE);
60177
60526
  }
60178
60527
  function listRoutines() {
60179
60528
  ensureLoaded();
@@ -60581,13 +60930,13 @@ var AppLifecycleError = class extends Error {
60581
60930
  }
60582
60931
  };
60583
60932
  function appsDir() {
60584
- return process.env.AWARE_HOME ? (0, import_node_path22.join)(process.env.AWARE_HOME, "apps") : (0, import_node_path22.join)((0, import_node_os16.homedir)(), ".aware", "apps");
60933
+ return process.env.AWARE_HOME ? (0, import_node_path24.join)(process.env.AWARE_HOME, "apps") : (0, import_node_path24.join)((0, import_node_os16.homedir)(), ".aware", "apps");
60585
60934
  }
60586
60935
  function appDirPath(id) {
60587
- return (0, import_node_path22.join)(appsDir(), id);
60936
+ return (0, import_node_path24.join)(appsDir(), id);
60588
60937
  }
60589
60938
  function appInstalled(id) {
60590
- return APP_ID3.test(id) && (0, import_node_fs25.existsSync)(appDirPath(id));
60939
+ return APP_ID3.test(id) && (0, import_node_fs27.existsSync)(appDirPath(id));
60591
60940
  }
60592
60941
  function logCascade(what, e) {
60593
60942
  console.error(`[app-lifecycle] cascade step failed (${what}):`, e instanceof Error ? e.message : e);
@@ -60703,9 +61052,9 @@ function isGatedAwareRoute(url, method) {
60703
61052
 
60704
61053
  // autostart.mjs
60705
61054
  var import_node_child_process5 = require("node:child_process");
60706
- var import_node_fs26 = require("node:fs");
61055
+ var import_node_fs28 = require("node:fs");
60707
61056
  var import_node_os17 = require("node:os");
60708
- var import_node_path23 = require("node:path");
61057
+ var import_node_path25 = require("node:path");
60709
61058
 
60710
61059
  // teardown.mjs
60711
61060
  var RUN_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
@@ -60817,8 +61166,8 @@ function removeLegacyRunKey() {
60817
61166
  }
60818
61167
  function logLine(msg) {
60819
61168
  try {
60820
- (0, import_node_fs26.mkdirSync)(logDir(), { recursive: true });
60821
- (0, import_node_fs26.appendFileSync)(logFilePath(), `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
61169
+ (0, import_node_fs28.mkdirSync)(logDir(), { recursive: true });
61170
+ (0, import_node_fs28.appendFileSync)(logFilePath(), `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
60822
61171
  `);
60823
61172
  } catch {
60824
61173
  }
@@ -60826,8 +61175,8 @@ function logLine(msg) {
60826
61175
  function registerAutostart(exePath) {
60827
61176
  if (!isWin) return;
60828
61177
  const xml = buildAutostartTaskXml(exePath, currentUserId());
60829
- const tmp = (0, import_node_path23.join)((0, import_node_os17.tmpdir)(), `floless-autostart-${process.pid}-${Date.now()}.xml`);
60830
- (0, import_node_fs26.writeFileSync)(tmp, "\uFEFF" + xml, { encoding: "utf16le" });
61178
+ const tmp = (0, import_node_path25.join)((0, import_node_os17.tmpdir)(), `floless-autostart-${process.pid}-${Date.now()}.xml`);
61179
+ (0, import_node_fs28.writeFileSync)(tmp, "\uFEFF" + xml, { encoding: "utf16le" });
60831
61180
  try {
60832
61181
  (0, import_node_child_process5.execFileSync)("schtasks", ["/Create", "/TN", TASK_NAME, "/XML", tmp, "/F"], {
60833
61182
  stdio: ["ignore", "ignore", "ignore"],
@@ -60838,7 +61187,7 @@ function registerAutostart(exePath) {
60838
61187
  throw err2;
60839
61188
  } finally {
60840
61189
  try {
60841
- (0, import_node_fs26.rmSync)(tmp, { force: true });
61190
+ (0, import_node_fs28.rmSync)(tmp, { force: true });
60842
61191
  } catch {
60843
61192
  }
60844
61193
  }
@@ -60872,27 +61221,27 @@ function unregisterAutostart() {
60872
61221
 
60873
61222
  // updater.ts
60874
61223
  var import_node_child_process6 = require("node:child_process");
60875
- var import_node_crypto7 = require("node:crypto");
60876
- var import_node_fs28 = require("node:fs");
61224
+ var import_node_crypto8 = require("node:crypto");
61225
+ var import_node_fs30 = require("node:fs");
60877
61226
  var import_node_stream3 = require("node:stream");
60878
61227
  var import_promises = require("node:stream/promises");
60879
- var import_node_path25 = require("node:path");
61228
+ var import_node_path27 = require("node:path");
60880
61229
 
60881
61230
  // post-update-marker.mjs
60882
- var import_node_fs27 = require("node:fs");
61231
+ var import_node_fs29 = require("node:fs");
60883
61232
  var import_node_os18 = require("node:os");
60884
- var import_node_path24 = require("node:path");
61233
+ var import_node_path26 = require("node:path");
60885
61234
  var FRESH_MS = 12e4;
60886
61235
  function markerPath() {
60887
61236
  const override = (process.env.FLOLESS_POST_UPDATE_MARKER ?? "").trim();
60888
61237
  if (override) return override;
60889
- const root = process.env.FLOLESS_HOME ?? (0, import_node_path24.join)((0, import_node_os18.homedir)(), ".floless");
60890
- return (0, import_node_path24.join)(root, ".post-update");
61238
+ const root = process.env.FLOLESS_HOME ?? (0, import_node_path26.join)((0, import_node_os18.homedir)(), ".floless");
61239
+ return (0, import_node_path26.join)(root, ".post-update");
60891
61240
  }
60892
61241
  function legacyMarkerPath() {
60893
61242
  if ((process.env.FLOLESS_POST_UPDATE_MARKER ?? "").trim()) return null;
60894
61243
  try {
60895
- return (0, import_node_path24.join)((0, import_node_path24.dirname)((0, import_node_path24.dirname)(process.execPath)), ".floless-post-update");
61244
+ return (0, import_node_path26.join)((0, import_node_path26.dirname)((0, import_node_path26.dirname)(process.execPath)), ".floless-post-update");
60896
61245
  } catch {
60897
61246
  return null;
60898
61247
  }
@@ -60902,7 +61251,7 @@ function writePostUpdateMarker() {
60902
61251
  for (const p of [markerPath(), legacyMarkerPath()]) {
60903
61252
  if (!p) continue;
60904
61253
  try {
60905
- (0, import_node_fs27.writeFileSync)(p, (/* @__PURE__ */ new Date()).toISOString());
61254
+ (0, import_node_fs29.writeFileSync)(p, (/* @__PURE__ */ new Date()).toISOString());
60906
61255
  wrote = true;
60907
61256
  } catch {
60908
61257
  }
@@ -60914,9 +61263,9 @@ function consumePostUpdateMarker() {
60914
61263
  for (const p of [markerPath(), legacyMarkerPath()]) {
60915
61264
  if (!p) continue;
60916
61265
  try {
60917
- if (!(0, import_node_fs27.existsSync)(p)) continue;
60918
- const ageMs = Date.now() - (0, import_node_fs27.statSync)(p).mtimeMs;
60919
- (0, import_node_fs27.rmSync)(p, { force: true });
61266
+ if (!(0, import_node_fs29.existsSync)(p)) continue;
61267
+ const ageMs = Date.now() - (0, import_node_fs29.statSync)(p).mtimeMs;
61268
+ (0, import_node_fs29.rmSync)(p, { force: true });
60920
61269
  if (ageMs < FRESH_MS) fresh = true;
60921
61270
  } catch {
60922
61271
  }
@@ -60933,13 +61282,13 @@ function currentVersion() {
60933
61282
  return appVersion();
60934
61283
  }
60935
61284
  function installRoot() {
60936
- return (0, import_node_path25.dirname)((0, import_node_path25.dirname)(process.execPath));
61285
+ return (0, import_node_path27.dirname)((0, import_node_path27.dirname)(process.execPath));
60937
61286
  }
60938
61287
  function updateExePath() {
60939
- return (0, import_node_path25.join)(installRoot(), "Update.exe");
61288
+ return (0, import_node_path27.join)(installRoot(), "Update.exe");
60940
61289
  }
60941
61290
  function packagesDir() {
60942
- return (0, import_node_path25.join)(installRoot(), "packages");
61291
+ return (0, import_node_path27.join)(installRoot(), "packages");
60943
61292
  }
60944
61293
  function feedUrl() {
60945
61294
  const env2 = (process.env.FLOLESS_UPDATE_URL ?? "").trim().replace(/\/+$/, "");
@@ -61031,23 +61380,23 @@ async function checkForUpdate() {
61031
61380
  return { supported: true, currentVersion: cur, updateAvailable: true, targetVersion: latest.Version, asset: latest };
61032
61381
  }
61033
61382
  async function sha1OfFile(path) {
61034
- const hash = (0, import_node_crypto7.createHash)("sha1");
61035
- await (0, import_promises.pipeline)((0, import_node_fs28.createReadStream)(path), hash);
61383
+ const hash = (0, import_node_crypto8.createHash)("sha1");
61384
+ await (0, import_promises.pipeline)((0, import_node_fs30.createReadStream)(path), hash);
61036
61385
  return hash.digest("hex").toUpperCase();
61037
61386
  }
61038
61387
  async function downloadPackage(asset) {
61039
61388
  if (!NUPKG_NAME.test(asset.FileName)) throw new Error(`refusing suspicious package name: ${asset.FileName}`);
61040
61389
  const want = asset.SHA1.toUpperCase();
61041
61390
  const dir = packagesDir();
61042
- (0, import_node_fs28.mkdirSync)(dir, { recursive: true });
61043
- const dest = (0, import_node_path25.join)(dir, asset.FileName);
61044
- if ((0, import_node_fs28.existsSync)(dest) && await sha1OfFile(dest) === want) return dest;
61391
+ (0, import_node_fs30.mkdirSync)(dir, { recursive: true });
61392
+ const dest = (0, import_node_path27.join)(dir, asset.FileName);
61393
+ if ((0, import_node_fs30.existsSync)(dest) && await sha1OfFile(dest) === want) return dest;
61045
61394
  const res = await authedFetch(`${feedUrl()}/${encodeURIComponent(asset.FileName)}`, {
61046
61395
  redirect: "follow",
61047
61396
  signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS)
61048
61397
  });
61049
61398
  if (!res.ok || !res.body) throw new Error(`download failed: HTTP ${res.status} for ${asset.FileName}`);
61050
- await (0, import_promises.pipeline)(import_node_stream3.Readable.fromWeb(res.body), (0, import_node_fs28.createWriteStream)(dest));
61399
+ await (0, import_promises.pipeline)(import_node_stream3.Readable.fromWeb(res.body), (0, import_node_fs30.createWriteStream)(dest));
61051
61400
  const got = await sha1OfFile(dest);
61052
61401
  if (got !== want) throw new Error(`SHA1 mismatch for ${asset.FileName}: feed=${want} got=${got}`);
61053
61402
  return dest;
@@ -61056,7 +61405,7 @@ async function applyUpdate(check, opts) {
61056
61405
  if (!check.supported) return { applied: false, message: check.reason ?? "auto-update not supported in this runtime" };
61057
61406
  if (!check.updateAvailable || !check.asset) return { applied: false, message: check.reason ?? "no update available" };
61058
61407
  const exe = updateExePath();
61059
- if (!(0, import_node_fs28.existsSync)(exe)) {
61408
+ if (!(0, import_node_fs30.existsSync)(exe)) {
61060
61409
  return { applied: false, message: `Update.exe not found at ${exe} \u2014 is this a Velopack install?` };
61061
61410
  }
61062
61411
  const pkg = await downloadPackage(check.asset);
@@ -61296,12 +61645,12 @@ function isTraceCorrupt(events) {
61296
61645
 
61297
61646
  // launch.mjs
61298
61647
  var import_node_child_process7 = require("node:child_process");
61299
- var import_node_path26 = require("node:path");
61648
+ var import_node_path28 = require("node:path");
61300
61649
  var import_node_url3 = require("node:url");
61301
- var import_node_fs29 = require("node:fs");
61650
+ var import_node_fs31 = require("node:fs");
61302
61651
  var import_node_http = __toESM(require("node:http"), 1);
61303
61652
  var import_node_readline = require("node:readline");
61304
- var __dirname2 = (0, import_node_path26.dirname)((0, import_node_url3.fileURLToPath)(__import_meta_url));
61653
+ var __dirname2 = (0, import_node_path28.dirname)((0, import_node_url3.fileURLToPath)(__import_meta_url));
61305
61654
  var PORT = Number(process.env.PORT ?? 4317);
61306
61655
  var HEALTH_URL = `http://127.0.0.1:${PORT}/api/health`;
61307
61656
  var BROWSER_URL = `http://floless.localhost:${PORT}`;
@@ -61373,8 +61722,8 @@ async function waitHealthy(timeoutMs = 3e4) {
61373
61722
  function resolveServerStart() {
61374
61723
  const packaged = /flolessapp\.exe$/i.test(process.execPath);
61375
61724
  if (packaged) return { cmd: process.execPath, args: ["--serve"], shell: false };
61376
- const bundle = (0, import_node_path26.join)(__dirname2, "dist", "floless-server.cjs");
61377
- if ((0, import_node_fs29.existsSync)(bundle)) return { cmd: process.execPath, args: [bundle, "--serve"], shell: false };
61725
+ const bundle = (0, import_node_path28.join)(__dirname2, "dist", "floless-server.cjs");
61726
+ if ((0, import_node_fs31.existsSync)(bundle)) return { cmd: process.execPath, args: [bundle, "--serve"], shell: false };
61378
61727
  return { cmd: "npm", args: ["run", "start"], shell: isWin2 };
61379
61728
  }
61380
61729
  function startServerDetached() {
@@ -61527,8 +61876,8 @@ function taskkillArgs(pid, { tree = true } = {}) {
61527
61876
  }
61528
61877
  function killSupervisor({ tree = true } = {}) {
61529
61878
  if (!isWin2) return;
61530
- const isNpmChannel = /^node(\.exe)?$/i.test((0, import_node_path26.basename)(process.execPath));
61531
- const scriptMatch = isNpmChannel ? (0, import_node_path26.basename)((0, import_node_url3.fileURLToPath)(__import_meta_url)) : void 0;
61879
+ const isNpmChannel = /^node(\.exe)?$/i.test((0, import_node_path28.basename)(process.execPath));
61880
+ const scriptMatch = isNpmChannel ? (0, import_node_path28.basename)((0, import_node_url3.fileURLToPath)(__import_meta_url)) : void 0;
61532
61881
  const realExe = resolveRealInstallExe(process.execPath);
61533
61882
  const exeMatch = realExe === process.execPath ? process.execPath : [process.execPath, realExe];
61534
61883
  const pids = supervisorPidsToKill(enumerateProcesses(), process.pid, exeMatch, scriptMatch);
@@ -61693,7 +62042,7 @@ async function runAction(arg, flagArgv = [], selfVersion = null) {
61693
62042
  }
61694
62043
  await action(parseTeardownFlags(flagArgv));
61695
62044
  }
61696
- var entry = (0, import_node_path26.basename)(process.argv[1] ?? "").toLowerCase();
62045
+ var entry = (0, import_node_path28.basename)(process.argv[1] ?? "").toLowerCase();
61697
62046
  if (entry === "launch.mjs") {
61698
62047
  runAction(process.argv[2], process.argv.slice(3)).catch((e) => {
61699
62048
  log(`error: ${e?.message ?? e}`);
@@ -61767,9 +62116,9 @@ function awareUpgradeBlockReason(s) {
61767
62116
  }
61768
62117
 
61769
62118
  // skill-sync.ts
61770
- var import_node_fs30 = require("node:fs");
62119
+ var import_node_fs32 = require("node:fs");
61771
62120
  var import_node_os19 = require("node:os");
61772
- var import_node_path27 = require("node:path");
62121
+ var import_node_path29 = require("node:path");
61773
62122
  var import_node_url4 = require("node:url");
61774
62123
  var import_yaml8 = __toESM(require_dist6(), 1);
61775
62124
 
@@ -61812,14 +62161,14 @@ function selectShippedSkillNames(names) {
61812
62161
  }
61813
62162
 
61814
62163
  // skill-sync.ts
61815
- var __dirname3 = (0, import_node_path27.dirname)((0, import_node_url4.fileURLToPath)(__import_meta_url));
62164
+ var __dirname3 = (0, import_node_path29.dirname)((0, import_node_url4.fileURLToPath)(__import_meta_url));
61816
62165
  function bundledSkillsRoot() {
61817
62166
  const candidates = [
61818
- (0, import_node_path27.join)(__dirname3, "skills"),
61819
- (0, import_node_path27.join)((0, import_node_path27.dirname)(process.execPath), "skills"),
61820
- (0, import_node_path27.join)(__dirname3, "..", ".claude", "skills")
62167
+ (0, import_node_path29.join)(__dirname3, "skills"),
62168
+ (0, import_node_path29.join)((0, import_node_path29.dirname)(process.execPath), "skills"),
62169
+ (0, import_node_path29.join)(__dirname3, "..", ".claude", "skills")
61821
62170
  ];
61822
- return candidates.find((p) => (0, import_node_fs30.existsSync)(p)) ?? null;
62171
+ return candidates.find((p) => (0, import_node_fs32.existsSync)(p)) ?? null;
61823
62172
  }
61824
62173
  function targetConfigDirs() {
61825
62174
  const override = process.env.FLOLESS_SKILL_TARGETS;
@@ -61828,14 +62177,14 @@ function targetConfigDirs() {
61828
62177
  }
61829
62178
  const home = (0, import_node_os19.homedir)();
61830
62179
  return [
61831
- { runtime: "claude", dir: (0, import_node_path27.join)(home, ".claude") },
61832
- { runtime: "codex", dir: (0, import_node_path27.join)(home, ".codex") },
61833
- { runtime: "opencode", dir: (0, import_node_path27.join)(home, ".opencode") }
62180
+ { runtime: "claude", dir: (0, import_node_path29.join)(home, ".claude") },
62181
+ { runtime: "codex", dir: (0, import_node_path29.join)(home, ".codex") },
62182
+ { runtime: "opencode", dir: (0, import_node_path29.join)(home, ".opencode") }
61834
62183
  ];
61835
62184
  }
61836
62185
  function skillVersion(skillMdPath) {
61837
62186
  try {
61838
- const text = (0, import_node_fs30.readFileSync)(skillMdPath, "utf8");
62187
+ const text = (0, import_node_fs32.readFileSync)(skillMdPath, "utf8");
61839
62188
  const m = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text);
61840
62189
  if (!m || m[1] === void 0) return null;
61841
62190
  const fm = (0, import_yaml8.parse)(m[1]);
@@ -61875,21 +62224,21 @@ function decideAction(installed, bundled) {
61875
62224
  function bundledSkills(root) {
61876
62225
  let entries = [];
61877
62226
  try {
61878
- entries = selectShippedSkillNames((0, import_node_fs30.readdirSync)(root));
62227
+ entries = selectShippedSkillNames((0, import_node_fs32.readdirSync)(root));
61879
62228
  } catch {
61880
62229
  return [];
61881
62230
  }
61882
62231
  const out = [];
61883
62232
  for (const name of entries) {
61884
- const dir = (0, import_node_path27.join)(root, name);
62233
+ const dir = (0, import_node_path29.join)(root, name);
61885
62234
  let isDir = false;
61886
62235
  try {
61887
- isDir = (0, import_node_fs30.statSync)(dir).isDirectory();
62236
+ isDir = (0, import_node_fs32.statSync)(dir).isDirectory();
61888
62237
  } catch {
61889
62238
  isDir = false;
61890
62239
  }
61891
62240
  if (!isDir) continue;
61892
- const v = skillVersion((0, import_node_path27.join)(dir, "SKILL.md"));
62241
+ const v = skillVersion((0, import_node_path29.join)(dir, "SKILL.md"));
61893
62242
  if (!v) continue;
61894
62243
  out.push({ name, dir, version: v });
61895
62244
  }
@@ -61902,17 +62251,17 @@ function syncSkills() {
61902
62251
  const skills = bundledSkills(root);
61903
62252
  if (!skills.length) return results;
61904
62253
  for (const { runtime, dir: cfg } of targetConfigDirs()) {
61905
- if (!(0, import_node_fs30.existsSync)(cfg)) continue;
61906
- const skillsDir = (0, import_node_path27.join)(cfg, "skills");
62254
+ if (!(0, import_node_fs32.existsSync)(cfg)) continue;
62255
+ const skillsDir = (0, import_node_path29.join)(cfg, "skills");
61907
62256
  for (const s of skills) {
61908
- const dest = (0, import_node_path27.join)(skillsDir, s.name);
61909
- const installedMd = (0, import_node_path27.join)(dest, "SKILL.md");
61910
- const installed = (0, import_node_fs30.existsSync)(installedMd) ? skillVersion(installedMd) : null;
62257
+ const dest = (0, import_node_path29.join)(skillsDir, s.name);
62258
+ const installedMd = (0, import_node_path29.join)(dest, "SKILL.md");
62259
+ const installed = (0, import_node_fs32.existsSync)(installedMd) ? skillVersion(installedMd) : null;
61911
62260
  const action = decideAction(installed, s.version);
61912
62261
  if (action === "installed" || action === "updated") {
61913
62262
  try {
61914
- if (action === "updated") (0, import_node_fs30.rmSync)(dest, { recursive: true, force: true });
61915
- (0, import_node_fs30.cpSync)(s.dir, dest, { recursive: true });
62263
+ if (action === "updated") (0, import_node_fs32.rmSync)(dest, { recursive: true, force: true });
62264
+ (0, import_node_fs32.cpSync)(s.dir, dest, { recursive: true });
61916
62265
  results.push({ runtime, skill: s.name, action, from: installed, to: s.version });
61917
62266
  } catch {
61918
62267
  }
@@ -61926,8 +62275,8 @@ function syncSkills() {
61926
62275
 
61927
62276
  // watch.ts
61928
62277
  var import_node_os20 = require("node:os");
61929
- var import_node_path29 = require("node:path");
61930
- var import_node_fs31 = require("node:fs");
62278
+ var import_node_path31 = require("node:path");
62279
+ var import_node_fs33 = require("node:fs");
61931
62280
 
61932
62281
  // node_modules/chokidar/esm/index.js
61933
62282
  var import_fs2 = require("fs");
@@ -61938,7 +62287,7 @@ var sysPath2 = __toESM(require("path"), 1);
61938
62287
  // node_modules/readdirp/esm/index.js
61939
62288
  var import_promises2 = require("node:fs/promises");
61940
62289
  var import_node_stream4 = require("node:stream");
61941
- var import_node_path28 = require("node:path");
62290
+ var import_node_path30 = require("node:path");
61942
62291
  var EntryTypes = {
61943
62292
  FILE_TYPE: "files",
61944
62293
  DIR_TYPE: "directories",
@@ -62013,7 +62362,7 @@ var ReaddirpStream = class extends import_node_stream4.Readable {
62013
62362
  this._wantsDir = type ? DIR_TYPES.has(type) : false;
62014
62363
  this._wantsFile = type ? FILE_TYPES.has(type) : false;
62015
62364
  this._wantsEverything = type === EntryTypes.EVERYTHING_TYPE;
62016
- this._root = (0, import_node_path28.resolve)(root);
62365
+ this._root = (0, import_node_path30.resolve)(root);
62017
62366
  this._isDirent = !opts.alwaysStat;
62018
62367
  this._statsProp = this._isDirent ? "dirent" : "stats";
62019
62368
  this._rdOptions = { encoding: "utf8", withFileTypes: this._isDirent };
@@ -62084,8 +62433,8 @@ var ReaddirpStream = class extends import_node_stream4.Readable {
62084
62433
  let entry2;
62085
62434
  const basename5 = this._isDirent ? dirent.name : dirent;
62086
62435
  try {
62087
- const fullPath = (0, import_node_path28.resolve)((0, import_node_path28.join)(path, basename5));
62088
- entry2 = { path: (0, import_node_path28.relative)(this._root, fullPath), fullPath, basename: basename5 };
62436
+ const fullPath = (0, import_node_path30.resolve)((0, import_node_path30.join)(path, basename5));
62437
+ entry2 = { path: (0, import_node_path30.relative)(this._root, fullPath), fullPath, basename: basename5 };
62089
62438
  entry2[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
62090
62439
  } catch (err2) {
62091
62440
  this._onError(err2);
@@ -62119,7 +62468,7 @@ var ReaddirpStream = class extends import_node_stream4.Readable {
62119
62468
  }
62120
62469
  if (entryRealPathStats.isDirectory()) {
62121
62470
  const len2 = entryRealPath.length;
62122
- if (full.startsWith(entryRealPath) && full.substr(len2, 1) === import_node_path28.sep) {
62471
+ if (full.startsWith(entryRealPath) && full.substr(len2, 1) === import_node_path30.sep) {
62123
62472
  const recursiveError = new Error(`Circular symlink detected: "${full}" points to "${entryRealPath}"`);
62124
62473
  recursiveError.code = RECURSIVE_ERROR_CODE;
62125
62474
  return this._onError(recursiveError);
@@ -63627,33 +63976,33 @@ function appIdFromLogPath(path) {
63627
63976
  return i >= 0 && parts[i + 1] ? parts[i + 1] : null;
63628
63977
  }
63629
63978
  function samePath(a, b) {
63630
- const ra = (0, import_node_path29.resolve)(a);
63631
- const rb = (0, import_node_path29.resolve)(b);
63979
+ const ra = (0, import_node_path31.resolve)(a);
63980
+ const rb = (0, import_node_path31.resolve)(b);
63632
63981
  return process.platform === "win32" ? ra.toLowerCase() === rb.toLowerCase() : ra === rb;
63633
63982
  }
63634
63983
  function underDir(path, dir) {
63635
- const rp = (0, import_node_path29.resolve)(path);
63636
- const rd = (0, import_node_path29.resolve)(dir);
63984
+ const rp = (0, import_node_path31.resolve)(path);
63985
+ const rd = (0, import_node_path31.resolve)(dir);
63637
63986
  const [p, d] = process.platform === "win32" ? [rp.toLowerCase(), rd.toLowerCase()] : [rp, rd];
63638
- return p === d || p.startsWith(d + import_node_path29.sep);
63987
+ return p === d || p.startsWith(d + import_node_path31.sep);
63639
63988
  }
63640
63989
  function startWatcher() {
63641
- const awareDir = process.env.AWARE_HOME ?? (0, import_node_path29.join)((0, import_node_os20.homedir)(), ".aware");
63642
- const credentialsDir = (0, import_node_path29.join)(awareDir, "credentials");
63643
- if (!(0, import_node_fs31.existsSync)(credentialsDir)) {
63990
+ const awareDir = process.env.AWARE_HOME ?? (0, import_node_path31.join)((0, import_node_os20.homedir)(), ".aware");
63991
+ const credentialsDir = (0, import_node_path31.join)(awareDir, "credentials");
63992
+ if (!(0, import_node_fs33.existsSync)(credentialsDir)) {
63644
63993
  try {
63645
- (0, import_node_fs31.mkdirSync)(credentialsDir, { recursive: true });
63994
+ (0, import_node_fs33.mkdirSync)(credentialsDir, { recursive: true });
63646
63995
  } catch {
63647
63996
  }
63648
63997
  }
63649
- if (!(0, import_node_fs31.existsSync)(uiDir)) {
63998
+ if (!(0, import_node_fs33.existsSync)(uiDir)) {
63650
63999
  try {
63651
- (0, import_node_fs31.mkdirSync)(uiDir, { recursive: true });
64000
+ (0, import_node_fs33.mkdirSync)(uiDir, { recursive: true });
63652
64001
  } catch {
63653
64002
  }
63654
64003
  }
63655
- const targets = ["apps", "logs", "credentials"].map((d) => (0, import_node_path29.join)(awareDir, d)).filter((p) => (0, import_node_fs31.existsSync)(p));
63656
- if ((0, import_node_fs31.existsSync)(uiDir)) targets.push(uiDir);
64004
+ const targets = ["apps", "logs", "credentials"].map((d) => (0, import_node_path31.join)(awareDir, d)).filter((p) => (0, import_node_fs33.existsSync)(p));
64005
+ if ((0, import_node_fs33.existsSync)(uiDir)) targets.push(uiDir);
63657
64006
  if (targets.length === 0) {
63658
64007
  return null;
63659
64008
  }
@@ -63682,11 +64031,11 @@ function startWatcher() {
63682
64031
  const isCredential = path.split(/[\\/]/).includes("credentials");
63683
64032
  const kind = isCredential ? "credential" : path.endsWith(".jsonl") ? "trace" : path.endsWith(".lock") ? "lock" : path.endsWith(".flo") || path.endsWith(".app") ? "source" : "file";
63684
64033
  broadcast({ type: "fs-change", kind, event, path });
63685
- if (kind === "trace" && event !== "unlink" && (0, import_node_fs31.existsSync)(path)) {
64034
+ if (kind === "trace" && event !== "unlink" && (0, import_node_fs33.existsSync)(path)) {
63686
64035
  const id = appIdFromLogPath(path);
63687
64036
  if (!id) return;
63688
64037
  try {
63689
- broadcast({ type: "trace-file", id, runId: path.split(import_node_path29.sep).pop()?.replace(/\.jsonl$/, "") ?? null, events: parseTrace((0, import_node_fs31.readFileSync)(path, "utf8")) });
64038
+ broadcast({ type: "trace-file", id, runId: path.split(import_node_path31.sep).pop()?.replace(/\.jsonl$/, "") ?? null, events: parseTrace((0, import_node_fs33.readFileSync)(path, "utf8")) });
63690
64039
  } catch {
63691
64040
  }
63692
64041
  }
@@ -63695,10 +64044,10 @@ function startWatcher() {
63695
64044
  }
63696
64045
 
63697
64046
  // index.ts
63698
- var __dirname4 = (0, import_node_path30.dirname)((0, import_node_url5.fileURLToPath)(__import_meta_url));
63699
- var WEB_ROOT = [(0, import_node_path30.join)(__dirname4, "web"), (0, import_node_path30.join)((0, import_node_path30.dirname)(process.execPath), "web"), (0, import_node_path30.join)(__dirname4, "..", "web")].find(
63700
- (p) => (0, import_node_fs32.existsSync)(p)
63701
- ) ?? (0, import_node_path30.join)(__dirname4, "..", "web");
64047
+ var __dirname4 = (0, import_node_path32.dirname)((0, import_node_url5.fileURLToPath)(__import_meta_url));
64048
+ var WEB_ROOT = [(0, import_node_path32.join)(__dirname4, "web"), (0, import_node_path32.join)((0, import_node_path32.dirname)(process.execPath), "web"), (0, import_node_path32.join)(__dirname4, "..", "web")].find(
64049
+ (p) => (0, import_node_fs34.existsSync)(p)
64050
+ ) ?? (0, import_node_path32.join)(__dirname4, "..", "web");
63702
64051
  var PORT2 = Number(process.env.PORT ?? 4317);
63703
64052
  var HOST = "127.0.0.1";
63704
64053
  var crashHandlersInstalled = false;
@@ -63714,7 +64063,7 @@ function installCrashHandlers() {
63714
64063
  ${stack}
63715
64064
  `;
63716
64065
  try {
63717
- (0, import_node_fs32.appendFileSync)(logFilePath(), line);
64066
+ (0, import_node_fs34.appendFileSync)(logFilePath(), line);
63718
64067
  } catch {
63719
64068
  }
63720
64069
  if (process.stderr.isTTY) process.stderr.write(line);
@@ -64011,9 +64360,9 @@ async function startServer() {
64011
64360
  const { id, templateId, templatePath, srcPath, appDirPath: appDirPath2, fromVersion, contract, verb } = opts;
64012
64361
  const bdir = backupDir(id, fromVersion, Date.now());
64013
64362
  try {
64014
- (0, import_node_fs32.mkdirSync)((0, import_node_path30.dirname)(bdir), { recursive: true });
64015
- (0, import_node_fs32.cpSync)(appDirPath2, bdir, { recursive: true });
64016
- (0, import_node_fs32.cpSync)(templatePath, srcPath);
64363
+ (0, import_node_fs34.mkdirSync)((0, import_node_path32.dirname)(bdir), { recursive: true });
64364
+ (0, import_node_fs34.cpSync)(appDirPath2, bdir, { recursive: true });
64365
+ (0, import_node_fs34.cpSync)(templatePath, srcPath);
64017
64366
  if (id !== templateId) stampSourceAppId(srcPath, id);
64018
64367
  if (contract != null) {
64019
64368
  bakeContractIntoApp(srcPath, contract);
@@ -64024,8 +64373,8 @@ async function startServer() {
64024
64373
  } catch (e) {
64025
64374
  const msg = e instanceof Error ? e.message : String(e);
64026
64375
  try {
64027
- (0, import_node_fs32.rmSync)(appDirPath2, { recursive: true, force: true });
64028
- (0, import_node_fs32.cpSync)(bdir, appDirPath2, { recursive: true });
64376
+ (0, import_node_fs34.rmSync)(appDirPath2, { recursive: true, force: true });
64377
+ (0, import_node_fs34.cpSync)(bdir, appDirPath2, { recursive: true });
64029
64378
  } catch (re) {
64030
64379
  const rmsg = re instanceof Error ? re.message : String(re);
64031
64380
  throw new TemplateSwapError(`${verb} failed (${msg}) AND rollback failed (${rmsg}) \u2014 restore manually from ${bdir}`, "rollback-failed", bdir);
@@ -64056,7 +64405,7 @@ async function startServer() {
64056
64405
  return reply.status(409).send({ ok: false, error: `"${id}" is already at ${fromVersion}`, code: "up-to-date" });
64057
64406
  }
64058
64407
  const contract = readContract(id);
64059
- if (contract == null && (0, import_node_fs32.existsSync)(contractPath(id))) {
64408
+ if (contract == null && (0, import_node_fs34.existsSync)(contractPath(id))) {
64060
64409
  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" });
64061
64410
  }
64062
64411
  if (contract != null) {
@@ -64238,6 +64587,29 @@ async function startServer() {
64238
64587
  throw e;
64239
64588
  }
64240
64589
  });
64590
+ app.get("/api/projects/:id/versions", async (req, reply) => {
64591
+ const p = getProject(req.params.id);
64592
+ if (!p) return reply.status(404).send({ ok: false, error: `unknown project: ${req.params.id}` });
64593
+ return { ok: true, versions: listVersions(req.params.id), approvedAt: p.approvedAt ?? null };
64594
+ });
64595
+ app.post("/api/projects/:id/versions/:n/rollback", async (req, reply) => {
64596
+ const project = getProject(req.params.id);
64597
+ if (!project) return reply.status(404).send({ ok: false, error: `unknown project: ${req.params.id}` });
64598
+ const n = Number(req.params.n);
64599
+ if (!Number.isInteger(n) || n < 1) return reply.status(400).send({ ok: false, error: `invalid version: ${req.params.n}` });
64600
+ const doc = readVersion(project.id, n);
64601
+ if (doc == null) return reply.status(404).send({ ok: false, error: `could not read version v${n} (missing or corrupt)` });
64602
+ try {
64603
+ writeContract(project.app, doc, project.id);
64604
+ } catch (e) {
64605
+ if (e instanceof ContractError) return reply.status(400).send({ ok: false, error: e.message });
64606
+ throw e;
64607
+ }
64608
+ clearApproval(project.id);
64609
+ const row = createVersion(project.id, doc, { author: "You", message: `Rolled back to v${n}`, gate: null, kind: "rollback" });
64610
+ broadcast({ type: "contract-changed", appId: project.app, project: project.id });
64611
+ return { ok: true, version: row };
64612
+ });
64241
64613
  function projectAppMismatch(appId, project) {
64242
64614
  const p = getProject(project);
64243
64615
  if (!p) return `unknown project: ${project}`;
@@ -64320,7 +64692,10 @@ async function startServer() {
64320
64692
  }
64321
64693
  broadcast({ type: "compiled", id: req.params.appId, lockPath: result.lockPath });
64322
64694
  let approvedAt;
64323
- if (project) approvedAt = markApproved(project).approvedAt;
64695
+ if (project) {
64696
+ createVersion(project, doc, { author: "You", message: "Approved the model", gate: "model", kind: "approve" });
64697
+ approvedAt = markApproved(project).approvedAt;
64698
+ }
64324
64699
  return { ok: true, result, approvedAt };
64325
64700
  });
64326
64701
  app.post(
@@ -64392,10 +64767,10 @@ async function startServer() {
64392
64767
  if (bad) return reply.status(404).send({ ok: false, error: bad });
64393
64768
  const dir = projectExportsDir(project);
64394
64769
  const exports2 = projectExportFiles(req.params.appId).map(({ kind, filename }) => {
64395
- const path = (0, import_node_path30.join)(dir, filename);
64770
+ const path = (0, import_node_path32.join)(dir, filename);
64396
64771
  let exportedAt = null;
64397
64772
  try {
64398
- const st = (0, import_node_fs32.statSync)(path);
64773
+ const st = (0, import_node_fs34.statSync)(path);
64399
64774
  if (st.isFile()) exportedAt = st.mtime.toISOString();
64400
64775
  } catch {
64401
64776
  }
@@ -64479,8 +64854,8 @@ async function startServer() {
64479
64854
  }
64480
64855
  const companionId = `${req.params.appId}-ifc`;
64481
64856
  const filename = `${req.params.appId}.ifc`;
64482
- const outPath = project ? (0, import_node_path30.join)(projectExportsDir(project), filename) : (0, import_node_path30.join)(appPath(companionId), filename);
64483
- if (project) (0, import_node_fs32.mkdirSync)((0, import_node_path30.dirname)(outPath), { recursive: true });
64857
+ const outPath = project ? (0, import_node_path32.join)(projectExportsDir(project), filename) : (0, import_node_path32.join)(appPath(companionId), filename);
64858
+ if (project) (0, import_node_fs34.mkdirSync)((0, import_node_path32.dirname)(outPath), { recursive: true });
64484
64859
  let flo;
64485
64860
  try {
64486
64861
  flo = writeIfcApp(appPath(companionId), companionId, scene, outPath, writeOpts);
@@ -64503,8 +64878,8 @@ async function startServer() {
64503
64878
  }
64504
64879
  throw e;
64505
64880
  }
64506
- if (!(0, import_node_fs32.existsSync)(outPath)) return reply.send({ ok: false, error: "the IFC export produced no file" });
64507
- const content = (0, import_node_fs32.readFileSync)(outPath, "utf8");
64881
+ if (!(0, import_node_fs34.existsSync)(outPath)) return reply.send({ ok: false, error: "the IFC export produced no file" });
64882
+ const content = (0, import_node_fs34.readFileSync)(outPath, "utf8");
64508
64883
  broadcast({ type: "apps-changed" });
64509
64884
  return { ok: true, filename, savedTo: outPath, content, bytes: Buffer.byteLength(content), skipped, ...extrusionsSkipped ? { extrusionsSkipped } : {} };
64510
64885
  });
@@ -64593,17 +64968,17 @@ async function startServer() {
64593
64968
  return reply.status(422).send({ ok: false, error: "no priced members to export \u2014 every member is an RFI (no AISC size / weight yet)" });
64594
64969
  }
64595
64970
  const filename = `${appId}-bom.${format}`;
64596
- const outPath = project ? (0, import_node_path30.join)(projectExportsDir(project), filename) : bomExportPath(appId, format);
64597
- const dir = (0, import_node_path30.dirname)(outPath);
64971
+ const outPath = project ? (0, import_node_path32.join)(projectExportsDir(project), filename) : bomExportPath(appId, format);
64972
+ const dir = (0, import_node_path32.dirname)(outPath);
64598
64973
  try {
64599
- (0, import_node_fs32.mkdirSync)(dir, { recursive: true });
64974
+ (0, import_node_fs34.mkdirSync)(dir, { recursive: true });
64600
64975
  if (format === "csv") {
64601
64976
  const text = bomToCsv(bom);
64602
- (0, import_node_fs32.writeFileSync)(outPath, text, "utf8");
64977
+ (0, import_node_fs34.writeFileSync)(outPath, text, "utf8");
64603
64978
  return { ok: true, filename, encoding: "utf8", content: text, bytes: Buffer.byteLength(text), savedTo: outPath };
64604
64979
  }
64605
64980
  const buf = await bomToXlsx(bom);
64606
- (0, import_node_fs32.writeFileSync)(outPath, buf);
64981
+ (0, import_node_fs34.writeFileSync)(outPath, buf);
64607
64982
  return { ok: true, filename, encoding: "base64", content: buf.toString("base64"), bytes: buf.length, savedTo: outPath };
64608
64983
  } catch (e) {
64609
64984
  app.log.error({ appId, format, err: e instanceof Error ? e.message : e }, "export-bom: generation failed");
@@ -64673,7 +65048,7 @@ async function startServer() {
64673
65048
  return reply.status(404).send({ ok: false, error: `"${id}" is not installed`, code: "not-installed" });
64674
65049
  }
64675
65050
  const contract = readContract(id);
64676
- if (contract == null && (0, import_node_fs32.existsSync)(contractPath(id))) {
65051
+ if (contract == null && (0, import_node_fs34.existsSync)(contractPath(id))) {
64677
65052
  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" });
64678
65053
  }
64679
65054
  if (contract != null) {
@@ -64751,11 +65126,11 @@ async function startServer() {
64751
65126
  if (appExists(id)) {
64752
65127
  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 });
64753
65128
  }
64754
- const stageRoot = (0, import_node_fs32.mkdtempSync)((0, import_node_path30.join)((0, import_node_os21.tmpdir)(), "floless-import-"));
65129
+ const stageRoot = (0, import_node_fs34.mkdtempSync)((0, import_node_path32.join)((0, import_node_os21.tmpdir)(), "floless-import-"));
64755
65130
  try {
64756
- const stageDir = (0, import_node_path30.join)(stageRoot, id);
64757
- (0, import_node_fs32.mkdirSync)(stageDir);
64758
- (0, import_node_fs32.writeFileSync)((0, import_node_path30.join)(stageDir, `${id}.flo`), content);
65131
+ const stageDir = (0, import_node_path32.join)(stageRoot, id);
65132
+ (0, import_node_fs34.mkdirSync)(stageDir);
65133
+ (0, import_node_fs34.writeFileSync)((0, import_node_path32.join)(stageDir, `${id}.flo`), content);
64759
65134
  await aware.install(stageDir);
64760
65135
  } catch (err2) {
64761
65136
  try {
@@ -64765,7 +65140,7 @@ async function startServer() {
64765
65140
  const msg = err2 instanceof AwareError ? err2.message : String(err2?.message ?? err2);
64766
65141
  return reply.status(502).send({ ok: false, error: `import failed: ${msg}` });
64767
65142
  } finally {
64768
- (0, import_node_fs32.rmSync)(stageRoot, { recursive: true, force: true });
65143
+ (0, import_node_fs34.rmSync)(stageRoot, { recursive: true, force: true });
64769
65144
  }
64770
65145
  broadcast({ type: "apps-changed", id });
64771
65146
  return { ok: true, id };
@@ -64779,13 +65154,13 @@ async function startServer() {
64779
65154
  }
64780
65155
  const inputs = appData.inputs.map((i) => ({ name: i.name, type: i.type }));
64781
65156
  const baked = bakeFloSource(appData.source.text, inputs);
64782
- const tmpRoot = (0, import_node_fs32.mkdtempSync)((0, import_node_path30.join)((0, import_node_os21.tmpdir)(), "floless-bake-"));
64783
- const backupDir2 = (0, import_node_path30.join)(tmpRoot, `${id}-backup`);
64784
- const bakeDir = (0, import_node_path30.join)(tmpRoot, id);
64785
- (0, import_node_fs32.cpSync)(appDir(id), backupDir2, { recursive: true });
64786
- (0, import_node_fs32.cpSync)(appDir(id), bakeDir, { recursive: true });
65157
+ const tmpRoot = (0, import_node_fs34.mkdtempSync)((0, import_node_path32.join)((0, import_node_os21.tmpdir)(), "floless-bake-"));
65158
+ const backupDir2 = (0, import_node_path32.join)(tmpRoot, `${id}-backup`);
65159
+ const bakeDir = (0, import_node_path32.join)(tmpRoot, id);
65160
+ (0, import_node_fs34.cpSync)(appDir(id), backupDir2, { recursive: true });
65161
+ (0, import_node_fs34.cpSync)(appDir(id), bakeDir, { recursive: true });
64787
65162
  const floName = appData.source.path.split(/[\\/]/).pop();
64788
- (0, import_node_fs32.writeFileSync)((0, import_node_path30.join)(bakeDir, floName), baked);
65163
+ (0, import_node_fs34.writeFileSync)((0, import_node_path32.join)(bakeDir, floName), baked);
64789
65164
  let appInstalled2 = true;
64790
65165
  try {
64791
65166
  await aware.uninstall(id);
@@ -64807,17 +65182,17 @@ async function startServer() {
64807
65182
  throw installErr;
64808
65183
  }
64809
65184
  try {
64810
- await aware.compile((0, import_node_path30.join)(appDir(id), floName));
65185
+ await aware.compile((0, import_node_path32.join)(appDir(id), floName));
64811
65186
  } catch (compileErr) {
64812
65187
  app.log.warn({ id, compileErr: String(compileErr) }, "bake: post-install recompile failed (app baked but may need a manual Compile)");
64813
65188
  }
64814
65189
  broadcast({ type: "baked", id });
64815
65190
  return { ok: true, id, agent: id, inputs };
64816
65191
  } finally {
64817
- if (appInstalled2) (0, import_node_fs32.rmSync)(tmpRoot, { recursive: true, force: true });
65192
+ if (appInstalled2) (0, import_node_fs34.rmSync)(tmpRoot, { recursive: true, force: true });
64818
65193
  }
64819
65194
  });
64820
- const graftAgentsDir = () => (0, import_node_path30.join)((0, import_node_os21.homedir)(), ".aware", "agents");
65195
+ const graftAgentsDir = () => (0, import_node_path32.join)((0, import_node_os21.homedir)(), ".aware", "agents");
64821
65196
  app.post("/api/graft/match", async (req, reply) => {
64822
65197
  const { glob } = req.body ?? {};
64823
65198
  if (!glob) return reply.status(400).send({ ok: false, error: "glob required" });
@@ -64834,7 +65209,7 @@ async function startServer() {
64834
65209
  if (!sourceKind || !sourceRef) {
64835
65210
  return reply.status(400).send({ ok: false, error: "sourceKind and sourceRef required" });
64836
65211
  }
64837
- const tempHome = (0, import_node_fs32.mkdtempSync)((0, import_node_path30.join)((0, import_node_os21.tmpdir)(), "floless-graft-"));
65212
+ const tempHome = (0, import_node_fs34.mkdtempSync)((0, import_node_path32.join)((0, import_node_os21.tmpdir)(), "floless-graft-"));
64838
65213
  let result;
64839
65214
  try {
64840
65215
  result = await aware.build({
@@ -64847,19 +65222,19 @@ async function startServer() {
64847
65222
  awareHome: tempHome
64848
65223
  });
64849
65224
  } catch (err2) {
64850
- (0, import_node_fs32.rmSync)(tempHome, { recursive: true, force: true });
65225
+ (0, import_node_fs34.rmSync)(tempHome, { recursive: true, force: true });
64851
65226
  const msg = err2 instanceof AwareError ? err2.message : String(err2?.message ?? err2);
64852
65227
  return reply.status(422).send({ ok: false, error: msg });
64853
65228
  }
64854
65229
  const manifest = readStagedManifest(result.agentDir);
64855
65230
  if (!manifest) {
64856
- (0, import_node_fs32.rmSync)(tempHome, { recursive: true, force: true });
65231
+ (0, import_node_fs34.rmSync)(tempHome, { recursive: true, force: true });
64857
65232
  return reply.status(502).send({ ok: false, error: `build produced output at ${result.agentDir} but no manifest.yaml` });
64858
65233
  }
64859
- const token = (0, import_node_crypto8.randomUUID)();
65234
+ const token = (0, import_node_crypto9.randomUUID)();
64860
65235
  registerStage(token, tempHome, result.agentId);
64861
65236
  const preview = buildPreview(manifest, sourceKind, sourceRef, token);
64862
- if ((0, import_node_fs32.existsSync)((0, import_node_path30.join)(graftAgentsDir(), result.agentId))) {
65237
+ if ((0, import_node_fs34.existsSync)((0, import_node_path32.join)(graftAgentsDir(), result.agentId))) {
64863
65238
  preview.warnings.unshift(`An agent named "${result.agentId}" is already installed \u2014 creating it will overwrite it.`);
64864
65239
  }
64865
65240
  return { ok: true, preview };
@@ -64878,7 +65253,7 @@ async function startServer() {
64878
65253
  registerStage(stagedRef, stage.tempDir, stage.agentId);
64879
65254
  return reply.status(409).send({ ok: false, error: err2.message, agentId: stage.agentId, collision: true });
64880
65255
  }
64881
- (0, import_node_fs32.rmSync)(stage.tempDir, { recursive: true, force: true });
65256
+ (0, import_node_fs34.rmSync)(stage.tempDir, { recursive: true, force: true });
64882
65257
  throw err2;
64883
65258
  }
64884
65259
  broadcast({ type: "grafted", id: stage.agentId });
@@ -65149,11 +65524,11 @@ async function startServer() {
65149
65524
  app.get("/api/requests/:id/snapshot/:n", async (req, reply) => {
65150
65525
  const n = Number.parseInt(req.params.n, 10);
65151
65526
  const p = Number.isInteger(n) ? snapshotPathFor(req.params.id, n) : null;
65152
- if (!p || !(0, import_node_fs32.existsSync)(p)) return reply.status(404).send({ ok: false, error: "snapshot not found" });
65527
+ if (!p || !(0, import_node_fs34.existsSync)(p)) return reply.status(404).send({ ok: false, error: "snapshot not found" });
65153
65528
  const ext = p.split(".").pop().toLowerCase();
65154
65529
  reply.header("Content-Type", ext === "png" ? "image/png" : ext === "webp" ? "image/webp" : ext === "pdf" ? "application/pdf" : "image/jpeg");
65155
65530
  reply.header("Cache-Control", "no-store");
65156
- return (0, import_node_fs32.readFileSync)(p);
65531
+ return (0, import_node_fs34.readFileSync)(p);
65157
65532
  });
65158
65533
  app.post(
65159
65534
  "/api/tweak",
@@ -65235,6 +65610,63 @@ async function startServer() {
65235
65610
  }
65236
65611
  }
65237
65612
  );
65613
+ const CONN_APPID = /^[a-z0-9][a-z0-9._-]*$/i;
65614
+ app.post(
65615
+ "/api/import-connection/list",
65616
+ { bodyLimit: 96 * 1024 * 1024 },
65617
+ // a base64 IFC inflates ~4/3; 96 MB covers the 64 MB stash cap
65618
+ async (req, reply) => {
65619
+ const { appId, dataUrl } = req.body ?? {};
65620
+ if (!appId || !CONN_APPID.test(appId)) return reply.status(400).send({ ok: false, error: "a valid appId is required" });
65621
+ if (!dataUrl) return reply.status(400).send({ ok: false, error: "an IFC file is required" });
65622
+ let stored;
65623
+ try {
65624
+ stored = storeIfcInput(appId, dataUrl);
65625
+ } catch (e) {
65626
+ if (e instanceof VisualInputError) return reply.status(400).send({ ok: false, error: e.message });
65627
+ throw e;
65628
+ }
65629
+ let done = false;
65630
+ req.raw.on("close", () => {
65631
+ if (!done && isRunActive()) cancelActiveRun();
65632
+ });
65633
+ try {
65634
+ const connections = await listConnections(`${appId}-connimport`, stored.path);
65635
+ return { ok: true, sha: stored.sha256, connections };
65636
+ } catch (e) {
65637
+ if (e instanceof ConnectionImportError) return reply.send({ ok: false, error: e.message });
65638
+ app.log.error({ appId, err: e instanceof Error ? e.message : e }, "import-connection list failed");
65639
+ return reply.status(500).send({ ok: false, error: "could not read the IFC file" });
65640
+ } finally {
65641
+ done = true;
65642
+ }
65643
+ }
65644
+ );
65645
+ app.post(
65646
+ "/api/import-connection/extract",
65647
+ async (req, reply) => {
65648
+ const { appId, sha, id } = req.body ?? {};
65649
+ if (!appId || !CONN_APPID.test(appId)) return reply.status(400).send({ ok: false, error: "a valid appId is required" });
65650
+ if (!sha || !id) return reply.status(400).send({ ok: false, error: "sha and connection id are required" });
65651
+ const path = ifcInputPath(appId, sha);
65652
+ if (!path) return reply.status(404).send({ ok: false, error: "the uploaded IFC is no longer available \u2014 drop the file again" });
65653
+ let done = false;
65654
+ req.raw.on("close", () => {
65655
+ if (!done && isRunActive()) cancelActiveRun();
65656
+ });
65657
+ try {
65658
+ const connection = await extractConnection(`${appId}-connimport`, path, id);
65659
+ if (!connection.geometry.length) return reply.send({ ok: false, error: "that connection has no importable geometry" });
65660
+ return { ok: true, connection };
65661
+ } catch (e) {
65662
+ if (e instanceof ConnectionImportError) return reply.send({ ok: false, error: e.message });
65663
+ app.log.error({ appId, id, err: e instanceof Error ? e.message : e }, "import-connection extract failed");
65664
+ return reply.status(500).send({ ok: false, error: "could not read that connection" });
65665
+ } finally {
65666
+ done = true;
65667
+ }
65668
+ }
65669
+ );
65238
65670
  app.post("/api/use-template", async (req, reply) => {
65239
65671
  const { appId, templateId } = req.body ?? {};
65240
65672
  if (!appId || !templateId) return reply.status(400).send({ ok: false, error: "appId and templateId required" });
@@ -65322,7 +65754,7 @@ async function startServer() {
65322
65754
  warn(`last-run-status:${ref} \u2014 trace exists but couldn't be parsed (corrupt/truncated)`);
65323
65755
  let finishedAt2 = null;
65324
65756
  try {
65325
- finishedAt2 = (0, import_node_fs32.statSync)(latest.path).mtime.toISOString();
65757
+ finishedAt2 = (0, import_node_fs34.statSync)(latest.path).mtime.toISOString();
65326
65758
  } catch {
65327
65759
  finishedAt2 = null;
65328
65760
  }
@@ -65332,7 +65764,7 @@ async function startServer() {
65332
65764
  let finishedAt = typeof runEnd?.ts === "string" ? runEnd.ts : null;
65333
65765
  if (!finishedAt) {
65334
65766
  try {
65335
- finishedAt = (0, import_node_fs32.statSync)(latest.path).mtime.toISOString();
65767
+ finishedAt = (0, import_node_fs34.statSync)(latest.path).mtime.toISOString();
65336
65768
  } catch {
65337
65769
  finishedAt = null;
65338
65770
  }