Skip to content

Development setup

Edit on GitHub
  • Bun 1.4.1+, required by the --bytecode build; bun run build refuses an older Bun (CI pins 1.4.1 — see build and release)
  • TypeScript 5.x (installed as a dev dependency)
  • pre-commit — required, brew install pre-commit or pip install pre-commit
  • Optional: Chrome, Edge, Chromium, or Brave, to exercise auth login-auto
Terminal window
git clone https://github.com/shaharia-lab/slackcli.git
cd slackcli
bun install
pre-commit install # required — constitution §5, not optional

pre-commit install wires up the same checks CI runs, so you find breakage before you push instead of in a red PR. Working hooks are a prerequisite for contributing here: if a hook needs a tool this machine does not have, install it before you start rather than working without the check.

bun.lock is committed, and every CI job that installs the project’s dependencies does so with bun install --frozen-lockfile (ci.yml, both jobs in test.yml, sonar.yml, and release.yml; the site workflows install web/ with npm ci, which is already lockfile-bound). Locally you keep using plain bun install — but whenever you change package.json, commit the regenerated bun.lock alongside it. Otherwise the install step fails with:

error: lockfile had changes, but lockfile is frozen
note: try re-running without --frozen-lockfile and commit the updated lockfile

The check is about what the lockfile can satisfy, not about textual equality: widening a range the locked version still matches needs no new lockfile, while adding a dependency or moving a range past the locked version does.

Terminal window
bun run dev --help # run from source
bun run dev conversations list --json # any command, no build step
bun test # whole suite
bun test src/lib/curl-parser.test.ts # one file
bun test --watch # while iterating
bun run type-check # bunx tsc --noEmit
bun run build # ./dist/slackcli for this platform
bun run build:all # linux + macos + windows

There is no watch/rebuild step for normal work — bun run dev executes the TypeScript directly.

.pre-commit-config.yaml runs on every commit:

Hook Why
trailing-whitespace, end-of-file-fixer Keeps diffs clean
check-yaml, check-json, check-merge-conflict Catches unparseable files
no-commit-to-branch (main, master) Work happens on a branch
actionlint An invalid workflow file is a startup failure — no logs, and scheduled runs are silently never created. CI runs the same pinned hook id, so local and CI cannot drift.
bun run type-check Types must pass
bun test Tests must pass

Run them all by hand with pre-commit run --all-files.

Never bypass a hook — no git commit --no-verify / -n, no SKIP=, no re-running a commit with checks disabled. A failing hook means fix the code. This is constitution §5 in CLAUDE.md, and it binds AI agents as strictly as it binds people.

Commits must be signed — there is a Signed Commits workflow that enforces it. If you have not set signing up:

Terminal window
git config --global commit.gpgsign true
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub

Then add the same public key to GitHub as a signing key (separate from an authentication key). See CONTRIBUTING.md.

The repository policy is binding, and it is stricter than most:

  1. An issue must exist first, describing WHAT, WHY, and optionally HOW.
  2. The issue must carry the ready-for-pr label. A PR opened before the issue is triaged is likely to be rejected even if the code is good.
  3. The PR must link that issue — a PR must link an open issue workflow checks this.
  4. Type-check, tests, and hooks must pass; commits must be signed.

Full text: CONTRIBUTING.md and the constitution in CLAUDE.md.

Running from source uses the same ~/.config/slackcli/workspaces.json as an installed binary. If you want a throwaway identity for testing, add it under its own --profile and pass --workspace=<profile> rather than changing your default.

The self-updater and its “update available” notice are disabled automatically when running under Bun, so bun run dev will never nag you or try to replace a binary that does not exist.

Cookies