diff --git a/AGENTS.md b/AGENTS.md index ffb66f7..f8458e7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 @@ -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 diff --git a/README.md b/README.md index 8ef786b..b9491d8 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,15 @@ GitHub stars

+

+ Works with
+ Claude Code + Cursor + Windsurf + Cline + Continue.dev (advisory) +

+ --- ## Why @@ -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 . +- 🐚 **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 @@ -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: diff --git a/docker-compose.yml b/docker-compose.yml index 0057574..8c10ef7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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: diff --git a/packages/cli/src/help-json.ts b/packages/cli/src/help-json.ts index 1dbc4b4..5074180 100644 --- a/packages/cli/src/help-json.ts +++ b/packages/cli/src/help-json.ts @@ -120,7 +120,13 @@ const COMMAND_META: Record ], }, init: { - exitCodes: [ExitCode.SUCCESS, ExitCode.GENERIC, ExitCode.USAGE], + exitCodes: [ + ExitCode.SUCCESS, + ExitCode.GENERIC, + ExitCode.USAGE, + ExitCode.NOT_FOUND, + ExitCode.CONFLICT, + ], examples: [ 'openhop init', 'openhop init --dry-run --json', diff --git a/packages/cli/src/init.ts b/packages/cli/src/init.ts index e565b94..33dcfc7 100644 --- a/packages/cli/src/init.ts +++ b/packages/cli/src/init.ts @@ -25,6 +25,8 @@ import { fileURLToPath } from 'node:url' const EXIT_OK = 0 const EXIT_GENERIC = 1 const EXIT_USAGE = 2 +const EXIT_NOT_FOUND = 4 +const EXIT_CONFLICT = 5 /** A supported AI client and where it expects skills on this machine. */ export interface ClientSpec { @@ -324,13 +326,18 @@ 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: + // CONFLICT (5) — at least one install actually errored. + // NOT_FOUND (4) — no clients were detected at all (every result is + // `skipped: not detected`); nothing to do. + // SUCCESS (0) — anything else, including a converged machine where + // every detected client is `already installed`, and + // advisory-only detections (e.g. Continue.dev). + if (failed.length > 0) process.exit(EXIT_CONFLICT) + const detectedCount = results.filter( + (r) => !(r.status === 'skipped' && r.reason === 'not detected') + ).length + if (detectedCount === 0) process.exit(EXIT_NOT_FOUND) process.exit(EXIT_OK) }) } diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 80115c0..a6e08d0 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -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`)