@pi-claudian/auto-save-to-markdown 0.2.0 → 0.3.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/README.md +17 -15
- package/README.zh.md +15 -14
- package/index.ts +46 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -88,28 +88,30 @@ The login page redirects in a loop after the auth refactor...
|
|
|
88
88
|
Assistant · 13:05:40 · claude-sonnet-4-5
|
|
89
89
|
===
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
Let me check the redirect chain...
|
|
95
|
-
|
|
96
|
-
</details>
|
|
91
|
+
> [!tldr]- Thinking
|
|
92
|
+
>
|
|
93
|
+
> Let me check the redirect chain...
|
|
97
94
|
|
|
98
95
|
I'll trace the middleware order first.
|
|
99
96
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
> **Tool · read** /Users/me/project/src/auth/middleware.ts — 120 lines …
|
|
97
|
+
> [!quote]- Tool Calls · 1 (read)
|
|
98
|
+
> **`read`** `{"filePath":"/Users/me/project/src/auth/middleware.ts"}`
|
|
99
|
+
>
|
|
100
|
+
> > `import { NextResponse } from "next/server"; export function middleware(…) …`
|
|
105
101
|
|
|
106
102
|
---
|
|
107
103
|
```
|
|
108
104
|
|
|
109
|
-
The body renders user and assistant messages in full (assistant thinking
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
105
|
+
The body renders user and assistant messages in full (assistant thinking and
|
|
106
|
+
per-turn tool calls are each folded into a collapsed Obsidian callout —
|
|
107
|
+
`> [!tldr]- Thinking` and `> [!quote]- Tool Calls · …`) and summarizes each
|
|
108
|
+
tool call and result in one line, so the file stays readable while still
|
|
109
|
+
showing what the agent did. Callouts are used instead of HTML `<details>`
|
|
110
|
+
because Obsidian renders markdown inside HTML blocks unreliably; outside
|
|
111
|
+
Obsidian the callouts degrade to plain blockquotes. Result and argument
|
|
112
|
+
previews are wrapped in inline code spans (with a delimiter sized to survive
|
|
113
|
+
backticks inside the content), so raw tool output renders literally instead of
|
|
114
|
+
being parsed as markdown.
|
|
113
115
|
|
|
114
116
|
Each message block opens with a setext level-1 info header (`User · …`,
|
|
115
117
|
underlined with `===`) — one level above the `##` headings AI content
|
package/README.zh.md
CHANGED
|
@@ -78,27 +78,28 @@ auth 重构之后登录页一直重定向死循环……
|
|
|
78
78
|
Assistant · 13:05:40 · claude-sonnet-4-5
|
|
79
79
|
===
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
先看中间件的执行顺序……
|
|
85
|
-
|
|
86
|
-
</details>
|
|
81
|
+
> [!tldr]- Thinking
|
|
82
|
+
>
|
|
83
|
+
> 先看中间件的执行顺序……
|
|
87
84
|
|
|
88
85
|
我先追踪一下中间件链。
|
|
89
86
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
> **Tool · read** /Users/me/project/src/auth/middleware.ts — 120 lines …
|
|
87
|
+
> [!quote]- Tool Calls · 1 (read)
|
|
88
|
+
> **`read`** `{"filePath":"/Users/me/project/src/auth/middleware.ts"}`
|
|
89
|
+
>
|
|
90
|
+
> > `import { NextResponse } from "next/server"; export function middleware(…) …`
|
|
95
91
|
|
|
96
92
|
---
|
|
97
93
|
```
|
|
98
94
|
|
|
99
|
-
正文完整渲染 user / assistant 消息(assistant 的 thinking
|
|
100
|
-
|
|
101
|
-
|
|
95
|
+
正文完整渲染 user / assistant 消息(assistant 的 thinking 与每轮工具调用
|
|
96
|
+
分别折叠在可折叠的 Obsidian callout 中——`> [!tldr]- Thinking` 和
|
|
97
|
+
`> [!quote]- Tool Calls · …`),每个工具调用和结果各压缩成一行摘要,既可读
|
|
98
|
+
又能看出 agent 做了什么。之所以用 callout 而不是 HTML `<details>`,是因为
|
|
99
|
+
Obsidian 对 HTML 块内嵌 Markdown 的渲染不可靠;在非 Obsidian 环境下 callout
|
|
100
|
+
退化为普通引用块。结果与参数预览会包在 inline code 里(分隔符长度会自动
|
|
101
|
+
压过内容中的反引号序列),工具的原始输出因此按字面渲染,不会被当作
|
|
102
|
+
Markdown 解析。
|
|
102
103
|
|
|
103
104
|
每个消息块以 setext 一级信息头(`User · …`,下一行以 `===` 下划)开头——
|
|
104
105
|
高于 AI 内容常见的 `##` 二级标题,解析时也能与内容中的 `#` 一级标题区分
|
package/index.ts
CHANGED
|
@@ -27,11 +27,17 @@
|
|
|
27
27
|
* - Tool call/result folding: calls live in the assistant entry while their
|
|
28
28
|
* results are separate toolResult entries; saves pair them by toolCall id
|
|
29
29
|
* and fold each assistant block's calls, with a short result preview each,
|
|
30
|
-
* into one
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
30
|
+
* into one collapsed Obsidian callout (`> [!quote]- Tool Calls · …`).
|
|
31
|
+
* Thinking folds the same way into `> [!tldr]- Thinking`. Callouts are used
|
|
32
|
+
* instead of HTML `<details>` because Obsidian's views render embedded
|
|
33
|
+
* markdown inside HTML blocks unreliably, while callouts fold and render
|
|
34
|
+
* markdown in both Live Preview and Reading view. Outside Obsidian the
|
|
35
|
+
* callouts degrade to plain blockquotes. Argument previews and result
|
|
36
|
+
* previews are wrapped in inline code spans (delimiter sized to survive
|
|
37
|
+
* backticks inside the content), so raw output renders literally instead
|
|
38
|
+
* of being parsed as markdown. A result whose call was saved in
|
|
39
|
+
* an earlier file (mid-turn manual save) falls back to a standalone
|
|
40
|
+
* one-line block.
|
|
35
41
|
* - Branching: each file records exactly ONE branch (the root→leaf path
|
|
36
42
|
* returned by sessionManager.getBranch()). State is persisted via
|
|
37
43
|
* `pi.appendEntry()` custom entries, which are part of the session tree
|
|
@@ -290,6 +296,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
290
296
|
|
|
291
297
|
// ---------- markdown rendering ----------
|
|
292
298
|
|
|
299
|
+
/**
|
|
300
|
+
* Inline code span for arbitrary raw output (tool results, argument JSON):
|
|
301
|
+
* the delimiter is always one backtick longer than the longest backtick run
|
|
302
|
+
* inside the text, so content that itself contains backticks cannot break
|
|
303
|
+
* the span. Tool output renders literally instead of being parsed as
|
|
304
|
+
* markdown (headings, bold, wiki links …).
|
|
305
|
+
*/
|
|
306
|
+
function inlineCode(text: string): string {
|
|
307
|
+
const longest = (text.match(/`+/g) ?? []).reduce((a, r) => Math.max(a, r.length), 0);
|
|
308
|
+
const fence = "`".repeat(longest + 1);
|
|
309
|
+
return `${fence}${text}${fence}`;
|
|
310
|
+
}
|
|
311
|
+
|
|
293
312
|
/**
|
|
294
313
|
* Strip leading blank lines and trailing whitespace from a rendered block,
|
|
295
314
|
* so joins and separators always keep exactly one blank line around them
|
|
@@ -322,7 +341,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
322
341
|
|
|
323
342
|
/** Standalone one-line block for a result whose call is not in this file. */
|
|
324
343
|
function renderToolResult(m: ToolResultMessage): string {
|
|
325
|
-
return `> **Tool · ${m.toolName}** ${resultPreview(m)}`.trim();
|
|
344
|
+
return `> **Tool · ${m.toolName}** ${inlineCode(resultPreview(m))}`.trim();
|
|
326
345
|
}
|
|
327
346
|
|
|
328
347
|
/** "read, web_search ×2" — tool names with repeat counts, first-seen order. */
|
|
@@ -333,21 +352,30 @@ export default function (pi: ExtensionAPI) {
|
|
|
333
352
|
}
|
|
334
353
|
|
|
335
354
|
/**
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
355
|
+
* Collapsed Obsidian callout (`> [!type]- title`) wrapping a markdown body:
|
|
356
|
+
* every body line is prefixed with `>` (empty lines become bare `>`), so the
|
|
357
|
+
* body keeps rendering as markdown while folding works in both Obsidian
|
|
358
|
+
* views. Outside Obsidian the callout degrades to a plain blockquote.
|
|
359
|
+
*/
|
|
360
|
+
function callout(type: string, title: string, body: string): string {
|
|
361
|
+
const lines = body.split("\n").map((line) => (line ? `> ${line}` : ">"));
|
|
362
|
+
return `> [!${type}]- ${title}\n${lines.join("\n")}`;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Fold tool calls and their paired results into one collapsed callout.
|
|
367
|
+
* Argument JSON and result previews are wrapped in inline code spans, so
|
|
368
|
+
* raw output renders literally instead of being parsed as markdown.
|
|
339
369
|
*/
|
|
340
|
-
function
|
|
370
|
+
function renderToolCallsCallout(calls: RenderedToolCall[]): string {
|
|
341
371
|
const summary = summarizeToolNames(calls.map((c) => c.name));
|
|
342
372
|
const items = calls.map((c) => {
|
|
343
|
-
const head = c.args ? `**\`${c.name}\`**
|
|
344
|
-
const result =
|
|
373
|
+
const head = c.args ? `**\`${c.name}\`** ${inlineCode(c.args)}` : `**\`${c.name}\`**`;
|
|
374
|
+
const result =
|
|
375
|
+
c.result === null ? "_(no result)_" : inlineCode(c.result || "_(empty result)_");
|
|
345
376
|
return `${head}\n\n> ${result}`;
|
|
346
377
|
});
|
|
347
|
-
return (
|
|
348
|
-
`<details>\n<summary>Tool Calls · ${calls.length} (${summary})</summary>\n\n` +
|
|
349
|
-
`${items.join("\n\n")}\n\n</details>`
|
|
350
|
-
);
|
|
378
|
+
return callout("quote", `Tool Calls · ${calls.length} (${summary})`, items.join("\n\n"));
|
|
351
379
|
}
|
|
352
380
|
|
|
353
381
|
function renderAssistant(
|
|
@@ -364,9 +392,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
364
392
|
const thinkings: string[] = [];
|
|
365
393
|
const flushThinking = () => {
|
|
366
394
|
if (thinkings.length) {
|
|
367
|
-
parts.push(
|
|
368
|
-
`<details>\n<summary>Thinking</summary>\n\n${thinkings.join("\n\n")}\n\n</details>`,
|
|
369
|
-
);
|
|
395
|
+
parts.push(callout("tldr", "Thinking", thinkings.join("\n\n")));
|
|
370
396
|
thinkings.length = 0;
|
|
371
397
|
}
|
|
372
398
|
};
|
|
@@ -389,7 +415,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
389
415
|
}
|
|
390
416
|
}
|
|
391
417
|
flushThinking();
|
|
392
|
-
if (calls.length) parts.push(
|
|
418
|
+
if (calls.length) parts.push(renderToolCallsCallout(calls));
|
|
393
419
|
if (m.errorMessage) parts.push(`> Error: ${m.errorMessage.replace(/\s+/g, " ").trim()}`);
|
|
394
420
|
if (parts.length === 0) parts.push("_(empty response)_");
|
|
395
421
|
return `${header}\n\n${parts.join("\n\n")}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-claudian/auto-save-to-markdown",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Pi extension that automatically saves each completed conversation turn as a markdown file with YAML frontmatter, one file per session-tree branch.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|