Skip to content
Merged
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
64 changes: 64 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,70 @@ jobs:
- name: Compile standalone binary
run: bun build --compile --outfile "release/${{ matrix.build_output }}" ./src/index.ts

# macOS code signing + notarization (optional). Requires the
# following GitHub Actions secrets to be set on the repository:
# APPLE_DEVELOPER_ID_APPLICATION_CERT_BASE64
# base64-encoded .p12 of "Developer ID Application: <name>" cert
# APPLE_DEVELOPER_ID_APPLICATION_CERT_PASSWORD
# password used when exporting the .p12
# APPLE_ID — Apple ID email
# APPLE_APP_SPECIFIC_PASSWORD — app-specific pwd for notarytool
# APPLE_TEAM_ID — 10-char Team ID
# Without these the step is skipped and the binary ships ad-hoc
# signed (which still satisfies the macOS arm64 SIGKILL-on-launch
# check, but Gatekeeper will warn on first run from outside the
# Mac App Store).
- name: Codesign + notarize (macOS)
if: runner.os == 'macOS' && env.APPLE_DEVELOPER_ID_APPLICATION_CERT_BASE64 != ''
env:
APPLE_DEVELOPER_ID_APPLICATION_CERT_BASE64: ${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_CERT_BASE64 }}
APPLE_DEVELOPER_ID_APPLICATION_CERT_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_CERT_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
set -euo pipefail
KEYCHAIN_PATH="$RUNNER_TEMP/build.keychain"
KEYCHAIN_PASSWORD=$(uuidgen)

security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"

echo "$APPLE_DEVELOPER_ID_APPLICATION_CERT_BASE64" | base64 --decode > "$RUNNER_TEMP/cert.p12"
security import "$RUNNER_TEMP/cert.p12" \
-P "$APPLE_DEVELOPER_ID_APPLICATION_CERT_PASSWORD" \
-k "$KEYCHAIN_PATH" -T /usr/bin/codesign
security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | sed s/\"//g)
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" >/dev/null

IDENTITY=$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" \
| awk -F\" '/Developer ID Application/ { print $2; exit }')
if [[ -z "$IDENTITY" ]]; then
echo "::error::Could not locate Developer ID Application identity in keychain."
exit 1
fi

codesign --force --options runtime --timestamp \
--sign "$IDENTITY" \
"release/${{ matrix.build_output }}"

# Notarize. Bun-compiled binaries must be inside a .zip for notarytool.
ditto -c -k --keepParent "release/${{ matrix.build_output }}" "$RUNNER_TEMP/notarize.zip"
xcrun notarytool submit "$RUNNER_TEMP/notarize.zip" \
--apple-id "$APPLE_ID" \
--password "$APPLE_APP_SPECIFIC_PASSWORD" \
--team-id "$APPLE_TEAM_ID" \
--wait

# Standalone binaries (not .app bundles) cannot be stapled,
# but the notary record is cached by Apple's CDN and Gatekeeper
# will look it up online on first launch.

# Cleanup
security delete-keychain "$KEYCHAIN_PATH"
rm -f "$RUNNER_TEMP/cert.p12" "$RUNNER_TEMP/notarize.zip"

- name: Compute checksum (unix)
if: runner.os != 'Windows'
shell: bash
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/typecheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,8 @@ jobs:
- name: Type Check
run: bun run typecheck

- name: Test
run: bun run test

- name: Build standalone binary
run: bun run build:binary
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ Grok CLI (`@vibe-kit/grok-cli`) is a single-package TypeScript CLI tool — no d
| CLI help | `node dist/index.js --help` |


### Known issues
### Linting

- **ESLint config is broken**: The repo has `.eslintrc.js` (legacy format) but uses ESLint 9 (`^9.31.0`) + `@typescript-eslint` v8, which require flat config (`eslint.config.js`). Additionally, `.eslintrc.js` uses `module.exports` (CJS) but `package.json` has `"type": "module"` (ESM). Running `bun run lint` will fail. Use `bun run typecheck` as the primary code quality check (this is also what CI enforces).
- **Dev mode (`bun run dev` / `bun run dev:node`) fails at runtime**: `src/utils/model-config.ts` imports TypeScript interfaces (`UserSettings`, `ProjectSettings`) as value imports from `settings-manager.ts`. These type-only exports are erased at runtime by Bun and tsx, causing `SyntaxError: export '...' not found`. The fix is to use `import type` syntax, but this is a pre-existing repo issue. **Workaround**: build first (`bun run build`), then run the compiled version (`node dist/index.js`).
Biome is the only linter (`bun run lint` → `biome check src/`). There is no
ESLint config; `bun run typecheck` and `bun run lint` both run in CI.

### Environment

Expand Down
125 changes: 125 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,131 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.7.0] - 2026-04-28

Full-spectrum security, performance, release-engineering, and DX hardening
based on a six-agent code review of the entire codebase.

### Security
- **Wallet private keys are now AES-256-GCM encrypted at rest.** Previously
`~/.grok/wallet.json` stored the privateKey in plaintext (mode 0600).
Existing plaintext wallets are migrated to encrypted form transparently
on first read. Encryption key derives from `GROK_STORAGE_KEY` (preferred)
or a per-machine fallback.
- **Schedule daemon and detached headless runs no longer spread `process.env`.**
Replaced with an explicit allowlist (`PATH`/`HOME`/`SHELL`/`USER`/`LANG`/
`TERM`/`TMPDIR`/`TZ`/`EDITOR` plus `GROK_*`/`NODE_*`/`BUN_*`/`LC_*`/`XDG_*`
prefixes) and an explicit blocklist for `TELEGRAM_BOT_TOKEN`,
`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `AWS_SECRET_ACCESS_KEY`,
`AWS_SESSION_TOKEN`. Bounds the blast radius if the daemon's env is
visible via `/proc` on Linux or process listings.
- **Schedule directory traversal hardened.** `validateScheduleDirectory()`
realpath-resolves the target, enforces `isDirectory`, and rejects
sensitive system roots (`/etc`, `/usr`, `/sbin`, `/bin`, `/boot`,
`/proc`, `/sys`, `/dev`, `/root`, `/System`, `/Library`, `/Applications`,
and the `/private/*` macOS mirrors). Validation runs at both schedule
create time and detached spawn time, so a tampered stored schedule
cannot escape.
- **Per-user Telegram rate limit.** Sliding-window cap (default 10
messages / 60s, tunable via `GROK_TELEGRAM_RATE_LIMIT_MAX` and
`GROK_TELEGRAM_RATE_LIMIT_WINDOW_MS`). Bounds API spend if the bot
token leaks or an approved user goes rogue.
- **Sandbox-off warning banner.** When the agent is about to run shell
commands directly on the host (current `--no-sandbox` default), a
yellow stderr banner prints once at startup. Suppressible via
`GROK_SUPPRESS_SANDBOX_WARNING=1`. Default behavior unchanged
(flipping to `--sandbox` default is reserved for a major version bump).

### Release engineering
- **Atomic, size-verified install.** `install.sh` and the in-process
auto-update path (`src/utils/install-manager.ts`) now HEAD-probe
Content-Length, verify post-download size on disk, retry transient
failures with exponential backoff, refuse to proceed on an empty
`checksums.txt`, and write to a `.new` / `.part` staging file before
atomic rename. Roots out a real-world failure mode where a 71 MB
release landed as a 21 MB truncated binary that passed the HTTP
success check but got SIGKILL'd by macOS Gatekeeper at first launch.
- **Optional macOS codesign + notarization** in the release workflow
(`.github/workflows/release.yml`), gated on five GitHub Actions
secrets. Documented in [`docs/RELEASE_SIGNING.md`](docs/RELEASE_SIGNING.md).
Without the secrets, the workflow ships unsigned binaries (current
behavior).
- **Vitest now runs in CI** between typecheck and binary build. Forty-nine
test files / 281 tests previously never ran on PR.
- **Fixed stale `superagent-ai/grok-cli` reference** in the in-process
update checker; auto-updates now resolve correctly to
`alphaonedev/grok-cli`. README/Pages still credit upstream as the
fork source (intentional attribution).

### Performance
- **OpenTUI subsystem and 21 unused tree-sitter packages removed.** The
`src/ui/` directory (12 files, 7,541 LOC) was unmaintained and never
loaded — only `src/ui-ink/` is wired to `src/index.ts`. Dropping
`@opentui/core`, `@opentui/react`, `web-tree-sitter`, every
`tree-sitter-*` dep, the brittle `postinstall` hook (which patched a
hardcoded chunk filename inside `@opentui/core`), and the `patches/`
directory removes 25 packages and ~15–20 MB from the standalone
binary.
- **Markdown re-parse storm during streaming fixed.** `MarkdownView`
accepts a `streaming` prop that debounces parses to 120ms while the
buffer is rapidly growing (~8/sec instead of ~50/sec). Static
completed messages still parse synchronously and benefit from Ink's
`<Static>` memoization.
- **Tool-result lookup is O(1).** Replaced `tools.find(t => t.call.id === ...)`
with a `Map<string, entry>` indexed by call id.

### Code quality
- **`noUncheckedIndexedAccess` enabled** in `tsconfig.json`. Resolved
all 82 surfaced array/record accesses across 17 files (refactored
bounded for-loops to `for..of`, added explicit guards or `??` fallbacks
at parse boundaries, applied non-null assertions where index validity
was already established by control flow).
- **Silent fire-and-forget `.catch(() => {})` replaced with logger
breadcrumbs.** New `src/utils/debug-log.ts` writes to
`~/.grok/debug.log` only when `GROK_DEBUG` is set. 26 sites now
leave a trail without changing observable behavior — hook/MCP/clipboard
failures become debuggable.
- **Crash log writer** (`src/utils/crash-log.ts`). On uncaught
exception or unhandled rejection, a sanitized snapshot (timestamp,
kind, version, node, platform, argv, cwd, scoped env, full stack)
is appended to `~/.grok/crash.log` (mode 0600). Secrets like
`GROK_API_KEY`, `TELEGRAM_BOT_TOKEN`, `sk-*`, `xai-*`, `ghp_*`,
Telegram bot-token shapes are redacted before write.
- **Differentiated exit codes:** 0 (success), 1 (user error), 2
(transient), 3 (agent/tool error), 4 (panic). Documented in
[`docs/HEADLESS_JSON_SPEC.md`](docs/HEADLESS_JSON_SPEC.md).

### UX
- **Missing-API-key error now points to <https://console.x.ai>** with
formatted setup instructions for all three valid paths (env var,
CLI flag, settings file).
- **Removed JSX import source dependency on `@opentui/react`** in
`tsconfig.json`. Standard React JSX is used throughout (Ink-native).

### Documentation
- New [`docs/HEADLESS_JSON_SPEC.md`](docs/HEADLESS_JSON_SPEC.md):
full schema for the `--format json` JSONL stream — all five event
types, ordering guarantees, exit-code matrix, pipe-friendly `jq`
examples.
- New [`docs/RELEASE_SIGNING.md`](docs/RELEASE_SIGNING.md): macOS
codesign + notarization setup with the five required GitHub Actions
secrets and step-by-step `.p12` export instructions.
- README and GitHub Pages site (`docs/index.html`) updated to credit
React Ink for the terminal UI.
- `AGENTS.md` "Known issues" section corrected — both previously
documented bugs (`bun run dev` import-type issue, ESLint flat-config
mismatch) had already been fixed in source but never reflected in
the doc.

### Tests
- **49 test files / 281 tests** (was 47 / 257). Three modules
previously had zero coverage and now have unit tests:
`src/storage/migrations.test.ts` (4 tests, in-memory mock of the
SQLiteDatabase interface), `src/hooks/config.test.ts` (12 tests
covering `isHookEvent`, `getMatchingHooks`, `getMatchQuery`),
`src/payments/service.test.ts` (8 tests covering
`formatInspectionOutput` paths and Brin scan rendering).

## [1.1.3] - 2026-04-01

### Added
Expand Down
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -542,8 +542,54 @@ Other useful commands:
bun run dev # run from source (Bun)
bun run typecheck
bun run lint
bun run test # vitest, also gated in CI as of v1.7.0
```

### Optional environment variables

| Variable | What it does |
|---|---|
| `GROK_DEBUG=1` | Enable per-call debug logging to `~/.grok/debug.log` (covers swallowed `.catch` paths in hooks/MCP/clipboard). Off by default. |
| `GROK_SUPPRESS_SANDBOX_WARNING=1` | Silence the yellow stderr banner that prints when `--sandbox` is off. |
| `GROK_TELEGRAM_RATE_LIMIT_MAX` | Max messages per user before pause (default `10`). |
| `GROK_TELEGRAM_RATE_LIMIT_WINDOW_MS` | Sliding window in ms (default `60000`). |
| `GROK_STORAGE_KEY` | Override the default per-machine key derivation for AES-256-GCM at-rest encryption (DB fields, wallet private key). |

If the agent crashes, a sanitized snapshot is appended to `~/.grok/crash.log`
(mode 0600). The path is also printed by the panic handler. Secrets
(`GROK_API_KEY`, `TELEGRAM_BOT_TOKEN`, `sk-*`/`xai-*`/`ghp_*`/Telegram
bot-token shapes) are redacted before write.

### Headless / CI integration

Headless runs (`grok --prompt "..." --format json`) emit one JSON event
per line. The full schema with all five event types and a jq cookbook
lives in [`docs/HEADLESS_JSON_SPEC.md`](docs/HEADLESS_JSON_SPEC.md). Exit
codes are differentiated:

| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | User error (bad flag, missing API key) |
| 2 | Transient (network, rate-limit) |
| 3 | Agent / tool execution error |
| 4 | Internal panic |

---

## Built with

The interactive console is built with **[React Ink](https://github.com/vadimdemedes/ink)**
(Vadim Demedes' React renderer for terminal UIs) on the **[Bun](https://bun.sh/)**
runtime. Markdown rendering uses **[marked](https://github.com/markedjs/marked)**
+ **[marked-terminal](https://github.com/mikaelbr/marked-terminal)** with
**[chalk](https://github.com/chalk/chalk)** for ANSI colors. The agent
loop talks to the xAI API via **[Vercel AI SDK](https://github.com/vercel/ai)**.
Schema validation uses **[zod](https://github.com/colinhacks/zod)**.
Telegram integration uses **[grammY](https://grammy.dev/)**. Local tests
run on **[Vitest](https://vitest.dev/)**; lint and format with
**[Biome](https://biomejs.dev/)**.

---

## License
Expand Down
Loading
Loading