dsh-crate-web 0.1.1 → 0.1.2-preview.2

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.
Files changed (34) hide show
  1. package/README.md +11 -290
  2. package/README.zh-CN.md +2 -303
  3. package/THIRD-PARTY-LICENSE.txt +21 -0
  4. package/cordis.patch.yml +1 -6
  5. package/core/dsh_pack/_version.py +1 -0
  6. package/core/dsh_pack/bundle.py +74 -0
  7. package/core/dsh_pack/cli.py +81 -18
  8. package/core/dsh_pack/desktop_verify.py +304 -0
  9. package/core/dsh_pack/diagnostics.py +1170 -0
  10. package/core/dsh_pack/errors.py +16 -1
  11. package/core/dsh_pack/export.py +86 -103
  12. package/core/dsh_pack/export_plan.py +96 -0
  13. package/core/dsh_pack/identity.py +25 -0
  14. package/core/dsh_pack/importer.py +468 -217
  15. package/core/dsh_pack/network.py +5 -4
  16. package/core/dsh_pack/pack.py +14 -2
  17. package/core/dsh_pack/portability.py +392 -0
  18. package/core/dsh_pack/preflight.py +63 -16
  19. package/core/dsh_pack/troubleshooting.py +94 -0
  20. package/core/dsh_pack/user_cli.py +16 -0
  21. package/core/dsh_pack/validation.py +64 -4
  22. package/core/dsh_pack/verify.py +343 -194
  23. package/core/dsh_pack/windows_verify.py +177 -0
  24. package/desktop-patch-helper.mjs +140 -0
  25. package/lib/client.js +292 -102
  26. package/lib/index.d.ts +7 -1
  27. package/lib/index.js +1121 -229
  28. package/package.json +14 -7
  29. package/skills/dsh-crate-troubleshooting/SKILL.md +172 -0
  30. package/skills/dsh-crate-troubleshooting/compatibility.json +11 -0
  31. package/skills/dsh-crate-troubleshooting/reference/diagnostics.generated.md +96 -0
  32. package/skills/dsh-crate-troubleshooting/reference/repair-boundaries.md +60 -0
  33. package/skills/dsh-crate-troubleshooting/reference/stages.generated.md +22 -0
  34. package/verify-desktop.mjs +314 -0
package/lib/client.js CHANGED
@@ -43,17 +43,14 @@ window.__ModuleLoader__.load({
43
43
  groupToggle: "dsh-crate-group-toggle",
44
44
  groupBody: "dsh-crate-group-body",
45
45
  badge: "dsh-crate-badge",
46
- conflict: "dsh-crate-conflict",
47
- toneOk: "dsh-crate-tone-ok",
48
- toneBad: "dsh-crate-tone-bad",
49
- toneNeutral: "dsh-crate-tone-neutral"
46
+ conflict: "dsh-crate-conflict"
50
47
  };
51
48
  const STYLE = `
52
49
  .dsh-crate-section{display:flex;flex-direction:column;gap:12px;max-width:760px;color:var(--dsw-alias-label-primary)}
53
50
  .dsh-crate-heading{margin:0;font-size:18px;font-weight:600}.dsh-crate-intro,.dsh-crate-muted{margin:0;font-size:13px;color:var(--dsw-alias-label-tertiary)}
54
51
  .dsh-crate-tabs{display:flex;gap:20px;border-bottom:1px solid var(--dsw-alias-border-l2);overflow:auto}.dsh-crate-tab{border:0;padding:7px 1px 9px;background:transparent;color:var(--dsw-alias-label-tertiary);font:inherit;cursor:pointer;white-space:nowrap}
55
52
  .dsh-crate-tab[data-active=true]{color:var(--dsw-alias-label-primary);border-bottom:2px solid var(--dsw-alias-label-primary)}.dsh-crate-form{display:flex;flex-direction:column;gap:10px}
56
- .dsh-crate-label{display:flex;flex-direction:column;gap:5px;font-size:13px}.dsh-crate-select,.dsh-crate-file{max-width:100%;padding:7px 8px;border:1px solid var(--dsw-alias-border-l2,#3a3a40);border-radius:6px;background-color:var(--dsw-alias-fill-secondary,rgba(255,255,255,.08));color:var(--dsw-alias-label-primary,#f9fafb);color-scheme:dark}.dsh-crate-select option{background-color:#242428;color:#f9fafb}@media (prefers-color-scheme:light){.dsh-crate-select,.dsh-crate-file{background-color:var(--dsw-alias-fill-secondary,#fff);color:var(--dsw-alias-label-primary,#111827);color-scheme:light}.dsh-crate-select option{background-color:#fff;color:#111827}}
53
+ .dsh-crate-label{display:flex;flex-direction:column;gap:5px;font-size:13px}.dsh-crate-select,.dsh-crate-file{max-width:100%;min-width:0;padding:7px 8px;border:1px solid var(--dsw-alias-border-l2,#3a3a40);border-radius:6px;background-color:var(--dsw-alias-bg-layer-1,Canvas);color:var(--dsw-alias-label-primary,CanvasText);color-scheme:inherit}.dsh-crate-select option{background-color:var(--dsw-alias-bg-layer-1,Canvas);color:var(--dsw-alias-label-primary,CanvasText)}.dsh-crate-inventory-item{overflow-wrap:anywhere}.dsh-crate-inventory-item>span{min-width:0}
57
54
  .dsh-crate-plugin{display:grid;grid-template-columns:minmax(0,1fr) 150px;gap:8px;align-items:center}.dsh-crate-button{align-self:flex-start;border:0;border-radius:6px;padding:8px 13px;background:var(--dsw-alias-state-business-primary);color:white;cursor:pointer}.dsh-crate-button:disabled{cursor:default;opacity:.5}
58
55
  .dsh-crate-report,.dsh-crate-preview,.dsh-crate-diagnostic{display:flex;flex-direction:column;gap:8px;padding:10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;overflow-wrap:anywhere;font-size:12px}.dsh-crate-finding,.dsh-crate-step{padding:7px;border-radius:5px;background:var(--dsw-alias-fill-tertiary)}
59
56
  .dsh-crate-preview-row{display:grid;grid-template-columns:170px minmax(0,1fr);gap:8px}.dsh-crate-preview-row pre{margin:0;white-space:pre-wrap}.dsh-crate-diagnostic-grid{display:grid;grid-template-columns:150px minmax(0,1fr);gap:6px}.dsh-crate-diagnostic-grid dt{font-weight:600}.dsh-crate-diagnostic-grid dd{margin:0;white-space:pre-wrap}.dsh-crate-step{display:flex;gap:8px;align-items:flex-start}.dsh-crate-step-status{min-width:80px;font-weight:600}
@@ -63,36 +60,24 @@ window.__ModuleLoader__.load({
63
60
  .dsh-crate-group{display:flex;flex-direction:column;gap:6px}.dsh-crate-group-head{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:2px 0}.dsh-crate-group-toggle{border:0;background:transparent;color:var(--dsw-alias-state-business-primary,#4f8cff);font:inherit;font-size:12px;cursor:pointer;padding:2px 4px}.dsh-crate-group-body{display:flex;flex-direction:column;gap:6px}
64
61
  .dsh-crate-badge{margin-left:6px;padding:1px 5px;border-radius:4px;background:var(--dsw-alias-fill-tertiary);color:var(--dsw-alias-label-tertiary);font-size:11px;border:1px solid var(--dsw-alias-border-l2)}.dsh-crate-conflict{color:var(--dsw-alias-label-negative)}
65
62
  .dsh-crate-modal-backdrop{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;padding:24px;background:rgba(0,0,0,.58)}.dsh-crate-modal{display:flex;flex-direction:column;gap:12px;width:min(420px,calc(100vw - 48px));padding:18px;border:1px solid var(--dsw-alias-border-l2,#3a3a40);border-radius:10px;background:var(--dsw-alias-fill-primary,#1d1d20);color:var(--dsw-alias-label-primary,#f9fafb);box-shadow:0 18px 50px rgba(0,0,0,.45)}.dsh-crate-modal h3{margin:0;font-size:16px}.dsh-crate-modal p{margin:0;font-size:13px;color:var(--dsw-alias-label-tertiary,#b6b6bd)}.dsh-crate-modal-actions{display:flex;justify-content:flex-end;gap:8px}.dsh-crate-modal-actions button:last-child{background:var(--dsw-alias-state-business-primary,#4b7bec);color:#fff}
66
- .dsh-crate-tone-ok{color:var(--dsw-alias-label-positive)}.dsh-crate-tone-bad{color:var(--dsw-alias-label-negative)}.dsh-crate-tone-neutral{color:var(--dsw-alias-label-tertiary)}
63
+ @media(max-width:600px){
64
+ [data-dsh-module=settings]:has(.dsh-crate-section){flex-direction:column;width:calc(100vw - 24px);max-width:calc(100vw - 24px)}
65
+ [data-dsh-module=settings]:has(.dsh-crate-section)>nav{width:100%;gap:8px;padding:12px 12px 0}
66
+ [data-dsh-module=settings]:has(.dsh-crate-section)>nav>div:last-child{flex-direction:row;overflow-x:auto}
67
+ [data-dsh-module=settings]:has(.dsh-crate-section)>nav button{flex-shrink:0}
68
+ [data-dsh-module=settings]:has(.dsh-crate-section)>div{min-height:0}
69
+ [data-dsh-module=settings]:has(.dsh-crate-section)>div>div:last-child{padding:0 12px 12px}
70
+ .dsh-crate-section{min-width:0;max-width:100%}
71
+ .dsh-crate-inventory-item{flex-wrap:wrap;justify-content:flex-start}
72
+ .dsh-crate-inventory-item>span:first-of-type{flex:1 1 100%}
73
+ .dsh-crate-preview-row,.dsh-crate-diagnostic-grid{grid-template-columns:minmax(0,1fr)}
74
+ .dsh-crate-section pre{max-width:100%;overflow:auto;white-space:pre-wrap;overflow-wrap:anywhere}
75
+ }
76
+
67
77
  `;
68
78
  function isObject(value) {
69
79
  return value !== null && typeof value === "object" && !Array.isArray(value);
70
80
  }
71
- function formatTime(value) {
72
- if (typeof value !== "string" || !value) return "";
73
- const date = new Date(value);
74
- return Number.isNaN(date.getTime()) ? value : date.toLocaleString();
75
- }
76
- function toneOf(status) {
77
- const text = String(status ?? "").toUpperCase();
78
- if (text.includes("FAIL") || text.includes("ERROR") || text === "BLOCKER" || text === "UNKNOWN") return css.toneBad;
79
- if (text.includes("PASS") || text === "READY" || text === "PREPARED" || text === "OK") return css.toneOk;
80
- return css.toneNeutral;
81
- }
82
- const FIELD_LABELS = {
83
- code: "diagCode",
84
- stage: "diagStage",
85
- item: "diagItem",
86
- expected: "diagExpected",
87
- observed: "diagObserved",
88
- evidence: "diagEvidence",
89
- impact: "diagImpact",
90
- originalProfileStatus: "diagOriginalProfile",
91
- failedProfileStatus: "diagFailedProfile",
92
- temporaryProfileStatus: "diagTemporaryProfile",
93
- canContinue: "diagCanContinue",
94
- suggestedNextStep: "diagSuggestedNext"
95
- };
96
81
  function resultOf(value) {
97
82
  return isObject(value.result) ? value.result : value;
98
83
  }
@@ -113,7 +98,9 @@ window.__ModuleLoader__.load({
113
98
  }
114
99
  function pluginLabel(plugin) {
115
100
  const resolved = isObject(plugin.resolved) ? plugin.resolved : {};
116
- return `${String(plugin.name ?? "unknown")}@${String(resolved.version ?? "unknown")}`;
101
+ const runtime = isObject(plugin.runtime) ? plugin.runtime : {};
102
+ const version = typeof resolved.version === "string" && resolved.version !== "unknown" ? resolved.version : runtime.version;
103
+ return `${String(plugin.name ?? "unknown")}@${String(version ?? "unknown")}`;
117
104
  }
118
105
  function upsertProfileRow(list, row) {
119
106
  const name = typeof row.name === "string" ? row.name : "";
@@ -250,7 +237,7 @@ window.__ModuleLoader__.load({
250
237
  "temporaryProfileStatus",
251
238
  "canContinue",
252
239
  "suggestedNextStep"
253
- ].filter((field) => diagnostic[field] !== void 0).map((field) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t(FIELD_LABELS[field] ?? field) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: display(diagnostic[field]) })] }, field))
240
+ ].filter((field) => diagnostic[field] !== void 0).map((field) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: field }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: display(diagnostic[field]) })] }, field))
254
241
  }),
255
242
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
256
243
  className: css.button,
@@ -276,10 +263,7 @@ window.__ModuleLoader__.load({
276
263
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
277
264
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("coreStatus") }),
278
265
  " · ",
279
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
280
- className: toneOf(result.status),
281
- children: String(result.status ?? "UNKNOWN")
282
- })
266
+ String(result.status ?? "UNKNOWN")
283
267
  ] }),
284
268
  findings.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("findings") }), findings.map((finding, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
285
269
  className: css.finding,
@@ -301,12 +285,14 @@ window.__ModuleLoader__.load({
301
285
  }
302
286
  function ImportPreview({ value, t, targetProfile, overwrite }) {
303
287
  const result = resultOf(value);
288
+ if (typeof result.canContinue !== "boolean") return null;
304
289
  const pack = isObject(result.pack) ? result.pack : {};
305
290
  const profile = isObject(pack.profile) ? pack.profile : {};
306
291
  const requiredPlugins = objects(result.requiredPlugins);
307
292
  const optionalPlugins = objects(result.optionalPlugins);
308
293
  const warningCount = typeof result.warningCount === "number" ? result.warningCount : 0;
309
294
  const environment = isObject(result.environment) ? result.environment : {};
295
+ const secrets = isObject(result.secrets) ? result.secrets : {};
310
296
  const target = targetProfile?.trim() || String(result.targetProfile ?? profile.name ?? "UNKNOWN");
311
297
  const safety = overwrite ? t("overwritePreview") : t("willNotOverwrite");
312
298
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -338,6 +324,16 @@ window.__ModuleLoader__.load({
338
324
  "data-field": "environment",
339
325
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("environment") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", { children: display(environment) })]
340
326
  }),
327
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
328
+ className: css.previewRow,
329
+ "data-field": "requiredSecrets",
330
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("requiredSecrets") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: Array.isArray(secrets.required) ? secrets.required.join(", ") || t("none") : t("none") })]
331
+ }),
332
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
333
+ className: css.previewRow,
334
+ "data-field": "missingSecrets",
335
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("missingSecrets") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: Array.isArray(secrets.missing) ? secrets.missing.join(", ") || t("none") : t("none") })]
336
+ }),
341
337
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
342
338
  className: css.previewRow,
343
339
  "data-field": "coreDecision",
@@ -404,14 +400,10 @@ window.__ModuleLoader__.load({
404
400
  " · ",
405
401
  String(result.status ?? "UNKNOWN")
406
402
  ] }),
407
- String(result.status ?? "") === "UNTESTED" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
408
- className: css.muted,
409
- children: t("verifyUntestedNote")
410
- }) : null,
411
403
  steps.map((step, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
412
404
  className: css.step,
413
405
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
414
- className: `${css.stepStatus} ${toneOf(step.status)}`,
406
+ className: css.stepStatus,
415
407
  children: String(step.status ?? "UNKNOWN")
416
408
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
417
409
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: String(step.name ?? "unknown") }),
@@ -434,6 +426,14 @@ window.__ModuleLoader__.load({
434
426
  const [profiles, setProfiles] = (0, react.useState)([]);
435
427
  const [profile, setProfile] = (0, react.useState)("");
436
428
  const [modes, setModes] = (0, react.useState)({});
429
+ const [preset, setPreset] = (0, react.useState)("custom");
430
+ const [secretNames, setSecretNames] = (0, react.useState)("");
431
+ const [dataSelection, setDataSelection] = (0, react.useState)([]);
432
+ const [dataOwners, setDataOwners] = (0, react.useState)({});
433
+ const [dataKinds, setDataKinds] = (0, react.useState)({});
434
+ const [exportPlan, setExportPlan] = (0, react.useState)();
435
+ const [reviewedSelection, setReviewedSelection] = (0, react.useState)("");
436
+ const [downloadSelection, setDownloadSelection] = (0, react.useState)("");
437
437
  const [file, setFile] = (0, react.useState)();
438
438
  const [report, setReport] = (0, react.useState)();
439
439
  const [history, setHistory] = (0, react.useState)([]);
@@ -467,6 +467,41 @@ window.__ModuleLoader__.load({
467
467
  const exportBundles = installedBundles.filter((bundle) => bundle.active === true);
468
468
  const conflictBundles = installedBundles.filter((bundle) => bundle.conflict === true);
469
469
  const exportNames = exportBundles.map((bundle) => String(bundle.name ?? "")).filter(Boolean);
470
+ const requiredSecrets = [...new Set(secretNames.split(/[\s,]+/).filter(Boolean))];
471
+ const invalidSecretNames = requiredSecrets.some((name) => !/^[A-Za-z_][A-Za-z0-9_]*$/.test(name));
472
+ const exportBody = {
473
+ profileName: profile,
474
+ includeInstalledBundles: true,
475
+ preset,
476
+ dataSelection,
477
+ requiredSecrets,
478
+ embed: exportNames.filter((name) => (modes[name] ?? "embedded") === "embedded"),
479
+ referenceOnly: exportNames.filter((name) => (modes[name] ?? "embedded") === "reference-only")
480
+ };
481
+ const exportSignature = JSON.stringify(exportBody);
482
+ const previewExport = async () => {
483
+ setBusy(true);
484
+ setError("");
485
+ try {
486
+ const value = await request("export-plan", exportBody);
487
+ if (value.status === "failed") throw new Error(failMessage(value) || t("error"));
488
+ setExportPlan(resultOf(value));
489
+ setReviewedSelection(exportSignature);
490
+ } catch (caught) {
491
+ setError(caught instanceof Error ? caught.message : String(caught));
492
+ setReviewedSelection("");
493
+ } finally {
494
+ setBusy(false);
495
+ }
496
+ };
497
+ (0, react.useEffect)(() => {
498
+ setDataSelection([]);
499
+ setDataOwners({});
500
+ setDataKinds({});
501
+ setSecretNames("");
502
+ setExportPlan(void 0);
503
+ setReviewedSelection("");
504
+ }, [profile]);
470
505
  const canImport = isObject(report?.result) && report.result.canContinue === true;
471
506
  const currentRuntimeProfile = typeof runtime?.currentProfile === "string" ? runtime.currentProfile : "";
472
507
  const loadProfiles = async () => {
@@ -524,19 +559,18 @@ window.__ModuleLoader__.load({
524
559
  const run = async (action, body) => {
525
560
  setBusy(true);
526
561
  setError("");
562
+ if (action === "export") setDownloadSelection("");
527
563
  try {
528
564
  const value = await request(action, body);
529
565
  setReport(value);
530
566
  const result = resultOf(value);
531
567
  const profileLabel = String(body.profileName ?? "");
532
568
  if (value.status === "failed" || result.status === "failed" || result.status === "FAIL") showToast(`${t("error")}: ${failMessage(value) || t("error")}`);
533
- else if (action === "export" && typeof value.downloadName === "string") showToast(`${t("exportSuccess")} ${profileLabel}`.trim());
534
- else if (action === "inspect") showToast(t("inspectSuccess"));
535
- else if (action === "verify") {
536
- if (value.success === true) showToast(`${t("verifySuccess")} ${profileLabel}`.trim());
537
- else if (result.status === "FAIL") showToast(`${t("error")}: ${failMessage(value) || t("verifyFailed")}`);
538
- else showToast(`${t("verifyFinished")} ${profileLabel} · ${String(result.status ?? "")}`.trim());
539
- }
569
+ else if (action === "export" && typeof value.downloadName === "string") {
570
+ setDownloadSelection(JSON.stringify(body));
571
+ showToast(`${t("exportSuccess")} ${profileLabel}`.trim());
572
+ } else if (action === "inspect") showToast(t("inspectSuccess"));
573
+ else if (action === "verify" && value.success !== true) showToast(`${t("verifyFinished")} ${profileLabel} · ${String(result.status ?? "")}`.trim());
540
574
  } catch (caught) {
541
575
  const message = caught instanceof Error ? caught.message : String(caught);
542
576
  setError(message);
@@ -695,8 +729,7 @@ window.__ModuleLoader__.load({
695
729
  return;
696
730
  }
697
731
  if (!window.confirm(`${t("overwriteWarning")}\n\n${target}`)) {
698
- showToast(t("overwriteCanceled"));
699
- setError("");
732
+ setError(t("overwriteCanceled"));
700
733
  return;
701
734
  }
702
735
  setBusy(true);
@@ -840,10 +873,14 @@ window.__ModuleLoader__.load({
840
873
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
841
874
  className: css.button,
842
875
  type: "button",
843
- disabled: busy || !switchTarget,
876
+ disabled: busy || !switchTarget || runtime?.restartConfigured !== true,
844
877
  onClick: () => void switchSelectedProfile(),
845
878
  children: busy ? t("switchPending") : t("switchProfile")
846
879
  }),
880
+ runtime?.restartConfigured === false ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
881
+ className: css.muted,
882
+ children: t("switchUnavailable")
883
+ }) : null,
847
884
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
848
885
  className: css.label,
849
886
  children: [t("deleteProfile"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
@@ -1006,10 +1043,13 @@ window.__ModuleLoader__.load({
1006
1043
  className: css.select,
1007
1044
  disabled: conflict,
1008
1045
  value: modes[name] ?? "embedded",
1009
- onChange: (event) => setModes((current) => ({
1010
- ...current,
1011
- [name]: event.target.value
1012
- })),
1046
+ onChange: (event) => {
1047
+ setPreset("custom");
1048
+ setModes((current) => ({
1049
+ ...current,
1050
+ [name]: event.target.value
1051
+ }));
1052
+ },
1013
1053
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1014
1054
  value: "embedded",
1015
1055
  children: t("embedded")
@@ -1047,19 +1087,172 @@ window.__ModuleLoader__.load({
1047
1087
  })]
1048
1088
  }, `conflict-${String(bundle.name)}`))]
1049
1089
  }) : null,
1090
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [
1091
+ t("exportPreset"),
1092
+ " ",
1093
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1094
+ className: css.select,
1095
+ value: preset,
1096
+ disabled: busy,
1097
+ onChange: (event) => {
1098
+ const value = event.target.value;
1099
+ setPreset(value);
1100
+ if (value !== "custom") setModes(Object.fromEntries(exportNames.map((name) => [name, value === "freeze" ? "embedded" : "reference-only"])));
1101
+ },
1102
+ children: [
1103
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1104
+ value: "share",
1105
+ children: "Share"
1106
+ }),
1107
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1108
+ value: "freeze",
1109
+ children: "Freeze"
1110
+ }),
1111
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1112
+ value: "custom",
1113
+ children: "Custom"
1114
+ })
1115
+ ]
1116
+ })
1117
+ ] }),
1118
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1119
+ className: css.label,
1120
+ children: [t("requiredSecrets"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1121
+ className: css.file,
1122
+ value: secretNames,
1123
+ disabled: busy,
1124
+ autoComplete: "off",
1125
+ spellCheck: false,
1126
+ "aria-describedby": "crate-secret-hint",
1127
+ "aria-invalid": invalidSecretNames,
1128
+ onChange: (event) => setSecretNames(event.target.value)
1129
+ })]
1130
+ }),
1131
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1132
+ className: css.muted,
1133
+ id: "crate-secret-hint",
1134
+ children: t("secretNamesHint")
1135
+ }),
1136
+ invalidSecretNames ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1137
+ className: css.error,
1138
+ role: "alert",
1139
+ children: t("invalidSecretNames")
1140
+ }) : null,
1050
1141
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1051
1142
  className: css.button,
1052
1143
  type: "button",
1053
- disabled: busy || !profile,
1054
- onClick: () => void run("export", {
1055
- profileName: profile,
1056
- includeInstalledBundles: true,
1057
- embed: exportNames.filter((name) => (modes[name] ?? "embedded") === "embedded"),
1058
- referenceOnly: exportNames.filter((name) => (modes[name] ?? "embedded") === "reference-only")
1059
- }),
1144
+ disabled: busy || !profile || invalidSecretNames,
1145
+ onClick: () => void previewExport(),
1146
+ children: busy ? t("working") : t("previewExport")
1147
+ }),
1148
+ exportPlan ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1149
+ className: css.inventory,
1150
+ "data-field": "exportPreview",
1151
+ children: [
1152
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("requiredSecrets") }),
1153
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1154
+ "data-field": "reviewedSecrets",
1155
+ children: Array.isArray(exportPlan.requiredSecrets) ? exportPlan.requiredSecrets.join(", ") || t("none") : t("none")
1156
+ }),
1157
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("portableData") }),
1158
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("privacyReview") }),
1159
+ objects(exportPlan.dataInventory).map((item, index) => {
1160
+ const path = typeof item.path === "string" ? item.path : "";
1161
+ const conversation = item.kind === "conversation";
1162
+ const chosen = dataSelection.some((row) => row.path === path);
1163
+ const owner = conversation ? "@deepseek-ai/dsh-session-persistence-jsonl" : dataOwners[path] || "";
1164
+ const selectable = item.selectable === true || item.classification === "portable";
1165
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1166
+ className: css.inventoryItem,
1167
+ children: [
1168
+ selectable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1169
+ type: "checkbox",
1170
+ "aria-label": path,
1171
+ disabled: busy || !owner,
1172
+ checked: chosen,
1173
+ onChange: (event) => {
1174
+ setDataSelection((current) => event.target.checked ? [...current.filter((row) => row.path !== path), {
1175
+ path,
1176
+ owner,
1177
+ kind: conversation ? "conversation" : dataKinds[path] || "plugin-data"
1178
+ }] : current.filter((row) => row.path !== path));
1179
+ }
1180
+ }) : null,
1181
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
1182
+ path || String(item.name ?? ""),
1183
+ " · ",
1184
+ String(item.classification ?? "")
1185
+ ] }),
1186
+ selectable && !conversation ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1187
+ className: css.select,
1188
+ "aria-label": path + " " + t("dataOwner"),
1189
+ disabled: busy || chosen,
1190
+ value: owner,
1191
+ onChange: (event) => setDataOwners((current) => ({
1192
+ ...current,
1193
+ [path]: event.target.value
1194
+ })),
1195
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1196
+ value: "",
1197
+ children: t("chooseOwner")
1198
+ }), objects(exportPlan.plugins).map((plugin) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1199
+ value: String(plugin.name),
1200
+ children: String(plugin.name)
1201
+ }, String(plugin.name)))]
1202
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1203
+ className: css.select,
1204
+ "aria-label": path + " " + t("dataKind"),
1205
+ disabled: busy || chosen,
1206
+ value: dataKinds[path] || "plugin-data",
1207
+ onChange: (event) => setDataKinds((current) => ({
1208
+ ...current,
1209
+ [path]: event.target.value
1210
+ })),
1211
+ children: [
1212
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1213
+ value: "plugin-data",
1214
+ children: "Plugin data"
1215
+ }),
1216
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1217
+ value: "plugin-config",
1218
+ children: "Plugin config"
1219
+ }),
1220
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1221
+ value: "plugin-workflow",
1222
+ children: "Plugin workflow"
1223
+ })
1224
+ ]
1225
+ })] }) : null
1226
+ ]
1227
+ }, path || index);
1228
+ }),
1229
+ isObject(exportPlan.completeness) ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1230
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("completeness") }),
1231
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
1232
+ t("embedded"),
1233
+ ": ",
1234
+ Array.isArray(exportPlan.completeness.embedded) ? exportPlan.completeness.embedded.length : 0,
1235
+ " · ",
1236
+ t("referenceOnly"),
1237
+ ": ",
1238
+ Array.isArray(exportPlan.completeness.referenceOnly) ? exportPlan.completeness.referenceOnly.length : 0,
1239
+ " · ",
1240
+ t("offlineRestore"),
1241
+ ": ",
1242
+ String(exportPlan.completeness.offlineRestorable ?? "UNPROVEN")
1243
+ ] }),
1244
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("rawJson") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", { children: JSON.stringify(exportPlan.completeness, null, 2) })] })
1245
+ ] }) : null
1246
+ ]
1247
+ }) : null,
1248
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1249
+ className: css.button,
1250
+ type: "button",
1251
+ disabled: busy || !profile || invalidSecretNames || reviewedSelection !== exportSignature,
1252
+ onClick: () => void run("export", exportBody),
1060
1253
  children: busy ? t("exporting") : t("exportButton")
1061
1254
  }),
1062
- isObject(report) && typeof report.downloadName === "string" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
1255
+ !busy && downloadSelection === exportSignature && isObject(report) && typeof report.downloadName === "string" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
1063
1256
  href: `/dsh-crate/download?name=${encodeURIComponent(report.downloadName)}`,
1064
1257
  children: t("download")
1065
1258
  }) : null,
@@ -1188,15 +1381,12 @@ window.__ModuleLoader__.load({
1188
1381
  }) : history.map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
1189
1382
  className: css.historyItem,
1190
1383
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
1191
- formatTime(item.time),
1384
+ item.time,
1192
1385
  " · ",
1193
1386
  item.action,
1194
1387
  " · ",
1195
1388
  item.profile ?? item.pack ?? ""
1196
- ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
1197
- className: toneOf(item.status),
1198
- children: item.status
1199
- })]
1389
+ ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: item.status })]
1200
1390
  }, `${item.time}-${index}`))
1201
1391
  }) : null,
1202
1392
  newProfileDialogOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -1241,7 +1431,7 @@ window.__ModuleLoader__.load({
1241
1431
  className: css.button,
1242
1432
  type: "button",
1243
1433
  onClick: () => void confirmNewProfileImport(),
1244
- children: t("confirmImportAction")
1434
+ children: t("importButton")
1245
1435
  })]
1246
1436
  })
1247
1437
  ]
@@ -1366,8 +1556,7 @@ window.__ModuleLoader__.load({
1366
1556
  choosePack: "Choose a .dshcrate file",
1367
1557
  inspectButton: "Run Preflight",
1368
1558
  importButton: "Import Crate",
1369
- confirmImportAction: "Confirm Import",
1370
- confirmImport: "Imports as a new Profile after a Preflight check. Continue?",
1559
+ confirmImport: "Preflight passed. Import as a new Profile?",
1371
1560
  working: "Working…",
1372
1561
  noReport: "No report yet.",
1373
1562
  noHistory: "No DSH Crate operations have been recorded.",
@@ -1418,6 +1607,11 @@ window.__ModuleLoader__.load({
1418
1607
  profileNameRequired: "Profile name is required.",
1419
1608
  profileNameExists: "That Profile name already exists.",
1420
1609
  cancel: "Cancel",
1610
+ switchUnavailable: "This desktop environment cannot switch and restart Profiles here. Start the restored environment in a separate DSH Home.",
1611
+ requiredSecrets: "Required Secret names",
1612
+ missingSecrets: "Missing Secret names",
1613
+ secretNamesHint: "Enter environment variable names only, separated by spaces or commas (for example DEEPSEEK_API_KEY). Never enter values. Blank keeps names recorded by a prior import; no credentials are read automatically.",
1614
+ invalidSecretNames: "Use environment variable names containing letters, digits and underscores, starting with a letter or underscore. Do not enter assignments or Secret values.",
1421
1615
  createProfile: "New Profile",
1422
1616
  createProfilePlaceholder: "new profile name",
1423
1617
  createProfileHint: "A new Profile includes the official DSH bundles and the DSH Crate plugin by default.",
@@ -1448,26 +1642,22 @@ window.__ModuleLoader__.load({
1448
1642
  conflictBundles: "Conflicted Bundles (not exported)",
1449
1643
  collapse: "Collapse",
1450
1644
  expand: "Expand",
1645
+ exportPreset: "Export mode",
1646
+ previewExport: "Review export contents",
1647
+ portableData: "Selected conversations and plugin data",
1648
+ dataOwner: "Plugin owner",
1649
+ chooseOwner: "Choose the owner after checking its storage contract",
1650
+ dataKind: "Data type",
1651
+ privacyReview: "Review selected data before sharing. Recognizable credentials are blocked; private text still needs your review.",
1652
+ completeness: "Freeze completeness",
1653
+ offlineRestore: "Offline-restorable",
1451
1654
  exportSuccess: "Crate exported successfully.",
1452
1655
  inspectSuccess: "Preflight finished.",
1453
1656
  verifySuccess: "Verification passed.",
1454
1657
  verifyFailed: "Verification failed.",
1455
1658
  verifyFinished: "Verification finished.",
1456
- verifyUntestedNote: "UNTESTED means composition was checked only; runtime, model, session, and plugin behavior were NOT actually verified.",
1457
1659
  importOverwritten: "Profile overwritten.",
1458
- switchPending: "Switching Profile and waiting for DSH ready…",
1459
- diagCode: "Code",
1460
- diagStage: "Stage",
1461
- diagItem: "Item",
1462
- diagExpected: "Expected",
1463
- diagObserved: "Observed",
1464
- diagEvidence: "Evidence",
1465
- diagImpact: "Impact",
1466
- diagOriginalProfile: "Original Profile",
1467
- diagFailedProfile: "Failed Profile",
1468
- diagTemporaryProfile: "Temporary Profile",
1469
- diagCanContinue: "Can continue",
1470
- diagSuggestedNext: "Suggested next step"
1660
+ switchPending: "Switching Profile and waiting for DSH ready…"
1471
1661
  };
1472
1662
  const zh = {
1473
1663
  nav: "DSH Crate",
@@ -1487,8 +1677,7 @@ window.__ModuleLoader__.load({
1487
1677
  choosePack: "选择 .dshcrate 文件",
1488
1678
  inspectButton: "运行 Preflight",
1489
1679
  importButton: "导入 Crate",
1490
- confirmImportAction: "确认导入",
1491
- confirmImport: "将作为新 Profile 导入,导入前会先执行 Preflight 检查。是否继续?",
1680
+ confirmImport: "Preflight 已通过。要作为新 Profile 导入吗?",
1492
1681
  working: "处理中…",
1493
1682
  noReport: "暂无报告。",
1494
1683
  noHistory: "还没有 DSH Crate 操作记录。",
@@ -1539,6 +1728,11 @@ window.__ModuleLoader__.load({
1539
1728
  profileNameRequired: "必须填写 Profile 名称。",
1540
1729
  profileNameExists: "该 Profile 名称已存在。",
1541
1730
  cancel: "取消",
1731
+ switchUnavailable: "当前桌面环境不支持在此切换并重启 Profile。请在独立的 DSH Home 中启动恢复后的环境。",
1732
+ requiredSecrets: "所需 Secret 名称",
1733
+ missingSecrets: "缺失的 Secret 名称",
1734
+ secretNamesHint: "只填写环境变量名称,用空格或逗号分隔,例如 DEEPSEEK_API_KEY。不要填写值。留空时沿用此前导入记录的名称;不会自动读取凭据。",
1735
+ invalidSecretNames: "名称只能包含字母、数字和下划线,并以字母或下划线开头。不要填写赋值表达式或 Secret 值。",
1542
1736
  createProfile: "新建 Profile",
1543
1737
  createProfilePlaceholder: "新 Profile 名称",
1544
1738
  createProfileHint: "新 Profile 默认包含官方插件与本插件(DSH Crate)。",
@@ -1569,26 +1763,22 @@ window.__ModuleLoader__.load({
1569
1763
  conflictBundles: "冲突 Bundle(未导出)",
1570
1764
  collapse: "折叠",
1571
1765
  expand: "展开",
1766
+ exportPreset: "导出模式",
1767
+ previewExport: "预览导出内容",
1768
+ portableData: "选择会话与插件数据",
1769
+ dataOwner: "所属插件",
1770
+ chooseOwner: "确认存储归属后选择插件",
1771
+ dataKind: "数据类型",
1772
+ privacyReview: "分享前请检查所选内容。可识别的凭据会被阻止,私人文本仍需自行检查。",
1773
+ completeness: "Freeze 完整度",
1774
+ offlineRestore: "离线可恢复",
1572
1775
  exportSuccess: "Crate 导出成功。",
1573
1776
  inspectSuccess: "Preflight 检查完成。",
1574
1777
  verifySuccess: "验证通过。",
1575
1778
  verifyFailed: "验证未通过。",
1576
1779
  verifyFinished: "验证完成。",
1577
- verifyUntestedNote: "UNTESTED 表示仅验证了组合(composition),运行时、模型、会话、插件业务行为均未实际验证。",
1578
1780
  importOverwritten: "Profile 已覆盖。",
1579
- switchPending: "正在切换 Profile,等待 DSH ready…",
1580
- diagCode: "诊断代码",
1581
- diagStage: "失败阶段",
1582
- diagItem: "对象",
1583
- diagExpected: "预期",
1584
- diagObserved: "实际",
1585
- diagEvidence: "证据",
1586
- diagImpact: "影响",
1587
- diagOriginalProfile: "原 Profile 状态",
1588
- diagFailedProfile: "失败 Profile 状态",
1589
- diagTemporaryProfile: "临时 Profile 状态",
1590
- diagCanContinue: "是否可继续",
1591
- diagSuggestedNext: "建议下一步"
1781
+ switchPending: "正在切换 Profile,等待 DSH ready…"
1592
1782
  };
1593
1783
  //#endregion
1594
1784
  //#region src/client/index.tsx
package/lib/index.d.ts CHANGED
@@ -1,4 +1,10 @@
1
1
  import { Context } from "@deepseek-ai/cordis";
2
+
3
+ //#region src/windows-terminal.d.ts
4
+ interface WindowsTerminalConfig {
5
+ windowsTerminalCompatibility?: boolean;
6
+ }
7
+ //#endregion
2
8
  //#region src/index.d.ts
3
9
  /** Minimal structural projection of the Cordis Loader entry tree used by DSH. */
4
10
  interface LoaderEntryOptions {
@@ -25,6 +31,6 @@ declare module '@deepseek-ai/cordis' {
25
31
  }
26
32
  /** Register the host bridge. The Core executable is intentionally external. */
27
33
  declare const inject: string[];
28
- declare function apply(ctx: Context): void;
34
+ declare function apply(ctx: Context, config?: WindowsTerminalConfig): void;
29
35
  //#endregion
30
36
  export { apply, inject };