akm-cli 0.7.2 → 0.7.3

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.
@@ -5,6 +5,8 @@
5
5
  * registry selection, stash sources, and agent platform discovery.
6
6
  * Collects all choices and writes config once at the end.
7
7
  */
8
+ import fs from "node:fs";
9
+ import os from "node:os";
8
10
  import path from "node:path";
9
11
  import * as p from "@clack/prompts";
10
12
  import { akmInit } from "../commands/init";
@@ -90,6 +92,114 @@ async function promptOrBack(fn) {
90
92
  return null;
91
93
  return result;
92
94
  }
95
+ function configuredSourceKey(source) {
96
+ return `${source.type}:${source.path ?? source.url ?? source.name ?? "unknown"}`;
97
+ }
98
+ function describeConfiguredSource(source) {
99
+ const target = source.path ?? source.url ?? "(unknown target)";
100
+ const typeLabel = source.type === "git" ? "Git" : source.type === "filesystem" ? "Filesystem" : source.type;
101
+ return {
102
+ value: configuredSourceKey(source),
103
+ label: source.name ?? target,
104
+ hint: `${typeLabel}: ${target}`,
105
+ };
106
+ }
107
+ function renderConfiguredSourceList(sources) {
108
+ return sources
109
+ .map((source) => {
110
+ const described = describeConfiguredSource(source);
111
+ return `- ${described.label} (${described.hint})`;
112
+ })
113
+ .join("\n");
114
+ }
115
+ function renderInstalledSourceList(installed) {
116
+ return installed.map((entry) => `- ${entry.id} (${entry.source})`).join("\n");
117
+ }
118
+ function cloneLlmConfig(llm) {
119
+ if (!llm)
120
+ return undefined;
121
+ return {
122
+ ...llm,
123
+ ...(llm.capabilities ? { capabilities: { ...llm.capabilities } } : {}),
124
+ ...(llm.features ? { features: { ...llm.features } } : {}),
125
+ ...(llm.extraParams ? { extraParams: { ...llm.extraParams } } : {}),
126
+ };
127
+ }
128
+ async function stepAdditionalSources(currentSources) {
129
+ const sources = [...currentSources];
130
+ let addMore = true;
131
+ while (addMore) {
132
+ const action = await prompt(() => p.select({
133
+ message: "Add another stash source?",
134
+ options: [
135
+ { value: "done", label: "Done — no more sources" },
136
+ { value: "github-repo", label: "GitHub repository", hint: "custom URL" },
137
+ { value: "filesystem", label: "Filesystem path", hint: "local directory" },
138
+ ],
139
+ initialValue: "done",
140
+ }));
141
+ if (action === "done") {
142
+ addMore = false;
143
+ break;
144
+ }
145
+ if (action === "github-repo") {
146
+ const url = await promptOrBack(() => p.text({
147
+ message: "Enter the GitHub repository URL:",
148
+ placeholder: "https://github.com/owner/repo",
149
+ validate: (v) => {
150
+ if (!v?.trim())
151
+ return "URL cannot be empty";
152
+ },
153
+ }));
154
+ if (url === null)
155
+ continue;
156
+ const name = await promptOrBack(() => p.text({
157
+ message: "Give this stash a name (optional):",
158
+ placeholder: "my-repo",
159
+ }));
160
+ if (name === null)
161
+ continue;
162
+ const entry = { type: "git", url: url.trim() };
163
+ if (name.trim())
164
+ entry.name = name.trim();
165
+ if (!sources.some((s) => s.url === entry.url)) {
166
+ sources.push(entry);
167
+ }
168
+ else {
169
+ p.log.warn("This URL is already configured.");
170
+ }
171
+ }
172
+ if (action === "filesystem") {
173
+ const fsPath = await promptOrBack(() => p.text({
174
+ message: "Enter the directory path:",
175
+ placeholder: "/path/to/stash",
176
+ validate: (v) => {
177
+ if (!v?.trim())
178
+ return "Path cannot be empty";
179
+ },
180
+ }));
181
+ if (fsPath === null)
182
+ continue;
183
+ const resolved = fsPath.trim();
184
+ const name = await promptOrBack(() => p.text({
185
+ message: "Give this stash a name (optional):",
186
+ placeholder: "my-stash",
187
+ }));
188
+ if (name === null)
189
+ continue;
190
+ const entry = { type: "filesystem", path: resolved };
191
+ if (name.trim())
192
+ entry.name = name.trim();
193
+ if (!sources.some((s) => s.path === entry.path)) {
194
+ sources.push(entry);
195
+ }
196
+ else {
197
+ p.log.warn("This path is already configured.");
198
+ }
199
+ }
200
+ }
201
+ return sources;
202
+ }
93
203
  /**
94
204
  * Quick connectivity check. Returns true if we can reach a public
95
205
  * endpoint within 3 seconds, false otherwise. Used to skip network-
@@ -196,8 +306,10 @@ async function prepareSemanticSearchAssets(config) {
196
306
  }
197
307
  spin.stop(remote ? "Remote embedding endpoint is ready." : "Local embedding model downloaded and ready.");
198
308
  let db;
309
+ let probeDir;
199
310
  try {
200
- db = openDatabase();
311
+ probeDir = fs.mkdtempSync(path.join(os.tmpdir(), "akm-setup-vec-probe-"));
312
+ db = openDatabase(path.join(probeDir, "probe.db"), config.embedding?.dimension ? { embeddingDim: config.embedding.dimension } : undefined);
201
313
  if (isVecAvailable(db)) {
202
314
  p.log.info("sqlite-vec is available for fast vector search.");
203
315
  }
@@ -213,6 +325,14 @@ async function prepareSemanticSearchAssets(config) {
213
325
  finally {
214
326
  if (db)
215
327
  closeDatabase(db);
328
+ if (probeDir) {
329
+ try {
330
+ fs.rmSync(probeDir, { recursive: true, force: true });
331
+ }
332
+ catch {
333
+ /* ignore cleanup failure */
334
+ }
335
+ }
216
336
  }
217
337
  return { ok: true };
218
338
  }
@@ -382,7 +502,7 @@ export async function stepLlm(current, ollamaEndpoint, ollamaChatModels) {
382
502
  initialValue,
383
503
  }));
384
504
  if (choice === "keep")
385
- return current.llm;
505
+ return cloneLlmConfig(current.llm);
386
506
  if (choice === "none")
387
507
  return undefined;
388
508
  let llm;
@@ -514,10 +634,26 @@ export async function stepRegistries(current) {
514
634
  /**
515
635
  * @internal Exported for testing only.
516
636
  */
517
- export async function stepAddSources(current) {
518
- const sources = [...(current.sources ?? current.stashes ?? [])];
519
- if (sources.length > 0) {
520
- p.log.info(`You have ${sources.length} existing stash source(s).`);
637
+ export async function stepAddSources(current, options) {
638
+ const existingSources = [...(current.sources ?? current.stashes ?? [])];
639
+ const sources = [];
640
+ if (existingSources.length > 0) {
641
+ p.note(renderConfiguredSourceList(existingSources), "Configured stash sources");
642
+ const options = existingSources.map(describeConfiguredSource);
643
+ const selected = await prompt(() => p.multiselect({
644
+ message: "Configured stash sources — uncheck any you want to disable:",
645
+ options,
646
+ initialValues: options.map((option) => option.value),
647
+ required: false,
648
+ }));
649
+ for (const source of existingSources) {
650
+ if (selected.includes(configuredSourceKey(source))) {
651
+ sources.push(source);
652
+ }
653
+ }
654
+ }
655
+ if ((current.installed?.length ?? 0) > 0) {
656
+ p.note(renderInstalledSourceList(current.installed ?? []), "Installed managed stashes (preserved)");
521
657
  }
522
658
  // ── Recommended GitHub repos ───────────────────────────────────────────
523
659
  // Skip the prompt entirely when there are no recommendations to show.
@@ -558,78 +694,10 @@ export async function stepAddSources(current) {
558
694
  }
559
695
  }
560
696
  }
561
- // ── Additional stash sources loop ──────────────────────────────────────
562
- let addMore = true;
563
- while (addMore) {
564
- const action = await prompt(() => p.select({
565
- message: "Add another stash source?",
566
- options: [
567
- { value: "github-repo", label: "GitHub repository", hint: "custom URL" },
568
- { value: "filesystem", label: "Filesystem path", hint: "local directory" },
569
- { value: "done", label: "Done — no more sources" },
570
- ],
571
- }));
572
- if (action === "done") {
573
- addMore = false;
574
- break;
575
- }
576
- if (action === "github-repo") {
577
- const url = await promptOrBack(() => p.text({
578
- message: "Enter the GitHub repository URL:",
579
- placeholder: "https://github.com/owner/repo",
580
- validate: (v) => {
581
- if (!v?.trim())
582
- return "URL cannot be empty";
583
- },
584
- }));
585
- if (url === null)
586
- continue;
587
- const name = await promptOrBack(() => p.text({
588
- message: "Give this stash a name (optional):",
589
- placeholder: "my-repo",
590
- }));
591
- if (name === null)
592
- continue;
593
- const entry = { type: "git", url: url.trim() };
594
- if (name.trim())
595
- entry.name = name.trim();
596
- if (!sources.some((s) => s.url === entry.url)) {
597
- sources.push(entry);
598
- }
599
- else {
600
- p.log.warn("This URL is already configured.");
601
- }
602
- }
603
- if (action === "filesystem") {
604
- const fsPath = await promptOrBack(() => p.text({
605
- message: "Enter the directory path:",
606
- placeholder: "/path/to/stash",
607
- validate: (v) => {
608
- if (!v?.trim())
609
- return "Path cannot be empty";
610
- },
611
- }));
612
- if (fsPath === null)
613
- continue;
614
- const resolved = fsPath.trim();
615
- const name = await promptOrBack(() => p.text({
616
- message: "Give this stash a name (optional):",
617
- placeholder: "my-stash",
618
- }));
619
- if (name === null)
620
- continue;
621
- const entry = { type: "filesystem", path: resolved };
622
- if (name.trim())
623
- entry.name = name.trim();
624
- if (!sources.some((s) => s.path === entry.path)) {
625
- sources.push(entry);
626
- }
627
- else {
628
- p.log.warn("This path is already configured.");
629
- }
630
- }
697
+ if (options?.promptForAdditional === false) {
698
+ return sources;
631
699
  }
632
- return sources;
700
+ return stepAdditionalSources(sources);
633
701
  }
634
702
  async function stepAgentPlatforms(current) {
635
703
  const platforms = detectAgentPlatforms();
@@ -637,7 +705,7 @@ async function stepAgentPlatforms(current) {
637
705
  p.log.info("No agent platform configurations detected.");
638
706
  return [];
639
707
  }
640
- const existingPaths = new Set((current.stashes ?? []).map((s) => s.path));
708
+ const existingPaths = new Set((current.sources ?? current.stashes ?? []).map((s) => s.path));
641
709
  // Filter out platforms already configured
642
710
  const newPlatforms = platforms.filter((pl) => !existingPaths.has(pl.path));
643
711
  if (newPlatforms.length === 0) {
@@ -666,6 +734,60 @@ async function stepAgentPlatforms(current) {
666
734
  }
667
735
  return entries;
668
736
  }
737
+ export async function stepAgentSelection(current, detections) {
738
+ const available = detections.filter((d) => d.available);
739
+ if (available.length === 0) {
740
+ return current.agent;
741
+ }
742
+ const initialValue = pickDefaultAgentProfile(detections, current.agent?.default) ?? available[0]?.name;
743
+ const selectedDefault = await prompt(() => p.select({
744
+ message: "Which detected agent CLI should be the default?",
745
+ options: [
746
+ ...available.map((d) => ({
747
+ value: d.name,
748
+ label: d.name,
749
+ hint: d.resolvedPath ?? d.bin,
750
+ })),
751
+ { value: "disabled", label: "Disabled", hint: "do not configure a default agent CLI" },
752
+ ],
753
+ initialValue,
754
+ }));
755
+ if (selectedDefault === "disabled") {
756
+ if (!current.agent?.profiles && !current.agent?.timeoutMs) {
757
+ return undefined;
758
+ }
759
+ return {
760
+ ...(current.agent ?? {}),
761
+ default: undefined,
762
+ };
763
+ }
764
+ return {
765
+ ...(current.agent ?? {}),
766
+ default: selectedDefault,
767
+ };
768
+ }
769
+ export async function stepOutputConfig(current) {
770
+ const defaultOutput = current.output ?? DEFAULT_CONFIG.output ?? { format: "json", detail: "brief" };
771
+ const format = await prompt(() => p.select({
772
+ message: "Default output format?",
773
+ options: [
774
+ { value: "json", label: "json", hint: "structured default" },
775
+ { value: "text", label: "text", hint: "human-readable CLI output" },
776
+ { value: "yaml", label: "yaml", hint: "structured text" },
777
+ ],
778
+ initialValue: defaultOutput.format ?? "json",
779
+ }));
780
+ const detail = await prompt(() => p.select({
781
+ message: "Default output detail level?",
782
+ options: [
783
+ { value: "brief", label: "brief", hint: "compact summaries" },
784
+ { value: "normal", label: "normal", hint: "balanced detail" },
785
+ { value: "full", label: "full", hint: "max available detail" },
786
+ ],
787
+ initialValue: defaultOutput.detail ?? "brief",
788
+ }));
789
+ return { format: format, detail: detail };
790
+ }
669
791
  /**
670
792
  * Detect installed agent CLIs and produce an updated `agent` config block
671
793
  * with a sensible `default` (the first detected profile that the user has
@@ -761,20 +883,20 @@ export function buildSetupSteps(options) {
761
883
  id: "stash-sources",
762
884
  label: "Stash Sources",
763
885
  async run(ctx) {
764
- const stashes = await stepAddSources(ctx.config);
765
- const platforms = await stepAgentPlatforms(ctx.config);
886
+ const stashes = await stepAddSources(ctx.config, { promptForAdditional: false });
887
+ const platforms = await stepAgentPlatforms({ ...ctx.config, sources: stashes });
766
888
  const merged = [...stashes];
767
889
  for (const ps of platforms) {
768
890
  if (!merged.some((s) => s.path === ps.path))
769
891
  merged.push(ps);
770
892
  }
771
- ctx.apply({ sources: merged.length > 0 ? merged : undefined });
893
+ const withAdditional = await stepAdditionalSources(merged);
894
+ ctx.apply({ sources: withAdditional.length > 0 ? withAdditional : undefined });
772
895
  },
773
896
  },
774
897
  {
775
898
  id: "agent-cli",
776
899
  label: "Agent CLI",
777
- nonInteractive: true,
778
900
  async run(ctx) {
779
901
  const result = stepAgentCliDetection(ctx.config);
780
902
  const detected = result.detections.filter((d) => d.available);
@@ -785,7 +907,16 @@ export function buildSetupSteps(options) {
785
907
  else {
786
908
  p.log.info("No agent CLIs detected on PATH. Agent commands will be disabled until one is installed and `akm setup` is re-run.");
787
909
  }
788
- ctx.apply({ agent: result.agent });
910
+ const agent = await stepAgentSelection({ ...ctx.config, agent: result.agent }, result.detections);
911
+ ctx.apply({ agent });
912
+ },
913
+ },
914
+ {
915
+ id: "output",
916
+ label: "Output Defaults",
917
+ async run(ctx) {
918
+ const output = await stepOutputConfig(ctx.config);
919
+ ctx.apply({ output });
789
920
  },
790
921
  },
791
922
  ];
@@ -821,7 +952,6 @@ export async function runSetupWizard() {
821
952
  ...ctx.config,
822
953
  // Preserve fields the steps don't manage explicitly.
823
954
  installed: current.installed,
824
- output: current.output,
825
955
  };
826
956
  const semanticSearchMode = outcome.semantic;
827
957
  const stashDir = newConfig.stashDir ?? current.stashDir ?? getDefaultStashDir();
@@ -838,6 +968,8 @@ export async function runSetupWizard() {
838
968
  `Semantic search: ${semanticSearchMode.mode}`,
839
969
  `Registries: ${effectiveRegistries.filter((r) => r.enabled !== false).length} enabled`,
840
970
  `Stash sources: ${allStashes.length}`,
971
+ `Agent default: ${newConfig.agent?.default ?? "disabled"}`,
972
+ `Output: ${newConfig.output?.format ?? "json"} / ${newConfig.output?.detail ?? "brief"}`,
841
973
  ].join("\n"), "Configuration Summary");
842
974
  const shouldSave = await prompt(() => p.confirm({
843
975
  message: "Save this configuration?",
@@ -2,6 +2,7 @@ import { spawnSync } from "node:child_process";
2
2
  import { createHash, randomBytes } from "node:crypto";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
+ import { TYPE_DIRS } from "../../core/asset-spec";
5
6
  import { resolveStashDir } from "../../core/common";
6
7
  import { loadConfig } from "../../core/config";
7
8
  import { ConfigError, UsageError } from "../../core/errors";
@@ -123,7 +124,7 @@ async function ensureGitMirror(repo, cachePaths, options) {
123
124
  * shared registry-index cache (12h TTL) and exposes the working tree as the
124
125
  * stash content directory.
125
126
  */
126
- async function syncMirroredRepo(config, options) {
127
+ export async function syncMirroredRepo(config, options) {
127
128
  if (!config.url) {
128
129
  throw new ConfigError("git stash entry requires a URL when no install ref is supplied");
129
130
  }
@@ -303,7 +304,18 @@ function pullRepo(repoDir) {
303
304
  }
304
305
  function hasExtractedRepo(repoDir) {
305
306
  try {
306
- return fs.statSync(repoDir).isDirectory() && fs.statSync(path.join(repoDir, "content")).isDirectory();
307
+ if (!fs.statSync(repoDir).isDirectory())
308
+ return false;
309
+ if (fs.statSync(path.join(repoDir, "content")).isDirectory())
310
+ return true;
311
+ }
312
+ catch {
313
+ /* fall through to root-layout detection */
314
+ }
315
+ try {
316
+ if (!fs.statSync(repoDir).isDirectory())
317
+ return false;
318
+ return Object.values(TYPE_DIRS).some((dirName) => fs.existsSync(path.join(repoDir, dirName)));
307
319
  }
308
320
  catch {
309
321
  return false;
package/dist/wiki/wiki.js CHANGED
@@ -894,7 +894,17 @@ export function regenerateWikiIndex(stashDir, name) {
894
894
  }
895
895
  lines.push("");
896
896
  }
897
- fs.writeFileSync(path.join(wikiDir, INDEX_MD), lines.join("\n"), "utf8");
897
+ const next = `${lines.join("\n")}\n`;
898
+ const indexPath = path.join(wikiDir, INDEX_MD);
899
+ try {
900
+ const current = fs.readFileSync(indexPath, "utf8");
901
+ if (current === next)
902
+ return false;
903
+ }
904
+ catch {
905
+ /* missing file -> write below */
906
+ }
907
+ fs.writeFileSync(indexPath, next, "utf8");
898
908
  return true;
899
909
  }
900
910
  catch {
@@ -24,10 +24,25 @@ const SUBSECTION_COMPLETION_CRITERIA = "Completion Criteria";
24
24
  * the matcher and parser cannot drift.
25
25
  */
26
26
  export function looksLikeWorkflow(body) {
27
- return (/^#\s+Workflow:\s+/m.test(body) &&
28
- /^##\s+Step:\s+/m.test(body) &&
29
- /^Step ID:\s+/m.test(body) &&
30
- /^###\s+Instructions\s*$/m.test(body));
27
+ const structuralBody = stripFencedCodeBlocks(body);
28
+ return (/^#\s+Workflow:\s+/m.test(structuralBody) &&
29
+ /^##\s+Step:\s+/m.test(structuralBody) &&
30
+ /^Step ID:\s+/m.test(structuralBody) &&
31
+ /^###\s+Instructions\s*$/m.test(structuralBody));
32
+ }
33
+ function stripFencedCodeBlocks(body) {
34
+ let inFence = false;
35
+ const lines = body.split(/\r?\n/);
36
+ const stripped = [];
37
+ for (const line of lines) {
38
+ if (/^\s*```/.test(line) || /^\s*~~~/.test(line)) {
39
+ inFence = !inFence;
40
+ stripped.push("");
41
+ continue;
42
+ }
43
+ stripped.push(inFence ? "" : line);
44
+ }
45
+ return stripped.join("\n");
31
46
  }
32
47
  export function parseWorkflow(markdown, source) {
33
48
  const errors = [];
@@ -5,7 +5,7 @@ import { loadConfig } from "../core/config";
5
5
  import { NotFoundError, UsageError } from "../core/errors";
6
6
  import { appendEvent } from "../core/events";
7
7
  import { getDbPath } from "../core/paths";
8
- import { closeDatabase, openDatabase } from "../indexer/db";
8
+ import { closeDatabase, openExistingDatabase } from "../indexer/db";
9
9
  import { resolveSourceEntries } from "../indexer/search-source";
10
10
  import { resolveSourcesForOrigin } from "../registry/origin-resolve";
11
11
  import { resolveAssetPath } from "../sources/resolve";
@@ -264,7 +264,7 @@ function readWorkflowDocumentFromIndex(sourcePath, ref) {
264
264
  const dbPath = getDbPath();
265
265
  if (!fs.existsSync(dbPath))
266
266
  return null;
267
- const db = openDatabase(dbPath);
267
+ const db = openExistingDatabase(dbPath);
268
268
  try {
269
269
  const parsed = parseAssetRef(ref);
270
270
  const entryKey = `${sourcePath}:${parsed.type}:${parsed.name}`;
@@ -315,7 +315,7 @@ function resolveWorkflowEntryId(sourcePath, ref) {
315
315
  const dbPath = getDbPath();
316
316
  if (!fs.existsSync(dbPath))
317
317
  return null;
318
- const db = openDatabase(dbPath);
318
+ const db = openExistingDatabase(dbPath);
319
319
  try {
320
320
  const parsed = parseAssetRef(ref);
321
321
  const entryKey = `${sourcePath}:${parsed.type}:${parsed.name}`;
package/docs/README.md CHANGED
@@ -10,8 +10,8 @@
10
10
 
11
11
  ## Upgrading
12
12
 
13
- - [v1 migration guide](migration/v1.md) -- The path from 0.x to v1.0
14
- - [Release notes (latest: 0.7.0)](migration/release-notes/0.7.0.md) -- Per-release notes drop into `migration/release-notes/`
13
+ - [v1 migration guide](migration/v1.md) -- The path from 0.x to v1.0, including the `.stash.json` removal scheduled for v0.8.0
14
+ - [Release notes (latest: 0.7.0)](migration/release-notes/0.7.0.md) -- Per-release notes drop into `migration/release-notes/`, including current pre-release removals
15
15
  - [v0.5 → v0.6 migration guide](migration/v0.5-to-v0.6.md) -- Every breaking change with before/after code, publisher checklist, and troubleshooting
16
16
 
17
17
  ## Reference
@@ -19,7 +19,7 @@
19
19
  - [CLI](cli.md) -- All `akm` commands and flags
20
20
  - [Registry](registry.md) -- Registries, search, hosting, and managing sources
21
21
  - [Configuration](configuration.md) -- Providers, settings, and Ollama setup
22
- - [Filesystem](technical/filesystem.md) -- Directory layout and `.stash.json` schema
22
+ - [Filesystem](technical/filesystem.md) -- Directory layout plus `.stash.json` deprecation and migration notes
23
23
 
24
24
  ## Official Ecosystem Repositories
25
25
 
@@ -17,6 +17,14 @@ If you are coming from 0.6.x, the
17
17
  canonical upgrade reference. This file is the executive summary of
18
18
  what's new in 0.7.0.
19
19
 
20
+ For stash authors on the 0.7.x pre-release line: `.stash.json` remains supported
21
+ for compatibility in this release, but it is deprecated and will be removed in
22
+ v0.8.0. That timeline is intentional: during this aggressive pre-release
23
+ phase-out window, compatibility shims do not stay around until 1.0 unless they
24
+ still earn their cost. Prefer frontmatter for markdown assets and structured
25
+ code comments for scripts, and migrate any remaining `.stash.json` metadata
26
+ before taking the 0.8 upgrade.
27
+
20
28
  ## Major new surfaces in 0.7.0
21
29
 
22
30
  ### Proposal queue (`akm proposal *`) — new (#225, #226, #233)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akm-cli",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
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": [
@@ -66,7 +66,7 @@
66
66
  "typescript": "^5.9.3"
67
67
  },
68
68
  "optionalDependencies": {
69
- "@huggingface/transformers": "3.8.1",
69
+ "@huggingface/transformers": "^4.2.0",
70
70
  "sqlite-vec": "0.1.7-alpha.2"
71
71
  },
72
72
  "engines": {