formhaus 0.0.0-stage → 0.14.2

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 (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +35 -2
  3. package/dist/cli.js +1223 -0
  4. package/package.json +45 -3
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 formhaus contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,36 @@
1
- # Temporary Holding Version
1
+ # formhaus
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Adds the [Formhaus](https://formhaus.dev) MCP server and agent instructions to the coding agents on your machine.
4
+
5
+ ```bash
6
+ npx formhaus
7
+ ```
8
+
9
+ Run it inside a project to also add the Cursor rule there. Requires Node 20 or later.
10
+
11
+ ## What it changes
12
+
13
+ | Agent | Detected by | Change |
14
+ |-------|-------------|--------|
15
+ | Claude Code | `claude` on `PATH` | `claude mcp add --scope user formhaus -- npx -y @formhaus/mcp` and the `formhaus-create-form` skill in `~/.claude/skills` |
16
+ | Codex | `~/.codex` or `codex` on `PATH` | `[mcp_servers.formhaus]` in `~/.codex/config.toml` |
17
+ | Cursor | `~/.cursor` or `cursor` on `PATH` | `formhaus` in `~/.cursor/mcp.json`, and `.cursor/rules/formhaus.mdc` when run inside a project |
18
+ | Claude Desktop | its config directory on macOS and Windows | `formhaus` in `claude_desktop_config.json` |
19
+
20
+ Windsurf, Gemini CLI, OpenCode, VS Code and Claude Desktop on Linux are detected and listed with manual setup steps.
21
+
22
+ Existing entries are kept. An existing `formhaus` server with another command or args is reported and left unchanged, and a project `.mcp.json` never counts as set up. Before the first change to a file, the original is saved as `<file>.bak`. Formhaus does not write to a file that is a symbolic link, or through a linked directory inside the project. Running it again changes nothing. If the Formhaus Claude Code plugin is installed, Claude Code is left as is.
23
+
24
+ ## Options
25
+
26
+ ```
27
+ npx formhaus [setup] [options]
28
+
29
+ --dry-run Show the planned changes and write nothing
30
+ -h, --help Show help
31
+ -v, --version Show the version
32
+ ```
33
+
34
+ The exit code is 0 when every agent is set up, including when nothing changed, and 1 when a step fails.
35
+
36
+ [MCP server guide](https://formhaus.dev/guide/mcp) · MIT
package/dist/cli.js ADDED
@@ -0,0 +1,1223 @@
1
+ #!/usr/bin/env node
2
+ import { homedir } from 'os';
3
+ import { parseArgs, isDeepStrictEqual } from 'util';
4
+ import { existsSync, statSync, accessSync, constants, readFileSync, mkdirSync, copyFileSync, rmSync, writeFileSync, chmodSync, renameSync, lstatSync } from 'fs';
5
+ import { join, isAbsolute, sep, dirname, relative, resolve } from 'path';
6
+ import { spawnSync } from 'child_process';
7
+
8
+ // package.json
9
+ var version = "0.14.2";
10
+
11
+ // ../../.cursor/rules/formhaus.mdc
12
+ var formhaus_default = '---\ndescription: Formhaus form definitions. Use when creating, editing or reviewing a Formhaus JSON form definition, or code that passes one to FormEngine or FormRenderer.\nalwaysApply: false\n---\n\n# Formhaus form definitions\n\nA Formhaus form is a JSON definition rendered by `@formhaus/core` with `@formhaus/react` or `@formhaus/vue`. One definition drives the web form and the Figma plugin.\n\n- Start each definition file with `"$schema": "https://formhaus.dev/schema/form-definition.json"`.\n- Required keys: `id` (kebab-case), `title`, `submit: { "label": "..." }`.\n- Use `fields` for a single page or `steps` for a multi-step form, never both.\n- Field `key` values are camelCase and unique across all steps.\n- `show` and `showAny` conditions reference existing field keys and use `eq`, `neq`, `in`, `notIn` or `notEmpty`.\n- Step `routes` only point to later steps. `to: null` ends the form without auto-submission.\n\n## Checking definitions\n\nWhen the `formhaus` MCP server is available (`npx -y @formhaus/mcp`):\n\n- Call `capabilities` for the current field types, validation rules and step semantics.\n- Call `example_definitions` for working examples.\n- Call `validate_definition` after every change and fix all `errors` before finishing.\n- Call `simulate_path` with sample answers to check branching steps and conditions.\n\nReference: https://formhaus.dev/api/definition.html\n';
13
+
14
+ // ../../plugins/formhaus/skills/formhaus-create-form/SKILL.md
15
+ var SKILL_default = '---\nname: formhaus-create-form\ndescription: |\n Generate @formhaus/core form definitions from any input: text description,\n CSV/table, or screenshot of a form. Interactive step/condition builder.\n Output pastes directly into the Formhaus Figma plugin or FormRenderer.\n Use when: "generate form", "form definition", "form JSON", "create a form",\n "formhaus", or when user describes form fields in any format.\nallowed-tools:\n - Read\n - Write\n - Glob\n - Grep\n - AskUserQuestion\n - Agent\n - mcp__plugin_formhaus_formhaus__validate_definition\n - mcp__plugin_formhaus_formhaus__simulate_path\n - mcp__plugin_formhaus_formhaus__capabilities\n - mcp__plugin_formhaus_formhaus__example_definitions\n - mcp__plugin_formhaus_formhaus__publish_form\n - mcp__plugin_formhaus_formhaus__list_forms\n - mcp__formhaus__validate_definition\n - mcp__formhaus__simulate_path\n - mcp__formhaus__capabilities\n - mcp__formhaus__example_definitions\n - mcp__formhaus__publish_form\n - mcp__formhaus__list_forms\n - Edit\n - Bash\n---\n\n# Form Definition Generator\n\nYou generate valid `@formhaus/core` form definitions from any input.\nThe output is ready to paste into the Formhaus Figma plugin or use with FormRenderer.\nThe formhaus repo does not need to be cloned. The JSON Schema is published at\n`https://formhaus.dev/schema/form-definition.json`.\n\n## Formhaus MCP tools\n\nCheck whether the `formhaus` MCP server tools are available (`validate_definition`,\n`simulate_path`, `capabilities`, `example_definitions`, `publish_form`). The Formhaus Claude Code plugin\nstarts the server. Without the plugin, it is added with\n`claude mcp add formhaus -- npx -y @formhaus/mcp`.\n\n- `capabilities` returns the current field types, validation rules, condition operators\n and step semantics. Prefer it over the reference below when they differ.\n- `example_definitions` lists bundled example definitions and returns one by `id`.\n- `validate_definition` checks a definition against the JSON Schema and the engine.\n- `simulate_path` walks a multi-step definition with sample answers and reports the step path.\n- `publish_form` publishes a definition to Formhaus Cloud and returns a live endpoint, a hosted page URL, an embed snippet and a React snippet.\n\nIf the tools are not available, validate by hand against the types below and tell the\nuser that `@formhaus/mcp` can check the result.\n\n## Types Reference\n\nThese are the exact TypeScript types. Your output MUST conform to them.\n\n```typescript\ninterface FormDefinition {\n id: string; // kebab-case identifier\n title: string; // human-readable form title\n description?: string; // short intro under the title, up to 1000 characters\n submit: FormAction; // submit button config (required)\n cancel?: FormAction; // cancel button (optional)\n fields?: FormField[]; // single-step form\n steps?: FormStep[]; // multi-step form (use steps OR fields, not both)\n}\n\ninterface FormStep {\n id: string;\n title: string;\n description?: string;\n fields: FormField[];\n show?: ShowCondition[]; // step visible when ALL conditions true\n showAny?: ShowCondition[]; // step visible when ANY condition true\n routes?: StepRoute[]; // ordered forward destinations\n next?: FormAction | false; // custom "next" or false to hide it\n back?: FormAction | false; // custom "back" or false to hide it\n skip?: FormAction; // Skip button: resets the step, moves on without validation\n}\n\ninterface StepRoute {\n to: string | null; // later step id, or terminal without auto-submission\n show?: ShowCondition[];\n showAny?: ShowCondition[];\n}\n\ninterface FormField {\n key: string; // camelCase field identifier\n type: FieldType;\n label: string;\n placeholder?: string;\n helperText?: string;\n defaultValue?: unknown;\n autoAdvance?: boolean; // explicit activation advances; built-in radio supports it\n show?: ShowCondition[]; // field visible when ALL conditions true\n showAny?: ShowCondition[]; // field visible when ANY condition true\n validation?: FieldValidation;\n options?: FieldOption[]; // for select, multiselect, radio\n layout?: \'stack\' | \'inline\'; // radio/multiselect; inline only for a few short options\n optionsFrom?: string; // dynamic options loaded at runtime\n optionsDependsOn?: string[];\n accept?: string; // for file type: "image/*", ".pdf"\n rows?: number; // for textarea\n mask?: string; // input mask\n inputMode?: \'text\' | \'numeric\' | \'decimal\' | \'tel\' | \'email\' | \'url\' | \'search\';\n}\n\ntype FieldType =\n | \'text\' | \'email\' | \'phone\' | \'number\' | \'password\'\n | \'select\' | \'autocomplete\' | \'multiselect\'\n | \'checkbox\' | \'radio\' | \'switch\'\n | \'file\' | \'date\' | \'datetime\' | \'time\' | \'textarea\' | \'hidden\';\n\ninterface ShowCondition {\n field: string; // references another field\'s key\n eq?: string | number | boolean; // equals\n neq?: string | number | boolean; // not equals\n in?: (string | number)[]; // one of\n notIn?: (string | number)[]; // not one of\n notEmpty?: boolean; // has a value\n}\n\ninterface FieldValidation {\n required?: boolean | string; // true or custom message\n minLength?: number;\n minLengthMessage?: string;\n maxLength?: number;\n maxLengthMessage?: string;\n min?: number;\n minMessage?: string;\n max?: number;\n maxMessage?: string;\n pattern?: string; // regex\n patternMessage?: string;\n matchField?: string; // must match another field (e.g. confirmPassword)\n matchFieldMessage?: string;\n validator?: string; // custom validator name\n}\n\ninterface FieldOption { value: string; label: string; }\ninterface FormAction {\n label: string;\n variant?: \'primary\' | \'secondary\' | \'text\';\n action?: string;\n disabled?: ShowCondition[];\n}\n```\n\n## Workflow\n\n### Step 1: Detect input format\n\nLook at the user\'s input and classify:\n\n- **Text description**: "Registration form with name, email, password"\n- **CSV/table**: structured data with columns like key, type, label, required\n- **Screenshot**: user provides a path to an image file or says "look at this screenshot"\n- **Existing definition**: user references a bundled example (`example_definitions`)\n\n### Step 2: Extract fields\n\nFrom the input, extract a flat list of fields with: key, type, label, and any\nobvious validation (required, email format, etc).\n\n**Type inference rules:**\n- Email addresses \u2192 `email`\n- Phone numbers \u2192 `phone`\n- Passwords, PINs \u2192 `password`\n- "Agree to terms", "Subscribe" \u2192 `checkbox`\n- "Account type", "Gender" with 2-4 options \u2192 `radio`\n- Dropdowns, "Select..." with 5+ options \u2192 `select`\n- Searchable dropdowns, typeahead \u2192 `autocomplete`\n- Country, city, currency \u2192 `select`\n- Long text, "Description", "Notes", "Bio" \u2192 `textarea`\n- Dates, birthdays, "When" \u2192 `date`\n- Date and time together \u2192 `datetime`\n- Time of day only, "Start time", "Opening hours" \u2192 `time`\n- Tracking values the user never sees, "Source", "Campaign", "Referral code from the link" \u2192 `hidden`\n- File upload, "Attach", "Upload" \u2192 `file`\n- Toggle, "Enable/Disable", notifications \u2192 `switch`\n- Numbers, amounts, quantities \u2192 `number`\n- Everything else \u2192 `text`\n\n### Step 3: Interactive refinement\n\nUse AskUserQuestion for each decision point. ONE question at a time.\n\n**3a: Steps**\n\nIf the form has 5+ fields, ask:\n\n> "This form has N fields. Should it be a single page or split into steps?\n> If steps, describe how to group them (e.g. \'Personal info, Address, Payment\')."\n\nOptions:\n- A) Single step (all fields on one page)\n- B) Multi-step (I\'ll describe the groups)\n- C) Auto-group (you decide based on field types)\n\nIf C: group by semantic similarity (personal info, address, account, payment, etc.).\n\n**3b: Conditions**\n\nIf any field logically depends on another (e.g. "Bank name" only makes sense\nwhen payment method is "bank"), ask:\n\n> "I noticed [field X] probably only shows when [field Y] = [value].\n> Should I add show conditions?"\n\nOptions:\n- A) Yes, add the conditions you suggested\n- B) No conditions, show everything always\n- C) Let me specify which fields depend on what\n\n**3c: Validation**\n\nFor each field, infer validation:\n- Fields marked "required" or with * \u2192 `required: true`\n- Email fields \u2192 pattern for email format\n- Password fields \u2192 minLength: 8\n- Phone fields \u2192 inputMode: \'tel\'\n- Number fields \u2192 inputMode: \'numeric\'\n\nAsk only if there\'s ambiguity: "Should [field] be required?"\n\n### Step 4: Generate JSON\n\nOutput the complete form definition JSON. Follow these rules:\n\n1. `id` is kebab-case derived from the title\n2. `key` for each field is camelCase\n3. Every form MUST have `submit: { label: "..." }`\n4. Use `fields` for single-step, `steps` for multi-step\n5. Only include optional properties when they add value (no empty strings, no `required: false`)\n6. Options for select/radio must have both `value` and `label`\n\nPut `"$schema": "https://formhaus.dev/schema/form-definition.json"` as the first key\nso editors validate and autocomplete the file.\n\n### Step 5: Validate\n\nIf the MCP tools are available:\n\n1. Call `validate_definition` with the definition. Fix every entry in `errors` and\n re-run until `errors` is empty. Review `warnings` and fix the ones that are real mistakes.\n2. For multi-step definitions with conditions or routes, call `simulate_path` with answers\n that take each branch and check that the path matches the user\'s intent.\n\n### Step 6: Present and confirm\n\nShow the generated JSON in a code block. Ask:\n\n> "Here\'s your form definition. You can paste this directly into the\n> Formhaus Figma plugin or use with FormRenderer."\n\nOptions:\n- A) Looks good, copy and done\n- B) Make changes (specify what)\n- C) Publish it and get a live endpoint\n\nIf B: apply changes and re-present\n\n### Step 7: Publish and wire into the project\n\nOnly when the user chose C, or asked for a working form, a hosted form or an endpoint.\nRequires the `publish_form` tool and a definition that passed `validate_definition`.\n\n1. Call `publish_form` with the definition. Hosted forms do not support `password`, `file`,\n `autocomplete`, `optionsFrom` and custom field types. If the tool reports an error, fix every\n listed path and call it again.\n2. Tell the user the `hosted_url` and `endpoint`. Without an API key the form is unclaimed\n (7 days, 100 submissions): give them the `claim_url` from the result.\n3. Detect the project: React or Vue in `package.json`, or plain HTML. Install the matching\n package (`@formhaus/react` or `@formhaus/vue`, plus `@formhaus/core` for `style.css`) with\n the project\'s package manager.\n4. Add the `react_snippet` from the result where the user wants the form. It imports\n `FormhausForm` from `@formhaus/react/cloud`. For Vue use `FormhausForm` from\n `@formhaus/vue/cloud` with the same `id`. For plain HTML use `embed_snippet`.\n5. Publishing again with the same `form_id` ships a new version at the same endpoint, so\n later edits to the definition do not change the project code.\n\n## Example Definitions\n\nCall `example_definitions` to list the bundled examples and fetch one by `id` when you\nneed to verify a pattern or show the user an example. They cover common field types,\nvalidation, show conditions, linear steps, and branching steps with routes.\n\n## Rules\n\n- NEVER call `publish_form` unless the user asked for a hosted form or Formhaus Cloud. If the project has its own backend, render with `FormRenderer` and submit to that backend.\n- NEVER output invalid JSON. Validate before presenting, with `validate_definition` when available.\n- NEVER invent field types not in the FieldType union.\n- NEVER use `fields` AND `steps` in the same definition. Pick one.\n- Keys must be unique within a definition (across all steps).\n- ShowCondition.field must reference an existing field key.\n- For multi-step: each step needs `id`, `title`, and `fields`.\n- Submit label should match the form\'s purpose ("Submit", "Create transfer", "Register", etc.).\n';
16
+
17
+ // src/context.ts
18
+ var SetupError = class extends Error {
19
+ };
20
+ var SERVER_NAME = "formhaus";
21
+ var SERVER_PACKAGE = "@formhaus/mcp";
22
+ var SERVER = { command: "npx", args: ["-y", SERVER_PACKAGE] };
23
+ var GUIDE_URL = "https://formhaus.dev/guide/mcp";
24
+ function isOurServer(command2, args) {
25
+ return command2 === SERVER.command && Array.isArray(args) && args.length === SERVER.args.length && args.every((arg, index) => arg === SERVER.args[index]);
26
+ }
27
+ function readText(file) {
28
+ return existsSync(file) ? readFileSync(file, "utf8") : void 0;
29
+ }
30
+ function isDirectory(path) {
31
+ return existsSync(path) && statSync(path).isDirectory();
32
+ }
33
+ function refuseSymlink(ctx, path) {
34
+ if (lstatSync(path, { throwIfNoEntry: false })?.isSymbolicLink()) {
35
+ throw new SetupError(`${display(ctx, path)} is a symbolic link. Formhaus does not write through links. Edit the file by hand.`);
36
+ }
37
+ }
38
+ function isInside(parent, path) {
39
+ const between = relative(parent, path);
40
+ return between !== "" && !between.startsWith("..") && !isAbsolute(between);
41
+ }
42
+ function refuseLinkedDirectories(ctx, root, file) {
43
+ if (!isInside(root, file)) throw new SetupError(`${display(ctx, file)} is outside ${display(ctx, root)}. Edit the file by hand.`);
44
+ const parts = relative(root, dirname(file)).split(sep).filter(Boolean);
45
+ for (let index = 1; index <= parts.length; index++) refuseSymlink(ctx, resolve(root, ...parts.slice(0, index)));
46
+ }
47
+ function writeText(ctx, file, text, project) {
48
+ const backup = `${file}.bak`;
49
+ const temporary = `${file}.tmp`;
50
+ if (project !== void 0) refuseLinkedDirectories(ctx, project, file);
51
+ for (const path of [file, backup, temporary]) refuseSymlink(ctx, path);
52
+ if (ctx.dryRun) return;
53
+ mkdirSync(dirname(file), { recursive: true });
54
+ if (existsSync(file) && !existsSync(backup)) copyFileSync(file, backup, constants.COPYFILE_EXCL);
55
+ const mode = statSync(file, { throwIfNoEntry: false })?.mode;
56
+ rmSync(temporary, { force: true });
57
+ writeFileSync(temporary, text, { flag: "wx", mode: mode === void 0 ? void 0 : mode & 384 });
58
+ if (mode !== void 0) chmodSync(temporary, mode & 4095);
59
+ renameSync(temporary, file);
60
+ }
61
+ function writeIfChanged(ctx, file, text, project) {
62
+ const current = readText(file);
63
+ if (current === text) return "present";
64
+ writeText(ctx, file, text, project);
65
+ return current === void 0 ? "added" : "updated";
66
+ }
67
+ function display(ctx, path) {
68
+ return path.startsWith(ctx.home + sep) ? `~${path.slice(ctx.home.length)}` : path;
69
+ }
70
+ function step(ctx, agent, item, where, apply) {
71
+ const target = display(ctx, where);
72
+ const firstBackup = existsSync(where) && !existsSync(`${where}.bak`);
73
+ try {
74
+ const state = apply();
75
+ const backedUp = firstBackup && !ctx.dryRun && (state === "added" || state === "updated");
76
+ return { agent, item, where: backedUp ? `${target} (backup: ${target}.bak)` : target, state };
77
+ } catch (error) {
78
+ return { agent, item, where: target, state: "failed", error: error instanceof Error ? error.message : String(error) };
79
+ }
80
+ }
81
+
82
+ // src/json-config.ts
83
+ function isObject(value) {
84
+ return typeof value === "object" && value !== null && !Array.isArray(value);
85
+ }
86
+ function readJsonObject(ctx, file) {
87
+ const text = readText(file);
88
+ if (text === void 0 || text.trim() === "") return void 0;
89
+ let parsed;
90
+ try {
91
+ parsed = JSON.parse(text);
92
+ } catch {
93
+ throw new SetupError(`${display(ctx, file)} is not valid JSON. Fix it and run again. The file was not changed.`);
94
+ }
95
+ if (!isObject(parsed)) throw new SetupError(`${display(ctx, file)} must contain a JSON object. The file was not changed.`);
96
+ return parsed;
97
+ }
98
+ function jsonServerState(config) {
99
+ const servers = config?.mcpServers;
100
+ if (!isObject(servers) || !(SERVER_NAME in servers)) return "missing";
101
+ const entry = servers[SERVER_NAME];
102
+ return isObject(entry) && isOurServer(entry.command, entry.args) ? "present" : "different";
103
+ }
104
+ function addJsonServer(ctx, file) {
105
+ const config = readJsonObject(ctx, file) ?? {};
106
+ const servers = config.mcpServers ?? {};
107
+ if (!isObject(servers)) throw new SetupError(`"mcpServers" in ${display(ctx, file)} is not an object. The file was not changed.`);
108
+ const existing = jsonServerState({ mcpServers: servers });
109
+ if (existing !== "missing") return existing;
110
+ const next = { ...config, mcpServers: { ...servers, [SERVER_NAME]: SERVER } };
111
+ writeText(ctx, file, `${JSON.stringify(next, null, 2)}
112
+ `);
113
+ return "added";
114
+ }
115
+ function isExecutable(file) {
116
+ try {
117
+ accessSync(file, constants.X_OK);
118
+ return statSync(file).isFile();
119
+ } catch {
120
+ return false;
121
+ }
122
+ }
123
+ function findOnPath(ctx, name) {
124
+ const windows = ctx.platform === "win32";
125
+ const extensions = windows ? (ctx.env.PATHEXT || ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
126
+ for (const directory of (ctx.env.PATH ?? "").split(windows ? ";" : ":")) {
127
+ if (!isAbsolute(directory)) continue;
128
+ for (const extension of extensions) {
129
+ const candidate = join(directory, name + extension.toLowerCase());
130
+ if (isExecutable(candidate)) return candidate;
131
+ }
132
+ }
133
+ return void 0;
134
+ }
135
+ function command(ctx, executable, args) {
136
+ if (ctx.platform !== "win32" || !/\.(cmd|bat)$/i.test(executable)) return { file: executable, args, verbatim: false };
137
+ const shell = ctx.env.ComSpec || join(ctx.env.SystemRoot || "C:\\Windows", "System32", "cmd.exe");
138
+ return { file: shell, args: ["/d", "/s", "/c", `""${executable}" ${args.join(" ")}"`], verbatim: true };
139
+ }
140
+
141
+ // src/claude-code.ts
142
+ var NAME = "Claude Code";
143
+ var PLUGIN_PREFIX = `${SERVER_NAME}@`;
144
+ var CLAUDE_ADD_ARGS = ["mcp", "add", "--scope", "user", SERVER_NAME, "--", SERVER.command, ...SERVER.args];
145
+ function configDir(ctx) {
146
+ return ctx.env.CLAUDE_CONFIG_DIR || join(ctx.home, ".claude");
147
+ }
148
+ function enabledPlugins(ctx) {
149
+ try {
150
+ const enabled = readJsonObject(ctx, join(configDir(ctx), "settings.json"))?.enabledPlugins;
151
+ return typeof enabled === "object" && enabled !== null ? enabled : {};
152
+ } catch {
153
+ return {};
154
+ }
155
+ }
156
+ function installedForUser(installs) {
157
+ return Array.isArray(installs) && installs.some((install) => typeof install === "object" && install !== null && (install.scope ?? "user") === "user");
158
+ }
159
+ function hasPlugin(ctx) {
160
+ const plugins = readJsonObject(ctx, join(configDir(ctx), "plugins", "installed_plugins.json"))?.plugins;
161
+ if (typeof plugins !== "object" || plugins === null) return false;
162
+ const enabled = enabledPlugins(ctx);
163
+ return Object.entries(plugins).some(
164
+ ([id, installs]) => id.startsWith(PLUGIN_PREFIX) && installedForUser(installs) && enabled[id] === true
165
+ );
166
+ }
167
+ function userServer(ctx) {
168
+ const file = ctx.env.CLAUDE_CONFIG_DIR ? join(ctx.env.CLAUDE_CONFIG_DIR, ".claude.json") : join(ctx.home, ".claude.json");
169
+ return jsonServerState(readJsonObject(ctx, file));
170
+ }
171
+ function projectWarning(ctx) {
172
+ const file = join(ctx.cwd, ".mcp.json");
173
+ try {
174
+ if (jsonServerState(readJsonObject(ctx, file)) !== "different") return [];
175
+ } catch {
176
+ return [];
177
+ }
178
+ return [{ agent: NAME, item: "project MCP server", where: display(ctx, file), state: "different" }];
179
+ }
180
+ function addServer(ctx, claude) {
181
+ if (hasPlugin(ctx)) return "present";
182
+ const existing = userServer(ctx);
183
+ if (existing !== "missing") return existing;
184
+ if (ctx.dryRun) return "added";
185
+ const { file, args, verbatim } = command(ctx, claude, CLAUDE_ADD_ARGS);
186
+ const result = spawnSync(file, args, { encoding: "utf8", env: ctx.env, windowsVerbatimArguments: verbatim });
187
+ if (result.status !== 0) {
188
+ const output = `${result.stderr ?? ""}${result.stdout ?? ""}`.trim() || result.error?.message || `exit code ${result.status}`;
189
+ throw new Error(`claude ${CLAUDE_ADD_ARGS.join(" ")} failed: ${output}`);
190
+ }
191
+ return "added";
192
+ }
193
+ var claudeCode = {
194
+ name: NAME,
195
+ detect: (ctx) => findOnPath(ctx, "claude") !== void 0,
196
+ apply(ctx) {
197
+ const claude = findOnPath(ctx, "claude");
198
+ const skill = join(configDir(ctx), "skills", "formhaus-create-form", "SKILL.md");
199
+ return [
200
+ step(ctx, NAME, "MCP server", `claude mcp add --scope user ${SERVER_NAME}`, () => addServer(ctx, claude)),
201
+ step(
202
+ ctx,
203
+ NAME,
204
+ "formhaus-create-form skill",
205
+ skill,
206
+ () => hasPlugin(ctx) ? "present" : writeIfChanged(ctx, skill, SKILL_default)
207
+ ),
208
+ ...projectWarning(ctx)
209
+ ];
210
+ }
211
+ };
212
+
213
+ // ../../node_modules/.pnpm/smol-toml@1.9.0/node_modules/smol-toml/dist/error.js
214
+ function getLineColFromPtr(string, ptr) {
215
+ let lines = string.slice(0, ptr).split(/\r?\n/);
216
+ return [lines.length, lines.pop().length + 1];
217
+ }
218
+ function makeCodeBlock(string, line, column) {
219
+ let lines = string.split(/\r?\n/);
220
+ let codeblock = "";
221
+ let numberLen = (Math.log10(line + 1) | 0) + 1;
222
+ for (let i = line - 1; i <= line + 1; i++) {
223
+ let l = lines[i - 1];
224
+ if (!l)
225
+ continue;
226
+ codeblock += i.toString().padEnd(numberLen, " ");
227
+ codeblock += ": ";
228
+ codeblock += l;
229
+ codeblock += "\n";
230
+ if (i === line) {
231
+ codeblock += " ".repeat(numberLen + column + 2);
232
+ codeblock += "^\n";
233
+ }
234
+ }
235
+ return codeblock;
236
+ }
237
+ var TomlError = class _TomlError extends Error {
238
+ line;
239
+ column;
240
+ codeblock;
241
+ constructor(message, options) {
242
+ const [line, column] = getLineColFromPtr(options.toml, options.ptr);
243
+ const codeblock = makeCodeBlock(options.toml, line, column);
244
+ super(`Invalid TOML document: ${message}
245
+
246
+ ${codeblock}`, options);
247
+ this.line = line;
248
+ this.column = column;
249
+ this.codeblock = codeblock;
250
+ }
251
+ /** @internal */
252
+ static x(message, ctx, ptr) {
253
+ throw new _TomlError(message, { toml: ctx.s, ptr: ptr ?? ctx.p });
254
+ }
255
+ };
256
+
257
+ // ../../node_modules/.pnpm/smol-toml@1.9.0/node_modules/smol-toml/dist/primitive.js
258
+ function parseString(ctx) {
259
+ let startPtr = ctx.p;
260
+ let c = ctx.s.charCodeAt(ctx.p++);
261
+ let first = c;
262
+ let isLiteral = c === 39;
263
+ let isMultiline = c === ctx.s.charCodeAt(ctx.p) && c === ctx.s.charCodeAt(ctx.p + 1);
264
+ if (isMultiline) {
265
+ if ((c = ctx.s.charCodeAt(ctx.p += 2)) === 10)
266
+ ctx.p++;
267
+ else if (c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10)
268
+ ctx.p += 2;
269
+ }
270
+ let parsed = "";
271
+ let sliceStart = ctx.p;
272
+ let state = 0;
273
+ for (; ctx.p < ctx.s.length; ctx.p++) {
274
+ c = ctx.s.charCodeAt(ctx.p);
275
+ if (isMultiline && (c === 10 || c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10)) {
276
+ state = state && 3;
277
+ } else if (c < 32 && c !== 9 || c === 127) {
278
+ TomlError.x("control characters are not allowed in strings", ctx);
279
+ } else if ((!state || state === 3) && c === first && (!isMultiline || ctx.s.charCodeAt(ctx.p + 1) === first && ctx.s.charCodeAt(ctx.p + 2) === first)) {
280
+ if (isMultiline) {
281
+ if (ctx.s.charCodeAt(ctx.p + 3) === first)
282
+ ctx.p++;
283
+ if (ctx.s.charCodeAt(ctx.p + 3) === first)
284
+ ctx.p++;
285
+ }
286
+ if (!state) {
287
+ let s = ctx.s.slice(sliceStart, ctx.p);
288
+ parsed = parsed ? parsed + s : s;
289
+ }
290
+ ctx.p += isMultiline ? 3 : 1;
291
+ return parsed;
292
+ } else if (!state) {
293
+ if (!isLiteral && c === 92) {
294
+ parsed += ctx.s.slice(sliceStart, sliceStart = ctx.p);
295
+ state = 1;
296
+ }
297
+ } else if (state === 1) {
298
+ if (c === 120 || c === 117 || c === 85) {
299
+ let errPtr = ctx.p++ - 1;
300
+ let value = 0;
301
+ let len = c === 120 ? 2 : c === 117 ? 4 : 8;
302
+ for (let j = 0; j < len; j++, ctx.p++) {
303
+ let hex = ctx.s.charCodeAt(ctx.p);
304
+ let digit = (
305
+ /* 0-9 */
306
+ hex >= 48 && hex <= 57 ? hex - 48 : (
307
+ /* A-F */
308
+ hex >= 65 && hex <= 70 ? hex - 65 + 10 : (
309
+ /* a-f */
310
+ hex >= 97 && hex <= 102 ? hex - 97 + 10 : -1
311
+ )
312
+ )
313
+ );
314
+ if (digit < 0)
315
+ TomlError.x("invalid non-hex character in unicode escape", ctx);
316
+ value = value << 4 | digit;
317
+ }
318
+ if (value < 0 || value > 1114111 || value >= 55296 && value <= 57343) {
319
+ TomlError.x("invalid unicode escape", ctx, errPtr);
320
+ }
321
+ parsed += String.fromCodePoint(value);
322
+ sliceStart = ctx.p--;
323
+ state = 0;
324
+ } else if (isMultiline && (c === 32 || c === 9)) {
325
+ state = 2;
326
+ } else {
327
+ if (c === 98)
328
+ parsed += "\b";
329
+ else if (c === 116)
330
+ parsed += " ";
331
+ else if (c === 110)
332
+ parsed += "\n";
333
+ else if (c === 102)
334
+ parsed += "\f";
335
+ else if (c === 114)
336
+ parsed += "\r";
337
+ else if (c === 101)
338
+ parsed += "\x1B";
339
+ else if (c === 34)
340
+ parsed += '"';
341
+ else if (c === 92)
342
+ parsed += "\\";
343
+ else
344
+ TomlError.x("unrecognised escape sequence", ctx);
345
+ sliceStart = ctx.p + 1;
346
+ state = 0;
347
+ }
348
+ } else if (c !== 32 && c !== 9) {
349
+ if (state === 2)
350
+ TomlError.x("invalid escape: only line-ending whitespace may be escaped", ctx, sliceStart);
351
+ state = !isLiteral && c === 92 ? 1 : 0;
352
+ sliceStart = ctx.p;
353
+ }
354
+ }
355
+ TomlError.x("unfinished string", ctx, startPtr);
356
+ }
357
+
358
+ // ../../node_modules/.pnpm/smol-toml@1.9.0/node_modules/smol-toml/dist/date.js
359
+ var DATE_TIME_RE = /^(\d{4}-\d{2}-\d{2})?[Tt ]?(?:(\d{2}):\d{2}(?::\d{2}(?:\.\d+)?)?)?(Z|z|[-+]\d{2}:\d{2})?$/i;
360
+ var TomlDate = class _TomlDate extends Date {
361
+ #hasDate = false;
362
+ #hasTime = false;
363
+ #offset = null;
364
+ constructor(date, fasttype, unsafeDelim) {
365
+ let hasDate = true;
366
+ let hasTime = true;
367
+ let offset = "Z";
368
+ let c;
369
+ if (typeof date === "string") {
370
+ if (fasttype)
371
+ prep: {
372
+ if (fasttype < 3) {
373
+ if (+date.slice(11, 13) > 23) {
374
+ date = "";
375
+ break prep;
376
+ }
377
+ if (fasttype === 2) {
378
+ offset = null;
379
+ date += "Z";
380
+ } else if ((c = date.charCodeAt(date.length - 1)) !== 90 && c !== 122) {
381
+ offset = date.slice(date.length - 6);
382
+ }
383
+ if (unsafeDelim)
384
+ date = date.slice(0, 10) + "T" + date.slice(11);
385
+ } else if (fasttype === 4) {
386
+ date = +date.slice(0, 2) > 23 ? "" : `0000-01-01T${date}Z`;
387
+ }
388
+ hasDate = fasttype !== 4;
389
+ hasTime = fasttype !== 3;
390
+ }
391
+ else {
392
+ let match = date.match(DATE_TIME_RE);
393
+ if (match) {
394
+ if (!match[1]) {
395
+ hasDate = false;
396
+ date = `0000-01-01T${date}`;
397
+ }
398
+ hasTime = !!match[2];
399
+ hasTime && date[10] === " " && (date = date.replace(" ", "T"));
400
+ if (match[2] && +match[2] > 23) {
401
+ date = "";
402
+ } else {
403
+ offset = match[3] || null;
404
+ if (!offset && hasTime)
405
+ date += "Z";
406
+ }
407
+ } else {
408
+ date = "";
409
+ }
410
+ }
411
+ }
412
+ super(date);
413
+ if (!isNaN(this.getTime())) {
414
+ this.#hasDate = hasDate;
415
+ this.#hasTime = hasTime;
416
+ this.#offset = offset;
417
+ }
418
+ }
419
+ isDateTime() {
420
+ return this.#hasDate && this.#hasTime;
421
+ }
422
+ isLocal() {
423
+ return !this.#hasDate || !this.#hasTime || !this.#offset;
424
+ }
425
+ isDate() {
426
+ return this.#hasDate && !this.#hasTime;
427
+ }
428
+ isTime() {
429
+ return this.#hasTime && !this.#hasDate;
430
+ }
431
+ isValid() {
432
+ return this.#hasDate || this.#hasTime;
433
+ }
434
+ toISOString() {
435
+ let iso = super.toISOString();
436
+ if (this.isDate())
437
+ return iso.slice(0, 10);
438
+ if (this.isTime())
439
+ return iso.slice(11, 23);
440
+ if (this.#offset === null)
441
+ return iso.slice(0, -1);
442
+ if (this.#offset === "Z" || this.#offset === "z")
443
+ return iso;
444
+ let offset = +this.#offset.slice(1, 3) * 60 + +this.#offset.slice(4, 6);
445
+ offset = this.#offset[0] === "-" ? offset : -offset;
446
+ let offsetDate = new Date(this.getTime() - offset * 6e4);
447
+ return offsetDate.toISOString().slice(0, -1) + this.#offset;
448
+ }
449
+ static wrapAsOffsetDateTime(jsDate, offset = "Z") {
450
+ let date = new _TomlDate(jsDate);
451
+ date.#offset = offset;
452
+ return date;
453
+ }
454
+ static wrapAsLocalDateTime(jsDate) {
455
+ let date = new _TomlDate(jsDate);
456
+ date.#offset = null;
457
+ return date;
458
+ }
459
+ static wrapAsLocalDate(jsDate) {
460
+ let date = new _TomlDate(jsDate);
461
+ date.#hasTime = false;
462
+ date.#offset = null;
463
+ return date;
464
+ }
465
+ static wrapAsLocalTime(jsDate) {
466
+ let date = new _TomlDate(jsDate);
467
+ date.#hasDate = false;
468
+ date.#offset = null;
469
+ return date;
470
+ }
471
+ };
472
+
473
+ // ../../node_modules/.pnpm/smol-toml@1.9.0/node_modules/smol-toml/dist/extract.js
474
+ function isDigit(char, base = 10) {
475
+ return base === 16 ? char > 47 && char < 58 || char > 64 && char < 71 || char > 96 && char < 103 : char > 47 && char < 48 + base;
476
+ }
477
+ function isEndOfValue(char, delim) {
478
+ return char === 32 || char === 9 || char === 10 || char === 13 || // Structure end or next value delimiter
479
+ delim && (char === delim || char === 44) || // Comment
480
+ char === 35;
481
+ }
482
+ function extractValue(ctx, end) {
483
+ let errPtr = ctx.p;
484
+ let c = ctx.s.charCodeAt(ctx.p);
485
+ if (c === 91 || c === 123) {
486
+ ctx.d-- || TomlError.x("document contains excessively nested structures. aborting.", ctx);
487
+ let value = c === 91 ? parseArray(ctx) : parseInlineTable(ctx);
488
+ ctx.d++;
489
+ return value;
490
+ }
491
+ if (c === 34 || c === 39) {
492
+ return parseString(ctx);
493
+ }
494
+ if (c === 116) {
495
+ if (ctx.s.charCodeAt(++ctx.p) !== 114 || ctx.s.charCodeAt(++ctx.p) !== 117 || ctx.s.charCodeAt(++ctx.p) !== 101)
496
+ TomlError.x("invalid value", ctx, errPtr);
497
+ return ctx.p++, true;
498
+ }
499
+ if (c === 102) {
500
+ if (ctx.s.charCodeAt(++ctx.p) !== 97 || ctx.s.charCodeAt(++ctx.p) !== 108 || ctx.s.charCodeAt(++ctx.p) !== 115 || ctx.s.charCodeAt(++ctx.p) !== 101)
501
+ TomlError.x("invalid value", ctx, errPtr);
502
+ return ctx.p++, false;
503
+ }
504
+ if (c === 43 || c === 45) {
505
+ return parseNumber(ctx, ctx.p, ctx.s.charCodeAt(++ctx.p), 44 - c, end);
506
+ }
507
+ if (ctx.s.charCodeAt(ctx.p + 4) === 45 && ctx.s.charCodeAt(ctx.p + 7) === 45) {
508
+ return parseDate(ctx, c, end);
509
+ }
510
+ if (ctx.s.charCodeAt(ctx.p + 2) === 58) {
511
+ return parseTime(ctx, c, end);
512
+ }
513
+ return parseNumber(ctx, ctx.p, c, 0, end);
514
+ }
515
+ function parseNumber(ctx, startPtr, startChr, sign, endChr) {
516
+ let c = startChr;
517
+ let state = 0;
518
+ let hasUnderscores = false;
519
+ if (c === 105) {
520
+ if (ctx.s.charCodeAt(++ctx.p) !== 110 || ctx.s.charCodeAt(++ctx.p) !== 102)
521
+ TomlError.x("invalid value", ctx, startPtr);
522
+ return ctx.p++, (sign || 1) / 0;
523
+ }
524
+ if (c === 110) {
525
+ if (ctx.s.charCodeAt(++ctx.p) !== 97 || ctx.s.charCodeAt(++ctx.p) !== 110)
526
+ TomlError.x("invalid value", ctx, startPtr);
527
+ return ctx.p++, NaN;
528
+ }
529
+ if (c === 48) {
530
+ if (++ctx.p >= ctx.s.length || isEndOfValue(c = ctx.s.charCodeAt(ctx.p), endChr))
531
+ return ctx.bi === true ? 0n : 0;
532
+ if (!sign) {
533
+ if (c === 120)
534
+ return parseIntegerBaseN(ctx, startPtr, 16, endChr);
535
+ else if (c === 98)
536
+ return parseIntegerBaseN(ctx, startPtr, 2, endChr);
537
+ else if (c === 111)
538
+ return parseIntegerBaseN(ctx, startPtr, 8, endChr);
539
+ }
540
+ if (c === 46)
541
+ state = 2;
542
+ else if (c === 101 || c === 69)
543
+ state = 4;
544
+ else
545
+ TomlError.x("illegal leading zero", ctx, startPtr);
546
+ } else if (!isDigit(c))
547
+ TomlError.x("invalid value", ctx, startPtr);
548
+ while (++ctx.p < ctx.s.length && (c = ctx.s.charCodeAt(ctx.p), !isEndOfValue(c, endChr))) {
549
+ if (!state)
550
+ state = 1;
551
+ if (c === 95) {
552
+ if (!(state & 1))
553
+ TomlError.x("illegal underscore", ctx);
554
+ state += 11;
555
+ hasUnderscores = true;
556
+ } else if (state === 1 && c === 46)
557
+ state = 2;
558
+ else if ((state === 1 || state === 3) && (c === 101 || c === 69))
559
+ state = 4;
560
+ else if (state === 4 && (c === 43 || c === 45)) ; else if (!isDigit(c))
561
+ TomlError.x(`illegal character in numeric literal`, ctx);
562
+ else if (state > 9)
563
+ state -= 11;
564
+ else if (!(state & 1))
565
+ state++;
566
+ }
567
+ if (!state) {
568
+ let val = (startChr - 48) * (sign || 1);
569
+ return ctx.bi === true ? BigInt(val) : val;
570
+ }
571
+ if (!(state & 1))
572
+ TomlError.x("unfinished numeric value", ctx, startPtr);
573
+ let str = ctx.s.slice(startPtr, ctx.p);
574
+ if (hasUnderscores)
575
+ str = str.replaceAll("_", "");
576
+ return state > 1 ? parseFloat(str) : parseInteger(ctx, str, 10, startPtr);
577
+ }
578
+ function parseIntegerBaseN(ctx, startPtr, base, endChr) {
579
+ let c, underscore = 1;
580
+ while (++ctx.p < ctx.s.length && (c = ctx.s.charCodeAt(ctx.p), !isEndOfValue(c, endChr))) {
581
+ if (c === 95) {
582
+ if (underscore & 1)
583
+ TomlError.x("illegal underscore", ctx);
584
+ underscore = 3;
585
+ } else if (!isDigit(c, base))
586
+ TomlError.x(`illegal character in numeric literal`, ctx);
587
+ else if (underscore & 1)
588
+ underscore--;
589
+ }
590
+ if (underscore & 1)
591
+ TomlError.x("unfinished numeric value", ctx);
592
+ let str = ctx.s.slice(startPtr + 2, ctx.p);
593
+ if (underscore)
594
+ str = str.replaceAll("_", "");
595
+ return parseInteger(ctx, str, base, startPtr);
596
+ }
597
+ function parseInteger(ctx, str, base, startPtr) {
598
+ if (ctx.bi !== true)
599
+ int: {
600
+ let val = parseInt(str, base);
601
+ if (!Number.isSafeInteger(val)) {
602
+ if (ctx.bi)
603
+ break int;
604
+ TomlError.x("integer value cannot be represented losslessly", ctx, startPtr);
605
+ }
606
+ return val;
607
+ }
608
+ return base === 10 ? BigInt(str) : BigInt((base === 2 ? "0b" : base === 8 ? "0o" : "0x") + str);
609
+ }
610
+ function parseDate(ctx, c, endChr) {
611
+ let startPtr = ctx.p++, unsafeSeparator;
612
+ if (!isDigit(c) || !isDigit(ctx.s.charCodeAt(ctx.p++)) || !isDigit(ctx.s.charCodeAt(ctx.p++)) || !isDigit(ctx.s.charCodeAt(ctx.p++))) {
613
+ return parseNumber(ctx, ctx.p = startPtr, c, 0, endChr);
614
+ }
615
+ ctx.p += 5;
616
+ if (!isDigit(ctx.s.charCodeAt(ctx.p++)))
617
+ TomlError.x("invalid date-time: date part is malformed", ctx, startPtr);
618
+ if (ctx.p >= ctx.s.length || ((c = ctx.s.charCodeAt(ctx.p)) !== 32 || (unsafeSeparator = true, !isDigit(ctx.s.charCodeAt(ctx.p + 1)))) && c !== 84 && c !== 116) {
619
+ let t2 = ctx.s.slice(startPtr, ctx.p);
620
+ return readDate(ctx, t2, 3, false, startPtr);
621
+ }
622
+ if (ctx.s.charCodeAt(ctx.p += 3) !== 58)
623
+ TomlError.x("invalid date-time: time part is malformed", ctx, startPtr);
624
+ if (ctx.s.charCodeAt(ctx.p += 3) === 58)
625
+ ctx.p += 3;
626
+ if (ctx.s.charCodeAt(ctx.p) === 46)
627
+ while (isDigit(ctx.s.charCodeAt(++ctx.p)))
628
+ ;
629
+ if (c = ctx.s.charCodeAt(ctx.p)) {
630
+ if (c === 90 || c === 122) {
631
+ let t2 = ctx.s.slice(startPtr, ++ctx.p);
632
+ return readDate(ctx, t2, 1, unsafeSeparator, startPtr, "[+00:00]");
633
+ }
634
+ if (c === 43 || c === 45) {
635
+ let t2 = ctx.s.slice(startPtr, ctx.p += 6);
636
+ return readDate(ctx, t2, 1, unsafeSeparator, startPtr, !ctx.ld && "[" + ctx.s.slice(ctx.p - 6, ctx.p) + "]");
637
+ }
638
+ }
639
+ let t = ctx.s.slice(startPtr, ctx.p);
640
+ return readDate(ctx, t, 2, unsafeSeparator, startPtr);
641
+ }
642
+ function parseTime(ctx, c, endChr) {
643
+ let start = ctx.p;
644
+ if (!isDigit(c) || !isDigit(ctx.s.charCodeAt(++ctx.p))) {
645
+ return parseNumber(ctx, --ctx.p, c, 0, endChr);
646
+ }
647
+ if (ctx.s.charCodeAt(ctx.p += 4) === 58)
648
+ ctx.p += 3;
649
+ if (ctx.s.charCodeAt(ctx.p) === 46)
650
+ while (isDigit(ctx.s.charCodeAt(++ctx.p)))
651
+ ;
652
+ let t = ctx.s.slice(start, ctx.p);
653
+ return readDate(ctx, t, 4, false, start);
654
+ }
655
+ function readDate(ctx, str, type, unsafeDelim, errPtr, temporalSuffix) {
656
+ if (ctx.ld) {
657
+ let date = new TomlDate(str, type, unsafeDelim);
658
+ if (!date.isValid())
659
+ TomlError.x("invalid date", ctx, errPtr);
660
+ return date;
661
+ }
662
+ try {
663
+ if (temporalSuffix)
664
+ str += temporalSuffix;
665
+ switch (type) {
666
+ case 1:
667
+ return Temporal.ZonedDateTime.from(str);
668
+ case 2:
669
+ return Temporal.PlainDateTime.from(str);
670
+ case 3:
671
+ return Temporal.PlainDate.from(str);
672
+ case 4:
673
+ return Temporal.PlainTime.from(str);
674
+ }
675
+ } catch (e) {
676
+ TomlError.x(e instanceof Error ? e.message : "" + e, ctx, errPtr);
677
+ }
678
+ }
679
+
680
+ // ../../node_modules/.pnpm/smol-toml@1.9.0/node_modules/smol-toml/dist/util.js
681
+ function skipComment(ctx) {
682
+ for (; ctx.p < ctx.s.length; ctx.p++) {
683
+ let c = ctx.s.charCodeAt(ctx.p);
684
+ if (c === 10)
685
+ break;
686
+ if (c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10) {
687
+ ctx.p++;
688
+ break;
689
+ }
690
+ if (c < 32 && c !== 9 || c === 127) {
691
+ TomlError.x("control characters are not allowed in comments", ctx);
692
+ }
693
+ }
694
+ }
695
+ function skipVoid(ctx, banNewLines, banComments) {
696
+ let c;
697
+ while (ctx.p < ctx.s.length) {
698
+ while (ctx.p < ctx.s.length && ((c = ctx.s.charCodeAt(ctx.p)) === 32 || c === 9 || !banNewLines && (c === 10 || c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10)))
699
+ ctx.p++;
700
+ if (banComments || c !== 35)
701
+ break;
702
+ skipComment(ctx);
703
+ }
704
+ }
705
+
706
+ // ../../node_modules/.pnpm/smol-toml@1.9.0/node_modules/smol-toml/dist/struct.js
707
+ function parseKey(ctx, end = 61) {
708
+ let startPtr;
709
+ let state = 0;
710
+ let parsed = [];
711
+ let sliceStart;
712
+ let c = ctx.s.charCodeAt(startPtr = ctx.p);
713
+ do {
714
+ if (c === end) {
715
+ if (!state)
716
+ TomlError.x("unexpected end of key", ctx);
717
+ if (state === 1)
718
+ parsed.push(ctx.s.slice(sliceStart, ctx.p));
719
+ return ctx.p++, parsed;
720
+ } else if (c === 46) {
721
+ if (!state)
722
+ TomlError.x("illegal empty bare key", ctx);
723
+ if (state === 1)
724
+ parsed.push(ctx.s.slice(sliceStart, ctx.p));
725
+ state = 0;
726
+ } else if (!state && (c === 34 || c === 39)) {
727
+ if (c === ctx.s.charCodeAt(ctx.p + 1) && c === ctx.s.charCodeAt(ctx.p + 2))
728
+ TomlError.x("illegal quoted key: multiline strings are not allowed", ctx);
729
+ parsed.push(parseString(ctx));
730
+ state = 2;
731
+ ctx.p--;
732
+ } else if (c === 32 || c === 9) {
733
+ if (state === 1) {
734
+ parsed.push(ctx.s.slice(sliceStart, ctx.p));
735
+ state = 2;
736
+ }
737
+ } else if (state === 2 || c < 48 && c !== 45 || c > 57 && c < 65 || c > 90 && c < 97 && c !== 95 || c > 122) {
738
+ TomlError.x("illegal character in key", ctx);
739
+ } else if (!state) {
740
+ state = 1;
741
+ sliceStart = ctx.p;
742
+ }
743
+ } while (c = ctx.s.charCodeAt(++ctx.p));
744
+ TomlError.x("incomplete key-value: cannot find end of key", ctx, startPtr);
745
+ }
746
+ function parseInlineTable(ctx) {
747
+ let startPtr = ctx.p++;
748
+ let res = /* @__PURE__ */ Object.create(null);
749
+ let seen = /* @__PURE__ */ new Set();
750
+ let c;
751
+ while (ctx.p < ctx.s.length) {
752
+ skipVoid(ctx);
753
+ if ((c = ctx.s.charCodeAt(ctx.p)) === 125) {
754
+ ctx.p++;
755
+ return res;
756
+ }
757
+ let k;
758
+ let t = res;
759
+ let hasOwn = false;
760
+ let errPtr = ctx.p;
761
+ let key = parseKey(ctx);
762
+ for (let i = 0; i < key.length; i++) {
763
+ if (i)
764
+ t = hasOwn ? t[k] : t[k] = /* @__PURE__ */ Object.create(null);
765
+ k = key[i];
766
+ if ((hasOwn = Object.hasOwn(t, k)) && (typeof t[k] !== "object" || seen.has(t[k]))) {
767
+ TomlError.x("trying to redefine an already defined value", ctx, errPtr);
768
+ }
769
+ let unsafe = k === "__proto__";
770
+ if (ctx.uk && (unsafe || k === "constructor")) {
771
+ t = ctx.uk !== 1 && TomlError.x("document contains an unsafe property", ctx, errPtr);
772
+ break;
773
+ }
774
+ if (!hasOwn && unsafe) {
775
+ Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true });
776
+ }
777
+ }
778
+ if (hasOwn) {
779
+ TomlError.x("trying to redefine an already defined value", ctx, errPtr);
780
+ }
781
+ skipVoid(ctx, true, true);
782
+ let value = extractValue(
783
+ ctx,
784
+ 125
785
+ /* } */
786
+ );
787
+ if (t && typeof (t[k] = value) === "object")
788
+ seen.add(value);
789
+ skipVoid(ctx);
790
+ if ((c = ctx.s.charCodeAt(ctx.p++)) === 125) {
791
+ return res;
792
+ }
793
+ if (c !== 44)
794
+ TomlError.x("expected comma or end of structure", ctx, ctx.p - 1);
795
+ }
796
+ TomlError.x("unfinished table", ctx, startPtr);
797
+ }
798
+ function parseArray(ctx) {
799
+ let startPtr = ctx.p++;
800
+ let res = [];
801
+ let c;
802
+ while (ctx.p < ctx.s.length) {
803
+ skipVoid(ctx);
804
+ if ((c = ctx.s.charCodeAt(ctx.p)) === 93) {
805
+ ctx.p++;
806
+ return res;
807
+ }
808
+ res.push(extractValue(
809
+ ctx,
810
+ 93
811
+ /* ] */
812
+ ));
813
+ skipVoid(ctx);
814
+ if ((c = ctx.s.charCodeAt(ctx.p++)) === 93) {
815
+ return res;
816
+ }
817
+ if (c !== 44)
818
+ TomlError.x("expected comma or end of structure", ctx, ctx.p - 1);
819
+ }
820
+ TomlError.x("unfinished array", ctx, startPtr);
821
+ }
822
+
823
+ // ../../node_modules/.pnpm/smol-toml@1.9.0/node_modules/smol-toml/dist/parse.js
824
+ function peekTable(ctx, key, table, meta, type) {
825
+ let t = table;
826
+ let m = meta;
827
+ let k;
828
+ let hasOwn = false;
829
+ let state;
830
+ for (let i = 0; i < key.length; i++) {
831
+ if (i) {
832
+ t = hasOwn ? t[k] : t[k] = /* @__PURE__ */ Object.create(null);
833
+ m = (state = m[k]).c;
834
+ if (type === 0 && (state.t === 1 || state.t === 2)) {
835
+ return null;
836
+ }
837
+ if (state.t === 2) {
838
+ let l = t.length - 1;
839
+ t = t[l];
840
+ m = m[l].c;
841
+ }
842
+ }
843
+ k = key[i];
844
+ if ((hasOwn = Object.hasOwn(t, k)) && m[k]?.t === 0 && m[k]?.d) {
845
+ return null;
846
+ }
847
+ if (!hasOwn) {
848
+ let unsafe = k === "__proto__";
849
+ if (ctx.uk && (unsafe || k === "constructor"))
850
+ return false;
851
+ if (unsafe) {
852
+ Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true });
853
+ Object.defineProperty(m, k, { enumerable: true, configurable: true, writable: true });
854
+ }
855
+ m[k] = {
856
+ t: i < key.length - 1 && type === 2 ? 3 : type,
857
+ d: false,
858
+ i: 0,
859
+ c: /* @__PURE__ */ Object.create(null)
860
+ };
861
+ }
862
+ }
863
+ state = m[k];
864
+ if (state.t !== type && !(type === 1 && state.t === 3)) {
865
+ return null;
866
+ }
867
+ if (type === 2) {
868
+ if (!state.d) {
869
+ state.d = true;
870
+ t[k] = [];
871
+ }
872
+ t[k].push(t = /* @__PURE__ */ Object.create(null));
873
+ state.c[state.i++] = state = { t: 1, d: false, i: 0, c: /* @__PURE__ */ Object.create(null) };
874
+ }
875
+ if (state.d) {
876
+ return null;
877
+ }
878
+ state.d = true;
879
+ if (type === 1) {
880
+ t = hasOwn ? t[k] : t[k] = /* @__PURE__ */ Object.create(null);
881
+ } else if (type === 0 && hasOwn) {
882
+ return null;
883
+ }
884
+ return [k, t, state.c];
885
+ }
886
+ function validateTablePeek(ctx, peek, ptr) {
887
+ if (peek === null || ctx.uk === 2)
888
+ TomlError.x(peek === null ? "trying to redefine an already defined table or value" : "document contains an unsafe property", ctx, ptr);
889
+ }
890
+ function parse(toml, options = {}) {
891
+ let ctx = {
892
+ s: toml,
893
+ p: 0,
894
+ d: options.maxDepth ?? 1e3,
895
+ bi: options.integersAsBigInt ?? false,
896
+ ld: options.useLegacyDate ?? true,
897
+ uk: options.unsafeKeyBehaviour === "throw" ? 2 : options.unsafeKeyBehaviour === "drop" ? 1 : 0
898
+ };
899
+ let res = /* @__PURE__ */ Object.create(null);
900
+ let meta = /* @__PURE__ */ Object.create(null);
901
+ let tmp;
902
+ let skipping = false;
903
+ let tbl = res;
904
+ let m = meta;
905
+ if (toml.charCodeAt(0) === 65279)
906
+ ctx.p++;
907
+ skipVoid(ctx);
908
+ while (ctx.p < toml.length) {
909
+ if (toml.charCodeAt(ctx.p) === 91) {
910
+ let isTableArray = toml.charCodeAt(++ctx.p) === 91;
911
+ tmp = ctx.p += +isTableArray;
912
+ skipping = false;
913
+ let k = parseKey(
914
+ ctx,
915
+ 93
916
+ /* ] */
917
+ );
918
+ if (isTableArray) {
919
+ if (toml.charCodeAt(ctx.p) !== 93) {
920
+ TomlError.x("expected end of table array declaration", ctx);
921
+ }
922
+ ctx.p++;
923
+ }
924
+ let p = peekTable(
925
+ ctx,
926
+ k,
927
+ res,
928
+ meta,
929
+ isTableArray ? 2 : 1
930
+ /* Type.EXPLICIT */
931
+ );
932
+ if (!p) {
933
+ validateTablePeek(ctx, p, tmp);
934
+ skipping = true;
935
+ } else {
936
+ m = p[2];
937
+ tbl = p[1];
938
+ }
939
+ } else {
940
+ tmp = ctx.p;
941
+ let k = parseKey(ctx);
942
+ let p = peekTable(
943
+ ctx,
944
+ k,
945
+ tbl,
946
+ m,
947
+ 0
948
+ /* Type.DOTTED */
949
+ );
950
+ if (!p && !skipping)
951
+ validateTablePeek(ctx, p, tmp);
952
+ skipVoid(ctx, true, true);
953
+ let v = extractValue(ctx, void 0);
954
+ if (p && !skipping)
955
+ p[1][p[0]] = v;
956
+ }
957
+ skipVoid(ctx, true);
958
+ if (ctx.p < toml.length && (tmp = toml.charCodeAt(ctx.p)) !== 10 && (tmp !== 13 || toml.charCodeAt(ctx.p + 1) !== 10)) {
959
+ TomlError.x("each key-value declaration must be followed by an end-of-line", ctx);
960
+ }
961
+ skipVoid(ctx);
962
+ }
963
+ return res;
964
+ }
965
+
966
+ // src/toml-config.ts
967
+ var TOML_BLOCK = [
968
+ `[mcp_servers.${SERVER_NAME}]`,
969
+ `command = ${JSON.stringify(SERVER.command)}`,
970
+ `args = [${SERVER.args.map((arg) => JSON.stringify(arg)).join(", ")}]`,
971
+ ""
972
+ ].join("\n");
973
+ function isTable(value) {
974
+ return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
975
+ }
976
+ function parseToml(text) {
977
+ try {
978
+ return parse(text, { integersAsBigInt: true });
979
+ } catch {
980
+ return void 0;
981
+ }
982
+ }
983
+ function serverState(config) {
984
+ const servers = config.mcp_servers;
985
+ if (!isTable(servers) || !(SERVER_NAME in servers)) return "missing";
986
+ const server = servers[SERVER_NAME];
987
+ return isTable(server) && isOurServer(server.command, server.args) ? "present" : "different";
988
+ }
989
+ function keepsEverythingElse(before, after) {
990
+ const { mcp_servers: servers, ...rest } = after;
991
+ if (!isTable(servers)) return false;
992
+ const { [SERVER_NAME]: server, ...others } = servers;
993
+ const { mcp_servers: previous = {}, ...previousRest } = before;
994
+ return isTable(server) && isDeepStrictEqual({ ...server }, SERVER) && isDeepStrictEqual(others, { ...previous }) && isDeepStrictEqual(rest, previousRest);
995
+ }
996
+ function append(text) {
997
+ if (text.trim() === "") return TOML_BLOCK;
998
+ const eol = text.includes("\r\n") ? "\r\n" : "\n";
999
+ const separator = text.endsWith("\n") ? eol : eol + eol;
1000
+ return text + separator + TOML_BLOCK.replaceAll("\n", eol);
1001
+ }
1002
+ function addTomlServer(ctx, file) {
1003
+ const text = readText(file) ?? "";
1004
+ const refuse = (reason) => new SetupError(`${display(ctx, file)} ${reason}. Add the formhaus server by hand. The file was not changed.`);
1005
+ const config = parseToml(text);
1006
+ if (config === void 0) throw refuse("is not valid TOML");
1007
+ if ("mcp_servers" in config && !isTable(config.mcp_servers)) throw refuse("has an mcp_servers value that is not a table");
1008
+ const state = serverState(config);
1009
+ if (state !== "missing") return state;
1010
+ const next = append(text);
1011
+ const updated = parseToml(next);
1012
+ if (updated === void 0 || !keepsEverythingElse(config, updated)) throw refuse("defines mcp_servers in a way Formhaus cannot extend");
1013
+ writeText(ctx, file, next);
1014
+ return "added";
1015
+ }
1016
+
1017
+ // src/agents.ts
1018
+ function codexHome(ctx) {
1019
+ return ctx.env.CODEX_HOME || join(ctx.home, ".codex");
1020
+ }
1021
+ function claudeDesktopDir(ctx) {
1022
+ if (ctx.platform === "darwin") return join(ctx.home, "Library", "Application Support", "Claude");
1023
+ if (ctx.platform === "win32") return join(ctx.env.APPDATA || join(ctx.home, "AppData", "Roaming"), "Claude");
1024
+ return void 0;
1025
+ }
1026
+ function found(ctx, directory, bin) {
1027
+ return isDirectory(directory) || findOnPath(ctx, bin) !== void 0;
1028
+ }
1029
+ function isProject(ctx) {
1030
+ return ctx.cwd !== ctx.home && existsSync(join(ctx.cwd, "package.json"));
1031
+ }
1032
+ var codex = {
1033
+ name: "Codex",
1034
+ detect: (ctx) => found(ctx, codexHome(ctx), "codex"),
1035
+ apply(ctx) {
1036
+ const file = join(codexHome(ctx), "config.toml");
1037
+ return [step(ctx, "Codex", "MCP server", file, () => addTomlServer(ctx, file))];
1038
+ }
1039
+ };
1040
+ var cursor = {
1041
+ name: "Cursor",
1042
+ detect: (ctx) => found(ctx, join(ctx.home, ".cursor"), "cursor"),
1043
+ apply(ctx) {
1044
+ const file = join(ctx.home, ".cursor", "mcp.json");
1045
+ const changes = [step(ctx, "Cursor", "MCP server", file, () => addJsonServer(ctx, file))];
1046
+ if (!isProject(ctx)) return changes;
1047
+ const rule = join(ctx.cwd, ".cursor", "rules", "formhaus.mdc");
1048
+ changes.push(step(ctx, "Cursor", "formhaus rule", rule, () => writeIfChanged(ctx, rule, formhaus_default, ctx.cwd)));
1049
+ return changes;
1050
+ }
1051
+ };
1052
+ var claudeDesktop = {
1053
+ name: "Claude Desktop",
1054
+ detect: (ctx) => {
1055
+ const directory = claudeDesktopDir(ctx);
1056
+ return directory !== void 0 && isDirectory(directory);
1057
+ },
1058
+ apply(ctx) {
1059
+ const directory = claudeDesktopDir(ctx);
1060
+ const file = join(directory, "claude_desktop_config.json");
1061
+ return [step(ctx, "Claude Desktop", "MCP server", file, () => addJsonServer(ctx, file))];
1062
+ }
1063
+ };
1064
+ var AGENTS = [claudeCode, codex, cursor, claudeDesktop];
1065
+ var MANUAL_AGENTS = [
1066
+ { name: "Windsurf", detect: (ctx) => found(ctx, join(ctx.home, ".codeium", "windsurf"), "windsurf") },
1067
+ { name: "Gemini CLI", detect: (ctx) => found(ctx, join(ctx.home, ".gemini"), "gemini") },
1068
+ { name: "OpenCode", detect: (ctx) => found(ctx, join(ctx.home, ".config", "opencode"), "opencode") },
1069
+ { name: "Claude Desktop", detect: (ctx) => ctx.platform === "linux" && isDirectory(join(ctx.home, ".config", "Claude")) },
1070
+ { name: "VS Code", detect: (ctx) => findOnPath(ctx, "code") !== void 0 }
1071
+ ];
1072
+
1073
+ // src/run.ts
1074
+ var LABELS = {
1075
+ added: ["added", "will add"],
1076
+ updated: ["updated", "will update"],
1077
+ present: ["already set up", "already set up"],
1078
+ different: ["different server, check it", "different server, check it"],
1079
+ failed: ["failed", "failed"]
1080
+ };
1081
+ function label(ctx, state) {
1082
+ return LABELS[state][ctx.dryRun ? 1 : 0];
1083
+ }
1084
+ function printChanges(ctx, changes, print) {
1085
+ const width = (pick) => Math.max(...changes.map((change) => pick(change).length));
1086
+ const agentWidth = width((change) => change.agent);
1087
+ const itemWidth = width((change) => change.item);
1088
+ const stateWidth = width((change) => label(ctx, change.state));
1089
+ for (const change of changes) {
1090
+ const row = [change.agent.padEnd(agentWidth), change.item.padEnd(itemWidth), label(ctx, change.state).padEnd(stateWidth), change.where];
1091
+ print(` ${row.join(" ")}`);
1092
+ if (change.error) print(` ${change.error}`);
1093
+ }
1094
+ }
1095
+ function printManual(names, print) {
1096
+ print(names.length > 0 ? `Set up by hand: ${names.join(", ")}` : "Set up any other MCP client by hand:");
1097
+ print(` Add a stdio MCP server named "${SERVER_NAME}" with command "${SERVER.command}" and args ${JSON.stringify(SERVER.args)}.`);
1098
+ print(` Guide: ${GUIDE_URL}`);
1099
+ }
1100
+ function setup(ctx, print) {
1101
+ const agents = AGENTS.filter((agent) => agent.detect(ctx));
1102
+ const manual = MANUAL_AGENTS.filter((agent) => agent.detect(ctx)).map((agent) => agent.name);
1103
+ if (ctx.dryRun) print("Dry run: nothing is written.");
1104
+ if (agents.length === 0) {
1105
+ print("No supported coding agent found. Formhaus sets up Claude Code, Codex, Cursor and Claude Desktop.");
1106
+ printManual(manual, print);
1107
+ return 0;
1108
+ }
1109
+ print(`Found ${agents.map((agent) => agent.name).join(", ")}.`);
1110
+ const changes = agents.flatMap((agent) => agent.apply(ctx));
1111
+ print("");
1112
+ printChanges(ctx, changes, print);
1113
+ if (manual.length > 0) {
1114
+ print("");
1115
+ printManual(manual, print);
1116
+ }
1117
+ const failed = changes.some((change) => change.state === "failed");
1118
+ const changed = changes.some((change) => change.state === "added" || change.state === "updated");
1119
+ print("");
1120
+ const different = changes.some((change) => change.state === "different");
1121
+ if (different) {
1122
+ print(
1123
+ `A "${SERVER_NAME}" server with another command is configured where marked. Formhaus left it unchanged. Check that you trust it.`
1124
+ );
1125
+ }
1126
+ if (failed) print("Some steps failed. Fix the errors above and run formhaus again.");
1127
+ else if (ctx.dryRun) print("Run without --dry-run to apply.");
1128
+ else if (changed) print("Done. Restart your agents to load the Formhaus MCP server.");
1129
+ else if (!different) print("Everything is already set up.");
1130
+ return failed ? 1 : 0;
1131
+ }
1132
+
1133
+ // src/main.ts
1134
+ var HELP = `Usage: npx formhaus [setup] [options]
1135
+
1136
+ Adds the Formhaus MCP server (npx -y @formhaus/mcp) to the coding agents
1137
+ installed on this machine: Claude Code, Codex, Cursor and Claude Desktop.
1138
+ Installs the formhaus-create-form skill in Claude Code and, inside a project,
1139
+ the Formhaus rule in .cursor/rules. Running it again changes nothing.
1140
+
1141
+ Options:
1142
+ --dry-run Show the planned changes and write nothing
1143
+ -h, --help Show this help
1144
+ -v, --version Show the version`;
1145
+ function main(argv, ctx, print, printError) {
1146
+ let parsed;
1147
+ try {
1148
+ parsed = parse2(argv);
1149
+ } catch (error) {
1150
+ printError(`${error instanceof Error ? error.message : String(error)}
1151
+ Run npx formhaus --help for usage.`);
1152
+ return 1;
1153
+ }
1154
+ const { values, positionals } = parsed;
1155
+ if (values.help || values.version) {
1156
+ print(values.help ? HELP : version);
1157
+ return 0;
1158
+ }
1159
+ if (positionals.length > 1 || positionals.length === 1 && positionals[0] !== "setup") {
1160
+ printError(`Unknown command: ${positionals.join(" ")}
1161
+ Run npx formhaus --help for usage.`);
1162
+ return 1;
1163
+ }
1164
+ return setup({ ...ctx, dryRun: values["dry-run"] === true }, print);
1165
+ }
1166
+ function parse2(argv) {
1167
+ return parseArgs({
1168
+ args: argv,
1169
+ allowPositionals: true,
1170
+ options: {
1171
+ "dry-run": { type: "boolean" },
1172
+ help: { type: "boolean", short: "h" },
1173
+ version: { type: "boolean", short: "v" }
1174
+ }
1175
+ });
1176
+ }
1177
+
1178
+ // src/cli.ts
1179
+ process.exitCode = main(
1180
+ process.argv.slice(2),
1181
+ { home: homedir(), cwd: process.cwd(), env: process.env, platform: process.platform },
1182
+ (line) => console.log(line),
1183
+ (line) => console.error(line)
1184
+ );
1185
+ /*! Bundled license information:
1186
+
1187
+ smol-toml/dist/error.js:
1188
+ smol-toml/dist/primitive.js:
1189
+ smol-toml/dist/date.js:
1190
+ smol-toml/dist/extract.js:
1191
+ smol-toml/dist/util.js:
1192
+ smol-toml/dist/struct.js:
1193
+ smol-toml/dist/parse.js:
1194
+ smol-toml/dist/stringify.js:
1195
+ smol-toml/dist/index.js:
1196
+ (*!
1197
+ * Copyright (c) Squirrel Chat et al., All rights reserved.
1198
+ * SPDX-License-Identifier: BSD-3-Clause
1199
+ *
1200
+ * Redistribution and use in source and binary forms, with or without
1201
+ * modification, are permitted provided that the following conditions are met:
1202
+ *
1203
+ * 1. Redistributions of source code must retain the above copyright notice, this
1204
+ * list of conditions and the following disclaimer.
1205
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
1206
+ * this list of conditions and the following disclaimer in the
1207
+ * documentation and/or other materials provided with the distribution.
1208
+ * 3. Neither the name of the copyright holder nor the names of its contributors
1209
+ * may be used to endorse or promote products derived from this software without
1210
+ * specific prior written permission.
1211
+ *
1212
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
1213
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
1214
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
1215
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
1216
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1217
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
1218
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
1219
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
1220
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
1221
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1222
+ *)
1223
+ */
package/package.json CHANGED
@@ -1,6 +1,48 @@
1
1
  {
2
2
  "name": "formhaus",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.14.2",
4
+ "description": "Set up the Formhaus MCP server and agent instructions in Claude Code, Codex, Cursor and Claude Desktop with one command.",
5
+ "type": "module",
6
+ "engines": {
7
+ "node": ">=20"
8
+ },
9
+ "bin": {
10
+ "formhaus": "./dist/cli.js"
11
+ },
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "devDependencies": {
16
+ "@types/node": "^20",
17
+ "smol-toml": "1.9.0",
18
+ "tsup": "^8.5.1",
19
+ "typescript": "^5.7.3",
20
+ "vitest": "^4.1.3"
21
+ },
22
+ "license": "MIT",
23
+ "homepage": "https://formhaus.dev/guide/mcp.html",
24
+ "bugs": {
25
+ "url": "https://github.com/ignsm/formhaus/issues"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/ignsm/formhaus.git",
30
+ "directory": "packages/formhaus"
31
+ },
32
+ "keywords": [
33
+ "formhaus",
34
+ "mcp",
35
+ "claude-code",
36
+ "codex",
37
+ "cursor",
38
+ "ai-agents",
39
+ "forms",
40
+ "cli"
41
+ ],
42
+ "scripts": {
43
+ "build": "tsup",
44
+ "test": "vitest run",
45
+ "typecheck": "tsc --noEmit",
46
+ "clean": "rm -rf dist"
47
+ }
6
48
  }