@meistrari/tela-cli 0.1.1 → 0.3.0
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 +5 -6
- package/package.json +4 -2
- package/src/agent-api.ts +102 -2
- package/src/assets.d.ts +4 -0
- package/src/auth.ts +423 -7
- package/src/commands/feedback.ts +1 -1
- package/src/commands/metadata.ts +17 -6
- package/src/commands/status.ts +10 -1
- package/src/config.ts +522 -147
- package/src/diagnostics.ts +127 -5
- package/src/forgejo.ts +189 -0
- package/src/ink/assets/160092__jorickhoofd__dog-bark-1.mp3 +0 -0
- package/src/ink/bark.tsx +14 -62
- package/src/ink/components.tsx +20 -18
- package/src/ink/screens/setup/breadcrumb.tsx +23 -0
- package/src/ink/screens/setup/setup-screen.tsx +574 -0
- package/src/ink/screens/setup/step-frame.tsx +30 -0
- package/src/ink/screens/setup/steps/done-step.tsx +28 -0
- package/src/ink/screens/setup/steps/environment-step.tsx +22 -0
- package/src/ink/screens/setup/steps/organization-step.tsx +34 -0
- package/src/ink/screens/setup/steps/review-step.tsx +76 -0
- package/src/ink/screens/setup/steps/running-step.tsx +36 -0
- package/src/ink/screens/setup/steps/ssh-key-step.tsx +47 -0
- package/src/ink/screens/setup/steps/text-field-step.tsx +17 -0
- package/src/ink/screens/setup/steps.ts +145 -0
- package/src/ink/screens/setup/types.ts +63 -0
- package/src/program.ts +280 -124
- package/src/types.ts +64 -1
- package/src/ui.ts +2 -4
- package/src/ink/screens/setup-screen.tsx +0 -352
package/src/program.ts
CHANGED
|
@@ -2,11 +2,6 @@ import { existsSync, mkdirSync, statSync } from 'node:fs'
|
|
|
2
2
|
import { basename, join } from 'node:path'
|
|
3
3
|
import React from 'react'
|
|
4
4
|
import { Command } from 'commander'
|
|
5
|
-
import {
|
|
6
|
-
createInkBarkSession,
|
|
7
|
-
shouldShowBarkIntro,
|
|
8
|
-
type BarkIntroSession,
|
|
9
|
-
} from './ink/bark.tsx'
|
|
10
5
|
import {
|
|
11
6
|
FeedbackClaudeHandoffScreen,
|
|
12
7
|
FeedbackOutcomeScreen,
|
|
@@ -14,9 +9,11 @@ import {
|
|
|
14
9
|
} from './ink/screens/feedback-screen.tsx'
|
|
15
10
|
import { PushScreen } from './ink/screens/push-screen.tsx'
|
|
16
11
|
import { RunChatHandoffScreen, RunScreen } from './ink/screens/run-screen.tsx'
|
|
17
|
-
import { SetupScreen, type SetupScreenResult } from './ink/screens/setup-screen.tsx'
|
|
12
|
+
import { SetupScreen, type SetupScreenResult } from './ink/screens/setup/setup-screen.tsx'
|
|
18
13
|
import { SyncScreen, type SyncProgressItem } from './ink/screens/sync-screen.tsx'
|
|
19
14
|
import { renderCommandScreen } from './ink/runtime.tsx'
|
|
15
|
+
import { listOrganizations } from './agent-api.ts'
|
|
16
|
+
import { authenticateWithDeviceFlow, type SetupAuthEvent } from './auth.ts'
|
|
20
17
|
import {
|
|
21
18
|
checkAgentApiReachability,
|
|
22
19
|
getAgentApiOriginCheckStatus,
|
|
@@ -69,26 +66,39 @@ import {
|
|
|
69
66
|
import { getDoctorReport, renderDoctorReport } from './commands/doctor.ts'
|
|
70
67
|
import { getStatusReport, renderStatusReport } from './commands/status.ts'
|
|
71
68
|
import {
|
|
72
|
-
|
|
73
|
-
DEFAULT_AGENT_API_URL,
|
|
69
|
+
DEVICE_FLOW_BROWSER_HELP,
|
|
74
70
|
ensureConfigInteractive,
|
|
71
|
+
ENVIRONMENT_PRESET_OPTIONS,
|
|
72
|
+
getPreferredForgejoOrganization,
|
|
75
73
|
getDefaultCloneRoot,
|
|
76
74
|
inspectSavedConfig,
|
|
77
|
-
|
|
75
|
+
redactSecret,
|
|
76
|
+
resolveEnvironmentConfigForPreset,
|
|
78
77
|
saveConfig,
|
|
79
78
|
validateAgentApiUrl,
|
|
80
|
-
|
|
79
|
+
validateApplicationId,
|
|
80
|
+
validateAuthApiUrl,
|
|
81
|
+
validateAuthDashboardUrl,
|
|
81
82
|
validateCloneRoot,
|
|
83
|
+
validateTelaApiUrl,
|
|
82
84
|
setupConfigInteractive,
|
|
83
85
|
type ConfigInspection,
|
|
86
|
+
type SetupConfigInput,
|
|
84
87
|
} from './config.ts'
|
|
85
88
|
import { findRepositoryRoot, getDiffStatAgainstHead } from './git.ts'
|
|
89
|
+
import {
|
|
90
|
+
ensureForgejoUserAccess,
|
|
91
|
+
listDiscoveredSshPublicKeys,
|
|
92
|
+
type SelectedSshPublicKey,
|
|
93
|
+
} from './forgejo.ts'
|
|
86
94
|
import { commandExists, runInteractiveCommand, runStreamingCommand } from './process.ts'
|
|
87
95
|
import { CommandRuntime } from './runtime.ts'
|
|
88
96
|
import type {
|
|
89
97
|
BatchSyncFailure,
|
|
90
98
|
SyncResult,
|
|
99
|
+
TelaCliAuthState,
|
|
91
100
|
TelaCliConfig,
|
|
101
|
+
TelaCliForgejoOrganization,
|
|
92
102
|
} from './types.ts'
|
|
93
103
|
import {
|
|
94
104
|
bulletList,
|
|
@@ -109,6 +119,7 @@ function promptValue<T>(value: T | symbol): T {
|
|
|
109
119
|
}
|
|
110
120
|
|
|
111
121
|
interface CliDeps {
|
|
122
|
+
authenticateWithDeviceFlow?: typeof authenticateWithDeviceFlow
|
|
112
123
|
prompts?: PromptModule
|
|
113
124
|
ensureConfigInteractive?: typeof ensureConfigInteractive
|
|
114
125
|
setupConfigInteractive?: typeof setupConfigInteractive
|
|
@@ -128,19 +139,20 @@ interface CliDeps {
|
|
|
128
139
|
getDiffStatAgainstHead?: typeof getDiffStatAgainstHead
|
|
129
140
|
getStatusReport?: typeof getStatusReport
|
|
130
141
|
getDoctorReport?: typeof getDoctorReport
|
|
131
|
-
|
|
142
|
+
renderCommandScreen?: typeof renderCommandScreen
|
|
132
143
|
commandExists?: typeof commandExists
|
|
133
144
|
runInteractiveCommand?: typeof runInteractiveCommand
|
|
134
145
|
runStreamingCommand?: typeof runStreamingCommand
|
|
135
146
|
fetchFn?: typeof fetch
|
|
136
147
|
writeStdout?: (message: string) => void
|
|
137
148
|
writeStderr?: (message: string) => void
|
|
149
|
+
stdinIsTTY?: boolean
|
|
150
|
+
stdoutIsTTY?: boolean
|
|
138
151
|
}
|
|
139
152
|
|
|
140
153
|
interface GlobalCliOptions {
|
|
141
154
|
json?: boolean
|
|
142
155
|
verbose?: boolean
|
|
143
|
-
silentBark?: boolean
|
|
144
156
|
}
|
|
145
157
|
|
|
146
158
|
interface SyncCommandOptions extends GlobalCliOptions {
|
|
@@ -190,16 +202,24 @@ function renderOptionsForStdout(): { decorate: boolean } {
|
|
|
190
202
|
return { decorate: Boolean(process.stdout.isTTY) }
|
|
191
203
|
}
|
|
192
204
|
|
|
193
|
-
function
|
|
194
|
-
return
|
|
205
|
+
function getStdinIsTTY(deps: CliDeps): boolean {
|
|
206
|
+
return deps.stdinIsTTY ?? Boolean(process.stdin.isTTY)
|
|
195
207
|
}
|
|
196
208
|
|
|
197
|
-
function
|
|
209
|
+
function getStdoutIsTTY(deps: CliDeps): boolean {
|
|
210
|
+
return deps.stdoutIsTTY ?? Boolean(process.stdout.isTTY)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function isInteractiveInkSession(deps: CliDeps, options: GlobalCliOptions = {}): boolean {
|
|
214
|
+
return !options.json && getStdoutIsTTY(deps) && getStdinIsTTY(deps)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function resolveRunSurface(deps: CliDeps, options: RunCommandOptions = {}): 'plain' | 'viewer' | 'json' {
|
|
198
218
|
if (options.json) {
|
|
199
219
|
return 'json'
|
|
200
220
|
}
|
|
201
221
|
|
|
202
|
-
if (options.plain || !isInteractiveInkSession(options)) {
|
|
222
|
+
if (options.plain || !isInteractiveInkSession(deps, options)) {
|
|
203
223
|
return 'plain'
|
|
204
224
|
}
|
|
205
225
|
|
|
@@ -230,6 +250,10 @@ function createRuntime(deps: CliDeps, options: GlobalCliOptions = {}): CommandRu
|
|
|
230
250
|
})
|
|
231
251
|
}
|
|
232
252
|
|
|
253
|
+
function getRenderCommandScreen(deps: CliDeps): typeof renderCommandScreen {
|
|
254
|
+
return deps.renderCommandScreen ?? renderCommandScreen
|
|
255
|
+
}
|
|
256
|
+
|
|
233
257
|
function renderExamples(command: CommandMetadata): string {
|
|
234
258
|
return command.examples
|
|
235
259
|
.map(example => `${example.command.padEnd(36, ' ')} ${example.description}`)
|
|
@@ -261,7 +285,6 @@ function renderRootHelp(program: Command): string {
|
|
|
261
285
|
section('Global flags', table(
|
|
262
286
|
['Flag', 'Description'],
|
|
263
287
|
[
|
|
264
|
-
['--silent-bark', 'Disable bark audio during interactive Ink startup'],
|
|
265
288
|
['--verbose', 'Include raw evidence for failures where available'],
|
|
266
289
|
['-h, --help', 'Show command help'],
|
|
267
290
|
],
|
|
@@ -312,25 +335,6 @@ function installCustomHelp(program: Command): void {
|
|
|
312
335
|
})
|
|
313
336
|
}
|
|
314
337
|
|
|
315
|
-
function createBarkSession(
|
|
316
|
-
deps: CliDeps,
|
|
317
|
-
options: GlobalCliOptions,
|
|
318
|
-
commandTitle: string,
|
|
319
|
-
): BarkIntroSession {
|
|
320
|
-
const createSession = deps.createInkBarkSession ?? createInkBarkSession
|
|
321
|
-
return createSession({
|
|
322
|
-
commandTitle,
|
|
323
|
-
enabled: shouldShowBarkIntro({
|
|
324
|
-
json: options.json,
|
|
325
|
-
stdinIsTTY: process.stdin.isTTY,
|
|
326
|
-
stdoutIsTTY: process.stdout.isTTY,
|
|
327
|
-
}),
|
|
328
|
-
silentBark: options.silentBark,
|
|
329
|
-
env: process.env,
|
|
330
|
-
platform: process.platform,
|
|
331
|
-
})
|
|
332
|
-
}
|
|
333
|
-
|
|
334
338
|
function emitBootstrap(runtime: CommandRuntime, title: string, summary: string): void {
|
|
335
339
|
runtime.emit(bootstrapPane(title, summary, { decorate: runtime.decorate }))
|
|
336
340
|
}
|
|
@@ -353,6 +357,7 @@ function summarizeConfigInspection(inspection: ConfigInspection): string {
|
|
|
353
357
|
return kvList([
|
|
354
358
|
{ label: 'Config path', value: inspection.path },
|
|
355
359
|
{ label: 'Status', value: 'missing' },
|
|
360
|
+
{ label: 'Environment', value: 'production' },
|
|
356
361
|
{ label: 'Clone root', value: getDefaultCloneRoot() },
|
|
357
362
|
])
|
|
358
363
|
}
|
|
@@ -367,9 +372,15 @@ function summarizeConfigInspection(inspection: ConfigInspection): string {
|
|
|
367
372
|
|
|
368
373
|
return kvList([
|
|
369
374
|
{ label: 'Config path', value: inspection.path },
|
|
375
|
+
{ label: 'Environment', value: inspection.config.environment },
|
|
370
376
|
{ label: 'Agent API origin', value: inspection.config.agentApiUrl },
|
|
377
|
+
{ label: 'Auth API URL', value: inspection.config.authApiUrl },
|
|
378
|
+
{ label: 'Tela API URL', value: inspection.config.telaApiUrl },
|
|
371
379
|
{ label: 'Clone root', value: inspection.config.cloneRoot },
|
|
372
|
-
{ label: '
|
|
380
|
+
{ label: 'User', value: inspection.config.auth?.user.email ?? '<signed out>' },
|
|
381
|
+
{ label: 'Workspace', value: inspection.config.auth?.workspace.title ?? '<signed out>' },
|
|
382
|
+
{ label: 'Organization', value: inspection.config.forgejoOrganization?.name ?? '<not selected>' },
|
|
383
|
+
{ label: 'Forgejo user', value: inspection.config.forgejoUserRegistered ? 'registered' : 'pending' },
|
|
373
384
|
])
|
|
374
385
|
}
|
|
375
386
|
|
|
@@ -377,7 +388,7 @@ function buildSetupChecksRows(checks: Array<{ label: string, status: string, det
|
|
|
377
388
|
return checks.map(check => [check.label, check.status, check.detail])
|
|
378
389
|
}
|
|
379
390
|
|
|
380
|
-
function ensureCloneRootReady(
|
|
391
|
+
function ensureCloneRootReady(cloneRoot: string): { created: boolean, warning?: string } {
|
|
381
392
|
if (!existsSync(cloneRoot)) {
|
|
382
393
|
return { created: false }
|
|
383
394
|
}
|
|
@@ -397,30 +408,51 @@ function ensureCloneRootReady(prompts: PromptModule, cloneRoot: string): { creat
|
|
|
397
408
|
}
|
|
398
409
|
|
|
399
410
|
async function executeSetupFlow(
|
|
400
|
-
input: {
|
|
401
|
-
agentApiUrl: string
|
|
402
|
-
cloneRoot: string
|
|
403
|
-
apiKey: string
|
|
411
|
+
input: SetupConfigInput & {
|
|
404
412
|
createCloneRoot: boolean
|
|
413
|
+
sshKey: SelectedSshPublicKey
|
|
414
|
+
auth: TelaCliAuthState
|
|
415
|
+
organization: TelaCliForgejoOrganization
|
|
405
416
|
},
|
|
406
417
|
deps: CliDeps = {},
|
|
418
|
+
onEvent?: (event: Pick<SetupAuthEvent, 'message' | 'title'>) => void,
|
|
407
419
|
): Promise<SetupScreenResult> {
|
|
408
420
|
const commandExistsFn = deps.commandExists ?? commandExists
|
|
409
421
|
const fetchFn = deps.fetchFn ?? fetch
|
|
410
|
-
|
|
411
|
-
const
|
|
422
|
+
|
|
423
|
+
const baseConfig: TelaCliConfig = {
|
|
424
|
+
environment: input.environment,
|
|
412
425
|
agentApiUrl: input.agentApiUrl,
|
|
426
|
+
authApiUrl: input.authApiUrl,
|
|
427
|
+
authDashboardUrl: input.authDashboardUrl,
|
|
428
|
+
telaApiUrl: input.telaApiUrl,
|
|
413
429
|
cloneRoot: input.cloneRoot,
|
|
414
|
-
|
|
415
|
-
|
|
430
|
+
requesterApplicationId: input.requesterApplicationId,
|
|
431
|
+
targetApplicationId: input.targetApplicationId,
|
|
432
|
+
auth: input.auth,
|
|
433
|
+
forgejoOrganization: input.organization,
|
|
434
|
+
forgejoUserRegistered: false,
|
|
435
|
+
}
|
|
436
|
+
const forgejoProvision = await ensureForgejoUserAccess({
|
|
437
|
+
config: baseConfig,
|
|
438
|
+
organization: input.organization,
|
|
439
|
+
sshKey: input.sshKey,
|
|
440
|
+
fetchFn,
|
|
441
|
+
onProgress: (message, title) => {
|
|
442
|
+
onEvent?.({
|
|
443
|
+
title,
|
|
444
|
+
message,
|
|
445
|
+
})
|
|
446
|
+
},
|
|
447
|
+
saveConfig,
|
|
448
|
+
})
|
|
449
|
+
const config = forgejoProvision.config
|
|
416
450
|
|
|
417
451
|
const cloneRootExists = existsSync(config.cloneRoot)
|
|
418
452
|
if (!cloneRootExists && input.createCloneRoot) {
|
|
419
453
|
mkdirSync(config.cloneRoot, { recursive: true, mode: 0o755 })
|
|
420
454
|
}
|
|
421
455
|
|
|
422
|
-
saveConfig(config)
|
|
423
|
-
|
|
424
456
|
const [gitAvailable, claudeAvailable, reachability] = await Promise.all([
|
|
425
457
|
commandExistsFn('git'),
|
|
426
458
|
commandExistsFn('claude'),
|
|
@@ -434,6 +466,9 @@ async function executeSetupFlow(
|
|
|
434
466
|
getAgentApiOriginWarning(reachability),
|
|
435
467
|
].filter((warning): warning is string => Boolean(warning))
|
|
436
468
|
const checks = [
|
|
469
|
+
{ label: 'auth', status: 'authenticated', detail: `${input.auth.user.email} @ ${input.auth.workspace.title}` },
|
|
470
|
+
{ label: 'organization', status: 'selected', detail: input.organization.name },
|
|
471
|
+
{ label: 'forgejo-user', status: forgejoProvision.response.userCreated ? 'created' : 'ensured', detail: forgejoProvision.detail },
|
|
437
472
|
{ label: 'clone-root', status: existsSync(config.cloneRoot) ? 'ready' : 'missing', detail: config.cloneRoot },
|
|
438
473
|
{ label: 'git', status: gitAvailable ? 'available' : 'missing', detail: gitAvailable ? 'ready' : 'install git' },
|
|
439
474
|
{ label: 'claude', status: claudeAvailable ? 'available' : 'missing', detail: claudeAvailable ? 'ready' : 'install Claude CLI' },
|
|
@@ -445,7 +480,7 @@ async function executeSetupFlow(
|
|
|
445
480
|
config,
|
|
446
481
|
warnings,
|
|
447
482
|
nextActions: [
|
|
448
|
-
'Run `tela status` to review the saved
|
|
483
|
+
'Run `tela status` to review the saved workspace session.',
|
|
449
484
|
existsSync(config.cloneRoot)
|
|
450
485
|
? 'Run `tela sync <repo>` or `tela sync --current` when you need a checkout.'
|
|
451
486
|
: 'Create the clone root or rerun setup before the first repository sync.',
|
|
@@ -457,25 +492,56 @@ async function executeSetupFlow(
|
|
|
457
492
|
}
|
|
458
493
|
}
|
|
459
494
|
|
|
495
|
+
function createMissingSetupOrganizationsError(workspaceTitle: string): ReturnType<typeof createUserFacingError> {
|
|
496
|
+
return createUserFacingError({
|
|
497
|
+
code: 'workspace_organization_required',
|
|
498
|
+
summary: 'No Forgejo organizations are registered for this workspace.',
|
|
499
|
+
details: `Tela authenticated successfully for ${workspaceTitle}, but the workspace does not have any registered organizations yet.`,
|
|
500
|
+
fixes: [
|
|
501
|
+
'Ask the Tela team to register at least one organization for this workspace, then rerun `tela setup`.',
|
|
502
|
+
],
|
|
503
|
+
})
|
|
504
|
+
}
|
|
505
|
+
|
|
460
506
|
async function runSetupInk(
|
|
461
507
|
deps: CliDeps = {},
|
|
462
|
-
options: GlobalCliOptions = {},
|
|
463
|
-
barkSession = createBarkSession(deps, options, 'Setup'),
|
|
464
508
|
): Promise<SetupScreenResult> {
|
|
509
|
+
const renderScreen = getRenderCommandScreen(deps)
|
|
465
510
|
const inspection = (deps.inspectSavedConfig ?? inspectSavedConfig)()
|
|
466
|
-
|
|
467
|
-
|
|
511
|
+
const defaultEnvironment = inspection.config?.environment ?? 'production'
|
|
512
|
+
const defaultEnvironmentConfig = defaultEnvironment === 'custom'
|
|
513
|
+
? {
|
|
514
|
+
environment: 'custom' as const,
|
|
515
|
+
agentApiUrl: inspection.config?.agentApiUrl ?? '',
|
|
516
|
+
authApiUrl: inspection.config?.authApiUrl ?? '',
|
|
517
|
+
authDashboardUrl: inspection.config?.authDashboardUrl ?? '',
|
|
518
|
+
telaApiUrl: inspection.config?.telaApiUrl ?? '',
|
|
519
|
+
requesterApplicationId: inspection.config?.requesterApplicationId ?? '',
|
|
520
|
+
targetApplicationId: inspection.config?.targetApplicationId ?? '',
|
|
521
|
+
}
|
|
522
|
+
: resolveEnvironmentConfigForPreset(defaultEnvironment)
|
|
523
|
+
return await renderScreen<SetupScreenResult>((controller) => React.createElement(SetupScreen, {
|
|
468
524
|
controller,
|
|
469
525
|
currentConfigSummary: summarizeConfigInspection(inspection).split('\n'),
|
|
470
526
|
defaults: {
|
|
471
|
-
|
|
527
|
+
environment: defaultEnvironment,
|
|
528
|
+
agentApiUrl: inspection.config?.agentApiUrl ?? defaultEnvironmentConfig.agentApiUrl,
|
|
529
|
+
authApiUrl: inspection.config?.authApiUrl ?? defaultEnvironmentConfig.authApiUrl,
|
|
530
|
+
authDashboardUrl: inspection.config?.authDashboardUrl ?? defaultEnvironmentConfig.authDashboardUrl,
|
|
531
|
+
telaApiUrl: inspection.config?.telaApiUrl ?? defaultEnvironmentConfig.telaApiUrl,
|
|
532
|
+
requesterApplicationId: inspection.config?.requesterApplicationId ?? defaultEnvironmentConfig.requesterApplicationId,
|
|
533
|
+
targetApplicationId: inspection.config?.targetApplicationId ?? defaultEnvironmentConfig.targetApplicationId,
|
|
472
534
|
cloneRoot: inspection.config?.cloneRoot ?? getDefaultCloneRoot(),
|
|
473
|
-
|
|
535
|
+
forgejoOrganization: inspection.config?.forgejoOrganization ?? null,
|
|
536
|
+
forgejoUserRegistered: inspection.config?.forgejoUserRegistered ?? false,
|
|
474
537
|
},
|
|
475
|
-
|
|
538
|
+
sshPublicKeys: listDiscoveredSshPublicKeys(),
|
|
476
539
|
validateAgentApiUrl,
|
|
540
|
+
validateAuthApiUrl,
|
|
541
|
+
validateAuthDashboardUrl,
|
|
542
|
+
validateTelaApiUrl,
|
|
543
|
+
validateApplicationId,
|
|
477
544
|
validateCloneRoot,
|
|
478
|
-
validateApiKey,
|
|
479
545
|
inspectCloneRoot: (path: string) => {
|
|
480
546
|
if (!existsSync(path)) {
|
|
481
547
|
return { exists: false, isDirectory: false }
|
|
@@ -486,42 +552,58 @@ async function runSetupInk(
|
|
|
486
552
|
isDirectory: statSync(path).isDirectory(),
|
|
487
553
|
}
|
|
488
554
|
},
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
555
|
+
authenticate: async (input, onEvent) => await (deps.authenticateWithDeviceFlow ?? authenticateWithDeviceFlow)({
|
|
556
|
+
environment: input.environment,
|
|
557
|
+
authApiUrl: input.authApiUrl,
|
|
558
|
+
authDashboardUrl: input.authDashboardUrl,
|
|
559
|
+
requesterApplicationId: input.requesterApplicationId,
|
|
560
|
+
targetApplicationId: input.targetApplicationId,
|
|
561
|
+
}, {
|
|
562
|
+
onEvent,
|
|
563
|
+
}),
|
|
564
|
+
resolveOrganizations: async (input) => {
|
|
565
|
+
const config: TelaCliConfig = {
|
|
566
|
+
environment: input.environment,
|
|
567
|
+
agentApiUrl: input.agentApiUrl,
|
|
568
|
+
authApiUrl: input.authApiUrl,
|
|
569
|
+
authDashboardUrl: input.authDashboardUrl,
|
|
570
|
+
telaApiUrl: input.telaApiUrl,
|
|
571
|
+
cloneRoot: input.cloneRoot,
|
|
572
|
+
requesterApplicationId: input.requesterApplicationId,
|
|
573
|
+
targetApplicationId: input.targetApplicationId,
|
|
574
|
+
auth: input.auth,
|
|
575
|
+
forgejoOrganization: null,
|
|
576
|
+
forgejoUserRegistered: false,
|
|
577
|
+
}
|
|
578
|
+
const organizations = await listOrganizations(config, deps.fetchFn ?? fetch)
|
|
579
|
+
if (organizations.length === 0) {
|
|
580
|
+
throw createMissingSetupOrganizationsError(input.auth.workspace.title)
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
return {
|
|
584
|
+
organizations,
|
|
585
|
+
preferredOrganization: getPreferredForgejoOrganization(
|
|
586
|
+
inspection.config ?? null,
|
|
587
|
+
input.auth,
|
|
588
|
+
organizations,
|
|
589
|
+
),
|
|
590
|
+
}
|
|
591
|
+
},
|
|
592
|
+
execute: async (input, onEvent) => await executeSetupFlow(input, deps, onEvent),
|
|
495
593
|
}))
|
|
496
594
|
}
|
|
497
595
|
|
|
498
596
|
async function ensureConfigForCommand(
|
|
499
597
|
deps: CliDeps,
|
|
500
|
-
options: GlobalCliOptions,
|
|
501
|
-
barkSession?: BarkIntroSession,
|
|
502
598
|
): Promise<TelaCliConfig> {
|
|
503
599
|
const inspection = (deps.inspectSavedConfig ?? inspectSavedConfig)()
|
|
504
|
-
if (inspection.valid && inspection.config) {
|
|
600
|
+
if (inspection.valid && inspection.config?.auth) {
|
|
505
601
|
return inspection.config
|
|
506
602
|
}
|
|
507
603
|
|
|
508
|
-
if (isInteractiveInkSession(options)) {
|
|
509
|
-
const setupResult = await runSetupInk(
|
|
510
|
-
deps,
|
|
511
|
-
options,
|
|
512
|
-
barkSession ?? createBarkSession(deps, options, 'Setup'),
|
|
513
|
-
)
|
|
514
|
-
return setupResult.config
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
if (deps.prompts) {
|
|
518
|
-
const ensureConfig = deps.ensureConfigInteractive ?? ensureConfigInteractive
|
|
519
|
-
return await ensureConfig({ prompts: deps.prompts })
|
|
520
|
-
}
|
|
521
|
-
|
|
522
604
|
throw createUserFacingError({
|
|
523
605
|
code: 'config_required',
|
|
524
|
-
summary: 'Tela CLI is not configured.',
|
|
606
|
+
summary: inspection.valid ? 'Tela CLI is signed out.' : 'Tela CLI is not configured.',
|
|
525
607
|
fixes: [
|
|
526
608
|
'Run `tela setup` in an interactive terminal first.',
|
|
527
609
|
],
|
|
@@ -532,8 +614,8 @@ export async function runSetup(
|
|
|
532
614
|
deps: CliDeps = {},
|
|
533
615
|
options: GlobalCliOptions = {},
|
|
534
616
|
): Promise<SetupCommandResult> {
|
|
535
|
-
if (isInteractiveInkSession(options)) {
|
|
536
|
-
return await runSetupInk(deps
|
|
617
|
+
if (isInteractiveInkSession(deps, options)) {
|
|
618
|
+
return await runSetupInk(deps)
|
|
537
619
|
}
|
|
538
620
|
|
|
539
621
|
const prompts = getPromptModule(deps)
|
|
@@ -542,17 +624,20 @@ export async function runSetup(
|
|
|
542
624
|
const inspect = deps.inspectSavedConfig ?? inspectSavedConfig
|
|
543
625
|
const commandExistsFn = deps.commandExists ?? commandExists
|
|
544
626
|
const fetchFn = deps.fetchFn ?? fetch
|
|
627
|
+
const authenticate = deps.authenticateWithDeviceFlow ?? authenticateWithDeviceFlow
|
|
545
628
|
const inspection = inspect()
|
|
546
629
|
|
|
547
630
|
prompts.intro('Tela CLI setup')
|
|
548
631
|
emitBootstrap(runtime, 'Tela setup', 'Bootstrap the local Tela environment before the first sync or push.')
|
|
549
632
|
runtime.emit(resultCard('Setup preflight', [
|
|
550
633
|
section('Current config', summarizeConfigInspection(inspection), { decorate: runtime.decorate }),
|
|
551
|
-
section('
|
|
634
|
+
section('Authentication', DEVICE_FLOW_BROWSER_HELP, { decorate: runtime.decorate }),
|
|
552
635
|
section('What setup updates', bulletList([
|
|
553
|
-
'
|
|
636
|
+
'Tela environment and service endpoints',
|
|
554
637
|
'Clone root directory',
|
|
555
|
-
'
|
|
638
|
+
'Workspace-backed Tela session',
|
|
639
|
+
'Forgejo organization selection for the workspace',
|
|
640
|
+
'Forgejo user access with your SSH public key',
|
|
556
641
|
]), { decorate: runtime.decorate }),
|
|
557
642
|
], { decorate: runtime.decorate }))
|
|
558
643
|
|
|
@@ -560,11 +645,22 @@ export async function runSetup(
|
|
|
560
645
|
prompts,
|
|
561
646
|
existingConfig: inspection.config ?? null,
|
|
562
647
|
skipLifecycle: true,
|
|
648
|
+
authenticate: async (input, notify) => {
|
|
649
|
+
return await authenticate({
|
|
650
|
+
environment: input.environment,
|
|
651
|
+
authApiUrl: input.authApiUrl,
|
|
652
|
+
authDashboardUrl: input.authDashboardUrl,
|
|
653
|
+
requesterApplicationId: input.requesterApplicationId,
|
|
654
|
+
targetApplicationId: input.targetApplicationId,
|
|
655
|
+
}, {
|
|
656
|
+
onEvent: event => notify(event.message, event.title),
|
|
657
|
+
})
|
|
658
|
+
},
|
|
563
659
|
})
|
|
564
660
|
|
|
565
661
|
let cloneRootCreated = false
|
|
566
662
|
let cloneRootWarning: string | undefined
|
|
567
|
-
const cloneRootStatus = ensureCloneRootReady(
|
|
663
|
+
const cloneRootStatus = ensureCloneRootReady(config.cloneRoot)
|
|
568
664
|
cloneRootWarning = cloneRootStatus.warning
|
|
569
665
|
|
|
570
666
|
if (!existsSync(config.cloneRoot)) {
|
|
@@ -599,7 +695,7 @@ export async function runSetup(
|
|
|
599
695
|
].filter((warning): warning is string => Boolean(warning))
|
|
600
696
|
|
|
601
697
|
const nextSetupActions = [
|
|
602
|
-
'Run `tela status` to review the saved
|
|
698
|
+
'Run `tela status` to review the saved workspace session.',
|
|
603
699
|
cloneRootCreated
|
|
604
700
|
? 'Run `tela sync <repo>` to clone a repository into the new clone root.'
|
|
605
701
|
: 'Run `tela sync <repo>` or `tela sync --current` when you need a checkout.',
|
|
@@ -610,14 +706,24 @@ export async function runSetup(
|
|
|
610
706
|
|
|
611
707
|
runtime.emit(resultCard('Setup complete', [
|
|
612
708
|
section('Saved config', kvList([
|
|
709
|
+
{ label: 'Environment', value: config.environment },
|
|
613
710
|
{ label: 'Agent API origin', value: config.agentApiUrl },
|
|
711
|
+
{ label: 'Auth API URL', value: config.authApiUrl },
|
|
712
|
+
{ label: 'Tela API URL', value: config.telaApiUrl },
|
|
614
713
|
{ label: 'Clone root', value: config.cloneRoot },
|
|
615
|
-
{ label: '
|
|
714
|
+
{ label: 'User', value: config.auth?.user.email ?? '<missing>' },
|
|
715
|
+
{ label: 'Workspace', value: config.auth?.workspace.title ?? '<missing>' },
|
|
716
|
+
{ label: 'Organization', value: config.forgejoOrganization?.name ?? '<missing>' },
|
|
717
|
+
{ label: 'Forgejo user', value: config.forgejoUserRegistered ? 'registered' : 'pending' },
|
|
718
|
+
{ label: 'Session', value: config.auth ? `${redactSecret(config.auth.session.accessToken)} (expires ${new Date(config.auth.session.expiresAt).toISOString()})` : '<missing>' },
|
|
616
719
|
]), { decorate: runtime.decorate }),
|
|
617
|
-
section('
|
|
720
|
+
section('Authentication', DEVICE_FLOW_BROWSER_HELP, { decorate: runtime.decorate }),
|
|
618
721
|
section('Checks', table(
|
|
619
722
|
['Check', 'Status', 'Detail'],
|
|
620
723
|
buildSetupChecksRows([
|
|
724
|
+
{ label: 'auth', status: 'authenticated', detail: `${config.auth?.user.email ?? '<missing>'} @ ${config.auth?.workspace.title ?? '<missing>'}` },
|
|
725
|
+
{ label: 'organization', status: config.forgejoOrganization ? 'selected' : 'missing', detail: config.forgejoOrganization?.name ?? 'not selected' },
|
|
726
|
+
{ label: 'forgejo-user', status: config.forgejoUserRegistered ? 'registered' : 'pending', detail: config.forgejoUserRegistered ? 'SSH-key provisioning completed.' : 'SSH-key provisioning is pending.' },
|
|
621
727
|
{ label: 'clone-root', status: cloneRootCreated ? 'created' : (existsSync(config.cloneRoot) ? 'ready' : 'missing'), detail: config.cloneRoot },
|
|
622
728
|
{ label: 'git', status: gitAvailable ? 'available' : 'missing', detail: gitAvailable ? 'ready' : 'install git' },
|
|
623
729
|
{ label: 'claude', status: claudeAvailable ? 'available' : 'missing', detail: claudeAvailable ? 'ready' : 'install Claude CLI' },
|
|
@@ -841,8 +947,7 @@ async function runSyncInk(
|
|
|
841
947
|
deps: CliDeps,
|
|
842
948
|
options: SyncCommandOptions,
|
|
843
949
|
): Promise<SyncCommandResult> {
|
|
844
|
-
const
|
|
845
|
-
await barkSession.playOnce()
|
|
950
|
+
const renderScreen = getRenderCommandScreen(deps)
|
|
846
951
|
const findRepo = deps.findRepositoryRoot ?? findRepositoryRoot
|
|
847
952
|
const listRepositories = deps.listCloneRootRepositories ?? listCloneRootRepositories
|
|
848
953
|
const syncAll = deps.syncAllRepositories ?? syncAllRepositories
|
|
@@ -852,10 +957,10 @@ async function runSyncInk(
|
|
|
852
957
|
const mode = resolveSyncMode(repository, options, repoRoot)
|
|
853
958
|
const config = mode === 'current'
|
|
854
959
|
? undefined
|
|
855
|
-
: await ensureConfigForCommand(deps
|
|
960
|
+
: await ensureConfigForCommand(deps)
|
|
856
961
|
const availableRepositories = config ? listRepositories(config.cloneRoot) : undefined
|
|
857
962
|
|
|
858
|
-
return await
|
|
963
|
+
return await renderScreen<SyncCommandResult>((controller) => React.createElement(SyncScreen, {
|
|
859
964
|
controller,
|
|
860
965
|
mode,
|
|
861
966
|
dryRun: options.dryRun,
|
|
@@ -942,7 +1047,7 @@ export async function runSync(
|
|
|
942
1047
|
): Promise<SyncCommandResult> {
|
|
943
1048
|
validateSyncOptions(repository, options)
|
|
944
1049
|
|
|
945
|
-
if (isInteractiveInkSession(options)) {
|
|
1050
|
+
if (isInteractiveInkSession(deps, options)) {
|
|
946
1051
|
return await runSyncInk(repository, deps, options)
|
|
947
1052
|
}
|
|
948
1053
|
|
|
@@ -959,7 +1064,7 @@ export async function runSync(
|
|
|
959
1064
|
|
|
960
1065
|
const repoRoot = await findRepo(process.cwd())
|
|
961
1066
|
const config = options.all || !repoRoot || Boolean(repository)
|
|
962
|
-
? await ensureConfig(
|
|
1067
|
+
? await ensureConfig()
|
|
963
1068
|
: undefined
|
|
964
1069
|
|
|
965
1070
|
if (options.all) {
|
|
@@ -1170,8 +1275,7 @@ async function runPushInk(
|
|
|
1170
1275
|
deps: CliDeps,
|
|
1171
1276
|
options: PushCommandOptions,
|
|
1172
1277
|
): Promise<PushCommandResult> {
|
|
1173
|
-
const
|
|
1174
|
-
await barkSession.playOnce()
|
|
1278
|
+
const renderScreen = getRenderCommandScreen(deps)
|
|
1175
1279
|
const findRepo = deps.findRepositoryRoot ?? findRepositoryRoot
|
|
1176
1280
|
const preview = deps.previewPush ?? previewPush
|
|
1177
1281
|
const push = deps.pushRepository ?? pushRepository
|
|
@@ -1190,7 +1294,7 @@ async function runPushInk(
|
|
|
1190
1294
|
: undefined
|
|
1191
1295
|
const initialManualMessage = options.message?.trim() || undefined
|
|
1192
1296
|
|
|
1193
|
-
return await
|
|
1297
|
+
return await renderScreen<PushCommandResult>((controller) => React.createElement(PushScreen, {
|
|
1194
1298
|
controller,
|
|
1195
1299
|
initialMode,
|
|
1196
1300
|
initialManualMessage,
|
|
@@ -1241,7 +1345,7 @@ export async function runPush(
|
|
|
1241
1345
|
deps: CliDeps = {},
|
|
1242
1346
|
options: PushCommandOptions = {},
|
|
1243
1347
|
): Promise<PushCommandResult> {
|
|
1244
|
-
if (isInteractiveInkSession(options)) {
|
|
1348
|
+
if (isInteractiveInkSession(deps, options)) {
|
|
1245
1349
|
return await runPushInk(deps, options)
|
|
1246
1350
|
}
|
|
1247
1351
|
|
|
@@ -1446,7 +1550,7 @@ async function runFeedbackServiceFlow(
|
|
|
1446
1550
|
deps: CliDeps,
|
|
1447
1551
|
options: FeedbackCommandFlags & GlobalCliOptions,
|
|
1448
1552
|
): Promise<FeedbackRunResult> {
|
|
1449
|
-
const config = await ensureConfigForCommand(deps
|
|
1553
|
+
const config = await ensureConfigForCommand(deps)
|
|
1450
1554
|
const loadFeedbackDataFn = deps.loadFeedbackData ?? loadFeedbackData
|
|
1451
1555
|
const launchFeedbackClaudeFn = deps.launchFeedbackClaude ?? launchFeedbackClaude
|
|
1452
1556
|
const commandExistsFn = deps.commandExists ?? commandExists
|
|
@@ -1504,9 +1608,8 @@ async function runFeedbackInk(
|
|
|
1504
1608
|
deps: CliDeps,
|
|
1505
1609
|
options: FeedbackCommandFlags & GlobalCliOptions,
|
|
1506
1610
|
): Promise<FeedbackRunResult> {
|
|
1507
|
-
const
|
|
1508
|
-
await
|
|
1509
|
-
const config = await ensureConfigForCommand(deps, options, barkSession)
|
|
1611
|
+
const renderScreen = getRenderCommandScreen(deps)
|
|
1612
|
+
const config = await ensureConfigForCommand(deps)
|
|
1510
1613
|
const loadFeedbackDataFn = deps.loadFeedbackData ?? loadFeedbackData
|
|
1511
1614
|
const launchFeedbackClaudeFn = deps.launchFeedbackClaude ?? launchFeedbackClaude
|
|
1512
1615
|
const commandExistsFn = deps.commandExists ?? commandExists
|
|
@@ -1517,7 +1620,7 @@ async function runFeedbackInk(
|
|
|
1517
1620
|
config,
|
|
1518
1621
|
})
|
|
1519
1622
|
|
|
1520
|
-
const selection = await
|
|
1623
|
+
const selection = await renderScreen<{
|
|
1521
1624
|
selectedFeedbacks: FeedbackData['entries']
|
|
1522
1625
|
prompt?: string
|
|
1523
1626
|
launchedClaude: boolean
|
|
@@ -1545,7 +1648,7 @@ async function runFeedbackInk(
|
|
|
1545
1648
|
})
|
|
1546
1649
|
}
|
|
1547
1650
|
|
|
1548
|
-
await
|
|
1651
|
+
await renderScreen<void>((controller) => React.createElement(FeedbackClaudeHandoffScreen, {
|
|
1549
1652
|
controller,
|
|
1550
1653
|
repoRoot: data.repoRoot,
|
|
1551
1654
|
commitHash: data.commitHash,
|
|
@@ -1559,7 +1662,7 @@ async function runFeedbackInk(
|
|
|
1559
1662
|
|
|
1560
1663
|
const diffStat = await getDiffStatAgainstHeadFn(repoRoot)
|
|
1561
1664
|
|
|
1562
|
-
await
|
|
1665
|
+
await renderScreen<void>((controller) => React.createElement(FeedbackOutcomeScreen, {
|
|
1563
1666
|
controller,
|
|
1564
1667
|
commitHash: data.commitHash,
|
|
1565
1668
|
selectionMode,
|
|
@@ -1594,7 +1697,7 @@ export async function runFeedback(
|
|
|
1594
1697
|
})
|
|
1595
1698
|
}
|
|
1596
1699
|
|
|
1597
|
-
const interactive = isInteractiveInkSession(normalizedOptions)
|
|
1700
|
+
const interactive = isInteractiveInkSession(deps, normalizedOptions)
|
|
1598
1701
|
if (!interactive) {
|
|
1599
1702
|
emitBootstrap(runtime, 'Tela feedback', 'Review commit feedback and optionally hand selected fixes to Claude.')
|
|
1600
1703
|
}
|
|
@@ -1625,9 +1728,10 @@ export async function runAgent(
|
|
|
1625
1728
|
options: RunCommandOptions = {},
|
|
1626
1729
|
): Promise<RunCommandResult> {
|
|
1627
1730
|
const runtime = createRuntime(deps, options)
|
|
1731
|
+
const renderScreen = getRenderCommandScreen(deps)
|
|
1628
1732
|
const runAgentCommandFn = deps.runLocalAgentCommand ?? runLocalAgentCommand
|
|
1629
1733
|
const prepareRunFn = deps.prepareLocalAgentRun ?? prepareLocalAgentRun
|
|
1630
|
-
const surface = resolveRunSurface(options)
|
|
1734
|
+
const surface = resolveRunSurface(deps, options)
|
|
1631
1735
|
|
|
1632
1736
|
let result: RunCommandResult
|
|
1633
1737
|
|
|
@@ -1636,11 +1740,9 @@ export async function runAgent(
|
|
|
1636
1740
|
cwd: process.cwd(),
|
|
1637
1741
|
commandExists: deps.commandExists,
|
|
1638
1742
|
})
|
|
1639
|
-
const barkSession = createBarkSession(deps, options, 'Run')
|
|
1640
|
-
await barkSession.playOnce()
|
|
1641
1743
|
|
|
1642
1744
|
if (prepared.config.isMultiturn) {
|
|
1643
|
-
await
|
|
1745
|
+
await renderScreen<void>((controller) => React.createElement(RunChatHandoffScreen, {
|
|
1644
1746
|
controller,
|
|
1645
1747
|
snapshot: createInitialRunSnapshot(prepared, 'chat'),
|
|
1646
1748
|
}), {
|
|
@@ -1656,13 +1758,13 @@ export async function runAgent(
|
|
|
1656
1758
|
runStreamingCommand: deps.runStreamingCommand,
|
|
1657
1759
|
writeStdout: deps.writeStdout,
|
|
1658
1760
|
writeStderr: deps.writeStderr,
|
|
1659
|
-
stdinIsTTY:
|
|
1660
|
-
stdoutIsTTY:
|
|
1761
|
+
stdinIsTTY: getStdinIsTTY(deps),
|
|
1762
|
+
stdoutIsTTY: getStdoutIsTTY(deps),
|
|
1661
1763
|
verbose: options.verbose,
|
|
1662
1764
|
})
|
|
1663
1765
|
}
|
|
1664
1766
|
else {
|
|
1665
|
-
result = await
|
|
1767
|
+
result = await renderScreen<RunCommandResult>((controller) => React.createElement(RunScreen, {
|
|
1666
1768
|
controller,
|
|
1667
1769
|
initialSnapshot: createInitialRunSnapshot(prepared, 'one-shot'),
|
|
1668
1770
|
execute: async ({ onSnapshot, onControl }) => {
|
|
@@ -1677,8 +1779,8 @@ export async function runAgent(
|
|
|
1677
1779
|
writeStderr: deps.writeStderr,
|
|
1678
1780
|
onSnapshot,
|
|
1679
1781
|
onControl,
|
|
1680
|
-
stdinIsTTY:
|
|
1681
|
-
stdoutIsTTY:
|
|
1782
|
+
stdinIsTTY: getStdinIsTTY(deps),
|
|
1783
|
+
stdoutIsTTY: getStdoutIsTTY(deps),
|
|
1682
1784
|
verbose: options.verbose,
|
|
1683
1785
|
})
|
|
1684
1786
|
},
|
|
@@ -1698,8 +1800,8 @@ export async function runAgent(
|
|
|
1698
1800
|
runStreamingCommand: deps.runStreamingCommand,
|
|
1699
1801
|
writeStdout: deps.writeStdout,
|
|
1700
1802
|
writeStderr: deps.writeStderr,
|
|
1701
|
-
stdinIsTTY:
|
|
1702
|
-
stdoutIsTTY:
|
|
1803
|
+
stdinIsTTY: getStdinIsTTY(deps),
|
|
1804
|
+
stdoutIsTTY: getStdoutIsTTY(deps),
|
|
1703
1805
|
verbose: options.verbose,
|
|
1704
1806
|
})
|
|
1705
1807
|
}
|
|
@@ -1757,6 +1859,53 @@ export async function runDoctor(
|
|
|
1757
1859
|
return report
|
|
1758
1860
|
}
|
|
1759
1861
|
|
|
1862
|
+
export interface ListRepositoriesResult {
|
|
1863
|
+
cloneRoot: string
|
|
1864
|
+
repositories: string[]
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
export async function runListRepositories(
|
|
1868
|
+
deps: CliDeps = {},
|
|
1869
|
+
options: GlobalCliOptions = {},
|
|
1870
|
+
): Promise<ListRepositoriesResult> {
|
|
1871
|
+
const runtime = createRuntime(deps, options)
|
|
1872
|
+
const inspection = (deps.inspectSavedConfig ?? inspectSavedConfig)()
|
|
1873
|
+
const cloneRoot = inspection.config?.cloneRoot ?? getDefaultCloneRoot()
|
|
1874
|
+
const listRepos = deps.listCloneRootRepositories ?? listCloneRootRepositories
|
|
1875
|
+
const repositories = listRepos(cloneRoot)
|
|
1876
|
+
const result: ListRepositoriesResult = { cloneRoot, repositories }
|
|
1877
|
+
|
|
1878
|
+
if (options.json) {
|
|
1879
|
+
runtime.emitJson(result)
|
|
1880
|
+
return result
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
emitBootstrap(runtime, 'Tela CLI', 'Workspace repositories under the configured clone root.')
|
|
1884
|
+
const decorate = runtime.decorate
|
|
1885
|
+
const body = repositories.length > 0
|
|
1886
|
+
? section('Repositories', bulletList(repositories), { decorate })
|
|
1887
|
+
: section('Repositories', [
|
|
1888
|
+
`No repositories found under ${cloneRoot}.`,
|
|
1889
|
+
'',
|
|
1890
|
+
'Next steps:',
|
|
1891
|
+
bulletList([
|
|
1892
|
+
'Run `tela setup` to configure the clone root.',
|
|
1893
|
+
'Run `tela sync <repo>` to clone a workspace repository.',
|
|
1894
|
+
]),
|
|
1895
|
+
], { decorate })
|
|
1896
|
+
|
|
1897
|
+
runtime.emit(joinBlocks([
|
|
1898
|
+
body,
|
|
1899
|
+
section('Clone root', cloneRoot, { decorate }),
|
|
1900
|
+
nextActions([
|
|
1901
|
+
'Run `tela sync <repo>` to clone or refresh a repository.',
|
|
1902
|
+
'Run `tela --help` to see all available commands.',
|
|
1903
|
+
], { decorate }),
|
|
1904
|
+
]))
|
|
1905
|
+
|
|
1906
|
+
return result
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1760
1909
|
function addStandardReadOptions(command: Command): Command {
|
|
1761
1910
|
return command
|
|
1762
1911
|
.option('--json', 'emit machine-readable JSON')
|
|
@@ -1765,16 +1914,23 @@ function addStandardReadOptions(command: Command): Command {
|
|
|
1765
1914
|
export function createProgram(deps: CliDeps = {}): Command {
|
|
1766
1915
|
const program = new Command()
|
|
1767
1916
|
.name('tela')
|
|
1768
|
-
.description('Tela CLI workspace helper')
|
|
1917
|
+
.description('Tela CLI workspace and auth helper')
|
|
1769
1918
|
.showHelpAfterError()
|
|
1770
|
-
.option('--silent-bark', 'disable bark audio during interactive Ink startup')
|
|
1771
1919
|
.option('--verbose', 'include raw evidence on failures')
|
|
1772
1920
|
|
|
1773
1921
|
installCustomHelp(program)
|
|
1774
1922
|
|
|
1923
|
+
addStandardReadOptions(program).action(async (commandOptions: GlobalCliOptions) => {
|
|
1924
|
+
const globalOptions = program.opts<GlobalCliOptions>()
|
|
1925
|
+
await runListRepositories(deps, {
|
|
1926
|
+
...globalOptions,
|
|
1927
|
+
...commandOptions,
|
|
1928
|
+
})
|
|
1929
|
+
})
|
|
1930
|
+
|
|
1775
1931
|
program
|
|
1776
1932
|
.command('setup')
|
|
1777
|
-
.description('Configure Tela CLI settings and
|
|
1933
|
+
.description('Configure Tela CLI settings and complete device-flow authentication')
|
|
1778
1934
|
.action(async () => {
|
|
1779
1935
|
const globalOptions = program.opts<GlobalCliOptions>()
|
|
1780
1936
|
await runSetup(deps, globalOptions)
|
|
@@ -1842,7 +1998,7 @@ export function createProgram(deps: CliDeps = {}): Command {
|
|
|
1842
1998
|
addStandardReadOptions(
|
|
1843
1999
|
program
|
|
1844
2000
|
.command('status')
|
|
1845
|
-
.description('Show Tela config, repo state, tool availability, and Agent API
|
|
2001
|
+
.description('Show Tela config, session state, repo state, tool availability, and Agent API reachability'),
|
|
1846
2002
|
).action(async (commandOptions: StatusCommandOptions) => {
|
|
1847
2003
|
const globalOptions = program.opts<GlobalCliOptions>()
|
|
1848
2004
|
await runStatus(deps, {
|
|
@@ -1855,7 +2011,7 @@ export function createProgram(deps: CliDeps = {}): Command {
|
|
|
1855
2011
|
program
|
|
1856
2012
|
.command('doctor')
|
|
1857
2013
|
.alias('diag')
|
|
1858
|
-
.description('Diagnose config, tools, filesystem permissions, repo health, and Agent API
|
|
2014
|
+
.description('Diagnose config, auth session, tools, filesystem permissions, repo health, and Agent API reachability'),
|
|
1859
2015
|
).action(async (commandOptions: DoctorCommandOptions) => {
|
|
1860
2016
|
const globalOptions = program.opts<GlobalCliOptions>()
|
|
1861
2017
|
await runDoctor(deps, {
|