@mycadet/assistant 0.12.10 → 0.13.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/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
- package/node_modules/@mycadet/ces-client/src/http-credentials.ts +18 -1
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
- package/node_modules/@mycadet/gateway-client/src/gateway-ipc-contracts.ts +0 -20
- package/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
- package/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
- package/openapi.yaml +2 -708
- package/package.json +2 -3
- package/scripts/sync-web-search-catalog.ts +1 -2
- package/src/__tests__/cloud-profile-guard.test.ts +41 -5
- package/src/__tests__/config-schema.test.ts +6 -38
- package/src/__tests__/conversation-queue.test.ts +45 -6
- package/src/__tests__/db-readiness-http-gate.test.ts +0 -6
- package/src/__tests__/persist-onboarding-artifacts.test.ts +0 -1
- package/src/__tests__/persona-resolver.test.ts +0 -51
- package/src/__tests__/provider-cloud-proxy-integration.test.ts +2 -110
- package/src/__tests__/run-due-schedules.test.ts +0 -4
- package/src/__tests__/schedule-store.test.ts +0 -12
- package/src/__tests__/stt-stream-session.test.ts +4 -4
- package/src/__tests__/telephony-tts-guard.test.ts +2 -50
- package/src/__tests__/voice-config-update.test.ts +12 -167
- package/src/__tests__/web-search-catalog-parity.test.ts +0 -2
- package/src/__tests__/workspace-migration-130-speech-mode-to-provider.test.ts +9 -7
- package/src/__tests__/workspace-migration-132-web-search-mode-to-provider.test.ts +6 -6
- package/src/calls/__tests__/voice-session-bridge.test.ts +1 -4
- package/src/calls/resolve-call-tts-provider.ts +6 -25
- package/src/calls/telephony-credential-preflight.ts +0 -10
- package/src/calls/telephony-tts-capability.ts +1 -17
- package/src/cli/commands/tts.ts +1 -2
- package/src/cli/index.help.ts +0 -2
- package/src/cli/program.ts +0 -2
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +4 -42
- package/src/config/schemas/__tests__/stt.test.ts +10 -14
- package/src/config/schemas/llm.ts +3 -5
- package/src/config/schemas/services.ts +5 -6
- package/src/config/schemas/stt.ts +1 -3
- package/src/config/schemas/tts.ts +1 -30
- package/src/daemon/daemon-readiness.ts +4 -29
- package/src/daemon/lifecycle.ts +0 -31
- package/src/daemon/providers-setup.ts +0 -8
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
- package/src/heartbeat/heartbeat-service.ts +1 -66
- package/src/ipc/__tests__/db-readiness-ipc-gate.test.ts +1 -9
- package/src/live-voice/__tests__/live-voice-credential-preflight.test.ts +0 -44
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +4 -6
- package/src/live-voice/__tests__/live-voice-stt.test.ts +1 -1
- package/src/live-voice/live-voice-credential-preflight.ts +9 -33
- package/src/live-voice/live-voice-session.ts +10 -6
- package/src/live-voice/live-voice-tts.ts +1 -8
- package/src/plugins/defaults/memory/worker.ts +3 -3
- package/src/prompts/persona-resolver.ts +0 -35
- package/src/providers/__tests__/search-provider-catalog.test.ts +5 -8
- package/src/providers/__tests__/voice-error-copy.test.ts +9 -10
- package/src/providers/search-provider-catalog.ts +7 -39
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +3 -47
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +6 -372
- package/src/providers/speech-to-text/provider-catalog.ts +2 -73
- package/src/providers/speech-to-text/resolve.ts +3 -120
- package/src/runtime/http-router-types.ts +1 -3
- package/src/runtime/http-server.ts +1 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +13 -11
- package/src/runtime/routes/__tests__/tts-routes.test.ts +2 -6
- package/src/runtime/routes/conversation-query-routes.ts +0 -96
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +7 -7
- package/src/runtime/routes/index.ts +0 -4
- package/src/runtime/routes/live-voice-routes.test.ts +4 -11
- package/src/runtime/routes/live-voice-routes.ts +8 -11
- package/src/runtime/routes/secret-routes.ts +0 -6
- package/src/runtime/routes/tts-routes.ts +0 -38
- package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1 -7
- package/src/schedule/__tests__/worker-mcp-readiness.test.ts +0 -4
- package/src/schedule/schedule-store.ts +0 -7
- package/src/schedule/scheduler.ts +0 -5
- package/src/schedule/worker.ts +1 -1
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +2 -10
- package/src/stt/daemon-batch-transcriber.ts +4 -38
- package/src/stt/types.ts +1 -9
- package/src/tools/network/__tests__/web-search.test.ts +3 -250
- package/src/tools/network/web-search.ts +4 -165
- package/src/tts/__tests__/provider-resolution.test.ts +0 -5
- package/src/tts/provider-catalog.ts +0 -4
- package/src/tts/types.ts +0 -1
- package/src/__tests__/cloud-speech-defaults.test.ts +0 -330
- package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -1708
- package/src/__tests__/migration-export-http.test.ts +0 -557
- package/src/__tests__/migration-export-streaming.test.ts +0 -350
- package/src/__tests__/migration-export-to-gcs.test.ts +0 -706
- package/src/__tests__/migration-import-commit-http.test.ts +0 -1176
- package/src/__tests__/migration-import-from-gcs.test.ts +0 -534
- package/src/__tests__/migration-import-from-path.test.ts +0 -324
- package/src/__tests__/migration-import-from-url.test.ts +0 -588
- package/src/__tests__/migration-import-preflight-http.test.ts +0 -739
- package/src/__tests__/migration-jobs-status.test.ts +0 -139
- package/src/__tests__/migration-parity-persistence.test.ts +0 -1891
- package/src/__tests__/migration-transport.test.ts +0 -1089
- package/src/__tests__/migration-validate-http.test.ts +0 -634
- package/src/__tests__/migration-wizard.test.ts +0 -1395
- package/src/__tests__/rebind-secrets-screen.test.ts +0 -876
- package/src/__tests__/sanitize-config-for-transfer.test.ts +0 -259
- package/src/__tests__/transfer-progress-screen.test.ts +0 -1217
- package/src/__tests__/validation-results-screen.test.ts +0 -1130
- package/src/__tests__/vbundle-pax-and-symlink.test.ts +0 -147
- package/src/background-wake/background-wake-routes.test.ts +0 -906
- package/src/background-wake/cloud-client.test.ts +0 -328
- package/src/background-wake/cloud-client.ts +0 -196
- package/src/background-wake/next-wake.test.ts +0 -335
- package/src/background-wake/next-wake.ts +0 -188
- package/src/background-wake/publisher.ts +0 -93
- package/src/background-wake/wake-intent-hooks.test.ts +0 -234
- package/src/cli/commands/__tests__/backup.test.ts +0 -363
- package/src/cli/commands/backup.help.ts +0 -180
- package/src/cli/commands/backup.ts +0 -398
- package/src/cloud/cloud-speech.test.ts +0 -335
- package/src/cloud/cloud-speech.ts +0 -300
- package/src/config/cloud-speech-defaults.ts +0 -300
- package/src/config/sanitize-for-transfer.ts +0 -111
- package/src/live-voice/__tests__/live-voice-cloud-speech-fallback.test.ts +0 -250
- package/src/providers/speech-to-text/__tests__/cadet-cloud-realtime.test.ts +0 -457
- package/src/providers/speech-to-text/__tests__/cadet-cloud.test.ts +0 -271
- package/src/providers/speech-to-text/__tests__/cadet-speech-relay-connection.test.ts +0 -126
- package/src/providers/speech-to-text/cadet-cloud-flux-realtime.ts +0 -309
- package/src/providers/speech-to-text/cadet-cloud-realtime.ts +0 -315
- package/src/providers/speech-to-text/cadet-cloud.ts +0 -108
- package/src/providers/speech-to-text/cadet-speech-relay-connection.ts +0 -176
- package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +0 -254
- package/src/runtime/migrations/__tests__/job-registry.test.ts +0 -346
- package/src/runtime/migrations/__tests__/origin-mode.test.ts +0 -62
- package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +0 -184
- package/src/runtime/migrations/__tests__/staged-import-path.test.ts +0 -110
- package/src/runtime/migrations/__tests__/v1-test-helpers.ts +0 -112
- package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +0 -285
- package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +0 -126
- package/src/runtime/migrations/__tests__/vbundle-builder-v1-shape.test.ts +0 -253
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +0 -208
- package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +0 -485
- package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +0 -349
- package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +0 -196
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +0 -551
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +0 -428
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +0 -220
- package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +0 -1823
- package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +0 -598
- package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +0 -463
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -538
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +0 -515
- package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +0 -453
- package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +0 -319
- package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +0 -240
- package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +0 -473
- package/src/runtime/migrations/gcs-signed-url.ts +0 -190
- package/src/runtime/migrations/job-registry.ts +0 -297
- package/src/runtime/migrations/migration-transport.ts +0 -612
- package/src/runtime/migrations/migration-wizard.ts +0 -747
- package/src/runtime/migrations/origin-mode.ts +0 -42
- package/src/runtime/migrations/rebind-secrets-screen.ts +0 -412
- package/src/runtime/migrations/staged-import-path.ts +0 -116
- package/src/runtime/migrations/transfer-progress-screen.ts +0 -323
- package/src/runtime/migrations/validation-results-screen.ts +0 -469
- package/src/runtime/migrations/vbundle-builder.ts +0 -1382
- package/src/runtime/migrations/vbundle-import-analyzer.ts +0 -405
- package/src/runtime/migrations/vbundle-import-policy.ts +0 -241
- package/src/runtime/migrations/vbundle-importer.ts +0 -826
- package/src/runtime/migrations/vbundle-metadata-merge.ts +0 -113
- package/src/runtime/migrations/vbundle-streaming-importer.ts +0 -2844
- package/src/runtime/migrations/vbundle-streaming-validator.ts +0 -426
- package/src/runtime/migrations/vbundle-tar-stream.ts +0 -244
- package/src/runtime/migrations/vbundle-validator.ts +0 -724
- package/src/runtime/routes/__tests__/migration-cadet-metadata-reconcile.test.ts +0 -227
- package/src/runtime/routes/__tests__/migration-export-secrets-redacted.test.ts +0 -54
- package/src/runtime/routes/__tests__/migration-import-allowed-hosts-env.test.ts +0 -70
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +0 -257
- package/src/runtime/routes/background-wake-routes.ts +0 -387
- package/src/runtime/routes/migration-routes.ts +0 -2584
- package/src/tools/network/__tests__/cloud-search-proxy.test.ts +0 -282
- package/src/tools/network/cloud-search-proxy.ts +0 -211
- package/src/tts/__tests__/cadet-provider.test.ts +0 -253
- package/src/tts/providers/cadet-provider.ts +0 -244
- package/src/types/tar-stream.d.ts +0 -66
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
/** Declarative help for the `assistant backup` command. */
|
|
2
|
-
|
|
3
|
-
import type { CliCommandHelp } from "../lib/cli-command-help.js";
|
|
4
|
-
|
|
5
|
-
export const backupHelp: CliCommandHelp = {
|
|
6
|
-
name: "backup",
|
|
7
|
-
description: "Manage automated backup configuration and list snapshots",
|
|
8
|
-
helpText: `
|
|
9
|
-
Backups capture a snapshot of the assistant workspace (config, conversations,
|
|
10
|
-
trust rules, hooks, the SQLite database) as a .vbundle file. Credentials are
|
|
11
|
-
NOT included — they live in the OS keychain / CES and users re-authenticate
|
|
12
|
-
integrations after a restore (via the gateway). The automated worker runs on a configurable
|
|
13
|
-
interval and writes to a local pool under ~/.cadet/backups/local/, optionally
|
|
14
|
-
mirroring each snapshot to one or more offsite destinations. macOS uses
|
|
15
|
-
encrypted iCloud Drive by default. Windows and Linux require an explicit path.
|
|
16
|
-
|
|
17
|
-
Offsite destinations can be per-destination encrypted (AES-256-GCM) or
|
|
18
|
-
plaintext — plaintext only makes sense when the user owns physical access to
|
|
19
|
-
the medium (e.g. an external SSD).
|
|
20
|
-
|
|
21
|
-
Examples:
|
|
22
|
-
$ assistant backup enable --interval 6 --retention 3
|
|
23
|
-
$ assistant backup destinations add /Volumes/BackupSSD/cadet --plaintext
|
|
24
|
-
$ assistant backup status
|
|
25
|
-
$ assistant backup list`,
|
|
26
|
-
subcommands: [
|
|
27
|
-
{
|
|
28
|
-
name: "enable",
|
|
29
|
-
description: "Enable automated backups",
|
|
30
|
-
options: [
|
|
31
|
-
{
|
|
32
|
-
flags: "--interval <hours>",
|
|
33
|
-
description:
|
|
34
|
-
"Hours between automated backups (1-168). Defaults to 6.",
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
flags: "--retention <n>",
|
|
38
|
-
description:
|
|
39
|
-
"Snapshots to retain per destination (1-100). Defaults to 3.",
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
flags: "--no-offsite",
|
|
43
|
-
description:
|
|
44
|
-
"Disable offsite backup (local only). Does not touch the destinations list.",
|
|
45
|
-
},
|
|
46
|
-
],
|
|
47
|
-
helpText: `
|
|
48
|
-
Sets backup.enabled = true in config.json. Optionally overrides intervalHours,
|
|
49
|
-
retention, and the offsite.enabled flag. Does NOT modify
|
|
50
|
-
backup.offsite.destinations — use 'assistant backup destinations add/remove' to
|
|
51
|
-
manage those.
|
|
52
|
-
|
|
53
|
-
Examples:
|
|
54
|
-
$ assistant backup enable
|
|
55
|
-
$ assistant backup enable --interval 12 --retention 14
|
|
56
|
-
$ assistant backup enable --no-offsite`,
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
name: "disable",
|
|
60
|
-
description: "Disable automated backups",
|
|
61
|
-
helpText: `
|
|
62
|
-
Sets backup.enabled = false in config.json. Existing snapshots are untouched;
|
|
63
|
-
only the automated worker stops creating new ones.
|
|
64
|
-
|
|
65
|
-
Examples:
|
|
66
|
-
$ assistant backup disable`,
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
name: "destinations",
|
|
70
|
-
description: "Manage offsite backup destinations",
|
|
71
|
-
helpText: `
|
|
72
|
-
Offsite destinations are absolute paths the backup worker writes a copy of
|
|
73
|
-
each snapshot to after the local write succeeds. macOS uses the iCloud Drive
|
|
74
|
-
CadetAssistant folder until an explicit destinations array is configured.
|
|
75
|
-
Windows and Linux have no implicit offsite destination.
|
|
76
|
-
|
|
77
|
-
Each destination has an 'encrypt' flag. When true (the default), snapshots
|
|
78
|
-
are written as .vbundle.enc (AES-256-GCM). When false, snapshots are copied
|
|
79
|
-
as plaintext .vbundle — only use this for media you control physically.
|
|
80
|
-
|
|
81
|
-
Examples:
|
|
82
|
-
$ assistant backup destinations list
|
|
83
|
-
$ assistant backup destinations add /Volumes/BackupSSD/cadet --plaintext
|
|
84
|
-
$ assistant backup destinations remove /Volumes/BackupSSD/cadet
|
|
85
|
-
$ assistant backup destinations set-encrypt /Volumes/BackupSSD/cadet false`,
|
|
86
|
-
subcommands: [
|
|
87
|
-
{
|
|
88
|
-
name: "list",
|
|
89
|
-
description: "List configured offsite destinations",
|
|
90
|
-
helpText: `
|
|
91
|
-
Resolves the current destinations array and prints each path and encryption
|
|
92
|
-
flag. On macOS this includes the implicit iCloud default when configured paths
|
|
93
|
-
are null.
|
|
94
|
-
|
|
95
|
-
Examples:
|
|
96
|
-
$ assistant backup destinations list`,
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
name: "add",
|
|
100
|
-
args: "<path>",
|
|
101
|
-
description: "Add an offsite backup destination",
|
|
102
|
-
options: [
|
|
103
|
-
{
|
|
104
|
-
flags: "--plaintext",
|
|
105
|
-
description:
|
|
106
|
-
"Write snapshots as plaintext .vbundle (default is AES-256-GCM encrypted .vbundle.enc)",
|
|
107
|
-
},
|
|
108
|
-
],
|
|
109
|
-
helpText: `
|
|
110
|
-
Arguments:
|
|
111
|
-
path Absolute path to the destination directory. Must be on a mount the
|
|
112
|
-
caller controls; the backup worker writes files inside this
|
|
113
|
-
directory, not the directory itself.
|
|
114
|
-
|
|
115
|
-
On macOS, adding the first explicit destination preserves the implicit iCloud
|
|
116
|
-
destination. Windows and Linux start with only the destination supplied here.
|
|
117
|
-
|
|
118
|
-
Examples:
|
|
119
|
-
$ assistant backup destinations add /Volumes/BackupSSD/cadet --plaintext
|
|
120
|
-
$ assistant backup destinations add ~/Dropbox/CadetAssistant/backups`,
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
name: "remove",
|
|
124
|
-
args: "<path>",
|
|
125
|
-
description: "Remove an offsite backup destination by path",
|
|
126
|
-
helpText: `
|
|
127
|
-
Arguments:
|
|
128
|
-
path Exact path match of the destination to remove. Run
|
|
129
|
-
'assistant backup destinations list' to see configured paths.
|
|
130
|
-
|
|
131
|
-
Errors if no destination with the given path exists.
|
|
132
|
-
|
|
133
|
-
Examples:
|
|
134
|
-
$ assistant backup destinations remove /Volumes/BackupSSD/cadet`,
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
name: "set-encrypt",
|
|
138
|
-
args: "<path> <value>",
|
|
139
|
-
description: "Toggle encryption for an existing destination",
|
|
140
|
-
helpText: `
|
|
141
|
-
Arguments:
|
|
142
|
-
path Exact path match of an existing destination. Run
|
|
143
|
-
'assistant backup destinations list' to see configured paths.
|
|
144
|
-
value "true" to encrypt, "false" for plaintext writes.
|
|
145
|
-
|
|
146
|
-
Errors if no destination with the given path exists. Existing snapshot files
|
|
147
|
-
are not modified; only future writes honour the new setting.
|
|
148
|
-
|
|
149
|
-
Examples:
|
|
150
|
-
$ assistant backup destinations set-encrypt /Volumes/BackupSSD/cadet false
|
|
151
|
-
$ assistant backup destinations set-encrypt /Volumes/BackupSSD/cadet true`,
|
|
152
|
-
},
|
|
153
|
-
],
|
|
154
|
-
},
|
|
155
|
-
{
|
|
156
|
-
name: "status",
|
|
157
|
-
description: "Show backup status and next-run timing",
|
|
158
|
-
helpText: `
|
|
159
|
-
Reports enabled/disabled state, interval and retention, last-run and next-run
|
|
160
|
-
timing (from the backup:last_run_at memory checkpoint), and a per-destination
|
|
161
|
-
reachability probe. Unreachable destinations (parent directory missing, e.g.
|
|
162
|
-
iCloud Drive not enabled or external volume unplugged) are flagged
|
|
163
|
-
[unreachable] and skipped by the worker.
|
|
164
|
-
|
|
165
|
-
Examples:
|
|
166
|
-
$ assistant backup status`,
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
name: "list",
|
|
170
|
-
description: "List all backup snapshots, grouped by destination",
|
|
171
|
-
helpText: `
|
|
172
|
-
Prints a per-destination table of snapshots with timestamp, size, and
|
|
173
|
-
encryption flag. Local destination is listed first, followed by each offsite
|
|
174
|
-
destination. Unreachable destinations are listed with an empty snapshot set.
|
|
175
|
-
|
|
176
|
-
Examples:
|
|
177
|
-
$ assistant backup list`,
|
|
178
|
-
},
|
|
179
|
-
],
|
|
180
|
-
};
|
|
@@ -1,398 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `assistant backup` — manage automated backup configuration and list snapshots.
|
|
3
|
-
*
|
|
4
|
-
* Thin IPC wrapper: each subcommand forwards its request to the daemon via
|
|
5
|
-
* cliIpcCall.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { Command } from "commander";
|
|
9
|
-
|
|
10
|
-
import { cliIpcCall, exitFromIpcResult } from "../../ipc/cli-client.js";
|
|
11
|
-
import { applyCommandHelp, subcommand } from "../lib/cli-command-help.js";
|
|
12
|
-
import { registerCommand } from "../lib/register-command.js";
|
|
13
|
-
import { log } from "../logger.js";
|
|
14
|
-
import { backupHelp } from "./backup.help.js";
|
|
15
|
-
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
// Small formatting helpers
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
|
|
20
|
-
/** Format a byte count as a human-readable string (B / KB / MB / GB). */
|
|
21
|
-
function formatBytes(bytes: number): string {
|
|
22
|
-
if (bytes < 1024) {
|
|
23
|
-
return `${bytes} B`;
|
|
24
|
-
}
|
|
25
|
-
if (bytes < 1024 * 1024) {
|
|
26
|
-
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
27
|
-
}
|
|
28
|
-
if (bytes < 1024 * 1024 * 1024) {
|
|
29
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
30
|
-
}
|
|
31
|
-
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** Format an ISO date string as `YYYY-MM-DD HH:MM UTC`. */
|
|
35
|
-
function formatDate(date: Date): string {
|
|
36
|
-
const y = date.getUTCFullYear().toString().padStart(4, "0");
|
|
37
|
-
const mo = (date.getUTCMonth() + 1).toString().padStart(2, "0");
|
|
38
|
-
const d = date.getUTCDate().toString().padStart(2, "0");
|
|
39
|
-
const h = date.getUTCHours().toString().padStart(2, "0");
|
|
40
|
-
const mi = date.getUTCMinutes().toString().padStart(2, "0");
|
|
41
|
-
return `${y}-${mo}-${d} ${h}:${mi} UTC`;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Format a duration (milliseconds) as a short human string: "3h 12m",
|
|
46
|
-
* "12m", "45s", or "just now".
|
|
47
|
-
*/
|
|
48
|
-
function formatDurationShort(ms: number): string {
|
|
49
|
-
if (ms < 0) {
|
|
50
|
-
ms = 0;
|
|
51
|
-
}
|
|
52
|
-
const seconds = Math.floor(ms / 1000);
|
|
53
|
-
if (seconds < 30) {
|
|
54
|
-
return "just now";
|
|
55
|
-
}
|
|
56
|
-
const minutes = Math.floor(seconds / 60);
|
|
57
|
-
if (minutes < 1) {
|
|
58
|
-
return `${seconds}s`;
|
|
59
|
-
}
|
|
60
|
-
const hours = Math.floor(minutes / 60);
|
|
61
|
-
const remMinutes = minutes - hours * 60;
|
|
62
|
-
if (hours < 1) {
|
|
63
|
-
return `${minutes}m`;
|
|
64
|
-
}
|
|
65
|
-
const days = Math.floor(hours / 24);
|
|
66
|
-
const remHours = hours - days * 24;
|
|
67
|
-
if (days < 1) {
|
|
68
|
-
return `${hours}h ${remMinutes}m`;
|
|
69
|
-
}
|
|
70
|
-
return `${days}d ${remHours}h`;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// ---------------------------------------------------------------------------
|
|
74
|
-
// Command wiring
|
|
75
|
-
// ---------------------------------------------------------------------------
|
|
76
|
-
|
|
77
|
-
export function registerBackupCommand(program: Command): void {
|
|
78
|
-
registerCommand(program, {
|
|
79
|
-
name: backupHelp.name,
|
|
80
|
-
transport: "ipc",
|
|
81
|
-
description: backupHelp.description,
|
|
82
|
-
build: (backup) => {
|
|
83
|
-
applyCommandHelp(backup, backupHelp);
|
|
84
|
-
|
|
85
|
-
// ── enable ───────────────────────────────────────────────────────
|
|
86
|
-
subcommand(backup, "enable").action(
|
|
87
|
-
async (
|
|
88
|
-
opts: { interval?: string; retention?: string; offsite?: boolean },
|
|
89
|
-
cmd: Command,
|
|
90
|
-
) => {
|
|
91
|
-
const r = await cliIpcCall("backup_enable", {
|
|
92
|
-
body: {
|
|
93
|
-
...(opts.interval !== undefined && {
|
|
94
|
-
intervalHours: Number.parseInt(opts.interval, 10),
|
|
95
|
-
}),
|
|
96
|
-
...(opts.retention !== undefined && {
|
|
97
|
-
retention: Number.parseInt(opts.retention, 10),
|
|
98
|
-
}),
|
|
99
|
-
...(opts.offsite === false && { offsiteEnabled: false }),
|
|
100
|
-
},
|
|
101
|
-
});
|
|
102
|
-
if (!r.ok) {
|
|
103
|
-
return exitFromIpcResult(
|
|
104
|
-
{ ok: false, error: r.error, statusCode: r.statusCode },
|
|
105
|
-
cmd,
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
const cfg = r.result as {
|
|
109
|
-
intervalHours: number;
|
|
110
|
-
retention: number;
|
|
111
|
-
offsite: { enabled: boolean };
|
|
112
|
-
};
|
|
113
|
-
log.info(
|
|
114
|
-
`Automatic backups enabled (interval=${cfg.intervalHours}h, retention=${cfg.retention}, offsite=${cfg.offsite.enabled ? "on" : "off"})`,
|
|
115
|
-
);
|
|
116
|
-
},
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
// ── disable ──────────────────────────────────────────────────────
|
|
120
|
-
subcommand(backup, "disable").action(
|
|
121
|
-
async (_opts: unknown, cmd: Command) => {
|
|
122
|
-
const r = await cliIpcCall("backup_disable");
|
|
123
|
-
if (!r.ok) {
|
|
124
|
-
return exitFromIpcResult(
|
|
125
|
-
{ ok: false, error: r.error, statusCode: r.statusCode },
|
|
126
|
-
cmd,
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
log.info("Automatic backups disabled");
|
|
130
|
-
},
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
// ── destinations ─────────────────────────────────────────────────
|
|
134
|
-
const destinations = subcommand(backup, "destinations");
|
|
135
|
-
|
|
136
|
-
subcommand(destinations, "list").action(
|
|
137
|
-
async (_opts: unknown, cmd: Command) => {
|
|
138
|
-
const r = await cliIpcCall<{
|
|
139
|
-
destinations: Array<{ path: string; encrypt: boolean }>;
|
|
140
|
-
}>("backup_destinations_list");
|
|
141
|
-
if (!r.ok) {
|
|
142
|
-
return exitFromIpcResult(
|
|
143
|
-
{ ok: false, error: r.error, statusCode: r.statusCode },
|
|
144
|
-
cmd,
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
const { destinations: dests } = r.result!;
|
|
148
|
-
if (dests.length === 0) {
|
|
149
|
-
log.info("No offsite destinations configured");
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
const pathW = Math.max(4, ...dests.map((d) => d.path.length));
|
|
153
|
-
log.info("Path".padEnd(pathW) + " " + "Encrypted");
|
|
154
|
-
log.info("-".repeat(pathW + 2 + 9));
|
|
155
|
-
for (const d of dests) {
|
|
156
|
-
log.info(d.path.padEnd(pathW) + " " + (d.encrypt ? "yes" : "no"));
|
|
157
|
-
}
|
|
158
|
-
},
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
subcommand(destinations, "add").action(
|
|
162
|
-
async (path: string, opts: { plaintext?: boolean }, cmd: Command) => {
|
|
163
|
-
const r = await cliIpcCall("backup_destinations_add", {
|
|
164
|
-
body: {
|
|
165
|
-
path,
|
|
166
|
-
encrypt: !opts.plaintext,
|
|
167
|
-
},
|
|
168
|
-
});
|
|
169
|
-
if (!r.ok) {
|
|
170
|
-
return exitFromIpcResult(
|
|
171
|
-
{ ok: false, error: r.error, statusCode: r.statusCode },
|
|
172
|
-
cmd,
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
log.info(
|
|
176
|
-
`Added destination ${path} (${opts.plaintext ? "plaintext" : "encrypted"})`,
|
|
177
|
-
);
|
|
178
|
-
},
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
subcommand(destinations, "remove").action(
|
|
182
|
-
async (path: string, _opts: unknown, cmd: Command) => {
|
|
183
|
-
const r = await cliIpcCall("backup_destinations_remove", {
|
|
184
|
-
body: { path },
|
|
185
|
-
});
|
|
186
|
-
if (!r.ok) {
|
|
187
|
-
return exitFromIpcResult(
|
|
188
|
-
{ ok: false, error: r.error, statusCode: r.statusCode },
|
|
189
|
-
cmd,
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
log.info(`Removed destination ${path}`);
|
|
193
|
-
},
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
subcommand(destinations, "set-encrypt").action(
|
|
197
|
-
async (path: string, value: string, _opts: unknown, cmd: Command) => {
|
|
198
|
-
const normalized = value.toLowerCase();
|
|
199
|
-
if (normalized !== "true" && normalized !== "false") {
|
|
200
|
-
log.error(
|
|
201
|
-
`Invalid encrypt value "${value}". Must be "true" or "false". ` +
|
|
202
|
-
`Run 'assistant backup destinations set-encrypt --help' for usage.`,
|
|
203
|
-
);
|
|
204
|
-
process.exitCode = 1;
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
const r = await cliIpcCall("backup_destinations_set_encrypt", {
|
|
208
|
-
body: {
|
|
209
|
-
path,
|
|
210
|
-
encrypt: normalized === "true",
|
|
211
|
-
},
|
|
212
|
-
});
|
|
213
|
-
if (!r.ok) {
|
|
214
|
-
return exitFromIpcResult(
|
|
215
|
-
{ ok: false, error: r.error, statusCode: r.statusCode },
|
|
216
|
-
cmd,
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
log.info(`Set ${path} encrypt=${normalized}`);
|
|
220
|
-
},
|
|
221
|
-
);
|
|
222
|
-
|
|
223
|
-
// ── status ───────────────────────────────────────────────────────
|
|
224
|
-
subcommand(backup, "status").action(
|
|
225
|
-
async (_opts: unknown, cmd: Command) => {
|
|
226
|
-
const r = await cliIpcCall<{
|
|
227
|
-
enabled: boolean;
|
|
228
|
-
intervalHours: number;
|
|
229
|
-
retention: number;
|
|
230
|
-
lastRunAt: string | null;
|
|
231
|
-
nextRunAt: string | null;
|
|
232
|
-
localDir: string;
|
|
233
|
-
localSnapshotCount: number;
|
|
234
|
-
offsiteEnabled: boolean;
|
|
235
|
-
offsite: Array<{
|
|
236
|
-
path: string;
|
|
237
|
-
encrypt: boolean;
|
|
238
|
-
reachable: boolean;
|
|
239
|
-
snapshotCount: number;
|
|
240
|
-
}>;
|
|
241
|
-
}>("backup_status");
|
|
242
|
-
if (!r.ok) {
|
|
243
|
-
return exitFromIpcResult(
|
|
244
|
-
{ ok: false, error: r.error, statusCode: r.statusCode },
|
|
245
|
-
cmd,
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
const s = r.result!;
|
|
249
|
-
const now = Date.now();
|
|
250
|
-
|
|
251
|
-
log.info(`Automatic backups: ${s.enabled ? "enabled" : "disabled"}`);
|
|
252
|
-
log.info(`Interval: every ${s.intervalHours}h`);
|
|
253
|
-
log.info(
|
|
254
|
-
`Retention: ${s.retention} snapshots per destination`,
|
|
255
|
-
);
|
|
256
|
-
|
|
257
|
-
if (s.lastRunAt) {
|
|
258
|
-
const lastRunMs = new Date(s.lastRunAt).getTime();
|
|
259
|
-
log.info(
|
|
260
|
-
`Last run: ${formatDate(new Date(s.lastRunAt))} (${formatDurationShort(now - lastRunMs)} ago)`,
|
|
261
|
-
);
|
|
262
|
-
if (s.enabled && s.nextRunAt) {
|
|
263
|
-
const nextMs = new Date(s.nextRunAt).getTime();
|
|
264
|
-
const delta = nextMs - now;
|
|
265
|
-
if (delta <= 0) {
|
|
266
|
-
log.info(`Next run: due now`);
|
|
267
|
-
} else {
|
|
268
|
-
log.info(`Next run: in ${formatDurationShort(delta)}`);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
} else {
|
|
272
|
-
log.info(`Last run: never`);
|
|
273
|
-
if (s.enabled) {
|
|
274
|
-
log.info(`Next run: on next tick`);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
log.info(
|
|
279
|
-
`Local directory: ${s.localDir} (${s.localSnapshotCount} snapshots)`,
|
|
280
|
-
);
|
|
281
|
-
|
|
282
|
-
log.info(
|
|
283
|
-
`Offsite: ${s.offsiteEnabled ? "enabled" : "disabled"}`,
|
|
284
|
-
);
|
|
285
|
-
if (!s.offsiteEnabled) {
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
if (s.offsite.length === 0) {
|
|
289
|
-
log.info(` (no destinations configured)`);
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
for (const dest of s.offsite) {
|
|
293
|
-
const tag = dest.reachable ? "[OK]" : "[unreachable]";
|
|
294
|
-
const enc = dest.encrypt ? "encrypted" : "plaintext";
|
|
295
|
-
const suffix = dest.reachable
|
|
296
|
-
? ""
|
|
297
|
-
: " -- parent directory not reachable";
|
|
298
|
-
log.info(
|
|
299
|
-
` ${tag} ${dest.path} (${enc}, ${dest.snapshotCount} snapshots)${suffix}`,
|
|
300
|
-
);
|
|
301
|
-
}
|
|
302
|
-
},
|
|
303
|
-
);
|
|
304
|
-
|
|
305
|
-
// ── list ─────────────────────────────────────────────────────────
|
|
306
|
-
subcommand(backup, "list").action(
|
|
307
|
-
async (_opts: unknown, cmd: Command) => {
|
|
308
|
-
const r = await cliIpcCall<{
|
|
309
|
-
local: Array<{
|
|
310
|
-
filename: string;
|
|
311
|
-
createdAt: string;
|
|
312
|
-
sizeBytes: number;
|
|
313
|
-
encrypted: boolean;
|
|
314
|
-
}>;
|
|
315
|
-
offsite: Array<{
|
|
316
|
-
destination: { path: string; encrypt: boolean };
|
|
317
|
-
snapshots: Array<{
|
|
318
|
-
filename: string;
|
|
319
|
-
createdAt: string;
|
|
320
|
-
sizeBytes: number;
|
|
321
|
-
encrypted: boolean;
|
|
322
|
-
}>;
|
|
323
|
-
reachable: boolean;
|
|
324
|
-
}>;
|
|
325
|
-
offsiteEnabled: boolean;
|
|
326
|
-
nextRunAt: string | null;
|
|
327
|
-
}>("backups_list");
|
|
328
|
-
if (!r.ok) {
|
|
329
|
-
return exitFromIpcResult(
|
|
330
|
-
{ ok: false, error: r.error, statusCode: r.statusCode },
|
|
331
|
-
cmd,
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
|
-
const data = r.result!;
|
|
335
|
-
|
|
336
|
-
printSnapshotGroup(`Local:`, data.local);
|
|
337
|
-
|
|
338
|
-
if (!data.offsiteEnabled) {
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
for (const dest of data.offsite) {
|
|
342
|
-
const tag = dest.destination.encrypt ? "encrypted" : "plaintext";
|
|
343
|
-
log.info("");
|
|
344
|
-
printSnapshotGroup(
|
|
345
|
-
`Offsite: ${dest.destination.path} (${tag})`,
|
|
346
|
-
dest.snapshots,
|
|
347
|
-
);
|
|
348
|
-
}
|
|
349
|
-
},
|
|
350
|
-
);
|
|
351
|
-
},
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// ---------------------------------------------------------------------------
|
|
356
|
-
// Snapshot table printer
|
|
357
|
-
// ---------------------------------------------------------------------------
|
|
358
|
-
|
|
359
|
-
function printSnapshotGroup(
|
|
360
|
-
heading: string,
|
|
361
|
-
entries: Array<{
|
|
362
|
-
filename: string;
|
|
363
|
-
createdAt: string;
|
|
364
|
-
sizeBytes: number;
|
|
365
|
-
encrypted: boolean;
|
|
366
|
-
}>,
|
|
367
|
-
): void {
|
|
368
|
-
log.info(heading);
|
|
369
|
-
if (entries.length === 0) {
|
|
370
|
-
log.info(" (none)");
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
const tsW = 19;
|
|
374
|
-
const sizeW = 10;
|
|
375
|
-
const encW = 9;
|
|
376
|
-
log.info(
|
|
377
|
-
" " +
|
|
378
|
-
"Timestamp".padEnd(tsW) +
|
|
379
|
-
" " +
|
|
380
|
-
"Size".padEnd(sizeW) +
|
|
381
|
-
" " +
|
|
382
|
-
"Encrypted".padEnd(encW) +
|
|
383
|
-
" " +
|
|
384
|
-
"Filename",
|
|
385
|
-
);
|
|
386
|
-
for (const e of entries) {
|
|
387
|
-
log.info(
|
|
388
|
-
" " +
|
|
389
|
-
formatDate(new Date(e.createdAt)).padEnd(tsW) +
|
|
390
|
-
" " +
|
|
391
|
-
formatBytes(e.sizeBytes).padEnd(sizeW) +
|
|
392
|
-
" " +
|
|
393
|
-
(e.encrypted ? "yes" : "no").padEnd(encW) +
|
|
394
|
-
" " +
|
|
395
|
-
e.filename,
|
|
396
|
-
);
|
|
397
|
-
}
|
|
398
|
-
}
|