@meistrari/tela-cli 0.6.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 -240
  36. package/src/ink/components.tsx +0 -177
  37. package/src/ink/runtime.tsx +0 -89
  38. package/src/ink/screens/feedback-screen.tsx +0 -394
  39. package/src/ink/screens/push-screen.tsx +0 -231
  40. package/src/ink/screens/run-screen.tsx +0 -291
  41. package/src/ink/screens/setup/breadcrumb.tsx +0 -23
  42. package/src/ink/screens/setup/setup-screen.tsx +0 -611
  43. package/src/ink/screens/setup/step-frame.tsx +0 -30
  44. package/src/ink/screens/setup/steps/done-step.tsx +0 -28
  45. package/src/ink/screens/setup/steps/environment-step.tsx +0 -22
  46. package/src/ink/screens/setup/steps/organization-step.tsx +0 -34
  47. package/src/ink/screens/setup/steps/review-step.tsx +0 -77
  48. package/src/ink/screens/setup/steps/running-step.tsx +0 -36
  49. package/src/ink/screens/setup/steps/ssh-key-step.tsx +0 -47
  50. package/src/ink/screens/setup/steps/text-field-step.tsx +0 -17
  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 -282
  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
package/src/git.ts DELETED
@@ -1,457 +0,0 @@
1
- import { basename } from 'node:path'
2
- import { inspectSavedConfig } from './config.ts'
3
- import { createUserFacingError } from './errors.ts'
4
- import { resolveSshPrivateKeyPath } from './forgejo.ts'
5
- import {
6
- CommandError,
7
- runCommand,
8
- type CommandResult,
9
- type RunCommandOptions,
10
- } from './process.ts'
11
- import type { TelaCliGitSshKey } from './types.ts'
12
-
13
- export interface GitStatusEntry {
14
- index: string
15
- worktree: string
16
- path: string
17
- }
18
-
19
- export interface GitStatusCounts {
20
- staged: number
21
- unstaged: number
22
- untracked: number
23
- total: number
24
- }
25
-
26
- type GitSshHostKeyPolicy = 'default' | 'accept-new'
27
-
28
- interface GitRunOptions extends Omit<RunCommandOptions, 'env'> {
29
- requireConfiguredSsh?: boolean
30
- sshHostKeyPolicy?: GitSshHostKeyPolicy
31
- }
32
-
33
- function quoteForShell(value: string): string {
34
- return `'${value.replaceAll('\'', `'"'"'`)}'`
35
- }
36
-
37
- export function buildGitSshCommand(
38
- privateKeyPath: string,
39
- options: {
40
- hostKeyPolicy?: GitSshHostKeyPolicy
41
- } = {},
42
- ): string {
43
- const args = [
44
- `-i ${quoteForShell(privateKeyPath)}`,
45
- '-o IdentitiesOnly=yes',
46
- ]
47
-
48
- if (options.hostKeyPolicy === 'accept-new') {
49
- args.push('-o StrictHostKeyChecking=accept-new')
50
- }
51
-
52
- return `ssh ${args.join(' ')}`
53
- }
54
-
55
- export function isSshRemoteUrl(remoteUrl: string): boolean {
56
- const trimmed = remoteUrl.trim()
57
- return /^ssh:\/\//i.test(trimmed) || /^[^\s@]+@[^\s:]+:.+$/.test(trimmed)
58
- }
59
-
60
- function resolveConfiguredGitSshKey(requireConfiguredSsh: boolean): TelaCliGitSshKey | null {
61
- const inspection = inspectSavedConfig()
62
- if (!inspection.exists) {
63
- if (requireConfiguredSsh) {
64
- throw createUserFacingError({
65
- code: 'git_ssh_key_config_missing',
66
- summary: 'Tela CLI is not configured with a Git SSH key.',
67
- fixes: [
68
- 'Run `tela setup` and choose the SSH key to force on Git operations.',
69
- ],
70
- })
71
- }
72
-
73
- return null
74
- }
75
-
76
- if (!inspection.valid || !inspection.config) {
77
- if (requireConfiguredSsh) {
78
- throw createUserFacingError({
79
- code: 'git_ssh_key_config_invalid',
80
- summary: 'Tela CLI config is invalid, so the Git SSH key could not be loaded.',
81
- details: inspection.error,
82
- fixes: [
83
- 'Run `tela setup` to rewrite the config and choose the SSH key again.',
84
- ],
85
- })
86
- }
87
-
88
- return null
89
- }
90
-
91
- if (!inspection.config.gitSshKey) {
92
- if (requireConfiguredSsh) {
93
- throw createUserFacingError({
94
- code: 'git_ssh_key_missing',
95
- summary: 'No Git SSH key is configured.',
96
- fixes: [
97
- 'Run `tela setup` again and choose the SSH key to use for Git operations.',
98
- ],
99
- })
100
- }
101
-
102
- return null
103
- }
104
-
105
- try {
106
- return {
107
- ...inspection.config.gitSshKey,
108
- privateKeyPath: resolveSshPrivateKeyPath(inspection.config.gitSshKey.privateKeyPath),
109
- }
110
- }
111
- catch (error) {
112
- if (requireConfiguredSsh) {
113
- throw createUserFacingError({
114
- code: 'git_ssh_key_invalid',
115
- summary: 'The configured Git SSH private key is unavailable.',
116
- details: error instanceof Error ? error.message : String(error),
117
- fixes: [
118
- 'Restore the configured private key file, or run `tela setup` again and choose another SSH key.',
119
- ],
120
- cause: error,
121
- })
122
- }
123
-
124
- return null
125
- }
126
- }
127
-
128
- function buildGitEnv(
129
- requireConfiguredSsh: boolean,
130
- sshHostKeyPolicy: GitSshHostKeyPolicy = 'default',
131
- ): Record<string, string> | undefined {
132
- const gitSshKey = resolveConfiguredGitSshKey(requireConfiguredSsh)
133
- if (!gitSshKey) {
134
- return undefined
135
- }
136
-
137
- return {
138
- GIT_SSH_COMMAND: buildGitSshCommand(gitSshKey.privateKeyPath, {
139
- hostKeyPolicy: sshHostKeyPolicy,
140
- }),
141
- }
142
- }
143
-
144
- async function runGitCommand(
145
- args: string[],
146
- options: GitRunOptions = {},
147
- ): Promise<CommandResult> {
148
- const env = buildGitEnv(
149
- Boolean(options.requireConfiguredSsh),
150
- options.sshHostKeyPolicy,
151
- )
152
-
153
- return await runCommand(
154
- ['git', ...args],
155
- {
156
- ...options,
157
- env: env ? env : undefined,
158
- },
159
- )
160
- }
161
-
162
- async function getRemoteUrlInternal(cwd: string, remote: string): Promise<string | null> {
163
- const result = await runGitCommand(['remote', 'get-url', remote], { cwd, allowFailure: true })
164
- if (result.exitCode !== 0) {
165
- return null
166
- }
167
-
168
- return result.stdout.trim() || null
169
- }
170
-
171
- async function remoteRequiresConfiguredSsh(cwd: string, remote: string): Promise<boolean> {
172
- const remoteUrl = await getRemoteUrlInternal(cwd, remote)
173
- return Boolean(remoteUrl && isSshRemoteUrl(remoteUrl))
174
- }
175
-
176
- async function anyRemoteRequiresConfiguredSsh(cwd: string): Promise<boolean> {
177
- const result = await runGitCommand(['remote'], { cwd, allowFailure: true })
178
- if (result.exitCode !== 0) {
179
- return false
180
- }
181
-
182
- const remotes = result.stdout
183
- .split(/\r?\n/)
184
- .map(line => line.trim())
185
- .filter(Boolean)
186
-
187
- for (const remote of remotes) {
188
- if (await remoteRequiresConfiguredSsh(cwd, remote)) {
189
- return true
190
- }
191
- }
192
-
193
- return false
194
- }
195
-
196
- async function upstreamRequiresConfiguredSsh(cwd: string): Promise<boolean> {
197
- const upstream = await getUpstreamBranch(cwd)
198
- if (!upstream) {
199
- return false
200
- }
201
-
202
- const separator = upstream.indexOf('/')
203
- if (separator <= 0) {
204
- return false
205
- }
206
-
207
- return await remoteRequiresConfiguredSsh(cwd, upstream.slice(0, separator))
208
- }
209
-
210
- export async function findRepositoryRoot(cwd: string): Promise<string | null> {
211
- const result = await runGitCommand(['rev-parse', '--show-toplevel'], { cwd, allowFailure: true })
212
- if (result.exitCode !== 0) {
213
- return null
214
- }
215
-
216
- return result.stdout.trim() || null
217
- }
218
-
219
- export async function getRepositoryRoot(cwd: string): Promise<string> {
220
- const repoRoot = await findRepositoryRoot(cwd)
221
- if (!repoRoot) {
222
- throw new Error(`Not a git repository: ${cwd}`)
223
- }
224
-
225
- return repoRoot
226
- }
227
-
228
- export async function assertGitRepository(cwd: string): Promise<void> {
229
- try {
230
- await runGitCommand(['rev-parse', '--is-inside-work-tree'], { cwd })
231
- }
232
- catch (error) {
233
- if (error instanceof CommandError) {
234
- throw new Error(`Not a git repository: ${cwd}`)
235
- }
236
- throw error
237
- }
238
- }
239
-
240
- export async function cloneRepository(remoteUrl: string, targetDir: string): Promise<void> {
241
- await runGitCommand(['clone', remoteUrl, targetDir], {
242
- requireConfiguredSsh: isSshRemoteUrl(remoteUrl),
243
- // Sync accepts first-seen SSH host keys automatically, but changed host keys still fail.
244
- sshHostKeyPolicy: 'accept-new',
245
- })
246
- }
247
-
248
- export async function getOriginUrl(cwd: string): Promise<string> {
249
- const result = await runGitCommand(['remote', 'get-url', 'origin'], { cwd })
250
- return result.stdout.trim()
251
- }
252
-
253
- export async function fetchOrigin(cwd: string): Promise<void> {
254
- await runGitCommand(['fetch', 'origin', '--prune'], {
255
- cwd,
256
- requireConfiguredSsh: await remoteRequiresConfiguredSsh(cwd, 'origin'),
257
- sshHostKeyPolicy: 'accept-new',
258
- })
259
- }
260
-
261
- export async function fetchAllRemotes(cwd: string): Promise<void> {
262
- await runGitCommand(['fetch', '--all', '--prune'], {
263
- cwd,
264
- requireConfiguredSsh: await anyRemoteRequiresConfiguredSsh(cwd),
265
- sshHostKeyPolicy: 'accept-new',
266
- })
267
- }
268
-
269
- export async function getWorktreeStatus(cwd: string): Promise<string> {
270
- const result = await runGitCommand(['status', '--short'], { cwd })
271
- return result.stdout.trim()
272
- }
273
-
274
- export async function isWorktreeDirty(cwd: string): Promise<boolean> {
275
- return Boolean(await getWorktreeStatus(cwd))
276
- }
277
-
278
- export async function stageAll(cwd: string): Promise<void> {
279
- await runGitCommand(['add', '-A'], { cwd })
280
- }
281
-
282
- export async function getCurrentBranch(cwd: string): Promise<string> {
283
- try {
284
- const result = await runGitCommand(['symbolic-ref', '--quiet', '--short', 'HEAD'], { cwd })
285
- return result.stdout.trim()
286
- }
287
- catch (error) {
288
- if (error instanceof CommandError) {
289
- throw new Error('Detached HEAD; check out a branch before syncing or pushing.')
290
- }
291
- throw error
292
- }
293
- }
294
-
295
- export async function getUpstreamBranch(cwd: string): Promise<string | null> {
296
- const result = await runGitCommand(
297
- ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{upstream}'],
298
- { cwd, allowFailure: true },
299
- )
300
-
301
- if (result.exitCode !== 0) {
302
- return null
303
- }
304
-
305
- return result.stdout.trim() || null
306
- }
307
-
308
- export async function pullCurrentBranch(cwd: string): Promise<void> {
309
- await runGitCommand(['pull', '--ff-only'], {
310
- cwd,
311
- requireConfiguredSsh: await upstreamRequiresConfiguredSsh(cwd),
312
- sshHostKeyPolicy: 'accept-new',
313
- })
314
- }
315
-
316
- export async function pullDefaultBranch(cwd: string, defaultBranch: string): Promise<void> {
317
- await runGitCommand(['pull', '--ff-only', 'origin', defaultBranch], {
318
- cwd,
319
- requireConfiguredSsh: await remoteRequiresConfiguredSsh(cwd, 'origin'),
320
- sshHostKeyPolicy: 'accept-new',
321
- })
322
- }
323
-
324
- export async function getShortStatus(cwd: string): Promise<string> {
325
- const result = await runGitCommand(['status', '--short'], { cwd })
326
- return result.stdout
327
- }
328
-
329
- export function parseShortStatus(output: string): GitStatusEntry[] {
330
- return output
331
- .split(/\r?\n/)
332
- .map(line => line.trimEnd())
333
- .filter(Boolean)
334
- .map((line) => {
335
- const normalized = line.padEnd(3, ' ')
336
- return {
337
- index: normalized[0] ?? ' ',
338
- worktree: normalized[1] ?? ' ',
339
- path: normalized.slice(3).trim(),
340
- }
341
- })
342
- }
343
-
344
- export async function getStatusCounts(cwd: string): Promise<GitStatusCounts> {
345
- const entries = parseShortStatus(await getShortStatus(cwd))
346
- const counts: GitStatusCounts = {
347
- staged: 0,
348
- unstaged: 0,
349
- untracked: 0,
350
- total: entries.length,
351
- }
352
-
353
- for (const entry of entries) {
354
- if (entry.index !== ' ' && entry.index !== '?') {
355
- counts.staged += 1
356
- }
357
-
358
- if (entry.worktree !== ' ' && entry.worktree !== '?') {
359
- counts.unstaged += 1
360
- }
361
-
362
- if (entry.index === '?' || entry.worktree === '?') {
363
- counts.untracked += 1
364
- }
365
- }
366
-
367
- return counts
368
- }
369
-
370
- export async function getStagedDiff(cwd: string): Promise<string> {
371
- const result = await runGitCommand(['diff', '--cached'], { cwd })
372
- return result.stdout
373
- }
374
-
375
- export async function getDiffAgainstHead(cwd: string): Promise<string> {
376
- const result = await runGitCommand(['diff', 'HEAD', '--'], { cwd })
377
- return result.stdout
378
- }
379
-
380
- export async function getRecentLog(cwd: string, count = 10): Promise<string> {
381
- const result = await runGitCommand(['log', '--oneline', `-${count}`], { cwd, allowFailure: true })
382
- return result.stdout
383
- }
384
-
385
- export async function getStagedFiles(cwd: string): Promise<string[]> {
386
- const result = await runGitCommand(['diff', '--cached', '--name-only', '--diff-filter=ACMRTUXB'], { cwd })
387
- return result.stdout
388
- .split(/\r?\n/)
389
- .map(line => line.trim())
390
- .filter(Boolean)
391
- }
392
-
393
- export async function getChangedFilesAgainstHead(cwd: string): Promise<string[]> {
394
- const result = await runGitCommand(['diff', '--name-only', 'HEAD', '--diff-filter=ACMRTUXB'], { cwd })
395
- return result.stdout
396
- .split(/\r?\n/)
397
- .map(line => line.trim())
398
- .filter(Boolean)
399
- }
400
-
401
- export async function getDiffStatAgainstHead(cwd: string): Promise<string> {
402
- const result = await runGitCommand(['diff', '--stat', 'HEAD'], { cwd, allowFailure: true })
403
- return result.stdout.trim()
404
- }
405
-
406
- export function buildFallbackCommitMessage(files: string[]): string {
407
- if (files.length === 0) {
408
- return 'Update repository files'
409
- }
410
-
411
- if (files.length === 1) {
412
- return `Update ${basename(files[0]!) || files[0]}`
413
- }
414
-
415
- return `Update ${basename(files[0]!) || files[0]} and ${files.length - 1} other files`
416
- }
417
-
418
- export async function commitWithMessage(cwd: string, message: string): Promise<void> {
419
- await runGitCommand(['commit', '-F', '-'], {
420
- cwd,
421
- input: message,
422
- })
423
- }
424
-
425
- export async function pushBranch(cwd: string, branch: string, setUpstream: boolean): Promise<void> {
426
- const args = setUpstream
427
- ? ['push', '--set-upstream', 'origin', branch]
428
- : ['push']
429
- const requireConfiguredSsh = setUpstream
430
- ? await remoteRequiresConfiguredSsh(cwd, 'origin')
431
- : await upstreamRequiresConfiguredSsh(cwd)
432
-
433
- await runGitCommand(args, {
434
- cwd,
435
- requireConfiguredSsh,
436
- })
437
- }
438
-
439
- export async function getHeadShortSha(cwd: string): Promise<string> {
440
- const result = await runGitCommand(['rev-parse', '--short', 'HEAD'], { cwd })
441
- return result.stdout.trim()
442
- }
443
-
444
- export async function getHeadCommitHash(cwd: string): Promise<string> {
445
- const result = await runGitCommand(['rev-parse', 'HEAD'], { cwd })
446
- return result.stdout.trim()
447
- }
448
-
449
- export async function getHeadCommitMessage(cwd: string): Promise<string> {
450
- const result = await runGitCommand(['log', '-1', '--pretty=%B'], { cwd })
451
- return result.stdout.trim()
452
- }
453
-
454
- export async function getHeadShortMessage(cwd: string): Promise<string> {
455
- const result = await runGitCommand(['log', '-1', '--pretty=%s'], { cwd })
456
- return result.stdout.trim()
457
- }
package/src/ink/bark.tsx DELETED
@@ -1,240 +0,0 @@
1
- import { existsSync, mkdtempSync, rmSync } from 'node:fs'
2
- import { tmpdir } from 'node:os'
3
- import { join } from 'node:path'
4
- import React, { useEffect, useMemo, useState } from 'react'
5
- import { Text } from 'ink'
6
- import barkAudioAssetPath from './assets/160092__jorickhoofd__dog-bark-1.mp3' with { type: 'file' }
7
- import { getBootstrapPixelRows } from '../ui.ts'
8
- import { AppFrame } from './components.tsx'
9
- import {
10
- renderCommandScreen,
11
- type CommandScreenController,
12
- } from './runtime.tsx'
13
-
14
- export const BARK_FRAME_DURATION_MS = 500
15
- export const BARK_FRAME_COUNT = 2
16
- export const BARK_ANIMATION_DURATION_MS = BARK_FRAME_DURATION_MS * BARK_FRAME_COUNT
17
-
18
- export const BARKING_PIXEL_ROWS = [
19
- '3222222223344553222222233222',
20
- '2223332224557888853233333222',
21
- '2233433336688889a98534433222',
22
- '2333433368877688aa9853433233',
23
- '3333333499754477a89754333234',
24
- '3433333685333455579755433344',
25
- '3333324654136764238545643344',
26
- '3333316443063136716542653334',
27
- '3334214434671124964531554333',
28
- '3334123235864246995431345443',
29
- '3343112157743224786521335543',
30
- '3452112245524023687520333543',
31
- '3452111244424006276630323244',
32
- '3342111354221000135420222344',
33
- '3343143444411000036421223544',
34
- '3444244511411223136322434544',
35
- '4444444400221526155313245554',
36
- '4443343421133546253553135555',
37
- '44433344111218b9362991014555',
38
- '3444333214113111627b41101355',
39
- '333332111652233339c911111134',
40
- '23342111169722236cb411111113',
41
- '3333111117a51248ccb211111113',
42
- '3333111129931117cc5111111113',
43
- '333211112a9a2278971111111112',
44
- '2231111119b912ab721111111102',
45
- '2231111116b51299321111101101',
46
- '2221111112721176111111101001',
47
- ] as const
48
-
49
-
50
- const BARK_FRAME_ROWS = [
51
- [...getBootstrapPixelRows()],
52
- [...BARKING_PIXEL_ROWS],
53
- ] as const
54
-
55
- export interface BarkAudioWriteResult {
56
- directory: string
57
- filePath: string
58
- }
59
-
60
- export interface BarkAudioPlaybackResult {
61
- attempted: boolean
62
- }
63
-
64
- export interface BarkAudioPlaybackOptions {
65
- silentBark?: boolean
66
- env?: Record<string, string | undefined>
67
- platform?: string
68
- afplayPath?: string
69
- audioPlayerExists?: (path: string) => boolean
70
- writeAudioFile?: () => BarkAudioWriteResult | Promise<BarkAudioWriteResult>
71
- spawnAudio?: (command: string[]) => { exited?: Promise<unknown> } | void
72
- }
73
-
74
- export interface BarkIntroSessionOptions {
75
- commandTitle: string
76
- enabled: boolean
77
- silentBark?: boolean
78
- env?: Record<string, string | undefined>
79
- platform?: string
80
- afplayPath?: string
81
- renderIntro?: (
82
- build: (controller: CommandScreenController<void>) => React.ReactElement,
83
- ) => Promise<void>
84
- playAudio?: (options: BarkAudioPlaybackOptions) => Promise<BarkAudioPlaybackResult>
85
- }
86
-
87
- export interface BarkIntroSession {
88
- playOnce(): Promise<void>
89
- }
90
-
91
- export function getBarkFrameRows(): string[][] {
92
- return BARK_FRAME_ROWS.map(frame => [...frame])
93
- }
94
-
95
- export function getBarkAnimationFrameIndex(elapsedMs: number): number {
96
- const clamped = Math.max(0, Math.min(elapsedMs, BARK_ANIMATION_DURATION_MS - 1))
97
- return Math.floor(clamped / BARK_FRAME_DURATION_MS)
98
- }
99
-
100
- export function shouldShowBarkIntro(options: {
101
- json?: boolean
102
- stdinIsTTY?: boolean
103
- stdoutIsTTY?: boolean
104
- }): boolean {
105
- return !options.json && Boolean(options.stdinIsTTY && options.stdoutIsTTY)
106
- }
107
-
108
- export function shouldPlayBarkAudio(options: {
109
- silentBark?: boolean
110
- env?: Record<string, string | undefined>
111
- platform?: string
112
- afplayPath?: string
113
- audioPlayerExists?: (path: string) => boolean
114
- }): boolean {
115
- if (options.silentBark || options.env?.TELA_SILENT_BARK === '1') {
116
- return false
117
- }
118
-
119
- if ((options.platform ?? process.platform) !== 'darwin') {
120
- return false
121
- }
122
-
123
- const afplayPath = options.afplayPath ?? '/usr/bin/afplay'
124
- const audioPlayerExists = options.audioPlayerExists ?? existsSync
125
- return audioPlayerExists(afplayPath)
126
- }
127
-
128
- export async function writeBarkAudioFile(baseDirectory = tmpdir()): Promise<BarkAudioWriteResult> {
129
- const directory = mkdtempSync(join(baseDirectory, 'tela-bark-'))
130
- const filePath = join(directory, 'bark.mp3')
131
- await Bun.write(filePath, Bun.file(barkAudioAssetPath))
132
- return { directory, filePath }
133
- }
134
-
135
- function spawnAudioCommand(command: string[]): { exited?: Promise<unknown> } {
136
- const proc = Bun.spawn(command, {
137
- stdin: 'ignore',
138
- stdout: 'ignore',
139
- stderr: 'ignore',
140
- })
141
-
142
- return { exited: proc.exited }
143
- }
144
-
145
- export async function playBarkAudio(options: BarkAudioPlaybackOptions = {}): Promise<BarkAudioPlaybackResult> {
146
- if (!shouldPlayBarkAudio(options)) {
147
- return { attempted: false }
148
- }
149
-
150
- const writeAudioFile = options.writeAudioFile ?? writeBarkAudioFile
151
- const spawnAudio = options.spawnAudio ?? spawnAudioCommand
152
- const afplayPath = options.afplayPath ?? '/usr/bin/afplay'
153
- let audioFile: BarkAudioWriteResult | undefined
154
-
155
- try {
156
- audioFile = await writeAudioFile()
157
- const writtenAudioFile = audioFile
158
- const result = spawnAudio([afplayPath, writtenAudioFile.filePath])
159
- void Promise.resolve(result?.exited)
160
- .catch(() => {})
161
- .finally(() => {
162
- rmSync(writtenAudioFile.directory, { recursive: true, force: true })
163
- })
164
- return { attempted: true }
165
- }
166
- catch {
167
- if (audioFile) {
168
- rmSync(audioFile.directory, { recursive: true, force: true })
169
- }
170
- return { attempted: false }
171
- }
172
- }
173
-
174
- export function createInkBarkSession(options: BarkIntroSessionOptions): BarkIntroSession {
175
- let played = false
176
-
177
- return {
178
- async playOnce(): Promise<void> {
179
- if (played || !options.enabled) {
180
- return
181
- }
182
-
183
- played = true
184
-
185
- const playAudio = options.playAudio ?? playBarkAudio
186
- void playAudio({
187
- silentBark: options.silentBark,
188
- env: options.env,
189
- platform: options.platform,
190
- afplayPath: options.afplayPath,
191
- }).catch(() => {})
192
-
193
- const renderIntro = options.renderIntro ?? (async (build) => {
194
- await renderCommandScreen<void>(build, { clearOnExit: true })
195
- })
196
-
197
- await renderIntro((controller) => React.createElement(BarkIntroScreen, {
198
- controller,
199
- title: options.commandTitle,
200
- }))
201
- },
202
- }
203
- }
204
-
205
- export function BarkIntroScreen(props: {
206
- controller: CommandScreenController<void>
207
- title: string
208
- }): React.ReactElement {
209
- const frames = useMemo(() => getBarkFrameRows(), [])
210
- const [frameIndex, setFrameIndex] = useState(0)
211
-
212
- useEffect(() => {
213
- const interval = setInterval(() => {
214
- setFrameIndex((current) => {
215
- if (current >= frames.length - 1) {
216
- clearInterval(interval)
217
- props.controller.resolve(undefined, 20)
218
- return current
219
- }
220
-
221
- return current + 1
222
- })
223
- }, BARK_FRAME_DURATION_MS)
224
-
225
- return () => {
226
- clearInterval(interval)
227
- }
228
- }, [frames.length, props.controller])
229
-
230
- return (
231
- <AppFrame
232
- title={props.title}
233
- subtitle="Starting interactive command"
234
- status="Bark intro"
235
- mascotRows={frames[frameIndex]}
236
- >
237
- <Text dimColor>Preparing the workflow…</Text>
238
- </AppFrame>
239
- )
240
- }