@aisystemresources/emdee 0.1.1 → 0.2.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.
@@ -0,0 +1,106 @@
1
+ ---
2
+ name: emdee-conventions
3
+ description: |
4
+ Use whenever reading or writing an EMDEE vault. Covers the 5-node OS layer,
5
+ doc shape (H1 + blockquote + relationship sections), edge discipline, tool
6
+ selection (CLI vs MCP, atomic multi-side writes), and path conventions
7
+ (UPPERCASE filenames, lowercase folders, hub-next-to-folder). Load this once
8
+ and every subsequent vault operation lands correctly the first time.
9
+ ---
10
+
11
+ # EMDEE conventions — always-loaded
12
+
13
+ You are working inside an Emdee vault. Every doc is plain markdown. Humans browse it through a Next.js renderer at [emdee.tech](https://emdee.tech); agents (Claude Code, Claude.ai, Cursor) read and write the same files through an MCP server OR the `emdee` CLI. The vault is the source of truth — anything you say must trace back to a file the user wrote.
14
+
15
+ ## The 5-node OS layer (virtual system nodes)
16
+
17
+ Every vault has exactly 5 canonical top-level docs plus one owner node. **These 5 are virtual** — they appear in every read (`emdee list`, `get_doc`, MCP responses) without being on disk. Edit them and your version wins.
18
+
19
+ | Node | Purpose |
20
+ |---|---|
21
+ | `EMDEE` | Vault root — everything's anchor |
22
+ | `VAULT` | Private notes, projects, knowledge |
23
+ | `SHARED` | Content shared into this vault by others (cloud only) |
24
+ | `GRAVEYARD` | Archived / retired docs |
25
+ | `IMAGES` | Images and visual assets |
26
+ | `<YOUR-NAME>` | Personal subtree, one per user, uppercase |
27
+
28
+ ## Doc shape (universal)
29
+
30
+ ```md
31
+ # TITLE
32
+
33
+ > One-line blockquote summary — this is what routing sees.
34
+
35
+ ## Child of
36
+
37
+ * [[PARENT]]
38
+
39
+ ## Parent of
40
+
41
+ * [[CHILD1]]
42
+ * [[CHILD2]]
43
+
44
+ ## Associated with
45
+
46
+ * [[CROSS-TREE-NODE]] — optional prose about the link
47
+
48
+ ## Notes
49
+
50
+ Freeform content.
51
+ ```
52
+
53
+ Rules the lint enforces (see `lint_doc(path)` or `emdee patch-section --gate-on <code>`):
54
+
55
+ - **One parent per doc.** `## Child of` should have exactly one bullet. Multiple parents → demote secondaries to `## Associated with`.
56
+ - **No sibling associations.** Docs that share a parent are already related through it. `## Associated with` is for cross-tree links (project↔person, sprint↔learning).
57
+ - **Reciprocal edges.** If A's `## Parent of` lists `[[B]]`, B's `## Child of` must list `[[A]]`. Asymmetric edges fire `asymmetric_parent_edge` / `asymmetric_child_edge` warnings.
58
+ - **First wiki-link = declared edge.** `* [[TARGET]] — prose about the link`. The bullet's leading link is the edge; other links in the bullet are inline mentions only.
59
+ - **Sibling order is derived, never declared.** `get_neighbors` returns `prev_sibling` / `next_sibling` computed from the parent's `## Parent of` order. Do NOT add `[[next-node]]` / `[[prev-node]]` bullets.
60
+
61
+ ## Path conventions
62
+
63
+ - **Filenames UPPERCASE with HYPHENS:** `EDMUND.md`, `HANDSTAND-BALANCE-DRILL.md`. Never `edmund.md` or `handstandBalanceDrill.md`.
64
+ - **Folders lowercase:** `edmund/personal/philosophy/CLARITY-IS-POWER.md`. Not `edmund/Personal/PHILOSOPHY/`.
65
+ - **Hub sits next to folder.** `edmund/personal/PHILOSOPHY.md` (the hub) + `edmund/personal/philosophy/*` (the children). NOT `edmund/personal/philosophy/PHILOSOPHY.md`. The hub is a sibling of the folder it heads.
66
+
67
+ ## Tool selection
68
+
69
+ Prefer CLI over MCP for token efficiency:
70
+
71
+ ```bash
72
+ emdee list --remote # 40× cheaper than list_docs MCP call
73
+ emdee get-doc --path X --remote --full # 3× cheaper than get_doc MCP call
74
+ ```
75
+
76
+ All 16 tool verbs are available as `emdee <verb>`. Same guards, same errors, same semantics as the MCP tool of the same name (hyphenated). Everything supports `--remote` (cloud) or defaults local.
77
+
78
+ For writes, always prefer the atomic multi-side variants over raw section patches:
79
+
80
+ | Instead of… | Use… |
81
+ |---|---|
82
+ | Two `patch_section` calls (child's Child of + parent's Parent of) | `emdee create-child` |
83
+ | Two `patch_section` calls on both docs' Associated with | `emdee add-association` |
84
+ | Three `patch_section` calls to reparent | `emdee move-doc` |
85
+ | Search+replace across every doc that references a title | `emdee rename-doc` |
86
+
87
+ The atomic variants take care of edge discipline (hard-refuse on sibling-assoc-redundant, would-duplicate-hierarchy) and rollback semantics.
88
+
89
+ For destructive full-file writes, always run `emdee write-doc-preview` first — `write-doc` silently drops any section not in the new payload.
90
+
91
+ ## Version-guarded writes
92
+
93
+ Every destructive write (`patch-section`, `patch-preamble`, `move-doc`) requires `--expected-hash <hash>` from a prior read. Get it via `emdee get-doc --path X` (returns per-section content_hash + doc_content_hash). A stale hash returns `version_conflict` — refetch, reconcile, retry.
94
+
95
+ ## HARD RULES you'll trip if you're not careful
96
+
97
+ 1. **Any Supabase `.select()` reading > 1000 rows must paginate.** Use `.range(offset, offset + PAGE - 1)` in a loop. `.range()` alone doesn't lift the server-side 1000-row cap.
98
+ 2. **`patch_section` on a `## Child of` requires patching the new parent's `## Parent of` in the same turn.** Otherwise you leave an asymmetric edge. Or just use `move-doc`.
99
+ 3. **New / modified MCP tools require an e2e spec in the same PR.** Test the tool's behaviour end-to-end via a real `ToolContext` + temp filesystem.
100
+ 4. **Migrations touch `supabase/migrations/**` and are NEVER auto-merged.** Human review required.
101
+
102
+ ## When in doubt
103
+
104
+ - `emdee lint-doc --path X` — surface every warning code the doc trips
105
+ - `emdee get-doc --path X` — see current state including per-section hashes
106
+ - Read the source of truth: `edmund/projects/emdee_os/LEARNINGS.md` in the vault
@@ -0,0 +1,98 @@
1
+ ---
2
+ name: emdee-describe-image
3
+ description: |
4
+ Use whenever you encounter an EMDEE vault doc under IMAGES/ whose summary is
5
+ `_description pending_` or whose title looks like a timestamp
6
+ (`IMAGE-14-19-31`, `PHOTO-2026-06-05-...`). The upload flow creates docs in
7
+ this shape by design — this skill fills in the meaning.
8
+ ---
9
+
10
+ # emdee-describe-image — rename + summarise uploaded images
11
+
12
+ When you see an EMDEE image doc that hasn't been described yet, run this workflow. It uses the existing `get_image` + `rename_doc` + `patch_preamble` MCP tools (or their `emdee` CLI equivalents) — no new server-side dependencies.
13
+
14
+ ## Trigger patterns
15
+
16
+ Any vault doc where:
17
+ - Path matches `IMAGES/**/*.md` OR `images/**/*.md`, and
18
+ - Summary blockquote is exactly `_description pending_`, OR
19
+ - Title matches `IMAGE-\d{2}-\d{2}-\d{2}` or `PHOTO-\d{4}-\d{2}-\d{2}-.*`
20
+
21
+ ## Workflow (4 steps)
22
+
23
+ ### 1. Read the image
24
+
25
+ ```
26
+ get_image(doc_path=<PATH>)
27
+ ```
28
+
29
+ Or via CLI (when SPRINT-091 chunk 4 lands `get-image`):
30
+
31
+ ```
32
+ emdee get-image --path <PATH> --remote
33
+ ```
34
+
35
+ The MCP tool returns the image as a visual content block — you can see it.
36
+
37
+ ### 2. Compose
38
+
39
+ - **Semantic title**, 3-6 words, UPPERCASE-with-HYPHENS. Describe the subject, not the container.
40
+ - Good: `HANDSTAND-BALANCE-DRILL`, `AIDA-WORKSHOP-WHITEBOARD`, `KOBE-BRYANT-CAPS-SPEECH`
41
+ - Bad: `PHOTO`, `IMAGE-OF-MAN-STANDING`, `image-14-19-31` (not uppercase, not descriptive)
42
+ - **One-line summary**, 15-30 words, that a future search would surface. What is this, why was it captured, what does it show?
43
+
44
+ ### 3. Rename
45
+
46
+ ```
47
+ emdee rename-doc --old-path <PATH> --new-title <TITLE> --remote
48
+ ```
49
+
50
+ The tool atomically:
51
+ - Rewrites the H1
52
+ - Moves the file (default: same folder, `<TITLE>.md`)
53
+ - Updates every `[[<old title>]]` wiki-link across the vault
54
+
55
+ ### 4. Replace the summary
56
+
57
+ Fetch the fresh preamble hash:
58
+
59
+ ```
60
+ emdee get-doc --path <NEW-PATH> --remote --json
61
+ ```
62
+
63
+ Take `preamble.content_hash` from the response, then:
64
+
65
+ ```
66
+ emdee patch-preamble --path <NEW-PATH> \
67
+ --body "> <your 15-30 word summary>" \
68
+ --expected-hash <hash> --remote
69
+ ```
70
+
71
+ ## Batch mode
72
+
73
+ If several images need describing:
74
+
75
+ ```
76
+ emdee list-docs --prefix "IMAGES/" --remote | \
77
+ xargs -I{} emdee get-summary --path {} --remote --format text
78
+ ```
79
+
80
+ Filter for `_description pending_` in the output, then run the 4-step workflow per image. Cap at 20 per batch — image content is high-signal but you can misread ambiguous shots at scale.
81
+
82
+ ## Failure modes
83
+
84
+ - **Title collision** — `rename_doc` returns `title_conflict`. Pick a more specific title (add a subject qualifier).
85
+ - **`_description pending_` was already replaced** — someone else already ran this. `emdee get-doc` will show the new summary; skip.
86
+ - **Image is illegible** — set the summary to `> Illegible / unable to describe from image alone.` and flag it in the user-facing report so they can annotate manually.
87
+
88
+ ## What to report at the end
89
+
90
+ For each image processed:
91
+
92
+ ```
93
+ <old-path> → <new-path>
94
+ <title>
95
+ <summary>
96
+ ```
97
+
98
+ So the user can sanity-check without opening each doc.
@@ -0,0 +1,121 @@
1
+ ---
2
+ name: emdee-onboarder
3
+ description: |
4
+ Use when the user's vault is fresh (`emdee list` returns exactly the 5
5
+ virtual system nodes + at most 1 owner node), OR when they explicitly ask
6
+ "how do I start with EMDEE" / "walk me through onboarding". Guides them
7
+ through init → first project → connecting to Claude.
8
+ ---
9
+
10
+ # emdee-onboarder — get a new user from install to first useful doc
11
+
12
+ A new user with EMDEE installed has:
13
+ - The `emdee` CLI on PATH
14
+ - A blank `~/.claude/skills/` (or this skill installed)
15
+ - Nothing yet in their vault (5 virtual system nodes + optionally an owner node)
16
+
17
+ Your job is to walk them from that state to a functional vault with at least one project + one note + Claude wired to it.
18
+
19
+ ## Trigger patterns
20
+
21
+ - `emdee list --remote` returns 5 or 6 paths (the system nodes ± the owner)
22
+ - User says: "walk me through onboarding", "how do I start", "what's next"
23
+ - User just ran `emdee init` and is asking what to do next
24
+
25
+ ## Workflow
26
+
27
+ ### 1. Confirm the vault state
28
+
29
+ ```
30
+ emdee list --remote --format text
31
+ ```
32
+
33
+ You should see roughly:
34
+ ```
35
+ EMDEE.md
36
+ GRAVEYARD.md
37
+ IMAGES.md
38
+ SHARED.md
39
+ VAULT.md
40
+ <OWNER>.md # optional — present if they've run `emdee init --nickname`
41
+ ```
42
+
43
+ If the owner is missing, guide them:
44
+ ```
45
+ emdee init --nickname "Their Name"
46
+ ```
47
+
48
+ This writes ONE file (their owner node) locally. In cloud mode, the owner node is created by the first web sign-in via the nickname prompt.
49
+
50
+ ### 2. Explain the 5-node OS layer
51
+
52
+ Quickly. One breath. See `emdee-conventions` skill.
53
+
54
+ Emphasise: **the 5 system nodes are virtual. You don't create them, you don't edit them (unless you really want to override the default content). You reference them from your own content.**
55
+
56
+ ### 3. Create their first real doc
57
+
58
+ Ask what they want to track first. Most users start with either:
59
+ - A project (they're building something)
60
+ - A person (someone they collaborate with or admire)
61
+ - A concept (something they're learning)
62
+
63
+ Then guide them:
64
+
65
+ **For a project:**
66
+ ```
67
+ emdee create-child --parent-path VAULT.md \
68
+ --title "PROJECT-NAME" \
69
+ --summary "One sentence: what this project is and why." \
70
+ --remote
71
+ ```
72
+
73
+ **For a person:**
74
+ ```
75
+ emdee create-child --parent-path VAULT.md \
76
+ --title "FIRSTNAME-LASTNAME" \
77
+ --summary "How you know them + one thing to remember." \
78
+ --remote
79
+ ```
80
+
81
+ **For a concept:**
82
+ ```
83
+ emdee create-child --parent-path VAULT.md \
84
+ --title "CONCEPT-NAME" \
85
+ --summary "The load-bearing idea in one line." \
86
+ --remote
87
+ ```
88
+
89
+ ### 4. Show them the graph
90
+
91
+ Have them open [emdee.tech](https://emdee.tech) in their browser. They should see EMDEE at the centre, connected to their owner node, connected to the doc they just created. This is the moment onboarding clicks — the graph is EMDEE's superpower.
92
+
93
+ ### 5. Connect Claude
94
+
95
+ Explain that everything they just did via `emdee` CLI can also happen via Claude directly. Two paths:
96
+
97
+ **For Claude Code:** the CLI is already installed. Every future session, Claude can run `emdee` commands directly.
98
+
99
+ **For claude.ai:** they connect the emdee.tech MCP server via the connector panel (link in their emdee.tech sidebar).
100
+
101
+ ### 6. Hand off to conventions
102
+
103
+ Say: "The `emdee-conventions` skill is now the always-loaded reference for how to write to your vault. Everything else — creating docs, linking them, tracking edges — is documented there."
104
+
105
+ Load the conventions skill (if not already loaded) and let it take over.
106
+
107
+ ## What to avoid
108
+
109
+ - **Don't dump the full conventions in step 2.** The user is trying to see one thing work. Ship them from zero → seeing their first graph node in under 5 minutes. Depth comes after.
110
+ - **Don't force `create_child` before they know what to create.** The step-3 question ("what do you want to track first?") is critical. If they say "I don't know yet," don't create a doc — instead, suggest they browse [emdee.tech](https://emdee.tech) in incognito to see the public demo vault. Come back when they have an answer.
111
+ - **Don't reference SPRINTs / LOGS / LEARNINGS in onboarding.** Those are for later. Zero-to-one first.
112
+
113
+ ## Success signal
114
+
115
+ The user, at the end of the onboarding, has:
116
+ - ✅ Their owner node visible
117
+ - ✅ At least one child doc under VAULT with a real summary
118
+ - ✅ Both docs visible in the emdee.tech graph, connected
119
+ - ✅ Understands they can use `emdee <verb>` OR Claude directly
120
+
121
+ If all four are true, hand off to `emdee-conventions` and end the onboarding.
@@ -0,0 +1,98 @@
1
+ ---
2
+ name: emdee-summariser
3
+ description: |
4
+ Use whenever the user asks for a summary refresh, or when `emdee
5
+ list-summary-drift` returns ≥ 1 candidate, or when you notice a doc whose
6
+ summary is stale relative to its content (drifted body, new sections added,
7
+ scope change). Runs the SPRINT-081 batch summariser flow.
8
+ ---
9
+
10
+ # emdee-summariser — refresh drifting doc summaries
11
+
12
+ Docs in EMDEE carry a one-line `> blockquote` summary right below the H1. That summary is what routing sees — search, `get_summary`, cheap enumeration all pivot on it. When the body drifts (new sections, refined framing, changed scope), the summary should catch up. This skill runs that refresh.
13
+
14
+ ## Trigger patterns
15
+
16
+ - User asks: "refresh summaries", "which docs have drifted", "run the summariser"
17
+ - Output of `emdee list-summary-drift` or `emdee drift-batch` returns any candidates
18
+ - You notice: after making substantial edits to a doc's body, its blockquote no longer accurately previews what's inside
19
+
20
+ ## Workflow
21
+
22
+ ### 1. Enumerate drift
23
+
24
+ ```
25
+ emdee list-summary-drift --limit 20 --remote --format text
26
+ ```
27
+
28
+ Returns up to 20 paths, one per line. Each is a doc where:
29
+ - `content_hash_at_summary_write` is null (never baselined), OR
30
+ - `hash(current_content) != content_hash_at_summary_write` (body drifted since summary was written)
31
+
32
+ For a bigger batch, raise `--limit`. Don't run > 50 in one pass — token cost + human review burden compounds.
33
+
34
+ ### 2. Per doc, propose a new summary
35
+
36
+ For each returned path:
37
+
38
+ ```
39
+ emdee get-doc --path <PATH> --remote --full --format text
40
+ ```
41
+
42
+ Read the full body. Compose a new one-line summary (15-40 words) that:
43
+ - **Answers "what is this and why does it exist"** — routing signal
44
+ - **Uses the doc's own vocabulary** — searchers will match on their terms
45
+ - **Mentions the load-bearing thing first** — a scan of the first 20 characters should telegraph the doc's purpose
46
+
47
+ ### 3. Report proposals to the user
48
+
49
+ Do NOT patch yet. Present the full batch as a punch list:
50
+
51
+ ```
52
+ <PATH>
53
+ Current: <old blockquote>
54
+ Proposed: <new blockquote>
55
+
56
+ <PATH>
57
+ Current: ...
58
+ Proposed: ...
59
+ ```
60
+
61
+ The user approves per-doc or in bulk.
62
+
63
+ ### 4. On approval, patch each
64
+
65
+ Get the fresh preamble hash:
66
+
67
+ ```
68
+ emdee get-doc --path <PATH> --remote --json
69
+ ```
70
+
71
+ Take `preamble.content_hash`. Then:
72
+
73
+ ```
74
+ emdee patch-preamble --path <PATH> \
75
+ --body "> <new summary>" \
76
+ --expected-hash <hash> --remote
77
+ ```
78
+
79
+ The patch REPLACES the entire preamble body (blockquote + any intro paragraphs). If the doc has intro paragraphs before the first H2, preserve them by including them in the new `--body`.
80
+
81
+ ## Batching etiquette
82
+
83
+ - **Read all 20 first, then propose, then wait for approval, then patch.** Don't interleave read + patch — you'll bill the user for reads on docs they'll reject.
84
+ - **Sort proposals by importance** (parent hubs first, leaves last). If the user only has time to review 5, they see the highest-signal ones.
85
+ - **If two docs share a common subject** and both need re-summarising, note it: `SEE ALSO: <other-path>`. Helps the user spot when a broader restructure is warranted.
86
+
87
+ ## When to say no
88
+
89
+ - If the drift is small (a typo fix, a minor addition), skip the summary refresh. Only touch docs where the body has shifted enough that the current blockquote actively misleads.
90
+ - If the doc's structure has changed (added new sections that fundamentally change scope), don't just refresh the summary — suggest a broader restructure via `emdee move-doc` or `emdee split-doc`. Escalate to the user.
91
+
92
+ ## Reporting after the batch
93
+
94
+ ```
95
+ Refreshed <N> summaries in <namespace>.
96
+ Skipped <M> where drift was cosmetic.
97
+ Flagged <K> for restructure (see notes).
98
+ ```
@@ -0,0 +1,72 @@
1
+ // SPRINT-091: entrypoint for `emdee login | logout | whoami`.
2
+ //
3
+ // Shelled from bin/emdee.js via `npx tsx`. Kept separate from read-commands
4
+ // so the auth flow's node:http server + browser-open logic doesn't get
5
+ // loaded on every read invocation.
6
+
7
+ import { parseArgs } from "node:util";
8
+ import { login, deleteCreds, loadCreds, whoami, DEFAULT_HOST, NeedsLoginError } from "./auth";
9
+
10
+ async function cmdLogin(argv: string[]): Promise<void> {
11
+ const { values } = parseArgs({ args: argv, options: { host: { type: "string" } }, strict: true });
12
+ const host = values.host ?? DEFAULT_HOST;
13
+ const creds = await login(host);
14
+ let email: string | null = null;
15
+ try {
16
+ const w = await whoami(creds);
17
+ email = w.email;
18
+ } catch {
19
+ // Non-fatal; login succeeded.
20
+ }
21
+ process.stdout.write(
22
+ `Logged in to ${host}${email ? ` as ${email}` : ""}.\n` +
23
+ `Credentials saved to ~/.config/emdee/credentials.json (mode 0600).\n`,
24
+ );
25
+ }
26
+
27
+ async function cmdLogout(): Promise<void> {
28
+ const removed = await deleteCreds();
29
+ process.stdout.write(removed ? "Logged out.\n" : "Already logged out.\n");
30
+ }
31
+
32
+ async function cmdWhoami(): Promise<void> {
33
+ const creds = await loadCreds();
34
+ if (!creds) {
35
+ process.stderr.write("Not logged in. Run `emdee login`.\n");
36
+ process.exit(1);
37
+ }
38
+ try {
39
+ const w = await whoami(creds);
40
+ process.stdout.write(`${w.email ?? "(no email on record)"}\nnamespace: ${w.namespace}\nhost: ${creds.host}\n`);
41
+ } catch (err) {
42
+ if (err instanceof NeedsLoginError) {
43
+ process.stderr.write(`${err.message}\n`);
44
+ process.exit(1);
45
+ }
46
+ throw err;
47
+ }
48
+ }
49
+
50
+ const [, , sub, ...rest] = process.argv;
51
+
52
+ async function main(): Promise<void> {
53
+ switch (sub) {
54
+ case "login":
55
+ await cmdLogin(rest);
56
+ return;
57
+ case "logout":
58
+ await cmdLogout();
59
+ return;
60
+ case "whoami":
61
+ await cmdWhoami();
62
+ return;
63
+ default:
64
+ process.stderr.write(`unknown subcommand: ${sub ?? "(none)"}\nusage: emdee <login|logout|whoami> [--host URL]\n`);
65
+ process.exit(1);
66
+ }
67
+ }
68
+
69
+ main().catch((err) => {
70
+ process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
71
+ process.exit(1);
72
+ });