@rubytech/create-maxy 1.0.774 → 1.0.776
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/package.json +1 -1
- package/payload/platform/config/brand.json +1 -0
- package/payload/platform/lib/entitlement/PUBKEY-HASH.txt +1 -0
- package/payload/platform/lib/entitlement/dist/canonicalize.d.ts +26 -0
- package/payload/platform/lib/entitlement/dist/canonicalize.d.ts.map +1 -0
- package/payload/platform/lib/entitlement/dist/canonicalize.js +54 -0
- package/payload/platform/lib/entitlement/dist/canonicalize.js.map +1 -0
- package/payload/platform/lib/entitlement/dist/index.d.ts +76 -0
- package/payload/platform/lib/entitlement/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/entitlement/dist/index.js +293 -0
- package/payload/platform/lib/entitlement/dist/index.js.map +1 -0
- package/payload/platform/lib/entitlement/rubytech-pubkey.pem +3 -0
- package/payload/platform/package.json +2 -2
- package/payload/platform/plugins/admin/hooks/pre-tool-use.sh +32 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js +97 -6
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/skills/plugin-management/SKILL.md +10 -5
- package/payload/platform/scripts/generate-entitlement-fixture.mjs +152 -0
- package/payload/platform/scripts/wifi-provision.sh +25 -2
- package/payload/server/chunk-MIP54X7Q.js +3244 -0
- package/payload/server/chunk-TM3EQSID.js +9800 -0
- package/payload/server/client-pool-4MZN42GG.js +28 -0
- package/payload/server/maxy-edge.js +2 -2
- package/payload/server/public/assets/{Checkbox-DEE8t2QO.js → Checkbox-C_KxaLc-.js} +1 -1
- package/payload/server/public/assets/{admin-BCLHIuWG.js → admin-xbKPR6ZI.js} +30 -30
- package/payload/server/public/assets/data-D23IzpJ2.js +1 -0
- package/payload/server/public/assets/graph-D2AS9zFS.js +1 -0
- package/payload/server/public/assets/{jsx-runtime-DSbkOE76.css → jsx-runtime-BZtBxBng.css} +1 -1
- package/payload/server/public/assets/{page-BOpPVs9J.js → page-CjTfZ3O6.js} +1 -1
- package/payload/server/public/assets/{page-ZATk95ZG.js → page-DEWgk_nR.js} +1 -1
- package/payload/server/public/assets/{public-BLi3J8KU.js → public-CehiL-qZ.js} +1 -1
- package/payload/server/public/assets/{share-2-DS7Pnkkq.js → share-2-BG1VXt3z.js} +1 -1
- package/payload/server/public/assets/{useVoiceRecorder-pEHqS1ib.js → useVoiceRecorder-1Dvb-yHn.js} +1 -1
- package/payload/server/public/data.html +5 -5
- package/payload/server/public/graph.html +6 -6
- package/payload/server/public/index.html +8 -8
- package/payload/server/public/public.html +5 -5
- package/payload/server/server.js +31 -16
- package/payload/server/public/assets/data-ryPag-T-.js +0 -1
- package/payload/server/public/assets/graph-CD-Zqscg.js +0 -1
- /package/payload/server/public/assets/{jsx-runtime-DeNudFNA.js → jsx-runtime-DrneHL3t.js} +0 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* One-shot Rubytech-side entitlement fixture generator.
|
|
4
|
+
*
|
|
5
|
+
* RUN BY FOUNDER ONCE; NEVER ON CUSTOMER DEVICES.
|
|
6
|
+
*
|
|
7
|
+
* Produces:
|
|
8
|
+
* - platform/lib/entitlement/rubytech-pubkey.pem (vendored, committed)
|
|
9
|
+
* - platform/lib/entitlement/PUBKEY-HASH.txt (sha256 of pubkey, committed)
|
|
10
|
+
* - .secrets/rubytech-private-key.pem (LOCAL ONLY — never commit)
|
|
11
|
+
* - <out-dir>/entitlement.json (signed payload fixture)
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* node platform/scripts/generate-entitlement-fixture.mjs init
|
|
15
|
+
* → generates a fresh keypair + writes the pubkey + hash
|
|
16
|
+
*
|
|
17
|
+
* node platform/scripts/generate-entitlement-fixture.mjs sign \
|
|
18
|
+
* --account-id <id> --email <email> --tier <solo|family|pro> \
|
|
19
|
+
* --plugins <comma-list> --days <n> --out <path>
|
|
20
|
+
* → signs a payload using the existing private key
|
|
21
|
+
*
|
|
22
|
+
* The verifier at platform/lib/entitlement/src/index.ts has PUBKEY_SHA256
|
|
23
|
+
* baked into source. After running `init` the script prints the new hash —
|
|
24
|
+
* paste it into the verifier and rebuild.
|
|
25
|
+
*
|
|
26
|
+
* For Task 832 (Rubytech-side issuance endpoint), this script will be
|
|
27
|
+
* replaced by a server-side equivalent. Until then, it stands in.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import {
|
|
31
|
+
generateKeyPairSync,
|
|
32
|
+
createPublicKey,
|
|
33
|
+
createPrivateKey,
|
|
34
|
+
createHash,
|
|
35
|
+
sign as cryptoSign,
|
|
36
|
+
} from "node:crypto";
|
|
37
|
+
import { writeFileSync, readFileSync, existsSync, mkdirSync } from "node:fs";
|
|
38
|
+
import { resolve, dirname } from "node:path";
|
|
39
|
+
import { fileURLToPath } from "node:url";
|
|
40
|
+
|
|
41
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
42
|
+
const __dirname = dirname(__filename);
|
|
43
|
+
const PLATFORM_ROOT = resolve(__dirname, "..");
|
|
44
|
+
const REPO_ROOT = resolve(PLATFORM_ROOT, "..");
|
|
45
|
+
const PUBKEY_PATH = resolve(PLATFORM_ROOT, "lib", "entitlement", "rubytech-pubkey.pem");
|
|
46
|
+
const HASH_PATH = resolve(PLATFORM_ROOT, "lib", "entitlement", "PUBKEY-HASH.txt");
|
|
47
|
+
const SECRET_DIR = resolve(REPO_ROOT, ".secrets");
|
|
48
|
+
const PRIVKEY_PATH = resolve(SECRET_DIR, "rubytech-private-key.pem");
|
|
49
|
+
|
|
50
|
+
function canonicalize(value) {
|
|
51
|
+
if (value === null) return "null";
|
|
52
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
53
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
54
|
+
if (typeof value === "number") {
|
|
55
|
+
if (!Number.isFinite(value)) throw new Error("non-finite number");
|
|
56
|
+
if (Object.is(value, -0)) throw new Error("negative zero");
|
|
57
|
+
return JSON.stringify(value);
|
|
58
|
+
}
|
|
59
|
+
if (Array.isArray(value)) return "[" + value.map(canonicalize).join(",") + "]";
|
|
60
|
+
if (typeof value === "object") {
|
|
61
|
+
const keys = Object.keys(value).sort();
|
|
62
|
+
return "{" + keys.map((k) => JSON.stringify(k) + ":" + canonicalize(value[k])).join(",") + "}";
|
|
63
|
+
}
|
|
64
|
+
throw new Error(`unsupported value type ${typeof value}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function init() {
|
|
68
|
+
if (existsSync(PRIVKEY_PATH)) {
|
|
69
|
+
console.error(`ABORT: private key already exists at ${PRIVKEY_PATH}.`);
|
|
70
|
+
console.error("Delete it manually if you really want to regenerate (ALL EXISTING SIGNED PAYLOADS WILL BREAK).");
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
74
|
+
const pubPem = publicKey.export({ type: "spki", format: "pem" });
|
|
75
|
+
const privPem = privateKey.export({ type: "pkcs8", format: "pem" });
|
|
76
|
+
|
|
77
|
+
if (!existsSync(SECRET_DIR)) mkdirSync(SECRET_DIR, { recursive: true });
|
|
78
|
+
writeFileSync(PRIVKEY_PATH, privPem, { mode: 0o600 });
|
|
79
|
+
writeFileSync(PUBKEY_PATH, pubPem, "utf-8");
|
|
80
|
+
|
|
81
|
+
const hash = createHash("sha256").update(pubPem).digest("hex");
|
|
82
|
+
writeFileSync(HASH_PATH, hash + "\n", "utf-8");
|
|
83
|
+
|
|
84
|
+
console.log(`Generated keypair.`);
|
|
85
|
+
console.log(` Public key : ${PUBKEY_PATH}`);
|
|
86
|
+
console.log(` Hash file : ${HASH_PATH} (${hash})`);
|
|
87
|
+
console.log(` Private key : ${PRIVKEY_PATH} (DO NOT COMMIT — verify .gitignore)`);
|
|
88
|
+
console.log("");
|
|
89
|
+
console.log(`NEXT: paste the hash into platform/lib/entitlement/src/index.ts:`);
|
|
90
|
+
console.log(` export const PUBKEY_SHA256 = "${hash}";`);
|
|
91
|
+
console.log(`Then run npm --prefix platform run build:lib`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function parseArgs(argv) {
|
|
95
|
+
const out = {};
|
|
96
|
+
for (let i = 0; i < argv.length; i += 2) {
|
|
97
|
+
const key = argv[i].replace(/^--/, "");
|
|
98
|
+
out[key] = argv[i + 1];
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function sign(args) {
|
|
104
|
+
const required = ["account-id", "email", "tier", "out"];
|
|
105
|
+
for (const k of required) {
|
|
106
|
+
if (!args[k]) {
|
|
107
|
+
console.error(`Missing --${k}`);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (!existsSync(PRIVKEY_PATH)) {
|
|
112
|
+
console.error(`Private key missing at ${PRIVKEY_PATH}. Run 'init' first.`);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
const days = Number(args.days ?? "365");
|
|
116
|
+
const plugins = (args.plugins ?? "")
|
|
117
|
+
.split(",")
|
|
118
|
+
.map((s) => s.trim())
|
|
119
|
+
.filter(Boolean);
|
|
120
|
+
|
|
121
|
+
const now = Date.now();
|
|
122
|
+
const issued = new Date(now).toISOString();
|
|
123
|
+
const expires = new Date(now + days * 86400 * 1000).toISOString();
|
|
124
|
+
|
|
125
|
+
const payload = {
|
|
126
|
+
accountId: args["account-id"],
|
|
127
|
+
customerEmail: args.email,
|
|
128
|
+
tier: args.tier,
|
|
129
|
+
purchasedPlugins: plugins,
|
|
130
|
+
issued,
|
|
131
|
+
expires,
|
|
132
|
+
};
|
|
133
|
+
const canonical = canonicalize(payload);
|
|
134
|
+
const privKey = createPrivateKey(readFileSync(PRIVKEY_PATH, "utf-8"));
|
|
135
|
+
const sigBuf = cryptoSign(null, Buffer.from(canonical, "utf-8"), privKey);
|
|
136
|
+
const envelope = { payload, signature: sigBuf.toString("base64") };
|
|
137
|
+
writeFileSync(args.out, JSON.stringify(envelope, null, 2) + "\n", "utf-8");
|
|
138
|
+
console.log(`Signed payload written to ${args.out}`);
|
|
139
|
+
console.log(` tier=${payload.tier} plugins=${plugins.length} expires=${expires}`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const cmd = process.argv[2];
|
|
143
|
+
if (cmd === "init") {
|
|
144
|
+
init();
|
|
145
|
+
} else if (cmd === "sign") {
|
|
146
|
+
sign(parseArgs(process.argv.slice(3)));
|
|
147
|
+
} else {
|
|
148
|
+
console.error("Usage:");
|
|
149
|
+
console.error(" generate-entitlement-fixture.mjs init");
|
|
150
|
+
console.error(" generate-entitlement-fixture.mjs sign --account-id <id> --email <e> --tier <t> [--plugins a,b] [--days N] --out <path>");
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
@@ -157,8 +157,31 @@ get_wifi_ip() {
|
|
|
157
157
|
|
|
158
158
|
scan_networks() {
|
|
159
159
|
log "scanning WiFi networks"
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
|
|
161
|
+
# `nmcli device wifi list --rescan yes` triggers a rescan but returns the
|
|
162
|
+
# currently cached list immediately — on cold boot the cache is empty
|
|
163
|
+
# because the radio hasn't completed its first scan yet. Trigger an
|
|
164
|
+
# explicit rescan, then poll the cached list until it has at least one
|
|
165
|
+
# visible (non-hidden) SSID, up to a hard timeout.
|
|
166
|
+
nmcli device wifi rescan 2>/dev/null || true
|
|
167
|
+
local raw=""
|
|
168
|
+
local elapsed=0
|
|
169
|
+
local max_wait=15
|
|
170
|
+
while [ "$elapsed" -lt "$max_wait" ]; do
|
|
171
|
+
raw=$(nmcli -t -f SSID,SIGNAL,SECURITY device wifi list 2>/dev/null) || true
|
|
172
|
+
# Lines have form SSID:SIGNAL:SECURITY; a leading ':' means hidden SSID.
|
|
173
|
+
# Break as soon as at least one line starts with a non-':' character.
|
|
174
|
+
if [ -n "$raw" ] && echo "$raw" | grep -qE '^[^:]'; then
|
|
175
|
+
break
|
|
176
|
+
fi
|
|
177
|
+
sleep 1
|
|
178
|
+
elapsed=$((elapsed + 1))
|
|
179
|
+
done
|
|
180
|
+
if [ "$elapsed" -ge "$max_wait" ]; then
|
|
181
|
+
log "scan timeout: no visible networks after ${max_wait}s — proceeding with empty list"
|
|
182
|
+
else
|
|
183
|
+
log "scan settled in ${elapsed}s"
|
|
184
|
+
fi
|
|
162
185
|
|
|
163
186
|
# Parse nmcli terse output into JSON using jq for safe escaping.
|
|
164
187
|
# Terse mode uses colon separators and escapes literal colons as \:
|