zapdev 0.10.0 → 1.0.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.
Files changed (3) hide show
  1. package/README.md +70 -25
  2. package/dist/cli.js +66 -60
  3. package/package.json +2 -3
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  **zapdev** is a lightweight TypeScript CLI that makes small, repetitive Git chores fast and precise.
6
6
 
7
- It stages changes, scans them for secrets, generates Conventional Commit messages with Ollama, and streamlines repository cleanup.
7
+ It stages changes, scans them for secrets, generates Conventional Commit messages with an OpenAI-compatible LLM endpoint, and streamlines repository cleanup.
8
8
 
9
9
  ## Project Architecture
10
10
 
@@ -16,24 +16,27 @@ flowchart LR
16
16
  Commands --> Types["src/types"]
17
17
  Lib --> Prompts["src/prompts"]
18
18
  Lib --> Types
19
- Lib --> Tools["Git, Gitleaks, Ollama"]
19
+ Lib --> Tools["Git, Gitleaks, LLM API"]
20
20
  ```
21
21
 
22
22
  - `src/index.ts`: bin launcher; enables the V8 compile cache, then loads `cli.js`.
23
23
  - `src/cli.ts`: CLI entry; registers subcommands and opens the interactive menu.
24
24
  - `src/commands/`: command UI and orchestration.
25
- - `src/lib/`: pure logic and isolated Git, Gitleaks, and Ollama side effects.
25
+ - `src/lib/`: pure logic and isolated Git, Gitleaks, and LLM side effects.
26
26
  - `src/prompts/`: LLM prompts inlined into the bundle at build time.
27
27
  - `src/types/`: shared type declarations.
28
28
 
29
29
  ## Environment Variables
30
30
 
31
- | Variable | Default | Required | Description |
32
- | --- | --- | --- | --- |
33
- | `OLLAMA_URL` | `http://localhost:11434` | No | Ollama base URL |
34
- | `OLLAMA_MODEL` | `deepseek-v4-flash:cloud` | No | Model used to generate commit messages |
35
- | `OLLAMA_BACKUP_MODEL` | - | No | Model used when generation with the primary model fails |
36
- | `OLLAMA_EFFORT` | - | No | Ollama thinking effort (`low`, `medium`, `high`, or `max`) |
31
+ | Variable | Required | Description |
32
+ | -------- | -------- | ----------- |
33
+ | `ZD_URL` | For `commit` | Complete HTTP(S) Chat Completions endpoint, including its path |
34
+ | `ZD_MODEL` | For `commit` | Model identifier supported by the endpoint |
35
+ | `ZD_EFFORT` | For `commit` | Sent as `reasoning_effort`; use a value supported by your model, such as `low`, `medium`, or `high` |
36
+
37
+ There are no defaults, automatic provider detection, or backup models. CLI flags override these variables. Legacy `OLLAMA_*` variables are no longer read. Other commands do not require LLM configuration.
38
+
39
+ Requests use the OpenAI Chat Completions format over plain `fetch`, without a provider SDK. No authentication headers are sent; use an endpoint that does not require them. The endpoint and model must support `reasoning_effort`.
37
40
 
38
41
  ## Setup
39
42
 
@@ -41,7 +44,7 @@ flowchart LR
41
44
 
42
45
  - **Node.js >= 20 (required):** runs the CLI.
43
46
  - **Git (required):** provides the repository operations.
44
- - **Ollama (required for `commit`):** generates Conventional Commit messages.
47
+ - **OpenAI-compatible Chat Completions endpoint (required for `commit`):** generates Conventional Commit messages.
45
48
  - **Gitleaks (recommended):** scans staged changes before message generation when available on `PATH`.
46
49
 
47
50
  ### Install
@@ -52,6 +55,14 @@ Install zapdev globally for daily use:
52
55
  npm install -g zapdev
53
56
  ```
54
57
 
58
+ Configure your endpoint and model before running `commit` (replace these example values):
59
+
60
+ ```bash
61
+ export ZD_URL="http://localhost:1234/v1/chat/completions"
62
+ export ZD_MODEL="your-model-id"
63
+ export ZD_EFFORT="low"
64
+ ```
65
+
55
66
  Or run it once without installing:
56
67
 
57
68
  ```bash
@@ -85,27 +96,61 @@ Stages all changes, scans them with Gitleaks when installed, generates a Convent
85
96
  zapdev commit
86
97
  ```
87
98
 
88
- | Flag | Description |
89
- | --- | --- |
90
- | `--model <model>` | Override the Ollama model |
99
+ | Flag | Description |
100
+ | ------------------- | ----------------------------------------------------------------- |
101
+ | `--url <url>` | Override the complete Chat Completions endpoint |
102
+ | `--model <model>` | Override the model |
103
+ | `--effort <effort>` | Override the reasoning effort |
91
104
  | `-t, --type <type>` | Force the Conventional Commit type (`feat`, `fix`, `chore`, etc.) |
92
- | `-p, --push` | Push after committing without asking |
93
- | `-s, --staged` | Commit only changes that are already staged |
94
- | `-r, --rebase` | Rebase on upstream if the push is rejected |
95
- | `-m, --merge` | Merge upstream if the push is rejected |
96
- | `-y, --yes` | Skip prompts and commit directly |
105
+ | `-p, --push` | Push after committing without asking |
106
+ | `-s, --staged` | Commit only changes that are already staged |
107
+ | `-r, --rebase` | Rebase on upstream if the push is rejected |
108
+ | `-m, --merge` | Merge upstream if the push is rejected |
109
+ | `-y, --yes` | Skip prompts and commit directly |
97
110
 
98
111
  ```bash
99
112
  zapdev commit -t feat # force the type
100
113
  zapdev commit --staged # leave unstaged changes untouched
101
114
  ```
102
115
 
103
- Before contacting Ollama, zapdev runs `gitleaks git --staged` when Gitleaks is installed. A failed scan stops the commit; when Gitleaks is absent, the scan is skipped.
116
+ Before contacting the LLM endpoint, zapdev runs `gitleaks git --staged` when Gitleaks is installed. A failed scan stops the commit; when Gitleaks is absent, the scan is skipped. The staged diff is sent to the configured endpoint, which may be remote.
104
117
 
105
118
  Pushing is optimistic, with no preliminary fetch. If the branch is behind upstream, `--rebase` runs `git pull --rebase`, while `--merge` runs `git pull --no-rebase --no-edit`; zapdev then retries once. Without either flag, interactive runs ask whether to rebase, merge, or quit. Runs using `--yes` or without a TTY must provide one of the flags.
106
119
 
107
120
  Without a TTY, zapdev commits automatically and only pushes when `--push` is set.
108
121
 
122
+ ### Zed IDE
123
+
124
+ For a faster review and commit workflow, review and stage changes from Zed's Git panel, then run `zapdev commit -syp` from a task. The command commits only staged changes, skips prompts, and pushes the commit.
125
+
126
+ Add the following tasks to `.zed/tasks.json`:
127
+
128
+ ```json
129
+ [
130
+ {
131
+ "label": "Safely commit staged changes.",
132
+ "command": "zapdev commit -s",
133
+ "reveal": "always",
134
+ "hide": "on_success",
135
+ "reveal_target": "center"
136
+ }
137
+ ]
138
+ ```
139
+
140
+ Add this entry to Zed's `keymap.json` to run the commit task with `ctrl-cmd-enter`:
141
+
142
+ ```json
143
+ {
144
+ "context": "Pane",
145
+ "bindings": {
146
+ "ctrl-cmd-enter": [
147
+ "task::Spawn",
148
+ { "task_name": "Safely commit staged changes." }
149
+ ]
150
+ }
151
+ }
152
+ ```
153
+
109
154
  ### `zapdev reset`
110
155
 
111
156
  Operates on a Git repository or the direct child repositories of a directory. It fetches and prunes, switches branch, then permanently removes other local branches and linked worktrees.
@@ -117,12 +162,12 @@ zapdev reset -p # switch to the principal branch without prompting
117
162
  zapdev reset -t dev # switch to dev or fall back to the principal branch
118
163
  ```
119
164
 
120
- | Flag | Description |
121
- | --- | --- |
122
- | `-p, --principal` | Switch every repo to its resolved principal branch (`origin/HEAD`) |
123
- | `-t, --target <branch>` | Switch to a target branch, falling back to the principal branch |
124
- | `--pull` | Pull the checked-out branch after reset without asking |
125
- | `-y, --yes` | Switch and delete without confirmation |
165
+ | Flag | Description |
166
+ | ----------------------- | ------------------------------------------------------------------ |
167
+ | `-p, --principal` | Switch every repo to its resolved principal branch (`origin/HEAD`) |
168
+ | `-t, --target <branch>` | Switch to a target branch, falling back to the principal branch |
169
+ | `--pull` | Pull the checked-out branch after reset without asking |
170
+ | `-y, --yes` | Switch and delete without confirmation |
126
171
 
127
172
  Deletion is permanent. Branches are removed with `git branch -D`; worktrees are removed with `git worktree remove --force`. Without a TTY, pass `--yes` or the destructive step is refused. `node_modules` is never scanned.
128
173
 
package/dist/cli.js CHANGED
@@ -1718,8 +1718,8 @@ function _getBuiltinFlags(long, short, userNames, userAliases) {
1718
1718
  // package.json
1719
1719
  var package_default = {
1720
1720
  name: "zapdev",
1721
- version: "0.10.0",
1722
- description: "Fast, performant and lightweight git chores and dev tools using your terminal and your own local LLM.",
1721
+ version: "1.0.0",
1722
+ description: "Fast, performant and lightweight git chores and dev tools using your terminal and an OpenAI-compatible LLM endpoint.",
1723
1723
  type: "module",
1724
1724
  bin: {
1725
1725
  zapdev: "./dist/index.js"
@@ -1735,7 +1735,6 @@ var package_default = {
1735
1735
  "cli",
1736
1736
  "commit",
1737
1737
  "conventional-commits",
1738
- "ollama",
1739
1738
  "llm",
1740
1739
  "ai"
1741
1740
  ],
@@ -1812,22 +1811,25 @@ function sanitizeCommitMessage(raw) {
1812
1811
  }
1813
1812
 
1814
1813
  // src/lib/config.ts
1815
- var DEFAULT_OLLAMA_URL = "http://localhost:11434";
1816
- var DEFAULT_MODEL = "deepseek-v4-flash:cloud";
1817
1814
  function resolveConfig(env = process.env, overrides = {}) {
1818
- return {
1819
- ollamaUrl: normalizeBaseUrl(overrides.ollamaUrl ?? env.OLLAMA_URL ?? DEFAULT_OLLAMA_URL),
1820
- model: overrides.model ?? env.OLLAMA_MODEL ?? DEFAULT_MODEL,
1821
- backupModel: overrides.backupModel ?? optionalValue(env.OLLAMA_BACKUP_MODEL),
1822
- effort: overrides.effort ?? optionalValue(env.OLLAMA_EFFORT)
1815
+ const config = {
1816
+ url: (overrides.url ?? env.ZD_URL)?.trim() ?? "",
1817
+ model: (overrides.model ?? env.ZD_MODEL)?.trim() ?? "",
1818
+ effort: (overrides.effort ?? env.ZD_EFFORT)?.trim() ?? ""
1823
1819
  };
1824
- }
1825
- function optionalValue(value) {
1826
- return value?.trim() || void 0;
1827
- }
1828
- function normalizeBaseUrl(raw) {
1829
- const trimmed = raw.trim().replace(/\/+$/, "");
1830
- return /^https?:\/\//.test(trimmed) ? trimmed : `http://${trimmed}`;
1820
+ for (const [key, value] of Object.entries(config)) {
1821
+ if (!value) throw new Error(`Set ZD_${key.toUpperCase()} or --${key} before generating a commit message.`);
1822
+ }
1823
+ let url;
1824
+ try {
1825
+ url = new URL(config.url);
1826
+ } catch {
1827
+ throw new Error("URL must be a complete HTTP(S) Chat Completions endpoint.");
1828
+ }
1829
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
1830
+ throw new Error("URL must be a complete HTTP(S) Chat Completions endpoint.");
1831
+ }
1832
+ return config;
1831
1833
  }
1832
1834
 
1833
1835
  // src/lib/git.ts
@@ -2329,65 +2331,49 @@ var commit_message_default = "You are zapdev, a CLI tool that creates commits qu
2329
2331
  // src/prompts/index.ts
2330
2332
  var COMMIT_SYSTEM_PROMPT = commit_message_default.trim();
2331
2333
 
2332
- // src/lib/ollama.ts
2334
+ // src/lib/llm.ts
2333
2335
  var REQUEST_TIMEOUT_MS = 25e3;
2334
- var KEEP_ALIVE = "30m";
2335
2336
  async function generateCommitMessage(diff, config, type) {
2336
2337
  const systemPrompt = type ? applyCommitType(COMMIT_SYSTEM_PROMPT, type) : COMMIT_SYSTEM_PROMPT;
2337
2338
  const messages = [
2338
2339
  { role: "system", content: systemPrompt },
2339
2340
  { role: "user", content: truncateDiff(diff) }
2340
2341
  ];
2341
- let content;
2342
- try {
2343
- content = await chat(config, config.model, messages);
2344
- } catch (error) {
2345
- if (!config.backupModel || config.backupModel === config.model) throw error;
2346
- try {
2347
- content = await chat(config, config.backupModel, messages);
2348
- } catch (backupError) {
2349
- throw new AggregateError(
2350
- [error, backupError],
2351
- `Primary model failed: ${errorMessage(error)} Backup model failed: ${errorMessage(backupError)}`,
2352
- { cause: backupError }
2353
- );
2354
- }
2355
- }
2356
- return sanitizeCommitMessage(content);
2357
- }
2358
- async function chat(config, model, messages) {
2359
- const url = `${config.ollamaUrl}/api/chat`;
2360
2342
  let response;
2343
+ let body;
2361
2344
  try {
2362
- response = await fetch(url, {
2345
+ response = await fetch(config.url, {
2363
2346
  method: "POST",
2364
2347
  headers: { "content-type": "application/json" },
2365
2348
  body: JSON.stringify({
2366
- model,
2349
+ model: config.model,
2367
2350
  stream: false,
2368
- keep_alive: KEEP_ALIVE,
2369
- think: config.effort,
2370
- options: { temperature: 0.2 },
2351
+ reasoning_effort: config.effort,
2371
2352
  messages
2372
2353
  }),
2373
2354
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
2374
2355
  });
2356
+ const text2 = await response.text();
2357
+ try {
2358
+ body = JSON.parse(text2);
2359
+ } catch {
2360
+ body = null;
2361
+ }
2375
2362
  } catch (error) {
2376
- throw new Error(describeRequestError(error, url), { cause: error });
2363
+ throw new Error(describeRequestError(error), { cause: error });
2377
2364
  }
2378
- const body = await response.json().catch(() => null);
2379
2365
  if (!response.ok) {
2380
- throw new Error(`Ollama error: ${serverError(body) ?? `HTTP ${response.status}`}`);
2366
+ throw new Error(`LLM error: ${serverError(body) ?? `HTTP ${response.status}`}`);
2381
2367
  }
2382
2368
  const content = messageContent(body);
2383
- if (content === null) throw new Error("Ollama returned an unexpected response shape.");
2384
- return content;
2369
+ if (content === null) throw new Error("LLM returned an unexpected response shape.");
2370
+ return sanitizeCommitMessage(content);
2385
2371
  }
2386
- function describeRequestError(error, url) {
2372
+ function describeRequestError(error) {
2387
2373
  if (errorName(error) === "TimeoutError") {
2388
- return `Ollama did not answer within ${REQUEST_TIMEOUT_MS / 1e3}s (${url}).`;
2374
+ return `LLM did not answer within ${REQUEST_TIMEOUT_MS / 1e3}s.`;
2389
2375
  }
2390
- return `Could not reach Ollama at ${url}. Is it running?`;
2376
+ return "Could not complete the LLM request. Check URL and endpoint availability.";
2391
2377
  }
2392
2378
  function errorName(error) {
2393
2379
  if (error && typeof error === "object" && "name" in error && typeof error.name === "string") {
@@ -2396,14 +2382,18 @@ function errorName(error) {
2396
2382
  return null;
2397
2383
  }
2398
2384
  function serverError(body) {
2399
- if (body && typeof body === "object" && "error" in body && typeof body.error === "string") {
2400
- return body.error;
2385
+ if (!body || typeof body !== "object" || !("error" in body)) return null;
2386
+ const error = body.error;
2387
+ if (error && typeof error === "object" && "message" in error && typeof error.message === "string") {
2388
+ return error.message;
2401
2389
  }
2402
2390
  return null;
2403
2391
  }
2404
2392
  function messageContent(body) {
2405
- if (!body || typeof body !== "object" || !("message" in body)) return null;
2406
- const message = body.message;
2393
+ if (!body || typeof body !== "object" || !("choices" in body) || !Array.isArray(body.choices)) return null;
2394
+ const choice = body.choices[0];
2395
+ if (!choice || typeof choice !== "object" || !("message" in choice)) return null;
2396
+ const message = choice.message;
2407
2397
  if (!message || typeof message !== "object" || !("content" in message)) return null;
2408
2398
  return typeof message.content === "string" ? message.content : null;
2409
2399
  }
@@ -2415,9 +2405,17 @@ var commitCommand = defineCommand({
2415
2405
  description: "Stage all changes and commit with an LLM-generated Conventional Commits message."
2416
2406
  },
2417
2407
  args: {
2408
+ url: {
2409
+ type: "string",
2410
+ description: "Override $ZD_URL, the complete Chat Completions endpoint."
2411
+ },
2418
2412
  model: {
2419
2413
  type: "string",
2420
- description: "Override the Ollama model (defaults to $OLLAMA_MODEL)."
2414
+ description: "Override the model configured with $ZD_MODEL."
2415
+ },
2416
+ effort: {
2417
+ type: "string",
2418
+ description: "Override $ZD_EFFORT, sent as reasoning_effort."
2421
2419
  },
2422
2420
  type: {
2423
2421
  type: "string",
@@ -2452,10 +2450,6 @@ var commitCommand = defineCommand({
2452
2450
  },
2453
2451
  async run({ args }) {
2454
2452
  const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
2455
- const config = resolveConfig(
2456
- process.env,
2457
- args.model ? { model: args.model } : {}
2458
- );
2459
2453
  if (args.rebase && args.merge) {
2460
2454
  log.error("Choose either --rebase or --merge, not both.");
2461
2455
  process.exitCode = 1;
@@ -2470,6 +2464,18 @@ var commitCommand = defineCommand({
2470
2464
  process.exitCode = 1;
2471
2465
  return;
2472
2466
  }
2467
+ let config;
2468
+ try {
2469
+ config = resolveConfig(process.env, {
2470
+ url: args.url,
2471
+ model: args.model,
2472
+ effort: args.effort
2473
+ });
2474
+ } catch (error) {
2475
+ log.error(errorMessage(error));
2476
+ process.exitCode = 1;
2477
+ return;
2478
+ }
2473
2479
  if (interactive) intro("zapdev commit");
2474
2480
  if (!args.staged) await stageAll();
2475
2481
  const diff = await getStagedDiff();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "zapdev",
3
- "version": "0.10.0",
4
- "description": "Fast, performant and lightweight git chores and dev tools using your terminal and your own local LLM.",
3
+ "version": "1.0.0",
4
+ "description": "Fast, performant and lightweight git chores and dev tools using your terminal and an OpenAI-compatible LLM endpoint.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "zapdev": "./dist/index.js"
@@ -17,7 +17,6 @@
17
17
  "cli",
18
18
  "commit",
19
19
  "conventional-commits",
20
- "ollama",
21
20
  "llm",
22
21
  "ai"
23
22
  ],