dsh-tiddlywiki 0.4.0 → 0.5.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 +33 -20
- package/lib/client.bundle.js +899 -384
- package/lib/client.js +899 -384
- package/lib/index.js +663 -29
- package/lib/index.js.map +1 -1
- package/package.json +2 -2
- package/src/client/index.ts +10 -4
- package/src/client/knowledge-fab.ts +232 -0
- package/src/client/markdown-editor.ts +5 -0
- package/src/client/note-widget.ts +721 -475
- package/src/client/panel.ts +7 -56
- package/src/client/settings-page.ts +3 -3
- package/src/client/styles.ts +110 -0
- package/src/client/sync-button.ts +191 -188
- package/src/host/git.ts +21 -0
- package/src/host/routes.ts +65 -0
- package/src/host/seed-notes.ts +7 -5
- package/src/host/tools.ts +589 -281
- package/src/host/tw-api.ts +80 -6
- package/src/index.ts +12 -4
- package/src/sdk.ts +8 -2
package/lib/index.js
CHANGED
|
@@ -275,6 +275,47 @@ var GitFace = class {
|
|
|
275
275
|
message: (r.stderr.trim() || r.stdout.trim()).slice(0, 500)
|
|
276
276
|
};
|
|
277
277
|
}
|
|
278
|
+
/** `git fetch` (no remote configured → failure reported by the caller). */
|
|
279
|
+
async fetch(dir) {
|
|
280
|
+
const r = await this.exec(["fetch"], {
|
|
281
|
+
cwd: dir,
|
|
282
|
+
timeout: HEAVY_TIMEOUT_MS
|
|
283
|
+
});
|
|
284
|
+
return r.ok ? {
|
|
285
|
+
ok: true,
|
|
286
|
+
message: r.stdout.trim() || "fetch ok"
|
|
287
|
+
} : {
|
|
288
|
+
ok: false,
|
|
289
|
+
message: (r.stderr.trim() || r.stdout.trim()).slice(0, 500)
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Restore the given files from the freshly fetched remote HEAD (FETCH_HEAD)
|
|
294
|
+
* into the working tree + index — the "keep remote version" half of
|
|
295
|
+
* tiddler-granular conflict resolution. Callers must `git fetch` first.
|
|
296
|
+
*/
|
|
297
|
+
async checkoutFetchHead(dir, files) {
|
|
298
|
+
if (files.length === 0) return {
|
|
299
|
+
ok: true,
|
|
300
|
+
message: "no files given"
|
|
301
|
+
};
|
|
302
|
+
const r = await this.exec([
|
|
303
|
+
"checkout",
|
|
304
|
+
"FETCH_HEAD",
|
|
305
|
+
"--",
|
|
306
|
+
...files
|
|
307
|
+
], {
|
|
308
|
+
cwd: dir,
|
|
309
|
+
timeout: HEAVY_TIMEOUT_MS
|
|
310
|
+
});
|
|
311
|
+
return r.ok ? {
|
|
312
|
+
ok: true,
|
|
313
|
+
message: `已从远端检出 ${files.length} 个文件`
|
|
314
|
+
} : {
|
|
315
|
+
ok: false,
|
|
316
|
+
message: (r.stderr.trim() || r.stdout.trim()).slice(0, 500)
|
|
317
|
+
};
|
|
318
|
+
}
|
|
278
319
|
/** Ensure `origin` points at `url` (add or set-url). */
|
|
279
320
|
async ensureRemote(dir, url) {
|
|
280
321
|
const cur = await this.exec([
|
|
@@ -701,6 +742,11 @@ function json$1(res, payload, status = 200) {
|
|
|
701
742
|
function pad(n) {
|
|
702
743
|
return n < 10 ? `0${n}` : String(n);
|
|
703
744
|
}
|
|
745
|
+
/** Flat one-line snippet for the recent-notes picker. */
|
|
746
|
+
function snippetOf$1(text, max = 120) {
|
|
747
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
748
|
+
return flat.length <= max ? flat : `${flat.slice(0, max)}…`;
|
|
749
|
+
}
|
|
704
750
|
/** Default note title: `YYYY-MM-DD HH:mm` (design doc D6). */
|
|
705
751
|
function timestampTitle(date = /* @__PURE__ */ new Date()) {
|
|
706
752
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
@@ -867,6 +913,85 @@ function registerRoutes(ctx, deps) {
|
|
|
867
913
|
}, 500);
|
|
868
914
|
}
|
|
869
915
|
};
|
|
916
|
+
/** Recent non-system tiddlers for the quick-note "最近" picker (newest first). */
|
|
917
|
+
const handleRecent = async (req, res) => {
|
|
918
|
+
const client = deps.getClient();
|
|
919
|
+
if (client === void 0) {
|
|
920
|
+
json$1(res, {
|
|
921
|
+
ok: false,
|
|
922
|
+
error: "wiki service is not running"
|
|
923
|
+
}, 503);
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
try {
|
|
927
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
928
|
+
const limitRaw = Number(url.searchParams.get("limit") ?? 15);
|
|
929
|
+
const limit = Number.isFinite(limitRaw) ? Math.max(1, Math.min(Math.floor(limitRaw), 200)) : 15;
|
|
930
|
+
json$1(res, {
|
|
931
|
+
ok: true,
|
|
932
|
+
limit,
|
|
933
|
+
items: (await client.recent(limit)).map((t) => ({
|
|
934
|
+
title: t.title,
|
|
935
|
+
tags: t.tags ?? [],
|
|
936
|
+
modified: typeof t.modified === "string" ? t.modified : null,
|
|
937
|
+
snippet: snippetOf$1(t.text ?? "")
|
|
938
|
+
}))
|
|
939
|
+
});
|
|
940
|
+
} catch (err) {
|
|
941
|
+
json$1(res, {
|
|
942
|
+
ok: false,
|
|
943
|
+
error: err instanceof Error ? err.message : String(err)
|
|
944
|
+
}, 500);
|
|
945
|
+
}
|
|
946
|
+
};
|
|
947
|
+
/** Full tiddler for the quick-note "最近" picker (load into the editor). */
|
|
948
|
+
const handleGet = async (req, res) => {
|
|
949
|
+
const client = deps.getClient();
|
|
950
|
+
if (client === void 0) {
|
|
951
|
+
json$1(res, {
|
|
952
|
+
ok: false,
|
|
953
|
+
error: "wiki service is not running"
|
|
954
|
+
}, 503);
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
try {
|
|
958
|
+
const title = new URL(req.url ?? "/", "http://127.0.0.1").searchParams.get("title") ?? "";
|
|
959
|
+
if (title.length === 0) {
|
|
960
|
+
json$1(res, {
|
|
961
|
+
ok: false,
|
|
962
|
+
error: "missing title"
|
|
963
|
+
}, 400);
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
const t = await client.get(title);
|
|
967
|
+
if (t === void 0) {
|
|
968
|
+
json$1(res, {
|
|
969
|
+
ok: false,
|
|
970
|
+
notFound: true,
|
|
971
|
+
title
|
|
972
|
+
}, 404);
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
const fields = {};
|
|
976
|
+
for (const [k, v] of Object.entries(t)) {
|
|
977
|
+
if (k === "title" || k === "text" || k === "tags") continue;
|
|
978
|
+
fields[k] = v;
|
|
979
|
+
}
|
|
980
|
+
json$1(res, {
|
|
981
|
+
ok: true,
|
|
982
|
+
title: t.title,
|
|
983
|
+
text: t.text ?? "",
|
|
984
|
+
tags: t.tags ?? [],
|
|
985
|
+
type: t.type ?? "text/vnd.tiddlywiki",
|
|
986
|
+
fields
|
|
987
|
+
});
|
|
988
|
+
} catch (err) {
|
|
989
|
+
json$1(res, {
|
|
990
|
+
ok: false,
|
|
991
|
+
error: err instanceof Error ? err.message : String(err)
|
|
992
|
+
}, 500);
|
|
993
|
+
}
|
|
994
|
+
};
|
|
870
995
|
const handleRestart = async (_req, res) => {
|
|
871
996
|
try {
|
|
872
997
|
await deps.server.restart();
|
|
@@ -1061,6 +1186,20 @@ function registerRoutes(ctx, deps) {
|
|
|
1061
1186
|
handleTags(req, res);
|
|
1062
1187
|
}
|
|
1063
1188
|
}),
|
|
1189
|
+
ctx.webServer.register({
|
|
1190
|
+
kind: "exact",
|
|
1191
|
+
path: `${ROUTE_PREFIX}/recent`,
|
|
1192
|
+
handler: (req, res) => {
|
|
1193
|
+
handleRecent(req, res);
|
|
1194
|
+
}
|
|
1195
|
+
}),
|
|
1196
|
+
ctx.webServer.register({
|
|
1197
|
+
kind: "exact",
|
|
1198
|
+
path: `${ROUTE_PREFIX}/get`,
|
|
1199
|
+
handler: (req, res) => {
|
|
1200
|
+
handleGet(req, res);
|
|
1201
|
+
}
|
|
1202
|
+
}),
|
|
1064
1203
|
ctx.webServer.register({
|
|
1065
1204
|
kind: "exact",
|
|
1066
1205
|
path: `${ROUTE_PREFIX}/sync`,
|
|
@@ -1542,17 +1681,19 @@ const DOC_NOTE_TEXT = `! dsh-tiddlywiki 插件说明
|
|
|
1542
1681
|
|
|
1543
1682
|
!! 它能做什么
|
|
1544
1683
|
|
|
1545
|
-
* **
|
|
1684
|
+
* **10 个 agent 工具**:\`tiddlywiki_search\`(检索,支持 tags/since/type/limit 过滤)/ \`tiddlywiki_get\`(读)/ \`tiddlywiki_put\`(写)/ \`tiddlywiki_batch_put\`(批量写)/ \`tiddlywiki_rename\`(重命名+同步引用)/ \`tiddlywiki_delete\`(删)/ \`tiddlywiki_recent\`(最近修改)/ \`tiddlywiki_list_tags\`(标签清单)/ \`tiddlywiki_git_sync\`(git 同步)/ \`tiddlywiki_git_resolve\`(冲突按 tiddler 二选一)。
|
|
1546
1685
|
* **TW 编辑器面板**:侧边栏「TiddlyWiki」按钮 → 在界面中央打开完整版 TW 编辑器。
|
|
1547
|
-
*
|
|
1686
|
+
* **快速笔记**:右下角「知识库」悬浮按钮 → 「📝 快速笔记」写随手记(Markdown 高亮、文件上传、多选/自动补全 tag,草稿自动保存到本地),\`Ctrl+Enter\` 保存;「🕘 最近」可一键载入旧笔记继续编辑;「✏️ 在 TW 中编辑」会弹出独立小窗用 TW 原生编辑器编辑。
|
|
1687
|
+
* **一键同步**:「知识库」按钮 → 「🔁 同步」一键 pull → commit → push,按钮上的状态点实时反映 git 状态。
|
|
1548
1688
|
* **git 同步**:写入自动防抖 commit(默认 60 秒);手动 \`tiddlywiki_git_sync action=sync\` 做 pull → commit → push。
|
|
1549
|
-
* **设置页**:DSH 设置 → 「TiddlyWiki
|
|
1689
|
+
* **设置页**:DSH 设置 → 「TiddlyWiki 知识库」管理插件/主题/语言与运行配置(含「知识库」按钮相关显示开关)。
|
|
1550
1690
|
|
|
1551
1691
|
!! 知识库纪律(三条)
|
|
1552
1692
|
|
|
1553
1693
|
1. 开工先 \`tiddlywiki_git_sync action=pull\`(rebase + autostash,真冲突会自动 abort 并报文件)。
|
|
1554
|
-
2.
|
|
1555
|
-
3.
|
|
1694
|
+
2. 冲突后:\`tiddlywiki_git_resolve files=[冲突文件] strategy=keep-local|keep-remote\` 按 tiddler 二选一解决,再重新 sync。
|
|
1695
|
+
3. 收工 \`tiddlywiki_git_sync action=sync\`。
|
|
1696
|
+
4. 插件自动 commit 兜底,手动 sync 用于需要主动推送的场合。
|
|
1556
1697
|
|
|
1557
1698
|
!! 主题与语言
|
|
1558
1699
|
|
|
@@ -1689,20 +1830,82 @@ var TiddlyWebClient = class {
|
|
|
1689
1830
|
/**
|
|
1690
1831
|
* Search non-system tiddlers: one request (default listing with text) plus
|
|
1691
1832
|
* local case-insensitive substring matching on title + text, optional exact
|
|
1692
|
-
*
|
|
1833
|
+
* tags (AND), a `since` modified-time floor, an exact `type`, capped at
|
|
1834
|
+
* `limit`. Robust against the server's external-filter 403.
|
|
1693
1835
|
*/
|
|
1694
|
-
async search(query,
|
|
1836
|
+
async search(query, options = {}) {
|
|
1695
1837
|
const items = await this.list(void 0, true);
|
|
1696
1838
|
const needle = query.toLowerCase();
|
|
1697
|
-
|
|
1839
|
+
const sinceTime = parseSince(options.since);
|
|
1840
|
+
const wantedTags = [...options.tags ?? [], options.tag].filter((t) => typeof t === "string" && t.trim().length > 0);
|
|
1841
|
+
const limit = options.limit ?? 30;
|
|
1842
|
+
const matched = items.filter((t) => {
|
|
1843
|
+
if (t.title.startsWith("$:/")) return false;
|
|
1698
1844
|
if (!t.title.toLowerCase().includes(needle) && !(t.text ?? "").toLowerCase().includes(needle)) return false;
|
|
1699
|
-
if (
|
|
1700
|
-
|
|
1845
|
+
if (sinceTime !== void 0) {
|
|
1846
|
+
const modified = typeof t.modified === "string" ? new Date(t.modified).getTime() : NaN;
|
|
1847
|
+
if (Number.isNaN(modified) || modified < sinceTime) return false;
|
|
1848
|
+
}
|
|
1849
|
+
if (options.type !== void 0 && options.type.length > 0 && (t.type ?? "text/vnd.tiddlywiki") !== options.type) return false;
|
|
1850
|
+
if (wantedTags.length > 0) {
|
|
1851
|
+
const tags = (t.tags ?? []).map((tag) => tag.toLowerCase());
|
|
1852
|
+
if (!wantedTags.every((w) => tags.includes(w.toLowerCase()))) return false;
|
|
1853
|
+
}
|
|
1854
|
+
return true;
|
|
1855
|
+
});
|
|
1856
|
+
return {
|
|
1857
|
+
items: matched.slice(0, limit),
|
|
1858
|
+
total: matched.length
|
|
1859
|
+
};
|
|
1860
|
+
}
|
|
1861
|
+
/**
|
|
1862
|
+
* List the most recently modified NON-SYSTEM tiddlers, newest first
|
|
1863
|
+
* (missing/modified-less tiddlers sort to the tail). `since` keeps only
|
|
1864
|
+
* tiddlers modified at/after that instant.
|
|
1865
|
+
*/
|
|
1866
|
+
async recent(limit = 15, since) {
|
|
1867
|
+
const items = await this.list(void 0, true);
|
|
1868
|
+
const sinceTime = parseSince(since);
|
|
1869
|
+
const filtered = items.filter((t) => {
|
|
1870
|
+
if (t.title.startsWith("$:/")) return false;
|
|
1871
|
+
if (sinceTime !== void 0) {
|
|
1872
|
+
const modified = typeof t.modified === "string" ? new Date(t.modified).getTime() : NaN;
|
|
1873
|
+
if (Number.isNaN(modified) || modified < sinceTime) return false;
|
|
1701
1874
|
}
|
|
1702
1875
|
return true;
|
|
1703
|
-
})
|
|
1876
|
+
});
|
|
1877
|
+
filtered.sort((a, b) => {
|
|
1878
|
+
const am = typeof a.modified === "string" ? new Date(a.modified).getTime() : 0;
|
|
1879
|
+
return (typeof b.modified === "string" ? new Date(b.modified).getTime() : 0) - am;
|
|
1880
|
+
});
|
|
1881
|
+
return filtered.slice(0, Math.max(1, Math.min(limit, 200)));
|
|
1882
|
+
}
|
|
1883
|
+
/**
|
|
1884
|
+
* Distinct non-system tags with their tiddler counts, most-used first then
|
|
1885
|
+
* zh-locale. One skinny listing request (no text payloads).
|
|
1886
|
+
*/
|
|
1887
|
+
async listTags() {
|
|
1888
|
+
const items = await this.list(void 0, false);
|
|
1889
|
+
const map = /* @__PURE__ */ new Map();
|
|
1890
|
+
for (const t of items) {
|
|
1891
|
+
if (t.title.startsWith("$:/")) continue;
|
|
1892
|
+
for (const tag of t.tags ?? []) {
|
|
1893
|
+
if (tag.startsWith("$:/")) continue;
|
|
1894
|
+
map.set(tag, (map.get(tag) ?? 0) + 1);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
return [...map.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0], "zh")).map(([tag, count]) => ({
|
|
1898
|
+
tag,
|
|
1899
|
+
count
|
|
1900
|
+
}));
|
|
1704
1901
|
}
|
|
1705
1902
|
};
|
|
1903
|
+
/** Parse a `since` value into an epoch ms, or undefined when absent/invalid. */
|
|
1904
|
+
function parseSince(since) {
|
|
1905
|
+
if (typeof since !== "string" || since.trim().length === 0) return void 0;
|
|
1906
|
+
const ms = new Date(since.trim()).getTime();
|
|
1907
|
+
return Number.isNaN(ms) ? void 0 : ms;
|
|
1908
|
+
}
|
|
1706
1909
|
//#endregion
|
|
1707
1910
|
//#region src/sdk.ts
|
|
1708
1911
|
/**
|
|
@@ -1738,7 +1941,11 @@ function compileValue(spec) {
|
|
|
1738
1941
|
const objectSpec = spec;
|
|
1739
1942
|
node.type = "object";
|
|
1740
1943
|
node.additionalProperties = objectSpec.additionalProperties;
|
|
1741
|
-
if (objectSpec.properties !== void 0)
|
|
1944
|
+
if (objectSpec.properties !== void 0) {
|
|
1945
|
+
const compiled = compilePropertyMap(objectSpec.properties);
|
|
1946
|
+
node.properties = compiled.properties;
|
|
1947
|
+
if (compiled.required !== void 0) node.required = compiled.required;
|
|
1948
|
+
}
|
|
1742
1949
|
return node;
|
|
1743
1950
|
}
|
|
1744
1951
|
if (type === "array") {
|
|
@@ -1842,10 +2049,14 @@ function defineTool(options) {
|
|
|
1842
2049
|
//#endregion
|
|
1843
2050
|
//#region src/host/tools.ts
|
|
1844
2051
|
/**
|
|
1845
|
-
* The
|
|
2052
|
+
* The `tiddlywiki_*` agent tools (design doc §11, D8) plus the extension
|
|
1846
2053
|
* point: `registerTiddlywikiTools(ctx, deps)` registers tools list-style, so a
|
|
1847
2054
|
* new tool is just one more `defineTool` in the array — index.ts never changes.
|
|
1848
2055
|
*
|
|
2056
|
+
* Toolset (v0.5):
|
|
2057
|
+
* search / get / put / batch_put / rename / delete / recent / list_tags /
|
|
2058
|
+
* git_sync / git_resolve
|
|
2059
|
+
*
|
|
1849
2060
|
* RENDER CONTRACT (design doc §4.3): the registry feeds `output.render(args,
|
|
1850
2061
|
* value)` into the loop — the model sees ONLY the rendered text, never the raw
|
|
1851
2062
|
* JSON `value`. Every render must carry the complete facts an agent needs to
|
|
@@ -1866,6 +2077,42 @@ function pickFields(t) {
|
|
|
1866
2077
|
}
|
|
1867
2078
|
return out;
|
|
1868
2079
|
}
|
|
2080
|
+
/** A put-ready copy of a tiddler (no created/modified, tags as array). */
|
|
2081
|
+
function cleanTiddler(t) {
|
|
2082
|
+
const out = {
|
|
2083
|
+
title: t.title,
|
|
2084
|
+
text: t.text ?? "",
|
|
2085
|
+
tags: t.tags ?? []
|
|
2086
|
+
};
|
|
2087
|
+
if (typeof t.type === "string") out.type = t.type;
|
|
2088
|
+
for (const [k, v] of Object.entries(t)) {
|
|
2089
|
+
if (k === "title" || k === "text" || k === "tags" || k === "type" || k === "created" || k === "modified" || k === "fields") continue;
|
|
2090
|
+
out[k] = v;
|
|
2091
|
+
}
|
|
2092
|
+
return out;
|
|
2093
|
+
}
|
|
2094
|
+
/**
|
|
2095
|
+
* Rewrite TiddlyWiki references to a title inside wiki text: `[[Title]]`,
|
|
2096
|
+
* `[[display|Title]]`, `{{Title}}` → the new title. Best-effort link/text
|
|
2097
|
+
* migration for tiddlywiki_rename; returns the rewritten text + hit count.
|
|
2098
|
+
*/
|
|
2099
|
+
function rewriteRefs(text, oldTitle, newTitle) {
|
|
2100
|
+
if (oldTitle.length === 0) return {
|
|
2101
|
+
text,
|
|
2102
|
+
count: 0
|
|
2103
|
+
};
|
|
2104
|
+
const escaped = oldTitle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2105
|
+
const re = new RegExp(`(\\[\\[[^\\]|]*\\|)${escaped}(\\]\\])|(\\[\\[)${escaped}(\\]\\])|(\\{\\{)${escaped}(\\}\\})`, "g");
|
|
2106
|
+
let count = 0;
|
|
2107
|
+
return {
|
|
2108
|
+
text: text.replace(re, (...args) => {
|
|
2109
|
+
const p = args;
|
|
2110
|
+
count++;
|
|
2111
|
+
return `${p[1] ?? p[3] ?? p[5] ?? ""}${newTitle}${p[2] ?? p[4] ?? p[6] ?? ""}`;
|
|
2112
|
+
}),
|
|
2113
|
+
count
|
|
2114
|
+
};
|
|
2115
|
+
}
|
|
1869
2116
|
function registerTiddlywikiTools(ctx, deps) {
|
|
1870
2117
|
const disposers = [];
|
|
1871
2118
|
const register = (tool) => {
|
|
@@ -1873,29 +2120,51 @@ function registerTiddlywikiTools(ctx, deps) {
|
|
|
1873
2120
|
};
|
|
1874
2121
|
register(defineTool({
|
|
1875
2122
|
name: "tiddlywiki_search",
|
|
1876
|
-
description: "检索 TiddlyWiki 持久知识库:按关键词(可选
|
|
2123
|
+
description: "检索 TiddlyWiki 持久知识库:按关键词(可选 tags 数组 / since 修改时间 / type / limit)搜索非系统 tiddler,返回标题、标签、修改时间与摘要片段。",
|
|
1877
2124
|
parameters: {
|
|
1878
2125
|
query: {
|
|
1879
2126
|
type: "string",
|
|
1880
2127
|
description: "搜索关键词(大小写不敏感,子串匹配)",
|
|
1881
2128
|
required: true
|
|
1882
2129
|
},
|
|
2130
|
+
tags: {
|
|
2131
|
+
type: "array",
|
|
2132
|
+
items: { type: "string" },
|
|
2133
|
+
description: "可选:要求同时包含的标签(AND)"
|
|
2134
|
+
},
|
|
1883
2135
|
tag: {
|
|
1884
2136
|
type: "string",
|
|
1885
|
-
description: "
|
|
2137
|
+
description: "可选:单个精确标签(与 tags 同为 AND)"
|
|
2138
|
+
},
|
|
2139
|
+
since: {
|
|
2140
|
+
type: "string",
|
|
2141
|
+
description: "可选:ISO 时间(如 2026-09-01 或 2026-09-01T00:00:00Z),只返回修改时间不早于它的 tiddler"
|
|
2142
|
+
},
|
|
2143
|
+
type: {
|
|
2144
|
+
type: "string",
|
|
2145
|
+
description: "可选:精确 tiddler 类型(默认 text/vnd.tiddlywiki)"
|
|
2146
|
+
},
|
|
2147
|
+
limit: {
|
|
2148
|
+
type: "integer",
|
|
2149
|
+
description: "可选:返回条数上限(默认 30,最大 200)"
|
|
1886
2150
|
}
|
|
1887
2151
|
},
|
|
1888
2152
|
output: {
|
|
1889
2153
|
schema: { type: "json" },
|
|
1890
2154
|
render: (_args, value) => {
|
|
1891
|
-
const
|
|
2155
|
+
const filters = [];
|
|
2156
|
+
if (value.tags.length > 0) filters.push(`tags=${value.tags.join(",")}`);
|
|
2157
|
+
if (value.since !== null) filters.push(`since=${value.since}`);
|
|
2158
|
+
if (value.type !== null) filters.push(`type=${value.type}`);
|
|
2159
|
+
const lines = [`TiddlyWiki 搜索「${value.query}」${filters.length > 0 ? ` (${filters.join(" · ")})` : ""}:命中 ${value.total} 条。`];
|
|
1892
2160
|
if (value.results.length === 0) lines.push("没有匹配的 tiddler。");
|
|
1893
2161
|
for (const r of value.results) {
|
|
1894
2162
|
const tags = r.tags.length > 0 ? ` [${r.tags.join(", ")}]` : "";
|
|
1895
|
-
|
|
2163
|
+
const modified = r.modified !== null ? ` (${r.modified})` : "";
|
|
2164
|
+
lines.push(`- ${r.title}${tags}${modified}`);
|
|
1896
2165
|
if (r.snippet.length > 0) lines.push(` ${r.snippet}`);
|
|
1897
2166
|
}
|
|
1898
|
-
if (value.
|
|
2167
|
+
if (value.total > value.results.length) lines.push(`(另有 ${value.total - value.results.length} 条未展开,可用 tiddlywiki_get 读取具体标题,或提高 limit)`);
|
|
1899
2168
|
return [{
|
|
1900
2169
|
type: "text",
|
|
1901
2170
|
text: lines.join("\n")
|
|
@@ -1905,19 +2174,101 @@ function registerTiddlywikiTools(ctx, deps) {
|
|
|
1905
2174
|
execute: async (args) => {
|
|
1906
2175
|
const wiki = deps.wiki();
|
|
1907
2176
|
if (wiki === void 0) throw new Error("TiddlyWiki 服务未运行(tiddlywiki_status 可查)");
|
|
1908
|
-
const
|
|
2177
|
+
const { items, total } = await wiki.search(args.query, {
|
|
2178
|
+
tags: args.tags,
|
|
2179
|
+
tag: args.tag,
|
|
2180
|
+
since: args.since,
|
|
2181
|
+
type: args.type,
|
|
2182
|
+
limit: args.limit
|
|
2183
|
+
});
|
|
1909
2184
|
return {
|
|
1910
2185
|
query: args.query,
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
2186
|
+
tags: args.tags ?? [],
|
|
2187
|
+
since: args.since ?? null,
|
|
2188
|
+
type: args.type ?? null,
|
|
2189
|
+
total,
|
|
2190
|
+
results: items.map((t) => ({
|
|
2191
|
+
title: t.title,
|
|
2192
|
+
tags: t.tags ?? [],
|
|
2193
|
+
modified: typeof t.modified === "string" ? t.modified : null,
|
|
2194
|
+
snippet: snippetOf(t.text ?? "")
|
|
2195
|
+
}))
|
|
2196
|
+
};
|
|
2197
|
+
}
|
|
2198
|
+
}));
|
|
2199
|
+
register(defineTool({
|
|
2200
|
+
name: "tiddlywiki_recent",
|
|
2201
|
+
description: "查看 TiddlyWiki 知识库最近修改的笔记(按修改时间倒序,排除系统 tiddler),返回标题、标签、修改时间与摘要。适合开工时快速了解近期动态。",
|
|
2202
|
+
parameters: {
|
|
2203
|
+
limit: {
|
|
2204
|
+
type: "integer",
|
|
2205
|
+
description: "可选:返回条数(默认 15,最大 200)"
|
|
2206
|
+
},
|
|
2207
|
+
since: {
|
|
2208
|
+
type: "string",
|
|
2209
|
+
description: "可选:只返回修改时间不早于该 ISO 时间的 tiddler"
|
|
2210
|
+
}
|
|
2211
|
+
},
|
|
2212
|
+
output: {
|
|
2213
|
+
schema: { type: "json" },
|
|
2214
|
+
render: (_args, value) => {
|
|
2215
|
+
const lines = [`TiddlyWiki 最近修改(最近 ${value.results.length} 条${value.since !== null ? `,since=${value.since}` : ""}):`];
|
|
2216
|
+
if (value.results.length === 0) lines.push("暂无笔记。");
|
|
2217
|
+
for (const r of value.results) {
|
|
2218
|
+
const tags = r.tags.length > 0 ? ` [${r.tags.join(", ")}]` : "";
|
|
2219
|
+
lines.push(`- ${r.title}${tags} (${r.modified ?? "?"})`);
|
|
2220
|
+
if (r.snippet.length > 0) lines.push(` ${r.snippet}`);
|
|
2221
|
+
}
|
|
2222
|
+
return [{
|
|
2223
|
+
type: "text",
|
|
2224
|
+
text: lines.join("\n")
|
|
2225
|
+
}];
|
|
2226
|
+
}
|
|
2227
|
+
},
|
|
2228
|
+
execute: async (args) => {
|
|
2229
|
+
const wiki = deps.wiki();
|
|
2230
|
+
if (wiki === void 0) throw new Error("TiddlyWiki 服务未运行(tiddlywiki_status 可查)");
|
|
2231
|
+
const items = await wiki.recent(args.limit ?? 15, args.since);
|
|
2232
|
+
return {
|
|
2233
|
+
since: args.since ?? null,
|
|
2234
|
+
results: items.map((t) => ({
|
|
1914
2235
|
title: t.title,
|
|
1915
2236
|
tags: t.tags ?? [],
|
|
2237
|
+
modified: typeof t.modified === "string" ? t.modified : null,
|
|
1916
2238
|
snippet: snippetOf(t.text ?? "")
|
|
1917
2239
|
}))
|
|
1918
2240
|
};
|
|
1919
2241
|
}
|
|
1920
2242
|
}));
|
|
2243
|
+
register(defineTool({
|
|
2244
|
+
name: "tiddlywiki_list_tags",
|
|
2245
|
+
description: "列出 TiddlyWiki 知识库现有的非系统标签及各自计数(按使用次数降序),方便决定给笔记打什么 tag。",
|
|
2246
|
+
parameters: {},
|
|
2247
|
+
output: {
|
|
2248
|
+
schema: { type: "json" },
|
|
2249
|
+
render: (_args, value) => {
|
|
2250
|
+
if (value.tags.length === 0) return [{
|
|
2251
|
+
type: "text",
|
|
2252
|
+
text: "知识库暂无标签。"
|
|
2253
|
+
}];
|
|
2254
|
+
const lines = [`现有标签(${value.tags.length} 个,按使用次数降序):`];
|
|
2255
|
+
for (const t of value.tags) lines.push(`- ${t.tag} × ${t.count}`);
|
|
2256
|
+
return [{
|
|
2257
|
+
type: "text",
|
|
2258
|
+
text: lines.join("\n")
|
|
2259
|
+
}];
|
|
2260
|
+
}
|
|
2261
|
+
},
|
|
2262
|
+
execute: async () => {
|
|
2263
|
+
const wiki = deps.wiki();
|
|
2264
|
+
if (wiki === void 0) throw new Error("TiddlyWiki 服务未运行(tiddlywiki_status 可查)");
|
|
2265
|
+
const tags = await wiki.listTags();
|
|
2266
|
+
return {
|
|
2267
|
+
count: tags.length,
|
|
2268
|
+
tags
|
|
2269
|
+
};
|
|
2270
|
+
}
|
|
2271
|
+
}));
|
|
1921
2272
|
register(defineTool({
|
|
1922
2273
|
name: "tiddlywiki_get",
|
|
1923
2274
|
description: "读取一个 TiddlyWiki tiddler 的完整内容(标题、全文、标签、自定义字段)。",
|
|
@@ -1935,6 +2286,7 @@ function registerTiddlywikiTools(ctx, deps) {
|
|
|
1935
2286
|
}];
|
|
1936
2287
|
const lines = [`tiddler「${value.title}」`];
|
|
1937
2288
|
if (value.tags.length > 0) lines.push(`标签: ${value.tags.join(", ")}`);
|
|
2289
|
+
if (value.modified !== null) lines.push(`修改: ${value.modified}`);
|
|
1938
2290
|
const fields = Object.entries(value.fields);
|
|
1939
2291
|
if (fields.length > 0) lines.push(`字段: ${fields.map(([k, v]) => `${k}=${String(v)}`).join(", ")}`);
|
|
1940
2292
|
lines.push("--- 全文 ---");
|
|
@@ -1954,14 +2306,16 @@ function registerTiddlywikiTools(ctx, deps) {
|
|
|
1954
2306
|
title: args.title,
|
|
1955
2307
|
text: "",
|
|
1956
2308
|
tags: [],
|
|
1957
|
-
fields: {}
|
|
2309
|
+
fields: {},
|
|
2310
|
+
modified: null
|
|
1958
2311
|
};
|
|
1959
2312
|
return {
|
|
1960
2313
|
notFound: false,
|
|
1961
2314
|
title: t.title,
|
|
1962
2315
|
text: t.text ?? "",
|
|
1963
2316
|
tags: t.tags ?? [],
|
|
1964
|
-
fields: pickFields(t)
|
|
2317
|
+
fields: pickFields(t),
|
|
2318
|
+
modified: typeof t.modified === "string" ? t.modified : null
|
|
1965
2319
|
};
|
|
1966
2320
|
}
|
|
1967
2321
|
}));
|
|
@@ -2023,6 +2377,186 @@ function registerTiddlywikiTools(ctx, deps) {
|
|
|
2023
2377
|
};
|
|
2024
2378
|
}
|
|
2025
2379
|
}));
|
|
2380
|
+
register(defineTool({
|
|
2381
|
+
name: "tiddlywiki_batch_put",
|
|
2382
|
+
description: "批量写入/覆盖多个 TiddlyWiki tiddler(一次工具调用)。overwrite=false 时跳过已存在的标题;返回逐条结果。写入后触发自动 commit。",
|
|
2383
|
+
parameters: {
|
|
2384
|
+
items: {
|
|
2385
|
+
type: "array",
|
|
2386
|
+
items: {
|
|
2387
|
+
type: "object",
|
|
2388
|
+
additionalProperties: true,
|
|
2389
|
+
description: "要写入的 tiddler 数组",
|
|
2390
|
+
properties: {
|
|
2391
|
+
title: {
|
|
2392
|
+
type: "string",
|
|
2393
|
+
description: "标题(精确匹配,覆盖同名)",
|
|
2394
|
+
required: true
|
|
2395
|
+
},
|
|
2396
|
+
text: {
|
|
2397
|
+
type: "string",
|
|
2398
|
+
description: "全文(wiki 文本)",
|
|
2399
|
+
required: true
|
|
2400
|
+
},
|
|
2401
|
+
tags: {
|
|
2402
|
+
type: "array",
|
|
2403
|
+
items: { type: "string" },
|
|
2404
|
+
description: "标签数组(可选)"
|
|
2405
|
+
},
|
|
2406
|
+
fields: {
|
|
2407
|
+
type: "json",
|
|
2408
|
+
description: "附加自定义字段(可选)"
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
},
|
|
2413
|
+
overwrite: {
|
|
2414
|
+
type: "boolean",
|
|
2415
|
+
description: "可选:true=覆盖同名(默认),false=跳过已存在的标题"
|
|
2416
|
+
}
|
|
2417
|
+
},
|
|
2418
|
+
output: {
|
|
2419
|
+
schema: { type: "json" },
|
|
2420
|
+
render: (_args, value) => {
|
|
2421
|
+
const lines = [`批量写入完成:成功 ${value.written},跳过 ${value.skipped},共 ${value.items.length} 条。`];
|
|
2422
|
+
for (const r of value.items) lines.push(`- ${r.title}:${r.written ? "已写入" : "已跳过(存在)"}`);
|
|
2423
|
+
return [{
|
|
2424
|
+
type: "text",
|
|
2425
|
+
text: lines.join("\n")
|
|
2426
|
+
}];
|
|
2427
|
+
}
|
|
2428
|
+
},
|
|
2429
|
+
execute: async (args) => {
|
|
2430
|
+
const wiki = deps.wiki();
|
|
2431
|
+
if (wiki === void 0) throw new Error("TiddlyWiki 服务未运行(tiddlywiki_status 可查)");
|
|
2432
|
+
const list = Array.isArray(args.items) ? args.items : [];
|
|
2433
|
+
if (list.length === 0) return {
|
|
2434
|
+
ok: true,
|
|
2435
|
+
written: 0,
|
|
2436
|
+
skipped: 0,
|
|
2437
|
+
items: []
|
|
2438
|
+
};
|
|
2439
|
+
const overwrite = args.overwrite !== false;
|
|
2440
|
+
const results = [];
|
|
2441
|
+
let written = 0;
|
|
2442
|
+
let skipped = 0;
|
|
2443
|
+
for (const item of list) {
|
|
2444
|
+
if (typeof item.title !== "string" || item.title.length === 0) throw new Error("batch_put: 每条 items 都需要非空 title");
|
|
2445
|
+
if (typeof item.text !== "string") throw new Error(`batch_put: items「${item.title}」缺少 text`);
|
|
2446
|
+
if (!overwrite) {
|
|
2447
|
+
if (await wiki.get(item.title).catch(() => void 0) !== void 0) {
|
|
2448
|
+
skipped++;
|
|
2449
|
+
results.push({
|
|
2450
|
+
title: item.title,
|
|
2451
|
+
written: false,
|
|
2452
|
+
skipped: true
|
|
2453
|
+
});
|
|
2454
|
+
continue;
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
const tiddler = {
|
|
2458
|
+
title: item.title,
|
|
2459
|
+
text: item.text
|
|
2460
|
+
};
|
|
2461
|
+
if (Array.isArray(item.tags) && item.tags.length > 0) tiddler.tags = item.tags;
|
|
2462
|
+
if (item.fields !== void 0 && typeof item.fields === "object" && item.fields !== null) Object.assign(tiddler, item.fields);
|
|
2463
|
+
await wiki.put(tiddler);
|
|
2464
|
+
written++;
|
|
2465
|
+
results.push({
|
|
2466
|
+
title: item.title,
|
|
2467
|
+
written: true,
|
|
2468
|
+
skipped: false
|
|
2469
|
+
});
|
|
2470
|
+
}
|
|
2471
|
+
deps.autoCommit();
|
|
2472
|
+
return {
|
|
2473
|
+
ok: true,
|
|
2474
|
+
written,
|
|
2475
|
+
skipped,
|
|
2476
|
+
items: results
|
|
2477
|
+
};
|
|
2478
|
+
}
|
|
2479
|
+
}));
|
|
2480
|
+
register(defineTool({
|
|
2481
|
+
name: "tiddlywiki_rename",
|
|
2482
|
+
description: "重命名一个 TiddlyWiki tiddler:把旧标题的内容复制到新标题、删除旧标题,并可选地更新其他 tiddler 里的 [[旧标题]] / {{旧标题}} 引用(最佳努力)。",
|
|
2483
|
+
parameters: {
|
|
2484
|
+
oldTitle: {
|
|
2485
|
+
type: "string",
|
|
2486
|
+
description: "当前标题",
|
|
2487
|
+
required: true
|
|
2488
|
+
},
|
|
2489
|
+
newTitle: {
|
|
2490
|
+
type: "string",
|
|
2491
|
+
description: "新标题",
|
|
2492
|
+
required: true
|
|
2493
|
+
},
|
|
2494
|
+
updateRefs: {
|
|
2495
|
+
type: "boolean",
|
|
2496
|
+
description: "可选:是否同步更新其他 tiddler 里的引用(默认 true)"
|
|
2497
|
+
}
|
|
2498
|
+
},
|
|
2499
|
+
output: {
|
|
2500
|
+
schema: { type: "json" },
|
|
2501
|
+
render: (_args, value) => {
|
|
2502
|
+
const lines = [`已重命名「${value.from}」→「${value.to}」`];
|
|
2503
|
+
lines.push(`更新了 ${value.refsUpdated} 处引用(${value.refsTiddlers} 个 tiddler)`);
|
|
2504
|
+
if (value.warning !== void 0) lines.push(`注意: ${value.warning}`);
|
|
2505
|
+
return [{
|
|
2506
|
+
type: "text",
|
|
2507
|
+
text: lines.join("\n")
|
|
2508
|
+
}];
|
|
2509
|
+
}
|
|
2510
|
+
},
|
|
2511
|
+
execute: async (args) => {
|
|
2512
|
+
const wiki = deps.wiki();
|
|
2513
|
+
if (wiki === void 0) throw new Error("TiddlyWiki 服务未运行(tiddlywiki_status 可查)");
|
|
2514
|
+
const { oldTitle, newTitle } = args;
|
|
2515
|
+
if (oldTitle === newTitle) return {
|
|
2516
|
+
ok: true,
|
|
2517
|
+
from: oldTitle,
|
|
2518
|
+
to: newTitle,
|
|
2519
|
+
refsUpdated: 0,
|
|
2520
|
+
refsTiddlers: 0
|
|
2521
|
+
};
|
|
2522
|
+
const existing = await wiki.get(oldTitle);
|
|
2523
|
+
if (existing === void 0) throw new Error(`tiddler「${oldTitle}」不存在`);
|
|
2524
|
+
if (await wiki.get(newTitle) !== void 0) throw new Error(`新标题「${newTitle}」已存在(可先用 tiddlywiki_delete 删除)`);
|
|
2525
|
+
let refsUpdated = 0;
|
|
2526
|
+
let refsTiddlers = 0;
|
|
2527
|
+
let warning;
|
|
2528
|
+
if (args.updateRefs !== false) {
|
|
2529
|
+
const all = await wiki.list(void 0, true);
|
|
2530
|
+
for (const t of all) {
|
|
2531
|
+
if (t.title === oldTitle || t.title === newTitle) continue;
|
|
2532
|
+
if (t.title.startsWith("$:/")) continue;
|
|
2533
|
+
const text = t.text ?? "";
|
|
2534
|
+
if (text.length === 0) continue;
|
|
2535
|
+
const rewritten = rewriteRefs(text, oldTitle, newTitle);
|
|
2536
|
+
if (rewritten.count > 0) {
|
|
2537
|
+
await wiki.put({
|
|
2538
|
+
...cleanTiddler(t),
|
|
2539
|
+
text: rewritten.text
|
|
2540
|
+
});
|
|
2541
|
+
refsUpdated += rewritten.count;
|
|
2542
|
+
refsTiddlers++;
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
await wiki.put(cleanTiddler(existing));
|
|
2547
|
+
await wiki.delete(oldTitle);
|
|
2548
|
+
if (refsTiddlers === 0) warning = "未找到任何其他 tiddler 引用旧标题;如确实需要,可手动补充链接。";
|
|
2549
|
+
deps.autoCommit();
|
|
2550
|
+
return {
|
|
2551
|
+
ok: true,
|
|
2552
|
+
from: oldTitle,
|
|
2553
|
+
to: newTitle,
|
|
2554
|
+
refsUpdated,
|
|
2555
|
+
refsTiddlers,
|
|
2556
|
+
...warning !== void 0 ? { warning } : {}
|
|
2557
|
+
};
|
|
2558
|
+
}
|
|
2559
|
+
}));
|
|
2026
2560
|
register(defineTool({
|
|
2027
2561
|
name: "tiddlywiki_delete",
|
|
2028
2562
|
description: "删除一个 TiddlyWiki tiddler(不存在时是幂等空操作)。删除后触发自动 commit。",
|
|
@@ -2132,6 +2666,98 @@ function registerTiddlywikiTools(ctx, deps) {
|
|
|
2132
2666
|
}
|
|
2133
2667
|
}
|
|
2134
2668
|
}));
|
|
2669
|
+
register(defineTool({
|
|
2670
|
+
name: "tiddlywiki_git_resolve",
|
|
2671
|
+
description: "在 tiddlywiki_git_sync action=pull 冲突(已 abort)后,按 tiddler 二选一解决:keep-local 保留本地版本;keep-remote 用 git fetch 拉取远端并检出远端版本(需已配置 git.remote);list 仅报告当前状态。解决后建议重新 pull/sync 整合其余改动。",
|
|
2672
|
+
parameters: {
|
|
2673
|
+
strategy: {
|
|
2674
|
+
type: "string",
|
|
2675
|
+
enum: [
|
|
2676
|
+
"keep-local",
|
|
2677
|
+
"keep-remote",
|
|
2678
|
+
"list"
|
|
2679
|
+
],
|
|
2680
|
+
description: "keep-local=保留本地;keep-remote=改用远端版本;list=仅报告当前 git 状态",
|
|
2681
|
+
required: true
|
|
2682
|
+
},
|
|
2683
|
+
files: {
|
|
2684
|
+
type: "array",
|
|
2685
|
+
items: { type: "string" },
|
|
2686
|
+
description: "冲突文件名数组(来自 pull 返回的 conflictFiles;list 时忽略)"
|
|
2687
|
+
}
|
|
2688
|
+
},
|
|
2689
|
+
output: {
|
|
2690
|
+
schema: { type: "json" },
|
|
2691
|
+
render: (_args, value) => {
|
|
2692
|
+
const lines = [`git resolve ${value.action}: ${value.ok ? "成功" : "失败"}`];
|
|
2693
|
+
lines.push(` ${value.message}`);
|
|
2694
|
+
if (value.files !== void 0 && value.files.length > 0) lines.push(`涉及文件: ${value.files.join(", ")}`);
|
|
2695
|
+
if (value.commit !== void 0) lines.push(`本地 commit: ${value.commit}`);
|
|
2696
|
+
if (value.status !== void 0) {
|
|
2697
|
+
const s = value.status;
|
|
2698
|
+
const bits = [`分支 ${s.branch}`];
|
|
2699
|
+
if (s.ahead !== void 0) bits.push(`领先 ${s.ahead}`);
|
|
2700
|
+
if (s.behind !== void 0) bits.push(`落后 ${s.behind}`);
|
|
2701
|
+
if (s.dirty) bits.push(`工作区有 ${s.dirtyFiles.length} 个未提交改动`);
|
|
2702
|
+
if (s.lastCommit !== void 0) bits.push(`最近提交 ${s.lastCommit}`);
|
|
2703
|
+
lines.push(`状态: ${bits.join(" · ")}`);
|
|
2704
|
+
}
|
|
2705
|
+
return [{
|
|
2706
|
+
type: "text",
|
|
2707
|
+
text: lines.join("\n")
|
|
2708
|
+
}];
|
|
2709
|
+
}
|
|
2710
|
+
},
|
|
2711
|
+
execute: async (args) => {
|
|
2712
|
+
const dir = deps.wikiPath();
|
|
2713
|
+
if (args.strategy === "list") return {
|
|
2714
|
+
ok: true,
|
|
2715
|
+
action: "list",
|
|
2716
|
+
message: "当前仓库状态(pull 冲突已 abort,工作区即本地版本,不会残留未合并状态)",
|
|
2717
|
+
status: await deps.git.status(dir)
|
|
2718
|
+
};
|
|
2719
|
+
const files = (args.files ?? []).filter((f) => typeof f === "string" && f.length > 0);
|
|
2720
|
+
if (files.length === 0) return {
|
|
2721
|
+
ok: false,
|
|
2722
|
+
action: args.strategy,
|
|
2723
|
+
message: "请提供 conflictFiles(来自 pull 返回)",
|
|
2724
|
+
hint: "可先执行 tiddlywiki_git_sync action=pull 查看冲突文件"
|
|
2725
|
+
};
|
|
2726
|
+
if (args.strategy === "keep-local") {
|
|
2727
|
+
const status = await deps.git.status(dir);
|
|
2728
|
+
return {
|
|
2729
|
+
ok: true,
|
|
2730
|
+
action: "keep-local",
|
|
2731
|
+
message: `已保留本地版本(${files.length} 个文件;abort 后本地即为工作区内容)。建议重新 tiddlywiki_git_sync action=sync 整合远端其余改动。`,
|
|
2732
|
+
files,
|
|
2733
|
+
status
|
|
2734
|
+
};
|
|
2735
|
+
}
|
|
2736
|
+
const fetched = await deps.git.fetch(dir);
|
|
2737
|
+
if (!fetched.ok) return {
|
|
2738
|
+
ok: false,
|
|
2739
|
+
action: "keep-remote",
|
|
2740
|
+
message: `fetch 失败(可能未配置 git.remote):${fetched.message}`
|
|
2741
|
+
};
|
|
2742
|
+
const checked = await deps.git.checkoutFetchHead(dir, files);
|
|
2743
|
+
if (!checked.ok) return {
|
|
2744
|
+
ok: false,
|
|
2745
|
+
action: "keep-remote",
|
|
2746
|
+
message: `从远端检出失败:${checked.message}`
|
|
2747
|
+
};
|
|
2748
|
+
const committed = await deps.git.commit(dir, `resolve conflict (keep remote) ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
2749
|
+
deps.autoCommit();
|
|
2750
|
+
const status = await deps.git.status(dir);
|
|
2751
|
+
return {
|
|
2752
|
+
ok: true,
|
|
2753
|
+
action: "keep-remote",
|
|
2754
|
+
message: `已把 ${files.length} 个冲突文件改为远端版本并提交。建议继续 tiddlywiki_git_sync action=sync 完成整合与推送。`,
|
|
2755
|
+
files,
|
|
2756
|
+
commit: committed.message,
|
|
2757
|
+
status
|
|
2758
|
+
};
|
|
2759
|
+
}
|
|
2760
|
+
}));
|
|
2135
2761
|
return disposers;
|
|
2136
2762
|
}
|
|
2137
2763
|
function renderSync(value) {
|
|
@@ -2140,7 +2766,7 @@ function renderSync(value) {
|
|
|
2140
2766
|
if (value.conflictFiles !== void 0 && value.conflictFiles.length > 0) {
|
|
2141
2767
|
lines.push(`冲突文件(rebase 已 abort,勿自动覆盖):`);
|
|
2142
2768
|
for (const f of value.conflictFiles) lines.push(` - ${f}`);
|
|
2143
|
-
lines.push("
|
|
2769
|
+
lines.push("处理方式:用 tiddlywiki_git_resolve files=[以上文件] strategy=keep-local|keep-remote 按 tiddler 二选一解决,再重新 sync;也可以直接让用户处理。");
|
|
2144
2770
|
}
|
|
2145
2771
|
if (value.commit !== void 0) lines.push(`本地 commit: ${value.commit}`);
|
|
2146
2772
|
if (value.push !== void 0) lines.push(`远端 push: ${value.push}`);
|
|
@@ -2248,14 +2874,17 @@ const PROMPT_TEXT = `## TiddlyWiki 持久知识库
|
|
|
2248
2874
|
|
|
2249
2875
|
本机有一个 TiddlyWiki 5 持久知识库(wiki 文件夹即 git 仓库)。你可以用工具读写 tiddler:
|
|
2250
2876
|
|
|
2251
|
-
- \`tiddlywiki_search\`(query
|
|
2252
|
-
- \`
|
|
2877
|
+
- \`tiddlywiki_search\`(query 必填;可选 tags[]/tag、since 修改时间、type、limit)检索;\`tiddlywiki_get\`(title)读全文;\`tiddlywiki_put\`(title, text, tags?, fields?)写/覆盖;\`tiddlywiki_batch_put\`(items[])批量写;\`tiddlywiki_rename\`(oldTitle, newTitle, updateRefs?)重命名并尽量同步引用;\`tiddlywiki_delete\`(title)删除。
|
|
2878
|
+
- \`tiddlywiki_recent\`(limit?, since?)看最近修改的笔记;\`tiddlywiki_list_tags\` 看现有 tag 及计数。
|
|
2879
|
+
- \`tiddlywiki_git_sync\`(pull|push|sync)做 git 同步;\`tiddlywiki_git_resolve\`(files, strategy=keep-local|keep-remote|list)在 pull 冲突后按 tiddler 二选一解决。
|
|
2253
2880
|
|
|
2254
2881
|
知识库同步纪律(三条):
|
|
2255
2882
|
1. 开工先 pull:\`tiddlywiki_git_sync action=pull\`(rebase + autostash;真冲突会自动 abort 并报冲突文件)。
|
|
2256
2883
|
2. 收工 commit + push:\`tiddlywiki_git_sync action=sync\`(pull → commit → push)。
|
|
2257
2884
|
3. 插件会自动防抖 commit(默认 60s),手动同步用上面的工具。
|
|
2258
2885
|
|
|
2886
|
+
pull 冲突后:先 \`tiddlywiki_git_resolve files=[冲突文件] strategy=keep-local\`(保留本地)或 \`strategy=keep-remote\`(改用远端版本),再重新 pull/sync 整合其余改动。
|
|
2887
|
+
|
|
2259
2888
|
把 wiki 当作长期记忆与知识沉淀的地方:会议纪要、决策记录、调研笔记、随手的想法都可存成独立 tiddler(tag 建议用 inbox/meeting/decision 等便于检索)。
|
|
2260
2889
|
|
|
2261
2890
|
用本插件自动创建笔记时,除了业务性 tag 外,请把「当前工作区(项目)的名字」也作为标签之一加上去(例如 \`tiddlywiki_put\` 的 tags 里带上当前 workspace 名),这样笔记能按项目归集、检索。`;
|
|
@@ -2427,8 +3056,13 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2427
3056
|
};
|
|
2428
3057
|
});
|
|
2429
3058
|
ctx.effect(() => () => {
|
|
2430
|
-
|
|
2431
|
-
|
|
3059
|
+
(async () => {
|
|
3060
|
+
try {
|
|
3061
|
+
await committer?.flush();
|
|
3062
|
+
} catch {}
|
|
3063
|
+
disposeAll();
|
|
3064
|
+
await server.stop();
|
|
3065
|
+
})();
|
|
2432
3066
|
}, "dsh-tiddlywiki: host teardown");
|
|
2433
3067
|
}
|
|
2434
3068
|
//#endregion
|