🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@01.software/init

Package Overview
Dependencies
Maintainers
1
Versions
22
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@01.software/init - npm Package Compare versions

Comparing version
0.7.0
to
0.8.0
+163
dist/chunk-3MXIG3ZL.js
#!/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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\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":[]}
#!/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,

#!/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
#!/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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
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,

{
"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