akm-cli 0.4.0 → 0.4.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/dist/cli.js +270 -1
- package/dist/embedder.js +26 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { defineCommand, runMain } from "citty";
|
|
5
|
-
import {
|
|
5
|
+
import { resolveAssetPathFromName } from "./asset-spec";
|
|
6
|
+
import { isWithin, resolveStashDir } from "./common";
|
|
6
7
|
import { generateBashCompletions, installBashCompletions } from "./completions";
|
|
7
8
|
import { DEFAULT_CONFIG, getConfigPath, loadConfig, loadUserConfig, saveConfig } from "./config";
|
|
8
9
|
import { getConfigValue, listConfig, setConfigValue, unsetConfigValue } from "./config-cli";
|
|
@@ -28,8 +29,12 @@ import { setQuiet, warn } from "./warn";
|
|
|
28
29
|
const OUTPUT_FORMATS = ["json", "yaml", "text", "jsonl"];
|
|
29
30
|
const DETAIL_LEVELS = ["brief", "normal", "full", "summary"];
|
|
30
31
|
const NORMAL_DESCRIPTION_LIMIT = 250;
|
|
32
|
+
const MAX_CAPTURED_ASSET_SLUG_LENGTH = 64;
|
|
31
33
|
const CONTEXT_HUB_ALIAS_REF = "context-hub";
|
|
32
34
|
const CONTEXT_HUB_ALIAS_URL = "https://github.com/andrewyng/context-hub";
|
|
35
|
+
const SKILLS_SH_NAME = "skills.sh";
|
|
36
|
+
const SKILLS_SH_URL = "https://skills.sh";
|
|
37
|
+
const SKILLS_SH_PROVIDER = "skills-sh";
|
|
33
38
|
import { stringify as yamlStringify } from "yaml";
|
|
34
39
|
function parseOutputFormat(value) {
|
|
35
40
|
if (!value)
|
|
@@ -1356,6 +1361,164 @@ const feedbackCommand = defineCommand({
|
|
|
1356
1361
|
});
|
|
1357
1362
|
},
|
|
1358
1363
|
});
|
|
1364
|
+
function tryReadStdinText() {
|
|
1365
|
+
if (process.stdin.isTTY)
|
|
1366
|
+
return undefined;
|
|
1367
|
+
const input = fs.readFileSync(0, "utf8");
|
|
1368
|
+
return input.length > 0 ? input : undefined;
|
|
1369
|
+
}
|
|
1370
|
+
function normalizeMarkdownAssetName(name, fallback) {
|
|
1371
|
+
const trimmed = (name ?? fallback)
|
|
1372
|
+
.trim()
|
|
1373
|
+
.replace(/\\/g, "/")
|
|
1374
|
+
.replace(/^\/+|\/+$/g, "")
|
|
1375
|
+
.replace(/\.md$/i, "");
|
|
1376
|
+
if (!trimmed)
|
|
1377
|
+
throw new UsageError("Asset name cannot be empty.");
|
|
1378
|
+
const segments = trimmed.split("/");
|
|
1379
|
+
if (segments.some((segment) => !segment || segment === "." || segment === "..")) {
|
|
1380
|
+
throw new UsageError("Asset name must be a relative path without '.' or '..' segments.");
|
|
1381
|
+
}
|
|
1382
|
+
return trimmed;
|
|
1383
|
+
}
|
|
1384
|
+
function slugifyAssetName(value, fallbackPrefix) {
|
|
1385
|
+
const slug = value
|
|
1386
|
+
.toLowerCase()
|
|
1387
|
+
.replace(/^[#>\-\s]+/, "")
|
|
1388
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
1389
|
+
.replace(/^-+|-+$/g, "")
|
|
1390
|
+
.slice(0, MAX_CAPTURED_ASSET_SLUG_LENGTH);
|
|
1391
|
+
return slug || `${fallbackPrefix}-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
1392
|
+
}
|
|
1393
|
+
function inferAssetName(content, fallbackPrefix, preferred) {
|
|
1394
|
+
const firstNonEmptyLine = content
|
|
1395
|
+
.split(/\r?\n/)
|
|
1396
|
+
.map((line) => line.trim())
|
|
1397
|
+
.find((line) => line.length > 0);
|
|
1398
|
+
const basis = preferred?.trim() || firstNonEmptyLine || fallbackPrefix;
|
|
1399
|
+
return slugifyAssetName(basis, fallbackPrefix);
|
|
1400
|
+
}
|
|
1401
|
+
function readMemoryContent(contentArg) {
|
|
1402
|
+
const content = contentArg ?? tryReadStdinText();
|
|
1403
|
+
if (!content?.trim()) {
|
|
1404
|
+
throw new UsageError("Memory content is required. Pass quoted text or pipe markdown into stdin.");
|
|
1405
|
+
}
|
|
1406
|
+
return content;
|
|
1407
|
+
}
|
|
1408
|
+
function readKnowledgeContent(source) {
|
|
1409
|
+
if (source === "-") {
|
|
1410
|
+
const content = tryReadStdinText();
|
|
1411
|
+
if (!content?.trim()) {
|
|
1412
|
+
throw new UsageError("No stdin content received. Pipe a document into stdin or pass a file path.");
|
|
1413
|
+
}
|
|
1414
|
+
return { content };
|
|
1415
|
+
}
|
|
1416
|
+
const resolvedSource = path.resolve(source);
|
|
1417
|
+
let stat;
|
|
1418
|
+
try {
|
|
1419
|
+
stat = fs.statSync(resolvedSource);
|
|
1420
|
+
}
|
|
1421
|
+
catch {
|
|
1422
|
+
throw new UsageError(`Knowledge source not found: "${source}". Pass a readable file path or "-" for stdin.`);
|
|
1423
|
+
}
|
|
1424
|
+
if (!stat.isFile()) {
|
|
1425
|
+
throw new UsageError(`Knowledge source must be a file: "${source}".`);
|
|
1426
|
+
}
|
|
1427
|
+
return {
|
|
1428
|
+
content: fs.readFileSync(resolvedSource, "utf8"),
|
|
1429
|
+
preferredName: path.basename(resolvedSource, path.extname(resolvedSource)),
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
function writeMarkdownAsset(options) {
|
|
1433
|
+
const stashDir = resolveStashDir();
|
|
1434
|
+
const typeRoot = path.join(stashDir, options.type === "knowledge" ? "knowledge" : "memories");
|
|
1435
|
+
fs.mkdirSync(typeRoot, { recursive: true });
|
|
1436
|
+
const normalizedName = normalizeMarkdownAssetName(options.name, inferAssetName(options.content, options.fallbackPrefix, options.preferredName));
|
|
1437
|
+
const assetPath = resolveAssetPathFromName(options.type, typeRoot, normalizedName);
|
|
1438
|
+
if (!isWithin(assetPath, typeRoot)) {
|
|
1439
|
+
throw new UsageError(`Resolved ${options.type} path escapes the stash: "${normalizedName}"`);
|
|
1440
|
+
}
|
|
1441
|
+
if (fs.existsSync(assetPath) && !options.force) {
|
|
1442
|
+
throw new UsageError(`${options.type === "knowledge" ? "Knowledge" : "Memory"} "${normalizedName}" already exists. Re-run with --force to overwrite it.`);
|
|
1443
|
+
}
|
|
1444
|
+
fs.mkdirSync(path.dirname(assetPath), { recursive: true });
|
|
1445
|
+
fs.writeFileSync(assetPath, options.content.endsWith("\n") ? options.content : `${options.content}\n`, "utf8");
|
|
1446
|
+
return {
|
|
1447
|
+
ref: `${options.type}:${normalizedName}`,
|
|
1448
|
+
path: assetPath,
|
|
1449
|
+
stashDir,
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
const rememberCommand = defineCommand({
|
|
1453
|
+
meta: {
|
|
1454
|
+
name: "remember",
|
|
1455
|
+
description: "Record a memory in the default stash",
|
|
1456
|
+
},
|
|
1457
|
+
args: {
|
|
1458
|
+
content: {
|
|
1459
|
+
type: "positional",
|
|
1460
|
+
description: "Memory content. Omit to read markdown from stdin.",
|
|
1461
|
+
required: false,
|
|
1462
|
+
},
|
|
1463
|
+
name: {
|
|
1464
|
+
type: "string",
|
|
1465
|
+
description: "Memory name (defaults to a slug from the content)",
|
|
1466
|
+
},
|
|
1467
|
+
force: {
|
|
1468
|
+
type: "boolean",
|
|
1469
|
+
description: "Overwrite an existing memory with the same name",
|
|
1470
|
+
default: false,
|
|
1471
|
+
},
|
|
1472
|
+
},
|
|
1473
|
+
run({ args }) {
|
|
1474
|
+
return runWithJsonErrors(() => {
|
|
1475
|
+
const result = writeMarkdownAsset({
|
|
1476
|
+
type: "memory",
|
|
1477
|
+
content: readMemoryContent(args.content),
|
|
1478
|
+
name: args.name,
|
|
1479
|
+
fallbackPrefix: "memory",
|
|
1480
|
+
force: args.force,
|
|
1481
|
+
});
|
|
1482
|
+
output("remember", { ok: true, ...result });
|
|
1483
|
+
});
|
|
1484
|
+
},
|
|
1485
|
+
});
|
|
1486
|
+
const importKnowledgeCommand = defineCommand({
|
|
1487
|
+
meta: {
|
|
1488
|
+
name: "import",
|
|
1489
|
+
description: "Import a knowledge document into the default stash",
|
|
1490
|
+
},
|
|
1491
|
+
args: {
|
|
1492
|
+
source: {
|
|
1493
|
+
type: "positional",
|
|
1494
|
+
description: 'Source file path, or "-" to read from stdin',
|
|
1495
|
+
required: true,
|
|
1496
|
+
},
|
|
1497
|
+
name: {
|
|
1498
|
+
type: "string",
|
|
1499
|
+
description: "Knowledge name (defaults to the source filename or content slug)",
|
|
1500
|
+
},
|
|
1501
|
+
force: {
|
|
1502
|
+
type: "boolean",
|
|
1503
|
+
description: "Overwrite an existing knowledge document with the same name",
|
|
1504
|
+
default: false,
|
|
1505
|
+
},
|
|
1506
|
+
},
|
|
1507
|
+
run({ args }) {
|
|
1508
|
+
return runWithJsonErrors(() => {
|
|
1509
|
+
const { content, preferredName } = readKnowledgeContent(args.source);
|
|
1510
|
+
const result = writeMarkdownAsset({
|
|
1511
|
+
type: "knowledge",
|
|
1512
|
+
content,
|
|
1513
|
+
name: args.name,
|
|
1514
|
+
fallbackPrefix: "knowledge",
|
|
1515
|
+
preferredName,
|
|
1516
|
+
force: args.force,
|
|
1517
|
+
});
|
|
1518
|
+
output("import", { ok: true, source: args.source, ...result });
|
|
1519
|
+
});
|
|
1520
|
+
},
|
|
1521
|
+
});
|
|
1359
1522
|
const hintsCommand = defineCommand({
|
|
1360
1523
|
meta: {
|
|
1361
1524
|
name: "hints",
|
|
@@ -1401,6 +1564,83 @@ const completionsCommand = defineCommand({
|
|
|
1401
1564
|
}
|
|
1402
1565
|
},
|
|
1403
1566
|
});
|
|
1567
|
+
function normalizeToggleTarget(target) {
|
|
1568
|
+
const normalized = target.trim().toLowerCase();
|
|
1569
|
+
if (normalized === "skills.sh" || normalized === "skills-sh")
|
|
1570
|
+
return "skills.sh";
|
|
1571
|
+
if (normalized === "context-hub")
|
|
1572
|
+
return "context-hub";
|
|
1573
|
+
throw new UsageError(`Unsupported target "${target}". Supported targets: skills.sh, context-hub`);
|
|
1574
|
+
}
|
|
1575
|
+
function toggleSkillsShRegistry(enabled) {
|
|
1576
|
+
const config = loadUserConfig();
|
|
1577
|
+
const registries = (config.registries ?? DEFAULT_CONFIG.registries ?? []).map((registry) => ({ ...registry }));
|
|
1578
|
+
const idx = registries.findIndex((registry) => registry.provider === SKILLS_SH_PROVIDER || registry.name === SKILLS_SH_NAME || registry.url === SKILLS_SH_URL);
|
|
1579
|
+
if (idx >= 0) {
|
|
1580
|
+
const existing = registries[idx];
|
|
1581
|
+
const wasEnabled = existing.enabled !== false;
|
|
1582
|
+
existing.enabled = enabled;
|
|
1583
|
+
saveConfig({ ...config, registries });
|
|
1584
|
+
return { changed: wasEnabled !== enabled, component: SKILLS_SH_NAME, enabled };
|
|
1585
|
+
}
|
|
1586
|
+
if (!enabled) {
|
|
1587
|
+
// Materialize the skills.sh registry explicitly if absent.
|
|
1588
|
+
registries.push({ url: SKILLS_SH_URL, name: SKILLS_SH_NAME, provider: SKILLS_SH_PROVIDER, enabled: false });
|
|
1589
|
+
saveConfig({ ...config, registries });
|
|
1590
|
+
return { changed: true, component: SKILLS_SH_NAME, enabled: false };
|
|
1591
|
+
}
|
|
1592
|
+
registries.push({ url: SKILLS_SH_URL, name: SKILLS_SH_NAME, provider: SKILLS_SH_PROVIDER, enabled: true });
|
|
1593
|
+
saveConfig({ ...config, registries });
|
|
1594
|
+
return { changed: true, component: SKILLS_SH_NAME, enabled: true };
|
|
1595
|
+
}
|
|
1596
|
+
function toggleContextHubStash(enabled) {
|
|
1597
|
+
const config = loadUserConfig();
|
|
1598
|
+
const stashes = [...(config.stashes ?? [])];
|
|
1599
|
+
const idx = stashes.findIndex((stash) => stash.name === CONTEXT_HUB_ALIAS_REF || stash.url === CONTEXT_HUB_ALIAS_URL);
|
|
1600
|
+
if (idx >= 0) {
|
|
1601
|
+
const existing = stashes[idx];
|
|
1602
|
+
const wasEnabled = existing.enabled !== false;
|
|
1603
|
+
existing.enabled = enabled;
|
|
1604
|
+
saveConfig({ ...config, stashes });
|
|
1605
|
+
return { changed: wasEnabled !== enabled, component: CONTEXT_HUB_ALIAS_REF, enabled };
|
|
1606
|
+
}
|
|
1607
|
+
if (!enabled) {
|
|
1608
|
+
return { changed: false, component: CONTEXT_HUB_ALIAS_REF, enabled: false };
|
|
1609
|
+
}
|
|
1610
|
+
stashes.push({ type: "git", url: CONTEXT_HUB_ALIAS_URL, name: CONTEXT_HUB_ALIAS_REF, enabled: true });
|
|
1611
|
+
saveConfig({ ...config, stashes });
|
|
1612
|
+
return { changed: true, component: CONTEXT_HUB_ALIAS_REF, enabled: true };
|
|
1613
|
+
}
|
|
1614
|
+
function toggleComponent(targetRaw, enabled) {
|
|
1615
|
+
const target = normalizeToggleTarget(targetRaw);
|
|
1616
|
+
if (target === "skills.sh")
|
|
1617
|
+
return toggleSkillsShRegistry(enabled);
|
|
1618
|
+
return toggleContextHubStash(enabled);
|
|
1619
|
+
}
|
|
1620
|
+
const enableCommand = defineCommand({
|
|
1621
|
+
meta: { name: "enable", description: "Enable an optional component (skills.sh or context-hub)" },
|
|
1622
|
+
args: {
|
|
1623
|
+
target: { type: "positional", description: "Component to enable (skills.sh|context-hub)", required: true },
|
|
1624
|
+
},
|
|
1625
|
+
run({ args }) {
|
|
1626
|
+
return runWithJsonErrors(() => {
|
|
1627
|
+
const result = toggleComponent(args.target, true);
|
|
1628
|
+
output("enable", result);
|
|
1629
|
+
});
|
|
1630
|
+
},
|
|
1631
|
+
});
|
|
1632
|
+
const disableCommand = defineCommand({
|
|
1633
|
+
meta: { name: "disable", description: "Disable an optional component (skills.sh or context-hub)" },
|
|
1634
|
+
args: {
|
|
1635
|
+
target: { type: "positional", description: "Component to disable (skills.sh|context-hub)", required: true },
|
|
1636
|
+
},
|
|
1637
|
+
run({ args }) {
|
|
1638
|
+
return runWithJsonErrors(() => {
|
|
1639
|
+
const result = toggleComponent(args.target, false);
|
|
1640
|
+
output("disable", result);
|
|
1641
|
+
});
|
|
1642
|
+
},
|
|
1643
|
+
});
|
|
1404
1644
|
const main = defineCommand({
|
|
1405
1645
|
meta: {
|
|
1406
1646
|
name: "akm",
|
|
@@ -1425,9 +1665,13 @@ const main = defineCommand({
|
|
|
1425
1665
|
search: searchCommand,
|
|
1426
1666
|
curate: curateCommand,
|
|
1427
1667
|
show: showCommand,
|
|
1668
|
+
remember: rememberCommand,
|
|
1669
|
+
import: importKnowledgeCommand,
|
|
1428
1670
|
clone: cloneCommand,
|
|
1429
1671
|
registry: registryCommand,
|
|
1430
1672
|
config: configCommand,
|
|
1673
|
+
enable: enableCommand,
|
|
1674
|
+
disable: disableCommand,
|
|
1431
1675
|
feedback: feedbackCommand,
|
|
1432
1676
|
hints: hintsCommand,
|
|
1433
1677
|
completions: completionsCommand,
|
|
@@ -1592,6 +1836,9 @@ akm curate "<task>" # Curate the best matches for a tas
|
|
|
1592
1836
|
akm search "<query>" --type skill # Filter by type
|
|
1593
1837
|
akm search "<query>" --source both # Also search registries
|
|
1594
1838
|
akm show <ref> # View asset details
|
|
1839
|
+
akm remember "Deployment needs VPN access" # Record a memory in your stash
|
|
1840
|
+
akm import ./notes/release-checklist.md # Import a knowledge doc into your stash
|
|
1841
|
+
akm feedback <ref> --positive|--negative # Record whether an asset helped
|
|
1595
1842
|
akm add <ref> # Add a source (npm, GitHub, git, local dir)
|
|
1596
1843
|
akm clone <ref> # Copy an asset to the working stash (optional --dest arg to clone to specific location)
|
|
1597
1844
|
akm registry search "<query>" # Search all registries
|
|
@@ -1606,6 +1853,10 @@ akm registry search "<query>" # Search all registries
|
|
|
1606
1853
|
| command | A prompt template with placeholders to fill in |
|
|
1607
1854
|
| agent | A system prompt with model and tool hints |
|
|
1608
1855
|
| knowledge | A reference doc (use \`toc\` or \`section "..."\` to navigate) |
|
|
1856
|
+
| memory | Recalled context (read the content for background information) |
|
|
1857
|
+
|
|
1858
|
+
When an asset meaningfully helps or fails, record that with \`akm feedback\` so
|
|
1859
|
+
future search ranking can learn from real usage.
|
|
1609
1860
|
|
|
1610
1861
|
Run \`akm -h\` for the full command reference.
|
|
1611
1862
|
`;
|
|
@@ -1668,6 +1919,20 @@ akm show knowledge:my-doc # Show content (local or remote)
|
|
|
1668
1919
|
| knowledge | \`content\` (with view modes: \`full\`, \`toc\`, \`frontmatter\`, \`section\`, \`lines\`) |
|
|
1669
1920
|
| memory | \`content\` (recalled context) |
|
|
1670
1921
|
|
|
1922
|
+
## Capture Knowledge While You Work
|
|
1923
|
+
|
|
1924
|
+
\`\`\`sh
|
|
1925
|
+
akm remember "Deployment needs VPN access" # Record a memory in your stash
|
|
1926
|
+
akm remember --name release-retro < notes.md # Save multiline memory from stdin
|
|
1927
|
+
akm import ./docs/auth-flow.md # Import a file as knowledge
|
|
1928
|
+
akm import - --name scratch-notes < notes.md # Import stdin as a knowledge doc
|
|
1929
|
+
akm feedback skill:code-review --positive # Record that an asset helped
|
|
1930
|
+
akm feedback agent:reviewer --negative # Record that an asset missed the mark
|
|
1931
|
+
\`\`\`
|
|
1932
|
+
|
|
1933
|
+
Use \`akm feedback\` whenever an asset materially helps or fails so future search
|
|
1934
|
+
ranking can learn from actual usage.
|
|
1935
|
+
|
|
1671
1936
|
## Add & Manage Sources
|
|
1672
1937
|
|
|
1673
1938
|
\`\`\`sh
|
|
@@ -1675,6 +1940,10 @@ akm add <ref> # Add a source
|
|
|
1675
1940
|
akm add @scope/kit # From npm (managed)
|
|
1676
1941
|
akm add owner/repo # From GitHub (managed)
|
|
1677
1942
|
akm add ./path/to/local/kit # Local directory
|
|
1943
|
+
akm enable skills.sh # Enable the skills.sh registry
|
|
1944
|
+
akm disable skills.sh # Disable the skills.sh registry
|
|
1945
|
+
akm enable context-hub # Add/enable the context-hub source
|
|
1946
|
+
akm disable context-hub # Disable the context-hub source
|
|
1678
1947
|
akm list # List all sources
|
|
1679
1948
|
akm list --kind managed # List managed sources only
|
|
1680
1949
|
akm remove <target> # Remove by id, ref, path, or name
|
package/dist/embedder.js
CHANGED
|
@@ -102,6 +102,28 @@ function l2Normalize(vec) {
|
|
|
102
102
|
return vec.map((v) => v / norm);
|
|
103
103
|
}
|
|
104
104
|
// ── OpenAI-compatible remote embedder ───────────────────────────────────────
|
|
105
|
+
function normalizeEmbeddingEndpoint(endpoint) {
|
|
106
|
+
let parsed;
|
|
107
|
+
try {
|
|
108
|
+
parsed = new URL(endpoint);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return endpoint;
|
|
112
|
+
}
|
|
113
|
+
const normalizedPath = parsed.pathname.replace(/\/+$/, "");
|
|
114
|
+
if (normalizedPath.endsWith("/embeddings")) {
|
|
115
|
+
return parsed.toString();
|
|
116
|
+
}
|
|
117
|
+
parsed.pathname = normalizedPath ? `${normalizedPath}/embeddings` : "/embeddings";
|
|
118
|
+
return parsed.toString();
|
|
119
|
+
}
|
|
120
|
+
function embeddingEndpointPathHint(endpoint) {
|
|
121
|
+
const normalizedEndpoint = normalizeEmbeddingEndpoint(endpoint);
|
|
122
|
+
if (normalizedEndpoint !== endpoint) {
|
|
123
|
+
return ` Check that your endpoint includes the full embeddings path (for example "${normalizedEndpoint}", not just "${endpoint}").`;
|
|
124
|
+
}
|
|
125
|
+
return "";
|
|
126
|
+
}
|
|
105
127
|
async function embedRemote(text, config) {
|
|
106
128
|
const headers = { "Content-Type": "application/json" };
|
|
107
129
|
if (config.apiKey) {
|
|
@@ -114,7 +136,7 @@ async function embedRemote(text, config) {
|
|
|
114
136
|
if (config.dimension) {
|
|
115
137
|
body.dimensions = config.dimension;
|
|
116
138
|
}
|
|
117
|
-
const response = await fetchWithTimeout(config.endpoint, {
|
|
139
|
+
const response = await fetchWithTimeout(normalizeEmbeddingEndpoint(config.endpoint), {
|
|
118
140
|
method: "POST",
|
|
119
141
|
headers,
|
|
120
142
|
body: JSON.stringify(body),
|
|
@@ -125,7 +147,7 @@ async function embedRemote(text, config) {
|
|
|
125
147
|
}
|
|
126
148
|
const json = (await response.json());
|
|
127
149
|
if (!json.data?.[0]?.embedding) {
|
|
128
|
-
throw new Error(
|
|
150
|
+
throw new Error(`Unexpected embedding response format: missing data[0].embedding.${embeddingEndpointPathHint(config.endpoint)}`);
|
|
129
151
|
}
|
|
130
152
|
return l2Normalize(json.data[0].embedding);
|
|
131
153
|
}
|
|
@@ -228,7 +250,7 @@ async function embedRemoteBatch(texts, config) {
|
|
|
228
250
|
if (config.dimension) {
|
|
229
251
|
body.dimensions = config.dimension;
|
|
230
252
|
}
|
|
231
|
-
const response = await fetchWithTimeout(config.endpoint, {
|
|
253
|
+
const response = await fetchWithTimeout(normalizeEmbeddingEndpoint(config.endpoint), {
|
|
232
254
|
method: "POST",
|
|
233
255
|
headers,
|
|
234
256
|
body: JSON.stringify(body),
|
|
@@ -239,7 +261,7 @@ async function embedRemoteBatch(texts, config) {
|
|
|
239
261
|
}
|
|
240
262
|
const json = (await response.json());
|
|
241
263
|
if (!json.data || json.data.length !== batch.length) {
|
|
242
|
-
throw new Error(`Unexpected embedding batch response: expected ${batch.length} embeddings, got ${json.data?.length ?? 0}`);
|
|
264
|
+
throw new Error(`Unexpected embedding batch response: expected ${batch.length} embeddings, got ${json.data?.length ?? 0}.${embeddingEndpointPathHint(config.endpoint)}`);
|
|
243
265
|
}
|
|
244
266
|
// Sort by index to guarantee correct order (OpenAI API doesn't guarantee order)
|
|
245
267
|
const sorted = [...json.data].sort((a, b) => a.index - b.index);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akm-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "akm (Agent Kit Manager) — A package manager for AI agent skills, commands, tools, and knowledge. Works with Claude Code, OpenCode, Cursor, and any AI coding assistant.",
|
|
6
6
|
"keywords": [
|