@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
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { readVaultFile, validatePath } from "./vault";
|
|
2
|
+
import type { ToolContext } from "./types";
|
|
3
|
+
|
|
4
|
+
function err(text: string) {
|
|
5
|
+
return { content: [{ type: "text" as const, text: JSON.stringify({ error: text }) }] };
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Fetch an image doc from the vault and return both its metadata and the raw
|
|
10
|
+
* image bytes as an MCP image content block so Claude can visually analyze it.
|
|
11
|
+
*
|
|
12
|
+
* Workflow for batch labelling:
|
|
13
|
+
* 1. list_docs → find images/*.md with "_description pending_" in summary
|
|
14
|
+
* 2. get_image(doc_path) → Claude sees the image
|
|
15
|
+
* 3. patch_preamble → write a real description as the blockquote summary
|
|
16
|
+
* 4. add_association → link to relevant project / person / event docs
|
|
17
|
+
*/
|
|
18
|
+
export async function getImage(ctx: ToolContext, args: Record<string, unknown>): Promise<unknown> {
|
|
19
|
+
const docPath = String(args.doc_path ?? "").trim();
|
|
20
|
+
if (!docPath) return err("doc_path is required");
|
|
21
|
+
|
|
22
|
+
try { validatePath(docPath); } catch (e) { return err(String(e)); }
|
|
23
|
+
|
|
24
|
+
const raw = await readVaultFile(ctx, docPath);
|
|
25
|
+
if (!raw) return err(`doc not found: ${docPath}`);
|
|
26
|
+
|
|
27
|
+
// Extract the first image URL from the doc body.
|
|
28
|
+
const m = raw.match(/!\[.*?\]\((https?:\/\/[^)]+)\)/);
|
|
29
|
+
if (!m) return err(`no image URL found in ${docPath}`);
|
|
30
|
+
const imageUrl = m[1];
|
|
31
|
+
|
|
32
|
+
// Fetch the image binary.
|
|
33
|
+
let imageResp: Response;
|
|
34
|
+
try {
|
|
35
|
+
imageResp = await fetch(imageUrl);
|
|
36
|
+
} catch (e) {
|
|
37
|
+
return err(`failed to fetch image: ${String(e)}`);
|
|
38
|
+
}
|
|
39
|
+
if (!imageResp.ok) return err(`image fetch returned ${imageResp.status}`);
|
|
40
|
+
|
|
41
|
+
const contentType = imageResp.headers.get("content-type") ?? "image/jpeg";
|
|
42
|
+
const mimeType = contentType.split(";")[0].trim();
|
|
43
|
+
|
|
44
|
+
const buffer = Buffer.from(await imageResp.arrayBuffer());
|
|
45
|
+
if (buffer.length === 0) return err("image fetch returned empty body");
|
|
46
|
+
|
|
47
|
+
const base64 = buffer.toString("base64");
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
content: [
|
|
51
|
+
{
|
|
52
|
+
type: "text" as const,
|
|
53
|
+
text: JSON.stringify({ doc_path: docPath, image_url: imageUrl }),
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
type: "image" as const,
|
|
57
|
+
data: base64,
|
|
58
|
+
mimeType,
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { loadVaultIndex } from "./vault";
|
|
2
|
+
import type { DocIndex, DocNode, Link, ToolContext } from "./types";
|
|
3
|
+
import { getPrevNextSiblings } from "../../../core/siblings";
|
|
4
|
+
import { resolveWikiLink } from "../../../core/resolveLink";
|
|
5
|
+
|
|
6
|
+
function json(value: unknown) {
|
|
7
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(value, null, 2) }] };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface NeighborRef { path: string; title: string; summary: string; note: string; }
|
|
11
|
+
|
|
12
|
+
function buildNeighbors(idx: DocIndex, focal: DocNode) {
|
|
13
|
+
const byPath = new Map(idx.docs.map((d) => [d.path, d]));
|
|
14
|
+
// Locality-aware resolver: bullets like `[[DAY1]]` are disambiguated
|
|
15
|
+
// by the focal's path when two docs share the title or slug.
|
|
16
|
+
const resolve = (t: string) => byPath.get(t) ?? resolveWikiLink(idx, t, focal.path);
|
|
17
|
+
const refFor = (n: DocNode, note: string): NeighborRef => ({ path: n.path, title: n.title, summary: n.summary, note });
|
|
18
|
+
|
|
19
|
+
const declaredParents = new Map<string, NeighborRef>();
|
|
20
|
+
const declaredChildren = new Map<string, NeighborRef>();
|
|
21
|
+
const declaredAssoc = new Map<string, NeighborRef>();
|
|
22
|
+
for (const l of focal.parents) { const n = resolve(l.title); if (n) declaredParents.set(n.path, refFor(n, l.note)); }
|
|
23
|
+
for (const l of focal.children) { const n = resolve(l.title); if (n) declaredChildren.set(n.path, refFor(n, l.note)); }
|
|
24
|
+
// Compute focal's parent paths once for the sibling check below.
|
|
25
|
+
const focalParentPaths = new Set(
|
|
26
|
+
focal.parents
|
|
27
|
+
.map((l) => resolve(l.title)?.path)
|
|
28
|
+
.filter((p): p is string => !!p)
|
|
29
|
+
);
|
|
30
|
+
for (const l of focal.associates) {
|
|
31
|
+
const n = resolve(l.title);
|
|
32
|
+
if (!n) continue;
|
|
33
|
+
// Hierarchy beats associate — if the target is already a parent or
|
|
34
|
+
// child of this focal, drop the assoc entry.
|
|
35
|
+
if (declaredParents.has(n.path) || declaredChildren.has(n.path)) continue;
|
|
36
|
+
// Sibling beats associate — if the target shares one of this focal's
|
|
37
|
+
// parents, the relationship is already conveyed by the hierarchy.
|
|
38
|
+
if (focalParentPaths.size > 0) {
|
|
39
|
+
const candidateParentPaths = n.parents
|
|
40
|
+
.map((pl) => resolve(pl.title)?.path)
|
|
41
|
+
.filter((p): p is string => !!p);
|
|
42
|
+
if (candidateParentPaths.some((p) => focalParentPaths.has(p))) continue;
|
|
43
|
+
}
|
|
44
|
+
declaredAssoc.set(n.path, refFor(n, l.note));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const focalTitleLower = focal.title.toLowerCase();
|
|
48
|
+
const matchesFocal = (l: Link) => l.title.toLowerCase() === focalTitleLower;
|
|
49
|
+
for (const other of idx.docs) {
|
|
50
|
+
if (other.path === focal.path) continue;
|
|
51
|
+
const asChild = other.children.find(matchesFocal);
|
|
52
|
+
if (asChild && !declaredParents.has(other.path)) declaredParents.set(other.path, refFor(other, asChild.note));
|
|
53
|
+
const asParent = other.parents.find(matchesFocal);
|
|
54
|
+
if (asParent && !declaredChildren.has(other.path)) declaredChildren.set(other.path, refFor(other, asParent.note));
|
|
55
|
+
const asAssoc = other.associates.find(matchesFocal);
|
|
56
|
+
if (asAssoc && !declaredAssoc.has(other.path)) declaredAssoc.set(other.path, refFor(other, asAssoc.note));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const declared = new Set([...declaredParents.keys(), ...declaredChildren.keys(), ...declaredAssoc.keys()]);
|
|
60
|
+
const mentionedIn = idx.docs
|
|
61
|
+
.filter((d) => d.path !== focal.path && !declared.has(d.path) && d.mentions.some((m) => m.toLowerCase() === focalTitleLower))
|
|
62
|
+
.map((d) => ({ path: d.path, title: d.title, summary: d.summary }));
|
|
63
|
+
|
|
64
|
+
// Prev/next sibling — shared helper. Uses the parent's `## Parent of`
|
|
65
|
+
// bullet order, augmented with any other doc whose primary parent
|
|
66
|
+
// matches focal's primary parent (catches asymmetric edges where the
|
|
67
|
+
// child declared `Child of` but the parent didn't reciprocate).
|
|
68
|
+
let prev_sibling: { path: string; title: string; summary: string } | null = null;
|
|
69
|
+
let next_sibling: { path: string; title: string; summary: string } | null = null;
|
|
70
|
+
const { prevPath, nextPath } = getPrevNextSiblings(idx, focal.path);
|
|
71
|
+
if (prevPath) {
|
|
72
|
+
const p = idx.docs.find((d) => d.path === prevPath);
|
|
73
|
+
if (p) prev_sibling = { path: p.path, title: p.title, summary: p.summary };
|
|
74
|
+
}
|
|
75
|
+
if (nextPath) {
|
|
76
|
+
const n = idx.docs.find((d) => d.path === nextPath);
|
|
77
|
+
if (n) next_sibling = { path: n.path, title: n.title, summary: n.summary };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
path: focal.path, title: focal.title, summary: focal.summary,
|
|
82
|
+
parents: [...declaredParents.values()],
|
|
83
|
+
children: [...declaredChildren.values()],
|
|
84
|
+
associated: [...declaredAssoc.values()],
|
|
85
|
+
mentioned_in: mentionedIn,
|
|
86
|
+
prev_sibling,
|
|
87
|
+
next_sibling,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function getNeighbors(ctx: ToolContext, args: Record<string, unknown>): Promise<unknown> {
|
|
92
|
+
const idx = await loadVaultIndex(ctx);
|
|
93
|
+
const focal = idx.docs.find((d) => d.path === String(args.path));
|
|
94
|
+
if (!focal) throw new Error(`no such doc: ${args.path}`);
|
|
95
|
+
return json(buildNeighbors(idx, focal));
|
|
96
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { loadVaultIndex } from "./vault";
|
|
2
|
+
import type { ToolContext } from "./types";
|
|
3
|
+
|
|
4
|
+
function json(value: unknown) {
|
|
5
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(value, null, 2) }] };
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function text(value: string) {
|
|
9
|
+
return { content: [{ type: "text" as const, text: value }] };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function getSummary(ctx: ToolContext, args: Record<string, unknown>): Promise<unknown> {
|
|
13
|
+
const idx = await loadVaultIndex(ctx);
|
|
14
|
+
const doc = idx.docs.find((d) => d.path === String(args.path));
|
|
15
|
+
if (!doc) throw new Error(`no such doc: ${args.path}`);
|
|
16
|
+
if (args.format === "text") return text(doc.summary ?? "");
|
|
17
|
+
return json({ path: doc.path, title: doc.title, summary: doc.summary });
|
|
18
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export { listDocs } from "./list_docs";
|
|
2
|
+
export { listSummaryDrift } from "./list_summary_drift";
|
|
3
|
+
export { getSummary } from "./get_summary";
|
|
4
|
+
export { getNeighbors } from "./get_neighbors";
|
|
5
|
+
export { getContext } from "./get_context";
|
|
6
|
+
export { getDoc } from "./get_doc";
|
|
7
|
+
export { readDocSection } from "./read_doc_section";
|
|
8
|
+
export { search } from "./search";
|
|
9
|
+
export { appendSection } from "./append_section";
|
|
10
|
+
export { patchSection } from "./patch_section";
|
|
11
|
+
export { writeDocPreview } from "./write_doc_preview";
|
|
12
|
+
export { writeDoc } from "./write_doc";
|
|
13
|
+
export { deleteDoc } from "./delete_doc";
|
|
14
|
+
export { splitDoc } from "./split_doc";
|
|
15
|
+
export { materializeSubgroup } from "./materialize_subgroup";
|
|
16
|
+
export { renameDoc } from "./rename_doc";
|
|
17
|
+
export { patchPreamble } from "./patch_preamble";
|
|
18
|
+
export { appendDoc } from "./append_doc";
|
|
19
|
+
export { lintDoc } from "./lint_doc";
|
|
20
|
+
export { distillDoc } from "./distill_doc";
|
|
21
|
+
export { createChild } from "./create_child";
|
|
22
|
+
export { addAssociation } from "./add_association";
|
|
23
|
+
export { moveDoc } from "./move_doc";
|
|
24
|
+
export { trashDoc } from "./trash_doc";
|
|
25
|
+
export { restoreDoc } from "./restore_doc";
|
|
26
|
+
export { getImage } from "./get_image";
|