@sagebase/cli 0.0.2 → 1.0.1

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
@@ -56,6 +56,9 @@ function createSpinner(text) {
56
56
  stop() {
57
57
  spinner.stop();
58
58
  return this;
59
+ },
60
+ set text(val) {
61
+ spinner.text = val;
59
62
  }
60
63
  };
61
64
  }
@@ -70,6 +73,76 @@ function handleError(error) {
70
73
  process.exit(1);
71
74
  }
72
75
 
76
+ // src/utils/update-dependencies.ts
77
+ import { existsSync as existsSync2, readFileSync } from "fs";
78
+ import { join as join2 } from "path";
79
+ import { execa } from "execa";
80
+
81
+ // src/utils/get-package-manager.ts
82
+ import { existsSync } from "fs";
83
+ import { join } from "path";
84
+ async function getPackageManager(cwd) {
85
+ if (existsSync(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
86
+ if (existsSync(join(cwd, "yarn.lock"))) return "yarn";
87
+ if (existsSync(join(cwd, "bun.lock"))) return "bun";
88
+ if (existsSync(join(cwd, "package-lock.json"))) return "npm";
89
+ const userAgent = process.env.npm_config_user_agent || "";
90
+ if (userAgent.startsWith("pnpm")) return "pnpm";
91
+ if (userAgent.startsWith("yarn")) return "yarn";
92
+ if (userAgent.startsWith("bun")) return "bun";
93
+ return "npm";
94
+ }
95
+
96
+ // src/utils/update-dependencies.ts
97
+ var FLUE_DEPS = [];
98
+ var FLUE_DEV_DEPS = ["@flue/runtime", "@flue/cli", "valibot"];
99
+ function getInstalledDeps(cwd) {
100
+ try {
101
+ const pkgPath = join2(cwd, "package.json");
102
+ if (!existsSync2(pkgPath)) return /* @__PURE__ */ new Set();
103
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
104
+ return /* @__PURE__ */ new Set([
105
+ ...Object.keys(pkg.dependencies ?? {}),
106
+ ...Object.keys(pkg.devDependencies ?? {})
107
+ ]);
108
+ } catch {
109
+ return /* @__PURE__ */ new Set();
110
+ }
111
+ }
112
+ function getMissingDeps(cwd) {
113
+ const installed = getInstalledDeps(cwd);
114
+ return {
115
+ deps: FLUE_DEPS.filter((dep) => !installed.has(dep)),
116
+ devDeps: FLUE_DEV_DEPS.filter((dep) => !installed.has(dep))
117
+ };
118
+ }
119
+ async function installWithPackageManager(pm, deps, devDeps, cwd) {
120
+ if (pm === "npm") {
121
+ if (deps.length) await execa("npm", ["install", ...deps], { cwd });
122
+ if (devDeps.length)
123
+ await execa("npm", ["install", "-D", ...devDeps], { cwd });
124
+ } else {
125
+ if (deps.length) await execa(pm, ["add", ...deps], { cwd });
126
+ if (devDeps.length) await execa(pm, ["add", "-D", ...devDeps], { cwd });
127
+ }
128
+ }
129
+ async function ensureFlueDeps(cwd) {
130
+ const { deps, devDeps } = getMissingDeps(cwd);
131
+ const allMissing = [...deps, ...devDeps];
132
+ if (allMissing.length === 0) return;
133
+ const spinner = createSpinner(
134
+ `Installing ${allMissing.join(", ")}...`
135
+ ).start();
136
+ try {
137
+ const pm = await getPackageManager(cwd);
138
+ await installWithPackageManager(pm, deps, devDeps, cwd);
139
+ spinner.succeed();
140
+ } catch (error) {
141
+ spinner.fail("Failed to install Flue dependencies");
142
+ throw error;
143
+ }
144
+ }
145
+
73
146
  // src/utils/get-config.ts
74
147
  import { cosmiconfig } from "cosmiconfig";
75
148
  import { z } from "zod";
@@ -103,14 +176,14 @@ function getDefaultConfig() {
103
176
  }
104
177
 
105
178
  // src/utils/get-project-info.ts
106
- import { existsSync } from "fs";
179
+ import { existsSync as existsSync3 } from "fs";
107
180
  import { resolve } from "path";
108
181
  function detectFramework(cwd) {
109
- if (existsSync(resolve(cwd, "next.config.js")) || existsSync(resolve(cwd, "next.config.ts")) || existsSync(resolve(cwd, "next.config.mjs"))) {
182
+ if (existsSync3(resolve(cwd, "next.config.js")) || existsSync3(resolve(cwd, "next.config.ts")) || existsSync3(resolve(cwd, "next.config.mjs"))) {
110
183
  return "nextjs";
111
184
  }
112
- if (existsSync(resolve(cwd, "vite.config.ts")) || existsSync(resolve(cwd, "vite.config.js"))) {
113
- const hasRouter = existsSync(resolve(cwd, "app.config.ts"));
185
+ if (existsSync3(resolve(cwd, "vite.config.ts")) || existsSync3(resolve(cwd, "vite.config.js"))) {
186
+ const hasRouter = existsSync3(resolve(cwd, "app.config.ts"));
114
187
  if (hasRouter) return "tanstack-start";
115
188
  }
116
189
  return null;
@@ -118,7 +191,7 @@ function detectFramework(cwd) {
118
191
  function detectContentDir(cwd) {
119
192
  const candidates = ["content/docs", "src/content/docs"];
120
193
  for (const dir of candidates) {
121
- if (existsSync(resolve(cwd, dir))) {
194
+ if (existsSync3(resolve(cwd, dir))) {
122
195
  return dir;
123
196
  }
124
197
  }
@@ -276,13 +349,8 @@ function parsePnpmWorkspacePackages(content) {
276
349
  return patterns;
277
350
  }
278
351
 
279
- // src/commands/init.ts
280
- var initOptionsSchema = z2.object({
281
- cwd: z2.string().optional(),
282
- defaults: z2.boolean().optional(),
283
- helpCenter: z2.boolean().optional()
284
- });
285
- var CONTENT_SAMPLE = `---
352
+ // src/utils/templates.ts
353
+ var INDEX_MDX = `---
286
354
  title: Welcome
287
355
  description: Welcome to our help center
288
356
  ---
@@ -295,29 +363,213 @@ This help center is maintained by Sage \u2014 an AI agent that keeps your docume
295
363
 
296
364
  Replace this sample content with your own documentation.
297
365
  `;
298
- var WORKFLOW_YML = `name: Sage Documentation
366
+ var SAGE_WORKFLOW = `import { defineAgent, defineWorkflow } from "@flue/runtime";
367
+ import { local } from "@flue/runtime/node";
368
+ import * as v from "valibot";
369
+
370
+ const agent = defineAgent(() => ({
371
+ sandbox: local({
372
+ env: {
373
+ GH_TOKEN: process.env.GH_TOKEN,
374
+ },
375
+ }),
376
+ model: process.env.SAGE_MODEL ?? "anthropic/claude-sonnet-4-6",
377
+ instructions: \`You are Sage, an AI documentation agent. Your job is to analyze code changes and update documentation.
378
+
379
+ ## Frontmatter requirements
380
+ Every MDX file MUST start with valid YAML frontmatter:
381
+ ---
382
+ title: Page Title
383
+ description: Brief description
384
+ ---
385
+
386
+ ## Instructions
387
+ 1. Analyze the code diff to understand what changed
388
+ 2. Create new MDX files for new features/modules
389
+ 3. Update existing MDX files when related code changes
390
+ 4. Use clear, concise technical writing
391
+ 5. Include code examples where appropriate
392
+
393
+ ## Output format
394
+ Return a JSON array of file updates. ONLY include files that need creating or updating:
395
+ [
396
+ {
397
+ "path": "relative/path/from-content-dir/file.mdx",
398
+ "content": "---\\\\ntitle: ...\\\\ndescription: ...\\\\n---\\\\n\\\\nFull MDX content here"
399
+ }
400
+ ]\`,
401
+ }));
402
+
403
+ export default defineWorkflow({
404
+ agent,
405
+ input: v.object({
406
+ contentDir: v.optional(v.string(), "content/docs"),
407
+ diffSinceSha: v.optional(v.string()),
408
+ }),
409
+ output: v.object({
410
+ filesUpdated: v.number(),
411
+ prUrl: v.optional(v.string()),
412
+ }),
413
+ async run({ harness, input, log }) {
414
+ const session = await harness.session();
415
+ const contentDir = input.contentDir;
416
+
417
+ // 1. Get git diff
418
+ const mergeSha =
419
+ input.diffSinceSha ||
420
+ (await session.shell('git log --merges -1 --format="%H"')).stdout.trim();
421
+
422
+ const diff = await session.shell(
423
+ \`git diff \${mergeSha}..HEAD --diff-filter=ACMR -- .\`,
424
+ );
425
+
426
+ if (!diff.stdout.trim()) {
427
+ log.info("no code changes detected \u2014 nothing to document");
428
+ return { filesUpdated: 0 };
429
+ }
430
+
431
+ const changedFiles = (
432
+ await session.shell(
433
+ \`git diff \${mergeSha}..HEAD --diff-filter=ACMR --name-only -- .\`,
434
+ )
435
+ ).stdout
436
+ .trim()
437
+ .split("\\n")
438
+ .filter(Boolean);
439
+
440
+ // 2. Read existing docs
441
+ const existingDocs = (
442
+ await session.shell(
443
+ \`find \${contentDir} -name '*.mdx' -type f 2>/dev/null | sort\`,
444
+ )
445
+ ).stdout
446
+ .trim()
447
+ .split("\\n")
448
+ .filter(Boolean);
449
+
450
+ const existingContent: { path: string; content: string }[] = [];
451
+ for (const doc of existingDocs) {
452
+ const content = (await session.shell(\`cat "\${doc}" 2>/dev/null\`)).stdout
453
+ .trim()
454
+ .slice(0, 2000);
455
+ existingContent.push({ path: doc, content });
456
+ }
457
+
458
+ // 3. Call LLM to analyze diff and determine doc updates
459
+ let existingContext = "";
460
+ if (existingContent.length > 0) {
461
+ existingContext =
462
+ "\\n\\n## Existing documentation\\n" +
463
+ existingContent
464
+ .map((d) => \`### \${d.path}\\n\\\`\\\`\\\`mdx\\n\${d.content}\\n\\\`\\\`\\\`\`)
465
+ .join("\\n");
466
+ }
467
+
468
+ const userMessage = \`Documentation files live in \\\`\${contentDir}\\\`. Create and update MDX files there.\\n\\nHere is the code diff:\\n\\n\\\`\\\`\\\`diff\\n\${diff.stdout.slice(0, 50000)}\\n\\\`\\\`\\\`\\n\\nChanged files:\\n\${changedFiles.map((f) => \`- \${f}\`).join("\\n")}\${existingContext}\`;
469
+
470
+ const { data } = await session.prompt(userMessage, {
471
+ result: v.object({
472
+ updates: v.array(
473
+ v.object({
474
+ path: v.string(),
475
+ content: v.string(),
476
+ }),
477
+ ),
478
+ }),
479
+ });
480
+
481
+ if (data.updates.length === 0) {
482
+ log.info("no documentation updates needed");
483
+ return { filesUpdated: 0 };
484
+ }
485
+
486
+ // 4. Write MDX files
487
+ for (const update of data.updates) {
488
+ const fullPath = \`\${contentDir}/\${update.path}\`;
489
+ await harness.fs.writeFile(fullPath, update.content);
490
+ log.info("wrote", { path: fullPath });
491
+ }
492
+
493
+ // 5. Create PR
494
+ const timestamp = Date.now();
495
+ const branchName = \`sage/docs-\${timestamp}\`;
496
+
497
+ const branchExists = (
498
+ await session.shell(\`git branch --list '\${branchName}'\`)
499
+ ).stdout.trim();
500
+
501
+ if (branchExists) {
502
+ await session.shell(\`git checkout "\${branchName}"\`);
503
+ } else {
504
+ await session.shell(\`git checkout -b "\${branchName}"\`);
505
+ }
506
+
507
+ await session.shell("git add -A");
508
+
509
+ const hasChanges = (
510
+ await session.shell("git status --porcelain")
511
+ ).stdout.trim();
512
+ if (!hasChanges) {
513
+ log.info("no changes to commit");
514
+ return { filesUpdated: data.updates.length };
515
+ }
516
+
517
+ await session.shell("git commit -m 'docs: update documentation via Sage'");
518
+ await session.shell(\`git push origin "\${branchName}" -u\`);
519
+
520
+ const prUrl = (
521
+ await session.shell(
522
+ \`gh pr create --title "docs: update documentation via Sage" --body "Automated documentation update by Sage.\\n\\nFiles changed:\\n\${data.updates.map((u: { path: string; content: string }) => \`- \\\`\${u.path}\\\`\`).join("\\n")}"\`,
523
+ )
524
+ ).stdout.trim();
525
+
526
+ log.info("PR created", { url: prUrl });
527
+
528
+ return { filesUpdated: data.updates.length, prUrl };
529
+ },
530
+ });
531
+ `;
532
+ var FLUE_CONFIG = `import { defineConfig } from "@flue/cli/config";
533
+
534
+ export default defineConfig({
535
+ target: "node",
536
+ });
537
+ `;
538
+ var CI_WORKFLOW = `name: Sage Documentation
299
539
  on:
300
540
  push:
301
541
  branches: [main]
302
542
  jobs:
303
543
  sage:
304
544
  runs-on: ubuntu-latest
545
+ permissions:
546
+ contents: write
547
+ pull-requests: write
305
548
  steps:
306
549
  - uses: actions/checkout@v4
307
550
  with:
308
551
  fetch-depth: 0
309
552
  - uses: actions/setup-node@v4
310
553
  with:
311
- node-version: 20
312
- - run: npx @sagebase/cli run
554
+ node-version: 22
555
+ - run: npm ci
556
+ - name: Run Sage
313
557
  env:
314
558
  ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
559
+ GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
560
+ run: npx flue run sage
315
561
  `;
562
+
563
+ // src/commands/init.ts
564
+ var initOptionsSchema = z2.object({
565
+ cwd: z2.string().optional(),
566
+ defaults: z2.boolean().optional(),
567
+ helpCenter: z2.boolean().optional()
568
+ });
316
569
  var init = new Command().name("init").alias("create").description("initialize sage in your project").argument("[dir]", "project directory").option("--cwd <cwd>", "working directory").option("--defaults", "use default configuration without prompting").option("--help-center", "add Fumadocs help center").action(async (dir, opts) => {
317
570
  try {
318
571
  const options = initOptionsSchema.parse(opts);
319
572
  const cwd = resolve2(dir || options.cwd || process.cwd());
320
- logger.log(options, cwd);
321
573
  const spinner = createSpinner("Setting up Sage...").start();
322
574
  const framework = detectFramework(cwd);
323
575
  if (!framework) {
@@ -344,25 +596,32 @@ var init = new Command().name("init").alias("create").description("initialize sa
344
596
  try {
345
597
  await access(indexMdxPath, constants.F_OK);
346
598
  } catch {
347
- await writeFile(indexMdxPath, CONTENT_SAMPLE);
599
+ await writeFile(indexMdxPath, INDEX_MDX);
348
600
  }
601
+ const flueWorkflowsDir = resolve2(cwd, "src", "workflows");
602
+ await mkdir(flueWorkflowsDir, { recursive: true });
603
+ await writeFile(resolve2(flueWorkflowsDir, "sage.ts"), SAGE_WORKFLOW);
604
+ await writeFile(resolve2(cwd, "flue.config.ts"), FLUE_CONFIG);
349
605
  const workflowsDir = resolve2(cwd, ".github", "workflows");
350
606
  await mkdir(workflowsDir, { recursive: true });
351
- await writeFile(resolve2(workflowsDir, "sage.yml"), WORKFLOW_YML);
607
+ await writeFile(resolve2(workflowsDir, "sage.yml"), CI_WORKFLOW);
352
608
  await writeFile(
353
609
  resolve2(cwd, "sage.json"),
354
610
  JSON.stringify(config, null, 2)
355
611
  );
612
+ await ensureFlueDeps(cwd);
356
613
  spinner.succeed("Sage initialized!");
357
614
  logger.break();
358
615
  logger.success("Created:");
359
616
  logger.log(` sage.json \u2014 configuration`);
617
+ logger.log(` src/workflows/ \u2014 Flue agent workflow`);
360
618
  logger.log(` ${contentDir}/ \u2014 documentation source`);
361
619
  logger.log(` .github/workflows/sage.yml \u2014 CI workflow`);
362
620
  logger.break();
363
621
  logger.info("Next steps:");
364
622
  logger.log(" Run sage dev to preview your help center");
365
- logger.log(" Edit content/docs/ to add your documentation");
623
+ logger.log(" Run sage run to generate documentation from code changes");
624
+ logger.log(" Edit src/workflows/sage.ts to customize the agent");
366
625
  logger.break();
367
626
  if (!existingContent) {
368
627
  logger.info("Created: content/docs/index.mdx \u2014 sample article");
@@ -384,7 +643,6 @@ var dev = new Command2().name("dev").description("start the help center preview
384
643
  try {
385
644
  const options = devOptionsSchema.parse(opts);
386
645
  const cwd = resolve3(options.cwd || process.cwd());
387
- logger.log(options, cwd);
388
646
  const config = await getConfig(cwd);
389
647
  if (!config) {
390
648
  logger.error("No sage.json found. Run sage init first.");
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/commands/init.ts","../src/utils/logger.ts","../src/utils/spinner.ts","../src/utils/handle-error.ts","../src/utils/get-config.ts","../src/utils/get-project-info.ts","../src/utils/get-monorepo-info.ts","../src/utils/highlighter.ts","../src/commands/dev.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from \"commander\"\nimport { init } from \"./commands/init.js\"\nimport { dev } from \"./commands/dev.js\"\n\nprocess.on(\"SIGINT\", () => process.exit(0))\nprocess.on(\"SIGTERM\", () => process.exit(0))\n\nasync function main() {\n const program = new Command()\n .name(\"sage\")\n .description(\"AI documentation agent for your codebase\")\n .version(\"0.0.1\")\n\n program.addCommand(init)\n program.addCommand(dev)\n\n program.parse()\n}\n\nmain()\n","import { Command } from \"commander\";\nimport { constants } from \"fs\";\nimport { access, mkdir, writeFile } from \"fs/promises\";\nimport { resolve } from \"path\";\nimport { z } from \"zod\";\nimport { logger } from \"../utils/logger.js\";\nimport { createSpinner } from \"../utils/spinner.js\";\nimport { handleError } from \"../utils/handle-error.js\";\nimport { getDefaultConfig } from \"../utils/get-config.js\";\nimport {\n detectContentDir,\n detectFramework,\n} from \"../utils/get-project-info.js\";\nimport {\n formatMonorepoMessage,\n getMonorepoTargets,\n isMonorepoRoot,\n} from \"../utils/get-monorepo-info.js\";\n\nconst initOptionsSchema = z.object({\n cwd: z.string().optional(),\n defaults: z.boolean().optional(),\n helpCenter: z.boolean().optional(),\n});\n\nconst CONTENT_SAMPLE = `---\ntitle: Welcome\ndescription: Welcome to our help center\n---\n\n# Welcome\n\nThis help center is maintained by Sage — an AI agent that keeps your documentation accurate as you ship.\n\n## Getting started\n\nReplace this sample content with your own documentation.\n`;\n\nconst WORKFLOW_YML = `name: Sage Documentation\non:\n push:\n branches: [main]\njobs:\n sage:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n with:\n fetch-depth: 0\n - uses: actions/setup-node@v4\n with:\n node-version: 20\n - run: npx @sagebase/cli run\n env:\n ANTHROPIC_API_KEY: \\${{ secrets.ANTHROPIC_API_KEY }}\n`;\n\n// TODO: we need a robust test fixture for both mono-repo, non-monorepo, next, tanstack; shadcn has all figured that out so we can just copy that approach for this; doing it manually is painstaking and always leads to some error cuz i have to do it and i'm lazy\n//\n// DO this before implementing second issue;\nexport const init = new Command()\n .name(\"init\")\n .alias(\"create\")\n .description(\"initialize sage in your project\")\n .argument(\"[dir]\", \"project directory\")\n .option(\"--cwd <cwd>\", \"working directory\")\n .option(\"--defaults\", \"use default configuration without prompting\")\n .option(\"--help-center\", \"add Fumadocs help center\")\n .action(async (dir, opts) => {\n try {\n const options = initOptionsSchema.parse(opts);\n const cwd = resolve(dir || options.cwd || process.cwd());\n\n // pnpm --filter=@sagebase/cli start:dev init ./apps/web\n // {} /Users/bishalneupane/Desktop/myprojects/sagebase/packages/cli/apps/web\n // Relative path does not work, have to pass absolute stuff; maybe fix this?? prioritize maybe the process.cwd more?? that would probably create yet another set of problems right????\n // This worked: bun sage init ~/Desktop/myprojects/sagebase/apps/web\n //\n // init didn't add any cli script into package.json how is the user supposed to run dev now\n // maybe again same npx sage dev command?? right\n //\n //\n // Would it be possible to avoid creating yet anaother config file cuz we're literally drowing in them\n logger.log(options, cwd);\n\n const spinner = createSpinner(\"Setting up Sage...\").start();\n\n const framework = detectFramework(cwd);\n if (!framework) {\n // Check if we're in a monorepo root before failing.\n if (await isMonorepoRoot(cwd)) {\n const targets = await getMonorepoTargets(cwd);\n if (targets.length > 0) {\n spinner.stop();\n formatMonorepoMessage(\"init\", targets);\n process.exit(1);\n }\n }\n spinner.fail(\n \"Could not detect framework. Only Next.js and TanStack Start are supported.\",\n );\n process.exit(1);\n }\n\n // TODO: maybe here we could use the default fuma docs cli here instead of trying to do this stuff manaully we could have more options from there; but that means we are getting joined with fuma-docs so there is a bit of trade-off there; we won't be able to control the whole setting up experience\n // how does the better-t-setup does this things well??\n //\n // Better idea is to let the user do the setup themselves directly using the fuma-docs cli and configure everything they want, and we don't deal with all that complexity ourselves and doing that will make ur repo both easy and we'll not be tied to fuma-docs as well;\n const existingContent = detectContentDir(cwd);\n const contentDir = existingContent || \"content/docs\";\n\n const config = getDefaultConfig();\n config.framework = framework;\n config.content = contentDir;\n\n await mkdir(resolve(cwd, \"content\", \"docs\"), { recursive: true });\n\n const indexMdxPath = resolve(cwd, contentDir, \"index.mdx\");\n try {\n await access(indexMdxPath, constants.F_OK);\n } catch {\n await writeFile(indexMdxPath, CONTENT_SAMPLE);\n }\n\n // TODO: here should also not do the access and write only if not exists; or is it just okay to write multiple times; here\n const workflowsDir = resolve(cwd, \".github\", \"workflows\");\n await mkdir(workflowsDir, { recursive: true });\n await writeFile(resolve(workflowsDir, \"sage.yml\"), WORKFLOW_YML);\n\n await writeFile(\n resolve(cwd, \"sage.json\"),\n JSON.stringify(config, null, 2),\n );\n\n spinner.succeed(\"Sage initialized!\");\n\n logger.break();\n logger.success(\"Created:\");\n logger.log(` sage.json — configuration`);\n logger.log(` ${contentDir}/ — documentation source`);\n logger.log(` .github/workflows/sage.yml — CI workflow`);\n logger.break();\n logger.info(\"Next steps:\");\n logger.log(\" Run sage dev to preview your help center\");\n logger.log(\" Edit content/docs/ to add your documentation\");\n logger.break();\n\n if (!existingContent) {\n logger.info(\"Created: content/docs/index.mdx — sample article\");\n }\n } catch (error) {\n handleError(error);\n }\n });\n","import kleur from \"kleur\"\n\nexport const logger = {\n error(...args: unknown[]) {\n console.log(kleur.red(\"✗\"), ...args)\n },\n warn(...args: unknown[]) {\n console.log(kleur.yellow(\"⚠\"), ...args)\n },\n info(...args: unknown[]) {\n console.log(kleur.cyan(\"ℹ\"), ...args)\n },\n success(...args: unknown[]) {\n console.log(kleur.green(\"✓\"), ...args)\n },\n log(...args: unknown[]) {\n console.log(...args)\n },\n break() {\n console.log(\"\")\n },\n}\n","import ora from \"ora\"\n\nexport function createSpinner(text: string) {\n const spinner = ora({\n text,\n color: \"cyan\",\n })\n\n return {\n start() {\n spinner.start()\n return this\n },\n succeed(text?: string) {\n spinner.succeed(text)\n return this\n },\n fail(text?: string) {\n spinner.fail(text)\n return this\n },\n stop() {\n spinner.stop()\n return this\n },\n }\n}\n","import { logger } from \"./logger.js\"\n\nexport function handleError(error: unknown) {\n if (error instanceof Error) {\n logger.error(error.message)\n } else {\n logger.error(String(error))\n }\n\n process.exit(1)\n}\n","import { cosmiconfig } from \"cosmiconfig\"\nimport { z } from \"zod\"\n\nconst explorer = cosmiconfig(\"sage\", {\n searchPlaces: [\"sage.json\", \"sage.config.json\", \"sage.config.ts\"],\n})\n\nexport const sageConfigSchema = z.object({\n $schema: z.string().optional(),\n framework: z.enum([\"nextjs\", \"tanstack-start\"]).default(\"nextjs\"),\n content: z.string().default(\"content/docs\"),\n agent: z\n .object({\n model: z.string().default(\"anthropic/claude-sonnet-4-6\"),\n provider: z.string().default(\"anthropic\"),\n })\n .default({}),\n ci: z\n .object({\n trigger: z.string().default(\"pr-merge\"),\n workflow: z.string().default(\".github/workflows/sage.yml\"),\n })\n .default({}),\n})\n\nexport type SageConfig = z.infer<typeof sageConfigSchema>\n\nexport async function getConfig(cwd?: string): Promise<SageConfig | null> {\n try {\n const result = await explorer.search(cwd)\n if (!result?.config) return null\n return sageConfigSchema.parse(result.config)\n } catch {\n return null\n }\n}\n\nexport function getDefaultConfig(): SageConfig {\n return sageConfigSchema.parse({})\n}\n","import { existsSync } from \"fs\"\nimport { resolve } from \"path\"\n\nexport type Framework = \"nextjs\" | \"tanstack-start\" | null\n\nexport function detectFramework(cwd: string): Framework {\n if (existsSync(resolve(cwd, \"next.config.js\")) || existsSync(resolve(cwd, \"next.config.ts\")) || existsSync(resolve(cwd, \"next.config.mjs\"))) {\n return \"nextjs\"\n }\n if (existsSync(resolve(cwd, \"vite.config.ts\")) || existsSync(resolve(cwd, \"vite.config.js\"))) {\n const hasRouter = existsSync(resolve(cwd, \"app.config.ts\"))\n if (hasRouter) return \"tanstack-start\"\n }\n return null\n}\n\nexport function detectContentDir(cwd: string): string | null {\n const candidates = [\"content/docs\", \"src/content/docs\"]\n for (const dir of candidates) {\n if (existsSync(resolve(cwd, dir))) {\n return dir\n }\n }\n return null\n}\n","import path from \"path\"\nimport { highlighter } from \"../utils/highlighter.js\"\nimport { logger } from \"../utils/logger.js\"\nimport fg from \"fast-glob\"\nimport fs from \"fs-extra\"\n\nconst FRAMEWORK_CONFIG_FILES = [\n \"next.config.*\",\n \"vite.config.*\",\n \"astro.config.*\",\n \"remix.config.*\",\n \"nuxt.config.*\",\n \"svelte.config.*\",\n \"gatsby-config.*\",\n \"angular.json\",\n]\n\n// Checks for workspace signals at the given directory.\nexport async function isMonorepoRoot(cwd: string) {\n // pnpm workspaces.\n if (fs.existsSync(path.resolve(cwd, \"pnpm-workspace.yaml\"))) {\n return true\n }\n\n // npm/yarn workspaces.\n const packageJsonPath = path.resolve(cwd, \"package.json\")\n if (fs.existsSync(packageJsonPath)) {\n try {\n const packageJson = await fs.readJson(packageJsonPath)\n if (packageJson.workspaces) {\n return true\n }\n } catch {\n // Ignore parse errors.\n }\n }\n\n // Lerna.\n if (fs.existsSync(path.resolve(cwd, \"lerna.json\"))) {\n return true\n }\n\n // Nx.\n if (fs.existsSync(path.resolve(cwd, \"nx.json\"))) {\n return true\n }\n\n return false\n}\n\n// Finds app directories in a monorepo that contain framework configs or sage.json.\nexport async function getMonorepoTargets(cwd: string) {\n const patterns = await getWorkspacePatterns(cwd)\n\n if (!patterns.length) {\n return []\n }\n\n // Resolve patterns to directories.\n const dirs = await fg(patterns, {\n cwd,\n onlyDirectories: true,\n ignore: [\"**/node_modules/**\"],\n })\n\n const targets: { name: string; hasConfig: boolean }[] = []\n\n for (const dir of dirs) {\n const fullPath = path.resolve(cwd, dir)\n\n // Check if it has a package.json (it's an actual workspace).\n if (!fs.existsSync(path.resolve(fullPath, \"package.json\"))) {\n continue\n }\n\n const hasSageJson = fs.existsSync(\n path.resolve(fullPath, \"sage.json\")\n )\n\n // Check for framework config files.\n const hasFrameworkConfig = FRAMEWORK_CONFIG_FILES.some((pattern) => {\n const matches = fg.sync(pattern, {\n cwd: fullPath,\n dot: true,\n })\n return matches.length > 0\n })\n\n if (hasSageJson || hasFrameworkConfig) {\n targets.push({\n name: dir,\n hasConfig: hasSageJson,\n })\n }\n }\n\n return targets\n}\n\n// Formats and logs the monorepo detection message.\nexport function formatMonorepoMessage(\n command: string,\n targets: { name: string; hasConfig: boolean }[],\n options?: {\n cwdFlag?: string\n }\n) {\n const cwdFlag = options?.cwdFlag ?? \"--cwd\"\n\n logger.break()\n logger.log(\n `It looks like you are running ${highlighter.info(\n command\n )} from a monorepo root.`\n )\n logger.log(\n `To use sage in a specific workspace, use the ${highlighter.info(\n cwdFlag\n )} flag:`\n )\n logger.break()\n\n for (const target of targets) {\n logger.log(` sage ${command} ${cwdFlag} ${target.name}`)\n }\n\n logger.break()\n}\n\nexport async function getWorkspacePatterns(cwd: string) {\n const patterns: string[] = []\n\n // Read pnpm-workspace.yaml.\n const pnpmWorkspacePath = path.resolve(cwd, \"pnpm-workspace.yaml\")\n if (fs.existsSync(pnpmWorkspacePath)) {\n const content = await fs.readFile(pnpmWorkspacePath, \"utf8\")\n patterns.push(...parsePnpmWorkspacePackages(content))\n }\n\n // Read package.json workspaces.\n const packageJsonPath = path.resolve(cwd, \"package.json\")\n if (fs.existsSync(packageJsonPath)) {\n try {\n const packageJson = await fs.readJson(packageJsonPath)\n const workspaces = Array.isArray(packageJson.workspaces)\n ? packageJson.workspaces\n : packageJson.workspaces?.packages\n if (Array.isArray(workspaces)) {\n patterns.push(...workspaces.filter((w: string) => !w.startsWith(\"!\")))\n }\n } catch {\n // Ignore parse errors.\n }\n }\n\n return Array.from(new Set(patterns))\n}\n\nexport function parsePnpmWorkspacePackages(content: string) {\n const patterns: string[] = []\n let inPackages = false\n let packagesIndent = 0\n\n for (const line of content.split(\"\\n\")) {\n const trimmed = line.trim()\n\n if (!trimmed || trimmed.startsWith(\"#\")) {\n continue\n }\n\n const keyMatch = line.match(/^(\\s*)([A-Za-z0-9_-]+)\\s*:/)\n if (keyMatch) {\n packagesIndent = keyMatch[1].length\n inPackages = keyMatch[2] === \"packages\"\n continue\n }\n\n if (!inPackages) {\n continue\n }\n\n const itemMatch = line.match(/^(\\s*)-\\s*(.+?)\\s*(?:#.*)?$/)\n if (!itemMatch || itemMatch[1].length <= packagesIndent) {\n continue\n }\n\n patterns.push(itemMatch[2].trim().replace(/^[\"']|[\"']$/g, \"\"))\n }\n\n return patterns\n}\n","import kleur from \"kleur\"\n\nexport const highlighter = {\n error: kleur.red,\n warn: kleur.yellow,\n info: kleur.cyan,\n success: kleur.green,\n highlight: kleur.bold,\n dim: kleur.dim,\n code: kleur.italic,\n}\n","import { Command } from \"commander\";\nimport { resolve } from \"path\";\nimport { z } from \"zod\";\nimport { handleError } from \"../utils/handle-error.js\";\nimport { getConfig } from \"../utils/get-config.js\";\nimport { logger } from \"../utils/logger.js\";\n\nconst devOptionsSchema = z.object({\n cwd: z.string().optional(),\n port: z.coerce.number().optional(),\n});\n\n// Right now it does not do shit apart from just informing user if config is fine, and giving instruction to setup of fumadocs properly\n//\n// Probably here see if fuma-docs is setup properly?? if not setup than give user quick command i.e fuma-docs setup script they can run; otherwise start the fuma-docs server from here;\n//\n// They can directly do that, and this is duplicate but still good to have here\nexport const dev = new Command()\n .name(\"dev\")\n .description(\"start the help center preview server\")\n .option(\"--cwd <cwd>\", \"working directory\")\n .option(\"-p, --port <port>\", \"port to serve on\")\n .action(async (opts) => {\n try {\n const options = devOptionsSchema.parse(opts);\n const cwd = resolve(options.cwd || process.cwd());\n\n logger.log(options, cwd);\n\n const config = await getConfig(cwd);\n if (!config) {\n logger.error(\"No sage.json found. Run sage init first.\");\n process.exit(1);\n }\n\n logger.info(`Content directory: ${config.content}`);\n logger.info(`Framework: ${config.framework}`);\n logger.break();\n logger.info(\"To preview your help center, run your app's dev server:\");\n logger.info(\" npm run dev\");\n logger.break();\n logger.info(\"Make sure Fumadocs is configured in your app to read from:\");\n logger.info(` ${config.content}`);\n } catch (error) {\n handleError(error);\n }\n });\n"],"mappings":";;;AAEA,SAAS,WAAAA,gBAAe;;;ACFxB,SAAS,eAAe;AACxB,SAAS,iBAAiB;AAC1B,SAAS,QAAQ,OAAO,iBAAiB;AACzC,SAAS,WAAAC,gBAAe;AACxB,SAAS,KAAAC,UAAS;;;ACJlB,OAAO,WAAW;AAEX,IAAM,SAAS;AAAA,EACpB,SAAS,MAAiB;AACxB,YAAQ,IAAI,MAAM,IAAI,QAAG,GAAG,GAAG,IAAI;AAAA,EACrC;AAAA,EACA,QAAQ,MAAiB;AACvB,YAAQ,IAAI,MAAM,OAAO,QAAG,GAAG,GAAG,IAAI;AAAA,EACxC;AAAA,EACA,QAAQ,MAAiB;AACvB,YAAQ,IAAI,MAAM,KAAK,QAAG,GAAG,GAAG,IAAI;AAAA,EACtC;AAAA,EACA,WAAW,MAAiB;AAC1B,YAAQ,IAAI,MAAM,MAAM,QAAG,GAAG,GAAG,IAAI;AAAA,EACvC;AAAA,EACA,OAAO,MAAiB;AACtB,YAAQ,IAAI,GAAG,IAAI;AAAA,EACrB;AAAA,EACA,QAAQ;AACN,YAAQ,IAAI,EAAE;AAAA,EAChB;AACF;;;ACrBA,OAAO,SAAS;AAET,SAAS,cAAc,MAAc;AAC1C,QAAM,UAAU,IAAI;AAAA,IAClB;AAAA,IACA,OAAO;AAAA,EACT,CAAC;AAED,SAAO;AAAA,IACL,QAAQ;AACN,cAAQ,MAAM;AACd,aAAO;AAAA,IACT;AAAA,IACA,QAAQC,OAAe;AACrB,cAAQ,QAAQA,KAAI;AACpB,aAAO;AAAA,IACT;AAAA,IACA,KAAKA,OAAe;AAClB,cAAQ,KAAKA,KAAI;AACjB,aAAO;AAAA,IACT;AAAA,IACA,OAAO;AACL,cAAQ,KAAK;AACb,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;ACxBO,SAAS,YAAY,OAAgB;AAC1C,MAAI,iBAAiB,OAAO;AAC1B,WAAO,MAAM,MAAM,OAAO;AAAA,EAC5B,OAAO;AACL,WAAO,MAAM,OAAO,KAAK,CAAC;AAAA,EAC5B;AAEA,UAAQ,KAAK,CAAC;AAChB;;;ACVA,SAAS,mBAAmB;AAC5B,SAAS,SAAS;AAElB,IAAM,WAAW,YAAY,QAAQ;AAAA,EACnC,cAAc,CAAC,aAAa,oBAAoB,gBAAgB;AAClE,CAAC;AAEM,IAAM,mBAAmB,EAAE,OAAO;AAAA,EACvC,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,WAAW,EAAE,KAAK,CAAC,UAAU,gBAAgB,CAAC,EAAE,QAAQ,QAAQ;AAAA,EAChE,SAAS,EAAE,OAAO,EAAE,QAAQ,cAAc;AAAA,EAC1C,OAAO,EACJ,OAAO;AAAA,IACN,OAAO,EAAE,OAAO,EAAE,QAAQ,6BAA6B;AAAA,IACvD,UAAU,EAAE,OAAO,EAAE,QAAQ,WAAW;AAAA,EAC1C,CAAC,EACA,QAAQ,CAAC,CAAC;AAAA,EACb,IAAI,EACD,OAAO;AAAA,IACN,SAAS,EAAE,OAAO,EAAE,QAAQ,UAAU;AAAA,IACtC,UAAU,EAAE,OAAO,EAAE,QAAQ,4BAA4B;AAAA,EAC3D,CAAC,EACA,QAAQ,CAAC,CAAC;AACf,CAAC;AAID,eAAsB,UAAU,KAA0C;AACxE,MAAI;AACF,UAAM,SAAS,MAAM,SAAS,OAAO,GAAG;AACxC,QAAI,CAAC,QAAQ,OAAQ,QAAO;AAC5B,WAAO,iBAAiB,MAAM,OAAO,MAAM;AAAA,EAC7C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,mBAA+B;AAC7C,SAAO,iBAAiB,MAAM,CAAC,CAAC;AAClC;;;ACvCA,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AAIjB,SAAS,gBAAgB,KAAwB;AACtD,MAAI,WAAW,QAAQ,KAAK,gBAAgB,CAAC,KAAK,WAAW,QAAQ,KAAK,gBAAgB,CAAC,KAAK,WAAW,QAAQ,KAAK,iBAAiB,CAAC,GAAG;AAC3I,WAAO;AAAA,EACT;AACA,MAAI,WAAW,QAAQ,KAAK,gBAAgB,CAAC,KAAK,WAAW,QAAQ,KAAK,gBAAgB,CAAC,GAAG;AAC5F,UAAM,YAAY,WAAW,QAAQ,KAAK,eAAe,CAAC;AAC1D,QAAI,UAAW,QAAO;AAAA,EACxB;AACA,SAAO;AACT;AAEO,SAAS,iBAAiB,KAA4B;AAC3D,QAAM,aAAa,CAAC,gBAAgB,kBAAkB;AACtD,aAAW,OAAO,YAAY;AAC5B,QAAI,WAAW,QAAQ,KAAK,GAAG,CAAC,GAAG;AACjC,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;;;ACxBA,OAAO,UAAU;;;ACAjB,OAAOC,YAAW;AAEX,IAAM,cAAc;AAAA,EACzB,OAAOA,OAAM;AAAA,EACb,MAAMA,OAAM;AAAA,EACZ,MAAMA,OAAM;AAAA,EACZ,SAASA,OAAM;AAAA,EACf,WAAWA,OAAM;AAAA,EACjB,KAAKA,OAAM;AAAA,EACX,MAAMA,OAAM;AACd;;;ADPA,OAAO,QAAQ;AACf,OAAO,QAAQ;AAEf,IAAM,yBAAyB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGA,eAAsB,eAAe,KAAa;AAEhD,MAAI,GAAG,WAAW,KAAK,QAAQ,KAAK,qBAAqB,CAAC,GAAG;AAC3D,WAAO;AAAA,EACT;AAGA,QAAM,kBAAkB,KAAK,QAAQ,KAAK,cAAc;AACxD,MAAI,GAAG,WAAW,eAAe,GAAG;AAClC,QAAI;AACF,YAAM,cAAc,MAAM,GAAG,SAAS,eAAe;AACrD,UAAI,YAAY,YAAY;AAC1B,eAAO;AAAA,MACT;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAGA,MAAI,GAAG,WAAW,KAAK,QAAQ,KAAK,YAAY,CAAC,GAAG;AAClD,WAAO;AAAA,EACT;AAGA,MAAI,GAAG,WAAW,KAAK,QAAQ,KAAK,SAAS,CAAC,GAAG;AAC/C,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAGA,eAAsB,mBAAmB,KAAa;AACpD,QAAM,WAAW,MAAM,qBAAqB,GAAG;AAE/C,MAAI,CAAC,SAAS,QAAQ;AACpB,WAAO,CAAC;AAAA,EACV;AAGA,QAAM,OAAO,MAAM,GAAG,UAAU;AAAA,IAC9B;AAAA,IACA,iBAAiB;AAAA,IACjB,QAAQ,CAAC,oBAAoB;AAAA,EAC/B,CAAC;AAED,QAAM,UAAkD,CAAC;AAEzD,aAAW,OAAO,MAAM;AACtB,UAAM,WAAW,KAAK,QAAQ,KAAK,GAAG;AAGtC,QAAI,CAAC,GAAG,WAAW,KAAK,QAAQ,UAAU,cAAc,CAAC,GAAG;AAC1D;AAAA,IACF;AAEA,UAAM,cAAc,GAAG;AAAA,MACrB,KAAK,QAAQ,UAAU,WAAW;AAAA,IACpC;AAGA,UAAM,qBAAqB,uBAAuB,KAAK,CAAC,YAAY;AAClE,YAAM,UAAU,GAAG,KAAK,SAAS;AAAA,QAC/B,KAAK;AAAA,QACL,KAAK;AAAA,MACP,CAAC;AACD,aAAO,QAAQ,SAAS;AAAA,IAC1B,CAAC;AAED,QAAI,eAAe,oBAAoB;AACrC,cAAQ,KAAK;AAAA,QACX,MAAM;AAAA,QACN,WAAW;AAAA,MACb,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;AAGO,SAAS,sBACd,SACA,SACA,SAGA;AACA,QAAM,UAAU,SAAS,WAAW;AAEpC,SAAO,MAAM;AACb,SAAO;AAAA,IACL,iCAAiC,YAAY;AAAA,MAC3C;AAAA,IACF,CAAC;AAAA,EACH;AACA,SAAO;AAAA,IACL,gDAAgD,YAAY;AAAA,MAC1D;AAAA,IACF,CAAC;AAAA,EACH;AACA,SAAO,MAAM;AAEb,aAAW,UAAU,SAAS;AAC5B,WAAO,IAAI,UAAU,OAAO,IAAI,OAAO,IAAI,OAAO,IAAI,EAAE;AAAA,EAC1D;AAEA,SAAO,MAAM;AACf;AAEA,eAAsB,qBAAqB,KAAa;AACtD,QAAM,WAAqB,CAAC;AAG5B,QAAM,oBAAoB,KAAK,QAAQ,KAAK,qBAAqB;AACjE,MAAI,GAAG,WAAW,iBAAiB,GAAG;AACpC,UAAM,UAAU,MAAM,GAAG,SAAS,mBAAmB,MAAM;AAC3D,aAAS,KAAK,GAAG,2BAA2B,OAAO,CAAC;AAAA,EACtD;AAGA,QAAM,kBAAkB,KAAK,QAAQ,KAAK,cAAc;AACxD,MAAI,GAAG,WAAW,eAAe,GAAG;AAClC,QAAI;AACF,YAAM,cAAc,MAAM,GAAG,SAAS,eAAe;AACrD,YAAM,aAAa,MAAM,QAAQ,YAAY,UAAU,IACnD,YAAY,aACZ,YAAY,YAAY;AAC5B,UAAI,MAAM,QAAQ,UAAU,GAAG;AAC7B,iBAAS,KAAK,GAAG,WAAW,OAAO,CAAC,MAAc,CAAC,EAAE,WAAW,GAAG,CAAC,CAAC;AAAA,MACvE;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,IAAI,IAAI,QAAQ,CAAC;AACrC;AAEO,SAAS,2BAA2B,SAAiB;AAC1D,QAAM,WAAqB,CAAC;AAC5B,MAAI,aAAa;AACjB,MAAI,iBAAiB;AAErB,aAAW,QAAQ,QAAQ,MAAM,IAAI,GAAG;AACtC,UAAM,UAAU,KAAK,KAAK;AAE1B,QAAI,CAAC,WAAW,QAAQ,WAAW,GAAG,GAAG;AACvC;AAAA,IACF;AAEA,UAAM,WAAW,KAAK,MAAM,4BAA4B;AACxD,QAAI,UAAU;AACZ,uBAAiB,SAAS,CAAC,EAAE;AAC7B,mBAAa,SAAS,CAAC,MAAM;AAC7B;AAAA,IACF;AAEA,QAAI,CAAC,YAAY;AACf;AAAA,IACF;AAEA,UAAM,YAAY,KAAK,MAAM,6BAA6B;AAC1D,QAAI,CAAC,aAAa,UAAU,CAAC,EAAE,UAAU,gBAAgB;AACvD;AAAA,IACF;AAEA,aAAS,KAAK,UAAU,CAAC,EAAE,KAAK,EAAE,QAAQ,gBAAgB,EAAE,CAAC;AAAA,EAC/D;AAEA,SAAO;AACT;;;AN3KA,IAAM,oBAAoBC,GAAE,OAAO;AAAA,EACjC,KAAKA,GAAE,OAAO,EAAE,SAAS;AAAA,EACzB,UAAUA,GAAE,QAAQ,EAAE,SAAS;AAAA,EAC/B,YAAYA,GAAE,QAAQ,EAAE,SAAS;AACnC,CAAC;AAED,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcvB,IAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsBd,IAAM,OAAO,IAAI,QAAQ,EAC7B,KAAK,MAAM,EACX,MAAM,QAAQ,EACd,YAAY,iCAAiC,EAC7C,SAAS,SAAS,mBAAmB,EACrC,OAAO,eAAe,mBAAmB,EACzC,OAAO,cAAc,6CAA6C,EAClE,OAAO,iBAAiB,0BAA0B,EAClD,OAAO,OAAO,KAAK,SAAS;AAC3B,MAAI;AACF,UAAM,UAAU,kBAAkB,MAAM,IAAI;AAC5C,UAAM,MAAMC,SAAQ,OAAO,QAAQ,OAAO,QAAQ,IAAI,CAAC;AAYvD,WAAO,IAAI,SAAS,GAAG;AAEvB,UAAM,UAAU,cAAc,oBAAoB,EAAE,MAAM;AAE1D,UAAM,YAAY,gBAAgB,GAAG;AACrC,QAAI,CAAC,WAAW;AAEd,UAAI,MAAM,eAAe,GAAG,GAAG;AAC7B,cAAM,UAAU,MAAM,mBAAmB,GAAG;AAC5C,YAAI,QAAQ,SAAS,GAAG;AACtB,kBAAQ,KAAK;AACb,gCAAsB,QAAQ,OAAO;AACrC,kBAAQ,KAAK,CAAC;AAAA,QAChB;AAAA,MACF;AACA,cAAQ;AAAA,QACN;AAAA,MACF;AACA,cAAQ,KAAK,CAAC;AAAA,IAChB;AAMA,UAAM,kBAAkB,iBAAiB,GAAG;AAC5C,UAAM,aAAa,mBAAmB;AAEtC,UAAM,SAAS,iBAAiB;AAChC,WAAO,YAAY;AACnB,WAAO,UAAU;AAEjB,UAAM,MAAMA,SAAQ,KAAK,WAAW,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AAEhE,UAAM,eAAeA,SAAQ,KAAK,YAAY,WAAW;AACzD,QAAI;AACF,YAAM,OAAO,cAAc,UAAU,IAAI;AAAA,IAC3C,QAAQ;AACN,YAAM,UAAU,cAAc,cAAc;AAAA,IAC9C;AAGA,UAAM,eAAeA,SAAQ,KAAK,WAAW,WAAW;AACxD,UAAM,MAAM,cAAc,EAAE,WAAW,KAAK,CAAC;AAC7C,UAAM,UAAUA,SAAQ,cAAc,UAAU,GAAG,YAAY;AAE/D,UAAM;AAAA,MACJA,SAAQ,KAAK,WAAW;AAAA,MACxB,KAAK,UAAU,QAAQ,MAAM,CAAC;AAAA,IAChC;AAEA,YAAQ,QAAQ,mBAAmB;AAEnC,WAAO,MAAM;AACb,WAAO,QAAQ,UAAU;AACzB,WAAO,IAAI,kCAA6B;AACxC,WAAO,IAAI,KAAK,UAAU,+BAA0B;AACpD,WAAO,IAAI,iDAA4C;AACvD,WAAO,MAAM;AACb,WAAO,KAAK,aAAa;AACzB,WAAO,IAAI,4CAA4C;AACvD,WAAO,IAAI,gDAAgD;AAC3D,WAAO,MAAM;AAEb,QAAI,CAAC,iBAAiB;AACpB,aAAO,KAAK,uDAAkD;AAAA,IAChE;AAAA,EACF,SAAS,OAAO;AACd,gBAAY,KAAK;AAAA,EACnB;AACF,CAAC;;;AQ1JH,SAAS,WAAAC,gBAAe;AACxB,SAAS,WAAAC,gBAAe;AACxB,SAAS,KAAAC,UAAS;AAKlB,IAAM,mBAAmBC,GAAE,OAAO;AAAA,EAChC,KAAKA,GAAE,OAAO,EAAE,SAAS;AAAA,EACzB,MAAMA,GAAE,OAAO,OAAO,EAAE,SAAS;AACnC,CAAC;AAOM,IAAM,MAAM,IAAIC,SAAQ,EAC5B,KAAK,KAAK,EACV,YAAY,sCAAsC,EAClD,OAAO,eAAe,mBAAmB,EACzC,OAAO,qBAAqB,kBAAkB,EAC9C,OAAO,OAAO,SAAS;AACtB,MAAI;AACF,UAAM,UAAU,iBAAiB,MAAM,IAAI;AAC3C,UAAM,MAAMC,SAAQ,QAAQ,OAAO,QAAQ,IAAI,CAAC;AAEhD,WAAO,IAAI,SAAS,GAAG;AAEvB,UAAM,SAAS,MAAM,UAAU,GAAG;AAClC,QAAI,CAAC,QAAQ;AACX,aAAO,MAAM,0CAA0C;AACvD,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,WAAO,KAAK,sBAAsB,OAAO,OAAO,EAAE;AAClD,WAAO,KAAK,cAAc,OAAO,SAAS,EAAE;AAC5C,WAAO,MAAM;AACb,WAAO,KAAK,yDAAyD;AACrE,WAAO,KAAK,eAAe;AAC3B,WAAO,MAAM;AACb,WAAO,KAAK,4DAA4D;AACxE,WAAO,KAAK,KAAK,OAAO,OAAO,EAAE;AAAA,EACnC,SAAS,OAAO;AACd,gBAAY,KAAK;AAAA,EACnB;AACF,CAAC;;;ATxCH,QAAQ,GAAG,UAAU,MAAM,QAAQ,KAAK,CAAC,CAAC;AAC1C,QAAQ,GAAG,WAAW,MAAM,QAAQ,KAAK,CAAC,CAAC;AAE3C,eAAe,OAAO;AACpB,QAAM,UAAU,IAAIC,SAAQ,EACzB,KAAK,MAAM,EACX,YAAY,0CAA0C,EACtD,QAAQ,OAAO;AAElB,UAAQ,WAAW,IAAI;AACvB,UAAQ,WAAW,GAAG;AAEtB,UAAQ,MAAM;AAChB;AAEA,KAAK;","names":["Command","resolve","z","text","kleur","z","resolve","Command","resolve","z","z","Command","resolve","Command"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/commands/init.ts","../src/utils/logger.ts","../src/utils/spinner.ts","../src/utils/handle-error.ts","../src/utils/update-dependencies.ts","../src/utils/get-package-manager.ts","../src/utils/get-config.ts","../src/utils/get-project-info.ts","../src/utils/get-monorepo-info.ts","../src/utils/highlighter.ts","../src/utils/templates.ts","../src/commands/dev.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from \"commander\";\nimport { init } from \"./commands/init.js\";\nimport { dev } from \"./commands/dev.js\";\n\nprocess.on(\"SIGINT\", () => process.exit(0));\nprocess.on(\"SIGTERM\", () => process.exit(0));\n\nasync function main() {\n const program = new Command()\n .name(\"sage\")\n .description(\"AI documentation agent for your codebase\")\n .version(\"0.0.1\");\n\n program.addCommand(init);\n program.addCommand(dev);\n\n program.parse();\n}\n\nmain();\n","import { Command } from \"commander\";\nimport { constants } from \"fs\";\nimport { access, mkdir, writeFile } from \"fs/promises\";\nimport { resolve } from \"path\";\nimport { z } from \"zod\";\nimport { logger } from \"../utils/logger.js\";\nimport { createSpinner } from \"../utils/spinner.js\";\nimport { handleError } from \"../utils/handle-error.js\";\nimport { ensureFlueDeps } from \"../utils/update-dependencies.js\";\nimport { getDefaultConfig } from \"../utils/get-config.js\";\nimport {\n detectContentDir,\n detectFramework,\n} from \"../utils/get-project-info.js\";\nimport {\n formatMonorepoMessage,\n getMonorepoTargets,\n isMonorepoRoot,\n} from \"../utils/get-monorepo-info.js\";\nimport {\n CI_WORKFLOW,\n FLUE_CONFIG,\n INDEX_MDX,\n SAGE_WORKFLOW,\n} from \"../utils/templates.js\";\n\nconst initOptionsSchema = z.object({\n cwd: z.string().optional(),\n defaults: z.boolean().optional(),\n helpCenter: z.boolean().optional(),\n});\n\n// TODO: we need a robust test fixture for both mono-repo, non-monorepo, next, tanstack; shadcn has all figured that out so we can just copy that approach for this; doing it manually is painstaking and always leads to some error cuz i have to do it and i'm lazy\n//\n// DO this before implementing second issue;\nexport const init = new Command()\n .name(\"init\")\n .alias(\"create\")\n .description(\"initialize sage in your project\")\n .argument(\"[dir]\", \"project directory\")\n .option(\"--cwd <cwd>\", \"working directory\")\n .option(\"--defaults\", \"use default configuration without prompting\")\n .option(\"--help-center\", \"add Fumadocs help center\")\n .action(async (dir, opts) => {\n try {\n const options = initOptionsSchema.parse(opts);\n const cwd = resolve(dir || options.cwd || process.cwd());\n\n // pnpm --filter=@sagebase/cli start:dev init ./apps/web\n // {} /Users/bishalneupane/Desktop/myprojects/sagebase/packages/cli/apps/web\n // Relative path does not work, have to pass absolute stuff; maybe fix this?? prioritize maybe the process.cwd more?? that would probably create yet another set of problems right????\n // This worked: bun sage init ~/Desktop/myprojects/sagebase/apps/web\n //\n // init didn't add any cli script into package.json how is the user supposed to run dev now\n // maybe again same npx sage dev command?? right\n //\n //\n // Would it be possible to avoid creating yet anaother config file cuz we're literally drowing in them\n\n const spinner = createSpinner(\"Setting up Sage...\").start();\n\n const framework = detectFramework(cwd);\n if (!framework) {\n // Check if we're in a monorepo root before failing.\n if (await isMonorepoRoot(cwd)) {\n const targets = await getMonorepoTargets(cwd);\n if (targets.length > 0) {\n spinner.stop();\n formatMonorepoMessage(\"init\", targets);\n process.exit(1);\n }\n }\n spinner.fail(\n \"Could not detect framework. Only Next.js and TanStack Start are supported.\",\n );\n process.exit(1);\n }\n\n // TODO: maybe here we could use the default fuma docs cli here instead of trying to do this stuff manaully we could have more options from there; but that means we are getting joined with fuma-docs so there is a bit of trade-off there; we won't be able to control the whole setting up experience\n // how does the better-t-setup does this things well??\n //\n // Better idea is to let the user do the setup themselves directly using the fuma-docs cli and configure everything they want, and we don't deal with all that complexity ourselves and doing that will make ur repo both easy and we'll not be tied to fuma-docs as well;\n const existingContent = detectContentDir(cwd);\n const contentDir = existingContent || \"content/docs\";\n\n const config = getDefaultConfig();\n config.framework = framework;\n config.content = contentDir;\n\n await mkdir(resolve(cwd, \"content\", \"docs\"), { recursive: true });\n\n const indexMdxPath = resolve(cwd, contentDir, \"index.mdx\");\n try {\n await access(indexMdxPath, constants.F_OK);\n } catch {\n await writeFile(indexMdxPath, INDEX_MDX);\n }\n\n // Even in mono repo it adds the workflow to apps/web/.github** is that the right way?? Need answer on that??\n // Scaffold Flue workflow\n // It has to be workflows cuz that's what flue cli expects\n const flueWorkflowsDir = resolve(cwd, \"src\", \"workflows\");\n await mkdir(flueWorkflowsDir, { recursive: true });\n await writeFile(resolve(flueWorkflowsDir, \"sage.ts\"), SAGE_WORKFLOW);\n await writeFile(resolve(cwd, \"flue.config.ts\"), FLUE_CONFIG);\n\n // Scaffold CI workflow\n const workflowsDir = resolve(cwd, \".github\", \"workflows\");\n await mkdir(workflowsDir, { recursive: true });\n await writeFile(resolve(workflowsDir, \"sage.yml\"), CI_WORKFLOW);\n\n await writeFile(\n resolve(cwd, \"sage.json\"),\n JSON.stringify(config, null, 2),\n );\n\n await ensureFlueDeps(cwd);\n\n spinner.succeed(\"Sage initialized!\");\n\n logger.break();\n logger.success(\"Created:\");\n logger.log(` sage.json — configuration`);\n logger.log(` src/workflows/ — Flue agent workflow`);\n logger.log(` ${contentDir}/ — documentation source`);\n logger.log(` .github/workflows/sage.yml — CI workflow`);\n logger.break();\n logger.info(\"Next steps:\");\n logger.log(\" Run sage dev to preview your help center\");\n logger.log(\" Run sage run to generate documentation from code changes\");\n logger.log(\" Edit src/workflows/sage.ts to customize the agent\");\n logger.break();\n\n if (!existingContent) {\n logger.info(\"Created: content/docs/index.mdx — sample article\");\n }\n } catch (error) {\n handleError(error);\n }\n });\n","import kleur from \"kleur\"\n\nexport const logger = {\n error(...args: unknown[]) {\n console.log(kleur.red(\"✗\"), ...args)\n },\n warn(...args: unknown[]) {\n console.log(kleur.yellow(\"⚠\"), ...args)\n },\n info(...args: unknown[]) {\n console.log(kleur.cyan(\"ℹ\"), ...args)\n },\n success(...args: unknown[]) {\n console.log(kleur.green(\"✓\"), ...args)\n },\n log(...args: unknown[]) {\n console.log(...args)\n },\n break() {\n console.log(\"\")\n },\n}\n","import ora from \"ora\"\n\nexport function createSpinner(text: string) {\n const spinner = ora({\n text,\n color: \"cyan\",\n })\n\n return {\n start() {\n spinner.start()\n return this\n },\n succeed(text?: string) {\n spinner.succeed(text)\n return this\n },\n fail(text?: string) {\n spinner.fail(text)\n return this\n },\n stop() {\n spinner.stop()\n return this\n },\n set text(val: string) {\n spinner.text = val\n },\n }\n}\n","import { logger } from \"./logger.js\"\n\nexport function handleError(error: unknown) {\n if (error instanceof Error) {\n logger.error(error.message)\n } else {\n logger.error(String(error))\n }\n\n process.exit(1)\n}\n","import { existsSync, readFileSync } from \"fs\";\nimport { join } from \"path\";\nimport { execa } from \"execa\";\nimport {\n getPackageManager,\n type PackageManager,\n} from \"./get-package-manager.js\";\nimport { createSpinner } from \"./spinner.js\";\n\nconst FLUE_DEPS: string[] = [];\nconst FLUE_DEV_DEPS = [\"@flue/runtime\", \"@flue/cli\", \"valibot\"];\n\nfunction getInstalledDeps(cwd: string): Set<string> {\n try {\n const pkgPath = join(cwd, \"package.json\");\n if (!existsSync(pkgPath)) return new Set();\n\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\"));\n return new Set([\n ...Object.keys(pkg.dependencies ?? {}),\n ...Object.keys(pkg.devDependencies ?? {}),\n ]);\n } catch {\n return new Set();\n }\n}\n\nfunction getMissingDeps(cwd: string): { deps: string[]; devDeps: string[] } {\n const installed = getInstalledDeps(cwd);\n return {\n deps: FLUE_DEPS.filter((dep) => !installed.has(dep)),\n devDeps: FLUE_DEV_DEPS.filter((dep) => !installed.has(dep)),\n };\n}\n\nasync function installWithPackageManager(\n pm: PackageManager,\n deps: string[],\n devDeps: string[],\n cwd: string,\n) {\n if (pm === \"npm\") {\n if (deps.length) await execa(\"npm\", [\"install\", ...deps], { cwd });\n if (devDeps.length)\n await execa(\"npm\", [\"install\", \"-D\", ...devDeps], { cwd });\n } else {\n if (deps.length) await execa(pm, [\"add\", ...deps], { cwd });\n if (devDeps.length) await execa(pm, [\"add\", \"-D\", ...devDeps], { cwd });\n }\n}\n\nexport async function ensureFlueDeps(cwd: string) {\n const { deps, devDeps } = getMissingDeps(cwd);\n const allMissing = [...deps, ...devDeps];\n if (allMissing.length === 0) return;\n\n const spinner = createSpinner(\n `Installing ${allMissing.join(\", \")}...`,\n ).start();\n\n try {\n const pm = await getPackageManager(cwd);\n await installWithPackageManager(pm, deps, devDeps, cwd);\n spinner.succeed();\n } catch (error) {\n spinner.fail(\"Failed to install Flue dependencies\");\n throw error;\n }\n}\n","import { existsSync } from \"fs\"\nimport { join } from \"path\"\n\nexport type PackageManager = \"npm\" | \"pnpm\" | \"yarn\" | \"bun\"\n\nexport async function getPackageManager(\n cwd: string\n): Promise<PackageManager> {\n if (existsSync(join(cwd, \"pnpm-lock.yaml\"))) return \"pnpm\"\n if (existsSync(join(cwd, \"yarn.lock\"))) return \"yarn\"\n if (existsSync(join(cwd, \"bun.lock\"))) return \"bun\"\n if (existsSync(join(cwd, \"package-lock.json\"))) return \"npm\"\n\n const userAgent = process.env.npm_config_user_agent || \"\"\n if (userAgent.startsWith(\"pnpm\")) return \"pnpm\"\n if (userAgent.startsWith(\"yarn\")) return \"yarn\"\n if (userAgent.startsWith(\"bun\")) return \"bun\"\n\n return \"npm\"\n}\n","import { cosmiconfig } from \"cosmiconfig\"\nimport { z } from \"zod\"\n\nconst explorer = cosmiconfig(\"sage\", {\n searchPlaces: [\"sage.json\", \"sage.config.json\", \"sage.config.ts\"],\n})\n\nexport const sageConfigSchema = z.object({\n $schema: z.string().optional(),\n framework: z.enum([\"nextjs\", \"tanstack-start\"]).default(\"nextjs\"),\n content: z.string().default(\"content/docs\"),\n agent: z\n .object({\n model: z.string().default(\"anthropic/claude-sonnet-4-6\"),\n provider: z.string().default(\"anthropic\"),\n })\n .default({}),\n ci: z\n .object({\n trigger: z.string().default(\"pr-merge\"),\n workflow: z.string().default(\".github/workflows/sage.yml\"),\n })\n .default({}),\n})\n\nexport type SageConfig = z.infer<typeof sageConfigSchema>\n\nexport async function getConfig(cwd?: string): Promise<SageConfig | null> {\n try {\n const result = await explorer.search(cwd)\n if (!result?.config) return null\n return sageConfigSchema.parse(result.config)\n } catch {\n return null\n }\n}\n\nexport function getDefaultConfig(): SageConfig {\n return sageConfigSchema.parse({})\n}\n","import { existsSync } from \"fs\"\nimport { resolve } from \"path\"\n\nexport type Framework = \"nextjs\" | \"tanstack-start\" | null\n\nexport function detectFramework(cwd: string): Framework {\n if (existsSync(resolve(cwd, \"next.config.js\")) || existsSync(resolve(cwd, \"next.config.ts\")) || existsSync(resolve(cwd, \"next.config.mjs\"))) {\n return \"nextjs\"\n }\n if (existsSync(resolve(cwd, \"vite.config.ts\")) || existsSync(resolve(cwd, \"vite.config.js\"))) {\n const hasRouter = existsSync(resolve(cwd, \"app.config.ts\"))\n if (hasRouter) return \"tanstack-start\"\n }\n return null\n}\n\nexport function detectContentDir(cwd: string): string | null {\n const candidates = [\"content/docs\", \"src/content/docs\"]\n for (const dir of candidates) {\n if (existsSync(resolve(cwd, dir))) {\n return dir\n }\n }\n return null\n}\n","import path from \"path\"\nimport { highlighter } from \"../utils/highlighter.js\"\nimport { logger } from \"../utils/logger.js\"\nimport fg from \"fast-glob\"\nimport fs from \"fs-extra\"\n\nconst FRAMEWORK_CONFIG_FILES = [\n \"next.config.*\",\n \"vite.config.*\",\n \"astro.config.*\",\n \"remix.config.*\",\n \"nuxt.config.*\",\n \"svelte.config.*\",\n \"gatsby-config.*\",\n \"angular.json\",\n]\n\n// Checks for workspace signals at the given directory.\nexport async function isMonorepoRoot(cwd: string) {\n // pnpm workspaces.\n if (fs.existsSync(path.resolve(cwd, \"pnpm-workspace.yaml\"))) {\n return true\n }\n\n // npm/yarn workspaces.\n const packageJsonPath = path.resolve(cwd, \"package.json\")\n if (fs.existsSync(packageJsonPath)) {\n try {\n const packageJson = await fs.readJson(packageJsonPath)\n if (packageJson.workspaces) {\n return true\n }\n } catch {\n // Ignore parse errors.\n }\n }\n\n // Lerna.\n if (fs.existsSync(path.resolve(cwd, \"lerna.json\"))) {\n return true\n }\n\n // Nx.\n if (fs.existsSync(path.resolve(cwd, \"nx.json\"))) {\n return true\n }\n\n return false\n}\n\n// Finds app directories in a monorepo that contain framework configs or sage.json.\nexport async function getMonorepoTargets(cwd: string) {\n const patterns = await getWorkspacePatterns(cwd)\n\n if (!patterns.length) {\n return []\n }\n\n // Resolve patterns to directories.\n const dirs = await fg(patterns, {\n cwd,\n onlyDirectories: true,\n ignore: [\"**/node_modules/**\"],\n })\n\n const targets: { name: string; hasConfig: boolean }[] = []\n\n for (const dir of dirs) {\n const fullPath = path.resolve(cwd, dir)\n\n // Check if it has a package.json (it's an actual workspace).\n if (!fs.existsSync(path.resolve(fullPath, \"package.json\"))) {\n continue\n }\n\n const hasSageJson = fs.existsSync(\n path.resolve(fullPath, \"sage.json\")\n )\n\n // Check for framework config files.\n const hasFrameworkConfig = FRAMEWORK_CONFIG_FILES.some((pattern) => {\n const matches = fg.sync(pattern, {\n cwd: fullPath,\n dot: true,\n })\n return matches.length > 0\n })\n\n if (hasSageJson || hasFrameworkConfig) {\n targets.push({\n name: dir,\n hasConfig: hasSageJson,\n })\n }\n }\n\n return targets\n}\n\n// Formats and logs the monorepo detection message.\nexport function formatMonorepoMessage(\n command: string,\n targets: { name: string; hasConfig: boolean }[],\n options?: {\n cwdFlag?: string\n }\n) {\n const cwdFlag = options?.cwdFlag ?? \"--cwd\"\n\n logger.break()\n logger.log(\n `It looks like you are running ${highlighter.info(\n command\n )} from a monorepo root.`\n )\n logger.log(\n `To use sage in a specific workspace, use the ${highlighter.info(\n cwdFlag\n )} flag:`\n )\n logger.break()\n\n for (const target of targets) {\n logger.log(` sage ${command} ${cwdFlag} ${target.name}`)\n }\n\n logger.break()\n}\n\nexport async function getWorkspacePatterns(cwd: string) {\n const patterns: string[] = []\n\n // Read pnpm-workspace.yaml.\n const pnpmWorkspacePath = path.resolve(cwd, \"pnpm-workspace.yaml\")\n if (fs.existsSync(pnpmWorkspacePath)) {\n const content = await fs.readFile(pnpmWorkspacePath, \"utf8\")\n patterns.push(...parsePnpmWorkspacePackages(content))\n }\n\n // Read package.json workspaces.\n const packageJsonPath = path.resolve(cwd, \"package.json\")\n if (fs.existsSync(packageJsonPath)) {\n try {\n const packageJson = await fs.readJson(packageJsonPath)\n const workspaces = Array.isArray(packageJson.workspaces)\n ? packageJson.workspaces\n : packageJson.workspaces?.packages\n if (Array.isArray(workspaces)) {\n patterns.push(...workspaces.filter((w: string) => !w.startsWith(\"!\")))\n }\n } catch {\n // Ignore parse errors.\n }\n }\n\n return Array.from(new Set(patterns))\n}\n\nexport function parsePnpmWorkspacePackages(content: string) {\n const patterns: string[] = []\n let inPackages = false\n let packagesIndent = 0\n\n for (const line of content.split(\"\\n\")) {\n const trimmed = line.trim()\n\n if (!trimmed || trimmed.startsWith(\"#\")) {\n continue\n }\n\n const keyMatch = line.match(/^(\\s*)([A-Za-z0-9_-]+)\\s*:/)\n if (keyMatch) {\n packagesIndent = keyMatch[1].length\n inPackages = keyMatch[2] === \"packages\"\n continue\n }\n\n if (!inPackages) {\n continue\n }\n\n const itemMatch = line.match(/^(\\s*)-\\s*(.+?)\\s*(?:#.*)?$/)\n if (!itemMatch || itemMatch[1].length <= packagesIndent) {\n continue\n }\n\n patterns.push(itemMatch[2].trim().replace(/^[\"']|[\"']$/g, \"\"))\n }\n\n return patterns\n}\n","import kleur from \"kleur\"\n\nexport const highlighter = {\n error: kleur.red,\n warn: kleur.yellow,\n info: kleur.cyan,\n success: kleur.green,\n highlight: kleur.bold,\n dim: kleur.dim,\n code: kleur.italic,\n}\n","export const INDEX_MDX = `---\ntitle: Welcome\ndescription: Welcome to our help center\n---\n\n# Welcome\n\nThis help center is maintained by Sage — an AI agent that keeps your documentation accurate as you ship.\n\n## Getting started\n\nReplace this sample content with your own documentation.\n`;\n\nexport const SAGE_WORKFLOW = `import { defineAgent, defineWorkflow } from \"@flue/runtime\";\nimport { local } from \"@flue/runtime/node\";\nimport * as v from \"valibot\";\n\nconst agent = defineAgent(() => ({\n sandbox: local({\n env: {\n GH_TOKEN: process.env.GH_TOKEN,\n },\n }),\n model: process.env.SAGE_MODEL ?? \"anthropic/claude-sonnet-4-6\",\n instructions: \\`You are Sage, an AI documentation agent. Your job is to analyze code changes and update documentation.\n\n## Frontmatter requirements\nEvery MDX file MUST start with valid YAML frontmatter:\n---\ntitle: Page Title\ndescription: Brief description\n---\n\n## Instructions\n1. Analyze the code diff to understand what changed\n2. Create new MDX files for new features/modules\n3. Update existing MDX files when related code changes\n4. Use clear, concise technical writing\n5. Include code examples where appropriate\n\n## Output format\nReturn a JSON array of file updates. ONLY include files that need creating or updating:\n[\n {\n \"path\": \"relative/path/from-content-dir/file.mdx\",\n \"content\": \"---\\\\\\\\ntitle: ...\\\\\\\\ndescription: ...\\\\\\\\n---\\\\\\\\n\\\\\\\\nFull MDX content here\"\n }\n]\\`,\n}));\n\nexport default defineWorkflow({\n agent,\n input: v.object({\n contentDir: v.optional(v.string(), \"content/docs\"),\n diffSinceSha: v.optional(v.string()),\n }),\n output: v.object({\n filesUpdated: v.number(),\n prUrl: v.optional(v.string()),\n }),\n async run({ harness, input, log }) {\n const session = await harness.session();\n const contentDir = input.contentDir;\n\n // 1. Get git diff\n const mergeSha =\n input.diffSinceSha ||\n (await session.shell('git log --merges -1 --format=\"%H\"')).stdout.trim();\n\n const diff = await session.shell(\n \\`git diff \\${mergeSha}..HEAD --diff-filter=ACMR -- .\\`,\n );\n\n if (!diff.stdout.trim()) {\n log.info(\"no code changes detected — nothing to document\");\n return { filesUpdated: 0 };\n }\n\n const changedFiles = (\n await session.shell(\n \\`git diff \\${mergeSha}..HEAD --diff-filter=ACMR --name-only -- .\\`,\n )\n ).stdout\n .trim()\n .split(\"\\\\n\")\n .filter(Boolean);\n\n // 2. Read existing docs\n const existingDocs = (\n await session.shell(\n \\`find \\${contentDir} -name '*.mdx' -type f 2>/dev/null | sort\\`,\n )\n ).stdout\n .trim()\n .split(\"\\\\n\")\n .filter(Boolean);\n\n const existingContent: { path: string; content: string }[] = [];\n for (const doc of existingDocs) {\n const content = (await session.shell(\\`cat \"\\${doc}\" 2>/dev/null\\`)).stdout\n .trim()\n .slice(0, 2000);\n existingContent.push({ path: doc, content });\n }\n\n // 3. Call LLM to analyze diff and determine doc updates\n let existingContext = \"\";\n if (existingContent.length > 0) {\n existingContext =\n \"\\\\n\\\\n## Existing documentation\\\\n\" +\n existingContent\n .map((d) => \\`### \\${d.path}\\\\n\\\\\\`\\\\\\`\\\\\\`mdx\\\\n\\${d.content}\\\\n\\\\\\`\\\\\\`\\\\\\`\\`)\n .join(\"\\\\n\");\n }\n\n const userMessage = \\`Documentation files live in \\\\\\`\\${contentDir}\\\\\\`. Create and update MDX files there.\\\\n\\\\nHere is the code diff:\\\\n\\\\n\\\\\\`\\\\\\`\\\\\\`diff\\\\n\\${diff.stdout.slice(0, 50000)}\\\\n\\\\\\`\\\\\\`\\\\\\`\\\\n\\\\nChanged files:\\\\n\\${changedFiles.map((f) => \\`- \\${f}\\`).join(\"\\\\n\")}\\${existingContext}\\`;\n\n const { data } = await session.prompt(userMessage, {\n result: v.object({\n updates: v.array(\n v.object({\n path: v.string(),\n content: v.string(),\n }),\n ),\n }),\n });\n\n if (data.updates.length === 0) {\n log.info(\"no documentation updates needed\");\n return { filesUpdated: 0 };\n }\n\n // 4. Write MDX files\n for (const update of data.updates) {\n const fullPath = \\`\\${contentDir}/\\${update.path}\\`;\n await harness.fs.writeFile(fullPath, update.content);\n log.info(\"wrote\", { path: fullPath });\n }\n\n // 5. Create PR\n const timestamp = Date.now();\n const branchName = \\`sage/docs-\\${timestamp}\\`;\n\n const branchExists = (\n await session.shell(\\`git branch --list '\\${branchName}'\\`)\n ).stdout.trim();\n\n if (branchExists) {\n await session.shell(\\`git checkout \"\\${branchName}\"\\`);\n } else {\n await session.shell(\\`git checkout -b \"\\${branchName}\"\\`);\n }\n\n await session.shell(\"git add -A\");\n\n const hasChanges = (\n await session.shell(\"git status --porcelain\")\n ).stdout.trim();\n if (!hasChanges) {\n log.info(\"no changes to commit\");\n return { filesUpdated: data.updates.length };\n }\n\n await session.shell(\"git commit -m 'docs: update documentation via Sage'\");\n await session.shell(\\`git push origin \"\\${branchName}\" -u\\`);\n\n const prUrl = (\n await session.shell(\n \\`gh pr create --title \"docs: update documentation via Sage\" --body \"Automated documentation update by Sage.\\\\n\\\\nFiles changed:\\\\n\\${data.updates.map((u: { path: string; content: string }) => \\`- \\\\\\`\\${u.path}\\\\\\`\\`).join(\"\\\\n\")}\"\\`,\n )\n ).stdout.trim();\n\n log.info(\"PR created\", { url: prUrl });\n\n return { filesUpdated: data.updates.length, prUrl };\n },\n});\n`;\n\nexport const FLUE_CONFIG = `import { defineConfig } from \"@flue/cli/config\";\n\nexport default defineConfig({\n target: \"node\",\n});\n`;\n\nexport const CI_WORKFLOW = `name: Sage Documentation\non:\n push:\n branches: [main]\njobs:\n sage:\n runs-on: ubuntu-latest\n permissions:\n contents: write\n pull-requests: write\n steps:\n - uses: actions/checkout@v4\n with:\n fetch-depth: 0\n - uses: actions/setup-node@v4\n with:\n node-version: 22\n - run: npm ci\n - name: Run Sage\n env:\n ANTHROPIC_API_KEY: \\${{ secrets.ANTHROPIC_API_KEY }}\n GH_TOKEN: \\${{ secrets.GITHUB_TOKEN }}\n run: npx flue run sage\n`;\n","import { Command } from \"commander\";\nimport { resolve } from \"path\";\nimport { z } from \"zod\";\nimport { handleError } from \"../utils/handle-error.js\";\nimport { getConfig } from \"../utils/get-config.js\";\nimport { logger } from \"../utils/logger.js\";\n\nconst devOptionsSchema = z.object({\n cwd: z.string().optional(),\n port: z.coerce.number().optional(),\n});\n\n// Right now it does not do shit apart from just informing user if config is fine, and giving instruction to setup of fumadocs properly\n//\n// Probably here see if fuma-docs is setup properly?? if not setup than give user quick command i.e fuma-docs setup script they can run; otherwise start the fuma-docs server from here;\n//\n// They can directly do that, and this is duplicate but still good to have here\nexport const dev = new Command()\n .name(\"dev\")\n .description(\"start the help center preview server\")\n .option(\"--cwd <cwd>\", \"working directory\")\n .option(\"-p, --port <port>\", \"port to serve on\")\n .action(async (opts) => {\n try {\n const options = devOptionsSchema.parse(opts);\n const cwd = resolve(options.cwd || process.cwd());\n\n const config = await getConfig(cwd);\n if (!config) {\n logger.error(\"No sage.json found. Run sage init first.\");\n process.exit(1);\n }\n\n logger.info(`Content directory: ${config.content}`);\n logger.info(`Framework: ${config.framework}`);\n logger.break();\n logger.info(\"To preview your help center, run your app's dev server:\");\n logger.info(\" npm run dev\");\n logger.break();\n logger.info(\"Make sure Fumadocs is configured in your app to read from:\");\n logger.info(` ${config.content}`);\n } catch (error) {\n handleError(error);\n }\n });\n"],"mappings":"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","names":["Command","resolve","z","text","existsSync","join","join","existsSync","existsSync","kleur","z","resolve","Command","resolve","z","z","Command","resolve","Command"]}
package/package.json CHANGED
@@ -1,11 +1,21 @@
1
1
  {
2
2
  "name": "@sagebase/cli",
3
- "version": "0.0.2",
3
+ "version": "1.0.1",
4
+ "description": "Scaffold, preview, and auto-update your help center documentation with Sage.",
5
+ "keywords": [
6
+ "sage",
7
+ "documentation",
8
+ "ai-agent",
9
+ "cli",
10
+ "docs",
11
+ "help-center",
12
+ "knowledge-base"
13
+ ],
4
14
  "license": "MIT",
5
15
  "type": "module",
6
16
  "repository": {
7
17
  "type": "git",
8
- "url": "https://github.com/BishalN/sagebase.git",
18
+ "url": "https://github.com/bishalneupane/sagebase.git",
9
19
  "directory": "packages/cli"
10
20
  },
11
21
  "publishConfig": {
@@ -20,21 +30,25 @@
20
30
  "dependencies": {
21
31
  "commander": "^13.0.0",
22
32
  "cosmiconfig": "^9.0.0",
33
+ "execa": "^9.6.1",
23
34
  "fast-glob": "^3.3.3",
24
- "zod": "^3.24.0",
25
- "prompts": "^2.4.2",
35
+ "fs-extra": "^11.3.0",
26
36
  "kleur": "^4.1.5",
27
37
  "ora": "^8.1.0",
28
- "fs-extra": "^11.3.0"
38
+ "prompts": "^2.4.2",
39
+ "zod": "^3.24.0"
29
40
  },
30
41
  "devDependencies": {
42
+ "@flue/cli": "1.0.0-beta.9",
43
+ "@flue/runtime": "1.0.0-beta.9",
31
44
  "@types/fs-extra": "^11.0.4",
32
45
  "@types/node": "^22.0.0",
33
46
  "@types/prompts": "^2.4.9",
34
47
  "tsup": "^8.3.0",
35
48
  "typescript": "^5.7.0",
36
- "vitest": "^3.0.0",
37
- "vite-tsconfig-paths": "^5.1.0"
49
+ "valibot": "^1.4.2",
50
+ "vite-tsconfig-paths": "^5.1.0",
51
+ "vitest": "^3.0.0"
38
52
  },
39
53
  "scripts": {
40
54
  "build": "tsup",