UNPKG

configure

Version:

Identity layer SDK for AI agents

303 lines (301 loc) 12.9 kB
#!/usr/bin/env node "use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); // src/cli.ts var import_node_fs = __toESM(require("fs")); var import_node_path = __toESM(require("path")); var import_node_child_process = require("child_process"); var import_node_readline = require("readline"); var API_BASE = process.env.CONFIGURE_BASE_URL || "https://api.configure.dev"; var POLL_INTERVAL_MS = 2e3; var TIMEOUT_MS = 15 * 60 * 1e3; var LOGO = ` \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588`; var TAGLINE = "identity layer for AI agents"; var SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"]; async function printLogo() { const wide = (process.stdout.columns || 80) >= 75; if (wide) { try { const gs = await import("gradient-string"); const gradient = gs.default(["#FF8250", "#8C50F0", "#3C8CFF"]); print(gradient.multiline(LOGO)); } catch { print(LOGO); } } else { print("\x1B[1m Configure\x1B[0m"); } print(`\x1B[2m ${TAGLINE}\x1B[0m`); print(""); } function print(msg) { process.stdout.write(msg + "\n"); } function waitForEnter() { return new Promise((resolve) => { if (!process.stdin.isTTY) { resolve(); return; } print(" Press \x1B[1mEnter\x1B[0m to log in and register your agent"); print(""); const rl = (0, import_node_readline.createInterface)({ input: process.stdin }); rl.once("line", () => { rl.close(); resolve(); }); }); } async function openBrowser(url) { const cmd2 = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`; return new Promise((resolve) => { (0, import_node_child_process.exec)(cmd2, () => resolve()); }); } function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } function readExistingConfig(cwd) { const envPath = import_node_path.default.join(cwd, ".env"); if (!import_node_fs.default.existsSync(envPath)) { return { apiKey: null, publishableKey: null, agent: null }; } const content = import_node_fs.default.readFileSync(envPath, "utf-8"); function readValue(key) { const match = content.match(new RegExp(`^${key}=(.+)$`, "m")); return match && match[1].trim() ? match[1].trim() : null; } return { apiKey: readValue("CONFIGURE_API_KEY"), publishableKey: readValue("CONFIGURE_PUBLISHABLE_KEY"), agent: readValue("CONFIGURE_AGENT") }; } function writeEnvFile(apiKey, publishableKey, agent, cwd) { const envPath = import_node_path.default.join(cwd, ".env"); function upsertLine(content2, key, value) { const line = `${key}=${value}`; if (content2.includes(`${key}=`)) { return content2.replace(new RegExp(`^${key}=.*`, "m"), line); } const sep = content2.endsWith("\n") ? "" : "\n"; return content2 + sep + line + "\n"; } let content = import_node_fs.default.existsSync(envPath) ? import_node_fs.default.readFileSync(envPath, "utf-8") : ""; content = upsertLine(content, "CONFIGURE_API_KEY", apiKey); if (publishableKey) { content = upsertLine(content, "CONFIGURE_PUBLISHABLE_KEY", publishableKey); } if (agent) { content = upsertLine(content, "CONFIGURE_AGENT", agent); } import_node_fs.default.writeFileSync(envPath, content, "utf-8"); } function updateGitignore(cwd) { const gitignorePath = import_node_path.default.join(cwd, ".gitignore"); if (import_node_fs.default.existsSync(gitignorePath)) { const existing = import_node_fs.default.readFileSync(gitignorePath, "utf-8"); if (!existing.split("\n").some((l) => l.trim() === ".env")) { const separator = existing.endsWith("\n") ? "" : "\n"; import_node_fs.default.writeFileSync(gitignorePath, existing + separator + ".env\n", "utf-8"); } } else { import_node_fs.default.writeFileSync(gitignorePath, ".env\n", "utf-8"); } } async function createSession() { const res = await fetch(`${API_BASE}/v1/developer/cli/sessions`, { method: "POST", headers: { "Content-Type": "application/json" } }); if (!res.ok) { throw new Error(`Failed to create session (${res.status})`); } return res.json(); } async function pollSession(sessionId) { try { const res = await fetch(`${API_BASE}/v1/developer/cli/sessions/${sessionId}`); if (!res.ok) return null; const data = await res.json(); if (data.status === "completed" && data.apiKey) { return { apiKey: data.apiKey, publishableKey: data.publishableKey || null, agent: data.agent || null }; } return null; } catch { return null; } } async function runInit(force2, interactive) { const cwd = process.cwd(); await printLogo(); if (!force2) { const existing = readExistingConfig(cwd); if (existing.apiKey && existing.publishableKey && existing.agent) { print(` Already configured. Secret key: ${existing.apiKey.slice(0, 10)}...`); print(` Publishable key: ${existing.publishableKey.slice(0, 10)}...`); print(` Agent: ${existing.agent}`); print(""); print(" Run \x1B[1mnpx configure setup --force\x1B[0m to reconfigure."); print(""); return; } if (existing.apiKey || existing.publishableKey || existing.agent) { const missing = [ ["CONFIGURE_API_KEY", existing.apiKey], ["CONFIGURE_PUBLISHABLE_KEY", existing.publishableKey], ["CONFIGURE_AGENT", existing.agent] ].filter(([, value]) => !value).map(([key]) => key); print(` Existing Configure env is incomplete. Missing: ${missing.join(", ")}`); print(" Continuing setup to write the full hosted iframe configuration."); print(""); } } if (interactive) { await waitForEnter(); print(""); } let sessionId; let browserUrl; try { const session = await createSession(); sessionId = session.sessionId; browserUrl = session.browserUrl; } catch (err) { print(` \x1B[31m\u2717\x1B[0m ${err instanceof Error ? err.message : String(err)}`); process.exit(1); } if (interactive) { print(" Opening browser..."); print(` \x1B[2m${browserUrl}\x1B[0m`); print(""); await openBrowser(browserUrl); } else { print(" Open this URL to continue setup:"); print(` \x1B[2m${browserUrl}\x1B[0m`); print(""); } const sigintHandler = () => { process.stdout.write("\r \x1B[33m\u26A0\x1B[0m Cancelled \n"); print(""); print(" Your keys are on the dashboard at \x1B[1mconfigure.dev/login\x1B[0m"); print(" Run \x1B[1mnpx configure setup\x1B[0m to try again."); print(""); process.exit(0); }; process.on("SIGINT", sigintHandler); const startTime = Date.now(); let result = null; let frame = 0; while (Date.now() - startTime < TIMEOUT_MS) { result = await pollSession(sessionId); if (result) break; process.stdout.write(`\r ${SPINNER[frame++ % SPINNER.length]} Waiting for sign-in...`); await sleep(POLL_INTERVAL_MS); } process.removeListener("SIGINT", sigintHandler); if (!result) { process.stdout.write("\r \x1B[31m\u2717\x1B[0m Timed out (15 minutes) \n"); print(""); print(" Run \x1B[1mnpx configure setup\x1B[0m to try again."); print(""); process.exit(1); } process.stdout.write("\r \x1B[32m\u2713\x1B[0m Signed in \n"); const { apiKey, publishableKey, agent } = result; if (!apiKey.startsWith("sk_") && !apiKey.startsWith("cfg_")) { print(` \x1B[33m\u26A0\x1B[0m Secret key has unexpected prefix (expected sk_). Continuing anyway.`); } if (!publishableKey) { print(` \x1B[33m\u26A0\x1B[0m Publishable key was not returned. Hosted browser auth requires CONFIGURE_PUBLISHABLE_KEY.`); print(` Visit \x1B[1mhttps://configure.dev/login\x1B[0m if you need to copy it manually.`); } else if (!publishableKey.startsWith("pk_")) { print(` \x1B[33m\u26A0\x1B[0m Publishable key has unexpected prefix (expected pk_). Continuing anyway.`); } if (!agent) { print(` \x1B[33m\u26A0\x1B[0m No agent handle returned. You can set CONFIGURE_AGENT in .env manually.`); } writeEnvFile(apiKey, publishableKey, agent, cwd); updateGitignore(cwd); print(""); if (agent) { print(` Agent: ${agent}`); } print(` Secret key: ${apiKey.slice(0, 10)}...`); if (publishableKey) { print(` Publishable key: ${publishableKey}`); } print(" Written to \x1B[1m.env\x1B[0m"); print(""); print(` Next: tell your coding agent to read \x1B[1mhttps://configure.dev/skill.md\x1B[0m.`); print(` The browser flow may ask you to choose an existing agent or create a new one for this project.`); print(` The canonical starter lives in the Configure template and on \x1B[1mhttps://platform.configure.dev/quickstart\x1B[0m.`); print(` For the complete API reference, read \x1B[1mhttps://configure.dev/llms.txt\x1B[0m`); print(""); } var args = process.argv.slice(2); var cmd = args.find((arg) => !arg.startsWith("-")); var showHelp = args.includes("--help") || args.includes("-h"); var force = args.includes("--force"); if (showHelp) { printLogo().then(() => { print(" Usage:"); print(" npx configure Link Configure to this project"); print(" npx configure setup Create or attach an agent and write .env"); print(" npx configure setup --force Reconfigure this project"); print(" npx configure init Alias for setup"); print(""); print(" Setup creates a Configure-owned browser flow, lets you choose"); print(" or create the agent for this project, then writes the hosted"); print(" auth env vars into \x1B[1m.env\x1B[0m when you complete the flow."); print(" \x1B[1mnpx configure setup\x1B[0m prints the auth URL; plain \x1B[1mnpx configure\x1B[0m"); print(" also opens it for interactive local use."); print(""); }); } else if (cmd === "init" || cmd === "setup") { runInit(force, false).catch((err) => { process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)} `); process.exit(1); }); } else if (!cmd) { runInit(false, true).catch((err) => { process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)} `); process.exit(1); }); } else { process.stderr.write(`Unknown command: ${cmd} `); process.stderr.write("Run \x1B[1mnpx configure --help\x1B[0m for usage.\n"); process.exit(1); }