@cat-factory/app 0.120.0 → 0.121.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.
@@ -21,6 +21,7 @@ const tasks = useTasksStore()
21
21
  const tracker = useTrackerStore()
22
22
  const releaseHealth = useReleaseHealthStore()
23
23
  const packageRegistries = usePackageRegistriesStore()
24
+ const publicApiKeys = usePublicApiKeysStore()
24
25
  const userSecrets = useUserSecretsStore()
25
26
  const apiKeys = useApiKeysStore()
26
27
  const workspace = useWorkspaceStore()
@@ -51,6 +52,7 @@ watch(
51
52
  query.value = ''
52
53
  void releaseHealth.ensureLoaded().catch(() => {})
53
54
  void packageRegistries.ensureLoaded().catch(() => {})
55
+ void publicApiKeys.ensureLoaded().catch(() => {})
54
56
  void userSecrets.load().catch(() => {})
55
57
  // Drives the OpenRouter row's "Key connected" badge.
56
58
  if (workspace.workspaceId) void apiKeys.load(workspace.workspaceId).catch(() => {})
@@ -269,26 +271,36 @@ const groups = computed<IntegrationGroup[]>(() => {
269
271
  })
270
272
  }
271
273
 
272
- // --- Development (private package registries) -------------------------------
273
- // Gated like observability: hidden until a probe confirms the module is wired
274
- // (`available === true`), so an unconfigured backend doesn't show a dead row.
274
+ // --- Development (private package registries + API access tokens) -----------
275
+ // Each row is gated like observability: hidden until a probe confirms its module is
276
+ // wired (`available === true`), so an unconfigured backend doesn't show a dead row.
277
+ const development: IntegrationItem[] = []
275
278
  if (packageRegistries.available) {
276
279
  const hasEntries = packageRegistries.entries.length > 0
277
- out.push({
278
- title: t('layout.integrationsHub.groups.development'),
279
- items: [
280
- {
281
- key: 'package-registries',
282
- icon: 'i-lucide-package',
283
- label: t('layout.integrationsHub.items.packageRegistries.label'),
284
- description: t('layout.integrationsHub.items.packageRegistries.description'),
285
- status: hasEntries ? t('layout.integrationsHub.status.connected') : undefined,
286
- connected: hasEntries,
287
- onClick: () => go(ui.openPackageRegistries),
288
- },
289
- ],
280
+ development.push({
281
+ key: 'package-registries',
282
+ icon: 'i-lucide-package',
283
+ label: t('layout.integrationsHub.items.packageRegistries.label'),
284
+ description: t('layout.integrationsHub.items.packageRegistries.description'),
285
+ status: hasEntries ? t('layout.integrationsHub.status.connected') : undefined,
286
+ connected: hasEntries,
287
+ onClick: () => go(ui.openPackageRegistries),
288
+ })
289
+ }
290
+ if (publicApiKeys.available) {
291
+ const hasKeys = publicApiKeys.keys.length > 0
292
+ development.push({
293
+ key: 'api-tokens',
294
+ icon: 'i-lucide-key-round',
295
+ label: t('layout.integrationsHub.items.apiTokens.label'),
296
+ description: t('layout.integrationsHub.items.apiTokens.description'),
297
+ status: hasKeys ? t('layout.integrationsHub.status.connected') : undefined,
298
+ connected: hasKeys,
299
+ onClick: () => go(ui.openApiTokens),
290
300
  })
291
301
  }
302
+ if (development.length)
303
+ out.push({ title: t('layout.integrationsHub.groups.development'), items: development })
292
304
 
293
305
  // NOTE: Infrastructure (agent-container execution + Tester environments + the local-mode
294
306
  // warm pool/checkout) is no longer listed here — it moved to its OWN top-level navbar menu
@@ -0,0 +1,205 @@
1
+ <script setup lang="ts">
2
+ // API access tokens — the workspace's inbound public-API keys external systems present to the
3
+ // `/api/v1` surface (`Authorization: Bearer cf_live_…`). Keys are hashed one-way server-side,
4
+ // so the raw secret is shown EXACTLY ONCE, on create; the list thereafter renders metadata
5
+ // only (label + created / last-used). To rotate a token, revoke it and mint a new one.
6
+ // Opened from the Integrations hub.
7
+ import { computed, ref, watch } from 'vue'
8
+ import type { PublicApiKey } from '~/types/publicApiKeys'
9
+ import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
10
+ import CopyButton from '~/components/common/CopyButton.vue'
11
+
12
+ const { t, d } = useI18n()
13
+ const ui = useUiStore()
14
+ const store = usePublicApiKeysStore()
15
+ const toast = useToast()
16
+ const { confirmAction, toastDone } = useConfirmAction()
17
+
18
+ const open = computed({
19
+ get: () => ui.apiTokensOpen,
20
+ set: (v: boolean) => (v ? ui.openApiTokens() : ui.closeApiTokens()),
21
+ })
22
+ const back = useIntegrationBack(open)
23
+
24
+ const label = ref('')
25
+ const busy = ref(false)
26
+ // The full raw secret from the most recent create — surfaced once, then dismissed. Never
27
+ // re-fetchable, so it lives only in this transient ref (not the store).
28
+ const newSecret = ref<string | null>(null)
29
+
30
+ function notifyError(title: string, e: unknown) {
31
+ toast.add({
32
+ title,
33
+ description: e instanceof Error ? e.message : String(e),
34
+ icon: 'i-lucide-triangle-alert',
35
+ color: 'error',
36
+ })
37
+ }
38
+
39
+ watch(
40
+ open,
41
+ async (isOpen) => {
42
+ if (!isOpen) {
43
+ // Never leave a revealed secret hanging around once the panel closes.
44
+ newSecret.value = null
45
+ return
46
+ }
47
+ try {
48
+ await store.ensureLoaded()
49
+ } catch (e) {
50
+ notifyError(t('settings.apiTokens.toast.loadFailed'), e)
51
+ }
52
+ },
53
+ { immediate: true },
54
+ )
55
+
56
+ async function createToken() {
57
+ const trimmed = label.value.trim()
58
+ if (!trimmed) return
59
+ busy.value = true
60
+ try {
61
+ const created = await store.create(trimmed)
62
+ newSecret.value = created.secret
63
+ label.value = ''
64
+ toast.add({
65
+ title: t('settings.apiTokens.toast.created'),
66
+ icon: 'i-lucide-check',
67
+ color: 'success',
68
+ })
69
+ } catch (e) {
70
+ notifyError(t('settings.apiTokens.toast.createFailed'), e)
71
+ } finally {
72
+ busy.value = false
73
+ }
74
+ }
75
+
76
+ function dismissSecret() {
77
+ newSecret.value = null
78
+ }
79
+
80
+ async function revokeToken(key: PublicApiKey) {
81
+ if (!(await confirmAction('revoke', key.label))) return
82
+ busy.value = true
83
+ try {
84
+ await store.revoke(key.id)
85
+ toastDone('revoke', key.label)
86
+ } catch (e) {
87
+ notifyError(t('settings.apiTokens.toast.revokeFailed'), e)
88
+ } finally {
89
+ busy.value = false
90
+ }
91
+ }
92
+ </script>
93
+
94
+ <template>
95
+ <UModal v-model:open="open" :title="t('settings.apiTokens.title')" :ui="{ content: 'max-w-lg' }">
96
+ <template #title>
97
+ <IntegrationBackTitle :title="t('settings.apiTokens.title')" @back="back" />
98
+ </template>
99
+ <template #body>
100
+ <div class="space-y-4" data-testid="api-tokens-panel">
101
+ <p class="text-sm text-slate-400">
102
+ {{ t('settings.apiTokens.intro') }}
103
+ </p>
104
+
105
+ <!-- One-time secret reveal: shown once after create, dismissed by the user. The full
106
+ key is never recoverable, so it must be copied now. -->
107
+ <section
108
+ v-if="newSecret"
109
+ class="space-y-2 rounded-lg border border-primary-500/40 bg-primary-500/10 p-3"
110
+ data-testid="api-token-secret"
111
+ >
112
+ <div class="flex items-center gap-2 text-sm font-medium text-primary-200">
113
+ <UIcon name="i-lucide-key-round" class="h-4 w-4 shrink-0" />
114
+ <span>{{ t('settings.apiTokens.secret.heading') }}</span>
115
+ </div>
116
+ <p class="text-xs text-slate-300">{{ t('settings.apiTokens.secret.warning') }}</p>
117
+ <div
118
+ class="flex items-center gap-2 rounded-md border border-slate-700 bg-slate-950/60 px-3 py-2"
119
+ >
120
+ <code class="min-w-0 flex-1 truncate font-mono text-xs text-slate-100">{{
121
+ newSecret
122
+ }}</code>
123
+ <CopyButton :text="newSecret" :label="t('settings.apiTokens.secret.copy')" size="sm" />
124
+ </div>
125
+ <div class="flex justify-end">
126
+ <UButton
127
+ color="neutral"
128
+ variant="ghost"
129
+ size="xs"
130
+ data-testid="api-token-secret-dismiss"
131
+ @click="dismissSecret"
132
+ >
133
+ {{ t('settings.apiTokens.secret.done') }}
134
+ </UButton>
135
+ </div>
136
+ </section>
137
+
138
+ <section v-if="store.keys.length" class="space-y-2 rounded-lg border border-slate-700 p-3">
139
+ <h3 class="text-sm font-semibold">
140
+ {{ t('settings.apiTokens.list.heading') }}
141
+ </h3>
142
+ <div
143
+ v-for="key in store.keys"
144
+ :key="key.id"
145
+ class="flex items-center justify-between gap-2 rounded-md border border-slate-800 px-3 py-2"
146
+ >
147
+ <div class="min-w-0 space-y-0.5">
148
+ <div class="truncate text-sm font-medium">{{ key.label }}</div>
149
+ <div class="text-[11px] text-slate-500">
150
+ {{
151
+ t('settings.apiTokens.list.created', {
152
+ date: d(new Date(key.createdAt), 'short'),
153
+ })
154
+ }}
155
+ <span aria-hidden="true"> · </span>
156
+ <template v-if="key.lastUsedAt">{{
157
+ t('settings.apiTokens.list.lastUsed', {
158
+ date: d(new Date(key.lastUsedAt), 'short'),
159
+ })
160
+ }}</template>
161
+ <template v-else>{{ t('settings.apiTokens.list.neverUsed') }}</template>
162
+ </div>
163
+ </div>
164
+ <UButton
165
+ color="error"
166
+ variant="ghost"
167
+ icon="i-lucide-ban"
168
+ size="sm"
169
+ :loading="busy"
170
+ :data-testid="`api-token-revoke-${key.id}`"
171
+ :aria-label="t('settings.apiTokens.list.revoke')"
172
+ @click="revokeToken(key)"
173
+ />
174
+ </div>
175
+ </section>
176
+
177
+ <section class="space-y-3 rounded-lg border border-slate-700 p-3">
178
+ <h3 class="text-sm font-semibold">
179
+ {{ t('settings.apiTokens.add.heading') }}
180
+ </h3>
181
+ <UFormField
182
+ :label="t('settings.apiTokens.add.label')"
183
+ :help="t('settings.apiTokens.add.labelHelp')"
184
+ >
185
+ <UInput
186
+ v-model="label"
187
+ :placeholder="t('settings.apiTokens.add.labelPlaceholder')"
188
+ class="w-full"
189
+ data-testid="api-token-label"
190
+ @keyup.enter="createToken"
191
+ />
192
+ </UFormField>
193
+ <UButton
194
+ :loading="busy"
195
+ :disabled="!label.trim()"
196
+ data-testid="api-token-create"
197
+ @click="createToken"
198
+ >
199
+ {{ t('settings.apiTokens.add.create') }}
200
+ </UButton>
201
+ </section>
202
+ </div>
203
+ </template>
204
+ </UModal>
205
+ </template>
@@ -0,0 +1,26 @@
1
+ import {
2
+ createPublicApiKeyContract,
3
+ listPublicApiKeysContract,
4
+ revokePublicApiKeyContract,
5
+ } from '@cat-factory/contracts'
6
+ import type { CreatePublicApiKeyInput } from '~/types/publicApiKeys'
7
+ import type { ApiContext } from './context'
8
+
9
+ /**
10
+ * Inbound public-API keys ("API access tokens") a workspace mints for external systems to
11
+ * call the `/api/v1` surface. Management routes are session-authed under
12
+ * `/workspaces/:workspaceId`; the raw secret comes back only on create. See
13
+ * PublicApiKeyController.
14
+ */
15
+ export function publicApiKeysApi({ send, ws }: ApiContext) {
16
+ return {
17
+ listPublicApiKeys: (workspaceId: string) =>
18
+ send(listPublicApiKeysContract, { pathPrefix: ws(workspaceId) }),
19
+
20
+ createPublicApiKey: (workspaceId: string, body: CreatePublicApiKeyInput) =>
21
+ send(createPublicApiKeyContract, { pathPrefix: ws(workspaceId), body }),
22
+
23
+ revokePublicApiKey: (workspaceId: string, id: string) =>
24
+ send(revokePublicApiKeyContract, { pathPrefix: ws(workspaceId), pathParams: { id } }),
25
+ }
26
+ }
@@ -25,6 +25,7 @@ import { notificationsApi } from './api/notifications'
25
25
  import { packageRegistriesApi } from './api/packageRegistries'
26
26
  import { preflightsApi } from './api/preflights'
27
27
  import { presetsApi } from './api/presets'
28
+ import { publicApiKeysApi } from './api/publicApiKeys'
28
29
  import { sharedStacksApi } from './api/sharedStacks'
29
30
  import { providerConnectionsApi } from './api/providerConnections'
30
31
  import { provisioningLogsApi } from './api/provisioningLogs'
@@ -121,6 +122,7 @@ export function useApi() {
121
122
  ...notificationsApi(ctx),
122
123
  ...presetsApi(ctx),
123
124
  ...preflightsApi(ctx),
125
+ ...publicApiKeysApi(ctx),
124
126
  ...sharedStacksApi(ctx),
125
127
  ...providerConnectionsApi(ctx),
126
128
  ...infraHandlersApi(ctx),
@@ -96,6 +96,9 @@ const ObservabilityConnectionPanel = defineAsyncComponent(
96
96
  const PackageRegistriesPanel = defineAsyncComponent(
97
97
  () => import('~/components/settings/PackageRegistriesPanel.vue'),
98
98
  )
99
+ const ApiTokensPanel = defineAsyncComponent(
100
+ () => import('~/components/settings/ApiTokensPanel.vue'),
101
+ )
99
102
  const InfrastructureWindow = defineAsyncComponent(
100
103
  () => import('~/components/settings/InfrastructureWindow.vue'),
101
104
  )
@@ -402,6 +405,7 @@ watch(
402
405
  <AccountSettingsPanel v-if="ui.accountSettingsOpen" />
403
406
  <ObservabilityConnectionPanel v-if="ui.observabilityConnectionOpen" />
404
407
  <PackageRegistriesPanel v-if="ui.packageRegistriesOpen" />
408
+ <ApiTokensPanel v-if="ui.apiTokensOpen" />
405
409
  <InfrastructureWindow v-if="ui.infrastructureOpen" />
406
410
  <EnvironmentSetupWizard v-if="ui.environmentWizardOpen" />
407
411
  <ModelConfigurationPanel v-if="ui.modelConfigOpen" />
@@ -0,0 +1,105 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest'
2
+ import { usePublicApiKeysStore } from '~/stores/publicApiKeys'
3
+ import { useWorkspaceStore } from '~/stores/workspace'
4
+ import type { CreatedPublicApiKey, PublicApiKey } from '~/types/publicApiKeys'
5
+
6
+ /** Minimal metadata-view factory — only the fields the store passes through. */
7
+ function key(over: Partial<PublicApiKey> = {}): PublicApiKey {
8
+ return {
9
+ id: 'pak_1',
10
+ accountId: 'acc1',
11
+ workspaceId: 'ws1',
12
+ label: 'CI',
13
+ createdAt: 1,
14
+ lastUsedAt: null,
15
+ revokedAt: null,
16
+ ...over,
17
+ }
18
+ }
19
+
20
+ describe('publicApiKeys store', () => {
21
+ beforeEach(() => {
22
+ useWorkspaceStore().workspaceId = 'ws1'
23
+ })
24
+
25
+ it('load stores the key list and marks the feature available', async () => {
26
+ vi.stubGlobal('useApi', () => ({
27
+ listPublicApiKeys: () => Promise.resolve({ keys: [key()] }),
28
+ }))
29
+
30
+ const store = usePublicApiKeysStore()
31
+ await store.load()
32
+
33
+ expect(store.available).toBe(true)
34
+ expect(store.keys).toHaveLength(1)
35
+ expect(store.loading).toBe(false)
36
+ })
37
+
38
+ it('a definitive 503 latches the feature unavailable and clears the list', async () => {
39
+ vi.stubGlobal('useApi', () => ({
40
+ listPublicApiKeys: () => Promise.reject({ statusCode: 503 }),
41
+ }))
42
+
43
+ const store = usePublicApiKeysStore()
44
+ await store.load()
45
+
46
+ expect(store.available).toBe(false)
47
+ expect(store.keys).toEqual([])
48
+ })
49
+
50
+ it('a transient failure leaves `available` null so ensureLoaded stays retryable', async () => {
51
+ vi.stubGlobal('useApi', () => ({
52
+ listPublicApiKeys: () => Promise.reject({ statusCode: 500 }),
53
+ }))
54
+
55
+ const store = usePublicApiKeysStore()
56
+ await store.load()
57
+
58
+ // Never latched — a network/5xx blip must not hide an otherwise-available panel.
59
+ expect(store.available).toBeNull()
60
+ })
61
+
62
+ it('ensureLoaded coalesces concurrent callers into one request', async () => {
63
+ const list = vi.fn(() => Promise.resolve({ keys: [key()] }))
64
+ vi.stubGlobal('useApi', () => ({ listPublicApiKeys: list }))
65
+
66
+ const store = usePublicApiKeysStore()
67
+ await Promise.all([store.ensureLoaded(), store.ensureLoaded()])
68
+ // And once probed, it never re-fetches.
69
+ await store.ensureLoaded()
70
+
71
+ expect(list).toHaveBeenCalledTimes(1)
72
+ })
73
+
74
+ it('create prepends the new key (newest-first) and returns the one-time secret', async () => {
75
+ const created: CreatedPublicApiKey = {
76
+ key: key({ id: 'pak_new', label: 'deploy' }),
77
+ secret: 'cf_live_pak_new.abc',
78
+ }
79
+ vi.stubGlobal('useApi', () => ({
80
+ listPublicApiKeys: () => Promise.resolve({ keys: [key({ id: 'pak_old' })] }),
81
+ createPublicApiKey: () => Promise.resolve(created),
82
+ }))
83
+
84
+ const store = usePublicApiKeysStore()
85
+ await store.load()
86
+ const result = await store.create('deploy')
87
+
88
+ expect(result.secret).toBe('cf_live_pak_new.abc')
89
+ expect(store.keys.map((k) => k.id)).toEqual(['pak_new', 'pak_old'])
90
+ expect(store.available).toBe(true)
91
+ })
92
+
93
+ it('revoke drops the key from the list', async () => {
94
+ vi.stubGlobal('useApi', () => ({
95
+ listPublicApiKeys: () => Promise.resolve({ keys: [key({ id: 'a' }), key({ id: 'b' })] }),
96
+ revokePublicApiKey: () => Promise.resolve(),
97
+ }))
98
+
99
+ const store = usePublicApiKeysStore()
100
+ await store.load()
101
+ await store.revoke('a')
102
+
103
+ expect(store.keys.map((k) => k.id)).toEqual(['b'])
104
+ })
105
+ })
@@ -0,0 +1,72 @@
1
+ import { defineStore } from 'pinia'
2
+ import { ref } from 'vue'
3
+ import type { CreatedPublicApiKey, PublicApiKey } from '~/types/publicApiKeys'
4
+ import { useWorkspaceStore } from '~/stores/workspace'
5
+ import { apiErrorStatus } from '~/composables/api/errors'
6
+
7
+ /**
8
+ * The workspace's inbound public-API keys ("API access tokens") external systems present
9
+ * to the `/api/v1` surface. Secrets are one-way hashed server-side and returned only once
10
+ * on create, so the store holds metadata-only views; the raw secret is surfaced by the
11
+ * caller from the `create()` result. Loaded on demand (the tokens panel + the Integrations
12
+ * hub badge), not from the snapshot.
13
+ */
14
+ export const usePublicApiKeysStore = defineStore('publicApiKeys', () => {
15
+ const api = useApi()
16
+
17
+ const keys = ref<PublicApiKey[]>([])
18
+ const loading = ref(false)
19
+ // Mirrors the backend's opt-in gate (the module 503s when the encryption key is absent):
20
+ // `null` until first probed, then `true`/`false`. The hub hides its tokens entry point
21
+ // when this is false.
22
+ const available = ref<boolean | null>(null)
23
+ let inFlight: Promise<void> | null = null
24
+
25
+ /** Force a refresh of the key list (used after a create/revoke). */
26
+ async function load() {
27
+ const ws = useWorkspaceStore()
28
+ loading.value = true
29
+ try {
30
+ keys.value = (await api.listPublicApiKeys(ws.requireId())).keys
31
+ available.value = true
32
+ } catch (err) {
33
+ if (apiErrorStatus(err) === 503) {
34
+ // A definitive 503 means the feature is unconfigured (no encryption key on the
35
+ // backend): hide the UI entry points and stop probing.
36
+ available.value = false
37
+ keys.value = []
38
+ }
39
+ // Any other failure (transient 5xx / network) is left untouched: it must not hide an
40
+ // already-available panel nor cache a false "unavailable". `available` stays `null`
41
+ // when never probed, so `ensureLoaded` remains retryable on the next open.
42
+ } finally {
43
+ loading.value = false
44
+ }
45
+ }
46
+
47
+ /** Load once and share the result (coalescing concurrent callers); `load()` refreshes. */
48
+ async function ensureLoaded() {
49
+ if (available.value !== null) return
50
+ if (!inFlight) inFlight = load().finally(() => (inFlight = null))
51
+ return inFlight
52
+ }
53
+
54
+ /** Mint a key. Returns the created record PLUS the one-time raw secret (shown once). */
55
+ async function create(label: string): Promise<CreatedPublicApiKey> {
56
+ const ws = useWorkspaceStore()
57
+ const created = await api.createPublicApiKey(ws.requireId(), { label })
58
+ // Prepend: the backend lists newest-first, so the freshly minted key belongs at the
59
+ // top — matching the order a subsequent `load()` would produce.
60
+ keys.value = [created.key, ...keys.value]
61
+ available.value = true
62
+ return created
63
+ }
64
+
65
+ async function revoke(id: string) {
66
+ const ws = useWorkspaceStore()
67
+ await api.revokePublicApiKey(ws.requireId(), id)
68
+ keys.value = keys.value.filter((k) => k.id !== id)
69
+ }
70
+
71
+ return { keys, loading, available, load, ensureLoaded, create, revoke }
72
+ })
package/app/stores/ui.ts CHANGED
@@ -168,6 +168,9 @@ export const useUiStore = defineStore('ui', () => {
168
168
  // Private package registries: the workspace's npm/GitHub-Packages entries agent
169
169
  // containers install with. Opened from the Integrations hub.
170
170
  const packageRegistriesOpen = ref(false)
171
+ // API access tokens: the workspace's inbound public-API keys external systems present to
172
+ // the `/api/v1` surface. Opened from the Integrations hub.
173
+ const apiTokensOpen = ref(false)
171
174
  // The single tabbed Infrastructure window — a TOP-LEVEL navbar destination (no longer
172
175
  // reached via the Integrations hub). Two topical tabs: "Agent containers" (the execution
173
176
  // backend + self-hosted runner pool, plus the local-mode warm pool/checkout) and "Test
@@ -618,6 +621,13 @@ export const useUiStore = defineStore('ui', () => {
618
621
  function closePackageRegistries() {
619
622
  packageRegistriesOpen.value = false
620
623
  }
624
+ function openApiTokens() {
625
+ resetHubReturn()
626
+ apiTokensOpen.value = true
627
+ }
628
+ function closeApiTokens() {
629
+ apiTokensOpen.value = false
630
+ }
621
631
  // Top-level navbar entry into the Infrastructure window. No hub-return marker (it isn't
622
632
  // reached from the Integrations hub), so the window shows no "Back to Integrations" control.
623
633
  function openInfrastructure(tab: 'environment' | 'runner-pool' = 'runner-pool') {
@@ -923,6 +933,7 @@ export const useUiStore = defineStore('ui', () => {
923
933
  accountSettingsScrollTarget,
924
934
  observabilityConnectionOpen,
925
935
  packageRegistriesOpen,
936
+ apiTokensOpen,
926
937
  infrastructureOpen,
927
938
  infrastructureTab,
928
939
  openInfrastructure,
@@ -1021,6 +1032,8 @@ export const useUiStore = defineStore('ui', () => {
1021
1032
  closeObservabilityConnection,
1022
1033
  openPackageRegistries,
1023
1034
  closePackageRegistries,
1035
+ openApiTokens,
1036
+ closeApiTokens,
1024
1037
  openProviderConnection,
1025
1038
  closeProviderConnection,
1026
1039
  k3sSetupPrefill,
@@ -0,0 +1,10 @@
1
+ // Inbound public-API keys — the "API access tokens" external systems present to the
2
+ // `/api/v1` surface (`Authorization: Bearer cf_live_…`). Workspace-scoped; the raw secret
3
+ // is returned exactly once on create and never again, so the list only ever holds metadata.
4
+ // Re-exported from the shared contracts package (the single source of truth).
5
+ export type {
6
+ PublicApiKey,
7
+ PublicApiKeyListResult,
8
+ CreatePublicApiKeyInput,
9
+ CreatedPublicApiKey,
10
+ } from '@cat-factory/contracts'
@@ -519,6 +519,36 @@
519
519
  "removeFailed": "Der Registry-Eintrag konnte nicht entfernt werden"
520
520
  }
521
521
  },
522
+ "apiTokens": {
523
+ "title": "API-Zugriffstokens",
524
+ "intro": "Erstelle Tokens, die externe Systeme der cat-factory-API vorlegen. Jedes Token authentifiziert sich als dieser Arbeitsbereich an den /api/v1-Endpunkten. Das Geheimnis wird nur einmal bei der Erstellung angezeigt und kann nicht wiederhergestellt werden. Speichere es daher sofort.",
525
+ "secret": {
526
+ "heading": "Kopiere dein Token jetzt",
527
+ "warning": "Dies ist das einzige Mal, dass das vollständige Token angezeigt wird. Bewahre es sicher auf; es kann nicht wiederhergestellt werden.",
528
+ "copy": "Token kopieren",
529
+ "done": "Fertig"
530
+ },
531
+ "list": {
532
+ "heading": "Aktive Tokens",
533
+ "created": "Erstellt am {date}",
534
+ "lastUsed": "zuletzt verwendet am {date}",
535
+ "neverUsed": "nie verwendet",
536
+ "revoke": "Token widerrufen"
537
+ },
538
+ "add": {
539
+ "heading": "Token erstellen",
540
+ "label": "Bezeichnung",
541
+ "labelHelp": "Ein Name, um dieses Token später wiederzuerkennen.",
542
+ "labelPlaceholder": "z. B. CI-Pipeline",
543
+ "create": "Token erstellen"
544
+ },
545
+ "toast": {
546
+ "loadFailed": "API-Tokens konnten nicht geladen werden",
547
+ "created": "Token erstellt",
548
+ "createFailed": "Token konnte nicht erstellt werden",
549
+ "revokeFailed": "Token konnte nicht widerrufen werden"
550
+ }
551
+ },
522
552
  "sharedStacks": {
523
553
  "tab": "Gemeinsame Stacks",
524
554
  "intro": "Langlebige Compose-Infrastruktur (Datenbanken, Broker, Suche, Mail), die einmal pro Workspace hochgefahren und über Ausführungen und Pull Requests hinweg wiederverwendet wird. Eine Testumgebung verbindet sich mit dem verwalteten Netzwerk eines Stacks. Das Hochfahren eines Stacks läuft auf einem lokalen Docker-Deployment; auf anderen Backends kannst du die Definition dennoch verwalten.",
@@ -1916,6 +1946,10 @@
1916
1946
  "label": "Private Package-Registries",
1917
1947
  "description": "npm- und GitHub-Packages-Tokens, mit denen Agenten private Abhängigkeiten installieren."
1918
1948
  },
1949
+ "apiTokens": {
1950
+ "label": "API-Zugriffstokens",
1951
+ "description": "Tokens, die externe Systeme vorlegen, um die cat-factory-API aufzurufen."
1952
+ },
1919
1953
  "githubPat": {
1920
1954
  "label": "Mein GitHub-Token",
1921
1955
  "description": "Ein Personal Access Token, der für von Ihnen gestartete Läufe verwendet wird (Pushes, PRs, CI, Merge)."
@@ -1859,6 +1859,10 @@
1859
1859
  "label": "Private package registries",
1860
1860
  "description": "npm and GitHub Packages tokens agents use to install private dependencies."
1861
1861
  },
1862
+ "apiTokens": {
1863
+ "label": "API access tokens",
1864
+ "description": "Tokens external systems present to call the cat-factory API."
1865
+ },
1862
1866
  "githubPat": {
1863
1867
  "label": "My GitHub token",
1864
1868
  "description": "A personal access token used for runs you start (pushes, PRs, CI, merge)."
@@ -2396,6 +2400,36 @@
2396
2400
  "removeFailed": "Could not remove the registry entry"
2397
2401
  }
2398
2402
  },
2403
+ "apiTokens": {
2404
+ "title": "API access tokens",
2405
+ "intro": "Create tokens that external systems present to the cat-factory API. Each token authenticates as this workspace on the /api/v1 endpoints. The secret is shown only once, when you create it, and cannot be recovered, so store it right away.",
2406
+ "secret": {
2407
+ "heading": "Copy your token now",
2408
+ "warning": "This is the only time the full token is shown. Store it somewhere safe; it cannot be recovered.",
2409
+ "copy": "Copy token",
2410
+ "done": "Done"
2411
+ },
2412
+ "list": {
2413
+ "heading": "Active tokens",
2414
+ "created": "Created {date}",
2415
+ "lastUsed": "last used {date}",
2416
+ "neverUsed": "never used",
2417
+ "revoke": "Revoke token"
2418
+ },
2419
+ "add": {
2420
+ "heading": "Create a token",
2421
+ "label": "Label",
2422
+ "labelHelp": "A name to recognize this token by later.",
2423
+ "labelPlaceholder": "e.g. CI pipeline",
2424
+ "create": "Create token"
2425
+ },
2426
+ "toast": {
2427
+ "loadFailed": "Could not load API tokens",
2428
+ "created": "Token created",
2429
+ "createFailed": "Could not create the token",
2430
+ "revokeFailed": "Could not revoke the token"
2431
+ }
2432
+ },
2399
2433
  "sharedStacks": {
2400
2434
  "tab": "Shared stacks",
2401
2435
  "intro": "Long-lived compose infrastructure (databases, brokers, search, mail) brought up once per workspace and reused across runs and pull requests. A test environment attaches to a stack's managed network. Bringing a stack up runs on a local Docker deployment; on other backends you can still manage the definition.",
@@ -1792,6 +1792,10 @@
1792
1792
  "label": "Registros privados de paquetes",
1793
1793
  "description": "Tokens de npm y GitHub Packages que los agentes usan para instalar dependencias privadas."
1794
1794
  },
1795
+ "apiTokens": {
1796
+ "label": "Tokens de acceso a la API",
1797
+ "description": "Tokens que los sistemas externos presentan para llamar a la API de cat-factory."
1798
+ },
1795
1799
  "githubPat": {
1796
1800
  "label": "Mi token de GitHub",
1797
1801
  "description": "Un token de acceso personal usado para las ejecuciones que inicias (pushes, PR, CI, fusión)."
@@ -2211,6 +2215,36 @@
2211
2215
  "removeFailed": "No se pudo eliminar la entrada del registro"
2212
2216
  }
2213
2217
  },
2218
+ "apiTokens": {
2219
+ "title": "Tokens de acceso a la API",
2220
+ "intro": "Crea tokens que los sistemas externos presentan a la API de cat-factory. Cada token se autentica como este espacio de trabajo en los endpoints /api/v1. El secreto se muestra una sola vez, al crearlo, y no se puede recuperar, así que guárdalo de inmediato.",
2221
+ "secret": {
2222
+ "heading": "Copia tu token ahora",
2223
+ "warning": "Esta es la única vez que se muestra el token completo. Guárdalo en un lugar seguro; no se puede recuperar.",
2224
+ "copy": "Copiar token",
2225
+ "done": "Listo"
2226
+ },
2227
+ "list": {
2228
+ "heading": "Tokens activos",
2229
+ "created": "Creado el {date}",
2230
+ "lastUsed": "usado por última vez el {date}",
2231
+ "neverUsed": "nunca usado",
2232
+ "revoke": "Revocar token"
2233
+ },
2234
+ "add": {
2235
+ "heading": "Crear un token",
2236
+ "label": "Etiqueta",
2237
+ "labelHelp": "Un nombre para reconocer este token más adelante.",
2238
+ "labelPlaceholder": "p. ej. pipeline de CI",
2239
+ "create": "Crear token"
2240
+ },
2241
+ "toast": {
2242
+ "loadFailed": "No se pudieron cargar los tokens de la API",
2243
+ "created": "Token creado",
2244
+ "createFailed": "No se pudo crear el token",
2245
+ "revokeFailed": "No se pudo revocar el token"
2246
+ }
2247
+ },
2214
2248
  "sharedStacks": {
2215
2249
  "tab": "Stacks compartidos",
2216
2250
  "intro": "Infraestructura de Compose de larga duración (bases de datos, brokers, búsqueda, correo) que se levanta una vez por espacio de trabajo y se reutiliza en todas las ejecuciones y pull requests. Un entorno de pruebas se conecta a la red gestionada de un stack. Levantar un stack requiere un despliegue local de Docker; en otros backends puedes gestionar igualmente la definición.",
@@ -1792,6 +1792,10 @@
1792
1792
  "label": "Registres de paquets privés",
1793
1793
  "description": "Jetons npm et GitHub Packages que les agents utilisent pour installer les dépendances privées."
1794
1794
  },
1795
+ "apiTokens": {
1796
+ "label": "Jetons d'accès à l'API",
1797
+ "description": "Jetons que les systèmes externes présentent pour appeler l'API cat-factory."
1798
+ },
1795
1799
  "githubPat": {
1796
1800
  "label": "Mon jeton GitHub",
1797
1801
  "description": "Un jeton d'accès personnel utilisé pour les exécutions que vous lancez (pushes, PR, CI, fusion)."
@@ -2211,6 +2215,36 @@
2211
2215
  "removeFailed": "Impossible de supprimer l'entrée du registre"
2212
2216
  }
2213
2217
  },
2218
+ "apiTokens": {
2219
+ "title": "Jetons d'accès à l'API",
2220
+ "intro": "Créez des jetons que les systèmes externes présentent à l'API cat-factory. Chaque jeton s'authentifie en tant que cet espace de travail sur les points de terminaison /api/v1. Le secret n'est affiché qu'une seule fois, à sa création, et ne peut pas être récupéré, alors conservez-le immédiatement.",
2221
+ "secret": {
2222
+ "heading": "Copiez votre jeton maintenant",
2223
+ "warning": "C'est la seule fois où le jeton complet est affiché. Conservez-le en lieu sûr ; il ne peut pas être récupéré.",
2224
+ "copy": "Copier le jeton",
2225
+ "done": "Terminé"
2226
+ },
2227
+ "list": {
2228
+ "heading": "Jetons actifs",
2229
+ "created": "Créé le {date}",
2230
+ "lastUsed": "dernière utilisation le {date}",
2231
+ "neverUsed": "jamais utilisé",
2232
+ "revoke": "Révoquer le jeton"
2233
+ },
2234
+ "add": {
2235
+ "heading": "Créer un jeton",
2236
+ "label": "Libellé",
2237
+ "labelHelp": "Un nom pour reconnaître ce jeton plus tard.",
2238
+ "labelPlaceholder": "ex. pipeline CI",
2239
+ "create": "Créer le jeton"
2240
+ },
2241
+ "toast": {
2242
+ "loadFailed": "Impossible de charger les jetons d'API",
2243
+ "created": "Jeton créé",
2244
+ "createFailed": "Impossible de créer le jeton",
2245
+ "revokeFailed": "Impossible de révoquer le jeton"
2246
+ }
2247
+ },
2214
2248
  "sharedStacks": {
2215
2249
  "tab": "Stacks partagés",
2216
2250
  "intro": "Infrastructure Compose de longue durée (bases de données, brokers, recherche, e-mail) démarrée une fois par espace de travail et réutilisée pour toutes les exécutions et pull requests. Un environnement de test se connecte au réseau géré d'un stack. Démarrer un stack nécessite un déploiement Docker local ; sur les autres backends, vous pouvez tout de même gérer la définition.",
@@ -1792,6 +1792,10 @@
1792
1792
  "label": "מאגרי חבילות פרטיים",
1793
1793
  "description": "אסימוני npm ו-GitHub Packages שסוכנים משתמשים בהם להתקנת תלויות פרטיות."
1794
1794
  },
1795
+ "apiTokens": {
1796
+ "label": "אסימוני גישה ל-API",
1797
+ "description": "אסימונים שמערכות חיצוניות מציגות כדי לקרוא ל-API של cat-factory."
1798
+ },
1795
1799
  "githubPat": {
1796
1800
  "label": "אסימון ה-GitHub שלי",
1797
1801
  "description": "אסימון גישה אישי המשמש להרצות שאתה מתחיל (דחיפות, PR, CI, מיזוג)."
@@ -2332,6 +2336,36 @@
2332
2336
  "removeFailed": "הסרת רשומת המאגר נכשלה"
2333
2337
  }
2334
2338
  },
2339
+ "apiTokens": {
2340
+ "title": "אסימוני גישה ל-API",
2341
+ "intro": "צור אסימונים שמערכות חיצוניות מציגות ל-API של cat-factory. כל אסימון מאמת את עצמו כמרחב העבודה הזה בנקודות הקצה /api/v1. הסוד מוצג פעם אחת בלבד, בעת היצירה, ולא ניתן לשחזרו, לכן שמור אותו מיד.",
2342
+ "secret": {
2343
+ "heading": "העתק את האסימון שלך עכשיו",
2344
+ "warning": "זו הפעם היחידה שבה מוצג האסימון המלא. שמור אותו במקום בטוח; לא ניתן לשחזרו.",
2345
+ "copy": "העתק אסימון",
2346
+ "done": "סיום"
2347
+ },
2348
+ "list": {
2349
+ "heading": "אסימונים פעילים",
2350
+ "created": "נוצר בתאריך {date}",
2351
+ "lastUsed": "שימוש אחרון בתאריך {date}",
2352
+ "neverUsed": "מעולם לא היה בשימוש",
2353
+ "revoke": "בטל אסימון"
2354
+ },
2355
+ "add": {
2356
+ "heading": "צור אסימון",
2357
+ "label": "תווית",
2358
+ "labelHelp": "שם שיעזור לך לזהות את האסימון הזה בהמשך.",
2359
+ "labelPlaceholder": "לדוגמה, צינור CI",
2360
+ "create": "צור אסימון"
2361
+ },
2362
+ "toast": {
2363
+ "loadFailed": "לא ניתן לטעון את אסימוני ה-API",
2364
+ "created": "האסימון נוצר",
2365
+ "createFailed": "לא ניתן ליצור את האסימון",
2366
+ "revokeFailed": "לא ניתן לבטל את האסימון"
2367
+ }
2368
+ },
2335
2369
  "sharedStacks": {
2336
2370
  "tab": "מקבצים משותפים",
2337
2371
  "intro": "תשתית Compose ארוכת-טווח (מסדי נתונים, ברוקרים, חיפוש, דואר) שמופעלת פעם אחת לכל סביבת עבודה ומשמשת מחדש בכל ההרצות ובקשות המשיכה. סביבת בדיקה מתחברת לרשת המנוהלת של מקבץ. הפעלת מקבץ מחייבת פריסת Docker מקומית; ב-backends אחרים עדיין ניתן לנהל את ההגדרה.",
@@ -519,6 +519,36 @@
519
519
  "removeFailed": "Impossibile rimuovere la voce del registry"
520
520
  }
521
521
  },
522
+ "apiTokens": {
523
+ "title": "Token di accesso API",
524
+ "intro": "Crea token che i sistemi esterni presentano all'API di cat-factory. Ogni token si autentica come questo spazio di lavoro sugli endpoint /api/v1. Il segreto viene mostrato una sola volta, al momento della creazione, e non può essere recuperato, quindi conservalo subito.",
525
+ "secret": {
526
+ "heading": "Copia il tuo token ora",
527
+ "warning": "Questa è l'unica volta in cui viene mostrato il token completo. Conservalo in un luogo sicuro; non può essere recuperato.",
528
+ "copy": "Copia token",
529
+ "done": "Fatto"
530
+ },
531
+ "list": {
532
+ "heading": "Token attivi",
533
+ "created": "Creato il {date}",
534
+ "lastUsed": "ultimo utilizzo il {date}",
535
+ "neverUsed": "mai utilizzato",
536
+ "revoke": "Revoca token"
537
+ },
538
+ "add": {
539
+ "heading": "Crea un token",
540
+ "label": "Etichetta",
541
+ "labelHelp": "Un nome per riconoscere questo token in seguito.",
542
+ "labelPlaceholder": "es. pipeline CI",
543
+ "create": "Crea token"
544
+ },
545
+ "toast": {
546
+ "loadFailed": "Impossibile caricare i token API",
547
+ "created": "Token creato",
548
+ "createFailed": "Impossibile creare il token",
549
+ "revokeFailed": "Impossibile revocare il token"
550
+ }
551
+ },
522
552
  "sharedStacks": {
523
553
  "tab": "Stack condivisi",
524
554
  "intro": "Infrastruttura compose a lunga durata (database, broker, ricerca, mail) avviata una volta per workspace e riutilizzata tra run e pull request. Un ambiente di test si collega alla rete gestita di uno stack. L'avvio di uno stack viene eseguito su un deployment Docker locale; su altri backend puoi comunque gestire la definizione.",
@@ -1916,6 +1946,10 @@
1916
1946
  "label": "Registry di pacchetti privati",
1917
1947
  "description": "Token npm e GitHub Packages che gli agenti usano per installare dipendenze private."
1918
1948
  },
1949
+ "apiTokens": {
1950
+ "label": "Token di accesso API",
1951
+ "description": "Token che i sistemi esterni presentano per chiamare l'API di cat-factory."
1952
+ },
1919
1953
  "githubPat": {
1920
1954
  "label": "Il mio token GitHub",
1921
1955
  "description": "Un personal access token usato per le esecuzioni che avvii tu (push, PR, CI, merge)."
@@ -1792,6 +1792,10 @@
1792
1792
  "label": "プライベートパッケージレジストリ",
1793
1793
  "description": "エージェントがプライベート依存関係のインストールに使う npm と GitHub Packages のトークン。"
1794
1794
  },
1795
+ "apiTokens": {
1796
+ "label": "APIアクセストークン",
1797
+ "description": "外部システムが cat-factory API を呼び出すために提示するトークン。"
1798
+ },
1795
1799
  "githubPat": {
1796
1800
  "label": "マイ GitHub トークン",
1797
1801
  "description": "自分が開始した実行 (push、PR、CI、マージ) に使用するパーソナルアクセストークン。"
@@ -2333,6 +2337,36 @@
2333
2337
  "removeFailed": "レジストリエントリを削除できませんでした"
2334
2338
  }
2335
2339
  },
2340
+ "apiTokens": {
2341
+ "title": "APIアクセストークン",
2342
+ "intro": "外部システムが cat-factory API に提示するトークンを作成します。各トークンは /api/v1 エンドポイントでこのワークスペースとして認証されます。シークレットは作成時に一度だけ表示され、復元できないため、すぐに保存してください。",
2343
+ "secret": {
2344
+ "heading": "今すぐトークンをコピーしてください",
2345
+ "warning": "完全なトークンが表示されるのはこの一度だけです。安全な場所に保管してください。復元はできません。",
2346
+ "copy": "トークンをコピー",
2347
+ "done": "完了"
2348
+ },
2349
+ "list": {
2350
+ "heading": "有効なトークン",
2351
+ "created": "{date} に作成",
2352
+ "lastUsed": "最終使用 {date}",
2353
+ "neverUsed": "未使用",
2354
+ "revoke": "トークンを取り消す"
2355
+ },
2356
+ "add": {
2357
+ "heading": "トークンを作成",
2358
+ "label": "ラベル",
2359
+ "labelHelp": "後でこのトークンを識別するための名前。",
2360
+ "labelPlaceholder": "例: CI パイプライン",
2361
+ "create": "トークンを作成"
2362
+ },
2363
+ "toast": {
2364
+ "loadFailed": "APIトークンを読み込めませんでした",
2365
+ "created": "トークンを作成しました",
2366
+ "createFailed": "トークンを作成できませんでした",
2367
+ "revokeFailed": "トークンを取り消せませんでした"
2368
+ }
2369
+ },
2336
2370
  "sharedStacks": {
2337
2371
  "tab": "共有スタック",
2338
2372
  "intro": "ワークスペースごとに一度だけ起動され、すべての実行とプルリクエストで再利用される長期稼働の Compose インフラ(データベース、ブローカー、検索、メール)です。テスト環境はスタックのマネージドネットワークに接続します。スタックの起動にはローカルの Docker デプロイが必要です。他のバックエンドでも定義の管理は可能です。",
@@ -1792,6 +1792,10 @@
1792
1792
  "label": "Prywatne rejestry pakietów",
1793
1793
  "description": "Tokeny npm i GitHub Packages, których agenci używają do instalowania prywatnych zależności."
1794
1794
  },
1795
+ "apiTokens": {
1796
+ "label": "Tokeny dostępu do API",
1797
+ "description": "Tokeny, które systemy zewnętrzne przedstawiają, aby wywołać API cat-factory."
1798
+ },
1795
1799
  "githubPat": {
1796
1800
  "label": "Mój token GitHub",
1797
1801
  "description": "Osobisty token dostępu używany do uruchomień, które rozpoczynasz (pushe, PR-y, CI, scalanie)."
@@ -2211,6 +2215,36 @@
2211
2215
  "removeFailed": "Nie udało się usunąć wpisu rejestru"
2212
2216
  }
2213
2217
  },
2218
+ "apiTokens": {
2219
+ "title": "Tokeny dostępu do API",
2220
+ "intro": "Twórz tokeny, które systemy zewnętrzne przedstawiają API cat-factory. Każdy token uwierzytelnia się jako ten obszar roboczy w punktach końcowych /api/v1. Sekret jest wyświetlany tylko raz, podczas tworzenia, i nie można go odzyskać, więc zapisz go od razu.",
2221
+ "secret": {
2222
+ "heading": "Skopiuj token teraz",
2223
+ "warning": "To jedyny raz, gdy wyświetlany jest pełny token. Przechowuj go w bezpiecznym miejscu; nie można go odzyskać.",
2224
+ "copy": "Kopiuj token",
2225
+ "done": "Gotowe"
2226
+ },
2227
+ "list": {
2228
+ "heading": "Aktywne tokeny",
2229
+ "created": "Utworzono {date}",
2230
+ "lastUsed": "ostatnio użyto {date}",
2231
+ "neverUsed": "nigdy nie użyto",
2232
+ "revoke": "Unieważnij token"
2233
+ },
2234
+ "add": {
2235
+ "heading": "Utwórz token",
2236
+ "label": "Etykieta",
2237
+ "labelHelp": "Nazwa, po której później rozpoznasz ten token.",
2238
+ "labelPlaceholder": "np. potok CI",
2239
+ "create": "Utwórz token"
2240
+ },
2241
+ "toast": {
2242
+ "loadFailed": "Nie udało się załadować tokenów API",
2243
+ "created": "Token utworzony",
2244
+ "createFailed": "Nie udało się utworzyć tokenu",
2245
+ "revokeFailed": "Nie udało się unieważnić tokenu"
2246
+ }
2247
+ },
2214
2248
  "sharedStacks": {
2215
2249
  "tab": "Współdzielone stosy",
2216
2250
  "intro": "Długo działająca infrastruktura Compose (bazy danych, brokery, wyszukiwanie, poczta) uruchamiana raz na przestrzeń roboczą i wykorzystywana ponownie we wszystkich uruchomieniach i pull requestach. Środowisko testowe łączy się z zarządzaną siecią stosu. Uruchomienie stosu wymaga lokalnego wdrożenia Dockera; w innych backendach nadal możesz zarządzać definicją.",
@@ -1792,6 +1792,10 @@
1792
1792
  "label": "Özel paket kayıt defterleri",
1793
1793
  "description": "Aracıların özel bağımlılıkları yüklemek için kullandığı npm ve GitHub Packages token'ları."
1794
1794
  },
1795
+ "apiTokens": {
1796
+ "label": "API erişim belirteçleri",
1797
+ "description": "Harici sistemlerin cat-factory API'sini çağırmak için sunduğu belirteçler."
1798
+ },
1795
1799
  "githubPat": {
1796
1800
  "label": "GitHub token'ım",
1797
1801
  "description": "Başlattığınız çalıştırmalar için kullanılan kişisel erişim token'ı (push'lar, PR'lar, CI, birleştirme)."
@@ -2333,6 +2337,36 @@
2333
2337
  "removeFailed": "Kayıt defteri girdisi kaldırılamadı"
2334
2338
  }
2335
2339
  },
2340
+ "apiTokens": {
2341
+ "title": "API erişim belirteçleri",
2342
+ "intro": "Harici sistemlerin cat-factory API'sine sunduğu belirteçler oluşturun. Her belirteç, /api/v1 uç noktalarında bu çalışma alanı olarak kimlik doğrular. Gizli anahtar yalnızca oluşturulduğunda bir kez gösterilir ve kurtarılamaz, bu yüzden onu hemen saklayın.",
2343
+ "secret": {
2344
+ "heading": "Belirtecinizi şimdi kopyalayın",
2345
+ "warning": "Belirtecin tamamı yalnızca bu sefer gösterilir. Güvenli bir yerde saklayın; kurtarılamaz.",
2346
+ "copy": "Belirteci kopyala",
2347
+ "done": "Tamam"
2348
+ },
2349
+ "list": {
2350
+ "heading": "Etkin belirteçler",
2351
+ "created": "{date} tarihinde oluşturuldu",
2352
+ "lastUsed": "son kullanım {date}",
2353
+ "neverUsed": "hiç kullanılmadı",
2354
+ "revoke": "Belirteci iptal et"
2355
+ },
2356
+ "add": {
2357
+ "heading": "Belirteç oluştur",
2358
+ "label": "Etiket",
2359
+ "labelHelp": "Bu belirteci daha sonra tanımak için bir ad.",
2360
+ "labelPlaceholder": "örn. CI hattı",
2361
+ "create": "Belirteç oluştur"
2362
+ },
2363
+ "toast": {
2364
+ "loadFailed": "API belirteçleri yüklenemedi",
2365
+ "created": "Belirteç oluşturuldu",
2366
+ "createFailed": "Belirteç oluşturulamadı",
2367
+ "revokeFailed": "Belirteç iptal edilemedi"
2368
+ }
2369
+ },
2336
2370
  "sharedStacks": {
2337
2371
  "tab": "Paylaşılan yığınlar",
2338
2372
  "intro": "Çalışma alanı başına bir kez başlatılan ve tüm çalıştırmalar ile pull request'lerde yeniden kullanılan uzun ömürlü Compose altyapısı (veritabanları, aracılar, arama, posta). Bir test ortamı, yığının yönetilen ağına bağlanır. Bir yığını başlatmak yerel bir Docker dağıtımı gerektirir; diğer arka uçlarda tanımı yine de yönetebilirsiniz.",
@@ -1792,6 +1792,10 @@
1792
1792
  "label": "Приватні реєстри пакетів",
1793
1793
  "description": "Токени npm і GitHub Packages, які агенти використовують для встановлення приватних залежностей."
1794
1794
  },
1795
+ "apiTokens": {
1796
+ "label": "Токени доступу до API",
1797
+ "description": "Токени, які зовнішні системи надають для виклику API cat-factory."
1798
+ },
1795
1799
  "githubPat": {
1796
1800
  "label": "Мій токен GitHub",
1797
1801
  "description": "Особистий токен доступу, який використовується для запусків, що ви розпочинаєте (pushes, PR-и, CI, злиття)."
@@ -2211,6 +2215,36 @@
2211
2215
  "removeFailed": "Не вдалося видалити запис реєстру"
2212
2216
  }
2213
2217
  },
2218
+ "apiTokens": {
2219
+ "title": "Токени доступу до API",
2220
+ "intro": "Створюйте токени, які зовнішні системи надають API cat-factory. Кожен токен автентифікується як цей робочий простір на кінцевих точках /api/v1. Секрет показується лише один раз, під час створення, і його не можна відновити, тож збережіть його одразу.",
2221
+ "secret": {
2222
+ "heading": "Скопіюйте токен зараз",
2223
+ "warning": "Це єдиний раз, коли показується повний токен. Зберігайте його в безпечному місці; його не можна відновити.",
2224
+ "copy": "Копіювати токен",
2225
+ "done": "Готово"
2226
+ },
2227
+ "list": {
2228
+ "heading": "Активні токени",
2229
+ "created": "Створено {date}",
2230
+ "lastUsed": "востаннє використано {date}",
2231
+ "neverUsed": "ніколи не використовувався",
2232
+ "revoke": "Відкликати токен"
2233
+ },
2234
+ "add": {
2235
+ "heading": "Створити токен",
2236
+ "label": "Мітка",
2237
+ "labelHelp": "Назва, за якою ви пізніше впізнаєте цей токен.",
2238
+ "labelPlaceholder": "напр. конвеєр CI",
2239
+ "create": "Створити токен"
2240
+ },
2241
+ "toast": {
2242
+ "loadFailed": "Не вдалося завантажити токени API",
2243
+ "created": "Токен створено",
2244
+ "createFailed": "Не вдалося створити токен",
2245
+ "revokeFailed": "Не вдалося відкликати токен"
2246
+ }
2247
+ },
2214
2248
  "sharedStacks": {
2215
2249
  "tab": "Спільні стеки",
2216
2250
  "intro": "Довготривала інфраструктура Compose (бази даних, брокери, пошук, пошта), яку запускають один раз на робочий простір і повторно використовують у всіх запусках та pull request'ах. Тестове середовище під'єднується до керованої мережі стека. Запуск стека потребує локального розгортання Docker; на інших бекендах ви все одно можете керувати визначенням.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.120.0",
3
+ "version": "0.121.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",