export type JsonRecord = Record export type LinearAuthMode = 'oauth' | 'api-key' export type LinearAuthInput = { /** Saved OAuth integration name. Defaults to `linear`. */ integrationName?: string /** Alias of `integrationName` (Notion-style). */ integration?: string /** * Short account alias. `work` → `linear-work`. Values that already start * with `linear-` are used as-is. `default` / `linear` / omitted → `linear`. */ account?: string /** User secret name for a personal API key. Defaults to `linearApiKey`. */ secretName?: string /** Force OAuth or API-key auth when both exist. */ auth?: LinearAuthMode } export type LinearPageInfo = { hasNextPage: boolean endCursor: string | null } export type LinearNamedRef = { id: string name: string key?: string type?: string } export type LinearIssueSummary = { id: string identifier: string title: string url: string | null priority: number | null createdAt: string | null updatedAt: string | null state: LinearNamedRef | null team: LinearNamedRef | null assignee: LinearNamedRef | null project: LinearNamedRef | null } export type LinearCommentSummary = { id: string body: string | null url: string | null createdAt: string | null updatedAt: string | null user: LinearNamedRef | null } export type LinearProjectSummary = { id: string name: string url: string | null description: string | null state: string | null startDate: string | null targetDate: string | null progress: number | null } export type LinearTeamSummary = { id: string name: string key: string description: string | null } export type LinearWorkflowStateSummary = { id: string name: string type: string team: LinearNamedRef | null } export type DryRunResult = { dryRun: true wouldCall: T } /** * Shared Linear types and parsing helpers for this package. Import from `kody:@kody/linear/types` when typing other Linear exports. * * @returns Catalog of exported type/helper names for agent discovery. * * @example * import types from 'kody:@kody/linear/types' * const catalog = await types() */ export function requireRecord(value: unknown, label: string): JsonRecord { if (!value || typeof value !== 'object' || Array.isArray(value)) { throw new Error(`${label} must be an object.`) } return value as JsonRecord } export function requireString( value: unknown, label: string, options: { allowEmpty?: boolean } = {}, ): string { if (typeof value !== 'string' || (!options.allowEmpty && value.trim().length === 0)) { throw new Error( `${label} must be ${options.allowEmpty ? 'a string' : 'a non-empty string'}.`, ) } return options.allowEmpty ? value : value.trim() } export function optionalString(value: unknown, label: string): string | undefined { if (value === undefined || value === null) return undefined return requireString(value, label) } export function optionalBoolean(value: unknown, label: string): boolean | undefined { if (value === undefined) return undefined if (typeof value !== 'boolean') throw new Error(`${label} must be a boolean.`) return value } export function optionalNumber(value: unknown, label: string): number | undefined { if (value === undefined || value === null) return undefined if (typeof value !== 'number' || !Number.isFinite(value)) { throw new Error(`${label} must be a finite number.`) } return value } export function optionalStringArray(value: unknown, label: string): Array | undefined { if (value === undefined || value === null) return undefined if (!Array.isArray(value)) throw new Error(`${label} must be an array of strings.`) return value.map((item, index) => requireString(item, `${label}[${index}]`)) } export function clampInt( value: unknown, min: number, max: number, fallback: number, label = 'first', ): number { if (value === undefined || value === null) return fallback if (typeof value !== 'number' || !Number.isInteger(value)) { throw new Error(`${label} must be an integer.`) } if (value < min || value > max) { throw new Error(`${label} must be between ${min} and ${max}.`) } return value } export function requirePriority(value: unknown, label = 'priority'): number { const priority = optionalNumber(value, label) if (priority === undefined) { throw new Error(`${label} is required.`) } if (![0, 1, 2, 3, 4].includes(priority)) { throw new Error(`${label} must be 0 (none), 1 (urgent), 2 (high), 3 (medium), or 4 (low).`) } return priority } export function optionalPriority(value: unknown, label = 'priority'): number | undefined { if (value === undefined || value === null) return undefined return requirePriority(value, label) } export const WORKFLOW_STATE_TYPES = [ 'triage', 'backlog', 'unstarted', 'started', 'completed', 'canceled', ] as const export type WorkflowStateType = (typeof WORKFLOW_STATE_TYPES)[number] export function optionalWorkflowStateType( value: unknown, label = 'stateType', ): WorkflowStateType | undefined { if (value === undefined || value === null) return undefined const type = requireString(value, label) if (!(WORKFLOW_STATE_TYPES as readonly string[]).includes(type)) { throw new Error( `${label} must be one of: ${WORKFLOW_STATE_TYPES.join(', ')}.`, ) } return type as WorkflowStateType } export function compactRecord(value: T): T { const output: JsonRecord = {} for (const [key, item] of Object.entries(value)) { if (item === undefined) continue output[key] = item } return output as T } /** * Shared Linear types for this package. * @example * import types from 'kody:@kody/linear/types' * const catalog = await types() */ export default async function typesOverview() { return { auth: ['LinearAuthInput', 'LinearAuthMode'], models: [ 'LinearIssueSummary', 'LinearProjectSummary', 'LinearTeamSummary', 'LinearCommentSummary', 'LinearWorkflowStateSummary', ], } }