feat(skills): /sleep-setup 5-phase wizard (click + 1 free-text URL)

This commit is contained in:
Parfii-bot 2026-04-22 01:34:42 +08:00
parent 7db2328b68
commit 8e9c05272f
6 changed files with 489 additions and 0 deletions

102
skills/sleep-setup/SKILL.md Normal file
View file

@ -0,0 +1,102 @@
---
name: sleep-setup
description: One-time wizard (RULE 0.15) that configures KeiSeiKit v0.11 cloud REM sync. Generates an SSH deploy key, initializes the user's memory-repo, writes env refs (RULE 0.8), and emits a ready-to-paste `/schedule create` command for nightly consolidation. Pure-click except the 1 free-text field for repo URL.
argument-hint: (no arguments)
---
# Sleep Setup — Cloud REM Sync Wizard (index)
You are running the one-time configuration wizard for the KeiSeiKit v0.11
sleep layer. Each session-end dump pushes to a private git repo; a cloud
Claude Code agent on `/schedule` clones the repo nightly, analyzes traces,
and commits a consolidation report back. In the morning the user runs
`git pull` and reads the report. Nothing in this pipeline blocks session
close, and report content is for the user to READ — never auto-injected.
This `SKILL.md` is the INDEX. Each phase lives in its own file and is
executed in order. Never skip a phase. Never re-order phases.
---
## Pipeline overview (5 phases, 5+ AskUserQuestion)
| Phase | File | Purpose | AskUserQuestion |
|---|---|---|---|
| 1 | [phase-1-repo-pick.md](phase-1-repo-pick.md) | Pick repo provider + visibility | 2 (click-only) |
| 2 | [phase-2-repo-url.md](phase-2-repo-url.md) | Collect SSH URL (1 free-text field) | 1 (AskUserQuestion `freeText`) |
| 3 | [phase-3-deploy-key.md](phase-3-deploy-key.md) | Run `kei-sleep-setup.sh`, show pubkey, confirm deploy-key added | 1 (click) |
| 4 | [phase-4-test-push.md](phase-4-test-push.md) | Dry-run a test commit via `kei-sleep-sync.sh` | 1 (click) |
| 5 | [phase-5-trigger.md](phase-5-trigger.md) | Render `/schedule create` command, offer to run now | 1 (click) |
**Minimum AskUserQuestion count: 6.** All clicks except the single repo-URL
free-text in Phase 2.
---
## Variables the pipeline produces
| Name | Set in | Meaning |
|---|---|---|
| `PROVIDER` | Phase 1 | `github` / `gitlab` / `bitbucket` / `self-hosted` |
| `VISIBILITY` | Phase 1 | `private` (recommended) / `public` (explicit user choice) |
| `REPO_URL` | Phase 2 | Validated SSH URL (`git@host:org/repo.git`) |
| `KEY_ADDED` | Phase 3 | boolean; was deploy key confirmed added? |
| `TEST_VERIFIED` | Phase 4 | boolean; did the user see the test commit in the remote? |
| `SCHEDULE_ACTION` | Phase 5 | `run-now` / `copy-later` / `skip` |
---
## Final report (emit after Phase 5)
```
=== SLEEP-SETUP REPORT ===
Provider: <PROVIDER> (visibility: <VISIBILITY>)
Repo URL: <REPO_URL>
Deploy key: ~/.ssh/keisei-memory-sync(.pub)
Sync repo path: ~/.claude/memory/sync-repo/
Env refs: ~/.claude/secrets/.env (KEI_MEMORY_REPO_URL, _PATH, _SSH_KEY)
Test push: <PASS/FAIL> (Phase 4)
Schedule: <SCHEDULE_ACTION>
```
If `SCHEDULE_ACTION == skip`, add:
```
Local-only mode. Traces will be pushed to the repo on every session end.
To enable nightly consolidation later: paste the prompt from Phase 5 into
`/schedule create` any time.
```
---
## Rules (apply throughout — enforced at every phase)
- **Pure-click contract.** Only Phase 2 asks for free text; every
decision is an `AskUserQuestion`. No `freeText` outside Phase 2.
- **Idempotent.** Re-running the wizard must NOT clobber existing
`~/.ssh/keisei-memory-sync` or `~/.claude/memory/sync-repo/`; the
helper script handles re-use.
- **NO DOWNGRADE (RULE -1).** If SSH auth fails, return 2-3 constructive
paths (re-check deploy key, check `sshd` host, fall back to HTTPS with
PAT — with warning) — never "cannot set up".
- **NO HALLUCINATION (RULE 0.4).** Never fabricate repo URLs, key
fingerprints, or commit hashes. Show the real output of the script.
- **RULE 0.8 secrets.** The wizard writes env-var REFERENCES to
`~/.claude/secrets/.env`, never inline tokens in any generated file.
- **RULE 0.1 private remotes.** Recommend private visibility in Phase 1.
If the user explicitly picks `public`, warn once: "a public memory
repo leaks your session prompts and tool usage — confirm?".
- **Silent failure (RULE 0.15).** Nothing in the session-end path may
block the session from closing. The wizard itself may fail loudly.
- **Constructor Pattern (RULE ZERO).** Every phase file < 100 LOC
(well under the 200-LOC file limit).
---
## References
- `~/.claude/rules/sleep-layer.md` — RULE 0.15 full text
- `~/.claude/rules/secrets-single-source.md` — RULE 0.8 enforcement
- `_primitives/kei-sleep-setup.sh` — the imperative setup helper
- `_primitives/kei-sleep-sync.sh` — the session-end-dump callback
- `_primitives/templates/sleep-trigger-prompt.md` — cloud agent prompt
- `hooks/session-end-dump.sh` — where `kei-sleep-sync.sh` is invoked

View file

@ -0,0 +1,71 @@
# Phase 1 — Repo provider + visibility
Ask the user to pick where the memory-repo lives. Purely click-based —
two `AskUserQuestion` batches, zero free text.
## 1a — Provider click
Emit ONE `AskUserQuestion`:
```json
{
"questions": [
{
"question": "Where does your memory-repo live?",
"header": "Provider",
"multiSelect": false,
"options": [
{"label": "GitHub", "description": "github.com — easiest; private repo recommended"},
{"label": "GitLab", "description": "gitlab.com or self-managed GitLab"},
{"label": "Bitbucket", "description": "bitbucket.org (Atlassian)"},
{"label": "Self-hosted", "description": "Forgejo / Gitea / custom; requires SSH access"}
]
}
]
}
```
Store the pick as `PROVIDER`.
## 1b — Visibility click
Emit ONE `AskUserQuestion`:
```json
{
"questions": [
{
"question": "Repo visibility — private is strongly recommended. Your traces contain prompts and tool calls.",
"header": "Visibility",
"multiSelect": false,
"options": [
{"label": "Private (recommended)", "description": "Only you + the deploy key can read"},
{"label": "Public (I accept the risk)", "description": "Traces visible to anyone — confirm below"}
]
}
]
}
```
Store the pick as `VISIBILITY`.
## 1c — Public-visibility warning
If `VISIBILITY == "Public (I accept the risk)"`, print the warning block
below to stdout BEFORE proceeding to Phase 2:
```
WARNING: a public memory repo leaks your session prompts, tool usage, and
file paths to anyone who finds the repo. This is rarely what you want.
If you proceed, the rest of the wizard will continue unchanged — there is
no second confirmation.
```
Do NOT emit a third AskUserQuestion for re-confirm — the user already
picked "I accept the risk".
## Verify-criterion
- `PROVIDER ∈ {GitHub, GitLab, Bitbucket, Self-hosted}`.
- `VISIBILITY ∈ {Private, Public}`.
- Exactly TWO `AskUserQuestion` calls were emitted in this phase.

View file

@ -0,0 +1,62 @@
# Phase 2 — Collect SSH repo URL
The one and only free-text field in the wizard. Everything else is a
click.
## 2a — Free-text prompt
Emit ONE `AskUserQuestion` with a `freeText` field:
```json
{
"questions": [
{
"question": "Paste the SSH URL of the memory repo you created.",
"header": "Repo URL",
"multiSelect": false,
"freeText": true,
"placeholder": "git@github.com:you/kei-memory.git"
}
]
}
```
## 2b — Validate
Regex: `^git@[A-Za-z0-9._-]+:[A-Za-z0-9._/-]+\.git$`
If the user's input does NOT match, print:
```
Invalid SSH URL. Expected shape: git@<host>:<org>/<repo>.git
Examples:
git@github.com:alice/kei-memory.git
git@gitlab.com:alice/devops/kei-memory.git
git@forgejo.keisei.app:alice/kei-memory.git
```
Re-emit the same `AskUserQuestion`. Up to 3 attempts; on the 3rd failure
abort the wizard with a short "try again later with `/sleep-setup`"
message. Do not loop silently.
## 2c — Cross-check against Phase 1
Extract the host from the URL:
```
host = url.match(/^git@([^:]+):/)[1]
```
If `PROVIDER == "GitHub"` and `host != "github.com"`, print a soft
warning: "host <host> doesn't look like github.com — continuing anyway".
Same for GitLab → `gitlab.com`, Bitbucket → `bitbucket.org`. For
`Self-hosted`, skip this check.
Store the URL as `REPO_URL`.
## Verify-criterion
- `REPO_URL` matches the validation regex.
- Exactly ONE `AskUserQuestion` (or up to 3 if the user mistyped).
- No secret-like token accidentally pasted into the URL
(regex rejects `@` outside the leading `git@`).

View file

@ -0,0 +1,70 @@
# Phase 3 — Run setup script, hand off deploy key
Run the imperative helper and hand the public-key material to the user.
## 3a — Invoke `kei-sleep-setup.sh`
Run the primitive non-interactively with `REPO_URL` pre-supplied:
```bash
KEI_MEMORY_REPO_URL="<REPO_URL>" \
~/.claude/agents/_primitives/kei-sleep-setup.sh
```
Capture stdout + stderr. The script:
1. Generates `~/.ssh/keisei-memory-sync` if missing.
2. Prints the `.pub` contents and fingerprint.
3. Scaffolds `~/.claude/memory/sync-repo/` and writes config + env refs.
4. Tests SSH auth against the host (advisory).
If the script exits non-zero, surface its stderr directly to chat and
abort the wizard. Do NOT retry silently.
## 3b — Render deploy-key block to chat
The script already printed the key + fingerprint to its stdout. Echo
that block back to the user verbatim, prefaced with:
```
Add this key as a DEPLOY KEY with WRITE access to <REPO_URL>.
GitHub: Settings → Deploy keys → Add deploy key ("Allow write access")
GitLab: Settings → Repository → Deploy keys → Enable with write access
Bitbucket: Repository settings → Access keys → Add key (write)
Self-host: check your provider's "deploy key" or "access key" feature
```
NEVER show the private key. The `.pub` file is safe to display.
## 3c — Confirm click
Emit ONE `AskUserQuestion`:
```json
{
"questions": [
{
"question": "Have you added the deploy key to the repo with WRITE access?",
"header": "Deploy key",
"multiSelect": false,
"options": [
{"label": "Yes, it's added", "description": "Proceed to a test push"},
{"label": "Show me the key again", "description": "Re-print the public key + fingerprint"},
{"label": "Abort", "description": "Cancel — re-run /sleep-setup later"}
]
}
]
}
```
Handle each option:
- `Yes` → set `KEY_ADDED = true`, proceed to Phase 4.
- `Show again` → re-print the block from 3b, re-emit this click.
- `Abort` → print "aborted — re-run /sleep-setup later"; exit.
## Verify-criterion
- `~/.ssh/keisei-memory-sync(.pub)` exist.
- `~/.claude/memory/sync-repo/.git/` exists.
- `~/.claude/secrets/.env` contains all three `KEI_MEMORY_*` refs.
- `KEY_ADDED == true`.
- Exactly ONE `AskUserQuestion` (plus loops on "Show me again").

View file

@ -0,0 +1,89 @@
# Phase 4 — Test push (verify write access)
Write a tiny marker file, call `kei-sleep-sync.sh`, let the user confirm
the commit landed in the remote.
## 4a — Write a test marker
```bash
touch ~/.claude/memory/sync-repo/traces/.sleep-setup-test
```
The marker file is tracked the same way real traces are (so it tests the
real `git add traces/` path used at session end).
## 4b — Invoke the sync helper
```bash
~/.claude/agents/_primitives/kei-sleep-sync.sh
```
Capture exit code. The helper is designed to be silent on success; capture
`~/.claude/memory/sync-errors.log` as well — if it gained a new line in
the last 60s, surface that line to chat.
## 4c — Show expected commit to user
Read `HEAD`'s commit message from the local mirror:
```bash
( cd ~/.claude/memory/sync-repo && git log -1 --pretty=format:'%h %s' )
```
Print this commit to chat as "expected to appear on your remote:".
## 4d — Confirm click
Emit ONE `AskUserQuestion`:
```json
{
"questions": [
{
"question": "Do you see this commit on the remote (refresh the repo page)?",
"header": "Test push",
"multiSelect": false,
"options": [
{"label": "Yes, commit is there", "description": "Proceed to schedule"},
{"label": "No, not showing up", "description": "Show diagnostics + 2-3 fix paths"},
{"label": "Skip — I'll check later", "description": "Mark as UNVERIFIED, continue"}
]
}
]
}
```
Handle each option:
- `Yes` → set `TEST_VERIFIED = true`, clean up marker, proceed to Phase 5.
- `No` → print the diagnostic block below; re-emit the click.
- `Skip` → set `TEST_VERIFIED = false`, proceed to Phase 5.
## 4e — Diagnostic block (when user says "not showing up")
Render constructively per RULE -1:
```
Three things to check:
1. Deploy key write-access — GitHub/GitLab/Bitbucket default to READ,
you must tick the write box explicitly.
2. Default branch — your repo must have a 'main' branch; if it has
'master' or nothing at all the push target is missing.
3. SSH reachability — run:
ssh -i ~/.ssh/keisei-memory-sync -T git@<host>
and confirm the auth banner shows your repo account.
If all three look correct, check ~/.claude/memory/sync-errors.log.
```
## 4f — Cleanup marker
Regardless of branch:
```bash
rm -f ~/.claude/memory/sync-repo/traces/.sleep-setup-test
```
## Verify-criterion
- Exactly ONE `AskUserQuestion` (plus loops on the "No" branch).
- `TEST_VERIFIED` is either `true` or `false` (both acceptable; only
"Abort" terminates the wizard, and that option doesn't exist here).

View file

@ -0,0 +1,95 @@
# Phase 5 — Emit `/schedule create` command
Render the ready-to-paste nightly trigger and ask whether to run it now,
copy and do later, or skip to local-only mode.
## 5a — Load template
Read `_primitives/templates/sleep-trigger-prompt.md` from the kit install
path:
```
~/.claude/agents/_primitives/templates/sleep-trigger-prompt.md
```
If the file is missing (older kit version), fall back to the inline
template in this phase file (see 5e).
## 5b — Compute UTC cron
Local target: `03:00` user-local time, every day.
```bash
# macOS / GNU date — detect local TZ offset in minutes
offset_min=$(date +%z | awk '{ s=substr($0,1,1); h=substr($0,2,2); m=substr($0,4,2); print (s=="-" ? 1 : -1) * (h*60+m) }')
# local 03:00 = 180 minutes past midnight local
local_minutes=180
utc_minutes=$(( (local_minutes + offset_min + 1440) % 1440 ))
utc_hour=$(( utc_minutes / 60 ))
utc_min=$(( utc_minutes % 60 ))
utc_cron=$(printf '%d %d * * *' "$utc_min" "$utc_hour")
```
Keep arithmetic in the skill prompt if Claude Code executes shell;
otherwise have Claude compute the offset directly.
## 5c — Render placeholders
Replace `{REPO_URL}` with `REPO_URL` and `{UTC_CRON}` with `utc_cron` in
the template. Print the rendered prompt to chat inside a fenced code
block so the user can one-click-copy.
## 5d — Click
Emit ONE `AskUserQuestion`:
```json
{
"questions": [
{
"question": "How should we register the nightly REM trigger?",
"header": "Schedule",
"multiSelect": false,
"options": [
{"label": "Run /schedule now", "description": "Invoke /schedule create with the rendered prompt"},
{"label": "Copy, run later", "description": "Leave it to me — I'll paste into /schedule create myself"},
{"label": "Skip (local-only)", "description": "Just push traces; no nightly consolidation"}
]
}
]
}
```
Handle:
- `Run now` → set `SCHEDULE_ACTION = run-now`; invoke
`/schedule create` with the rendered body.
- `Copy later` → set `SCHEDULE_ACTION = copy-later`; print the body
again and a one-line reminder.
- `Skip` → set `SCHEDULE_ACTION = skip`; print the local-only
footer from SKILL.md's "Final report".
## 5e — Fallback inline template (if kit missing the file)
If `~/.claude/agents/_primitives/templates/sleep-trigger-prompt.md` is
absent, use this minimal inline prompt:
```
Clone: <REPO_URL>
At UTC <utc_cron>:
1. Clone shallow, read traces/ since reports/last-run.txt
2. Write reports/sleep-<date>.md with session + tool + error summary
3. If >=3 cross-session patterns, prepend to backlog.md
4. Commit + push to main
Invariants: append-only traces; no fabricated findings; never
paraphrase patent-sensitive content into report bodies.
```
Note the fallback is strictly less capable — loudly log "template file
missing from kit install; using fallback" so the user can re-install.
## Verify-criterion
- Exactly ONE `AskUserQuestion`.
- Rendered prompt contains no placeholder (`{REPO_URL}` / `{UTC_CRON}`).
- `SCHEDULE_ACTION` is one of `run-now` / `copy-later` / `skip`.
- The final report block from SKILL.md is emitted with real values.