Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
17 changes: 16 additions & 1 deletion .agents/skills/obol-stack-dev/references/dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,5 +115,20 @@ When `OBOL_DEVELOPMENT=true`, `obol stack up` creates k3d pull-through caches an

Caveats:

- Pull-through caches do **not** speed up local-build flows (`docker build` runs on the host daemon, `k3d image import` bypasses registries). Use the local push target (`just dev-frontend` does this).
- Pull-through caches do **not** speed up local-build flows (`docker build` runs on the host daemon). `just dev-frontend` pushes to `localhost:54103` **and** runs `k3d image import` so k3s picks up the new `:dev` digest (`imagePullPolicy: IfNotPresent` otherwise serves a stale image).
- Registry config is only set up at cluster create. If `obol stack up` is starting an existing cluster, registry setup is skipped — recreate (`obol stack down && obol stack up`) once to pick up new entries.

### Local frontend (`obol-stack-front-end`)

```bash
# From obol-stack — use .envrc.local so OBOL_CONFIG_DIR points at ~/.config/obol (one cluster)
source .envrc.local
FRONTEND_DIR=../obol-stack-front-end just dev-frontend-rebuild
open http://obol.stack:8080
```

- `dev-frontend` — build (may use Docker cache) + push + import + rollout
- `dev-frontend-rebuild` — same with `--no-cache` (use after editing frontend source)
- `dev-frontend-reset` — restore released chart image

Host `pnpm run dev` on `:3000` is optional for HMR; see frontend repo `.env.example` for kubeconfig/Prometheus port-forwards.
21 changes: 13 additions & 8 deletions .envrc.local.example
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
# Local environment overrides (copy to .envrc.local)
# This file is for local development customizations and is gitignored
# Local environment overrides (copy to .envrc.local, then: source .envrc.local)
# This file is gitignored. Use direnv (`direnv allow`) for auto-load in this repo.

# Example: Override development mode
# export OBOL_DEVELOPMENT=false
# --- Recommended: dev-build obol CLI against your real stack (single cluster) ---
# Without OBOL_CONFIG_DIR, OBOL_DEVELOPMENT=true alone uses .workspace/config and
# creates a SECOND k3d cluster (new petname stack ID). Point at ~/.config/obol instead.
#
# export OBOL_DEVELOPMENT=true
# export OBOL_CONFIG_DIR="${HOME}/.config/obol"
# export OBOL_DATA_DIR="${HOME}/.local/share/obol"
# export OBOL_STATE_DIR="${HOME}/.local/state/obol"
# export OBOL_BIN_DIR="${PWD}/.workspace/bin"
# if type PATH_add >/dev/null 2>&1; then PATH_add .workspace/bin; else export PATH="${PWD}/.workspace/bin:${PATH}"; fi

# Example: Add workspace bin to PATH
# PATH_add .workspace/bin

# Example: Override config directories
# Example: custom paths
# export OBOL_CONFIG_DIR=/custom/config/path
# export OBOL_BIN_DIR=/custom/bin/path
# export OBOL_STATE_DIR=/custom/state/path
Expand Down
23 changes: 22 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,28 @@ k3d: 1 server, ports `80:80` + `8080:80` + `443:443` + `8443:443`, image `ranche

Generated k3d registry config written to `$OBOL_CONFIG_DIR/registries.yaml`. Cache data under `~/.local/state/obol/registry-cache/` by default, or under `OBOL_REGISTRY_CACHE_DIR` when set.

Local push target: `just dev-frontend` swaps layered diffs into cluster via `docker push localhost:54103/...` (deployment image `localhost:54103/...:dev`) — only changed layers transfer, vs. `k3d image import`'s full-tarball round-trip.
Local push target: `just dev-frontend` builds `obol-stack-front-end`, pushes `localhost:54103/obol-stack-front-end:dev`, **imports into the active k3d cluster** (`k3d image import` — required because `imagePullPolicy: IfNotPresent` caches the `:dev` tag), and restarts the frontend pod. Use `just dev-frontend-rebuild` after code changes (forces `docker build --no-cache`). Reset: `just dev-frontend-reset`.

### Local frontend development

**Two ways to run the UI:**

| Mode | URL | When |
|------|-----|------|
| In-cluster (recommended) | `http://obol.stack:8080` | Stable Prometheus/disk metrics; no port-forwards |
| Host `pnpm run dev` | `http://obol.stack:3000` | Fast React HMR; needs kubeconfig + optional Prometheus/eRPC port-forwards (see frontend `.env.example`) |

**Single cluster when dev-building from this repo** — copy `.envrc.local.example` → `.envrc.local` and `source` it (or `direnv allow`). This sets `OBOL_CONFIG_DIR=$HOME/.config/obol` so `obol kubectl` / `just dev-frontend` hit your real stack instead of spawning a second `.workspace` cluster.

```bash
cd obol-stack && source .envrc.local
FRONTEND_DIR=../obol-stack-front-end just dev-frontend-rebuild # after UI code changes
open http://obol.stack:8080
```

**k3d vs `obol stack up`:** `k3d cluster start` only powers Docker containers + API. `obol stack up` deploys Helm infra, agents, tunnel replay, etc. Use k3d directly only for loadbalancer/port emergencies; otherwise `obol stack up`.

**Stale frontend image symptoms:** build log shows all `CACHED` layers → use `dev-frontend-rebuild`; rollout succeeds but UI unchanged → `k3d image import` (now automatic in `just dev-frontend`).

Caveats:
- Pull-through caches don't help host `docker build` flows — `k3d image import` bypasses registries entirely. The local push target is what speeds up local-build redeploys.
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,8 @@ OBOL_DEVELOPMENT=true ./obolup.sh

Development mode uses `.workspace/` instead of XDG directories and runs `go run` on every `obol` invocation — no build step needed.

**Already have a stack in `~/.config/obol`?** Copy `.envrc.local.example` → `.envrc.local` and `source` it so dev commands use that cluster (avoids a second k3d stack in `.workspace/config`). **Frontend in-cluster:** `FRONTEND_DIR=../obol-stack-front-end just dev-frontend-rebuild` → `http://obol.stack:8080` (see `CLAUDE.md` → Local frontend development).

Networks are embedded at `internal/embed/networks/`. Each uses annotated Go templates that auto-generate CLI flags:

```yaml
Expand Down
17 changes: 16 additions & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ dev_image := "localhost:54103/obol-stack-front-end:dev"
# Build frontend from local source, push to local registry, and restart the pod
dev-frontend: (_dev-frontend-build "false" "true")

# Rebuild and hot-swap frontend (skip docker cache for faster iteration)
# Rebuild and hot-swap frontend (forces docker --no-cache; use after code changes)
dev-frontend-rebuild: (_dev-frontend-build "true" "false")

# Internal: build the frontend dev image, push it, and roll out the deployment.
Expand Down Expand Up @@ -103,6 +103,21 @@ _dev-frontend-build no_cache set_image:
docker build "${build_args[@]}" -t {{ dev_image }} {{ frontend_dir }}
echo "→ Pushing {{ dev_image }} to local registry"
docker push {{ dev_image }}

# k3s caches images by tag (imagePullPolicy: IfNotPresent). Pushing a new
# digest to :dev does not replace the node's copy — import forces the update.
cfg_dir="${OBOL_CONFIG_DIR:-}"
if [ -z "$cfg_dir" ] && [ "${OBOL_DEVELOPMENT:-}" = "true" ]; then
cfg_dir="{{ justfile_directory() }}/.workspace/config"
fi
if [ -z "$cfg_dir" ]; then
cfg_dir="${XDG_CONFIG_HOME:-$HOME/.config}/obol"
fi
stack_id="$(cat "$cfg_dir/.stack-id")"
cluster="obol-stack-${stack_id}"
echo "→ Importing {{ dev_image }} into ${cluster}"
k3d image import {{ dev_image }} -c "${cluster}"

echo "→ Restarting frontend deployment"
if [ "{{ set_image }}" = "true" ]; then
obol kubectl set image deployment/obol-frontend-obol-app \
Expand Down
40 changes: 34 additions & 6 deletions web/public-storefront/src/components/ServiceCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,20 @@ function optionLabel(opt: ServicePayment): string {
// obol.org/llms.txt: /skill.md carries the full x402 payment flow and
// /openapi.json the exact request shapes. Falls back to a generic x402
// pointer if the endpoint origin can't be parsed.
function docsRef(endpoint: string): string {
function endpointOrigin(endpoint: string): string | null {
try {
const origin = new URL(endpoint).origin;
return `Read ${origin}/skill.md for the x402 payment flow and ${origin}/openapi.json for the exact request shapes.`;
return new URL(endpoint).origin;
} catch {
return null;
}
}

function docsRef(endpoint: string): string {
const origin = endpointOrigin(endpoint);
if (!origin) {
return "See https://www.x402.org for how x402 micropayments work.";
}
return `Read ${origin}/skill.md for the x402 payment flow and ${origin}/openapi.json for the exact request shapes.`;
}

const typeColors: Record<string, string> = {
Expand Down Expand Up @@ -194,6 +201,28 @@ export function ServiceCard({ service }: { service: Service }) {
{service.endpoint}
</a>
</div>
{kind === "http" && endpointOrigin(service.endpoint) ? (
<div className="col-span-2">
<span className="text-text-muted">API docs</span>
<a
href={`${endpointOrigin(service.endpoint)}/api`}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 text-xs text-obol-green hover:underline"
>
Swagger UI ↗
</a>
<span className="text-text-muted text-xs mx-1">·</span>
<a
href={`${endpointOrigin(service.endpoint)}/openapi.json`}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 text-xs text-obol-green hover:underline"
>
openapi.json ↗
</a>
</div>
) : null}
</div>

<button
Expand Down Expand Up @@ -317,9 +346,8 @@ function TabBar({ tab, onChange }: { tab: Tab; onChange: (t: Tab) => void }) {

// BuyViaObolAgent branches on service.type so the prompt matches what
// the buy-x402 skill actually does for that shape (`pay` for http,
// `pay` against chat-completions for agents, `obol buy inference` CLI
// for inference). Mirrors inferenceCopy/agentCopy/httpCopy in
// internal/x402/paymentrequired.go.
// `pay-agent` for agents, `obol buy inference` CLI for inference).
// Mirrors inferenceCopy/agentCopy/httpCopy in internal/x402/paymentrequired.go.
function BuyViaObolAgent({
service,
opt,
Expand Down
Loading