@floless/app 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/floless-server.cjs +163 -36
- package/dist/skills/floless-app-onboarding/SKILL.md +106 -0
- package/dist/skills/floless-app-onboarding/references/aware-runtime.md +159 -0
- package/dist/skills/floless-app-onboarding/references/floless-app-tour.md +149 -0
- package/dist/web/app.css +123 -24
- package/dist/web/app.js +40 -20
- package/dist/web/aware.js +88 -10
- package/dist/web/index.html +9 -7
- package/package.json +2 -1
package/dist/floless-server.cjs
CHANGED
|
@@ -6005,7 +6005,7 @@ var require_thread_stream = __commonJS({
|
|
|
6005
6005
|
var require_transport = __commonJS({
|
|
6006
6006
|
"node_modules/pino/lib/transport.js"(exports2, module2) {
|
|
6007
6007
|
"use strict";
|
|
6008
|
-
var { createRequire:
|
|
6008
|
+
var { createRequire: createRequire4 } = require("module");
|
|
6009
6009
|
var { existsSync: existsSync17 } = require("node:fs");
|
|
6010
6010
|
var getCallers = require_caller();
|
|
6011
6011
|
var { join: join19, isAbsolute: isAbsolute2, sep: sep3 } = require("node:path");
|
|
@@ -6208,7 +6208,7 @@ var require_transport = __commonJS({
|
|
|
6208
6208
|
for (const filePath of callers) {
|
|
6209
6209
|
try {
|
|
6210
6210
|
const context = filePath === "node:repl" ? process.cwd() + sep3 : filePath;
|
|
6211
|
-
fixTarget2 =
|
|
6211
|
+
fixTarget2 = createRequire4(context).resolve(origin);
|
|
6212
6212
|
break;
|
|
6213
6213
|
} catch (err) {
|
|
6214
6214
|
continue;
|
|
@@ -16329,7 +16329,7 @@ var require_core = __commonJS({
|
|
|
16329
16329
|
};
|
|
16330
16330
|
var MAX_EXPRESSION = 200;
|
|
16331
16331
|
function requiredOptions(o) {
|
|
16332
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u,
|
|
16332
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v2, _w, _x, _y, _z, _0;
|
|
16333
16333
|
const s = o.strict;
|
|
16334
16334
|
const _optz = (_a = o.code) === null || _a === void 0 ? void 0 : _a.optimize;
|
|
16335
16335
|
const optimize = _optz === true || _optz === void 0 ? 1 : _optz || 0;
|
|
@@ -16347,7 +16347,7 @@ var require_core = __commonJS({
|
|
|
16347
16347
|
meta: (_s = o.meta) !== null && _s !== void 0 ? _s : true,
|
|
16348
16348
|
messages: (_t = o.messages) !== null && _t !== void 0 ? _t : true,
|
|
16349
16349
|
inlineRefs: (_u = o.inlineRefs) !== null && _u !== void 0 ? _u : true,
|
|
16350
|
-
schemaId: (
|
|
16350
|
+
schemaId: (_v2 = o.schemaId) !== null && _v2 !== void 0 ? _v2 : "$id",
|
|
16351
16351
|
addUsedSchema: (_w = o.addUsedSchema) !== null && _w !== void 0 ? _w : true,
|
|
16352
16352
|
validateSchema: (_x = o.validateSchema) !== null && _x !== void 0 ? _x : true,
|
|
16353
16353
|
validateFormats: (_y = o.validateFormats) !== null && _y !== void 0 ? _y : true,
|
|
@@ -51362,6 +51362,9 @@ function cancelActiveRun() {
|
|
|
51362
51362
|
killTree(run.child);
|
|
51363
51363
|
return true;
|
|
51364
51364
|
}
|
|
51365
|
+
function isRunActive() {
|
|
51366
|
+
return activeRun !== null;
|
|
51367
|
+
}
|
|
51365
51368
|
var LOGS_DIR = process.env.AWARE_HOME ? (0, import_node_path3.join)(process.env.AWARE_HOME, "logs") : (0, import_node_path3.join)((0, import_node_os3.homedir)(), ".aware", "logs");
|
|
51366
51369
|
var TRIGGER_POLL_MS = 250;
|
|
51367
51370
|
function parseRunHandle(stdout) {
|
|
@@ -51797,12 +51800,12 @@ function cmpVersions(a, b) {
|
|
|
51797
51800
|
return 0;
|
|
51798
51801
|
}
|
|
51799
51802
|
function decideBootstrap(input) {
|
|
51800
|
-
const { toolchain, aware: aware2,
|
|
51803
|
+
const { toolchain, aware: aware2, floor, skip } = input;
|
|
51801
51804
|
if (!toolchain.hasNode) return { kind: "fail", reason: "no-node" };
|
|
51802
51805
|
if (skip) return aware2.installed ? { kind: "ready" } : { kind: "fail", reason: "absent-and-skipped" };
|
|
51803
|
-
if (aware2.installed && isValidVersion(aware2.version) && cmpVersions(aware2.version,
|
|
51806
|
+
if (aware2.installed && isValidVersion(aware2.version) && cmpVersions(aware2.version, floor) >= 0) return { kind: "ready" };
|
|
51804
51807
|
if (!toolchain.hasNpm) return aware2.installed ? { kind: "ready" } : { kind: "fail", reason: "no-npm" };
|
|
51805
|
-
return { kind: "install", version:
|
|
51808
|
+
return { kind: "install", version: "latest" };
|
|
51806
51809
|
}
|
|
51807
51810
|
var REMEDIATION = {
|
|
51808
51811
|
"no-node": "AWARE needs Node.js 20+. Install it from https://nodejs.org and relaunch floless.app.",
|
|
@@ -51840,7 +51843,7 @@ function runBootstrap(deps) {
|
|
|
51840
51843
|
set(deps, { status: "probing", awareReady: false, awareVersion: null, reason: null, remediation: null });
|
|
51841
51844
|
const aware0 = deps.getAware();
|
|
51842
51845
|
version = aware0.version;
|
|
51843
|
-
const decision = decideBootstrap({ toolchain: deps.probeToolchain(), aware: aware0,
|
|
51846
|
+
const decision = decideBootstrap({ toolchain: deps.probeToolchain(), aware: aware0, floor: deps.floor, skip: deps.skip });
|
|
51844
51847
|
if (decision.kind === "fail") return fail(deps, decision.reason, version);
|
|
51845
51848
|
if (decision.kind === "install") {
|
|
51846
51849
|
set(deps, { status: "installing", awareReady: false, awareVersion: version, reason: null, remediation: null });
|
|
@@ -52251,10 +52254,18 @@ function appVersion() {
|
|
|
52251
52254
|
return resolveVersion({
|
|
52252
52255
|
isSea: isSea2(),
|
|
52253
52256
|
sqVersionXml: readSqVersionXml(),
|
|
52254
|
-
define: true ? "0.
|
|
52257
|
+
define: true ? "0.6.0" : void 0,
|
|
52255
52258
|
pkgVersion: readPkgVersion()
|
|
52256
52259
|
});
|
|
52257
52260
|
}
|
|
52261
|
+
function resolveChannel(s) {
|
|
52262
|
+
if (s.isSea) return "desktop";
|
|
52263
|
+
if (typeof s.define === "string") return "npm";
|
|
52264
|
+
return "dev";
|
|
52265
|
+
}
|
|
52266
|
+
function appChannel() {
|
|
52267
|
+
return resolveChannel({ isSea: isSea2(), define: true ? "0.6.0" : void 0 });
|
|
52268
|
+
}
|
|
52258
52269
|
|
|
52259
52270
|
// bake.ts
|
|
52260
52271
|
var import_yaml2 = __toESM(require_dist6(), 1);
|
|
@@ -53558,21 +53569,10 @@ var import_node_fs14 = require("node:fs");
|
|
|
53558
53569
|
var import_node_stream = require("node:stream");
|
|
53559
53570
|
var import_promises = require("node:stream/promises");
|
|
53560
53571
|
var import_node_path12 = require("node:path");
|
|
53561
|
-
var import_node_module4 = require("node:module");
|
|
53562
53572
|
var CHANNEL = "win";
|
|
53563
53573
|
var FEED_TIMEOUT_MS = 15e3;
|
|
53564
53574
|
var DOWNLOAD_TIMEOUT_MS = 3e5;
|
|
53565
53575
|
var NUPKG_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*\.nupkg$/;
|
|
53566
|
-
var _isSea;
|
|
53567
|
-
function isPackaged() {
|
|
53568
|
-
if (_isSea !== void 0) return _isSea;
|
|
53569
|
-
try {
|
|
53570
|
-
_isSea = (0, import_node_module4.createRequire)(__import_meta_url)("node:sea").isSea();
|
|
53571
|
-
} catch {
|
|
53572
|
-
_isSea = false;
|
|
53573
|
-
}
|
|
53574
|
-
return _isSea;
|
|
53575
|
-
}
|
|
53576
53576
|
function currentVersion() {
|
|
53577
53577
|
return appVersion();
|
|
53578
53578
|
}
|
|
@@ -53623,14 +53623,32 @@ function versionGt(a, b) {
|
|
|
53623
53623
|
}
|
|
53624
53624
|
return false;
|
|
53625
53625
|
}
|
|
53626
|
+
var NPM_LATEST_URL = "https://registry.npmjs.org/@floless/app/latest";
|
|
53627
|
+
var NPM_UPDATE_COMMAND = "npm i -g @floless/app@latest";
|
|
53628
|
+
async function checkNpmUpdate(cur) {
|
|
53629
|
+
const base = { supported: true, channel: "npm", currentVersion: cur };
|
|
53630
|
+
try {
|
|
53631
|
+
const res = await fetch(NPM_LATEST_URL, { redirect: "follow", signal: AbortSignal.timeout(FEED_TIMEOUT_MS) });
|
|
53632
|
+
if (!res.ok) return { ...base, updateAvailable: false, reason: `npm registry returned HTTP ${res.status}` };
|
|
53633
|
+
const body = await res.json();
|
|
53634
|
+
const latest = typeof body.version === "string" ? body.version : null;
|
|
53635
|
+
if (!latest) return { ...base, updateAvailable: false, reason: "npm registry returned no version" };
|
|
53636
|
+
if (!versionGt(latest, cur)) return { ...base, updateAvailable: false, targetVersion: latest, reason: `already up to date (v${cur})` };
|
|
53637
|
+
return { ...base, updateAvailable: true, targetVersion: latest, command: NPM_UPDATE_COMMAND };
|
|
53638
|
+
} catch (err) {
|
|
53639
|
+
return { ...base, updateAvailable: false, reason: `npm registry unreachable: ${err instanceof Error ? err.message : String(err)}` };
|
|
53640
|
+
}
|
|
53641
|
+
}
|
|
53626
53642
|
async function checkForUpdate() {
|
|
53627
53643
|
const cur = currentVersion();
|
|
53628
|
-
|
|
53644
|
+
const channel = appChannel();
|
|
53645
|
+
if (channel === "npm") return checkNpmUpdate(cur);
|
|
53646
|
+
if (channel !== "desktop") {
|
|
53629
53647
|
return {
|
|
53630
53648
|
supported: false,
|
|
53631
53649
|
currentVersion: cur,
|
|
53632
53650
|
updateAvailable: false,
|
|
53633
|
-
reason: "auto-update
|
|
53651
|
+
reason: "auto-update runs in the installed app; from source just `git pull`"
|
|
53634
53652
|
};
|
|
53635
53653
|
}
|
|
53636
53654
|
const base = feedUrl();
|
|
@@ -53715,12 +53733,95 @@ async function applyUpdate(check, opts) {
|
|
|
53715
53733
|
return { applied: true, message: `updating to v${check.targetVersion}\u2026 the app will relaunch` };
|
|
53716
53734
|
}
|
|
53717
53735
|
|
|
53736
|
+
// aware-update.ts
|
|
53737
|
+
var REGISTRY_LATEST = "https://registry.npmjs.org/@aware-aeco/cli/latest";
|
|
53738
|
+
var FEED_TIMEOUT_MS2 = 15e3;
|
|
53739
|
+
var VERSION_TTL_MS = 5e3;
|
|
53740
|
+
var _v = null;
|
|
53741
|
+
function awareInstalledVersion(probe = aware.npmVersion) {
|
|
53742
|
+
const now = Date.now();
|
|
53743
|
+
if (_v && now - _v.at < VERSION_TTL_MS) return _v.val;
|
|
53744
|
+
_v = { at: now, val: probe() };
|
|
53745
|
+
return _v.val;
|
|
53746
|
+
}
|
|
53747
|
+
function invalidateAwareVersion() {
|
|
53748
|
+
_v = null;
|
|
53749
|
+
}
|
|
53750
|
+
function versionGt2(a, b) {
|
|
53751
|
+
const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
|
|
53752
|
+
const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
|
|
53753
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
53754
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
53755
|
+
if (d !== 0) return d > 0;
|
|
53756
|
+
}
|
|
53757
|
+
return false;
|
|
53758
|
+
}
|
|
53759
|
+
function decideAwareUpdate(cur, latest) {
|
|
53760
|
+
const base = { supported: true, currentVersion: cur };
|
|
53761
|
+
if (!latest) return { ...base, updateAvailable: false, reason: "could not read the npm registry latest" };
|
|
53762
|
+
if (!cur) return { ...base, updateAvailable: false, targetVersion: latest, reason: "AWARE not installed yet" };
|
|
53763
|
+
if (versionGt2(latest, cur)) return { ...base, updateAvailable: true, targetVersion: latest };
|
|
53764
|
+
return { ...base, updateAvailable: false, targetVersion: latest, reason: `already up to date (v${cur})` };
|
|
53765
|
+
}
|
|
53766
|
+
async function fetchAwareLatest() {
|
|
53767
|
+
try {
|
|
53768
|
+
const res = await fetch(REGISTRY_LATEST, { redirect: "follow", signal: AbortSignal.timeout(FEED_TIMEOUT_MS2) });
|
|
53769
|
+
if (!res.ok) return null;
|
|
53770
|
+
const body = await res.json();
|
|
53771
|
+
return typeof body.version === "string" ? body.version : null;
|
|
53772
|
+
} catch {
|
|
53773
|
+
return null;
|
|
53774
|
+
}
|
|
53775
|
+
}
|
|
53776
|
+
async function checkAwareUpdate() {
|
|
53777
|
+
const cur = awareInstalledVersion();
|
|
53778
|
+
const latest = await fetchAwareLatest();
|
|
53779
|
+
return decideAwareUpdate(cur, latest);
|
|
53780
|
+
}
|
|
53781
|
+
async function applyAwareUpdate(deps) {
|
|
53782
|
+
try {
|
|
53783
|
+
await deps.install("latest");
|
|
53784
|
+
deps.refresh();
|
|
53785
|
+
invalidateAwareVersion();
|
|
53786
|
+
return { ok: true, version: deps.probe() };
|
|
53787
|
+
} catch (err) {
|
|
53788
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
53789
|
+
}
|
|
53790
|
+
}
|
|
53791
|
+
function awareApplyDeps(install) {
|
|
53792
|
+
return { install, refresh: refreshInvoker, probe: aware.npmVersion };
|
|
53793
|
+
}
|
|
53794
|
+
function awareUpgradeBlockReason(s) {
|
|
53795
|
+
if (s.bootstrapInstalling || s.installInFlight) return "an AWARE install is already in progress \u2014 try again in a moment";
|
|
53796
|
+
if (s.runActive) return "a workflow run is in progress \u2014 wait for it to finish before upgrading AWARE";
|
|
53797
|
+
if (s.hostWatcherActive) return "a live trigger/watch session is using the host \u2014 stop it before upgrading AWARE";
|
|
53798
|
+
return null;
|
|
53799
|
+
}
|
|
53800
|
+
|
|
53718
53801
|
// skill-sync.ts
|
|
53719
53802
|
var import_node_fs15 = require("node:fs");
|
|
53720
53803
|
var import_node_os9 = require("node:os");
|
|
53721
53804
|
var import_node_path13 = require("node:path");
|
|
53722
53805
|
var import_node_url = require("node:url");
|
|
53723
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));
|
|
53822
|
+
}
|
|
53823
|
+
|
|
53824
|
+
// skill-sync.ts
|
|
53724
53825
|
var __dirname2 = (0, import_node_path13.dirname)((0, import_node_url.fileURLToPath)(__import_meta_url));
|
|
53725
53826
|
function bundledSkillsRoot() {
|
|
53726
53827
|
const candidates = [
|
|
@@ -53784,7 +53885,7 @@ function decideAction(installed, bundled) {
|
|
|
53784
53885
|
function bundledSkills(root) {
|
|
53785
53886
|
let entries = [];
|
|
53786
53887
|
try {
|
|
53787
|
-
entries = (0, import_node_fs15.readdirSync)(root)
|
|
53888
|
+
entries = selectShippedSkillNames((0, import_node_fs15.readdirSync)(root));
|
|
53788
53889
|
} catch {
|
|
53789
53890
|
return [];
|
|
53790
53891
|
}
|
|
@@ -55630,7 +55731,7 @@ async function startServer() {
|
|
|
55630
55731
|
}
|
|
55631
55732
|
});
|
|
55632
55733
|
await app.register(import_static.default, { root: WEB_ROOT, prefix: "/" });
|
|
55633
|
-
const
|
|
55734
|
+
const MIN_AWARE = "0.51.0";
|
|
55634
55735
|
const isWin3 = process.platform === "win32";
|
|
55635
55736
|
const has = (cmd) => {
|
|
55636
55737
|
try {
|
|
@@ -55640,14 +55741,10 @@ async function startServer() {
|
|
|
55640
55741
|
return false;
|
|
55641
55742
|
}
|
|
55642
55743
|
};
|
|
55643
|
-
|
|
55644
|
-
|
|
55645
|
-
|
|
55646
|
-
const
|
|
55647
|
-
return { installed: v !== null, version: v };
|
|
55648
|
-
},
|
|
55649
|
-
installAware: (version, onLine) => new Promise((resolve4, reject) => {
|
|
55650
|
-
const child = (0, import_node_child_process5.spawn)("npm", ["i", "-g", `@aware-aeco/cli@${version}`], { shell: isWin3, windowsHide: true });
|
|
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 });
|
|
55651
55748
|
let stderrTail = "";
|
|
55652
55749
|
let combinedTail = "";
|
|
55653
55750
|
const tail = (buf, s) => (buf + s).slice(-4e3);
|
|
@@ -55664,11 +55761,19 @@ async function startServer() {
|
|
|
55664
55761
|
});
|
|
55665
55762
|
child.on("error", (e) => reject(e));
|
|
55666
55763
|
child.on("close", (code) => code === 0 ? resolve4() : reject(new Error(`npm exited ${code}: ${stderrTail || combinedTail}`)));
|
|
55667
|
-
})
|
|
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,
|
|
55668
55773
|
smoke: () => aware.smoke(),
|
|
55669
55774
|
refreshInvoker,
|
|
55670
55775
|
emit: broadcast,
|
|
55671
|
-
|
|
55776
|
+
floor: MIN_AWARE,
|
|
55672
55777
|
skip: process.env.AWARE_SKIP_BOOTSTRAP === "1"
|
|
55673
55778
|
};
|
|
55674
55779
|
app.setErrorHandler((err, _req, reply) => {
|
|
@@ -55700,7 +55805,7 @@ async function startServer() {
|
|
|
55700
55805
|
});
|
|
55701
55806
|
app.addHook("onRequest", async (req, reply) => {
|
|
55702
55807
|
if (!req.url.startsWith("/api/")) return;
|
|
55703
|
-
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")) 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;
|
|
55704
55809
|
const { state: state2, signInUrl: signInUrl2 } = await getLicenseStatus();
|
|
55705
55810
|
if (state2 !== "valid" && state2 !== "offline-grace") {
|
|
55706
55811
|
return reply.status(402).send({ ok: false, error: "subscription required", state: state2, signInUrl: signInUrl2 });
|
|
@@ -55719,7 +55824,8 @@ async function startServer() {
|
|
|
55719
55824
|
ok: true,
|
|
55720
55825
|
appVersion: appVersion(),
|
|
55721
55826
|
// the installed build (sq.version), so it's scriptable
|
|
55722
|
-
awareVersion: bs.awareVersion,
|
|
55827
|
+
awareVersion: awareInstalledVersion() ?? bs.awareVersion,
|
|
55828
|
+
// fresh (TTL-cached) install version; the boot snapshot is only a fallback
|
|
55723
55829
|
awareReady: bs.awareReady,
|
|
55724
55830
|
bootstrap: bs.status,
|
|
55725
55831
|
bootstrapReason: bs.reason,
|
|
@@ -55766,6 +55872,27 @@ async function startServer() {
|
|
|
55766
55872
|
reply.send({ ok: true, ...result });
|
|
55767
55873
|
setTimeout(() => process.exit(0), 250);
|
|
55768
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()
|
|
55885
|
+
});
|
|
55886
|
+
if (block) return reply.status(409).send({ ok: false, error: block });
|
|
55887
|
+
awareInstallInFlight = true;
|
|
55888
|
+
try {
|
|
55889
|
+
const res = await applyAwareUpdate(awareApplyDeps(installAwareGlobal));
|
|
55890
|
+
if (!res.ok) return reply.status(409).send({ ok: false, error: res.error ?? "aware upgrade failed" });
|
|
55891
|
+
return { ok: true, version: res.version };
|
|
55892
|
+
} finally {
|
|
55893
|
+
awareInstallInFlight = false;
|
|
55894
|
+
}
|
|
55895
|
+
});
|
|
55769
55896
|
app.post("/api/bootstrap/retry", async () => {
|
|
55770
55897
|
const st = getBootstrapState().status;
|
|
55771
55898
|
if (st === "failed" || st === "idle") {
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: floless-app-onboarding
|
|
3
|
+
description: This skill should be used to onboard a user into floless.app and its AWARE runtime as a guided, re-runnable walkthrough. It first explains what AWARE is (the runtime), checks the installed AWARE version and offers an update, maps the command surface, and shows the AVAILABLE agents via the `aware agent catalog` command (installing one on request); then it tours floless.app (the 3-column chat/canvas/inspect UI, the Compile-Approve-Run gate, Simulate vs real Run, Templates, Routines, the Agents library, Bake and Graft, the Inspect tabs, the version footer) and the three floless skills (bridge, routines, workflows). Use it when the user is new or wants a refresher, on triggers like "onboard me", "get me started", "I'm new to floless.app or AWARE", "give me a tour", "how do I use this", "walk me through floless.app", "what can AWARE do", "show me the agents", or "/floless-app-onboarding". It can be re-invoked anytime for the whole tour or a single section.
|
|
4
|
+
metadata:
|
|
5
|
+
version: 0.1.0
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Onboarding a user into floless.app + AWARE
|
|
9
|
+
|
|
10
|
+
Guide the user through the whole program end-to-end, as the **host terminal AI** they are talking
|
|
11
|
+
to. floless.app is a **thin web UI**; the terminal AI (you) is the brain and AWARE is the runtime.
|
|
12
|
+
This skill makes you the tour guide: explain AWARE first (the runtime everything sits on), then
|
|
13
|
+
floless.app (the window onto it), driving the user's **real** environment as you go.
|
|
14
|
+
|
|
15
|
+
Two arcs, with detail pushed to references (read each one when entering its arc):
|
|
16
|
+
|
|
17
|
+
- **Arc A — AWARE, the runtime** → `references/aware-runtime.md`
|
|
18
|
+
- **Arc B — floless.app, the UI** → `references/floless-app-tour.md`
|
|
19
|
+
|
|
20
|
+
## Operating principles (apply throughout)
|
|
21
|
+
|
|
22
|
+
- **Drive live, don't just lecture.** Run the read-only probes as they come up (`/api/health`, the
|
|
23
|
+
AWARE version, `aware agent catalog`) and narrate the user's **actual** state — their version,
|
|
24
|
+
their agents, what's on their canvas. A tour of the real thing beats prose.
|
|
25
|
+
- **Mutations need consent.** Anything that changes the machine — updating AWARE, installing an
|
|
26
|
+
agent — is **offered, then run only on an explicit yes**. Read-only probes run freely.
|
|
27
|
+
- **Degrade gracefully, never block.** Features land at different times. If a command or endpoint is
|
|
28
|
+
missing (e.g. an older AWARE without `aware agent catalog`, or the in-app AWARE-upgrade pill not
|
|
29
|
+
yet present), say so plainly, fall back to what exists, and keep going. Each arc's reference spells
|
|
30
|
+
out its fallback.
|
|
31
|
+
- **Stay faithful to the architecture.** The UI never becomes the brain; determinism is AWARE's (the
|
|
32
|
+
`.lock` is the approved contract, Run is gated on source-hash freshness). Never teach anything that
|
|
33
|
+
contradicts this.
|
|
34
|
+
- **One concept at a time; check in often.** This is a walkthrough, not a wall of text. After each
|
|
35
|
+
section, pause — "make sense? want to go deeper, or move on?" Let the user steer the pace.
|
|
36
|
+
|
|
37
|
+
## Step 0 — Orient and route
|
|
38
|
+
|
|
39
|
+
1. **Confirm the ground.** Probe the floless.app server and the installed AWARE version (read-only):
|
|
40
|
+
```bash
|
|
41
|
+
curl -s http://127.0.0.1:4317/api/health
|
|
42
|
+
# → {"ok":true,"license":"valid","awareVersion":"<installed>", ...}
|
|
43
|
+
```
|
|
44
|
+
- No response → the floless.app desktop app isn't running. Ask the user to start it (so the tour
|
|
45
|
+
can drive its UI). The AWARE arc still works without it.
|
|
46
|
+
- `awareVersion` is the AWARE version the app reports. On current builds it's freshly re-probed
|
|
47
|
+
and trustworthy; an **older app build can show a stale snapshot**, so when the exact version
|
|
48
|
+
matters (e.g. deciding whether to update), confirm it against npm in A2 — `npm ls -g
|
|
49
|
+
@aware-aeco/cli` is the source of truth. Note the version for Arc A.
|
|
50
|
+
- `license` not `valid`/`offline-grace` → some UI actions sit behind a sign-in gate; mention it
|
|
51
|
+
when the UI arc reaches a gated action, don't block the tour.
|
|
52
|
+
|
|
53
|
+
2. **Pick the path.** Ask the user once:
|
|
54
|
+
> "Want the **full guided tour** (AWARE → floless.app), or **jump to one section**?"
|
|
55
|
+
- **New user / unsure → full tour:** run Arc A then Arc B, in order, top to bottom.
|
|
56
|
+
- **Refresher → menu:** show the section map below and run only the chosen section(s).
|
|
57
|
+
- The user can always say "skip ahead", "go deeper on X", or "stop" — honor it.
|
|
58
|
+
|
|
59
|
+
## The section map (also the re-run menu)
|
|
60
|
+
|
|
61
|
+
Offer this list when the user wants to jump. Each item maps to a section in a reference file.
|
|
62
|
+
|
|
63
|
+
**Arc A — AWARE (the runtime)** — `references/aware-runtime.md`
|
|
64
|
+
- **A1 · What AWARE is** — the substrate; where it lives (`~/.aware`); build-once / run-forever.
|
|
65
|
+
- **A2 · Version & update** — show the installed version, check for newer, offer the update (consent).
|
|
66
|
+
- **A3 · The command surface** — the `aware` verbs worth knowing (apps, agents, sidecars, logs).
|
|
67
|
+
- **A4 · Available agents** — browse `aware agent catalog`, search, capability-check, install one (consent).
|
|
68
|
+
|
|
69
|
+
**Arc B — floless.app (the UI)** — `references/floless-app-tour.md`
|
|
70
|
+
- **B1 · The 3-column window** — chat│canvas│inspect; the app is the prompt, the browser is the window.
|
|
71
|
+
- **B2 · The gate** — Compile → Approve → Run; the source-hash Run gate; Simulate vs real Run.
|
|
72
|
+
- **B3 · Templates, Routines, Agents, Inspect** — the panels and tabs the user will actually click.
|
|
73
|
+
- **B4 · The footer** — installed AWARE version + app version; updating AWARE / the app from here.
|
|
74
|
+
- **B5 · Bake & Graft** — export a workflow as an agent (Bake); import your own tool as an agent (Graft).
|
|
75
|
+
- **B6 · The floless skills** — bridge / routines / workflows, and how to drive them from the terminal.
|
|
76
|
+
- **B7 · How to run + best practices** — the everyday loop and the habits that keep it deterministic.
|
|
77
|
+
|
|
78
|
+
## Running an arc
|
|
79
|
+
|
|
80
|
+
When entering **Arc A**, read `references/aware-runtime.md` and walk A1→A4 (or the chosen item).
|
|
81
|
+
When entering **Arc B**, read `references/floless-app-tour.md` and walk B1→B7 (or the chosen item).
|
|
82
|
+
Each reference carries the exact commands, the talking points, the consent prompts, and the
|
|
83
|
+
fallbacks. Narrate the user's real output; do not paste a reference verbatim.
|
|
84
|
+
|
|
85
|
+
## Close
|
|
86
|
+
|
|
87
|
+
When the tour (or chosen section) is done, recap in two or three lines what the user now knows, and
|
|
88
|
+
tell them how to come back:
|
|
89
|
+
|
|
90
|
+
> "That's the tour. Re-run **/floless-app-onboarding** anytime — for the whole thing, or just name a
|
|
91
|
+
> section ('the agents part', 'Bake & Graft', 'routines') and I'll jump straight there."
|
|
92
|
+
|
|
93
|
+
Then offer a concrete next step grounded in what they have — e.g. "want to build your first
|
|
94
|
+
workflow?" (hand off to **floless-app-workflows**) or "install one of those agents?" (A4).
|
|
95
|
+
|
|
96
|
+
## Guardrails
|
|
97
|
+
|
|
98
|
+
- **Read-only by default; mutations on consent.** Never update AWARE or install an agent without an
|
|
99
|
+
explicit yes.
|
|
100
|
+
- **Never invent.** Don't name agents, commands, or UI elements that aren't really there — probe and
|
|
101
|
+
report what exists. If `aware agent catalog` is unavailable, say so and use `aware agent list`.
|
|
102
|
+
- **Don't contradict the architecture.** The UI renders AWARE state and triggers `aware` verbs; it
|
|
103
|
+
never composes workflows or calls an LLM. Determinism is AWARE's `.lock`.
|
|
104
|
+
- **Hand off for depth.** Onboarding orients; it does not replace the doing-skills. Point to
|
|
105
|
+
**floless-app-workflows** (build/run `.flo` apps), **floless-app-routines** (automate them), and
|
|
106
|
+
**floless-app-bridge** (apply UI tweaks/templates) when the user wants to actually do the thing.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Arc A — AWARE, the runtime
|
|
2
|
+
|
|
3
|
+
The detail behind Arc A. Walk A1→A4 (or the one section the user picked). Run the read-only probes
|
|
4
|
+
live and narrate the user's real output; offer mutations (update, install) and run them only on a
|
|
5
|
+
yes. Keep each section short and check in before moving on.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## A1 · What AWARE is
|
|
10
|
+
|
|
11
|
+
**The pitch (say it in plain terms):** AWARE is the open-source **agentic substrate for AECO**
|
|
12
|
+
([github.com/aware-aeco/aware](https://github.com/aware-aeco/aware)) — the runtime everything in
|
|
13
|
+
floless.app sits on. You describe what you want; the terminal AI composes a plain-text **`.flo`**
|
|
14
|
+
workflow; AWARE **compiles** it into a frozen, version-pinned **`.lock`** (the approved contract) and
|
|
15
|
+
**runs** it. floless.app is just the window onto this — it holds no engine and no LLM of its own.
|
|
16
|
+
|
|
17
|
+
**Build once, run forever** (the core idea — make sure it lands):
|
|
18
|
+
- `aware app compile` freezes a workflow's source into `<app>.lock` and pins the agent versions.
|
|
19
|
+
- `aware app run` **refuses to run** if the source changed since that compile (a "source-hash drift").
|
|
20
|
+
So an approved app stays exactly as approved until you deliberately recompile. That gate is what
|
|
21
|
+
makes runs reproducible — and it's the same gate the floless.app **Run** button enforces (B2).
|
|
22
|
+
- Determinism is layered: the `.lock` at the **app** level, and compiled code (an `exec` node's C#,
|
|
23
|
+
a Smart Node) at the **node** level.
|
|
24
|
+
|
|
25
|
+
**Where it lives** (show them — read-only):
|
|
26
|
+
```bash
|
|
27
|
+
ls ~/.aware
|
|
28
|
+
# apps/ installed workflows (<id>/<app>.flo + <app>.lock)
|
|
29
|
+
# agents/ installed capabilities apps can call
|
|
30
|
+
# bridges/ host connectors (e.g. aware-tekla.exe)
|
|
31
|
+
# logs/ per-run JSONL execution traces
|
|
32
|
+
# credentials/ connection secrets (opaque; never through the model)
|
|
33
|
+
```
|
|
34
|
+
AWARE itself is installed as the npm global **`@aware-aeco/cli`** (not bundled into floless.app —
|
|
35
|
+
floless.app installs/repairs it on first start). That detail matters for A2.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## A2 · Version & update
|
|
40
|
+
|
|
41
|
+
**Show the installed version.** The **authoritative source is the installed npm package** (a file
|
|
42
|
+
read of what's actually on disk, not a guess):
|
|
43
|
+
```bash
|
|
44
|
+
npm ls -g @aware-aeco/cli # the installed global — the source of truth
|
|
45
|
+
```
|
|
46
|
+
The floless.app footer and `/api/health` (`awareVersion`, captured in Step 0) also surface it —
|
|
47
|
+
freshly re-probed on current builds, but an **older app build can show a stale boot snapshot**. If
|
|
48
|
+
the app's number and npm disagree, trust npm. State the version back to the user.
|
|
49
|
+
|
|
50
|
+
**Check for a newer release** (read-only):
|
|
51
|
+
```bash
|
|
52
|
+
npm view @aware-aeco/cli version # the latest published on npm
|
|
53
|
+
```
|
|
54
|
+
Compare to the installed version. If they match, say "you're on the latest" and move on.
|
|
55
|
+
|
|
56
|
+
**If newer exists, offer the update (consent — don't run it unprompted):**
|
|
57
|
+
> "AWARE v<new> is out (you're on v<cur>). Update now?"
|
|
58
|
+
|
|
59
|
+
The reliable path on **every** build is the npm global — lead with it:
|
|
60
|
+
```bash
|
|
61
|
+
npm i -g @aware-aeco/cli@latest # always works; no running app required
|
|
62
|
+
```
|
|
63
|
+
**Newer floless.app builds also add an in-app shortcut:** a footer **`↑ Upgrade AWARE to v<target>`**
|
|
64
|
+
pill next to the version (clicking it reinstalls in place, no relaunch) — programmatically
|
|
65
|
+
`GET /api/aware/update` then `POST /api/aware/update/apply`. If that pill isn't there, the app isn't
|
|
66
|
+
running, or `GET /api/aware/update` 404s, the build predates the shortcut — just use the npm command
|
|
67
|
+
above.
|
|
68
|
+
|
|
69
|
+
After applying, **re-probe** (`npm ls -g @aware-aeco/cli`) and confirm the new version to the user.
|
|
70
|
+
|
|
71
|
+
**Why there's a button at all:** floless.app installs `@aware-aeco/cli@latest` above a minimum floor
|
|
72
|
+
on first run, but it deliberately does **not** force-upgrade an existing install on every boot (no
|
|
73
|
+
surprise upgrades, no slow starts). Moving an up-to-date-enough install to the newest release is
|
|
74
|
+
exactly what this on-demand update is for.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## A3 · The command surface
|
|
79
|
+
|
|
80
|
+
Give a **map**, not an exhaustive dump. Most of these the user will drive through floless.app's
|
|
81
|
+
buttons (B2) — the CLI is the same machinery underneath. Group it like this, then suggest
|
|
82
|
+
`aware --help` (and `aware app --help` / `aware agent --help`) to see the live surface for *their*
|
|
83
|
+
version rather than enumerating flags they may not have.
|
|
84
|
+
|
|
85
|
+
**Apps — the workflows (`aware app …`):**
|
|
86
|
+
- `list` — installed apps. `show <id>` — an app's manifest. `inspect <dir>` — the "Glass Box" plan
|
|
87
|
+
(takes the app **path**, like `validate`/`compile`).
|
|
88
|
+
- `install <dir>` — install from an app **directory**. `uninstall <id>`.
|
|
89
|
+
- `validate <dir>` / `compile <dir>` — check / freeze to `<app>.lock` (compile is "Approve").
|
|
90
|
+
- `run <id> [--input k=v] [--json]` — execute the approved app (run takes the **id**).
|
|
91
|
+
- `logs` — past runs (traces also land at `~/.aware/logs/<id>/…`).
|
|
92
|
+
|
|
93
|
+
**Agents — the capabilities apps call (`aware agent …`):**
|
|
94
|
+
- `list` — **installed** agents. `describe <id>` — an installed agent's commands + skills.
|
|
95
|
+
- `install <id>` / `uninstall <id>` — add/remove a capability.
|
|
96
|
+
- …plus the **catalog** commands for discovering agents you *don't* have yet — that's A4.
|
|
97
|
+
|
|
98
|
+
**Sidecars / bridges — host connectors (`aware sidecar …`):**
|
|
99
|
+
- `install <host>` — fetch a host bridge (e.g. `tekla`, `rhino`, `sketchup`, `revit`) so workflows
|
|
100
|
+
can drive that application. Required before a real host run against, say, a live Tekla model.
|
|
101
|
+
|
|
102
|
+
**Build — graft a tool into an agent (`aware build agent --from-…`):**
|
|
103
|
+
- Turns a foreign tool you own (a DLL, an OpenAPI spec, a NuGet/npm package, a COM ProgID, a CLI…)
|
|
104
|
+
into a first-class AWARE agent. This is the **Graft** capability — covered in B5.
|
|
105
|
+
|
|
106
|
+
**Connections / credentials (`aware connect …`):** wire up external services; secrets live as opaque
|
|
107
|
+
handles under `~/.aware/credentials/`, never passed through the model.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## A4 · Available agents (browse before you install)
|
|
112
|
+
|
|
113
|
+
**The gap to explain:** `aware agent list` and `aware agent describe` only see agents you've
|
|
114
|
+
**already installed**. To discover *what agents exist* before installing — and what each one does —
|
|
115
|
+
use the **catalog** — a searchable index of available agents across architecture, construction,
|
|
116
|
+
engineering, and visualization (the live count is whatever `aware agent catalog` prints for the
|
|
117
|
+
user; don't quote a fixed number).
|
|
118
|
+
|
|
119
|
+
**Browse — run it live and narrate the result:**
|
|
120
|
+
```bash
|
|
121
|
+
aware agent catalog
|
|
122
|
+
# ID · DISPLAY-NAME · VERSION · STATUS · COMMANDS · SKILLS · DESCRIPTION (one line)
|
|
123
|
+
```
|
|
124
|
+
Point out a few agents relevant to the user's domain (for an AEC user: `tekla`, `rhino`, `revit`,
|
|
125
|
+
etc.). Add `--json` for machine-readable output.
|
|
126
|
+
|
|
127
|
+
**Search by functionality:**
|
|
128
|
+
```bash
|
|
129
|
+
aware agent search "steel connection"
|
|
130
|
+
aware agent search "macro" --capability # --capability biases to command names/methods
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Describe one you haven't installed:**
|
|
134
|
+
```bash
|
|
135
|
+
aware agent describe <id> --available # full description + commands + skills for a NOT-installed agent
|
|
136
|
+
```
|
|
137
|
+
(Plain `aware agent describe <id>` stays installed-only; `--available` reaches into the catalog.)
|
|
138
|
+
|
|
139
|
+
**Capability check (scriptable, exits 0/1):**
|
|
140
|
+
```bash
|
|
141
|
+
aware agent has <agent> <capability> # exit 0 if it exposes that capability, nonzero if not
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Install on request (consent — this changes the machine):**
|
|
145
|
+
> "Want me to install `<id>`?"
|
|
146
|
+
```bash
|
|
147
|
+
aware agent install <id>
|
|
148
|
+
```
|
|
149
|
+
It lands under `~/.aware/agents/<id>/`, then appears in `aware agent list` and is usable as a
|
|
150
|
+
workflow node (`agent: <id>`). It also shows up in floless.app's **`⊞ Agents`** library (B3).
|
|
151
|
+
|
|
152
|
+
**Degradation — older AWARE has no catalog (handle this gracefully):**
|
|
153
|
+
The catalog commands are newer than some installs. If `aware agent catalog` errors, is an unknown
|
|
154
|
+
subcommand, or prints something like *"this AWARE's registry has no catalog yet — update AWARE"*,
|
|
155
|
+
then the user's AWARE predates the feature. Don't push through it:
|
|
156
|
+
1. Say so plainly: "Your AWARE is a bit older and doesn't have the agent catalog yet."
|
|
157
|
+
2. Point back to **A2** and offer the update (`npm i -g @aware-aeco/cli@latest`) — on consent.
|
|
158
|
+
3. Until they update, fall back to `aware agent list` (installed-only) so the section still delivers
|
|
159
|
+
*something* — show what they already have, and note the catalog unlocks discovery once updated.
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Arc B — floless.app, the UI
|
|
2
|
+
|
|
3
|
+
The detail behind Arc B. Walk B1→B7 (or the one section the user picked). Point the user at the
|
|
4
|
+
real elements in their browser (floless.app at `http://127.0.0.1:4317`) and, where useful, show the
|
|
5
|
+
live state. Keep each section short; check in before moving on. If the app isn't running, ask the
|
|
6
|
+
user to start it first.
|
|
7
|
+
|
|
8
|
+
Throughout: floless.app is a **thin UI** — it renders AWARE state and triggers `aware` verbs. It
|
|
9
|
+
never composes workflows or calls an LLM. The terminal AI (you) is the brain.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## B1 · The 3-column window
|
|
14
|
+
|
|
15
|
+
The layout is **header / `chat │ canvas │ inspect` / footer**:
|
|
16
|
+
- **Left — chat (~340px):** "the app is the prompt; it lives in your AI terminal; the browser is the
|
|
17
|
+
window." This panel is a relay/echo of the terminal session, **not a second chatbot** — the real
|
|
18
|
+
conversation is where you're typing now, in the terminal.
|
|
19
|
+
- **Center — canvas:** the open workflow's **topology** — nodes and the edges between them, rendered
|
|
20
|
+
from the `.flo`. Zoom / fit controls; click a node to inspect it; **star ★** a node to save it as a
|
|
21
|
+
reusable Template (B3).
|
|
22
|
+
- **Right — inspect (~420px):** details of the selected node (B3).
|
|
23
|
+
- **Top-left — the workflow switcher:** pick which installed app is on the canvas. A small dot marks
|
|
24
|
+
unsaved edits.
|
|
25
|
+
|
|
26
|
+
Have the user pick a workflow from the switcher so there's something real on the canvas to tour.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## B2 · The gate (Compile → Approve → Run)
|
|
31
|
+
|
|
32
|
+
The header carries the run controls and a **state** chip. This is "build once, run forever" (A1)
|
|
33
|
+
made visible:
|
|
34
|
+
|
|
35
|
+
- **`⎙ Compile`** — freezes the current `.flo` into its **`.lock`**. This *is* "Approve": the lock is
|
|
36
|
+
the contract that will run. After any edit, the Run gate **locks** until you Compile again (the
|
|
37
|
+
source-hash changed — a "drift").
|
|
38
|
+
- **`▶ Run workflow`** — the **real** run: executes the approved app against your live host. Each node
|
|
39
|
+
paints progress on the canvas (running → ✓ done / ✗ failed); if the app produces a report, the HTML
|
|
40
|
+
Viewer opens with the result. Run always executes the single open workflow against your one live
|
|
41
|
+
host — there's no parallel or multi-target run.
|
|
42
|
+
- **`Simulate`** — a **composition check**: every node is stubbed from its schema, no host touched.
|
|
43
|
+
Great for sanity-checking a workflow's *shape* fast. Caveat to mention: Simulate can't truly run
|
|
44
|
+
data-carrying `exec` apps (stubs carry no real values) — for those, use the real Run.
|
|
45
|
+
- **state chip** — shows where the gate is (needs compile / ready / running).
|
|
46
|
+
|
|
47
|
+
The everyday rhythm: **edit → Compile/Approve → Run**. Run stays disabled on drift until you
|
|
48
|
+
re-Compile. That gate is what keeps runs reproducible.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## B3 · Templates, Routines, Agents, Inspect
|
|
53
|
+
|
|
54
|
+
The panels and buttons the user will actually click:
|
|
55
|
+
|
|
56
|
+
- **Templates (★):** star any node on the canvas — or, inside the Agents library, any command — to
|
|
57
|
+
save it as a reusable **Template**. It appears in the template bar and is reusable in **any**
|
|
58
|
+
workflow, and persists across projects. The terminal AI is *template-aware*: when a plain-English
|
|
59
|
+
request matches a saved Template, it reuses that saved logic instead of rebuilding it.
|
|
60
|
+
- **Routines (`⏱ Routines`):** run a workflow **automatically** — on a **schedule** (e.g. weekdays at
|
|
61
|
+
07:00) or on a **trigger** (a live event, e.g. *whenever your Tekla model changes*). Runs happen on
|
|
62
|
+
this machine through your live host, exactly like clicking ▶ Run. Authoring path: the
|
|
63
|
+
**floless-app-routines** skill (B6).
|
|
64
|
+
- **Agents (`⊞ Agents`):** the **Agents library** — every installed agent, each command starrable as a
|
|
65
|
+
Template. Agents you install (A4) show up here. It's also the entry point to **Graft** (B5).
|
|
66
|
+
- **Inspect (right panel):** click any node → its details. The tabs surface the node's
|
|
67
|
+
**Description / Skill**, its **Code** (the real source — an `exec` node's C#, syntax-highlighted),
|
|
68
|
+
and the live **Execution** trace while a run is in flight. A report/viewer node carries a
|
|
69
|
+
**"View report ▸"** button that opens the in-app **HTML Viewer** (a sandboxed iframe showing the
|
|
70
|
+
last result — load is split from run, so it shows the last report instantly without recomputing).
|
|
71
|
+
- **Tweak (✎):** select a node and queue a plain-English change ("also group by assembly"). The
|
|
72
|
+
terminal AI picks it up (via **floless-app-bridge**, B6), edits just that one node, and recompiles —
|
|
73
|
+
the UI itself never edits the `.flo`.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## B4 · The footer
|
|
78
|
+
|
|
79
|
+
The bottom strip shows the **installed AWARE version** and the **floless.app version**.
|
|
80
|
+
- On current builds the AWARE version is **live** — it re-stamps if AWARE changes out of band (e.g.
|
|
81
|
+
you updated it from the terminal). On older builds it can be a boot-time snapshot, so if it ever
|
|
82
|
+
looks off, npm is the source of truth (A2).
|
|
83
|
+
- Update affordances live here too: an **app self-update** pill (`↑ Update to v…`) when a newer
|
|
84
|
+
floless.app is published, and — on builds that include it — an **`↑ Upgrade AWARE to v…`** pill (the
|
|
85
|
+
in-app AWARE update from A2). If you don't see the AWARE pill, your build predates it; the reliable
|
|
86
|
+
path is always the CLI (`npm i -g @aware-aeco/cli@latest`).
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## B5 · Bake & Graft (the two killer moves)
|
|
91
|
+
|
|
92
|
+
A matched pair — the input and output sides of "turn things into agents":
|
|
93
|
+
|
|
94
|
+
- **Bake — export a workflow as an agent.** (Reachable from the workflow menu as **"Bake into
|
|
95
|
+
agent"**; the confirm action carries the **⊙ Bake** glyph.) Takes a working multi-node `.flo` and
|
|
96
|
+
packages it as a single **callable agent** whose inputs become the agent's parameters. AWARE
|
|
97
|
+
reinstalls it with that exposed interface; it then appears in the **Agents library** as one callable
|
|
98
|
+
node that other workflows can drop in. (Mechanism: `exposes-as-agent` + `exposed-commands` in the
|
|
99
|
+
`.flo`.) Use it to turn a proven workflow into a reusable building block.
|
|
100
|
+
- **Graft — import your own tool as an agent.** (Reachable from the **`⊞ Agents`** library —
|
|
101
|
+
**"Graft new agent"** — or the workflow menu.) The input-side mirror of Bake. Turn a foreign tool
|
|
102
|
+
you already own — a .NET DLL (or a whole folder of them), an OpenAPI spec, a NuGet/npm package, a
|
|
103
|
+
COM ProgID, a CLI — into a first-class AWARE agent you can compose. The hero case: an in-house Tekla
|
|
104
|
+
plugin becomes a headless command you can run across an entire model at 2am. (Mechanism: AWARE's
|
|
105
|
+
`aware build agent --from-…`, A3; floless stages a preview of the discovered commands before you
|
|
106
|
+
commit.)
|
|
107
|
+
|
|
108
|
+
The full arc: **Graft your tool → compose it into a workflow → Bake the workflow → distribute.** That
|
|
109
|
+
is how floless.app turns "use the agents we ship" into "any tool you own becomes composable AI
|
|
110
|
+
infrastructure." Keep it concept-level here; the deep authoring lives in **floless-app-workflows**.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## B6 · The floless skills (how to drive it from the terminal)
|
|
115
|
+
|
|
116
|
+
floless.app ships three skills into the AI terminal — they are how the terminal AI acts on the
|
|
117
|
+
user's behalf. When the user wants to *do* something, invoke the matching one:
|
|
118
|
+
|
|
119
|
+
- **floless-app-workflows** — build / modify / run `.flo` workflows: the install→validate→compile→run
|
|
120
|
+
loop, `exec` nodes (Roslyn C# against a live host), app inputs, the HTML Viewer. The doing-skill for
|
|
121
|
+
authoring.
|
|
122
|
+
- **floless-app-routines** — set up automatic runs (schedule or trigger) through the Routines API.
|
|
123
|
+
- **floless-app-bridge** — pick up what the user did in the UI (a queued **Tweak**, a "use this
|
|
124
|
+
Template" request), apply the change to the `.flo`, recompile, and clear the request.
|
|
125
|
+
|
|
126
|
+
All three talk to the same local server (port 4317) and preserve the contract: the UI queues intent;
|
|
127
|
+
the terminal AI does the real authoring. The user drives by **asking you in the terminal**, then
|
|
128
|
+
watching and approving in the browser.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## B7 · How to run + best practices
|
|
133
|
+
|
|
134
|
+
**The everyday loop:** pick a workflow (switcher) → set its inputs (on the input node) →
|
|
135
|
+
**`⎙ Compile`/Approve** → **`▶ Run workflow`** → read the result (HTML Viewer, or the Execution tab).
|
|
136
|
+
Edit → recompile → run.
|
|
137
|
+
|
|
138
|
+
**Habits that keep it good:**
|
|
139
|
+
- **Compile after every edit.** Run is gated on a fresh lock — determinism is AWARE's, not the UI's.
|
|
140
|
+
- **Trust the real Run, not a clean compile.** `Simulate` checks shape; only a real `▶ Run` proves a
|
|
141
|
+
workflow works against the host.
|
|
142
|
+
- **Tweak, don't rebuild.** To change one node, select it and **✎ Tweak** — the AI edits just that
|
|
143
|
+
node and recompiles.
|
|
144
|
+
- **Automate the repeat.** Once a workflow is solid, make it a **Routine** (schedule or trigger).
|
|
145
|
+
- **Reuse aggressively.** ★ star nodes/commands as **Templates**; **Bake** proven workflows into
|
|
146
|
+
agents; **Graft** your own tools in.
|
|
147
|
+
- **Keep AWARE current (A2).** New releases bring new agents and features.
|
|
148
|
+
- **Remember who's the brain.** Ask the terminal AI to build and change things; watch, approve, and
|
|
149
|
+
run in the browser. The UI is the window, not the engine.
|
package/dist/web/app.css
CHANGED
|
@@ -158,9 +158,12 @@
|
|
|
158
158
|
quietest tier. Label kept — an unlabeled icon would be an unclear affordance. */
|
|
159
159
|
#browse-btn { background: transparent; border-color: transparent; color: var(--text-dim); }
|
|
160
160
|
#browse-btn:hover { background: var(--surface-2); border-color: var(--border-strong); color: var(--text); }
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
161
|
+
/* Simulate mirrors Compile (outline-accent tier) — a consistent secondary
|
|
162
|
+
sibling; Run stays the only filled (primary) button, so the two outline
|
|
163
|
+
buttons reading alike is the correct hierarchy, not a lost signal. */
|
|
164
|
+
#sim-btn { color: var(--text); border-color: var(--accent-dim); }
|
|
165
|
+
#sim-btn:hover { color: var(--text); border-color: var(--accent); }
|
|
166
|
+
#sim-btn:disabled { color: var(--text-dim); border-color: var(--border-strong); cursor: default; opacity: 0.6; }
|
|
164
167
|
#run-btn {
|
|
165
168
|
background: var(--accent);
|
|
166
169
|
color: #ffffff;
|
|
@@ -396,18 +399,27 @@
|
|
|
396
399
|
.onb-num { font-family: var(--mono); font-size: 11px; font-weight: 600; color: var(--accent); min-width: 14px; }
|
|
397
400
|
.onb-step-label { font-size: 12px; color: var(--text); }
|
|
398
401
|
.onb-step-note { font-size: 11px; color: var(--text-dim); font-style: italic; }
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
.onb-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
}
|
|
410
|
-
.onb-
|
|
402
|
+
/* Secondary AI-native pointer (replaces the old raw `aware app install` chip — confusing for a
|
|
403
|
+
newcomer and off-message against "your terminal AI builds the workflows"). */
|
|
404
|
+
.onb-foot { font-size: 12px; color: var(--text-muted); margin-top: 16px; line-height: 1.5; }
|
|
405
|
+
/* "Start the guided tour" — the primary first-run action (copies the onboarding launch prompt).
|
|
406
|
+
Accent-soft region + filled-accent button mirror the .agent-card.selected / #run-btn primary
|
|
407
|
+
tier; all tokens existing (locked baseline — no new fonts/colors). */
|
|
408
|
+
.onb-tour {
|
|
409
|
+
display: flex; align-items: center; justify-content: space-between; gap: 12px;
|
|
410
|
+
margin-top: 14px; padding: 11px 14px;
|
|
411
|
+
background: var(--accent-soft); border: 1px solid var(--accent-dim); border-radius: 6px;
|
|
412
|
+
}
|
|
413
|
+
.onb-tour-hint { flex: 1; font-size: 11px; color: var(--text-dim); font-style: italic; line-height: 1.45; }
|
|
414
|
+
.onb-tour-btn {
|
|
415
|
+
flex: none; background: var(--accent); color: #ffffff; border: 1px solid var(--accent);
|
|
416
|
+
border-radius: 4px; font-family: var(--ui); font-size: 12px; font-weight: 600;
|
|
417
|
+
padding: 7px 14px; letter-spacing: 0.02em; cursor: pointer; white-space: nowrap;
|
|
418
|
+
transition: background 0.15s, border-color 0.15s, box-shadow 0.15s;
|
|
419
|
+
}
|
|
420
|
+
.onb-tour-btn:hover { background: var(--accent-bright); border-color: var(--accent-bright); box-shadow: 0 0 14px var(--accent-glow); }
|
|
421
|
+
.onb-tour-btn:disabled { cursor: default; opacity: 0.85; box-shadow: none; }
|
|
422
|
+
.onb-tour-ico { font-size: 11px; opacity: 0.7; margin-left: 6px; }
|
|
411
423
|
|
|
412
424
|
.agent-card {
|
|
413
425
|
background: var(--surface);
|
|
@@ -1198,7 +1210,11 @@
|
|
|
1198
1210
|
.sep { color: var(--text-dim); opacity: 0.4; }
|
|
1199
1211
|
.version-info { display: inline-flex; align-items: center; gap: 8px; }
|
|
1200
1212
|
.aware-version, .app-version { font-family: var(--mono); font-size: 11px; color: var(--text-muted); letter-spacing: 0.02em; }
|
|
1201
|
-
|
|
1213
|
+
/* Separator between the AWARE runtime version and the app build version. An explicit
|
|
1214
|
+
.ver-sep span (revealed once versions stamp) — robust to the upgrade pill sitting
|
|
1215
|
+
between the two versions, which a `+`-adjacency ::before could not span. Flex `gap`
|
|
1216
|
+
on .version-info spaces it on both sides. */
|
|
1217
|
+
.ver-sep { font-size: 11px; }
|
|
1202
1218
|
|
|
1203
1219
|
/* ========== TOOLTIPS (styled, app-themed; replaces native title="") ========== */
|
|
1204
1220
|
#tooltip {
|
|
@@ -1589,15 +1605,27 @@
|
|
|
1589
1605
|
header dirty-dot so the menu reinforces it without a new component. */
|
|
1590
1606
|
.menu-item-dirty .menu-icon { color: var(--warn); }
|
|
1591
1607
|
.menu-item-dirty .menu-label::after { content: " ●"; color: var(--warn); font-size: 9px; vertical-align: 1px; }
|
|
1608
|
+
/* Icon column. Glyphs are inline Lucide stroke SVGs (shadcn's icon family),
|
|
1609
|
+
not Unicode characters — ad-hoc symbols rendered at wildly different ink
|
|
1610
|
+
sizes (7–19px tall at the same font-size); a uniform 15px SVG box fixes it.
|
|
1611
|
+
stroke:currentColor keeps the dirty-state recolor (.menu-item-dirty) working. */
|
|
1592
1612
|
.menu-icon {
|
|
1593
1613
|
width: 18px;
|
|
1594
|
-
font-size: 13px;
|
|
1595
1614
|
color: var(--text-muted);
|
|
1596
1615
|
display: inline-flex;
|
|
1597
1616
|
align-items: center;
|
|
1598
1617
|
justify-content: center;
|
|
1599
1618
|
flex-shrink: 0;
|
|
1600
1619
|
}
|
|
1620
|
+
.menu-icon svg {
|
|
1621
|
+
width: 15px;
|
|
1622
|
+
height: 15px;
|
|
1623
|
+
fill: none;
|
|
1624
|
+
stroke: currentColor;
|
|
1625
|
+
stroke-width: 1.5;
|
|
1626
|
+
stroke-linecap: round;
|
|
1627
|
+
stroke-linejoin: round;
|
|
1628
|
+
}
|
|
1601
1629
|
.menu-label { flex: 1; }
|
|
1602
1630
|
.menu-kbd {
|
|
1603
1631
|
font-family: var(--mono);
|
|
@@ -1871,7 +1899,7 @@ body {
|
|
|
1871
1899
|
|
|
1872
1900
|
/* Compile / approve — the gated step before Run. Outline-accent tier: an
|
|
1873
1901
|
accent-dim border pairs it visually with the filled Run button, so the eye
|
|
1874
|
-
reads "Compile → Run" as the spine
|
|
1902
|
+
reads "Compile → Run" as the spine; Simulate is a matching outline sibling. */
|
|
1875
1903
|
#compile-btn { color: var(--text); border-color: var(--accent-dim); }
|
|
1876
1904
|
#compile-btn:hover { color: var(--text); border-color: var(--accent); }
|
|
1877
1905
|
#compile-btn:disabled { color: var(--text-dim); border-color: var(--border-strong); cursor: default; opacity: 0.6; }
|
|
@@ -2066,12 +2094,45 @@ body {
|
|
|
2066
2094
|
.agent-card .node-inputs .ni-val { color: var(--accent); font-weight: 600; }
|
|
2067
2095
|
|
|
2068
2096
|
/* Per-node run status, painted from the live trace (running → done / error).
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2097
|
+
Running = a STATIC accent ring + glow (set once, never animated — so the
|
|
2098
|
+
compositor isn't recalculating box-shadow blur on every node every frame)
|
|
2099
|
+
plus a bright accent band that travels left→right across the card face. Colour
|
|
2100
|
+
is never the only signal — the spinner + "running" text pip and the filled
|
|
2101
|
+
stat bar are static cues alongside the sweep. */
|
|
2102
|
+
.agent-card.node-running { box-shadow: 0 0 0 1px var(--accent), 0 0 14px var(--accent-glow); }
|
|
2103
|
+
/* Repurpose ::before (the hover/selected corner-wash) ONLY while running: it
|
|
2104
|
+
becomes a traveling light band. The card is overflow:hidden + radius:8px, so
|
|
2105
|
+
the band clips to the rounded rect for free. position/inset/pointer-events
|
|
2106
|
+
come from the base ::before rule; we override background + animation and kill
|
|
2107
|
+
the inherited opacity transition so the band shows instantly (no 0.25s fade). */
|
|
2108
|
+
.agent-card.node-running::before {
|
|
2109
|
+
opacity: 1;
|
|
2110
|
+
/* Wide soft band with a bright (but glow-cored, not hard-line) centre, so the
|
|
2111
|
+
sweep reads clearly without washing out the text it passes over. */
|
|
2112
|
+
background: linear-gradient(100deg,
|
|
2113
|
+
transparent 0%, transparent 28%,
|
|
2114
|
+
var(--accent-soft) 38%,
|
|
2115
|
+
color-mix(in srgb, var(--accent) 78%, transparent) 47%,
|
|
2116
|
+
color-mix(in srgb, var(--accent-bright) 85%, transparent) 50%,
|
|
2117
|
+
color-mix(in srgb, var(--accent) 78%, transparent) 53%,
|
|
2118
|
+
var(--accent-soft) 62%,
|
|
2119
|
+
transparent 72%, transparent 100%);
|
|
2120
|
+
background-size: 235% 100%;
|
|
2121
|
+
background-repeat: no-repeat;
|
|
2122
|
+
background-position: -30% 0;
|
|
2123
|
+
animation: nodeSweep 1.7s linear infinite;
|
|
2124
|
+
transition: none;
|
|
2125
|
+
}
|
|
2126
|
+
/* Off the left edge → off the right edge, no idle gap, constant speed. */
|
|
2127
|
+
@keyframes nodeSweep {
|
|
2128
|
+
0% { background-position: -30% 0; }
|
|
2129
|
+
100% { background-position: 130% 0; }
|
|
2130
|
+
}
|
|
2131
|
+
/* Selected + running: keep BOTH rings (.node-running would otherwise clobber the
|
|
2132
|
+
.selected ring). The sweep ::before overrides the static .selected::before wash. */
|
|
2133
|
+
.agent-card.selected.node-running {
|
|
2134
|
+
border-color: var(--accent);
|
|
2135
|
+
box-shadow: 0 0 0 1px var(--accent), 0 0 14px var(--accent-glow), 0 8px 32px -8px var(--accent-glow);
|
|
2075
2136
|
}
|
|
2076
2137
|
.agent-card.node-done { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ok) 50%, transparent); }
|
|
2077
2138
|
.agent-card.node-error { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--err) 60%, transparent); }
|
|
@@ -2082,8 +2143,46 @@ body {
|
|
|
2082
2143
|
border-top: 1px solid var(--border);
|
|
2083
2144
|
}
|
|
2084
2145
|
.agent-card.node-running .node-stat { color: var(--accent); background: var(--accent-soft); }
|
|
2146
|
+
/* "running" gets a real working indicator — a small ring spinner before the
|
|
2147
|
+
label, reusing the global `spin` keyframe + the .rtn-spinner recipe (so it
|
|
2148
|
+
speaks the same "in-flight" language as the routines panel). No arbitrary
|
|
2149
|
+
glyph. done/error keep their meaningful ✓/✗ text marks. */
|
|
2150
|
+
.agent-card.node-running .node-stat::before {
|
|
2151
|
+
content: "";
|
|
2152
|
+
display: inline-block;
|
|
2153
|
+
width: 10px; height: 10px;
|
|
2154
|
+
border: 1.5px solid var(--border-strong);
|
|
2155
|
+
border-top-color: var(--accent);
|
|
2156
|
+
border-radius: 50%;
|
|
2157
|
+
animation: spin 0.8s linear infinite;
|
|
2158
|
+
vertical-align: middle;
|
|
2159
|
+
margin-right: 5px;
|
|
2160
|
+
position: relative; top: -1px; /* nudge the ring onto the 10px text midline */
|
|
2161
|
+
}
|
|
2085
2162
|
.agent-card.node-done .node-stat { color: var(--ok); background: color-mix(in srgb, var(--ok) 12%, transparent); }
|
|
2086
2163
|
.agent-card.node-error .node-stat { color: var(--err); background: color-mix(in srgb, var(--err) 14%, transparent); }
|
|
2164
|
+
/* Reduced motion — no traveling band, no spin. The node still reads as running
|
|
2165
|
+
via the static ring + glow, the filled accent-soft stat bar, and the "running"
|
|
2166
|
+
label; the card ::before falls back to the native 135deg wash, and the stat
|
|
2167
|
+
spinner becomes a static accent dot (active via colour, no implied motion).
|
|
2168
|
+
Mirrors the .rtn-fired-flash / .so-spinner reduced-motion precedents. */
|
|
2169
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2170
|
+
.agent-card.node-running::before {
|
|
2171
|
+
animation: none;
|
|
2172
|
+
background: linear-gradient(135deg, var(--accent-soft), transparent 60%);
|
|
2173
|
+
background-size: auto;
|
|
2174
|
+
background-position: 0 0;
|
|
2175
|
+
opacity: 1;
|
|
2176
|
+
transition: none;
|
|
2177
|
+
}
|
|
2178
|
+
.agent-card.node-running .node-stat::before {
|
|
2179
|
+
animation: none;
|
|
2180
|
+
width: 5px; height: 5px;
|
|
2181
|
+
border: none;
|
|
2182
|
+
background: var(--accent);
|
|
2183
|
+
top: 0;
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2087
2186
|
/* Description-tab pointer to the Code tab (not the code itself). */
|
|
2088
2187
|
.dim-note { color: var(--text-dim); font-size: 12px; margin-top: 8px; }
|
|
2089
2188
|
|
package/dist/web/app.js
CHANGED
|
@@ -97,10 +97,14 @@ function renderCanvasPlaceholder(kind) {
|
|
|
97
97
|
`</div>`;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
// First-run onboarding card (the 'empty' placeholder).
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
|
|
100
|
+
// First-run onboarding card (the 'empty' placeholder). The primary action is "Start the guided tour"
|
|
101
|
+
// — it copies a prompt the user pastes into their terminal AI to launch onboarding. No raw CLI command
|
|
102
|
+
// is shown: the AI builds the workflows (per the headline), so the card stays AI-native, not "go run
|
|
103
|
+
// this command yourself". No repeated brand mark (not a splash).
|
|
104
|
+
// The natural-language launch prompt the "Start the guided tour" button copies. Phrased to match the
|
|
105
|
+
// floless-app-onboarding skill's auto-trigger ("onboard me", "guided tour") so pasting it into Claude
|
|
106
|
+
// Code / Codex starts the tour. Plain language (not a slash command) = portable across terminal hosts.
|
|
107
|
+
const ONB_TOUR_PROMPT = 'Onboard me into floless.app and AWARE — give me the guided tour.';
|
|
104
108
|
function onboardingHtml() {
|
|
105
109
|
const step = (n, label, note) =>
|
|
106
110
|
`<li><span class="onb-num">${n}</span><span class="onb-step"><span class="onb-step-label">${label}</span> <span class="onb-step-note">${note}</span></span></li>`;
|
|
@@ -108,6 +112,11 @@ function onboardingHtml() {
|
|
|
108
112
|
`<div class="canvas-empty onboarding" data-kind="empty">` +
|
|
109
113
|
`<div class="onb-headline">Your terminal AI builds the workflows.<br>You run and inspect them here.</div>` +
|
|
110
114
|
`<div class="onb-sub">floless.app shows the canvas, triggers runs, and streams results. Compose in Claude Code or Codex; the browser is the window.</div>` +
|
|
115
|
+
`<div class="onb-tour">` +
|
|
116
|
+
`<span class="onb-tour-hint">New here? Copies a prompt — paste it into Claude Code or Codex.</span>` +
|
|
117
|
+
`<button class="onb-tour-btn" type="button" aria-label="Start the guided tour — copies the onboarding prompt to your clipboard"><span class="onb-tour-label">Start the guided tour</span><span class="onb-tour-ico" aria-hidden="true">⎘</span></button>` +
|
|
118
|
+
`<span class="sr-only onb-tour-live" aria-live="polite"></span>` +
|
|
119
|
+
`</div>` +
|
|
111
120
|
`<div class="onb-divider"></div>` +
|
|
112
121
|
`<ol class="onb-steps">` +
|
|
113
122
|
step(1, 'Pick a workflow', '— the picker, top right') +
|
|
@@ -115,26 +124,37 @@ function onboardingHtml() {
|
|
|
115
124
|
step(3, 'Compile', '— freeze the approved lock') +
|
|
116
125
|
step(4, 'Run', '— against the live host; results render here') +
|
|
117
126
|
`</ol>` +
|
|
118
|
-
`<div class="onb-
|
|
119
|
-
`<div class="onb-cmd"><code>${ONB_INSTALL_CMD.replace('<', '<').replace('>', '>')}</code>` +
|
|
120
|
-
`<button class="onb-copy" type="button" data-tip="Copy command" aria-label="Copy command">⎘</button></div>` +
|
|
127
|
+
`<div class="onb-foot">No workflows yet? Just ask your terminal AI to build one.</div>` +
|
|
121
128
|
`</div>`
|
|
122
129
|
);
|
|
123
130
|
}
|
|
124
131
|
function wireOnboardingCopy() {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
132
|
+
// The primary first-run action: copy the natural-language onboarding prompt so the user can paste it
|
|
133
|
+
// into their terminal AI to launch the guided tour (the floless-app-onboarding skill).
|
|
134
|
+
const tour = $topology.querySelector('.onb-tour-btn');
|
|
135
|
+
if (tour) {
|
|
136
|
+
const live = $topology.querySelector('.onb-tour-live');
|
|
137
|
+
const label = tour.querySelector('.onb-tour-label');
|
|
138
|
+
const ico = tour.querySelector('.onb-tour-ico');
|
|
139
|
+
tour.onclick = async () => {
|
|
140
|
+
try {
|
|
141
|
+
await navigator.clipboard.writeText(ONB_TOUR_PROMPT);
|
|
142
|
+
tour.disabled = true;
|
|
143
|
+
if (label) label.textContent = 'Copied';
|
|
144
|
+
if (ico) ico.textContent = '✓';
|
|
145
|
+
if (live) live.textContent = 'Copied to clipboard';
|
|
146
|
+
showToast('Copied — paste it into your terminal AI', 'ok');
|
|
147
|
+
setTimeout(() => {
|
|
148
|
+
if (label) label.textContent = 'Start the guided tour';
|
|
149
|
+
if (ico) ico.textContent = '⎘';
|
|
150
|
+
tour.disabled = false;
|
|
151
|
+
if (live) live.textContent = '';
|
|
152
|
+
}, 1600);
|
|
153
|
+
} catch {
|
|
154
|
+
showToast('Could not copy — select and paste the prompt manually.', 'warn');
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
138
158
|
}
|
|
139
159
|
|
|
140
160
|
function nodeIds(p) {
|
package/dist/web/aware.js
CHANGED
|
@@ -1477,17 +1477,31 @@
|
|
|
1477
1477
|
const r = await fetch('/api/update');
|
|
1478
1478
|
const d = await r.json();
|
|
1479
1479
|
if (r.ok && d.supported && d.updateAvailable && d.targetVersion) {
|
|
1480
|
+
const npm = d.channel === 'npm';
|
|
1480
1481
|
$appUpdate.textContent = '↑ Update to v' + d.targetVersion;
|
|
1481
|
-
$appUpdate.dataset.
|
|
1482
|
+
$appUpdate.dataset.channel = d.channel || 'desktop';
|
|
1483
|
+
$appUpdate.dataset.command = (npm && d.command) ? d.command : '';
|
|
1484
|
+
$appUpdate.dataset.tip = npm
|
|
1485
|
+
? 'A newer floless.app (v' + d.targetVersion + ') is on npm — click to copy the update command'
|
|
1486
|
+
: 'A newer floless.app is available — click to download and relaunch into v' + d.targetVersion;
|
|
1482
1487
|
$appUpdate.hidden = false;
|
|
1483
1488
|
} else {
|
|
1484
|
-
$appUpdate.hidden = true; // up-to-date /
|
|
1489
|
+
$appUpdate.hidden = true; // up-to-date / dev / registry-or-feed error → no tag
|
|
1485
1490
|
}
|
|
1486
1491
|
} catch { $appUpdate.hidden = true; }
|
|
1487
1492
|
}
|
|
1488
1493
|
if ($appUpdate) {
|
|
1489
1494
|
$appUpdate.onclick = async () => {
|
|
1490
|
-
|
|
1495
|
+
// npm channel can't self-apply (no Update.exe) — copy the command for the user's terminal.
|
|
1496
|
+
if ($appUpdate.dataset.channel === 'npm') {
|
|
1497
|
+
const cmd = $appUpdate.dataset.command || 'npm i -g @floless/app@latest';
|
|
1498
|
+
// Best-effort copy — fire-and-forget so the toast never blocks on clipboard permission.
|
|
1499
|
+
if (navigator.clipboard) navigator.clipboard.writeText(cmd).catch(() => {});
|
|
1500
|
+
showToast('Update in your terminal — ' + cmd, 'info');
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1503
|
+
// desktop (Velopack): one-click download + relaunch
|
|
1504
|
+
const v = $appUpdate.textContent.replace(/^[^0-9]*/, ''); // "↑ Update to v0.5.2" → "0.5.2"
|
|
1491
1505
|
if (!window.confirm('Download v' + v + ' and relaunch floless.app now?')) return;
|
|
1492
1506
|
$appUpdate.disabled = true;
|
|
1493
1507
|
$appUpdate.textContent = '↑ Updating…';
|
|
@@ -1507,6 +1521,61 @@
|
|
|
1507
1521
|
setInterval(refreshUpdate, 6 * 60 * 60 * 1000); // re-check every 6h so a long-running window notices
|
|
1508
1522
|
}
|
|
1509
1523
|
|
|
1524
|
+
// ── AWARE runtime upgrade tag ─────────────────────────────────────────────────
|
|
1525
|
+
// Surface aware-update.ts: poll GET /api/aware/update (on load + every 6h); when a
|
|
1526
|
+
// newer @aware-aeco/cli exists, show a footer pill → confirm → POST apply, which
|
|
1527
|
+
// reinstalls AWARE in place (no relaunch — the app stays open) and re-stamps the
|
|
1528
|
+
// version live. Mirrors the app self-update pill; differs in that success is silent
|
|
1529
|
+
// (the live version re-stamp IS the confirmation) and there is no relaunch.
|
|
1530
|
+
const $awareUpdate = document.getElementById('aware-update');
|
|
1531
|
+
// After a successful upgrade, briefly trust the upgraded version: a /api/health request that
|
|
1532
|
+
// was in flight before the reinstall finished could resolve with the OLD version and momentarily
|
|
1533
|
+
// revert the footer. The live re-stamp (health poll) honors this short settle window.
|
|
1534
|
+
let awareUpgradeFloor = null, awareUpgradeFloorUntil = 0;
|
|
1535
|
+
async function refreshAwareUpdate() {
|
|
1536
|
+
if (!$awareUpdate) return;
|
|
1537
|
+
try {
|
|
1538
|
+
const r = await fetch('/api/aware/update');
|
|
1539
|
+
const d = await r.json();
|
|
1540
|
+
if (r.ok && d.updateAvailable && d.targetVersion) {
|
|
1541
|
+
$awareUpdate.textContent = '↑ Upgrade AWARE to v' + d.targetVersion;
|
|
1542
|
+
$awareUpdate.dataset.target = d.targetVersion;
|
|
1543
|
+
$awareUpdate.dataset.tip = 'A newer AWARE runtime (v' + d.targetVersion + ') is available — click to install it in place (no relaunch)';
|
|
1544
|
+
$awareUpdate.hidden = false;
|
|
1545
|
+
} else {
|
|
1546
|
+
$awareUpdate.hidden = true; // up-to-date / absent / registry error → no pill
|
|
1547
|
+
}
|
|
1548
|
+
} catch { $awareUpdate.hidden = true; }
|
|
1549
|
+
}
|
|
1550
|
+
if ($awareUpdate) {
|
|
1551
|
+
$awareUpdate.onclick = async () => {
|
|
1552
|
+
const v = $awareUpdate.dataset.target || '';
|
|
1553
|
+
if (!window.confirm('Upgrade AWARE runtime to v' + v + '? This reinstalls the npm package in place — the app stays open and the version restamps automatically.')) return;
|
|
1554
|
+
$awareUpdate.disabled = true;
|
|
1555
|
+
$awareUpdate.textContent = '↑ Upgrading…';
|
|
1556
|
+
try {
|
|
1557
|
+
// Cap the wait so the pill never sticks disabled if the global npm install wedges.
|
|
1558
|
+
const r = await fetch('/api/aware/update/apply', { method: 'POST', headers: { 'content-type': 'application/json' }, signal: AbortSignal.timeout(120000) });
|
|
1559
|
+
const d = await r.json().catch(() => ({}));
|
|
1560
|
+
if (!r.ok || !d.ok) throw new Error(d.error || 'aware upgrade failed');
|
|
1561
|
+
// Success is silent: the pill disappears and the AWARE version re-stamps live.
|
|
1562
|
+
$awareUpdate.hidden = true;
|
|
1563
|
+
$awareUpdate.disabled = false;
|
|
1564
|
+
const wv = document.getElementById('aware-version');
|
|
1565
|
+
if (wv && d.version) { wv.textContent = 'AWARE ' + d.version; awareUpgradeFloor = d.version; awareUpgradeFloorUntil = Date.now() + 10000; }
|
|
1566
|
+
} catch (e) {
|
|
1567
|
+
$awareUpdate.disabled = false;
|
|
1568
|
+
const msg = (e && e.name === 'TimeoutError')
|
|
1569
|
+
? 'AWARE upgrade is taking a while — it may still be installing; the version updates when it finishes'
|
|
1570
|
+
: 'AWARE upgrade failed — ' + String((e && e.message) || e).slice(0, 80);
|
|
1571
|
+
showToast(msg, 'warn');
|
|
1572
|
+
refreshAwareUpdate(); // restore the pill text for retry
|
|
1573
|
+
}
|
|
1574
|
+
};
|
|
1575
|
+
refreshAwareUpdate();
|
|
1576
|
+
setInterval(refreshAwareUpdate, 6 * 60 * 60 * 1000); // re-check every 6h
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1510
1579
|
// ONE Run (the approved single-Run model). "▶ Run workflow" does a REAL run against
|
|
1511
1580
|
// the live host, using the app inputs. If the app has a report node, it drives
|
|
1512
1581
|
// the in-app HTML Viewer (renders + caches the returned HTML); otherwise it
|
|
@@ -1589,7 +1658,7 @@
|
|
|
1589
1658
|
// — that's the during-run feedback; pushTrace then refines each node to
|
|
1590
1659
|
// done/error as the (batched) trace arrives. Covers UI runs, report runs (the
|
|
1591
1660
|
// canvas paints behind the modal), and terminal-driven runs (trace-file).
|
|
1592
|
-
const NODE_STAT_LABEL = { running: '
|
|
1661
|
+
const NODE_STAT_LABEL = { running: 'running', done: '✓ done', error: '✗ failed' };
|
|
1593
1662
|
function setNodeStatus(nodeId, status) {
|
|
1594
1663
|
if (!nodeId) return;
|
|
1595
1664
|
const card = document.querySelector(`.agent-card[data-agent-id="${(window.CSS && CSS.escape) ? CSS.escape(nodeId) : nodeId}"]`);
|
|
@@ -2045,13 +2114,22 @@
|
|
|
2045
2114
|
// stale install or AWARE mismatch is self-diagnosable). appVersion is
|
|
2046
2115
|
// the real sq.version on an install (package.json only from source);
|
|
2047
2116
|
// awareVersion is the aware npm package this app drives.
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2117
|
+
// The app BUILD version is immutable at runtime → stamp once. The AWARE RUNTIME
|
|
2118
|
+
// version CAN change (an out-of-band `npm i -g`, or the in-app upgrade), so
|
|
2119
|
+
// re-stamp it whenever /api/health reports a different value — never show a
|
|
2120
|
+
// stale runtime version in the footer.
|
|
2121
|
+
const av = document.getElementById('app-version');
|
|
2122
|
+
if (av && h && h.appVersion && !shownVersion) { av.textContent = 'v' + h.appVersion; shownVersion = true; }
|
|
2123
|
+
const wv = document.getElementById('aware-version');
|
|
2124
|
+
if (wv && h && h.awareVersion) {
|
|
2125
|
+
const next = 'AWARE ' + h.awareVersion;
|
|
2126
|
+
// During the post-upgrade settle window, ignore a stale health value that disagrees
|
|
2127
|
+
// with the version we just upgraded to (a poll in flight during the reinstall).
|
|
2128
|
+
const settling = awareUpgradeFloor && Date.now() < awareUpgradeFloorUntil && h.awareVersion !== awareUpgradeFloor;
|
|
2129
|
+
if (!settling && wv.textContent !== next) wv.textContent = next;
|
|
2054
2130
|
}
|
|
2131
|
+
const sep = document.getElementById('ver-sep');
|
|
2132
|
+
if (sep && h && (h.appVersion || h.awareVersion)) sep.hidden = false;
|
|
2055
2133
|
// Bootstrap BACKSTOP: reconcile the setup overlay from cached health
|
|
2056
2134
|
// state in case an SSE event was missed. ready → finish the beat &
|
|
2057
2135
|
// dismiss; failed/installing/probing → (re)render that state.
|
package/dist/web/index.html
CHANGED
|
@@ -147,6 +147,8 @@
|
|
|
147
147
|
</div>
|
|
148
148
|
<span class="version-info">
|
|
149
149
|
<span class="aware-version" id="aware-version" data-tip="AWARE runtime version — the aware CLI/npm package this app drives"></span>
|
|
150
|
+
<button id="aware-update" class="app-update" hidden data-tip="An AWARE runtime upgrade is available — reinstalls in place, no relaunch"></button>
|
|
151
|
+
<span class="sep ver-sep" id="ver-sep" hidden>·</span>
|
|
150
152
|
<span class="app-version" id="app-version" data-tip="floless.app build serving this window (installed build, or the dev build when run from source)"></span>
|
|
151
153
|
<button id="app-update" class="app-update" hidden></button>
|
|
152
154
|
</span>
|
|
@@ -155,38 +157,38 @@
|
|
|
155
157
|
|
|
156
158
|
<div class="menu" id="menu" role="menu">
|
|
157
159
|
<button class="menu-item" data-action="open" role="menuitem">
|
|
158
|
-
<span class="menu-icon"
|
|
160
|
+
<span class="menu-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2"/></svg></span>
|
|
159
161
|
<span class="menu-label">Open</span>
|
|
160
162
|
<span class="menu-kbd">Ctrl+O</span>
|
|
161
163
|
</button>
|
|
162
164
|
<button class="menu-item" data-action="save" role="menuitem">
|
|
163
|
-
<span class="menu-icon"
|
|
165
|
+
<span class="menu-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z"/><path d="M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7"/><path d="M7 3v4a1 1 0 0 0 1 1h7"/></svg></span>
|
|
164
166
|
<span class="menu-label">Save inputs</span>
|
|
165
167
|
<span class="menu-kbd">Ctrl+S</span>
|
|
166
168
|
</button>
|
|
167
169
|
<div class="menu-divider"></div>
|
|
168
170
|
<button class="menu-item" data-action="find" role="menuitem">
|
|
169
|
-
<span class="menu-icon"
|
|
171
|
+
<span class="menu-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="m21 21-4.34-4.34"/><circle cx="11" cy="11" r="8"/></svg></span>
|
|
170
172
|
<span class="menu-label">Find on canvas</span>
|
|
171
173
|
<span class="menu-kbd">Ctrl+F</span>
|
|
172
174
|
</button>
|
|
173
175
|
<div class="menu-divider"></div>
|
|
174
176
|
<button class="menu-item" data-action="graft" role="menuitem">
|
|
175
|
-
<span class="menu-icon"
|
|
177
|
+
<span class="menu-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 22v-5"/><path d="M15 8V2"/><path d="M17 8a1 1 0 0 1 1 1v4a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1z"/><path d="M9 8V2"/></svg></span>
|
|
176
178
|
<span class="menu-label">Graft into agent</span>
|
|
177
179
|
</button>
|
|
178
180
|
<button class="menu-item" data-action="bake" id="menu-bake-item" role="menuitem">
|
|
179
|
-
<span class="menu-icon"
|
|
181
|
+
<span class="menu-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z"/><path d="M12 22V12"/><polyline points="3.29 7 12 12 20.71 7"/><path d="m7.5 4.27 9 5.15"/></svg></span>
|
|
180
182
|
<span class="menu-label" id="menu-bake-label">Bake into agent</span>
|
|
181
183
|
</button>
|
|
182
184
|
<div class="menu-divider"></div>
|
|
183
185
|
<button class="menu-item" data-action="integrations" role="menuitem">
|
|
184
|
-
<span class="menu-icon"
|
|
186
|
+
<span class="menu-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></span>
|
|
185
187
|
<span class="menu-label">Integrations</span>
|
|
186
188
|
<span class="menu-kbd">Ctrl+I</span>
|
|
187
189
|
</button>
|
|
188
190
|
<button class="menu-item" data-action="routines" role="menuitem">
|
|
189
|
-
<span class="menu-icon"
|
|
191
|
+
<span class="menu-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><line x1="10" x2="14" y1="2" y2="2"/><line x1="12" x2="15" y1="14" y2="11"/><circle cx="12" cy="14" r="8"/></svg></span>
|
|
190
192
|
<span class="menu-label">Routines</span>
|
|
191
193
|
</button>
|
|
192
194
|
<!-- Start-on-login + Theme are sibling machine preferences (no divider between
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@floless/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Thin localhost host for floless.app — serves web/ and shells the aware CLI. No engine, no LLM.",
|
|
6
6
|
"bin": {
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"app:restart": "node launch.mjs restart",
|
|
25
25
|
"build": "node build/bundle.mjs",
|
|
26
26
|
"build:exe": "node build/bundle.mjs && node build/make-sea.mjs",
|
|
27
|
+
"verify:skills": "node build/verify-shipped-skills.mjs",
|
|
27
28
|
"typecheck": "tsc --noEmit",
|
|
28
29
|
"prepack": "npm run build"
|
|
29
30
|
},
|