@sourcepress/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.turbo/turbo-test.log +46 -0
  3. package/bin/sourcepress.js +2 -0
  4. package/dist/__tests__/config.test.d.ts +2 -0
  5. package/dist/__tests__/config.test.d.ts.map +1 -0
  6. package/dist/__tests__/config.test.js +70 -0
  7. package/dist/__tests__/config.test.js.map +1 -0
  8. package/dist/commands/build.d.ts +3 -0
  9. package/dist/commands/build.d.ts.map +1 -0
  10. package/dist/commands/build.js +27 -0
  11. package/dist/commands/build.js.map +1 -0
  12. package/dist/commands/dev.d.ts +3 -0
  13. package/dist/commands/dev.d.ts.map +1 -0
  14. package/dist/commands/dev.js +21 -0
  15. package/dist/commands/dev.js.map +1 -0
  16. package/dist/commands/eval.d.ts +3 -0
  17. package/dist/commands/eval.d.ts.map +1 -0
  18. package/dist/commands/eval.js +40 -0
  19. package/dist/commands/eval.js.map +1 -0
  20. package/dist/commands/gaps.d.ts +3 -0
  21. package/dist/commands/gaps.d.ts.map +1 -0
  22. package/dist/commands/gaps.js +31 -0
  23. package/dist/commands/gaps.js.map +1 -0
  24. package/dist/commands/graph.d.ts +3 -0
  25. package/dist/commands/graph.d.ts.map +1 -0
  26. package/dist/commands/graph.js +33 -0
  27. package/dist/commands/graph.js.map +1 -0
  28. package/dist/commands/init.d.ts +2 -0
  29. package/dist/commands/init.d.ts.map +1 -0
  30. package/dist/commands/init.js +117 -0
  31. package/dist/commands/init.js.map +1 -0
  32. package/dist/commands/stale.d.ts +3 -0
  33. package/dist/commands/stale.d.ts.map +1 -0
  34. package/dist/commands/stale.js +35 -0
  35. package/dist/commands/stale.js.map +1 -0
  36. package/dist/commands/status.d.ts +4 -0
  37. package/dist/commands/status.d.ts.map +1 -0
  38. package/dist/commands/status.js +26 -0
  39. package/dist/commands/status.js.map +1 -0
  40. package/dist/commands/sync.d.ts +3 -0
  41. package/dist/commands/sync.d.ts.map +1 -0
  42. package/dist/commands/sync.js +22 -0
  43. package/dist/commands/sync.js.map +1 -0
  44. package/dist/config.d.ts +3 -0
  45. package/dist/config.d.ts.map +1 -0
  46. package/dist/config.js +42 -0
  47. package/dist/config.js.map +1 -0
  48. package/dist/engine-boot.d.ts +4 -0
  49. package/dist/engine-boot.d.ts.map +1 -0
  50. package/dist/engine-boot.js +15 -0
  51. package/dist/engine-boot.js.map +1 -0
  52. package/dist/index.d.ts +2 -0
  53. package/dist/index.d.ts.map +1 -0
  54. package/dist/index.js +133 -0
  55. package/dist/index.js.map +1 -0
  56. package/package.json +28 -0
  57. package/src/__tests__/config.test.ts +80 -0
  58. package/src/commands/build.ts +29 -0
  59. package/src/commands/dev.ts +27 -0
  60. package/src/commands/eval.ts +47 -0
  61. package/src/commands/gaps.ts +38 -0
  62. package/src/commands/graph.ts +41 -0
  63. package/src/commands/init.ts +123 -0
  64. package/src/commands/stale.ts +40 -0
  65. package/src/commands/status.ts +37 -0
  66. package/src/commands/sync.ts +28 -0
  67. package/src/config.ts +47 -0
  68. package/src/engine-boot.ts +17 -0
  69. package/src/index.ts +146 -0
  70. package/tsconfig.json +5 -0
@@ -0,0 +1,3 @@
1
+ import type { SourcePressConfig } from "@sourcepress/core";
2
+ export declare function loadConfig(cwd: string): Promise<SourcePressConfig | null>;
3
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAO3D,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAmC/E"}
package/dist/config.js ADDED
@@ -0,0 +1,42 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { validateConfig } from "@sourcepress/core";
5
+ async function importTsFile(fullPath) {
6
+ const { tsImport } = await import("tsx/esm/api");
7
+ return tsImport(pathToFileURL(fullPath).href, import.meta.url);
8
+ }
9
+ export async function loadConfig(cwd) {
10
+ const candidates = ["sourcepress.config.ts", "sourcepress.config.js"];
11
+ for (const name of candidates) {
12
+ const fullPath = join(cwd, name);
13
+ if (existsSync(fullPath)) {
14
+ let mod;
15
+ try {
16
+ if (name.endsWith(".ts")) {
17
+ mod = await importTsFile(fullPath);
18
+ }
19
+ else {
20
+ mod = await import(pathToFileURL(fullPath).href);
21
+ }
22
+ }
23
+ catch (err) {
24
+ throw new Error(`Failed to load ${name}: ${err instanceof Error ? err.message : String(err)}`);
25
+ }
26
+ let raw = mod != null && typeof mod === "object" && "default" in mod
27
+ ? mod.default
28
+ : mod;
29
+ // tsx wraps modules with an extra layer — unwrap if needed
30
+ if (raw != null && typeof raw === "object" && "default" in raw) {
31
+ raw = raw.default;
32
+ }
33
+ const result = validateConfig(raw);
34
+ if (!result.success) {
35
+ throw new Error(`Invalid sourcepress config in ${name}:\n${result.errors.map((e) => ` - ${e}`).join("\n")}`);
36
+ }
37
+ return result.data;
38
+ }
39
+ }
40
+ return null;
41
+ }
42
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGnD,KAAK,UAAU,YAAY,CAAC,QAAgB;IAC3C,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;IACjD,OAAO,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAChE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAW;IAC3C,MAAM,UAAU,GAAG,CAAC,uBAAuB,EAAE,uBAAuB,CAAC,CAAC;IACtE,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACjC,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1B,IAAI,GAAY,CAAC;YACjB,IAAI,CAAC;gBACJ,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;oBAC1B,GAAG,GAAG,MAAM,YAAY,CAAC,QAAQ,CAAC,CAAC;gBACpC,CAAC;qBAAM,CAAC;oBACP,GAAG,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC;gBAClD,CAAC;YACF,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACd,MAAM,IAAI,KAAK,CACd,kBAAkB,IAAI,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAC7E,CAAC;YACH,CAAC;YACD,IAAI,GAAG,GACN,GAAG,IAAI,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,SAAS,IAAI,GAAG;gBACzD,CAAC,CAAE,GAA4B,CAAC,OAAO;gBACvC,CAAC,CAAC,GAAG,CAAC;YACR,2DAA2D;YAC3D,IAAI,GAAG,IAAI,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,SAAS,IAAI,GAAG,EAAE,CAAC;gBAChE,GAAG,GAAI,GAA4B,CAAC,OAAO,CAAC;YAC7C,CAAC;YACD,MAAM,MAAM,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;YACnC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACrB,MAAM,IAAI,KAAK,CACd,iCAAiC,IAAI,MAAM,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC5F,CAAC;YACH,CAAC;YACD,OAAO,MAAM,CAAC,IAAyB,CAAC;QACzC,CAAC;IACF,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { SourcePressConfig } from "@sourcepress/core";
2
+ import type { EngineContext } from "@sourcepress/server";
3
+ export declare function bootEngine(config: SourcePressConfig): Promise<EngineContext>;
4
+ //# sourceMappingURL=engine-boot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine-boot.d.ts","sourceRoot":"","sources":["../src/engine-boot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,wBAAsB,UAAU,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,aAAa,CAAC,CAYlF"}
@@ -0,0 +1,15 @@
1
+ import { createEngine } from "@sourcepress/server";
2
+ export async function bootEngine(config) {
3
+ const githubToken = process.env.GITHUB_TOKEN;
4
+ if (!githubToken) {
5
+ console.error("Error: GITHUB_TOKEN environment variable is required.");
6
+ console.error("Set it: export GITHUB_TOKEN=ghp_...");
7
+ process.exit(1);
8
+ }
9
+ return createEngine({
10
+ config,
11
+ githubToken,
12
+ aiApiKey: process.env.AI_API_KEY,
13
+ });
14
+ }
15
+ //# sourceMappingURL=engine-boot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine-boot.js","sourceRoot":"","sources":["../src/engine-boot.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGnD,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,MAAyB;IACzD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IAC7C,IAAI,CAAC,WAAW,EAAE,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,uDAAuD,CAAC,CAAC;QACvE,OAAO,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;QACrD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,YAAY,CAAC;QACnB,MAAM;QACN,WAAW;QACX,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU;KAChC,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,133 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { build } from "./commands/build.js";
4
+ import { dev } from "./commands/dev.js";
5
+ import { evalCmd } from "./commands/eval.js";
6
+ import { gaps } from "./commands/gaps.js";
7
+ import { graph } from "./commands/graph.js";
8
+ import { init } from "./commands/init.js";
9
+ import { stale } from "./commands/stale.js";
10
+ import { status } from "./commands/status.js";
11
+ import { sync } from "./commands/sync.js";
12
+ import { loadConfig } from "./config.js";
13
+ import { bootEngine } from "./engine-boot.js";
14
+ function loadEnvFile(dir) {
15
+ const envPath = join(dir, ".env");
16
+ if (!existsSync(envPath))
17
+ return;
18
+ const content = readFileSync(envPath, "utf-8");
19
+ for (const line of content.split("\n")) {
20
+ const trimmed = line.trim();
21
+ if (!trimmed || trimmed.startsWith("#"))
22
+ continue;
23
+ const eqIndex = trimmed.indexOf("=");
24
+ if (eqIndex === -1)
25
+ continue;
26
+ const key = trimmed.slice(0, eqIndex).trim();
27
+ let value = trimmed.slice(eqIndex + 1).trim();
28
+ if ((value.startsWith('"') && value.endsWith('"')) ||
29
+ (value.startsWith("'") && value.endsWith("'"))) {
30
+ value = value.slice(1, -1);
31
+ }
32
+ if (!(key in process.env)) {
33
+ process.env[key] = value;
34
+ }
35
+ }
36
+ }
37
+ const COMMANDS_NEEDING_CONFIG = [
38
+ "dev",
39
+ "build",
40
+ "sync",
41
+ "graph",
42
+ "stale",
43
+ "eval",
44
+ "status",
45
+ "gaps",
46
+ ];
47
+ function printHelp() {
48
+ console.log("sourcepress — Open source Content Engine with knowledge graph\n");
49
+ console.log("Commands:");
50
+ console.log(" init Initialize a new SourcePress project");
51
+ console.log(" dev Start the Content Engine server");
52
+ console.log(" build Sync content + build site");
53
+ console.log(" sync Sync content from GitHub");
54
+ console.log(" graph Show/rebuild knowledge graph");
55
+ console.log(" stale List stale content");
56
+ console.log(" eval Run evals");
57
+ console.log(" status Health check");
58
+ console.log(" gaps List knowledge gaps");
59
+ console.log("\nOptions:");
60
+ console.log(" --help Show this help");
61
+ console.log(" --version Show version");
62
+ }
63
+ function printVersion() {
64
+ console.log("0.1.0");
65
+ }
66
+ async function main() {
67
+ loadEnvFile(process.cwd());
68
+ const args = process.argv.slice(2);
69
+ const command = args[0];
70
+ if (command === "init") {
71
+ init(process.cwd());
72
+ return;
73
+ }
74
+ if (!command || command === "--help") {
75
+ printHelp();
76
+ return;
77
+ }
78
+ if (command === "--version") {
79
+ printVersion();
80
+ return;
81
+ }
82
+ if (COMMANDS_NEEDING_CONFIG.includes(command)) {
83
+ let config;
84
+ try {
85
+ config = await loadConfig(process.cwd());
86
+ }
87
+ catch (err) {
88
+ console.error(err instanceof Error ? err.message : String(err));
89
+ process.exit(1);
90
+ }
91
+ if (!config) {
92
+ console.error("No sourcepress.config.ts found. Run `sourcepress init` first.");
93
+ process.exit(1);
94
+ }
95
+ const engine = await bootEngine(config);
96
+ // Command dispatch
97
+ switch (command) {
98
+ case "dev":
99
+ await dev(engine, args.slice(1));
100
+ break;
101
+ case "build":
102
+ await build(engine, process.cwd());
103
+ break;
104
+ case "sync":
105
+ await sync(engine);
106
+ break;
107
+ case "graph":
108
+ await graph(engine, args.slice(1));
109
+ break;
110
+ case "stale":
111
+ await stale(engine);
112
+ break;
113
+ case "eval":
114
+ await evalCmd(engine, args.slice(1));
115
+ break;
116
+ case "status":
117
+ await status(engine, config);
118
+ break;
119
+ case "gaps":
120
+ await gaps(engine);
121
+ break;
122
+ }
123
+ return;
124
+ }
125
+ console.error(`Unknown command: ${command}`);
126
+ console.error("Run `sourcepress --help` for usage.");
127
+ process.exit(1);
128
+ }
129
+ main().catch((err) => {
130
+ console.error(err instanceof Error ? err.message : String(err));
131
+ process.exit(1);
132
+ });
133
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@sourcepress/cli",
3
+ "version": "0.1.0",
4
+ "publishConfig": { "access": "public" },
5
+ "type": "module",
6
+ "bin": { "sourcepress": "./bin/sourcepress.js" },
7
+ "exports": {
8
+ ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" }
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "typecheck": "tsc --noEmit",
13
+ "clean": "rm -rf dist",
14
+ "test": "vitest run"
15
+ },
16
+ "dependencies": {
17
+ "@sourcepress/core": "workspace:*",
18
+ "@sourcepress/server": "workspace:*",
19
+ "@sourcepress/mcp": "workspace:*",
20
+ "@hono/node-server": "^1.13.0",
21
+ "tsx": "^4.0.0"
22
+ },
23
+ "devDependencies": {
24
+ "typescript": "^5.7.0",
25
+ "@types/node": "^22.0.0",
26
+ "vitest": "^3.0.0"
27
+ }
28
+ }
@@ -0,0 +1,80 @@
1
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
5
+ import { loadConfig } from "../config.js";
6
+
7
+ const VALID_CONFIG_JS = `
8
+ export default {
9
+ repository: { owner: "acme", repo: "content", branch: "main" },
10
+ ai: { provider: "anthropic", model: "claude-sonnet-4-5-20250514" },
11
+ collections: {
12
+ posts: {
13
+ name: "posts",
14
+ path: "content/posts/",
15
+ format: "mdx",
16
+ fields: { title: { type: "string", required: true } },
17
+ },
18
+ },
19
+ knowledge: { path: "knowledge/", graph: { backend: "local" } },
20
+ intent: { path: "intent/" },
21
+ };
22
+ `;
23
+
24
+ let tmpDir: string;
25
+
26
+ beforeEach(() => {
27
+ tmpDir = mkdtempSync(join(tmpdir(), "sp-cli-test-"));
28
+ });
29
+
30
+ afterEach(() => {
31
+ rmSync(tmpDir, { recursive: true, force: true });
32
+ });
33
+
34
+ describe("loadConfig", () => {
35
+ it("returns null when no config file exists", async () => {
36
+ const result = await loadConfig(tmpDir);
37
+ expect(result).toBeNull();
38
+ });
39
+
40
+ it("returns validated config when sourcepress.config.js exists", async () => {
41
+ writeFileSync(join(tmpDir, "sourcepress.config.js"), VALID_CONFIG_JS);
42
+ const config = await loadConfig(tmpDir);
43
+ expect(config).not.toBeNull();
44
+ expect(config?.repository.owner).toBe("acme");
45
+ expect(config?.repository.repo).toBe("content");
46
+ expect(config?.ai.provider).toBe("anthropic");
47
+ });
48
+
49
+ it("prefers sourcepress.config.ts over .js when both exist", async () => {
50
+ // .ts file would be tried first but can't be dynamically imported without tsx in test env,
51
+ // so we write a .ts file that is actually valid JS (no TS-only syntax) to verify ordering.
52
+ const tsContent = `export default {
53
+ repository: { owner: "ts-owner", repo: "ts-repo", branch: "main" },
54
+ ai: { provider: "anthropic", model: "claude-sonnet-4-5-20250514" },
55
+ collections: {
56
+ posts: {
57
+ name: "posts",
58
+ path: "content/posts/",
59
+ format: "mdx",
60
+ fields: { title: { type: "string", required: true } },
61
+ },
62
+ },
63
+ knowledge: { path: "knowledge/", graph: { backend: "local" } },
64
+ intent: { path: "intent/" },
65
+ };`;
66
+ writeFileSync(join(tmpDir, "sourcepress.config.ts"), tsContent);
67
+ writeFileSync(join(tmpDir, "sourcepress.config.js"), VALID_CONFIG_JS);
68
+ const config = await loadConfig(tmpDir);
69
+ // .ts is tried first — should load ts-owner
70
+ expect(config?.repository.owner).toBe("ts-owner");
71
+ });
72
+
73
+ it("throws on invalid config", async () => {
74
+ writeFileSync(
75
+ join(tmpDir, "sourcepress.config.js"),
76
+ `export default { repository: { owner: "" } };`,
77
+ );
78
+ await expect(loadConfig(tmpDir)).rejects.toThrow(/Invalid sourcepress config/);
79
+ });
80
+ });
@@ -0,0 +1,29 @@
1
+ import { execSync } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import type { EngineContext } from "@sourcepress/server";
5
+ import { sync } from "./sync.js";
6
+
7
+ export async function build(engine: EngineContext, cwd: string): Promise<void> {
8
+ // Step 1: sync content from GitHub
9
+ await sync(engine);
10
+
11
+ // Step 2: build site if site/ directory exists
12
+ const siteDir = join(cwd, "site");
13
+ if (existsSync(join(siteDir, "package.json"))) {
14
+ console.log("\nBuilding site...");
15
+ try {
16
+ execSync("npm run build", { cwd: siteDir, stdio: "inherit" });
17
+ console.log("Build complete.");
18
+ } catch (err) {
19
+ console.error("Site build failed.");
20
+ console.error(err instanceof Error ? err.message : String(err));
21
+ process.exit(1);
22
+ }
23
+ } else {
24
+ console.log("\nNo site/ directory found. Sync-only complete.");
25
+ console.log(
26
+ "To build a site, create a site/ directory with a package.json and `build` script.",
27
+ );
28
+ }
29
+ }
@@ -0,0 +1,27 @@
1
+ import { serve } from "@hono/node-server";
2
+ import { createApp } from "@sourcepress/server";
3
+ import type { EngineContext } from "@sourcepress/server";
4
+
5
+ export async function dev(engine: EngineContext, args: string[]): Promise<void> {
6
+ const portArg = args.find((a) => a.startsWith("--port="))?.split("=")[1];
7
+ const port = Number(process.env.PORT ?? portArg ?? 4321);
8
+
9
+ const app = createApp(engine);
10
+
11
+ console.log(`SourcePress engine running at http://localhost:${port}`);
12
+ console.log(`Health: http://localhost:${port}/api/health`);
13
+ console.log(`Content: http://localhost:${port}/api/content`);
14
+ console.log("\nMCP: npx sourcepress mcp (in another terminal)");
15
+ console.log("\nPress Ctrl+C to stop.\n");
16
+
17
+ const server = serve({ fetch: app.fetch, port });
18
+
19
+ process.on("SIGINT", () => {
20
+ console.log("\nShutting down...");
21
+ server.close(() => process.exit(0));
22
+ });
23
+
24
+ process.on("SIGTERM", () => {
25
+ server.close(() => process.exit(0));
26
+ });
27
+ }
@@ -0,0 +1,47 @@
1
+ import type { ContentFile } from "@sourcepress/core";
2
+ import type { EngineContext } from "@sourcepress/server";
3
+
4
+ export async function evalCmd(engine: EngineContext, args: string[]): Promise<void> {
5
+ const runLoop = args.includes("--run");
6
+
7
+ if (runLoop) {
8
+ console.log("Running eval loop (generate → judge → improve)...\n");
9
+ } else {
10
+ console.log("Evaluating content quality...\n");
11
+ console.log(" (Use --run to execute the full generate/improve loop)\n");
12
+ }
13
+
14
+ const collections = engine.listCollections();
15
+
16
+ if (collections.length === 0) {
17
+ console.log("No collections configured. Add collections to sourcepress.config.ts.");
18
+ return;
19
+ }
20
+
21
+ for (const name of collections) {
22
+ let files: ContentFile[];
23
+ try {
24
+ files = await engine.listContent(name);
25
+ } catch (err) {
26
+ console.error(` ${name}: error — ${err instanceof Error ? err.message : String(err)}`);
27
+ continue;
28
+ }
29
+
30
+ console.log(`${name}: ${files.length} file${files.length !== 1 ? "s" : ""}`);
31
+
32
+ for (const file of files) {
33
+ const score = file.frontmatter?.eval_score as number | undefined;
34
+
35
+ if (score !== undefined) {
36
+ const indicator = score >= 70 ? "pass" : "fail";
37
+ console.log(` [${indicator}] ${file.path} — score: ${score}`);
38
+ } else {
39
+ console.log(` [---] ${file.path} — not yet evaluated`);
40
+ }
41
+ }
42
+ }
43
+
44
+ if (runLoop) {
45
+ console.log("\nEval loop complete. Re-run `sourcepress eval` to see updated scores.");
46
+ }
47
+ }
@@ -0,0 +1,38 @@
1
+ import type { EngineContext } from "@sourcepress/server";
2
+
3
+ export async function gaps(engine: EngineContext): Promise<void> {
4
+ console.log("Scanning for knowledge gaps...\n");
5
+
6
+ // Gather all content files across collections
7
+ const collections = engine.listCollections();
8
+ const allContent = [];
9
+
10
+ for (const name of collections) {
11
+ try {
12
+ const files = await engine.listContent(name);
13
+ allContent.push(...files);
14
+ } catch (err) {
15
+ console.error(` ${name}: error — ${err instanceof Error ? err.message : String(err)}`);
16
+ }
17
+ }
18
+
19
+ // Use the knowledge engine to find gaps
20
+ const foundGaps = engine.knowledge.findGaps(allContent);
21
+
22
+ if (foundGaps.length === 0) {
23
+ console.log("No knowledge gaps detected. Knowledge coverage looks complete.");
24
+ return;
25
+ }
26
+
27
+ console.log(`Found ${foundGaps.length} knowledge gap${foundGaps.length !== 1 ? "s" : ""}:\n`);
28
+
29
+ for (const gap of foundGaps) {
30
+ console.log(` ${gap.entity_name} (${gap.entity_type})`);
31
+ console.log(` Knowledge files: ${gap.knowledge_file_count}`);
32
+ console.log(` Content files: ${gap.content_file_count}`);
33
+ console.log(` Reason: ${gap.reason}`);
34
+ console.log("");
35
+ }
36
+
37
+ console.log("Add knowledge files to the knowledge/ directory to fill these gaps.");
38
+ }
@@ -0,0 +1,41 @@
1
+ import type { EngineContext } from "@sourcepress/server";
2
+
3
+ export async function graph(engine: EngineContext, args: string[]): Promise<void> {
4
+ const rebuild = args.includes("--rebuild");
5
+
6
+ if (rebuild) {
7
+ console.log("Rebuilding knowledge graph...\n");
8
+ try {
9
+ const knowledgeFiles = await engine.knowledgeStore.list();
10
+ console.log(
11
+ ` Found ${knowledgeFiles.length} knowledge file${knowledgeFiles.length !== 1 ? "s" : ""}.`,
12
+ );
13
+ for (const file of knowledgeFiles) {
14
+ await engine.knowledge.ingest(file.path, file.body, file.source, file.source_url);
15
+ console.log(` Ingested: ${file.path}`);
16
+ }
17
+ console.log("\nKnowledge graph rebuilt.");
18
+ } catch (err) {
19
+ console.error(`Error rebuilding graph: ${err instanceof Error ? err.message : String(err)}`);
20
+ process.exit(1);
21
+ }
22
+ }
23
+
24
+ // Show graph stats
25
+ const g = await engine.knowledgeStore.loadGraph();
26
+ if (!g) {
27
+ console.log(
28
+ "Knowledge graph is empty. Add knowledge files and run `sourcepress graph --rebuild`.",
29
+ );
30
+ return;
31
+ }
32
+
33
+ const entityCount = g.entities instanceof Map ? g.entities.size : 0;
34
+ const relationCount = g.relations?.length ?? 0;
35
+ const clusterCount = g.clusters?.length ?? 0;
36
+
37
+ console.log("Knowledge graph stats:");
38
+ console.log(` Entities: ${entityCount}`);
39
+ console.log(` Relations: ${relationCount}`);
40
+ console.log(` Clusters: ${clusterCount}`);
41
+ }
@@ -0,0 +1,123 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ const DEFAULT_CONFIG = `import { defineConfig, collection, field, relation } from '@sourcepress/core'
5
+
6
+ export default defineConfig({
7
+ repository: {
8
+ owner: 'OWNER',
9
+ repo: 'REPO',
10
+ branch: 'main',
11
+ },
12
+
13
+ ai: {
14
+ provider: 'anthropic',
15
+ model: 'claude-sonnet-4-5-20250514',
16
+ },
17
+
18
+ collections: {},
19
+
20
+ knowledge: {
21
+ path: 'knowledge/',
22
+ graph: { backend: 'local' },
23
+ },
24
+
25
+ intent: {
26
+ path: 'intent/',
27
+ },
28
+
29
+ media: {
30
+ storage: 'git',
31
+ path: 'assets/',
32
+ registry: 'media.json',
33
+ },
34
+
35
+ jobs: {
36
+ backend: 'in-process',
37
+ },
38
+
39
+ evals: {
40
+ threshold: 70,
41
+ },
42
+
43
+ approval: {
44
+ provider: 'github-pr',
45
+ rules: {
46
+ 'content/*': 'pr',
47
+ 'knowledge/*': 'direct',
48
+ },
49
+ },
50
+
51
+ auth: {
52
+ provider: 'github',
53
+ },
54
+
55
+ cache: {
56
+ backend: 'sqlite',
57
+ },
58
+
59
+ sync: {
60
+ reconciliation_interval: '1h',
61
+ },
62
+ })
63
+ `;
64
+
65
+ const DEFAULT_TONE = `# Tone of Voice
66
+
67
+ Describe your brand's tone of voice here. This file guides AI content generation.
68
+
69
+ ## Principles
70
+ - Be clear and concise
71
+ - Use active voice
72
+ - Be professional but approachable
73
+
74
+ ## Forbidden words
75
+ - "revolutionary"
76
+ - "synergies"
77
+ - "holistic"
78
+ `;
79
+
80
+ const DEFAULT_JUDGE = `# Content Quality Judge
81
+
82
+ You are evaluating content quality. Score the content 0-100 based on:
83
+
84
+ 1. **Factual grounding** — Every claim should be traceable to a knowledge source
85
+ 2. **Tone match** — Does it follow the intent/tone-of-voice guidelines?
86
+ 3. **Specificity** — Concrete details, names, numbers — not generic filler
87
+ 4. **Structure** — Clear flow, appropriate length, good headings
88
+ 5. **Readability** — Clear language, no jargon without explanation
89
+
90
+ Return a single JSON object:
91
+ { "score": <0-100>, "reasoning": "<one paragraph explaining the score>" }
92
+ `;
93
+
94
+ export function init(targetDir: string): void {
95
+ const dirs = ["knowledge", "content", "intent", "evals/standards", "evals/prompts", "assets"];
96
+
97
+ for (const dir of dirs) {
98
+ const fullPath = join(targetDir, dir);
99
+ if (!existsSync(fullPath)) {
100
+ mkdirSync(fullPath, { recursive: true });
101
+ console.log(` Created ${dir}/`);
102
+ }
103
+ }
104
+
105
+ const files: Array<[string, string]> = [
106
+ ["sourcepress.config.ts", DEFAULT_CONFIG],
107
+ ["intent/tone-of-voice.md", DEFAULT_TONE],
108
+ ["evals/judge.md", DEFAULT_JUDGE],
109
+ ["media.json", "{}"],
110
+ ];
111
+
112
+ for (const [filePath, content] of files) {
113
+ const fullPath = join(targetDir, filePath);
114
+ if (!existsSync(fullPath)) {
115
+ writeFileSync(fullPath, content, "utf-8");
116
+ console.log(` Created ${filePath}`);
117
+ } else {
118
+ console.log(` Skipped ${filePath} (already exists)`);
119
+ }
120
+ }
121
+
122
+ console.log("\nSourcePress initialized. Edit sourcepress.config.ts to configure your project.");
123
+ }