Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/browser-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"agentcrumbs": minor
---

Add browser support via tshy `esmDialects`. Bundlers that respect the `"browser"` export condition (Vite, webpack, esbuild, Next.js) automatically resolve to the browser build. Same `"agentcrumbs"` import path — no separate entry point. Adds `configure()` API for enabling tracing in the browser.
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ All methods are documented in detail at [agentcrumbs.dev/docs/api](https://agent

| Method | Purpose |
| --- | --- |
| `configure(config)` | Enable tracing in the browser (no-op in Node.js) |
| `trail(namespace)` | Create a trail function for a namespace |
| `crumb(msg, data?, options?)` | Drop a crumb with message and optional data |
| `crumb.scope(name, fn)` | Wrap a function with entry/exit/error tracking |
Expand All @@ -104,9 +105,16 @@ All methods are documented in detail at [agentcrumbs.dev/docs/api](https://agent

Mark crumb lines with `// @crumbs` (single line) or `// #region @crumbs` / `// #endregion @crumbs` (block) so they can be stripped before merge. See the [markers docs](https://agentcrumbs.dev/docs/markers) for details and examples.

## Environment variable
## Configuration

Everything is controlled by a single `AGENTCRUMBS` environment variable.
In Node.js, everything is controlled by a single `AGENTCRUMBS` environment variable. In the browser, use `configure()`:

```typescript
import { configure } from "agentcrumbs"; // @crumbs
configure("*"); // @crumbs — enable all namespaces
```

### Environment variable (Node.js)

| Value | Effect |
| --- | --- |
Expand Down Expand Up @@ -173,9 +181,10 @@ curl -X POST http://localhost:8374/crumb \

## Runtime compatibility

Zero runtime dependencies. Node.js built-in modules only: `node:http`, `node:async_hooks`, `node:crypto`, `node:fs`, `node:util`.
Zero runtime dependencies.

Verified compatible with **Node.js 18+** and **Bun**.
- **Node.js 18+** and **Bun** — uses `node:async_hooks`, `node:crypto`, `node:fs`, `node:util`
- **Browsers** — Vite, webpack, esbuild, Next.js auto-resolve to the browser build via the `"browser"` export condition. Same `"agentcrumbs"` import path. Use `configure()` instead of the env var to enable tracing. See the [browser guide](https://agentcrumbs.dev/docs/guides/browser).

## Docs

Expand Down
2 changes: 1 addition & 1 deletion docs/content/docs/api/trail.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Returns a `TrailFn`, a callable function with additional methods:
When a namespace is disabled, `trail()` returns a pre-built frozen noop function. There is no `if (enabled)` check on every call. The function itself IS the noop.

```typescript
// When AGENTCRUMBS is unset:
// When tracing is not enabled:
const crumb = trail("my-service"); // returns frozen NOOP
crumb("msg", { data }); // empty function, returns undefined
crumb.scope("op", fn); // calls fn() directly
Expand Down
2 changes: 1 addition & 1 deletion docs/content/docs/config/env-var.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: "Environment variable"
description: "Configure agentcrumbs with the AGENTCRUMBS env var"
---

Everything is controlled by a single `AGENTCRUMBS` environment variable.
In Node.js, everything is controlled by a single `AGENTCRUMBS` environment variable. In the browser, use [`configure()`](/guides/browser) instead.

## Shorthand values

Expand Down
7 changes: 5 additions & 2 deletions docs/content/docs/config/sinks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: "Sinks"
description: "Configure where crumbs are sent"
---

By default, crumbs are sent via HTTP to the collector and also printed to stderr. You can add custom sinks or replace the defaults.
By default, crumbs are sent via HTTP to the collector and also printed to the console. In Node.js, output goes to stderr with ANSI colors. In the browser, output goes to `console.debug()` with CSS styling. You can add custom sinks or replace the defaults.

## Custom sink

Expand Down Expand Up @@ -32,7 +32,10 @@ import { HttpSink } from "agentcrumbs";

### ConsoleSink

Pretty-printed stderr output. Added automatically when `AGENTCRUMBS` is set.
Pretty-printed console output. Added automatically when tracing is enabled.

- **Node.js**: ANSI-colored output to stderr
- **Browser**: CSS-styled output via `console.debug()`, with `console.groupCollapsed()` for scopes and interactive object rendering in DevTools

```typescript
import { ConsoleSink } from "agentcrumbs";
Expand Down
96 changes: 96 additions & 0 deletions docs/content/docs/guides/browser.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
---
title: "Browser"
description: "Use agentcrumbs in browser apps with the same import path"
---

agentcrumbs works in the browser with the same `"agentcrumbs"` import. Bundlers that respect the `"browser"` export condition (Vite, webpack, esbuild, Next.js) automatically resolve to the browser build.

## Enable tracing

Browsers don't have environment variables. Use `configure()` instead:

```typescript
import { configure, trail } from "agentcrumbs"; // @crumbs

configure("*"); // @crumbs — enable all namespaces

const crumb = trail("ui"); // @crumbs
```

`configure()` accepts the same values as the `AGENTCRUMBS` env var:

```typescript
// Enable all
configure("*"); // @crumbs

// Namespace filter
configure("ui-*,api-*"); // @crumbs

// Full config object
configure({ ns: "ui-*", app: "my-app", format: "pretty" }); // @crumbs
```

Call `configure()` before any `trail()` calls. A good place is your app's entry point.

### Declarative fallback

You can also set config on `globalThis` before importing agentcrumbs:

```html
<script>
window.__AGENTCRUMBS__ = "*";
// or: window.__AGENTCRUMBS__ = { ns: "ui-*", app: "my-app" };
// or set just the app name: window.__AGENTCRUMBS_APP__ = "my-app";
</script>
```

## App name

In the browser, the app name is resolved in this order:
1. `app` field from `configure()` config
2. `globalThis.__AGENTCRUMBS_APP__`
3. Fallback: `"browser"`

## Console output

In the browser, crumbs are written to `console.debug()` with CSS styling:

- Namespace labels are color-coded
- Scope enter/exit use `console.groupCollapsed()` / `console.groupEnd()` for collapsible nesting
- Data objects are passed as additional arguments so DevTools renders them interactively

When `format: "json"` is set, crumbs are written as JSON strings via `console.debug()`.

## Collector support

The browser build includes the HTTP sink, so crumbs are sent to the collector just like in Node.js. Start `agentcrumbs collect` on your dev machine and crumbs from both your server and browser flow to the same place.

```bash
# Terminal: Start collector
agentcrumbs collect

# Browser crumbs + server crumbs appear together
agentcrumbs tail --all-apps
```

The browser defaults to `http://localhost:8374/crumb`. Make sure CORS allows it, or the HTTP sink silently fails (crumbs still appear in the DevTools console).

## Differences from Node.js

| | Node.js | Browser |
|---|---------|---------|
| **Config** | `AGENTCRUMBS` env var | `configure()` call |
| **Console output** | ANSI-colored stderr | CSS-styled DevTools console |
| **Async context** | `AsyncLocalStorage` | Sync stack (single-threaded) |
| **Process ID** | `process.pid` | `0` |
| **Session file** | Reads `/tmp/agentcrumbs.session` | Skipped |
| **UUID** | `node:crypto` | Web Crypto API |
| **App fallback** | Nearest `package.json` name | `"browser"` |

### Context isolation

The browser uses a sync stack instead of `AsyncLocalStorage`. This works for all linear async flows. However, concurrent branches in `Promise.all` won't isolate context from each other. This is acceptable for debugging — just be aware that nested scopes inside `Promise.all` may share context.

## configure() in Node.js

`configure()` is exported from both builds so your code compiles in both environments. In Node.js it's a no-op — use the `AGENTCRUMBS` env var instead.
2 changes: 1 addition & 1 deletion docs/content/docs/guides/meta.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"title": "Guides",
"pages": ["node-modules", "multi-service", "pr-reviewers", "cross-language"]
"pages": ["browser", "node-modules", "multi-service", "pr-reviewers", "cross-language"]
}
1 change: 1 addition & 0 deletions docs/content/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Service C ──┘ (fire & forget) └── ~/.agentcrumbs/<app
- **Strip before merge.** `agentcrumbs strip` removes all `// @crumbs` lines and `#region @crumbs` blocks. Clean diffs, no debug code on main.
- **HTTP collector.** `agentcrumbs collect` receives crumbs from all services via fire-and-forget HTTP. Tail, query, and replay from the CLI.
- **Works with any agent.** Claude Code, Cursor, Copilot, Aider, custom agents. If the agent can write code, it can write crumbs.
- **Works in the browser.** Same import path. Bundlers auto-resolve to the browser build. Use `configure()` instead of the env var. See the [browser guide](/guides/browser).
- **Ships with agent skills.** Built on [@tanstack/intent](https://tanstack.com/blog/from-docs-to-agents). Install the package, tell your agent to run `npx @tanstack/intent install`, and it learns how to use crumbs from the package itself. No stale training data.

## Install
Expand Down
16 changes: 16 additions & 0 deletions docs/content/docs/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,25 @@ agentcrumbs strip
agentcrumbs strip --check
```

## Browser apps

agentcrumbs works in the browser with the same import. Use `configure()` instead of the env var:

```typescript
import { configure, trail } from "agentcrumbs"; // @crumbs

configure("*"); // @crumbs
const crumb = trail("ui"); // @crumbs

crumb("button clicked", { id: "submit" }); // @crumbs
```

Bundlers that support the `"browser"` export condition (Vite, webpack, esbuild, Next.js) resolve to the browser build automatically. See the [browser guide](/guides/browser) for details.

## Next steps

- [Skills](/skills): how agents learn to use agentcrumbs from the package itself
- [Workflow](/workflow): how crumbs fit into the branch lifecycle
- [API reference](/api/trail): full API docs
- [CLI reference](/cli/collect): collector, tail, query, strip
- [Browser guide](/guides/browser): using agentcrumbs in browser apps
6 changes: 5 additions & 1 deletion packages/agentcrumbs/.tshy/commonjs.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
"exclude": [
"../src/__tests__/**/*",
"../src/**/*.mts",
"../src/package.json"
"../src/package.json",
"../src/context-browser.mts",
"../src/env-browser.mts",
"../src/sinks/console-browser.mts",
"../src/trail-browser.mts"
],
"compilerOptions": {
"outDir": "../.tshy-build/commonjs"
Expand Down
6 changes: 5 additions & 1 deletion packages/agentcrumbs/.tshy/esm.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
],
"exclude": [
"../src/__tests__/**/*",
"../src/package.json"
"../src/package.json",
"../src/context-browser.mts",
"../src/env-browser.mts",
"../src/sinks/console-browser.mts",
"../src/trail-browser.mts"
],
"compilerOptions": {
"outDir": "../.tshy-build/esm"
Expand Down
17 changes: 13 additions & 4 deletions packages/agentcrumbs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ All methods are documented in detail at [agentcrumbs.dev/docs/api](https://agent

| Method | Purpose |
| --- | --- |
| `configure(config)` | Enable tracing in the browser (no-op in Node.js) |
| `trail(namespace)` | Create a trail function for a namespace |
| `crumb(msg, data?, options?)` | Drop a crumb with message and optional data |
| `crumb.scope(name, fn)` | Wrap a function with entry/exit/error tracking |
Expand All @@ -104,9 +105,16 @@ All methods are documented in detail at [agentcrumbs.dev/docs/api](https://agent

Mark crumb lines with `// @crumbs` (single line) or `// #region @crumbs` / `// #endregion @crumbs` (block) so they can be stripped before merge. See the [markers docs](https://agentcrumbs.dev/docs/markers) for details and examples.

## Environment variable
## Configuration

Everything is controlled by a single `AGENTCRUMBS` environment variable.
In Node.js, everything is controlled by a single `AGENTCRUMBS` environment variable. In the browser, use `configure()`:

```typescript
import { configure } from "agentcrumbs"; // @crumbs
configure("*"); // @crumbs — enable all namespaces
```

### Environment variable (Node.js)

| Value | Effect |
| --- | --- |
Expand Down Expand Up @@ -173,9 +181,10 @@ curl -X POST http://localhost:8374/crumb \

## Runtime compatibility

Zero runtime dependencies. Node.js built-in modules only: `node:http`, `node:async_hooks`, `node:crypto`, `node:fs`, `node:util`.
Zero runtime dependencies.

Verified compatible with **Node.js 18+** and **Bun**.
- **Node.js 18+** and **Bun** — uses `node:async_hooks`, `node:crypto`, `node:fs`, `node:util`
- **Browsers** — Vite, webpack, esbuild, Next.js auto-resolve to the browser build via the `"browser"` export condition. Same `"agentcrumbs"` import path. Use `configure()` instead of the env var to enable tracing. See the [browser guide](https://agentcrumbs.dev/docs/guides/browser).

## Docs

Expand Down
7 changes: 7 additions & 0 deletions packages/agentcrumbs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
"exports": {
".": "./src/index.ts"
},
"esmDialects": [
"browser"
],
"exclude": [
"src/__tests__/**/*"
]
Expand Down Expand Up @@ -65,6 +68,10 @@
"type": "module",
"exports": {
".": {
"browser": {
"types": "./dist/browser/index.d.ts",
"default": "./dist/browser/index.js"
},
"import": {
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.js"
Expand Down
17 changes: 15 additions & 2 deletions packages/agentcrumbs/skills/agentcrumbs/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,24 @@ Run `agentcrumbs <command> --help` for detailed options on any command.

## Enable tracing

**Node.js:** Set the `AGENTCRUMBS` environment variable:

```bash
AGENTCRUMBS=1 node app.js # Enable all namespaces
AGENTCRUMBS='{"ns":"auth-*"}' node app.js # Filter by namespace
```

When `AGENTCRUMBS` is not set, `trail()` returns a frozen noop. No conditionals, no overhead.
**Browser:** Use `configure()` instead (no env vars in browsers):

```typescript
import { configure, trail } from "agentcrumbs"; // @crumbs
configure("*"); // @crumbs — enable all namespaces
const crumb = trail("ui"); // @crumbs
```

Bundlers (Vite, webpack, esbuild, Next.js) auto-resolve to the browser build. Same import path.

When tracing is not enabled, `trail()` returns a frozen noop. No conditionals, no overhead.

## App isolation

Expand All @@ -156,7 +168,8 @@ agentcrumbs stats --all-apps # Per-app statistics

1. **Over-filtering queries** — Do NOT add `--ns` or `--match` filters to narrow results. Use `--limit` and `--cursor` to paginate instead. Filtering to one namespace hides cross-service bugs. If there are too many results, narrow the time window or reduce `--limit`, not the namespaces.
2. **Missing markers** — Every crumb line needs `// @crumbs` or a `#region @crumbs` block. Without them, `strip` can't clean up.
3. **Creating trail() in hot paths** — `trail()` parses the env var each call. Create once at module scope, use `child()` for per-request context.
3. **Creating trail() in hot paths** — `trail()` parses config each call. Create once at module scope, use `child()` for per-request context.
4. **Forgetting configure() in the browser** — In browser apps, call `configure("*")` before any `trail()` calls. Without it, all namespaces are disabled.
4. **No collector running** — Without `agentcrumbs collect`, crumbs go to stderr only and can't be queried. Start the collector before reproducing issues.

## Further discovery
Expand Down
10 changes: 9 additions & 1 deletion packages/agentcrumbs/skills/agentcrumbs/init/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ This is what gets stamped on every crumb as the `app` field.
If the repo is a monorepo, use the root package name (not individual
workspace packages — those become namespaces, not apps).

### Browser apps

If the project has browser-side code (React, Vue, Svelte, etc.), include
browser-facing namespaces in the catalog. The same `"agentcrumbs"` import
works in the browser — bundlers auto-resolve to the browser build. Note
in the config that browser apps use `configure()` instead of the env var.

### What to capture for each namespace

For each namespace, record:
Expand Down Expand Up @@ -141,7 +148,8 @@ production.
### CLI

```bash
AGENTCRUMBS=1 node app.js # enable tracing
AGENTCRUMBS=1 node app.js # enable tracing (Node.js)
configure("*") # enable tracing (browser — call before trail())
agentcrumbs collect # start collector
agentcrumbs tail # live tail (scoped to this app)
agentcrumbs query --since 5m # query recent crumbs (all namespaces)
Expand Down
Loading