@nitrostack/cli 1.0.12 → 1.0.14

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 (42) hide show
  1. package/dist/commands/cursor.d.ts +11 -0
  2. package/dist/commands/cursor.d.ts.map +1 -0
  3. package/dist/commands/cursor.js +238 -0
  4. package/dist/commands/init.d.ts +1 -0
  5. package/dist/commands/init.d.ts.map +1 -1
  6. package/dist/commands/init.js +82 -9
  7. package/dist/commands/upgrade.d.ts +12 -0
  8. package/dist/commands/upgrade.d.ts.map +1 -1
  9. package/dist/commands/upgrade.js +210 -106
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +14 -0
  13. package/dist/skills/clone.d.ts +28 -0
  14. package/dist/skills/clone.d.ts.map +1 -0
  15. package/dist/skills/clone.js +60 -0
  16. package/dist/skills/detect-agents.d.ts +8 -0
  17. package/dist/skills/detect-agents.d.ts.map +1 -0
  18. package/dist/skills/detect-agents.js +129 -0
  19. package/dist/skills/discover.d.ts +12 -0
  20. package/dist/skills/discover.d.ts.map +1 -0
  21. package/dist/skills/discover.js +45 -0
  22. package/dist/skills/index.d.ts +21 -0
  23. package/dist/skills/index.d.ts.map +1 -0
  24. package/dist/skills/index.js +97 -0
  25. package/dist/skills/installer.d.ts +21 -0
  26. package/dist/skills/installer.d.ts.map +1 -0
  27. package/dist/skills/installer.js +48 -0
  28. package/dist/skills/types.d.ts +39 -0
  29. package/dist/skills/types.d.ts.map +1 -0
  30. package/dist/skills/types.js +1 -0
  31. package/dist/skills/ui.d.ts +55 -0
  32. package/dist/skills/ui.d.ts.map +1 -0
  33. package/dist/skills/ui.js +102 -0
  34. package/package.json +3 -3
  35. package/templates/typescript-oauth/.env.example +71 -14
  36. package/templates/typescript-oauth/src/app.module.ts +7 -0
  37. package/templates/typescript-oauth/src/guards/oauth.guard.ts +19 -0
  38. package/templates/typescript-oauth/src/index.ts +9 -11
  39. package/templates/typescript-oauth/src/modules/flights/flights.prompts.ts +19 -1
  40. package/templates/typescript-oauth/src/services/duffel.service.ts +4 -2
  41. package/templates/typescript-pizzaz/.env.example +10 -0
  42. package/templates/typescript-starter/.env.example +10 -0
@@ -0,0 +1,11 @@
1
+ interface CursorOptions {
2
+ global?: boolean;
3
+ local?: boolean;
4
+ type?: string;
5
+ url?: string;
6
+ port?: string;
7
+ force?: boolean;
8
+ }
9
+ export declare function cursorCommand(options: CursorOptions): Promise<void>;
10
+ export {};
11
+ //# sourceMappingURL=cursor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../src/commands/cursor.ts"],"names":[],"mappings":"AAqBA,UAAU,aAAa;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAuBD,wBAAsB,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAoOzE"}
@@ -0,0 +1,238 @@
1
+ import chalk from 'chalk';
2
+ import path from 'path';
3
+ import fs from 'fs-extra';
4
+ import os from 'os';
5
+ import inquirer from 'inquirer';
6
+ import { createHeader, createSuccessBox, createErrorBox, NitroSpinner, log, spacer, nextSteps, showFooter, NITRO_BANNER_FULL } from '../ui/branding.js';
7
+ import { trackEvent, shutdownAnalytics } from '../analytics/posthog.js';
8
+ /** Normalize CLI --type values, including the deprecated `sse` alias. */
9
+ function normalizeConnectionType(raw) {
10
+ if (!raw)
11
+ return undefined;
12
+ if (raw === 'sse')
13
+ return 'legacy-sse';
14
+ if (raw === 'command' || raw === 'legacy-sse' || raw === 'streamable-http') {
15
+ return raw;
16
+ }
17
+ return undefined;
18
+ }
19
+ function defaultHttpUrl(type, port) {
20
+ if (type === 'streamable-http') {
21
+ return `http://localhost:${port}/mcp`;
22
+ }
23
+ return `http://localhost:${port}/sse`;
24
+ }
25
+ function isHttpConnectionType(type) {
26
+ return type === 'legacy-sse' || type === 'streamable-http';
27
+ }
28
+ export async function cursorCommand(options) {
29
+ console.log(NITRO_BANNER_FULL);
30
+ console.log(createHeader('Cursor Integration', 'Configure MCP server in Cursor'));
31
+ const startTime = Date.now();
32
+ trackEvent('cli_command_invoked', {
33
+ command: 'cursor',
34
+ options: Object.keys(options).filter(k => options[k] !== undefined),
35
+ });
36
+ const projectRoot = process.cwd();
37
+ const packageJsonPath = path.join(projectRoot, 'package.json');
38
+ // Validate project
39
+ if (!fs.existsSync(packageJsonPath)) {
40
+ console.log(createErrorBox('Not a Valid Project', 'package.json not found in the current directory.'));
41
+ trackEvent('cli_cursor_failed', {
42
+ error: 'Not a valid project: package.json missing',
43
+ });
44
+ await shutdownAnalytics();
45
+ process.exit(1);
46
+ }
47
+ let packageJson;
48
+ try {
49
+ packageJson = await fs.readJson(packageJsonPath);
50
+ }
51
+ catch (error) {
52
+ console.log(createErrorBox('Invalid package.json', 'Could not parse project package.json.'));
53
+ trackEvent('cli_cursor_failed', {
54
+ error: 'Invalid package.json parsing error',
55
+ });
56
+ await shutdownAnalytics();
57
+ process.exit(1);
58
+ }
59
+ const rawName = packageJson.name || path.basename(projectRoot);
60
+ const serverName = rawName.includes('/') ? rawName.split('/').pop() : rawName;
61
+ let target;
62
+ if (options.global) {
63
+ target = 'global';
64
+ }
65
+ else if (options.local) {
66
+ target = 'local';
67
+ }
68
+ else {
69
+ const answers = await inquirer.prompt([
70
+ {
71
+ type: 'list',
72
+ name: 'target',
73
+ message: chalk.white('Where would you like to install the Cursor MCP configuration?'),
74
+ choices: [
75
+ { name: `Project-level ${chalk.dim('(.cursor/mcp.json)')}`, value: 'local' },
76
+ { name: `Global ${chalk.dim('(~/.cursor/mcp.json)')}`, value: 'global' },
77
+ ],
78
+ default: 'local',
79
+ }
80
+ ]);
81
+ target = answers.target;
82
+ }
83
+ let type = normalizeConnectionType(options.type);
84
+ if (options.type && !type) {
85
+ console.log(createErrorBox('Invalid connection type', `Unknown --type "${options.type}". Use: command, legacy-sse, or streamable-http.`));
86
+ await shutdownAnalytics();
87
+ process.exit(1);
88
+ }
89
+ if (!type) {
90
+ const answers = await inquirer.prompt([
91
+ {
92
+ type: 'list',
93
+ name: 'type',
94
+ message: chalk.white('Choose the connection type for Cursor:'),
95
+ choices: [
96
+ { name: `Command (Stdio) ${chalk.dim('─ Starts subprocess (recommended for local dev)')}`, value: 'command' },
97
+ { name: `Legacy SSE (/sse) ${chalk.dim('─ Cursor and older HTTP clients (recommended for Cursor)')}`, value: 'legacy-sse' },
98
+ { name: `Streamable HTTP (/mcp) ${chalk.dim('─ MCP Inspector and modern Streamable HTTP clients')}`, value: 'streamable-http' },
99
+ ],
100
+ default: 'command',
101
+ }
102
+ ]);
103
+ type = answers.type;
104
+ }
105
+ if (!type) {
106
+ console.log(createErrorBox('Invalid connection type', 'No connection type selected.'));
107
+ await shutdownAnalytics();
108
+ process.exit(1);
109
+ }
110
+ const port = options.port || '3000';
111
+ let httpUrl = options.url;
112
+ if (isHttpConnectionType(type) && !httpUrl) {
113
+ const defaultUrl = defaultHttpUrl(type, port);
114
+ const urlHint = type === 'legacy-sse'
115
+ ? chalk.dim('Default /sse; /mcp also works for Cursor (legacy SSE fallback on GET).')
116
+ : chalk.dim('For MCP Inspector and clients that use POST initialize + mcp-session-id.');
117
+ const answers = await inquirer.prompt([
118
+ {
119
+ type: 'input',
120
+ name: 'url',
121
+ message: chalk.white('HTTP connection URL:'),
122
+ default: defaultUrl,
123
+ }
124
+ ]);
125
+ httpUrl = answers.url;
126
+ if (urlHint) {
127
+ console.log(urlHint);
128
+ }
129
+ }
130
+ let configFilePath;
131
+ if (target === 'global') {
132
+ configFilePath = path.join(os.homedir(), '.cursor', 'mcp.json');
133
+ }
134
+ else {
135
+ configFilePath = path.join(projectRoot, '.cursor', 'mcp.json');
136
+ }
137
+ let entry;
138
+ if (type === 'command') {
139
+ const absIndexPath = path.join(projectRoot, 'dist', 'index.js');
140
+ entry = {
141
+ command: 'node',
142
+ args: [absIndexPath],
143
+ env: {}
144
+ };
145
+ }
146
+ else {
147
+ entry = {
148
+ url: httpUrl
149
+ };
150
+ }
151
+ const spinner = new NitroSpinner(`Configuring Cursor MCP server...`).start();
152
+ try {
153
+ await fs.ensureDir(path.dirname(configFilePath));
154
+ let mcpConfig = { mcpServers: {} };
155
+ if (await fs.pathExists(configFilePath)) {
156
+ try {
157
+ mcpConfig = await fs.readJson(configFilePath);
158
+ }
159
+ catch (e) {
160
+ // Corrupted or empty file, initialize it
161
+ mcpConfig = { mcpServers: {} };
162
+ }
163
+ }
164
+ if (!mcpConfig.mcpServers) {
165
+ mcpConfig.mcpServers = {};
166
+ }
167
+ // Check if it already exists
168
+ if (mcpConfig.mcpServers[serverName] && !options.force) {
169
+ spinner.stop();
170
+ const { overwrite } = await inquirer.prompt([
171
+ {
172
+ type: 'confirm',
173
+ name: 'overwrite',
174
+ message: chalk.yellow(`MCP Server "${serverName}" is already registered in Cursor config. Overwrite?`),
175
+ default: true,
176
+ }
177
+ ]);
178
+ if (!overwrite) {
179
+ log('Cancelled integration', 'warning');
180
+ trackEvent('cli_cursor_cancelled', {
181
+ server_name: serverName,
182
+ target,
183
+ type,
184
+ });
185
+ await shutdownAnalytics();
186
+ return;
187
+ }
188
+ spinner.start();
189
+ }
190
+ mcpConfig.mcpServers[serverName] = entry;
191
+ await fs.writeJson(configFilePath, mcpConfig, { spaces: 2 });
192
+ spinner.succeed(`Registered "${serverName}" in Cursor config`);
193
+ }
194
+ catch (err) {
195
+ spinner.fail('Failed to update Cursor configuration');
196
+ console.log(createErrorBox('Integration Failed', err instanceof Error ? err.message : String(err)));
197
+ trackEvent('cli_cursor_failed', {
198
+ error: err instanceof Error ? err.message : String(err),
199
+ target,
200
+ type,
201
+ });
202
+ await shutdownAnalytics();
203
+ return;
204
+ }
205
+ // Summary
206
+ spacer();
207
+ console.log(createSuccessBox('Cursor Integration Successful', [
208
+ `Configured target: ${chalk.cyan(configFilePath)}`,
209
+ `Server registered: ${chalk.cyan(serverName)}`,
210
+ `Connection type: ${chalk.cyan(type)}`,
211
+ type === 'command'
212
+ ? `Command path: ${chalk.dim(path.join(projectRoot, 'dist', 'index.js'))}`
213
+ : `HTTP URL: ${chalk.cyan(httpUrl)}`
214
+ ]));
215
+ if (type === 'command') {
216
+ // Check if dist/index.js exists
217
+ const distIndexPath = path.join(projectRoot, 'dist', 'index.js');
218
+ if (!fs.existsSync(distIndexPath)) {
219
+ log('Warning: dist/index.js not found. Remember to run "npm run build" first!', 'warning');
220
+ spacer();
221
+ }
222
+ }
223
+ if (type === 'legacy-sse') {
224
+ log('Tip: ensure the server is running in dual or http mode before connecting from Cursor.', 'info');
225
+ }
226
+ nextSteps([
227
+ 'Restart/Reload your Cursor window to load the new MCP server',
228
+ 'Check active MCP servers in Cursor under Settings > Tools > MCP',
229
+ ]);
230
+ showFooter();
231
+ trackEvent('cli_cursor_completed', {
232
+ target,
233
+ type,
234
+ server_name: serverName,
235
+ duration_ms: Date.now() - startTime,
236
+ });
237
+ await shutdownAnalytics();
238
+ }
@@ -3,6 +3,7 @@ interface InitOptions {
3
3
  description?: string;
4
4
  author?: string;
5
5
  skipInstall?: boolean;
6
+ force?: boolean;
6
7
  }
7
8
  export declare function initCommand(projectName: string | undefined, options: InitOptions): Promise<void>;
8
9
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAqDA,UAAU,WAAW;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAsB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,WAAW,iBA+OtF"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AA4IA,UAAU,WAAW;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,wBAAsB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,WAAW,iBAmPtF"}
@@ -7,6 +7,80 @@ import inquirer from 'inquirer';
7
7
  import { execSync } from 'child_process';
8
8
  import { NITRO_BANNER_FULL, createSuccessBox, NitroSpinner, log, spacer, nextSteps, brand, showFooter } from '../ui/branding.js';
9
9
  import { trackEvent, shutdownAnalytics } from '../analytics/posthog.js';
10
+ import { runSkillsFlow } from '../skills/index.js';
11
+ import readline from 'readline';
12
+ /**
13
+ * Prompts the user with a horizontal YES/NO choice using left/right arrow keys.
14
+ */
15
+ async function promptYesNoHorizontal(message, defaultValue = false) {
16
+ return new Promise((resolve) => {
17
+ let value = defaultValue;
18
+ const stdin = process.stdin;
19
+ const stdout = process.stdout;
20
+ // Save TTY and raw mode state
21
+ const isRaw = stdin.isRaw;
22
+ if (stdin.isTTY) {
23
+ stdin.setRawMode(true);
24
+ }
25
+ readline.emitKeypressEvents(stdin);
26
+ stdin.resume();
27
+ // Hide cursor
28
+ stdout.write('\u001B[?25l');
29
+ const render = () => {
30
+ readline.clearLine(stdout, 0);
31
+ readline.cursorTo(stdout, 0);
32
+ const qMark = chalk.cyan('?');
33
+ const msg = chalk.bold(message);
34
+ const pointer = chalk.dim('›');
35
+ const separator = chalk.dim(' / ');
36
+ const noPart = !value
37
+ ? chalk.cyan.underline('No')
38
+ : chalk.dim('No');
39
+ const yesPart = value
40
+ ? chalk.cyan.underline('Yes')
41
+ : chalk.dim('Yes');
42
+ stdout.write(`${qMark} ${msg} ${pointer} ${noPart}${separator}${yesPart}`);
43
+ };
44
+ render();
45
+ const onKeypress = (str, key) => {
46
+ if (!key)
47
+ return;
48
+ if (key.name === 'left' || key.name === 'right') {
49
+ value = !value;
50
+ render();
51
+ }
52
+ else if (key.name === 'return' || key.name === 'enter') {
53
+ cleanup();
54
+ // Clear prompt line and print final answer
55
+ readline.clearLine(stdout, 0);
56
+ readline.cursorTo(stdout, 0);
57
+ const checkMark = chalk.green('✔');
58
+ const finalAns = value ? chalk.cyan('Yes') : chalk.cyan('No');
59
+ stdout.write(`${checkMark} ${chalk.white(message)} ${finalAns}\n`);
60
+ // Resolve after a small delay to prevent keypress bleeding into the next prompt
61
+ setTimeout(() => {
62
+ resolve(value);
63
+ }, 100);
64
+ }
65
+ else if (key.ctrl && key.name === 'c') {
66
+ cleanup();
67
+ process.exit(130); // SIGINT exit code
68
+ }
69
+ };
70
+ const cleanup = () => {
71
+ // Show cursor
72
+ stdout.write('\u001B[?25h');
73
+ stdin.removeListener('keypress', onKeypress);
74
+ // Consume any data currently sitting in the stream buffer
75
+ stdin.read();
76
+ if (stdin.isTTY) {
77
+ stdin.setRawMode(isRaw);
78
+ }
79
+ stdin.pause();
80
+ };
81
+ stdin.on('keypress', onKeypress);
82
+ });
83
+ }
10
84
  // ES module equivalent of __dirname
11
85
  const __filename = fileURLToPath(import.meta.url);
12
86
  const __dirname = dirname(__filename);
@@ -69,14 +143,7 @@ export async function initCommand(projectName, options) {
69
143
  const targetDir = path.join(process.cwd(), finalProjectName);
70
144
  // Check if directory exists
71
145
  if (fs.existsSync(targetDir)) {
72
- const { overwrite } = await inquirer.prompt([
73
- {
74
- type: 'confirm',
75
- name: 'overwrite',
76
- message: chalk.yellow(`Directory ${finalProjectName} already exists. Overwrite?`),
77
- default: false,
78
- },
79
- ]);
146
+ const overwrite = await promptYesNoHorizontal(chalk.yellow(`Directory ${finalProjectName} already exists. Overwrite?`), false);
80
147
  if (!overwrite) {
81
148
  log('Cancelled', 'warning');
82
149
  process.exit(0);
@@ -99,7 +166,7 @@ export async function initCommand(projectName, options) {
99
166
  value: 'typescript-pizzaz',
100
167
  },
101
168
  {
102
- name: `${brand.signal('OAuth')} ${chalk.dim('Flight booking with OAuth 2.1 auth')}`,
169
+ name: `${brand.signal('Flight booking')} ${chalk.dim('Flight booking with OAuth 2.1 auth')}`,
103
170
  value: 'typescript-oauth',
104
171
  },
105
172
  ],
@@ -152,6 +219,12 @@ export async function initCommand(projectName, options) {
152
219
  fs.writeJSONSync(packageJsonPath, packageJson, { spaces: 2 });
153
220
  }
154
221
  spinner.succeed('Project created');
222
+ // Agent skills prompt bypassed - installing project-level skills by default
223
+ // const addAgentSkills = await promptYesNoHorizontal('Add agent skills?', true);
224
+ // if (addAgentSkills) {
225
+ // await runSkillsFlow(options.force ?? false, targetDir);
226
+ // }
227
+ await runSkillsFlow(options.force ?? false, targetDir);
155
228
  // Install dependencies
156
229
  if (!options.skipInstall) {
157
230
  spinner = new NitroSpinner('Installing dependencies...').start();
@@ -2,6 +2,18 @@ interface UpgradeOptions {
2
2
  latest?: boolean;
3
3
  dryRun?: boolean;
4
4
  }
5
+ /**
6
+ * Fetch a package's latest published version from NPM using standard https module.
7
+ */
8
+ export declare function fetchLatestNpmVersion(packageName: string): Promise<string>;
9
+ /**
10
+ * Compare two version strings
11
+ */
12
+ export declare function compareVersions(v1: string, v2: string): number;
13
+ /**
14
+ * Determine if a dependency refers to a local file or workspace link.
15
+ */
16
+ export declare function isLocalDependency(version: string): boolean;
5
17
  /**
6
18
  * Main upgrade command handler
7
19
  */
@@ -1 +1 @@
1
- {"version":3,"file":"upgrade.d.ts","sourceRoot":"","sources":["../../src/commands/upgrade.ts"],"names":[],"mappings":"AA0BA,UAAU,cAAc;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAwHD;;GAEG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CA2J3E"}
1
+ {"version":3,"file":"upgrade.d.ts","sourceRoot":"","sources":["../../src/commands/upgrade.ts"],"names":[],"mappings":"AAmCA,UAAU,cAAc;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAUD;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAgD1E;AA2BD;;GAEG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAa9D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAM1D;AAqED;;GAEG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAmM3E"}