@outfitter/presets 0.2.1 → 0.3.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 (64) hide show
  1. package/package.json +2 -2
  2. package/presets/_base/AGENTS.md.template +3 -0
  3. package/presets/_base/CLAUDE.md.template +35 -0
  4. package/presets/_examples/cli-todo/src/program.ts.template +202 -0
  5. package/presets/_examples/mcp-files/src/mcp.ts.template +181 -0
  6. package/presets/basic/README.md.template +22 -0
  7. package/presets/basic/package.json.template +41 -37
  8. package/presets/basic/src/index.test.ts.template +26 -0
  9. package/presets/basic/src/index.ts.template +32 -15
  10. package/presets/basic/tsconfig.json.template +28 -29
  11. package/presets/cli/CLAUDE.md.template +60 -0
  12. package/presets/cli/README.md.template +5 -1
  13. package/presets/cli/package.json.template +47 -44
  14. package/presets/cli/src/commands/hello.ts.template +26 -0
  15. package/presets/cli/src/index.test.ts.template +38 -0
  16. package/presets/cli/src/index.ts.template +2 -0
  17. package/presets/cli/src/program.ts.template +17 -19
  18. package/presets/cli/src/types.ts.template +13 -0
  19. package/presets/cli/tsconfig.json.template +29 -29
  20. package/presets/daemon/CLAUDE.md.template +53 -0
  21. package/presets/daemon/README.md.template +6 -7
  22. package/presets/daemon/package.json.template +50 -47
  23. package/presets/daemon/src/cli.ts.template +73 -66
  24. package/presets/daemon/src/daemon-main.ts.template +56 -55
  25. package/presets/daemon/src/daemon.ts.template +7 -3
  26. package/presets/daemon/src/index.test.ts.template +9 -0
  27. package/presets/daemon/tsconfig.json.template +21 -21
  28. package/presets/full-stack/CLAUDE.md.template +66 -0
  29. package/presets/full-stack/apps/cli/package.json.template +34 -33
  30. package/presets/full-stack/apps/cli/src/cli.ts.template +16 -15
  31. package/presets/full-stack/apps/cli/src/index.test.ts.template +12 -11
  32. package/presets/full-stack/apps/cli/tsconfig.json.template +32 -32
  33. package/presets/full-stack/apps/mcp/package.json.template +35 -34
  34. package/presets/full-stack/apps/mcp/src/index.test.ts.template +12 -11
  35. package/presets/full-stack/apps/mcp/src/mcp.ts.template +10 -10
  36. package/presets/full-stack/apps/mcp/src/server.ts.template +3 -2
  37. package/presets/full-stack/apps/mcp/tsconfig.json.template +32 -32
  38. package/presets/full-stack/package.json.template +20 -14
  39. package/presets/full-stack/packages/core/package.json.template +31 -30
  40. package/presets/full-stack/packages/core/src/handlers.ts.template +29 -24
  41. package/presets/full-stack/packages/core/src/index.test.ts.template +23 -21
  42. package/presets/full-stack/packages/core/src/types.ts.template +11 -8
  43. package/presets/full-stack/packages/core/tsconfig.json.template +29 -29
  44. package/presets/library/CLAUDE.md.template +68 -0
  45. package/presets/library/bunup.config.ts.template +16 -16
  46. package/presets/library/package.json.template +51 -50
  47. package/presets/library/src/handlers.ts.template +51 -27
  48. package/presets/library/src/index.test.ts.template +40 -29
  49. package/presets/library/src/types.ts.template +14 -8
  50. package/presets/library/tsconfig.json.template +29 -29
  51. package/presets/mcp/CLAUDE.md.template +97 -0
  52. package/presets/mcp/README.md.template +12 -9
  53. package/presets/mcp/package.json.template +48 -44
  54. package/presets/mcp/src/index.test.ts.template +49 -0
  55. package/presets/mcp/src/index.ts.template +2 -0
  56. package/presets/mcp/src/mcp.ts.template +16 -16
  57. package/presets/mcp/src/server.ts.template +8 -1
  58. package/presets/mcp/src/tools/hello.ts.template +48 -0
  59. package/presets/mcp/tsconfig.json.template +21 -21
  60. package/presets/minimal/README.md.template +22 -0
  61. package/presets/minimal/package.json.template +47 -44
  62. package/presets/minimal/src/index.test.ts.template +19 -0
  63. package/presets/minimal/src/index.ts.template +10 -15
  64. package/presets/minimal/tsconfig.json.template +28 -29
@@ -1,46 +1,50 @@
1
1
  {
2
- "name": "{{packageName}}",
3
- "version": "{{version}}",
4
- "description": "{{description}}",
5
- "type": "module",
6
- "bin": {
7
- "{{binName}}": "./dist/server.js"
8
- },
9
- "main": "./dist/index.js",
10
- "types": "./dist/index.d.ts",
11
- "exports": {
12
- ".": {
13
- "types": "./dist/index.d.ts",
14
- "import": "./dist/index.js"
15
- }
16
- },
17
- "scripts": {
18
- "build": "bun build src/server.ts --outdir dist --target bun && bun build src/index.ts --outdir dist --target bun --sourcemap",
19
- "dev": "bun --watch src/server.ts",
20
- "typecheck": "tsc --noEmit",
21
- "check": "ultracite check",
22
- "verify:ci": "bun run typecheck && bun run check && bun run build && bun run test",
23
- "test": "bun test",
24
- "test:watch": "bun test --watch",
25
- "lint": "oxlint .",
26
- "lint:fix": "oxlint --fix .",
27
- "format": "oxfmt --write .",
28
- "clean:artifacts": "rm -rf dist .turbo"
29
- },
30
- "dependencies": {
31
- "@modelcontextprotocol/sdk": "^1.12.1",
32
- "@outfitter/contracts": "workspace:*",
33
- "@outfitter/types": "workspace:*",
34
- "@outfitter/mcp": "workspace:*",
35
- "@outfitter/logging": "workspace:*"
36
- },
37
- "devDependencies": {},
38
- "outfitter": {
39
- "template": {
40
- "kind": "runnable",
41
- "placement": "apps",
42
- "surfaces": ["mcp"]
43
- }
44
- },
45
- "license": "MIT"
2
+ "name": "{{packageName}}",
3
+ "version": "{{version}}",
4
+ "description": "{{description}}",
5
+ "license": "MIT",
6
+ "bin": {
7
+ "{{binName}}": "./dist/server.js"
8
+ },
9
+ "type": "module",
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ }
17
+ },
18
+ "scripts": {
19
+ "build": "bun build src/server.ts --outdir dist --target bun && bun build src/index.ts --outdir dist --target bun --sourcemap",
20
+ "dev": "bun --watch src/server.ts",
21
+ "typecheck": "tsc --noEmit",
22
+ "check": "ultracite check",
23
+ "verify:ci": "bun run typecheck && bun run check && bun run build && bun run test",
24
+ "test": "bun test",
25
+ "test:watch": "bun test --watch",
26
+ "lint": "oxlint .",
27
+ "lint:fix": "oxlint --fix .",
28
+ "format": "oxfmt --write .",
29
+ "clean:artifacts": "rm -rf dist .turbo"
30
+ },
31
+ "dependencies": {
32
+ "@modelcontextprotocol/sdk": "catalog:",
33
+ "@outfitter/contracts": "workspace:*",
34
+ "@outfitter/mcp": "workspace:*",
35
+ "@outfitter/types": "workspace:*",
36
+ "zod": "catalog:"
37
+ },
38
+ "devDependencies": {
39
+ "@outfitter/testing": "workspace:*"
40
+ },
41
+ "outfitter": {
42
+ "template": {
43
+ "kind": "runnable",
44
+ "placement": "apps",
45
+ "surfaces": [
46
+ "mcp"
47
+ ]
48
+ }
49
+ }
46
50
  }
@@ -0,0 +1,49 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { createContext } from "@outfitter/contracts";
4
+ import { createMcpHarness } from "@outfitter/testing";
5
+
6
+ import { server } from "./mcp.js";
7
+ import { helloTool } from "./tools/hello.js";
8
+
9
+ describe("server", () => {
10
+ test("registers the hello tool", () => {
11
+ const harness = createMcpHarness(server);
12
+ const tools = harness.listTools();
13
+ const registered = tools.some((tool) => tool.name === "hello");
14
+
15
+ expect(registered).toBe(true);
16
+ });
17
+
18
+ test("can call hello tool via harness", async () => {
19
+ const harness = createMcpHarness(server);
20
+ const result = await harness.callTool("hello", {
21
+ name: "Outfitter",
22
+ });
23
+
24
+ expect(result.isOk()).toBe(true);
25
+ });
26
+ });
27
+
28
+ describe("helloTool", () => {
29
+ test("returns greeting for valid input", async () => {
30
+ const ctx = createContext({ cwd: process.cwd(), env: process.env });
31
+ const result = await helloTool.handler({ name: "Outfitter" }, ctx);
32
+
33
+ expect(result.isOk()).toBe(true);
34
+ if (result.isErr()) return;
35
+ expect(result.value.greeting).toBe("Hello, Outfitter!");
36
+ });
37
+
38
+ // Direct handler invocation returns ValidationError.
39
+ // Via harness.callTool(), errors are wrapped as McpError by translateError().
40
+ test("returns error for reserved name", async () => {
41
+ const ctx = createContext({ cwd: process.cwd(), env: process.env });
42
+ const result = await helloTool.handler({ name: "error" }, ctx);
43
+
44
+ expect(result.isErr()).toBe(true);
45
+ if (!result.isErr()) return;
46
+ expect(result.error.name).toBe("ValidationError");
47
+ expect(result.error.message).toBe("name: reserved name not allowed");
48
+ });
49
+ });
@@ -5,3 +5,5 @@
5
5
  */
6
6
 
7
7
  export { server, startServer } from "./mcp.js";
8
+ export { helloTool } from "./tools/hello.js";
9
+ export type { HelloInput, HelloOutput } from "./tools/hello.js";
@@ -3,31 +3,31 @@
3
3
  */
4
4
 
5
5
  import { Result } from "@outfitter/contracts";
6
- import { createMcpServer, defineTool, connectStdio } from "@outfitter/mcp";
7
- import { z } from "zod";
6
+ import { createMcpServer, connectStdio, defineResource } from "@outfitter/mcp";
7
+
8
+ import { helloTool } from "./tools/hello.js";
8
9
 
9
10
  const server = createMcpServer({
10
- name: "{{binName}}",
11
- version: "{{version}}",
11
+ name: "{{binName}}",
12
+ version: "{{version}}",
12
13
  });
13
14
 
14
- const helloTool = defineTool({
15
- name: "hello",
16
- description: "Say hello to someone",
17
- inputSchema: z.object({
18
- name: z.string().default("World").describe("Name to greet"),
19
- }),
20
- handler: async (input, ctx) => {
21
- ctx.logger.info(`Greeting ${input.name}`);
22
- return Result.ok({ greeting: `Hello, ${input.name}!` });
23
- },
15
+ server.registerTool(helloTool);
16
+
17
+ /** Static resource exposing the server version. */
18
+ const versionResource = defineResource({
19
+ uri: "info:///version",
20
+ name: "Server Version",
21
+ description: "Returns the current server version",
22
+ handler: async (uri, _ctx) =>
23
+ Result.ok([{ uri, text: JSON.stringify({ version: "{{version}}" }) }]),
24
24
  });
25
25
 
26
- server.registerTool(helloTool);
26
+ server.registerResource(versionResource);
27
27
 
28
28
  export { server };
29
29
 
30
30
  /** Start the MCP server over stdio transport. */
31
31
  export async function startServer(): Promise<void> {
32
- await connectStdio(server);
32
+ await connectStdio(server);
33
33
  }
@@ -3,6 +3,13 @@
3
3
  * {{projectName}} MCP server entry point
4
4
  */
5
5
 
6
+ import { createLogger } from "@outfitter/logging";
7
+
6
8
  import { startServer } from "./mcp.js";
7
9
 
8
- startServer().catch(console.error);
10
+ const logger = createLogger({ name: "{{binName}}" });
11
+
12
+ startServer().catch((error: unknown) => {
13
+ logger.error("Server failed", { error });
14
+ process.exit(1);
15
+ });
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Hello tool - demonstrates MCP tool definition with Result types.
3
+ */
4
+
5
+ import { Result, ValidationError } from "@outfitter/contracts";
6
+ import { defineTool } from "@outfitter/mcp";
7
+ import { type ZodType, z } from "zod";
8
+
9
+ /** Input for the hello tool. */
10
+ export interface HelloInput {
11
+ readonly name: string;
12
+ }
13
+
14
+ /** Zod schema for hello tool input validation. */
15
+ export const helloInputSchema: ZodType<HelloInput> = z.object({
16
+ name: z.string().min(1, "name must not be empty").describe("Name to greet"),
17
+ });
18
+
19
+ /** Output from the hello tool. */
20
+ export interface HelloOutput {
21
+ readonly greeting: string;
22
+ }
23
+
24
+ /** Tool that greets a user by name. */
25
+ export const helloTool = defineTool({
26
+ name: "hello",
27
+ description: "Say hello to someone",
28
+ inputSchema: helloInputSchema,
29
+ annotations: {
30
+ readOnlyHint: true,
31
+ openWorldHint: false,
32
+ },
33
+ handler: async (
34
+ input,
35
+ _ctx
36
+ ): Promise<Result<HelloOutput, ValidationError>> => {
37
+ // Demonstrate Result.err() for business logic errors
38
+ if (input.name.toLowerCase() === "error") {
39
+ return Result.err(
40
+ ValidationError.create("name", "reserved name not allowed", {
41
+ reserved: ["error"],
42
+ })
43
+ );
44
+ }
45
+
46
+ return Result.ok({ greeting: `Hello, ${input.name}!` });
47
+ },
48
+ });
@@ -1,23 +1,23 @@
1
1
  {
2
- "compilerOptions": {
3
- "target": "ESNext",
4
- "module": "ESNext",
5
- "moduleResolution": "bundler",
6
- "strict": true,
7
- "esModuleInterop": true,
8
- "verbatimModuleSyntax": true,
9
- "skipLibCheck": true,
10
- "declaration": true,
11
- "declarationDir": "dist",
12
- "outDir": "dist",
13
- "rootDir": "src",
14
- "types": ["bun-types"],
15
- "noImplicitAny": true,
16
- "strictNullChecks": true,
17
- "noUncheckedIndexedAccess": true,
18
- "exactOptionalPropertyTypes": true,
19
- "noPropertyAccessFromIndexSignature": true
20
- },
21
- "include": ["src"],
22
- "exclude": ["node_modules", "dist"]
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "verbatimModuleSyntax": true,
9
+ "skipLibCheck": true,
10
+ "declaration": true,
11
+ "declarationDir": "dist",
12
+ "outDir": "dist",
13
+ "rootDir": "src",
14
+ "types": ["bun-types"],
15
+ "noImplicitAny": true,
16
+ "strictNullChecks": true,
17
+ "noUncheckedIndexedAccess": true,
18
+ "exactOptionalPropertyTypes": true,
19
+ "noPropertyAccessFromIndexSignature": true
20
+ },
21
+ "include": ["src"],
22
+ "exclude": ["node_modules", "dist"]
23
23
  }
@@ -0,0 +1,22 @@
1
+ # {{projectName}}
2
+
3
+ {{description}}
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ bun install
9
+ bun run dev
10
+ ```
11
+
12
+ ## Scripts
13
+
14
+ ```bash
15
+ bun test # Run tests
16
+ bun run build # Build
17
+ bun run typecheck # Type checking
18
+ ```
19
+
20
+ ## License
21
+
22
+ MIT
@@ -1,46 +1,49 @@
1
1
  {
2
- "name": "{{packageName}}",
3
- "version": "{{version}}",
4
- "description": "{{description}}",
5
- "type": "module",
6
- "main": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
8
- "files": [
9
- "dist"
10
- ],
11
- "exports": {
12
- ".": {
13
- "import": {
14
- "types": "./dist/index.d.ts",
15
- "default": "./dist/index.js"
16
- }
17
- }
18
- },
19
- "scripts": {
20
- "build": "bun build ./src/index.ts --outdir=dist --format=esm",
21
- "dev": "bun run --watch src/index.ts",
22
- "test": "bun test",
23
- "test:watch": "bun test --watch",
24
- "typecheck": "tsc --noEmit",
25
- "check": "ultracite check",
26
- "verify:ci": "bun run typecheck && bun run check && bun run build && bun run test",
27
- "lint": "oxlint .",
28
- "lint:fix": "oxlint --fix .",
29
- "format": "oxfmt --write .",
30
- "clean:artifacts": "rm -rf dist .turbo",
31
- "clean": "rm -rf dist"
32
- },
33
- "devDependencies": {},
34
- "outfitter": {
35
- "template": {
36
- "kind": "library",
37
- "placement": "packages",
38
- "surfaces": []
39
- }
40
- },
41
- "engines": {
42
- "bun": ">=1.3.10"
43
- },
44
- "keywords": [],
45
- "license": "MIT"
2
+ "name": "{{packageName}}",
3
+ "version": "{{version}}",
4
+ "description": "{{description}}",
5
+ "keywords": [],
6
+ "license": "MIT",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "type": "module",
11
+ "main": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "import": {
16
+ "types": "./dist/index.d.ts",
17
+ "default": "./dist/index.js"
18
+ }
19
+ }
20
+ },
21
+ "scripts": {
22
+ "build": "bun build ./src/index.ts --outdir=dist --format=esm && tsc --emitDeclarationOnly",
23
+ "dev": "bun run --watch src/index.ts",
24
+ "test": "bun test",
25
+ "test:watch": "bun test --watch",
26
+ "typecheck": "tsc --noEmit",
27
+ "check": "ultracite check",
28
+ "verify:ci": "bun run typecheck && bun run check && bun run build && bun run test",
29
+ "lint": "oxlint .",
30
+ "lint:fix": "oxlint --fix .",
31
+ "format": "oxfmt --write .",
32
+ "clean:artifacts": "rm -rf dist .turbo",
33
+ "clean": "rm -rf dist"
34
+ },
35
+ "dependencies": {
36
+ "@outfitter/contracts": "workspace:*"
37
+ },
38
+ "devDependencies": {},
39
+ "engines": {
40
+ "bun": ">=1.3.10"
41
+ },
42
+ "outfitter": {
43
+ "template": {
44
+ "kind": "library",
45
+ "placement": "packages",
46
+ "surfaces": []
47
+ }
48
+ }
46
49
  }
@@ -0,0 +1,19 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { greet } from "./index.js";
4
+
5
+ describe("greet", () => {
6
+ test("returns greeting for valid name", () => {
7
+ const result = greet("World");
8
+
9
+ expect(result.isOk()).toBe(true);
10
+ if (result.isErr()) return;
11
+ expect(result.value.message).toBe("Hello, World!");
12
+ });
13
+
14
+ test("returns error for empty name", () => {
15
+ const result = greet("");
16
+
17
+ expect(result.isErr()).toBe(true);
18
+ });
19
+ });
@@ -6,21 +6,16 @@
6
6
  * @packageDocumentation
7
7
  */
8
8
 
9
- /**
10
- * Main entry point for {{projectName}}.
11
- *
12
- * @example
13
- * ```typescript
14
- * import { main } from "{{packageName}}";
15
- *
16
- * main();
17
- * ```
18
- */
19
- export function main(): void {
20
- console.log("Hello from {{projectName}}!");
9
+ import { Result } from "@outfitter/contracts";
10
+
11
+ export interface Greeting {
12
+ readonly message: string;
21
13
  }
22
14
 
23
- // Run if executed directly
24
- if (import.meta.main) {
25
- main();
15
+ export function greet(name: string): Result<Greeting, Error> {
16
+ if (!name) {
17
+ return Result.err(new Error("name is required"));
18
+ }
19
+
20
+ return Result.ok({ message: `Hello, ${name}!` });
26
21
  }
@@ -1,34 +1,33 @@
1
1
  {
2
- "$schema": "https://json.schemastore.org/tsconfig",
3
- "compilerOptions": {
4
- "target": "ESNext",
5
- "module": "ESNext",
6
- "moduleResolution": "bundler",
7
- "lib": ["ESNext"],
8
- "types": ["@types/bun"],
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "compilerOptions": {
4
+ "target": "ESNext",
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "lib": ["ESNext"],
8
+ "types": ["@types/bun"],
9
9
 
10
- "strict": true,
11
- "noImplicitAny": true,
12
- "strictNullChecks": true,
13
- "noUncheckedIndexedAccess": true,
14
- "exactOptionalPropertyTypes": true,
15
- "noPropertyAccessFromIndexSignature": true,
16
- "noImplicitReturns": true,
17
- "noFallthroughCasesInSwitch": true,
10
+ "strict": true,
11
+ "noImplicitAny": true,
12
+ "strictNullChecks": true,
13
+ "noUncheckedIndexedAccess": true,
14
+ "exactOptionalPropertyTypes": true,
15
+ "noPropertyAccessFromIndexSignature": true,
16
+ "noImplicitReturns": true,
17
+ "noFallthroughCasesInSwitch": true,
18
18
 
19
- "declaration": true,
20
- "declarationMap": true,
21
- "sourceMap": true,
22
- "outDir": "./dist",
23
- "rootDir": "./src",
19
+ "declaration": true,
20
+ "sourceMap": true,
21
+ "outDir": "./dist",
22
+ "rootDir": "./src",
24
23
 
25
- "esModuleInterop": true,
26
- "forceConsistentCasingInFileNames": true,
27
- "isolatedModules": true,
28
- "verbatimModuleSyntax": true,
29
- "skipLibCheck": true,
30
- "resolveJsonModule": true
31
- },
32
- "include": ["src/**/*"],
33
- "exclude": ["node_modules", "dist"]
24
+ "esModuleInterop": true,
25
+ "forceConsistentCasingInFileNames": true,
26
+ "isolatedModules": true,
27
+ "verbatimModuleSyntax": true,
28
+ "skipLibCheck": true,
29
+ "resolveJsonModule": true
30
+ },
31
+ "include": ["src/**/*"],
32
+ "exclude": ["node_modules", "dist", "src/**/*.test.ts"]
34
33
  }