@narumitw/pi-sync 0.50.3 → 0.51.0
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 +4 -1
- package/dist/chunks/{chunk-6LP3MCR2.ts → chunk-2JZFHNLM.ts} +3 -5
- package/dist/chunks/chunk-2JZFHNLM.ts.map +7 -0
- package/dist/chunks/{chunk-LCRI3ABW.ts → chunk-2RZSQWBH.ts} +8 -19
- package/dist/chunks/chunk-2RZSQWBH.ts.map +7 -0
- package/dist/chunks/{chunk-IJ6FJFA5.ts → chunk-4AIHXRKE.ts} +11 -17
- package/dist/chunks/chunk-4AIHXRKE.ts.map +7 -0
- package/dist/chunks/{chunk-C7XY5NF5.ts → chunk-4MPIH46N.ts} +29 -51
- package/dist/chunks/chunk-4MPIH46N.ts.map +7 -0
- package/dist/chunks/{chunk-5B3FXBLE.ts → chunk-6546TS54.ts} +16 -20
- package/dist/chunks/chunk-6546TS54.ts.map +7 -0
- package/dist/chunks/{chunk-Q2TUM5X6.ts → chunk-6NT3YFGY.ts} +2 -2
- package/dist/chunks/chunk-6NT3YFGY.ts.map +7 -0
- package/dist/chunks/{chunk-2B2ELUF5.ts → chunk-7D5SF7P3.ts} +1 -1
- package/dist/chunks/chunk-7D5SF7P3.ts.map +7 -0
- package/dist/chunks/{chunk-YDWUZVPN.ts → chunk-7KHKWL3Q.ts} +28 -47
- package/dist/chunks/chunk-7KHKWL3Q.ts.map +7 -0
- package/dist/chunks/{chunk-ZI5MXMEX.ts → chunk-BVVP33U5.ts} +2 -4
- package/dist/chunks/chunk-BVVP33U5.ts.map +7 -0
- package/dist/chunks/{chunk-SZWBGPID.ts → chunk-DM4X4FIA.ts} +8 -13
- package/dist/chunks/chunk-DM4X4FIA.ts.map +7 -0
- package/dist/chunks/{chunk-SL64RLXR.ts → chunk-DZUSTGQV.ts} +8 -16
- package/dist/chunks/chunk-DZUSTGQV.ts.map +7 -0
- package/dist/chunks/{chunk-YHEMENKV.ts → chunk-E42GXD6D.ts} +13 -19
- package/dist/chunks/chunk-E42GXD6D.ts.map +7 -0
- package/dist/chunks/{chunk-Z4DQF6L3.ts → chunk-ECYCNZOE.ts} +1 -1
- package/dist/chunks/chunk-ECYCNZOE.ts.map +7 -0
- package/dist/chunks/{chunk-OEX4ABY6.ts → chunk-EEJPYTWE.ts} +11 -21
- package/dist/chunks/chunk-EEJPYTWE.ts.map +7 -0
- package/dist/chunks/{chunk-CPEHGXJV.ts → chunk-G4RUXRWZ.ts} +3 -5
- package/dist/chunks/chunk-G4RUXRWZ.ts.map +7 -0
- package/dist/chunks/{chunk-T2B5IU74.ts → chunk-HGJH56KR.ts} +4 -4
- package/dist/chunks/chunk-HGJH56KR.ts.map +7 -0
- package/dist/chunks/{chunk-JAAKLL6M.ts → chunk-IVXEDPAD.ts} +10 -34
- package/dist/chunks/chunk-IVXEDPAD.ts.map +7 -0
- package/dist/chunks/chunk-K3RIEL6J.ts +20 -0
- package/dist/chunks/chunk-K3RIEL6J.ts.map +7 -0
- package/dist/chunks/{chunk-LTBWGSBV.ts → chunk-MSSW6F3R.ts} +1 -1
- package/dist/chunks/chunk-MSSW6F3R.ts.map +7 -0
- package/dist/chunks/{chunk-T3HH3UEJ.ts → chunk-OVDTF5CH.ts} +4 -10
- package/dist/chunks/chunk-OVDTF5CH.ts.map +7 -0
- package/dist/chunks/{chunk-NJFUIVYL.ts → chunk-PWMWOJP4.ts} +1 -1
- package/dist/chunks/chunk-PWMWOJP4.ts.map +7 -0
- package/dist/chunks/{chunk-HUDQATCL.ts → chunk-Q32CQWPM.ts} +6 -8
- package/dist/chunks/chunk-Q32CQWPM.ts.map +7 -0
- package/dist/chunks/{chunk-ETTRJDNP.ts → chunk-QLSN7AGX.ts} +6 -13
- package/dist/chunks/chunk-QLSN7AGX.ts.map +7 -0
- package/dist/chunks/{chunk-THMF4BVL.ts → chunk-SWUXA3MH.ts} +1 -1
- package/dist/chunks/chunk-SWUXA3MH.ts.map +7 -0
- package/dist/chunks/{chunk-TEN2ZBJV.ts → chunk-T2SJD3N7.ts} +2 -4
- package/dist/chunks/chunk-T2SJD3N7.ts.map +7 -0
- package/dist/chunks/{chunk-AUSDLD6F.ts → chunk-T56RNRZJ.ts} +10 -26
- package/dist/chunks/chunk-T56RNRZJ.ts.map +7 -0
- package/dist/chunks/{chunk-DHVFS4QK.ts → chunk-UA7RXOIT.ts} +37 -90
- package/dist/chunks/chunk-UA7RXOIT.ts.map +7 -0
- package/dist/chunks/{chunk-D5X27LCA.ts → chunk-UETBKTDI.ts} +1 -1
- package/dist/chunks/chunk-UETBKTDI.ts.map +7 -0
- package/dist/chunks/{chunk-CHH26D4F.ts → chunk-YFADF5ZQ.ts} +7 -6
- package/dist/chunks/chunk-YFADF5ZQ.ts.map +7 -0
- package/dist/chunks/{chunk-CCSN3F47.ts → chunk-YPXGU5OR.ts} +4 -4
- package/dist/chunks/chunk-YPXGU5OR.ts.map +7 -0
- package/dist/chunks/{file-selection-FTTKAOYX.ts → file-selection-RAVHRYFG.ts} +14 -26
- package/dist/chunks/file-selection-RAVHRYFG.ts.map +7 -0
- package/dist/chunks/{git-backend-GMVS4PD4.ts → git-backend-IAANDL3K.ts} +41 -104
- package/dist/chunks/git-backend-IAANDL3K.ts.map +7 -0
- package/dist/chunks/manager-result-dispatcher-OTUMQM26.ts +23 -0
- package/dist/chunks/{manager-ui-IPAOAWZA.ts → manager-ui-763NEKJY.ts} +124 -251
- package/dist/chunks/manager-ui-763NEKJY.ts.map +7 -0
- package/dist/chunks/remote-selection-ui-TZTJF32Y.ts +22 -0
- package/dist/chunks/{s3-backend-66O4FGBH.ts → s3-backend-XGXUOPUN.ts} +23 -54
- package/dist/chunks/s3-backend-XGXUOPUN.ts.map +7 -0
- package/dist/chunks/{setup-switch-IS2PEZ3J.ts → setup-switch-5G2VNJHL.ts} +9 -9
- package/dist/chunks/{setup-wizard-JUJZYNNM.ts → setup-wizard-YIEWGX32.ts} +12 -12
- package/dist/chunks/setup-wizard-YIEWGX32.ts.map +7 -0
- package/dist/chunks/{snapshot-HKEWCVK6.ts → snapshot-JL5DFSXI.ts} +5 -5
- package/dist/chunks/sync-inspection-P3ENJLNF.ts +20 -0
- package/dist/chunks/{sync-operations-LBX3GKCM.ts → sync-operations-AR76EB6C.ts} +85 -201
- package/dist/chunks/sync-operations-AR76EB6C.ts.map +7 -0
- package/dist/chunks/{sync-state-Q2FTXTW6.ts → sync-state-VHJUZ4YG.ts} +6 -6
- package/dist/chunks/{webdav-backend-ECIKD7SO.ts → webdav-backend-3EGUONAT.ts} +20 -51
- package/dist/chunks/webdav-backend-3EGUONAT.ts.map +7 -0
- package/dist/index.ts +49 -74
- package/dist/index.ts.map +3 -3
- package/docs/settings.md +9 -0
- package/package.json +61 -62
- package/src/backends/backend-factory.ts +15 -15
- package/src/backends/backend-identity.ts +31 -31
- package/src/backends/backend-types.ts +42 -42
- package/src/backends/git/git-backend.ts +714 -788
- package/src/backends/git/git-config.ts +104 -111
- package/src/backends/git/git-runner.ts +230 -247
- package/src/backends/git/git-storage.ts +228 -245
- package/src/backends/s3/s3-backend.ts +408 -441
- package/src/backends/s3/s3-client.ts +288 -333
- package/src/backends/sync-backend.ts +65 -65
- package/src/backends/webdav/webdav-backend.ts +542 -589
- package/src/backends/webdav/webdav-client.ts +292 -316
- package/src/backends/webdav/webdav-config.ts +57 -68
- package/src/commands/command-execution.ts +270 -289
- package/src/commands/command-handler.ts +160 -163
- package/src/commands/command-types.ts +13 -13
- package/src/commands/command.ts +190 -196
- package/src/commands/setup-completions.ts +1 -1
- package/src/paths.ts +47 -47
- package/src/settings/config-errors.ts +4 -5
- package/src/settings/config-file.ts +473 -506
- package/src/settings/config.ts +171 -174
- package/src/settings/settings-management.ts +191 -216
- package/src/settings/settings-store.ts +63 -77
- package/src/settings/settings-types.ts +78 -75
- package/src/settings/settings-validation.ts +328 -364
- package/src/snapshot/session-paths.ts +41 -55
- package/src/snapshot/snapshot-apply.ts +197 -229
- package/src/snapshot/snapshot-codec.ts +29 -31
- package/src/snapshot/snapshot-paths.ts +4 -6
- package/src/snapshot/snapshot-transaction.ts +161 -170
- package/src/snapshot/snapshot-types.ts +24 -24
- package/src/snapshot/snapshot.ts +310 -343
- package/src/state/json-file.ts +11 -11
- package/src/state/lock.ts +226 -242
- package/src/state/lockfile-fs.ts +20 -20
- package/src/state/operation-availability.ts +36 -41
- package/src/state/state-directory.ts +171 -173
- package/src/state/state-types.ts +15 -15
- package/src/state/sync-state-store.ts +23 -23
- package/src/sync/automatic-sync.ts +75 -80
- package/src/sync/remote-snapshot.ts +52 -60
- package/src/sync/setup-switch.ts +99 -114
- package/src/sync/signals.ts +3 -5
- package/src/sync/startup-check.ts +97 -111
- package/src/sync/sync-decision.ts +76 -80
- package/src/sync/sync-error-guidance.ts +41 -48
- package/src/sync/sync-errors.ts +10 -10
- package/src/sync/sync-inspection.ts +29 -33
- package/src/sync/sync-loaders.ts +28 -36
- package/src/sync/sync-mutations.ts +510 -622
- package/src/sync/sync-operations.ts +7 -7
- package/src/sync/sync-policy.ts +296 -326
- package/src/sync/sync-queries.ts +175 -194
- package/src/sync/sync-state.ts +74 -100
- package/src/sync-extension.ts +89 -101
- package/src/sync.ts +26 -26
- package/src/types.ts +37 -37
- package/src/ui/automatic-sync-summary.ts +1 -3
- package/src/ui/cancellable-operation.ts +89 -101
- package/src/ui/file-selection.ts +250 -274
- package/src/ui/manager-attention.ts +51 -63
- package/src/ui/manager-recovery.ts +48 -57
- package/src/ui/manager-result-dispatcher.ts +62 -76
- package/src/ui/manager-state.ts +200 -232
- package/src/ui/manager-ui.ts +264 -362
- package/src/ui/remote-selection-ui.ts +547 -588
- package/src/ui/secret-input.ts +197 -205
- package/src/ui/settings-ui.ts +175 -172
- package/src/ui/setup/git-ui.ts +232 -261
- package/src/ui/setup/s3-credentials-ui.ts +58 -65
- package/src/ui/setup/s3-ui.ts +171 -195
- package/src/ui/setup/setup-actions.ts +91 -101
- package/src/ui/setup/setup-location-ui.ts +121 -137
- package/src/ui/setup/setup-name-ui.ts +27 -27
- package/src/ui/setup/setup-prompts.ts +65 -65
- package/src/ui/setup/setup-review.ts +58 -59
- package/src/ui/setup/setup-switcher.ts +156 -160
- package/src/ui/setup/setup-wizard.ts +28 -28
- package/src/ui/setup/text-input.ts +54 -58
- package/src/ui/setup/webdav-ui.ts +246 -293
- package/src/ui/storage-connections-ui.ts +317 -369
- package/src/ui/sync-attention.ts +148 -164
- package/src/ui/sync-format.ts +110 -121
- package/src/ui/sync-resolution-ui.ts +147 -153
- package/src/ui/sync-setups-ui.ts +218 -238
- package/src/ui/sync-status.ts +17 -0
- package/src/ui/terminal-text.ts +2 -2
- package/dist/chunks/chunk-2B2ELUF5.ts.map +0 -7
- package/dist/chunks/chunk-5B3FXBLE.ts.map +0 -7
- package/dist/chunks/chunk-6LP3MCR2.ts.map +0 -7
- package/dist/chunks/chunk-AUSDLD6F.ts.map +0 -7
- package/dist/chunks/chunk-C7XY5NF5.ts.map +0 -7
- package/dist/chunks/chunk-CCSN3F47.ts.map +0 -7
- package/dist/chunks/chunk-CHH26D4F.ts.map +0 -7
- package/dist/chunks/chunk-CPEHGXJV.ts.map +0 -7
- package/dist/chunks/chunk-D5X27LCA.ts.map +0 -7
- package/dist/chunks/chunk-DHVFS4QK.ts.map +0 -7
- package/dist/chunks/chunk-ETTRJDNP.ts.map +0 -7
- package/dist/chunks/chunk-HUDQATCL.ts.map +0 -7
- package/dist/chunks/chunk-IJ6FJFA5.ts.map +0 -7
- package/dist/chunks/chunk-JAAKLL6M.ts.map +0 -7
- package/dist/chunks/chunk-LCRI3ABW.ts.map +0 -7
- package/dist/chunks/chunk-LTBWGSBV.ts.map +0 -7
- package/dist/chunks/chunk-NJFUIVYL.ts.map +0 -7
- package/dist/chunks/chunk-OEX4ABY6.ts.map +0 -7
- package/dist/chunks/chunk-Q2TUM5X6.ts.map +0 -7
- package/dist/chunks/chunk-SL64RLXR.ts.map +0 -7
- package/dist/chunks/chunk-SZWBGPID.ts.map +0 -7
- package/dist/chunks/chunk-T2B5IU74.ts.map +0 -7
- package/dist/chunks/chunk-T3HH3UEJ.ts.map +0 -7
- package/dist/chunks/chunk-TEN2ZBJV.ts.map +0 -7
- package/dist/chunks/chunk-THMF4BVL.ts.map +0 -7
- package/dist/chunks/chunk-YDWUZVPN.ts.map +0 -7
- package/dist/chunks/chunk-YHEMENKV.ts.map +0 -7
- package/dist/chunks/chunk-Z4DQF6L3.ts.map +0 -7
- package/dist/chunks/chunk-ZI5MXMEX.ts.map +0 -7
- package/dist/chunks/file-selection-FTTKAOYX.ts.map +0 -7
- package/dist/chunks/git-backend-GMVS4PD4.ts.map +0 -7
- package/dist/chunks/manager-result-dispatcher-ASKGOSW5.ts +0 -22
- package/dist/chunks/manager-ui-IPAOAWZA.ts.map +0 -7
- package/dist/chunks/remote-selection-ui-QDNR7WEI.ts +0 -21
- package/dist/chunks/s3-backend-66O4FGBH.ts.map +0 -7
- package/dist/chunks/setup-wizard-JUJZYNNM.ts.map +0 -7
- package/dist/chunks/sync-inspection-CST2KL65.ts +0 -20
- package/dist/chunks/sync-operations-LBX3GKCM.ts.map +0 -7
- package/dist/chunks/webdav-backend-ECIKD7SO.ts.map +0 -7
- /package/dist/chunks/{manager-result-dispatcher-ASKGOSW5.ts.map → manager-result-dispatcher-OTUMQM26.ts.map} +0 -0
- /package/dist/chunks/{remote-selection-ui-QDNR7WEI.ts.map → remote-selection-ui-TZTJF32Y.ts.map} +0 -0
- /package/dist/chunks/{setup-switch-IS2PEZ3J.ts.map → setup-switch-5G2VNJHL.ts.map} +0 -0
- /package/dist/chunks/{snapshot-HKEWCVK6.ts.map → snapshot-JL5DFSXI.ts.map} +0 -0
- /package/dist/chunks/{sync-inspection-CST2KL65.ts.map → sync-inspection-P3ENJLNF.ts.map} +0 -0
- /package/dist/chunks/{sync-state-Q2FTXTW6.ts.map → sync-state-VHJUZ4YG.ts.map} +0 -0
package/src/commands/command.ts
CHANGED
|
@@ -2,240 +2,234 @@ import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import type { CommandArgumentCompletion, CommandOptions } from "./command-types.js";
|
|
3
3
|
|
|
4
4
|
const YES_FLAG_COMPLETIONS: readonly CommandArgumentCompletion[] = [
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
{ value: "--yes", label: "--yes", description: "Skip confirmation prompts" },
|
|
6
|
+
{ value: "-y", label: "-y", description: "Skip confirmation prompts" },
|
|
7
7
|
];
|
|
8
8
|
export const SYNC_COMMANDS = [
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
9
|
+
{ name: "help", description: "Show command usage" },
|
|
10
|
+
{ name: "use", description: "Switch the current sync setup", usageSuffix: " <setup>" },
|
|
11
|
+
{ name: "init", description: "Create local config template" },
|
|
12
|
+
{ name: "config", description: "Show resolved configuration" },
|
|
13
|
+
{ name: "files", description: "Choose included content" },
|
|
14
|
+
{ name: "status", description: "Show sync status" },
|
|
15
|
+
{ name: "diff", description: "Show local/remote diff" },
|
|
16
|
+
{ name: "doctor", description: "Check config, secrets, and lock state" },
|
|
17
|
+
{ name: "push", description: "Upload local settings" },
|
|
18
|
+
{ name: "pull", description: "Apply remote settings" },
|
|
19
|
+
{ name: "sync", description: "Push or pull as needed" },
|
|
20
|
+
{ name: "history", description: "Show recent remote snapshots" },
|
|
21
|
+
{ name: "rollback", description: "Apply a previous snapshot", usageSuffix: " <snapshot>" },
|
|
22
|
+
{ name: "migrate-state", description: "Move legacy state into pi-sync/" },
|
|
23
|
+
{ name: "unlock", description: "Remove a stale local lock", usageSuffix: " --stale" },
|
|
24
24
|
] as const;
|
|
25
25
|
|
|
26
26
|
export type SyncCommandName = (typeof SYNC_COMMANDS)[number]["name"];
|
|
27
27
|
|
|
28
|
-
const SYNC_COMMAND_COMPLETIONS: readonly CommandArgumentCompletion[] = SYNC_COMMANDS.map(
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
const SYNC_COMMAND_COMPLETIONS: readonly CommandArgumentCompletion[] = SYNC_COMMANDS.map(({ name, description }) => ({
|
|
29
|
+
value: name,
|
|
30
|
+
label: name,
|
|
31
|
+
description,
|
|
32
|
+
}));
|
|
31
33
|
let setupCompletionNames: string[] = [];
|
|
32
34
|
|
|
33
35
|
export function setSyncSetupCompletions(names: readonly string[]) {
|
|
34
|
-
|
|
36
|
+
setupCompletionNames = [...new Set(names)].sort((left, right) => left.localeCompare(right));
|
|
35
37
|
}
|
|
36
38
|
const SETUP_FLAG_COMPLETION = {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
value: "--setup",
|
|
40
|
+
label: "--setup",
|
|
41
|
+
description: "Address a sync setup without switching",
|
|
40
42
|
} as const;
|
|
41
43
|
const SYNC_FLAG_COMPLETIONS: Record<string, readonly CommandArgumentCompletion[]> = {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
44
|
+
config: [SETUP_FLAG_COMPLETION],
|
|
45
|
+
files: [SETUP_FLAG_COMPLETION],
|
|
46
|
+
status: [SETUP_FLAG_COMPLETION],
|
|
47
|
+
diff: [SETUP_FLAG_COMPLETION],
|
|
48
|
+
doctor: [SETUP_FLAG_COMPLETION],
|
|
49
|
+
push: [
|
|
50
|
+
...YES_FLAG_COMPLETIONS,
|
|
51
|
+
{ value: "--force", label: "--force", description: "Overwrite visible remote changes" },
|
|
52
|
+
SETUP_FLAG_COMPLETION,
|
|
53
|
+
],
|
|
54
|
+
pull: [
|
|
55
|
+
...YES_FLAG_COMPLETIONS,
|
|
56
|
+
{ value: "--force", label: "--force", description: "Overwrite local changes" },
|
|
57
|
+
SETUP_FLAG_COMPLETION,
|
|
58
|
+
],
|
|
59
|
+
sync: [
|
|
60
|
+
...YES_FLAG_COMPLETIONS,
|
|
61
|
+
{ value: "--force", label: "--force", description: "Resolve conflicts by forcing action" },
|
|
62
|
+
SETUP_FLAG_COMPLETION,
|
|
63
|
+
],
|
|
64
|
+
history: [SETUP_FLAG_COMPLETION],
|
|
65
|
+
rollback: [...YES_FLAG_COMPLETIONS, SETUP_FLAG_COMPLETION],
|
|
66
|
+
"migrate-state": YES_FLAG_COMPLETIONS,
|
|
67
|
+
unlock: [{ value: "--stale", label: "--stale", description: "Remove only a stale lock" }],
|
|
66
68
|
};
|
|
67
69
|
|
|
68
70
|
export function splitArgs(input: string) {
|
|
69
|
-
|
|
70
|
-
input.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)?.map((arg) => arg.replace(/^['"]|['"]$/g, "")) ??
|
|
71
|
-
[]
|
|
72
|
-
);
|
|
71
|
+
return input.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)?.map((arg) => arg.replace(/^['"]|['"]$/g, "")) ?? [];
|
|
73
72
|
}
|
|
74
73
|
|
|
75
74
|
export function parseOptions(args: string[]): CommandOptions {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
75
|
+
const values: string[] = [];
|
|
76
|
+
let yes = false;
|
|
77
|
+
let force = false;
|
|
78
|
+
let stale = false;
|
|
79
|
+
let setup: string | undefined;
|
|
80
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
81
|
+
const arg = args[index];
|
|
82
|
+
if (arg === "--yes" || arg === "-y") yes = true;
|
|
83
|
+
else if (arg === "--force") force = true;
|
|
84
|
+
else if (arg === "--stale") stale = true;
|
|
85
|
+
else if (arg === "--setup") {
|
|
86
|
+
const name = args[index + 1];
|
|
87
|
+
if (!name || name.startsWith("-")) throw new Error("--setup requires a sync setup name.");
|
|
88
|
+
if (setup !== undefined) throw new Error("--setup may be provided only once.");
|
|
89
|
+
setup = name;
|
|
90
|
+
index += 1;
|
|
91
|
+
} else if (arg.startsWith("-")) {
|
|
92
|
+
throw new Error(`Unknown sync option: ${arg}`);
|
|
93
|
+
} else values.push(arg);
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
yes,
|
|
97
|
+
force,
|
|
98
|
+
stale,
|
|
99
|
+
silent: false,
|
|
100
|
+
reload: true,
|
|
101
|
+
auto: false,
|
|
102
|
+
...(setup === undefined ? {} : { setup }),
|
|
103
|
+
args: values,
|
|
104
|
+
};
|
|
106
105
|
}
|
|
107
106
|
|
|
108
107
|
export function validateCommandOptions(command: string, options: CommandOptions) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
108
|
+
const setupAllowed = new Set([
|
|
109
|
+
"config",
|
|
110
|
+
"files",
|
|
111
|
+
"status",
|
|
112
|
+
"diff",
|
|
113
|
+
"doctor",
|
|
114
|
+
"push",
|
|
115
|
+
"pull",
|
|
116
|
+
"sync",
|
|
117
|
+
"history",
|
|
118
|
+
"rollback",
|
|
119
|
+
]);
|
|
120
|
+
if (options.setup && !setupAllowed.has(command)) {
|
|
121
|
+
throw new Error(`--setup is not supported by /sync ${command}.`);
|
|
122
|
+
}
|
|
123
|
+
if (options.yes && !["push", "pull", "sync", "rollback", "migrate-state"].includes(command)) {
|
|
124
|
+
throw new Error(`Confirmation/force options are not supported by /sync ${command}.`);
|
|
125
|
+
}
|
|
126
|
+
if (options.force && !["push", "pull", "sync", "rollback"].includes(command)) {
|
|
127
|
+
throw new Error(`Confirmation/force options are not supported by /sync ${command}.`);
|
|
128
|
+
}
|
|
129
|
+
if (options.stale && command !== "unlock") {
|
|
130
|
+
throw new Error(`--stale is not supported by /sync ${command}.`);
|
|
131
|
+
}
|
|
132
|
+
const expectedValues = command === "rollback" || command === "use" ? 1 : 0;
|
|
133
|
+
if (options.args.length !== expectedValues) {
|
|
134
|
+
if (command === "rollback") throw new Error("Usage: /sync rollback <snapshot-id> [--yes] [--setup <name>]");
|
|
135
|
+
if (command === "use") throw new Error("Usage: /sync use <setup>");
|
|
136
|
+
throw new Error(`Unexpected argument for /sync ${command}: ${options.args.join(" ")}`);
|
|
137
|
+
}
|
|
140
138
|
}
|
|
141
139
|
|
|
142
140
|
export function completeSyncArguments(argumentPrefix: string): CommandArgumentCompletion[] | null {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const matches = flagCompletions.filter((item) => item.value.startsWith(current));
|
|
184
|
-
return matches.length > 0
|
|
185
|
-
? matches.map((item) => ({ ...item, value: `${completionPrefix}${item.value}` }))
|
|
186
|
-
: null;
|
|
141
|
+
const prefix = argumentPrefix.trimStart();
|
|
142
|
+
if (prefix === "") return [...SYNC_COMMAND_COMPLETIONS];
|
|
143
|
+
|
|
144
|
+
const trailingSpace = /\s$/.test(prefix);
|
|
145
|
+
const tokens = splitArgs(prefix);
|
|
146
|
+
if (tokens.length === 0) return [...SYNC_COMMAND_COMPLETIONS];
|
|
147
|
+
|
|
148
|
+
const [command] = tokens;
|
|
149
|
+
if (tokens.length === 1 && !trailingSpace) {
|
|
150
|
+
const matches = SYNC_COMMAND_COMPLETIONS.filter((item) => item.value.startsWith(command));
|
|
151
|
+
return matches.length > 0 ? [...matches] : null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const args = tokens.slice(1);
|
|
155
|
+
if (command === "use") {
|
|
156
|
+
if (args.length > 1 || (trailingSpace && args.length > 0)) return null;
|
|
157
|
+
return completeSetupValue(prefix, trailingSpace ? "" : (args[0] ?? ""));
|
|
158
|
+
}
|
|
159
|
+
const setupFlagIndex = args.lastIndexOf("--setup");
|
|
160
|
+
if (setupFlagIndex >= 0 && setupFlagIndex === args.length - (trailingSpace ? 1 : 2)) {
|
|
161
|
+
const currentSetup = trailingSpace ? "" : (args.at(-1) ?? "");
|
|
162
|
+
if (!currentSetup.startsWith("-")) return completeSetupValue(prefix, currentSetup);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const flagCompletions = SYNC_FLAG_COMPLETIONS[command];
|
|
166
|
+
if (!flagCompletions) return null;
|
|
167
|
+
|
|
168
|
+
const completedArgs = trailingSpace ? args : args.slice(0, -1);
|
|
169
|
+
const completedValues = completedArgs.filter((arg) => !arg.startsWith("-"));
|
|
170
|
+
if (command === "rollback" ? completedValues.length > 1 : completedValues.length > 0) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const current = trailingSpace ? "" : (args.at(-1) ?? "");
|
|
175
|
+
if (current && !current.startsWith("-")) return null;
|
|
176
|
+
|
|
177
|
+
const currentRaw = trailingSpace ? "" : (prefix.match(/\S+$/)?.[0] ?? "");
|
|
178
|
+
const completionPrefix = trailingSpace ? prefix : prefix.slice(0, prefix.length - currentRaw.length);
|
|
179
|
+
const matches = flagCompletions.filter((item) => item.value.startsWith(current));
|
|
180
|
+
return matches.length > 0 ? matches.map((item) => ({ ...item, value: `${completionPrefix}${item.value}` })) : null;
|
|
187
181
|
}
|
|
188
182
|
|
|
189
183
|
function completeSetupValue(prefix: string, current: string) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
184
|
+
const currentRaw = current ? (prefix.match(/\S+$/u)?.[0] ?? "") : "";
|
|
185
|
+
const completionPrefix = currentRaw ? prefix.slice(0, prefix.length - currentRaw.length) : prefix;
|
|
186
|
+
const matches = setupCompletionNames.filter((name) => name.startsWith(current));
|
|
187
|
+
return matches.length > 0
|
|
188
|
+
? matches.map((name) => ({
|
|
189
|
+
value: `${completionPrefix}${name}`,
|
|
190
|
+
label: name,
|
|
191
|
+
description: "Sync setup",
|
|
192
|
+
}))
|
|
193
|
+
: null;
|
|
200
194
|
}
|
|
201
195
|
|
|
202
196
|
export function syncMenuOptions() {
|
|
203
|
-
|
|
197
|
+
return SYNC_COMMANDS.map(({ name, description }) => `${name} — ${description}`);
|
|
204
198
|
}
|
|
205
199
|
|
|
206
200
|
export function syncCommandFromMenuOption(option: string): SyncCommandName | undefined {
|
|
207
|
-
|
|
201
|
+
return SYNC_COMMANDS.find(({ name, description }) => option === `${name} — ${description}`)?.name;
|
|
208
202
|
}
|
|
209
203
|
|
|
210
204
|
export async function resolveSyncCommand(input: string, ctx: ExtensionCommandContext) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
205
|
+
const [subcommand, ...rest] = splitArgs(input);
|
|
206
|
+
if (subcommand) return { subcommand, rest };
|
|
207
|
+
if (!ctx.hasUI) {
|
|
208
|
+
ctx.ui.notify(usage(), "info");
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const selectedOption = await ctx.ui.select("pi-sync", syncMenuOptions());
|
|
213
|
+
const selected = selectedOption ? syncCommandFromMenuOption(selectedOption) : undefined;
|
|
214
|
+
if (!selected) return undefined;
|
|
215
|
+
if (selected !== "rollback") return { subcommand: selected, rest: [] };
|
|
216
|
+
|
|
217
|
+
const target = (await ctx.ui.input("Rollback snapshot", "snapshot id"))?.trim();
|
|
218
|
+
if (!target) {
|
|
219
|
+
ctx.ui.notify("Rollback cancelled.", "info");
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
return { subcommand: selected, rest: [target] };
|
|
229
223
|
}
|
|
230
224
|
|
|
231
225
|
export function usage() {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
226
|
+
const commands = SYNC_COMMANDS.map(
|
|
227
|
+
(command) => `${command.name}${"usageSuffix" in command ? command.usageSuffix : ""}`,
|
|
228
|
+
).join(", ");
|
|
229
|
+
return [
|
|
230
|
+
"Usage: /sync <command>",
|
|
231
|
+
`Commands: ${commands}`,
|
|
232
|
+
"Automatic sync: background startup checks only; use /sync to review and transfer. At shutdown, selected content is pushed automatically only when sessions are included. Startup checks run in TUI/RPC, not print/JSON.",
|
|
233
|
+
"Settings: use /sync init or edit storage connections and sync setups in ~/.pi/agent/pi-sync.json (or the configured Pi agent directory). Version 1 and version 2 settings are unsupported and are never rewritten.",
|
|
234
|
+
].join("\n");
|
|
241
235
|
}
|
|
@@ -2,5 +2,5 @@ import { configuredSyncSetupNames } from "../settings/settings-store.js";
|
|
|
2
2
|
import { setSyncSetupCompletions } from "./command.js";
|
|
3
3
|
|
|
4
4
|
export async function refreshTargetCompletions() {
|
|
5
|
-
|
|
5
|
+
setSyncSetupCompletions(await configuredSyncSetupNames());
|
|
6
6
|
}
|
package/src/paths.ts
CHANGED
|
@@ -1,78 +1,78 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
|
|
3
3
|
export function isDeniedPath(relativePath: string) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
4
|
+
const normalized = toPosix(relativePath);
|
|
5
|
+
const lower = normalized.toLowerCase();
|
|
6
|
+
const segments = lower.split("/");
|
|
7
|
+
const base = path.posix.basename(lower);
|
|
8
|
+
return (
|
|
9
|
+
segments.includes("node_modules") ||
|
|
10
|
+
segments.includes(".git") ||
|
|
11
|
+
segments.includes(".pisync") ||
|
|
12
|
+
segments.includes("pi-sync") ||
|
|
13
|
+
segments.includes(".pi-sync-state-migration.lock") ||
|
|
14
|
+
base === ".env" ||
|
|
15
|
+
base.startsWith(".env.") ||
|
|
16
|
+
base.endsWith(".env") ||
|
|
17
|
+
base.includes("secret") ||
|
|
18
|
+
base.includes("token") ||
|
|
19
|
+
base === "pi-sync.json" ||
|
|
20
|
+
base.startsWith("pi-sync.json.") ||
|
|
21
|
+
base.startsWith(".pi-sync.json.") ||
|
|
22
|
+
base === "pi-sync.local.json" ||
|
|
23
|
+
base.startsWith("pi-sync.local.json.") ||
|
|
24
|
+
base.startsWith(".pi-sync.local.json.")
|
|
25
|
+
);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export function isPathInside(parent: string, child: string) {
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
const relative = path.relative(path.resolve(parent), path.resolve(child));
|
|
30
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export function safeJoin(root: string, relativePath: string) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
const target = path.resolve(root, relativePath);
|
|
35
|
+
assertWithinRoot(root, target, relativePath);
|
|
36
|
+
return target;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
export function assertWithinRoot(root: string, target: string, label = target) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
const resolvedRoot = path.resolve(root);
|
|
41
|
+
const resolvedTarget = path.resolve(target);
|
|
42
|
+
if (resolvedTarget !== resolvedRoot && !resolvedTarget.startsWith(`${resolvedRoot}${path.sep}`)) {
|
|
43
|
+
throw new Error(`Unsafe path in snapshot: ${label}`);
|
|
44
|
+
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
export function encodeKey(key: string) {
|
|
48
|
-
|
|
48
|
+
return key.split("/").map(encodeURIComponent).join("/");
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
export function posixJoin(...parts: string[]) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
return parts
|
|
53
|
+
.map((part) => trimSlashes(part))
|
|
54
|
+
.filter(Boolean)
|
|
55
|
+
.join("/");
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
export function parentPaths(relativePath: string) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
59
|
+
const results: string[] = [];
|
|
60
|
+
let index = relativePath.lastIndexOf("/");
|
|
61
|
+
while (index > 0) {
|
|
62
|
+
results.push(relativePath.slice(0, index));
|
|
63
|
+
index = relativePath.lastIndexOf("/", index - 1);
|
|
64
|
+
}
|
|
65
|
+
return results;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
export function toPosix(value: string) {
|
|
69
|
-
|
|
69
|
+
return value.split(path.sep).join("/");
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
function trimSlashes(value: string) {
|
|
73
|
-
|
|
73
|
+
return value.replace(/^\/+|\/+$/g, "");
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
export function safeName(value: string) {
|
|
77
|
-
|
|
77
|
+
return value.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
78
78
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export function isMissingConfigError(error: unknown) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
);
|
|
2
|
+
return (
|
|
3
|
+
error instanceof Error &&
|
|
4
|
+
(error.message.startsWith("Missing pi-sync settings.") || error.message === "No sync setups are configured.")
|
|
5
|
+
);
|
|
7
6
|
}
|