@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 +278 -20
- package/dist/index.js.map +1 -1
- package/package.json +21 -7
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 (
|
|
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 (
|
|
113
|
-
const hasRouter =
|
|
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 (
|
|
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/
|
|
280
|
-
var
|
|
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
|
|
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:
|
|
312
|
-
- 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,
|
|
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"),
|
|
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("
|
|
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":"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","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": "
|
|
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/
|
|
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
|
-
"
|
|
25
|
-
"prompts": "^2.4.2",
|
|
35
|
+
"fs-extra": "^11.3.0",
|
|
26
36
|
"kleur": "^4.1.5",
|
|
27
37
|
"ora": "^8.1.0",
|
|
28
|
-
"
|
|
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
|
-
"
|
|
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",
|