@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.
@@ -0,0 +1,76 @@
1
+ import React from 'react'
2
+ import { Text } from 'ink'
3
+ import { DEVICE_FLOW_BROWSER_HELP } from '../../../../config.ts'
4
+ import type { SelectedSshPublicKey } from '../../../../forgejo.ts'
5
+ import type { TelaCliAuthState, TelaCliForgejoOrganization } from '../../../../types.ts'
6
+ import { AppFrame, Callout, Section } from '../../../components.tsx'
7
+ import { getEnvironmentLabel } from '../steps.ts'
8
+ import type { SetupFormValues } from '../types.ts'
9
+
10
+ export interface ResolvedReviewConfig {
11
+ agentApiUrl: string
12
+ authApiUrl: string
13
+ authDashboardUrl: string
14
+ telaApiUrl: string
15
+ requesterApplicationId: string
16
+ targetApplicationId: string
17
+ }
18
+
19
+ export function ReviewStep(props: {
20
+ values: SetupFormValues
21
+ resolvedConfig: ResolvedReviewConfig
22
+ auth: TelaCliAuthState | null
23
+ selectedOrganization: TelaCliForgejoOrganization | null
24
+ selectedSshKey: SelectedSshPublicKey | undefined
25
+ cloneRootState: { exists: boolean, isDirectory: boolean }
26
+ createCloneRoot: boolean
27
+ }): React.ReactElement {
28
+ const awaitingAuthentication = !props.auth || !props.selectedOrganization
29
+
30
+ return (
31
+ <AppFrame
32
+ title="Setup"
33
+ subtitle={awaitingAuthentication
34
+ ? 'Review your configuration before signing in.'
35
+ : 'Review the resolved workspace access before Forgejo provisioning.'}
36
+ footer={awaitingAuthentication
37
+ ? 'Enter start sign-in · Space toggle clone-root creation · Esc back'
38
+ : 'Enter provision Forgejo · Space toggle clone-root creation · Esc back'}
39
+ >
40
+ <Section title="Review">
41
+ <Text>Environment: {getEnvironmentLabel(props.values.environment)}</Text>
42
+ <Text>Agent API origin: {props.resolvedConfig.agentApiUrl}</Text>
43
+ <Text>Auth API URL: {props.resolvedConfig.authApiUrl}</Text>
44
+ <Text>Auth dashboard URL: {props.resolvedConfig.authDashboardUrl}</Text>
45
+ <Text>Tela API URL: {props.resolvedConfig.telaApiUrl}</Text>
46
+ <Text>Requester app: {props.resolvedConfig.requesterApplicationId}</Text>
47
+ <Text>Target app: {props.resolvedConfig.targetApplicationId}</Text>
48
+ <Text>Clone root: {props.values.cloneRoot}</Text>
49
+ <Text>User: {props.auth?.user.email ?? '<pending sign-in>'}</Text>
50
+ <Text>Workspace: {props.auth?.workspace.title ?? '<pending sign-in>'}</Text>
51
+ <Text>Organization: {props.selectedOrganization?.name ?? '<resolve after sign-in>'}</Text>
52
+ <Text>Forgejo provisioning: yes</Text>
53
+ <Text>
54
+ SSH key:
55
+ {' '}
56
+ {props.selectedSshKey
57
+ ? `${props.selectedSshKey.title} (${props.selectedSshKey.path})`
58
+ : '<missing>'}
59
+ </Text>
60
+ <Text>
61
+ Clone root state:
62
+ {' '}
63
+ {props.cloneRootState.exists
64
+ ? (props.cloneRootState.isDirectory ? 'existing directory' : 'existing non-directory path')
65
+ : 'missing'}
66
+ </Text>
67
+ {!props.cloneRootState.exists ? (
68
+ <Text>Create clone root if missing: {props.createCloneRoot ? 'yes' : 'no'}</Text>
69
+ ) : null}
70
+ </Section>
71
+ {awaitingAuthentication ? (
72
+ <Callout tone="info">{DEVICE_FLOW_BROWSER_HELP}</Callout>
73
+ ) : null}
74
+ </AppFrame>
75
+ )
76
+ }
@@ -0,0 +1,36 @@
1
+ import React from 'react'
2
+ import { Text } from 'ink'
3
+ import { DEVICE_FLOW_BROWSER_HELP } from '../../../../config.ts'
4
+ import { AppFrame, Callout, Section, SpinnerText } from '../../../components.tsx'
5
+
6
+ export function RunningStep(props: {
7
+ stage: 'auth' | 'provision'
8
+ organizationName?: string
9
+ verificationUrl: string | null
10
+ userCode: string | null
11
+ progressLines: string[]
12
+ }): React.ReactElement {
13
+ const provisioning = props.stage === 'provision'
14
+
15
+ return (
16
+ <AppFrame
17
+ title="Setup"
18
+ subtitle={provisioning
19
+ ? 'Provisioning Forgejo access for the selected workspace organization.'
20
+ : 'Authenticating your Tela workspace.'}
21
+ status={provisioning
22
+ ? `Provisioning Forgejo access${props.organizationName ? ` for ${props.organizationName}` : ''}`
23
+ : 'Waiting for browser-based authorization'}
24
+ >
25
+ <Section title={provisioning ? 'Provisioning' : 'Authentication'}>
26
+ <SpinnerText label={provisioning ? 'Provisioning Forgejo access' : 'Polling auth approval'} />
27
+ {!provisioning && props.verificationUrl ? <Text>Verification URL: {props.verificationUrl}</Text> : null}
28
+ {!provisioning && props.userCode ? <Text>User code: {props.userCode}</Text> : null}
29
+ {props.progressLines.map((line, index) => (
30
+ <Text key={`${index}-${line}`}>{line}</Text>
31
+ ))}
32
+ </Section>
33
+ {!provisioning ? <Callout tone="info">{DEVICE_FLOW_BROWSER_HELP}</Callout> : null}
34
+ </AppFrame>
35
+ )
36
+ }
@@ -0,0 +1,47 @@
1
+ import React from 'react'
2
+ import { Callout, SelectList } from '../../../components.tsx'
3
+ import type { SelectedSshPublicKey } from '../../../../forgejo.ts'
4
+ import { CUSTOM_SSH_KEY_OPTION } from '../types.ts'
5
+
6
+ export interface SshKeyOption {
7
+ key: string
8
+ label: string
9
+ hint?: string
10
+ }
11
+
12
+ export function buildSshKeyOptions(sshPublicKeys: SelectedSshPublicKey[]): SshKeyOption[] {
13
+ return [
14
+ ...sshPublicKeys.map(key => ({
15
+ key: key.path,
16
+ label: key.title,
17
+ hint: key.path,
18
+ })),
19
+ {
20
+ key: CUSTOM_SSH_KEY_OPTION,
21
+ label: 'Custom path…',
22
+ hint: 'Load another public key file',
23
+ },
24
+ ]
25
+ }
26
+
27
+ export function SshKeyStep(props: {
28
+ options: SshKeyOption[]
29
+ selection: string
30
+ hasDiscoveredKeys: boolean
31
+ }): React.ReactElement {
32
+ const currentIndex = Math.max(
33
+ props.options.findIndex(option => option.key === props.selection),
34
+ 0,
35
+ )
36
+
37
+ return (
38
+ <>
39
+ <SelectList items={props.options} currentIndex={currentIndex} />
40
+ {!props.hasDiscoveredKeys ? (
41
+ <Callout tone="info">
42
+ No `~/.ssh/*.pub` keys were found. Use Custom path… to continue.
43
+ </Callout>
44
+ ) : null}
45
+ </>
46
+ )
47
+ }
@@ -0,0 +1,17 @@
1
+ import React from 'react'
2
+ import { TextEntry } from '../../../components.tsx'
3
+
4
+ export function TextFieldStep(props: {
5
+ value: string
6
+ placeholder?: string
7
+ error?: string
8
+ }): React.ReactElement {
9
+ return (
10
+ <TextEntry
11
+ label=""
12
+ value={props.value}
13
+ placeholder={props.placeholder}
14
+ error={props.error}
15
+ />
16
+ )
17
+ }
@@ -0,0 +1,145 @@
1
+ import { ENVIRONMENT_PRESET_OPTIONS } from '../../../config.ts'
2
+ import { CUSTOM_SSH_KEY_OPTION, type SetupFormValues } from './types.ts'
3
+
4
+ export type SetupStepId =
5
+ | 'environment'
6
+ | 'agentApiUrl'
7
+ | 'authApiUrl'
8
+ | 'authDashboardUrl'
9
+ | 'telaApiUrl'
10
+ | 'requesterApplicationId'
11
+ | 'targetApplicationId'
12
+ | 'cloneRoot'
13
+ | 'sshKey'
14
+ | 'sshKeyPath'
15
+
16
+ export type SetupStepKind = 'select-environment' | 'select-ssh-key' | 'text'
17
+
18
+ export interface SetupStepDescriptor {
19
+ id: SetupStepId
20
+ label: string
21
+ question: string
22
+ kind: SetupStepKind
23
+ placeholder?: string
24
+ visible: (values: SetupFormValues) => boolean
25
+ }
26
+
27
+ export const EDIT_STEPS: readonly SetupStepDescriptor[] = [
28
+ {
29
+ id: 'environment',
30
+ label: 'Environment',
31
+ question: 'Which Tela environment do you want to use?',
32
+ kind: 'select-environment',
33
+ visible: () => true,
34
+ },
35
+ {
36
+ id: 'agentApiUrl',
37
+ label: 'Agent API URL',
38
+ question: 'Custom Agent API origin URL',
39
+ kind: 'text',
40
+ placeholder: 'https://agent-api.example.com',
41
+ visible: values => values.environment === 'custom',
42
+ },
43
+ {
44
+ id: 'authApiUrl',
45
+ label: 'Auth API URL',
46
+ question: 'Custom Auth API URL',
47
+ kind: 'text',
48
+ placeholder: 'https://auth.example.com',
49
+ visible: values => values.environment === 'custom',
50
+ },
51
+ {
52
+ id: 'authDashboardUrl',
53
+ label: 'Auth Dashboard URL',
54
+ question: 'Custom Auth dashboard URL',
55
+ kind: 'text',
56
+ placeholder: 'https://accounts.example.com',
57
+ visible: values => values.environment === 'custom',
58
+ },
59
+ {
60
+ id: 'telaApiUrl',
61
+ label: 'Tela API URL',
62
+ question: 'Custom Tela API URL',
63
+ kind: 'text',
64
+ placeholder: 'https://api.example.com',
65
+ visible: values => values.environment === 'custom',
66
+ },
67
+ {
68
+ id: 'requesterApplicationId',
69
+ label: 'Requester App ID',
70
+ question: 'Requester application ID',
71
+ kind: 'text',
72
+ placeholder: '00000000-0000-0000-0000-000000000000',
73
+ visible: values => values.environment === 'custom',
74
+ },
75
+ {
76
+ id: 'targetApplicationId',
77
+ label: 'Target App ID',
78
+ question: 'Target application ID',
79
+ kind: 'text',
80
+ placeholder: '00000000-0000-0000-0000-000000000000',
81
+ visible: values => values.environment === 'custom',
82
+ },
83
+ {
84
+ id: 'cloneRoot',
85
+ label: 'Clone Root',
86
+ question: 'Clone root directory',
87
+ kind: 'text',
88
+ placeholder: '/path/to/Code/Tela',
89
+ visible: () => true,
90
+ },
91
+ {
92
+ id: 'sshKey',
93
+ label: 'SSH Key',
94
+ question: 'Pick an SSH public key for Forgejo provisioning',
95
+ kind: 'select-ssh-key',
96
+ visible: () => true,
97
+ },
98
+ {
99
+ id: 'sshKeyPath',
100
+ label: 'SSH Key Path',
101
+ question: 'Path to your SSH public key',
102
+ kind: 'text',
103
+ placeholder: '~/.ssh/id_ed25519.pub',
104
+ visible: values => values.sshKeySelection === CUSTOM_SSH_KEY_OPTION,
105
+ },
106
+ ]
107
+
108
+ export function getVisibleEditSteps(values: SetupFormValues): SetupStepDescriptor[] {
109
+ return EDIT_STEPS.filter(step => step.visible(values))
110
+ }
111
+
112
+ export function getEnvironmentLabel(value: string): string {
113
+ return ENVIRONMENT_PRESET_OPTIONS.find(option => option.value === value)?.label ?? value
114
+ }
115
+
116
+ export function summarizeStep(
117
+ step: SetupStepDescriptor,
118
+ values: SetupFormValues,
119
+ sshKeyTitle: string | undefined,
120
+ ): string {
121
+ switch (step.id) {
122
+ case 'environment':
123
+ return getEnvironmentLabel(values.environment)
124
+ case 'agentApiUrl':
125
+ return values.agentApiUrl
126
+ case 'authApiUrl':
127
+ return values.authApiUrl
128
+ case 'authDashboardUrl':
129
+ return values.authDashboardUrl
130
+ case 'telaApiUrl':
131
+ return values.telaApiUrl
132
+ case 'requesterApplicationId':
133
+ return values.requesterApplicationId
134
+ case 'targetApplicationId':
135
+ return values.targetApplicationId
136
+ case 'cloneRoot':
137
+ return values.cloneRoot
138
+ case 'sshKey':
139
+ return values.sshKeySelection === CUSTOM_SSH_KEY_OPTION
140
+ ? 'Custom path…'
141
+ : (sshKeyTitle ?? values.sshKeySelection)
142
+ case 'sshKeyPath':
143
+ return values.sshKeyPath
144
+ }
145
+ }
@@ -0,0 +1,63 @@
1
+ import type { SetupAuthEvent } from '../../../auth.ts'
2
+ import type { SetupConfigInput } from '../../../config.ts'
3
+ import type { SelectedSshPublicKey } from '../../../forgejo.ts'
4
+ import type {
5
+ TelaCliAuthState,
6
+ TelaCliConfig,
7
+ TelaCliForgejoOrganization,
8
+ } from '../../../types.ts'
9
+ import type { CommandScreenController } from '../../runtime.tsx'
10
+
11
+ export const CUSTOM_SSH_KEY_OPTION = '__custom__'
12
+
13
+ export type SetupFormValues = SetupConfigInput & {
14
+ sshKeySelection: string
15
+ sshKeyPath: string
16
+ }
17
+
18
+ export interface SetupScreenResult {
19
+ ok: true
20
+ config: TelaCliConfig
21
+ warnings: string[]
22
+ nextActions: string[]
23
+ checks: Array<{ label: string, status: string, detail: string }>
24
+ }
25
+
26
+ export interface SetupScreenProps {
27
+ controller: CommandScreenController<SetupScreenResult>
28
+ currentConfigSummary: string[]
29
+ defaults: SetupConfigInput & {
30
+ forgejoOrganization: TelaCliForgejoOrganization | null
31
+ forgejoUserRegistered: boolean
32
+ }
33
+ sshPublicKeys: SelectedSshPublicKey[]
34
+ validateAgentApiUrl: (value: string) => string | undefined
35
+ validateAuthApiUrl: (value: string) => string | undefined
36
+ validateAuthDashboardUrl: (value: string) => string | undefined
37
+ validateTelaApiUrl: (value: string) => string | undefined
38
+ validateApplicationId: (value: string, label?: string) => string | undefined
39
+ validateCloneRoot: (value: string) => string | undefined
40
+ inspectCloneRoot: (path: string) => {
41
+ exists: boolean
42
+ isDirectory: boolean
43
+ }
44
+ authenticate: (
45
+ input: SetupConfigInput,
46
+ onEvent: (event: SetupAuthEvent) => void,
47
+ ) => Promise<TelaCliAuthState>
48
+ resolveOrganizations: (
49
+ input: SetupConfigInput & { auth: TelaCliAuthState },
50
+ ) => Promise<{
51
+ organizations: TelaCliForgejoOrganization[]
52
+ preferredOrganization: TelaCliForgejoOrganization | null
53
+ }>
54
+ execute: (
55
+ input: SetupConfigInput & {
56
+ createCloneRoot: boolean
57
+ sshKey: SelectedSshPublicKey
58
+ auth: TelaCliAuthState
59
+ organization: TelaCliForgejoOrganization
60
+ },
61
+ onEvent: (event: Pick<SetupAuthEvent, 'message' | 'title'>) => void,
62
+ ) => Promise<SetupScreenResult>
63
+ }