@floless/app 0.12.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 } = require("worker_threads");
5492
- var { join: join21 } = require("path");
5492
+ var { join: join22 } = 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"] || join21(__dirname, "lib", "worker.js");
5543
+ const toExecute = bundlerOverrides["thread-stream-worker"] || join22(__dirname, "lib", "worker.js");
5544
5544
  const worker = new Worker(toExecute, {
5545
5545
  ...opts.workerOpts,
5546
5546
  name: opts.workerOpts?.name || "thread-stream",
@@ -6006,9 +6006,9 @@ var require_transport = __commonJS({
6006
6006
  "node_modules/pino/lib/transport.js"(exports2, module2) {
6007
6007
  "use strict";
6008
6008
  var { createRequire: createRequire4 } = require("module");
6009
- var { existsSync: existsSync19 } = require("node:fs");
6009
+ var { existsSync: existsSync20 } = require("node:fs");
6010
6010
  var getCallers = require_caller();
6011
- var { join: join21, isAbsolute: isAbsolute2, sep: sep3 } = require("node:path");
6011
+ var { join: join22, isAbsolute: isAbsolute2, sep: sep3 } = require("node:path");
6012
6012
  var { fileURLToPath: fileURLToPath4 } = require("node:url");
6013
6013
  var sleep = require_atomic_sleep();
6014
6014
  var onExit = require_on_exit_leak_free();
@@ -6080,7 +6080,7 @@ var require_transport = __commonJS({
6080
6080
  return false;
6081
6081
  }
6082
6082
  }
6083
- return isAbsolute2(path) && !existsSync19(path);
6083
+ return isAbsolute2(path) && !existsSync20(path);
6084
6084
  }
6085
6085
  function stripQuotes(value) {
6086
6086
  const first = value[0];
@@ -6161,7 +6161,7 @@ var require_transport = __commonJS({
6161
6161
  throw new Error("only one of target or targets can be specified");
6162
6162
  }
6163
6163
  if (targets) {
6164
- target = bundlerOverrides["pino-worker"] || join21(__dirname, "worker.js");
6164
+ target = bundlerOverrides["pino-worker"] || join22(__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"] || join21(__dirname, "worker.js");
6182
+ target = bundlerOverrides["pino-worker"] || join22(__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 join21(__dirname, "..", "file.js");
6205
+ return join22(__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 join21 = ",";
7185
+ let join22 = ",";
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
- join21 = `,
7199
+ join22 = `,
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 += join21;
7207
+ res += join22;
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 += `${join21}"... ${getItemCount(removedKeys)} not stringified"`;
7213
+ res += `${join22}"... ${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
- join21 = `,
7234
+ join22 = `,
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 = join21;
7248
+ separator = join22;
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 = join21;
7254
+ separator = join22;
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 join21 = ",";
7295
+ let join22 = ",";
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
- join21 = `,
7308
+ join22 = `,
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 += join21;
7316
+ res += join22;
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 += `${join21}"... ${getItemCount(removedKeys)} not stringified"`;
7322
+ res += `${join22}"... ${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
- join21 = `,
7335
+ join22 = `,
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 = join21;
7344
+ separator = join22;
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 join22 = `,
7402
+ const join23 = `,
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 += join22;
7409
+ res2 += join23;
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 += `${join22}"... ${getItemCount(removedKeys)} not stringified"`;
7415
+ res2 += `${join23}"... ${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 join21 = `,
7431
+ const join22 = `,
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, join21, maximumBreadth);
7437
+ res += stringifyTypedArray(value, join22, maximumBreadth);
7438
7438
  keys = keys.slice(value.length);
7439
7439
  maximumPropertiesToStringify -= value.length;
7440
- separator = join21;
7440
+ separator = join22;
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 = join21;
7451
+ separator = join22;
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 = join21;
7457
+ separator = join22;
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 join21 = path.join;
41994
+ var join22 = path.join;
41995
41995
  var normalize2 = path.normalize;
41996
41996
  var resolve5 = path.resolve;
41997
41997
  var sep3 = path.sep;
@@ -42078,7 +42078,7 @@ var require_send = __commonJS({
42078
42078
  return { statusCode: 403 };
42079
42079
  }
42080
42080
  parts = path2.split(sep3);
42081
- path2 = normalize2(join21(root, path2));
42081
+ path2 = normalize2(join22(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 err;
42362
42362
  for (let i = 0; i < options.index.length; i++) {
42363
42363
  const index = options.index[i];
42364
- const p = join21(path2, index);
42364
+ const p = join22(path2, index);
42365
42365
  const { error, stat: stat4 } = await tryStat(p);
42366
42366
  if (error) {
42367
42367
  err = error;
@@ -43145,8 +43145,8 @@ var require_static = __commonJS({
43145
43145
  }
43146
43146
  }
43147
43147
  }
43148
- for (const [dirname9, rootPath] of indexDirs.entries()) {
43149
- const pathname = dirname9 + (dirname9.endsWith("/") ? "" : "/");
43148
+ for (const [dirname10, rootPath] of indexDirs.entries()) {
43149
+ const pathname = dirname10 + (dirname10.endsWith("/") ? "" : "/");
43150
43150
  const file = "/" + pathname.replace(prefix, "");
43151
43151
  setUpHeadAndGet(routeOpts, pathname, file, rootPath);
43152
43152
  if (opts.redirect === true) {
@@ -50827,9 +50827,9 @@ var import_node_readline2 = require("node:readline");
50827
50827
 
50828
50828
  // index.ts
50829
50829
  var import_node_url3 = require("node:url");
50830
- var import_node_path19 = require("node:path");
50830
+ var import_node_path20 = require("node:path");
50831
50831
  var import_node_os13 = require("node:os");
50832
- var import_node_fs20 = require("node:fs");
50832
+ var import_node_fs21 = require("node:fs");
50833
50833
  var import_node_child_process6 = require("node:child_process");
50834
50834
 
50835
50835
  // log.mjs
@@ -52609,7 +52609,7 @@ function appVersion() {
52609
52609
  return resolveVersion({
52610
52610
  isSea: isSea2(),
52611
52611
  sqVersionXml: readSqVersionXml(),
52612
- define: true ? "0.12.0" : void 0,
52612
+ define: true ? "0.12.1" : void 0,
52613
52613
  pkgVersion: readPkgVersion()
52614
52614
  });
52615
52615
  }
@@ -52619,7 +52619,7 @@ function resolveChannel(s) {
52619
52619
  return "dev";
52620
52620
  }
52621
52621
  function appChannel() {
52622
- return resolveChannel({ isSea: isSea2(), define: true ? "0.12.0" : void 0 });
52622
+ return resolveChannel({ isSea: isSea2(), define: true ? "0.12.1" : void 0 });
52623
52623
  }
52624
52624
 
52625
52625
  // oauth-presets.ts
@@ -52964,6 +52964,13 @@ function writeTemplates(list) {
52964
52964
  (0, import_node_fs12.renameSync)(tmp, TEMPLATES_FILE);
52965
52965
  }
52966
52966
  function addTemplate(input) {
52967
+ const list = listTemplates();
52968
+ if (!input.source?.nodeId && input.node.agent != null && input.node.command != null) {
52969
+ const dup = list.find(
52970
+ (t) => !t.source?.nodeId && t.node.agent === input.node.agent && t.node.command === input.node.command && t.node.kind === input.node.kind
52971
+ );
52972
+ if (dup) return dup;
52973
+ }
52967
52974
  const tpl = {
52968
52975
  id: (0, import_node_crypto4.randomUUID)(),
52969
52976
  name: input.name.trim(),
@@ -52972,7 +52979,6 @@ function addTemplate(input) {
52972
52979
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
52973
52980
  source: input.source
52974
52981
  };
52975
- const list = listTemplates();
52976
52982
  list.push(tpl);
52977
52983
  writeTemplates(list);
52978
52984
  return tpl;
@@ -54026,10 +54032,39 @@ function unregisterAutostart() {
54026
54032
  // updater.ts
54027
54033
  var import_node_child_process4 = require("node:child_process");
54028
54034
  var import_node_crypto5 = require("node:crypto");
54029
- var import_node_fs16 = require("node:fs");
54035
+ var import_node_fs17 = require("node:fs");
54030
54036
  var import_node_stream = require("node:stream");
54031
54037
  var import_promises = require("node:stream/promises");
54038
+ var import_node_path15 = require("node:path");
54039
+
54040
+ // post-update-marker.mjs
54041
+ var import_node_fs16 = require("node:fs");
54032
54042
  var import_node_path14 = require("node:path");
54043
+ function markerPath() {
54044
+ const override = (process.env.FLOLESS_POST_UPDATE_MARKER ?? "").trim();
54045
+ if (override) return override;
54046
+ return (0, import_node_path14.join)((0, import_node_path14.dirname)((0, import_node_path14.dirname)(process.execPath)), ".floless-post-update");
54047
+ }
54048
+ function writePostUpdateMarker() {
54049
+ try {
54050
+ (0, import_node_fs16.writeFileSync)(markerPath(), (/* @__PURE__ */ new Date()).toISOString());
54051
+ return true;
54052
+ } catch {
54053
+ return false;
54054
+ }
54055
+ }
54056
+ function consumePostUpdateMarker() {
54057
+ const p = markerPath();
54058
+ try {
54059
+ if (!(0, import_node_fs16.existsSync)(p)) return false;
54060
+ (0, import_node_fs16.rmSync)(p, { force: true });
54061
+ return true;
54062
+ } catch {
54063
+ return false;
54064
+ }
54065
+ }
54066
+
54067
+ // updater.ts
54033
54068
  var CHANNEL = "win";
54034
54069
  var FEED_TIMEOUT_MS = 15e3;
54035
54070
  var DOWNLOAD_TIMEOUT_MS = 3e5;
@@ -54038,13 +54073,13 @@ function currentVersion() {
54038
54073
  return appVersion();
54039
54074
  }
54040
54075
  function installRoot() {
54041
- return (0, import_node_path14.dirname)((0, import_node_path14.dirname)(process.execPath));
54076
+ return (0, import_node_path15.dirname)((0, import_node_path15.dirname)(process.execPath));
54042
54077
  }
54043
54078
  function updateExePath() {
54044
- return (0, import_node_path14.join)(installRoot(), "Update.exe");
54079
+ return (0, import_node_path15.join)(installRoot(), "Update.exe");
54045
54080
  }
54046
54081
  function packagesDir() {
54047
- return (0, import_node_path14.join)(installRoot(), "packages");
54082
+ return (0, import_node_path15.join)(installRoot(), "packages");
54048
54083
  }
54049
54084
  function feedUrl() {
54050
54085
  const env2 = (process.env.FLOLESS_UPDATE_URL ?? "").trim().replace(/\/+$/, "");
@@ -54137,22 +54172,22 @@ async function checkForUpdate() {
54137
54172
  }
54138
54173
  async function sha1OfFile(path) {
54139
54174
  const hash = (0, import_node_crypto5.createHash)("sha1");
54140
- await (0, import_promises.pipeline)((0, import_node_fs16.createReadStream)(path), hash);
54175
+ await (0, import_promises.pipeline)((0, import_node_fs17.createReadStream)(path), hash);
54141
54176
  return hash.digest("hex").toUpperCase();
54142
54177
  }
54143
54178
  async function downloadPackage(asset) {
54144
54179
  if (!NUPKG_NAME.test(asset.FileName)) throw new Error(`refusing suspicious package name: ${asset.FileName}`);
54145
54180
  const want = asset.SHA1.toUpperCase();
54146
54181
  const dir = packagesDir();
54147
- (0, import_node_fs16.mkdirSync)(dir, { recursive: true });
54148
- const dest = (0, import_node_path14.join)(dir, asset.FileName);
54149
- if ((0, import_node_fs16.existsSync)(dest) && await sha1OfFile(dest) === want) return dest;
54182
+ (0, import_node_fs17.mkdirSync)(dir, { recursive: true });
54183
+ const dest = (0, import_node_path15.join)(dir, asset.FileName);
54184
+ if ((0, import_node_fs17.existsSync)(dest) && await sha1OfFile(dest) === want) return dest;
54150
54185
  const res = await authedFetch(`${feedUrl()}/${encodeURIComponent(asset.FileName)}`, {
54151
54186
  redirect: "follow",
54152
54187
  signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS)
54153
54188
  });
54154
54189
  if (!res.ok || !res.body) throw new Error(`download failed: HTTP ${res.status} for ${asset.FileName}`);
54155
- await (0, import_promises.pipeline)(import_node_stream.Readable.fromWeb(res.body), (0, import_node_fs16.createWriteStream)(dest));
54190
+ await (0, import_promises.pipeline)(import_node_stream.Readable.fromWeb(res.body), (0, import_node_fs17.createWriteStream)(dest));
54156
54191
  const got = await sha1OfFile(dest);
54157
54192
  if (got !== want) throw new Error(`SHA1 mismatch for ${asset.FileName}: feed=${want} got=${got}`);
54158
54193
  return dest;
@@ -54161,7 +54196,7 @@ async function applyUpdate(check, opts) {
54161
54196
  if (!check.supported) return { applied: false, message: check.reason ?? "auto-update not supported in this runtime" };
54162
54197
  if (!check.updateAvailable || !check.asset) return { applied: false, message: check.reason ?? "no update available" };
54163
54198
  const exe = updateExePath();
54164
- if (!(0, import_node_fs16.existsSync)(exe)) {
54199
+ if (!(0, import_node_fs17.existsSync)(exe)) {
54165
54200
  return { applied: false, message: `Update.exe not found at ${exe} \u2014 is this a Velopack install?` };
54166
54201
  }
54167
54202
  const pkg = await downloadPackage(check.asset);
@@ -54179,6 +54214,7 @@ async function applyUpdate(check, opts) {
54179
54214
  resolve5();
54180
54215
  });
54181
54216
  });
54217
+ writePostUpdateMarker();
54182
54218
  return { applied: true, message: `updating to v${check.targetVersion}\u2026 the app will relaunch` };
54183
54219
  }
54184
54220
 
@@ -54369,12 +54405,12 @@ function isTraceCorrupt(events) {
54369
54405
 
54370
54406
  // launch.mjs
54371
54407
  var import_node_child_process5 = require("node:child_process");
54372
- var import_node_path15 = require("node:path");
54408
+ var import_node_path16 = require("node:path");
54373
54409
  var import_node_url = require("node:url");
54374
- var import_node_fs17 = require("node:fs");
54410
+ var import_node_fs18 = require("node:fs");
54375
54411
  var import_node_http = __toESM(require("node:http"), 1);
54376
54412
  var import_node_readline = require("node:readline");
54377
- var __dirname2 = (0, import_node_path15.dirname)((0, import_node_url.fileURLToPath)(__import_meta_url));
54413
+ var __dirname2 = (0, import_node_path16.dirname)((0, import_node_url.fileURLToPath)(__import_meta_url));
54378
54414
  var PORT = Number(process.env.PORT ?? 4317);
54379
54415
  var HEALTH_URL = `http://127.0.0.1:${PORT}/api/health`;
54380
54416
  var BROWSER_URL = `http://floless.localhost:${PORT}`;
@@ -54446,8 +54482,8 @@ async function waitHealthy(timeoutMs = 3e4) {
54446
54482
  function resolveServerStart() {
54447
54483
  const packaged = /flolessapp\.exe$/i.test(process.execPath);
54448
54484
  if (packaged) return { cmd: process.execPath, args: ["--serve"], shell: false };
54449
- const bundle = (0, import_node_path15.join)(__dirname2, "dist", "floless-server.cjs");
54450
- if ((0, import_node_fs17.existsSync)(bundle)) return { cmd: process.execPath, args: [bundle, "--serve"], shell: false };
54485
+ const bundle = (0, import_node_path16.join)(__dirname2, "dist", "floless-server.cjs");
54486
+ if ((0, import_node_fs18.existsSync)(bundle)) return { cmd: process.execPath, args: [bundle, "--serve"], shell: false };
54451
54487
  return { cmd: "npm", args: ["run", "start"], shell: isWin2 };
54452
54488
  }
54453
54489
  function startServerDetached() {
@@ -54495,6 +54531,7 @@ async function ensureServerUp() {
54495
54531
  log("server up");
54496
54532
  }
54497
54533
  async function cmdOpen() {
54534
+ const postUpdate = consumePostUpdateMarker();
54498
54535
  if (await ping()) {
54499
54536
  const running = await probeVersion();
54500
54537
  if (shouldTakeOver(running, _selfVersion)) {
@@ -54503,11 +54540,15 @@ async function cmdOpen() {
54503
54540
  await new Promise((r) => setTimeout(r, 500));
54504
54541
  await ensureServerUp();
54505
54542
  } else {
54506
- log(`already running${running ? ` (v${running})` : ""} \u2014 opening browser`);
54543
+ log(`already running${running ? ` (v${running})` : ""}`);
54507
54544
  }
54508
54545
  } else {
54509
54546
  await ensureServerUp();
54510
54547
  }
54548
+ if (postUpdate) {
54549
+ log("post-update relaunch \u2014 server up; the existing tab reconnects via its health poll (no new tab)");
54550
+ return;
54551
+ }
54511
54552
  log(`opening ${BROWSER_URL}`);
54512
54553
  openBrowser2(BROWSER_URL);
54513
54554
  }
@@ -54595,8 +54636,8 @@ function taskkillArgs(pid, { tree = true } = {}) {
54595
54636
  }
54596
54637
  function killSupervisor({ tree = true } = {}) {
54597
54638
  if (!isWin2) return;
54598
- const isNpmChannel = /^node(\.exe)?$/i.test((0, import_node_path15.basename)(process.execPath));
54599
- const scriptMatch = isNpmChannel ? (0, import_node_path15.basename)((0, import_node_url.fileURLToPath)(__import_meta_url)) : void 0;
54639
+ const isNpmChannel = /^node(\.exe)?$/i.test((0, import_node_path16.basename)(process.execPath));
54640
+ const scriptMatch = isNpmChannel ? (0, import_node_path16.basename)((0, import_node_url.fileURLToPath)(__import_meta_url)) : void 0;
54600
54641
  const realExe = resolveRealInstallExe(process.execPath);
54601
54642
  const exeMatch = realExe === process.execPath ? process.execPath : [process.execPath, realExe];
54602
54643
  const pids = supervisorPidsToKill(enumerateProcesses(), process.pid, exeMatch, scriptMatch);
@@ -54761,7 +54802,7 @@ async function runAction(arg, flagArgv = [], selfVersion = null) {
54761
54802
  }
54762
54803
  await action(parseTeardownFlags(flagArgv));
54763
54804
  }
54764
- var entry = (0, import_node_path15.basename)(process.argv[1] ?? "").toLowerCase();
54805
+ var entry = (0, import_node_path16.basename)(process.argv[1] ?? "").toLowerCase();
54765
54806
  if (entry === "launch.mjs") {
54766
54807
  runAction(process.argv[2], process.argv.slice(3)).catch((e) => {
54767
54808
  log(`error: ${e?.message ?? e}`);
@@ -54835,9 +54876,9 @@ function awareUpgradeBlockReason(s) {
54835
54876
  }
54836
54877
 
54837
54878
  // skill-sync.ts
54838
- var import_node_fs18 = require("node:fs");
54879
+ var import_node_fs19 = require("node:fs");
54839
54880
  var import_node_os11 = require("node:os");
54840
- var import_node_path16 = require("node:path");
54881
+ var import_node_path17 = require("node:path");
54841
54882
  var import_node_url2 = require("node:url");
54842
54883
  var import_yaml5 = __toESM(require_dist6(), 1);
54843
54884
 
@@ -54862,14 +54903,14 @@ function selectShippedSkillNames(names) {
54862
54903
  }
54863
54904
 
54864
54905
  // skill-sync.ts
54865
- var __dirname3 = (0, import_node_path16.dirname)((0, import_node_url2.fileURLToPath)(__import_meta_url));
54906
+ var __dirname3 = (0, import_node_path17.dirname)((0, import_node_url2.fileURLToPath)(__import_meta_url));
54866
54907
  function bundledSkillsRoot() {
54867
54908
  const candidates = [
54868
- (0, import_node_path16.join)(__dirname3, "skills"),
54869
- (0, import_node_path16.join)((0, import_node_path16.dirname)(process.execPath), "skills"),
54870
- (0, import_node_path16.join)(__dirname3, "..", ".claude", "skills")
54909
+ (0, import_node_path17.join)(__dirname3, "skills"),
54910
+ (0, import_node_path17.join)((0, import_node_path17.dirname)(process.execPath), "skills"),
54911
+ (0, import_node_path17.join)(__dirname3, "..", ".claude", "skills")
54871
54912
  ];
54872
- return candidates.find((p) => (0, import_node_fs18.existsSync)(p)) ?? null;
54913
+ return candidates.find((p) => (0, import_node_fs19.existsSync)(p)) ?? null;
54873
54914
  }
54874
54915
  function targetConfigDirs() {
54875
54916
  const override = process.env.FLOLESS_SKILL_TARGETS;
@@ -54878,14 +54919,14 @@ function targetConfigDirs() {
54878
54919
  }
54879
54920
  const home = (0, import_node_os11.homedir)();
54880
54921
  return [
54881
- { runtime: "claude", dir: (0, import_node_path16.join)(home, ".claude") },
54882
- { runtime: "codex", dir: (0, import_node_path16.join)(home, ".codex") },
54883
- { runtime: "opencode", dir: (0, import_node_path16.join)(home, ".opencode") }
54922
+ { runtime: "claude", dir: (0, import_node_path17.join)(home, ".claude") },
54923
+ { runtime: "codex", dir: (0, import_node_path17.join)(home, ".codex") },
54924
+ { runtime: "opencode", dir: (0, import_node_path17.join)(home, ".opencode") }
54884
54925
  ];
54885
54926
  }
54886
54927
  function skillVersion(skillMdPath) {
54887
54928
  try {
54888
- const text = (0, import_node_fs18.readFileSync)(skillMdPath, "utf8");
54929
+ const text = (0, import_node_fs19.readFileSync)(skillMdPath, "utf8");
54889
54930
  const m = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text);
54890
54931
  if (!m || m[1] === void 0) return null;
54891
54932
  const fm = (0, import_yaml5.parse)(m[1]);
@@ -54925,21 +54966,21 @@ function decideAction(installed, bundled) {
54925
54966
  function bundledSkills(root) {
54926
54967
  let entries = [];
54927
54968
  try {
54928
- entries = selectShippedSkillNames((0, import_node_fs18.readdirSync)(root));
54969
+ entries = selectShippedSkillNames((0, import_node_fs19.readdirSync)(root));
54929
54970
  } catch {
54930
54971
  return [];
54931
54972
  }
54932
54973
  const out = [];
54933
54974
  for (const name of entries) {
54934
- const dir = (0, import_node_path16.join)(root, name);
54975
+ const dir = (0, import_node_path17.join)(root, name);
54935
54976
  let isDir = false;
54936
54977
  try {
54937
- isDir = (0, import_node_fs18.statSync)(dir).isDirectory();
54978
+ isDir = (0, import_node_fs19.statSync)(dir).isDirectory();
54938
54979
  } catch {
54939
54980
  isDir = false;
54940
54981
  }
54941
54982
  if (!isDir) continue;
54942
- const v = skillVersion((0, import_node_path16.join)(dir, "SKILL.md"));
54983
+ const v = skillVersion((0, import_node_path17.join)(dir, "SKILL.md"));
54943
54984
  if (!v) continue;
54944
54985
  out.push({ name, dir, version: v });
54945
54986
  }
@@ -54952,17 +54993,17 @@ function syncSkills() {
54952
54993
  const skills = bundledSkills(root);
54953
54994
  if (!skills.length) return results;
54954
54995
  for (const { runtime, dir: cfg } of targetConfigDirs()) {
54955
- if (!(0, import_node_fs18.existsSync)(cfg)) continue;
54956
- const skillsDir = (0, import_node_path16.join)(cfg, "skills");
54996
+ if (!(0, import_node_fs19.existsSync)(cfg)) continue;
54997
+ const skillsDir = (0, import_node_path17.join)(cfg, "skills");
54957
54998
  for (const s of skills) {
54958
- const dest = (0, import_node_path16.join)(skillsDir, s.name);
54959
- const installedMd = (0, import_node_path16.join)(dest, "SKILL.md");
54960
- const installed = (0, import_node_fs18.existsSync)(installedMd) ? skillVersion(installedMd) : null;
54999
+ const dest = (0, import_node_path17.join)(skillsDir, s.name);
55000
+ const installedMd = (0, import_node_path17.join)(dest, "SKILL.md");
55001
+ const installed = (0, import_node_fs19.existsSync)(installedMd) ? skillVersion(installedMd) : null;
54961
55002
  const action = decideAction(installed, s.version);
54962
55003
  if (action === "installed" || action === "updated") {
54963
55004
  try {
54964
- if (action === "updated") (0, import_node_fs18.rmSync)(dest, { recursive: true, force: true });
54965
- (0, import_node_fs18.cpSync)(s.dir, dest, { recursive: true });
55005
+ if (action === "updated") (0, import_node_fs19.rmSync)(dest, { recursive: true, force: true });
55006
+ (0, import_node_fs19.cpSync)(s.dir, dest, { recursive: true });
54966
55007
  results.push({ runtime, skill: s.name, action, from: installed, to: s.version });
54967
55008
  } catch {
54968
55009
  }
@@ -54976,8 +55017,8 @@ function syncSkills() {
54976
55017
 
54977
55018
  // watch.ts
54978
55019
  var import_node_os12 = require("node:os");
54979
- var import_node_path18 = require("node:path");
54980
- var import_node_fs19 = require("node:fs");
55020
+ var import_node_path19 = require("node:path");
55021
+ var import_node_fs20 = require("node:fs");
54981
55022
 
54982
55023
  // node_modules/chokidar/esm/index.js
54983
55024
  var import_fs2 = require("fs");
@@ -54988,7 +55029,7 @@ var sysPath2 = __toESM(require("path"), 1);
54988
55029
  // node_modules/readdirp/esm/index.js
54989
55030
  var import_promises2 = require("node:fs/promises");
54990
55031
  var import_node_stream2 = require("node:stream");
54991
- var import_node_path17 = require("node:path");
55032
+ var import_node_path18 = require("node:path");
54992
55033
  var EntryTypes = {
54993
55034
  FILE_TYPE: "files",
54994
55035
  DIR_TYPE: "directories",
@@ -55063,7 +55104,7 @@ var ReaddirpStream = class extends import_node_stream2.Readable {
55063
55104
  this._wantsDir = type ? DIR_TYPES.has(type) : false;
55064
55105
  this._wantsFile = type ? FILE_TYPES.has(type) : false;
55065
55106
  this._wantsEverything = type === EntryTypes.EVERYTHING_TYPE;
55066
- this._root = (0, import_node_path17.resolve)(root);
55107
+ this._root = (0, import_node_path18.resolve)(root);
55067
55108
  this._isDirent = !opts.alwaysStat;
55068
55109
  this._statsProp = this._isDirent ? "dirent" : "stats";
55069
55110
  this._rdOptions = { encoding: "utf8", withFileTypes: this._isDirent };
@@ -55134,8 +55175,8 @@ var ReaddirpStream = class extends import_node_stream2.Readable {
55134
55175
  let entry2;
55135
55176
  const basename5 = this._isDirent ? dirent.name : dirent;
55136
55177
  try {
55137
- const fullPath = (0, import_node_path17.resolve)((0, import_node_path17.join)(path, basename5));
55138
- entry2 = { path: (0, import_node_path17.relative)(this._root, fullPath), fullPath, basename: basename5 };
55178
+ const fullPath = (0, import_node_path18.resolve)((0, import_node_path18.join)(path, basename5));
55179
+ entry2 = { path: (0, import_node_path18.relative)(this._root, fullPath), fullPath, basename: basename5 };
55139
55180
  entry2[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
55140
55181
  } catch (err) {
55141
55182
  this._onError(err);
@@ -55169,7 +55210,7 @@ var ReaddirpStream = class extends import_node_stream2.Readable {
55169
55210
  }
55170
55211
  if (entryRealPathStats.isDirectory()) {
55171
55212
  const len = entryRealPath.length;
55172
- if (full.startsWith(entryRealPath) && full.substr(len, 1) === import_node_path17.sep) {
55213
+ if (full.startsWith(entryRealPath) && full.substr(len, 1) === import_node_path18.sep) {
55173
55214
  const recursiveError = new Error(`Circular symlink detected: "${full}" points to "${entryRealPath}"`);
55174
55215
  recursiveError.code = RECURSIVE_ERROR_CODE;
55175
55216
  return this._onError(recursiveError);
@@ -55708,9 +55749,9 @@ var NodeFsHandler = class {
55708
55749
  if (this.fsw.closed) {
55709
55750
  return;
55710
55751
  }
55711
- const dirname9 = sysPath.dirname(file);
55752
+ const dirname10 = sysPath.dirname(file);
55712
55753
  const basename5 = sysPath.basename(file);
55713
- const parent = this.fsw._getWatchedDir(dirname9);
55754
+ const parent = this.fsw._getWatchedDir(dirname10);
55714
55755
  let prevStats = stats;
55715
55756
  if (parent.has(basename5))
55716
55757
  return;
@@ -55737,7 +55778,7 @@ var NodeFsHandler = class {
55737
55778
  prevStats = newStats2;
55738
55779
  }
55739
55780
  } catch (error) {
55740
- this.fsw._remove(dirname9, basename5);
55781
+ this.fsw._remove(dirname10, basename5);
55741
55782
  }
55742
55783
  } else if (parent.has(basename5)) {
55743
55784
  const at = newStats.atimeMs;
@@ -56677,33 +56718,33 @@ function appIdFromLogPath(path) {
56677
56718
  return i >= 0 && parts[i + 1] ? parts[i + 1] : null;
56678
56719
  }
56679
56720
  function samePath(a, b) {
56680
- const ra = (0, import_node_path18.resolve)(a);
56681
- const rb = (0, import_node_path18.resolve)(b);
56721
+ const ra = (0, import_node_path19.resolve)(a);
56722
+ const rb = (0, import_node_path19.resolve)(b);
56682
56723
  return process.platform === "win32" ? ra.toLowerCase() === rb.toLowerCase() : ra === rb;
56683
56724
  }
56684
56725
  function underDir(path, dir) {
56685
- const rp = (0, import_node_path18.resolve)(path);
56686
- const rd = (0, import_node_path18.resolve)(dir);
56726
+ const rp = (0, import_node_path19.resolve)(path);
56727
+ const rd = (0, import_node_path19.resolve)(dir);
56687
56728
  const [p, d] = process.platform === "win32" ? [rp.toLowerCase(), rd.toLowerCase()] : [rp, rd];
56688
- return p === d || p.startsWith(d + import_node_path18.sep);
56729
+ return p === d || p.startsWith(d + import_node_path19.sep);
56689
56730
  }
56690
56731
  function startWatcher() {
56691
- const awareDir = process.env.AWARE_HOME ?? (0, import_node_path18.join)((0, import_node_os12.homedir)(), ".aware");
56692
- const credentialsDir = (0, import_node_path18.join)(awareDir, "credentials");
56693
- if (!(0, import_node_fs19.existsSync)(credentialsDir)) {
56732
+ const awareDir = process.env.AWARE_HOME ?? (0, import_node_path19.join)((0, import_node_os12.homedir)(), ".aware");
56733
+ const credentialsDir = (0, import_node_path19.join)(awareDir, "credentials");
56734
+ if (!(0, import_node_fs20.existsSync)(credentialsDir)) {
56694
56735
  try {
56695
- (0, import_node_fs19.mkdirSync)(credentialsDir, { recursive: true });
56736
+ (0, import_node_fs20.mkdirSync)(credentialsDir, { recursive: true });
56696
56737
  } catch {
56697
56738
  }
56698
56739
  }
56699
- if (!(0, import_node_fs19.existsSync)(uiDir)) {
56740
+ if (!(0, import_node_fs20.existsSync)(uiDir)) {
56700
56741
  try {
56701
- (0, import_node_fs19.mkdirSync)(uiDir, { recursive: true });
56742
+ (0, import_node_fs20.mkdirSync)(uiDir, { recursive: true });
56702
56743
  } catch {
56703
56744
  }
56704
56745
  }
56705
- const targets = ["apps", "logs", "credentials"].map((d) => (0, import_node_path18.join)(awareDir, d)).filter((p) => (0, import_node_fs19.existsSync)(p));
56706
- if ((0, import_node_fs19.existsSync)(uiDir)) targets.push(uiDir);
56746
+ const targets = ["apps", "logs", "credentials"].map((d) => (0, import_node_path19.join)(awareDir, d)).filter((p) => (0, import_node_fs20.existsSync)(p));
56747
+ if ((0, import_node_fs20.existsSync)(uiDir)) targets.push(uiDir);
56707
56748
  if (targets.length === 0) {
56708
56749
  return null;
56709
56750
  }
@@ -56733,11 +56774,11 @@ function startWatcher() {
56733
56774
  const isCredential = path.split(/[\\/]/).includes("credentials");
56734
56775
  const kind = isCredential ? "credential" : path.endsWith(".jsonl") ? "trace" : path.endsWith(".lock") ? "lock" : path.endsWith(".flo") || path.endsWith(".app") ? "source" : "file";
56735
56776
  broadcast({ type: "fs-change", kind, event, path });
56736
- if (kind === "trace" && event !== "unlink" && (0, import_node_fs19.existsSync)(path)) {
56777
+ if (kind === "trace" && event !== "unlink" && (0, import_node_fs20.existsSync)(path)) {
56737
56778
  const id = appIdFromLogPath(path);
56738
56779
  if (!id) return;
56739
56780
  try {
56740
- broadcast({ type: "trace-file", id, runId: path.split(import_node_path18.sep).pop()?.replace(/\.jsonl$/, "") ?? null, events: parseTrace((0, import_node_fs19.readFileSync)(path, "utf8")) });
56781
+ broadcast({ type: "trace-file", id, runId: path.split(import_node_path19.sep).pop()?.replace(/\.jsonl$/, "") ?? null, events: parseTrace((0, import_node_fs20.readFileSync)(path, "utf8")) });
56741
56782
  } catch {
56742
56783
  }
56743
56784
  }
@@ -56746,10 +56787,10 @@ function startWatcher() {
56746
56787
  }
56747
56788
 
56748
56789
  // index.ts
56749
- var __dirname4 = (0, import_node_path19.dirname)((0, import_node_url3.fileURLToPath)(__import_meta_url));
56750
- var WEB_ROOT = [(0, import_node_path19.join)(__dirname4, "web"), (0, import_node_path19.join)((0, import_node_path19.dirname)(process.execPath), "web"), (0, import_node_path19.join)(__dirname4, "..", "web")].find(
56751
- (p) => (0, import_node_fs20.existsSync)(p)
56752
- ) ?? (0, import_node_path19.join)(__dirname4, "..", "web");
56790
+ var __dirname4 = (0, import_node_path20.dirname)((0, import_node_url3.fileURLToPath)(__import_meta_url));
56791
+ var WEB_ROOT = [(0, import_node_path20.join)(__dirname4, "web"), (0, import_node_path20.join)((0, import_node_path20.dirname)(process.execPath), "web"), (0, import_node_path20.join)(__dirname4, "..", "web")].find(
56792
+ (p) => (0, import_node_fs21.existsSync)(p)
56793
+ ) ?? (0, import_node_path20.join)(__dirname4, "..", "web");
56753
56794
  var PORT2 = Number(process.env.PORT ?? 4317);
56754
56795
  var HOST = "127.0.0.1";
56755
56796
  function extractReportHtml(events) {
@@ -56776,7 +56817,7 @@ function installCrashHandlers() {
56776
56817
  ${stack}
56777
56818
  `;
56778
56819
  try {
56779
- (0, import_node_fs20.appendFileSync)(logFilePath(), line);
56820
+ (0, import_node_fs21.appendFileSync)(logFilePath(), line);
56780
56821
  } catch {
56781
56822
  }
56782
56823
  if (process.stderr.isTTY) process.stderr.write(line);
@@ -57121,13 +57162,13 @@ async function startServer() {
57121
57162
  }
57122
57163
  const inputs = appData.inputs.map((i) => ({ name: i.name, type: i.type }));
57123
57164
  const baked = bakeFloSource(appData.source.text, inputs);
57124
- const tmpRoot = (0, import_node_fs20.mkdtempSync)((0, import_node_path19.join)((0, import_node_os13.tmpdir)(), "floless-bake-"));
57125
- const backupDir = (0, import_node_path19.join)(tmpRoot, `${id}-backup`);
57126
- const bakeDir = (0, import_node_path19.join)(tmpRoot, id);
57127
- (0, import_node_fs20.cpSync)(appDir(id), backupDir, { recursive: true });
57128
- (0, import_node_fs20.cpSync)(appDir(id), bakeDir, { recursive: true });
57165
+ const tmpRoot = (0, import_node_fs21.mkdtempSync)((0, import_node_path20.join)((0, import_node_os13.tmpdir)(), "floless-bake-"));
57166
+ const backupDir = (0, import_node_path20.join)(tmpRoot, `${id}-backup`);
57167
+ const bakeDir = (0, import_node_path20.join)(tmpRoot, id);
57168
+ (0, import_node_fs21.cpSync)(appDir(id), backupDir, { recursive: true });
57169
+ (0, import_node_fs21.cpSync)(appDir(id), bakeDir, { recursive: true });
57129
57170
  const floName = appData.source.path.split(/[\\/]/).pop();
57130
- (0, import_node_fs20.writeFileSync)((0, import_node_path19.join)(bakeDir, floName), baked);
57171
+ (0, import_node_fs21.writeFileSync)((0, import_node_path20.join)(bakeDir, floName), baked);
57131
57172
  let appInstalled = true;
57132
57173
  try {
57133
57174
  await aware.uninstall(id);
@@ -57149,17 +57190,17 @@ async function startServer() {
57149
57190
  throw installErr;
57150
57191
  }
57151
57192
  try {
57152
- await aware.compile((0, import_node_path19.join)(appDir(id), floName));
57193
+ await aware.compile((0, import_node_path20.join)(appDir(id), floName));
57153
57194
  } catch (compileErr) {
57154
57195
  app.log.warn({ id, compileErr: String(compileErr) }, "bake: post-install recompile failed (app baked but may need a manual Compile)");
57155
57196
  }
57156
57197
  broadcast({ type: "baked", id });
57157
57198
  return { ok: true, id, agent: id, inputs };
57158
57199
  } finally {
57159
- if (appInstalled) (0, import_node_fs20.rmSync)(tmpRoot, { recursive: true, force: true });
57200
+ if (appInstalled) (0, import_node_fs21.rmSync)(tmpRoot, { recursive: true, force: true });
57160
57201
  }
57161
57202
  });
57162
- const graftAgentsDir = () => (0, import_node_path19.join)((0, import_node_os13.homedir)(), ".aware", "agents");
57203
+ const graftAgentsDir = () => (0, import_node_path20.join)((0, import_node_os13.homedir)(), ".aware", "agents");
57163
57204
  app.post("/api/graft/match", async (req, reply) => {
57164
57205
  const { glob } = req.body ?? {};
57165
57206
  if (!glob) return reply.status(400).send({ ok: false, error: "glob required" });
@@ -57176,7 +57217,7 @@ async function startServer() {
57176
57217
  if (!sourceKind || !sourceRef) {
57177
57218
  return reply.status(400).send({ ok: false, error: "sourceKind and sourceRef required" });
57178
57219
  }
57179
- const tempHome = (0, import_node_fs20.mkdtempSync)((0, import_node_path19.join)((0, import_node_os13.tmpdir)(), "floless-graft-"));
57220
+ const tempHome = (0, import_node_fs21.mkdtempSync)((0, import_node_path20.join)((0, import_node_os13.tmpdir)(), "floless-graft-"));
57180
57221
  let result;
57181
57222
  try {
57182
57223
  result = await aware.build({
@@ -57189,19 +57230,19 @@ async function startServer() {
57189
57230
  awareHome: tempHome
57190
57231
  });
57191
57232
  } catch (err) {
57192
- (0, import_node_fs20.rmSync)(tempHome, { recursive: true, force: true });
57233
+ (0, import_node_fs21.rmSync)(tempHome, { recursive: true, force: true });
57193
57234
  const msg = err instanceof AwareError ? err.message : String(err?.message ?? err);
57194
57235
  return reply.status(422).send({ ok: false, error: msg });
57195
57236
  }
57196
57237
  const manifest = readStagedManifest(result.agentDir);
57197
57238
  if (!manifest) {
57198
- (0, import_node_fs20.rmSync)(tempHome, { recursive: true, force: true });
57239
+ (0, import_node_fs21.rmSync)(tempHome, { recursive: true, force: true });
57199
57240
  return reply.status(502).send({ ok: false, error: `build produced output at ${result.agentDir} but no manifest.yaml` });
57200
57241
  }
57201
57242
  const token = (0, import_node_crypto6.randomUUID)();
57202
57243
  registerStage(token, tempHome, result.agentId);
57203
57244
  const preview = buildPreview(manifest, sourceKind, sourceRef, token);
57204
- if ((0, import_node_fs20.existsSync)((0, import_node_path19.join)(graftAgentsDir(), result.agentId))) {
57245
+ if ((0, import_node_fs21.existsSync)((0, import_node_path20.join)(graftAgentsDir(), result.agentId))) {
57205
57246
  preview.warnings.unshift(`An agent named "${result.agentId}" is already installed \u2014 creating it will overwrite it.`);
57206
57247
  }
57207
57248
  return { ok: true, preview };
@@ -57220,7 +57261,7 @@ async function startServer() {
57220
57261
  registerStage(stagedRef, stage.tempDir, stage.agentId);
57221
57262
  return reply.status(409).send({ ok: false, error: err.message, agentId: stage.agentId, collision: true });
57222
57263
  }
57223
- (0, import_node_fs20.rmSync)(stage.tempDir, { recursive: true, force: true });
57264
+ (0, import_node_fs21.rmSync)(stage.tempDir, { recursive: true, force: true });
57224
57265
  throw err;
57225
57266
  }
57226
57267
  broadcast({ type: "grafted", id: stage.agentId });
@@ -57394,11 +57435,11 @@ async function startServer() {
57394
57435
  app.get("/api/requests/:id/snapshot/:n", async (req, reply) => {
57395
57436
  const n = Number.parseInt(req.params.n, 10);
57396
57437
  const p = Number.isInteger(n) ? snapshotPathFor(req.params.id, n) : null;
57397
- if (!p || !(0, import_node_fs20.existsSync)(p)) return reply.status(404).send({ ok: false, error: "snapshot not found" });
57438
+ if (!p || !(0, import_node_fs21.existsSync)(p)) return reply.status(404).send({ ok: false, error: "snapshot not found" });
57398
57439
  const ext = p.split(".").pop().toLowerCase();
57399
57440
  reply.header("Content-Type", ext === "png" ? "image/png" : ext === "webp" ? "image/webp" : "image/jpeg");
57400
57441
  reply.header("Cache-Control", "no-store");
57401
- return (0, import_node_fs20.readFileSync)(p);
57442
+ return (0, import_node_fs21.readFileSync)(p);
57402
57443
  });
57403
57444
  app.post(
57404
57445
  "/api/tweak",
@@ -57492,7 +57533,7 @@ async function startServer() {
57492
57533
  warn(`last-run-status:${ref} \u2014 trace exists but couldn't be parsed (corrupt/truncated)`);
57493
57534
  let finishedAt2 = null;
57494
57535
  try {
57495
- finishedAt2 = (0, import_node_fs20.statSync)(latest.path).mtime.toISOString();
57536
+ finishedAt2 = (0, import_node_fs21.statSync)(latest.path).mtime.toISOString();
57496
57537
  } catch {
57497
57538
  finishedAt2 = null;
57498
57539
  }
@@ -57502,7 +57543,7 @@ async function startServer() {
57502
57543
  let finishedAt = typeof runEnd?.ts === "string" ? runEnd.ts : null;
57503
57544
  if (!finishedAt) {
57504
57545
  try {
57505
- finishedAt = (0, import_node_fs20.statSync)(latest.path).mtime.toISOString();
57546
+ finishedAt = (0, import_node_fs21.statSync)(latest.path).mtime.toISOString();
57506
57547
  } catch {
57507
57548
  finishedAt = null;
57508
57549
  }
package/dist/web/app.css CHANGED
@@ -1935,6 +1935,25 @@ body {
1935
1935
  .run-state.drift { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 40%, transparent); }
1936
1936
  .run-state.uncompiled { color: var(--text-muted); border-color: var(--border-strong); }
1937
1937
 
1938
+ /* Header Stop — the always-reachable escape hatch during a run, regardless of whether
1939
+ the HTML Viewer modal is open or closed (#39). Shown only while a run is in flight.
1940
+ Mirrors the in-modal .overlay-stop: a quiet destructive action — warn-tinted outline
1941
+ that fills on hover, never a primary. Inherits the header button's size/radius/font;
1942
+ only the colours are overridden here. */
1943
+ .run-stop-btn {
1944
+ background: transparent;
1945
+ color: var(--warn);
1946
+ border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent);
1947
+ font-weight: 600;
1948
+ letter-spacing: 0.04em;
1949
+ }
1950
+ .run-stop-btn:hover {
1951
+ background: color-mix(in srgb, var(--warn) 16%, transparent);
1952
+ border-color: var(--warn);
1953
+ color: var(--text);
1954
+ }
1955
+ .run-stop-btn:disabled { opacity: 0.6; cursor: default; }
1956
+
1938
1957
  /* Compile-notes strip over the canvas (CONCERNS §1). These are info-level FYIs
1939
1958
  (e.g. AWARE's read-mode-on-exec note), so they read as info — not a warning —
1940
1959
  and collapse to a faint one-line pill so they don't eat canvas height. */
package/dist/web/aware.js CHANGED
@@ -24,6 +24,7 @@
24
24
  const $reportOpen = document.getElementById('report-open');
25
25
  const $reportShare = document.getElementById('report-share');
26
26
  const $reportClose = document.getElementById('report-close');
27
+ const $stopRunBtn = document.getElementById('stop-run-btn');
27
28
 
28
29
  // One persistent array the inspect Execution tab reads; we mutate in place so
29
30
  // every AGENTS[node].execution reference stays valid across re-renders.
@@ -327,7 +328,10 @@
327
328
  };
328
329
  $runState.dataset.tip = stateTips[app.runState] || '';
329
330
 
330
- $runBtn.disabled = !app.runnable;
331
+ // Keep Run disabled (and the header Stop shown) if a gate re-paint lands mid-run (#39) —
332
+ // never let a re-render re-arm Run while a run is still in flight.
333
+ $runBtn.disabled = !app.runnable || reportRunning || state.running;
334
+ if ($stopRunBtn) $stopRunBtn.hidden = !(reportRunning || state.running);
331
335
  $runBtn.dataset.tip = app.runnable
332
336
  ? 'Run the approved workflow'
333
337
  : app.runState === 'drift'
@@ -896,6 +900,35 @@
896
900
  // double-click LOAD the last result instantly instead of re-running.
897
901
  const lastReportByApp = new Map();
898
902
 
903
+ // Reflect run-in-flight state in the ALWAYS-VISIBLE header so a run is stoppable even
904
+ // when the HTML Viewer modal is closed (#39). The header ■ Stop run appears for the whole
905
+ // duration of either run path (the modal run `reportRunning` and the inline run
906
+ // `state.running`) and disappears when neither is active. Run stays disabled while a run
907
+ // is in flight; when idle it falls back to the compile-gate's runnable state. Also tells
908
+ // the user, on the modal's × tooltip, that closing leaves the run going (Stop is in the
909
+ // header) — the canvas keeps showing progress (markCanvasRunning), by design.
910
+ function syncRunControls() {
911
+ const running = reportRunning || state.running;
912
+ if ($stopRunBtn) {
913
+ $stopRunBtn.hidden = !running;
914
+ // Reset to a fresh, clickable Stop each run; stopRun() flips it to "Cancelling…".
915
+ if (running && !cancelRequested) { $stopRunBtn.disabled = false; $stopRunBtn.textContent = '■ Stop run'; }
916
+ }
917
+ if (running) {
918
+ $runBtn.disabled = true;
919
+ } else {
920
+ const app = currentId && apps.get(currentId);
921
+ $runBtn.disabled = !(app && app.runnable);
922
+ }
923
+ // Rely on the native `disabled` attribute for AT state (paintGate also flips it); a
924
+ // separate aria-disabled would be redundant and could go stale on a gate re-paint.
925
+ if ($reportClose) {
926
+ $reportClose.dataset.tip = reportRunning
927
+ ? 'Close viewer (the run keeps going — ■ Stop run is in the header)'
928
+ : 'Close';
929
+ }
930
+ }
931
+
899
932
  // Stop the in-flight run (the escape hatch for a hung/unattached host). Marks
900
933
  // cancelRequested so the catch shows "cancelled", clears the canvas running
901
934
  // pulse, and asks the server to kill the `aware app run` child. The in-flight
@@ -904,8 +937,10 @@
904
937
  if (!reportRunning && !state.running) return;
905
938
  cancelRequested = true;
906
939
  clearNodeStatus();
940
+ // Reflect "cancelling" on BOTH Stop surfaces — the in-modal overlay and the header.
907
941
  const stopBtn = document.querySelector('.overlay-stop');
908
942
  if (stopBtn) { stopBtn.disabled = true; stopBtn.textContent = 'Cancelling…'; }
943
+ if ($stopRunBtn) { $stopRunBtn.disabled = true; $stopRunBtn.textContent = 'Cancelling…'; }
909
944
  try { await api('/api/run/stop', { method: 'POST' }); } catch { /* the run's own catch surfaces it */ }
910
945
  }
911
946
 
@@ -946,6 +981,7 @@
946
981
  reportRunning = true;
947
982
  cancelRequested = false;
948
983
  markCanvasRunning(); // paints behind the modal; visible once it's closed
984
+ syncRunControls(); // header ■ Stop run + Run disabled — reachable even if the modal is closed (#39)
949
985
 
950
986
  const inputs = currentInputs();
951
987
  const inputBadge = Object.entries(inputs).map(([k, v]) => `${k}=${v}`).join(' · ');
@@ -1004,6 +1040,7 @@
1004
1040
  }
1005
1041
  } finally {
1006
1042
  reportRunning = false;
1043
+ syncRunControls(); // run ended — hide the header Stop, restore Run from the gate
1007
1044
  }
1008
1045
  }
1009
1046
 
@@ -1264,6 +1301,8 @@
1264
1301
  // The Stop button is rebuilt into the overlay each run — delegate so one
1265
1302
  // listener survives every innerHTML swap.
1266
1303
  $reportOverlay.addEventListener('click', (e) => { if (e.target.closest('.overlay-stop')) stopRun(); });
1304
+ // The header ■ Stop run — the always-reachable twin of the overlay Stop (#39).
1305
+ if ($stopRunBtn) $stopRunBtn.onclick = () => stopRun();
1267
1306
  $reportOpen.onclick = () => {
1268
1307
  const html = $reportFrame.srcdoc;
1269
1308
  if (!html) return;
@@ -2064,6 +2103,7 @@
2064
2103
  $runBtn.disabled = true;
2065
2104
  if ($simBtn) $simBtn.disabled = true;
2066
2105
  $runBtn.textContent = '◆ Running…';
2106
+ syncRunControls(); // surface the header ■ Stop run for the inline (no-modal) run too (#39)
2067
2107
  liveTrace.length = 0;
2068
2108
  state.hasRun = true;
2069
2109
  try {
@@ -2102,6 +2142,7 @@
2102
2142
  $runBtn.disabled = false;
2103
2143
  if ($simBtn) $simBtn.disabled = false;
2104
2144
  $runBtn.textContent = '▶ Run workflow';
2145
+ syncRunControls(); // hide the header Stop; reconcile Run with the gate's runnable state
2105
2146
  }
2106
2147
  }
2107
2148
  $runBtn.onclick = () => runApp({ simulate: false });
@@ -2592,7 +2633,7 @@
2592
2633
  // (popover + what's-new). Empty until /api/health reports it → links omitted.
2593
2634
  if (h && h.webBase) webBase = h.webBase;
2594
2635
  const av = document.getElementById('app-version');
2595
- if (av && h && h.appVersion && !shownVersion) { av.textContent = 'v' + h.appVersion; shownVersion = true; }
2636
+ if (av && h && h.appVersion && !shownVersion) { av.textContent = 'FloLess ' + h.appVersion; shownVersion = true; }
2596
2637
  // After the build version is stamped, reveal the relaunch-surviving what's-new
2597
2638
  // panel iff this is the build we just self-updated into (guarded to once).
2598
2639
  maybeShowWhatsNew();
@@ -3716,7 +3757,7 @@
3716
3757
  riAwareVersion = h.awareVersion || '';
3717
3758
  const app = currentId && apps.get(currentId);
3718
3759
  const wf = app ? ` · workflow "${app.displayName || currentId}"` : '';
3719
- $riContext.textContent = `Also sent automatically: FloLess v${h.appVersion || '?'}, AWARE v${h.awareVersion || '?'}${wf}`;
3760
+ $riContext.textContent = `Also sent automatically: FloLess ${h.appVersion || '?'}, AWARE ${h.awareVersion || '?'}${wf}`;
3720
3761
  }).catch(() => { /* keep the generic line on a health blip */ });
3721
3762
  showModal($riModal);
3722
3763
  setTimeout(() => $riTitle.focus(), 0);
@@ -3971,7 +4012,7 @@
3971
4012
  // /api/health is un-gated, so this works even while unlicensed.
3972
4013
  fetch('/api/health', { cache: 'no-store' })
3973
4014
  .then((r) => r.json())
3974
- .then((h) => { if (h && h.appVersion) document.getElementById('lg-version').textContent = 'v' + h.appVersion; })
4015
+ .then((h) => { if (h && h.appVersion) document.getElementById('lg-version').textContent = 'FloLess ' + h.appVersion; })
3975
4016
  .catch(() => { /* version is a nicety; never block the gate on it */ });
3976
4017
  document.getElementById('lg-signin').onclick = async () => {
3977
4018
  const s = document.getElementById('lg-status');
@@ -60,10 +60,11 @@
60
60
  <button id="browse-btn" data-tip="Browse all installed agents">⊞ Agents</button>
61
61
  <button id="routines-btn" data-tip="Routines — run a workflow on a schedule or a live trigger">⏱ Routines</button>
62
62
  <span class="ctl-sep" aria-hidden="true"></span>
63
- <span class="run-state" id="run-state"></span>
63
+ <span class="run-state" id="run-state" role="status" aria-live="polite"></span>
64
64
  <button id="compile-btn" data-tip="Compile + approve → freeze the .lock">⎙ Compile</button>
65
65
  <button id="sim-btn" data-tip="Simulate: stub every node from its output schema — no live host is contacted. Validates the workflow's composition end-to-end, even when the real agents aren't connected yet.">Simulate</button>
66
66
  <button id="run-btn" data-tip="Run the workflow for real against the live host (uses the inputs; renders the report node in the HTML Viewer)">▶ Run workflow</button>
67
+ <button id="stop-run-btn" class="run-stop-btn" type="button" aria-label="Stop the current run" data-tip="Stop the in-flight run (always reachable, even with the HTML Viewer closed)" hidden>■ Stop run</button>
67
68
  </div>
68
69
  </header>
69
70
 
package/launch.mjs CHANGED
@@ -17,6 +17,7 @@ import { createInterface } from 'node:readline';
17
17
  import { rotateLog, openLogFd } from './log.mjs';
18
18
  import { supervisorPidsToKill, teardownDecision, awareIsPresent, RUN_KEY, RUN_VALUE, PROTOCOL_KEY } from './teardown.mjs';
19
19
  import { resolveRealInstallExe } from './install-path.mjs';
20
+ import { consumePostUpdateMarker } from './post-update-marker.mjs';
20
21
 
21
22
  const __dirname = dirname(fileURLToPath(import.meta.url));
22
23
  const PORT = Number(process.env.PORT ?? 4317);
@@ -163,6 +164,11 @@ async function ensureServerUp() {
163
164
  }
164
165
 
165
166
  export async function cmdOpen() {
167
+ // A post-self-update relaunch must start the server but NOT open a second browser tab —
168
+ // Velopack relaunches FlolessApp.exe bare (→ this `open` action), while the user's existing
169
+ // tab is still alive and reconnects via its 5s health poll (#37). Consume the one-shot
170
+ // marker the updater dropped before the Update.exe handoff; if present, behave like `start`.
171
+ const postUpdate = consumePostUpdateMarker();
166
172
  if (await ping()) {
167
173
  // Something is already serving the port. If it's a DIFFERENT, OLDER floless.app build than
168
174
  // this one, take it over ("newest wins") so the version the user just launched is the one
@@ -174,11 +180,15 @@ export async function cmdOpen() {
174
180
  await new Promise((r) => setTimeout(r, 500)); // let the OS release the port
175
181
  await ensureServerUp();
176
182
  } else {
177
- log(`already running${running ? ` (v${running})` : ''} — opening browser`);
183
+ log(`already running${running ? ` (v${running})` : ''}`);
178
184
  }
179
185
  } else {
180
186
  await ensureServerUp();
181
187
  }
188
+ if (postUpdate) {
189
+ log('post-update relaunch — server up; the existing tab reconnects via its health poll (no new tab)');
190
+ return;
191
+ }
182
192
  log(`opening ${BROWSER_URL}`);
183
193
  openBrowser(BROWSER_URL);
184
194
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floless/app",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "type": "module",
5
5
  "description": "Thin localhost host for floless.app — serves web/ and shells the aware CLI. No engine, no LLM.",
6
6
  "bin": {