@pi-claudian/auto-save-to-markdown 0.3.1 → 0.3.3
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 +15 -8
- package/README.zh.md +11 -6
- package/debug.ts +8 -5
- package/index.ts +30 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -78,14 +78,14 @@ session_file: "~/.pi/agent/sessions/--Users-me-project-20260829-050500_ab12.json
|
|
|
78
78
|
|
|
79
79
|
# Fix login redirect loop
|
|
80
80
|
|
|
81
|
-
User
|
|
81
|
+
User <span style="font-size: 0.5em; color: var(--text-faint);">2026-08-29 13:05:12</span>
|
|
82
82
|
===
|
|
83
83
|
|
|
84
84
|
The login page redirects in a loop after the auth refactor...
|
|
85
85
|
|
|
86
86
|
---
|
|
87
87
|
|
|
88
|
-
Assistant
|
|
88
|
+
Assistant <span style="font-size: 0.5em; color: var(--text-faint);">2026-08-29 13:05:40 · claude-sonnet-4-5</span>
|
|
89
89
|
===
|
|
90
90
|
|
|
91
91
|
> [!tldr]- Thinking
|
|
@@ -113,12 +113,15 @@ previews are wrapped in inline code spans (with a delimiter sized to survive
|
|
|
113
113
|
backticks inside the content), so raw tool output renders literally instead of
|
|
114
114
|
being parsed as markdown.
|
|
115
115
|
|
|
116
|
-
Each message block opens with a setext level-1 info header (`User
|
|
117
|
-
underlined with `===`
|
|
118
|
-
typically starts with, and distinguishable from content `#` headings
|
|
119
|
-
parsing
|
|
120
|
-
|
|
121
|
-
|
|
116
|
+
Each message block opens with a setext level-1 info header (`User`,
|
|
117
|
+
`Assistant`) underlined with `===` — one level above the `##` headings AI
|
|
118
|
+
content typically starts with, and distinguishable from content `#` headings
|
|
119
|
+
when parsing. The header's metadata (local date-time, and the model for
|
|
120
|
+
assistant messages) sits in a small faint `<span>` (`0.5em`, Obsidian's
|
|
121
|
+
`--text-faint` color), so the role stays visually dominant while the details
|
|
122
|
+
remain a glance away. Each block ends with a `---` separator wrapped in single
|
|
123
|
+
blank lines (extra blank lines are trimmed), so blocks are easy to tell apart
|
|
124
|
+
both when reading and when splitting the file programmatically.
|
|
122
125
|
|
|
123
126
|
### Fragmented thinking repair
|
|
124
127
|
|
|
@@ -165,6 +168,10 @@ always contains the complete conversation, not the compacted context.
|
|
|
165
168
|
PI_CLAUDIAN_DEBUG=1 pi
|
|
166
169
|
```
|
|
167
170
|
|
|
171
|
+
Any value other than an explicit false token (empty, `0`, `false`, `no`, `off` —
|
|
172
|
+
case-insensitive) enables it; unset the variable (or set one of those tokens)
|
|
173
|
+
to turn it off.
|
|
174
|
+
|
|
168
175
|
## License
|
|
169
176
|
|
|
170
177
|
MIT
|
package/README.zh.md
CHANGED
|
@@ -68,14 +68,14 @@ session_file: "~/.pi/agent/sessions/--Users-me-project-20260829-050500_ab12.json
|
|
|
68
68
|
|
|
69
69
|
# 修复登录重定向死循环
|
|
70
70
|
|
|
71
|
-
User
|
|
71
|
+
User <span style="font-size: 0.5em; color: var(--text-faint);">2026-08-29 13:05:12</span>
|
|
72
72
|
===
|
|
73
73
|
|
|
74
74
|
auth 重构之后登录页一直重定向死循环……
|
|
75
75
|
|
|
76
76
|
---
|
|
77
77
|
|
|
78
|
-
Assistant
|
|
78
|
+
Assistant <span style="font-size: 0.5em; color: var(--text-faint);">2026-08-29 13:05:40 · claude-sonnet-4-5</span>
|
|
79
79
|
===
|
|
80
80
|
|
|
81
81
|
> [!tldr]- Thinking
|
|
@@ -101,10 +101,12 @@ Obsidian 对 HTML 块内嵌 Markdown 的渲染不可靠;在非 Obsidian 环境
|
|
|
101
101
|
压过内容中的反引号序列),工具的原始输出因此按字面渲染,不会被当作
|
|
102
102
|
Markdown 解析。
|
|
103
103
|
|
|
104
|
-
每个消息块以 setext 一级信息头(`User
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
每个消息块以 setext 一级信息头(`User`、`Assistant`,下一行以 `===` 下划)
|
|
105
|
+
开头——高于 AI 内容常见的 `##` 二级标题,解析时也能与内容中的 `#` 一级标题
|
|
106
|
+
区分开。信息头的元数据(本地日期时间,assistant 消息还带模型名)放在一个小号
|
|
107
|
+
浅色 `<span>` 中(`0.5em`,Obsidian 的 `--text-faint` 颜色),角色名因此保持
|
|
108
|
+
醒目,细节又触手可及。每个消息块以"上下各一个空行"包裹的 `---` 分隔线结尾
|
|
109
|
+
(多余空行会被裁剪),无论是阅读还是程序化切分,都能清楚地区分每个消息块。
|
|
108
110
|
|
|
109
111
|
### 碎片化 thinking 修复
|
|
110
112
|
|
|
@@ -144,6 +146,9 @@ markdown 文件只记录**一个分支**——即该分支看到的 root→leaf
|
|
|
144
146
|
PI_CLAUDIAN_DEBUG=1 pi
|
|
145
147
|
```
|
|
146
148
|
|
|
149
|
+
除显式假值(空串、`0`、`false`、`no`、`off`,忽略大小写)以外的任何值都会开启
|
|
150
|
+
调试;取消该变量或将其设为其中某个假值即可关闭。
|
|
151
|
+
|
|
147
152
|
## 许可
|
|
148
153
|
|
|
149
154
|
MIT
|
package/debug.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared debug logging for all @pi-claudian extensions.
|
|
3
3
|
*
|
|
4
|
-
* Enable by setting the PI_CLAUDIAN_DEBUG environment variable to any
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Enable by setting the PI_CLAUDIAN_DEBUG environment variable to any value
|
|
5
|
+
* other than an explicit false token (empty, "0", "false", "no", "off" —
|
|
6
|
+
* case-insensitive). Output goes to stderr via console.error, so it never
|
|
7
|
+
* mixes with pi's stdout and can be captured separately:
|
|
7
8
|
*
|
|
8
9
|
* PI_CLAUDIAN_DEBUG=1 pi # show inline
|
|
9
10
|
* PI_CLAUDIAN_DEBUG=1 pi 2>debug.log # capture to a file
|
|
@@ -15,9 +16,11 @@
|
|
|
15
16
|
*/
|
|
16
17
|
|
|
17
18
|
const TAG = "[pi-claudian]";
|
|
18
|
-
const
|
|
19
|
+
const FALSE_TOKENS = new Set(["", "0", "false", "no", "off"]);
|
|
20
|
+
const raw = process.env.PI_CLAUDIAN_DEBUG?.trim().toLowerCase();
|
|
21
|
+
const enabled = raw !== undefined && !FALSE_TOKENS.has(raw);
|
|
19
22
|
|
|
20
|
-
/** Log a debug message when PI_CLAUDIAN_DEBUG is set. */
|
|
23
|
+
/** Log a debug message when PI_CLAUDIAN_DEBUG is set to an enabling value. */
|
|
21
24
|
export function debug(...args: unknown[]): void {
|
|
22
25
|
if (!enabled) return;
|
|
23
26
|
console.error(TAG, ...args);
|
package/index.ts
CHANGED
|
@@ -21,9 +21,12 @@
|
|
|
21
21
|
* cumulative cost and tokens (input, output, cache read/write), message
|
|
22
22
|
* count, created/updated timestamps, project root and session file.
|
|
23
23
|
* - Body format: every message block opens with a setext-H1 info header
|
|
24
|
-
* (`User
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* (`User <span …>YYYY-MM-DD HH:MM:SS</span>` /
|
|
25
|
+
* `Assistant <span …>YYYY-MM-DD HH:MM:SS · model</span>`, where the span
|
|
26
|
+
* renders the metadata as small faint text — Obsidian CSS variables, so it
|
|
27
|
+
* degrades gracefully elsewhere — underlined with `===`, distinct from the
|
|
28
|
+
* `#`/`##` ATX headings AI content uses) and ends with a `---` separator
|
|
29
|
+
* wrapped in single blank lines.
|
|
27
30
|
* - Tool call/result folding: calls live in the assistant entry while their
|
|
28
31
|
* results are separate toolResult entries; saves pair them by toolCall id
|
|
29
32
|
* and fold each assistant block's calls, with a short result preview each,
|
|
@@ -224,10 +227,28 @@ export default function (pi: ExtensionAPI) {
|
|
|
224
227
|
);
|
|
225
228
|
}
|
|
226
229
|
|
|
227
|
-
/** Local-time
|
|
228
|
-
function
|
|
230
|
+
/** Local-time label for a message entry: YYYY-MM-DD HH:MM:SS. */
|
|
231
|
+
function dateTime(iso: string): string {
|
|
229
232
|
const d = new Date(iso);
|
|
230
|
-
return
|
|
233
|
+
return (
|
|
234
|
+
`${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` +
|
|
235
|
+
` ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Wrap header metadata (timestamp, model) in a small faint span: 0.5em text
|
|
241
|
+
* in Obsidian's `--text-faint` color, so the role stays visually dominant.
|
|
242
|
+
* HTML-escaped because the model string lands inside a raw HTML span.
|
|
243
|
+
*/
|
|
244
|
+
function metaSpan(text: string): string {
|
|
245
|
+
const safe = text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
246
|
+
return `<span style="font-size: 0.5em; color: var(--text-faint);">${safe}</span>`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Setext-H1 info header: `Role <span …>meta</span>` underlined with `===`. */
|
|
250
|
+
function messageHeader(role: string, meta: string): string {
|
|
251
|
+
return `${role} ${metaSpan(meta)}\n===`;
|
|
231
252
|
}
|
|
232
253
|
|
|
233
254
|
/** Make a string safe for use as a filename component. */
|
|
@@ -387,7 +408,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
387
408
|
// precedes the text it produced, instead of being grouped after the fact.
|
|
388
409
|
// Setext H1 (`===` underline): one level above the `##` headings AI
|
|
389
410
|
// content typically starts with, and distinct from content `#` headings.
|
|
390
|
-
const header =
|
|
411
|
+
const header = messageHeader("Assistant", [t, m.model].filter(Boolean).join(" · "));
|
|
391
412
|
const parts: string[] = [];
|
|
392
413
|
const thinkings: string[] = [];
|
|
393
414
|
const flushThinking = () => {
|
|
@@ -436,9 +457,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
436
457
|
const blocks: string[] = [];
|
|
437
458
|
for (const e of entries) {
|
|
438
459
|
const m = e.message;
|
|
439
|
-
const t =
|
|
460
|
+
const t = dateTime(e.timestamp);
|
|
440
461
|
if (m.role === "user") {
|
|
441
|
-
blocks.push(
|
|
462
|
+
blocks.push(`${messageHeader("User", t)}\n\n${userText(m.content)}`);
|
|
442
463
|
} else if (m.role === "assistant") {
|
|
443
464
|
blocks.push(renderAssistant(m, t, results));
|
|
444
465
|
} else if (m.role === "toolResult") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-claudian/auto-save-to-markdown",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
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",
|