@01.software/init 0.3.0 → 0.4.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,4 +1,19 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ fetchTenantContext,
4
+ generateClaudeMd,
5
+ getSkillFiles
6
+ } from "./chunk-SRLZ5OIV.js";
7
+ import {
8
+ CODEX_MCP_SECTION_MARKER,
9
+ getClientTemplate,
10
+ getCodexMcpTomlSection,
11
+ getEnvContent,
12
+ getMcpConfigTemplate,
13
+ getMcpServerEntry,
14
+ getQueryProviderTemplate,
15
+ getServerTemplate
16
+ } from "./chunk-OEAQV63E.js";
2
17
 
3
18
  // src/index.ts
4
19
  import pc3 from "picocolors";
@@ -11,12 +26,13 @@ function detectProject(cwd) {
11
26
  const hasPackageJson = fs.existsSync(pkgPath);
12
27
  let env = "node";
13
28
  let hasSdk = false;
29
+ let parseError = false;
14
30
  if (hasPackageJson) {
15
31
  let pkg;
16
32
  try {
17
33
  pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
18
34
  } catch {
19
- return { hasPackageJson: false, parseError: true, env: "node", packageManager: null, hasSdk: false, srcDir: false };
35
+ return { hasPackageJson: true, parseError: true, env: "node", packageManager: null, hasSdk: false, srcDir: false };
20
36
  }
21
37
  const deps = {
22
38
  ...pkg.dependencies || {},
@@ -52,7 +68,7 @@ function detectProject(cwd) {
52
68
  packageManager = "npm";
53
69
  }
54
70
  const srcDir = env === "nextjs" ? fs.existsSync(path.join(cwd, "src", "app")) : fs.existsSync(path.join(cwd, "src"));
55
- return { hasPackageJson, env, packageManager, hasSdk, srcDir };
71
+ return { hasPackageJson, parseError, env, packageManager, hasSdk, srcDir };
56
72
  }
57
73
  function needsClient(env) {
58
74
  return env === "nextjs" || env === "react-vite" || env === "react-cra" || env === "vanilla";
@@ -171,7 +187,7 @@ async function promptUser(hasSdk, detectedEnv, detectedPm) {
171
187
  keyPrompts.push({
172
188
  type: "text",
173
189
  name: "publishableKey",
174
- message: "Client Key (optional, saved to .env)",
190
+ message: "Publishable Key (optional, saved to .env)",
175
191
  initial: ""
176
192
  });
177
193
  }
@@ -187,19 +203,20 @@ async function promptUser(hasSdk, detectedEnv, detectedPm) {
187
203
  {
188
204
  type: "multiselect",
189
205
  name: "selectedTools",
190
- message: "Connect AI tools:",
206
+ message: "Connect AI tools (leave empty to skip):",
191
207
  choices: [
192
208
  { title: "Claude Code", description: ".mcp.json + .claude/ docs", value: "claude" },
193
209
  { title: "Cursor", description: ".cursor/mcp.json", value: "cursor" },
194
210
  { title: "VS Code", description: ".vscode/mcp.json", value: "vscode" },
195
211
  { title: "Windsurf", description: "~/.codeium/windsurf/mcp_config.json", value: "windsurf" },
196
- { title: "Skip", value: "skip" }
212
+ { title: "Codex CLI", description: "~/.codex/config.toml", value: "codex" },
213
+ { title: "Gemini CLI", description: "~/.gemini/settings.json", value: "gemini" }
197
214
  ],
198
- hint: "space to select"
215
+ hint: "space to select, enter to confirm"
199
216
  },
200
217
  { onCancel }
201
218
  );
202
- const aiTools = Array.isArray(selectedTools) ? selectedTools.filter((t) => t !== "skip") : [];
219
+ const aiTools = Array.isArray(selectedTools) ? selectedTools : [];
203
220
  let authMethod = "skip";
204
221
  let keys = {};
205
222
  if (aiTools.length > 0 && env !== "vanilla") {
@@ -239,103 +256,10 @@ async function promptUser(hasSdk, detectedEnv, detectedPm) {
239
256
  // src/init.ts
240
257
  import fs2 from "fs";
241
258
  import path2 from "path";
259
+ import os from "os";
242
260
  import { execSync } from "child_process";
243
261
  import pc2 from "picocolors";
244
262
 
245
- // src/templates.ts
246
- function getClientTemplate(env, publishableKeyEnvVar) {
247
- if (env === "nextjs") {
248
- return `import { createClient } from '@01.software/sdk'
249
-
250
- export const client = createClient({
251
- publishableKey: process.env.${publishableKeyEnvVar}!,
252
- })
253
- `;
254
- }
255
- if (env === "react-cra") {
256
- return `import { createClient } from '@01.software/sdk'
257
-
258
- export const client = createClient({
259
- publishableKey: process.env.${publishableKeyEnvVar}!,
260
- })
261
- `;
262
- }
263
- if (env === "vanilla") {
264
- return `import { createClient } from '@01.software/sdk'
265
-
266
- // Replace 'YOUR_PUBLISHABLE_KEY' with your actual publishable key from the 01.software console
267
- export const client = createClient({
268
- publishableKey: 'YOUR_PUBLISHABLE_KEY',
269
- })
270
- `;
271
- }
272
- return `import { createClient } from '@01.software/sdk'
273
-
274
- export const client = createClient({
275
- publishableKey: import.meta.env.${publishableKeyEnvVar},
276
- })
277
- `;
278
- }
279
- function getQueryProviderTemplate(env) {
280
- const useClientDirective = env === "nextjs" ? "'use client'\n\n" : "";
281
- return `${useClientDirective}import { QueryClientProvider } from '@tanstack/react-query'
282
- import { client } from './client'
283
-
284
- export function QueryProvider({ children }: { children: React.ReactNode }) {
285
- return (
286
- <QueryClientProvider client={client.queryClient}>
287
- {children}
288
- </QueryClientProvider>
289
- )
290
- }
291
- `;
292
- }
293
- function getServerTemplate(env, publishableKeyEnvVar, secretKeyEnvVar) {
294
- if (env === "edge") {
295
- return `import { createServerClient } from '@01.software/sdk'
296
-
297
- // Edge runtime: pass your env bindings here
298
- // e.g. Cloudflare Workers: use env.SOFTWARE_PUBLISHABLE_KEY from the handler context
299
- // e.g. Vercel Edge: use process.env.${publishableKeyEnvVar}
300
- export function createEdgeClient(publishableKey: string, secretKey: string) {
301
- return createServerClient({ publishableKey, secretKey })
302
- }
303
- `;
304
- }
305
- return `import { createServerClient } from '@01.software/sdk'
306
-
307
- export const serverClient = createServerClient({
308
- publishableKey: process.env.${publishableKeyEnvVar}!,
309
- secretKey: process.env.${secretKeyEnvVar}!,
310
- })
311
- `;
312
- }
313
- function getEnvContent(publishableKey, secretKey, publishableKeyEnvVar, secretKeyEnvVar) {
314
- let content = `
315
- # 01.software
316
- ${publishableKeyEnvVar}=${publishableKey}
317
- `;
318
- if (secretKeyEnvVar) {
319
- content += `${secretKeyEnvVar}=${secretKey}
320
- `;
321
- }
322
- return content;
323
- }
324
- function getMcpServerEntry(apiKey) {
325
- return {
326
- type: "http",
327
- url: "https://mcp.01.software/mcp",
328
- headers: { "x-api-key": apiKey }
329
- };
330
- }
331
- function getMcpConfigTemplate(apiKey) {
332
- return JSON.stringify(
333
- { mcpServers: { "01software": getMcpServerEntry(apiKey) } },
334
- null,
335
- 2
336
- ) + "\n";
337
- }
338
-
339
263
  // src/browser-auth.ts
340
264
  import { randomBytes } from "crypto";
341
265
  import { createServer } from "http";
@@ -349,7 +273,7 @@ function escapeHtml(s) {
349
273
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
350
274
  }
351
275
  function openBrowser(url) {
352
- const os = platform();
276
+ const os2 = platform();
353
277
  const onError = () => {
354
278
  console.log(
355
279
  pc.yellow(
@@ -358,12 +282,12 @@ ${url}`
358
282
  )
359
283
  );
360
284
  };
361
- if (os === "win32") {
285
+ if (os2 === "win32") {
362
286
  exec(`start "" "${url}"`, (err) => {
363
287
  if (err) onError();
364
288
  });
365
289
  } else {
366
- const cmd = os === "darwin" ? "open" : "xdg-open";
290
+ const cmd = os2 === "darwin" ? "open" : "xdg-open";
367
291
  execFile(cmd, [url], (err) => {
368
292
  if (err) onError();
369
293
  });
@@ -395,6 +319,7 @@ async function startBrowserAuth(options) {
395
319
  "Access-Control-Allow-Origin": webUrl,
396
320
  "Access-Control-Allow-Methods": "POST, OPTIONS",
397
321
  "Access-Control-Allow-Headers": "Content-Type",
322
+ "Access-Control-Allow-Private-Network": "true",
398
323
  "Access-Control-Max-Age": "600",
399
324
  Vary: "Origin"
400
325
  };
@@ -518,156 +443,6 @@ ${loginUrl}`));
518
443
  });
519
444
  }
520
445
 
521
- // src/ai-docs.ts
522
- function generateClaudeMd(ctx) {
523
- const featuresSection = ctx.features && ctx.features.length > 0 ? ctx.features.map((f) => `- ${f}`).join("\n") : "- See console";
524
- const collectionsSection = ctx.collections && ctx.collections.length > 0 ? ctx.collections.join(", ") : "Run `01 schema list`";
525
- return `# 01.software SDK \u2014 ${ctx.tenantName}
526
-
527
- ## Connection
528
- - Publishable Key: \`NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY\` (env)
529
- - Secret Key: \`SOFTWARE_SECRET_KEY\` (env)
530
- - MCP: \`.mcp.json\`
531
-
532
- ## Active Features
533
- ${featuresSection}
534
-
535
- ## Collections
536
- ${collectionsSection}
537
-
538
- ## MCP Quick Reference
539
- | Tool | Use |
540
- |------|-----|
541
- | \`query-collection\` | List/filter documents |
542
- | \`create-collection\` | Create documents |
543
- | \`update-field-config\` | Hide unused fields |
544
- | \`get-tenant-context\` | Show active features & collections |
545
-
546
- ## CLI
547
- - \`01 query <collection>\` \u2014 query data
548
- - \`01 schema show <collection>\` \u2014 inspect fields
549
- - \`01 schema list\` \u2014 list all collections
550
-
551
- ## Initial Setup
552
- Run \`/01software-field-config\` in Claude Code to configure field visibility for your use case.
553
- `;
554
- }
555
- function getSkillFiles() {
556
- return [
557
- {
558
- dirName: "01software-field-config",
559
- content: `---
560
- name: 01software-field-config
561
- description: Configure field visibility for this tenant \u2014 hide unused collections and fields via MCP
562
- disable-model-invocation: true
563
- ---
564
-
565
- Steps:
566
- 1. Use \`list-configurable-fields\` to see current visibility settings
567
- 2. Identify fields/collections not needed for your use case
568
- 3. Use \`update-field-config\` to hide them
569
-
570
- Common setups:
571
- - Blog only: hide \`ecommerce\`, \`customers\`, \`videos\` collections
572
- - Store: hide \`posts\`, \`documents\`, \`galleries\`, \`canvas\` collections
573
- - Minimal: hide all except the collections you actively use
574
-
575
- Ask me: "Show current field config" or "Hide ecommerce fields"
576
- `
577
- },
578
- {
579
- dirName: "01software-query",
580
- content: `---
581
- name: 01software-query
582
- description: Query 01.software collections via MCP or CLI with filter, sort, and pagination examples
583
- ---
584
-
585
- Query collections using the MCP \`query-collection\` tool or CLI.
586
-
587
- MCP examples:
588
- - List products: \`query-collection\` with collection="products", limit=10
589
- - Filter by status: add where={"status":{"equals":"published"}}
590
- - Sort by date: sort="-createdAt"
591
- - Paginate: page=2, limit=20
592
-
593
- CLI examples:
594
- - \`01 query products --limit 10\`
595
- - \`01 query orders --where '{"status":{"equals":"paid"}}'\`
596
- - \`01 schema show products\` \u2014 inspect available fields
597
-
598
- SDK (server):
599
- \`\`\`typescript
600
- const { docs } = await serverClient.collection('products').find({
601
- where: { status: { equals: 'published' } },
602
- sort: '-createdAt',
603
- limit: 10,
604
- })
605
- \`\`\`
606
- `
607
- },
608
- {
609
- dirName: "01software-order-flow",
610
- content: `---
611
- name: 01software-order-flow
612
- description: Order lifecycle reference \u2014 create, pay, fulfill, and return flows for 01.software
613
- ---
614
-
615
- Complete order flow from creation to fulfillment.
616
-
617
- States: pending \u2192 paid \u2192 preparing \u2192 shipped \u2192 delivered \u2192 confirmed
618
-
619
- 1. Create order: \`create-order\` with orderNumber, customerSnapshot, orderProducts, totalAmount
620
- 2. Mark paid: \`update-order\` with status="paid" (after payment gateway confirms)
621
- 3. Fulfill: \`create-fulfillment\` with items and carrier/trackingNumber
622
- 4. Returns: \`create-return\` or \`return-with-refund\` (atomic)
623
-
624
- Free orders: omit paymentId, totalAmount=0 \u2192 auto-transitions to paid
625
-
626
- CLI: \`01 order create --help\` for full options
627
- `
628
- },
629
- {
630
- dirName: "01software-schema",
631
- content: `---
632
- name: 01software-schema
633
- description: Inspect 01.software collection schemas and available fields via MCP or CLI
634
- ---
635
-
636
- Inspect collection schemas to understand available fields.
637
-
638
- MCP: use \`get-collection-fields\` with collectionSlug
639
-
640
- CLI:
641
- - \`01 schema list\` \u2014 all available collections
642
- - \`01 schema show <collection>\` \u2014 field names, types, required status
643
-
644
- Common collections: products, orders, customers, posts, documents, images
645
- Use \`get-tenant-context\` to see which collections are active for this tenant.
646
- `
647
- }
648
- ];
649
- }
650
- async function fetchTenantContext(publishableKey, secretKey) {
651
- try {
652
- const apiUrl = process.env.SOFTWARE_API_URL || "https://api.01.software";
653
- const res = await fetch(`${apiUrl}/api/tenants/context`, {
654
- headers: {
655
- "X-Publishable-Key": publishableKey,
656
- Authorization: `Bearer ${secretKey}`
657
- }
658
- });
659
- if (!res.ok) return null;
660
- const data = await res.json();
661
- return {
662
- tenantName: data.tenant?.name || "",
663
- features: data.features || [],
664
- collections: data.collections || []
665
- };
666
- } catch {
667
- return null;
668
- }
669
- }
670
-
671
446
  // src/init.ts
672
447
  var SECRET_KEY_ENV_VAR = "SOFTWARE_SECRET_KEY";
673
448
  async function init(cwd, info, answers) {
@@ -680,71 +455,55 @@ async function init(cwd, info, answers) {
680
455
  const wantsReactQuery = needsReactQuery(env);
681
456
  const deps = ["@01.software/sdk"];
682
457
  if (wantsReactQuery) deps.push("@tanstack/react-query");
458
+ const addCmd = buildAddCmd(packageManager, hasPnpmWorkspace(cwd), deps);
459
+ let installFailed = false;
683
460
  console.log(pc2.dim(` Installing ${deps.join(" and ")}...`));
684
461
  const wsPatched = packageManager === "pnpm" && patchPnpmWorkspace(cwd);
685
- const pkgs = deps.join(" ");
686
- const pnpmFlag = hasPnpmWorkspace(cwd) ? " -w" : "";
687
- const addCmd = packageManager === "pnpm" ? `pnpm add${pnpmFlag} ${pkgs}` : packageManager === "yarn" ? `yarn add ${pkgs}` : packageManager === "bun" ? `bun add ${pkgs}` : `npm install ${pkgs}`;
688
462
  try {
689
463
  execSync(addCmd, { cwd, stdio: "pipe" });
464
+ console.log(pc2.green(" Installed"), deps.join(", "));
690
465
  } catch (error) {
466
+ installFailed = true;
691
467
  const err = error;
692
468
  const msg = String(err.stderr || "").trim() || String(err.stdout || "").trim() || String(error);
693
- console.log(pc2.red(" Failed to install dependencies:"));
694
- console.log(pc2.dim(` ${msg}`));
695
- throw error;
469
+ console.log(pc2.yellow(" Install failed \u2014 continuing with scaffolding"));
470
+ const firstLines = msg.split("\n").slice(0, 3).map((l) => ` ${l}`).join("\n");
471
+ if (firstLines) console.log(pc2.dim(firstLines));
472
+ console.log(pc2.dim(` Run manually: ${addCmd}`));
696
473
  } finally {
697
474
  if (wsPatched) restorePnpmWorkspace(cwd);
698
475
  }
699
476
  const libDir = path2.join(baseDir, "lib", "software");
700
477
  fs2.mkdirSync(libDir, { recursive: true });
701
478
  if (wantsClient) {
702
- const clientPath = path2.join(libDir, "client.ts");
703
- if (fs2.existsSync(clientPath)) {
704
- console.log(pc2.yellow(" Skipped"), relativePath(cwd, clientPath), pc2.dim("(already exists)"));
705
- } else {
706
- fs2.writeFileSync(clientPath, getClientTemplate(env, publishableKeyEnvVar));
707
- console.log(pc2.green(" Created"), relativePath(cwd, clientPath));
708
- }
479
+ writeFileIfAbsent(
480
+ cwd,
481
+ path2.join(libDir, "client.ts"),
482
+ getClientTemplate(env, publishableKeyEnvVar)
483
+ );
709
484
  }
710
485
  if (wantsReactQuery) {
711
- const queryProviderPath = path2.join(libDir, "query-provider.tsx");
712
- if (fs2.existsSync(queryProviderPath)) {
713
- console.log(pc2.yellow(" Skipped"), relativePath(cwd, queryProviderPath), pc2.dim("(already exists)"));
714
- } else {
715
- fs2.writeFileSync(queryProviderPath, getQueryProviderTemplate(env));
716
- console.log(pc2.green(" Created"), relativePath(cwd, queryProviderPath));
717
- }
486
+ writeFileIfAbsent(
487
+ cwd,
488
+ path2.join(libDir, "query-provider.tsx"),
489
+ getQueryProviderTemplate(env)
490
+ );
718
491
  }
719
492
  if (wantsServer) {
720
- const serverPath = path2.join(libDir, "server.ts");
721
- if (fs2.existsSync(serverPath)) {
722
- console.log(pc2.yellow(" Skipped"), relativePath(cwd, serverPath), pc2.dim("(already exists)"));
723
- } else {
724
- fs2.writeFileSync(serverPath, getServerTemplate(env, publishableKeyEnvVar, SECRET_KEY_ENV_VAR));
725
- console.log(pc2.green(" Created"), relativePath(cwd, serverPath));
726
- }
493
+ writeFileIfAbsent(
494
+ cwd,
495
+ path2.join(libDir, "server.ts"),
496
+ getServerTemplate(env, publishableKeyEnvVar, SECRET_KEY_ENV_VAR)
497
+ );
727
498
  }
728
499
  if (env !== "vanilla" && env !== "edge" && answers.authMethod !== "browser") {
729
- const envPath = path2.join(cwd, ".env");
730
- const envContent = getEnvContent(
500
+ writeEnv(
501
+ cwd,
731
502
  answers.publishableKey || "",
732
503
  answers.secretKey || "",
733
504
  publishableKeyEnvVar,
734
505
  wantsServer ? SECRET_KEY_ENV_VAR : null
735
506
  );
736
- if (fs2.existsSync(envPath)) {
737
- const existing = fs2.readFileSync(envPath, "utf-8");
738
- if (existing.includes(publishableKeyEnvVar)) {
739
- console.log(pc2.yellow(" Skipped"), ".env", pc2.dim("(keys already present)"));
740
- } else {
741
- fs2.appendFileSync(envPath, envContent);
742
- console.log(pc2.green(" Updated"), ".env");
743
- }
744
- } else {
745
- fs2.writeFileSync(envPath, envContent.trimStart());
746
- console.log(pc2.green(" Created"), ".env");
747
- }
748
507
  }
749
508
  let publishableKey = answers.publishableKey;
750
509
  let secretKey = answers.secretKey;
@@ -757,23 +516,14 @@ async function init(cwd, info, answers) {
757
516
  secretKey = creds.secretKey;
758
517
  tenantName = creds.tenantName;
759
518
  if (env !== "vanilla" && env !== "edge" && publishableKey) {
760
- const envPath = path2.join(cwd, ".env");
761
- const envContent = getEnvContent(
519
+ writeEnv(
520
+ cwd,
762
521
  publishableKey,
763
522
  secretKey,
764
523
  publishableKeyEnvVar,
765
- wantsServer ? SECRET_KEY_ENV_VAR : null
524
+ wantsServer ? SECRET_KEY_ENV_VAR : null,
525
+ true
766
526
  );
767
- if (fs2.existsSync(envPath)) {
768
- const existing = fs2.readFileSync(envPath, "utf-8");
769
- if (!existing.includes(publishableKeyEnvVar)) {
770
- fs2.appendFileSync(envPath, envContent);
771
- console.log(pc2.green(" Updated"), ".env", pc2.dim("(added API keys)"));
772
- }
773
- } else {
774
- fs2.writeFileSync(envPath, envContent.trimStart());
775
- console.log(pc2.green(" Created"), ".env");
776
- }
777
527
  }
778
528
  } catch (err) {
779
529
  console.log(
@@ -792,63 +542,147 @@ async function init(cwd, info, answers) {
792
542
  await writeClaudeDocs(cwd, publishableKey, secretKey, tenantName);
793
543
  }
794
544
  }
545
+ return { installFailed, installCmd: addCmd };
795
546
  }
796
- function writeMcpConfig(tool, cwd, apiKey) {
797
- let configPath;
798
- let displayPath;
547
+ function buildAddCmd(pm, hasPnpmWs, deps) {
548
+ const pkgs = deps.join(" ");
549
+ switch (pm) {
550
+ case "pnpm":
551
+ return hasPnpmWs ? `pnpm add -w ${pkgs}` : `pnpm add ${pkgs}`;
552
+ case "yarn":
553
+ return `yarn add ${pkgs}`;
554
+ case "bun":
555
+ return `bun add ${pkgs}`;
556
+ default:
557
+ return `npm install ${pkgs}`;
558
+ }
559
+ }
560
+ function writeFileIfAbsent(cwd, filePath, content) {
561
+ if (fs2.existsSync(filePath)) {
562
+ console.log(pc2.yellow(" Skipped"), relativePath(cwd, filePath), pc2.dim("(already exists)"));
563
+ return;
564
+ }
565
+ fs2.writeFileSync(filePath, content);
566
+ console.log(pc2.green(" Created"), relativePath(cwd, filePath));
567
+ }
568
+ function writeEnv(cwd, publishableKey, secretKey, publishableKeyEnvVar, secretKeyEnvVar, afterBrowserAuth = false) {
569
+ const envPath = path2.join(cwd, ".env");
570
+ const envContent = getEnvContent(publishableKey, secretKey, publishableKeyEnvVar, secretKeyEnvVar);
571
+ if (fs2.existsSync(envPath)) {
572
+ const existing = fs2.readFileSync(envPath, "utf-8");
573
+ if (existing.includes(publishableKeyEnvVar)) {
574
+ if (!afterBrowserAuth) {
575
+ console.log(pc2.yellow(" Skipped"), ".env", pc2.dim("(keys already present)"));
576
+ }
577
+ return;
578
+ }
579
+ fs2.appendFileSync(envPath, envContent);
580
+ console.log(
581
+ pc2.green(" Updated"),
582
+ ".env",
583
+ afterBrowserAuth ? pc2.dim("(added API keys)") : ""
584
+ );
585
+ } else {
586
+ fs2.writeFileSync(envPath, envContent.trimStart());
587
+ console.log(pc2.green(" Created"), ".env");
588
+ }
589
+ }
590
+ function resolveMcpLocation(tool, cwd) {
591
+ const home = os.homedir();
799
592
  switch (tool) {
800
593
  case "claude":
801
- configPath = path2.join(cwd, ".mcp.json");
802
- displayPath = ".mcp.json";
803
- break;
594
+ return {
595
+ kind: "json",
596
+ absolutePath: path2.join(cwd, ".mcp.json"),
597
+ displayPath: ".mcp.json",
598
+ gitignoreEntry: ".mcp.json"
599
+ };
804
600
  case "cursor":
805
- configPath = path2.join(cwd, ".cursor", "mcp.json");
806
- displayPath = ".cursor/mcp.json";
807
- fs2.mkdirSync(path2.dirname(configPath), { recursive: true });
808
- break;
601
+ return {
602
+ kind: "json",
603
+ absolutePath: path2.join(cwd, ".cursor", "mcp.json"),
604
+ displayPath: ".cursor/mcp.json",
605
+ gitignoreEntry: ".cursor/mcp.json"
606
+ };
809
607
  case "vscode":
810
- configPath = path2.join(cwd, ".vscode", "mcp.json");
811
- displayPath = ".vscode/mcp.json";
812
- fs2.mkdirSync(path2.dirname(configPath), { recursive: true });
813
- break;
608
+ return {
609
+ kind: "json",
610
+ absolutePath: path2.join(cwd, ".vscode", "mcp.json"),
611
+ displayPath: ".vscode/mcp.json",
612
+ gitignoreEntry: ".vscode/mcp.json"
613
+ };
814
614
  case "windsurf": {
815
- const home = process.env.HOME || process.env.USERPROFILE || "";
816
- if (!home) {
817
- console.log(pc2.yellow(" Skipped windsurf"), pc2.dim("(HOME not set)"));
818
- return;
819
- }
820
- configPath = path2.join(home, ".codeium", "windsurf", "mcp_config.json");
821
- displayPath = configPath;
822
- fs2.mkdirSync(path2.dirname(configPath), { recursive: true });
823
- break;
615
+ if (!home) return null;
616
+ const p = path2.join(home, ".codeium", "windsurf", "mcp_config.json");
617
+ return { kind: "json", absolutePath: p, displayPath: p, gitignoreEntry: null };
824
618
  }
825
- default:
826
- return;
619
+ case "codex": {
620
+ if (!home) return null;
621
+ const p = path2.join(home, ".codex", "config.toml");
622
+ return { kind: "toml", absolutePath: p, displayPath: p, gitignoreEntry: null };
623
+ }
624
+ case "gemini": {
625
+ if (!home) return null;
626
+ const p = path2.join(home, ".gemini", "settings.json");
627
+ return { kind: "json", absolutePath: p, displayPath: p, gitignoreEntry: null };
628
+ }
629
+ }
630
+ }
631
+ function writeMcpConfig(tool, cwd, apiKey) {
632
+ const loc = resolveMcpLocation(tool, cwd);
633
+ if (!loc) {
634
+ console.log(pc2.yellow(` Skipped ${tool}`), pc2.dim("(HOME not set)"));
635
+ return;
636
+ }
637
+ fs2.mkdirSync(path2.dirname(loc.absolutePath), { recursive: true });
638
+ if (loc.kind === "json") {
639
+ writeJsonMcp(loc, apiKey);
640
+ } else {
641
+ writeTomlMcp(loc, apiKey);
827
642
  }
828
- if (fs2.existsSync(configPath)) {
643
+ }
644
+ function writeJsonMcp(loc, apiKey) {
645
+ if (fs2.existsSync(loc.absolutePath)) {
829
646
  try {
830
- const existing = JSON.parse(fs2.readFileSync(configPath, "utf-8"));
647
+ const existing = JSON.parse(fs2.readFileSync(loc.absolutePath, "utf-8"));
831
648
  if (existing.mcpServers?.["01software"]) {
832
- console.log(pc2.yellow(" Skipped"), displayPath, pc2.dim("(01software already configured)"));
649
+ console.log(pc2.yellow(" Skipped"), loc.displayPath, pc2.dim("(01software already configured)"));
833
650
  return;
834
651
  }
835
652
  existing.mcpServers = existing.mcpServers || {};
836
653
  existing.mcpServers["01software"] = getMcpServerEntry(apiKey);
837
- fs2.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
838
- console.log(pc2.green(" Updated"), displayPath);
654
+ fs2.writeFileSync(loc.absolutePath, JSON.stringify(existing, null, 2) + "\n");
655
+ console.log(pc2.green(" Updated"), loc.displayPath);
839
656
  } catch {
840
- console.log(pc2.yellow(" Skipped"), displayPath, pc2.dim("(could not parse existing file)"));
657
+ console.log(pc2.yellow(" Skipped"), loc.displayPath, pc2.dim("(could not parse existing file)"));
841
658
  }
842
659
  } else {
843
- fs2.writeFileSync(configPath, getMcpConfigTemplate(apiKey));
844
- console.log(pc2.green(" Created"), displayPath);
660
+ fs2.writeFileSync(loc.absolutePath, getMcpConfigTemplate(apiKey));
661
+ console.log(pc2.green(" Created"), loc.displayPath);
662
+ }
663
+ }
664
+ function writeTomlMcp(loc, apiKey) {
665
+ const section = getCodexMcpTomlSection(apiKey);
666
+ if (fs2.existsSync(loc.absolutePath)) {
667
+ const existing = fs2.readFileSync(loc.absolutePath, "utf-8");
668
+ if (existing.includes(CODEX_MCP_SECTION_MARKER)) {
669
+ console.log(pc2.yellow(" Skipped"), loc.displayPath, pc2.dim("(01software already configured)"));
670
+ return;
671
+ }
672
+ const sep = existing.endsWith("\n") ? "" : "\n";
673
+ fs2.appendFileSync(loc.absolutePath, sep + section);
674
+ console.log(pc2.green(" Updated"), loc.displayPath);
675
+ } else {
676
+ fs2.writeFileSync(loc.absolutePath, section.trimStart());
677
+ console.log(pc2.green(" Created"), loc.displayPath);
845
678
  }
846
679
  }
847
680
  function addToGitignore(cwd, tools) {
848
681
  const entries = [];
849
- if (tools.includes("claude")) entries.push(".mcp.json");
850
- if (tools.includes("cursor")) entries.push(".cursor/mcp.json");
851
- if (tools.includes("vscode")) entries.push(".vscode/mcp.json");
682
+ for (const tool of tools) {
683
+ const loc = resolveMcpLocation(tool, cwd);
684
+ if (loc?.gitignoreEntry) entries.push(loc.gitignoreEntry);
685
+ }
852
686
  if (entries.length === 0) return;
853
687
  const gitignorePath = path2.join(cwd, ".gitignore");
854
688
  const existing = fs2.existsSync(gitignorePath) ? fs2.readFileSync(gitignorePath, "utf-8") : "";
@@ -966,7 +800,7 @@ var OTHER_FRAMEWORK_GUIDE = `
966
800
  secretKey: process.env.SOFTWARE_SECRET_KEY!,
967
801
  })
968
802
 
969
- 4. Docs: https://01.software/docs/guide/quickstart
803
+ 4. Docs: https://01.software/docs/developers/sdk/client
970
804
  `;
971
805
  async function main() {
972
806
  const cwd = process.cwd();
@@ -975,7 +809,7 @@ async function main() {
975
809
  console.log(pc3.dim(" Initialize 01.software SDK in your project"));
976
810
  console.log();
977
811
  const info = detectProject(cwd);
978
- if (!info.hasPackageJson) {
812
+ if (!info.hasPackageJson || info.parseError) {
979
813
  if (info.parseError) {
980
814
  console.log(pc3.red(" Could not parse package.json (invalid JSON)."));
981
815
  console.log(pc3.dim(" Fix the syntax error and try again."));
@@ -1007,7 +841,7 @@ async function main() {
1007
841
  const resolvedPm = info.packageManager ?? answers.packageManager ?? "npm";
1008
842
  const resolvedInfo = { ...info, packageManager: resolvedPm };
1009
843
  console.log();
1010
- await init(cwd, resolvedInfo, answers);
844
+ const result = await init(cwd, resolvedInfo, answers);
1011
845
  const env = answers.env;
1012
846
  const run = resolvedPm === "npm" ? "npm run" : resolvedPm;
1013
847
  console.log();
@@ -1015,6 +849,11 @@ async function main() {
1015
849
  console.log();
1016
850
  console.log(" Next steps:");
1017
851
  console.log();
852
+ if (result.installFailed) {
853
+ console.log(pc3.yellow(" Install the SDK manually:"));
854
+ console.log(pc3.cyan(` ${result.installCmd}`));
855
+ console.log();
856
+ }
1018
857
  if (env === "nextjs") {
1019
858
  console.log(pc3.dim(" Add QueryProvider to your root layout:"));
1020
859
  console.log();
@@ -1053,7 +892,7 @@ async function main() {
1053
892
  console.log();
1054
893
  }
1055
894
  if (answers.aiTools.length > 0 && (!answers.publishableKey || !answers.secretKey)) {
1056
- console.log(pc3.dim(" Update .mcp.json x-api-key with your sk01_... token"));
895
+ console.log(pc3.dim(" Update MCP config x-api-key with your sk01_... token"));
1057
896
  console.log();
1058
897
  }
1059
898
  if (env !== "vanilla") {