@pi-claudian/auto-save-to-markdown 0.3.2 → 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 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 · 13:05:12
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 · 13:05:40 · claude-sonnet-4-5
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 `===`) — one level above the `##` headings AI content
118
- typically starts with, and distinguishable from content `#` headings when
119
- parsing — and ends with a `---` separator wrapped in single blank lines
120
- (extra blank lines are trimmed), so blocks are easy to tell apart both when
121
- reading and when splitting the file programmatically.
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
 
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 · 13:05:12
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 · 13:05:40 · claude-sonnet-4-5
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
- 高于 AI 内容常见的 `##` 二级标题,解析时也能与内容中的 `#` 一级标题区分
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
 
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 · HH:MM:SS` / `Assistant · HH:MM:SS · model`, underlined with
25
- * `===`, distinct from the `#`/`##` ATX headings AI content uses) and
26
- * ends with a `---` separator wrapped in single blank lines.
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 clock label for a message entry: HH:MM:SS. */
228
- function clock(iso: string): string {
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 `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
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 = `Assistant · ${t}${m.model ? ` · ${m.model}` : ""}\n===`;
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 = clock(e.timestamp);
460
+ const t = dateTime(e.timestamp);
440
461
  if (m.role === "user") {
441
- blocks.push(`User · ${t}\n===\n\n${userText(m.content)}`);
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.2",
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",