@synmux/claude-commit 1.0.3 → 1.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/CHANGELOG.md ADDED
@@ -0,0 +1,238 @@
1
+ # Changelog
2
+
3
+ All notable changes to `claude-commit` are recorded here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project
5
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ Each version also has a [GitHub release](https://github.com/synmux/claude-commit/releases)
8
+ carrying the same notes at greater length.
9
+
10
+ ## [1.1.0] - 2026-09-13
11
+
12
+ ### [1.1.0] - Changed
13
+
14
+ - **Runs on Node, not Bun.** `cco` now requires Node.js 22.18 or later
15
+ (24 LTS recommended) and no longer needs Bun at all. The package ships a
16
+ bundled `dist/` built with esbuild plus type declarations for the library
17
+ entry; the executable is `bin/cco.js`, which runs the TypeScript sources
18
+ directly from a checkout via Node's native type stripping.
19
+ - **The interactive picker is built on Clack** (`@clack/core` +
20
+ `@clack/prompts`) instead of OpenTUI. It looks a little different - a
21
+ title, a hint line, and every candidate shown as its subject plus a
22
+ one-line body preview - and gains `j`/`k` navigation with wrapping and
23
+ `Ctrl-C` to cancel alongside the existing arrow keys, `Enter`, `e` and
24
+ `q`/`Esc`. The list windows itself to the terminal height. It still draws
25
+ on stderr only.
26
+ - Path patterns (`lowPriorityPaths`, `ignore`) are matched by `picomatch`
27
+ instead of `Bun.Glob`. Every well-formed pattern behaves exactly as
28
+ before. Two ill-formed cases differ: an unbalanced `{` now matches
29
+ nothing (it used to match its first alternative), and an unterminated `[`
30
+ now matches its literal text (it used to match nothing).
31
+ - Development moved to pnpm (`pnpm-lock.yaml`, `pnpm-workspace.yaml`),
32
+ vitest (`pnpm test`) and esbuild (`pnpm run build`). The repository's own
33
+ `lowPriorityPaths` deprioritise `pnpm-lock.yaml` instead of `bun.lock`.
34
+ - The npm publish workflow now runs `ci.yml` (lint, typecheck, build, test)
35
+ as a reusable workflow against the tagged commit and only publishes once
36
+ it succeeds. It no longer carries its own test step, which had been
37
+ calling a `typecheck` script that no longer exists.
38
+
39
+ ### [1.1.0] - Removed
40
+
41
+ - The `@opentui/core` and `@types/bun` dependencies, `bun.lock`, and the Bun
42
+ pin in `mise.toml` (now Node 24.20.0).
43
+
44
+ ## [1.0.4] - 2026-09-11
45
+
46
+ ### [1.0.4] - Added
47
+
48
+ - **Filenames-only mode.** Set `"filenamesOnly": true` in your config, or
49
+ pass `-f` / `--filenames-only`, to skip the summariser and send only
50
+ filenames to the final model. This reduces model work at the cost of
51
+ broader, less useful messages; the default remains `false`.
52
+ - Filename lists retain `ignore` filtering and `lowPriorityPaths` weighting,
53
+ including both paths of renames and copies. Formatting, templates, custom
54
+ instructions and interactive options still apply; the model is instructed
55
+ to avoid inventing specific edits or motivations from filenames alone.
56
+ - Verbose output identifies when the summariser was skipped. In this mode,
57
+ library results contain an empty `summaries` array and `chunkCount: 0`,
58
+ and the summary model is never called or preloaded.
59
+
60
+ ## [1.0.3] - 2026-09-11
61
+
62
+ ### Added
63
+
64
+ - **Ollama models.** Any model can run on a local or self-hosted
65
+ [Ollama](https://ollama.com) server by prefixing its name with `ollama:`;
66
+ everything after the prefix is the Ollama model name verbatim, tag
67
+ included. The two pipeline stages resolve independently, so the diff can be
68
+ summarised locally and free while the final message still goes to Claude.
69
+ `cco` uses the native `/api/chat` endpoint rather than either compatibility
70
+ layer, because only the native API can set a context length.
71
+ - **`ollama.context`**, defaulting to `"auto"`. Ollama truncates an oversized
72
+ prompt silently - HTTP 200, oldest content dropped, nothing in the response
73
+ to say so - so `cco` never lets the window stay implicit: it preloads the
74
+ model with no window set, reads the server's own VRAM-based choice back from
75
+ `/api/ps`, pins that number on every request, sizes chunks against it, and
76
+ checks the returned token counts to catch a truncation that happened anyway.
77
+ A number pins the window instead.
78
+ - **`ollama.host`** (defaults to `$OLLAMA_HOST`, then `http://localhost:11434`)
79
+ and **`ollama.keepAlive`**, plus the `--ollama-host` and `--ollama-context`
80
+ flags.
81
+ - **`lowPriorityPaths`.** Gitignore-style patterns for churn-heavy paths whose
82
+ changes are summarised separately, so they cannot crowd out the code in the
83
+ final message. The content is still read and still costs the same; only its
84
+ weight changes. `--no-low-priority-paths` disables it for one run.
85
+ - **`ignore`.** The same pattern syntax, but matching file sections are removed
86
+ from the diff before anything else looks at it - before the low-priority
87
+ partition, before chunking, before any model call. The files are still
88
+ committed; `ignore` governs what the model reads, never what git stages. When
89
+ a pattern matches everything, `cco` stops with an error naming the directive
90
+ rather than inventing a message about changes you told it not to read.
91
+ `--no-ignore` disables it for one run.
92
+
93
+ ### Changed
94
+
95
+ - The default spinner is now `material`.
96
+ - `@opentui/core` bumped to 0.5.7, alongside SDK and skill dependency bumps.
97
+ - CI workflows bump `checkout`, `setup-node` and `claude-code-action`.
98
+
99
+ ## [1.0.2] - 2026-07-27
100
+
101
+ ### [1.0.2] - Changed
102
+
103
+ - The default spinner is now `dwarfFortress` instead of `bouncingBall`.
104
+
105
+ ## [1.0.1] - 2026-07-27
106
+
107
+ ### [1.0.1] - Added
108
+
109
+ - **A configurable spinner.** The `spinner` config key accepts any name from
110
+ the [cli-spinners](https://github.com/sindresorhus/cli-spinners) set bundled
111
+ with [ora](https://github.com/sindresorhus/ora). Unknown names fall back to
112
+ the default rather than throwing, because a cosmetic option must never be
113
+ able to break a commit.
114
+
115
+ ### [1.0.1] - Changed
116
+
117
+ - The progress spinner is an ora instance rather than a hand-rolled frame
118
+ timer. Enablement is still decided solely by the existing TTY and
119
+ `--no-spinner` checks - ora's own CI auto-detection is bypassed - and a final
120
+ success or failure line still prints when the animation is disabled.
121
+
122
+ ## [1.0.0] - 2026-07-24
123
+
124
+ ### [1.0.0] - Added
125
+
126
+ - **`--skip-armored`** and the matching `skipArmored` config key, replacing
127
+ each run of armoured or encoded lines with a short marker. Runs of one or two
128
+ lines survive, because a lone URL or hash is content. Ciphertext is
129
+ unreadable to the model and re-encrypts nondeterministically on every
130
+ `chezmoi re-add`, so this is the recommended mode for repos holding encrypted
131
+ files.
132
+
133
+ ### [1.0.0] - Fixed
134
+
135
+ - **Chunks are sized by real token density.** `Prompt is too long` returned,
136
+ and the isolation fix in 0.1.3 turned out to have treated a symptom. Armoured
137
+ and base64 content tokenises at roughly 1.14 chars per token on current
138
+ Claude models, while chunk budgets assumed the configured `charsPerToken` of
139
+ 3.5: a 1,247,318-character diff was estimated at ~356k tokens and really
140
+ counted ~1.19M. Long unbroken runs are now priced at their own, much denser
141
+ ratio. A third-party "real tokeniser" would not fix this class of bug -
142
+ tiktoken-style vocabularies compress base64 about three times better than
143
+ Claude's actual tokeniser, so they underestimate the same way.
144
+ - **The summary stage is a work queue.** If the backend rejects a chunk anyway
145
+ (its rejection is free, unbilled, and the only authoritative count), the
146
+ budget is halved, that chunk is re-split, and processing continues in place.
147
+
148
+ ### [1.0.0] - Changed
149
+
150
+ - The 0.1.3 documentation blaming these failures on MCP/skill leakage is
151
+ corrected. That isolation stays as hygiene and cost control, but density was
152
+ the bug.
153
+
154
+ ## [0.1.4] - 2026-07-23
155
+
156
+ ### [0.1.4] - Fixed
157
+
158
+ - `cco --version` reported a hardcoded string rather than the installed package
159
+ version.
160
+
161
+ ## [0.1.3] - 2026-07-23
162
+
163
+ ### [0.1.3] - Fixed
164
+
165
+ - **Requests no longer inherit your global Claude Code context.** Runs failed
166
+ reporting ~1,149k tokens against a conversation of only ~301k; the missing
167
+ ~848k were MCP tool definitions and skills pulled from the user's global
168
+ configuration into what should be an isolated prompt-in, text-out request.
169
+ The Agent SDK gates each context source separately, and `settingSources: []`
170
+ disables only settings files and `CLAUDE.md` - MCP servers and plugins load
171
+ regardless, and the CLI performs skill discovery even when the `skills`
172
+ option is omitted. Every switch is now set explicitly in one tested
173
+ `buildQueryOptions()`.
174
+ - **The chunk budget is clamped to the summary model's context window.**
175
+ `maxChunkTokens` was converted straight into a character budget regardless of
176
+ the configured model, so on any 200k-context model a single chunk could
177
+ exceed the whole window. It is now a cap rather than a promise, clamped to
178
+ the model's window minus a 32k reserve.
179
+
180
+ ### [0.1.3] - Added
181
+
182
+ - npm publishing on version tags via CI.
183
+
184
+ ## [0.1.2] - 2026-07-09
185
+
186
+ ### [0.1.2] - Changed
187
+
188
+ - **Both pipeline stages default to `sonnet`.** The final stage's input is a
189
+ handful of summaries and its output is the entire point of the tool, so a
190
+ strong model there costs almost nothing and writes a visibly better message.
191
+
192
+ ### [0.1.2] - Added
193
+
194
+ - MIT licence.
195
+
196
+ ## [0.1.1] - 2026-07-02
197
+
198
+ ### [0.1.1] - Changed
199
+
200
+ - Minor tweaks.
201
+
202
+ ## [0.1.0] - 2026-07-02
203
+
204
+ The first release, published as `@synmux/claude-commit` and providing the `cco`
205
+ and `claude-commit` binaries.
206
+
207
+ ### [0.1.0] - Added
208
+
209
+ - **The two-stage pipeline.** The diff is split into chunks that fit the
210
+ context window, each chunk is summarised, and the summaries are handed back
211
+ to a model to write the final message. Diffs too large for a single context
212
+ window simply produce more chunks.
213
+ - **Message formatting.** Conventional Commits (`-c`), gitmoji (`-g`),
214
+ multi-line subject and body (`-m`), a first-line template (`-t`), and
215
+ free-form extra instructions (`-p`).
216
+ - **Interactive mode.** `cco -i` lists candidate messages in a TUI, generated
217
+ at a higher temperature for variety, with the scrollable diff alongside.
218
+ - **Layered configuration.** Built-in defaults, a global user config, a
219
+ `claude-commit` key in the repo's `package.json`, the nearest dotted config
220
+ file, then CLI flags.
221
+ - **Structured output.** The final message is requested as JSON against a
222
+ `{ messages: string[] }` schema rather than parsed out of free-form text,
223
+ degrading to a temperature-free request and then to delimiter parsing.
224
+ - **`allowApiKey`, defaulting to false.** An exported `ANTHROPIC_API_KEY` would
225
+ otherwise silently switch every generation from subscription auth to
226
+ pay-as-you-go billing, because the SDK subprocess inherits `process.env`.
227
+ Those credentials are stripped unless you opt in.
228
+
229
+ [1.0.4]: https://github.com/synmux/claude-commit/compare/1.0.3...1.0.4
230
+ [1.0.3]: https://github.com/synmux/claude-commit/compare/1.0.2...1.0.3
231
+ [1.0.2]: https://github.com/synmux/claude-commit/compare/1.0.1...1.0.2
232
+ [1.0.1]: https://github.com/synmux/claude-commit/compare/1.0.0...1.0.1
233
+ [1.0.0]: https://github.com/synmux/claude-commit/compare/0.1.4...1.0.0
234
+ [0.1.4]: https://github.com/synmux/claude-commit/compare/0.1.3...0.1.4
235
+ [0.1.3]: https://github.com/synmux/claude-commit/compare/0.1.2...0.1.3
236
+ [0.1.2]: https://github.com/synmux/claude-commit/compare/0.1.1...0.1.2
237
+ [0.1.1]: https://github.com/synmux/claude-commit/compare/0.1.0...0.1.1
238
+ [0.1.0]: https://github.com/synmux/claude-commit/releases/tag/0.1.0
package/README.md CHANGED
@@ -39,19 +39,29 @@ Either stage can run on a local [Ollama](#ollama-models) model instead; by
39
39
  default both go through the
40
40
  [Claude Agent SDK](https://code.claude.com/docs/en/agent-sdk/overview).
41
41
 
42
+ For less model work at the cost of less useful messages, enable
43
+ [`filenamesOnly`](#filenames-only-mode) to skip summarisation entirely.
44
+
42
45
  ## Install
43
46
 
44
- Requires [Bun](https://bun.sh).
47
+ Requires [Node.js](https://nodejs.org) 22.18 or later (24 LTS recommended).
48
+
49
+ ```sh
50
+ npm install -g @synmux/claude-commit # `cco` and `claude-commit` on your PATH
51
+ ```
52
+
53
+ From a checkout, [pnpm](https://pnpm.io) is the package manager (the
54
+ `packageManager` field pins the version, so `corepack enable` is enough):
45
55
 
46
56
  ```sh
47
- bun install
48
- bun link # makes `cco` and `claude-commit` available on your PATH
57
+ pnpm install
58
+ pnpm link --global # makes `cco` and `claude-commit` available on your PATH
49
59
  ```
50
60
 
51
61
  Or run it directly without linking:
52
62
 
53
63
  ```sh
54
- bun run bin/cco.ts --help
64
+ node bin/cco.js --help
55
65
  ```
56
66
 
57
67
  ## Authentication
@@ -85,28 +95,29 @@ and asks for confirmation before committing. Pass `-y` to skip the prompt, or
85
95
 
86
96
  ### Options
87
97
 
88
- | Flag | Description |
89
- | ---------------------------------------- | --------------------------------------------------------------------------------------- |
90
- | `-i, --interactive` / `--no-interactive` | Choose between several options in an interactive TUI, or skip it when enabled in config |
91
- | `-n, --count <n>` | Number of options to generate in interactive mode (default 3) |
92
- | `-a, --all` | Stage all changes (`git add -A`) before committing |
93
- | `-c, --conventional` | Format as a [Conventional Commit](https://www.conventionalcommits.org) |
94
- | `-g, --gitmoji` | Prefix the subject with a [gitmoji](https://gitmoji.dev) |
95
- | `-m, --multiline` / `--no-multiline` | Write a multi-line commit (subject + body), or force a single line |
96
- | `-t, --template <tpl>` | Template for the first line, e.g. `"[PROJ-1] {message}"` |
97
- | `-p, --prompt <text>` | Extra instructions appended to the prompt |
98
- | `--model-summary <model>` | Model used to summarize the diff (default `sonnet`) |
99
- | `--model-final <model>` | Model used to write the message (default `sonnet`) |
100
- | `--skip-armored` | Omit armored/encoded lines (age/gpg armor, base64 blobs) from the summarized diff |
101
- | `--no-low-priority-paths` | Ignore `lowPriorityPaths` for this run, so every change weighs the same |
102
- | `--no-ignore` | Disregard `ignore` for this run, so every staged change is read |
103
- | `--ollama-host <url>` | Base URL of the Ollama server for `ollama:` models |
104
- | `--ollama-context <tokens>` | Context window requested from Ollama models |
105
- | `-d, --dry-run` | Print the message to stdout without committing |
106
- | `-y, --yes` | Commit without asking for confirmation |
107
- | `--no-spinner` | Disable the progress spinner |
108
- | `--config <path>` | Path to a config file |
109
- | `-v, --verbose` | Print summaries, cost and debug output |
98
+ | Flag | Description |
99
+ | ---------------------------------------- | ------------------------------------------------------------------------------------------ |
100
+ | `-i, --interactive` / `--no-interactive` | Choose between several options in an interactive picker, or skip it when enabled in config |
101
+ | `-n, --count <n>` | Number of options to generate in interactive mode (default 3) |
102
+ | `-a, --all` | Stage all changes (`git add -A`) before committing |
103
+ | `-c, --conventional` | Format as a [Conventional Commit](https://www.conventionalcommits.org) |
104
+ | `-g, --gitmoji` | Prefix the subject with a [gitmoji](https://gitmoji.dev) |
105
+ | `-m, --multiline` / `--no-multiline` | Write a multi-line commit (subject + body), or force a single line |
106
+ | `-t, --template <tpl>` | Template for the first line, e.g. `"[PROJ-1] {message}"` |
107
+ | `-p, --prompt <text>` | Extra instructions appended to the prompt |
108
+ | `-f, --filenames-only` | Skip summarisation and send only filenames to the final model |
109
+ | `--model-summary <model>` | Model used to summarize the diff (default `sonnet`) |
110
+ | `--model-final <model>` | Model used to write the message (default `sonnet`) |
111
+ | `--skip-armored` | Omit armored/encoded lines (age/gpg armor, base64 blobs) from the summarized diff |
112
+ | `--no-low-priority-paths` | Ignore `lowPriorityPaths` for this run, so every change weighs the same |
113
+ | `--no-ignore` | Disregard `ignore` for this run, so every staged change is read |
114
+ | `--ollama-host <url>` | Base URL of the Ollama server for `ollama:` models |
115
+ | `--ollama-context <tokens>` | Context window requested from Ollama models |
116
+ | `-d, --dry-run` | Print the message to stdout without committing |
117
+ | `-y, --yes` | Commit without asking for confirmation |
118
+ | `--no-spinner` | Disable the progress spinner |
119
+ | `--config <path>` | Path to a config file |
120
+ | `-v, --verbose` | Print summaries, cost and debug output |
110
121
 
111
122
  ### Examples
112
123
 
@@ -115,20 +126,48 @@ cco # generate, confirm, and commit staged changes
115
126
  cco -a -c # stage everything and write a Conventional Commit
116
127
  cco -c -g -m # conventional + gitmoji + a body
117
128
  cco -i -n 5 # pick from 5 options interactively
118
- cco --dry-run | cat # print a message without committing (TUI-free, pipe-safe)
129
+ cco -f --dry-run # generate from filenames only, without committing
130
+ cco --dry-run | cat # print a message without committing (no picker, pipe-safe)
119
131
  git commit -F <(cco -d) # use the message with your own git invocation
120
132
  ```
121
133
 
122
134
  In a pipe (no TTY) there is no spinner and no confirmation prompt - `cco` just
123
135
  generates and commits (or prints, with `--dry-run`).
124
136
 
137
+ ## Filenames-only mode
138
+
139
+ Set `"filenamesOnly": true` in any config layer, or pass `-f` /
140
+ `--filenames-only`, to send only the list of filenames touched by staged
141
+ changes to `models.final`. The default is `false`.
142
+
143
+ ```json
144
+ { "filenamesOnly": true }
145
+ ```
146
+
147
+ The summariser is skipped entirely: no diff chunks, summary calls, or
148
+ summary-model preload. The final model receives no file contents or diff
149
+ hunks, so expect broader, less useful messages. It is instructed to describe
150
+ the affected areas without inventing specific edits or reasons for them.
151
+ Normal formatting, custom instructions and interactive options still apply.
152
+
153
+ `ignore` still removes matching file sections, and `lowPriorityPaths` still
154
+ groups and weights the remaining filenames. Renames and copies include both
155
+ paths; additions, deletions, binary files and mode changes are included.
156
+ If every staged file is ignored, generation still stops with an error.
157
+
158
+ `models.summary`, `maxChunkTokens`, `charsPerToken` and `skipArmored` have no
159
+ effect in this mode. `--verbose` reports that the summariser was skipped;
160
+ library results have an empty `summaries` array and `chunkCount: 0`.
161
+
125
162
  ## Interactive mode
126
163
 
127
- `cco -i` opens a TUI listing several candidate messages to choose from. The
128
- options are generated with a higher temperature (`interactiveTemperature`) for
129
- more variety. Use the arrow keys to move between options, `Enter` to commit the
130
- highlighted option, `e` to edit it in your `$EDITOR` first, and `q`/`Esc` to
131
- cancel.
164
+ `cco -i` opens a picker (built on [Clack](https://github.com/bombshell-dev/clack))
165
+ listing several candidate messages, each with its subject and a one-line body
166
+ preview. The options are generated with a higher temperature
167
+ (`interactiveTemperature`) for more variety. Use the arrow keys (or `j`/`k`) to
168
+ move between options, `Enter` to commit the highlighted option, `e` to edit it
169
+ in your `$EDITOR` first, and `q`/`Esc`/`Ctrl-C` to cancel. The picker draws on
170
+ stderr, so stdout stays clean.
132
171
 
133
172
  To make interactive mode the default without typing `-i` every time, set
134
173
  `"interactive": true` in your config (see below); opt out of a single run with
@@ -172,6 +211,7 @@ keys are valid at every level:
172
211
  },
173
212
  "maxChunkTokens": 600000,
174
213
  "charsPerToken": 3.5,
214
+ "filenamesOnly": false,
175
215
  "skipArmored": false,
176
216
  "lowPriorityPaths": [],
177
217
  "ignore": [],
@@ -226,7 +266,12 @@ those paths:
226
266
 
227
267
  ```json
228
268
  {
229
- "lowPriorityPaths": [".agents/skills/*-skilld", "bun.lock", "!bun.lock.keep"]
269
+ "lowPriorityPaths": [
270
+ ".agents/skills/*-skilld",
271
+ "pnpm-lock.yaml",
272
+ "generated/**",
273
+ "!generated/schema.ts"
274
+ ]
230
275
  }
231
276
  ```
232
277
 
@@ -244,9 +289,9 @@ usually be copied in:
244
289
  - A pattern with a `/` in it is anchored at the repository root and matches a
245
290
  path or any directory above it - `.agents/skills/*-skilld` covers every file
246
291
  inside each matching directory.
247
- - A pattern without a `/` matches any path segment at any depth - `bun.lock`
248
- matches `packages/app/bun.lock`; `*-skilld` matches everything inside any
249
- `*-skilld` directory.
292
+ - A pattern without a `/` matches any path segment at any depth -
293
+ `pnpm-lock.yaml` matches `packages/app/pnpm-lock.yaml`; `*-skilld` matches
294
+ everything inside any `*-skilld` directory.
250
295
  - `*` matches dotfiles and does not cross `/`; `**` does; `{a,b}` expands. A
251
296
  leading `/` or `./` anchors, a trailing `/` is ignored. The anchoring
252
297
  decision looks at the whole pattern, so a `/` inside a brace group anchors
@@ -399,10 +444,19 @@ Where the number comes from is `ollama.context`:
399
444
  ## Development
400
445
 
401
446
  ```sh
402
- bun test # run the test suite
403
- bun run typecheck # tsc --noEmit
447
+ pnpm test # run the test suite (vitest)
448
+ pnpm run typecheck # tsc --noEmit
449
+ pnpm run build # bundle bin/ and index.ts into dist/ (only needed to publish)
404
450
  ```
405
451
 
452
+ The sources run directly on Node's native type stripping, so there is no build
453
+ step during development: `node bin/cco.js` picks up `dist/` when it exists and
454
+ falls back to the TypeScript entry otherwise.
455
+
456
+ What changed between versions is in [CHANGELOG.md](CHANGELOG.md), and at
457
+ greater length on the
458
+ [releases page](https://github.com/synmux/claude-commit/releases).
459
+
406
460
  ## Did you vibe this?
407
461
 
408
462
  I distinguish vibe coding and AI-assisted development by
package/bin/cco.js ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Launcher for `cco` / `claude-commit`.
4
+ *
5
+ * The published package ships a bundled `dist/bin/cco.js` (Node refuses to
6
+ * strip types from files under `node_modules`); a development checkout runs
7
+ * the TypeScript entry directly through Node's native type stripping. Build
8
+ * output wins when present, so `pnpm run build` is only ever needed for
9
+ * publishing.
10
+ */
11
+ import { existsSync } from "node:fs";
12
+
13
+ const bundled = new URL("../dist/bin/cco.js", import.meta.url);
14
+ const source = new URL("./cco.ts", import.meta.url);
15
+
16
+ await import(existsSync(bundled) ? bundled.href : source.href);
package/bin/cco.ts CHANGED
@@ -1,9 +1,8 @@
1
- #!/usr/bin/env bun
2
1
  /**
3
2
  * Executable entry point for `cco` / `claude-commit`.
4
3
  */
5
- import { run } from "../src/cli";
6
- import { color } from "../src/ui/colors";
4
+ import { run } from "../src/cli.ts";
5
+ import { color } from "../src/ui/colors.ts";
7
6
 
8
7
  run(process.argv.slice(2))
9
8
  .then((code) => {
@@ -11,8 +10,6 @@ run(process.argv.slice(2))
11
10
  })
12
11
  .catch((err) => {
13
12
  // Unexpected (non-ClaudeCommitError) failures: print a stack for debugging.
14
- process.stderr.write(
15
- `${color("31", "unexpected error:")} ${err?.stack ?? err}\n`,
16
- );
13
+ process.stderr.write(`${color("31", "unexpected error:")} ${err?.stack ?? err}\n`);
17
14
  process.exitCode = 1;
18
15
  });