import { mkdtempSync, rmSync, existsSync, readFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { resolve, join } from 'node:path'; import { spawnSync } from 'node:child_process'; const args = new Set(process.argv.slice(2)); const checkOnly = args.has('--check'); const repoRoot = resolve(new URL('../..', import.meta.url).pathname); const matrixCli = resolve(repoRoot, 'packages', 'cli', 'dist', 'bin', 'matrix.js'); function envString(...names) { for (const name of names) { const value = process.env[name]; if (typeof value === 'string' && value.trim()) return value.trim(); } return undefined; } function run(command, commandArgs, options = {}) { const result = spawnSync(command, commandArgs, { cwd: options.cwd ?? repoRoot, stdio: options.stdio ?? 'pipe', env: process.env, encoding: 'utf8', }); if (result.error) throw result.error; if (result.status !== 0) { throw new Error(`${command} ${commandArgs.join(' ')} failed with exit ${result.status}\n${result.stderr}${result.stdout}`); } return result; } function resolveConfig() { const key = envString('MATRIX_API_KEY', 'HIVECAST_API_KEY', 'HIVECAST_TEST_KEY'); const cloud = envString('MATRIX_CLOUD', 'HIVECAST_CLOUD', 'HIVECAST_TEST_CLOUD'); const space = envString('MATRIX_SPACE', 'HIVECAST_SPACE', 'HIVECAST_TEST_SPACE'); const provided = [key, cloud, space].filter(Boolean).length; if (provided > 0 && provided < 3) { throw new Error('Provide MATRIX_API_KEY, MATRIX_CLOUD, and MATRIX_SPACE together'); } return key && cloud && space ? { key, cloud: cloud.replace(/\/+$/g, ''), space } : null; } async function validateHiveCastKey(config) { const response = await fetch(`${config.cloud}/api/install/key-info`, { method: 'POST', headers: { authorization: `Bearer ${config.key}`, 'content-type': 'application/json', }, body: JSON.stringify({ space: config.space }), }); const text = await response.text(); if (!response.ok) { throw new Error(`HiveCast key validation failed (${response.status}): ${text}`); } const body = text.trim() ? JSON.parse(text) : {}; if (body?.ok !== true) { throw new Error(`HiveCast key validation did not return ok:true: ${text}`); } return { keyId: body.key?.id ?? body.id ?? null, fingerprint: body.key?.fingerprint ?? body.fingerprint ?? null, }; } const config = resolveConfig(); if (!config) { const message = 'HiveCast demo requires MATRIX_API_KEY, MATRIX_CLOUD, and MATRIX_SPACE'; if (checkOnly) { console.log(JSON.stringify({ ok: true, demo: 'hivecast-login', credentialsRequired: true, message }, null, 2)); process.exit(0); } throw new Error(message); } if (checkOnly) { console.log(JSON.stringify({ ok: true, demo: 'hivecast-login', credentialsAvailable: true }, null, 2)); process.exit(0); } const demoHome = process.env.MATRIX_DEMO_HOME ? resolve(process.env.MATRIX_DEMO_HOME) : mkdtempSync(join(tmpdir(), 'hivecast-sdk-login.')); const keep = process.env.MATRIX_DEMO_KEEP === '1' || Boolean(process.env.MATRIX_DEMO_HOME); try { const validation = await validateHiveCastKey(config); run(process.execPath, [ matrixCli, 'configure', '--dir', demoHome, '--key', config.key, '--cloud', config.cloud, '--space', config.space, ]); const credentialPath = join(demoHome, '.matrix', 'credentials', 'matrix-api-key.json'); const configPath = join(demoHome, '.matrix', 'config.json'); if (!existsSync(credentialPath)) throw new Error(`Credential file was not written: ${credentialPath}`); if (!existsSync(configPath)) throw new Error(`Config file was not written: ${configPath}`); const writtenCredential = JSON.parse(readFileSync(credentialPath, 'utf8')); if (writtenCredential.key !== config.key) throw new Error('Written credential key does not match input key'); if (writtenCredential.cloud !== config.cloud) throw new Error('Written credential cloud does not match input cloud'); if (writtenCredential.space !== config.space) throw new Error('Written credential space does not match input space'); console.log(JSON.stringify({ ok: true, demo: 'hivecast-login', cloud: config.cloud, space: config.space, keyValidated: true, keyId: validation.keyId, fingerprint: validation.fingerprint, demoHome: keep ? demoHome : '(temporary, removed)', credentialPath: keep ? credentialPath : '(temporary, removed)', }, null, 2)); } finally { if (!keep) rmSync(demoHome, { recursive: true, force: true }); }