open-walnut 0.3.0
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/LICENSE +21 -0
- package/README.md +339 -0
- package/bin/open-walnut.js +2 -0
- package/bin/walnut.js +2 -0
- package/dist/cli.js +137849 -0
- package/dist/daemon-binaries/acp-worker.js +44 -0
- package/dist/daemon-binaries/acp-worker.js.version +1 -0
- package/dist/data/skills/daily-standup/SKILL.md +16 -0
- package/dist/data/skills/install-plugin/SKILL.md +118 -0
- package/dist/data/skills/learn/SKILL.md +46 -0
- package/dist/data/skills/register-repo/SKILL.md +20 -0
- package/dist/data/skills/setup-cloud-companion/SKILL.md +326 -0
- package/dist/data/skills/triage/SKILL.md +23 -0
- package/dist/data/skills/walnut-calendar/SKILL.md +92 -0
- package/dist/data/skills/walnut-manage-repos/SKILL.md +107 -0
- package/dist/data/skills/walnut-peer-sessions/SKILL.md +61 -0
- package/dist/data/skills/walnut-register-repo/SKILL.md +72 -0
- package/dist/data/skills/walnut-register-repo/references/yaml-format.md +111 -0
- package/dist/data/skills/walnut-register-repo/scripts/validate-repo.py +177 -0
- package/dist/data/skills/walnut-routines/SKILL.md +65 -0
- package/dist/data/skills/weekly-review/SKILL.md +22 -0
- package/dist/data/walnut-calendar.swift +213 -0
- package/dist/data/walnut-extract.swift +80 -0
- package/dist/hooks/on-compact.js +976 -0
- package/dist/hooks/on-stop.js +1329 -0
- package/dist/integrations/jira/index.js +4200 -0
- package/dist/integrations/jira/manifest.json +26 -0
- package/dist/integrations/local/index.js +51 -0
- package/dist/integrations/local/manifest.json +10 -0
- package/dist/integrations/ms-todo/index.js +4672 -0
- package/dist/integrations/ms-todo/manifest.json +17 -0
- package/dist/session-server/index.js +1382 -0
- package/dist/web/server.js +135275 -0
- package/dist/web/static/assets/apl-B4CMkyY2.js +1 -0
- package/dist/web/static/assets/asciiarmor-Df11BRmG.js +1 -0
- package/dist/web/static/assets/asn1-EdZsLKOL.js +1 -0
- package/dist/web/static/assets/asterisk-B-8jnY81.js +1 -0
- package/dist/web/static/assets/brainfuck-C4LP7Hcl.js +1 -0
- package/dist/web/static/assets/clike-B9uivgTg.js +1 -0
- package/dist/web/static/assets/clojure-BMjYHr_A.js +1 -0
- package/dist/web/static/assets/cmake-BQqOBYOt.js +1 -0
- package/dist/web/static/assets/cobol-CWcv1MsR.js +1 -0
- package/dist/web/static/assets/coffeescript-S37ZYGWr.js +1 -0
- package/dist/web/static/assets/commonlisp-DBKNyK5s.js +1 -0
- package/dist/web/static/assets/crystal-SjHAIU92.js +1 -0
- package/dist/web/static/assets/css-BnMrqG3P.js +1 -0
- package/dist/web/static/assets/cypher-C_CwsFkJ.js +1 -0
- package/dist/web/static/assets/d-pRatUO7H.js +1 -0
- package/dist/web/static/assets/diff-DbItnlRl.js +1 -0
- package/dist/web/static/assets/dockerfile-BKs6k2Af.js +1 -0
- package/dist/web/static/assets/dtd-DF_7sFjM.js +1 -0
- package/dist/web/static/assets/dylan-DwRh75JA.js +1 -0
- package/dist/web/static/assets/ebnf-CDyGwa7X.js +1 -0
- package/dist/web/static/assets/ecl-Cabwm37j.js +1 -0
- package/dist/web/static/assets/eiffel-CnydiIhH.js +1 -0
- package/dist/web/static/assets/elm-vLlmbW-K.js +1 -0
- package/dist/web/static/assets/erlang-BNw1qcRV.js +1 -0
- package/dist/web/static/assets/factor-kuTfRLto.js +1 -0
- package/dist/web/static/assets/fcl-Kvtd6kyn.js +1 -0
- package/dist/web/static/assets/forth-Ffai-XNe.js +1 -0
- package/dist/web/static/assets/fortran-DYz_wnZ1.js +1 -0
- package/dist/web/static/assets/gas-Bneqetm1.js +1 -0
- package/dist/web/static/assets/gherkin-heZmZLOM.js +1 -0
- package/dist/web/static/assets/groovy-D9Dt4D0W.js +1 -0
- package/dist/web/static/assets/haskell-Cw1EW3IL.js +1 -0
- package/dist/web/static/assets/haxe-H-WmDvRZ.js +1 -0
- package/dist/web/static/assets/http-DBlCnlav.js +1 -0
- package/dist/web/static/assets/idl-BEugSyMb.js +1 -0
- package/dist/web/static/assets/index-B3K5yAdA.js +1 -0
- package/dist/web/static/assets/index-B7OvpXOs.js +632 -0
- package/dist/web/static/assets/index-C07UNJIJ.js +1 -0
- package/dist/web/static/assets/index-C8k-3wfu.js +2 -0
- package/dist/web/static/assets/index-CBXGsT3t.js +1 -0
- package/dist/web/static/assets/index-CUrO3qpX.js +1 -0
- package/dist/web/static/assets/index-CpTvBBAa.js +1 -0
- package/dist/web/static/assets/index-D4J6slTf.js +1 -0
- package/dist/web/static/assets/index-DUgC5K_q.js +3 -0
- package/dist/web/static/assets/index-DhqXE1wa.js +1 -0
- package/dist/web/static/assets/index-DkGqHTCk.js +1 -0
- package/dist/web/static/assets/index-DsxFiBic.js +1 -0
- package/dist/web/static/assets/index-Dz3umkfm.js +1 -0
- package/dist/web/static/assets/index-G2v0oaqp.js +1 -0
- package/dist/web/static/assets/index-GNDKJKwy.js +1 -0
- package/dist/web/static/assets/index-U1CVtNUH.css +32 -0
- package/dist/web/static/assets/index-ft4kpQXq.js +1 -0
- package/dist/web/static/assets/index-ls7h0u-p.js +1 -0
- package/dist/web/static/assets/index-nL5l5WPW.js +7 -0
- package/dist/web/static/assets/javascript-iXu5QeM3.js +1 -0
- package/dist/web/static/assets/julia-DuME0IfC.js +1 -0
- package/dist/web/static/assets/livescript-BwQOo05w.js +1 -0
- package/dist/web/static/assets/lua-VAEuO923.js +1 -0
- package/dist/web/static/assets/mathematica-DTrFuWx2.js +1 -0
- package/dist/web/static/assets/mbox-CNhZ1qSd.js +1 -0
- package/dist/web/static/assets/mirc-CjQqDB4T.js +1 -0
- package/dist/web/static/assets/mllike-CXdrOF99.js +1 -0
- package/dist/web/static/assets/modelica-Dc1JOy9r.js +1 -0
- package/dist/web/static/assets/mscgen-BA5vi2Kp.js +1 -0
- package/dist/web/static/assets/mumps-BT43cFF4.js +1 -0
- package/dist/web/static/assets/nginx-DdIZxoE0.js +1 -0
- package/dist/web/static/assets/nsis-LdVXkNf5.js +1 -0
- package/dist/web/static/assets/ntriples-BfvgReVJ.js +1 -0
- package/dist/web/static/assets/octave-Ck1zUtKM.js +1 -0
- package/dist/web/static/assets/oz-BzwKVEFT.js +1 -0
- package/dist/web/static/assets/pascal--L3eBynH.js +1 -0
- package/dist/web/static/assets/perl-CdXCOZ3F.js +1 -0
- package/dist/web/static/assets/pig-CevX1Tat.js +1 -0
- package/dist/web/static/assets/powershell-CFHJl5sT.js +1 -0
- package/dist/web/static/assets/properties-C78fOPTZ.js +1 -0
- package/dist/web/static/assets/protobuf-ChK-085T.js +1 -0
- package/dist/web/static/assets/pug-DeIclll2.js +1 -0
- package/dist/web/static/assets/puppet-DMA9R1ak.js +1 -0
- package/dist/web/static/assets/python-BuPzkPfP.js +1 -0
- package/dist/web/static/assets/q-pXgVlZs6.js +1 -0
- package/dist/web/static/assets/r-B6wPVr8A.js +1 -0
- package/dist/web/static/assets/rpm-CTu-6PCP.js +1 -0
- package/dist/web/static/assets/ruby-B2Rjki9n.js +1 -0
- package/dist/web/static/assets/sas-B4kiWyti.js +1 -0
- package/dist/web/static/assets/scheme-C41bIUwD.js +1 -0
- package/dist/web/static/assets/shell-CjFT_Tl9.js +1 -0
- package/dist/web/static/assets/sieve-C3Gn_uJK.js +1 -0
- package/dist/web/static/assets/simple-mode-GW_nhZxv.js +1 -0
- package/dist/web/static/assets/smalltalk-CnHTOXQT.js +1 -0
- package/dist/web/static/assets/solr-DehyRSwq.js +1 -0
- package/dist/web/static/assets/sparql-DkYu6x3z.js +1 -0
- package/dist/web/static/assets/spreadsheet-BCZA_wO0.js +1 -0
- package/dist/web/static/assets/sql-D0XecflT.js +1 -0
- package/dist/web/static/assets/stex-C3f8Ysf7.js +1 -0
- package/dist/web/static/assets/stylus-B533Al4x.js +1 -0
- package/dist/web/static/assets/swift-BzpIVaGY.js +1 -0
- package/dist/web/static/assets/tcl-DVfN8rqt.js +1 -0
- package/dist/web/static/assets/textile-CnDTJFAw.js +1 -0
- package/dist/web/static/assets/tiddlywiki-DO-Gjzrf.js +1 -0
- package/dist/web/static/assets/tiki-DGYXhP31.js +1 -0
- package/dist/web/static/assets/toml-Bm5Em-hy.js +1 -0
- package/dist/web/static/assets/troff-wAsdV37c.js +1 -0
- package/dist/web/static/assets/ttcn-CfJYG6tj.js +1 -0
- package/dist/web/static/assets/ttcn-cfg-B9xdYoR4.js +1 -0
- package/dist/web/static/assets/turtle-B1tBg_DP.js +1 -0
- package/dist/web/static/assets/vb-CmGdzxic.js +1 -0
- package/dist/web/static/assets/vbscript-BuJXcnF6.js +1 -0
- package/dist/web/static/assets/velocity-D8B20fx6.js +1 -0
- package/dist/web/static/assets/verilog-C6RDOZhf.js +1 -0
- package/dist/web/static/assets/vhdl-lSbBsy5d.js +1 -0
- package/dist/web/static/assets/webidl-ZXfAyPTL.js +1 -0
- package/dist/web/static/assets/xquery-DzFWVndE.js +1 -0
- package/dist/web/static/assets/yacas-BJ4BC0dw.js +1 -0
- package/dist/web/static/assets/z80-Hz9HOZM7.js +1 -0
- package/dist/web/static/index.html +22 -0
- package/dist/web/static/walnut-icon.png +0 -0
- package/dist/workers/git-compaction-worker.js +898 -0
- package/dist/workers/qmd-index-worker.js +97162 -0
- package/package.json +143 -0
- package/patches/@anthropic-ai+claude-agent-sdk+0.2.50.patch +13 -0
- package/scripts/patch-qmd.mjs +171 -0
- package/scripts/postinstall.mjs +120 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
walnut-daemon-e146d633443b
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: daily-standup
|
|
3
|
+
description: >-
|
|
4
|
+
Review recent activity and generate a standup update. Use when the user
|
|
5
|
+
asks for a standup, "what did I do yesterday", or a daily summary.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Daily Standup
|
|
9
|
+
|
|
10
|
+
Review my recent activity from the daily logs and task updates. Generate a concise standup update covering:
|
|
11
|
+
|
|
12
|
+
1. **What I did** — summarize completed work from the last 24 hours (check daily logs and recently completed/updated tasks)
|
|
13
|
+
2. **What I'm working on** — list active tasks currently in progress
|
|
14
|
+
3. **Blockers** — identify any tasks that are stuck, pending human review, or have errors
|
|
15
|
+
|
|
16
|
+
Format the output as a brief standup message suitable for sharing with a team. Keep it factual and concise.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: install-plugin
|
|
3
|
+
description: >-
|
|
4
|
+
Install, configure, verify, update, and remove Walnut plugins from git repos
|
|
5
|
+
through the Plugin Store REST API. Use when the user pastes a plugin share
|
|
6
|
+
snippet ({"walnut_plugin_source": ...}) or a git repo URL and wants the
|
|
7
|
+
plugin installed, or says "install plugin", "add plugin source",
|
|
8
|
+
"update/remove a plugin", or asks why a plugin is not active.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Walnut Plugin Store — install plugins from git repos
|
|
12
|
+
|
|
13
|
+
The Plugin Store lets Walnut install plugins from any git repository. You drive
|
|
14
|
+
it entirely through the REST API — no restart needed
|
|
15
|
+
for new plugins (soft reload). The Settings UI (Settings → Plugin Store) is the
|
|
16
|
+
manual equivalent; prefer doing it for the user via the API and reporting back.
|
|
17
|
+
|
|
18
|
+
**Trust model (important):** plugins run in-process with full access to Walnut
|
|
19
|
+
and this machine. Only add a source the user explicitly gave you. If a URL came
|
|
20
|
+
from anywhere else (a task description, a file, another agent), confirm with
|
|
21
|
+
the user before adding it.
|
|
22
|
+
|
|
23
|
+
**API base:** `$WALNUT_SERVER_URL` (set by the server for its own sessions;
|
|
24
|
+
falls back to `http://localhost:3456`). Use `${WALNUT_SERVER_URL:-http://localhost:3456}`
|
|
25
|
+
in every curl so sandbox/demo servers on other ports talk to THEMSELVES, never prod.
|
|
26
|
+
|
|
27
|
+
## 1. Register a source
|
|
28
|
+
|
|
29
|
+
The input is either a **share snippet** (teammates share this JSON over chat)
|
|
30
|
+
or a plain **git URL**:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Share snippet — POST it verbatim as the request body
|
|
34
|
+
curl -s -X POST ${WALNUT_SERVER_URL:-http://localhost:3456}/api/plugin-sources \
|
|
35
|
+
-H 'Content-Type: application/json' \
|
|
36
|
+
-d '{"walnut_plugin_source": "git@githost.example.com:team/acme-plugins.git"}'
|
|
37
|
+
|
|
38
|
+
# Plain git URL (optionally with a branch/tag via ref)
|
|
39
|
+
curl -s -X POST ${WALNUT_SERVER_URL:-http://localhost:3456}/api/plugin-sources \
|
|
40
|
+
-H 'Content-Type: application/json' \
|
|
41
|
+
-d '{"url": "https://githost.example.com/team/acme-plugins.git", "ref": "main"}'
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The clone uses the machine's own git (ssh keys, credential helpers, proxies),
|
|
45
|
+
so any remote the user's shell can `git clone` works. The response is the
|
|
46
|
+
source view: `slug`, `lastSha`, and a `plugins` array where each entry has a
|
|
47
|
+
`status` — that status tells you what to do next (section 2).
|
|
48
|
+
|
|
49
|
+
Errors worth translating for the user: `Invalid git URL` (shell characters or
|
|
50
|
+
unsupported scheme), `already added` (source exists — use update instead),
|
|
51
|
+
`git clone failed: ...` (auth/network — show the git error, it names the cause).
|
|
52
|
+
|
|
53
|
+
## 2. Read plugin status and act on it
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
curl -s ${WALNUT_SERVER_URL:-http://localhost:3456}/api/plugin-sources | jq .
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
| status | Meaning | What to do |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| `loaded` | Active now (soft reload already ran) | Nothing — report success |
|
|
62
|
+
| `needs-config` | Valid, but required config fields are missing | Section 3 |
|
|
63
|
+
| `unsupported` | Needs a newer Walnut (unknown capabilities) | Tell the user to update Walnut |
|
|
64
|
+
| `duplicate` | Same plugin id already loaded from elsewhere | Point at the existing one; remove one of them |
|
|
65
|
+
| `error` | Broken manifest.json | Show the `error` field to the user |
|
|
66
|
+
| `pending-restart` | Code on disk changed after load | Restart Walnut to pick it up |
|
|
67
|
+
|
|
68
|
+
## 3. Configure a needs-config plugin
|
|
69
|
+
|
|
70
|
+
Field definitions (labels, help text with links, defaults, which are required)
|
|
71
|
+
come from the plugin's manifest:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
curl -s ${WALNUT_SERVER_URL:-http://localhost:3456}/api/integrations/settings | jq '.[] | select(.id=="<plugin-id>")'
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`missing` lists the required keys that are empty; `uiHints.<key>.help` tells
|
|
78
|
+
the user exactly where to find each value — quote it when asking them.
|
|
79
|
+
|
|
80
|
+
Save values with a config update. **`PUT /api/config` replaces the whole
|
|
81
|
+
`plugins` key, so read-merge-write** — never send just the one plugin:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# 1. Current plugins map
|
|
85
|
+
current=$(curl -s ${WALNUT_SERVER_URL:-http://localhost:3456}/api/config | jq '.config.plugins // {}')
|
|
86
|
+
# 2. Merge in the new plugin's values (enabled: true activates it)
|
|
87
|
+
merged=$(echo "$current" | jq '. + {"<plugin-id>": {"enabled": true, "<key>": "<value>"}}')
|
|
88
|
+
# 3. Write back
|
|
89
|
+
curl -s -X PUT ${WALNUT_SERVER_URL:-http://localhost:3456}/api/config \
|
|
90
|
+
-H 'Content-Type: application/json' \
|
|
91
|
+
-d "{\"plugins\": $merged}"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Saving triggers a soft reload automatically. Verify: re-run the section 2 GET
|
|
95
|
+
and confirm the plugin is now `loaded`. If it stays `needs-config`, re-read
|
|
96
|
+
`missing` — a required value is still empty or wrong.
|
|
97
|
+
|
|
98
|
+
## 4. Update / remove / share
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
curl -s -X POST ${WALNUT_SERVER_URL:-http://localhost:3456}/api/plugin-sources/<slug>/update # git pull + soft reload
|
|
102
|
+
curl -s -X POST ${WALNUT_SERVER_URL:-http://localhost:3456}/api/plugin-sources/<slug>/check # commits behind, no change
|
|
103
|
+
curl -s -X DELETE ${WALNUT_SERVER_URL:-http://localhost:3456}/api/plugin-sources/<slug> # remove source + clone
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Update/remove responses carry `restartRequired: true` when already-loaded
|
|
107
|
+
plugin code changed or was removed — the in-memory version keeps running until
|
|
108
|
+
a restart. Tell the user; do not restart the server yourself.
|
|
109
|
+
|
|
110
|
+
To share a source with a teammate, use the `shareSnippet` field from the
|
|
111
|
+
section 2 GET (absent for URLs with embedded credentials — never share those).
|
|
112
|
+
|
|
113
|
+
## 5. Report back
|
|
114
|
+
|
|
115
|
+
Summarize honestly: which plugins were found, which are active, which need
|
|
116
|
+
what config (with the uiHints help text), and any restart needed. If nothing
|
|
117
|
+
was found in the repo: it must have a `manifest.json` at the root or in
|
|
118
|
+
top-level subdirectories — say so.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: learn
|
|
3
|
+
description: >-
|
|
4
|
+
Distill a workflow, doc, or conversation into a reusable skill. Use when
|
|
5
|
+
the user says "learn this", "save this as a skill", or wants a procedure
|
|
6
|
+
captured for reuse.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Learn a Reusable Skill
|
|
10
|
+
|
|
11
|
+
The user wants you to learn a reusable skill from their request and save it.
|
|
12
|
+
|
|
13
|
+
The request is open-ended and may mix two kinds of content, in any order: SOURCES to gather (directories, file paths, URLs, "what we just did", pasted notes) AND REQUIREMENTS that shape the skill (what to focus on, what to leave out, scope, naming). Treat EVERY part of the request as load-bearing — prose after a path or link is the user telling you what they want from that source. Never gather the first source and ignore the rest. If no request text was given, distill the workflow from THIS conversation: review the steps taken and capture them.
|
|
14
|
+
|
|
15
|
+
Do this:
|
|
16
|
+
1. Gather every source the user named, using the tools you already have — `file_read` for local files, `web_fetch` for URLs, the current conversation history if they referred to something you just did, and pasted text as-is. For codebase exploration, delegate to a session. If scope is ambiguous, make a reasonable choice and note it; do not stall.
|
|
17
|
+
2. Apply every requirement/focus/constraint in the request to the skill you author — these govern what the SKILL.md covers, not just which sources you read.
|
|
18
|
+
3. Confirm the skill name + category + one-line description with the user (creating a NEW skill always requires confirmation), then author ONE skill via `skill_manage` (action="create").
|
|
19
|
+
|
|
20
|
+
Follow these authoring standards exactly (HARDLINE — same rules a maintainer enforces in review):
|
|
21
|
+
|
|
22
|
+
**Frontmatter (passed as skill_manage params):**
|
|
23
|
+
- name: lowercase-hyphenated, ≤64 chars, no spaces.
|
|
24
|
+
- description: ONE sentence, **≤60 characters**, ends with a period. State the capability, not the implementation. No marketing words (powerful, comprehensive, seamless, robust). Do NOT repeat the skill name. This rule is NOT cosmetic: the skill index injects the description into every session and routing depends on it — anything vague or over-long breaks routing. COUNT the characters before saving; skill_manage hard-rejects >60.
|
|
25
|
+
- Good (≤60): `Search arXiv papers by keyword, author, or ID.`
|
|
26
|
+
- Bad: `A comprehensive skill that lets the agent search arXiv for academic papers using keywords and authors.`
|
|
27
|
+
- type: `action` for a procedure/how-to; `knowledge` for curated stable facts about a topic.
|
|
28
|
+
- category: pick a sensible grouping (finance, career, projects, walnut, ...). Reuse an existing category when one fits.
|
|
29
|
+
|
|
30
|
+
**Body — action skills use this section order (omit a section only if genuinely empty):**
|
|
31
|
+
1. `# <Human Title>` + 2-3 sentence intro: what it does, what it does NOT do, key dependency stance.
|
|
32
|
+
2. `## When to Use` — bullet list of concrete trigger phrases.
|
|
33
|
+
3. `## Prerequisites` — exact env vars, install steps, credentials.
|
|
34
|
+
4. `## Procedure` — numbered steps with copy-paste-exact commands.
|
|
35
|
+
5. `## Pitfalls` — known limits, things that look broken but aren't.
|
|
36
|
+
6. `## Verification` — a single check that proves it worked.
|
|
37
|
+
|
|
38
|
+
**Body — knowledge skills:** freeform but concise (~100 lines): stable facts, decisions and their WHY, key references. No episodic events ("last Tuesday we...") — those belong in the daily log.
|
|
39
|
+
|
|
40
|
+
**Quality bar:**
|
|
41
|
+
- Prefer exact commands, URLs, signatures, and config keys that appear VERBATIM in the source. NEVER invent flags, paths, or APIs — if you didn't see it in the source, don't write it.
|
|
42
|
+
- Keep it tight and scannable: ~100 lines simple, ~200 complex. Don't re-paste source docs.
|
|
43
|
+
- Don't write a router/index skill that only points at other skills.
|
|
44
|
+
- When updating later: replace outdated facts in place — never append duplicates.
|
|
45
|
+
|
|
46
|
+
When done, tell the user the skill name, its category, and a one-line summary of what it captured.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: register-repo
|
|
3
|
+
description: >-
|
|
4
|
+
Register a repository — delegate a Claude Code session to explore the
|
|
5
|
+
codebase and create a structured profile. Use when the user says
|
|
6
|
+
"register repo", "add this repo", or wants a repository profile created.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Register Repository (delegate)
|
|
10
|
+
|
|
11
|
+
The user wants to register a repository. Delegate to a Claude Code session — the session does the exploration, not you.
|
|
12
|
+
|
|
13
|
+
1. **Figure out the path** — from user input, task context, or ask if unclear.
|
|
14
|
+
2. **Find the session-side skill** — look in `<available_skills>` for `walnut-register-repo` and note its `<location>` path.
|
|
15
|
+
3. **Start a session** with:
|
|
16
|
+
- `working_directory`: the target repo path
|
|
17
|
+
- `prompt`: "Read the skill at {location} and follow its instructions to explore this codebase and register it as a repository." Include any extra context the user provided.
|
|
18
|
+
4. **After session completes** — verify with `file_read source='repos/{slug}'`.
|
|
19
|
+
|
|
20
|
+
Do NOT investigate the codebase yourself. Do NOT ask the user a bunch of questions. The session + skill handles everything.
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: setup-cloud-companion
|
|
3
|
+
description: >-
|
|
4
|
+
Set up the self-hosted cloud companion — provision a small always-on VM, wire
|
|
5
|
+
data sync to it, and get the user's phone working off Wi-Fi. Use when the user
|
|
6
|
+
says "set up my cloud companion", "I want to reach Walnut from my phone
|
|
7
|
+
anywhere / away from home", "set up cloud sync", "deploy the cloud box", or
|
|
8
|
+
asks why their phone only works on the home network.
|
|
9
|
+
type: action
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Set up the cloud companion
|
|
13
|
+
|
|
14
|
+
The cloud companion is a small always-on VM the user owns. It receives the Mac's
|
|
15
|
+
data repo over git-smart-HTTP, serves the phone over HTTPS from anywhere, and
|
|
16
|
+
relays live session traffic. Setting one up means: provision a VM → point a
|
|
17
|
+
hostname at it → wait for first boot → claim it → verify sync.
|
|
18
|
+
|
|
19
|
+
You do **not** re-implement that sequence. A resumable job on this machine owns
|
|
20
|
+
it, and you drive that job over REST — the same endpoints the Settings →
|
|
21
|
+
Cloud Companion wizard uses. The job survives your session ending, a server
|
|
22
|
+
restart, and a long poll; your role is to explain, collect choices, answer the
|
|
23
|
+
job's questions, and narrate progress.
|
|
24
|
+
|
|
25
|
+
**API base:** `$WALNUT_SERVER_URL` (set by the server for its own sessions;
|
|
26
|
+
falls back to `http://localhost:3456`). Use
|
|
27
|
+
`${WALNUT_SERVER_URL:-http://localhost:3456}` in every curl so a sandbox or demo
|
|
28
|
+
server on another port talks to ITSELF, never to the user's real server.
|
|
29
|
+
|
|
30
|
+
## 0. Money and consent — before anything billable
|
|
31
|
+
|
|
32
|
+
Provisioning creates **real, recurring, billed cloud resources in the user's own
|
|
33
|
+
account**. Say so plainly, in this order, and get an explicit yes before you
|
|
34
|
+
POST anything:
|
|
35
|
+
|
|
36
|
+
1. What gets created: one small VM with a public IP, an encrypted disk that is
|
|
37
|
+
*kept* if the VM is terminated, daily disk snapshots, and (on AWS) a
|
|
38
|
+
CloudFormation stack that owns them.
|
|
39
|
+
2. What it costs per month — read the real number from `costHint` in step 1
|
|
40
|
+
below, do not quote a figure from memory.
|
|
41
|
+
3. That the bill runs until they tear it down, and that teardown is manual
|
|
42
|
+
(section 9).
|
|
43
|
+
|
|
44
|
+
Then ask for confirmation in one question ("Ready for me to create this?") and
|
|
45
|
+
**wait for a real yes.** If the user is only asking how it works, answer and
|
|
46
|
+
stop — do not start a job.
|
|
47
|
+
|
|
48
|
+
Two hard rules:
|
|
49
|
+
|
|
50
|
+
- **Never invent credentials, tokens, domains, or hostnames.** The user supplies
|
|
51
|
+
the domain and any provider API token. If something is missing, ask.
|
|
52
|
+
- **Never echo a secret back.** Provider tokens go into one POST body and
|
|
53
|
+
nowhere else — not into your reply, not into a file, not into a task note.
|
|
54
|
+
The pairing code that authorizes the claim is generated server-side and
|
|
55
|
+
redacted from every response you can see (`redactCloudSetupJob`), so you will
|
|
56
|
+
never hold it; the one place it appears is the boot-script blob in section 7.
|
|
57
|
+
|
|
58
|
+
## 1. Check what this machine can provision
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
curl -s ${WALNUT_SERVER_URL:-http://localhost:3456}/api/cloud-setup/providers
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Returns `{ providers: [{ id, label, costHint, canProvision, detect }] }`:
|
|
65
|
+
|
|
66
|
+
| Field | Meaning for the user |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `label` | Human name, e.g. "AWS (EC2 + CDK)" |
|
|
69
|
+
| `costHint` | Rough monthly cost — quote this verbatim in section 0 |
|
|
70
|
+
| `canProvision` | `true` = Walnut creates the VM itself. `false` = manual path (section 7) |
|
|
71
|
+
| `detect.available` | `true` = credentials are already usable, nothing to collect |
|
|
72
|
+
| `detect.detail` | One sentence to relay as-is — it says exactly what is missing |
|
|
73
|
+
| `detect.needs` | `nothing` / `cli-login` (user fixes their own CLI) / `api-token` (you collect it) |
|
|
74
|
+
|
|
75
|
+
Report readiness in plain language, e.g. "AWS is ready (credentials found), and
|
|
76
|
+
'Any VM (paste a script)' always works if you'd rather use a host you already
|
|
77
|
+
have." If a provider is `available: false` with `needs: 'cli-login'`, relay
|
|
78
|
+
`detail` — that is the fix — and do not try to log them in yourself.
|
|
79
|
+
|
|
80
|
+
Do not read `needs: 'cli-login'` as "this provider is unavailable". Azure and
|
|
81
|
+
Google Cloud provision through the user's own `az` / `gcloud` login, so they
|
|
82
|
+
report `cli-login` until that CLI is installed and signed in — and they become
|
|
83
|
+
one-click the moment it is. Relay the one-line fix from `detail`, and offer the
|
|
84
|
+
manual path as the alternative rather than the only option.
|
|
85
|
+
|
|
86
|
+
## 2. Collect the two choices
|
|
87
|
+
|
|
88
|
+
**Provider** — one of the `id` values from step 1.
|
|
89
|
+
|
|
90
|
+
**Domain mode** — this is the choice users get wrong, so present the trade-off:
|
|
91
|
+
|
|
92
|
+
- `own-domain` — they own a hostname (e.g. `wn.example.com`) and add one DNS
|
|
93
|
+
A record. **Recommended for anything long-term**: the address is theirs, it
|
|
94
|
+
survives rebuilds, and certificate issuance is not shared with strangers.
|
|
95
|
+
Requires access to their DNS registrar during setup.
|
|
96
|
+
- `sslip` — free automatic address derived from the VM's IP
|
|
97
|
+
(`<dashed-ip>.sslip.io`). **Fastest start**, zero DNS work. Caveat worth
|
|
98
|
+
stating: `sslip.io` shares one certificate rate-limit bucket across all its
|
|
99
|
+
users, so issuance can occasionally be slow or throttled, and the address
|
|
100
|
+
changes if the IP ever changes.
|
|
101
|
+
|
|
102
|
+
If they pick `own-domain`, get the exact hostname. Optionally collect `region`
|
|
103
|
+
and `instanceType` if they care; otherwise omit them and let the defaults win.
|
|
104
|
+
|
|
105
|
+
## 3. Start the job
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
curl -s -X POST ${WALNUT_SERVER_URL:-http://localhost:3456}/api/cloud-setup/start \
|
|
109
|
+
-H 'Content-Type: application/json' \
|
|
110
|
+
-d '{"provider":"aws","domainMode":"own-domain","domain":"wn.example.com"}'
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Body fields: `provider` (required), `domainMode` (required, `own-domain` or
|
|
114
|
+
`sslip`), `domain` (required when `own-domain`), `region`, `instanceType`,
|
|
115
|
+
`credentials` (only when step 1 said `needs: 'api-token'`), `force`.
|
|
116
|
+
|
|
117
|
+
- **202** `{ job }` — started. Go to section 4.
|
|
118
|
+
- **400** `{ error }` — bad input, or preflight refused. The most common one:
|
|
119
|
+
cloud sync is **already configured on this machine**. Do not pass `force`
|
|
120
|
+
reflexively — tell the user they already have a companion wired up, and only
|
|
121
|
+
start over (with `"force": true`) if they explicitly say they want to replace
|
|
122
|
+
it.
|
|
123
|
+
- **409** `{ error }` — a job is already in flight. Fetch it and describe it
|
|
124
|
+
before doing anything:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
curl -s ${WALNUT_SERVER_URL:-http://localhost:3456}/api/cloud-setup/job
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Then act on that job's `status`: `awaiting-input` → answer it (section 6);
|
|
131
|
+
`running` → just resume narrating (section 5); `failed` → section 8;
|
|
132
|
+
`done` → it already finished (section 9). Offer
|
|
133
|
+
`POST /api/cloud-setup/job/cancel` if the user wants to abandon it, and
|
|
134
|
+
`DELETE /api/cloud-setup/job` to clear a finished/failed/cancelled record
|
|
135
|
+
(409 while one is still in flight — cancel first).
|
|
136
|
+
|
|
137
|
+
## 4. Read the job shape once
|
|
138
|
+
|
|
139
|
+
Every job response is `{ job: {...} }` with:
|
|
140
|
+
|
|
141
|
+
- `status` — `running` | `awaiting-input` | `failed` | `done` | `cancelled`
|
|
142
|
+
- `currentStep` — where it is now
|
|
143
|
+
- `steps` — every step id → `{ status: pending|running|done|error|skipped }`
|
|
144
|
+
- `awaitingInput` — `{ kind, prompt }` when blocked (section 6)
|
|
145
|
+
- `logTail` — recent operator-visible lines (ring-capped)
|
|
146
|
+
- `domain`, `ip`, `error`
|
|
147
|
+
|
|
148
|
+
Step order, with what to tell the user each means:
|
|
149
|
+
|
|
150
|
+
| Step | Plain language | Rough ETA |
|
|
151
|
+
|---|---|---|
|
|
152
|
+
| `preflight` | checking credentials and that sync isn't already set up | seconds |
|
|
153
|
+
| `generate` | writing the first-boot script | seconds |
|
|
154
|
+
| `provision` | creating the VM | ~5 min (AWS: 3-6 min, longer on a first-ever deploy that installs CDK deps) |
|
|
155
|
+
| `await-vm` | waiting for the IP of a VM you created | manual path only |
|
|
156
|
+
| `dns` | waiting for the A record to point at the VM | minutes to ~30 min; skipped in sslip mode |
|
|
157
|
+
| `await-server` | first boot: clone, build, certificate | ~10 min (budget 20 min) |
|
|
158
|
+
| `claim-and-wire` | claiming the box and configuring the git remote | seconds |
|
|
159
|
+
| `verify-sync` | first push + independent auth check | seconds |
|
|
160
|
+
| `done` | ready | — |
|
|
161
|
+
|
|
162
|
+
A `skipped` step is normal, not a failure (`dns` in sslip mode, `await-vm`
|
|
163
|
+
whenever Walnut provisioned the VM itself).
|
|
164
|
+
|
|
165
|
+
## 5. Poll — one short command per poll
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
curl -s ${WALNUT_SERVER_URL:-http://localhost:3456}/api/cloud-setup/job
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Each poll must be its own separate short command, roughly 30s apart.** Do
|
|
172
|
+
not write a `sleep`/`while` loop inside a single call: `shell_exec` is capped at
|
|
173
|
+
600s (default 120s), so a loop that waits out a 10-minute first boot gets killed
|
|
174
|
+
mid-wait and you lose the thread — while the job itself keeps running fine.
|
|
175
|
+
Polling is cheap and the job is authoritative, so a dropped poll costs nothing.
|
|
176
|
+
|
|
177
|
+
Between polls, narrate transitions in the user's language, not step ids: "the VM
|
|
178
|
+
is up, now waiting for it to finish building Walnut — usually about ten
|
|
179
|
+
minutes." Only surface `logTail` lines when something is slow or wrong; a
|
|
180
|
+
healthy provision does not need its CDK output pasted at the user.
|
|
181
|
+
|
|
182
|
+
If `GET /job` returns **404**, no job exists — it was deleted, or you never
|
|
183
|
+
started one.
|
|
184
|
+
|
|
185
|
+
## 6. Answer the job's questions (`status: awaiting-input`)
|
|
186
|
+
|
|
187
|
+
`awaitingInput.kind` tells you what to ask. Relay `awaitingInput.prompt` too —
|
|
188
|
+
it is written for the user. All three answers go to the same endpoint:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
curl -s -X POST ${WALNUT_SERVER_URL:-http://localhost:3456}/api/cloud-setup/job/input \
|
|
192
|
+
-H 'Content-Type: application/json' -d '<one of the bodies below>'
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**`dns-confirm`** — the A record still doesn't resolve to the VM's IP after the
|
|
196
|
+
patience window. Show the user the exact record to create, built from the job's
|
|
197
|
+
own fields:
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
Type: A Name: <job.domain> Value: <job.ip> Proxy/CDN: OFF (DNS-only)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
The CDN-proxy warning matters: an orange-cloud/proxied record breaks both the
|
|
204
|
+
certificate challenge and TLS termination. Then tell them Walnut **keeps
|
|
205
|
+
checking in the background**, so if they add the record now it will continue on
|
|
206
|
+
its own with no further action. Only POST the override once they confirm they
|
|
207
|
+
either added it or want to proceed regardless:
|
|
208
|
+
|
|
209
|
+
```json
|
|
210
|
+
{"confirmDnsSkip": true}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
(Anything else on a `dns-confirm` job is rejected — the field name is exact.)
|
|
214
|
+
|
|
215
|
+
**`vm-ip`** — manual path: ask for the VM's public IPv4 address and post it.
|
|
216
|
+
Must be a plain dotted IPv4; a hostname is rejected.
|
|
217
|
+
|
|
218
|
+
```json
|
|
219
|
+
{"ip": "203.0.113.10"}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
In sslip mode the hostname is derived from this IP automatically — nothing else
|
|
223
|
+
to collect.
|
|
224
|
+
|
|
225
|
+
**`credentials`** — the provider needs an API token (also happens after a server
|
|
226
|
+
restart mid-setup, since tokens are held in memory only and never persisted).
|
|
227
|
+
Ask the user to paste it, post it, and then **forget it**: do not repeat it, do
|
|
228
|
+
not save it anywhere, do not put it in a summary.
|
|
229
|
+
|
|
230
|
+
```json
|
|
231
|
+
{"credentials": "<token the user pasted>"}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Each of these returns **200** `{ job }` and the job continues. **409** means the
|
|
235
|
+
job is not actually waiting for input (someone else answered, or it moved on) —
|
|
236
|
+
re-read `GET /job` before trying again. **400** means the value was rejected
|
|
237
|
+
(bad IPv4, missing field); relay the `error` and ask again.
|
|
238
|
+
|
|
239
|
+
## 7. Manual path — the user brings their own VM
|
|
240
|
+
|
|
241
|
+
When `canProvision` is `false` (provider `manual`), or the user prefers a host
|
|
242
|
+
they already have: **start the job first** (section 3), then fetch the boot
|
|
243
|
+
script, which needs the job's pairing code to exist.
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
curl -s "${WALNUT_SERVER_URL:-http://localhost:3456}/api/cloud-setup/user-data?provider=manual"
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Optional query params `domainMode` and `domain` default to the job's own values,
|
|
250
|
+
so normally you pass neither. Returns `{ userData, steps, consoleUrl }`.
|
|
251
|
+
**409** means no job with a pairing code exists yet — start the job first.
|
|
252
|
+
|
|
253
|
+
Print `steps` as the checklist and `userData` verbatim in a fenced code block,
|
|
254
|
+
telling the user to paste it as the VM's cloud-init / user-data (or run it as
|
|
255
|
+
root on the box). This blob is the one thing you handle that contains the
|
|
256
|
+
pairing code by design — it has to, that is what lets the box be claimed. Say
|
|
257
|
+
that plainly: it is a one-time secret, so it belongs in the VM's user-data
|
|
258
|
+
field, not in a chat, a gist, or a ticket.
|
|
259
|
+
|
|
260
|
+
Then continue from `await-vm`: the job will ask for the VM's public IP
|
|
261
|
+
(section 6, `vm-ip`), and from there polling is identical to section 5.
|
|
262
|
+
|
|
263
|
+
## 8. When it fails (`status: failed`)
|
|
264
|
+
|
|
265
|
+
Show two things: `job.error` (the actual cause) and the last few `job.logTail`
|
|
266
|
+
lines (the evidence). Do not paraphrase the error into something vaguer.
|
|
267
|
+
|
|
268
|
+
Common causes worth translating: a provider CLI that isn't signed in; a
|
|
269
|
+
CloudFormation/quota rejection (the log names it); the box not answering within
|
|
270
|
+
its 20-minute budget — for which the log itself points at
|
|
271
|
+
`/var/log/walnut-setup.log` on the VM; or a box that is already claimed by
|
|
272
|
+
another device, which needs its auth wiped or a redeploy before the code works.
|
|
273
|
+
|
|
274
|
+
Retry re-runs from the failed step and keeps everything already done:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
curl -s -X POST ${WALNUT_SERVER_URL:-http://localhost:3456}/api/cloud-setup/job/retry
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**200** `{ job }` → resume polling. **409** means it can't be retried right now
|
|
281
|
+
(already running, or already finished). If the user wants to stop instead, use
|
|
282
|
+
`POST /api/cloud-setup/job/cancel` (**200**, or **404** if no job exists).
|
|
283
|
+
|
|
284
|
+
Retry is safe to offer for infrastructure hiccups — steps are idempotent and an
|
|
285
|
+
AWS re-deploy converges on the same stack rather than creating a second one. If
|
|
286
|
+
the same step fails twice for the same reason, stop retrying and fix the cause.
|
|
287
|
+
|
|
288
|
+
## 9. Done — and the one step you cannot do
|
|
289
|
+
|
|
290
|
+
At `status: done`, data sync is live: the Mac's data repo now pushes to the
|
|
291
|
+
companion, so tasks and notes appear there automatically. Tell the user the
|
|
292
|
+
address (`job.domain`).
|
|
293
|
+
|
|
294
|
+
**The last step is human-only: pairing the phone requires scanning a QR code.**
|
|
295
|
+
Point them at **Settings → Cloud Companion → "Connect your phone"**, which mints
|
|
296
|
+
a device credential and shows the QR for the Walnut iOS app. You cannot scan it
|
|
297
|
+
for them, and you should not try to mint a device token as a substitute — say
|
|
298
|
+
this clearly instead of implying setup is 100% complete.
|
|
299
|
+
|
|
300
|
+
Then, briefly:
|
|
301
|
+
|
|
302
|
+
- Costs are running now and are the user's, not Walnut's.
|
|
303
|
+
- **Teardown, if they ever want out: revoke the device tokens FIRST**
|
|
304
|
+
(Settings → Devices, or `DELETE /api/devices/<name>?target=cloud`), then
|
|
305
|
+
destroy the infrastructure — on AWS `cd infra && npx cdk destroy
|
|
306
|
+
WalnutCloudStack`; on Azure `az group delete -n walnut-cloud` (one command,
|
|
307
|
+
because everything Walnut made lives in that group); on Google Cloud
|
|
308
|
+
`gcloud compute instances delete walnut-cloud --zone <zone>` **plus**
|
|
309
|
+
`gcloud compute addresses delete walnut-cloud-ip --region <region>` — the
|
|
310
|
+
reserved address starts charging once it is no longer attached, so deleting
|
|
311
|
+
only the instance leaves a small ongoing cost; otherwise delete the VM in the
|
|
312
|
+
provider console. Order
|
|
313
|
+
matters: destroying the box first leaves live credentials pointing at an
|
|
314
|
+
address someone else may later be handed. Note the root volume is deliberately
|
|
315
|
+
retained on termination, so it may need deleting separately, and it holds
|
|
316
|
+
their data.
|
|
317
|
+
- Reference docs: `docs/reference/cloud-sync.md` (sync + troubleshooting) and
|
|
318
|
+
`infra/README.md` (what the stack contains).
|
|
319
|
+
|
|
320
|
+
## Reporting back
|
|
321
|
+
|
|
322
|
+
Be honest about state. Good report: which provider and address, which steps
|
|
323
|
+
completed, that sync is verified live, that phone pairing is still pending on
|
|
324
|
+
them, and the monthly cost they've taken on. Never claim "fully set up" while
|
|
325
|
+
the QR hasn't been scanned, and never report success from a `start` 202 alone —
|
|
326
|
+
202 means the job began, not that it worked.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: triage
|
|
3
|
+
description: >-
|
|
4
|
+
Triage and prioritize pending tasks. Use when the user asks to triage,
|
|
5
|
+
review priorities, clean up stale tasks, or decide what to work on next.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Task Triage
|
|
9
|
+
|
|
10
|
+
Review all my non-completed tasks and help me triage them. For each task, consider:
|
|
11
|
+
|
|
12
|
+
1. **Priority assessment** — is the current priority (immediate/important/backlog/none) correct?
|
|
13
|
+
2. **Staleness** — are there tasks that haven't been touched in a while and should be closed or deprioritized?
|
|
14
|
+
3. **Quick wins** — identify tasks that could be completed quickly
|
|
15
|
+
4. **Dependencies** — are any tasks blocked by other tasks?
|
|
16
|
+
|
|
17
|
+
Group the output by recommended action:
|
|
18
|
+
- **Do now** — high-impact tasks that should be tackled immediately
|
|
19
|
+
- **Schedule** — important but not urgent, suggest when to work on them
|
|
20
|
+
- **Delegate/Automate** — tasks that could be handled by a session
|
|
21
|
+
- **Drop/Archive** — tasks that are stale or no longer relevant
|
|
22
|
+
|
|
23
|
+
Be opinionated and direct. If something should be dropped, say so.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: walnut-calendar
|
|
3
|
+
description: >-
|
|
4
|
+
Read and edit the user's real calendars (macOS Calendar: iCloud, Google,
|
|
5
|
+
Exchange accounts) through Walnut's /api/calendar REST endpoints. Use when
|
|
6
|
+
asked about the user's schedule, meetings, or availability, or to create,
|
|
7
|
+
move, retime, or delete calendar events. Covers listing events in a date
|
|
8
|
+
range, enumerating calendars, and write-back that syncs to the cloud
|
|
9
|
+
provider via macOS.
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Walnut Calendar API
|
|
13
|
+
|
|
14
|
+
Walnut exposes the Mac's calendars (every account added in macOS System
|
|
15
|
+
Settings → Internet Accounts: iCloud, Google, Exchange, …) over REST. Edits
|
|
16
|
+
write back through macOS EventKit, so a change made here shows up in Google
|
|
17
|
+
Calendar / iCloud within seconds — no separate login or OAuth involved.
|
|
18
|
+
|
|
19
|
+
Base URL: `http://localhost:3456/api/calendar` (Walnut server).
|
|
20
|
+
|
|
21
|
+
## Date format contract (IMPORTANT)
|
|
22
|
+
|
|
23
|
+
All dates are **tz-less local ISO** — the server's local wall time, never a
|
|
24
|
+
`Z` or `+hh:mm` suffix:
|
|
25
|
+
|
|
26
|
+
- Timed: `2026-08-05T09:00:00`
|
|
27
|
+
- All-day / date-only: `2026-08-05`
|
|
28
|
+
|
|
29
|
+
## Read
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Events in a date range (inclusive days)
|
|
33
|
+
curl -s 'http://localhost:3456/api/calendar/events?from=2026-08-03&to=2026-08-09'
|
|
34
|
+
# → { "events": [ { "id", "title", "start", "end", "allDay", "calendarId",
|
|
35
|
+
# "calendarName", "accountName", "color", "location?", "readonly?" } ],
|
|
36
|
+
# "sources": [ { "id": "eventkit", "available", "enabled", "reason?", "message?" } ] }
|
|
37
|
+
|
|
38
|
+
# List the calendars themselves (to pick a create target)
|
|
39
|
+
curl -s 'http://localhost:3456/api/calendar/sources'
|
|
40
|
+
# → { "sources": [...], "calendars": [ { "id", "title", "account", "color",
|
|
41
|
+
# "readonly", "hidden" } ] }
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- If `sources[0].available` is `false`, read `reason`/`message` — e.g.
|
|
45
|
+
`permission-denied` means the user must grant Calendar access in System
|
|
46
|
+
Settings → Privacy & Security → Calendars. `GET /events` still returns
|
|
47
|
+
`{ events: [] }` in that case rather than erroring.
|
|
48
|
+
- Event ids of recurring occurrences look like `<baseId>#<epoch>`; treat the
|
|
49
|
+
whole string as opaque and **URL-encode it** in paths (`#` → `%23`).
|
|
50
|
+
|
|
51
|
+
## Write
|
|
52
|
+
|
|
53
|
+
Only calendars with `readonly: false` accept writes (a write to a read-only
|
|
54
|
+
calendar returns 409).
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Create (end defaults are NOT applied server-side — always send end)
|
|
58
|
+
curl -s -X POST http://localhost:3456/api/calendar/events \
|
|
59
|
+
-H 'Content-Type: application/json' \
|
|
60
|
+
-d '{"calendarId":"<id from /sources>","title":"Dentist",
|
|
61
|
+
"start":"2026-08-05T15:00:00","end":"2026-08-05T16:00:00"}'
|
|
62
|
+
# All-day: use date-only start/end (end inclusive) and "allDay": true.
|
|
63
|
+
|
|
64
|
+
# Move / retime / rename (start AND end required; PATCH only that occurrence
|
|
65
|
+
# for recurring events)
|
|
66
|
+
curl -s -X PATCH 'http://localhost:3456/api/calendar/events/<url-encoded id>' \
|
|
67
|
+
-H 'Content-Type: application/json' \
|
|
68
|
+
-d '{"start":"2026-08-06T15:00:00","end":"2026-08-06T16:00:00","title":"Dentist (moved)"}'
|
|
69
|
+
|
|
70
|
+
# Delete (only that occurrence for recurring events). Confirm with the user
|
|
71
|
+
# before deleting anything you did not just create.
|
|
72
|
+
curl -s -X DELETE 'http://localhost:3456/api/calendar/events/<url-encoded id>'
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Error codes
|
|
76
|
+
|
|
77
|
+
| HTTP | code | meaning |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| 400 | `usage` | bad params / date format |
|
|
80
|
+
| 403 | `permission-denied` | macOS Calendar access not granted (Full Access) |
|
|
81
|
+
| 404 | `not-found` | event id doesn't resolve |
|
|
82
|
+
| 409 | `readonly` | target calendar is read-only |
|
|
83
|
+
| 503 | `disabled` / `cloud` / `not-configured` | source off in Settings, cloud companion (no Mac calendars), or helper unavailable |
|
|
84
|
+
| 502 | other | EventKit helper failure — surface `message` to the user |
|
|
85
|
+
|
|
86
|
+
## Tips
|
|
87
|
+
|
|
88
|
+
- The Walnut web calendar (`/calendar`) live-updates after any write — no
|
|
89
|
+
refresh call needed on your side.
|
|
90
|
+
- To answer "what's on my schedule", query a generous range and filter by
|
|
91
|
+
`calendarName`/`accountName` yourself if the user means one account.
|
|
92
|
+
- Move = keep the duration: compute `new end = new start + (old end − old start)`.
|