pi-auto-save-to-markdown 0.7.2 → 0.7.4
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 +24 -10
- package/README.zh.md +19 -9
- package/index.ts +104 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -92,10 +92,10 @@ changes never touch the filename, and manually renamed files are left alone.
|
|
|
92
92
|
---
|
|
93
93
|
title: "Fix login redirect loop"
|
|
94
94
|
agent: "pi"
|
|
95
|
-
format_version: "1.
|
|
95
|
+
format_version: "1.3"
|
|
96
96
|
session_id: "d0a4f541-976d-4d1b-8e1c-30a1f2b3c4d5"
|
|
97
97
|
session_key: "c2088d77"
|
|
98
|
-
|
|
98
|
+
branch_last_entry_id: "019be3a2-1f4d-7c8a-9b01-d23e45f6a7b8"
|
|
99
99
|
model: "z-ai/glm-5.3"
|
|
100
100
|
provider: "openrouter"
|
|
101
101
|
cost: 0.023401
|
|
@@ -105,8 +105,8 @@ tokens_output: 3515
|
|
|
105
105
|
tokens_cache_read: 0
|
|
106
106
|
tokens_cache_write: 0
|
|
107
107
|
messages: 8
|
|
108
|
-
created: "2026-08-
|
|
109
|
-
updated: "2026-08-
|
|
108
|
+
created: "2026-08-29T13:05:12+08:00"
|
|
109
|
+
updated: "2026-08-29T13:42:10+08:00"
|
|
110
110
|
project_root: "/Users/me/project"
|
|
111
111
|
session_file: "~/.pi/agent/sessions/--Users-me-project-20260829-050500_ab12.jsonl"
|
|
112
112
|
---
|
|
@@ -156,13 +156,21 @@ assistant messages) sits in a small faint `<span>` (`0.5em`, Obsidian's
|
|
|
156
156
|
`--text-faint` color), so the role stays visually dominant while the details
|
|
157
157
|
remain a glance away. Each block ends with a `---` separator wrapped in single
|
|
158
158
|
blank lines (extra blank lines are trimmed), so blocks are easy to tell apart
|
|
159
|
-
both when reading and when splitting the file programmatically.
|
|
159
|
+
both when reading and when splitting the file programmatically. The document
|
|
160
|
+
heading sits directly after the frontmatter with no blank line between them;
|
|
161
|
+
appends heal the blank line that older versions wrote there.
|
|
160
162
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
Tool Calls callouts collapsed
|
|
163
|
+
Two views of the same saved file rendered in Obsidian — the
|
|
164
|
+
`<title>-<key>-<time>.md` filename on top, message blocks with role headers
|
|
165
|
+
and timestamps, and the Thinking and Tool Calls callouts collapsed. First
|
|
166
|
+
with the Properties panel expanded, showing all frontmatter fields:
|
|
164
167
|
|
|
165
|
-

|
|
169
|
+
|
|
170
|
+
Then with the Properties panel folded away and the full conversation body in
|
|
171
|
+
view:
|
|
172
|
+
|
|
173
|
+

|
|
166
174
|
|
|
167
175
|
### Fragmented thinking repair
|
|
168
176
|
|
|
@@ -172,7 +180,13 @@ original spaces collapse into leading spaces of one-word fragments joined by
|
|
|
172
180
|
runs of newlines. The extension detects this corruption (lines starting with a
|
|
173
181
|
single leading space, or a majority of 1–2-character fragment lines) and
|
|
174
182
|
re-joins the fragments into flowing text, so saved thinking reads normally
|
|
175
|
-
instead of one word per line.
|
|
183
|
+
instead of one word per line. Paragraph breaks survive the repair: a separator
|
|
184
|
+
run of 3+ newlines that follows a sentence-final character is a real paragraph
|
|
185
|
+
break about three times out of four in corrupted blocks, so exactly those
|
|
186
|
+
separators are restored to blank-line paragraphs while every other separator
|
|
187
|
+
joins — a break is never inserted mid-sentence; worst case, one lands between
|
|
188
|
+
two complete sentences, which still reads fine. Clean thinking blocks are
|
|
189
|
+
written untouched.
|
|
176
190
|
|
|
177
191
|
`cost` and the token fields cover the whole saved branch and include cached
|
|
178
192
|
tokens (priced at the provider's cache rates), so the totals are comparable
|
package/README.zh.md
CHANGED
|
@@ -66,10 +66,10 @@ PI_SAVE_CONVERSATION_DIR=notes/ai pi
|
|
|
66
66
|
---
|
|
67
67
|
title: "修复登录重定向死循环"
|
|
68
68
|
agent: "pi"
|
|
69
|
-
format_version: "1.
|
|
69
|
+
format_version: "1.3"
|
|
70
70
|
session_id: "d0a4f541-976d-4d1b-8e1c-30a1f2b3c4d5"
|
|
71
71
|
session_key: "c2088d77"
|
|
72
|
-
|
|
72
|
+
branch_last_entry_id: "019be3a2-1f4d-7c8a-9b01-d23e45f6a7b8"
|
|
73
73
|
model: "z-ai/glm-5.3"
|
|
74
74
|
provider: "openrouter"
|
|
75
75
|
cost: 0.023401
|
|
@@ -79,8 +79,8 @@ tokens_output: 3515
|
|
|
79
79
|
tokens_cache_read: 0
|
|
80
80
|
tokens_cache_write: 0
|
|
81
81
|
messages: 8
|
|
82
|
-
created: "2026-08-
|
|
83
|
-
updated: "2026-08-
|
|
82
|
+
created: "2026-08-29T13:05:12+08:00"
|
|
83
|
+
updated: "2026-08-29T13:42:10+08:00"
|
|
84
84
|
project_root: "/Users/me/project"
|
|
85
85
|
session_file: "~/.pi/agent/sessions/--Users-me-project-20260829-050500_ab12.jsonl"
|
|
86
86
|
---
|
|
@@ -126,12 +126,19 @@ Markdown 解析。
|
|
|
126
126
|
浅色 `<span>` 中(`0.5em`,Obsidian 的 `--text-faint` 颜色),角色名因此保持
|
|
127
127
|
醒目,细节又触手可及。每个消息块以"上下各一个空行"包裹的 `---` 分隔线结尾
|
|
128
128
|
(多余空行会被裁剪),无论是阅读还是程序化切分,都能清楚地区分每个消息块。
|
|
129
|
+
文档标题紧跟在 frontmatter 之后,中间没有空行;追加保存时会顺带修复旧版本
|
|
130
|
+
在两者之间写下的空行。
|
|
129
131
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
两个 callout
|
|
132
|
+
同一个保存文件在 Obsidian 中的两种渲染视图——顶部为
|
|
133
|
+
`<标题>-<key>-<时间>.md` 文件名,消息块带角色信息头和时间戳,Thinking
|
|
134
|
+
与 Tool Calls 两个 callout 处于折叠状态。首先是 Properties 面板展开、
|
|
135
|
+
展示全部 frontmatter 字段的效果:
|
|
133
136
|
|
|
134
|
-

|
|
138
|
+
|
|
139
|
+
然后是 Properties 面板折叠、完整对话正文的效果:
|
|
140
|
+
|
|
141
|
+

|
|
135
142
|
|
|
136
143
|
### 碎片化 thinking 修复
|
|
137
144
|
|
|
@@ -139,7 +146,10 @@ Markdown 解析。
|
|
|
139
146
|
存成一词一行、甚至一字一行:原始空格塌缩成碎片行开头的单个空格,碎片之间
|
|
140
147
|
被成串的换行拼接。扩展会检测这种损坏(依据带单个前导空格的行、或大量
|
|
141
148
|
1–2 字符碎片行),把碎片重新接回通顺的文本,保存的 thinking 不再一行
|
|
142
|
-
|
|
149
|
+
一词。段落分隔在修复后得以保留:句末标点之后紧跟 3 个以上换行的分隔串,
|
|
150
|
+
在损坏块中约四分之三是真实的段落边界,因此恰好这类分隔被还原成空行段落,
|
|
151
|
+
其余全部拼接——断行永远不会插进句子中间,最坏也只是落在两个完整句子
|
|
152
|
+
之间,阅读不受影响。正常的 thinking 块原样保存,不做任何改动。
|
|
143
153
|
|
|
144
154
|
`cost` 和 token 字段统计整条已保存分支,且包含缓存 token(按供应商缓存
|
|
145
155
|
价格计费),因此总计可与供应商侧账单(如 OpenRouter Activity)对照。未
|
package/index.ts
CHANGED
|
@@ -25,11 +25,14 @@
|
|
|
25
25
|
* plugins for other runtimes would write their own value), format_version
|
|
26
26
|
* (the document format of the last write — additive frontmatter fields
|
|
27
27
|
* never bump it; see FORMAT_VERSION), session id, session key (the
|
|
28
|
-
* filename key), last entry id (
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
28
|
+
* filename key), branch last entry id (field `branch_last_entry_id` — the
|
|
29
|
+
* id of the deepest message entry on the saved branch: the file's position
|
|
30
|
+
* in the session jsonl tree at the last write, scoped to this file's
|
|
31
|
+
* branch, not the session-wide last entry), model, provider, cumulative
|
|
32
|
+
* cost and tokens (input, output,
|
|
33
|
+
* cache read/write), message count, created/updated timestamps (tz-aware
|
|
34
|
+
* ISO 8601 in the local timezone with its numeric UTC offset, e.g.
|
|
35
|
+
* "2026-08-29T13:05:12+08:00"), project root and session file.
|
|
33
36
|
* - Body format: every message block opens with a setext-H1 info header
|
|
34
37
|
* (`User <span …>YYYY-MM-DD HH:MM:SS</span>` /
|
|
35
38
|
* `Assistant <span …>YYYY-MM-DD HH:MM:SS · model</span>`, where the span
|
|
@@ -114,7 +117,9 @@
|
|
|
114
117
|
* still exports its complete history.
|
|
115
118
|
* - Thinking repair: reasoning blocks stored with the upstream
|
|
116
119
|
* newline-fragmentation corruption (one word per line) are detected and
|
|
117
|
-
* re-joined into flowing text before saving
|
|
120
|
+
* re-joined into flowing text before saving, keeping paragraph breaks
|
|
121
|
+
* where they survive as long separator runs after sentence ends; clean
|
|
122
|
+
* thinking is untouched.
|
|
118
123
|
*
|
|
119
124
|
* Manual commands:
|
|
120
125
|
* - `/save-conversation` saves the current branch immediately and reports
|
|
@@ -190,10 +195,11 @@ const SAVE_STATE_SCHEMA = "1.2";
|
|
|
190
195
|
* structural breaks that change how a parser or migration tool must match
|
|
191
196
|
* blocks (message header structure, `---` separators, callout syntax);
|
|
192
197
|
* MINOR for parse-invariant tweaks and bugfixes (header styling, content
|
|
193
|
-
* transforms like the thinking repair
|
|
194
|
-
*
|
|
198
|
+
* transforms like the thinking repair, the blank line once written between
|
|
199
|
+
* the frontmatter and the document heading); additive frontmatter fields do
|
|
200
|
+
* NOT bump it — they are invisible to any within-major parser.
|
|
195
201
|
*/
|
|
196
|
-
const FORMAT_VERSION = "1.
|
|
202
|
+
const FORMAT_VERSION = "1.3";
|
|
197
203
|
|
|
198
204
|
/**
|
|
199
205
|
* Package version of this extension, read best-effort from the adjacent
|
|
@@ -245,8 +251,8 @@ interface SaveState {
|
|
|
245
251
|
/**
|
|
246
252
|
* Id of the session tree leaf at save time (getLeafId()) — may be a custom
|
|
247
253
|
* state entry rather than a message; used to rank continuation candidates
|
|
248
|
-
* on the current path. Distinct from frontmatter `
|
|
249
|
-
* the deepest message entry (an id actually present in the file).
|
|
254
|
+
* on the current path. Distinct from frontmatter `branch_last_entry_id`,
|
|
255
|
+
* which is the deepest message entry (an id actually present in the file).
|
|
250
256
|
*/
|
|
251
257
|
lastSavedEntryId: string | null;
|
|
252
258
|
file: string;
|
|
@@ -385,10 +391,11 @@ interface BranchMeta {
|
|
|
385
391
|
sessionKey: string;
|
|
386
392
|
/**
|
|
387
393
|
* Id of the deepest message entry on the saved branch at the last write
|
|
388
|
-
* (field name `
|
|
389
|
-
* session jsonl tree
|
|
394
|
+
* (field name `branch_last_entry_id`) — the file's exact position in the
|
|
395
|
+
* session jsonl tree, scoped to this file's branch rather than the
|
|
396
|
+
* session-wide last entry. Updated on every append, like `updated`.
|
|
390
397
|
*/
|
|
391
|
-
|
|
398
|
+
branchLastEntryId: string;
|
|
392
399
|
model: string | null;
|
|
393
400
|
provider: string | null;
|
|
394
401
|
cost: number;
|
|
@@ -416,10 +423,21 @@ interface BranchMeta {
|
|
|
416
423
|
* " " lines included), and an excess of 1–2-char non-list-marker lines (CJK
|
|
417
424
|
* fragments carry no leading space). Clean thinking never matches either.
|
|
418
425
|
*
|
|
419
|
-
* Repair
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
*
|
|
426
|
+
* Repair re-joins the fragments into flowing text. Word separators — the
|
|
427
|
+
* whitespace runs between two fragments — lose their newlines: run length
|
|
428
|
+
* alone carries no recoverable meaning, because the same word separator
|
|
429
|
+
* appears as 1, 2 or 3 newlines depending on the block. Original paragraph
|
|
430
|
+
* boundaries survive as a faint but strong signal: a separator run of 3+
|
|
431
|
+
* newlines that follows a sentence-final character (closing quotes and
|
|
432
|
+
* brackets skipped when looking) marks a real paragraph break 73–93% of the
|
|
433
|
+
* time in corrupted blocks, while plain word separators sit mid-sentence —
|
|
434
|
+
* so exactly those separators become blank-line paragraph breaks and
|
|
435
|
+
* everything else is joined. The sentence-final guard means a break is never
|
|
436
|
+
* inserted mid-sentence: worst case, one lands between two complete
|
|
437
|
+
* sentences, which still reads fine. Join spacing comes from the separator
|
|
438
|
+
* itself: a separator containing a surviving space joins with one space, a
|
|
439
|
+
* bare one (CJK fragments, attached punctuation) joins with nothing. Clean
|
|
440
|
+
* blocks pass through untouched.
|
|
423
441
|
*/
|
|
424
442
|
|
|
425
443
|
/** Line whose single leading space is a survived word separator. */
|
|
@@ -445,14 +463,42 @@ function isFragmentedThinking(s: string): boolean {
|
|
|
445
463
|
return nonBlank.filter(isThinkingShortFragment).length / nonBlank.length >= 0.4;
|
|
446
464
|
}
|
|
447
465
|
|
|
466
|
+
/** Sentence-final characters: a long separator run after one may be a paragraph break. */
|
|
467
|
+
const THINKING_SENTENCE_END = /[.!?。!?…]/;
|
|
468
|
+
/** Closing punctuation skipped when looking for the sentence end behind it. */
|
|
469
|
+
const THINKING_CLOSING = /[)\]}"'”』」)】》]/;
|
|
470
|
+
/** Newlines a separator run needs before it can count as a paragraph break. */
|
|
471
|
+
const THINKING_PARAGRAPH_RUN = 3;
|
|
472
|
+
|
|
448
473
|
/** Repair newline-fragmented thinking; clean thinking is returned unchanged. */
|
|
449
474
|
function repairThinking(s: string): string {
|
|
450
475
|
if (!isFragmentedThinking(s)) return s;
|
|
451
476
|
debug("repairing fragmented thinking block:", s.length, "chars");
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
477
|
+
let out = "";
|
|
478
|
+
let i = 0;
|
|
479
|
+
while (i < s.length) {
|
|
480
|
+
let end = i;
|
|
481
|
+
while (end < s.length && !/\s/.test(s[end])) end++;
|
|
482
|
+
out += s.slice(i, end);
|
|
483
|
+
let next = end;
|
|
484
|
+
while (next < s.length && /\s/.test(s[next])) next++;
|
|
485
|
+
if (next >= s.length) break; // trailing whitespace: drop
|
|
486
|
+
const sep = s.slice(end, next);
|
|
487
|
+
if (!/[\n\r]/.test(sep)) {
|
|
488
|
+
out += " "; // plain spaces: a single word separator
|
|
489
|
+
} else {
|
|
490
|
+
const newlines = sep.match(/[\n\r]/g)!.length;
|
|
491
|
+
let p = out.length - 1;
|
|
492
|
+
while (p >= 0 && THINKING_CLOSING.test(out[p])) p--;
|
|
493
|
+
const sentenceEnd = p >= 0 && THINKING_SENTENCE_END.test(out[p]);
|
|
494
|
+
if (newlines >= THINKING_PARAGRAPH_RUN && sentenceEnd) out += "\n\n";
|
|
495
|
+
else if (/[ \t]/.test(sep)) out += " ";
|
|
496
|
+
// A bare newline separator attached CJK fragments or punctuation:
|
|
497
|
+
// join with nothing.
|
|
498
|
+
}
|
|
499
|
+
i = next;
|
|
500
|
+
}
|
|
501
|
+
return out.replace(/[ \t]{2,}/g, " ").trim();
|
|
456
502
|
}
|
|
457
503
|
|
|
458
504
|
// ---------- foreign sessions for /save-conversation-all ----------
|
|
@@ -1055,6 +1101,24 @@ export default function (pi: ExtensionAPI) {
|
|
|
1055
1101
|
return `"${safe.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
1056
1102
|
}
|
|
1057
1103
|
|
|
1104
|
+
/**
|
|
1105
|
+
* ISO 8601 timestamp in the machine's local timezone, with the numeric
|
|
1106
|
+
* UTC offset appended (e.g. "2026-08-29T13:05:12+08:00"): tz-aware, so the
|
|
1107
|
+
* value reads as local wall-clock time without assuming the reader's
|
|
1108
|
+
* timezone. Legacy files written with UTC "Z" values parse identically.
|
|
1109
|
+
*/
|
|
1110
|
+
function localIsoTimestamp(date: Date): string {
|
|
1111
|
+
const pad = (n: number) => String(n).padStart(2, "0");
|
|
1112
|
+
const offsetMin = -date.getTimezoneOffset();
|
|
1113
|
+
const sign = offsetMin >= 0 ? "+" : "-";
|
|
1114
|
+
const abs = Math.abs(offsetMin);
|
|
1115
|
+
return (
|
|
1116
|
+
`${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}` +
|
|
1117
|
+
`T${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}` +
|
|
1118
|
+
`${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`
|
|
1119
|
+
);
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1058
1122
|
function frontmatter(meta: BranchMeta): string {
|
|
1059
1123
|
const lines: string[] = ["---"];
|
|
1060
1124
|
lines.push(`title: ${yamlQuote(meta.title)}`);
|
|
@@ -1062,7 +1126,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1062
1126
|
lines.push(`format_version: ${yamlQuote(FORMAT_VERSION)}`);
|
|
1063
1127
|
if (meta.sessionId) lines.push(`session_id: ${yamlQuote(meta.sessionId)}`);
|
|
1064
1128
|
lines.push(`session_key: ${yamlQuote(meta.sessionKey)}`);
|
|
1065
|
-
lines.push(`
|
|
1129
|
+
lines.push(`branch_last_entry_id: ${yamlQuote(meta.branchLastEntryId)}`);
|
|
1066
1130
|
if (meta.model) lines.push(`model: ${yamlQuote(meta.model)}`);
|
|
1067
1131
|
if (meta.provider) lines.push(`provider: ${yamlQuote(meta.provider)}`);
|
|
1068
1132
|
lines.push(`cost: ${meta.cost.toFixed(6)}`);
|
|
@@ -1135,14 +1199,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
1135
1199
|
tokensCacheWrite += usage.cacheWrite ?? 0;
|
|
1136
1200
|
}
|
|
1137
1201
|
}
|
|
1138
|
-
const now = new Date()
|
|
1202
|
+
const now = localIsoTimestamp(new Date());
|
|
1139
1203
|
return {
|
|
1140
1204
|
title: displayTitle(ctx, firstUserText(pathMessages)),
|
|
1141
1205
|
agent: agent ?? AGENT,
|
|
1142
1206
|
sessionId: ctx.session.getSessionId(),
|
|
1143
1207
|
sessionFile: ctx.session.getSessionFile() ?? null,
|
|
1144
1208
|
sessionKey,
|
|
1145
|
-
|
|
1209
|
+
branchLastEntryId: pathMessages[pathMessages.length - 1].id,
|
|
1146
1210
|
model,
|
|
1147
1211
|
provider,
|
|
1148
1212
|
cost,
|
|
@@ -1405,7 +1469,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
1405
1469
|
|
|
1406
1470
|
function replaceFrontmatter(existing: string, fm: string): string {
|
|
1407
1471
|
if (/^---\r?\n[\s\S]*?\r?\n---\r?\n/.test(existing)) {
|
|
1408
|
-
|
|
1472
|
+
const rest = existing.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, "");
|
|
1473
|
+
// Heal the old layout's blank line(s) between the frontmatter and the
|
|
1474
|
+
// document heading (new files write the heading directly after the
|
|
1475
|
+
// frontmatter). Only blank lines immediately followed by a `#` heading
|
|
1476
|
+
// at the very start of the body are stripped — anything the user
|
|
1477
|
+
// reorganized stays untouched.
|
|
1478
|
+
const healed = rest.replace(/^(?:[ \t]*\r?\n)+(#[^\r\n]*)/, "$1");
|
|
1479
|
+
return fm + "\n" + healed;
|
|
1409
1480
|
}
|
|
1410
1481
|
return `${fm}\n\n${existing}`;
|
|
1411
1482
|
}
|
|
@@ -1414,10 +1485,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
1414
1485
|
* Rewrite the document heading (the `# title` line written at file
|
|
1415
1486
|
* creation) to the current title. Only called on rename-on-title saves.
|
|
1416
1487
|
* The heading is located as the first non-blank line after the frontmatter
|
|
1417
|
-
* closing delimiter
|
|
1418
|
-
*
|
|
1419
|
-
*
|
|
1420
|
-
*
|
|
1488
|
+
* closing delimiter — new files write it directly after the frontmatter
|
|
1489
|
+
* with no blank line, legacy files wrote exactly one blank line before it
|
|
1490
|
+
* (replaceFrontmatter heals that away on appends) — rather than "the
|
|
1491
|
+
* first # line anywhere", so a manually deleted heading (whose place
|
|
1492
|
+
* would otherwise be taken by some content heading further down) cannot
|
|
1493
|
+
* be mis-rewritten.
|
|
1421
1494
|
*/
|
|
1422
1495
|
function rewriteDocumentHeading(content: string, title: string): string {
|
|
1423
1496
|
const fm = /^---\r?\n[\s\S]*?\r?\n---\r?\n/.exec(content);
|
|
@@ -1643,7 +1716,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1643
1716
|
const target = path.join(plan.dir, plan.filename);
|
|
1644
1717
|
const meta = computeMeta(ctx, plan.pathMessages, plan.sessionKey, undefined, undefined);
|
|
1645
1718
|
const body = renderEntries(plan.pathMessages); // ends with the trailing separator
|
|
1646
|
-
const content = `${frontmatter(meta)}\n
|
|
1719
|
+
const content = `${frontmatter(meta)}\n# ${meta.title}\n\n${body}`;
|
|
1647
1720
|
await atomicWrite(target, content);
|
|
1648
1721
|
debug("created conversation file:", target);
|
|
1649
1722
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-auto-save-to-markdown",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
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",
|