@aisystemresources/emdee 0.1.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/README.md +85 -0
- package/bin/emdee.js +196 -0
- package/package.json +78 -0
- package/src/cli/read-commands.ts +64 -0
- package/src/core/indexer.ts +346 -0
- package/src/core/parseEdges.ts +106 -0
- package/src/core/resolveLink.ts +136 -0
- package/src/core/siblings.ts +65 -0
- package/src/core/syncDocEdges.ts +427 -0
- package/src/lib/cache/bust.ts +44 -0
- package/src/lib/cache/invalidation.ts +28 -0
- package/src/lib/mcp/activity.ts +254 -0
- package/src/lib/mcp/tools/add_association.ts +311 -0
- package/src/lib/mcp/tools/append_doc.ts +70 -0
- package/src/lib/mcp/tools/append_section.ts +121 -0
- package/src/lib/mcp/tools/create_child.ts +319 -0
- package/src/lib/mcp/tools/delete_doc.ts +68 -0
- package/src/lib/mcp/tools/distill_doc.ts +262 -0
- package/src/lib/mcp/tools/filename.ts +63 -0
- package/src/lib/mcp/tools/get_context.ts +227 -0
- package/src/lib/mcp/tools/get_doc.ts +91 -0
- package/src/lib/mcp/tools/get_image.ts +62 -0
- package/src/lib/mcp/tools/get_neighbors.ts +96 -0
- package/src/lib/mcp/tools/get_summary.ts +18 -0
- package/src/lib/mcp/tools/index.ts +26 -0
- package/src/lib/mcp/tools/lint.ts +552 -0
- package/src/lib/mcp/tools/lint_doc.ts +76 -0
- package/src/lib/mcp/tools/lint_gate.ts +49 -0
- package/src/lib/mcp/tools/list_docs.ts +18 -0
- package/src/lib/mcp/tools/list_summary_drift.ts +95 -0
- package/src/lib/mcp/tools/materialize_subgroup.ts +274 -0
- package/src/lib/mcp/tools/move_doc.ts +439 -0
- package/src/lib/mcp/tools/patch_preamble.ts +145 -0
- package/src/lib/mcp/tools/patch_section.ts +113 -0
- package/src/lib/mcp/tools/read_doc_section.ts +70 -0
- package/src/lib/mcp/tools/rename_doc.ts +167 -0
- package/src/lib/mcp/tools/restore_doc.ts +41 -0
- package/src/lib/mcp/tools/search.ts +36 -0
- package/src/lib/mcp/tools/sections.ts +130 -0
- package/src/lib/mcp/tools/split_doc.ts +129 -0
- package/src/lib/mcp/tools/trash_doc.ts +116 -0
- package/src/lib/mcp/tools/types.ts +7 -0
- package/src/lib/mcp/tools/upload_image.ts +77 -0
- package/src/lib/mcp/tools/validate_args.ts +36 -0
- package/src/lib/mcp/tools/vault.ts +430 -0
- package/src/lib/mcp/tools/write_doc.ts +81 -0
- package/src/lib/mcp/tools/write_doc_preview.ts +59 -0
- package/src/lib/owner/identity.ts +65 -0
- package/src/lib/storage/FilesystemStorage.ts +88 -0
- package/src/lib/storage/SupabaseStorage.ts +358 -0
- package/src/lib/storage/VaultStorage.ts +35 -0
- package/src/lib/storage/index.ts +41 -0
- package/src/lib/supabase/admin.ts +16 -0
- package/src/lib/supabase/client.ts +8 -0
- package/src/lib/supabase/oauth.ts +296 -0
- package/src/lib/supabase/server.ts +22 -0
- package/src/lib/system-nodes.ts +68 -0
- package/src/lib/trash/state.ts +88 -0
- package/src/mcp/server.ts +380 -0
- package/templates/types/CONCEPT.md +21 -0
- package/templates/types/HACKATHON.md +28 -0
- package/templates/types/NOVEL/CHARACTERS.md +29 -0
- package/templates/types/NOVEL/DRAFT.md +15 -0
- package/templates/types/NOVEL/EDITS.md +19 -0
- package/templates/types/NOVEL/INBOX.md +15 -0
- package/templates/types/NOVEL/INSTRUCTIONS.md +32 -0
- package/templates/types/NOVEL/LEARNINGS.md +9 -0
- package/templates/types/NOVEL/OUTBOX.md +15 -0
- package/templates/types/NOVEL/PLOT.md +27 -0
- package/templates/types/NOVEL/WORLDBUILDING.md +23 -0
- package/templates/types/NOVEL.md +32 -0
- package/templates/types/PERSON.md +27 -0
- package/templates/types/PROJECT/BRAND.md +23 -0
- package/templates/types/PROJECT/BUILD.md +9 -0
- package/templates/types/PROJECT/IDEAS.md +11 -0
- package/templates/types/PROJECT/INBOX.md +15 -0
- package/templates/types/PROJECT/INSTRUCTIONS.md +23 -0
- package/templates/types/PROJECT/LEARNINGS.md +9 -0
- package/templates/types/PROJECT/LOGS.md +9 -0
- package/templates/types/PROJECT/OUTBOX.md +15 -0
- package/templates/types/PROJECT/SPRINT.md +56 -0
- package/templates/types/PROJECT.md +26 -0
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Silent Mane
|
|
2
|
+
|
|
3
|
+
Local-first knowledge graph backed by plain markdown. Humans browse it through a React renderer; agents (Claude, Cursor, Codex) read and write the same files through an MCP server. The vault is the source of truth — anything an LLM says traces back to a file you wrote.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
LLM agents need a stable, human-readable substrate to read and write their own context over time. Most knowledge-graph tools are either built for humans (Obsidian) or built for agents (vector stores). Silent Mane is a single substrate for both: the markdown a human edits is the exact bytes an agent reads, with no hidden index, no parallel summaries, no schema gymnastics. Build up a working journal that survives across sessions.
|
|
8
|
+
|
|
9
|
+
## Status
|
|
10
|
+
|
|
11
|
+
Published on npm as `@aisystemresources/emdee`. The CLI (`emdee init`, `emdee mcp`, `emdee list`, `emdee drift-batch`) is globally installable; `emdee start` / `emdee serve-next` still require a repo checkout because they run the full Vite / Next viewer.
|
|
12
|
+
|
|
13
|
+
## Install (consumer)
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g @aisystemresources/emdee
|
|
17
|
+
cd ~/my-vault
|
|
18
|
+
emdee init --nickname "Your Name" # writes ./docs/YOUR-NAME.md as the owner node
|
|
19
|
+
emdee list # your owner + 5 virtual system nodes
|
|
20
|
+
emdee mcp # stdio MCP server — point Claude Code / claude.ai at it
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick start (developer)
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
git clone https://github.com/elz-ming/silent-mane.git
|
|
27
|
+
cd silent-mane
|
|
28
|
+
npm install
|
|
29
|
+
./bin/mane.js init # seeds docs/ with the entry doc, conventions, and sample branch
|
|
30
|
+
npm run dev # Vite dev server with hot reload at http://localhost:5173
|
|
31
|
+
npm run mcp # MCP server over stdio (point Claude.ai / Cursor / Codex at it)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`mane init` lays down:
|
|
35
|
+
|
|
36
|
+
- `docs/MANE.md` — vault entry point
|
|
37
|
+
- `docs/VAULT.md` — meta-pillar grouping the system docs below
|
|
38
|
+
- `docs/INFO.md` — conventions (filenames, relationships, writing format)
|
|
39
|
+
- `docs/INSTRUCTIONS.md` — CEO operating protocol for cross-project agents
|
|
40
|
+
- `docs/BRAIN.md` — cross-project distilled wisdom (always-loaded prior)
|
|
41
|
+
- `docs/WORKFLOWS.md` — concrete procedures the vault runs
|
|
42
|
+
- `docs/SAMPLE.md` + `docs/sample/` — pedagogical examples; delete with `rm -rf docs/sample/` once you've read them
|
|
43
|
+
|
|
44
|
+
Set `SILENT_MANE_ENTRY=your-file.md` to override the default entry name.
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
## MCP tools
|
|
48
|
+
|
|
49
|
+
The MCP server (`mane mcp`) exposes:
|
|
50
|
+
|
|
51
|
+
- `list_docs` — every doc as `{path, title, summary}`. Cold-start enumeration.
|
|
52
|
+
- `get_summary(path)` — one doc's `{path, title, summary}`. Cheap.
|
|
53
|
+
- `get_neighbors(path)` — focal doc + 1-hop neighbors, categorized as `parents / children / associated`. Each neighbor carries the prose note attached to its wiki-link.
|
|
54
|
+
- `get_doc(path)` — full markdown plus per-section `content_hash` for safe patches.
|
|
55
|
+
- `search(query, limit?)` — substring match over titles, summaries, content.
|
|
56
|
+
- `append_section(path, heading, body, create_if_missing?)` — section-scoped append. Safer than `write_doc` for incremental edits.
|
|
57
|
+
- `patch_section(path, heading, body, expected_content_hash)` — version-guarded section replacement. Mismatched hash returns a structured `version_conflict`.
|
|
58
|
+
- `write_doc_preview(path, content)` — diff and list of removed sections before any full-file write.
|
|
59
|
+
- `write_doc(path, content)` — full-file replace (destructive; prefer the section-scoped tools).
|
|
60
|
+
|
|
61
|
+
## Design principles
|
|
62
|
+
|
|
63
|
+
1. **Markdown is the only source of truth.** No persisted index, no derived database, no parallel summaries.
|
|
64
|
+
2. **Same substrate, different lenses.** Renderer and MCP read the same files via the same indexer. Nothing the LLM sees is invisible to the human.
|
|
65
|
+
3. **Convention over schema.** Light structure — H1 + `> blockquote` summary + three relationship sections (`## Parent of`, `## Child of`, `## Associated with`). The LLM parses English natively; rigid schemas only add authoring friction.
|
|
66
|
+
4. **Single summary per doc.** The blockquote under the H1 is the routing decision for both humans and LLMs.
|
|
67
|
+
|
|
68
|
+
## What's in here
|
|
69
|
+
|
|
70
|
+
- `bin/mane.js` — the `mane` CLI (`init`, `start`, `mcp`)
|
|
71
|
+
- `src/core/indexer.ts` — walks `docs/`, parses wiki-links and relationship sections, derives summaries, skips fenced code blocks
|
|
72
|
+
- `src/mcp/server.ts` — MCP server with the tool surface above
|
|
73
|
+
- `src/web/` — React + TypeScript renderer (Toast UI Editor + Cytoscape egocentric graph, category-colored nodes)
|
|
74
|
+
- `src/server/dev-plugin.ts` — Vite middleware that serves the index in dev
|
|
75
|
+
- `api/index.ts` — Vercel serverless function that serves the index in prod
|
|
76
|
+
- `templates/` — vault seeds plus typed templates for `PROJECT`, `NOVEL`, `PERSON`, `HACKATHON`, `CONCEPT`. The engineering layer is type-agnostic — types are conventions plus templates, not schema. Adding a new type is one new file.
|
|
77
|
+
|
|
78
|
+
## Conventions for the vault
|
|
79
|
+
|
|
80
|
+
The seeded `docs/INFO.md` is the full conventions reference: filename rules, the relationship grammar (first wiki-link on each bullet is the declared edge, inline links are context-only), the LEARNINGS authoring format, attribution lines for provenance. Read it once when you start a vault, refer back when you forget how something works.
|
|
81
|
+
|
|
82
|
+
## Deploying to Vercel
|
|
83
|
+
|
|
84
|
+
Vercel auto-detects Vite. Set `SILENT_MANE_DOCS` (or commit a `docs/` for a public vault) and it will serve the SPA plus the `/api/index` endpoint. The default `.gitignore` excludes `docs/` so your vault stays private; remove that line if you want the vault public.
|
|
85
|
+
|
package/bin/emdee.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { mkdir, writeFile, access } from "node:fs/promises";
|
|
5
|
+
import readline from "node:readline/promises";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const pkgRoot = path.resolve(__dirname, "..");
|
|
11
|
+
|
|
12
|
+
// SPRINT-090: `start` and `serve-next` shell out to Vite / Next.js against
|
|
13
|
+
// the full repo. Those files (app/, next.config.*, etc.) aren't in the
|
|
14
|
+
// published tarball, so when the CLI is installed via `npm install -g`
|
|
15
|
+
// those commands need to bail loudly rather than crash with a confusing
|
|
16
|
+
// Vite / Next stack.
|
|
17
|
+
async function ensureRepoCheckout(commandName) {
|
|
18
|
+
try {
|
|
19
|
+
await access(path.join(pkgRoot, "app"));
|
|
20
|
+
} catch {
|
|
21
|
+
console.error(
|
|
22
|
+
`emdee ${commandName} requires a repo checkout — clone https://github.com/AISystemResources/emdee\n` +
|
|
23
|
+
`The published npm package ships the CLI + MCP server only, not the web viewer.`
|
|
24
|
+
);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// SPRINT-093: keep owner-title derivation logic in lockstep with
|
|
30
|
+
// src/lib/owner/identity.ts. The published tarball is plain JS (no tsx),
|
|
31
|
+
// so this duplicates the 15-line function rather than pull in a runtime
|
|
32
|
+
// compilation step. `e2e/cli/init.spec.ts` pins the two copies together —
|
|
33
|
+
// if they ever diverge, the consistency test fails.
|
|
34
|
+
function normalizeOwnerTitle(input) {
|
|
35
|
+
const normalized = input
|
|
36
|
+
.trim()
|
|
37
|
+
.toUpperCase()
|
|
38
|
+
.replace(/[._\s]/g, "-")
|
|
39
|
+
.replace(/[^A-Z0-9-]/g, "")
|
|
40
|
+
.replace(/-+/g, "-")
|
|
41
|
+
.replace(/^-|-$/g, "");
|
|
42
|
+
return normalized || "OWNER";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function ownerNodeScaffold(title) {
|
|
46
|
+
return `# ${title}
|
|
47
|
+
|
|
48
|
+
> Your personal subtree. Top-level content (projects, people, notes, etc.) lives here. Renameable any time via \`rename_doc\` — inbound wiki-link references update atomically across the vault.
|
|
49
|
+
|
|
50
|
+
## Child of
|
|
51
|
+
|
|
52
|
+
* [[EMDEE]]
|
|
53
|
+
|
|
54
|
+
## Parent of
|
|
55
|
+
|
|
56
|
+
## Associated with
|
|
57
|
+
|
|
58
|
+
## Notes
|
|
59
|
+
`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const program = new Command();
|
|
63
|
+
program.name("emdee").description("Emdee — local docs + knowledge graph + MCP").version("0.1.0");
|
|
64
|
+
|
|
65
|
+
program
|
|
66
|
+
.command("init")
|
|
67
|
+
.description("Create a docs/ folder with your owner node. The 5 system nodes (EMDEE, VAULT, SHARED, GRAVEYARD, IMAGES) are virtual — never written to disk.")
|
|
68
|
+
.option("--nickname <name>", "Display name for the owner node (required non-interactively)")
|
|
69
|
+
.action(async (opts) => {
|
|
70
|
+
const cwd = process.cwd();
|
|
71
|
+
const docsDir = path.join(cwd, "docs");
|
|
72
|
+
await mkdir(docsDir, { recursive: true });
|
|
73
|
+
|
|
74
|
+
let nickname = (opts.nickname ?? "").trim();
|
|
75
|
+
if (!nickname) {
|
|
76
|
+
if (!process.stdin.isTTY) {
|
|
77
|
+
console.error("emdee init needs --nickname when running non-interactively.");
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
81
|
+
nickname = (await rl.question("Your name (owner node title): ")).trim();
|
|
82
|
+
rl.close();
|
|
83
|
+
if (!nickname) {
|
|
84
|
+
console.error("emdee init: nickname cannot be empty.");
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const title = normalizeOwnerTitle(nickname);
|
|
90
|
+
if (title === "OWNER") {
|
|
91
|
+
console.error(`emdee init: "${nickname}" normalised to the fallback OWNER — pick a name with ASCII letters.`);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const target = path.join(docsDir, `${title}.md`);
|
|
96
|
+
try {
|
|
97
|
+
await access(target);
|
|
98
|
+
console.log(`Already initialised at docs/${title}.md — leaving it alone.`);
|
|
99
|
+
} catch {
|
|
100
|
+
await writeFile(target, ownerNodeScaffold(title), "utf8");
|
|
101
|
+
console.log(`Created docs/${title}.md — your owner node.`);
|
|
102
|
+
}
|
|
103
|
+
console.log(
|
|
104
|
+
`\nThe 5 system nodes (EMDEE, VAULT, SHARED, GRAVEYARD, IMAGES) are virtual — they appear in \`emdee list\` and \`get_doc\` without being written to disk.`
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
program
|
|
109
|
+
.command("start")
|
|
110
|
+
.description("Start the Emdee viewer against ./docs — requires a repo checkout")
|
|
111
|
+
.option("-p, --port <port>", "port", "5173")
|
|
112
|
+
.option("-d, --docs <dir>", "docs directory", "docs")
|
|
113
|
+
.action(async (opts) => {
|
|
114
|
+
await ensureRepoCheckout("start");
|
|
115
|
+
const docs = path.resolve(process.cwd(), opts.docs);
|
|
116
|
+
const child = spawn("npx", ["vite", "--port", opts.port], {
|
|
117
|
+
cwd: pkgRoot,
|
|
118
|
+
stdio: "inherit",
|
|
119
|
+
env: { ...process.env, EMDEE_DOCS: docs },
|
|
120
|
+
});
|
|
121
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
program
|
|
125
|
+
.command("serve-next")
|
|
126
|
+
.description("Start the Emdee viewer using Next.js (App Router) — requires a repo checkout")
|
|
127
|
+
.option("-p, --port <port>", "port", "3000")
|
|
128
|
+
.option("-d, --docs <dir>", "docs directory", "docs")
|
|
129
|
+
.action(async (opts) => {
|
|
130
|
+
await ensureRepoCheckout("serve-next");
|
|
131
|
+
const docs = path.resolve(process.cwd(), opts.docs);
|
|
132
|
+
const child = spawn("npx", ["next", "dev", "--port", opts.port], {
|
|
133
|
+
cwd: pkgRoot,
|
|
134
|
+
stdio: "inherit",
|
|
135
|
+
env: { ...process.env, EMDEE_DOCS: docs },
|
|
136
|
+
});
|
|
137
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
program
|
|
141
|
+
.command("mcp")
|
|
142
|
+
.description("Run the Emdee MCP server over stdio")
|
|
143
|
+
.option("-d, --docs <dir>", "docs directory", "docs")
|
|
144
|
+
.action((opts) => {
|
|
145
|
+
const docs = path.resolve(process.cwd(), opts.docs);
|
|
146
|
+
const child = spawn("npx", ["tsx", path.join(pkgRoot, "src/mcp/server.ts")], {
|
|
147
|
+
cwd: pkgRoot,
|
|
148
|
+
stdio: "inherit",
|
|
149
|
+
env: { ...process.env, EMDEE_DOCS: docs },
|
|
150
|
+
});
|
|
151
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
program
|
|
155
|
+
.command("list")
|
|
156
|
+
.description("Print one doc path per line (token-cheap; local docs/ only)")
|
|
157
|
+
.option("-d, --docs <dir>", "docs directory", "docs")
|
|
158
|
+
.option("--prefix <prefix>", "filter to paths starting with this prefix")
|
|
159
|
+
.action((opts) => {
|
|
160
|
+
const docs = path.resolve(process.cwd(), opts.docs);
|
|
161
|
+
const args = ["tsx", path.join(pkgRoot, "src/cli/read-commands.ts"), "list"];
|
|
162
|
+
if (opts.prefix) args.push("--prefix", opts.prefix);
|
|
163
|
+
const child = spawn("npx", args, {
|
|
164
|
+
cwd: pkgRoot,
|
|
165
|
+
stdio: "inherit",
|
|
166
|
+
env: { ...process.env, EMDEE_DOCS: docs },
|
|
167
|
+
});
|
|
168
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
program
|
|
172
|
+
.command("drift-batch")
|
|
173
|
+
.description("Print a batch of docs (path + summary + body) for offline summariser workflows")
|
|
174
|
+
.option("-d, --docs <dir>", "docs directory", "docs")
|
|
175
|
+
.option("--limit <n>", "docs per batch", "10")
|
|
176
|
+
.option("--offset <k>", "skip the first K docs", "0")
|
|
177
|
+
.option("--prefix <prefix>", "filter to paths starting with this prefix")
|
|
178
|
+
.action((opts) => {
|
|
179
|
+
const docs = path.resolve(process.cwd(), opts.docs);
|
|
180
|
+
const args = [
|
|
181
|
+
"tsx",
|
|
182
|
+
path.join(pkgRoot, "src/cli/read-commands.ts"),
|
|
183
|
+
"drift-batch",
|
|
184
|
+
"--limit", opts.limit,
|
|
185
|
+
"--offset", opts.offset,
|
|
186
|
+
];
|
|
187
|
+
if (opts.prefix) args.push("--prefix", opts.prefix);
|
|
188
|
+
const child = spawn("npx", args, {
|
|
189
|
+
cwd: pkgRoot,
|
|
190
|
+
stdio: "inherit",
|
|
191
|
+
env: { ...process.env, EMDEE_DOCS: docs },
|
|
192
|
+
});
|
|
193
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
program.parseAsync();
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aisystemresources/emdee",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Local-first document management with markdown rendering, knowledge graph, and MCP server.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"emdee": "./bin/emdee.js"
|
|
8
|
+
},
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"dev": "next dev --webpack",
|
|
14
|
+
"build": "next build --webpack",
|
|
15
|
+
"start": "next start",
|
|
16
|
+
"lint": "eslint",
|
|
17
|
+
"mcp": "tsx src/mcp/server.ts",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"e2e": "playwright test",
|
|
20
|
+
"e2e:ui": "playwright test --ui",
|
|
21
|
+
"e2e:report": "playwright show-report",
|
|
22
|
+
"check:migrations": "node scripts/check-migration-drift.mjs",
|
|
23
|
+
"ci-package": "node scripts/verify-package.mjs"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"bin/",
|
|
27
|
+
"src/cli/",
|
|
28
|
+
"src/core/",
|
|
29
|
+
"src/lib/cache/",
|
|
30
|
+
"src/lib/mcp/",
|
|
31
|
+
"src/lib/storage/",
|
|
32
|
+
"src/lib/supabase/",
|
|
33
|
+
"src/lib/trash/",
|
|
34
|
+
"src/lib/owner/",
|
|
35
|
+
"src/lib/system-nodes.ts",
|
|
36
|
+
"src/mcp/",
|
|
37
|
+
"templates/",
|
|
38
|
+
"README.md"
|
|
39
|
+
],
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@clerk/nextjs": "^7.3.3",
|
|
42
|
+
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
43
|
+
"@sparticuz/chromium": "^149.0.0",
|
|
44
|
+
"@supabase/ssr": "^0.10.3",
|
|
45
|
+
"@supabase/supabase-js": "^2.105.4",
|
|
46
|
+
"@toast-ui/editor": "^3.2.2",
|
|
47
|
+
"commander": "^12.1.0",
|
|
48
|
+
"cytoscape": "^3.30.2",
|
|
49
|
+
"jszip": "^3.10.1",
|
|
50
|
+
"marked": "^18.0.4",
|
|
51
|
+
"next": "16.2.6",
|
|
52
|
+
"puppeteer-core": "^25.1.0",
|
|
53
|
+
"react": "19.2.4",
|
|
54
|
+
"react-dom": "19.2.4",
|
|
55
|
+
"tsx": "^4.22.4"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@clerk/testing": "^2.1.5",
|
|
59
|
+
"@playwright/test": "^1.61.0",
|
|
60
|
+
"@tailwindcss/postcss": "^4",
|
|
61
|
+
"@types/cytoscape": "^3.21.4",
|
|
62
|
+
"@types/node": "^20",
|
|
63
|
+
"@types/react": "^19",
|
|
64
|
+
"@types/react-dom": "^19",
|
|
65
|
+
"eslint": "^9",
|
|
66
|
+
"eslint-config-next": "16.2.6",
|
|
67
|
+
"tailwindcss": "^4",
|
|
68
|
+
"typescript": "^5"
|
|
69
|
+
},
|
|
70
|
+
"engines": {
|
|
71
|
+
"node": ">=20"
|
|
72
|
+
},
|
|
73
|
+
"overrides": {
|
|
74
|
+
"postcss": "^8.5.10",
|
|
75
|
+
"dompurify": "^3.4.11",
|
|
76
|
+
"esbuild": "^0.28.1"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
import { buildIndex } from "../core/indexer";
|
|
4
|
+
|
|
5
|
+
const docsDir = path.resolve(process.env.EMDEE_DOCS ?? path.join(process.cwd(), "docs"));
|
|
6
|
+
|
|
7
|
+
async function cmdList(argv: string[]): Promise<void> {
|
|
8
|
+
const { values } = parseArgs({
|
|
9
|
+
args: argv,
|
|
10
|
+
options: { prefix: { type: "string" } },
|
|
11
|
+
strict: true,
|
|
12
|
+
});
|
|
13
|
+
const idx = await buildIndex(docsDir);
|
|
14
|
+
const prefix = values.prefix ?? "";
|
|
15
|
+
for (const d of idx.docs) {
|
|
16
|
+
if (!prefix || d.path.startsWith(prefix)) process.stdout.write(d.path + "\n");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function cmdDriftBatch(argv: string[]): Promise<void> {
|
|
21
|
+
const { values } = parseArgs({
|
|
22
|
+
args: argv,
|
|
23
|
+
options: {
|
|
24
|
+
limit: { type: "string", default: "10" },
|
|
25
|
+
offset: { type: "string", default: "0" },
|
|
26
|
+
prefix: { type: "string" },
|
|
27
|
+
},
|
|
28
|
+
strict: true,
|
|
29
|
+
});
|
|
30
|
+
const limit = Math.max(1, Number(values.limit) | 0);
|
|
31
|
+
const offset = Math.max(0, Number(values.offset) | 0);
|
|
32
|
+
const idx = await buildIndex(docsDir);
|
|
33
|
+
const filtered = idx.docs
|
|
34
|
+
.filter((d) => !values.prefix || d.path.startsWith(values.prefix))
|
|
35
|
+
.sort((a, b) => a.path.localeCompare(b.path))
|
|
36
|
+
.slice(offset, offset + limit);
|
|
37
|
+
for (const d of filtered) {
|
|
38
|
+
process.stdout.write(`--- ${d.path}\n`);
|
|
39
|
+
process.stdout.write(`${d.summary ?? ""}\n\n`);
|
|
40
|
+
process.stdout.write(`${d.content}\n\n`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const [, , sub, ...rest] = process.argv;
|
|
45
|
+
|
|
46
|
+
async function main(): Promise<void> {
|
|
47
|
+
switch (sub) {
|
|
48
|
+
case "list":
|
|
49
|
+
await cmdList(rest);
|
|
50
|
+
return;
|
|
51
|
+
case "drift-batch":
|
|
52
|
+
await cmdDriftBatch(rest);
|
|
53
|
+
return;
|
|
54
|
+
default:
|
|
55
|
+
process.stderr.write(`unknown subcommand: ${sub ?? "(none)"}\n`);
|
|
56
|
+
process.stderr.write(`usage: emdee <list|drift-batch> [--prefix P] [--limit N] [--offset K]\n`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
main().catch((err) => {
|
|
62
|
+
process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
});
|