@meistrari/tela-cli 0.6.0 → 0.7.4

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.
Files changed (59) hide show
  1. package/README.md +56 -106
  2. package/dist/commands/auth.d.ts +2 -0
  3. package/dist/commands/context.d.ts +2 -0
  4. package/dist/commands/doctor-auth.d.ts +2 -0
  5. package/dist/commands/env.d.ts +2 -0
  6. package/dist/credentials.d.ts +2 -0
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.js +7027 -0
  9. package/dist/output.d.ts +13 -0
  10. package/dist/program.d.ts +2 -0
  11. package/package.json +17 -24
  12. package/scripts/install-skills.cjs +127 -0
  13. package/skills/tela-cli/SKILL.md +20 -0
  14. package/skills/tela-cli/references/agent.md +111 -0
  15. package/cli.ts +0 -25
  16. package/postinstall.sh +0 -19
  17. package/src/agent-api.ts +0 -300
  18. package/src/assets.d.ts +0 -4
  19. package/src/auth.ts +0 -432
  20. package/src/claude.ts +0 -101
  21. package/src/commands/doctor.ts +0 -35
  22. package/src/commands/feedback.ts +0 -701
  23. package/src/commands/metadata.ts +0 -242
  24. package/src/commands/push.ts +0 -208
  25. package/src/commands/run.ts +0 -1699
  26. package/src/commands/status.ts +0 -109
  27. package/src/commands/sync.ts +0 -245
  28. package/src/commit-message.ts +0 -69
  29. package/src/config.ts +0 -799
  30. package/src/diagnostics.ts +0 -813
  31. package/src/errors.ts +0 -122
  32. package/src/forgejo.ts +0 -233
  33. package/src/git.ts +0 -457
  34. package/src/ink/assets/160092__jorickhoofd__dog-bark-1.mp3 +0 -0
  35. package/src/ink/bark.tsx +0 -240
  36. package/src/ink/components.tsx +0 -177
  37. package/src/ink/runtime.tsx +0 -89
  38. package/src/ink/screens/feedback-screen.tsx +0 -394
  39. package/src/ink/screens/push-screen.tsx +0 -231
  40. package/src/ink/screens/run-screen.tsx +0 -291
  41. package/src/ink/screens/setup/breadcrumb.tsx +0 -23
  42. package/src/ink/screens/setup/setup-screen.tsx +0 -611
  43. package/src/ink/screens/setup/step-frame.tsx +0 -30
  44. package/src/ink/screens/setup/steps/done-step.tsx +0 -28
  45. package/src/ink/screens/setup/steps/environment-step.tsx +0 -22
  46. package/src/ink/screens/setup/steps/organization-step.tsx +0 -34
  47. package/src/ink/screens/setup/steps/review-step.tsx +0 -77
  48. package/src/ink/screens/setup/steps/running-step.tsx +0 -36
  49. package/src/ink/screens/setup/steps/ssh-key-step.tsx +0 -47
  50. package/src/ink/screens/setup/steps/text-field-step.tsx +0 -17
  51. package/src/ink/screens/setup/steps.ts +0 -156
  52. package/src/ink/screens/setup/types.ts +0 -65
  53. package/src/ink/screens/sync-screen.tsx +0 -282
  54. package/src/process.ts +0 -251
  55. package/src/program.ts +0 -2105
  56. package/src/runtime.ts +0 -147
  57. package/src/types.ts +0 -165
  58. package/src/ui.ts +0 -295
  59. 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
- }
@@ -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
- }