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
13 changes: 13 additions & 0 deletions .changeset/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Changesets

Release flow (single-branch, adapted from anchorage's two-branch setup):

1. Every PR that changes a published package (`@understudy/protocol`,
`@understudy/connector`) adds a changeset: `pnpm changeset`.
2. On push to `master`, `.github/workflows/release.yml` either opens/updates
the standing "Version Packages" PR (when changesets are pending) or
publishes any package whose version is not yet on npm (when none are).
3. Merging the Version Packages PR is the release trigger.

The apps (`apps/backend`, `apps/extension`) are `private: true` and never
publish. Docs: https://github.com/changesets/changesets
14 changes: 14 additions & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "master",
"updateInternalDependencies": "patch",
"ignore": [],
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
"onlyUpdatePeerDependentsWhenOutOfRange": true
}
}
47 changes: 47 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: CI

on:
push:
branches: [master]
pull_request:

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
ci:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4

# Reads the pnpm version from package.json "packageManager".
- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

- name: Install (frozen lockfile)
run: pnpm install --frozen-lockfile

# dist/ is gitignored and @understudy/protocol resolves via dist (the
# npm-publish-correct layout), so the workspace libs must be built
# before anything that imports them typechecks or runs. Topological:
# protocol builds before connector.
- name: Build packages
run: pnpm --filter "./packages/*" build

- name: Typecheck
run: pnpm -r typecheck

- name: Test
run: pnpm -r test

- name: Build
run: pnpm -r build
66 changes: 66 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: Release

# Single-branch changesets flow (adapted from anchorage's two-branch
# version.yml + release.yml — understudy has no dev/main split, so one
# workflow does both halves). On every push to master the changesets action
# either:
# (a) opens/updates the standing "Version Packages" PR when changesets are
# pending (version bumps + CHANGELOG entries + changeset deletion), or
# (b) publishes any public package whose package.json version is not yet on
# npm, creates git tags, and creates GitHub releases.
# A push with nothing pending and nothing unpublished is a clean no-op.
#
# Requires the NPM_TOKEN repo secret: an npm automation token with publish
# rights on the @understudy scope (the org must exist on npm first). Without
# it the publish step fails loudly with ENEEDAUTH — nothing partial happens.
# apps/* are private and never publish. Provenance: id-token: write +
# NPM_CONFIG_PROVENANCE below.

on:
push:
branches: [master]

concurrency: release-${{ github.ref }}

permissions:
contents: write
pull-requests: write
id-token: write

jobs:
release:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
with:
# Changesets reads history to decide what changed since the last tag.
fetch-depth: 0

# Reads the pnpm version from package.json "packageManager".
- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
registry-url: https://registry.npmjs.org

- name: Install (frozen lockfile)
run: pnpm install --frozen-lockfile

# prepublishOnly builds each package again at publish time; this build
# front-loads failures so a broken build never reaches the registry.
- name: Build publishable packages
run: pnpm --filter "./packages/*" build

- name: Version PR or publish
uses: changesets/action@v1
with:
publish: pnpm changeset publish
title: 'release: version packages'
commit: 'release: version packages'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_CONFIG_PROVENANCE: 'true'
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 ProofOfTechOrg and contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
44 changes: 32 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
# understudy

An LLM-driven system that puppets a user's *already-logged-in* browser via a Chromium
extension, coordinated by a Cloudflare-hosted backend brain. The backend never touches a
browser — it sends *intents* over a WebSocket; the extension executes them in the user's
real tab via CDP and reports back.
A governed **browser-execution service** that puppets a user's *already-logged-in* browser
via a Chromium extension. The Cloudflare-hosted service holds the live sessions and exposes
`POST /v1/sessions/:sessionId/commands`; the extension executes each command in the user's
real tab via CDP and reports back. understudy runs **no LLM** — the agent brain and all
governance (approvals, RBAC, audit via breakwater/flowsafe) live in the consumer apps that
drive it over HTTP (Topology 1).

**Full design + build plan: [`docs/technical-plan.md`](docs/technical-plan.md).** Read it first.

## Layout (current scaffold — milestones M0 + M1 + M2 + M3)
## Layout (current scaffold — milestones M0–M4)

- **`packages/protocol`** — the shared command/event protocol (TypeScript + zod 4, published
`@understudy/protocol`). The stable contract between backend and extension; the core IP.
`@understudy/protocol`). The stable contract between the service, the extension, and
consumer connectors; the core IP.
- **`packages/connector`** — **M4** the reference `@proofoftech/breakwater` connectors
(`@understudy/connector`): `observe` / `act` / `fill_credential`, approval-gated via
flowsafe grants, egress-pinned to the service host. What consumer apps import to turn
browser actions into governed Mastra tools. See its README.
- **`apps/cdp-spike`** — **M0** throwaway harness: a buildless MV3 extension that verifies the
`chrome.debugger` CDP command surface (the plan's one gating technical risk). See its README.
- **`apps/extension`** — **M2** the real extension: a WXT + React MV3 extension that puppets a
Expand All @@ -21,22 +28,35 @@ real tab via CDP and reports back.
no LLM and embeds no agent framework — the brain and governance (breakwater/flowsafe) live in
the consumers. See its README.

Coming next per the plan: **M4** — consumer integration + published contract
(`@understudy/protocol` plus a reference `@understudy/connector`); the agent loop and
governance (breakwater/flowsafe) stay consumer-side, per Topology 1.
M4 status: both packages are publish-ready (`@understudy/protocol` 0.3.0,
`@understudy/connector` 0.1.0 — MIT, `files`-scoped tarballs, `publishConfig.access:
public`) and the CI release flow is in place (see Release below); publishing waits only on
the npm `understudy` org + `NPM_TOKEN` secret. The cross-repo consumer e2e (a metamind /
smart-compliance Mastra agent + flowsafe approvals driving understudy) is the remaining
M4 step. The agent loop and governance stay consumer-side, per Topology 1.

## Develop

```sh
pnpm install
pnpm test # protocol round-trip / validation tests
pnpm build # first on a fresh clone: @understudy/* resolve via gitignored dist/
pnpm typecheck
pnpm build
pnpm test
```

Requires Node ≥22 and pnpm ≥10.16 (see `package.json`). Dependencies are quarantined for
7 days via `minimumReleaseAge` in `pnpm-workspace.yaml` (supply-chain guard against
freshly-published malicious versions).
freshly-published malicious versions; first-party `@proofoftech/*` packages are exempt).

## Release (npm)

Changesets + GitHub Actions, single-branch (see `.changeset/README.md`): a PR touching a
published package adds a changeset (`pnpm changeset`); on push to `master`,
`.github/workflows/release.yml` opens/updates the "Version Packages" PR — or, when nothing
is pending, publishes any package version not yet on npm (tags + GitHub releases, with
provenance). Requires the `NPM_TOKEN` repo secret with publish rights on the `@understudy`
scope. The first publish (`@understudy/protocol` 0.3.0, `@understudy/connector` 0.1.0)
needs no changeset — those versions are already set and unpublished.

The M0 harness needs no build — load `apps/cdp-spike` unpacked in a Chromium browser
(`apps/cdp-spike/README.md`).
23 changes: 18 additions & 5 deletions docs/technical-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ understudy/
packages/
protocol/ # PUBLISHED @understudy/protocol (zod 4): Command/Event unions + schemas.
src/index.ts # The shared contract consumers import. Add fill_secret here.
connector/ # PUBLISHED @understudy/connector (M4): reference breakwater connectors
src/index.ts # (observe/act/fill_credential) consumers import as Mastra tools.
apps/
backend/ # Cloudflare Worker — the browser-execution SERVICE (M3)
src/index.ts # Hono app: POST /v1/sessions/:id/commands, session mgmt, auth, health
Expand All @@ -139,9 +141,10 @@ understudy/
src/driver/cdp.ts # CDP executors: snapshot/click/type/navigate/... (+ dry-run seam)
wxt.config.ts
# apps/backend-node/ # FUTURE self-host of the SERVICE: reuses protocol + SessionCoordinator
# The agent loop, Claude client, tool defs, and breakwater connectors are CONSUMER code
# (metamind / smart-compliance), NOT here. understudy MAY publish a reference connector
# (@understudy/connector) as an example, but does not run it.
# The agent loop, Claude client, and tool wiring are CONSUMER code (metamind /
# smart-compliance), NOT here. understudy PUBLISHES the reference connector
# (packages/connector -> @understudy/connector) for consumers to import, but does not
# run it - no breakwater/flowsafe/Mastra code executes in this repo's apps.
```

## The command protocol (`packages/protocol`) — the core contract
Expand Down Expand Up @@ -279,7 +282,10 @@ if cross-frame targeting is needed — the single-frame path is proven.
## Consumer integration — the governed connector (breakwater + flowsafe)

This is CONSUMER code (in metamind / smart-compliance), documented here because it defines understudy's
outward contract. Reference: `smart-compliance/docs/examples/understudy-browser-connector.ts`.
outward contract. Canonical reference: **`packages/connector` (`@understudy/connector`, M4)** — tested
against the shipped M3 service contract (bearer caller auth, protocol v0.3.0). The historical sketch it
grew from, `smart-compliance/docs/examples/understudy-browser-connector.ts`, predates M3 (no caller
auth, local `fill_secret` shim) and carries stale pre-Topology-1 prose; prefer the package.

- A browser action is wrapped as a **breakwater `createConnector()`** (three connectors:
`observe` = read/no-approval, `act` = write/discriminated-union, `fill_credential` = write/vaulted).
Expand Down Expand Up @@ -396,7 +402,14 @@ outward contract. Reference: `smart-compliance/docs/examples/understudy-browser-
reference breakwater connector (`@understudy/connector`, mirroring the smart-compliance example). A real
consumer (metamind / smart-compliance) drives understudy end-to-end with a Mastra agent + flowsafe
approvals. *Cross-repo; understudy's deliverable is the published contract + reference connector, not
the agent.*
the agent.* **Status (2026-07-16): `packages/connector` BUILT** — observe (snapshot/get_tabs/wait) /
act (click/type/navigate/key/scroll/switch_tab, grant-gated) / fill_credential (vaulted), egress-pinned
`runtime.fetch`, caller bearer auth, 15 tests against the real breakwater wrapper (fail-closed grant,
idempotent replay, per-hop egress denial, dry-run). Both packages are publish-ready (MIT,
`files`-scoped tarballs, `publishConfig.access: public`) and the changesets + GitHub Actions release
flow is wired (`.github/workflows/release.yml`, single-branch master — first push publishes 0.3.0 /
0.1.0 with no changeset needed); publishing waits on the npm `understudy` org + `NPM_TOKEN` secret.
The consumer-side Mastra+flowsafe e2e remains open.
- **M5 — Substrate hardening.** Session/tenant isolation verified with two tenants; credential vault +
D-SEC audit invariant; dialog handling; error/timeout paths on every command; session/GIF logging for
audit. (Approval/RBAC/policy live in the consumer, not here.)
Expand Down
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "understudy",
"version": "0.0.0",
"private": true,
"license": "MIT",
"type": "module",
"packageManager": "pnpm@11.5.2",
"engines": {
Expand All @@ -11,6 +12,10 @@
"scripts": {
"typecheck": "pnpm -r typecheck",
"test": "pnpm -r test",
"build": "pnpm -r build"
"build": "pnpm -r build",
"changeset": "changeset"
},
"devDependencies": {
"@changesets/cli": "^2.27.0"
}
}
21 changes: 21 additions & 0 deletions packages/connector/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 ProofOfTechOrg and contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Loading
Loading