spaps 0.2.4 ā 0.2.6
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/bin/spaps.js +58 -0
- package/package.json +1 -1
- package/src/docs-system.js +806 -0
- package/src/help-system.js +487 -0
package/bin/spaps.js
CHANGED
|
@@ -13,6 +13,8 @@ const { spawn } = require('child_process');
|
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const fs = require('fs');
|
|
15
15
|
const { handleError } = require('../src/error-handler');
|
|
16
|
+
const { showInteractiveHelp, showQuickHelp } = require('../src/help-system');
|
|
17
|
+
const { showInteractiveDocs, showQuickReference, searchDocs } = require('../src/docs-system');
|
|
16
18
|
|
|
17
19
|
const version = require('../package.json').version;
|
|
18
20
|
|
|
@@ -176,6 +178,60 @@ program
|
|
|
176
178
|
console.log(chalk.yellow(`š§ 'spaps types' coming in v0.4.0!`));
|
|
177
179
|
});
|
|
178
180
|
|
|
181
|
+
// Help command - Interactive help system
|
|
182
|
+
program
|
|
183
|
+
.command('help')
|
|
184
|
+
.description('Show help and guides')
|
|
185
|
+
.option('-i, --interactive', 'Interactive help mode')
|
|
186
|
+
.option('-q, --quick', 'Quick reference')
|
|
187
|
+
.action(async (options) => {
|
|
188
|
+
if (options.interactive) {
|
|
189
|
+
await showInteractiveHelp();
|
|
190
|
+
} else if (options.quick) {
|
|
191
|
+
showQuickHelp();
|
|
192
|
+
} else {
|
|
193
|
+
// Default to quick help
|
|
194
|
+
showQuickHelp();
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Docs command - SDK documentation
|
|
199
|
+
program
|
|
200
|
+
.command('docs')
|
|
201
|
+
.description('Browse SDK documentation')
|
|
202
|
+
.option('-i, --interactive', 'Interactive documentation browser')
|
|
203
|
+
.option('-s, --search <query>', 'Search documentation')
|
|
204
|
+
.option('--json', 'Output in JSON format')
|
|
205
|
+
.action(async (options) => {
|
|
206
|
+
if (options.search) {
|
|
207
|
+
const results = searchDocs(options.search);
|
|
208
|
+
|
|
209
|
+
if (options.json) {
|
|
210
|
+
console.log(JSON.stringify({ results }, null, 2));
|
|
211
|
+
} else {
|
|
212
|
+
console.log(chalk.yellow(`\nš Search results for "${options.search}":\n`));
|
|
213
|
+
|
|
214
|
+
if (results.length === 0) {
|
|
215
|
+
console.log(chalk.gray(' No results found'));
|
|
216
|
+
} else {
|
|
217
|
+
results.forEach((result, i) => {
|
|
218
|
+
console.log(chalk.green(` ${i + 1}. ${result.title}`));
|
|
219
|
+
console.log(chalk.gray(` ${result.preview}`));
|
|
220
|
+
console.log();
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
console.log(chalk.blue(' Run: npx spaps docs --interactive'));
|
|
225
|
+
console.log(chalk.blue(' to browse full documentation\n'));
|
|
226
|
+
}
|
|
227
|
+
} else if (options.interactive) {
|
|
228
|
+
await showInteractiveDocs();
|
|
229
|
+
} else {
|
|
230
|
+
// Default to quick reference
|
|
231
|
+
showQuickReference();
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
179
235
|
// Help command enhancement
|
|
180
236
|
program.on('--help', () => {
|
|
181
237
|
console.log();
|
|
@@ -192,6 +248,8 @@ program.on('--help', () => {
|
|
|
192
248
|
if (!process.argv.slice(2).length) {
|
|
193
249
|
console.log(logo);
|
|
194
250
|
program.outputHelp();
|
|
251
|
+
console.log();
|
|
252
|
+
console.log(chalk.yellow('š” Try: npx spaps help --interactive'));
|
|
195
253
|
}
|
|
196
254
|
|
|
197
255
|
program.parse(process.argv);
|
package/package.json
CHANGED
|
@@ -0,0 +1,806 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPAPS CLI Documentation System
|
|
3
|
+
* Comprehensive SDK and API documentation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
const prompts = require('prompts');
|
|
8
|
+
|
|
9
|
+
const SDK_DOCS = {
|
|
10
|
+
quickstart: {
|
|
11
|
+
title: 'SDK Quick Start',
|
|
12
|
+
content: `
|
|
13
|
+
${chalk.green('Installation:')}
|
|
14
|
+
npm install spaps-sdk
|
|
15
|
+
# or
|
|
16
|
+
yarn add spaps-sdk
|
|
17
|
+
|
|
18
|
+
${chalk.green('Basic Usage:')}
|
|
19
|
+
${chalk.gray('// ES6 Import')}
|
|
20
|
+
import { SPAPSClient } from 'spaps-sdk'
|
|
21
|
+
|
|
22
|
+
${chalk.gray('// CommonJS')}
|
|
23
|
+
const { SPAPSClient } = require('spaps-sdk')
|
|
24
|
+
|
|
25
|
+
${chalk.gray('// Create client (auto-detects local mode)')}
|
|
26
|
+
const spaps = new SPAPSClient()
|
|
27
|
+
|
|
28
|
+
${chalk.gray('// With custom config')}
|
|
29
|
+
const spaps = new SPAPSClient({
|
|
30
|
+
apiUrl: 'http://localhost:3300',
|
|
31
|
+
apiKey: 'your-api-key', ${chalk.gray('// Not needed for localhost')}
|
|
32
|
+
timeout: 10000
|
|
33
|
+
})
|
|
34
|
+
`
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
authentication: {
|
|
38
|
+
title: 'Authentication Methods',
|
|
39
|
+
content: `
|
|
40
|
+
${chalk.green('Email/Password Authentication:')}
|
|
41
|
+
${chalk.gray('// Register new user')}
|
|
42
|
+
const { data } = await spaps.register(email, password)
|
|
43
|
+
console.log('User:', data.user)
|
|
44
|
+
console.log('Token:', data.access_token)
|
|
45
|
+
|
|
46
|
+
${chalk.gray('// Login existing user')}
|
|
47
|
+
const { data } = await spaps.login(email, password)
|
|
48
|
+
|
|
49
|
+
${chalk.gray('// Check authentication status')}
|
|
50
|
+
if (spaps.isAuthenticated()) {
|
|
51
|
+
const user = await spaps.getUser()
|
|
52
|
+
console.log('Current user:', user.data)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
${chalk.gray('// Logout')}
|
|
56
|
+
await spaps.logout()
|
|
57
|
+
|
|
58
|
+
${chalk.green('Wallet Authentication:')}
|
|
59
|
+
${chalk.gray('// Solana wallet')}
|
|
60
|
+
await spaps.walletSignIn(
|
|
61
|
+
walletAddress,
|
|
62
|
+
signature,
|
|
63
|
+
message,
|
|
64
|
+
'solana'
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
${chalk.gray('// Ethereum wallet')}
|
|
68
|
+
await spaps.walletSignIn(
|
|
69
|
+
walletAddress,
|
|
70
|
+
signature,
|
|
71
|
+
message,
|
|
72
|
+
'ethereum'
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
${chalk.green('Token Management:')}
|
|
76
|
+
${chalk.gray('// Get current token')}
|
|
77
|
+
const token = spaps.getAccessToken()
|
|
78
|
+
|
|
79
|
+
${chalk.gray('// Set token manually')}
|
|
80
|
+
spaps.setAccessToken(token)
|
|
81
|
+
|
|
82
|
+
${chalk.gray('// Refresh token')}
|
|
83
|
+
await spaps.refresh()
|
|
84
|
+
`
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
payments: {
|
|
88
|
+
title: 'Payment Integration',
|
|
89
|
+
content: `
|
|
90
|
+
${chalk.green('Stripe Checkout:')}
|
|
91
|
+
${chalk.gray('// Create checkout session')}
|
|
92
|
+
const session = await spaps.createCheckoutSession(
|
|
93
|
+
'price_123abc', ${chalk.gray('// Stripe price ID')}
|
|
94
|
+
'http://localhost:3000/success', ${chalk.gray('// Success URL')}
|
|
95
|
+
'http://localhost:3000/cancel' ${chalk.gray('// Cancel URL (optional)')}
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
${chalk.gray('// Redirect to Stripe')}
|
|
99
|
+
window.location.href = session.data.url
|
|
100
|
+
|
|
101
|
+
${chalk.green('Subscription Management:')}
|
|
102
|
+
${chalk.gray('// Get current subscription')}
|
|
103
|
+
const subscription = await spaps.getSubscription()
|
|
104
|
+
console.log('Status:', subscription.data.status)
|
|
105
|
+
console.log('Plan:', subscription.data.plan)
|
|
106
|
+
console.log('Renews:', subscription.data.current_period_end)
|
|
107
|
+
|
|
108
|
+
${chalk.gray('// Cancel subscription')}
|
|
109
|
+
await spaps.cancelSubscription()
|
|
110
|
+
|
|
111
|
+
${chalk.green('Usage Tracking:')}
|
|
112
|
+
${chalk.gray('// Check balance')}
|
|
113
|
+
const balance = await spaps.getUsageBalance()
|
|
114
|
+
console.log('Credits:', balance.data.balance)
|
|
115
|
+
|
|
116
|
+
${chalk.gray('// Record usage')}
|
|
117
|
+
await spaps.recordUsage('api-call', 1)
|
|
118
|
+
await spaps.recordUsage('image-generation', 10)
|
|
119
|
+
`
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
config: {
|
|
123
|
+
title: 'Configuration',
|
|
124
|
+
content: `
|
|
125
|
+
${chalk.green('Environment Variables:')}
|
|
126
|
+
${chalk.gray('# .env or .env.local')}
|
|
127
|
+
SPAPS_API_URL=http://localhost:3300
|
|
128
|
+
SPAPS_API_KEY=spaps_live_abc123...
|
|
129
|
+
|
|
130
|
+
${chalk.gray('# Next.js (use NEXT_PUBLIC_ prefix)')}
|
|
131
|
+
NEXT_PUBLIC_SPAPS_API_URL=http://localhost:3300
|
|
132
|
+
NEXT_PUBLIC_SPAPS_API_KEY=spaps_live_abc123...
|
|
133
|
+
|
|
134
|
+
${chalk.green('Configuration Options:')}
|
|
135
|
+
const spaps = new SPAPSClient({
|
|
136
|
+
${chalk.gray('// API endpoint (auto-detected from env)')}
|
|
137
|
+
apiUrl: 'http://localhost:3300',
|
|
138
|
+
|
|
139
|
+
${chalk.gray('// API key (not needed for localhost)')}
|
|
140
|
+
apiKey: 'spaps_live_abc123...',
|
|
141
|
+
|
|
142
|
+
${chalk.gray('// Request timeout in milliseconds')}
|
|
143
|
+
timeout: 10000,
|
|
144
|
+
|
|
145
|
+
${chalk.gray('// Auto-detect local mode (default: true)')}
|
|
146
|
+
autoDetect: true
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
${chalk.green('Local Mode Detection:')}
|
|
150
|
+
${chalk.gray('// The SDK automatically detects local mode when:')}
|
|
151
|
+
- URL contains 'localhost'
|
|
152
|
+
- URL contains '127.0.0.1'
|
|
153
|
+
- No API URL is provided
|
|
154
|
+
|
|
155
|
+
${chalk.gray('// Check if in local mode')}
|
|
156
|
+
if (spaps.isLocalMode()) {
|
|
157
|
+
console.log('Running in local mode - no API key needed!')
|
|
158
|
+
}
|
|
159
|
+
`
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
react: {
|
|
163
|
+
title: 'React Integration',
|
|
164
|
+
content: `
|
|
165
|
+
${chalk.green('React Context Setup:')}
|
|
166
|
+
${chalk.gray('// contexts/SpapsContext.tsx')}
|
|
167
|
+
import { createContext, useContext } from 'react'
|
|
168
|
+
import { SPAPSClient } from 'spaps-sdk'
|
|
169
|
+
|
|
170
|
+
const spaps = new SPAPSClient()
|
|
171
|
+
const SpapsContext = createContext(spaps)
|
|
172
|
+
|
|
173
|
+
export function SpapsProvider({ children }) {
|
|
174
|
+
return (
|
|
175
|
+
<SpapsContext.Provider value={spaps}>
|
|
176
|
+
{children}
|
|
177
|
+
</SpapsContext.Provider>
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export const useSpaps = () => useContext(SpapsContext)
|
|
182
|
+
|
|
183
|
+
${chalk.green('React Hook Example:')}
|
|
184
|
+
${chalk.gray('// hooks/useAuth.ts')}
|
|
185
|
+
import { useState, useEffect } from 'react'
|
|
186
|
+
import { useSpaps } from '../contexts/SpapsContext'
|
|
187
|
+
|
|
188
|
+
export function useAuth() {
|
|
189
|
+
const spaps = useSpaps()
|
|
190
|
+
const [user, setUser] = useState(null)
|
|
191
|
+
const [loading, setLoading] = useState(true)
|
|
192
|
+
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
if (spaps.isAuthenticated()) {
|
|
195
|
+
spaps.getUser()
|
|
196
|
+
.then(res => setUser(res.data))
|
|
197
|
+
.finally(() => setLoading(false))
|
|
198
|
+
} else {
|
|
199
|
+
setLoading(false)
|
|
200
|
+
}
|
|
201
|
+
}, [])
|
|
202
|
+
|
|
203
|
+
return { user, loading, isAuthenticated: !!user }
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
${chalk.green('Component Example:')}
|
|
207
|
+
${chalk.gray('// components/LoginForm.tsx')}
|
|
208
|
+
function LoginForm() {
|
|
209
|
+
const spaps = useSpaps()
|
|
210
|
+
const [email, setEmail] = useState('')
|
|
211
|
+
const [password, setPassword] = useState('')
|
|
212
|
+
|
|
213
|
+
const handleSubmit = async (e) => {
|
|
214
|
+
e.preventDefault()
|
|
215
|
+
try {
|
|
216
|
+
await spaps.login(email, password)
|
|
217
|
+
// Redirect or update state
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error('Login failed:', error)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<form onSubmit={handleSubmit}>
|
|
225
|
+
<input
|
|
226
|
+
type="email"
|
|
227
|
+
value={email}
|
|
228
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
229
|
+
/>
|
|
230
|
+
<input
|
|
231
|
+
type="password"
|
|
232
|
+
value={password}
|
|
233
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
234
|
+
/>
|
|
235
|
+
<button type="submit">Login</button>
|
|
236
|
+
</form>
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
`
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
nextjs: {
|
|
243
|
+
title: 'Next.js Integration',
|
|
244
|
+
content: `
|
|
245
|
+
${chalk.green('App Router Setup:')}
|
|
246
|
+
${chalk.gray('// app/providers.tsx')}
|
|
247
|
+
'use client'
|
|
248
|
+
|
|
249
|
+
import { SPAPSClient } from 'spaps-sdk'
|
|
250
|
+
import { createContext, useContext } from 'react'
|
|
251
|
+
|
|
252
|
+
const SpapsContext = createContext<SPAPSClient | null>(null)
|
|
253
|
+
|
|
254
|
+
export function Providers({ children }) {
|
|
255
|
+
const spaps = new SPAPSClient({
|
|
256
|
+
apiUrl: process.env.NEXT_PUBLIC_SPAPS_API_URL
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<SpapsContext.Provider value={spaps}>
|
|
261
|
+
{children}
|
|
262
|
+
</SpapsContext.Provider>
|
|
263
|
+
)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export const useSpaps = () => {
|
|
267
|
+
const context = useContext(SpapsContext)
|
|
268
|
+
if (!context) throw new Error('Missing SpapsProvider')
|
|
269
|
+
return context
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
${chalk.green('Server Actions:')}
|
|
273
|
+
${chalk.gray('// app/actions/auth.ts')}
|
|
274
|
+
'use server'
|
|
275
|
+
|
|
276
|
+
import { SPAPSClient } from 'spaps-sdk'
|
|
277
|
+
import { cookies } from 'next/headers'
|
|
278
|
+
|
|
279
|
+
const spaps = new SPAPSClient({
|
|
280
|
+
apiUrl: process.env.SPAPS_API_URL,
|
|
281
|
+
apiKey: process.env.SPAPS_API_KEY
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
export async function loginAction(email: string, password: string) {
|
|
285
|
+
const { data } = await spaps.login(email, password)
|
|
286
|
+
|
|
287
|
+
// Store token in cookie
|
|
288
|
+
cookies().set('spaps_token', data.access_token, {
|
|
289
|
+
httpOnly: true,
|
|
290
|
+
secure: process.env.NODE_ENV === 'production',
|
|
291
|
+
sameSite: 'lax',
|
|
292
|
+
maxAge: 60 * 60 * 24 * 7 // 1 week
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
return { success: true, user: data.user }
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
${chalk.green('Middleware Protection:')}
|
|
299
|
+
${chalk.gray('// middleware.ts')}
|
|
300
|
+
import { NextResponse } from 'next/server'
|
|
301
|
+
import type { NextRequest } from 'next/server'
|
|
302
|
+
|
|
303
|
+
export function middleware(request: NextRequest) {
|
|
304
|
+
const token = request.cookies.get('spaps_token')
|
|
305
|
+
|
|
306
|
+
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
|
|
307
|
+
return NextResponse.redirect(new URL('/login', request.url))
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return NextResponse.next()
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export const config = {
|
|
314
|
+
matcher: '/dashboard/:path*'
|
|
315
|
+
}
|
|
316
|
+
`
|
|
317
|
+
},
|
|
318
|
+
|
|
319
|
+
nodejs: {
|
|
320
|
+
title: 'Node.js/Express Integration',
|
|
321
|
+
content: `
|
|
322
|
+
${chalk.green('Express Middleware:')}
|
|
323
|
+
${chalk.gray('// server.js')}
|
|
324
|
+
const express = require('express')
|
|
325
|
+
const { SPAPSClient } = require('spaps-sdk')
|
|
326
|
+
|
|
327
|
+
const app = express()
|
|
328
|
+
const spaps = new SPAPSClient({
|
|
329
|
+
apiUrl: process.env.SPAPS_API_URL,
|
|
330
|
+
apiKey: process.env.SPAPS_API_KEY
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
${chalk.gray('// Add SPAPS to request')}
|
|
334
|
+
app.use((req, res, next) => {
|
|
335
|
+
req.spaps = spaps
|
|
336
|
+
next()
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
${chalk.gray('// Auth middleware')}
|
|
340
|
+
async function requireAuth(req, res, next) {
|
|
341
|
+
const token = req.headers.authorization?.split(' ')[1]
|
|
342
|
+
|
|
343
|
+
if (!token) {
|
|
344
|
+
return res.status(401).json({ error: 'No token provided' })
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
try {
|
|
348
|
+
req.spaps.setAccessToken(token)
|
|
349
|
+
const { data } = await req.spaps.getUser()
|
|
350
|
+
req.user = data
|
|
351
|
+
next()
|
|
352
|
+
} catch (error) {
|
|
353
|
+
res.status(401).json({ error: 'Invalid token' })
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
${chalk.green('Route Examples:')}
|
|
358
|
+
${chalk.gray('// Login endpoint')}
|
|
359
|
+
app.post('/api/login', async (req, res) => {
|
|
360
|
+
const { email, password } = req.body
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
const { data } = await req.spaps.login(email, password)
|
|
364
|
+
res.json(data)
|
|
365
|
+
} catch (error) {
|
|
366
|
+
res.status(401).json({ error: 'Invalid credentials' })
|
|
367
|
+
}
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
${chalk.gray('// Protected route')}
|
|
371
|
+
app.get('/api/profile', requireAuth, (req, res) => {
|
|
372
|
+
res.json(req.user)
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
${chalk.gray('// Stripe webhook')}
|
|
376
|
+
app.post('/api/webhooks/stripe', async (req, res) => {
|
|
377
|
+
// Verify webhook signature
|
|
378
|
+
// Update user subscription status
|
|
379
|
+
res.json({ received: true })
|
|
380
|
+
})
|
|
381
|
+
`
|
|
382
|
+
},
|
|
383
|
+
|
|
384
|
+
errors: {
|
|
385
|
+
title: 'Error Handling',
|
|
386
|
+
content: `
|
|
387
|
+
${chalk.green('Error Types:')}
|
|
388
|
+
try {
|
|
389
|
+
await spaps.login(email, password)
|
|
390
|
+
} catch (error) {
|
|
391
|
+
if (error.response) {
|
|
392
|
+
${chalk.gray('// Server responded with error')}
|
|
393
|
+
console.log('Status:', error.response.status)
|
|
394
|
+
console.log('Data:', error.response.data)
|
|
395
|
+
|
|
396
|
+
switch (error.response.status) {
|
|
397
|
+
case 401:
|
|
398
|
+
console.error('Invalid credentials')
|
|
399
|
+
break
|
|
400
|
+
case 429:
|
|
401
|
+
console.error('Rate limited - try again later')
|
|
402
|
+
break
|
|
403
|
+
case 500:
|
|
404
|
+
console.error('Server error')
|
|
405
|
+
break
|
|
406
|
+
}
|
|
407
|
+
} else if (error.request) {
|
|
408
|
+
${chalk.gray('// Request made but no response')}
|
|
409
|
+
console.error('Network error - server unreachable')
|
|
410
|
+
} else {
|
|
411
|
+
${chalk.gray('// Error in request setup')}
|
|
412
|
+
console.error('Request error:', error.message)
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
${chalk.green('Custom Error Handling:')}
|
|
417
|
+
${chalk.gray('// Global error handler')}
|
|
418
|
+
spaps.client.interceptors.response.use(
|
|
419
|
+
response => response,
|
|
420
|
+
error => {
|
|
421
|
+
if (error.response?.status === 401) {
|
|
422
|
+
// Token expired - redirect to login
|
|
423
|
+
window.location.href = '/login'
|
|
424
|
+
}
|
|
425
|
+
return Promise.reject(error)
|
|
426
|
+
}
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
${chalk.green('Retry Logic:')}
|
|
430
|
+
${chalk.gray('// Retry failed requests')}
|
|
431
|
+
async function retryRequest(fn, retries = 3) {
|
|
432
|
+
try {
|
|
433
|
+
return await fn()
|
|
434
|
+
} catch (error) {
|
|
435
|
+
if (retries > 0 && error.response?.status >= 500) {
|
|
436
|
+
await new Promise(r => setTimeout(r, 1000))
|
|
437
|
+
return retryRequest(fn, retries - 1)
|
|
438
|
+
}
|
|
439
|
+
throw error
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
${chalk.gray('// Usage')}
|
|
444
|
+
const user = await retryRequest(() => spaps.getUser())
|
|
445
|
+
`
|
|
446
|
+
},
|
|
447
|
+
|
|
448
|
+
typescript: {
|
|
449
|
+
title: 'TypeScript Support',
|
|
450
|
+
content: `
|
|
451
|
+
${chalk.green('Type Definitions:')}
|
|
452
|
+
import {
|
|
453
|
+
SPAPSClient,
|
|
454
|
+
SPAPSConfig,
|
|
455
|
+
AuthResponse,
|
|
456
|
+
User,
|
|
457
|
+
CheckoutSession,
|
|
458
|
+
Subscription,
|
|
459
|
+
UsageBalance
|
|
460
|
+
} from 'spaps-sdk'
|
|
461
|
+
|
|
462
|
+
${chalk.green('Interface Examples:')}
|
|
463
|
+
${chalk.gray('// User type')}
|
|
464
|
+
interface User {
|
|
465
|
+
id: string
|
|
466
|
+
email?: string
|
|
467
|
+
wallet_address?: string
|
|
468
|
+
chain_type?: string
|
|
469
|
+
role: string
|
|
470
|
+
created_at?: string
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
${chalk.gray('// Auth response')}
|
|
474
|
+
interface AuthResponse {
|
|
475
|
+
access_token: string
|
|
476
|
+
refresh_token: string
|
|
477
|
+
user: User
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
${chalk.gray('// Config options')}
|
|
481
|
+
interface SPAPSConfig {
|
|
482
|
+
apiUrl?: string
|
|
483
|
+
apiKey?: string
|
|
484
|
+
autoDetect?: boolean
|
|
485
|
+
timeout?: number
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
${chalk.green('Type-Safe Usage:')}
|
|
489
|
+
${chalk.gray('// Typed client')}
|
|
490
|
+
const spaps: SPAPSClient = new SPAPSClient({
|
|
491
|
+
apiUrl: 'http://localhost:3300'
|
|
492
|
+
})
|
|
493
|
+
|
|
494
|
+
${chalk.gray('// Typed responses')}
|
|
495
|
+
const login = async (email: string, password: string): Promise<User> => {
|
|
496
|
+
const { data }: { data: AuthResponse } = await spaps.login(email, password)
|
|
497
|
+
return data.user
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
${chalk.gray('// Generic wrapper')}
|
|
501
|
+
async function apiCall<T>(
|
|
502
|
+
fn: () => Promise<{ data: T }>
|
|
503
|
+
): Promise<T> {
|
|
504
|
+
try {
|
|
505
|
+
const { data } = await fn()
|
|
506
|
+
return data
|
|
507
|
+
} catch (error) {
|
|
508
|
+
console.error('API call failed:', error)
|
|
509
|
+
throw error
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
${chalk.gray('// Usage')}
|
|
514
|
+
const user = await apiCall<User>(() => spaps.getUser())
|
|
515
|
+
`
|
|
516
|
+
},
|
|
517
|
+
|
|
518
|
+
testing: {
|
|
519
|
+
title: 'Testing',
|
|
520
|
+
content: `
|
|
521
|
+
${chalk.green('Unit Testing with Jest:')}
|
|
522
|
+
${chalk.gray('// __tests__/auth.test.js')}
|
|
523
|
+
import { SPAPSClient } from 'spaps-sdk'
|
|
524
|
+
|
|
525
|
+
describe('Authentication', () => {
|
|
526
|
+
let spaps
|
|
527
|
+
|
|
528
|
+
beforeEach(() => {
|
|
529
|
+
spaps = new SPAPSClient({
|
|
530
|
+
apiUrl: 'http://localhost:3300'
|
|
531
|
+
})
|
|
532
|
+
})
|
|
533
|
+
|
|
534
|
+
test('login returns user and token', async () => {
|
|
535
|
+
const { data } = await spaps.login('test@example.com', 'password')
|
|
536
|
+
|
|
537
|
+
expect(data.user).toBeDefined()
|
|
538
|
+
expect(data.user.email).toBe('test@example.com')
|
|
539
|
+
expect(data.access_token).toBeDefined()
|
|
540
|
+
})
|
|
541
|
+
|
|
542
|
+
test('sets auth token after login', async () => {
|
|
543
|
+
await spaps.login('test@example.com', 'password')
|
|
544
|
+
|
|
545
|
+
expect(spaps.isAuthenticated()).toBe(true)
|
|
546
|
+
expect(spaps.getAccessToken()).toBeDefined()
|
|
547
|
+
})
|
|
548
|
+
})
|
|
549
|
+
|
|
550
|
+
${chalk.green('E2E Testing with Cypress:')}
|
|
551
|
+
${chalk.gray('// cypress/e2e/auth.cy.js')}
|
|
552
|
+
describe('Auth Flow', () => {
|
|
553
|
+
beforeEach(() => {
|
|
554
|
+
cy.visit('http://localhost:3000')
|
|
555
|
+
})
|
|
556
|
+
|
|
557
|
+
it('allows user to login', () => {
|
|
558
|
+
cy.get('[data-cy=email]').type('test@example.com')
|
|
559
|
+
cy.get('[data-cy=password]').type('password')
|
|
560
|
+
cy.get('[data-cy=submit]').click()
|
|
561
|
+
|
|
562
|
+
cy.url().should('include', '/dashboard')
|
|
563
|
+
cy.contains('Welcome back')
|
|
564
|
+
})
|
|
565
|
+
})
|
|
566
|
+
|
|
567
|
+
${chalk.green('Mocking for Tests:')}
|
|
568
|
+
${chalk.gray('// Mock SPAPS client')}
|
|
569
|
+
jest.mock('spaps-sdk', () => ({
|
|
570
|
+
SPAPSClient: jest.fn().mockImplementation(() => ({
|
|
571
|
+
login: jest.fn().mockResolvedValue({
|
|
572
|
+
data: {
|
|
573
|
+
user: { id: '123', email: 'test@example.com' },
|
|
574
|
+
access_token: 'mock-token'
|
|
575
|
+
}
|
|
576
|
+
}),
|
|
577
|
+
isAuthenticated: jest.fn().mockReturnValue(true),
|
|
578
|
+
getUser: jest.fn().mockResolvedValue({
|
|
579
|
+
data: { id: '123', email: 'test@example.com' }
|
|
580
|
+
})
|
|
581
|
+
}))
|
|
582
|
+
}))
|
|
583
|
+
`
|
|
584
|
+
},
|
|
585
|
+
|
|
586
|
+
api_reference: {
|
|
587
|
+
title: 'API Reference',
|
|
588
|
+
content: `
|
|
589
|
+
${chalk.green('Authentication Methods:')}
|
|
590
|
+
login(email: string, password: string): Promise<{data: AuthResponse}>
|
|
591
|
+
register(email: string, password: string): Promise<{data: AuthResponse}>
|
|
592
|
+
walletSignIn(address: string, signature: string, message: string, chain: string): Promise<{data: AuthResponse}>
|
|
593
|
+
refresh(refreshToken?: string): Promise<{data: AuthResponse}>
|
|
594
|
+
logout(): Promise<void>
|
|
595
|
+
getUser(): Promise<{data: User}>
|
|
596
|
+
|
|
597
|
+
${chalk.green('Payment Methods:')}
|
|
598
|
+
createCheckoutSession(priceId: string, successUrl: string, cancelUrl?: string): Promise<{data: CheckoutSession}>
|
|
599
|
+
getSubscription(): Promise<{data: Subscription}>
|
|
600
|
+
cancelSubscription(): Promise<void>
|
|
601
|
+
|
|
602
|
+
${chalk.green('Usage Methods:')}
|
|
603
|
+
getUsageBalance(): Promise<{data: UsageBalance}>
|
|
604
|
+
recordUsage(feature: string, amount: number): Promise<void>
|
|
605
|
+
|
|
606
|
+
${chalk.green('Utility Methods:')}
|
|
607
|
+
isAuthenticated(): boolean
|
|
608
|
+
getAccessToken(): string | undefined
|
|
609
|
+
setAccessToken(token: string): void
|
|
610
|
+
isLocalMode(): boolean
|
|
611
|
+
health(): Promise<{data: any}>
|
|
612
|
+
|
|
613
|
+
${chalk.green('Properties:')}
|
|
614
|
+
client: AxiosInstance ${chalk.gray('// Direct access to Axios client')}
|
|
615
|
+
|
|
616
|
+
${chalk.green('Response Types:')}
|
|
617
|
+
${chalk.gray('// All methods return data wrapped in { data: T }')}
|
|
618
|
+
${chalk.gray('// This matches Axios response structure')}
|
|
619
|
+
${chalk.gray('// Example:')}
|
|
620
|
+
const response = await spaps.login(email, password)
|
|
621
|
+
// response.data contains AuthResponse
|
|
622
|
+
// response.status, response.headers also available
|
|
623
|
+
`
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
const API_ENDPOINTS = {
|
|
628
|
+
title: 'API Endpoints',
|
|
629
|
+
content: `
|
|
630
|
+
${chalk.green('Authentication Endpoints:')}
|
|
631
|
+
POST /api/auth/register ${chalk.gray('Register new user')}
|
|
632
|
+
POST /api/auth/login ${chalk.gray('Login with email/password')}
|
|
633
|
+
POST /api/auth/wallet-sign-in ${chalk.gray('Login with wallet')}
|
|
634
|
+
POST /api/auth/refresh ${chalk.gray('Refresh access token')}
|
|
635
|
+
POST /api/auth/logout ${chalk.gray('Logout user')}
|
|
636
|
+
GET /api/auth/user ${chalk.gray('Get current user')}
|
|
637
|
+
|
|
638
|
+
${chalk.green('Stripe Endpoints:')}
|
|
639
|
+
POST /api/stripe/create-checkout-session ${chalk.gray('Create Stripe checkout')}
|
|
640
|
+
GET /api/stripe/subscription ${chalk.gray('Get subscription status')}
|
|
641
|
+
DELETE /api/stripe/subscription ${chalk.gray('Cancel subscription')}
|
|
642
|
+
POST /api/stripe/webhook ${chalk.gray('Stripe webhook handler')}
|
|
643
|
+
|
|
644
|
+
${chalk.green('Usage Endpoints:')}
|
|
645
|
+
GET /api/usage/balance ${chalk.gray('Get usage balance')}
|
|
646
|
+
POST /api/usage/record ${chalk.gray('Record usage event')}
|
|
647
|
+
GET /api/usage/history ${chalk.gray('Get usage history')}
|
|
648
|
+
|
|
649
|
+
${chalk.green('Health Endpoints:')}
|
|
650
|
+
GET /health ${chalk.gray('Health check')}
|
|
651
|
+
GET /health/local-mode ${chalk.gray('Local mode status')}
|
|
652
|
+
|
|
653
|
+
${chalk.green('Local Mode Features:')}
|
|
654
|
+
${chalk.gray('In local mode (http://localhost:3300):')}
|
|
655
|
+
⢠No API key required
|
|
656
|
+
⢠Auto-authentication enabled
|
|
657
|
+
⢠CORS disabled for all origins
|
|
658
|
+
⢠Mock responses for all endpoints
|
|
659
|
+
⢠Test users available: user, admin, premium
|
|
660
|
+
`
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
async function showDocsMenu() {
|
|
664
|
+
const choices = [
|
|
665
|
+
{ title: 'š Quick Start', value: 'quickstart' },
|
|
666
|
+
{ title: 'š Authentication', value: 'authentication' },
|
|
667
|
+
{ title: 'š³ Payments', value: 'payments' },
|
|
668
|
+
{ title: 'āļø Configuration', value: 'config' },
|
|
669
|
+
{ title: 'āļø React Integration', value: 'react' },
|
|
670
|
+
{ title: 'ā² Next.js Integration', value: 'nextjs' },
|
|
671
|
+
{ title: 'š¢ Node.js/Express', value: 'nodejs' },
|
|
672
|
+
{ title: 'ā Error Handling', value: 'errors' },
|
|
673
|
+
{ title: 'š TypeScript', value: 'typescript' },
|
|
674
|
+
{ title: 'š§Ŗ Testing', value: 'testing' },
|
|
675
|
+
{ title: 'š API Reference', value: 'api_reference' },
|
|
676
|
+
{ title: 'š API Endpoints', value: 'endpoints' },
|
|
677
|
+
{ title: chalk.gray('ā Back'), value: '__back__' },
|
|
678
|
+
{ title: chalk.gray('ā Exit'), value: '__exit__' }
|
|
679
|
+
];
|
|
680
|
+
|
|
681
|
+
const response = await prompts({
|
|
682
|
+
type: 'select',
|
|
683
|
+
name: 'section',
|
|
684
|
+
message: 'Select documentation section:',
|
|
685
|
+
choices: choices
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
return response.section;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
async function showInteractiveDocs() {
|
|
692
|
+
console.log(chalk.yellow('\nš SPAPS SDK Documentation\n'));
|
|
693
|
+
|
|
694
|
+
while (true) {
|
|
695
|
+
const section = await showDocsMenu();
|
|
696
|
+
|
|
697
|
+
if (!section || section === '__exit__') {
|
|
698
|
+
console.log(chalk.gray('\nGoodbye! š\n'));
|
|
699
|
+
process.exit(0);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (section === '__back__') {
|
|
703
|
+
return; // Go back to main help
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
if (section === 'endpoints') {
|
|
707
|
+
console.log(chalk.yellow(`\n${API_ENDPOINTS.title}`));
|
|
708
|
+
console.log(API_ENDPOINTS.content);
|
|
709
|
+
} else if (SDK_DOCS[section]) {
|
|
710
|
+
const doc = SDK_DOCS[section];
|
|
711
|
+
console.log(chalk.yellow(`\n${doc.title}`));
|
|
712
|
+
console.log(doc.content);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// Wait for user to read
|
|
716
|
+
const cont = await prompts({
|
|
717
|
+
type: 'confirm',
|
|
718
|
+
name: 'continue',
|
|
719
|
+
message: 'Continue browsing docs?',
|
|
720
|
+
initial: true
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
if (!cont.continue) {
|
|
724
|
+
console.log(chalk.gray('\nGoodbye! š\n'));
|
|
725
|
+
process.exit(0);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function showQuickReference() {
|
|
731
|
+
console.log(chalk.yellow('\nš SPAPS SDK Quick Reference\n'));
|
|
732
|
+
|
|
733
|
+
console.log(chalk.green('Installation:'));
|
|
734
|
+
console.log(' npm install spaps-sdk');
|
|
735
|
+
console.log();
|
|
736
|
+
|
|
737
|
+
console.log(chalk.green('Basic Setup:'));
|
|
738
|
+
console.log(chalk.gray(` import { SPAPSClient } from 'spaps-sdk'`));
|
|
739
|
+
console.log(chalk.gray(` const spaps = new SPAPSClient()`));
|
|
740
|
+
console.log();
|
|
741
|
+
|
|
742
|
+
console.log(chalk.green('Common Methods:'));
|
|
743
|
+
console.log(' await spaps.login(email, password)');
|
|
744
|
+
console.log(' await spaps.register(email, password)');
|
|
745
|
+
console.log(' await spaps.getUser()');
|
|
746
|
+
console.log(' await spaps.createCheckoutSession(priceId, successUrl)');
|
|
747
|
+
console.log(' await spaps.getSubscription()');
|
|
748
|
+
console.log(' await spaps.getUsageBalance()');
|
|
749
|
+
console.log();
|
|
750
|
+
|
|
751
|
+
console.log(chalk.green('Helper Methods:'));
|
|
752
|
+
console.log(' spaps.isAuthenticated() ' + chalk.gray('// Check auth status'));
|
|
753
|
+
console.log(' spaps.isLocalMode() ' + chalk.gray('// Check if local mode'));
|
|
754
|
+
console.log(' spaps.getAccessToken() ' + chalk.gray('// Get current token'));
|
|
755
|
+
console.log();
|
|
756
|
+
|
|
757
|
+
console.log(chalk.blue('š Full docs: npx spaps docs --interactive'));
|
|
758
|
+
console.log();
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function searchDocs(query) {
|
|
762
|
+
const results = [];
|
|
763
|
+
const searchTerm = query.toLowerCase();
|
|
764
|
+
|
|
765
|
+
// Search through all documentation
|
|
766
|
+
Object.entries(SDK_DOCS).forEach(([key, doc]) => {
|
|
767
|
+
const content = doc.content.toLowerCase();
|
|
768
|
+
const title = doc.title.toLowerCase();
|
|
769
|
+
|
|
770
|
+
if (title.includes(searchTerm) || content.includes(searchTerm)) {
|
|
771
|
+
// Count occurrences
|
|
772
|
+
const titleMatches = (title.match(new RegExp(searchTerm, 'g')) || []).length;
|
|
773
|
+
const contentMatches = (content.match(new RegExp(searchTerm, 'g')) || []).length;
|
|
774
|
+
|
|
775
|
+
results.push({
|
|
776
|
+
section: key,
|
|
777
|
+
title: doc.title,
|
|
778
|
+
score: titleMatches * 10 + contentMatches,
|
|
779
|
+
preview: extractPreview(doc.content, searchTerm)
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
// Sort by relevance
|
|
785
|
+
results.sort((a, b) => b.score - a.score);
|
|
786
|
+
|
|
787
|
+
return results.slice(0, 5); // Top 5 results
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function extractPreview(content, searchTerm) {
|
|
791
|
+
const lines = content.split('\n');
|
|
792
|
+
for (const line of lines) {
|
|
793
|
+
if (line.toLowerCase().includes(searchTerm)) {
|
|
794
|
+
return line.trim().substring(0, 80) + '...';
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
return lines[0].trim().substring(0, 80) + '...';
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
module.exports = {
|
|
801
|
+
showInteractiveDocs,
|
|
802
|
+
showQuickReference,
|
|
803
|
+
searchDocs,
|
|
804
|
+
SDK_DOCS,
|
|
805
|
+
API_ENDPOINTS
|
|
806
|
+
};
|
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPAPS CLI Interactive Help System
|
|
3
|
+
* Decision trees and structured guidance for developers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
const prompts = require('prompts');
|
|
8
|
+
|
|
9
|
+
const HELP_TREE = {
|
|
10
|
+
root: {
|
|
11
|
+
question: 'What would you like to do?',
|
|
12
|
+
options: [
|
|
13
|
+
{
|
|
14
|
+
title: 'š Start developing locally',
|
|
15
|
+
value: 'local-dev',
|
|
16
|
+
description: 'Run SPAPS on your machine'
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
title: 'š¦ Set up a new project',
|
|
20
|
+
value: 'new-project',
|
|
21
|
+
description: 'Create a new app with SPAPS'
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
title: 'š§ Add SPAPS to existing project',
|
|
25
|
+
value: 'existing-project',
|
|
26
|
+
description: 'Integrate SPAPS into your app'
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
title: 'š Debug an issue',
|
|
30
|
+
value: 'debug',
|
|
31
|
+
description: 'Troubleshoot problems'
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
title: 'š Learn about SPAPS',
|
|
35
|
+
value: 'learn',
|
|
36
|
+
description: 'Understand concepts and features'
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
'local-dev': {
|
|
42
|
+
question: 'How do you want to run SPAPS locally?',
|
|
43
|
+
options: [
|
|
44
|
+
{
|
|
45
|
+
title: 'Quick start (default settings)',
|
|
46
|
+
value: 'quick-start',
|
|
47
|
+
command: 'npx spaps local',
|
|
48
|
+
description: 'Start on port 3300'
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
title: 'Custom port',
|
|
52
|
+
value: 'custom-port',
|
|
53
|
+
command: 'npx spaps local --port 3001',
|
|
54
|
+
description: 'Choose your own port'
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
title: 'Open browser automatically',
|
|
58
|
+
value: 'auto-open',
|
|
59
|
+
command: 'npx spaps local --open',
|
|
60
|
+
description: 'Opens docs in browser'
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
title: 'JSON mode (for CI/CD)',
|
|
64
|
+
value: 'json-mode',
|
|
65
|
+
command: 'npx spaps local --json',
|
|
66
|
+
description: 'Machine-readable output'
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
'new-project': {
|
|
72
|
+
question: 'What type of project?',
|
|
73
|
+
options: [
|
|
74
|
+
{
|
|
75
|
+
title: 'Next.js App',
|
|
76
|
+
value: 'nextjs',
|
|
77
|
+
command: 'npx spaps create my-app --template nextjs',
|
|
78
|
+
description: 'Full-stack React framework',
|
|
79
|
+
available: 'v0.3.0'
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
title: 'React + Vite',
|
|
83
|
+
value: 'react',
|
|
84
|
+
command: 'npx spaps create my-app --template react',
|
|
85
|
+
description: 'Fast, modern React setup',
|
|
86
|
+
available: 'v0.3.0'
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
title: 'Node.js API',
|
|
90
|
+
value: 'node',
|
|
91
|
+
command: 'npx spaps create my-api --template node',
|
|
92
|
+
description: 'Express.js backend',
|
|
93
|
+
available: 'v0.3.0'
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
title: 'Vanilla JavaScript',
|
|
97
|
+
value: 'vanilla',
|
|
98
|
+
command: 'npx spaps create my-app --template vanilla',
|
|
99
|
+
description: 'No framework, just JS',
|
|
100
|
+
available: 'v0.3.0'
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
'existing-project': {
|
|
106
|
+
question: 'What framework are you using?',
|
|
107
|
+
options: [
|
|
108
|
+
{
|
|
109
|
+
title: 'Next.js',
|
|
110
|
+
value: 'integrate-nextjs',
|
|
111
|
+
steps: [
|
|
112
|
+
'npm install spaps-sdk',
|
|
113
|
+
'npx spaps init',
|
|
114
|
+
'Add to your app:',
|
|
115
|
+
'',
|
|
116
|
+
'// app/providers.tsx',
|
|
117
|
+
'import { SPAPSClient } from "spaps-sdk"',
|
|
118
|
+
'const spaps = new SPAPSClient()'
|
|
119
|
+
]
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
title: 'React',
|
|
123
|
+
value: 'integrate-react',
|
|
124
|
+
steps: [
|
|
125
|
+
'npm install spaps-sdk',
|
|
126
|
+
'npx spaps init',
|
|
127
|
+
'Create a context:',
|
|
128
|
+
'',
|
|
129
|
+
'// src/contexts/SpapsContext.tsx',
|
|
130
|
+
'import { SPAPSClient } from "spaps-sdk"',
|
|
131
|
+
'const spaps = new SPAPSClient()',
|
|
132
|
+
'export const SpapsContext = React.createContext(spaps)'
|
|
133
|
+
]
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
title: 'Node.js/Express',
|
|
137
|
+
value: 'integrate-node',
|
|
138
|
+
steps: [
|
|
139
|
+
'npm install spaps-sdk',
|
|
140
|
+
'npx spaps init',
|
|
141
|
+
'Add middleware:',
|
|
142
|
+
'',
|
|
143
|
+
'// server.js',
|
|
144
|
+
'const { SPAPSClient } = require("spaps-sdk")',
|
|
145
|
+
'const spaps = new SPAPSClient()',
|
|
146
|
+
'',
|
|
147
|
+
'app.use(async (req, res, next) => {',
|
|
148
|
+
' req.spaps = spaps',
|
|
149
|
+
' next()',
|
|
150
|
+
'})'
|
|
151
|
+
]
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
title: 'Other/Vanilla',
|
|
155
|
+
value: 'integrate-other',
|
|
156
|
+
steps: [
|
|
157
|
+
'npm install spaps-sdk',
|
|
158
|
+
'npx spaps init',
|
|
159
|
+
'Import in your code:',
|
|
160
|
+
'',
|
|
161
|
+
'// Using ES6 modules',
|
|
162
|
+
'import { SPAPSClient } from "spaps-sdk"',
|
|
163
|
+
'',
|
|
164
|
+
'// Using CommonJS',
|
|
165
|
+
'const { SPAPSClient } = require("spaps-sdk")',
|
|
166
|
+
'',
|
|
167
|
+
'// Using CDN',
|
|
168
|
+
'<script src="https://unpkg.com/spaps-sdk"></script>'
|
|
169
|
+
]
|
|
170
|
+
}
|
|
171
|
+
]
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
'debug': {
|
|
175
|
+
question: 'What issue are you experiencing?',
|
|
176
|
+
options: [
|
|
177
|
+
{
|
|
178
|
+
title: 'Port already in use',
|
|
179
|
+
value: 'port-error',
|
|
180
|
+
solution: {
|
|
181
|
+
commands: [
|
|
182
|
+
'npx spaps local --port 3301',
|
|
183
|
+
'lsof -ti:3300 | xargs kill -9',
|
|
184
|
+
'npx spaps local --port 0'
|
|
185
|
+
],
|
|
186
|
+
explanation: 'Another process is using port 3300. Either use a different port or kill the existing process.'
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
title: 'Module not found',
|
|
191
|
+
value: 'module-error',
|
|
192
|
+
solution: {
|
|
193
|
+
commands: [
|
|
194
|
+
'npm install',
|
|
195
|
+
'rm -rf node_modules package-lock.json && npm install',
|
|
196
|
+
'npm install spaps-sdk'
|
|
197
|
+
],
|
|
198
|
+
explanation: 'Dependencies are missing. Reinstall packages to fix this.'
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
title: 'CORS errors',
|
|
203
|
+
value: 'cors-error',
|
|
204
|
+
solution: {
|
|
205
|
+
commands: [
|
|
206
|
+
'npx spaps local',
|
|
207
|
+
'Check your API URL: process.env.SPAPS_API_URL'
|
|
208
|
+
],
|
|
209
|
+
explanation: 'CORS is automatically handled in local mode. Make sure your client is pointing to the local server.'
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
title: 'Authentication failing',
|
|
214
|
+
value: 'auth-error',
|
|
215
|
+
solution: {
|
|
216
|
+
commands: [
|
|
217
|
+
'curl http://localhost:3300/health',
|
|
218
|
+
'npx spaps local --port 3300'
|
|
219
|
+
],
|
|
220
|
+
explanation: 'In local mode, authentication is automatic. Make sure the local server is running.'
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
]
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
'learn': {
|
|
227
|
+
question: 'What would you like to learn about?',
|
|
228
|
+
options: [
|
|
229
|
+
{
|
|
230
|
+
title: 'How SPAPS works',
|
|
231
|
+
value: 'how-it-works',
|
|
232
|
+
content: `
|
|
233
|
+
SPAPS (Sweet Potato Authentication & Payment Service) provides:
|
|
234
|
+
|
|
235
|
+
1. **Authentication**: Email/password, magic links, and wallet-based auth
|
|
236
|
+
2. **Payments**: Stripe subscriptions and wallet-based payments
|
|
237
|
+
3. **Local Development**: Zero-config local mode with auto-auth
|
|
238
|
+
4. **Type Safety**: Full TypeScript support with generated types
|
|
239
|
+
5. **Multi-tenant**: API key-based app isolation
|
|
240
|
+
|
|
241
|
+
In local mode, everything is mocked so you can develop without:
|
|
242
|
+
- API keys
|
|
243
|
+
- Database setup
|
|
244
|
+
- External services
|
|
245
|
+
- Configuration files
|
|
246
|
+
`
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
title: 'Local vs Production',
|
|
250
|
+
value: 'environments',
|
|
251
|
+
content: `
|
|
252
|
+
**Local Mode** (http://localhost:3300):
|
|
253
|
+
- No API key required
|
|
254
|
+
- Auto-authentication
|
|
255
|
+
- Mocked responses
|
|
256
|
+
- CORS disabled
|
|
257
|
+
- Perfect for development
|
|
258
|
+
|
|
259
|
+
**Production Mode** (https://api.yourapp.com):
|
|
260
|
+
- API key required
|
|
261
|
+
- Real authentication
|
|
262
|
+
- Database persistence
|
|
263
|
+
- Stripe integration
|
|
264
|
+
- Rate limiting
|
|
265
|
+
|
|
266
|
+
The SDK auto-detects which mode to use based on the URL!
|
|
267
|
+
`
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
title: 'Authentication methods',
|
|
271
|
+
value: 'auth-methods',
|
|
272
|
+
content: `
|
|
273
|
+
SPAPS supports multiple authentication methods:
|
|
274
|
+
|
|
275
|
+
**Traditional Auth**:
|
|
276
|
+
- Email/password login
|
|
277
|
+
- Magic link (passwordless)
|
|
278
|
+
- Social logins (coming soon)
|
|
279
|
+
|
|
280
|
+
**Wallet Auth**:
|
|
281
|
+
- Solana wallets
|
|
282
|
+
- Ethereum wallets
|
|
283
|
+
- Bitcoin (coming soon)
|
|
284
|
+
- Base chain (coming soon)
|
|
285
|
+
|
|
286
|
+
**Code Examples**:
|
|
287
|
+
\`\`\`javascript
|
|
288
|
+
// Email/password
|
|
289
|
+
await spaps.login(email, password)
|
|
290
|
+
|
|
291
|
+
// Wallet
|
|
292
|
+
await spaps.walletSignIn(address, signature, message, 'solana')
|
|
293
|
+
\`\`\`
|
|
294
|
+
`
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
title: 'Payment integration',
|
|
298
|
+
value: 'payments',
|
|
299
|
+
content: `
|
|
300
|
+
SPAPS handles two payment types:
|
|
301
|
+
|
|
302
|
+
**Stripe Subscriptions**:
|
|
303
|
+
- Monthly/annual plans
|
|
304
|
+
- Usage-based billing
|
|
305
|
+
- Customer portal
|
|
306
|
+
- Webhook handling
|
|
307
|
+
|
|
308
|
+
**Wallet Payments**:
|
|
309
|
+
- Direct crypto payments
|
|
310
|
+
- Usage credits
|
|
311
|
+
- Instant settlement
|
|
312
|
+
- Multi-chain support
|
|
313
|
+
|
|
314
|
+
**Code Example**:
|
|
315
|
+
\`\`\`javascript
|
|
316
|
+
// Create Stripe checkout
|
|
317
|
+
const session = await spaps.createCheckoutSession(priceId, successUrl)
|
|
318
|
+
|
|
319
|
+
// Check subscription
|
|
320
|
+
const sub = await spaps.getSubscription()
|
|
321
|
+
\`\`\`
|
|
322
|
+
`
|
|
323
|
+
}
|
|
324
|
+
]
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
async function showInteractiveHelp() {
|
|
329
|
+
console.log(chalk.yellow('\nš SPAPS Interactive Help\n'));
|
|
330
|
+
|
|
331
|
+
let currentNode = 'root';
|
|
332
|
+
const history = [];
|
|
333
|
+
|
|
334
|
+
while (true) {
|
|
335
|
+
const node = HELP_TREE[currentNode];
|
|
336
|
+
|
|
337
|
+
if (!node) {
|
|
338
|
+
// Leaf node - show result
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const choices = node.options.map(opt => ({
|
|
343
|
+
title: opt.title,
|
|
344
|
+
description: opt.description || '',
|
|
345
|
+
value: opt.value
|
|
346
|
+
}));
|
|
347
|
+
|
|
348
|
+
// Add back option if not at root
|
|
349
|
+
if (currentNode !== 'root') {
|
|
350
|
+
choices.push({
|
|
351
|
+
title: chalk.gray('ā Back'),
|
|
352
|
+
value: '__back__'
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
choices.push({
|
|
357
|
+
title: chalk.gray('ā Exit'),
|
|
358
|
+
value: '__exit__'
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const response = await prompts({
|
|
362
|
+
type: 'select',
|
|
363
|
+
name: 'choice',
|
|
364
|
+
message: node.question,
|
|
365
|
+
choices: choices
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
if (!response.choice || response.choice === '__exit__') {
|
|
369
|
+
console.log(chalk.gray('\nGoodbye! š\n'));
|
|
370
|
+
process.exit(0);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (response.choice === '__back__') {
|
|
374
|
+
currentNode = history.pop() || 'root';
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Find the selected option
|
|
379
|
+
const selected = node.options.find(opt => opt.value === response.choice);
|
|
380
|
+
|
|
381
|
+
if (selected) {
|
|
382
|
+
// Show immediate result if available
|
|
383
|
+
if (selected.command) {
|
|
384
|
+
console.log(chalk.green('\n⨠Run this command:\n'));
|
|
385
|
+
console.log(chalk.bgBlack.white(` $ ${selected.command}`));
|
|
386
|
+
|
|
387
|
+
if (selected.available) {
|
|
388
|
+
console.log(chalk.yellow(`\n ā ļø Available in ${selected.available}`));
|
|
389
|
+
}
|
|
390
|
+
console.log();
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (selected.steps) {
|
|
394
|
+
console.log(chalk.green('\nš Follow these steps:\n'));
|
|
395
|
+
selected.steps.forEach((step, i) => {
|
|
396
|
+
if (step.startsWith('//') || step.startsWith('#')) {
|
|
397
|
+
console.log(chalk.gray(step));
|
|
398
|
+
} else if (step === '') {
|
|
399
|
+
console.log();
|
|
400
|
+
} else if (step.includes('npm') || step.includes('npx')) {
|
|
401
|
+
console.log(chalk.bgBlack.white(` $ ${step}`));
|
|
402
|
+
} else {
|
|
403
|
+
console.log(` ${step}`);
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
console.log();
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (selected.solution) {
|
|
410
|
+
console.log(chalk.green('\nš” Solution:\n'));
|
|
411
|
+
console.log(chalk.white(selected.solution.explanation));
|
|
412
|
+
console.log(chalk.green('\nTry these commands:\n'));
|
|
413
|
+
selected.solution.commands.forEach(cmd => {
|
|
414
|
+
console.log(chalk.bgBlack.white(` $ ${cmd}`));
|
|
415
|
+
});
|
|
416
|
+
console.log();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (selected.content) {
|
|
420
|
+
console.log(chalk.green('\nš Information:\n'));
|
|
421
|
+
console.log(selected.content);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Navigate to next level if exists
|
|
425
|
+
if (HELP_TREE[response.choice]) {
|
|
426
|
+
history.push(currentNode);
|
|
427
|
+
currentNode = response.choice;
|
|
428
|
+
} else {
|
|
429
|
+
// Ask to continue or exit
|
|
430
|
+
const cont = await prompts({
|
|
431
|
+
type: 'confirm',
|
|
432
|
+
name: 'continue',
|
|
433
|
+
message: 'Continue exploring help?',
|
|
434
|
+
initial: true
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
if (!cont.continue) {
|
|
438
|
+
console.log(chalk.gray('\nGoodbye! š\n'));
|
|
439
|
+
process.exit(0);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Go back to previous menu
|
|
443
|
+
currentNode = history.pop() || 'root';
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function showQuickHelp() {
|
|
450
|
+
console.log(chalk.yellow('\nš SPAPS Quick Reference\n'));
|
|
451
|
+
|
|
452
|
+
console.log(chalk.green('Common Commands:'));
|
|
453
|
+
console.log(' npx spaps local ' + chalk.gray('# Start local server'));
|
|
454
|
+
console.log(' npx spaps init ' + chalk.gray('# Initialize in project'));
|
|
455
|
+
console.log(' npx spaps create <name> ' + chalk.gray('# Create new project (v0.3.0)'));
|
|
456
|
+
console.log(' npx spaps types ' + chalk.gray('# Generate types (v0.4.0)'));
|
|
457
|
+
console.log();
|
|
458
|
+
|
|
459
|
+
console.log(chalk.green('Local Development:'));
|
|
460
|
+
console.log(' npx spaps local --port 3001 ' + chalk.gray('# Custom port'));
|
|
461
|
+
console.log(' npx spaps local --open ' + chalk.gray('# Open browser'));
|
|
462
|
+
console.log(' npx spaps local --json ' + chalk.gray('# JSON output'));
|
|
463
|
+
console.log();
|
|
464
|
+
|
|
465
|
+
console.log(chalk.green('SDK Usage:'));
|
|
466
|
+
console.log(' npm install spaps-sdk ' + chalk.gray('# Install SDK'));
|
|
467
|
+
console.log();
|
|
468
|
+
console.log(chalk.gray(' import { SPAPSClient } from "spaps-sdk"'));
|
|
469
|
+
console.log(chalk.gray(' const spaps = new SPAPSClient()'));
|
|
470
|
+
console.log(chalk.gray(' await spaps.login(email, password)'));
|
|
471
|
+
console.log();
|
|
472
|
+
|
|
473
|
+
console.log(chalk.green('Debugging:'));
|
|
474
|
+
console.log(' curl http://localhost:3300/health ' + chalk.gray('# Check server'));
|
|
475
|
+
console.log(' npx spaps help --interactive ' + chalk.gray('# Interactive help'));
|
|
476
|
+
console.log();
|
|
477
|
+
|
|
478
|
+
console.log(chalk.blue('š Docs: https://sweetpotato.dev'));
|
|
479
|
+
console.log(chalk.blue('š¬ Discord: https://discord.gg/sweetpotato'));
|
|
480
|
+
console.log();
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
module.exports = {
|
|
484
|
+
showInteractiveHelp,
|
|
485
|
+
showQuickHelp,
|
|
486
|
+
HELP_TREE
|
|
487
|
+
};
|