@anweat/dsh-browser 0.1.7 → 0.1.8

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 (53) hide show
  1. package/README.md +306 -245
  2. package/cordis.patch.yml +26 -16
  3. package/lib/approval-policy.js +15 -0
  4. package/lib/approval-policy.js.map +1 -1
  5. package/lib/browser-service.d.ts +68 -25
  6. package/lib/browser-service.js +222 -62
  7. package/lib/browser-service.js.map +1 -1
  8. package/lib/client/SettingsCard.d.ts +2 -0
  9. package/lib/client/SettingsCard.js +21 -0
  10. package/lib/client/SettingsCard.js.map +1 -0
  11. package/lib/client/context-types.d.ts +8 -0
  12. package/lib/client/context-types.js +2 -0
  13. package/lib/client/context-types.js.map +1 -0
  14. package/lib/client/form.d.ts +62 -0
  15. package/lib/client/form.js +198 -0
  16. package/lib/client/form.js.map +1 -0
  17. package/lib/client/index.d.ts +16 -0
  18. package/lib/client/index.js +18 -0
  19. package/lib/client/index.js.map +1 -0
  20. package/lib/client/locales.d.ts +58 -0
  21. package/lib/client/locales.js +55 -0
  22. package/lib/client/locales.js.map +1 -0
  23. package/lib/client/settings-namespace.d.ts +2 -0
  24. package/lib/client/settings-namespace.js +3 -0
  25. package/lib/client/settings-namespace.js.map +1 -0
  26. package/lib/client/styles.d.ts +37 -0
  27. package/lib/client/styles.js +25 -0
  28. package/lib/client/styles.js.map +1 -0
  29. package/lib/client.js +753 -0
  30. package/lib/client.js.map +1 -0
  31. package/lib/config.d.ts +10 -0
  32. package/lib/config.js +22 -0
  33. package/lib/config.js.map +1 -1
  34. package/lib/deps.d.ts +7 -1
  35. package/lib/deps.js +22 -6
  36. package/lib/deps.js.map +1 -1
  37. package/lib/freedom.d.ts +1 -1
  38. package/lib/freedom.js +4 -0
  39. package/lib/freedom.js.map +1 -1
  40. package/lib/index.js +13 -1
  41. package/lib/index.js.map +1 -1
  42. package/lib/opencli-catalog.d.ts +21 -0
  43. package/lib/opencli-catalog.js +49 -0
  44. package/lib/opencli-catalog.js.map +1 -0
  45. package/lib/scripts.js +27 -27
  46. package/lib/tools.js +75 -1
  47. package/lib/tools.js.map +1 -1
  48. package/lib/usage-policy.d.ts +49 -0
  49. package/lib/usage-policy.js +171 -0
  50. package/lib/usage-policy.js.map +1 -0
  51. package/package.json +133 -78
  52. package/scripts/check-client-bundle.mjs +15 -0
  53. package/scripts/clean-lib.mjs +3 -0
package/lib/client.js ADDED
@@ -0,0 +1,753 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@anweat/dsh-browser",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ let react_jsx_runtime = require("react/jsx-runtime");
9
+ //#region src/client/form.ts
10
+ const textField = (field) => ({
11
+ field,
12
+ format: (value) => typeof value === "string" ? value : "",
13
+ parse(text) {
14
+ return text.trim() === "" ? { kind: "clear" } : {
15
+ kind: "set",
16
+ value: text.trim()
17
+ };
18
+ }
19
+ });
20
+ const booleanField = (field) => ({
21
+ field,
22
+ format: (value) => value === true ? "true" : "false",
23
+ parse: (text) => text === "true" || text === "false" ? {
24
+ kind: "set",
25
+ value: text === "true"
26
+ } : void 0
27
+ });
28
+ const enumField = (field, values) => ({
29
+ field,
30
+ format: (value) => typeof value === "string" ? value : values[0] ?? "",
31
+ parse: (text) => values.includes(text) ? {
32
+ kind: "set",
33
+ value: text
34
+ } : void 0
35
+ });
36
+ const jsonField = (field, validate) => ({
37
+ field,
38
+ format: (value) => value && typeof value === "object" && !Array.isArray(value) ? JSON.stringify(value, null, 2) : "",
39
+ parse(text) {
40
+ if (text.trim() === "") return { kind: "clear" };
41
+ try {
42
+ const value = JSON.parse(text);
43
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
44
+ const record = value;
45
+ return validate && !validate(record) ? void 0 : {
46
+ kind: "set",
47
+ value: record
48
+ };
49
+ } catch {
50
+ return;
51
+ }
52
+ }
53
+ });
54
+ const POLICY_BOUNDS = {
55
+ minDelayMs: [0, 6e4],
56
+ maxConcurrency: [1, 8],
57
+ burst: [1, 20],
58
+ maxPagesPerRun: [1, 100],
59
+ maxDepth: [0, 5],
60
+ retryLimit: [0, 5],
61
+ backoffBaseMs: [1, 6e4],
62
+ cooldownMs: [100, 3e5]
63
+ };
64
+ function validUsagePolicy(value) {
65
+ if (Object.keys(value).some((key) => !(key in POLICY_BOUNDS))) return false;
66
+ return Object.entries(POLICY_BOUNDS).every(([key, [min, max]]) => {
67
+ const entry = value[key];
68
+ return entry === void 0 || typeof entry === "number" && Number.isInteger(entry) && entry >= min && entry <= max;
69
+ });
70
+ }
71
+ const FIELD_SPECS = [
72
+ booleanField("enabled"),
73
+ enumField("automationMode", [
74
+ "read-only",
75
+ "standard",
76
+ "autonomous",
77
+ "unrestricted"
78
+ ]),
79
+ enumField("browserRuntime", ["playwright", "patchright"]),
80
+ textField("channel"),
81
+ booleanField("headless"),
82
+ booleanField("opencliEnabled"),
83
+ jsonField("usagePolicy", validUsagePolicy),
84
+ booleanField("autoInstall"),
85
+ textField("storageStatePath"),
86
+ jsonField("authProfiles"),
87
+ textField("defaultAuthProfile"),
88
+ jsonField("rulePacks"),
89
+ textField("executablePath"),
90
+ textField("snapshotDir"),
91
+ booleanField("verbose")
92
+ ];
93
+ const SPEC_BY_FIELD = new Map(FIELD_SPECS.map((spec) => [spec.field, spec]));
94
+ function stable(value) {
95
+ if (Array.isArray(value)) return `[${value.map(stable).join(",")}]`;
96
+ if (value && typeof value === "object") return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, entry]) => `${JSON.stringify(key)}:${stable(entry)}`).join(",")}}`;
97
+ return JSON.stringify(value);
98
+ }
99
+ function same(left, right) {
100
+ return stable(left) === stable(right);
101
+ }
102
+ function createLocalStore(initial) {
103
+ let snapshot = initial;
104
+ const listeners = /* @__PURE__ */ new Set();
105
+ return {
106
+ getSnapshot: () => snapshot,
107
+ subscribe(listener) {
108
+ listeners.add(listener);
109
+ return () => {
110
+ listeners.delete(listener);
111
+ };
112
+ },
113
+ set(next) {
114
+ snapshot = next;
115
+ for (const listener of listeners) listener();
116
+ },
117
+ update(updater) {
118
+ const draft = structuredClone(snapshot);
119
+ updater(draft);
120
+ snapshot = draft;
121
+ for (const listener of listeners) listener();
122
+ }
123
+ };
124
+ }
125
+ var BrowserSettingsController = class {
126
+ scope;
127
+ staged = /* @__PURE__ */ new Map();
128
+ store;
129
+ unsubscribe;
130
+ saving = false;
131
+ failed = false;
132
+ constructor(scope) {
133
+ this.scope = scope;
134
+ this.store = createLocalStore(this.project());
135
+ this.unsubscribe = scope.subscribe(() => {
136
+ this.publish();
137
+ });
138
+ }
139
+ inject() {
140
+ return {
141
+ hooks: { browserSettings: this.store },
142
+ edit: (field, text) => {
143
+ this.edit(field, text);
144
+ },
145
+ resetField: (field) => {
146
+ this.resetField(field);
147
+ },
148
+ save: () => {
149
+ this.save();
150
+ },
151
+ discard: () => {
152
+ this.discard();
153
+ }
154
+ };
155
+ }
156
+ snapshot() {
157
+ return this.store.getSnapshot();
158
+ }
159
+ edit(field, text) {
160
+ this.staged.set(field, {
161
+ text,
162
+ clear: false
163
+ });
164
+ this.failed = false;
165
+ this.publish();
166
+ }
167
+ resetField(field) {
168
+ const spec = this.spec(field);
169
+ this.staged.set(field, {
170
+ text: spec.format(this.baseValue(field)),
171
+ clear: true
172
+ });
173
+ this.failed = false;
174
+ this.publish();
175
+ }
176
+ discard() {
177
+ this.staged.clear();
178
+ this.failed = false;
179
+ this.publish();
180
+ }
181
+ async save() {
182
+ const plan = this.plan();
183
+ if (this.saving || plan.some((item) => item.write === void 0) || plan.length === 0) return;
184
+ this.saving = true;
185
+ this.failed = false;
186
+ this.publish();
187
+ let landed = true;
188
+ try {
189
+ for (const item of plan) {
190
+ if (!item.write) {
191
+ landed = false;
192
+ break;
193
+ }
194
+ if (item.write.kind === "clear") {
195
+ await this.scope.unset(item.field);
196
+ landed = !this.stored(item.field) && landed;
197
+ } else {
198
+ await this.scope.set(item.field, item.write.value);
199
+ landed = same(this.userLayer()?.[item.field], item.write.value) && landed;
200
+ }
201
+ }
202
+ } catch {
203
+ landed = false;
204
+ }
205
+ if (landed) this.staged.clear();
206
+ this.saving = false;
207
+ this.failed = !landed;
208
+ this.publish();
209
+ }
210
+ dispose() {
211
+ this.unsubscribe();
212
+ }
213
+ project() {
214
+ const fields = {};
215
+ for (const spec of FIELD_SPECS) fields[spec.field] = this.field(spec.field);
216
+ const plan = this.plan();
217
+ return {
218
+ available: this.scope.getSnapshot().status === "ready",
219
+ writable: this.scope.getSnapshot().writable,
220
+ dirty: plan.length > 0,
221
+ invalid: plan.some((item) => item.write === void 0),
222
+ saving: this.saving,
223
+ failed: this.failed,
224
+ fields
225
+ };
226
+ }
227
+ field(field) {
228
+ const spec = this.spec(field);
229
+ const draft = this.staged.get(field);
230
+ if (!draft) return {
231
+ text: spec.format(this.sectionValue(field)),
232
+ overridden: this.stored(field),
233
+ invalid: false
234
+ };
235
+ const write = draft.clear ? { kind: "clear" } : spec.parse(draft.text);
236
+ return {
237
+ text: draft.text,
238
+ overridden: write?.kind === "set",
239
+ invalid: write === void 0
240
+ };
241
+ }
242
+ plan() {
243
+ const writes = [];
244
+ for (const [field, draft] of this.staged) {
245
+ const spec = this.spec(field);
246
+ if (draft.clear) {
247
+ if (this.stored(field)) writes.push({
248
+ field,
249
+ write: { kind: "clear" }
250
+ });
251
+ continue;
252
+ }
253
+ if (draft.text === spec.format(this.sectionValue(field))) continue;
254
+ writes.push({
255
+ field,
256
+ write: spec.parse(draft.text)
257
+ });
258
+ }
259
+ return writes;
260
+ }
261
+ spec(field) {
262
+ const spec = SPEC_BY_FIELD.get(field);
263
+ if (!spec) throw new Error(`unknown browser settings field: ${field}`);
264
+ return spec;
265
+ }
266
+ sectionValue(field) {
267
+ return this.scope.getSnapshot().value?.[field];
268
+ }
269
+ baseValue(field) {
270
+ return this.scope.getSnapshot().base?.[field];
271
+ }
272
+ userLayer() {
273
+ return this.scope.getSnapshot().user;
274
+ }
275
+ stored(field) {
276
+ const user = this.userLayer();
277
+ return user !== void 0 && Object.hasOwn(user, field);
278
+ }
279
+ publish() {
280
+ this.store.set(this.project());
281
+ }
282
+ };
283
+ //#endregion
284
+ //#region src/client/styles.ts
285
+ const styles = {
286
+ card: "dsb-card",
287
+ open: "dsb-open",
288
+ header: "dsb-header",
289
+ head: "dsb-head",
290
+ titleRow: "dsb-title-row",
291
+ name: "dsb-name",
292
+ description: "dsb-description",
293
+ badge: "dsb-badge",
294
+ chevron: "dsb-chevron",
295
+ chevronOpen: "dsb-chevron-open",
296
+ body: "dsb-body",
297
+ notice: "dsb-notice",
298
+ section: "dsb-section",
299
+ sectionHead: "dsb-section-head",
300
+ grid: "dsb-grid",
301
+ field: "dsb-field",
302
+ invalid: "dsb-invalid",
303
+ fieldHead: "dsb-field-head",
304
+ label: "dsb-label",
305
+ hint: "dsb-hint",
306
+ input: "dsb-input",
307
+ textarea: "dsb-textarea",
308
+ code: "dsb-code",
309
+ reset: "dsb-reset",
310
+ toggle: "dsb-toggle",
311
+ toggleLabel: "dsb-toggle-label",
312
+ check: "dsb-check",
313
+ advanced: "dsb-advanced",
314
+ footer: "dsb-footer",
315
+ status: "dsb-status",
316
+ failed: "dsb-failed",
317
+ actions: "dsb-actions",
318
+ primary: "dsb-primary",
319
+ secondary: "dsb-secondary"
320
+ };
321
+ function ensureStyles() {
322
+ if (document.getElementById("dsh-browser-settings-styles")) return;
323
+ const style = document.createElement("style");
324
+ style.id = "dsh-browser-settings-styles";
325
+ style.textContent = `
326
+ .dsb-card{list-style:none;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-3);transition:border-color .16s,background .16s}.dsb-card:hover{border-color:var(--dsw-alias-label-dimmed)}.dsb-open{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}
327
+ .dsb-header{width:100%;appearance:none;border:0;background:none;font:inherit;color:inherit;text-align:left;cursor:pointer;display:flex;align-items:center;gap:12px;padding:14px 16px;border-radius:12px}.dsb-header:focus-visible,.dsb-input:focus-visible,.dsb-reset:focus-visible,.dsb-primary:focus-visible,.dsb-secondary:focus-visible,.dsb-check:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
328
+ .dsb-head{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.dsb-title-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.dsb-name{font-size:15px;font-weight:600;color:var(--dsw-alias-label-primary)}.dsb-description,.dsb-hint,.dsb-section-head p{font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary);margin:0}.dsb-badge{font-size:11px;padding:2px 7px;border-radius:9px;color:var(--dsw-alias-brand-primary);background:color-mix(in srgb,var(--dsw-alias-brand-primary) 12%,transparent)}
329
+ .dsb-chevron{flex:none;color:var(--dsw-alias-label-tertiary);transition:transform .16s}.dsb-chevron-open{transform:rotate(180deg)}.dsb-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding:4px 0 8px}.dsb-notice{margin:12px 0 0;padding:9px 11px;border-radius:8px;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-layer-3)}
330
+ .dsb-section{padding:18px 0}.dsb-section+.dsb-section{border-top:1px solid var(--dsw-alias-border-l2)}.dsb-section-head{margin-bottom:14px}.dsb-section-head h3{margin:0;font-size:14px;color:var(--dsw-alias-label-primary)}.dsb-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:14px 16px}.dsb-field{display:flex;min-width:0;flex-direction:column;gap:6px}.dsb-field-head{display:flex;align-items:center;justify-content:space-between;gap:8px}.dsb-label{font-size:13px;font-weight:500;color:var(--dsw-alias-label-primary)}
331
+ .dsb-input{box-sizing:border-box;width:100%;min-width:0;height:34px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-3);font:inherit;font-size:13px;color:var(--dsw-alias-label-primary)}.dsb-input:focus-visible{outline:none;border-color:var(--dsw-alias-brand-primary)}.dsb-input:disabled{opacity:.55}.dsb-invalid .dsb-input{border-color:var(--dsw-alias-label-error)}.dsb-textarea{height:auto;padding:9px 10px;resize:vertical;line-height:1.45}.dsb-code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:12px}.dsb-reset{appearance:none;border:0;background:none;padding:0;color:var(--dsw-alias-brand-primary);font:inherit;font-size:11px;cursor:pointer}
332
+ .dsb-toggle{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;padding-top:2px}.dsb-toggle-label{display:flex;align-items:flex-start;gap:9px;cursor:pointer}.dsb-check{width:16px;height:16px;flex:none;margin:2px 0 0;accent-color:var(--dsw-alias-brand-primary)}.dsb-advanced{padding:16px 0;border-top:1px solid var(--dsw-alias-border-l2)}.dsb-advanced>summary{cursor:pointer;font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary)}
333
+ .dsb-footer{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:14px 0 4px;border-top:1px solid var(--dsw-alias-border-l2)}.dsb-status,.dsb-failed{margin:0;font-size:12px}.dsb-status{color:var(--dsw-alias-label-tertiary)}.dsb-failed{color:var(--dsw-alias-label-error)}.dsb-actions{display:flex;gap:8px}.dsb-primary,.dsb-secondary{appearance:none;border-radius:8px;padding:6px 14px;font:inherit;font-size:13px;cursor:pointer}.dsb-primary{border:1px solid transparent;background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.dsb-secondary{border:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary)}.dsb-primary:disabled,.dsb-secondary:disabled,.dsb-reset:disabled{opacity:.4;cursor:default}
334
+ @media(max-width:720px){.dsb-grid{grid-template-columns:minmax(0,1fr)}.dsb-footer{align-items:stretch;flex-direction:column}.dsb-actions{justify-content:flex-end}}@media(max-width:420px){.dsb-body{margin:0 12px}.dsb-actions{display:grid;grid-template-columns:1fr 1fr}.dsb-primary,.dsb-secondary{width:100%}}
335
+ `;
336
+ document.head.append(style);
337
+ }
338
+ //#endregion
339
+ //#region src/client/SettingsCard.tsx
340
+ function FieldShell(props) {
341
+ const id = `dsh-browser-${props.field}`;
342
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
343
+ className: `${styles.field} ${props.state.invalid ? styles.invalid : ""}`,
344
+ children: [
345
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
346
+ className: styles.fieldHead,
347
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
348
+ className: styles.label,
349
+ htmlFor: id,
350
+ children: props.label
351
+ }), props.state.overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
352
+ type: "button",
353
+ className: styles.reset,
354
+ disabled: props.disabled,
355
+ onClick: () => props.onReset(props.field),
356
+ children: props.resetLabel
357
+ }) : null]
358
+ }),
359
+ props.children,
360
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
361
+ className: styles.hint,
362
+ children: props.hint
363
+ })
364
+ ]
365
+ });
366
+ }
367
+ function SettingsCard(props) {
368
+ const { t } = props;
369
+ const state = props.useBrowserSettings((snapshot) => snapshot);
370
+ const [open, setOpen] = (0, react.useState)(false);
371
+ if (!state.available) return null;
372
+ const disabled = !state.writable || state.saving;
373
+ const text = (field, label, hint) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldShell, {
374
+ field,
375
+ state: state.fields[field],
376
+ label: t(label),
377
+ hint: state.fields[field].invalid ? t("invalid") : t(hint),
378
+ disabled,
379
+ resetLabel: t("reset"),
380
+ onReset: props.resetField,
381
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
382
+ id: `dsh-browser-${field}`,
383
+ className: styles.input,
384
+ value: state.fields[field].text,
385
+ disabled,
386
+ "aria-invalid": state.fields[field].invalid || void 0,
387
+ onChange: (event) => props.edit(field, event.currentTarget.value)
388
+ })
389
+ });
390
+ const select = (field, label, hint, options) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldShell, {
391
+ field,
392
+ state: state.fields[field],
393
+ label: t(label),
394
+ hint: state.fields[field].invalid ? t("invalid") : t(hint),
395
+ disabled,
396
+ resetLabel: t("reset"),
397
+ onReset: props.resetField,
398
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
399
+ id: `dsh-browser-${field}`,
400
+ className: styles.input,
401
+ value: state.fields[field].text,
402
+ disabled,
403
+ onChange: (event) => props.edit(field, event.currentTarget.value),
404
+ children: options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
405
+ value: option,
406
+ children: option
407
+ }, option))
408
+ })
409
+ });
410
+ const json = (field, label, hint, rows = 7) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldShell, {
411
+ field,
412
+ state: state.fields[field],
413
+ label: t(label),
414
+ hint: state.fields[field].invalid ? t("invalidJson") : t(hint),
415
+ disabled,
416
+ resetLabel: t("reset"),
417
+ onReset: props.resetField,
418
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
419
+ id: `dsh-browser-${field}`,
420
+ className: `${styles.input} ${styles.textarea} ${styles.code}`,
421
+ rows,
422
+ value: state.fields[field].text,
423
+ disabled,
424
+ spellCheck: false,
425
+ "aria-invalid": state.fields[field].invalid || void 0,
426
+ onChange: (event) => props.edit(field, event.currentTarget.value)
427
+ })
428
+ });
429
+ const toggle = (field, label, hint) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
430
+ className: styles.toggle,
431
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
432
+ className: styles.toggleLabel,
433
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
434
+ className: styles.check,
435
+ type: "checkbox",
436
+ checked: state.fields[field].text === "true",
437
+ disabled,
438
+ onChange: (event) => props.edit(field, String(event.currentTarget.checked))
439
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
440
+ className: styles.label,
441
+ children: t(label)
442
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
443
+ className: styles.hint,
444
+ children: t(hint)
445
+ })] })]
446
+ }), state.fields[field].overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
447
+ type: "button",
448
+ className: styles.reset,
449
+ disabled,
450
+ onClick: () => props.resetField(field),
451
+ children: t("reset")
452
+ }) : null]
453
+ });
454
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
455
+ className: `${styles.card} ${open ? styles.open : ""}`,
456
+ "data-dsh-browser-settings": true,
457
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
458
+ type: "button",
459
+ className: styles.header,
460
+ "aria-expanded": open,
461
+ "aria-label": `${t(open ? "collapse" : "expand")}: ${t("title")}`,
462
+ onClick: () => setOpen(!open),
463
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
464
+ className: styles.head,
465
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
466
+ className: styles.titleRow,
467
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
468
+ className: styles.name,
469
+ children: t("title")
470
+ }), state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
471
+ className: styles.badge,
472
+ children: t("unsaved")
473
+ }) : null]
474
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
475
+ className: styles.description,
476
+ children: t("description")
477
+ })]
478
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
479
+ className: `${styles.chevron} ${open ? styles.chevronOpen : ""}`,
480
+ viewBox: "0 0 14 14",
481
+ width: "14",
482
+ height: "14",
483
+ "aria-hidden": "true",
484
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
485
+ d: "M3.5 5.5 7 9l3.5-3.5",
486
+ fill: "none",
487
+ stroke: "currentColor",
488
+ strokeWidth: "1.5"
489
+ })
490
+ })]
491
+ }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
492
+ className: styles.body,
493
+ children: [
494
+ !state.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
495
+ className: styles.notice,
496
+ role: "status",
497
+ children: t("readOnly")
498
+ }) : null,
499
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
500
+ className: styles.section,
501
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
502
+ className: styles.sectionHead,
503
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("freedom") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("freedomHint") })]
504
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
505
+ className: styles.grid,
506
+ children: [
507
+ toggle("enabled", "enabled", "enabledHint"),
508
+ select("automationMode", "automationMode", "automationModeHint", [
509
+ "read-only",
510
+ "standard",
511
+ "autonomous",
512
+ "unrestricted"
513
+ ]),
514
+ toggle("opencliEnabled", "opencliEnabled", "opencliEnabledHint")
515
+ ]
516
+ })]
517
+ }),
518
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
519
+ className: styles.section,
520
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
521
+ className: styles.sectionHead,
522
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("runtime") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("runtimeHint") })]
523
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
524
+ className: styles.grid,
525
+ children: [
526
+ select("browserRuntime", "browserRuntime", "browserRuntimeHint", ["playwright", "patchright"]),
527
+ text("channel", "channel", "channelHint"),
528
+ toggle("headless", "headless", "headlessHint"),
529
+ toggle("autoInstall", "autoInstall", "autoInstallHint"),
530
+ text("executablePath", "executablePath", "executablePathHint")
531
+ ]
532
+ })]
533
+ }),
534
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
535
+ className: styles.section,
536
+ children: [
537
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
538
+ className: styles.sectionHead,
539
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("usage") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("usageHint") })]
540
+ }),
541
+ json("usagePolicy", "usagePolicy", "usagePolicyHint", 10),
542
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
543
+ className: styles.notice,
544
+ role: "note",
545
+ children: t("restart")
546
+ })
547
+ ]
548
+ }),
549
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
550
+ className: styles.advanced,
551
+ children: [
552
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("advanced") }),
553
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
554
+ className: styles.hint,
555
+ children: t("advancedHint")
556
+ }),
557
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
558
+ className: styles.grid,
559
+ children: [
560
+ text("storageStatePath", "storageStatePath", "storageStatePathHint"),
561
+ text("defaultAuthProfile", "defaultAuthProfile", "defaultAuthProfileHint"),
562
+ json("authProfiles", "authProfiles", "authProfilesHint"),
563
+ json("rulePacks", "rulePacks", "rulePacksHint"),
564
+ text("snapshotDir", "snapshotDir", "snapshotDirHint"),
565
+ toggle("verbose", "verbose", "verboseHint")
566
+ ]
567
+ })
568
+ ]
569
+ }),
570
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
571
+ className: styles.footer,
572
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
573
+ className: state.failed ? styles.failed : styles.status,
574
+ role: "status",
575
+ "aria-live": "polite",
576
+ children: t(state.failed ? "saveFailed" : state.invalid ? "invalidSave" : state.dirty ? "pending" : "saved")
577
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
578
+ className: styles.actions,
579
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
580
+ type: "button",
581
+ className: styles.secondary,
582
+ disabled: !state.dirty || state.saving,
583
+ onClick: props.discard,
584
+ children: t("discard")
585
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
586
+ type: "button",
587
+ className: styles.primary,
588
+ disabled: !state.dirty || state.invalid || state.saving || !state.writable,
589
+ onClick: props.save,
590
+ children: t(state.saving ? "saving" : "save")
591
+ })]
592
+ })]
593
+ })
594
+ ]
595
+ }) : null]
596
+ });
597
+ }
598
+ //#endregion
599
+ //#region src/client/locales.ts
600
+ const zh = {
601
+ title: "浏览器自动化",
602
+ description: "运行时、工具自由度、OpenCLI 与防止过度调用的缓冲策略。",
603
+ expand: "展开设置",
604
+ collapse: "收起设置",
605
+ unsaved: "未保存",
606
+ readOnly: "当前配置只读。",
607
+ freedom: "自动化自由度",
608
+ freedomHint: "无审批模式只跳过人工确认,不会绕过调用缓冲和参数校验。",
609
+ runtime: "浏览器运行时",
610
+ runtimeHint: "默认 Playwright;遇到兼容性检测时可显式切换 Patchright。",
611
+ usage: "使用策略缓冲",
612
+ usageHint: "限制并发、短时突发、爬取预算,并对 429/503 等响应退避。",
613
+ advanced: "登录态与高级设置",
614
+ advancedHint: "状态文件不要提交到仓库;AuthProfile 必须配置 allowedDomains。",
615
+ enabled: "启用浏览器服务",
616
+ enabledHint: "关闭后浏览器服务不可用。",
617
+ automationMode: "自动化模式",
618
+ automationModeHint: "read-only / standard / autonomous / unrestricted。",
619
+ browserRuntime: "运行时提供器",
620
+ browserRuntimeHint: "Patchright 仅支持 Chromium。",
621
+ channel: "浏览器通道",
622
+ channelHint: "chromium、chrome 或 msedge。Patchright 推荐 chrome。",
623
+ headless: "无头模式",
624
+ headlessHint: "Patchright 兼容性最佳配置通常是关闭无头模式。",
625
+ opencliEnabled: "启用 OpenCLI",
626
+ opencliEnabledHint: "站点 adapter 和 Chrome Browser Bridge 总开关。",
627
+ usagePolicy: "调用缓冲 JSON",
628
+ usagePolicyHint: "minDelayMs、maxConcurrency、burst、maxPagesPerRun、maxDepth、retryLimit、backoffBaseMs、cooldownMs。",
629
+ autoInstall: "缺失时自动安装 Chromium",
630
+ autoInstallHint: "可能触发较大下载,日常建议关闭并显式调用 browser_install。",
631
+ storageStatePath: "全局 storageState 路径",
632
+ storageStatePathHint: "旧版兼容入口;新配置优先使用限域 AuthProfile。",
633
+ authProfiles: "AuthProfiles JSON",
634
+ authProfilesHint: "命名登录态、allowedDomains 与 persistState。",
635
+ defaultAuthProfile: "默认 AuthProfile",
636
+ defaultAuthProfileHint: "未显式选择登录态时使用;browser_crawl 始终匿名。",
637
+ rulePacks: "RulePacks JSON",
638
+ rulePacksHint: "域名匹配、哈希固定 init script 和有界步骤。",
639
+ executablePath: "浏览器可执行文件",
640
+ executablePathHint: "少数自定义部署才需要覆盖。",
641
+ snapshotDir: "快照目录",
642
+ snapshotDirHint: "留空使用 DSH_HOME 下的默认目录。",
643
+ verbose: "详细日志",
644
+ verboseHint: "输出启动和诊断信息。",
645
+ reset: "恢复部署值",
646
+ invalid: "值无效,请检查格式或范围。",
647
+ invalidJson: "JSON 或数值范围无效。",
648
+ restart: "保存后完整重启 profile,运行时和工具目录才会重新注册。",
649
+ saved: "配置已同步。",
650
+ pending: "有待保存的修改。",
651
+ invalidSave: "存在无效字段。",
652
+ saveFailed: "保存失败,草稿已保留。",
653
+ saving: "保存中…",
654
+ save: "保存",
655
+ discard: "放弃修改"
656
+ };
657
+ const en = {
658
+ title: "Browser automation",
659
+ description: "Runtime, tool freedom, OpenCLI, and overuse buffering policy.",
660
+ expand: "Expand settings",
661
+ collapse: "Collapse settings",
662
+ unsaved: "Unsaved",
663
+ readOnly: "Configuration is read-only.",
664
+ freedom: "Automation freedom",
665
+ freedomHint: "No-approval skips human confirmation only; buffering and validation remain active.",
666
+ runtime: "Browser runtime",
667
+ runtimeHint: "Playwright by default; explicitly select Patchright for compatibility-sensitive sites.",
668
+ usage: "Usage buffer",
669
+ usageHint: "Bounds concurrency, bursts and crawl budgets, with backoff for 429/503 responses.",
670
+ advanced: "Auth and advanced settings",
671
+ advancedHint: "Never commit state files; AuthProfiles must declare allowedDomains.",
672
+ enabled: "Enable browser service",
673
+ enabledHint: "Disabling makes the browser service unavailable.",
674
+ automationMode: "Automation mode",
675
+ automationModeHint: "read-only / standard / autonomous / unrestricted.",
676
+ browserRuntime: "Runtime provider",
677
+ browserRuntimeHint: "Patchright is Chromium-only.",
678
+ channel: "Browser channel",
679
+ channelHint: "chromium, chrome, or msedge. Patchright recommends chrome.",
680
+ headless: "Headless mode",
681
+ headlessHint: "Patchright compatibility is usually strongest in headed mode.",
682
+ opencliEnabled: "Enable OpenCLI",
683
+ opencliEnabledHint: "Master switch for site adapters and the Chrome Browser Bridge.",
684
+ usagePolicy: "Usage buffer JSON",
685
+ usagePolicyHint: "minDelayMs, maxConcurrency, burst, maxPagesPerRun, maxDepth, retryLimit, backoffBaseMs, cooldownMs.",
686
+ autoInstall: "Auto-install Chromium when missing",
687
+ autoInstallHint: "May download a large binary; explicit browser_install is safer for daily use.",
688
+ storageStatePath: "Global storageState path",
689
+ storageStatePathHint: "Legacy fallback; prefer domain-scoped AuthProfiles.",
690
+ authProfiles: "AuthProfiles JSON",
691
+ authProfilesHint: "Named states with allowedDomains and persistState.",
692
+ defaultAuthProfile: "Default AuthProfile",
693
+ defaultAuthProfileHint: "Used when no profile is selected; browser_crawl is always anonymous.",
694
+ rulePacks: "RulePacks JSON",
695
+ rulePacksHint: "Domain match, hash-pinned init scripts, and bounded steps.",
696
+ executablePath: "Browser executable",
697
+ executablePathHint: "Override only for custom deployments.",
698
+ snapshotDir: "Snapshot directory",
699
+ snapshotDirHint: "Leave empty for the DSH_HOME default.",
700
+ verbose: "Verbose logs",
701
+ verboseHint: "Emit startup and diagnostic details.",
702
+ reset: "Restore deployed",
703
+ invalid: "Invalid value. Check format and bounds.",
704
+ invalidJson: "Invalid JSON or numeric bounds.",
705
+ restart: "Fully restart the profile after saving so runtime and tool catalogs are re-registered.",
706
+ saved: "Configuration is synchronized.",
707
+ pending: "Changes are ready to save.",
708
+ invalidSave: "Some fields are invalid.",
709
+ saveFailed: "Save failed; the draft was retained.",
710
+ saving: "Saving…",
711
+ save: "Save",
712
+ discard: "Discard"
713
+ };
714
+ //#endregion
715
+ //#region src/client/settings-namespace.ts
716
+ /** Settings namespace used by both the Host registry and the card slot key. */
717
+ const SETTINGS_NAMESPACE = "browser";
718
+ //#endregion
719
+ //#region src/client/index.ts
720
+ const name = "dsh-browser-client";
721
+ const inject = [
722
+ "slots",
723
+ "locale",
724
+ "connection",
725
+ "settingsScope"
726
+ ];
727
+ const NS = "dsh-browser.card";
728
+ function apply(ctx) {
729
+ ensureStyles();
730
+ ctx.effect(() => ctx.locale.register(NS, {
731
+ zh,
732
+ en
733
+ }), "dsh-browser: settings dictionaries");
734
+ const controller = new BrowserSettingsController(ctx.settingsScope.bind({ namespace: SETTINGS_NAMESPACE }));
735
+ ctx.effect(() => () => controller.dispose(), "dsh-browser: settings controller");
736
+ ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
737
+ name: "settings.plugin.item",
738
+ key: SETTINGS_NAMESPACE,
739
+ locale: NS,
740
+ inject: () => controller.inject()
741
+ }, SettingsCard));
742
+ }
743
+ //#endregion
744
+ exports.NS = NS;
745
+ exports.SETTINGS_NAMESPACE = SETTINGS_NAMESPACE;
746
+ exports.apply = apply;
747
+ exports.inject = inject;
748
+ exports.name = name;
749
+ return module.exports;
750
+ }
751
+ });
752
+
753
+ //# sourceMappingURL=client.js.map