@floless/app 0.6.0 → 0.6.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.
@@ -50826,11 +50826,11 @@ var import_node_child_process8 = require("node:child_process");
50826
50826
  var import_node_readline2 = require("node:readline");
50827
50827
 
50828
50828
  // index.ts
50829
- var import_node_url2 = require("node:url");
50830
- var import_node_path16 = require("node:path");
50829
+ var import_node_url3 = require("node:url");
50830
+ var import_node_path17 = require("node:path");
50831
50831
  var import_node_os11 = require("node:os");
50832
- var import_node_fs17 = require("node:fs");
50833
- var import_node_child_process5 = require("node:child_process");
50832
+ var import_node_fs18 = require("node:fs");
50833
+ var import_node_child_process6 = require("node:child_process");
50834
50834
 
50835
50835
  // log.mjs
50836
50836
  var import_node_path = require("node:path");
@@ -52254,7 +52254,7 @@ function appVersion() {
52254
52254
  return resolveVersion({
52255
52255
  isSea: isSea2(),
52256
52256
  sqVersionXml: readSqVersionXml(),
52257
- define: true ? "0.6.0" : void 0,
52257
+ define: true ? "0.6.2" : void 0,
52258
52258
  pkgVersion: readPkgVersion()
52259
52259
  });
52260
52260
  }
@@ -52264,7 +52264,7 @@ function resolveChannel(s) {
52264
52264
  return "dev";
52265
52265
  }
52266
52266
  function appChannel() {
52267
- return resolveChannel({ isSea: isSea2(), define: true ? "0.6.0" : void 0 });
52267
+ return resolveChannel({ isSea: isSea2(), define: true ? "0.6.2" : void 0 });
52268
52268
  }
52269
52269
 
52270
52270
  // bake.ts
@@ -53733,379 +53733,781 @@ async function applyUpdate(check, opts) {
53733
53733
  return { applied: true, message: `updating to v${check.targetVersion}\u2026 the app will relaunch` };
53734
53734
  }
53735
53735
 
53736
- // aware-update.ts
53737
- var REGISTRY_LATEST = "https://registry.npmjs.org/@aware-aeco/cli/latest";
53738
- var FEED_TIMEOUT_MS2 = 15e3;
53739
- var VERSION_TTL_MS = 5e3;
53740
- var _v = null;
53741
- function awareInstalledVersion(probe = aware.npmVersion) {
53742
- const now = Date.now();
53743
- if (_v && now - _v.at < VERSION_TTL_MS) return _v.val;
53744
- _v = { at: now, val: probe() };
53745
- return _v.val;
53746
- }
53747
- function invalidateAwareVersion() {
53748
- _v = null;
53749
- }
53750
- function versionGt2(a, b) {
53751
- const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
53752
- const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
53753
- for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
53754
- const d = (pa[i] ?? 0) - (pb[i] ?? 0);
53755
- if (d !== 0) return d > 0;
53756
- }
53757
- return false;
53758
- }
53759
- function decideAwareUpdate(cur, latest) {
53760
- const base = { supported: true, currentVersion: cur };
53761
- if (!latest) return { ...base, updateAvailable: false, reason: "could not read the npm registry latest" };
53762
- if (!cur) return { ...base, updateAvailable: false, targetVersion: latest, reason: "AWARE not installed yet" };
53763
- if (versionGt2(latest, cur)) return { ...base, updateAvailable: true, targetVersion: latest };
53764
- return { ...base, updateAvailable: false, targetVersion: latest, reason: `already up to date (v${cur})` };
53765
- }
53766
- async function fetchAwareLatest() {
53736
+ // launch.mjs
53737
+ var import_node_child_process5 = require("node:child_process");
53738
+ var import_node_path13 = require("node:path");
53739
+ var import_node_url = require("node:url");
53740
+ var import_node_fs15 = require("node:fs");
53741
+ var import_node_http = __toESM(require("node:http"), 1);
53742
+ var import_node_readline = require("node:readline");
53743
+ var __dirname2 = (0, import_node_path13.dirname)((0, import_node_url.fileURLToPath)(__import_meta_url));
53744
+ var PORT = Number(process.env.PORT ?? 4317);
53745
+ var HEALTH_URL = `http://127.0.0.1:${PORT}/api/health`;
53746
+ var BROWSER_URL = `http://floless.localhost:${PORT}`;
53747
+ var isWin2 = process.platform === "win32";
53748
+ var log = (m) => {
53767
53749
  try {
53768
- const res = await fetch(REGISTRY_LATEST, { redirect: "follow", signal: AbortSignal.timeout(FEED_TIMEOUT_MS2) });
53769
- if (!res.ok) return null;
53770
- const body = await res.json();
53771
- return typeof body.version === "string" ? body.version : null;
53750
+ process.stdout.write(`floless: ${m}
53751
+ `);
53772
53752
  } catch {
53773
- return null;
53774
53753
  }
53754
+ };
53755
+ function ping() {
53756
+ return new Promise((resolve4) => {
53757
+ const req = import_node_http.default.get(HEALTH_URL, { timeout: 1500 }, (res) => {
53758
+ res.resume();
53759
+ resolve4(res.statusCode === 200);
53760
+ });
53761
+ req.on("error", () => resolve4(false));
53762
+ req.on("timeout", () => {
53763
+ req.destroy();
53764
+ resolve4(false);
53765
+ });
53766
+ });
53775
53767
  }
53776
- async function checkAwareUpdate() {
53777
- const cur = awareInstalledVersion();
53778
- const latest = await fetchAwareLatest();
53779
- return decideAwareUpdate(cur, latest);
53768
+ function probeVersion() {
53769
+ return new Promise((resolve4) => {
53770
+ const req = import_node_http.default.get(HEALTH_URL, { timeout: 1500 }, (res) => {
53771
+ let body = "";
53772
+ res.on("data", (c) => {
53773
+ body += c;
53774
+ });
53775
+ res.on("end", () => {
53776
+ try {
53777
+ resolve4(JSON.parse(body).appVersion || null);
53778
+ } catch {
53779
+ resolve4(null);
53780
+ }
53781
+ });
53782
+ });
53783
+ req.on("error", () => resolve4(null));
53784
+ req.on("timeout", () => {
53785
+ req.destroy();
53786
+ resolve4(null);
53787
+ });
53788
+ });
53780
53789
  }
53781
- async function applyAwareUpdate(deps) {
53782
- try {
53783
- await deps.install("latest");
53784
- deps.refresh();
53785
- invalidateAwareVersion();
53786
- return { ok: true, version: deps.probe() };
53787
- } catch (err) {
53788
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
53790
+ function cmpVersion(a, b) {
53791
+ const pa = String(a ?? "").split(".").map((n) => parseInt(n, 10) || 0);
53792
+ const pb = String(b ?? "").split(".").map((n) => parseInt(n, 10) || 0);
53793
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
53794
+ const d = (pa[i] || 0) - (pb[i] || 0);
53795
+ if (d !== 0) return d > 0 ? 1 : -1;
53789
53796
  }
53797
+ return 0;
53790
53798
  }
53791
- function awareApplyDeps(install) {
53792
- return { install, refresh: refreshInvoker, probe: aware.npmVersion };
53799
+ function shouldTakeOver(runningVersion, selfVersion) {
53800
+ if (!runningVersion || !selfVersion) return false;
53801
+ return cmpVersion(selfVersion, runningVersion) > 0;
53793
53802
  }
53794
- function awareUpgradeBlockReason(s) {
53795
- if (s.bootstrapInstalling || s.installInFlight) return "an AWARE install is already in progress \u2014 try again in a moment";
53796
- if (s.runActive) return "a workflow run is in progress \u2014 wait for it to finish before upgrading AWARE";
53797
- if (s.hostWatcherActive) return "a live trigger/watch session is using the host \u2014 stop it before upgrading AWARE";
53798
- return null;
53803
+ var _selfVersion = null;
53804
+ async function waitHealthy(timeoutMs = 3e4) {
53805
+ const deadline = Date.now() + timeoutMs;
53806
+ while (Date.now() < deadline) {
53807
+ if (await ping()) return true;
53808
+ await new Promise((r) => setTimeout(r, 500));
53809
+ }
53810
+ return false;
53799
53811
  }
53800
-
53801
- // skill-sync.ts
53802
- var import_node_fs15 = require("node:fs");
53803
- var import_node_os9 = require("node:os");
53804
- var import_node_path13 = require("node:path");
53805
- var import_node_url = require("node:url");
53806
- var import_yaml5 = __toESM(require_dist6(), 1);
53807
-
53808
- // build/ship-skills.mjs
53809
- var PRODUCT_SKILLS = [
53810
- "floless-app-bridge",
53811
- // drive the floless.app CLI / desktop bridge from the user's AI
53812
- "floless-app-onboarding",
53813
- // guided, re-runnable tour of AWARE + floless.app for new users
53814
- "floless-app-routines",
53815
- // author event-driven ("on trigger") routines
53816
- "floless-app-workflows"
53817
- // author/run .flo workflows
53818
- ];
53819
- function selectShippedSkillNames(names) {
53820
- const present = new Set(names);
53821
- return PRODUCT_SKILLS.filter((name) => present.has(name));
53812
+ function resolveServerStart() {
53813
+ const packaged = /flolessapp\.exe$/i.test(process.execPath);
53814
+ if (packaged) return { cmd: process.execPath, args: ["--serve"], shell: false };
53815
+ const bundle = (0, import_node_path13.join)(__dirname2, "dist", "floless-server.cjs");
53816
+ if ((0, import_node_fs15.existsSync)(bundle)) return { cmd: process.execPath, args: [bundle, "--serve"], shell: false };
53817
+ return { cmd: "npm", args: ["run", "start"], shell: isWin2 };
53822
53818
  }
53823
-
53824
- // skill-sync.ts
53825
- var __dirname2 = (0, import_node_path13.dirname)((0, import_node_url.fileURLToPath)(__import_meta_url));
53826
- function bundledSkillsRoot() {
53827
- const candidates = [
53828
- (0, import_node_path13.join)(__dirname2, "skills"),
53829
- (0, import_node_path13.join)((0, import_node_path13.dirname)(process.execPath), "skills"),
53830
- (0, import_node_path13.join)(__dirname2, "..", ".claude", "skills")
53831
- ];
53832
- return candidates.find((p) => (0, import_node_fs15.existsSync)(p)) ?? null;
53819
+ function startServerDetached() {
53820
+ const { cmd, args, shell } = resolveServerStart();
53821
+ rotateLog();
53822
+ const fd = openLogFd();
53823
+ const child = (0, import_node_child_process5.spawn)(cmd, args, {
53824
+ cwd: __dirname2,
53825
+ detached: true,
53826
+ stdio: fd == null ? "ignore" : ["ignore", fd, fd],
53827
+ windowsHide: true,
53828
+ shell
53829
+ });
53830
+ child.unref();
53833
53831
  }
53834
- function targetConfigDirs() {
53835
- const override = process.env.FLOLESS_SKILL_TARGETS;
53836
- if (override) {
53837
- return override.split(";").map((d) => d.trim()).filter(Boolean).map((dir) => ({ runtime: "custom", dir }));
53838
- }
53839
- const home = (0, import_node_os9.homedir)();
53840
- return [
53841
- { runtime: "claude", dir: (0, import_node_path13.join)(home, ".claude") },
53842
- { runtime: "codex", dir: (0, import_node_path13.join)(home, ".codex") },
53843
- { runtime: "opencode", dir: (0, import_node_path13.join)(home, ".opencode") }
53844
- ];
53832
+ function openBrowser2(url) {
53833
+ if (isWin2) (0, import_node_child_process5.spawn)("cmd", ["/c", "start", "", url], { windowsHide: true, detached: true }).unref();
53834
+ else (0, import_node_child_process5.spawn)(process.platform === "darwin" ? "open" : "xdg-open", [url], { detached: true }).unref();
53845
53835
  }
53846
- function skillVersion(skillMdPath) {
53836
+ function stopServer() {
53837
+ if (!isWin2) {
53838
+ try {
53839
+ (0, import_node_child_process5.execSync)(`bash -lc "fuser -k ${PORT}/tcp"`, { stdio: "ignore" });
53840
+ return true;
53841
+ } catch {
53842
+ return false;
53843
+ }
53844
+ }
53847
53845
  try {
53848
- const text = (0, import_node_fs15.readFileSync)(skillMdPath, "utf8");
53849
- const m = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text);
53850
- if (!m || m[1] === void 0) return null;
53851
- const fm = (0, import_yaml5.parse)(m[1]);
53852
- const meta = fm?.metadata;
53853
- const v = meta?.version;
53854
- return typeof v === "string" || typeof v === "number" ? String(v) : null;
53846
+ const ps = `$p = Get-NetTCPConnection -LocalPort ${PORT} -State Listen -ErrorAction SilentlyContinue | Select-Object -Expand OwningProcess -Unique; if ($p) { $p | ForEach-Object { taskkill /PID $_ /T /F } } else { exit 9 }`;
53847
+ (0, import_node_child_process5.execSync)(`powershell -NoProfile -Command "${ps}"`, { stdio: "ignore" });
53848
+ return true;
53855
53849
  } catch {
53856
- return null;
53850
+ return false;
53857
53851
  }
53858
53852
  }
53859
- function compareVersions(a, b) {
53860
- const split = (v) => {
53861
- const dash = v.indexOf("-");
53862
- const core = dash >= 0 ? v.slice(0, dash) : v;
53863
- const pre = dash >= 0 ? v.slice(dash + 1) : "";
53864
- return { nums: core.split(".").map((s) => parseInt(s, 10) || 0), pre };
53865
- };
53866
- const pa = split(a);
53867
- const pb = split(b);
53868
- const n = Math.max(pa.nums.length, pb.nums.length);
53869
- for (let i = 0; i < n; i++) {
53870
- const d = (pa.nums[i] ?? 0) - (pb.nums[i] ?? 0);
53871
- if (d !== 0) return d > 0 ? 1 : -1;
53853
+ async function ensureServerUp() {
53854
+ if (await ping()) return;
53855
+ log("starting server\u2026");
53856
+ startServerDetached();
53857
+ if (!await waitHealthy()) {
53858
+ log(`server did not become healthy on ${HEALTH_URL} within 30s \u2014 check for errors with "npm run dev"`);
53859
+ process.exit(1);
53872
53860
  }
53873
- if (pa.pre === pb.pre) return 0;
53874
- if (pa.pre === "") return 1;
53875
- if (pb.pre === "") return -1;
53876
- return pa.pre < pb.pre ? -1 : 1;
53877
- }
53878
- function decideAction(installed, bundled) {
53879
- if (installed === null) return "installed";
53880
- const c = compareVersions(bundled, installed);
53881
- if (c > 0) return "updated";
53882
- if (c === 0) return "skipped-current";
53883
- return "skipped-newer";
53861
+ log("server up");
53884
53862
  }
53885
- function bundledSkills(root) {
53886
- let entries = [];
53887
- try {
53888
- entries = selectShippedSkillNames((0, import_node_fs15.readdirSync)(root));
53889
- } catch {
53890
- return [];
53891
- }
53892
- const out = [];
53893
- for (const name of entries) {
53894
- const dir = (0, import_node_path13.join)(root, name);
53895
- let isDir = false;
53896
- try {
53897
- isDir = (0, import_node_fs15.statSync)(dir).isDirectory();
53898
- } catch {
53899
- isDir = false;
53863
+ async function cmdOpen() {
53864
+ if (await ping()) {
53865
+ const running = await probeVersion();
53866
+ if (shouldTakeOver(running, _selfVersion)) {
53867
+ log(`floless.app v${running} is already running on ${PORT}; this build is v${_selfVersion} \u2014 taking over`);
53868
+ stopServer();
53869
+ await new Promise((r) => setTimeout(r, 500));
53870
+ await ensureServerUp();
53871
+ } else {
53872
+ log(`already running${running ? ` (v${running})` : ""} \u2014 opening browser`);
53900
53873
  }
53901
- if (!isDir) continue;
53902
- const v = skillVersion((0, import_node_path13.join)(dir, "SKILL.md"));
53903
- if (!v) continue;
53904
- out.push({ name, dir, version: v });
53874
+ } else {
53875
+ await ensureServerUp();
53905
53876
  }
53906
- return out;
53877
+ log(`opening ${BROWSER_URL}`);
53878
+ openBrowser2(BROWSER_URL);
53907
53879
  }
53908
- function syncSkills() {
53909
- const results = [];
53910
- const root = bundledSkillsRoot();
53911
- if (!root) return results;
53912
- const skills = bundledSkills(root);
53913
- if (!skills.length) return results;
53914
- for (const { runtime, dir: cfg } of targetConfigDirs()) {
53915
- if (!(0, import_node_fs15.existsSync)(cfg)) continue;
53916
- const skillsDir = (0, import_node_path13.join)(cfg, "skills");
53917
- for (const s of skills) {
53918
- const dest = (0, import_node_path13.join)(skillsDir, s.name);
53919
- const installedMd = (0, import_node_path13.join)(dest, "SKILL.md");
53920
- const installed = (0, import_node_fs15.existsSync)(installedMd) ? skillVersion(installedMd) : null;
53921
- const action = decideAction(installed, s.version);
53922
- if (action === "installed" || action === "updated") {
53923
- try {
53924
- if (action === "updated") (0, import_node_fs15.rmSync)(dest, { recursive: true, force: true });
53925
- (0, import_node_fs15.cpSync)(s.dir, dest, { recursive: true });
53926
- results.push({ runtime, skill: s.name, action, from: installed, to: s.version });
53927
- } catch {
53928
- }
53929
- } else {
53930
- results.push({ runtime, skill: s.name, action, from: installed, to: s.version });
53931
- }
53932
- }
53880
+ async function cmdStart() {
53881
+ if (await ping()) {
53882
+ log("already running");
53883
+ return;
53933
53884
  }
53934
- return results;
53885
+ await ensureServerUp();
53935
53886
  }
53936
-
53937
- // watch.ts
53938
- var import_node_os10 = require("node:os");
53939
- var import_node_path15 = require("node:path");
53940
- var import_node_fs16 = require("node:fs");
53941
-
53942
- // node_modules/chokidar/esm/index.js
53943
- var import_fs2 = require("fs");
53944
- var import_promises4 = require("fs/promises");
53945
- var import_events = require("events");
53946
- var sysPath2 = __toESM(require("path"), 1);
53947
-
53948
- // node_modules/readdirp/esm/index.js
53949
- var import_promises2 = require("node:fs/promises");
53950
- var import_node_stream2 = require("node:stream");
53951
- var import_node_path14 = require("node:path");
53952
- var EntryTypes = {
53953
- FILE_TYPE: "files",
53954
- DIR_TYPE: "directories",
53955
- FILE_DIR_TYPE: "files_directories",
53956
- EVERYTHING_TYPE: "all"
53957
- };
53958
- var defaultOptions = {
53959
- root: ".",
53960
- fileFilter: (_entryInfo) => true,
53961
- directoryFilter: (_entryInfo) => true,
53962
- type: EntryTypes.FILE_TYPE,
53963
- lstat: false,
53964
- depth: 2147483648,
53965
- alwaysStat: false,
53966
- highWaterMark: 4096
53967
- };
53968
- Object.freeze(defaultOptions);
53969
- var RECURSIVE_ERROR_CODE = "READDIRP_RECURSIVE_ERROR";
53970
- var NORMAL_FLOW_ERRORS = /* @__PURE__ */ new Set(["ENOENT", "EPERM", "EACCES", "ELOOP", RECURSIVE_ERROR_CODE]);
53971
- var ALL_TYPES = [
53972
- EntryTypes.DIR_TYPE,
53973
- EntryTypes.EVERYTHING_TYPE,
53974
- EntryTypes.FILE_DIR_TYPE,
53975
- EntryTypes.FILE_TYPE
53976
- ];
53977
- var DIR_TYPES = /* @__PURE__ */ new Set([
53978
- EntryTypes.DIR_TYPE,
53979
- EntryTypes.EVERYTHING_TYPE,
53980
- EntryTypes.FILE_DIR_TYPE
53981
- ]);
53982
- var FILE_TYPES = /* @__PURE__ */ new Set([
53983
- EntryTypes.EVERYTHING_TYPE,
53984
- EntryTypes.FILE_DIR_TYPE,
53985
- EntryTypes.FILE_TYPE
53986
- ]);
53987
- var isNormalFlowError = (error) => NORMAL_FLOW_ERRORS.has(error.code);
53988
- var wantBigintFsStats = process.platform === "win32";
53989
- var emptyFn = (_entryInfo) => true;
53990
- var normalizeFilter = (filter) => {
53991
- if (filter === void 0)
53992
- return emptyFn;
53993
- if (typeof filter === "function")
53994
- return filter;
53995
- if (typeof filter === "string") {
53996
- const fl = filter.trim();
53997
- return (entry2) => entry2.basename === fl;
53998
- }
53999
- if (Array.isArray(filter)) {
54000
- const trItems = filter.map((item) => item.trim());
54001
- return (entry2) => trItems.some((f) => entry2.basename === f);
54002
- }
54003
- return emptyFn;
54004
- };
54005
- var ReaddirpStream = class extends import_node_stream2.Readable {
54006
- constructor(options = {}) {
54007
- super({
54008
- objectMode: true,
54009
- autoDestroy: true,
54010
- highWaterMark: options.highWaterMark
53887
+ var SUPERVISE_POLL_MS = 3e3;
53888
+ var SUPERVISE_RESPAWN_ENV = "FLOLESS_SUPERVISE_RESPAWNED";
53889
+ async function cmdSupervise() {
53890
+ const isSeaChannel = /flolessapp\.exe$/i.test(process.execPath);
53891
+ if (isSeaChannel && !process.env[SUPERVISE_RESPAWN_ENV]) {
53892
+ rotateLog();
53893
+ const fd = openLogFd();
53894
+ const child = (0, import_node_child_process5.spawn)(process.execPath, ["--supervise"], {
53895
+ detached: true,
53896
+ stdio: fd == null ? "ignore" : ["ignore", fd, fd],
53897
+ windowsHide: true,
53898
+ env: { ...process.env, [SUPERVISE_RESPAWN_ENV]: "1" }
54011
53899
  });
54012
- const opts = { ...defaultOptions, ...options };
54013
- const { root, type } = opts;
54014
- this._fileFilter = normalizeFilter(opts.fileFilter);
54015
- this._directoryFilter = normalizeFilter(opts.directoryFilter);
54016
- const statMethod = opts.lstat ? import_promises2.lstat : import_promises2.stat;
54017
- if (wantBigintFsStats) {
54018
- this._stat = (path) => statMethod(path, { bigint: true });
54019
- } else {
54020
- this._stat = statMethod;
53900
+ child.unref();
53901
+ log("supervisor re-spawned detached");
53902
+ return;
53903
+ }
53904
+ log("supervisor up \u2014 keeping the server alive");
53905
+ for (; ; ) {
53906
+ if (!await ping()) {
53907
+ log("server not responding \u2014 (re)starting");
53908
+ startServerDetached();
53909
+ log(await waitHealthy() ? "server up" : "server not healthy yet \u2014 will retry");
54021
53910
  }
54022
- this._maxDepth = opts.depth ?? defaultOptions.depth;
54023
- this._wantsDir = type ? DIR_TYPES.has(type) : false;
54024
- this._wantsFile = type ? FILE_TYPES.has(type) : false;
54025
- this._wantsEverything = type === EntryTypes.EVERYTHING_TYPE;
54026
- this._root = (0, import_node_path14.resolve)(root);
54027
- this._isDirent = !opts.alwaysStat;
54028
- this._statsProp = this._isDirent ? "dirent" : "stats";
54029
- this._rdOptions = { encoding: "utf8", withFileTypes: this._isDirent };
54030
- this.parents = [this._exploreDir(root, 1)];
54031
- this.reading = false;
54032
- this.parent = void 0;
53911
+ await new Promise((r) => setTimeout(r, SUPERVISE_POLL_MS));
54033
53912
  }
54034
- async _read(batch) {
54035
- if (this.reading)
53913
+ }
53914
+ async function cmdStop() {
53915
+ if (!stopServer()) {
53916
+ log("not running \u2014 nothing to stop");
53917
+ return;
53918
+ }
53919
+ log(`stopping server on port ${PORT}\u2026`);
53920
+ for (let i = 0; i < 10; i++) {
53921
+ if (!await ping()) {
53922
+ log("stopped");
54036
53923
  return;
54037
- this.reading = true;
54038
- try {
54039
- while (!this.destroyed && batch > 0) {
54040
- const par = this.parent;
54041
- const fil = par && par.files;
54042
- if (fil && fil.length > 0) {
54043
- const { path, depth } = par;
54044
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path));
54045
- const awaited = await Promise.all(slice);
54046
- for (const entry2 of awaited) {
54047
- if (!entry2)
54048
- continue;
54049
- if (this.destroyed)
54050
- return;
54051
- const entryType = await this._getEntryType(entry2);
54052
- if (entryType === "directory" && this._directoryFilter(entry2)) {
54053
- if (depth <= this._maxDepth) {
54054
- this.parents.push(this._exploreDir(entry2.fullPath, depth + 1));
54055
- }
54056
- if (this._wantsDir) {
54057
- this.push(entry2);
54058
- batch--;
54059
- }
54060
- } else if ((entryType === "file" || this._includeAsFile(entry2)) && this._fileFilter(entry2)) {
54061
- if (this._wantsFile) {
54062
- this.push(entry2);
54063
- batch--;
54064
- }
54065
- }
54066
- }
54067
- } else {
54068
- const parent = this.parents.pop();
54069
- if (!parent) {
54070
- this.push(null);
54071
- break;
54072
- }
54073
- this.parent = await parent;
54074
- if (this.destroyed)
54075
- return;
54076
- }
54077
- }
54078
- } catch (error) {
54079
- this.destroy(error);
54080
- } finally {
54081
- this.reading = false;
54082
53924
  }
53925
+ await new Promise((r) => setTimeout(r, 300));
54083
53926
  }
54084
- async _exploreDir(path, depth) {
54085
- let files;
54086
- try {
54087
- files = await (0, import_promises2.readdir)(path, this._rdOptions);
54088
- } catch (error) {
54089
- this._onError(error);
54090
- }
54091
- return { files, depth, path };
53927
+ log("warning: a process may still be holding the port");
53928
+ }
53929
+ function parseTeardownFlags(argv = []) {
53930
+ return {
53931
+ purge: argv.includes("--purge"),
53932
+ keepAware: argv.includes("--keep-aware")
53933
+ };
53934
+ }
53935
+ function parseProcessList(json) {
53936
+ let parsed;
53937
+ try {
53938
+ parsed = JSON.parse(json || "[]");
53939
+ } catch {
53940
+ return [];
54092
53941
  }
54093
- async _formatEntry(dirent, path) {
54094
- let entry2;
54095
- const basename5 = this._isDirent ? dirent.name : dirent;
53942
+ const arr = Array.isArray(parsed) ? parsed : [parsed];
53943
+ return arr.filter((p) => p && p.ProcessId != null).map((p) => ({ pid: Number(p.ProcessId), cmd: String(p.CommandLine ?? "") }));
53944
+ }
53945
+ function enumerateProcesses() {
53946
+ if (!isWin2) return [];
53947
+ try {
53948
+ const ps = "Get-CimInstance Win32_Process | Select-Object ProcessId,CommandLine | ConvertTo-Json -Compress";
53949
+ const out = (0, import_node_child_process5.execSync)(`powershell -NoProfile -Command "${ps}"`, {
53950
+ encoding: "utf8",
53951
+ windowsHide: true,
53952
+ maxBuffer: 16 * 1024 * 1024
53953
+ });
53954
+ return parseProcessList(out);
53955
+ } catch {
53956
+ return [];
53957
+ }
53958
+ }
53959
+ function taskkillArgs(pid, { tree = true } = {}) {
53960
+ return tree ? ["/PID", String(pid), "/T", "/F"] : ["/PID", String(pid), "/F"];
53961
+ }
53962
+ function killSupervisor({ tree = true } = {}) {
53963
+ if (!isWin2) return;
53964
+ const isNpmChannel = /^node(\.exe)?$/i.test((0, import_node_path13.basename)(process.execPath));
53965
+ const scriptMatch = isNpmChannel ? (0, import_node_path13.basename)((0, import_node_url.fileURLToPath)(__import_meta_url)) : void 0;
53966
+ const realExe = resolveRealInstallExe(process.execPath);
53967
+ const exeMatch = realExe === process.execPath ? process.execPath : [process.execPath, realExe];
53968
+ const pids = supervisorPidsToKill(enumerateProcesses(), process.pid, exeMatch, scriptMatch);
53969
+ for (const pid of pids) {
53970
+ log(`stopping supervisor (pid ${pid})\u2026`);
54096
53971
  try {
54097
- const fullPath = (0, import_node_path14.resolve)((0, import_node_path14.join)(path, basename5));
54098
- entry2 = { path: (0, import_node_path14.relative)(this._root, fullPath), fullPath, basename: basename5 };
54099
- entry2[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
54100
- } catch (err) {
54101
- this._onError(err);
54102
- return;
53972
+ (0, import_node_child_process5.execFileSync)("taskkill", taskkillArgs(pid, { tree }), { stdio: "ignore", windowsHide: true });
53973
+ } catch {
54103
53974
  }
54104
- return entry2;
54105
53975
  }
54106
- _onError(err) {
54107
- if (isNormalFlowError(err) && !this.destroyed) {
54108
- this.emit("warn", err);
53976
+ }
53977
+ async function cmdTeardown() {
53978
+ killSupervisor();
53979
+ stopServer();
53980
+ const deadline = Date.now() + 3e3;
53981
+ while (Date.now() < deadline) {
53982
+ await new Promise((r) => setTimeout(r, 300));
53983
+ if (await ping()) {
53984
+ log("server respawned after kill \u2014 re-killing supervisor and stopping again");
53985
+ killSupervisor();
53986
+ stopServer();
53987
+ }
53988
+ }
53989
+ if (await ping()) log("warning: a process may still be holding the port");
53990
+ else log("server and supervisor stopped");
53991
+ }
53992
+ async function cmdRestart() {
53993
+ await cmdStop();
53994
+ await new Promise((r) => setTimeout(r, 500));
53995
+ await cmdOpen();
53996
+ }
53997
+ function apiJson(path, method = "GET") {
53998
+ return new Promise((resolve4, reject) => {
53999
+ const req = import_node_http.default.request(`http://127.0.0.1:${PORT}${path}`, { method, timeout: 5e3 }, (res) => {
54000
+ let body = "";
54001
+ res.on("data", (c) => body += c);
54002
+ res.on("end", () => {
54003
+ try {
54004
+ resolve4(JSON.parse(body || "{}"));
54005
+ } catch {
54006
+ resolve4({});
54007
+ }
54008
+ });
54009
+ });
54010
+ req.on("error", reject);
54011
+ req.on("timeout", () => {
54012
+ req.destroy();
54013
+ reject(new Error("timeout"));
54014
+ });
54015
+ req.end();
54016
+ });
54017
+ }
54018
+ async function cmdLogin() {
54019
+ if (!await ping()) {
54020
+ log("starting server\u2026");
54021
+ startServerDetached();
54022
+ if (!await waitHealthy()) {
54023
+ log('server did not start \u2014 try "npm run dev" to see errors');
54024
+ process.exit(1);
54025
+ }
54026
+ }
54027
+ const started = await apiJson("/api/license/start", "POST").catch(() => ({}));
54028
+ log(`opening sign-in in your browser${started.signInUrl ? `: ${started.signInUrl}` : ""}`);
54029
+ log("waiting for sign-in to complete\u2026");
54030
+ const deadline = Date.now() + 185e3;
54031
+ while (Date.now() < deadline) {
54032
+ const s = await apiJson("/api/license/status").catch(() => ({}));
54033
+ if (s.state === "valid" || s.state === "offline-grace") {
54034
+ log("signed in \u2014 subscription active");
54035
+ return;
54036
+ }
54037
+ if (s.state === "expired") {
54038
+ log(`signed in, but the subscription is expired \u2014 subscribe at ${s.signInUrl ?? "floless.io"}`);
54039
+ return;
54040
+ }
54041
+ await new Promise((r) => setTimeout(r, 2e3));
54042
+ }
54043
+ log('sign-in timed out \u2014 run "floless login" again to retry');
54044
+ }
54045
+ function parseAction(arg) {
54046
+ let cmd = (arg || "open").toLowerCase();
54047
+ if (cmd.startsWith("floless:")) {
54048
+ try {
54049
+ cmd = (new URL(arg).hostname || "open").toLowerCase();
54050
+ } catch {
54051
+ cmd = "open";
54052
+ }
54053
+ }
54054
+ return cmd;
54055
+ }
54056
+ async function cmdUpdate() {
54057
+ log('this is the npm build \u2014 update it with "npm i -g @floless/app"');
54058
+ log('(the installer build self-updates via "floless-app update")');
54059
+ }
54060
+ function removeRegistryFootprint() {
54061
+ if (!isWin2) return;
54062
+ try {
54063
+ (0, import_node_child_process5.execFileSync)("reg", ["delete", RUN_KEY, "/v", RUN_VALUE, "/f"], { stdio: "ignore", windowsHide: true });
54064
+ } catch {
54065
+ }
54066
+ try {
54067
+ (0, import_node_child_process5.execFileSync)("reg", ["delete", PROTOCOL_KEY, "/f"], { stdio: "ignore", windowsHide: true });
54068
+ } catch {
54069
+ }
54070
+ }
54071
+ function promptYesNo(question) {
54072
+ return new Promise((resolve4) => {
54073
+ const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stdout });
54074
+ rl.question(`${question} `, (answer) => {
54075
+ rl.close();
54076
+ resolve4(/^y(es)?$/i.test((answer ?? "").trim()));
54077
+ });
54078
+ });
54079
+ }
54080
+ async function cmdUninstall(flags = {}) {
54081
+ const decision = teardownDecision({ ...flags, isTTY: process.stdin.isTTY });
54082
+ log("stopping the server and supervisor\u2026");
54083
+ await cmdTeardown();
54084
+ log("removing the autostart entry and floless:// scheme\u2026");
54085
+ removeRegistryFootprint();
54086
+ let removeAware = decision.removeAware;
54087
+ if (decision.prompt) {
54088
+ removeAware = await promptYesNo("Also remove the AWARE runtime (@aware-aeco/cli)? [y/N]");
54089
+ }
54090
+ if (removeAware) {
54091
+ log("removing the AWARE runtime \u2014 this will affect ANY other tool that uses @aware-aeco/cli.");
54092
+ try {
54093
+ (0, import_node_child_process5.execSync)("npm uninstall -g @aware-aeco/cli", { stdio: "inherit", shell: isWin2 });
54094
+ } catch {
54095
+ log('warning: "npm uninstall -g @aware-aeco/cli" failed \u2014 see the output above.');
54096
+ }
54097
+ let lsJson = "";
54098
+ try {
54099
+ lsJson = (0, import_node_child_process5.execSync)("npm ls -g @aware-aeco/cli --depth=0 --json", {
54100
+ encoding: "utf8",
54101
+ stdio: ["ignore", "pipe", "ignore"],
54102
+ shell: isWin2
54103
+ });
54104
+ } catch (err) {
54105
+ lsJson = String(err?.stdout ?? "");
54106
+ }
54107
+ const stillPresent = awareIsPresent(lsJson);
54108
+ if (stillPresent) {
54109
+ log("AWARE may still be installed \u2014 remove it manually with: npm uninstall -g @aware-aeco/cli");
54110
+ log("(if that fails with a permission error, retry in an elevated shell).");
54111
+ } else {
54112
+ log("AWARE runtime removed.");
54113
+ }
54114
+ } else {
54115
+ log("keeping the AWARE runtime (@aware-aeco/cli). Remove it later with: npm uninstall -g @aware-aeco/cli");
54116
+ }
54117
+ log("done. Finally, remove floless.app itself with: npm uninstall -g @floless/app");
54118
+ }
54119
+ var ACTIONS = { open: cmdOpen, start: cmdStart, supervise: cmdSupervise, stop: cmdStop, restart: cmdRestart, login: cmdLogin, update: cmdUpdate, uninstall: cmdUninstall };
54120
+ async function runAction(arg, flagArgv = [], selfVersion = null) {
54121
+ _selfVersion = selfVersion;
54122
+ const cmd = parseAction(arg);
54123
+ const action = ACTIONS[cmd];
54124
+ if (!action) {
54125
+ log(`unknown command "${cmd}" \u2014 use: open | start | supervise | stop | restart | login | update | uninstall`);
54126
+ process.exit(2);
54127
+ }
54128
+ await action(parseTeardownFlags(flagArgv));
54129
+ }
54130
+ var entry = (0, import_node_path13.basename)(process.argv[1] ?? "").toLowerCase();
54131
+ if (entry === "launch.mjs") {
54132
+ runAction(process.argv[2], process.argv.slice(3)).catch((e) => {
54133
+ log(`error: ${e?.message ?? e}`);
54134
+ process.exit(1);
54135
+ });
54136
+ }
54137
+
54138
+ // aware-update.ts
54139
+ var REGISTRY_LATEST = "https://registry.npmjs.org/@aware-aeco/cli/latest";
54140
+ var FEED_TIMEOUT_MS2 = 15e3;
54141
+ var VERSION_TTL_MS = 5e3;
54142
+ var _v = null;
54143
+ function awareInstalledVersion(probe = aware.npmVersion) {
54144
+ const now = Date.now();
54145
+ if (_v && now - _v.at < VERSION_TTL_MS) return _v.val;
54146
+ _v = { at: now, val: probe() };
54147
+ return _v.val;
54148
+ }
54149
+ function invalidateAwareVersion() {
54150
+ _v = null;
54151
+ }
54152
+ function versionGt2(a, b) {
54153
+ const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
54154
+ const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
54155
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
54156
+ const d = (pa[i] ?? 0) - (pb[i] ?? 0);
54157
+ if (d !== 0) return d > 0;
54158
+ }
54159
+ return false;
54160
+ }
54161
+ function decideAwareUpdate(cur, latest) {
54162
+ const base = { supported: true, currentVersion: cur };
54163
+ if (!latest) return { ...base, updateAvailable: false, reason: "could not read the npm registry latest" };
54164
+ if (!cur) return { ...base, updateAvailable: false, targetVersion: latest, reason: "AWARE not installed yet" };
54165
+ if (versionGt2(latest, cur)) return { ...base, updateAvailable: true, targetVersion: latest };
54166
+ return { ...base, updateAvailable: false, targetVersion: latest, reason: `already up to date (v${cur})` };
54167
+ }
54168
+ async function fetchAwareLatest() {
54169
+ try {
54170
+ const res = await fetch(REGISTRY_LATEST, { redirect: "follow", signal: AbortSignal.timeout(FEED_TIMEOUT_MS2) });
54171
+ if (!res.ok) return null;
54172
+ const body = await res.json();
54173
+ return typeof body.version === "string" ? body.version : null;
54174
+ } catch {
54175
+ return null;
54176
+ }
54177
+ }
54178
+ async function checkAwareUpdate() {
54179
+ const cur = awareInstalledVersion();
54180
+ const latest = await fetchAwareLatest();
54181
+ return decideAwareUpdate(cur, latest);
54182
+ }
54183
+ async function applyAwareUpdate(deps) {
54184
+ try {
54185
+ await deps.install("latest");
54186
+ deps.refresh();
54187
+ invalidateAwareVersion();
54188
+ return { ok: true, version: deps.probe() };
54189
+ } catch (err) {
54190
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
54191
+ }
54192
+ }
54193
+ function awareApplyDeps(install) {
54194
+ return { install, refresh: refreshInvoker, probe: aware.npmVersion };
54195
+ }
54196
+ function awareUpgradeBlockReason(s) {
54197
+ if (s.bootstrapInstalling || s.installInFlight) return "an AWARE install is already in progress \u2014 try again in a moment";
54198
+ if (s.runActive) return "a workflow run is in progress \u2014 wait for it to finish before upgrading AWARE";
54199
+ if (s.hostWatcherActive) return "a live trigger/watch session is using the host \u2014 stop it before upgrading AWARE";
54200
+ return null;
54201
+ }
54202
+
54203
+ // skill-sync.ts
54204
+ var import_node_fs16 = require("node:fs");
54205
+ var import_node_os9 = require("node:os");
54206
+ var import_node_path14 = require("node:path");
54207
+ var import_node_url2 = require("node:url");
54208
+ var import_yaml5 = __toESM(require_dist6(), 1);
54209
+
54210
+ // build/ship-skills.mjs
54211
+ var PRODUCT_SKILLS = [
54212
+ "floless-app-bridge",
54213
+ // drive the floless.app CLI / desktop bridge from the user's AI
54214
+ "floless-app-onboarding",
54215
+ // guided, re-runnable tour of AWARE + floless.app for new users
54216
+ "floless-app-routines",
54217
+ // author event-driven ("on trigger") routines
54218
+ "floless-app-workflows"
54219
+ // author/run .flo workflows
54220
+ ];
54221
+ function selectShippedSkillNames(names) {
54222
+ const present = new Set(names);
54223
+ return PRODUCT_SKILLS.filter((name) => present.has(name));
54224
+ }
54225
+
54226
+ // skill-sync.ts
54227
+ var __dirname3 = (0, import_node_path14.dirname)((0, import_node_url2.fileURLToPath)(__import_meta_url));
54228
+ function bundledSkillsRoot() {
54229
+ const candidates = [
54230
+ (0, import_node_path14.join)(__dirname3, "skills"),
54231
+ (0, import_node_path14.join)((0, import_node_path14.dirname)(process.execPath), "skills"),
54232
+ (0, import_node_path14.join)(__dirname3, "..", ".claude", "skills")
54233
+ ];
54234
+ return candidates.find((p) => (0, import_node_fs16.existsSync)(p)) ?? null;
54235
+ }
54236
+ function targetConfigDirs() {
54237
+ const override = process.env.FLOLESS_SKILL_TARGETS;
54238
+ if (override) {
54239
+ return override.split(";").map((d) => d.trim()).filter(Boolean).map((dir) => ({ runtime: "custom", dir }));
54240
+ }
54241
+ const home = (0, import_node_os9.homedir)();
54242
+ return [
54243
+ { runtime: "claude", dir: (0, import_node_path14.join)(home, ".claude") },
54244
+ { runtime: "codex", dir: (0, import_node_path14.join)(home, ".codex") },
54245
+ { runtime: "opencode", dir: (0, import_node_path14.join)(home, ".opencode") }
54246
+ ];
54247
+ }
54248
+ function skillVersion(skillMdPath) {
54249
+ try {
54250
+ const text = (0, import_node_fs16.readFileSync)(skillMdPath, "utf8");
54251
+ const m = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text);
54252
+ if (!m || m[1] === void 0) return null;
54253
+ const fm = (0, import_yaml5.parse)(m[1]);
54254
+ const meta = fm?.metadata;
54255
+ const v = meta?.version;
54256
+ return typeof v === "string" || typeof v === "number" ? String(v) : null;
54257
+ } catch {
54258
+ return null;
54259
+ }
54260
+ }
54261
+ function compareVersions(a, b) {
54262
+ const split = (v) => {
54263
+ const dash = v.indexOf("-");
54264
+ const core = dash >= 0 ? v.slice(0, dash) : v;
54265
+ const pre = dash >= 0 ? v.slice(dash + 1) : "";
54266
+ return { nums: core.split(".").map((s) => parseInt(s, 10) || 0), pre };
54267
+ };
54268
+ const pa = split(a);
54269
+ const pb = split(b);
54270
+ const n = Math.max(pa.nums.length, pb.nums.length);
54271
+ for (let i = 0; i < n; i++) {
54272
+ const d = (pa.nums[i] ?? 0) - (pb.nums[i] ?? 0);
54273
+ if (d !== 0) return d > 0 ? 1 : -1;
54274
+ }
54275
+ if (pa.pre === pb.pre) return 0;
54276
+ if (pa.pre === "") return 1;
54277
+ if (pb.pre === "") return -1;
54278
+ return pa.pre < pb.pre ? -1 : 1;
54279
+ }
54280
+ function decideAction(installed, bundled) {
54281
+ if (installed === null) return "installed";
54282
+ const c = compareVersions(bundled, installed);
54283
+ if (c > 0) return "updated";
54284
+ if (c === 0) return "skipped-current";
54285
+ return "skipped-newer";
54286
+ }
54287
+ function bundledSkills(root) {
54288
+ let entries = [];
54289
+ try {
54290
+ entries = selectShippedSkillNames((0, import_node_fs16.readdirSync)(root));
54291
+ } catch {
54292
+ return [];
54293
+ }
54294
+ const out = [];
54295
+ for (const name of entries) {
54296
+ const dir = (0, import_node_path14.join)(root, name);
54297
+ let isDir = false;
54298
+ try {
54299
+ isDir = (0, import_node_fs16.statSync)(dir).isDirectory();
54300
+ } catch {
54301
+ isDir = false;
54302
+ }
54303
+ if (!isDir) continue;
54304
+ const v = skillVersion((0, import_node_path14.join)(dir, "SKILL.md"));
54305
+ if (!v) continue;
54306
+ out.push({ name, dir, version: v });
54307
+ }
54308
+ return out;
54309
+ }
54310
+ function syncSkills() {
54311
+ const results = [];
54312
+ const root = bundledSkillsRoot();
54313
+ if (!root) return results;
54314
+ const skills = bundledSkills(root);
54315
+ if (!skills.length) return results;
54316
+ for (const { runtime, dir: cfg } of targetConfigDirs()) {
54317
+ if (!(0, import_node_fs16.existsSync)(cfg)) continue;
54318
+ const skillsDir = (0, import_node_path14.join)(cfg, "skills");
54319
+ for (const s of skills) {
54320
+ const dest = (0, import_node_path14.join)(skillsDir, s.name);
54321
+ const installedMd = (0, import_node_path14.join)(dest, "SKILL.md");
54322
+ const installed = (0, import_node_fs16.existsSync)(installedMd) ? skillVersion(installedMd) : null;
54323
+ const action = decideAction(installed, s.version);
54324
+ if (action === "installed" || action === "updated") {
54325
+ try {
54326
+ if (action === "updated") (0, import_node_fs16.rmSync)(dest, { recursive: true, force: true });
54327
+ (0, import_node_fs16.cpSync)(s.dir, dest, { recursive: true });
54328
+ results.push({ runtime, skill: s.name, action, from: installed, to: s.version });
54329
+ } catch {
54330
+ }
54331
+ } else {
54332
+ results.push({ runtime, skill: s.name, action, from: installed, to: s.version });
54333
+ }
54334
+ }
54335
+ }
54336
+ return results;
54337
+ }
54338
+
54339
+ // watch.ts
54340
+ var import_node_os10 = require("node:os");
54341
+ var import_node_path16 = require("node:path");
54342
+ var import_node_fs17 = require("node:fs");
54343
+
54344
+ // node_modules/chokidar/esm/index.js
54345
+ var import_fs2 = require("fs");
54346
+ var import_promises4 = require("fs/promises");
54347
+ var import_events = require("events");
54348
+ var sysPath2 = __toESM(require("path"), 1);
54349
+
54350
+ // node_modules/readdirp/esm/index.js
54351
+ var import_promises2 = require("node:fs/promises");
54352
+ var import_node_stream2 = require("node:stream");
54353
+ var import_node_path15 = require("node:path");
54354
+ var EntryTypes = {
54355
+ FILE_TYPE: "files",
54356
+ DIR_TYPE: "directories",
54357
+ FILE_DIR_TYPE: "files_directories",
54358
+ EVERYTHING_TYPE: "all"
54359
+ };
54360
+ var defaultOptions = {
54361
+ root: ".",
54362
+ fileFilter: (_entryInfo) => true,
54363
+ directoryFilter: (_entryInfo) => true,
54364
+ type: EntryTypes.FILE_TYPE,
54365
+ lstat: false,
54366
+ depth: 2147483648,
54367
+ alwaysStat: false,
54368
+ highWaterMark: 4096
54369
+ };
54370
+ Object.freeze(defaultOptions);
54371
+ var RECURSIVE_ERROR_CODE = "READDIRP_RECURSIVE_ERROR";
54372
+ var NORMAL_FLOW_ERRORS = /* @__PURE__ */ new Set(["ENOENT", "EPERM", "EACCES", "ELOOP", RECURSIVE_ERROR_CODE]);
54373
+ var ALL_TYPES = [
54374
+ EntryTypes.DIR_TYPE,
54375
+ EntryTypes.EVERYTHING_TYPE,
54376
+ EntryTypes.FILE_DIR_TYPE,
54377
+ EntryTypes.FILE_TYPE
54378
+ ];
54379
+ var DIR_TYPES = /* @__PURE__ */ new Set([
54380
+ EntryTypes.DIR_TYPE,
54381
+ EntryTypes.EVERYTHING_TYPE,
54382
+ EntryTypes.FILE_DIR_TYPE
54383
+ ]);
54384
+ var FILE_TYPES = /* @__PURE__ */ new Set([
54385
+ EntryTypes.EVERYTHING_TYPE,
54386
+ EntryTypes.FILE_DIR_TYPE,
54387
+ EntryTypes.FILE_TYPE
54388
+ ]);
54389
+ var isNormalFlowError = (error) => NORMAL_FLOW_ERRORS.has(error.code);
54390
+ var wantBigintFsStats = process.platform === "win32";
54391
+ var emptyFn = (_entryInfo) => true;
54392
+ var normalizeFilter = (filter) => {
54393
+ if (filter === void 0)
54394
+ return emptyFn;
54395
+ if (typeof filter === "function")
54396
+ return filter;
54397
+ if (typeof filter === "string") {
54398
+ const fl = filter.trim();
54399
+ return (entry2) => entry2.basename === fl;
54400
+ }
54401
+ if (Array.isArray(filter)) {
54402
+ const trItems = filter.map((item) => item.trim());
54403
+ return (entry2) => trItems.some((f) => entry2.basename === f);
54404
+ }
54405
+ return emptyFn;
54406
+ };
54407
+ var ReaddirpStream = class extends import_node_stream2.Readable {
54408
+ constructor(options = {}) {
54409
+ super({
54410
+ objectMode: true,
54411
+ autoDestroy: true,
54412
+ highWaterMark: options.highWaterMark
54413
+ });
54414
+ const opts = { ...defaultOptions, ...options };
54415
+ const { root, type } = opts;
54416
+ this._fileFilter = normalizeFilter(opts.fileFilter);
54417
+ this._directoryFilter = normalizeFilter(opts.directoryFilter);
54418
+ const statMethod = opts.lstat ? import_promises2.lstat : import_promises2.stat;
54419
+ if (wantBigintFsStats) {
54420
+ this._stat = (path) => statMethod(path, { bigint: true });
54421
+ } else {
54422
+ this._stat = statMethod;
54423
+ }
54424
+ this._maxDepth = opts.depth ?? defaultOptions.depth;
54425
+ this._wantsDir = type ? DIR_TYPES.has(type) : false;
54426
+ this._wantsFile = type ? FILE_TYPES.has(type) : false;
54427
+ this._wantsEverything = type === EntryTypes.EVERYTHING_TYPE;
54428
+ this._root = (0, import_node_path15.resolve)(root);
54429
+ this._isDirent = !opts.alwaysStat;
54430
+ this._statsProp = this._isDirent ? "dirent" : "stats";
54431
+ this._rdOptions = { encoding: "utf8", withFileTypes: this._isDirent };
54432
+ this.parents = [this._exploreDir(root, 1)];
54433
+ this.reading = false;
54434
+ this.parent = void 0;
54435
+ }
54436
+ async _read(batch) {
54437
+ if (this.reading)
54438
+ return;
54439
+ this.reading = true;
54440
+ try {
54441
+ while (!this.destroyed && batch > 0) {
54442
+ const par = this.parent;
54443
+ const fil = par && par.files;
54444
+ if (fil && fil.length > 0) {
54445
+ const { path, depth } = par;
54446
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path));
54447
+ const awaited = await Promise.all(slice);
54448
+ for (const entry2 of awaited) {
54449
+ if (!entry2)
54450
+ continue;
54451
+ if (this.destroyed)
54452
+ return;
54453
+ const entryType = await this._getEntryType(entry2);
54454
+ if (entryType === "directory" && this._directoryFilter(entry2)) {
54455
+ if (depth <= this._maxDepth) {
54456
+ this.parents.push(this._exploreDir(entry2.fullPath, depth + 1));
54457
+ }
54458
+ if (this._wantsDir) {
54459
+ this.push(entry2);
54460
+ batch--;
54461
+ }
54462
+ } else if ((entryType === "file" || this._includeAsFile(entry2)) && this._fileFilter(entry2)) {
54463
+ if (this._wantsFile) {
54464
+ this.push(entry2);
54465
+ batch--;
54466
+ }
54467
+ }
54468
+ }
54469
+ } else {
54470
+ const parent = this.parents.pop();
54471
+ if (!parent) {
54472
+ this.push(null);
54473
+ break;
54474
+ }
54475
+ this.parent = await parent;
54476
+ if (this.destroyed)
54477
+ return;
54478
+ }
54479
+ }
54480
+ } catch (error) {
54481
+ this.destroy(error);
54482
+ } finally {
54483
+ this.reading = false;
54484
+ }
54485
+ }
54486
+ async _exploreDir(path, depth) {
54487
+ let files;
54488
+ try {
54489
+ files = await (0, import_promises2.readdir)(path, this._rdOptions);
54490
+ } catch (error) {
54491
+ this._onError(error);
54492
+ }
54493
+ return { files, depth, path };
54494
+ }
54495
+ async _formatEntry(dirent, path) {
54496
+ let entry2;
54497
+ const basename5 = this._isDirent ? dirent.name : dirent;
54498
+ try {
54499
+ const fullPath = (0, import_node_path15.resolve)((0, import_node_path15.join)(path, basename5));
54500
+ entry2 = { path: (0, import_node_path15.relative)(this._root, fullPath), fullPath, basename: basename5 };
54501
+ entry2[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
54502
+ } catch (err) {
54503
+ this._onError(err);
54504
+ return;
54505
+ }
54506
+ return entry2;
54507
+ }
54508
+ _onError(err) {
54509
+ if (isNormalFlowError(err) && !this.destroyed) {
54510
+ this.emit("warn", err);
54109
54511
  } else {
54110
54512
  this.destroy(err);
54111
54513
  }
@@ -54129,7 +54531,7 @@ var ReaddirpStream = class extends import_node_stream2.Readable {
54129
54531
  }
54130
54532
  if (entryRealPathStats.isDirectory()) {
54131
54533
  const len = entryRealPath.length;
54132
- if (full.startsWith(entryRealPath) && full.substr(len, 1) === import_node_path14.sep) {
54534
+ if (full.startsWith(entryRealPath) && full.substr(len, 1) === import_node_path15.sep) {
54133
54535
  const recursiveError = new Error(`Circular symlink detected: "${full}" points to "${entryRealPath}"`);
54134
54536
  recursiveError.code = RECURSIVE_ERROR_CODE;
54135
54537
  return this._onError(recursiveError);
@@ -55614,1081 +56016,684 @@ var FSWatcher = class extends import_events.EventEmitter {
55614
56016
  stream.once(STR_CLOSE, () => {
55615
56017
  stream = void 0;
55616
56018
  });
55617
- stream.once(STR_END, () => {
55618
- if (stream) {
55619
- this._streams.delete(stream);
55620
- stream = void 0;
55621
- }
55622
- });
55623
- return stream;
55624
- }
55625
- };
55626
- function watch(paths, options = {}) {
55627
- const watcher = new FSWatcher(options);
55628
- watcher.add(paths);
55629
- return watcher;
55630
- }
55631
- var esm_default = { watch, FSWatcher };
55632
-
55633
- // watch.ts
55634
- function appIdFromLogPath(path) {
55635
- const parts = path.split(/[\\/]/);
55636
- const i = parts.lastIndexOf("logs");
55637
- return i >= 0 && parts[i + 1] ? parts[i + 1] : null;
55638
- }
55639
- function startWatcher() {
55640
- const awareDir = process.env.AWARE_HOME ?? (0, import_node_path15.join)((0, import_node_os10.homedir)(), ".aware");
55641
- const credentialsDir = (0, import_node_path15.join)(awareDir, "credentials");
55642
- if (!(0, import_node_fs16.existsSync)(credentialsDir)) {
55643
- try {
55644
- (0, import_node_fs16.mkdirSync)(credentialsDir, { recursive: true });
55645
- } catch {
55646
- }
55647
- }
55648
- const targets = ["apps", "logs", "credentials"].map((d) => (0, import_node_path15.join)(awareDir, d)).filter((p) => (0, import_node_fs16.existsSync)(p));
55649
- if (targets.length === 0) {
55650
- return null;
55651
- }
55652
- const watcher = esm_default.watch(targets, {
55653
- ignoreInitial: true,
55654
- awaitWriteFinish: { stabilityThreshold: 150, pollInterval: 50 }
55655
- });
55656
- watcher.on("all", (event, path) => {
55657
- const isCredential = path.split(/[\\/]/).includes("credentials");
55658
- const kind = isCredential ? "credential" : path.endsWith(".jsonl") ? "trace" : path.endsWith(".lock") ? "lock" : path.endsWith(".flo") || path.endsWith(".app") ? "source" : "file";
55659
- broadcast({ type: "fs-change", kind, event, path });
55660
- if (kind === "trace" && event !== "unlink" && (0, import_node_fs16.existsSync)(path)) {
55661
- const id = appIdFromLogPath(path);
55662
- if (!id) return;
55663
- try {
55664
- broadcast({ type: "trace-file", id, runId: path.split(import_node_path15.sep).pop()?.replace(/\.jsonl$/, "") ?? null, events: parseTrace((0, import_node_fs16.readFileSync)(path, "utf8")) });
55665
- } catch {
55666
- }
55667
- }
55668
- });
55669
- return { close: () => watcher.close() };
55670
- }
55671
-
55672
- // index.ts
55673
- var __dirname3 = (0, import_node_path16.dirname)((0, import_node_url2.fileURLToPath)(__import_meta_url));
55674
- var WEB_ROOT = [(0, import_node_path16.join)(__dirname3, "web"), (0, import_node_path16.join)((0, import_node_path16.dirname)(process.execPath), "web"), (0, import_node_path16.join)(__dirname3, "..", "web")].find(
55675
- (p) => (0, import_node_fs17.existsSync)(p)
55676
- ) ?? (0, import_node_path16.join)(__dirname3, "..", "web");
55677
- var PORT = Number(process.env.PORT ?? 4317);
55678
- var HOST = "127.0.0.1";
55679
- function extractReportHtml(events) {
55680
- let found = null;
55681
- for (const ev of events) {
55682
- const data = ev?.data;
55683
- if (!data || typeof data !== "object") continue;
55684
- const result = data.result;
55685
- const html = typeof data.html === "string" ? data.html : result && typeof result.html === "string" ? result.html : null;
55686
- if (html) found = { nodeId: ev.node ?? null, html };
55687
- }
55688
- return found;
55689
- }
55690
- var crashHandlersInstalled = false;
55691
- function installCrashHandlers() {
55692
- if (crashHandlersInstalled) return;
55693
- crashHandlersInstalled = true;
55694
- ensureLogDir();
55695
- rotateLog();
55696
- const record = (kind, err) => {
55697
- const stack = err instanceof Error && err.stack ? err.stack : String(err);
55698
- const line = `
55699
- [${(/* @__PURE__ */ new Date()).toISOString()}] ${kind}
55700
- ${stack}
55701
- `;
55702
- try {
55703
- (0, import_node_fs17.appendFileSync)(logFilePath(), line);
55704
- } catch {
55705
- }
55706
- if (process.stderr.isTTY) process.stderr.write(line);
55707
- };
55708
- process.on("uncaughtException", (err) => {
55709
- record("uncaughtException", err);
55710
- process.exit(1);
55711
- });
55712
- process.on("unhandledRejection", (reason) => {
55713
- record("unhandledRejection", reason);
55714
- process.exit(1);
55715
- });
55716
- }
55717
- async function startServer() {
55718
- installCrashHandlers();
55719
- const logger = true ? true : { transport: { target: "pino-pretty" } };
55720
- const app = (0, import_fastify.default)({ logger });
55721
- app.addContentTypeParser("application/json", { parseAs: "string" }, (_req, body, done) => {
55722
- const s = body ?? "";
55723
- if (s.trim() === "") {
55724
- done(null, {});
55725
- return;
55726
- }
55727
- try {
55728
- done(null, JSON.parse(s));
55729
- } catch (err) {
55730
- done(err);
55731
- }
55732
- });
55733
- await app.register(import_static.default, { root: WEB_ROOT, prefix: "/" });
55734
- const MIN_AWARE = "0.51.0";
55735
- const isWin3 = process.platform === "win32";
55736
- const has = (cmd) => {
55737
- try {
55738
- (0, import_node_child_process5.execFileSync)(isWin3 ? "where" : "which", [cmd], { stdio: "ignore" });
55739
- return true;
55740
- } catch {
55741
- return false;
55742
- }
55743
- };
55744
- function installAwareGlobal(spec, onLine = () => {
55745
- }) {
55746
- return new Promise((resolve4, reject) => {
55747
- const child = (0, import_node_child_process5.spawn)("npm", ["i", "-g", `@aware-aeco/cli@${spec}`], { shell: isWin3, windowsHide: true });
55748
- let stderrTail = "";
55749
- let combinedTail = "";
55750
- const tail = (buf, s) => (buf + s).slice(-4e3);
55751
- child.stdout.on("data", (d) => {
55752
- const s = d.toString();
55753
- combinedTail = tail(combinedTail, s);
55754
- onLine(s.trimEnd());
55755
- });
55756
- child.stderr.on("data", (d) => {
55757
- const s = d.toString();
55758
- stderrTail = tail(stderrTail, s);
55759
- combinedTail = tail(combinedTail, s);
55760
- onLine(s.trimEnd());
55761
- });
55762
- child.on("error", (e) => reject(e));
55763
- child.on("close", (code) => code === 0 ? resolve4() : reject(new Error(`npm exited ${code}: ${stderrTail || combinedTail}`)));
55764
- });
55765
- }
55766
- const bootstrapDeps = {
55767
- probeToolchain: () => ({ hasNode: has("node"), hasNpm: has("npm") }),
55768
- getAware: () => {
55769
- const v = aware.npmVersion();
55770
- return { installed: v !== null, version: v };
55771
- },
55772
- installAware: installAwareGlobal,
55773
- smoke: () => aware.smoke(),
55774
- refreshInvoker,
55775
- emit: broadcast,
55776
- floor: MIN_AWARE,
55777
- skip: process.env.AWARE_SKIP_BOOTSTRAP === "1"
55778
- };
55779
- app.setErrorHandler((err, _req, reply) => {
55780
- if (err instanceof AppNotFoundError) {
55781
- return reply.status(404).send({ ok: false, error: err.message });
55782
- }
55783
- if (err instanceof RoutineError) {
55784
- return reply.status(err.status).send({ ok: false, error: err.message });
55785
- }
55786
- if (err instanceof AwareError) {
55787
- app.log.warn({ detail: err.detail }, err.message);
55788
- return reply.status(502).send({ ok: false, error: err.message, detail: err.detail ?? null });
55789
- }
55790
- app.log.error(err);
55791
- return reply.status(500).send({ ok: false, error: err.message });
55792
- });
55793
- const SELF_ORIGINS = /* @__PURE__ */ new Set([
55794
- `http://127.0.0.1:${PORT}`,
55795
- `http://localhost:${PORT}`,
55796
- `http://floless.localhost:${PORT}`
55797
- // branded address (browsers resolve *.localhost → 127.0.0.1)
55798
- ]);
55799
- app.addHook("onRequest", async (req, reply) => {
55800
- if (!req.url.startsWith("/api/")) return;
55801
- const origin = req.headers.origin;
55802
- if (origin && !SELF_ORIGINS.has(origin)) {
55803
- return reply.status(403).send({ ok: false, error: "cross-origin request rejected" });
55804
- }
55805
- });
55806
- app.addHook("onRequest", async (req, reply) => {
55807
- if (!req.url.startsWith("/api/")) return;
55808
- if (req.url.startsWith("/api/health") || req.url.startsWith("/api/license/") || req.url.startsWith("/api/bootstrap/") || req.url.startsWith("/api/autostart") || req.url.startsWith("/api/update") || req.url.startsWith("/api/aware/update")) return;
55809
- const { state: state2, signInUrl: signInUrl2 } = await getLicenseStatus();
55810
- if (state2 !== "valid" && state2 !== "offline-grace") {
55811
- return reply.status(402).send({ ok: false, error: "subscription required", state: state2, signInUrl: signInUrl2 });
55812
- }
55813
- });
55814
- app.addHook("onRequest", async (req, reply) => {
55815
- if (!isGatedAwareRoute(req.url, req.method)) return;
55816
- const st = getBootstrapState();
55817
- if (st.status === "ready") return;
55818
- if (st.status === "failed") return reply.status(503).send({ ok: false, error: "AWARE unavailable", bootstrap: st.status, reason: st.reason, remediation: st.remediation });
55819
- return reply.status(409).send({ ok: false, error: "AWARE is still setting up", bootstrap: st.status });
55820
- });
55821
- app.get("/api/health", async () => {
55822
- const bs = getBootstrapState();
55823
- return {
55824
- ok: true,
55825
- appVersion: appVersion(),
55826
- // the installed build (sq.version), so it's scriptable
55827
- awareVersion: awareInstalledVersion() ?? bs.awareVersion,
55828
- // fresh (TTL-cached) install version; the boot snapshot is only a fallback
55829
- awareReady: bs.awareReady,
55830
- bootstrap: bs.status,
55831
- bootstrapReason: bs.reason,
55832
- bootstrapRemediation: bs.remediation,
55833
- // UI maps reason→fixed copy; this is secondary dev context
55834
- sseClients: clientCount(),
55835
- license: (await getLicenseStatus()).state
55836
- // UI + launcher branch on this
55837
- };
55838
- });
55839
- app.get("/api/license/status", async () => ({ ok: true, ...await getLicenseStatus() }));
55840
- app.post("/api/license/start", async () => ({ ok: true, ...await startLogin() }));
55841
- app.post("/api/license/logout", async () => {
55842
- logout();
55843
- return { ok: true };
55844
- });
55845
- app.get("/api/autostart", async () => {
55846
- const supported = autostartSupported();
55847
- return { ok: true, supported, enabled: supported ? autostartPresent() : false };
55848
- });
55849
- app.put("/api/autostart", async (req, reply) => {
55850
- if (!autostartSupported()) return reply.send({ ok: true, supported: false, enabled: false });
55851
- if (typeof req.body?.enabled !== "boolean") {
55852
- return reply.status(400).send({ ok: false, error: "body { enabled: boolean } required" });
55853
- }
55854
- try {
55855
- if (req.body.enabled) registerAutostart(resolveRealInstallExe(process.execPath));
55856
- else unregisterAutostart();
55857
- } catch (err) {
55858
- return reply.status(500).send({ ok: false, error: err instanceof Error ? err.message : String(err) });
55859
- }
55860
- return { ok: true, supported: true, enabled: autostartPresent() };
55861
- });
55862
- app.get("/api/update", async () => {
55863
- return { ok: true, ...await checkForUpdate() };
55864
- });
55865
- app.post("/api/update/apply", async (_req, reply) => {
55866
- const check = await checkForUpdate();
55867
- if (!check.supported || !check.updateAvailable) {
55868
- return reply.status(409).send({ ok: false, ...check, error: check.reason ?? "no update available" });
55869
- }
55870
- const result = await applyUpdate(check);
55871
- if (!result.applied) return reply.status(500).send({ ok: false, error: result.message });
55872
- reply.send({ ok: true, ...result });
55873
- setTimeout(() => process.exit(0), 250);
55874
- });
55875
- app.get("/api/aware/update", async () => {
55876
- return { ok: true, ...await checkAwareUpdate() };
55877
- });
55878
- let awareInstallInFlight = false;
55879
- app.post("/api/aware/update/apply", async (_req, reply) => {
55880
- const block = awareUpgradeBlockReason({
55881
- bootstrapInstalling: getBootstrapState().status === "installing",
55882
- installInFlight: awareInstallInFlight,
55883
- runActive: isRunActive(),
55884
- hostWatcherActive: hostWatcherActive()
56019
+ stream.once(STR_END, () => {
56020
+ if (stream) {
56021
+ this._streams.delete(stream);
56022
+ stream = void 0;
56023
+ }
55885
56024
  });
55886
- if (block) return reply.status(409).send({ ok: false, error: block });
55887
- awareInstallInFlight = true;
56025
+ return stream;
56026
+ }
56027
+ };
56028
+ function watch(paths, options = {}) {
56029
+ const watcher = new FSWatcher(options);
56030
+ watcher.add(paths);
56031
+ return watcher;
56032
+ }
56033
+ var esm_default = { watch, FSWatcher };
56034
+
56035
+ // watch.ts
56036
+ function appIdFromLogPath(path) {
56037
+ const parts = path.split(/[\\/]/);
56038
+ const i = parts.lastIndexOf("logs");
56039
+ return i >= 0 && parts[i + 1] ? parts[i + 1] : null;
56040
+ }
56041
+ function startWatcher() {
56042
+ const awareDir = process.env.AWARE_HOME ?? (0, import_node_path16.join)((0, import_node_os10.homedir)(), ".aware");
56043
+ const credentialsDir = (0, import_node_path16.join)(awareDir, "credentials");
56044
+ if (!(0, import_node_fs17.existsSync)(credentialsDir)) {
55888
56045
  try {
55889
- const res = await applyAwareUpdate(awareApplyDeps(installAwareGlobal));
55890
- if (!res.ok) return reply.status(409).send({ ok: false, error: res.error ?? "aware upgrade failed" });
55891
- return { ok: true, version: res.version };
55892
- } finally {
55893
- awareInstallInFlight = false;
55894
- }
55895
- });
55896
- app.post("/api/bootstrap/retry", async () => {
55897
- const st = getBootstrapState().status;
55898
- if (st === "failed" || st === "idle") {
55899
- void runBootstrap(bootstrapDeps);
56046
+ (0, import_node_fs17.mkdirSync)(credentialsDir, { recursive: true });
56047
+ } catch {
55900
56048
  }
55901
- return { ok: true, bootstrap: getBootstrapState().status };
55902
- });
55903
- onSeatLost((reason) => broadcast({ type: "seat-taken", reason }));
55904
- app.get("/api/apps", async () => {
55905
- const apps = await aware.list();
55906
- return { ok: true, apps: apps.map((a) => ({ ...a, provider: appProvider(a.id) })) };
55907
- });
55908
- app.get("/api/agents", async () => {
55909
- const agents = await aware.agentList();
55910
- return { ok: true, agents };
55911
- });
55912
- app.get("/api/agent/:id", async (req) => {
55913
- const agent = await aware.agentDescribe(req.params.id);
55914
- return { ok: true, agent };
56049
+ }
56050
+ const targets = ["apps", "logs", "credentials"].map((d) => (0, import_node_path16.join)(awareDir, d)).filter((p) => (0, import_node_fs17.existsSync)(p));
56051
+ if (targets.length === 0) {
56052
+ return null;
56053
+ }
56054
+ const watcher = esm_default.watch(targets, {
56055
+ ignoreInitial: true,
56056
+ awaitWriteFinish: { stabilityThreshold: 150, pollInterval: 50 }
55915
56057
  });
55916
- app.get("/api/integrations", async () => {
55917
- const integrations = listIntegrations();
55918
- try {
55919
- const live = await aware.connectList();
55920
- const byId = new Map(live.map((c) => [c.integration, c]));
55921
- for (const it of integrations) {
55922
- const c = byId.get(it.id);
55923
- if (!c) continue;
55924
- it.status = c.status === "valid" ? "connected" : c.status === "expired" ? "expired" : "disconnected";
55925
- it.expiresAt = c.expires_in_secs != null ? new Date(Date.now() + c.expires_in_secs * 1e3).toISOString() : it.expiresAt;
56058
+ watcher.on("all", (event, path) => {
56059
+ const isCredential = path.split(/[\\/]/).includes("credentials");
56060
+ const kind = isCredential ? "credential" : path.endsWith(".jsonl") ? "trace" : path.endsWith(".lock") ? "lock" : path.endsWith(".flo") || path.endsWith(".app") ? "source" : "file";
56061
+ broadcast({ type: "fs-change", kind, event, path });
56062
+ if (kind === "trace" && event !== "unlink" && (0, import_node_fs17.existsSync)(path)) {
56063
+ const id = appIdFromLogPath(path);
56064
+ if (!id) return;
56065
+ try {
56066
+ broadcast({ type: "trace-file", id, runId: path.split(import_node_path16.sep).pop()?.replace(/\.jsonl$/, "") ?? null, events: parseTrace((0, import_node_fs17.readFileSync)(path, "utf8")) });
56067
+ } catch {
55926
56068
  }
55927
- } catch (err) {
55928
- app.log.warn({ err: String(err) }, "aware connect --list unavailable; using file-based status");
55929
56069
  }
55930
- return { ok: true, integrations };
55931
56070
  });
55932
- const DEFAULT_CONNECT_FLOW = {};
55933
- app.post("/api/connect/:id", async (req) => {
55934
- const id = req.params.id;
55935
- const requested = req.body?.flow;
55936
- const flow = requested === "device-code" || requested === "oauth" ? requested : DEFAULT_CONNECT_FLOW[id] ?? "oauth";
55937
- if (flow === "device-code") {
55938
- const { prompt } = await aware.connectDeviceCode(id, (event) => {
55939
- if (event.phase === "result") {
55940
- broadcast({ type: "connect-result", id, status: event.status ?? "unknown", error: event.error ?? null });
55941
- }
55942
- });
55943
- return { ok: true, id, flow, prompt };
56071
+ return { close: () => watcher.close() };
56072
+ }
56073
+
56074
+ // index.ts
56075
+ var __dirname4 = (0, import_node_path17.dirname)((0, import_node_url3.fileURLToPath)(__import_meta_url));
56076
+ var WEB_ROOT = [(0, import_node_path17.join)(__dirname4, "web"), (0, import_node_path17.join)((0, import_node_path17.dirname)(process.execPath), "web"), (0, import_node_path17.join)(__dirname4, "..", "web")].find(
56077
+ (p) => (0, import_node_fs18.existsSync)(p)
56078
+ ) ?? (0, import_node_path17.join)(__dirname4, "..", "web");
56079
+ var PORT2 = Number(process.env.PORT ?? 4317);
56080
+ var HOST = "127.0.0.1";
56081
+ function extractReportHtml(events) {
56082
+ let found = null;
56083
+ for (const ev of events) {
56084
+ const data = ev?.data;
56085
+ if (!data || typeof data !== "object") continue;
56086
+ const result = data.result;
56087
+ const html = typeof data.html === "string" ? data.html : result && typeof result.html === "string" ? result.html : null;
56088
+ if (html) found = { nodeId: ev.node ?? null, html };
56089
+ }
56090
+ return found;
56091
+ }
56092
+ var crashHandlersInstalled = false;
56093
+ function installCrashHandlers() {
56094
+ if (crashHandlersInstalled) return;
56095
+ crashHandlersInstalled = true;
56096
+ ensureLogDir();
56097
+ rotateLog();
56098
+ const record = (kind, err) => {
56099
+ const stack = err instanceof Error && err.stack ? err.stack : String(err);
56100
+ const line = `
56101
+ [${(/* @__PURE__ */ new Date()).toISOString()}] ${kind}
56102
+ ${stack}
56103
+ `;
56104
+ try {
56105
+ (0, import_node_fs18.appendFileSync)(logFilePath(), line);
56106
+ } catch {
55944
56107
  }
55945
- aware.connectOAuth(id, (result) => {
55946
- broadcast({
55947
- type: "connect-result",
55948
- id,
55949
- status: result.status === "connected" ? "connected" : "failed",
55950
- error: result.status === "connected" ? null : "sign-in failed or was cancelled"
55951
- });
55952
- });
55953
- return { ok: true, id, flow, started: true };
55954
- });
55955
- app.get("/api/app/:id", async (req) => {
55956
- return { ok: true, app: readApp(req.params.id) };
56108
+ if (process.stderr.isTTY) process.stderr.write(line);
56109
+ };
56110
+ process.on("uncaughtException", (err) => {
56111
+ record("uncaughtException", err);
56112
+ process.exit(1);
55957
56113
  });
55958
- app.post("/api/compile", async (req, reply) => {
55959
- const { id, sourcePath } = req.body ?? {};
55960
- const path = sourcePath ?? (id ? readApp(id).source.path : void 0);
55961
- if (!path) return reply.status(400).send({ ok: false, error: "id or sourcePath required" });
55962
- const result = await aware.compile(path);
55963
- broadcast({ type: "compiled", id: id ?? null, lockPath: result.lockPath });
55964
- return { ok: true, result };
56114
+ process.on("unhandledRejection", (reason) => {
56115
+ record("unhandledRejection", reason);
56116
+ process.exit(1);
55965
56117
  });
55966
- app.post("/api/bake", async (req, reply) => {
55967
- const { id } = req.body ?? {};
55968
- if (!id) return reply.status(400).send({ ok: false, error: "id required" });
55969
- const appData = readApp(id);
55970
- if (!appData.runnable) {
55971
- return reply.status(409).send({ ok: false, error: "app not runnable \u2014 compile the lock first, then bake" });
56118
+ }
56119
+ async function startServer() {
56120
+ installCrashHandlers();
56121
+ const logger = true ? true : { transport: { target: "pino-pretty" } };
56122
+ const app = (0, import_fastify.default)({ logger });
56123
+ app.addContentTypeParser("application/json", { parseAs: "string" }, (_req, body, done) => {
56124
+ const s = body ?? "";
56125
+ if (s.trim() === "") {
56126
+ done(null, {});
56127
+ return;
55972
56128
  }
55973
- const inputs = appData.inputs.map((i) => ({ name: i.name, type: i.type }));
55974
- const baked = bakeFloSource(appData.source.text, inputs);
55975
- const tmpRoot = (0, import_node_fs17.mkdtempSync)((0, import_node_path16.join)((0, import_node_os11.tmpdir)(), "floless-bake-"));
55976
- const backupDir = (0, import_node_path16.join)(tmpRoot, `${id}-backup`);
55977
- const bakeDir = (0, import_node_path16.join)(tmpRoot, id);
55978
- (0, import_node_fs17.cpSync)(appDir(id), backupDir, { recursive: true });
55979
- (0, import_node_fs17.cpSync)(appDir(id), bakeDir, { recursive: true });
55980
- const floName = appData.source.path.split(/[\\/]/).pop();
55981
- (0, import_node_fs17.writeFileSync)((0, import_node_path16.join)(bakeDir, floName), baked);
55982
- let appInstalled = true;
55983
56129
  try {
55984
- await aware.uninstall(id);
55985
- appInstalled = false;
55986
- try {
55987
- await aware.install(bakeDir);
55988
- appInstalled = true;
55989
- } catch (installErr) {
55990
- try {
55991
- await aware.install(backupDir);
55992
- appInstalled = true;
55993
- } catch (restoreErr) {
55994
- app.log.error({ id, tmpRoot, restoreErr: String(restoreErr) }, "bake: install AND restore failed \u2014 app uninstalled; backup retained");
55995
- return reply.status(502).send({
55996
- ok: false,
55997
- error: `bake failed and the app could not be restored \u2014 its source is backed up at ${backupDir}. Reinstall it from there with: aware app install "${backupDir}"`
55998
- });
55999
- }
56000
- throw installErr;
56001
- }
56002
- try {
56003
- await aware.compile((0, import_node_path16.join)(appDir(id), floName));
56004
- } catch (compileErr) {
56005
- app.log.warn({ id, compileErr: String(compileErr) }, "bake: post-install recompile failed (app baked but may need a manual Compile)");
56006
- }
56007
- broadcast({ type: "baked", id });
56008
- return { ok: true, id, agent: id, inputs };
56009
- } finally {
56010
- if (appInstalled) (0, import_node_fs17.rmSync)(tmpRoot, { recursive: true, force: true });
56130
+ done(null, JSON.parse(s));
56131
+ } catch (err) {
56132
+ done(err);
56011
56133
  }
56012
56134
  });
56013
- const graftAgentsDir = () => (0, import_node_path16.join)((0, import_node_os11.homedir)(), ".aware", "agents");
56014
- app.post("/api/graft/match", async (req, reply) => {
56015
- const { glob } = req.body ?? {};
56016
- if (!glob) return reply.status(400).send({ ok: false, error: "glob required" });
56017
- let safe;
56135
+ await app.register(import_static.default, { root: WEB_ROOT, prefix: "/" });
56136
+ const MIN_AWARE = "0.51.0";
56137
+ const isWin3 = process.platform === "win32";
56138
+ const has = (cmd) => {
56018
56139
  try {
56019
- safe = assertSourceRef("dlls", glob);
56020
- } catch (e) {
56021
- return reply.status(400).send({ ok: false, error: e.message });
56022
- }
56023
- return { ok: true, files: matchAssemblies(safe) };
56024
- });
56025
- app.post("/api/graft/introspect", async (req, reply) => {
56026
- const { sourceKind, sourceRef, decompile, outputId, acceptLicense, referenceDirs } = req.body ?? {};
56027
- if (!sourceKind || !sourceRef) {
56028
- return reply.status(400).send({ ok: false, error: "sourceKind and sourceRef required" });
56140
+ (0, import_node_child_process6.execFileSync)(isWin3 ? "where" : "which", [cmd], { stdio: "ignore" });
56141
+ return true;
56142
+ } catch {
56143
+ return false;
56029
56144
  }
56030
- const tempHome = (0, import_node_fs17.mkdtempSync)((0, import_node_path16.join)((0, import_node_os11.tmpdir)(), "floless-graft-"));
56031
- let result;
56032
- try {
56033
- result = await aware.build({
56034
- sourceKind,
56035
- sourceRef,
56036
- decompile,
56037
- outputId,
56038
- acceptLicense,
56039
- referenceDirs,
56040
- awareHome: tempHome
56145
+ };
56146
+ function installAwareGlobal(spec, onLine = () => {
56147
+ }) {
56148
+ return new Promise((resolve4, reject) => {
56149
+ const child = (0, import_node_child_process6.spawn)("npm", ["i", "-g", `@aware-aeco/cli@${spec}`], { shell: isWin3, windowsHide: true });
56150
+ let stderrTail = "";
56151
+ let combinedTail = "";
56152
+ const tail = (buf, s) => (buf + s).slice(-4e3);
56153
+ child.stdout.on("data", (d) => {
56154
+ const s = d.toString();
56155
+ combinedTail = tail(combinedTail, s);
56156
+ onLine(s.trimEnd());
56041
56157
  });
56042
- } catch (err) {
56043
- (0, import_node_fs17.rmSync)(tempHome, { recursive: true, force: true });
56044
- const msg = err instanceof AwareError ? err.message : String(err?.message ?? err);
56045
- return reply.status(422).send({ ok: false, error: msg });
56158
+ child.stderr.on("data", (d) => {
56159
+ const s = d.toString();
56160
+ stderrTail = tail(stderrTail, s);
56161
+ combinedTail = tail(combinedTail, s);
56162
+ onLine(s.trimEnd());
56163
+ });
56164
+ child.on("error", (e) => reject(e));
56165
+ child.on("close", (code) => code === 0 ? resolve4() : reject(new Error(`npm exited ${code}: ${stderrTail || combinedTail}`)));
56166
+ });
56167
+ }
56168
+ const bootstrapDeps = {
56169
+ probeToolchain: () => ({ hasNode: has("node"), hasNpm: has("npm") }),
56170
+ getAware: () => {
56171
+ const v = aware.npmVersion();
56172
+ return { installed: v !== null, version: v };
56173
+ },
56174
+ installAware: installAwareGlobal,
56175
+ smoke: () => aware.smoke(),
56176
+ refreshInvoker,
56177
+ emit: broadcast,
56178
+ floor: MIN_AWARE,
56179
+ skip: process.env.AWARE_SKIP_BOOTSTRAP === "1"
56180
+ };
56181
+ app.setErrorHandler((err, _req, reply) => {
56182
+ if (err instanceof AppNotFoundError) {
56183
+ return reply.status(404).send({ ok: false, error: err.message });
56046
56184
  }
56047
- const manifest = readStagedManifest(result.agentDir);
56048
- if (!manifest) {
56049
- (0, import_node_fs17.rmSync)(tempHome, { recursive: true, force: true });
56050
- return reply.status(502).send({ ok: false, error: `build produced output at ${result.agentDir} but no manifest.yaml` });
56185
+ if (err instanceof RoutineError) {
56186
+ return reply.status(err.status).send({ ok: false, error: err.message });
56051
56187
  }
56052
- const token = (0, import_node_crypto5.randomUUID)();
56053
- registerStage(token, tempHome, result.agentId);
56054
- const preview = buildPreview(manifest, sourceKind, sourceRef, token);
56055
- if ((0, import_node_fs17.existsSync)((0, import_node_path16.join)(graftAgentsDir(), result.agentId))) {
56056
- preview.warnings.unshift(`An agent named "${result.agentId}" is already installed \u2014 creating it will overwrite it.`);
56188
+ if (err instanceof AwareError) {
56189
+ app.log.warn({ detail: err.detail }, err.message);
56190
+ return reply.status(502).send({ ok: false, error: err.message, detail: err.detail ?? null });
56057
56191
  }
56058
- return { ok: true, preview };
56192
+ app.log.error(err);
56193
+ return reply.status(500).send({ ok: false, error: err.message });
56059
56194
  });
56060
- app.post("/api/graft/commit", async (req, reply) => {
56061
- const { stagedRef, force = false } = req.body ?? {};
56062
- if (!stagedRef) return reply.status(400).send({ ok: false, error: "stagedRef required" });
56063
- const stage = consumeStage(stagedRef);
56064
- if (!stage) {
56065
- return reply.status(410).send({ ok: false, error: "staged build expired or not found \u2014 introspect again" });
56066
- }
56067
- try {
56068
- commitStaged(stage.tempDir, stage.agentId, graftAgentsDir(), force);
56069
- } catch (err) {
56070
- if (err instanceof GraftCommitError && err.code === "collision") {
56071
- registerStage(stagedRef, stage.tempDir, stage.agentId);
56072
- return reply.status(409).send({ ok: false, error: err.message, agentId: stage.agentId, collision: true });
56073
- }
56074
- (0, import_node_fs17.rmSync)(stage.tempDir, { recursive: true, force: true });
56075
- throw err;
56195
+ const SELF_ORIGINS = /* @__PURE__ */ new Set([
56196
+ `http://127.0.0.1:${PORT2}`,
56197
+ `http://localhost:${PORT2}`,
56198
+ `http://floless.localhost:${PORT2}`
56199
+ // branded address (browsers resolve *.localhost → 127.0.0.1)
56200
+ ]);
56201
+ app.addHook("onRequest", async (req, reply) => {
56202
+ if (!req.url.startsWith("/api/")) return;
56203
+ const origin = req.headers.origin;
56204
+ if (origin && !SELF_ORIGINS.has(origin)) {
56205
+ return reply.status(403).send({ ok: false, error: "cross-origin request rejected" });
56076
56206
  }
56077
- broadcast({ type: "grafted", id: stage.agentId });
56078
- return { ok: true, agentId: stage.agentId };
56079
56207
  });
56080
- app.post(
56081
- "/api/run",
56082
- async (req, reply) => {
56083
- const { id, dryRun, simulate, inputs } = req.body ?? {};
56084
- if (!id) return reply.status(400).send({ ok: false, error: "id required" });
56085
- const blocked = hostRunBlocked(appProvider(id));
56086
- if (blocked) return reply.send({ ok: false, error: blocked, blocked: true, simulate: !!simulate });
56087
- let result;
56088
- try {
56089
- result = await aware.run(id, { dryRun, simulate, inputs });
56090
- } catch (err) {
56091
- if (err instanceof AwareError) {
56092
- const detail = err.detail && typeof err.detail === "object" ? err.detail : null;
56093
- if (detail && detail.cancelled === true) {
56094
- app.log.info({ id }, "run cancelled by user");
56095
- return reply.send({ ok: false, error: "Run cancelled", cancelled: true, simulate: !!simulate });
56096
- }
56097
- const stderr = detail && typeof detail.stderr === "string" ? detail.stderr.trim() : "";
56098
- app.log.warn({ detail: err.detail }, err.message);
56099
- return reply.send({ ok: false, error: err.message, reason: stderr || null, simulate: !!simulate });
56100
- }
56101
- throw err;
56102
- }
56103
- const events = result.traceText ? parseTrace(result.traceText) : [];
56104
- broadcast({ type: "run-started", id, dryRun: !!dryRun, simulate: !!simulate, runId: result.runId });
56105
- for (const ev of events) broadcast({ type: "trace", id, event: ev });
56106
- broadcast({ type: "run-ended", id, runId: result.runId });
56107
- const report = extractReportHtml(events);
56108
- return { ok: true, runId: result.runId, tracePath: result.tracePath, events, report };
56208
+ app.addHook("onRequest", async (req, reply) => {
56209
+ if (!req.url.startsWith("/api/")) return;
56210
+ if (req.url.startsWith("/api/health") || req.url.startsWith("/api/license/") || req.url.startsWith("/api/bootstrap/") || req.url.startsWith("/api/autostart") || req.url.startsWith("/api/update") || req.url.startsWith("/api/aware/update")) return;
56211
+ const { state: state2, signInUrl: signInUrl2 } = await getLicenseStatus();
56212
+ if (state2 !== "valid" && state2 !== "offline-grace") {
56213
+ return reply.status(402).send({ ok: false, error: "subscription required", state: state2, signInUrl: signInUrl2 });
56109
56214
  }
56110
- );
56111
- app.post("/api/run/stop", async () => {
56112
- const running = cancelActiveRun();
56113
- return { ok: true, running };
56114
56215
  });
56115
- function resolveArgs(configArgs, inputs) {
56116
- const out = {};
56117
- for (const [k, v] of Object.entries(configArgs)) {
56118
- if (typeof v === "string") {
56119
- const m = v.match(/^\{\{\s*inputs\.([A-Za-z0-9._-]+)\s*\}\}$/);
56120
- out[k] = m && m[1] != null && m[1] in inputs ? inputs[m[1]] : v;
56121
- } else {
56122
- out[k] = v;
56123
- }
56124
- }
56125
- return out;
56126
- }
56127
- app.post(
56128
- "/api/debug-node",
56129
- async (req, reply) => {
56130
- const { id, nodeId, inputs } = req.body ?? {};
56131
- if (!id || !nodeId) return reply.status(400).send({ ok: false, error: "id and nodeId required" });
56132
- const appData = readApp(id);
56133
- if (!appData.runnable) return reply.status(409).send({ ok: false, error: "app not runnable \u2014 compile the lock first" });
56134
- const node = appData.nodes.find((n) => n.id === nodeId);
56135
- if (!node) return reply.status(404).send({ ok: false, error: `node not found: ${nodeId}` });
56136
- const code = node.config?.code;
56137
- if (typeof code !== "string") return reply.status(400).send({ ok: false, error: `node ${nodeId} has no exec code` });
56138
- const version = typeof node.config?.version === "string" ? node.config.version : void 0;
56139
- const configArgs = node.config?.args ?? {};
56140
- const effectiveInputs = {};
56141
- for (const inp of appData.inputs) {
56142
- const d = inp.default;
56143
- if (typeof d === "string" || typeof d === "number" || typeof d === "boolean") effectiveInputs[inp.name] = d;
56144
- }
56145
- Object.assign(effectiveInputs, inputs ?? {});
56146
- const args = resolveArgs(configArgs, effectiveInputs);
56147
- broadcast({ type: "debug-started", id, nodeId });
56148
- const result = await aware.execTekla(code, { version, args, debug: true });
56149
- broadcast({ type: "debug-ended", id, nodeId });
56150
- const r = result.result ?? {};
56151
- const html = typeof r.html === "string" ? r.html : null;
56152
- return { ok: true, result, report: html ? { nodeId, html } : null };
56153
- }
56154
- );
56155
- app.get("/api/routines", async () => ({
56156
- // Annotate each with live state so the UI reconciles from the server (a dropped
56157
- // SSE event can't wedge an indicator): schedule routines carry queued/active
56158
- // `running`; trigger routines carry their live `session` snapshot
56159
- // (listening/fired×N/error/stopped).
56160
- ok: true,
56161
- routines: listRoutines().map(
56162
- (r) => r.kind === "trigger" ? { ...r, session: getSnapshot(r.id) } : { ...r, running: isRoutineRunning(r.id) }
56163
- ),
56164
- max: MAX_ROUTINES
56165
- }));
56166
- app.post(
56167
- "/api/routines",
56168
- async (req, reply) => {
56169
- const routine = createRoutine(req.body ?? {});
56170
- return reply.status(201).send({ ok: true, routine });
56216
+ app.addHook("onRequest", async (req, reply) => {
56217
+ if (!isGatedAwareRoute(req.url, req.method)) return;
56218
+ const st = getBootstrapState();
56219
+ if (st.status === "ready") return;
56220
+ if (st.status === "failed") return reply.status(503).send({ ok: false, error: "AWARE unavailable", bootstrap: st.status, reason: st.reason, remediation: st.remediation });
56221
+ return reply.status(409).send({ ok: false, error: "AWARE is still setting up", bootstrap: st.status });
56222
+ });
56223
+ app.get("/api/health", async () => {
56224
+ const bs = getBootstrapState();
56225
+ return {
56226
+ ok: true,
56227
+ appVersion: appVersion(),
56228
+ // the installed build (sq.version), so it's scriptable
56229
+ awareVersion: awareInstalledVersion() ?? bs.awareVersion,
56230
+ // fresh (TTL-cached) install version; the boot snapshot is only a fallback
56231
+ awareReady: bs.awareReady,
56232
+ bootstrap: bs.status,
56233
+ bootstrapReason: bs.reason,
56234
+ bootstrapRemediation: bs.remediation,
56235
+ // UI maps reason→fixed copy; this is secondary dev context
56236
+ sseClients: clientCount(),
56237
+ license: (await getLicenseStatus()).state
56238
+ // UI + launcher branch on this
56239
+ };
56240
+ });
56241
+ app.get("/api/license/status", async () => ({ ok: true, ...await getLicenseStatus() }));
56242
+ app.post("/api/license/start", async () => ({ ok: true, ...await startLogin() }));
56243
+ app.post("/api/license/logout", async () => {
56244
+ logout();
56245
+ return { ok: true };
56246
+ });
56247
+ app.get("/api/autostart", async () => {
56248
+ const supported = autostartSupported();
56249
+ return { ok: true, supported, enabled: supported ? autostartPresent() : false };
56250
+ });
56251
+ app.put("/api/autostart", async (req, reply) => {
56252
+ if (!autostartSupported()) return reply.send({ ok: true, supported: false, enabled: false });
56253
+ if (typeof req.body?.enabled !== "boolean") {
56254
+ return reply.status(400).send({ ok: false, error: "body { enabled: boolean } required" });
56171
56255
  }
56172
- );
56173
- app.patch(
56174
- "/api/routines/:id",
56175
- async (req, reply) => {
56176
- const routine = updateRoutine(req.params.id, req.body ?? {});
56177
- if (!routine) return reply.status(404).send({ ok: false, error: "routine not found" });
56178
- return { ok: true, routine };
56256
+ try {
56257
+ if (req.body.enabled) registerAutostart(resolveRealInstallExe(process.execPath));
56258
+ else unregisterAutostart();
56259
+ } catch (err) {
56260
+ return reply.status(500).send({ ok: false, error: err instanceof Error ? err.message : String(err) });
56179
56261
  }
56180
- );
56181
- app.delete("/api/routines/:id", async (req, reply) => {
56182
- if (!deleteRoutine(req.params.id)) return reply.status(404).send({ ok: false, error: "routine not found" });
56183
- return { ok: true };
56262
+ return { ok: true, supported: true, enabled: autostartPresent() };
56184
56263
  });
56185
- app.post("/api/routines/:id/run", async (req, reply) => {
56186
- if (!runNow(req.params.id)) return reply.status(404).send({ ok: false, error: "routine not found" });
56187
- return { ok: true };
56264
+ app.get("/api/update", async () => {
56265
+ return { ok: true, ...await checkForUpdate() };
56188
56266
  });
56189
- app.get("/api/templates", async () => ({ ok: true, templates: listTemplates() }));
56190
- app.post(
56191
- "/api/templates",
56192
- async (req, reply) => {
56193
- const { name, category, node, source } = req.body ?? {};
56194
- if (!name || !node) return reply.status(400).send({ ok: false, error: "name and node required" });
56195
- const tpl = addTemplate({ name, category, node, source });
56196
- broadcast({ type: "templates-changed" });
56197
- return { ok: true, template: tpl };
56267
+ app.post("/api/update/apply", async (_req, reply) => {
56268
+ const check = await checkForUpdate();
56269
+ if (!check.supported || !check.updateAvailable) {
56270
+ return reply.status(409).send({ ok: false, ...check, error: check.reason ?? "no update available" });
56198
56271
  }
56199
- );
56200
- app.delete("/api/templates/:id", async (req, reply) => {
56201
- if (!deleteTemplate(req.params.id)) return reply.status(404).send({ ok: false, error: "template not found" });
56202
- broadcast({ type: "templates-changed" });
56203
- return { ok: true };
56272
+ const result = await applyUpdate(check, { onBeforeApply: async () => {
56273
+ killSupervisor({ tree: false });
56274
+ } });
56275
+ if (!result.applied) return reply.status(500).send({ ok: false, error: result.message });
56276
+ reply.send({ ok: true, ...result });
56277
+ setTimeout(() => process.exit(0), 250);
56204
56278
  });
56205
- app.get("/api/requests", async () => ({ ok: true, requests: listRequests() }));
56206
- app.get("/api/requests/:id/snapshot/:n", async (req, reply) => {
56207
- const n = Number.parseInt(req.params.n, 10);
56208
- const p = Number.isInteger(n) ? snapshotPathFor(req.params.id, n) : null;
56209
- if (!p || !(0, import_node_fs17.existsSync)(p)) return reply.status(404).send({ ok: false, error: "snapshot not found" });
56210
- const ext = p.split(".").pop().toLowerCase();
56211
- reply.header("Content-Type", ext === "png" ? "image/png" : ext === "webp" ? "image/webp" : "image/jpeg");
56212
- reply.header("Cache-Control", "no-store");
56213
- return (0, import_node_fs17.readFileSync)(p);
56279
+ app.get("/api/aware/update", async () => {
56280
+ return { ok: true, ...await checkAwareUpdate() };
56281
+ });
56282
+ let awareInstallInFlight = false;
56283
+ app.post("/api/aware/update/apply", async (_req, reply) => {
56284
+ const block = awareUpgradeBlockReason({
56285
+ bootstrapInstalling: getBootstrapState().status === "installing",
56286
+ installInFlight: awareInstallInFlight,
56287
+ runActive: isRunActive(),
56288
+ hostWatcherActive: hostWatcherActive()
56289
+ });
56290
+ if (block) return reply.status(409).send({ ok: false, error: block });
56291
+ awareInstallInFlight = true;
56292
+ try {
56293
+ const res = await applyAwareUpdate(awareApplyDeps(installAwareGlobal));
56294
+ if (!res.ok) return reply.status(409).send({ ok: false, error: res.error ?? "aware upgrade failed" });
56295
+ return { ok: true, version: res.version };
56296
+ } finally {
56297
+ awareInstallInFlight = false;
56298
+ }
56214
56299
  });
56215
- app.post(
56216
- "/api/tweak",
56217
- { bodyLimit: 25 * 1024 * 1024 },
56218
- async (req, reply) => {
56219
- const { appId, nodeId, instruction, snapshots } = req.body ?? {};
56220
- if (!appId || !nodeId || !instruction) {
56221
- return reply.status(400).send({ ok: false, error: "appId, nodeId, instruction required" });
56222
- }
56223
- let decoded;
56224
- try {
56225
- decoded = decodeSnapshots(snapshots);
56226
- } catch (e) {
56227
- return reply.status(400).send({ ok: false, error: e instanceof Error ? e.message : "bad snapshot" });
56228
- }
56229
- const request = addRequest({ type: "tweak", appId, nodeId, instruction }, decoded);
56230
- broadcast({ type: "request-added", request });
56231
- return { ok: true, request };
56300
+ app.post("/api/bootstrap/retry", async () => {
56301
+ const st = getBootstrapState().status;
56302
+ if (st === "failed" || st === "idle") {
56303
+ void runBootstrap(bootstrapDeps);
56232
56304
  }
56233
- );
56234
- app.post("/api/use-template", async (req, reply) => {
56235
- const { appId, templateId } = req.body ?? {};
56236
- if (!appId || !templateId) return reply.status(400).send({ ok: false, error: "appId and templateId required" });
56237
- const template = getTemplate(templateId);
56238
- if (!template) return reply.status(404).send({ ok: false, error: "template not found" });
56239
- const request = addRequest({ type: "use-template", appId, templateId, template });
56240
- broadcast({ type: "request-added", request });
56241
- return { ok: true, request };
56305
+ return { ok: true, bootstrap: getBootstrapState().status };
56242
56306
  });
56243
- app.delete("/api/requests/:id", async (req, reply) => {
56244
- if (!deleteRequest(req.params.id)) return reply.status(404).send({ ok: false, error: "request not found" });
56245
- broadcast({ type: "requests-changed" });
56246
- return { ok: true };
56307
+ onSeatLost((reason) => broadcast({ type: "seat-taken", reason }));
56308
+ app.get("/api/apps", async () => {
56309
+ const apps = await aware.list();
56310
+ return { ok: true, apps: apps.map((a) => ({ ...a, provider: appProvider(a.id) })) };
56247
56311
  });
56248
- app.delete("/api/requests", async () => {
56249
- const cleared = clearRequests();
56250
- broadcast({ type: "requests-changed" });
56251
- return { ok: true, cleared };
56312
+ app.get("/api/agents", async () => {
56313
+ const agents = await aware.agentList();
56314
+ return { ok: true, agents };
56252
56315
  });
56253
- app.get("/api/events", async (_req, reply) => {
56254
- reply.hijack();
56255
- reply.raw.writeHead(200, {
56256
- "Content-Type": "text/event-stream",
56257
- "Cache-Control": "no-cache",
56258
- Connection: "keep-alive"
56259
- });
56260
- reply.raw.write(`data: ${JSON.stringify({ type: "connected" })}
56261
-
56262
- `);
56263
- addClient(reply);
56316
+ app.get("/api/agent/:id", async (req) => {
56317
+ const agent = await aware.agentDescribe(req.params.id);
56318
+ return { ok: true, agent };
56264
56319
  });
56265
- const watcher = startWatcher();
56266
- runBootstrap(bootstrapDeps).then((st) => {
56267
- if (st.status === "ready") startScheduler();
56268
- else app.log.warn({ reason: st.reason }, "AWARE bootstrap not ready; scheduler deferred");
56269
- }).catch((err) => app.log.error({ err: String(err) }, "bootstrap continuation failed"));
56270
- const close = async () => {
56271
- cancelAllConnects();
56272
- clearAllStages();
56273
- await stopScheduler();
56274
- await watcher?.close();
56275
- await app.close();
56276
- process.exit(0);
56277
- };
56278
- process.on("SIGINT", close);
56279
- process.on("SIGTERM", close);
56280
- await app.listen({ port: PORT, host: HOST });
56281
- app.log.info(`floless.app on http://${HOST}:${PORT} (aware npm ${getBootstrapState().awareVersion ?? "unresolved"})`);
56282
- try {
56283
- const synced = syncSkills().filter((r) => r.action === "installed" || r.action === "updated");
56284
- if (synced.length) {
56285
- app.log.info(
56286
- { skills: synced.map((s) => `${s.runtime}:${s.skill}@${s.to}`) },
56287
- `synced ${synced.length} floless.app skill(s) into AI terminal(s)`
56288
- );
56320
+ app.get("/api/integrations", async () => {
56321
+ const integrations = listIntegrations();
56322
+ try {
56323
+ const live = await aware.connectList();
56324
+ const byId = new Map(live.map((c) => [c.integration, c]));
56325
+ for (const it of integrations) {
56326
+ const c = byId.get(it.id);
56327
+ if (!c) continue;
56328
+ it.status = c.status === "valid" ? "connected" : c.status === "expired" ? "expired" : "disconnected";
56329
+ it.expiresAt = c.expires_in_secs != null ? new Date(Date.now() + c.expires_in_secs * 1e3).toISOString() : it.expiresAt;
56330
+ }
56331
+ } catch (err) {
56332
+ app.log.warn({ err: String(err) }, "aware connect --list unavailable; using file-based status");
56289
56333
  }
56290
- } catch (err) {
56291
- app.log.warn({ err: String(err) }, "skill sync failed (non-fatal)");
56292
- }
56293
- }
56294
-
56295
- // launch.mjs
56296
- var import_node_child_process6 = require("node:child_process");
56297
- var import_node_path17 = require("node:path");
56298
- var import_node_url3 = require("node:url");
56299
- var import_node_fs18 = require("node:fs");
56300
- var import_node_http = __toESM(require("node:http"), 1);
56301
- var import_node_readline = require("node:readline");
56302
- var __dirname4 = (0, import_node_path17.dirname)((0, import_node_url3.fileURLToPath)(__import_meta_url));
56303
- var PORT2 = Number(process.env.PORT ?? 4317);
56304
- var HEALTH_URL = `http://127.0.0.1:${PORT2}/api/health`;
56305
- var BROWSER_URL = `http://floless.localhost:${PORT2}`;
56306
- var isWin2 = process.platform === "win32";
56307
- var log = (m) => {
56308
- try {
56309
- process.stdout.write(`floless: ${m}
56310
- `);
56311
- } catch {
56312
- }
56313
- };
56314
- function ping() {
56315
- return new Promise((resolve4) => {
56316
- const req = import_node_http.default.get(HEALTH_URL, { timeout: 1500 }, (res) => {
56317
- res.resume();
56318
- resolve4(res.statusCode === 200);
56319
- });
56320
- req.on("error", () => resolve4(false));
56321
- req.on("timeout", () => {
56322
- req.destroy();
56323
- resolve4(false);
56324
- });
56334
+ return { ok: true, integrations };
56325
56335
  });
56326
- }
56327
- function probeVersion() {
56328
- return new Promise((resolve4) => {
56329
- const req = import_node_http.default.get(HEALTH_URL, { timeout: 1500 }, (res) => {
56330
- let body = "";
56331
- res.on("data", (c) => {
56332
- body += c;
56333
- });
56334
- res.on("end", () => {
56335
- try {
56336
- resolve4(JSON.parse(body).appVersion || null);
56337
- } catch {
56338
- resolve4(null);
56336
+ const DEFAULT_CONNECT_FLOW = {};
56337
+ app.post("/api/connect/:id", async (req) => {
56338
+ const id = req.params.id;
56339
+ const requested = req.body?.flow;
56340
+ const flow = requested === "device-code" || requested === "oauth" ? requested : DEFAULT_CONNECT_FLOW[id] ?? "oauth";
56341
+ if (flow === "device-code") {
56342
+ const { prompt } = await aware.connectDeviceCode(id, (event) => {
56343
+ if (event.phase === "result") {
56344
+ broadcast({ type: "connect-result", id, status: event.status ?? "unknown", error: event.error ?? null });
56339
56345
  }
56340
56346
  });
56347
+ return { ok: true, id, flow, prompt };
56348
+ }
56349
+ aware.connectOAuth(id, (result) => {
56350
+ broadcast({
56351
+ type: "connect-result",
56352
+ id,
56353
+ status: result.status === "connected" ? "connected" : "failed",
56354
+ error: result.status === "connected" ? null : "sign-in failed or was cancelled"
56355
+ });
56341
56356
  });
56342
- req.on("error", () => resolve4(null));
56343
- req.on("timeout", () => {
56344
- req.destroy();
56345
- resolve4(null);
56346
- });
56357
+ return { ok: true, id, flow, started: true };
56347
56358
  });
56348
- }
56349
- function cmpVersion(a, b) {
56350
- const pa = String(a ?? "").split(".").map((n) => parseInt(n, 10) || 0);
56351
- const pb = String(b ?? "").split(".").map((n) => parseInt(n, 10) || 0);
56352
- for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
56353
- const d = (pa[i] || 0) - (pb[i] || 0);
56354
- if (d !== 0) return d > 0 ? 1 : -1;
56355
- }
56356
- return 0;
56357
- }
56358
- function shouldTakeOver(runningVersion, selfVersion) {
56359
- if (!runningVersion || !selfVersion) return false;
56360
- return cmpVersion(selfVersion, runningVersion) > 0;
56361
- }
56362
- var _selfVersion = null;
56363
- async function waitHealthy(timeoutMs = 3e4) {
56364
- const deadline = Date.now() + timeoutMs;
56365
- while (Date.now() < deadline) {
56366
- if (await ping()) return true;
56367
- await new Promise((r) => setTimeout(r, 500));
56368
- }
56369
- return false;
56370
- }
56371
- function resolveServerStart() {
56372
- const packaged = /flolessapp\.exe$/i.test(process.execPath);
56373
- if (packaged) return { cmd: process.execPath, args: ["--serve"], shell: false };
56374
- const bundle = (0, import_node_path17.join)(__dirname4, "dist", "floless-server.cjs");
56375
- if ((0, import_node_fs18.existsSync)(bundle)) return { cmd: process.execPath, args: [bundle, "--serve"], shell: false };
56376
- return { cmd: "npm", args: ["run", "start"], shell: isWin2 };
56377
- }
56378
- function startServerDetached() {
56379
- const { cmd, args, shell } = resolveServerStart();
56380
- rotateLog();
56381
- const fd = openLogFd();
56382
- const child = (0, import_node_child_process6.spawn)(cmd, args, {
56383
- cwd: __dirname4,
56384
- detached: true,
56385
- stdio: fd == null ? "ignore" : ["ignore", fd, fd],
56386
- windowsHide: true,
56387
- shell
56359
+ app.get("/api/app/:id", async (req) => {
56360
+ return { ok: true, app: readApp(req.params.id) };
56361
+ });
56362
+ app.post("/api/compile", async (req, reply) => {
56363
+ const { id, sourcePath } = req.body ?? {};
56364
+ const path = sourcePath ?? (id ? readApp(id).source.path : void 0);
56365
+ if (!path) return reply.status(400).send({ ok: false, error: "id or sourcePath required" });
56366
+ const result = await aware.compile(path);
56367
+ broadcast({ type: "compiled", id: id ?? null, lockPath: result.lockPath });
56368
+ return { ok: true, result };
56369
+ });
56370
+ app.post("/api/bake", async (req, reply) => {
56371
+ const { id } = req.body ?? {};
56372
+ if (!id) return reply.status(400).send({ ok: false, error: "id required" });
56373
+ const appData = readApp(id);
56374
+ if (!appData.runnable) {
56375
+ return reply.status(409).send({ ok: false, error: "app not runnable \u2014 compile the lock first, then bake" });
56376
+ }
56377
+ const inputs = appData.inputs.map((i) => ({ name: i.name, type: i.type }));
56378
+ const baked = bakeFloSource(appData.source.text, inputs);
56379
+ const tmpRoot = (0, import_node_fs18.mkdtempSync)((0, import_node_path17.join)((0, import_node_os11.tmpdir)(), "floless-bake-"));
56380
+ const backupDir = (0, import_node_path17.join)(tmpRoot, `${id}-backup`);
56381
+ const bakeDir = (0, import_node_path17.join)(tmpRoot, id);
56382
+ (0, import_node_fs18.cpSync)(appDir(id), backupDir, { recursive: true });
56383
+ (0, import_node_fs18.cpSync)(appDir(id), bakeDir, { recursive: true });
56384
+ const floName = appData.source.path.split(/[\\/]/).pop();
56385
+ (0, import_node_fs18.writeFileSync)((0, import_node_path17.join)(bakeDir, floName), baked);
56386
+ let appInstalled = true;
56387
+ try {
56388
+ await aware.uninstall(id);
56389
+ appInstalled = false;
56390
+ try {
56391
+ await aware.install(bakeDir);
56392
+ appInstalled = true;
56393
+ } catch (installErr) {
56394
+ try {
56395
+ await aware.install(backupDir);
56396
+ appInstalled = true;
56397
+ } catch (restoreErr) {
56398
+ app.log.error({ id, tmpRoot, restoreErr: String(restoreErr) }, "bake: install AND restore failed \u2014 app uninstalled; backup retained");
56399
+ return reply.status(502).send({
56400
+ ok: false,
56401
+ error: `bake failed and the app could not be restored \u2014 its source is backed up at ${backupDir}. Reinstall it from there with: aware app install "${backupDir}"`
56402
+ });
56403
+ }
56404
+ throw installErr;
56405
+ }
56406
+ try {
56407
+ await aware.compile((0, import_node_path17.join)(appDir(id), floName));
56408
+ } catch (compileErr) {
56409
+ app.log.warn({ id, compileErr: String(compileErr) }, "bake: post-install recompile failed (app baked but may need a manual Compile)");
56410
+ }
56411
+ broadcast({ type: "baked", id });
56412
+ return { ok: true, id, agent: id, inputs };
56413
+ } finally {
56414
+ if (appInstalled) (0, import_node_fs18.rmSync)(tmpRoot, { recursive: true, force: true });
56415
+ }
56416
+ });
56417
+ const graftAgentsDir = () => (0, import_node_path17.join)((0, import_node_os11.homedir)(), ".aware", "agents");
56418
+ app.post("/api/graft/match", async (req, reply) => {
56419
+ const { glob } = req.body ?? {};
56420
+ if (!glob) return reply.status(400).send({ ok: false, error: "glob required" });
56421
+ let safe;
56422
+ try {
56423
+ safe = assertSourceRef("dlls", glob);
56424
+ } catch (e) {
56425
+ return reply.status(400).send({ ok: false, error: e.message });
56426
+ }
56427
+ return { ok: true, files: matchAssemblies(safe) };
56388
56428
  });
56389
- child.unref();
56390
- }
56391
- function openBrowser2(url) {
56392
- if (isWin2) (0, import_node_child_process6.spawn)("cmd", ["/c", "start", "", url], { windowsHide: true, detached: true }).unref();
56393
- else (0, import_node_child_process6.spawn)(process.platform === "darwin" ? "open" : "xdg-open", [url], { detached: true }).unref();
56394
- }
56395
- function stopServer() {
56396
- if (!isWin2) {
56429
+ app.post("/api/graft/introspect", async (req, reply) => {
56430
+ const { sourceKind, sourceRef, decompile, outputId, acceptLicense, referenceDirs } = req.body ?? {};
56431
+ if (!sourceKind || !sourceRef) {
56432
+ return reply.status(400).send({ ok: false, error: "sourceKind and sourceRef required" });
56433
+ }
56434
+ const tempHome = (0, import_node_fs18.mkdtempSync)((0, import_node_path17.join)((0, import_node_os11.tmpdir)(), "floless-graft-"));
56435
+ let result;
56397
56436
  try {
56398
- (0, import_node_child_process6.execSync)(`bash -lc "fuser -k ${PORT2}/tcp"`, { stdio: "ignore" });
56399
- return true;
56400
- } catch {
56401
- return false;
56437
+ result = await aware.build({
56438
+ sourceKind,
56439
+ sourceRef,
56440
+ decompile,
56441
+ outputId,
56442
+ acceptLicense,
56443
+ referenceDirs,
56444
+ awareHome: tempHome
56445
+ });
56446
+ } catch (err) {
56447
+ (0, import_node_fs18.rmSync)(tempHome, { recursive: true, force: true });
56448
+ const msg = err instanceof AwareError ? err.message : String(err?.message ?? err);
56449
+ return reply.status(422).send({ ok: false, error: msg });
56402
56450
  }
56403
- }
56404
- try {
56405
- const ps = `$p = Get-NetTCPConnection -LocalPort ${PORT2} -State Listen -ErrorAction SilentlyContinue | Select-Object -Expand OwningProcess -Unique; if ($p) { $p | ForEach-Object { taskkill /PID $_ /T /F } } else { exit 9 }`;
56406
- (0, import_node_child_process6.execSync)(`powershell -NoProfile -Command "${ps}"`, { stdio: "ignore" });
56407
- return true;
56408
- } catch {
56409
- return false;
56410
- }
56411
- }
56412
- async function ensureServerUp() {
56413
- if (await ping()) return;
56414
- log("starting server\u2026");
56415
- startServerDetached();
56416
- if (!await waitHealthy()) {
56417
- log(`server did not become healthy on ${HEALTH_URL} within 30s \u2014 check for errors with "npm run dev"`);
56418
- process.exit(1);
56419
- }
56420
- log("server up");
56421
- }
56422
- async function cmdOpen() {
56423
- if (await ping()) {
56424
- const running = await probeVersion();
56425
- if (shouldTakeOver(running, _selfVersion)) {
56426
- log(`floless.app v${running} is already running on ${PORT2}; this build is v${_selfVersion} \u2014 taking over`);
56427
- stopServer();
56428
- await new Promise((r) => setTimeout(r, 500));
56429
- await ensureServerUp();
56430
- } else {
56431
- log(`already running${running ? ` (v${running})` : ""} \u2014 opening browser`);
56451
+ const manifest = readStagedManifest(result.agentDir);
56452
+ if (!manifest) {
56453
+ (0, import_node_fs18.rmSync)(tempHome, { recursive: true, force: true });
56454
+ return reply.status(502).send({ ok: false, error: `build produced output at ${result.agentDir} but no manifest.yaml` });
56432
56455
  }
56433
- } else {
56434
- await ensureServerUp();
56435
- }
56436
- log(`opening ${BROWSER_URL}`);
56437
- openBrowser2(BROWSER_URL);
56438
- }
56439
- async function cmdStart() {
56440
- if (await ping()) {
56441
- log("already running");
56442
- return;
56443
- }
56444
- await ensureServerUp();
56445
- }
56446
- var SUPERVISE_POLL_MS = 3e3;
56447
- var SUPERVISE_RESPAWN_ENV = "FLOLESS_SUPERVISE_RESPAWNED";
56448
- async function cmdSupervise() {
56449
- const isSeaChannel = /flolessapp\.exe$/i.test(process.execPath);
56450
- if (isSeaChannel && !process.env[SUPERVISE_RESPAWN_ENV]) {
56451
- rotateLog();
56452
- const fd = openLogFd();
56453
- const child = (0, import_node_child_process6.spawn)(process.execPath, ["--supervise"], {
56454
- detached: true,
56455
- stdio: fd == null ? "ignore" : ["ignore", fd, fd],
56456
- windowsHide: true,
56457
- env: { ...process.env, [SUPERVISE_RESPAWN_ENV]: "1" }
56458
- });
56459
- child.unref();
56460
- log("supervisor re-spawned detached");
56461
- return;
56462
- }
56463
- log("supervisor up \u2014 keeping the server alive");
56464
- for (; ; ) {
56465
- if (!await ping()) {
56466
- log("server not responding \u2014 (re)starting");
56467
- startServerDetached();
56468
- log(await waitHealthy() ? "server up" : "server not healthy yet \u2014 will retry");
56456
+ const token = (0, import_node_crypto5.randomUUID)();
56457
+ registerStage(token, tempHome, result.agentId);
56458
+ const preview = buildPreview(manifest, sourceKind, sourceRef, token);
56459
+ if ((0, import_node_fs18.existsSync)((0, import_node_path17.join)(graftAgentsDir(), result.agentId))) {
56460
+ preview.warnings.unshift(`An agent named "${result.agentId}" is already installed \u2014 creating it will overwrite it.`);
56469
56461
  }
56470
- await new Promise((r) => setTimeout(r, SUPERVISE_POLL_MS));
56471
- }
56472
- }
56473
- async function cmdStop() {
56474
- if (!stopServer()) {
56475
- log("not running \u2014 nothing to stop");
56476
- return;
56477
- }
56478
- log(`stopping server on port ${PORT2}\u2026`);
56479
- for (let i = 0; i < 10; i++) {
56480
- if (!await ping()) {
56481
- log("stopped");
56482
- return;
56462
+ return { ok: true, preview };
56463
+ });
56464
+ app.post("/api/graft/commit", async (req, reply) => {
56465
+ const { stagedRef, force = false } = req.body ?? {};
56466
+ if (!stagedRef) return reply.status(400).send({ ok: false, error: "stagedRef required" });
56467
+ const stage = consumeStage(stagedRef);
56468
+ if (!stage) {
56469
+ return reply.status(410).send({ ok: false, error: "staged build expired or not found \u2014 introspect again" });
56483
56470
  }
56484
- await new Promise((r) => setTimeout(r, 300));
56485
- }
56486
- log("warning: a process may still be holding the port");
56487
- }
56488
- function parseTeardownFlags(argv = []) {
56489
- return {
56490
- purge: argv.includes("--purge"),
56491
- keepAware: argv.includes("--keep-aware")
56492
- };
56493
- }
56494
- function parseProcessList(json) {
56495
- let parsed;
56496
- try {
56497
- parsed = JSON.parse(json || "[]");
56498
- } catch {
56499
- return [];
56500
- }
56501
- const arr = Array.isArray(parsed) ? parsed : [parsed];
56502
- return arr.filter((p) => p && p.ProcessId != null).map((p) => ({ pid: Number(p.ProcessId), cmd: String(p.CommandLine ?? "") }));
56503
- }
56504
- function enumerateProcesses() {
56505
- if (!isWin2) return [];
56506
- try {
56507
- const ps = "Get-CimInstance Win32_Process | Select-Object ProcessId,CommandLine | ConvertTo-Json -Compress";
56508
- const out = (0, import_node_child_process6.execSync)(`powershell -NoProfile -Command "${ps}"`, {
56509
- encoding: "utf8",
56510
- windowsHide: true,
56511
- maxBuffer: 16 * 1024 * 1024
56512
- });
56513
- return parseProcessList(out);
56514
- } catch {
56515
- return [];
56516
- }
56517
- }
56518
- function killSupervisor() {
56519
- if (!isWin2) return;
56520
- const isNpmChannel = /^node(\.exe)?$/i.test((0, import_node_path17.basename)(process.execPath));
56521
- const scriptMatch = isNpmChannel ? (0, import_node_path17.basename)((0, import_node_url3.fileURLToPath)(__import_meta_url)) : void 0;
56522
- const realExe = resolveRealInstallExe(process.execPath);
56523
- const exeMatch = realExe === process.execPath ? process.execPath : [process.execPath, realExe];
56524
- const pids = supervisorPidsToKill(enumerateProcesses(), process.pid, exeMatch, scriptMatch);
56525
- for (const pid of pids) {
56526
- log(`stopping supervisor (pid ${pid})\u2026`);
56527
56471
  try {
56528
- (0, import_node_child_process6.execFileSync)("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
56529
- } catch {
56530
- }
56531
- }
56532
- }
56533
- async function cmdTeardown() {
56534
- killSupervisor();
56535
- stopServer();
56536
- const deadline = Date.now() + 3e3;
56537
- while (Date.now() < deadline) {
56538
- await new Promise((r) => setTimeout(r, 300));
56539
- if (await ping()) {
56540
- log("server respawned after kill \u2014 re-killing supervisor and stopping again");
56541
- killSupervisor();
56542
- stopServer();
56472
+ commitStaged(stage.tempDir, stage.agentId, graftAgentsDir(), force);
56473
+ } catch (err) {
56474
+ if (err instanceof GraftCommitError && err.code === "collision") {
56475
+ registerStage(stagedRef, stage.tempDir, stage.agentId);
56476
+ return reply.status(409).send({ ok: false, error: err.message, agentId: stage.agentId, collision: true });
56477
+ }
56478
+ (0, import_node_fs18.rmSync)(stage.tempDir, { recursive: true, force: true });
56479
+ throw err;
56543
56480
  }
56544
- }
56545
- if (await ping()) log("warning: a process may still be holding the port");
56546
- else log("server and supervisor stopped");
56547
- }
56548
- async function cmdRestart() {
56549
- await cmdStop();
56550
- await new Promise((r) => setTimeout(r, 500));
56551
- await cmdOpen();
56552
- }
56553
- function apiJson(path, method = "GET") {
56554
- return new Promise((resolve4, reject) => {
56555
- const req = import_node_http.default.request(`http://127.0.0.1:${PORT2}${path}`, { method, timeout: 5e3 }, (res) => {
56556
- let body = "";
56557
- res.on("data", (c) => body += c);
56558
- res.on("end", () => {
56559
- try {
56560
- resolve4(JSON.parse(body || "{}"));
56561
- } catch {
56562
- resolve4({});
56481
+ broadcast({ type: "grafted", id: stage.agentId });
56482
+ return { ok: true, agentId: stage.agentId };
56483
+ });
56484
+ app.post(
56485
+ "/api/run",
56486
+ async (req, reply) => {
56487
+ const { id, dryRun, simulate, inputs } = req.body ?? {};
56488
+ if (!id) return reply.status(400).send({ ok: false, error: "id required" });
56489
+ const blocked = hostRunBlocked(appProvider(id));
56490
+ if (blocked) return reply.send({ ok: false, error: blocked, blocked: true, simulate: !!simulate });
56491
+ let result;
56492
+ try {
56493
+ result = await aware.run(id, { dryRun, simulate, inputs });
56494
+ } catch (err) {
56495
+ if (err instanceof AwareError) {
56496
+ const detail = err.detail && typeof err.detail === "object" ? err.detail : null;
56497
+ if (detail && detail.cancelled === true) {
56498
+ app.log.info({ id }, "run cancelled by user");
56499
+ return reply.send({ ok: false, error: "Run cancelled", cancelled: true, simulate: !!simulate });
56500
+ }
56501
+ const stderr = detail && typeof detail.stderr === "string" ? detail.stderr.trim() : "";
56502
+ app.log.warn({ detail: err.detail }, err.message);
56503
+ return reply.send({ ok: false, error: err.message, reason: stderr || null, simulate: !!simulate });
56563
56504
  }
56564
- });
56565
- });
56566
- req.on("error", reject);
56567
- req.on("timeout", () => {
56568
- req.destroy();
56569
- reject(new Error("timeout"));
56570
- });
56571
- req.end();
56505
+ throw err;
56506
+ }
56507
+ const events = result.traceText ? parseTrace(result.traceText) : [];
56508
+ broadcast({ type: "run-started", id, dryRun: !!dryRun, simulate: !!simulate, runId: result.runId });
56509
+ for (const ev of events) broadcast({ type: "trace", id, event: ev });
56510
+ broadcast({ type: "run-ended", id, runId: result.runId });
56511
+ const report = extractReportHtml(events);
56512
+ return { ok: true, runId: result.runId, tracePath: result.tracePath, events, report };
56513
+ }
56514
+ );
56515
+ app.post("/api/run/stop", async () => {
56516
+ const running = cancelActiveRun();
56517
+ return { ok: true, running };
56572
56518
  });
56573
- }
56574
- async function cmdLogin() {
56575
- if (!await ping()) {
56576
- log("starting server\u2026");
56577
- startServerDetached();
56578
- if (!await waitHealthy()) {
56579
- log('server did not start \u2014 try "npm run dev" to see errors');
56580
- process.exit(1);
56519
+ function resolveArgs(configArgs, inputs) {
56520
+ const out = {};
56521
+ for (const [k, v] of Object.entries(configArgs)) {
56522
+ if (typeof v === "string") {
56523
+ const m = v.match(/^\{\{\s*inputs\.([A-Za-z0-9._-]+)\s*\}\}$/);
56524
+ out[k] = m && m[1] != null && m[1] in inputs ? inputs[m[1]] : v;
56525
+ } else {
56526
+ out[k] = v;
56527
+ }
56581
56528
  }
56529
+ return out;
56582
56530
  }
56583
- const started = await apiJson("/api/license/start", "POST").catch(() => ({}));
56584
- log(`opening sign-in in your browser${started.signInUrl ? `: ${started.signInUrl}` : ""}`);
56585
- log("waiting for sign-in to complete\u2026");
56586
- const deadline = Date.now() + 185e3;
56587
- while (Date.now() < deadline) {
56588
- const s = await apiJson("/api/license/status").catch(() => ({}));
56589
- if (s.state === "valid" || s.state === "offline-grace") {
56590
- log("signed in \u2014 subscription active");
56591
- return;
56531
+ app.post(
56532
+ "/api/debug-node",
56533
+ async (req, reply) => {
56534
+ const { id, nodeId, inputs } = req.body ?? {};
56535
+ if (!id || !nodeId) return reply.status(400).send({ ok: false, error: "id and nodeId required" });
56536
+ const appData = readApp(id);
56537
+ if (!appData.runnable) return reply.status(409).send({ ok: false, error: "app not runnable \u2014 compile the lock first" });
56538
+ const node = appData.nodes.find((n) => n.id === nodeId);
56539
+ if (!node) return reply.status(404).send({ ok: false, error: `node not found: ${nodeId}` });
56540
+ const code = node.config?.code;
56541
+ if (typeof code !== "string") return reply.status(400).send({ ok: false, error: `node ${nodeId} has no exec code` });
56542
+ const version = typeof node.config?.version === "string" ? node.config.version : void 0;
56543
+ const configArgs = node.config?.args ?? {};
56544
+ const effectiveInputs = {};
56545
+ for (const inp of appData.inputs) {
56546
+ const d = inp.default;
56547
+ if (typeof d === "string" || typeof d === "number" || typeof d === "boolean") effectiveInputs[inp.name] = d;
56548
+ }
56549
+ Object.assign(effectiveInputs, inputs ?? {});
56550
+ const args = resolveArgs(configArgs, effectiveInputs);
56551
+ broadcast({ type: "debug-started", id, nodeId });
56552
+ const result = await aware.execTekla(code, { version, args, debug: true });
56553
+ broadcast({ type: "debug-ended", id, nodeId });
56554
+ const r = result.result ?? {};
56555
+ const html = typeof r.html === "string" ? r.html : null;
56556
+ return { ok: true, result, report: html ? { nodeId, html } : null };
56592
56557
  }
56593
- if (s.state === "expired") {
56594
- log(`signed in, but the subscription is expired \u2014 subscribe at ${s.signInUrl ?? "floless.io"}`);
56595
- return;
56558
+ );
56559
+ app.get("/api/routines", async () => ({
56560
+ // Annotate each with live state so the UI reconciles from the server (a dropped
56561
+ // SSE event can't wedge an indicator): schedule routines carry queued/active
56562
+ // `running`; trigger routines carry their live `session` snapshot
56563
+ // (listening/fired×N/error/stopped).
56564
+ ok: true,
56565
+ routines: listRoutines().map(
56566
+ (r) => r.kind === "trigger" ? { ...r, session: getSnapshot(r.id) } : { ...r, running: isRoutineRunning(r.id) }
56567
+ ),
56568
+ max: MAX_ROUTINES
56569
+ }));
56570
+ app.post(
56571
+ "/api/routines",
56572
+ async (req, reply) => {
56573
+ const routine = createRoutine(req.body ?? {});
56574
+ return reply.status(201).send({ ok: true, routine });
56596
56575
  }
56597
- await new Promise((r) => setTimeout(r, 2e3));
56598
- }
56599
- log('sign-in timed out \u2014 run "floless login" again to retry');
56600
- }
56601
- function parseAction(arg) {
56602
- let cmd = (arg || "open").toLowerCase();
56603
- if (cmd.startsWith("floless:")) {
56604
- try {
56605
- cmd = (new URL(arg).hostname || "open").toLowerCase();
56606
- } catch {
56607
- cmd = "open";
56576
+ );
56577
+ app.patch(
56578
+ "/api/routines/:id",
56579
+ async (req, reply) => {
56580
+ const routine = updateRoutine(req.params.id, req.body ?? {});
56581
+ if (!routine) return reply.status(404).send({ ok: false, error: "routine not found" });
56582
+ return { ok: true, routine };
56608
56583
  }
56609
- }
56610
- return cmd;
56611
- }
56612
- async function cmdUpdate() {
56613
- log('this is the npm build \u2014 update it with "npm i -g @floless/app"');
56614
- log('(the installer build self-updates via "floless-app update")');
56615
- }
56616
- function removeRegistryFootprint() {
56617
- if (!isWin2) return;
56618
- try {
56619
- (0, import_node_child_process6.execFileSync)("reg", ["delete", RUN_KEY, "/v", RUN_VALUE, "/f"], { stdio: "ignore", windowsHide: true });
56620
- } catch {
56621
- }
56622
- try {
56623
- (0, import_node_child_process6.execFileSync)("reg", ["delete", PROTOCOL_KEY, "/f"], { stdio: "ignore", windowsHide: true });
56624
- } catch {
56625
- }
56626
- }
56627
- function promptYesNo(question) {
56628
- return new Promise((resolve4) => {
56629
- const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stdout });
56630
- rl.question(`${question} `, (answer) => {
56631
- rl.close();
56632
- resolve4(/^y(es)?$/i.test((answer ?? "").trim()));
56633
- });
56584
+ );
56585
+ app.delete("/api/routines/:id", async (req, reply) => {
56586
+ if (!deleteRoutine(req.params.id)) return reply.status(404).send({ ok: false, error: "routine not found" });
56587
+ return { ok: true };
56634
56588
  });
56635
- }
56636
- async function cmdUninstall(flags = {}) {
56637
- const decision = teardownDecision({ ...flags, isTTY: process.stdin.isTTY });
56638
- log("stopping the server and supervisor\u2026");
56639
- await cmdTeardown();
56640
- log("removing the autostart entry and floless:// scheme\u2026");
56641
- removeRegistryFootprint();
56642
- let removeAware = decision.removeAware;
56643
- if (decision.prompt) {
56644
- removeAware = await promptYesNo("Also remove the AWARE runtime (@aware-aeco/cli)? [y/N]");
56645
- }
56646
- if (removeAware) {
56647
- log("removing the AWARE runtime \u2014 this will affect ANY other tool that uses @aware-aeco/cli.");
56648
- try {
56649
- (0, import_node_child_process6.execSync)("npm uninstall -g @aware-aeco/cli", { stdio: "inherit", shell: isWin2 });
56650
- } catch {
56651
- log('warning: "npm uninstall -g @aware-aeco/cli" failed \u2014 see the output above.');
56589
+ app.post("/api/routines/:id/run", async (req, reply) => {
56590
+ if (!runNow(req.params.id)) return reply.status(404).send({ ok: false, error: "routine not found" });
56591
+ return { ok: true };
56592
+ });
56593
+ app.get("/api/templates", async () => ({ ok: true, templates: listTemplates() }));
56594
+ app.post(
56595
+ "/api/templates",
56596
+ async (req, reply) => {
56597
+ const { name, category, node, source } = req.body ?? {};
56598
+ if (!name || !node) return reply.status(400).send({ ok: false, error: "name and node required" });
56599
+ const tpl = addTemplate({ name, category, node, source });
56600
+ broadcast({ type: "templates-changed" });
56601
+ return { ok: true, template: tpl };
56652
56602
  }
56653
- let lsJson = "";
56654
- try {
56655
- lsJson = (0, import_node_child_process6.execSync)("npm ls -g @aware-aeco/cli --depth=0 --json", {
56656
- encoding: "utf8",
56657
- stdio: ["ignore", "pipe", "ignore"],
56658
- shell: isWin2
56659
- });
56660
- } catch (err) {
56661
- lsJson = String(err?.stdout ?? "");
56603
+ );
56604
+ app.delete("/api/templates/:id", async (req, reply) => {
56605
+ if (!deleteTemplate(req.params.id)) return reply.status(404).send({ ok: false, error: "template not found" });
56606
+ broadcast({ type: "templates-changed" });
56607
+ return { ok: true };
56608
+ });
56609
+ app.get("/api/requests", async () => ({ ok: true, requests: listRequests() }));
56610
+ app.get("/api/requests/:id/snapshot/:n", async (req, reply) => {
56611
+ const n = Number.parseInt(req.params.n, 10);
56612
+ const p = Number.isInteger(n) ? snapshotPathFor(req.params.id, n) : null;
56613
+ if (!p || !(0, import_node_fs18.existsSync)(p)) return reply.status(404).send({ ok: false, error: "snapshot not found" });
56614
+ const ext = p.split(".").pop().toLowerCase();
56615
+ reply.header("Content-Type", ext === "png" ? "image/png" : ext === "webp" ? "image/webp" : "image/jpeg");
56616
+ reply.header("Cache-Control", "no-store");
56617
+ return (0, import_node_fs18.readFileSync)(p);
56618
+ });
56619
+ app.post(
56620
+ "/api/tweak",
56621
+ { bodyLimit: 25 * 1024 * 1024 },
56622
+ async (req, reply) => {
56623
+ const { appId, nodeId, instruction, snapshots } = req.body ?? {};
56624
+ if (!appId || !nodeId || !instruction) {
56625
+ return reply.status(400).send({ ok: false, error: "appId, nodeId, instruction required" });
56626
+ }
56627
+ let decoded;
56628
+ try {
56629
+ decoded = decodeSnapshots(snapshots);
56630
+ } catch (e) {
56631
+ return reply.status(400).send({ ok: false, error: e instanceof Error ? e.message : "bad snapshot" });
56632
+ }
56633
+ const request = addRequest({ type: "tweak", appId, nodeId, instruction }, decoded);
56634
+ broadcast({ type: "request-added", request });
56635
+ return { ok: true, request };
56662
56636
  }
56663
- const stillPresent = awareIsPresent(lsJson);
56664
- if (stillPresent) {
56665
- log("AWARE may still be installed \u2014 remove it manually with: npm uninstall -g @aware-aeco/cli");
56666
- log("(if that fails with a permission error, retry in an elevated shell).");
56667
- } else {
56668
- log("AWARE runtime removed.");
56637
+ );
56638
+ app.post("/api/use-template", async (req, reply) => {
56639
+ const { appId, templateId } = req.body ?? {};
56640
+ if (!appId || !templateId) return reply.status(400).send({ ok: false, error: "appId and templateId required" });
56641
+ const template = getTemplate(templateId);
56642
+ if (!template) return reply.status(404).send({ ok: false, error: "template not found" });
56643
+ const request = addRequest({ type: "use-template", appId, templateId, template });
56644
+ broadcast({ type: "request-added", request });
56645
+ return { ok: true, request };
56646
+ });
56647
+ app.delete("/api/requests/:id", async (req, reply) => {
56648
+ if (!deleteRequest(req.params.id)) return reply.status(404).send({ ok: false, error: "request not found" });
56649
+ broadcast({ type: "requests-changed" });
56650
+ return { ok: true };
56651
+ });
56652
+ app.delete("/api/requests", async () => {
56653
+ const cleared = clearRequests();
56654
+ broadcast({ type: "requests-changed" });
56655
+ return { ok: true, cleared };
56656
+ });
56657
+ app.get("/api/events", async (_req, reply) => {
56658
+ reply.hijack();
56659
+ reply.raw.writeHead(200, {
56660
+ "Content-Type": "text/event-stream",
56661
+ "Cache-Control": "no-cache",
56662
+ Connection: "keep-alive"
56663
+ });
56664
+ reply.raw.write(`data: ${JSON.stringify({ type: "connected" })}
56665
+
56666
+ `);
56667
+ addClient(reply);
56668
+ });
56669
+ const watcher = startWatcher();
56670
+ runBootstrap(bootstrapDeps).then((st) => {
56671
+ if (st.status === "ready") startScheduler();
56672
+ else app.log.warn({ reason: st.reason }, "AWARE bootstrap not ready; scheduler deferred");
56673
+ }).catch((err) => app.log.error({ err: String(err) }, "bootstrap continuation failed"));
56674
+ const close = async () => {
56675
+ cancelAllConnects();
56676
+ clearAllStages();
56677
+ await stopScheduler();
56678
+ await watcher?.close();
56679
+ await app.close();
56680
+ process.exit(0);
56681
+ };
56682
+ process.on("SIGINT", close);
56683
+ process.on("SIGTERM", close);
56684
+ await app.listen({ port: PORT2, host: HOST });
56685
+ app.log.info(`floless.app on http://${HOST}:${PORT2} (aware npm ${getBootstrapState().awareVersion ?? "unresolved"})`);
56686
+ try {
56687
+ const synced = syncSkills().filter((r) => r.action === "installed" || r.action === "updated");
56688
+ if (synced.length) {
56689
+ app.log.info(
56690
+ { skills: synced.map((s) => `${s.runtime}:${s.skill}@${s.to}`) },
56691
+ `synced ${synced.length} floless.app skill(s) into AI terminal(s)`
56692
+ );
56669
56693
  }
56670
- } else {
56671
- log("keeping the AWARE runtime (@aware-aeco/cli). Remove it later with: npm uninstall -g @aware-aeco/cli");
56672
- }
56673
- log("done. Finally, remove floless.app itself with: npm uninstall -g @floless/app");
56674
- }
56675
- var ACTIONS = { open: cmdOpen, start: cmdStart, supervise: cmdSupervise, stop: cmdStop, restart: cmdRestart, login: cmdLogin, update: cmdUpdate, uninstall: cmdUninstall };
56676
- async function runAction(arg, flagArgv = [], selfVersion = null) {
56677
- _selfVersion = selfVersion;
56678
- const cmd = parseAction(arg);
56679
- const action = ACTIONS[cmd];
56680
- if (!action) {
56681
- log(`unknown command "${cmd}" \u2014 use: open | start | supervise | stop | restart | login | update | uninstall`);
56682
- process.exit(2);
56694
+ } catch (err) {
56695
+ app.log.warn({ err: String(err) }, "skill sync failed (non-fatal)");
56683
56696
  }
56684
- await action(parseTeardownFlags(flagArgv));
56685
- }
56686
- var entry = (0, import_node_path17.basename)(process.argv[1] ?? "").toLowerCase();
56687
- if (entry === "launch.mjs") {
56688
- runAction(process.argv[2], process.argv.slice(3)).catch((e) => {
56689
- log(`error: ${e?.message ?? e}`);
56690
- process.exit(1);
56691
- });
56692
56697
  }
56693
56698
 
56694
56699
  // protocol.ts
@@ -56816,7 +56821,7 @@ async function main() {
56816
56821
  `);
56817
56822
  return;
56818
56823
  }
56819
- const result = await applyUpdate(check, { onBeforeApply: () => runAction("stop") });
56824
+ const result = await applyUpdate(check, { onBeforeApply: () => cmdTeardown() });
56820
56825
  process.stdout.write(`floless: ${result.message}
56821
56826
  `);
56822
56827
  process.exit(result.applied ? 0 : 1);