membot 0.2.0 → 0.2.1
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/.claude/skills/membot.md +1 -1
- package/.cursor/rules/membot.mdc +1 -1
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/operations/tree.ts +49 -10
package/.claude/skills/membot.md
CHANGED
|
@@ -110,7 +110,7 @@ Tombstones hide a path from `ls` / `tree` / `search` but `versions` and `read --
|
|
|
110
110
|
| ------------------------------------- | ------------------------------------------------------------------------------ |
|
|
111
111
|
| `membot add <source>` | Ingest file, directory, glob, URL, or `inline:<text>`. Skips unchanged sources; pass `--force` to re-ingest |
|
|
112
112
|
| `membot ls [prefix]` | List current files (size, mime, refresh status) |
|
|
113
|
-
| `membot tree [prefix]` | Render the synthesised logical-path tree
|
|
113
|
+
| `membot tree [prefix]` | Render the synthesised logical-path tree (`--max-depth`, `--max-items` cap output) |
|
|
114
114
|
| `membot read <path>` | Read current markdown surrogate (or `--bytes` for original) |
|
|
115
115
|
| `membot write <path> --content <txt>` | Write inline agent-authored markdown as a new version |
|
|
116
116
|
| `membot search <query>` | Hybrid search (semantic + BM25); add `--include-history` to search older versions |
|
package/.cursor/rules/membot.mdc
CHANGED
|
@@ -110,7 +110,7 @@ Tombstones hide a path from `ls` / `tree` / `search` but `versions` and `read --
|
|
|
110
110
|
| ------------------------------------- | ------------------------------------------------------------------------------ |
|
|
111
111
|
| `membot add <source>` | Ingest file, directory, glob, URL, or `inline:<text>`. Skips unchanged sources; pass `--force` to re-ingest |
|
|
112
112
|
| `membot ls [prefix]` | List current files (size, mime, refresh status) |
|
|
113
|
-
| `membot tree [prefix]` | Render the synthesised logical-path tree
|
|
113
|
+
| `membot tree [prefix]` | Render the synthesised logical-path tree (`--max-depth`, `--max-items` cap output) |
|
|
114
114
|
| `membot read <path>` | Read current markdown surrogate (or `--bytes` for original) |
|
|
115
115
|
| `membot write <path> --content <txt>` | Write inline agent-authored markdown as a new version |
|
|
116
116
|
| `membot search <query>` | Hybrid search (semantic + BM25); add `--include-history` to search older versions |
|
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ The skill files describe the discover → ingest → search → read → write w
|
|
|
52
52
|
| ------------------------------- | --------------------------------------------------------------------------------- |
|
|
53
53
|
| `membot add <source>` | Ingest a file, directory, glob, URL, or `inline:<text>`. Default `logical_path` mirrors the source (absolute path for local files, `remotes/{host}/{path}` for URLs) so files with the same basename in different projects don't collide. Pass `-p <path>` to override or, on a directory walk, to set a prefix. Skips on unchanged source bytes; pass `--force` to re-ingest. |
|
|
54
54
|
| `membot ls [prefix]` | List current files (size, mime, refresh status) |
|
|
55
|
-
| `membot tree [prefix]` | Render the synthesised logical-path tree
|
|
55
|
+
| `membot tree [prefix]` | Render the synthesised logical-path tree (`--max-depth`, `--max-items` cap output) |
|
|
56
56
|
| `membot read <path>` | Read the markdown surrogate (or `--bytes` for original bytes, base64) |
|
|
57
57
|
| `membot search <query>` | Hybrid search (semantic + BM25); `--include-history` searches older versions |
|
|
58
58
|
| `membot info <path>` | Inspect metadata (source, fetcher, schedule, digests) without content |
|
package/package.json
CHANGED
package/src/operations/tree.ts
CHANGED
|
@@ -8,6 +8,7 @@ interface TreeNode {
|
|
|
8
8
|
full_path: string;
|
|
9
9
|
is_file: boolean;
|
|
10
10
|
children?: TreeNode[];
|
|
11
|
+
children_truncated?: number;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export const treeOperation = defineOperation({
|
|
@@ -18,6 +19,10 @@ export const treeOperation = defineOperation({
|
|
|
18
19
|
inputSchema: z.object({
|
|
19
20
|
prefix: z.string().optional().describe("Only show paths starting with this prefix"),
|
|
20
21
|
max_depth: z.number().default(4).describe("How many path segments deep to render"),
|
|
22
|
+
max_items: z
|
|
23
|
+
.number()
|
|
24
|
+
.default(20)
|
|
25
|
+
.describe("Max children to render at each level; remainder is summarised as '+N more'"),
|
|
21
26
|
}),
|
|
22
27
|
outputSchema: z.object({
|
|
23
28
|
root: z.string(),
|
|
@@ -27,21 +32,30 @@ export const treeOperation = defineOperation({
|
|
|
27
32
|
full_path: z.string(),
|
|
28
33
|
is_file: z.boolean(),
|
|
29
34
|
children: z.array(z.unknown()).optional(),
|
|
35
|
+
children_truncated: z.number().optional(),
|
|
30
36
|
}),
|
|
31
37
|
),
|
|
38
|
+
truncated: z.number().optional(),
|
|
32
39
|
}),
|
|
33
40
|
cli: { positional: ["prefix"] },
|
|
34
41
|
console_formatter: (result) => {
|
|
35
42
|
const lines: string[] = [colors.bold(result.root)];
|
|
36
43
|
const nodes = result.tree as TreeNode[];
|
|
37
|
-
|
|
44
|
+
const topTruncated = (result as { truncated?: number }).truncated ?? 0;
|
|
45
|
+
renderNodes(nodes, "", lines, topTruncated);
|
|
38
46
|
if (lines.length === 1) lines.push(colors.dim("(empty)"));
|
|
39
47
|
return lines.join("\n");
|
|
40
48
|
},
|
|
41
49
|
handler: async (input, ctx) => {
|
|
42
50
|
const allPaths = await listAllCurrentPaths(ctx.db);
|
|
43
51
|
const filtered = input.prefix ? allPaths.filter((p) => p.startsWith(input.prefix!)) : allPaths;
|
|
44
|
-
|
|
52
|
+
const tree = buildTree(filtered, input.max_depth);
|
|
53
|
+
const truncated = truncateTree(tree, input.max_items);
|
|
54
|
+
return {
|
|
55
|
+
root: input.prefix ?? "/",
|
|
56
|
+
tree,
|
|
57
|
+
...(truncated > 0 ? { truncated } : {}),
|
|
58
|
+
};
|
|
45
59
|
},
|
|
46
60
|
});
|
|
47
61
|
|
|
@@ -50,9 +64,10 @@ export const treeOperation = defineOperation({
|
|
|
50
64
|
* Splits each path into segments and groups by common prefix. Segments
|
|
51
65
|
* deeper than `maxDepth` are folded into the deepest visible ancestor —
|
|
52
66
|
* that ancestor is marked `is_file=true` so the renderer surfaces it as a
|
|
53
|
-
* leaf even though longer paths exist underneath.
|
|
67
|
+
* leaf even though longer paths exist underneath. Children are sorted by
|
|
68
|
+
* name within each level so downstream truncation is deterministic.
|
|
54
69
|
*/
|
|
55
|
-
function buildTree(paths: string[], maxDepth: number): TreeNode[] {
|
|
70
|
+
export function buildTree(paths: string[], maxDepth: number): TreeNode[] {
|
|
56
71
|
interface MutableNode {
|
|
57
72
|
name: string;
|
|
58
73
|
full_path: string;
|
|
@@ -90,19 +105,43 @@ function buildTree(paths: string[], maxDepth: number): TreeNode[] {
|
|
|
90
105
|
return finalize(root);
|
|
91
106
|
}
|
|
92
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Trim each child list (and the root list) to `maxItems`, mutating in place.
|
|
110
|
+
* Returns the number of root entries dropped; per-node drops are recorded on
|
|
111
|
+
* `node.children_truncated`. Input is assumed pre-sorted (by `buildTree`) so
|
|
112
|
+
* "first N" is stable.
|
|
113
|
+
*/
|
|
114
|
+
export function truncateTree(nodes: TreeNode[], maxItems: number): number {
|
|
115
|
+
for (const node of nodes) {
|
|
116
|
+
if (node.children?.length) {
|
|
117
|
+
const dropped = truncateTree(node.children, maxItems);
|
|
118
|
+
if (dropped > 0) node.children_truncated = dropped;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (nodes.length > maxItems) {
|
|
122
|
+
const dropped = nodes.length - maxItems;
|
|
123
|
+
nodes.length = maxItems;
|
|
124
|
+
return dropped;
|
|
125
|
+
}
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
93
129
|
/**
|
|
94
130
|
* Walk a tree and append `├── name` / `└── name` lines with proper continuation
|
|
95
|
-
* prefixes. Directories are rendered in cyan-bold; files in plain text.
|
|
131
|
+
* prefixes. Directories are rendered in cyan-bold; files in plain text. When a
|
|
132
|
+
* level was truncated, a dim trailing `+N more` line is appended at that level.
|
|
96
133
|
*/
|
|
97
|
-
function renderNodes(nodes: TreeNode[], prefix: string, out: string[]): void {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const last = i === sorted.length - 1;
|
|
134
|
+
function renderNodes(nodes: TreeNode[], prefix: string, out: string[], truncatedCount = 0): void {
|
|
135
|
+
nodes.forEach((node, i) => {
|
|
136
|
+
const last = i === nodes.length - 1 && truncatedCount === 0;
|
|
101
137
|
const branch = last ? "└── " : "├── ";
|
|
102
138
|
const label = node.is_file && !node.children?.length ? node.name : colors.cyan(colors.bold(node.name));
|
|
103
139
|
out.push(`${prefix}${branch}${label}`);
|
|
104
140
|
if (node.children?.length) {
|
|
105
|
-
renderNodes(node.children, prefix + (last ? " " : "│ "), out);
|
|
141
|
+
renderNodes(node.children, prefix + (last ? " " : "│ "), out, node.children_truncated ?? 0);
|
|
106
142
|
}
|
|
107
143
|
});
|
|
144
|
+
if (truncatedCount > 0) {
|
|
145
|
+
out.push(`${prefix}└── ${colors.dim(`+${truncatedCount} more`)}`);
|
|
146
|
+
}
|
|
108
147
|
}
|