@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.
@@ -1,59 +1,112 @@
1
1
  import chalk from 'chalk';
2
2
  import ora from 'ora';
3
3
  // ═══════════════════════════════════════════════════════════════════════════
4
- // NITROSTACK CLI BRANDING
4
+ // OFFICIAL MCP BRANDING (Wekan Enterprise Solutions)
5
5
  // ═══════════════════════════════════════════════════════════════════════════
6
- const ACCENT = '#FF6B35';
7
- const ACCENT_LIGHT = '#FF8C42';
8
- const ACCENT_LIGHTER = '#FFA94D';
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
- accent: chalk.hex(ACCENT),
11
- accentBold: chalk.hex(ACCENT).bold,
12
- accentLight: chalk.hex(ACCENT_LIGHT),
13
- accentLighter: chalk.hex(ACCENT_LIGHTER),
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.accentBold('╔══════════════════════════════════════════════════════════════════╗')}
17
- ${brand.accentBold('')} ${brand.accentBold('║')}
18
- ${brand.accentBold('')} ${brand.accentBold('███╗ ██╗██╗████████╗██████╗ ██████╗ ')} ${brand.accentBold('║')}
19
- ${brand.accentBold('')} ${brand.accentLight('████╗ ██║██║╚══██╔══╝██╔══██╗██╔═══██╗')} ${brand.accentBold('║')}
20
- ${brand.accentBold('')} ${brand.accentLighter('██╔██╗ ██║██║ ██║ ██████╔╝██║ ██║')} ${brand.accentBold('║')}
21
- ${brand.accentBold('')} ${chalk.hex('#FFCC80')('██║╚██╗██║██║ ██║ ██╔══██╗██║ ██║')} ${brand.accentBold('║')}
22
- ${brand.accentBold('')} ${chalk.hex('#FFE0B2')('██║ ╚████║██║ ██║ ██║ ██║╚██████╔╝')} ${brand.accentBold('║')}
23
- ${brand.accentBold('')} ${chalk.dim('╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ')} ${brand.accentBold('║')}
24
- ${brand.accentBold('')} ${brand.accentBold('║')}
25
- ${brand.accentBold('')} ${chalk.white.bold('S T A C K')} ${chalk.dim('─────────────────────────────────────────')} ${brand.accentBold('║')}
26
- ${brand.accentBold('')} ${brand.accentBold('║')}
27
- ${brand.accentBold('╚══════════════════════════════════════════════════════════════════╝')}
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 paddedTitle = title.padEnd(40);
31
- const paddedSubtitle = (subtitle || '').padEnd(40);
32
- return `
33
- ${brand.accentBold('┌──────────────────────────────────────────────────────────────────┐')}
34
- ${brand.accentBold('│')} ${brand.accentBold('NITRO')}${chalk.white.bold('STACK')} ${chalk.dim('━━')} ${chalk.white.bold(paddedTitle)} ${brand.accentBold('│')}
35
- ${subtitle ? brand.accentBold('│') + ' ' + chalk.dim(paddedSubtitle) + ' ' + brand.accentBold('│') + '\n' : ''}${brand.accentBold('└──────────────────────────────────────────────────────────────────┘')}
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: chalk.green, title: chalk.green.bold },
41
- error: { border: chalk.red, title: chalk.red.bold },
42
- info: { border: brand.accent, title: brand.accentBold },
43
- warning: { border: chalk.yellow, title: chalk.yellow.bold },
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('┌──────────────────────────────────────────────────────────────────┐\n');
47
- for (const line of lines) {
48
- const paddedLine = line.padEnd(64);
49
- output += border('│') + ' ' + paddedLine + ' ' + border('│') + '\n';
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
- chalk.green.bold(`✓ ${title}`),
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
- chalk.red.bold(`✗ ${title}`),
118
+ brand.error.bold(`✗ ${title}`),
66
119
  '',
67
- chalk.white(message.substring(0, 60)),
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))} ${chalk.cyan(value)}`);
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: 'yellow',
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 ? chalk.green('✓ ') + chalk.dim(text) : undefined);
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 ? chalk.red('✗ ') + chalk.dim(text) : undefined);
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 ? chalk.blue('ℹ ') + chalk.dim(text) : undefined);
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 ? chalk.yellow('⚠ ') + chalk.dim(text) : undefined);
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: chalk.green('✓'),
117
- error: chalk.red('✗'),
118
- info: chalk.blue('ℹ'),
119
- warning: chalk.yellow('⚠'),
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}.`) + ' ' + chalk.cyan(step));
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.6",
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": "Abhishek Pandit <abhishekpanditofficial@gmail.com>",
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/abhishekpanditofficial/nitrostack.git",
62
+ "url": "https://github.com/nitrocloudofficial/nitrostack.git",
63
63
  "directory": "typescript/packages/cli"
64
64
  },
65
65
  "bugs": {
66
- "url": "https://github.com/abhishekpanditofficial/nitrostack/issues"
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 NitroStack template showcasing real-time flight search and booking with **Duffel API** integration. Search flights, view details, select seats, and book - all through beautiful interactive widgets.
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
- ### 🔌 **Duffel API Integration**
8
- - Real-time flight search across 300+ airlines
9
- - Live pricing and availability
10
- - Seat selection with cabin maps
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
- ### 🎨 **Interactive Widgets**
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
- ### Prerequisites
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
- # Create a new project
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. Get Your Duffel API Key (Free)
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
- cp .env.example .env
26
+ npm run install:all
70
27
  ```
71
28
 
72
- Edit `.env` and update:
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
- # Development
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
- # Upgrade
141
- npm run upgrade # Upgrade nitrostack to latest version
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
- # Widget Management
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
- ## 📚 Resources
38
+ ## ⚙️ Configuration
232
39
 
233
- ### Duffel Documentation
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
- ### NitroStack Documentation
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
- ## 💡 Tips
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
- - **Use Test Mode**: Start with `duffel_test_*` tokens for development
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
- ## 📚 Next Steps
54
+ For production scenarios requiring user authentication, refer to [OAUTH_SETUP.md](./OAUTH_SETUP.md).
250
55
 
251
- - Try the **Starter Template** - Learn NitroStack basics
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
- ## License
58
+ ## 🛠️ Commands
256
59
 
257
- MIT
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
- **Built with ❤️ using NitroStack + Duffel API**
262
-
263
- Need help? Check [Duffel Support](https://duffel.com/docs) or [NitroStack Docs](https://nitrostack.ai/docs)
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