# SANDCODE.md

This file provides guidance to SANDCODE when working with code in this repository.

## What is SANDCODE

A fully independent, privately branded AI coding CLI. Uses **z.ai GLM-5.1** as the default AI model via `api.z.ai/api/anthropic`. Zero dependency on upstream npm packages — the SDK lives at `src/vendor/sandcode-sdk/`.

## Development Commands

```bash
# Install (also runs postinstall: vendor copy + ripgrep download)
npm install

# Run from source
node bin/sandcode.js          # auto-detects bun or tsx

# Link globally
npm link                      # then use `sandcode` anywhere

# Production build (requires Bun)
bun run build.ts              # outputs dist/cli.js

# Type checking
npm run typecheck             # tsc --noEmit

# Linting
npm run lint                  # eslint src/
npm run lint:fix              # eslint src/ --fix

# Formatting
npm run format                # prettier --write src/**/*.{ts,tsx}
npm run format:check          # prettier --check

# Testing
npm run test                  # vitest run (all tests)
npm run test:watch            # vitest watch
npm run test:coverage         # vitest run --coverage
npx vitest run src/services/api/__tests__/openaiCompatAdapter.test.ts   # single test file
```

### Requirements

- Node.js >= 22, tsx >= 4 (or Bun >= 1.2)
- After `npm install`, verify vendor packages exist in `node_modules/@sandcode/` — `npm install` wipes `node_modules` before the postinstall hook restores them

## Key Architecture

### Entry Flow

`bin/sandcode.js` → spawns `tsx` (or `bun`) → `src/entrypoints/cli.tsx` → `src/main.tsx`

The bin script detects Bun or tsx, sets up MACRO globals and `bun:bundle` shims, then spawns the entry point. `cli.tsx` has fast-paths for `--version`, `--help`, etc. Everything else loads `main.tsx` which sets up Commander, config, and the Ink React UI.

### Runtime Shims (`shims/`)

The shims bridge `bun:bundle` APIs for tsx dev mode:
- `loader.js` — registers the `bun-bundle-hook.js` module resolution hook for tsx
- `bun-bundle-hook.js` — redirects `bun:bundle` imports to `shims/bun_bundle.js`, handles `.md` file imports, `.node`/`.napi` stubs
- `bun-preload.ts` — defines `globalThis.MACRO` for Bun dev mode
- `bun_bundle.js` — `feature()` always returns `false` in dev mode (tests stub it the same way via `src/test/setup.ts`)

### MACRO Globals

Build-time constants defined in `build.ts` and `shims/bun-preload.ts`:
- `MACRO.VERSION` — from `src/version.ts` (single source of truth, update there)
- `MACRO.BUILD_TIME`, `MACRO.PACKAGE_URL`, `MACRO.ISSUES_EXPLAINER`, `MACRO.FEEDBACK_CHANNEL`
- `MACRO.NATIVE_PACKAGE_URL`, `MACRO.VERSION_CHANGELOG` — set to `undefined`

### `bun:bundle` and `feature()`

Code uses `import { feature } from 'bun:bundle'` for feature flags. In dev/test mode, `feature()` always returns `false`. In production builds, Bun's bundler evaluates these at build time — only enabled code is included in the bundle.

### React Compiler Output

Most `.tsx` files in `src/components/` are **React Compiler output** with `_c()` memoization caches. When editing:
- Only change string literals and prop values
- NEVER change `$[N]` cache indices or `_c(N)` cache sizes
- `Symbol.for("react.memo_cache_sentinel")` checks are one-time init — don't duplicate

### Path Aliases

TypeScript and Vitest both resolve `src/*` to `./src/*`. Import as:
```typescript
import { something } from 'src/utils/foo.js'
```
All source imports use `.js` extensions (ESM convention for TypeScript).

### Vendor Packages (`src/vendor/`)

Vendored copies of `@anthropic-ai/sdk` (as `@sandcode/sdk`), `color-diff-napi` (stub), `sandbox-runtime` (stub). The postinstall script (`scripts/copy-vendor.js`) copies these to `node_modules/@sandcode/` and `node_modules/color-diff-napi/`.

### z.ai API Integration

- Config: `src/utils/zaiSetupPrompt.ts` — plain readline prompt before Ink renders (avoids keybinding conflicts)
- Client: `src/services/api/client.ts` — z.ai branch creates the vendored `Sandcode` SDK client pointed at `api.z.ai/api/anthropic`
- Provider detection: `src/utils/model/providers.ts` — `isZaiProvider()` checks env and config
- Model configs: `src/utils/model/configs.ts` — GLM model entries in `ALL_MODEL_CONFIGS`
- Context window: `src/utils/context.ts` — GLM models get 200k

### Plugin Compatibility

Plugins using legacy `.claude-plugin/` directory names still work. SANDCODE has fallback logic in `src/utils/plugins/marketplaceManager.ts` that checks `.claude-plugin/` when `.sandcode-plugin/` is not found. The hook system also expands both `${SANDCODE_PLUGIN_ROOT}` and `${CLAUDE_PLUGIN_ROOT}` for backward compat (these literal names are wire-visible external contracts — do not rename).

### Branding Constants

- Theme colors: `src/utils/theme.ts` — amber/gold palette (`rgb(245,166,35)`)
- Figures: `src/constants/figures.ts` — `TEARDROP_ASTERISK = '◈'` (diamond glyph)
- Turn verbs: `src/constants/turnCompletionVerbs.ts` — building theme (Forged, Crafted, etc.)
- Product URL: `src/constants/product.ts` — `https://sandcode.sh`
- Logo: `src/components/LogoV2/Clawd.tsx` — "SAND" box-drawing wordmark
- Distro defaults: `src/distro/defaults.ts` — `defaultProvider: 'zai'`, `defaultModel: 'GLM-5.1'`

### Config Hierarchy

`src/distro/defaults.ts` < `~/.sandcode/config.json` (user) < `.sandcode/settings.json` (project)

### Distro System (`src/distro/`)

SANDCODE uses a distro configuration layer (`SANDCODE_DISTRO`) that defines identity, default provider/model, branding, and feature flags (autoMemory, flow, forge, instincts, selfPatching, etc.). This is the lowest-priority config — everything else overrides it.

### Directory Layout

```
bin/            CLI entry point
shims/          bun:bundle and MACRO shims for tsx
scripts/        postinstall vendor copy
src/
  entrypoints/  cli.tsx (fast paths + main import)
  main.tsx      Commander setup, startup, Ink root
  screens/      REPL.tsx (main conversation loop)
  components/   React/Ink UI components (compiler output)
  services/     API client, analytics, compact, MCP, OAuth
  tools/        Tool implementations (Bash, Edit, Read, etc.)
  commands/     Slash commands (/init, /model, /compact, etc.)
  utils/        Config, auth, model, theme, hooks, plugins
  constants/    Prompts, figures, betas, tool limits
  rc/           Remote control server (Tailscale + WebSocket)
  recipes/      Recipe system (bundled workflows)
  distro/       SANDCODE identity defaults + welcome
  vendor/       Vendored @sandcode/sdk, stubs
  test/         Test setup (vitest global stubs)
```

## ESLint Rules

The project uses `@eslint/js` + `typescript-eslint` + `eslint-config-prettier`. Notable rules:
- `@typescript-eslint/no-unused-vars`: warn, args prefixed with `_` ignored
- `@typescript-eslint/no-explicit-any`: warn
- `no-console`: warn (only `console.warn` and `console.error` allowed)

## Telemetry

All first-party telemetry endpoints are disabled (metrics, feedback, transcript sharing return no-ops). Set `SANDCODE_METRICS_ENDPOINT` to enable your own telemetry server.
