…
name: d2-maker
description: Build on a d2 project at <project>.aiheroapps.com from an AI assistant, as its maker: model the data, build the pages and apps, fill in data, write rules and stories, keep the project's Specification, report your status and hand over. Everything any AI needs on d2, fitted to the project's level. Fetched by d2-connect with GET /api/v2/skills; use whenever the person mentions d2 or their d2 project.
-core: [install, calls, working-with-your-person, log, topics-you-cant-see, new-topics, topics-and-categories, things-to-know]
+core: [install, calls, working-with-your-person, topics-you-cant-see, new-topics, topics-and-categories, things-to-know]
---
…
**Drafts.** Most tokens write **drafts**: a `PUT` to a topic answers `{"draft": true}` and saves your change as the user's draft, not as the published topic; everyone else still sees the old version. Tell the user what you changed and that it's waiting on their **Drafts** page (`/drafts`, the Drafts button in the top bar), where they see the diff and publish it. `GET /api/v2/drafts` lists the drafts, `GET /api/v2/drafts/<topic>` gives one; read your own draft, not the published topic, when you continue work on something you drafted.
**Drafts are shared.** All of one person's AIs (designer, coder, any agent) write into the *same* draft of a topic, so before any edit `GET /api/v2/drafts/<topic>`: if there is one, build on it and change only your part; starting from the published text when a draft exists silently erases another agent's pending work. A **404** from that call means no draft: then read `GET /api/v2/topics/<topic>` and build on that. Decide by the HTTP status, never by searching the reply for `E_NOT_FOUND` (topics like Design contain that text). **Every save says which version it built on** (P-103): the draft `GET` gives `ETag: "d<n>"` (and `dver`; a 404 gives `"none"`), and your `PUT` sends it back as `If-Match: "d<n>"` or `"none"`, or `?ver=d<n>` / `?ver=none`. Without it you get `428 E_VERSION_REQUIRED`; if the draft moved on since you read it, `409 E_DRAFT_CHANGED` (who saved it, and the new `dver`) and nothing is written: re-read, merge your change into it, and save again. Each `PUT` answers the new `dver`, so several saves in a row chain without re-reading. A draft or topic `PUT` takes the **raw markdown** with `Content-Type: text/markdown`; a JSON body (`{"content": …}`) is saved as the topic's text, so read the draft back once after a first write if unsure. When inserting, match a whole unique line: ids like `^pipe-27` also appear in changelog lines, so a bare id can match twice. After editing, leave a note (on your pipeline item, or an Agents board message) naming the topics and sections you touched, so another agent checks those spots before its next edit; re-read a draft before editing only when someone else has touched it since your last read. Objects aren't drafts: writes to objects are live. Deleting a topic isn't possible with a drafts token: ask the user.
-3. **The skills.** Your person installs one small skill once, **d2-connect** (Download d2-connect on the project's Tokens page, `/d2-connect.skill`). At the start of every session it has you fetch the skills for your role and the project's level: `GET /api/v2/skills?role=<your role>` (no role: you are the **maker**), and you follow what comes back, in order. Each skill has a `version`: re-read only when one changes. This skill, `d2-maker`, is the first; on a master project a specialist role also gets `d2-agents`, and a project may add skills of its own. A project's own process lives in `Skill:process`, served after the base skills: read it after them; it narrows the base rules and never loosens a permission. Projects keep no copies of base skills: to change one, propose it on base d2.
+3. **The skills.** Your person installs one small skill once, **d2-connect** (`/d2-connect.skill`, the Download d2-connect button on the project's **Settings → AI permissions → AI tokens** tab, `/ai/tokens`). At the start of every session it has you fetch the skills for your role and the project's level: `GET /api/v2/skills?role=<your role>` (no role: you are the **maker**), and you follow what comes back, in order. Each skill has a `version`: re-read only when one changes. This skill, `d2-maker`, is the first; on a master project a specialist role also gets `d2-agents`, and a project may add skills of its own. A project's own process lives in `Skill:process`, served after the base skills: read it after them; it narrows the base rules and never loosens a permission. Projects keep no copies of base skills: to change one, propose it on base d2.
4. **Try it.** Ask your AI to "list the d2 topics" and then "open the Welcome topic".
…
- People filter the wiki by tag (`/topics?tag=gold`) and search with `#gold` (`GET $B/search?q=%23gold%20nevada`: the tagged topics that also mention nevada, and the tagged files).
-## Memories: what you remember about the user
+## Memories: what you remember about the user {lookup}
+when: your person asks you to remember something, or asks what you remember
Keep what you learn about the user in `Memory:` topics instead of only in your own memory, so the user can read and correct it at `/topics?category=Memory`.
…
- **Name items as links:** whenever you mention one, link `/pipeline/P-n` with a few words on what it is; people don't remember numbers.
-## All your person's projects: an all-projects token
+## All your person's projects: an all-projects token {lookup}
+when: your person wants you to work across several of their projects
A token made with **Scope: All my projects** works on every project your person owns. List them with `GET /api/v2/projects` (on any of them): `{scope, data: [{project, url, level, plan, role}]}`, then call each at its own `url` with the same token. A project token lists only its own project. `401 E_TOKEN_SCOPE` means this token is for another project (the message names it); `403 E_TOKEN_SCOPE` a project your person doesn't own; `403 E_PLAN` that all-projects tokens are paused because your person is on the free plan.
-## Secrets: the vault
+## Secrets: the vault {lookup}
+when: you need to store or read a secret (an API key, a password)
Never ask your person to paste a secret (an API key, a GitHub token) into the chat. They keep it in their **vault** (Settings → AI permissions → Vault) and allow your token; you get it with `GET /api/v2/vault/<name>` and your own token (MCP: `vault_get`), at the start of each session. `E_SECRET` means your token isn't allowed it: ask your person to allow it in the vault, naming the secret. Use the value for the task only; never write it into a topic, memory, file, log, commit or chat. At most 30 reads a minute. [[Help:Vault]]
-## Connect over MCP
+## Connect over MCP {lookup}
+when: your person wants to connect you over MCP instead of plain HTTP
Every d2 project is also an MCP server, at `https://<project>.aiheroapps.com/mcp` (Streamable HTTP), so an agent that speaks MCP gets d2 as its own tools. In Claude Code:
…
- For an app: put its table (or tables) at the top of the app's page, then a line on what it is. Use computed fields for the numbers and the table for the view: don't compute in the page.
-## Live HTML on a page: ```embedhtml
+## Live HTML on a page: ```embedhtml {lookup}
+when: a page needs live HTML or a small script in it
When a page needs more than markdown and tables (live numbers from outside, a chart, a small widget), put HTML, CSS and a `<script>` in a ```` ```embedhtml ```` fence. It runs in a sandboxed frame inside the page:
…
- Keep it small and show a clear state when a source doesn't answer ("source unreachable"), never a blank. d2portfolio's home has an example: five live market cards.
-## Apps: whole HTML pages
+## Apps: whole HTML pages {lookup}
+when: you build a whole-page HTML app
An `App:<name>` topic is a whole HTML page, opened at `/app/<name>` under the project's top bar. Use one when a page needs a free hand (a dashboard, a custom editor); for data on an ordinary page, a ```` ```table ```` is simpler.
…
- `layout: full` in the App topic's frontmatter gives it the whole window, with no d2 chrome and a small button home; handle your own navigation (hash links). For now it runs on the project's own address as the viewer, so only make one full-page when the page needs it (a console, an app to put on a phone's home screen).
-## Fetching from the web in rules
+## Fetching from the web in rules {lookup}
+when: a rule has to fetch from another site
`http.json(url = …)` and `http.text(url = …)` GET a URL (https, public host names only; 10 s, 2 MB), `csv.parse(text = …)` turns CSV into rows, `json.parse(text = …)` parses JSON. Time spent waiting on them doesn't count against a flow's 5 s, up to 60 s in all. A flow waiting on them pauses; the server keeps serving. They work in rules and flows only; in a computed field, a table or a query they're `E_WAIT`. To fetch many at once: `ids flatMap par (i => http.json(url = …))` (or `map par`), at most 4 at a time by default, `flatMap par 8 (…)` for more (up to 32); results keep their order, and a failure is raised after all branches finish.
…
- People manage files on the **Files** page, `/cdn`.
-## Make a stylesheet
+## Make a stylesheet {lookup}
+when: your person wants their own look (a stylesheet)
A stylesheet is a `Style:` topic. When the user wants a different look (colours, fonts, the logo text), write one:
…
- Then send the user to `https://<project>.aiheroapps.com/styles`, where it's listed under **Style topics** with a **Use this stylesheet** button.
-## Crons: messages on a schedule
+## Crons: messages on a schedule {lookup}
+when: something must run on a schedule
A project's rules make crons with `diesel.cron(name, schedule, tz, msg, args)`, best in `Settings:Init` on `diesel.project.on.init` so they're declared on every start (same name replaces). Schedules: 5-field cron, `every 15m`, `hourly`, `daily 03:15` (UTC unless `tz`). Each run is a flow; 5 failures in a row switch it off. See them with `GET /api/v2/crons` or the Crons page; admins `POST /api/v2/crons/<name>/run | on | off`. Details: Help:LifecycleMessages.
…