Your build is a terminal.

pici is a CI system where every build step runs in an attachable terminal session. Your pipeline is a bash script. It runs the same locally and in CI.

CI gives you logs. Not answers.

A build fails. You scroll through 2,000 lines of log output. You guess what went wrong. You re-run the job and wait 10 minutes to find out you were wrong. Repeat.

Debugging CI shouldn't require you to guess what happened.

ci-myrepo-test: build log
Line 1847: ...
Line 1848: ...
Line 1849: ...
Line 1850: FAIL: test_connect (db.TimeoutError)
Line 1851: ...
Line 1852: ...
...
# scroll up... scroll up... scroll up...
# what was the state before this?
# re-run the job? wait 10 more minutes?

Attach to your failing build.

Every build step in pici runs as a real terminal session: a PTY you can attach to. No "enable debugging" or "re-run with SSH" buttons.

A test fails? Attach to the session, press + Enter to rerun the command, inspect the environment, and fix the issue before trying again.

zmx attach ci.myrepo.test
$ zmx attach ci.myrepo.test
→ attached to session ci.myrepo.test
 
$ go test ./...
ok myapp/core 8.2s
FAIL myapp/api 11.4s
Error: dial tcp 10.0.1.5:5432: connection refused
 
# ↑ you're in the live session
# ↑ press ↑ + Enter to rerun, or inspect freely
 
$ cat .env | grep DB
DB_HOST=postgres.internal:5432
 
$ nslookup postgres.internal
;; no servers could be reached
 
# ↑ found it: DNS not resolving in this container

Parallel tasks, zero config, and zero install.

pico.sh
#!/usr/bin/env bash
set -euo pipefail
 
# This step runs until it's finished
zmx run lint docker run golangci-lint run
→ No issues found!
 
# These run in parallel
zmx run test -d go test ./...
→ started session: ci.myrepo.test
 
zmx run build -d go build -o bin/pici .
→ started session: ci.myrepo.build
 
# Wait for all tasks to finish
zmx wait "*"
 
# Runs identically locally and in CI

Your pipeline is pico.sh: a bash script that runs identically on your machine and on the CI runner; no yaml required.

zmx is the job engine. Each zmx run spawns a parallel terminal session. zmx wait "*" blocks until they all finish. Run tasks sequentially by just calling them one after another, or in parallel by using the detach (-d) flag.

  • Same zmx run commands locally and in CI
  • Sequential by default, parallel when you want it
  • Every step is an attachable terminal session
  • Bring your own isolation: use docker in your step commands
  • No YAML matrices, no run: parallel keywords

Git is optional. rsync + ssh pubsub is the core.

Most CI systems are glued to Git webhooks. pici isn't. The core loop is simple: rsync a workspace and publish an event over SSH.

Git post-receive hooks are just one trigger. You can fire builds from anything: a cron job, a file watcher, a webhook receiver on your own server, a button press.

Managed CI at ci.pico.sh.

Trigger builds by publishing events over SSH. Your pipeline runs as real terminal sessions you can attach to. No web console, no API keys, no OAuth.

The managed service runs on our own hardware, not a cloud provider. Self-hosted is coming once the runner is ready.

  • SSH keys are your auth
  • SSH pubsub is your event bus
  • Our hardware, not a hyperscaler
  • Self-hosted coming soon
ci.pico.sh
$ echo '{"type":"push"}' | ssh pipe.pico.sh pub build.event
subscribe to this channel: ssh pipe.pico.sh sub build.event
 
🚀 starting job ci.myrepo.a3f2b8c1
📦 syncing workspace
✅ workspace ready
🏃 launching sessions...
✅ job launched

CI that works for you, not against you.

pici is built for individuals and small teams who want to move fast. You write a bash script, you push your code, your build runs, and if something breaks you jump into the terminal and fix it. No YAML labyrinths, no approval workflows, no complex pipeline DAGs.

We're not building marketplace integrations, compliance reports, or workflow bureaucracy. What you will find is the CI system you'd write for yourself.

Powered by cd.pico.sh

ci.pico.sh runs on cd.pico.sh: our SSH VM service on hardware we own.

Push a docker-compose.yml to an SSH endpoint and your containers are live. Label a service and it gets a public HTTP URL. There's no provider lock-in because it's a tool you likely already use for local development.

The same platform that runs your CI runs your apps.

  • Docker Compose via git push
  • Expose HTTP services with compose labels
  • Our hardware, not a hyperscaler
  • Limited hardware availability: when it's gone, it's gone
cd.pico.sh
--- docker-compose.yml ---
services:
  echo:
    build: .
    networks:
      - default
      - picd-ingress
    labels:
      traefik.enable: true
      traefik.http.routers.echo.rule: Host(`echo-<user>.apps.pico.sh`)
 
networks:
  picd-ingress:
    external: true
 
$ git remote add picd ssh://cd.pico.sh/user/project.git
$ git push picd main
 
→ containers live
→ echo-<user>.apps.pico.sh is live

Built for terminals, humans, and automation

Terminal Friendly

Because pici doesn't rely on git-ops and triggering a build is done with rsync + ssh, we have the building blocks for you or a code agent to trigger builds, monitor progress, and attach to failures. Start as many jobs as you need and read results synchronously.

SSH-First

SSH keys are your auth and we even support using SSH certificates for RBAC control. Rsync to upload workspaces, build artifacts, and ssh pubsub as your event bus.

Static Site Artifacts

Build artifacts are plain HTML + CSS. There's no app server or build step. Serve the directory with any static host: nginx, s3, pgs.sh, python -m http.server. It's a static site with zero runtime dependencies.

Build Attestation

Automatic provenance baked into every job: runner hostname, OS, arch, repo, branch, commit, and workspace checksum.

The difference is the terminal.

Other CI
build log
$ go test ./...
ok myapp/db 12.4s
FAIL myapp/api (14.2s)
Error: connection refused
...
 
# ← that's all you get
# ← can't inspect the environment
# ← can't rerun just the failing step
# ← re-run entire pipeline? wait 10 min
pici
zmx attach ci.myrepo.api
$ go test ./...
ok myapp/db 12.4s
FAIL myapp/api (14.2s)
Error: connection refused
 
# you're in the session, inspect freely
$ cat .env
DB_HOST=postgres.internal:5432
$ nc -zv postgres.internal 5432
Connection refused
# ↑ found it: wrong hostname
# ↑ press ↑ to rerun the test after fixing

Frequently asked questions.

Where's the source code?

It's coming. pici is still in early development and not yet ready for public review. We'll open-source everything when the API, runner, and tooling are stable enough to be useful. Sign up for the beta below to get notified.

Can I self-host?

Yes, self-hosting is a core goal. Once the source is released you'll be able to run pici on your own infra with your own isolation strategy: docker, namespaces, bare metal. You choose.

How much does it cost?

The managed service at ci.pico.sh requires cd.pico.sh hosting. Pricing is TBD and will be announced with the beta. Self-hosted is free.

How are secrets handled?

Secrets are managed through cd.pico.sh's environment variable system. Inject them into your build sessions the same way you'd set any env var. No secret scanning, no vault, no extra tooling.

Get early access to pici.

We're opening the managed beta at ci.pico.sh.