@floless/app 0.12.0 → 0.12.2

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;
@@ -43012,7 +43012,7 @@ var require_static = __commonJS({
43012
43012
  "use strict";
43013
43013
  var path = require("node:path");
43014
43014
  var { fileURLToPath: fileURLToPath4 } = require("node:url");
43015
- var { statSync: statSync7 } = require("node:fs");
43015
+ var { statSync: statSync8 } = require("node:fs");
43016
43016
  var { glob } = require_commonjs6();
43017
43017
  var fp = require_plugin2();
43018
43018
  var send = require_send2();
@@ -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) {
@@ -43375,7 +43375,7 @@ var require_static = __commonJS({
43375
43375
  }
43376
43376
  let pathStat;
43377
43377
  try {
43378
- pathStat = statSync7(rootPath);
43378
+ pathStat = statSync8(rootPath);
43379
43379
  } catch (e) {
43380
43380
  if (e.code === "ENOENT") {
43381
43381
  fastify.log.warn(`"root" path "${rootPath}" must exist`);
@@ -43399,7 +43399,7 @@ var require_static = __commonJS({
43399
43399
  return indexFiles.find((filename) => {
43400
43400
  const p = path.join(root, pathname, filename);
43401
43401
  try {
43402
- const stats = statSync7(p);
43402
+ const stats = statSync8(p);
43403
43403
  return !stats.isDirectory();
43404
43404
  } catch {
43405
43405
  return false;
@@ -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");
50831
- var import_node_os13 = require("node:os");
50832
- var import_node_fs20 = require("node:fs");
50830
+ var import_node_path20 = require("node:path");
50831
+ var import_node_os14 = require("node:os");
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.2" : 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.2" : 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,58 @@ 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");
54042
+ var import_node_os11 = require("node:os");
54032
54043
  var import_node_path14 = require("node:path");
54044
+ var FRESH_MS = 12e4;
54045
+ function markerPath() {
54046
+ const override = (process.env.FLOLESS_POST_UPDATE_MARKER ?? "").trim();
54047
+ if (override) return override;
54048
+ const root = process.env.FLOLESS_HOME ?? (0, import_node_path14.join)((0, import_node_os11.homedir)(), ".floless");
54049
+ return (0, import_node_path14.join)(root, ".post-update");
54050
+ }
54051
+ function legacyMarkerPath() {
54052
+ if ((process.env.FLOLESS_POST_UPDATE_MARKER ?? "").trim()) return null;
54053
+ try {
54054
+ return (0, import_node_path14.join)((0, import_node_path14.dirname)((0, import_node_path14.dirname)(process.execPath)), ".floless-post-update");
54055
+ } catch {
54056
+ return null;
54057
+ }
54058
+ }
54059
+ function writePostUpdateMarker() {
54060
+ let wrote = false;
54061
+ for (const p of [markerPath(), legacyMarkerPath()]) {
54062
+ if (!p) continue;
54063
+ try {
54064
+ (0, import_node_fs16.writeFileSync)(p, (/* @__PURE__ */ new Date()).toISOString());
54065
+ wrote = true;
54066
+ } catch {
54067
+ }
54068
+ }
54069
+ return wrote;
54070
+ }
54071
+ function consumePostUpdateMarker() {
54072
+ let fresh = false;
54073
+ for (const p of [markerPath(), legacyMarkerPath()]) {
54074
+ if (!p) continue;
54075
+ try {
54076
+ if (!(0, import_node_fs16.existsSync)(p)) continue;
54077
+ const ageMs = Date.now() - (0, import_node_fs16.statSync)(p).mtimeMs;
54078
+ (0, import_node_fs16.rmSync)(p, { force: true });
54079
+ if (ageMs < FRESH_MS) fresh = true;
54080
+ } catch {
54081
+ }
54082
+ }
54083
+ return fresh;
54084
+ }
54085
+
54086
+ // updater.ts
54033
54087
  var CHANNEL = "win";
54034
54088
  var FEED_TIMEOUT_MS = 15e3;
54035
54089
  var DOWNLOAD_TIMEOUT_MS = 3e5;
@@ -54038,13 +54092,13 @@ function currentVersion() {
54038
54092
  return appVersion();
54039
54093
  }
54040
54094
  function installRoot() {
54041
- return (0, import_node_path14.dirname)((0, import_node_path14.dirname)(process.execPath));
54095
+ return (0, import_node_path15.dirname)((0, import_node_path15.dirname)(process.execPath));
54042
54096
  }
54043
54097
  function updateExePath() {
54044
- return (0, import_node_path14.join)(installRoot(), "Update.exe");
54098
+ return (0, import_node_path15.join)(installRoot(), "Update.exe");
54045
54099
  }
54046
54100
  function packagesDir() {
54047
- return (0, import_node_path14.join)(installRoot(), "packages");
54101
+ return (0, import_node_path15.join)(installRoot(), "packages");
54048
54102
  }
54049
54103
  function feedUrl() {
54050
54104
  const env2 = (process.env.FLOLESS_UPDATE_URL ?? "").trim().replace(/\/+$/, "");
@@ -54137,22 +54191,22 @@ async function checkForUpdate() {
54137
54191
  }
54138
54192
  async function sha1OfFile(path) {
54139
54193
  const hash = (0, import_node_crypto5.createHash)("sha1");
54140
- await (0, import_promises.pipeline)((0, import_node_fs16.createReadStream)(path), hash);
54194
+ await (0, import_promises.pipeline)((0, import_node_fs17.createReadStream)(path), hash);
54141
54195
  return hash.digest("hex").toUpperCase();
54142
54196
  }
54143
54197
  async function downloadPackage(asset) {
54144
54198
  if (!NUPKG_NAME.test(asset.FileName)) throw new Error(`refusing suspicious package name: ${asset.FileName}`);
54145
54199
  const want = asset.SHA1.toUpperCase();
54146
54200
  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;
54201
+ (0, import_node_fs17.mkdirSync)(dir, { recursive: true });
54202
+ const dest = (0, import_node_path15.join)(dir, asset.FileName);
54203
+ if ((0, import_node_fs17.existsSync)(dest) && await sha1OfFile(dest) === want) return dest;
54150
54204
  const res = await authedFetch(`${feedUrl()}/${encodeURIComponent(asset.FileName)}`, {
54151
54205
  redirect: "follow",
54152
54206
  signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS)
54153
54207
  });
54154
54208
  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));
54209
+ await (0, import_promises.pipeline)(import_node_stream.Readable.fromWeb(res.body), (0, import_node_fs17.createWriteStream)(dest));
54156
54210
  const got = await sha1OfFile(dest);
54157
54211
  if (got !== want) throw new Error(`SHA1 mismatch for ${asset.FileName}: feed=${want} got=${got}`);
54158
54212
  return dest;
@@ -54161,7 +54215,7 @@ async function applyUpdate(check, opts) {
54161
54215
  if (!check.supported) return { applied: false, message: check.reason ?? "auto-update not supported in this runtime" };
54162
54216
  if (!check.updateAvailable || !check.asset) return { applied: false, message: check.reason ?? "no update available" };
54163
54217
  const exe = updateExePath();
54164
- if (!(0, import_node_fs16.existsSync)(exe)) {
54218
+ if (!(0, import_node_fs17.existsSync)(exe)) {
54165
54219
  return { applied: false, message: `Update.exe not found at ${exe} \u2014 is this a Velopack install?` };
54166
54220
  }
54167
54221
  const pkg = await downloadPackage(check.asset);
@@ -54179,6 +54233,7 @@ async function applyUpdate(check, opts) {
54179
54233
  resolve5();
54180
54234
  });
54181
54235
  });
54236
+ writePostUpdateMarker();
54182
54237
  return { applied: true, message: `updating to v${check.targetVersion}\u2026 the app will relaunch` };
54183
54238
  }
54184
54239
 
@@ -54369,12 +54424,12 @@ function isTraceCorrupt(events) {
54369
54424
 
54370
54425
  // launch.mjs
54371
54426
  var import_node_child_process5 = require("node:child_process");
54372
- var import_node_path15 = require("node:path");
54427
+ var import_node_path16 = require("node:path");
54373
54428
  var import_node_url = require("node:url");
54374
- var import_node_fs17 = require("node:fs");
54429
+ var import_node_fs18 = require("node:fs");
54375
54430
  var import_node_http = __toESM(require("node:http"), 1);
54376
54431
  var import_node_readline = require("node:readline");
54377
- var __dirname2 = (0, import_node_path15.dirname)((0, import_node_url.fileURLToPath)(__import_meta_url));
54432
+ var __dirname2 = (0, import_node_path16.dirname)((0, import_node_url.fileURLToPath)(__import_meta_url));
54378
54433
  var PORT = Number(process.env.PORT ?? 4317);
54379
54434
  var HEALTH_URL = `http://127.0.0.1:${PORT}/api/health`;
54380
54435
  var BROWSER_URL = `http://floless.localhost:${PORT}`;
@@ -54446,8 +54501,8 @@ async function waitHealthy(timeoutMs = 3e4) {
54446
54501
  function resolveServerStart() {
54447
54502
  const packaged = /flolessapp\.exe$/i.test(process.execPath);
54448
54503
  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 };
54504
+ const bundle = (0, import_node_path16.join)(__dirname2, "dist", "floless-server.cjs");
54505
+ if ((0, import_node_fs18.existsSync)(bundle)) return { cmd: process.execPath, args: [bundle, "--serve"], shell: false };
54451
54506
  return { cmd: "npm", args: ["run", "start"], shell: isWin2 };
54452
54507
  }
54453
54508
  function startServerDetached() {
@@ -54495,6 +54550,7 @@ async function ensureServerUp() {
54495
54550
  log("server up");
54496
54551
  }
54497
54552
  async function cmdOpen() {
54553
+ const postUpdate = consumePostUpdateMarker();
54498
54554
  if (await ping()) {
54499
54555
  const running = await probeVersion();
54500
54556
  if (shouldTakeOver(running, _selfVersion)) {
@@ -54503,11 +54559,15 @@ async function cmdOpen() {
54503
54559
  await new Promise((r) => setTimeout(r, 500));
54504
54560
  await ensureServerUp();
54505
54561
  } else {
54506
- log(`already running${running ? ` (v${running})` : ""} \u2014 opening browser`);
54562
+ log(`already running${running ? ` (v${running})` : ""}`);
54507
54563
  }
54508
54564
  } else {
54509
54565
  await ensureServerUp();
54510
54566
  }
54567
+ if (postUpdate) {
54568
+ log("post-update relaunch \u2014 server up; the existing tab reconnects via its health poll (no new tab)");
54569
+ return;
54570
+ }
54511
54571
  log(`opening ${BROWSER_URL}`);
54512
54572
  openBrowser2(BROWSER_URL);
54513
54573
  }
@@ -54595,8 +54655,8 @@ function taskkillArgs(pid, { tree = true } = {}) {
54595
54655
  }
54596
54656
  function killSupervisor({ tree = true } = {}) {
54597
54657
  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;
54658
+ const isNpmChannel = /^node(\.exe)?$/i.test((0, import_node_path16.basename)(process.execPath));
54659
+ const scriptMatch = isNpmChannel ? (0, import_node_path16.basename)((0, import_node_url.fileURLToPath)(__import_meta_url)) : void 0;
54600
54660
  const realExe = resolveRealInstallExe(process.execPath);
54601
54661
  const exeMatch = realExe === process.execPath ? process.execPath : [process.execPath, realExe];
54602
54662
  const pids = supervisorPidsToKill(enumerateProcesses(), process.pid, exeMatch, scriptMatch);
@@ -54761,7 +54821,7 @@ async function runAction(arg, flagArgv = [], selfVersion = null) {
54761
54821
  }
54762
54822
  await action(parseTeardownFlags(flagArgv));
54763
54823
  }
54764
- var entry = (0, import_node_path15.basename)(process.argv[1] ?? "").toLowerCase();
54824
+ var entry = (0, import_node_path16.basename)(process.argv[1] ?? "").toLowerCase();
54765
54825
  if (entry === "launch.mjs") {
54766
54826
  runAction(process.argv[2], process.argv.slice(3)).catch((e) => {
54767
54827
  log(`error: ${e?.message ?? e}`);
@@ -54835,9 +54895,9 @@ function awareUpgradeBlockReason(s) {
54835
54895
  }
54836
54896
 
54837
54897
  // skill-sync.ts
54838
- var import_node_fs18 = require("node:fs");
54839
- var import_node_os11 = require("node:os");
54840
- var import_node_path16 = require("node:path");
54898
+ var import_node_fs19 = require("node:fs");
54899
+ var import_node_os12 = require("node:os");
54900
+ var import_node_path17 = require("node:path");
54841
54901
  var import_node_url2 = require("node:url");
54842
54902
  var import_yaml5 = __toESM(require_dist6(), 1);
54843
54903
 
@@ -54862,30 +54922,30 @@ function selectShippedSkillNames(names) {
54862
54922
  }
54863
54923
 
54864
54924
  // skill-sync.ts
54865
- var __dirname3 = (0, import_node_path16.dirname)((0, import_node_url2.fileURLToPath)(__import_meta_url));
54925
+ var __dirname3 = (0, import_node_path17.dirname)((0, import_node_url2.fileURLToPath)(__import_meta_url));
54866
54926
  function bundledSkillsRoot() {
54867
54927
  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")
54928
+ (0, import_node_path17.join)(__dirname3, "skills"),
54929
+ (0, import_node_path17.join)((0, import_node_path17.dirname)(process.execPath), "skills"),
54930
+ (0, import_node_path17.join)(__dirname3, "..", ".claude", "skills")
54871
54931
  ];
54872
- return candidates.find((p) => (0, import_node_fs18.existsSync)(p)) ?? null;
54932
+ return candidates.find((p) => (0, import_node_fs19.existsSync)(p)) ?? null;
54873
54933
  }
54874
54934
  function targetConfigDirs() {
54875
54935
  const override = process.env.FLOLESS_SKILL_TARGETS;
54876
54936
  if (override) {
54877
54937
  return override.split(";").map((d) => d.trim()).filter(Boolean).map((dir) => ({ runtime: "custom", dir }));
54878
54938
  }
54879
- const home = (0, import_node_os11.homedir)();
54939
+ const home = (0, import_node_os12.homedir)();
54880
54940
  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") }
54941
+ { runtime: "claude", dir: (0, import_node_path17.join)(home, ".claude") },
54942
+ { runtime: "codex", dir: (0, import_node_path17.join)(home, ".codex") },
54943
+ { runtime: "opencode", dir: (0, import_node_path17.join)(home, ".opencode") }
54884
54944
  ];
54885
54945
  }
54886
54946
  function skillVersion(skillMdPath) {
54887
54947
  try {
54888
- const text = (0, import_node_fs18.readFileSync)(skillMdPath, "utf8");
54948
+ const text = (0, import_node_fs19.readFileSync)(skillMdPath, "utf8");
54889
54949
  const m = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text);
54890
54950
  if (!m || m[1] === void 0) return null;
54891
54951
  const fm = (0, import_yaml5.parse)(m[1]);
@@ -54925,21 +54985,21 @@ function decideAction(installed, bundled) {
54925
54985
  function bundledSkills(root) {
54926
54986
  let entries = [];
54927
54987
  try {
54928
- entries = selectShippedSkillNames((0, import_node_fs18.readdirSync)(root));
54988
+ entries = selectShippedSkillNames((0, import_node_fs19.readdirSync)(root));
54929
54989
  } catch {
54930
54990
  return [];
54931
54991
  }
54932
54992
  const out = [];
54933
54993
  for (const name of entries) {
54934
- const dir = (0, import_node_path16.join)(root, name);
54994
+ const dir = (0, import_node_path17.join)(root, name);
54935
54995
  let isDir = false;
54936
54996
  try {
54937
- isDir = (0, import_node_fs18.statSync)(dir).isDirectory();
54997
+ isDir = (0, import_node_fs19.statSync)(dir).isDirectory();
54938
54998
  } catch {
54939
54999
  isDir = false;
54940
55000
  }
54941
55001
  if (!isDir) continue;
54942
- const v = skillVersion((0, import_node_path16.join)(dir, "SKILL.md"));
55002
+ const v = skillVersion((0, import_node_path17.join)(dir, "SKILL.md"));
54943
55003
  if (!v) continue;
54944
55004
  out.push({ name, dir, version: v });
54945
55005
  }
@@ -54952,17 +55012,17 @@ function syncSkills() {
54952
55012
  const skills = bundledSkills(root);
54953
55013
  if (!skills.length) return results;
54954
55014
  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");
55015
+ if (!(0, import_node_fs19.existsSync)(cfg)) continue;
55016
+ const skillsDir = (0, import_node_path17.join)(cfg, "skills");
54957
55017
  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;
55018
+ const dest = (0, import_node_path17.join)(skillsDir, s.name);
55019
+ const installedMd = (0, import_node_path17.join)(dest, "SKILL.md");
55020
+ const installed = (0, import_node_fs19.existsSync)(installedMd) ? skillVersion(installedMd) : null;
54961
55021
  const action = decideAction(installed, s.version);
54962
55022
  if (action === "installed" || action === "updated") {
54963
55023
  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 });
55024
+ if (action === "updated") (0, import_node_fs19.rmSync)(dest, { recursive: true, force: true });
55025
+ (0, import_node_fs19.cpSync)(s.dir, dest, { recursive: true });
54966
55026
  results.push({ runtime, skill: s.name, action, from: installed, to: s.version });
54967
55027
  } catch {
54968
55028
  }
@@ -54975,9 +55035,9 @@ function syncSkills() {
54975
55035
  }
54976
55036
 
54977
55037
  // watch.ts
54978
- var import_node_os12 = require("node:os");
54979
- var import_node_path18 = require("node:path");
54980
- var import_node_fs19 = require("node:fs");
55038
+ var import_node_os13 = require("node:os");
55039
+ var import_node_path19 = require("node:path");
55040
+ var import_node_fs20 = require("node:fs");
54981
55041
 
54982
55042
  // node_modules/chokidar/esm/index.js
54983
55043
  var import_fs2 = require("fs");
@@ -54988,7 +55048,7 @@ var sysPath2 = __toESM(require("path"), 1);
54988
55048
  // node_modules/readdirp/esm/index.js
54989
55049
  var import_promises2 = require("node:fs/promises");
54990
55050
  var import_node_stream2 = require("node:stream");
54991
- var import_node_path17 = require("node:path");
55051
+ var import_node_path18 = require("node:path");
54992
55052
  var EntryTypes = {
54993
55053
  FILE_TYPE: "files",
54994
55054
  DIR_TYPE: "directories",
@@ -55063,7 +55123,7 @@ var ReaddirpStream = class extends import_node_stream2.Readable {
55063
55123
  this._wantsDir = type ? DIR_TYPES.has(type) : false;
55064
55124
  this._wantsFile = type ? FILE_TYPES.has(type) : false;
55065
55125
  this._wantsEverything = type === EntryTypes.EVERYTHING_TYPE;
55066
- this._root = (0, import_node_path17.resolve)(root);
55126
+ this._root = (0, import_node_path18.resolve)(root);
55067
55127
  this._isDirent = !opts.alwaysStat;
55068
55128
  this._statsProp = this._isDirent ? "dirent" : "stats";
55069
55129
  this._rdOptions = { encoding: "utf8", withFileTypes: this._isDirent };
@@ -55134,8 +55194,8 @@ var ReaddirpStream = class extends import_node_stream2.Readable {
55134
55194
  let entry2;
55135
55195
  const basename5 = this._isDirent ? dirent.name : dirent;
55136
55196
  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 };
55197
+ const fullPath = (0, import_node_path18.resolve)((0, import_node_path18.join)(path, basename5));
55198
+ entry2 = { path: (0, import_node_path18.relative)(this._root, fullPath), fullPath, basename: basename5 };
55139
55199
  entry2[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
55140
55200
  } catch (err) {
55141
55201
  this._onError(err);
@@ -55169,7 +55229,7 @@ var ReaddirpStream = class extends import_node_stream2.Readable {
55169
55229
  }
55170
55230
  if (entryRealPathStats.isDirectory()) {
55171
55231
  const len = entryRealPath.length;
55172
- if (full.startsWith(entryRealPath) && full.substr(len, 1) === import_node_path17.sep) {
55232
+ if (full.startsWith(entryRealPath) && full.substr(len, 1) === import_node_path18.sep) {
55173
55233
  const recursiveError = new Error(`Circular symlink detected: "${full}" points to "${entryRealPath}"`);
55174
55234
  recursiveError.code = RECURSIVE_ERROR_CODE;
55175
55235
  return this._onError(recursiveError);
@@ -55708,9 +55768,9 @@ var NodeFsHandler = class {
55708
55768
  if (this.fsw.closed) {
55709
55769
  return;
55710
55770
  }
55711
- const dirname9 = sysPath.dirname(file);
55771
+ const dirname10 = sysPath.dirname(file);
55712
55772
  const basename5 = sysPath.basename(file);
55713
- const parent = this.fsw._getWatchedDir(dirname9);
55773
+ const parent = this.fsw._getWatchedDir(dirname10);
55714
55774
  let prevStats = stats;
55715
55775
  if (parent.has(basename5))
55716
55776
  return;
@@ -55737,7 +55797,7 @@ var NodeFsHandler = class {
55737
55797
  prevStats = newStats2;
55738
55798
  }
55739
55799
  } catch (error) {
55740
- this.fsw._remove(dirname9, basename5);
55800
+ this.fsw._remove(dirname10, basename5);
55741
55801
  }
55742
55802
  } else if (parent.has(basename5)) {
55743
55803
  const at = newStats.atimeMs;
@@ -56677,33 +56737,33 @@ function appIdFromLogPath(path) {
56677
56737
  return i >= 0 && parts[i + 1] ? parts[i + 1] : null;
56678
56738
  }
56679
56739
  function samePath(a, b) {
56680
- const ra = (0, import_node_path18.resolve)(a);
56681
- const rb = (0, import_node_path18.resolve)(b);
56740
+ const ra = (0, import_node_path19.resolve)(a);
56741
+ const rb = (0, import_node_path19.resolve)(b);
56682
56742
  return process.platform === "win32" ? ra.toLowerCase() === rb.toLowerCase() : ra === rb;
56683
56743
  }
56684
56744
  function underDir(path, dir) {
56685
- const rp = (0, import_node_path18.resolve)(path);
56686
- const rd = (0, import_node_path18.resolve)(dir);
56745
+ const rp = (0, import_node_path19.resolve)(path);
56746
+ const rd = (0, import_node_path19.resolve)(dir);
56687
56747
  const [p, d] = process.platform === "win32" ? [rp.toLowerCase(), rd.toLowerCase()] : [rp, rd];
56688
- return p === d || p.startsWith(d + import_node_path18.sep);
56748
+ return p === d || p.startsWith(d + import_node_path19.sep);
56689
56749
  }
56690
56750
  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)) {
56751
+ const awareDir = process.env.AWARE_HOME ?? (0, import_node_path19.join)((0, import_node_os13.homedir)(), ".aware");
56752
+ const credentialsDir = (0, import_node_path19.join)(awareDir, "credentials");
56753
+ if (!(0, import_node_fs20.existsSync)(credentialsDir)) {
56694
56754
  try {
56695
- (0, import_node_fs19.mkdirSync)(credentialsDir, { recursive: true });
56755
+ (0, import_node_fs20.mkdirSync)(credentialsDir, { recursive: true });
56696
56756
  } catch {
56697
56757
  }
56698
56758
  }
56699
- if (!(0, import_node_fs19.existsSync)(uiDir)) {
56759
+ if (!(0, import_node_fs20.existsSync)(uiDir)) {
56700
56760
  try {
56701
- (0, import_node_fs19.mkdirSync)(uiDir, { recursive: true });
56761
+ (0, import_node_fs20.mkdirSync)(uiDir, { recursive: true });
56702
56762
  } catch {
56703
56763
  }
56704
56764
  }
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);
56765
+ const targets = ["apps", "logs", "credentials"].map((d) => (0, import_node_path19.join)(awareDir, d)).filter((p) => (0, import_node_fs20.existsSync)(p));
56766
+ if ((0, import_node_fs20.existsSync)(uiDir)) targets.push(uiDir);
56707
56767
  if (targets.length === 0) {
56708
56768
  return null;
56709
56769
  }
@@ -56733,11 +56793,11 @@ function startWatcher() {
56733
56793
  const isCredential = path.split(/[\\/]/).includes("credentials");
56734
56794
  const kind = isCredential ? "credential" : path.endsWith(".jsonl") ? "trace" : path.endsWith(".lock") ? "lock" : path.endsWith(".flo") || path.endsWith(".app") ? "source" : "file";
56735
56795
  broadcast({ type: "fs-change", kind, event, path });
56736
- if (kind === "trace" && event !== "unlink" && (0, import_node_fs19.existsSync)(path)) {
56796
+ if (kind === "trace" && event !== "unlink" && (0, import_node_fs20.existsSync)(path)) {
56737
56797
  const id = appIdFromLogPath(path);
56738
56798
  if (!id) return;
56739
56799
  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")) });
56800
+ 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
56801
  } catch {
56742
56802
  }
56743
56803
  }
@@ -56746,10 +56806,10 @@ function startWatcher() {
56746
56806
  }
56747
56807
 
56748
56808
  // 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");
56809
+ var __dirname4 = (0, import_node_path20.dirname)((0, import_node_url3.fileURLToPath)(__import_meta_url));
56810
+ 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(
56811
+ (p) => (0, import_node_fs21.existsSync)(p)
56812
+ ) ?? (0, import_node_path20.join)(__dirname4, "..", "web");
56753
56813
  var PORT2 = Number(process.env.PORT ?? 4317);
56754
56814
  var HOST = "127.0.0.1";
56755
56815
  function extractReportHtml(events) {
@@ -56776,7 +56836,7 @@ function installCrashHandlers() {
56776
56836
  ${stack}
56777
56837
  `;
56778
56838
  try {
56779
- (0, import_node_fs20.appendFileSync)(logFilePath(), line);
56839
+ (0, import_node_fs21.appendFileSync)(logFilePath(), line);
56780
56840
  } catch {
56781
56841
  }
56782
56842
  if (process.stderr.isTTY) process.stderr.write(line);
@@ -57121,13 +57181,13 @@ async function startServer() {
57121
57181
  }
57122
57182
  const inputs = appData.inputs.map((i) => ({ name: i.name, type: i.type }));
57123
57183
  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 });
57184
+ const tmpRoot = (0, import_node_fs21.mkdtempSync)((0, import_node_path20.join)((0, import_node_os14.tmpdir)(), "floless-bake-"));
57185
+ const backupDir = (0, import_node_path20.join)(tmpRoot, `${id}-backup`);
57186
+ const bakeDir = (0, import_node_path20.join)(tmpRoot, id);
57187
+ (0, import_node_fs21.cpSync)(appDir(id), backupDir, { recursive: true });
57188
+ (0, import_node_fs21.cpSync)(appDir(id), bakeDir, { recursive: true });
57129
57189
  const floName = appData.source.path.split(/[\\/]/).pop();
57130
- (0, import_node_fs20.writeFileSync)((0, import_node_path19.join)(bakeDir, floName), baked);
57190
+ (0, import_node_fs21.writeFileSync)((0, import_node_path20.join)(bakeDir, floName), baked);
57131
57191
  let appInstalled = true;
57132
57192
  try {
57133
57193
  await aware.uninstall(id);
@@ -57149,17 +57209,17 @@ async function startServer() {
57149
57209
  throw installErr;
57150
57210
  }
57151
57211
  try {
57152
- await aware.compile((0, import_node_path19.join)(appDir(id), floName));
57212
+ await aware.compile((0, import_node_path20.join)(appDir(id), floName));
57153
57213
  } catch (compileErr) {
57154
57214
  app.log.warn({ id, compileErr: String(compileErr) }, "bake: post-install recompile failed (app baked but may need a manual Compile)");
57155
57215
  }
57156
57216
  broadcast({ type: "baked", id });
57157
57217
  return { ok: true, id, agent: id, inputs };
57158
57218
  } finally {
57159
- if (appInstalled) (0, import_node_fs20.rmSync)(tmpRoot, { recursive: true, force: true });
57219
+ if (appInstalled) (0, import_node_fs21.rmSync)(tmpRoot, { recursive: true, force: true });
57160
57220
  }
57161
57221
  });
57162
- const graftAgentsDir = () => (0, import_node_path19.join)((0, import_node_os13.homedir)(), ".aware", "agents");
57222
+ const graftAgentsDir = () => (0, import_node_path20.join)((0, import_node_os14.homedir)(), ".aware", "agents");
57163
57223
  app.post("/api/graft/match", async (req, reply) => {
57164
57224
  const { glob } = req.body ?? {};
57165
57225
  if (!glob) return reply.status(400).send({ ok: false, error: "glob required" });
@@ -57176,7 +57236,7 @@ async function startServer() {
57176
57236
  if (!sourceKind || !sourceRef) {
57177
57237
  return reply.status(400).send({ ok: false, error: "sourceKind and sourceRef required" });
57178
57238
  }
57179
- const tempHome = (0, import_node_fs20.mkdtempSync)((0, import_node_path19.join)((0, import_node_os13.tmpdir)(), "floless-graft-"));
57239
+ const tempHome = (0, import_node_fs21.mkdtempSync)((0, import_node_path20.join)((0, import_node_os14.tmpdir)(), "floless-graft-"));
57180
57240
  let result;
57181
57241
  try {
57182
57242
  result = await aware.build({
@@ -57189,19 +57249,19 @@ async function startServer() {
57189
57249
  awareHome: tempHome
57190
57250
  });
57191
57251
  } catch (err) {
57192
- (0, import_node_fs20.rmSync)(tempHome, { recursive: true, force: true });
57252
+ (0, import_node_fs21.rmSync)(tempHome, { recursive: true, force: true });
57193
57253
  const msg = err instanceof AwareError ? err.message : String(err?.message ?? err);
57194
57254
  return reply.status(422).send({ ok: false, error: msg });
57195
57255
  }
57196
57256
  const manifest = readStagedManifest(result.agentDir);
57197
57257
  if (!manifest) {
57198
- (0, import_node_fs20.rmSync)(tempHome, { recursive: true, force: true });
57258
+ (0, import_node_fs21.rmSync)(tempHome, { recursive: true, force: true });
57199
57259
  return reply.status(502).send({ ok: false, error: `build produced output at ${result.agentDir} but no manifest.yaml` });
57200
57260
  }
57201
57261
  const token = (0, import_node_crypto6.randomUUID)();
57202
57262
  registerStage(token, tempHome, result.agentId);
57203
57263
  const preview = buildPreview(manifest, sourceKind, sourceRef, token);
57204
- if ((0, import_node_fs20.existsSync)((0, import_node_path19.join)(graftAgentsDir(), result.agentId))) {
57264
+ if ((0, import_node_fs21.existsSync)((0, import_node_path20.join)(graftAgentsDir(), result.agentId))) {
57205
57265
  preview.warnings.unshift(`An agent named "${result.agentId}" is already installed \u2014 creating it will overwrite it.`);
57206
57266
  }
57207
57267
  return { ok: true, preview };
@@ -57220,7 +57280,7 @@ async function startServer() {
57220
57280
  registerStage(stagedRef, stage.tempDir, stage.agentId);
57221
57281
  return reply.status(409).send({ ok: false, error: err.message, agentId: stage.agentId, collision: true });
57222
57282
  }
57223
- (0, import_node_fs20.rmSync)(stage.tempDir, { recursive: true, force: true });
57283
+ (0, import_node_fs21.rmSync)(stage.tempDir, { recursive: true, force: true });
57224
57284
  throw err;
57225
57285
  }
57226
57286
  broadcast({ type: "grafted", id: stage.agentId });
@@ -57394,11 +57454,11 @@ async function startServer() {
57394
57454
  app.get("/api/requests/:id/snapshot/:n", async (req, reply) => {
57395
57455
  const n = Number.parseInt(req.params.n, 10);
57396
57456
  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" });
57457
+ if (!p || !(0, import_node_fs21.existsSync)(p)) return reply.status(404).send({ ok: false, error: "snapshot not found" });
57398
57458
  const ext = p.split(".").pop().toLowerCase();
57399
57459
  reply.header("Content-Type", ext === "png" ? "image/png" : ext === "webp" ? "image/webp" : "image/jpeg");
57400
57460
  reply.header("Cache-Control", "no-store");
57401
- return (0, import_node_fs20.readFileSync)(p);
57461
+ return (0, import_node_fs21.readFileSync)(p);
57402
57462
  });
57403
57463
  app.post(
57404
57464
  "/api/tweak",
@@ -57492,7 +57552,7 @@ async function startServer() {
57492
57552
  warn(`last-run-status:${ref} \u2014 trace exists but couldn't be parsed (corrupt/truncated)`);
57493
57553
  let finishedAt2 = null;
57494
57554
  try {
57495
- finishedAt2 = (0, import_node_fs20.statSync)(latest.path).mtime.toISOString();
57555
+ finishedAt2 = (0, import_node_fs21.statSync)(latest.path).mtime.toISOString();
57496
57556
  } catch {
57497
57557
  finishedAt2 = null;
57498
57558
  }
@@ -57502,7 +57562,7 @@ async function startServer() {
57502
57562
  let finishedAt = typeof runEnd?.ts === "string" ? runEnd.ts : null;
57503
57563
  if (!finishedAt) {
57504
57564
  try {
57505
- finishedAt = (0, import_node_fs20.statSync)(latest.path).mtime.toISOString();
57565
+ finishedAt = (0, import_node_fs21.statSync)(latest.path).mtime.toISOString();
57506
57566
  } catch {
57507
57567
  finishedAt = null;
57508
57568
  }
@@ -57704,6 +57764,7 @@ async function main() {
57704
57764
  const realExe = resolveRealInstallExe(process.execPath);
57705
57765
  registerProtocol(realExe);
57706
57766
  registerAutostart(realExe);
57767
+ if (veloHook === "--veloapp-updated") writePostUpdateMarker();
57707
57768
  } else if (veloHook === "--veloapp-uninstall") {
57708
57769
  unregisterProtocol();
57709
57770
  unregisterAutostart();
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'
@@ -787,11 +791,22 @@
787
791
  const saveItem = document.querySelector('.menu-item[data-action="save"]');
788
792
  if (saveItem) saveItem.classList.toggle('menu-item-dirty', dirty);
789
793
  }
794
+ // Set while WE programmatically reload to adopt a self-update (#37) — an in-place build
795
+ // adoption is not the user navigating away, so it must NOT trigger the unsaved-changes
796
+ // prompt below (that would strand the tab behind a modal). The reload path persists the
797
+ // inputs first, so nothing is lost.
798
+ let selfUpdating = false;
790
799
  // Refresh/close guard: unsaved input values live only in memory, so warn before
791
800
  // the page unloads while dirty (the browser shows its own generic confirm).
792
801
  window.addEventListener('beforeunload', (e) => {
793
- if (isInputsDirty(currentId)) { e.preventDefault(); e.returnValue = ''; }
802
+ if (!selfUpdating && isInputsDirty(currentId)) { e.preventDefault(); e.returnValue = ''; }
794
803
  });
804
+ // Persist the active app's inputs to localStorage (the new build restores them on load).
805
+ // Used before a self-update reload so an in-place adoption never drops unsaved edits.
806
+ function persistCurrentInputs() {
807
+ if (!currentId) return;
808
+ try { localStorage.setItem(lsInputsKey(currentId), JSON.stringify(appInputValues.get(currentId) || {})); } catch { /* private mode / quota */ }
809
+ }
795
810
 
796
811
  function seedAppInputs(app) {
797
812
  if (app && Array.isArray(app.inputs) && app.inputs.length) {
@@ -896,6 +911,35 @@
896
911
  // double-click LOAD the last result instantly instead of re-running.
897
912
  const lastReportByApp = new Map();
898
913
 
914
+ // Reflect run-in-flight state in the ALWAYS-VISIBLE header so a run is stoppable even
915
+ // when the HTML Viewer modal is closed (#39). The header ■ Stop run appears for the whole
916
+ // duration of either run path (the modal run `reportRunning` and the inline run
917
+ // `state.running`) and disappears when neither is active. Run stays disabled while a run
918
+ // is in flight; when idle it falls back to the compile-gate's runnable state. Also tells
919
+ // the user, on the modal's × tooltip, that closing leaves the run going (Stop is in the
920
+ // header) — the canvas keeps showing progress (markCanvasRunning), by design.
921
+ function syncRunControls() {
922
+ const running = reportRunning || state.running;
923
+ if ($stopRunBtn) {
924
+ $stopRunBtn.hidden = !running;
925
+ // Reset to a fresh, clickable Stop each run; stopRun() flips it to "Cancelling…".
926
+ if (running && !cancelRequested) { $stopRunBtn.disabled = false; $stopRunBtn.textContent = '■ Stop run'; }
927
+ }
928
+ if (running) {
929
+ $runBtn.disabled = true;
930
+ } else {
931
+ const app = currentId && apps.get(currentId);
932
+ $runBtn.disabled = !(app && app.runnable);
933
+ }
934
+ // Rely on the native `disabled` attribute for AT state (paintGate also flips it); a
935
+ // separate aria-disabled would be redundant and could go stale on a gate re-paint.
936
+ if ($reportClose) {
937
+ $reportClose.dataset.tip = reportRunning
938
+ ? 'Close viewer (the run keeps going — ■ Stop run is in the header)'
939
+ : 'Close';
940
+ }
941
+ }
942
+
899
943
  // Stop the in-flight run (the escape hatch for a hung/unattached host). Marks
900
944
  // cancelRequested so the catch shows "cancelled", clears the canvas running
901
945
  // pulse, and asks the server to kill the `aware app run` child. The in-flight
@@ -904,8 +948,10 @@
904
948
  if (!reportRunning && !state.running) return;
905
949
  cancelRequested = true;
906
950
  clearNodeStatus();
951
+ // Reflect "cancelling" on BOTH Stop surfaces — the in-modal overlay and the header.
907
952
  const stopBtn = document.querySelector('.overlay-stop');
908
953
  if (stopBtn) { stopBtn.disabled = true; stopBtn.textContent = 'Cancelling…'; }
954
+ if ($stopRunBtn) { $stopRunBtn.disabled = true; $stopRunBtn.textContent = 'Cancelling…'; }
909
955
  try { await api('/api/run/stop', { method: 'POST' }); } catch { /* the run's own catch surfaces it */ }
910
956
  }
911
957
 
@@ -946,6 +992,7 @@
946
992
  reportRunning = true;
947
993
  cancelRequested = false;
948
994
  markCanvasRunning(); // paints behind the modal; visible once it's closed
995
+ syncRunControls(); // header ■ Stop run + Run disabled — reachable even if the modal is closed (#39)
949
996
 
950
997
  const inputs = currentInputs();
951
998
  const inputBadge = Object.entries(inputs).map(([k, v]) => `${k}=${v}`).join(' · ');
@@ -1004,6 +1051,7 @@
1004
1051
  }
1005
1052
  } finally {
1006
1053
  reportRunning = false;
1054
+ syncRunControls(); // run ended — hide the header Stop, restore Run from the gate
1007
1055
  }
1008
1056
  }
1009
1057
 
@@ -1264,6 +1312,8 @@
1264
1312
  // The Stop button is rebuilt into the overlay each run — delegate so one
1265
1313
  // listener survives every innerHTML swap.
1266
1314
  $reportOverlay.addEventListener('click', (e) => { if (e.target.closest('.overlay-stop')) stopRun(); });
1315
+ // The header ■ Stop run — the always-reachable twin of the overlay Stop (#39).
1316
+ if ($stopRunBtn) $stopRunBtn.onclick = () => stopRun();
1267
1317
  $reportOpen.onclick = () => {
1268
1318
  const html = $reportFrame.srcdoc;
1269
1319
  if (!html) return;
@@ -2064,6 +2114,7 @@
2064
2114
  $runBtn.disabled = true;
2065
2115
  if ($simBtn) $simBtn.disabled = true;
2066
2116
  $runBtn.textContent = '◆ Running…';
2117
+ syncRunControls(); // surface the header ■ Stop run for the inline (no-modal) run too (#39)
2067
2118
  liveTrace.length = 0;
2068
2119
  state.hasRun = true;
2069
2120
  try {
@@ -2102,6 +2153,7 @@
2102
2153
  $runBtn.disabled = false;
2103
2154
  if ($simBtn) $simBtn.disabled = false;
2104
2155
  $runBtn.textContent = '▶ Run workflow';
2156
+ syncRunControls(); // hide the header Stop; reconcile Run with the gate's runnable state
2105
2157
  }
2106
2158
  }
2107
2159
  $runBtn.onclick = () => runApp({ simulate: false });
@@ -2572,6 +2624,7 @@
2572
2624
  }
2573
2625
  }
2574
2626
  let shownVersion = false;
2627
+ let loadedAppVersion = null; // the build this tab is running against (first health wins)
2575
2628
  function startHealthPoll() {
2576
2629
  const tick = async () => {
2577
2630
  let ok = false;
@@ -2592,7 +2645,25 @@
2592
2645
  // (popover + what's-new). Empty until /api/health reports it → links omitted.
2593
2646
  if (h && h.webBase) webBase = h.webBase;
2594
2647
  const av = document.getElementById('app-version');
2595
- if (av && h && h.appVersion && !shownVersion) { av.textContent = 'v' + h.appVersion; shownVersion = true; }
2648
+ if (av && h && h.appVersion && !shownVersion) { av.textContent = 'FloLess ' + h.appVersion; shownVersion = true; }
2649
+ // Adopt a self-update IN PLACE (#37): this tab loaded build X, but the server now
2650
+ // reports build Y — a self-update swapped the build under us. Reload so the tab runs
2651
+ // the NEW build's web assets (and a fresh footer + what's-new) instead of being
2652
+ // stranded on the old build's UI — stuck "Updating…", a frozen version, version skew.
2653
+ // This is what makes the post-update tab genuinely "reusable": the existing tab
2654
+ // becomes the new-version tab. Only fires on a real version change (first health sets
2655
+ // the baseline), so normal restarts / same-version polls never reload.
2656
+ if (h && h.appVersion) {
2657
+ if (loadedAppVersion === null) loadedAppVersion = h.appVersion;
2658
+ else if (h.appVersion !== loadedAppVersion) {
2659
+ // Persist unsaved inputs and suppress the beforeunload prompt (this is an
2660
+ // in-place build adoption, not the user leaving) before reloading.
2661
+ selfUpdating = true;
2662
+ persistCurrentInputs();
2663
+ location.reload();
2664
+ return;
2665
+ }
2666
+ }
2596
2667
  // After the build version is stamped, reveal the relaunch-surviving what's-new
2597
2668
  // panel iff this is the build we just self-updated into (guarded to once).
2598
2669
  maybeShowWhatsNew();
@@ -3716,7 +3787,7 @@
3716
3787
  riAwareVersion = h.awareVersion || '';
3717
3788
  const app = currentId && apps.get(currentId);
3718
3789
  const wf = app ? ` · workflow "${app.displayName || currentId}"` : '';
3719
- $riContext.textContent = `Also sent automatically: FloLess v${h.appVersion || '?'}, AWARE v${h.awareVersion || '?'}${wf}`;
3790
+ $riContext.textContent = `Also sent automatically: FloLess ${h.appVersion || '?'}, AWARE ${h.awareVersion || '?'}${wf}`;
3720
3791
  }).catch(() => { /* keep the generic line on a health blip */ });
3721
3792
  showModal($riModal);
3722
3793
  setTimeout(() => $riTitle.focus(), 0);
@@ -3971,7 +4042,7 @@
3971
4042
  // /api/health is un-gated, so this works even while unlicensed.
3972
4043
  fetch('/api/health', { cache: 'no-store' })
3973
4044
  .then((r) => r.json())
3974
- .then((h) => { if (h && h.appVersion) document.getElementById('lg-version').textContent = 'v' + h.appVersion; })
4045
+ .then((h) => { if (h && h.appVersion) document.getElementById('lg-version').textContent = 'FloLess ' + h.appVersion; })
3975
4046
  .catch(() => { /* version is a nicety; never block the gate on it */ });
3976
4047
  document.getElementById('lg-signin').onclick = async () => {
3977
4048
  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.2",
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": {