@wipcomputer/wip-ldm-os 0.4.85-alpha.9 → 0.4.86

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.
Files changed (56) hide show
  1. package/README.md +22 -2
  2. package/SKILL.md +137 -15
  3. package/bin/ldm.js +608 -75
  4. package/lib/deploy.mjs +36 -1
  5. package/lib/registry-migrations.mjs +296 -0
  6. package/package.json +20 -3
  7. package/scripts/test-boot-dir-truth.mjs +158 -0
  8. package/scripts/test-boot-hook-registration.mjs +136 -0
  9. package/scripts/test-boot-payload-trim.mjs +200 -0
  10. package/scripts/test-crc-agentid-tenant-boundary.mjs +2 -2
  11. package/scripts/test-crc-e2ee-key-persistence.mjs +150 -0
  12. package/scripts/test-crc-e2ee-session-route.mjs +9 -2
  13. package/scripts/test-crc-pair-login-flow.mjs +76 -1
  14. package/scripts/test-crc-pair-relink-audit-and-rotation.mjs +164 -0
  15. package/scripts/test-crc-websocket-abuse-limits.mjs +128 -0
  16. package/scripts/test-deploy-hook-ownership.mjs +160 -0
  17. package/scripts/test-doctor-hook-dedupe.mjs +188 -0
  18. package/scripts/test-install-prompt-policy.mjs +84 -0
  19. package/scripts/test-installer-target-self-update.mjs +131 -0
  20. package/scripts/test-kaleidoscope-onboarding-copy.mjs +170 -0
  21. package/scripts/test-kaleidoscope-public-stats-baseline.mjs +129 -0
  22. package/scripts/test-kaleidoscope-qr-authenticator-confirmation.mjs +89 -0
  23. package/scripts/test-ldm-status-concurrency.mjs +118 -0
  24. package/scripts/test-ldm-status-timeout.mjs +96 -0
  25. package/scripts/test-legacy-npm-sources-migration.mjs +460 -0
  26. package/scripts/test-readme-install-prompt.mjs +66 -0
  27. package/shared/boot/boot-config.json +18 -8
  28. package/shared/docs/dev-guide-wipcomputerinc.md.tmpl +5 -4
  29. package/shared/rules/security.md +4 -0
  30. package/shared/templates/install-prompt.md +20 -2
  31. package/src/boot/README.md +24 -1
  32. package/src/boot/boot-config.json +18 -8
  33. package/src/boot/boot-hook.mjs +118 -28
  34. package/src/boot/installer.mjs +33 -10
  35. package/src/hosted-mcp/.env.example +4 -0
  36. package/src/hosted-mcp/README.md +37 -0
  37. package/src/hosted-mcp/app/footer.js +2 -2
  38. package/src/hosted-mcp/app/kaleidoscope-login.html +489 -42
  39. package/src/hosted-mcp/app/pair.html +21 -3
  40. package/src/hosted-mcp/app/wip-logo.png +0 -0
  41. package/src/hosted-mcp/codex-relay-e2ee-registry.mjs +208 -0
  42. package/src/hosted-mcp/codex-relay-ws-abuse-limits.mjs +140 -0
  43. package/src/hosted-mcp/demo/footer.js +2 -2
  44. package/src/hosted-mcp/demo/index.html +166 -44
  45. package/src/hosted-mcp/demo/login.html +87 -23
  46. package/src/hosted-mcp/demo/privacy.html +4 -214
  47. package/src/hosted-mcp/demo/tos.html +4 -189
  48. package/src/hosted-mcp/deploy.sh +2 -0
  49. package/src/hosted-mcp/docs/self-host.md +268 -0
  50. package/src/hosted-mcp/legal/internet-services/kaleidoscope/index.html +257 -0
  51. package/src/hosted-mcp/legal/internet-services/terms/site.html +224 -168
  52. package/src/hosted-mcp/legal/legal-footer.js +75 -0
  53. package/src/hosted-mcp/legal/legal.css +166 -0
  54. package/src/hosted-mcp/legal/privacy/en-ww/index.html +4 -221
  55. package/src/hosted-mcp/legal/privacy/index.html +253 -0
  56. package/src/hosted-mcp/server.mjs +920 -74
@@ -0,0 +1,84 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const repoRoot = fileURLToPath(new URL("..", import.meta.url));
6
+
7
+ const files = [
8
+ "README.md",
9
+ "SKILL.md",
10
+ "shared/templates/install-prompt.md",
11
+ ];
12
+
13
+ const contents = Object.fromEntries(
14
+ files.map((file) => [file, readFileSync(join(repoRoot, file), "utf8")]),
15
+ );
16
+
17
+ const failures = [];
18
+
19
+ for (const file of ["README.md", "shared/templates/install-prompt.md"]) {
20
+ const text = contents[file];
21
+ for (const phrase of [
22
+ "Use the install document and live local checks as the source of truth.",
23
+ "Do not search memory or prior notes for this install. Do not pre-load context from `MEMORY.md`, `crystal_search`, local skill dev guides, or other local memory before fetching the install document.",
24
+ "Do not run GitHub commands during this install flow. Do not run or request approval for `gh release`, `gh api`, or `gh search`.",
25
+ "If release notes are not available from local or npm metadata, say that and do not fetch them from GitHub.",
26
+ "If installed: run `ldm status`",
27
+ "check available npm tracks from the install document",
28
+ "If yes to dry run, use the selected track's dry-run path from the install document.",
29
+ "If I say install, use the selected track's install path from the install document, then run `ldm doctor`.",
30
+ "install the CLI first using the selected track's bootstrap command from the install document",
31
+ "Then run:\n`ldm init --dry-run`",
32
+ "If I say install, run:\n`ldm init`",
33
+ ]) {
34
+ if (!text.includes(phrase)) {
35
+ failures.push(`${file} missing install prompt phrase: ${phrase}`);
36
+ }
37
+ }
38
+ if (text.includes("If it is, run ldm install --dry-run")) {
39
+ failures.push(`${file} still tells installed users to start with ldm install --dry-run`);
40
+ }
41
+ }
42
+
43
+ const skill = contents["SKILL.md"];
44
+ for (const phrase of [
45
+ "Memory policy for install flows: do not consult `MEMORY.md`, do not run `crystal_search`, and do not search prior notes when this skill is invoked, including in any parallel or batched exploration step.",
46
+ "The only context sources for this install flow are `https://wip.computer/install/wip-ldm-os.txt` and the live local commands that document prescribes.",
47
+ "Read that document and run those commands. Do not pre-load other context.",
48
+ "Do not run GitHub commands during the install-state flow.",
49
+ "Do not run or request approval for `gh release list`, `gh release view`, `gh api repos/*`, `gh search`, or any other GitHub query unless the user explicitly asks for release notes.",
50
+ "npm view @wipcomputer/wip-ldm-os dist-tags --json",
51
+ "The README prompt should stay short. This install document owns the detailed track rules.",
52
+ "stable/current/latest: `ldm install --dry-run`",
53
+ "beta/latest beta: `ldm install --beta --dry-run`",
54
+ "alpha/latest alpha: `ldm install --alpha --dry-run`",
55
+ "beta/latest beta: `npm install -g @wipcomputer/wip-ldm-os@beta`",
56
+ "alpha/latest alpha: `npm install -g @wipcomputer/wip-ldm-os@alpha`",
57
+ "Use the output of `ldm status`, installed package metadata, and npm metadata.",
58
+ "Do not use GitHub commands here.",
59
+ "If npm metadata for a package does not include release notes:",
60
+ "Say \"release notes not available from local metadata.\"",
61
+ "Do not infer release-note content from package descriptions, commit messages, or repo READMEs.",
62
+ "An approval dialog is not a user request.",
63
+ ]) {
64
+ if (!skill.includes(phrase)) {
65
+ failures.push(`SKILL.md missing install policy phrase: ${phrase}`);
66
+ }
67
+ }
68
+
69
+ if (/gh release (list|view) --repo/.test(skill)) {
70
+ failures.push("SKILL.md still includes concrete gh release commands in the install flow");
71
+ }
72
+
73
+ const temporalMemoryPolicyPhrase = "your first action is " + "to fetch";
74
+ if (skill.includes(temporalMemoryPolicyPhrase)) {
75
+ failures.push("SKILL.md still uses temporal first-action memory-policy phrasing");
76
+ }
77
+
78
+ if (failures.length > 0) {
79
+ console.error("install prompt policy checks failed:");
80
+ for (const failure of failures) console.error(` - ${failure}`);
81
+ process.exit(1);
82
+ }
83
+
84
+ console.log("install-prompt-policy: LDM OS prompt and install doc agree");
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env node
2
+ import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { dirname, join } from 'node:path';
5
+ import { spawnSync } from 'node:child_process';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ const root = dirname(dirname(fileURLToPath(import.meta.url)));
9
+ const cli = readFileSync(join(root, 'bin', 'ldm.js'), 'utf8');
10
+
11
+ const helperName = 'function maybeSelfUpdateLdmCliBeforeInstall()';
12
+ const helperIdx = cli.indexOf(helperName);
13
+ if (helperIdx === -1) {
14
+ throw new Error('Missing shared LDM OS self-update preflight helper');
15
+ }
16
+
17
+ const helperBlock = cli.slice(helperIdx, cli.indexOf('// ── Dead backup trigger cleanup', helperIdx));
18
+ if (!helperBlock.includes('Dry run only: continuing with v${PKG_VERSION}.')) {
19
+ throw new Error('Self-update helper must warn on dry run without updating');
20
+ }
21
+
22
+ if (!helperBlock.includes("execSync(`npm install -g @wipcomputer/wip-ldm-os@${latest}`")) {
23
+ throw new Error('Self-update helper must update LDM OS before real installs');
24
+ }
25
+
26
+ if (!helperBlock.includes("spawnSync('ldm'")) {
27
+ throw new Error('Self-update helper must re-run the original install command without shell joining args');
28
+ }
29
+
30
+ if (helperBlock.includes('process.argv.slice(2).join')) {
31
+ throw new Error('Self-update helper must preserve argv boundaries when re-running install');
32
+ }
33
+
34
+ const cmdInstallIdx = cli.indexOf('async function cmdInstall()');
35
+ const lockIdx = cli.indexOf('acquireInstallLock()', cmdInstallIdx);
36
+ const initIdx = cli.indexOf('LDM OS not initialized. Running init first', cmdInstallIdx);
37
+ const targetIdx = cli.indexOf('// Find the target (skip flags)', cmdInstallIdx);
38
+ const preflightCallIdx = cli.indexOf('maybeSelfUpdateLdmCliBeforeInstall();', cmdInstallIdx);
39
+ if (cmdInstallIdx === -1 || targetIdx === -1 || preflightCallIdx === -1) {
40
+ throw new Error('Could not find cmdInstall self-update placement');
41
+ }
42
+
43
+ if (preflightCallIdx > lockIdx || preflightCallIdx > initIdx) {
44
+ throw new Error('Self-update preflight must run before lock acquisition and init work');
45
+ }
46
+
47
+ if (preflightCallIdx > targetIdx) {
48
+ throw new Error('Self-update preflight must run before target resolution so app installs are covered');
49
+ }
50
+
51
+ const catalogIdx = cli.indexOf('async function cmdInstallCatalog()');
52
+ const oldCatalogBlock = cli.indexOf('Self-update: check if CLI itself is outdated', catalogIdx);
53
+ const autoDetectIdx = cli.indexOf('autoDetectExtensions();', catalogIdx);
54
+ if (oldCatalogBlock !== -1 && oldCatalogBlock < autoDetectIdx) {
55
+ throw new Error('Catalog install should not own the only self-update block');
56
+ }
57
+
58
+ const tempRoot = mkdtempSync(join(tmpdir(), 'ldm-target-self-update-'));
59
+ try {
60
+ const home = join(tempRoot, 'home');
61
+ const fakeBin = join(tempRoot, 'bin');
62
+ const target = join(tempRoot, 'target skill with spaces');
63
+
64
+ mkdirSync(join(home, '.ldm'), { recursive: true });
65
+ writeFileSync(join(home, '.ldm', 'version.json'), JSON.stringify({
66
+ version: '0.0.0',
67
+ installed: new Date().toISOString(),
68
+ updated: new Date().toISOString(),
69
+ }, null, 2) + '\n');
70
+
71
+ mkdirSync(fakeBin, { recursive: true });
72
+ const fakeNpm = join(fakeBin, 'npm');
73
+ writeFileSync(fakeNpm, `#!/usr/bin/env bash
74
+ if [ "$1" = "view" ] && [ "$2" = "@wipcomputer/wip-ldm-os" ] && [ "$3" = "dist-tags.alpha" ]; then
75
+ echo "99.0.0-alpha.1"
76
+ exit 0
77
+ fi
78
+ echo "unexpected npm command: $*" >&2
79
+ exit 64
80
+ `);
81
+ chmodSync(fakeNpm, 0o755);
82
+
83
+ mkdirSync(target, { recursive: true });
84
+ writeFileSync(join(target, 'SKILL.md'), `---
85
+ name: test-target-skill
86
+ description: Test target skill for installer self-update dry-run checks.
87
+ ---
88
+
89
+ # Test Target Skill
90
+ `);
91
+
92
+ const result = spawnSync(process.execPath, [
93
+ join(root, 'bin', 'ldm.js'),
94
+ 'install',
95
+ '--alpha',
96
+ '--dry-run',
97
+ target,
98
+ ], {
99
+ cwd: root,
100
+ encoding: 'utf8',
101
+ env: {
102
+ ...process.env,
103
+ HOME: home,
104
+ PATH: `${fakeBin}:${process.env.PATH || ''}`,
105
+ },
106
+ });
107
+
108
+ if (result.status !== 0) {
109
+ throw new Error(`Runtime dry-run exited ${result.status}\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`);
110
+ }
111
+
112
+ if (!result.stdout.includes('LDM OS CLI v')) {
113
+ throw new Error(`Runtime dry-run did not print the LDM OS skew warning\nstdout:\n${result.stdout}`);
114
+ }
115
+
116
+ if (!result.stdout.includes('-> v99.0.0-alpha.1 (alpha track) is available.')) {
117
+ throw new Error(`Runtime dry-run did not include the selected alpha track version\nstdout:\n${result.stdout}`);
118
+ }
119
+
120
+ if (!result.stdout.includes('Dry run only: continuing with v')) {
121
+ throw new Error(`Runtime dry-run did not say it would continue without updating\nstdout:\n${result.stdout}`);
122
+ }
123
+
124
+ if (!result.stdout.includes('Installing: target skill with spaces (dry run)')) {
125
+ throw new Error(`Runtime dry-run did not continue to the targeted install preview\nstdout:\n${result.stdout}`);
126
+ }
127
+ } finally {
128
+ rmSync(tempRoot, { recursive: true, force: true });
129
+ }
130
+
131
+ console.log('targeted install self-update regression checks passed');
@@ -0,0 +1,170 @@
1
+ import { readFileSync } from "node:fs";
2
+ import assert from "node:assert/strict";
3
+
4
+ const demo = readFileSync("src/hosted-mcp/demo/index.html", "utf8");
5
+ const server = readFileSync("src/hosted-mcp/server.mjs", "utf8");
6
+
7
+ function functionBody(source, name) {
8
+ const start = source.indexOf("async function " + name + "(");
9
+ assert.notEqual(start, -1, "missing function " + name);
10
+ const open = source.indexOf("{", start);
11
+ let depth = 0;
12
+ for (let i = open; i < source.length; i++) {
13
+ if (source[i] === "{") depth += 1;
14
+ if (source[i] === "}") depth -= 1;
15
+ if (depth === 0) return source.slice(open + 1, i);
16
+ }
17
+ throw new Error("unterminated function " + name);
18
+ }
19
+
20
+ function functionSource(source, name) {
21
+ const start = source.indexOf("function " + name + "(");
22
+ assert.notEqual(start, -1, "missing function " + name);
23
+ const open = source.indexOf("{", start);
24
+ let depth = 0;
25
+ for (let i = open; i < source.length; i++) {
26
+ if (source[i] === "{") depth += 1;
27
+ if (source[i] === "}") depth -= 1;
28
+ if (depth === 0) return source.slice(start, i + 1);
29
+ }
30
+ throw new Error("unterminated function " + name);
31
+ }
32
+
33
+ const noThanks = functionBody(demo, "showNoThanksFlow");
34
+ const returningNoThanks = functionBody(demo, "showReturningUserNoThanksFlow");
35
+ const startDemo = functionBody(demo, "startDemo");
36
+ const authFlow = functionBody(demo, "doFaceIDAuth");
37
+ const imageFlow = functionBody(demo, "generateKaleidoscope");
38
+ const oldOutro = functionBody(demo, "showOutro");
39
+ const approvedOutro = functionBody(demo, "showApprovedOutro");
40
+ const returningOutro = functionBody(demo, "showReturningUserOutro");
41
+
42
+ assert.match(startDemo, /Hi, I'm L\\u0113sa\. Welcome to Kaleidoscope\./);
43
+ assert.match(startDemo, /You just created an account with a passkey\. The passkey lives on your phone\./);
44
+ assert.match(startDemo, /Going forward, you can use your phone to log into any Work in Progress Computer service\./);
45
+ assert.match(startDemo, /Anytime I need your permission to do something, I'll ask, and you authorize with your fingerprint or face\. No passwords\. Ever\./);
46
+ assert.match(startDemo, /Hi, " \+ currentAccountLabel\(\) \+ "\. Welcome back to Kaleidoscope\./);
47
+ assert.match(startDemo, /Do you want to try giving me permission with your device to create something that costs money\?/);
48
+ assert.doesNotMatch(startDemo, /getWalletSummary\(\)/);
49
+ assert.doesNotMatch(startDemo, /Your balance is " \+ wallet\.balance/);
50
+ assert.doesNotMatch(startDemo, /Your account is a key on your device, not an email\./);
51
+ assert.doesNotMatch(startDemo, /You just logged in with your passkey/);
52
+ assert.doesNotMatch(startDemo, /And I can use it too/);
53
+
54
+ assert.match(demo, /I have a wallet with " \+ wallet\.balance \+ "\. Do I have your permission to spend " \+ wallet\.cost/);
55
+ assert.match(demo, /addMessage\('Authorizing', 'user'\)/);
56
+ assert.doesNotMatch(demo, /addMessage\('Authorized', 'user'\)/);
57
+ assert.doesNotMatch(demo, /This is the end of the demo\. Tap the icon to start over\./);
58
+ assert.doesNotMatch(demo, /open wip\.computer\/demo/);
59
+
60
+ assert.match(noThanks, /No problem\. Let me show you something beautiful\./);
61
+ assert.match(noThanks, /IS_RETURNING_USER_FLOW/);
62
+ assert.match(noThanks, /showReturningUserNoThanksFlow\(\)/);
63
+ assert.match(noThanks, /generateKaleidoscope\(false, \{ showWalletReceipt: false, showOutro: false \}\)/);
64
+ assert.match(noThanks, /showApprovedOutro\(\)/);
65
+ assert.doesNotMatch(noThanks, /\/demo\/api\/imagine/);
66
+ assert.doesNotMatch(noThanks, /fallback\.jpg/);
67
+ assert.doesNotMatch(noThanks, /Cost:/);
68
+ assert.doesNotMatch(noThanks, /Balance:/);
69
+
70
+ assert.match(returningNoThanks, /No problem\. You can come back anytime\./);
71
+ assert.match(returningNoThanks, /At Work in Progress Computer, we are building the future of AI and human interaction\./);
72
+ assert.match(returningNoThanks, /https:\/\/x\.com\/wipcomputer/);
73
+ assert.match(returningNoThanks, /Made in California by WIP Computer, Inc\. Learning Dreaming Machines\./);
74
+ assert.doesNotMatch(returningNoThanks, /generateKaleidoscope/);
75
+ assert.doesNotMatch(returningNoThanks, /\/demo\/api\/imagine/);
76
+ assert.doesNotMatch(returningNoThanks, /Cost:/);
77
+ assert.doesNotMatch(returningNoThanks, /Balance:/);
78
+ assert.doesNotMatch(returningNoThanks, /passkey/i);
79
+
80
+ assert.match(imageFlow, /var showWalletReceipt = options\.showWalletReceipt !== false;/);
81
+ assert.match(imageFlow, /if \(showWalletReceipt\) \{/);
82
+ assert.match(imageFlow, /body: JSON\.stringify\(\{ prompt: prompt \}\)/);
83
+ assert.doesNotMatch(imageFlow, /mode:/);
84
+ assert.doesNotMatch(server, /no_thanks/i);
85
+ assert.doesNotMatch(server, /Free onboarding generation/);
86
+
87
+ assert.match(oldOutro, /IS_RETURNING_USER_FLOW/);
88
+ assert.match(oldOutro, /showReturningUserOutro\(\)/);
89
+ assert.match(oldOutro, /showApprovedOutro\(\)/);
90
+ assert.match(approvedOutro, /At Work in Progress Computer, we are building the future of AI and human interaction\./);
91
+ assert.match(approvedOutro, /https:\/\/wip\.computer\/demo/);
92
+ assert.match(approvedOutro, /https:\/\/x\.com\/wipcomputer/);
93
+ assert.match(approvedOutro, /Your passkey will keep working after you leave\./);
94
+ assert.doesNotMatch(approvedOutro, /Your account stays active/);
95
+ assert.doesNotMatch(approvedOutro, /When you come back/);
96
+
97
+ assert.match(returningOutro, /At Work in Progress Computer, we are building the future of AI and human interaction\./);
98
+ assert.match(returningOutro, /We believe permission is a conversation\. Your AI asks\. You decide\. One glance, one tap\./);
99
+ assert.match(returningOutro, /https:\/\/x\.com\/wipcomputer/);
100
+ assert.match(returningOutro, /Made in California by WIP Computer, Inc\. Learning Dreaming Machines\./);
101
+ assert.doesNotMatch(returningOutro, /https:\/\/wip\.computer\/demo/);
102
+ assert.doesNotMatch(returningOutro, /log out/);
103
+ assert.doesNotMatch(returningOutro, /Your passkey will keep working after you leave\./);
104
+ assert.doesNotMatch(returningOutro, /Your account stays active/);
105
+ assert.doesNotMatch(returningOutro, /When you come back/);
106
+
107
+ assert.match(authFlow, /That passkey belongs to a different account/);
108
+ assert.match(authFlow, /isSameAccountAuthorization\(result, activeToken, activeTenant\)/);
109
+ assert.doesNotMatch(authFlow, /result\.tenantId !== activeTenant/);
110
+
111
+ const { canonicalAccountId, isSameAccountAuthorization } = Function(
112
+ functionSource(demo, "canonicalAccountId") + "\n"
113
+ + functionSource(demo, "isSameAccountAuthorization") + "\n"
114
+ + "return { canonicalAccountId, isSameAccountAuthorization };"
115
+ )();
116
+
117
+ assert.equal(canonicalAccountId("acct:parker-smoke-test"), "parker-smoke-test");
118
+ assert.equal(canonicalAccountId("parker-smoke-test"), "parker-smoke-test");
119
+ assert.equal(canonicalAccountId(" key:abc "), "key:abc");
120
+ assert.equal(canonicalAccountId(""), null);
121
+ assert.equal(canonicalAccountId(null), null);
122
+
123
+ assert.equal(
124
+ isSameAccountAuthorization({ tenantId: "parker-smoke-test", apiKey: "different-token" }, "active-token", "acct:parker-smoke-test"),
125
+ true,
126
+ );
127
+ assert.equal(
128
+ isSameAccountAuthorization({ tenantId: "acct:parker-smoke-test", apiKey: "different-token" }, "active-token", "parker-smoke-test"),
129
+ true,
130
+ );
131
+ assert.equal(
132
+ isSameAccountAuthorization({ tenantId: "acct:other-account", apiKey: "active-token" }, "active-token", "acct:parker-smoke-test"),
133
+ false,
134
+ );
135
+ assert.equal(
136
+ isSameAccountAuthorization({ apiKey: "active-token" }, "active-token", null),
137
+ true,
138
+ );
139
+ assert.equal(
140
+ isSameAccountAuthorization({ apiKey: "other-token" }, "active-token", null),
141
+ false,
142
+ );
143
+
144
+ assert.match(demo, /id="kscopeIcon" type="button" onclick="logoutAndSignBackIn\(\)"/);
145
+ assert.match(demo, /sessionStorage\.removeItem\('lesa-token'\)/);
146
+ assert.match(demo, /window\.location\.href = '\/login\?next=\/demo'/);
147
+
148
+ assert.match(server, /const IMAGE_COST_CENTS = 1;/);
149
+ assert.match(server, /const INITIAL_BALANCE_CENTS = 1000;/);
150
+ assert.match(server, /const DEMO_WALLET_RESET_MARKER_FILE = join/);
151
+ assert.match(server, /\.demo-wallet-reset-v0-4-87\.json/);
152
+ assert.match(server, /function walletUserIdForAgent\(agentId\)/);
153
+ assert.match(server, /agentId\.startsWith\("acct:"\) \? agentId\.slice\("acct:"\.length\) : agentId/);
154
+ assert.match(server, /function resetAndNormalizeWalletFileEntries\(wallets\)/);
155
+ assert.match(server, /normalizedWallets\[walletUserIdForAgent\(agentId\)\] = INITIAL_BALANCE_CENTS/);
156
+ assert.match(server, /async function resetExistingDemoWalletsToStarterBalanceOnce\(\)/);
157
+ assert.match(server, /await prisma\.wallet\.updateMany\(\{ data: \{ balance: INITIAL_BALANCE_CENTS \} \}\)/);
158
+ assert.match(server, /saveWalletsToFile\(normalizedWalletFile\.wallets\)/);
159
+ assert.match(server, /prismaCount: prismaResetCount/);
160
+ assert.match(server, /jsonCount: jsonResetCount/);
161
+ assert.match(server, /const walletUserId = walletUserIdForAgent\(agentId\);/);
162
+ assert.match(server, /where: \{ userId: walletUserId \}/);
163
+ assert.match(server, /data: \{ userId: walletUserId, balance: INITIAL_BALANCE_CENTS \}/);
164
+ assert.match(server, /return w\[walletUserId\] !== undefined \? w\[walletUserId\] : INITIAL_BALANCE_CENTS/);
165
+ assert.match(server, /if \(w\[walletUserId\] === undefined\) w\[walletUserId\] = INITIAL_BALANCE_CENTS/);
166
+ assert.match(server, /w\[walletUserId\] = Math\.max\(0, w\[walletUserId\] - cents\)/);
167
+ assert.match(server, /return w\[walletUserId\]/);
168
+ assert.match(server, /await resetExistingDemoWalletsToStarterBalanceOnce\(\);/);
169
+
170
+ console.log("Kaleidoscope onboarding copy checks passed");
@@ -0,0 +1,129 @@
1
+ import assert from "node:assert/strict";
2
+ import { readFileSync } from "node:fs";
3
+ import vm from "node:vm";
4
+
5
+ const serverSource = readFileSync("src/hosted-mcp/server.mjs", "utf8");
6
+ assert.match(serverSource, /startsAt: "2026-05-21T07:00:00\.000Z"/);
7
+ assert.match(serverSource, /keysCreated: 3/);
8
+ assert.match(serverSource, /genericKaleidoscopes: 11/);
9
+ assert.match(serverSource, /imageKaleidoscopes: 3/);
10
+
11
+ const start = serverSource.indexOf("function parseTimestampMs");
12
+ const end = serverSource.indexOf("function loadLiveWallState()");
13
+ assert.notEqual(start, -1, "baseline helper block start not found");
14
+ assert.notEqual(end, -1, "baseline helper block end not found");
15
+ assert.ok(end > start, "baseline helper block is malformed");
16
+
17
+ const helperSource = `
18
+ const KALEIDOSCOPE_PUBLIC_STATS_BASELINE = Object.freeze({
19
+ timezone: "America/Los_Angeles",
20
+ date: "2026-05-21",
21
+ startsAt: "2026-05-21T07:00:00.000Z",
22
+ counts: Object.freeze({
23
+ keysCreated: 3,
24
+ genericKaleidoscopes: 11,
25
+ imageKaleidoscopes: 3,
26
+ }),
27
+ });
28
+ ${serverSource.slice(start, end)}
29
+ globalThis.__statsHelpers = {
30
+ KALEIDOSCOPE_PUBLIC_STATS_BASELINE,
31
+ deriveKaleidoscopePublicStats,
32
+ isWipTestHandle,
33
+ createdAtSinceBaseline,
34
+ };
35
+ `;
36
+
37
+ const context = {
38
+ console,
39
+ Date,
40
+ Object,
41
+ Number,
42
+ String,
43
+ Array,
44
+ isPublicWallImageUrl(value) {
45
+ return typeof value === "string" && value.startsWith("https://wip.computer/media/kaleidoscope/generated/");
46
+ },
47
+ };
48
+
49
+ vm.createContext(context);
50
+ vm.runInContext(helperSource, context);
51
+
52
+ const {
53
+ KALEIDOSCOPE_PUBLIC_STATS_BASELINE,
54
+ deriveKaleidoscopePublicStats,
55
+ isWipTestHandle,
56
+ createdAtSinceBaseline,
57
+ } = context.__statsHelpers;
58
+
59
+ assert.deepEqual(JSON.parse(JSON.stringify(KALEIDOSCOPE_PUBLIC_STATS_BASELINE.counts)), {
60
+ keysCreated: 3,
61
+ genericKaleidoscopes: 11,
62
+ imageKaleidoscopes: 3,
63
+ });
64
+ assert.equal(KALEIDOSCOPE_PUBLIC_STATS_BASELINE.startsAt, "2026-05-21T07:00:00.000Z");
65
+ assert.equal(isWipTestHandle("wiptest-parker"), true);
66
+ assert.equal(isWipTestHandle("WIPTEST-PARKER"), true);
67
+ assert.equal(isWipTestHandle("passkey-123"), false);
68
+ assert.equal(createdAtSinceBaseline({ createdAt: "2026-05-21T06:59:59.999Z" }), false);
69
+ assert.equal(createdAtSinceBaseline({ createdAt: "2026-05-21T07:00:00.000Z" }), true);
70
+
71
+ const images = [
72
+ {
73
+ url: "https://wip.computer/media/kaleidoscope/generated/prebaseline-generic.jpg",
74
+ kind: "generic",
75
+ createdAt: "2026-05-20T00:00:00.000Z",
76
+ },
77
+ {
78
+ url: "https://wip.computer/media/kaleidoscope/generated/postbaseline-generic.jpg",
79
+ kind: "generic",
80
+ createdAt: "2026-05-21T07:00:00.000Z",
81
+ },
82
+ {
83
+ url: "https://wip.computer/media/kaleidoscope/generated/postbaseline-image.jpg",
84
+ kind: "image",
85
+ createdAt: "2026-05-21T08:00:00.000Z",
86
+ },
87
+ {
88
+ url: "https://wip.computer/media/kaleidoscope/generated/older-image.jpg",
89
+ kind: "image",
90
+ createdAt: "2026-05-20T01:00:00.000Z",
91
+ },
92
+ ];
93
+ const passkeyEntries = [
94
+ { handle: "passkey-before", createdAt: "2026-05-20T00:00:00.000Z" },
95
+ { handle: "wiptest-after", createdAt: "2026-05-21T08:00:00.000Z" },
96
+ { handle: "user-after", createdAt: "2026-05-21T09:00:00.000Z" },
97
+ { handle: "wiptest-newest", createdAt: "2026-05-21T09:15:00.000Z" },
98
+ ];
99
+
100
+ const stats = deriveKaleidoscopePublicStats({
101
+ images,
102
+ passkeyEntries,
103
+ now: new Date("2026-05-21T09:30:00.000Z"),
104
+ });
105
+
106
+ assert.equal(images.length, 4, "stats helper must not remove live-wall images");
107
+ assert.equal(stats.genericKaleidoscopes, 12);
108
+ assert.equal(stats.imageKaleidoscopes, 4);
109
+ assert.equal(stats.keysCreated, 4);
110
+ assert.equal(stats.publicWallImages, 4);
111
+ assert.deepEqual(JSON.parse(JSON.stringify(stats.newSinceBaseline)), {
112
+ genericKaleidoscopes: 1,
113
+ imageKaleidoscopes: 1,
114
+ keysCreated: 1,
115
+ total: 3,
116
+ });
117
+ assert.equal(stats.last24Hours.wallImages, 2);
118
+ assert.equal(stats.last24Hours.keysCreated, 1);
119
+ assert.equal(stats.lastCreated, "2026-05-21T09:00:00.000Z");
120
+
121
+ const wiptestOnly = deriveKaleidoscopePublicStats({
122
+ images: [],
123
+ passkeyEntries: [{ handle: "wiptest-only", createdAt: "2026-05-21T10:00:00.000Z" }],
124
+ now: new Date("2026-05-21T10:00:00.000Z"),
125
+ });
126
+ assert.equal(wiptestOnly.keysCreated, 3);
127
+ assert.equal(wiptestOnly.newSinceBaseline.keysCreated, 0);
128
+
129
+ console.log("kaleidoscope public stats baseline tests passed");
@@ -0,0 +1,89 @@
1
+ import { readFileSync } from "node:fs";
2
+
3
+ const loginFiles = [
4
+ "src/hosted-mcp/app/kaleidoscope-login.html",
5
+ "src/hosted-mcp/demo/login.html",
6
+ ];
7
+
8
+ function assertContains(source, needle, label) {
9
+ if (!source.includes(needle)) {
10
+ throw new Error(`${label} missing expected text: ${needle}`);
11
+ }
12
+ }
13
+
14
+ function assertBefore(source, first, second, label) {
15
+ const firstIndex = source.indexOf(first);
16
+ const secondIndex = source.indexOf(second);
17
+ if (firstIndex === -1 || secondIndex === -1 || firstIndex >= secondIndex) {
18
+ throw new Error(`${label} expected "${first}" before "${second}"`);
19
+ }
20
+ }
21
+
22
+ function extractFunction(source, name) {
23
+ const start = source.indexOf(`function ${name}(`);
24
+ if (start === -1) throw new Error(`could not find function ${name}`);
25
+ const bodyStart = source.indexOf("{", start);
26
+ let depth = 0;
27
+ for (let i = bodyStart; i < source.length; i += 1) {
28
+ if (source[i] === "{") depth += 1;
29
+ if (source[i] === "}") depth -= 1;
30
+ if (depth === 0) return source.slice(start, i + 1);
31
+ }
32
+ throw new Error(`could not extract function ${name}`);
33
+ }
34
+
35
+ function assertNotContains(source, needle, label) {
36
+ if (source.includes(needle)) {
37
+ throw new Error(`${label} must not contain: ${needle}`);
38
+ }
39
+ }
40
+
41
+ for (const file of loginFiles) {
42
+ const html = readFileSync(file, "utf8");
43
+ const followFunction = extractFunction(html, "followPairNextIfPresent");
44
+ const backFunction = extractFunction(html, "backToLoginAfterQrScan");
45
+
46
+ assertContains(html, 'id="qr-auth-confirm-view"', `${file} confirmation view`);
47
+ assertContains(html, "Your authenticated Kaleidoscope session is available on your other device.", `${file} confirmation headline`);
48
+ assertContains(html, "Back to login", `${file} back button copy`);
49
+ assertNotContains(html, "Open session here", `${file} authenticator completion must not offer local demo open`);
50
+ assertNotContains(html, ">Close</button>", `${file} authenticator completion must not show close action`);
51
+ assertNotContains(html, "Kaleidoscope is ready there.", `${file} removed ready helper copy`);
52
+ assertNotContains(html, "Open Kaleidoscope here", `${file} removed duplicated open copy`);
53
+ assertNotContains(html, "Keep using Kaleidoscope on the other device.", `${file} removed cancel helper copy`);
54
+ assertNotContains(html, "Scan QR Code", `${file} login-page scanner action must be absent`);
55
+ assertNotContains(html, "BarcodeDetector", `${file} login-page scanner detector must be absent`);
56
+ assertNotContains(html, "getUserMedia", `${file} login-page scanner camera code must be absent`);
57
+ assertContains(html, "You can close this page.", `${file} close fallback copy`);
58
+
59
+ assertContains(html, "function showQrAuthenticatorConfirmation(identity, isNewAccount)", `${file} confirmation function`);
60
+ assertContains(html, "function backToLoginAfterQrScan()", `${file} back-to-login function`);
61
+
62
+ assertContains(
63
+ html,
64
+ "if (DEMO_NEXT_REGEX.test(approveResponse.next)) {\n return showQrAuthenticatorConfirmation(identity, isNewAccount === true);\n }",
65
+ `${file} QR authenticator demo next shows confirmation`,
66
+ );
67
+ assertBefore(
68
+ html,
69
+ "return showQrAuthenticatorConfirmation(identity, isNewAccount === true);",
70
+ "next = approveResponse.next;",
71
+ `${file} confirmation intercepts approve next before redirect`,
72
+ );
73
+ assertNotContains(followFunction, "location.replace('/demo');", `${file} QR approve path must not directly enter demo`);
74
+ assertNotContains(followFunction, 'location.href = "/demo";', `${file} QR approve path must not assign demo href`);
75
+ assertNotContains(followFunction, "storeDemoHandoffIfNeeded('/demo'", `${file} QR approve path must not store demo handoff`);
76
+
77
+ assertContains(backFunction, "location.replace('/login?next=/demo');", `${file} back returns to login`);
78
+ assertNotContains(backFunction, "storeDemoHandoffIfNeeded", `${file} back must not store demo handoff`);
79
+ assertNotContains(backFunction, "location.replace('/demo')", `${file} back must not open demo`);
80
+
81
+ assertContains(html, "if (await followPairNextIfPresent(approveResponse, result, true)) return;", `${file} create caller stops after confirmation`);
82
+ assertContains(html, "if (await followPairNextIfPresent(approveResponse, result, false)) return;", `${file} sign-in caller stops after confirmation`);
83
+ assertContains(html, "storeDemoHandoffIfNeeded(data.next, data, qrLoginMode === 'register');", `${file} requester QR status still stores handoff`);
84
+ assertContains(html, "location.replace(data.next);", `${file} requester QR status still redirects`);
85
+ assertContains(html, "storeDemoHandoffIfNeeded('/demo', result, true);", `${file} same-device create still stores demo handoff`);
86
+ assertContains(html, "storeDemoHandoffIfNeeded('/demo', result, false);", `${file} same-device sign-in still stores demo handoff`);
87
+ }
88
+
89
+ console.log("kaleidoscope QR authenticator confirmation checks passed");