@floless/app 0.74.0 → 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: join32 } = 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"] || join32(__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",
@@ -6008,7 +6008,7 @@ var require_transport = __commonJS({
6008
6008
  var { createRequire: createRequire5 } = require("module");
6009
6009
  var { existsSync: existsSync28 } = require("node:fs");
6010
6010
  var getCallers = require_caller();
6011
- var { join: join32, 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();
@@ -6161,7 +6161,7 @@ var require_transport = __commonJS({
6161
6161
  throw new Error("only one of target or targets can be specified");
6162
6162
  }
6163
6163
  if (targets) {
6164
- target = bundlerOverrides["pino-worker"] || join32(__dirname, "worker.js");
6164
+ target = bundlerOverrides["pino-worker"] || 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"] || join32(__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 join32(__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 join32 = ",";
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
- join32 = `,
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 += join32;
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 += `${join32}"... ${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
- join32 = `,
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 = join32;
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 = join32;
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 join32 = ",";
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
- join32 = `,
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 += join32;
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 += `${join32}"... ${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
- join32 = `,
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 = join32;
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 join33 = `,
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 += join33;
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 += `${join33}"... ${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 join32 = `,
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, join32, maximumBreadth);
7437
+ res += stringifyTypedArray(value, join33, maximumBreadth);
7438
7438
  keys = keys.slice(value.length);
7439
7439
  maximumPropertiesToStringify -= value.length;
7440
- separator = join32;
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 = join32;
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 = join32;
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 join32 = 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(join32(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 = join32(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_path31 = require("node:path");
50830
+ var import_node_path32 = require("node:path");
50831
50831
  var import_node_os21 = require("node:os");
50832
- var import_node_fs33 = 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.74.0" : 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.74.0" : void 0 });
53106
+ return resolveChannel({ isSea: isSea2(), define: true ? "0.75.0" : void 0 });
53036
53107
  }
53037
53108
 
53038
53109
  // workflow-update.ts
@@ -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,27 +54269,27 @@ 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);
54062
54277
  }
54063
54278
 
54064
54279
  // project-versions-store.ts
54065
54280
  var import_node_crypto7 = require("node:crypto");
54066
- var import_node_fs20 = require("node:fs");
54067
- var import_node_path18 = require("node:path");
54281
+ var import_node_fs21 = require("node:fs");
54282
+ var import_node_path19 = require("node:path");
54068
54283
  var BLOB_MIN = 8192;
54069
54284
  var BLOB_KEY = "__blobRef__";
54070
54285
  var HASH_RE = /^[0-9a-f]{64}$/;
54071
- var versionsDir = (id) => (0, import_node_path18.join)(projectDir(id), "versions");
54072
- var objectsDir = (id) => (0, import_node_path18.join)(projectDir(id), "objects");
54073
- var logPath = (id) => (0, import_node_path18.join)(versionsDir(id), "log.json");
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");
54074
54289
  function writeAtomic(path, text) {
54075
54290
  const tmp = `${path}.${process.pid}.tmp`;
54076
- (0, import_node_fs20.writeFileSync)(tmp, text);
54077
- (0, import_node_fs20.renameSync)(tmp, path);
54291
+ (0, import_node_fs21.writeFileSync)(tmp, text);
54292
+ (0, import_node_fs21.renameSync)(tmp, path);
54078
54293
  }
54079
54294
  function isVersionMeta(v) {
54080
54295
  if (!v || typeof v !== "object") return false;
@@ -54083,7 +54298,7 @@ function isVersionMeta(v) {
54083
54298
  }
54084
54299
  function loadLog(id) {
54085
54300
  try {
54086
- const parsed = JSON.parse((0, import_node_fs20.readFileSync)(logPath(id), "utf8"));
54301
+ const parsed = JSON.parse((0, import_node_fs21.readFileSync)(logPath(id), "utf8"));
54087
54302
  const raw = parsed && typeof parsed === "object" && Array.isArray(parsed.versions) ? parsed.versions : [];
54088
54303
  return { versions: raw.filter(isVersionMeta) };
54089
54304
  } catch {
@@ -54114,7 +54329,7 @@ function rehydrate(node, id) {
54114
54329
  const keys = Object.keys(obj);
54115
54330
  const ref = obj[BLOB_KEY];
54116
54331
  if (keys.length === 1 && keys[0] === BLOB_KEY && typeof ref === "string" && HASH_RE.test(ref)) {
54117
- return (0, import_node_fs20.readFileSync)((0, import_node_path18.join)(objectsDir(id), ref), "utf8");
54332
+ return (0, import_node_fs21.readFileSync)((0, import_node_path19.join)(objectsDir(id), ref), "utf8");
54118
54333
  }
54119
54334
  const out = {};
54120
54335
  for (const [k, v] of Object.entries(obj)) out[k] = rehydrate(v, id);
@@ -54123,13 +54338,13 @@ function rehydrate(node, id) {
54123
54338
  return node;
54124
54339
  }
54125
54340
  function createVersion(id, contract, meta) {
54126
- (0, import_node_fs20.mkdirSync)(versionsDir(id), { recursive: true });
54127
- (0, import_node_fs20.mkdirSync)(objectsDir(id), { recursive: true });
54341
+ (0, import_node_fs21.mkdirSync)(versionsDir(id), { recursive: true });
54342
+ (0, import_node_fs21.mkdirSync)(objectsDir(id), { recursive: true });
54128
54343
  const blobs = /* @__PURE__ */ new Map();
54129
54344
  const skeleton = dehydrate(contract, blobs);
54130
54345
  for (const [hash, text] of blobs) {
54131
- const p = (0, import_node_path18.join)(objectsDir(id), hash);
54132
- if (!(0, import_node_fs20.existsSync)(p)) writeAtomic(p, text);
54346
+ const p = (0, import_node_path19.join)(objectsDir(id), hash);
54347
+ if (!(0, import_node_fs21.existsSync)(p)) writeAtomic(p, text);
54133
54348
  }
54134
54349
  const log2 = loadLog(id);
54135
54350
  const n = (log2.versions.at(-1)?.n ?? 0) + 1;
@@ -54141,7 +54356,7 @@ function createVersion(id, contract, meta) {
54141
54356
  gate: meta.gate,
54142
54357
  kind: meta.kind
54143
54358
  };
54144
- writeAtomic((0, import_node_path18.join)(versionsDir(id), `${n}.json`), JSON.stringify({ meta: row, contract: skeleton }, null, 2));
54359
+ writeAtomic((0, import_node_path19.join)(versionsDir(id), `${n}.json`), JSON.stringify({ meta: row, contract: skeleton }, null, 2));
54145
54360
  log2.versions.push(row);
54146
54361
  writeAtomic(logPath(id), JSON.stringify(log2, null, 2));
54147
54362
  return row;
@@ -54153,7 +54368,7 @@ function listVersions(id) {
54153
54368
  }
54154
54369
  function readVersion(id, n) {
54155
54370
  try {
54156
- const snap = JSON.parse((0, import_node_fs20.readFileSync)((0, import_node_path18.join)(versionsDir(id), `${n}.json`), "utf8"));
54371
+ const snap = JSON.parse((0, import_node_fs21.readFileSync)((0, import_node_path19.join)(versionsDir(id), `${n}.json`), "utf8"));
54157
54372
  return rehydrate(snap.contract, id);
54158
54373
  } catch {
54159
54374
  return null;
@@ -54161,10 +54376,10 @@ function readVersion(id, n) {
54161
54376
  }
54162
54377
 
54163
54378
  // contract-bake.ts
54164
- var import_node_fs21 = require("node:fs");
54379
+ var import_node_fs22 = require("node:fs");
54165
54380
  var import_yaml5 = __toESM(require_dist6(), 1);
54166
54381
  function bakeContractIntoApp(sourcePath, contract) {
54167
- const doc = (0, import_yaml5.parseDocument)((0, import_node_fs21.readFileSync)(sourcePath, "utf8"));
54382
+ const doc = (0, import_yaml5.parseDocument)((0, import_node_fs22.readFileSync)(sourcePath, "utf8"));
54168
54383
  if (doc.errors.length > 0) {
54169
54384
  throw new Error(`contract bake: source is not valid YAML: ${doc.errors[0]?.message ?? "parse error"}`);
54170
54385
  }
@@ -54214,7 +54429,7 @@ function bakeContractIntoApp(sourcePath, contract) {
54214
54429
  );
54215
54430
  }
54216
54431
  doc.setIn(["nodes", idx, "config", "takeoff"], baked);
54217
- (0, import_node_fs21.writeFileSync)(sourcePath, doc.toString());
54432
+ (0, import_node_fs22.writeFileSync)(sourcePath, doc.toString());
54218
54433
  }
54219
54434
 
54220
54435
  // vectorize.ts
@@ -54537,8 +54752,12 @@ var GROUPS = {
54537
54752
  bolt: { key: "bolt", label: "Bolts", color: "#b0c4d8" },
54538
54753
  // cool zinc-grey (matches the anchor fasteners)
54539
54754
  stiffener: { key: "stiffener", label: "Stiffeners", color: "#9aafc3" },
54540
- cope: { key: "cope", label: "Copes", color: "#c2703a" }
54755
+ cope: { key: "cope", label: "Copes", color: "#c2703a" },
54541
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" }
54542
54761
  };
54543
54762
  var EMBED = 1;
54544
54763
  function num(params, key, def) {
@@ -54897,17 +55116,48 @@ function expandShearPlate(joint, beam, support) {
54897
55116
  if (cope.bottom) pushCope("bottom", cope.bottom);
54898
55117
  return parts;
54899
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
+ }
54900
55138
  function expandJoints(joints, memberGeo) {
54901
55139
  const elements = [];
54902
55140
  const skipped = [];
54903
55141
  const usedGroups = /* @__PURE__ */ new Set();
54904
55142
  (joints ?? []).forEach((j, i) => {
54905
- if (!j || !j.id || !j.main) {
54906
- skipped.push(j?.id || `joint#${i}`);
55143
+ if (!j || !j.id) {
55144
+ skipped.push(`joint#${i}`);
54907
55145
  return;
54908
55146
  }
54909
- if (j.kind === "base-plate") {
54910
- 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;
54911
55161
  if (!col || col.role !== "column") {
54912
55162
  skipped.push(j.id);
54913
55163
  return;
@@ -54923,7 +55173,7 @@ function expandJoints(joints, memberGeo) {
54923
55173
  usedGroups.add(part.group);
54924
55174
  }
54925
55175
  } else if (j.kind === "shear-plate") {
54926
- const beam = memberGeo.get(j.main);
55176
+ const beam = j.main ? memberGeo.get(j.main) : void 0;
54927
55177
  if (!beam || beam.role !== "beam") {
54928
55178
  skipped.push(j.id);
54929
55179
  return;
@@ -55145,10 +55395,10 @@ function contractToBom(contractInput) {
55145
55395
 
55146
55396
  // bom-export.ts
55147
55397
  var import_node_os14 = require("node:os");
55148
- var import_node_path19 = require("node:path");
55398
+ var import_node_path20 = require("node:path");
55149
55399
 
55150
55400
  // node_modules/write-excel-file/modules/export/writeXlsxFileNode.js
55151
- var import_node_fs22 = __toESM(require("node:fs"), 1);
55401
+ var import_node_fs23 = __toESM(require("node:fs"), 1);
55152
55402
 
55153
55403
  // node_modules/write-excel-file/modules/xlsx/helpers/features/getAdditionalContent.js
55154
55404
  function _createForOfIteratorHelperLoose(r, e) {
@@ -59320,7 +59570,7 @@ function writeXlsxFile(arg1, arg2, arg3) {
59320
59570
  },
59321
59571
  toFile: function toFile(filePath) {
59322
59572
  return createReadableStream().then(function(readableStream) {
59323
- return pipe(readableStream, import_node_fs22.default.createWriteStream(filePath));
59573
+ return pipe(readableStream, import_node_fs23.default.createWriteStream(filePath));
59324
59574
  });
59325
59575
  }
59326
59576
  };
@@ -59345,8 +59595,8 @@ function pipe(readableStream, writableStream) {
59345
59595
 
59346
59596
  // bom-export.ts
59347
59597
  function bomExportPath(appId, format) {
59348
- const root = process.env.FLOLESS_HOME ?? (0, import_node_path19.join)((0, import_node_os14.homedir)(), ".floless");
59349
- return (0, import_node_path19.join)(root, "exports", appId, `${appId}-bom.${format}`);
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}`);
59350
59600
  }
59351
59601
  function csvField(v) {
59352
59602
  let s = String(v);
@@ -59374,11 +59624,11 @@ async function bomToXlsx(bom) {
59374
59624
  }
59375
59625
 
59376
59626
  // scene-bake.ts
59377
- var import_node_fs23 = require("node:fs");
59378
- var import_node_path20 = require("node:path");
59627
+ var import_node_fs24 = require("node:fs");
59628
+ var import_node_path21 = require("node:path");
59379
59629
  var import_yaml6 = __toESM(require_dist6(), 1);
59380
59630
  function bakeSceneIntoApp(sourcePath, scene, agent = "viewer-3d") {
59381
- const doc = (0, import_yaml6.parseDocument)((0, import_node_fs23.readFileSync)(sourcePath, "utf8"));
59631
+ const doc = (0, import_yaml6.parseDocument)((0, import_node_fs24.readFileSync)(sourcePath, "utf8"));
59382
59632
  if (doc.errors.length > 0) {
59383
59633
  throw new Error(`scene bake: source is not valid YAML: ${doc.errors[0]?.message ?? "parse error"}`);
59384
59634
  }
@@ -59387,10 +59637,10 @@ function bakeSceneIntoApp(sourcePath, scene, agent = "viewer-3d") {
59387
59637
  const idx = items.findIndex((it) => ((0, import_yaml6.isMap)(it) ? it.toJSON() : null)?.agent === agent);
59388
59638
  if (idx < 0) throw new Error(`no ${agent} render node to bake the scene into`);
59389
59639
  doc.setIn(["nodes", idx, "config", "scene"], scene);
59390
- (0, import_node_fs23.writeFileSync)(sourcePath, doc.toString());
59640
+ (0, import_node_fs24.writeFileSync)(sourcePath, doc.toString());
59391
59641
  }
59392
59642
  function bakeNodeConfigById(sourcePath, nodeId, patch2) {
59393
- const doc = (0, import_yaml6.parseDocument)((0, import_node_fs23.readFileSync)(sourcePath, "utf8"));
59643
+ const doc = (0, import_yaml6.parseDocument)((0, import_node_fs24.readFileSync)(sourcePath, "utf8"));
59394
59644
  if (doc.errors.length > 0) {
59395
59645
  throw new Error(`node bake: source is not valid YAML: ${doc.errors[0]?.message ?? "parse error"}`);
59396
59646
  }
@@ -59399,12 +59649,12 @@ function bakeNodeConfigById(sourcePath, nodeId, patch2) {
59399
59649
  const idx = items.findIndex((it) => ((0, import_yaml6.isMap)(it) ? it.toJSON() : null)?.id === nodeId);
59400
59650
  if (idx < 0) throw new Error(`no node with id "${nodeId}"`);
59401
59651
  for (const [key, value] of Object.entries(patch2)) doc.setIn(["nodes", idx, "config", key], value);
59402
- (0, import_node_fs23.writeFileSync)(sourcePath, doc.toString());
59652
+ (0, import_node_fs24.writeFileSync)(sourcePath, doc.toString());
59403
59653
  }
59404
59654
  function writeViewer3dApp(dir, appId, scene) {
59405
- (0, import_node_fs23.mkdirSync)(dir, { recursive: true });
59406
- const flo = (0, import_node_path20.join)(dir, `${appId}.flo`);
59407
- (0, import_node_fs23.writeFileSync)(flo, [
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, [
59408
59658
  `app: ${appId}`,
59409
59659
  "version: 0.1.0",
59410
59660
  "display-name: Steel 3D",
@@ -59425,11 +59675,11 @@ function writeViewer3dApp(dir, appId, scene) {
59425
59675
  return flo;
59426
59676
  }
59427
59677
  function writeIfcApp(dir, appId, scene, outputPath, opts = {}) {
59428
- (0, import_node_fs23.mkdirSync)(dir, { recursive: true });
59429
- const flo = (0, import_node_path20.join)(dir, `${appId}.flo`);
59678
+ (0, import_node_fs24.mkdirSync)(dir, { recursive: true });
59679
+ const flo = (0, import_node_path21.join)(dir, `${appId}.flo`);
59430
59680
  const displayName = opts.displayName ?? "Steel IFC";
59431
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).";
59432
- (0, import_node_fs23.writeFileSync)(flo, [
59682
+ (0, import_node_fs24.writeFileSync)(flo, [
59433
59683
  `app: ${appId}`,
59434
59684
  "version: 0.1.0",
59435
59685
  `display-name: ${displayName}`,
@@ -59452,9 +59702,9 @@ function writeIfcApp(dir, appId, scene, outputPath, opts = {}) {
59452
59702
  return flo;
59453
59703
  }
59454
59704
  function writeTeklaApp(dir, appId, scene, teklaVersion = "2026.0") {
59455
- (0, import_node_fs23.mkdirSync)(dir, { recursive: true });
59456
- const flo = (0, import_node_path20.join)(dir, `${appId}.flo`);
59457
- (0, import_node_fs23.writeFileSync)(flo, [
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, [
59458
59708
  `app: ${appId}`,
59459
59709
  "version: 0.1.0",
59460
59710
  "display-name: Steel to Tekla",
@@ -59862,13 +60112,13 @@ function scoreContract2(contractInput) {
59862
60112
  }
59863
60113
 
59864
60114
  // app-lifecycle.ts
59865
- var import_node_fs26 = require("node:fs");
60115
+ var import_node_fs27 = require("node:fs");
59866
60116
  var import_node_os16 = require("node:os");
59867
- var import_node_path23 = require("node:path");
60117
+ var import_node_path24 = require("node:path");
59868
60118
 
59869
60119
  // routines.ts
59870
- var import_node_fs25 = require("node:fs");
59871
- var import_node_path22 = require("node:path");
60120
+ var import_node_fs26 = require("node:fs");
60121
+ var import_node_path23 = require("node:path");
59872
60122
 
59873
60123
  // sse.ts
59874
60124
  var clients = /* @__PURE__ */ new Set();
@@ -59905,11 +60155,11 @@ function clientCount() {
59905
60155
  }
59906
60156
 
59907
60157
  // trigger-sessions.ts
59908
- var import_node_fs24 = require("node:fs");
60158
+ var import_node_fs25 = require("node:fs");
59909
60159
  var import_node_os15 = require("node:os");
59910
- var import_node_path21 = require("node:path");
60160
+ var import_node_path22 = require("node:path");
59911
60161
  var import_yaml7 = __toESM(require_dist6(), 1);
59912
- var AGENTS_DIR2 = process.env.AWARE_HOME ? (0, import_node_path21.join)(process.env.AWARE_HOME, "agents") : (0, import_node_path21.join)((0, import_node_os15.homedir)(), ".aware", "agents");
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");
59913
60163
  function summarizeFire(data) {
59914
60164
  if (!data) return "event";
59915
60165
  const type = typeof data.type === "string" ? data.type : "";
@@ -59926,10 +60176,10 @@ function mapTriggerState(phase) {
59926
60176
  function isHostBacked(agent, agentsDir = AGENTS_DIR2) {
59927
60177
  const safe = (n) => !n.includes("/") && !n.includes("\\") && !n.includes("..");
59928
60178
  if (!safe(agent)) return false;
59929
- const manifestPath = (0, import_node_path21.join)(agentsDir, agent, "manifest.yaml");
59930
- if (!(0, import_node_fs24.existsSync)(manifestPath)) return false;
60179
+ const manifestPath = (0, import_node_path22.join)(agentsDir, agent, "manifest.yaml");
60180
+ if (!(0, import_node_fs25.existsSync)(manifestPath)) return false;
59931
60181
  try {
59932
- const doc = (0, import_yaml7.parse)((0, import_node_fs24.readFileSync)(manifestPath, "utf8"));
60182
+ const doc = (0, import_yaml7.parse)((0, import_node_fs25.readFileSync)(manifestPath, "utf8"));
59933
60183
  const transport = doc?.transport;
59934
60184
  return !!(transport && typeof transport === "object" && "cli" in transport);
59935
60185
  } catch {
@@ -60214,7 +60464,7 @@ var RoutineError = class extends Error {
60214
60464
  }
60215
60465
  status;
60216
60466
  };
60217
- var ROUTINES_FILE = (0, import_node_path22.join)(flolessRoot, "routines.json");
60467
+ var ROUTINES_FILE = (0, import_node_path23.join)(flolessRoot, "routines.json");
60218
60468
  var routines = [];
60219
60469
  var loaded = false;
60220
60470
  function ensureLoaded() {
@@ -60250,10 +60500,10 @@ function sanitizeRoutine(raw) {
60250
60500
  };
60251
60501
  }
60252
60502
  function loadFromDisk() {
60253
- if (!(0, import_node_fs25.existsSync)(ROUTINES_FILE)) return [];
60503
+ if (!(0, import_node_fs26.existsSync)(ROUTINES_FILE)) return [];
60254
60504
  let text;
60255
60505
  try {
60256
- text = (0, import_node_fs25.readFileSync)(ROUTINES_FILE, "utf8");
60506
+ text = (0, import_node_fs26.readFileSync)(ROUTINES_FILE, "utf8");
60257
60507
  } catch {
60258
60508
  return [];
60259
60509
  }
@@ -60262,17 +60512,17 @@ function loadFromDisk() {
60262
60512
  return Array.isArray(parsed) ? parsed.map(sanitizeRoutine).filter((r) => r !== null) : [];
60263
60513
  } catch {
60264
60514
  try {
60265
- (0, import_node_fs25.renameSync)(ROUTINES_FILE, `${ROUTINES_FILE}.corrupt-${Date.now()}`);
60515
+ (0, import_node_fs26.renameSync)(ROUTINES_FILE, `${ROUTINES_FILE}.corrupt-${Date.now()}`);
60266
60516
  } catch {
60267
60517
  }
60268
60518
  return [];
60269
60519
  }
60270
60520
  }
60271
60521
  function saveRoutines() {
60272
- if (!(0, import_node_fs25.existsSync)(flolessRoot)) (0, import_node_fs25.mkdirSync)(flolessRoot, { recursive: true });
60522
+ if (!(0, import_node_fs26.existsSync)(flolessRoot)) (0, import_node_fs26.mkdirSync)(flolessRoot, { recursive: true });
60273
60523
  const tmp = `${ROUTINES_FILE}.${process.pid}.tmp`;
60274
- (0, import_node_fs25.writeFileSync)(tmp, JSON.stringify(routines, null, 2));
60275
- (0, import_node_fs25.renameSync)(tmp, ROUTINES_FILE);
60524
+ (0, import_node_fs26.writeFileSync)(tmp, JSON.stringify(routines, null, 2));
60525
+ (0, import_node_fs26.renameSync)(tmp, ROUTINES_FILE);
60276
60526
  }
60277
60527
  function listRoutines() {
60278
60528
  ensureLoaded();
@@ -60680,13 +60930,13 @@ var AppLifecycleError = class extends Error {
60680
60930
  }
60681
60931
  };
60682
60932
  function appsDir() {
60683
- return process.env.AWARE_HOME ? (0, import_node_path23.join)(process.env.AWARE_HOME, "apps") : (0, import_node_path23.join)((0, import_node_os16.homedir)(), ".aware", "apps");
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");
60684
60934
  }
60685
60935
  function appDirPath(id) {
60686
- return (0, import_node_path23.join)(appsDir(), id);
60936
+ return (0, import_node_path24.join)(appsDir(), id);
60687
60937
  }
60688
60938
  function appInstalled(id) {
60689
- return APP_ID3.test(id) && (0, import_node_fs26.existsSync)(appDirPath(id));
60939
+ return APP_ID3.test(id) && (0, import_node_fs27.existsSync)(appDirPath(id));
60690
60940
  }
60691
60941
  function logCascade(what, e) {
60692
60942
  console.error(`[app-lifecycle] cascade step failed (${what}):`, e instanceof Error ? e.message : e);
@@ -60802,9 +61052,9 @@ function isGatedAwareRoute(url, method) {
60802
61052
 
60803
61053
  // autostart.mjs
60804
61054
  var import_node_child_process5 = require("node:child_process");
60805
- var import_node_fs27 = require("node:fs");
61055
+ var import_node_fs28 = require("node:fs");
60806
61056
  var import_node_os17 = require("node:os");
60807
- var import_node_path24 = require("node:path");
61057
+ var import_node_path25 = require("node:path");
60808
61058
 
60809
61059
  // teardown.mjs
60810
61060
  var RUN_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
@@ -60916,8 +61166,8 @@ function removeLegacyRunKey() {
60916
61166
  }
60917
61167
  function logLine(msg) {
60918
61168
  try {
60919
- (0, import_node_fs27.mkdirSync)(logDir(), { recursive: true });
60920
- (0, import_node_fs27.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}
60921
61171
  `);
60922
61172
  } catch {
60923
61173
  }
@@ -60925,8 +61175,8 @@ function logLine(msg) {
60925
61175
  function registerAutostart(exePath) {
60926
61176
  if (!isWin) return;
60927
61177
  const xml = buildAutostartTaskXml(exePath, currentUserId());
60928
- const tmp = (0, import_node_path24.join)((0, import_node_os17.tmpdir)(), `floless-autostart-${process.pid}-${Date.now()}.xml`);
60929
- (0, import_node_fs27.writeFileSync)(tmp, "\uFEFF" + xml, { encoding: "utf16le" });
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" });
60930
61180
  try {
60931
61181
  (0, import_node_child_process5.execFileSync)("schtasks", ["/Create", "/TN", TASK_NAME, "/XML", tmp, "/F"], {
60932
61182
  stdio: ["ignore", "ignore", "ignore"],
@@ -60937,7 +61187,7 @@ function registerAutostart(exePath) {
60937
61187
  throw err2;
60938
61188
  } finally {
60939
61189
  try {
60940
- (0, import_node_fs27.rmSync)(tmp, { force: true });
61190
+ (0, import_node_fs28.rmSync)(tmp, { force: true });
60941
61191
  } catch {
60942
61192
  }
60943
61193
  }
@@ -60972,26 +61222,26 @@ function unregisterAutostart() {
60972
61222
  // updater.ts
60973
61223
  var import_node_child_process6 = require("node:child_process");
60974
61224
  var import_node_crypto8 = require("node:crypto");
60975
- var import_node_fs29 = require("node:fs");
61225
+ var import_node_fs30 = require("node:fs");
60976
61226
  var import_node_stream3 = require("node:stream");
60977
61227
  var import_promises = require("node:stream/promises");
60978
- var import_node_path26 = require("node:path");
61228
+ var import_node_path27 = require("node:path");
60979
61229
 
60980
61230
  // post-update-marker.mjs
60981
- var import_node_fs28 = require("node:fs");
61231
+ var import_node_fs29 = require("node:fs");
60982
61232
  var import_node_os18 = require("node:os");
60983
- var import_node_path25 = require("node:path");
61233
+ var import_node_path26 = require("node:path");
60984
61234
  var FRESH_MS = 12e4;
60985
61235
  function markerPath() {
60986
61236
  const override = (process.env.FLOLESS_POST_UPDATE_MARKER ?? "").trim();
60987
61237
  if (override) return override;
60988
- const root = process.env.FLOLESS_HOME ?? (0, import_node_path25.join)((0, import_node_os18.homedir)(), ".floless");
60989
- return (0, import_node_path25.join)(root, ".post-update");
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");
60990
61240
  }
60991
61241
  function legacyMarkerPath() {
60992
61242
  if ((process.env.FLOLESS_POST_UPDATE_MARKER ?? "").trim()) return null;
60993
61243
  try {
60994
- return (0, import_node_path25.join)((0, import_node_path25.dirname)((0, import_node_path25.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");
60995
61245
  } catch {
60996
61246
  return null;
60997
61247
  }
@@ -61001,7 +61251,7 @@ function writePostUpdateMarker() {
61001
61251
  for (const p of [markerPath(), legacyMarkerPath()]) {
61002
61252
  if (!p) continue;
61003
61253
  try {
61004
- (0, import_node_fs28.writeFileSync)(p, (/* @__PURE__ */ new Date()).toISOString());
61254
+ (0, import_node_fs29.writeFileSync)(p, (/* @__PURE__ */ new Date()).toISOString());
61005
61255
  wrote = true;
61006
61256
  } catch {
61007
61257
  }
@@ -61013,9 +61263,9 @@ function consumePostUpdateMarker() {
61013
61263
  for (const p of [markerPath(), legacyMarkerPath()]) {
61014
61264
  if (!p) continue;
61015
61265
  try {
61016
- if (!(0, import_node_fs28.existsSync)(p)) continue;
61017
- const ageMs = Date.now() - (0, import_node_fs28.statSync)(p).mtimeMs;
61018
- (0, import_node_fs28.rmSync)(p, { force: true });
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 });
61019
61269
  if (ageMs < FRESH_MS) fresh = true;
61020
61270
  } catch {
61021
61271
  }
@@ -61032,13 +61282,13 @@ function currentVersion() {
61032
61282
  return appVersion();
61033
61283
  }
61034
61284
  function installRoot() {
61035
- return (0, import_node_path26.dirname)((0, import_node_path26.dirname)(process.execPath));
61285
+ return (0, import_node_path27.dirname)((0, import_node_path27.dirname)(process.execPath));
61036
61286
  }
61037
61287
  function updateExePath() {
61038
- return (0, import_node_path26.join)(installRoot(), "Update.exe");
61288
+ return (0, import_node_path27.join)(installRoot(), "Update.exe");
61039
61289
  }
61040
61290
  function packagesDir() {
61041
- return (0, import_node_path26.join)(installRoot(), "packages");
61291
+ return (0, import_node_path27.join)(installRoot(), "packages");
61042
61292
  }
61043
61293
  function feedUrl() {
61044
61294
  const env2 = (process.env.FLOLESS_UPDATE_URL ?? "").trim().replace(/\/+$/, "");
@@ -61131,22 +61381,22 @@ async function checkForUpdate() {
61131
61381
  }
61132
61382
  async function sha1OfFile(path) {
61133
61383
  const hash = (0, import_node_crypto8.createHash)("sha1");
61134
- await (0, import_promises.pipeline)((0, import_node_fs29.createReadStream)(path), hash);
61384
+ await (0, import_promises.pipeline)((0, import_node_fs30.createReadStream)(path), hash);
61135
61385
  return hash.digest("hex").toUpperCase();
61136
61386
  }
61137
61387
  async function downloadPackage(asset) {
61138
61388
  if (!NUPKG_NAME.test(asset.FileName)) throw new Error(`refusing suspicious package name: ${asset.FileName}`);
61139
61389
  const want = asset.SHA1.toUpperCase();
61140
61390
  const dir = packagesDir();
61141
- (0, import_node_fs29.mkdirSync)(dir, { recursive: true });
61142
- const dest = (0, import_node_path26.join)(dir, asset.FileName);
61143
- if ((0, import_node_fs29.existsSync)(dest) && await sha1OfFile(dest) === want) return dest;
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;
61144
61394
  const res = await authedFetch(`${feedUrl()}/${encodeURIComponent(asset.FileName)}`, {
61145
61395
  redirect: "follow",
61146
61396
  signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS)
61147
61397
  });
61148
61398
  if (!res.ok || !res.body) throw new Error(`download failed: HTTP ${res.status} for ${asset.FileName}`);
61149
- await (0, import_promises.pipeline)(import_node_stream3.Readable.fromWeb(res.body), (0, import_node_fs29.createWriteStream)(dest));
61399
+ await (0, import_promises.pipeline)(import_node_stream3.Readable.fromWeb(res.body), (0, import_node_fs30.createWriteStream)(dest));
61150
61400
  const got = await sha1OfFile(dest);
61151
61401
  if (got !== want) throw new Error(`SHA1 mismatch for ${asset.FileName}: feed=${want} got=${got}`);
61152
61402
  return dest;
@@ -61155,7 +61405,7 @@ async function applyUpdate(check, opts) {
61155
61405
  if (!check.supported) return { applied: false, message: check.reason ?? "auto-update not supported in this runtime" };
61156
61406
  if (!check.updateAvailable || !check.asset) return { applied: false, message: check.reason ?? "no update available" };
61157
61407
  const exe = updateExePath();
61158
- if (!(0, import_node_fs29.existsSync)(exe)) {
61408
+ if (!(0, import_node_fs30.existsSync)(exe)) {
61159
61409
  return { applied: false, message: `Update.exe not found at ${exe} \u2014 is this a Velopack install?` };
61160
61410
  }
61161
61411
  const pkg = await downloadPackage(check.asset);
@@ -61395,12 +61645,12 @@ function isTraceCorrupt(events) {
61395
61645
 
61396
61646
  // launch.mjs
61397
61647
  var import_node_child_process7 = require("node:child_process");
61398
- var import_node_path27 = require("node:path");
61648
+ var import_node_path28 = require("node:path");
61399
61649
  var import_node_url3 = require("node:url");
61400
- var import_node_fs30 = require("node:fs");
61650
+ var import_node_fs31 = require("node:fs");
61401
61651
  var import_node_http = __toESM(require("node:http"), 1);
61402
61652
  var import_node_readline = require("node:readline");
61403
- var __dirname2 = (0, import_node_path27.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));
61404
61654
  var PORT = Number(process.env.PORT ?? 4317);
61405
61655
  var HEALTH_URL = `http://127.0.0.1:${PORT}/api/health`;
61406
61656
  var BROWSER_URL = `http://floless.localhost:${PORT}`;
@@ -61472,8 +61722,8 @@ async function waitHealthy(timeoutMs = 3e4) {
61472
61722
  function resolveServerStart() {
61473
61723
  const packaged = /flolessapp\.exe$/i.test(process.execPath);
61474
61724
  if (packaged) return { cmd: process.execPath, args: ["--serve"], shell: false };
61475
- const bundle = (0, import_node_path27.join)(__dirname2, "dist", "floless-server.cjs");
61476
- if ((0, import_node_fs30.existsSync)(bundle)) return { cmd: process.execPath, args: [bundle, "--serve"], shell: false };
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 };
61477
61727
  return { cmd: "npm", args: ["run", "start"], shell: isWin2 };
61478
61728
  }
61479
61729
  function startServerDetached() {
@@ -61626,8 +61876,8 @@ function taskkillArgs(pid, { tree = true } = {}) {
61626
61876
  }
61627
61877
  function killSupervisor({ tree = true } = {}) {
61628
61878
  if (!isWin2) return;
61629
- const isNpmChannel = /^node(\.exe)?$/i.test((0, import_node_path27.basename)(process.execPath));
61630
- const scriptMatch = isNpmChannel ? (0, import_node_path27.basename)((0, import_node_url3.fileURLToPath)(__import_meta_url)) : void 0;
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;
61631
61881
  const realExe = resolveRealInstallExe(process.execPath);
61632
61882
  const exeMatch = realExe === process.execPath ? process.execPath : [process.execPath, realExe];
61633
61883
  const pids = supervisorPidsToKill(enumerateProcesses(), process.pid, exeMatch, scriptMatch);
@@ -61792,7 +62042,7 @@ async function runAction(arg, flagArgv = [], selfVersion = null) {
61792
62042
  }
61793
62043
  await action(parseTeardownFlags(flagArgv));
61794
62044
  }
61795
- var entry = (0, import_node_path27.basename)(process.argv[1] ?? "").toLowerCase();
62045
+ var entry = (0, import_node_path28.basename)(process.argv[1] ?? "").toLowerCase();
61796
62046
  if (entry === "launch.mjs") {
61797
62047
  runAction(process.argv[2], process.argv.slice(3)).catch((e) => {
61798
62048
  log(`error: ${e?.message ?? e}`);
@@ -61866,9 +62116,9 @@ function awareUpgradeBlockReason(s) {
61866
62116
  }
61867
62117
 
61868
62118
  // skill-sync.ts
61869
- var import_node_fs31 = require("node:fs");
62119
+ var import_node_fs32 = require("node:fs");
61870
62120
  var import_node_os19 = require("node:os");
61871
- var import_node_path28 = require("node:path");
62121
+ var import_node_path29 = require("node:path");
61872
62122
  var import_node_url4 = require("node:url");
61873
62123
  var import_yaml8 = __toESM(require_dist6(), 1);
61874
62124
 
@@ -61911,14 +62161,14 @@ function selectShippedSkillNames(names) {
61911
62161
  }
61912
62162
 
61913
62163
  // skill-sync.ts
61914
- var __dirname3 = (0, import_node_path28.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));
61915
62165
  function bundledSkillsRoot() {
61916
62166
  const candidates = [
61917
- (0, import_node_path28.join)(__dirname3, "skills"),
61918
- (0, import_node_path28.join)((0, import_node_path28.dirname)(process.execPath), "skills"),
61919
- (0, import_node_path28.join)(__dirname3, "..", ".claude", "skills")
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")
61920
62170
  ];
61921
- return candidates.find((p) => (0, import_node_fs31.existsSync)(p)) ?? null;
62171
+ return candidates.find((p) => (0, import_node_fs32.existsSync)(p)) ?? null;
61922
62172
  }
61923
62173
  function targetConfigDirs() {
61924
62174
  const override = process.env.FLOLESS_SKILL_TARGETS;
@@ -61927,14 +62177,14 @@ function targetConfigDirs() {
61927
62177
  }
61928
62178
  const home = (0, import_node_os19.homedir)();
61929
62179
  return [
61930
- { runtime: "claude", dir: (0, import_node_path28.join)(home, ".claude") },
61931
- { runtime: "codex", dir: (0, import_node_path28.join)(home, ".codex") },
61932
- { runtime: "opencode", dir: (0, import_node_path28.join)(home, ".opencode") }
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") }
61933
62183
  ];
61934
62184
  }
61935
62185
  function skillVersion(skillMdPath) {
61936
62186
  try {
61937
- const text = (0, import_node_fs31.readFileSync)(skillMdPath, "utf8");
62187
+ const text = (0, import_node_fs32.readFileSync)(skillMdPath, "utf8");
61938
62188
  const m = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text);
61939
62189
  if (!m || m[1] === void 0) return null;
61940
62190
  const fm = (0, import_yaml8.parse)(m[1]);
@@ -61974,21 +62224,21 @@ function decideAction(installed, bundled) {
61974
62224
  function bundledSkills(root) {
61975
62225
  let entries = [];
61976
62226
  try {
61977
- entries = selectShippedSkillNames((0, import_node_fs31.readdirSync)(root));
62227
+ entries = selectShippedSkillNames((0, import_node_fs32.readdirSync)(root));
61978
62228
  } catch {
61979
62229
  return [];
61980
62230
  }
61981
62231
  const out = [];
61982
62232
  for (const name of entries) {
61983
- const dir = (0, import_node_path28.join)(root, name);
62233
+ const dir = (0, import_node_path29.join)(root, name);
61984
62234
  let isDir = false;
61985
62235
  try {
61986
- isDir = (0, import_node_fs31.statSync)(dir).isDirectory();
62236
+ isDir = (0, import_node_fs32.statSync)(dir).isDirectory();
61987
62237
  } catch {
61988
62238
  isDir = false;
61989
62239
  }
61990
62240
  if (!isDir) continue;
61991
- const v = skillVersion((0, import_node_path28.join)(dir, "SKILL.md"));
62241
+ const v = skillVersion((0, import_node_path29.join)(dir, "SKILL.md"));
61992
62242
  if (!v) continue;
61993
62243
  out.push({ name, dir, version: v });
61994
62244
  }
@@ -62001,17 +62251,17 @@ function syncSkills() {
62001
62251
  const skills = bundledSkills(root);
62002
62252
  if (!skills.length) return results;
62003
62253
  for (const { runtime, dir: cfg } of targetConfigDirs()) {
62004
- if (!(0, import_node_fs31.existsSync)(cfg)) continue;
62005
- const skillsDir = (0, import_node_path28.join)(cfg, "skills");
62254
+ if (!(0, import_node_fs32.existsSync)(cfg)) continue;
62255
+ const skillsDir = (0, import_node_path29.join)(cfg, "skills");
62006
62256
  for (const s of skills) {
62007
- const dest = (0, import_node_path28.join)(skillsDir, s.name);
62008
- const installedMd = (0, import_node_path28.join)(dest, "SKILL.md");
62009
- const installed = (0, import_node_fs31.existsSync)(installedMd) ? skillVersion(installedMd) : null;
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;
62010
62260
  const action = decideAction(installed, s.version);
62011
62261
  if (action === "installed" || action === "updated") {
62012
62262
  try {
62013
- if (action === "updated") (0, import_node_fs31.rmSync)(dest, { recursive: true, force: true });
62014
- (0, import_node_fs31.cpSync)(s.dir, dest, { recursive: true });
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 });
62015
62265
  results.push({ runtime, skill: s.name, action, from: installed, to: s.version });
62016
62266
  } catch {
62017
62267
  }
@@ -62025,8 +62275,8 @@ function syncSkills() {
62025
62275
 
62026
62276
  // watch.ts
62027
62277
  var import_node_os20 = require("node:os");
62028
- var import_node_path30 = require("node:path");
62029
- var import_node_fs32 = require("node:fs");
62278
+ var import_node_path31 = require("node:path");
62279
+ var import_node_fs33 = require("node:fs");
62030
62280
 
62031
62281
  // node_modules/chokidar/esm/index.js
62032
62282
  var import_fs2 = require("fs");
@@ -62037,7 +62287,7 @@ var sysPath2 = __toESM(require("path"), 1);
62037
62287
  // node_modules/readdirp/esm/index.js
62038
62288
  var import_promises2 = require("node:fs/promises");
62039
62289
  var import_node_stream4 = require("node:stream");
62040
- var import_node_path29 = require("node:path");
62290
+ var import_node_path30 = require("node:path");
62041
62291
  var EntryTypes = {
62042
62292
  FILE_TYPE: "files",
62043
62293
  DIR_TYPE: "directories",
@@ -62112,7 +62362,7 @@ var ReaddirpStream = class extends import_node_stream4.Readable {
62112
62362
  this._wantsDir = type ? DIR_TYPES.has(type) : false;
62113
62363
  this._wantsFile = type ? FILE_TYPES.has(type) : false;
62114
62364
  this._wantsEverything = type === EntryTypes.EVERYTHING_TYPE;
62115
- this._root = (0, import_node_path29.resolve)(root);
62365
+ this._root = (0, import_node_path30.resolve)(root);
62116
62366
  this._isDirent = !opts.alwaysStat;
62117
62367
  this._statsProp = this._isDirent ? "dirent" : "stats";
62118
62368
  this._rdOptions = { encoding: "utf8", withFileTypes: this._isDirent };
@@ -62183,8 +62433,8 @@ var ReaddirpStream = class extends import_node_stream4.Readable {
62183
62433
  let entry2;
62184
62434
  const basename5 = this._isDirent ? dirent.name : dirent;
62185
62435
  try {
62186
- const fullPath = (0, import_node_path29.resolve)((0, import_node_path29.join)(path, basename5));
62187
- entry2 = { path: (0, import_node_path29.relative)(this._root, fullPath), fullPath, basename: basename5 };
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 };
62188
62438
  entry2[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
62189
62439
  } catch (err2) {
62190
62440
  this._onError(err2);
@@ -62218,7 +62468,7 @@ var ReaddirpStream = class extends import_node_stream4.Readable {
62218
62468
  }
62219
62469
  if (entryRealPathStats.isDirectory()) {
62220
62470
  const len2 = entryRealPath.length;
62221
- if (full.startsWith(entryRealPath) && full.substr(len2, 1) === import_node_path29.sep) {
62471
+ if (full.startsWith(entryRealPath) && full.substr(len2, 1) === import_node_path30.sep) {
62222
62472
  const recursiveError = new Error(`Circular symlink detected: "${full}" points to "${entryRealPath}"`);
62223
62473
  recursiveError.code = RECURSIVE_ERROR_CODE;
62224
62474
  return this._onError(recursiveError);
@@ -63726,33 +63976,33 @@ function appIdFromLogPath(path) {
63726
63976
  return i >= 0 && parts[i + 1] ? parts[i + 1] : null;
63727
63977
  }
63728
63978
  function samePath(a, b) {
63729
- const ra = (0, import_node_path30.resolve)(a);
63730
- const rb = (0, import_node_path30.resolve)(b);
63979
+ const ra = (0, import_node_path31.resolve)(a);
63980
+ const rb = (0, import_node_path31.resolve)(b);
63731
63981
  return process.platform === "win32" ? ra.toLowerCase() === rb.toLowerCase() : ra === rb;
63732
63982
  }
63733
63983
  function underDir(path, dir) {
63734
- const rp = (0, import_node_path30.resolve)(path);
63735
- const rd = (0, import_node_path30.resolve)(dir);
63984
+ const rp = (0, import_node_path31.resolve)(path);
63985
+ const rd = (0, import_node_path31.resolve)(dir);
63736
63986
  const [p, d] = process.platform === "win32" ? [rp.toLowerCase(), rd.toLowerCase()] : [rp, rd];
63737
- return p === d || p.startsWith(d + import_node_path30.sep);
63987
+ return p === d || p.startsWith(d + import_node_path31.sep);
63738
63988
  }
63739
63989
  function startWatcher() {
63740
- const awareDir = process.env.AWARE_HOME ?? (0, import_node_path30.join)((0, import_node_os20.homedir)(), ".aware");
63741
- const credentialsDir = (0, import_node_path30.join)(awareDir, "credentials");
63742
- if (!(0, import_node_fs32.existsSync)(credentialsDir)) {
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)) {
63743
63993
  try {
63744
- (0, import_node_fs32.mkdirSync)(credentialsDir, { recursive: true });
63994
+ (0, import_node_fs33.mkdirSync)(credentialsDir, { recursive: true });
63745
63995
  } catch {
63746
63996
  }
63747
63997
  }
63748
- if (!(0, import_node_fs32.existsSync)(uiDir)) {
63998
+ if (!(0, import_node_fs33.existsSync)(uiDir)) {
63749
63999
  try {
63750
- (0, import_node_fs32.mkdirSync)(uiDir, { recursive: true });
64000
+ (0, import_node_fs33.mkdirSync)(uiDir, { recursive: true });
63751
64001
  } catch {
63752
64002
  }
63753
64003
  }
63754
- const targets = ["apps", "logs", "credentials"].map((d) => (0, import_node_path30.join)(awareDir, d)).filter((p) => (0, import_node_fs32.existsSync)(p));
63755
- if ((0, import_node_fs32.existsSync)(uiDir)) targets.push(uiDir);
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);
63756
64006
  if (targets.length === 0) {
63757
64007
  return null;
63758
64008
  }
@@ -63781,11 +64031,11 @@ function startWatcher() {
63781
64031
  const isCredential = path.split(/[\\/]/).includes("credentials");
63782
64032
  const kind = isCredential ? "credential" : path.endsWith(".jsonl") ? "trace" : path.endsWith(".lock") ? "lock" : path.endsWith(".flo") || path.endsWith(".app") ? "source" : "file";
63783
64033
  broadcast({ type: "fs-change", kind, event, path });
63784
- if (kind === "trace" && event !== "unlink" && (0, import_node_fs32.existsSync)(path)) {
64034
+ if (kind === "trace" && event !== "unlink" && (0, import_node_fs33.existsSync)(path)) {
63785
64035
  const id = appIdFromLogPath(path);
63786
64036
  if (!id) return;
63787
64037
  try {
63788
- broadcast({ type: "trace-file", id, runId: path.split(import_node_path30.sep).pop()?.replace(/\.jsonl$/, "") ?? null, events: parseTrace((0, import_node_fs32.readFileSync)(path, "utf8")) });
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")) });
63789
64039
  } catch {
63790
64040
  }
63791
64041
  }
@@ -63794,10 +64044,10 @@ function startWatcher() {
63794
64044
  }
63795
64045
 
63796
64046
  // index.ts
63797
- var __dirname4 = (0, import_node_path31.dirname)((0, import_node_url5.fileURLToPath)(__import_meta_url));
63798
- var WEB_ROOT = [(0, import_node_path31.join)(__dirname4, "web"), (0, import_node_path31.join)((0, import_node_path31.dirname)(process.execPath), "web"), (0, import_node_path31.join)(__dirname4, "..", "web")].find(
63799
- (p) => (0, import_node_fs33.existsSync)(p)
63800
- ) ?? (0, import_node_path31.join)(__dirname4, "..", "web");
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");
63801
64051
  var PORT2 = Number(process.env.PORT ?? 4317);
63802
64052
  var HOST = "127.0.0.1";
63803
64053
  var crashHandlersInstalled = false;
@@ -63813,7 +64063,7 @@ function installCrashHandlers() {
63813
64063
  ${stack}
63814
64064
  `;
63815
64065
  try {
63816
- (0, import_node_fs33.appendFileSync)(logFilePath(), line);
64066
+ (0, import_node_fs34.appendFileSync)(logFilePath(), line);
63817
64067
  } catch {
63818
64068
  }
63819
64069
  if (process.stderr.isTTY) process.stderr.write(line);
@@ -64110,9 +64360,9 @@ async function startServer() {
64110
64360
  const { id, templateId, templatePath, srcPath, appDirPath: appDirPath2, fromVersion, contract, verb } = opts;
64111
64361
  const bdir = backupDir(id, fromVersion, Date.now());
64112
64362
  try {
64113
- (0, import_node_fs33.mkdirSync)((0, import_node_path31.dirname)(bdir), { recursive: true });
64114
- (0, import_node_fs33.cpSync)(appDirPath2, bdir, { recursive: true });
64115
- (0, import_node_fs33.cpSync)(templatePath, srcPath);
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);
64116
64366
  if (id !== templateId) stampSourceAppId(srcPath, id);
64117
64367
  if (contract != null) {
64118
64368
  bakeContractIntoApp(srcPath, contract);
@@ -64123,8 +64373,8 @@ async function startServer() {
64123
64373
  } catch (e) {
64124
64374
  const msg = e instanceof Error ? e.message : String(e);
64125
64375
  try {
64126
- (0, import_node_fs33.rmSync)(appDirPath2, { recursive: true, force: true });
64127
- (0, import_node_fs33.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 });
64128
64378
  } catch (re) {
64129
64379
  const rmsg = re instanceof Error ? re.message : String(re);
64130
64380
  throw new TemplateSwapError(`${verb} failed (${msg}) AND rollback failed (${rmsg}) \u2014 restore manually from ${bdir}`, "rollback-failed", bdir);
@@ -64155,7 +64405,7 @@ async function startServer() {
64155
64405
  return reply.status(409).send({ ok: false, error: `"${id}" is already at ${fromVersion}`, code: "up-to-date" });
64156
64406
  }
64157
64407
  const contract = readContract(id);
64158
- if (contract == null && (0, import_node_fs33.existsSync)(contractPath(id))) {
64408
+ if (contract == null && (0, import_node_fs34.existsSync)(contractPath(id))) {
64159
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" });
64160
64410
  }
64161
64411
  if (contract != null) {
@@ -64517,10 +64767,10 @@ async function startServer() {
64517
64767
  if (bad) return reply.status(404).send({ ok: false, error: bad });
64518
64768
  const dir = projectExportsDir(project);
64519
64769
  const exports2 = projectExportFiles(req.params.appId).map(({ kind, filename }) => {
64520
- const path = (0, import_node_path31.join)(dir, filename);
64770
+ const path = (0, import_node_path32.join)(dir, filename);
64521
64771
  let exportedAt = null;
64522
64772
  try {
64523
- const st = (0, import_node_fs33.statSync)(path);
64773
+ const st = (0, import_node_fs34.statSync)(path);
64524
64774
  if (st.isFile()) exportedAt = st.mtime.toISOString();
64525
64775
  } catch {
64526
64776
  }
@@ -64604,8 +64854,8 @@ async function startServer() {
64604
64854
  }
64605
64855
  const companionId = `${req.params.appId}-ifc`;
64606
64856
  const filename = `${req.params.appId}.ifc`;
64607
- const outPath = project ? (0, import_node_path31.join)(projectExportsDir(project), filename) : (0, import_node_path31.join)(appPath(companionId), filename);
64608
- if (project) (0, import_node_fs33.mkdirSync)((0, import_node_path31.dirname)(outPath), { recursive: true });
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 });
64609
64859
  let flo;
64610
64860
  try {
64611
64861
  flo = writeIfcApp(appPath(companionId), companionId, scene, outPath, writeOpts);
@@ -64628,8 +64878,8 @@ async function startServer() {
64628
64878
  }
64629
64879
  throw e;
64630
64880
  }
64631
- if (!(0, import_node_fs33.existsSync)(outPath)) return reply.send({ ok: false, error: "the IFC export produced no file" });
64632
- const content = (0, import_node_fs33.readFileSync)(outPath, "utf8");
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");
64633
64883
  broadcast({ type: "apps-changed" });
64634
64884
  return { ok: true, filename, savedTo: outPath, content, bytes: Buffer.byteLength(content), skipped, ...extrusionsSkipped ? { extrusionsSkipped } : {} };
64635
64885
  });
@@ -64718,17 +64968,17 @@ async function startServer() {
64718
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)" });
64719
64969
  }
64720
64970
  const filename = `${appId}-bom.${format}`;
64721
- const outPath = project ? (0, import_node_path31.join)(projectExportsDir(project), filename) : bomExportPath(appId, format);
64722
- const dir = (0, import_node_path31.dirname)(outPath);
64971
+ const outPath = project ? (0, import_node_path32.join)(projectExportsDir(project), filename) : bomExportPath(appId, format);
64972
+ const dir = (0, import_node_path32.dirname)(outPath);
64723
64973
  try {
64724
- (0, import_node_fs33.mkdirSync)(dir, { recursive: true });
64974
+ (0, import_node_fs34.mkdirSync)(dir, { recursive: true });
64725
64975
  if (format === "csv") {
64726
64976
  const text = bomToCsv(bom);
64727
- (0, import_node_fs33.writeFileSync)(outPath, text, "utf8");
64977
+ (0, import_node_fs34.writeFileSync)(outPath, text, "utf8");
64728
64978
  return { ok: true, filename, encoding: "utf8", content: text, bytes: Buffer.byteLength(text), savedTo: outPath };
64729
64979
  }
64730
64980
  const buf = await bomToXlsx(bom);
64731
- (0, import_node_fs33.writeFileSync)(outPath, buf);
64981
+ (0, import_node_fs34.writeFileSync)(outPath, buf);
64732
64982
  return { ok: true, filename, encoding: "base64", content: buf.toString("base64"), bytes: buf.length, savedTo: outPath };
64733
64983
  } catch (e) {
64734
64984
  app.log.error({ appId, format, err: e instanceof Error ? e.message : e }, "export-bom: generation failed");
@@ -64798,7 +65048,7 @@ async function startServer() {
64798
65048
  return reply.status(404).send({ ok: false, error: `"${id}" is not installed`, code: "not-installed" });
64799
65049
  }
64800
65050
  const contract = readContract(id);
64801
- if (contract == null && (0, import_node_fs33.existsSync)(contractPath(id))) {
65051
+ if (contract == null && (0, import_node_fs34.existsSync)(contractPath(id))) {
64802
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" });
64803
65053
  }
64804
65054
  if (contract != null) {
@@ -64876,11 +65126,11 @@ async function startServer() {
64876
65126
  if (appExists(id)) {
64877
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 });
64878
65128
  }
64879
- const stageRoot = (0, import_node_fs33.mkdtempSync)((0, import_node_path31.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-"));
64880
65130
  try {
64881
- const stageDir = (0, import_node_path31.join)(stageRoot, id);
64882
- (0, import_node_fs33.mkdirSync)(stageDir);
64883
- (0, import_node_fs33.writeFileSync)((0, import_node_path31.join)(stageDir, `${id}.flo`), content);
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);
64884
65134
  await aware.install(stageDir);
64885
65135
  } catch (err2) {
64886
65136
  try {
@@ -64890,7 +65140,7 @@ async function startServer() {
64890
65140
  const msg = err2 instanceof AwareError ? err2.message : String(err2?.message ?? err2);
64891
65141
  return reply.status(502).send({ ok: false, error: `import failed: ${msg}` });
64892
65142
  } finally {
64893
- (0, import_node_fs33.rmSync)(stageRoot, { recursive: true, force: true });
65143
+ (0, import_node_fs34.rmSync)(stageRoot, { recursive: true, force: true });
64894
65144
  }
64895
65145
  broadcast({ type: "apps-changed", id });
64896
65146
  return { ok: true, id };
@@ -64904,13 +65154,13 @@ async function startServer() {
64904
65154
  }
64905
65155
  const inputs = appData.inputs.map((i) => ({ name: i.name, type: i.type }));
64906
65156
  const baked = bakeFloSource(appData.source.text, inputs);
64907
- const tmpRoot = (0, import_node_fs33.mkdtempSync)((0, import_node_path31.join)((0, import_node_os21.tmpdir)(), "floless-bake-"));
64908
- const backupDir2 = (0, import_node_path31.join)(tmpRoot, `${id}-backup`);
64909
- const bakeDir = (0, import_node_path31.join)(tmpRoot, id);
64910
- (0, import_node_fs33.cpSync)(appDir(id), backupDir2, { recursive: true });
64911
- (0, import_node_fs33.cpSync)(appDir(id), bakeDir, { recursive: true });
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 });
64912
65162
  const floName = appData.source.path.split(/[\\/]/).pop();
64913
- (0, import_node_fs33.writeFileSync)((0, import_node_path31.join)(bakeDir, floName), baked);
65163
+ (0, import_node_fs34.writeFileSync)((0, import_node_path32.join)(bakeDir, floName), baked);
64914
65164
  let appInstalled2 = true;
64915
65165
  try {
64916
65166
  await aware.uninstall(id);
@@ -64932,17 +65182,17 @@ async function startServer() {
64932
65182
  throw installErr;
64933
65183
  }
64934
65184
  try {
64935
- await aware.compile((0, import_node_path31.join)(appDir(id), floName));
65185
+ await aware.compile((0, import_node_path32.join)(appDir(id), floName));
64936
65186
  } catch (compileErr) {
64937
65187
  app.log.warn({ id, compileErr: String(compileErr) }, "bake: post-install recompile failed (app baked but may need a manual Compile)");
64938
65188
  }
64939
65189
  broadcast({ type: "baked", id });
64940
65190
  return { ok: true, id, agent: id, inputs };
64941
65191
  } finally {
64942
- if (appInstalled2) (0, import_node_fs33.rmSync)(tmpRoot, { recursive: true, force: true });
65192
+ if (appInstalled2) (0, import_node_fs34.rmSync)(tmpRoot, { recursive: true, force: true });
64943
65193
  }
64944
65194
  });
64945
- const graftAgentsDir = () => (0, import_node_path31.join)((0, import_node_os21.homedir)(), ".aware", "agents");
65195
+ const graftAgentsDir = () => (0, import_node_path32.join)((0, import_node_os21.homedir)(), ".aware", "agents");
64946
65196
  app.post("/api/graft/match", async (req, reply) => {
64947
65197
  const { glob } = req.body ?? {};
64948
65198
  if (!glob) return reply.status(400).send({ ok: false, error: "glob required" });
@@ -64959,7 +65209,7 @@ async function startServer() {
64959
65209
  if (!sourceKind || !sourceRef) {
64960
65210
  return reply.status(400).send({ ok: false, error: "sourceKind and sourceRef required" });
64961
65211
  }
64962
- const tempHome = (0, import_node_fs33.mkdtempSync)((0, import_node_path31.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-"));
64963
65213
  let result;
64964
65214
  try {
64965
65215
  result = await aware.build({
@@ -64972,19 +65222,19 @@ async function startServer() {
64972
65222
  awareHome: tempHome
64973
65223
  });
64974
65224
  } catch (err2) {
64975
- (0, import_node_fs33.rmSync)(tempHome, { recursive: true, force: true });
65225
+ (0, import_node_fs34.rmSync)(tempHome, { recursive: true, force: true });
64976
65226
  const msg = err2 instanceof AwareError ? err2.message : String(err2?.message ?? err2);
64977
65227
  return reply.status(422).send({ ok: false, error: msg });
64978
65228
  }
64979
65229
  const manifest = readStagedManifest(result.agentDir);
64980
65230
  if (!manifest) {
64981
- (0, import_node_fs33.rmSync)(tempHome, { recursive: true, force: true });
65231
+ (0, import_node_fs34.rmSync)(tempHome, { recursive: true, force: true });
64982
65232
  return reply.status(502).send({ ok: false, error: `build produced output at ${result.agentDir} but no manifest.yaml` });
64983
65233
  }
64984
65234
  const token = (0, import_node_crypto9.randomUUID)();
64985
65235
  registerStage(token, tempHome, result.agentId);
64986
65236
  const preview = buildPreview(manifest, sourceKind, sourceRef, token);
64987
- if ((0, import_node_fs33.existsSync)((0, import_node_path31.join)(graftAgentsDir(), result.agentId))) {
65237
+ if ((0, import_node_fs34.existsSync)((0, import_node_path32.join)(graftAgentsDir(), result.agentId))) {
64988
65238
  preview.warnings.unshift(`An agent named "${result.agentId}" is already installed \u2014 creating it will overwrite it.`);
64989
65239
  }
64990
65240
  return { ok: true, preview };
@@ -65003,7 +65253,7 @@ async function startServer() {
65003
65253
  registerStage(stagedRef, stage.tempDir, stage.agentId);
65004
65254
  return reply.status(409).send({ ok: false, error: err2.message, agentId: stage.agentId, collision: true });
65005
65255
  }
65006
- (0, import_node_fs33.rmSync)(stage.tempDir, { recursive: true, force: true });
65256
+ (0, import_node_fs34.rmSync)(stage.tempDir, { recursive: true, force: true });
65007
65257
  throw err2;
65008
65258
  }
65009
65259
  broadcast({ type: "grafted", id: stage.agentId });
@@ -65274,11 +65524,11 @@ async function startServer() {
65274
65524
  app.get("/api/requests/:id/snapshot/:n", async (req, reply) => {
65275
65525
  const n = Number.parseInt(req.params.n, 10);
65276
65526
  const p = Number.isInteger(n) ? snapshotPathFor(req.params.id, n) : null;
65277
- if (!p || !(0, import_node_fs33.existsSync)(p)) return reply.status(404).send({ ok: false, error: "snapshot not found" });
65527
+ if (!p || !(0, import_node_fs34.existsSync)(p)) return reply.status(404).send({ ok: false, error: "snapshot not found" });
65278
65528
  const ext = p.split(".").pop().toLowerCase();
65279
65529
  reply.header("Content-Type", ext === "png" ? "image/png" : ext === "webp" ? "image/webp" : ext === "pdf" ? "application/pdf" : "image/jpeg");
65280
65530
  reply.header("Cache-Control", "no-store");
65281
- return (0, import_node_fs33.readFileSync)(p);
65531
+ return (0, import_node_fs34.readFileSync)(p);
65282
65532
  });
65283
65533
  app.post(
65284
65534
  "/api/tweak",
@@ -65360,6 +65610,63 @@ async function startServer() {
65360
65610
  }
65361
65611
  }
65362
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
+ );
65363
65670
  app.post("/api/use-template", async (req, reply) => {
65364
65671
  const { appId, templateId } = req.body ?? {};
65365
65672
  if (!appId || !templateId) return reply.status(400).send({ ok: false, error: "appId and templateId required" });
@@ -65447,7 +65754,7 @@ async function startServer() {
65447
65754
  warn(`last-run-status:${ref} \u2014 trace exists but couldn't be parsed (corrupt/truncated)`);
65448
65755
  let finishedAt2 = null;
65449
65756
  try {
65450
- finishedAt2 = (0, import_node_fs33.statSync)(latest.path).mtime.toISOString();
65757
+ finishedAt2 = (0, import_node_fs34.statSync)(latest.path).mtime.toISOString();
65451
65758
  } catch {
65452
65759
  finishedAt2 = null;
65453
65760
  }
@@ -65457,7 +65764,7 @@ async function startServer() {
65457
65764
  let finishedAt = typeof runEnd?.ts === "string" ? runEnd.ts : null;
65458
65765
  if (!finishedAt) {
65459
65766
  try {
65460
- finishedAt = (0, import_node_fs33.statSync)(latest.path).mtime.toISOString();
65767
+ finishedAt = (0, import_node_fs34.statSync)(latest.path).mtime.toISOString();
65461
65768
  } catch {
65462
65769
  finishedAt = null;
65463
65770
  }