@synmux/claude-commit 1.0.0 → 1.0.1
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 +8 -0
- package/package.json +5 -2
- package/src/cli.ts +1 -1
- package/src/config.ts +5 -0
- package/src/types.ts +6 -0
- package/src/ui/interactive.ts +1 -1
- package/src/ui/spinner.ts +47 -39
package/README.md
CHANGED
|
@@ -151,6 +151,7 @@ keys are valid at every level:
|
|
|
151
151
|
"interactive": true,
|
|
152
152
|
"interactiveCount": 3,
|
|
153
153
|
"interactiveTemperature": 1,
|
|
154
|
+
"spinner": "bouncingBall",
|
|
154
155
|
"models": {
|
|
155
156
|
"summary": "sonnet",
|
|
156
157
|
"final": "sonnet"
|
|
@@ -162,6 +163,13 @@ keys are valid at every level:
|
|
|
162
163
|
}
|
|
163
164
|
```
|
|
164
165
|
|
|
166
|
+
`spinner` chooses the progress animation: any name from the
|
|
167
|
+
[cli-spinners](https://github.com/sindresorhus/cli-spinners) set bundled with
|
|
168
|
+
[ora](https://github.com/sindresorhus/ora) (`"dots"`, `"moon"`, `"pong"`,
|
|
169
|
+
`"bouncingBall"`, ...). Unknown names are ignored and the default
|
|
170
|
+
`bouncingBall` is used. `--no-spinner` disables the animated spinner, but final
|
|
171
|
+
status lines still print.
|
|
172
|
+
|
|
165
173
|
`maxChunkTokens` is a cap, not a promise: at run time it is clamped to the
|
|
166
174
|
summary model's context window minus a fixed reserve (1M-window models such as
|
|
167
175
|
current Sonnet/Opus keep the full budget; Haiku, older pinned model ids, and
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synmux/claude-commit",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Generate git commit messages with Claude, using your Claude Code subscription.",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"module": "index.ts",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"interactive": false,
|
|
25
25
|
"interactiveCount": 3,
|
|
26
26
|
"interactiveTemperature": 1,
|
|
27
|
+
"spinner": "bouncingBall",
|
|
27
28
|
"models": {
|
|
28
29
|
"summary": "sonnet",
|
|
29
30
|
"final": "sonnet"
|
|
@@ -45,7 +46,9 @@
|
|
|
45
46
|
"dependencies": {
|
|
46
47
|
"@anthropic-ai/claude-agent-sdk": "^0.3.218",
|
|
47
48
|
"@opentui/core": "^0.4.5",
|
|
48
|
-
"
|
|
49
|
+
"cli-spinners": "^3.4.0",
|
|
50
|
+
"commander": "^15.0.0",
|
|
51
|
+
"ora": "^9.4.1"
|
|
49
52
|
},
|
|
50
53
|
"repository": {
|
|
51
54
|
"type": "git",
|
package/src/cli.ts
CHANGED
|
@@ -256,7 +256,7 @@ async function runNonInteractive(
|
|
|
256
256
|
abortController: AbortController,
|
|
257
257
|
): Promise<number> {
|
|
258
258
|
const useSpinner = opts.spinner !== false && process.stderr.isTTY;
|
|
259
|
-
const spinner = new Spinner(useSpinner);
|
|
259
|
+
const spinner = new Spinner(useSpinner, config.spinner);
|
|
260
260
|
|
|
261
261
|
spinner.start("Reading diff");
|
|
262
262
|
let result;
|
package/src/config.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
11
11
|
import { homedir } from "node:os";
|
|
12
12
|
import { ClaudeCommitError } from "./errors";
|
|
13
|
+
import { DEFAULT_SPINNER, isSpinnerName } from "./ui/spinner";
|
|
13
14
|
import type { Config, ModelConfig, PartialConfig } from "./types";
|
|
14
15
|
|
|
15
16
|
export const DEFAULT_CONFIG: Config = {
|
|
@@ -21,6 +22,7 @@ export const DEFAULT_CONFIG: Config = {
|
|
|
21
22
|
interactive: false,
|
|
22
23
|
interactiveCount: 3,
|
|
23
24
|
interactiveTemperature: 1,
|
|
25
|
+
spinner: DEFAULT_SPINNER,
|
|
24
26
|
models: {
|
|
25
27
|
summary: "sonnet",
|
|
26
28
|
final: "sonnet",
|
|
@@ -116,6 +118,9 @@ export function sanitizePartial(raw: unknown): PartialConfig {
|
|
|
116
118
|
Math.max(0, obj.interactiveTemperature),
|
|
117
119
|
);
|
|
118
120
|
}
|
|
121
|
+
if (typeof obj.spinner === "string" && isSpinnerName(obj.spinner)) {
|
|
122
|
+
out.spinner = obj.spinner;
|
|
123
|
+
}
|
|
119
124
|
if (typeof obj.maxChunkTokens === "number" && obj.maxChunkTokens > 0) {
|
|
120
125
|
out.maxChunkTokens = Math.floor(obj.maxChunkTokens);
|
|
121
126
|
}
|
package/src/types.ts
CHANGED
|
@@ -40,6 +40,12 @@ export interface Config {
|
|
|
40
40
|
* at its default. Only applied in interactive mode.
|
|
41
41
|
*/
|
|
42
42
|
interactiveTemperature: number | null;
|
|
43
|
+
/**
|
|
44
|
+
* Name of the progress spinner animation: any spinner from the cli-spinners
|
|
45
|
+
* set bundled with ora (e.g. `"dots"`, `"moon"`, `"bouncingBall"`). Unknown
|
|
46
|
+
* names are ignored and the default is used instead.
|
|
47
|
+
*/
|
|
48
|
+
spinner: string;
|
|
43
49
|
/** Models for each pipeline stage. */
|
|
44
50
|
models: ModelConfig;
|
|
45
51
|
/**
|
package/src/ui/interactive.ts
CHANGED
|
@@ -30,7 +30,7 @@ export async function runInteractive(
|
|
|
30
30
|
opts: InteractiveOptions,
|
|
31
31
|
): Promise<number> {
|
|
32
32
|
const count = Math.max(1, config.interactiveCount);
|
|
33
|
-
const spinner = new Spinner(process.stderr.isTTY);
|
|
33
|
+
const spinner = new Spinner(process.stderr.isTTY, config.spinner);
|
|
34
34
|
spinner.start(`Generating ${count} option${count === 1 ? "" : "s"}`);
|
|
35
35
|
let result;
|
|
36
36
|
try {
|
package/src/ui/spinner.ts
CHANGED
|
@@ -1,48 +1,71 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The progress spinner for non-interactive runs, rendered by `ora`.
|
|
3
3
|
*
|
|
4
|
-
* It
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* It writes to stderr (so stdout stays clean for piping) and only animates when
|
|
5
|
+
* explicitly enabled: callers gate on `process.stderr.isTTY` and `--no-spinner`,
|
|
6
|
+
* and ora's own TTY/CI detection is bypassed so that decision lives in one
|
|
7
|
+
* place. When disabled, `start`/`update` are silent no-ops but final status
|
|
8
|
+
* lines (`succeed`/`fail`) still print. This keeps the progress indicator out
|
|
9
|
+
* of the way of `cco --dry-run | git commit -F -`.
|
|
10
|
+
*
|
|
11
|
+
* The animation is chosen by the `spinner` config key: any name from the
|
|
12
|
+
* cli-spinners set bundled with ora, defaulting to {@link DEFAULT_SPINNER}.
|
|
7
13
|
*/
|
|
14
|
+
import ora, { type Ora } from "ora";
|
|
15
|
+
import spinners, { type Spinner as SpinnerAnimation } from "cli-spinners";
|
|
8
16
|
import { color } from "./colors";
|
|
9
17
|
|
|
10
|
-
|
|
11
|
-
const
|
|
18
|
+
/** The spinner used when none (or an unknown one) is configured. */
|
|
19
|
+
export const DEFAULT_SPINNER = "bouncingBall";
|
|
20
|
+
|
|
21
|
+
/** Whether `name` is one of the cli-spinners animations bundled with ora. */
|
|
22
|
+
export function isSpinnerName(name: string): boolean {
|
|
23
|
+
return Object.hasOwn(spinners, name);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Look up a spinner animation by name, falling back to {@link DEFAULT_SPINNER}
|
|
28
|
+
* for unknown names (ora itself throws on those, and a cosmetic option must
|
|
29
|
+
* never be able to break a commit).
|
|
30
|
+
*/
|
|
31
|
+
export function resolveSpinner(name: string): SpinnerAnimation {
|
|
32
|
+
const known = (spinners as Record<string, SpinnerAnimation | undefined>)[
|
|
33
|
+
name
|
|
34
|
+
];
|
|
35
|
+
return known ?? spinners[DEFAULT_SPINNER];
|
|
36
|
+
}
|
|
12
37
|
|
|
13
38
|
export class Spinner {
|
|
14
|
-
private
|
|
15
|
-
private frame = 0;
|
|
16
|
-
private label = "";
|
|
39
|
+
private instance: Ora | null = null;
|
|
17
40
|
private readonly enabled: boolean;
|
|
41
|
+
private readonly animation: SpinnerAnimation;
|
|
18
42
|
|
|
19
|
-
constructor(enabled = process.stderr.isTTY) {
|
|
43
|
+
constructor(enabled = process.stderr.isTTY, spinnerName = DEFAULT_SPINNER) {
|
|
20
44
|
this.enabled = Boolean(enabled);
|
|
45
|
+
this.animation = resolveSpinner(spinnerName);
|
|
21
46
|
}
|
|
22
47
|
|
|
23
48
|
start(label: string): void {
|
|
24
|
-
this.label = label;
|
|
25
49
|
if (!this.enabled) return;
|
|
26
|
-
this.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
50
|
+
this.instance?.stop();
|
|
51
|
+
this.instance = ora({
|
|
52
|
+
text: label,
|
|
53
|
+
spinner: this.animation,
|
|
54
|
+
stream: process.stderr,
|
|
55
|
+
// The caller already decided (TTY check + --no-spinner); don't let ora's
|
|
56
|
+
// own TTY/CI detection silently disagree.
|
|
57
|
+
isEnabled: true,
|
|
58
|
+
}).start();
|
|
33
59
|
}
|
|
34
60
|
|
|
35
61
|
update(label: string): void {
|
|
36
|
-
this.
|
|
37
|
-
if (this.enabled && this.timer) this.render();
|
|
62
|
+
if (this.instance) this.instance.text = label;
|
|
38
63
|
}
|
|
39
64
|
|
|
40
65
|
/** Stop and clear the spinner line, optionally printing a final status line. */
|
|
41
66
|
stop(finalLine?: string): void {
|
|
42
|
-
this.
|
|
43
|
-
|
|
44
|
-
process.stderr.write("\r\x1b[2K\x1b[?25h"); // clear line, show cursor
|
|
45
|
-
}
|
|
67
|
+
this.instance?.stop();
|
|
68
|
+
this.instance = null;
|
|
46
69
|
if (finalLine !== undefined) process.stderr.write(finalLine + "\n");
|
|
47
70
|
}
|
|
48
71
|
|
|
@@ -53,19 +76,4 @@ export class Spinner {
|
|
|
53
76
|
fail(label: string): void {
|
|
54
77
|
this.stop(`${color("31", "✖")} ${label}`);
|
|
55
78
|
}
|
|
56
|
-
|
|
57
|
-
private render(): void {
|
|
58
|
-
// `\r\x1b[2K` (cursor return + clear line) only runs when enabled (a TTY);
|
|
59
|
-
// the frame color additionally respects NO_COLOR via the helper.
|
|
60
|
-
process.stderr.write(
|
|
61
|
-
`\r\x1b[2K${color("36", FRAMES[this.frame]!)} ${this.label}`,
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
private stopTimer(): void {
|
|
66
|
-
if (this.timer) {
|
|
67
|
-
clearInterval(this.timer);
|
|
68
|
-
this.timer = null;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
79
|
}
|