@outfitter/presets 0.2.0 → 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 (74) hide show
  1. package/README.md +30 -0
  2. package/dist/index.js +1 -1
  3. package/package.json +25 -24
  4. package/presets/_base/AGENTS.md.template +3 -0
  5. package/presets/_base/CLAUDE.md.template +35 -0
  6. package/presets/_examples/cli-todo/src/program.ts.template +202 -0
  7. package/presets/_examples/mcp-files/src/mcp.ts.template +181 -0
  8. package/presets/basic/.lefthook.yml.template +2 -2
  9. package/presets/basic/README.md.template +22 -0
  10. package/presets/basic/package.json.template +41 -37
  11. package/presets/basic/src/index.test.ts.template +26 -0
  12. package/presets/basic/src/index.ts.template +32 -15
  13. package/presets/basic/tsconfig.json.template +28 -29
  14. package/presets/cli/.lefthook.yml.template +2 -2
  15. package/presets/cli/CLAUDE.md.template +60 -0
  16. package/presets/cli/README.md.template +5 -1
  17. package/presets/cli/package.json.template +47 -44
  18. package/presets/cli/src/commands/hello.ts.template +26 -0
  19. package/presets/cli/src/index.test.ts.template +38 -0
  20. package/presets/cli/src/index.ts.template +2 -0
  21. package/presets/cli/src/program.ts.template +17 -19
  22. package/presets/cli/src/types.ts.template +13 -0
  23. package/presets/cli/tsconfig.json.template +29 -29
  24. package/presets/daemon/.lefthook.yml.template +2 -2
  25. package/presets/daemon/CLAUDE.md.template +53 -0
  26. package/presets/daemon/README.md.template +6 -7
  27. package/presets/daemon/package.json.template +50 -47
  28. package/presets/daemon/src/cli.ts.template +73 -66
  29. package/presets/daemon/src/daemon-main.ts.template +56 -55
  30. package/presets/daemon/src/daemon.ts.template +7 -3
  31. package/presets/daemon/src/index.test.ts.template +9 -0
  32. package/presets/daemon/tsconfig.json.template +21 -21
  33. package/presets/full-stack/CLAUDE.md.template +66 -0
  34. package/presets/full-stack/apps/cli/package.json.template +34 -33
  35. package/presets/full-stack/apps/cli/src/cli.ts.template +16 -15
  36. package/presets/full-stack/apps/cli/src/index.test.ts.template +12 -11
  37. package/presets/full-stack/apps/cli/tsconfig.json.template +32 -32
  38. package/presets/full-stack/apps/mcp/package.json.template +35 -34
  39. package/presets/full-stack/apps/mcp/src/index.test.ts.template +12 -11
  40. package/presets/full-stack/apps/mcp/src/mcp.ts.template +10 -10
  41. package/presets/full-stack/apps/mcp/src/server.ts.template +3 -2
  42. package/presets/full-stack/apps/mcp/tsconfig.json.template +32 -32
  43. package/presets/full-stack/package.json.template +20 -14
  44. package/presets/full-stack/packages/core/package.json.template +31 -30
  45. package/presets/full-stack/packages/core/src/handlers.ts.template +29 -24
  46. package/presets/full-stack/packages/core/src/index.test.ts.template +23 -21
  47. package/presets/full-stack/packages/core/src/types.ts.template +11 -8
  48. package/presets/full-stack/packages/core/tsconfig.json.template +29 -29
  49. package/presets/library/CLAUDE.md.template +68 -0
  50. package/presets/library/bunup.config.ts.template +16 -16
  51. package/presets/library/package.json.template +51 -50
  52. package/presets/library/src/handlers.ts.template +51 -27
  53. package/presets/library/src/index.test.ts.template +40 -29
  54. package/presets/library/src/types.ts.template +14 -8
  55. package/presets/library/tsconfig.json.template +29 -29
  56. package/presets/mcp/.lefthook.yml.template +2 -2
  57. package/presets/mcp/CLAUDE.md.template +97 -0
  58. package/presets/mcp/README.md.template +12 -9
  59. package/presets/mcp/package.json.template +48 -44
  60. package/presets/mcp/src/index.test.ts.template +49 -0
  61. package/presets/mcp/src/index.ts.template +2 -0
  62. package/presets/mcp/src/mcp.ts.template +16 -16
  63. package/presets/mcp/src/server.ts.template +8 -1
  64. package/presets/mcp/src/tools/hello.ts.template +48 -0
  65. package/presets/mcp/tsconfig.json.template +21 -21
  66. package/presets/minimal/.lefthook.yml.template +2 -2
  67. package/presets/minimal/README.md.template +22 -0
  68. package/presets/minimal/package.json.template +47 -44
  69. package/presets/minimal/src/index.test.ts.template +19 -0
  70. package/presets/minimal/src/index.ts.template +10 -15
  71. package/presets/minimal/tsconfig.json.template +28 -29
  72. package/presets/cli/biome.json.template +0 -4
  73. package/presets/daemon/biome.json.template +0 -4
  74. package/presets/mcp/biome.json.template +0 -4
@@ -0,0 +1,26 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { createContext } from "@outfitter/contracts";
4
+
5
+ import { greet } from "./index.js";
6
+
7
+ describe("greet", () => {
8
+ const ctx = createContext({ cwd: process.cwd(), env: process.env });
9
+
10
+ test("returns greeting for valid input", async () => {
11
+ const result = await greet({ name: "World" }, ctx);
12
+
13
+ expect(result.isOk()).toBe(true);
14
+ if (result.isErr()) return;
15
+ expect(result.value.message).toBe("Hello, World!");
16
+ });
17
+
18
+ test("returns validation error for empty name", async () => {
19
+ const result = await greet({ name: "" }, ctx);
20
+
21
+ expect(result.isErr()).toBe(true);
22
+ if (result.isErr()) {
23
+ expect(result.error.name).toBe("ValidationError");
24
+ }
25
+ });
26
+ });
@@ -6,21 +6,38 @@
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 {
10
+ type HandlerContext,
11
+ Result,
12
+ ValidationError,
13
+ } from "@outfitter/contracts";
14
+ import { type ZodType, z } from "zod";
15
+
16
+ export interface GreetingInput {
17
+ readonly name: string;
21
18
  }
22
19
 
23
- // Run if executed directly
24
- if (import.meta.main) {
25
- main();
20
+ export const greetingInputSchema: ZodType<GreetingInput> = z.object({
21
+ name: z.string().min(1, "name is required"),
22
+ });
23
+
24
+ export interface Greeting {
25
+ readonly message: string;
26
+ }
27
+
28
+ export async function greet(
29
+ input: unknown,
30
+ ctx: HandlerContext
31
+ ): Promise<Result<Greeting, ValidationError>> {
32
+ const parsed = greetingInputSchema.safeParse(input);
33
+ if (!parsed.success) {
34
+ return Result.err(
35
+ new ValidationError({ message: "Invalid input", field: "name" })
36
+ );
37
+ }
38
+
39
+ ctx.logger.info(`Greeting ${parsed.data.name}`, {
40
+ requestId: ctx.requestId,
41
+ });
42
+ return Result.ok({ message: `Hello, ${parsed.data.name}!` });
26
43
  }
@@ -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
  }
@@ -6,12 +6,12 @@ pre-commit:
6
6
  commands:
7
7
  format:
8
8
  glob: "*.{js,ts,tsx,json,md}"
9
- run: bunx biome format --no-errors-on-unmatched {staged_files}
9
+ run: bunx oxfmt --write {staged_files}
10
10
  stage_fixed: true
11
11
 
12
12
  lint:
13
13
  glob: "*.{js,ts,tsx}"
14
- run: bunx biome lint --no-errors-on-unmatched {staged_files}
14
+ run: bunx oxlint {staged_files}
15
15
 
16
16
  typecheck:
17
17
  glob: "*.{ts,tsx}"
@@ -0,0 +1,60 @@
1
+ # CLAUDE.md
2
+
3
+ Bun-first TypeScript CLI. Tests before code. Result types, not exceptions.
4
+
5
+ ## Commands
6
+
7
+ ```bash
8
+ bun run build # Build CLI + library to dist/
9
+ bun run dev # Watch mode (auto-restart on changes)
10
+ bun run test # Run tests
11
+ bun run typecheck # TypeScript validation
12
+ bun run check # Lint + format check
13
+ bun run lint:fix # Auto-fix lint issues
14
+ bun run format # Auto-fix formatting
15
+ bun run verify:ci # Full CI validation (typecheck + check + build + test)
16
+ ```
17
+
18
+ ## Architecture
19
+
20
+ CLI application built with Commander and `@outfitter/cli`.
21
+
22
+ ### Project Structure
23
+
24
+ - `src/cli.ts` — Entry point, creates and runs the program
25
+ - `src/program.ts` — Command registration and wiring
26
+ - `src/commands/` — Command handlers (pure functions returning `Result<T, E>`)
27
+ - `src/types.ts` — Zod schemas and TypeScript interfaces
28
+
29
+ ### Handler Contract
30
+
31
+ All domain logic uses handlers returning `Result<T, E>`:
32
+
33
+ ```typescript
34
+ function handler(
35
+ input: Input,
36
+ ctx: HandlerContext
37
+ ): Promise<Result<Output, Error>>;
38
+ ```
39
+
40
+ CLI commands are thin adapters over shared handlers. Use `runHandler()` from `@outfitter/cli/envelope` to wrap handler results in output envelopes.
41
+
42
+ ### Adding a Command
43
+
44
+ 1. Define types and Zod schema in `src/types.ts`
45
+ 2. Create handler in `src/commands/<name>.ts` returning `Result<T, E>`
46
+ 3. Wire command in `src/program.ts` using the CommandBuilder pattern
47
+ 4. Add tests in `src/<name>.test.ts`
48
+
49
+ ## Development Principles
50
+
51
+ - **TDD-First** — Write the test before the code (Red / Green / Refactor)
52
+ - **Result Types** — Handlers return `Result<T, E>`, not exceptions
53
+ - **Bun-First** — Use Bun-native APIs before npm packages
54
+ - **Strict TypeScript** — No `any`, no `as` casts; narrow instead of assert
55
+
56
+ ## Testing
57
+
58
+ - Runner: Bun test runner
59
+ - Files: `src/*.test.ts`
60
+ - Run: `bun test` or `bun run test`
@@ -5,7 +5,11 @@
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- bun add {{packageName}}
8
+ # Run directly
9
+ bunx {{packageName}}
10
+
11
+ # Or install globally
12
+ bun add -g {{packageName}}
9
13
  ```
10
14
 
11
15
  ## Usage
@@ -1,46 +1,49 @@
1
1
  {
2
- "name": "{{packageName}}",
3
- "version": "{{version}}",
4
- "description": "{{description}}",
5
- "type": "module",
6
- "bin": {
7
- "{{binName}}": "./dist/cli.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/cli.ts --outdir dist --target bun && bun build src/index.ts --outdir dist --target bun --sourcemap",
19
- "dev": "bun --watch src/cli.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": "biome check .",
26
- "lint:fix": "biome check . --write",
27
- "format": "biome format --write .",
28
- "clean:artifacts": "rm -rf dist .turbo"
29
- },
30
- "dependencies": {
31
- "@outfitter/contracts": "workspace:*",
32
- "@outfitter/types": "workspace:*",
33
- "@outfitter/cli": "workspace:*",
34
- "@outfitter/logging": "workspace:*",
35
- "commander": "^14.0.2"
36
- },
37
- "devDependencies": {},
38
- "outfitter": {
39
- "template": {
40
- "kind": "runnable",
41
- "placement": "apps",
42
- "surfaces": ["cli"]
43
- }
44
- },
45
- "license": "MIT"
2
+ "name": "{{packageName}}",
3
+ "version": "{{version}}",
4
+ "description": "{{description}}",
5
+ "license": "MIT",
6
+ "bin": {
7
+ "{{binName}}": "./dist/cli.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/cli.ts --outdir dist --target bun && bun build src/index.ts --outdir dist --target bun --sourcemap",
20
+ "dev": "bun --watch src/cli.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
+ "@outfitter/cli": "workspace:*",
33
+ "@outfitter/contracts": "workspace:*",
34
+ "@outfitter/logging": "workspace:*",
35
+ "@outfitter/types": "workspace:*",
36
+ "commander": "catalog:",
37
+ "zod": "catalog:"
38
+ },
39
+ "devDependencies": {},
40
+ "outfitter": {
41
+ "template": {
42
+ "kind": "runnable",
43
+ "placement": "apps",
44
+ "surfaces": [
45
+ "cli"
46
+ ]
47
+ }
48
+ }
46
49
  }
@@ -0,0 +1,26 @@
1
+ import {
2
+ Result,
3
+ ValidationError,
4
+ type HandlerContext,
5
+ } from "@outfitter/contracts";
6
+ import { createLogger } from "@outfitter/logging";
7
+
8
+ import { greetingInputSchema, type GreetingOutput } from "../types.js";
9
+
10
+ const logger = createLogger({ name: "{{binName}}" });
11
+
12
+ export async function greet(
13
+ input: unknown,
14
+ ctx: HandlerContext
15
+ ): Promise<Result<GreetingOutput, ValidationError>> {
16
+ const parsed = greetingInputSchema.safeParse(input);
17
+ if (!parsed.success) {
18
+ return Result.err(
19
+ new ValidationError({ message: "Invalid greeting input", field: "name" })
20
+ );
21
+ }
22
+
23
+ const message = `Hello, ${parsed.data.name}!`;
24
+ logger.info(message, { requestId: ctx.requestId });
25
+ return Result.ok({ message });
26
+ }
@@ -0,0 +1,38 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { createContext } from "@outfitter/contracts";
4
+
5
+ import { greet } from "./commands/hello.js";
6
+ import { program } from "./index.js";
7
+
8
+ describe("program", () => {
9
+ test("registers the hello command", () => {
10
+ expect(program.program.name()).toBe("{{binName}}");
11
+
12
+ const registered = program.program.commands.some(
13
+ (cmd) => cmd.name() === "hello"
14
+ );
15
+ expect(registered).toBe(true);
16
+ });
17
+ });
18
+
19
+ describe("greet", () => {
20
+ test("returns greeting for valid input", async () => {
21
+ const ctx = createContext({ cwd: process.cwd(), env: process.env });
22
+ const result = await greet({ name: "Outfitter" }, ctx);
23
+
24
+ expect(result.isOk()).toBe(true);
25
+ if (result.isErr()) return;
26
+ expect(result.value.message).toBe("Hello, Outfitter!");
27
+ });
28
+
29
+ test("returns validation error for empty name", async () => {
30
+ const ctx = createContext({ cwd: process.cwd(), env: process.env });
31
+ const result = await greet({ name: "" }, ctx);
32
+
33
+ expect(result.isErr()).toBe(true);
34
+ if (result.isErr()) {
35
+ expect(result.error.name).toBe("ValidationError");
36
+ }
37
+ });
38
+ });
@@ -5,3 +5,5 @@
5
5
  */
6
6
 
7
7
  export { program } from "./program.js";
8
+ export { greet } from "./commands/hello.js";
9
+ export type { GreetingInput, GreetingOutput } from "./types.js";
@@ -3,29 +3,27 @@
3
3
  */
4
4
 
5
5
  import { command, createCLI } from "@outfitter/cli/command";
6
- import { verbosePreset } from "@outfitter/cli/flags";
7
- import { createLogger } from "@outfitter/logging";
6
+ import { runHandler } from "@outfitter/cli/envelope";
7
+ import { createContext } from "@outfitter/contracts";
8
8
 
9
- const logger = createLogger({ name: "{{binName}}" });
9
+ import { greet } from "./commands/hello.js";
10
10
 
11
11
  export const program = createCLI({
12
- name: "{{binName}}",
13
- version: "{{version}}",
14
- description: "{{description}}",
12
+ name: "{{binName}}",
13
+ version: "{{version}}",
14
+ description: "{{description}}",
15
15
  });
16
16
 
17
- const verbose = verbosePreset();
18
-
19
17
  program.register(
20
- command("hello [name]")
21
- .description("Say hello")
22
- .preset(verbose)
23
- .action<{ verbose: boolean }>(async ({ args, flags }) => {
24
- const { verbose: isVerbose } = verbose.resolve(flags);
25
- const name = args[0] ?? "World";
26
- logger.info(`Hello, ${name}!`);
27
- if (isVerbose) {
28
- logger.debug("Running in verbose mode");
29
- }
30
- }),
18
+ command("hello [name]")
19
+ .description("Say hello")
20
+ .action(async ({ args }) => {
21
+ await runHandler({
22
+ command: "hello",
23
+ input: { name: args[0] ?? "World" },
24
+ contextFactory: () =>
25
+ createContext({ cwd: process.cwd(), env: process.env }),
26
+ handler: greet,
27
+ });
28
+ })
31
29
  );
@@ -0,0 +1,13 @@
1
+ import { type ZodType, z } from "zod";
2
+
3
+ export interface GreetingInput {
4
+ readonly name: string;
5
+ }
6
+
7
+ export const greetingInputSchema: ZodType<GreetingInput> = z.object({
8
+ name: z.string().min(1, "name is required"),
9
+ });
10
+
11
+ export interface GreetingOutput {
12
+ readonly message: string;
13
+ }
@@ -1,34 +1,34 @@
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
+ "declarationMap": true,
21
+ "sourceMap": true,
22
+ "outDir": "./dist",
23
+ "rootDir": "./src",
24
24
 
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"]
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"]
34
34
  }
@@ -6,12 +6,12 @@ pre-commit:
6
6
  commands:
7
7
  format:
8
8
  glob: "*.{js,ts,tsx,json,md}"
9
- run: bunx biome format --no-errors-on-unmatched {staged_files}
9
+ run: bunx oxfmt --write {staged_files}
10
10
  stage_fixed: true
11
11
 
12
12
  lint:
13
13
  glob: "*.{js,ts,tsx}"
14
- run: bunx biome lint --no-errors-on-unmatched {staged_files}
14
+ run: bunx oxlint {staged_files}
15
15
 
16
16
  typecheck:
17
17
  glob: "*.{ts,tsx}"
@@ -0,0 +1,53 @@
1
+ # CLAUDE.md
2
+
3
+ Bun-first TypeScript daemon. Tests before code. Result types, not exceptions.
4
+
5
+ ## Commands
6
+
7
+ ```bash
8
+ bun run build # Build CLI + daemon to dist/
9
+ bun run dev # Watch mode (foreground)
10
+ bun run dev:daemon # Watch mode (foreground, explicit)
11
+ bun run test # Run tests
12
+ bun run typecheck # TypeScript validation
13
+ bun run check # Lint + format check
14
+ bun run lint:fix # Auto-fix lint issues
15
+ bun run format # Auto-fix formatting
16
+ bun run verify:ci # Full CI validation (typecheck + check + build + test)
17
+ ```
18
+
19
+ ## Architecture
20
+
21
+ Background daemon with CLI control interface, built with `@outfitter/daemon` and `@outfitter/cli`.
22
+
23
+ ### Project Structure
24
+
25
+ - `src/daemon.ts` — Daemon entry point (background process)
26
+ - `src/daemon-main.ts` — Daemon lifecycle and HTTP server (Unix socket)
27
+ - `src/cli.ts` — CLI control commands (start, stop, status)
28
+ - `src/index.ts` — Library re-exports
29
+
30
+ ### Daemon Lifecycle
31
+
32
+ The daemon uses `acquireDaemonLock()` and `releaseDaemonLock()` from `@outfitter/daemon` with `Bun.serve()` on a Unix socket:
33
+
34
+ - **start** — Checks liveness, spawns background process (or runs in foreground with `--foreground`)
35
+ - **stop** — Checks liveness, then sends POST to `/shutdown` endpoint
36
+ - **status** — Checks liveness via `isDaemonAlive()`, then fetches `/health` for uptime and version
37
+
38
+ ### Handler Contract
39
+
40
+ CLI commands and daemon operations use handlers returning `Result<T, E>`. Use structured logging via `@outfitter/logging` instead of `console.error`.
41
+
42
+ ## Development Principles
43
+
44
+ - **TDD-First** — Write the test before the code (Red / Green / Refactor)
45
+ - **Result Types** — Handlers return `Result<T, E>`, not exceptions
46
+ - **Bun-First** — Use Bun-native APIs before npm packages
47
+ - **Strict TypeScript** — No `any`, no `as` casts; narrow instead of assert
48
+
49
+ ## Testing
50
+
51
+ - Runner: Bun test runner
52
+ - Files: `src/*.test.ts`
53
+ - Run: `bun test` or `bun run test`
@@ -4,12 +4,6 @@
4
4
 
5
5
  A background daemon with CLI control interface.
6
6
 
7
- ## Installation
8
-
9
- ```bash
10
- bun add {{packageName}}
11
- ```
12
-
13
7
  ## Usage
14
8
 
15
9
  ```bash
@@ -52,7 +46,12 @@ Gracefully shuts down the daemon.
52
46
  # Install dependencies
53
47
  bun install
54
48
 
55
- # Run daemon in foreground
49
+ # Run from source (foreground only — background spawn requires a build)
50
+ bun run src/cli.ts start --foreground
51
+ bun run src/cli.ts status
52
+ bun run src/cli.ts stop
53
+
54
+ # Run daemon in foreground (via dev script)
56
55
  bun run dev
57
56
 
58
57
  # Build