@floless/app 0.6.1 → 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.
- package/dist/floless-server.cjs +1365 -1360
- package/launch.mjs +18 -2
- package/package.json +1 -1
package/dist/floless-server.cjs
CHANGED
|
@@ -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
|
|
50830
|
-
var
|
|
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
|
|
50833
|
-
var
|
|
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.
|
|
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.
|
|
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
|
-
//
|
|
53737
|
-
var
|
|
53738
|
-
var
|
|
53739
|
-
var
|
|
53740
|
-
var
|
|
53741
|
-
|
|
53742
|
-
|
|
53743
|
-
|
|
53744
|
-
|
|
53745
|
-
|
|
53746
|
-
}
|
|
53747
|
-
|
|
53748
|
-
|
|
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
|
-
|
|
53769
|
-
|
|
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
|
-
|
|
53777
|
-
|
|
53778
|
-
|
|
53779
|
-
|
|
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
|
-
|
|
53782
|
-
|
|
53783
|
-
|
|
53784
|
-
|
|
53785
|
-
|
|
53786
|
-
return
|
|
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
|
|
53792
|
-
|
|
53799
|
+
function shouldTakeOver(runningVersion, selfVersion) {
|
|
53800
|
+
if (!runningVersion || !selfVersion) return false;
|
|
53801
|
+
return cmpVersion(selfVersion, runningVersion) > 0;
|
|
53793
53802
|
}
|
|
53794
|
-
|
|
53795
|
-
|
|
53796
|
-
|
|
53797
|
-
|
|
53798
|
-
|
|
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
|
-
|
|
53802
|
-
|
|
53803
|
-
|
|
53804
|
-
|
|
53805
|
-
|
|
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
|
-
|
|
53825
|
-
|
|
53826
|
-
|
|
53827
|
-
const
|
|
53828
|
-
|
|
53829
|
-
|
|
53830
|
-
|
|
53831
|
-
|
|
53832
|
-
|
|
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
|
|
53835
|
-
|
|
53836
|
-
|
|
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
|
|
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
|
|
53849
|
-
|
|
53850
|
-
|
|
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
|
|
53850
|
+
return false;
|
|
53857
53851
|
}
|
|
53858
53852
|
}
|
|
53859
|
-
function
|
|
53860
|
-
|
|
53861
|
-
|
|
53862
|
-
|
|
53863
|
-
|
|
53864
|
-
|
|
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
|
-
|
|
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
|
|
53886
|
-
|
|
53887
|
-
|
|
53888
|
-
|
|
53889
|
-
|
|
53890
|
-
|
|
53891
|
-
|
|
53892
|
-
|
|
53893
|
-
|
|
53894
|
-
|
|
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
|
-
|
|
53902
|
-
|
|
53903
|
-
if (!v) continue;
|
|
53904
|
-
out.push({ name, dir, version: v });
|
|
53874
|
+
} else {
|
|
53875
|
+
await ensureServerUp();
|
|
53905
53876
|
}
|
|
53906
|
-
|
|
53877
|
+
log(`opening ${BROWSER_URL}`);
|
|
53878
|
+
openBrowser2(BROWSER_URL);
|
|
53907
53879
|
}
|
|
53908
|
-
function
|
|
53909
|
-
|
|
53910
|
-
|
|
53911
|
-
|
|
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
|
-
|
|
53885
|
+
await ensureServerUp();
|
|
53935
53886
|
}
|
|
53936
|
-
|
|
53937
|
-
|
|
53938
|
-
|
|
53939
|
-
|
|
53940
|
-
|
|
53941
|
-
|
|
53942
|
-
|
|
53943
|
-
|
|
53944
|
-
|
|
53945
|
-
|
|
53946
|
-
|
|
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
|
-
|
|
54013
|
-
|
|
54014
|
-
|
|
54015
|
-
|
|
54016
|
-
|
|
54017
|
-
|
|
54018
|
-
|
|
54019
|
-
|
|
54020
|
-
|
|
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
|
-
|
|
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
|
-
|
|
54035
|
-
|
|
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
|
-
|
|
54085
|
-
|
|
54086
|
-
|
|
54087
|
-
|
|
54088
|
-
|
|
54089
|
-
|
|
54090
|
-
|
|
54091
|
-
|
|
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
|
-
|
|
54094
|
-
|
|
54095
|
-
|
|
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
|
-
|
|
54098
|
-
|
|
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
|
-
|
|
54107
|
-
|
|
54108
|
-
|
|
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) ===
|
|
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
|
-
|
|
55887
|
-
|
|
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
|
-
|
|
55890
|
-
|
|
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
|
-
|
|
55902
|
-
|
|
55903
|
-
|
|
55904
|
-
|
|
55905
|
-
|
|
55906
|
-
|
|
55907
|
-
|
|
55908
|
-
|
|
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
|
-
|
|
55917
|
-
const
|
|
55918
|
-
|
|
55919
|
-
|
|
55920
|
-
|
|
55921
|
-
|
|
55922
|
-
|
|
55923
|
-
|
|
55924
|
-
|
|
55925
|
-
|
|
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
|
-
|
|
55933
|
-
|
|
55934
|
-
|
|
55935
|
-
|
|
55936
|
-
|
|
55937
|
-
|
|
55938
|
-
|
|
55939
|
-
|
|
55940
|
-
|
|
55941
|
-
|
|
55942
|
-
|
|
55943
|
-
|
|
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
|
-
|
|
55946
|
-
|
|
55947
|
-
|
|
55948
|
-
|
|
55949
|
-
|
|
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
|
-
|
|
55959
|
-
|
|
55960
|
-
|
|
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
|
-
|
|
55967
|
-
|
|
55968
|
-
|
|
55969
|
-
|
|
55970
|
-
|
|
55971
|
-
|
|
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
|
-
|
|
55985
|
-
|
|
55986
|
-
|
|
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
|
-
|
|
56014
|
-
|
|
56015
|
-
|
|
56016
|
-
|
|
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
|
-
|
|
56020
|
-
|
|
56021
|
-
|
|
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
|
-
|
|
56031
|
-
|
|
56032
|
-
|
|
56033
|
-
|
|
56034
|
-
|
|
56035
|
-
|
|
56036
|
-
|
|
56037
|
-
|
|
56038
|
-
|
|
56039
|
-
|
|
56040
|
-
|
|
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
|
-
|
|
56043
|
-
|
|
56044
|
-
|
|
56045
|
-
|
|
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
|
-
|
|
56048
|
-
|
|
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
|
-
|
|
56053
|
-
|
|
56054
|
-
|
|
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
|
-
|
|
56192
|
+
app.log.error(err);
|
|
56193
|
+
return reply.status(500).send({ ok: false, error: err.message });
|
|
56059
56194
|
});
|
|
56060
|
-
|
|
56061
|
-
|
|
56062
|
-
|
|
56063
|
-
|
|
56064
|
-
|
|
56065
|
-
|
|
56066
|
-
|
|
56067
|
-
|
|
56068
|
-
|
|
56069
|
-
|
|
56070
|
-
|
|
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.
|
|
56081
|
-
"/api/
|
|
56082
|
-
|
|
56083
|
-
|
|
56084
|
-
|
|
56085
|
-
|
|
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
|
-
|
|
56116
|
-
|
|
56117
|
-
|
|
56118
|
-
|
|
56119
|
-
|
|
56120
|
-
|
|
56121
|
-
|
|
56122
|
-
|
|
56123
|
-
|
|
56124
|
-
|
|
56125
|
-
|
|
56126
|
-
|
|
56127
|
-
|
|
56128
|
-
|
|
56129
|
-
|
|
56130
|
-
|
|
56131
|
-
|
|
56132
|
-
|
|
56133
|
-
|
|
56134
|
-
|
|
56135
|
-
|
|
56136
|
-
|
|
56137
|
-
|
|
56138
|
-
|
|
56139
|
-
|
|
56140
|
-
|
|
56141
|
-
|
|
56142
|
-
|
|
56143
|
-
|
|
56144
|
-
|
|
56145
|
-
|
|
56146
|
-
|
|
56147
|
-
|
|
56148
|
-
|
|
56149
|
-
|
|
56150
|
-
|
|
56151
|
-
|
|
56152
|
-
|
|
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
|
-
|
|
56174
|
-
|
|
56175
|
-
|
|
56176
|
-
|
|
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.
|
|
56186
|
-
|
|
56187
|
-
return { ok: true };
|
|
56264
|
+
app.get("/api/update", async () => {
|
|
56265
|
+
return { ok: true, ...await checkForUpdate() };
|
|
56188
56266
|
});
|
|
56189
|
-
app.
|
|
56190
|
-
|
|
56191
|
-
|
|
56192
|
-
|
|
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
|
-
|
|
56201
|
-
|
|
56202
|
-
|
|
56203
|
-
|
|
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/
|
|
56206
|
-
|
|
56207
|
-
|
|
56208
|
-
|
|
56209
|
-
|
|
56210
|
-
const
|
|
56211
|
-
|
|
56212
|
-
|
|
56213
|
-
|
|
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
|
-
|
|
56217
|
-
|
|
56218
|
-
|
|
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
|
-
|
|
56244
|
-
|
|
56245
|
-
|
|
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.
|
|
56249
|
-
const
|
|
56250
|
-
|
|
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/
|
|
56254
|
-
|
|
56255
|
-
|
|
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
|
-
|
|
56266
|
-
|
|
56267
|
-
|
|
56268
|
-
|
|
56269
|
-
|
|
56270
|
-
|
|
56271
|
-
|
|
56272
|
-
|
|
56273
|
-
|
|
56274
|
-
|
|
56275
|
-
|
|
56276
|
-
|
|
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
|
-
|
|
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
|
-
|
|
56328
|
-
|
|
56329
|
-
const
|
|
56330
|
-
|
|
56331
|
-
|
|
56332
|
-
|
|
56333
|
-
|
|
56334
|
-
|
|
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
|
-
|
|
56343
|
-
req.on("timeout", () => {
|
|
56344
|
-
req.destroy();
|
|
56345
|
-
resolve4(null);
|
|
56346
|
-
});
|
|
56357
|
+
return { ok: true, id, flow, started: true };
|
|
56347
56358
|
});
|
|
56348
|
-
|
|
56349
|
-
|
|
56350
|
-
|
|
56351
|
-
|
|
56352
|
-
|
|
56353
|
-
const
|
|
56354
|
-
if (
|
|
56355
|
-
|
|
56356
|
-
|
|
56357
|
-
}
|
|
56358
|
-
|
|
56359
|
-
|
|
56360
|
-
|
|
56361
|
-
}
|
|
56362
|
-
|
|
56363
|
-
|
|
56364
|
-
|
|
56365
|
-
|
|
56366
|
-
|
|
56367
|
-
|
|
56368
|
-
|
|
56369
|
-
|
|
56370
|
-
|
|
56371
|
-
|
|
56372
|
-
|
|
56373
|
-
|
|
56374
|
-
|
|
56375
|
-
|
|
56376
|
-
|
|
56377
|
-
|
|
56378
|
-
|
|
56379
|
-
|
|
56380
|
-
|
|
56381
|
-
|
|
56382
|
-
|
|
56383
|
-
|
|
56384
|
-
|
|
56385
|
-
|
|
56386
|
-
|
|
56387
|
-
|
|
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
|
-
|
|
56390
|
-
}
|
|
56391
|
-
|
|
56392
|
-
|
|
56393
|
-
|
|
56394
|
-
|
|
56395
|
-
|
|
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
|
-
|
|
56399
|
-
|
|
56400
|
-
|
|
56401
|
-
|
|
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
|
-
|
|
56405
|
-
|
|
56406
|
-
|
|
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
|
-
|
|
56434
|
-
|
|
56435
|
-
|
|
56436
|
-
|
|
56437
|
-
|
|
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
|
-
|
|
56471
|
-
}
|
|
56472
|
-
|
|
56473
|
-
|
|
56474
|
-
|
|
56475
|
-
|
|
56476
|
-
|
|
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
|
-
(
|
|
56529
|
-
} catch {
|
|
56530
|
-
|
|
56531
|
-
|
|
56532
|
-
}
|
|
56533
|
-
|
|
56534
|
-
|
|
56535
|
-
|
|
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
|
-
|
|
56546
|
-
|
|
56547
|
-
|
|
56548
|
-
|
|
56549
|
-
|
|
56550
|
-
|
|
56551
|
-
|
|
56552
|
-
|
|
56553
|
-
|
|
56554
|
-
|
|
56555
|
-
|
|
56556
|
-
|
|
56557
|
-
|
|
56558
|
-
|
|
56559
|
-
|
|
56560
|
-
|
|
56561
|
-
|
|
56562
|
-
|
|
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
|
-
|
|
56567
|
-
|
|
56568
|
-
|
|
56569
|
-
|
|
56570
|
-
|
|
56571
|
-
|
|
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
|
-
|
|
56575
|
-
|
|
56576
|
-
|
|
56577
|
-
|
|
56578
|
-
|
|
56579
|
-
|
|
56580
|
-
|
|
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
|
-
|
|
56584
|
-
|
|
56585
|
-
|
|
56586
|
-
|
|
56587
|
-
|
|
56588
|
-
|
|
56589
|
-
|
|
56590
|
-
|
|
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
|
-
|
|
56594
|
-
|
|
56595
|
-
|
|
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
|
-
|
|
56598
|
-
|
|
56599
|
-
|
|
56600
|
-
|
|
56601
|
-
|
|
56602
|
-
|
|
56603
|
-
|
|
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
|
-
|
|
56611
|
-
}
|
|
56612
|
-
|
|
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
|
-
|
|
56637
|
-
|
|
56638
|
-
|
|
56639
|
-
|
|
56640
|
-
|
|
56641
|
-
|
|
56642
|
-
|
|
56643
|
-
|
|
56644
|
-
|
|
56645
|
-
|
|
56646
|
-
|
|
56647
|
-
|
|
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
|
-
|
|
56654
|
-
|
|
56655
|
-
|
|
56656
|
-
|
|
56657
|
-
|
|
56658
|
-
|
|
56659
|
-
|
|
56660
|
-
|
|
56661
|
-
|
|
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
|
-
|
|
56664
|
-
|
|
56665
|
-
|
|
56666
|
-
|
|
56667
|
-
|
|
56668
|
-
|
|
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
|
-
}
|
|
56671
|
-
log(
|
|
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: () =>
|
|
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);
|