Skip to content
← Public packages

@kentcdodds/devin

Start, monitor, and manage Devin sessions, knowledge, playbooks, and schedules via the Devin v3 API

src/knowledge.ts

148 lines · 3.6 KB · TypeScript
import {
	clampInt,
	compact,
	devinOrgApi,
	type Paginated,
	trimString,
} from './lib/client.ts'

export type KnowledgeNote = {
	note_id: string
	name: string
	body: string
	trigger: string
	folder_id: string | null
	folder_path: string
	is_enabled: boolean
	pinned_repo: string | null
	macro: string | null
	access_type: 'org' | 'enterprise' | string
	org_id: string | null
	created_at: number
	updated_at: number
}

export type KnowledgeFolder = {
	folder_id?: string | null
	name?: string | null
	path?: string | null
	[key: string]: unknown
}

export async function listNotes(params: {
	orgId?: string
	first?: number
	after?: string
	search?: string
	folderPath?: string
	pinnedRepo?: string
} = {}) {
	return devinOrgApi<Paginated<KnowledgeNote>>({
		orgId: params.orgId,
		path: '/knowledge/notes',
		query: compact({
			first: params.first === undefined ? undefined : clampInt(params.first, 1, 200, 50),
			after: params.after,
			search: params.search,
			folder_path: params.folderPath,
			pinned_repo: params.pinnedRepo,
		}),
	})
}

export async function getNote(params: { noteId: string; orgId?: string }) {
	return devinOrgApi<KnowledgeNote>({
		orgId: params.orgId,
		path: `/knowledge/notes/${encodeURIComponent(params.noteId)}`,
	})
}

export type NoteInput = {
	orgId?: string
	name: string
	body: string
	/** When Devin should pull this note in, e.g. "When working in owner/repo". */
	trigger: string
	folderId?: string
	pinnedRepo?: string
	isEnabled?: boolean
}

function noteBody(params: NoteInput) {
	const name = trimString(params.name)
	const body = trimString(params.body)
	const trigger = trimString(params.trigger)
	if (!name || !body || !trigger) {
		throw new Error('Knowledge notes require name, body, and trigger')
	}
	return compact({
		name,
		body,
		trigger,
		folder_id: params.folderId,
		pinned_repo: params.pinnedRepo,
		is_enabled: params.isEnabled,
	})
}

export async function createNote(params: NoteInput) {
	return devinOrgApi<KnowledgeNote>({
		orgId: params.orgId,
		path: '/knowledge/notes',
		method: 'POST',
		body: noteBody(params),
	})
}

/** Full replace — Devin's PUT takes the same shape as create. */
export async function updateNote(params: NoteInput & { noteId: string }) {
	return devinOrgApi<KnowledgeNote>({
		orgId: params.orgId,
		path: `/knowledge/notes/${encodeURIComponent(params.noteId)}`,
		method: 'PUT',
		body: noteBody(params),
	})
}

/** Destructive — confirm the note id before calling. */
export async function deleteNote(params: { noteId: string; orgId?: string }) {
	return devinOrgApi<unknown>({
		orgId: params.orgId,
		path: `/knowledge/notes/${encodeURIComponent(params.noteId)}`,
		method: 'DELETE',
	})
}

export async function listFolders(params: { orgId?: string } = {}) {
	return devinOrgApi<{ items: KnowledgeFolder[] } | KnowledgeFolder[]>({
		orgId: params.orgId,
		path: '/knowledge/folders',
	})
}

/**
 * List Devin knowledge notes for the configured org (default `./knowledge` action).
 * Use when browsing or searching notes; named exports cover get/create/update/delete.
 *
 * @param params.orgId - Optional org override (else stored destOrgId)
 * @param params.search - Optional note search string
 * @param params.first - Page size (1–200)
 * @returns Paginated knowledge notes
 *
 * @example
 * import listNotes from 'kody:@kentcdodds/devin/knowledge'
 * const page = await listNotes({ first: 20 })
 * // => { items: [{ note_id: '...', name: '...', ... }], ... }
 */
export default async function listNotesDefault(
	params: {
		orgId?: string
		first?: number
		after?: string
		search?: string
		folderPath?: string
		pinnedRepo?: string
	} = {},
) {
	return await listNotes(params)
}