@meistrari/tela-cli 0.4.0 → 0.5.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 +23 -6
- package/package.json +1 -1
- package/src/agent-api.ts +76 -0
- package/src/claude.ts +101 -0
- package/src/commands/metadata.ts +23 -6
- package/src/commands/run.ts +116 -18
- package/src/commands/status.ts +14 -1
- package/src/config.ts +40 -0
- package/src/diagnostics.ts +122 -9
- package/src/forgejo.ts +44 -0
- package/src/git.ts +252 -45
- package/src/ink/screens/setup/setup-screen.tsx +37 -0
- package/src/ink/screens/setup/steps/review-step.tsx +1 -0
- package/src/ink/screens/setup/steps.ts +11 -0
- package/src/ink/screens/setup/types.ts +2 -0
- package/src/program.ts +95 -33
- package/src/types.ts +22 -0
package/src/program.ts
CHANGED
|
@@ -12,9 +12,9 @@ import { RunChatHandoffScreen, RunScreen } from './ink/screens/run-screen.tsx'
|
|
|
12
12
|
import { SetupScreen, type SetupScreenResult } from './ink/screens/setup/setup-screen.tsx'
|
|
13
13
|
import { SyncScreen, type SyncProgressItem } from './ink/screens/sync-screen.tsx'
|
|
14
14
|
import { renderCommandScreen } from './ink/runtime.tsx'
|
|
15
|
-
import { listOrganizations } from './agent-api.ts'
|
|
15
|
+
import { listAvailableRepositories, listOrganizations } from './agent-api.ts'
|
|
16
16
|
import { authenticateWithDeviceFlow, type SetupAuthEvent } from './auth.ts'
|
|
17
|
-
import { createUserFacingError } from './errors.ts'
|
|
17
|
+
import { createUserFacingError, toUserFacingError } from './errors.ts'
|
|
18
18
|
import {
|
|
19
19
|
getCommandMetadata,
|
|
20
20
|
listCommandMetadata,
|
|
@@ -82,6 +82,7 @@ import {
|
|
|
82
82
|
} from './config.ts'
|
|
83
83
|
import { findRepositoryRoot, getDiffStatAgainstHead } from './git.ts'
|
|
84
84
|
import {
|
|
85
|
+
createGitSshKey,
|
|
85
86
|
ensureForgejoUserAccess,
|
|
86
87
|
listDiscoveredSshPublicKeys,
|
|
87
88
|
type SelectedSshPublicKey,
|
|
@@ -89,6 +90,7 @@ import {
|
|
|
89
90
|
import { commandExists, runInteractiveCommand, runStreamingCommand } from './process.ts'
|
|
90
91
|
import { CommandRuntime } from './runtime.ts'
|
|
91
92
|
import type {
|
|
93
|
+
AvailableRepositorySummary,
|
|
92
94
|
BatchSyncFailure,
|
|
93
95
|
SyncResult,
|
|
94
96
|
TelaCliAuthState,
|
|
@@ -124,6 +126,7 @@ interface CliDeps {
|
|
|
124
126
|
syncAllRepositories?: typeof syncAllRepositories
|
|
125
127
|
syncCurrentRepository?: typeof syncCurrentRepository
|
|
126
128
|
listCloneRootRepositories?: typeof listCloneRootRepositories
|
|
129
|
+
listAvailableRepositories?: typeof listAvailableRepositories
|
|
127
130
|
previewPush?: typeof previewPush
|
|
128
131
|
pushRepository?: typeof pushRepository
|
|
129
132
|
runLocalAgentCommand?: typeof runLocalAgentCommand
|
|
@@ -265,11 +268,13 @@ function renderRootHelp(program: Command): string {
|
|
|
265
268
|
return joinBlocks([
|
|
266
269
|
bootstrapPane('Tela CLI', [
|
|
267
270
|
'One-shot workflows for setting up Tela, syncing repositories, running local agents, preparing pushes, and applying commit feedback.',
|
|
271
|
+
'Run `tela` with no subcommand to list workspace repositories available to sync.',
|
|
268
272
|
'',
|
|
269
273
|
'First successful path:',
|
|
270
274
|
stepList([
|
|
271
275
|
'tela setup',
|
|
272
276
|
'tela status',
|
|
277
|
+
'tela',
|
|
273
278
|
'tela sync <repo> or tela sync --current',
|
|
274
279
|
'tela run',
|
|
275
280
|
'tela push',
|
|
@@ -376,6 +381,7 @@ function summarizeConfigInspection(inspection: ConfigInspection): string {
|
|
|
376
381
|
{ label: 'Workspace', value: inspection.config.auth?.workspace.title ?? '<signed out>' },
|
|
377
382
|
{ label: 'Organization', value: inspection.config.forgejoOrganization?.name ?? '<not selected>' },
|
|
378
383
|
{ label: 'Forgejo user', value: inspection.config.forgejoUserRegistered ? 'registered' : 'pending' },
|
|
384
|
+
{ label: 'Git SSH key', value: inspection.config.gitSshKey ? `${inspection.config.gitSshKey.title} (${inspection.config.gitSshKey.privateKeyPath})` : '<missing>' },
|
|
379
385
|
])
|
|
380
386
|
}
|
|
381
387
|
|
|
@@ -406,6 +412,7 @@ async function executeSetupFlow(
|
|
|
406
412
|
input: SetupConfigInput & {
|
|
407
413
|
createCloneRoot: boolean
|
|
408
414
|
sshKey: SelectedSshPublicKey
|
|
415
|
+
sshPrivateKeyPath: string
|
|
409
416
|
auth: TelaCliAuthState
|
|
410
417
|
organization: TelaCliForgejoOrganization
|
|
411
418
|
},
|
|
@@ -427,6 +434,7 @@ async function executeSetupFlow(
|
|
|
427
434
|
auth: input.auth,
|
|
428
435
|
forgejoOrganization: input.organization,
|
|
429
436
|
forgejoUserRegistered: false,
|
|
437
|
+
gitSshKey: createGitSshKey(input.sshKey, input.sshPrivateKeyPath),
|
|
430
438
|
}
|
|
431
439
|
const forgejoProvision = await ensureForgejoUserAccess({
|
|
432
440
|
config: baseConfig,
|
|
@@ -567,6 +575,7 @@ async function runSetupInk(
|
|
|
567
575
|
auth: input.auth,
|
|
568
576
|
forgejoOrganization: null,
|
|
569
577
|
forgejoUserRegistered: false,
|
|
578
|
+
gitSshKey: inspection.config?.gitSshKey ?? null,
|
|
570
579
|
}
|
|
571
580
|
const organizations = await listOrganizations(config, deps.fetchFn ?? fetch)
|
|
572
581
|
if (organizations.length === 0) {
|
|
@@ -705,6 +714,7 @@ export async function runSetup(
|
|
|
705
714
|
{ label: 'Workspace', value: config.auth?.workspace.title ?? '<missing>' },
|
|
706
715
|
{ label: 'Organization', value: config.forgejoOrganization?.name ?? '<missing>' },
|
|
707
716
|
{ label: 'Forgejo user', value: config.forgejoUserRegistered ? 'registered' : 'pending' },
|
|
717
|
+
{ label: 'Git SSH key', value: config.gitSshKey ? `${config.gitSshKey.title} (${config.gitSshKey.privateKeyPath})` : '<missing>' },
|
|
708
718
|
{ label: 'Session', value: config.auth ? `${redactSecret(config.auth.session.accessToken)} (expires ${new Date(config.auth.session.expiresAt).toISOString()})` : '<missing>' },
|
|
709
719
|
]), { decorate: runtime.decorate }),
|
|
710
720
|
section('Authentication', DEVICE_FLOW_BROWSER_HELP, { decorate: runtime.decorate }),
|
|
@@ -714,6 +724,7 @@ export async function runSetup(
|
|
|
714
724
|
{ label: 'auth', status: 'authenticated', detail: `${config.auth?.user.email ?? '<missing>'} @ ${config.auth?.workspace.title ?? '<missing>'}` },
|
|
715
725
|
{ label: 'organization', status: config.forgejoOrganization ? 'selected' : 'missing', detail: config.forgejoOrganization?.name ?? 'not selected' },
|
|
716
726
|
{ label: 'forgejo-user', status: config.forgejoUserRegistered ? 'registered' : 'pending', detail: config.forgejoUserRegistered ? 'SSH-key provisioning completed.' : 'SSH-key provisioning is pending.' },
|
|
727
|
+
{ label: 'git-ssh-key', status: config.gitSshKey ? 'configured' : 'missing', detail: config.gitSshKey?.privateKeyPath ?? 'rerun setup to choose an SSH key' },
|
|
717
728
|
{ label: 'clone-root', status: cloneRootCreated ? 'created' : (existsSync(config.cloneRoot) ? 'ready' : 'missing'), detail: config.cloneRoot },
|
|
718
729
|
{ label: 'git', status: gitAvailable ? 'available' : 'missing', detail: gitAvailable ? 'ready' : 'install git' },
|
|
719
730
|
{ label: 'claude', status: claudeAvailable ? 'available' : 'missing', detail: claudeAvailable ? 'ready' : 'install Claude CLI' },
|
|
@@ -755,6 +766,26 @@ function syncCategoryFixes(category: SyncFailureCategory): string[] {
|
|
|
755
766
|
}
|
|
756
767
|
}
|
|
757
768
|
|
|
769
|
+
function buildSyncFailureError(error: unknown): ReturnType<typeof createUserFacingError> {
|
|
770
|
+
const category = categorizeSyncError(error)
|
|
771
|
+
const userError = toUserFacingError(error)
|
|
772
|
+
const stderrDetails = userError.evidence
|
|
773
|
+
.find(line => line.startsWith('stderr: '))
|
|
774
|
+
?.replace(/^stderr: /, '')
|
|
775
|
+
const stdoutDetails = userError.evidence
|
|
776
|
+
.find(line => line.startsWith('stdout: '))
|
|
777
|
+
?.replace(/^stdout: /, '')
|
|
778
|
+
|
|
779
|
+
return createUserFacingError({
|
|
780
|
+
code: `sync_${category}`,
|
|
781
|
+
summary: error instanceof Error ? error.message : String(error),
|
|
782
|
+
details: stderrDetails || stdoutDetails || userError.details,
|
|
783
|
+
fixes: syncCategoryFixes(category),
|
|
784
|
+
evidence: userError.evidence,
|
|
785
|
+
cause: error,
|
|
786
|
+
})
|
|
787
|
+
}
|
|
788
|
+
|
|
758
789
|
function renderSyncPlan(input: {
|
|
759
790
|
mode: SyncCommandResult['mode']
|
|
760
791
|
target: string
|
|
@@ -1018,13 +1049,7 @@ async function runSyncInk(
|
|
|
1018
1049
|
}
|
|
1019
1050
|
}
|
|
1020
1051
|
catch (error) {
|
|
1021
|
-
|
|
1022
|
-
throw createUserFacingError({
|
|
1023
|
-
code: `sync_${category}`,
|
|
1024
|
-
summary: error instanceof Error ? error.message : String(error),
|
|
1025
|
-
fixes: syncCategoryFixes(category),
|
|
1026
|
-
cause: error,
|
|
1027
|
-
})
|
|
1052
|
+
throw buildSyncFailureError(error)
|
|
1028
1053
|
}
|
|
1029
1054
|
},
|
|
1030
1055
|
}))
|
|
@@ -1196,13 +1221,7 @@ export async function runSync(
|
|
|
1196
1221
|
return result
|
|
1197
1222
|
}
|
|
1198
1223
|
catch (error) {
|
|
1199
|
-
|
|
1200
|
-
throw createUserFacingError({
|
|
1201
|
-
code: `sync_${category}`,
|
|
1202
|
-
summary: error instanceof Error ? error.message : String(error),
|
|
1203
|
-
fixes: syncCategoryFixes(category),
|
|
1204
|
-
cause: error,
|
|
1205
|
-
})
|
|
1224
|
+
throw buildSyncFailureError(error)
|
|
1206
1225
|
}
|
|
1207
1226
|
}
|
|
1208
1227
|
|
|
@@ -1850,8 +1869,10 @@ export async function runDoctor(
|
|
|
1850
1869
|
}
|
|
1851
1870
|
|
|
1852
1871
|
export interface ListRepositoriesResult {
|
|
1853
|
-
|
|
1854
|
-
repositories:
|
|
1872
|
+
success: boolean
|
|
1873
|
+
repositories: AvailableRepositorySummary[]
|
|
1874
|
+
message?: string
|
|
1875
|
+
nextActions?: string[]
|
|
1855
1876
|
}
|
|
1856
1877
|
|
|
1857
1878
|
export async function runListRepositories(
|
|
@@ -1860,35 +1881,76 @@ export async function runListRepositories(
|
|
|
1860
1881
|
): Promise<ListRepositoriesResult> {
|
|
1861
1882
|
const runtime = createRuntime(deps, options)
|
|
1862
1883
|
const inspection = (deps.inspectSavedConfig ?? inspectSavedConfig)()
|
|
1863
|
-
const
|
|
1864
|
-
const
|
|
1865
|
-
|
|
1866
|
-
|
|
1884
|
+
const decorate = runtime.decorate
|
|
1885
|
+
const setupRequiredNextActions = [
|
|
1886
|
+
'Run `tela setup` to configure Tela CLI and sign in.',
|
|
1887
|
+
'Run `tela --help` to see all available commands.',
|
|
1888
|
+
]
|
|
1889
|
+
const setupRequiredMessage = 'Setup and sign-in are required before Tela can list repositories available to sync.'
|
|
1890
|
+
|
|
1891
|
+
if (!inspection.valid || !inspection.config?.auth) {
|
|
1892
|
+
const result: ListRepositoriesResult = {
|
|
1893
|
+
success: false,
|
|
1894
|
+
repositories: [],
|
|
1895
|
+
message: setupRequiredMessage,
|
|
1896
|
+
nextActions: setupRequiredNextActions,
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
if (options.json) {
|
|
1900
|
+
runtime.emitJson(result)
|
|
1901
|
+
return result
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
emitBootstrap(runtime, 'Tela CLI', 'Workspace repositories available to sync.')
|
|
1905
|
+
runtime.emit(joinBlocks([
|
|
1906
|
+
section('Repositories', [
|
|
1907
|
+
setupRequiredMessage,
|
|
1908
|
+
'',
|
|
1909
|
+
'These repositories will appear here after setup is complete.',
|
|
1910
|
+
], { decorate }),
|
|
1911
|
+
nextActions(setupRequiredNextActions, { decorate }),
|
|
1912
|
+
]))
|
|
1913
|
+
|
|
1914
|
+
return result
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
const listRepos = deps.listAvailableRepositories ?? listAvailableRepositories
|
|
1918
|
+
const repositories = await listRepos(inspection.config, deps.fetchFn ?? fetch)
|
|
1919
|
+
const result: ListRepositoriesResult = {
|
|
1920
|
+
success: true,
|
|
1921
|
+
repositories,
|
|
1922
|
+
}
|
|
1867
1923
|
|
|
1868
1924
|
if (options.json) {
|
|
1869
1925
|
runtime.emitJson(result)
|
|
1870
1926
|
return result
|
|
1871
1927
|
}
|
|
1872
1928
|
|
|
1873
|
-
emitBootstrap(runtime, 'Tela CLI', 'Workspace repositories
|
|
1874
|
-
const decorate = runtime.decorate
|
|
1929
|
+
emitBootstrap(runtime, 'Tela CLI', 'Workspace repositories available to sync.')
|
|
1875
1930
|
const body = repositories.length > 0
|
|
1876
|
-
? section(
|
|
1931
|
+
? section(
|
|
1932
|
+
'Repositories',
|
|
1933
|
+
table(
|
|
1934
|
+
['Organization', 'Repository', 'Sync'],
|
|
1935
|
+
repositories.map(repository => [
|
|
1936
|
+
repository.organization.name,
|
|
1937
|
+
repository.agentRepo.repository,
|
|
1938
|
+
repository.syncStatus,
|
|
1939
|
+
]),
|
|
1940
|
+
{ decorate },
|
|
1941
|
+
),
|
|
1942
|
+
{ decorate },
|
|
1943
|
+
)
|
|
1877
1944
|
: section('Repositories', [
|
|
1878
|
-
|
|
1945
|
+
'No workspace repositories are currently registered for this workspace.',
|
|
1879
1946
|
'',
|
|
1880
|
-
'
|
|
1881
|
-
bulletList([
|
|
1882
|
-
'Run `tela setup` to configure the clone root.',
|
|
1883
|
-
'Run `tela sync <repo>` to clone a workspace repository.',
|
|
1884
|
-
]),
|
|
1947
|
+
'There is nothing to sync yet.',
|
|
1885
1948
|
], { decorate })
|
|
1886
1949
|
|
|
1887
1950
|
runtime.emit(joinBlocks([
|
|
1888
1951
|
body,
|
|
1889
|
-
section('Clone root', cloneRoot, { decorate }),
|
|
1890
1952
|
nextActions([
|
|
1891
|
-
'Run `tela sync <repo>` to clone or refresh
|
|
1953
|
+
'Run `tela sync <repo>` to clone or refresh an available repository.',
|
|
1892
1954
|
'Run `tela --help` to see all available commands.',
|
|
1893
1955
|
], { decorate }),
|
|
1894
1956
|
]))
|
package/src/types.ts
CHANGED
|
@@ -22,6 +22,12 @@ export interface TelaCliForgejoOrganization {
|
|
|
22
22
|
name: string
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
export interface TelaCliGitSshKey {
|
|
26
|
+
title: string
|
|
27
|
+
publicKeyPath: string
|
|
28
|
+
privateKeyPath: string
|
|
29
|
+
}
|
|
30
|
+
|
|
25
31
|
export interface TelaCliAuthState {
|
|
26
32
|
user: TelaCliUser
|
|
27
33
|
workspace: TelaCliWorkspace
|
|
@@ -40,6 +46,7 @@ export interface TelaCliConfig {
|
|
|
40
46
|
auth: TelaCliAuthState | null
|
|
41
47
|
forgejoOrganization: TelaCliForgejoOrganization | null
|
|
42
48
|
forgejoUserRegistered: boolean
|
|
49
|
+
gitSshKey: TelaCliGitSshKey | null
|
|
43
50
|
}
|
|
44
51
|
|
|
45
52
|
export interface OrganizationSummary {
|
|
@@ -75,6 +82,21 @@ export interface RepositoryLookupResponse {
|
|
|
75
82
|
error?: string
|
|
76
83
|
}
|
|
77
84
|
|
|
85
|
+
export interface RepositoryListItemResponse {
|
|
86
|
+
organization: OrganizationSummary
|
|
87
|
+
agentRepo: AgentRepoSummary
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface RepositoryListResponse {
|
|
91
|
+
success: boolean
|
|
92
|
+
repositories?: RepositoryListItemResponse[]
|
|
93
|
+
error?: string
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface AvailableRepositorySummary extends RepositoryListItemResponse {
|
|
97
|
+
syncStatus: 'available'
|
|
98
|
+
}
|
|
99
|
+
|
|
78
100
|
export interface OrganizationListResponse {
|
|
79
101
|
success: boolean
|
|
80
102
|
organizations?: TelaCliForgejoOrganization[]
|