@meistrari/tela-cli 0.7.0 → 0.7.5

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,109 +0,0 @@
1
- import {
2
- createStatusReport,
3
- getAgentApiOriginCheckStatus,
4
- type StatusReport,
5
- } from '../diagnostics.ts'
6
- import { joinBlocks, kvList, nextActions, resultCard, section, table, type RenderOptions } from '../ui.ts'
7
-
8
- function renderWarnings(report: StatusReport, options: RenderOptions): string | undefined {
9
- if (report.warnings.length === 0) {
10
- return undefined
11
- }
12
-
13
- return section(
14
- 'Warnings',
15
- report.warnings.map(warning => `- ${warning}`).join('\n'),
16
- options,
17
- )
18
- }
19
-
20
- export function renderStatusReport(report: StatusReport, options: RenderOptions = {}): string {
21
- const configSection = section('Config', kvList([
22
- { label: 'Path', value: report.config.path },
23
- { label: 'Present', value: report.config.present ? 'yes' : 'no' },
24
- { label: 'Valid', value: report.config.valid ? 'yes' : 'no' },
25
- { label: 'Environment', value: report.config.environment ?? '<unconfigured>' },
26
- { label: 'Agent API origin', value: report.config.agentApiUrl ?? '<unconfigured>' },
27
- { label: 'Auth API URL', value: report.config.authApiUrl ?? '<unconfigured>' },
28
- { label: 'Tela API URL', value: report.config.telaApiUrl ?? '<unconfigured>' },
29
- { label: 'Clone root', value: report.config.cloneRoot ?? '<unconfigured>' },
30
- { label: 'User', value: report.config.user ?? '<signed out>' },
31
- { label: 'Workspace', value: report.config.workspace ?? '<signed out>' },
32
- { label: 'Organization', value: report.config.forgejoOrganization ?? '<not selected>' },
33
- { label: 'Forgejo user', value: report.config.forgejoUserRegistered == null ? '<unknown>' : (report.config.forgejoUserRegistered ? 'registered' : 'pending') },
34
- { label: 'Git SSH key', value: report.config.gitSshKeyTitle ?? '<missing>' },
35
- { label: 'Git SSH public key', value: report.config.gitSshPublicKeyPath ?? '<missing>' },
36
- { label: 'Git SSH private key', value: report.config.gitSshPrivateKeyPath ?? '<missing>' },
37
- { label: 'Access token', value: report.config.accessToken ?? '<missing>' },
38
- { label: 'Session expiry', value: report.config.sessionExpiresAt ? new Date(report.config.sessionExpiresAt).toISOString() : '<missing>' },
39
- { label: 'Refresh token', value: report.config.hasRefreshToken ? 'present' : 'missing' },
40
- ], options), options)
41
-
42
- const repoSection = section('Repository', kvList([
43
- { label: 'CWD', value: report.repo.cwd },
44
- { label: 'Inside repo', value: report.repo.inRepository ? 'yes' : 'no' },
45
- { label: 'Repo root', value: report.repo.repoRoot ?? '<none>' },
46
- { label: 'HEAD', value: report.repo.head ?? '<unknown>' },
47
- { label: 'Branch', value: report.repo.detached ? '<detached>' : (report.repo.branch ?? '<unknown>') },
48
- { label: 'Upstream', value: report.repo.upstream ?? '<none>' },
49
- { label: 'Dirty', value: report.repo.dirty ? 'yes' : 'no' },
50
- { label: 'Origin', value: report.repo.originUrl ?? '<missing>' },
51
- ], options), options)
52
-
53
- const toolRows = [
54
- ['git', report.tools.git ? 'available' : 'missing', report.tools.git ? 'ready' : 'install git'],
55
- [
56
- 'claude',
57
- !report.tools.claude
58
- ? 'missing'
59
- : (report.tools.claudeProjectScopedRunSupported === false ? 'unsupported' : 'available'),
60
- !report.tools.claude
61
- ? 'required for `tela run`, `tela feedback`, and Claude commit messages'
62
- : (report.tools.claudeProjectScopedRunSupported === false
63
- ? (report.tools.claudeDetail ?? `version ${report.tools.claudeVersion ?? '<unknown>'}`)
64
- : (report.tools.claudeVersion ? `version ${report.tools.claudeVersion}` : 'ready')),
65
- ],
66
- [
67
- 'agent-api-origin',
68
- report.tools.agentApiReachable === null
69
- ? 'skipped'
70
- : getAgentApiOriginCheckStatus({
71
- reachable: report.tools.agentApiReachable,
72
- category: report.tools.agentApiCategory ?? 'network_error',
73
- statusCode: report.tools.agentApiStatus,
74
- detail: report.tools.agentApiDetail ?? '<not checked>',
75
- }),
76
- report.tools.agentApiDetail ?? '<not checked>',
77
- ],
78
- ]
79
- const toolsSection = section('Tools', table(['Tool', 'Status', 'Detail'], toolRows, options), options)
80
-
81
- const changesSection = report.repo.shortStatus
82
- ? section('Worktree', report.repo.shortStatus, options)
83
- : undefined
84
-
85
- return resultCard('Tela status', [
86
- configSection,
87
- toolsSection,
88
- repoSection,
89
- changesSection,
90
- renderWarnings(report, options),
91
- nextActions(report.nextActions, options),
92
- ], options)
93
- }
94
-
95
- export async function getStatusReport(cwd = process.cwd(), fetchFn: typeof fetch = fetch): Promise<StatusReport> {
96
- return await createStatusReport(cwd, fetchFn)
97
- }
98
-
99
- export async function runStatusCommand(input: {
100
- cwd?: string
101
- json?: boolean
102
- fetchFn?: typeof fetch
103
- } = {}): Promise<{ report: StatusReport, output?: string }> {
104
- const report = await getStatusReport(input.cwd, input.fetchFn)
105
- return {
106
- report,
107
- output: input.json ? undefined : renderStatusReport(report, { decorate: Boolean(process.stdout.isTTY) }),
108
- }
109
- }
@@ -1,245 +0,0 @@
1
- import { existsSync, readdirSync, realpathSync, statSync } from 'node:fs'
2
- import { join } from 'node:path'
3
- import { fetchRepositoryLookup } from '../agent-api.ts'
4
- import type { FetchLike } from '../auth.ts'
5
- import {
6
- assertGitRepository,
7
- cloneRepository,
8
- fetchAllRemotes,
9
- fetchOrigin,
10
- getCurrentBranch,
11
- getHeadShortSha,
12
- getOriginUrl,
13
- findRepositoryRoot,
14
- getRepositoryRoot,
15
- getUpstreamBranch,
16
- isWorktreeDirty,
17
- pullCurrentBranch,
18
- pullDefaultBranch,
19
- } from '../git.ts'
20
- import type { BatchSyncResult, SyncResult, TelaCliConfig } from '../types.ts'
21
-
22
- export type SyncFailureCategory =
23
- | 'dirty_worktree'
24
- | 'detached_head'
25
- | 'origin_mismatch'
26
- | 'missing_upstream'
27
- | 'missing_default_branch'
28
- | 'not_git_repository'
29
- | 'other'
30
-
31
- export interface SyncAllProgressEvent {
32
- targetDir: string
33
- status: 'running' | 'success' | 'failure'
34
- result?: SyncResult
35
- error?: string
36
- }
37
-
38
- function normalizeRemoteUrl(url: string): string {
39
- return url.trim().replace(/\/+$/, '')
40
- }
41
-
42
- function normalizePath(path: string): string {
43
- return realpathSync(path)
44
- }
45
-
46
- export async function syncCurrentRepository(cwd = process.cwd()): Promise<SyncResult> {
47
- const targetDir = await getRepositoryRoot(cwd)
48
-
49
- await fetchAllRemotes(targetDir)
50
-
51
- if (await isWorktreeDirty(targetDir)) {
52
- throw new Error(`Worktree is dirty: ${targetDir}`)
53
- }
54
-
55
- const branch = await getCurrentBranch(targetDir)
56
- const upstream = await getUpstreamBranch(targetDir)
57
- if (!upstream) {
58
- throw new Error(`Current branch does not track a remote branch: ${branch}`)
59
- }
60
-
61
- await pullCurrentBranch(targetDir)
62
-
63
- return {
64
- targetDir,
65
- branch,
66
- head: await getHeadShortSha(targetDir),
67
- cloned: false,
68
- }
69
- }
70
-
71
- export async function syncAllRepositories(
72
- cloneRoot: string,
73
- options: {
74
- onProgress?: (event: SyncAllProgressEvent) => void
75
- } = {},
76
- ): Promise<BatchSyncResult> {
77
- if (!existsSync(cloneRoot)) {
78
- throw new Error(`Clone root does not exist: ${cloneRoot}`)
79
- }
80
-
81
- const cloneRootStat = statSync(cloneRoot)
82
- if (!cloneRootStat.isDirectory()) {
83
- throw new Error(`Clone root is not a directory: ${cloneRoot}`)
84
- }
85
-
86
- const repoDirs: string[] = []
87
- for (const entry of readdirSync(cloneRoot, { withFileTypes: true })) {
88
- if (!entry.isDirectory()) {
89
- continue
90
- }
91
-
92
- const entryPath = join(cloneRoot, entry.name)
93
- const repoRoot = await findRepositoryRoot(entryPath)
94
- if (!repoRoot) {
95
- continue
96
- }
97
-
98
- if (normalizePath(repoRoot) !== normalizePath(entryPath)) {
99
- continue
100
- }
101
-
102
- repoDirs.push(entryPath)
103
- }
104
-
105
- repoDirs.sort((left, right) => left.localeCompare(right))
106
-
107
- const successes: SyncResult[] = []
108
- const failures: BatchSyncResult['failures'] = []
109
-
110
- for (const repoDir of repoDirs) {
111
- options.onProgress?.({
112
- targetDir: repoDir,
113
- status: 'running',
114
- })
115
-
116
- try {
117
- const result = await syncCurrentRepository(repoDir)
118
- successes.push(result)
119
- options.onProgress?.({
120
- targetDir: repoDir,
121
- status: 'success',
122
- result,
123
- })
124
- }
125
- catch (error) {
126
- const errorMessage = error instanceof Error ? error.message : String(error)
127
- failures.push({
128
- targetDir: repoDir,
129
- error: errorMessage,
130
- category: categorizeSyncError(error),
131
- })
132
- options.onProgress?.({
133
- targetDir: repoDir,
134
- status: 'failure',
135
- error: errorMessage,
136
- })
137
- }
138
- }
139
-
140
- return { successes, failures }
141
- }
142
-
143
- export function categorizeSyncError(error: unknown): SyncFailureCategory {
144
- const message = error instanceof Error ? error.message : String(error)
145
-
146
- if (message.includes('Worktree is dirty')) {
147
- return 'dirty_worktree'
148
- }
149
-
150
- if (message.includes('Detached HEAD')) {
151
- return 'detached_head'
152
- }
153
-
154
- if (message.includes('Origin mismatch')) {
155
- return 'origin_mismatch'
156
- }
157
-
158
- if (message.includes('does not track a remote branch')) {
159
- return 'missing_upstream'
160
- }
161
-
162
- if (message.includes('does not expose a default branch')) {
163
- return 'missing_default_branch'
164
- }
165
-
166
- if (message.includes('Not a git repository')) {
167
- return 'not_git_repository'
168
- }
169
-
170
- return 'other'
171
- }
172
-
173
- export function listCloneRootRepositories(cloneRoot: string): string[] {
174
- if (!existsSync(cloneRoot)) {
175
- return []
176
- }
177
-
178
- const cloneRootStat = statSync(cloneRoot)
179
- if (!cloneRootStat.isDirectory()) {
180
- return []
181
- }
182
-
183
- return readdirSync(cloneRoot, { withFileTypes: true })
184
- .filter(entry => entry.isDirectory())
185
- .map(entry => entry.name)
186
- .sort((left, right) => left.localeCompare(right))
187
- }
188
-
189
- export async function syncRepository(
190
- repository: string,
191
- config: TelaCliConfig,
192
- fetchFn: FetchLike = fetch,
193
- ): Promise<SyncResult> {
194
- const repoDetails = await fetchRepositoryLookup(repository, config, fetchFn)
195
- const targetDir = join(config.cloneRoot, repository)
196
-
197
- if (!existsSync(targetDir)) {
198
- await cloneRepository(repoDetails.forgejoRepository.sshUrl, targetDir)
199
- return {
200
- targetDir,
201
- branch: await getCurrentBranch(targetDir),
202
- head: await getHeadShortSha(targetDir),
203
- cloned: true,
204
- }
205
- }
206
-
207
- const targetStat = statSync(targetDir)
208
- if (!targetStat.isDirectory()) {
209
- throw new Error(`Target path exists but is not a directory: ${targetDir}`)
210
- }
211
-
212
- await assertGitRepository(targetDir)
213
-
214
- const originUrl = await getOriginUrl(targetDir)
215
- if (normalizeRemoteUrl(originUrl) !== normalizeRemoteUrl(repoDetails.forgejoRepository.sshUrl)) {
216
- throw new Error(`Origin mismatch for ${targetDir}. Expected ${repoDetails.forgejoRepository.sshUrl}, found ${originUrl}`)
217
- }
218
-
219
- await fetchOrigin(targetDir)
220
-
221
- if (await isWorktreeDirty(targetDir)) {
222
- throw new Error(`Worktree is dirty: ${targetDir}`)
223
- }
224
-
225
- const branch = await getCurrentBranch(targetDir)
226
- const upstream = await getUpstreamBranch(targetDir)
227
-
228
- if (upstream) {
229
- await pullCurrentBranch(targetDir)
230
- }
231
- else {
232
- const defaultBranch = repoDetails.forgejoRepository.defaultBranch
233
- if (!defaultBranch) {
234
- throw new Error(`Repository ${repository} does not expose a default branch`)
235
- }
236
- await pullDefaultBranch(targetDir, defaultBranch)
237
- }
238
-
239
- return {
240
- targetDir,
241
- branch,
242
- head: await getHeadShortSha(targetDir),
243
- cloned: false,
244
- }
245
- }
@@ -1,69 +0,0 @@
1
- import { basename } from 'node:path'
2
- import { commandExists, runCommand } from './process.ts'
3
-
4
- export interface ClaudeCommitContext {
5
- status: string
6
- diff: string
7
- log: string
8
- }
9
-
10
- function firstNonEmptyLine(input: string): string {
11
- return input
12
- .split(/\r?\n/)
13
- .map(line => line.trim())
14
- .find(Boolean) ?? ''
15
- }
16
-
17
- export async function generateCommitMessageWithClaude(
18
- context: ClaudeCommitContext,
19
- ): Promise<string> {
20
- if (!await commandExists('claude')) {
21
- throw new Error('Claude CLI is not available')
22
- }
23
-
24
- const prompt = `You are writing a git commit message for already staged changes.
25
-
26
- Recent git status:
27
- ${context.status || '<empty>'}
28
-
29
- Recent git diff --cached:
30
- ${context.diff || '<empty>'}
31
-
32
- Recent git log:
33
- ${context.log || '<empty>'}
34
-
35
- Write exactly one short natural-language git commit message.
36
- - Do not use conventional commit prefixes like feat:, fix:, or chore:
37
- - Focus on the purpose of the change
38
- - Keep it concise
39
- - Return only the commit message text`
40
-
41
- const result = await runCommand([
42
- 'claude',
43
- '-p',
44
- prompt,
45
- '--output-format',
46
- 'text',
47
- '--tools',
48
- '',
49
- ])
50
-
51
- const message = firstNonEmptyLine(result.stdout)
52
- if (!message) {
53
- throw new Error('Claude returned an empty commit message')
54
- }
55
-
56
- return message
57
- }
58
-
59
- export function fallbackCommitMessageFromFiles(files: string[]): string {
60
- if (files.length === 0) {
61
- return 'Update repository files'
62
- }
63
-
64
- if (files.length === 1) {
65
- return `Update ${basename(files[0]!) || files[0]}`
66
- }
67
-
68
- return `Update ${basename(files[0]!) || files[0]} and ${files.length - 1} other files`
69
- }