@meistrari/tela-cli 0.7.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 -241
  36. package/src/ink/components.tsx +0 -178
  37. package/src/ink/runtime.tsx +0 -90
  38. package/src/ink/screens/feedback-screen.tsx +0 -395
  39. package/src/ink/screens/push-screen.tsx +0 -232
  40. package/src/ink/screens/run-screen.tsx +0 -292
  41. package/src/ink/screens/setup/breadcrumb.tsx +0 -24
  42. package/src/ink/screens/setup/setup-screen.tsx +0 -612
  43. package/src/ink/screens/setup/step-frame.tsx +0 -31
  44. package/src/ink/screens/setup/steps/done-step.tsx +0 -29
  45. package/src/ink/screens/setup/steps/environment-step.tsx +0 -23
  46. package/src/ink/screens/setup/steps/organization-step.tsx +0 -35
  47. package/src/ink/screens/setup/steps/review-step.tsx +0 -78
  48. package/src/ink/screens/setup/steps/running-step.tsx +0 -37
  49. package/src/ink/screens/setup/steps/ssh-key-step.tsx +0 -48
  50. package/src/ink/screens/setup/steps/text-field-step.tsx +0 -18
  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 -283
  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
@@ -1,232 +0,0 @@
1
- /** @jsxImportSource react */
2
- import React, { useEffect, useRef, useState } from 'react'
3
- import { Text, useInput } from 'ink'
4
- import type { PushPreview } from '../../commands/push.ts'
5
- import type { PushResult } from '../../types.ts'
6
- import { AppFrame, Callout, Section, SelectList, SpinnerText, TextEntry } from '../components.tsx'
7
- import type { CommandScreenController } from '../runtime.tsx'
8
-
9
- export interface PushScreenResult {
10
- ok: boolean
11
- branch?: string
12
- commitMessage?: string
13
- commitSha?: string
14
- retriedCommit?: boolean
15
- nextActions: string[]
16
- }
17
-
18
- export interface PushScreenProps {
19
- controller: CommandScreenController<PushScreenResult>
20
- initialMode?: 'manual' | 'claude'
21
- initialManualMessage?: string
22
- dryRun?: boolean
23
- loadPreview: (input: {
24
- mode: 'manual' | 'claude'
25
- manualMessage?: string
26
- }) => Promise<PushPreview>
27
- execute: (input: {
28
- commitMessage: string
29
- messageSource: 'manual' | 'claude' | 'fallback'
30
- fallbackReason?: string
31
- }) => Promise<PushResult>
32
- }
33
-
34
- type PushPhase = 'mode' | 'loadingPreview' | 'review' | 'running' | 'done'
35
-
36
- export function PushScreen(props: PushScreenProps): React.ReactElement {
37
- const [phase, setPhase] = useState<PushPhase>(props.initialMode ? 'loadingPreview' : 'mode')
38
- const [mode, setMode] = useState<'manual' | 'claude'>(props.initialMode ?? 'manual')
39
- const [modeIndex, setModeIndex] = useState(props.initialMode === 'claude' ? 1 : 0)
40
- const [preview, setPreview] = useState<PushPreview | null>(null)
41
- const [message, setMessage] = useState(props.initialManualMessage ?? '')
42
- const [result, setResult] = useState<PushResult | null>(null)
43
- const startedRef = useRef(false)
44
-
45
- useEffect(() => {
46
- if (phase !== 'loadingPreview' || startedRef.current) {
47
- return
48
- }
49
-
50
- startedRef.current = true
51
- void props.loadPreview({
52
- mode,
53
- manualMessage: props.initialManualMessage ?? undefined,
54
- })
55
- .then((nextPreview) => {
56
- setPreview(nextPreview)
57
- setMessage(nextPreview.suggestedMessage ?? props.initialManualMessage ?? '')
58
- setPhase('review')
59
- })
60
- .catch((error) => {
61
- props.controller.reject(error)
62
- })
63
- }, [mode, phase, props])
64
-
65
- useEffect(() => {
66
- if (phase === 'review' || phase === 'done') {
67
- startedRef.current = false
68
- }
69
- }, [phase])
70
-
71
- useEffect(() => {
72
- if (phase === 'done' && result) {
73
- props.controller.resolve({
74
- ok: true,
75
- branch: result.branch,
76
- commitMessage: result.message,
77
- commitSha: result.commitSha,
78
- retriedCommit: result.retriedCommit,
79
- nextActions: [
80
- 'Run `tela feedback --list` to inspect feedback for the pushed commit.',
81
- 'Use `tela status` if you want to verify the repository is clean afterward.',
82
- ],
83
- }, 40)
84
- }
85
- }, [phase, props, result])
86
-
87
- useInput((input, key) => {
88
- if (phase === 'mode') {
89
- if (key.downArrow || input === 'j') {
90
- setModeIndex(current => Math.min(current + 1, 1))
91
- return
92
- }
93
-
94
- if (key.upArrow || input === 'k') {
95
- setModeIndex(current => Math.max(current - 1, 0))
96
- return
97
- }
98
-
99
- if (key.escape) {
100
- props.controller.reject(new Error('Push cancelled.'))
101
- return
102
- }
103
-
104
- if (key.return) {
105
- setMode(modeIndex === 0 ? 'manual' : 'claude')
106
- setPhase('loadingPreview')
107
- }
108
- return
109
- }
110
-
111
- if (phase === 'review') {
112
- if (key.escape) {
113
- props.controller.reject(new Error('Push cancelled.'))
114
- return
115
- }
116
-
117
- if (key.backspace || key.delete) {
118
- setMessage(current => current.slice(0, -1))
119
- return
120
- }
121
-
122
- if (key.return) {
123
- if (props.dryRun) {
124
- props.controller.resolve({
125
- ok: true,
126
- branch: preview?.branch,
127
- commitMessage: message,
128
- nextActions: ['Run `tela push` without `--dry-run` when the plan looks correct.'],
129
- })
130
- return
131
- }
132
-
133
- setPhase('running')
134
- void props.execute({
135
- commitMessage: message,
136
- messageSource: preview?.messageSource ?? (mode === 'manual' ? 'manual' : 'claude'),
137
- fallbackReason: preview?.fallbackReason,
138
- })
139
- .then((nextResult) => {
140
- setResult(nextResult)
141
- setPhase('done')
142
- })
143
- .catch((error) => {
144
- props.controller.reject(error)
145
- })
146
- return
147
- }
148
-
149
- if (input) {
150
- setMessage(current => `${current}${input}`)
151
- }
152
- }
153
- })
154
-
155
- return (
156
- <AppFrame
157
- title="Push"
158
- subtitle="Preview the worktree, review the commit message, and publish the branch."
159
- footer={
160
- phase === 'mode'
161
- ? '↑/↓ move · Enter select · Esc cancel'
162
- : phase === 'review'
163
- ? 'Type to edit commit message · Enter push · Esc cancel'
164
- : undefined
165
- }
166
- status={phase === 'running' ? 'Creating commit and pushing branch' : undefined}
167
- >
168
- {phase === 'mode' ? (
169
- <Section title="Commit message mode">
170
- <SelectList
171
- items={[
172
- { key: 'manual', label: 'Write the commit message manually' },
173
- { key: 'claude', label: 'Generate the commit message with Claude' },
174
- ]}
175
- currentIndex={modeIndex}
176
- />
177
- </Section>
178
- ) : null}
179
-
180
- {phase === 'loadingPreview' ? (
181
- <Section title="Loading preview">
182
- <SpinnerText label="Inspecting the current repository state" />
183
- </Section>
184
- ) : null}
185
-
186
- {phase === 'review' && preview ? (
187
- <>
188
- <Section title="Repository">
189
- <Text>Repo root: {preview.repoRoot}</Text>
190
- <Text>Branch: {preview.detached ? '<detached>' : (preview.branch ?? '<unknown>')}</Text>
191
- <Text>Upstream: {preview.upstream ?? '<none>'}</Text>
192
- <Text>Changed files: {preview.statusCounts.total}</Text>
193
- <Text>Staged: {preview.statusCounts.staged}</Text>
194
- <Text>Unstaged: {preview.statusCounts.unstaged}</Text>
195
- <Text>Untracked: {preview.statusCounts.untracked}</Text>
196
- </Section>
197
- {preview.worktreeStatus ? (
198
- <Section title="Worktree">
199
- <Text>{preview.worktreeStatus}</Text>
200
- </Section>
201
- ) : null}
202
- {preview.messageSource === 'fallback' ? (
203
- <Callout tone="warn">
204
- {`Using deterministic fallback message because ${preview.fallbackReason ?? 'Claude did not return a usable message'}.`}
205
- </Callout>
206
- ) : null}
207
- <Section title="Commit message">
208
- <TextEntry label="Message" value={message} placeholder="Commit message" />
209
- </Section>
210
- </>
211
- ) : null}
212
-
213
- {phase === 'running' ? (
214
- <Section title="Running">
215
- <SpinnerText label="Staging, committing, and pushing" />
216
- </Section>
217
- ) : null}
218
-
219
- {phase === 'done' && result ? (
220
- <>
221
- <Section title="Result">
222
- <Text>Branch: {result.branch}</Text>
223
- <Text>Commit: {result.commitSha}</Text>
224
- <Text>Upstream: {result.upstream ?? '<set on push>'}</Text>
225
- <Text>Retry after hook changes: {result.retriedCommit ? 'yes' : 'no'}</Text>
226
- </Section>
227
- <Callout tone="success">Push completed successfully.</Callout>
228
- </>
229
- ) : null}
230
- </AppFrame>
231
- )
232
- }
@@ -1,292 +0,0 @@
1
- /** @jsxImportSource react */
2
- import React, { useEffect, useMemo, useRef, useState } from 'react'
3
- import { Box, Text, useInput } from 'ink'
4
- import type {
5
- RunCommandResult,
6
- RunExecutionControl,
7
- RunExecutionSnapshot,
8
- RunStepSummary,
9
- } from '../../commands/run.ts'
10
- import { createUserFacingError, toUserFacingError } from '../../errors.ts'
11
- import { AppFrame, Callout, Section, SpinnerText } from '../components.tsx'
12
- import type { CommandScreenController } from '../runtime.tsx'
13
-
14
- export interface RunScreenProps {
15
- controller: CommandScreenController<RunCommandResult>
16
- initialSnapshot: RunExecutionSnapshot
17
- execute: (input: {
18
- onSnapshot: (snapshot: RunExecutionSnapshot) => void
19
- onControl: (control: RunExecutionControl) => void
20
- }) => Promise<RunCommandResult>
21
- completionDelayMs?: number
22
- }
23
-
24
- export interface RunChatHandoffScreenProps {
25
- controller: CommandScreenController<void>
26
- snapshot: RunExecutionSnapshot
27
- launchDelayMs?: number
28
- }
29
-
30
- type RunScreenPhase = 'running' | 'done' | 'failed'
31
-
32
- function appendTerminalStep(
33
- snapshot: RunExecutionSnapshot,
34
- status: RunExecutionSnapshot['status'],
35
- summary: string,
36
- detail?: string,
37
- ): RunExecutionSnapshot {
38
- const lastStep = snapshot.steps.at(-1)
39
- const steps = lastStep?.summary === summary
40
- ? snapshot.steps
41
- : [
42
- ...snapshot.steps,
43
- {
44
- id: `step-${snapshot.steps.length + 1}`,
45
- kind: 'result' as const,
46
- summary,
47
- detail,
48
- notable: status !== 'success',
49
- },
50
- ]
51
-
52
- return {
53
- ...snapshot,
54
- status,
55
- currentActivity: summary,
56
- steps,
57
- activeTools: [],
58
- execution: {
59
- ...snapshot.execution,
60
- status,
61
- errors: detail ? [detail, ...snapshot.execution.errors] : snapshot.execution.errors,
62
- },
63
- }
64
- }
65
-
66
- function snapshotFromResult(
67
- result: RunCommandResult,
68
- startedAtMs: number,
69
- ): RunExecutionSnapshot {
70
- return {
71
- repoRoot: result.repoRoot,
72
- mode: result.mode,
73
- model: result.config.model,
74
- artifactPath: result.resultPath,
75
- startedAtMs,
76
- status: result.execution.status,
77
- currentActivity: result.steps.at(-1)?.summary ?? 'Run complete.',
78
- steps: result.steps,
79
- activeTools: [],
80
- execution: result.execution,
81
- }
82
- }
83
-
84
- function formatElapsed(startedAtMs: number): string {
85
- const elapsedMs = Math.max(0, Date.now() - startedAtMs)
86
- if (elapsedMs < 1000) {
87
- return `${elapsedMs}ms`
88
- }
89
-
90
- const seconds = elapsedMs / 1000
91
- if (seconds < 60) {
92
- return `${seconds.toFixed(seconds >= 10 ? 1 : 2)}s`
93
- }
94
-
95
- const minutes = Math.floor(seconds / 60)
96
- const remainder = Math.round(seconds % 60)
97
- return `${minutes}m ${remainder}s`
98
- }
99
-
100
- function renderStep(step: RunStepSummary, showDetails: boolean): React.ReactElement {
101
- return (
102
- <Box key={step.id} flexDirection="column" marginBottom={showDetails && step.detail ? 1 : 0}>
103
- <Text color={step.notable ? 'yellowBright' : undefined}>{`• ${step.summary}`}</Text>
104
- {showDetails && step.detail ? (
105
- <Box marginLeft={2} flexDirection="column">
106
- {step.detail.split('\n').map((line, index) => (
107
- <Text key={`${step.id}-detail-${index}`} dimColor>{line}</Text>
108
- ))}
109
- </Box>
110
- ) : null}
111
- </Box>
112
- )
113
- }
114
-
115
- export function RunScreen(props: RunScreenProps): React.ReactElement {
116
- const [snapshot, setSnapshot] = useState<RunExecutionSnapshot>(props.initialSnapshot)
117
- const [phase, setPhase] = useState<RunScreenPhase>('running')
118
- const [showDetails, setShowDetails] = useState(false)
119
- const [autoScroll, setAutoScroll] = useState(true)
120
- const [now, setNow] = useState(Date.now())
121
- const controlRef = useRef<RunExecutionControl | null>(null)
122
- const cancelStateRef = useRef<'idle' | 'interrupt' | 'kill'>('idle')
123
- const startedRef = useRef(false)
124
-
125
- useEffect(() => {
126
- const interval = setInterval(() => {
127
- setNow(Date.now())
128
- }, 250)
129
-
130
- return () => {
131
- clearInterval(interval)
132
- }
133
- }, [])
134
-
135
- useEffect(() => {
136
- if (startedRef.current) {
137
- return
138
- }
139
-
140
- startedRef.current = true
141
- void props.execute({
142
- onSnapshot(nextSnapshot) {
143
- setSnapshot(nextSnapshot)
144
- },
145
- onControl(control) {
146
- controlRef.current = control
147
- },
148
- })
149
- .then((result) => {
150
- setSnapshot(current => snapshotFromResult(result, current.startedAtMs))
151
- setPhase('done')
152
- props.controller.resolve(result, props.completionDelayMs ?? 180)
153
- })
154
- .catch((error) => {
155
- const userError = toUserFacingError(error)
156
- setSnapshot(current => appendTerminalStep(
157
- current,
158
- userError.code === 'run_cancelled' ? 'cancelled' : 'failed',
159
- userError.summary,
160
- userError.details,
161
- ))
162
- setPhase('failed')
163
- props.controller.reject(error, props.completionDelayMs ?? 180)
164
- })
165
- }, [props])
166
-
167
- useInput((input, key) => {
168
- if (input === 'd') {
169
- setShowDetails(current => !current)
170
- return
171
- }
172
-
173
- if (input === 'a') {
174
- setAutoScroll(current => !current)
175
- return
176
- }
177
-
178
- if (!key.ctrl || input !== 'c' || phase !== 'running') {
179
- return
180
- }
181
-
182
- if (!controlRef.current) {
183
- props.controller.reject(createUserFacingError({
184
- code: 'run_cancelled',
185
- summary: 'Claude run was cancelled.',
186
- fixes: ['Run `tela run` again when you are ready to continue.'],
187
- exitCode: 130,
188
- }))
189
- return
190
- }
191
-
192
- if (cancelStateRef.current === 'idle') {
193
- cancelStateRef.current = 'interrupt'
194
- setSnapshot(current => ({
195
- ...current,
196
- currentActivity: 'Interrupt requested. Waiting for Claude to stop…',
197
- }))
198
- controlRef.current.interrupt()
199
- return
200
- }
201
-
202
- cancelStateRef.current = 'kill'
203
- setSnapshot(current => ({
204
- ...current,
205
- currentActivity: 'Force stopping Claude…',
206
- }))
207
- controlRef.current.kill()
208
- })
209
-
210
- const displayedSteps = useMemo(() => {
211
- return autoScroll ? snapshot.steps.slice(-8) : snapshot.steps
212
- }, [autoScroll, snapshot.steps])
213
-
214
- const footer = [
215
- 'd details',
216
- autoScroll ? 'a show-all' : 'a auto-scroll',
217
- 'ctrl+c cancel',
218
- ].join(' ')
219
-
220
- return (
221
- <AppFrame
222
- title="Run"
223
- subtitle="Follow the current local agent execution in real time."
224
- footer={footer}
225
- status={phase === 'running' ? 'Claude one-shot run in progress' : undefined}
226
- >
227
- <Section title="Overview">
228
- <Text>Repo root: {snapshot.repoRoot}</Text>
229
- <Text>Mode: {snapshot.mode}</Text>
230
- <Text>Model: {snapshot.model ?? '<default>'}</Text>
231
- <Text>Artifact: {snapshot.artifactPath}</Text>
232
- <Text>Elapsed: {formatElapsed(snapshot.startedAtMs || now)}</Text>
233
- <Text>Status: {snapshot.status}</Text>
234
- </Section>
235
-
236
- <Section title="Activity">
237
- {phase === 'running'
238
- ? <SpinnerText label={snapshot.currentActivity} />
239
- : <Text>{snapshot.currentActivity}</Text>}
240
- </Section>
241
-
242
- {snapshot.activeTools.length > 0 ? (
243
- <Section title="Active tools">
244
- {snapshot.activeTools.map(tool => (
245
- <Text key={tool.id}>
246
- {`${tool.summary} · ${tool.elapsedSeconds}s`}
247
- </Text>
248
- ))}
249
- </Section>
250
- ) : null}
251
-
252
- <Section title={autoScroll ? 'Timeline (latest)' : 'Timeline'}>
253
- {displayedSteps.length > 0 ? displayedSteps.map(step => renderStep(step, showDetails)) : (
254
- <Text dimColor>No execution steps yet.</Text>
255
- )}
256
- </Section>
257
-
258
- {phase === 'done' ? (
259
- <Callout tone="success">Run complete. Tela will print the durable recap next.</Callout>
260
- ) : null}
261
- {phase === 'failed' ? (
262
- <Callout tone={snapshot.status === 'cancelled' ? 'warn' : 'error'}>
263
- {snapshot.status === 'cancelled' ? 'Run cancelled.' : 'Run failed.'}
264
- </Callout>
265
- ) : null}
266
- </AppFrame>
267
- )
268
- }
269
-
270
- export function RunChatHandoffScreen(props: RunChatHandoffScreenProps): React.ReactElement {
271
- useEffect(() => {
272
- props.controller.resolve(undefined, props.launchDelayMs ?? 650)
273
- }, [props])
274
-
275
- return (
276
- <AppFrame
277
- title="Run"
278
- subtitle="Handing off to native Claude chat. Tela will resume after you exit Claude."
279
- status="Launching Claude chat…"
280
- >
281
- <Section title="Overview">
282
- <Text>Repo root: {props.snapshot.repoRoot}</Text>
283
- <Text>Mode: {props.snapshot.mode}</Text>
284
- <Text>Model: {props.snapshot.model ?? '<default>'}</Text>
285
- <Text>Artifact: {props.snapshot.artifactPath}</Text>
286
- </Section>
287
- <Callout tone="info">
288
- Claude chat will take over this terminal. Follow the steps there, then exit Claude to let Tela read the result artifact.
289
- </Callout>
290
- </AppFrame>
291
- )
292
- }
@@ -1,24 +0,0 @@
1
- /** @jsxImportSource react */
2
- import React from 'react'
3
- import { Box, Text } from 'ink'
4
-
5
- export interface BreadcrumbEntry {
6
- label: string
7
- value: string
8
- }
9
-
10
- export function Breadcrumb(props: { entries: BreadcrumbEntry[] }): React.ReactElement | null {
11
- if (props.entries.length === 0) {
12
- return null
13
- }
14
-
15
- return (
16
- <Box flexDirection="column" marginBottom={1}>
17
- {props.entries.map(entry => (
18
- <Text key={entry.label} dimColor>
19
- {entry.label}: {entry.value || '—'}
20
- </Text>
21
- ))}
22
- </Box>
23
- )
24
- }