@wuyaos/pi-sync 1.2.0 → 1.2.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.
|
@@ -85,6 +85,12 @@ export interface EnhancedSelectOptions {
|
|
|
85
85
|
* key takes precedence over filter input, mirroring shortcut-key behavior.
|
|
86
86
|
*/
|
|
87
87
|
actionKeys?: Array<{ key: string; label: string }>;
|
|
88
|
+
/**
|
|
89
|
+
* Initial highlighted index (0-based). Defaults to 0. Clamped to the
|
|
90
|
+
* valid item range. Use to preserve cursor position across re-renders in
|
|
91
|
+
* looping menus (e.g. a config toggle that re-enters the select).
|
|
92
|
+
*/
|
|
93
|
+
initialIndex?: number;
|
|
88
94
|
}
|
|
89
95
|
|
|
90
96
|
/** Sentinel prefix marking an action-key result from enhancedSelect. */
|
|
@@ -143,6 +149,13 @@ class EnhancedSelectComponent {
|
|
|
143
149
|
this.shortcutMap.set(key, i);
|
|
144
150
|
}
|
|
145
151
|
}
|
|
152
|
+
|
|
153
|
+
// Preserve caller-provided cursor position; clamp to valid range so a
|
|
154
|
+
// stale index from a previous (longer) item list cannot overflow.
|
|
155
|
+
const initial = this.options.initialIndex ?? 0;
|
|
156
|
+
this.selectedIdx = this.items.length > 0
|
|
157
|
+
? Math.max(0, Math.min(initial, this.items.length - 1))
|
|
158
|
+
: 0;
|
|
146
159
|
}
|
|
147
160
|
|
|
148
161
|
// ── Fuzzy filter state ──────────────────────────────────────────────
|
package/extensions/sync/i18n.ts
CHANGED
|
@@ -6,9 +6,9 @@ const EN = {
|
|
|
6
6
|
enabled: "enabled",
|
|
7
7
|
disabled: "disabled",
|
|
8
8
|
keepAll: "keep all",
|
|
9
|
-
cancel: "
|
|
10
|
-
back: "
|
|
11
|
-
save: "s Save",
|
|
9
|
+
cancel: "x ✕ Cancel",
|
|
10
|
+
back: "b ← Back",
|
|
11
|
+
save: "s ✓ Save",
|
|
12
12
|
|
|
13
13
|
menuTitle: "Pi WebDAV Backup",
|
|
14
14
|
uploadAllBackups: "⬆️ Back Up All",
|
|
@@ -101,9 +101,9 @@ const ZH: Record<TranslationKey, string> = {
|
|
|
101
101
|
enabled: "已启用",
|
|
102
102
|
disabled: "已禁用",
|
|
103
103
|
keepAll: "全部保留",
|
|
104
|
-
cancel: "
|
|
105
|
-
back: "
|
|
106
|
-
save: "s 保存",
|
|
104
|
+
cancel: "x ✕ 取消",
|
|
105
|
+
back: "b ← 返回",
|
|
106
|
+
save: "s ✓ 保存",
|
|
107
107
|
|
|
108
108
|
menuTitle: "Pi WebDAV 备份",
|
|
109
109
|
uploadAllBackups: "⬆️ 全部备份",
|
package/extensions/sync/menus.ts
CHANGED
|
@@ -73,8 +73,9 @@ async function selectAction<T extends string>(
|
|
|
73
73
|
ctx: ExtensionCommandContext,
|
|
74
74
|
title: string,
|
|
75
75
|
items: SelectItem<T>[],
|
|
76
|
+
initialIndex = 0,
|
|
76
77
|
): Promise<T | undefined> {
|
|
77
|
-
const selected = await enhancedSelect(ctx, title, items.map((item) => item.label));
|
|
78
|
+
const selected = await enhancedSelect(ctx, title, items.map((item) => item.label), { initialIndex });
|
|
78
79
|
return items.find((item) => item.label === selected)?.id;
|
|
79
80
|
}
|
|
80
81
|
|
|
@@ -135,6 +136,7 @@ function sessionSelectionLabel(config: SyncConfig): string {
|
|
|
135
136
|
}
|
|
136
137
|
|
|
137
138
|
async function showSessionProjectSelect(ctx: ExtensionCommandContext, config: SyncConfig): Promise<void> {
|
|
139
|
+
let cursor = 0;
|
|
138
140
|
while (true) {
|
|
139
141
|
const language = config.language;
|
|
140
142
|
const projects = listSessionProjects();
|
|
@@ -149,8 +151,9 @@ async function showSessionProjectSelect(ctx: ExtensionCommandContext, config: Sy
|
|
|
149
151
|
})),
|
|
150
152
|
{ id: "back", label: t(language, "back") },
|
|
151
153
|
];
|
|
152
|
-
const action = await selectAction(ctx, t(language, "selectSessionProjects", { mode: modeLabel }), items);
|
|
154
|
+
const action = await selectAction(ctx, t(language, "selectSessionProjects", { mode: modeLabel }), items, cursor);
|
|
153
155
|
if (!action || action === "back") return;
|
|
156
|
+
cursor = Math.max(0, items.findIndex((item) => item.id === action));
|
|
154
157
|
if (action === "mode") {
|
|
155
158
|
config.sessionProjectMode = config.sessionProjectMode === "whitelist" ? "blacklist" : "whitelist";
|
|
156
159
|
} else if (action === "all") {
|
|
@@ -182,6 +185,7 @@ export async function showSetupWizard(ctx: ExtensionCommandContext): Promise<boo
|
|
|
182
185
|
|
|
183
186
|
export async function showConfigureSettings(ctx: ExtensionCommandContext): Promise<void> {
|
|
184
187
|
const config = cloneConfig(loadConfig());
|
|
188
|
+
let cursor = 0;
|
|
185
189
|
while (true) {
|
|
186
190
|
const language = config.language;
|
|
187
191
|
const items: SelectItem<string>[] = [
|
|
@@ -199,8 +203,9 @@ export async function showConfigureSettings(ctx: ExtensionCommandContext): Promi
|
|
|
199
203
|
{ id: "save", label: t(language, "save") },
|
|
200
204
|
{ id: "back", label: t(language, "back") },
|
|
201
205
|
];
|
|
202
|
-
const action = await selectAction(ctx, t(language, "configureTitle"), items);
|
|
206
|
+
const action = await selectAction(ctx, t(language, "configureTitle"), items, cursor);
|
|
203
207
|
if (!action || action === "back") return;
|
|
208
|
+
cursor = Math.max(0, items.findIndex((item) => item.id === action));
|
|
204
209
|
if (action === "save") {
|
|
205
210
|
saveConfig(config, ctx);
|
|
206
211
|
ctx.ui.notify(t(language, "configSaved"), "info");
|
|
@@ -361,6 +366,19 @@ async function showRestorePackage(
|
|
|
361
366
|
)) return false;
|
|
362
367
|
}
|
|
363
368
|
const restored = kind === "pi" ? await extractPiAgentZip(local) : await extractAgentSkillsZip(local);
|
|
369
|
+
if (kind === "pi") {
|
|
370
|
+
try {
|
|
371
|
+
// ctx.reload() 只重载扩展等资源,不会刷新内存中的 ModelRegistry。
|
|
372
|
+
// 禁用网络请求,仅重读刚恢复的 ~/.pi/agent/models.json,使模型选择器立即可见最新目录。
|
|
373
|
+
const refresh = await ctx.modelRegistry.refresh({ allowNetwork: false });
|
|
374
|
+
if (refresh.errors.size > 0) {
|
|
375
|
+
console.warn(`[pi-sync] Model catalog refresh completed with ${refresh.errors.size} error(s)`);
|
|
376
|
+
}
|
|
377
|
+
} catch (error) {
|
|
378
|
+
// 文件已恢复;模型刷新失败不应让整个恢复操作误报失败,重启 Pi 后仍会重新读取 models.json。
|
|
379
|
+
console.warn(`[pi-sync] Failed to refresh restored model catalog: ${error instanceof Error ? error.message : String(error)}`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
364
382
|
ctx.ui.notify(t(language, "restoreCompleted", { kind: label, contents: restored.join("\n") }), "info");
|
|
365
383
|
if (kind === "pi" && reloadAfter && await ctx.ui.confirm(t(language, "reloadRuntimeTitle"), t(language, "reloadRuntimeBody"))) {
|
|
366
384
|
await ctx.reload();
|
|
@@ -15,6 +15,9 @@ async function extractToTemp(archivePath: string, prefix: string): Promise<strin
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
function assertSafeRestoreParent(root: string, destination: string): void {
|
|
18
|
+
// destination 就是 root 本身(sessions 顶层合并的目标)是安全的:
|
|
19
|
+
// 此时 dirname(destination) 在 root 之外,会被下面的检查误判为路径逃逸。
|
|
20
|
+
if (path.resolve(destination) === path.resolve(root)) return;
|
|
18
21
|
const relative = path.relative(root, path.dirname(destination));
|
|
19
22
|
if (relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(`Restore destination escapes root: ${destination}`);
|
|
20
23
|
let current = root;
|
|
@@ -134,13 +137,14 @@ export async function extractAgentSkillsZip(archivePath: string, targetDir = AGE
|
|
|
134
137
|
}
|
|
135
138
|
}
|
|
136
139
|
|
|
137
|
-
export async function extractSessionsArchiveZip(archivePath: string): Promise<string[]> {
|
|
140
|
+
export async function extractSessionsArchiveZip(archivePath: string, targetDir = SESSIONS_DIR): Promise<string[]> {
|
|
138
141
|
const tempDir = await extractToTemp(archivePath, "pi_sessions_extract");
|
|
139
142
|
try {
|
|
140
143
|
const source = path.join(tempDir, "sessions");
|
|
141
144
|
if (!fs.existsSync(source)) throw new Error("Archive does not contain sessions/.");
|
|
142
|
-
|
|
143
|
-
|
|
145
|
+
const target = path.resolve(targetDir);
|
|
146
|
+
fs.mkdirSync(target, { recursive: true });
|
|
147
|
+
const fileCount = copyExtractedTree(source, target, target);
|
|
144
148
|
return [`Session archive merged: ${fileCount} file(s)`];
|
|
145
149
|
} finally { fs.rmSync(tempDir, { recursive: true, force: true }); }
|
|
146
150
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wuyaos/pi-sync",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "WebDAV archive backup and restore for Pi agent data, shared skills, and per-project sessions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
],
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|
|
14
|
-
"url": "https://github.com/wuyaos/pi-packages.git",
|
|
14
|
+
"url": "git+https://github.com/wuyaos/pi-packages.git",
|
|
15
15
|
"directory": "pi-sync"
|
|
16
16
|
},
|
|
17
17
|
"publishConfig": {
|