dsh-config-manager 0.1.69 → 0.1.71
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 +59 -18
- package/README.zh-CN.md +29 -9
- package/lib/adapters/credentials.js +6 -3
- package/lib/adapters/file-collection.d.ts +21 -1
- package/lib/adapters/file-collection.js +32 -8
- package/lib/adapters/mcp.d.ts +12 -2
- package/lib/adapters/mcp.js +30 -13
- package/lib/adapters/plugin-files.d.ts +8 -0
- package/lib/adapters/plugin-files.js +36 -4
- package/lib/adapters/plugins.d.ts +28 -2
- package/lib/adapters/plugins.js +204 -55
- package/lib/adapters/prompts.d.ts +12 -2
- package/lib/adapters/prompts.js +34 -19
- package/lib/adapters/providers.js +5 -4
- package/lib/adapters/self.d.ts +20 -0
- package/lib/adapters/self.js +73 -0
- package/lib/adapters/sessions.d.ts +8 -0
- package/lib/adapters/sessions.js +47 -1
- package/lib/adapters/settings.d.ts +21 -3
- package/lib/adapters/settings.js +44 -7
- package/lib/adapters/skills.d.ts +36 -4
- package/lib/adapters/skills.js +222 -3
- package/lib/adapters/test-helpers.d.ts +31 -2
- package/lib/adapters/test-helpers.js +42 -5
- package/lib/adapters/workspaces.js +97 -5
- package/lib/cli/actions.d.ts +50 -3
- package/lib/cli/actions.js +103 -11
- package/lib/cli/help.d.ts +65 -0
- package/lib/cli/help.js +517 -0
- package/lib/cli/import-source.d.ts +32 -0
- package/lib/cli/import-source.js +178 -0
- package/lib/cli/index.d.ts +18 -2
- package/lib/cli/index.js +118 -90
- package/lib/cli/web/client-script.d.ts +8 -0
- package/lib/cli/web/client-script.js +11 -0
- package/lib/cli/web/http.d.ts +5 -0
- package/lib/cli/web/http.js +2 -1
- package/lib/cli/web/page.d.ts +16 -6
- package/lib/cli/web/page.js +102 -25
- package/lib/cli/web/routes.js +24 -5
- package/lib/cli/web/server.js +23 -2
- package/lib/client.d.ts +1019 -36
- package/lib/client.js +5522 -1564
- package/lib/core/analyzer.d.ts +15 -1
- package/lib/core/analyzer.js +114 -13
- package/lib/core/backup-plan.js +17 -1
- package/lib/core/backup.d.ts +8 -0
- package/lib/core/backup.js +28 -6
- package/lib/core/cache-cleaner.d.ts +10 -0
- package/lib/core/cache-cleaner.js +63 -22
- package/lib/core/checkpoint.d.ts +362 -0
- package/lib/core/checkpoint.js +1309 -0
- package/lib/core/journal.d.ts +37 -0
- package/lib/core/journal.js +69 -0
- package/lib/core/messages.d.ts +13 -0
- package/lib/core/messages.js +26 -0
- package/lib/core/model-tools.js +3 -0
- package/lib/core/patch-layers.d.ts +83 -0
- package/lib/core/patch-layers.js +110 -0
- package/lib/core/restore.d.ts +21 -0
- package/lib/core/restore.js +37 -3
- package/lib/core/rollback.js +6 -3
- package/lib/core/self-update.d.ts +52 -0
- package/lib/core/self-update.js +70 -0
- package/lib/core/session-health.d.ts +29 -5
- package/lib/core/session-health.js +28 -3
- package/lib/core/types.d.ts +126 -0
- package/lib/foreign/antigravity.d.ts +28 -0
- package/lib/foreign/antigravity.js +157 -0
- package/lib/foreign/bundle.d.ts +25 -0
- package/lib/foreign/bundle.js +79 -0
- package/lib/foreign/chatgpt.d.ts +6 -0
- package/lib/foreign/chatgpt.js +25 -0
- package/lib/foreign/claude-code.d.ts +3 -0
- package/lib/foreign/claude-code.js +85 -0
- package/lib/foreign/claude-sessions.d.ts +108 -0
- package/lib/foreign/claude-sessions.js +299 -0
- package/lib/foreign/cline.d.ts +6 -0
- package/lib/foreign/cline.js +27 -0
- package/lib/foreign/codex.d.ts +83 -0
- package/lib/foreign/codex.js +348 -0
- package/lib/foreign/continue.d.ts +9 -0
- package/lib/foreign/continue.js +65 -0
- package/lib/foreign/copilot.d.ts +39 -0
- package/lib/foreign/copilot.js +95 -0
- package/lib/foreign/crush.d.ts +11 -0
- package/lib/foreign/crush.js +57 -0
- package/lib/foreign/cursor.d.ts +87 -0
- package/lib/foreign/cursor.js +171 -0
- package/lib/foreign/dsh.d.ts +30 -0
- package/lib/foreign/dsh.js +101 -0
- package/lib/foreign/dsh4.d.ts +3 -0
- package/lib/foreign/dsh4.js +13 -0
- package/lib/foreign/gemini.d.ts +7 -0
- package/lib/foreign/gemini.js +44 -0
- package/lib/foreign/goose.d.ts +11 -0
- package/lib/foreign/goose.js +53 -0
- package/lib/foreign/grokbuild.d.ts +8 -0
- package/lib/foreign/grokbuild.js +25 -0
- package/lib/foreign/hermes.d.ts +2 -0
- package/lib/foreign/hermes.js +78 -0
- package/lib/foreign/kernel.d.ts +132 -0
- package/lib/foreign/kernel.js +569 -0
- package/lib/foreign/kilocode.d.ts +11 -0
- package/lib/foreign/kilocode.js +51 -0
- package/lib/foreign/kimi.d.ts +7 -0
- package/lib/foreign/kimi.js +47 -0
- package/lib/foreign/mimocode.d.ts +11 -0
- package/lib/foreign/mimocode.js +51 -0
- package/lib/foreign/openclaw.d.ts +7 -0
- package/lib/foreign/openclaw.js +46 -0
- package/lib/foreign/opencode.d.ts +11 -0
- package/lib/foreign/opencode.js +52 -0
- package/lib/foreign/pi.d.ts +7 -0
- package/lib/foreign/pi.js +48 -0
- package/lib/foreign/platform-paths.d.ts +66 -0
- package/lib/foreign/platform-paths.js +141 -0
- package/lib/foreign/qoder.d.ts +10 -0
- package/lib/foreign/qoder.js +49 -0
- package/lib/foreign/qwen.d.ts +7 -0
- package/lib/foreign/qwen.js +45 -0
- package/lib/foreign/read-antigravity.d.ts +57 -0
- package/lib/foreign/read-antigravity.js +504 -0
- package/lib/foreign/read-chatgpt.d.ts +20 -0
- package/lib/foreign/read-chatgpt.js +509 -0
- package/lib/foreign/read-claude-code.d.ts +24 -0
- package/lib/foreign/read-claude-code.js +234 -0
- package/lib/foreign/read-cline.d.ts +53 -0
- package/lib/foreign/read-cline.js +479 -0
- package/lib/foreign/read-codex.d.ts +75 -0
- package/lib/foreign/read-codex.js +842 -0
- package/lib/foreign/read-continue.d.ts +87 -0
- package/lib/foreign/read-continue.js +489 -0
- package/lib/foreign/read-copilot.d.ts +42 -0
- package/lib/foreign/read-copilot.js +267 -0
- package/lib/foreign/read-crush.d.ts +54 -0
- package/lib/foreign/read-crush.js +478 -0
- package/lib/foreign/read-cursor.d.ts +141 -0
- package/lib/foreign/read-cursor.js +705 -0
- package/lib/foreign/read-dsh.d.ts +43 -0
- package/lib/foreign/read-dsh.js +205 -0
- package/lib/foreign/read-dsh4.d.ts +14 -0
- package/lib/foreign/read-dsh4.js +27 -0
- package/lib/foreign/read-gemini.d.ts +55 -0
- package/lib/foreign/read-gemini.js +273 -0
- package/lib/foreign/read-goose.d.ts +25 -0
- package/lib/foreign/read-goose.js +322 -0
- package/lib/foreign/read-grokbuild.d.ts +34 -0
- package/lib/foreign/read-grokbuild.js +455 -0
- package/lib/foreign/read-hermes.d.ts +74 -0
- package/lib/foreign/read-hermes.js +690 -0
- package/lib/foreign/read-kilocode.d.ts +17 -0
- package/lib/foreign/read-kilocode.js +46 -0
- package/lib/foreign/read-kimi.d.ts +63 -0
- package/lib/foreign/read-kimi.js +719 -0
- package/lib/foreign/read-mimocode.d.ts +12 -0
- package/lib/foreign/read-mimocode.js +56 -0
- package/lib/foreign/read-openclaw.d.ts +51 -0
- package/lib/foreign/read-openclaw.js +445 -0
- package/lib/foreign/read-opencode.d.ts +150 -0
- package/lib/foreign/read-opencode.js +896 -0
- package/lib/foreign/read-pi.d.ts +56 -0
- package/lib/foreign/read-pi.js +411 -0
- package/lib/foreign/read-qoder.d.ts +54 -0
- package/lib/foreign/read-qoder.js +317 -0
- package/lib/foreign/read-qwen.d.ts +40 -0
- package/lib/foreign/read-qwen.js +326 -0
- package/lib/foreign/read-reasonix.d.ts +66 -0
- package/lib/foreign/read-reasonix.js +537 -0
- package/lib/foreign/read-teleagent.d.ts +15 -0
- package/lib/foreign/read-teleagent.js +68 -0
- package/lib/foreign/read-trae.d.ts +67 -0
- package/lib/foreign/read-trae.js +492 -0
- package/lib/foreign/read-vibe.d.ts +28 -0
- package/lib/foreign/read-vibe.js +364 -0
- package/lib/foreign/read-workbuddy.d.ts +49 -0
- package/lib/foreign/read-workbuddy.js +346 -0
- package/lib/foreign/read-zcode.d.ts +12 -0
- package/lib/foreign/read-zcode.js +38 -0
- package/lib/foreign/read-zed.d.ts +30 -0
- package/lib/foreign/read-zed.js +473 -0
- package/lib/foreign/reasonix.d.ts +6 -0
- package/lib/foreign/reasonix.js +20 -0
- package/lib/foreign/registry.d.ts +196 -0
- package/lib/foreign/registry.js +573 -0
- package/lib/foreign/session-ir.d.ts +186 -0
- package/lib/foreign/session-ir.js +427 -0
- package/lib/foreign/session-read.d.ts +102 -0
- package/lib/foreign/session-read.js +254 -0
- package/lib/foreign/session-source.d.ts +216 -0
- package/lib/foreign/session-source.js +453 -0
- package/lib/foreign/source-modules.d.ts +45 -0
- package/lib/foreign/source-modules.js +52 -0
- package/lib/foreign/sqlite.d.ts +61 -0
- package/lib/foreign/sqlite.js +404 -0
- package/lib/foreign/teleagent.d.ts +11 -0
- package/lib/foreign/teleagent.js +53 -0
- package/lib/foreign/trae.d.ts +6 -0
- package/lib/foreign/trae.js +20 -0
- package/lib/foreign/truth-table.d.ts +62 -0
- package/lib/foreign/truth-table.js +333 -0
- package/lib/foreign/types.d.ts +272 -0
- package/lib/foreign/types.js +2 -0
- package/lib/foreign/vibe.d.ts +6 -0
- package/lib/foreign/vibe.js +23 -0
- package/lib/foreign/workbuddy.d.ts +7 -0
- package/lib/foreign/workbuddy.js +45 -0
- package/lib/foreign/zcode.d.ts +11 -0
- package/lib/foreign/zcode.js +51 -0
- package/lib/foreign/zed.d.ts +11 -0
- package/lib/foreign/zed.js +53 -0
- package/lib/index.d.ts +154 -13
- package/lib/index.js +661 -84
- package/lib/market/github-repos.d.ts +28 -0
- package/lib/market/github-repos.js +43 -1
- package/lib/market/types.d.ts +1 -3
- package/lib/profiles/dsh-profile-manager.d.ts +54 -2
- package/lib/profiles/dsh-profile-manager.js +182 -11
- package/lib/profiles/dsh-profile-shared.d.ts +47 -0
- package/lib/profiles/dsh-profile-shared.js +15 -1
- package/lib/profiles/index.d.ts +1 -1
- package/lib/profiles/index.js +1 -1
- package/lib/routes/consult.js +51 -5
- package/lib/routes/foreign-context.d.ts +43 -0
- package/lib/routes/foreign-context.js +65 -0
- package/lib/routes/foreign.d.ts +11 -0
- package/lib/routes/foreign.js +208 -0
- package/lib/routes/import.d.ts +8 -0
- package/lib/routes/import.js +22 -1
- package/lib/routes/index.d.ts +2 -2
- package/lib/routes/index.js +4 -0
- package/lib/routes/kit.d.ts +4 -3
- package/lib/routes/market.js +0 -1
- package/lib/routes/prefs.d.ts +2 -1
- package/lib/routes/prefs.js +14 -2
- package/lib/routes/recovery.js +363 -3
- package/lib/routes/session-export.d.ts +117 -0
- package/lib/routes/session-export.js +270 -0
- package/lib/routes/snapshots.js +12 -2
- package/lib/routes/sync.js +82 -10
- package/lib/security/redaction.d.ts +14 -0
- package/lib/security/redaction.js +24 -9
- package/lib/security/secret-scanner.d.ts +27 -0
- package/lib/security/secret-scanner.js +54 -1
- package/lib/sync/autosync-scheduler.d.ts +13 -1
- package/lib/sync/autosync-scheduler.js +49 -26
- package/lib/sync/backup-files.js +6 -0
- package/lib/sync/gist/gist-transport.d.ts +91 -0
- package/lib/sync/gist/gist-transport.js +406 -0
- package/lib/sync/gist/messages.d.ts +22 -0
- package/lib/sync/gist/messages.js +46 -0
- package/lib/sync/s3/messages.d.ts +53 -0
- package/lib/sync/s3/messages.js +83 -0
- package/lib/sync/s3/s3-providers.d.ts +78 -0
- package/lib/sync/s3/s3-providers.js +156 -0
- package/lib/sync/s3/s3-transport.d.ts +126 -0
- package/lib/sync/s3/s3-transport.js +551 -0
- package/lib/sync/s3/sigv4.d.ts +107 -0
- package/lib/sync/s3/sigv4.js +196 -0
- package/lib/sync/sync-config.d.ts +250 -4
- package/lib/sync/sync-config.js +658 -57
- package/lib/sync/sync-engine.d.ts +1 -0
- package/lib/sync/sync-engine.js +4 -2
- package/lib/sync/sync-history.d.ts +1 -1
- package/lib/sync/webdav/webdav-transport.d.ts +31 -3
- package/lib/sync/webdav/webdav-transport.js +102 -18
- package/lib/ui/commands.d.ts +4 -3
- package/lib/ui/commands.js +41 -9
- package/lib/ui/disk-usage-view.d.ts +44 -0
- package/lib/ui/disk-usage-view.js +55 -1
- package/lib/ui/dsh-profiles-view.d.ts +53 -0
- package/lib/ui/dsh-profiles-view.js +56 -0
- package/lib/ui/foreign-view.d.ts +162 -0
- package/lib/ui/foreign-view.js +225 -0
- package/lib/ui/i18n.d.ts +77 -0
- package/lib/ui/i18n.js +164 -0
- package/lib/ui/session-inventory-view.d.ts +106 -0
- package/lib/ui/session-inventory-view.js +58 -0
- package/lib/ui/session-layout-view.d.ts +265 -0
- package/lib/ui/session-layout-view.js +271 -0
- package/lib/ui/sync-repository-picker.d.ts +98 -0
- package/lib/ui/sync-repository-picker.js +145 -0
- package/lib/ui/sync-settings-view.d.ts +143 -4
- package/lib/ui/sync-settings-view.js +231 -7
- package/lib/ui/types.d.ts +47 -0
- package/lib/utils/asar-read.d.ts +113 -0
- package/lib/utils/asar-read.js +523 -0
- package/lib/utils/env-lock.js +7 -2
- package/lib/utils/patch-yaml.d.ts +25 -0
- package/lib/utils/patch-yaml.js +62 -0
- package/lib/utils/paths.d.ts +10 -1
- package/lib/utils/paths.js +24 -3
- package/lib/utils/recursive-walk.js +12 -13
- package/lib/utils/session-format.d.ts +35 -2
- package/lib/utils/session-format.js +131 -5
- package/lib/utils/session-health-scan.d.ts +7 -1
- package/lib/utils/session-health-scan.js +369 -12
- package/lib/utils/session-layout-repair-service.d.ts +98 -0
- package/lib/utils/session-layout-repair-service.js +457 -0
- package/lib/utils/session-log-repair.d.ts +27 -0
- package/lib/utils/session-log-repair.js +233 -62
- package/lib/utils/session-repair-service.d.ts +32 -3
- package/lib/utils/session-repair-service.js +148 -9
- package/lib/utils/session-row-facts.d.ts +73 -0
- package/lib/utils/session-row-facts.js +182 -0
- package/lib/utils/session-verify.d.ts +118 -0
- package/lib/utils/session-verify.js +705 -0
- package/lib/utils/shared-constants.d.ts +59 -0
- package/lib/utils/shared-constants.js +71 -0
- package/package.json +1 -1
- package/src/adapters/credentials.test.ts +27 -0
- package/src/adapters/credentials.ts +6 -3
- package/src/adapters/file-collection.ts +33 -8
- package/src/adapters/files.test.ts +222 -0
- package/src/adapters/mcp.test.ts +43 -0
- package/src/adapters/mcp.ts +37 -14
- package/src/adapters/plugin-files.ts +34 -4
- package/src/adapters/plugins.test.ts +241 -0
- package/src/adapters/plugins.ts +201 -53
- package/src/adapters/prompts.test.ts +49 -0
- package/src/adapters/prompts.ts +41 -20
- package/src/adapters/providers.test.ts +16 -0
- package/src/adapters/providers.ts +4 -3
- package/src/adapters/self.test.ts +77 -0
- package/src/adapters/self.ts +75 -0
- package/src/adapters/sessions.test.ts +77 -0
- package/src/adapters/sessions.ts +47 -1
- package/src/adapters/settings.test.ts +78 -0
- package/src/adapters/settings.ts +42 -6
- package/src/adapters/skills.ts +221 -3
- package/src/adapters/test-helpers.ts +43 -5
- package/src/adapters/workspaces-dup-id.test.ts +144 -0
- package/src/adapters/workspaces.test.ts +10 -0
- package/src/adapters/workspaces.ts +97 -5
- package/src/cli/actions.test.ts +117 -2
- package/src/cli/actions.ts +132 -12
- package/src/cli/help.ts +570 -0
- package/src/cli/import-source.test.ts +193 -0
- package/src/cli/import-source.ts +229 -0
- package/src/cli/index.ts +129 -94
- package/src/cli/web/client-script.ts +12 -0
- package/src/cli/web/console-fixes.test.ts +36 -0
- package/src/cli/web/http.ts +7 -1
- package/src/cli/web/page.ts +108 -25
- package/src/cli/web/routes.ts +23 -4
- package/src/cli/web/script-anchor.test.ts +33 -0
- package/src/cli/web/server.ts +28 -2
- package/src/cli/web/web.test.ts +447 -4
- package/src/client/ConfigManagerSection.tsx +118 -63
- package/src/client/about/AboutPanel.tsx +42 -3
- package/src/client/about/PluginUpdateDialog.tsx +185 -0
- package/src/client/about/ReleaseBody.tsx +144 -0
- package/src/client/about/ReleaseNotesDialog.tsx +3 -130
- package/src/client/about/about-view.ts +7 -5
- package/src/client/about/plugin-update-view.test.ts +25 -0
- package/src/client/about/plugin-update-view.ts +26 -0
- package/src/client/about/release-notes-view.test.ts +33 -0
- package/src/client/about/release-notes-view.ts +34 -0
- package/src/client/api.ts +145 -1
- package/src/client/common/Icon.tsx +3 -0
- package/src/client/common/RunsCenter.tsx +8 -2
- package/src/client/common/Select.tsx +9 -2
- package/src/client/common/info-hint-guard.test.ts +63 -18
- package/src/client/common/plan-text-redaction.test.ts +193 -8
- package/src/client/common/routes.ts +15 -0
- package/src/client/config-manager.module.css +506 -354
- package/src/client/css-scale-guard.test.ts +122 -0
- package/src/client/css-token-guard.test.ts +82 -0
- package/src/client/environment/EnvironmentPanel.tsx +45 -16
- package/src/client/{snapshots → environment/maintenance}/DiskUsageCard.tsx +21 -17
- package/src/client/environment/profiles-incomplete-ui.test.ts +103 -0
- package/src/client/export/ExportView.tsx +9 -2
- package/src/client/{snapshots → home}/BackupScheduleCard.tsx +13 -4
- package/src/client/home/HomePanel.tsx +69 -157
- package/src/client/icon-layer-guard.test.ts +1 -1
- package/src/client/import/ForeignImportView.tsx +318 -0
- package/src/client/import/ImportWizardView.tsx +3 -1
- package/src/client/import/foreign-import-view-guard.test.ts +195 -0
- package/src/client/import/import-file-select.test.ts +6 -6
- package/src/client/import/import-wizard-steps.tsx +17 -1
- package/src/client/import/use-import-wizard-controller.ts +3 -3
- package/src/client/index.ts +1 -1
- package/src/client/library/LibraryActions.tsx +31 -34
- package/src/client/library/LibraryPanel.tsx +4 -1
- package/src/client/{snapshots → library}/RestorePlanView.tsx +1 -1
- package/src/client/{snapshots → library}/files-layout.test.ts +2 -2
- package/src/client/{snapshots → library}/report-dismiss.test.ts +26 -3
- package/src/client/library/task-panel-visibility.test.ts +25 -17
- package/src/client/locales.ts +278 -36
- package/src/client/lucide-icons.d.ts +1 -0
- package/src/client/market/MarketPanel.tsx +11 -2
- package/src/client/recovery/IncompleteCopiesSection.tsx +132 -0
- package/src/client/recovery/RecoveryPanel.tsx +486 -59
- package/src/client/recovery/recovery-api.ts +30 -0
- package/src/client/recovery/recovery-incomplete-copies.test.ts +151 -0
- package/src/client/recovery/recovery-locales.test.ts +88 -0
- package/src/client/recovery/recovery-locales.ts +321 -6
- package/src/client/recovery/recovery-panel-layout.test.ts +52 -0
- package/src/client/recovery/recovery-view.ts +92 -2
- package/src/client/run-store.test.ts +201 -89
- package/src/client/run-store.ts +114 -143
- package/src/client/session-export/session-export-view.test.ts +103 -0
- package/src/client/session-export/session-export-view.ts +118 -0
- package/src/client/sync/ChannelConfigDialog.tsx +219 -14
- package/src/client/sync/SyncChannelEntryCard.tsx +52 -4
- package/src/client/sync/SyncLogList.tsx +1 -1
- package/src/client/sync/SyncPanel.tsx +166 -102
- package/src/client/sync/SyncRepositoryPicker.tsx +216 -0
- package/src/client/sync/SyncSettingsView.test.ts +6 -1
- package/src/client/sync/history-model.test.ts +94 -57
- package/src/client/sync/history-model.ts +44 -25
- package/src/client/sync/sync-api.test.ts +56 -0
- package/src/client/sync/sync-api.ts +92 -0
- package/src/client/sync/sync-locales.ts +162 -2
- package/src/client/sync/sync-view.test.ts +3 -8
- package/src/client/sync/sync-view.ts +111 -21
- package/src/core/analyzer-cache.test.ts +47 -0
- package/src/core/analyzer.ts +115 -14
- package/src/core/backup-plan.test.ts +35 -0
- package/src/core/backup-plan.ts +19 -1
- package/src/core/backup.test.ts +37 -1
- package/src/core/backup.ts +27 -6
- package/src/core/cache-cleaner.test.ts +120 -1
- package/src/core/cache-cleaner.ts +59 -22
- package/src/core/checkpoint.test.ts +665 -0
- package/src/core/checkpoint.ts +1614 -0
- package/src/core/credential-plan-consistency.test.ts +93 -0
- package/src/core/incident-wiring.test.ts +6 -2
- package/src/core/journal.test.ts +70 -0
- package/src/core/journal.ts +68 -0
- package/src/core/messages.ts +26 -0
- package/src/core/model-tools.ts +3 -0
- package/src/core/patch-layers.test.ts +94 -0
- package/src/core/patch-layers.ts +150 -0
- package/src/core/restore.ts +59 -3
- package/src/core/rollback.ts +6 -3
- package/src/core/section-content-consistency.test.ts +185 -0
- package/src/core/self-update.test.ts +110 -0
- package/src/core/self-update.ts +118 -0
- package/src/core/session-health.test.ts +91 -1
- package/src/core/session-health.ts +56 -9
- package/src/core/types.ts +127 -1
- package/src/foreign/antigravity.test.ts +271 -0
- package/src/foreign/antigravity.ts +188 -0
- package/src/foreign/bundle.ts +108 -0
- package/src/foreign/chatgpt.test.ts +279 -0
- package/src/foreign/chatgpt.ts +30 -0
- package/src/foreign/claude-code.test.ts +234 -0
- package/src/foreign/claude-code.ts +106 -0
- package/src/foreign/claude-sessions-bytes.test.ts +227 -0
- package/src/foreign/claude-sessions.test.ts +180 -0
- package/src/foreign/claude-sessions.ts +461 -0
- package/src/foreign/cline.test.ts +274 -0
- package/src/foreign/cline.ts +32 -0
- package/src/foreign/codex.test.ts +585 -0
- package/src/foreign/codex.ts +409 -0
- package/src/foreign/continue.test.ts +365 -0
- package/src/foreign/continue.ts +73 -0
- package/src/foreign/copilot.test.ts +273 -0
- package/src/foreign/copilot.ts +133 -0
- package/src/foreign/crush.test.ts +234 -0
- package/src/foreign/crush.ts +66 -0
- package/src/foreign/cursor.test.ts +509 -0
- package/src/foreign/cursor.ts +259 -0
- package/src/foreign/dsh.test.ts +198 -0
- package/src/foreign/dsh.ts +117 -0
- package/src/foreign/dsh4.test.ts +127 -0
- package/src/foreign/dsh4.ts +15 -0
- package/src/foreign/file-budget.test.ts +256 -0
- package/src/foreign/fixtures/antigravity/README.md +18 -0
- package/src/foreign/fixtures/antigravity/basic/gemini/antigravity/mcp_config.json +13 -0
- package/src/foreign/fixtures/antigravity/basic/gemini/config/mcp_config.json +24 -0
- package/src/foreign/fixtures/antigravity/empty/gemini/antigravity/mcp_config.json +0 -0
- package/src/foreign/fixtures/antigravity/empty/gemini/antigravity/mcp_oauth_tokens.json +0 -0
- package/src/foreign/fixtures/antigravity/empty/gemini/config/mcp_config.json +0 -0
- package/src/foreign/fixtures/codex/README.md +31 -0
- package/src/foreign/fixtures/codex/basic/.agents/skills/hello/SKILL.md +6 -0
- package/src/foreign/fixtures/codex/basic/.agents/skills/wrapper/nested/SKILL.md +6 -0
- package/src/foreign/fixtures/codex/basic/.codex/AGENTS.md +4 -0
- package/src/foreign/fixtures/codex/basic/.codex/config.toml +28 -0
- package/src/foreign/fixtures/codex/malformed/.codex/config.toml +3 -0
- package/src/foreign/fixtures/codex/override/.codex/AGENTS.md +1 -0
- package/src/foreign/fixtures/codex/override/.codex/AGENTS.override.md +3 -0
- package/src/foreign/fixtures/codex/sessions/.codex/archived_sessions/rollout-2026-10-05T09-00-00-22222222-2222-4222-8222-222222222222.jsonl +2 -0
- package/src/foreign/fixtures/codex/sessions/.codex/sessions/2026/10/06/rollout-2026-10-06T10-00-00-11111111-1111-4111-8111-111111111111.jsonl +15 -0
- package/src/foreign/fixtures/codex/sessions/.codex/sessions/2026/10/06/rollout-2026-10-06T11-00-00-33333333-3333-4333-8333-333333333333.jsonl +2 -0
- package/src/foreign/fixtures/codex/sessions/.codex/sessions/2026/10/06/rollout-2026-10-06T11-30-00-55555555-5555-4555-8555-555555555555.jsonl +2 -0
- package/src/foreign/fixtures/codex/sessions/.codex/sessions/2026/10/06/rollout-2026-10-06T12-00-00-44444444-4444-4444-8444-444444444444.jsonl +2 -0
- package/src/foreign/fixtures/codex/sessions/.codex/sessions/notes.jsonl +1 -0
- package/src/foreign/fixtures/copilot/README.md +22 -0
- package/src/foreign/fixtures/copilot/basic/copilot-instructions.md +3 -0
- package/src/foreign/fixtures/copilot/basic/instructions/empty.instructions.md +1 -0
- package/src/foreign/fixtures/copilot/basic/instructions/repo.instructions.md +5 -0
- package/src/foreign/fixtures/copilot/basic/mcp-config.json +24 -0
- package/src/foreign/fixtures/copilot/basic/skills/hello/SKILL.md +6 -0
- package/src/foreign/fixtures/copilot/overridden/mcp-config.json +10 -0
- package/src/foreign/fixtures/cursor/README.md +17 -0
- package/src/foreign/fixtures/cursor/alt/.cursor/mcp.json +5 -0
- package/src/foreign/fixtures/cursor/basic/.cursor/mcp.json +18 -0
- package/src/foreign/fixtures/cursor/basic/.cursor/rules/agent.mdc +5 -0
- package/src/foreign/fixtures/cursor/basic/.cursor/rules/always.mdc +6 -0
- package/src/foreign/fixtures/cursor/basic/.cursor/rules/auto.mdc +7 -0
- package/src/foreign/fixtures/cursor/basic/.cursor/rules/manual.mdc +3 -0
- package/src/foreign/fixtures/cursor/basic/.cursor/skills/.hidden/SKILL.md +6 -0
- package/src/foreign/fixtures/cursor/basic/.cursor/skills/hello/SKILL.md +6 -0
- package/src/foreign/fixtures/cursor/basic/.cursor/skills/wrapper/nested/SKILL.md +6 -0
- package/src/foreign/fixtures/cursor/basic/.cursor/skills/wrapper/nested/notes.md +1 -0
- package/src/foreign/fixtures/cursor/basic/.cursorrules +1 -0
- package/src/foreign/fixtures/cursor/basic/project/.cursor/mcp.json +9 -0
- package/src/foreign/fixtures/cursor/basic/project/.cursor/rules/project-always.mdc +5 -0
- package/src/foreign/fixtures/cursor/basic/project/.cursor/skills/hello/SKILL.md +6 -0
- package/src/foreign/fixtures/cursor/basic/project/.cursorrules +1 -0
- package/src/foreign/fixtures/cursor/empty/.cursor/mcp.json +0 -0
- package/src/foreign/fixtures/cursor/malformed/.cursor/mcp.json +3 -0
- package/src/foreign/fixtures/cursor/malformed/.cursor/rules/broken.mdc +6 -0
- package/src/foreign/fixtures/cursor/shape/.cursor/mcp.json +3 -0
- package/src/foreign/fixtures/hermes/README.md +14 -0
- package/src/foreign/fixtures/hermes/basic/SOUL.md +3 -0
- package/src/foreign/fixtures/hermes/basic/config.yaml +48 -0
- package/src/foreign/fixtures/hermes/basic/dotenv.fixture +1 -0
- package/src/foreign/fixtures/hermes/basic/memories/MEMORY.md +1 -0
- package/src/foreign/fixtures/hermes/basic/memories/USER.md +1 -0
- package/src/foreign/fixtures/hermes/basic/skills/se-team-design/SKILL.md +6 -0
- package/src/foreign/fixtures/hermes/basic/skills/se-team-design/examples/usage.md +1 -0
- package/src/foreign/fixtures/hermes/basic/skills/software-development/dogfood/SKILL.md +6 -0
- package/src/foreign/fixtures/hermes/basic/skills/software-development/dogfood/notes.md +1 -0
- package/src/foreign/fixtures/hermes/basic/state.db +1 -0
- package/src/foreign/fixtures/hermes/empty/config.yaml +0 -0
- package/src/foreign/gemini.test.ts +291 -0
- package/src/foreign/gemini.ts +52 -0
- package/src/foreign/goose.test.ts +196 -0
- package/src/foreign/goose.ts +62 -0
- package/src/foreign/grokbuild.test.ts +246 -0
- package/src/foreign/grokbuild.ts +30 -0
- package/src/foreign/hermes.test.ts +545 -0
- package/src/foreign/hermes.ts +86 -0
- package/src/foreign/kernel-credentials.test.ts +88 -0
- package/src/foreign/kernel-workspaces.test.ts +88 -0
- package/src/foreign/kernel.ts +584 -0
- package/src/foreign/kilocode.test.ts +186 -0
- package/src/foreign/kilocode.ts +60 -0
- package/src/foreign/kimi.test.ts +259 -0
- package/src/foreign/kimi.ts +55 -0
- package/src/foreign/mcp-value-shape.test.ts +286 -0
- package/src/foreign/mimocode.test.ts +178 -0
- package/src/foreign/mimocode.ts +60 -0
- package/src/foreign/openclaw.test.ts +248 -0
- package/src/foreign/openclaw.ts +54 -0
- package/src/foreign/opencode.test.ts +376 -0
- package/src/foreign/opencode.ts +61 -0
- package/src/foreign/pi.test.ts +275 -0
- package/src/foreign/pi.ts +56 -0
- package/src/foreign/platform-paths.ts +156 -0
- package/src/foreign/qoder.test.ts +266 -0
- package/src/foreign/qoder.ts +57 -0
- package/src/foreign/qwen.test.ts +203 -0
- package/src/foreign/qwen.ts +53 -0
- package/src/foreign/read-antigravity.ts +569 -0
- package/src/foreign/read-chatgpt.ts +506 -0
- package/src/foreign/read-claude-code.ts +264 -0
- package/src/foreign/read-cline.ts +517 -0
- package/src/foreign/read-codex.ts +823 -0
- package/src/foreign/read-continue.ts +557 -0
- package/src/foreign/read-copilot.ts +304 -0
- package/src/foreign/read-crush.ts +503 -0
- package/src/foreign/read-cursor.ts +778 -0
- package/src/foreign/read-dsh.ts +215 -0
- package/src/foreign/read-dsh4.ts +35 -0
- package/src/foreign/read-gemini.ts +306 -0
- package/src/foreign/read-goose.ts +333 -0
- package/src/foreign/read-grokbuild.ts +475 -0
- package/src/foreign/read-hermes.ts +724 -0
- package/src/foreign/read-kilocode.ts +55 -0
- package/src/foreign/read-kimi.ts +768 -0
- package/src/foreign/read-mimocode.ts +66 -0
- package/src/foreign/read-openclaw.ts +505 -0
- package/src/foreign/read-opencode.ts +994 -0
- package/src/foreign/read-pi.ts +459 -0
- package/src/foreign/read-qoder.ts +351 -0
- package/src/foreign/read-qwen.ts +342 -0
- package/src/foreign/read-reasonix.ts +590 -0
- package/src/foreign/read-teleagent.ts +82 -0
- package/src/foreign/read-trae.ts +488 -0
- package/src/foreign/read-vibe.ts +353 -0
- package/src/foreign/read-workbuddy.ts +374 -0
- package/src/foreign/read-zcode.ts +48 -0
- package/src/foreign/read-zed.ts +479 -0
- package/src/foreign/reasonix.test.ts +336 -0
- package/src/foreign/reasonix.ts +25 -0
- package/src/foreign/registry.test.ts +377 -0
- package/src/foreign/registry.ts +750 -0
- package/src/foreign/session-ir.test.ts +189 -0
- package/src/foreign/session-ir.ts +614 -0
- package/src/foreign/session-read.ts +295 -0
- package/src/foreign/session-source.ts +625 -0
- package/src/foreign/skill-file-caps.test.ts +171 -0
- package/src/foreign/source-modules.ts +86 -0
- package/src/foreign/source-registry.test.ts +190 -0
- package/src/foreign/sqlite.test.ts +313 -0
- package/src/foreign/sqlite.ts +446 -0
- package/src/foreign/teleagent.test.ts +147 -0
- package/src/foreign/teleagent.ts +62 -0
- package/src/foreign/trae.test.ts +316 -0
- package/src/foreign/trae.ts +25 -0
- package/src/foreign/truncation-report.test.ts +238 -0
- package/src/foreign/truth-table.ts +453 -0
- package/src/foreign/types.ts +312 -0
- package/src/foreign/vibe.test.ts +209 -0
- package/src/foreign/vibe.ts +28 -0
- package/src/foreign/workbuddy.test.ts +207 -0
- package/src/foreign/workbuddy.ts +53 -0
- package/src/foreign/zcode.test.ts +224 -0
- package/src/foreign/zcode.ts +60 -0
- package/src/foreign/zed.test.ts +240 -0
- package/src/foreign/zed.ts +62 -0
- package/src/index.facade.test.ts +254 -16
- package/src/index.ts +704 -82
- package/src/market/github-repos.test.ts +98 -1
- package/src/market/github-repos.ts +63 -1
- package/src/market/types.ts +3 -3
- package/src/profiles/dsh-profile-incomplete-copy.test.ts +177 -0
- package/src/profiles/dsh-profile-manager.test.ts +37 -0
- package/src/profiles/dsh-profile-manager.ts +188 -13
- package/src/profiles/dsh-profile-shared.ts +55 -1
- package/src/profiles/index.ts +2 -1
- package/src/routes/consult.ts +53 -4
- package/src/routes/foreign-context.ts +89 -0
- package/src/routes/foreign.test.ts +443 -0
- package/src/routes/foreign.ts +226 -0
- package/src/routes/import.ts +23 -1
- package/src/routes/index.ts +6 -2
- package/src/routes/kit.ts +4 -3
- package/src/routes/market.ts +0 -1
- package/src/routes/prefs.ts +15 -1
- package/src/routes/recovery.test.ts +167 -0
- package/src/routes/recovery.ts +331 -1
- package/src/routes/session-export.ts +308 -0
- package/src/routes/snapshots.ts +12 -2
- package/src/routes/sync.ts +91 -7
- package/src/security/redaction.test.ts +149 -0
- package/src/security/redaction.ts +25 -9
- package/src/security/secret-scanner.test.ts +186 -0
- package/src/security/secret-scanner.ts +57 -1
- package/src/sync/autosync-risk-tier.test.ts +61 -0
- package/src/sync/autosync-scheduler.test.ts +48 -1
- package/src/sync/autosync-scheduler.ts +50 -26
- package/src/sync/backup-files.ts +5 -0
- package/src/sync/gist/gist-msgkeys-catalog.test.ts +33 -0
- package/src/sync/gist/gist-msgkeys.test.ts +71 -0
- package/src/sync/gist/gist-transport.test.ts +308 -0
- package/src/sync/gist/gist-transport.ts +550 -0
- package/src/sync/gist/messages.ts +49 -0
- package/src/sync/retention-prune-all-off.test.ts +50 -0
- package/src/sync/s3/cloud-config.test.ts +283 -0
- package/src/sync/s3/messages.ts +102 -0
- package/src/sync/s3/s3-delete-index-404.test.ts +170 -0
- package/src/sync/s3/s3-delete-index-unreadable.test.ts +141 -0
- package/src/sync/s3/s3-msgkeys.test.ts +101 -0
- package/src/sync/s3/s3-providers.ts +213 -0
- package/src/sync/s3/s3-transport.test.ts +394 -0
- package/src/sync/s3/s3-transport.ts +651 -0
- package/src/sync/s3/s3-upload-index-404.test.ts +171 -0
- package/src/sync/s3/sigv4.test.ts +219 -0
- package/src/sync/s3/sigv4.ts +283 -0
- package/src/sync/sync-config.test.ts +237 -5
- package/src/sync/sync-config.ts +841 -52
- package/src/sync/sync-engine-download-tombstone.test.ts +73 -0
- package/src/sync/sync-engine.ts +5 -3
- package/src/sync/sync-history.ts +1 -1
- package/src/sync/webdav/webdav-delete-index-404.test.ts +178 -0
- package/src/sync/webdav/webdav-delete-index-unreadable.test.ts +143 -0
- package/src/sync/webdav/webdav-msgkeys.test.ts +53 -0
- package/src/sync/webdav/webdav-transport.test.ts +9 -5
- package/src/sync/webdav/webdav-transport.ts +99 -17
- package/src/sync/webdav/webdav-upload-index-404.test.ts +177 -0
- package/src/ui/commands.test.ts +4 -1
- package/src/ui/commands.ts +46 -12
- package/src/ui/disk-usage-view.test.ts +63 -1
- package/src/ui/disk-usage-view.ts +93 -1
- package/src/ui/dsh-profiles-incomplete-view.test.ts +102 -0
- package/src/ui/dsh-profiles-view.ts +94 -0
- package/src/ui/foreign-view.test.ts +241 -0
- package/src/ui/foreign-view.ts +362 -0
- package/src/ui/i18n.ts +164 -0
- package/src/ui/mid-ellipsis.test.ts +5 -10
- package/src/ui/session-inventory-view.test.ts +142 -2
- package/src/ui/session-inventory-view.ts +127 -0
- package/src/ui/session-layout-view.test.ts +361 -0
- package/src/ui/session-layout-view.ts +444 -0
- package/src/ui/sync-repository-picker.test.ts +130 -0
- package/src/ui/sync-repository-picker.ts +180 -0
- package/src/ui/sync-settings-view.test.ts +166 -0
- package/src/ui/sync-settings-view.ts +319 -9
- package/src/ui/types.ts +42 -0
- package/src/utils/asar-read.test.ts +330 -0
- package/src/utils/asar-read.ts +606 -0
- package/src/utils/env-lock.test.ts +55 -0
- package/src/utils/env-lock.ts +6 -1
- package/src/utils/patch-yaml.test.ts +112 -0
- package/src/utils/patch-yaml.ts +72 -0
- package/src/utils/paths.test.ts +63 -0
- package/src/utils/paths.ts +25 -3
- package/src/utils/recursive-walk.test.ts +20 -0
- package/src/utils/recursive-walk.ts +12 -13
- package/src/utils/session-format.test.ts +113 -2
- package/src/utils/session-format.ts +127 -5
- package/src/utils/session-health-scan.test.ts +364 -1
- package/src/utils/session-health-scan.ts +434 -15
- package/src/utils/session-layout-repair-service.test.ts +362 -0
- package/src/utils/session-layout-repair-service.ts +568 -0
- package/src/utils/session-log-repair.test.ts +260 -1
- package/src/utils/session-log-repair.ts +294 -55
- package/src/utils/session-repair-service.test.ts +148 -1
- package/src/utils/session-repair-service.ts +165 -10
- package/src/utils/session-row-facts.test.ts +159 -0
- package/src/utils/session-row-facts.ts +187 -0
- package/src/utils/session-verify.test.ts +762 -0
- package/src/utils/session-verify.ts +784 -0
- package/src/utils/shared-constants.ts +82 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dcm import --from <来源> [--dry-run]` —— 把**本机已装的外部 agent 配置**翻译成标准 bundle v1 ZIP。
|
|
3
|
+
*
|
|
4
|
+
* 为什么是「翻译成标准 bundle」而不是自己写导入:读侧只需要实现一次(Importer/analyzer)——
|
|
5
|
+
* 校验和、schema 版本协商、Zip Slip 拒绝、冲突判定、导入前强制快照、dry-run 全部自动生效,
|
|
6
|
+
* 不为外部来源另开一条写入通道(见 src/foreign/bundle.ts 的文件头)。
|
|
7
|
+
*
|
|
8
|
+
* 四条纪律:
|
|
9
|
+
* ① **--dry-run 零写入**:只跑检测 + 构建并打印分区摘要,连导出目录都不 mkdir;
|
|
10
|
+
* ② **绝不覆盖既有文件**:--out 命中已有文件时自动加 -1/-2 后缀(与 backup 同语义);
|
|
11
|
+
* 一个分区都产不出来时退出码 1(空的 bundle 会「自检通过」却什么都没有,绝不静默成功);
|
|
12
|
+
* ③ **凭据值绝不出现**:摘要只打印 skipped 机器码与**凭据引用名**(形如 mcp:svc:GITHUB_TOKEN);
|
|
13
|
+
* ④ **未知来源不猜**:打印可用来源清单并退出码 1(绝不回退到某个默认来源)。
|
|
14
|
+
*
|
|
15
|
+
* 本模块不 import ./index.ts(避免运行时循环):所有需要由 CLI 解析的东西
|
|
16
|
+
* (导出目录、版本号、--data-dir 语义)都由调用方作为参数传入。
|
|
17
|
+
*/
|
|
18
|
+
import fsp from 'node:fs/promises';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { verifyBackupZip } from '../core/backup-verify.js';
|
|
21
|
+
import { writeForeignBundle } from '../foreign/bundle.js';
|
|
22
|
+
import { FOREIGN_CONFLICT_POLICY, ForeignSourceError, createBuiltinForeignSourceRegistry, } from '../foreign/registry.js';
|
|
23
|
+
import { foreignSourceContext } from '../routes/foreign-context.js';
|
|
24
|
+
/** 分区摘要的一行:文件类给文件数,JSON 类给该分区的计数键(全部是机器可读的条目数) */
|
|
25
|
+
function sectionLine(section, result) {
|
|
26
|
+
if (section.files !== undefined)
|
|
27
|
+
return ` ${section.sectionId}: ${section.files.length} 个文件`;
|
|
28
|
+
const counters = Object.entries(result.counts)
|
|
29
|
+
.filter(([key]) => key.startsWith(section.sectionId + '.'))
|
|
30
|
+
.map(([key, value]) => key + '=' + String(value));
|
|
31
|
+
return ` ${section.sectionId}: JSON${counters.length > 0 ? '(' + counters.join(', ') + ')' : '(无条目计数)'}`;
|
|
32
|
+
}
|
|
33
|
+
function printSkipped(result, io) {
|
|
34
|
+
if (result.skipped.length === 0) {
|
|
35
|
+
io.log('未迁移项 / skipped: 无');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
io.log(`未迁移项 / skipped(${result.skipped.length}):`);
|
|
39
|
+
for (const s of result.skipped) {
|
|
40
|
+
const origin = s.origin !== undefined ? s.origin : '';
|
|
41
|
+
const detail = s.detail !== undefined ? '(' + s.detail + ')' : '';
|
|
42
|
+
const count = s.count !== undefined ? ' ×' + String(s.count) : '';
|
|
43
|
+
io.log(` [${s.code}] ${origin}${detail}${count}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** 打印摘要:**只有路径/机器码/引用名,绝无任何配置值** */
|
|
47
|
+
function printImportSummary(sourceId, detection, result, io) {
|
|
48
|
+
io.log(`来源 / source: ${sourceId}`);
|
|
49
|
+
io.log(`本机检测 / detection: ${detection.found ? '已找到 / found' : '未找到本机痕迹 / not found'}`);
|
|
50
|
+
if (detection.paths.length > 0)
|
|
51
|
+
io.log(` 位置 /${' '}paths: ${detection.paths.join(', ')}`);
|
|
52
|
+
for (const s of detection.skipped ?? []) {
|
|
53
|
+
io.log(` 检测提示 / detection note: [${s.code}]${s.origin !== undefined ? ' ' + s.origin : ''}`);
|
|
54
|
+
}
|
|
55
|
+
io.log(`待导入分区 / sections(${result.sections.length}):`);
|
|
56
|
+
if (result.sections.length === 0)
|
|
57
|
+
io.log(' (无可导入内容)/ nothing to import');
|
|
58
|
+
for (const section of result.sections)
|
|
59
|
+
io.log(sectionLine(section, result));
|
|
60
|
+
printSkipped(result, io);
|
|
61
|
+
io.log(result.credentialRefs.length > 0
|
|
62
|
+
? `需在 DSH 补录的凭据引用 / credential refs(${result.credentialRefs.length}): ${result.credentialRefs.join(', ')}`
|
|
63
|
+
: '需在 DSH 补录的凭据引用 / credential refs: 无');
|
|
64
|
+
io.log(`冲突策略 / conflict policy: ${FOREIGN_CONFLICT_POLICY}(不覆盖目标机既有同 id;跳过并报码)`);
|
|
65
|
+
}
|
|
66
|
+
/** 目标 ZIP:--out 优先;命中既有文件时自动加后缀,**绝不覆盖** */
|
|
67
|
+
async function resolveOutPath(options, sourceId) {
|
|
68
|
+
const desired = options.out !== undefined && options.out !== ''
|
|
69
|
+
? path.resolve(options.out)
|
|
70
|
+
: path.join(options.exportsDir, 'foreign-' + sourceId + '-' + stamp() + '-' + randomHex4() + '.zip');
|
|
71
|
+
if (!(await pathExists(desired)))
|
|
72
|
+
return desired;
|
|
73
|
+
const base = desired.endsWith('.zip') ? desired.slice(0, -4) : desired;
|
|
74
|
+
for (let i = 1;; i += 1) {
|
|
75
|
+
const candidate = base + '-' + String(i) + '.zip';
|
|
76
|
+
if (!(await pathExists(candidate)))
|
|
77
|
+
return candidate;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function stamp() {
|
|
81
|
+
const now = new Date();
|
|
82
|
+
return `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}`
|
|
83
|
+
+ `-${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(2, '0')}${String(now.getSeconds()).padStart(2, '0')}`;
|
|
84
|
+
}
|
|
85
|
+
function randomHex4() {
|
|
86
|
+
// 用全局 crypto(Node ≥ 19);零额外依赖
|
|
87
|
+
const bytes = new Uint8Array(4);
|
|
88
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
89
|
+
return [...bytes].map((b) => b.toString(16).padStart(2, '0')).join('');
|
|
90
|
+
}
|
|
91
|
+
async function pathExists(p) {
|
|
92
|
+
try {
|
|
93
|
+
await fsp.stat(p);
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 执行 `import --from`。
|
|
102
|
+
*
|
|
103
|
+
* `projectDir` 语义:`--cwd` 显式给出时用它;缺省 = 进程 cwd(「我现在在哪个项目里」就是
|
|
104
|
+
* Cursor/Codex 项目级配置的含义)。宿主路由走 `?projectDir=`,缺省不猜(不扫 DSH 自己的 cwd)。
|
|
105
|
+
*/
|
|
106
|
+
export async function runImportSource(options, io, env = process.env, deps = {}) {
|
|
107
|
+
const registry = createBuiltinForeignSourceRegistry();
|
|
108
|
+
let source;
|
|
109
|
+
try {
|
|
110
|
+
source = registry.get(options.from);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
if (error instanceof ForeignSourceError) {
|
|
114
|
+
io.error('未知外部来源 / unknown foreign source: ' + options.from);
|
|
115
|
+
io.error('可用来源 / available sources: ' + (error.available.length > 0 ? error.available.join(', ') : '(空)'));
|
|
116
|
+
return 1;
|
|
117
|
+
}
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* 来源上下文**经唯一构造点**(src/routes/foreign-context.ts)——宿主 GET/POST 与 CLI 三处
|
|
122
|
+
* 必须同源,否则会出现「界面上找得到、命令行读不到」这类只在某一入口复现的怪事。
|
|
123
|
+
*
|
|
124
|
+
* 注意 projectDir 的缺省语义**在 CLI 侧不同**:CLI 的「我现在在哪个项目里」就是进程 cwd
|
|
125
|
+
* (用户显式在项目目录下敲命令),所以这里缺省传 process.cwd();宿主侧则**绝不猜**
|
|
126
|
+
* (宿主进程的 cwd 不是用户的项目,缺省不传 = 不扫项目级)。这是刻意的入口语义差异,
|
|
127
|
+
* 不是分叉 —— 传什么由调用方决定,怎么拼只有一处实现。
|
|
128
|
+
*/
|
|
129
|
+
const ctx = foreignSourceContext({
|
|
130
|
+
userHome: deps.userHome,
|
|
131
|
+
env,
|
|
132
|
+
projectDir: options.cwd !== undefined && options.cwd !== '' ? options.cwd : process.cwd(),
|
|
133
|
+
});
|
|
134
|
+
const detection = await source.detect(ctx);
|
|
135
|
+
const result = await source.build(ctx);
|
|
136
|
+
printImportSummary(source.id, detection, result, io);
|
|
137
|
+
// 一个分区都产不出来时**必须失败**:空的 bundle 会自检通过,用户却拿到什么都没有的包
|
|
138
|
+
//(与 backup「没有可打包的内容」同口径)。读盘层的 skipped 已经在上面如实打印了原因。
|
|
139
|
+
if (result.sections.length === 0) {
|
|
140
|
+
io.error('没有可导入的内容 / nothing to import(' + source.id + ':本机没有可转换的配置)');
|
|
141
|
+
return 1;
|
|
142
|
+
}
|
|
143
|
+
if (options.dryRun) {
|
|
144
|
+
io.log('(dry-run:零写入,未生成任何 ZIP)/ dry-run: nothing written');
|
|
145
|
+
return 0;
|
|
146
|
+
}
|
|
147
|
+
const outPath = await resolveOutPath(options, source.id);
|
|
148
|
+
try {
|
|
149
|
+
// 真正落盘前才创建目录(dry-run 已提前返回,不产生任何写入)
|
|
150
|
+
await fsp.mkdir(path.dirname(outPath), { recursive: true });
|
|
151
|
+
await writeForeignBundle({
|
|
152
|
+
result,
|
|
153
|
+
outPath,
|
|
154
|
+
exporterVersion: options.exporterVersion ?? '0.0.0-unknown',
|
|
155
|
+
// 离线 CLI 探测不到真实 DSH 版本:如实标注(与 backup 同口径,绝不谎报)
|
|
156
|
+
dshVersion: options.dshVersion ?? 'cli-offline',
|
|
157
|
+
platform: process.platform,
|
|
158
|
+
arch: process.arch,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
io.error('生成 bundle 失败 / failed to write bundle: ' + (error instanceof Error ? error.message : String(error)));
|
|
163
|
+
return 1;
|
|
164
|
+
}
|
|
165
|
+
io.log('已生成 bundle / bundle written: ' + outPath);
|
|
166
|
+
// 落盘后立即自检(与 verify 同一引擎):只有自检通过才返回 0
|
|
167
|
+
const check = await verifyBackupZip(outPath);
|
|
168
|
+
io.log(`[${check.verdict}] ${path.basename(outPath)}`);
|
|
169
|
+
for (const e of check.errors)
|
|
170
|
+
io.error(' ! ' + e);
|
|
171
|
+
if (check.verdict !== 'OK') {
|
|
172
|
+
io.error('自检未通过(文件已保留,请复核)/ self-verification failed(file kept)');
|
|
173
|
+
return 1;
|
|
174
|
+
}
|
|
175
|
+
io.log('自检通过 / self-verification OK');
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=import-source.js.map
|
package/lib/cli/index.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { type SnapshotMeta } from '../core/restore.ts';
|
|
3
3
|
import { type ReinstallItemId } from '../core/reinstall.ts';
|
|
4
|
-
export type CliCommand = 'snapshots' | 'restore' | 'reinstall' | 'recover-stale-lock' | 'verify' | 'backup' | 'sessions' | 'web' | 'help';
|
|
4
|
+
export type CliCommand = 'snapshots' | 'restore' | 'reinstall' | 'recover-stale-lock' | 'verify' | 'backup' | 'import' | 'sessions' | 'web' | 'help';
|
|
5
5
|
export interface CliOptions {
|
|
6
6
|
command: CliCommand;
|
|
7
|
+
/**
|
|
8
|
+
* 详情页主题(`dcm help <命令>` / `dcm <命令> --help`)。
|
|
9
|
+
* 只在 command === 'help' 时有意义;认不认识由运行层判定(解析器不重复维护一份命令表)。
|
|
10
|
+
*/
|
|
11
|
+
helpTopic?: string;
|
|
7
12
|
/** --data-dir:快照数据目录(verify/backup 下为导出目录)覆盖 */
|
|
8
13
|
dataDir?: string;
|
|
9
14
|
/**
|
|
@@ -31,8 +36,12 @@ export interface CliOptions {
|
|
|
31
36
|
json: boolean;
|
|
32
37
|
/** --sections <a,b,c>:backup 要打包的离线分区白名单(缺省取默认分区) */
|
|
33
38
|
sections?: string;
|
|
34
|
-
/** --out <path>:backup 输出 ZIP 路径(缺省写入导出目录,自动去重不覆盖) */
|
|
39
|
+
/** --out <path>:backup / import 输出 ZIP 路径(缺省写入导出目录,自动去重不覆盖) */
|
|
35
40
|
out?: string;
|
|
41
|
+
/** --from <source>:import 的外部来源 id(30 个之一) */
|
|
42
|
+
from?: string;
|
|
43
|
+
/** --cwd <dir>:import 的项目目录(契约 §8.2 的项目级路径;缺省 = 进程 cwd) */
|
|
44
|
+
cwd?: string;
|
|
36
45
|
/** --home <dir>:sessions repair 要修复的 DSH home(缺省 $DSH_HOME) */
|
|
37
46
|
home?: string;
|
|
38
47
|
/** --fix:sessions repair 真的落盘(缺省只报告) */
|
|
@@ -107,6 +116,13 @@ export interface CliIo {
|
|
|
107
116
|
error: (s: string) => void;
|
|
108
117
|
}
|
|
109
118
|
export declare function printUsage(io?: CliIo): void;
|
|
119
|
+
/**
|
|
120
|
+
* 打印单命令详情页(`dcm <命令> --help` / `dcm help <命令>`)。
|
|
121
|
+
*
|
|
122
|
+
* 返回 false 表示不认识该命令名(调用方决定报错文案);成功时已写完输出。
|
|
123
|
+
* 文案与退出码都在 ./help.ts,本函数只做「渲染 → 输出」。
|
|
124
|
+
*/
|
|
125
|
+
export declare function printCommandUsage(name: string, io?: CliIo): boolean;
|
|
110
126
|
export interface CliDeps {
|
|
111
127
|
/** 交互提问(缺省用真实 stdin/stdout readline) */
|
|
112
128
|
ask?: (prompt: string) => Promise<string>;
|
package/lib/cli/index.js
CHANGED
|
@@ -4,19 +4,11 @@
|
|
|
4
4
|
* 离线快照列表/恢复 + 一键重装 DSH 命令,零 DSH 运行时依赖(仅 node 内置 + 本项目
|
|
5
5
|
* core 引擎;绝不 import @deepseek-ai/*,peerDependencies 缺失也能跑)。
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* 一键重装 DSH 程序(交互多选 + 二次确认 / 跨平台执行)
|
|
13
|
-
* verify [--id <file|path>] [--json] [--data-dir <dir>]
|
|
14
|
-
* 离线只读自检备份 ZIP(结构 + integrity/checksums 完整性;不改一个字节)
|
|
15
|
-
* backup [--sections <a,b,c>] [--out <path>] [--dry-run] [--data-dir <dir>]
|
|
16
|
-
* 离线文件级备份(导出目录内生成与 GUI 同结构的 ZIP,落盘后自检)
|
|
17
|
-
* web [--port <n>] [--no-open] [--home <dir>] [--data-root <dir>] [--idle-timeout <分钟>]
|
|
18
|
-
* 离线只读救急台(本机网页:实例/SafeMode/锁/快照/备份自检/磁盘/会话体检)
|
|
19
|
-
* help | --help | -h 显示全部命令与说明
|
|
7
|
+
* 子命令一览、选项、退出码的**唯一事实源是 ./help.ts**(速查页 + 每命令详情页):
|
|
8
|
+
* 本文件只负责解析参数与执行,不再散写文案。
|
|
9
|
+
* dcm help 速查页(按风险分组的命令表 + 通用选项)
|
|
10
|
+
* dcm <命令> --help 单命令详情页(说明 / 选项 / 退出码 / 示例)
|
|
11
|
+
* dcm help <命令> 同上
|
|
20
12
|
*
|
|
21
13
|
* 缺省数据目录 = $DSH_HOME/dsh-config-manager/snapshots($DSH_HOME 缺省 ~/.dsh)。
|
|
22
14
|
* 破坏性命令(restore / reinstall)的 SAFE MODE 门按「控制面根」定位标记:
|
|
@@ -36,10 +28,12 @@ import { REINSTALL_ITEMS, buildReinstallPlan, isWindows, detectInstalledDshVersi
|
|
|
36
28
|
import { EnvironmentLockManager, runWithMutationLock, EnvironmentLockUnavailableError, OWNERSHIP_FILE } from '../utils/env-lock.js';
|
|
37
29
|
import { runSessionsRepair } from './sessions-repair.js';
|
|
38
30
|
import { runSessionsInspect } from './sessions-inspect.js';
|
|
31
|
+
import { runImportSource } from './import-source.js';
|
|
32
|
+
import { renderCommandHelp, renderOverview } from './help.js';
|
|
39
33
|
import { collectVerifyResults, readRunningInstances } from './actions.js';
|
|
40
34
|
import { Phase3Recovery, readSafeModeMarkerSync, safeModeMarkerPath } from '../core/phase3-host.js';
|
|
41
35
|
import { verifyBackupZip, } from '../core/backup-verify.js';
|
|
42
|
-
import { collectBackupEntries, buildSectionFlags, parseSectionsArg,
|
|
36
|
+
import { collectBackupEntries, buildSectionFlags, parseSectionsArg, OFFLINE_UNAVAILABLE_SECTIONS, } from '../core/backup-plan.js';
|
|
43
37
|
import { buildChecksums } from '../utils/hashing.js';
|
|
44
38
|
import { stringifyJsonSafe } from '../utils/json.js';
|
|
45
39
|
import { buildManifest, CHECKSUMS_FILE, MANIFEST_FILE } from '../schema/manifest.js';
|
|
@@ -63,6 +57,8 @@ const BACKUP_REJECTED_FLAGS = new Set(['--id', '--profile', '--settings']);
|
|
|
63
57
|
const RESTORE_ONLY_FLAGS = new Set(['--id', '--dry-run', '--profile', '--settings']);
|
|
64
58
|
/** 仅 reinstall 子命令允许的参数 */
|
|
65
59
|
const REINSTALL_ONLY_FLAGS = new Set(['--yes', '--list', '--wipe-config', '--version']);
|
|
60
|
+
/** 仅 import 允许的参数(--out/--dry-run 与 backup/restore 共用,故不在此表) */
|
|
61
|
+
const IMPORT_ONLY_FLAGS = new Set(['--from', '--cwd']);
|
|
66
62
|
/** 仅 web(离线救急台)允许的参数 */
|
|
67
63
|
const WEB_ONLY_FLAGS = new Set(['--port', '--no-open', '--idle-timeout']);
|
|
68
64
|
/** web 明确不支持的参数:宁可报错,也不悄悄忽略(它们属于读写命令,不属于只读台) */
|
|
@@ -75,6 +71,9 @@ function parseCliDataDir(argv) {
|
|
|
75
71
|
const options = { command: 'recover-stale-lock', dryRun: false, profile: 'web', yes: false, list: false, wipeConfig: false, json: false, positionals: [] };
|
|
76
72
|
for (let i = 1; i < argv.length; i += 1) {
|
|
77
73
|
const flag = argv[i];
|
|
74
|
+
if (flag === '--help' || flag === '-h') {
|
|
75
|
+
return { ok: true, options: { ...options, command: 'help', helpTopic: 'recover-stale-lock' } };
|
|
76
|
+
}
|
|
78
77
|
if (flag === '--data-dir') {
|
|
79
78
|
const value = argv[i + 1];
|
|
80
79
|
if (value === undefined || value === '' || value.startsWith('-')) {
|
|
@@ -113,6 +112,9 @@ function parseCliSessions(argv) {
|
|
|
113
112
|
const action = rest[0];
|
|
114
113
|
if (action === undefined)
|
|
115
114
|
return { ok: false, error: 'sessions 需要子动作 / missing action: sessions repair' };
|
|
115
|
+
if (action === '--help' || action === '-h') {
|
|
116
|
+
return { ok: true, options: { ...options, command: 'help', helpTopic: 'sessions' } };
|
|
117
|
+
}
|
|
116
118
|
// T6:三个只读/安全动作 —— list(列出会话)/ doctor(体检)/ repair(预览或 --fix 执行)
|
|
117
119
|
if (action !== 'repair' && action !== 'list' && action !== 'doctor') {
|
|
118
120
|
return { ok: false, error: 'sessions 只支持 list / doctor / repair / unsupported action: ' + action };
|
|
@@ -120,6 +122,9 @@ function parseCliSessions(argv) {
|
|
|
120
122
|
options.positionals.push(action);
|
|
121
123
|
for (let i = 1; i < rest.length; i += 1) {
|
|
122
124
|
const flag = rest[i];
|
|
125
|
+
if (flag === '--help' || flag === '-h') {
|
|
126
|
+
return { ok: true, options: { ...options, command: 'help', helpTopic: 'sessions' } };
|
|
127
|
+
}
|
|
123
128
|
if (flag === '--fix') {
|
|
124
129
|
options.fix = true;
|
|
125
130
|
continue;
|
|
@@ -164,20 +169,77 @@ function parseCliSessions(argv) {
|
|
|
164
169
|
}
|
|
165
170
|
return { ok: true, options };
|
|
166
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* import 专用解析:`import --from <source> [--dry-run] [--out <path>] [--cwd <dir>] [--data-dir <dir>]`
|
|
174
|
+
*
|
|
175
|
+
* 与 sessions / recover-stale-lock 同样的姿态:只接受本动作真正有意义的参数(绝不悄悄忽略)。
|
|
176
|
+
* `--from` 的值在这里**不做词表校验**:未知来源要由运行层给出「可用来源清单」——
|
|
177
|
+
* 那需要真实注册表(CLI 与宿主共用同一份装配,见 src/foreign/registry.ts)。
|
|
178
|
+
*/
|
|
179
|
+
function parseCliImport(argv) {
|
|
180
|
+
const options = {
|
|
181
|
+
command: 'import', dryRun: false, profile: 'web', yes: false, list: false,
|
|
182
|
+
wipeConfig: false, json: false, positionals: [],
|
|
183
|
+
};
|
|
184
|
+
const rest = argv.slice(1);
|
|
185
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
186
|
+
const flag = rest[i];
|
|
187
|
+
if (flag === '--help' || flag === '-h')
|
|
188
|
+
return { ok: true, options: { ...options, command: 'help', helpTopic: 'import' } };
|
|
189
|
+
if (flag === '--dry-run') {
|
|
190
|
+
options.dryRun = true;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const at = flag.indexOf('=');
|
|
194
|
+
const name = at >= 0 ? flag.slice(0, at) : flag;
|
|
195
|
+
const isValueFlag = name === '--from' || name === '--cwd' || name === '--out' || name === '--data-dir';
|
|
196
|
+
if (isValueFlag) {
|
|
197
|
+
const value = at >= 0 ? flag.slice(at + 1) : rest[i + 1];
|
|
198
|
+
if (value === undefined || value === '' || value.startsWith('-')) {
|
|
199
|
+
return { ok: false, error: '参数 ' + name + ' 缺少值 / missing value for ' + name };
|
|
200
|
+
}
|
|
201
|
+
if (name === '--from')
|
|
202
|
+
options.from = value;
|
|
203
|
+
else if (name === '--cwd')
|
|
204
|
+
options.cwd = value;
|
|
205
|
+
else if (name === '--out')
|
|
206
|
+
options.out = value;
|
|
207
|
+
else
|
|
208
|
+
options.dataDir = value;
|
|
209
|
+
if (at < 0)
|
|
210
|
+
i += 1;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
return { ok: false, error: '未知参数 / unknown flag: ' + flag };
|
|
214
|
+
}
|
|
215
|
+
if (options.from === undefined || options.from === '') {
|
|
216
|
+
return { ok: false, error: 'import 需要 --from <来源> / import requires --from <source>' };
|
|
217
|
+
}
|
|
218
|
+
return { ok: true, options };
|
|
219
|
+
}
|
|
167
220
|
/** 解析 CLI 参数(纯函数;help 返回 command:'help',未知/缺值返回错误) */
|
|
168
221
|
export function parseCli(argv) {
|
|
169
222
|
const command = argv[0];
|
|
170
223
|
if (command === undefined)
|
|
171
224
|
return { ok: false, error: '缺少子命令 / missing subcommand' };
|
|
172
225
|
if (command === '--help' || command === '-h' || command === 'help') {
|
|
173
|
-
|
|
226
|
+
const base = { command: 'help', dryRun: false, profile: 'web', yes: false, list: false, wipeConfig: false, json: false, positionals: [] };
|
|
227
|
+
// `dcm help <命令>`:把主题带下去(认不认识由运行层判定,解析器不重复维护一份命令表)
|
|
228
|
+
const topic = argv[1];
|
|
229
|
+
if (command === 'help' && topic !== undefined && topic !== '' && !topic.startsWith('-')) {
|
|
230
|
+
return { ok: true, options: { ...base, helpTopic: topic } };
|
|
231
|
+
}
|
|
232
|
+
return { ok: true, options: base };
|
|
174
233
|
}
|
|
175
234
|
if (command !== 'snapshots' && command !== 'restore' && command !== 'reinstall' && command !== 'sessions'
|
|
176
|
-
&& command !== 'recover-stale-lock' && command !== 'verify' && command !== 'backup' && command !== 'web'
|
|
235
|
+
&& command !== 'recover-stale-lock' && command !== 'verify' && command !== 'backup' && command !== 'web'
|
|
236
|
+
&& command !== 'import') {
|
|
177
237
|
return { ok: false, error: `未知子命令 / unknown subcommand: ${command}` };
|
|
178
238
|
}
|
|
179
239
|
if (command === 'sessions')
|
|
180
240
|
return parseCliSessions(argv);
|
|
241
|
+
if (command === 'import')
|
|
242
|
+
return parseCliImport(argv);
|
|
181
243
|
if (command === 'recover-stale-lock') {
|
|
182
244
|
// recover-stale-lock:独立显式 recovery,不接受 destructive 执行参数(只能 --data-dir 定位锁目录)。
|
|
183
245
|
// 只需 parseCliDataDir —— 它自己就会拒绝任何未知 flag。此前这里还有一道「逐 token 只许 --data-dir」的
|
|
@@ -190,7 +252,7 @@ export function parseCli(argv) {
|
|
|
190
252
|
for (let i = 0; i < rest.length; i += 1) {
|
|
191
253
|
const flag = rest[i];
|
|
192
254
|
if (flag === '--help' || flag === '-h') {
|
|
193
|
-
return { ok: true, options: { ...options, command: 'help' } };
|
|
255
|
+
return { ok: true, options: { ...options, command: 'help', helpTopic: command } };
|
|
194
256
|
}
|
|
195
257
|
// 位置参数:仅 verify 接受(文件名或路径),其余子命令沿用「未知参数」拒绝
|
|
196
258
|
if (!flag.startsWith('-')) {
|
|
@@ -206,6 +268,9 @@ export function parseCli(argv) {
|
|
|
206
268
|
if (command !== 'verify' && VERIFY_ONLY_FLAGS.has(flag)) {
|
|
207
269
|
return { ok: false, error: `${command} 子命令不支持参数 / flag not allowed here: ${flag}` };
|
|
208
270
|
}
|
|
271
|
+
if (IMPORT_ONLY_FLAGS.has(flag)) {
|
|
272
|
+
return { ok: false, error: `${command} 子命令不支持参数 / flag not allowed here: ${flag}` };
|
|
273
|
+
}
|
|
209
274
|
if (command !== 'backup' && BACKUP_ONLY_FLAGS.has(flag)) {
|
|
210
275
|
return { ok: false, error: `${command} 子命令不支持参数 / flag not allowed here: ${flag}` };
|
|
211
276
|
}
|
|
@@ -443,78 +508,20 @@ function resolveCliVersion() {
|
|
|
443
508
|
/** 进程内缓存(每次 CLI 运行只读一次 package.json) */
|
|
444
509
|
const CLI_EXPORTER_VERSION = resolveCliVersion();
|
|
445
510
|
export function printUsage(io = defaultIo) {
|
|
446
|
-
io.log(
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
' / recover a stale environment lock left by a killed process',
|
|
461
|
-
' 仅当持有者已确证死亡才回收;活锁一律拒绝。GUI 操作报「操作暂时无法执行」',
|
|
462
|
-
' 且重试无效时使用(另有 GUI「事故恢复」入口)。',
|
|
463
|
-
' Refuses unless the owner is proven dead; never touches a live lock.',
|
|
464
|
-
' dsh-config-manager verify [--id <file|path>] [--json] [--data-dir <dir>]',
|
|
465
|
-
' 离线只读自检备份 ZIP(结构 + integrity/checksums 完整性;不改一个字节)',
|
|
466
|
-
' / read-only integrity check of backup ZIPs(零写入)',
|
|
467
|
-
' 无参校验导出目录下全部 *.zip;也可用位置参数指定单个文件或路径。',
|
|
468
|
-
' 退出码:全部 OK → 0,任一非 OK → 1(便于 CI / 定时任务断言)。',
|
|
469
|
-
' Exit code: 0 only when every checked backup is OK.',
|
|
470
|
-
' dsh-config-manager backup [--sections <a,b,c>] [--out <path>] [--dry-run]',
|
|
471
|
-
' [--data-dir <dir>]',
|
|
472
|
-
' 离线文件级备份(导出目录内生成与 GUI 同结构的 ZIP,落盘后自动自检)',
|
|
473
|
-
' / offline file-level backup(dropped ZIP is self-verified)',
|
|
474
|
-
' 只打包离线可直读的分区;凭据类文件(凭据文件名 / .env / *.pem)永不进入备份。',
|
|
475
|
-
' dsh-config-manager web [--port <n>] [--no-open] [--home <dir>] [--data-root <dir>]',
|
|
476
|
-
' [--idle-timeout <分钟>]',
|
|
477
|
-
' 启动离线只读救急台(本机网页):实例心跳 / SAFE MODE / 残留锁 / 快照 / 备份产物',
|
|
478
|
-
' (可一键自检)/ 磁盘占用 / 会话体检,全部只读 / offline read-only rescue console',
|
|
479
|
-
' 只绑 127.0.0.1,端口缺省 0(内核分配);启动后打印带一次性 token 的 URL 并打开浏览器。',
|
|
480
|
-
' Ctrl+C 或空闲超时即退出;等价简写:dcm web',
|
|
481
|
-
' dsh-config-manager sessions list [--home <dir>] [--json] # 只读:列出本机会话',
|
|
482
|
-
' dsh-config-manager sessions doctor [--home <dir>] [--json] # 只读:体检并给处置建议',
|
|
483
|
-
' dsh-config-manager sessions repair [--home <dir>] [--apply] [--keep <dir>] [--map old=new]...',
|
|
484
|
-
' 离线修复会话日志布局(DSH 已起不来时的唯一通道)/ offline session layout repair',
|
|
485
|
-
' 按每条会话 header 的 cwd 把目录归位到 projectKeyOf(cwd);缺省只报告,--fix 才落盘。',
|
|
486
|
-
' --map 用于跨机恢复:old=new 前缀映射会先改写会话日志第 1 帧 header(其余帧逐字节保留)。',
|
|
487
|
-
' 重复 id(同一会话出现在多个 projectKey 目录)只在 --keep <目录> 点名保留谁时,',
|
|
488
|
-
' 才把其它副本移进 sessions/.cm-repair-quarantine-<时间戳>/(只搬不删)。',
|
|
489
|
-
' Exit code: dry-run 恒 0;--fix 有失败/冲突/回滚 → 1。',
|
|
490
|
-
' dsh-config-manager help',
|
|
491
|
-
' 显示全部命令与说明 / show all commands',
|
|
492
|
-
'',
|
|
493
|
-
'选项 / Options:',
|
|
494
|
-
' --data-dir <dir> 快照数据目录(缺省 $DSH_HOME/dsh-config-manager/snapshots;verify/backup',
|
|
495
|
-
' 下为导出目录;recover-stale-lock 用它定位环境锁所在的数据目录)',
|
|
496
|
-
' --data-root <dir> 插件数据根 dataDir(宿主 config.dataDir;SAFE MODE / 环境锁 / journal 所在根)',
|
|
497
|
-
' 缺省 $DSH_HOME/dsh-config-manager。插件配置了自定义 dataDir 时,破坏性命令',
|
|
498
|
-
' (restore / reinstall)必须显式传它,否则安全门只按候选位置保守检查。',
|
|
499
|
-
' --id <uuid> 目标快照 id(缺省取最近一个非 rolled-back 快照)',
|
|
500
|
-
' --dry-run 只打印计划,不执行 / print plan only',
|
|
501
|
-
' --profile <name> 管理的 DSH profile(缺省 web)',
|
|
502
|
-
' --settings <path> 覆盖 settings 文件路径',
|
|
503
|
-
' --version <v> 要安装的 DSH 版本(缺省 latest)',
|
|
504
|
-
' --yes 非交互:全选并跳过二次确认 / non-interactive',
|
|
505
|
-
' --list 只列出 reinstall 可选清理项 / list selectable items only',
|
|
506
|
-
' --wipe-config 一并勾选数据类(settings/plugins/data)/ also wipe ~/.dsh data',
|
|
507
|
-
' --json verify 输出机器可读 JSON / machine-readable output',
|
|
508
|
-
' --sections <list> backup 分区白名单(逗号分隔;缺省 ' + DEFAULT_BACKUP_SECTIONS.join(',') + ')',
|
|
509
|
-
' --out <path> backup 输出 ZIP 路径(缺省自动命名,绝不覆盖既有文件)',
|
|
510
|
-
' --home <dir> sessions repair / web 的 DSH home(缺省 $DSH_HOME,即 ~/.dsh)',
|
|
511
|
-
' --port <n> web:监听端口(缺省 0 = 内核随机分配;恒只绑 127.0.0.1)',
|
|
512
|
-
' --no-open web:启动后不自动打开浏览器 / do not open the browser',
|
|
513
|
-
' --idle-timeout <m> web:空闲多少分钟自动退出(0 = 不自动退出;缺省 30)',
|
|
514
|
-
' --fix / --apply sessions repair 真的落盘(缺省只打印计划;写前会检查 DSH 是否在跑)',
|
|
515
|
-
' --keep <dir> 重复 id 时保留哪一份会话目录(其余移入隔离目录,不删除)',
|
|
516
|
-
' --map old=new 路径前缀映射(可重复;命中即改写会话首帧 cwd)',
|
|
517
|
-
].join('\n'));
|
|
511
|
+
io.log(renderOverview(CLI_EXPORTER_VERSION).join('\n'));
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* 打印单命令详情页(`dcm <命令> --help` / `dcm help <命令>`)。
|
|
515
|
+
*
|
|
516
|
+
* 返回 false 表示不认识该命令名(调用方决定报错文案);成功时已写完输出。
|
|
517
|
+
* 文案与退出码都在 ./help.ts,本函数只做「渲染 → 输出」。
|
|
518
|
+
*/
|
|
519
|
+
export function printCommandUsage(name, io = defaultIo) {
|
|
520
|
+
const lines = renderCommandHelp(name, CLI_EXPORTER_VERSION);
|
|
521
|
+
if (lines === null)
|
|
522
|
+
return false;
|
|
523
|
+
io.log(lines.join('\n'));
|
|
524
|
+
return true;
|
|
518
525
|
}
|
|
519
526
|
function printSnapshots(metas, io) {
|
|
520
527
|
if (metas.length === 0) {
|
|
@@ -790,7 +797,17 @@ export async function runCli(argv, io = defaultIo, env = process.env, deps = {})
|
|
|
790
797
|
}
|
|
791
798
|
const { options } = parsed;
|
|
792
799
|
if (options.command === 'help') {
|
|
793
|
-
|
|
800
|
+
const topic = options.helpTopic;
|
|
801
|
+
if (topic === undefined) {
|
|
802
|
+
printUsage(io);
|
|
803
|
+
return 0;
|
|
804
|
+
}
|
|
805
|
+
// 认不出的主题不静默回落:先说出来,再给速查页(否则用户以为拼错的名字是有效的)
|
|
806
|
+
if (!printCommandUsage(topic, io)) {
|
|
807
|
+
io.error('未知命令 / unknown command: ' + topic);
|
|
808
|
+
printUsage(io);
|
|
809
|
+
return 1;
|
|
810
|
+
}
|
|
794
811
|
return 0;
|
|
795
812
|
}
|
|
796
813
|
if (options.command === 'reinstall') {
|
|
@@ -807,6 +824,17 @@ export async function runCli(argv, io = defaultIo, env = process.env, deps = {})
|
|
|
807
824
|
if (options.command === 'backup') {
|
|
808
825
|
return runBackup(options, io, env);
|
|
809
826
|
}
|
|
827
|
+
// import:把外部 agent 配置翻译成标准 bundle(dry-run 零写入;只可能写导出目录,不碰 $DSH_HOME 配置)
|
|
828
|
+
if (options.command === 'import') {
|
|
829
|
+
return runImportSource({
|
|
830
|
+
from: options.from ?? '',
|
|
831
|
+
dryRun: options.dryRun,
|
|
832
|
+
exportsDir: resolveExportsDir(options.dataDir, env),
|
|
833
|
+
exporterVersion: CLI_EXPORTER_VERSION,
|
|
834
|
+
...(options.out !== undefined ? { out: options.out } : {}),
|
|
835
|
+
...(options.cwd !== undefined ? { cwd: options.cwd } : {}),
|
|
836
|
+
}, io, env);
|
|
837
|
+
}
|
|
810
838
|
if (options.command === 'sessions') {
|
|
811
839
|
// 离线修复:不碰 $DSH_HOME 的其它部分、不需要环境锁(只读写会话目录,且默认 dry-run)
|
|
812
840
|
const home = options.home ?? resolveDshHome(env);
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
/** 客户端脚本原文(page.ts 内联进 <script>;不得含字面量 </script 或 <!--)。 */
|
|
2
2
|
export declare const CONSOLE_SCRIPT = "(function () {\n 'use strict';\n\n // ---- \u4E09\u6001\u4E3B\u9898\uFF1A\u81EA\u52A8 / \u6D45\u8272 / \u6DF1\u8272 ----\n // \u4E3A\u4EC0\u4E48\u653E\u5728\u6700\u524D\u9762\uFF1A\u5B83\u8981\u5728**\u9996\u5E27\u4E4B\u524D**\u628A data-theme \u843D\u5230 <html> \u4E0A\uFF0C\u5426\u5219\u5F3A\u5236\u4E3B\u9898\u4F1A\u5148\u95EA\u4E00\u4E0B\u3002\n // \u4E3A\u4EC0\u4E48\u7528 cookie \u800C\u4E0D\u662F storage\uFF1Acookie \u4E0D\u533A\u5206\u7AEF\u53E3\uFF0C\u800C\u6551\u6025\u53F0\u6BCF\u6B21\u542F\u52A8\u6362\u968F\u673A\u7AEF\u53E3 \u2014\u2014 \u53EA\u6709 cookie \u80FD\u8DE8\u6B21\u8BB0\u4F4F\u3002\n var THEME_KEY = 'dcm-theme';\n var THEME_MODES = ['auto', 'light', 'dark'];\n function readTheme() {\n var parts = ('; ' + document.cookie).split('; ' + THEME_KEY + '=');\n if (parts.length < 2) return 'auto';\n var value = String(parts[1]).split(';')[0];\n return THEME_MODES.indexOf(value) === -1 ? 'auto' : value;\n }\n function applyTheme(mode, persist) {\n var next = THEME_MODES.indexOf(mode) === -1 ? 'auto' : mode;\n document.documentElement.setAttribute('data-theme', next);\n var list = document.querySelectorAll('[data-dcm-theme]');\n for (var i = 0; i < list.length; i++) {\n list[i].setAttribute('aria-pressed', list[i].getAttribute('data-dcm-theme') === next ? 'true' : 'false');\n }\n if (persist === true) {\n // \u975E\u654F\u611F\u504F\u597D\uFF08\u4E0D\u542B\u4EFB\u4F55\u5BC6\u94A5/\u8DEF\u5F84\uFF09\uFF1Bcookie \u88AB\u7981\u7528\u65F6\u9759\u9ED8\u964D\u7EA7\u4E3A\u300C\u672C\u6B21\u6709\u6548\u300D\n document.cookie = THEME_KEY + '=' + next + '; path=/; max-age=31536000; SameSite=Lax';\n }\n }\n // \u6709\u811A\u672C = \u5F00\u5173\u53EF\u7528\uFF1B\u65E0\u811A\u672C\u65F6\u5B83\u88AB CSS \u9690\u85CF\uFF0C\u9875\u9762\u56DE\u843D\u5230 prefers-color-scheme \u7684\u81EA\u52A8\u6A21\u5F0F\n document.documentElement.setAttribute('data-dcm-js', '1');\n applyTheme(readTheme(), false);\n document.addEventListener('click', function (event) {\n var node = event.target;\n while (node !== null && node !== document) {\n if (typeof node.hasAttribute === 'function' && node.hasAttribute('data-dcm-theme')) {\n applyTheme(String(node.getAttribute('data-dcm-theme')), true);\n return;\n }\n node = node.parentNode;\n }\n });\n // \u79BB\u7EBF\u6551\u6025\u53F0\u7684**\u552F\u4E00**\u5BA2\u6237\u7AEF\u811A\u672C\uFF1A\u53EA\u505A\u4E24\u4EF6\u4E8B \u2014\u2014 \u2460 \u4F1A\u8BDD\u5185\u72B6\u6001\u6062\u590D\uFF08sessionStorage\uFF0Cdcm: \u524D\u7F00\uFF09\n // \u2461 \u628A\u786E\u8BA4\u8868\u5355\u4ECE details \u5347\u7EA7\u6210\u539F\u751F dialog \u5143\u7D20\u3002\u7981\u7528 JS \u65F6\u4E24\u8005\u90FD\u4E0D\u53C2\u4E0E\uFF0C\u9875\u9762\u56DE\u843D\u5230\u7EAF HTML\u3002\n var PREFIX = 'dcm:';\n // \u663E\u5F0F\u9ED1\u540D\u5355\uFF08\u4E0D\u662F\u300C\u9760\u6CA1\u5199\u8FDB\u53BB\u300D\uFF09\uFF1A\u8FD9\u4E9B type \u4E00\u5F8B\u4E0D\u843D storage\n var BLOCKED_TYPES = { password: 1, hidden: 1, file: 1, submit: 1, button: 1, reset: 1, image: 1 };\n // \u663E\u5F0F\u9ED1\u540D\u5355\uFF08\u6309\u5B57\u6BB5\u540D\uFF09\uFF1Atoken / \u5BC6\u7801 / \u673A\u5BC6 / \u51ED\u636E / \u91CD\u88C5\u786E\u8BA4\u7801 / cookie \u4E00\u5F8B\u4E0D\u843D storage\n var BLOCKED_NAMES = /(token|password|passwd|secret|credential|phrase|cookie|apikey|authorization)/i;\n var store = null;\n try {\n store = window.sessionStorage;\n store.setItem(PREFIX + 'probe', '1');\n store.removeItem(PREFIX + 'probe');\n } catch (error) {\n store = null;\n }\n function readJson(key) {\n if (store === null) return null;\n try {\n var raw = store.getItem(key);\n return raw === null ? null : JSON.parse(raw);\n } catch (error) {\n return null;\n }\n }\n function writeJson(key, value) {\n if (store === null) return;\n try {\n store.setItem(key, JSON.stringify(value));\n } catch (error) {\n // \u914D\u989D\u5DF2\u6EE1 / \u9690\u79C1\u6A21\u5F0F\uFF1A\u9759\u9ED8\u964D\u7EA7\uFF0C\u9875\u9762\u7167\u5E38\u53EF\u7528\n }\n }\n function pageKey() {\n return location.pathname + location.search;\n }\n function persistable(el) {\n if (!el || typeof el.name !== 'string' || el.name === '') return false;\n var type = (el.type || '').toLowerCase();\n if (BLOCKED_TYPES[type] === 1) return false;\n if (BLOCKED_NAMES.test(el.name)) return false;\n if (typeof el.id === 'string' && el.id !== '' && BLOCKED_NAMES.test(el.id)) return false;\n return true;\n }\n function fieldKey(el) {\n var scope = el.form ? String(el.form.getAttribute('action') || '') : '';\n return PREFIX + 'field:' + pageKey() + ':' + scope + ':' + el.name + (el.type === 'radio' ? '=' + String(el.value) : '');\n }\n function restoreFields() {\n var list = document.querySelectorAll('input, select, textarea');\n for (var i = 0; i < list.length; i++) {\n var el = list[i];\n if (!persistable(el)) continue;\n var saved = readJson(fieldKey(el));\n if (saved === null) continue;\n if (el.type === 'checkbox' || el.type === 'radio') {\n if (typeof saved.checked === 'boolean') el.checked = saved.checked;\n } else if (typeof saved.value === 'string') {\n el.value = saved.value;\n }\n }\n }\n function onField(event) {\n var el = event.target;\n if (!persistable(el)) return;\n if (el.type === 'checkbox' || el.type === 'radio') writeJson(fieldKey(el), { checked: el.checked === true });\n else writeJson(fieldKey(el), { value: String(el.value) });\n }\n document.addEventListener('change', onField, true);\n document.addEventListener('input', onField, true);\n function detailsState() {\n var list = document.querySelectorAll('details[data-dcm-persist]');\n for (var i = 0; i < list.length; i++) {\n var details = list[i];\n var id = details.getAttribute('data-dcm-persist') || String(i);\n var saved = readJson(PREFIX + 'open:' + pageKey() + ':' + id);\n if (typeof saved === 'boolean') details.open = saved;\n }\n }\n document.addEventListener('toggle', function (event) {\n var details = event.target;\n if (!details || details.tagName !== 'DETAILS') return;\n var id = details.getAttribute('data-dcm-persist');\n if (id === null) return;\n writeJson(PREFIX + 'open:' + pageKey() + ':' + id, details.open === true);\n }, true);\n function scrollKey() {\n return PREFIX + 'scroll:' + pageKey();\n }\n var navType = '';\n try {\n var entries = performance.getEntriesByType('navigation');\n if (entries.length > 0) navType = String(entries[0].type || '');\n } catch (error) {\n navType = '';\n }\n function currentScroll() {\n return window.pageYOffset || document.documentElement.scrollTop || 0;\n }\n var scrollTimer = null;\n function saveScroll() {\n writeJson(scrollKey(), { y: Math.round(currentScroll()), t: Date.now() });\n }\n window.addEventListener('scroll', function () {\n if (scrollTimer !== null) return;\n scrollTimer = window.setTimeout(function () {\n scrollTimer = null;\n saveScroll();\n }, 150);\n }, { passive: true });\n window.addEventListener('pagehide', saveScroll);\n document.addEventListener('visibilitychange', function () {\n if (document.visibilityState === 'hidden') saveScroll();\n });\n function restoreScroll() {\n var saved = readJson(scrollKey());\n if (saved === null || typeof saved.y !== 'number' || saved.y <= 0) return;\n window.scrollTo(0, saved.y);\n }\n // \u53EA\u5728\u300C\u5237\u65B0\u300D\u4E0E\u300C\u540E\u9000/\u524D\u8FDB\u300D\u6062\u590D\uFF1A\u666E\u901A\u65B0\u5BFC\u822A\uFF08type=navigate\uFF09\u4ECE\u9876\u90E8\u5F00\u59CB\n if (navType === '' || navType === 'reload' || navType === 'back_forward') {\n window.requestAnimationFrame(function () {\n window.requestAnimationFrame(restoreScroll);\n });\n }\n function enhanceOne(details) {\n var form = details.querySelector('form');\n if (form === null) return;\n var summary = details.querySelector('summary');\n var dialog = document.createElement('dialog');\n dialog.className = 'dcmDialog';\n var body = details.querySelector('.confirmBody');\n if (body !== null) {\n while (body.firstChild !== null) dialog.appendChild(body.firstChild);\n } else {\n dialog.appendChild(form);\n }\n var footer = document.createElement('div');\n footer.className = 'dialogFooter';\n var cancel = document.createElement('button');\n cancel.type = 'button';\n cancel.className = 'btnPlain';\n cancel.textContent = '\u53D6\u6D88';\n cancel.addEventListener('click', function () { dialog.close(); });\n footer.appendChild(cancel);\n dialog.appendChild(footer);\n dialog.addEventListener('click', function (event) {\n if (event.target === dialog) dialog.close();\n });\n document.body.appendChild(dialog);\n var trigger = document.createElement('button');\n trigger.type = 'button';\n trigger.setAttribute('data-dcm-trigger', '');\n trigger.className = summary === null ? 'btnPrimary' : summary.className;\n trigger.textContent = summary === null ? '\u786E\u8BA4\u6267\u884C' : String(summary.textContent || '\u786E\u8BA4\u6267\u884C');\n trigger.addEventListener('click', function () {\n if (dialog.open) dialog.close();\n else dialog.showModal();\n });\n if (details.parentNode !== null) details.parentNode.replaceChild(trigger, details);\n }\n function enhanceDialogs() {\n if (typeof document.createElement('dialog').showModal !== 'function') return;\n var list = document.querySelectorAll('details[data-dcm-confirm]');\n for (var i = 0; i < list.length; i++) enhanceOne(list[i]);\n }\n document.addEventListener('click', function (event) {\n var node = event.target;\n while (node !== null && node !== document) {\n if (typeof node.hasAttribute === 'function' && node.hasAttribute('data-dcm-back')) {\n if (window.history.length > 1) {\n event.preventDefault();\n window.history.back();\n }\n return;\n }\n node = node.parentNode;\n }\n });\n function boot() {\n applyTheme(readTheme(), false); // DOM \u5230\u4F4D\u540E\u628A\u5F00\u5173\u7684\u9009\u4E2D\u6001\u5BF9\u9F50\uFF08\u9996\u5E27\u90A3\u6761\u5DF2\u7ECF\u8BBE\u8FC7 data-theme\uFF09\n detailsState();\n enhanceDialogs();\n restoreFields();\n }\n if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot);\n else boot();\n})();";
|
|
3
|
+
/**
|
|
4
|
+
* 从本文件的**源码文本**里抽出 CONSOLE_SCRIPT 模板串(测试用;**唯一实现**)。
|
|
5
|
+
*
|
|
6
|
+
* 为什么要归一化换行(cli-F5):Windows 上 `core.autocrlf=true` 的检出会让闭引号后面是
|
|
7
|
+
* `\r\n`,只认 `\n` 的正则失配 → 测试在自己的检出上假红(R3-04)。抽取逻辑收在这里,
|
|
8
|
+
* web.test.ts 与 script-anchor.test.ts 共用,避免第二份口径再次漂移。
|
|
9
|
+
*/
|
|
10
|
+
export declare function extractConsoleScriptSource(source: string): string | undefined;
|
|
3
11
|
/** 该脚本的 CSP hash-source(含引号,可直接拼进 script-src)。 */
|
|
4
12
|
export declare function consoleScriptCspHash(): string;
|
|
@@ -237,6 +237,17 @@ export const CONSOLE_SCRIPT = `(function () {
|
|
|
237
237
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot);
|
|
238
238
|
else boot();
|
|
239
239
|
})();`;
|
|
240
|
+
/**
|
|
241
|
+
* 从本文件的**源码文本**里抽出 CONSOLE_SCRIPT 模板串(测试用;**唯一实现**)。
|
|
242
|
+
*
|
|
243
|
+
* 为什么要归一化换行(cli-F5):Windows 上 `core.autocrlf=true` 的检出会让闭引号后面是
|
|
244
|
+
* `\r\n`,只认 `\n` 的正则失配 → 测试在自己的检出上假红(R3-04)。抽取逻辑收在这里,
|
|
245
|
+
* web.test.ts 与 script-anchor.test.ts 共用,避免第二份口径再次漂移。
|
|
246
|
+
*/
|
|
247
|
+
export function extractConsoleScriptSource(source) {
|
|
248
|
+
const lf = source.split('\r\n').join('\n');
|
|
249
|
+
return /export const CONSOLE_SCRIPT = `([\s\S]*?)`\n/.exec(lf)?.[1];
|
|
250
|
+
}
|
|
240
251
|
const SCRIPT_HASH = "'sha256-" + createHash('sha256').update(CONSOLE_SCRIPT, 'utf8').digest('base64') + "'";
|
|
241
252
|
/** 该脚本的 CSP hash-source(含引号,可直接拼进 script-src)。 */
|
|
242
253
|
export function consoleScriptCspHash() {
|
package/lib/cli/web/http.d.ts
CHANGED
|
@@ -13,6 +13,11 @@
|
|
|
13
13
|
* 全部 403**(实测 Chrome 154:no-referrer → null;same-origin/strict-origin/默认策略 → 正常)。
|
|
14
14
|
* same-origin 的强度并不降低:跨源请求仍然一个字节的 Referer 都不带(第三方零泄漏),
|
|
15
15
|
* 而带一次性 token 的启动地址走 server.ts 的 302,那一步单独保持 `no-referrer`。
|
|
16
|
+
*
|
|
17
|
+
* `frame-ancestors 'none'` **必须显式写**(cli-F3):它**不被** `default-src` 覆盖 —— 只写
|
|
18
|
+
* `default-src 'none'` 时页面仍可被 iframe。SameSite=Strict 的判据是 **site**(忽略端口),
|
|
19
|
+
* 本机另一个 127.0.0.1:<其它端口> 的页面同 site,iframe 里 cookie 照送 → 可点劫写动作。
|
|
20
|
+
* 一并给 `X-Frame-Options: DENY`(老浏览器)。
|
|
16
21
|
*/
|
|
17
22
|
import type { ServerResponse } from 'node:http';
|
|
18
23
|
/** HTML 响应(content-type + no-store + referrer-policy + CSP 只在这一处维护)。 */
|
package/lib/cli/web/http.js
CHANGED
|
@@ -5,8 +5,9 @@ export function writeHtml(res, status, html) {
|
|
|
5
5
|
'content-type': 'text/html; charset=utf-8',
|
|
6
6
|
'referrer-policy': 'same-origin',
|
|
7
7
|
'cache-control': 'no-store',
|
|
8
|
+
'x-frame-options': 'DENY',
|
|
8
9
|
'content-security-policy': "default-src 'none'; style-src 'unsafe-inline'; img-src data:; "
|
|
9
|
-
+ "script-src " + consoleScriptCspHash() + "; base-uri 'none'; form-action 'self'",
|
|
10
|
+
+ "script-src " + consoleScriptCspHash() + "; base-uri 'none'; form-action 'self'; frame-ancestors 'none'",
|
|
10
11
|
});
|
|
11
12
|
res.end(html);
|
|
12
13
|
}
|