agentikit 0.0.13 → 0.0.15
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/LICENSE +385 -0
- package/README.md +187 -110
- package/dist/{src/asset-spec.js → asset-spec.js} +11 -2
- package/dist/{src/asset-type-handler.js → asset-type-handler.js} +4 -3
- package/dist/cli.js +709 -0
- package/dist/common.js +192 -0
- package/dist/{src/config-cli.js → config-cli.js} +36 -30
- package/dist/{src/config.js → config.js} +95 -25
- package/dist/{src/db.js → db.js} +123 -51
- package/dist/{src/embedder.js → embedder.js} +57 -2
- package/dist/errors.js +28 -0
- package/dist/file-context.js +188 -0
- package/dist/{src/frontmatter.js → frontmatter.js} +1 -1
- package/dist/{src/github.js → github.js} +1 -3
- package/dist/handlers/agent-handler.js +19 -0
- package/dist/handlers/command-handler.js +20 -0
- package/dist/handlers/handler-bridge.js +51 -0
- package/dist/handlers/index.js +19 -0
- package/dist/handlers/knowledge-handler.js +32 -0
- package/dist/handlers/script-handler.js +42 -0
- package/dist/{src/handlers → handlers}/skill-handler.js +5 -6
- package/dist/{src/handlers → handlers}/tool-handler.js +8 -24
- package/dist/{src/indexer.js → indexer.js} +50 -26
- package/dist/init.js +43 -0
- package/dist/{src/llm.js → llm.js} +6 -11
- package/dist/lockfile.js +60 -0
- package/dist/matchers.js +163 -0
- package/dist/{src/metadata.js → metadata.js} +36 -16
- package/dist/{src/origin-resolve.js → origin-resolve.js} +10 -9
- package/dist/paths.js +83 -0
- package/dist/{src/registry-install.js → registry-install.js} +151 -19
- package/dist/{src/registry-resolve.js → registry-resolve.js} +190 -26
- package/dist/{src/registry-search.js → registry-search.js} +13 -21
- package/dist/renderers.js +286 -0
- package/dist/{src/ripgrep-install.js → ripgrep-install.js} +8 -27
- package/dist/{src/ripgrep-resolve.js → ripgrep-resolve.js} +21 -11
- package/dist/ripgrep.js +2 -0
- package/dist/self-update.js +226 -0
- package/dist/{src/stash-add.js → stash-add.js} +14 -4
- package/dist/stash-clone.js +115 -0
- package/dist/{src/stash-ref.js → stash-ref.js} +10 -9
- package/dist/{src/stash-registry.js → stash-registry.js} +21 -46
- package/dist/{src/stash-resolve.js → stash-resolve.js} +10 -9
- package/dist/{src/stash-search.js → stash-search.js} +89 -74
- package/dist/stash-show.js +74 -0
- package/dist/stash-source.js +127 -0
- package/dist/submit.js +557 -0
- package/dist/{src/tool-runner.js → tool-runner.js} +1 -5
- package/dist/{src/walker.js → walker.js} +38 -0
- package/dist/warn.js +20 -0
- package/package.json +13 -18
- package/dist/index.d.ts +0 -28
- package/dist/index.js +0 -15
- package/dist/src/asset-spec.d.ts +0 -16
- package/dist/src/asset-type-handler.d.ts +0 -27
- package/dist/src/cli.d.ts +0 -2
- package/dist/src/cli.js +0 -399
- package/dist/src/common.d.ts +0 -13
- package/dist/src/common.js +0 -60
- package/dist/src/config-cli.d.ts +0 -9
- package/dist/src/config.d.ts +0 -50
- package/dist/src/db.d.ts +0 -46
- package/dist/src/embedder.d.ts +0 -10
- package/dist/src/frontmatter.d.ts +0 -30
- package/dist/src/github.d.ts +0 -4
- package/dist/src/handlers/agent-handler.d.ts +0 -2
- package/dist/src/handlers/agent-handler.js +0 -26
- package/dist/src/handlers/command-handler.d.ts +0 -2
- package/dist/src/handlers/command-handler.js +0 -23
- package/dist/src/handlers/index.d.ts +0 -6
- package/dist/src/handlers/index.js +0 -23
- package/dist/src/handlers/knowledge-handler.d.ts +0 -2
- package/dist/src/handlers/knowledge-handler.js +0 -56
- package/dist/src/handlers/markdown-helpers.d.ts +0 -7
- package/dist/src/handlers/script-handler.d.ts +0 -2
- package/dist/src/handlers/script-handler.js +0 -78
- package/dist/src/handlers/skill-handler.d.ts +0 -2
- package/dist/src/handlers/tool-handler.d.ts +0 -2
- package/dist/src/indexer.d.ts +0 -22
- package/dist/src/init.d.ts +0 -19
- package/dist/src/init.js +0 -99
- package/dist/src/llm.d.ts +0 -15
- package/dist/src/markdown.d.ts +0 -18
- package/dist/src/metadata.d.ts +0 -41
- package/dist/src/origin-resolve.d.ts +0 -19
- package/dist/src/registry-install.d.ts +0 -11
- package/dist/src/registry-resolve.d.ts +0 -3
- package/dist/src/registry-search.d.ts +0 -27
- package/dist/src/registry-types.d.ts +0 -62
- package/dist/src/ripgrep-install.d.ts +0 -12
- package/dist/src/ripgrep-resolve.d.ts +0 -13
- package/dist/src/ripgrep.d.ts +0 -3
- package/dist/src/ripgrep.js +0 -2
- package/dist/src/stash-add.d.ts +0 -4
- package/dist/src/stash-clone.d.ts +0 -22
- package/dist/src/stash-clone.js +0 -83
- package/dist/src/stash-ref.d.ts +0 -31
- package/dist/src/stash-registry.d.ts +0 -18
- package/dist/src/stash-resolve.d.ts +0 -2
- package/dist/src/stash-search.d.ts +0 -8
- package/dist/src/stash-show.d.ts +0 -5
- package/dist/src/stash-show.js +0 -46
- package/dist/src/stash-source.d.ts +0 -24
- package/dist/src/stash-source.js +0 -81
- package/dist/src/stash-types.d.ts +0 -227
- package/dist/src/stash.d.ts +0 -16
- package/dist/src/stash.js +0 -9
- package/dist/src/tool-runner.d.ts +0 -35
- package/dist/src/walker.d.ts +0 -19
- package/src/asset-spec.ts +0 -85
- package/src/asset-type-handler.ts +0 -77
- package/src/cli.ts +0 -427
- package/src/common.ts +0 -76
- package/src/config-cli.ts +0 -499
- package/src/config.ts +0 -305
- package/src/db.ts +0 -411
- package/src/embedder.ts +0 -128
- package/src/frontmatter.ts +0 -95
- package/src/github.ts +0 -21
- package/src/handlers/agent-handler.ts +0 -32
- package/src/handlers/command-handler.ts +0 -29
- package/src/handlers/index.ts +0 -25
- package/src/handlers/knowledge-handler.ts +0 -62
- package/src/handlers/markdown-helpers.ts +0 -19
- package/src/handlers/script-handler.ts +0 -92
- package/src/handlers/skill-handler.ts +0 -37
- package/src/handlers/tool-handler.ts +0 -71
- package/src/indexer.ts +0 -392
- package/src/init.ts +0 -114
- package/src/llm.ts +0 -125
- package/src/markdown.ts +0 -106
- package/src/metadata.ts +0 -333
- package/src/origin-resolve.ts +0 -67
- package/src/registry-install.ts +0 -361
- package/src/registry-resolve.ts +0 -341
- package/src/registry-search.ts +0 -335
- package/src/registry-types.ts +0 -72
- package/src/ripgrep-install.ts +0 -200
- package/src/ripgrep-resolve.ts +0 -72
- package/src/ripgrep.ts +0 -3
- package/src/stash-add.ts +0 -63
- package/src/stash-clone.ts +0 -127
- package/src/stash-ref.ts +0 -99
- package/src/stash-registry.ts +0 -259
- package/src/stash-resolve.ts +0 -50
- package/src/stash-search.ts +0 -613
- package/src/stash-show.ts +0 -55
- package/src/stash-source.ts +0 -103
- package/src/stash-types.ts +0 -231
- package/src/stash.ts +0 -39
- package/src/tool-runner.ts +0 -142
- package/src/walker.ts +0 -53
- /package/dist/{src/handlers → handlers}/markdown-helpers.js +0 -0
- /package/dist/{src/markdown.js → markdown.js} +0 -0
- /package/dist/{src/registry-types.js → registry-types.js} +0 -0
- /package/dist/{src/stash-types.js → stash-types.js} +0 -0
package/dist/common.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { TYPE_DIRS } from "./asset-spec";
|
|
4
|
+
import { ConfigError } from "./errors";
|
|
5
|
+
import { getConfigPath, getDefaultStashDir } from "./paths";
|
|
6
|
+
// ── Constants ───────────────────────────────────────────────────────────────
|
|
7
|
+
export const IS_WINDOWS = process.platform === "win32";
|
|
8
|
+
// ── Validators ──────────────────────────────────────────────────────────────
|
|
9
|
+
export function isAssetType(type) {
|
|
10
|
+
return type in TYPE_DIRS;
|
|
11
|
+
}
|
|
12
|
+
// ── Utilities ───────────────────────────────────────────────────────────────
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the stash directory using a three-level fallback chain:
|
|
15
|
+
* 1. AKM_STASH_DIR environment variable (override for CI/scripts)
|
|
16
|
+
* 2. stashDir field in config.json
|
|
17
|
+
* 3. Platform default (~/agentikit or ~/Documents/agentikit on Windows)
|
|
18
|
+
*
|
|
19
|
+
* Throws if no valid stash directory is found.
|
|
20
|
+
*/
|
|
21
|
+
export function resolveStashDir(options) {
|
|
22
|
+
// 1. Env var override (for CI, scripts, testing)
|
|
23
|
+
const envDir = process.env.AKM_STASH_DIR?.trim();
|
|
24
|
+
if (envDir) {
|
|
25
|
+
const resolved = validateStashDir(envDir);
|
|
26
|
+
if (!options?.readOnly)
|
|
27
|
+
persistStashDirToConfig(resolved);
|
|
28
|
+
return resolved;
|
|
29
|
+
}
|
|
30
|
+
// 2. Config file stashDir field
|
|
31
|
+
const configStashDir = readStashDirFromConfig();
|
|
32
|
+
if (configStashDir)
|
|
33
|
+
return validateStashDir(configStashDir);
|
|
34
|
+
// 3. Platform default — use it if it exists
|
|
35
|
+
const defaultDir = getDefaultStashDir();
|
|
36
|
+
if (isValidDirectory(defaultDir)) {
|
|
37
|
+
return defaultDir;
|
|
38
|
+
}
|
|
39
|
+
throw new ConfigError(`No stash directory found. Run "akm init" to create one at ${defaultDir}, ` +
|
|
40
|
+
`or set stashDir in ${getConfigPath()}.`);
|
|
41
|
+
}
|
|
42
|
+
function validateStashDir(raw) {
|
|
43
|
+
const stashDir = path.resolve(raw);
|
|
44
|
+
let stat;
|
|
45
|
+
try {
|
|
46
|
+
stat = fs.statSync(stashDir);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
throw new ConfigError(`Unable to read stash directory at "${stashDir}".`);
|
|
50
|
+
}
|
|
51
|
+
if (!stat.isDirectory()) {
|
|
52
|
+
throw new ConfigError(`Stash path must point to a directory: "${stashDir}".`);
|
|
53
|
+
}
|
|
54
|
+
return stashDir;
|
|
55
|
+
}
|
|
56
|
+
function isValidDirectory(dir) {
|
|
57
|
+
try {
|
|
58
|
+
return fs.statSync(dir).isDirectory();
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Read stashDir directly from config.json without pulling in the full config
|
|
66
|
+
* module, to avoid circular dependencies.
|
|
67
|
+
*/
|
|
68
|
+
function readStashDirFromConfig() {
|
|
69
|
+
try {
|
|
70
|
+
const configPath = getConfigPath();
|
|
71
|
+
const text = fs.readFileSync(configPath, "utf8");
|
|
72
|
+
const raw = JSON.parse(text);
|
|
73
|
+
if (typeof raw === "object" && raw !== null && typeof raw.stashDir === "string" && raw.stashDir.trim()) {
|
|
74
|
+
return raw.stashDir.trim();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Config doesn't exist or is invalid — fall through
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Persist stashDir to config.json if not already set, so users can
|
|
84
|
+
* transition away from relying on the AKM_STASH_DIR env var.
|
|
85
|
+
*/
|
|
86
|
+
function persistStashDirToConfig(stashDir) {
|
|
87
|
+
try {
|
|
88
|
+
const configPath = getConfigPath();
|
|
89
|
+
let raw = {};
|
|
90
|
+
try {
|
|
91
|
+
const text = fs.readFileSync(configPath, "utf8");
|
|
92
|
+
const parsed = JSON.parse(text);
|
|
93
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
94
|
+
raw = parsed;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// No existing config or invalid — start fresh
|
|
99
|
+
}
|
|
100
|
+
if (!raw.stashDir) {
|
|
101
|
+
raw.stashDir = stashDir;
|
|
102
|
+
const dir = path.dirname(configPath);
|
|
103
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
104
|
+
const tmpPath = configPath + `.tmp.${process.pid}`;
|
|
105
|
+
fs.writeFileSync(tmpPath, JSON.stringify(raw, null, 2) + "\n", "utf8");
|
|
106
|
+
fs.renameSync(tmpPath, configPath);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Non-fatal: best-effort persistence
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export function toPosix(input) {
|
|
114
|
+
return input.replace(/\\/g, "/");
|
|
115
|
+
}
|
|
116
|
+
export function hasErrnoCode(error, code) {
|
|
117
|
+
if (typeof error !== "object" || error === null || !("code" in error))
|
|
118
|
+
return false;
|
|
119
|
+
return error.code === code;
|
|
120
|
+
}
|
|
121
|
+
export function isWithin(candidate, root) {
|
|
122
|
+
const resolvedRoot = safeRealpath(root);
|
|
123
|
+
const resolvedCandidate = safeRealpath(candidate);
|
|
124
|
+
const normalizedRoot = normalizeFsPathForComparison(resolvedRoot);
|
|
125
|
+
const normalizedCandidate = normalizeFsPathForComparison(resolvedCandidate);
|
|
126
|
+
const rel = path.relative(normalizedRoot, normalizedCandidate);
|
|
127
|
+
return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
|
|
128
|
+
}
|
|
129
|
+
function safeRealpath(p) {
|
|
130
|
+
try {
|
|
131
|
+
return fs.realpathSync(path.resolve(p));
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return path.resolve(p);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function normalizeFsPathForComparison(value) {
|
|
138
|
+
return process.platform === "win32" ? value.toLowerCase() : value;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Fetch with an AbortController timeout.
|
|
142
|
+
* Defaults to 30 seconds if no timeout is specified.
|
|
143
|
+
*/
|
|
144
|
+
export async function fetchWithTimeout(url, opts, timeoutMs = 30_000) {
|
|
145
|
+
const controller = new AbortController();
|
|
146
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
147
|
+
try {
|
|
148
|
+
return await fetch(url, { ...opts, signal: controller.signal });
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
clearTimeout(timer);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Fetch with retry and exponential backoff.
|
|
156
|
+
* Retries on network errors, 429, and 5xx responses.
|
|
157
|
+
* Honors Retry-After header for 429 responses.
|
|
158
|
+
*/
|
|
159
|
+
export async function fetchWithRetry(url, init, options) {
|
|
160
|
+
const maxRetries = options?.retries ?? 3;
|
|
161
|
+
const baseDelay = options?.baseDelay ?? 500;
|
|
162
|
+
const timeout = options?.timeout ?? 30_000;
|
|
163
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
164
|
+
try {
|
|
165
|
+
const response = await fetchWithTimeout(url, init, timeout);
|
|
166
|
+
if (attempt < maxRetries && shouldRetry(response.status)) {
|
|
167
|
+
const retryAfter = parseRetryAfter(response);
|
|
168
|
+
const delay = retryAfter ?? baseDelay * 2 ** attempt * (0.5 + Math.random() * 0.5);
|
|
169
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
return response;
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
if (attempt >= maxRetries)
|
|
176
|
+
throw err;
|
|
177
|
+
const delay = baseDelay * 2 ** attempt * (0.5 + Math.random() * 0.5);
|
|
178
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
throw new Error("fetchWithRetry: unreachable");
|
|
182
|
+
}
|
|
183
|
+
function shouldRetry(status) {
|
|
184
|
+
return status === 429 || status >= 500;
|
|
185
|
+
}
|
|
186
|
+
function parseRetryAfter(response) {
|
|
187
|
+
const header = response.headers.get("retry-after");
|
|
188
|
+
if (!header)
|
|
189
|
+
return undefined;
|
|
190
|
+
const seconds = parseInt(header, 10);
|
|
191
|
+
return isNaN(seconds) ? undefined : seconds * 1000;
|
|
192
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_CONFIG, } from "./config";
|
|
2
2
|
import { EMBEDDING_DIM } from "./db";
|
|
3
|
+
import { ConfigError, UsageError } from "./errors";
|
|
3
4
|
const LOCAL_EMBEDDING_MODEL = "Xenova/all-MiniLM-L6-v2";
|
|
4
5
|
const DEFAULT_LLM_TEMPERATURE = 0.3;
|
|
5
6
|
const DEFAULT_LLM_MAX_TOKENS = 512;
|
|
@@ -59,35 +60,39 @@ const LLM_PROVIDER_PRESETS = {
|
|
|
59
60
|
};
|
|
60
61
|
export function parseConfigValue(key, value) {
|
|
61
62
|
switch (key) {
|
|
63
|
+
case "stashDir":
|
|
64
|
+
return { stashDir: requireNonEmptyString(value, key) };
|
|
62
65
|
case "semanticSearch":
|
|
63
66
|
if (value !== "true" && value !== "false") {
|
|
64
|
-
throw new
|
|
67
|
+
throw new UsageError(`Invalid value for semanticSearch: expected "true" or "false"`);
|
|
65
68
|
}
|
|
66
69
|
return { semanticSearch: value === "true" };
|
|
67
|
-
case "
|
|
70
|
+
case "searchPaths":
|
|
68
71
|
try {
|
|
69
72
|
const parsed = JSON.parse(value);
|
|
70
73
|
if (!Array.isArray(parsed))
|
|
71
|
-
throw new
|
|
72
|
-
return {
|
|
74
|
+
throw new UsageError("expected JSON array");
|
|
75
|
+
return { searchPaths: parsed.filter((d) => typeof d === "string") };
|
|
73
76
|
}
|
|
74
77
|
catch {
|
|
75
|
-
throw new
|
|
78
|
+
throw new UsageError(`Invalid value for searchPaths: expected JSON array (e.g. '["/path/a","/path/b"]')`);
|
|
76
79
|
}
|
|
77
80
|
case "embedding":
|
|
78
81
|
return { embedding: parseEmbeddingConnectionValue(value) };
|
|
79
82
|
case "llm":
|
|
80
83
|
return { llm: parseLlmConnectionValue(value) };
|
|
81
84
|
default:
|
|
82
|
-
throw new
|
|
85
|
+
throw new UsageError(`Unknown config key: ${key}`);
|
|
83
86
|
}
|
|
84
87
|
}
|
|
85
88
|
export function getConfigValue(config, key) {
|
|
86
89
|
switch (key) {
|
|
90
|
+
case "stashDir":
|
|
91
|
+
return config.stashDir ?? null;
|
|
87
92
|
case "semanticSearch":
|
|
88
93
|
return config.semanticSearch;
|
|
89
|
-
case "
|
|
90
|
-
return [...config.
|
|
94
|
+
case "searchPaths":
|
|
95
|
+
return [...config.searchPaths];
|
|
91
96
|
case "embedding":
|
|
92
97
|
return maskSecrets(getEmbeddingDisplayConfig(config));
|
|
93
98
|
case "embedding.provider":
|
|
@@ -115,13 +120,14 @@ export function getConfigValue(config, key) {
|
|
|
115
120
|
case "llm.apiKey":
|
|
116
121
|
return maskSecret(getLlmDisplayConfig(config).apiKey) ?? null;
|
|
117
122
|
default:
|
|
118
|
-
throw new
|
|
123
|
+
throw new UsageError(`Unknown config key: ${key}`);
|
|
119
124
|
}
|
|
120
125
|
}
|
|
121
126
|
export function setConfigValue(config, key, rawValue) {
|
|
122
127
|
switch (key) {
|
|
128
|
+
case "stashDir":
|
|
123
129
|
case "semanticSearch":
|
|
124
|
-
case "
|
|
130
|
+
case "searchPaths":
|
|
125
131
|
case "embedding":
|
|
126
132
|
case "llm":
|
|
127
133
|
return { ...config, ...parseConfigValue(key, rawValue) };
|
|
@@ -202,11 +208,13 @@ export function setConfigValue(config, key, rawValue) {
|
|
|
202
208
|
},
|
|
203
209
|
};
|
|
204
210
|
default:
|
|
205
|
-
throw new
|
|
211
|
+
throw new UsageError(`Unknown config key: ${key}`);
|
|
206
212
|
}
|
|
207
213
|
}
|
|
208
214
|
export function unsetConfigValue(config, key) {
|
|
209
215
|
switch (key) {
|
|
216
|
+
case "stashDir":
|
|
217
|
+
return { ...config, stashDir: undefined };
|
|
210
218
|
case "embedding":
|
|
211
219
|
return { ...config, embedding: undefined };
|
|
212
220
|
case "embedding.apiKey":
|
|
@@ -240,13 +248,14 @@ export function unsetConfigValue(config, key) {
|
|
|
240
248
|
return config;
|
|
241
249
|
return { ...config, llm: omitKey(config.llm, "provider") };
|
|
242
250
|
default:
|
|
243
|
-
throw new
|
|
251
|
+
throw new UsageError(`Unknown or unsupported unset key: ${key}`);
|
|
244
252
|
}
|
|
245
253
|
}
|
|
246
254
|
export function listConfig(config) {
|
|
247
255
|
return {
|
|
248
256
|
...DEFAULT_CONFIG,
|
|
249
257
|
...maskSecrets(config),
|
|
258
|
+
stashDir: config.stashDir ?? null,
|
|
250
259
|
embedding: maskSecrets(getEmbeddingDisplayConfig(config)),
|
|
251
260
|
llm: maskSecrets(getLlmDisplayConfig(config)),
|
|
252
261
|
};
|
|
@@ -265,7 +274,7 @@ export function useProvider(config, scope, providerName) {
|
|
|
265
274
|
if (scope === "embedding") {
|
|
266
275
|
const preset = EMBEDDING_PROVIDER_PRESETS[providerName];
|
|
267
276
|
if (!preset) {
|
|
268
|
-
throw new
|
|
277
|
+
throw new UsageError(`Unknown embedding provider: ${providerName}`);
|
|
269
278
|
}
|
|
270
279
|
if (!preset.config) {
|
|
271
280
|
return { ...config, embedding: undefined };
|
|
@@ -274,7 +283,7 @@ export function useProvider(config, scope, providerName) {
|
|
|
274
283
|
}
|
|
275
284
|
const preset = LLM_PROVIDER_PRESETS[providerName];
|
|
276
285
|
if (!preset) {
|
|
277
|
-
throw new
|
|
286
|
+
throw new UsageError(`Unknown llm provider: ${providerName}`);
|
|
278
287
|
}
|
|
279
288
|
if (!preset.config) {
|
|
280
289
|
return { ...config, llm: undefined };
|
|
@@ -380,68 +389,65 @@ function parseJsonObject(value, key, example) {
|
|
|
380
389
|
parsed = JSON.parse(value);
|
|
381
390
|
}
|
|
382
391
|
catch {
|
|
383
|
-
throw new
|
|
384
|
-
|
|
392
|
+
throw new UsageError(`Invalid value for ${key}: expected JSON object with endpoint and model` +
|
|
393
|
+
` (e.g. '{"endpoint":"${example.endpoint}","model":"${example.model}"}')`);
|
|
385
394
|
}
|
|
386
395
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
387
|
-
throw new
|
|
396
|
+
throw new UsageError(`Invalid value for ${key}: expected a JSON object`);
|
|
388
397
|
}
|
|
389
398
|
return parsed;
|
|
390
399
|
}
|
|
391
400
|
function asRequiredString(value, key, field) {
|
|
392
401
|
if (typeof value !== "string" || !value) {
|
|
393
|
-
throw new
|
|
402
|
+
throw new UsageError(`Invalid value for ${key}: "${field}" is a required string field`);
|
|
394
403
|
}
|
|
395
404
|
return value;
|
|
396
405
|
}
|
|
397
406
|
function requireEmbeddingConfig(config) {
|
|
398
407
|
if (!config.embedding) {
|
|
399
|
-
throw new
|
|
408
|
+
throw new ConfigError("Embedding provider is using the built-in local default. Run `akm config use embedding <provider>` first.");
|
|
400
409
|
}
|
|
401
410
|
return config.embedding;
|
|
402
411
|
}
|
|
403
412
|
function requireLlmConfig(config) {
|
|
404
413
|
if (!config.llm) {
|
|
405
|
-
throw new
|
|
414
|
+
throw new ConfigError("LLM provider is disabled. Run `akm config use llm <provider>` first.");
|
|
406
415
|
}
|
|
407
416
|
return config.llm;
|
|
408
417
|
}
|
|
409
418
|
function requireNonEmptyString(value, key) {
|
|
410
419
|
if (!value) {
|
|
411
|
-
throw new
|
|
420
|
+
throw new UsageError(`Invalid value for ${key}: expected a non-empty string`);
|
|
412
421
|
}
|
|
413
422
|
return value;
|
|
414
423
|
}
|
|
415
424
|
function parseNumber(value, key) {
|
|
416
425
|
const parsed = Number(value);
|
|
417
426
|
if (!Number.isFinite(parsed)) {
|
|
418
|
-
throw new
|
|
427
|
+
throw new UsageError(`Invalid value for ${key}: expected a number`);
|
|
419
428
|
}
|
|
420
429
|
return parsed;
|
|
421
430
|
}
|
|
422
431
|
function parsePositiveInteger(value, key) {
|
|
423
432
|
const trimmed = value.trim();
|
|
424
433
|
if (!/^[1-9]\d*$/.test(trimmed)) {
|
|
425
|
-
throw new
|
|
434
|
+
throw new UsageError(`Invalid value for ${key}: expected a positive integer`);
|
|
426
435
|
}
|
|
427
436
|
const parsed = Number(trimmed);
|
|
428
437
|
if (!Number.isFinite(parsed) || !Number.isInteger(parsed) || parsed <= 0) {
|
|
429
|
-
throw new
|
|
438
|
+
throw new UsageError(`Invalid value for ${key}: expected a positive integer`);
|
|
430
439
|
}
|
|
431
440
|
return parsed;
|
|
432
441
|
}
|
|
433
442
|
function parseUnknownNumber(value, key) {
|
|
434
443
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
435
|
-
throw new
|
|
444
|
+
throw new UsageError(`Invalid value for ${key}: expected a number`);
|
|
436
445
|
}
|
|
437
446
|
return value;
|
|
438
447
|
}
|
|
439
448
|
function parseUnknownPositiveInteger(value, key) {
|
|
440
|
-
if (typeof value !== "number" ||
|
|
441
|
-
|
|
442
|
-
!Number.isInteger(value) ||
|
|
443
|
-
value <= 0) {
|
|
444
|
-
throw new Error(`Invalid value for ${key}: expected a positive integer`);
|
|
449
|
+
if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value <= 0) {
|
|
450
|
+
throw new UsageError(`Invalid value for ${key}: expected a positive integer`);
|
|
445
451
|
}
|
|
446
452
|
return value;
|
|
447
453
|
}
|
|
@@ -1,37 +1,33 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { getConfigDir as _getConfigDir, getConfigPath as _getConfigPath } from "./paths";
|
|
3
4
|
// ── Defaults ────────────────────────────────────────────────────────────────
|
|
4
5
|
export const DEFAULT_CONFIG = {
|
|
5
6
|
semanticSearch: true,
|
|
6
|
-
|
|
7
|
+
searchPaths: [],
|
|
7
8
|
};
|
|
8
9
|
// ── Paths ───────────────────────────────────────────────────────────────────
|
|
9
|
-
export function getConfigDir(env
|
|
10
|
-
|
|
11
|
-
const appData = env.APPDATA?.trim();
|
|
12
|
-
if (appData)
|
|
13
|
-
return path.join(appData, "agentikit");
|
|
14
|
-
const userProfile = env.USERPROFILE?.trim();
|
|
15
|
-
if (!userProfile) {
|
|
16
|
-
throw new Error("Unable to determine config directory. Set APPDATA or USERPROFILE.");
|
|
17
|
-
}
|
|
18
|
-
return path.join(userProfile, "AppData", "Roaming", "agentikit");
|
|
19
|
-
}
|
|
20
|
-
const xdgConfigHome = env.XDG_CONFIG_HOME?.trim();
|
|
21
|
-
if (xdgConfigHome)
|
|
22
|
-
return path.join(xdgConfigHome, "agentikit");
|
|
23
|
-
const home = env.HOME?.trim();
|
|
24
|
-
if (!home) {
|
|
25
|
-
throw new Error("Unable to determine config directory. Set XDG_CONFIG_HOME or HOME.");
|
|
26
|
-
}
|
|
27
|
-
return path.join(home, ".config", "agentikit");
|
|
10
|
+
export function getConfigDir(env, platform) {
|
|
11
|
+
return _getConfigDir(env, platform);
|
|
28
12
|
}
|
|
29
13
|
export function getConfigPath() {
|
|
30
|
-
return
|
|
14
|
+
return _getConfigPath();
|
|
31
15
|
}
|
|
32
16
|
// ── Load / Save / Update ────────────────────────────────────────────────────
|
|
17
|
+
let cachedConfig;
|
|
33
18
|
export function loadConfig() {
|
|
34
19
|
const configPath = getConfigPath();
|
|
20
|
+
try {
|
|
21
|
+
const stat = fs.statSync(configPath);
|
|
22
|
+
if (cachedConfig && cachedConfig.path === configPath && cachedConfig.mtime === stat.mtimeMs) {
|
|
23
|
+
return cachedConfig.config;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// File doesn't exist — return defaults below
|
|
28
|
+
cachedConfig = undefined;
|
|
29
|
+
return { ...DEFAULT_CONFIG };
|
|
30
|
+
}
|
|
35
31
|
const raw = readConfigObject(configPath);
|
|
36
32
|
const config = raw ? pickKnownKeys(raw) : { ...DEFAULT_CONFIG };
|
|
37
33
|
// Inject API keys from environment variables.
|
|
@@ -47,9 +43,18 @@ export function loadConfig() {
|
|
|
47
43
|
if (envKey)
|
|
48
44
|
config.llm.apiKey = envKey;
|
|
49
45
|
}
|
|
46
|
+
// Cache the parsed config with its path and mtime for subsequent calls
|
|
47
|
+
try {
|
|
48
|
+
const stat = fs.statSync(configPath);
|
|
49
|
+
cachedConfig = { config, path: configPath, mtime: stat.mtimeMs };
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// If we can't stat (unlikely since we just read it), skip caching
|
|
53
|
+
}
|
|
50
54
|
return config;
|
|
51
55
|
}
|
|
52
56
|
export function saveConfig(config) {
|
|
57
|
+
cachedConfig = undefined;
|
|
53
58
|
const configPath = getConfigPath();
|
|
54
59
|
const dir = path.dirname(configPath);
|
|
55
60
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -63,7 +68,9 @@ export function saveConfig(config) {
|
|
|
63
68
|
try {
|
|
64
69
|
fs.unlinkSync(tmpPath);
|
|
65
70
|
}
|
|
66
|
-
catch {
|
|
71
|
+
catch {
|
|
72
|
+
/* ignore cleanup failure */
|
|
73
|
+
}
|
|
67
74
|
throw err;
|
|
68
75
|
}
|
|
69
76
|
}
|
|
@@ -93,11 +100,23 @@ export function updateConfig(partial) {
|
|
|
93
100
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
94
101
|
function pickKnownKeys(raw) {
|
|
95
102
|
const config = { ...DEFAULT_CONFIG };
|
|
103
|
+
if (typeof raw.stashDir === "string" && raw.stashDir.trim()) {
|
|
104
|
+
config.stashDir = raw.stashDir.trim();
|
|
105
|
+
}
|
|
96
106
|
if (typeof raw.semanticSearch === "boolean") {
|
|
97
107
|
config.semanticSearch = raw.semanticSearch;
|
|
98
108
|
}
|
|
109
|
+
if (Array.isArray(raw.searchPaths)) {
|
|
110
|
+
config.searchPaths = raw.searchPaths.filter((d) => typeof d === "string");
|
|
111
|
+
}
|
|
112
|
+
// Backward compat: merge legacy mountedStashDirs into searchPaths
|
|
99
113
|
if (Array.isArray(raw.mountedStashDirs)) {
|
|
100
|
-
|
|
114
|
+
const legacy = raw.mountedStashDirs.filter((d) => typeof d === "string");
|
|
115
|
+
const existing = new Set(config.searchPaths);
|
|
116
|
+
for (const d of legacy) {
|
|
117
|
+
if (!existing.has(d))
|
|
118
|
+
config.searchPaths.push(d);
|
|
119
|
+
}
|
|
101
120
|
}
|
|
102
121
|
const embedding = parseEmbeddingConfig(raw.embedding);
|
|
103
122
|
if (embedding)
|
|
@@ -115,7 +134,8 @@ function pickKnownKeys(raw) {
|
|
|
115
134
|
}
|
|
116
135
|
function readConfigObject(configPath) {
|
|
117
136
|
try {
|
|
118
|
-
const
|
|
137
|
+
const text = fs.readFileSync(configPath, "utf8");
|
|
138
|
+
const raw = JSON.parse(stripJsonComments(text));
|
|
119
139
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
120
140
|
return undefined;
|
|
121
141
|
return raw;
|
|
@@ -124,6 +144,54 @@ function readConfigObject(configPath) {
|
|
|
124
144
|
return undefined;
|
|
125
145
|
}
|
|
126
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Strip JavaScript-style comments from a JSON string (JSONC support).
|
|
149
|
+
* Handles // line comments and /* block comments while preserving
|
|
150
|
+
* comment-like sequences inside quoted strings.
|
|
151
|
+
*/
|
|
152
|
+
export function stripJsonComments(text) {
|
|
153
|
+
let result = "";
|
|
154
|
+
let i = 0;
|
|
155
|
+
let inString = false;
|
|
156
|
+
let stringChar = "";
|
|
157
|
+
while (i < text.length) {
|
|
158
|
+
if (inString) {
|
|
159
|
+
if (text[i] === "\\") {
|
|
160
|
+
result += text[i] + (text[i + 1] ?? "");
|
|
161
|
+
i += 2;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (text[i] === stringChar) {
|
|
165
|
+
inString = false;
|
|
166
|
+
}
|
|
167
|
+
result += text[i];
|
|
168
|
+
i++;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (text[i] === '"' || text[i] === "'") {
|
|
172
|
+
inString = true;
|
|
173
|
+
stringChar = text[i];
|
|
174
|
+
result += text[i];
|
|
175
|
+
i++;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (text[i] === "/" && text[i + 1] === "/") {
|
|
179
|
+
while (i < text.length && text[i] !== "\n")
|
|
180
|
+
i++;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (text[i] === "/" && text[i + 1] === "*") {
|
|
184
|
+
i += 2;
|
|
185
|
+
while (i < text.length && !(text[i] === "*" && text[i + 1] === "/"))
|
|
186
|
+
i++;
|
|
187
|
+
i += 2;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
result += text[i];
|
|
191
|
+
i++;
|
|
192
|
+
}
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
127
195
|
function parseEmbeddingConfig(value) {
|
|
128
196
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
129
197
|
return undefined;
|
|
@@ -230,5 +298,7 @@ function asNonEmptyString(value) {
|
|
|
230
298
|
return typeof value === "string" && value ? value : undefined;
|
|
231
299
|
}
|
|
232
300
|
function asRegistrySource(value) {
|
|
233
|
-
|
|
301
|
+
if (value === "npm" || value === "github" || value === "git" || value === "local")
|
|
302
|
+
return value;
|
|
303
|
+
return undefined;
|
|
234
304
|
}
|