spaps 0.1.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,6 +12,8 @@ npx spaps local
12
12
 
13
13
  That's it! Your local SPAPS server is running at http://localhost:3300 šŸš€
14
14
 
15
+ ✨ **New in v0.2.0**: The `spaps local` command now starts a real server!
16
+
15
17
  ## Installation
16
18
 
17
19
  ```bash
@@ -28,10 +30,17 @@ Start a local SPAPS development server with:
28
30
  - Auto-authentication enabled
29
31
  - Test users (user/admin/premium)
30
32
  - CORS configured for any frontend
33
+ - Mock Stripe endpoints
34
+ - Full auth flow support
31
35
 
32
36
  ```bash
33
37
  spaps local
34
38
  # Server running at http://localhost:3300
39
+ # Documentation at http://localhost:3300/docs
40
+
41
+ # Options:
42
+ spaps local --port 3301 # Use different port
43
+ spaps local --open # Open browser automatically
35
44
  ```
36
45
 
37
46
  ### `spaps init`
package/bin/spaps.js CHANGED
@@ -23,41 +23,116 @@ ${chalk.yellow('šŸ  SPAPS')} - Sweet Potato Authentication & Payment Service
23
23
  program
24
24
  .name('spaps')
25
25
  .description('CLI for Sweet Potato Authentication & Payment Service')
26
- .version(version);
26
+ .version(version)
27
+ .option('--json', 'Output in JSON format for machine parsing');
27
28
 
28
29
  // Local command - Start local development server
29
30
  program
30
31
  .command('local')
31
32
  .description('Start local SPAPS server (no API keys required!)')
32
33
  .option('-p, --port <port>', 'Port to run on', '3300')
33
- .action((options) => {
34
- console.log(logo);
35
- console.log(chalk.green('šŸš€ Starting SPAPS local development server...'));
36
- console.log(chalk.dim(` Port: ${options.port}`));
37
- console.log(chalk.dim(' Mode: Local Development (auto-auth enabled)'));
38
- console.log();
34
+ .option('-o, --open', 'Open browser automatically', false)
35
+ .option('--json', 'Output in JSON format')
36
+ .action(async (options, command) => {
37
+ const isJson = options.json || command.parent.opts().json;
39
38
 
40
- // For now, just show instructions
41
- // TODO: Actually spawn the server process
42
- console.log(chalk.yellow('šŸ“¦ Local mode coming soon!'));
43
- console.log();
44
- console.log('For now, run SPAPS locally with:');
45
- console.log(chalk.cyan(' git clone https://github.com/yourusername/sweet-potato'));
46
- console.log(chalk.cyan(' cd sweet-potato'));
47
- console.log(chalk.cyan(' npm install'));
48
- console.log(chalk.cyan(' npm run dev'));
49
- console.log();
50
- console.log('Full CLI functionality coming in v0.2.0! šŸš€');
39
+ if (!isJson) {
40
+ console.log(logo);
41
+ }
42
+
43
+ try {
44
+ // Import and start the local server
45
+ const LocalServer = require('../src/local-server.js');
46
+ const server = new LocalServer({ port: options.port, json: isJson });
47
+
48
+ if (isJson) {
49
+ // For JSON output, start server and return immediately
50
+ await server.start();
51
+ console.log(JSON.stringify({
52
+ success: true,
53
+ command: 'local',
54
+ server: {
55
+ url: `http://localhost:${options.port}`,
56
+ docs: `http://localhost:${options.port}/docs`,
57
+ mode: 'local-development',
58
+ port: parseInt(options.port),
59
+ features: {
60
+ autoAuth: true,
61
+ corsEnabled: true,
62
+ testUsers: ['user', 'admin', 'premium'],
63
+ apiKeyRequired: false
64
+ }
65
+ }
66
+ }));
67
+ } else {
68
+ await server.start();
69
+
70
+ // Open browser if requested
71
+ if (options.open) {
72
+ const { exec } = require('child_process');
73
+ const url = `http://localhost:${options.port}/docs`;
74
+ const start = process.platform === 'darwin' ? 'open' :
75
+ process.platform === 'win32' ? 'start' : 'xdg-open';
76
+ exec(`${start} ${url}`);
77
+ }
78
+ }
79
+
80
+ // Keep process running
81
+ process.on('SIGINT', () => {
82
+ if (!isJson) {
83
+ console.log(chalk.yellow('\nšŸ‘‹ Shutting down SPAPS local server...'));
84
+ }
85
+ process.exit(0);
86
+ });
87
+
88
+ } catch (error) {
89
+ if (isJson) {
90
+ console.log(JSON.stringify({
91
+ success: false,
92
+ command: 'local',
93
+ error: {
94
+ message: error.message,
95
+ code: error.code,
96
+ suggestion: error.code === 'EADDRINUSE' ?
97
+ 'Try a different port with --port option' :
98
+ 'Check error message for details'
99
+ }
100
+ }));
101
+ } else {
102
+ console.error(chalk.red('āŒ Failed to start server:'), error.message);
103
+
104
+ if (error.code === 'MODULE_NOT_FOUND') {
105
+ console.log(chalk.yellow('\nšŸ’” Installing dependencies...'));
106
+ const { execSync } = require('child_process');
107
+ try {
108
+ execSync('npm install express cors', {
109
+ cwd: path.join(__dirname, '..'),
110
+ stdio: 'inherit'
111
+ });
112
+ console.log(chalk.green('āœ… Dependencies installed! Please run the command again.'));
113
+ } catch (installError) {
114
+ console.error(chalk.red('Failed to install dependencies. Please run: npm install express cors'));
115
+ }
116
+ }
117
+ }
118
+
119
+ process.exit(1);
120
+ }
51
121
  });
52
122
 
53
123
  // Init command - Initialize SPAPS in existing project
54
124
  program
55
125
  .command('init')
56
126
  .description('Initialize SPAPS in your project')
57
- .action(() => {
58
- console.log(logo);
59
- console.log(chalk.green('šŸ”§ Initializing SPAPS...'));
60
- console.log();
127
+ .option('--json', 'Output in JSON format')
128
+ .action((options, command) => {
129
+ const isJson = options.json || command.parent.opts().json;
130
+
131
+ if (!isJson) {
132
+ console.log(logo);
133
+ console.log(chalk.green('šŸ”§ Initializing SPAPS...'));
134
+ console.log();
135
+ }
61
136
 
62
137
  // Create minimal .env.local
63
138
  const envContent = `# SPAPS Local Development Configuration
@@ -70,20 +145,43 @@ NODE_ENV=development
70
145
  # SPAPS_API_KEY=your-api-key-here
71
146
  `;
72
147
 
148
+ const result = {
149
+ success: true,
150
+ command: 'init',
151
+ files_created: [],
152
+ files_skipped: [],
153
+ next_steps: [
154
+ 'npx spaps local',
155
+ 'npm install @spaps/sdk',
156
+ 'Start coding!'
157
+ ]
158
+ };
159
+
73
160
  if (!fs.existsSync('.env.local')) {
74
161
  fs.writeFileSync('.env.local', envContent);
75
- console.log(chalk.green('āœ… Created .env.local'));
162
+ result.files_created.push('.env.local');
163
+ if (!isJson) {
164
+ console.log(chalk.green('āœ… Created .env.local'));
165
+ }
76
166
  } else {
77
- console.log(chalk.yellow('āš ļø .env.local already exists'));
167
+ result.files_skipped.push('.env.local');
168
+ result.message = '.env.local already exists';
169
+ if (!isJson) {
170
+ console.log(chalk.yellow('āš ļø .env.local already exists'));
171
+ }
78
172
  }
79
173
 
80
- console.log();
81
- console.log(chalk.green('✨ SPAPS initialized!'));
82
- console.log();
83
- console.log('Next steps:');
84
- console.log(chalk.cyan(' 1. Run: npx spaps local'));
85
- console.log(chalk.cyan(' 2. Install SDK: npm install @spaps/sdk'));
86
- console.log(chalk.cyan(' 3. Start coding!'));
174
+ if (isJson) {
175
+ console.log(JSON.stringify(result));
176
+ } else {
177
+ console.log();
178
+ console.log(chalk.green('✨ SPAPS initialized!'));
179
+ console.log();
180
+ console.log('Next steps:');
181
+ console.log(chalk.cyan(' 1. Run: npx spaps local'));
182
+ console.log(chalk.cyan(' 2. Install SDK: npm install @spaps/sdk'));
183
+ console.log(chalk.cyan(' 3. Start coding!'));
184
+ }
87
185
  });
88
186
 
89
187
  // Create command (placeholder)
package/client.js ADDED
@@ -0,0 +1,102 @@
1
+ /**
2
+ * SPAPS Client - Re-export from spaps-sdk
3
+ * This allows users to import from 'spaps/client'
4
+ */
5
+
6
+ try {
7
+ // Try to load spaps-sdk if it's installed
8
+ module.exports = require('spaps-sdk');
9
+ } catch (error) {
10
+ // Fallback to a simple client implementation for local development
11
+ const axios = require('axios');
12
+
13
+ class SPAPSClient {
14
+ constructor(config = {}) {
15
+ const apiUrl = config.apiUrl || process.env.SPAPS_API_URL || 'http://localhost:3300';
16
+ this.isLocalMode = apiUrl.includes('localhost') || apiUrl.includes('127.0.0.1');
17
+
18
+ this.client = axios.create({
19
+ baseURL: apiUrl,
20
+ timeout: config.timeout || 10000,
21
+ headers: {
22
+ 'Content-Type': 'application/json',
23
+ ...(config.apiKey && { 'X-API-Key': config.apiKey })
24
+ }
25
+ });
26
+
27
+ this.accessToken = null;
28
+ this.refreshToken = null;
29
+ }
30
+
31
+ async login(email, password) {
32
+ const response = await this.client.post('/api/auth/login', { email, password });
33
+ this.accessToken = response.data.access_token;
34
+ this.refreshToken = response.data.refresh_token;
35
+ return response;
36
+ }
37
+
38
+ async register(email, password) {
39
+ const response = await this.client.post('/api/auth/register', { email, password });
40
+ this.accessToken = response.data.access_token;
41
+ this.refreshToken = response.data.refresh_token;
42
+ return response;
43
+ }
44
+
45
+ async walletSignIn(walletAddress, signature, message, chainType = 'solana') {
46
+ const response = await this.client.post('/api/auth/wallet-sign-in', {
47
+ wallet_address: walletAddress,
48
+ signature,
49
+ message,
50
+ chain_type: chainType
51
+ });
52
+ this.accessToken = response.data.access_token;
53
+ this.refreshToken = response.data.refresh_token;
54
+ return response;
55
+ }
56
+
57
+ async getUser() {
58
+ return this.client.get('/api/auth/user', {
59
+ headers: { Authorization: `Bearer ${this.accessToken}` }
60
+ });
61
+ }
62
+
63
+ async createCheckoutSession(priceId, successUrl, cancelUrl) {
64
+ return this.client.post('/api/stripe/create-checkout-session',
65
+ { price_id: priceId, success_url: successUrl, cancel_url: cancelUrl },
66
+ { headers: { Authorization: `Bearer ${this.accessToken}` } }
67
+ );
68
+ }
69
+
70
+ async getSubscription() {
71
+ return this.client.get('/api/stripe/subscription', {
72
+ headers: { Authorization: `Bearer ${this.accessToken}` }
73
+ });
74
+ }
75
+
76
+ async getUsageBalance() {
77
+ return this.client.get('/api/usage/balance', {
78
+ headers: { Authorization: `Bearer ${this.accessToken}` }
79
+ });
80
+ }
81
+
82
+ isAuthenticated() {
83
+ return !!this.accessToken;
84
+ }
85
+
86
+ getAccessToken() {
87
+ return this.accessToken;
88
+ }
89
+
90
+ setAccessToken(token) {
91
+ this.accessToken = token;
92
+ }
93
+
94
+ isLocalMode() {
95
+ return this.isLocalMode;
96
+ }
97
+ }
98
+
99
+ module.exports = SPAPSClient;
100
+ module.exports.SPAPSClient = SPAPSClient;
101
+ module.exports.default = SPAPSClient;
102
+ }
package/package.json CHANGED
@@ -1,11 +1,15 @@
1
1
  {
2
2
  "name": "spaps",
3
- "version": "0.1.0",
3
+ "version": "0.2.2",
4
4
  "description": "Sweet Potato Authentication & Payment Service CLI - Zero-config local development and project scaffolding",
5
5
  "main": "bin/spaps.js",
6
6
  "bin": {
7
7
  "spaps": "./bin/spaps.js"
8
8
  },
9
+ "exports": {
10
+ ".": "./bin/spaps.js",
11
+ "./client": "./client.js"
12
+ },
9
13
  "scripts": {
10
14
  "test": "echo \"No tests yet\""
11
15
  },
@@ -37,13 +41,17 @@
37
41
  "commander": "^11.1.0",
38
42
  "ora": "^5.4.1",
39
43
  "prompts": "^2.4.2",
40
- "axios": "^1.6.0"
44
+ "axios": "^1.6.0",
45
+ "express": "^4.18.2",
46
+ "cors": "^2.8.5"
41
47
  },
42
48
  "engines": {
43
49
  "node": ">=16.0.0"
44
50
  },
45
51
  "files": [
46
52
  "bin",
53
+ "src",
54
+ "client.js",
47
55
  "README.md"
48
56
  ]
49
57
  }
@@ -0,0 +1,323 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * SPAPS Local Development Server
5
+ * Minimal, zero-config server for local development
6
+ */
7
+
8
+ const express = require('express');
9
+ const cors = require('cors');
10
+ const chalk = require('chalk');
11
+
12
+ class LocalServer {
13
+ constructor(options = {}) {
14
+ this.port = options.port || process.env.PORT || 3300;
15
+ this.json = options.json || false;
16
+ this.app = express();
17
+ this.setupMiddleware();
18
+ this.setupRoutes();
19
+ }
20
+
21
+ setupMiddleware() {
22
+ // CORS - allow everything in local mode
23
+ this.app.use(cors({
24
+ origin: true,
25
+ credentials: true,
26
+ methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
27
+ allowedHeaders: ['Content-Type', 'Authorization', 'X-API-Key', 'X-Test-User'],
28
+ }));
29
+
30
+ // Body parsing
31
+ this.app.use(express.json());
32
+ this.app.use(express.urlencoded({ extended: true }));
33
+
34
+ // Local mode indicator
35
+ this.app.use((req, res, next) => {
36
+ res.setHeader('X-SPAPS-Mode', 'local-development');
37
+
38
+ // Auto-auth in local mode
39
+ if (!req.headers.authorization && !req.headers['x-api-key']) {
40
+ req.headers['x-api-key'] = 'local-dev-key';
41
+ req.user = {
42
+ id: 'local-user-123',
43
+ email: 'dev@localhost',
44
+ role: req.query._user || req.headers['x-test-user'] || 'user'
45
+ };
46
+ }
47
+
48
+ // Log requests (unless in JSON mode)
49
+ if (!this.json) {
50
+ console.log(chalk.dim(`${req.method} ${req.path}`));
51
+ }
52
+ next();
53
+ });
54
+ }
55
+
56
+ setupRoutes() {
57
+ // Health check
58
+ this.app.get('/health', (req, res) => {
59
+ res.json({
60
+ status: 'healthy',
61
+ mode: 'local-development',
62
+ version: '0.2.0',
63
+ timestamp: new Date().toISOString()
64
+ });
65
+ });
66
+
67
+ // Local mode status
68
+ this.app.get('/health/local-mode', (req, res) => {
69
+ res.json({
70
+ enabled: true,
71
+ environment: 'local-development',
72
+ features: {
73
+ autoAuth: true,
74
+ corsEnabled: true,
75
+ testUsers: ['user', 'admin', 'premium'],
76
+ apiKeyRequired: false
77
+ }
78
+ });
79
+ });
80
+
81
+ // Mock authentication endpoints
82
+ this.app.post('/api/auth/login', (req, res) => {
83
+ const { email, password } = req.body;
84
+ res.json({
85
+ access_token: 'local-jwt-token-' + Date.now(),
86
+ refresh_token: 'local-refresh-token-' + Date.now(),
87
+ user: {
88
+ id: 'local-user-123',
89
+ email: email || 'dev@localhost',
90
+ role: 'user'
91
+ }
92
+ });
93
+ });
94
+
95
+ this.app.post('/api/auth/register', (req, res) => {
96
+ const { email, password } = req.body;
97
+ res.json({
98
+ access_token: 'local-jwt-token-' + Date.now(),
99
+ refresh_token: 'local-refresh-token-' + Date.now(),
100
+ user: {
101
+ id: 'local-user-' + Date.now(),
102
+ email: email || 'dev@localhost',
103
+ role: 'user'
104
+ }
105
+ });
106
+ });
107
+
108
+ this.app.post('/api/auth/wallet-sign-in', (req, res) => {
109
+ const { wallet_address, chain_type } = req.body;
110
+ res.json({
111
+ access_token: 'local-jwt-token-' + Date.now(),
112
+ refresh_token: 'local-refresh-token-' + Date.now(),
113
+ user: {
114
+ id: 'local-wallet-user-123',
115
+ wallet_address,
116
+ chain_type,
117
+ role: 'user'
118
+ }
119
+ });
120
+ });
121
+
122
+ this.app.post('/api/auth/refresh', (req, res) => {
123
+ res.json({
124
+ access_token: 'local-jwt-token-refreshed-' + Date.now(),
125
+ refresh_token: 'local-refresh-token-refreshed-' + Date.now()
126
+ });
127
+ });
128
+
129
+ this.app.post('/api/auth/logout', (req, res) => {
130
+ res.json({ success: true, message: 'Logged out successfully' });
131
+ });
132
+
133
+ this.app.get('/api/auth/user', (req, res) => {
134
+ res.json({
135
+ id: req.user?.id || 'local-user-123',
136
+ email: req.user?.email || 'dev@localhost',
137
+ role: req.user?.role || 'user',
138
+ created_at: new Date().toISOString()
139
+ });
140
+ });
141
+
142
+ // Mock Stripe endpoints
143
+ this.app.post('/api/stripe/create-checkout-session', (req, res) => {
144
+ res.json({
145
+ sessionId: 'cs_test_local_' + Date.now(),
146
+ url: 'https://checkout.stripe.com/pay/cs_test_local'
147
+ });
148
+ });
149
+
150
+ this.app.get('/api/stripe/subscription', (req, res) => {
151
+ res.json({
152
+ id: 'sub_local_123',
153
+ status: 'active',
154
+ plan: 'premium',
155
+ current_period_end: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString()
156
+ });
157
+ });
158
+
159
+ // Mock usage endpoints
160
+ this.app.get('/api/usage/balance', (req, res) => {
161
+ res.json({
162
+ balance: 1000,
163
+ currency: 'credits',
164
+ updated_at: new Date().toISOString()
165
+ });
166
+ });
167
+
168
+ // Documentation endpoint
169
+ this.app.get('/docs', (req, res) => {
170
+ res.send(`
171
+ <!DOCTYPE html>
172
+ <html>
173
+ <head>
174
+ <title>SPAPS Local Mode</title>
175
+ <style>
176
+ body {
177
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
178
+ max-width: 800px;
179
+ margin: 50px auto;
180
+ padding: 20px;
181
+ line-height: 1.6;
182
+ }
183
+ h1 { color: #ff6b6b; }
184
+ code {
185
+ background: #f4f4f4;
186
+ padding: 2px 6px;
187
+ border-radius: 3px;
188
+ }
189
+ pre {
190
+ background: #f4f4f4;
191
+ padding: 15px;
192
+ border-radius: 5px;
193
+ overflow-x: auto;
194
+ }
195
+ .endpoint {
196
+ margin: 20px 0;
197
+ padding: 15px;
198
+ border-left: 3px solid #ff6b6b;
199
+ background: #fff9f9;
200
+ }
201
+ </style>
202
+ </head>
203
+ <body>
204
+ <h1>šŸ  SPAPS Local Development Mode</h1>
205
+ <p>You're running in <strong>local development mode</strong>. No API keys required!</p>
206
+
207
+ <h2>Quick Start</h2>
208
+ <pre>
209
+ // No configuration needed!
210
+ const response = await fetch('http://localhost:${this.port}/api/auth/login', {
211
+ method: 'POST',
212
+ headers: { 'Content-Type': 'application/json' },
213
+ body: JSON.stringify({
214
+ email: 'test@example.com',
215
+ password: 'password'
216
+ })
217
+ });
218
+ const { access_token, user } = await response.json();
219
+ </pre>
220
+
221
+ <h2>Available Endpoints</h2>
222
+
223
+ <div class="endpoint">
224
+ <strong>GET /health</strong> - Health check
225
+ </div>
226
+
227
+ <div class="endpoint">
228
+ <strong>POST /api/auth/login</strong> - Email/password login
229
+ </div>
230
+
231
+ <div class="endpoint">
232
+ <strong>POST /api/auth/register</strong> - Create account
233
+ </div>
234
+
235
+ <div class="endpoint">
236
+ <strong>POST /api/auth/wallet-sign-in</strong> - Wallet authentication
237
+ </div>
238
+
239
+ <div class="endpoint">
240
+ <strong>GET /api/auth/user</strong> - Get current user
241
+ </div>
242
+
243
+ <div class="endpoint">
244
+ <strong>POST /api/stripe/create-checkout-session</strong> - Create payment session
245
+ </div>
246
+
247
+ <div class="endpoint">
248
+ <strong>GET /api/usage/balance</strong> - Check usage balance
249
+ </div>
250
+
251
+ <h2>Test Users</h2>
252
+ <p>Switch between test users by adding <code>?_user=admin</code> or <code>?_user=premium</code> to any request.</p>
253
+
254
+ <h2>Environment</h2>
255
+ <pre>
256
+ Mode: ${process.env.NODE_ENV || 'development'}
257
+ Port: ${this.port}
258
+ Auto-Auth: Enabled
259
+ CORS: Enabled (all origins)
260
+ </pre>
261
+ </body>
262
+ </html>
263
+ `);
264
+ });
265
+
266
+ // Catch-all for unimplemented routes
267
+ this.app.use((req, res) => {
268
+ res.status(404).json({
269
+ error: 'Not found',
270
+ message: `Endpoint ${req.method} ${req.path} not implemented in local mode`,
271
+ suggestion: 'Check /docs for available endpoints'
272
+ });
273
+ });
274
+ }
275
+
276
+ start() {
277
+ return new Promise((resolve, reject) => {
278
+ const server = this.app.listen(this.port, (err) => {
279
+ if (err) {
280
+ reject(err);
281
+ } else {
282
+ if (!this.json) {
283
+ console.log();
284
+ console.log(chalk.yellow('šŸ  SPAPS Local Development Server'));
285
+ console.log(chalk.green(`✨ Running at: http://localhost:${this.port}`));
286
+ console.log(chalk.blue(`šŸ“ Documentation: http://localhost:${this.port}/docs`));
287
+ console.log(chalk.dim(' Press Ctrl+C to stop'));
288
+ console.log();
289
+ }
290
+ resolve(server);
291
+ }
292
+ });
293
+
294
+ // Handle errors
295
+ server.on('error', (err) => {
296
+ if (!this.json) {
297
+ if (err.code === 'EADDRINUSE') {
298
+ console.error(chalk.red(`āŒ Port ${this.port} is already in use`));
299
+ console.log(chalk.yellow('šŸ’” Try: spaps local --port 3301'));
300
+ } else {
301
+ console.error(chalk.red('āŒ Server error:'), err.message);
302
+ }
303
+ }
304
+ reject(err);
305
+ });
306
+ });
307
+ }
308
+ }
309
+
310
+ // Export for use in CLI
311
+ module.exports = LocalServer;
312
+
313
+ // Run directly if called as script
314
+ if (require.main === module) {
315
+ const server = new LocalServer();
316
+ server.start().catch(console.error);
317
+
318
+ // Graceful shutdown
319
+ process.on('SIGINT', () => {
320
+ console.log(chalk.yellow('\nšŸ‘‹ Shutting down...'));
321
+ process.exit(0);
322
+ });
323
+ }