agent-remnote 0.0.1 → 0.0.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/cli.js +2 -0
- package/dist/apps/cli/src/adapters/mcp.js +1 -0
- package/dist/apps/cli/src/commands/_enqueue.js +138 -0
- package/dist/apps/cli/src/commands/_shared.js +57 -0
- package/dist/apps/cli/src/commands/_tool.js +28 -0
- package/dist/apps/cli/src/commands/apply.js +81 -0
- package/dist/apps/cli/src/commands/config/index.js +3 -0
- package/dist/apps/cli/src/commands/config/print.js +28 -0
- package/dist/apps/cli/src/commands/daily/index.js +4 -0
- package/dist/apps/cli/src/commands/daily/summary.js +25 -0
- package/dist/apps/cli/src/commands/daily/write.js +145 -0
- package/dist/apps/cli/src/commands/db/backups.js +23 -0
- package/dist/apps/cli/src/commands/db/index.js +4 -0
- package/dist/apps/cli/src/commands/db/recent.js +178 -0
- package/dist/apps/cli/src/commands/doctor.js +124 -0
- package/dist/apps/cli/src/commands/index.js +73 -0
- package/dist/apps/cli/src/commands/ops/index.js +4 -0
- package/dist/apps/cli/src/commands/ops/list.js +12 -0
- package/dist/apps/cli/src/commands/ops/schema.js +77 -0
- package/dist/apps/cli/src/commands/queue/enqueue.js +73 -0
- package/dist/apps/cli/src/commands/queue/index.js +5 -0
- package/dist/apps/cli/src/commands/queue/inspect.js +26 -0
- package/dist/apps/cli/src/commands/queue/stats.js +14 -0
- package/dist/apps/cli/src/commands/read/by-reference.js +35 -0
- package/dist/apps/cli/src/commands/read/connections.js +15 -0
- package/dist/apps/cli/src/commands/read/index.js +21 -0
- package/dist/apps/cli/src/commands/read/inspect.js +34 -0
- package/dist/apps/cli/src/commands/read/outline.js +59 -0
- package/dist/apps/cli/src/commands/read/query.js +95 -0
- package/dist/apps/cli/src/commands/read/references.js +41 -0
- package/dist/apps/cli/src/commands/read/resolve-ref.js +32 -0
- package/dist/apps/cli/src/commands/read/search.js +40 -0
- package/dist/apps/cli/src/commands/read/table.js +32 -0
- package/dist/apps/cli/src/commands/todos/index.js +3 -0
- package/dist/apps/cli/src/commands/todos/list.js +33 -0
- package/dist/apps/cli/src/commands/topic/index.js +3 -0
- package/dist/apps/cli/src/commands/topic/summary.js +44 -0
- package/dist/apps/cli/src/commands/wechat/index.js +3 -0
- package/dist/apps/cli/src/commands/wechat/outline.js +430 -0
- package/dist/apps/cli/src/commands/write/bullet.js +76 -0
- package/dist/apps/cli/src/commands/write/index.js +4 -0
- package/dist/apps/cli/src/commands/write/md.js +91 -0
- package/dist/apps/cli/src/commands/ws/_shared.js +129 -0
- package/dist/apps/cli/src/commands/ws/ensure.js +22 -0
- package/dist/apps/cli/src/commands/ws/health.js +15 -0
- package/dist/apps/cli/src/commands/ws/index.js +21 -0
- package/dist/apps/cli/src/commands/ws/logs.js +95 -0
- package/dist/apps/cli/src/commands/ws/restart.js +73 -0
- package/dist/apps/cli/src/commands/ws/serve.js +52 -0
- package/dist/apps/cli/src/commands/ws/start.js +70 -0
- package/dist/apps/cli/src/commands/ws/status.js +60 -0
- package/dist/apps/cli/src/commands/ws/stop.js +59 -0
- package/dist/apps/cli/src/commands/ws/trigger.js +20 -0
- package/dist/apps/cli/src/main.js +79 -0
- package/dist/apps/cli/src/services/AppConfig.js +3 -0
- package/dist/apps/cli/src/services/Config.js +91 -0
- package/dist/apps/cli/src/services/DaemonFiles.js +91 -0
- package/dist/apps/cli/src/services/Errors.js +49 -0
- package/dist/apps/cli/src/services/Output.js +16 -0
- package/dist/apps/cli/src/services/Payload.js +90 -0
- package/dist/apps/cli/src/services/Process.js +94 -0
- package/dist/apps/cli/src/services/Queue.js +120 -0
- package/dist/apps/cli/src/services/RefResolver.js +111 -0
- package/dist/apps/cli/src/services/RemDb.js +35 -0
- package/dist/apps/cli/src/services/WsClient.js +170 -0
- package/dist/apps/cli/tests/apply.contract.test.js +31 -0
- package/dist/apps/cli/tests/db-recent.contract.test.js +22 -0
- package/dist/apps/cli/tests/help.contract.test.js +30 -0
- package/dist/apps/cli/tests/helpers/runCli.js +45 -0
- package/dist/apps/cli/tests/ids-output.contract.test.js +30 -0
- package/dist/apps/cli/tests/payload-stdin.contract.test.js +15 -0
- package/dist/apps/cli/tests/read-search.contract.test.js +22 -0
- package/dist/apps/cli/tests/ws-health.contract.test.js +36 -0
- package/dist/apps/cli/vitest.config.js +7 -0
- package/dist/main.js +100985 -0
- package/dist/packages/mcp/src/public.js +18 -0
- package/dist/packages/mcp/src/queue/dao.js +165 -0
- package/dist/packages/mcp/src/queue/db.js +26 -0
- package/dist/packages/mcp/src/tools/executeSearchQuery.js +914 -0
- package/dist/packages/mcp/src/tools/findRemsByReference.js +447 -0
- package/dist/packages/mcp/src/tools/getRemConnections.js +566 -0
- package/dist/packages/mcp/src/tools/inspectRemDoc.js +60 -0
- package/dist/packages/mcp/src/tools/listRemBackups.js +35 -0
- package/dist/packages/mcp/src/tools/listRemReferences.js +421 -0
- package/dist/packages/mcp/src/tools/listSupportedOps.js +41 -0
- package/dist/packages/mcp/src/tools/listTodos.js +815 -0
- package/dist/packages/mcp/src/tools/outlineRemSubtree.js +203 -0
- package/dist/packages/mcp/src/tools/readRemTable.js +252 -0
- package/dist/packages/mcp/src/tools/resolveRemReference.js +174 -0
- package/dist/packages/mcp/src/tools/searchQueryTypes.js +127 -0
- package/dist/packages/mcp/src/tools/searchRemOverview.js +422 -0
- package/dist/packages/mcp/src/tools/searchUtils.js +32 -0
- package/dist/packages/mcp/src/tools/shared.js +393 -0
- package/dist/packages/mcp/src/tools/summarizeDailyNotes.js +221 -0
- package/dist/packages/mcp/src/tools/summarizeTopicActivity.js +605 -0
- package/dist/packages/mcp/src/tools/timeFilters.js +130 -0
- package/dist/packages/mcp/src/ws/bridge.js +377 -0
- package/package.json +40 -8
- package/README.md +0 -3
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -5
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { buildGuidedResponse, summarizeKey, safeJsonParse, withResolvedDatabase, parseOrThrow, SYSTEM_REM_IDS, SYSTEM_REM_KEYS, } from "./shared.js";
|
|
3
|
+
const inputShape = {
|
|
4
|
+
id: z.string().min(1, "id 必填").describe("根 Rem ID"),
|
|
5
|
+
maxDepth: z
|
|
6
|
+
.number()
|
|
7
|
+
.int()
|
|
8
|
+
.min(0)
|
|
9
|
+
.max(10)
|
|
10
|
+
.optional()
|
|
11
|
+
.describe("最大展开深度(默认 5)"),
|
|
12
|
+
includeEmpty: z.boolean().optional().describe("是否包含空白节点(默认 false)"),
|
|
13
|
+
expandReferences: z.boolean().optional().describe("是否展开 [[引用]] 文本(默认 true)"),
|
|
14
|
+
maxReferenceDepth: z
|
|
15
|
+
.number()
|
|
16
|
+
.int()
|
|
17
|
+
.min(0)
|
|
18
|
+
.max(5)
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("引用展开的最大深度(默认 1)"),
|
|
21
|
+
format: z
|
|
22
|
+
.enum(["json", "markdown"]).optional().describe("返回格式:json/markdown(默认 markdown)"),
|
|
23
|
+
dbPath: z.string().optional().describe("数据库文件路径(默认自动发现)"),
|
|
24
|
+
startOffset: z
|
|
25
|
+
.number()
|
|
26
|
+
.int()
|
|
27
|
+
.min(0)
|
|
28
|
+
.optional()
|
|
29
|
+
.describe("分页起始节点序号(默认 0)"),
|
|
30
|
+
maxNodes: z
|
|
31
|
+
.number()
|
|
32
|
+
.int()
|
|
33
|
+
.min(1)
|
|
34
|
+
.max(200)
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("本次返回的最大节点数(默认 80)"),
|
|
37
|
+
excludeProperties: z
|
|
38
|
+
.boolean()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("是否排除表格属性/选项节点(默认 false)"),
|
|
41
|
+
detail: z
|
|
42
|
+
.boolean()
|
|
43
|
+
.optional()
|
|
44
|
+
.describe("json 格式时是否包含完整节点信息(默认 false)"),
|
|
45
|
+
};
|
|
46
|
+
export const outlineRemSubtreeSchema = z.object(inputShape);
|
|
47
|
+
export async function executeOutlineRemSubtree(params) {
|
|
48
|
+
const parsed = parseOrThrow(outlineRemSubtreeSchema, params, { label: "outline_rem_subtree" });
|
|
49
|
+
const maxDepth = parsed.maxDepth ?? 5;
|
|
50
|
+
const includeEmpty = parsed.includeEmpty ?? false;
|
|
51
|
+
const expandReferences = parsed.expandReferences ?? true;
|
|
52
|
+
const maxReferenceDepth = parsed.maxReferenceDepth ?? 1;
|
|
53
|
+
const format = parsed.format ?? "markdown";
|
|
54
|
+
const offset = parsed.startOffset ?? 0;
|
|
55
|
+
const maxNodes = parsed.maxNodes ?? 80;
|
|
56
|
+
const excludeProperties = parsed.excludeProperties ?? false;
|
|
57
|
+
const detail = parsed.detail ?? false;
|
|
58
|
+
const { result, info } = await withResolvedDatabase(parsed.dbPath, async (db) => {
|
|
59
|
+
const nodes = fetchOutlineNodes(db, {
|
|
60
|
+
rootId: parsed.id,
|
|
61
|
+
maxDepth,
|
|
62
|
+
includeEmpty,
|
|
63
|
+
expandReferences,
|
|
64
|
+
maxReferenceDepth,
|
|
65
|
+
excludeProperties,
|
|
66
|
+
});
|
|
67
|
+
const sliced = nodes.slice(offset, offset + maxNodes);
|
|
68
|
+
const markdown = format === "markdown" ? toMarkdown(sliced) : undefined;
|
|
69
|
+
return {
|
|
70
|
+
allNodes: nodes,
|
|
71
|
+
sliced,
|
|
72
|
+
markdown,
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
const title = result.allNodes[0]?.text ?? parsed.id;
|
|
76
|
+
const nodeCount = result.sliced.length;
|
|
77
|
+
const total = result.allNodes.length;
|
|
78
|
+
const hasMore = offset + nodeCount < total;
|
|
79
|
+
const response = {
|
|
80
|
+
dbPath: info.dbPath,
|
|
81
|
+
resolution: info.source,
|
|
82
|
+
dirName: info.dirName,
|
|
83
|
+
rootId: parsed.id,
|
|
84
|
+
title,
|
|
85
|
+
maxDepth,
|
|
86
|
+
nodeCount,
|
|
87
|
+
totalNodeCount: total,
|
|
88
|
+
offset,
|
|
89
|
+
maxNodes,
|
|
90
|
+
hasMore,
|
|
91
|
+
nextOffset: hasMore ? offset + nodeCount : null,
|
|
92
|
+
excludeProperties,
|
|
93
|
+
markdown: result.markdown,
|
|
94
|
+
};
|
|
95
|
+
if (format === "json") {
|
|
96
|
+
return {
|
|
97
|
+
...response,
|
|
98
|
+
tree: detail ? result.sliced : simplifyOutlineNodes(result.sliced),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (detail) {
|
|
102
|
+
return {
|
|
103
|
+
...response,
|
|
104
|
+
tree: result.sliced,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return response;
|
|
108
|
+
}
|
|
109
|
+
export function registerOutlineRemSubtree(server) {
|
|
110
|
+
server.tool("outline_rem_subtree", `<usecase>获取指定 Rem 的大纲视图。常用于阅读每日笔记或任意文档的结构。</usecase>
|
|
111
|
+
<instructions>
|
|
112
|
+
- 默认返回 Markdown,可通过 format=json 获取结构化节点。
|
|
113
|
+
- startOffset/maxNodes 用于分页浏览长文档;返回值会告知是否还有剩余内容。
|
|
114
|
+
- excludeProperties=true 时会跳过表格属性/选项节点,阅读文档结构更清爽。
|
|
115
|
+
- 工具自动过滤每日模板节点(Date String、timestamp、Status 等)。
|
|
116
|
+
- 如需进一步展开引用,请结合 resolve_rem_reference。
|
|
117
|
+
- 若需完整的结构化节点信息,请设置 detail=true;默认仅输出与 UI 相近的 Markdown。
|
|
118
|
+
</instructions>`, inputShape, async (input) => {
|
|
119
|
+
const parsed = parseOrThrow(outlineRemSubtreeSchema, input, { label: "outline_rem_subtree" });
|
|
120
|
+
const result = await executeOutlineRemSubtree(parsed);
|
|
121
|
+
const suggestions = [
|
|
122
|
+
"如需展开引用内容,可调用 resolve_rem_reference(ids=[...])",
|
|
123
|
+
];
|
|
124
|
+
if (result.hasMore && result.nextOffset != null) {
|
|
125
|
+
suggestions.unshift(`继续阅读:再次调用 outline_rem_subtree 并设置 startOffset=${result.nextOffset}`);
|
|
126
|
+
}
|
|
127
|
+
const guidance = result.nodeCount > 0
|
|
128
|
+
? `生成 "${result.title}" 的大纲,本次返回 ${result.nodeCount} 个节点(总计 ${result.totalNodeCount},${result.hasMore ? "尚有剩余" : "全部读取"})。`
|
|
129
|
+
: `未找到可用节点("${result.title}" 可能为空或仅包含模板内容)。`;
|
|
130
|
+
return buildGuidedResponse({ guidance, ...result }, suggestions);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function fetchOutlineNodes(db, options) {
|
|
134
|
+
const stmt = db.prepare(`WITH RECURSIVE tree(id, depth, order_path) AS (
|
|
135
|
+
SELECT _id, 0 AS depth, COALESCE(json_extract(doc, '$.f'), '')
|
|
136
|
+
FROM quanta WHERE _id = @rootId
|
|
137
|
+
UNION ALL
|
|
138
|
+
SELECT child._id,
|
|
139
|
+
tree.depth + 1,
|
|
140
|
+
tree.order_path || char(0) || COALESCE(json_extract(child.doc, '$.f'), '')
|
|
141
|
+
FROM quanta child
|
|
142
|
+
JOIN tree ON json_extract(child.doc, '$.parent') = tree.id
|
|
143
|
+
WHERE tree.depth + 1 <= @maxDepth
|
|
144
|
+
)
|
|
145
|
+
SELECT tree.id,
|
|
146
|
+
tree.depth,
|
|
147
|
+
tree.order_path AS orderPath,
|
|
148
|
+
quanta.doc AS doc
|
|
149
|
+
FROM tree
|
|
150
|
+
JOIN quanta ON quanta._id = tree.id
|
|
151
|
+
ORDER BY tree.order_path`);
|
|
152
|
+
const rows = stmt.all({ rootId: options.rootId, maxDepth: options.maxDepth });
|
|
153
|
+
const nodes = [];
|
|
154
|
+
for (const row of rows) {
|
|
155
|
+
const doc = safeJsonParse(row.doc);
|
|
156
|
+
const rawKey = doc?.key;
|
|
157
|
+
const keySummary = summarizeKey(rawKey, db, {
|
|
158
|
+
expand: options.expandReferences,
|
|
159
|
+
maxDepth: options.maxReferenceDepth,
|
|
160
|
+
});
|
|
161
|
+
if (!options.includeEmpty && !keySummary.text && row.depth !== 0) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const isSystemRem = SYSTEM_REM_IDS.has(row.id) || SYSTEM_REM_KEYS.has(keySummary.text);
|
|
165
|
+
if (isSystemRem) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (options.excludeProperties) {
|
|
169
|
+
const rcrs = typeof doc?.rcrs === "string" ? doc.rcrs : null;
|
|
170
|
+
const rcre = typeof doc?.rcre === "string" ? doc.rcre : null;
|
|
171
|
+
if ((rcrs && rcrs.startsWith("t.")) || (rcre && rcre.startsWith("t."))) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
nodes.push({
|
|
176
|
+
id: row.id,
|
|
177
|
+
depth: row.depth,
|
|
178
|
+
sortKey: typeof doc?.f === "string" ? doc.f : null,
|
|
179
|
+
parentId: typeof doc?.parent === "string" ? doc.parent : null,
|
|
180
|
+
text: keySummary.text,
|
|
181
|
+
references: keySummary.references,
|
|
182
|
+
rawKey,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return nodes;
|
|
186
|
+
}
|
|
187
|
+
function simplifyOutlineNodes(nodes) {
|
|
188
|
+
return nodes.map((node) => ({
|
|
189
|
+
id: node.id,
|
|
190
|
+
depth: node.depth,
|
|
191
|
+
text: node.text,
|
|
192
|
+
references: node.references,
|
|
193
|
+
}));
|
|
194
|
+
}
|
|
195
|
+
function toMarkdown(nodes) {
|
|
196
|
+
return nodes
|
|
197
|
+
.map((node) => {
|
|
198
|
+
const indent = " ".repeat(node.depth);
|
|
199
|
+
const text = node.text || "(空)";
|
|
200
|
+
return `${indent}- ${text}`;
|
|
201
|
+
})
|
|
202
|
+
.join("\n");
|
|
203
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { buildGuidedResponse, summarizeKey, safeJsonParse, withResolvedDatabase, parseOrThrow, } from "./shared.js";
|
|
3
|
+
const inputShape = {
|
|
4
|
+
tagId: z
|
|
5
|
+
.string()
|
|
6
|
+
.min(1, "tagId 必填")
|
|
7
|
+
.describe("表格标签 Rem ID(表头标签)"),
|
|
8
|
+
limit: z
|
|
9
|
+
.number()
|
|
10
|
+
.int()
|
|
11
|
+
.min(1)
|
|
12
|
+
.max(200)
|
|
13
|
+
.optional()
|
|
14
|
+
.describe("每次返回的行数上限(默认 50)"),
|
|
15
|
+
offset: z
|
|
16
|
+
.number()
|
|
17
|
+
.int()
|
|
18
|
+
.min(0)
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("分页偏移量(默认 0)"),
|
|
21
|
+
includeOptions: z
|
|
22
|
+
.boolean()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("是否返回每个属性的全部选项信息及计数(默认 false)"),
|
|
25
|
+
dbPath: z
|
|
26
|
+
.string()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("数据库文件路径(默认自动发现)"),
|
|
29
|
+
};
|
|
30
|
+
export const readRemTableSchema = z.object(inputShape);
|
|
31
|
+
export async function executeReadRemTable(params) {
|
|
32
|
+
const parsed = parseOrThrow(readRemTableSchema, params, { label: "read_table_rem" });
|
|
33
|
+
const limit = parsed.limit ?? 50;
|
|
34
|
+
const offset = parsed.offset ?? 0;
|
|
35
|
+
const includeOptions = parsed.includeOptions ?? false;
|
|
36
|
+
const { result, info } = await withResolvedDatabase(parsed.dbPath, async (db) => {
|
|
37
|
+
const tagRow = db
|
|
38
|
+
.prepare("SELECT doc FROM quanta WHERE _id = ?")
|
|
39
|
+
.get(parsed.tagId);
|
|
40
|
+
if (!tagRow) {
|
|
41
|
+
throw new Error(`未找到该表格标签 Rem(tagId=${parsed.tagId}),请确认 ID 是否正确`);
|
|
42
|
+
}
|
|
43
|
+
const tagDoc = safeJsonParse(tagRow.doc);
|
|
44
|
+
const tagName = summarizeKey(tagDoc?.key, db, { expand: false, maxDepth: 0 }).text || parsed.tagId;
|
|
45
|
+
const propertyContext = loadProperties(db, parsed.tagId);
|
|
46
|
+
const rowContext = loadRows(db, parsed.tagId, { limit, offset });
|
|
47
|
+
const propertyValuesByRow = buildPropertyValueIndex(propertyContext);
|
|
48
|
+
const rows = rowContext.rows.map((row) => {
|
|
49
|
+
const rowDoc = safeJsonParse(row.doc);
|
|
50
|
+
const summary = summarizeKey(rowDoc?.key, db, { expand: false, maxDepth: 0 });
|
|
51
|
+
const values = {};
|
|
52
|
+
const propertyLookup = propertyValuesByRow.get(row.id);
|
|
53
|
+
if (propertyLookup) {
|
|
54
|
+
for (const [propertyId, optionIds] of propertyLookup.entries()) {
|
|
55
|
+
const optionNames = optionIds
|
|
56
|
+
.map((optionId) => propertyContext.optionNameById.get(optionId) ?? optionId)
|
|
57
|
+
.filter(Boolean);
|
|
58
|
+
values[propertyId] = {
|
|
59
|
+
optionIds,
|
|
60
|
+
optionNames,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
id: row.id,
|
|
66
|
+
title: summary.text || "",
|
|
67
|
+
options: values,
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
const hasMore = offset + rows.length < rowContext.total;
|
|
71
|
+
const properties = propertyContext.properties.map((property) => ({
|
|
72
|
+
id: property.id,
|
|
73
|
+
name: property.name,
|
|
74
|
+
rawType: property.rawType,
|
|
75
|
+
kind: property.kind,
|
|
76
|
+
optionCount: property.options.length,
|
|
77
|
+
options: includeOptions
|
|
78
|
+
? property.options.map((option) => ({
|
|
79
|
+
id: option.id,
|
|
80
|
+
name: option.name,
|
|
81
|
+
rowCount: option.rowIds.size,
|
|
82
|
+
}))
|
|
83
|
+
: undefined,
|
|
84
|
+
}));
|
|
85
|
+
return {
|
|
86
|
+
tagId: parsed.tagId,
|
|
87
|
+
tagName,
|
|
88
|
+
properties,
|
|
89
|
+
propertyCount: properties.length,
|
|
90
|
+
rows,
|
|
91
|
+
rowCount: rows.length,
|
|
92
|
+
totalRows: rowContext.total,
|
|
93
|
+
offset,
|
|
94
|
+
limit,
|
|
95
|
+
hasMore,
|
|
96
|
+
nextOffset: hasMore ? offset + rows.length : null,
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
const suggestions = [];
|
|
100
|
+
suggestions.push("若需要查看行的完整大纲,可结合 outline_rem_subtree id=<rowId>");
|
|
101
|
+
if (result.hasMore && result.nextOffset != null) {
|
|
102
|
+
suggestions.unshift(`还有更多行,可再次调用 read_table_rem 并设置 offset=${result.nextOffset}`);
|
|
103
|
+
}
|
|
104
|
+
if (result.properties.length === 0) {
|
|
105
|
+
suggestions.push("未检测到表格属性,检查 tagId 是否为表格标签或是否使用 Powerup 表格");
|
|
106
|
+
}
|
|
107
|
+
const guidance = `已解析标签 "${result.tagName}" 的表格,本次返回 ${result.rowCount} 行(总计 ${result.totalRows})。`;
|
|
108
|
+
return buildGuidedResponse({ guidance, ...result, dbPath: info.dbPath, resolution: info.source, dirName: info.dirName }, suggestions);
|
|
109
|
+
}
|
|
110
|
+
export function registerReadRemTable(server) {
|
|
111
|
+
server.tool("read_table_rem", `<usecase>读取基于 Tag 的表格数据,列出属性与当前页的行记录。</usecase>
|
|
112
|
+
<instructions>
|
|
113
|
+
- 提供表格 Tag 的 Rem ID(通常是表头标签)。
|
|
114
|
+
- 默认返回前 50 行,可通过 limit/offset 分页。
|
|
115
|
+
- includeOptions=true 时会输出每个属性的所有选项及引用行数。
|
|
116
|
+
- 如果表格未检测到属性,请确认是否为 RemNote 表格或自定义结构。
|
|
117
|
+
</instructions>`, inputShape, async (input) => {
|
|
118
|
+
const parsed = parseOrThrow(readRemTableSchema, input, { label: "read_table_rem" });
|
|
119
|
+
return executeReadRemTable(parsed);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
function loadProperties(db, tagId) {
|
|
123
|
+
const stmt = db.prepare(`SELECT _id AS id,
|
|
124
|
+
doc,
|
|
125
|
+
json_extract(doc, '$.rcrs') AS rawType
|
|
126
|
+
FROM quanta
|
|
127
|
+
WHERE json_extract(doc, '$.parent') = @tagId
|
|
128
|
+
AND json_extract(doc, '$.rcrs') IS NOT NULL
|
|
129
|
+
ORDER BY json_extract(doc, '$.f')`);
|
|
130
|
+
const optionStmt = db.prepare(`SELECT _id AS id,
|
|
131
|
+
doc,
|
|
132
|
+
json_extract(doc, '$.rcre') AS rawOptionType
|
|
133
|
+
FROM quanta
|
|
134
|
+
WHERE json_extract(doc, '$.parent') = @parent
|
|
135
|
+
AND json_extract(doc, '$.rcre') IS NOT NULL
|
|
136
|
+
ORDER BY json_extract(doc, '$.f')`);
|
|
137
|
+
const properties = [];
|
|
138
|
+
const optionNameById = new Map();
|
|
139
|
+
const propertyRows = stmt.all({ tagId });
|
|
140
|
+
for (const row of propertyRows) {
|
|
141
|
+
const doc = safeJsonParse(row.doc);
|
|
142
|
+
const rawType = typeof row.rawType === "string" ? row.rawType : null;
|
|
143
|
+
const typeCode = rawType ? rawType.split(".")[1] ?? null : null;
|
|
144
|
+
const kind = mapPropertyType(typeCode);
|
|
145
|
+
const summary = summarizeKey(doc?.key, db, { expand: false, maxDepth: 0 });
|
|
146
|
+
const options = [];
|
|
147
|
+
const optionRows = optionStmt.all({ parent: row.id });
|
|
148
|
+
for (const optionRow of optionRows) {
|
|
149
|
+
const optionDoc = safeJsonParse(optionRow.doc);
|
|
150
|
+
const optionSummary = summarizeKey(optionDoc?.key, db, { expand: false, maxDepth: 0 });
|
|
151
|
+
const pdRaw = optionDoc?.pd;
|
|
152
|
+
const pdObject = typeof pdRaw === "string" ? safeJsonParse(pdRaw) : pdRaw;
|
|
153
|
+
const rowIds = new Set();
|
|
154
|
+
if (pdObject && typeof pdObject === "object") {
|
|
155
|
+
for (const key of Object.keys(pdObject)) {
|
|
156
|
+
if (key) {
|
|
157
|
+
rowIds.add(key);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
options.push({
|
|
162
|
+
id: optionRow.id,
|
|
163
|
+
name: optionSummary.text || optionRow.id,
|
|
164
|
+
rowIds,
|
|
165
|
+
});
|
|
166
|
+
optionNameById.set(optionRow.id, optionSummary.text || optionRow.id);
|
|
167
|
+
}
|
|
168
|
+
properties.push({
|
|
169
|
+
id: row.id,
|
|
170
|
+
name: summary.text || row.id,
|
|
171
|
+
rawType,
|
|
172
|
+
kind,
|
|
173
|
+
options,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
properties,
|
|
178
|
+
optionNameById,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function loadRows(db, tagId, options) {
|
|
182
|
+
const countRow = db
|
|
183
|
+
.prepare(`SELECT COUNT(*) AS total
|
|
184
|
+
FROM (
|
|
185
|
+
SELECT q._id
|
|
186
|
+
FROM quanta q
|
|
187
|
+
JOIN json_each(q.doc, '$.tp') jt
|
|
188
|
+
ON 1 = 1
|
|
189
|
+
WHERE jt.key = @tagId
|
|
190
|
+
GROUP BY q._id
|
|
191
|
+
)`)
|
|
192
|
+
.get({ tagId });
|
|
193
|
+
const rows = db
|
|
194
|
+
.prepare(`SELECT q._id AS id,
|
|
195
|
+
q.doc AS doc
|
|
196
|
+
FROM quanta q
|
|
197
|
+
JOIN json_each(q.doc, '$.tp') jt
|
|
198
|
+
ON 1 = 1
|
|
199
|
+
WHERE jt.key = @tagId
|
|
200
|
+
GROUP BY q._id
|
|
201
|
+
ORDER BY COALESCE(json_extract(q.doc, '$.u'), json_extract(q.doc, '$.createdAt'), 0) DESC
|
|
202
|
+
LIMIT @limit OFFSET @offset`)
|
|
203
|
+
.all({ tagId, limit: options.limit, offset: options.offset });
|
|
204
|
+
return {
|
|
205
|
+
rows,
|
|
206
|
+
total: countRow?.total ?? 0,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function buildPropertyValueIndex(context) {
|
|
210
|
+
const index = new Map();
|
|
211
|
+
for (const property of context.properties) {
|
|
212
|
+
for (const option of property.options) {
|
|
213
|
+
for (const rowId of option.rowIds) {
|
|
214
|
+
if (!index.has(rowId)) {
|
|
215
|
+
index.set(rowId, new Map());
|
|
216
|
+
}
|
|
217
|
+
const propertyMap = index.get(rowId);
|
|
218
|
+
if (!propertyMap.has(property.id)) {
|
|
219
|
+
propertyMap.set(property.id, []);
|
|
220
|
+
}
|
|
221
|
+
propertyMap.get(property.id).push(option.id);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// Ensure option list ordering is stable
|
|
226
|
+
for (const propertyMap of index.values()) {
|
|
227
|
+
for (const entry of propertyMap.values()) {
|
|
228
|
+
entry.sort();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return index;
|
|
232
|
+
}
|
|
233
|
+
function mapPropertyType(code) {
|
|
234
|
+
if (!code)
|
|
235
|
+
return "unknown";
|
|
236
|
+
switch (code) {
|
|
237
|
+
case "s":
|
|
238
|
+
return "select";
|
|
239
|
+
case "m":
|
|
240
|
+
return "multi_select";
|
|
241
|
+
case "t":
|
|
242
|
+
return "text";
|
|
243
|
+
case "n":
|
|
244
|
+
return "number";
|
|
245
|
+
case "d":
|
|
246
|
+
return "date";
|
|
247
|
+
case "c":
|
|
248
|
+
return "checkbox";
|
|
249
|
+
default:
|
|
250
|
+
return `unknown(${code})`;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { buildGuidedResponse, summarizeKey, safeJsonParse, withResolvedDatabase, parseOrThrow, } from "./shared.js";
|
|
3
|
+
const inputShape = {
|
|
4
|
+
ids: z
|
|
5
|
+
.array(z.string().min(1))
|
|
6
|
+
.min(1, "ids 必填,至少 1 个")
|
|
7
|
+
.describe("要解析的引用 Rem ID 列表"),
|
|
8
|
+
dbPath: z
|
|
9
|
+
.string()
|
|
10
|
+
.optional()
|
|
11
|
+
.describe("数据库文件路径(默认自动发现)"),
|
|
12
|
+
expandReferences: z
|
|
13
|
+
.boolean()
|
|
14
|
+
.optional()
|
|
15
|
+
.describe("是否展开引用链文本(默认 true)"),
|
|
16
|
+
maxReferenceDepth: z
|
|
17
|
+
.number()
|
|
18
|
+
.int()
|
|
19
|
+
.min(0)
|
|
20
|
+
.max(5)
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("展开引用的最大深度(默认 1)"),
|
|
23
|
+
detail: z.boolean().optional().describe("是否返回原始 key/doc 等详细信息"),
|
|
24
|
+
};
|
|
25
|
+
export const resolveRemReferenceSchema = z.object(inputShape);
|
|
26
|
+
export async function executeResolveRemReference(params) {
|
|
27
|
+
const parsed = parseOrThrow(resolveRemReferenceSchema, params, { label: "resolve_rem_reference" });
|
|
28
|
+
const expand = parsed.expandReferences ?? true;
|
|
29
|
+
const maxDepth = parsed.maxReferenceDepth ?? 1;
|
|
30
|
+
const detail = parsed.detail ?? false;
|
|
31
|
+
const { result, info } = await withResolvedDatabase(parsed.dbPath, async (db) => {
|
|
32
|
+
const stmt = db.prepare("SELECT doc FROM quanta WHERE _id = ?");
|
|
33
|
+
const referenceTexts = new Map();
|
|
34
|
+
const referenceSources = new Map();
|
|
35
|
+
const details = parsed.ids.map((id) => {
|
|
36
|
+
const row = stmt.get(id);
|
|
37
|
+
if (!row) {
|
|
38
|
+
referenceTexts.set(id, null);
|
|
39
|
+
return {
|
|
40
|
+
id,
|
|
41
|
+
found: false,
|
|
42
|
+
text: "",
|
|
43
|
+
references: [],
|
|
44
|
+
rawKey: undefined,
|
|
45
|
+
rawDoc: undefined,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const doc = safeJsonParse(row.doc);
|
|
49
|
+
const keySummary = summarizeKey(doc?.key, db, { expand, maxDepth });
|
|
50
|
+
referenceTexts.set(id, keySummary.text ?? null);
|
|
51
|
+
for (const ref of keySummary.references) {
|
|
52
|
+
if (!referenceSources.has(ref)) {
|
|
53
|
+
referenceSources.set(ref, new Set([id]));
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
referenceSources.get(ref).add(id);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
id,
|
|
61
|
+
found: true,
|
|
62
|
+
text: keySummary.text,
|
|
63
|
+
references: keySummary.references,
|
|
64
|
+
rawKey: doc?.key,
|
|
65
|
+
rawDoc: doc,
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
const nestedTargets = [];
|
|
69
|
+
for (const refId of referenceSources.keys()) {
|
|
70
|
+
if (!referenceTexts.has(refId)) {
|
|
71
|
+
nestedTargets.push(refId);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const limit = Math.min(nestedTargets.length, 200);
|
|
75
|
+
for (let index = 0; index < limit; index++) {
|
|
76
|
+
const refId = nestedTargets[index];
|
|
77
|
+
const row = stmt.get(refId);
|
|
78
|
+
if (!row) {
|
|
79
|
+
referenceTexts.set(refId, null);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const doc = safeJsonParse(row.doc);
|
|
83
|
+
const summary = summarizeKey(doc?.key, db, { expand: false, maxDepth: 0 });
|
|
84
|
+
referenceTexts.set(refId, summary.text ?? null);
|
|
85
|
+
}
|
|
86
|
+
const referenceTextRecord = {};
|
|
87
|
+
for (const [id, text] of referenceTexts.entries()) {
|
|
88
|
+
referenceTextRecord[id] = text ?? null;
|
|
89
|
+
}
|
|
90
|
+
const referenceSourceRecord = {};
|
|
91
|
+
for (const [id, sources] of referenceSources.entries()) {
|
|
92
|
+
referenceSourceRecord[id] = Array.from(sources);
|
|
93
|
+
}
|
|
94
|
+
return { details, referenceTexts: referenceTextRecord, referenceSources: referenceSourceRecord };
|
|
95
|
+
});
|
|
96
|
+
const simplified = detail
|
|
97
|
+
? result.details
|
|
98
|
+
: result.details.map((item) => ({
|
|
99
|
+
id: item.id,
|
|
100
|
+
found: item.found,
|
|
101
|
+
text: item.text,
|
|
102
|
+
references: item.references,
|
|
103
|
+
}));
|
|
104
|
+
const markdown = buildResolvedMarkdown(result.details);
|
|
105
|
+
const referenceIndex = buildReferenceIndex(result.details, result.referenceTexts, result.referenceSources);
|
|
106
|
+
return {
|
|
107
|
+
dbPath: info.dbPath,
|
|
108
|
+
resolution: info.source,
|
|
109
|
+
dirName: info.dirName,
|
|
110
|
+
count: simplified.length,
|
|
111
|
+
results: simplified,
|
|
112
|
+
markdown,
|
|
113
|
+
referenceIndex,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function buildResolvedMarkdown(details) {
|
|
117
|
+
if (!details || details.length === 0) {
|
|
118
|
+
return "未解析到任何引用。";
|
|
119
|
+
}
|
|
120
|
+
const lines = ["# 引用解析结果"];
|
|
121
|
+
for (const item of details) {
|
|
122
|
+
const status = item.found ? "" : "(未找到)";
|
|
123
|
+
lines.push(`- **${item.id}**${status}`);
|
|
124
|
+
if (item.text) {
|
|
125
|
+
lines.push(` - 文本:${item.text}`);
|
|
126
|
+
}
|
|
127
|
+
if (item.references.length > 0) {
|
|
128
|
+
lines.push(` - 下游引用:${item.references.join(", ")}`);
|
|
129
|
+
}
|
|
130
|
+
if (!item.found && item.references.length === 0 && !item.text) {
|
|
131
|
+
lines.push(" - 未检索到内容");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return lines.join("\n");
|
|
135
|
+
}
|
|
136
|
+
function buildReferenceIndex(details, texts, sources) {
|
|
137
|
+
const map = {};
|
|
138
|
+
const ensureEntry = (id) => {
|
|
139
|
+
if (!map[id]) {
|
|
140
|
+
map[id] = {
|
|
141
|
+
text: texts[id] ?? null,
|
|
142
|
+
sources: Array.from(new Set(sources[id] ?? [])),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
map[id].text ??= texts[id] ?? null;
|
|
147
|
+
const merged = new Set([...(map[id].sources ?? []), ...(sources[id] ?? [])]);
|
|
148
|
+
map[id].sources = Array.from(merged);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
for (const detail of details) {
|
|
152
|
+
ensureEntry(detail.id);
|
|
153
|
+
map[detail.id].text ??= detail.text ?? null;
|
|
154
|
+
for (const ref of detail.references) {
|
|
155
|
+
ensureEntry(ref);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return map;
|
|
159
|
+
}
|
|
160
|
+
export function registerResolveRemReference(server) {
|
|
161
|
+
server.tool("resolve_rem_reference", `<usecase>解析 Rem 引用,获取引用文本或继续展开下一层。</usecase>
|
|
162
|
+
<instructions>
|
|
163
|
+
- 传入 Rem ID 列表。默认会展开一层引用;可通过 \\"maxReferenceDepth\\" 调整。
|
|
164
|
+
- 通常结合 outline_rem_subtree 的输出中的 {ref:...} 再调用本工具。
|
|
165
|
+
</instructions>`, inputShape, async (input) => {
|
|
166
|
+
const parsed = parseOrThrow(resolveRemReferenceSchema, input, { label: "resolve_rem_reference" });
|
|
167
|
+
const result = await executeResolveRemReference(parsed);
|
|
168
|
+
const guidance = `已解析 ${result.results.length} 条引用。`;
|
|
169
|
+
const suggestions = [
|
|
170
|
+
"若需要查看引用上下文,可调用 outline_rem_subtree(可结合 startOffset 分页)",
|
|
171
|
+
];
|
|
172
|
+
return buildGuidedResponse({ guidance, ...result }, suggestions);
|
|
173
|
+
});
|
|
174
|
+
}
|