@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.
- 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
package/src/commands/status.ts
DELETED
|
@@ -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
|
-
}
|
package/src/commands/sync.ts
DELETED
|
@@ -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
|
-
}
|
package/src/commit-message.ts
DELETED
|
@@ -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
|
-
}
|