shadownrx-code 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. package/README.md +130 -0
  2. package/bin.js +159 -0
  3. package/lib.js +95 -0
  4. package/mcp-server.js +135 -0
  5. package/package.json +29 -0
package/README.md ADDED
@@ -0,0 +1,130 @@
1
+ # shadownrx-code
2
+
3
+ Configurador interactivo de terminal para la plataforma de build de
4
+ `shadownrx/code`. Detecta si tu proyecto es Flutter, React Native, una PWA
5
+ (Capacitor) o Electron, te hace un par de preguntas, y te escribe
6
+ `.github/workflows/build.yml` ya configurado — no compila nada localmente, solo
7
+ prepara el workflow que corre gratis en GitHub Actions.
8
+
9
+ ```
10
+ ▲ shadownrx/code — configurador de CI para Android, iOS y Electron
11
+
12
+ ? Detecté un proyecto React Native en este directorio. ¿Es correcto? › Yes
13
+ ? ¿Qué deseas compilar? › Android + iOS (ambas)
14
+ ? ¿Ya tenés listos los secrets de firma (keystore / certificado de Apple)? › No
15
+ ? ¿Adjuntar los builds a un GitHub Release cuando pushees un tag (v1.2.3)? › No
16
+
17
+ ✔ Escribí .github/workflows/build.yml
18
+ ```
19
+
20
+ Para Electron, la pregunta "¿Qué deseas compilar?" y la de firma no aparecen
21
+ (Electron siempre compila Linux + macOS + Windows, y la firma todavía no está
22
+ soportada) — solo pregunta el tipo de proyecto y si querés adjuntar a un
23
+ GitHub Release.
24
+
25
+ ## Usar
26
+
27
+ La idea es que, una vez publicado, alcance con:
28
+
29
+ ```bash
30
+ npx shadownrx-code
31
+ ```
32
+
33
+ parado en la raíz de tu proyecto (Flutter, React Native, PWA o Electron) — sin
34
+ clonar nada. **Todavía no está publicado en npm** (publicarlo es distribuirlo
35
+ públicamente, así que quedó pendiente de que lo pidas explícito — y hace falta
36
+ una cuenta/token de npm que este entorno no tiene). El nombre `shadownrx-code`
37
+ está libre en el registro — no se reserva hasta el primer `npm publish`.
38
+
39
+ Mientras tanto, corré la versión local:
40
+
41
+ ```bash
42
+ git clone https://github.com/shadownrx/code
43
+ cd code/cli
44
+ npm install
45
+ node bin.js # parado en la raíz de TU proyecto, o pasale el cwd que corresponda
46
+ ```
47
+
48
+ o, apuntando directo a la carpeta de tu proyecto:
49
+
50
+ ```bash
51
+ cd /ruta/a/tu/proyecto
52
+ node /ruta/a/code/cli/bin.js
53
+ ```
54
+
55
+ ## Como tool de Cursor (o cualquier cliente MCP)
56
+
57
+ Además del CLI interactivo, `mcp-server.js` expone lo mismo como un servidor
58
+ [MCP](https://modelcontextprotocol.io) — así el agente de Cursor (o Claude
59
+ Code, Windsurf, o cualquier editor con soporte MCP; no es algo específico de
60
+ Cursor) puede llamarlo directamente en la conversación, sin que abras una
61
+ terminal vos.
62
+
63
+ Agregá esto a `.cursor/mcp.json` en tu proyecto (o al `mcp.json` global de
64
+ Cursor):
65
+
66
+ ```json
67
+ {
68
+ "mcpServers": {
69
+ "shadownrx-code": {
70
+ "command": "node",
71
+ "args": ["/ruta/absoluta/a/code/cli/mcp-server.js"]
72
+ }
73
+ }
74
+ }
75
+ ```
76
+
77
+ (Una vez publicado en npm, `"command": "npx", "args": ["-y", "shadownrx-code-mcp"]`
78
+ alcanzaría, sin ruta absoluta ni clonar nada — mismo estado pendiente que el
79
+ CLI, ver arriba.)
80
+
81
+ Este mismo repo ya trae [`.cursor/mcp.json`](../.cursor/mcp.json) apuntando a
82
+ `cli/mcp-server.js` con `${workspaceFolder}` — si abrís `shadownrx/code` en
83
+ Cursor, la tool queda disponible sola. Eso sí, corré `npm install` en `cli/`
84
+ primero (`node_modules` no se commitea): el server necesita
85
+ `@modelcontextprotocol/sdk` y `zod` instalados para arrancar.
86
+
87
+ Expone dos tools:
88
+
89
+ | Tool | Qué hace |
90
+ |---|---|
91
+ | `detect_project_type` | Solo lee: reporta si un directorio es Flutter/React Native/PWA/Electron. No escribe nada. |
92
+ | `setup_mobile_ci` | Detecta (o recibe `project_type` explícito) y escribe `.github/workflows/build.yml` — mismos parámetros que el CLI interactivo (`build_android`, `build_ios`, `build_electron`, `create_release`), más `overwrite` para confirmar si el archivo ya existe. |
93
+
94
+ Ambas tools están probadas de punta a punta contra el server real (no un
95
+ mock): `mcp-server.test.js` levanta `mcp-server.js` como subproceso vía stdio
96
+ con el `Client` del SDK de MCP y llama las tools de verdad.
97
+
98
+ ## Qué pregunta y por qué
99
+
100
+ Cada pregunta mapea 1 a 1 a un input real de
101
+ [`build-mobile.yml`](../.github/workflows/build-mobile.yml) — no hay ninguna
102
+ opción que prometa algo que la plataforma no haga. Ver
103
+ [`docs/USAGE.md`](../docs/USAGE.md) para el detalle de cada input.
104
+
105
+ | Pregunta | Input generado |
106
+ |---|---|
107
+ | Tipo de proyecto (auto-detectado o elegido) | `project_type` |
108
+ | Qué compilar (Android / iOS / ambas) — no aplica a Electron | `build_android`, `build_ios` |
109
+ | ¿Adjuntar a un GitHub Release en tags? | `create_release` (+ el bloque `permissions: contents: write` que necesita) |
110
+
111
+ Para Electron, en vez de `build_android`/`build_ios` se emite
112
+ `build_electron: true` (siempre compila los tres sistemas operativos).
113
+
114
+ Si respondés que ya tenés los secrets de firma listos, te lista los nombres
115
+ exactos que hay que cargar en GitHub y te manda a `docs/SIGNING.md` — no los
116
+ pide ni los toca, esos siempre se configuran a mano como secrets de GitHub.
117
+
118
+ Si `.github/workflows/build.yml` ya existe, pregunta antes de pisarlo.
119
+
120
+ ## Desarrollo
121
+
122
+ `lib.js` tiene toda la lógica pura (detección de proyecto + generación del
123
+ YAML) separada de `bin.js` (la parte interactiva con
124
+ [`prompts`](https://www.npmjs.com/package/prompts)) y de `mcp-server.js` (el
125
+ server MCP) — justamente para no duplicar la lógica y poder testearla sin
126
+ simular una terminal ni un cliente MCP real:
127
+
128
+ ```bash
129
+ npm test # node --test — corre lib.test.js y mcp-server.test.js
130
+ ```
package/bin.js ADDED
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import prompts from 'prompts';
5
+ import {
6
+ PROJECT_LABELS,
7
+ androidSecrets,
8
+ buildWorkflowYaml,
9
+ detectProjectType,
10
+ iosSecrets,
11
+ } from './lib.js';
12
+
13
+ const cwd = process.cwd();
14
+
15
+ function onCancel() {
16
+ console.log('\nCancelado — no se modificó nada.\n');
17
+ process.exit(1);
18
+ }
19
+
20
+ async function main() {
21
+ console.log('\n▲ shadownrx/code — configurador de CI para Android, iOS y Electron\n');
22
+ console.log('Esto escribe .github/workflows/build.yml en este proyecto. No compila nada');
23
+ console.log('acá: el build corre gratis en GitHub Actions con cada push.\n');
24
+
25
+ const detected = detectProjectType(cwd);
26
+ let projectType = detected;
27
+
28
+ if (detected) {
29
+ const { confirmDetected } = await prompts(
30
+ {
31
+ type: 'confirm',
32
+ name: 'confirmDetected',
33
+ message: `Detecté un proyecto ${PROJECT_LABELS[detected]} en este directorio. ¿Es correcto?`,
34
+ initial: true,
35
+ },
36
+ { onCancel }
37
+ );
38
+ if (!confirmDetected) projectType = null;
39
+ }
40
+
41
+ if (!projectType) {
42
+ const { picked } = await prompts(
43
+ {
44
+ type: 'select',
45
+ name: 'picked',
46
+ message: '¿Qué tipo de proyecto es?',
47
+ choices: [
48
+ { title: 'Flutter', value: 'flutter' },
49
+ { title: 'React Native', value: 'react-native' },
50
+ { title: 'PWA (Capacitor)', value: 'pwa' },
51
+ { title: 'Electron', value: 'electron' },
52
+ { title: 'Detectar automáticamente en cada build', value: 'auto' },
53
+ ],
54
+ },
55
+ { onCancel }
56
+ );
57
+ projectType = picked;
58
+ }
59
+
60
+ const isElectron = projectType === 'electron';
61
+
62
+ let buildAndroid = true;
63
+ let buildIos = true;
64
+ let buildElectron = true;
65
+ let wantsSigning = false;
66
+
67
+ if (isElectron) {
68
+ console.log('\nElectron compila Linux, macOS y Windows en paralelo — no hace falta elegir.\n');
69
+ } else {
70
+ const { target } = await prompts(
71
+ {
72
+ type: 'select',
73
+ name: 'target',
74
+ message: '¿Qué deseas compilar?',
75
+ choices: [
76
+ { title: 'Android + iOS (ambas)', value: 'both' },
77
+ { title: 'Solo Android', value: 'android' },
78
+ { title: 'Solo iOS', value: 'ios' },
79
+ ],
80
+ },
81
+ { onCancel }
82
+ );
83
+ buildAndroid = target !== 'ios';
84
+ buildIos = target !== 'android';
85
+
86
+ const signingAnswer = await prompts(
87
+ {
88
+ type: 'confirm',
89
+ name: 'wantsSigning',
90
+ message: '¿Ya tenés listos los secrets de firma (keystore / certificado de Apple)?',
91
+ initial: false,
92
+ },
93
+ { onCancel }
94
+ );
95
+ wantsSigning = signingAnswer.wantsSigning;
96
+ }
97
+
98
+ const { wantsRelease } = await prompts(
99
+ {
100
+ type: 'confirm',
101
+ name: 'wantsRelease',
102
+ message: '¿Adjuntar los builds a un GitHub Release cuando pushees un tag (v1.2.3)?',
103
+ initial: false,
104
+ },
105
+ { onCancel }
106
+ );
107
+
108
+ const workflowPath = path.join(cwd, '.github', 'workflows', 'build.yml');
109
+ if (fs.existsSync(workflowPath)) {
110
+ const { overwrite } = await prompts(
111
+ {
112
+ type: 'confirm',
113
+ name: 'overwrite',
114
+ message: `Ya existe ${path.relative(cwd, workflowPath)}. ¿Sobrescribir?`,
115
+ initial: false,
116
+ },
117
+ { onCancel }
118
+ );
119
+ if (!overwrite) {
120
+ console.log('\nCancelado — no se modificó nada.\n');
121
+ return;
122
+ }
123
+ }
124
+
125
+ const yaml = buildWorkflowYaml({
126
+ projectType,
127
+ buildAndroid,
128
+ buildIos,
129
+ buildElectron,
130
+ createRelease: wantsRelease,
131
+ });
132
+
133
+ fs.mkdirSync(path.dirname(workflowPath), { recursive: true });
134
+ fs.writeFileSync(workflowPath, yaml);
135
+
136
+ console.log(`\n✔ Escribí ${path.relative(cwd, workflowPath)}\n`);
137
+
138
+ if (isElectron) {
139
+ console.log(
140
+ 'Los builds de Electron salen sin firmar por ahora (la firma de macOS/Windows\n' +
141
+ 'todavía no está soportada por la plataforma).\n'
142
+ );
143
+ } else if (wantsSigning) {
144
+ console.log('Agregá estos secrets en GitHub (Settings → Secrets and variables → Actions):\n');
145
+ if (buildAndroid) console.log(` Android: ${androidSecrets().join(', ')}`);
146
+ if (buildIos) console.log(` iOS: ${iosSecrets().join(', ')}`);
147
+ console.log('\nDetalle paso a paso: https://github.com/shadownrx/code/blob/main/docs/SIGNING.md\n');
148
+ } else {
149
+ console.log(
150
+ 'Por ahora va a compilar sin firmar (sirve para probar que compila y correr en\n' +
151
+ 'simulador/emulador). Cuando quieras builds firmados, mirá docs/SIGNING.md.\n'
152
+ );
153
+ }
154
+
155
+ console.log('Siguiente paso: hacé commit y push — el primer build corre solo, gratis,');
156
+ console.log('sin necesitar Mac (ni Windows, si es Electron) en tu máquina.\n');
157
+ }
158
+
159
+ main();
package/lib.js ADDED
@@ -0,0 +1,95 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ export const PROJECT_LABELS = {
5
+ flutter: 'Flutter',
6
+ 'react-native': 'React Native',
7
+ pwa: 'PWA (Capacitor)',
8
+ electron: 'Electron',
9
+ };
10
+
11
+ /**
12
+ * Same detection rules as .github/actions/detect-project/action.yml,
13
+ * ported to Node so the CLI can guess before asking.
14
+ */
15
+ export function detectProjectType(cwd) {
16
+ const pubspecPath = path.join(cwd, 'pubspec.yaml');
17
+ if (fs.existsSync(pubspecPath)) {
18
+ const content = fs.readFileSync(pubspecPath, 'utf8');
19
+ if (/^\s*flutter:/m.test(content)) return 'flutter';
20
+ }
21
+
22
+ if (
23
+ fs.existsSync(path.join(cwd, 'capacitor.config.ts')) ||
24
+ fs.existsSync(path.join(cwd, 'capacitor.config.json'))
25
+ ) {
26
+ return 'pwa';
27
+ }
28
+
29
+ const pkgPath = path.join(cwd, 'package.json');
30
+ if (fs.existsSync(pkgPath)) {
31
+ try {
32
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
33
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
34
+ if (deps && (deps['electron'] || deps['electron-builder'])) return 'electron';
35
+ if (deps && deps['react-native']) return 'react-native';
36
+ } catch {
37
+ // malformed package.json — fall through to "not detected"
38
+ }
39
+ }
40
+
41
+ return null;
42
+ }
43
+
44
+ export function androidSecrets() {
45
+ return ['ANDROID_KEYSTORE_BASE64', 'ANDROID_KEYSTORE_PASSWORD', 'ANDROID_KEY_ALIAS', 'ANDROID_KEY_PASSWORD'];
46
+ }
47
+
48
+ export function iosSecrets() {
49
+ return ['IOS_CERTIFICATE_BASE64', 'IOS_CERTIFICATE_PASSWORD', 'IOS_PROVISION_PROFILE_BASE64', 'IOS_TEAM_ID'];
50
+ }
51
+
52
+ /**
53
+ * Builds the literal contents of .github/workflows/build.yml.
54
+ * Kept as plain string templating (not a YAML library) so the output
55
+ * matches exactly what docs/USAGE.md shows and stays easy to diff by eye.
56
+ *
57
+ * For projectType 'electron', buildAndroid/buildIos are irrelevant (that
58
+ * job only runs for mobile project types) and buildElectron is emitted
59
+ * instead — and vice versa.
60
+ */
61
+ export function buildWorkflowYaml({ projectType, buildAndroid, buildIos, buildElectron, createRelease }) {
62
+ const lines = [
63
+ 'name: Build Mobile Apps',
64
+ '',
65
+ 'on:',
66
+ ' push:',
67
+ ' branches: [main]',
68
+ ' tags: ["v*"]',
69
+ ' pull_request:',
70
+ ' workflow_dispatch:',
71
+ '',
72
+ 'jobs:',
73
+ ' build:',
74
+ ];
75
+
76
+ if (createRelease) {
77
+ lines.push(' permissions:', ' contents: write');
78
+ }
79
+
80
+ lines.push(' uses: shadownrx/code/.github/workflows/build-mobile.yml@main', ' with:', ` project_type: ${projectType}`);
81
+
82
+ if (projectType === 'electron') {
83
+ lines.push(` build_electron: ${buildElectron}`);
84
+ } else {
85
+ lines.push(` build_android: ${buildAndroid}`, ` build_ios: ${buildIos}`);
86
+ }
87
+
88
+ if (createRelease) {
89
+ lines.push(' create_release: true');
90
+ }
91
+
92
+ lines.push(' secrets: inherit', '');
93
+
94
+ return lines.join('\n');
95
+ }
package/mcp-server.js ADDED
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env node
2
+ // MCP server exposing shadownrx/code's build-mobile.yml setup as tools for
3
+ // any MCP client (Cursor, Claude Code, Windsurf, etc.) — not Cursor-specific,
4
+ // MCP is an open standard. See cli/README.md for how to point Cursor at this.
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
8
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
9
+ import { z } from 'zod';
10
+ import {
11
+ PROJECT_LABELS,
12
+ androidSecrets,
13
+ buildWorkflowYaml,
14
+ detectProjectType,
15
+ iosSecrets,
16
+ } from './lib.js';
17
+
18
+ const server = new McpServer({ name: 'shadownrx-code', version: '1.0.0' });
19
+
20
+ const PROJECT_TYPES = ['auto', 'flutter', 'react-native', 'pwa', 'electron'];
21
+
22
+ function text(str) {
23
+ return { content: [{ type: 'text', text: str }] };
24
+ }
25
+
26
+ server.registerTool(
27
+ 'detect_project_type',
28
+ {
29
+ title: 'Detect mobile/desktop project type',
30
+ description:
31
+ 'Looks at a directory and reports whether it is a Flutter, React Native, PWA (Capacitor), or Electron ' +
32
+ 'project, using the same rules as build-mobile.yml\'s auto-detection. Read-only — writes nothing.',
33
+ inputSchema: {
34
+ cwd: z.string().optional().describe('Directory to inspect. Defaults to the server process cwd.'),
35
+ },
36
+ },
37
+ async ({ cwd }) => {
38
+ const dir = cwd ? path.resolve(cwd) : process.cwd();
39
+ if (!fs.existsSync(dir)) {
40
+ return { ...text(`No existe el directorio: ${dir}`), isError: true };
41
+ }
42
+ const detected = detectProjectType(dir);
43
+ if (!detected) {
44
+ return text(
45
+ `No se detectó ningún tipo de proyecto conocido en ${dir}.\n` +
46
+ 'Buscado: pubspec.yaml con flutter:, capacitor.config.ts/json, o package.json ' +
47
+ 'con electron/electron-builder/react-native.'
48
+ );
49
+ }
50
+ return text(`Detectado: ${PROJECT_LABELS[detected]} (project_type: ${detected}) en ${dir}`);
51
+ }
52
+ );
53
+
54
+ server.registerTool(
55
+ 'setup_mobile_ci',
56
+ {
57
+ title: 'Set up free Android/iOS/Electron CI',
58
+ description:
59
+ 'Writes .github/workflows/build.yml wired to shadownrx/code\'s free reusable GitHub Actions workflow ' +
60
+ '(build-mobile.yml), which builds Android/iOS (no Mac needed) or Electron for Linux/macOS/Windows ' +
61
+ '(no Mac or Windows machine needed), depending on project_type. Detects the project type automatically ' +
62
+ 'if not given. Refuses to overwrite an existing build.yml unless overwrite is true.',
63
+ inputSchema: {
64
+ cwd: z.string().optional().describe('Project root to write .github/workflows/build.yml into. Defaults to the server process cwd.'),
65
+ project_type: z.enum(PROJECT_TYPES).optional().describe('Defaults to auto-detecting from the project files.'),
66
+ build_android: z.boolean().optional().describe('Only relevant for flutter/react-native/pwa. Default true.'),
67
+ build_ios: z.boolean().optional().describe('Only relevant for flutter/react-native/pwa. Default true.'),
68
+ build_electron: z.boolean().optional().describe('Only relevant for project_type electron. Default true.'),
69
+ create_release: z.boolean().optional().describe('Attach build artifacts to a GitHub Release on tag pushes. Default false.'),
70
+ overwrite: z.boolean().optional().describe('Overwrite an existing .github/workflows/build.yml. Default false.'),
71
+ },
72
+ },
73
+ async ({ cwd, project_type, build_android, build_ios, build_electron, create_release, overwrite }) => {
74
+ const dir = cwd ? path.resolve(cwd) : process.cwd();
75
+ if (!fs.existsSync(dir)) {
76
+ return { ...text(`No existe el directorio: ${dir}`), isError: true };
77
+ }
78
+
79
+ let projectType = project_type;
80
+ if (!projectType || projectType === 'auto') {
81
+ const detected = detectProjectType(dir);
82
+ if (!detected) {
83
+ return {
84
+ ...text(
85
+ `No pude detectar el tipo de proyecto en ${dir}. Pasá project_type explícitamente ` +
86
+ `(${PROJECT_TYPES.filter((t) => t !== 'auto').join(', ')}).`
87
+ ),
88
+ isError: true,
89
+ };
90
+ }
91
+ projectType = detected;
92
+ }
93
+
94
+ const isElectron = projectType === 'electron';
95
+ const yaml = buildWorkflowYaml({
96
+ projectType,
97
+ buildAndroid: build_android ?? true,
98
+ buildIos: build_ios ?? true,
99
+ buildElectron: build_electron ?? true,
100
+ createRelease: create_release ?? false,
101
+ });
102
+
103
+ const workflowPath = path.join(dir, '.github', 'workflows', 'build.yml');
104
+ const relPath = path.relative(dir, workflowPath);
105
+ if (fs.existsSync(workflowPath) && !overwrite) {
106
+ return {
107
+ ...text(
108
+ `Ya existe ${relPath}. Llamá de nuevo con overwrite: true si querés reemplazarlo.\n\n` +
109
+ 'Contenido propuesto:\n\n```yaml\n' + yaml + '\n```'
110
+ ),
111
+ isError: true,
112
+ };
113
+ }
114
+
115
+ fs.mkdirSync(path.dirname(workflowPath), { recursive: true });
116
+ fs.writeFileSync(workflowPath, yaml);
117
+
118
+ const notes = [`Escribí ${relPath} (project_type: ${projectType}).`];
119
+ if (isElectron) {
120
+ notes.push('Los builds de Electron salen sin firmar por ahora (firma de macOS/Windows no soportada todavía).');
121
+ } else {
122
+ notes.push(
123
+ 'Sin firma por defecto. Para builds firmados agregá los secrets de GitHub descritos en docs/SIGNING.md:\n' +
124
+ ` Android: ${androidSecrets().join(', ')}\n` +
125
+ ` iOS: ${iosSecrets().join(', ')}`
126
+ );
127
+ }
128
+ notes.push('Siguiente paso: commit + push. El build corre gratis en GitHub Actions.');
129
+
130
+ return text(notes.join('\n\n') + '\n\n```yaml\n' + yaml + '\n```');
131
+ }
132
+ );
133
+
134
+ const transport = new StdioServerTransport();
135
+ await server.connect(transport);
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "shadownrx-code",
3
+ "version": "1.0.0",
4
+ "description": "Interactive CLI + MCP server for the shadownrx/code free Android/iOS/PWA/Electron build platform — writes .github/workflows/build.yml for your project, or exposes the same thing as tools for Cursor/Claude Code/any MCP client.",
5
+ "type": "module",
6
+ "bin": {
7
+ "shadownrx-code": "bin.js",
8
+ "shadownrx-code-mcp": "mcp-server.js"
9
+ },
10
+ "files": [
11
+ "bin.js",
12
+ "lib.js",
13
+ "mcp-server.js"
14
+ ],
15
+ "scripts": {
16
+ "start": "node bin.js",
17
+ "mcp": "node mcp-server.js",
18
+ "test": "node --test"
19
+ },
20
+ "license": "MIT",
21
+ "engines": {
22
+ "node": ">=18"
23
+ },
24
+ "dependencies": {
25
+ "@modelcontextprotocol/sdk": "^1.30.0",
26
+ "prompts": "^2.4.2",
27
+ "zod": "^4.5.4"
28
+ }
29
+ }