@cat-factory/app 0.65.0 → 0.66.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.
@@ -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"
@@ -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.
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'."
@@ -1353,6 +1354,10 @@
1353
1354
  },
1354
1355
  "kubernetesEngine": {
1355
1356
  "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.",
1357
+ "autoSetup": {
1358
+ "title": "Auto-setup with the CLI",
1359
+ "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."
1360
+ },
1356
1361
  "label": "Connection label",
1357
1362
  "labelPlaceholder": "Preview cluster",
1358
1363
  "apiServerUrl": "API server URL",
@@ -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"
@@ -1762,6 +1763,10 @@
1762
1763
  },
1763
1764
  "kubernetesEngine": {
1764
1765
  "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.",
1766
+ "autoSetup": {
1767
+ "title": "Configuración automática con la CLI",
1768
+ "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."
1769
+ },
1765
1770
  "label": "Etiqueta de la conexión",
1766
1771
  "labelPlaceholder": "Clúster de vista previa",
1767
1772
  "apiServerUrl": "URL del API server",
@@ -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"
@@ -1762,6 +1763,10 @@
1762
1763
  },
1763
1764
  "kubernetesEngine": {
1764
1765
  "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.",
1766
+ "autoSetup": {
1767
+ "title": "Configuration automatique avec la CLI",
1768
+ "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."
1769
+ },
1765
1770
  "label": "Libellé de la connexion",
1766
1771
  "labelPlaceholder": "Cluster de prévisualisation",
1767
1772
  "apiServerUrl": "URL de l'API server",
@@ -12,6 +12,7 @@
12
12
  "save": "שמור",
13
13
  "cancel": "ביטול",
14
14
  "retry": "נסה שוב",
15
+ "copy": "העתק",
15
16
  "block": "בלוק",
16
17
  "actionFailed": "הפעולה נכשלה",
17
18
  "close": "סגור"
@@ -1311,6 +1312,10 @@
1311
1312
  },
1312
1313
  "kubernetesEngine": {
1313
1314
  "localK3sHint": "מולא מראש עבור אשכול k3s/k3d/kind מקומי במחשב הזה. קשרו ServiceAccount לתפקיד, הנפיקו עבורו token באמצעות `kubectl create token NAME -n NAMESPACE` (Kubernetes 1.24+) והדביקו אותו למטה. לאחר מכן בחרו כיצד נגזרת כתובת ה-URL של הסביבה, וערכו את כתובת ה-API server אם האשכול שלכם מאזין ביציאה אחרת.",
1315
+ "autoSetup": {
1316
+ "title": "הגדרה אוטומטית באמצעות ה-CLI",
1317
+ "description": "הרץ זאת בטרמינל כדי לזהות או להקצות אשכול מקומי, ליצור אסימון ServiceAccount ולפתוח טופס זה כשהוא ממולא מראש. הדבק את האסימון המוצג, ולאחר מכן בצע בדיקה ושמירה."
1318
+ },
1314
1319
  "label": "תווית החיבור",
1315
1320
  "labelPlaceholder": "אשכול תצוגה מקדימה",
1316
1321
  "apiServerUrl": "כתובת ה-API server",
@@ -12,6 +12,7 @@
12
12
  "save": "保存",
13
13
  "cancel": "キャンセル",
14
14
  "retry": "再試行",
15
+ "copy": "コピー",
15
16
  "block": "ブロック",
16
17
  "actionFailed": "操作に失敗しました",
17
18
  "close": "閉じる"
@@ -1313,6 +1314,10 @@
1313
1314
  },
1314
1315
  "kubernetesEngine": {
1315
1316
  "localK3sHint": "このマシン上のローカル k3s/k3d/kind クラスター向けにあらかじめ入力されています。ServiceAccount をロールにバインドし、`kubectl create token NAME -n NAMESPACE`(Kubernetes 1.24 以降)でトークンを発行して下記に貼り付けてください。その後、環境 URL の導出方法を選択し、クラスターが別のポートで待ち受けている場合は API サーバー URL を編集してください。",
1317
+ "autoSetup": {
1318
+ "title": "CLI による自動セットアップ",
1319
+ "description": "これをターミナルで実行すると、ローカルクラスターを検出またはプロビジョニングし、ServiceAccount トークンを生成して、このフォームを事前入力した状態で開きます。表示されたトークンを貼り付けてから、テストして保存してください。"
1320
+ },
1316
1321
  "label": "接続ラベル",
1317
1322
  "labelPlaceholder": "プレビュークラスター",
1318
1323
  "apiServerUrl": "API サーバー URL",
@@ -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"
@@ -1762,6 +1763,10 @@
1762
1763
  },
1763
1764
  "kubernetesEngine": {
1764
1765
  "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.",
1766
+ "autoSetup": {
1767
+ "title": "Automatyczna konfiguracja przez CLI",
1768
+ "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."
1769
+ },
1765
1770
  "label": "Etykieta połączenia",
1766
1771
  "labelPlaceholder": "Klaster podglądu",
1767
1772
  "apiServerUrl": "URL serwera API",
@@ -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"
@@ -1313,6 +1314,10 @@
1313
1314
  },
1314
1315
  "kubernetesEngine": {
1315
1316
  "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.",
1317
+ "autoSetup": {
1318
+ "title": "CLI ile otomatik kurulum",
1319
+ "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."
1320
+ },
1316
1321
  "label": "Bağlantı etiketi",
1317
1322
  "labelPlaceholder": "Önizleme kümesi",
1318
1323
  "apiServerUrl": "API sunucu URL'si",
@@ -12,6 +12,7 @@
12
12
  "save": "Зберегти",
13
13
  "cancel": "Скасувати",
14
14
  "retry": "Повторити",
15
+ "copy": "Копіювати",
15
16
  "actionFailed": "Не вдалося виконати дію",
16
17
  "close": "Закрити",
17
18
  "block": "Блок"
@@ -1762,6 +1763,10 @@
1762
1763
  },
1763
1764
  "kubernetesEngine": {
1764
1765
  "localK3sHint": "Попередньо заповнено для локального кластера k3s/k3d/kind на цьому комп'ютері. Прив'яжіть ServiceAccount до ролі, згенеруйте його токен командою `kubectl create token NAME -n NAMESPACE` (Kubernetes 1.24+) і вставте його нижче. Потім виберіть, як визначається URL середовища, і змініть URL сервера API, якщо ваш кластер слухає на іншому порту.",
1766
+ "autoSetup": {
1767
+ "title": "Автоматичне налаштування через CLI",
1768
+ "description": "Запустіть це в терміналі, щоб виявити або підготувати локальний кластер, згенерувати токен ServiceAccount і відкрити цю форму заздалегідь заповненою. Вставте показаний токен, потім натисніть «Перевірити» та «Зберегти»."
1769
+ },
1765
1770
  "label": "Мітка з'єднання",
1766
1771
  "labelPlaceholder": "Кластер попереднього перегляду",
1767
1772
  "apiServerUrl": "URL сервера API",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.65.0",
3
+ "version": "0.66.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",