@pinixai/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # pinixai-core
2
+
3
+ To install dependencies:
4
+
5
+ ```bash
6
+ bun install
7
+ ```
8
+
9
+ To run:
10
+
11
+ ```bash
12
+ bun run index.ts
13
+ ```
14
+
15
+ This project was created using `bun init` in bun v1.3.11. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@pinixai/core",
3
+ "version": "0.1.0",
4
+ "description": "Clip framework for Pinix — define once, run as CLI / MCP / Pinix bridge",
5
+ "main": "src/index.ts",
6
+ "module": "src/index.ts",
7
+ "type": "module",
8
+ "files": [
9
+ "src"
10
+ ],
11
+ "keywords": [
12
+ "clip",
13
+ "pinix",
14
+ "mcp",
15
+ "agent",
16
+ "cli"
17
+ ],
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/epiral/pinixai-core.git"
21
+ },
22
+ "license": "MIT",
23
+ "devDependencies": {
24
+ "@types/bun": "^1.3.11"
25
+ },
26
+ "peerDependencies": {
27
+ "typescript": "^5.9.3"
28
+ },
29
+ "dependencies": {
30
+ "@modelcontextprotocol/sdk": "^1.27.1",
31
+ "zod": "^4.3.6"
32
+ }
33
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,233 @@
1
+ import { z, type ZodError, type ZodObject, type ZodType } from "zod";
2
+ import { zodToManifestType } from "./manifest";
3
+
4
+ export class CLIHelpError extends Error {
5
+ constructor(message: string) {
6
+ super(message);
7
+ this.name = "CLIHelpError";
8
+ }
9
+ }
10
+
11
+ function unwrapSchema(schema: ZodType): ZodType {
12
+ if (
13
+ schema instanceof z.ZodOptional ||
14
+ schema instanceof z.ZodNullable ||
15
+ schema instanceof z.ZodDefault
16
+ ) {
17
+ return unwrapSchema(schema.unwrap());
18
+ }
19
+
20
+ return schema;
21
+ }
22
+
23
+ function getSchemaDescription(schema: ZodType): string | undefined {
24
+ if (schema.description) {
25
+ return schema.description;
26
+ }
27
+
28
+ if (
29
+ schema instanceof z.ZodOptional ||
30
+ schema instanceof z.ZodNullable ||
31
+ schema instanceof z.ZodDefault
32
+ ) {
33
+ return getSchemaDescription(schema.unwrap());
34
+ }
35
+
36
+ return undefined;
37
+ }
38
+
39
+ function getDefaultValue(schema: ZodType): unknown {
40
+ if (schema instanceof z.ZodDefault) {
41
+ return (schema as ZodType & { _def: { defaultValue: unknown } })._def.defaultValue;
42
+ }
43
+
44
+ if (schema instanceof z.ZodOptional || schema instanceof z.ZodNullable) {
45
+ return getDefaultValue(schema.unwrap());
46
+ }
47
+
48
+ return undefined;
49
+ }
50
+
51
+ function hasDefaultValue(schema: ZodType): boolean {
52
+ if (schema instanceof z.ZodDefault) {
53
+ return true;
54
+ }
55
+
56
+ if (schema instanceof z.ZodOptional || schema instanceof z.ZodNullable) {
57
+ return hasDefaultValue(schema.unwrap());
58
+ }
59
+
60
+ return false;
61
+ }
62
+
63
+ function formatOptionType(schema: ZodType): string {
64
+ if (schema instanceof z.ZodOptional || schema instanceof z.ZodDefault) {
65
+ return formatOptionType(schema.unwrap());
66
+ }
67
+
68
+ return zodToManifestType(schema);
69
+ }
70
+
71
+ function formatZodError(error: ZodError): string {
72
+ return error.issues
73
+ .map((issue) => {
74
+ const path = issue.path.length > 0 ? issue.path.join(".") : "input";
75
+ return `${path}: ${issue.message}`;
76
+ })
77
+ .join("\n");
78
+ }
79
+
80
+ function parseBoolean(value: string): boolean {
81
+ if (value === "true" || value === "1") {
82
+ return true;
83
+ }
84
+
85
+ if (value === "false" || value === "0") {
86
+ return false;
87
+ }
88
+
89
+ throw new Error(`Invalid boolean value: ${value}`);
90
+ }
91
+
92
+ function parseScalarValue(value: string, schema: ZodType): unknown {
93
+ const normalized = unwrapSchema(schema);
94
+
95
+ if (normalized instanceof z.ZodNumber) {
96
+ const parsed = Number.parseInt(value, 10);
97
+
98
+ if (Number.isNaN(parsed)) {
99
+ throw new Error(`Invalid number value: ${value}`);
100
+ }
101
+
102
+ return parsed;
103
+ }
104
+
105
+ if (normalized instanceof z.ZodBoolean) {
106
+ return parseBoolean(value);
107
+ }
108
+
109
+ if (normalized instanceof z.ZodLiteral) {
110
+ const literalValues = Array.from((normalized as ZodType & { values: Set<unknown> }).values);
111
+ const literalValue = literalValues[0];
112
+
113
+ if (typeof literalValue === "number") {
114
+ const parsed = Number.parseInt(value, 10);
115
+
116
+ if (Number.isNaN(parsed)) {
117
+ throw new Error(`Invalid number value: ${value}`);
118
+ }
119
+
120
+ return parsed;
121
+ }
122
+
123
+ if (typeof literalValue === "boolean") {
124
+ return parseBoolean(value);
125
+ }
126
+ }
127
+
128
+ return value;
129
+ }
130
+
131
+ export function formatCLIHelp(
132
+ schema: ZodObject,
133
+ commandName?: string,
134
+ describe?: string,
135
+ ): string {
136
+ const lines: string[] = [];
137
+
138
+ if (commandName) {
139
+ lines.push(`Command: ${commandName}`);
140
+ }
141
+
142
+ if (describe) {
143
+ lines.push(describe);
144
+ }
145
+
146
+ const entries = Object.entries(schema.shape);
147
+ lines.push("Arguments:");
148
+
149
+ if (entries.length === 0) {
150
+ lines.push(" (none)");
151
+ }
152
+
153
+ for (const [key, value] of entries) {
154
+ const flags: string[] = [value.isOptional() ? "optional" : "required"];
155
+
156
+ if (hasDefaultValue(value)) {
157
+ flags.push(`default: ${JSON.stringify(getDefaultValue(value))}`);
158
+ }
159
+
160
+ const description = getSchemaDescription(value);
161
+ const suffix = description ? ` - ${description}` : "";
162
+ lines.push(` --${key}: ${formatOptionType(value)} (${flags.join(", ")})${suffix}`);
163
+ }
164
+
165
+ lines.push(" --help: boolean (optional) - Show command help");
166
+
167
+ return lines.join("\n");
168
+ }
169
+
170
+ export function parseCLIArgs<T extends ZodObject>(args: string[], schema: T): z.infer<T> {
171
+ if (args.includes("--help")) {
172
+ throw new CLIHelpError(formatCLIHelp(schema));
173
+ }
174
+
175
+ const values: Record<string, unknown> = {};
176
+ const shape = schema.shape as Record<string, ZodType>;
177
+ let index = 0;
178
+
179
+ while (index < args.length) {
180
+ const arg = args[index];
181
+
182
+ if (arg === undefined) {
183
+ break;
184
+ }
185
+
186
+ if (!arg.startsWith("--")) {
187
+ throw new Error(`Unexpected argument: ${arg}`);
188
+ }
189
+
190
+ const key = arg.slice(2);
191
+ const field = shape[key];
192
+
193
+ if (!field) {
194
+ throw new Error(`Unknown option: --${key}`);
195
+ }
196
+
197
+ const normalized = unwrapSchema(field);
198
+ const next = args[index + 1];
199
+
200
+ if (normalized instanceof z.ZodBoolean && (next === undefined || next.startsWith("--"))) {
201
+ values[key] = true;
202
+ index += 1;
203
+ continue;
204
+ }
205
+
206
+ if (next === undefined) {
207
+ throw new Error(`Missing value for --${key}`);
208
+ }
209
+
210
+ if (normalized instanceof z.ZodArray) {
211
+ const current = values[key];
212
+ const parsed = parseScalarValue(next, normalized.element);
213
+
214
+ if (Array.isArray(current)) {
215
+ current.push(parsed);
216
+ } else {
217
+ values[key] = [parsed];
218
+ }
219
+ } else {
220
+ values[key] = parseScalarValue(next, field);
221
+ }
222
+
223
+ index += 2;
224
+ }
225
+
226
+ const result = schema.safeParse(values);
227
+
228
+ if (!result.success) {
229
+ throw new Error(formatZodError(result.error));
230
+ }
231
+
232
+ return result.data;
233
+ }
package/src/clip.ts ADDED
@@ -0,0 +1,170 @@
1
+ import { z } from "zod";
2
+ import { CLIHelpError, formatCLIHelp, parseCLIArgs } from "./cli";
3
+ import type { HandlerDef } from "./handler";
4
+ import { serveIPC } from "./ipc";
5
+ import { serveMCP } from "./mcp";
6
+ import { generateManifest } from "./manifest";
7
+
8
+ function isHandlerDef(value: unknown): value is HandlerDef {
9
+ if (!value || typeof value !== "object") {
10
+ return false;
11
+ }
12
+
13
+ const candidate = value as Partial<HandlerDef>;
14
+ return (
15
+ candidate.input instanceof z.ZodType &&
16
+ candidate.output instanceof z.ZodType &&
17
+ typeof candidate.fn === "function"
18
+ );
19
+ }
20
+
21
+ export abstract class Clip {
22
+ abstract name: string;
23
+ abstract domain: string;
24
+ abstract patterns: string[];
25
+ entities: Record<string, z.ZodObject<any>> = {};
26
+
27
+ protected readonly commands = new Map<string, HandlerDef>();
28
+ protected readonly commandDescriptions = new Map<string, string>();
29
+
30
+ async start(): Promise<void> {
31
+ const args = process.argv.slice(2);
32
+ const [modeOrCommand, ...restArgs] = args;
33
+
34
+ if (!modeOrCommand || modeOrCommand === "--help") {
35
+ console.log(this.printHelp());
36
+ return;
37
+ }
38
+
39
+ if (modeOrCommand === "--mcp") {
40
+ return serveMCP(this);
41
+ }
42
+
43
+ if (modeOrCommand === "--ipc") {
44
+ return serveIPC(this);
45
+ }
46
+
47
+ if (modeOrCommand === "--manifest") {
48
+ console.log(this.toManifest());
49
+ return;
50
+ }
51
+
52
+ const commandName = modeOrCommand;
53
+ const commandHandler = this.commands.get(commandName);
54
+
55
+ if (!commandHandler) {
56
+ console.error(`Unknown command: ${commandName}`);
57
+ console.log(this.printHelp());
58
+ return;
59
+ }
60
+
61
+ if (!(commandHandler.input instanceof z.ZodObject)) {
62
+ console.error(`CLI mode only supports object input schemas for command: ${commandName}`);
63
+ return;
64
+ }
65
+
66
+ try {
67
+ const input = parseCLIArgs(restArgs, commandHandler.input);
68
+ const output = await commandHandler.fn(input);
69
+ const parsedOutput = commandHandler.output.parse(output);
70
+ console.log(JSON.stringify(parsedOutput));
71
+ } catch (error) {
72
+ if (error instanceof CLIHelpError) {
73
+ console.log(this.printCommandHelp(commandName, commandHandler));
74
+ return;
75
+ }
76
+
77
+ console.error(error instanceof Error ? error.message : String(error));
78
+ }
79
+ }
80
+
81
+ toManifest(): string {
82
+ return generateManifest(this);
83
+ }
84
+
85
+ printHelp(): string {
86
+ const lines: string[] = [
87
+ `${this.name} (${this.domain})`,
88
+ "",
89
+ ];
90
+
91
+ if (this.patterns.length > 0) {
92
+ lines.push("Patterns:");
93
+ for (const pattern of this.patterns) {
94
+ lines.push(` ${pattern}`);
95
+ }
96
+ lines.push("");
97
+ }
98
+
99
+ lines.push("Usage:");
100
+ lines.push(" bun run <script> <command> [options]");
101
+ lines.push(" bun run <script> --help");
102
+ lines.push(" bun run <script> --manifest");
103
+ lines.push(" bun run <script> --mcp");
104
+ lines.push(" bun run <script> --ipc");
105
+ lines.push("");
106
+ lines.push("Commands:");
107
+
108
+ if (this.commands.size === 0) {
109
+ lines.push(" (none)");
110
+ return lines.join("\n");
111
+ }
112
+
113
+ for (const [commandName, commandHandler] of this.commands) {
114
+ const describe = this.commandDescriptions.get(commandName);
115
+ lines.push(` ${commandName}${describe ? ` - ${describe}` : ""}`);
116
+
117
+ if (commandHandler.input instanceof z.ZodObject) {
118
+ const commandHelp = formatCLIHelp(commandHandler.input);
119
+ lines.push(...commandHelp.split("\n").map((line) => ` ${line}`));
120
+ } else {
121
+ lines.push(` Input: ${commandHandler.input.constructor.name}`);
122
+ }
123
+ }
124
+
125
+ return lines.join("\n");
126
+ }
127
+
128
+ getCommands(): ReadonlyMap<string, HandlerDef> {
129
+ return this.commands;
130
+ }
131
+
132
+ getCommandDescription(name: string): string | undefined {
133
+ return this.commandDescriptions.get(name);
134
+ }
135
+
136
+ protected printCommandHelp(commandName: string, commandHandler: HandlerDef): string {
137
+ const describe = this.commandDescriptions.get(commandName);
138
+
139
+ if (!(commandHandler.input instanceof z.ZodObject)) {
140
+ return [
141
+ `Command: ${commandName}`,
142
+ describe,
143
+ `Input: ${commandHandler.input.constructor.name}`,
144
+ ]
145
+ .filter(Boolean)
146
+ .join("\n");
147
+ }
148
+
149
+ return formatCLIHelp(commandHandler.input, commandName, describe);
150
+ }
151
+
152
+ static _registerCommand(target: object, propertyKey: string, describe?: string): void {
153
+ if (!(target instanceof Clip)) {
154
+ throw new Error("@command can only be used on Clip instances");
155
+ }
156
+
157
+ const clip = target as Clip;
158
+ const value = (clip as Record<string, unknown>)[propertyKey];
159
+
160
+ if (!isHandlerDef(value)) {
161
+ throw new Error(`@command can only decorate handler() fields: ${propertyKey}`);
162
+ }
163
+
164
+ clip.commands.set(propertyKey, value);
165
+
166
+ if (describe) {
167
+ clip.commandDescriptions.set(propertyKey, describe);
168
+ }
169
+ }
170
+ }
package/src/command.ts ADDED
@@ -0,0 +1,27 @@
1
+ import type { HandlerDef } from "./handler";
2
+
3
+ type CommandHost = {
4
+ constructor: {
5
+ _registerCommand(target: object, propertyKey: string, describe?: string): void;
6
+ };
7
+ };
8
+
9
+ export function command(describe?: string) {
10
+ return function (
11
+ _value: undefined,
12
+ context: ClassFieldDecoratorContext<CommandHost, HandlerDef>,
13
+ ): void {
14
+ if (context.static) {
15
+ throw new Error("@command can only be used on instance fields");
16
+ }
17
+
18
+ if (context.private) {
19
+ throw new Error("@command cannot be used on private fields");
20
+ }
21
+
22
+ context.addInitializer(function () {
23
+ const constructor = this.constructor as CommandHost["constructor"];
24
+ constructor._registerCommand(this, String(context.name), describe);
25
+ });
26
+ };
27
+ }
package/src/handler.ts ADDED
@@ -0,0 +1,15 @@
1
+ import { z, type ZodType } from "zod";
2
+
3
+ export interface HandlerDef<I extends ZodType = ZodType, O extends ZodType = ZodType> {
4
+ input: I;
5
+ output: O;
6
+ fn: (input: z.infer<I>) => Promise<z.infer<O>>;
7
+ }
8
+
9
+ export function handler<I extends ZodType, O extends ZodType>(
10
+ input: I,
11
+ output: O,
12
+ fn: (input: z.infer<I>) => Promise<z.infer<O>>,
13
+ ): HandlerDef<I, O> {
14
+ return { input, output, fn };
15
+ }
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ export { Clip } from "./clip";
2
+ export { command } from "./command";
3
+ export { handler, type HandlerDef } from "./handler";
4
+ export { serveIPC } from "./ipc";
5
+ export { serveMCP } from "./mcp";
6
+ export { z } from "zod";
package/src/ipc.ts ADDED
@@ -0,0 +1,160 @@
1
+ import type { z } from "zod";
2
+ import type { Clip } from "./clip";
3
+
4
+ type IPCRequest = {
5
+ id?: unknown;
6
+ command?: unknown;
7
+ input?: unknown;
8
+ };
9
+
10
+ type IPCResponse =
11
+ | {
12
+ id: unknown;
13
+ output: unknown;
14
+ }
15
+ | {
16
+ id: unknown;
17
+ error: {
18
+ message: string;
19
+ code: string;
20
+ };
21
+ };
22
+
23
+ function toErrorMessage(error: unknown): string {
24
+ return error instanceof Error ? error.message : String(error);
25
+ }
26
+
27
+ function writeResponse(response: IPCResponse): void {
28
+ process.stdout.write(`${JSON.stringify(response)}\n`);
29
+ }
30
+
31
+ function isObject(value: unknown): value is Record<string, unknown> {
32
+ return value !== null && typeof value === "object";
33
+ }
34
+
35
+ function createErrorResponse(id: unknown, message: string, code: string): IPCResponse {
36
+ return {
37
+ id,
38
+ error: {
39
+ message,
40
+ code,
41
+ },
42
+ };
43
+ }
44
+
45
+ async function handleRequestLine(clip: Clip, line: string): Promise<void> {
46
+ let request: IPCRequest;
47
+
48
+ try {
49
+ request = JSON.parse(line) as IPCRequest;
50
+ } catch (error) {
51
+ writeResponse(createErrorResponse(null, `Invalid JSON: ${toErrorMessage(error)}`, "INVALID_JSON"));
52
+ return;
53
+ }
54
+
55
+ const { id = null, command, input } = request;
56
+
57
+ if (typeof command !== "string" || command.length === 0) {
58
+ writeResponse(createErrorResponse(id, "Request command must be a non-empty string", "INVALID_REQUEST"));
59
+ return;
60
+ }
61
+
62
+ const commandHandler = clip.getCommands().get(command);
63
+
64
+ if (!commandHandler) {
65
+ writeResponse(createErrorResponse(id, `Unknown command: ${command}`, "COMMAND_NOT_FOUND"));
66
+ return;
67
+ }
68
+
69
+ if (!isObject(input)) {
70
+ writeResponse(createErrorResponse(id, "Request input must be an object", "INVALID_INPUT"));
71
+ return;
72
+ }
73
+
74
+ try {
75
+ const parsedInput = await commandHandler.input.parseAsync(input) as z.infer<typeof commandHandler.input>;
76
+ const output = await commandHandler.fn(parsedInput);
77
+ const parsedOutput = await commandHandler.output.parseAsync(output);
78
+
79
+ writeResponse({
80
+ id,
81
+ output: parsedOutput,
82
+ });
83
+ } catch (error) {
84
+ writeResponse(createErrorResponse(id, toErrorMessage(error), "COMMAND_ERROR"));
85
+ }
86
+ }
87
+
88
+ function redirectConsoleLogToStderr(): void {
89
+ console.log = (...args: unknown[]) => {
90
+ const serialized = args
91
+ .map((arg) => {
92
+ if (typeof arg === "string") {
93
+ return arg;
94
+ }
95
+
96
+ try {
97
+ return JSON.stringify(arg);
98
+ } catch {
99
+ return String(arg);
100
+ }
101
+ })
102
+ .join(" ");
103
+
104
+ process.stderr.write(`${serialized}\n`);
105
+ };
106
+ }
107
+
108
+ export async function serveIPC(clip: Clip): Promise<void> {
109
+ redirectConsoleLogToStderr();
110
+
111
+ const reader = Bun.stdin.stream().getReader();
112
+ const decoder = new TextDecoder();
113
+ let buffer = "";
114
+
115
+ try {
116
+ while (true) {
117
+ const { done, value } = await reader.read();
118
+
119
+ if (done) {
120
+ break;
121
+ }
122
+
123
+ buffer += decoder.decode(value, { stream: true });
124
+
125
+ while (true) {
126
+ const newlineIndex = buffer.indexOf("\n");
127
+
128
+ if (newlineIndex === -1) {
129
+ break;
130
+ }
131
+
132
+ const line = buffer.slice(0, newlineIndex).trim();
133
+ buffer = buffer.slice(newlineIndex + 1);
134
+
135
+ if (line.length === 0) {
136
+ continue;
137
+ }
138
+
139
+ try {
140
+ await handleRequestLine(clip, line);
141
+ } catch (error) {
142
+ writeResponse(createErrorResponse(null, toErrorMessage(error), "INTERNAL_ERROR"));
143
+ }
144
+ }
145
+ }
146
+
147
+ buffer += decoder.decode();
148
+ const tail = buffer.trim();
149
+
150
+ if (tail.length > 0) {
151
+ try {
152
+ await handleRequestLine(clip, tail);
153
+ } catch (error) {
154
+ writeResponse(createErrorResponse(null, toErrorMessage(error), "INTERNAL_ERROR"));
155
+ }
156
+ }
157
+ } finally {
158
+ reader.releaseLock();
159
+ }
160
+ }
@@ -0,0 +1,141 @@
1
+ import { z, type ZodType } from "zod";
2
+ import type { Clip } from "./clip";
3
+
4
+ function formatLiteralValue(value: unknown): string {
5
+ return JSON.stringify(value);
6
+ }
7
+
8
+ function getDefaultValue(schema: ZodType): unknown {
9
+ if (schema instanceof z.ZodDefault) {
10
+ return (schema as ZodType & { _def: { defaultValue: unknown } })._def.defaultValue;
11
+ }
12
+
13
+ return undefined;
14
+ }
15
+
16
+ export function zodToManifestType(schema: ZodType): string {
17
+ if (schema instanceof z.ZodOptional) {
18
+ return `${zodToManifestType(schema.unwrap())} (optional)`;
19
+ }
20
+
21
+ if (schema instanceof z.ZodDefault) {
22
+ return `${zodToManifestType(schema.unwrap())} (default: ${formatLiteralValue(getDefaultValue(schema))})`;
23
+ }
24
+
25
+ if (schema instanceof z.ZodNullable) {
26
+ return `${zodToManifestType(schema.unwrap())} | null`;
27
+ }
28
+
29
+ if (schema instanceof z.ZodString) {
30
+ return "string";
31
+ }
32
+
33
+ if (schema instanceof z.ZodNumber) {
34
+ return "number";
35
+ }
36
+
37
+ if (schema instanceof z.ZodBoolean) {
38
+ return "boolean";
39
+ }
40
+
41
+ if (schema instanceof z.ZodEnum) {
42
+ return schema.options.map(formatLiteralValue).join(" | ");
43
+ }
44
+
45
+ if (schema instanceof z.ZodLiteral) {
46
+ const literalValues = Array.from((schema as ZodType & { values: Set<unknown> }).values);
47
+ return literalValues.map(formatLiteralValue).join(" | ");
48
+ }
49
+
50
+ if (schema instanceof z.ZodArray) {
51
+ return `Array<${zodToManifestType(schema.element)}>`;
52
+ }
53
+
54
+ if (schema instanceof z.ZodObject) {
55
+ const entries = Object.entries(schema.shape);
56
+
57
+ if (entries.length === 0) {
58
+ return "{}";
59
+ }
60
+
61
+ return `{ ${entries
62
+ .map(([key, value]) => `${key}${value.isOptional() ? "?" : ""}: ${zodToManifestType(value)}`)
63
+ .join("; ")} }`;
64
+ }
65
+
66
+ if (schema instanceof z.ZodUnion) {
67
+ return schema.options.map(zodToManifestType).join(" | ");
68
+ }
69
+
70
+ if (schema instanceof z.ZodNull) {
71
+ return "null";
72
+ }
73
+
74
+ if (schema instanceof z.ZodUndefined) {
75
+ return "undefined";
76
+ }
77
+
78
+ if (schema instanceof z.ZodAny) {
79
+ return "any";
80
+ }
81
+
82
+ if (schema instanceof z.ZodUnknown) {
83
+ return "unknown";
84
+ }
85
+
86
+ return schema.constructor.name.replace(/^Zod/, "").toLowerCase();
87
+ }
88
+
89
+ export function generateManifest(clip: Clip): string {
90
+ const lines = [
91
+ `Clip: ${clip.name}`,
92
+ `Domain: ${clip.domain}`,
93
+ "",
94
+ ];
95
+
96
+ if (clip.patterns.length > 0) {
97
+ lines.push("Patterns:");
98
+ for (const pattern of clip.patterns) {
99
+ lines.push(` ${pattern}`);
100
+ }
101
+ lines.push("");
102
+ }
103
+
104
+ const entityEntries = Object.entries(clip.entities);
105
+ if (entityEntries.length > 0) {
106
+ lines.push("Entities:");
107
+ for (const [name, schema] of entityEntries) {
108
+ const entityDesc = schema.description;
109
+ lines.push(` ${name}${entityDesc ? ` — ${entityDesc}` : ""}:`);
110
+ for (const [field, fieldSchema] of Object.entries(schema.shape)) {
111
+ const desc = (fieldSchema as z.ZodType).description;
112
+ const type = zodToManifestType(fieldSchema as z.ZodType);
113
+ lines.push(` ${field}: ${type}${desc ? ` — ${desc}` : ""}`);
114
+ }
115
+ }
116
+ lines.push("");
117
+ }
118
+
119
+ lines.push("Commands:");
120
+
121
+ const commands = Array.from(clip.getCommands().entries());
122
+
123
+ if (commands.length === 0) {
124
+ lines.push("- none");
125
+ return lines.join("\n");
126
+ }
127
+
128
+ for (const [name, handler] of commands) {
129
+ lines.push(`- ${name}`);
130
+
131
+ const describe = clip.getCommandDescription(name);
132
+ if (describe) {
133
+ lines.push(` Description: ${describe}`);
134
+ }
135
+
136
+ lines.push(` Input: ${zodToManifestType(handler.input)}`);
137
+ lines.push(` Output: ${zodToManifestType(handler.output)}`);
138
+ }
139
+
140
+ return lines.join("\n");
141
+ }
package/src/mcp.ts ADDED
@@ -0,0 +1,38 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import type { Clip } from "./clip";
4
+
5
+ export async function serveMCP(clip: Clip): Promise<void> {
6
+ const server = new McpServer({
7
+ name: clip.name,
8
+ version: "1.0.0",
9
+ });
10
+
11
+ for (const [name, commandHandler] of clip.getCommands()) {
12
+ const description = clip.getCommandDescription(name);
13
+
14
+ server.registerTool(
15
+ name,
16
+ {
17
+ description,
18
+ inputSchema: commandHandler.input,
19
+ },
20
+ async (input) => {
21
+ const output = await commandHandler.fn(input);
22
+ const parsedOutput = commandHandler.output.parse(output);
23
+
24
+ return {
25
+ content: [
26
+ {
27
+ type: "text" as const,
28
+ text: JSON.stringify(parsedOutput),
29
+ },
30
+ ],
31
+ };
32
+ },
33
+ );
34
+ }
35
+
36
+ const transport = new StdioServerTransport();
37
+ await server.connect(transport);
38
+ }