diff --git a/fedac/native/pieces/list.mjs b/fedac/native/pieces/list.mjs index 1c9ca25da6..b24921a224 100644 --- a/fedac/native/pieces/list.mjs +++ b/fedac/native/pieces/list.mjs @@ -13,6 +13,7 @@ const MAIN = [ const TOOLS = [ { name: "os", desc: "system updates (OTA)" }, { name: "samples", desc: "sample library" }, + { name: "secrets", desc: "config & credentials" }, { name: "wifi", desc: "network picker" }, { name: "code", desc: "AI assistant (Claude)" }, { name: "terminal", desc: "shell (PTY)" }, diff --git a/fedac/native/pieces/secrets.mjs b/fedac/native/pieces/secrets.mjs new file mode 100644 index 0000000000..a2e2190b6f --- /dev/null +++ b/fedac/native/pieces/secrets.mjs @@ -0,0 +1,207 @@ +// secrets.mjs — Show config.json secrets status on AC Native +// Values hidden by default, tap/enter on a row to reveal, tap again to hide. +// Jumped to from prompt via "secrets" command. + +let items = []; // { key, status, value, revealed } +let selectedIdx = 0; +let frame = 0; +let storageDevice = ""; + +function boot({ system }) { + storageDevice = system?.bootDevice || "?"; + items = []; + + // Read config.json + const raw = system?.readFile?.("/mnt/config.json"); + let cfg = {}; + if (raw) { + try { cfg = JSON.parse(raw); } catch {} + } + + // Handle + items.push({ + key: "handle", + status: cfg.handle ? "set" : "missing", + value: cfg.handle ? "@" + cfg.handle : "", + revealed: true, // always show handle + }); + + // AC token + items.push({ + key: "ac token", + status: cfg.token ? "set (" + cfg.token.length + " chars)" : "missing", + value: cfg.token || "", + revealed: false, + }); + + // User sub + items.push({ + key: "user sub", + status: cfg.sub ? "set" : "missing", + value: cfg.sub || "", + revealed: false, + }); + + // Email + items.push({ + key: "email", + status: cfg.email ? "set" : "missing", + value: cfg.email || "", + revealed: false, + }); + + // Claude credentials + const cc = cfg.claudeCreds; + if (cc) { + const tokenPreview = cc.claudeAiOauth?.accessToken ? "token:" + cc.claudeAiOauth.accessToken.length + "chars" : "no token"; + items.push({ + key: "claude creds", + status: "set (" + tokenPreview + ")", + value: JSON.stringify(cc).slice(0, 200), + revealed: false, + }); + } else { + items.push({ key: "claude creds", status: "missing", value: "", revealed: false }); + } + + // Claude state + items.push({ + key: "claude state", + status: cfg.claudeState ? "set" : "missing", + value: cfg.claudeState ? JSON.stringify(cfg.claudeState).slice(0, 200) : "", + revealed: false, + }); + + // WiFi saved creds + const wifiRaw = system?.readFile?.("/mnt/wifi_creds.json"); + if (wifiRaw) { + try { + const nets = JSON.parse(wifiRaw); + const ssids = nets.map(n => n.ssid).join(", "); + items.push({ + key: "wifi creds", + status: nets.length + " networks", + value: ssids, + revealed: false, + }); + } catch { + items.push({ key: "wifi creds", status: "parse error", value: wifiRaw.slice(0, 100), revealed: false }); + } + } else { + items.push({ key: "wifi creds", status: "no file", value: "", revealed: false }); + } + + // Piece + items.push({ + key: "default piece", + status: cfg.piece || "notepat", + value: cfg.piece || "notepat", + revealed: true, + }); + + // Boot device + items.push({ + key: "boot device", + status: storageDevice, + value: storageDevice, + revealed: true, + }); + + // Version + items.push({ + key: "version", + status: system?.version || "unknown", + value: system?.version || "unknown", + revealed: true, + }); +} + +function act({ event: e, sound, system }) { + if (!e.is("keyboard:down")) return; + + if (e.is("keyboard:down:escape") || e.is("keyboard:down:backspace")) { + system?.jump?.("prompt"); + return; + } + + if (e.is("keyboard:down:arrowdown") || e.is("keyboard:down:tab")) { + selectedIdx = (selectedIdx + 1) % Math.max(1, items.length); + sound?.synth({ type: "sine", tone: 440, duration: 0.03, volume: 0.08, attack: 0.002, decay: 0.025 }); + return; + } + if (e.is("keyboard:down:arrowup")) { + selectedIdx = (selectedIdx - 1 + items.length) % Math.max(1, items.length); + sound?.synth({ type: "sine", tone: 440, duration: 0.03, volume: 0.08, attack: 0.002, decay: 0.025 }); + return; + } + + // Toggle reveal + if (e.is("keyboard:down:enter") || e.is("keyboard:down:return") || e.is("keyboard:down:space")) { + if (items[selectedIdx]) { + items[selectedIdx].revealed = !items[selectedIdx].revealed; + sound?.synth({ type: "sine", tone: items[selectedIdx].revealed ? 523 : 392, duration: 0.04, volume: 0.08, attack: 0.002, decay: 0.03 }); + } + return; + } +} + +function paint({ wipe, ink, box, write, screen }) { + frame++; + const T = __theme.update(); + const w = screen.width, h = screen.height; + const pad = 10; + const font = "font_1"; + + wipe(T.bg[0], T.bg[1], T.bg[2]); + + ink(T.fg, T.fg + 10, T.fg); + write("secrets", { x: pad, y: 10, size: 2, font: "matrix" }); + + ink(T.fgMute, T.fgMute, T.fgMute + 10); + write(storageDevice + " /mnt/config.json", { x: pad, y: 34, size: 1, font }); + + const listY = 50; + const rowH = 22; + + for (let i = 0; i < items.length; i++) { + const item = items[i]; + const ry = listY + i * rowH; + const selected = i === selectedIdx; + + if (ry > h - 20) break; + + // Selection highlight + if (selected) { + ink(30, 45, 55); + box(pad - 2, ry - 2, w - pad * 2 + 4, rowH - 2, true); + } + + // Key name + ink(selected ? 200 : 120, selected ? 200 : 120, selected ? 220 : 140); + write(item.key, { x: pad, y: ry, size: 1, font }); + + // Status (always visible) + const statusColor = item.status.startsWith("missing") || item.status.startsWith("no ") + ? [200, 80, 80] : [80, 180, 100]; + ink(...statusColor); + write(item.status, { x: pad + 90, y: ry, size: 1, font }); + + // Value (only if revealed and has content) + if (item.revealed && item.value) { + ink(160, 160, 120); + const maxChars = Math.floor((w - pad * 2 - 90) / 6); + write(item.value.slice(0, maxChars), { x: pad + 4, y: ry + 10, size: 1, font }); + } else if (!item.revealed && item.value) { + ink(60, 60, 70); + write("enter to reveal", { x: pad + 90, y: ry + 10, size: 1, font }); + } + } + + // Bottom + ink(T.fgMute, T.fgMute + 10, T.fgMute); + write("arrows: select enter: reveal/hide esc: back", { x: pad, y: h - 12, size: 1, font }); +} + +function sim() {} + +export { boot, paint, act, sim };