@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.
- package/README.md +32 -0
- package/bin/emdee.js +475 -4
- package/package.json +2 -1
- package/skills/emdee-conventions.md +106 -0
- package/skills/emdee-describe-image.md +98 -0
- package/skills/emdee-onboarder.md +121 -0
- package/skills/emdee-summariser.md +98 -0
- package/src/cli/auth-commands.ts +72 -0
- package/src/cli/auth.ts +215 -0
- package/src/cli/read-commands.ts +245 -15
- package/src/cli/remote-client.ts +74 -0
- package/src/cli/skills-install.ts +54 -0
- package/src/cli/write-commands.ts +349 -0
- package/src/lib/mcp/tools/add_association.ts +2 -14
- package/src/lib/mcp/tools/create_child.ts +0 -2
- package/src/mcp/server.ts +2 -1
package/README.md
CHANGED
|
@@ -44,6 +44,38 @@ npm run mcp # stdio MCP server
|
|
|
44
44
|
|
|
45
45
|
The web viewer (`emdee start`, `emdee serve-next`) is repo-only — it needs the Next.js `app/` tree that isn't in the published tarball. `init`, `list`, `drift-batch`, `mcp` all work from the global install.
|
|
46
46
|
|
|
47
|
+
## Claude Code skills
|
|
48
|
+
|
|
49
|
+
The package ships a set of `.md` skill files under `skills/` that teach Claude Code the vault conventions + workflows. Install them into your Claude Code skills directory:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
emdee skills-install
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This copies:
|
|
56
|
+
|
|
57
|
+
- **emdee-conventions** — always-loaded; teaches Claude the 5-node OS layer, doc shape, edge discipline, tool selection (CLI vs MCP), path conventions. Loading this once means Claude writes correctly to the vault the first time in every session.
|
|
58
|
+
- **emdee-describe-image** — auto-triggers on IMAGES/ docs with `_description pending_` summary. Runs the 4-step get-image → rename-doc → patch-preamble workflow.
|
|
59
|
+
- **emdee-summariser** — batch-refresh drifting doc summaries via `list-summary-drift`.
|
|
60
|
+
- **emdee-onboarder** — walks a new user from `emdee init` through their first project doc + connecting to Claude.
|
|
61
|
+
|
|
62
|
+
Re-run `emdee skills-install` after upgrading the package to pick up updated skill content.
|
|
63
|
+
|
|
64
|
+
## CLI
|
|
65
|
+
|
|
66
|
+
Every MCP tool has a matching `emdee <verb>` CLI command. Same guards, same errors, same semantics — 3–40× cheaper in tokens because the JSON-RPC envelope is skipped. Everything supports `--remote` (routes through emdee.tech via `POST /api/mcp`) or defaults local.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
emdee login # PKCE flow, saves creds to ~/.config/emdee/
|
|
70
|
+
emdee whoami
|
|
71
|
+
emdee list --remote # your live vault paths
|
|
72
|
+
emdee get-doc --path VAULT.md --full --remote # full markdown
|
|
73
|
+
emdee create-child --parent-path VAULT.md --title "MY-PROJECT" --remote
|
|
74
|
+
emdee patch-section --path X --heading Notes --body "..." --expected-hash <hash> --remote
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Full verb list via `emdee --help`. Auth commands: `login`, `logout`, `whoami`. Reads: `get-doc`, `get-summary`, `get-neighbors`, `get-context`, `search`, `read-doc-section`, `list-docs`, `list-summary-drift`, `list`, `drift-batch`. Writes: `patch-section`, `append-section`, `append-doc`, `patch-preamble`, `write-doc`, `write-doc-preview`, `create-child`, `add-association`, `move-doc`, `rename-doc`, `trash-doc`, `restore-doc`, `delete-doc`.
|
|
78
|
+
|
|
47
79
|
## MCP tools
|
|
48
80
|
|
|
49
81
|
The stdio server (`emdee mcp`) exposes 18 tools.
|
package/bin/emdee.js
CHANGED
|
@@ -2,12 +2,18 @@
|
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
4
|
import { mkdir, writeFile, access } from "node:fs/promises";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
5
6
|
import readline from "node:readline/promises";
|
|
6
7
|
import path from "node:path";
|
|
7
8
|
import { fileURLToPath } from "node:url";
|
|
8
9
|
|
|
9
10
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
11
|
const pkgRoot = path.resolve(__dirname, "..");
|
|
12
|
+
// Version comes from package.json — `npm version <bump>` is the one
|
|
13
|
+
// place the string ever needs to change. createRequire works on any
|
|
14
|
+
// Node >= 14 without depending on the newer `import ... with { type: "json" }`
|
|
15
|
+
// attribute syntax (stable only in Node 20.10+).
|
|
16
|
+
const pkg = createRequire(import.meta.url)("../package.json");
|
|
11
17
|
|
|
12
18
|
// SPRINT-090: `start` and `serve-next` shell out to Vite / Next.js against
|
|
13
19
|
// the full repo. Those files (app/, next.config.*, etc.) aren't in the
|
|
@@ -60,7 +66,7 @@ function ownerNodeScaffold(title) {
|
|
|
60
66
|
}
|
|
61
67
|
|
|
62
68
|
const program = new Command();
|
|
63
|
-
program.name("emdee").description("Emdee — local docs + knowledge graph + MCP").version(
|
|
69
|
+
program.name("emdee").description("Emdee — local docs + knowledge graph + MCP").version(pkg.version);
|
|
64
70
|
|
|
65
71
|
program
|
|
66
72
|
.command("init")
|
|
@@ -153,13 +159,15 @@ program
|
|
|
153
159
|
|
|
154
160
|
program
|
|
155
161
|
.command("list")
|
|
156
|
-
.description("Print one doc path per line
|
|
157
|
-
.option("-d, --docs <dir>", "docs directory", "docs")
|
|
162
|
+
.description("Print one doc path per line. Local by default; --remote reads your live vault via emdee.tech.")
|
|
163
|
+
.option("-d, --docs <dir>", "docs directory (local mode)", "docs")
|
|
158
164
|
.option("--prefix <prefix>", "filter to paths starting with this prefix")
|
|
165
|
+
.option("--remote", "route through emdee.tech (requires `emdee login`)")
|
|
159
166
|
.action((opts) => {
|
|
160
167
|
const docs = path.resolve(process.cwd(), opts.docs);
|
|
161
168
|
const args = ["tsx", path.join(pkgRoot, "src/cli/read-commands.ts"), "list"];
|
|
162
169
|
if (opts.prefix) args.push("--prefix", opts.prefix);
|
|
170
|
+
if (opts.remote) args.push("--remote");
|
|
163
171
|
const child = spawn("npx", args, {
|
|
164
172
|
cwd: pkgRoot,
|
|
165
173
|
stdio: "inherit",
|
|
@@ -171,10 +179,11 @@ program
|
|
|
171
179
|
program
|
|
172
180
|
.command("drift-batch")
|
|
173
181
|
.description("Print a batch of docs (path + summary + body) for offline summariser workflows")
|
|
174
|
-
.option("-d, --docs <dir>", "docs directory", "docs")
|
|
182
|
+
.option("-d, --docs <dir>", "docs directory (local mode)", "docs")
|
|
175
183
|
.option("--limit <n>", "docs per batch", "10")
|
|
176
184
|
.option("--offset <k>", "skip the first K docs", "0")
|
|
177
185
|
.option("--prefix <prefix>", "filter to paths starting with this prefix")
|
|
186
|
+
.option("--remote", "route through emdee.tech (requires `emdee login`)")
|
|
178
187
|
.action((opts) => {
|
|
179
188
|
const docs = path.resolve(process.cwd(), opts.docs);
|
|
180
189
|
const args = [
|
|
@@ -185,6 +194,7 @@ program
|
|
|
185
194
|
"--offset", opts.offset,
|
|
186
195
|
];
|
|
187
196
|
if (opts.prefix) args.push("--prefix", opts.prefix);
|
|
197
|
+
if (opts.remote) args.push("--remote");
|
|
188
198
|
const child = spawn("npx", args, {
|
|
189
199
|
cwd: pkgRoot,
|
|
190
200
|
stdio: "inherit",
|
|
@@ -193,4 +203,465 @@ program
|
|
|
193
203
|
child.on("exit", (code) => process.exit(code ?? 0));
|
|
194
204
|
});
|
|
195
205
|
|
|
206
|
+
// SPRINT-091: PKCE login against emdee.tech. Credentials stashed in
|
|
207
|
+
// ~/.config/emdee/credentials.json for `--remote` calls to pick up.
|
|
208
|
+
function shellAuth(sub, extra = []) {
|
|
209
|
+
const child = spawn(
|
|
210
|
+
"npx",
|
|
211
|
+
["tsx", path.join(pkgRoot, "src/cli/auth-commands.ts"), sub, ...extra],
|
|
212
|
+
{ cwd: pkgRoot, stdio: "inherit", env: { ...process.env } },
|
|
213
|
+
);
|
|
214
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// SPRINT-091 chunk 2: write verbs shell through the dispatcher.
|
|
218
|
+
function shellWrite(verb, opts, extra = []) {
|
|
219
|
+
const docs = opts.docs ? path.resolve(process.cwd(), opts.docs) : path.join(process.cwd(), "docs");
|
|
220
|
+
const child = spawn(
|
|
221
|
+
"npx",
|
|
222
|
+
["tsx", path.join(pkgRoot, "src/cli/write-commands.ts"), verb, ...extra],
|
|
223
|
+
{ cwd: pkgRoot, stdio: "inherit", env: { ...process.env, EMDEE_DOCS: docs } },
|
|
224
|
+
);
|
|
225
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// SPRINT-091 chunk 3: structured read verbs share read-commands.ts dispatcher.
|
|
229
|
+
function shellRead(verb, opts, extra = []) {
|
|
230
|
+
const docs = opts.docs ? path.resolve(process.cwd(), opts.docs) : path.join(process.cwd(), "docs");
|
|
231
|
+
const child = spawn(
|
|
232
|
+
"npx",
|
|
233
|
+
["tsx", path.join(pkgRoot, "src/cli/read-commands.ts"), verb, ...extra],
|
|
234
|
+
{ cwd: pkgRoot, stdio: "inherit", env: { ...process.env, EMDEE_DOCS: docs } },
|
|
235
|
+
);
|
|
236
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Every write verb takes the same core flag surface; a small helper builds
|
|
240
|
+
// the extra-args array from commander's parsed opts + a spec of which flags
|
|
241
|
+
// map to which write-commands.ts flag names.
|
|
242
|
+
function argsFromOpts(opts, mapping) {
|
|
243
|
+
const extra = [];
|
|
244
|
+
for (const [optKey, cliFlag] of Object.entries(mapping)) {
|
|
245
|
+
const v = opts[optKey];
|
|
246
|
+
if (Array.isArray(v)) {
|
|
247
|
+
for (const item of v) extra.push(cliFlag, item);
|
|
248
|
+
} else if (typeof v === "string" && v.length > 0) {
|
|
249
|
+
extra.push(cliFlag, v);
|
|
250
|
+
} else if (v === true) {
|
|
251
|
+
extra.push(cliFlag);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return extra;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
program
|
|
258
|
+
.command("login")
|
|
259
|
+
.description("Sign in to emdee.tech via browser (PKCE). Stashes tokens in ~/.config/emdee/.")
|
|
260
|
+
.option("--host <url>", "override the cloud host (defaults to $EMDEE_CLOUD_URL or https://emdee.tech)")
|
|
261
|
+
.action((opts) => {
|
|
262
|
+
const extra = opts.host ? ["--host", opts.host] : [];
|
|
263
|
+
shellAuth("login", extra);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
program
|
|
267
|
+
.command("logout")
|
|
268
|
+
.description("Remove stored credentials.")
|
|
269
|
+
.action(() => shellAuth("logout"));
|
|
270
|
+
|
|
271
|
+
program
|
|
272
|
+
.command("whoami")
|
|
273
|
+
.description("Print the currently logged-in email + namespace.")
|
|
274
|
+
.action(() => shellAuth("whoami"));
|
|
275
|
+
|
|
276
|
+
// SPRINT-094: install the EMDEE Claude Code skills into ~/.claude/skills/
|
|
277
|
+
program
|
|
278
|
+
.command("skills-install")
|
|
279
|
+
.description("Copy packaged skills/*.md into a Claude Code skills directory (default ~/.claude/skills/).")
|
|
280
|
+
.option("--dir <path>", "Target directory")
|
|
281
|
+
.action((opts) => {
|
|
282
|
+
// Resolve --dir against the user's original cwd (not pkgRoot). We resolve
|
|
283
|
+
// here in the shell so the child process sees an absolute path regardless
|
|
284
|
+
// of where tsx runs from.
|
|
285
|
+
const resolvedDir = opts.dir ? path.resolve(process.cwd(), opts.dir) : "";
|
|
286
|
+
const extra = resolvedDir ? ["--dir", resolvedDir] : [];
|
|
287
|
+
const child = spawn(
|
|
288
|
+
"npx",
|
|
289
|
+
["tsx", path.join(pkgRoot, "src/cli/skills-install.ts"), ...extra],
|
|
290
|
+
{ cwd: pkgRoot, stdio: "inherit", env: { ...process.env } },
|
|
291
|
+
);
|
|
292
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// -----------------------------------------------------------------------
|
|
296
|
+
// SPRINT-091 chunk 2: write-side CLI verbs.
|
|
297
|
+
// Each mirrors the corresponding MCP tool. --remote routes through cloud;
|
|
298
|
+
// --json returns the raw MCP envelope for machine consumption.
|
|
299
|
+
// -----------------------------------------------------------------------
|
|
300
|
+
|
|
301
|
+
program
|
|
302
|
+
.command("patch-section")
|
|
303
|
+
.description("Replace an H2 section's body — version-guarded. Same shape as the patch_section MCP tool.")
|
|
304
|
+
.requiredOption("--path <path>", "Vault doc path")
|
|
305
|
+
.requiredOption("--body <text>", "New section body")
|
|
306
|
+
.requiredOption("--expected-hash <hash>", "Prior content_hash from get_doc")
|
|
307
|
+
.option("--section-id <id>", "Section id (preferred over --heading)")
|
|
308
|
+
.option("--heading <heading>", "H2 heading text (without ##)")
|
|
309
|
+
.option("--gate-on <code...>", "Lint codes to hard-block on")
|
|
310
|
+
.option("-d, --docs <dir>", "docs directory (local mode)")
|
|
311
|
+
.option("--remote", "Route through emdee.tech")
|
|
312
|
+
.option("--json", "Machine-parseable output")
|
|
313
|
+
.action((opts) => {
|
|
314
|
+
const extra = argsFromOpts(opts, {
|
|
315
|
+
path: "--path", body: "--body", expectedHash: "--expected-hash",
|
|
316
|
+
sectionId: "--section-id", heading: "--heading", gateOn: "--gate-on",
|
|
317
|
+
remote: "--remote", json: "--json",
|
|
318
|
+
});
|
|
319
|
+
shellWrite("patch-section", opts, extra);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
program
|
|
323
|
+
.command("append-section")
|
|
324
|
+
.description("Append markdown to the end of an existing H2 section. --create-if-missing adds it at end of file.")
|
|
325
|
+
.requiredOption("--path <path>", "Vault doc path")
|
|
326
|
+
.requiredOption("--body <text>", "Content to append")
|
|
327
|
+
.option("--section-id <id>", "Section id (preferred over --heading)")
|
|
328
|
+
.option("--heading <heading>", "H2 heading text (without ##)")
|
|
329
|
+
.option("--create-if-missing", "Create the section at end of file if not found")
|
|
330
|
+
.option("--gate-on <code...>", "Lint codes to hard-block on")
|
|
331
|
+
.option("-d, --docs <dir>", "docs directory (local mode)")
|
|
332
|
+
.option("--remote", "Route through emdee.tech")
|
|
333
|
+
.option("--json", "Machine-parseable output")
|
|
334
|
+
.action((opts) => {
|
|
335
|
+
const extra = argsFromOpts(opts, {
|
|
336
|
+
path: "--path", body: "--body", sectionId: "--section-id",
|
|
337
|
+
heading: "--heading", createIfMissing: "--create-if-missing",
|
|
338
|
+
gateOn: "--gate-on", remote: "--remote", json: "--json",
|
|
339
|
+
});
|
|
340
|
+
shellWrite("append-section", opts, extra);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
program
|
|
344
|
+
.command("append-doc")
|
|
345
|
+
.description("Append to the end of a doc (after every section). Ideal for LOGS, daily notes.")
|
|
346
|
+
.requiredOption("--path <path>", "Vault doc path")
|
|
347
|
+
.requiredOption("--body <text>", "Content to append")
|
|
348
|
+
.option("--gate-on <code...>", "Lint codes to hard-block on")
|
|
349
|
+
.option("-d, --docs <dir>", "docs directory (local mode)")
|
|
350
|
+
.option("--remote", "Route through emdee.tech")
|
|
351
|
+
.option("--json", "Machine-parseable output")
|
|
352
|
+
.action((opts) => {
|
|
353
|
+
const extra = argsFromOpts(opts, {
|
|
354
|
+
path: "--path", body: "--body", gateOn: "--gate-on",
|
|
355
|
+
remote: "--remote", json: "--json",
|
|
356
|
+
});
|
|
357
|
+
shellWrite("append-doc", opts, extra);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
program
|
|
361
|
+
.command("patch-preamble")
|
|
362
|
+
.description("Replace the region between H1 and first H2 (blockquote summary + intro paragraphs).")
|
|
363
|
+
.requiredOption("--path <path>", "Vault doc path")
|
|
364
|
+
.requiredOption("--body <text>", "New preamble body")
|
|
365
|
+
.requiredOption("--expected-hash <hash>", "Prior preamble content_hash from get_doc")
|
|
366
|
+
.option("--gate-on <code...>", "Lint codes to hard-block on")
|
|
367
|
+
.option("-d, --docs <dir>", "docs directory (local mode)")
|
|
368
|
+
.option("--remote", "Route through emdee.tech")
|
|
369
|
+
.option("--json", "Machine-parseable output")
|
|
370
|
+
.action((opts) => {
|
|
371
|
+
const extra = argsFromOpts(opts, {
|
|
372
|
+
path: "--path", body: "--body", expectedHash: "--expected-hash",
|
|
373
|
+
gateOn: "--gate-on", remote: "--remote", json: "--json",
|
|
374
|
+
});
|
|
375
|
+
shellWrite("patch-preamble", opts, extra);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
program
|
|
379
|
+
.command("create-child")
|
|
380
|
+
.description("Atomic write + parent-of patch: create a new doc as child of an existing one.")
|
|
381
|
+
.requiredOption("--parent-path <path>", "Parent doc path")
|
|
382
|
+
.requiredOption("--title <title>", "New doc's H1 title")
|
|
383
|
+
.option("--body <text>", "Optional body appended after ## Notes")
|
|
384
|
+
.option("--summary <text>", "Optional blockquote summary (placeholder if omitted)")
|
|
385
|
+
.option("--child-path <path>", "Override the derived child path")
|
|
386
|
+
.option("--gate-on <code...>", "Lint codes to hard-block on")
|
|
387
|
+
.option("-d, --docs <dir>", "docs directory (local mode)")
|
|
388
|
+
.option("--remote", "Route through emdee.tech")
|
|
389
|
+
.option("--json", "Machine-parseable output")
|
|
390
|
+
.action((opts) => {
|
|
391
|
+
const extra = argsFromOpts(opts, {
|
|
392
|
+
parentPath: "--parent-path", title: "--title", body: "--body",
|
|
393
|
+
summary: "--summary", childPath: "--child-path", gateOn: "--gate-on",
|
|
394
|
+
remote: "--remote", json: "--json",
|
|
395
|
+
});
|
|
396
|
+
shellWrite("create-child", opts, extra);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
program
|
|
400
|
+
.command("add-association")
|
|
401
|
+
.description("Atomic two-sided assoc patch. Hard-refuses hierarchy or sibling duplicates.")
|
|
402
|
+
.requiredOption("--a-path <path>", "First doc path")
|
|
403
|
+
.requiredOption("--b-path <path>", "Second doc path")
|
|
404
|
+
.option("--label <text>", "Shared label on both bullets")
|
|
405
|
+
.option("--gate-on <code...>", "Lint codes to hard-block on")
|
|
406
|
+
.option("-d, --docs <dir>", "docs directory (local mode)")
|
|
407
|
+
.option("--remote", "Route through emdee.tech")
|
|
408
|
+
.option("--json", "Machine-parseable output")
|
|
409
|
+
.action((opts) => {
|
|
410
|
+
const extra = argsFromOpts(opts, {
|
|
411
|
+
aPath: "--a-path", bPath: "--b-path", label: "--label",
|
|
412
|
+
gateOn: "--gate-on", remote: "--remote", json: "--json",
|
|
413
|
+
});
|
|
414
|
+
shellWrite("add-association", opts, extra);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
program
|
|
418
|
+
.command("move-doc")
|
|
419
|
+
.description("Atomic reparent: three-side edge update (child's Child of + both parents' Parent of).")
|
|
420
|
+
.requiredOption("--path <path>", "Child doc to reparent")
|
|
421
|
+
.requiredOption("--new-parent-path <path>", "New parent doc")
|
|
422
|
+
.option("--old-parent-path <path>", "Old parent (required if child has multiple Child of bullets)")
|
|
423
|
+
.option("--position <n>", "0-indexed position in new parent's Parent of")
|
|
424
|
+
.option("--gate-on <code...>", "Lint codes to hard-block on")
|
|
425
|
+
.option("-d, --docs <dir>", "docs directory (local mode)")
|
|
426
|
+
.option("--remote", "Route through emdee.tech")
|
|
427
|
+
.option("--json", "Machine-parseable output")
|
|
428
|
+
.action((opts) => {
|
|
429
|
+
const extra = argsFromOpts(opts, {
|
|
430
|
+
path: "--path", newParentPath: "--new-parent-path",
|
|
431
|
+
oldParentPath: "--old-parent-path", position: "--position",
|
|
432
|
+
gateOn: "--gate-on", remote: "--remote", json: "--json",
|
|
433
|
+
});
|
|
434
|
+
shellWrite("move-doc", opts, extra);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
program
|
|
438
|
+
.command("rename-doc")
|
|
439
|
+
.description("Rewrite H1, move file, update every [[old_title]] wiki-link across the vault. DESTRUCTIVE.")
|
|
440
|
+
.requiredOption("--old-path <path>", "Existing doc path")
|
|
441
|
+
.requiredOption("--new-title <title>", "New H1 title")
|
|
442
|
+
.option("--new-path <path>", "Override the derived new path")
|
|
443
|
+
.option("-d, --docs <dir>", "docs directory (local mode)")
|
|
444
|
+
.option("--remote", "Route through emdee.tech")
|
|
445
|
+
.option("--json", "Machine-parseable output")
|
|
446
|
+
.action((opts) => {
|
|
447
|
+
const extra = argsFromOpts(opts, {
|
|
448
|
+
oldPath: "--old-path", newTitle: "--new-title", newPath: "--new-path",
|
|
449
|
+
remote: "--remote", json: "--json",
|
|
450
|
+
});
|
|
451
|
+
shellWrite("rename-doc", opts, extra);
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
// -----------------------------------------------------------------------
|
|
455
|
+
// SPRINT-091 chunk 3: full-file writes + lifecycle.
|
|
456
|
+
// -----------------------------------------------------------------------
|
|
457
|
+
|
|
458
|
+
program
|
|
459
|
+
.command("write-doc")
|
|
460
|
+
.description("Create or overwrite an entire doc. DESTRUCTIVE — always run write-doc-preview first.")
|
|
461
|
+
.requiredOption("--path <path>", "Vault doc path")
|
|
462
|
+
.requiredOption("--content <text>", "Full markdown content")
|
|
463
|
+
.option("--gate-on <code...>", "Lint codes to hard-block on")
|
|
464
|
+
.option("-d, --docs <dir>", "docs directory (local mode)")
|
|
465
|
+
.option("--remote", "Route through emdee.tech")
|
|
466
|
+
.option("--json", "Machine-parseable output")
|
|
467
|
+
.action((opts) => {
|
|
468
|
+
const extra = argsFromOpts(opts, {
|
|
469
|
+
path: "--path", content: "--content", gateOn: "--gate-on",
|
|
470
|
+
remote: "--remote", json: "--json",
|
|
471
|
+
});
|
|
472
|
+
shellWrite("write-doc", opts, extra);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
program
|
|
476
|
+
.command("write-doc-preview")
|
|
477
|
+
.description("Diff + list of sections that would be removed by write-doc. Always call before write-doc.")
|
|
478
|
+
.requiredOption("--path <path>", "Vault doc path")
|
|
479
|
+
.requiredOption("--content <text>", "Proposed full markdown content")
|
|
480
|
+
.option("-d, --docs <dir>", "docs directory (local mode)")
|
|
481
|
+
.option("--remote", "Route through emdee.tech")
|
|
482
|
+
.option("--json", "Machine-parseable output")
|
|
483
|
+
.action((opts) => {
|
|
484
|
+
const extra = argsFromOpts(opts, {
|
|
485
|
+
path: "--path", content: "--content",
|
|
486
|
+
remote: "--remote", json: "--json",
|
|
487
|
+
});
|
|
488
|
+
shellWrite("write-doc-preview", opts, extra);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
program
|
|
492
|
+
.command("trash-doc")
|
|
493
|
+
.description("Sidecar-based soft delete. Restore is lossless (edges preserved).")
|
|
494
|
+
.requiredOption("--path <path>", "Vault doc path")
|
|
495
|
+
.option("--original-parent-path <path>", "Override the auto-derived restore target")
|
|
496
|
+
.option("-d, --docs <dir>", "docs directory (local mode)")
|
|
497
|
+
.option("--remote", "Route through emdee.tech")
|
|
498
|
+
.option("--json", "Machine-parseable output")
|
|
499
|
+
.action((opts) => {
|
|
500
|
+
const extra = argsFromOpts(opts, {
|
|
501
|
+
path: "--path", originalParentPath: "--original-parent-path",
|
|
502
|
+
remote: "--remote", json: "--json",
|
|
503
|
+
});
|
|
504
|
+
shellWrite("trash-doc", opts, extra);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
program
|
|
508
|
+
.command("restore-doc")
|
|
509
|
+
.description("Reverse a previous trash-doc. Edges were never touched.")
|
|
510
|
+
.requiredOption("--path <path>", "Vault doc path")
|
|
511
|
+
.option("-d, --docs <dir>", "docs directory (local mode)")
|
|
512
|
+
.option("--remote", "Route through emdee.tech")
|
|
513
|
+
.option("--json", "Machine-parseable output")
|
|
514
|
+
.action((opts) => {
|
|
515
|
+
const extra = argsFromOpts(opts, { path: "--path", remote: "--remote", json: "--json" });
|
|
516
|
+
shellWrite("restore-doc", opts, extra);
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
program
|
|
520
|
+
.command("delete-doc")
|
|
521
|
+
.description("Permanently remove a doc. NO UNDO. Returns inbound_edges + title_conflicts.")
|
|
522
|
+
.requiredOption("--path <path>", "Vault doc path")
|
|
523
|
+
.option("-d, --docs <dir>", "docs directory (local mode)")
|
|
524
|
+
.option("--remote", "Route through emdee.tech")
|
|
525
|
+
.option("--json", "Machine-parseable output")
|
|
526
|
+
.action((opts) => {
|
|
527
|
+
const extra = argsFromOpts(opts, { path: "--path", remote: "--remote", json: "--json" });
|
|
528
|
+
shellWrite("delete-doc", opts, extra);
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
// -----------------------------------------------------------------------
|
|
532
|
+
// SPRINT-091 chunk 3: structured reads (get-doc, get-summary, get-neighbors,
|
|
533
|
+
// get-context, search, read-doc-section, list-docs, list-summary-drift).
|
|
534
|
+
// -----------------------------------------------------------------------
|
|
535
|
+
|
|
536
|
+
program
|
|
537
|
+
.command("get-doc")
|
|
538
|
+
.description("Fetch a doc's envelope (title + summary + preamble + section headings). Pass --full for the body.")
|
|
539
|
+
.requiredOption("--path <path>", "Vault doc path")
|
|
540
|
+
.option("--full", "Include the full markdown body")
|
|
541
|
+
.option("--format <fmt>", "text | json (default text for --full)")
|
|
542
|
+
.option("--expected-hash <hash>", "Short-circuit if focal unchanged")
|
|
543
|
+
.option("-d, --docs <dir>", "docs directory (local mode)")
|
|
544
|
+
.option("--remote", "Route through emdee.tech")
|
|
545
|
+
.option("--json", "Machine-parseable output")
|
|
546
|
+
.action((opts) => {
|
|
547
|
+
const extra = argsFromOpts(opts, {
|
|
548
|
+
path: "--path", full: "--full", format: "--format",
|
|
549
|
+
expectedHash: "--expected-hash", remote: "--remote", json: "--json",
|
|
550
|
+
});
|
|
551
|
+
shellRead("get-doc", opts, extra);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
program
|
|
555
|
+
.command("get-summary")
|
|
556
|
+
.description("Return {path, title, summary} for one doc — cheapest way to preview.")
|
|
557
|
+
.requiredOption("--path <path>", "Vault doc path")
|
|
558
|
+
.option("--format <fmt>", "text | json")
|
|
559
|
+
.option("-d, --docs <dir>", "docs directory (local mode)")
|
|
560
|
+
.option("--remote", "Route through emdee.tech")
|
|
561
|
+
.option("--json", "Machine-parseable output")
|
|
562
|
+
.action((opts) => {
|
|
563
|
+
const extra = argsFromOpts(opts, {
|
|
564
|
+
path: "--path", format: "--format", remote: "--remote", json: "--json",
|
|
565
|
+
});
|
|
566
|
+
shellRead("get-summary", opts, extra);
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
program
|
|
570
|
+
.command("get-neighbors")
|
|
571
|
+
.description("Return the doc + 1-hop neighbours categorised by relationship type.")
|
|
572
|
+
.requiredOption("--path <path>", "Vault doc path")
|
|
573
|
+
.option("-d, --docs <dir>", "docs directory (local mode)")
|
|
574
|
+
.option("--remote", "Route through emdee.tech")
|
|
575
|
+
.option("--json", "Machine-parseable output")
|
|
576
|
+
.action((opts) => {
|
|
577
|
+
const extra = argsFromOpts(opts, { path: "--path", remote: "--remote", json: "--json" });
|
|
578
|
+
shellRead("get-neighbors", opts, extra);
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
program
|
|
582
|
+
.command("get-context")
|
|
583
|
+
.description("Return the focal doc + multi-hop neighbourhood within a token budget.")
|
|
584
|
+
.requiredOption("--path <path>", "Vault doc path")
|
|
585
|
+
.option("--hops <n>", "Max BFS depth (1-3, default 2)")
|
|
586
|
+
.option("--budget-tokens <n>", "Rough token cap (default 8000)")
|
|
587
|
+
.option("--include-full", "Inline focal + hop-1 bodies")
|
|
588
|
+
.option("--include-associates", "Include assoc edges in the walk")
|
|
589
|
+
.option("--expected-hash <hash>", "Short-circuit if focal unchanged")
|
|
590
|
+
.option("-d, --docs <dir>", "docs directory (local mode)")
|
|
591
|
+
.option("--remote", "Route through emdee.tech")
|
|
592
|
+
.option("--json", "Machine-parseable output")
|
|
593
|
+
.action((opts) => {
|
|
594
|
+
const extra = argsFromOpts(opts, {
|
|
595
|
+
path: "--path", hops: "--hops", budgetTokens: "--budget-tokens",
|
|
596
|
+
includeFull: "--include-full", includeAssociates: "--include-associates",
|
|
597
|
+
expectedHash: "--expected-hash", remote: "--remote", json: "--json",
|
|
598
|
+
});
|
|
599
|
+
shellRead("get-context", opts, extra);
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
program
|
|
603
|
+
.command("search")
|
|
604
|
+
.description("Case-insensitive substring match over titles, summaries, content.")
|
|
605
|
+
.requiredOption("--query <text>", "Search query")
|
|
606
|
+
.option("--limit <n>", "Max results (default 10)")
|
|
607
|
+
.option("-d, --docs <dir>", "docs directory (local mode)")
|
|
608
|
+
.option("--remote", "Route through emdee.tech")
|
|
609
|
+
.option("--json", "Machine-parseable output")
|
|
610
|
+
.action((opts) => {
|
|
611
|
+
const extra = argsFromOpts(opts, {
|
|
612
|
+
query: "--query", limit: "--limit", remote: "--remote", json: "--json",
|
|
613
|
+
});
|
|
614
|
+
shellRead("search", opts, extra);
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
program
|
|
618
|
+
.command("read-doc-section")
|
|
619
|
+
.description("Read one H2 section's body without paying for the whole doc.")
|
|
620
|
+
.requiredOption("--path <path>", "Vault doc path")
|
|
621
|
+
.option("--section-id <id>", "Section id (preferred over --heading)")
|
|
622
|
+
.option("--heading <heading>", "H2 heading text (without ##)")
|
|
623
|
+
.option("--expected-hash <hash>", "Short-circuit if unchanged")
|
|
624
|
+
.option("-d, --docs <dir>", "docs directory (local mode)")
|
|
625
|
+
.option("--remote", "Route through emdee.tech")
|
|
626
|
+
.option("--json", "Machine-parseable output")
|
|
627
|
+
.action((opts) => {
|
|
628
|
+
const extra = argsFromOpts(opts, {
|
|
629
|
+
path: "--path", sectionId: "--section-id", heading: "--heading",
|
|
630
|
+
expectedHash: "--expected-hash", remote: "--remote", json: "--json",
|
|
631
|
+
});
|
|
632
|
+
shellRead("read-doc-section", opts, extra);
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
program
|
|
636
|
+
.command("list-docs")
|
|
637
|
+
.description("Enumerate every doc in the vault. Structured (vs `list` which is bytes-only).")
|
|
638
|
+
.option("--format <fmt>", "text | json (default text)")
|
|
639
|
+
.option("-d, --docs <dir>", "docs directory (local mode)")
|
|
640
|
+
.option("--remote", "Route through emdee.tech")
|
|
641
|
+
.option("--json", "Machine-parseable output")
|
|
642
|
+
.action((opts) => {
|
|
643
|
+
const extra = argsFromOpts(opts, {
|
|
644
|
+
format: "--format", remote: "--remote", json: "--json",
|
|
645
|
+
});
|
|
646
|
+
shellRead("list-docs", opts, extra);
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
program
|
|
650
|
+
.command("list-summary-drift")
|
|
651
|
+
.description("Return paths whose body has drifted since their summary was last authored.")
|
|
652
|
+
.option("--prefix <p>", "Path prefix filter")
|
|
653
|
+
.option("--limit <n>", "Max candidates (default 20)")
|
|
654
|
+
.option("--offset <k>", "Skip first N candidates")
|
|
655
|
+
.option("--format <fmt>", "text | json (default text)")
|
|
656
|
+
.option("-d, --docs <dir>", "docs directory (local mode)")
|
|
657
|
+
.option("--remote", "Route through emdee.tech")
|
|
658
|
+
.option("--json", "Machine-parseable output")
|
|
659
|
+
.action((opts) => {
|
|
660
|
+
const extra = argsFromOpts(opts, {
|
|
661
|
+
prefix: "--prefix", limit: "--limit", offset: "--offset",
|
|
662
|
+
format: "--format", remote: "--remote", json: "--json",
|
|
663
|
+
});
|
|
664
|
+
shellRead("list-summary-drift", opts, extra);
|
|
665
|
+
});
|
|
666
|
+
|
|
196
667
|
program.parseAsync();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aisystemresources/emdee",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Local-first document management with markdown rendering, knowledge graph, and MCP server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"src/lib/owner/",
|
|
35
35
|
"src/lib/system-nodes.ts",
|
|
36
36
|
"src/mcp/",
|
|
37
|
+
"skills/",
|
|
37
38
|
"templates/",
|
|
38
39
|
"README.md"
|
|
39
40
|
],
|