AI tracker: owner-only Setup tab (per-provider connections, skills, instructions, plugins, routines, agents, plan/key labels, notes; ai.provider_setup_providers + provider_setup_entries, secrets refused)
Some checks failed
Deploy production / deploy (push) Failing after 1m3s
Some checks failed
Deploy production / deploy (push) Failing after 1m3s
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { check, date, doublePrecision, index, text, uniqueIndex } from 'drizzle-orm/pg-core';
|
||||
import {
|
||||
check,
|
||||
date,
|
||||
doublePrecision,
|
||||
index,
|
||||
integer,
|
||||
text,
|
||||
uniqueIndex
|
||||
} from 'drizzle-orm/pg-core';
|
||||
import { aiSchema } from './schemas';
|
||||
import { id, timestamps, timestamptz } from './helpers';
|
||||
|
||||
@@ -82,3 +90,84 @@ export const aiUsageEntries = aiSchema.table(
|
||||
check('usage_entries_metric_check', sql`length(btrim(${t.metric})) > 0`)
|
||||
]
|
||||
);
|
||||
|
||||
/**
|
||||
* Setup tab (owner-only): how each AI provider is set up. One row per provider card; entries
|
||||
* are grouped into sections. Plan/API keys are labels only: the app refuses values that look
|
||||
* like a secret, and nothing here is ever a key value.
|
||||
*/
|
||||
export const AI_SETUP_SECTIONS = [
|
||||
'connections',
|
||||
'skills',
|
||||
'instructions',
|
||||
'plugins',
|
||||
'routines',
|
||||
'agents',
|
||||
'plan',
|
||||
'notes'
|
||||
] as const;
|
||||
export type AiSetupSection = (typeof AI_SETUP_SECTIONS)[number];
|
||||
export const AI_SETUP_SCOPES = ['global', 'per-repo', 'app'] as const;
|
||||
export type AiSetupScope = (typeof AI_SETUP_SCOPES)[number];
|
||||
export const AI_SETUP_COLORS = [
|
||||
'orange',
|
||||
'green',
|
||||
'blue',
|
||||
'violet',
|
||||
'pink',
|
||||
'sky',
|
||||
'amber',
|
||||
'zinc'
|
||||
] as const;
|
||||
|
||||
export const aiSetupProviders = aiSchema.table(
|
||||
'provider_setup_providers',
|
||||
{
|
||||
id: id(),
|
||||
name: text('name').notNull(),
|
||||
/** The products this card covers, e.g. "claude.ai + Claude Code". */
|
||||
products: text('products'),
|
||||
color: text('color').notNull().default('zinc'),
|
||||
sortOrder: integer('sort_order').notNull().default(0),
|
||||
...timestamps
|
||||
},
|
||||
(t) => [
|
||||
uniqueIndex('provider_setup_providers_name_key').on(t.name),
|
||||
check('provider_setup_providers_name_check', sql`length(btrim(${t.name})) > 0`),
|
||||
check(
|
||||
'provider_setup_providers_color_check',
|
||||
sql`${t.color} IN ('orange','green','blue','violet','pink','sky','amber','zinc')`
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
export const aiSetupEntries = aiSchema.table(
|
||||
'provider_setup_entries',
|
||||
{
|
||||
id: id(),
|
||||
providerId: integer('provider_id')
|
||||
.notNull()
|
||||
.references(() => aiSetupProviders.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
section: text('section').notNull(),
|
||||
name: text('name').notNull(),
|
||||
note: text('note'),
|
||||
scope: text('scope'),
|
||||
link: text('link'),
|
||||
/** Free markdown text (custom instructions, longer notes). */
|
||||
body: text('body'),
|
||||
sortOrder: integer('sort_order').notNull().default(0),
|
||||
...timestamps
|
||||
},
|
||||
(t) => [
|
||||
index('provider_setup_entries_provider_idx').on(t.providerId, t.section, t.sortOrder),
|
||||
check(
|
||||
'provider_setup_entries_section_check',
|
||||
sql`${t.section} IN ('connections','skills','instructions','plugins','routines','agents','plan','notes')`
|
||||
),
|
||||
check(
|
||||
'provider_setup_entries_scope_check',
|
||||
sql`${t.scope} IS NULL OR ${t.scope} IN ('global','per-repo','app')`
|
||||
),
|
||||
check('provider_setup_entries_name_check', sql`length(btrim(${t.name})) > 0`)
|
||||
]
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user