@woosau/dsh-custom-env 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js ADDED
@@ -0,0 +1,864 @@
1
+ window.__ModuleLoader__.load({ id: "@woosau/dsh-custom-env", factory: (require) => {
2
+ var module = { exports: {} };
3
+ var exports = module.exports;
4
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
+
23
+ // src/client/index.ts
24
+ var index_exports = {};
25
+ __export(index_exports, {
26
+ apply: () => apply,
27
+ inject: () => inject
28
+ });
29
+ module.exports = __toCommonJS(index_exports);
30
+
31
+ // lib/typert.remote-client.js
32
+ var import_zod = require("zod");
33
+
34
+ // ../vertax-work/packages/typert/protocol/lib/types/remote-info.js
35
+ function createTypertRemoteInfo(spec, zod) {
36
+ return new RemoteInfo(spec, zod);
37
+ }
38
+ var RemoteInfo = class {
39
+ spec;
40
+ zod;
41
+ index;
42
+ constructor(spec, zod) {
43
+ this.spec = spec;
44
+ this.zod = zod;
45
+ }
46
+ get package() {
47
+ return this.spec.package;
48
+ }
49
+ get descriptors() {
50
+ return this.resolve().descriptors;
51
+ }
52
+ get methods() {
53
+ return this.resolve().methods;
54
+ }
55
+ resolve() {
56
+ if (this.index !== void 0)
57
+ return this.index;
58
+ const zod = this.zod;
59
+ const descriptors = [];
60
+ const methods = /* @__PURE__ */ Object.create(null);
61
+ for (const [endpoint, definition] of Object.entries(this.spec.methods)) {
62
+ const separator = endpoint.indexOf("/");
63
+ const namespace = endpoint.slice(0, separator);
64
+ const method = endpoint.slice(separator + 1);
65
+ const parameters = definition.parameters.map((parameter) => ({ ...parameter, wire: parameter.wire ?? parameter.name }));
66
+ const descriptor = {
67
+ ...definition,
68
+ id: `${this.spec.package}#${endpoint}`,
69
+ namespace,
70
+ method,
71
+ parameters,
72
+ result: definition.result.description === void 0 ? definition.result : new ResultValue(definition.result)
73
+ };
74
+ if (definition.invocation.kind === "direct") {
75
+ const inputs = [];
76
+ const direct = Object.assign(descriptor, {
77
+ input(options) {
78
+ const strict = options?.strict === true;
79
+ return inputs[strict ? 1 : 0] ??= new NamedInput(parameters, strict, zod);
80
+ },
81
+ invoke(remote, input, signal) {
82
+ const named = input;
83
+ const values = parameters.map((parameter) => named[parameter.wire]);
84
+ if (definition.cancellation !== void 0)
85
+ values.push(signal);
86
+ const receiver = Reflect.get(remote, namespace);
87
+ const call = Reflect.get(receiver, method);
88
+ return call.apply(receiver, values);
89
+ }
90
+ });
91
+ methods[endpoint] = direct;
92
+ }
93
+ descriptors.push(descriptor);
94
+ }
95
+ return this.index = { descriptors, methods };
96
+ }
97
+ };
98
+ function optional(parameter) {
99
+ return parameter.source === "json" && parameter.acceptsUndefined === true;
100
+ }
101
+ var NamedInput = class {
102
+ parameters;
103
+ strict;
104
+ zod;
105
+ schema;
106
+ projection;
107
+ constructor(parameters, strict, zod) {
108
+ this.parameters = parameters;
109
+ this.strict = strict;
110
+ this.zod = zod;
111
+ }
112
+ codec(parameter) {
113
+ return this.strict ? parameter.strictCodec : parameter.codec;
114
+ }
115
+ create = () => this.schema ??= this.zod.strictObject(Object.fromEntries(this.parameters.map((parameter) => {
116
+ const value = this.codec(parameter).create();
117
+ return [parameter.wire, optional(parameter) ? value.optional() : value.nonoptional()];
118
+ })));
119
+ get jsonSchema() {
120
+ return this.project().jsonSchema;
121
+ }
122
+ get jsonSchemaError() {
123
+ return this.project().jsonSchemaError;
124
+ }
125
+ project() {
126
+ if (this.projection !== void 0)
127
+ return this.projection;
128
+ const unavailable = this.parameters.find((parameter) => this.codec(parameter).jsonSchema === void 0);
129
+ if (unavailable !== void 0) {
130
+ return this.projection = { jsonSchemaError: this.codec(unavailable).jsonSchemaError ?? `No JSON Schema for parameter ${unavailable.wire}` };
131
+ }
132
+ return this.projection = {
133
+ jsonSchema: {
134
+ type: "object",
135
+ properties: Object.fromEntries(this.parameters.map((parameter) => [parameter.wire, {
136
+ ...this.codec(parameter).jsonSchema,
137
+ ...parameter.description === void 0 ? {} : { description: parameter.description }
138
+ }])),
139
+ required: this.parameters.filter((parameter) => !optional(parameter)).map((parameter) => parameter.wire),
140
+ additionalProperties: false
141
+ }
142
+ };
143
+ }
144
+ };
145
+ var ResultValue = class {
146
+ spec;
147
+ projection;
148
+ constructor(spec) {
149
+ this.spec = spec;
150
+ }
151
+ get mode() {
152
+ return this.spec.mode;
153
+ }
154
+ get typeSymbol() {
155
+ return this.spec.typeSymbol;
156
+ }
157
+ get create() {
158
+ return this.spec.create;
159
+ }
160
+ get decode() {
161
+ return this.spec.decode;
162
+ }
163
+ get encode() {
164
+ return this.spec.encode;
165
+ }
166
+ get description() {
167
+ return this.spec.description;
168
+ }
169
+ get jsonSchemaError() {
170
+ return this.spec.jsonSchemaError;
171
+ }
172
+ get jsonSchema() {
173
+ if (this.projection === void 0) {
174
+ const schema = this.spec.jsonSchema;
175
+ this.projection = { value: schema === void 0 ? void 0 : { ...schema, description: this.spec.description } };
176
+ }
177
+ return this.projection.value;
178
+ }
179
+ };
180
+
181
+ // lib/typert.remote-client.js
182
+ var list$result$schema$value;
183
+ var list$result$schema = () => list$result$schema$value ??= import_zod.z.object({
184
+ path: import_zod.z.string().readonly(),
185
+ exists: import_zod.z.boolean().readonly(),
186
+ entries: import_zod.z.array(import_zod.z.object({
187
+ id: import_zod.z.string().readonly(),
188
+ name: import_zod.z.string().readonly(),
189
+ value: import_zod.z.string().readonly(),
190
+ kind: import_zod.z.enum(["unchanged", "modified", "added"]).readonly(),
191
+ previous: import_zod.z.string().nullable().readonly()
192
+ }).readonly()).readonly()
193
+ });
194
+ var cliStatus$result$schema$value;
195
+ var cliStatus$result$schema = () => cliStatus$result$schema$value ??= import_zod.z.object({
196
+ platform: import_zod.z.string().readonly(),
197
+ activePath: import_zod.z.string().nullable().readonly(),
198
+ expectedPath: import_zod.z.string().nullable().readonly(),
199
+ expectedDirectory: import_zod.z.string().nullable().readonly(),
200
+ matches: import_zod.z.boolean().readonly(),
201
+ canInstall: import_zod.z.boolean().readonly(),
202
+ reason: import_zod.z.string().nullable().readonly()
203
+ });
204
+ var upsert$parameter$0$schema$value;
205
+ var upsert$parameter$0$schema = () => upsert$parameter$0$schema$value ??= import_zod.z.string();
206
+ var upsert$parameter$1$schema$value;
207
+ var upsert$parameter$1$schema = () => upsert$parameter$1$schema$value ??= import_zod.z.string();
208
+ var upsert$parameter$2$schema$value;
209
+ var upsert$parameter$2$schema = () => upsert$parameter$2$schema$value ??= import_zod.z.string();
210
+ var remove$parameter$0$schema$value;
211
+ var remove$parameter$0$schema = () => remove$parameter$0$schema$value ??= import_zod.z.string();
212
+ var restore$parameter$0$schema$value;
213
+ var restore$parameter$0$schema = () => restore$parameter$0$schema$value ??= import_zod.z.string();
214
+ var cliStatusJsonSchema = {
215
+ type: "object",
216
+ properties: {
217
+ platform: { type: "string" },
218
+ activePath: { type: ["string", "null"] },
219
+ expectedPath: { type: ["string", "null"] },
220
+ expectedDirectory: { type: ["string", "null"] },
221
+ matches: { type: "boolean" },
222
+ canInstall: { type: "boolean" },
223
+ reason: { type: ["string", "null"] }
224
+ },
225
+ required: ["platform", "activePath", "expectedPath", "expectedDirectory", "matches", "canInstall", "reason"],
226
+ additionalProperties: true
227
+ };
228
+ var envSnapshotJsonSchema = {
229
+ type: "object",
230
+ properties: {
231
+ path: { type: "string" },
232
+ exists: { type: "boolean" },
233
+ entries: {
234
+ type: "array",
235
+ items: {
236
+ type: "object",
237
+ properties: {
238
+ id: { type: "string" },
239
+ name: { type: "string" },
240
+ value: { type: "string" },
241
+ kind: { type: "string", enum: ["unchanged", "modified", "added"] },
242
+ previous: { type: ["string", "null"] }
243
+ },
244
+ required: ["id", "name", "value", "kind", "previous"],
245
+ additionalProperties: true
246
+ }
247
+ }
248
+ },
249
+ required: ["path", "exists", "entries"],
250
+ additionalProperties: true
251
+ };
252
+ var TYPERT_REMOTE = createTypertRemoteInfo({
253
+ package: "@woosau/dsh-custom-env",
254
+ methods: {
255
+ "customEnv/list": {
256
+ service: "customEnv",
257
+ description: "List current $DSH_HOME/.env entries with pending classification.",
258
+ invocation: { kind: "direct" },
259
+ parameters: [],
260
+ result: {
261
+ mode: "strict",
262
+ typeSymbol: "@woosau/dsh-custom-env/types#EnvSnapshot",
263
+ create: list$result$schema,
264
+ description: "path, existence, and sorted entries.",
265
+ jsonSchema: envSnapshotJsonSchema
266
+ },
267
+ sourceLocation: { file: "src/index.ts", line: 1, column: 1 }
268
+ },
269
+ "customEnv/upsert": {
270
+ service: "customEnv",
271
+ description: "Create or update one row. Empty id creates a new row; otherwise keeps that baseline id across renames.",
272
+ invocation: { kind: "direct" },
273
+ parameters: [
274
+ {
275
+ name: "id",
276
+ description: "stable row id from list(), or empty string for a new row.",
277
+ source: "json",
278
+ codec: {
279
+ mode: "strict",
280
+ typeSymbol: "string",
281
+ create: upsert$parameter$0$schema,
282
+ jsonSchema: { type: "string" }
283
+ }
284
+ },
285
+ {
286
+ name: "name",
287
+ description: "writable variable name.",
288
+ source: "json",
289
+ codec: {
290
+ mode: "strict",
291
+ typeSymbol: "string",
292
+ create: upsert$parameter$1$schema,
293
+ jsonSchema: { type: "string" }
294
+ }
295
+ },
296
+ {
297
+ name: "value",
298
+ description: "non-empty value.",
299
+ source: "json",
300
+ codec: {
301
+ mode: "strict",
302
+ typeSymbol: "string",
303
+ create: upsert$parameter$2$schema,
304
+ jsonSchema: { type: "string" }
305
+ }
306
+ }
307
+ ],
308
+ result: {
309
+ mode: "strict",
310
+ typeSymbol: "@woosau/dsh-custom-env/types#EnvSnapshot",
311
+ create: list$result$schema,
312
+ description: "updated snapshot.",
313
+ jsonSchema: envSnapshotJsonSchema
314
+ },
315
+ sourceLocation: { file: "src/index.ts", line: 1, column: 1 }
316
+ },
317
+ "customEnv/deleteEntry": {
318
+ service: "customEnv",
319
+ description: "Delete one entry from the file, Host, and OS user environment.",
320
+ invocation: { kind: "direct" },
321
+ parameters: [
322
+ {
323
+ name: "id",
324
+ description: "stable row id from list().",
325
+ source: "json",
326
+ codec: {
327
+ mode: "strict",
328
+ typeSymbol: "string",
329
+ create: remove$parameter$0$schema,
330
+ jsonSchema: { type: "string" }
331
+ }
332
+ }
333
+ ],
334
+ result: {
335
+ mode: "strict",
336
+ typeSymbol: "@woosau/dsh-custom-env/types#EnvSnapshot",
337
+ create: list$result$schema,
338
+ description: "updated snapshot.",
339
+ jsonSchema: envSnapshotJsonSchema
340
+ },
341
+ sourceLocation: { file: "src/index.ts", line: 1, column: 1 }
342
+ },
343
+ "customEnv/restore": {
344
+ service: "customEnv",
345
+ description: "Restore Host-start original name+value for this id, or delete if added after start.",
346
+ invocation: { kind: "direct" },
347
+ parameters: [
348
+ {
349
+ name: "id",
350
+ description: "stable row id from list().",
351
+ source: "json",
352
+ codec: {
353
+ mode: "strict",
354
+ typeSymbol: "string",
355
+ create: restore$parameter$0$schema,
356
+ jsonSchema: { type: "string" }
357
+ }
358
+ }
359
+ ],
360
+ result: {
361
+ mode: "strict",
362
+ typeSymbol: "@woosau/dsh-custom-env/types#EnvSnapshot",
363
+ create: list$result$schema,
364
+ description: "updated snapshot.",
365
+ jsonSchema: envSnapshotJsonSchema
366
+ },
367
+ sourceLocation: { file: "src/index.ts", line: 1, column: 1 }
368
+ },
369
+ "customEnv/cliStatus": {
370
+ service: "customEnv",
371
+ description: "Compare the user-environment dsh with the launcher expected for this Host.",
372
+ invocation: { kind: "direct" },
373
+ parameters: [],
374
+ result: {
375
+ mode: "strict",
376
+ typeSymbol: "@woosau/dsh-custom-env/types#CliStatus",
377
+ create: cliStatus$result$schema,
378
+ description: "active path, expected path, and whether Install should be offered.",
379
+ jsonSchema: cliStatusJsonSchema
380
+ },
381
+ sourceLocation: { file: "src/index.ts", line: 1, column: 1 }
382
+ },
383
+ "customEnv/cliInstall": {
384
+ service: "customEnv",
385
+ description: "Install the expected CLI onto the Windows user PATH or as /usr/local/bin/dsh on macOS.",
386
+ invocation: { kind: "direct" },
387
+ parameters: [],
388
+ result: {
389
+ mode: "strict",
390
+ typeSymbol: "@woosau/dsh-custom-env/types#CliStatus",
391
+ create: cliStatus$result$schema,
392
+ description: "refreshed status after install.",
393
+ jsonSchema: cliStatusJsonSchema
394
+ },
395
+ sourceLocation: { file: "src/index.ts", line: 1, column: 1 }
396
+ }
397
+ }
398
+ }, import_zod.z);
399
+ var typert_remote_client_default = TYPERT_REMOTE;
400
+
401
+ // src/client/EnvSettingsSection.tsx
402
+ var import_react = require("react");
403
+ var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
404
+
405
+ // src/client/EnvSettingsSection.module.css
406
+ var css = ".EnvSettingsSection_section {\n display: flex;\n flex-direction: column;\n gap: 16px;\n max-width: 760px;\n color: var(--dsw-alias-label-primary);\n}\n\n.EnvSettingsSection_heading {\n margin: 0;\n font-size: 18px;\n font-weight: 500;\n line-height: 1.3;\n}\n\n/* \u2014\u2014 dsh CLI: exactly two lines (label + path) \u2014\u2014 */\n.EnvSettingsSection_cli {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.EnvSettingsSection_cliHeading {\n margin: 0;\n font-size: 13px;\n font-weight: 500;\n line-height: 20px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.EnvSettingsSection_cliBody {\n display: grid;\n grid-template-columns: minmax(0, 1fr) 28px;\n gap: 8px;\n align-items: center;\n height: 28px;\n}\n\n.EnvSettingsSection_cliPath {\n margin: 0;\n min-width: 0;\n font-size: 13px;\n line-height: 28px;\n color: var(--dsw-alias-label-tertiary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.EnvSettingsSection_cliSlot {\n display: flex;\n width: 28px;\n height: 28px;\n align-items: center;\n justify-content: center;\n}\n\n.EnvSettingsSection_cliSlot[data-empty='true'] {\n visibility: hidden;\n pointer-events: none;\n}\n\n.EnvSettingsSection_notice,\n.EnvSettingsSection_empty {\n margin: 0;\n font-size: 13px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.EnvSettingsSection_notice[data-tone='error'] {\n color: var(--dsw-alias-label-danger, #c62828);\n}\n\n/* \u2014\u2014 variable list: CSS grid, not table (avoids Input overflow overlap) \u2014\u2014 */\n.EnvSettingsSection_list {\n display: flex;\n flex-direction: column;\n gap: 8px;\n min-width: 0;\n}\n\n.EnvSettingsSection_header,\n.EnvSettingsSection_row {\n display: grid;\n grid-template-columns: minmax(120px, 0.85fr) minmax(0, 1.55fr) 100px;\n gap: 10px;\n align-items: center;\n min-width: 0;\n}\n\n.EnvSettingsSection_header {\n padding: 0 0 2px;\n}\n\n.EnvSettingsSection_headerLabel {\n margin: 0;\n font-size: 12px;\n font-weight: 500;\n line-height: 1.3;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.EnvSettingsSection_headerActions {\n width: 100px;\n}\n\n.EnvSettingsSection_field {\n min-width: 0;\n overflow: hidden;\n}\n\n.EnvSettingsSection_fieldInput {\n display: flex;\n box-sizing: border-box;\n width: 100%;\n max-width: 100%;\n min-width: 0;\n}\n\n.EnvSettingsSection_actions {\n display: grid;\n grid-template-columns: repeat(3, 28px);\n gap: 4px;\n align-items: center;\n justify-content: end;\n width: 100px;\n margin-left: auto;\n flex-shrink: 0;\n}\n\n.EnvSettingsSection_slot {\n display: flex;\n width: 28px;\n height: 28px;\n align-items: center;\n justify-content: center;\n}\n\n.EnvSettingsSection_slot[data-empty='true'] {\n visibility: hidden;\n pointer-events: none;\n}\n\n.EnvSettingsSection_iconButton {\n display: inline-flex;\n width: 28px;\n height: 28px;\n margin: 0;\n padding: 0;\n align-items: center;\n justify-content: center;\n border: none;\n border-radius: var(--dsw-radius-sm, 6px);\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n}\n\n.EnvSettingsSection_iconButton:hover:not(:disabled) {\n background: var(--dsw-alias-fill-hover, rgba(127, 127, 127, 0.12));\n color: var(--dsw-alias-label-primary);\n}\n\n.EnvSettingsSection_iconButton:disabled {\n opacity: 0.45;\n cursor: default;\n}\n\n.EnvSettingsSection_iconButtonDanger:hover:not(:disabled) {\n color: var(--dsw-alias-label-danger, #c62828);\n}\n\n.EnvSettingsSection_iconButtonPrimary {\n color: var(--dsw-alias-brand-primary, #247bbf);\n}\n\n.EnvSettingsSection_restartBadge {\n display: inline-flex;\n width: 28px;\n height: 28px;\n align-items: center;\n justify-content: center;\n color: var(--dsw-alias-label-warning, #c67c00);\n}\n\n.EnvSettingsSection_spinner {\n animation: spin 1s linear infinite;\n}\n\n@keyframes spin {\n to { transform: rotate(360deg); }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .EnvSettingsSection_spinner {\n animation: none;\n }\n}\n";
407
+ if (typeof document !== "undefined" && !document.getElementById("dsh-custom-env-css")) {
408
+ const style = document.createElement("style");
409
+ style.id = "dsh-custom-env-css";
410
+ style.textContent = css;
411
+ document.head.appendChild(style);
412
+ }
413
+ var EnvSettingsSection_default = { "section": "EnvSettingsSection_section", "heading": "EnvSettingsSection_heading", "cli": "EnvSettingsSection_cli", "cliHeading": "EnvSettingsSection_cliHeading", "cliBody": "EnvSettingsSection_cliBody", "cliPath": "EnvSettingsSection_cliPath", "cliSlot": "EnvSettingsSection_cliSlot", "notice": "EnvSettingsSection_notice", "empty": "EnvSettingsSection_empty", "list": "EnvSettingsSection_list", "header": "EnvSettingsSection_header", "row": "EnvSettingsSection_row", "headerLabel": "EnvSettingsSection_headerLabel", "headerActions": "EnvSettingsSection_headerActions", "field": "EnvSettingsSection_field", "fieldInput": "EnvSettingsSection_fieldInput", "actions": "EnvSettingsSection_actions", "slot": "EnvSettingsSection_slot", "iconButton": "EnvSettingsSection_iconButton", "iconButtonDanger": "EnvSettingsSection_iconButtonDanger", "iconButtonPrimary": "EnvSettingsSection_iconButtonPrimary", "restartBadge": "EnvSettingsSection_restartBadge", "spinner": "EnvSettingsSection_spinner" };
414
+
415
+ // src/client/EnvSettingsSection.tsx
416
+ var import_jsx_runtime = require("react/jsx-runtime");
417
+ var NAME_OK = /^[A-Za-z_][A-Za-z0-9_]*$/u;
418
+ var NEW_ID = "";
419
+ function IconAction({
420
+ label,
421
+ disabled,
422
+ onClick,
423
+ danger,
424
+ primary,
425
+ children
426
+ }) {
427
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tooltip, { portal: true, label, side: "top", delayMs: 400, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
428
+ "button",
429
+ {
430
+ type: "button",
431
+ className: [EnvSettingsSection_default.iconButton, danger ? EnvSettingsSection_default.iconButtonDanger : "", primary ? EnvSettingsSection_default.iconButtonPrimary : ""].filter(Boolean).join(" "),
432
+ "aria-label": label,
433
+ title: label,
434
+ disabled,
435
+ onClick,
436
+ children
437
+ }
438
+ ) });
439
+ }
440
+ function Slot({ empty, children }) {
441
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: EnvSettingsSection_default.slot, "data-empty": empty ? "true" : "false", children: empty ? null : children });
442
+ }
443
+ function RowActions({
444
+ t,
445
+ busy,
446
+ loading,
447
+ dirty,
448
+ canConfirm,
449
+ pending = false,
450
+ showRemove = true,
451
+ onConfirm,
452
+ onCancel,
453
+ onRestore,
454
+ onRemove
455
+ }) {
456
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: EnvSettingsSection_default.actions, role: "group", "aria-label": t("actions"), children: [
457
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Slot, { empty: !dirty && !pending, children: dirty ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconAction, { label: loading ? t("loading") : t("confirm"), primary: true, disabled: busy || !canConfirm, onClick: onConfirm, children: loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconLoadingOutlineRegular, { className: EnvSettingsSection_default.spinner, size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconCheckOutlineRegular, { size: 14 }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tooltip, { portal: true, label: t("restartRequired"), side: "top", delayMs: 400, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: EnvSettingsSection_default.restartBadge, role: "img", "aria-label": t("restartRequired"), title: t("restartRequired"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconWarningOutlineRegular, { size: 14 }) }) }) }),
458
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Slot, { empty: !dirty && !pending, children: dirty ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconAction, { label: t("cancel"), disabled: busy, onClick: onCancel, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconCloseOutlineRegular, { size: 14 }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconAction, { label: t("restore"), disabled: busy, onClick: onRestore, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconRefreshOutlineRegular, { size: 14 }) }) }),
459
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Slot, { empty: !showRemove, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconAction, { label: t("remove"), danger: true, disabled: busy, onClick: onRemove, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconTrashOutlineRegular, { size: 14 }) }) })
460
+ ] });
461
+ }
462
+ function Field(props) {
463
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: EnvSettingsSection_default.field, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
464
+ import_dsh_client_ui_primitives.Input,
465
+ {
466
+ className: EnvSettingsSection_default.fieldInput,
467
+ id: props.id,
468
+ value: props.value,
469
+ disabled: props.disabled,
470
+ placeholder: props.placeholder,
471
+ title: props.title,
472
+ "aria-invalid": props.invalid || void 0,
473
+ "aria-label": props.label,
474
+ onChange: (event) => {
475
+ props.onChange(event.target.value);
476
+ }
477
+ }
478
+ ) });
479
+ }
480
+ function EnvSettingsSection({
481
+ t,
482
+ load,
483
+ upsert,
484
+ remove,
485
+ restore,
486
+ cliStatus,
487
+ cliInstall
488
+ }) {
489
+ const [snapshot, setSnapshot] = (0, import_react.useState)();
490
+ const [cli, setCli] = (0, import_react.useState)();
491
+ const [drafts, setDrafts] = (0, import_react.useState)({ [NEW_ID]: { name: "", value: "" } });
492
+ const [busyTarget, setBusyTarget] = (0, import_react.useState)();
493
+ const [error, setError] = (0, import_react.useState)();
494
+ const [nameError, setNameError] = (0, import_react.useState)(false);
495
+ const [pendingRemove, setPendingRemove] = (0, import_react.useState)();
496
+ const busy = busyTarget !== void 0;
497
+ const entries = snapshot?.entries ?? [];
498
+ const taken = new Set(entries.map((entry) => entry.name));
499
+ const applySnapshot = (next) => {
500
+ setSnapshot(next);
501
+ setDrafts({
502
+ [NEW_ID]: { name: "", value: "" },
503
+ ...Object.fromEntries(next.entries.map((entry) => [entry.id, { name: entry.name, value: entry.value }]))
504
+ });
505
+ };
506
+ const setDraft = (id, patch) => {
507
+ setDrafts((previous) => ({
508
+ ...previous,
509
+ [id]: { name: patch.name ?? previous[id]?.name ?? "", value: patch.value ?? previous[id]?.value ?? "" }
510
+ }));
511
+ };
512
+ const run = async (target, action) => {
513
+ if (busyTarget !== void 0) return;
514
+ setBusyTarget(target);
515
+ setError(void 0);
516
+ try {
517
+ applySnapshot(await action());
518
+ } catch {
519
+ setError(t("error"));
520
+ } finally {
521
+ setBusyTarget(void 0);
522
+ }
523
+ };
524
+ const refresh = (0, import_react.useCallback)(async () => {
525
+ setBusyTarget("boot");
526
+ setError(void 0);
527
+ try {
528
+ applySnapshot(await load());
529
+ try {
530
+ setCli(await cliStatus());
531
+ } catch {
532
+ setCli(void 0);
533
+ }
534
+ } catch {
535
+ setError(t("error"));
536
+ } finally {
537
+ setBusyTarget(void 0);
538
+ }
539
+ }, [load, cliStatus, t]);
540
+ (0, import_react.useEffect)(() => {
541
+ void refresh();
542
+ }, [refresh]);
543
+ const saveRow = (id, entryName) => {
544
+ const draft = drafts[id] ?? { name: "", value: "" };
545
+ const name = draft.name.trim();
546
+ const takenByOther = name !== entryName && taken.has(name);
547
+ if (!NAME_OK.test(name) || takenByOther) {
548
+ setNameError(true);
549
+ return;
550
+ }
551
+ if (draft.value === "") return;
552
+ setNameError(false);
553
+ const target = id === NEW_ID ? "new" : `row:${id}`;
554
+ void run(target, () => upsert(id, name, draft.value));
555
+ };
556
+ const newDraft = drafts[NEW_ID] ?? { name: "", value: "" };
557
+ const newDirty = newDraft.name.trim() !== "" || newDraft.value !== "";
558
+ const cliInstalling = busyTarget === "cli";
559
+ const cliBooting = busyTarget === "boot" || cli === void 0;
560
+ const showCliAction = cli?.canInstall === true || cliInstalling;
561
+ const notice = error ?? (nameError ? t("invalidName") : void 0) ?? (cli?.reason === "could-not-resolve-expected-cli" ? t("cliReasonUnresolved") : void 0) ?? (cli?.reason === "unsupported-platform" ? t("cliReasonUnsupported") : void 0);
562
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: EnvSettingsSection_default.section, children: [
563
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: EnvSettingsSection_default.heading, children: t("title") }),
564
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: EnvSettingsSection_default.cli, "aria-label": t("cliTitle"), children: [
565
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { className: EnvSettingsSection_default.cliHeading, children: t("cliTitle") }),
566
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: EnvSettingsSection_default.cliBody, children: [
567
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
568
+ "p",
569
+ {
570
+ className: EnvSettingsSection_default.cliPath,
571
+ title: cliBooting ? void 0 : cli.activePath ?? void 0,
572
+ "aria-busy": cliBooting || cliInstalling,
573
+ children: cliBooting ? t("loading") : cli.activePath === null ? t("cliActiveMissing") : cli.activePath
574
+ }
575
+ ),
576
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: EnvSettingsSection_default.cliSlot, "data-empty": showCliAction ? "false" : "true", children: showCliAction ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
577
+ IconAction,
578
+ {
579
+ label: cliInstalling ? t("loading") : cli?.platform === "darwin" ? t("cliMacConflict") : t("cliInstall"),
580
+ disabled: busy,
581
+ onClick: () => {
582
+ if (busyTarget !== void 0) return;
583
+ setBusyTarget("cli");
584
+ setError(void 0);
585
+ void (async () => {
586
+ try {
587
+ setCli(await cliInstall());
588
+ } catch {
589
+ setError(t("cliInstallError"));
590
+ } finally {
591
+ setBusyTarget(void 0);
592
+ }
593
+ })();
594
+ },
595
+ children: cliInstalling ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconLoadingOutlineRegular, { className: EnvSettingsSection_default.spinner, size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconLinkOutlineRegular, { size: 14 })
596
+ }
597
+ ) : null })
598
+ ] })
599
+ ] }),
600
+ notice !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: EnvSettingsSection_default.notice, "data-tone": error !== void 0 || nameError ? "error" : void 0, role: "status", children: notice }) : null,
601
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: EnvSettingsSection_default.list, children: [
602
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: EnvSettingsSection_default.header, children: [
603
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: EnvSettingsSection_default.headerLabel, children: t("name") }),
604
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: EnvSettingsSection_default.headerLabel, children: t("value") }),
605
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: EnvSettingsSection_default.headerActions, "aria-hidden": "true" })
606
+ ] }),
607
+ snapshot !== void 0 && entries.length === 0 && notice === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: EnvSettingsSection_default.empty, children: t("empty") }) : null,
608
+ entries.map((entry) => {
609
+ const draft = drafts[entry.id] ?? { name: entry.name, value: entry.value };
610
+ const dirty = draft.name !== entry.name || draft.value !== entry.value;
611
+ const pending = entry.kind === "added" || entry.kind === "modified";
612
+ const loading = busyTarget === `row:${entry.id}`;
613
+ const nextName = draft.name.trim();
614
+ const canConfirm = NAME_OK.test(nextName) && draft.value !== "" && !(nextName !== entry.name && taken.has(nextName));
615
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: EnvSettingsSection_default.row, children: [
616
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
617
+ Field,
618
+ {
619
+ id: `env-name-${entry.id}`,
620
+ value: draft.name,
621
+ disabled: busy,
622
+ title: draft.name,
623
+ invalid: nameError && dirty,
624
+ label: t("name"),
625
+ onChange: (name) => {
626
+ setNameError(false);
627
+ setDraft(entry.id, { name });
628
+ }
629
+ }
630
+ ),
631
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
632
+ Field,
633
+ {
634
+ id: `env-value-${entry.id}`,
635
+ value: draft.value,
636
+ disabled: busy,
637
+ title: draft.value,
638
+ label: t("value"),
639
+ onChange: (value) => {
640
+ setDraft(entry.id, { value });
641
+ }
642
+ }
643
+ ),
644
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
645
+ RowActions,
646
+ {
647
+ t,
648
+ busy,
649
+ loading,
650
+ dirty: dirty || loading,
651
+ canConfirm,
652
+ pending,
653
+ onConfirm: () => {
654
+ saveRow(entry.id, entry.name);
655
+ },
656
+ onCancel: () => {
657
+ setNameError(false);
658
+ setDraft(entry.id, { name: entry.name, value: entry.value });
659
+ },
660
+ onRestore: () => {
661
+ void run(`row:${entry.id}`, () => restore(entry.id));
662
+ },
663
+ onRemove: () => {
664
+ setPendingRemove({ id: entry.id, name: entry.name });
665
+ }
666
+ }
667
+ )
668
+ ] }, entry.id);
669
+ }),
670
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: EnvSettingsSection_default.row, children: [
671
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
672
+ Field,
673
+ {
674
+ id: "env-new-name",
675
+ value: newDraft.name,
676
+ disabled: busy,
677
+ placeholder: t("namePlaceholder"),
678
+ title: newDraft.name || t("namePlaceholder"),
679
+ invalid: nameError,
680
+ label: t("name"),
681
+ onChange: (name) => {
682
+ setNameError(false);
683
+ setDraft(NEW_ID, { name });
684
+ }
685
+ }
686
+ ),
687
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
688
+ Field,
689
+ {
690
+ id: "env-new-value",
691
+ value: newDraft.value,
692
+ disabled: busy,
693
+ placeholder: t("valuePlaceholder"),
694
+ title: newDraft.value || t("valuePlaceholder"),
695
+ label: t("value"),
696
+ onChange: (value) => {
697
+ setDraft(NEW_ID, { value });
698
+ }
699
+ }
700
+ ),
701
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
702
+ RowActions,
703
+ {
704
+ t,
705
+ busy,
706
+ loading: busyTarget === "new",
707
+ dirty: newDirty || busyTarget === "new",
708
+ canConfirm: NAME_OK.test(newDraft.name.trim()) && newDraft.value !== "" && !taken.has(newDraft.name.trim()),
709
+ showRemove: false,
710
+ onConfirm: () => {
711
+ saveRow(NEW_ID, void 0);
712
+ },
713
+ onCancel: () => {
714
+ setNameError(false);
715
+ setDraft(NEW_ID, { name: "", value: "" });
716
+ },
717
+ onRestore: () => void 0,
718
+ onRemove: () => void 0
719
+ }
720
+ )
721
+ ] })
722
+ ] }),
723
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
724
+ import_dsh_client_ui_primitives.Modal,
725
+ {
726
+ open: pendingRemove !== void 0,
727
+ onClose: () => {
728
+ if (!busy) setPendingRemove(void 0);
729
+ },
730
+ title: t("removeConfirmTitle"),
731
+ closeLabel: t("close"),
732
+ description: pendingRemove ? t("removeConfirm", { name: pendingRemove.name }) : void 0,
733
+ footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
734
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", disabled: busy, onClick: () => {
735
+ setPendingRemove(void 0);
736
+ }, children: t("cancel") }),
737
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
738
+ import_dsh_client_ui_primitives.Button,
739
+ {
740
+ variant: "primary",
741
+ disabled: busy,
742
+ onClick: () => {
743
+ if (pendingRemove === void 0 || busyTarget !== void 0) return;
744
+ const { id } = pendingRemove;
745
+ setPendingRemove(void 0);
746
+ void run(`row:${id}`, () => remove(id));
747
+ },
748
+ children: t("remove")
749
+ }
750
+ )
751
+ ] })
752
+ }
753
+ )
754
+ ] });
755
+ }
756
+
757
+ // src/client/locales.ts
758
+ var NS = "settings.env";
759
+ var en = {
760
+ nav: "Environment",
761
+ title: "Environment",
762
+ empty: "No variables",
763
+ name: "Name",
764
+ value: "Value",
765
+ actions: "Actions",
766
+ confirm: "Confirm",
767
+ cancel: "Cancel",
768
+ remove: "Delete",
769
+ removeConfirmTitle: "Delete variable",
770
+ removeConfirm: "Delete {name}?",
771
+ restore: "Restore original",
772
+ close: "Close",
773
+ namePlaceholder: "MY_VAR",
774
+ valuePlaceholder: "value",
775
+ loading: "Loading\u2026",
776
+ error: "Update failed",
777
+ invalidName: "Invalid name",
778
+ restartRequired: "Restart the client",
779
+ cliTitle: "dsh",
780
+ cliActiveMissing: "Not found",
781
+ cliInstall: "Install",
782
+ cliMacConflict: "Replaces /usr/local/bin/dsh",
783
+ cliInstallError: "Install failed",
784
+ cliReasonUnresolved: "Install unavailable",
785
+ cliReasonUnsupported: "Windows / macOS only"
786
+ };
787
+ var zh = {
788
+ nav: "\u73AF\u5883\u53D8\u91CF",
789
+ title: "\u73AF\u5883\u53D8\u91CF",
790
+ empty: "\u6682\u65E0\u53D8\u91CF",
791
+ name: "\u540D\u79F0",
792
+ value: "\u503C",
793
+ actions: "\u64CD\u4F5C",
794
+ confirm: "\u786E\u5B9A",
795
+ cancel: "\u53D6\u6D88",
796
+ remove: "\u5220\u9664",
797
+ removeConfirmTitle: "\u5220\u9664\u53D8\u91CF",
798
+ removeConfirm: "\u5220\u9664 {name}\uFF1F",
799
+ restore: "\u6062\u590D\u539F\u503C",
800
+ close: "\u5173\u95ED",
801
+ namePlaceholder: "MY_VAR",
802
+ valuePlaceholder: "\u503C",
803
+ loading: "\u52A0\u8F7D\u4E2D\u2026",
804
+ error: "\u66F4\u65B0\u5931\u8D25",
805
+ invalidName: "\u540D\u79F0\u65E0\u6548",
806
+ restartRequired: "\u9700\u91CD\u542F\u5BA2\u6237\u7AEF",
807
+ cliTitle: "dsh",
808
+ cliActiveMissing: "\u672A\u627E\u5230",
809
+ cliInstall: "\u5B89\u88C5",
810
+ cliMacConflict: "\u5C06\u66FF\u6362 /usr/local/bin/dsh",
811
+ cliInstallError: "\u5B89\u88C5\u5931\u8D25",
812
+ cliReasonUnresolved: "\u65E0\u6CD5\u5B89\u88C5",
813
+ cliReasonUnsupported: "\u4EC5 Windows / macOS"
814
+ };
815
+
816
+ // src/client/mount.ts
817
+ var inject = ["remote", "slots", "locale"];
818
+ function registerUi(ctx) {
819
+ ctx.effect(() => ctx.locale.register(NS, { en, zh }), "custom-env: dictionaries");
820
+ const t = ctx.locale.bind(NS);
821
+ const call = async (result) => {
822
+ if (!result.ok) throw result.error;
823
+ return result.value;
824
+ };
825
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
826
+ name: "settings.section",
827
+ id: "env",
828
+ order: 25,
829
+ label: () => t("nav"),
830
+ locale: NS,
831
+ inject: () => ({
832
+ load: async () => call(await ctx.remote.customEnv.list()),
833
+ upsert: async (id, name, value) => call(await ctx.remote.customEnv.upsert(id, name, value)),
834
+ remove: async (id) => call(await ctx.remote.customEnv.deleteEntry(id)),
835
+ restore: async (id) => call(await ctx.remote.customEnv.restore(id)),
836
+ cliStatus: async () => call(await ctx.remote.customEnv.cliStatus()),
837
+ cliInstall: async () => call(await ctx.remote.customEnv.cliInstall())
838
+ })
839
+ }, EnvSettingsSection));
840
+ }
841
+ async function mountCustomEnv(ctx, contribution) {
842
+ const unmountRemote = await ctx.remote.$mount(contribution);
843
+ const ui = ctx.inject(["remote.customEnv", "slots", "locale"], (child) => registerUi(child));
844
+ try {
845
+ await ui;
846
+ } catch (error) {
847
+ await ui.dispose();
848
+ await unmountRemote();
849
+ throw error;
850
+ }
851
+ return async () => {
852
+ await ui.dispose();
853
+ await unmountRemote();
854
+ };
855
+ }
856
+
857
+ // src/client/index.ts
858
+ async function apply(ctx) {
859
+ return await mountCustomEnv(ctx, typert_remote_client_default);
860
+ }
861
+ return module.exports;
862
+ }
863
+ });
864
+ //# sourceMappingURL=client.js.map