@01.software/init
Advanced tools
| #!/usr/bin/env node | ||
| // src/ai-docs.ts | ||
| function normalizeActiveCollections(collections) { | ||
| if (Array.isArray(collections?.active)) return collections.active; | ||
| return []; | ||
| } | ||
| function generateClaudeMd(ctx) { | ||
| const featuresSection = ctx.features && ctx.features.length > 0 ? ctx.features.map((f) => `- ${f}`).join("\n") : "- See console"; | ||
| const collectionsSection = ctx.collections && ctx.collections.length > 0 ? ctx.collections.join(", ") : "Run `01 schema list`"; | ||
| return `# 01.software SDK \u2014 ${ctx.tenantName} | ||
| ## Connection | ||
| - Publishable Key: \`NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY\` (env) | ||
| - Secret Key: \`SOFTWARE_SECRET_KEY\` (env) | ||
| - MCP: \`.mcp.json\` | ||
| ## Active Features | ||
| ${featuresSection} | ||
| ## Active Collections | ||
| ${collectionsSection} | ||
| ## MCP Quick Reference | ||
| | Tool | Use | | ||
| |------|-----| | ||
| | \`query-collection\` | List/filter documents | | ||
| | \`get-collection-schema\` | Inspect tenant-aware fields for a collection | | ||
| | \`update-field-config\` | Hide unused fields | | ||
| | \`get-tenant-context\` | Show active features & collections | | ||
| ## CLI | ||
| - \`01 query <collection>\` \u2014 query data | ||
| - \`01 schema show <collection>\` \u2014 inspect fields | ||
| - \`01 schema list\` \u2014 list all collections | ||
| ## Initial Setup | ||
| Run \`/01software-field-config\` in Claude Code to configure field visibility for your use case. | ||
| Use \`get-collection-schema\` or \`01 schema show <collection>\` for live field introspection instead of relying on this document as a schema snapshot. | ||
| `; | ||
| } | ||
| function getSkillFiles() { | ||
| return [ | ||
| { | ||
| dirName: "01software-field-config", | ||
| content: `--- | ||
| name: 01software-field-config | ||
| description: Configure field visibility for this tenant \u2014 hide unused collections and fields via MCP | ||
| disable-model-invocation: true | ||
| --- | ||
| Steps: | ||
| 1. Use \`list-configurable-fields\` to see current visibility settings | ||
| 2. Identify fields/collections not needed for your use case | ||
| 3. Use \`update-field-config\` to hide them | ||
| Common setups: | ||
| - Blog only: hide \`ecommerce\`, \`customers\`, \`videos\` collections | ||
| - Store: hide \`articles\`, \`documents\`, \`galleries\`, \`canvas\` collections | ||
| - Minimal: hide all except the collections you actively use | ||
| Ask me: "Show current field config" or "Hide ecommerce fields" | ||
| ` | ||
| }, | ||
| { | ||
| dirName: "01software-query", | ||
| content: `--- | ||
| name: 01software-query | ||
| description: Query 01.software collections via MCP or CLI with filter, sort, and pagination examples | ||
| --- | ||
| Query collections using the MCP \`query-collection\` tool or CLI. | ||
| MCP examples: | ||
| - List products: \`query-collection\` with collection="products", limit=10 | ||
| - Filter by status: add where={"status":{"equals":"published"}} | ||
| - Sort by date: sort="-createdAt" | ||
| - Paginate: page=2, limit=20 | ||
| CLI examples: | ||
| - \`01 query products --limit 10\` | ||
| - \`01 query orders --where '{"status":{"equals":"paid"}}'\` | ||
| - \`01 schema show products\` \u2014 inspect available fields | ||
| SDK (server): | ||
| \`\`\`typescript | ||
| const { docs } = await serverClient.collections.from('products').find({ | ||
| where: { status: { equals: 'published' } }, | ||
| sort: '-createdAt', | ||
| limit: 10, | ||
| }) | ||
| \`\`\` | ||
| ` | ||
| }, | ||
| { | ||
| dirName: "01software-order-flow", | ||
| content: `--- | ||
| name: 01software-order-flow | ||
| description: Order lifecycle reference \u2014 create, pay, fulfill, and return flows for 01.software | ||
| --- | ||
| Complete order flow from creation to fulfillment. | ||
| States: pending \u2192 paid \u2192 preparing \u2192 shipped \u2192 delivered \u2192 confirmed | ||
| 1. Create order: \`create-order\` with orderNumber, customerSnapshot, orderProducts, totalAmount | ||
| 2. Mark paid: \`update-order\` with status="paid" (after payment gateway confirms) | ||
| 3. Fulfill: \`create-fulfillment\` with items and carrier/trackingNumber | ||
| 4. Returns: \`create-return\` or \`return-with-refund\` (atomic) | ||
| Free orders: omit paymentId, totalAmount=0 \u2192 auto-transitions to paid | ||
| CLI: \`01 order create --help\` for full options | ||
| ` | ||
| }, | ||
| { | ||
| dirName: "01software-schema", | ||
| content: `--- | ||
| name: 01software-schema | ||
| description: Inspect 01.software collection schemas and available fields via MCP or CLI | ||
| --- | ||
| Inspect collection schemas to understand available fields. | ||
| MCP: use \`get-collection-schema\` with collection | ||
| CLI: | ||
| - \`01 schema list\` \u2014 all available collections | ||
| - \`01 schema show <collection>\` \u2014 field names, types, required status | ||
| Common collections: products, orders, customers, articles, documents, images | ||
| Use \`get-tenant-context\` to see which collections are active for this tenant. | ||
| ` | ||
| } | ||
| ]; | ||
| } | ||
| async function fetchTenantContext(publishableKey, secretKey) { | ||
| try { | ||
| const apiUrl = process.env.SOFTWARE_API_URL || "https://api.01.software"; | ||
| const res = await fetch(`${apiUrl}/api/tenants/context`, { | ||
| headers: { | ||
| "X-Publishable-Key": publishableKey, | ||
| Authorization: `Bearer ${secretKey}` | ||
| } | ||
| }); | ||
| if (!res.ok) return null; | ||
| const data = await res.json(); | ||
| return { | ||
| tenantName: data.tenant?.name || "", | ||
| features: data.features || [], | ||
| collections: normalizeActiveCollections(data.collections) | ||
| }; | ||
| } catch { | ||
| return null; | ||
| } | ||
| } | ||
| export { | ||
| generateClaudeMd, | ||
| getSkillFiles, | ||
| fetchTenantContext | ||
| }; | ||
| //# sourceMappingURL=chunk-3MXIG3ZL.js.map |
| {"version":3,"sources":["../src/ai-docs.ts"],"sourcesContent":["export interface TenantContext {\n tenantName: string\n features?: string[]\n collections?: string[]\n}\n\ninterface TenantContextApiResponse {\n tenant?: { name?: string }\n features?: string[]\n collections?: { active?: string[]; inactive?: string[] }\n}\n\nfunction normalizeActiveCollections(\n collections: TenantContextApiResponse['collections'],\n): string[] {\n if (Array.isArray(collections?.active)) return collections.active\n return []\n}\n\n// ── CLAUDE.md ────────────────────────────────────────────────────────\n\nexport function generateClaudeMd(ctx: TenantContext): string {\n const featuresSection =\n ctx.features && ctx.features.length > 0\n ? ctx.features.map((f) => `- ${f}`).join('\\n')\n : '- See console'\n\n const collectionsSection =\n ctx.collections && ctx.collections.length > 0\n ? ctx.collections.join(', ')\n : 'Run `01 schema list`'\n\n return `# 01.software SDK — ${ctx.tenantName}\n\n## Connection\n- Publishable Key: \\`NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY\\` (env)\n- Secret Key: \\`SOFTWARE_SECRET_KEY\\` (env)\n- MCP: \\`.mcp.json\\`\n\n## Active Features\n${featuresSection}\n\n## Active Collections\n${collectionsSection}\n\n## MCP Quick Reference\n| Tool | Use |\n|------|-----|\n| \\`query-collection\\` | List/filter documents |\n| \\`get-collection-schema\\` | Inspect tenant-aware fields for a collection |\n| \\`update-field-config\\` | Hide unused fields |\n| \\`get-tenant-context\\` | Show active features & collections |\n\n## CLI\n- \\`01 query <collection>\\` — query data\n- \\`01 schema show <collection>\\` — inspect fields\n- \\`01 schema list\\` — list all collections\n\n## Initial Setup\nRun \\`/01software-field-config\\` in Claude Code to configure field visibility for your use case.\nUse \\`get-collection-schema\\` or \\`01 schema show <collection>\\` for live field introspection instead of relying on this document as a schema snapshot.\n`\n}\n\n// ── Skill files ──────────────────────────────────────────────────────\n\nexport function getSkillFiles(): Array<{ dirName: string; content: string }> {\n return [\n {\n dirName: '01software-field-config',\n content: `---\nname: 01software-field-config\ndescription: Configure field visibility for this tenant — hide unused collections and fields via MCP\ndisable-model-invocation: true\n---\n\nSteps:\n1. Use \\`list-configurable-fields\\` to see current visibility settings\n2. Identify fields/collections not needed for your use case\n3. Use \\`update-field-config\\` to hide them\n\nCommon setups:\n- Blog only: hide \\`ecommerce\\`, \\`customers\\`, \\`videos\\` collections\n- Store: hide \\`articles\\`, \\`documents\\`, \\`galleries\\`, \\`canvas\\` collections\n- Minimal: hide all except the collections you actively use\n\nAsk me: \"Show current field config\" or \"Hide ecommerce fields\"\n`,\n },\n {\n dirName: '01software-query',\n content: `---\nname: 01software-query\ndescription: Query 01.software collections via MCP or CLI with filter, sort, and pagination examples\n---\n\nQuery collections using the MCP \\`query-collection\\` tool or CLI.\n\nMCP examples:\n- List products: \\`query-collection\\` with collection=\"products\", limit=10\n- Filter by status: add where={\"status\":{\"equals\":\"published\"}}\n- Sort by date: sort=\"-createdAt\"\n- Paginate: page=2, limit=20\n\nCLI examples:\n- \\`01 query products --limit 10\\`\n- \\`01 query orders --where '{\"status\":{\"equals\":\"paid\"}}'\\`\n- \\`01 schema show products\\` — inspect available fields\n\nSDK (server):\n\\`\\`\\`typescript\nconst { docs } = await serverClient.collections.from('products').find({\n where: { status: { equals: 'published' } },\n sort: '-createdAt',\n limit: 10,\n})\n\\`\\`\\`\n`,\n },\n {\n dirName: '01software-order-flow',\n content: `---\nname: 01software-order-flow\ndescription: Order lifecycle reference — create, pay, fulfill, and return flows for 01.software\n---\n\nComplete order flow from creation to fulfillment.\n\nStates: pending → paid → preparing → shipped → delivered → confirmed\n\n1. Create order: \\`create-order\\` with orderNumber, customerSnapshot, orderProducts, totalAmount\n2. Mark paid: \\`update-order\\` with status=\"paid\" (after payment gateway confirms)\n3. Fulfill: \\`create-fulfillment\\` with items and carrier/trackingNumber\n4. Returns: \\`create-return\\` or \\`return-with-refund\\` (atomic)\n\nFree orders: omit paymentId, totalAmount=0 → auto-transitions to paid\n\nCLI: \\`01 order create --help\\` for full options\n`,\n },\n {\n dirName: '01software-schema',\n content: `---\nname: 01software-schema\ndescription: Inspect 01.software collection schemas and available fields via MCP or CLI\n---\n\nInspect collection schemas to understand available fields.\n\nMCP: use \\`get-collection-schema\\` with collection\n\nCLI:\n- \\`01 schema list\\` — all available collections\n- \\`01 schema show <collection>\\` — field names, types, required status\n\nCommon collections: products, orders, customers, articles, documents, images\nUse \\`get-tenant-context\\` to see which collections are active for this tenant.\n`,\n },\n ]\n}\n\n// ── Tenant context fetch ─────────────────────────────────────────────\n\nexport async function fetchTenantContext(\n publishableKey: string,\n secretKey: string,\n): Promise<TenantContext | null> {\n try {\n const apiUrl = process.env.SOFTWARE_API_URL || 'https://api.01.software'\n // secretKey is now an opaque sk01_/pat01_ bearer token — send it directly.\n const res = await fetch(`${apiUrl}/api/tenants/context`, {\n headers: {\n 'X-Publishable-Key': publishableKey,\n Authorization: `Bearer ${secretKey}`,\n },\n })\n if (!res.ok) return null\n const data = (await res.json()) as TenantContextApiResponse\n return {\n tenantName: data.tenant?.name || '',\n features: data.features || [],\n collections: normalizeActiveCollections(data.collections),\n }\n } catch {\n return null\n }\n}\n"],"mappings":";;;AAYA,SAAS,2BACP,aACU;AACV,MAAI,MAAM,QAAQ,aAAa,MAAM,EAAG,QAAO,YAAY;AAC3D,SAAO,CAAC;AACV;AAIO,SAAS,iBAAiB,KAA4B;AAC3D,QAAM,kBACJ,IAAI,YAAY,IAAI,SAAS,SAAS,IAClC,IAAI,SAAS,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,EAAE,KAAK,IAAI,IAC3C;AAEN,QAAM,qBACJ,IAAI,eAAe,IAAI,YAAY,SAAS,IACxC,IAAI,YAAY,KAAK,IAAI,IACzB;AAEN,SAAO,4BAAuB,IAAI,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ5C,eAAe;AAAA;AAAA;AAAA,EAGf,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBpB;AAIO,SAAS,gBAA6D;AAC3E,SAAO;AAAA,IACL;AAAA,MACE,SAAS;AAAA,MACT,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAkBX;AAAA,IACA;AAAA,MACE,SAAS;AAAA,MACT,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IA2BX;AAAA,IACA;AAAA,MACE,SAAS;AAAA,MACT,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAkBX;AAAA,IACA;AAAA,MACE,SAAS;AAAA,MACT,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAgBX;AAAA,EACF;AACF;AAIA,eAAsB,mBACpB,gBACA,WAC+B;AAC/B,MAAI;AACF,UAAM,SAAS,QAAQ,IAAI,oBAAoB;AAE/C,UAAM,MAAM,MAAM,MAAM,GAAG,MAAM,wBAAwB;AAAA,MACvD,SAAS;AAAA,QACP,qBAAqB;AAAA,QACrB,eAAe,UAAU,SAAS;AAAA,MACpC;AAAA,IACF,CAAC;AACD,QAAI,CAAC,IAAI,GAAI,QAAO;AACpB,UAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,WAAO;AAAA,MACL,YAAY,KAAK,QAAQ,QAAQ;AAAA,MACjC,UAAU,KAAK,YAAY,CAAC;AAAA,MAC5B,aAAa,2BAA2B,KAAK,WAAW;AAAA,IAC1D;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]} |
| #!/usr/bin/env node | ||
| import { | ||
| fetchTenantContext, | ||
| generateClaudeMd, | ||
| getSkillFiles | ||
| } from "./chunk-3MXIG3ZL.js"; | ||
| import { | ||
| chmodSecretFile, | ||
| readEnvValue, | ||
| replaceTomlMcpSection, | ||
| setEnvValue, | ||
| writeEnvFile, | ||
| writeSecretGlobalConfig | ||
| } from "./chunk-UA7WNT2F.js"; | ||
| import { | ||
| CODEX_MCP_SECTION_MARKER, | ||
| getAnalyticsTemplate, | ||
| getClientTemplate, | ||
| getCodexMcpTomlSection, | ||
| getEnvContent, | ||
| getMcpConfigTemplate, | ||
| getMcpRootKey, | ||
| getMcpServerEntry, | ||
| getQueryProviderTemplate, | ||
| getServerTemplate | ||
| } from "./chunk-TBGKXE3Q.js"; | ||
| // src/init.ts | ||
| import fs2 from "fs"; | ||
| import path2 from "path"; | ||
| import os from "os"; | ||
| import { execSync } from "child_process"; | ||
| import pc2 from "picocolors"; | ||
| import prompts from "prompts"; | ||
| // src/detect.ts | ||
| import fs from "fs"; | ||
| import path from "path"; | ||
| function detectProject(cwd) { | ||
| const pkgPath = path.join(cwd, "package.json"); | ||
| const hasPackageJson = fs.existsSync(pkgPath); | ||
| let env = "node"; | ||
| let hasSdk = false; | ||
| let hasReactQuery = false; | ||
| let parseError = false; | ||
| if (hasPackageJson) { | ||
| let pkg; | ||
| try { | ||
| pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8")); | ||
| } catch { | ||
| return { | ||
| hasPackageJson: true, | ||
| parseError: true, | ||
| env: "node", | ||
| packageManager: null, | ||
| hasSdk: false, | ||
| hasReactQuery: false, | ||
| srcDir: false | ||
| }; | ||
| } | ||
| const deps = { | ||
| ...pkg.dependencies || {}, | ||
| ...pkg.devDependencies || {} | ||
| }; | ||
| hasSdk = "@01.software/sdk" in deps; | ||
| hasReactQuery = "@tanstack/react-query" in deps; | ||
| if ("next" in deps) { | ||
| env = "nextjs"; | ||
| } else if ("astro" in deps || "@astrojs/node" in deps) { | ||
| env = "other"; | ||
| } else if ("@remix-run/node" in deps || "@remix-run/react" in deps) { | ||
| env = "other"; | ||
| } else if ("@sveltejs/kit" in deps) { | ||
| env = "other"; | ||
| } else if ("react" in deps) { | ||
| if ("vite" in deps) { | ||
| env = "react-vite"; | ||
| } else if ("react-scripts" in deps) { | ||
| env = "react-cra"; | ||
| } else { | ||
| env = "node"; | ||
| } | ||
| } | ||
| } | ||
| let packageManager = null; | ||
| if (fs.existsSync(path.join(cwd, "pnpm-lock.yaml"))) { | ||
| packageManager = "pnpm"; | ||
| } else if (fs.existsSync(path.join(cwd, "yarn.lock"))) { | ||
| packageManager = "yarn"; | ||
| } else if (fs.existsSync(path.join(cwd, "bun.lockb")) || fs.existsSync(path.join(cwd, "bun.lock"))) { | ||
| packageManager = "bun"; | ||
| } else if (fs.existsSync(path.join(cwd, "package-lock.json"))) { | ||
| packageManager = "npm"; | ||
| } | ||
| const srcDir = env === "nextjs" ? fs.existsSync(path.join(cwd, "src", "app")) : fs.existsSync(path.join(cwd, "src")); | ||
| return { hasPackageJson, parseError, env, packageManager, hasSdk, hasReactQuery, srcDir }; | ||
| } | ||
| function needsClient(env) { | ||
| return env === "nextjs" || env === "react-vite" || env === "react-cra" || env === "vanilla"; | ||
| } | ||
| function needsServer(env) { | ||
| return env === "nextjs" || env === "node" || env === "edge"; | ||
| } | ||
| function needsReactQuery(env) { | ||
| return env === "nextjs" || env === "react-vite" || env === "react-cra"; | ||
| } | ||
| function getPublishableKeyEnvVar(env) { | ||
| switch (env) { | ||
| case "nextjs": | ||
| return "NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY"; | ||
| case "react-vite": | ||
| return "VITE_SOFTWARE_PUBLISHABLE_KEY"; | ||
| case "react-cra": | ||
| return "REACT_APP_SOFTWARE_PUBLISHABLE_KEY"; | ||
| default: | ||
| return "SOFTWARE_PUBLISHABLE_KEY"; | ||
| } | ||
| } | ||
| // src/browser-auth.ts | ||
| import { randomBytes } from "crypto"; | ||
| import { createServer } from "http"; | ||
| import { execFile, exec } from "child_process"; | ||
| import { platform } from "os"; | ||
| import { URL } from "url"; | ||
| import pc from "picocolors"; | ||
| var DEFAULT_WEB_URL = process.env.SOFTWARE_WEB_URL || "https://01.software"; | ||
| var TIMEOUT_MS = 5 * 60 * 1e3; | ||
| function escapeHtml(s) { | ||
| return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """); | ||
| } | ||
| function openBrowser(url) { | ||
| const os2 = platform(); | ||
| const onError = () => { | ||
| console.log( | ||
| pc.yellow( | ||
| `Could not open browser automatically. Open this URL manually: | ||
| ${url}` | ||
| ) | ||
| ); | ||
| }; | ||
| if (os2 === "win32") { | ||
| exec(`start "" "${url}"`, (err) => { | ||
| if (err) onError(); | ||
| }); | ||
| } else { | ||
| const cmd = os2 === "darwin" ? "open" : "xdg-open"; | ||
| execFile(cmd, [url], (err) => { | ||
| if (err) onError(); | ||
| }); | ||
| } | ||
| } | ||
| var PAGE_STYLE = `*{margin:0;box-sizing:border-box} | ||
| body{font-family:system-ui,-apple-system,sans-serif;display:flex;justify-content:center;align-items:center;min-height:100vh;background:#fff;color:#252525} | ||
| @media(prefers-color-scheme:dark){body{background:#252525;color:#f5f5f5}} | ||
| .card{text-align:center;padding:2rem 2.5rem;border-radius:10px;max-width:380px;width:100%} | ||
| .icon{width:40px;height:40px;margin:0 auto 1rem;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:1.25rem} | ||
| .icon.ok{background:rgba(0,0,0,.05);color:#252525} | ||
| .icon.err{background:rgba(220,38,38,.08);color:#dc2626} | ||
| @media(prefers-color-scheme:dark){.icon.ok{background:rgba(255,255,255,.08);color:#f5f5f5}} | ||
| h1{font-size:.875rem;font-weight:600;margin-bottom:.375rem} | ||
| p{font-size:.75rem;color:#737373;line-height:1.5}`; | ||
| var SUCCESS_HTML = `<!DOCTYPE html> | ||
| <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Login</title> | ||
| <style>${PAGE_STYLE}</style> | ||
| </head><body><div class="card"><div class="icon ok">\u2713</div><h1>Authenticated</h1><p>You can close this tab and return to the terminal.</p></div></body></html>`; | ||
| var ERROR_HTML = (msg) => `<!DOCTYPE html> | ||
| <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Login Error</title> | ||
| <style>${PAGE_STYLE}</style> | ||
| </head><body><div class="card"><div class="icon err">!</div><h1>Authentication failed</h1><p>${escapeHtml(msg)}</p></div></body></html>`; | ||
| async function exchangeCode(webUrl, code) { | ||
| const url = `${webUrl}/api/cli/exchange`; | ||
| try { | ||
| const res = await fetch(url, { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ code }) | ||
| }); | ||
| if (!res.ok) { | ||
| const body = await res.text().catch(() => ""); | ||
| console.error( | ||
| pc.red( | ||
| `Exchange failed: HTTP ${res.status} from ${url}${body ? ` \u2014 ${body.slice(0, 200)}` : ""}` | ||
| ) | ||
| ); | ||
| return null; | ||
| } | ||
| const data = await res.json(); | ||
| if (typeof data.publishableKey !== "string" || typeof data.secretKey !== "string" || typeof data.tenantName !== "string" || typeof data.tenantId !== "string") { | ||
| console.error(pc.red(`Exchange failed: malformed response from ${url}`)); | ||
| return null; | ||
| } | ||
| return { | ||
| publishableKey: data.publishableKey, | ||
| secretKey: data.secretKey, | ||
| tenantName: data.tenantName, | ||
| tenantId: data.tenantId | ||
| }; | ||
| } catch (err) { | ||
| console.error( | ||
| pc.red( | ||
| `Exchange request to ${url} failed: ${err instanceof Error ? err.message : String(err)}` | ||
| ) | ||
| ); | ||
| return null; | ||
| } | ||
| } | ||
| async function startBrowserAuth(options) { | ||
| const state = randomBytes(32).toString("hex"); | ||
| const webUrl = options?.webUrl ?? DEFAULT_WEB_URL; | ||
| return new Promise((resolve, reject) => { | ||
| const server = createServer((req, res) => { | ||
| if (!req.url) { | ||
| res.writeHead(400).end(); | ||
| return; | ||
| } | ||
| const url = new URL(req.url, `http://localhost`); | ||
| if (url.pathname !== "/callback" || req.method !== "GET") { | ||
| res.writeHead(404).end(); | ||
| return; | ||
| } | ||
| const error = url.searchParams.get("error"); | ||
| if (error) { | ||
| res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(ERROR_HTML(error)); | ||
| console.error(pc.red(`Login failed: ${error}`)); | ||
| cleanup(new Error(`Login failed: ${error}`)); | ||
| return; | ||
| } | ||
| const code = url.searchParams.get("code"); | ||
| const receivedState = url.searchParams.get("state"); | ||
| if (!code || !receivedState) { | ||
| res.writeHead(400, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(ERROR_HTML("Missing code or state.")); | ||
| cleanup(new Error("Login failed: missing code or state.")); | ||
| return; | ||
| } | ||
| if (receivedState !== state) { | ||
| res.writeHead(403, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(ERROR_HTML("State mismatch.")); | ||
| console.error(pc.red("Login failed: state mismatch.")); | ||
| cleanup(new Error("Login failed: state mismatch.")); | ||
| return; | ||
| } | ||
| exchangeCode(webUrl, code).then((creds) => { | ||
| if (!creds) { | ||
| res.writeHead(400, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(ERROR_HTML("Invalid or expired code.")); | ||
| cleanup(new Error("Login failed: code exchange failed.")); | ||
| return; | ||
| } | ||
| res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(SUCCESS_HTML); | ||
| console.log(pc.green(` | ||
| Logged in successfully!`)); | ||
| console.log(pc.dim(`Tenant: ${creds.tenantName}`)); | ||
| cleanup(null, creds); | ||
| }); | ||
| }); | ||
| let timeout; | ||
| let completed = false; | ||
| function cleanup(err, result) { | ||
| if (completed) return; | ||
| completed = true; | ||
| clearTimeout(timeout); | ||
| server.closeAllConnections?.(); | ||
| server.close(() => { | ||
| if (err) { | ||
| reject(err); | ||
| } else { | ||
| resolve(result); | ||
| } | ||
| }); | ||
| } | ||
| server.listen(0, "127.0.0.1", () => { | ||
| const addr = server.address(); | ||
| if (!addr || typeof addr === "string") { | ||
| reject(new Error("Failed to start local server.")); | ||
| return; | ||
| } | ||
| const port = addr.port; | ||
| timeout = setTimeout(() => { | ||
| console.error(pc.red("\nLogin timed out (5 minutes). Please try again.")); | ||
| cleanup(new Error("Login timed out")); | ||
| }, TIMEOUT_MS); | ||
| const params = new URLSearchParams({ port: String(port), state }); | ||
| if (options?.tenantId) { | ||
| params.set("tenantId", options.tenantId); | ||
| } | ||
| const loginUrl = `${webUrl}/cli-auth?${params.toString()}`; | ||
| console.log(pc.dim("Opening browser for login...")); | ||
| console.log(pc.dim(`If the browser does not open, visit: | ||
| ${loginUrl}`)); | ||
| openBrowser(loginUrl); | ||
| }); | ||
| server.on("error", (err) => { | ||
| reject(err); | ||
| }); | ||
| }); | ||
| } | ||
| // src/init.ts | ||
| var SECRET_KEY_ENV_VAR = "SOFTWARE_SECRET_KEY"; | ||
| async function init(cwd, info, answers, deps = {}) { | ||
| const { packageManager, srcDir } = info; | ||
| const env = answers.env; | ||
| const baseDir = srcDir ? path2.join(cwd, "src") : cwd; | ||
| const browserAuth = deps.startBrowserAuth ?? startBrowserAuth; | ||
| const publishableKeyEnvVar = getPublishableKeyEnvVar(env); | ||
| const wantsClient = needsClient(env); | ||
| const wantsServer = needsServer(env); | ||
| const wantsReactQuery = needsReactQuery(env); | ||
| const plan = await planConflictsAndEnv(cwd, baseDir, env, answers); | ||
| const installResult = deps.skipInstall ? { | ||
| installFailed: false, | ||
| installSkipped: true, | ||
| installCmd: buildAddCmd(packageManager, hasPnpmWorkspace(cwd), [ | ||
| "@01.software/sdk" | ||
| ]) | ||
| } : installDeps( | ||
| cwd, | ||
| packageManager, | ||
| info.hasSdk, | ||
| info.hasReactQuery, | ||
| wantsReactQuery | ||
| ); | ||
| if (wantsClient || wantsReactQuery || wantsServer) { | ||
| fs2.mkdirSync(path2.join(baseDir, "lib", "software"), { recursive: true }); | ||
| } | ||
| const libDir = path2.join(baseDir, "lib", "software"); | ||
| if (wantsClient) { | ||
| await writeFileWithPolicy( | ||
| cwd, | ||
| path2.join(libDir, "client.ts"), | ||
| getClientTemplate(env, publishableKeyEnvVar), | ||
| plan.policy | ||
| ); | ||
| await writeFileWithPolicy( | ||
| cwd, | ||
| path2.join(libDir, "analytics.ts"), | ||
| getAnalyticsTemplate(env, publishableKeyEnvVar), | ||
| plan.policy | ||
| ); | ||
| } | ||
| if (wantsReactQuery) { | ||
| await writeFileWithPolicy( | ||
| cwd, | ||
| path2.join(libDir, "query-provider.tsx"), | ||
| getQueryProviderTemplate(env), | ||
| plan.policy | ||
| ); | ||
| } | ||
| if (wantsServer) { | ||
| await writeFileWithPolicy( | ||
| cwd, | ||
| path2.join(libDir, "server.ts"), | ||
| getServerTemplate(env, publishableKeyEnvVar, SECRET_KEY_ENV_VAR), | ||
| plan.policy | ||
| ); | ||
| } | ||
| if (plan.envFile && answers.authMethod !== "browser") { | ||
| await writeEnv( | ||
| cwd, | ||
| plan.envFile, | ||
| answers.publishableKey || "", | ||
| answers.secretKey || "", | ||
| publishableKeyEnvVar, | ||
| wantsServer ? SECRET_KEY_ENV_VAR : null, | ||
| plan.policy | ||
| ); | ||
| } | ||
| let publishableKey = answers.publishableKey; | ||
| let secretKey = answers.secretKey; | ||
| let tenantName = ""; | ||
| if (answers.authMethod === "browser" && answers.aiTools.length > 0) { | ||
| try { | ||
| console.log(); | ||
| const creds = await browserAuth(); | ||
| publishableKey = creds.publishableKey; | ||
| secretKey = creds.secretKey; | ||
| tenantName = creds.tenantName; | ||
| if (plan.envFile && publishableKey) { | ||
| await writeEnv( | ||
| cwd, | ||
| plan.envFile, | ||
| publishableKey, | ||
| secretKey, | ||
| publishableKeyEnvVar, | ||
| wantsServer ? SECRET_KEY_ENV_VAR : null, | ||
| "overwrite", | ||
| true | ||
| ); | ||
| } | ||
| } catch (err) { | ||
| console.log( | ||
| pc2.yellow(" Browser auth skipped:"), | ||
| err instanceof Error ? err.message : String(err) | ||
| ); | ||
| } | ||
| } | ||
| if (answers.aiTools.length > 0) { | ||
| for (const tool of answers.aiTools) { | ||
| await writeMcpConfig(tool, cwd); | ||
| } | ||
| addToGitignore(cwd, answers.aiTools); | ||
| if (answers.aiTools.includes("claude")) { | ||
| await writeClaudeDocs(cwd, publishableKey, secretKey, tenantName, plan.policy); | ||
| } | ||
| } | ||
| return { | ||
| ...installResult, | ||
| publishableKey: publishableKey || void 0, | ||
| secretKey: secretKey || void 0, | ||
| tenantName: tenantName || void 0 | ||
| }; | ||
| } | ||
| function installDeps(cwd, pm, hasSdk, hasReactQuery, wantsReactQuery) { | ||
| const allDeps = [ | ||
| { name: "@01.software/sdk", installed: hasSdk, needed: true }, | ||
| { name: "@tanstack/react-query", installed: hasReactQuery, needed: wantsReactQuery } | ||
| ]; | ||
| const fullList = allDeps.filter((d) => d.needed).map((d) => d.name); | ||
| const toInstall = allDeps.filter((d) => d.needed && !d.installed).map((d) => d.name); | ||
| const fullCmd = buildAddCmd(pm, hasPnpmWorkspace(cwd), fullList); | ||
| if (toInstall.length === 0) { | ||
| console.log(pc2.dim(` Dependencies already installed: ${fullList.join(", ")}`)); | ||
| return { installFailed: false, installSkipped: true, installCmd: fullCmd }; | ||
| } | ||
| const addCmd = buildAddCmd(pm, hasPnpmWorkspace(cwd), toInstall); | ||
| console.log(pc2.dim(` Installing ${toInstall.join(" and ")}...`)); | ||
| const wsPatched = pm === "pnpm" && patchPnpmWorkspace(cwd); | ||
| let installFailed = false; | ||
| try { | ||
| execSync(addCmd, { cwd, stdio: "pipe" }); | ||
| console.log(pc2.green(" Installed"), toInstall.join(", ")); | ||
| } catch (error) { | ||
| installFailed = true; | ||
| const err = error; | ||
| const msg = String(err.stderr || "").trim() || String(err.stdout || "").trim() || String(error); | ||
| console.log(pc2.yellow(" Install failed \u2014 continuing with scaffolding")); | ||
| const firstLines = msg.split("\n").slice(0, 3).map((l) => ` ${l}`).join("\n"); | ||
| if (firstLines) console.log(pc2.dim(firstLines)); | ||
| console.log(pc2.dim(` Run manually: ${addCmd}`)); | ||
| } finally { | ||
| if (wsPatched) restorePnpmWorkspace(cwd); | ||
| } | ||
| return { installFailed, installSkipped: false, installCmd: addCmd }; | ||
| } | ||
| function buildAddCmd(pm, hasPnpmWs, deps) { | ||
| const pkgs = deps.join(" "); | ||
| switch (pm) { | ||
| case "pnpm": | ||
| return hasPnpmWs ? `pnpm add -w ${pkgs}` : `pnpm add ${pkgs}`; | ||
| case "yarn": | ||
| return `yarn add ${pkgs}`; | ||
| case "bun": | ||
| return `bun add ${pkgs}`; | ||
| default: | ||
| return `npm install ${pkgs}`; | ||
| } | ||
| } | ||
| async function planConflictsAndEnv(cwd, baseDir, env, answers) { | ||
| const candidates = []; | ||
| const libDir = path2.join(baseDir, "lib", "software"); | ||
| if (needsClient(env)) candidates.push(path2.join(libDir, "client.ts")); | ||
| if (needsReactQuery(env)) candidates.push(path2.join(libDir, "query-provider.tsx")); | ||
| if (needsServer(env)) candidates.push(path2.join(libDir, "server.ts")); | ||
| if (answers.aiTools.includes("claude")) { | ||
| for (const { dirName } of getSkillFiles()) { | ||
| candidates.push(path2.join(cwd, ".claude", "skills", dirName, "SKILL.md")); | ||
| } | ||
| } | ||
| const conflicts = candidates.filter((p) => fs2.existsSync(p)); | ||
| let policy = "skip"; | ||
| if (conflicts.length > 0) { | ||
| console.log(pc2.yellow(` ${conflicts.length} file(s) already exist:`)); | ||
| for (const c of conflicts) console.log(pc2.dim(` ${path2.relative(cwd, c)}`)); | ||
| const { selected } = await prompts({ | ||
| type: "select", | ||
| name: "selected", | ||
| message: "How should I handle existing files?", | ||
| choices: [ | ||
| { title: "Keep existing (skip)", value: "skip" }, | ||
| { title: "Overwrite all", value: "overwrite" }, | ||
| { title: "Ask for each", value: "ask" } | ||
| ], | ||
| initial: 0 | ||
| }); | ||
| policy = selected ?? "skip"; | ||
| } | ||
| const envFile = env === "vanilla" || env === "edge" ? "" : await pickEnvFile(cwd, env); | ||
| return { policy, envFile }; | ||
| } | ||
| async function pickEnvFile(cwd, env) { | ||
| const candidates = [".env.local", ".env", ".env.development"]; | ||
| const existing = candidates.filter((f) => fs2.existsSync(path2.join(cwd, f))); | ||
| const preferred = env === "nextjs" ? ".env.local" : ".env"; | ||
| if (existing.length === 0) return preferred; | ||
| if (existing.length === 1 && existing[0] === preferred) return existing[0]; | ||
| const options = Array.from(/* @__PURE__ */ new Set([...existing, preferred])); | ||
| const choices = options.map((f) => ({ | ||
| title: f, | ||
| description: existing.includes(f) ? "exists" : "create", | ||
| value: f | ||
| })); | ||
| const initial = Math.max( | ||
| 0, | ||
| choices.findIndex((c) => c.value === preferred) | ||
| ); | ||
| const { file } = await prompts({ | ||
| type: "select", | ||
| name: "file", | ||
| message: "Which env file should I write SDK credentials to?", | ||
| choices, | ||
| initial | ||
| }); | ||
| return file ?? preferred; | ||
| } | ||
| async function writeFileWithPolicy(cwd, filePath, content, policy) { | ||
| const rel = path2.relative(cwd, filePath); | ||
| if (!fs2.existsSync(filePath)) { | ||
| fs2.mkdirSync(path2.dirname(filePath), { recursive: true }); | ||
| fs2.writeFileSync(filePath, content); | ||
| console.log(pc2.green(" Created"), rel); | ||
| return; | ||
| } | ||
| const existing = fs2.readFileSync(filePath, "utf-8"); | ||
| if (existing === content) { | ||
| console.log(pc2.dim(" Unchanged"), rel); | ||
| return; | ||
| } | ||
| let shouldWrite = false; | ||
| if (policy === "overwrite") { | ||
| shouldWrite = true; | ||
| } else if (policy === "ask") { | ||
| const { confirm } = await prompts({ | ||
| type: "confirm", | ||
| name: "confirm", | ||
| message: `Overwrite ${rel}?`, | ||
| initial: false | ||
| }); | ||
| shouldWrite = !!confirm; | ||
| } | ||
| if (shouldWrite) { | ||
| fs2.writeFileSync(filePath, content); | ||
| console.log(pc2.green(" Overwrote"), rel); | ||
| } else { | ||
| console.log(pc2.yellow(" Skipped"), rel, pc2.dim("(already exists)")); | ||
| } | ||
| } | ||
| async function writeEnv(cwd, envFile, publishableKey, secretKey, publishableKeyEnvVar, secretKeyEnvVar, policy, fromBrowserAuth = false) { | ||
| const envPath = path2.join(cwd, envFile); | ||
| const targets = [ | ||
| { name: publishableKeyEnvVar, value: publishableKey } | ||
| ]; | ||
| if (secretKeyEnvVar) { | ||
| targets.push({ name: secretKeyEnvVar, value: secretKey }); | ||
| } | ||
| if (!fs2.existsSync(envPath)) { | ||
| const initial = getEnvContent(publishableKey, secretKey, publishableKeyEnvVar, secretKeyEnvVar); | ||
| writeEnvFile(envPath, initial.trimStart()); | ||
| console.log(pc2.green(" Created"), envFile); | ||
| return; | ||
| } | ||
| let content = fs2.readFileSync(envPath, "utf-8"); | ||
| let modified = false; | ||
| let appendedHeader = false; | ||
| const headerAlreadyPresent = targets.some( | ||
| (t) => readEnvValue(content, t.name) !== null | ||
| ); | ||
| for (const { name, value } of targets) { | ||
| const existing = readEnvValue(content, name); | ||
| if (existing === null) { | ||
| if (!headerAlreadyPresent && !appendedHeader) { | ||
| if (content.length > 0 && !content.endsWith("\n")) content += "\n"; | ||
| content += "\n# 01.software\n"; | ||
| appendedHeader = true; | ||
| } | ||
| content = setEnvValue(content, name, value); | ||
| modified = true; | ||
| continue; | ||
| } | ||
| if (existing === value) continue; | ||
| if (!value) continue; | ||
| let shouldOverwrite = false; | ||
| if (policy === "overwrite") { | ||
| shouldOverwrite = true; | ||
| } else if (policy === "ask") { | ||
| const { confirm } = await prompts({ | ||
| type: "confirm", | ||
| name: "confirm", | ||
| message: `${name} already set in ${envFile}. Overwrite?`, | ||
| initial: fromBrowserAuth | ||
| }); | ||
| shouldOverwrite = !!confirm; | ||
| } | ||
| if (shouldOverwrite) { | ||
| content = setEnvValue(content, name, value); | ||
| modified = true; | ||
| } | ||
| } | ||
| if (modified) { | ||
| writeEnvFile(envPath, content); | ||
| console.log( | ||
| pc2.green(" Updated"), | ||
| envFile, | ||
| fromBrowserAuth ? pc2.dim("(SDK credentials)") : "" | ||
| ); | ||
| } else { | ||
| chmodSecretFile(envPath); | ||
| console.log(pc2.dim(" Unchanged"), envFile); | ||
| } | ||
| } | ||
| function resolveMcpLocation(tool, cwd) { | ||
| const home = os.homedir(); | ||
| switch (tool) { | ||
| case "claude": | ||
| return { | ||
| kind: "json", | ||
| absolutePath: path2.join(cwd, ".mcp.json"), | ||
| displayPath: ".mcp.json", | ||
| gitignoreEntry: ".mcp.json" | ||
| }; | ||
| case "cursor": | ||
| return { | ||
| kind: "json", | ||
| absolutePath: path2.join(cwd, ".cursor", "mcp.json"), | ||
| displayPath: ".cursor/mcp.json", | ||
| gitignoreEntry: ".cursor/mcp.json" | ||
| }; | ||
| case "vscode": | ||
| return { | ||
| kind: "json", | ||
| absolutePath: path2.join(cwd, ".vscode", "mcp.json"), | ||
| jsonClient: "vscode", | ||
| displayPath: ".vscode/mcp.json", | ||
| gitignoreEntry: ".vscode/mcp.json" | ||
| }; | ||
| case "windsurf": { | ||
| if (!home) return null; | ||
| const p = path2.join(home, ".codeium", "windsurf", "mcp_config.json"); | ||
| return { | ||
| kind: "json", | ||
| absolutePath: p, | ||
| jsonClient: "windsurf", | ||
| displayPath: p, | ||
| gitignoreEntry: null, | ||
| global: true | ||
| }; | ||
| } | ||
| case "codex": { | ||
| if (!home) return null; | ||
| const p = path2.join(home, ".codex", "config.toml"); | ||
| return { kind: "toml", absolutePath: p, displayPath: p, gitignoreEntry: null, global: true }; | ||
| } | ||
| case "gemini": { | ||
| if (!home) return null; | ||
| const p = path2.join(home, ".gemini", "settings.json"); | ||
| return { kind: "json", absolutePath: p, displayPath: p, gitignoreEntry: null, global: true }; | ||
| } | ||
| } | ||
| } | ||
| async function writeMcpConfig(tool, cwd) { | ||
| const loc = resolveMcpLocation(tool, cwd); | ||
| if (!loc) { | ||
| console.log(pc2.yellow(` Skipped ${tool}`), pc2.dim("(HOME not set)")); | ||
| return; | ||
| } | ||
| if (!loc.global) { | ||
| fs2.mkdirSync(path2.dirname(loc.absolutePath), { recursive: true }); | ||
| } | ||
| try { | ||
| if (loc.kind === "json") { | ||
| writeJsonMcp(loc); | ||
| } else { | ||
| writeTomlMcp(loc); | ||
| } | ||
| } catch (err) { | ||
| console.log( | ||
| pc2.yellow(` Skipped ${loc.displayPath}`), | ||
| pc2.dim(err instanceof Error ? err.message : String(err)) | ||
| ); | ||
| } | ||
| } | ||
| function writeJsonMcp(loc) { | ||
| const writeFile = loc.global ? (target, content) => writeSecretGlobalConfig(target, content) : (target, content) => fs2.writeFileSync(target, content); | ||
| if (!fs2.existsSync(loc.absolutePath)) { | ||
| writeFile(loc.absolutePath, getMcpConfigTemplate(loc.jsonClient)); | ||
| console.log(pc2.green(" Created"), loc.displayPath); | ||
| return; | ||
| } | ||
| const rootKey = getMcpRootKey(loc.jsonClient); | ||
| let existing; | ||
| try { | ||
| existing = JSON.parse(fs2.readFileSync(loc.absolutePath, "utf-8")); | ||
| } catch { | ||
| console.log(pc2.yellow(" Skipped"), loc.displayPath, pc2.dim("(could not parse existing file)")); | ||
| return; | ||
| } | ||
| const nextEntry = getMcpServerEntry(loc.jsonClient); | ||
| const existingServers = existing[rootKey] ?? void 0; | ||
| if (existingServers?.["01software"] && JSON.stringify(existingServers["01software"]) === JSON.stringify(nextEntry)) { | ||
| console.log(pc2.dim(" Unchanged"), loc.displayPath); | ||
| return; | ||
| } | ||
| const servers = existing[rootKey] ?? {}; | ||
| servers["01software"] = nextEntry; | ||
| existing[rootKey] = servers; | ||
| writeFile(loc.absolutePath, JSON.stringify(existing, null, 2) + "\n"); | ||
| console.log(pc2.green(" Updated"), loc.displayPath); | ||
| } | ||
| function writeTomlMcp(loc) { | ||
| const section = getCodexMcpTomlSection(); | ||
| const writeFile = loc.global ? (target, content) => writeSecretGlobalConfig(target, content) : (target, content) => fs2.writeFileSync(target, content); | ||
| if (!fs2.existsSync(loc.absolutePath)) { | ||
| writeFile(loc.absolutePath, section.trimStart()); | ||
| console.log(pc2.green(" Created"), loc.displayPath); | ||
| return; | ||
| } | ||
| const existing = fs2.readFileSync(loc.absolutePath, "utf-8"); | ||
| if (!existing.includes(CODEX_MCP_SECTION_MARKER)) { | ||
| const sep = existing.endsWith("\n") ? "" : "\n"; | ||
| writeFile(loc.absolutePath, existing + sep + section); | ||
| console.log(pc2.green(" Updated"), loc.displayPath); | ||
| return; | ||
| } | ||
| const replaced = replaceTomlMcpSection(existing, section); | ||
| if (replaced === existing) { | ||
| console.log(pc2.dim(" Unchanged"), loc.displayPath); | ||
| return; | ||
| } | ||
| writeFile(loc.absolutePath, replaced); | ||
| console.log(pc2.green(" Updated"), loc.displayPath); | ||
| } | ||
| function addToGitignore(cwd, tools) { | ||
| const entries = []; | ||
| for (const tool of tools) { | ||
| const loc = resolveMcpLocation(tool, cwd); | ||
| if (loc?.gitignoreEntry) entries.push(loc.gitignoreEntry); | ||
| } | ||
| if (entries.length === 0) return; | ||
| const gitignorePath = path2.join(cwd, ".gitignore"); | ||
| const existing = fs2.existsSync(gitignorePath) ? fs2.readFileSync(gitignorePath, "utf-8") : ""; | ||
| const toAdd = entries.filter((e) => !existing.includes(e)); | ||
| if (toAdd.length === 0) return; | ||
| const content = "\n# MCP configs\n" + toAdd.join("\n") + "\n"; | ||
| if (fs2.existsSync(gitignorePath)) { | ||
| fs2.appendFileSync(gitignorePath, content); | ||
| } else { | ||
| fs2.writeFileSync(gitignorePath, content.trimStart()); | ||
| } | ||
| console.log(pc2.green(" Updated"), ".gitignore", pc2.dim(`(added ${toAdd.join(", ")})`)); | ||
| } | ||
| async function writeClaudeDocs(cwd, publishableKey, secretKey, tenantName, policy) { | ||
| let ctx = { | ||
| tenantName: tenantName || "Your Tenant", | ||
| features: void 0, | ||
| collections: void 0 | ||
| }; | ||
| if (publishableKey && secretKey) { | ||
| const fetched = await fetchTenantContext(publishableKey, secretKey); | ||
| if (fetched) { | ||
| ctx = { | ||
| tenantName: fetched.tenantName || ctx.tenantName, | ||
| features: fetched.features, | ||
| collections: fetched.collections | ||
| }; | ||
| } | ||
| } | ||
| const claudeDir = path2.join(cwd, ".claude"); | ||
| const softwareDir = path2.join(claudeDir, "01software"); | ||
| const skillsDir = path2.join(claudeDir, "skills"); | ||
| fs2.mkdirSync(softwareDir, { recursive: true }); | ||
| fs2.mkdirSync(skillsDir, { recursive: true }); | ||
| const contextPath = path2.join(softwareDir, "context.md"); | ||
| const contextExists = fs2.existsSync(contextPath); | ||
| fs2.writeFileSync(contextPath, generateClaudeMd(ctx)); | ||
| console.log(pc2.green(contextExists ? " Updated" : " Created"), ".claude/01software/context.md"); | ||
| const claudeMdPath = path2.join(claudeDir, "CLAUDE.md"); | ||
| const importLine = "@.claude/01software/context.md"; | ||
| if (!fs2.existsSync(claudeMdPath)) { | ||
| fs2.writeFileSync(claudeMdPath, importLine + "\n"); | ||
| console.log(pc2.green(" Created"), ".claude/CLAUDE.md"); | ||
| } else { | ||
| const existing = fs2.readFileSync(claudeMdPath, "utf-8"); | ||
| if (!existing.includes(importLine)) { | ||
| const prefix = existing.endsWith("\n") ? "\n" : "\n\n"; | ||
| fs2.appendFileSync(claudeMdPath, prefix + importLine + "\n"); | ||
| console.log(pc2.green(" Updated"), ".claude/CLAUDE.md", pc2.dim("(added @import)")); | ||
| } else { | ||
| console.log(pc2.dim(" Unchanged"), ".claude/CLAUDE.md"); | ||
| } | ||
| } | ||
| for (const { dirName, content } of getSkillFiles()) { | ||
| const skillDir = path2.join(skillsDir, dirName); | ||
| const skillPath = path2.join(skillDir, "SKILL.md"); | ||
| fs2.mkdirSync(skillDir, { recursive: true }); | ||
| await writeFileWithPolicy(cwd, skillPath, content, policy); | ||
| } | ||
| } | ||
| var WS_FILE = "pnpm-workspace.yaml"; | ||
| var WS_BACKUP = "pnpm-workspace.yaml.bak"; | ||
| function hasPnpmWorkspace(cwd) { | ||
| return fs2.existsSync(path2.join(cwd, WS_FILE)); | ||
| } | ||
| function patchPnpmWorkspace(cwd) { | ||
| const wsPath = path2.join(cwd, WS_FILE); | ||
| if (!fs2.existsSync(wsPath)) return false; | ||
| const content = fs2.readFileSync(wsPath, "utf-8"); | ||
| if (content.includes("packages:")) return false; | ||
| fs2.copyFileSync(wsPath, path2.join(cwd, WS_BACKUP)); | ||
| fs2.writeFileSync(wsPath, content.trimEnd() + "\npackages: []\n"); | ||
| return true; | ||
| } | ||
| function restorePnpmWorkspace(cwd) { | ||
| const backupPath = path2.join(cwd, WS_BACKUP); | ||
| if (!fs2.existsSync(backupPath)) return; | ||
| fs2.copyFileSync(backupPath, path2.join(cwd, WS_FILE)); | ||
| fs2.unlinkSync(backupPath); | ||
| } | ||
| export { | ||
| detectProject, | ||
| init | ||
| }; | ||
| //# sourceMappingURL=chunk-3RQE6YVO.js.map |
| {"version":3,"sources":["../src/init.ts","../src/detect.ts","../src/browser-auth.ts"],"sourcesContent":["import fs from 'node:fs'\nimport path from 'node:path'\nimport os from 'node:os'\nimport { execSync } from 'node:child_process'\nimport pc from 'picocolors'\nimport prompts from 'prompts'\nimport type { PackageManager, ProjectEnv, ProjectInfo } from './detect'\nimport {\n needsClient,\n needsServer,\n needsReactQuery,\n getPublishableKeyEnvVar,\n} from './detect'\nimport type { AiTool, InitAnswers } from './prompts'\nimport {\n getClientTemplate,\n getAnalyticsTemplate,\n getQueryProviderTemplate,\n getServerTemplate,\n getEnvContent,\n getMcpConfigTemplate,\n getMcpRootKey,\n getMcpServerEntry,\n getCodexMcpTomlSection,\n CODEX_MCP_SECTION_MARKER,\n} from './templates'\nimport { startBrowserAuth } from './browser-auth'\nimport {\n generateClaudeMd,\n getSkillFiles,\n fetchTenantContext,\n} from './ai-docs'\nimport {\n readEnvValue,\n setEnvValue,\n replaceTomlMcpSection,\n writeEnvFile,\n chmodSecretFile,\n writeSecretGlobalConfig,\n} from './file-ops'\n\ntype ResolvedProjectInfo = Omit<ProjectInfo, 'packageManager'> & {\n packageManager: PackageManager\n}\n\nconst SECRET_KEY_ENV_VAR = 'SOFTWARE_SECRET_KEY'\n\nexport type ConflictPolicy = 'overwrite' | 'skip' | 'ask'\n\nexport interface InitResult {\n installFailed: boolean\n installSkipped: boolean\n installCmd: string\n /** Set when browser-auth or manual entry produced a publishable key the\n * caller should prefer over `answers.publishableKey` when computing\n * next-step messages. */\n publishableKey?: string\n /** Set when browser-auth or manual entry produced a secret key. */\n secretKey?: string\n /** Tenant display name from browser-auth, when available. */\n tenantName?: string\n}\n\nexport interface InitDeps {\n /** Test seam — replaces the real browser-auth flow. */\n startBrowserAuth?: typeof startBrowserAuth\n /** Test seam — skips actually invoking the package manager. */\n skipInstall?: boolean\n}\n\nexport async function init(\n cwd: string,\n info: ResolvedProjectInfo,\n answers: InitAnswers,\n deps: InitDeps = {},\n): Promise<InitResult> {\n const { packageManager, srcDir } = info\n const env = answers.env\n const baseDir = srcDir ? path.join(cwd, 'src') : cwd\n const browserAuth = deps.startBrowserAuth ?? startBrowserAuth\n\n const publishableKeyEnvVar = getPublishableKeyEnvVar(env)\n const wantsClient = needsClient(env)\n const wantsServer = needsServer(env)\n const wantsReactQuery = needsReactQuery(env)\n\n // 0. Plan: scan for conflicts and pick the env file. Both prompts are\n // front-loaded so the rest of init() doesn't interleave I/O with prompts.\n const plan = await planConflictsAndEnv(cwd, baseDir, env, answers)\n\n // 1. Install dependencies — skip whatever's already in package.json\n const installResult = deps.skipInstall\n ? {\n installFailed: false,\n installSkipped: true,\n installCmd: buildAddCmd(packageManager, hasPnpmWorkspace(cwd), [\n '@01.software/sdk',\n ]),\n }\n : installDeps(\n cwd,\n packageManager,\n info.hasSdk,\n info.hasReactQuery,\n wantsReactQuery,\n )\n\n // 2. Write lib/software/ files\n if (wantsClient || wantsReactQuery || wantsServer) {\n fs.mkdirSync(path.join(baseDir, 'lib', 'software'), { recursive: true })\n }\n const libDir = path.join(baseDir, 'lib', 'software')\n\n if (wantsClient) {\n await writeFileWithPolicy(\n cwd,\n path.join(libDir, 'client.ts'),\n getClientTemplate(env, publishableKeyEnvVar),\n plan.policy,\n )\n await writeFileWithPolicy(\n cwd,\n path.join(libDir, 'analytics.ts'),\n getAnalyticsTemplate(env, publishableKeyEnvVar),\n plan.policy,\n )\n }\n if (wantsReactQuery) {\n await writeFileWithPolicy(\n cwd,\n path.join(libDir, 'query-provider.tsx'),\n getQueryProviderTemplate(env),\n plan.policy,\n )\n }\n if (wantsServer) {\n await writeFileWithPolicy(\n cwd,\n path.join(libDir, 'server.ts'),\n getServerTemplate(env, publishableKeyEnvVar, SECRET_KEY_ENV_VAR),\n plan.policy,\n )\n }\n\n // 3. Append to env file (browser auth handles its own write in step 4)\n if (plan.envFile && answers.authMethod !== 'browser') {\n await writeEnv(\n cwd,\n plan.envFile,\n answers.publishableKey || '',\n answers.secretKey || '',\n publishableKeyEnvVar,\n wantsServer ? SECRET_KEY_ENV_VAR : null,\n plan.policy,\n )\n }\n\n // 4. Browser auth — get real credentials and force-write env\n let publishableKey = answers.publishableKey\n let secretKey = answers.secretKey\n let tenantName = ''\n\n if (answers.authMethod === 'browser' && answers.aiTools.length > 0) {\n try {\n console.log()\n const creds = await browserAuth()\n publishableKey = creds.publishableKey\n secretKey = creds.secretKey\n tenantName = creds.tenantName\n\n // Browser auth just minted fresh credentials → policy is overwrite.\n if (plan.envFile && publishableKey) {\n await writeEnv(\n cwd,\n plan.envFile,\n publishableKey,\n secretKey,\n publishableKeyEnvVar,\n wantsServer ? SECRET_KEY_ENV_VAR : null,\n 'overwrite',\n true,\n )\n }\n } catch (err) {\n console.log(\n pc.yellow(' Browser auth skipped:'),\n err instanceof Error ? err.message : String(err),\n )\n }\n }\n\n // 5. AI tool configs (MCP + Claude docs)\n if (answers.aiTools.length > 0) {\n for (const tool of answers.aiTools) {\n await writeMcpConfig(tool, cwd)\n }\n\n addToGitignore(cwd, answers.aiTools)\n\n if (answers.aiTools.includes('claude')) {\n await writeClaudeDocs(cwd, publishableKey, secretKey, tenantName, plan.policy)\n }\n }\n\n return {\n ...installResult,\n publishableKey: publishableKey || undefined,\n secretKey: secretKey || undefined,\n tenantName: tenantName || undefined,\n }\n}\n\n// ── Install ──────────────────────────────────────────────────────────\n\ninterface DepSpec {\n name: string\n installed: boolean\n needed: boolean\n}\n\nfunction installDeps(\n cwd: string,\n pm: PackageManager,\n hasSdk: boolean,\n hasReactQuery: boolean,\n wantsReactQuery: boolean,\n): InitResult {\n const allDeps: DepSpec[] = [\n { name: '@01.software/sdk', installed: hasSdk, needed: true },\n { name: '@tanstack/react-query', installed: hasReactQuery, needed: wantsReactQuery },\n ]\n const fullList = allDeps.filter((d) => d.needed).map((d) => d.name)\n const toInstall = allDeps.filter((d) => d.needed && !d.installed).map((d) => d.name)\n const fullCmd = buildAddCmd(pm, hasPnpmWorkspace(cwd), fullList)\n\n if (toInstall.length === 0) {\n console.log(pc.dim(` Dependencies already installed: ${fullList.join(', ')}`))\n return { installFailed: false, installSkipped: true, installCmd: fullCmd }\n }\n\n const addCmd = buildAddCmd(pm, hasPnpmWorkspace(cwd), toInstall)\n console.log(pc.dim(` Installing ${toInstall.join(' and ')}...`))\n\n const wsPatched = pm === 'pnpm' && patchPnpmWorkspace(cwd)\n let installFailed = false\n\n try {\n execSync(addCmd, { cwd, stdio: 'pipe' })\n console.log(pc.green(' Installed'), toInstall.join(', '))\n } catch (error) {\n installFailed = true\n const err = error as { stdout?: Buffer; stderr?: Buffer }\n const msg =\n String(err.stderr || '').trim() ||\n String(err.stdout || '').trim() ||\n String(error)\n console.log(pc.yellow(' Install failed — continuing with scaffolding'))\n const firstLines = msg.split('\\n').slice(0, 3).map((l) => ` ${l}`).join('\\n')\n if (firstLines) console.log(pc.dim(firstLines))\n console.log(pc.dim(` Run manually: ${addCmd}`))\n } finally {\n if (wsPatched) restorePnpmWorkspace(cwd)\n }\n\n return { installFailed, installSkipped: false, installCmd: addCmd }\n}\n\nfunction buildAddCmd(\n pm: PackageManager,\n hasPnpmWs: boolean,\n deps: string[],\n): string {\n const pkgs = deps.join(' ')\n switch (pm) {\n case 'pnpm':\n return hasPnpmWs ? `pnpm add -w ${pkgs}` : `pnpm add ${pkgs}`\n case 'yarn':\n return `yarn add ${pkgs}`\n case 'bun':\n return `bun add ${pkgs}`\n default:\n return `npm install ${pkgs}`\n }\n}\n\n// ── Conflict planning ────────────────────────────────────────────────\n\ninterface PlanResult {\n policy: ConflictPolicy\n /** Empty string when env file is not used (vanilla / edge) */\n envFile: string\n}\n\nasync function planConflictsAndEnv(\n cwd: string,\n baseDir: string,\n env: ProjectEnv,\n answers: InitAnswers,\n): Promise<PlanResult> {\n const candidates: string[] = []\n const libDir = path.join(baseDir, 'lib', 'software')\n if (needsClient(env)) candidates.push(path.join(libDir, 'client.ts'))\n if (needsReactQuery(env)) candidates.push(path.join(libDir, 'query-provider.tsx'))\n if (needsServer(env)) candidates.push(path.join(libDir, 'server.ts'))\n if (answers.aiTools.includes('claude')) {\n for (const { dirName } of getSkillFiles()) {\n candidates.push(path.join(cwd, '.claude', 'skills', dirName, 'SKILL.md'))\n }\n }\n\n const conflicts = candidates.filter((p) => fs.existsSync(p))\n\n let policy: ConflictPolicy = 'skip'\n if (conflicts.length > 0) {\n console.log(pc.yellow(` ${conflicts.length} file(s) already exist:`))\n for (const c of conflicts) console.log(pc.dim(` ${path.relative(cwd, c)}`))\n const { selected } = await prompts({\n type: 'select',\n name: 'selected',\n message: 'How should I handle existing files?',\n choices: [\n { title: 'Keep existing (skip)', value: 'skip' },\n { title: 'Overwrite all', value: 'overwrite' },\n { title: 'Ask for each', value: 'ask' },\n ],\n initial: 0,\n })\n policy = (selected as ConflictPolicy) ?? 'skip'\n }\n\n const envFile =\n env === 'vanilla' || env === 'edge' ? '' : await pickEnvFile(cwd, env)\n\n return { policy, envFile }\n}\n\nasync function pickEnvFile(cwd: string, env: ProjectEnv): Promise<string> {\n // Order matters — `.env.local` first so it's the default selection on\n // Next.js, where it's the conventional secret store.\n const candidates = ['.env.local', '.env', '.env.development']\n const existing = candidates.filter((f) => fs.existsSync(path.join(cwd, f)))\n const preferred = env === 'nextjs' ? '.env.local' : '.env'\n\n if (existing.length === 0) return preferred\n if (existing.length === 1 && existing[0] === preferred) return existing[0]\n\n // Multiple files exist OR the only existing file isn't the preferred default.\n const options = Array.from(new Set([...existing, preferred]))\n const choices = options.map((f) => ({\n title: f,\n description: existing.includes(f) ? 'exists' : 'create',\n value: f,\n }))\n const initial = Math.max(\n 0,\n choices.findIndex((c) => c.value === preferred),\n )\n const { file } = await prompts({\n type: 'select',\n name: 'file',\n message: 'Which env file should I write SDK credentials to?',\n choices,\n initial,\n })\n return file ?? preferred\n}\n\n// ── Generic file write ───────────────────────────────────────────────\n\nasync function writeFileWithPolicy(\n cwd: string,\n filePath: string,\n content: string,\n policy: ConflictPolicy,\n): Promise<void> {\n const rel = path.relative(cwd, filePath)\n if (!fs.existsSync(filePath)) {\n fs.mkdirSync(path.dirname(filePath), { recursive: true })\n fs.writeFileSync(filePath, content)\n console.log(pc.green(' Created'), rel)\n return\n }\n\n const existing = fs.readFileSync(filePath, 'utf-8')\n if (existing === content) {\n console.log(pc.dim(' Unchanged'), rel)\n return\n }\n\n let shouldWrite = false\n if (policy === 'overwrite') {\n shouldWrite = true\n } else if (policy === 'ask') {\n const { confirm } = await prompts({\n type: 'confirm',\n name: 'confirm',\n message: `Overwrite ${rel}?`,\n initial: false,\n })\n shouldWrite = !!confirm\n }\n\n if (shouldWrite) {\n fs.writeFileSync(filePath, content)\n console.log(pc.green(' Overwrote'), rel)\n } else {\n console.log(pc.yellow(' Skipped'), rel, pc.dim('(already exists)'))\n }\n}\n\n// ── Env file write ───────────────────────────────────────────────────\n\nasync function writeEnv(\n cwd: string,\n envFile: string,\n publishableKey: string,\n secretKey: string,\n publishableKeyEnvVar: string,\n secretKeyEnvVar: string | null,\n policy: ConflictPolicy,\n fromBrowserAuth = false,\n): Promise<void> {\n const envPath = path.join(cwd, envFile)\n\n const targets: { name: string; value: string }[] = [\n { name: publishableKeyEnvVar, value: publishableKey },\n ]\n if (secretKeyEnvVar) {\n targets.push({ name: secretKeyEnvVar, value: secretKey })\n }\n\n if (!fs.existsSync(envPath)) {\n const initial = getEnvContent(publishableKey, secretKey, publishableKeyEnvVar, secretKeyEnvVar)\n writeEnvFile(envPath, initial.trimStart())\n console.log(pc.green(' Created'), envFile)\n return\n }\n\n let content = fs.readFileSync(envPath, 'utf-8')\n let modified = false\n let appendedHeader = false\n const headerAlreadyPresent = targets.some(\n (t) => readEnvValue(content, t.name) !== null,\n )\n\n for (const { name, value } of targets) {\n const existing = readEnvValue(content, name)\n\n if (existing === null) {\n // Append. Add the `# 01.software` section header on first append, but\n // only if no other 01.software keys are already in the file.\n if (!headerAlreadyPresent && !appendedHeader) {\n if (content.length > 0 && !content.endsWith('\\n')) content += '\\n'\n content += '\\n# 01.software\\n'\n appendedHeader = true\n }\n content = setEnvValue(content, name, value)\n modified = true\n continue\n }\n\n if (existing === value) continue\n // Don't overwrite an existing real value with empty (e.g. user skipped key entry).\n if (!value) continue\n\n let shouldOverwrite = false\n if (policy === 'overwrite') {\n shouldOverwrite = true\n } else if (policy === 'ask') {\n const { confirm } = await prompts({\n type: 'confirm',\n name: 'confirm',\n message: `${name} already set in ${envFile}. Overwrite?`,\n initial: fromBrowserAuth,\n })\n shouldOverwrite = !!confirm\n }\n\n if (shouldOverwrite) {\n content = setEnvValue(content, name, value)\n modified = true\n }\n }\n\n if (modified) {\n writeEnvFile(envPath, content)\n console.log(\n pc.green(' Updated'),\n envFile,\n fromBrowserAuth ? pc.dim('(SDK credentials)') : '',\n )\n } else {\n // Tighten mode on existing files we did not modify, so re-runs harden\n // permissions on env files created by an earlier looser version.\n chmodSecretFile(envPath)\n console.log(pc.dim(' Unchanged'), envFile)\n }\n}\n\n// ── MCP targets registry ─────────────────────────────────────────────\n\ntype McpFormat = 'json' | 'toml'\ntype McpJsonClient = 'generic' | 'windsurf' | 'vscode'\ninterface McpLocation {\n kind: McpFormat\n absolutePath: string\n jsonClient?: McpJsonClient\n displayPath: string\n gitignoreEntry: string | null // null = global path, not worth ignoring\n /** Global config under user HOME — apply 0o600 mode, atomic rename, and\n * symlink/hardlink rejection per spec §\"Secret Handling Rules\". */\n global?: boolean\n}\n\nfunction resolveMcpLocation(tool: AiTool, cwd: string): McpLocation | null {\n const home = os.homedir()\n\n switch (tool) {\n case 'claude':\n return {\n kind: 'json',\n absolutePath: path.join(cwd, '.mcp.json'),\n displayPath: '.mcp.json',\n gitignoreEntry: '.mcp.json',\n }\n case 'cursor':\n return {\n kind: 'json',\n absolutePath: path.join(cwd, '.cursor', 'mcp.json'),\n displayPath: '.cursor/mcp.json',\n gitignoreEntry: '.cursor/mcp.json',\n }\n case 'vscode':\n return {\n kind: 'json',\n absolutePath: path.join(cwd, '.vscode', 'mcp.json'),\n jsonClient: 'vscode',\n displayPath: '.vscode/mcp.json',\n gitignoreEntry: '.vscode/mcp.json',\n }\n case 'windsurf': {\n if (!home) return null\n const p = path.join(home, '.codeium', 'windsurf', 'mcp_config.json')\n return {\n kind: 'json',\n absolutePath: p,\n jsonClient: 'windsurf',\n displayPath: p,\n gitignoreEntry: null,\n global: true,\n }\n }\n case 'codex': {\n if (!home) return null\n const p = path.join(home, '.codex', 'config.toml')\n return { kind: 'toml', absolutePath: p, displayPath: p, gitignoreEntry: null, global: true }\n }\n case 'gemini': {\n if (!home) return null\n const p = path.join(home, '.gemini', 'settings.json')\n return { kind: 'json', absolutePath: p, displayPath: p, gitignoreEntry: null, global: true }\n }\n }\n}\n\nasync function writeMcpConfig(\n tool: AiTool,\n cwd: string,\n): Promise<void> {\n const loc = resolveMcpLocation(tool, cwd)\n if (!loc) {\n console.log(pc.yellow(` Skipped ${tool}`), pc.dim('(HOME not set)'))\n return\n }\n\n // For global secret-bearing config, parent-dir creation with 0o700 is\n // handled inside `writeSecretGlobalConfig`. Repo-local writes (Claude Code,\n // Cursor, VS Code) keep loose mkdir — they only contain OAuth/discovery URLs.\n if (!loc.global) {\n fs.mkdirSync(path.dirname(loc.absolutePath), { recursive: true })\n }\n\n try {\n if (loc.kind === 'json') {\n writeJsonMcp(loc)\n } else {\n writeTomlMcp(loc)\n }\n } catch (err) {\n console.log(\n pc.yellow(` Skipped ${loc.displayPath}`),\n pc.dim(err instanceof Error ? err.message : String(err)),\n )\n }\n}\n\nfunction writeJsonMcp(loc: McpLocation): void {\n const writeFile = loc.global\n ? (target: string, content: string) => writeSecretGlobalConfig(target, content)\n : (target: string, content: string) => fs.writeFileSync(target, content)\n\n if (!fs.existsSync(loc.absolutePath)) {\n writeFile(loc.absolutePath, getMcpConfigTemplate(loc.jsonClient))\n console.log(pc.green(' Created'), loc.displayPath)\n return\n }\n\n // VS Code uses `servers` as the config root; all other JSON clients use `mcpServers`.\n const rootKey = getMcpRootKey(loc.jsonClient)\n\n let existing: Record<string, unknown>\n try {\n existing = JSON.parse(fs.readFileSync(loc.absolutePath, 'utf-8'))\n } catch {\n console.log(pc.yellow(' Skipped'), loc.displayPath, pc.dim('(could not parse existing file)'))\n return\n }\n\n const nextEntry = getMcpServerEntry(loc.jsonClient)\n const existingServers = (existing[rootKey] as Record<string, unknown> | undefined) ?? undefined\n if (\n existingServers?.['01software'] &&\n JSON.stringify(existingServers['01software']) === JSON.stringify(nextEntry)\n ) {\n console.log(pc.dim(' Unchanged'), loc.displayPath)\n return\n }\n\n const servers = (existing[rootKey] as Record<string, unknown> | undefined) ?? {}\n servers['01software'] = nextEntry\n existing[rootKey] = servers\n writeFile(loc.absolutePath, JSON.stringify(existing, null, 2) + '\\n')\n console.log(pc.green(' Updated'), loc.displayPath)\n}\n\nfunction writeTomlMcp(loc: McpLocation): void {\n const section = getCodexMcpTomlSection()\n const writeFile = loc.global\n ? (target: string, content: string) => writeSecretGlobalConfig(target, content)\n : (target: string, content: string) => fs.writeFileSync(target, content)\n\n if (!fs.existsSync(loc.absolutePath)) {\n writeFile(loc.absolutePath, section.trimStart())\n console.log(pc.green(' Created'), loc.displayPath)\n return\n }\n\n const existing = fs.readFileSync(loc.absolutePath, 'utf-8')\n\n if (!existing.includes(CODEX_MCP_SECTION_MARKER)) {\n const sep = existing.endsWith('\\n') ? '' : '\\n'\n // Atomic rewrite (read-modify-write) — `appendFileSync` would bypass the\n // symlink/hardlink guards and the 0o600 mode of `writeSecretGlobalConfig`.\n writeFile(loc.absolutePath, existing + sep + section)\n console.log(pc.green(' Updated'), loc.displayPath)\n return\n }\n\n const replaced = replaceTomlMcpSection(existing, section)\n if (replaced === existing) {\n console.log(pc.dim(' Unchanged'), loc.displayPath)\n return\n }\n\n writeFile(loc.absolutePath, replaced)\n console.log(pc.green(' Updated'), loc.displayPath)\n}\n\nfunction addToGitignore(cwd: string, tools: AiTool[]): void {\n const entries: string[] = []\n for (const tool of tools) {\n const loc = resolveMcpLocation(tool, cwd)\n if (loc?.gitignoreEntry) entries.push(loc.gitignoreEntry)\n }\n\n if (entries.length === 0) return\n\n const gitignorePath = path.join(cwd, '.gitignore')\n const existing = fs.existsSync(gitignorePath)\n ? fs.readFileSync(gitignorePath, 'utf-8')\n : ''\n const toAdd = entries.filter((e) => !existing.includes(e))\n if (toAdd.length === 0) return\n\n const content = '\\n# MCP configs\\n' + toAdd.join('\\n') + '\\n'\n if (fs.existsSync(gitignorePath)) {\n fs.appendFileSync(gitignorePath, content)\n } else {\n fs.writeFileSync(gitignorePath, content.trimStart())\n }\n console.log(pc.green(' Updated'), '.gitignore', pc.dim(`(added ${toAdd.join(', ')})`))\n}\n\nasync function writeClaudeDocs(\n cwd: string,\n publishableKey: string,\n secretKey: string,\n tenantName: string,\n policy: ConflictPolicy,\n): Promise<void> {\n let ctx = {\n tenantName: tenantName || 'Your Tenant',\n features: undefined as string[] | undefined,\n collections: undefined as string[] | undefined,\n }\n\n if (publishableKey && secretKey) {\n const fetched = await fetchTenantContext(publishableKey, secretKey)\n if (fetched) {\n ctx = {\n tenantName: fetched.tenantName || ctx.tenantName,\n features: fetched.features,\n collections: fetched.collections,\n }\n }\n }\n\n const claudeDir = path.join(cwd, '.claude')\n const softwareDir = path.join(claudeDir, '01software')\n const skillsDir = path.join(claudeDir, 'skills')\n fs.mkdirSync(softwareDir, { recursive: true })\n fs.mkdirSync(skillsDir, { recursive: true })\n\n // context.md is intentionally always overwritten so re-running init\n // refreshes tenant data; users don't customize this file.\n const contextPath = path.join(softwareDir, 'context.md')\n const contextExists = fs.existsSync(contextPath)\n fs.writeFileSync(contextPath, generateClaudeMd(ctx))\n console.log(pc.green(contextExists ? ' Updated' : ' Created'), '.claude/01software/context.md')\n\n // CLAUDE.md — append @import line (idempotent, never overwrites user content)\n const claudeMdPath = path.join(claudeDir, 'CLAUDE.md')\n const importLine = '@.claude/01software/context.md'\n if (!fs.existsSync(claudeMdPath)) {\n fs.writeFileSync(claudeMdPath, importLine + '\\n')\n console.log(pc.green(' Created'), '.claude/CLAUDE.md')\n } else {\n const existing = fs.readFileSync(claudeMdPath, 'utf-8')\n if (!existing.includes(importLine)) {\n const prefix = existing.endsWith('\\n') ? '\\n' : '\\n\\n'\n fs.appendFileSync(claudeMdPath, prefix + importLine + '\\n')\n console.log(pc.green(' Updated'), '.claude/CLAUDE.md', pc.dim('(added @import)'))\n } else {\n console.log(pc.dim(' Unchanged'), '.claude/CLAUDE.md')\n }\n }\n\n // Skill files honour the user's conflict policy (templates the user may have\n // tuned to their workflow).\n for (const { dirName, content } of getSkillFiles()) {\n const skillDir = path.join(skillsDir, dirName)\n const skillPath = path.join(skillDir, 'SKILL.md')\n fs.mkdirSync(skillDir, { recursive: true })\n await writeFileWithPolicy(cwd, skillPath, content, policy)\n }\n}\n\nconst WS_FILE = 'pnpm-workspace.yaml'\nconst WS_BACKUP = 'pnpm-workspace.yaml.bak'\n\nfunction hasPnpmWorkspace(cwd: string): boolean {\n return fs.existsSync(path.join(cwd, WS_FILE))\n}\n\nfunction patchPnpmWorkspace(cwd: string): boolean {\n const wsPath = path.join(cwd, WS_FILE)\n if (!fs.existsSync(wsPath)) return false\n const content = fs.readFileSync(wsPath, 'utf-8')\n if (content.includes('packages:')) return false\n fs.copyFileSync(wsPath, path.join(cwd, WS_BACKUP))\n fs.writeFileSync(wsPath, content.trimEnd() + '\\npackages: []\\n')\n return true\n}\n\nfunction restorePnpmWorkspace(cwd: string): void {\n const backupPath = path.join(cwd, WS_BACKUP)\n if (!fs.existsSync(backupPath)) return\n fs.copyFileSync(backupPath, path.join(cwd, WS_FILE))\n fs.unlinkSync(backupPath)\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\n\nexport type PackageManager = 'pnpm' | 'npm' | 'yarn' | 'bun'\n\nexport type ProjectEnv =\n | 'nextjs'\n | 'react-vite'\n | 'react-cra'\n | 'vanilla'\n | 'node'\n | 'edge'\n | 'other' // Astro, Remix, SvelteKit, etc. — print guidance only\n\nexport interface ProjectInfo {\n hasPackageJson: boolean\n parseError: boolean\n env: ProjectEnv\n packageManager: PackageManager | null\n hasSdk: boolean\n hasReactQuery: boolean\n srcDir: boolean\n}\n\nexport function detectProject(cwd: string): ProjectInfo {\n const pkgPath = path.join(cwd, 'package.json')\n const hasPackageJson = fs.existsSync(pkgPath)\n\n let env: ProjectEnv = 'node'\n let hasSdk = false\n let hasReactQuery = false\n let parseError = false\n\n if (hasPackageJson) {\n let pkg: Record<string, unknown>\n try {\n pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))\n } catch {\n return {\n hasPackageJson: true,\n parseError: true,\n env: 'node',\n packageManager: null,\n hasSdk: false,\n hasReactQuery: false,\n srcDir: false,\n }\n }\n\n const deps = {\n ...((pkg.dependencies as Record<string, string>) || {}),\n ...((pkg.devDependencies as Record<string, string>) || {}),\n }\n hasSdk = '@01.software/sdk' in deps\n hasReactQuery = '@tanstack/react-query' in deps\n\n if ('next' in deps) {\n env = 'nextjs'\n } else if ('astro' in deps || '@astrojs/node' in deps) {\n env = 'other'\n } else if ('@remix-run/node' in deps || '@remix-run/react' in deps) {\n env = 'other'\n } else if ('@sveltejs/kit' in deps) {\n env = 'other'\n } else if ('react' in deps) {\n if ('vite' in deps) {\n env = 'react-vite'\n } else if ('react-scripts' in deps) {\n env = 'react-cra'\n } else {\n // React + unknown bundler (Webpack, Parcel, etc.) — leave as 'node'\n // so the prompt selector is shown\n env = 'node'\n }\n }\n // else: no react/next → 'node' (covers real Node.js, Deno, Bun, etc.)\n }\n\n // Detect package manager from lockfile\n let packageManager: PackageManager | null = null\n if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'))) {\n packageManager = 'pnpm'\n } else if (fs.existsSync(path.join(cwd, 'yarn.lock'))) {\n packageManager = 'yarn'\n } else if (\n fs.existsSync(path.join(cwd, 'bun.lockb')) ||\n fs.existsSync(path.join(cwd, 'bun.lock'))\n ) {\n packageManager = 'bun'\n } else if (fs.existsSync(path.join(cwd, 'package-lock.json'))) {\n packageManager = 'npm'\n }\n\n // Detect src directory structure\n const srcDir =\n env === 'nextjs'\n ? fs.existsSync(path.join(cwd, 'src', 'app'))\n : fs.existsSync(path.join(cwd, 'src'))\n\n return { hasPackageJson, parseError, env, packageManager, hasSdk, hasReactQuery, srcDir }\n}\n\n/** Environments that generate a browser client file */\nexport function needsClient(env: ProjectEnv): boolean {\n return env === 'nextjs' || env === 'react-vite' || env === 'react-cra' || env === 'vanilla'\n}\n\n/** Environments that generate a server client file */\nexport function needsServer(env: ProjectEnv): boolean {\n return env === 'nextjs' || env === 'node' || env === 'edge'\n}\n\n/** Environments that generate a query-provider file */\nexport function needsReactQuery(env: ProjectEnv): boolean {\n return env === 'nextjs' || env === 'react-vite' || env === 'react-cra'\n}\n\n/** Environments that support MCP setup (need both client + secret key) */\nexport function supportsMcp(env: ProjectEnv): boolean {\n return env === 'nextjs' || env === 'node' || env === 'edge'\n}\n\n/** Get the env var name for the public publishable key */\nexport function getPublishableKeyEnvVar(env: ProjectEnv): string {\n switch (env) {\n case 'nextjs':\n return 'NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY'\n case 'react-vite':\n return 'VITE_SOFTWARE_PUBLISHABLE_KEY'\n case 'react-cra':\n return 'REACT_APP_SOFTWARE_PUBLISHABLE_KEY'\n default:\n return 'SOFTWARE_PUBLISHABLE_KEY'\n }\n}\n","import { randomBytes } from 'node:crypto'\nimport { createServer } from 'node:http'\nimport { execFile, exec } from 'node:child_process'\nimport { platform } from 'node:os'\nimport { URL } from 'node:url'\nimport pc from 'picocolors'\n\nconst DEFAULT_WEB_URL = process.env.SOFTWARE_WEB_URL || 'https://01.software'\nconst TIMEOUT_MS = 5 * 60 * 1000 // 5 minutes\n\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n}\n\nfunction openBrowser(url: string): void {\n const os = platform()\n\n const onError = () => {\n console.log(\n pc.yellow(\n `Could not open browser automatically. Open this URL manually:\\n${url}`,\n ),\n )\n }\n\n if (os === 'win32') {\n exec(`start \"\" \"${url}\"`, (err) => {\n if (err) onError()\n })\n } else {\n const cmd = os === 'darwin' ? 'open' : 'xdg-open'\n execFile(cmd, [url], (err) => {\n if (err) onError()\n })\n }\n}\n\nconst PAGE_STYLE = `*{margin:0;box-sizing:border-box}\nbody{font-family:system-ui,-apple-system,sans-serif;display:flex;justify-content:center;align-items:center;min-height:100vh;background:#fff;color:#252525}\n@media(prefers-color-scheme:dark){body{background:#252525;color:#f5f5f5}}\n.card{text-align:center;padding:2rem 2.5rem;border-radius:10px;max-width:380px;width:100%}\n.icon{width:40px;height:40px;margin:0 auto 1rem;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:1.25rem}\n.icon.ok{background:rgba(0,0,0,.05);color:#252525}\n.icon.err{background:rgba(220,38,38,.08);color:#dc2626}\n@media(prefers-color-scheme:dark){.icon.ok{background:rgba(255,255,255,.08);color:#f5f5f5}}\nh1{font-size:.875rem;font-weight:600;margin-bottom:.375rem}\np{font-size:.75rem;color:#737373;line-height:1.5}`\n\nconst SUCCESS_HTML = `<!DOCTYPE html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width\"><title>Login</title>\n<style>${PAGE_STYLE}</style>\n</head><body><div class=\"card\"><div class=\"icon ok\">\\u2713</div><h1>Authenticated</h1><p>You can close this tab and return to the terminal.</p></div></body></html>`\n\nconst ERROR_HTML = (msg: string) => `<!DOCTYPE html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width\"><title>Login Error</title>\n<style>${PAGE_STYLE}</style>\n</head><body><div class=\"card\"><div class=\"icon err\">!</div><h1>Authentication failed</h1><p>${escapeHtml(msg)}</p></div></body></html>`\n\ninterface ExchangeResponse {\n publishableKey: string\n secretKey: string\n tenantName: string\n tenantId: string\n}\n\nasync function exchangeCode(\n webUrl: string,\n code: string,\n): Promise<ExchangeResponse | null> {\n const url = `${webUrl}/api/cli/exchange`\n try {\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ code }),\n })\n if (!res.ok) {\n const body = await res.text().catch(() => '')\n console.error(\n pc.red(\n `Exchange failed: HTTP ${res.status} from ${url}${body ? ` — ${body.slice(0, 200)}` : ''}`,\n ),\n )\n return null\n }\n const data = (await res.json()) as Partial<ExchangeResponse>\n if (\n typeof data.publishableKey !== 'string' ||\n typeof data.secretKey !== 'string' ||\n typeof data.tenantName !== 'string' ||\n typeof data.tenantId !== 'string'\n ) {\n console.error(pc.red(`Exchange failed: malformed response from ${url}`))\n return null\n }\n return {\n publishableKey: data.publishableKey,\n secretKey: data.secretKey,\n tenantName: data.tenantName,\n tenantId: data.tenantId,\n }\n } catch (err) {\n console.error(\n pc.red(\n `Exchange request to ${url} failed: ${err instanceof Error ? err.message : String(err)}`,\n ),\n )\n return null\n }\n}\n\nexport async function startBrowserAuth(options?: {\n webUrl?: string\n tenantId?: string\n}): Promise<{\n publishableKey: string\n secretKey: string\n tenantName: string\n tenantId?: string\n}> {\n const state = randomBytes(32).toString('hex')\n const webUrl = options?.webUrl ?? DEFAULT_WEB_URL\n\n return new Promise((resolve, reject) => {\n const server = createServer((req, res) => {\n if (!req.url) {\n res.writeHead(400).end()\n return\n }\n\n const url = new URL(req.url, `http://localhost`)\n\n if (url.pathname !== '/callback' || req.method !== 'GET') {\n res.writeHead(404).end()\n return\n }\n\n const error = url.searchParams.get('error')\n if (error) {\n res\n .writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', Connection: 'close' })\n .end(ERROR_HTML(error))\n console.error(pc.red(`Login failed: ${error}`))\n cleanup(new Error(`Login failed: ${error}`))\n return\n }\n\n const code = url.searchParams.get('code')\n const receivedState = url.searchParams.get('state')\n\n if (!code || !receivedState) {\n res\n .writeHead(400, { 'Content-Type': 'text/html; charset=utf-8', Connection: 'close' })\n .end(ERROR_HTML('Missing code or state.'))\n cleanup(new Error('Login failed: missing code or state.'))\n return\n }\n\n if (receivedState !== state) {\n res\n .writeHead(403, { 'Content-Type': 'text/html; charset=utf-8', Connection: 'close' })\n .end(ERROR_HTML('State mismatch.'))\n console.error(pc.red('Login failed: state mismatch.'))\n cleanup(new Error('Login failed: state mismatch.'))\n return\n }\n\n exchangeCode(webUrl, code).then((creds) => {\n if (!creds) {\n res\n .writeHead(400, { 'Content-Type': 'text/html; charset=utf-8', Connection: 'close' })\n .end(ERROR_HTML('Invalid or expired code.'))\n cleanup(new Error('Login failed: code exchange failed.'))\n return\n }\n\n res\n .writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', Connection: 'close' })\n .end(SUCCESS_HTML)\n\n console.log(pc.green(`\\nLogged in successfully!`))\n console.log(pc.dim(`Tenant: ${creds.tenantName}`))\n\n cleanup(null, creds)\n })\n })\n\n let timeout: ReturnType<typeof setTimeout>\n let completed = false\n\n function cleanup(err: Error | null, result?: ExchangeResponse) {\n if (completed) return\n completed = true\n clearTimeout(timeout)\n server.closeAllConnections?.()\n server.close(() => {\n if (err) {\n reject(err)\n } else {\n resolve(result!)\n }\n })\n }\n\n server.listen(0, '127.0.0.1', () => {\n const addr = server.address()\n if (!addr || typeof addr === 'string') {\n reject(new Error('Failed to start local server.'))\n return\n }\n\n const port = addr.port\n\n timeout = setTimeout(() => {\n console.error(pc.red('\\nLogin timed out (5 minutes). Please try again.'))\n cleanup(new Error('Login timed out'))\n }, TIMEOUT_MS)\n\n const params = new URLSearchParams({ port: String(port), state })\n if (options?.tenantId) {\n params.set('tenantId', options.tenantId)\n }\n const loginUrl = `${webUrl}/cli-auth?${params.toString()}`\n\n console.log(pc.dim('Opening browser for login...'))\n console.log(pc.dim(`If the browser does not open, visit:\\n${loginUrl}`))\n openBrowser(loginUrl)\n })\n\n server.on('error', (err) => {\n reject(err)\n })\n })\n}\n"],"mappings":"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","names":["fs","path","pc","os","path","fs","pc"]} |
| #!/usr/bin/env node | ||
| // src/templates.ts | ||
| var MCP_RESOURCE_AUDIENCE = "https://mcp.01.software/mcp"; | ||
| function getClientTemplate(env, publishableKeyEnvVar) { | ||
| if (env === "nextjs") { | ||
| return `import { createClient } from '@01.software/sdk' | ||
| export const client = createClient({ | ||
| publishableKey: process.env.${publishableKeyEnvVar}!, | ||
| }) | ||
| `; | ||
| } | ||
| if (env === "react-cra") { | ||
| return `import { createClient } from '@01.software/sdk' | ||
| export const client = createClient({ | ||
| publishableKey: process.env.${publishableKeyEnvVar}!, | ||
| }) | ||
| `; | ||
| } | ||
| if (env === "vanilla") { | ||
| return `import { createClient } from '@01.software/sdk' | ||
| // Replace 'YOUR_PUBLISHABLE_KEY' with your actual publishable key from the 01.software console | ||
| export const client = createClient({ | ||
| publishableKey: 'YOUR_PUBLISHABLE_KEY', | ||
| }) | ||
| `; | ||
| } | ||
| return `import { createClient } from '@01.software/sdk' | ||
| export const client = createClient({ | ||
| publishableKey: import.meta.env.${publishableKeyEnvVar}, | ||
| }) | ||
| `; | ||
| } | ||
| function getAnalyticsTemplate(env, publishableKeyEnvVar) { | ||
| if (env === "vanilla") { | ||
| return `import { createAnalytics } from '@01.software/sdk/analytics' | ||
| // Replace 'YOUR_PUBLISHABLE_KEY' with your actual publishable key from the 01.software console | ||
| export const analytics = createAnalytics({ | ||
| publishableKey: 'YOUR_PUBLISHABLE_KEY', | ||
| }) | ||
| `; | ||
| } | ||
| const publishableKeyExpression = env === "react-vite" ? `import.meta.env.${publishableKeyEnvVar}` : `process.env.${publishableKeyEnvVar}!`; | ||
| return `import { createAnalytics } from '@01.software/sdk/analytics' | ||
| export const analytics = createAnalytics({ | ||
| publishableKey: ${publishableKeyExpression}, | ||
| }) | ||
| `; | ||
| } | ||
| function getQueryProviderTemplate(env) { | ||
| const useClientDirective = env === "nextjs" ? "'use client'\n\n" : ""; | ||
| return `${useClientDirective}import { QueryClientProvider } from '@tanstack/react-query' | ||
| import { client } from './client' | ||
| export function QueryProvider({ children }: { children: React.ReactNode }) { | ||
| return ( | ||
| <QueryClientProvider client={client.queryClient}> | ||
| {children} | ||
| </QueryClientProvider> | ||
| ) | ||
| } | ||
| `; | ||
| } | ||
| function getServerTemplate(env, publishableKeyEnvVar, secretKeyEnvVar) { | ||
| if (env === "edge") { | ||
| return `import { createServerClient } from '@01.software/sdk' | ||
| // Edge runtime: pass your env bindings here | ||
| // e.g. Cloudflare Workers: use env.SOFTWARE_PUBLISHABLE_KEY from the handler context | ||
| // e.g. Vercel Edge: use process.env.${publishableKeyEnvVar} | ||
| export function createEdgeClient(publishableKey: string, secretKey: string) { | ||
| return createServerClient({ publishableKey, secretKey }) | ||
| } | ||
| `; | ||
| } | ||
| return `import { createServerClient } from '@01.software/sdk' | ||
| /** | ||
| * Runtime guard: this module contains secret keys and must never be | ||
| * imported from client-side (browser) code. If the bundler accidentally | ||
| * includes it in a client bundle, this throws at module-evaluation time. | ||
| */ | ||
| if (typeof window !== 'undefined') { | ||
| throw new Error( | ||
| 'lib/software/server.ts must not be imported in client-side code \u2014 it contains secret keys.', | ||
| ) | ||
| } | ||
| type ServerClient = ReturnType<typeof createServerClient> | ||
| let cachedClient: ServerClient | null = null | ||
| function createConfiguredClient(): ServerClient { | ||
| const publishableKey = process.env.${publishableKeyEnvVar} | ||
| const secretKey = process.env.${secretKeyEnvVar} | ||
| if (!publishableKey || !secretKey) { | ||
| throw new Error( | ||
| 'Server client requires ${publishableKeyEnvVar} and ${secretKeyEnvVar}.', | ||
| ) | ||
| } | ||
| return createServerClient({ publishableKey, secretKey }) | ||
| } | ||
| export function getServerClient(): ServerClient { | ||
| cachedClient ??= createConfiguredClient() | ||
| return cachedClient | ||
| } | ||
| /** | ||
| * Lazy Proxy: the underlying client is created only on first property access, | ||
| * so importing this file has no side effects until you actually call it. | ||
| */ | ||
| export const serverClient = new Proxy({} as ServerClient, { | ||
| get(_target, prop, receiver) { | ||
| const target = getServerClient() | ||
| const value = Reflect.get(target as object, prop, receiver) | ||
| return typeof value === 'function' ? value.bind(target) : value | ||
| }, | ||
| }) | ||
| `; | ||
| } | ||
| function getEnvContent(publishableKey, secretKey, publishableKeyEnvVar, secretKeyEnvVar) { | ||
| let content = ` | ||
| # 01.software | ||
| ${publishableKeyEnvVar}=${publishableKey} | ||
| `; | ||
| if (secretKeyEnvVar) { | ||
| content += `${secretKeyEnvVar}=${secretKey} | ||
| `; | ||
| } | ||
| return content; | ||
| } | ||
| function getMcpRootKey(client = "generic") { | ||
| return client === "vscode" ? "servers" : "mcpServers"; | ||
| } | ||
| function getMcpServerEntry(client = "generic") { | ||
| if (client === "windsurf") { | ||
| return { | ||
| serverUrl: MCP_RESOURCE_AUDIENCE | ||
| }; | ||
| } | ||
| return { | ||
| type: "http", | ||
| url: MCP_RESOURCE_AUDIENCE | ||
| }; | ||
| } | ||
| function getMcpConfigTemplate(client = "generic") { | ||
| const rootKey = getMcpRootKey(client); | ||
| return JSON.stringify( | ||
| { [rootKey]: { "01software": getMcpServerEntry(client) } }, | ||
| null, | ||
| 2 | ||
| ) + "\n"; | ||
| } | ||
| function getCodexMcpTomlSection() { | ||
| return ` | ||
| [mcp_servers.01software] | ||
| url = "${MCP_RESOURCE_AUDIENCE}" | ||
| `; | ||
| } | ||
| var CODEX_MCP_SECTION_MARKER = "[mcp_servers.01software]"; | ||
| export { | ||
| getClientTemplate, | ||
| getAnalyticsTemplate, | ||
| getQueryProviderTemplate, | ||
| getServerTemplate, | ||
| getEnvContent, | ||
| getMcpRootKey, | ||
| getMcpServerEntry, | ||
| getMcpConfigTemplate, | ||
| getCodexMcpTomlSection, | ||
| CODEX_MCP_SECTION_MARKER | ||
| }; | ||
| //# sourceMappingURL=chunk-TBGKXE3Q.js.map |
| {"version":3,"sources":["../src/templates.ts"],"sourcesContent":["import type { ProjectEnv } from './detect'\n\nconst MCP_RESOURCE_AUDIENCE = 'https://mcp.01.software/mcp'\n\n// ── Client template (browser) ────────────────────────────────────────\n\nexport function getClientTemplate(env: ProjectEnv, publishableKeyEnvVar: string): string {\n if (env === 'nextjs') {\n return `import { createClient } from '@01.software/sdk'\n\nexport const client = createClient({\n publishableKey: process.env.${publishableKeyEnvVar}!,\n})\n`\n }\n\n if (env === 'react-cra') {\n return `import { createClient } from '@01.software/sdk'\n\nexport const client = createClient({\n publishableKey: process.env.${publishableKeyEnvVar}!,\n})\n`\n }\n\n if (env === 'vanilla') {\n return `import { createClient } from '@01.software/sdk'\n\n// Replace 'YOUR_PUBLISHABLE_KEY' with your actual publishable key from the 01.software console\nexport const client = createClient({\n publishableKey: 'YOUR_PUBLISHABLE_KEY',\n})\n`\n }\n\n // react-vite (import.meta.env)\n return `import { createClient } from '@01.software/sdk'\n\nexport const client = createClient({\n publishableKey: import.meta.env.${publishableKeyEnvVar},\n})\n`\n}\n\n// ── Analytics template (browser) ─────────────────────────────────────\n\nexport function getAnalyticsTemplate(\n env: ProjectEnv,\n publishableKeyEnvVar: string,\n): string {\n if (env === 'vanilla') {\n return `import { createAnalytics } from '@01.software/sdk/analytics'\n\n// Replace 'YOUR_PUBLISHABLE_KEY' with your actual publishable key from the 01.software console\nexport const analytics = createAnalytics({\n publishableKey: 'YOUR_PUBLISHABLE_KEY',\n})\n`\n }\n\n const publishableKeyExpression =\n env === 'react-vite'\n ? `import.meta.env.${publishableKeyEnvVar}`\n : `process.env.${publishableKeyEnvVar}!`\n\n return `import { createAnalytics } from '@01.software/sdk/analytics'\n\nexport const analytics = createAnalytics({\n publishableKey: ${publishableKeyExpression},\n})\n`\n}\n\n// ── Query Provider template ──────────────────────────────────────────\n\nexport function getQueryProviderTemplate(env: ProjectEnv): string {\n const useClientDirective = env === 'nextjs' ? \"'use client'\\n\\n\" : ''\n\n return `${useClientDirective}import { QueryClientProvider } from '@tanstack/react-query'\nimport { client } from './client'\n\nexport function QueryProvider({ children }: { children: React.ReactNode }) {\n return (\n <QueryClientProvider client={client.queryClient}>\n {children}\n </QueryClientProvider>\n )\n}\n`\n}\n\n// ── Server template ──────────────────────────────────────────────────\n\nexport function getServerTemplate(\n env: ProjectEnv,\n publishableKeyEnvVar: string,\n secretKeyEnvVar: string,\n): string {\n if (env === 'edge') {\n return `import { createServerClient } from '@01.software/sdk'\n\n// Edge runtime: pass your env bindings here\n// e.g. Cloudflare Workers: use env.SOFTWARE_PUBLISHABLE_KEY from the handler context\n// e.g. Vercel Edge: use process.env.${publishableKeyEnvVar}\nexport function createEdgeClient(publishableKey: string, secretKey: string) {\n return createServerClient({ publishableKey, secretKey })\n}\n`\n }\n\n return `import { createServerClient } from '@01.software/sdk'\n\n/**\n * Runtime guard: this module contains secret keys and must never be\n * imported from client-side (browser) code. If the bundler accidentally\n * includes it in a client bundle, this throws at module-evaluation time.\n */\nif (typeof window !== 'undefined') {\n throw new Error(\n 'lib/software/server.ts must not be imported in client-side code — it contains secret keys.',\n )\n}\n\ntype ServerClient = ReturnType<typeof createServerClient>\n\nlet cachedClient: ServerClient | null = null\n\nfunction createConfiguredClient(): ServerClient {\n const publishableKey = process.env.${publishableKeyEnvVar}\n const secretKey = process.env.${secretKeyEnvVar}\n\n if (!publishableKey || !secretKey) {\n throw new Error(\n 'Server client requires ${publishableKeyEnvVar} and ${secretKeyEnvVar}.',\n )\n }\n\n return createServerClient({ publishableKey, secretKey })\n}\n\nexport function getServerClient(): ServerClient {\n cachedClient ??= createConfiguredClient()\n return cachedClient\n}\n\n/**\n * Lazy Proxy: the underlying client is created only on first property access,\n * so importing this file has no side effects until you actually call it.\n */\nexport const serverClient = new Proxy({} as ServerClient, {\n get(_target, prop, receiver) {\n const target = getServerClient()\n const value = Reflect.get(target as object, prop, receiver)\n return typeof value === 'function' ? value.bind(target) : value\n },\n})\n`\n}\n\n// ── Env file content ─────────────────────────────────────────────────\n\nexport function getEnvContent(\n publishableKey: string,\n secretKey: string,\n publishableKeyEnvVar: string,\n secretKeyEnvVar: string | null,\n): string {\n let content = `\\n# 01.software\\n${publishableKeyEnvVar}=${publishableKey}\\n`\n if (secretKeyEnvVar) {\n content += `${secretKeyEnvVar}=${secretKey}\\n`\n }\n return content\n}\n\n// ── MCP config (JSON) ────────────────────────────────────────────────\n\nexport type McpJsonClient = 'generic' | 'windsurf' | 'vscode'\n\n/** VS Code's MCP config root key is `servers`; all other JSON clients use\n * `mcpServers`. See spec §\"MCP Configuration Rules\". */\nexport function getMcpRootKey(client: McpJsonClient = 'generic'): 'servers' | 'mcpServers' {\n return client === 'vscode' ? 'servers' : 'mcpServers'\n}\n\nexport function getMcpServerEntry(client: McpJsonClient = 'generic') {\n if (client === 'windsurf') {\n return {\n serverUrl: MCP_RESOURCE_AUDIENCE,\n }\n }\n\n return {\n type: 'http' as const,\n url: MCP_RESOURCE_AUDIENCE,\n }\n}\n\nexport function getMcpConfigTemplate(client: McpJsonClient = 'generic'): string {\n const rootKey = getMcpRootKey(client)\n return (\n JSON.stringify(\n { [rootKey]: { '01software': getMcpServerEntry(client) } },\n null,\n 2,\n ) + '\\n'\n )\n}\n\n// ── MCP config (TOML — Codex CLI) ────────────────────────────────────\n\n/** Codex CLI `[mcp_servers.01software]` block. Idempotent marker: the header\n * line. Intended to be appended to `~/.codex/config.toml`. */\nexport function getCodexMcpTomlSection(): string {\n return `\n[mcp_servers.01software]\nurl = \"${MCP_RESOURCE_AUDIENCE}\"\n`\n}\n\nexport const CODEX_MCP_SECTION_MARKER = '[mcp_servers.01software]'\n"],"mappings":";;;AAEA,IAAM,wBAAwB;AAIvB,SAAS,kBAAkB,KAAiB,sBAAsC;AACvF,MAAI,QAAQ,UAAU;AACpB,WAAO;AAAA;AAAA;AAAA,gCAGqB,oBAAoB;AAAA;AAAA;AAAA,EAGlD;AAEA,MAAI,QAAQ,aAAa;AACvB,WAAO;AAAA;AAAA;AAAA,gCAGqB,oBAAoB;AAAA;AAAA;AAAA,EAGlD;AAEA,MAAI,QAAQ,WAAW;AACrB,WAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOT;AAGA,SAAO;AAAA;AAAA;AAAA,oCAG2B,oBAAoB;AAAA;AAAA;AAGxD;AAIO,SAAS,qBACd,KACA,sBACQ;AACR,MAAI,QAAQ,WAAW;AACrB,WAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOT;AAEA,QAAM,2BACJ,QAAQ,eACJ,mBAAmB,oBAAoB,KACvC,eAAe,oBAAoB;AAEzC,SAAO;AAAA;AAAA;AAAA,oBAGW,wBAAwB;AAAA;AAAA;AAG5C;AAIO,SAAS,yBAAyB,KAAyB;AAChE,QAAM,qBAAqB,QAAQ,WAAW,qBAAqB;AAEnE,SAAO,GAAG,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAW9B;AAIO,SAAS,kBACd,KACA,sBACA,iBACQ;AACR,MAAI,QAAQ,QAAQ;AAClB,WAAO;AAAA;AAAA;AAAA;AAAA,8CAImC,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKhE;AAEA,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uCAkB8B,oBAAoB;AAAA,kCACzB,eAAe;AAAA;AAAA;AAAA;AAAA,gCAIjB,oBAAoB,QAAQ,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwB3E;AAIO,SAAS,cACd,gBACA,WACA,sBACA,iBACQ;AACR,MAAI,UAAU;AAAA;AAAA,EAAoB,oBAAoB,IAAI,cAAc;AAAA;AACxE,MAAI,iBAAiB;AACnB,eAAW,GAAG,eAAe,IAAI,SAAS;AAAA;AAAA,EAC5C;AACA,SAAO;AACT;AAQO,SAAS,cAAc,SAAwB,WAAqC;AACzF,SAAO,WAAW,WAAW,YAAY;AAC3C;AAEO,SAAS,kBAAkB,SAAwB,WAAW;AACnE,MAAI,WAAW,YAAY;AACzB,WAAO;AAAA,MACL,WAAW;AAAA,IACb;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,KAAK;AAAA,EACP;AACF;AAEO,SAAS,qBAAqB,SAAwB,WAAmB;AAC9E,QAAM,UAAU,cAAc,MAAM;AACpC,SACE,KAAK;AAAA,IACH,EAAE,CAAC,OAAO,GAAG,EAAE,cAAc,kBAAkB,MAAM,EAAE,EAAE;AAAA,IACzD;AAAA,IACA;AAAA,EACF,IAAI;AAER;AAMO,SAAS,yBAAiC;AAC/C,SAAO;AAAA;AAAA,SAEA,qBAAqB;AAAA;AAE9B;AAEO,IAAM,2BAA2B;","names":[]} |
| #!/usr/bin/env node | ||
| // src/file-ops.ts | ||
| import fs from "fs"; | ||
| import path from "path"; | ||
| import { randomBytes } from "crypto"; | ||
| var SECRET_FILE_MODE = 384; | ||
| var SECRET_DIR_MODE = 448; | ||
| var isWin32 = process.platform === "win32"; | ||
| function writeFileWithSecretMode(target, content) { | ||
| fs.writeFileSync(target, content, isWin32 ? void 0 : { mode: SECRET_FILE_MODE }); | ||
| } | ||
| function mkdirWithSecretMode(dir) { | ||
| fs.mkdirSync( | ||
| dir, | ||
| isWin32 ? { recursive: true } : { recursive: true, mode: SECRET_DIR_MODE } | ||
| ); | ||
| } | ||
| function writeEnvFile(target, content) { | ||
| writeFileWithSecretMode(target, content); | ||
| } | ||
| function chmodSecretFile(target) { | ||
| if (isWin32) return; | ||
| try { | ||
| fs.chmodSync(target, SECRET_FILE_MODE); | ||
| } catch { | ||
| } | ||
| } | ||
| function writeSecretGlobalConfig(target, content) { | ||
| mkdirWithSecretMode(path.dirname(target)); | ||
| if (fs.existsSync(target)) { | ||
| const stat = fs.lstatSync(target); | ||
| if (stat.isSymbolicLink()) { | ||
| throw new Error( | ||
| `Refusing to write secret-bearing config through a symlink: ${target}. Remove the symlink and re-run init.` | ||
| ); | ||
| } | ||
| if (!stat.isFile()) { | ||
| throw new Error( | ||
| `Refusing to write secret-bearing config to non-regular file: ${target}.` | ||
| ); | ||
| } | ||
| if (!isWin32 && stat.nlink > 1) { | ||
| throw new Error( | ||
| `Refusing to write secret-bearing config to file with multiple hard links: ${target}.` | ||
| ); | ||
| } | ||
| } | ||
| const tmp = `${target}.${randomBytes(6).toString("hex")}.tmp`; | ||
| writeFileWithSecretMode(tmp, content); | ||
| try { | ||
| fs.renameSync(tmp, target); | ||
| } catch (err) { | ||
| try { | ||
| fs.unlinkSync(tmp); | ||
| } catch { | ||
| } | ||
| throw err; | ||
| } | ||
| } | ||
| var envLineRegexCache = /* @__PURE__ */ new Map(); | ||
| function envLineRegex(name) { | ||
| let re = envLineRegexCache.get(name); | ||
| if (!re) { | ||
| const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); | ||
| re = new RegExp(`^${escaped}=(.*)$`, "m"); | ||
| envLineRegexCache.set(name, re); | ||
| } | ||
| return re; | ||
| } | ||
| function readEnvValue(content, name) { | ||
| const m = content.match(envLineRegex(name)); | ||
| return m ? m[1] : null; | ||
| } | ||
| function setEnvValue(content, name, value) { | ||
| const re = envLineRegex(name); | ||
| if (re.test(content)) return content.replace(re, `${name}=${value}`); | ||
| const sep = content.length === 0 || content.endsWith("\n") ? "" : "\n"; | ||
| return content + sep + `${name}=${value} | ||
| `; | ||
| } | ||
| var OWNED_MCP_TOML_SECTION = "mcp_servers.01software"; | ||
| function tomlSectionName(line) { | ||
| const match = line.trim().match(/^\[([^\]]+)\]$/); | ||
| return match ? match[1] : null; | ||
| } | ||
| function isOwnedMcpTomlSection(sectionName) { | ||
| return sectionName === OWNED_MCP_TOML_SECTION || sectionName.startsWith(`${OWNED_MCP_TOML_SECTION}.`); | ||
| } | ||
| function replaceTomlMcpSection(content, newSection) { | ||
| const lines = content.split("\n"); | ||
| const kept = []; | ||
| let inOurSection = false; | ||
| for (const line of lines) { | ||
| const sectionName = tomlSectionName(line); | ||
| if (sectionName) { | ||
| inOurSection = isOwnedMcpTomlSection(sectionName); | ||
| if (inOurSection) continue; | ||
| } | ||
| if (!inOurSection) kept.push(line); | ||
| } | ||
| let result = kept.join("\n").replace(/\n+$/, ""); | ||
| if (result.length > 0) result += "\n"; | ||
| result += newSection.startsWith("\n") ? newSection : "\n" + newSection; | ||
| return result; | ||
| } | ||
| export { | ||
| SECRET_FILE_MODE, | ||
| SECRET_DIR_MODE, | ||
| writeEnvFile, | ||
| chmodSecretFile, | ||
| writeSecretGlobalConfig, | ||
| readEnvValue, | ||
| setEnvValue, | ||
| replaceTomlMcpSection | ||
| }; | ||
| //# sourceMappingURL=chunk-UA7WNT2F.js.map |
| {"version":3,"sources":["../src/file-ops.ts"],"sourcesContent":["// Helpers for `.env*` / Codex TOML / secret-bearing global config writes.\n// The pure string utilities (`readEnvValue`, `setEnvValue`,\n// `replaceTomlMcpSection`) are unit-tested without filesystem access; the\n// secret-write helpers (`writeEnvFile`, `chmodSecretFile`,\n// `writeSecretGlobalConfig`) are tested against an isolated tmpdir.\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { randomBytes } from 'node:crypto'\n\n// ── Secret-bearing writes ────────────────────────────────────────────\n\n/** POSIX file mode for secret-bearing files (`.env*`, global auth config). */\nexport const SECRET_FILE_MODE = 0o600\n/** POSIX directory mode for parents of global auth-bearing config. */\nexport const SECRET_DIR_MODE = 0o700\n\nconst isWin32 = process.platform === 'win32'\n\nfunction writeFileWithSecretMode(target: string, content: string): void {\n fs.writeFileSync(target, content, isWin32 ? undefined : { mode: SECRET_FILE_MODE })\n}\n\nfunction mkdirWithSecretMode(dir: string): void {\n fs.mkdirSync(\n dir,\n isWin32 ? { recursive: true } : { recursive: true, mode: SECRET_DIR_MODE },\n )\n}\n\n/** Write a `.env*` file with mode `0o600` on POSIX. Skips mode on Windows\n * per spec §\"Secret Handling Rules\". */\nexport function writeEnvFile(target: string, content: string): void {\n writeFileWithSecretMode(target, content)\n}\n\n/** Apply secret-file mode to an existing file (no-op on Windows). Useful\n * after merging an `.env*` file that already existed. */\nexport function chmodSecretFile(target: string): void {\n if (isWin32) return\n try {\n fs.chmodSync(target, SECRET_FILE_MODE)\n } catch {\n // Mode tightening is best-effort; the spec waives the requirement on\n // platforms that reject the chmod.\n }\n}\n\n/** Write a global auth-bearing config (e.g. `~/.codex/config.toml`) using\n * symlink-safe atomic rename. Throws when the existing target is a symlink,\n * has multiple hard links, or is not a regular file. */\nexport function writeSecretGlobalConfig(target: string, content: string): void {\n mkdirWithSecretMode(path.dirname(target))\n\n // Reject symlinks / non-regular files / multi-hardlink targets before any write.\n if (fs.existsSync(target)) {\n const stat = fs.lstatSync(target)\n if (stat.isSymbolicLink()) {\n throw new Error(\n `Refusing to write secret-bearing config through a symlink: ${target}. ` +\n `Remove the symlink and re-run init.`,\n )\n }\n if (!stat.isFile()) {\n throw new Error(\n `Refusing to write secret-bearing config to non-regular file: ${target}.`,\n )\n }\n if (!isWin32 && stat.nlink > 1) {\n throw new Error(\n `Refusing to write secret-bearing config to file with multiple hard links: ${target}.`,\n )\n }\n }\n\n // Atomic temp-file replacement: write tmp with 0o600, then rename to target.\n const tmp = `${target}.${randomBytes(6).toString('hex')}.tmp`\n writeFileWithSecretMode(tmp, content)\n try {\n fs.renameSync(tmp, target)\n } catch (err) {\n // Best-effort cleanup of the tmp file if rename failed (e.g. EXDEV).\n try {\n fs.unlinkSync(tmp)\n } catch {\n // ignore\n }\n throw err\n }\n}\n\n// ── .env merge ───────────────────────────────────────────────────────\n\nconst envLineRegexCache = new Map<string, RegExp>()\n\nfunction envLineRegex(name: string): RegExp {\n let re = envLineRegexCache.get(name)\n if (!re) {\n const escaped = name.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n re = new RegExp(`^${escaped}=(.*)$`, 'm')\n envLineRegexCache.set(name, re)\n }\n return re\n}\n\nexport function readEnvValue(content: string, name: string): string | null {\n const m = content.match(envLineRegex(name))\n return m ? m[1] : null\n}\n\nexport function setEnvValue(content: string, name: string, value: string): string {\n const re = envLineRegex(name)\n if (re.test(content)) return content.replace(re, `${name}=${value}`)\n const sep = content.length === 0 || content.endsWith('\\n') ? '' : '\\n'\n return content + sep + `${name}=${value}\\n`\n}\n\n// ── Codex TOML manipulation ──────────────────────────────────────────\n\nconst OWNED_MCP_TOML_SECTION = 'mcp_servers.01software'\n\nfunction tomlSectionName(line: string): string | null {\n const match = line.trim().match(/^\\[([^\\]]+)\\]$/)\n return match ? match[1] : null\n}\n\nfunction isOwnedMcpTomlSection(sectionName: string): boolean {\n return (\n sectionName === OWNED_MCP_TOML_SECTION ||\n sectionName.startsWith(`${OWNED_MCP_TOML_SECTION}.`)\n )\n}\n\n/** Removes the existing `[mcp_servers.01software]` block and sub-blocks from a\n * TOML document, then appends the provided section. */\nexport function replaceTomlMcpSection(content: string, newSection: string): string {\n const lines = content.split('\\n')\n const kept: string[] = []\n let inOurSection = false\n for (const line of lines) {\n const sectionName = tomlSectionName(line)\n if (sectionName) {\n inOurSection = isOwnedMcpTomlSection(sectionName)\n if (inOurSection) continue\n }\n if (!inOurSection) kept.push(line)\n }\n let result = kept.join('\\n').replace(/\\n+$/, '')\n if (result.length > 0) result += '\\n'\n // newSection already starts with a blank line; keep it that way\n result += newSection.startsWith('\\n') ? newSection : '\\n' + newSection\n return result\n}\n"],"mappings":"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","names":[]} |
+11
| #!/usr/bin/env node | ||
| import { | ||
| init | ||
| } from "./chunk-3RQE6YVO.js"; | ||
| import "./chunk-3MXIG3ZL.js"; | ||
| import "./chunk-UA7WNT2F.js"; | ||
| import "./chunk-TBGKXE3Q.js"; | ||
| export { | ||
| init | ||
| }; | ||
| //# sourceMappingURL=init.js.map |
| {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]} |
+1
-1
@@ -6,3 +6,3 @@ #!/usr/bin/env node | ||
| getSkillFiles | ||
| } from "./chunk-T3A5SLEJ.js"; | ||
| } from "./chunk-3MXIG3ZL.js"; | ||
| export { | ||
@@ -9,0 +9,0 @@ fetchTenantContext, |
+13
-3
| #!/usr/bin/env node | ||
| import { | ||
| SECRET_DIR_MODE, | ||
| SECRET_FILE_MODE, | ||
| chmodSecretFile, | ||
| readEnvValue, | ||
| replaceTomlMcpSection, | ||
| setEnvValue | ||
| } from "./chunk-JT3G6B66.js"; | ||
| setEnvValue, | ||
| writeEnvFile, | ||
| writeSecretGlobalConfig | ||
| } from "./chunk-UA7WNT2F.js"; | ||
| export { | ||
| SECRET_DIR_MODE, | ||
| SECRET_FILE_MODE, | ||
| chmodSecretFile, | ||
| readEnvValue, | ||
| replaceTomlMcpSection, | ||
| setEnvValue | ||
| setEnvValue, | ||
| writeEnvFile, | ||
| writeSecretGlobalConfig | ||
| }; | ||
| //# sourceMappingURL=file-ops.js.map |
+50
-822
| #!/usr/bin/env node | ||
| import { | ||
| fetchTenantContext, | ||
| generateClaudeMd, | ||
| getSkillFiles | ||
| } from "./chunk-T3A5SLEJ.js"; | ||
| import { | ||
| readEnvValue, | ||
| replaceTomlMcpSection, | ||
| setEnvValue | ||
| } from "./chunk-JT3G6B66.js"; | ||
| import { | ||
| CODEX_MCP_SECTION_MARKER, | ||
| getAnalyticsTemplate, | ||
| getClientTemplate, | ||
| getCodexMcpTomlSection, | ||
| getEnvContent, | ||
| getMcpConfigTemplate, | ||
| getMcpServerEntry, | ||
| getQueryProviderTemplate, | ||
| getServerTemplate | ||
| } from "./chunk-S3KHPWCE.js"; | ||
| detectProject, | ||
| init | ||
| } from "./chunk-3RQE6YVO.js"; | ||
| import "./chunk-3MXIG3ZL.js"; | ||
| import "./chunk-UA7WNT2F.js"; | ||
| import "./chunk-TBGKXE3Q.js"; | ||
| // src/index.ts | ||
| import pc3 from "picocolors"; | ||
| import pc from "picocolors"; | ||
| // src/detect.ts | ||
| import fs from "fs"; | ||
| import path from "path"; | ||
| function detectProject(cwd) { | ||
| const pkgPath = path.join(cwd, "package.json"); | ||
| const hasPackageJson = fs.existsSync(pkgPath); | ||
| let env = "node"; | ||
| let hasSdk = false; | ||
| let hasReactQuery = false; | ||
| let parseError = false; | ||
| if (hasPackageJson) { | ||
| let pkg; | ||
| try { | ||
| pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8")); | ||
| } catch { | ||
| return { | ||
| hasPackageJson: true, | ||
| parseError: true, | ||
| env: "node", | ||
| packageManager: null, | ||
| hasSdk: false, | ||
| hasReactQuery: false, | ||
| srcDir: false | ||
| }; | ||
| } | ||
| const deps = { | ||
| ...pkg.dependencies || {}, | ||
| ...pkg.devDependencies || {} | ||
| }; | ||
| hasSdk = "@01.software/sdk" in deps; | ||
| hasReactQuery = "@tanstack/react-query" in deps; | ||
| if ("next" in deps) { | ||
| env = "nextjs"; | ||
| } else if ("astro" in deps || "@astrojs/node" in deps) { | ||
| env = "other"; | ||
| } else if ("@remix-run/node" in deps || "@remix-run/react" in deps) { | ||
| env = "other"; | ||
| } else if ("@sveltejs/kit" in deps) { | ||
| env = "other"; | ||
| } else if ("react" in deps) { | ||
| if ("vite" in deps) { | ||
| env = "react-vite"; | ||
| } else if ("react-scripts" in deps) { | ||
| env = "react-cra"; | ||
| } else { | ||
| env = "node"; | ||
| } | ||
| } | ||
| } | ||
| let packageManager = null; | ||
| if (fs.existsSync(path.join(cwd, "pnpm-lock.yaml"))) { | ||
| packageManager = "pnpm"; | ||
| } else if (fs.existsSync(path.join(cwd, "yarn.lock"))) { | ||
| packageManager = "yarn"; | ||
| } else if (fs.existsSync(path.join(cwd, "bun.lockb")) || fs.existsSync(path.join(cwd, "bun.lock"))) { | ||
| packageManager = "bun"; | ||
| } else if (fs.existsSync(path.join(cwd, "package-lock.json"))) { | ||
| packageManager = "npm"; | ||
| } | ||
| const srcDir = env === "nextjs" ? fs.existsSync(path.join(cwd, "src", "app")) : fs.existsSync(path.join(cwd, "src")); | ||
| return { hasPackageJson, parseError, env, packageManager, hasSdk, hasReactQuery, srcDir }; | ||
| } | ||
| function needsClient(env) { | ||
| return env === "nextjs" || env === "react-vite" || env === "react-cra" || env === "vanilla"; | ||
| } | ||
| function needsServer(env) { | ||
| return env === "nextjs" || env === "node" || env === "edge"; | ||
| } | ||
| function needsReactQuery(env) { | ||
| return env === "nextjs" || env === "react-vite" || env === "react-cra"; | ||
| } | ||
| function getPublishableKeyEnvVar(env) { | ||
| switch (env) { | ||
| case "nextjs": | ||
| return "NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY"; | ||
| case "react-vite": | ||
| return "VITE_SOFTWARE_PUBLISHABLE_KEY"; | ||
| case "react-cra": | ||
| return "REACT_APP_SOFTWARE_PUBLISHABLE_KEY"; | ||
| default: | ||
| return "SOFTWARE_PUBLISHABLE_KEY"; | ||
| } | ||
| } | ||
| // src/prompts.ts | ||
@@ -272,678 +174,2 @@ import prompts from "prompts"; | ||
| // src/init.ts | ||
| import fs2 from "fs"; | ||
| import path2 from "path"; | ||
| import os from "os"; | ||
| import { execSync } from "child_process"; | ||
| import pc2 from "picocolors"; | ||
| import prompts2 from "prompts"; | ||
| // src/browser-auth.ts | ||
| import { randomBytes } from "crypto"; | ||
| import { createServer } from "http"; | ||
| import { execFile, exec } from "child_process"; | ||
| import { platform } from "os"; | ||
| import { URL } from "url"; | ||
| import pc from "picocolors"; | ||
| var DEFAULT_WEB_URL = process.env.SOFTWARE_WEB_URL || "https://01.software"; | ||
| var TIMEOUT_MS = 5 * 60 * 1e3; | ||
| function escapeHtml(s) { | ||
| return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """); | ||
| } | ||
| function openBrowser(url) { | ||
| const os2 = platform(); | ||
| const onError = () => { | ||
| console.log( | ||
| pc.yellow( | ||
| `Could not open browser automatically. Open this URL manually: | ||
| ${url}` | ||
| ) | ||
| ); | ||
| }; | ||
| if (os2 === "win32") { | ||
| exec(`start "" "${url}"`, (err) => { | ||
| if (err) onError(); | ||
| }); | ||
| } else { | ||
| const cmd = os2 === "darwin" ? "open" : "xdg-open"; | ||
| execFile(cmd, [url], (err) => { | ||
| if (err) onError(); | ||
| }); | ||
| } | ||
| } | ||
| var PAGE_STYLE = `*{margin:0;box-sizing:border-box} | ||
| body{font-family:system-ui,-apple-system,sans-serif;display:flex;justify-content:center;align-items:center;min-height:100vh;background:#fff;color:#252525} | ||
| @media(prefers-color-scheme:dark){body{background:#252525;color:#f5f5f5}} | ||
| .card{text-align:center;padding:2rem 2.5rem;border-radius:10px;max-width:380px;width:100%} | ||
| .icon{width:40px;height:40px;margin:0 auto 1rem;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:1.25rem} | ||
| .icon.ok{background:rgba(0,0,0,.05);color:#252525} | ||
| .icon.err{background:rgba(220,38,38,.08);color:#dc2626} | ||
| @media(prefers-color-scheme:dark){.icon.ok{background:rgba(255,255,255,.08);color:#f5f5f5}} | ||
| h1{font-size:.875rem;font-weight:600;margin-bottom:.375rem} | ||
| p{font-size:.75rem;color:#737373;line-height:1.5}`; | ||
| var SUCCESS_HTML = `<!DOCTYPE html> | ||
| <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Login</title> | ||
| <style>${PAGE_STYLE}</style> | ||
| </head><body><div class="card"><div class="icon ok">\u2713</div><h1>Authenticated</h1><p>You can close this tab and return to the terminal.</p></div></body></html>`; | ||
| var ERROR_HTML = (msg) => `<!DOCTYPE html> | ||
| <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Login Error</title> | ||
| <style>${PAGE_STYLE}</style> | ||
| </head><body><div class="card"><div class="icon err">!</div><h1>Authentication failed</h1><p>${escapeHtml(msg)}</p></div></body></html>`; | ||
| async function exchangeCode(webUrl, code) { | ||
| const url = `${webUrl}/api/cli/exchange`; | ||
| try { | ||
| const res = await fetch(url, { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ code }) | ||
| }); | ||
| if (!res.ok) { | ||
| const body = await res.text().catch(() => ""); | ||
| console.error( | ||
| pc.red( | ||
| `Exchange failed: HTTP ${res.status} from ${url}${body ? ` \u2014 ${body.slice(0, 200)}` : ""}` | ||
| ) | ||
| ); | ||
| return null; | ||
| } | ||
| const data = await res.json(); | ||
| if (typeof data.publishableKey !== "string" || typeof data.secretKey !== "string" || typeof data.tenantName !== "string" || typeof data.tenantId !== "string") { | ||
| console.error(pc.red(`Exchange failed: malformed response from ${url}`)); | ||
| return null; | ||
| } | ||
| return { | ||
| publishableKey: data.publishableKey, | ||
| secretKey: data.secretKey, | ||
| tenantName: data.tenantName, | ||
| tenantId: data.tenantId | ||
| }; | ||
| } catch (err) { | ||
| console.error( | ||
| pc.red( | ||
| `Exchange request to ${url} failed: ${err instanceof Error ? err.message : String(err)}` | ||
| ) | ||
| ); | ||
| return null; | ||
| } | ||
| } | ||
| async function startBrowserAuth(options) { | ||
| const state = randomBytes(32).toString("hex"); | ||
| const webUrl = options?.webUrl ?? DEFAULT_WEB_URL; | ||
| return new Promise((resolve, reject) => { | ||
| const server = createServer((req, res) => { | ||
| if (!req.url) { | ||
| res.writeHead(400).end(); | ||
| return; | ||
| } | ||
| const url = new URL(req.url, `http://localhost`); | ||
| if (url.pathname !== "/callback" || req.method !== "GET") { | ||
| res.writeHead(404).end(); | ||
| return; | ||
| } | ||
| const error = url.searchParams.get("error"); | ||
| if (error) { | ||
| res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(ERROR_HTML(error)); | ||
| console.error(pc.red(`Login failed: ${error}`)); | ||
| cleanup(new Error(`Login failed: ${error}`)); | ||
| return; | ||
| } | ||
| const code = url.searchParams.get("code"); | ||
| const receivedState = url.searchParams.get("state"); | ||
| if (!code || !receivedState) { | ||
| res.writeHead(400, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(ERROR_HTML("Missing code or state.")); | ||
| cleanup(new Error("Login failed: missing code or state.")); | ||
| return; | ||
| } | ||
| if (receivedState !== state) { | ||
| res.writeHead(403, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(ERROR_HTML("State mismatch.")); | ||
| console.error(pc.red("Login failed: state mismatch.")); | ||
| cleanup(new Error("Login failed: state mismatch.")); | ||
| return; | ||
| } | ||
| exchangeCode(webUrl, code).then((creds) => { | ||
| if (!creds) { | ||
| res.writeHead(400, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(ERROR_HTML("Invalid or expired code.")); | ||
| cleanup(new Error("Login failed: code exchange failed.")); | ||
| return; | ||
| } | ||
| res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(SUCCESS_HTML); | ||
| console.log(pc.green(` | ||
| Logged in successfully!`)); | ||
| console.log(pc.dim(`Tenant: ${creds.tenantName}`)); | ||
| cleanup(null, creds); | ||
| }); | ||
| }); | ||
| let timeout; | ||
| let completed = false; | ||
| function cleanup(err, result) { | ||
| if (completed) return; | ||
| completed = true; | ||
| clearTimeout(timeout); | ||
| server.closeAllConnections?.(); | ||
| server.close(() => { | ||
| if (err) { | ||
| reject(err); | ||
| } else { | ||
| resolve(result); | ||
| } | ||
| }); | ||
| } | ||
| server.listen(0, "127.0.0.1", () => { | ||
| const addr = server.address(); | ||
| if (!addr || typeof addr === "string") { | ||
| reject(new Error("Failed to start local server.")); | ||
| return; | ||
| } | ||
| const port = addr.port; | ||
| timeout = setTimeout(() => { | ||
| console.error(pc.red("\nLogin timed out (5 minutes). Please try again.")); | ||
| cleanup(new Error("Login timed out")); | ||
| }, TIMEOUT_MS); | ||
| const params = new URLSearchParams({ port: String(port), state }); | ||
| if (options?.tenantId) { | ||
| params.set("tenantId", options.tenantId); | ||
| } | ||
| const loginUrl = `${webUrl}/cli-auth?${params.toString()}`; | ||
| console.log(pc.dim("Opening browser for login...")); | ||
| console.log(pc.dim(`If the browser does not open, visit: | ||
| ${loginUrl}`)); | ||
| openBrowser(loginUrl); | ||
| }); | ||
| server.on("error", (err) => { | ||
| reject(err); | ||
| }); | ||
| }); | ||
| } | ||
| // src/init.ts | ||
| var SECRET_KEY_ENV_VAR = "SOFTWARE_SECRET_KEY"; | ||
| async function init(cwd, info, answers) { | ||
| const { packageManager, srcDir } = info; | ||
| const env = answers.env; | ||
| const baseDir = srcDir ? path2.join(cwd, "src") : cwd; | ||
| const publishableKeyEnvVar = getPublishableKeyEnvVar(env); | ||
| const wantsClient = needsClient(env); | ||
| const wantsServer = needsServer(env); | ||
| const wantsReactQuery = needsReactQuery(env); | ||
| const plan = await planConflictsAndEnv(cwd, baseDir, env, answers); | ||
| const installResult = installDeps( | ||
| cwd, | ||
| packageManager, | ||
| info.hasSdk, | ||
| info.hasReactQuery, | ||
| wantsReactQuery | ||
| ); | ||
| if (wantsClient || wantsReactQuery || wantsServer) { | ||
| fs2.mkdirSync(path2.join(baseDir, "lib", "software"), { recursive: true }); | ||
| } | ||
| const libDir = path2.join(baseDir, "lib", "software"); | ||
| if (wantsClient) { | ||
| await writeFileWithPolicy( | ||
| cwd, | ||
| path2.join(libDir, "client.ts"), | ||
| getClientTemplate(env, publishableKeyEnvVar), | ||
| plan.policy | ||
| ); | ||
| await writeFileWithPolicy( | ||
| cwd, | ||
| path2.join(libDir, "analytics.ts"), | ||
| getAnalyticsTemplate(env, publishableKeyEnvVar), | ||
| plan.policy | ||
| ); | ||
| } | ||
| if (wantsReactQuery) { | ||
| await writeFileWithPolicy( | ||
| cwd, | ||
| path2.join(libDir, "query-provider.tsx"), | ||
| getQueryProviderTemplate(env), | ||
| plan.policy | ||
| ); | ||
| } | ||
| if (wantsServer) { | ||
| await writeFileWithPolicy( | ||
| cwd, | ||
| path2.join(libDir, "server.ts"), | ||
| getServerTemplate(env, publishableKeyEnvVar, SECRET_KEY_ENV_VAR), | ||
| plan.policy | ||
| ); | ||
| } | ||
| if (plan.envFile && answers.authMethod !== "browser") { | ||
| await writeEnv( | ||
| cwd, | ||
| plan.envFile, | ||
| answers.publishableKey || "", | ||
| answers.secretKey || "", | ||
| publishableKeyEnvVar, | ||
| wantsServer ? SECRET_KEY_ENV_VAR : null, | ||
| plan.policy | ||
| ); | ||
| } | ||
| let publishableKey = answers.publishableKey; | ||
| let secretKey = answers.secretKey; | ||
| let tenantName = ""; | ||
| if (answers.authMethod === "browser" && answers.aiTools.length > 0) { | ||
| try { | ||
| console.log(); | ||
| const creds = await startBrowserAuth(); | ||
| publishableKey = creds.publishableKey; | ||
| secretKey = creds.secretKey; | ||
| tenantName = creds.tenantName; | ||
| if (plan.envFile && publishableKey) { | ||
| await writeEnv( | ||
| cwd, | ||
| plan.envFile, | ||
| publishableKey, | ||
| secretKey, | ||
| publishableKeyEnvVar, | ||
| wantsServer ? SECRET_KEY_ENV_VAR : null, | ||
| "overwrite", | ||
| true | ||
| ); | ||
| } | ||
| } catch (err) { | ||
| console.log( | ||
| pc2.yellow(" Browser auth skipped:"), | ||
| err instanceof Error ? err.message : String(err) | ||
| ); | ||
| } | ||
| } | ||
| if (answers.aiTools.length > 0) { | ||
| for (const tool of answers.aiTools) { | ||
| await writeMcpConfig(tool, cwd); | ||
| } | ||
| addToGitignore(cwd, answers.aiTools); | ||
| if (answers.aiTools.includes("claude")) { | ||
| await writeClaudeDocs(cwd, publishableKey, secretKey, tenantName, plan.policy); | ||
| } | ||
| } | ||
| return installResult; | ||
| } | ||
| function installDeps(cwd, pm, hasSdk, hasReactQuery, wantsReactQuery) { | ||
| const allDeps = [ | ||
| { name: "@01.software/sdk", installed: hasSdk, needed: true }, | ||
| { name: "@tanstack/react-query", installed: hasReactQuery, needed: wantsReactQuery } | ||
| ]; | ||
| const fullList = allDeps.filter((d) => d.needed).map((d) => d.name); | ||
| const toInstall = allDeps.filter((d) => d.needed && !d.installed).map((d) => d.name); | ||
| const fullCmd = buildAddCmd(pm, hasPnpmWorkspace(cwd), fullList); | ||
| if (toInstall.length === 0) { | ||
| console.log(pc2.dim(` Dependencies already installed: ${fullList.join(", ")}`)); | ||
| return { installFailed: false, installSkipped: true, installCmd: fullCmd }; | ||
| } | ||
| const addCmd = buildAddCmd(pm, hasPnpmWorkspace(cwd), toInstall); | ||
| console.log(pc2.dim(` Installing ${toInstall.join(" and ")}...`)); | ||
| const wsPatched = pm === "pnpm" && patchPnpmWorkspace(cwd); | ||
| let installFailed = false; | ||
| try { | ||
| execSync(addCmd, { cwd, stdio: "pipe" }); | ||
| console.log(pc2.green(" Installed"), toInstall.join(", ")); | ||
| } catch (error) { | ||
| installFailed = true; | ||
| const err = error; | ||
| const msg = String(err.stderr || "").trim() || String(err.stdout || "").trim() || String(error); | ||
| console.log(pc2.yellow(" Install failed \u2014 continuing with scaffolding")); | ||
| const firstLines = msg.split("\n").slice(0, 3).map((l) => ` ${l}`).join("\n"); | ||
| if (firstLines) console.log(pc2.dim(firstLines)); | ||
| console.log(pc2.dim(` Run manually: ${addCmd}`)); | ||
| } finally { | ||
| if (wsPatched) restorePnpmWorkspace(cwd); | ||
| } | ||
| return { installFailed, installSkipped: false, installCmd: addCmd }; | ||
| } | ||
| function buildAddCmd(pm, hasPnpmWs, deps) { | ||
| const pkgs = deps.join(" "); | ||
| switch (pm) { | ||
| case "pnpm": | ||
| return hasPnpmWs ? `pnpm add -w ${pkgs}` : `pnpm add ${pkgs}`; | ||
| case "yarn": | ||
| return `yarn add ${pkgs}`; | ||
| case "bun": | ||
| return `bun add ${pkgs}`; | ||
| default: | ||
| return `npm install ${pkgs}`; | ||
| } | ||
| } | ||
| async function planConflictsAndEnv(cwd, baseDir, env, answers) { | ||
| const candidates = []; | ||
| const libDir = path2.join(baseDir, "lib", "software"); | ||
| if (needsClient(env)) candidates.push(path2.join(libDir, "client.ts")); | ||
| if (needsReactQuery(env)) candidates.push(path2.join(libDir, "query-provider.tsx")); | ||
| if (needsServer(env)) candidates.push(path2.join(libDir, "server.ts")); | ||
| if (answers.aiTools.includes("claude")) { | ||
| for (const { dirName } of getSkillFiles()) { | ||
| candidates.push(path2.join(cwd, ".claude", "skills", dirName, "SKILL.md")); | ||
| } | ||
| } | ||
| const conflicts = candidates.filter((p) => fs2.existsSync(p)); | ||
| let policy = "skip"; | ||
| if (conflicts.length > 0) { | ||
| console.log(pc2.yellow(` ${conflicts.length} file(s) already exist:`)); | ||
| for (const c of conflicts) console.log(pc2.dim(` ${path2.relative(cwd, c)}`)); | ||
| const { selected } = await prompts2({ | ||
| type: "select", | ||
| name: "selected", | ||
| message: "How should I handle existing files?", | ||
| choices: [ | ||
| { title: "Keep existing (skip)", value: "skip" }, | ||
| { title: "Overwrite all", value: "overwrite" }, | ||
| { title: "Ask for each", value: "ask" } | ||
| ], | ||
| initial: 0 | ||
| }); | ||
| policy = selected ?? "skip"; | ||
| } | ||
| const envFile = env === "vanilla" || env === "edge" ? "" : await pickEnvFile(cwd, env); | ||
| return { policy, envFile }; | ||
| } | ||
| async function pickEnvFile(cwd, env) { | ||
| const candidates = [".env.local", ".env", ".env.development"]; | ||
| const existing = candidates.filter((f) => fs2.existsSync(path2.join(cwd, f))); | ||
| const preferred = env === "nextjs" ? ".env.local" : ".env"; | ||
| if (existing.length === 0) return preferred; | ||
| if (existing.length === 1 && existing[0] === preferred) return existing[0]; | ||
| const options = Array.from(/* @__PURE__ */ new Set([...existing, preferred])); | ||
| const choices = options.map((f) => ({ | ||
| title: f, | ||
| description: existing.includes(f) ? "exists" : "create", | ||
| value: f | ||
| })); | ||
| const initial = Math.max( | ||
| 0, | ||
| choices.findIndex((c) => c.value === preferred) | ||
| ); | ||
| const { file } = await prompts2({ | ||
| type: "select", | ||
| name: "file", | ||
| message: "Which env file should I write SDK credentials to?", | ||
| choices, | ||
| initial | ||
| }); | ||
| return file ?? preferred; | ||
| } | ||
| async function writeFileWithPolicy(cwd, filePath, content, policy) { | ||
| const rel = path2.relative(cwd, filePath); | ||
| if (!fs2.existsSync(filePath)) { | ||
| fs2.mkdirSync(path2.dirname(filePath), { recursive: true }); | ||
| fs2.writeFileSync(filePath, content); | ||
| console.log(pc2.green(" Created"), rel); | ||
| return; | ||
| } | ||
| const existing = fs2.readFileSync(filePath, "utf-8"); | ||
| if (existing === content) { | ||
| console.log(pc2.dim(" Unchanged"), rel); | ||
| return; | ||
| } | ||
| let shouldWrite = false; | ||
| if (policy === "overwrite") { | ||
| shouldWrite = true; | ||
| } else if (policy === "ask") { | ||
| const { confirm } = await prompts2({ | ||
| type: "confirm", | ||
| name: "confirm", | ||
| message: `Overwrite ${rel}?`, | ||
| initial: false | ||
| }); | ||
| shouldWrite = !!confirm; | ||
| } | ||
| if (shouldWrite) { | ||
| fs2.writeFileSync(filePath, content); | ||
| console.log(pc2.green(" Overwrote"), rel); | ||
| } else { | ||
| console.log(pc2.yellow(" Skipped"), rel, pc2.dim("(already exists)")); | ||
| } | ||
| } | ||
| async function writeEnv(cwd, envFile, publishableKey, secretKey, publishableKeyEnvVar, secretKeyEnvVar, policy, fromBrowserAuth = false) { | ||
| const envPath = path2.join(cwd, envFile); | ||
| const targets = [ | ||
| { name: publishableKeyEnvVar, value: publishableKey } | ||
| ]; | ||
| if (secretKeyEnvVar) { | ||
| targets.push({ name: secretKeyEnvVar, value: secretKey }); | ||
| } | ||
| if (!fs2.existsSync(envPath)) { | ||
| const initial = getEnvContent(publishableKey, secretKey, publishableKeyEnvVar, secretKeyEnvVar); | ||
| fs2.writeFileSync(envPath, initial.trimStart()); | ||
| console.log(pc2.green(" Created"), envFile); | ||
| return; | ||
| } | ||
| let content = fs2.readFileSync(envPath, "utf-8"); | ||
| let modified = false; | ||
| let appendedHeader = false; | ||
| const headerAlreadyPresent = targets.some( | ||
| (t) => readEnvValue(content, t.name) !== null | ||
| ); | ||
| for (const { name, value } of targets) { | ||
| const existing = readEnvValue(content, name); | ||
| if (existing === null) { | ||
| if (!headerAlreadyPresent && !appendedHeader) { | ||
| if (content.length > 0 && !content.endsWith("\n")) content += "\n"; | ||
| content += "\n# 01.software\n"; | ||
| appendedHeader = true; | ||
| } | ||
| content = setEnvValue(content, name, value); | ||
| modified = true; | ||
| continue; | ||
| } | ||
| if (existing === value) continue; | ||
| if (!value) continue; | ||
| let shouldOverwrite = false; | ||
| if (policy === "overwrite") { | ||
| shouldOverwrite = true; | ||
| } else if (policy === "ask") { | ||
| const { confirm } = await prompts2({ | ||
| type: "confirm", | ||
| name: "confirm", | ||
| message: `${name} already set in ${envFile}. Overwrite?`, | ||
| initial: fromBrowserAuth | ||
| }); | ||
| shouldOverwrite = !!confirm; | ||
| } | ||
| if (shouldOverwrite) { | ||
| content = setEnvValue(content, name, value); | ||
| modified = true; | ||
| } | ||
| } | ||
| if (modified) { | ||
| fs2.writeFileSync(envPath, content); | ||
| console.log( | ||
| pc2.green(" Updated"), | ||
| envFile, | ||
| fromBrowserAuth ? pc2.dim("(SDK credentials)") : "" | ||
| ); | ||
| } else { | ||
| console.log(pc2.dim(" Unchanged"), envFile); | ||
| } | ||
| } | ||
| function resolveMcpLocation(tool, cwd) { | ||
| const home = os.homedir(); | ||
| switch (tool) { | ||
| case "claude": | ||
| return { | ||
| kind: "json", | ||
| absolutePath: path2.join(cwd, ".mcp.json"), | ||
| displayPath: ".mcp.json", | ||
| gitignoreEntry: ".mcp.json" | ||
| }; | ||
| case "cursor": | ||
| return { | ||
| kind: "json", | ||
| absolutePath: path2.join(cwd, ".cursor", "mcp.json"), | ||
| displayPath: ".cursor/mcp.json", | ||
| gitignoreEntry: ".cursor/mcp.json" | ||
| }; | ||
| case "vscode": | ||
| return { | ||
| kind: "json", | ||
| absolutePath: path2.join(cwd, ".vscode", "mcp.json"), | ||
| displayPath: ".vscode/mcp.json", | ||
| gitignoreEntry: ".vscode/mcp.json" | ||
| }; | ||
| case "windsurf": { | ||
| if (!home) return null; | ||
| const p = path2.join(home, ".codeium", "windsurf", "mcp_config.json"); | ||
| return { | ||
| kind: "json", | ||
| absolutePath: p, | ||
| jsonClient: "windsurf", | ||
| displayPath: p, | ||
| gitignoreEntry: null | ||
| }; | ||
| } | ||
| case "codex": { | ||
| if (!home) return null; | ||
| const p = path2.join(home, ".codex", "config.toml"); | ||
| return { kind: "toml", absolutePath: p, displayPath: p, gitignoreEntry: null }; | ||
| } | ||
| case "gemini": { | ||
| if (!home) return null; | ||
| const p = path2.join(home, ".gemini", "settings.json"); | ||
| return { kind: "json", absolutePath: p, displayPath: p, gitignoreEntry: null }; | ||
| } | ||
| } | ||
| } | ||
| async function writeMcpConfig(tool, cwd) { | ||
| const loc = resolveMcpLocation(tool, cwd); | ||
| if (!loc) { | ||
| console.log(pc2.yellow(` Skipped ${tool}`), pc2.dim("(HOME not set)")); | ||
| return; | ||
| } | ||
| fs2.mkdirSync(path2.dirname(loc.absolutePath), { recursive: true }); | ||
| if (loc.kind === "json") { | ||
| writeJsonMcp(loc); | ||
| } else { | ||
| writeTomlMcp(loc); | ||
| } | ||
| } | ||
| function writeJsonMcp(loc) { | ||
| if (!fs2.existsSync(loc.absolutePath)) { | ||
| fs2.writeFileSync(loc.absolutePath, getMcpConfigTemplate(loc.jsonClient)); | ||
| console.log(pc2.green(" Created"), loc.displayPath); | ||
| return; | ||
| } | ||
| let existing; | ||
| try { | ||
| existing = JSON.parse(fs2.readFileSync(loc.absolutePath, "utf-8")); | ||
| } catch { | ||
| console.log(pc2.yellow(" Skipped"), loc.displayPath, pc2.dim("(could not parse existing file)")); | ||
| return; | ||
| } | ||
| const nextEntry = getMcpServerEntry(loc.jsonClient); | ||
| if (existing.mcpServers?.["01software"] && JSON.stringify(existing.mcpServers["01software"]) === JSON.stringify(nextEntry)) { | ||
| console.log(pc2.dim(" Unchanged"), loc.displayPath); | ||
| return; | ||
| } | ||
| existing.mcpServers = existing.mcpServers || {}; | ||
| existing.mcpServers["01software"] = nextEntry; | ||
| fs2.writeFileSync(loc.absolutePath, JSON.stringify(existing, null, 2) + "\n"); | ||
| console.log(pc2.green(" Updated"), loc.displayPath); | ||
| } | ||
| function writeTomlMcp(loc) { | ||
| const section = getCodexMcpTomlSection(); | ||
| if (!fs2.existsSync(loc.absolutePath)) { | ||
| fs2.writeFileSync(loc.absolutePath, section.trimStart()); | ||
| console.log(pc2.green(" Created"), loc.displayPath); | ||
| return; | ||
| } | ||
| const existing = fs2.readFileSync(loc.absolutePath, "utf-8"); | ||
| if (!existing.includes(CODEX_MCP_SECTION_MARKER)) { | ||
| const sep = existing.endsWith("\n") ? "" : "\n"; | ||
| fs2.appendFileSync(loc.absolutePath, sep + section); | ||
| console.log(pc2.green(" Updated"), loc.displayPath); | ||
| return; | ||
| } | ||
| const replaced = replaceTomlMcpSection(existing, section); | ||
| if (replaced === existing) { | ||
| console.log(pc2.dim(" Unchanged"), loc.displayPath); | ||
| return; | ||
| } | ||
| fs2.writeFileSync(loc.absolutePath, replaced); | ||
| console.log(pc2.green(" Updated"), loc.displayPath); | ||
| } | ||
| function addToGitignore(cwd, tools) { | ||
| const entries = []; | ||
| for (const tool of tools) { | ||
| const loc = resolveMcpLocation(tool, cwd); | ||
| if (loc?.gitignoreEntry) entries.push(loc.gitignoreEntry); | ||
| } | ||
| if (entries.length === 0) return; | ||
| const gitignorePath = path2.join(cwd, ".gitignore"); | ||
| const existing = fs2.existsSync(gitignorePath) ? fs2.readFileSync(gitignorePath, "utf-8") : ""; | ||
| const toAdd = entries.filter((e) => !existing.includes(e)); | ||
| if (toAdd.length === 0) return; | ||
| const content = "\n# MCP configs\n" + toAdd.join("\n") + "\n"; | ||
| if (fs2.existsSync(gitignorePath)) { | ||
| fs2.appendFileSync(gitignorePath, content); | ||
| } else { | ||
| fs2.writeFileSync(gitignorePath, content.trimStart()); | ||
| } | ||
| console.log(pc2.green(" Updated"), ".gitignore", pc2.dim(`(added ${toAdd.join(", ")})`)); | ||
| } | ||
| async function writeClaudeDocs(cwd, publishableKey, secretKey, tenantName, policy) { | ||
| let ctx = { | ||
| tenantName: tenantName || "Your Tenant", | ||
| features: void 0, | ||
| collections: void 0 | ||
| }; | ||
| if (publishableKey && secretKey) { | ||
| const fetched = await fetchTenantContext(publishableKey, secretKey); | ||
| if (fetched) { | ||
| ctx = { | ||
| tenantName: fetched.tenantName || ctx.tenantName, | ||
| features: fetched.features, | ||
| collections: fetched.collections | ||
| }; | ||
| } | ||
| } | ||
| const claudeDir = path2.join(cwd, ".claude"); | ||
| const softwareDir = path2.join(claudeDir, "01software"); | ||
| const skillsDir = path2.join(claudeDir, "skills"); | ||
| fs2.mkdirSync(softwareDir, { recursive: true }); | ||
| fs2.mkdirSync(skillsDir, { recursive: true }); | ||
| const contextPath = path2.join(softwareDir, "context.md"); | ||
| const contextExists = fs2.existsSync(contextPath); | ||
| fs2.writeFileSync(contextPath, generateClaudeMd(ctx)); | ||
| console.log(pc2.green(contextExists ? " Updated" : " Created"), ".claude/01software/context.md"); | ||
| const claudeMdPath = path2.join(claudeDir, "CLAUDE.md"); | ||
| const importLine = "@.claude/01software/context.md"; | ||
| if (!fs2.existsSync(claudeMdPath)) { | ||
| fs2.writeFileSync(claudeMdPath, importLine + "\n"); | ||
| console.log(pc2.green(" Created"), ".claude/CLAUDE.md"); | ||
| } else { | ||
| const existing = fs2.readFileSync(claudeMdPath, "utf-8"); | ||
| if (!existing.includes(importLine)) { | ||
| const prefix = existing.endsWith("\n") ? "\n" : "\n\n"; | ||
| fs2.appendFileSync(claudeMdPath, prefix + importLine + "\n"); | ||
| console.log(pc2.green(" Updated"), ".claude/CLAUDE.md", pc2.dim("(added @import)")); | ||
| } else { | ||
| console.log(pc2.dim(" Unchanged"), ".claude/CLAUDE.md"); | ||
| } | ||
| } | ||
| for (const { dirName, content } of getSkillFiles()) { | ||
| const skillDir = path2.join(skillsDir, dirName); | ||
| const skillPath = path2.join(skillDir, "SKILL.md"); | ||
| fs2.mkdirSync(skillDir, { recursive: true }); | ||
| await writeFileWithPolicy(cwd, skillPath, content, policy); | ||
| } | ||
| } | ||
| var WS_FILE = "pnpm-workspace.yaml"; | ||
| var WS_BACKUP = "pnpm-workspace.yaml.bak"; | ||
| function hasPnpmWorkspace(cwd) { | ||
| return fs2.existsSync(path2.join(cwd, WS_FILE)); | ||
| } | ||
| function patchPnpmWorkspace(cwd) { | ||
| const wsPath = path2.join(cwd, WS_FILE); | ||
| if (!fs2.existsSync(wsPath)) return false; | ||
| const content = fs2.readFileSync(wsPath, "utf-8"); | ||
| if (content.includes("packages:")) return false; | ||
| fs2.copyFileSync(wsPath, path2.join(cwd, WS_BACKUP)); | ||
| fs2.writeFileSync(wsPath, content.trimEnd() + "\npackages: []\n"); | ||
| return true; | ||
| } | ||
| function restorePnpmWorkspace(cwd) { | ||
| const backupPath = path2.join(cwd, WS_BACKUP); | ||
| if (!fs2.existsSync(backupPath)) return; | ||
| fs2.copyFileSync(backupPath, path2.join(cwd, WS_FILE)); | ||
| fs2.unlinkSync(backupPath); | ||
| } | ||
| // src/index.ts | ||
@@ -982,4 +208,4 @@ var ENV_LABELS = { | ||
| console.log(); | ||
| console.log(pc3.bold(" @01.software/init")); | ||
| console.log(pc3.dim(" Initialize 01.software SDK in your project")); | ||
| console.log(pc.bold(" @01.software/init")); | ||
| console.log(pc.dim(" Initialize 01.software SDK in your project")); | ||
| console.log(); | ||
@@ -989,7 +215,7 @@ const info = detectProject(cwd); | ||
| if (info.parseError) { | ||
| console.log(pc3.red(" Could not parse package.json (invalid JSON).")); | ||
| console.log(pc3.dim(" Fix the syntax error and try again.")); | ||
| console.log(pc.red(" Could not parse package.json (invalid JSON).")); | ||
| console.log(pc.dim(" Fix the syntax error and try again.")); | ||
| } else { | ||
| console.log(pc3.red(" No package.json found in the current directory.")); | ||
| console.log(pc3.dim(" Run this command inside an existing project.")); | ||
| console.log(pc.red(" No package.json found in the current directory.")); | ||
| console.log(pc.dim(" Run this command inside an existing project.")); | ||
| } | ||
@@ -1003,3 +229,3 @@ console.log(); | ||
| if (info.env !== "node") { | ||
| console.log(pc3.dim(` Detected: ${detectedParts.join(" / ")}`)); | ||
| console.log(pc.dim(` Detected: ${detectedParts.join(" / ")}`)); | ||
| console.log(); | ||
@@ -1010,7 +236,7 @@ } | ||
| if (!answers) { | ||
| console.log(pc3.yellow(" Cancelled.")); | ||
| console.log(pc.yellow(" Cancelled.")); | ||
| process.exit(0); | ||
| } | ||
| if (answers.env === "other") { | ||
| console.log(pc3.yellow(" Manual setup required for your framework:")); | ||
| console.log(pc.yellow(" Manual setup required for your framework:")); | ||
| console.log(OTHER_FRAMEWORK_GUIDE); | ||
@@ -1026,3 +252,3 @@ process.exit(0); | ||
| console.log(); | ||
| console.log(pc3.green(" Done!")); | ||
| console.log(pc.green(" Done!")); | ||
| console.log(); | ||
@@ -1032,61 +258,63 @@ console.log(" Next steps:"); | ||
| if (result.installFailed) { | ||
| console.log(pc3.yellow(" Install the SDK manually:")); | ||
| console.log(pc3.cyan(` ${result.installCmd}`)); | ||
| console.log(pc.yellow(" Install the SDK manually:")); | ||
| console.log(pc.cyan(` ${result.installCmd}`)); | ||
| console.log(); | ||
| } | ||
| if (env === "nextjs") { | ||
| console.log(pc3.dim(" Add QueryProvider to your root layout:")); | ||
| console.log(pc.dim(" Add QueryProvider to your root layout:")); | ||
| console.log(); | ||
| console.log(pc3.cyan(" import { QueryProvider } from '@/lib/software/query-provider'")); | ||
| console.log(pc3.cyan(" <QueryProvider>{children}</QueryProvider>")); | ||
| console.log(pc.cyan(" import { QueryProvider } from '@/lib/software/query-provider'")); | ||
| console.log(pc.cyan(" <QueryProvider>{children}</QueryProvider>")); | ||
| console.log(); | ||
| console.log(pc3.dim(" Optional: start browser analytics with the generated helper:")); | ||
| console.log(pc.dim(" Optional: start browser analytics with the generated helper:")); | ||
| console.log(); | ||
| console.log(pc3.cyan(" import { analytics } from '@/lib/software/analytics'")); | ||
| console.log(pc3.cyan(" analytics.track('signup')")); | ||
| console.log(pc.cyan(" import { analytics } from '@/lib/software/analytics'")); | ||
| console.log(pc.cyan(" analytics.track('signup')")); | ||
| console.log(); | ||
| } else if (env === "react-vite" || env === "react-cra") { | ||
| console.log(pc3.dim(" Wrap your app entry with QueryProvider:")); | ||
| console.log(pc.dim(" Wrap your app entry with QueryProvider:")); | ||
| console.log(); | ||
| console.log(pc3.cyan(" import { QueryProvider } from './lib/software/query-provider'")); | ||
| console.log(pc3.cyan(" <QueryProvider><App /></QueryProvider>")); | ||
| console.log(pc.cyan(" import { QueryProvider } from './lib/software/query-provider'")); | ||
| console.log(pc.cyan(" <QueryProvider><App /></QueryProvider>")); | ||
| console.log(); | ||
| console.log(pc3.dim(" Optional: start browser analytics with the generated helper:")); | ||
| console.log(pc.dim(" Optional: start browser analytics with the generated helper:")); | ||
| console.log(); | ||
| console.log(pc3.cyan(" import { analytics } from './lib/software/analytics'")); | ||
| console.log(pc3.cyan(" analytics.track('signup')")); | ||
| console.log(pc.cyan(" import { analytics } from './lib/software/analytics'")); | ||
| console.log(pc.cyan(" analytics.track('signup')")); | ||
| console.log(); | ||
| } else if (env === "vanilla") { | ||
| console.log(pc3.dim(" Replace YOUR_PUBLISHABLE_KEY in lib/software/client.ts")); | ||
| console.log(pc.dim(" Replace YOUR_PUBLISHABLE_KEY in lib/software/client.ts")); | ||
| console.log(); | ||
| console.log(pc3.cyan(" import { client } from './lib/software/client'")); | ||
| console.log(pc3.cyan(" const articles = await client.from('articles').find()")); | ||
| console.log(pc.cyan(" import { client } from './lib/software/client'")); | ||
| console.log(pc.cyan(" const articles = await client.collections.from('articles').find()")); | ||
| console.log(); | ||
| console.log(pc3.dim(" Optional: wire the analytics helper in lib/software/analytics.ts")); | ||
| console.log(pc.dim(" Optional: wire the analytics helper in lib/software/analytics.ts")); | ||
| console.log(); | ||
| } else if (env === "node") { | ||
| console.log(pc3.dim(" Use the server client:")); | ||
| console.log(pc.dim(" Use the server client:")); | ||
| console.log(); | ||
| console.log(pc3.cyan(" import { serverClient } from './lib/software/server'")); | ||
| console.log(pc3.cyan(" const articles = await serverClient.from('articles').find()")); | ||
| console.log(pc.cyan(" import { serverClient } from './lib/software/server'")); | ||
| console.log(pc.cyan(" const articles = await serverClient.collections.from('articles').find()")); | ||
| console.log(); | ||
| } else if (env === "edge") { | ||
| console.log(pc3.dim(" Pass your env bindings to createEdgeClient():")); | ||
| console.log(pc.dim(" Pass your env bindings to createEdgeClient():")); | ||
| console.log(); | ||
| console.log(pc3.cyan(" import { createEdgeClient } from './lib/software/server'")); | ||
| console.log(pc3.cyan(" const serverClient = createEdgeClient(env.PUBLISHABLE_KEY, env.SECRET_KEY)")); | ||
| console.log(pc.cyan(" import { createEdgeClient } from './lib/software/server'")); | ||
| console.log(pc.cyan(" const serverClient = createEdgeClient(env.PUBLISHABLE_KEY, env.SECRET_KEY)")); | ||
| console.log(); | ||
| } | ||
| const missingPublishableKey = env !== "vanilla" && !answers.publishableKey; | ||
| const missingSecretKey = (env === "nextjs" || env === "node") && !answers.secretKey; | ||
| const effectivePublishableKey = result.publishableKey ?? answers.publishableKey; | ||
| const effectiveSecretKey = result.secretKey ?? answers.secretKey; | ||
| const missingPublishableKey = env !== "vanilla" && !effectivePublishableKey; | ||
| const missingSecretKey = (env === "nextjs" || env === "node") && !effectiveSecretKey; | ||
| if (missingPublishableKey || missingSecretKey) { | ||
| console.log(pc3.dim(" Update .env with your SDK credentials")); | ||
| console.log(pc.dim(" Update .env with your SDK credentials")); | ||
| console.log(); | ||
| } | ||
| if (answers.aiTools.length > 0) { | ||
| console.log(pc3.dim(" MCP config uses OAuth discovery.")); | ||
| console.log(pc.dim(" MCP config uses OAuth discovery.")); | ||
| console.log(); | ||
| } | ||
| if (env !== "vanilla") { | ||
| console.log(pc3.cyan(` ${run} dev`)); | ||
| console.log(pc.cyan(` ${run} dev`)); | ||
| console.log(); | ||
@@ -1096,6 +324,6 @@ } | ||
| if (error instanceof Error && error.message === "cancelled") { | ||
| console.log(pc3.yellow(" Cancelled.")); | ||
| console.log(pc.yellow(" Cancelled.")); | ||
| process.exit(0); | ||
| } | ||
| console.error(pc3.red(" Error:"), error); | ||
| console.error(pc.red(" Error:"), error); | ||
| process.exit(1); | ||
@@ -1102,0 +330,0 @@ } |
@@ -9,6 +9,7 @@ #!/usr/bin/env node | ||
| getMcpConfigTemplate, | ||
| getMcpRootKey, | ||
| getMcpServerEntry, | ||
| getQueryProviderTemplate, | ||
| getServerTemplate | ||
| } from "./chunk-S3KHPWCE.js"; | ||
| } from "./chunk-TBGKXE3Q.js"; | ||
| export { | ||
@@ -21,2 +22,3 @@ CODEX_MCP_SECTION_MARKER, | ||
| getMcpConfigTemplate, | ||
| getMcpRootKey, | ||
| getMcpServerEntry, | ||
@@ -23,0 +25,0 @@ getQueryProviderTemplate, |
+5
-4
| { | ||
| "name": "@01.software/init", | ||
| "version": "0.7.0", | ||
| "version": "0.8.0", | ||
| "description": "Initialize 01.software SDK in your project (Next.js, React, Vanilla JS, Node.js, Edge)", | ||
@@ -17,4 +17,3 @@ "type": "module", | ||
| "picocolors": "^1.1.1", | ||
| "prompts": "^2.4.2", | ||
| "@01.software/auth-contracts": "0.1.0" | ||
| "prompts": "^2.4.2" | ||
| }, | ||
@@ -25,3 +24,4 @@ "devDependencies": { | ||
| "tsup": "^8.5.0", | ||
| "typescript": "^5.9.3" | ||
| "typescript": "^5.9.3", | ||
| "@01.software/sdk": "0.20.0" | ||
| }, | ||
@@ -36,2 +36,3 @@ "author": "<office@01.works>", | ||
| "check-types": "tsc --noEmit", | ||
| "check-snippets": "tsc --project tsconfig.fixture.json --noEmit", | ||
| "test": "pnpm run build && node --test tests/**/*.test.mjs", | ||
@@ -38,0 +39,0 @@ "release": "node ../../scripts/publish.js" |
| #!/usr/bin/env node | ||
| // src/file-ops.ts | ||
| var envLineRegexCache = /* @__PURE__ */ new Map(); | ||
| function envLineRegex(name) { | ||
| let re = envLineRegexCache.get(name); | ||
| if (!re) { | ||
| const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); | ||
| re = new RegExp(`^${escaped}=(.*)$`, "m"); | ||
| envLineRegexCache.set(name, re); | ||
| } | ||
| return re; | ||
| } | ||
| function readEnvValue(content, name) { | ||
| const m = content.match(envLineRegex(name)); | ||
| return m ? m[1] : null; | ||
| } | ||
| function setEnvValue(content, name, value) { | ||
| const re = envLineRegex(name); | ||
| if (re.test(content)) return content.replace(re, `${name}=${value}`); | ||
| const sep = content.length === 0 || content.endsWith("\n") ? "" : "\n"; | ||
| return content + sep + `${name}=${value} | ||
| `; | ||
| } | ||
| var OWNED_MCP_TOML_SECTION = "mcp_servers.01software"; | ||
| function tomlSectionName(line) { | ||
| const match = line.trim().match(/^\[([^\]]+)\]$/); | ||
| return match ? match[1] : null; | ||
| } | ||
| function isOwnedMcpTomlSection(sectionName) { | ||
| return sectionName === OWNED_MCP_TOML_SECTION || sectionName.startsWith(`${OWNED_MCP_TOML_SECTION}.`); | ||
| } | ||
| function replaceTomlMcpSection(content, newSection) { | ||
| const lines = content.split("\n"); | ||
| const kept = []; | ||
| let inOurSection = false; | ||
| for (const line of lines) { | ||
| const sectionName = tomlSectionName(line); | ||
| if (sectionName) { | ||
| inOurSection = isOwnedMcpTomlSection(sectionName); | ||
| if (inOurSection) continue; | ||
| } | ||
| if (!inOurSection) kept.push(line); | ||
| } | ||
| let result = kept.join("\n").replace(/\n+$/, ""); | ||
| if (result.length > 0) result += "\n"; | ||
| result += newSection.startsWith("\n") ? newSection : "\n" + newSection; | ||
| return result; | ||
| } | ||
| export { | ||
| readEnvValue, | ||
| setEnvValue, | ||
| replaceTomlMcpSection | ||
| }; | ||
| //# sourceMappingURL=chunk-JT3G6B66.js.map |
| {"version":3,"sources":["../src/file-ops.ts"],"sourcesContent":["// Pure helpers for in-place file content manipulation. Kept side-effect-free\n// so they can be unit-tested without touching the filesystem or prompts.\n\n// ── .env merge ───────────────────────────────────────────────────────\n\nconst envLineRegexCache = new Map<string, RegExp>()\n\nfunction envLineRegex(name: string): RegExp {\n let re = envLineRegexCache.get(name)\n if (!re) {\n const escaped = name.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n re = new RegExp(`^${escaped}=(.*)$`, 'm')\n envLineRegexCache.set(name, re)\n }\n return re\n}\n\nexport function readEnvValue(content: string, name: string): string | null {\n const m = content.match(envLineRegex(name))\n return m ? m[1] : null\n}\n\nexport function setEnvValue(content: string, name: string, value: string): string {\n const re = envLineRegex(name)\n if (re.test(content)) return content.replace(re, `${name}=${value}`)\n const sep = content.length === 0 || content.endsWith('\\n') ? '' : '\\n'\n return content + sep + `${name}=${value}\\n`\n}\n\n// ── Codex TOML manipulation ──────────────────────────────────────────\n\nconst OWNED_MCP_TOML_SECTION = 'mcp_servers.01software'\n\nfunction tomlSectionName(line: string): string | null {\n const match = line.trim().match(/^\\[([^\\]]+)\\]$/)\n return match ? match[1] : null\n}\n\nfunction isOwnedMcpTomlSection(sectionName: string): boolean {\n return (\n sectionName === OWNED_MCP_TOML_SECTION ||\n sectionName.startsWith(`${OWNED_MCP_TOML_SECTION}.`)\n )\n}\n\n/** Removes the existing `[mcp_servers.01software]` block and sub-blocks from a\n * TOML document, then appends the provided section. */\nexport function replaceTomlMcpSection(content: string, newSection: string): string {\n const lines = content.split('\\n')\n const kept: string[] = []\n let inOurSection = false\n for (const line of lines) {\n const sectionName = tomlSectionName(line)\n if (sectionName) {\n inOurSection = isOwnedMcpTomlSection(sectionName)\n if (inOurSection) continue\n }\n if (!inOurSection) kept.push(line)\n }\n let result = kept.join('\\n').replace(/\\n+$/, '')\n if (result.length > 0) result += '\\n'\n // newSection already starts with a blank line; keep it that way\n result += newSection.startsWith('\\n') ? newSection : '\\n' + newSection\n return result\n}\n"],"mappings":";;;AAKA,IAAM,oBAAoB,oBAAI,IAAoB;AAElD,SAAS,aAAa,MAAsB;AAC1C,MAAI,KAAK,kBAAkB,IAAI,IAAI;AACnC,MAAI,CAAC,IAAI;AACP,UAAM,UAAU,KAAK,QAAQ,uBAAuB,MAAM;AAC1D,SAAK,IAAI,OAAO,IAAI,OAAO,UAAU,GAAG;AACxC,sBAAkB,IAAI,MAAM,EAAE;AAAA,EAChC;AACA,SAAO;AACT;AAEO,SAAS,aAAa,SAAiB,MAA6B;AACzE,QAAM,IAAI,QAAQ,MAAM,aAAa,IAAI,CAAC;AAC1C,SAAO,IAAI,EAAE,CAAC,IAAI;AACpB;AAEO,SAAS,YAAY,SAAiB,MAAc,OAAuB;AAChF,QAAM,KAAK,aAAa,IAAI;AAC5B,MAAI,GAAG,KAAK,OAAO,EAAG,QAAO,QAAQ,QAAQ,IAAI,GAAG,IAAI,IAAI,KAAK,EAAE;AACnE,QAAM,MAAM,QAAQ,WAAW,KAAK,QAAQ,SAAS,IAAI,IAAI,KAAK;AAClE,SAAO,UAAU,MAAM,GAAG,IAAI,IAAI,KAAK;AAAA;AACzC;AAIA,IAAM,yBAAyB;AAE/B,SAAS,gBAAgB,MAA6B;AACpD,QAAM,QAAQ,KAAK,KAAK,EAAE,MAAM,gBAAgB;AAChD,SAAO,QAAQ,MAAM,CAAC,IAAI;AAC5B;AAEA,SAAS,sBAAsB,aAA8B;AAC3D,SACE,gBAAgB,0BAChB,YAAY,WAAW,GAAG,sBAAsB,GAAG;AAEvD;AAIO,SAAS,sBAAsB,SAAiB,YAA4B;AACjF,QAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,QAAM,OAAiB,CAAC;AACxB,MAAI,eAAe;AACnB,aAAW,QAAQ,OAAO;AACxB,UAAM,cAAc,gBAAgB,IAAI;AACxC,QAAI,aAAa;AACf,qBAAe,sBAAsB,WAAW;AAChD,UAAI,aAAc;AAAA,IACpB;AACA,QAAI,CAAC,aAAc,MAAK,KAAK,IAAI;AAAA,EACnC;AACA,MAAI,SAAS,KAAK,KAAK,IAAI,EAAE,QAAQ,QAAQ,EAAE;AAC/C,MAAI,OAAO,SAAS,EAAG,WAAU;AAEjC,YAAU,WAAW,WAAW,IAAI,IAAI,aAAa,OAAO;AAC5D,SAAO;AACT;","names":[]} |
| #!/usr/bin/env node | ||
| // ../auth-contracts/dist/index.js | ||
| var MCP_RESOURCE_AUDIENCE = "https://mcp.01.software/mcp"; | ||
| // src/templates.ts | ||
| function getClientTemplate(env, publishableKeyEnvVar) { | ||
| if (env === "nextjs") { | ||
| return `import { createClient } from '@01.software/sdk' | ||
| export const client = createClient({ | ||
| publishableKey: process.env.${publishableKeyEnvVar}!, | ||
| }) | ||
| `; | ||
| } | ||
| if (env === "react-cra") { | ||
| return `import { createClient } from '@01.software/sdk' | ||
| export const client = createClient({ | ||
| publishableKey: process.env.${publishableKeyEnvVar}!, | ||
| }) | ||
| `; | ||
| } | ||
| if (env === "vanilla") { | ||
| return `import { createClient } from '@01.software/sdk' | ||
| // Replace 'YOUR_PUBLISHABLE_KEY' with your actual publishable key from the 01.software console | ||
| export const client = createClient({ | ||
| publishableKey: 'YOUR_PUBLISHABLE_KEY', | ||
| }) | ||
| `; | ||
| } | ||
| return `import { createClient } from '@01.software/sdk' | ||
| export const client = createClient({ | ||
| publishableKey: import.meta.env.${publishableKeyEnvVar}, | ||
| }) | ||
| `; | ||
| } | ||
| function getAnalyticsTemplate(env, publishableKeyEnvVar) { | ||
| if (env === "vanilla") { | ||
| return `import { createAnalytics } from '@01.software/sdk/analytics' | ||
| // Replace 'YOUR_PUBLISHABLE_KEY' with your actual publishable key from the 01.software console | ||
| export const analytics = createAnalytics({ | ||
| publishableKey: 'YOUR_PUBLISHABLE_KEY', | ||
| }) | ||
| `; | ||
| } | ||
| const publishableKeyExpression = env === "react-vite" ? `import.meta.env.${publishableKeyEnvVar}` : `process.env.${publishableKeyEnvVar}!`; | ||
| return `import { createAnalytics } from '@01.software/sdk/analytics' | ||
| export const analytics = createAnalytics({ | ||
| publishableKey: ${publishableKeyExpression}, | ||
| }) | ||
| `; | ||
| } | ||
| function getQueryProviderTemplate(env) { | ||
| const useClientDirective = env === "nextjs" ? "'use client'\n\n" : ""; | ||
| return `${useClientDirective}import { QueryClientProvider } from '@tanstack/react-query' | ||
| import { client } from './client' | ||
| export function QueryProvider({ children }: { children: React.ReactNode }) { | ||
| return ( | ||
| <QueryClientProvider client={client.queryClient}> | ||
| {children} | ||
| </QueryClientProvider> | ||
| ) | ||
| } | ||
| `; | ||
| } | ||
| function getServerTemplate(env, publishableKeyEnvVar, secretKeyEnvVar) { | ||
| if (env === "edge") { | ||
| return `import { createServerClient } from '@01.software/sdk' | ||
| // Edge runtime: pass your env bindings here | ||
| // e.g. Cloudflare Workers: use env.SOFTWARE_PUBLISHABLE_KEY from the handler context | ||
| // e.g. Vercel Edge: use process.env.${publishableKeyEnvVar} | ||
| export function createEdgeClient(publishableKey: string, secretKey: string) { | ||
| return createServerClient({ publishableKey, secretKey }) | ||
| } | ||
| `; | ||
| } | ||
| return `import { createServerClient } from '@01.software/sdk' | ||
| /** | ||
| * Runtime guard: this module contains secret keys and must never be | ||
| * imported from client-side (browser) code. If the bundler accidentally | ||
| * includes it in a client bundle, this throws at module-evaluation time. | ||
| */ | ||
| if (typeof window !== 'undefined') { | ||
| throw new Error( | ||
| 'lib/software/server.ts must not be imported in client-side code \u2014 it contains secret keys.', | ||
| ) | ||
| } | ||
| type ServerClient = ReturnType<typeof createServerClient> | ||
| let cachedClient: ServerClient | null = null | ||
| function createConfiguredClient(): ServerClient { | ||
| const publishableKey = process.env.${publishableKeyEnvVar} | ||
| const secretKey = process.env.${secretKeyEnvVar} | ||
| if (!publishableKey || !secretKey) { | ||
| throw new Error( | ||
| 'Server client requires ${publishableKeyEnvVar} and ${secretKeyEnvVar}.', | ||
| ) | ||
| } | ||
| return createServerClient({ publishableKey, secretKey }) | ||
| } | ||
| export function getServerClient(): ServerClient { | ||
| cachedClient ??= createConfiguredClient() | ||
| return cachedClient | ||
| } | ||
| /** | ||
| * Lazy Proxy: the underlying client is created only on first property access, | ||
| * so importing this file has no side effects until you actually call it. | ||
| */ | ||
| export const serverClient = new Proxy({} as ServerClient, { | ||
| get(_target, prop, receiver) { | ||
| const target = getServerClient() | ||
| const value = Reflect.get(target as object, prop, receiver) | ||
| return typeof value === 'function' ? value.bind(target) : value | ||
| }, | ||
| }) | ||
| `; | ||
| } | ||
| function getEnvContent(publishableKey, secretKey, publishableKeyEnvVar, secretKeyEnvVar) { | ||
| let content = ` | ||
| # 01.software | ||
| ${publishableKeyEnvVar}=${publishableKey} | ||
| `; | ||
| if (secretKeyEnvVar) { | ||
| content += `${secretKeyEnvVar}=${secretKey} | ||
| `; | ||
| } | ||
| return content; | ||
| } | ||
| function getMcpServerEntry(client = "generic") { | ||
| if (client === "windsurf") { | ||
| return { | ||
| serverUrl: MCP_RESOURCE_AUDIENCE | ||
| }; | ||
| } | ||
| return { | ||
| type: "http", | ||
| url: MCP_RESOURCE_AUDIENCE | ||
| }; | ||
| } | ||
| function getMcpConfigTemplate(client = "generic") { | ||
| return JSON.stringify( | ||
| { mcpServers: { "01software": getMcpServerEntry(client) } }, | ||
| null, | ||
| 2 | ||
| ) + "\n"; | ||
| } | ||
| function getCodexMcpTomlSection() { | ||
| return ` | ||
| [mcp_servers.01software] | ||
| url = "${MCP_RESOURCE_AUDIENCE}" | ||
| `; | ||
| } | ||
| var CODEX_MCP_SECTION_MARKER = "[mcp_servers.01software]"; | ||
| export { | ||
| getClientTemplate, | ||
| getAnalyticsTemplate, | ||
| getQueryProviderTemplate, | ||
| getServerTemplate, | ||
| getEnvContent, | ||
| getMcpServerEntry, | ||
| getMcpConfigTemplate, | ||
| getCodexMcpTomlSection, | ||
| CODEX_MCP_SECTION_MARKER | ||
| }; | ||
| //# sourceMappingURL=chunk-S3KHPWCE.js.map |
| {"version":3,"sources":["../../auth-contracts/src/index.ts","../src/templates.ts"],"sourcesContent":["export const MCP_RESOURCE_AUDIENCE = 'https://mcp.01.software/mcp'\n\nexport const MCP_OAUTH_ISSUER = 'https://01.software'\n\nexport const MCP_PROTECTED_RESOURCE_METADATA_PATH =\n '/.well-known/oauth-protected-resource/mcp'\n\nexport const MCP_AUTHORIZATION_SERVER_METADATA_PATH =\n '/.well-known/oauth-authorization-server'\n\nexport const MCP_TENANT_CLAIM = 'tenant_id'\n\nexport const MCP_TENANT_ROLE_CLAIM = 'tenant_role'\n\nexport const MCP_SCOPES = {\n read: 'mcp:read',\n write: 'mcp:write',\n} as const\n\nexport const MCP_CONSOLE_SERVICE_AUDIENCE =\n 'https://api.01.software/internal/mcp'\n\nexport const MCP_CONSOLE_SERVICE_SCOPE = 'console:mcp_proxy'\n\nexport const MCP_SERVICE_TOKEN_LIFETIME_SECONDS = 60\n","import type { ProjectEnv } from './detect'\nimport { MCP_RESOURCE_AUDIENCE } from '@01.software/auth-contracts'\n\n// ── Client template (browser) ────────────────────────────────────────\n\nexport function getClientTemplate(env: ProjectEnv, publishableKeyEnvVar: string): string {\n if (env === 'nextjs') {\n return `import { createClient } from '@01.software/sdk'\n\nexport const client = createClient({\n publishableKey: process.env.${publishableKeyEnvVar}!,\n})\n`\n }\n\n if (env === 'react-cra') {\n return `import { createClient } from '@01.software/sdk'\n\nexport const client = createClient({\n publishableKey: process.env.${publishableKeyEnvVar}!,\n})\n`\n }\n\n if (env === 'vanilla') {\n return `import { createClient } from '@01.software/sdk'\n\n// Replace 'YOUR_PUBLISHABLE_KEY' with your actual publishable key from the 01.software console\nexport const client = createClient({\n publishableKey: 'YOUR_PUBLISHABLE_KEY',\n})\n`\n }\n\n // react-vite (import.meta.env)\n return `import { createClient } from '@01.software/sdk'\n\nexport const client = createClient({\n publishableKey: import.meta.env.${publishableKeyEnvVar},\n})\n`\n}\n\n// ── Analytics template (browser) ─────────────────────────────────────\n\nexport function getAnalyticsTemplate(\n env: ProjectEnv,\n publishableKeyEnvVar: string,\n): string {\n if (env === 'vanilla') {\n return `import { createAnalytics } from '@01.software/sdk/analytics'\n\n// Replace 'YOUR_PUBLISHABLE_KEY' with your actual publishable key from the 01.software console\nexport const analytics = createAnalytics({\n publishableKey: 'YOUR_PUBLISHABLE_KEY',\n})\n`\n }\n\n const publishableKeyExpression =\n env === 'react-vite'\n ? `import.meta.env.${publishableKeyEnvVar}`\n : `process.env.${publishableKeyEnvVar}!`\n\n return `import { createAnalytics } from '@01.software/sdk/analytics'\n\nexport const analytics = createAnalytics({\n publishableKey: ${publishableKeyExpression},\n})\n`\n}\n\n// ── Query Provider template ──────────────────────────────────────────\n\nexport function getQueryProviderTemplate(env: ProjectEnv): string {\n const useClientDirective = env === 'nextjs' ? \"'use client'\\n\\n\" : ''\n\n return `${useClientDirective}import { QueryClientProvider } from '@tanstack/react-query'\nimport { client } from './client'\n\nexport function QueryProvider({ children }: { children: React.ReactNode }) {\n return (\n <QueryClientProvider client={client.queryClient}>\n {children}\n </QueryClientProvider>\n )\n}\n`\n}\n\n// ── Server template ──────────────────────────────────────────────────\n\nexport function getServerTemplate(\n env: ProjectEnv,\n publishableKeyEnvVar: string,\n secretKeyEnvVar: string,\n): string {\n if (env === 'edge') {\n return `import { createServerClient } from '@01.software/sdk'\n\n// Edge runtime: pass your env bindings here\n// e.g. Cloudflare Workers: use env.SOFTWARE_PUBLISHABLE_KEY from the handler context\n// e.g. Vercel Edge: use process.env.${publishableKeyEnvVar}\nexport function createEdgeClient(publishableKey: string, secretKey: string) {\n return createServerClient({ publishableKey, secretKey })\n}\n`\n }\n\n return `import { createServerClient } from '@01.software/sdk'\n\n/**\n * Runtime guard: this module contains secret keys and must never be\n * imported from client-side (browser) code. If the bundler accidentally\n * includes it in a client bundle, this throws at module-evaluation time.\n */\nif (typeof window !== 'undefined') {\n throw new Error(\n 'lib/software/server.ts must not be imported in client-side code — it contains secret keys.',\n )\n}\n\ntype ServerClient = ReturnType<typeof createServerClient>\n\nlet cachedClient: ServerClient | null = null\n\nfunction createConfiguredClient(): ServerClient {\n const publishableKey = process.env.${publishableKeyEnvVar}\n const secretKey = process.env.${secretKeyEnvVar}\n\n if (!publishableKey || !secretKey) {\n throw new Error(\n 'Server client requires ${publishableKeyEnvVar} and ${secretKeyEnvVar}.',\n )\n }\n\n return createServerClient({ publishableKey, secretKey })\n}\n\nexport function getServerClient(): ServerClient {\n cachedClient ??= createConfiguredClient()\n return cachedClient\n}\n\n/**\n * Lazy Proxy: the underlying client is created only on first property access,\n * so importing this file has no side effects until you actually call it.\n */\nexport const serverClient = new Proxy({} as ServerClient, {\n get(_target, prop, receiver) {\n const target = getServerClient()\n const value = Reflect.get(target as object, prop, receiver)\n return typeof value === 'function' ? value.bind(target) : value\n },\n})\n`\n}\n\n// ── Env file content ─────────────────────────────────────────────────\n\nexport function getEnvContent(\n publishableKey: string,\n secretKey: string,\n publishableKeyEnvVar: string,\n secretKeyEnvVar: string | null,\n): string {\n let content = `\\n# 01.software\\n${publishableKeyEnvVar}=${publishableKey}\\n`\n if (secretKeyEnvVar) {\n content += `${secretKeyEnvVar}=${secretKey}\\n`\n }\n return content\n}\n\n// ── MCP config (JSON) ────────────────────────────────────────────────\n\nexport type McpJsonClient = 'generic' | 'windsurf'\n\nexport function getMcpServerEntry(client: McpJsonClient = 'generic') {\n if (client === 'windsurf') {\n return {\n serverUrl: MCP_RESOURCE_AUDIENCE,\n }\n }\n\n return {\n type: 'http' as const,\n url: MCP_RESOURCE_AUDIENCE,\n }\n}\n\nexport function getMcpConfigTemplate(client: McpJsonClient = 'generic'): string {\n return (\n JSON.stringify(\n { mcpServers: { '01software': getMcpServerEntry(client) } },\n null,\n 2,\n ) + '\\n'\n )\n}\n\n// ── MCP config (TOML — Codex CLI) ────────────────────────────────────\n\n/** Codex CLI `[mcp_servers.01software]` block. Idempotent marker: the header\n * line. Intended to be appended to `~/.codex/config.toml`. */\nexport function getCodexMcpTomlSection(): string {\n return `\n[mcp_servers.01software]\nurl = \"${MCP_RESOURCE_AUDIENCE}\"\n`\n}\n\nexport const CODEX_MCP_SECTION_MARKER = '[mcp_servers.01software]'\n"],"mappings":";;;AAAO,IAAM,wBAAwB;;;ACK9B,SAAS,kBAAkB,KAAiB,sBAAsC;AACvF,MAAI,QAAQ,UAAU;AACpB,WAAO;AAAA;AAAA;AAAA,gCAGqB,oBAAoB;AAAA;AAAA;AAAA,EAGlD;AAEA,MAAI,QAAQ,aAAa;AACvB,WAAO;AAAA;AAAA;AAAA,gCAGqB,oBAAoB;AAAA;AAAA;AAAA,EAGlD;AAEA,MAAI,QAAQ,WAAW;AACrB,WAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOT;AAGA,SAAO;AAAA;AAAA;AAAA,oCAG2B,oBAAoB;AAAA;AAAA;AAGxD;AAIO,SAAS,qBACd,KACA,sBACQ;AACR,MAAI,QAAQ,WAAW;AACrB,WAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOT;AAEA,QAAM,2BACJ,QAAQ,eACJ,mBAAmB,oBAAoB,KACvC,eAAe,oBAAoB;AAEzC,SAAO;AAAA;AAAA;AAAA,oBAGW,wBAAwB;AAAA;AAAA;AAG5C;AAIO,SAAS,yBAAyB,KAAyB;AAChE,QAAM,qBAAqB,QAAQ,WAAW,qBAAqB;AAEnE,SAAO,GAAG,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAW9B;AAIO,SAAS,kBACd,KACA,sBACA,iBACQ;AACR,MAAI,QAAQ,QAAQ;AAClB,WAAO;AAAA;AAAA;AAAA;AAAA,8CAImC,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKhE;AAEA,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uCAkB8B,oBAAoB;AAAA,kCACzB,eAAe;AAAA;AAAA;AAAA;AAAA,gCAIjB,oBAAoB,QAAQ,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwB3E;AAIO,SAAS,cACd,gBACA,WACA,sBACA,iBACQ;AACR,MAAI,UAAU;AAAA;AAAA,EAAoB,oBAAoB,IAAI,cAAc;AAAA;AACxE,MAAI,iBAAiB;AACnB,eAAW,GAAG,eAAe,IAAI,SAAS;AAAA;AAAA,EAC5C;AACA,SAAO;AACT;AAMO,SAAS,kBAAkB,SAAwB,WAAW;AACnE,MAAI,WAAW,YAAY;AACzB,WAAO;AAAA,MACL,WAAW;AAAA,IACb;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,KAAK;AAAA,EACP;AACF;AAEO,SAAS,qBAAqB,SAAwB,WAAmB;AAC9E,SACE,KAAK;AAAA,IACH,EAAE,YAAY,EAAE,cAAc,kBAAkB,MAAM,EAAE,EAAE;AAAA,IAC1D;AAAA,IACA;AAAA,EACF,IAAI;AAER;AAMO,SAAS,yBAAiC;AAC/C,SAAO;AAAA;AAAA,SAEA,qBAAqB;AAAA;AAE9B;AAEO,IAAM,2BAA2B;","names":[]} |
| #!/usr/bin/env node | ||
| // src/ai-docs.ts | ||
| function normalizeActiveCollections(collections) { | ||
| if (Array.isArray(collections?.active)) return collections.active; | ||
| return []; | ||
| } | ||
| function generateClaudeMd(ctx) { | ||
| const featuresSection = ctx.features && ctx.features.length > 0 ? ctx.features.map((f) => `- ${f}`).join("\n") : "- See console"; | ||
| const collectionsSection = ctx.collections && ctx.collections.length > 0 ? ctx.collections.join(", ") : "Run `01 schema list`"; | ||
| return `# 01.software SDK \u2014 ${ctx.tenantName} | ||
| ## Connection | ||
| - Publishable Key: \`NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY\` (env) | ||
| - Secret Key: \`SOFTWARE_SECRET_KEY\` (env) | ||
| - MCP: \`.mcp.json\` | ||
| ## Active Features | ||
| ${featuresSection} | ||
| ## Active Collections | ||
| ${collectionsSection} | ||
| ## MCP Quick Reference | ||
| | Tool | Use | | ||
| |------|-----| | ||
| | \`query-collection\` | List/filter documents | | ||
| | \`create-collection\` | Create documents | | ||
| | \`update-field-config\` | Hide unused fields | | ||
| | \`get-tenant-context\` | Show active features & collections | | ||
| ## CLI | ||
| - \`01 query <collection>\` \u2014 query data | ||
| - \`01 schema show <collection>\` \u2014 inspect fields | ||
| - \`01 schema list\` \u2014 list all collections | ||
| ## Initial Setup | ||
| Run \`/01software-field-config\` in Claude Code to configure field visibility for your use case. | ||
| Use \`get-collection-schema\` or \`01 schema show <collection>\` for live field introspection instead of relying on this document as a schema snapshot. | ||
| `; | ||
| } | ||
| function getSkillFiles() { | ||
| return [ | ||
| { | ||
| dirName: "01software-field-config", | ||
| content: `--- | ||
| name: 01software-field-config | ||
| description: Configure field visibility for this tenant \u2014 hide unused collections and fields via MCP | ||
| disable-model-invocation: true | ||
| --- | ||
| Steps: | ||
| 1. Use \`list-configurable-fields\` to see current visibility settings | ||
| 2. Identify fields/collections not needed for your use case | ||
| 3. Use \`update-field-config\` to hide them | ||
| Common setups: | ||
| - Blog only: hide \`ecommerce\`, \`customers\`, \`videos\` collections | ||
| - Store: hide \`articles\`, \`documents\`, \`galleries\`, \`canvas\` collections | ||
| - Minimal: hide all except the collections you actively use | ||
| Ask me: "Show current field config" or "Hide ecommerce fields" | ||
| ` | ||
| }, | ||
| { | ||
| dirName: "01software-query", | ||
| content: `--- | ||
| name: 01software-query | ||
| description: Query 01.software collections via MCP or CLI with filter, sort, and pagination examples | ||
| --- | ||
| Query collections using the MCP \`query-collection\` tool or CLI. | ||
| MCP examples: | ||
| - List products: \`query-collection\` with collection="products", limit=10 | ||
| - Filter by status: add where={"status":{"equals":"published"}} | ||
| - Sort by date: sort="-createdAt" | ||
| - Paginate: page=2, limit=20 | ||
| CLI examples: | ||
| - \`01 query products --limit 10\` | ||
| - \`01 query orders --where '{"status":{"equals":"paid"}}'\` | ||
| - \`01 schema show products\` \u2014 inspect available fields | ||
| SDK (server): | ||
| \`\`\`typescript | ||
| const { docs } = await serverClient.collection('products').find({ | ||
| where: { status: { equals: 'published' } }, | ||
| sort: '-createdAt', | ||
| limit: 10, | ||
| }) | ||
| \`\`\` | ||
| ` | ||
| }, | ||
| { | ||
| dirName: "01software-order-flow", | ||
| content: `--- | ||
| name: 01software-order-flow | ||
| description: Order lifecycle reference \u2014 create, pay, fulfill, and return flows for 01.software | ||
| --- | ||
| Complete order flow from creation to fulfillment. | ||
| States: pending \u2192 paid \u2192 preparing \u2192 shipped \u2192 delivered \u2192 confirmed | ||
| 1. Create order: \`create-order\` with orderNumber, customerSnapshot, orderProducts, totalAmount | ||
| 2. Mark paid: \`update-order\` with status="paid" (after payment gateway confirms) | ||
| 3. Fulfill: \`create-fulfillment\` with items and carrier/trackingNumber | ||
| 4. Returns: \`create-return\` or \`return-with-refund\` (atomic) | ||
| Free orders: omit paymentId, totalAmount=0 \u2192 auto-transitions to paid | ||
| CLI: \`01 order create --help\` for full options | ||
| ` | ||
| }, | ||
| { | ||
| dirName: "01software-schema", | ||
| content: `--- | ||
| name: 01software-schema | ||
| description: Inspect 01.software collection schemas and available fields via MCP or CLI | ||
| --- | ||
| Inspect collection schemas to understand available fields. | ||
| MCP: use \`get-collection-schema\` with collection | ||
| CLI: | ||
| - \`01 schema list\` \u2014 all available collections | ||
| - \`01 schema show <collection>\` \u2014 field names, types, required status | ||
| Common collections: products, orders, customers, articles, documents, images | ||
| Use \`get-tenant-context\` to see which collections are active for this tenant. | ||
| ` | ||
| } | ||
| ]; | ||
| } | ||
| async function fetchTenantContext(publishableKey, secretKey) { | ||
| try { | ||
| const apiUrl = process.env.SOFTWARE_API_URL || "https://api.01.software"; | ||
| const res = await fetch(`${apiUrl}/api/tenants/context`, { | ||
| headers: { | ||
| "X-Publishable-Key": publishableKey, | ||
| Authorization: `Bearer ${secretKey}` | ||
| } | ||
| }); | ||
| if (!res.ok) return null; | ||
| const data = await res.json(); | ||
| return { | ||
| tenantName: data.tenant?.name || "", | ||
| features: data.features || [], | ||
| collections: normalizeActiveCollections(data.collections) | ||
| }; | ||
| } catch { | ||
| return null; | ||
| } | ||
| } | ||
| export { | ||
| generateClaudeMd, | ||
| getSkillFiles, | ||
| fetchTenantContext | ||
| }; | ||
| //# sourceMappingURL=chunk-T3A5SLEJ.js.map |
| {"version":3,"sources":["../src/ai-docs.ts"],"sourcesContent":["export interface TenantContext {\n tenantName: string\n features?: string[]\n collections?: string[]\n}\n\ninterface TenantContextApiResponse {\n tenant?: { name?: string }\n features?: string[]\n collections?: { active?: string[]; inactive?: string[] }\n}\n\nfunction normalizeActiveCollections(\n collections: TenantContextApiResponse['collections'],\n): string[] {\n if (Array.isArray(collections?.active)) return collections.active\n return []\n}\n\n// ── CLAUDE.md ────────────────────────────────────────────────────────\n\nexport function generateClaudeMd(ctx: TenantContext): string {\n const featuresSection =\n ctx.features && ctx.features.length > 0\n ? ctx.features.map((f) => `- ${f}`).join('\\n')\n : '- See console'\n\n const collectionsSection =\n ctx.collections && ctx.collections.length > 0\n ? ctx.collections.join(', ')\n : 'Run `01 schema list`'\n\n return `# 01.software SDK — ${ctx.tenantName}\n\n## Connection\n- Publishable Key: \\`NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY\\` (env)\n- Secret Key: \\`SOFTWARE_SECRET_KEY\\` (env)\n- MCP: \\`.mcp.json\\`\n\n## Active Features\n${featuresSection}\n\n## Active Collections\n${collectionsSection}\n\n## MCP Quick Reference\n| Tool | Use |\n|------|-----|\n| \\`query-collection\\` | List/filter documents |\n| \\`create-collection\\` | Create documents |\n| \\`update-field-config\\` | Hide unused fields |\n| \\`get-tenant-context\\` | Show active features & collections |\n\n## CLI\n- \\`01 query <collection>\\` — query data\n- \\`01 schema show <collection>\\` — inspect fields\n- \\`01 schema list\\` — list all collections\n\n## Initial Setup\nRun \\`/01software-field-config\\` in Claude Code to configure field visibility for your use case.\nUse \\`get-collection-schema\\` or \\`01 schema show <collection>\\` for live field introspection instead of relying on this document as a schema snapshot.\n`\n}\n\n// ── Skill files ──────────────────────────────────────────────────────\n\nexport function getSkillFiles(): Array<{ dirName: string; content: string }> {\n return [\n {\n dirName: '01software-field-config',\n content: `---\nname: 01software-field-config\ndescription: Configure field visibility for this tenant — hide unused collections and fields via MCP\ndisable-model-invocation: true\n---\n\nSteps:\n1. Use \\`list-configurable-fields\\` to see current visibility settings\n2. Identify fields/collections not needed for your use case\n3. Use \\`update-field-config\\` to hide them\n\nCommon setups:\n- Blog only: hide \\`ecommerce\\`, \\`customers\\`, \\`videos\\` collections\n- Store: hide \\`articles\\`, \\`documents\\`, \\`galleries\\`, \\`canvas\\` collections\n- Minimal: hide all except the collections you actively use\n\nAsk me: \"Show current field config\" or \"Hide ecommerce fields\"\n`,\n },\n {\n dirName: '01software-query',\n content: `---\nname: 01software-query\ndescription: Query 01.software collections via MCP or CLI with filter, sort, and pagination examples\n---\n\nQuery collections using the MCP \\`query-collection\\` tool or CLI.\n\nMCP examples:\n- List products: \\`query-collection\\` with collection=\"products\", limit=10\n- Filter by status: add where={\"status\":{\"equals\":\"published\"}}\n- Sort by date: sort=\"-createdAt\"\n- Paginate: page=2, limit=20\n\nCLI examples:\n- \\`01 query products --limit 10\\`\n- \\`01 query orders --where '{\"status\":{\"equals\":\"paid\"}}'\\`\n- \\`01 schema show products\\` — inspect available fields\n\nSDK (server):\n\\`\\`\\`typescript\nconst { docs } = await serverClient.collection('products').find({\n where: { status: { equals: 'published' } },\n sort: '-createdAt',\n limit: 10,\n})\n\\`\\`\\`\n`,\n },\n {\n dirName: '01software-order-flow',\n content: `---\nname: 01software-order-flow\ndescription: Order lifecycle reference — create, pay, fulfill, and return flows for 01.software\n---\n\nComplete order flow from creation to fulfillment.\n\nStates: pending → paid → preparing → shipped → delivered → confirmed\n\n1. Create order: \\`create-order\\` with orderNumber, customerSnapshot, orderProducts, totalAmount\n2. Mark paid: \\`update-order\\` with status=\"paid\" (after payment gateway confirms)\n3. Fulfill: \\`create-fulfillment\\` with items and carrier/trackingNumber\n4. Returns: \\`create-return\\` or \\`return-with-refund\\` (atomic)\n\nFree orders: omit paymentId, totalAmount=0 → auto-transitions to paid\n\nCLI: \\`01 order create --help\\` for full options\n`,\n },\n {\n dirName: '01software-schema',\n content: `---\nname: 01software-schema\ndescription: Inspect 01.software collection schemas and available fields via MCP or CLI\n---\n\nInspect collection schemas to understand available fields.\n\nMCP: use \\`get-collection-schema\\` with collection\n\nCLI:\n- \\`01 schema list\\` — all available collections\n- \\`01 schema show <collection>\\` — field names, types, required status\n\nCommon collections: products, orders, customers, articles, documents, images\nUse \\`get-tenant-context\\` to see which collections are active for this tenant.\n`,\n },\n ]\n}\n\n// ── Tenant context fetch ─────────────────────────────────────────────\n\nexport async function fetchTenantContext(\n publishableKey: string,\n secretKey: string,\n): Promise<TenantContext | null> {\n try {\n const apiUrl = process.env.SOFTWARE_API_URL || 'https://api.01.software'\n // secretKey is now an opaque sk01_/pat01_ bearer token — send it directly.\n const res = await fetch(`${apiUrl}/api/tenants/context`, {\n headers: {\n 'X-Publishable-Key': publishableKey,\n Authorization: `Bearer ${secretKey}`,\n },\n })\n if (!res.ok) return null\n const data = (await res.json()) as TenantContextApiResponse\n return {\n tenantName: data.tenant?.name || '',\n features: data.features || [],\n collections: normalizeActiveCollections(data.collections),\n }\n } catch {\n return null\n }\n}\n"],"mappings":";;;AAYA,SAAS,2BACP,aACU;AACV,MAAI,MAAM,QAAQ,aAAa,MAAM,EAAG,QAAO,YAAY;AAC3D,SAAO,CAAC;AACV;AAIO,SAAS,iBAAiB,KAA4B;AAC3D,QAAM,kBACJ,IAAI,YAAY,IAAI,SAAS,SAAS,IAClC,IAAI,SAAS,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,EAAE,KAAK,IAAI,IAC3C;AAEN,QAAM,qBACJ,IAAI,eAAe,IAAI,YAAY,SAAS,IACxC,IAAI,YAAY,KAAK,IAAI,IACzB;AAEN,SAAO,4BAAuB,IAAI,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ5C,eAAe;AAAA;AAAA;AAAA,EAGf,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBpB;AAIO,SAAS,gBAA6D;AAC3E,SAAO;AAAA,IACL;AAAA,MACE,SAAS;AAAA,MACT,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAkBX;AAAA,IACA;AAAA,MACE,SAAS;AAAA,MACT,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IA2BX;AAAA,IACA;AAAA,MACE,SAAS;AAAA,MACT,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAkBX;AAAA,IACA;AAAA,MACE,SAAS;AAAA,MACT,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAgBX;AAAA,EACF;AACF;AAIA,eAAsB,mBACpB,gBACA,WAC+B;AAC/B,MAAI;AACF,UAAM,SAAS,QAAQ,IAAI,oBAAoB;AAE/C,UAAM,MAAM,MAAM,MAAM,GAAG,MAAM,wBAAwB;AAAA,MACvD,SAAS;AAAA,QACP,qBAAqB;AAAA,QACrB,eAAe,UAAU,SAAS;AAAA,MACpC;AAAA,IACF,CAAC;AACD,QAAI,CAAC,IAAI,GAAI,QAAO;AACpB,UAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,WAAO;AAAA,MACL,YAAY,KAAK,QAAQ,QAAQ;AAAA,MACjC,UAAU,KAAK,YAAY,CAAC;AAAA,MAC5B,aAAa,2BAA2B,KAAK,WAAW;AAAA,IAC1D;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]} |
Sorry, the diff of this file is too big to display
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
158813
8.23%2
-33.33%19
26.67%1616
9.12%5
25%7
16.67%6
20%- Removed
- Removed