mcpill 1.3.0 → 1.5.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.
@@ -1,116 +0,0 @@
1
- import { describe, it, expect, afterEach } from "vitest";
2
- import fs from "fs";
3
- import path from "path";
4
- import os from "os";
5
- import { loadPrompts } from "../../loaders/prompts.js";
6
-
7
- describe("loadPrompts", () => {
8
- const dirs: string[] = [];
9
-
10
- function mkTmp(): string {
11
- const d = fs.mkdtempSync(path.join(os.tmpdir(), "mcpill-prompts-"));
12
- dirs.push(d);
13
- return d;
14
- }
15
-
16
- function writePrompts(dir: string, content: string) {
17
- fs.writeFileSync(path.join(dir, "prompts.json"), content);
18
- }
19
-
20
- afterEach(() => {
21
- for (const d of dirs.splice(0)) {
22
- fs.rmSync(d, { recursive: true, force: true });
23
- }
24
- });
25
-
26
- it("parses valid JSON correctly", async () => {
27
- const dir = mkTmp();
28
- writePrompts(
29
- dir,
30
- JSON.stringify([
31
- {
32
- name: "summarize",
33
- description: "Summarize text",
34
- args: { text: { type: "string" }, count: { type: "number" } },
35
- messages: [{ role: "user", content: "Summarize: {{text}}" }],
36
- },
37
- ])
38
- );
39
- const prompts = await loadPrompts(dir);
40
- expect(prompts).toHaveLength(1);
41
- expect(prompts[0]!.name).toBe("summarize");
42
- expect(Object.keys(prompts[0]!.argsShape)).toEqual(["text", "count"]);
43
- });
44
-
45
- it("throws on invalid JSON", async () => {
46
- const dir = mkTmp();
47
- writePrompts(dir, `{ not valid json`);
48
- await expect(loadPrompts(dir)).rejects.toThrow("prompts.json is not valid JSON:");
49
- });
50
-
51
- it("throws when file is missing", async () => {
52
- const dir = mkTmp();
53
- await expect(loadPrompts(dir)).rejects.toThrow("prompts.json is not valid JSON:");
54
- });
55
-
56
- it("throws on unsupported arg type", async () => {
57
- const dir = mkTmp();
58
- writePrompts(
59
- dir,
60
- JSON.stringify([
61
- {
62
- name: "greet",
63
- args: { user: { type: "object" } },
64
- messages: [],
65
- },
66
- ])
67
- );
68
- await expect(loadPrompts(dir)).rejects.toThrow(
69
- "Prompt 'greet': arg 'user' uses unsupported type 'object'. Supported: string, number, boolean"
70
- );
71
- });
72
-
73
- it("interpolates {{placeholder}} in message content", async () => {
74
- const dir = mkTmp();
75
- writePrompts(
76
- dir,
77
- JSON.stringify([
78
- {
79
- name: "greet",
80
- args: { name: { type: "string" } },
81
- messages: [{ role: "user", content: "Hello, {{name}}!" }],
82
- },
83
- ])
84
- );
85
- const prompts = await loadPrompts(dir);
86
- const { messages } = prompts[0]!;
87
-
88
- // Replicate the interpolation logic from run.ts
89
- const args: Record<string, unknown> = { name: "world" };
90
- const result = messages.map((m) => ({
91
- role: m.role,
92
- content: m.content.replace(
93
- /\{\{(\w+)\}\}/g,
94
- (_, k: string) => String(args[k] ?? "")
95
- ),
96
- }));
97
-
98
- expect(result[0]!.content).toBe("Hello, world!");
99
- });
100
-
101
- it("handles prompts with no args", async () => {
102
- const dir = mkTmp();
103
- writePrompts(
104
- dir,
105
- JSON.stringify([
106
- {
107
- name: "hello",
108
- args: {},
109
- messages: [{ role: "user", content: "Hello!" }],
110
- },
111
- ])
112
- );
113
- const prompts = await loadPrompts(dir);
114
- expect(prompts[0]!.argsShape).toEqual({});
115
- });
116
- });
@@ -1,86 +0,0 @@
1
- import { describe, it, expect, afterEach } from "vitest";
2
- import fs from "fs";
3
- import path from "path";
4
- import os from "os";
5
- import { loadResources } from "../../loaders/resources.js";
6
-
7
- describe("loadResources", () => {
8
- const dirs: string[] = [];
9
-
10
- function mkTmp(): string {
11
- const d = fs.mkdtempSync(path.join(os.tmpdir(), "mcpill-resources-"));
12
- dirs.push(d);
13
- return d;
14
- }
15
-
16
- function writeResources(dir: string, content: string) {
17
- fs.writeFileSync(path.join(dir, "resources.md"), content);
18
- }
19
-
20
- afterEach(() => {
21
- for (const d of dirs.splice(0)) {
22
- fs.rmSync(d, { recursive: true, force: true });
23
- }
24
- });
25
-
26
- it("parses a single resource block", async () => {
27
- const dir = mkTmp();
28
- writeResources(
29
- dir,
30
- "uri: config://app\nname: App Config\n---\nThis is the config resource."
31
- );
32
- const resources = await loadResources(dir);
33
- expect(resources).toHaveLength(1);
34
- expect(resources[0]!.uri).toBe("config://app");
35
- expect(resources[0]!.name).toBe("App Config");
36
- expect(resources[0]!.content).toBe("This is the config resource.");
37
- });
38
-
39
- it("parses multiple resource blocks", async () => {
40
- const dir = mkTmp();
41
- writeResources(
42
- dir,
43
- [
44
- "uri: config://app\nname: App Config",
45
- "Config content here.",
46
- "uri: data://schema\nname: Schema",
47
- "Schema content here.",
48
- ].join("\n---\n")
49
- );
50
- const resources = await loadResources(dir);
51
- expect(resources).toHaveLength(2);
52
- expect(resources[0]!.uri).toBe("config://app");
53
- expect(resources[1]!.uri).toBe("data://schema");
54
- expect(resources[1]!.content).toBe("Schema content here.");
55
- });
56
-
57
- it("throws when a block is missing uri", async () => {
58
- const dir = mkTmp();
59
- writeResources(dir, "name: App Config\n---\nSome content.");
60
- await expect(loadResources(dir)).rejects.toThrow(
61
- "resources.md block 1 is missing required frontmatter field: uri"
62
- );
63
- });
64
-
65
- it("handles empty body gracefully", async () => {
66
- const dir = mkTmp();
67
- writeResources(dir, "uri: config://app\n---\n");
68
- const resources = await loadResources(dir);
69
- expect(resources[0]!.content).toBe("");
70
- });
71
-
72
- it("throws when resources.md is missing", async () => {
73
- const dir = mkTmp();
74
- await expect(loadResources(dir)).rejects.toThrow();
75
- });
76
-
77
- it("parses optional mimeType from frontmatter", async () => {
78
- const dir = mkTmp();
79
- writeResources(
80
- dir,
81
- "uri: config://app\nmimeType: application/json\n---\n{}"
82
- );
83
- const resources = await loadResources(dir);
84
- expect(resources[0]!.mimeType).toBe("application/json");
85
- });
86
- });
@@ -1,128 +0,0 @@
1
- import { describe, it, expect, afterEach } from "vitest";
2
- import fs from "fs";
3
- import path from "path";
4
- import os from "os";
5
- import { loadTools } from "../../loaders/tools.js";
6
-
7
- // CJS-format tools.js files work reliably in temp dirs without needing
8
- // a package.json with "type":"module" — Node exposes module.exports as .default
9
- // when imported via import().
10
-
11
- describe("loadTools", () => {
12
- const dirs: string[] = [];
13
-
14
- function mkTmp(): string {
15
- const d = fs.mkdtempSync(path.join(os.tmpdir(), "mcpill-tools-"));
16
- dirs.push(d);
17
- return d;
18
- }
19
-
20
- function writeTools(dir: string, content: string) {
21
- fs.writeFileSync(path.join(dir, "tools.js"), content);
22
- }
23
-
24
- afterEach(() => {
25
- for (const d of dirs.splice(0)) {
26
- fs.rmSync(d, { recursive: true, force: true });
27
- }
28
- });
29
-
30
- it("valid tools.js export passes", async () => {
31
- const dir = mkTmp();
32
- writeTools(
33
- dir,
34
- `module.exports = [
35
- { name: "read_file", description: "Read a file", schema: {}, handler: async () => ({}) }
36
- ]`
37
- );
38
- const tools = await loadTools(dir);
39
- expect(tools).toHaveLength(1);
40
- expect(tools[0]!.name).toBe("read_file");
41
- expect(tools[0]!.description).toBe("Read a file");
42
- });
43
-
44
- it("throws when tools.js is missing", async () => {
45
- const dir = mkTmp();
46
- await expect(loadTools(dir)).rejects.toThrow("tools.js failed to load:");
47
- });
48
-
49
- it("throws when tools.js has a syntax error", async () => {
50
- const dir = mkTmp();
51
- writeTools(dir, `module.exports = [{ this is broken syntax`);
52
- await expect(loadTools(dir)).rejects.toThrow("tools.js failed to load:");
53
- });
54
-
55
- it("throws when default export is not an array", async () => {
56
- const dir = mkTmp();
57
- writeTools(dir, `module.exports = { name: "not-an-array" }`);
58
- await expect(loadTools(dir)).rejects.toThrow(
59
- "tools.js failed to load: default export must be an array"
60
- );
61
- });
62
-
63
- it("throws when name is missing", async () => {
64
- const dir = mkTmp();
65
- writeTools(
66
- dir,
67
- `module.exports = [{ description: "no name", schema: {}, handler: async () => ({}) }]`
68
- );
69
- await expect(loadTools(dir)).rejects.toThrow(
70
- "Tool at index 0 is missing required field: name"
71
- );
72
- });
73
-
74
- it("throws when description is missing", async () => {
75
- const dir = mkTmp();
76
- writeTools(
77
- dir,
78
- `module.exports = [{ name: "t", schema: {}, handler: async () => ({}) }]`
79
- );
80
- await expect(loadTools(dir)).rejects.toThrow(
81
- "Tool at index 0 is missing required field: description"
82
- );
83
- });
84
-
85
- it("throws when schema is missing", async () => {
86
- const dir = mkTmp();
87
- writeTools(
88
- dir,
89
- `module.exports = [{ name: "t", description: "d", handler: async () => ({}) }]`
90
- );
91
- await expect(loadTools(dir)).rejects.toThrow(
92
- "Tool at index 0 is missing required field: schema"
93
- );
94
- });
95
-
96
- it("throws when handler is missing", async () => {
97
- const dir = mkTmp();
98
- writeTools(
99
- dir,
100
- `module.exports = [{ name: "t", description: "d", schema: {} }]`
101
- );
102
- await expect(loadTools(dir)).rejects.toThrow(
103
- "Tool at index 0 is missing required field: handler"
104
- );
105
- });
106
-
107
- it("throws when handler is not a function", async () => {
108
- const dir = mkTmp();
109
- writeTools(
110
- dir,
111
- `module.exports = [{ name: "t", description: "d", schema: {}, handler: "not-a-function" }]`
112
- );
113
- await expect(loadTools(dir)).rejects.toThrow(
114
- "Tool 't': handler must be a function"
115
- );
116
- });
117
-
118
- it("throws when schema field is a plain value instead of a Zod type", async () => {
119
- const dir = mkTmp();
120
- writeTools(
121
- dir,
122
- `module.exports = [{ name: "foo", description: "d", schema: { input: { type: "string" } }, handler: async () => ({}) }]`
123
- );
124
- await expect(loadTools(dir)).rejects.toThrow(
125
- "Tool 'foo': schema field 'input' must be a Zod type"
126
- );
127
- });
128
- });
@@ -1,98 +0,0 @@
1
- import { describe, it, expect, vi, afterEach } from "vitest";
2
- import fs from "fs";
3
- import path from "path";
4
- import os from "os";
5
- import { runPack, SERVER_ENTRY_TEMPLATE } from "../commands/pack.js";
6
-
7
- function scaffoldPill(baseDir: string, name = "test-pill") {
8
- const mcpillDir = path.join(baseDir, ".mcpill", "server");
9
- fs.mkdirSync(mcpillDir, { recursive: true });
10
- fs.writeFileSync(
11
- path.join(mcpillDir, "tools.js"),
12
- `module.exports = [{ name: "t", description: "d", schema: {}, handler: async () => ({}) }]`
13
- );
14
- fs.writeFileSync(
15
- path.join(mcpillDir, "prompts.json"),
16
- JSON.stringify([{ name: "p", args: {}, messages: [] }])
17
- );
18
- fs.writeFileSync(
19
- path.join(mcpillDir, "resources.md"),
20
- "uri: config://app\n---\nContent."
21
- );
22
- fs.writeFileSync(
23
- path.join(mcpillDir, "mcpill.config.json"),
24
- JSON.stringify({ name, transport: "stdio", port: 3333 })
25
- );
26
- }
27
-
28
- describe("runPack", () => {
29
- const dirs: string[] = [];
30
-
31
- function mkTmp(): string {
32
- const d = fs.mkdtempSync(path.join(os.tmpdir(), "mcpill-pack-"));
33
- dirs.push(d);
34
- return d;
35
- }
36
-
37
- function mockExit() {
38
- return vi
39
- .spyOn(process, "exit")
40
- .mockImplementation((_code?: number | string | null) => {
41
- throw new Error("process.exit called");
42
- });
43
- }
44
-
45
- afterEach(() => {
46
- vi.restoreAllMocks();
47
- for (const d of dirs.splice(0)) {
48
- fs.rmSync(d, { recursive: true, force: true });
49
- }
50
- });
51
-
52
- it("writes bin/server.js with SERVER_ENTRY_TEMPLATE", async () => {
53
- const base = mkTmp();
54
- scaffoldPill(base);
55
- vi.spyOn(console, "log").mockImplementation(() => {});
56
-
57
- await runPack(base);
58
-
59
- const serverJs = fs.readFileSync(path.join(base, "bin", "server.js"), "utf-8");
60
- expect(serverJs).toBe(SERVER_ENTRY_TEMPLATE);
61
- });
62
-
63
- it("writes package.json with bin entry and mcpill-runtime dependency", async () => {
64
- const base = mkTmp();
65
- scaffoldPill(base);
66
- vi.spyOn(console, "log").mockImplementation(() => {});
67
-
68
- await runPack(base);
69
-
70
- const pkg = JSON.parse(fs.readFileSync(path.join(base, "package.json"), "utf-8"));
71
- expect(pkg.bin).toHaveProperty("test-pill");
72
- expect(pkg.dependencies["mcpill-runtime"]).toBe("^0.1.0");
73
- });
74
-
75
- it("does not overwrite existing name/version in package.json", async () => {
76
- const base = mkTmp();
77
- scaffoldPill(base);
78
- fs.writeFileSync(
79
- path.join(base, "package.json"),
80
- JSON.stringify({ name: "my-pill", version: "1.2.3" }, null, 2)
81
- );
82
- vi.spyOn(console, "log").mockImplementation(() => {});
83
-
84
- await runPack(base);
85
-
86
- const pkg = JSON.parse(fs.readFileSync(path.join(base, "package.json"), "utf-8"));
87
- expect(pkg.name).toBe("my-pill");
88
- expect(pkg.version).toBe("1.2.3");
89
- });
90
-
91
- it("throws when no pill directory is present", async () => {
92
- const base = mkTmp();
93
- mockExit();
94
- vi.spyOn(console, "error").mockImplementation(() => {});
95
-
96
- await expect(runPack(base)).rejects.toThrow();
97
- });
98
- });
@@ -1,152 +0,0 @@
1
- import { describe, it, expect, vi, afterEach } from "vitest";
2
- import fs from "fs";
3
- import path from "path";
4
- import os from "os";
5
- import { runValidate } from "../commands/validate.js";
6
-
7
- // Sets up a minimal valid .<name>/ pill dir using CJS tools.js so dynamic
8
- // import works without a mcpill-runtime resolution in the temp tree.
9
- function scaffoldValid(mcpillDir: string) {
10
- fs.mkdirSync(mcpillDir, { recursive: true });
11
- fs.writeFileSync(
12
- path.join(mcpillDir, "tools.js"),
13
- `module.exports = [{ name: "t", description: "d", schema: {}, handler: async () => ({}) }]`
14
- );
15
- fs.writeFileSync(
16
- path.join(mcpillDir, "prompts.json"),
17
- JSON.stringify([{ name: "p", args: {}, messages: [] }])
18
- );
19
- fs.writeFileSync(
20
- path.join(mcpillDir, "resources.md"),
21
- "uri: config://app\n---\nContent."
22
- );
23
- fs.writeFileSync(
24
- path.join(mcpillDir, "mcpill.config.json"),
25
- JSON.stringify({ name: "srv", transport: "stdio", port: 3333 })
26
- );
27
- }
28
-
29
- describe("runValidate", () => {
30
- const dirs: string[] = [];
31
-
32
- function mkTmp(): string {
33
- const d = fs.mkdtempSync(path.join(os.tmpdir(), "mcpill-validate-"));
34
- dirs.push(d);
35
- return d;
36
- }
37
-
38
- function mockExit() {
39
- return vi
40
- .spyOn(process, "exit")
41
- .mockImplementation((_code?: number | string | null) => {
42
- throw new Error("process.exit called");
43
- });
44
- }
45
-
46
- afterEach(() => {
47
- vi.restoreAllMocks();
48
- for (const d of dirs.splice(0)) {
49
- fs.rmSync(d, { recursive: true, force: true });
50
- }
51
- });
52
-
53
- it("passes on a valid scaffolded pill dir", async () => {
54
- const base = mkTmp();
55
- const mcpillDir = path.join(base, ".mcpill", "server");
56
- scaffoldValid(mcpillDir);
57
-
58
- const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
59
- await runValidate(base);
60
- expect(logSpy).toHaveBeenCalledWith("✓ Valid: 1 tools, 1 prompts, 1 resources");
61
- });
62
-
63
- it("exits with code 1 when no pill directories found", async () => {
64
- const base = mkTmp();
65
- const exitSpy = mockExit();
66
- const errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
67
-
68
- await expect(runValidate(base)).rejects.toThrow("process.exit called");
69
- expect(exitSpy).toHaveBeenCalledWith(1);
70
- expect(errSpy).toHaveBeenCalledWith(
71
- "No pill directories found — run mcpill compile first"
72
- );
73
- });
74
-
75
- it("collects and prints tool error then exits 1", async () => {
76
- const base = mkTmp();
77
- const mcpillDir = path.join(base, ".mcpill", "server");
78
- scaffoldValid(mcpillDir);
79
- // Break tools.js: handler is not a function
80
- fs.writeFileSync(
81
- path.join(mcpillDir, "tools.js"),
82
- `module.exports = [{ name: "t", description: "d", schema: {}, handler: "bad" }]`
83
- );
84
-
85
- const exitSpy = mockExit();
86
- const errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
87
-
88
- await expect(runValidate(base)).rejects.toThrow("process.exit called");
89
- expect(exitSpy).toHaveBeenCalledWith(1);
90
- expect(errSpy).toHaveBeenCalledWith("Tool 't': handler must be a function");
91
- });
92
-
93
- it("collects and prints prompt error then exits 1", async () => {
94
- const base = mkTmp();
95
- const mcpillDir = path.join(base, ".mcpill", "server");
96
- scaffoldValid(mcpillDir);
97
- fs.writeFileSync(path.join(mcpillDir, "prompts.json"), "not json {{{");
98
-
99
- const exitSpy = mockExit();
100
- const errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
101
-
102
- await expect(runValidate(base)).rejects.toThrow("process.exit called");
103
- expect(exitSpy).toHaveBeenCalledWith(1);
104
- expect(
105
- errSpy.mock.calls.some((c) =>
106
- String(c[0]).startsWith("prompts.json is not valid JSON:")
107
- )
108
- ).toBe(true);
109
- });
110
-
111
- it("collects and prints resource error then exits 1", async () => {
112
- const base = mkTmp();
113
- const mcpillDir = path.join(base, ".mcpill", "server");
114
- scaffoldValid(mcpillDir);
115
- // Block missing uri
116
- fs.writeFileSync(
117
- path.join(mcpillDir, "resources.md"),
118
- "name: No Uri\n---\nContent."
119
- );
120
-
121
- const exitSpy = mockExit();
122
- const errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
123
-
124
- await expect(runValidate(base)).rejects.toThrow("process.exit called");
125
- expect(exitSpy).toHaveBeenCalledWith(1);
126
- expect(errSpy).toHaveBeenCalledWith(
127
- "resources.md block 1 is missing required frontmatter field: uri"
128
- );
129
- });
130
-
131
- it("collects errors from multiple loaders before exiting", async () => {
132
- const base = mkTmp();
133
- const mcpillDir = path.join(base, ".mcpill", "server");
134
- scaffoldValid(mcpillDir);
135
- // Break both tools and prompts
136
- fs.writeFileSync(
137
- path.join(mcpillDir, "tools.js"),
138
- `module.exports = [{ description: "d", schema: {}, handler: async () => ({}) }]`
139
- );
140
- fs.writeFileSync(path.join(mcpillDir, "prompts.json"), "bad json");
141
-
142
- const exitSpy = mockExit();
143
- const errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
144
-
145
- await expect(runValidate(base)).rejects.toThrow("process.exit called");
146
- expect(exitSpy).toHaveBeenCalledWith(1);
147
- // Both errors must have been printed
148
- const messages = errSpy.mock.calls.map((c) => String(c[0]));
149
- expect(messages.some((m) => m.includes("Tool at index 0 is missing"))).toBe(true);
150
- expect(messages.some((m) => m.startsWith("prompts.json is not valid JSON:"))).toBe(true);
151
- });
152
- });
package/src/cli.ts DELETED
@@ -1,77 +0,0 @@
1
- import { Command } from "commander";
2
- import { fileURLToPath } from "url";
3
- import { dirname, join } from "path";
4
- import { readFileSync } from "fs";
5
- import { runInit } from "./commands/init.js";
6
- import { runServer } from "./commands/run.js";
7
- import { runValidate } from "./commands/validate.js";
8
- import { runCompile } from "./commands/compile.js";
9
- import { runPack } from "./commands/pack.js";
10
- import { runPublish } from "./commands/publish.js";
11
-
12
- const pkgDir = dirname(fileURLToPath(import.meta.url));
13
- const pkg = JSON.parse(
14
- readFileSync(join(pkgDir, "../package.json"), "utf-8")
15
- ) as { version: string };
16
-
17
- const program = new Command();
18
-
19
- program.name("mcpill").version(pkg.version);
20
-
21
- program
22
- .command("init")
23
- .description("Scaffold a new .mcpill/ directory")
24
- .option("--dir <path>", "Target directory")
25
- .action(async (opts: { dir?: string }) => {
26
- await runInit(opts);
27
- });
28
-
29
- program
30
- .command("run")
31
- .description("Start the MCP server")
32
- .option("--transport <transport>", "Transport type: stdio or http")
33
- .option("--port <n>", "Port number (HTTP only)", parseInt)
34
- .option("--dir <path>", "Directory containing .mcpill/")
35
- .action(
36
- async (opts: { transport?: "stdio" | "http"; port?: number; dir?: string }) => {
37
- await runServer(opts);
38
- }
39
- );
40
-
41
- program
42
- .command("validate")
43
- .description("Validate .mcpill/ configuration")
44
- .option("--dir <path>", "Directory containing .mcpill/")
45
- .action(async (opts: { dir?: string }) => {
46
- const { resolve } = await import("path");
47
- await runValidate(resolve(opts.dir ?? process.cwd()));
48
- });
49
-
50
- program
51
- .command("compile")
52
- .description("Compile server.md ↔ .mcpill/ files")
53
- .option("--dir <path>", "Directory containing server.md and .mcpill/")
54
- .option("--to-md", "Reverse: generate server.md from .mcpill/ files")
55
- .option("--strict", "Error on missing tool handlers instead of generating stubs")
56
- .option("--no-hooks", "Skip writing the PreToolUse hook to .claude/settings.json")
57
- .action(async (opts: { dir?: string; toMd?: boolean; strict?: boolean; hooks?: boolean }) => {
58
- await runCompile({ ...opts, noHooks: opts.hooks === false });
59
- });
60
-
61
- program
62
- .command("pack")
63
- .description("Prepare pill for npm distribution")
64
- .option("--dir <path>", "pill project root", ".")
65
- .action(({ dir }: { dir: string }) => runPack(dir));
66
-
67
- program
68
- .command("publish")
69
- .description("Pack and publish pill to npm")
70
- .option("--dir <path>", "pill project root", ".")
71
- .option("--access <level>", "npm access level", "public")
72
- .action(({ dir, access }: { dir: string; access: string }) => runPublish(dir, access));
73
-
74
- program.parseAsync(process.argv).catch((err) => {
75
- console.error(err instanceof Error ? err.message : String(err));
76
- process.exit(1);
77
- });