@cat-factory/app 0.47.11 → 0.48.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.
@@ -15,8 +15,10 @@ const toast = useToast()
15
15
  const { t } = useI18n()
16
16
 
17
17
  const slack = reactive({ clientId: '', clientSecret: '', redirectUrl: '' })
18
+ const linear = reactive({ clientId: '', clientSecret: '', redirectUrl: '' })
18
19
  const web = reactive({ braveApiKey: '', searxngUrl: '', searxngApiKey: '' })
19
20
  const savingSlack = ref(false)
21
+ const savingLinear = ref(false)
20
22
  const savingWeb = ref(false)
21
23
 
22
24
  const summary = computed(() => store.view?.summary ?? null)
@@ -200,6 +202,61 @@ async function clearSlack() {
200
202
  }
201
203
  }
202
204
 
205
+ async function saveLinear() {
206
+ if (!linear.clientId.trim() || !linear.clientSecret.trim() || !linear.redirectUrl.trim()) {
207
+ toast.add({ title: t('layout.accountDeployment.linear.validation'), color: 'error' })
208
+ return
209
+ }
210
+ savingLinear.value = true
211
+ try {
212
+ await store.save(props.accountId, {
213
+ secrets: {
214
+ linearOAuth: {
215
+ clientId: linear.clientId.trim(),
216
+ clientSecret: linear.clientSecret.trim(),
217
+ redirectUrl: linear.redirectUrl.trim(),
218
+ },
219
+ },
220
+ })
221
+ linear.clientId = ''
222
+ linear.clientSecret = ''
223
+ linear.redirectUrl = ''
224
+ toast.add({
225
+ title: t('layout.accountDeployment.linear.saved'),
226
+ icon: 'i-lucide-check',
227
+ color: 'success',
228
+ })
229
+ } catch (e) {
230
+ toast.add({
231
+ title: t('layout.accountDeployment.linear.saveFailed'),
232
+ description: e instanceof Error ? e.message : String(e),
233
+ color: 'error',
234
+ })
235
+ } finally {
236
+ savingLinear.value = false
237
+ }
238
+ }
239
+
240
+ async function clearLinear() {
241
+ savingLinear.value = true
242
+ try {
243
+ await store.save(props.accountId, { secrets: { linearOAuth: null } })
244
+ toast.add({
245
+ title: t('layout.accountDeployment.linear.cleared'),
246
+ icon: 'i-lucide-check',
247
+ color: 'success',
248
+ })
249
+ } catch (e) {
250
+ toast.add({
251
+ title: t('layout.accountDeployment.linear.clearFailed'),
252
+ description: e instanceof Error ? e.message : String(e),
253
+ color: 'error',
254
+ })
255
+ } finally {
256
+ savingLinear.value = false
257
+ }
258
+ }
259
+
203
260
  async function saveWeb() {
204
261
  const brave = web.braveApiKey.trim()
205
262
  const searxng = web.searxngUrl.trim()
@@ -329,6 +386,68 @@ async function clearWeb() {
329
386
  </div>
330
387
  </section>
331
388
 
389
+ <!-- Linear app OAuth -->
390
+ <section class="space-y-2 border-t border-slate-800 pt-6">
391
+ <div class="flex items-center gap-2">
392
+ <h4 class="text-sm font-semibold text-slate-200">
393
+ {{ t('layout.accountDeployment.linear.title') }}
394
+ </h4>
395
+ <UBadge
396
+ :color="summary?.linearOAuthConfigured ? 'success' : 'neutral'"
397
+ variant="subtle"
398
+ size="xs"
399
+ >
400
+ {{
401
+ summary?.linearOAuthConfigured
402
+ ? t('layout.accountDeployment.configured')
403
+ : t('layout.accountDeployment.notSet')
404
+ }}
405
+ </UBadge>
406
+ </div>
407
+ <p class="text-[11px] text-slate-400">
408
+ {{ t('layout.accountDeployment.linear.description') }}
409
+ </p>
410
+ <div class="grid grid-cols-1 gap-2 sm:grid-cols-3">
411
+ <UInput
412
+ v-model="linear.clientId"
413
+ :placeholder="t('layout.accountDeployment.linear.clientId')"
414
+ size="sm"
415
+ />
416
+ <UInput
417
+ v-model="linear.clientSecret"
418
+ type="password"
419
+ :placeholder="t('layout.accountDeployment.linear.clientSecret')"
420
+ size="sm"
421
+ />
422
+ <UInput
423
+ v-model="linear.redirectUrl"
424
+ :placeholder="t('layout.accountDeployment.linear.redirectUrl')"
425
+ size="sm"
426
+ />
427
+ </div>
428
+ <div class="flex gap-2">
429
+ <UButton
430
+ color="primary"
431
+ size="xs"
432
+ icon="i-lucide-save"
433
+ :loading="savingLinear"
434
+ @click="saveLinear"
435
+ >
436
+ {{ t('common.save') }}
437
+ </UButton>
438
+ <UButton
439
+ v-if="summary?.linearOAuthConfigured"
440
+ color="neutral"
441
+ variant="ghost"
442
+ size="xs"
443
+ :loading="savingLinear"
444
+ @click="clearLinear"
445
+ >
446
+ {{ t('layout.accountDeployment.clear') }}
447
+ </UButton>
448
+ </div>
449
+ </section>
450
+
332
451
  <!-- Web search keys -->
333
452
  <section class="space-y-2 border-t border-slate-800 pt-6">
334
453
  <div class="flex items-center gap-2">
@@ -68,6 +68,39 @@ const canSave = computed(() => {
68
68
  return true
69
69
  })
70
70
 
71
+ // Linear team picker: load the connected workspace's teams so filing offers a
72
+ // dropdown instead of a raw team-id paste. Falls back to the text input if the
73
+ // teams can't be loaded (a broken connection shouldn't block configuration).
74
+ const teamsLoading = ref(false)
75
+ const teamsError = ref(false)
76
+ const teamOptions = computed(() =>
77
+ tracker.linearTeams.map((tm) => ({
78
+ label: tm.key ? `${tm.name} (${tm.key})` : tm.name,
79
+ value: tm.id,
80
+ })),
81
+ )
82
+ async function loadLinearTeams() {
83
+ if (!linearConnected.value) return
84
+ teamsLoading.value = true
85
+ teamsError.value = false
86
+ try {
87
+ await tracker.loadLinearTeams()
88
+ } catch {
89
+ teamsError.value = true
90
+ } finally {
91
+ teamsLoading.value = false
92
+ }
93
+ }
94
+ watch(
95
+ () => [trackerKind.value, linearConnected.value] as const,
96
+ ([kind, connected]) => {
97
+ if (kind === 'linear' && connected && tracker.linearTeams.length === 0 && !teamsError.value) {
98
+ void loadLinearTeams()
99
+ }
100
+ },
101
+ { immediate: true },
102
+ )
103
+
71
104
  async function save() {
72
105
  if (!canSave.value) return
73
106
  saving.value = true
@@ -284,7 +317,21 @@ const STATUS_UI: Record<
284
317
  :label="t('settings.issueTracker.filing.linearTeamId')"
285
318
  class="w-64"
286
319
  >
287
- <UInput v-model="linearTeamId" placeholder="team_…" size="sm" class="w-full" />
320
+ <!-- Typeahead combobox when the connection's teams loaded (built-in client-side
321
+ filter over the option labels — a large org's team list is too long for a
322
+ plain dropdown); raw-id fallback otherwise. Mirrors the repo picker. -->
323
+ <UInputMenu
324
+ v-if="linearConnected && !teamsError && teamOptions.length > 0"
325
+ v-model="linearTeamId"
326
+ :items="teamOptions"
327
+ value-key="value"
328
+ :loading="teamsLoading"
329
+ icon="i-lucide-search"
330
+ :placeholder="t('settings.issueTracker.filing.linearTeamSearchPlaceholder')"
331
+ size="sm"
332
+ class="w-full"
333
+ />
334
+ <UInput v-else v-model="linearTeamId" placeholder="team_…" size="sm" class="w-full" />
288
335
  <template #help>
289
336
  <span class="text-[11px] text-slate-500">
290
337
  {{ t('settings.issueTracker.filing.linearTeamIdHelp') }}
@@ -24,6 +24,9 @@ const connection = computed(() => (source.value ? tasks.connectionFor(source.val
24
24
  const connected = computed(() => connection.value !== undefined)
25
25
  // A credentialless source (GitHub Issues) reuses the installed GitHub App: no form.
26
26
  const credentialless = computed(() => (descriptor.value?.credentialFields.length ?? 0) === 0)
27
+ // An OAuth source (Linear) offers a "Connect with X" button alongside the manual fields.
28
+ const oauth = computed(() => descriptor.value?.oauth ?? false)
29
+ const oauthStarting = ref(false)
27
30
  // Usable right now: a credentialed source is connected; GitHub Issues' App is installed.
28
31
  const available = computed(() => descriptor.value?.available ?? false)
29
32
 
@@ -77,6 +80,23 @@ async function submit() {
77
80
  }
78
81
  }
79
82
 
83
+ async function startOAuth() {
84
+ if (!source.value) return
85
+ oauthStarting.value = true
86
+ try {
87
+ // Only Linear wires an OAuth flow today; the browser navigates away on success.
88
+ if (source.value === 'linear') await tasks.startLinearOAuth()
89
+ } catch (e) {
90
+ toast.add({
91
+ title: t('tasks.connect.connectFailed'),
92
+ description: e instanceof Error ? e.message : String(e),
93
+ icon: 'i-lucide-triangle-alert',
94
+ color: 'error',
95
+ })
96
+ oauthStarting.value = false
97
+ }
98
+ }
99
+
80
100
  async function disconnect() {
81
101
  if (!source.value) return
82
102
  await tasks.disconnect(source.value)
@@ -129,8 +149,23 @@ async function toggleEnabled(enabled: boolean) {
129
149
  </p>
130
150
  </template>
131
151
 
132
- <!-- Credentialed source (Jira): the connect form, shown until connected. -->
152
+ <!-- Credentialed source (Jira/Linear): the connect form, shown until connected. -->
133
153
  <div v-else-if="!connected" class="space-y-3">
154
+ <!-- OAuth source (Linear): the redirect button, with the manual key form below. -->
155
+ <template v-if="oauth">
156
+ <UButton
157
+ block
158
+ color="primary"
159
+ icon="i-lucide-plug"
160
+ :loading="oauthStarting"
161
+ @click="startOAuth"
162
+ >
163
+ {{ t('tasks.connect.oauthButton', { label: descriptor.label }) }}
164
+ </UButton>
165
+ <p class="text-center text-[11px] text-slate-500">
166
+ {{ t('tasks.connect.oauthOr') }}
167
+ </p>
168
+ </template>
134
169
  <UFormField
135
170
  v-for="field in descriptor.credentialFields"
136
171
  :key="field.key"
@@ -3,9 +3,11 @@ import {
3
3
  createTaskFromIssueContract,
4
4
  diagnoseTaskSourceContract,
5
5
  disconnectTaskSourceContract,
6
+ getLinearInstallUrlContract,
6
7
  getTrackerSettingsContract,
7
8
  importTaskContract,
8
9
  linkTaskContract,
10
+ listLinearTeamsContract,
9
11
  listTaskConnectionsContract,
10
12
  listTaskSourcesContract,
11
13
  listTasksContract,
@@ -95,6 +97,15 @@ export function tasksApi({ send, ws }: ApiContext) {
95
97
  body: { ref: string; containerId: string; position?: { x: number; y: number } },
96
98
  ) => send(spawnEpicContract, { pathPrefix: ws(workspaceId), pathParams: { source }, body }),
97
99
 
100
+ // ---- Linear-specific --------------------------------------------------
101
+ // The connection's Linear teams, for the ticket-filing team picker.
102
+ listLinearTeams: (workspaceId: string) =>
103
+ send(listLinearTeamsContract, { pathPrefix: ws(workspaceId) }),
104
+
105
+ // The "Connect with Linear" OAuth authorize URL (the browser is redirected to it).
106
+ getLinearInstallUrl: (workspaceId: string) =>
107
+ send(getLinearInstallUrlContract, { pathPrefix: ws(workspaceId) }),
108
+
98
109
  // ---- issue-tracker selection (workspace-level) ------------------------
99
110
  getTrackerSettings: (workspaceId: string) =>
100
111
  send(getTrackerSettingsContract, { pathPrefix: ws(workspaceId) }),
@@ -90,6 +90,17 @@ export const useTasksStore = defineStore('tasks', () => {
90
90
  available.value = true
91
91
  }
92
92
 
93
+ /**
94
+ * Start the "Connect with Linear" OAuth flow by navigating the browser to the
95
+ * authorize URL the backend mints (carrying a signed `state`). Linear redirects
96
+ * back to the public callback, which stores the token; the settings panel's
97
+ * `probe()` on return then reflects the new connection.
98
+ */
99
+ async function startLinearOAuth() {
100
+ const { url } = await api.getLinearInstallUrl(workspace.requireId())
101
+ window.location.href = url
102
+ }
103
+
93
104
  /** Disconnect the workspace from a source. */
94
105
  async function disconnect(source: TaskSourceKind) {
95
106
  await api.disconnectTaskSource(workspace.requireId(), source)
@@ -203,6 +214,7 @@ export const useTasksStore = defineStore('tasks', () => {
203
214
  probe,
204
215
  checkSetup,
205
216
  connect,
217
+ startLinearOAuth,
206
218
  disconnect,
207
219
  setEnabled,
208
220
  loadTasks,
@@ -1,5 +1,6 @@
1
1
  import { defineStore } from 'pinia'
2
2
  import { ref } from 'vue'
3
+ import type { LinearTeam } from '~/types/domain'
3
4
  import type { PutTrackerSettingsInput, TrackerSettings } from '~/types/tracker'
4
5
  import { useWorkspaceStore } from '~/stores/workspace'
5
6
 
@@ -20,6 +21,9 @@ export const useTrackerStore = defineStore('tracker', () => {
20
21
  updatedAt: 0,
21
22
  })
22
23
 
24
+ /** The connected Linear workspace's teams, for the filing team picker (lazily loaded). */
25
+ const linearTeams = ref<LinearTeam[]>([])
26
+
23
27
  function hydrate(value: TrackerSettings | undefined) {
24
28
  settings.value = value ?? {
25
29
  tracker: null,
@@ -37,5 +41,12 @@ export const useTrackerStore = defineStore('tracker', () => {
37
41
  return settings.value
38
42
  }
39
43
 
40
- return { settings, hydrate, save }
44
+ /** Load the connected Linear workspace's teams for the filing team picker. */
45
+ async function loadLinearTeams() {
46
+ const ws = useWorkspaceStore()
47
+ const { teams } = await api.listLinearTeams(ws.requireId())
48
+ linearTeams.value = teams
49
+ }
50
+
51
+ return { settings, linearTeams, hydrate, save, loadLinearTeams }
41
52
  })
@@ -19,4 +19,5 @@ export type {
19
19
  SourceTask,
20
20
  TaskSearchResult,
21
21
  CredentialField,
22
+ LinearTeam,
22
23
  } from '@cat-factory/contracts'
@@ -828,6 +828,18 @@
828
828
  "cleared": "Slack OAuth cleared",
829
829
  "clearFailed": "Could not clear Slack OAuth"
830
830
  },
831
+ "linear": {
832
+ "title": "Linear app (OAuth)",
833
+ "description": "Enables the \"Connect with Linear\" OAuth flow. Without it, workspaces can still connect Linear by pasting a personal API key.",
834
+ "clientId": "Client ID",
835
+ "clientSecret": "Client secret",
836
+ "redirectUrl": "Redirect URL",
837
+ "validation": "Enter the client id, secret and redirect URL",
838
+ "saved": "Linear OAuth saved",
839
+ "saveFailed": "Could not save Linear OAuth",
840
+ "cleared": "Linear OAuth cleared",
841
+ "clearFailed": "Could not clear Linear OAuth"
842
+ },
831
843
  "web": {
832
844
  "title": "Container web search",
833
845
  "description": "The search upstream container agents reach through the backend proxy. Set a Brave key (recommended), or a self-hosted SearXNG URL (with an optional bearer key).",
@@ -1340,7 +1352,8 @@
1340
1352
  "jiraProjectKey": "Jira project key",
1341
1353
  "jiraProjectKeyHelp": "New tickets are filed under this project.",
1342
1354
  "linearTeamId": "Linear team id",
1343
- "linearTeamIdHelp": "New issues are created under this team (Linear requires a team to create an issue)."
1355
+ "linearTeamIdHelp": "New issues are created under this team (Linear requires a team to create an issue).",
1356
+ "linearTeamSearchPlaceholder": "Search teams…"
1344
1357
  },
1345
1358
  "vendor": {
1346
1359
  "github": "GitHub Issues",
@@ -2168,7 +2181,9 @@
2168
2181
  "offerHint": "When off, {label} is hidden from import and linking.",
2169
2182
  "disconnect": "Disconnect",
2170
2183
  "updateConnection": "Update connection",
2171
- "connect": "Connect"
2184
+ "connect": "Connect",
2185
+ "oauthButton": "Connect with {label}",
2186
+ "oauthOr": "or connect with an API key"
2172
2187
  }
2173
2188
  },
2174
2189
  "pipeline": {
@@ -789,6 +789,18 @@
789
789
  "cleared": "OAuth de Slack borrado",
790
790
  "clearFailed": "No se pudo borrar el OAuth de Slack"
791
791
  },
792
+ "linear": {
793
+ "title": "Aplicación de Linear (OAuth)",
794
+ "description": "Habilita el flujo de OAuth \"Conectar con Linear\". Sin ello, los espacios de trabajo aún pueden conectar Linear pegando una clave de API personal.",
795
+ "clientId": "ID de cliente",
796
+ "clientSecret": "Secreto de cliente",
797
+ "redirectUrl": "URL de redirección",
798
+ "validation": "Introduce el id de cliente, el secreto y la URL de redirección",
799
+ "saved": "OAuth de Linear guardado",
800
+ "saveFailed": "No se pudo guardar OAuth de Linear",
801
+ "cleared": "OAuth de Linear borrado",
802
+ "clearFailed": "No se pudo borrar OAuth de Linear"
803
+ },
792
804
  "web": {
793
805
  "title": "Búsqueda web del contenedor",
794
806
  "description": "El proveedor de búsqueda al que los agentes del contenedor acceden a través del proxy del backend. Define una clave de Brave (recomendado) o una URL de SearXNG autoalojada (con una clave bearer opcional).",
@@ -1298,7 +1310,8 @@
1298
1310
  "jiraProjectKey": "Clave de proyecto de Jira",
1299
1311
  "jiraProjectKeyHelp": "Los nuevos tickets se registran en este proyecto.",
1300
1312
  "linearTeamId": "Id de equipo de Linear",
1301
- "linearTeamIdHelp": "Las nuevas incidencias se crean en este equipo (Linear requiere un equipo para crear una incidencia)."
1313
+ "linearTeamIdHelp": "Las nuevas incidencias se crean en este equipo (Linear requiere un equipo para crear una incidencia).",
1314
+ "linearTeamSearchPlaceholder": "Buscar equipos…"
1302
1315
  },
1303
1316
  "vendor": {
1304
1317
  "github": "GitHub Issues",
@@ -2111,7 +2124,9 @@
2111
2124
  "offerHint": "Cuando está desactivada, {label} se oculta de la importación y la vinculación.",
2112
2125
  "disconnect": "Desconectar",
2113
2126
  "updateConnection": "Actualizar conexión",
2114
- "connect": "Conectar"
2127
+ "connect": "Conectar",
2128
+ "oauthButton": "Conectar con {label}",
2129
+ "oauthOr": "o conecta con una clave API"
2115
2130
  }
2116
2131
  },
2117
2132
  "pipeline": {
@@ -789,6 +789,18 @@
789
789
  "cleared": "OAuth Slack effacé",
790
790
  "clearFailed": "Impossible d'effacer l'OAuth Slack"
791
791
  },
792
+ "linear": {
793
+ "title": "Application Linear (OAuth)",
794
+ "description": "Active le flux OAuth « Se connecter avec Linear ». Sans cela, les espaces de travail peuvent toujours connecter Linear en collant une clé API personnelle.",
795
+ "clientId": "ID client",
796
+ "clientSecret": "Secret client",
797
+ "redirectUrl": "URL de redirection",
798
+ "validation": "Saisissez l'id client, le secret et l'URL de redirection",
799
+ "saved": "OAuth Linear enregistré",
800
+ "saveFailed": "Impossible d'enregistrer OAuth Linear",
801
+ "cleared": "OAuth Linear effacé",
802
+ "clearFailed": "Impossible d'effacer OAuth Linear"
803
+ },
792
804
  "web": {
793
805
  "title": "Recherche web du conteneur",
794
806
  "description": "Le fournisseur de recherche que les agents en conteneur atteignent via le proxy du backend. Définissez une clé Brave (recommandé) ou une URL SearXNG auto-hébergée (avec une clé bearer facultative).",
@@ -1298,7 +1310,8 @@
1298
1310
  "jiraProjectKey": "Clé de projet Jira",
1299
1311
  "jiraProjectKeyHelp": "Les nouveaux tickets sont créés dans ce projet.",
1300
1312
  "linearTeamId": "Id d'équipe Linear",
1301
- "linearTeamIdHelp": "Les nouveaux tickets sont créés dans cette équipe (Linear exige une équipe pour créer un ticket)."
1313
+ "linearTeamIdHelp": "Les nouveaux tickets sont créés dans cette équipe (Linear exige une équipe pour créer un ticket).",
1314
+ "linearTeamSearchPlaceholder": "Rechercher des équipes…"
1302
1315
  },
1303
1316
  "vendor": {
1304
1317
  "github": "GitHub Issues",
@@ -2111,7 +2124,9 @@
2111
2124
  "offerHint": "Lorsqu'elle est désactivée, {label} est masquée de l'importation et de la liaison.",
2112
2125
  "disconnect": "Déconnecter",
2113
2126
  "updateConnection": "Mettre à jour la connexion",
2114
- "connect": "Connecter"
2127
+ "connect": "Connecter",
2128
+ "oauthButton": "Se connecter avec {label}",
2129
+ "oauthOr": "ou connectez-vous avec une clé API"
2115
2130
  }
2116
2131
  },
2117
2132
  "pipeline": {
@@ -789,6 +789,18 @@
789
789
  "cleared": "Wyczyszczono OAuth Slacka",
790
790
  "clearFailed": "Nie udało się wyczyścić OAuth Slacka"
791
791
  },
792
+ "linear": {
793
+ "title": "Aplikacja Linear (OAuth)",
794
+ "description": "Włącza przepływ OAuth „Połącz z Linear”. Bez niego przestrzenie robocze nadal mogą połączyć Linear, wklejając osobisty klucz API.",
795
+ "clientId": "Identyfikator klienta",
796
+ "clientSecret": "Sekret klienta",
797
+ "redirectUrl": "Adres URL przekierowania",
798
+ "validation": "Podaj identyfikator klienta, sekret i adres URL przekierowania",
799
+ "saved": "Zapisano OAuth Linear",
800
+ "saveFailed": "Nie udało się zapisać OAuth Linear",
801
+ "cleared": "Wyczyszczono OAuth Linear",
802
+ "clearFailed": "Nie udało się wyczyścić OAuth Linear"
803
+ },
792
804
  "web": {
793
805
  "title": "Wyszukiwanie w sieci w kontenerze",
794
806
  "description": "Dostawca wyszukiwania, do którego agenci w kontenerze sięgają przez proxy backendu. Ustaw klucz Brave (zalecane) lub własny adres URL SearXNG (z opcjonalnym kluczem bearer).",
@@ -1298,7 +1310,8 @@
1298
1310
  "jiraProjectKey": "Klucz projektu Jira",
1299
1311
  "jiraProjectKeyHelp": "Nowe zgłoszenia są rejestrowane w tym projekcie.",
1300
1312
  "linearTeamId": "Identyfikator zespołu Linear",
1301
- "linearTeamIdHelp": "Nowe zgłoszenia są tworzone w tym zespole (Linear wymaga zespołu do utworzenia zgłoszenia)."
1313
+ "linearTeamIdHelp": "Nowe zgłoszenia są tworzone w tym zespole (Linear wymaga zespołu do utworzenia zgłoszenia).",
1314
+ "linearTeamSearchPlaceholder": "Szukaj zespołów…"
1302
1315
  },
1303
1316
  "vendor": {
1304
1317
  "github": "GitHub Issues",
@@ -2111,7 +2124,9 @@
2111
2124
  "offerHint": "Po wyłączeniu {label} jest ukryte przed importem i powiązywaniem.",
2112
2125
  "disconnect": "Rozłącz",
2113
2126
  "updateConnection": "Zaktualizuj połączenie",
2114
- "connect": "Połącz"
2127
+ "connect": "Połącz",
2128
+ "oauthButton": "Połącz z {label}",
2129
+ "oauthOr": "lub połącz za pomocą klucza API"
2115
2130
  }
2116
2131
  },
2117
2132
  "pipeline": {
@@ -789,6 +789,18 @@
789
789
  "cleared": "OAuth Slack очищено",
790
790
  "clearFailed": "Не вдалося очистити OAuth Slack"
791
791
  },
792
+ "linear": {
793
+ "title": "Застосунок Linear (OAuth)",
794
+ "description": "Вмикає потік OAuth «Підключити через Linear». Без нього робочі простори все одно можуть підключити Linear, вставивши особистий ключ API.",
795
+ "clientId": "ID клієнта",
796
+ "clientSecret": "Секрет клієнта",
797
+ "redirectUrl": "URL перенаправлення",
798
+ "validation": "Введіть ID клієнта, секрет і URL перенаправлення",
799
+ "saved": "OAuth Linear збережено",
800
+ "saveFailed": "Не вдалося зберегти OAuth Linear",
801
+ "cleared": "OAuth Linear очищено",
802
+ "clearFailed": "Не вдалося очистити OAuth Linear"
803
+ },
792
804
  "web": {
793
805
  "title": "Вебпошук контейнера",
794
806
  "description": "Постачальник пошуку, до якого агенти в контейнері звертаються через проксі бекенду. Задайте ключ Brave (рекомендовано) або власний URL SearXNG (з необов'язковим ключем bearer).",
@@ -1298,7 +1310,8 @@
1298
1310
  "jiraProjectKey": "Ключ проєкту Jira",
1299
1311
  "jiraProjectKeyHelp": "Нові тикети реєструються в цьому проєкті.",
1300
1312
  "linearTeamId": "Ідентифікатор команди Linear",
1301
- "linearTeamIdHelp": "Нові тикети створюються в цій команді (Linear вимагає команду для створення тикета)."
1313
+ "linearTeamIdHelp": "Нові тикети створюються в цій команді (Linear вимагає команду для створення тикета).",
1314
+ "linearTeamSearchPlaceholder": "Пошук команд…"
1302
1315
  },
1303
1316
  "vendor": {
1304
1317
  "github": "GitHub Issues",
@@ -2111,7 +2124,9 @@
2111
2124
  "offerHint": "Коли вимкнено, {label} приховано з імпорту та прив'язування.",
2112
2125
  "disconnect": "Відключити",
2113
2126
  "updateConnection": "Оновити підключення",
2114
- "connect": "Підключити"
2127
+ "connect": "Підключити",
2128
+ "oauthButton": "Підключити через {label}",
2129
+ "oauthOr": "або підключіться за допомогою ключа API"
2115
2130
  }
2116
2131
  },
2117
2132
  "pipeline": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.47.11",
3
+ "version": "0.48.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.45.1"
37
+ "@cat-factory/contracts": "0.46.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@toad-contracts/testing": "0.3.2",