@floless/app 0.52.0 → 0.54.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.
- package/dist/floless-server.cjs +194 -123
- package/dist/skills/floless-app-queue/SKILL.md +1 -1
- package/dist/skills/floless-app-steel-model/SKILL.md +10 -6
- package/dist/skills/floless-app-steel-takeoff/SKILL.md +8 -8
- package/dist/skills/floless-app-tweak-contract/SKILL.md +2 -2
- package/dist/skills/floless-app-workflows/SKILL.md +24 -1
- package/dist/templates/{steel-takeoff.flo → steel-model.flo} +46 -3
- package/dist/web/aware.js +1 -1
- package/dist/web/steel-3d-view.js +1 -1
- package/dist/web/steel-editor.html +2 -2
- package/package.json +1 -1
package/dist/floless-server.cjs
CHANGED
|
@@ -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:
|
|
5492
|
+
var { join: join29 } = 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"] ||
|
|
5543
|
+
const toExecute = bundlerOverrides["thread-stream-worker"] || join29(__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: existsSync24 } = require("node:fs");
|
|
6010
6010
|
var getCallers = require_caller();
|
|
6011
|
-
var { join:
|
|
6011
|
+
var { join: join29, 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"] ||
|
|
6164
|
+
target = bundlerOverrides["pino-worker"] || join29(__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"] ||
|
|
6182
|
+
target = bundlerOverrides["pino-worker"] || join29(__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
|
|
6205
|
+
return join29(__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
|
|
7185
|
+
let join29 = ",";
|
|
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
|
-
|
|
7199
|
+
join29 = `,
|
|
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 +=
|
|
7207
|
+
res += join29;
|
|
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 += `${
|
|
7213
|
+
res += `${join29}"... ${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
|
-
|
|
7234
|
+
join29 = `,
|
|
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 =
|
|
7248
|
+
separator = join29;
|
|
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 =
|
|
7254
|
+
separator = join29;
|
|
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
|
|
7295
|
+
let join29 = ",";
|
|
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
|
-
|
|
7308
|
+
join29 = `,
|
|
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 +=
|
|
7316
|
+
res += join29;
|
|
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 += `${
|
|
7322
|
+
res += `${join29}"... ${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
|
-
|
|
7335
|
+
join29 = `,
|
|
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 =
|
|
7344
|
+
separator = join29;
|
|
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
|
|
7402
|
+
const join30 = `,
|
|
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 +=
|
|
7409
|
+
res2 += join30;
|
|
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 += `${
|
|
7415
|
+
res2 += `${join30}"... ${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
|
|
7431
|
+
const join29 = `,
|
|
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,
|
|
7437
|
+
res += stringifyTypedArray(value, join29, maximumBreadth);
|
|
7438
7438
|
keys = keys.slice(value.length);
|
|
7439
7439
|
maximumPropertiesToStringify -= value.length;
|
|
7440
|
-
separator =
|
|
7440
|
+
separator = join29;
|
|
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 =
|
|
7451
|
+
separator = join29;
|
|
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 =
|
|
7457
|
+
separator = join29;
|
|
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 extname2 = path.extname;
|
|
41994
|
-
var
|
|
41994
|
+
var join29 = 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(
|
|
42081
|
+
path2 = normalize2(join29(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 =
|
|
42364
|
+
const p = join29(path2, index);
|
|
42365
42365
|
const { error, stat: stat4 } = await tryStat(p);
|
|
42366
42366
|
if (error) {
|
|
42367
42367
|
err2 = error;
|
|
@@ -50827,8 +50827,8 @@ var import_node_readline2 = require("node:readline");
|
|
|
50827
50827
|
|
|
50828
50828
|
// index.ts
|
|
50829
50829
|
var import_node_url5 = require("node:url");
|
|
50830
|
-
var
|
|
50831
|
-
var
|
|
50830
|
+
var import_node_path27 = require("node:path");
|
|
50831
|
+
var import_node_os19 = require("node:os");
|
|
50832
50832
|
var import_node_fs29 = require("node:fs");
|
|
50833
50833
|
var import_node_child_process7 = require("node:child_process");
|
|
50834
50834
|
|
|
@@ -52856,7 +52856,7 @@ function appVersion() {
|
|
|
52856
52856
|
return resolveVersion({
|
|
52857
52857
|
isSea: isSea2(),
|
|
52858
52858
|
sqVersionXml: readSqVersionXml(),
|
|
52859
|
-
define: true ? "0.
|
|
52859
|
+
define: true ? "0.54.0" : void 0,
|
|
52860
52860
|
pkgVersion: readPkgVersion()
|
|
52861
52861
|
});
|
|
52862
52862
|
}
|
|
@@ -52866,7 +52866,7 @@ function resolveChannel(s) {
|
|
|
52866
52866
|
return "dev";
|
|
52867
52867
|
}
|
|
52868
52868
|
function appChannel() {
|
|
52869
|
-
return resolveChannel({ isSea: isSea2(), define: true ? "0.
|
|
52869
|
+
return resolveChannel({ isSea: isSea2(), define: true ? "0.54.0" : void 0 });
|
|
52870
52870
|
}
|
|
52871
52871
|
|
|
52872
52872
|
// workflow-update.ts
|
|
@@ -53893,7 +53893,7 @@ function contractToScene(contractInput) {
|
|
|
53893
53893
|
color: pickColor(profile, i, contract)
|
|
53894
53894
|
}));
|
|
53895
53895
|
return {
|
|
53896
|
-
scene: { meta: { name: contract.title || "Steel
|
|
53896
|
+
scene: { meta: { name: contract.title || "Steel Model", units: "mm", up: "z" }, groups, elements },
|
|
53897
53897
|
skipped
|
|
53898
53898
|
};
|
|
53899
53899
|
}
|
|
@@ -53958,6 +53958,10 @@ function contractToBom(contractInput) {
|
|
|
53958
53958
|
};
|
|
53959
53959
|
}
|
|
53960
53960
|
|
|
53961
|
+
// bom-export.ts
|
|
53962
|
+
var import_node_os12 = require("node:os");
|
|
53963
|
+
var import_node_path15 = require("node:path");
|
|
53964
|
+
|
|
53961
53965
|
// node_modules/write-excel-file/modules/export/writeXlsxFileNode.js
|
|
53962
53966
|
var import_node_fs18 = __toESM(require("node:fs"), 1);
|
|
53963
53967
|
|
|
@@ -58155,6 +58159,10 @@ function pipe(readableStream, writableStream) {
|
|
|
58155
58159
|
}
|
|
58156
58160
|
|
|
58157
58161
|
// bom-export.ts
|
|
58162
|
+
function bomExportPath(appId, format) {
|
|
58163
|
+
const root = process.env.FLOLESS_HOME ?? (0, import_node_path15.join)((0, import_node_os12.homedir)(), ".floless");
|
|
58164
|
+
return (0, import_node_path15.join)(root, "exports", appId, `${appId}-bom.${format}`);
|
|
58165
|
+
}
|
|
58158
58166
|
function csvField(v) {
|
|
58159
58167
|
let s = String(v);
|
|
58160
58168
|
if (typeof v === "string" && /^[=+\-@\t\r]/.test(s)) s = `'${s}`;
|
|
@@ -58182,7 +58190,7 @@ async function bomToXlsx(bom) {
|
|
|
58182
58190
|
|
|
58183
58191
|
// scene-bake.ts
|
|
58184
58192
|
var import_node_fs19 = require("node:fs");
|
|
58185
|
-
var
|
|
58193
|
+
var import_node_path16 = require("node:path");
|
|
58186
58194
|
var import_yaml6 = __toESM(require_dist6(), 1);
|
|
58187
58195
|
function bakeSceneIntoApp(sourcePath, scene, agent = "viewer-3d") {
|
|
58188
58196
|
const doc = (0, import_yaml6.parseDocument)((0, import_node_fs19.readFileSync)(sourcePath, "utf8"));
|
|
@@ -58210,7 +58218,7 @@ function bakeNodeConfigById(sourcePath, nodeId, patch) {
|
|
|
58210
58218
|
}
|
|
58211
58219
|
function writeViewer3dApp(dir, appId, scene) {
|
|
58212
58220
|
(0, import_node_fs19.mkdirSync)(dir, { recursive: true });
|
|
58213
|
-
const flo = (0,
|
|
58221
|
+
const flo = (0, import_node_path16.join)(dir, `${appId}.flo`);
|
|
58214
58222
|
(0, import_node_fs19.writeFileSync)(flo, [
|
|
58215
58223
|
`app: ${appId}`,
|
|
58216
58224
|
"version: 0.1.0",
|
|
@@ -58233,7 +58241,7 @@ function writeViewer3dApp(dir, appId, scene) {
|
|
|
58233
58241
|
}
|
|
58234
58242
|
function writeIfcApp(dir, appId, scene, outputPath) {
|
|
58235
58243
|
(0, import_node_fs19.mkdirSync)(dir, { recursive: true });
|
|
58236
|
-
const flo = (0,
|
|
58244
|
+
const flo = (0, import_node_path16.join)(dir, `${appId}.flo`);
|
|
58237
58245
|
(0, import_node_fs19.writeFileSync)(flo, [
|
|
58238
58246
|
`app: ${appId}`,
|
|
58239
58247
|
"version: 0.1.0",
|
|
@@ -58258,7 +58266,7 @@ function writeIfcApp(dir, appId, scene, outputPath) {
|
|
|
58258
58266
|
}
|
|
58259
58267
|
function writeTeklaApp(dir, appId, scene, teklaVersion = "2026.0") {
|
|
58260
58268
|
(0, import_node_fs19.mkdirSync)(dir, { recursive: true });
|
|
58261
|
-
const flo = (0,
|
|
58269
|
+
const flo = (0, import_node_path16.join)(dir, `${appId}.flo`);
|
|
58262
58270
|
(0, import_node_fs19.writeFileSync)(flo, [
|
|
58263
58271
|
`app: ${appId}`,
|
|
58264
58272
|
"version: 0.1.0",
|
|
@@ -58285,6 +58293,41 @@ function writeTeklaApp(dir, appId, scene, teklaVersion = "2026.0") {
|
|
|
58285
58293
|
return flo;
|
|
58286
58294
|
}
|
|
58287
58295
|
|
|
58296
|
+
// bom-export-bake.ts
|
|
58297
|
+
var EXPORT_NODES = [
|
|
58298
|
+
{ id: "csv-export", format: "csv" },
|
|
58299
|
+
{ id: "xlsx-export", format: "xlsx" }
|
|
58300
|
+
];
|
|
58301
|
+
async function bakeBomExportNodes(sourcePath, appId, contract) {
|
|
58302
|
+
const baked = [];
|
|
58303
|
+
const bom = contract != null ? contractToBom(contract) : null;
|
|
58304
|
+
const bytesFor = {
|
|
58305
|
+
csv: bom ? bomToCsv(bom) : null,
|
|
58306
|
+
xlsx: bom ? (await bomToXlsx(bom)).toString("base64") : null
|
|
58307
|
+
};
|
|
58308
|
+
for (const { id, format } of EXPORT_NODES) {
|
|
58309
|
+
const patch = { path: bomExportPath(appId, format) };
|
|
58310
|
+
const bytes = bytesFor[format];
|
|
58311
|
+
if (bytes != null) patch.bytes = bytes;
|
|
58312
|
+
try {
|
|
58313
|
+
bakeNodeConfigById(sourcePath, id, patch);
|
|
58314
|
+
baked.push(id);
|
|
58315
|
+
} catch (e) {
|
|
58316
|
+
if (!(e instanceof Error && /no node with id/.test(e.message))) throw e;
|
|
58317
|
+
}
|
|
58318
|
+
}
|
|
58319
|
+
return baked;
|
|
58320
|
+
}
|
|
58321
|
+
function exportPathsNeedingBake(nodes, appId) {
|
|
58322
|
+
const out = [];
|
|
58323
|
+
for (const { id, format } of EXPORT_NODES) {
|
|
58324
|
+
const node = nodes.find((n) => n.id === id);
|
|
58325
|
+
if (!node || node.agent !== "file" || node.frozen) continue;
|
|
58326
|
+
if (node.config?.path !== bomExportPath(appId, format)) out.push(id);
|
|
58327
|
+
}
|
|
58328
|
+
return out;
|
|
58329
|
+
}
|
|
58330
|
+
|
|
58288
58331
|
// tekla-version.ts
|
|
58289
58332
|
var import_node_child_process3 = require("node:child_process");
|
|
58290
58333
|
var import_node_util = require("node:util");
|
|
@@ -58322,7 +58365,7 @@ async function teklaVersionToBake() {
|
|
|
58322
58365
|
}
|
|
58323
58366
|
|
|
58324
58367
|
// downstream-bake.ts
|
|
58325
|
-
function bakeDownstream(sourcePath, contract) {
|
|
58368
|
+
async function bakeDownstream(sourcePath, appId, contract) {
|
|
58326
58369
|
const { scene } = contractToScene(contract);
|
|
58327
58370
|
let sceneElements = 0;
|
|
58328
58371
|
try {
|
|
@@ -58339,7 +58382,8 @@ function bakeDownstream(sourcePath, contract) {
|
|
|
58339
58382
|
} catch (e) {
|
|
58340
58383
|
if (!(e instanceof Error && /no node with id "bom"/.test(e.message))) throw e;
|
|
58341
58384
|
}
|
|
58342
|
-
|
|
58385
|
+
const exportNodes = (await bakeBomExportNodes(sourcePath, appId, contract)).length;
|
|
58386
|
+
return { sceneElements, bomRows, exportNodes };
|
|
58343
58387
|
}
|
|
58344
58388
|
|
|
58345
58389
|
// steel-confidence.ts
|
|
@@ -58623,12 +58667,12 @@ function scoreContract2(contractInput) {
|
|
|
58623
58667
|
|
|
58624
58668
|
// app-lifecycle.ts
|
|
58625
58669
|
var import_node_fs22 = require("node:fs");
|
|
58626
|
-
var
|
|
58627
|
-
var
|
|
58670
|
+
var import_node_os14 = require("node:os");
|
|
58671
|
+
var import_node_path19 = require("node:path");
|
|
58628
58672
|
|
|
58629
58673
|
// routines.ts
|
|
58630
58674
|
var import_node_fs21 = require("node:fs");
|
|
58631
|
-
var
|
|
58675
|
+
var import_node_path18 = require("node:path");
|
|
58632
58676
|
|
|
58633
58677
|
// sse.ts
|
|
58634
58678
|
var clients = /* @__PURE__ */ new Set();
|
|
@@ -58666,10 +58710,10 @@ function clientCount() {
|
|
|
58666
58710
|
|
|
58667
58711
|
// trigger-sessions.ts
|
|
58668
58712
|
var import_node_fs20 = require("node:fs");
|
|
58669
|
-
var
|
|
58670
|
-
var
|
|
58713
|
+
var import_node_os13 = require("node:os");
|
|
58714
|
+
var import_node_path17 = require("node:path");
|
|
58671
58715
|
var import_yaml7 = __toESM(require_dist6(), 1);
|
|
58672
|
-
var AGENTS_DIR2 = process.env.AWARE_HOME ? (0,
|
|
58716
|
+
var AGENTS_DIR2 = process.env.AWARE_HOME ? (0, import_node_path17.join)(process.env.AWARE_HOME, "agents") : (0, import_node_path17.join)((0, import_node_os13.homedir)(), ".aware", "agents");
|
|
58673
58717
|
function summarizeFire(data) {
|
|
58674
58718
|
if (!data) return "event";
|
|
58675
58719
|
const type = typeof data.type === "string" ? data.type : "";
|
|
@@ -58686,7 +58730,7 @@ function mapTriggerState(phase) {
|
|
|
58686
58730
|
function isHostBacked(agent, agentsDir = AGENTS_DIR2) {
|
|
58687
58731
|
const safe = (n) => !n.includes("/") && !n.includes("\\") && !n.includes("..");
|
|
58688
58732
|
if (!safe(agent)) return false;
|
|
58689
|
-
const manifestPath = (0,
|
|
58733
|
+
const manifestPath = (0, import_node_path17.join)(agentsDir, agent, "manifest.yaml");
|
|
58690
58734
|
if (!(0, import_node_fs20.existsSync)(manifestPath)) return false;
|
|
58691
58735
|
try {
|
|
58692
58736
|
const doc = (0, import_yaml7.parse)((0, import_node_fs20.readFileSync)(manifestPath, "utf8"));
|
|
@@ -58974,7 +59018,7 @@ var RoutineError = class extends Error {
|
|
|
58974
59018
|
}
|
|
58975
59019
|
status;
|
|
58976
59020
|
};
|
|
58977
|
-
var ROUTINES_FILE = (0,
|
|
59021
|
+
var ROUTINES_FILE = (0, import_node_path18.join)(flolessRoot, "routines.json");
|
|
58978
59022
|
var routines = [];
|
|
58979
59023
|
var loaded = false;
|
|
58980
59024
|
function ensureLoaded() {
|
|
@@ -59440,10 +59484,10 @@ var AppLifecycleError = class extends Error {
|
|
|
59440
59484
|
}
|
|
59441
59485
|
};
|
|
59442
59486
|
function appsDir() {
|
|
59443
|
-
return process.env.AWARE_HOME ? (0,
|
|
59487
|
+
return process.env.AWARE_HOME ? (0, import_node_path19.join)(process.env.AWARE_HOME, "apps") : (0, import_node_path19.join)((0, import_node_os14.homedir)(), ".aware", "apps");
|
|
59444
59488
|
}
|
|
59445
59489
|
function appDirPath(id) {
|
|
59446
|
-
return (0,
|
|
59490
|
+
return (0, import_node_path19.join)(appsDir(), id);
|
|
59447
59491
|
}
|
|
59448
59492
|
function appInstalled(id) {
|
|
59449
59493
|
return APP_ID3.test(id) && (0, import_node_fs22.existsSync)(appDirPath(id));
|
|
@@ -59548,8 +59592,8 @@ function isGatedAwareRoute(url, method) {
|
|
|
59548
59592
|
// autostart.mjs
|
|
59549
59593
|
var import_node_child_process4 = require("node:child_process");
|
|
59550
59594
|
var import_node_fs23 = require("node:fs");
|
|
59551
|
-
var
|
|
59552
|
-
var
|
|
59595
|
+
var import_node_os15 = require("node:os");
|
|
59596
|
+
var import_node_path20 = require("node:path");
|
|
59553
59597
|
|
|
59554
59598
|
// teardown.mjs
|
|
59555
59599
|
var RUN_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
|
@@ -59670,7 +59714,7 @@ function logLine(msg) {
|
|
|
59670
59714
|
function registerAutostart(exePath) {
|
|
59671
59715
|
if (!isWin) return;
|
|
59672
59716
|
const xml = buildAutostartTaskXml(exePath, currentUserId());
|
|
59673
|
-
const tmp = (0,
|
|
59717
|
+
const tmp = (0, import_node_path20.join)((0, import_node_os15.tmpdir)(), `floless-autostart-${process.pid}-${Date.now()}.xml`);
|
|
59674
59718
|
(0, import_node_fs23.writeFileSync)(tmp, "\uFEFF" + xml, { encoding: "utf16le" });
|
|
59675
59719
|
try {
|
|
59676
59720
|
(0, import_node_child_process4.execFileSync)("schtasks", ["/Create", "/TN", TASK_NAME, "/XML", tmp, "/F"], {
|
|
@@ -59720,23 +59764,23 @@ var import_node_crypto6 = require("node:crypto");
|
|
|
59720
59764
|
var import_node_fs25 = require("node:fs");
|
|
59721
59765
|
var import_node_stream3 = require("node:stream");
|
|
59722
59766
|
var import_promises = require("node:stream/promises");
|
|
59723
|
-
var
|
|
59767
|
+
var import_node_path22 = require("node:path");
|
|
59724
59768
|
|
|
59725
59769
|
// post-update-marker.mjs
|
|
59726
59770
|
var import_node_fs24 = require("node:fs");
|
|
59727
|
-
var
|
|
59728
|
-
var
|
|
59771
|
+
var import_node_os16 = require("node:os");
|
|
59772
|
+
var import_node_path21 = require("node:path");
|
|
59729
59773
|
var FRESH_MS = 12e4;
|
|
59730
59774
|
function markerPath() {
|
|
59731
59775
|
const override = (process.env.FLOLESS_POST_UPDATE_MARKER ?? "").trim();
|
|
59732
59776
|
if (override) return override;
|
|
59733
|
-
const root = process.env.FLOLESS_HOME ?? (0,
|
|
59734
|
-
return (0,
|
|
59777
|
+
const root = process.env.FLOLESS_HOME ?? (0, import_node_path21.join)((0, import_node_os16.homedir)(), ".floless");
|
|
59778
|
+
return (0, import_node_path21.join)(root, ".post-update");
|
|
59735
59779
|
}
|
|
59736
59780
|
function legacyMarkerPath() {
|
|
59737
59781
|
if ((process.env.FLOLESS_POST_UPDATE_MARKER ?? "").trim()) return null;
|
|
59738
59782
|
try {
|
|
59739
|
-
return (0,
|
|
59783
|
+
return (0, import_node_path21.join)((0, import_node_path21.dirname)((0, import_node_path21.dirname)(process.execPath)), ".floless-post-update");
|
|
59740
59784
|
} catch {
|
|
59741
59785
|
return null;
|
|
59742
59786
|
}
|
|
@@ -59777,13 +59821,13 @@ function currentVersion() {
|
|
|
59777
59821
|
return appVersion();
|
|
59778
59822
|
}
|
|
59779
59823
|
function installRoot() {
|
|
59780
|
-
return (0,
|
|
59824
|
+
return (0, import_node_path22.dirname)((0, import_node_path22.dirname)(process.execPath));
|
|
59781
59825
|
}
|
|
59782
59826
|
function updateExePath() {
|
|
59783
|
-
return (0,
|
|
59827
|
+
return (0, import_node_path22.join)(installRoot(), "Update.exe");
|
|
59784
59828
|
}
|
|
59785
59829
|
function packagesDir() {
|
|
59786
|
-
return (0,
|
|
59830
|
+
return (0, import_node_path22.join)(installRoot(), "packages");
|
|
59787
59831
|
}
|
|
59788
59832
|
function feedUrl() {
|
|
59789
59833
|
const env2 = (process.env.FLOLESS_UPDATE_URL ?? "").trim().replace(/\/+$/, "");
|
|
@@ -59884,7 +59928,7 @@ async function downloadPackage(asset) {
|
|
|
59884
59928
|
const want = asset.SHA1.toUpperCase();
|
|
59885
59929
|
const dir = packagesDir();
|
|
59886
59930
|
(0, import_node_fs25.mkdirSync)(dir, { recursive: true });
|
|
59887
|
-
const dest = (0,
|
|
59931
|
+
const dest = (0, import_node_path22.join)(dir, asset.FileName);
|
|
59888
59932
|
if ((0, import_node_fs25.existsSync)(dest) && await sha1OfFile(dest) === want) return dest;
|
|
59889
59933
|
const res = await authedFetch(`${feedUrl()}/${encodeURIComponent(asset.FileName)}`, {
|
|
59890
59934
|
redirect: "follow",
|
|
@@ -60140,12 +60184,12 @@ function isTraceCorrupt(events) {
|
|
|
60140
60184
|
|
|
60141
60185
|
// launch.mjs
|
|
60142
60186
|
var import_node_child_process6 = require("node:child_process");
|
|
60143
|
-
var
|
|
60187
|
+
var import_node_path23 = require("node:path");
|
|
60144
60188
|
var import_node_url3 = require("node:url");
|
|
60145
60189
|
var import_node_fs26 = require("node:fs");
|
|
60146
60190
|
var import_node_http = __toESM(require("node:http"), 1);
|
|
60147
60191
|
var import_node_readline = require("node:readline");
|
|
60148
|
-
var __dirname2 = (0,
|
|
60192
|
+
var __dirname2 = (0, import_node_path23.dirname)((0, import_node_url3.fileURLToPath)(__import_meta_url));
|
|
60149
60193
|
var PORT = Number(process.env.PORT ?? 4317);
|
|
60150
60194
|
var HEALTH_URL = `http://127.0.0.1:${PORT}/api/health`;
|
|
60151
60195
|
var BROWSER_URL = `http://floless.localhost:${PORT}`;
|
|
@@ -60217,7 +60261,7 @@ async function waitHealthy(timeoutMs = 3e4) {
|
|
|
60217
60261
|
function resolveServerStart() {
|
|
60218
60262
|
const packaged = /flolessapp\.exe$/i.test(process.execPath);
|
|
60219
60263
|
if (packaged) return { cmd: process.execPath, args: ["--serve"], shell: false };
|
|
60220
|
-
const bundle = (0,
|
|
60264
|
+
const bundle = (0, import_node_path23.join)(__dirname2, "dist", "floless-server.cjs");
|
|
60221
60265
|
if ((0, import_node_fs26.existsSync)(bundle)) return { cmd: process.execPath, args: [bundle, "--serve"], shell: false };
|
|
60222
60266
|
return { cmd: "npm", args: ["run", "start"], shell: isWin2 };
|
|
60223
60267
|
}
|
|
@@ -60371,8 +60415,8 @@ function taskkillArgs(pid, { tree = true } = {}) {
|
|
|
60371
60415
|
}
|
|
60372
60416
|
function killSupervisor({ tree = true } = {}) {
|
|
60373
60417
|
if (!isWin2) return;
|
|
60374
|
-
const isNpmChannel = /^node(\.exe)?$/i.test((0,
|
|
60375
|
-
const scriptMatch = isNpmChannel ? (0,
|
|
60418
|
+
const isNpmChannel = /^node(\.exe)?$/i.test((0, import_node_path23.basename)(process.execPath));
|
|
60419
|
+
const scriptMatch = isNpmChannel ? (0, import_node_path23.basename)((0, import_node_url3.fileURLToPath)(__import_meta_url)) : void 0;
|
|
60376
60420
|
const realExe = resolveRealInstallExe(process.execPath);
|
|
60377
60421
|
const exeMatch = realExe === process.execPath ? process.execPath : [process.execPath, realExe];
|
|
60378
60422
|
const pids = supervisorPidsToKill(enumerateProcesses(), process.pid, exeMatch, scriptMatch);
|
|
@@ -60537,7 +60581,7 @@ async function runAction(arg, flagArgv = [], selfVersion = null) {
|
|
|
60537
60581
|
}
|
|
60538
60582
|
await action(parseTeardownFlags(flagArgv));
|
|
60539
60583
|
}
|
|
60540
|
-
var entry = (0,
|
|
60584
|
+
var entry = (0, import_node_path23.basename)(process.argv[1] ?? "").toLowerCase();
|
|
60541
60585
|
if (entry === "launch.mjs") {
|
|
60542
60586
|
runAction(process.argv[2], process.argv.slice(3)).catch((e) => {
|
|
60543
60587
|
log(`error: ${e?.message ?? e}`);
|
|
@@ -60612,8 +60656,8 @@ function awareUpgradeBlockReason(s) {
|
|
|
60612
60656
|
|
|
60613
60657
|
// skill-sync.ts
|
|
60614
60658
|
var import_node_fs27 = require("node:fs");
|
|
60615
|
-
var
|
|
60616
|
-
var
|
|
60659
|
+
var import_node_os17 = require("node:os");
|
|
60660
|
+
var import_node_path24 = require("node:path");
|
|
60617
60661
|
var import_node_url4 = require("node:url");
|
|
60618
60662
|
var import_yaml8 = __toESM(require_dist6(), 1);
|
|
60619
60663
|
|
|
@@ -60636,7 +60680,7 @@ var PRODUCT_SKILLS = [
|
|
|
60636
60680
|
"floless-app-steel-takeoff",
|
|
60637
60681
|
// read a steel drawing into an editable steel.takeoff/v1 contract (the Steel Takeoff reader)
|
|
60638
60682
|
"floless-app-tweak-contract",
|
|
60639
|
-
// handle a tweak-contract request from the
|
|
60683
|
+
// handle a tweak-contract request from the Steel Model contract editor (Slice 2 AI round-trip)
|
|
60640
60684
|
"floless-app-ui",
|
|
60641
60685
|
// compose Custom Panels (~/.floless/ui/extensions.json) for the Dashboard
|
|
60642
60686
|
"floless-app-workflows",
|
|
@@ -60650,12 +60694,12 @@ function selectShippedSkillNames(names) {
|
|
|
60650
60694
|
}
|
|
60651
60695
|
|
|
60652
60696
|
// skill-sync.ts
|
|
60653
|
-
var __dirname3 = (0,
|
|
60697
|
+
var __dirname3 = (0, import_node_path24.dirname)((0, import_node_url4.fileURLToPath)(__import_meta_url));
|
|
60654
60698
|
function bundledSkillsRoot() {
|
|
60655
60699
|
const candidates = [
|
|
60656
|
-
(0,
|
|
60657
|
-
(0,
|
|
60658
|
-
(0,
|
|
60700
|
+
(0, import_node_path24.join)(__dirname3, "skills"),
|
|
60701
|
+
(0, import_node_path24.join)((0, import_node_path24.dirname)(process.execPath), "skills"),
|
|
60702
|
+
(0, import_node_path24.join)(__dirname3, "..", ".claude", "skills")
|
|
60659
60703
|
];
|
|
60660
60704
|
return candidates.find((p) => (0, import_node_fs27.existsSync)(p)) ?? null;
|
|
60661
60705
|
}
|
|
@@ -60664,11 +60708,11 @@ function targetConfigDirs() {
|
|
|
60664
60708
|
if (override) {
|
|
60665
60709
|
return override.split(";").map((d) => d.trim()).filter(Boolean).map((dir) => ({ runtime: "custom", dir }));
|
|
60666
60710
|
}
|
|
60667
|
-
const home = (0,
|
|
60711
|
+
const home = (0, import_node_os17.homedir)();
|
|
60668
60712
|
return [
|
|
60669
|
-
{ runtime: "claude", dir: (0,
|
|
60670
|
-
{ runtime: "codex", dir: (0,
|
|
60671
|
-
{ runtime: "opencode", dir: (0,
|
|
60713
|
+
{ runtime: "claude", dir: (0, import_node_path24.join)(home, ".claude") },
|
|
60714
|
+
{ runtime: "codex", dir: (0, import_node_path24.join)(home, ".codex") },
|
|
60715
|
+
{ runtime: "opencode", dir: (0, import_node_path24.join)(home, ".opencode") }
|
|
60672
60716
|
];
|
|
60673
60717
|
}
|
|
60674
60718
|
function skillVersion(skillMdPath) {
|
|
@@ -60719,7 +60763,7 @@ function bundledSkills(root) {
|
|
|
60719
60763
|
}
|
|
60720
60764
|
const out = [];
|
|
60721
60765
|
for (const name of entries) {
|
|
60722
|
-
const dir = (0,
|
|
60766
|
+
const dir = (0, import_node_path24.join)(root, name);
|
|
60723
60767
|
let isDir = false;
|
|
60724
60768
|
try {
|
|
60725
60769
|
isDir = (0, import_node_fs27.statSync)(dir).isDirectory();
|
|
@@ -60727,7 +60771,7 @@ function bundledSkills(root) {
|
|
|
60727
60771
|
isDir = false;
|
|
60728
60772
|
}
|
|
60729
60773
|
if (!isDir) continue;
|
|
60730
|
-
const v = skillVersion((0,
|
|
60774
|
+
const v = skillVersion((0, import_node_path24.join)(dir, "SKILL.md"));
|
|
60731
60775
|
if (!v) continue;
|
|
60732
60776
|
out.push({ name, dir, version: v });
|
|
60733
60777
|
}
|
|
@@ -60741,10 +60785,10 @@ function syncSkills() {
|
|
|
60741
60785
|
if (!skills.length) return results;
|
|
60742
60786
|
for (const { runtime, dir: cfg } of targetConfigDirs()) {
|
|
60743
60787
|
if (!(0, import_node_fs27.existsSync)(cfg)) continue;
|
|
60744
|
-
const skillsDir = (0,
|
|
60788
|
+
const skillsDir = (0, import_node_path24.join)(cfg, "skills");
|
|
60745
60789
|
for (const s of skills) {
|
|
60746
|
-
const dest = (0,
|
|
60747
|
-
const installedMd = (0,
|
|
60790
|
+
const dest = (0, import_node_path24.join)(skillsDir, s.name);
|
|
60791
|
+
const installedMd = (0, import_node_path24.join)(dest, "SKILL.md");
|
|
60748
60792
|
const installed = (0, import_node_fs27.existsSync)(installedMd) ? skillVersion(installedMd) : null;
|
|
60749
60793
|
const action = decideAction(installed, s.version);
|
|
60750
60794
|
if (action === "installed" || action === "updated") {
|
|
@@ -60763,8 +60807,8 @@ function syncSkills() {
|
|
|
60763
60807
|
}
|
|
60764
60808
|
|
|
60765
60809
|
// watch.ts
|
|
60766
|
-
var
|
|
60767
|
-
var
|
|
60810
|
+
var import_node_os18 = require("node:os");
|
|
60811
|
+
var import_node_path26 = require("node:path");
|
|
60768
60812
|
var import_node_fs28 = require("node:fs");
|
|
60769
60813
|
|
|
60770
60814
|
// node_modules/chokidar/esm/index.js
|
|
@@ -60776,7 +60820,7 @@ var sysPath2 = __toESM(require("path"), 1);
|
|
|
60776
60820
|
// node_modules/readdirp/esm/index.js
|
|
60777
60821
|
var import_promises2 = require("node:fs/promises");
|
|
60778
60822
|
var import_node_stream4 = require("node:stream");
|
|
60779
|
-
var
|
|
60823
|
+
var import_node_path25 = require("node:path");
|
|
60780
60824
|
var EntryTypes = {
|
|
60781
60825
|
FILE_TYPE: "files",
|
|
60782
60826
|
DIR_TYPE: "directories",
|
|
@@ -60851,7 +60895,7 @@ var ReaddirpStream = class extends import_node_stream4.Readable {
|
|
|
60851
60895
|
this._wantsDir = type ? DIR_TYPES.has(type) : false;
|
|
60852
60896
|
this._wantsFile = type ? FILE_TYPES.has(type) : false;
|
|
60853
60897
|
this._wantsEverything = type === EntryTypes.EVERYTHING_TYPE;
|
|
60854
|
-
this._root = (0,
|
|
60898
|
+
this._root = (0, import_node_path25.resolve)(root);
|
|
60855
60899
|
this._isDirent = !opts.alwaysStat;
|
|
60856
60900
|
this._statsProp = this._isDirent ? "dirent" : "stats";
|
|
60857
60901
|
this._rdOptions = { encoding: "utf8", withFileTypes: this._isDirent };
|
|
@@ -60922,8 +60966,8 @@ var ReaddirpStream = class extends import_node_stream4.Readable {
|
|
|
60922
60966
|
let entry2;
|
|
60923
60967
|
const basename5 = this._isDirent ? dirent.name : dirent;
|
|
60924
60968
|
try {
|
|
60925
|
-
const fullPath = (0,
|
|
60926
|
-
entry2 = { path: (0,
|
|
60969
|
+
const fullPath = (0, import_node_path25.resolve)((0, import_node_path25.join)(path, basename5));
|
|
60970
|
+
entry2 = { path: (0, import_node_path25.relative)(this._root, fullPath), fullPath, basename: basename5 };
|
|
60927
60971
|
entry2[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
60928
60972
|
} catch (err2) {
|
|
60929
60973
|
this._onError(err2);
|
|
@@ -60957,7 +61001,7 @@ var ReaddirpStream = class extends import_node_stream4.Readable {
|
|
|
60957
61001
|
}
|
|
60958
61002
|
if (entryRealPathStats.isDirectory()) {
|
|
60959
61003
|
const len2 = entryRealPath.length;
|
|
60960
|
-
if (full.startsWith(entryRealPath) && full.substr(len2, 1) ===
|
|
61004
|
+
if (full.startsWith(entryRealPath) && full.substr(len2, 1) === import_node_path25.sep) {
|
|
60961
61005
|
const recursiveError = new Error(`Circular symlink detected: "${full}" points to "${entryRealPath}"`);
|
|
60962
61006
|
recursiveError.code = RECURSIVE_ERROR_CODE;
|
|
60963
61007
|
return this._onError(recursiveError);
|
|
@@ -62465,19 +62509,19 @@ function appIdFromLogPath(path) {
|
|
|
62465
62509
|
return i >= 0 && parts[i + 1] ? parts[i + 1] : null;
|
|
62466
62510
|
}
|
|
62467
62511
|
function samePath(a, b) {
|
|
62468
|
-
const ra = (0,
|
|
62469
|
-
const rb = (0,
|
|
62512
|
+
const ra = (0, import_node_path26.resolve)(a);
|
|
62513
|
+
const rb = (0, import_node_path26.resolve)(b);
|
|
62470
62514
|
return process.platform === "win32" ? ra.toLowerCase() === rb.toLowerCase() : ra === rb;
|
|
62471
62515
|
}
|
|
62472
62516
|
function underDir(path, dir) {
|
|
62473
|
-
const rp = (0,
|
|
62474
|
-
const rd = (0,
|
|
62517
|
+
const rp = (0, import_node_path26.resolve)(path);
|
|
62518
|
+
const rd = (0, import_node_path26.resolve)(dir);
|
|
62475
62519
|
const [p, d] = process.platform === "win32" ? [rp.toLowerCase(), rd.toLowerCase()] : [rp, rd];
|
|
62476
|
-
return p === d || p.startsWith(d +
|
|
62520
|
+
return p === d || p.startsWith(d + import_node_path26.sep);
|
|
62477
62521
|
}
|
|
62478
62522
|
function startWatcher() {
|
|
62479
|
-
const awareDir = process.env.AWARE_HOME ?? (0,
|
|
62480
|
-
const credentialsDir = (0,
|
|
62523
|
+
const awareDir = process.env.AWARE_HOME ?? (0, import_node_path26.join)((0, import_node_os18.homedir)(), ".aware");
|
|
62524
|
+
const credentialsDir = (0, import_node_path26.join)(awareDir, "credentials");
|
|
62481
62525
|
if (!(0, import_node_fs28.existsSync)(credentialsDir)) {
|
|
62482
62526
|
try {
|
|
62483
62527
|
(0, import_node_fs28.mkdirSync)(credentialsDir, { recursive: true });
|
|
@@ -62490,7 +62534,7 @@ function startWatcher() {
|
|
|
62490
62534
|
} catch {
|
|
62491
62535
|
}
|
|
62492
62536
|
}
|
|
62493
|
-
const targets = ["apps", "logs", "credentials"].map((d) => (0,
|
|
62537
|
+
const targets = ["apps", "logs", "credentials"].map((d) => (0, import_node_path26.join)(awareDir, d)).filter((p) => (0, import_node_fs28.existsSync)(p));
|
|
62494
62538
|
if ((0, import_node_fs28.existsSync)(uiDir)) targets.push(uiDir);
|
|
62495
62539
|
if (targets.length === 0) {
|
|
62496
62540
|
return null;
|
|
@@ -62524,7 +62568,7 @@ function startWatcher() {
|
|
|
62524
62568
|
const id = appIdFromLogPath(path);
|
|
62525
62569
|
if (!id) return;
|
|
62526
62570
|
try {
|
|
62527
|
-
broadcast({ type: "trace-file", id, runId: path.split(
|
|
62571
|
+
broadcast({ type: "trace-file", id, runId: path.split(import_node_path26.sep).pop()?.replace(/\.jsonl$/, "") ?? null, events: parseTrace((0, import_node_fs28.readFileSync)(path, "utf8")) });
|
|
62528
62572
|
} catch {
|
|
62529
62573
|
}
|
|
62530
62574
|
}
|
|
@@ -62533,10 +62577,10 @@ function startWatcher() {
|
|
|
62533
62577
|
}
|
|
62534
62578
|
|
|
62535
62579
|
// index.ts
|
|
62536
|
-
var __dirname4 = (0,
|
|
62537
|
-
var WEB_ROOT = [(0,
|
|
62580
|
+
var __dirname4 = (0, import_node_path27.dirname)((0, import_node_url5.fileURLToPath)(__import_meta_url));
|
|
62581
|
+
var WEB_ROOT = [(0, import_node_path27.join)(__dirname4, "web"), (0, import_node_path27.join)((0, import_node_path27.dirname)(process.execPath), "web"), (0, import_node_path27.join)(__dirname4, "..", "web")].find(
|
|
62538
62582
|
(p) => (0, import_node_fs29.existsSync)(p)
|
|
62539
|
-
) ?? (0,
|
|
62583
|
+
) ?? (0, import_node_path27.join)(__dirname4, "..", "web");
|
|
62540
62584
|
var PORT2 = Number(process.env.PORT ?? 4317);
|
|
62541
62585
|
var HOST = "127.0.0.1";
|
|
62542
62586
|
var crashHandlersInstalled = false;
|
|
@@ -62860,12 +62904,12 @@ async function startServer() {
|
|
|
62860
62904
|
_wfUpdating.add(id);
|
|
62861
62905
|
const bdir = backupDir(id, fromVersion, Date.now());
|
|
62862
62906
|
try {
|
|
62863
|
-
(0, import_node_fs29.mkdirSync)((0,
|
|
62907
|
+
(0, import_node_fs29.mkdirSync)((0, import_node_path27.dirname)(bdir), { recursive: true });
|
|
62864
62908
|
(0, import_node_fs29.cpSync)(appDirPath2, bdir, { recursive: true });
|
|
62865
62909
|
(0, import_node_fs29.cpSync)(t.path, srcPath);
|
|
62866
62910
|
if (contract != null) {
|
|
62867
62911
|
bakeContractIntoApp(srcPath, contract);
|
|
62868
|
-
bakeDownstream(srcPath, contract);
|
|
62912
|
+
await bakeDownstream(srcPath, id, contract);
|
|
62869
62913
|
}
|
|
62870
62914
|
await aware.compile(srcPath);
|
|
62871
62915
|
broadcast({ type: "apps-changed", id });
|
|
@@ -63027,7 +63071,7 @@ async function startServer() {
|
|
|
63027
63071
|
const sourcePath = readApp(req.params.appId).source.path;
|
|
63028
63072
|
try {
|
|
63029
63073
|
bakeContractIntoApp(sourcePath, doc);
|
|
63030
|
-
bakeDownstream(sourcePath, doc);
|
|
63074
|
+
await bakeDownstream(sourcePath, req.params.appId, doc);
|
|
63031
63075
|
} catch (e) {
|
|
63032
63076
|
return reply.status(422).send({ ok: false, error: e instanceof Error ? e.message : "bake failed" });
|
|
63033
63077
|
}
|
|
@@ -63146,7 +63190,7 @@ async function startServer() {
|
|
|
63146
63190
|
}
|
|
63147
63191
|
const companionId = `${req.params.appId}-ifc`;
|
|
63148
63192
|
const filename = `${req.params.appId}.ifc`;
|
|
63149
|
-
const outPath = (0,
|
|
63193
|
+
const outPath = (0, import_node_path27.join)(appPath(companionId), filename);
|
|
63150
63194
|
let flo;
|
|
63151
63195
|
try {
|
|
63152
63196
|
flo = writeIfcApp(appPath(companionId), companionId, scene, outPath);
|
|
@@ -63248,10 +63292,9 @@ async function startServer() {
|
|
|
63248
63292
|
if (bom.rows.filter((r) => r.Profile !== "TOTAL").length === 0) {
|
|
63249
63293
|
return reply.status(422).send({ ok: false, error: "no priced members to export \u2014 every member is an RFI (no AISC size / weight yet)" });
|
|
63250
63294
|
}
|
|
63251
|
-
const
|
|
63252
|
-
const dir = (0,
|
|
63295
|
+
const outPath = bomExportPath(appId, format);
|
|
63296
|
+
const dir = (0, import_node_path27.dirname)(outPath);
|
|
63253
63297
|
const filename = `${appId}-bom.${format}`;
|
|
63254
|
-
const outPath = (0, import_node_path26.join)(dir, filename);
|
|
63255
63298
|
try {
|
|
63256
63299
|
(0, import_node_fs29.mkdirSync)(dir, { recursive: true });
|
|
63257
63300
|
if (format === "csv") {
|
|
@@ -63354,11 +63397,11 @@ async function startServer() {
|
|
|
63354
63397
|
if (appExists(id)) {
|
|
63355
63398
|
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 });
|
|
63356
63399
|
}
|
|
63357
|
-
const stageRoot = (0, import_node_fs29.mkdtempSync)((0,
|
|
63400
|
+
const stageRoot = (0, import_node_fs29.mkdtempSync)((0, import_node_path27.join)((0, import_node_os19.tmpdir)(), "floless-import-"));
|
|
63358
63401
|
try {
|
|
63359
|
-
const stageDir = (0,
|
|
63402
|
+
const stageDir = (0, import_node_path27.join)(stageRoot, id);
|
|
63360
63403
|
(0, import_node_fs29.mkdirSync)(stageDir);
|
|
63361
|
-
(0, import_node_fs29.writeFileSync)((0,
|
|
63404
|
+
(0, import_node_fs29.writeFileSync)((0, import_node_path27.join)(stageDir, `${id}.flo`), content);
|
|
63362
63405
|
await aware.install(stageDir);
|
|
63363
63406
|
} catch (err2) {
|
|
63364
63407
|
try {
|
|
@@ -63382,13 +63425,13 @@ async function startServer() {
|
|
|
63382
63425
|
}
|
|
63383
63426
|
const inputs = appData.inputs.map((i) => ({ name: i.name, type: i.type }));
|
|
63384
63427
|
const baked = bakeFloSource(appData.source.text, inputs);
|
|
63385
|
-
const tmpRoot = (0, import_node_fs29.mkdtempSync)((0,
|
|
63386
|
-
const backupDir2 = (0,
|
|
63387
|
-
const bakeDir = (0,
|
|
63428
|
+
const tmpRoot = (0, import_node_fs29.mkdtempSync)((0, import_node_path27.join)((0, import_node_os19.tmpdir)(), "floless-bake-"));
|
|
63429
|
+
const backupDir2 = (0, import_node_path27.join)(tmpRoot, `${id}-backup`);
|
|
63430
|
+
const bakeDir = (0, import_node_path27.join)(tmpRoot, id);
|
|
63388
63431
|
(0, import_node_fs29.cpSync)(appDir(id), backupDir2, { recursive: true });
|
|
63389
63432
|
(0, import_node_fs29.cpSync)(appDir(id), bakeDir, { recursive: true });
|
|
63390
63433
|
const floName = appData.source.path.split(/[\\/]/).pop();
|
|
63391
|
-
(0, import_node_fs29.writeFileSync)((0,
|
|
63434
|
+
(0, import_node_fs29.writeFileSync)((0, import_node_path27.join)(bakeDir, floName), baked);
|
|
63392
63435
|
let appInstalled2 = true;
|
|
63393
63436
|
try {
|
|
63394
63437
|
await aware.uninstall(id);
|
|
@@ -63410,7 +63453,7 @@ async function startServer() {
|
|
|
63410
63453
|
throw installErr;
|
|
63411
63454
|
}
|
|
63412
63455
|
try {
|
|
63413
|
-
await aware.compile((0,
|
|
63456
|
+
await aware.compile((0, import_node_path27.join)(appDir(id), floName));
|
|
63414
63457
|
} catch (compileErr) {
|
|
63415
63458
|
app.log.warn({ id, compileErr: String(compileErr) }, "bake: post-install recompile failed (app baked but may need a manual Compile)");
|
|
63416
63459
|
}
|
|
@@ -63420,7 +63463,7 @@ async function startServer() {
|
|
|
63420
63463
|
if (appInstalled2) (0, import_node_fs29.rmSync)(tmpRoot, { recursive: true, force: true });
|
|
63421
63464
|
}
|
|
63422
63465
|
});
|
|
63423
|
-
const graftAgentsDir = () => (0,
|
|
63466
|
+
const graftAgentsDir = () => (0, import_node_path27.join)((0, import_node_os19.homedir)(), ".aware", "agents");
|
|
63424
63467
|
app.post("/api/graft/match", async (req, reply) => {
|
|
63425
63468
|
const { glob } = req.body ?? {};
|
|
63426
63469
|
if (!glob) return reply.status(400).send({ ok: false, error: "glob required" });
|
|
@@ -63437,7 +63480,7 @@ async function startServer() {
|
|
|
63437
63480
|
if (!sourceKind || !sourceRef) {
|
|
63438
63481
|
return reply.status(400).send({ ok: false, error: "sourceKind and sourceRef required" });
|
|
63439
63482
|
}
|
|
63440
|
-
const tempHome = (0, import_node_fs29.mkdtempSync)((0,
|
|
63483
|
+
const tempHome = (0, import_node_fs29.mkdtempSync)((0, import_node_path27.join)((0, import_node_os19.tmpdir)(), "floless-graft-"));
|
|
63441
63484
|
let result;
|
|
63442
63485
|
try {
|
|
63443
63486
|
result = await aware.build({
|
|
@@ -63462,7 +63505,7 @@ async function startServer() {
|
|
|
63462
63505
|
const token = (0, import_node_crypto7.randomUUID)();
|
|
63463
63506
|
registerStage(token, tempHome, result.agentId);
|
|
63464
63507
|
const preview = buildPreview(manifest, sourceKind, sourceRef, token);
|
|
63465
|
-
if ((0, import_node_fs29.existsSync)((0,
|
|
63508
|
+
if ((0, import_node_fs29.existsSync)((0, import_node_path27.join)(graftAgentsDir(), result.agentId))) {
|
|
63466
63509
|
preview.warnings.unshift(`An agent named "${result.agentId}" is already installed \u2014 creating it will overwrite it.`);
|
|
63467
63510
|
}
|
|
63468
63511
|
return { ok: true, preview };
|
|
@@ -63512,6 +63555,33 @@ async function startServer() {
|
|
|
63512
63555
|
}
|
|
63513
63556
|
return false;
|
|
63514
63557
|
}
|
|
63558
|
+
let _fileAgentReady = false;
|
|
63559
|
+
let _fileEnsureInflight = null;
|
|
63560
|
+
async function ensureSteelExportReady(id) {
|
|
63561
|
+
let appData;
|
|
63562
|
+
try {
|
|
63563
|
+
appData = readApp(id);
|
|
63564
|
+
} catch {
|
|
63565
|
+
return;
|
|
63566
|
+
}
|
|
63567
|
+
if (!appData.requires.some((r) => r.split("@")[0] === "file")) return;
|
|
63568
|
+
if (!_fileAgentReady) {
|
|
63569
|
+
_fileEnsureInflight ??= aware.ensureAgentInstalled("file").then(() => {
|
|
63570
|
+
_fileAgentReady = true;
|
|
63571
|
+
}).catch((e) => app.log.warn({ id, err: String(e) }, "ensure file agent before run failed (run will surface any real error)")).finally(() => {
|
|
63572
|
+
_fileEnsureInflight = null;
|
|
63573
|
+
});
|
|
63574
|
+
await _fileEnsureInflight;
|
|
63575
|
+
}
|
|
63576
|
+
if (appData.runnable && exportPathsNeedingBake(appData.nodes, id).length > 0) {
|
|
63577
|
+
try {
|
|
63578
|
+
await bakeBomExportNodes(appData.source.path, id);
|
|
63579
|
+
await aware.compile(appData.source.path);
|
|
63580
|
+
} catch (e) {
|
|
63581
|
+
app.log.warn({ id, err: String(e) }, "bake export path before run failed (run will surface any real error)");
|
|
63582
|
+
}
|
|
63583
|
+
}
|
|
63584
|
+
}
|
|
63515
63585
|
app.post(
|
|
63516
63586
|
"/api/run",
|
|
63517
63587
|
async (req, reply) => {
|
|
@@ -63519,6 +63589,7 @@ async function startServer() {
|
|
|
63519
63589
|
if (!id) return reply.status(400).send({ ok: false, error: "id required" });
|
|
63520
63590
|
const blocked = hostRunBlocked(appProvider(id));
|
|
63521
63591
|
if (blocked) return reply.send({ ok: false, error: blocked, blocked: true, simulate: !!simulate });
|
|
63592
|
+
if (!simulate) await ensureSteelExportReady(id);
|
|
63522
63593
|
let result;
|
|
63523
63594
|
try {
|
|
63524
63595
|
result = await aware.run(id, { dryRun, simulate, inputs });
|
|
@@ -40,7 +40,7 @@ documented in `floless-app-bridge`; the five types map to:
|
|
|
40
40
|
|---|---|---|
|
|
41
41
|
| `tweak` | "Tweak node **`<nodeId>`** in **`<appId>`** — *<instruction>*" | `floless-app-workflows` |
|
|
42
42
|
| `use-template` | "Add template **<template.name>** to **`<appId>`**" | `floless-app-workflows` |
|
|
43
|
-
| `tweak-contract` | "Edit the
|
|
43
|
+
| `tweak-contract` | "Edit the Steel Model contract for **`<appId>`** — *<instruction>* (+N screenshot(s))" | `floless-app-tweak-contract` |
|
|
44
44
|
| `ui-customize` | "Customize the Dashboard (optionally `panel <panelId>`) — *<instruction>*" | `floless-app-ui` |
|
|
45
45
|
| `rebake` | "Re-read the **<inputName>** drawing (<sourceName>) in **`<appId>`** — *<instruction>*" | `floless-app-rebake` |
|
|
46
46
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: floless-app-steel-model
|
|
3
3
|
description: The front-door guide for turning a structural steel drawing SET into an editable model in floless.app — the "Steel Model" flow. This skill orchestrates the whole pipeline — set up a project folder, learn the set into plain-English knowledge files, provision the right AWARE agents (region-specific steel detailer plus any bake targets), confirm which sheets are plans / elevations / details, then hand off to the Steel Takeoff reader to read each plan into a steel.takeoff/v1 contract — with explicit confirm gates between stages. Use it when a user wants to model steel from a drawing SET or asks things like "model this steel job from the drawings", "take off this whole set", "start a steel takeoff project", "turn these structural drawings into a model", "I have a PDF set of framing plans", or "/floless-app-steel-model". It is re-runnable — resume at any stage. For a single already-isolated framing plan the Steel Takeoff reader (floless-app-steel-takeoff) can be used directly; this guide is for driving a real job end to end.
|
|
4
4
|
metadata:
|
|
5
|
-
version: 0.
|
|
5
|
+
version: 0.2.0
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Steel Model — guide a steel drawing set into an editable model
|
|
@@ -127,18 +127,22 @@ the user at the editor:
|
|
|
127
127
|
- **Read each framing plan** via **`floless-app-steel-takeoff`** — it runs the filter pre-stage
|
|
128
128
|
(isolate the steel linework by layer/thickness/colour), the vision read, the AISC/EN/BS lookup
|
|
129
129
|
via the detailer, honors `target_confidence`, writes the `steel.takeoff/v1` contract, and opens
|
|
130
|
-
the overlay editor. One contract per `steel-
|
|
130
|
+
the overlay editor. One contract per `steel-model` app; multi-sheet sets ride the filter's
|
|
131
131
|
`sheets[]` + the editor's plan switcher.
|
|
132
132
|
- **Pre-fill the connection library** from `company-rules.md` (stage 2): emit the firm's rows into
|
|
133
133
|
the contract's `connections[]` with `source:"company-rules"`; the user reviews/assigns them per
|
|
134
134
|
member end in the editor's **Connections** panel (the per-end picker + confidence card).
|
|
135
135
|
- **Point at the rest of the canvas:** the **filter** view (refine the kept linework), **Approve**
|
|
136
|
-
(bake the lock — the determinism gate), **3D** model, **IFC** / **Tekla** export, the **BOM
|
|
137
|
-
**confidence report**, and the **callout bubbles** (click a
|
|
138
|
-
plan to jump to the referenced sheet).
|
|
136
|
+
(bake the lock — the determinism gate), **3D** model, **IFC** / **Tekla** export, the **BOM**
|
|
137
|
+
(exportable to **CSV / Excel**), the **confidence report**, and the **callout bubbles** (click a
|
|
138
|
+
section/elevation/detail mark on a plan to jump to the referenced sheet).
|
|
139
139
|
|
|
140
140
|
**Done** when each framing plan has an approved, editor-reviewed contract and the user knows which
|
|
141
|
-
on-canvas tool does what. The
|
|
141
|
+
on-canvas tool does what. The 3D/IFC/Tekla exports are on-demand from the editor; the **BOM is
|
|
142
|
+
exported two ways** — on-demand via the editor's *Export BOM (CSV)/(Excel)* buttons, **and written
|
|
143
|
+
to `~/.floless/exports/steel-model/steel-model-bom.{csv,xlsx}` automatically every time the workflow
|
|
144
|
+
is Run** (two `file/write` export steps at the end of the chain). Tell the user they can **freeze**
|
|
145
|
+
those export steps (right-click → Freeze) once the BOM is right, so a re-Run won't overwrite the files.
|
|
142
146
|
|
|
143
147
|
---
|
|
144
148
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: floless-app-steel-takeoff
|
|
3
|
-
description: Read a structural steel drawing into an editable steel.takeoff/v1 contract in floless.app — the "Steel Takeoff" reader. Triggers on a pending floless `rebake` request for the `steel-
|
|
3
|
+
description: Read a structural steel drawing into an editable steel.takeoff/v1 contract in floless.app — the "Steel Takeoff" reader. Triggers on a pending floless `rebake` request for the `steel-model` app (queued from its "Re-read & re-bake ▸" button), or asks like "read this framing plan into a takeoff", "turn this structural drawing into a takeoff", "bake my drawing into steel-model", "extract members from this PDF". Teaches the host AI to READ a steel drawing (PDF/image) with its own vision at COMPOSE time, build a steel.takeoff/v1 contract (members + profiles + elevations + weights from AISC lookup), write it to the contract store, and hand control to the editor — no model in the run path, no API key.
|
|
4
4
|
metadata:
|
|
5
5
|
version: 0.1.0
|
|
6
6
|
---
|
|
@@ -9,7 +9,7 @@ metadata:
|
|
|
9
9
|
|
|
10
10
|
## What this is
|
|
11
11
|
|
|
12
|
-
`steel-
|
|
12
|
+
`steel-model` is a FloLess app that turns a structural steel drawing into an editable
|
|
13
13
|
**takeoff contract** — members, profiles, AISC weights, T.O. elevations, connection detail refs
|
|
14
14
|
— rendered in the overlay editor, then baked at Approve into the app's `read` node.
|
|
15
15
|
|
|
@@ -34,7 +34,7 @@ but do not attempt them here.
|
|
|
34
34
|
### 1. Find the drawing
|
|
35
35
|
|
|
36
36
|
From a `rebake` request: `GET http://localhost:<port>/api/requests` → the entry with
|
|
37
|
-
`type:"rebake"` and `appId:"steel-
|
|
37
|
+
`type:"rebake"` and `appId:"steel-model"` carries `snapshots: ["<abs path>"]` (the drawing
|
|
38
38
|
file) and optionally `sourceName` (the original filename — use it for the `source.name`
|
|
39
39
|
provenance field). If the user just attaches or points at a drawing in prose, skip the lookup
|
|
40
40
|
and proceed from that path.
|
|
@@ -72,7 +72,7 @@ native/display coords):
|
|
|
72
72
|
`*BEAM*STEL*`, `*COL*`, `*BRACE*`, `*JOIST*`) → default **on**; others off. Mark
|
|
73
73
|
`drawable: false` on pure text/dimension/title-block layers.
|
|
74
74
|
5. Set `filter.mode: "layer"`, `filter.active: 0`. **PUT** the contract
|
|
75
|
-
(`PUT /api/contract/steel-
|
|
75
|
+
(`PUT /api/contract/steel-model`). On a re-read, preserve any existing per-facet `on` flags.
|
|
76
76
|
|
|
77
77
|
Then tell the user to open the **Filter** node, flip through the sheets, narrow to the steel, and
|
|
78
78
|
**Save**. The saved `on` flags + `mode` are what step 2 reads — **per sheet** (each sheet's elements
|
|
@@ -180,7 +180,7 @@ excluded from the average and counted separately as the headline drag).
|
|
|
180
180
|
**The score endpoint is your gauge — call it, don't guess:**
|
|
181
181
|
|
|
182
182
|
```
|
|
183
|
-
POST http://localhost:<port>/api/contract/steel-
|
|
183
|
+
POST http://localhost:<port>/api/contract/steel-model/score
|
|
184
184
|
Content-Type: application/json
|
|
185
185
|
{ "contract": <your draft contract>, "target": <target_confidence, e.g. 70> }
|
|
186
186
|
```
|
|
@@ -309,7 +309,7 @@ Key field rules:
|
|
|
309
309
|
### 5. Write to the contract store
|
|
310
310
|
|
|
311
311
|
```
|
|
312
|
-
PUT http://localhost:<port>/api/contract/steel-
|
|
312
|
+
PUT http://localhost:<port>/api/contract/steel-model
|
|
313
313
|
Content-Type: application/json
|
|
314
314
|
|
|
315
315
|
<contract JSON body>
|
|
@@ -317,7 +317,7 @@ Content-Type: application/json
|
|
|
317
317
|
|
|
318
318
|
The server schema-validates the body. If it responds with `400`, read the error, fix the
|
|
319
319
|
offending fields, and re-PUT. You can read back the current contract with
|
|
320
|
-
`GET /api/contract/steel-
|
|
320
|
+
`GET /api/contract/steel-model`.
|
|
321
321
|
|
|
322
322
|
### 6. Hand off to the user
|
|
323
323
|
|
|
@@ -347,7 +347,7 @@ DELETE http://localhost:<port>/api/requests/<id>
|
|
|
347
347
|
|
|
348
348
|
Never write client, firm, or project identifiers into files, commits, or responses. The
|
|
349
349
|
contract carries the source path in `source.path` and an embedded raster in `raster_b64` — both
|
|
350
|
-
are machine-local (stored under `~/.floless/contracts/steel-
|
|
350
|
+
are machine-local (stored under `~/.floless/contracts/steel-model.json` by the server and
|
|
351
351
|
never committed). Clip title blocks from any embedded preview.
|
|
352
352
|
|
|
353
353
|
---
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: floless-app-tweak-contract
|
|
3
|
-
description: This skill should be used when there is a pending floless `tweak-contract` request — the user typed an instruction in the
|
|
3
|
+
description: This skill should be used when there is a pending floless `tweak-contract` request — the user typed an instruction in the Steel Model contract editor and clicked Send (Ask AI). Also triggers on asks like "edit the steel takeoff contract via AI", "adjust the steel takeoff in the contract editor", "the user asked me to change the steel contract", "pick up my tweak-contract request", "update the member profile in the contract". It teaches the host AI to read the request, fetch the current steel.takeoff/v1 contract, apply the instruction (and any pasted screenshots), PUT the updated contract back, and resolve the request.
|
|
4
4
|
metadata:
|
|
5
5
|
version: 0.1.0
|
|
6
6
|
---
|
|
@@ -9,7 +9,7 @@ metadata:
|
|
|
9
9
|
|
|
10
10
|
## What this is
|
|
11
11
|
|
|
12
|
-
The floless.app
|
|
12
|
+
The floless.app Steel Model contract editor surfaces an **Ask AI** panel: the user types
|
|
13
13
|
an instruction ("change all W12X26 to W14X30", "add a new beam on sheet S-202", "mark this
|
|
14
14
|
member as moment-frame") and optionally pastes screenshots, then clicks **Send**. That records
|
|
15
15
|
a `tweak-contract` request in the reverse channel. You pick it up here, edit the contract
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: floless-app-workflows
|
|
3
3
|
description: This skill should be used when building, modifying, or reasoning about FloLess .flo workflows for floless.app — especially reusable apps that take inputs (e.g. a phase number) and run across different Tekla models, exec nodes that run Roslyn C# against the live model, the in-app HTML Viewer, the Code tab, or "Debug in VS". Covers the exec contract, the install→validate→compile→run loop, app inputs templating, and the server seams (aware-adapter, app-reader, index).
|
|
4
4
|
metadata:
|
|
5
|
-
version: 0.
|
|
5
|
+
version: 0.2.0
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Building reusable .flo workflows in floless.app
|
|
@@ -270,6 +270,29 @@ floless.app play (designed, now unblocked): a "Bake into agent" affordance that
|
|
|
270
270
|
`.flo` to add `exposes-as-agent` + `exposed-commands` mirroring the app's `inputs:`. Thin-UI: a
|
|
271
271
|
manifest edit + recompile, no engine. See the design doc for the Stage-1 plan.
|
|
272
272
|
|
|
273
|
+
## Write a file on Run (the `file` agent) — VERIFIED, AWARE 0.81.0+
|
|
274
|
+
|
|
275
|
+
A node can **land an artifact on disk as part of the run** with the `_core/file` agent (steel-model's
|
|
276
|
+
`csv-export` / `xlsx-export` nodes do exactly this). Key facts (all verified):
|
|
277
|
+
|
|
278
|
+
- **`file` is a builtin** (`transport: builtin {}`) — handled in-process, **no host binary**, like
|
|
279
|
+
`html-report` / `viewer-3d`. But its **manifest must be installed** (`aware agent install file`):
|
|
280
|
+
absent, the run fails with an opaque IO error; present, `aware app run` dispatches it. floless
|
|
281
|
+
ensures it before Run for any app that `requires: file@1.0.x` (see `ensureSteelExportReady`, index.ts).
|
|
282
|
+
- **Verbs:** `write` lands `bytes` at `path` (`encoding: text | base64`); `write-csv` takes
|
|
283
|
+
`{ path, columns, rows }` → RFC-4180. For a **binary** artifact (`.xlsx`, image, zip) **floless
|
|
284
|
+
generates the bytes in TS and the node base64-lands them** — AWARE has no xlsx serializer, so the
|
|
285
|
+
binary stays host-side (we reuse `bomToXlsx` and write `encoding: base64`).
|
|
286
|
+
- **`file/write*` are `mode: write`** (a generic arbitrary-path writer mutates external state), so an
|
|
287
|
+
**un-frozen** export node MUST carry a `safety:` block — `safety: { snapshot: false }` is enough. A
|
|
288
|
+
**frozen** node skips the safety preflight AND skips dispatch (it replays its pinned output, no
|
|
289
|
+
write) — that is the "freeze the export so Run won't regenerate it" affordance.
|
|
290
|
+
- **Path:** the producer owns `path`. A **bare relative path resolves to the run process's cwd**
|
|
291
|
+
(it'll litter the server dir), so floless bakes an **absolute** `~/.floless/exports/<appId>/…` path
|
|
292
|
+
into the node — on Approve via `bakeDownstream` → `bakeBomExportNodes` (path + real bytes), and as a
|
|
293
|
+
safety net before Run via `ensureSteelExportReady` (path only; idempotent, recompiles once). The
|
|
294
|
+
committed template carries a bare filename + a small example payload so the step is valid out of the box.
|
|
295
|
+
|
|
273
296
|
## How the UI surfaces map to AWARE state
|
|
274
297
|
|
|
275
298
|
- **Code tab** shows the node's real source: `node.config.code` (the exec C#), not lockfile
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
app: steel-
|
|
2
|
-
version: 0.
|
|
3
|
-
display-name: Steel
|
|
1
|
+
app: steel-model
|
|
2
|
+
version: 0.3.0
|
|
3
|
+
display-name: Steel Model
|
|
4
4
|
publisher: floless
|
|
5
5
|
module: steel-detailer
|
|
6
6
|
description: |
|
|
@@ -14,6 +14,10 @@ exposes-as-agent: false
|
|
|
14
14
|
# Workflow changelog (FloLess-published). Newest first; the app shows entries newer than the
|
|
15
15
|
# installed version when offering an update. Plain-English — it surfaces to the user.
|
|
16
16
|
changelog:
|
|
17
|
+
- version: 0.3.0
|
|
18
|
+
summary: Export your bill of materials as CSV and Excel on Run
|
|
19
|
+
changes:
|
|
20
|
+
- "Added: two export steps at the end that write your bill of materials to a .csv and a real .xlsx file when you Run the workflow — the same figures as the BOM table and the Export buttons, saved to your exports folder automatically. Freeze a step once it's right and Run won't overwrite the file."
|
|
17
21
|
- version: 0.2.0
|
|
18
22
|
summary: Filter your drawing down to the steel first
|
|
19
23
|
changes:
|
|
@@ -34,6 +38,7 @@ inputs:
|
|
|
34
38
|
requires:
|
|
35
39
|
- html-report@0.2.x
|
|
36
40
|
- viewer-3d@0.1.x
|
|
41
|
+
- file@1.0.x
|
|
37
42
|
layout: linear
|
|
38
43
|
nodes:
|
|
39
44
|
# ── node 1: filter — isolate the steel linework (FIRST stage) ─────────────────
|
|
@@ -145,7 +150,45 @@ nodes:
|
|
|
145
150
|
- { id: m1, profile: W10X33, role: beam, wp: [[0,0],[6000,0]] }
|
|
146
151
|
- { id: m2, profile: HSS6X6X3/8, role: column, wp: [[0,0],[0,4500]] }
|
|
147
152
|
|
|
153
|
+
# ── node 5: csv-export — write the bill of materials to a CSV file ────────────
|
|
154
|
+
# Saves the bill of materials to a .csv file on disk every time you Run the
|
|
155
|
+
# workflow — the same figures and the same file you'd get from the "Export BOM
|
|
156
|
+
# (CSV)" button, but produced by the run so it lands automatically. When you
|
|
157
|
+
# Approve a takeoff, FloLess fills in the finished CSV and points this at your
|
|
158
|
+
# ~/.floless/exports folder; until then it ships a small sample so the step works
|
|
159
|
+
# out of the box. Freeze this node once you're happy and Run won't rewrite the file.
|
|
160
|
+
# (Uses the generic `file/write` verb with the CSV text FloLess generates — so the
|
|
161
|
+
# exported file is byte-for-byte the one the Export button produces, formula-injection
|
|
162
|
+
# guard and all — rather than re-serialising the rows in the runtime.)
|
|
163
|
+
- id: csv-export
|
|
164
|
+
agent: file
|
|
165
|
+
command: write
|
|
166
|
+
safety: { snapshot: false } # writing a file is a write-mode act; an un-frozen run needs a safety block (a frozen node skips the check)
|
|
167
|
+
config:
|
|
168
|
+
path: steel-model-bom.csv # baked to <home>/.floless/exports/steel-model/steel-model-bom.csv on Approve
|
|
169
|
+
encoding: text
|
|
170
|
+
bytes: "Profile,Qty,Length (ft),lb/ft,Weight (lb)\r\nW10X33,4,96,49.1,4714\r\nHSS6X6X3/8,6,54,27.48,1484\r\nTOTAL,10,150,,6198\r\n"
|
|
171
|
+
|
|
172
|
+
# ── node 6: xlsx-export — write the bill of materials to an Excel file ────────
|
|
173
|
+
# Saves the bill of materials to a real .xlsx Excel file every time you Run the
|
|
174
|
+
# workflow — the same figures as the BOM table and the "Export BOM (Excel)" button.
|
|
175
|
+
# Excel files are binary, so FloLess builds the spreadsheet and hands its bytes to
|
|
176
|
+
# this step to land on disk (base64-encoded). When you Approve a takeoff, FloLess
|
|
177
|
+
# fills in the real workbook and points this at your ~/.floless/exports folder; until
|
|
178
|
+
# then it ships a small sample so the step works out of the box. Freeze this node once
|
|
179
|
+
# you're happy and Run won't rewrite the file.
|
|
180
|
+
- id: xlsx-export
|
|
181
|
+
agent: file
|
|
182
|
+
command: write
|
|
183
|
+
safety: { snapshot: false } # writing a file is a write-mode act; an un-frozen run needs a safety block (a frozen node skips the check)
|
|
184
|
+
config:
|
|
185
|
+
path: steel-model-bom.xlsx # baked to <home>/.floless/exports/steel-model/steel-model-bom.xlsx on Approve
|
|
186
|
+
encoding: base64
|
|
187
|
+
bytes: "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"
|
|
188
|
+
|
|
148
189
|
connections:
|
|
149
190
|
- { from: filter, to: read }
|
|
150
191
|
- { from: read, to: view }
|
|
151
192
|
- { from: view, to: bom }
|
|
193
|
+
- { from: bom, to: csv-export }
|
|
194
|
+
- { from: csv-export, to: xlsx-export }
|
package/dist/web/aware.js
CHANGED
|
@@ -2880,7 +2880,7 @@
|
|
|
2880
2880
|
}
|
|
2881
2881
|
|
|
2882
2882
|
// ── Workflow update (module-shaped) ─────────────────────────────────────────────
|
|
2883
|
-
// A FloLess-published workflow (e.g. steel-
|
|
2883
|
+
// A FloLess-published workflow (e.g. steel-model) has a newer bundled template. Poll GET
|
|
2884
2884
|
// /api/workflows/updates; when the OPEN app has one, show a pill → modal with the changelog →
|
|
2885
2885
|
// "Back up & update" POSTs /api/workflows/:id/update (backup → swap structure → re-bake the user's
|
|
2886
2886
|
// data from the contract store → recompile) → reload the app. No auto-update. `_wfUpdates` + the
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* steel-3d-view.js — the 3D render/interaction layer for the steel-
|
|
2
|
+
* steel-3d-view.js — the 3D render/interaction layer for the steel-model editor's 3D mode.
|
|
3
3
|
*
|
|
4
4
|
* Three.js lives ONLY here (the rest of the editor is import-free). It renders the SAME scene the
|
|
5
5
|
* IFC/Tekla bake uses (fetched from /api/contract/:id/scene → contractToScene), so the editable 3D
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!doctype html><html><head><meta charset=utf-8>
|
|
2
|
-
<title>Steel
|
|
2
|
+
<title>Steel Model — editor</title>
|
|
3
3
|
<style>
|
|
4
4
|
:root{--bg:#0f172a;--panel:#1e293b;--line:#334155;--text:#e2e8f0;--mut:#94a3b8;--brand:#3b82f6}
|
|
5
5
|
/* Theme EVERY scrollbar — no native white default may leak (applies to the canvas, panels, and every modal list). */
|
|
@@ -204,7 +204,7 @@
|
|
|
204
204
|
<script type="module" src="./steel-3d-view.js"></script>
|
|
205
205
|
</head><body>
|
|
206
206
|
<header>
|
|
207
|
-
<b>Steel
|
|
207
|
+
<b>Steel Model</b>
|
|
208
208
|
<div id=viewToggle role=group aria-label="Canvas view"><button id=vt2d class="seg on" aria-pressed=true title="Plan view (2D overlay)">2D</button><button id=vt3d class=seg aria-pressed=false title="3D model view">3D</button></div>
|
|
209
209
|
<select id=planSel title="Switch plan view"></select>
|
|
210
210
|
<span class=stat>Members <b id=mc>0</b></span><span class=stat>Weight <b id=wt>0</b> tons · <b id=wtlb>0</b> lb</span>
|