create-keel-app 0.2.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 (37) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +38 -0
  3. package/dist/dep-emit.d.ts +6 -0
  4. package/dist/dep-emit.d.ts.map +1 -0
  5. package/dist/dep-emit.js +47 -0
  6. package/dist/dep-emit.js.map +1 -0
  7. package/dist/index.d.ts +3 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +63 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/scaffold.d.ts +13 -0
  12. package/dist/scaffold.d.ts.map +1 -0
  13. package/dist/scaffold.js +57 -0
  14. package/dist/scaffold.js.map +1 -0
  15. package/package.json +43 -0
  16. package/templates/ai-app/README.md.tpl +56 -0
  17. package/templates/ai-app/architecture/data.ts.tpl +76 -0
  18. package/templates/ai-app/extras/mcp.md.tpl +148 -0
  19. package/templates/ai-app/package.json.tpl +37 -0
  20. package/templates/ai-app/src/agents/thin-slice.ts.tpl +28 -0
  21. package/templates/ai-app/src/index.ts.tpl +20 -0
  22. package/templates/ai-app/src/tools/hello.test.ts.tpl +14 -0
  23. package/templates/ai-app/src/tools/hello.ts.tpl +9 -0
  24. package/templates/ai-app/src/tools/index.ts.tpl +6 -0
  25. package/templates/framework/README.md.tpl +31 -0
  26. package/templates/framework/package.json.tpl +30 -0
  27. package/templates/framework/src/index.test.ts.tpl +8 -0
  28. package/templates/framework/src/index.ts.tpl +1 -0
  29. package/templates/shared/.github/workflows/keel-verify.yml.tpl +29 -0
  30. package/templates/shared/.gitignore.tpl +10 -0
  31. package/templates/shared/.keel/local-skill.md.tpl +19 -0
  32. package/templates/shared/.keel/memory.md.tpl +15 -0
  33. package/templates/shared/CLAUDE.md.tpl +28 -0
  34. package/templates/shared/architecture/data.ts.tpl +45 -0
  35. package/templates/shared/eslint.config.js.tpl +42 -0
  36. package/templates/shared/principles/index.ts.tpl +54 -0
  37. package/templates/shared/tsconfig.json.tpl +22 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 jglasskatz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # create-keel-app
2
+
3
+ Scaffold a new Keel project with all conventions pre-wired.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ npx create-keel-app <project-name>
9
+ npx create-keel-app <project-name> --template ai-app # default
10
+ npx create-keel-app <project-name> --template framework
11
+ ```
12
+
13
+ The default template (`ai-app`) creates a project with a working MCP tool server,
14
+ thin-slice agent, architecture map, and all Keel conventions in place.
15
+ The `framework` template creates a minimal framework-only scaffold without the
16
+ agent and MCP demo wiring.
17
+
18
+ ## Options
19
+
20
+ ### `--template <name>`
21
+
22
+ Choose a project template. Valid values: `ai-app` (default) or `framework`.
23
+
24
+ ### `--local <path-to-keel>`
25
+
26
+ Point the scaffolded project at a local Keel checkout via `link:` deps (pnpm link: protocol, also supported in npm 8+) instead of `"*"` npm version placeholders. Use this when the `@keel_flow/*` packages are not yet published to npm.
27
+
28
+ ```bash
29
+ npx create-keel-app my-project --local /path/to/keel
30
+ ```
31
+
32
+ The resulting `package.json` will have entries like:
33
+
34
+ ```json
35
+ "@keel_flow/core": "link:/path/to/keel/packages/core"
36
+ ```
37
+
38
+ Running `pnpm install` inside the scaffolded project will then resolve all `@keel_flow/*` deps from your local checkout immediately, with no publish step required.
@@ -0,0 +1,6 @@
1
+ export interface KeelDepResolution {
2
+ versions: Record<string, string>;
3
+ }
4
+ export declare function resolveKeelDeps(localPath: string | undefined): KeelDepResolution;
5
+ export declare function applyKeelDeps(packageJsonContent: string, versions: Record<string, string>): string;
6
+ //# sourceMappingURL=dep-emit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dep-emit.d.ts","sourceRoot":"","sources":["../src/dep-emit.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAmBD,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,iBAAiB,CAoBhF;AAED,wBAAgB,aAAa,CAC3B,kBAAkB,EAAE,MAAM,EAC1B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,MAAM,CAQR"}
@@ -0,0 +1,47 @@
1
+ import { existsSync } from "fs";
2
+ import { resolve } from "path";
3
+ const PACKAGE_DEPS = [
4
+ "@keel_flow/cli",
5
+ "@keel_flow/core",
6
+ "@keel_flow/schema",
7
+ "@keel_flow/runtime",
8
+ "@keel_flow/adopt",
9
+ "@keel_flow/extend",
10
+ "@keel_flow/pack-general-se",
11
+ "@keel_flow/pack-agent-topology",
12
+ "@keel_flow/pack-kb-hygiene",
13
+ "@keel_flow/eslint-plugin",
14
+ ];
15
+ const FOLDER_OVERRIDES = {
16
+ "@keel_flow/eslint-plugin": "eslint-plugin",
17
+ };
18
+ export function resolveKeelDeps(localPath) {
19
+ const versions = {};
20
+ if (!localPath) {
21
+ // Pin to the current minor (^0.2.0) rather than "*": a future breaking
22
+ // @keel_flow minor must not retroactively break already-scaffolded apps or a
23
+ // fresh `npx create-keel-app`.
24
+ for (const pkg of PACKAGE_DEPS)
25
+ versions[pkg] = "^0.2.0";
26
+ return { versions };
27
+ }
28
+ const root = resolve(localPath);
29
+ if (!existsSync(`${root}/packages/core/package.json`)) {
30
+ throw new Error(`--local path "${root}" does not look like a Keel checkout (no packages/core/package.json)`);
31
+ }
32
+ for (const pkg of PACKAGE_DEPS) {
33
+ const folder = FOLDER_OVERRIDES[pkg] ?? pkg.replace("@keel_flow/", "");
34
+ versions[pkg] = `link:${root}/packages/${folder}`;
35
+ }
36
+ return { versions };
37
+ }
38
+ export function applyKeelDeps(packageJsonContent, versions) {
39
+ let updated = packageJsonContent;
40
+ for (const [name, version] of Object.entries(versions)) {
41
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
42
+ const re = new RegExp(`("${escaped}"\\s*:\\s*)"[^"]*"`);
43
+ updated = updated.replace(re, `$1"${version}"`);
44
+ }
45
+ return updated;
46
+ }
47
+ //# sourceMappingURL=dep-emit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dep-emit.js","sourceRoot":"","sources":["../src/dep-emit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAM/B,MAAM,YAAY,GAAG;IACnB,gBAAgB;IAChB,iBAAiB;IACjB,mBAAmB;IACnB,oBAAoB;IACpB,kBAAkB;IAClB,mBAAmB;IACnB,4BAA4B;IAC5B,gCAAgC;IAChC,4BAA4B;IAC5B,0BAA0B;CAC3B,CAAC;AAEF,MAAM,gBAAgB,GAA2B;IAC/C,0BAA0B,EAAE,eAAe;CAC5C,CAAC;AAEF,MAAM,UAAU,eAAe,CAAC,SAA6B;IAC3D,MAAM,QAAQ,GAA2B,EAAE,CAAC;IAC5C,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,uEAAuE;QACvE,6EAA6E;QAC7E,+BAA+B;QAC/B,KAAK,MAAM,GAAG,IAAI,YAAY;YAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC;QACzD,OAAO,EAAE,QAAQ,EAAE,CAAC;IACtB,CAAC;IACD,MAAM,IAAI,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,IAAI,CAAC,UAAU,CAAC,GAAG,IAAI,6BAA6B,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,KAAK,CACb,iBAAiB,IAAI,sEAAsE,CAC5F,CAAC;IACJ,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,gBAAgB,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;QACvE,QAAQ,CAAC,GAAG,CAAC,GAAG,QAAQ,IAAI,aAAa,MAAM,EAAE,CAAC;IACpD,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,kBAA0B,EAC1B,QAAgC;IAEhC,IAAI,OAAO,GAAG,kBAAkB,CAAC;IACjC,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACvD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;QAC5D,MAAM,EAAE,GAAG,IAAI,MAAM,CAAC,KAAK,OAAO,oBAAoB,CAAC,CAAC;QACxD,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,MAAM,OAAO,GAAG,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# 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,63 @@
1
+ #!/usr/bin/env node
2
+ import { resolve } from "path";
3
+ import { existsSync } from "fs";
4
+ import { scaffold, VALID_TEMPLATES, DEFAULT_TEMPLATE } from "./scaffold.js";
5
+ const args = process.argv.slice(2);
6
+ let localKeelPath;
7
+ const localFlagIdx = args.indexOf("--local");
8
+ if (localFlagIdx !== -1) {
9
+ const raw = args[localFlagIdx + 1];
10
+ if (!raw || raw.startsWith("--")) {
11
+ process.stderr.write("--local requires a path argument (pnpm link: protocol)\n");
12
+ process.exit(1);
13
+ }
14
+ localKeelPath = resolve(raw);
15
+ if (!existsSync(`${localKeelPath}/packages/core/package.json`)) {
16
+ process.stderr.write(`--local path "${localKeelPath}" does not look like a Keel checkout (no packages/core/package.json)\n`);
17
+ process.exit(1);
18
+ }
19
+ args.splice(localFlagIdx, 2);
20
+ }
21
+ let template = DEFAULT_TEMPLATE;
22
+ const templateFlagIdx = args.indexOf("--template");
23
+ if (templateFlagIdx !== -1) {
24
+ const raw = args[templateFlagIdx + 1];
25
+ if (!raw || raw.startsWith("--")) {
26
+ process.stderr.write(`--template requires a value. Valid templates: ${VALID_TEMPLATES.join(", ")}\n`);
27
+ process.exit(1);
28
+ }
29
+ if (!VALID_TEMPLATES.includes(raw)) {
30
+ process.stderr.write(`Unknown template "${raw}". Valid templates: ${VALID_TEMPLATES.join(", ")}\n`);
31
+ process.exit(1);
32
+ }
33
+ template = raw;
34
+ args.splice(templateFlagIdx, 2);
35
+ }
36
+ const projectName = args[0];
37
+ if (!projectName) {
38
+ process.stderr.write("Usage: create-keel-app <project-name> [--template framework|ai-app] [--local <path-to-keel>]\n");
39
+ process.exit(1);
40
+ }
41
+ if (!/^[a-z0-9-_]+$/i.test(projectName)) {
42
+ process.stderr.write(`Invalid project name "${projectName}". Use only letters, numbers, hyphens, and underscores.\n`);
43
+ process.exit(1);
44
+ }
45
+ const targetDir = resolve(process.cwd(), projectName);
46
+ process.stdout.write(`Creating Keel project: ${projectName}\n`);
47
+ process.stdout.write(`Template: ${template}\n`);
48
+ process.stdout.write(`Target: ${targetDir}\n\n`);
49
+ const scaffoldOptions = { template };
50
+ if (localKeelPath)
51
+ scaffoldOptions.localKeelPath = localKeelPath;
52
+ scaffold(projectName, targetDir, scaffoldOptions);
53
+ process.stdout.write(`Done! Next steps:\n`);
54
+ process.stdout.write(` cd ${projectName}\n`);
55
+ process.stdout.write(` pnpm install\n`);
56
+ if (template === "ai-app") {
57
+ process.stdout.write(` export ANTHROPIC_API_KEY=sk-...\n`);
58
+ process.stdout.write(` pnpm dev\n`);
59
+ }
60
+ else {
61
+ process.stdout.write(` pnpm verify\n`);
62
+ }
63
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,gBAAgB,EAAqB,MAAM,eAAe,CAAC;AAE/F,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAEnC,IAAI,aAAiC,CAAC;AACtC,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAC7C,IAAI,YAAY,KAAK,CAAC,CAAC,EAAE,CAAC;IACxB,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC;IACnC,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACjC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,0DAA0D,CAAC,CAAC;QACjF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAC7B,IAAI,CAAC,UAAU,CAAC,GAAG,aAAa,6BAA6B,CAAC,EAAE,CAAC;QAC/D,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,iBAAiB,aAAa,wEAAwE,CACvG,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC;AAC/B,CAAC;AAED,IAAI,QAAQ,GAAiB,gBAAgB,CAAC;AAC9C,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;AACnD,IAAI,eAAe,KAAK,CAAC,CAAC,EAAE,CAAC;IAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC,CAAC;IACtC,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACjC,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,iDAAiD,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAChF,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,IAAI,CAAE,eAA4B,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACjD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,qBAAqB,GAAG,uBAAuB,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAC9E,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,QAAQ,GAAG,GAAmB,CAAC;IAC/B,IAAI,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,WAAW,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;AAE5B,IAAI,CAAC,WAAW,EAAE,CAAC;IACjB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,gGAAgG,CACjG,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;IACxC,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,yBAAyB,WAAW,2DAA2D,CAChG,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,WAAW,CAAC,CAAC;AAEtD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,0BAA0B,WAAW,IAAI,CAAC,CAAC;AAChE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,QAAQ,IAAI,CAAC,CAAC;AAChD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,SAAS,MAAM,CAAC,CAAC;AAEjD,MAAM,eAAe,GAAuD,EAAE,QAAQ,EAAE,CAAC;AACzF,IAAI,aAAa;IAAE,eAAe,CAAC,aAAa,GAAG,aAAa,CAAC;AACjE,QAAQ,CAAC,WAAW,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC;AAElD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;AAC5C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,WAAW,IAAI,CAAC,CAAC;AAC9C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;AACzC,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;IAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;IAC5D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;AACvC,CAAC;KAAM,CAAC;IACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { relative, resolve } from "path";
2
+ import { statSync } from "fs";
3
+ export type TemplateName = "framework" | "ai-app";
4
+ export declare const VALID_TEMPLATES: TemplateName[];
5
+ export declare const DEFAULT_TEMPLATE: TemplateName;
6
+ export interface ScaffoldOptions {
7
+ template?: TemplateName;
8
+ localKeelPath?: string;
9
+ }
10
+ export declare function scaffold(projectName: string, targetDir: string, optionsOrLocalKeelPath?: ScaffoldOptions | string): void;
11
+ export declare function getTemplatesDir(): string;
12
+ export { relative, statSync, resolve };
13
+ //# sourceMappingURL=scaffold.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,QAAQ,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAExD,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AAK9B,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,QAAQ,CAAC;AAElD,eAAO,MAAM,eAAe,EAAE,YAAY,EAA4B,CAAC;AACvE,eAAO,MAAM,gBAAgB,EAAE,YAAuB,CAAC;AAsCvD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,QAAQ,CACtB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,sBAAsB,CAAC,EAAE,eAAe,GAAG,MAAM,GAChD,IAAI,CAyBN;AAED,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC"}
@@ -0,0 +1,57 @@
1
+ import { mkdirSync, writeFileSync, readdirSync, readFileSync } from "fs";
2
+ import { join, dirname, relative, resolve } from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { statSync } from "fs";
5
+ import { resolveKeelDeps, applyKeelDeps } from "./dep-emit.js";
6
+ const TEMPLATES_DIR = join(dirname(fileURLToPath(import.meta.url)), "../templates");
7
+ export const VALID_TEMPLATES = ["framework", "ai-app"];
8
+ export const DEFAULT_TEMPLATE = "ai-app";
9
+ function substitute(content, projectName) {
10
+ return content.replaceAll("{{PROJECT_NAME}}", projectName);
11
+ }
12
+ function writeTemplate(templatePath, destPath, projectName) {
13
+ const content = readFileSync(templatePath, "utf-8");
14
+ const substituted = substitute(content, projectName);
15
+ mkdirSync(dirname(destPath), { recursive: true });
16
+ writeFileSync(destPath, substituted, "utf-8");
17
+ }
18
+ function copyTemplates(srcDir, destDir, projectName, skipDirNames = new Set()) {
19
+ const entries = readdirSync(srcDir, { withFileTypes: true });
20
+ for (const entry of entries) {
21
+ if (entry.isDirectory() && skipDirNames.has(entry.name))
22
+ continue;
23
+ const srcPath = join(srcDir, entry.name);
24
+ if (entry.isDirectory()) {
25
+ copyTemplates(srcPath, join(destDir, entry.name), projectName, skipDirNames);
26
+ }
27
+ else {
28
+ const destName = entry.name.endsWith(".tpl")
29
+ ? entry.name.slice(0, -4)
30
+ : entry.name;
31
+ writeTemplate(srcPath, join(destDir, destName), projectName);
32
+ }
33
+ }
34
+ }
35
+ export function scaffold(projectName, targetDir, optionsOrLocalKeelPath) {
36
+ const options = typeof optionsOrLocalKeelPath === "string"
37
+ ? { localKeelPath: optionsOrLocalKeelPath }
38
+ : (optionsOrLocalKeelPath ?? {});
39
+ const template = options.template ?? DEFAULT_TEMPLATE;
40
+ if (!VALID_TEMPLATES.includes(template)) {
41
+ throw new Error(`Unknown template "${template}". Valid templates: ${VALID_TEMPLATES.join(", ")}`);
42
+ }
43
+ mkdirSync(targetDir, { recursive: true });
44
+ const sharedDir = join(TEMPLATES_DIR, "shared");
45
+ copyTemplates(sharedDir, targetDir, projectName);
46
+ const templateDir = join(TEMPLATES_DIR, template);
47
+ copyTemplates(templateDir, targetDir, projectName, new Set(["extras"]));
48
+ const pkgJsonPath = join(targetDir, "package.json");
49
+ const pkgContent = readFileSync(pkgJsonPath, "utf-8");
50
+ const { versions } = resolveKeelDeps(options.localKeelPath);
51
+ writeFileSync(pkgJsonPath, applyKeelDeps(pkgContent, versions), "utf-8");
52
+ }
53
+ export function getTemplatesDir() {
54
+ return TEMPLATES_DIR;
55
+ }
56
+ export { relative, statSync, resolve };
57
+ //# sourceMappingURL=scaffold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AACzE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE/D,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC;AAIpF,MAAM,CAAC,MAAM,eAAe,GAAmB,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;AACvE,MAAM,CAAC,MAAM,gBAAgB,GAAiB,QAAQ,CAAC;AAEvD,SAAS,UAAU,CAAC,OAAe,EAAE,WAAmB;IACtD,OAAO,OAAO,CAAC,UAAU,CAAC,kBAAkB,EAAE,WAAW,CAAC,CAAC;AAC7D,CAAC;AAED,SAAS,aAAa,CACpB,YAAoB,EACpB,QAAgB,EAChB,WAAmB;IAEnB,MAAM,OAAO,GAAG,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IACpD,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IACrD,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,aAAa,CAAC,QAAQ,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;AAChD,CAAC;AAED,SAAS,aAAa,CACpB,MAAc,EACd,OAAe,EACf,WAAmB,EACnB,eAA4B,IAAI,GAAG,EAAE;IAErC,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,KAAK,CAAC,WAAW,EAAE,IAAI,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,SAAS;QAClE,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC;QAC/E,CAAC;aAAM,CAAC;YACN,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAC1C,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBACzB,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;YACf,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,WAAW,CAAC,CAAC;QAC/D,CAAC;IACH,CAAC;AACH,CAAC;AAOD,MAAM,UAAU,QAAQ,CACtB,WAAmB,EACnB,SAAiB,EACjB,sBAAiD;IAEjD,MAAM,OAAO,GACX,OAAO,sBAAsB,KAAK,QAAQ;QACxC,CAAC,CAAC,EAAE,aAAa,EAAE,sBAAsB,EAAE;QAC3C,CAAC,CAAC,CAAC,sBAAsB,IAAI,EAAE,CAAC,CAAC;IAErC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,gBAAgB,CAAC;IACtD,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CACb,qBAAqB,QAAQ,uBAAuB,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACjF,CAAC;IACJ,CAAC;IAED,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE1C,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;IAChD,aAAa,CAAC,SAAS,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;IAEjD,MAAM,WAAW,GAAG,IAAI,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;IAClD,aAAa,CAAC,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IAExE,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;IACpD,MAAM,UAAU,GAAG,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IACtD,MAAM,EAAE,QAAQ,EAAE,GAAG,eAAe,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IAC5D,aAAa,CAAC,WAAW,EAAE,aAAa,CAAC,UAAU,EAAE,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;AAC3E,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,aAAa,CAAC;AACvB,CAAC;AAED,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC"}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "create-keel-app",
3
+ "version": "0.2.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "templates",
12
+ "README.md"
13
+ ],
14
+ "bin": {
15
+ "create-keel-app": "./dist/index.js"
16
+ },
17
+ "main": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "import": "./dist/index.js",
22
+ "require": "./dist/index.js",
23
+ "types": "./dist/index.d.ts"
24
+ }
25
+ },
26
+ "dependencies": {
27
+ "@keel_flow/schema": "0.2.0"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^25.9.1",
31
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
32
+ "@typescript-eslint/parser": "^8.0.0",
33
+ "eslint": "^9.0.0",
34
+ "typescript": "^5.5.0",
35
+ "vitest": "^2.0.0"
36
+ },
37
+ "scripts": {
38
+ "build": "tsc",
39
+ "typecheck": "tsc --noEmit",
40
+ "test": "vitest run",
41
+ "lint": "eslint src"
42
+ }
43
+ }
@@ -0,0 +1,56 @@
1
+ # {{PROJECT_NAME}}
2
+
3
+ A Keel AI-app project. https://github.com/jglasskatz/keel
4
+
5
+ ## Quick start
6
+
7
+ ```bash
8
+ pnpm install
9
+ export ANTHROPIC_API_KEY=sk-...
10
+ pnpm dev
11
+ ```
12
+
13
+ ## Verify
14
+
15
+ ```bash
16
+ pnpm verify
17
+ ```
18
+
19
+ ## Structure
20
+
21
+ - `architecture/data.ts` — architecture map (keep in sync with code)
22
+ - `principles/index.ts` — engineering principles registry
23
+ - `src/agents/` — agent definitions via `defineAgent`
24
+ - `src/tools/` — in-process tool definitions via `defineTool`
25
+ - `src/index.ts` — bootstrap entrypoint
26
+
27
+ ## Tools (in-process)
28
+
29
+ Tools live in `src/tools/` and are defined with `defineTool` from
30
+ `@keel_flow/core`. Each tool exports a typed handler with a Zod
31
+ `inputSchema`. The agent loop in `@keel_flow/runtime` invokes them
32
+ directly in-process — no JSON-RPC, no child process, no extra
33
+ runtime cost.
34
+
35
+ ### Adding a new tool
36
+
37
+ 1. Create `src/tools/my-tool.ts` exporting a `defineTool({...})` value.
38
+ 2. Register it in `src/tools/index.ts` so it's part of the tool
39
+ registry.
40
+ 3. Pass it into `runThinSlice({ ..., tools: [...] })` (or whichever
41
+ agent invocation needs it).
42
+
43
+ ### Opt-in: MCP
44
+
45
+ If you need cross-host tool reuse (third-party MCP servers, sharing
46
+ the same tool with a Claude Desktop install, multi-process
47
+ isolation), see `create-keel-app`'s `extras/mcp.md` reference doc
48
+ for the recipe. MCP is not included in the default scaffold — most
49
+ projects don't need it.
50
+
51
+ ## Adding an agent
52
+
53
+ 1. Create `src/agents/my-agent.ts` using `defineAgent` from `@keel_flow/core`.
54
+ 2. Add the node to `architecture/data.ts`.
55
+ 3. Wire it into `src/index.ts`.
56
+ 4. Run `pnpm verify` — all checks must pass.
@@ -0,0 +1,76 @@
1
+ import type { ArchitectureMap } from "@keel_flow/schema";
2
+ import { SCHEMA_VERSION } from "@keel_flow/schema";
3
+
4
+ const data: ArchitectureMap = {
5
+ schemaVersion: SCHEMA_VERSION,
6
+ layers: [
7
+ {
8
+ id: "agents",
9
+ label: "Agents",
10
+ description: "AI agents for the {{PROJECT_NAME}} system",
11
+ color: "#4f46e5",
12
+ },
13
+ {
14
+ id: "tools",
15
+ label: "Tools",
16
+ description: "In-process tools called by agents via defineTool",
17
+ color: "#0891b2",
18
+ },
19
+ ],
20
+ nodes: [
21
+ {
22
+ id: "thin-slice-agent",
23
+ label: "Thin Slice Agent",
24
+ layer: "agents",
25
+ tech: "TypeScript / Anthropic SDK",
26
+ description:
27
+ "Seed agent for {{PROJECT_NAME}} — replace with your domain agents",
28
+ keyFiles: ["src/agents/thin-slice.ts"],
29
+ keyFunctionality: ["accept prompt", "call in-process tools", "return response"],
30
+ fileCount: 1,
31
+ linesOfCode: "~40",
32
+ principles: {
33
+ upholds: [
34
+ { id: "single-source-of-truth-per-tool", reason: "Each tool has one data path" },
35
+ ],
36
+ violates: [],
37
+ },
38
+ },
39
+ {
40
+ id: "hello-tool",
41
+ label: "Hello Tool",
42
+ layer: "tools",
43
+ tech: "TypeScript / Zod",
44
+ description:
45
+ "Example in-process tool defined via defineTool — replace with domain-specific tools",
46
+ keyFiles: ["src/tools/hello.ts"],
47
+ keyFunctionality: ["greet by name"],
48
+ fileCount: 1,
49
+ linesOfCode: "~10",
50
+ principles: {
51
+ upholds: [
52
+ { id: "tool-schemas-are-load-bearing", reason: "Zod inputSchema is source of truth" },
53
+ ],
54
+ violates: [],
55
+ },
56
+ },
57
+ ],
58
+ connections: [
59
+ {
60
+ from: "thin-slice-agent",
61
+ to: "hello-tool",
62
+ type: "tool-call",
63
+ label: "calls hello",
64
+ },
65
+ ],
66
+ contexts: [
67
+ {
68
+ id: "thin-slice",
69
+ label: "Thin Slice",
70
+ description: "Initial runnable context — expand as you add domain agents",
71
+ nodeIds: ["thin-slice-agent", "hello-tool"],
72
+ },
73
+ ],
74
+ };
75
+
76
+ export default data;
@@ -0,0 +1,148 @@
1
+ # Opt-in: Adding MCP to a Keel ai-app
2
+
3
+ The default ai-app scaffold uses **in-process tools** defined with
4
+ `defineTool` from `@keel_flow/core`. The agent loop calls them directly.
5
+ This is the simplest, fastest, and most debuggable arrangement, and
6
+ is the right default for almost every project.
7
+
8
+ You only need MCP (Model Context Protocol) if you have one of these
9
+ specific needs:
10
+
11
+ - **Cross-host tool reuse** — a tool server you want to share
12
+ between this app, Claude Desktop, and other agents.
13
+ - **Process isolation** — a tool that needs to run as a separate
14
+ process for sandboxing or because it uses a runtime your main
15
+ process cannot.
16
+ - **Third-party MCP servers** — you want to consume an existing
17
+ off-the-shelf MCP server (filesystem, GitHub, databases).
18
+
19
+ If none of those apply, **stick with in-process tools**. MCP adds a
20
+ JSON-RPC layer, a child process, and a schema-translation round
21
+ trip for zero functional benefit in the single-process case.
22
+
23
+ > See the Keel whitepaper sections on MCP for the framework-level
24
+ > rationale and the agent-topology principles that govern when to
25
+ > reach for it.
26
+
27
+ ## Recipe (when you do need it)
28
+
29
+ ### 1. Install the SDK
30
+
31
+ ```bash
32
+ pnpm add @modelcontextprotocol/sdk
33
+ ```
34
+
35
+ ### 2. Create `src/mcp-server.ts`
36
+
37
+ A minimal stdio-transport MCP server that exposes the same
38
+ `helloTool` over the wire:
39
+
40
+ ```ts
41
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
42
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
43
+ import {
44
+ CallToolRequestSchema,
45
+ ListToolsRequestSchema,
46
+ } from "@modelcontextprotocol/sdk/types.js";
47
+
48
+ const server = new Server(
49
+ { name: "{{PROJECT_NAME}}-tools", version: "0.1.0" },
50
+ { capabilities: { tools: {} } },
51
+ );
52
+
53
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
54
+ tools: [
55
+ {
56
+ name: "hello",
57
+ description: "Say hello to a name.",
58
+ inputSchema: {
59
+ type: "object",
60
+ properties: { name: { type: "string" } },
61
+ required: ["name"],
62
+ },
63
+ },
64
+ ],
65
+ }));
66
+
67
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
68
+ if (req.params.name === "hello") {
69
+ const input = req.params.arguments as { name: string };
70
+ return {
71
+ content: [{ type: "text", text: `Hello, ${input.name}!` }],
72
+ };
73
+ }
74
+ throw new Error(`Unknown tool: ${req.params.name}`);
75
+ });
76
+
77
+ const transport = new StdioServerTransport();
78
+ await server.connect(transport);
79
+ ```
80
+
81
+ ### 3. Adapt the in-process tool through MCP
82
+
83
+ Create `src/mcp-client.ts` that spawns the server, lists tools, and
84
+ returns `ToolDefinition[]` for `@keel_flow/runtime`:
85
+
86
+ ```ts
87
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
88
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
89
+ import { defineTool } from "@keel_flow/core";
90
+ import { z } from "zod";
91
+ import type { ToolDefinition } from "@keel_flow/runtime";
92
+
93
+ export async function createMCPTools(
94
+ command: string[],
95
+ ): Promise<ToolDefinition[]> {
96
+ const [cmd, ...args] = command;
97
+ if (!cmd) throw new Error("command must have at least one element");
98
+
99
+ const transport = new StdioClientTransport({ command: cmd, args });
100
+ const client = new Client(
101
+ { name: "{{PROJECT_NAME}}-client", version: "0.1.0" },
102
+ { capabilities: {} },
103
+ );
104
+ await client.connect(transport);
105
+
106
+ const { tools: mcpTools } = await client.listTools();
107
+ return mcpTools.map((t) =>
108
+ defineTool({
109
+ id: t.name,
110
+ description: t.description ?? t.name,
111
+ inputSchema: z.object({ name: z.string() }),
112
+ async handler(input: unknown) {
113
+ const result = await client.callTool({
114
+ name: t.name,
115
+ arguments: input as Record<string, unknown>,
116
+ });
117
+ return result.content;
118
+ },
119
+ }) as ToolDefinition,
120
+ );
121
+ }
122
+ ```
123
+
124
+ ### 4. Wire it from `src/index.ts`
125
+
126
+ ```ts
127
+ import { createProvider } from "@keel_flow/runtime";
128
+ import { runThinSlice } from "./agents/thin-slice.js";
129
+ import { createMCPTools } from "./mcp-client.js";
130
+
131
+ const provider = createProvider({ kind: "anthropic", apiKey: process.env["ANTHROPIC_API_KEY"] ?? "" });
132
+ const tools = await createMCPTools(["node", "./dist/mcp-server.js"]);
133
+ const result = await runThinSlice({ provider, tools, prompt: "Say hi to the world." });
134
+ console.log(result);
135
+ ```
136
+
137
+ ### 5. Update the architecture map
138
+
139
+ Add a `mcp-server` node + an `mcp-client` node to
140
+ `architecture/data.ts`. Mark the tool layer's tech as
141
+ `"TypeScript / MCP"` and add the connections explicitly. Verify
142
+ will block the merge until you do.
143
+
144
+ ---
145
+
146
+ **Note:** this document is reference material that ships with
147
+ `create-keel-app`. It is **not** emitted into your project by
148
+ default. Copy from it manually when you decide you need MCP.
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "tsx watch ./src/index.ts",
7
+ "build": "tsc",
8
+ "typecheck": "tsc --noEmit",
9
+ "test": "vitest run",
10
+ "lint": "eslint .",
11
+ "verify": "keel verify --diff origin/main..HEAD"
12
+ },
13
+ "dependencies": {
14
+ "@anthropic-ai/sdk": "*",
15
+ "@keel_flow/adopt": "^0.2.0",
16
+ "@keel_flow/cli": "^0.2.0",
17
+ "@keel_flow/core": "^0.2.0",
18
+ "@keel_flow/eslint-plugin": "^0.2.0",
19
+ "@keel_flow/extend": "^0.2.0",
20
+ "@keel_flow/runtime": "^0.2.0",
21
+ "@keel_flow/schema": "^0.2.0",
22
+ "@keel_flow/pack-agent-topology": "^0.2.0",
23
+ "@keel_flow/pack-general-se": "^0.2.0",
24
+ "@keel_flow/pack-kb-hygiene": "^0.2.0",
25
+ "next": "*",
26
+ "zod": "^3.23.0"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^20.0.0",
30
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
31
+ "@typescript-eslint/parser": "^8.0.0",
32
+ "eslint": "^9.0.0",
33
+ "tsx": "^4.0.0",
34
+ "typescript": "^5.5.0",
35
+ "vitest": "^2.0.0"
36
+ }
37
+ }
@@ -0,0 +1,28 @@
1
+ import { defineAgent } from "@keel_flow/core";
2
+ import { runAgent } from "@keel_flow/runtime";
3
+ import type { ModelProvider, ToolDefinition } from "@keel_flow/runtime";
4
+
5
+ export const thinSliceAgent = defineAgent({
6
+ id: "thin-slice-agent",
7
+ description:
8
+ "Seed agent for {{PROJECT_NAME}} — accepts a prompt and invokes in-process tools",
9
+ tools: [],
10
+ prompt:
11
+ "You are a helpful assistant for the {{PROJECT_NAME}} project. Use the available tools to demonstrate end-to-end tool invocation.",
12
+ model: "claude-sonnet-4-6",
13
+ });
14
+
15
+ export interface RunThinSliceArgs {
16
+ provider: ModelProvider;
17
+ tools: ToolDefinition[];
18
+ prompt: string;
19
+ }
20
+
21
+ export async function runThinSlice(args: RunThinSliceArgs) {
22
+ return runAgent({
23
+ agent: thinSliceAgent.config,
24
+ tools: args.tools,
25
+ prompt: args.prompt,
26
+ provider: args.provider,
27
+ });
28
+ }
@@ -0,0 +1,20 @@
1
+ import { createProvider } from "@keel_flow/runtime";
2
+ import { runThinSlice } from "./agents/thin-slice.js";
3
+ import { toolRegistry } from "./tools/index.js";
4
+
5
+ const provider = createProvider({
6
+ kind:
7
+ process.env["KEEL_PROVIDER_KIND"] === "openai-compatible"
8
+ ? "openai-compatible"
9
+ : "anthropic",
10
+ apiKey:
11
+ process.env["ANTHROPIC_API_KEY"] ?? process.env["OPENAI_API_KEY"] ?? "",
12
+ });
13
+
14
+ const result = await runThinSlice({
15
+ provider,
16
+ tools: toolRegistry,
17
+ prompt: "Say hi to the world.",
18
+ });
19
+
20
+ console.log(result);
@@ -0,0 +1,14 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { helloTool } from "./hello.js";
3
+
4
+ describe("helloTool", () => {
5
+ it("returns a greeting for the given name", async () => {
6
+ const result = await helloTool.call({ name: "world" });
7
+ expect(result).toEqual({ greeting: "Hello, world!" });
8
+ });
9
+
10
+ it("has id 'hello' and a non-empty description", () => {
11
+ expect(helloTool.config.id).toBe("hello");
12
+ expect(helloTool.config.description.length).toBeGreaterThan(0);
13
+ });
14
+ });
@@ -0,0 +1,9 @@
1
+ import { z } from "zod";
2
+ import { defineTool } from "@keel_flow/core";
3
+
4
+ export const helloTool = defineTool({
5
+ id: "hello",
6
+ description: "Say hello to a name",
7
+ inputSchema: z.object({ name: z.string() }),
8
+ handler: async ({ name }) => ({ greeting: `Hello, ${name}!` }),
9
+ });
@@ -0,0 +1,6 @@
1
+ import type { ToolDefinition } from "@keel_flow/runtime";
2
+ import { helloTool } from "./hello.js";
3
+
4
+ export const toolRegistry: ToolDefinition[] = [helloTool as unknown as ToolDefinition];
5
+
6
+ export { helloTool };
@@ -0,0 +1,31 @@
1
+ # {{PROJECT_NAME}}
2
+
3
+ A Keel-disciplined project (framework template). https://github.com/jglasskatz/keel
4
+
5
+ This template ships the Keel discipline only — `architecture/data.ts`,
6
+ `principles/index.ts`, the verify gate, eslint plugin, and the
7
+ principle packs. It does NOT include the agent runtime or any AI-app
8
+ scaffolding. Add `@keel_flow/runtime` + `@keel_flow/core` `defineAgent` /
9
+ `defineTool` patterns when you need them; until then, this project is
10
+ a plain TypeScript module under Keel's architectural guardrails.
11
+
12
+ ## Quick start
13
+
14
+ ```bash
15
+ pnpm install
16
+ pnpm verify
17
+ ```
18
+
19
+ ## Structure
20
+
21
+ - `architecture/data.ts` — architecture map (keep in sync with code)
22
+ - `principles/index.ts` — engineering principles registry
23
+ - `eslint.config.js` — editor-time principle checks via `@keel_flow/eslint-plugin`
24
+ - `src/index.ts` — your code
25
+
26
+ ## Adding agents later
27
+
28
+ If you decide you want an AI app, scaffold a new project with
29
+ `create-keel-app <name> --template ai-app` and copy the relevant
30
+ files over. The two templates share the same shared/ baseline so the
31
+ disciplines align.
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "build": "tsc",
7
+ "typecheck": "tsc --noEmit",
8
+ "test": "vitest run",
9
+ "lint": "eslint .",
10
+ "verify": "keel verify --diff origin/main..HEAD"
11
+ },
12
+ "dependencies": {
13
+ "@keel_flow/cli": "^0.2.0",
14
+ "@keel_flow/core": "^0.2.0",
15
+ "@keel_flow/schema": "^0.2.0",
16
+ "@keel_flow/eslint-plugin": "^0.2.0",
17
+ "@keel_flow/pack-agent-topology": "^0.2.0",
18
+ "@keel_flow/pack-general-se": "^0.2.0",
19
+ "@keel_flow/pack-kb-hygiene": "^0.2.0"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "^20.0.0",
23
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
24
+ "@typescript-eslint/parser": "^8.0.0",
25
+ "eslint": "^9.0.0",
26
+ "tsx": "^4.0.0",
27
+ "typescript": "^5.5.0",
28
+ "vitest": "^2.0.0"
29
+ }
30
+ }
@@ -0,0 +1,8 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { projectName } from "./index.js";
3
+
4
+ describe("{{PROJECT_NAME}}", () => {
5
+ it("exports the project name", () => {
6
+ expect(projectName).toBe("{{PROJECT_NAME}}");
7
+ });
8
+ });
@@ -0,0 +1 @@
1
+ export const projectName = "{{PROJECT_NAME}}";
@@ -0,0 +1,29 @@
1
+ name: Keel Verify
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ keel-verify:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ with:
14
+ fetch-depth: 0
15
+
16
+ - uses: pnpm/action-setup@v4
17
+ with:
18
+ version: 9
19
+
20
+ - uses: actions/setup-node@v4
21
+ with:
22
+ node-version: 20
23
+ cache: pnpm
24
+
25
+ - name: Install dependencies
26
+ run: pnpm install --frozen-lockfile
27
+
28
+ - name: Keel Verify
29
+ run: npx keel verify --diff origin/main..HEAD
@@ -0,0 +1,10 @@
1
+ node_modules
2
+ dist
3
+ .turbo
4
+ *.log
5
+ .env
6
+ .env.local
7
+ .DS_Store
8
+ .keel/sessions
9
+ .keel/repo-map.cache.json
10
+ .keel/encryption-key
@@ -0,0 +1,19 @@
1
+ <!--
2
+ Local skill overlay for {{PROJECT_NAME}}.
3
+
4
+ pi-keel reads this file (and ~/.keel/local-skill.md) and merges its
5
+ contents into the active skill at session start. Use it to encode
6
+ project-specific tool guidance, naming conventions, or repository
7
+ invariants that should be in the agent's context for every session.
8
+
9
+ Lines starting with "<!-- -->" comments are ignored. Uncomment and
10
+ edit to take effect.
11
+ -->
12
+
13
+ <!--
14
+ ## Project conventions
15
+
16
+ - All new tools live under `src/tools/` and use `defineTool`.
17
+ - All new agents live under `src/agents/` and use `defineAgent`.
18
+ - Update `architecture/data.ts` in the same commit as code changes.
19
+ -->
@@ -0,0 +1,15 @@
1
+ # Memory — {{PROJECT_NAME}}
2
+
3
+ <!--
4
+ Append-only project memory consumed by pi-keel and @keel_flow/memory.
5
+
6
+ Use `keel memory add "..."` from the CLI to add entries (each
7
+ prefixed with a timestamped `## ` header). Edit-by-hand is fine
8
+ but breaks idempotent id derivation — prefer the CLI in practice.
9
+ -->
10
+
11
+ ## {{PROJECT_NAME}} — project facts
12
+
13
+ Replace this block with the load-bearing facts about this project:
14
+ its domain, the primary agents, the data sources, the deployment
15
+ target. The agent reads this file at the top of every session.
@@ -0,0 +1,28 @@
1
+ # CLAUDE.md — {{PROJECT_NAME}}
2
+
3
+ This project uses the Keel framework: https://github.com/jglasskatz/keel
4
+
5
+ ## Conventions
6
+
7
+ - `architecture/data.ts` — the architecture map. Update it when you add/remove/rename any node.
8
+ - `principles/index.ts` — the principles registry. Add domain-specific principles here.
9
+ - `src/agents/` — one file per agent, using `defineAgent` from `@keel_flow/core`.
10
+ - `src/tools/` — one file per tool, using `defineTool` from `@keel_flow/core` with a Zod `inputSchema`.
11
+ - `src/index.ts` — the public entrypoint.
12
+
13
+ ## Verify gate
14
+
15
+ Every PR runs `keel verify --diff origin/main..HEAD` in CI.
16
+
17
+ Critical violations block merge. Run `pnpm verify` locally before pushing.
18
+
19
+ ## Env vars required
20
+
21
+ - `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` — required for agent invocations
22
+
23
+ ## Quick start
24
+
25
+ ```bash
26
+ pnpm install
27
+ ANTHROPIC_API_KEY=sk-... pnpm dev
28
+ ```
@@ -0,0 +1,45 @@
1
+ import type { ArchitectureMap } from "@keel_flow/schema";
2
+ import { SCHEMA_VERSION } from "@keel_flow/schema";
3
+
4
+ const data: ArchitectureMap = {
5
+ schemaVersion: SCHEMA_VERSION,
6
+ layers: [
7
+ {
8
+ id: "core",
9
+ label: "Core",
10
+ description: "Primary modules of the {{PROJECT_NAME}} system",
11
+ color: "#4f46e5",
12
+ },
13
+ ],
14
+ nodes: [
15
+ {
16
+ id: "{{PROJECT_NAME}}-root",
17
+ label: "{{PROJECT_NAME}} Root",
18
+ layer: "core",
19
+ tech: "TypeScript",
20
+ description:
21
+ "Seed node for {{PROJECT_NAME}} — replace with your domain modules and keep this map in sync with code",
22
+ keyFiles: ["src/index.ts"],
23
+ keyFunctionality: ["bootstrap"],
24
+ fileCount: 1,
25
+ linesOfCode: "~20",
26
+ principles: {
27
+ upholds: [
28
+ { id: "single-source-of-truth", reason: "One canonical entry per concept" },
29
+ ],
30
+ violates: [],
31
+ },
32
+ },
33
+ ],
34
+ connections: [],
35
+ contexts: [
36
+ {
37
+ id: "thin-slice",
38
+ label: "Thin Slice",
39
+ description: "Initial runnable context — expand as you add domain modules",
40
+ nodeIds: ["{{PROJECT_NAME}}-root"],
41
+ },
42
+ ],
43
+ };
44
+
45
+ export default data;
@@ -0,0 +1,42 @@
1
+ import tseslint from "@typescript-eslint/eslint-plugin";
2
+ import tsparser from "@typescript-eslint/parser";
3
+ import keelPlugin from "@keel_flow/eslint-plugin";
4
+
5
+ /** @type {import('eslint').Linter.Config[]} */
6
+ const config = [
7
+ {
8
+ ignores: ["**/dist/**", "**/node_modules/**", "**/.turbo/**"],
9
+ },
10
+ {
11
+ files: ["**/*.ts", "**/*.tsx"],
12
+ languageOptions: {
13
+ parser: tsparser,
14
+ parserOptions: {
15
+ ecmaVersion: 2022,
16
+ sourceType: "module",
17
+ },
18
+ },
19
+ plugins: {
20
+ "@typescript-eslint": tseslint,
21
+ "@keel_flow": keelPlugin,
22
+ },
23
+ rules: {
24
+ ...tseslint.configs.recommended.rules,
25
+ "@typescript-eslint/no-explicit-any": "error",
26
+ "@typescript-eslint/explicit-function-return-type": "off",
27
+ "@typescript-eslint/no-unused-vars": [
28
+ "error",
29
+ { argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
30
+ ],
31
+ "@keel_flow/tests-as-contracts": "warn",
32
+ "@keel_flow/wrong-way-impossible": "error",
33
+ "@keel_flow/naming-is-design": "warn",
34
+ "@keel_flow/tool-schemas-are-load-bearing": "error",
35
+ "@keel_flow/tool-failures-are-typed": "warn",
36
+ "@keel_flow/chunk-size-bounded": "warn",
37
+ "@keel_flow/retrieval-confidence-floor": "warn",
38
+ },
39
+ },
40
+ ];
41
+
42
+ export default config;
@@ -0,0 +1,54 @@
1
+ import { createPrincipleRegistry } from "@keel_flow/core";
2
+ import type { PrincipleRegistry } from "@keel_flow/core";
3
+ import type { PrincipleWithCheck } from "@keel_flow/schema";
4
+
5
+ const generalSePrinciples: PrincipleWithCheck[] = [
6
+ {
7
+ id: "single-source-of-truth",
8
+ description: "Every fact has exactly one canonical home",
9
+ rationale: "Duplicated truth diverges over time",
10
+ severity: "critical",
11
+ },
12
+ {
13
+ id: "tests-as-contracts",
14
+ description: "Every public module has a co-located test",
15
+ rationale: "A test is the cheapest precise documentation",
16
+ severity: "critical",
17
+ },
18
+ {
19
+ id: "errors-at-boundaries",
20
+ description: "Validate and throw at every system boundary",
21
+ rationale: "Errors close to origin are cheap",
22
+ severity: "critical",
23
+ },
24
+ ];
25
+
26
+ const agentTopologyPrinciples: PrincipleWithCheck[] = [
27
+ {
28
+ id: "single-source-of-truth-per-tool",
29
+ description: "Each tool reads/writes through exactly one data path",
30
+ rationale: "Multiple paths create state drift",
31
+ severity: "critical",
32
+ },
33
+ {
34
+ id: "tool-schemas-are-load-bearing",
35
+ description: "Tool input and output schemas are the source of truth",
36
+ rationale: "Prose descriptions drift; schemas are machine-checked",
37
+ severity: "critical",
38
+ },
39
+ ];
40
+
41
+ const kbHygienePrinciples: PrincipleWithCheck[] = [
42
+ {
43
+ id: "cite-or-decline",
44
+ description: "Every agent response derived from the KB must cite the source chunk",
45
+ rationale: "Uncited claims are indistinguishable from hallucinations",
46
+ severity: "critical",
47
+ },
48
+ ];
49
+
50
+ export const keelPrinciples: PrincipleRegistry = createPrincipleRegistry([
51
+ { principles: generalSePrinciples },
52
+ { principles: agentTopologyPrinciples },
53
+ { principles: kbHygienePrinciples },
54
+ ]);
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "strict": true,
4
+ "target": "ES2022",
5
+ "module": "NodeNext",
6
+ "moduleResolution": "NodeNext",
7
+ "declaration": true,
8
+ "declarationMap": true,
9
+ "sourceMap": true,
10
+ "esModuleInterop": false,
11
+ "skipLibCheck": true,
12
+ "noUncheckedIndexedAccess": true,
13
+ "noImplicitAny": true,
14
+ "noImplicitOverride": true,
15
+ "exactOptionalPropertyTypes": true,
16
+ "outDir": "dist",
17
+ "lib": ["ES2022"],
18
+ "types": ["node"]
19
+ },
20
+ "include": ["src", "architecture", "principles"],
21
+ "exclude": ["**/*.test.ts", "dist"]
22
+ }