@01.software/init 0.6.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,112 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- fetchTenantContext,
4
- generateClaudeMd,
5
- getSkillFiles
6
- } from "./chunk-SRLZ5OIV.js";
7
- import {
8
- extractTomlApiKey,
9
- readEnvValue,
10
- replaceTomlMcpSection,
11
- setEnvValue
12
- } from "./chunk-VOEXMD2S.js";
13
- import {
14
- CODEX_MCP_SECTION_MARKER,
15
- getClientTemplate,
16
- getCodexMcpTomlSection,
17
- getEnvContent,
18
- getMcpConfigTemplate,
19
- getMcpServerEntry,
20
- getQueryProviderTemplate,
21
- getServerTemplate
22
- } from "./chunk-OEAQV63E.js";
3
+ detectProject,
4
+ init
5
+ } from "./chunk-3RQE6YVO.js";
6
+ import "./chunk-3MXIG3ZL.js";
7
+ import "./chunk-UA7WNT2F.js";
8
+ import "./chunk-TBGKXE3Q.js";
23
9
 
24
10
  // src/index.ts
25
- import pc3 from "picocolors";
26
-
27
- // src/detect.ts
28
- import fs from "fs";
29
- import path from "path";
30
- function detectProject(cwd) {
31
- const pkgPath = path.join(cwd, "package.json");
32
- const hasPackageJson = fs.existsSync(pkgPath);
33
- let env = "node";
34
- let hasSdk = false;
35
- let hasReactQuery = false;
36
- let parseError = false;
37
- if (hasPackageJson) {
38
- let pkg;
39
- try {
40
- pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
41
- } catch {
42
- return {
43
- hasPackageJson: true,
44
- parseError: true,
45
- env: "node",
46
- packageManager: null,
47
- hasSdk: false,
48
- hasReactQuery: false,
49
- srcDir: false
50
- };
51
- }
52
- const deps = {
53
- ...pkg.dependencies || {},
54
- ...pkg.devDependencies || {}
55
- };
56
- hasSdk = "@01.software/sdk" in deps;
57
- hasReactQuery = "@tanstack/react-query" in deps;
58
- if ("next" in deps) {
59
- env = "nextjs";
60
- } else if ("astro" in deps || "@astrojs/node" in deps) {
61
- env = "other";
62
- } else if ("@remix-run/node" in deps || "@remix-run/react" in deps) {
63
- env = "other";
64
- } else if ("@sveltejs/kit" in deps) {
65
- env = "other";
66
- } else if ("react" in deps) {
67
- if ("vite" in deps) {
68
- env = "react-vite";
69
- } else if ("react-scripts" in deps) {
70
- env = "react-cra";
71
- } else {
72
- env = "node";
73
- }
74
- }
75
- }
76
- let packageManager = null;
77
- if (fs.existsSync(path.join(cwd, "pnpm-lock.yaml"))) {
78
- packageManager = "pnpm";
79
- } else if (fs.existsSync(path.join(cwd, "yarn.lock"))) {
80
- packageManager = "yarn";
81
- } else if (fs.existsSync(path.join(cwd, "bun.lockb")) || fs.existsSync(path.join(cwd, "bun.lock"))) {
82
- packageManager = "bun";
83
- } else if (fs.existsSync(path.join(cwd, "package-lock.json"))) {
84
- packageManager = "npm";
85
- }
86
- const srcDir = env === "nextjs" ? fs.existsSync(path.join(cwd, "src", "app")) : fs.existsSync(path.join(cwd, "src"));
87
- return { hasPackageJson, parseError, env, packageManager, hasSdk, hasReactQuery, srcDir };
88
- }
89
- function needsClient(env) {
90
- return env === "nextjs" || env === "react-vite" || env === "react-cra" || env === "vanilla";
91
- }
92
- function needsServer(env) {
93
- return env === "nextjs" || env === "node" || env === "edge";
94
- }
95
- function needsReactQuery(env) {
96
- return env === "nextjs" || env === "react-vite" || env === "react-cra";
97
- }
98
- function getPublishableKeyEnvVar(env) {
99
- switch (env) {
100
- case "nextjs":
101
- return "NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY";
102
- case "react-vite":
103
- return "VITE_SOFTWARE_PUBLISHABLE_KEY";
104
- case "react-cra":
105
- return "REACT_APP_SOFTWARE_PUBLISHABLE_KEY";
106
- default:
107
- return "SOFTWARE_PUBLISHABLE_KEY";
108
- }
109
- }
11
+ import pc from "picocolors";
110
12
 
111
13
  // src/prompts.ts
112
14
  import prompts from "prompts";
@@ -240,7 +142,7 @@ async function promptUser(hasSdk, detectedEnv, detectedPm) {
240
142
  {
241
143
  type: "select",
242
144
  name: "method",
243
- message: "API keys:",
145
+ message: "SDK credentials:",
244
146
  choices: [
245
147
  { title: "Browser login (recommended)", value: "browser" },
246
148
  { title: "Enter manually", value: "manual" },
@@ -269,703 +171,6 @@ async function promptUser(hasSdk, detectedEnv, detectedPm) {
269
171
  };
270
172
  }
271
173
 
272
- // src/init.ts
273
- import fs2 from "fs";
274
- import path2 from "path";
275
- import os from "os";
276
- import { execSync } from "child_process";
277
- import pc2 from "picocolors";
278
- import prompts2 from "prompts";
279
-
280
- // src/browser-auth.ts
281
- import { randomBytes } from "crypto";
282
- import { createServer } from "http";
283
- import { execFile, exec } from "child_process";
284
- import { platform } from "os";
285
- import { URL } from "url";
286
- import pc from "picocolors";
287
- var DEFAULT_WEB_URL = process.env.SOFTWARE_WEB_URL || "https://01.software";
288
- var TIMEOUT_MS = 5 * 60 * 1e3;
289
- function escapeHtml(s) {
290
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
291
- }
292
- function openBrowser(url) {
293
- const os2 = platform();
294
- const onError = () => {
295
- console.log(
296
- pc.yellow(
297
- `Could not open browser automatically. Open this URL manually:
298
- ${url}`
299
- )
300
- );
301
- };
302
- if (os2 === "win32") {
303
- exec(`start "" "${url}"`, (err) => {
304
- if (err) onError();
305
- });
306
- } else {
307
- const cmd = os2 === "darwin" ? "open" : "xdg-open";
308
- execFile(cmd, [url], (err) => {
309
- if (err) onError();
310
- });
311
- }
312
- }
313
- var PAGE_STYLE = `*{margin:0;box-sizing:border-box}
314
- body{font-family:system-ui,-apple-system,sans-serif;display:flex;justify-content:center;align-items:center;min-height:100vh;background:#fff;color:#252525}
315
- @media(prefers-color-scheme:dark){body{background:#252525;color:#f5f5f5}}
316
- .card{text-align:center;padding:2rem 2.5rem;border-radius:10px;max-width:380px;width:100%}
317
- .icon{width:40px;height:40px;margin:0 auto 1rem;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:1.25rem}
318
- .icon.ok{background:rgba(0,0,0,.05);color:#252525}
319
- .icon.err{background:rgba(220,38,38,.08);color:#dc2626}
320
- @media(prefers-color-scheme:dark){.icon.ok{background:rgba(255,255,255,.08);color:#f5f5f5}}
321
- h1{font-size:.875rem;font-weight:600;margin-bottom:.375rem}
322
- p{font-size:.75rem;color:#737373;line-height:1.5}`;
323
- var SUCCESS_HTML = `<!DOCTYPE html>
324
- <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Login</title>
325
- <style>${PAGE_STYLE}</style>
326
- </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>`;
327
- var ERROR_HTML = (msg) => `<!DOCTYPE html>
328
- <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Login Error</title>
329
- <style>${PAGE_STYLE}</style>
330
- </head><body><div class="card"><div class="icon err">!</div><h1>Authentication failed</h1><p>${escapeHtml(msg)}</p></div></body></html>`;
331
- async function exchangeCode(webUrl, code) {
332
- const url = `${webUrl}/api/cli/exchange`;
333
- try {
334
- const res = await fetch(url, {
335
- method: "POST",
336
- headers: { "Content-Type": "application/json" },
337
- body: JSON.stringify({ code })
338
- });
339
- if (!res.ok) {
340
- const body = await res.text().catch(() => "");
341
- console.error(
342
- pc.red(
343
- `Exchange failed: HTTP ${res.status} from ${url}${body ? ` \u2014 ${body.slice(0, 200)}` : ""}`
344
- )
345
- );
346
- return null;
347
- }
348
- const data = await res.json();
349
- if (typeof data.publishableKey !== "string" || typeof data.secretKey !== "string" || typeof data.tenantName !== "string" || typeof data.tenantId !== "string") {
350
- console.error(pc.red(`Exchange failed: malformed response from ${url}`));
351
- return null;
352
- }
353
- return {
354
- publishableKey: data.publishableKey,
355
- secretKey: data.secretKey,
356
- tenantName: data.tenantName,
357
- tenantId: data.tenantId
358
- };
359
- } catch (err) {
360
- console.error(
361
- pc.red(
362
- `Exchange request to ${url} failed: ${err instanceof Error ? err.message : String(err)}`
363
- )
364
- );
365
- return null;
366
- }
367
- }
368
- async function startBrowserAuth(options) {
369
- const state = randomBytes(32).toString("hex");
370
- const webUrl = options?.webUrl ?? DEFAULT_WEB_URL;
371
- return new Promise((resolve, reject) => {
372
- const server = createServer((req, res) => {
373
- if (!req.url) {
374
- res.writeHead(400).end();
375
- return;
376
- }
377
- const url = new URL(req.url, `http://localhost`);
378
- if (url.pathname !== "/callback" || req.method !== "GET") {
379
- res.writeHead(404).end();
380
- return;
381
- }
382
- const error = url.searchParams.get("error");
383
- if (error) {
384
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(ERROR_HTML(error));
385
- console.error(pc.red(`Login failed: ${error}`));
386
- cleanup(new Error(`Login failed: ${error}`));
387
- return;
388
- }
389
- const code = url.searchParams.get("code");
390
- const receivedState = url.searchParams.get("state");
391
- if (!code || !receivedState) {
392
- res.writeHead(400, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(ERROR_HTML("Missing code or state."));
393
- cleanup(new Error("Login failed: missing code or state."));
394
- return;
395
- }
396
- if (receivedState !== state) {
397
- res.writeHead(403, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(ERROR_HTML("State mismatch."));
398
- console.error(pc.red("Login failed: state mismatch."));
399
- cleanup(new Error("Login failed: state mismatch."));
400
- return;
401
- }
402
- exchangeCode(webUrl, code).then((creds) => {
403
- if (!creds) {
404
- res.writeHead(400, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(ERROR_HTML("Invalid or expired code."));
405
- cleanup(new Error("Login failed: code exchange failed."));
406
- return;
407
- }
408
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(SUCCESS_HTML);
409
- console.log(pc.green(`
410
- Logged in successfully!`));
411
- console.log(pc.dim(`Tenant: ${creds.tenantName}`));
412
- cleanup(null, creds);
413
- });
414
- });
415
- let timeout;
416
- let completed = false;
417
- function cleanup(err, result) {
418
- if (completed) return;
419
- completed = true;
420
- clearTimeout(timeout);
421
- server.closeAllConnections?.();
422
- server.close(() => {
423
- if (err) {
424
- reject(err);
425
- } else {
426
- resolve(result);
427
- }
428
- });
429
- }
430
- server.listen(0, "127.0.0.1", () => {
431
- const addr = server.address();
432
- if (!addr || typeof addr === "string") {
433
- reject(new Error("Failed to start local server."));
434
- return;
435
- }
436
- const port = addr.port;
437
- timeout = setTimeout(() => {
438
- console.error(pc.red("\nLogin timed out (5 minutes). Please try again."));
439
- cleanup(new Error("Login timed out"));
440
- }, TIMEOUT_MS);
441
- const params = new URLSearchParams({ port: String(port), state });
442
- if (options?.tenantId) {
443
- params.set("tenantId", options.tenantId);
444
- }
445
- const loginUrl = `${webUrl}/cli-auth?${params.toString()}`;
446
- console.log(pc.dim("Opening browser for login..."));
447
- console.log(pc.dim(`If the browser does not open, visit:
448
- ${loginUrl}`));
449
- openBrowser(loginUrl);
450
- });
451
- server.on("error", (err) => {
452
- reject(err);
453
- });
454
- });
455
- }
456
-
457
- // src/init.ts
458
- var SECRET_KEY_ENV_VAR = "SOFTWARE_SECRET_KEY";
459
- var API_KEY_PLACEHOLDER = "YOUR_API_KEY";
460
- async function init(cwd, info, answers) {
461
- const { packageManager, srcDir } = info;
462
- const env = answers.env;
463
- const baseDir = srcDir ? path2.join(cwd, "src") : cwd;
464
- const publishableKeyEnvVar = getPublishableKeyEnvVar(env);
465
- const wantsClient = needsClient(env);
466
- const wantsServer = needsServer(env);
467
- const wantsReactQuery = needsReactQuery(env);
468
- const plan = await planConflictsAndEnv(cwd, baseDir, env, answers);
469
- const installResult = installDeps(
470
- cwd,
471
- packageManager,
472
- info.hasSdk,
473
- info.hasReactQuery,
474
- wantsReactQuery
475
- );
476
- if (wantsClient || wantsReactQuery || wantsServer) {
477
- fs2.mkdirSync(path2.join(baseDir, "lib", "software"), { recursive: true });
478
- }
479
- const libDir = path2.join(baseDir, "lib", "software");
480
- if (wantsClient) {
481
- await writeFileWithPolicy(
482
- cwd,
483
- path2.join(libDir, "client.ts"),
484
- getClientTemplate(env, publishableKeyEnvVar),
485
- plan.policy
486
- );
487
- }
488
- if (wantsReactQuery) {
489
- await writeFileWithPolicy(
490
- cwd,
491
- path2.join(libDir, "query-provider.tsx"),
492
- getQueryProviderTemplate(env),
493
- plan.policy
494
- );
495
- }
496
- if (wantsServer) {
497
- await writeFileWithPolicy(
498
- cwd,
499
- path2.join(libDir, "server.ts"),
500
- getServerTemplate(env, publishableKeyEnvVar, SECRET_KEY_ENV_VAR),
501
- plan.policy
502
- );
503
- }
504
- if (plan.envFile && answers.authMethod !== "browser") {
505
- await writeEnv(
506
- cwd,
507
- plan.envFile,
508
- answers.publishableKey || "",
509
- answers.secretKey || "",
510
- publishableKeyEnvVar,
511
- wantsServer ? SECRET_KEY_ENV_VAR : null,
512
- plan.policy
513
- );
514
- }
515
- let publishableKey = answers.publishableKey;
516
- let secretKey = answers.secretKey;
517
- let tenantName = "";
518
- if (answers.authMethod === "browser" && answers.aiTools.length > 0) {
519
- try {
520
- console.log();
521
- const creds = await startBrowserAuth();
522
- publishableKey = creds.publishableKey;
523
- secretKey = creds.secretKey;
524
- tenantName = creds.tenantName;
525
- if (plan.envFile && publishableKey) {
526
- await writeEnv(
527
- cwd,
528
- plan.envFile,
529
- publishableKey,
530
- secretKey,
531
- publishableKeyEnvVar,
532
- wantsServer ? SECRET_KEY_ENV_VAR : null,
533
- "overwrite",
534
- true
535
- );
536
- }
537
- } catch (err) {
538
- console.log(
539
- pc2.yellow(" Browser auth skipped:"),
540
- err instanceof Error ? err.message : String(err)
541
- );
542
- }
543
- }
544
- if (answers.aiTools.length > 0) {
545
- const apiKey = secretKey || API_KEY_PLACEHOLDER;
546
- for (const tool of answers.aiTools) {
547
- await writeMcpConfig(tool, cwd, apiKey, plan.policy);
548
- }
549
- addToGitignore(cwd, answers.aiTools);
550
- if (answers.aiTools.includes("claude")) {
551
- await writeClaudeDocs(cwd, publishableKey, secretKey, tenantName, plan.policy);
552
- }
553
- }
554
- return installResult;
555
- }
556
- function installDeps(cwd, pm, hasSdk, hasReactQuery, wantsReactQuery) {
557
- const allDeps = [
558
- { name: "@01.software/sdk", installed: hasSdk, needed: true },
559
- { name: "@tanstack/react-query", installed: hasReactQuery, needed: wantsReactQuery }
560
- ];
561
- const fullList = allDeps.filter((d) => d.needed).map((d) => d.name);
562
- const toInstall = allDeps.filter((d) => d.needed && !d.installed).map((d) => d.name);
563
- const fullCmd = buildAddCmd(pm, hasPnpmWorkspace(cwd), fullList);
564
- if (toInstall.length === 0) {
565
- console.log(pc2.dim(` Dependencies already installed: ${fullList.join(", ")}`));
566
- return { installFailed: false, installSkipped: true, installCmd: fullCmd };
567
- }
568
- const addCmd = buildAddCmd(pm, hasPnpmWorkspace(cwd), toInstall);
569
- console.log(pc2.dim(` Installing ${toInstall.join(" and ")}...`));
570
- const wsPatched = pm === "pnpm" && patchPnpmWorkspace(cwd);
571
- let installFailed = false;
572
- try {
573
- execSync(addCmd, { cwd, stdio: "pipe" });
574
- console.log(pc2.green(" Installed"), toInstall.join(", "));
575
- } catch (error) {
576
- installFailed = true;
577
- const err = error;
578
- const msg = String(err.stderr || "").trim() || String(err.stdout || "").trim() || String(error);
579
- console.log(pc2.yellow(" Install failed \u2014 continuing with scaffolding"));
580
- const firstLines = msg.split("\n").slice(0, 3).map((l) => ` ${l}`).join("\n");
581
- if (firstLines) console.log(pc2.dim(firstLines));
582
- console.log(pc2.dim(` Run manually: ${addCmd}`));
583
- } finally {
584
- if (wsPatched) restorePnpmWorkspace(cwd);
585
- }
586
- return { installFailed, installSkipped: false, installCmd: addCmd };
587
- }
588
- function buildAddCmd(pm, hasPnpmWs, deps) {
589
- const pkgs = deps.join(" ");
590
- switch (pm) {
591
- case "pnpm":
592
- return hasPnpmWs ? `pnpm add -w ${pkgs}` : `pnpm add ${pkgs}`;
593
- case "yarn":
594
- return `yarn add ${pkgs}`;
595
- case "bun":
596
- return `bun add ${pkgs}`;
597
- default:
598
- return `npm install ${pkgs}`;
599
- }
600
- }
601
- async function planConflictsAndEnv(cwd, baseDir, env, answers) {
602
- const candidates = [];
603
- const libDir = path2.join(baseDir, "lib", "software");
604
- if (needsClient(env)) candidates.push(path2.join(libDir, "client.ts"));
605
- if (needsReactQuery(env)) candidates.push(path2.join(libDir, "query-provider.tsx"));
606
- if (needsServer(env)) candidates.push(path2.join(libDir, "server.ts"));
607
- if (answers.aiTools.includes("claude")) {
608
- for (const { dirName } of getSkillFiles()) {
609
- candidates.push(path2.join(cwd, ".claude", "skills", dirName, "SKILL.md"));
610
- }
611
- }
612
- const conflicts = candidates.filter((p) => fs2.existsSync(p));
613
- let policy = "skip";
614
- if (conflicts.length > 0) {
615
- console.log(pc2.yellow(` ${conflicts.length} file(s) already exist:`));
616
- for (const c of conflicts) console.log(pc2.dim(` ${path2.relative(cwd, c)}`));
617
- const { selected } = await prompts2({
618
- type: "select",
619
- name: "selected",
620
- message: "How should I handle existing files?",
621
- choices: [
622
- { title: "Keep existing (skip)", value: "skip" },
623
- { title: "Overwrite all", value: "overwrite" },
624
- { title: "Ask for each", value: "ask" }
625
- ],
626
- initial: 0
627
- });
628
- policy = selected ?? "skip";
629
- }
630
- const envFile = env === "vanilla" || env === "edge" ? "" : await pickEnvFile(cwd, env);
631
- return { policy, envFile };
632
- }
633
- async function pickEnvFile(cwd, env) {
634
- const candidates = [".env.local", ".env", ".env.development"];
635
- const existing = candidates.filter((f) => fs2.existsSync(path2.join(cwd, f)));
636
- const preferred = env === "nextjs" ? ".env.local" : ".env";
637
- if (existing.length === 0) return preferred;
638
- if (existing.length === 1 && existing[0] === preferred) return existing[0];
639
- const options = Array.from(/* @__PURE__ */ new Set([...existing, preferred]));
640
- const choices = options.map((f) => ({
641
- title: f,
642
- description: existing.includes(f) ? "exists" : "create",
643
- value: f
644
- }));
645
- const initial = Math.max(
646
- 0,
647
- choices.findIndex((c) => c.value === preferred)
648
- );
649
- const { file } = await prompts2({
650
- type: "select",
651
- name: "file",
652
- message: "Which env file should I write API keys to?",
653
- choices,
654
- initial
655
- });
656
- return file ?? preferred;
657
- }
658
- async function writeFileWithPolicy(cwd, filePath, content, policy) {
659
- const rel = path2.relative(cwd, filePath);
660
- if (!fs2.existsSync(filePath)) {
661
- fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
662
- fs2.writeFileSync(filePath, content);
663
- console.log(pc2.green(" Created"), rel);
664
- return;
665
- }
666
- const existing = fs2.readFileSync(filePath, "utf-8");
667
- if (existing === content) {
668
- console.log(pc2.dim(" Unchanged"), rel);
669
- return;
670
- }
671
- let shouldWrite = false;
672
- if (policy === "overwrite") {
673
- shouldWrite = true;
674
- } else if (policy === "ask") {
675
- const { confirm } = await prompts2({
676
- type: "confirm",
677
- name: "confirm",
678
- message: `Overwrite ${rel}?`,
679
- initial: false
680
- });
681
- shouldWrite = !!confirm;
682
- }
683
- if (shouldWrite) {
684
- fs2.writeFileSync(filePath, content);
685
- console.log(pc2.green(" Overwrote"), rel);
686
- } else {
687
- console.log(pc2.yellow(" Skipped"), rel, pc2.dim("(already exists)"));
688
- }
689
- }
690
- async function writeEnv(cwd, envFile, publishableKey, secretKey, publishableKeyEnvVar, secretKeyEnvVar, policy, fromBrowserAuth = false) {
691
- const envPath = path2.join(cwd, envFile);
692
- const targets = [
693
- { name: publishableKeyEnvVar, value: publishableKey }
694
- ];
695
- if (secretKeyEnvVar) {
696
- targets.push({ name: secretKeyEnvVar, value: secretKey });
697
- }
698
- if (!fs2.existsSync(envPath)) {
699
- const initial = getEnvContent(publishableKey, secretKey, publishableKeyEnvVar, secretKeyEnvVar);
700
- fs2.writeFileSync(envPath, initial.trimStart());
701
- console.log(pc2.green(" Created"), envFile);
702
- return;
703
- }
704
- let content = fs2.readFileSync(envPath, "utf-8");
705
- let modified = false;
706
- let appendedHeader = false;
707
- const headerAlreadyPresent = targets.some(
708
- (t) => readEnvValue(content, t.name) !== null
709
- );
710
- for (const { name, value } of targets) {
711
- const existing = readEnvValue(content, name);
712
- if (existing === null) {
713
- if (!headerAlreadyPresent && !appendedHeader) {
714
- if (content.length > 0 && !content.endsWith("\n")) content += "\n";
715
- content += "\n# 01.software\n";
716
- appendedHeader = true;
717
- }
718
- content = setEnvValue(content, name, value);
719
- modified = true;
720
- continue;
721
- }
722
- if (existing === value) continue;
723
- if (!value) continue;
724
- let shouldOverwrite = false;
725
- if (policy === "overwrite") {
726
- shouldOverwrite = true;
727
- } else if (policy === "ask") {
728
- const { confirm } = await prompts2({
729
- type: "confirm",
730
- name: "confirm",
731
- message: `${name} already set in ${envFile}. Overwrite?`,
732
- initial: fromBrowserAuth
733
- });
734
- shouldOverwrite = !!confirm;
735
- }
736
- if (shouldOverwrite) {
737
- content = setEnvValue(content, name, value);
738
- modified = true;
739
- }
740
- }
741
- if (modified) {
742
- fs2.writeFileSync(envPath, content);
743
- console.log(
744
- pc2.green(" Updated"),
745
- envFile,
746
- fromBrowserAuth ? pc2.dim("(API keys)") : ""
747
- );
748
- } else {
749
- console.log(pc2.dim(" Unchanged"), envFile);
750
- }
751
- }
752
- function resolveMcpLocation(tool, cwd) {
753
- const home = os.homedir();
754
- switch (tool) {
755
- case "claude":
756
- return {
757
- kind: "json",
758
- absolutePath: path2.join(cwd, ".mcp.json"),
759
- displayPath: ".mcp.json",
760
- gitignoreEntry: ".mcp.json"
761
- };
762
- case "cursor":
763
- return {
764
- kind: "json",
765
- absolutePath: path2.join(cwd, ".cursor", "mcp.json"),
766
- displayPath: ".cursor/mcp.json",
767
- gitignoreEntry: ".cursor/mcp.json"
768
- };
769
- case "vscode":
770
- return {
771
- kind: "json",
772
- absolutePath: path2.join(cwd, ".vscode", "mcp.json"),
773
- displayPath: ".vscode/mcp.json",
774
- gitignoreEntry: ".vscode/mcp.json"
775
- };
776
- case "windsurf": {
777
- if (!home) return null;
778
- const p = path2.join(home, ".codeium", "windsurf", "mcp_config.json");
779
- return { kind: "json", absolutePath: p, displayPath: p, gitignoreEntry: null };
780
- }
781
- case "codex": {
782
- if (!home) return null;
783
- const p = path2.join(home, ".codex", "config.toml");
784
- return { kind: "toml", absolutePath: p, displayPath: p, gitignoreEntry: null };
785
- }
786
- case "gemini": {
787
- if (!home) return null;
788
- const p = path2.join(home, ".gemini", "settings.json");
789
- return { kind: "json", absolutePath: p, displayPath: p, gitignoreEntry: null };
790
- }
791
- }
792
- }
793
- async function writeMcpConfig(tool, cwd, apiKey, policy) {
794
- const loc = resolveMcpLocation(tool, cwd);
795
- if (!loc) {
796
- console.log(pc2.yellow(` Skipped ${tool}`), pc2.dim("(HOME not set)"));
797
- return;
798
- }
799
- fs2.mkdirSync(path2.dirname(loc.absolutePath), { recursive: true });
800
- if (loc.kind === "json") {
801
- await writeJsonMcp(loc, apiKey, policy);
802
- } else {
803
- await writeTomlMcp(loc, apiKey, policy);
804
- }
805
- }
806
- async function writeJsonMcp(loc, apiKey, policy) {
807
- if (!fs2.existsSync(loc.absolutePath)) {
808
- fs2.writeFileSync(loc.absolutePath, getMcpConfigTemplate(apiKey));
809
- console.log(pc2.green(" Created"), loc.displayPath);
810
- return;
811
- }
812
- let existing;
813
- try {
814
- existing = JSON.parse(fs2.readFileSync(loc.absolutePath, "utf-8"));
815
- } catch {
816
- console.log(pc2.yellow(" Skipped"), loc.displayPath, pc2.dim("(could not parse existing file)"));
817
- return;
818
- }
819
- const existingApiKey = existing.mcpServers?.["01software"]?.headers?.["x-api-key"];
820
- if (existingApiKey === apiKey) {
821
- console.log(pc2.dim(" Unchanged"), loc.displayPath);
822
- return;
823
- }
824
- if (apiKey === API_KEY_PLACEHOLDER && existingApiKey && existingApiKey !== API_KEY_PLACEHOLDER) {
825
- console.log(pc2.dim(" Kept existing API key in"), loc.displayPath);
826
- return;
827
- }
828
- if (existingApiKey) {
829
- const shouldUpdate = await confirmKeyUpdate(loc.displayPath, policy);
830
- if (!shouldUpdate) {
831
- console.log(pc2.yellow(" Skipped"), loc.displayPath, pc2.dim("(kept existing API key)"));
832
- return;
833
- }
834
- }
835
- existing.mcpServers = existing.mcpServers || {};
836
- existing.mcpServers["01software"] = getMcpServerEntry(apiKey);
837
- fs2.writeFileSync(loc.absolutePath, JSON.stringify(existing, null, 2) + "\n");
838
- console.log(pc2.green(" Updated"), loc.displayPath);
839
- }
840
- async function writeTomlMcp(loc, apiKey, policy) {
841
- const section = getCodexMcpTomlSection(apiKey);
842
- if (!fs2.existsSync(loc.absolutePath)) {
843
- fs2.writeFileSync(loc.absolutePath, section.trimStart());
844
- console.log(pc2.green(" Created"), loc.displayPath);
845
- return;
846
- }
847
- const existing = fs2.readFileSync(loc.absolutePath, "utf-8");
848
- if (!existing.includes(CODEX_MCP_SECTION_MARKER)) {
849
- const sep = existing.endsWith("\n") ? "" : "\n";
850
- fs2.appendFileSync(loc.absolutePath, sep + section);
851
- console.log(pc2.green(" Updated"), loc.displayPath);
852
- return;
853
- }
854
- const existingApiKey = extractTomlApiKey(existing);
855
- if (existingApiKey === apiKey) {
856
- console.log(pc2.dim(" Unchanged"), loc.displayPath);
857
- return;
858
- }
859
- if (apiKey === API_KEY_PLACEHOLDER && existingApiKey && existingApiKey !== API_KEY_PLACEHOLDER) {
860
- console.log(pc2.dim(" Kept existing API key in"), loc.displayPath);
861
- return;
862
- }
863
- const shouldUpdate = await confirmKeyUpdate(loc.displayPath, policy);
864
- if (!shouldUpdate) {
865
- console.log(pc2.yellow(" Skipped"), loc.displayPath, pc2.dim("(kept existing API key)"));
866
- return;
867
- }
868
- const replaced = replaceTomlMcpSection(existing, section);
869
- fs2.writeFileSync(loc.absolutePath, replaced);
870
- console.log(pc2.green(" Updated"), loc.displayPath);
871
- }
872
- async function confirmKeyUpdate(displayPath, policy) {
873
- if (policy === "overwrite") return true;
874
- const { confirm } = await prompts2({
875
- type: "confirm",
876
- name: "confirm",
877
- message: `${displayPath} has a different 01software API key. Update?`,
878
- initial: true
879
- });
880
- return !!confirm;
881
- }
882
- function addToGitignore(cwd, tools) {
883
- const entries = [];
884
- for (const tool of tools) {
885
- const loc = resolveMcpLocation(tool, cwd);
886
- if (loc?.gitignoreEntry) entries.push(loc.gitignoreEntry);
887
- }
888
- if (entries.length === 0) return;
889
- const gitignorePath = path2.join(cwd, ".gitignore");
890
- const existing = fs2.existsSync(gitignorePath) ? fs2.readFileSync(gitignorePath, "utf-8") : "";
891
- const toAdd = entries.filter((e) => !existing.includes(e));
892
- if (toAdd.length === 0) return;
893
- const content = "\n# MCP configs (contain API key)\n" + toAdd.join("\n") + "\n";
894
- if (fs2.existsSync(gitignorePath)) {
895
- fs2.appendFileSync(gitignorePath, content);
896
- } else {
897
- fs2.writeFileSync(gitignorePath, content.trimStart());
898
- }
899
- console.log(pc2.green(" Updated"), ".gitignore", pc2.dim(`(added ${toAdd.join(", ")})`));
900
- }
901
- async function writeClaudeDocs(cwd, publishableKey, secretKey, tenantName, policy) {
902
- let ctx = {
903
- tenantName: tenantName || "Your Tenant",
904
- features: void 0,
905
- collections: void 0
906
- };
907
- if (publishableKey && secretKey) {
908
- const fetched = await fetchTenantContext(publishableKey, secretKey);
909
- if (fetched) {
910
- ctx = {
911
- tenantName: fetched.tenantName || ctx.tenantName,
912
- features: fetched.features,
913
- collections: fetched.collections
914
- };
915
- }
916
- }
917
- const claudeDir = path2.join(cwd, ".claude");
918
- const softwareDir = path2.join(claudeDir, "01software");
919
- const skillsDir = path2.join(claudeDir, "skills");
920
- fs2.mkdirSync(softwareDir, { recursive: true });
921
- fs2.mkdirSync(skillsDir, { recursive: true });
922
- const contextPath = path2.join(softwareDir, "context.md");
923
- const contextExists = fs2.existsSync(contextPath);
924
- fs2.writeFileSync(contextPath, generateClaudeMd(ctx));
925
- console.log(pc2.green(contextExists ? " Updated" : " Created"), ".claude/01software/context.md");
926
- const claudeMdPath = path2.join(claudeDir, "CLAUDE.md");
927
- const importLine = "@.claude/01software/context.md";
928
- if (!fs2.existsSync(claudeMdPath)) {
929
- fs2.writeFileSync(claudeMdPath, importLine + "\n");
930
- console.log(pc2.green(" Created"), ".claude/CLAUDE.md");
931
- } else {
932
- const existing = fs2.readFileSync(claudeMdPath, "utf-8");
933
- if (!existing.includes(importLine)) {
934
- const prefix = existing.endsWith("\n") ? "\n" : "\n\n";
935
- fs2.appendFileSync(claudeMdPath, prefix + importLine + "\n");
936
- console.log(pc2.green(" Updated"), ".claude/CLAUDE.md", pc2.dim("(added @import)"));
937
- } else {
938
- console.log(pc2.dim(" Unchanged"), ".claude/CLAUDE.md");
939
- }
940
- }
941
- for (const { dirName, content } of getSkillFiles()) {
942
- const skillDir = path2.join(skillsDir, dirName);
943
- const skillPath = path2.join(skillDir, "SKILL.md");
944
- fs2.mkdirSync(skillDir, { recursive: true });
945
- await writeFileWithPolicy(cwd, skillPath, content, policy);
946
- }
947
- }
948
- var WS_FILE = "pnpm-workspace.yaml";
949
- var WS_BACKUP = "pnpm-workspace.yaml.bak";
950
- function hasPnpmWorkspace(cwd) {
951
- return fs2.existsSync(path2.join(cwd, WS_FILE));
952
- }
953
- function patchPnpmWorkspace(cwd) {
954
- const wsPath = path2.join(cwd, WS_FILE);
955
- if (!fs2.existsSync(wsPath)) return false;
956
- const content = fs2.readFileSync(wsPath, "utf-8");
957
- if (content.includes("packages:")) return false;
958
- fs2.copyFileSync(wsPath, path2.join(cwd, WS_BACKUP));
959
- fs2.writeFileSync(wsPath, content.trimEnd() + "\npackages: []\n");
960
- return true;
961
- }
962
- function restorePnpmWorkspace(cwd) {
963
- const backupPath = path2.join(cwd, WS_BACKUP);
964
- if (!fs2.existsSync(backupPath)) return;
965
- fs2.copyFileSync(backupPath, path2.join(cwd, WS_FILE));
966
- fs2.unlinkSync(backupPath);
967
- }
968
-
969
174
  // src/index.ts
970
175
  var ENV_LABELS = {
971
176
  nextjs: "Next.js",
@@ -999,17 +204,17 @@ var OTHER_FRAMEWORK_GUIDE = `
999
204
  async function main() {
1000
205
  const cwd = process.cwd();
1001
206
  console.log();
1002
- console.log(pc3.bold(" @01.software/init"));
1003
- console.log(pc3.dim(" Initialize 01.software SDK in your project"));
207
+ console.log(pc.bold(" @01.software/init"));
208
+ console.log(pc.dim(" Initialize 01.software SDK in your project"));
1004
209
  console.log();
1005
210
  const info = detectProject(cwd);
1006
211
  if (!info.hasPackageJson || info.parseError) {
1007
212
  if (info.parseError) {
1008
- console.log(pc3.red(" Could not parse package.json (invalid JSON)."));
1009
- console.log(pc3.dim(" Fix the syntax error and try again."));
213
+ console.log(pc.red(" Could not parse package.json (invalid JSON)."));
214
+ console.log(pc.dim(" Fix the syntax error and try again."));
1010
215
  } else {
1011
- console.log(pc3.red(" No package.json found in the current directory."));
1012
- console.log(pc3.dim(" Run this command inside an existing project."));
216
+ console.log(pc.red(" No package.json found in the current directory."));
217
+ console.log(pc.dim(" Run this command inside an existing project."));
1013
218
  }
1014
219
  console.log();
1015
220
  process.exit(1);
@@ -1018,17 +223,17 @@ async function main() {
1018
223
  if (info.packageManager) detectedParts.push(info.packageManager);
1019
224
  if (info.srcDir) detectedParts.push("src/");
1020
225
  if (info.env !== "node") {
1021
- console.log(pc3.dim(` Detected: ${detectedParts.join(" / ")}`));
226
+ console.log(pc.dim(` Detected: ${detectedParts.join(" / ")}`));
1022
227
  console.log();
1023
228
  }
1024
229
  try {
1025
230
  const answers = await promptUser(info.hasSdk, info.env, info.packageManager);
1026
231
  if (!answers) {
1027
- console.log(pc3.yellow(" Cancelled."));
232
+ console.log(pc.yellow(" Cancelled."));
1028
233
  process.exit(0);
1029
234
  }
1030
235
  if (answers.env === "other") {
1031
- console.log(pc3.yellow(" Manual setup required for your framework:"));
236
+ console.log(pc.yellow(" Manual setup required for your framework:"));
1032
237
  console.log(OTHER_FRAMEWORK_GUIDE);
1033
238
  process.exit(0);
1034
239
  }
@@ -1039,70 +244,80 @@ async function main() {
1039
244
  const env = answers.env;
1040
245
  const run = resolvedPm === "npm" ? "npm run" : resolvedPm;
1041
246
  console.log();
1042
- console.log(pc3.green(" Done!"));
247
+ console.log(pc.green(" Done!"));
1043
248
  console.log();
1044
249
  console.log(" Next steps:");
1045
250
  console.log();
1046
251
  if (result.installFailed) {
1047
- console.log(pc3.yellow(" Install the SDK manually:"));
1048
- console.log(pc3.cyan(` ${result.installCmd}`));
252
+ console.log(pc.yellow(" Install the SDK manually:"));
253
+ console.log(pc.cyan(` ${result.installCmd}`));
1049
254
  console.log();
1050
255
  }
1051
256
  if (env === "nextjs") {
1052
- console.log(pc3.dim(" Add QueryProvider to your root layout:"));
257
+ console.log(pc.dim(" Add QueryProvider to your root layout:"));
258
+ console.log();
259
+ console.log(pc.cyan(" import { QueryProvider } from '@/lib/software/query-provider'"));
260
+ console.log(pc.cyan(" <QueryProvider>{children}</QueryProvider>"));
1053
261
  console.log();
1054
- console.log(pc3.cyan(" import { QueryProvider } from '@/lib/software/query-provider'"));
1055
- console.log(pc3.cyan(" <QueryProvider>{children}</QueryProvider>"));
262
+ console.log(pc.dim(" Optional: start browser analytics with the generated helper:"));
263
+ console.log();
264
+ console.log(pc.cyan(" import { analytics } from '@/lib/software/analytics'"));
265
+ console.log(pc.cyan(" analytics.track('signup')"));
1056
266
  console.log();
1057
267
  } else if (env === "react-vite" || env === "react-cra") {
1058
- console.log(pc3.dim(" Wrap your app entry with QueryProvider:"));
268
+ console.log(pc.dim(" Wrap your app entry with QueryProvider:"));
269
+ console.log();
270
+ console.log(pc.cyan(" import { QueryProvider } from './lib/software/query-provider'"));
271
+ console.log(pc.cyan(" <QueryProvider><App /></QueryProvider>"));
1059
272
  console.log();
1060
- console.log(pc3.cyan(" import { QueryProvider } from './lib/software/query-provider'"));
1061
- console.log(pc3.cyan(" <QueryProvider><App /></QueryProvider>"));
273
+ console.log(pc.dim(" Optional: start browser analytics with the generated helper:"));
274
+ console.log();
275
+ console.log(pc.cyan(" import { analytics } from './lib/software/analytics'"));
276
+ console.log(pc.cyan(" analytics.track('signup')"));
1062
277
  console.log();
1063
278
  } else if (env === "vanilla") {
1064
- console.log(pc3.dim(" Replace YOUR_PUBLISHABLE_KEY in lib/software/client.ts"));
279
+ console.log(pc.dim(" Replace YOUR_PUBLISHABLE_KEY in lib/software/client.ts"));
280
+ console.log();
281
+ console.log(pc.cyan(" import { client } from './lib/software/client'"));
282
+ console.log(pc.cyan(" const articles = await client.collections.from('articles').find()"));
1065
283
  console.log();
1066
- console.log(pc3.cyan(" import { client } from './lib/software/client'"));
1067
- console.log(pc3.cyan(" const posts = await client.from('posts').find()"));
284
+ console.log(pc.dim(" Optional: wire the analytics helper in lib/software/analytics.ts"));
1068
285
  console.log();
1069
286
  } else if (env === "node") {
1070
- console.log(pc3.dim(" Use the server client:"));
287
+ console.log(pc.dim(" Use the server client:"));
1071
288
  console.log();
1072
- console.log(pc3.cyan(" import { serverClient } from './lib/software/server'"));
1073
- console.log(pc3.cyan(" const posts = await serverClient.from('posts').find()"));
289
+ console.log(pc.cyan(" import { serverClient } from './lib/software/server'"));
290
+ console.log(pc.cyan(" const articles = await serverClient.collections.from('articles').find()"));
1074
291
  console.log();
1075
292
  } else if (env === "edge") {
1076
- console.log(pc3.dim(" Pass your env bindings to createEdgeClient():"));
293
+ console.log(pc.dim(" Pass your env bindings to createEdgeClient():"));
1077
294
  console.log();
1078
- console.log(pc3.cyan(" import { createEdgeClient } from './lib/software/server'"));
1079
- console.log(pc3.cyan(" const serverClient = createEdgeClient(env.PUBLISHABLE_KEY, env.SECRET_KEY)"));
295
+ console.log(pc.cyan(" import { createEdgeClient } from './lib/software/server'"));
296
+ console.log(pc.cyan(" const serverClient = createEdgeClient(env.PUBLISHABLE_KEY, env.SECRET_KEY)"));
1080
297
  console.log();
1081
298
  }
1082
- const missingPublishableKey = env !== "vanilla" && !answers.publishableKey;
1083
- const missingSecretKey = (env === "nextjs" || env === "node") && !answers.secretKey;
299
+ const effectivePublishableKey = result.publishableKey ?? answers.publishableKey;
300
+ const effectiveSecretKey = result.secretKey ?? answers.secretKey;
301
+ const missingPublishableKey = env !== "vanilla" && !effectivePublishableKey;
302
+ const missingSecretKey = (env === "nextjs" || env === "node") && !effectiveSecretKey;
1084
303
  if (missingPublishableKey || missingSecretKey) {
1085
- console.log(pc3.dim(" Update .env with your API keys"));
304
+ console.log(pc.dim(" Update .env with your SDK credentials"));
1086
305
  console.log();
1087
306
  }
1088
- if (answers.aiTools.length > 0 && (!answers.publishableKey || !answers.secretKey)) {
1089
- console.log(
1090
- pc3.dim(
1091
- " Update MCP config x-api-key with your sk01_... or pat01_... bearer token"
1092
- )
1093
- );
307
+ if (answers.aiTools.length > 0) {
308
+ console.log(pc.dim(" MCP config uses OAuth discovery."));
1094
309
  console.log();
1095
310
  }
1096
311
  if (env !== "vanilla") {
1097
- console.log(pc3.cyan(` ${run} dev`));
312
+ console.log(pc.cyan(` ${run} dev`));
1098
313
  console.log();
1099
314
  }
1100
315
  } catch (error) {
1101
316
  if (error instanceof Error && error.message === "cancelled") {
1102
- console.log(pc3.yellow(" Cancelled."));
317
+ console.log(pc.yellow(" Cancelled."));
1103
318
  process.exit(0);
1104
319
  }
1105
- console.error(pc3.red(" Error:"), error);
320
+ console.error(pc.red(" Error:"), error);
1106
321
  process.exit(1);
1107
322
  }
1108
323
  }