Skip to content
Merged

Dev #21

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,55 @@ jobs:
See `checksums.txt` in the release assets.
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

# ─── Publish to npm ────────────────────────────────────────────────────────
# @diffmind/cli is a launcher; the binaries ship in five per-platform packages
# declared as its optionalDependencies, so npm installs only the one matching
# the host. All six publish together at the tag version — a partial publish
# would leave @diffmind/cli pointing at versions that do not exist.
npm:
name: Publish npm packages
runs-on: ubuntu-latest
needs: release
if: github.repository_owner == 'thinkgrid-labs' && !contains(github.ref_name, '-')
permissions:
contents: read
id-token: write # required for npm provenance attestations

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "22"
registry-url: "https://registry.npmjs.org"

- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
merge-multiple: true

- name: Stamp versions and unpack binaries
run: node npm/scripts/prepare.mjs --version "${{ github.ref_name }}" --artifacts artifacts

- name: Verify the Linux binary runs
run: |
chmod +x npm/platform/linux-x64/bin/diffmind
npm/platform/linux-x64/bin/diffmind --version

# Platform packages first: @diffmind/cli depends on them, and publishing
# it first would briefly resolve to nothing.
- name: Publish platform packages
run: |
for dir in npm/platform/*/; do
echo "publishing ${dir}"
npm publish "${dir}" --provenance --access public
done
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

- name: Publish @diffmind/cli
run: npm publish npm/cli --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ pkg/
.npm/
.pnpm-debug.log*

# Wasm build output
packages/core-wasm/pkg/
# npm packaging — release binaries are injected by npm/scripts/prepare.mjs
# at publish time and must never be committed
npm/platform/*/bin/
*.tgz

# Model cache (never commit the GGUF file)
*.gguf
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,17 @@ VERSION=v.x.x curl -fsSL https://github.com/thinkgrid-labs/diffmind/releases/lat

Download `diffmind-x86_64-pc-windows-msvc.zip` from [GitHub Releases](https://github.com/thinkgrid-labs/diffmind/releases), extract it, and place `diffmind.exe` anywhere on your `PATH`.

### npm

```bash
npx @diffmind/cli --help

# or install globally
npm install -g @diffmind/cli
```

`@diffmind/cli` is a launcher — the binary ships in a per-platform package (`@diffmind/cli-darwin-arm64`, `@diffmind/cli-linux-x64`, …) declared as an optional dependency, so npm downloads only the one matching your machine. Linux binaries are glibc-linked; on musl (Alpine) use a glibc base image or build from source.

### Build from source (Rust)

```bash
Expand Down
2 changes: 1 addition & 1 deletion apps/tui-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "diffmind"
version = "0.7.0"
version = "0.7.1"
edition = "2024"
description = "Local-first AI code review agent — powered by on-device inference"

Expand Down
28 changes: 14 additions & 14 deletions apps/tui-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -875,20 +875,20 @@ where
};
app_lock.state.select(Some(i));
}
KeyCode::Char('a') => {
if !app_lock.analyzing {
app_lock.analyzing = true;
app_lock.status = "Analyzing...".to_string();
let app_clone = Arc::clone(&app);
tokio::spawn(async move {
let app_err = Arc::clone(&app_clone);
if let Err(e) = background_analysis(app_clone).await {
let mut app = app_err.lock().await;
app.status = format!("Error: {}", e);
app.analyzing = false;
}
});
}
// Guard rather than an inner `if`: pressing 'a' while a run is
// already in flight falls through to the catch-all and is ignored.
KeyCode::Char('a') if !app_lock.analyzing => {
app_lock.analyzing = true;
app_lock.status = "Analyzing...".to_string();
let app_clone = Arc::clone(&app);
tokio::spawn(async move {
let app_err = Arc::clone(&app_clone);
if let Err(e) = background_analysis(app_clone).await {
let mut app = app_err.lock().await;
app.status = format!("Error: {}", e);
app.analyzing = false;
}
});
}
_ => {}
}
Expand Down
54 changes: 54 additions & 0 deletions npm/cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# diffmind

Local-first AI code review for your git diffs. Runs entirely on your machine — no cloud, no API keys, no subscription.

This package is a thin installer for the [diffmind](https://github.com/thinkgrid-labs/diffmind) binary. The actual executable is Rust, shipped as a prebuilt binary in a per-platform package; npm downloads only the one matching your OS and CPU.

```bash
npx @diffmind/cli --help

# or install globally
npm install -g @diffmind/cli
```

## Quick start

```bash
# 1. Download a model (one-time; pick the size that suits your hardware)
diffmind download

# 2. Review the current branch against main
diffmind --branch main
```

`diffmind download` with no arguments shows an interactive picker with the RAM and disk requirements for each model, from Qwen2.5-Coder-0.5B up to 32B. Pick whatever your machine can run — bigger models give better reviews.

## Supported platforms

| Platform | Package |
| --- | --- |
| macOS Apple Silicon | `@diffmind/cli-darwin-arm64` |
| macOS Intel | `@diffmind/cli-darwin-x64` |
| Linux x86_64 | `@diffmind/cli-linux-x64` |
| Linux ARM64 | `@diffmind/cli-linux-arm64` |
| Windows x86_64 | `@diffmind/cli-win32-x64` |

Linux binaries are glibc-linked. On musl (Alpine), use a glibc base image or build from source.

## Not using npm?

npm is one of several install paths, and the others skip Node entirely:

```bash
# macOS / Linux
curl -fsSL https://github.com/thinkgrid-labs/diffmind/releases/latest/download/install.sh | bash

# From source
cargo install --git https://github.com/thinkgrid-labs/diffmind diffmind
```

Full documentation, model list, TUI keybindings, CI usage and `.diffmind/rules.toml` reference: **https://github.com/thinkgrid-labs/diffmind**

## License

MIT
132 changes: 132 additions & 0 deletions npm/cli/bin/diffmind.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
#!/usr/bin/env node
"use strict";

// Thin launcher for the diffmind binary.
//
// The real executable ships in a per-platform package (see optionalDependencies
// in ../package.json). npm installs only the one matching the host's os/cpu, so
// a macOS user never downloads the Windows build. This shim finds it and execs
// it, forwarding argv, stdio and the exit code untouched — `diffmind --tui` and
// the CI gate's exit-1-on-findings both depend on that being transparent.

const { spawnSync } = require("child_process");
const fs = require("fs");
const path = require("path");

const PLATFORMS = {
"darwin arm64": "@diffmind/cli-darwin-arm64",
"darwin x64": "@diffmind/cli-darwin-x64",
"linux x64": "@diffmind/cli-linux-x64",
"linux arm64": "@diffmind/cli-linux-arm64",
"win32 x64": "@diffmind/cli-win32-x64",
};

const RELEASES = "https://github.com/thinkgrid-labs/diffmind/releases";
const INSTALL_SH =
"curl -fsSL https://github.com/thinkgrid-labs/diffmind/releases/latest/download/install.sh | bash";

function fail(lines) {
console.error("\ndiffmind: " + lines.join("\ndiffmind: ") + "\n");
process.exit(1);
}

// The published Linux binaries are glibc-linked (the release matrix has no musl
// target). Detect musl up front — otherwise exec fails with a bare ENOENT that
// looks like a missing file rather than a missing loader.
function isMusl() {
if (process.platform !== "linux") return false;
const report =
typeof process.report?.getReport === "function"
? process.report.getReport()
: null;
if (report && report.header && typeof report.header.glibcVersionRuntime === "string") {
return false;
}
// No glibcVersionRuntime in the report means a non-glibc libc.
return report !== null;
}

function binaryName() {
return process.platform === "win32" ? "diffmind.exe" : "diffmind";
}

// Resolve the platform package. require.resolve handles npm, pnpm and yarn
// node_modules layouts; the relative fallback covers the case where the shim is
// run straight out of a checkout with the packages laid out side by side.
function findBinary(pkg) {
const rel = path.join("bin", binaryName());

try {
const manifest = require.resolve(pkg + "/package.json", { paths: [__dirname] });
const candidate = path.join(path.dirname(manifest), rel);
if (fs.existsSync(candidate)) return candidate;
} catch {
// fall through to the sibling lookup
}

const sibling = path.join(__dirname, "..", "..", "platform", pkg.split("/")[1], rel);
if (fs.existsSync(sibling)) return sibling;

return null;
}

function main() {
const key = process.platform + " " + process.arch;
const pkg = PLATFORMS[key];

if (!pkg) {
fail([
`no prebuilt binary for ${process.platform}-${process.arch}.`,
`Supported: ${Object.keys(PLATFORMS).join(", ")}.`,
`Build from source instead: cargo install --git https://github.com/thinkgrid-labs/diffmind diffmind`,
]);
}

if (isMusl()) {
fail([
"the published Linux binaries are glibc-linked and will not run on musl (Alpine).",
"Use a glibc image (e.g. node:22-slim), or build from source:",
" cargo install --git https://github.com/thinkgrid-labs/diffmind diffmind",
]);
}

const binary = findBinary(pkg);
if (!binary) {
fail([
`the platform package ${pkg} is missing.`,
"This usually means the install ran with --no-optional or --omit=optional.",
"Reinstall with optional dependencies enabled:",
` npm install ${pkg}`,
"",
`Or skip npm entirely: ${INSTALL_SH}`,
`Binaries: ${RELEASES}`,
]);
}

// chmod is a no-op on a correctly packed tarball, but npm has historically
// dropped the executable bit in some install paths; cheap to make sure.
if (process.platform !== "win32") {
try {
fs.chmodSync(binary, 0o755);
} catch {
// Read-only store (pnpm, Nix). If the bit is already set this is fine.
}
}

const result = spawnSync(binary, process.argv.slice(2), { stdio: "inherit" });

if (result.error) {
fail([`failed to run ${binary}: ${result.error.message}`]);
}

// Re-raise a fatal signal so the parent shell sees the real cause (Ctrl-C in
// the TUI must not look like a clean exit 0).
if (result.signal) {
process.kill(process.pid, result.signal);
return;
}

process.exit(result.status === null ? 1 : result.status);
}

main();
48 changes: 48 additions & 0 deletions npm/cli/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "@diffmind/cli",
"version": "0.7.1",
"description": "Local-first AI code review for your git diffs — on-device inference, no cloud, no API keys",
"author": "Thinkgrid Labs <dennis@thinkgrid.dev>",
"license": "MIT",
"homepage": "https://github.com/thinkgrid-labs/diffmind#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/thinkgrid-labs/diffmind.git",
"directory": "npm/cli"
},
"bugs": {
"url": "https://github.com/thinkgrid-labs/diffmind/issues"
},
"keywords": [
"ai",
"code-review",
"local-first",
"offline",
"privacy",
"security",
"git",
"diff",
"cli",
"rust"
],
"publishConfig": {
"access": "public"
},
"bin": {
"diffmind": "./bin/diffmind.js"
},
"files": [
"bin/diffmind.js",
"README.md"
],
"engines": {
"node": ">=18.0.0"
},
"optionalDependencies": {
"@diffmind/cli-darwin-arm64": "0.7.1",
"@diffmind/cli-darwin-x64": "0.7.1",
"@diffmind/cli-linux-arm64": "0.7.1",
"@diffmind/cli-linux-x64": "0.7.1",
"@diffmind/cli-win32-x64": "0.7.1"
}
}
29 changes: 29 additions & 0 deletions npm/platform/darwin-arm64/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "@diffmind/cli-darwin-arm64",
"version": "0.7.1",
"description": "diffmind prebuilt binary for darwin-arm64 (aarch64-apple-darwin)",
"author": "Thinkgrid Labs <dennis@thinkgrid.dev>",
"license": "MIT",
"homepage": "https://github.com/thinkgrid-labs/diffmind#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/thinkgrid-labs/diffmind.git",
"directory": "npm/platform/darwin-arm64"
},
"publishConfig": {
"access": "public"
},
"os": [
"darwin"
],
"cpu": [
"arm64"
],
"files": [
"bin/diffmind"
],
"preferUnplugged": true,
"engines": {
"node": ">=18.0.0"
}
}
Loading
Loading