@nitrostack/cli 1.0.6 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +39 -96
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +4 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +24 -52
- package/dist/commands/generate-types.d.ts +0 -1
- package/dist/commands/generate-types.d.ts.map +1 -1
- package/dist/commands/generate-types.js +18 -39
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +12 -4
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +6 -5
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/install.js +3 -1
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +4 -4
- package/dist/commands/upgrade.d.ts.map +1 -1
- package/dist/commands/upgrade.js +92 -77
- package/dist/ui/branding.d.ts +21 -4
- package/dist/ui/branding.d.ts.map +1 -1
- package/dist/ui/branding.js +121 -52
- package/package.json +5 -6
- package/templates/typescript-oauth/.env.example +27 -0
- package/templates/typescript-oauth/README.md +36 -231
- package/templates/typescript-pizzaz/.env.example +8 -0
- package/templates/typescript-pizzaz/README.md +42 -217
- package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.module.ts +2 -1
- package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.tasks.ts +294 -0
- package/templates/typescript-starter/.env.example +7 -0
- package/templates/typescript-starter/README.md +51 -284
package/dist/ui/branding.js
CHANGED
|
@@ -1,59 +1,112 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import ora from 'ora';
|
|
3
3
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
4
|
-
//
|
|
4
|
+
// OFFICIAL MCP BRANDING (Wekan Enterprise Solutions)
|
|
5
5
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
6
|
+
// Core Colors
|
|
7
|
+
const SIGNAL_BLUE = '#187CF4'; // Primary
|
|
8
|
+
const SKY_BLUE = '#05A3FD'; // Secondary
|
|
9
|
+
const SUCCESS_MINT = '#2AB5A5'; // Success
|
|
10
|
+
const ERROR_FLAME = '#EF4444'; // Error
|
|
11
|
+
const WARNING_AMBER = '#F59E0B'; // Warning
|
|
12
|
+
const SLATE_INK = '#121217'; // Text Primary (Light Mode Context)
|
|
13
|
+
// Official Links
|
|
14
|
+
export const LINKS = {
|
|
15
|
+
website: 'https://nitrostack.ai',
|
|
16
|
+
docs: 'https://docs.nitrostack.ai',
|
|
17
|
+
studio: 'https://nitrostack.ai/studio',
|
|
18
|
+
};
|
|
9
19
|
export const brand = {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
20
|
+
signal: chalk.hex(SIGNAL_BLUE),
|
|
21
|
+
signalBold: chalk.hex(SIGNAL_BLUE).bold,
|
|
22
|
+
sky: chalk.hex(SKY_BLUE),
|
|
23
|
+
skyBold: chalk.hex(SKY_BLUE).bold,
|
|
24
|
+
mint: chalk.hex(SUCCESS_MINT),
|
|
25
|
+
mintBold: chalk.hex(SUCCESS_MINT).bold,
|
|
26
|
+
error: chalk.hex(ERROR_FLAME),
|
|
27
|
+
warning: chalk.hex(WARNING_AMBER),
|
|
14
28
|
};
|
|
29
|
+
const TOTAL_WIDTH = 68;
|
|
30
|
+
/**
|
|
31
|
+
* Strips ANSI codes for visual length calculation
|
|
32
|
+
*/
|
|
33
|
+
const stripAnsi = (str) => str.replace(/\u001b\[[0-9;]*m/g, '');
|
|
34
|
+
/**
|
|
35
|
+
* Creates a clickable terminal link using OSC 8 escape sequence
|
|
36
|
+
*/
|
|
37
|
+
export function terminalLink(text, url) {
|
|
38
|
+
// OSC 8 escape sequence: \x1b]8;;url\x1b\\text\x1b]8;;\x1b\\
|
|
39
|
+
return `\u001b]8;;${url}\u001b\\${text}\u001b]8;;\u001b\\`;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Creates a line for a box with consistent width and borders
|
|
43
|
+
*/
|
|
44
|
+
function boxLine(content, borderColor = brand.signalBold) {
|
|
45
|
+
const visualLength = stripAnsi(content).length;
|
|
46
|
+
const paddingSize = Math.max(0, TOTAL_WIDTH - visualLength - 2);
|
|
47
|
+
const padding = ' '.repeat(paddingSize);
|
|
48
|
+
return borderColor('║') + content + padding + borderColor('║');
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Redesigned Banner with Restored ASCII NITRO
|
|
52
|
+
*/
|
|
15
53
|
export const NITRO_BANNER_FULL = `
|
|
16
|
-
${brand.
|
|
17
|
-
${
|
|
18
|
-
${
|
|
19
|
-
${
|
|
20
|
-
${
|
|
21
|
-
${
|
|
22
|
-
${
|
|
23
|
-
${
|
|
24
|
-
${
|
|
25
|
-
${brand.
|
|
26
|
-
${
|
|
27
|
-
${brand.
|
|
54
|
+
${brand.signalBold('╔' + '═'.repeat(TOTAL_WIDTH - 2) + '╗')}
|
|
55
|
+
${boxLine('')}
|
|
56
|
+
${boxLine(' ' + brand.signalBold('███╗ ██╗██╗████████╗██████╗ ██████╗ '))}
|
|
57
|
+
${boxLine(' ' + brand.signalBold('████╗ ██║██║╚══██╔══╝██╔══██╗██╔═══██╗'))}
|
|
58
|
+
${boxLine(' ' + brand.signalBold('██╔██╗ ██║██║ ██║ ██████╔╝██║ ██║'))}
|
|
59
|
+
${boxLine(' ' + brand.skyBold('██║╚██╗██║██║ ██║ ██╔══██╗██║ ██║'))}
|
|
60
|
+
${boxLine(' ' + brand.skyBold('██║ ╚████║██║ ██║ ██║ ██║╚██████╔╝'))}
|
|
61
|
+
${boxLine(' ' + chalk.dim('╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ '))}
|
|
62
|
+
${boxLine('')}
|
|
63
|
+
${boxLine(' ' + brand.signalBold('NITROSTACK') + ' ' + chalk.dim('─ Official MCP Framework'))}
|
|
64
|
+
${boxLine('')}
|
|
65
|
+
${brand.signalBold('╚' + '═'.repeat(TOTAL_WIDTH - 2) + '╝')}
|
|
28
66
|
`;
|
|
29
67
|
export function createHeader(title, subtitle) {
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
68
|
+
const content = ' ' + brand.signalBold('NITROSTACK') + ' ' + chalk.dim('─') + ' ' + chalk.white.bold(title);
|
|
69
|
+
const subContent = subtitle ? ' ' + chalk.dim(subtitle) : '';
|
|
70
|
+
const borderTop = brand.signalBold('┌' + '─'.repeat(TOTAL_WIDTH - 2) + '┐');
|
|
71
|
+
const borderBottom = brand.signalBold('└' + '─'.repeat(TOTAL_WIDTH - 2) + '┘');
|
|
72
|
+
const line = (c) => {
|
|
73
|
+
const visualLength = stripAnsi(c).length;
|
|
74
|
+
const paddingSize = Math.max(0, TOTAL_WIDTH - visualLength - 2);
|
|
75
|
+
const padding = ' '.repeat(paddingSize);
|
|
76
|
+
return brand.signalBold('│') + c + padding + brand.signalBold('│');
|
|
77
|
+
};
|
|
78
|
+
let header = `\n${borderTop}\n${line(content)}\n`;
|
|
79
|
+
if (subtitle) {
|
|
80
|
+
header += `${line(subContent)}\n`;
|
|
81
|
+
}
|
|
82
|
+
header += `${borderBottom}\n`;
|
|
83
|
+
return header;
|
|
37
84
|
}
|
|
38
85
|
export function createBox(lines, type = 'info') {
|
|
39
86
|
const colors = {
|
|
40
|
-
success: { border:
|
|
41
|
-
error: { border:
|
|
42
|
-
info: { border: brand.
|
|
43
|
-
warning: { border:
|
|
87
|
+
success: { border: brand.mint, bTop: '┌', bSide: '│', bBot: '└' },
|
|
88
|
+
error: { border: brand.error, bTop: '┌', bSide: '│', bBot: '└' },
|
|
89
|
+
info: { border: brand.signal, bTop: '┌', bSide: '│', bBot: '└' },
|
|
90
|
+
warning: { border: brand.warning, bTop: '┌', bSide: '│', bBot: '└' },
|
|
44
91
|
};
|
|
45
|
-
const { border } = colors[type];
|
|
46
|
-
let output = border('
|
|
47
|
-
for (
|
|
48
|
-
const
|
|
49
|
-
|
|
92
|
+
const { border, bTop, bSide, bBot } = colors[type];
|
|
93
|
+
let output = border(bTop + '─'.repeat(TOTAL_WIDTH - 2) + '┐\n');
|
|
94
|
+
for (let line of lines) {
|
|
95
|
+
const maxInnerWidth = TOTAL_WIDTH - 6;
|
|
96
|
+
const visualLength = stripAnsi(line).length;
|
|
97
|
+
if (visualLength > maxInnerWidth) {
|
|
98
|
+
line = line.substring(0, maxInnerWidth - 3) + '...';
|
|
99
|
+
}
|
|
100
|
+
const finalVisualLength = stripAnsi(line).length;
|
|
101
|
+
const padding = ' '.repeat(Math.max(0, TOTAL_WIDTH - finalVisualLength - 6));
|
|
102
|
+
output += border(bSide) + ' ' + line + padding + ' ' + border(bSide) + '\n';
|
|
50
103
|
}
|
|
51
|
-
output += border('
|
|
104
|
+
output += border(bBot + '─'.repeat(TOTAL_WIDTH - 2) + '┘');
|
|
52
105
|
return output;
|
|
53
106
|
}
|
|
54
107
|
export function createSuccessBox(title, items) {
|
|
55
108
|
const lines = [
|
|
56
|
-
|
|
109
|
+
brand.mintBold(`✓ ${title}`),
|
|
57
110
|
'',
|
|
58
111
|
...items.map(item => chalk.dim(` ${item}`)),
|
|
59
112
|
'',
|
|
@@ -62,15 +115,15 @@ export function createSuccessBox(title, items) {
|
|
|
62
115
|
}
|
|
63
116
|
export function createErrorBox(title, message) {
|
|
64
117
|
const lines = [
|
|
65
|
-
|
|
118
|
+
brand.error.bold(`✗ ${title}`),
|
|
66
119
|
'',
|
|
67
|
-
chalk.white(message.substring(0,
|
|
120
|
+
chalk.white(message.substring(0, TOTAL_WIDTH - 10)),
|
|
68
121
|
'',
|
|
69
122
|
];
|
|
70
123
|
return createBox(lines, 'error');
|
|
71
124
|
}
|
|
72
125
|
export function createInfoBox(items) {
|
|
73
|
-
const lines = items.map(({ label, value }) => `${chalk.white.bold(label.padEnd(14))} ${
|
|
126
|
+
const lines = items.map(({ label, value }) => `${chalk.white.bold(label.padEnd(14))} ${brand.sky(value)}`);
|
|
74
127
|
return createBox(['', ...lines, ''], 'info');
|
|
75
128
|
}
|
|
76
129
|
export class NitroSpinner {
|
|
@@ -78,7 +131,7 @@ export class NitroSpinner {
|
|
|
78
131
|
constructor(text) {
|
|
79
132
|
this.spinner = ora({
|
|
80
133
|
text: chalk.dim(text),
|
|
81
|
-
color: '
|
|
134
|
+
color: 'blue',
|
|
82
135
|
spinner: 'dots12',
|
|
83
136
|
});
|
|
84
137
|
}
|
|
@@ -91,19 +144,19 @@ export class NitroSpinner {
|
|
|
91
144
|
return this;
|
|
92
145
|
}
|
|
93
146
|
succeed(text) {
|
|
94
|
-
this.spinner.succeed(text ?
|
|
147
|
+
this.spinner.succeed(text ? brand.mint('✓ ') + chalk.dim(text) : undefined);
|
|
95
148
|
return this;
|
|
96
149
|
}
|
|
97
150
|
fail(text) {
|
|
98
|
-
this.spinner.fail(text ?
|
|
151
|
+
this.spinner.fail(text ? brand.error('✗ ') + chalk.dim(text) : undefined);
|
|
99
152
|
return this;
|
|
100
153
|
}
|
|
101
154
|
info(text) {
|
|
102
|
-
this.spinner.info(text ?
|
|
155
|
+
this.spinner.info(text ? brand.signal('ℹ ') + chalk.dim(text) : undefined);
|
|
103
156
|
return this;
|
|
104
157
|
}
|
|
105
158
|
warn(text) {
|
|
106
|
-
this.spinner.warn(text ?
|
|
159
|
+
this.spinner.warn(text ? brand.warning('⚠ ') + chalk.dim(text) : undefined);
|
|
107
160
|
return this;
|
|
108
161
|
}
|
|
109
162
|
stop() {
|
|
@@ -113,16 +166,16 @@ export class NitroSpinner {
|
|
|
113
166
|
}
|
|
114
167
|
export function log(message, type = 'info') {
|
|
115
168
|
const icons = {
|
|
116
|
-
success:
|
|
117
|
-
error:
|
|
118
|
-
info:
|
|
119
|
-
warning:
|
|
169
|
+
success: brand.mint('✓'),
|
|
170
|
+
error: brand.error('✗'),
|
|
171
|
+
info: brand.signal('ℹ'),
|
|
172
|
+
warning: brand.warning('⚠'),
|
|
120
173
|
dim: chalk.dim('·'),
|
|
121
174
|
};
|
|
122
175
|
console.log(` ${icons[type]} ${type === 'dim' ? chalk.dim(message) : message}`);
|
|
123
176
|
}
|
|
124
177
|
export function divider() {
|
|
125
|
-
console.log(chalk.dim('
|
|
178
|
+
console.log(chalk.dim(' ' + '─'.repeat(TOTAL_WIDTH - 4)));
|
|
126
179
|
}
|
|
127
180
|
export function spacer() {
|
|
128
181
|
console.log('');
|
|
@@ -130,7 +183,23 @@ export function spacer() {
|
|
|
130
183
|
export function nextSteps(steps) {
|
|
131
184
|
console.log(chalk.white.bold('\n Next steps:\n'));
|
|
132
185
|
steps.forEach((step, i) => {
|
|
133
|
-
console.log(chalk.dim(` ${i + 1}.`) + ' ' +
|
|
186
|
+
console.log(chalk.dim(` ${i + 1}.`) + ' ' + brand.sky(step));
|
|
134
187
|
});
|
|
188
|
+
// Always promote NitroStudio
|
|
189
|
+
const studioStep = steps.length + 1;
|
|
190
|
+
const studioText = brand.signalBold('Download NitroStudio');
|
|
191
|
+
const urlText = brand.sky(LINKS.studio);
|
|
192
|
+
const clickableUrl = terminalLink(urlText, LINKS.studio);
|
|
193
|
+
console.log(chalk.dim(` ${studioStep}.`) + ' ' + studioText + ' ' + chalk.dim('(') + clickableUrl + chalk.dim(')') + ' ' + chalk.dim('to run your MCP project with a visual client'));
|
|
135
194
|
console.log('');
|
|
136
195
|
}
|
|
196
|
+
export function showFooter() {
|
|
197
|
+
const website = terminalLink(brand.signal(LINKS.website), LINKS.website);
|
|
198
|
+
const docs = terminalLink(brand.signal(LINKS.docs), LINKS.docs);
|
|
199
|
+
const studio = terminalLink(brand.signal(LINKS.studio), LINKS.studio);
|
|
200
|
+
const content = `${chalk.dim('Website:')} ${website} ${chalk.dim('Docs:')} ${docs} ${chalk.dim('Studio:')} ${studio}`;
|
|
201
|
+
// Center the footer content
|
|
202
|
+
const visualLength = stripAnsi(content).length;
|
|
203
|
+
const padding = ' '.repeat(Math.max(0, Math.floor((TOTAL_WIDTH - visualLength) / 2)));
|
|
204
|
+
console.log('\n' + padding + content + '\n');
|
|
205
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nitrostack/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "CLI for NitroStack - Create and manage MCP server projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"scaffold",
|
|
37
37
|
"generator"
|
|
38
38
|
],
|
|
39
|
-
"author": "
|
|
39
|
+
"author": "Nitrostack Inc <hello@nitrostack.ai>",
|
|
40
40
|
"license": "Apache-2.0",
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"chalk": "^5.3.0",
|
|
@@ -59,12 +59,11 @@
|
|
|
59
59
|
},
|
|
60
60
|
"repository": {
|
|
61
61
|
"type": "git",
|
|
62
|
-
"url": "https://github.com/
|
|
62
|
+
"url": "https://github.com/nitrocloudofficial/nitrostack.git",
|
|
63
63
|
"directory": "typescript/packages/cli"
|
|
64
64
|
},
|
|
65
65
|
"bugs": {
|
|
66
|
-
"url": "https://github.com/
|
|
66
|
+
"url": "https://github.com/nitrocloudofficial/nitrostack/issues"
|
|
67
67
|
},
|
|
68
68
|
"homepage": "https://nitrostack.ai"
|
|
69
|
-
}
|
|
70
|
-
|
|
69
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# OAuth 2.1 MCP Server Configuration
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# TRANSPORT MODE (AUTO-CONFIGURED)
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# When OAuth is configured, the server automatically runs in DUAL mode:
|
|
6
|
+
# - STDIO: For MCP proto¯col communication with Studio/Claude
|
|
7
|
+
# - HTTP: For OAuth metadata endpoints (/.well-known/oauth-protected-resource)
|
|
8
|
+
# Both transports run simultaneously on different channels.
|
|
9
|
+
# =============================================================================
|
|
10
|
+
# REQUIRED: Server Configuration
|
|
11
|
+
# =============================================================================
|
|
12
|
+
# =============================================================================
|
|
13
|
+
# Auth0 Configuration
|
|
14
|
+
# =============================================================================
|
|
15
|
+
# After creating your API and Application in Auth0, fill in these values:
|
|
16
|
+
# Your Auth0 API Identifier (from APIs → MCP Server → Identifier)
|
|
17
|
+
# This MUST match exactly what you set when creating the API
|
|
18
|
+
# This MUST match exactly what you set when creating the API
|
|
19
|
+
RESOURCE_URI=https://mcplocal
|
|
20
|
+
# Your Auth0 tenant domain (authorization server)
|
|
21
|
+
AUTH_SERVER_URL=https://dev-5dt0utuk315713tjm.us.auth0.com
|
|
22
|
+
# Expected token audience (should match RESOURCE_URI)
|
|
23
|
+
TOKEN_AUDIENCE=https://mcplocal
|
|
24
|
+
# Expected token issuer (your Auth0 tenant domain with trailing slash)
|
|
25
|
+
TOKEN_ISSUER=https://dev-5dt0utuk315713tjm.us.auth0.com
|
|
26
|
+
|
|
27
|
+
DUFFEL_API_KEY=duffel_test_-w0wGDHB0M3DU9k-sBeUbxLqwcibUQqfEbjWDTKNnly
|
|
@@ -1,263 +1,68 @@
|
|
|
1
1
|
# ✈️ NitroStack Flight Booking
|
|
2
2
|
|
|
3
|
-
A production-ready
|
|
3
|
+
A production-ready template showcasing real-time flight search and booking with **Duffel API** integration. Learn how to build complex, authenticated MCP servers with high-impact visual widgets.
|
|
4
4
|
|
|
5
5
|
## ✨ Features
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
- Booking and order management
|
|
7
|
+
- **Real-time Search** — Powered by the Duffel API (300+ airlines).
|
|
8
|
+
- **Seat Selection** — Interactive visual cabin maps with seat pickers.
|
|
9
|
+
- **Secure Payments** — Simulated secure booking and confirmation flows.
|
|
10
|
+
- **Advanced Auth** — Optional OAuth 2.1 support for protected MCP servers.
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
- **Airport Search** - Autocomplete airport selection
|
|
15
|
-
- **Flight Search Results** - Compare flights with prices
|
|
16
|
-
- **Flight Details** - Comprehensive flight information
|
|
17
|
-
- **Seat Selection** - Visual cabin map with seat picker
|
|
18
|
-
- **Order Summary** - Complete booking overview
|
|
19
|
-
- **Payment Confirmation** - Secure payment flow
|
|
20
|
-
|
|
21
|
-
### 🛠️ **MCP Tools**
|
|
22
|
-
- `search_airports` - Find airports by name/code
|
|
23
|
-
- `search_flights` - Search available flights
|
|
24
|
-
- `show_flight_details` - Detailed flight information
|
|
25
|
-
- `select_seats` - Interactive seat selection
|
|
26
|
-
- `create_booking` - Book flights
|
|
27
|
-
- `get_order` - Retrieve booking details
|
|
12
|
+
---
|
|
28
13
|
|
|
29
14
|
## 🚀 Quick Start
|
|
30
15
|
|
|
31
|
-
###
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
# Install NitroStack CLI globally
|
|
35
|
-
npm install -g nitrostack
|
|
36
|
-
|
|
37
|
-
# Or use npx
|
|
38
|
-
npx nitrostack --version
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### 1. Create Your Project
|
|
16
|
+
### 1. Initialize Your Project
|
|
42
17
|
|
|
43
18
|
```bash
|
|
44
|
-
|
|
45
|
-
nitrostack init my-flight-app --template typescript-oauth
|
|
19
|
+
npx nitrostack init my-flight-app --template typescript-oauth
|
|
46
20
|
cd my-flight-app
|
|
47
|
-
|
|
48
|
-
# Install all dependencies (root + widgets)
|
|
49
|
-
nitrostack install
|
|
50
21
|
```
|
|
51
22
|
|
|
52
|
-
### 2.
|
|
53
|
-
|
|
54
|
-
[Duffel](https://duffel.com/) provides a free API for flight search and booking:
|
|
55
|
-
|
|
56
|
-
1. **Sign up** at [duffel.com](https://duffel.com/) (click "Start now" - it's free!)
|
|
57
|
-
2. Create a free account (no credit card required)
|
|
58
|
-
3. Go to your **Dashboard** → **Developers** → **Access tokens**
|
|
59
|
-
4. Click **"Create token"** to generate your API key
|
|
60
|
-
5. Copy the token (starts with `duffel_test_` for sandbox)
|
|
61
|
-
|
|
62
|
-
> 💡 **Tip**: The test mode (`duffel_test_*` tokens) gives you access to realistic mock data for development. Production tokens (`duffel_live_*`) connect to real airlines.
|
|
63
|
-
|
|
64
|
-
### 3. Configure Environment
|
|
65
|
-
|
|
66
|
-
Copy the example environment file and add your Duffel API key:
|
|
23
|
+
### 2. Install Dependencies
|
|
67
24
|
|
|
68
25
|
```bash
|
|
69
|
-
|
|
26
|
+
npm run install:all
|
|
70
27
|
```
|
|
71
28
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
```env
|
|
75
|
-
# Duffel API Configuration
|
|
76
|
-
DUFFEL_API_KEY=duffel_test_your_api_key_here
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### 4. Run Development Server
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
npm run dev
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
This starts:
|
|
86
|
-
- **MCP Server** - Hot reloads on code changes
|
|
87
|
-
- **Studio** on http://localhost:3000 - Visual testing environment
|
|
88
|
-
- **Widget Dev Server** on http://localhost:3001 - Hot module replacement
|
|
89
|
-
|
|
90
|
-
### 5. Test in Studio
|
|
91
|
-
|
|
92
|
-
Try these prompts in Studio chat:
|
|
93
|
-
- "Search flights from London to New York for next week"
|
|
94
|
-
- "Show me flight details"
|
|
95
|
-
- "I want to select seats"
|
|
96
|
-
- "Show me flights from JFK to LAX"
|
|
97
|
-
|
|
98
|
-
## 📁 Project Structure
|
|
99
|
-
|
|
100
|
-
```
|
|
101
|
-
typescript-oauth/
|
|
102
|
-
├── src/
|
|
103
|
-
│ ├── index.ts # Main server entry
|
|
104
|
-
│ ├── app.module.ts # App module
|
|
105
|
-
│ ├── services/
|
|
106
|
-
│ │ └── duffel.service.ts # Duffel API client
|
|
107
|
-
│ └── modules/
|
|
108
|
-
│ └── flights/
|
|
109
|
-
│ ├── flights.module.ts # Module definition
|
|
110
|
-
│ ├── flights.tools.ts # Search & display tools
|
|
111
|
-
│ ├── flights.prompts.ts # AI prompts
|
|
112
|
-
│ ├── flights.resources.ts # Static resources
|
|
113
|
-
│ └── booking.tools.ts # Booking tools
|
|
114
|
-
│ └── widgets/
|
|
115
|
-
│ ├── app/
|
|
116
|
-
│ │ ├── airport-search/ # Airport autocomplete
|
|
117
|
-
│ │ ├── flight-search-results/ # Results list
|
|
118
|
-
│ │ ├── flight-details/ # Flight info
|
|
119
|
-
│ │ ├── seat-selection/ # Seat picker
|
|
120
|
-
│ │ ├── order-summary/ # Booking summary
|
|
121
|
-
│ │ └── payment-confirmation/ # Payment widget
|
|
122
|
-
├── .env.example # Environment template
|
|
123
|
-
├── OAUTH_SETUP.md # OAuth configuration guide
|
|
124
|
-
├── package.json
|
|
125
|
-
└── README.md
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
## 🔧 Commands
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
# Installation
|
|
132
|
-
npm install # Install all dependencies (root + widgets)
|
|
133
|
-
nitrostack install # Same as above
|
|
29
|
+
### 3. Get NitroStudio
|
|
134
30
|
|
|
135
|
-
|
|
136
|
-
npm run dev # Start dev server with Studio
|
|
137
|
-
npm run build # Build TypeScript and widgets for production
|
|
138
|
-
npm start # Run production server
|
|
31
|
+
Experience the visual seat selection and flight search as your users would see it.
|
|
139
32
|
|
|
140
|
-
|
|
141
|
-
|
|
33
|
+
1. **Download NitroStudio**: [nitrostack.ai/studio](https://nitrostack.ai/studio)
|
|
34
|
+
2. **Open Project**: Launch NitroStudio and select your project folder.
|
|
142
35
|
|
|
143
|
-
|
|
144
|
-
npm run widget <command> # Run npm command in widgets directory
|
|
145
|
-
npm run widget add <pkg> # Add a widget dependency
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
## 🔐 OAuth Setup (Optional)
|
|
149
|
-
|
|
150
|
-
This template includes OAuth 2.1 authentication support. If you want to protect your MCP server with authentication:
|
|
151
|
-
|
|
152
|
-
1. Read the detailed guide: **[OAUTH_SETUP.md](./OAUTH_SETUP.md)**
|
|
153
|
-
2. Configure Auth0 (or your OAuth provider)
|
|
154
|
-
3. Update your `.env` with OAuth credentials
|
|
155
|
-
|
|
156
|
-
> **Note**: OAuth is optional for local development. The template works without it using Duffel's test API.
|
|
157
|
-
|
|
158
|
-
## 📊 Duffel API Features
|
|
159
|
-
|
|
160
|
-
### Test Mode vs Live Mode
|
|
161
|
-
|
|
162
|
-
| Feature | Test Mode (`duffel_test_*`) | Live Mode (`duffel_live_*`) |
|
|
163
|
-
|---------|----------------------------|----------------------------|
|
|
164
|
-
| API Access | ✅ Full access | ✅ Full access |
|
|
165
|
-
| Pricing | Mock data | Real prices |
|
|
166
|
-
| Bookings | Simulated | Real bookings |
|
|
167
|
-
| Credit Card | Not charged | Charged |
|
|
168
|
-
| Rate Limits | Generous | Production limits |
|
|
169
|
-
|
|
170
|
-
### Supported Airlines
|
|
171
|
-
|
|
172
|
-
Duffel provides access to 300+ airlines including:
|
|
173
|
-
- Major carriers (British Airways, Lufthansa, Emirates, etc.)
|
|
174
|
-
- Low-cost carriers (Ryanair, EasyJet, Spirit, etc.)
|
|
175
|
-
- Regional airlines
|
|
176
|
-
|
|
177
|
-
### API Capabilities
|
|
178
|
-
|
|
179
|
-
- **Search**: Real-time availability and pricing
|
|
180
|
-
- **Ancillaries**: Seats, bags, meals
|
|
181
|
-
- **Booking**: Create and manage orders
|
|
182
|
-
- **Changes**: Modify existing bookings
|
|
183
|
-
- **Cancellations**: Cancel and refund
|
|
184
|
-
|
|
185
|
-
## 🎨 Widget Features
|
|
186
|
-
|
|
187
|
-
### Airport Search Widget
|
|
188
|
-
- Autocomplete with IATA codes
|
|
189
|
-
- City and airport name search
|
|
190
|
-
- Recent searches
|
|
191
|
-
|
|
192
|
-
### Flight Search Results Widget
|
|
193
|
-
- Compare multiple flights
|
|
194
|
-
- Filter by stops, price, time
|
|
195
|
-
- Sort options
|
|
196
|
-
- Airline logos
|
|
197
|
-
|
|
198
|
-
### Flight Details Widget
|
|
199
|
-
- Complete itinerary
|
|
200
|
-
- Fare breakdown
|
|
201
|
-
- Baggage allowance
|
|
202
|
-
- Flight duration and stops
|
|
203
|
-
|
|
204
|
-
### Seat Selection Widget
|
|
205
|
-
- Visual cabin map
|
|
206
|
-
- Seat categories (standard, extra legroom, etc.)
|
|
207
|
-
- Price per seat
|
|
208
|
-
- Real-time availability
|
|
209
|
-
|
|
210
|
-
## 🛠️ Customization
|
|
211
|
-
|
|
212
|
-
### Adding New Airlines
|
|
213
|
-
|
|
214
|
-
Duffel handles airline integrations automatically. You don't need to configure individual airlines.
|
|
215
|
-
|
|
216
|
-
### Modifying Search Parameters
|
|
217
|
-
|
|
218
|
-
Edit `src/modules/flights/flights.tools.ts` to customize:
|
|
219
|
-
- Default passenger counts
|
|
220
|
-
- Cabin class options
|
|
221
|
-
- Date ranges
|
|
222
|
-
- Result limits
|
|
223
|
-
|
|
224
|
-
### Styling Widgets
|
|
225
|
-
|
|
226
|
-
Each widget in `src/widgets/app/` can be customized:
|
|
227
|
-
- Edit page.tsx for layout
|
|
228
|
-
- Add custom CSS
|
|
229
|
-
- Modify component styles
|
|
36
|
+
---
|
|
230
37
|
|
|
231
|
-
##
|
|
38
|
+
## ⚙️ Configuration
|
|
232
39
|
|
|
233
|
-
### Duffel
|
|
234
|
-
- [Duffel API Docs](https://duffel.com/docs/api)
|
|
235
|
-
- [Duffel SDK (Node.js)](https://github.com/duffel/duffel-api-javascript)
|
|
236
|
-
- [Duffel Postman Collection](https://duffel.com/docs/postman)
|
|
40
|
+
### 1. Duffel API Key
|
|
237
41
|
|
|
238
|
-
|
|
239
|
-
- [NitroStack Docs](https://nitrostack.ai/docs)
|
|
240
|
-
- [Widget Development Guide](https://nitrostack.ai/docs/widgets)
|
|
42
|
+
You need a free Duffel API key to see live flight data:
|
|
241
43
|
|
|
242
|
-
|
|
44
|
+
1. Sign up at [duffel.com](https://duffel.com/).
|
|
45
|
+
2. Copy your **Test Token** from the dashboard.
|
|
46
|
+
3. Create `.env` from the example:
|
|
47
|
+
```bash
|
|
48
|
+
cp .env.example .env
|
|
49
|
+
```
|
|
50
|
+
4. Update `DUFFEL_API_KEY` in your `.env`.
|
|
243
51
|
|
|
244
|
-
|
|
245
|
-
- **Check Rate Limits**: Duffel has rate limits - cache searches when possible
|
|
246
|
-
- **Error Handling**: The template includes comprehensive error handling
|
|
247
|
-
- **Responsive Design**: Widgets are mobile-friendly out of the box
|
|
52
|
+
### 2. OAuth (Optional)
|
|
248
53
|
|
|
249
|
-
|
|
54
|
+
For production scenarios requiring user authentication, refer to [OAUTH_SETUP.md](./OAUTH_SETUP.md).
|
|
250
55
|
|
|
251
|
-
|
|
252
|
-
- Try the **Pizza Shop Template** - Interactive maps with Mapbox
|
|
253
|
-
- Read the [Duffel Getting Started Guide](https://duffel.com/docs/guides/getting-started)
|
|
56
|
+
---
|
|
254
57
|
|
|
255
|
-
##
|
|
58
|
+
## 🛠️ Commands
|
|
256
59
|
|
|
257
|
-
|
|
60
|
+
- `npm run dev` — Start the development ecosystem.
|
|
61
|
+
- `npm run build` — Bundle TypeScript and widgets for deployment.
|
|
62
|
+
- `npm run upgrade` — Keep NitroStack core up to date.
|
|
258
63
|
|
|
259
64
|
---
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
65
|
+
**Official Resources**
|
|
66
|
+
- [Website](https://nitrostack.ai)
|
|
67
|
+
- [Docs](https://docs.nitrostack.ai)
|
|
68
|
+
- [Duffel API](https://duffel.com/docs)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Mapbox Configuration (Optional)
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# The map widget uses Mapbox GL for interactive maps.
|
|
4
|
+
# Get a free API key at: https://www.mapbox.com/
|
|
5
|
+
# =============================================================================
|
|
6
|
+
|
|
7
|
+
# Your Mapbox Public Token (starts with pk.)
|
|
8
|
+
NEXT_PUBLIC_MAPBOX_TOKEN=pk.your_mapbox_token_here
|