@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.
- package/package.json +2 -2
- package/presets/_base/AGENTS.md.template +3 -0
- package/presets/_base/CLAUDE.md.template +35 -0
- package/presets/_examples/cli-todo/src/program.ts.template +202 -0
- package/presets/_examples/mcp-files/src/mcp.ts.template +181 -0
- package/presets/basic/README.md.template +22 -0
- package/presets/basic/package.json.template +41 -37
- package/presets/basic/src/index.test.ts.template +26 -0
- package/presets/basic/src/index.ts.template +32 -15
- package/presets/basic/tsconfig.json.template +28 -29
- package/presets/cli/CLAUDE.md.template +60 -0
- package/presets/cli/README.md.template +5 -1
- package/presets/cli/package.json.template +47 -44
- package/presets/cli/src/commands/hello.ts.template +26 -0
- package/presets/cli/src/index.test.ts.template +38 -0
- package/presets/cli/src/index.ts.template +2 -0
- package/presets/cli/src/program.ts.template +17 -19
- package/presets/cli/src/types.ts.template +13 -0
- package/presets/cli/tsconfig.json.template +29 -29
- package/presets/daemon/CLAUDE.md.template +53 -0
- package/presets/daemon/README.md.template +6 -7
- package/presets/daemon/package.json.template +50 -47
- package/presets/daemon/src/cli.ts.template +73 -66
- package/presets/daemon/src/daemon-main.ts.template +56 -55
- package/presets/daemon/src/daemon.ts.template +7 -3
- package/presets/daemon/src/index.test.ts.template +9 -0
- package/presets/daemon/tsconfig.json.template +21 -21
- package/presets/full-stack/CLAUDE.md.template +66 -0
- package/presets/full-stack/apps/cli/package.json.template +34 -33
- package/presets/full-stack/apps/cli/src/cli.ts.template +16 -15
- package/presets/full-stack/apps/cli/src/index.test.ts.template +12 -11
- package/presets/full-stack/apps/cli/tsconfig.json.template +32 -32
- package/presets/full-stack/apps/mcp/package.json.template +35 -34
- package/presets/full-stack/apps/mcp/src/index.test.ts.template +12 -11
- package/presets/full-stack/apps/mcp/src/mcp.ts.template +10 -10
- package/presets/full-stack/apps/mcp/src/server.ts.template +3 -2
- package/presets/full-stack/apps/mcp/tsconfig.json.template +32 -32
- package/presets/full-stack/package.json.template +20 -14
- package/presets/full-stack/packages/core/package.json.template +31 -30
- package/presets/full-stack/packages/core/src/handlers.ts.template +29 -24
- package/presets/full-stack/packages/core/src/index.test.ts.template +23 -21
- package/presets/full-stack/packages/core/src/types.ts.template +11 -8
- package/presets/full-stack/packages/core/tsconfig.json.template +29 -29
- package/presets/library/CLAUDE.md.template +68 -0
- package/presets/library/bunup.config.ts.template +16 -16
- package/presets/library/package.json.template +51 -50
- package/presets/library/src/handlers.ts.template +51 -27
- package/presets/library/src/index.test.ts.template +40 -29
- package/presets/library/src/types.ts.template +14 -8
- package/presets/library/tsconfig.json.template +29 -29
- package/presets/mcp/CLAUDE.md.template +97 -0
- package/presets/mcp/README.md.template +12 -9
- package/presets/mcp/package.json.template +48 -44
- package/presets/mcp/src/index.test.ts.template +49 -0
- package/presets/mcp/src/index.ts.template +2 -0
- package/presets/mcp/src/mcp.ts.template +16 -16
- package/presets/mcp/src/server.ts.template +8 -1
- package/presets/mcp/src/tools/hello.ts.template +48 -0
- package/presets/mcp/tsconfig.json.template +21 -21
- package/presets/minimal/README.md.template +22 -0
- package/presets/minimal/package.json.template +47 -44
- package/presets/minimal/src/index.test.ts.template +19 -0
- package/presets/minimal/src/index.ts.template +10 -15
- package/presets/minimal/tsconfig.json.template +28 -29
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@outfitter/presets",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Scaffold presets and shared dependency versions for Outfitter projects",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"outfitter",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"./package.json": "./package.json"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
|
-
"build": "cd ../.. && bunup --filter @outfitter/presets",
|
|
35
|
+
"build": "cd ../.. && bash ./scripts/run-bunup-with-lock.sh bunup --filter @outfitter/presets",
|
|
36
36
|
"clean": "rm -rf dist .turbo",
|
|
37
37
|
"test": "bun test",
|
|
38
38
|
"typecheck": "tsc --noEmit",
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
Bun-first TypeScript project. Tests before code. Result types, not exceptions.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun run build # Build the project
|
|
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
|
+
Single-package TypeScript project built with Bun.
|
|
21
|
+
|
|
22
|
+
Handlers are pure functions returning `Result<T, E>`. Write the handler once, test it directly.
|
|
23
|
+
|
|
24
|
+
## Development Principles
|
|
25
|
+
|
|
26
|
+
- **TDD-First** — Write the test before the code (Red / Green / Refactor)
|
|
27
|
+
- **Result Types** — Handlers return `Result<T, E>`, not exceptions
|
|
28
|
+
- **Bun-First** — Use Bun-native APIs before npm packages
|
|
29
|
+
- **Strict TypeScript** — No `any`, no `as` casts; narrow instead of assert
|
|
30
|
+
|
|
31
|
+
## Testing
|
|
32
|
+
|
|
33
|
+
- Runner: Bun test runner
|
|
34
|
+
- Files: `src/*.test.ts`
|
|
35
|
+
- Run: `bun test` or `bun run test`
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{projectName}} - CLI program definition
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates the v0.5 builder pattern:
|
|
5
|
+
* - `.input(schema)` for Zod-to-Commander auto-derived flags
|
|
6
|
+
* - `runHandler()` for the full lifecycle bridge
|
|
7
|
+
* - `contextFactory` + `hints` passed in one canonical place
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { command, createCLI } from "@outfitter/cli/command";
|
|
11
|
+
import { runHandler } from "@outfitter/cli/envelope";
|
|
12
|
+
import type { CLIHint } from "@outfitter/contracts";
|
|
13
|
+
import { Result } from "@outfitter/contracts";
|
|
14
|
+
import { createLogger } from "@outfitter/logging";
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
|
|
17
|
+
const logger = createLogger({ name: "{{binName}}" });
|
|
18
|
+
|
|
19
|
+
// =============================================================================
|
|
20
|
+
// Domain Types
|
|
21
|
+
// =============================================================================
|
|
22
|
+
|
|
23
|
+
interface Todo {
|
|
24
|
+
readonly id: number;
|
|
25
|
+
readonly title: string;
|
|
26
|
+
readonly done: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** In-memory store (replace with file/DB in a real app). */
|
|
30
|
+
const store: Todo[] = [];
|
|
31
|
+
let nextId = 1;
|
|
32
|
+
|
|
33
|
+
// =============================================================================
|
|
34
|
+
// Context
|
|
35
|
+
// =============================================================================
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Shared context constructed once per command invocation.
|
|
39
|
+
* The `.context()` builder method calls this factory with the validated input.
|
|
40
|
+
*/
|
|
41
|
+
interface TodoContext {
|
|
42
|
+
readonly count: number;
|
|
43
|
+
readonly pendingCount: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function buildTodoContext(): Promise<TodoContext> {
|
|
47
|
+
return {
|
|
48
|
+
count: store.length,
|
|
49
|
+
pendingCount: store.filter((t) => !t.done).length,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// =============================================================================
|
|
54
|
+
// Schemas
|
|
55
|
+
// =============================================================================
|
|
56
|
+
|
|
57
|
+
const addInputSchema = z.object({
|
|
58
|
+
title: z.string().describe("Title of the todo item"),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const completeInputSchema = z.object({
|
|
62
|
+
id: z.number().describe("ID of the todo to complete"),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const listInputSchema = z.object({
|
|
66
|
+
all: z.boolean().default(false).describe("Show completed todos too"),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// =============================================================================
|
|
70
|
+
// Hint Functions
|
|
71
|
+
// =============================================================================
|
|
72
|
+
|
|
73
|
+
function addHints(
|
|
74
|
+
_result: unknown,
|
|
75
|
+
_input: z.infer<typeof addInputSchema>
|
|
76
|
+
): CLIHint[] {
|
|
77
|
+
return [
|
|
78
|
+
{ description: "List your todos", command: "{{binName}} list" },
|
|
79
|
+
{
|
|
80
|
+
description: "Add another todo",
|
|
81
|
+
command: `{{binName}} add --title "next task"`,
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function completeHints(
|
|
87
|
+
_result: unknown,
|
|
88
|
+
_input: z.infer<typeof completeInputSchema>
|
|
89
|
+
): CLIHint[] {
|
|
90
|
+
return [
|
|
91
|
+
{ description: "List remaining todos", command: "{{binName}} list" },
|
|
92
|
+
{
|
|
93
|
+
description: "Show all including completed",
|
|
94
|
+
command: "{{binName}} list --all",
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// =============================================================================
|
|
100
|
+
// CLI Program
|
|
101
|
+
// =============================================================================
|
|
102
|
+
|
|
103
|
+
export const program = createCLI({
|
|
104
|
+
name: "{{binName}}",
|
|
105
|
+
version: "{{version}}",
|
|
106
|
+
description: "{{description}}",
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* `add` — Create a new todo item.
|
|
111
|
+
*
|
|
112
|
+
* Uses `.input()` to auto-derive `--title` from the Zod schema and
|
|
113
|
+
* `runHandler()` to provide context + hints in one place.
|
|
114
|
+
*/
|
|
115
|
+
program.register(
|
|
116
|
+
command("add")
|
|
117
|
+
.description("Add a new todo item")
|
|
118
|
+
.input(addInputSchema)
|
|
119
|
+
.action(async ({ input }) => {
|
|
120
|
+
await runHandler({
|
|
121
|
+
command: "add",
|
|
122
|
+
input,
|
|
123
|
+
format: "json",
|
|
124
|
+
contextFactory: buildTodoContext,
|
|
125
|
+
hints: addHints,
|
|
126
|
+
handler: async (inp, context) => {
|
|
127
|
+
const todo: Todo = { id: nextId++, title: inp.title, done: false };
|
|
128
|
+
store.push(todo);
|
|
129
|
+
logger.info(`Added todo #${todo.id}: ${todo.title}`);
|
|
130
|
+
return Result.ok({
|
|
131
|
+
id: todo.id,
|
|
132
|
+
title: todo.title,
|
|
133
|
+
pending: context.pendingCount + 1,
|
|
134
|
+
});
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
})
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* `list` — Show current todo items.
|
|
142
|
+
*
|
|
143
|
+
* Uses `.input()` with a boolean flag auto-derived from the schema.
|
|
144
|
+
*/
|
|
145
|
+
program.register(
|
|
146
|
+
command("list")
|
|
147
|
+
.description("List todo items")
|
|
148
|
+
.input(listInputSchema)
|
|
149
|
+
.action(async ({ input }) => {
|
|
150
|
+
await runHandler({
|
|
151
|
+
command: "list",
|
|
152
|
+
input,
|
|
153
|
+
format: "json",
|
|
154
|
+
contextFactory: buildTodoContext,
|
|
155
|
+
handler: async (inp, context) => {
|
|
156
|
+
const items = inp.all ? store : store.filter((t) => !t.done);
|
|
157
|
+
return Result.ok({
|
|
158
|
+
items: items.map((t) => ({
|
|
159
|
+
id: t.id,
|
|
160
|
+
title: t.title,
|
|
161
|
+
done: t.done,
|
|
162
|
+
})),
|
|
163
|
+
total: context.count,
|
|
164
|
+
pending: context.pendingCount,
|
|
165
|
+
});
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
})
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* `complete` — Mark a todo as done.
|
|
173
|
+
*
|
|
174
|
+
* Uses `.input()` with a number flag and delegates hints/error handling
|
|
175
|
+
* through `runHandler()`.
|
|
176
|
+
*/
|
|
177
|
+
program.register(
|
|
178
|
+
command("complete")
|
|
179
|
+
.description("Mark a todo as completed")
|
|
180
|
+
.input(completeInputSchema)
|
|
181
|
+
.action(async ({ input }) => {
|
|
182
|
+
await runHandler({
|
|
183
|
+
command: "complete",
|
|
184
|
+
input,
|
|
185
|
+
format: "json",
|
|
186
|
+
contextFactory: buildTodoContext,
|
|
187
|
+
hints: completeHints,
|
|
188
|
+
onError: () => [
|
|
189
|
+
{ description: "List available todos", command: "{{binName}} list" },
|
|
190
|
+
],
|
|
191
|
+
handler: async (inp) => {
|
|
192
|
+
const todo = store.find((t) => t.id === inp.id);
|
|
193
|
+
if (!todo) {
|
|
194
|
+
return Result.err(new Error(`Todo #${inp.id} not found`));
|
|
195
|
+
}
|
|
196
|
+
(todo as { done: boolean }).done = true;
|
|
197
|
+
logger.info(`Completed todo #${todo.id}: ${todo.title}`);
|
|
198
|
+
return Result.ok({ id: todo.id, title: todo.title, done: true });
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
})
|
|
202
|
+
);
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{projectName}} - MCP server definition
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates v0.5 MCP patterns:
|
|
5
|
+
* - `defineResource()` for static resources
|
|
6
|
+
* - `defineResourceTemplate()` with Zod schema validation for parameterized resources
|
|
7
|
+
* - `defineTool()` for file operations with Result patterns
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Result } from "@outfitter/contracts";
|
|
11
|
+
import {
|
|
12
|
+
createMcpServer,
|
|
13
|
+
connectStdio,
|
|
14
|
+
defineResource,
|
|
15
|
+
defineResourceTemplate,
|
|
16
|
+
defineTool,
|
|
17
|
+
} from "@outfitter/mcp";
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
|
|
20
|
+
// =============================================================================
|
|
21
|
+
// In-memory file store (replace with real filesystem in production)
|
|
22
|
+
// =============================================================================
|
|
23
|
+
|
|
24
|
+
const files = new Map<string, string>([
|
|
25
|
+
["README.md", "# {{projectName}}\n\nWelcome to the project."],
|
|
26
|
+
[
|
|
27
|
+
"config.json",
|
|
28
|
+
JSON.stringify({ name: "{{binName}}", version: "{{version}}" }, null, 2),
|
|
29
|
+
],
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
// =============================================================================
|
|
33
|
+
// Resources — Static
|
|
34
|
+
// =============================================================================
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* List all known files as a static resource.
|
|
38
|
+
* Clients can read this resource to discover available files.
|
|
39
|
+
*/
|
|
40
|
+
const fileListResource = defineResource({
|
|
41
|
+
uri: "files:///index",
|
|
42
|
+
name: "File Index",
|
|
43
|
+
description: "List of all available files in the workspace",
|
|
44
|
+
mimeType: "application/json",
|
|
45
|
+
handler: async (_uri, ctx) => {
|
|
46
|
+
ctx.logger.info("Listing files", { count: files.size });
|
|
47
|
+
const listing = [...files.keys()].map((name) => ({
|
|
48
|
+
name,
|
|
49
|
+
size: files.get(name)?.length ?? 0,
|
|
50
|
+
}));
|
|
51
|
+
return Result.ok([{ uri: _uri, text: JSON.stringify(listing, null, 2) }]);
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// =============================================================================
|
|
56
|
+
// Resources — Parameterized (Templates)
|
|
57
|
+
// =============================================================================
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Read a file by name using a URI template.
|
|
61
|
+
* Uses `defineResourceTemplate()` with Zod schema validation
|
|
62
|
+
* for the `filename` parameter.
|
|
63
|
+
*/
|
|
64
|
+
const fileContentTemplate = defineResourceTemplate({
|
|
65
|
+
uriTemplate: "files:///{filename}",
|
|
66
|
+
name: "File Content",
|
|
67
|
+
description: "Read the content of a specific file",
|
|
68
|
+
mimeType: "text/plain",
|
|
69
|
+
paramSchema: z.object({
|
|
70
|
+
filename: z.string().describe("Name of the file to read"),
|
|
71
|
+
}),
|
|
72
|
+
handler: async (uri, params, ctx) => {
|
|
73
|
+
const { filename } = params as { filename: string };
|
|
74
|
+
ctx.logger.info("Reading file", { filename });
|
|
75
|
+
|
|
76
|
+
const content = files.get(filename);
|
|
77
|
+
if (content === undefined) {
|
|
78
|
+
return Result.err(new Error(`File not found: ${filename}`));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return Result.ok([{ uri, text: content }]);
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// =============================================================================
|
|
86
|
+
// Tools — File Operations
|
|
87
|
+
// =============================================================================
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Write content to a file.
|
|
91
|
+
* Demonstrates `defineTool()` with a Zod input schema and Result return.
|
|
92
|
+
*/
|
|
93
|
+
const writeFileTool = defineTool({
|
|
94
|
+
name: "write_file",
|
|
95
|
+
description: "Write content to a file (creates or overwrites)",
|
|
96
|
+
inputSchema: z.object({
|
|
97
|
+
filename: z.string().describe("Name of the file to write"),
|
|
98
|
+
content: z.string().describe("Content to write to the file"),
|
|
99
|
+
}),
|
|
100
|
+
handler: async (input, ctx) => {
|
|
101
|
+
ctx.logger.info("Writing file", { filename: input.filename });
|
|
102
|
+
const existed = files.has(input.filename);
|
|
103
|
+
files.set(input.filename, input.content);
|
|
104
|
+
return Result.ok({
|
|
105
|
+
filename: input.filename,
|
|
106
|
+
action: existed ? "updated" : "created",
|
|
107
|
+
size: input.content.length,
|
|
108
|
+
});
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Delete a file.
|
|
114
|
+
* Shows error handling with Result.err for missing files.
|
|
115
|
+
*/
|
|
116
|
+
const deleteFileTool = defineTool({
|
|
117
|
+
name: "delete_file",
|
|
118
|
+
description: "Delete a file from the workspace",
|
|
119
|
+
inputSchema: z.object({
|
|
120
|
+
filename: z.string().describe("Name of the file to delete"),
|
|
121
|
+
}),
|
|
122
|
+
handler: async (input, ctx) => {
|
|
123
|
+
ctx.logger.info("Deleting file", { filename: input.filename });
|
|
124
|
+
if (!files.has(input.filename)) {
|
|
125
|
+
return Result.err(new Error(`File not found: ${input.filename}`));
|
|
126
|
+
}
|
|
127
|
+
files.delete(input.filename);
|
|
128
|
+
return Result.ok({ filename: input.filename, deleted: true });
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Search files by content pattern.
|
|
134
|
+
* Demonstrates a read-only tool that queries the file store.
|
|
135
|
+
*/
|
|
136
|
+
const searchFilesTool = defineTool({
|
|
137
|
+
name: "search_files",
|
|
138
|
+
description: "Search for files containing a text pattern",
|
|
139
|
+
inputSchema: z.object({
|
|
140
|
+
pattern: z.string().describe("Text pattern to search for"),
|
|
141
|
+
}),
|
|
142
|
+
handler: async (input, ctx) => {
|
|
143
|
+
ctx.logger.info("Searching files", { pattern: input.pattern });
|
|
144
|
+
const matches: Array<{ filename: string; lineCount: number }> = [];
|
|
145
|
+
for (const [name, content] of files) {
|
|
146
|
+
if (content.includes(input.pattern)) {
|
|
147
|
+
matches.push({ filename: name, lineCount: content.split("\n").length });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return Result.ok({
|
|
151
|
+
pattern: input.pattern,
|
|
152
|
+
matches,
|
|
153
|
+
total: matches.length,
|
|
154
|
+
});
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// =============================================================================
|
|
159
|
+
// Server Setup
|
|
160
|
+
// =============================================================================
|
|
161
|
+
|
|
162
|
+
const server = createMcpServer({
|
|
163
|
+
name: "{{binName}}",
|
|
164
|
+
version: "{{version}}",
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Register resources
|
|
168
|
+
server.registerResource(fileListResource);
|
|
169
|
+
server.registerResourceTemplate(fileContentTemplate);
|
|
170
|
+
|
|
171
|
+
// Register tools
|
|
172
|
+
server.registerTool(writeFileTool);
|
|
173
|
+
server.registerTool(deleteFileTool);
|
|
174
|
+
server.registerTool(searchFilesTool);
|
|
175
|
+
|
|
176
|
+
export { server };
|
|
177
|
+
|
|
178
|
+
/** Start the MCP server over stdio transport. */
|
|
179
|
+
export async function startServer(): Promise<void> {
|
|
180
|
+
await connectStdio(server);
|
|
181
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# {{projectName}}
|
|
2
|
+
|
|
3
|
+
{{description}}
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun install
|
|
9
|
+
bun run dev
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Development
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
bun test # Run tests
|
|
16
|
+
bun run build # Build for production
|
|
17
|
+
bun run typecheck # Type checking
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## License
|
|
21
|
+
|
|
22
|
+
MIT
|
|
@@ -1,39 +1,43 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
+
"zod": "catalog:"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {},
|
|
40
|
+
"engines": {
|
|
41
|
+
"bun": ">=1.3.10"
|
|
42
|
+
}
|
|
39
43
|
}
|
|
@@ -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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
}
|