Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ npx tsc --noEmit -p packages/cli
npm run build -w @openhop/cli
```

There is **no** CI lint or formatter enforced — don't try to run one.
CI runs `npm run lint` and `npm run format:check` (see `.github/workflows/ci.yml`). Run them locally before pushing if you're touching a lot of files.

## Tests

Expand All @@ -66,7 +66,7 @@ npm test -w @openhop/shared # vitest, fast
npm test -w @openhop/server # vitest, touches the in-memory store
```

Known pre-existing failures in `packages/shared/__tests__/patch.test.ts` — it references old singular op names (`add-node`) that were renamed to plural (`add-nodes`). Don't waste time on those unless fixing them is your ticket.
The shared test suite is fully passing. The previous `patch.test.ts` "old singular op names" caveat no longer applies — it has been updated to the plural ops.

## Authoring flows

Expand Down
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@
<a href="https://github.com/naorsabag/OpenHop/stargazers"><img src="https://img.shields.io/github/stars/naorsabag/OpenHop?style=social" alt="GitHub stars" /></a>
</p>

<p align="center">
<sub>Works with</sub><br/>
<img src="https://img.shields.io/badge/Claude%20Code-✓-262626?style=flat-square" alt="Claude Code" />
<img src="https://img.shields.io/badge/Cursor-✓-262626?style=flat-square" alt="Cursor" />
<img src="https://img.shields.io/badge/Windsurf-✓-262626?style=flat-square" alt="Windsurf" />
<img src="https://img.shields.io/badge/Cline-✓-262626?style=flat-square" alt="Cline" />
<img src="https://img.shields.io/badge/Continue.dev-advisory-666?style=flat-square" alt="Continue.dev (advisory)" />
</p>

---

## Why
Expand All @@ -37,7 +46,9 @@ between components on a pixel-art canvas. Click any node to drill into its sub-f
- 🔍 **Multi-level drill-down.** Click a node to zoom into its sub-flow. Infinite depth.
- ⚡ **Live re-render.** `openhop patch` applies incremental changes without a full reload.
- 🧠 **Strict schema + fuzzy typo hints.** Invalid YAML fails loudly with helpful suggestions.
- 🐚 **CLI + HTTP API + web UI.** Script it, hit it from tools, or browse at <http://localhost:8788>.
- 🐚 **CLI + HTTP API + web UI.** Script it, hit it from tools, or browse at `http://localhost:8788`.
- 🔒 **Local-first, no telemetry.** Runs entirely on your machine — no analytics, no phone-home, no account required.
- ✂️ **Token-light.** A typical flow YAML is ~5–20× smaller than the equivalent prose explanation an agent would otherwise emit.

## Try it in 60 seconds

Expand Down Expand Up @@ -119,6 +130,7 @@ Pre-made flows under [`examples/`](examples/):
- `order-flow.yaml` — e-commerce order pipeline
- `simple-crud.yaml` — minimal CRUD example
- `type-variants.yaml` — every node type in one flow
- `self-loops.yaml` — same-node steps (internal work, retries) plus broadcasts and multi-data steps

Push any of them:

Expand Down
4 changes: 4 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ services:
- openhop-flows:/root/.openhop
environment:
- CHOKIDAR_USEPOLLING=true
# Inside the container we need to bind every interface so the host
# port-mapping above can reach the server. Outside docker the server
# defaults to 127.0.0.1.
- HOST=0.0.0.0
restart: unless-stopped

volumes:
Expand Down
18 changes: 11 additions & 7 deletions packages/cli/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,9 @@ export function registerInit(program: Command): void {
const skipped = results.filter((r) => r.status === 'skipped' || r.status === 'advisory')
const failed = results.filter((r) => r.status === 'failed')
const wouldInstall = results.filter((r) => r.status === 'would-install')
const alreadyInstalled = results.filter(
(r) => r.status === 'skipped' && r.reason?.startsWith('already installed')
)

if (opts.json) {
const payload = {
Expand All @@ -324,13 +327,14 @@ export function registerInit(program: Command): void {
}
}

// Exit code policy: success if anything was installed (or, in dry-run,
// would have been installed). Otherwise generic failure when nothing
// was actionable.
const anySuccess = installed.length > 0 || wouldInstall.length > 0
if (!anySuccess && installed.length === 0 && wouldInstall.length === 0) {
process.exit(EXIT_GENERIC)
}
// Exit code policy: a converged machine — every detected client already
// has the skill — is success, not failure. Only fail when something
// actually went wrong (a `failed` result) or there was nothing to do
// at all (no detected clients).
if (failed.length > 0) process.exit(EXIT_GENERIC)
const anyOutcome =
installed.length > 0 || wouldInstall.length > 0 || alreadyInstalled.length > 0
if (!anyOutcome) process.exit(EXIT_GENERIC)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
process.exit(EXIT_OK)
})
}
7 changes: 5 additions & 2 deletions packages/server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ try {
}

const port = parseInt(process.env.PORT ?? '8787')
await app.listen({ port, host: '0.0.0.0' })
console.log(`OpenHop server running on http://localhost:${port}`)
// Default to loopback so a casual `npm run dev` is not exposed to the LAN.
// Containers / docker-compose set HOST=0.0.0.0 to bind every interface.
const host = process.env.HOST ?? '127.0.0.1'
await app.listen({ port, host })
console.log(`OpenHop server running on http://localhost:${port} (bound to ${host})`)
console.log(`Swagger docs at http://localhost:${port}/docs`)
Loading