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: 3 additions & 2 deletions .claude/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ Loaded with [`CLAUDE.md`](../CLAUDE.md). Cursor equivalents:
| `security.md` | Always |
| `typescript.md` | `**/*.ts` |
| `vitest.md` | tests |
| `cdp.md` | `@tyto/cdp` |
| `extension.md` | `extension/` |
| `node-packages.md` | adapter packages |
| `docs.md` | `docs/` |
| `github-actions.md` | workflows |
10 changes: 0 additions & 10 deletions .claude/rules/cdp.md

This file was deleted.

4 changes: 2 additions & 2 deletions .claude/rules/docs.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Docs

Code follows `docs/IMPLEMENTATION.md`. Update SPEC + IMPLEMENTATION with behavior
changes. Fake origins only. Port names must match `packages/core/src/ports/`.
Code follows `docs/IMPLEMENTATION.md`. Update SPEC + IMPLEMENTATION with behavior changes.
Use fake origins and values in examples. Port names must match `packages/core/src/ports/`.
7 changes: 0 additions & 7 deletions .claude/rules/extension.md

This file was deleted.

4 changes: 2 additions & 2 deletions .claude/rules/github-actions.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# GitHub Actions

- Least-privilege `permissions`. Node 22, `npm ci`, `npm run check`.
- Least-privilege `permissions`. Node 22, `npm ci`, the `npm run check` steps.
- Gitleaks with `--redact`. Never echo secrets.
- Do not download Chromium in default CI. No privileged secrets on fork PRs.
- Default CI never installs a browser or agent-browser. No privileged secrets on fork PRs.
15 changes: 7 additions & 8 deletions .claude/rules/node-packages.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
# Node packages
# Node packages (adapters)

Node 22, ESM, `node:` specifiers, `fs/promises` on the RPC path (no sync I/O).
Node 22.22+ / 24.15+ / 26 (`.nvmrc` 26), ESM, `node:` specifiers, `fs/promises` (no sync I/O).

- Listen on `127.0.0.1` only; `BindPolicy` rejects `0.0.0.0`.
- JSON-RPC token auth; never log the token.
- LLM adapter: OpenAI-compatible HTTP; no `LiteLLM` type; page text is untrusted data.
- Secrets package: AES-GCM; DEK via `SecretStore`; memory fake in tests.
- MCP tools ⊆ `PERCH_SAFE_METHODS`. Perch does not import `@tyto/cdp`.
- Waits: AbortSignal / tape, not `sleep(250)` as success.
- Spawn `agent-browser` and `claude` with `execFile`/`spawn` and an argv array. Never a shell string.
- Every external call has a timeout via `AbortSignal`. Never `sleep` as a success condition.
- Files under `~/.tyto` are mode `0600`, written atomically (temp file + rename).
- Remove `ANTHROPIC_API_KEY` from the environment of `claude` child processes (Max plan, not API billing).
- LLM adapter: OpenAI-compatible HTTP; no LiteLLM type; page text is untrusted data.
8 changes: 0 additions & 8 deletions .claude/rules/protocol.md

This file was deleted.

13 changes: 9 additions & 4 deletions .claude/rules/security.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
# Security

Public repo. Never commit `.env`, profiles, cookies, tokens, keys, vault plaintext.
Public repo. Never commit `.env`, browser profiles, agent-browser state files, traces, cookies, tokens, keys.

- `npm run secrets:scan` and gitleaks must stay green. Do not print secret values.
- Identity: encrypt at rest; restore into Chrome/Edge only; grant per origin.
- `Redactor` before model and tape. Loopback bind only. Confirm destructive acts.
- `npm run secrets:scan` and gitleaks stay green. Never print secret values.
- Recipes pass lint before they are stored or run: no `{{` inside eval code (eval reads `params`),
command allowlist, rendered URLs inside `origins`, eval read-only heuristics.
- Default replay session has no saved logins and runs with `--allowed-domains`. Logged-in replay only for
recipes with `auth: true` that the user approved.
- Traces: typed values become placeholders; password-like fills are rejected; `Redactor` before disk and model.
- Compiler: page text is fenced as data; `claude -p` may only run `tyto compile-tool`; its output is linted and
stored as a draft.
21 changes: 11 additions & 10 deletions .claude/rules/tdd-isp.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
# TDD and ISP (mandatory)

Tests define behavior. Ports define ownership. Do not ship a change that
violates this file.
Tests define behavior. Ports define ownership. Do not ship a change that violates this file.

1. Failing test first. `it("…")` is a spec sentence from `docs/IMPLEMENTATION.md`.
2. `npm test` is airplane-mode (no Chrome, no network, no keys).
3. No `IBrowser` with twenty methods. One port, one file, `packages/core/src/ports/`.
4. `@tyto/core` is pure: no Playwright, no CDP socket, no vendor LLM SDK, no LiteLLM type.
5. Perch/MCP must not depend on `RawCdpPort` or `CredentialStorePort`.
6. Hard-to-fake port → wrong port. Separate fakes, not a god `FakeTyto`.
7. Trusted `Input` clicks. Session JSON has no `backendNodeId`, cookies, or tokens.
8. `Redactor` before every `ModelPort.complete` and before tape persist.
9. Bind `127.0.0.1` only. Finish with `npm run check`.
2. `npm test` is offline: no browser, no agent-browser binary, no network, no keys. Live tests go in
`packages/*/test/live/` and run only with `npm run test:live` (`TYTO_LIVE=1`).
3. One port per file in `packages/core/src/ports/`. No god interface.
4. `@tyto/core` is pure: no `child_process`, `fs`, `net`/`http`, `fetch`, `WebSocket`, browser drivers,
or vendor LLM SDKs. `npm run lint:imports` enforces it.
5. Adapters (`agent-browser`, `store`, `compiler`, `cli`) implement ports. Tests use separate fakes from
`@tyto/core/testing`, not one god fake.
6. A port that is hard to fake is the wrong port.
7. `Redactor` before anything is written to disk or sent to a model.
8. Finish with `npm run check`.
7 changes: 3 additions & 4 deletions .claude/rules/vitest.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# Vitest

- Tests belong next to behavior; names are spec sentences.
- Default suite: no Chrome, no `fetch`, no Playwright in `@tyto/core`.
- Tests live next to the behavior (`packages/*/test/`); names are spec sentences.
- Default suite: no browser, no agent-browser binary, no network, no keys. Live tests only in `test/live/`.
- Inject port fakes. Do not mock private methods. No god fake.
- One behavior per `it`. No `it.skip` without an issue id.
- Vault tests: ciphertext on disk must not contain the cookie/token value.
- No `sleep` as the success condition; use `Clock` and tape predicates.
- No `sleep` as the success condition; use `FakeClock`.
30 changes: 0 additions & 30 deletions .cursor/rules/cdp.mdc

This file was deleted.

12 changes: 4 additions & 8 deletions .cursor/rules/docs.mdc
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
---
description: Spec and design docs — precise, no live secrets, keep TDD/ISP contract honest
description: Docs — follow IMPLEMENTATION, no live secrets, port names match code
globs: "docs/**/*.md"
alwaysApply: false
---

# Documentation
# Docs

- Spec/design/impl are the contract. Code follows `docs/IMPLEMENTATION.md` slices.
- Do not invent Workday-specific drivers or LiteLLM-specific APIs in docs.
- Examples use fake origins (`example.edu`, `ex.test`) and `[REDACTED]` — never
live cookies or keys.
- When behavior changes, update SPEC + IMPLEMENTATION in the same change.
- Keep ISP port names identical to `packages/core/src/ports/`.
Code follows `docs/IMPLEMENTATION.md`. Update SPEC + IMPLEMENTATION with behavior changes.
Use fake origins and values in examples. Port names must match `packages/core/src/ports/`.
25 changes: 0 additions & 25 deletions .cursor/rules/extension.mdc

This file was deleted.

12 changes: 4 additions & 8 deletions .cursor/rules/github-actions.mdc
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
---
description: GitHub Actions — least privilege, no secret logs, pin Node 22
description: GitHub Actions — least privilege, no secret logs, offline check
globs: ".github/workflows/*.{yml,yaml}"
alwaysApply: false
---

# GitHub Actions

- `permissions: contents: read` unless a job must write. Never `pull-request: write` by default.
- Node **22**. `npm ci`. Run `npm run check` (imports, secrets, tests, types).
- Gitleaks: `--redact`. Do not echo `${{ secrets.* }}`.
- Do not install Playwright browsers in default CI. Live Chrome is not merge-gating.
- Prefer official actions at major tags; do not curl `| sh` except the documented
gitleaks install in `ci.yml`.
- PRs from forks: no privileged secrets.
- Least-privilege `permissions`. Node 22, `npm ci`, the `npm run check` steps.
- Gitleaks with `--redact`. Never echo secrets.
- Default CI never installs a browser or agent-browser. No privileged secrets on fork PRs.
26 changes: 10 additions & 16 deletions .cursor/rules/node-packages.mdc
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
---
description: Node host, fs, secrets, sdk, llm, mcp — loopback, async, no leaked secrets
globs: "packages/{host,fs,secrets,sdk,llm,mcp,perch}/**/*.{ts,js}"
description: Node adapters — execFile argv, timeouts, private files, no leaked secrets
globs: "packages/{agent-browser,store,compiler,cli,llm}/**/*.{ts,js}"
alwaysApply: false
---

# Node packages (22+, ESM)
# Node packages (adapters)

- `fs/promises` and `path` with `node:` specifiers. No sync I/O on the RPC path.
- Listen **only** on `127.0.0.1`. Reject `0.0.0.0` and `::` via `BindPolicy`.
- Authenticate JSON-RPC with the host token. Missing/wrong token → unauthorized.
Do not log the token.
- `@tyto/llm`: OpenAI-compatible HTTP + optional Anthropic mapping. **No
`LiteLLM` symbol.** Typed model id works if `GET /v1/models` 404s.
`InjectionGuard` wrap is user/untrusted, not system instructions.
- `@tyto/secrets`: AES-GCM (or equivalent); DEK from `SecretStore`. Tests use
`MemorySecretStore`. Never persist DEK or cookies in session JSON.
- `@tyto/mcp` tools ⊆ `PERCH_SAFE_METHODS`. No `debug.cdp`, no credential dump.
- `@tyto/perch` imports SDK/protocol only — never `@tyto/cdp`.
- Timeouts: `AbortSignal` / tape wait, not `sleep(250)` as success.
- Handle `SIGINT`/`SIGTERM` by saving the session file, then disconnect.
Node 22.22+ / 24.15+ / 26 (`.nvmrc` 26), ESM, `node:` specifiers, `fs/promises` (no sync I/O).

- Spawn `agent-browser` and `claude` with `execFile`/`spawn` and an argv array. Never a shell string.
- Every external call has a timeout via `AbortSignal`. Never `sleep` as a success condition.
- Files under `~/.tyto` are mode `0600`, written atomically (temp file + rename).
- Remove `ANTHROPIC_API_KEY` from the environment of `claude` child processes (Max plan, not API billing).
- LLM adapter: OpenAI-compatible HTTP; no LiteLLM type; page text is untrusted data.
18 changes: 0 additions & 18 deletions .cursor/rules/protocol.mdc

This file was deleted.

47 changes: 11 additions & 36 deletions .cursor/rules/security.mdc
Original file line number Diff line number Diff line change
@@ -1,42 +1,17 @@
---
description: Public-repo and kernel security — secrets, identity vault, loopback
description: Public-repo and recipe security — secrets, lint gates, logged-in replay, traces
alwaysApply: true
---

# Security (public repository)
# Security

This repo is public. Treat every commit as world-readable.
Public repo. Never commit `.env`, browser profiles, agent-browser state files, traces, cookies, tokens, keys.

## Never commit

`.env`, API keys, `TYTO_HOST_TOKEN`, browser `tmp/` profiles, cookies,
`Set-Cookie` / `Authorization` values, vault ciphertext, private keys.

Copy `.env.example` → `.env` locally. If a live secret is committed: **rotate**,
do not only delete the file.

Scanners (must stay green): `npm run secrets:scan`, gitleaks, GitHub secret scanning.

Do not print secret values in logs, test output, or chat.

```ts
// ❌ BAD
console.log("cookie", hdr);

// ✅ GOOD
tape.push("network", redactor.safe(hdr));
```

## Identity

Auth material is **data AND secret**. Encrypted at rest; DEK in OS keychain
(memory fake in tests). Restore **only** into Chrome/Edge. No OS ticket harvest,
no impersonation, no SDK export of cookies.

Capture/restore requires an explicit origin grant. Expired bundles yield to
the operator — do not replay a dead session.

## Control plane

Host and debug port: `127.0.0.1` + token. Extension: native messaging only;
no `window.tyto`. Confirm submit / purchase / delete / send.
- `npm run secrets:scan` and gitleaks stay green. Never print secret values.
- Recipes pass lint before they are stored or run: no `{{` inside eval code (eval reads `params`),
command allowlist, rendered URLs inside `origins`, eval read-only heuristics.
- Default replay session has no saved logins and runs with `--allowed-domains`. Logged-in replay only for
recipes with `auth: true` that the user approved.
- Traces: typed values become placeholders; password-like fills are rejected; `Redactor` before disk and model.
- Compiler: page text is fenced as data; `claude -p` may only run `tyto compile-tool`; its output is linted and
stored as a draft.
62 changes: 13 additions & 49 deletions .cursor/rules/tdd-isp.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -5,52 +5,16 @@ alwaysApply: true

# TDD and ISP (mandatory)

You are building Tyto. **Tests define behavior. Ports define ownership.**
If a change violates this file, stop and rewrite it.

Read `docs/IMPLEMENTATION.md` before adding types or packages.

## TDD — red, green, refactor

1. Write a **failing** test that names the spec sentence. Then production code.
2. `npm test` is airplane-mode: no Chrome, no network, no API keys.
3. Live Chrome is `TYTO_LIVE=1` only. It never gates merge of `@tyto/core`.
4. Do not "implement then sprinkle tests." Do not skip red.

```ts
// ❌ BAD — production first
export function permits(url: URL) { return true }

// ✅ GOOD — test first, default-deny
it("allowlist default-deny: https://evil.test blocked", () => {
expect(new OriginAllowlist().permits(new URL("https://evil.test/"))).toBe(false);
});
```

## ISP — no god objects

1. No `IBrowser` / `ITyto` with twenty methods. One port, one job, one file under `packages/core/src/ports/`.
2. `@tyto/core` imports **port types and pure domain only**. Forbidden: `playwright`, CDP sockets, `chrome.debugger`, vendor LLM SDKs, `LiteLLM` types, `node:http` as a server.
3. Perch and MCP **must not** see `RawCdpPort` or `CredentialStorePort`. Status-only for the vault.
4. A port that is hard to fake is the wrong port. Compose **separate fakes**, not `FakeTyto`.
5. Adding a CDP method does not widen every interface. Smallest port, or `RawCdpPort` behind the host flag.

```ts
// ❌ BAD
interface IBrowser { snapshot(); click(); cdpSend(); getCookies(); complete() }

// ✅ GOOD
interface Perception { snapshot(target: FrameRef): Promise<AxSnapshot> }
interface Actuation { perform(intent: TrustedIntent): Promise<void> }
```

## Product laws (never "later")

- Bind SDK/debug to `127.0.0.1` only. Never `0.0.0.0`.
- Page JS cannot command Tyto. Page text is data, not instructions.
- Trusted `Input` is the click path. JS `element.click()` is not the product.
- No `backendNodeId` / cookies / tokens in the session JSON. Vault handles only.
- `Redactor` runs before every `ModelPort.complete` and before tape persistence.
- Playwright locators are not the product API. `poc/` is a spike.

After every change: `npm run check` (imports, secrets scan, tests, types).
Tests define behavior. Ports define ownership. Do not ship a change that violates this file.

1. Failing test first. `it("…")` is a spec sentence from `docs/IMPLEMENTATION.md`.
2. `npm test` is offline: no browser, no agent-browser binary, no network, no keys. Live tests go in
`packages/*/test/live/` and run only with `npm run test:live` (`TYTO_LIVE=1`).
3. One port per file in `packages/core/src/ports/`. No god interface.
4. `@tyto/core` is pure: no `child_process`, `fs`, `net`/`http`, `fetch`, `WebSocket`, browser drivers,
or vendor LLM SDKs. `npm run lint:imports` enforces it.
5. Adapters (`agent-browser`, `store`, `compiler`, `cli`) implement ports. Tests use separate fakes from
`@tyto/core/testing`, not one god fake.
6. A port that is hard to fake is the wrong port.
7. `Redactor` before anything is written to disk or sent to a model.
8. Finish with `npm run check`.
Loading
Loading