@aisystemresources/emdee 0.1.2 → 0.2.2
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 +542 -3
- 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 +292 -15
- package/src/cli/remote-client.ts +74 -0
- package/src/cli/skills-install.ts +54 -0
- package/src/cli/write-commands.ts +410 -0
- package/src/lib/mcp/tools/add_association.ts +2 -14
- package/src/lib/mcp/tools/create_child.ts +0 -2
- package/src/lib/supabase/oauth.ts +2 -2
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
|
@@ -159,13 +159,15 @@ program
|
|
|
159
159
|
|
|
160
160
|
program
|
|
161
161
|
.command("list")
|
|
162
|
-
.description("Print one doc path per line
|
|
163
|
-
.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")
|
|
164
164
|
.option("--prefix <prefix>", "filter to paths starting with this prefix")
|
|
165
|
+
.option("--remote", "route through emdee.tech (requires `emdee login`)")
|
|
165
166
|
.action((opts) => {
|
|
166
167
|
const docs = path.resolve(process.cwd(), opts.docs);
|
|
167
168
|
const args = ["tsx", path.join(pkgRoot, "src/cli/read-commands.ts"), "list"];
|
|
168
169
|
if (opts.prefix) args.push("--prefix", opts.prefix);
|
|
170
|
+
if (opts.remote) args.push("--remote");
|
|
169
171
|
const child = spawn("npx", args, {
|
|
170
172
|
cwd: pkgRoot,
|
|
171
173
|
stdio: "inherit",
|
|
@@ -177,10 +179,11 @@ program
|
|
|
177
179
|
program
|
|
178
180
|
.command("drift-batch")
|
|
179
181
|
.description("Print a batch of docs (path + summary + body) for offline summariser workflows")
|
|
180
|
-
.option("-d, --docs <dir>", "docs directory", "docs")
|
|
182
|
+
.option("-d, --docs <dir>", "docs directory (local mode)", "docs")
|
|
181
183
|
.option("--limit <n>", "docs per batch", "10")
|
|
182
184
|
.option("--offset <k>", "skip the first K docs", "0")
|
|
183
185
|
.option("--prefix <prefix>", "filter to paths starting with this prefix")
|
|
186
|
+
.option("--remote", "route through emdee.tech (requires `emdee login`)")
|
|
184
187
|
.action((opts) => {
|
|
185
188
|
const docs = path.resolve(process.cwd(), opts.docs);
|
|
186
189
|
const args = [
|
|
@@ -191,6 +194,7 @@ program
|
|
|
191
194
|
"--offset", opts.offset,
|
|
192
195
|
];
|
|
193
196
|
if (opts.prefix) args.push("--prefix", opts.prefix);
|
|
197
|
+
if (opts.remote) args.push("--remote");
|
|
194
198
|
const child = spawn("npx", args, {
|
|
195
199
|
cwd: pkgRoot,
|
|
196
200
|
stdio: "inherit",
|
|
@@ -199,4 +203,539 @@ program
|
|
|
199
203
|
child.on("exit", (code) => process.exit(code ?? 0));
|
|
200
204
|
});
|
|
201
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
|
+
|
|
667
|
+
// -----------------------------------------------------------------------
|
|
668
|
+
// Deferred parity gap closers (post-SPRINT-091): 4 remaining tool verbs.
|
|
669
|
+
// -----------------------------------------------------------------------
|
|
670
|
+
|
|
671
|
+
program
|
|
672
|
+
.command("get-image")
|
|
673
|
+
.description("Fetch an image doc's binary. Default output prints metadata JSON; --out writes decoded bytes to file.")
|
|
674
|
+
.requiredOption("--doc-path <path>", "Path to the image doc (e.g. images/PHOTO-...md)")
|
|
675
|
+
.option("--out <path>", "Write the decoded image bytes to this file")
|
|
676
|
+
.option("--remote", "Route through emdee.tech")
|
|
677
|
+
.option("--json", "Pretty-print metadata")
|
|
678
|
+
.action((opts) => {
|
|
679
|
+
const outResolved = opts.out ? path.resolve(process.cwd(), opts.out) : "";
|
|
680
|
+
const extra = ["--doc-path", opts.docPath];
|
|
681
|
+
if (outResolved) extra.push("--out", outResolved);
|
|
682
|
+
if (opts.remote) extra.push("--remote");
|
|
683
|
+
if (opts.json) extra.push("--json");
|
|
684
|
+
shellRead("get-image", opts, extra);
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
program
|
|
688
|
+
.command("distill-doc")
|
|
689
|
+
.description("Read-only intake for split planning: returns section boundaries + rubric-quoted vault context.")
|
|
690
|
+
.requiredOption("--path <path>", "Doc to distill")
|
|
691
|
+
.option("-d, --docs <dir>", "docs directory (local mode)")
|
|
692
|
+
.option("--remote", "Route through emdee.tech")
|
|
693
|
+
.option("--json", "Machine-parseable output")
|
|
694
|
+
.action((opts) => {
|
|
695
|
+
const extra = argsFromOpts(opts, {
|
|
696
|
+
path: "--path", remote: "--remote", json: "--json",
|
|
697
|
+
});
|
|
698
|
+
shellWrite("distill-doc", opts, extra);
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
program
|
|
702
|
+
.command("materialize-subgroup")
|
|
703
|
+
.description("Promote an H3 subgroup inside a doc's Parent of into a real intermediate parent doc.")
|
|
704
|
+
.requiredOption("--source-path <path>", "Source doc holding the subgroup")
|
|
705
|
+
.requiredOption("--subgroup-heading <heading>", "H3 heading text (without ###)")
|
|
706
|
+
.option("--new-doc-title <title>", "Override the derived new doc title")
|
|
707
|
+
.option("--new-doc-path <path>", "Override the derived new doc path")
|
|
708
|
+
.option("--summary <text>", "Blockquote summary for the new intermediate")
|
|
709
|
+
.option("-d, --docs <dir>", "docs directory (local mode)")
|
|
710
|
+
.option("--remote", "Route through emdee.tech")
|
|
711
|
+
.option("--json", "Machine-parseable output")
|
|
712
|
+
.action((opts) => {
|
|
713
|
+
const extra = argsFromOpts(opts, {
|
|
714
|
+
sourcePath: "--source-path", subgroupHeading: "--subgroup-heading",
|
|
715
|
+
newDocTitle: "--new-doc-title", newDocPath: "--new-doc-path",
|
|
716
|
+
summary: "--summary", remote: "--remote", json: "--json",
|
|
717
|
+
});
|
|
718
|
+
shellWrite("materialize-subgroup", opts, extra);
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
program
|
|
722
|
+
.command("split-doc")
|
|
723
|
+
.description("Atomically refactor a doc into concept nodes. Extracts array read from a JSON file.")
|
|
724
|
+
.requiredOption("--source-path <path>", "Source doc being split")
|
|
725
|
+
.requiredOption("--rewrite-source-content <text>", "New markdown for the source (with wiki-links to extracts)")
|
|
726
|
+
.requiredOption("--extracts-file <path>", 'JSON file: [{"path":"<new>.md","content":"<md>"}, ...]')
|
|
727
|
+
.option("-d, --docs <dir>", "docs directory (local mode)")
|
|
728
|
+
.option("--remote", "Route through emdee.tech")
|
|
729
|
+
.option("--json", "Machine-parseable output")
|
|
730
|
+
.action((opts) => {
|
|
731
|
+
const extractsResolved = path.resolve(process.cwd(), opts.extractsFile);
|
|
732
|
+
const extra = argsFromOpts(opts, {
|
|
733
|
+
sourcePath: "--source-path",
|
|
734
|
+
rewriteSourceContent: "--rewrite-source-content",
|
|
735
|
+
remote: "--remote", json: "--json",
|
|
736
|
+
});
|
|
737
|
+
extra.push("--extracts-file", extractsResolved);
|
|
738
|
+
shellWrite("split-doc", opts, extra);
|
|
739
|
+
});
|
|
740
|
+
|
|
202
741
|
program.parseAsync();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aisystemresources/emdee",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.2",
|
|
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
|
],
|