@meistrari/tela-cli 0.7.0 → 0.7.5
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 +56 -106
- package/dist/commands/auth.d.ts +2 -0
- package/dist/commands/context.d.ts +2 -0
- package/dist/commands/doctor-auth.d.ts +2 -0
- package/dist/commands/env.d.ts +2 -0
- package/dist/credentials.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7027 -0
- package/dist/output.d.ts +13 -0
- package/dist/program.d.ts +2 -0
- package/package.json +17 -24
- package/scripts/install-skills.cjs +127 -0
- package/skills/tela-cli/SKILL.md +20 -0
- package/skills/tela-cli/references/agent.md +111 -0
- package/cli.ts +0 -25
- package/postinstall.sh +0 -19
- package/src/agent-api.ts +0 -300
- package/src/assets.d.ts +0 -4
- package/src/auth.ts +0 -432
- package/src/claude.ts +0 -101
- package/src/commands/doctor.ts +0 -35
- package/src/commands/feedback.ts +0 -701
- package/src/commands/metadata.ts +0 -242
- package/src/commands/push.ts +0 -208
- package/src/commands/run.ts +0 -1699
- package/src/commands/status.ts +0 -109
- package/src/commands/sync.ts +0 -245
- package/src/commit-message.ts +0 -69
- package/src/config.ts +0 -799
- package/src/diagnostics.ts +0 -813
- package/src/errors.ts +0 -122
- package/src/forgejo.ts +0 -233
- package/src/git.ts +0 -457
- package/src/ink/assets/160092__jorickhoofd__dog-bark-1.mp3 +0 -0
- package/src/ink/bark.tsx +0 -241
- package/src/ink/components.tsx +0 -178
- package/src/ink/runtime.tsx +0 -90
- package/src/ink/screens/feedback-screen.tsx +0 -395
- package/src/ink/screens/push-screen.tsx +0 -232
- package/src/ink/screens/run-screen.tsx +0 -292
- package/src/ink/screens/setup/breadcrumb.tsx +0 -24
- package/src/ink/screens/setup/setup-screen.tsx +0 -612
- package/src/ink/screens/setup/step-frame.tsx +0 -31
- package/src/ink/screens/setup/steps/done-step.tsx +0 -29
- package/src/ink/screens/setup/steps/environment-step.tsx +0 -23
- package/src/ink/screens/setup/steps/organization-step.tsx +0 -35
- package/src/ink/screens/setup/steps/review-step.tsx +0 -78
- package/src/ink/screens/setup/steps/running-step.tsx +0 -37
- package/src/ink/screens/setup/steps/ssh-key-step.tsx +0 -48
- package/src/ink/screens/setup/steps/text-field-step.tsx +0 -18
- package/src/ink/screens/setup/steps.ts +0 -156
- package/src/ink/screens/setup/types.ts +0 -65
- package/src/ink/screens/sync-screen.tsx +0 -283
- package/src/process.ts +0 -251
- package/src/program.ts +0 -2105
- package/src/runtime.ts +0 -147
- package/src/types.ts +0 -165
- package/src/ui.ts +0 -295
- package/src/version.ts +0 -3
package/src/auth.ts
DELETED
|
@@ -1,432 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DeviceAccessDeniedError,
|
|
3
|
-
DeviceCodeExpiredError,
|
|
4
|
-
DeviceFlow,
|
|
5
|
-
DeviceFlowAbortedError,
|
|
6
|
-
RefreshTokenExpiredError,
|
|
7
|
-
} from '@meistrari/auth-cli'
|
|
8
|
-
import {
|
|
9
|
-
clearSavedAuth,
|
|
10
|
-
isPlaceholderApplicationId,
|
|
11
|
-
saveConfig,
|
|
12
|
-
} from './config.ts'
|
|
13
|
-
import { createUserFacingError } from './errors.ts'
|
|
14
|
-
import { runCommand } from './process.ts'
|
|
15
|
-
import type {
|
|
16
|
-
TelaCliAuthState,
|
|
17
|
-
TelaCliConfig,
|
|
18
|
-
TelaCliUser,
|
|
19
|
-
TelaCliWorkspace,
|
|
20
|
-
} from './types.ts'
|
|
21
|
-
|
|
22
|
-
export type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>
|
|
23
|
-
|
|
24
|
-
type StoredTokensLike = {
|
|
25
|
-
accessToken: string
|
|
26
|
-
refreshToken: string
|
|
27
|
-
expiresAt: number
|
|
28
|
-
user: {
|
|
29
|
-
id: string
|
|
30
|
-
email: string
|
|
31
|
-
name?: string | null
|
|
32
|
-
}
|
|
33
|
-
organization: {
|
|
34
|
-
id: string
|
|
35
|
-
name?: string | null
|
|
36
|
-
title?: string | null
|
|
37
|
-
slug?: string | null
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface DeviceFlowLike {
|
|
42
|
-
on(
|
|
43
|
-
eventName: 'userCode',
|
|
44
|
-
listener: (payload: {
|
|
45
|
-
userCode: string
|
|
46
|
-
verificationUri: string
|
|
47
|
-
verificationUriComplete: string
|
|
48
|
-
expiresIn: number
|
|
49
|
-
interval: number
|
|
50
|
-
}) => void | Promise<void>,
|
|
51
|
-
): unknown
|
|
52
|
-
on(
|
|
53
|
-
eventName: 'pending',
|
|
54
|
-
listener: (payload: { attempt: number, elapsedMs: number }) => void | Promise<void>,
|
|
55
|
-
): unknown
|
|
56
|
-
on(
|
|
57
|
-
eventName: 'slowDown',
|
|
58
|
-
listener: (payload: { previousInterval: number, newInterval: number }) => void | Promise<void>,
|
|
59
|
-
): unknown
|
|
60
|
-
on(
|
|
61
|
-
eventName: 'transientError',
|
|
62
|
-
listener: (payload: { attempt: number }) => void | Promise<void>,
|
|
63
|
-
): unknown
|
|
64
|
-
authenticate(): Promise<StoredTokensLike>
|
|
65
|
-
refresh(refreshToken?: string): Promise<StoredTokensLike>
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export type DeviceFlowFactory = (input: {
|
|
69
|
-
apiUrl: string
|
|
70
|
-
requesterApplicationId: string
|
|
71
|
-
targetApplicationId: string
|
|
72
|
-
signal?: AbortSignal
|
|
73
|
-
}) => DeviceFlowLike
|
|
74
|
-
|
|
75
|
-
export interface SetupAuthEvent {
|
|
76
|
-
type: 'message' | 'user_code' | 'browser_opened' | 'browser_open_failed' | 'success'
|
|
77
|
-
message: string
|
|
78
|
-
title?: string
|
|
79
|
-
verificationUrl?: string
|
|
80
|
-
userCode?: string
|
|
81
|
-
expiresInSeconds?: number
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export interface AuthenticateWithDeviceFlowOptions {
|
|
85
|
-
createDeviceFlow?: DeviceFlowFactory
|
|
86
|
-
openBrowser?: (url: string) => Promise<boolean>
|
|
87
|
-
timeoutMs?: number
|
|
88
|
-
onEvent?: (event: SetupAuthEvent) => void
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export interface BuildAgentApiHeadersOptions {
|
|
92
|
-
clearAuth?: (config: TelaCliConfig) => TelaCliConfig
|
|
93
|
-
createDeviceFlow?: DeviceFlowFactory
|
|
94
|
-
fetchFn?: FetchLike
|
|
95
|
-
nowMs?: number
|
|
96
|
-
persistConfig?: (config: TelaCliConfig) => void
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function defaultDeviceFlowFactory(input: ConstructorParameters<typeof DeviceFlow>[0]): DeviceFlowLike {
|
|
100
|
-
return new DeviceFlow(input)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function createAuthRequiredError(summary: string): ReturnType<typeof createUserFacingError> {
|
|
104
|
-
return createUserFacingError({
|
|
105
|
-
code: 'auth_required',
|
|
106
|
-
summary,
|
|
107
|
-
fixes: ['Run `tela setup` in an interactive terminal to sign in again.'],
|
|
108
|
-
})
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function mapUser(user: StoredTokensLike['user']): TelaCliUser {
|
|
112
|
-
return {
|
|
113
|
-
id: user.id,
|
|
114
|
-
email: user.email,
|
|
115
|
-
name: user.name?.trim() || user.email,
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function mapWorkspace(organization: StoredTokensLike['organization']): TelaCliWorkspace {
|
|
120
|
-
return {
|
|
121
|
-
id: organization.id,
|
|
122
|
-
title: organization.name?.trim()
|
|
123
|
-
|| organization.title?.trim()
|
|
124
|
-
|| organization.slug?.trim()
|
|
125
|
-
|| organization.id,
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function mapTokens(tokens: StoredTokensLike): TelaCliAuthState {
|
|
130
|
-
return {
|
|
131
|
-
user: mapUser(tokens.user),
|
|
132
|
-
workspace: mapWorkspace(tokens.organization),
|
|
133
|
-
session: {
|
|
134
|
-
accessToken: tokens.accessToken,
|
|
135
|
-
refreshToken: tokens.refreshToken,
|
|
136
|
-
expiresAt: tokens.expiresAt,
|
|
137
|
-
},
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function buildDashboardVerificationUrl(dashboardUrl: string, userCode: string, fallbackUrl: string): string {
|
|
142
|
-
try {
|
|
143
|
-
const url = new URL('/applications/device', dashboardUrl)
|
|
144
|
-
url.searchParams.set('user_code', userCode)
|
|
145
|
-
return url.toString()
|
|
146
|
-
}
|
|
147
|
-
catch {
|
|
148
|
-
return fallbackUrl
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
async function defaultOpenBrowser(url: string): Promise<boolean> {
|
|
153
|
-
const command = (() => {
|
|
154
|
-
switch (process.platform) {
|
|
155
|
-
case 'darwin':
|
|
156
|
-
return ['open', url]
|
|
157
|
-
case 'win32':
|
|
158
|
-
return ['cmd', '/c', 'start', '', url]
|
|
159
|
-
default:
|
|
160
|
-
return ['xdg-open', url]
|
|
161
|
-
}
|
|
162
|
-
})()
|
|
163
|
-
|
|
164
|
-
try {
|
|
165
|
-
const result = await runCommand(command, { allowFailure: true })
|
|
166
|
-
return result.exitCode === 0
|
|
167
|
-
}
|
|
168
|
-
catch {
|
|
169
|
-
return false
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function ensurePresetApplicationIds(input: {
|
|
174
|
-
environment: TelaCliConfig['environment']
|
|
175
|
-
requesterApplicationId: string
|
|
176
|
-
targetApplicationId: string
|
|
177
|
-
}): void {
|
|
178
|
-
if (input.environment === 'custom') {
|
|
179
|
-
return
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (!isPlaceholderApplicationId(input.requesterApplicationId) && !isPlaceholderApplicationId(input.targetApplicationId)) {
|
|
183
|
-
return
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
throw createUserFacingError({
|
|
187
|
-
code: 'setup_preset_incomplete',
|
|
188
|
-
summary: `The ${input.environment} auth preset is missing the real Tela CLI application IDs.`,
|
|
189
|
-
fixes: [
|
|
190
|
-
'Update the production/staging requester and target application IDs in `src/config.ts`, or use the custom setup target.',
|
|
191
|
-
],
|
|
192
|
-
})
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export async function authenticateWithDeviceFlow(
|
|
196
|
-
input: Pick<TelaCliConfig, 'environment' | 'authApiUrl' | 'authDashboardUrl' | 'requesterApplicationId' | 'targetApplicationId'>,
|
|
197
|
-
options: AuthenticateWithDeviceFlowOptions = {},
|
|
198
|
-
): Promise<TelaCliAuthState> {
|
|
199
|
-
ensurePresetApplicationIds(input)
|
|
200
|
-
|
|
201
|
-
const timeoutMs = options.timeoutMs ?? (15 * 60 * 1000)
|
|
202
|
-
const signal = AbortSignal.timeout(timeoutMs)
|
|
203
|
-
const createDeviceFlow = options.createDeviceFlow ?? defaultDeviceFlowFactory
|
|
204
|
-
const openBrowser = options.openBrowser ?? defaultOpenBrowser
|
|
205
|
-
const emit = (event: SetupAuthEvent) => {
|
|
206
|
-
options.onEvent?.(event)
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const flow = createDeviceFlow({
|
|
210
|
-
apiUrl: input.authApiUrl,
|
|
211
|
-
requesterApplicationId: input.requesterApplicationId,
|
|
212
|
-
targetApplicationId: input.targetApplicationId,
|
|
213
|
-
signal,
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
flow.on('userCode', async ({ userCode, verificationUriComplete, expiresIn }) => {
|
|
217
|
-
const verificationUrl = buildDashboardVerificationUrl(
|
|
218
|
-
input.authDashboardUrl,
|
|
219
|
-
userCode,
|
|
220
|
-
verificationUriComplete,
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
emit({
|
|
224
|
-
type: 'user_code',
|
|
225
|
-
title: 'Authentication',
|
|
226
|
-
message: `Open ${verificationUrl}\nUser code: ${userCode}\nCode expires in ${expiresIn}s`,
|
|
227
|
-
verificationUrl,
|
|
228
|
-
userCode,
|
|
229
|
-
expiresInSeconds: expiresIn,
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
const opened = await openBrowser(verificationUrl)
|
|
233
|
-
emit(opened
|
|
234
|
-
? {
|
|
235
|
-
type: 'browser_opened',
|
|
236
|
-
title: 'Authentication',
|
|
237
|
-
message: 'Opened the verification URL in your browser.',
|
|
238
|
-
verificationUrl,
|
|
239
|
-
}
|
|
240
|
-
: {
|
|
241
|
-
type: 'browser_open_failed',
|
|
242
|
-
title: 'Authentication',
|
|
243
|
-
message: 'Could not open the browser automatically. Open the verification URL manually.',
|
|
244
|
-
verificationUrl,
|
|
245
|
-
})
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
flow.on('pending', ({ attempt }) => {
|
|
249
|
-
emit({
|
|
250
|
-
type: 'message',
|
|
251
|
-
title: 'Authentication',
|
|
252
|
-
message: `[poll ${attempt}] Waiting for approval...`,
|
|
253
|
-
})
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
flow.on('slowDown', ({ newInterval }) => {
|
|
257
|
-
emit({
|
|
258
|
-
type: 'message',
|
|
259
|
-
title: 'Authentication',
|
|
260
|
-
message: `Server requested a slower polling interval (${newInterval}s).`,
|
|
261
|
-
})
|
|
262
|
-
})
|
|
263
|
-
|
|
264
|
-
flow.on('transientError', ({ attempt }) => {
|
|
265
|
-
emit({
|
|
266
|
-
type: 'message',
|
|
267
|
-
title: 'Authentication',
|
|
268
|
-
message: `[poll ${attempt}] Temporary auth server error. Retrying...`,
|
|
269
|
-
})
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
try {
|
|
273
|
-
const tokens = await flow.authenticate()
|
|
274
|
-
const auth = mapTokens(tokens)
|
|
275
|
-
emit({
|
|
276
|
-
type: 'success',
|
|
277
|
-
title: 'Authentication',
|
|
278
|
-
message: `Signed in as ${auth.user.email} for workspace ${auth.workspace.title}.`,
|
|
279
|
-
})
|
|
280
|
-
return auth
|
|
281
|
-
}
|
|
282
|
-
catch (error) {
|
|
283
|
-
if (error instanceof DeviceAccessDeniedError) {
|
|
284
|
-
throw createUserFacingError({
|
|
285
|
-
code: 'device_access_denied',
|
|
286
|
-
summary: 'The device authorization request was denied.',
|
|
287
|
-
fixes: ['Run `tela setup` again and approve the request in the browser.'],
|
|
288
|
-
})
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
if (error instanceof DeviceCodeExpiredError) {
|
|
292
|
-
throw createUserFacingError({
|
|
293
|
-
code: 'device_code_expired',
|
|
294
|
-
summary: 'The device authorization expired before approval completed.',
|
|
295
|
-
fixes: ['Run `tela setup` again and approve the request before the code expires.'],
|
|
296
|
-
})
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
if (error instanceof DeviceFlowAbortedError) {
|
|
300
|
-
throw createUserFacingError({
|
|
301
|
-
code: 'device_flow_timeout',
|
|
302
|
-
summary: `Timed out waiting for device authorization after ${Math.round(timeoutMs / 60_000)} minutes.`,
|
|
303
|
-
fixes: ['Run `tela setup` again and complete the browser approval before the timeout.'],
|
|
304
|
-
})
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
throw error
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
async function refreshAuthState(
|
|
312
|
-
config: TelaCliConfig,
|
|
313
|
-
options: BuildAgentApiHeadersOptions,
|
|
314
|
-
): Promise<TelaCliAuthState> {
|
|
315
|
-
if (!config.auth?.session.refreshToken) {
|
|
316
|
-
throw createAuthRequiredError('Tela CLI is not authenticated.')
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const createDeviceFlow = options.createDeviceFlow ?? defaultDeviceFlowFactory
|
|
320
|
-
const flow = createDeviceFlow({
|
|
321
|
-
apiUrl: config.authApiUrl,
|
|
322
|
-
requesterApplicationId: config.requesterApplicationId,
|
|
323
|
-
targetApplicationId: config.targetApplicationId,
|
|
324
|
-
signal: AbortSignal.timeout(10_000),
|
|
325
|
-
})
|
|
326
|
-
|
|
327
|
-
try {
|
|
328
|
-
const refreshed = await flow.refresh(config.auth.session.refreshToken)
|
|
329
|
-
const nextAuth = mapTokens(refreshed)
|
|
330
|
-
const nextConfig: TelaCliConfig = {
|
|
331
|
-
...config,
|
|
332
|
-
auth: nextAuth,
|
|
333
|
-
}
|
|
334
|
-
;(options.persistConfig ?? saveConfig)(nextConfig)
|
|
335
|
-
config.auth = nextAuth
|
|
336
|
-
return nextAuth
|
|
337
|
-
}
|
|
338
|
-
catch (error) {
|
|
339
|
-
if (error instanceof RefreshTokenExpiredError) {
|
|
340
|
-
;(options.clearAuth ?? clearSavedAuth)(config)
|
|
341
|
-
config.auth = null
|
|
342
|
-
throw createAuthRequiredError('The saved Tela session expired and must be reauthenticated.')
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
throw error
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
async function getValidAccessToken(
|
|
350
|
-
config: TelaCliConfig,
|
|
351
|
-
options: BuildAgentApiHeadersOptions = {},
|
|
352
|
-
): Promise<string> {
|
|
353
|
-
if (!config.auth?.session.accessToken) {
|
|
354
|
-
throw createAuthRequiredError('Tela CLI is not authenticated.')
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
const nowMs = options.nowMs ?? Date.now()
|
|
358
|
-
if (config.auth.session.expiresAt > nowMs + 30_000) {
|
|
359
|
-
return config.auth.session.accessToken
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const nextAuth = await refreshAuthState(config, options)
|
|
363
|
-
return nextAuth.session.accessToken
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
type DataTokenResponse = {
|
|
367
|
-
dataToken?: string
|
|
368
|
-
error?: string
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
async function exchangeDataToken(
|
|
372
|
-
config: TelaCliConfig,
|
|
373
|
-
accessToken: string,
|
|
374
|
-
fetchFn: FetchLike,
|
|
375
|
-
): Promise<string> {
|
|
376
|
-
const dataTokenUrl = new URL('/data-token', config.authApiUrl)
|
|
377
|
-
dataTokenUrl.searchParams.set('expiresAt', '1d')
|
|
378
|
-
|
|
379
|
-
const response = await fetchFn(dataTokenUrl.toString(), {
|
|
380
|
-
method: 'GET',
|
|
381
|
-
headers: {
|
|
382
|
-
Authorization: `Bearer ${accessToken}`,
|
|
383
|
-
Accept: 'application/json',
|
|
384
|
-
},
|
|
385
|
-
signal: AbortSignal.timeout(10_000),
|
|
386
|
-
})
|
|
387
|
-
|
|
388
|
-
const responseText = await response.text()
|
|
389
|
-
let payload: DataTokenResponse | null = null
|
|
390
|
-
try {
|
|
391
|
-
payload = responseText ? JSON.parse(responseText) as DataTokenResponse : null
|
|
392
|
-
}
|
|
393
|
-
catch {
|
|
394
|
-
payload = null
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
if (!response.ok) {
|
|
398
|
-
throw createUserFacingError({
|
|
399
|
-
code: 'data_token_exchange_failed',
|
|
400
|
-
summary: payload?.error ?? `Tela data-token exchange failed with status ${response.status}.`,
|
|
401
|
-
fixes: ['Run `tela setup` again if the saved session is stale.'],
|
|
402
|
-
evidence: responseText ? [responseText] : [],
|
|
403
|
-
})
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
const headerToken = response.headers.get('x-data-token')?.trim()
|
|
407
|
-
const dataToken = headerToken || payload?.dataToken?.trim()
|
|
408
|
-
if (!dataToken) {
|
|
409
|
-
throw createUserFacingError({
|
|
410
|
-
code: 'data_token_exchange_invalid',
|
|
411
|
-
summary: 'Tela data-token exchange returned an invalid response.',
|
|
412
|
-
fixes: ['Run `tela setup` again if the saved session is stale.'],
|
|
413
|
-
evidence: responseText ? [responseText] : [],
|
|
414
|
-
})
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
return dataToken
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
export async function buildAgentApiHeaders(
|
|
421
|
-
config: TelaCliConfig,
|
|
422
|
-
options: BuildAgentApiHeadersOptions = {},
|
|
423
|
-
): Promise<Record<string, string>> {
|
|
424
|
-
const fetchFn = options.fetchFn ?? fetch
|
|
425
|
-
const accessToken = await getValidAccessToken(config, options)
|
|
426
|
-
const dataToken = await exchangeDataToken(config, accessToken, fetchFn)
|
|
427
|
-
|
|
428
|
-
return {
|
|
429
|
-
'x-data-token': dataToken,
|
|
430
|
-
Accept: 'application/json',
|
|
431
|
-
}
|
|
432
|
-
}
|
package/src/claude.ts
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { commandExists, runCommand } from './process.ts'
|
|
2
|
-
|
|
3
|
-
export interface ClaudeCliVersion {
|
|
4
|
-
major: number
|
|
5
|
-
minor: number
|
|
6
|
-
patch: number
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface ClaudeCliInspection {
|
|
10
|
-
available: boolean
|
|
11
|
-
version?: string
|
|
12
|
-
parsedVersion?: ClaudeCliVersion
|
|
13
|
-
supportsProjectScopedRun: boolean
|
|
14
|
-
problem?: 'missing' | 'unparseable' | 'unsupported'
|
|
15
|
-
detail?: string
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export const MIN_CLAUDE_VERSION_FOR_PROJECT_SCOPED_RUN: ClaudeCliVersion = {
|
|
19
|
-
major: 2,
|
|
20
|
-
minor: 1,
|
|
21
|
-
patch: 108,
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function formatClaudeCliVersion(version: ClaudeCliVersion): string {
|
|
25
|
-
return `${version.major}.${version.minor}.${version.patch}`
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function compareClaudeCliVersions(left: ClaudeCliVersion, right: ClaudeCliVersion): number {
|
|
29
|
-
if (left.major !== right.major) {
|
|
30
|
-
return left.major - right.major
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (left.minor !== right.minor) {
|
|
34
|
-
return left.minor - right.minor
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return left.patch - right.patch
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function parseClaudeCliVersion(raw: string): ClaudeCliVersion | null {
|
|
41
|
-
const match = raw.match(/\b(\d+)\.(\d+)\.(\d+)\b/)
|
|
42
|
-
if (!match) {
|
|
43
|
-
return null
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
major: Number(match[1]),
|
|
48
|
-
minor: Number(match[2]),
|
|
49
|
-
patch: Number(match[3]),
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export async function inspectClaudeCli(options: {
|
|
54
|
-
commandExistsFn?: typeof commandExists
|
|
55
|
-
runCommandFn?: typeof runCommand
|
|
56
|
-
} = {}): Promise<ClaudeCliInspection> {
|
|
57
|
-
const commandExistsFn = options.commandExistsFn ?? commandExists
|
|
58
|
-
const runCommandFn = options.runCommandFn ?? runCommand
|
|
59
|
-
|
|
60
|
-
if (!await commandExistsFn('claude')) {
|
|
61
|
-
return {
|
|
62
|
-
available: false,
|
|
63
|
-
supportsProjectScopedRun: false,
|
|
64
|
-
problem: 'missing',
|
|
65
|
-
detail: '`claude` is not available on PATH.',
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const result = await runCommandFn(['claude', '--version'], { allowFailure: true })
|
|
70
|
-
const output = `${result.stdout}\n${result.stderr}`.trim()
|
|
71
|
-
const parsedVersion = parseClaudeCliVersion(output)
|
|
72
|
-
|
|
73
|
-
if (!parsedVersion) {
|
|
74
|
-
return {
|
|
75
|
-
available: true,
|
|
76
|
-
supportsProjectScopedRun: false,
|
|
77
|
-
problem: 'unparseable',
|
|
78
|
-
detail: output || 'Claude CLI version output was empty.',
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const version = formatClaudeCliVersion(parsedVersion)
|
|
83
|
-
if (compareClaudeCliVersions(parsedVersion, MIN_CLAUDE_VERSION_FOR_PROJECT_SCOPED_RUN) < 0) {
|
|
84
|
-
return {
|
|
85
|
-
available: true,
|
|
86
|
-
version,
|
|
87
|
-
parsedVersion,
|
|
88
|
-
supportsProjectScopedRun: false,
|
|
89
|
-
problem: 'unsupported',
|
|
90
|
-
detail: `Claude CLI ${version} is older than the required ${formatClaudeCliVersion(MIN_CLAUDE_VERSION_FOR_PROJECT_SCOPED_RUN)}.`,
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return {
|
|
95
|
-
available: true,
|
|
96
|
-
version,
|
|
97
|
-
parsedVersion,
|
|
98
|
-
supportsProjectScopedRun: true,
|
|
99
|
-
detail: `Claude CLI ${version} supports project-scoped runs.`,
|
|
100
|
-
}
|
|
101
|
-
}
|
package/src/commands/doctor.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { createDoctorReport, type DoctorReport } from '../diagnostics.ts'
|
|
2
|
-
import { nextActions, resultCard, section, table, type RenderOptions } from '../ui.ts'
|
|
3
|
-
|
|
4
|
-
const STATUS_LABEL: Record<'pass' | 'warn' | 'fail', string> = {
|
|
5
|
-
pass: 'PASS',
|
|
6
|
-
warn: 'WARN',
|
|
7
|
-
fail: 'FAIL',
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function renderDoctorReport(report: DoctorReport, options: RenderOptions = {}): string {
|
|
11
|
-
const rows = report.checks.map(check => [
|
|
12
|
-
check.group,
|
|
13
|
-
STATUS_LABEL[check.status],
|
|
14
|
-
check.summary,
|
|
15
|
-
])
|
|
16
|
-
|
|
17
|
-
const remediation = report.checks
|
|
18
|
-
.filter(check => check.status !== 'pass')
|
|
19
|
-
.map((check) => {
|
|
20
|
-
const fixes = check.fixes.map(fix => `- ${fix}`).join('\n')
|
|
21
|
-
const evidence = check.evidence?.length ? `\n\nEvidence\n${check.evidence.map(item => `- ${item}`).join('\n')}` : ''
|
|
22
|
-
return `${check.summary}\n${fixes}${evidence}`
|
|
23
|
-
})
|
|
24
|
-
.join('\n\n')
|
|
25
|
-
|
|
26
|
-
return resultCard('Tela doctor', [
|
|
27
|
-
section('Checks', table(['Group', 'Status', 'Summary'], rows, options), options),
|
|
28
|
-
remediation ? section('Remediation', remediation, options) : undefined,
|
|
29
|
-
nextActions(report.nextActions, options),
|
|
30
|
-
], options)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export async function getDoctorReport(cwd = process.cwd(), fetchFn: typeof fetch = fetch): Promise<DoctorReport> {
|
|
34
|
-
return await createDoctorReport(cwd, fetchFn)
|
|
35
|
-
}
|