@rubytech/create-maxy 0.4.5 → 0.4.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.
Potentially problematic release.
This version of @rubytech/create-maxy might be problematic. Click here for more details.
package/package.json
CHANGED
|
@@ -1,8 +1,53 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import {
|
|
2
|
+
import { execFileSync } from 'node:child_process'
|
|
3
|
+
import { loginAnthropic, type OAuthCredentials } from '@mariozechner/pi-ai'
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs'
|
|
5
|
+
import { resolve } from 'node:path'
|
|
6
|
+
import { homedir } from 'node:os'
|
|
7
|
+
|
|
8
|
+
// --- Active OAuth session (same pattern as v1 gateway/server-methods/auth.ts) ---
|
|
9
|
+
|
|
10
|
+
type OAuthSession = {
|
|
11
|
+
authUrl: string | null
|
|
12
|
+
codeResolver: ((code: string) => void) | null
|
|
13
|
+
codePromise: Promise<string> | null
|
|
14
|
+
completed: boolean
|
|
15
|
+
error: string | null
|
|
16
|
+
credentials: OAuthCredentials | null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let activeSession: OAuthSession | null = null
|
|
20
|
+
|
|
21
|
+
function resetSession(): void {
|
|
22
|
+
activeSession = null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// --- Claude CLI credentials ---
|
|
26
|
+
|
|
27
|
+
const CLAUDE_CREDS_PATH = resolve(homedir(), '.claude/.credentials.json')
|
|
28
|
+
|
|
29
|
+
function writeClaudeCredentials(creds: OAuthCredentials): void {
|
|
30
|
+
const claudeDir = resolve(homedir(), '.claude')
|
|
31
|
+
mkdirSync(claudeDir, { recursive: true })
|
|
32
|
+
|
|
33
|
+
let data: Record<string, unknown> = {}
|
|
34
|
+
if (existsSync(CLAUDE_CREDS_PATH)) {
|
|
35
|
+
try {
|
|
36
|
+
data = JSON.parse(readFileSync(CLAUDE_CREDS_PATH, 'utf-8'))
|
|
37
|
+
} catch { /* start fresh */ }
|
|
38
|
+
}
|
|
3
39
|
|
|
4
|
-
|
|
5
|
-
|
|
40
|
+
data.claudeAiOauth = {
|
|
41
|
+
...(typeof data.claudeAiOauth === 'object' && data.claudeAiOauth ? data.claudeAiOauth : {}),
|
|
42
|
+
accessToken: creds.access,
|
|
43
|
+
refreshToken: creds.refresh,
|
|
44
|
+
expiresAt: creds.expires,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
writeFileSync(CLAUDE_CREDS_PATH, JSON.stringify(data, null, 2), { mode: 0o600 })
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// --- Endpoints ---
|
|
6
51
|
|
|
7
52
|
/**
|
|
8
53
|
* GET /api/onboarding/claude-auth
|
|
@@ -26,112 +71,118 @@ export async function GET() {
|
|
|
26
71
|
|
|
27
72
|
/**
|
|
28
73
|
* POST /api/onboarding/claude-auth
|
|
29
|
-
*
|
|
30
|
-
*
|
|
74
|
+
* Three actions:
|
|
75
|
+
* - {} — start OAuth flow, returns authUrl
|
|
76
|
+
* - { code: "..." } — submit the auth code
|
|
77
|
+
* - { action: "wait" } — poll for completion
|
|
31
78
|
*/
|
|
32
79
|
export async function POST(req: Request) {
|
|
33
|
-
let body: { code?: string } = {}
|
|
80
|
+
let body: { code?: string; action?: string } = {}
|
|
34
81
|
try {
|
|
35
82
|
body = await req.json()
|
|
36
83
|
} catch { /* empty body = start flow */ }
|
|
37
84
|
|
|
38
|
-
// --- Submit code
|
|
39
|
-
if (body.code
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
clearTimeout(timeout)
|
|
47
|
-
resolve()
|
|
48
|
-
})
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
loginProcess = null
|
|
85
|
+
// --- Submit code ---
|
|
86
|
+
if (body.code) {
|
|
87
|
+
if (!activeSession?.codeResolver) {
|
|
88
|
+
return NextResponse.json(
|
|
89
|
+
{ error: 'No active OAuth session. Click "Sign in with Claude" to start again.' },
|
|
90
|
+
{ status: 400 },
|
|
91
|
+
)
|
|
92
|
+
}
|
|
52
93
|
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
const output = execFileSync('claude', ['auth', 'status', '--json'], {
|
|
56
|
-
encoding: 'utf-8',
|
|
57
|
-
timeout: 5000,
|
|
58
|
-
})
|
|
59
|
-
const status = JSON.parse(output)
|
|
60
|
-
if (status.loggedIn) {
|
|
61
|
-
return NextResponse.json({ authenticated: true, email: status.email })
|
|
62
|
-
}
|
|
63
|
-
} catch { /* */ }
|
|
94
|
+
activeSession.codeResolver(body.code.trim())
|
|
64
95
|
|
|
65
|
-
return NextResponse.json({
|
|
66
|
-
authenticated: false,
|
|
67
|
-
error: 'Authentication failed. Check the code and try again.',
|
|
68
|
-
})
|
|
96
|
+
return NextResponse.json({ message: 'Code received — verifying...' })
|
|
69
97
|
}
|
|
70
98
|
|
|
71
|
-
// ---
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
99
|
+
// --- Poll for completion ---
|
|
100
|
+
if (body.action === 'wait') {
|
|
101
|
+
if (!activeSession) {
|
|
102
|
+
return NextResponse.json({ completed: false, error: 'No active session.' })
|
|
103
|
+
}
|
|
76
104
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
env: { ...process.env, BROWSER: 'echo', TERM: 'dumb' },
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
loginProcess = child
|
|
93
|
-
|
|
94
|
-
let output = ''
|
|
95
|
-
let resolved = false
|
|
96
|
-
|
|
97
|
-
function tryResolve() {
|
|
98
|
-
if (resolved) return
|
|
99
|
-
const match = output.match(/(https:\/\/claude\.ai\/oauth\/authorize[^\s]+)/)
|
|
100
|
-
if (match) {
|
|
101
|
-
resolved = true
|
|
102
|
-
resolve(NextResponse.json({ authUrl: match[1] }))
|
|
105
|
+
// Poll for up to 5 seconds
|
|
106
|
+
const start = Date.now()
|
|
107
|
+
while (Date.now() - start < 5000) {
|
|
108
|
+
if (activeSession.completed) {
|
|
109
|
+
if (activeSession.error) {
|
|
110
|
+
const error = activeSession.error
|
|
111
|
+
resetSession()
|
|
112
|
+
return NextResponse.json({ completed: true, authenticated: false, error })
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
resetSession()
|
|
116
|
+
return NextResponse.json({ completed: true, authenticated: true })
|
|
103
117
|
}
|
|
118
|
+
await new Promise(r => setTimeout(r, 500))
|
|
104
119
|
}
|
|
105
120
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
tryResolve()
|
|
109
|
-
})
|
|
121
|
+
return NextResponse.json({ completed: false })
|
|
122
|
+
}
|
|
110
123
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
124
|
+
// --- Start OAuth flow ---
|
|
125
|
+
resetSession()
|
|
126
|
+
|
|
127
|
+
activeSession = {
|
|
128
|
+
authUrl: null,
|
|
129
|
+
codeResolver: null,
|
|
130
|
+
codePromise: null,
|
|
131
|
+
completed: false,
|
|
132
|
+
error: null,
|
|
133
|
+
credentials: null,
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Set up code promise (resolved when user submits code via POST { code })
|
|
137
|
+
activeSession.codePromise = new Promise<string>((resolve) => {
|
|
138
|
+
activeSession!.codeResolver = resolve
|
|
139
|
+
})
|
|
115
140
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
141
|
+
// Start OAuth flow in background (same as v1)
|
|
142
|
+
const oauthPromise = loginAnthropic(
|
|
143
|
+
// onAuthUrl — called when we have the URL
|
|
144
|
+
(url: string) => {
|
|
145
|
+
if (activeSession) {
|
|
146
|
+
activeSession.authUrl = url
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
// onPromptCode — called when the library needs the code
|
|
150
|
+
async (): Promise<string> => {
|
|
151
|
+
if (!activeSession?.codePromise) {
|
|
152
|
+
throw new Error('OAuth session expired')
|
|
153
|
+
}
|
|
154
|
+
return activeSession.codePromise
|
|
155
|
+
},
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
// Handle completion in background
|
|
159
|
+
oauthPromise
|
|
160
|
+
.then((credentials) => {
|
|
161
|
+
if (activeSession) {
|
|
162
|
+
activeSession.credentials = credentials
|
|
163
|
+
activeSession.completed = true
|
|
164
|
+
writeClaudeCredentials(credentials)
|
|
124
165
|
}
|
|
125
166
|
})
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
resolve(NextResponse.json(
|
|
131
|
-
{ error: 'Could not start auth flow. Is Claude Code installed?' },
|
|
132
|
-
{ status: 500 },
|
|
133
|
-
))
|
|
167
|
+
.catch((err) => {
|
|
168
|
+
if (activeSession) {
|
|
169
|
+
activeSession.error = err instanceof Error ? err.message : String(err)
|
|
170
|
+
activeSession.completed = true
|
|
134
171
|
}
|
|
135
|
-
}
|
|
136
|
-
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
// Wait for the auth URL to appear
|
|
175
|
+
await new Promise(r => setTimeout(r, 500))
|
|
176
|
+
if (!activeSession?.authUrl) {
|
|
177
|
+
await new Promise(r => setTimeout(r, 1500))
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (!activeSession?.authUrl) {
|
|
181
|
+
return NextResponse.json(
|
|
182
|
+
{ error: 'Failed to start OAuth flow.' },
|
|
183
|
+
{ status: 500 },
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return NextResponse.json({ authUrl: activeSession.authUrl })
|
|
137
188
|
}
|
|
@@ -290,17 +290,35 @@ export default function AdminPage() {
|
|
|
290
290
|
setAuthLoading(true)
|
|
291
291
|
setPinError('')
|
|
292
292
|
try {
|
|
293
|
-
|
|
293
|
+
// Submit the code
|
|
294
|
+
await fetch('/api/onboarding/claude-auth', {
|
|
294
295
|
method: 'POST',
|
|
295
296
|
headers: { 'Content-Type': 'application/json' },
|
|
296
297
|
body: JSON.stringify({ code: authCode.trim() }),
|
|
297
298
|
})
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
299
|
+
|
|
300
|
+
// Poll for completion
|
|
301
|
+
for (let i = 0; i < 12; i++) {
|
|
302
|
+
const waitRes = await fetch('/api/onboarding/claude-auth', {
|
|
303
|
+
method: 'POST',
|
|
304
|
+
headers: { 'Content-Type': 'application/json' },
|
|
305
|
+
body: JSON.stringify({ action: 'wait' }),
|
|
306
|
+
})
|
|
307
|
+
const waitData = await waitRes.json()
|
|
308
|
+
|
|
309
|
+
if (waitData.completed) {
|
|
310
|
+
if (waitData.authenticated) {
|
|
311
|
+
setAppState('enter-pin')
|
|
312
|
+
return
|
|
313
|
+
} else {
|
|
314
|
+
setPinError(waitData.error || 'Authentication failed. Check the code and try again.')
|
|
315
|
+
setAuthLoading(false)
|
|
316
|
+
return
|
|
317
|
+
}
|
|
318
|
+
}
|
|
303
319
|
}
|
|
320
|
+
|
|
321
|
+
setPinError('Timed out waiting for verification. Try again.')
|
|
304
322
|
} catch {
|
|
305
323
|
setPinError('Could not verify code.')
|
|
306
324
|
}
|