1#!/usr/bin/env node 2 3// Fixed SplashIQ origin and setup-only commands. Never uses a user/service JWT. 4import { readFile } from "node:fs/promises"; 5 6const [command, ...args] = process.argv.slice(2); 7const usage = `Usage: 8 SPLASHIQ_SETUP_TOKEN=<token> node scripts/splashiq-tenant.mjs status 9 SPLASHIQ_SETUP_TOKEN=<token> node scripts/splashiq-tenant.mjs import-preview <setup.json> 10 SPLASHIQ_SETUP_TOKEN=<token> node scripts/splashiq-tenant.mjs import-commit <batch-id> <same-setup.json> --confirm 11 SPLASHIQ_SETUP_TOKEN=<token> node scripts/splashiq-tenant.mjs email-draft <draft.json> 12`; 13 14const token = process.env.SPLASHIQ_SETUP_TOKEN; 15if (!token || !/^si_setup_[0-9a-f-]{36}_[A-Za-z0-9_-]{43}$/.test(token)) { 16 process.stderr.write("Set a valid SPLASHIQ_SETUP_TOKEN from Settings â AI setup access.\n"); 17 process.exitCode = 2; 18} else if (command === "status" && args.length === 0) { 19 await callSetup("/api/tenant/setup", "GET"); 20} else if (command === "import-preview" && args.length === 1) { 21 const payload = await loadPayload(args[0]); 22 if (payload) await callSetup("/api/tenant/setup/imports", "POST", { action: "preview", payload }); 23} else if (command === "import-commit" && args.length === 3 24 && /^[0-9a-f-]{36}$/.test(args[0]) && args[2] === "--confirm") { 25 const payload = await loadPayload(args[1]); 26 if (payload) await callSetup("/api/tenant/setup/imports", "POST", { 27 action: "commit", batch_id: args[0], payload, 28 }); 29} else if (command === "email-draft" && args.length === 1) { 30 const payload = await loadPayload(args[0]); 31 if (payload) await callSetup("/api/tenant/setup/drafts", "POST", payload); 32} else { 33 process.stderr.write(usage); 34 process.exitCode = 2; 35} 36 37async function loadPayload(path) { 38 try { 39 const source = await readFile(path, "utf8"); 40 if (Buffer.byteLength(source) > 90_000) throw new Error("Import is too large"); 41 return JSON.parse(source); 42 } catch (error) { 43 process.stderr.write(`Could not read import file: ${error instanceof Error ? error.message : "unknown error"}\n`); 44 process.exitCode = 2; 45 return null; 46 } 47} 48 49async function callSetup(path, method, body) { 50 try { 51 const response = await fetch(`https://splashiq.ai${path}`, { 52 method, 53 headers: { Authorization: `Bearer ${token}`, ...(body ? { "Content-Type": "application/json" } : {}) }, 54 body: body ? JSON.stringify(body) : undefined, 55 signal: AbortSignal.timeout(20_000), 56 }); 57 const result = await response.json(); 58 if (!response.ok) { 59 process.stderr.write(response.status === 401 60 ? "Setup token is invalid, expired, revoked, or lacks permission for this command.\n" 61 : `${JSON.stringify(result, null, 2)}\n`); 62 process.exitCode = 1; 63 } else { 64 process.stdout.write(`${JSON.stringify(result, null, 2)}\n`); 65 } 66 } catch { 67 process.stderr.write("Could not reach SplashIQ. Try again later.\n"); 68 process.exitCode = 1; 69 } 70}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.