@helmlabs/docbot 0.0.1

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.
package/package.json ADDED
@@ -0,0 +1,87 @@
1
+ {
2
+ "name": "@helmlabs/docbot",
3
+ "version": "0.0.1",
4
+ "description": "AI-powered CLI for analyzing, planning, and executing documentation improvements",
5
+ "author": "Helm",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "bin": {
9
+ "docbot": "dist/cli.js"
10
+ },
11
+ "main": "./dist/cli.js",
12
+ "exports": {
13
+ ".": "./dist/cli.js",
14
+ "./config": "./src/config/index.ts"
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "src/config",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/helmlabs/docbot.git"
25
+ },
26
+ "homepage": "https://github.com/helmlabs/docbot/tree/main#readme",
27
+ "bugs": "https://github.com/helmlabs/docbot/issues",
28
+ "keywords": [
29
+ "documentation",
30
+ "ai",
31
+ "cli",
32
+ "docs",
33
+ "mdx",
34
+ "mintlify",
35
+ "bun"
36
+ ],
37
+ "engines": {
38
+ "bun": ">=1.3.0"
39
+ },
40
+ "scripts": {
41
+ "build": "bun build ./src/cli.ts --production --target=bun --format=esm --external react --external ink --outfile=dist/cli.js",
42
+ "dev": "bun run src/cli.ts",
43
+ "typecheck": "tsc --noEmit",
44
+ "prepublishOnly": "bun run build",
45
+ "knip": "knip",
46
+ "format-and-lint": "biome check .",
47
+ "format-and-lint:fix": "biome check . --fix"
48
+ },
49
+ "devDependencies": {
50
+ "@biomejs/biome": "2.0.6",
51
+ "@types/bun": "latest",
52
+ "@types/node": "^25.0.3",
53
+ "@types/react": "^19.0.0",
54
+ "@types/yargs": "^17.0.33",
55
+ "@ai-sdk/provider-utils": "^4.0.2",
56
+ "knip": "^5.79.0"
57
+ },
58
+ "peerDependencies": {
59
+ "typescript": "^5.9.3"
60
+ },
61
+ "dependencies": {
62
+ "@ai-sdk/anthropic": "^3.0.2",
63
+ "@ai-sdk/cohere": "^3.0.1",
64
+ "@ai-sdk/devtools": "^0.0.2",
65
+ "@ai-sdk/google": "^3.0.2",
66
+ "@ai-sdk/mcp": "1.0.1",
67
+ "@ai-sdk/openai": "^3.0.2",
68
+ "@ai-sdk/react": "^3.0.3",
69
+ "@elysiajs/cors": "^1.0.0",
70
+ "@qdrant/qdrant-js": "^1.16.2",
71
+ "ai": "^6.0.3",
72
+ "elysia": "^1.4.19",
73
+ "ink": "^6.6.0",
74
+ "ink-scroll-view": "^0.3.3",
75
+ "ink-spinner": "^5.0.0",
76
+ "mdast": "^3.0.0",
77
+ "nanoid": "^5.1.6",
78
+ "react": "^19.2.3",
79
+ "remark-frontmatter": "^5.0.0",
80
+ "remark-mdx": "^3.1.1",
81
+ "remark-parse": "^11.0.0",
82
+ "unified": "^11.0.5",
83
+ "yaml": "^2.8.2",
84
+ "yargs": "^18.0.0",
85
+ "zod": "^4.2.1"
86
+ }
87
+ }
package/readme.md ADDED
@@ -0,0 +1,218 @@
1
+ ![docbot header](https://ltwtljxsstgqk8ay.public.blob.vercel-storage.com/docbot.jpg)
2
+
3
+ # Docbot
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@helmlabs/docbot)](https://www.npmjs.com/package/@helmlabs/docbot)
6
+
7
+ Docbot is a CLI agent that helps you keep documentation up to date.
8
+
9
+ It reads your docs + codebase, proposes a concrete plan (file-level operations), and only writes changes after you approve.
10
+
11
+ ## Notes on speed (and why it's still worth it)
12
+
13
+ - Indexing can feel a bit slow right now; running a full cycle across a bunch of pages may take 5–10 minutes, but that's still way faster than the hours you'd spend doing it by hand
14
+ - Overall flow is under-optimized today; expect it to improve soon (again, the time and token costs are still much lower than manual work, at least for us)
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ bunx @helmlabs/docbot --help
20
+ ```
21
+
22
+ (Optional) global:
23
+
24
+ ```bash
25
+ bun add -g @helmlabs/docbot
26
+ docbot --help
27
+ ```
28
+
29
+ ## Quick Start
30
+
31
+ ```bash
32
+ # qdrant (required)
33
+ docker run --rm -p 6333:6333 -v "$(pwd)/qdrant_storage:/qdrant/storage" qdrant/qdrant
34
+
35
+ # config + local state (.docbot/, docbot.config.jsonc)
36
+ bunx @helmlabs/docbot init
37
+
38
+ # index docs/code (uses config; CLI flags override)
39
+ bunx @helmlabs/docbot index
40
+
41
+ # run the agent
42
+ bunx @helmlabs/docbot run "document the settings page"
43
+ ```
44
+
45
+ ## What It Does
46
+
47
+ - **Codebase-aware doc work**: finds gaps/stale pages by reading your code, not vibes
48
+ - **Search that's actually useful**: semantic + exact match, reranked
49
+ - **Interactive planning**: you approve the plan before anything touches your files
50
+ - **MDX-first output**: structured edits instead of “giant blob rewrite”
51
+ - **TUI + API**: run with a terminal UI, or start the HTTP server only
52
+
53
+ ## Requirements
54
+
55
+ Required:
56
+
57
+ - [Bun](https://bun.sh)
58
+ - [Qdrant](https://qdrant.tech) (local via Docker or remote - `docbot init` will set you up with a local instance via Docker)
59
+ - `rg` (ripgrep) for fast exact-match search
60
+ - `AI_GATEWAY_API_KEY` (Vercel AI Gateway)
61
+
62
+ ## How It Works
63
+
64
+ 1. **Analysis**: scan docs + codebase, find gaps/duplicates/stale content
65
+ 2. **Planning**: propose a structured set of operations (create/update/move/delete/consolidate)
66
+ 3. **Execution**: apply changes (MDX edits, component-aware when relevant)
67
+ 4. **Review**: verify and re-scan for obvious misses
68
+
69
+ ## Dependencies & Design Choices
70
+
71
+ Docbot is opinionated so we were able to build it fast, but it's not meant to stay tied to a single docs framework or provider forever.
72
+
73
+ - **Docs frameworks**: Today Docbot targets MDX-based doc sites and detects Mintlify project structure automatically (as long as you use `docs.json`). Mintlify was the first target because that's what we use at Helm; support will expand (custom MDX, Fumadocs, Nextra, Docusaurus, etc.). It's just a matter of tweaking the tools and prompts.
74
+ - **Vector store**: Currently Qdrant (required). It's easy to run locally and does the job well. This may evolve as CI/multi-user needs grow.
75
+ - **Models/provider**: Currently Vercel AI Gateway via `AI_GATEWAY_API_KEY`. Adding other providers is planned - you can use configure the models in the config file though.
76
+ - **Bun**: Required. Will not change.
77
+
78
+ ## Commands
79
+
80
+ ### `docbot init`
81
+
82
+ Scaffolds project config in the repo root:
83
+
84
+ - `.docbot/`
85
+ - `docbot.config.jsonc`
86
+
87
+ ```bash
88
+ docbot init
89
+ ```
90
+
91
+ Options:
92
+
93
+ - `--force`: overwrite existing config
94
+ - `--skip-docker`: skip docker setup (you'll need to set up Qdrant manually)
95
+
96
+ ### `docbot index`
97
+
98
+ Indexes docs/code for search. If you don't pass flags, it uses your config.
99
+
100
+ ```bash
101
+ docbot index
102
+ # or
103
+ docbot index --docs ./docs --codebase ./src
104
+ ```
105
+
106
+ Options:
107
+
108
+ - `--docs`: docs path (optional if configured)
109
+ - `--codebase`: one or more codebase paths
110
+ - `--config`: config file path (default: `docbot.config.jsonc`)
111
+ - `--qdrant-url`: qdrant url (default: [http://127.0.0.1:6333](http://127.0.0.1:6333))
112
+ - `--force`: force full re-index, ignoring manifest
113
+
114
+ ### `docbot run "<task>"`
115
+
116
+ Runs the interactive workflow (plan → approval → execution → review).
117
+
118
+ ```bash
119
+ docbot run "document the new api endpoints"
120
+ ```
121
+
122
+ Options:
123
+
124
+ - `--docs`: docs path (optional if configured)
125
+ - `--codebase`: one or more codebase paths
126
+ - `--config`: config file path (default: `docbot.config.jsonc`)
127
+ - `--interactive`: plan approval (default: true)
128
+ - `--port`: server port (default: 3070)
129
+ - `--qdrant-url`: qdrant url (default: [http://127.0.0.1:6333](http://127.0.0.1:6333))
130
+ - `--index-only`: only index, don't run
131
+ - `--verbose` / `--no-verbose`: detailed logging + log panel
132
+ - `--no-server`: reuse an already running server
133
+ - `--force`: rebuild embeddings from scratch
134
+
135
+ ### `docbot search "<query>"`
136
+
137
+ ```bash
138
+ docbot search "authentication" --type hybrid --limit 10
139
+ ```
140
+
141
+ Options:
142
+
143
+ - `--type`: `semantic`, `exact`, `hybrid` (default: `hybrid`)
144
+ - `--limit`: max results (default: 5)
145
+
146
+ ### `docbot serve`
147
+
148
+ Starts the HTTP server (Elysia) without the TUI.
149
+
150
+ ```bash
151
+ docbot serve --port 3070
152
+ ```
153
+
154
+ ## Configuration
155
+
156
+ `docbot init` creates `docbot.config.jsonc`. CLI flags override config.
157
+
158
+ Example:
159
+
160
+ ```jsonc
161
+ {
162
+ "projectSlug": "my-project",
163
+ "paths": {
164
+ "docs": "./docs",
165
+ "codebase": ["./apps/web", "./packages/shared"]
166
+ },
167
+ "qdrant": {
168
+ "url": "http://127.0.0.1:6333",
169
+ "manifestPath": ".docbot/manifest.json",
170
+ "collections": {
171
+ "docs": "docbot_my-project_docs",
172
+ "code": "docbot_my-project_code"
173
+ }
174
+ },
175
+ "server": { "port": 3070 },
176
+ "models": {
177
+ "planning": "openai/gpt-5.2",
178
+ "prose": "anthropic/claude-sonnet-4.5",
179
+ "fast": "anthropic/claude-haiku-4.5",
180
+ "embedding": "openai/text-embedding-3-small",
181
+ "reranker": "cohere/rerank-v3.5"
182
+ }
183
+ }
184
+ ```
185
+
186
+ CLI flags take precedence over the config file.
187
+
188
+ ## Logs & UI
189
+
190
+ - **Run (default)**: TUI + server in one process (verbose by default; log panel available)
191
+ - **Serve**: server only; logs to stdout
192
+ - **Index-only**: `--index-only`
193
+ - **No-server**: `--no-server`
194
+
195
+ Log panel (TUI, verbose only):
196
+
197
+ - Toggle: `Ctrl+L`
198
+ - Tabs: `←/→`
199
+ - Scroll: `Shift+↑/↓` or `Shift+PgUp/PgDn`
200
+ - Clear: `C`
201
+
202
+ ## Contributing
203
+
204
+ PRs welcome. Issues welcome.
205
+
206
+ ## Support
207
+
208
+ - This is a very new project—if you hit issues, please open an issue here
209
+ - You can also reach celia on X: [@pariscestchiant](https://x.com/pariscestchiant)
210
+ - Our own docs live at [docs.helmkit.com](https://docs.helmkit.com)
211
+
212
+ ## License
213
+
214
+ MIT
215
+
216
+ ## Todo
217
+
218
+ Tasks and progress are tracked in [todo.md](todo.md). Next big focus is changebot's integration inside of docbot (it's a changelog generator between two commits; we use it at [helmkit.com/changelog](https://helmkit.com/changelog))
@@ -0,0 +1,41 @@
1
+ // default configuration values for docbot
2
+
3
+ export const DEFAULT_QDRANT_URL = "http://127.0.0.1:6333"
4
+ export const DEFAULT_SERVER_PORT = 3070
5
+
6
+ export const DEFAULT_MODELS = {
7
+ context: "google/gemini-3-pro-preview",
8
+ embedding: "openai/text-embedding-3-small",
9
+ embeddingLarge: "openai/text-embedding-3-large",
10
+ fast: "openai/gpt-5.2",
11
+ nano: "google/gemini-3-flash",
12
+ planning: "openai/gpt-5.2",
13
+ planningHeavy: "anthropic/claude-opus-4.5",
14
+ prose: "anthropic/claude-sonnet-4.5",
15
+ } as const
16
+
17
+ export const DEFAULT_AGENTS = {
18
+ discoveryBudget: 6,
19
+ } as const
20
+
21
+ /**
22
+ * generate collection names from project slug
23
+ */
24
+ export function makeCollectionNames(slug: string) {
25
+ return {
26
+ code: `docbot_${slug}_code`,
27
+ docs: `docbot_${slug}_docs`,
28
+ }
29
+ }
30
+
31
+ /**
32
+ * sanitize a string for use as a project slug
33
+ * lowercase, alphanumeric and hyphens only
34
+ */
35
+ export function sanitizeSlug(name: string): string {
36
+ return name
37
+ .toLowerCase()
38
+ .replace(/[^a-z0-9-]/g, "-")
39
+ .replace(/-+/g, "-")
40
+ .replace(/^-|-$/g, "")
41
+ }
@@ -0,0 +1,57 @@
1
+ // public exports for user configuration files
2
+ // users can import from "docbot/config" in their docbot.config.ts
3
+
4
+ import { gateway as aiGateway } from "ai"
5
+ import type { DocbotUserConfig } from "./schema"
6
+
7
+ export {
8
+ DEFAULT_AGENTS,
9
+ DEFAULT_MODELS,
10
+ DEFAULT_QDRANT_URL,
11
+ DEFAULT_SERVER_PORT,
12
+ makeCollectionNames,
13
+ sanitizeSlug,
14
+ } from "./defaults"
15
+ export {
16
+ findProjectRoot,
17
+ type LoadConfigOptions,
18
+ loadConfig,
19
+ } from "./loader"
20
+ export type { DocbotUserConfig, ResolvedConfig } from "./schema"
21
+ export { docbotConfigSchema } from "./schema"
22
+
23
+ /**
24
+ * helper for defining a typed config file
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * // docbot.config.ts
29
+ * import { defineConfig } from "docbot/config"
30
+ *
31
+ * export default defineConfig({
32
+ * projectSlug: "my-docs",
33
+ * models: {
34
+ * planning: "openai/gpt-4o",
35
+ * },
36
+ * })
37
+ * ```
38
+ */
39
+ export function defineConfig(config: DocbotUserConfig): DocbotUserConfig {
40
+ return config
41
+ }
42
+
43
+ /**
44
+ * re-export ai gateway for model configuration
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * import { defineConfig, gateway } from "docbot/config"
49
+ *
50
+ * export default defineConfig({
51
+ * models: {
52
+ * planning: gateway("openai/gpt-4o"),
53
+ * },
54
+ * })
55
+ * ```
56
+ */
57
+ export const gateway: typeof aiGateway = aiGateway
@@ -0,0 +1,284 @@
1
+ import { existsSync } from "node:fs"
2
+ import { readFile } from "node:fs/promises"
3
+ import { dirname, join, resolve } from "node:path"
4
+ import {
5
+ DEFAULT_AGENTS,
6
+ DEFAULT_MODELS,
7
+ DEFAULT_QDRANT_URL,
8
+ DEFAULT_SERVER_PORT,
9
+ makeCollectionNames,
10
+ sanitizeSlug,
11
+ } from "./defaults"
12
+ import {
13
+ type DocbotUserConfig,
14
+ docbotConfigSchema,
15
+ type ResolvedConfig,
16
+ } from "./schema"
17
+
18
+ const CONFIG_FILENAMES = [
19
+ "docbot.config.ts",
20
+ "docbot.config.js",
21
+ "docbot.config.json",
22
+ "docbot.config.jsonc",
23
+ ]
24
+
25
+ /**
26
+ * find the config file by searching up from the given directory
27
+ */
28
+ function findConfigFile(startDir: string): string | null {
29
+ let current = resolve(startDir)
30
+ const root = dirname(current)
31
+
32
+ while (current !== root) {
33
+ for (const filename of CONFIG_FILENAMES) {
34
+ const candidate = join(current, filename)
35
+ if (existsSync(candidate)) {
36
+ return candidate
37
+ }
38
+ }
39
+ current = dirname(current)
40
+ }
41
+
42
+ return null
43
+ }
44
+
45
+ /**
46
+ * find package.json and extract project name
47
+ */
48
+ async function findProjectName(startDir: string): Promise<string | null> {
49
+ let current = resolve(startDir)
50
+ const root = dirname(current)
51
+
52
+ while (current !== root) {
53
+ const pkgPath = join(current, "package.json")
54
+ if (existsSync(pkgPath)) {
55
+ try {
56
+ const content = await readFile(pkgPath, "utf-8")
57
+ const pkg = JSON.parse(content)
58
+ return pkg.name ?? null
59
+ } catch {
60
+ return null
61
+ }
62
+ }
63
+ current = dirname(current)
64
+ }
65
+
66
+ return null
67
+ }
68
+
69
+ /**
70
+ * find the project root (directory containing package.json)
71
+ */
72
+ export function findProjectRoot(startDir: string): string | null {
73
+ let current = resolve(startDir)
74
+ const root = dirname(current)
75
+
76
+ while (current !== root) {
77
+ const pkgPath = join(current, "package.json")
78
+ if (existsSync(pkgPath)) {
79
+ return current
80
+ }
81
+ current = dirname(current)
82
+ }
83
+
84
+ return null
85
+ }
86
+
87
+ /**
88
+ * load and parse a config file
89
+ */
90
+ async function loadConfigFile(path: string): Promise<DocbotUserConfig | null> {
91
+ try {
92
+ if (path.endsWith(".ts") || path.endsWith(".js")) {
93
+ // use bun's import for ts/js files
94
+ const mod = await import(path)
95
+ return mod.default ?? mod
96
+ }
97
+
98
+ if (path.endsWith(".jsonc")) {
99
+ // use bun's native jsonc loader which handles comments and trailing commas
100
+ const mod = await import(path, { with: { type: "jsonc" } })
101
+ return mod.default
102
+ }
103
+
104
+ // plain json
105
+ const content = await readFile(path, "utf-8")
106
+ return JSON.parse(content)
107
+ } catch (error) {
108
+ console.warn(`failed to load config from ${path}:`, error)
109
+ return null
110
+ }
111
+ }
112
+
113
+ export interface LoadConfigOptions {
114
+ // starting directory for config search
115
+ startDir: string
116
+ // explicit config file path (overrides search)
117
+ configPath?: string
118
+ // cli overrides
119
+ overrides?: Partial<{
120
+ qdrantUrl: string
121
+ port: number
122
+ docs: string
123
+ codebase: string | string[]
124
+ }>
125
+ }
126
+
127
+ function normalizeCodebase(value?: string | string[]): string[] | undefined {
128
+ if (!value) return undefined
129
+ if (Array.isArray(value)) return value.filter(Boolean)
130
+ return value
131
+ .split(",")
132
+ .map((v) => v.trim())
133
+ .filter(Boolean)
134
+ }
135
+
136
+ function readEnv() {
137
+ return {
138
+ codebase: process.env.DOCBOT_CODEBASE,
139
+ docs: process.env.DOCBOT_DOCS,
140
+ manifest: process.env.DOCBOT_MANIFEST_PATH,
141
+ port: process.env.DOCBOT_PORT,
142
+ qdrantUrl: process.env.QDRANT_URL,
143
+ }
144
+ }
145
+
146
+ async function loadUserConfigFromFile(
147
+ configPath: string | undefined,
148
+ startDir: string,
149
+ ): Promise<DocbotUserConfig> {
150
+ const configFile = configPath ?? findConfigFile(startDir)
151
+ if (!configFile) return {}
152
+
153
+ const loaded = await loadConfigFile(configFile)
154
+ if (!loaded) return {}
155
+
156
+ const parsed = docbotConfigSchema.safeParse(loaded)
157
+ if (parsed.success) {
158
+ return parsed.data
159
+ }
160
+
161
+ console.warn("invalid config file:", parsed.error.format())
162
+ return {}
163
+ }
164
+
165
+ async function resolveProjectInfo(startDir: string) {
166
+ const projectRoot = findProjectRoot(startDir)
167
+ const projectName = await findProjectName(startDir)
168
+ return {
169
+ defaultSlug: projectName ? sanitizeSlug(projectName) : "docbot",
170
+ projectRoot,
171
+ }
172
+ }
173
+
174
+ function resolvePaths(
175
+ baseDir: string,
176
+ userConfig: DocbotUserConfig,
177
+ env: ReturnType<typeof readEnv>,
178
+ overrides?: LoadConfigOptions["overrides"],
179
+ ) {
180
+ const cacheDir = join(baseDir, ".docbot")
181
+ const manifestPath =
182
+ env.manifest ??
183
+ userConfig.qdrant?.manifestPath ??
184
+ join(cacheDir, "manifest.json")
185
+ const docsPath =
186
+ overrides?.docs ?? env.docs ?? userConfig.paths?.docs ?? undefined
187
+ const codebasePaths = normalizeCodebase(
188
+ overrides?.codebase ?? env.codebase ?? userConfig.paths?.codebase,
189
+ )
190
+
191
+ return {
192
+ cacheDir,
193
+ codebase: codebasePaths?.map((p) => resolve(baseDir, p)),
194
+ docs: docsPath ? resolve(baseDir, docsPath) : undefined,
195
+ manifest: manifestPath,
196
+ }
197
+ }
198
+
199
+ function resolveAgentsConfig(userConfig: DocbotUserConfig) {
200
+ return {
201
+ discoveryBudget:
202
+ userConfig.agents?.discoveryBudget ?? DEFAULT_AGENTS.discoveryBudget,
203
+ }
204
+ }
205
+
206
+ function resolveModelConfig(userConfig: DocbotUserConfig) {
207
+ return {
208
+ context: userConfig.models?.context ?? DEFAULT_MODELS.context,
209
+ embedding: userConfig.models?.embedding ?? DEFAULT_MODELS.embedding,
210
+ embeddingLarge:
211
+ userConfig.models?.embeddingLarge ?? DEFAULT_MODELS.embeddingLarge,
212
+ fast: userConfig.models?.fast ?? DEFAULT_MODELS.fast,
213
+ nano: userConfig.models?.nano ?? DEFAULT_MODELS.nano,
214
+ planning: userConfig.models?.planning ?? DEFAULT_MODELS.planning,
215
+ planningHeavy:
216
+ userConfig.models?.planningHeavy ?? DEFAULT_MODELS.planningHeavy,
217
+ prose: userConfig.models?.prose ?? DEFAULT_MODELS.prose,
218
+ }
219
+ }
220
+
221
+ function resolveQdrantConfig(
222
+ userConfig: DocbotUserConfig,
223
+ slug: string,
224
+ env: ReturnType<typeof readEnv>,
225
+ overrides: LoadConfigOptions["overrides"],
226
+ manifestPath: string,
227
+ ) {
228
+ return {
229
+ collections: userConfig.qdrant?.collections ?? makeCollectionNames(slug),
230
+ manifestPath,
231
+ url:
232
+ overrides?.qdrantUrl ??
233
+ userConfig.qdrant?.url ??
234
+ env.qdrantUrl ??
235
+ DEFAULT_QDRANT_URL,
236
+ }
237
+ }
238
+
239
+ function resolveServerConfig(
240
+ userConfig: DocbotUserConfig,
241
+ env: ReturnType<typeof readEnv>,
242
+ overrides: LoadConfigOptions["overrides"],
243
+ ) {
244
+ return {
245
+ port:
246
+ overrides?.port ??
247
+ userConfig.server?.port ??
248
+ (env.port ? Number(env.port) : undefined) ??
249
+ DEFAULT_SERVER_PORT,
250
+ }
251
+ }
252
+
253
+ /**
254
+ * load and resolve the full configuration
255
+ *
256
+ * priority: cli args > config file > defaults
257
+ */
258
+ export async function loadConfig(
259
+ options: LoadConfigOptions,
260
+ ): Promise<ResolvedConfig> {
261
+ const { startDir, configPath, overrides } = options
262
+
263
+ const env = readEnv()
264
+ const { defaultSlug, projectRoot } = await resolveProjectInfo(startDir)
265
+ const userConfig = await loadUserConfigFromFile(configPath, startDir)
266
+ const slug = userConfig.projectSlug ?? defaultSlug
267
+ const baseDir = projectRoot ?? startDir
268
+ const paths = resolvePaths(baseDir, userConfig, env, overrides)
269
+
270
+ return {
271
+ agents: resolveAgentsConfig(userConfig),
272
+ models: resolveModelConfig(userConfig),
273
+ paths,
274
+ projectSlug: slug,
275
+ qdrant: resolveQdrantConfig(
276
+ userConfig,
277
+ slug,
278
+ env,
279
+ overrides,
280
+ paths.manifest,
281
+ ),
282
+ server: resolveServerConfig(userConfig, env, overrides),
283
+ }
284
+ }