@rubytech/create-maxy-code 0.1.243 → 0.1.246
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/platform/docs/superpowers/plans/2026-06-04-public-agent-knowledge-delivery.md +230 -0
- package/payload/platform/neo4j/schema.cypher +0 -5
- package/payload/platform/plugins/admin/mcp/dist/index.js +3 -4
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +19 -22
- package/payload/platform/plugins/admin/skills/public-agent-manager/SKILL.md +38 -72
- package/payload/platform/plugins/cloudflare/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/cloudflare/PLUGIN.md +10 -7
- package/payload/platform/plugins/cloudflare/references/api.md +166 -0
- package/payload/platform/plugins/cloudflare/references/d1-data-capture.md +157 -0
- package/payload/platform/plugins/cloudflare/references/dashboard-guide.md +6 -6
- package/payload/platform/plugins/cloudflare/references/hosting-sites.md +66 -0
- package/payload/platform/plugins/cloudflare/references/manual-setup.md +5 -3
- package/payload/platform/plugins/cloudflare/skills/cloudflare/SKILL.md +72 -0
- package/payload/platform/plugins/docs/references/cloudflare.md +4 -4
- package/payload/platform/plugins/docs/references/deployment.md +1 -1
- package/payload/platform/plugins/docs/references/internals.md +3 -6
- package/payload/platform/plugins/memory/PLUGIN.md +2 -2
- package/payload/platform/scripts/setup-account.sh +16 -8
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +1 -32
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-tail.d.ts +12 -0
- package/payload/platform/services/claude-session-manager/dist/jsonl-tail.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-tail.js +1 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-tail.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +7 -33
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +150 -132
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts +2 -1
- package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/system-prompt.js +39 -6
- package/payload/platform/services/claude-session-manager/dist/system-prompt.js.map +1 -1
- package/payload/platform/templates/agents/public/IDENTITY.md +9 -62
- package/payload/platform/templates/specialists/agents/personal-assistant.md +2 -2
- package/payload/server/{chunk-ZY6W3UA2.js → chunk-JMEX5NRX.js} +1 -22
- package/payload/server/maxy-edge.js +1 -1
- package/payload/server/server.js +7 -21
- package/payload/platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md +0 -55
- package/payload/platform/templates/agents/public/SOUL.md +0 -19
- package/payload/platform/templates/agents/public/config.json +0 -9
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Cloudflare API — master token, minted narrow tokens, endpoint map
|
|
2
|
+
|
|
3
|
+
The Cloudflare REST API is a permitted surface on this install. It is how the agent does DNS edits, Pages deploys, D1 queries, Access policies, and apex-CNAME creation without driving the dashboard. This reference is the library: where the canonical docs live, how auth works (one operator-provisioned master token; the agent mints short-lived narrow tokens from it), where the master is stored, and the curated endpoints worth knowing.
|
|
4
|
+
|
|
5
|
+
Canonical reference (always the source of truth for request shapes, never mirror it wholesale here): **https://developers.cloudflare.com/api/**
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Auth model — one master, many minted narrow tokens
|
|
10
|
+
|
|
11
|
+
There are two distinct tokens in play. Keep them separate.
|
|
12
|
+
|
|
13
|
+
- **Master token** — fully-scoped, long-lived, provisioned **once** by the operator in the dashboard (see § Provisioning the master token, below). Broad enough to manage Pages, D1, DNS, **and** create API tokens. The agent reads it only to mint narrow tokens. It is never passed to `wrangler`, never put on a command line that gets echoed, never printed.
|
|
14
|
+
- **Minted narrow token** — scoped to exactly one operation class (a single-zone DNS edit, a one-project Pages deploy, a D1 query), short-lived, created by the agent via the API call below. Exported into the environment for the one `wrangler` / API call, then discarded. Never written to disk, never committed, never echoed into chat.
|
|
15
|
+
|
|
16
|
+
Why mint instead of hand-rolling: the operator provisions the master once; every per-operation token is minted by the agent on demand, so the operator never hand-crafts scoped tokens and no broad token is ever exported to a tool that could log it.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Master token storage — account-scoped secrets file
|
|
21
|
+
|
|
22
|
+
Store the master at an account-scoped secrets file, **not** in any deployable project tree:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
~/<brand>-code/data/accounts/<accountId>/secrets/cloudflare.env # mode 600, umask 077
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
(worked example: `~/realagent-code/data/accounts/<accountId>/secrets/cloudflare.env`)
|
|
29
|
+
|
|
30
|
+
It holds exactly two values:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
CLOUDFLARE_API_TOKEN=<master>
|
|
34
|
+
CLOUDFLARE_ACCOUNT_ID=<accountId>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Rationale (binding):
|
|
38
|
+
|
|
39
|
+
1. **Account-isolated** — under `data/accounts/<accountId>/`, consistent with brand isolation; one account's master never sits where another account's flow can read it.
|
|
40
|
+
2. **Outside every deployable/git project tree** — so the god-token can never be committed or shipped in a Pages upload. This is the failure mode a project-root `.env` invites: a `.env` next to a site's source gets swept into the `wrangler pages deploy` upload or a `git add .`.
|
|
41
|
+
3. **Sourced on demand for the master only** — `set -a; . <file>; set +a` to load `CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID` into the environment for the mint call; per-operation narrow tokens go into the ephemeral environment of their single command, never into this file.
|
|
42
|
+
|
|
43
|
+
Create it once (mode 600, parent dirs 700):
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
ACCOUNT_ID="<accountId>"
|
|
47
|
+
SECRETS_DIR="${HOME}/<brand>-code/data/accounts/${ACCOUNT_ID}/secrets"
|
|
48
|
+
( umask 077; mkdir -p "${SECRETS_DIR}" )
|
|
49
|
+
# The operator pastes the master token; the agent never echoes it back.
|
|
50
|
+
( umask 077; printf 'CLOUDFLARE_API_TOKEN=%s\nCLOUDFLARE_ACCOUNT_ID=%s\n' "<master>" "${ACCOUNT_ID}" > "${SECRETS_DIR}/cloudflare.env" )
|
|
51
|
+
chmod 600 "${SECRETS_DIR}/cloudflare.env"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Load the master for a mint call:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
set -a; . "${SECRETS_DIR}/cloudflare.env"; set +a
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Provisioning the master token (ADVANCED — operator-guided, never automated)
|
|
63
|
+
|
|
64
|
+
This is the one manual, operator-driven step. The agent relays the click-path; it does **not** browser-automate it (no Playwright, no Chrome DevTools, no consent-page driver).
|
|
65
|
+
|
|
66
|
+
1. Open **https://dash.cloudflare.com/profile/api-tokens** in your browser.
|
|
67
|
+
2. Click **Create Token** → **Create Custom Token** → **Get started**.
|
|
68
|
+
3. Name it (e.g. `<brand> master`). Under **Permissions**, add these rows (Account-level unless noted):
|
|
69
|
+
- **Account · Cloudflare Pages · Edit**
|
|
70
|
+
- **Account · D1 · Edit**
|
|
71
|
+
- **Account · API Tokens · Write** ← this is what lets the agent mint narrow tokens
|
|
72
|
+
- **Zone · DNS · Edit** (Zone Resources → the zones you route)
|
|
73
|
+
- **Account · Cloudflare Tunnel · Edit** (only if you want the API to manage tunnels alongside `cloudflared`)
|
|
74
|
+
4. **Account Resources** → Include → your account. **Zone Resources** → Include → the zones in scope.
|
|
75
|
+
5. Click **Continue to summary**, then **Create Token**.
|
|
76
|
+
6. Copy the token **once** — Cloudflare shows it a single time. Paste it into the secrets file (above). The agent stores it without echoing it back.
|
|
77
|
+
|
|
78
|
+
Verify the master works (token value never printed — only the verification result):
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
set -a; . "${SECRETS_DIR}/cloudflare.env"; set +a
|
|
82
|
+
curl -sS -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
|
|
83
|
+
"https://api.cloudflare.com/client/v4/user/tokens/verify" | jq '.result.status'
|
|
84
|
+
# expect: "active"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Minting a narrow token
|
|
90
|
+
|
|
91
|
+
The agent mints a per-operation token from the master via `POST /accounts/{account_id}/tokens`. Scope it to the single operation, give it a short TTL, export it for the one call, discard it.
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
set -a; . "${SECRETS_DIR}/cloudflare.env"; set +a # loads the master + account id
|
|
95
|
+
|
|
96
|
+
# Example: a one-project Pages-deploy token. Adjust the policy for the operation.
|
|
97
|
+
MINTED=$(curl -sS -X POST \
|
|
98
|
+
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
|
|
99
|
+
-H "Content-Type: application/json" \
|
|
100
|
+
"https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/tokens" \
|
|
101
|
+
--data @- <<'JSON' | jq -r '.result.value'
|
|
102
|
+
{
|
|
103
|
+
"name": "ephemeral-pages-deploy",
|
|
104
|
+
"policies": [{
|
|
105
|
+
"effect": "allow",
|
|
106
|
+
"resources": { "com.cloudflare.api.account.<accountId>": "*" },
|
|
107
|
+
"permission_groups": [{ "id": "<pages-edit-permission-group-id>", "name": "Pages Write" }]
|
|
108
|
+
}],
|
|
109
|
+
"expires_on": "<iso8601-a-few-minutes-out>"
|
|
110
|
+
}
|
|
111
|
+
JSON
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Use it for exactly one command, in this command's environment only:
|
|
115
|
+
CLOUDFLARE_API_TOKEN="${MINTED}" wrangler pages deploy ./dist --project-name <project> --branch=main
|
|
116
|
+
|
|
117
|
+
unset MINTED # discard; never written to disk, never echoed
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Look up `<pages-edit-permission-group-id>` (and the ids for DNS Edit, D1 Edit, etc.) once via `GET /accounts/{account_id}/tokens/permission_groups`; they are stable per account.
|
|
121
|
+
|
|
122
|
+
**Redaction is binding.** Every example above pipes the token into a variable or an `Authorization` header and never to stdout. The agent surfaces the operation as verb + target — "minting a Pages-deploy token", "creating CNAME `chat.example.com`" — never the secret. A failure surfaces the API error body with any `Authorization` value masked.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Curated endpoint map
|
|
127
|
+
|
|
128
|
+
Request/response shapes live at the canonical docs URL; this is the index of what to reach for. `${ACC}` = `CLOUDFLARE_ACCOUNT_ID`, `${ZONE}` = the zone id, `${TOKEN}` = a **minted narrow** token.
|
|
129
|
+
|
|
130
|
+
### Token create / verify
|
|
131
|
+
- `GET /user/tokens/verify` — confirm a token is active.
|
|
132
|
+
- `POST /accounts/${ACC}/tokens` — mint a narrow token (see above).
|
|
133
|
+
- `GET /accounts/${ACC}/tokens/permission_groups` — resolve permission-group ids for scoping.
|
|
134
|
+
|
|
135
|
+
### DNS records (zone-scoped; needs **Zone · DNS · Edit**)
|
|
136
|
+
- `GET /zones/${ZONE}/dns_records` — list / find a record id.
|
|
137
|
+
- `POST /zones/${ZONE}/dns_records` — create. For an **apex CNAME**, set `"type":"CNAME"`, `"name":"@"` (or the bare zone name), `"content":"<UUID>.cfargotunnel.com"`, `"proxied":true` — Cloudflare flattens it automatically. This is the API equivalent of the dashboard apex edit in `dashboard-guide.md`.
|
|
138
|
+
- `PUT/PATCH /zones/${ZONE}/dns_records/{id}` — update. `DELETE …/{id}` — remove a stray record.
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
curl -sS -X POST -H "Authorization: Bearer ${TOKEN}" -H "Content-Type: application/json" \
|
|
142
|
+
"https://api.cloudflare.com/client/v4/zones/${ZONE}/dns_records" \
|
|
143
|
+
--data '{"type":"CNAME","name":"@","content":"<UUID>.cfargotunnel.com","proxied":true}' \
|
|
144
|
+
| jq '{ok:.success, name:.result.name}'
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Zones
|
|
148
|
+
- `GET /zones?account.id=${ACC}` — list zones on the account; find a zone id by name (`?name=example.com`).
|
|
149
|
+
|
|
150
|
+
### Tunnels (Account · Cloudflare Tunnel)
|
|
151
|
+
- `GET /accounts/${ACC}/cfd_tunnel` — list tunnels.
|
|
152
|
+
- `POST /accounts/${ACC}/cfd_tunnel` — create. `DELETE …/{tunnel_id}` — delete.
|
|
153
|
+
- The OAuth/`cloudflared` path in `manual-setup.md` remains the primary tunnel flow; use the API only when an end-to-end programmatic run is wanted.
|
|
154
|
+
|
|
155
|
+
### Access (Zero Trust) applications + policies
|
|
156
|
+
- `POST /accounts/${ACC}/access/apps` and `POST /accounts/${ACC}/access/apps/{app_id}/policies` — author an Access application + policy programmatically. The dashboard click-path in `dashboard-guide.md` § "Author an Access policy" is the alternative for operators who prefer to click.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## When to use which path
|
|
161
|
+
|
|
162
|
+
- **Tunnel create/route/run** → `cloudflared` (OAuth cert), per `manual-setup.md`. The API can manage tunnels too, but the cert path is the established one.
|
|
163
|
+
- **Apex CNAME** → API (above) or dashboard (`dashboard-guide.md`). Either flattens correctly.
|
|
164
|
+
- **Pages deploy** → `wrangler` with a minted Pages-Edit token, per `hosting-sites.md`.
|
|
165
|
+
- **D1 read/write** → `wrangler d1 execute --remote` with a minted D1-Edit token, per `d1-data-capture.md`.
|
|
166
|
+
- **Inspecting account state** (which zones, which tunnels) → API `GET` with a read-scoped minted token, or the dashboard.
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Capturing form submissions into Cloudflare D1
|
|
2
|
+
|
|
3
|
+
This is the established pattern for turning a static site's contact / waitlist form into durable, queryable leads: the form POSTs to a **Pages Function**, the Function inserts a row into a **Cloudflare D1** database, and the agent reads and sweeps new rows with `wrangler d1 execute --remote`. It is live on `realagent.pages.dev` today — the worked example below is that deployment, generalized with placeholders.
|
|
4
|
+
|
|
5
|
+
Pair this with `hosting-sites.md` (how the site itself gets deployed) and `api.md` (the master token + mint-narrow discipline that authenticates every `wrangler` call).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## The single most common breakage — token scope
|
|
10
|
+
|
|
11
|
+
A Pages-only token **cannot touch D1**. The deploy succeeds, the form renders, and every submission silently 500s at the D1 insert. The token used for D1-backed Pages work must carry **both**:
|
|
12
|
+
|
|
13
|
+
- **Account · Cloudflare Pages · Edit**
|
|
14
|
+
- **Account · D1 · Edit**
|
|
15
|
+
|
|
16
|
+
Mint a narrow token that includes both permission groups (see `api.md` § Minting a narrow token) before any `wrangler d1` or D1-backed deploy command. This was observed live in the session that built `realagent.pages.dev`; it is the first thing to check when captures stop arriving.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 0. Mint the Pages-+-D1 token
|
|
21
|
+
|
|
22
|
+
Every command below uses `${MINTED_PAGES_D1}` — a narrow token scoped to **both** Pages Edit and D1 Edit, minted from the master per `api.md` § Minting a narrow token. Mint it once at the start of the session and export it; it is never written to disk or echoed:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
set -a; . "${SECRETS_DIR}/cloudflare.env"; set +a # loads master + account id
|
|
26
|
+
MINTED_PAGES_D1=$(curl -sS -X POST \
|
|
27
|
+
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
|
|
28
|
+
-H "Content-Type: application/json" \
|
|
29
|
+
"https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/tokens" \
|
|
30
|
+
--data @- <<'JSON' | jq -r '.result.value'
|
|
31
|
+
{
|
|
32
|
+
"name": "ephemeral-pages-d1",
|
|
33
|
+
"policies": [{
|
|
34
|
+
"effect": "allow",
|
|
35
|
+
"resources": { "com.cloudflare.api.account.<accountId>": "*" },
|
|
36
|
+
"permission_groups": [
|
|
37
|
+
{ "id": "<pages-edit-permission-group-id>", "name": "Pages Write" },
|
|
38
|
+
{ "id": "<d1-edit-permission-group-id>", "name": "D1 Write" }
|
|
39
|
+
]
|
|
40
|
+
}],
|
|
41
|
+
"expires_on": "<iso8601-a-few-minutes-out>"
|
|
42
|
+
}
|
|
43
|
+
JSON
|
|
44
|
+
)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Resolve the two permission-group ids once via `GET /accounts/{account_id}/tokens/permission_groups` (see `api.md`). When the work is done, `unset MINTED_PAGES_D1`.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 1. Create the database
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
CLOUDFLARE_API_TOKEN="${MINTED_PAGES_D1}" wrangler d1 create <db-name>
|
|
55
|
+
# worked example: wrangler d1 create realagent-leads
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`wrangler` prints the database `name` and `database_id`. Copy the `database_id` into `wrangler.toml` (next step). The token value is never printed — only the database identifiers.
|
|
59
|
+
|
|
60
|
+
## 2. Bind the database in `wrangler.toml`
|
|
61
|
+
|
|
62
|
+
The binding name (`DB` below) is the variable the Pages Function reads off `env`. Keep `wrangler.toml` in the site's project root — it carries no secret, only the database id.
|
|
63
|
+
|
|
64
|
+
```toml
|
|
65
|
+
name = "<project>" # worked example: realagent
|
|
66
|
+
pages_build_output_dir = "dist" # or the framework's output dir
|
|
67
|
+
|
|
68
|
+
[[d1_databases]]
|
|
69
|
+
binding = "DB"
|
|
70
|
+
database_name = "<db-name>" # realagent-leads
|
|
71
|
+
database_id = "<database_id>" # from step 1
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## 3. Create the table
|
|
75
|
+
|
|
76
|
+
Apply the schema to the remote database (`--remote` targets the live D1, not a local replica):
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
CLOUDFLARE_API_TOKEN="${MINTED_PAGES_D1}" wrangler d1 execute <db-name> --remote --command \
|
|
80
|
+
"CREATE TABLE IF NOT EXISTS leads (
|
|
81
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
82
|
+
name TEXT,
|
|
83
|
+
email TEXT NOT NULL,
|
|
84
|
+
message TEXT,
|
|
85
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
86
|
+
swept INTEGER NOT NULL DEFAULT 0
|
|
87
|
+
);"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The `swept` column is the read-cursor: a row is `swept = 0` until the agent has ingested it, then flipped to `1`. This is what makes "show me new leads" a deterministic query rather than a guess.
|
|
91
|
+
|
|
92
|
+
## 4. The Pages Function — `functions/api/contact.ts`
|
|
93
|
+
|
|
94
|
+
A file at `functions/api/contact.ts` is served at `POST /api/contact` automatically (Pages Functions route by file path). It reads the `DB` binding and inserts a row. No secret lives in this file — the D1 binding is injected by the platform at runtime.
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
interface Env { DB: D1Database }
|
|
98
|
+
|
|
99
|
+
export const onRequestPost: PagesFunction<Env> = async ({ request, env }) => {
|
|
100
|
+
const form = await request.formData();
|
|
101
|
+
const email = String(form.get("email") ?? "").trim();
|
|
102
|
+
if (!email) return new Response("email required", { status: 400 });
|
|
103
|
+
|
|
104
|
+
await env.DB
|
|
105
|
+
.prepare("INSERT INTO leads (name, email, message) VALUES (?, ?, ?)")
|
|
106
|
+
.bind(String(form.get("name") ?? ""), email, String(form.get("message") ?? ""))
|
|
107
|
+
.run();
|
|
108
|
+
|
|
109
|
+
return new Response(null, { status: 303, headers: { Location: "/thanks" } });
|
|
110
|
+
};
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The site's form posts to it:
|
|
114
|
+
|
|
115
|
+
```html
|
|
116
|
+
<form method="POST" action="/api/contact">
|
|
117
|
+
<input name="email" type="email" required>
|
|
118
|
+
<input name="name">
|
|
119
|
+
<textarea name="message"></textarea>
|
|
120
|
+
<button>Join the waitlist</button>
|
|
121
|
+
</form>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## 5. Deploy
|
|
125
|
+
|
|
126
|
+
Deploy via `hosting-sites.md`. The binding in `wrangler.toml` is what wires the deployed Function to D1; the deploy token must carry both Pages Edit and D1 Edit (above).
|
|
127
|
+
|
|
128
|
+
## 6. Read and sweep new submissions
|
|
129
|
+
|
|
130
|
+
Read everything not yet ingested:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
CLOUDFLARE_API_TOKEN="${MINTED_PAGES_D1}" wrangler d1 execute <db-name> --remote --json --command \
|
|
134
|
+
"SELECT id, name, email, message, created_at FROM leads WHERE swept = 0 ORDER BY id;"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
After ingesting those rows (e.g. writing them into the graph), mark them swept so the next read returns only newer ones:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
CLOUDFLARE_API_TOKEN="${MINTED_PAGES_D1}" wrangler d1 execute <db-name> --remote --command \
|
|
141
|
+
"UPDATE leads SET swept = 1 WHERE swept = 0;"
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Outcome contract
|
|
147
|
+
|
|
148
|
+
D1 capture is done when a **test POST to the live form** appears in the unswept set:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
curl -sS -X POST -d "email=test@example.com&name=verify" "https://<project>.pages.dev/api/contact" -i | head -1
|
|
152
|
+
# then:
|
|
153
|
+
CLOUDFLARE_API_TOKEN="${MINTED_PAGES_D1}" wrangler d1 execute <db-name> --remote --command \
|
|
154
|
+
"SELECT email FROM leads WHERE swept = 0;"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The test email appearing in that `SELECT` is the proof. A 200/303 on the POST alone is not — the row in D1 is the contract. Surface the operation as verb + target ("inserting a test lead", "reading unswept rows from `realagent-leads`"); the token value never appears.
|
|
@@ -105,12 +105,12 @@ Use this when you need to audit which hostnames are pointing at which `<UUID>.cf
|
|
|
105
105
|
|
|
106
106
|
## Author an Access policy for SSH or SMB
|
|
107
107
|
|
|
108
|
-
When the agent adds an SSH or SMB ingress hostname (per `references/manual-setup.md`),
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
(`
|
|
112
|
-
|
|
113
|
-
|
|
108
|
+
When the agent adds an SSH or SMB ingress hostname (per `references/manual-setup.md`),
|
|
109
|
+
the Access policy must exist before off-LAN clients can reach the Pi. There are two
|
|
110
|
+
paths: the agent can author it via the Cloudflare API with a minted Access-scoped
|
|
111
|
+
token (see `references/api.md` § Access), or the operator can author it by hand in the
|
|
112
|
+
dashboard with the click-path below. `cloudflared` CLI has no Access-application create
|
|
113
|
+
subcommand, so the dashboard path is the manual alternative to the API.
|
|
114
114
|
|
|
115
115
|
1. Click **Zero Trust** in the sidebar.
|
|
116
116
|
2. Click **Access** → **Applications**.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Deploying a site to Cloudflare Pages
|
|
2
|
+
|
|
3
|
+
How to put a static or Next.js site on Cloudflare Pages via `wrangler`, the way `realagent.pages.dev` is deployed. This covers the build → deploy → verify loop and the Next.js specifics. Auth is a minted narrow token per `api.md`; if the site also captures form data, read `d1-data-capture.md` for the dual Pages-Edit **and** D1-Edit scope.
|
|
4
|
+
|
|
5
|
+
This is distinct from `references/serving-published-sites.md`, which serves a platform-published site at a custom domain through the brand's **cloudflared tunnel**. This reference is about **Cloudflare Pages** — Cloudflare hosts the build, not the install device.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## When to use Pages vs the tunnel
|
|
10
|
+
|
|
11
|
+
- **Cloudflare Pages** (this reference) — a standalone marketing/landing/app site that Cloudflare builds and serves on its edge, optionally with Pages Functions + D1. Use when the site is its own project and you want Cloudflare to host it.
|
|
12
|
+
- **The brand tunnel** (`manual-setup.md` + `serving-published-sites.md`) — expose something running on the install device at a custom domain. Use when the content is served by the platform itself.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 1. Build the site
|
|
17
|
+
|
|
18
|
+
Produce the static output the framework emits.
|
|
19
|
+
|
|
20
|
+
- **Static / Vite / Astro:** `npm run build` → output in `dist/` (or the framework default).
|
|
21
|
+
- **Next.js:** Pages needs a static or edge-compatible build. For a fully static site use `output: "export"` in `next.config.js` and `npm run build` → output in `out/`. For a Next.js app with server routes, deploy through the Pages adapter (`@cloudflare/next-on-pages`): `npx @cloudflare/next-on-pages` → output in `.vercel/output/static`.
|
|
22
|
+
|
|
23
|
+
## 2. Configure `wrangler.toml`
|
|
24
|
+
|
|
25
|
+
```toml
|
|
26
|
+
name = "<project>" # the Pages project name, e.g. realagent
|
|
27
|
+
pages_build_output_dir = "<output-dir>" # dist | out | .vercel/output/static
|
|
28
|
+
compatibility_date = "2024-01-01"
|
|
29
|
+
compatibility_flags = ["nodejs_compat"] # required for next-on-pages and most Functions
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
If the site captures form data, add the `[[d1_databases]]` binding from `d1-data-capture.md` here.
|
|
33
|
+
|
|
34
|
+
## 3. Mint a deploy token
|
|
35
|
+
|
|
36
|
+
A Pages deploy needs **Account · Cloudflare Pages · Edit**. If the site has Pages Functions hitting D1, the token also needs **Account · D1 · Edit** (the single most common breakage — see `d1-data-capture.md`). Mint it per `api.md` § Minting a narrow token:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
set -a; . "${SECRETS_DIR}/cloudflare.env"; set +a
|
|
40
|
+
# MINTED = a narrow token scoped to Pages Edit (+ D1 Edit if the site uses D1)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 4. Deploy
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
CLOUDFLARE_API_TOKEN="${MINTED}" wrangler pages deploy <output-dir> \
|
|
47
|
+
--project-name <project> --branch=main
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`--branch=main` deploys to the production alias (`https://<project>.pages.dev`); any other branch name produces a preview URL instead. The first deploy of a new project name creates the project. The token value is never printed — `wrangler` prints the deployment URL, which is what gets surfaced.
|
|
51
|
+
|
|
52
|
+
## 5. Custom domain (optional)
|
|
53
|
+
|
|
54
|
+
Attach a custom domain to the Pages project in the dashboard (**Workers & Pages → your project → Custom domains → Set up a domain**), or via the API. Cloudflare provisions the certificate and the CNAME automatically when the zone is on the same account.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Outcome contract
|
|
59
|
+
|
|
60
|
+
Hosting is done when the deployed URL serves the **new** build:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
curl -s "https://<project>.pages.dev/" | head
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The response showing the new markup is the proof. A successful `wrangler` exit alone is not the contract — the live URL returning the new content is. Surface the operation as verb + target ("deploying Pages project `realagent`"); the token value never appears in chat or stdout.
|
|
@@ -293,9 +293,11 @@ does this on every Pi). The script skips the SMB ingress with
|
|
|
293
293
|
`[tunnel-install] smb-ingress-skipped reason=samba-not-provisioned`
|
|
294
294
|
rather than failing.
|
|
295
295
|
|
|
296
|
-
**Access policy
|
|
297
|
-
|
|
298
|
-
the
|
|
296
|
+
**Access policy — API or dashboard.** `cloudflared` CLI has no
|
|
297
|
+
Access-application create subcommand, so author the policy either via
|
|
298
|
+
the Cloudflare API with a minted Access-scoped token (see
|
|
299
|
+
`references/api.md` § Access) or by hand in the dashboard. After the
|
|
300
|
+
ingress is in place, the dashboard path is:
|
|
299
301
|
|
|
300
302
|
```
|
|
301
303
|
Cloudflare → Zero Trust → Access → Applications → Add → Self-hosted
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cloudflare
|
|
3
|
+
description: Cloudflare operations for the install — tunnel setup/diagnosis/reset, DNS edits, Pages hosting, and D1 data capture. The agent drives `cloudflared` and `wrangler` directly via Bash and reads the matching reference before each operation class; success is always a live behavioural signal (external HTTP 200, a deployed URL, a D1 round-trip), never an exit code.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Cloudflare operations
|
|
7
|
+
|
|
8
|
+
This is the entry point for every Cloudflare task on the install. Pick the operation class, read the matching reference, run the command via Bash, relay the literal output, and prove the outcome with a live signal. There are no Cloudflare MCP tools; the plugin registers none. The agent never browser-automates the dashboard — the operator clicks where a click is genuinely required.
|
|
9
|
+
|
|
10
|
+
| You want to… | Read | Outcome contract |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| Stand up / diagnose / reset a tunnel so a hostname is reachable | `references/manual-setup.md` | external `curl -I https://<hostname>` → `200` |
|
|
13
|
+
| Switch accounts, edit an apex CNAME by hand, or other dashboard-only clicks | `references/dashboard-guide.md` | the click-path completed in the operator's browser |
|
|
14
|
+
| Tear down corrupt tunnel state and start clean | `references/reset-guide.md` | a fresh `manual-setup.md` run reaches `200` |
|
|
15
|
+
| Call the Cloudflare API (DNS, zones, tunnels, Access, token mint) | `references/api.md` | the API call returns success and the change is observable |
|
|
16
|
+
| Deploy a static / Next.js site to Cloudflare Pages | `references/hosting-sites.md` | the deployed URL serves the new build |
|
|
17
|
+
| Capture form/waitlist submissions into a database | `references/d1-data-capture.md` | a test POST appears in `SELECT … WHERE swept = 0` |
|
|
18
|
+
|
|
19
|
+
## Two auth paths, by operation class
|
|
20
|
+
|
|
21
|
+
- **Tunnel auth is OAuth.** `cloudflared tunnel login` writes `cert.pem`; the operator clicks the linkified OAuth URL in their own browser. This is the only path for tunnel create/route/run. See `references/manual-setup.md`.
|
|
22
|
+
- **API auth is the master token.** The operator provisions one fully-scoped master token once (an **advanced**, operator-guided dashboard step — the agent does not automate it) and stores it at the account-scoped secrets file. The agent reads the master only to **mint a narrowly-scoped, short-lived token** for each operation — a single-zone DNS edit, a one-project Pages deploy, a D1 query — and exports that ephemeral token for the one call. See `references/api.md` for the storage convention, the mint flow, and the binding redaction discipline.
|
|
23
|
+
|
|
24
|
+
Neither the master token nor any minted token is ever written into a project tree, committed, or echoed into chat.
|
|
25
|
+
|
|
26
|
+
## Outcome contracts — what "done" means
|
|
27
|
+
|
|
28
|
+
Every Cloudflare operation proves itself with a live behavioural signal surfaced verbatim in chat:
|
|
29
|
+
|
|
30
|
+
- **Tunnel:** `curl -I https://<hostname>` from outside the local network returns `HTTP/2 200` (or `HTTP/1.1 200 OK`). No state file, no `result=ok` line, no "service is active" claim substitutes. If the curl returns anything else, diagnose with `cloudflared tunnel info <tunnelId>` and `systemctl --user status ${BRAND}-cloudflared.service` and recover per `references/manual-setup.md`.
|
|
31
|
+
- **Hosting:** the deployed Pages URL returns the new build (`curl -s https://<project>.pages.dev/ | head` shows the new markup).
|
|
32
|
+
- **D1 capture:** a test POST to the live form appears in `wrangler d1 execute <db> --remote --command "SELECT … WHERE swept = 0"`.
|
|
33
|
+
|
|
34
|
+
## Inputs (tunnel)
|
|
35
|
+
|
|
36
|
+
Four inputs come from the operator in chat: the admin FQDN, optional public FQDN, optional apex FQDN, and the admin password. `BRAND` is derived from disk — never hardcoded — with `BRAND=$(jq -r .hostname ~/.<configDir>/brand.json)`. The same install can host different brands on different ports; `brand.json.hostname` is the only authoritative source.
|
|
37
|
+
|
|
38
|
+
## Execution
|
|
39
|
+
|
|
40
|
+
The agent reads the relevant reference and executes its steps with Bash. Every `cloudflared` / `wrangler` / `curl` invocation's stdout and stderr are relayed into chat verbatim — minus any secret, which is always redacted. The OAuth URL printed by `cloudflared tunnel login` is linkified by the native PTY; the operator clicks it in their own browser. The agent does not spawn a browser, automate the Cloudflare consent page, or drive the dashboard via Playwright or Chrome DevTools.
|
|
41
|
+
|
|
42
|
+
Tunnel mutations the agent performs from `references/manual-setup.md` (each one only after reading the relevant step):
|
|
43
|
+
|
|
44
|
+
- `cloudflared tunnel login` — emits the OAuth URL; cert lands at `~/.cloudflared/cert.pem` after the operator authorises.
|
|
45
|
+
- `cloudflared tunnel --origincert <cert> create <name>` — produces `<tunnelId>.json` credentials.
|
|
46
|
+
- Write `~/.${BRAND}/cloudflared/config.yml` (ingress block per the runbook).
|
|
47
|
+
- `cloudflared tunnel --origincert <cert> route dns <tunnelId> <hostname>` — one call per non-apex hostname.
|
|
48
|
+
- For non-admin hostnames not starting with `public.`, append to `~/.${BRAND}/alias-domains.json` so `isPublicHost()` treats them as public.
|
|
49
|
+
- Install + start the `${BRAND}-cloudflared.service` user unit (`systemctl --user daemon-reload && systemctl --user enable --now ${BRAND}-cloudflared.service`).
|
|
50
|
+
|
|
51
|
+
After the service is up, the agent runs `curl -I https://<admin-hostname>` and pastes the response. The setup-done claim only fires when a `200` line appears in that response.
|
|
52
|
+
|
|
53
|
+
## Apex hostnames
|
|
54
|
+
|
|
55
|
+
The `cloudflared` CLI cannot route apex records (e.g. `maxy.chat`) — standard DNS forbids a CNAME at the zone apex. There are now two ways to create the apex record: the operator edits the apex CNAME in the dashboard (`references/dashboard-guide.md` § "Edit an apex CNAME"), or the agent creates it via the Cloudflare API with a minted DNS-scoped token (`references/api.md` § DNS records). Either reaches the same flattened-CNAME result; pick the dashboard path when the operator prefers to click, the API path when an end-to-end agent run is wanted.
|
|
56
|
+
|
|
57
|
+
## Reset
|
|
58
|
+
|
|
59
|
+
When local Cloudflare state is corrupt or the operator wants to start from a known-good cert, follow `references/reset-guide.md`. The agent issues the relevant `cloudflared tunnel delete` and `rm -rf ~/.${BRAND}/cloudflared/` commands in Bash, then re-runs the setup steps above.
|
|
60
|
+
|
|
61
|
+
## Tool discipline — binding
|
|
62
|
+
|
|
63
|
+
When the operator's request touches Cloudflare, the agent's permitted actions are:
|
|
64
|
+
|
|
65
|
+
- Invoke `cloudflared` via Bash, following `references/manual-setup.md`.
|
|
66
|
+
- Invoke `wrangler` and the Cloudflare API via Bash, following `references/api.md`, `references/hosting-sites.md`, and `references/d1-data-capture.md`, using a freshly-minted narrow token per operation.
|
|
67
|
+
- Write `config.yml` and `alias-domains.json` per the runbook.
|
|
68
|
+
- Install and start the brand's cloudflared user service.
|
|
69
|
+
- Quote `references/manual-setup.md`, `references/reset-guide.md`, or `references/dashboard-guide.md` verbatim when a dashboard click-path is the right tool.
|
|
70
|
+
- Verify reachability via `curl -I https://<hostname>` and surface the response.
|
|
71
|
+
|
|
72
|
+
The agent does not drive the dashboard via Playwright or Chrome DevTools, and does not browser-automate master-token creation. It does not synthesise `cloudflared` flag combinations from web search or training — every command comes from the runbook. It never writes a token to disk in a project tree, commits one, or prints one into chat. When a step fails, the agent reports the exact output, names the recovery step from `references/reset-guide.md`, and stops. Improvisation — "let me try a different flag" or "let me drive the dashboard myself" — is the behaviour this rule exists to prevent.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Cloudflare Tunnel — the dashboard is the source of truth
|
|
2
2
|
|
|
3
|
-
Each installation has its own Cloudflare account.
|
|
3
|
+
Each installation has its own Cloudflare account. The **tunnel** sign-in is OAuth: the agent invokes `cloudflared tunnel login` via Bash; the Cloudflare Authorize URL streams into the admin chat PTY and the native terminal renders it as a clickable link. Click it, authorise in your own browser, and `cloudflared` writes `cert.pem` to the brand's config directory. For **everything else** (DNS, Pages, D1, Access) the agent uses the Cloudflare API, authenticated by a short-lived narrow token it mints from a master token you provision once in the dashboard (an advanced step the agent never automates). Some account-side jobs — adding a domain, switching accounts — are still easiest in your browser, and the agent relays those click-paths; the rest it can do directly via the API.
|
|
4
4
|
|
|
5
5
|
## Identity model
|
|
6
6
|
|
|
@@ -8,10 +8,10 @@ Each installation has its own Cloudflare account. Sign-in is OAuth: the agent in
|
|
|
8
8
|
|------|--------|
|
|
9
9
|
| **Product identity** (Maxy vs Real Agent) | `brand.json` (`productName`, `configDir`) — known at install. |
|
|
10
10
|
| **Cloudflare account identity** | `cert.pem` from OAuth. One account per brand per device. |
|
|
11
|
-
| **Domain scope** (which zones the operator can route) |
|
|
11
|
+
| **Domain scope** (which zones the operator can route) | The operator picks the zone in the dashboard during OAuth or names it in chat; the agent can also enumerate zones via the API with a minted read-scoped token. |
|
|
12
12
|
| **Local tunnel state** | `~/{configDir}/cloudflared/` — `cert.pem`, `<UUID>.json`, `config.yml`, `alias-domains.json`. |
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
Tunnel auth on the operator-owned path (Mode A) is the OAuth cert (`cert.pem`); API operations use a narrow token the agent mints from your master token. To switch Cloudflare accounts, the agent runs the reset flow from `plugins/cloudflare/references/reset-guide.md` (deletes the cert and every tunnel on the current account), then the manual-setup flow again — `cloudflared tunnel login` picks a fresh account when you sign in.
|
|
15
15
|
|
|
16
16
|
## Setup flow
|
|
17
17
|
|
|
@@ -97,6 +97,6 @@ The most common cause is wrong nameservers on the domain. The domain must use Cl
|
|
|
97
97
|
|
|
98
98
|
**Does:** invokes `cloudflared` directly via Bash, following `plugins/cloudflare/references/manual-setup.md` step by step; quotes click-paths from the reference files verbatim; verifies external reachability with `curl -I` and surfaces the response.
|
|
99
99
|
|
|
100
|
-
**Does not:** drive the Cloudflare dashboard via Playwright, synthesise alternative `cloudflared` flag sequences not in the runbook,
|
|
100
|
+
**Does not:** drive the Cloudflare dashboard via Playwright, browser-automate master-token creation, synthesise alternative `cloudflared` flag sequences not in the runbook, write or echo any API token, write or edit `cert.pem` / `config.yml` directly outside the runbook's instructions.
|
|
101
101
|
|
|
102
102
|
When a command fails, the agent reports the failure and cites the relevant recovery step. It does not improvise.
|
|
@@ -70,7 +70,7 @@ Grep for both in `~/.<brand>/logs/install-*.log`. Absence after a clean install
|
|
|
70
70
|
|
|
71
71
|
The first user-domain write the agent attempts (e.g. recording who the operator is) hits the graph-write gate's `Write blocked (no-admin-user)` or `Write blocked (no-local-business)` error. The agent then asks the persona question, persists the answer through the `business-profile` skill or `profile-update.personFields`, and proceeds. The error itself is the signal — grep `Write blocked` in `~/.<brand>/logs/server.log` to confirm.
|
|
72
72
|
|
|
73
|
-
Cloudflare, WhatsApp, Telegram, and any other dormant capability surfaces on owner request via the `<dormant-plugins>` sentinel the manager injects per-spawn. Execution is the existing plugin skill (`cloudflare:
|
|
73
|
+
Cloudflare, WhatsApp, Telegram, and any other dormant capability surfaces on owner request via the `<dormant-plugins>` sentinel the manager injects per-spawn. Execution is the existing plugin skill (`cloudflare:cloudflare`, etc.) — no banner, no per-step flag.
|
|
74
74
|
|
|
75
75
|
### Per-spawn system-prompt sentinels
|
|
76
76
|
|
|
@@ -223,7 +223,7 @@ When `search` is true and `query` is non-null, the rewritten query replaces the
|
|
|
223
223
|
|
|
224
224
|
### Knowledge retrieval gate
|
|
225
225
|
|
|
226
|
-
|
|
226
|
+
The public agent is toolless by construction (Task 615): it has no `memory-search`, no graph access mid-turn, and no tools of any kind. KNOWLEDGE.md (when present) plus SOUL are assembled into the agent's system prompt at spawn time and are the entire knowledge surface. Every `role=public` spawn (webchat, whatsapp, telegram) resolves an empty allowlist and runs in `dontAsk` (Task 506) with a per-spawn `permissions.deny` covering every native, harness, and memory-MCP tool (Task 612). The spawner anchors the empty allowlist with a single non-native deny-basis token (`mcp__none__deny-basis`) so `--allowed-tools` is always present and native-excluding (Task 609) — without it, `dontAsk` would have nothing to deny against and the brand `allow:["*"]` would re-open native tools.
|
|
227
227
|
|
|
228
228
|
### Observability
|
|
229
229
|
|
|
@@ -340,10 +340,7 @@ The final step in the retrieval pipeline is injecting retrieved content into the
|
|
|
340
340
|
|
|
341
341
|
Public agents run on the same native Claude Code PTY surface as the admin, dispatched through the channel PTY-bridge with `role: 'public'`. The agent's directory files (IDENTITY.md, SOUL.md, KNOWLEDGE.md, KNOWLEDGE-SUMMARY.md when present) are assembled into the system prompt at spawn time. There is no per-turn server-side knowledge injection.
|
|
342
342
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
- **`liveMemory: false`** — `memory-search` is excluded from the per-spawn `--allowed-tools` allowlist. The agent has no graph access mid-conversation; KNOWLEDGE.md is the ceiling of factual knowledge.
|
|
346
|
-
- **`liveMemory: true`** — `memory-search` is in the allowlist. The agent decides at message time whether to call it; reads run against the graph with `ALLOWED_SCOPES=public` so only public-scoped nodes return. KNOWLEDGE.md and the live `memory-search` surface are complementary — the baked file covers evergreen facts; the live tool covers the long-tail public-scoped lookups.
|
|
343
|
+
The public agent is toolless by construction (Task 615): `memory-search` and every other tool are excluded from the per-spawn `--allowed-tools` allowlist on every public channel, and a per-spawn `permissions.deny` blocks them outright (Task 612). The agent has no graph access mid-conversation; KNOWLEDGE.md is the ceiling of factual knowledge.
|
|
347
344
|
|
|
348
345
|
### KNOWLEDGE.md staleness guard
|
|
349
346
|
|
|
@@ -447,7 +444,7 @@ The SDK's per-tool override is `_meta["anthropic/alwaysLoad"]: true` on each MCP
|
|
|
447
444
|
|
|
448
445
|
Each `claude` PTY spawn registers every callable MCP server and every dispatchable subagent before the operator's first turn. **Platform MCP servers come from one channel — installed plugins — for admin and specialist spawns (Task 502).** Claude Code's plugin system serves every plugin MCP tool under the long prefix `mcp__plugin_<plugin>_<server>__<tool>` (for platform plugins `plugin == server == directory`), which is the canonical name the admin `--allowed-tools` argv and every specialist `tools:` frontmatter bind to. Admin spawns no longer write a per-spawn `.mcp.json` or pass `--mcp-config`; the per-account env (`ACCOUNT_ID`, `USER_ID`, `NEO4J_URI`, `NEO4J_PASSWORD`, `PLATFORM_ROOT`, `CLAUDE_CONFIG_DIR`) rides the PTY env block.
|
|
449
446
|
|
|
450
|
-
**Public agents are the one exception.** A public-facing web agent
|
|
447
|
+
**Public agents are the one exception.** A public-facing web agent is toolless by construction (Task 615), so public spawns retain the per-spawn `mcp-config.json` (`--mcp-config <path>`) but register **zero** servers in it — the file carries an empty `mcpServers`. Combined with the empty `--allowed-tools`, the `dontAsk` mode, and the per-spawn `permissions.deny` (Task 612), no tool reaches an anonymous visitor on any channel. `--strict-mcp-config` (which only ever guarded auto-discovery of a project `.mcp.json`) is retained on the public per-spawn path so no project file is discovered either; it is dropped from admin spawns that no longer pass `--mcp-config`.
|
|
451
448
|
|
|
452
449
|
For subagents, the same spawn pushes `--add-dir` for every bundled plugin agents directory (`platform/plugins/*/agents/`, `premium-plugins/*/agents/`) — both roles — plus the per-account specialists directory `<accountDir>/specialists/agents/` (admin only). Claude Code's `subagent_type` dispatch reads the agent file off disk via the added directories; without `--add-dir` the dispatcher returns "no matching agent."
|
|
453
450
|
|
|
@@ -3,7 +3,7 @@ name: memory
|
|
|
3
3
|
description: "Graph memory plugin. Provides memory-search (with optional `fields` projection for known-shape lookups), memory-write, memory-update, memory-edge (create/delete typed directed edges between pre-existing nodes), memory-update-by-name (repair surface that resolves an elementId from a (label, name, accountId) tuple when search cannot return one), memory-lookup-by-name (deterministic read surface that returns nodes by case-insensitive exact `name` match, optionally constrained by labels, bypassing the memory-search ranking stack so a present entity is never missed because a diluted query ranked it below the cut), and the :Report surface (memory-report-write / memory-report-read-latest / memory-report-list) for reading from, writing to, and updating the Neo4j knowledge graph. Includes conversational memory — organic preference learning, evidence-backed recall, and transparent 'what do you know about me?' responses. Document ingestion goes through memory-ingest; the dispatched specialist produces typed-section JSON in-turn from the loaded ontology. Operator-solicited reclassify of an already-stored thread-shaped KD (today: email-thread KDs) is exposed as `kd-classify <attachmentId>` — runs a body-growth gate (proceed iff body grew ≥25% since the last classify, or first-ever classify), stages the body to a temp file under `$ACCOUNT_DIR/tmp/`, and returns a dispatch envelope for the `librarian`; on success `memory-ingest` replaces the prior :Section children and stamps `lastClassifiedAt` + `lastClassifiedBodyLength` on the parent KD. Two modes: `document` (default) for unstructured PDF/web content → :KnowledgeDocument (keyed on `attachmentId`) + :Section, and `chat` for conversation transcripts → :ConversationArchive (keyed on `conversationIdentity`) + :Section chunks; two parent labels, two writer paths chosen by which identity property is set. Conversation-archive Phase 2 splits across two read-only tools (Task 433): `conversation-archive-list-chunks` pages chunk bodies for the dispatched specialist to read in-turn; `conversation-archive-derive-insights` converts the specialist's per-chunk claims into operator-facing proposals with per-kind cypher. `conversation-archive-enrich-rejection` records (or undoes) durable per-row rejections so already-triaged claims do not re-surface on re-runs. Ships five skills: `conversational-memory`, `document-ingest`, `conversation-archive` (source-agnostic transcript ingest for WhatsApp, Telegram, Signal, LinkedIn DMs, Zoom, meeting minutes, iMessage, Slack), `conversation-archive-mcp` (operator-initiated MCP-driven ingest for Granola, Otter, Circleback meetings — sibling of `conversation-archive` sharing the same writer surface and identity formula), and `conversation-archive-enrich` (per-row operator-gated insight derivation over a named archive's chunks). Ships three thinking-tool skills: `challenge` (adversarial retrieval — strongest counter-evidence from the graph for a given claim), `connect` (bridge-finding between two topics via shared graph neighbors), and `emerge` (clusters uncategorised KnowledgeDocument and Section nodes into operator-approved Concept proposals)."
|
|
4
4
|
tools:
|
|
5
5
|
- name: memory-search
|
|
6
|
-
publicAllowlist:
|
|
6
|
+
publicAllowlist: false
|
|
7
7
|
adminAllowlist: true
|
|
8
8
|
- name: memory-write
|
|
9
9
|
publicAllowlist: false
|
|
@@ -161,7 +161,7 @@ mcp-manifest: auto
|
|
|
161
161
|
|
|
162
162
|
# Memory
|
|
163
163
|
|
|
164
|
-
Provides read and write access to the Neo4j knowledge graph — search, structured writes, document ingestion, attachment management, conversation history, and user profiles. Tool routing is each agent's IDENTITY.md responsibility; this plugin describes what tools are available, not when to use them. The admin agent and specialist subagents call these tools directly via MCP. Public agents
|
|
164
|
+
Provides read and write access to the Neo4j knowledge graph — search, structured writes, document ingestion, attachment management, conversation history, and user profiles. Tool routing is each agent's IDENTITY.md responsibility; this plugin describes what tools are available, not when to use them. The admin agent and specialist subagents call these tools directly via MCP. Public agents are toolless by construction (Task 615): they receive no MCP tools from this plugin and have no graph access mid-conversation — their knowledge comes only from the baked KNOWLEDGE.md and SOUL assembled into the system prompt at spawn.
|
|
165
165
|
|
|
166
166
|
Tools are available via the `memory` MCP server.
|
|
167
167
|
|