diff --git a/src/main/services/composio/composio-credentials.ts b/src/main/services/composio/composio-credentials.ts index 1b525bd9..bc81a0ea 100644 --- a/src/main/services/composio/composio-credentials.ts +++ b/src/main/services/composio/composio-credentials.ts @@ -3,30 +3,35 @@ import { join } from "node:path"; import type { DatabaseSync } from "node:sqlite"; /** - * The org's Composio API key. When the database has none, a developer - * credential file next to the app (cred.txt, first line "Composio apikey: - * ak_...", gitignored) seeds it once for this org — the product owner's - * local path. Packaged installs configure the key in Settings instead; a - * missing file simply yields null. + * The org's Composio API key. A developer credential file next to the app + * (cred.txt, first line "Composio apikey: ak_...", gitignored) is the source + * of truth whenever it exists: rotating the key in the file takes effect on + * the next resolution, replacing whatever the database held. Packaged + * installs have no credential file and manage the key in Settings instead. */ export function resolveComposioApiKey(db: DatabaseSync, orgId: string, credentialFileDir: string = process.cwd()): string | null { const stored = db.prepare( "select value from composio_settings where org_id = ? and key = 'api_key' limit 1", ).get(orgId) as { value: string } | undefined; - if (stored?.value) return stored.value; - const path = join(credentialFileDir, "cred.txt"); - if (!existsSync(path)) return null; - const firstLine = readFileSync(path, "utf8").split("\n")[0] ?? ""; - const key = firstLine.split(":").slice(1).join(":").trim(); - if (!key) return null; + const fileKey = readCredentialFile(credentialFileDir); + if (!fileKey) return stored?.value ?? null; + if (stored?.value === fileKey) return fileKey; const now = new Date().toISOString(); const upsert = db.prepare(` insert into composio_settings(org_id, key, value, updated_at) values (?, ?, ?, ?) on conflict(org_id, key) do update set value = excluded.value, updated_at = excluded.updated_at `); - upsert.run(orgId, "api_key", key, now); + upsert.run(orgId, "api_key", fileKey, now); upsert.run(orgId, "entity_id", orgId, now); - return key; + return fileKey; +} + +function readCredentialFile(dir: string): string | null { + const path = join(dir, "cred.txt"); + if (!existsSync(path)) return null; + const firstLine = readFileSync(path, "utf8").split("\n")[0] ?? ""; + const key = firstLine.split(":").slice(1).join(":").trim(); + return key || null; } diff --git a/src/renderer/src/pages/ConnectorsPage.tsx b/src/renderer/src/pages/ConnectorsPage.tsx index fff0b424..a4ae9b7f 100644 --- a/src/renderer/src/pages/ConnectorsPage.tsx +++ b/src/renderer/src/pages/ConnectorsPage.tsx @@ -123,7 +123,19 @@ export function ConnectorsPage({ snapshot, actions }: PageProps) { )} {catalog.phase === "error" && (
- {catalog.message} + + {catalog.message} + - -

Documents

-

{documents.length} {documents.length === 1 ? "document" : "documents"}

- -
-
-
Title
Type
Status
Updated
-
- {filtered.map((doc, index) => { - const author = agents.find((a) => a.id === doc.authorAgentId); - return ( -
setEditingId(doc.id)} - onKeyDown={(e) => { if (e.key === "Enter") setEditingId(doc.id); }} - style={{ gridTemplateColumns: "minmax(0, 2.5fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr) 60px", borderBottom: index === filtered.length - 1 ? "none" : "1px solid var(--border-soft)" }} - > -
- {author && } - {doc.title} -
-
{doc.docType}
-
{doc.status}
-
{formatRelative(doc.updatedAt)}
-
e.stopPropagation()}> - remove(doc.id)} /> -
-
- ); - })} -
- - setCreateOpen(false)} onSubmit={create}> - - - - -