@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.
- package/README.md +56 -106
- package/dist/commands/auth.d.ts +2 -0
- package/dist/commands/context.d.ts +2 -0
- package/dist/commands/doctor-auth.d.ts +2 -0
- package/dist/commands/env.d.ts +2 -0
- package/dist/credentials.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7027 -0
- package/dist/output.d.ts +13 -0
- package/dist/program.d.ts +2 -0
- package/package.json +17 -24
- package/scripts/install-skills.cjs +127 -0
- package/skills/tela-cli/SKILL.md +20 -0
- package/skills/tela-cli/references/agent.md +111 -0
- package/cli.ts +0 -25
- package/postinstall.sh +0 -19
- package/src/agent-api.ts +0 -300
- package/src/assets.d.ts +0 -4
- package/src/auth.ts +0 -432
- package/src/claude.ts +0 -101
- package/src/commands/doctor.ts +0 -35
- package/src/commands/feedback.ts +0 -701
- package/src/commands/metadata.ts +0 -242
- package/src/commands/push.ts +0 -208
- package/src/commands/run.ts +0 -1699
- package/src/commands/status.ts +0 -109
- package/src/commands/sync.ts +0 -245
- package/src/commit-message.ts +0 -69
- package/src/config.ts +0 -799
- package/src/diagnostics.ts +0 -813
- package/src/errors.ts +0 -122
- package/src/forgejo.ts +0 -233
- package/src/git.ts +0 -457
- package/src/ink/assets/160092__jorickhoofd__dog-bark-1.mp3 +0 -0
- package/src/ink/bark.tsx +0 -241
- package/src/ink/components.tsx +0 -178
- package/src/ink/runtime.tsx +0 -90
- package/src/ink/screens/feedback-screen.tsx +0 -395
- package/src/ink/screens/push-screen.tsx +0 -232
- package/src/ink/screens/run-screen.tsx +0 -292
- package/src/ink/screens/setup/breadcrumb.tsx +0 -24
- package/src/ink/screens/setup/setup-screen.tsx +0 -612
- package/src/ink/screens/setup/step-frame.tsx +0 -31
- package/src/ink/screens/setup/steps/done-step.tsx +0 -29
- package/src/ink/screens/setup/steps/environment-step.tsx +0 -23
- package/src/ink/screens/setup/steps/organization-step.tsx +0 -35
- package/src/ink/screens/setup/steps/review-step.tsx +0 -78
- package/src/ink/screens/setup/steps/running-step.tsx +0 -37
- package/src/ink/screens/setup/steps/ssh-key-step.tsx +0 -48
- package/src/ink/screens/setup/steps/text-field-step.tsx +0 -18
- package/src/ink/screens/setup/steps.ts +0 -156
- package/src/ink/screens/setup/types.ts +0 -65
- package/src/ink/screens/sync-screen.tsx +0 -283
- package/src/process.ts +0 -251
- package/src/program.ts +0 -2105
- package/src/runtime.ts +0 -147
- package/src/types.ts +0 -165
- package/src/ui.ts +0 -295
- 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
|
-
}
|