@skaile/forge-settings 0.0.0-stage → 1.0.1

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 (89) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/LICENSE +21 -0
  3. package/README.md +94 -2
  4. package/dist/module.d.mts +17 -0
  5. package/dist/module.json +9 -0
  6. package/dist/module.mjs +23 -0
  7. package/dist/runtime/ai-sdk/index.d.ts +46 -0
  8. package/dist/runtime/ai-sdk/index.js +31 -0
  9. package/dist/runtime/components/ForgeProvidersPanel.d.vue.ts +21 -0
  10. package/dist/runtime/components/ForgeProvidersPanel.vue +435 -0
  11. package/dist/runtime/components/ForgeProvidersPanel.vue.d.ts +21 -0
  12. package/dist/runtime/editor/index.d.ts +58 -0
  13. package/dist/runtime/editor/index.js +72 -0
  14. package/dist/runtime/model/errors.d.ts +32 -0
  15. package/dist/runtime/model/errors.js +19 -0
  16. package/dist/runtime/server/credential-policy.d.ts +61 -0
  17. package/dist/runtime/server/credential-policy.js +65 -0
  18. package/dist/runtime/server/credential-store.d.ts +184 -0
  19. package/dist/runtime/server/credential-store.js +234 -0
  20. package/dist/runtime/server/env-layers.d.ts +62 -0
  21. package/dist/runtime/server/env-layers.js +183 -0
  22. package/dist/runtime/server/env.d.ts +30 -0
  23. package/dist/runtime/server/env.js +15 -0
  24. package/dist/runtime/server/harness-settings.d.ts +150 -0
  25. package/dist/runtime/server/harness-settings.js +166 -0
  26. package/dist/runtime/server/index.d.ts +23 -0
  27. package/dist/runtime/server/index.js +14 -0
  28. package/dist/runtime/server/provider-discovery.d.ts +73 -0
  29. package/dist/runtime/server/provider-discovery.js +77 -0
  30. package/dist/runtime/server/provider-layers.d.ts +27 -0
  31. package/dist/runtime/server/provider-layers.js +35 -0
  32. package/dist/runtime/server/provider-service.d.ts +126 -0
  33. package/dist/runtime/server/provider-service.js +87 -0
  34. package/dist/runtime/server/provider-settings.d.ts +78 -0
  35. package/dist/runtime/server/provider-settings.js +81 -0
  36. package/dist/runtime/server/provider-write.d.ts +154 -0
  37. package/dist/runtime/server/provider-write.js +221 -0
  38. package/dist/runtime/server/settings-defaults.d.ts +26 -0
  39. package/dist/runtime/server/settings-defaults.js +46 -0
  40. package/dist/runtime/shared/env-vars.d.ts +55 -0
  41. package/dist/runtime/shared/env-vars.js +22 -0
  42. package/dist/runtime/shared/guards.d.ts +2 -0
  43. package/dist/runtime/shared/guards.js +3 -0
  44. package/dist/runtime/shared/harness-providers.d.ts +62 -0
  45. package/dist/runtime/shared/harness-providers.js +38 -0
  46. package/dist/runtime/shared/harnesses.d.ts +19 -0
  47. package/dist/runtime/shared/harnesses.js +15 -0
  48. package/dist/runtime/shared/provider-registry.d.ts +255 -0
  49. package/dist/runtime/shared/provider-registry.js +112 -0
  50. package/dist/runtime/shared/settings-types.d.ts +84 -0
  51. package/dist/runtime/shared/settings-types.js +0 -0
  52. package/dist/runtime/shims-vue.d.ts +8 -0
  53. package/dist/runtime/vue/index.d.ts +14 -0
  54. package/dist/runtime/vue/index.js +6 -0
  55. package/dist/runtime/vue/providers-panel-model.d.ts +147 -0
  56. package/dist/runtime/vue/providers-panel-model.js +37 -0
  57. package/dist/runtime/vue/settings-modal-scope.d.ts +62 -0
  58. package/dist/runtime/vue/settings-modal-scope.js +2 -0
  59. package/dist/runtime/vue/useProvidersPanel.d.ts +115 -0
  60. package/dist/runtime/vue/useProvidersPanel.js +633 -0
  61. package/dist/types.d.mts +9 -0
  62. package/package.json +117 -4
  63. package/src/runtime/ai-sdk/index.ts +88 -0
  64. package/src/runtime/components/ForgeProvidersPanel.vue +449 -0
  65. package/src/runtime/editor/index.ts +146 -0
  66. package/src/runtime/model/errors.ts +52 -0
  67. package/src/runtime/server/credential-policy.ts +139 -0
  68. package/src/runtime/server/credential-store.ts +473 -0
  69. package/src/runtime/server/env-layers.ts +287 -0
  70. package/src/runtime/server/env.ts +46 -0
  71. package/src/runtime/server/harness-settings.ts +373 -0
  72. package/src/runtime/server/index.ts +24 -0
  73. package/src/runtime/server/provider-discovery.ts +164 -0
  74. package/src/runtime/server/provider-layers.ts +67 -0
  75. package/src/runtime/server/provider-service.ts +244 -0
  76. package/src/runtime/server/provider-settings.ts +192 -0
  77. package/src/runtime/server/provider-write.ts +433 -0
  78. package/src/runtime/server/settings-defaults.ts +87 -0
  79. package/src/runtime/shared/env-vars.ts +82 -0
  80. package/src/runtime/shared/guards.ts +4 -0
  81. package/src/runtime/shared/harness-providers.ts +118 -0
  82. package/src/runtime/shared/harnesses.ts +34 -0
  83. package/src/runtime/shared/provider-registry.ts +391 -0
  84. package/src/runtime/shared/settings-types.ts +87 -0
  85. package/src/runtime/shims-vue.d.ts +8 -0
  86. package/src/runtime/vue/index.ts +15 -0
  87. package/src/runtime/vue/providers-panel-model.ts +203 -0
  88. package/src/runtime/vue/settings-modal-scope.ts +66 -0
  89. package/src/runtime/vue/useProvidersPanel.ts +930 -0
@@ -0,0 +1,633 @@
1
+ import { $fetch } from "ofetch";
2
+ import {
3
+ computed,
4
+ onMounted,
5
+ reactive,
6
+ ref,
7
+ watch
8
+ } from "vue";
9
+ import {
10
+ validateEnvVarName
11
+ } from "../shared/env-vars.js";
12
+ import {
13
+ AI_PROVIDER_SURFACES,
14
+ validateCredentialRef
15
+ } from "../shared/provider-registry.js";
16
+ import {
17
+ CREDENTIAL_ENTER_KEY,
18
+ CREDENTIAL_OTHER_REF,
19
+ CUSTOM_PRESET,
20
+ DEFAULT_PRESETS_URL,
21
+ EMPTY_FORM,
22
+ ENV_SOURCE_LABEL,
23
+ envNameOfRef,
24
+ FALLBACK_CREDENTIAL_KIND,
25
+ nonEnvSourceLabel,
26
+ OVERRIDES_INSTANCE_NOTICE,
27
+ PROVIDER_ID_PATTERN,
28
+ requestErrorMessage
29
+ } from "./providers-panel-model.js";
30
+ export { CREDENTIAL_ENTER_KEY, CREDENTIAL_OTHER_REF, CUSTOM_PRESET } from "./providers-panel-model.js";
31
+ export function useProvidersPanel(props, emit) {
32
+ const providers = ref([]);
33
+ const presets = ref([]);
34
+ const detected = ref([]);
35
+ const envDocument = ref(null);
36
+ const loading = ref(false);
37
+ const loadError = ref(null);
38
+ const envError = ref(null);
39
+ async function loadEnvironment() {
40
+ if (!props.environmentUrl) {
41
+ envDocument.value = null;
42
+ return;
43
+ }
44
+ try {
45
+ envDocument.value = await $fetch(props.environmentUrl);
46
+ envError.value = null;
47
+ } catch (err) {
48
+ envError.value = requestErrorMessage(err, "Could not load the environment variables.");
49
+ }
50
+ }
51
+ async function refresh() {
52
+ loading.value = true;
53
+ loadError.value = null;
54
+ try {
55
+ const [list, found] = await Promise.all([
56
+ $fetch(props.providersUrl, { query: { all: "1" } }),
57
+ $fetch(`${props.providersWriteUrl}/detected`),
58
+ loadEnvironment()
59
+ ]);
60
+ providers.value = Array.isArray(list) ? list : [];
61
+ detected.value = Array.isArray(found) ? found : [];
62
+ } catch (err) {
63
+ loadError.value = requestErrorMessage(err, "Could not load the providers.");
64
+ } finally {
65
+ loading.value = false;
66
+ }
67
+ }
68
+ async function loadPresets() {
69
+ try {
70
+ const list = await $fetch(
71
+ props.presetsUrl ?? DEFAULT_PRESETS_URL
72
+ );
73
+ presets.value = Array.isArray(list) ? list : [];
74
+ } catch (err) {
75
+ loadError.value = requestErrorMessage(err, "Could not load the provider presets.");
76
+ }
77
+ }
78
+ async function afterWrite() {
79
+ await refresh();
80
+ emit("changed");
81
+ }
82
+ onMounted(() => {
83
+ void loadPresets();
84
+ void refresh();
85
+ });
86
+ watch(
87
+ () => [props.providersUrl, props.providersWriteUrl, props.environmentUrl],
88
+ () => void refresh()
89
+ );
90
+ const envVars = computed(() => {
91
+ const byName = /* @__PURE__ */ new Map();
92
+ for (const v of envDocument.value?.vars ?? []) byName.set(v.name, v);
93
+ return byName;
94
+ });
95
+ const envEditable = computed(() => envDocument.value?.editable === true);
96
+ const canEnterKey = computed(
97
+ () => Boolean(props.environmentUrl) && envDocument.value?.editable !== false
98
+ );
99
+ function stateOf(entry) {
100
+ if (props.layer !== "workspace" || !entry.layers.includes("instance")) return "own";
101
+ return entry.layers.includes("workspace") ? "patched" : "inherited";
102
+ }
103
+ function isEditable(entry) {
104
+ return !props.readOnly && !entry.locked && stateOf(entry) === "own";
105
+ }
106
+ function credentialOptionsOf(entry) {
107
+ const doc = envDocument.value;
108
+ const currentEnv = envNameOfRef(entry.credentialRef);
109
+ const names = /* @__PURE__ */ new Set();
110
+ for (const v of doc?.vars ?? []) names.add(v.name);
111
+ for (const m of doc?.missing ?? []) names.add(m.name);
112
+ const rowPresetEnv = presets.value.find((p) => p.id === entry.preset)?.credential.env;
113
+ if (rowPresetEnv) names.add(rowPresetEnv);
114
+ for (const p of detected.value) if (p.credential.env) names.add(p.credential.env);
115
+ if (currentEnv) names.add(currentEnv);
116
+ const options = [...names].sort().map((name) => {
117
+ const v = envVars.value.get(name);
118
+ if (!doc) return { value: name, label: name };
119
+ if (!v) return { value: name, label: `${name} (not set)`, defined: false };
120
+ return {
121
+ value: name,
122
+ label: v.hint ? `${name} \u2022\u2022\u2022\u2022${v.hint}` : name,
123
+ defined: true,
124
+ hint: v.hint,
125
+ source: v.source
126
+ };
127
+ });
128
+ if (entry.credentialRef && !currentEnv) {
129
+ options.push({ value: entry.credentialRef, label: entry.credentialRef });
130
+ }
131
+ if (canEnterKey.value) {
132
+ options.push({ value: CREDENTIAL_ENTER_KEY, label: "Enter a key\u2026", action: "enter-key" });
133
+ }
134
+ options.push({ value: CREDENTIAL_OTHER_REF, label: "Other reference\u2026", action: "other-ref" });
135
+ return options;
136
+ }
137
+ function statusOf(entry, needsCredential) {
138
+ if (!needsCredential) return "No credential needed.";
139
+ const ref2 = entry.credentialRef;
140
+ const envName = envNameOfRef(ref2);
141
+ if (entry.configured) {
142
+ const source = envName ? (() => {
143
+ const v = envVars.value.get(envName);
144
+ return v ? ENV_SOURCE_LABEL[v.source] : void 0;
145
+ })() : ref2 ? nonEnvSourceLabel(ref2) : void 0;
146
+ return source ? `configured \xB7 ${source}` : "configured";
147
+ }
148
+ const missing = envName ?? entry.envKeyName;
149
+ return missing ? `missing ${missing}` : entry.error ?? "not configured";
150
+ }
151
+ function overrideNoticesOf(entry) {
152
+ const notices = [];
153
+ const envName = envNameOfRef(entry.credentialRef);
154
+ const v = envName ? envVars.value.get(envName) : void 0;
155
+ if (v && v.shadows.length > 0) {
156
+ notices.push(`Also defined in ${v.shadows.join(", ")}; the ${v.source} value is used.`);
157
+ }
158
+ if (v && props.layer === "workspace" && (v.source === "instance" || v.source === "process")) {
159
+ notices.push(OVERRIDES_INSTANCE_NOTICE);
160
+ }
161
+ if (entry.locked) notices.push("Locked by the control plane.");
162
+ return notices;
163
+ }
164
+ function buildRow(entry) {
165
+ const state = stateOf(entry);
166
+ const writable = !props.readOnly && !entry.locked;
167
+ const needsCredential = entry.credentialKind !== "none";
168
+ const envName = envNameOfRef(entry.credentialRef);
169
+ return {
170
+ ...entry,
171
+ state,
172
+ badge: state === "patched" ? "instance \xB7 overridden here" : state === "inherited" ? "instance" : props.layer,
173
+ inheritedNote: state === "inherited" ? "Inherited from the instance." : void 0,
174
+ editable: writable && state === "own",
175
+ canReset: writable && state === "patched",
176
+ canToggle: writable,
177
+ needsCredential,
178
+ canEditCredential: needsCredential && writable,
179
+ credentialValue: envName ?? entry.credentialRef ?? "",
180
+ credentialOptions: needsCredential ? credentialOptionsOf(entry) : [],
181
+ origin: entry.preset ?? entry.surface ?? entry.kind,
182
+ status: statusOf(entry, needsCredential),
183
+ overrideNotices: overrideNoticesOf(entry)
184
+ };
185
+ }
186
+ const rows = computed(() => providers.value.map(buildRow));
187
+ const dialogVisible = ref(false);
188
+ const editing = ref(null);
189
+ const form = reactive({ ...EMPTY_FORM });
190
+ const dialogError = ref(null);
191
+ const saving = ref(false);
192
+ const presetOptions = computed(() => [
193
+ ...presets.value.map((preset) => ({ label: preset.label, value: preset.id })),
194
+ { label: "Custom", value: CUSTOM_PRESET }
195
+ ]);
196
+ const surfaceOptions = AI_PROVIDER_SURFACES.map((surface) => ({
197
+ label: surface,
198
+ value: surface
199
+ }));
200
+ const selectedPreset = computed(() => presets.value.find((preset) => preset.id === form.preset));
201
+ const isCustom = computed(() => form.preset === CUSTOM_PRESET);
202
+ const customKind = computed(() => editing.value?.kind ?? "llm");
203
+ const showSurface = computed(
204
+ () => isCustom.value && (customKind.value === "llm" || customKind.value === "embed")
205
+ );
206
+ const baseUrlPlaceholder = computed(
207
+ () => selectedPreset.value?.base_url ?? "https://api.example.com/v1"
208
+ );
209
+ const canSetValue = computed(() => Boolean(props.environmentUrl));
210
+ const showCredentialFields = computed(() => editing.value === null);
211
+ const idError = computed(() => {
212
+ const id = form.id.trim();
213
+ return id && !PROVIDER_ID_PATTERN.test(id) ? "Use lower-case letters, digits, '-' and '_', starting with a letter or digit." : null;
214
+ });
215
+ const dialogTitle = computed(
216
+ () => editing.value ? `Edit provider \u2014 ${editing.value.id}` : "Add provider"
217
+ );
218
+ function resetDialog(values, row) {
219
+ Object.assign(form, EMPTY_FORM, values);
220
+ editing.value = row;
221
+ dialogError.value = null;
222
+ dialogVisible.value = true;
223
+ }
224
+ function openAdd(presetId) {
225
+ if (props.readOnly) return;
226
+ resetDialog({}, null);
227
+ if (presetId) selectPreset(presetId);
228
+ }
229
+ function openEdit(row) {
230
+ if (!isEditable(row)) return;
231
+ const preset = presets.value.find((p) => p.id === row.preset);
232
+ resetDialog(
233
+ {
234
+ preset: preset ? preset.id : CUSTOM_PRESET,
235
+ id: row.id,
236
+ surface: row.surface ?? "",
237
+ // The preset's own endpoint shows as the placeholder, not as a pinned value.
238
+ baseUrl: row.baseUrl && row.baseUrl !== preset?.base_url ? row.baseUrl : ""
239
+ },
240
+ row
241
+ );
242
+ }
243
+ function selectPreset(value) {
244
+ const previous = selectedPreset.value;
245
+ const next = presets.value.find((preset) => preset.id === value);
246
+ form.preset = value;
247
+ if (!editing.value && (form.id === "" || form.id === previous?.id)) form.id = next?.id ?? "";
248
+ if (form.envName === "" || form.envName === previous?.credential.env)
249
+ form.envName = next?.credential.env ?? "";
250
+ if (value === CUSTOM_PRESET && form.surface === "") form.surface = previous?.surface ?? "";
251
+ }
252
+ function formRefusal() {
253
+ if (!form.preset) return "Choose a preset or Custom.";
254
+ if (!form.id.trim()) return "Enter a provider id.";
255
+ if (idError.value) return idError.value;
256
+ if (showSurface.value && !form.surface) return "Choose the surface a custom provider speaks.";
257
+ if (isCustom.value && !form.baseUrl.trim()) return "A custom provider needs a base URL.";
258
+ if (showCredentialFields.value) {
259
+ const envName = form.envName.trim();
260
+ if (envName) {
261
+ const refusal = validateEnvVarName(envName);
262
+ if (refusal) return refusal;
263
+ }
264
+ if (form.secret && !envName) return "Name the environment variable the value is stored in.";
265
+ }
266
+ return null;
267
+ }
268
+ function buildBody() {
269
+ const row = editing.value;
270
+ const body = { ...row?.layerFields ?? {} };
271
+ const preset = selectedPreset.value;
272
+ const kept = row && form.preset === (row.preset ?? CUSTOM_PRESET) ? row : null;
273
+ if (isCustom.value) {
274
+ body.kind = customKind.value;
275
+ if (showSurface.value) body.surface = form.surface;
276
+ } else {
277
+ body.preset = form.preset;
278
+ if (kept?.surface && kept.surface !== preset?.surface) body.surface = kept.surface;
279
+ }
280
+ const baseUrl = form.baseUrl.trim();
281
+ if (baseUrl) body.base_url = baseUrl;
282
+ const credentialKind = kept?.credentialKind ?? (isCustom.value ? "bearer" : preset?.credential.kind ?? "bearer");
283
+ const envName = row ? "" : form.envName.trim();
284
+ if (envName) body.credential = { kind: credentialKind, ref: `env:${envName}` };
285
+ else if (row?.credentialRef) body.credential = { kind: credentialKind, ref: row.credentialRef };
286
+ return body;
287
+ }
288
+ async function saveProvider() {
289
+ if (props.readOnly) return;
290
+ const refusal = formRefusal();
291
+ if (refusal) {
292
+ dialogError.value = refusal;
293
+ return;
294
+ }
295
+ const id = form.id.trim();
296
+ saving.value = true;
297
+ dialogError.value = null;
298
+ try {
299
+ if (showCredentialFields.value && form.secret && props.environmentUrl) {
300
+ const update = {
301
+ set: { [form.envName.trim()]: form.secret },
302
+ unset: []
303
+ };
304
+ await $fetch(props.environmentUrl, { method: "PUT", body: update });
305
+ form.secret = "";
306
+ }
307
+ await $fetch(`${props.providersWriteUrl}/${encodeURIComponent(id)}`, {
308
+ method: "PUT",
309
+ body: buildBody()
310
+ });
311
+ dialogVisible.value = false;
312
+ editing.value = null;
313
+ await afterWrite();
314
+ } catch (err) {
315
+ dialogError.value = requestErrorMessage(err, "Could not save the provider.");
316
+ } finally {
317
+ saving.value = false;
318
+ }
319
+ }
320
+ const deleteTarget = ref(null);
321
+ const deleteError = ref(null);
322
+ const deleting = ref(false);
323
+ const deleteOpen = computed({
324
+ get: () => deleteTarget.value !== null,
325
+ set: (open) => {
326
+ if (!open) deleteTarget.value = null;
327
+ }
328
+ });
329
+ function requestDelete(row) {
330
+ if (!isEditable(row)) return;
331
+ deleteError.value = null;
332
+ deleteTarget.value = row;
333
+ }
334
+ async function confirmDelete() {
335
+ const target = deleteTarget.value;
336
+ if (!target || props.readOnly) return;
337
+ deleting.value = true;
338
+ deleteError.value = null;
339
+ try {
340
+ await $fetch(`${props.providersWriteUrl}/${encodeURIComponent(target.id)}`, {
341
+ method: "DELETE"
342
+ });
343
+ deleteTarget.value = null;
344
+ await afterWrite();
345
+ } catch (err) {
346
+ deleteError.value = requestErrorMessage(err, `Could not delete ${target.id}.`);
347
+ } finally {
348
+ deleting.value = false;
349
+ }
350
+ }
351
+ const detectedBusy = ref(null);
352
+ const detectedError = ref(null);
353
+ async function addDetected(preset) {
354
+ if (props.readOnly) return;
355
+ const taken = new Set(providers.value.map((entry) => entry.id));
356
+ const id = [preset.id, `${preset.id}-env`].find((candidate) => !taken.has(candidate));
357
+ const envName = preset.credential.env;
358
+ if (!id || !envName) {
359
+ openAdd(preset.id);
360
+ return;
361
+ }
362
+ detectedBusy.value = preset.id;
363
+ detectedError.value = null;
364
+ try {
365
+ await $fetch(`${props.providersWriteUrl}/${encodeURIComponent(id)}`, {
366
+ method: "PUT",
367
+ body: {
368
+ preset: preset.id,
369
+ credential: { kind: preset.credential.kind, ref: `env:${envName}` }
370
+ }
371
+ });
372
+ await afterWrite();
373
+ } catch (err) {
374
+ detectedError.value = requestErrorMessage(err, `Could not add ${preset.label}.`);
375
+ } finally {
376
+ detectedBusy.value = null;
377
+ }
378
+ }
379
+ const rowBusy = ref(null);
380
+ const rowError = ref(null);
381
+ function ownBody(row) {
382
+ const body = { ...row.layerFields ?? {} };
383
+ const preset = presets.value.find((p) => p.id === row.preset);
384
+ if (row.preset) {
385
+ body.preset = row.preset;
386
+ if (row.surface && row.surface !== preset?.surface) body.surface = row.surface;
387
+ } else {
388
+ body.kind = row.kind;
389
+ if (row.surface) body.surface = row.surface;
390
+ }
391
+ if (row.baseUrl && row.baseUrl !== preset?.base_url) body.base_url = row.baseUrl;
392
+ if (row.credentialRef) {
393
+ body.credential = {
394
+ kind: row.credentialKind ?? FALLBACK_CREDENTIAL_KIND,
395
+ ref: row.credentialRef
396
+ };
397
+ }
398
+ return body;
399
+ }
400
+ async function putRowChange(row, change) {
401
+ const body = row.state === "own" ? { ...ownBody(row), ...change } : change;
402
+ await $fetch(`${props.providersWriteUrl}/${encodeURIComponent(row.id)}`, {
403
+ method: "PUT",
404
+ body
405
+ });
406
+ }
407
+ function credentialFor(row, ref2) {
408
+ return { kind: row.credentialKind ?? FALLBACK_CREDENTIAL_KIND, ref: ref2 };
409
+ }
410
+ async function runRowWrite(id, work, fallback) {
411
+ rowBusy.value = id;
412
+ rowError.value = null;
413
+ try {
414
+ await work();
415
+ await afterWrite();
416
+ } catch (err) {
417
+ rowError.value = { id, message: requestErrorMessage(err, fallback) };
418
+ } finally {
419
+ rowBusy.value = null;
420
+ }
421
+ }
422
+ async function setCredentialEnv(row, name) {
423
+ if (!row.canEditCredential) return;
424
+ const ref2 = `env:${name}`;
425
+ if (row.credentialRef === ref2) return;
426
+ await runRowWrite(
427
+ row.id,
428
+ () => putRowChange(row, { credential: credentialFor(row, ref2) }),
429
+ `Could not change the credential of ${row.id}.`
430
+ );
431
+ }
432
+ async function setEnabled(row, enabled) {
433
+ if (!row.canToggle) return;
434
+ await runRowWrite(
435
+ row.id,
436
+ () => putRowChange(row, { enabled }),
437
+ `Could not ${enabled ? "enable" : "disable"} ${row.id}.`
438
+ );
439
+ }
440
+ async function resetPatch(row) {
441
+ if (!row.canReset) return;
442
+ await runRowWrite(
443
+ row.id,
444
+ async () => {
445
+ await $fetch(`${props.providersWriteUrl}/${encodeURIComponent(row.id)}`, {
446
+ method: "DELETE"
447
+ });
448
+ },
449
+ `Could not reset ${row.id}.`
450
+ );
451
+ }
452
+ const keyEditing = ref(null);
453
+ const keyForm = reactive({ name: "", value: "" });
454
+ const keyError = ref(null);
455
+ const refEditing = ref(null);
456
+ const refForm = reactive({ ref: "" });
457
+ const refError = ref(null);
458
+ function cancelKeyEditor() {
459
+ keyEditing.value = null;
460
+ keyForm.name = "";
461
+ keyForm.value = "";
462
+ keyError.value = null;
463
+ }
464
+ function cancelRefEditor() {
465
+ refEditing.value = null;
466
+ refForm.ref = "";
467
+ refError.value = null;
468
+ }
469
+ function openKeyEditor(row) {
470
+ if (!row.canEditCredential || !canEnterKey.value) return;
471
+ cancelRefEditor();
472
+ cancelKeyEditor();
473
+ const presetEnv = presets.value.find((p) => p.id === row.preset)?.credential.env;
474
+ keyForm.name = envNameOfRef(row.credentialRef) ?? presetEnv ?? "";
475
+ keyEditing.value = row.id;
476
+ }
477
+ async function saveKeyEditor() {
478
+ const row = rows.value.find((r) => r.id === keyEditing.value);
479
+ if (!row?.canEditCredential || !props.environmentUrl) return;
480
+ const name = keyForm.name.trim();
481
+ const refusal = validateEnvVarName(name);
482
+ if (refusal) {
483
+ keyError.value = refusal;
484
+ return;
485
+ }
486
+ if (!keyForm.value) {
487
+ keyError.value = "Enter the key.";
488
+ return;
489
+ }
490
+ rowBusy.value = row.id;
491
+ keyError.value = null;
492
+ let wroteEnv = false;
493
+ try {
494
+ const update = { set: { [name]: keyForm.value }, unset: [] };
495
+ await $fetch(props.environmentUrl, { method: "PUT", body: update });
496
+ wroteEnv = true;
497
+ const ref2 = `env:${name}`;
498
+ if (row.credentialRef !== ref2) {
499
+ await putRowChange(row, { credential: credentialFor(row, ref2) });
500
+ }
501
+ cancelKeyEditor();
502
+ } catch (err) {
503
+ keyError.value = requestErrorMessage(err, "Could not save the key.");
504
+ } finally {
505
+ rowBusy.value = null;
506
+ }
507
+ if (wroteEnv) await afterWrite();
508
+ }
509
+ function openRefEditor(row) {
510
+ if (!row.canEditCredential) return;
511
+ cancelKeyEditor();
512
+ cancelRefEditor();
513
+ refForm.ref = row.credentialRef && !envNameOfRef(row.credentialRef) ? row.credentialRef : "";
514
+ refEditing.value = row.id;
515
+ }
516
+ async function saveRefEditor() {
517
+ const row = rows.value.find((r) => r.id === refEditing.value);
518
+ if (!row?.canEditCredential) return;
519
+ const ref2 = refForm.ref.trim();
520
+ const refusal = ref2 ? validateCredentialRef(ref2) : "Enter a reference.";
521
+ if (refusal) {
522
+ refError.value = refusal;
523
+ return;
524
+ }
525
+ rowBusy.value = row.id;
526
+ refError.value = null;
527
+ try {
528
+ await putRowChange(row, { credential: credentialFor(row, ref2) });
529
+ cancelRefEditor();
530
+ await afterWrite();
531
+ } catch (err) {
532
+ refError.value = requestErrorMessage(err, "Could not save the reference.");
533
+ } finally {
534
+ rowBusy.value = null;
535
+ }
536
+ }
537
+ function onCredentialSelect(row, value) {
538
+ if (value === CREDENTIAL_ENTER_KEY) openKeyEditor(row);
539
+ else if (value === CREDENTIAL_OTHER_REF) openRefEditor(row);
540
+ else if (value !== row.credentialValue) void setCredentialEnv(row, value);
541
+ }
542
+ const envValues = reactive({});
543
+ const envBusy = ref(false);
544
+ async function writeEnvironment(update, fallback) {
545
+ if (!props.environmentUrl || !envEditable.value) return false;
546
+ envBusy.value = true;
547
+ envError.value = null;
548
+ try {
549
+ await $fetch(props.environmentUrl, { method: "PUT", body: update });
550
+ } catch (err) {
551
+ envError.value = requestErrorMessage(err, fallback);
552
+ return false;
553
+ } finally {
554
+ envBusy.value = false;
555
+ }
556
+ await afterWrite();
557
+ return true;
558
+ }
559
+ async function setEnvValue(name) {
560
+ const value = envValues[name];
561
+ if (!value) return;
562
+ if (await writeEnvironment({ set: { [name]: value }, unset: [] }, `Could not save ${name}.`)) {
563
+ delete envValues[name];
564
+ }
565
+ }
566
+ return {
567
+ // table
568
+ rows,
569
+ loading,
570
+ loadError,
571
+ refresh,
572
+ // dialog
573
+ dialogVisible,
574
+ dialogTitle,
575
+ editing,
576
+ form,
577
+ dialogError,
578
+ saving,
579
+ presetOptions,
580
+ surfaceOptions,
581
+ isCustom,
582
+ showSurface,
583
+ baseUrlPlaceholder,
584
+ canSetValue,
585
+ showCredentialFields,
586
+ idError,
587
+ openAdd,
588
+ openEdit,
589
+ selectPreset,
590
+ saveProvider,
591
+ // delete
592
+ deleteTarget,
593
+ deleteOpen,
594
+ deleteError,
595
+ deleting,
596
+ requestDelete,
597
+ confirmDelete,
598
+ // detected
599
+ detected,
600
+ detectedBusy,
601
+ detectedError,
602
+ addDetected,
603
+ // row actions
604
+ onCredentialSelect,
605
+ setCredentialEnv,
606
+ setEnabled,
607
+ resetPatch,
608
+ rowBusy,
609
+ rowError,
610
+ // key editor
611
+ canEnterKey,
612
+ keyEditing,
613
+ keyForm,
614
+ keyError,
615
+ openKeyEditor,
616
+ cancelKeyEditor,
617
+ saveKeyEditor,
618
+ // reference editor
619
+ refEditing,
620
+ refForm,
621
+ refError,
622
+ openRefEditor,
623
+ cancelRefEditor,
624
+ saveRefEditor,
625
+ // environment
626
+ envDocument,
627
+ envEditable,
628
+ envValues,
629
+ envError,
630
+ envBusy,
631
+ setEnvValue
632
+ };
633
+ }
@@ -0,0 +1,9 @@
1
+ import type { NuxtModule } from '@nuxt/schema'
2
+
3
+ import type { default as Module } from './module.mjs'
4
+
5
+ export type ModuleOptions = typeof Module extends NuxtModule<infer O> ? Partial<O> : Record<string, any>
6
+
7
+ export { default } from './module.mjs'
8
+
9
+ export { type ForgeSettingsOptions } from './module.mjs'