@meistrari/tela-cli 0.1.0

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 ADDED
@@ -0,0 +1,95 @@
1
+ # Tela CLI
2
+
3
+ Tela CLI is a one-shot terminal tool for syncing Tela repositories, running local agents, preparing pushes, and applying commit feedback with Claude.
4
+
5
+ ## Commands
6
+
7
+ ### `setup`
8
+
9
+ Configure Tela CLI, choose an Agent API target, and verify the local environment
10
+
11
+ When to use: Run this first on a new machine, or any time your Agent API target/origin, clone root, or key changes.
12
+
13
+ Examples:
14
+ - `tela setup` — Choose production, staging, or a custom API origin and run post-setup checks
15
+
16
+ ### `sync [repo]`
17
+
18
+ Clone or update a Tela repository, or refresh the current checkout
19
+
20
+ When to use: Use this before coding to get a clean local checkout, or to refresh every repository under the configured clone root.
21
+
22
+ Examples:
23
+ - `tela sync agent-api` — Clone or update a specific repository under the clone root
24
+ - `tela sync --current` — Sync the repository for the current working directory
25
+ - `tela sync --all` — Refresh every repository directly under the clone root
26
+
27
+ ### `run`
28
+
29
+ Run the local agent repository with Claude
30
+
31
+ When to use: Use this from inside an agent repository when you want Claude to follow the local contract files, show its progress live, and produce the expected result artifact.
32
+
33
+ Examples:
34
+ - `tela run` — Run the current agent repository with the live viewer
35
+ - `tela run --plain` — Force scrollback-first text output
36
+ - `tela run --json` — Emit the final execution and artifact envelope as JSON
37
+
38
+ ### `push`
39
+
40
+ Preview, commit, and push the current repository
41
+
42
+ When to use: Use this after reviewing local changes when you want Tela to stage everything, produce a commit message, and push the branch.
43
+
44
+ Examples:
45
+ - `tela push` — Choose the commit-message flow interactively
46
+ - `tela push --claude` — Generate a commit message from the current worktree
47
+ - `tela push --message "Update feedback flow"` — Skip prompts and use an explicit commit message
48
+
49
+ ### `feedback`
50
+
51
+ Guided review of commit feedback with optional Claude handoff
52
+
53
+ When to use: Use this after pushing or after a feedback-producing commit when you want a guided review of the feedback, a prompt preview before launch, and an outcome summary after Claude exits.
54
+
55
+ Examples:
56
+ - `tela feedback --list` — Open the guided review flow without launching Claude
57
+ - `tela feedback --apply all` — Preview and launch Claude against every actionable suggestion
58
+ - `tela feedback --apply select` — Choose specific actionable suggestions before launching Claude
59
+
60
+ ### `status`
61
+
62
+ Show Tela config, tool availability, repo state, and Agent API origin reachability
63
+
64
+ When to use: Use this for a quick snapshot of whether the local environment is ready before running mutating commands.
65
+
66
+ Examples:
67
+ - `tela status` — Render a human-readable environment summary
68
+ - `tela status --json` — Emit a machine-readable status report
69
+
70
+ ### `doctor`
71
+
72
+ Diagnose config, tools, filesystem state, repo health, and Agent API origin reachability
73
+
74
+ When to use: Use this when Tela fails or behaves unexpectedly and you need exact remediation steps.
75
+
76
+ Aliases: `diag`
77
+
78
+ Examples:
79
+ - `tela doctor` — Run the full diagnostic suite with remediation guidance
80
+ - `tela doctor --json` — Emit structured diagnostic output for scripts
81
+
82
+ ## Output modes
83
+
84
+ - Human output is structured for terminal use with grouped summaries and next actions.
85
+ - `--json` is available on read-heavy flows and on `tela run` for the final execution envelope.
86
+ - `--silent-bark` disables bark audio during interactive Ink startup while keeping the visual intro.
87
+
88
+ ## First run
89
+
90
+ 1. `tela setup`
91
+ 2. `tela status`
92
+ 3. `tela sync <repo>` or `tela sync --current`
93
+ 4. `tela run`
94
+ 5. `tela push`
95
+ 6. `tela feedback --list`
package/cli.ts ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { renderUserFacingError, serializeError, toUserFacingError } from './src/errors.ts'
4
+ import { main } from './src/program.ts'
5
+
6
+ function hasFlag(argv: string[], flag: string): boolean {
7
+ return argv.includes(flag)
8
+ }
9
+
10
+ try {
11
+ await main(process.argv)
12
+ }
13
+ catch (error) {
14
+ if (hasFlag(process.argv, '--json')) {
15
+ process.stderr.write(`${JSON.stringify(serializeError(error), null, 2)}\n`)
16
+ }
17
+ else {
18
+ process.stderr.write(`${renderUserFacingError(error, {
19
+ decorate: Boolean(process.stderr.isTTY),
20
+ verbose: hasFlag(process.argv, '--verbose'),
21
+ })}\n`)
22
+ }
23
+
24
+ process.exitCode = toUserFacingError(error).exitCode
25
+ }
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@meistrari/tela-cli",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "bin": {
6
+ "tela": "cli.ts"
7
+ },
8
+ "files": [
9
+ "cli.ts",
10
+ "src/**/*"
11
+ ],
12
+ "scripts": {
13
+ "build": "bun build --compile ./cli.ts --outfile ./dist/tela",
14
+ "test": "bun test",
15
+ "typecheck": "bunx tsc --noEmit"
16
+ },
17
+ "dependencies": {
18
+ "commander": "13.1.0",
19
+ "ink": "6.2.3",
20
+ "react": "19"
21
+ },
22
+ "devDependencies": {
23
+ "@types/react": "19",
24
+ "bun-types": "1.3.4",
25
+ "ink-testing-library": "4.0.0"
26
+ },
27
+ "patchedDependencies": {
28
+ "ink@6.2.3": "patches/ink@6.2.3.patch"
29
+ }
30
+ }
@@ -0,0 +1,124 @@
1
+ import { buildAgentApiHeaders, type FetchLike } from './auth.ts'
2
+ import type {
3
+ AgentFeedbackItem,
4
+ AgentFeedbackListResponse,
5
+ RepositoryLookupResponse,
6
+ TelaCliConfig,
7
+ } from './types.ts'
8
+
9
+ const DEFAULT_TIMEOUT_MS = 10_000
10
+
11
+ function joinUrl(baseUrl: string, path: string): string {
12
+ return new URL(path, baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`).toString()
13
+ }
14
+
15
+ function parseJsonBody(responseText: string): unknown {
16
+ try {
17
+ return responseText ? JSON.parse(responseText) as unknown : null
18
+ }
19
+ catch {
20
+ return null
21
+ }
22
+ }
23
+
24
+ function isFeedbackItem(value: unknown): value is AgentFeedbackItem {
25
+ if (!value || typeof value !== 'object') {
26
+ return false
27
+ }
28
+
29
+ const candidate = value as Partial<AgentFeedbackItem>
30
+ return (candidate.status === 'success' || candidate.status === 'failed')
31
+ && typeof candidate.reason === 'string'
32
+ && typeof candidate.suggestion === 'string'
33
+ }
34
+
35
+ export class AgentApiError extends Error {
36
+ constructor(
37
+ message: string,
38
+ public readonly status: number,
39
+ public readonly responseBody: string,
40
+ ) {
41
+ super(message)
42
+ }
43
+ }
44
+
45
+ export async function fetchRepositoryLookup(
46
+ repository: string,
47
+ config: TelaCliConfig,
48
+ fetchFn: FetchLike = fetch,
49
+ ): Promise<Required<Pick<RepositoryLookupResponse, 'organization' | 'agentRepo' | 'forgejoRepository'>> & { success: true }> {
50
+ const response = await fetchFn(
51
+ joinUrl(config.agentApiUrl, `/v3/agents/repositories/${encodeURIComponent(repository)}`),
52
+ {
53
+ method: 'GET',
54
+ headers: buildAgentApiHeaders(config),
55
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS),
56
+ },
57
+ )
58
+
59
+ const responseText = await response.text()
60
+ const payload = parseJsonBody(responseText) as RepositoryLookupResponse | null
61
+
62
+ if (!response.ok) {
63
+ throw new AgentApiError(
64
+ payload?.error ?? `Agent API lookup failed with status ${response.status}`,
65
+ response.status,
66
+ responseText,
67
+ )
68
+ }
69
+
70
+ if (!payload?.success || !payload.organization || !payload.agentRepo || !payload.forgejoRepository) {
71
+ throw new AgentApiError('Agent API lookup returned an invalid payload', response.status, responseText)
72
+ }
73
+
74
+ return {
75
+ success: true,
76
+ organization: payload.organization,
77
+ agentRepo: payload.agentRepo,
78
+ forgejoRepository: payload.forgejoRepository,
79
+ }
80
+ }
81
+
82
+ export async function fetchCommitFeedback(
83
+ organizationName: string,
84
+ repository: string,
85
+ commitHash: string,
86
+ config: TelaCliConfig,
87
+ fetchFn: FetchLike = fetch,
88
+ ): Promise<AgentFeedbackListResponse> {
89
+ const response = await fetchFn(
90
+ joinUrl(
91
+ config.agentApiUrl,
92
+ `/v3/agents/${encodeURIComponent(organizationName)}/${encodeURIComponent(repository)}/feedback?commitHash=${encodeURIComponent(commitHash)}`,
93
+ ),
94
+ {
95
+ method: 'GET',
96
+ headers: buildAgentApiHeaders(config),
97
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS),
98
+ },
99
+ )
100
+
101
+ const responseText = await response.text()
102
+ const payload = parseJsonBody(responseText) as Partial<AgentFeedbackListResponse> & { error?: string } | null
103
+
104
+ if (!response.ok) {
105
+ throw new AgentApiError(
106
+ payload?.error ?? `Agent API feedback lookup failed with status ${response.status}`,
107
+ response.status,
108
+ responseText,
109
+ )
110
+ }
111
+
112
+ if (!payload
113
+ || typeof payload.commitHash !== 'string'
114
+ || payload.commitHash !== commitHash
115
+ || !Array.isArray(payload.feedbacks)
116
+ || !payload.feedbacks.every(isFeedbackItem)) {
117
+ throw new AgentApiError('Agent API feedback lookup returned an invalid payload', response.status, responseText)
118
+ }
119
+
120
+ return {
121
+ commitHash: payload.commitHash,
122
+ feedbacks: payload.feedbacks,
123
+ }
124
+ }
package/src/auth.ts ADDED
@@ -0,0 +1,16 @@
1
+ import type { TelaCliConfig } from './types.ts'
2
+
3
+ export type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>
4
+
5
+ export function buildAuthorizationHeader(config: Pick<TelaCliConfig, 'apiKey'>): string {
6
+ return `Bearer ${config.apiKey.trim()}`
7
+ }
8
+
9
+ export function buildAgentApiHeaders(
10
+ config: Pick<TelaCliConfig, 'apiKey'>,
11
+ ): Record<string, string> {
12
+ return {
13
+ Authorization: buildAuthorizationHeader(config),
14
+ Accept: 'application/json',
15
+ }
16
+ }
@@ -0,0 +1,35 @@
1
+ import { createDoctorReport, type DoctorReport } from '../diagnostics.ts'
2
+ import { nextActions, resultCard, section, table, type RenderOptions } from '../ui.ts'
3
+
4
+ const STATUS_LABEL: Record<'pass' | 'warn' | 'fail', string> = {
5
+ pass: 'PASS',
6
+ warn: 'WARN',
7
+ fail: 'FAIL',
8
+ }
9
+
10
+ export function renderDoctorReport(report: DoctorReport, options: RenderOptions = {}): string {
11
+ const rows = report.checks.map(check => [
12
+ check.group,
13
+ STATUS_LABEL[check.status],
14
+ check.summary,
15
+ ])
16
+
17
+ const remediation = report.checks
18
+ .filter(check => check.status !== 'pass')
19
+ .map((check) => {
20
+ const fixes = check.fixes.map(fix => `- ${fix}`).join('\n')
21
+ const evidence = check.evidence?.length ? `\n\nEvidence\n${check.evidence.map(item => `- ${item}`).join('\n')}` : ''
22
+ return `${check.summary}\n${fixes}${evidence}`
23
+ })
24
+ .join('\n\n')
25
+
26
+ return resultCard('Tela doctor', [
27
+ section('Checks', table(['Group', 'Status', 'Summary'], rows, options), options),
28
+ remediation ? section('Remediation', remediation, options) : undefined,
29
+ nextActions(report.nextActions, options),
30
+ ], options)
31
+ }
32
+
33
+ export async function getDoctorReport(cwd = process.cwd(), fetchFn: typeof fetch = fetch): Promise<DoctorReport> {
34
+ return await createDoctorReport(cwd, fetchFn)
35
+ }