• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gnk-softworks / onpush-cli / 23694001720

28 Mar 2026 08:47PM UTC coverage: 34.443%. Remained the same
23694001720

push

github

Kawba
Initial commit

188 of 205 branches covered (91.71%)

Branch coverage included in aggregate %.

1085 of 3491 new or added lines in 46 files covered. (31.08%)

1085 of 3491 relevant lines covered (31.08%)

1.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/cli/ui/progress.ts
NEW
1
import chalk from "chalk";
×
2
import type { CostSummary } from "../../generation/cost.js";
3

4
export interface ProgressRenderer {
5
  header(projectName: string, model: string): void;
6
  generationType(type: "full" | "incremental", changesInfo?: string): void;
7
  triageResult(affectedTypes: string[]): void;
8
  startDocument(index: number, total: number, name: string): void;
9
  finishDocument(
10
    name: string,
11
    status: "generated" | "updated" | "failed",
12
    durationMs: number,
13
    costUsd: number,
14
    error?: string
15
  ): void;
16
  skipDocument(name: string): void;
17
  costLimitExceeded(currentCost: number, limit: number): void;
18
  summary(
19
    docsGenerated: number,
20
    docsSkipped: number,
21
    docsFailed: number,
22
    costSummary: CostSummary,
23
    durationMs: number,
24
    outputDir: string
25
  ): void;
26
  error(message: string): void;
27
}
28

NEW
29
export function createProgressRenderer(options: {
×
30
  ci: boolean;
31
  quiet: boolean;
32
  verbose: boolean;
33
  provider?: string;
NEW
34
}): ProgressRenderer {
×
NEW
35
  const showCost = options.provider !== "copilot";
×
NEW
36
  if (options.quiet) {
×
NEW
37
    return createQuietRenderer();
×
NEW
38
  }
×
NEW
39
  if (options.ci || !process.stdout.isTTY) {
×
NEW
40
    return createCiRenderer(showCost);
×
NEW
41
  }
×
NEW
42
  return createTtyRenderer(options.verbose, showCost);
×
NEW
43
}
×
44

NEW
45
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
×
46

47
interface DocLine {
48
  index: number;
49
  total: number;
50
  name: string;
51
  status: "in_progress" | "generated" | "updated" | "failed";
52
  durationMs?: number;
53
  costUsd?: number;
54
}
55

NEW
56
function createTtyRenderer(verbose: boolean, showCost: boolean): ProgressRenderer {
×
NEW
57
  const docs = new Map<string, DocLine>();
×
58

NEW
59
  return {
×
NEW
60
    header(projectName: string, model: string) {
×
NEW
61
      console.log();
×
NEW
62
      console.log(
×
NEW
63
        `  ${chalk.bold("OnPush")} — Generating documentation for ${chalk.cyan(projectName)}`
×
NEW
64
      );
×
NEW
65
      console.log(`  Model: ${chalk.dim(model)}`);
×
NEW
66
      console.log();
×
NEW
67
    },
×
68

NEW
69
    generationType(type: "full" | "incremental", changesInfo?: string) {
×
NEW
70
      if (type === "incremental" && changesInfo) {
×
NEW
71
        console.log(`  ${chalk.dim(changesInfo)}`);
×
NEW
72
        console.log();
×
NEW
73
      }
×
NEW
74
    },
×
75

NEW
76
    triageResult(affectedTypes: string[]) {
×
NEW
77
      if (verbose) {
×
NEW
78
        console.log(
×
NEW
79
          `  Agent analyzing changes...\n  Affected document types: ${chalk.cyan(affectedTypes.join(", "))}`
×
NEW
80
        );
×
NEW
81
        console.log();
×
NEW
82
      }
×
NEW
83
    },
×
84

NEW
85
    startDocument(index: number, total: number, name: string) {
×
NEW
86
      docs.set(name, { index, total, name, status: "in_progress" });
×
NEW
87
      console.log(`  [${index + 1}/${total}] ${name} ${chalk.cyan("generating...")}`);
×
NEW
88
    },
×
89

NEW
90
    finishDocument(
×
NEW
91
      name: string,
×
NEW
92
      status: "generated" | "updated" | "failed",
×
NEW
93
      durationMs: number,
×
NEW
94
      costUsd: number,
×
NEW
95
      error?: string
×
NEW
96
    ) {
×
NEW
97
      const doc = docs.get(name);
×
NEW
98
      if (doc) {
×
NEW
99
        doc.status = status;
×
NEW
100
        doc.durationMs = durationMs;
×
NEW
101
        doc.costUsd = costUsd;
×
102

NEW
103
        const prefix = `  [${doc.index + 1}/${doc.total}] ${doc.name} `;
×
NEW
104
        const duration = (durationMs / 1000).toFixed(1);
×
NEW
105
        if (status === "failed") {
×
NEW
106
          console.log(`${prefix}${chalk.red("✖ failed")}`);
×
NEW
107
        } else {
×
NEW
108
          const meta = showCost
×
NEW
109
            ? `(${duration}s, $${costUsd.toFixed(4)})`
×
NEW
110
            : `(${duration}s)`;
×
NEW
111
          console.log(`${prefix}${chalk.green(`✔ ${status}`)} ${chalk.dim(meta)}`);
×
NEW
112
        }
×
NEW
113
        if (error) {
×
NEW
114
          console.log(chalk.red(`    Error: ${error}`));
×
NEW
115
        }
×
NEW
116
        docs.delete(name);
×
NEW
117
      }
×
NEW
118
    },
×
119

NEW
120
    skipDocument(name: string) {
×
NEW
121
      console.log(`  ${chalk.dim(`${name} — unchanged, skipped`)}`);
×
NEW
122
    },
×
123

NEW
124
    costLimitExceeded(currentCost: number, limit: number) {
×
NEW
125
      console.log();
×
NEW
126
      console.log(
×
NEW
127
        chalk.yellow(
×
NEW
128
          `  Cost limit exceeded: $${currentCost.toFixed(4)} > $${limit.toFixed(4)}`
×
NEW
129
        )
×
NEW
130
      );
×
NEW
131
      console.log(chalk.yellow(`  Remaining documents skipped.`));
×
NEW
132
    },
×
133

NEW
134
    summary(
×
NEW
135
      docsGenerated: number,
×
NEW
136
      docsSkipped: number,
×
NEW
137
      docsFailed: number,
×
NEW
138
      costSummary: CostSummary,
×
NEW
139
      durationMs: number,
×
NEW
140
      outputDir: string
×
NEW
141
    ) {
×
NEW
142
      const duration = (durationMs / 1000).toFixed(1);
×
143

NEW
144
      console.log();
×
NEW
145
      const parts: string[] = [];
×
NEW
146
      if (docsGenerated > 0)
×
NEW
147
        parts.push(`${docsGenerated} document${docsGenerated > 1 ? "s" : ""} generated`);
×
NEW
148
      if (docsSkipped > 0)
×
NEW
149
        parts.push(`${docsSkipped} unchanged`);
×
NEW
150
      if (docsFailed > 0) parts.push(chalk.red(`${docsFailed} failed`));
×
151

NEW
152
      console.log(`  ${parts.join(", ")} in ${duration}s`);
×
NEW
153
      if (showCost) {
×
NEW
154
        const tokenSummary = `${(costSummary.totalInputTokens / 1000).toFixed(1)}K in / ${(costSummary.totalOutputTokens / 1000).toFixed(1)}K out`;
×
NEW
155
        console.log(
×
NEW
156
          `  Total cost: ${chalk.bold(`$${costSummary.totalCostUsd.toFixed(4)}`)} | Tokens: ${tokenSummary}`
×
NEW
157
        );
×
NEW
158
      }
×
NEW
159
      console.log(`  Output: ${chalk.cyan(outputDir)}`);
×
NEW
160
      console.log();
×
NEW
161
    },
×
162

NEW
163
    error(message: string) {
×
NEW
164
      console.error(chalk.red(`  Error: ${message}`));
×
NEW
165
    },
×
NEW
166
  };
×
NEW
167
}
×
168

NEW
169
function createCiRenderer(showCost: boolean): ProgressRenderer {
×
NEW
170
  return {
×
NEW
171
    header(projectName: string, model: string) {
×
NEW
172
      console.log(`[onpush] Generating documentation for ${projectName}`);
×
NEW
173
      console.log(`[onpush] Model: ${model}`);
×
NEW
174
    },
×
NEW
175
    generationType(type: string) {
×
NEW
176
      console.log(`[onpush] Generation type: ${type}`);
×
NEW
177
    },
×
NEW
178
    triageResult(affectedTypes: string[]) {
×
NEW
179
      console.log(
×
NEW
180
        `[onpush] Affected types: ${affectedTypes.join(", ")}`
×
NEW
181
      );
×
NEW
182
    },
×
NEW
183
    startDocument(index: number, total: number, name: string) {
×
NEW
184
      console.log(`[onpush] [${index + 1}/${total}] Generating ${name}...`);
×
NEW
185
    },
×
NEW
186
    finishDocument(
×
NEW
187
      name: string,
×
NEW
188
      status: string,
×
NEW
189
      durationMs: number,
×
NEW
190
      costUsd: number,
×
NEW
191
      error?: string
×
NEW
192
    ) {
×
NEW
193
      const meta = showCost
×
NEW
194
        ? `(${(durationMs / 1000).toFixed(1)}s, $${costUsd.toFixed(4)})`
×
NEW
195
        : `(${(durationMs / 1000).toFixed(1)}s)`;
×
NEW
196
      console.log(`[onpush] ${name}: ${status} ${meta}`);
×
NEW
197
      if (error) {
×
NEW
198
        console.log(`[onpush] ERROR: ${name}: ${error}`);
×
NEW
199
      }
×
NEW
200
    },
×
NEW
201
    skipDocument(name: string) {
×
NEW
202
      console.log(`[onpush] ${name}: skipped (unchanged)`);
×
NEW
203
    },
×
NEW
204
    costLimitExceeded(currentCost: number, limit: number) {
×
NEW
205
      console.log(
×
NEW
206
        `[onpush] WARNING: Cost limit exceeded ($${currentCost.toFixed(4)} > $${limit.toFixed(4)})`
×
NEW
207
      );
×
NEW
208
    },
×
NEW
209
    summary(
×
NEW
210
      docsGenerated: number,
×
NEW
211
      docsSkipped: number,
×
NEW
212
      docsFailed: number,
×
NEW
213
      costSummary: CostSummary,
×
NEW
214
      durationMs: number,
×
NEW
215
    ) {
×
NEW
216
      console.log(
×
NEW
217
        `[onpush] Done: ${docsGenerated} generated, ${docsSkipped} skipped, ${docsFailed} failed`
×
NEW
218
      );
×
NEW
219
      if (showCost) {
×
NEW
220
        console.log(
×
NEW
221
          `[onpush] Cost: $${costSummary.totalCostUsd.toFixed(4)} | Duration: ${(durationMs / 1000).toFixed(1)}s`
×
NEW
222
        );
×
NEW
223
      } else {
×
NEW
224
        console.log(`[onpush] Duration: ${(durationMs / 1000).toFixed(1)}s`);
×
NEW
225
      }
×
NEW
226
    },
×
NEW
227
    error(message: string) {
×
NEW
228
      console.error(`[onpush] ERROR: ${message}`);
×
NEW
229
    },
×
NEW
230
  };
×
NEW
231
}
×
232

NEW
233
function createQuietRenderer(): ProgressRenderer {
×
NEW
234
  return {
×
NEW
235
    header() {},
×
NEW
236
    generationType() {},
×
NEW
237
    triageResult() {},
×
NEW
238
    startDocument() {},
×
NEW
239
    finishDocument() {},
×
NEW
240
    skipDocument() {},
×
NEW
241
    costLimitExceeded() {},
×
NEW
242
    summary() {},
×
NEW
243
    error(message: string) {
×
NEW
244
      console.error(message);
×
NEW
245
    },
×
NEW
246
  };
×
NEW
247
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc