@exagone313/dsh-podman 0.2.0-rc.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/README.md +12 -0
  2. package/README.zh.md +14 -2
  3. package/cordis.patch.yml +58 -0
  4. package/dist/approval-reasons.d.ts +3 -4
  5. package/dist/approval-reasons.js +40 -49
  6. package/dist/approval-reasons.test.js +43 -15
  7. package/dist/approval.js +27 -10
  8. package/dist/approval.test.js +165 -34
  9. package/dist/card-route.d.ts +3 -2
  10. package/dist/card-route.js +103 -35
  11. package/dist/card-route.test.js +60 -43
  12. package/dist/card-test-support.js +39 -66
  13. package/dist/client/ContainerCard.d.ts +1 -1
  14. package/dist/client/ContainerCard.js +76 -70
  15. package/dist/client/card-protocol.d.ts +3 -3
  16. package/dist/client/container-card-caches.js +11 -6
  17. package/dist/client/container-card-controller.d.ts +13 -18
  18. package/dist/client/container-card-controller.js +86 -40
  19. package/dist/client/container-card-create-modal.d.ts +2 -2
  20. package/dist/client/container-card-create-modal.js +13 -5
  21. package/dist/client/container-card-default-env.d.ts +10 -0
  22. package/dist/client/container-card-default-env.js +69 -0
  23. package/dist/client/container-card-directory.d.ts +2 -2
  24. package/dist/client/container-card-directory.js +12 -4
  25. package/dist/client/container-card-editors.d.ts +3 -3
  26. package/dist/client/container-card-editors.js +104 -32
  27. package/dist/client/container-card-images.d.ts +4 -4
  28. package/dist/client/container-card-images.js +19 -6
  29. package/dist/client/container-card-paths.d.ts +3 -3
  30. package/dist/client/container-card-paths.js +18 -11
  31. package/dist/client/container-card-row.d.ts +10 -6
  32. package/dist/client/container-card-row.js +82 -13
  33. package/dist/client/container-card-secrets.d.ts +3 -3
  34. package/dist/client/container-card-secrets.js +8 -6
  35. package/dist/client/container-card-shared.d.ts +4 -17
  36. package/dist/client/container-card-shared.js +9 -25
  37. package/dist/client/container-card-styles.d.ts +2 -8
  38. package/dist/client/container-card-styles.js +14 -52
  39. package/dist/client/container-card-volumes.d.ts +2 -2
  40. package/dist/client/container-card-volumes.js +9 -5
  41. package/dist/client/container-card-workspace.d.ts +8 -6
  42. package/dist/client/container-card-workspace.js +9 -3
  43. package/dist/client/directory-picker.js +2 -1
  44. package/dist/client/index.js +13109 -2117
  45. package/dist/client/locales.d.ts +6 -1
  46. package/dist/client/locales.js +88 -38
  47. package/dist/client/podman-terminal-guide.d.ts +11 -0
  48. package/dist/client/podman-terminal-guide.js +208 -0
  49. package/dist/client/podman-terminal-title.d.ts +7 -0
  50. package/dist/client/podman-terminal-title.js +30 -0
  51. package/dist/client/podman-terminal.d.ts +26 -0
  52. package/dist/client/podman-terminal.js +412 -0
  53. package/dist/client/read-only-approval.d.ts +2 -2
  54. package/dist/client/read-only-approval.js +13 -3
  55. package/dist/client/slot-contract.d.ts +0 -11
  56. package/dist/client/terminal-preference.d.ts +26 -0
  57. package/dist/client/terminal-preference.js +67 -0
  58. package/dist/client/terminal-protocol.d.ts +85 -0
  59. package/dist/client/terminal-protocol.js +14 -0
  60. package/dist/client/terminal-tab.d.ts +60 -0
  61. package/dist/client/terminal-tab.js +35 -0
  62. package/dist/client/terminal-targets.d.ts +33 -0
  63. package/dist/client/terminal-targets.js +65 -0
  64. package/dist/client/terminal-titles.d.ts +23 -0
  65. package/dist/client/terminal-titles.js +52 -0
  66. package/dist/client/terminal-transport.d.ts +63 -0
  67. package/dist/client/terminal-transport.js +207 -0
  68. package/dist/client/tool-views.js +215 -49
  69. package/dist/container-env.d.ts +11 -0
  70. package/dist/container-env.js +96 -0
  71. package/dist/container-env.test.d.ts +1 -0
  72. package/dist/container-env.test.js +93 -0
  73. package/dist/containers.test.js +196 -18
  74. package/dist/daemons.test.js +18 -4
  75. package/dist/dsh-version.js +2 -1
  76. package/dist/env-rows.d.ts +14 -0
  77. package/dist/env-rows.js +52 -0
  78. package/dist/env-rows.test.d.ts +1 -0
  79. package/dist/env-rows.test.js +43 -0
  80. package/dist/fs-provider.d.ts +1 -0
  81. package/dist/fs-provider.js +26 -12
  82. package/dist/fs-provider.test.js +51 -10
  83. package/dist/generated/version.d.ts +2 -2
  84. package/dist/generated/version.js +2 -2
  85. package/dist/grpc/proto/dshctl/v1/control.proto +8 -1
  86. package/dist/grpc/proto/dshguest/v1/guest.proto +5 -0
  87. package/dist/grpc/runtime-client.d.ts +3 -1
  88. package/dist/grpc/runtime-client.js +90 -17
  89. package/dist/guest-rpc.d.ts +15 -19
  90. package/dist/guest-rpc.js +289 -70
  91. package/dist/guest-rpc.test.d.ts +1 -0
  92. package/dist/guest-rpc.test.js +241 -0
  93. package/dist/guest-terminal.d.ts +30 -0
  94. package/dist/guest-terminal.js +196 -0
  95. package/dist/images.test.js +10 -3
  96. package/dist/index.d.ts +11 -16
  97. package/dist/index.js +42 -36
  98. package/dist/locales.test.d.ts +1 -0
  99. package/dist/locales.test.js +34 -0
  100. package/dist/misc.test.js +38 -10
  101. package/dist/mount-enums.js +2 -1
  102. package/dist/mount-input.d.ts +2 -0
  103. package/dist/mount-input.js +38 -22
  104. package/dist/mounts.test.js +81 -11
  105. package/dist/naming.test.d.ts +1 -0
  106. package/dist/naming.test.js +28 -0
  107. package/dist/output-reader.js +36 -4
  108. package/dist/package-deps.test.d.ts +1 -0
  109. package/dist/package-deps.test.js +49 -0
  110. package/dist/paths.test.js +4 -2
  111. package/dist/plugin-meta.test.d.ts +1 -0
  112. package/dist/plugin-meta.test.js +29 -0
  113. package/dist/project-path.js +33 -7
  114. package/dist/project-path.test.js +14 -0
  115. package/dist/prompts.d.ts +0 -4
  116. package/dist/prompts.js +15 -84
  117. package/dist/read-only-shell.js +3 -1
  118. package/dist/read-only-shell.test.js +80 -25
  119. package/dist/runtime-client.test.d.ts +1 -0
  120. package/dist/runtime-client.test.js +53 -0
  121. package/dist/secrets.test.js +21 -6
  122. package/dist/settings-commands.test.js +61 -14
  123. package/dist/settings-create.test.js +32 -10
  124. package/dist/settings-default-env.test.d.ts +1 -0
  125. package/dist/settings-default-env.test.js +175 -0
  126. package/dist/settings-mounts.test.js +46 -7
  127. package/dist/settings-schema.d.ts +6 -11
  128. package/dist/settings-schema.js +4 -8
  129. package/dist/settings-schema.test.d.ts +1 -0
  130. package/dist/settings-schema.test.js +35 -0
  131. package/dist/settings-workspaces.test.js +36 -63
  132. package/dist/spill-store.d.ts +1 -0
  133. package/dist/spill-store.js +17 -4
  134. package/dist/spill-store.test.js +31 -8
  135. package/dist/subprocess.d.ts +4 -0
  136. package/dist/subprocess.js +148 -162
  137. package/dist/subprocess.test.js +118 -8
  138. package/dist/terminal-preference.test.d.ts +1 -0
  139. package/dist/terminal-preference.test.js +62 -0
  140. package/dist/terminal-route.d.ts +10 -0
  141. package/dist/terminal-route.js +270 -0
  142. package/dist/terminal-route.test.d.ts +1 -0
  143. package/dist/terminal-route.test.js +265 -0
  144. package/dist/terminal-sessions.d.ts +69 -0
  145. package/dist/terminal-sessions.js +252 -0
  146. package/dist/terminal-shells.d.ts +22 -0
  147. package/dist/terminal-shells.js +99 -0
  148. package/dist/terminal-tab.test.d.ts +1 -0
  149. package/dist/terminal-tab.test.js +58 -0
  150. package/dist/terminal-targets.test.d.ts +1 -0
  151. package/dist/terminal-targets.test.js +43 -0
  152. package/dist/terminal-titles.test.d.ts +1 -0
  153. package/dist/terminal-titles.test.js +36 -0
  154. package/dist/test-support.d.ts +35 -5
  155. package/dist/test-support.js +91 -29
  156. package/dist/tool-defs.js +19 -4
  157. package/dist/tool-handlers.js +80 -28
  158. package/dist/tool-params.js +19 -5
  159. package/dist/volumes.test.js +1 -1
  160. package/dist/workspace-binding.d.ts +18 -9
  161. package/dist/workspace-binding.js +244 -77
  162. package/dist/workspace-binding.test.js +189 -25
  163. package/docs/architecture.md +62 -21
  164. package/docs/architecture.zh.md +57 -13
  165. package/docs/configuration.md +61 -20
  166. package/docs/configuration.zh.md +53 -19
  167. package/docs/development-prompt.md +72 -0
  168. package/docs/development-prompt.zh.md +69 -0
  169. package/docs/development.md +141 -21
  170. package/docs/development.zh.md +125 -17
  171. package/docs/install-dsh-and-dsh-podman.md +21 -8
  172. package/docs/install-dsh-and-dsh-podman.zh.md +18 -8
  173. package/docs/uninstall.md +83 -0
  174. package/docs/uninstall.zh.md +79 -0
  175. package/docs/update.md +120 -0
  176. package/docs/update.zh.md +114 -0
  177. package/docs/usage.md +108 -61
  178. package/docs/usage.zh.md +76 -38
  179. package/locale/en.json +6 -0
  180. package/locale/zh.json +6 -0
  181. package/package.json +59 -26
  182. package/quadlet/dsh-podman-orchestrator.container +46 -0
  183. package/quadlet/dsh.container +35 -0
  184. package/LICENSE.pkg +0 -17103
  185. package/dist/preferences.d.ts +0 -6
  186. package/dist/preferences.js +0 -27
@@ -2,9 +2,9 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
2
2
  // SPDX-FileCopyrightText: 2026 Elouan Martinet <exa@elou.world>
3
3
  //
4
4
  // SPDX-License-Identifier: MIT
5
- import { ConfirmButton, Field, namePattern, sanitizeName } from "./container-card-shared.js";
6
- import { greyId, hint, imageSelect, sectionTitle, wsBody } from "./container-card-styles.js";
7
- import { Button, DisclosureRow, Input, Modal } from "@deepseek-ai/dsh-client-ui-primitives";
5
+ import { ConfirmButton, Field, namePattern, sanitizeName, } from "./container-card-shared.js";
6
+ import { greyId, hint, imageSelect, sectionTitle, wsBody, } from "./container-card-styles.js";
7
+ import { Button, DisclosureRow, Input, Modal, } from "@deepseek-ai/dsh-client-ui-primitives";
8
8
  import { useId, useState } from "react";
9
9
  export function SecretRow(props) {
10
10
  const { t, name, busy, writable, onSet, onRemove } = props;
@@ -29,7 +29,7 @@ export function SecretRow(props) {
29
29
  }, children: name }), _jsx(Input, { type: "password", value: content, disabled: !writable || busy, placeholder: t("setSecret"), onChange: (event) => setContent(event.target.value), onKeyDown: (event) => {
30
30
  if (event.key === "Enter")
31
31
  submit();
32
- }, style: { width: "200px" } }), _jsx(ConfirmButton, { t: t, label: t("setSecret"), title: t("confirmTitle"), description: t("confirmSetSecret"), disabled: !writable || !canSave, onConfirm: submit }), _jsx(ConfirmButton, { t: t, label: t("removeSecret"), title: t("confirmTitle"), description: t("confirmRemoveSecret"), disabled: busy, onConfirm: () => onRemove(name) })] }));
32
+ }, style: { width: "200px" } }), _jsx(ConfirmButton, { t: t, label: t("setSecret"), title: t("confirmTitle"), description: t("confirmSetSecret", { secret: name }), disabled: !writable || !canSave, onConfirm: submit }), _jsx(ConfirmButton, { t: t, label: t("removeSecret"), title: t("confirmTitle"), description: t("confirmRemoveSecret", { secret: name }), disabled: !writable || busy, onConfirm: () => onRemove(name) })] }));
33
33
  }
34
34
  export function SecretsSection(props) {
35
35
  const { t, secrets, busy, writable, onCreate, onRemove, onSet } = props;
@@ -60,12 +60,14 @@ export function SecretsSection(props) {
60
60
  flexWrap: "wrap",
61
61
  alignItems: "center",
62
62
  gap: "8px",
63
- }, children: [_jsx("div", { style: { ...sectionTitle, flex: 1 }, children: t("secretsTitle") }), _jsx(Button, { variant: "outline", size: "sm", disabled: !writable || busy, onClick: () => setOpen(true), children: t("createSecret") })] }), secrets.length === 0 ? (_jsx("p", { style: hint, children: t("none") })) : (secrets.map((secret) => (_jsx(DisclosureRow, { icon: _jsx("span", {}), title: secret.name, open: openSecret === secret.name, expandable: true, onToggle: () => setOpenSecret(openSecret === secret.name ? null : secret.name), children: _jsx("div", { style: wsBody, children: _jsx(SecretRow, { t: t, name: secret.name, busy: busy, writable: writable, onSet: onSet, onRemove: onRemove }) }) }, secret.name)))), _jsx(Modal, { open: open, onClose: () => setOpen(false), title: t("secretsTitle"), closeLabel: t("cancel"), footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => setOpen(false), children: t("cancel") }), _jsx(Button, { variant: "primary", size: "sm", disabled: !canCreate, onClick: submit, children: t("createSecret") })] }), children: _jsxs("div", { style: {
63
+ }, children: [_jsx("div", { style: { ...sectionTitle, flex: 1 }, children: t("secretsTitle") }), _jsx(Button, { variant: "outline", size: "sm", disabled: !writable || busy, onClick: () => setOpen(true), children: t("createSecret") })] }), secrets.length === 0 ? _jsx("p", { style: hint, children: t("none") }) : (secrets.map((secret) => (_jsx(DisclosureRow, { icon: _jsx("span", {}), title: secret.name, open: openSecret === secret.name, expandable: true, onToggle: () => setOpenSecret(openSecret === secret.name ? null : secret.name), children: _jsx("div", { style: wsBody, children: _jsx(SecretRow, { t: t, name: secret.name, busy: busy, writable: writable, onSet: onSet, onRemove: onRemove }) }) }, secret.name)))), _jsx(Modal, { open: open, onClose: () => setOpen(false), title: t("secretsTitle"), closeLabel: t("cancel"), footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => setOpen(false), children: t("cancel") }), _jsx(Button, { variant: "primary", size: "sm", disabled: !canCreate, onClick: submit, children: t("createSecret") })] }), children: _jsxs("div", { style: {
64
64
  display: "flex",
65
65
  flexDirection: "column",
66
66
  gap: "12px",
67
67
  }, children: [_jsxs(Field, { label: t("secretName"), htmlFor: secretNameId, children: [_jsx(Input, { id: secretNameId, value: name, disabled: !writable || busy, autoFocus: true, onChange: (event) => setName(sanitizeName(event.target.value)), onKeyDown: (event) => {
68
68
  if (event.key === "Enter")
69
69
  submit();
70
- } }), name !== "" && !namePattern.test(name) ? (_jsx("p", { style: { ...hint, margin: 0 }, children: t("invalidName") })) : null] }), _jsx(Field, { label: t("secretLength"), htmlFor: secretLengthId, children: _jsx(Input, { id: secretLengthId, type: "number", min: 1, max: 1024, value: length, disabled: !writable || busy, onChange: (event) => setLength(event.target.value) }) }), _jsx(Field, { label: t("secretCharset"), htmlFor: secretCharsetId, children: _jsxs("select", { id: secretCharsetId, style: imageSelect, value: charset, disabled: !writable || busy, onChange: (event) => setCharset(event.target.value), children: [_jsx("option", { value: "alphanumeric", children: "alphanumeric" }), _jsx("option", { value: "hex", children: "hex" }), _jsx("option", { value: "base64url", children: "base64url" })] }) })] }) })] }));
70
+ } }), name !== "" && !namePattern.test(name)
71
+ ? _jsx("p", { style: { ...hint, margin: 0 }, children: t("invalidName") })
72
+ : null] }), _jsx(Field, { label: t("secretLength"), htmlFor: secretLengthId, children: _jsx(Input, { id: secretLengthId, type: "number", min: 1, max: 1024, value: length, disabled: !writable || busy, onChange: (event) => setLength(event.target.value) }) }), _jsx(Field, { label: t("secretCharset"), htmlFor: secretCharsetId, children: _jsxs("select", { id: secretCharsetId, style: imageSelect, value: charset, disabled: !writable || busy, onChange: (event) => setCharset(event.target.value), children: [_jsx("option", { value: "alphanumeric", children: "alphanumeric" }), _jsx("option", { value: "hex", children: "hex" }), _jsx("option", { value: "base64url", children: "base64url" })] }) })] }) })] }));
71
73
  }
@@ -1,21 +1,8 @@
1
1
  import { type MountInput, type ProjectMountView } from "./container-card-controller.js";
2
- import { type ContainerPluginKey } from "./locales.js";
2
+ import { type ContainerPluginKey, type Translate } from "./locales.js";
3
3
  import { type StateDotState } from "@deepseek-ai/dsh-client-ui-primitives";
4
4
  import { type ReactNode } from "react";
5
- export declare function CardChevron({ open }: {
6
- open: boolean;
7
- }): ReactNode;
8
5
  export declare function containerStateDot(status: string): StateDotState;
9
- export declare function ConfigField(props: {
10
- t: (key: ContainerPluginKey) => string;
11
- label: string;
12
- value: string;
13
- current: string;
14
- writable: boolean;
15
- onChange: (text: string) => void;
16
- onSave: () => void;
17
- onDiscard: () => void;
18
- }): ReactNode;
19
6
  export declare function Field(props: {
20
7
  label: string;
21
8
  htmlFor: string;
@@ -31,7 +18,7 @@ export declare const mountViewToInput: (mount: ProjectMountView) => MountInput;
31
18
  export declare const mountLabel: (t: (key: ContainerPluginKey) => string, mount: MountInput, withMode?: boolean) => string;
32
19
  export declare const emptyMount: (kind?: string) => MountInput;
33
20
  export declare function ConfirmButton(props: {
34
- t: (key: ContainerPluginKey) => string;
21
+ t: Translate;
35
22
  label: string;
36
23
  title: string;
37
24
  description: string;
@@ -40,13 +27,13 @@ export declare function ConfirmButton(props: {
40
27
  onConfirm: () => void;
41
28
  }): ReactNode;
42
29
  export declare function Chip(props: {
43
- t: (key: ContainerPluginKey) => string;
30
+ t: Translate;
44
31
  label: string;
45
32
  disabled?: boolean;
46
33
  onRemove?: () => void;
47
34
  }): ReactNode;
48
35
  export declare function TagInput(props: {
49
- t: (key: ContainerPluginKey) => string;
36
+ t: Translate;
50
37
  value: readonly string[];
51
38
  onChange: (tags: string[]) => void;
52
39
  placeholder?: string;
@@ -3,31 +3,11 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
3
3
  //
4
4
  // SPDX-License-Identifier: MIT
5
5
  import { hint } from "./container-card-styles.js";
6
- import { Button, Input, Modal } from "@deepseek-ai/dsh-client-ui-primitives";
7
- import { useId, useState } from "react";
8
- export function CardChevron({ open }) {
9
- return (_jsx("svg", { viewBox: "0 0 14 14", width: "14", height: "14", "aria-hidden": "true", style: {
10
- flex: "none",
11
- color: "var(--dsw-alias-label-tertiary)",
12
- transition: "transform .16s",
13
- transform: open ? "rotate(180deg)" : undefined,
14
- }, children: _jsx("path", { d: "M3 5l4 4 4-4", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }));
15
- }
6
+ import { Button, Input, Modal, } from "@deepseek-ai/dsh-client-ui-primitives";
7
+ import { useState } from "react";
16
8
  export function containerStateDot(status) {
17
9
  return status === "running" ? "done" : "error";
18
10
  }
19
- export function ConfigField(props) {
20
- const { t, label, value, current, writable, onChange, onSave, onDiscard } = props;
21
- const id = useId();
22
- const dirty = value !== current;
23
- return (_jsxs("div", { style: {
24
- display: "flex",
25
- flexWrap: "wrap",
26
- alignItems: "center",
27
- gap: "8px",
28
- padding: "8px 0",
29
- }, children: [_jsx("label", { htmlFor: id, style: { fontSize: "13px", color: "var(--dsw-alias-label-secondary)", minWidth: "110px" }, children: label }), _jsx(Input, { id: id, value: value, disabled: !writable, onChange: (event) => onChange(event.target.value), style: { width: "200px" } }), _jsx(Button, { variant: "outline", size: "sm", disabled: !writable || !dirty, onClick: onSave, children: t("save") }), _jsx(Button, { variant: "ghost", size: "sm", disabled: !dirty, onClick: onDiscard, children: t("discard") })] }));
30
- }
31
11
  export function Field(props) {
32
12
  return (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "4px" }, children: [_jsx("label", { htmlFor: props.htmlFor, style: { fontSize: "13px", color: "var(--dsw-alias-label-secondary)" }, children: props.label }), props.children] }));
33
13
  }
@@ -55,7 +35,7 @@ export const mountViewToInput = (mount) => ({
55
35
  });
56
36
  export const mountLabel = (t, mount, withMode = true) => {
57
37
  if (mount.kind === "tmpfs") {
58
- return `tmpfs${mount.destination !== "" ? ` at ${mount.destination}` : ""}`;
38
+ return `${t("mountTmpfs")}${mount.destination !== "" ? ` ${t("mountAt")} ${mount.destination}` : ""}`;
59
39
  }
60
40
  if (mount.kind === "volume") {
61
41
  return `volume ${mount.volume}${mount.destination !== "" ? ` → ${mount.destination}` : ""}`;
@@ -63,7 +43,11 @@ export const mountLabel = (t, mount, withMode = true) => {
63
43
  if (mount.kind === "secret") {
64
44
  return `secret ${mount.secret}${mount.destination !== "" ? ` → ${mount.destination}` : ""}`;
65
45
  }
66
- const mode = withMode ? (mount.mode === "read_only" ? " (ro)" : " (rw)") : "";
46
+ const mode = withMode
47
+ ? ` ${t(mount.mode === "read_only"
48
+ ? "mountReadOnlySuffix"
49
+ : "mountReadWriteSuffix")}`
50
+ : "";
67
51
  return `${mount.project}${mount.destination !== "" ? ` → ${mount.destination}` : ""}${mode}`;
68
52
  };
69
53
  // Adding a mount defaults to read-only, so granting write access is always a
@@ -99,7 +83,7 @@ export function Chip(props) {
99
83
  fontSize: "12px",
100
84
  fontFamily: "var(--dsw-alias-font-mono, ui-monospace, monospace)",
101
85
  color: "var(--dsw-alias-label-primary)",
102
- }, children: [label, onRemove === undefined ? null : (_jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("removeTag"), disabled: disabled, onClick: onRemove, style: {
86
+ }, children: [label, onRemove === undefined ? null : (_jsx(Button, { variant: "ghost", size: "sm", "aria-label": `${t("removeTag")} ${label}`, disabled: disabled, onClick: onRemove, style: {
103
87
  padding: 0,
104
88
  minWidth: "18px",
105
89
  height: "18px",
@@ -1,15 +1,8 @@
1
- export declare const cardStyle: React.CSSProperties;
2
- export declare const cardOpenStyle: React.CSSProperties;
3
- export declare const cardHoverStyle: React.CSSProperties;
4
- export declare const cardHeaderStyle: React.CSSProperties;
5
- export declare const cardHeadTextStyle: React.CSSProperties;
6
- export declare const cardNameStyle: React.CSSProperties;
7
- export declare const cardDescriptionStyle: React.CSSProperties;
8
- export declare const cardBodyStyle: React.CSSProperties;
9
1
  export declare const sectionTitle: React.CSSProperties;
10
2
  export declare const banner: React.CSSProperties;
11
3
  export declare const greyId: React.CSSProperties;
12
4
  export declare const hint: React.CSSProperties;
5
+ export declare const fieldLabel: React.CSSProperties;
13
6
  export declare const wsBody: React.CSSProperties;
14
7
  export declare const containerRow: React.CSSProperties;
15
8
  export declare const containerHeader: React.CSSProperties;
@@ -19,3 +12,4 @@ export declare const tableStyle: React.CSSProperties;
19
12
  export declare const thStyle: React.CSSProperties;
20
13
  export declare const tdStyle: React.CSSProperties;
21
14
  export declare const imageSelect: React.CSSProperties;
15
+ export declare const fieldSelect: React.CSSProperties;
@@ -1,58 +1,6 @@
1
1
  // SPDX-FileCopyrightText: 2026 Elouan Martinet <exa@elou.world>
2
2
  //
3
3
  // SPDX-License-Identifier: MIT
4
- export const cardStyle = {
5
- listStyle: "none",
6
- border: "1px solid var(--dsw-alias-border-l2)",
7
- borderRadius: "12px",
8
- background: "var(--dsw-alias-bg-layer-3)",
9
- transition: "border-color .16s, background .16s",
10
- };
11
- export const cardOpenStyle = {
12
- background: "var(--dsw-alias-bg-layer-2)",
13
- borderColor: "var(--dsw-alias-label-dimmed)",
14
- };
15
- export const cardHoverStyle = {
16
- borderColor: "var(--dsw-alias-label-dimmed)",
17
- };
18
- export const cardHeaderStyle = {
19
- width: "100%",
20
- appearance: "none",
21
- border: 0,
22
- background: "none",
23
- font: "inherit",
24
- color: "inherit",
25
- textAlign: "left",
26
- cursor: "pointer",
27
- display: "flex",
28
- alignItems: "center",
29
- gap: "12px",
30
- padding: "14px 16px",
31
- borderRadius: "12px",
32
- };
33
- export const cardHeadTextStyle = {
34
- flex: 1,
35
- minWidth: 0,
36
- display: "flex",
37
- flexDirection: "column",
38
- gap: "4px",
39
- };
40
- export const cardNameStyle = {
41
- fontSize: "15px",
42
- fontWeight: 600,
43
- lineHeight: 1.4,
44
- color: "var(--dsw-alias-label-primary)",
45
- };
46
- export const cardDescriptionStyle = {
47
- fontSize: "13px",
48
- lineHeight: 1.5,
49
- color: "var(--dsw-alias-label-tertiary)",
50
- };
51
- export const cardBodyStyle = {
52
- borderTop: "1px solid var(--dsw-alias-border-l2)",
53
- margin: "0 16px",
54
- paddingBottom: "8px",
55
- };
56
4
  export const sectionTitle = {
57
5
  fontWeight: 600,
58
6
  margin: "16px 0 8px",
@@ -78,6 +26,14 @@ export const hint = {
78
26
  color: "var(--dsw-alias-label-tertiary)",
79
27
  margin: "8px 0",
80
28
  };
29
+ export const fieldLabel = {
30
+ fontSize: "12px",
31
+ color: "var(--dsw-alias-label-tertiary)",
32
+ whiteSpace: "nowrap",
33
+ // Hugs the control when the label fills a grid cell; an auto-width flex item
34
+ // (the tab body's toolbar) is unaffected.
35
+ textAlign: "right",
36
+ };
81
37
  export const wsBody = {
82
38
  display: "flex",
83
39
  flexDirection: "column",
@@ -141,3 +97,9 @@ export const imageSelect = {
141
97
  fontSize: "12px",
142
98
  maxWidth: "160px",
143
99
  };
100
+ // A select that fills its label's row where fields are stacked; the narrow
101
+ // guide card uses it so the container and shell controls share one width.
102
+ export const fieldSelect = {
103
+ ...imageSelect,
104
+ width: "100%",
105
+ };
@@ -1,7 +1,7 @@
1
- import { type ContainerPluginKey } from "./locales.js";
1
+ import type { Translate } from "./locales.js";
2
2
  import { type ReactNode } from "react";
3
3
  export declare function VolumesSection(props: {
4
- t: (key: ContainerPluginKey) => string;
4
+ t: Translate;
5
5
  volumes: readonly {
6
6
  name: string;
7
7
  }[];
@@ -2,9 +2,9 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
2
2
  // SPDX-FileCopyrightText: 2026 Elouan Martinet <exa@elou.world>
3
3
  //
4
4
  // SPDX-License-Identifier: MIT
5
- import { ConfirmButton, Field, namePattern, sanitizeName } from "./container-card-shared.js";
5
+ import { ConfirmButton, Field, namePattern, sanitizeName, } from "./container-card-shared.js";
6
6
  import { hint, sectionTitle, wsBody } from "./container-card-styles.js";
7
- import { Button, DisclosureRow, Input, Modal } from "@deepseek-ai/dsh-client-ui-primitives";
7
+ import { Button, DisclosureRow, Input, Modal, } from "@deepseek-ai/dsh-client-ui-primitives";
8
8
  import { useId, useState } from "react";
9
9
  export function VolumesSection(props) {
10
10
  const { t, volumes, busy, writable, onCreate, onRemove } = props;
@@ -25,13 +25,17 @@ export function VolumesSection(props) {
25
25
  flexWrap: "wrap",
26
26
  alignItems: "center",
27
27
  gap: "8px",
28
- }, children: [_jsx("div", { style: { ...sectionTitle, flex: 1 }, children: t("volumesTitle") }), _jsx(Button, { variant: "outline", size: "sm", disabled: !writable || busy, onClick: () => setOpen(true), children: t("createVolume") })] }), volumes.length === 0 ? (_jsx("p", { style: hint, children: t("none") })) : (volumes.map((volume) => (_jsx(DisclosureRow, { icon: _jsx("span", {}), title: volume.name, open: openVolume === volume.name, expandable: true, onToggle: () => setOpenVolume(openVolume === volume.name ? null : volume.name), children: _jsx("div", { style: wsBody, children: _jsx("div", { style: {
28
+ }, children: [_jsx("div", { style: { ...sectionTitle, flex: 1 }, children: t("volumesTitle") }), _jsx(Button, { variant: "outline", size: "sm", disabled: !writable || busy, onClick: () => setOpen(true), children: t("createVolume") })] }), volumes.length === 0 ? _jsx("p", { style: hint, children: t("none") }) : (volumes.map((volume) => (_jsx(DisclosureRow, { icon: _jsx("span", {}), title: volume.name, open: openVolume === volume.name, expandable: true, onToggle: () => setOpenVolume(openVolume === volume.name ? null : volume.name), children: _jsx("div", { style: wsBody, children: _jsx("div", { style: {
29
29
  display: "flex",
30
30
  flexWrap: "wrap",
31
31
  alignItems: "center",
32
32
  gap: "8px",
33
- }, children: _jsx(ConfirmButton, { t: t, label: t("removeVolume"), title: t("confirmTitle"), description: t("confirmRemoveVolume"), disabled: busy, onConfirm: () => onRemove(volume.name) }) }) }) }, volume.name)))), _jsx(Modal, { open: open, onClose: () => setOpen(false), title: t("volumesTitle"), closeLabel: t("cancel"), footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => setOpen(false), children: t("cancel") }), _jsx(Button, { variant: "primary", size: "sm", disabled: !canCreate, onClick: submit, children: t("createVolume") })] }), children: _jsxs(Field, { label: t("volumeName"), htmlFor: volumeId, children: [_jsx(Input, { id: volumeId, value: name, disabled: !writable || busy, autoFocus: true, onChange: (event) => setName(sanitizeName(event.target.value)), onKeyDown: (event) => {
33
+ }, children: _jsx(ConfirmButton, { t: t, label: t("removeVolume"), title: t("confirmTitle"), description: t("confirmRemoveVolume", {
34
+ volume: volume.name,
35
+ }), disabled: busy, onConfirm: () => onRemove(volume.name) }) }) }) }, volume.name)))), _jsx(Modal, { open: open, onClose: () => setOpen(false), title: t("volumesTitle"), closeLabel: t("cancel"), footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => setOpen(false), children: t("cancel") }), _jsx(Button, { variant: "primary", size: "sm", disabled: !canCreate, onClick: submit, children: t("createVolume") })] }), children: _jsxs(Field, { label: t("volumeName"), htmlFor: volumeId, children: [_jsx(Input, { id: volumeId, value: name, disabled: !writable || busy, autoFocus: true, onChange: (event) => setName(sanitizeName(event.target.value)), onKeyDown: (event) => {
34
36
  if (event.key === "Enter")
35
37
  submit();
36
- } }), name !== "" && !namePattern.test(name) ? (_jsx("p", { style: { ...hint, margin: 0 }, children: t("invalidName") })) : null] }) })] }));
38
+ } }), name !== "" && !namePattern.test(name)
39
+ ? _jsx("p", { style: { ...hint, margin: 0 }, children: t("invalidName") })
40
+ : null] }) })] }));
37
41
  }
@@ -1,9 +1,9 @@
1
1
  import { type ContainerCreateConfig, type ContainerView, type ImageView, type MountInput, type WorkspaceView } from "./container-card-controller.js";
2
2
  import { type DirectoryPickerFace } from "./directory-picker.js";
3
- import { type ContainerPluginKey } from "./locales.js";
3
+ import type { Translate } from "./locales.js";
4
4
  import { type ReactNode } from "react";
5
5
  export declare function WorkspaceSection(props: {
6
- t: (key: ContainerPluginKey) => string;
6
+ t: Translate;
7
7
  workspace: WorkspaceView;
8
8
  containers: readonly ContainerView[];
9
9
  images: readonly ImageView[];
@@ -17,15 +17,17 @@ export declare function WorkspaceSection(props: {
17
17
  defaultImage: string;
18
18
  projectsRoot: string;
19
19
  directoryPicker?: DirectoryPickerFace;
20
- onRemove: (workspace: string) => void;
20
+ onRemove: (workspace: string, container: string) => void;
21
21
  onRemoveWorkspace: (workspace: string) => void;
22
- onRecreate: (workspace: string, image: string, env?: Record<string, string>) => void;
22
+ onRecreate: (workspace: string, container: string, image: string, env?: Record<string, string>) => void;
23
23
  onCreate: (workspace: WorkspaceView, config?: ContainerCreateConfig) => void;
24
24
  onStartContainer: (workspace: WorkspaceView, container: string, config?: ContainerCreateConfig) => void;
25
25
  onAddContainerMount: (workspace: string, container: string, mount: MountInput) => void;
26
26
  onRemoveContainerMount: (workspace: string, container: string, mount: MountInput) => void;
27
27
  onUpdateContainerMount: (workspace: string, container: string, mount: MountInput) => void;
28
28
  onSetContainerPaths: (workspace: string, container: string, paths: readonly string[]) => void;
29
- onAddContainerSecret: (workspace: string, envVar: string, secret: string) => void;
30
- onRemoveContainerSecret: (workspace: string, envVar: string) => void;
29
+ onAddContainerSecret: (workspace: string, container: string, envVar: string, secret: string) => void;
30
+ onRemoveContainerSecret: (workspace: string, container: string, envVar: string) => void;
31
+ onSyncDefaults: (workspace: string) => void;
32
+ defaultEnvCount: number;
31
33
  }): ReactNode;
@@ -5,14 +5,20 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
5
5
  import { ContainerRow, CreateContainerModal } from "./container-card-row.js";
6
6
  import { ConfirmButton, containerStateDot } from "./container-card-shared.js";
7
7
  import { greyId, hint, wsBody } from "./container-card-styles.js";
8
- import { Button, DisclosureRow, StateDot } from "@deepseek-ai/dsh-client-ui-primitives";
8
+ import { Button, DisclosureRow, StateDot, } from "@deepseek-ai/dsh-client-ui-primitives";
9
9
  import { useState } from "react";
10
10
  export function WorkspaceSection(props) {
11
- const { t, workspace, containers, images, volumes, secrets, busy, defaultImage, projectsRoot, directoryPicker, onRemove, onRemoveWorkspace, onRecreate, onCreate, onStartContainer, onAddContainerMount, onRemoveContainerMount, onUpdateContainerMount, onSetContainerPaths, onAddContainerSecret, onRemoveContainerSecret, } = props;
11
+ const { t, workspace, containers, images, volumes, secrets, busy, defaultImage, projectsRoot, directoryPicker, onRemove, onRemoveWorkspace, onRecreate, onCreate, onStartContainer, onAddContainerMount, onRemoveContainerMount, onUpdateContainerMount, onSetContainerPaths, onAddContainerSecret, onRemoveContainerSecret, onSyncDefaults, defaultEnvCount, } = props;
12
12
  const [open, setOpen] = useState(false);
13
13
  const [createOpen, setCreateOpen] = useState(null);
14
14
  const hasContainer = containers.length > 0;
15
- return (_jsxs(DisclosureRow, { icon: hasContainer ? (_jsx(StateDot, { state: containerStateDot(containers[0]?.status ?? "") })) : (_jsx("span", {})), title: workspace.projectName, open: open, expandable: true, onToggle: () => setOpen(!open), children: [_jsxs("div", { style: wsBody, children: [_jsx("code", { style: greyId, children: workspace.workspaceSlug }), !hasContainer ? (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [_jsx("p", { style: { ...hint, margin: 0 }, children: t("noContainers") }), _jsx("div", { children: _jsx(Button, { variant: "outline", size: "sm", disabled: busy, onClick: () => setCreateOpen("default"), children: t("createContainer") }) })] })) : (_jsxs(_Fragment, { children: [containers.map((container) => (_jsx(ContainerRow, { t: t, container: container, images: images, volumes: volumes, secrets: secrets, busy: busy, onRemove: onRemove, onRecreate: onRecreate, onAddContainerMount: onAddContainerMount, onRemoveContainerMount: onRemoveContainerMount, onUpdateContainerMount: onUpdateContainerMount, onSetContainerPaths: onSetContainerPaths, projectName: workspace.projectName, projectsRoot: projectsRoot, directoryPicker: directoryPicker, onAddContainerSecret: onAddContainerSecret, onRemoveContainerSecret: onRemoveContainerSecret }, container.containerName))), _jsx("div", { children: _jsx(Button, { variant: "outline", size: "sm", disabled: busy, onClick: () => setCreateOpen("named"), children: t("addContainer") }) })] })), _jsx("div", { children: _jsx(ConfirmButton, { t: t, label: t("removePod"), title: t("confirmTitle"), description: t("confirmRemovePod"), disabled: busy, onConfirm: () => onRemoveWorkspace(workspace.workspaceSlug) }) })] }), _jsx(CreateContainerModal, { t: t, workspace: workspace, images: images, volumes: volumes, secrets: secrets, busy: busy, defaultImage: defaultImage, projectsRoot: projectsRoot, directoryPicker: directoryPicker, named: createOpen === "named", existing: containers.map((container) => container.containerName), open: createOpen !== null, onClose: () => setCreateOpen(null), onCreate: (container, config) => {
15
+ return (_jsxs(DisclosureRow, { icon: hasContainer
16
+ ? _jsx(StateDot, { state: containerStateDot(containers[0]?.status ?? "") })
17
+ : _jsx("span", {}), title: workspace.projectName, open: open, expandable: true, onToggle: () => setOpen(!open), children: [_jsxs("div", { style: wsBody, children: [_jsx("code", { style: greyId, children: workspace.workspaceSlug }), !hasContainer
18
+ ? (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [_jsx("p", { style: { ...hint, margin: 0 }, children: t("noContainers") }), _jsx("div", { children: _jsx(Button, { variant: "outline", size: "sm", disabled: busy, onClick: () => setCreateOpen("default"), children: t("createContainer") }) })] }))
19
+ : (_jsxs(_Fragment, { children: [containers.map((container) => (_jsx(ContainerRow, { t: t, container: container, images: images, volumes: volumes, secrets: secrets, busy: busy, onRemove: onRemove, onRecreate: onRecreate, onAddContainerMount: onAddContainerMount, onRemoveContainerMount: onRemoveContainerMount, onUpdateContainerMount: onUpdateContainerMount, onSetContainerPaths: onSetContainerPaths, projectName: workspace.projectName, projectsRoot: projectsRoot, directoryPicker: directoryPicker, onAddContainerSecret: onAddContainerSecret, onRemoveContainerSecret: onRemoveContainerSecret }, container.containerName))), _jsx("div", { children: _jsx(Button, { variant: "outline", size: "sm", disabled: busy, onClick: () => setCreateOpen("named"), children: t("addContainer") }) })] })), _jsxs("div", { style: { display: "flex", flexWrap: "wrap", gap: "8px" }, children: [_jsx(ConfirmButton, { t: t, label: t("applyDefaults"), title: t("confirmTitle"), description: t("confirmApplyDefaults"), disabled: busy || defaultEnvCount === 0, onConfirm: () => onSyncDefaults(workspace.workspaceSlug) }), _jsx(ConfirmButton, { t: t, label: t("removePod"), title: t("confirmTitle"), description: t("confirmRemovePod", {
20
+ workspace: workspace.projectName,
21
+ }), disabled: busy, onConfirm: () => onRemoveWorkspace(workspace.workspaceSlug) })] })] }), _jsx(CreateContainerModal, { t: t, workspace: workspace, images: images, volumes: volumes, secrets: secrets, busy: busy, defaultImage: defaultImage, projectsRoot: projectsRoot, directoryPicker: directoryPicker, named: createOpen === "named", existing: containers.map((container) => container.containerName), open: createOpen !== null, onClose: () => setCreateOpen(null), onCreate: (container, config) => {
16
22
  if (createOpen === "named") {
17
23
  onStartContainer(workspace, container, config);
18
24
  }
@@ -17,8 +17,9 @@ export class DirectoryPickerError extends Error {
17
17
  // then hides the browse affordance and the path stays a plain input).
18
18
  export function buildDirectoryPicker(remote) {
19
19
  const picker = remote?.directoryPicker;
20
- if (picker === undefined || typeof picker.list !== "function")
20
+ if (picker === undefined || typeof picker.list !== "function") {
21
21
  return undefined;
22
+ }
22
23
  return {
23
24
  async list(path, signal) {
24
25
  const result = await picker.list(path, signal);