@cat-factory/app 0.65.0 → 0.67.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.
@@ -37,6 +37,7 @@ const accounts = useAccountsStore()
37
37
  const github = useGitHubStore()
38
38
  const services = useServicesStore()
39
39
  const infra = useInfraConfigStore()
40
+ const agentRuns = useAgentRunsStore()
40
41
  const { t } = useI18n()
41
42
 
42
43
  // The custom-manifest-type catalog feeds the `custom` picker. Cheap + shared (coalesced).
@@ -83,6 +84,12 @@ watch(
83
84
  )
84
85
  const customManifestId = computed(() => props.block.provisioning?.manifestId ?? '')
85
86
  const customManifestPath = computed(() => props.block.provisioning?.manifestPath ?? '')
87
+ // The catalog entry for the pinned custom type — supplies its default manifest path (prefill +
88
+ // detection seed) and whether it can generate/fix (a `fixerPrompt` is declared).
89
+ const selectedCustomType = computed(() =>
90
+ infra.customTypes.find((c) => c.manifestId === customManifestId.value),
91
+ )
92
+ const manifestFixerAvailable = computed(() => !!selectedCustomType.value?.fixerPrompt)
86
93
 
87
94
  const PROVISION_TYPES = computed<{ value: ProvisionType; label: string }[]>(() => [
88
95
  { value: 'infraless', label: t('inspector.testConfig.provisionTypes.infraless') },
@@ -158,14 +165,82 @@ function setKubeRenderer(value: KubernetesRenderer) {
158
165
  commitManifestSource()
159
166
  }
160
167
 
168
+ // Root a service-relative default under the service subtree, normalizing `.`/`..`/empty segments
169
+ // (mirrors the backend `joinPath`). A stored `manifestPath` is always REPO-root-relative — the
170
+ // same form auto-detection produces — so prefill and generate agree with detect.
171
+ function rootUnderDirectory(directory: string | null | undefined, path: string): string {
172
+ const segs: string[] = []
173
+ for (const part of [directory ?? '', path]) {
174
+ for (const seg of part.split('/')) {
175
+ if (!seg || seg === '.') continue
176
+ if (seg === '..') segs.pop()
177
+ else segs.push(seg)
178
+ }
179
+ }
180
+ return segs.join('/')
181
+ }
182
+
161
183
  function setCustomManifestId(value: string) {
162
- patchProvisioning({ type: 'custom', manifestId: value || undefined })
184
+ // Prefill the manifest path with the selected type's default, rooted under the service subtree
185
+ // (repo-root-relative, editable afterwards) so a monorepo service targets the right location
186
+ // even without running Detect. Leave the existing path untouched when the type declares no default.
187
+ const type = infra.customTypes.find((c) => c.manifestId === value)
188
+ const rooted = type?.defaultManifestPath
189
+ ? rootUnderDirectory(repoContext.value?.directory, type.defaultManifestPath)
190
+ : ''
191
+ patchProvisioning({
192
+ type: 'custom',
193
+ manifestId: value || undefined,
194
+ ...(rooted ? { manifestPath: rooted } : {}),
195
+ })
163
196
  }
164
197
 
165
198
  function setCustomManifestPath(value: string) {
166
199
  patchProvisioning({ type: 'custom', manifestPath: value.trim() || undefined })
167
200
  }
168
201
 
202
+ // Generate (or fix) the custom manifest via the fixer coding agent (async repair run). Shown only
203
+ // when the selected type declares a `fixerPrompt`. The dispatched run is tracked live below by id.
204
+ const generating = ref(false)
205
+ const manifestRepairError = ref(false)
206
+ const manifestRepairJobId = ref<string | null>(null)
207
+ const manifestRepairJob = computed(() =>
208
+ manifestRepairJobId.value ? agentRuns.envConfigRepairById(manifestRepairJobId.value) : undefined,
209
+ )
210
+
211
+ async function generateOrFixManifest() {
212
+ const ctx = repoContext.value
213
+ const type = selectedCustomType.value
214
+ if (!ctx || !type?.fixerPrompt) return
215
+ const repo = github.repoFor(ctx.githubId)
216
+ // `manifestPath` is already repo-root-relative (prefill/detect root it); the fallback default
217
+ // still needs rooting for the rare case where nothing was prefilled yet.
218
+ const path =
219
+ customManifestPath.value.trim() ||
220
+ (type.defaultManifestPath ? rootUnderDirectory(ctx.directory, type.defaultManifestPath) : '')
221
+ if (!repo || !path) {
222
+ manifestRepairError.value = true
223
+ return
224
+ }
225
+ generating.value = true
226
+ manifestRepairError.value = false
227
+ manifestRepairJobId.value = null
228
+ try {
229
+ const res = await infra.repairCustomManifest({
230
+ manifestId: type.manifestId,
231
+ owner: repo.owner,
232
+ repo: repo.name,
233
+ manifestPath: path,
234
+ })
235
+ manifestRepairJobId.value = res.repairJobId ?? null
236
+ if (!res.usedAgent) manifestRepairError.value = true
237
+ } catch {
238
+ manifestRepairError.value = true
239
+ } finally {
240
+ generating.value = false
241
+ }
242
+ }
243
+
169
244
  // The provisioning hints (cloud provider + instance size) are advisory inputs to the
170
245
  // ephemeral-environment provisioner, not commonly tuned — keep them collapsed by default.
171
246
  const showProvisioning = ref(false)
@@ -239,19 +314,27 @@ async function detectFromRepo() {
239
314
  owner: repo.owner,
240
315
  repo: repo.name,
241
316
  ...(ctx.directory ? { directory: ctx.directory } : {}),
242
- // Prioritize the option matching the currently-selected tab (kubernetes vs
243
- // docker-compose); the detector falls back to the other when the preferred isn't found.
317
+ // Prioritize the option matching the currently-selected tab (kubernetes / docker-compose /
318
+ // custom); the detector falls back to the other when the preferred isn't found.
244
319
  prefer: provisionType.value,
320
+ // `custom`: the selected type seeds the path search from its default; the current path is
321
+ // kept when it already resolves.
322
+ ...(provisionType.value === 'custom' && customManifestId.value
323
+ ? {
324
+ manifestId: customManifestId.value,
325
+ ...(customManifestPath.value ? { currentManifestPath: customManifestPath.value } : {}),
326
+ }
327
+ : {}),
245
328
  })
246
329
  detectResult.value = rec
247
330
  // Pre-select the recommended compose service so the picker opens on a real choice.
248
331
  pickedComposeService.value =
249
332
  rec.composeServiceCandidates?.find((c) => c.recommended)?.service ?? null
250
- // Only prefill when the detector actually inferred something. A `detected: false`
251
- // recommendation is `infraless`; applying it would WIPE the service's existing
252
- // provisioning (board.updateBlock persists immediately). Leave the current config
253
- // untouched and just surface the "nothing found" note.
254
- if (rec.detected) {
333
+ // Prefill when the detector inferred something. A non-custom `detected: false` recommendation
334
+ // is `infraless`; applying it would WIPE the service's existing provisioning (updateBlock
335
+ // persists immediately) — so we skip it. A `custom` recommendation is non-destructive (it just
336
+ // carries the resolved/default manifest path), so we apply it even when the file wasn't found.
337
+ if (rec.detected || rec.provisioning.type === 'custom') {
255
338
  board.updateBlock(props.block.id, { provisioning: rec.provisioning })
256
339
  if (rec.provisioning.type === 'kubernetes') seedKubeSource(rec.provisioning.manifestSource)
257
340
  }
@@ -369,7 +452,10 @@ function setSize(value: InstanceSize) {
369
452
  </p>
370
453
 
371
454
  <template v-if="detectResult && !detecting">
372
- <p v-if="!detectResult.detected" class="text-[11px] text-amber-300/80">
455
+ <p
456
+ v-if="!detectResult.detected && detectResult.provisioning.type !== 'custom'"
457
+ class="text-[11px] text-amber-300/80"
458
+ >
373
459
  {{ t('inspector.testConfig.detect.none') }}
374
460
  </p>
375
461
  <template v-else>
@@ -659,6 +745,53 @@ function setSize(value: InstanceSize) {
659
745
  (e: KeyboardEvent) => setCustomManifestPath((e.target as HTMLInputElement).value)
660
746
  "
661
747
  />
748
+ <p class="text-[11px] leading-snug text-slate-500">
749
+ {{ t('inspector.testConfig.customManifestPathHint') }}
750
+ </p>
751
+ </div>
752
+
753
+ <!-- Generate (when missing) or fix (when invalid) the manifest via the fixer agent. Only
754
+ shown when the selected type declares a fixer prompt. -->
755
+ <div
756
+ v-if="manifestFixerAvailable && repoContext"
757
+ class="space-y-1.5 rounded border border-slate-800 bg-slate-900/40 p-2"
758
+ >
759
+ <div class="flex items-center justify-between gap-2">
760
+ <span class="text-[11px] text-slate-400">{{
761
+ t('inspector.testConfig.generateManifest.title')
762
+ }}</span>
763
+ <UButton
764
+ size="xs"
765
+ variant="soft"
766
+ color="primary"
767
+ icon="i-lucide-file-cog"
768
+ :loading="generating"
769
+ :disabled="!customManifestPath && !selectedCustomType?.defaultManifestPath"
770
+ @click="generateOrFixManifest"
771
+ >
772
+ {{ t('inspector.testConfig.generateManifest.button') }}
773
+ </UButton>
774
+ </div>
775
+ <p class="text-[11px] leading-snug text-slate-500">
776
+ {{ t('inspector.testConfig.generateManifest.hint') }}
777
+ </p>
778
+ <p v-if="manifestRepairError" class="text-[11px] text-rose-300/80">
779
+ {{ t('inspector.testConfig.generateManifest.error') }}
780
+ </p>
781
+ <p
782
+ v-else-if="manifestRepairJob"
783
+ class="text-[11px]"
784
+ :class="{
785
+ 'text-sky-300/80': manifestRepairJob.status === 'running',
786
+ 'text-emerald-300/80': manifestRepairJob.status === 'succeeded',
787
+ 'text-rose-300/80': manifestRepairJob.status === 'failed',
788
+ }"
789
+ >
790
+ {{ t(`inspector.testConfig.generateManifest.status.${manifestRepairJob.status}`) }}
791
+ </p>
792
+ <p v-else-if="manifestRepairJobId" class="text-[11px] text-sky-300/80">
793
+ {{ t('inspector.testConfig.generateManifest.dispatched') }}
794
+ </p>
662
795
  </div>
663
796
  </div>
664
797
 
@@ -12,7 +12,14 @@ const infra = useInfraConfigStore()
12
12
  const toast = useToast()
13
13
 
14
14
  // A draft for the add/edit form. `manifestId` is locked on edit (it's the PK).
15
- const draft = reactive({ manifestId: '', label: '', acceptsInputHint: '', description: '' })
15
+ const draft = reactive({
16
+ manifestId: '',
17
+ label: '',
18
+ acceptsInputHint: '',
19
+ description: '',
20
+ defaultManifestPath: '',
21
+ fixerPrompt: '',
22
+ })
16
23
  const editing = ref(false)
17
24
  const busy = ref(false)
18
25
 
@@ -22,7 +29,14 @@ const canSave = computed(
22
29
  )
23
30
 
24
31
  function startAdd() {
25
- Object.assign(draft, { manifestId: '', label: '', acceptsInputHint: '', description: '' })
32
+ Object.assign(draft, {
33
+ manifestId: '',
34
+ label: '',
35
+ acceptsInputHint: '',
36
+ description: '',
37
+ defaultManifestPath: '',
38
+ fixerPrompt: '',
39
+ })
26
40
  editing.value = false
27
41
  }
28
42
 
@@ -32,6 +46,8 @@ function startEdit(type: CustomManifestType) {
32
46
  label: type.label,
33
47
  acceptsInputHint: type.acceptsInputHint ?? '',
34
48
  description: type.description ?? '',
49
+ defaultManifestPath: type.defaultManifestPath ?? '',
50
+ fixerPrompt: type.fixerPrompt ?? '',
35
51
  })
36
52
  editing.value = true
37
53
  }
@@ -44,6 +60,10 @@ async function save() {
44
60
  label: draft.label.trim(),
45
61
  ...(draft.acceptsInputHint.trim() ? { acceptsInputHint: draft.acceptsInputHint.trim() } : {}),
46
62
  ...(draft.description.trim() ? { description: draft.description.trim() } : {}),
63
+ ...(draft.defaultManifestPath.trim()
64
+ ? { defaultManifestPath: draft.defaultManifestPath.trim() }
65
+ : {}),
66
+ ...(draft.fixerPrompt.trim() ? { fixerPrompt: draft.fixerPrompt.trim() } : {}),
47
67
  })
48
68
  startAdd()
49
69
  } catch (e) {
@@ -158,6 +178,22 @@ async function remove(type: CustomManifestType) {
158
178
  <UFormField :label="t('settings.infrastructure.customType.description')">
159
179
  <UTextarea v-model="draft.description" :rows="2" />
160
180
  </UFormField>
181
+ <UFormField
182
+ :label="t('settings.infrastructure.customType.defaultManifestPath')"
183
+ :help="t('settings.infrastructure.customType.defaultManifestPathHelp')"
184
+ >
185
+ <UInput
186
+ v-model="draft.defaultManifestPath"
187
+ class="font-mono"
188
+ placeholder="deploy/preview.yaml"
189
+ />
190
+ </UFormField>
191
+ <UFormField
192
+ :label="t('settings.infrastructure.customType.fixerPrompt')"
193
+ :help="t('settings.infrastructure.customType.fixerPromptHelp')"
194
+ >
195
+ <UTextarea v-model="draft.fixerPrompt" :rows="3" />
196
+ </UFormField>
161
197
  <div class="flex justify-end gap-2">
162
198
  <UButton v-if="editing" color="neutral" variant="ghost" size="sm" @click="startAdd">
163
199
  {{ t('common.cancel') }}
@@ -29,6 +29,7 @@ import CustomManifestTypeEditor from '~/components/settings/CustomManifestTypeEd
29
29
  const { t } = useI18n()
30
30
  const infra = useInfraConfigStore()
31
31
  const auth = useAuthStore()
32
+ const ui = useUiStore()
32
33
  const toast = useToast()
33
34
 
34
35
  const isLocal = computed(() => auth.localMode?.enabled === true)
@@ -77,6 +78,18 @@ watch(
77
78
  { immediate: true },
78
79
  )
79
80
 
81
+ // A `cat-factory k3s` CLI deep-link (captured by the ui store on app load) always targets the
82
+ // `local-k3s` engine — select it so the workspace form below seeds from the prefill, provided the
83
+ // mode offers it (it's local-mode only). Runs after the handler/engine watcher above so the CLI
84
+ // hand-off wins over the saved-handler default.
85
+ watch(
86
+ () => ui.k3sSetupPrefill,
87
+ (prefill) => {
88
+ if (prefill && kubeEngines.value.includes('local-k3s')) selectedKubeEngine.value = 'local-k3s'
89
+ },
90
+ { immediate: true },
91
+ )
92
+
80
93
  const busy = ref(false)
81
94
 
82
95
  // Connection-probe state for the kube engine forms (workspace + per-user override kept
@@ -384,6 +397,7 @@ function notifyError(e: unknown) {
384
397
  :testing="kubeTesting"
385
398
  :busy="busy"
386
399
  :test-result="kubeTestResult"
400
+ :prefill="ui.k3sSetupPrefill"
387
401
  @test="testKube"
388
402
  @save="saveKube"
389
403
  />
@@ -13,6 +13,7 @@ import type {
13
13
  InfraEngine,
14
14
  InfraHandlerConfig,
15
15
  } from '@cat-factory/contracts'
16
+ import type { K3sSetupPrefill } from '~/stores/ui'
16
17
 
17
18
  // The kube branch of the discriminated handler config this form produces (the `local-k3s` /
18
19
  // `remote-kubernetes` engines share `kubernetesEngineConfigSchema`). Emitting this typed
@@ -30,6 +31,11 @@ const props = defineProps<{
30
31
  testing: boolean
31
32
  busy: boolean
32
33
  testResult: { ok: boolean; message?: string } | null
34
+ /**
35
+ * Non-secret values from a `cat-factory k3s` CLI deep-link, seeded into a FRESH `local-k3s`
36
+ * form so the user only pastes the token + saves. Ignored when editing a saved handler.
37
+ */
38
+ prefill?: K3sSetupPrefill | null
33
39
  }>()
34
40
 
35
41
  const emit = defineEmits<{
@@ -144,6 +150,27 @@ watch(
144
150
  { immediate: true },
145
151
  )
146
152
 
153
+ // Seed a FRESH `local-k3s` form from a `cat-factory k3s` CLI deep-link (see the ui store's
154
+ // `consumeK3sSetupDeepLink`). Applied AFTER the engine-default seed above so the CLI's concrete
155
+ // values win, but never over a saved handler (an edit is authoritative) and only for the engine
156
+ // the link targets. Non-empty fields only, so a partial link falls back to the loopback defaults.
157
+ watch(
158
+ () => props.prefill,
159
+ (prefill) => {
160
+ if (!prefill || props.handler || props.engine !== 'local-k3s') return
161
+ if (prefill.label.trim()) form.label = prefill.label.trim()
162
+ if (prefill.apiServerUrl.trim()) form.apiServerUrl = prefill.apiServerUrl.trim()
163
+ if (prefill.insecureSkipTlsVerify !== undefined)
164
+ form.insecureSkipTlsVerify = prefill.insecureSkipTlsVerify
165
+ if (prefill.namespaceTemplate.trim()) form.namespaceTemplate = prefill.namespaceTemplate.trim()
166
+ if (prefill.hostTemplate.trim()) {
167
+ form.urlSource = 'ingressTemplate'
168
+ form.hostTemplate = prefill.hostTemplate.trim()
169
+ }
170
+ },
171
+ { immediate: true },
172
+ )
173
+
147
174
  const servicePortValid = computed(() => {
148
175
  const raw = form.servicePort.trim()
149
176
  if (!raw) return true
@@ -202,6 +229,14 @@ function buildPayload(): KubeHandlerPayload {
202
229
  function optional(label: string): string {
203
230
  return t('settings.providerConnection.form.optionalLabel', { label })
204
231
  }
232
+
233
+ // The guided-setup CLI command shown in the local-k3s "Auto-setup" affordance. A literal command
234
+ // example (not prose), so it stays inline rather than in the i18n catalog — mirroring the format
235
+ // examples the i18n rules keep out of message bodies.
236
+ const AUTO_SETUP_COMMAND = 'cat-factory k3s'
237
+ async function copyAutoSetupCommand() {
238
+ await navigator.clipboard?.writeText(AUTO_SETUP_COMMAND)
239
+ }
205
240
  </script>
206
241
 
207
242
  <template>
@@ -221,6 +256,37 @@ function optional(label: string): string {
221
256
  {{ t('settings.infrastructure.kubernetesEngine.localK3sHint') }}
222
257
  </p>
223
258
 
259
+ <!-- Auto-setup: point the user at the `cat-factory k3s` CLI, which probes/provisions a local
260
+ cluster, mints the ServiceAccount token, and deep-links back here to pre-fill this form
261
+ (the token is pasted, never in the link). -->
262
+ <div
263
+ v-if="engine === 'local-k3s'"
264
+ class="rounded-md border border-slate-700 bg-slate-900/40 p-2 space-y-1.5"
265
+ >
266
+ <p class="flex items-center gap-1.5 text-[11px] font-semibold text-slate-300">
267
+ <UIcon name="i-lucide-wand-2" class="h-3.5 w-3.5 text-slate-400" />
268
+ {{ t('settings.infrastructure.kubernetesEngine.autoSetup.title') }}
269
+ </p>
270
+ <p class="text-[11px] text-slate-400">
271
+ {{ t('settings.infrastructure.kubernetesEngine.autoSetup.description') }}
272
+ </p>
273
+ <div class="flex items-center gap-1.5">
274
+ <code
275
+ class="flex-1 rounded bg-slate-950 px-2 py-1 font-mono text-[11px] text-slate-200 select-all"
276
+ >
277
+ {{ AUTO_SETUP_COMMAND }}
278
+ </code>
279
+ <UButton
280
+ icon="i-lucide-copy"
281
+ color="neutral"
282
+ variant="ghost"
283
+ size="xs"
284
+ :aria-label="t('common.copy')"
285
+ @click="copyAutoSetupCommand"
286
+ />
287
+ </div>
288
+ </div>
289
+
224
290
  <UFormField :label="t('settings.infrastructure.kubernetesEngine.label')">
225
291
  <UInput
226
292
  v-model="form.label"
@@ -5,6 +5,7 @@ import {
5
5
  registerEnvironmentHandlerContract,
6
6
  removeCustomManifestTypeContract,
7
7
  removeEnvironmentUserHandlerContract,
8
+ repairCustomManifestContract,
8
9
  testEnvironmentHandlerContract,
9
10
  unregisterEnvironmentHandlerContract,
10
11
  upsertCustomManifestTypeContract,
@@ -14,6 +15,7 @@ import type {
14
15
  DetectServiceProvisioningInput,
15
16
  ProvisionType,
16
17
  RegisterEnvironmentHandlerInput,
18
+ RepairCustomManifestInput,
17
19
  TestEnvironmentHandlerInput,
18
20
  UpsertCustomManifestTypeInput,
19
21
  UpsertEnvironmentUserHandlerBody,
@@ -45,6 +47,10 @@ export function infraHandlersApi({ send, ws }: ApiContext) {
45
47
  detectServiceProvisioning: (workspaceId: string, body: DetectServiceProvisioningInput) =>
46
48
  send(detectServiceProvisioningContract, { pathPrefix: ws(workspaceId), body }),
47
49
 
50
+ // Generate/fix a service's custom manifest via the fixer coding agent (async repair run).
51
+ repairCustomManifest: (workspaceId: string, body: RepairCustomManifestInput) =>
52
+ send(repairCustomManifestContract, { pathPrefix: ws(workspaceId), body }),
53
+
48
54
  // `manifestId` (for a `custom` handler) rides as a query param; absent ⇒ the bare handler.
49
55
  unregisterEnvironmentHandler: (
50
56
  workspaceId: string,
@@ -114,7 +114,12 @@ const ui = useUiStore()
114
114
  const aiReadiness = useAiReadiness()
115
115
 
116
116
  // Load the board from the backend before rendering it.
117
- onMounted(() => workspace.init())
117
+ onMounted(() => {
118
+ void workspace.init()
119
+ // Honour a `cat-factory k3s` CLI hand-off (`?infraSetup=local-k3s&…`): open the Infrastructure
120
+ // window pre-seeded with the provisioned connection so the user only pastes the token + saves.
121
+ ui.consumeK3sSetupDeepLink()
122
+ })
118
123
 
119
124
  // Per-session guards so each AI-onboarding dialog auto-opens at most once (later opens are
120
125
  // user-driven from the banner). Reset on workspace switch by the catalog watcher below.
@@ -6,6 +6,7 @@ import type {
6
6
  EnvironmentHandlerView,
7
7
  ProvisionType,
8
8
  RegisterEnvironmentHandlerInput,
9
+ RepairCustomManifestInput,
9
10
  TestEnvironmentHandlerInput,
10
11
  UpsertCustomManifestTypeInput,
11
12
  UpsertEnvironmentUserHandlerBody,
@@ -112,6 +113,16 @@ export const useInfraConfigStore = defineStore('infraConfig', () => {
112
113
  return api.detectServiceProvisioning(ws.requireId(), input)
113
114
  }
114
115
 
116
+ /**
117
+ * Generate (or fix) a service's custom manifest via the fixer coding agent. Dispatches a
118
+ * durable async repair run and returns immediately with `usedAgent`/`repairJobId`; the run is
119
+ * tracked via the workspace stream like the provider-config repair. Nothing persisted here.
120
+ */
121
+ async function repairCustomManifest(input: RepairCustomManifestInput) {
122
+ const ws = useWorkspaceStore()
123
+ return api.repairCustomManifest(ws.requireId(), input)
124
+ }
125
+
115
126
  async function unregisterHandler(type: ProvisionType, manifestId?: string | null) {
116
127
  const ws = useWorkspaceStore()
117
128
  await api.unregisterEnvironmentHandler(ws.requireId(), type, manifestId ?? undefined)
@@ -181,6 +192,7 @@ export const useInfraConfigStore = defineStore('infraConfig', () => {
181
192
  registerHandler,
182
193
  testHandler,
183
194
  detectProvisioning,
195
+ repairCustomManifest,
184
196
  unregisterHandler,
185
197
  upsertCustomType,
186
198
  removeCustomType,
package/app/stores/ui.ts CHANGED
@@ -14,6 +14,21 @@ export interface AddTaskPrefill {
14
14
  context?: PendingContext[]
15
15
  }
16
16
 
17
+ /**
18
+ * Non-secret `local-k3s` connection values captured from the `cat-factory k3s` CLI deep-link
19
+ * (`?infraSetup=local-k3s&…`). Mirrors the params `buildK3sSetupUrl` emits (the CLI-side
20
+ * `k3s-handler.ts`); the ServiceAccount token is intentionally absent — the user pastes it.
21
+ */
22
+ export interface K3sSetupPrefill {
23
+ label: string
24
+ apiServerUrl: string
25
+ namespaceTemplate: string
26
+ hostTemplate: string
27
+ // Absent when the link omitted the param, so the form keeps its engine default rather than
28
+ // forcing verification back on (which would break a self-signed local cluster).
29
+ insecureSkipTlsVerify?: boolean
30
+ }
31
+
17
32
  /** Transient UI state: selection, panels, zoom level. */
18
33
  export const useUiStore = defineStore('ui', () => {
19
34
  const selectedBlockId = ref<string | null>(null)
@@ -143,6 +158,11 @@ export const useUiStore = defineStore('ui', () => {
143
158
  // `openProviderConnection(kind)` remains for deep-links (a banner's "Configure…" button).
144
159
  const infrastructureOpen = ref(false)
145
160
  const infrastructureTab = ref<'environment' | 'runner-pool'>('runner-pool')
161
+ // Non-secret prefill captured from the `cat-factory k3s` CLI deep-link (see
162
+ // `consumeK3sSetupDeepLink`). When set, the Test-environments tab's kube engine form seeds the
163
+ // `local-k3s` connection from it; the ServiceAccount token is deliberately NOT in the link (a
164
+ // secret in a URL leaks into history/logs), so the user still pastes it before Test → Save.
165
+ const k3sSetupPrefill = ref<K3sSetupPrefill | null>(null)
146
166
  const modelConfigOpen = ref(false)
147
167
  // LLM-vendor subscription credentials (the token pool powering the Claude Code
148
168
  // / Codex harnesses). `vendorCredentialsTab` lets a caller deep-link to one tab —
@@ -530,6 +550,44 @@ export const useUiStore = defineStore('ui', () => {
530
550
  }
531
551
  function closeProviderConnection() {
532
552
  infrastructureOpen.value = false
553
+ // Drop any consumed CLI prefill so re-opening the window normally doesn't re-seed the form.
554
+ k3sSetupPrefill.value = null
555
+ }
556
+ // Capture a `cat-factory k3s` deep-link (`?infraSetup=local-k3s&…`) on app load: stash the
557
+ // non-secret connection values, open the Infrastructure window on the Test-environments tab so
558
+ // the kube engine form seeds from them, then strip the params from the URL (mirrors the
559
+ // `?invite=` handling in the auth store) so a reload doesn't re-trigger and the link isn't left
560
+ // in history. No-op when the query param is absent.
561
+ function consumeK3sSetupDeepLink() {
562
+ if (typeof window === 'undefined') return
563
+ const params = new URLSearchParams(window.location.search)
564
+ if (params.get('infraSetup') !== 'local-k3s') return
565
+ k3sSetupPrefill.value = {
566
+ label: params.get('label') ?? 'Local k3s',
567
+ apiServerUrl: params.get('apiServerUrl') ?? '',
568
+ namespaceTemplate: params.get('namespaceTemplate') ?? '',
569
+ hostTemplate: params.get('hostTemplate') ?? '',
570
+ // Only carry the flag the link actually set — a missing param leaves the form's engine
571
+ // default (skip-TLS on for a local self-signed cluster) untouched.
572
+ insecureSkipTlsVerify: params.has('insecureSkipTlsVerify')
573
+ ? params.get('insecureSkipTlsVerify') === '1'
574
+ : undefined,
575
+ }
576
+ resetHubReturn()
577
+ infrastructureTab.value = 'environment'
578
+ infrastructureOpen.value = true
579
+ for (const key of [
580
+ 'infraSetup',
581
+ 'label',
582
+ 'apiServerUrl',
583
+ 'namespaceTemplate',
584
+ 'hostTemplate',
585
+ 'insecureSkipTlsVerify',
586
+ ]) {
587
+ params.delete(key)
588
+ }
589
+ const qs = params.toString()
590
+ history.replaceState(null, '', window.location.pathname + (qs ? `?${qs}` : ''))
533
591
  }
534
592
  function openModelConfig() {
535
593
  modelConfigOpen.value = true
@@ -792,6 +850,8 @@ export const useUiStore = defineStore('ui', () => {
792
850
  closeObservabilityConnection,
793
851
  openProviderConnection,
794
852
  closeProviderConnection,
853
+ k3sSetupPrefill,
854
+ consumeK3sSetupDeepLink,
795
855
  openModelConfig,
796
856
  closeModelConfig,
797
857
  openVendorCredentials,
@@ -12,6 +12,7 @@
12
12
  "save": "Save",
13
13
  "cancel": "Cancel",
14
14
  "retry": "Retry",
15
+ "copy": "Copy",
15
16
  "block": "Block",
16
17
  "@block": {
17
18
  "description": "Generic fallback NOUN for a board item whose title is unknown (a service / module / task node). Not the verb 'to block'."
@@ -475,6 +476,19 @@
475
476
  "customNoTypes": "No custom manifest types are defined yet. Add one in the Infrastructure window.",
476
477
  "customManifestIdHint": "The custom type this service produces, matched to a remote-custom handler the workspace configures.",
477
478
  "customManifestPath": "Manifest path (optional)",
479
+ "customManifestPathHint": "Prefilled from the type's default when you select it. Use Detect to locate an existing manifest in the repo.",
480
+ "generateManifest": {
481
+ "title": "Manifest file",
482
+ "button": "Generate / fix",
483
+ "hint": "Runs a coding agent to create the manifest at this path (or fix it if it exists but is invalid) and push it to the branch.",
484
+ "error": "Could not start the manifest generation run.",
485
+ "dispatched": "A manifest generation run was started.",
486
+ "status": {
487
+ "running": "Generating the manifest…",
488
+ "succeeded": "Manifest generated and pushed.",
489
+ "failed": "The manifest generation run failed. Retry it from the Infrastructure window."
490
+ }
491
+ },
478
492
  "detect": {
479
493
  "title": "Auto-detect",
480
494
  "button": "Detect from repo",
@@ -1353,6 +1367,10 @@
1353
1367
  },
1354
1368
  "kubernetesEngine": {
1355
1369
  "localK3sHint": "Prefilled for a local k3s/k3d/kind cluster on this machine. Bind a ServiceAccount to a role, mint its token with `kubectl create token NAME -n NAMESPACE` (Kubernetes 1.24+), and paste it below. Then choose how the environment URL is derived, and edit the API server URL if your cluster listens on a different port.",
1370
+ "autoSetup": {
1371
+ "title": "Auto-setup with the CLI",
1372
+ "description": "Run this in your terminal to probe or provision a local cluster, mint a ServiceAccount token, and open this form pre-filled. Paste the token it prints, then Test and Save."
1373
+ },
1356
1374
  "label": "Connection label",
1357
1375
  "labelPlaceholder": "Preview cluster",
1358
1376
  "apiServerUrl": "API server URL",
@@ -1399,6 +1417,10 @@
1399
1417
  "acceptsInputHint": "Input hint (optional)",
1400
1418
  "acceptsInputHintHelp": "Describes the input shape the provider expects.",
1401
1419
  "description": "Description (optional)",
1420
+ "defaultManifestPath": "Default manifest path (optional)",
1421
+ "defaultManifestPathHelp": "Prefilled on a service that pins this type, and the seed for path auto-detection. A complete path (deploy/preview.yaml) or a bare filename.",
1422
+ "fixerPrompt": "Fixer prompt (optional)",
1423
+ "fixerPromptHelp": "Coding-agent instructions to generate or fix the manifest. When set, a service shows a Generate / fix button.",
1402
1424
  "add": "Add type",
1403
1425
  "saveFailed": "Could not save the custom type",
1404
1426
  "removeFailed": "Could not remove the custom type"
@@ -12,6 +12,7 @@
12
12
  "save": "Guardar",
13
13
  "cancel": "Cancelar",
14
14
  "retry": "Reintentar",
15
+ "copy": "Copiar",
15
16
  "actionFailed": "La acción falló",
16
17
  "close": "Cerrar",
17
18
  "block": "Bloque"
@@ -453,6 +454,19 @@
453
454
  "namespace": "Los manifiestos fijan el espacio de nombres \"{namespace}\"; se recomienda respetarlo en el gestor del espacio de trabajo.",
454
455
  "confidenceHigh": "Detectado",
455
456
  "confidenceLow": "Sugerencia"
457
+ },
458
+ "customManifestPathHint": "Se rellena con el valor predeterminado del tipo al seleccionarlo. Usa Detectar para localizar un manifiesto existente en el repositorio.",
459
+ "generateManifest": {
460
+ "title": "Archivo de manifiesto",
461
+ "button": "Generar / corregir",
462
+ "hint": "Ejecuta un agente de código para crear el manifiesto en esta ruta (o corregirlo si existe pero no es válido) y subirlo a la rama.",
463
+ "error": "No se pudo iniciar la generación del manifiesto.",
464
+ "dispatched": "Se inició una ejecución de generación del manifiesto.",
465
+ "status": {
466
+ "running": "Generando el manifiesto…",
467
+ "succeeded": "Manifiesto generado y subido.",
468
+ "failed": "La generación del manifiesto falló. Vuelve a intentarlo desde la ventana de Infraestructura."
469
+ }
456
470
  }
457
471
  },
458
472
  "agentConfig": {
@@ -1762,6 +1776,10 @@
1762
1776
  },
1763
1777
  "kubernetesEngine": {
1764
1778
  "localK3sHint": "Precargado para un clúster local k3s/k3d/kind en esta máquina. Vincula una ServiceAccount a un rol, genera su token con `kubectl create token NAME -n NAMESPACE` (Kubernetes 1.24+) y pégalo abajo. Luego elige cómo se deriva la URL del entorno y edita la URL del API server si tu clúster escucha en otro puerto.",
1779
+ "autoSetup": {
1780
+ "title": "Configuración automática con la CLI",
1781
+ "description": "Ejecútalo en tu terminal para detectar o aprovisionar un clúster local, generar un token de ServiceAccount y abrir este formulario ya rellenado. Pega el token que muestra y luego pulsa Probar y Guardar."
1782
+ },
1765
1783
  "label": "Etiqueta de la conexión",
1766
1784
  "labelPlaceholder": "Clúster de vista previa",
1767
1785
  "apiServerUrl": "URL del API server",
@@ -1810,7 +1828,11 @@
1810
1828
  "description": "Descripción (opcional)",
1811
1829
  "add": "Añadir tipo",
1812
1830
  "saveFailed": "No se pudo guardar el tipo personalizado",
1813
- "removeFailed": "No se pudo eliminar el tipo personalizado"
1831
+ "removeFailed": "No se pudo eliminar el tipo personalizado",
1832
+ "defaultManifestPath": "Ruta de manifiesto predeterminada (opcional)",
1833
+ "defaultManifestPathHelp": "Se rellena en un servicio que fije este tipo, y es la base para la detección automática de la ruta. Una ruta completa (deploy/preview.yaml) o solo un nombre de archivo.",
1834
+ "fixerPrompt": "Prompt de corrección (opcional)",
1835
+ "fixerPromptHelp": "Instrucciones para el agente de código para generar o corregir el manifiesto. Cuando se define, el servicio muestra un botón Generar / corregir."
1814
1836
  },
1815
1837
  "handler": {
1816
1838
  "intro": "Configura cómo se gestiona cada tipo de aprovisionamiento que declara un servicio: el motor y la conexión. Los manifiestos o la ruta de compose de un servicio (el qué/dónde) se definen en el servicio.",
@@ -12,6 +12,7 @@
12
12
  "save": "Enregistrer",
13
13
  "cancel": "Annuler",
14
14
  "retry": "Réessayer",
15
+ "copy": "Copier",
15
16
  "actionFailed": "Échec de l’action",
16
17
  "close": "Fermer",
17
18
  "block": "Bloc"
@@ -453,6 +454,19 @@
453
454
  "namespace": "Les manifestes fixent l'espace de noms « {namespace} » ; il est recommandé de le respecter sur le gestionnaire de l'espace de travail.",
454
455
  "confidenceHigh": "Détecté",
455
456
  "confidenceLow": "Suggestion"
457
+ },
458
+ "customManifestPathHint": "Prérempli avec la valeur par défaut du type lors de sa sélection. Utilisez Détecter pour localiser un manifeste existant dans le dépôt.",
459
+ "generateManifest": {
460
+ "title": "Fichier de manifeste",
461
+ "button": "Générer / corriger",
462
+ "hint": "Lance un agent de code pour créer le manifeste à ce chemin (ou le corriger s'il existe mais n'est pas valide) et le pousser sur la branche.",
463
+ "error": "Impossible de démarrer la génération du manifeste.",
464
+ "dispatched": "Une exécution de génération du manifeste a démarré.",
465
+ "status": {
466
+ "running": "Génération du manifeste…",
467
+ "succeeded": "Manifeste généré et poussé.",
468
+ "failed": "La génération du manifeste a échoué. Réessayez depuis la fenêtre Infrastructure."
469
+ }
456
470
  }
457
471
  },
458
472
  "agentConfig": {
@@ -1762,6 +1776,10 @@
1762
1776
  },
1763
1777
  "kubernetesEngine": {
1764
1778
  "localK3sHint": "Prérempli pour un cluster local k3s/k3d/kind sur cette machine. Liez un ServiceAccount à un rôle, générez son token avec `kubectl create token NAME -n NAMESPACE` (Kubernetes 1.24+) et collez-le ci-dessous. Choisissez ensuite comment l'URL de l'environnement est dérivée, et modifiez l'URL de l'API server si votre cluster écoute sur un autre port.",
1779
+ "autoSetup": {
1780
+ "title": "Configuration automatique avec la CLI",
1781
+ "description": "Exécutez-le dans votre terminal pour détecter ou provisionner un cluster local, générer un jeton de ServiceAccount et ouvrir ce formulaire prérempli. Collez le jeton affiché, puis cliquez sur Tester et Enregistrer."
1782
+ },
1765
1783
  "label": "Libellé de la connexion",
1766
1784
  "labelPlaceholder": "Cluster de prévisualisation",
1767
1785
  "apiServerUrl": "URL de l'API server",
@@ -1810,7 +1828,11 @@
1810
1828
  "description": "Description (facultatif)",
1811
1829
  "add": "Ajouter le type",
1812
1830
  "saveFailed": "Impossible d'enregistrer le type personnalisé",
1813
- "removeFailed": "Impossible de supprimer le type personnalisé"
1831
+ "removeFailed": "Impossible de supprimer le type personnalisé",
1832
+ "defaultManifestPath": "Chemin de manifeste par défaut (facultatif)",
1833
+ "defaultManifestPathHelp": "Prérempli sur un service qui épingle ce type, et base de la détection automatique du chemin. Un chemin complet (deploy/preview.yaml) ou un simple nom de fichier.",
1834
+ "fixerPrompt": "Prompt de correction (facultatif)",
1835
+ "fixerPromptHelp": "Instructions pour l'agent de code afin de générer ou corriger le manifeste. Lorsqu'il est défini, le service affiche un bouton Générer / corriger."
1814
1836
  },
1815
1837
  "handler": {
1816
1838
  "intro": "Configurez la manière dont chaque type de provisionnement déclaré par un service est géré: le moteur et la connexion. Les manifestes ou le chemin compose d'un service (le quoi/où) se définissent sur le service.",
@@ -12,6 +12,7 @@
12
12
  "save": "שמור",
13
13
  "cancel": "ביטול",
14
14
  "retry": "נסה שוב",
15
+ "copy": "העתק",
15
16
  "block": "בלוק",
16
17
  "actionFailed": "הפעולה נכשלה",
17
18
  "close": "סגור"
@@ -453,6 +454,19 @@
453
454
  "namespace": "המניפסטים מקבעים את מרחב השמות \"{namespace}\"; מומלץ לכבד אותו במטפל של המרחב.",
454
455
  "confidenceHigh": "זוהה",
455
456
  "confidenceLow": "הצעה"
457
+ },
458
+ "customManifestPathHint": "מתמלא מברירת המחדל של הסוג בעת הבחירה. השתמש ב'זיהוי' כדי לאתר מניפסט קיים במאגר.",
459
+ "generateManifest": {
460
+ "title": "קובץ מניפסט",
461
+ "button": "צור / תקן",
462
+ "hint": "מריץ סוכן קוד ליצירת המניפסט בנתיב זה (או לתיקונו אם קיים אך אינו תקין) ולדחיפתו לענף.",
463
+ "error": "לא ניתן להתחיל את הפקת המניפסט.",
464
+ "dispatched": "הפקת מניפסט הופעלה.",
465
+ "status": {
466
+ "running": "מפיק את המניפסט…",
467
+ "succeeded": "המניפסט נוצר ונדחף.",
468
+ "failed": "הפקת המניפסט נכשלה. נסה שוב מחלון התשתית."
469
+ }
456
470
  }
457
471
  },
458
472
  "agentConfig": {
@@ -1311,6 +1325,10 @@
1311
1325
  },
1312
1326
  "kubernetesEngine": {
1313
1327
  "localK3sHint": "מולא מראש עבור אשכול k3s/k3d/kind מקומי במחשב הזה. קשרו ServiceAccount לתפקיד, הנפיקו עבורו token באמצעות `kubectl create token NAME -n NAMESPACE` (Kubernetes 1.24+) והדביקו אותו למטה. לאחר מכן בחרו כיצד נגזרת כתובת ה-URL של הסביבה, וערכו את כתובת ה-API server אם האשכול שלכם מאזין ביציאה אחרת.",
1328
+ "autoSetup": {
1329
+ "title": "הגדרה אוטומטית באמצעות ה-CLI",
1330
+ "description": "הרץ זאת בטרמינל כדי לזהות או להקצות אשכול מקומי, ליצור אסימון ServiceAccount ולפתוח טופס זה כשהוא ממולא מראש. הדבק את האסימון המוצג, ולאחר מכן בצע בדיקה ושמירה."
1331
+ },
1314
1332
  "label": "תווית החיבור",
1315
1333
  "labelPlaceholder": "אשכול תצוגה מקדימה",
1316
1334
  "apiServerUrl": "כתובת ה-API server",
@@ -1359,7 +1377,11 @@
1359
1377
  "description": "תיאור (אופציונלי)",
1360
1378
  "add": "הוסף סוג",
1361
1379
  "saveFailed": "לא ניתן לשמור את הסוג המותאם",
1362
- "removeFailed": "לא ניתן להסיר את הסוג המותאם"
1380
+ "removeFailed": "לא ניתן להסיר את הסוג המותאם",
1381
+ "defaultManifestPath": "נתיב מניפסט ברירת מחדל (אופציונלי)",
1382
+ "defaultManifestPathHelp": "מתמלא בשירות שמצמיד סוג זה, ומשמש בסיס לזיהוי אוטומטי של הנתיב. נתיב מלא (deploy/preview.yaml) או שם קובץ בלבד.",
1383
+ "fixerPrompt": "הנחיית תיקון (אופציונלי)",
1384
+ "fixerPromptHelp": "הוראות לסוכן הקוד ליצירה או תיקון של המניפסט. כאשר מוגדר, השירות מציג כפתור צור / תקן."
1363
1385
  },
1364
1386
  "handler": {
1365
1387
  "intro": "הגדר כיצד מטופל כל סוג אספקה שהשירות מצהיר עליו: המנוע והחיבור. המניפסטים או נתיב ה-compose של שירות (מה/היכן) מוגדרים על השירות.",
@@ -12,6 +12,7 @@
12
12
  "save": "保存",
13
13
  "cancel": "キャンセル",
14
14
  "retry": "再試行",
15
+ "copy": "コピー",
15
16
  "block": "ブロック",
16
17
  "actionFailed": "操作に失敗しました",
17
18
  "close": "閉じる"
@@ -453,6 +454,19 @@
453
454
  "namespace": "マニフェストは名前空間「{namespace}」を固定しています。ワークスペースのハンドラーでそれを尊重することを推奨します。",
454
455
  "confidenceHigh": "検出",
455
456
  "confidenceLow": "提案"
457
+ },
458
+ "customManifestPathHint": "タイプを選択すると既定値が自動入力されます。リポジトリ内の既存のマニフェストを探すには「検出」を使用してください。",
459
+ "generateManifest": {
460
+ "title": "マニフェストファイル",
461
+ "button": "生成 / 修正",
462
+ "hint": "コーディングエージェントを実行して、このパスにマニフェストを作成(存在するが無効な場合は修正)し、ブランチにプッシュします。",
463
+ "error": "マニフェスト生成の実行を開始できませんでした。",
464
+ "dispatched": "マニフェスト生成の実行を開始しました。",
465
+ "status": {
466
+ "running": "マニフェストを生成しています…",
467
+ "succeeded": "マニフェストを生成してプッシュしました。",
468
+ "failed": "マニフェスト生成の実行に失敗しました。インフラウィンドウから再試行してください。"
469
+ }
456
470
  }
457
471
  },
458
472
  "agentConfig": {
@@ -1313,6 +1327,10 @@
1313
1327
  },
1314
1328
  "kubernetesEngine": {
1315
1329
  "localK3sHint": "このマシン上のローカル k3s/k3d/kind クラスター向けにあらかじめ入力されています。ServiceAccount をロールにバインドし、`kubectl create token NAME -n NAMESPACE`(Kubernetes 1.24 以降)でトークンを発行して下記に貼り付けてください。その後、環境 URL の導出方法を選択し、クラスターが別のポートで待ち受けている場合は API サーバー URL を編集してください。",
1330
+ "autoSetup": {
1331
+ "title": "CLI による自動セットアップ",
1332
+ "description": "これをターミナルで実行すると、ローカルクラスターを検出またはプロビジョニングし、ServiceAccount トークンを生成して、このフォームを事前入力した状態で開きます。表示されたトークンを貼り付けてから、テストして保存してください。"
1333
+ },
1316
1334
  "label": "接続ラベル",
1317
1335
  "labelPlaceholder": "プレビュークラスター",
1318
1336
  "apiServerUrl": "API サーバー URL",
@@ -1361,7 +1379,11 @@
1361
1379
  "description": "説明(任意)",
1362
1380
  "add": "タイプを追加",
1363
1381
  "saveFailed": "カスタムタイプを保存できませんでした",
1364
- "removeFailed": "カスタムタイプを削除できませんでした"
1382
+ "removeFailed": "カスタムタイプを削除できませんでした",
1383
+ "defaultManifestPath": "既定のマニフェストパス(任意)",
1384
+ "defaultManifestPathHelp": "このタイプを固定するサービスに自動入力され、パス自動検出の起点になります。完全なパス(deploy/preview.yaml)またはファイル名のみ。",
1385
+ "fixerPrompt": "修正プロンプト(任意)",
1386
+ "fixerPromptHelp": "マニフェストを生成または修正するためのコーディングエージェントへの指示。設定すると、サービスに「生成 / 修正」ボタンが表示されます。"
1365
1387
  },
1366
1388
  "handler": {
1367
1389
  "intro": "サービスが宣言する各プロビジョニングタイプの処理方法(エンジンと接続)を構成します。サービスのマニフェストや compose パス(何を/どこで)はサービス側で設定します。",
@@ -12,6 +12,7 @@
12
12
  "save": "Zapisz",
13
13
  "cancel": "Anuluj",
14
14
  "retry": "Ponów",
15
+ "copy": "Kopiuj",
15
16
  "actionFailed": "Akcja nie powiodła się",
16
17
  "close": "Zamknij",
17
18
  "block": "Blok"
@@ -453,6 +454,19 @@
453
454
  "namespace": "Manifesty ustalają przestrzeń nazw \"{namespace}\"; zaleca się jej przestrzeganie w handlerze przestrzeni roboczej.",
454
455
  "confidenceHigh": "Wykryto",
455
456
  "confidenceLow": "Sugestia"
457
+ },
458
+ "customManifestPathHint": "Wypełniane wartością domyślną typu po jego wybraniu. Użyj Wykryj, aby znaleźć istniejący manifest w repozytorium.",
459
+ "generateManifest": {
460
+ "title": "Plik manifestu",
461
+ "button": "Generuj / napraw",
462
+ "hint": "Uruchamia agenta kodu, aby utworzyć manifest w tej ścieżce (lub naprawić go, jeśli istnieje, ale jest nieprawidłowy) i wypchnąć go do gałęzi.",
463
+ "error": "Nie udało się rozpocząć generowania manifestu.",
464
+ "dispatched": "Rozpoczęto generowanie manifestu.",
465
+ "status": {
466
+ "running": "Generowanie manifestu…",
467
+ "succeeded": "Manifest wygenerowany i wypchnięty.",
468
+ "failed": "Generowanie manifestu nie powiodło się. Spróbuj ponownie w oknie Infrastruktura."
469
+ }
456
470
  }
457
471
  },
458
472
  "agentConfig": {
@@ -1762,6 +1776,10 @@
1762
1776
  },
1763
1777
  "kubernetesEngine": {
1764
1778
  "localK3sHint": "Wstępnie wypełnione dla lokalnego klastra k3s/k3d/kind na tym komputerze. Powiąż ServiceAccount z rolą, wygeneruj jego token poleceniem `kubectl create token NAME -n NAMESPACE` (Kubernetes 1.24+) i wklej go poniżej. Następnie wybierz sposób ustalania adresu URL środowiska i zmień URL serwera API, jeśli Twój klaster nasłuchuje na innym porcie.",
1779
+ "autoSetup": {
1780
+ "title": "Automatyczna konfiguracja przez CLI",
1781
+ "description": "Uruchom to w terminalu, aby wykryć lub udostępnić lokalny klaster, wygenerować token ServiceAccount i otworzyć ten formularz wstępnie wypełniony. Wklej wyświetlony token, a następnie kliknij Przetestuj i Zapisz."
1782
+ },
1765
1783
  "label": "Etykieta połączenia",
1766
1784
  "labelPlaceholder": "Klaster podglądu",
1767
1785
  "apiServerUrl": "URL serwera API",
@@ -1810,7 +1828,11 @@
1810
1828
  "description": "Opis (opcjonalnie)",
1811
1829
  "add": "Dodaj typ",
1812
1830
  "saveFailed": "Nie udało się zapisać niestandardowego typu",
1813
- "removeFailed": "Nie udało się usunąć niestandardowego typu"
1831
+ "removeFailed": "Nie udało się usunąć niestandardowego typu",
1832
+ "defaultManifestPath": "Domyślna ścieżka manifestu (opcjonalnie)",
1833
+ "defaultManifestPathHelp": "Wypełniane dla usługi, która przypina ten typ, oraz podstawa automatycznego wykrywania ścieżki. Pełna ścieżka (deploy/preview.yaml) lub sama nazwa pliku.",
1834
+ "fixerPrompt": "Prompt naprawczy (opcjonalnie)",
1835
+ "fixerPromptHelp": "Instrukcje dla agenta kodu do wygenerowania lub naprawy manifestu. Gdy ustawione, usługa pokazuje przycisk Generuj / napraw."
1814
1836
  },
1815
1837
  "handler": {
1816
1838
  "intro": "Skonfiguruj, jak obsługiwany jest każdy typ provisioningu zadeklarowany przez usługę: silnik i połączenie. Manifesty lub ścieżka compose usługi (co/gdzie) są ustawiane na usłudze.",
@@ -12,6 +12,7 @@
12
12
  "save": "Kaydet",
13
13
  "cancel": "İptal",
14
14
  "retry": "Yeniden dene",
15
+ "copy": "Kopyala",
15
16
  "block": "Blok",
16
17
  "actionFailed": "İşlem başarısız oldu",
17
18
  "close": "Kapat"
@@ -453,6 +454,19 @@
453
454
  "namespace": "Manifestler \"{namespace}\" ad alanını sabitliyor; çalışma alanı işleyicisinde buna uymanız önerilir.",
454
455
  "confidenceHigh": "Algılandı",
455
456
  "confidenceLow": "Öneri"
457
+ },
458
+ "customManifestPathHint": "Türü seçtiğinizde varsayılan değeriyle doldurulur. Depodaki mevcut bir manifesti bulmak için Algıla'yı kullanın.",
459
+ "generateManifest": {
460
+ "title": "Manifest dosyası",
461
+ "button": "Oluştur / düzelt",
462
+ "hint": "Bu yolda manifesti oluşturmak (veya varsa ancak geçersizse düzeltmek) ve dala göndermek için bir kod aracısı çalıştırır.",
463
+ "error": "Manifest oluşturma çalışması başlatılamadı.",
464
+ "dispatched": "Bir manifest oluşturma çalışması başlatıldı.",
465
+ "status": {
466
+ "running": "Manifest oluşturuluyor…",
467
+ "succeeded": "Manifest oluşturuldu ve gönderildi.",
468
+ "failed": "Manifest oluşturma çalışması başarısız oldu. Altyapı penceresinden yeniden deneyin."
469
+ }
456
470
  }
457
471
  },
458
472
  "agentConfig": {
@@ -1313,6 +1327,10 @@
1313
1327
  },
1314
1328
  "kubernetesEngine": {
1315
1329
  "localK3sHint": "Bu makinedeki yerel bir k3s/k3d/kind kümesi için önceden dolduruldu. Bir ServiceAccount'u bir role bağlayın, `kubectl create token NAME -n NAMESPACE` (Kubernetes 1.24+) ile token'ını oluşturun ve aşağıya yapıştırın. Ardından ortam URL'sinin nasıl türetileceğini seçin ve kümeniz farklı bir bağlantı noktasını dinliyorsa API sunucu URL'sini düzenleyin.",
1330
+ "autoSetup": {
1331
+ "title": "CLI ile otomatik kurulum",
1332
+ "description": "Yerel bir kümeyi algılamak veya sağlamak, bir ServiceAccount belirteci oluşturmak ve bu formu önceden doldurulmuş olarak açmak için bunu terminalinizde çalıştırın. Yazdırdığı belirteci yapıştırın, ardından Test edin ve Kaydedin."
1333
+ },
1316
1334
  "label": "Bağlantı etiketi",
1317
1335
  "labelPlaceholder": "Önizleme kümesi",
1318
1336
  "apiServerUrl": "API sunucu URL'si",
@@ -1361,7 +1379,11 @@
1361
1379
  "description": "Açıklama (isteğe bağlı)",
1362
1380
  "add": "Tür ekle",
1363
1381
  "saveFailed": "Özel tür kaydedilemedi",
1364
- "removeFailed": "Özel tür kaldırılamadı"
1382
+ "removeFailed": "Özel tür kaldırılamadı",
1383
+ "defaultManifestPath": "Varsayılan manifest yolu (isteğe bağlı)",
1384
+ "defaultManifestPathHelp": "Bu türü sabitleyen bir hizmette önceden doldurulur ve yol otomatik algılamanın temelidir. Tam bir yol (deploy/preview.yaml) veya yalnızca bir dosya adı.",
1385
+ "fixerPrompt": "Düzeltme istemi (isteğe bağlı)",
1386
+ "fixerPromptHelp": "Manifesti oluşturmak veya düzeltmek için kod aracısı talimatları. Ayarlandığında hizmette bir Oluştur / düzelt düğmesi gösterilir."
1365
1387
  },
1366
1388
  "handler": {
1367
1389
  "intro": "Bir servisin bildirdiği her sağlama türünün nasıl ele alınacağını yapılandırın: motor ve bağlantı. Bir servisin manifestleri veya compose yolu (ne/nerede) serviste ayarlanır.",
@@ -12,6 +12,7 @@
12
12
  "save": "Зберегти",
13
13
  "cancel": "Скасувати",
14
14
  "retry": "Повторити",
15
+ "copy": "Копіювати",
15
16
  "actionFailed": "Не вдалося виконати дію",
16
17
  "close": "Закрити",
17
18
  "block": "Блок"
@@ -453,6 +454,19 @@
453
454
  "namespace": "Маніфести фіксують простір імен \"{namespace}\"; рекомендуємо дотримуватися його в обробнику робочого простору.",
454
455
  "confidenceHigh": "Виявлено",
455
456
  "confidenceLow": "Пропозиція"
457
+ },
458
+ "customManifestPathHint": "Заповнюється значенням типу за замовчуванням під час вибору. Скористайтеся «Виявити», щоб знайти наявний маніфест у репозиторії.",
459
+ "generateManifest": {
460
+ "title": "Файл маніфесту",
461
+ "button": "Згенерувати / виправити",
462
+ "hint": "Запускає агента коду, щоб створити маніфест за цим шляхом (або виправити його, якщо він існує, але недійсний) і надіслати до гілки.",
463
+ "error": "Не вдалося розпочати генерування маніфесту.",
464
+ "dispatched": "Розпочато генерування маніфесту.",
465
+ "status": {
466
+ "running": "Генерування маніфесту…",
467
+ "succeeded": "Маніфест згенеровано та надіслано.",
468
+ "failed": "Генерування маніфесту не вдалося. Повторіть спробу у вікні «Інфраструктура»."
469
+ }
456
470
  }
457
471
  },
458
472
  "agentConfig": {
@@ -1762,6 +1776,10 @@
1762
1776
  },
1763
1777
  "kubernetesEngine": {
1764
1778
  "localK3sHint": "Попередньо заповнено для локального кластера k3s/k3d/kind на цьому комп'ютері. Прив'яжіть ServiceAccount до ролі, згенеруйте його токен командою `kubectl create token NAME -n NAMESPACE` (Kubernetes 1.24+) і вставте його нижче. Потім виберіть, як визначається URL середовища, і змініть URL сервера API, якщо ваш кластер слухає на іншому порту.",
1779
+ "autoSetup": {
1780
+ "title": "Автоматичне налаштування через CLI",
1781
+ "description": "Запустіть це в терміналі, щоб виявити або підготувати локальний кластер, згенерувати токен ServiceAccount і відкрити цю форму заздалегідь заповненою. Вставте показаний токен, потім натисніть «Перевірити» та «Зберегти»."
1782
+ },
1765
1783
  "label": "Мітка з'єднання",
1766
1784
  "labelPlaceholder": "Кластер попереднього перегляду",
1767
1785
  "apiServerUrl": "URL сервера API",
@@ -1810,7 +1828,11 @@
1810
1828
  "description": "Опис (необов'язково)",
1811
1829
  "add": "Додати тип",
1812
1830
  "saveFailed": "Не вдалося зберегти власний тип",
1813
- "removeFailed": "Не вдалося видалити власний тип"
1831
+ "removeFailed": "Не вдалося видалити власний тип",
1832
+ "defaultManifestPath": "Типовий шлях маніфесту (необов’язково)",
1833
+ "defaultManifestPathHelp": "Заповнюється для сервісу, який закріплює цей тип, і є основою для автоматичного визначення шляху. Повний шлях (deploy/preview.yaml) або лише ім’я файлу.",
1834
+ "fixerPrompt": "Промпт для виправлення (необов’язково)",
1835
+ "fixerPromptHelp": "Інструкції для агента коду щодо генерування або виправлення маніфесту. Якщо задано, сервіс показує кнопку «Згенерувати / виправити»."
1814
1836
  },
1815
1837
  "handler": {
1816
1838
  "intro": "Налаштуйте, як обробляється кожен тип провіженінгу, оголошений службою: рушій і з'єднання. Маніфести або шлях compose служби (що/де) задаються на службі.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.65.0",
3
+ "version": "0.67.0",
4
4
  "description": "Reusable Nuxt layer for the Agent Architecture Board SPA (components, stores, composables, pages). Consume it from a thin deployment app via `extends: ['@cat-factory/app']` and point it at your backend with NUXT_PUBLIC_API_BASE. See deploy/frontend for an example.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -34,7 +34,7 @@
34
34
  "valibot": "^1.4.2",
35
35
  "vue": "^3.5.39",
36
36
  "wretch": "^3.0.9",
37
- "@cat-factory/contracts": "0.72.0"
37
+ "@cat-factory/contracts": "0.73.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@toad-contracts/testing": "0.3.2",