mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-07 15:52:52 +03:00
101 lines
3.1 KiB
JavaScript
101 lines
3.1 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* @file extract-credentials.mjs
|
|
* @description Print Raycast Pro credentials from local macOS install (redacted preview).
|
|
*
|
|
* Usage: node scripts/raycast/extract-credentials.mjs
|
|
*
|
|
* @changes
|
|
* - [2026-07-27] [Composer] - CLI credential extractor for local Raycast
|
|
*/
|
|
|
|
import { execFileSync } from "node:child_process";
|
|
import { createHash } from "node:crypto";
|
|
import { copyFileSync, existsSync, mkdtempSync, readFileSync, rmdirSync, unlinkSync } from "node:fs";
|
|
import { homedir, tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
const RAYCAST_SALT = "yvkwWXzxPPBAqY2tmaKrB*DvYjjMaeEf";
|
|
const RAYCAST_SUPPORT = join(homedir(), "Library", "Application Support", "com.raycast.macos");
|
|
const RAYCAST_DB = join(RAYCAST_SUPPORT, "raycast-enc.sqlite");
|
|
|
|
function redact(s, keep = 8) {
|
|
if (!s || s.length <= keep * 2) return "***";
|
|
return `${s.slice(0, keep)}…${s.slice(-4)}`;
|
|
}
|
|
|
|
function readKeychain(account) {
|
|
return JSON.parse(
|
|
execFileSync("security", ["find-generic-password", "-s", "Raycast", "-a", account, "-w"], {
|
|
encoding: "utf-8",
|
|
}).trim()
|
|
);
|
|
}
|
|
|
|
function dbPassphrase() {
|
|
const keyHex = execFileSync(
|
|
"security",
|
|
["find-generic-password", "-s", "Raycast", "-a", "database_key", "-w"],
|
|
{ encoding: "utf-8" }
|
|
).trim();
|
|
return createHash("sha256")
|
|
.update(keyHex + RAYCAST_SALT)
|
|
.digest("hex");
|
|
}
|
|
|
|
function queryDb(sql) {
|
|
const tmpDir = mkdtempSync(join(tmpdir(), "raycast-extract-"));
|
|
const tmpDb = join(tmpDir, "db.sqlite");
|
|
copyFileSync(RAYCAST_DB, tmpDb);
|
|
for (const ext of ["-wal", "-shm"]) {
|
|
const src = RAYCAST_DB + ext;
|
|
if (existsSync(src)) copyFileSync(src, tmpDb + ext);
|
|
}
|
|
const passphrase = dbPassphrase();
|
|
const input = `PRAGMA key = '${passphrase}';\n.mode json\n${sql}`;
|
|
const out = execFileSync("sqlcipher", [tmpDb], { input, encoding: "utf-8" });
|
|
for (const ext of ["", "-wal", "-shm"]) {
|
|
try {
|
|
unlinkSync(tmpDb + ext);
|
|
} catch {}
|
|
}
|
|
try {
|
|
rmdirSync(tmpDir);
|
|
} catch {}
|
|
const jsonStr = out.startsWith("ok\n") ? out.slice(3) : out;
|
|
return JSON.parse(jsonStr.trim() || "[]");
|
|
}
|
|
|
|
if (process.platform !== "darwin") {
|
|
console.error("macOS only");
|
|
process.exit(1);
|
|
}
|
|
|
|
const store = readKeychain("raycast-store_credentials");
|
|
const token = store?.oauth?.access_token;
|
|
if (!token) {
|
|
console.error("No Raycast bearer token in Keychain — open Raycast and sign in");
|
|
process.exit(1);
|
|
}
|
|
|
|
const users = queryDb("SELECT analyticsId, email, username, hasProFeatures, hasBetterAI FROM user LIMIT 1;");
|
|
const user = users[0] || {};
|
|
const deviceId =
|
|
user.analyticsId ||
|
|
JSON.parse(readFileSync(join(RAYCAST_SUPPORT, "posthog.distinctId"), "utf-8"))["posthog.distinctId"];
|
|
|
|
console.log(JSON.stringify({
|
|
accessTokenPreview: redact(token),
|
|
accessToken: token,
|
|
deviceId,
|
|
aid: deviceId,
|
|
email: user.email || store?.user?.email,
|
|
username: user.username || store?.user?.username,
|
|
hasProFeatures: !!user.hasProFeatures,
|
|
hasBetterAI: !!user.hasBetterAI,
|
|
sources: {
|
|
bearer: "Keychain Raycast / raycast-store_credentials",
|
|
deviceId: "raycast-enc.sqlite user.analyticsId",
|
|
},
|
|
}, null, 2));
|