@floless/app 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/floless.mjs +13 -0
- package/dist/floless-server.cjs +56801 -0
- package/dist/skills/floless-app-bridge/SKILL.md +80 -0
- package/dist/skills/floless-app-routines/SKILL.md +168 -0
- package/dist/skills/floless-app-routines/references/routines-api.md +130 -0
- package/dist/skills/floless-app-workflows/SKILL.md +352 -0
- package/dist/skills/floless-app-workflows/references/dev-server-and-run-trace.md +119 -0
- package/dist/skills/floless-app-workflows/references/exec-contract.md +104 -0
- package/dist/web/app.css +2129 -0
- package/dist/web/app.js +1334 -0
- package/dist/web/apple-touch-icon.png +0 -0
- package/dist/web/aware.js +3274 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/favicon.svg +98 -0
- package/dist/web/index.html +484 -0
- package/launch.mjs +543 -0
- package/package.json +43 -0
- package/teardown.mjs +128 -0
package/teardown.mjs
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// teardown — the SHARED pure layer behind `floless-app uninstall` (Part A.5).
|
|
2
|
+
//
|
|
3
|
+
// Plain JS (.mjs) on purpose: it is imported by BOTH launch.mjs (the npm bin,
|
|
4
|
+
// plain JS) and main.ts (the SEA dispatcher, TS), AND by autostart.ts/protocol.ts
|
|
5
|
+
// for the registry-key constants — one source, no JS/TS boundary problem (Codex S1).
|
|
6
|
+
// It holds ONLY pure functions + constants: no process killing, no reg/npm calls,
|
|
7
|
+
// no I/O. The executors that perform real side effects live in launch.mjs / main.ts
|
|
8
|
+
// and are exercised by the human's real teardown test, never here.
|
|
9
|
+
//
|
|
10
|
+
// Shipped to npm (added to package.json `files`), so it must stay dependency-free.
|
|
11
|
+
|
|
12
|
+
// --- registry-key constants (single source — must match autostart.ts/protocol.ts) ---
|
|
13
|
+
// autostart.ts: RUN_KEY + VALUE_NAME='FloLess'; protocol.ts: BASE.
|
|
14
|
+
export const RUN_KEY = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run';
|
|
15
|
+
export const RUN_VALUE = 'FloLess';
|
|
16
|
+
export const PROTOCOL_KEY = 'HKCU\\Software\\Classes\\floless';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Decide what `floless-app uninstall` should do about the AWARE runtime.
|
|
20
|
+
*
|
|
21
|
+
* --purge → remove AWARE, never prompt.
|
|
22
|
+
* --keep-aware → keep AWARE, never prompt.
|
|
23
|
+
* (no flags) → prompt ONLY when a TTY is present; a non-TTY (Velopack
|
|
24
|
+
* Add/Remove, piped stdin) NEVER blocks → keep AWARE.
|
|
25
|
+
*
|
|
26
|
+
* @param {{ purge?: boolean, keepAware?: boolean, isTTY?: boolean }} opts
|
|
27
|
+
* @returns {{ removeAware: boolean, prompt: boolean }}
|
|
28
|
+
* @throws {Error} when both --purge and --keep-aware are given (conflicting).
|
|
29
|
+
*/
|
|
30
|
+
export function teardownDecision({ purge = false, keepAware = false, isTTY = false } = {}) {
|
|
31
|
+
if (purge && keepAware) {
|
|
32
|
+
throw new Error('conflicting flags: --purge and --keep-aware cannot be combined');
|
|
33
|
+
}
|
|
34
|
+
if (purge) return { removeAware: true, prompt: false };
|
|
35
|
+
if (keepAware) return { removeAware: false, prompt: false };
|
|
36
|
+
// No flags: ask iff interactive; otherwise keep AWARE and never block.
|
|
37
|
+
return { removeAware: false, prompt: Boolean(isTTY) };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// The global npm package id for the AWARE runtime. Single source so the uninstall
|
|
41
|
+
// shell command and the JSON-verify check can't drift.
|
|
42
|
+
export const AWARE_PKG = '@aware-aeco/cli';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Decide whether AWARE is STILL installed from the output of
|
|
46
|
+
* `npm ls -g @aware-aeco/cli --depth=0 --json`. The package, when present, appears as
|
|
47
|
+
* a key under the top-level `dependencies` object; when absent, `dependencies` is
|
|
48
|
+
* missing or empty. Parsing this is more reliable than `npm ls`'s exit code, which
|
|
49
|
+
* varies across npm versions for a missing global (review Fix 3 / Codex S4).
|
|
50
|
+
*
|
|
51
|
+
* Defensive: any empty/undefined/unparseable input, or a non-object `dependencies`,
|
|
52
|
+
* is treated as ABSENT (removed) — a flaky `npm ls` must never raise a false
|
|
53
|
+
* "still installed" alarm.
|
|
54
|
+
*
|
|
55
|
+
* @param {string | undefined | null} npmLsJson raw stdout of the `npm ls … --json` call
|
|
56
|
+
* @returns {boolean} true iff the AWARE package is listed under dependencies
|
|
57
|
+
*/
|
|
58
|
+
export function awareIsPresent(npmLsJson) {
|
|
59
|
+
let parsed;
|
|
60
|
+
try { parsed = JSON.parse(npmLsJson || '{}'); } catch { return false; }
|
|
61
|
+
const deps = parsed && typeof parsed === 'object' ? parsed.dependencies : null;
|
|
62
|
+
if (!deps || typeof deps !== 'object') return false;
|
|
63
|
+
return Object.prototype.hasOwnProperty.call(deps, AWARE_PKG);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// A process is a floless.app supervisor iff its command line both names THIS app's
|
|
67
|
+
// exe and carries the supervise action. We match `--supervise` or a bare ` supervise`
|
|
68
|
+
// token (the exe-channel action) on a word boundary, so `--serve` /
|
|
69
|
+
// `--veloapp-uninstall` / a substring like `supervised` never qualify.
|
|
70
|
+
const SUPERVISE_TOKEN = /(?:^|\s)(?:--)?supervise(?:\s|$)/i;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Select supervisor PIDs to kill during teardown, with the self-kill guard baked in.
|
|
74
|
+
*
|
|
75
|
+
* A proc qualifies iff its `cmd` (its full command line):
|
|
76
|
+
* - contains AT LEAST ONE of the `exeMatch` candidates (this app's exe path),
|
|
77
|
+
* case-insensitively, AND
|
|
78
|
+
* - carries the supervise action (`--supervise` / ` supervise`), AND
|
|
79
|
+
* - its pid !== selfPid (NEVER kill the running uninstall process — Codex self-kill), AND
|
|
80
|
+
* - when `scriptMatch` is provided (non-empty), ALSO contains `scriptMatch`
|
|
81
|
+
* case-insensitively (review #1).
|
|
82
|
+
*
|
|
83
|
+
* `exeMatch` accepts EITHER a single string OR a string array (added 2026-05-30 for
|
|
84
|
+
* the Claude MSIX container case): inside the container `process.execPath` is the
|
|
85
|
+
* bind-link VIRTUAL path while a supervisor launched at logon — via a Run key /
|
|
86
|
+
* Scheduled Task registered with the REAL un-virtualized path — runs with the REAL
|
|
87
|
+
* path in its command line. Passing `[virtualPath, realPath]` matches BOTH so the
|
|
88
|
+
* in-container uninstaller can still find it. A single-string call still works
|
|
89
|
+
* unchanged for non-container installs (where the two paths are identical).
|
|
90
|
+
*
|
|
91
|
+
* The `scriptMatch` arg exists for the npm channel: there `exeMatch` is the generic
|
|
92
|
+
* `node.exe`, so without it the predicate could match ANY `node … supervise` process
|
|
93
|
+
* (e.g. an unrelated third-party watchdog). Passing the launcher script (launch.mjs)
|
|
94
|
+
* as `scriptMatch` narrows it to OUR supervisor. The packaged channel uses a unique
|
|
95
|
+
* `FlolessApp.exe` and passes no scriptMatch — behavior is unchanged there.
|
|
96
|
+
*
|
|
97
|
+
* This deliberately EXCLUDES the current `--veloapp-uninstall` proc (same exe, wrong
|
|
98
|
+
* action) and unrelated tools whose command line merely contains "--supervise"
|
|
99
|
+
* (right action, wrong exe).
|
|
100
|
+
*
|
|
101
|
+
* @param {Array<{ pid: number, cmd: string }>} procs enumerated processes
|
|
102
|
+
* @param {number} selfPid process.pid of the running uninstall — always excluded
|
|
103
|
+
* @param {string | string[]} exeMatch this app's exe path(s) to match in each cmd line;
|
|
104
|
+
* pass an array to match a supervisor that may have been launched at either of two
|
|
105
|
+
* equivalent paths (the MSIX virtual + real un-virtualized path)
|
|
106
|
+
* @param {string} [scriptMatch] optional launcher-script substring; when non-empty,
|
|
107
|
+
* the cmd must ALSO contain it (case-insensitively). Omitted/empty → no narrowing.
|
|
108
|
+
* @returns {number[]} pids to taskkill (caller does the killing; this is pure)
|
|
109
|
+
*/
|
|
110
|
+
export function supervisorPidsToKill(procs, selfPid, exeMatch, scriptMatch) {
|
|
111
|
+
if (!Array.isArray(procs) || !exeMatch) return [];
|
|
112
|
+
const needles = (Array.isArray(exeMatch) ? exeMatch : [exeMatch])
|
|
113
|
+
.filter((s) => typeof s === 'string' && s.length > 0)
|
|
114
|
+
.map((s) => s.toLowerCase());
|
|
115
|
+
if (needles.length === 0) return [];
|
|
116
|
+
const scriptNeedle = scriptMatch ? String(scriptMatch).toLowerCase() : '';
|
|
117
|
+
return procs
|
|
118
|
+
.filter((p) => {
|
|
119
|
+
if (!p || typeof p.cmd !== 'string') return false;
|
|
120
|
+
if (p.pid === selfPid) return false; // never self-kill
|
|
121
|
+
const cmd = p.cmd;
|
|
122
|
+
const lower = cmd.toLowerCase();
|
|
123
|
+
if (!needles.some((n) => lower.includes(n)) || !SUPERVISE_TOKEN.test(cmd)) return false;
|
|
124
|
+
if (scriptNeedle && !lower.includes(scriptNeedle)) return false; // npm-channel narrowing
|
|
125
|
+
return true;
|
|
126
|
+
})
|
|
127
|
+
.map((p) => p.pid);
|
|
128
|
+
}
|