scream-code 0.14.8 → 0.15.0

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.
@@ -3,6 +3,7 @@ import { fileURLToPath as __cjsShimFileURLToPath } from 'node:url';
3
3
  import { dirname as __cjsShimDirname } from 'node:path';
4
4
  const __filename = __cjsShimFileURLToPath(import.meta.url);
5
5
  const __dirname = __cjsShimDirname(__filename);
6
+ import { createHash } from "node:crypto";
6
7
  import { mkdir, readFile, readdir } from "node:fs/promises";
7
8
  import { DatabaseSync } from "node:sqlite";
8
9
  //#region ../../node_modules/.pnpm/pathe@2.0.3/node_modules/pathe/dist/shared/pathe.M-eThtNZ.mjs
@@ -203,6 +204,8 @@ function jsonToVector(json) {
203
204
  function isEntityType(value) {
204
205
  return ENTITY_TYPES$1.has(value);
205
206
  }
207
+ /** Meta key recording which embedding model the stored vectors came from. */
208
+ const EMBEDDING_MODEL_META_KEY = "embedding_model";
206
209
  var KnowledgeStore = class {
207
210
  dbPath;
208
211
  db;
@@ -288,6 +291,22 @@ var KnowledgeStore = class {
288
291
  this.db.exec("ROLLBACK");
289
292
  } catch {}
290
293
  }
294
+ /**
295
+ * Read a metadata value (e.g. the embedding model identity the library was
296
+ * built with). Returns null when the key is absent.
297
+ */
298
+ async getMeta(key) {
299
+ await this.init();
300
+ if (this.db === void 0) return null;
301
+ const row = this.db.prepare("SELECT value FROM knowledge_meta WHERE key = ?").get(key);
302
+ return row === void 0 ? null : asString(row["value"]);
303
+ }
304
+ /** Write a metadata value (upsert). */
305
+ async setMeta(key, value) {
306
+ await this.init();
307
+ if (this.db === void 0) throw new Error("knowledge store not initialized");
308
+ this.db.prepare("INSERT INTO knowledge_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(key, value);
309
+ }
291
310
  createSchema() {
292
311
  if (this.db === void 0) return;
293
312
  this.db.exec(`
@@ -383,22 +402,39 @@ var KnowledgeStore = class {
383
402
  CREATE VIRTUAL TABLE IF NOT EXISTS knowledge_entities_fts USING fts5(
384
403
  name, description, content='knowledge_entities', content_rowid='rowid'
385
404
  );
405
+
406
+ CREATE TABLE IF NOT EXISTS knowledge_meta (
407
+ key TEXT PRIMARY KEY,
408
+ value TEXT NOT NULL
409
+ );
386
410
  `);
411
+ try {
412
+ this.db.exec("ALTER TABLE knowledge_sources ADD COLUMN content_hash TEXT");
413
+ } catch (error) {
414
+ if (!(error instanceof Error ? error.message : String(error)).includes("duplicate column")) throw error;
415
+ }
387
416
  }
388
417
  async createSource(params) {
389
418
  await this.init();
390
419
  if (this.db === void 0) throw new Error("knowledge store not initialized");
391
420
  const id = generateId("src");
392
421
  const createdAt = Date.now();
393
- this.db.prepare("INSERT INTO knowledge_sources (id, name, file_path, description, created_at) VALUES (?, ?, ?, ?, ?)").run(id, params.name, params.filePath ?? null, params.description ?? null, createdAt);
422
+ this.db.prepare("INSERT INTO knowledge_sources (id, name, file_path, description, content_hash, created_at) VALUES (?, ?, ?, ?, ?, ?)").run(id, params.name, params.filePath ?? null, params.description ?? null, params.contentHash ?? null, createdAt);
394
423
  return {
395
424
  id,
396
425
  name: params.name,
397
426
  filePath: params.filePath ?? null,
398
427
  description: params.description ?? null,
428
+ contentHash: params.contentHash ?? null,
399
429
  createdAt
400
430
  };
401
431
  }
432
+ /** Backfill or refresh a source's content fingerprint (legacy rows / replacements). */
433
+ async updateSourceContentHash(id, contentHash) {
434
+ await this.init();
435
+ if (this.db === void 0) throw new Error("knowledge store not initialized");
436
+ this.db.prepare("UPDATE knowledge_sources SET content_hash = ? WHERE id = ?").run(contentHash, id);
437
+ }
402
438
  async findSourceByFilePath(filePath) {
403
439
  await this.init();
404
440
  if (this.db === void 0) return void 0;
@@ -766,6 +802,87 @@ var KnowledgeStore = class {
766
802
  }
767
803
  return out;
768
804
  }
805
+ /**
806
+ * Recompute every embedding for one source from the already-stored texts
807
+ * (no LLM extraction). Repairs libraries whose vectors are missing
808
+ * (ingested while the embedding model was unavailable) or stale (produced
809
+ * by a different model). All texts are embedded up front — any batch
810
+ * failure aborts before the first write, so the library is never left
811
+ * half-updated. On success the embedding-model meta is stamped with the
812
+ * engine's identity, unblocking future ingests after a model change.
813
+ */
814
+ async reembedSource(sourceId, engine) {
815
+ await this.init();
816
+ if (this.db === void 0) throw new Error("knowledge store not initialized");
817
+ if (!engine.available) throw new Error("embedding engine unavailable");
818
+ const chunkRows = this.db.prepare("SELECT id, heading, content FROM knowledge_chunks WHERE source_id = ? ORDER BY rank ASC").all(sourceId);
819
+ const chunkTexts = chunkRows.map((c) => c.heading !== null ? `${c.heading}\n${c.content}` : c.content);
820
+ const eventRows = this.db.prepare("SELECT id, title, content FROM knowledge_events WHERE source_id = ? ORDER BY rank ASC").all(sourceId);
821
+ const eventTitleTexts = eventRows.map((e) => e.title);
822
+ const eventContentTexts = eventRows.map((e) => `${e.title}\n\n${e.content}`);
823
+ const entityRows = this.db.prepare("SELECT id, name FROM knowledge_entities WHERE source_id = ?").all(sourceId);
824
+ const relationRows = this.db.prepare(`SELECT ee.id, ee.description, e.title, en.name
825
+ FROM knowledge_event_entities ee
826
+ JOIN knowledge_events e ON e.id = ee.event_id
827
+ JOIN knowledge_entities en ON en.id = ee.entity_id
828
+ WHERE e.source_id = ?`).all(sourceId);
829
+ const relationTexts = relationRows.map((r) => r.description !== null && r.description.length > 0 ? r.description : `${r.title} ${r.name}`);
830
+ const embedAll = async (texts) => {
831
+ if (texts.length === 0) return [];
832
+ const vectors = await engine.embedBatch(texts);
833
+ if (vectors === null || vectors.length !== texts.length) throw new Error("向量嵌入失败,已取消本次重新嵌入");
834
+ return vectors;
835
+ };
836
+ const chunkVecs = await embedAll(chunkTexts);
837
+ const eventTitleVecs = await embedAll(eventTitleTexts);
838
+ const eventContentVecs = await embedAll(eventContentTexts);
839
+ const entityVecs = await embedAll(entityRows.map((e) => e.name));
840
+ const relationVecs = await embedAll(relationTexts);
841
+ this.beginTransaction();
842
+ try {
843
+ const updChunk = this.db.prepare("UPDATE knowledge_chunks SET embedding_json = ? WHERE id = ?");
844
+ for (let i = 0; i < chunkRows.length; i++) updChunk.run(vectorToJson(chunkVecs[i] ?? null), chunkRows[i].id);
845
+ const updEvent = this.db.prepare("UPDATE knowledge_events SET title_embedding_json = ?, content_embedding_json = ? WHERE id = ?");
846
+ for (let i = 0; i < eventRows.length; i++) updEvent.run(vectorToJson(eventTitleVecs[i] ?? null), vectorToJson(eventContentVecs[i] ?? null), eventRows[i].id);
847
+ const updEntity = this.db.prepare("UPDATE knowledge_entities SET embedding_json = ? WHERE id = ?");
848
+ for (let i = 0; i < entityRows.length; i++) updEntity.run(vectorToJson(entityVecs[i] ?? null), entityRows[i].id);
849
+ const updRelation = this.db.prepare("UPDATE knowledge_event_entities SET embedding_json = ? WHERE id = ?");
850
+ for (let i = 0; i < relationRows.length; i++) updRelation.run(vectorToJson(relationVecs[i] ?? null), relationRows[i].id);
851
+ await this.setMeta(EMBEDDING_MODEL_META_KEY, engine.modelName);
852
+ this.commitTransaction();
853
+ } catch (error) {
854
+ this.rollbackTransaction();
855
+ throw error;
856
+ }
857
+ return {
858
+ chunks: chunkRows.length,
859
+ events: eventRows.length,
860
+ entities: entityRows.length,
861
+ relations: relationRows.length
862
+ };
863
+ }
864
+ /**
865
+ * Chunk vector coverage — how many of the stored chunks carry an embedding.
866
+ * Used by the TUI to surface partial/skipped embedding after an ingest.
867
+ */
868
+ async embeddingCoverage() {
869
+ await this.init();
870
+ if (this.db === void 0) return {
871
+ total: 0,
872
+ embedded: 0
873
+ };
874
+ const row = this.db.prepare(`SELECT COUNT(*) AS total,
875
+ SUM(CASE WHEN embedding_json IS NOT NULL THEN 1 ELSE 0 END) AS embedded
876
+ FROM knowledge_chunks`).get();
877
+ if (row === void 0) return {
878
+ total: 0,
879
+ embedded: 0
880
+ };
881
+ return {
882
+ total: Number(row["total"] ?? 0),
883
+ embedded: Number(row["embedded"] ?? 0)
884
+ };
885
+ }
769
886
  async stats() {
770
887
  await this.init();
771
888
  if (this.db === void 0) return {
@@ -825,6 +942,7 @@ function rowToSource(row) {
825
942
  name: asString(row["name"]),
826
943
  filePath: asNullableString(row["file_path"]),
827
944
  description: asNullableString(row["description"]),
945
+ contentHash: asNullableString(row["content_hash"]),
828
946
  createdAt: Number(row["created_at"])
829
947
  };
830
948
  }
@@ -1487,6 +1605,22 @@ async function mapWithConcurrency(items, limit, fn, onProgress) {
1487
1605
  return results;
1488
1606
  }
1489
1607
  /**
1608
+ * Stamp or verify the embedding-model identity recorded in the library.
1609
+ * A library written by a different model would mix vector spaces — block the
1610
+ * ingest with a clear message instead of silently storing incompatible
1611
+ * embeddings. A legacy library without the marker is stamped with the current
1612
+ * model (all existing data was written by it).
1613
+ */
1614
+ async function ensureEmbeddingModelMatches(store, engine) {
1615
+ if (engine === void 0) return;
1616
+ const recorded = await store.getMeta(EMBEDDING_MODEL_META_KEY);
1617
+ if (recorded === null) {
1618
+ await store.setMeta(EMBEDDING_MODEL_META_KEY, engine.modelName);
1619
+ return;
1620
+ }
1621
+ if (recorded !== engine.modelName) throw new Error(`知识库由模型 ${recorded} 嵌入,当前模型为 ${engine.modelName},请先在 /knowledge 中重新嵌入知识库`);
1622
+ }
1623
+ /**
1490
1624
  * Ingest a markdown file into the knowledge base.
1491
1625
  *
1492
1626
  * Steps:
@@ -1511,34 +1645,53 @@ async function ingestFile(store, llm, filePath, onProgress) {
1511
1645
  stage: "embedding-check",
1512
1646
  message: "向量模型未就绪,本次摄入跳过向量嵌入"
1513
1647
  });
1514
- const existing = await store.findSourceByFilePath(filePath);
1515
- if (existing !== void 0) {
1516
- onProgress?.({
1517
- stage: "error",
1518
- message: `文件已摄入:${existing.name}(source id=${existing.id})`
1519
- });
1520
- throw new Error(`文件已摄入过: ${filePath} (source ${existing.id})`);
1521
- }
1648
+ await ensureEmbeddingModelMatches(store, engine);
1522
1649
  onProgress?.({
1523
1650
  stage: "chunking",
1524
1651
  message: `读取并切分文件: ${basename(filePath)}`
1525
1652
  });
1526
1653
  const content = await readFileContent(filePath);
1527
- const sections = chunkContent(content, getFileExtension(filePath));
1528
- if (sections.length === 0) {
1654
+ const contentHash = createHash("sha256").update(content).digest("hex");
1655
+ const existing = await store.findSourceByFilePath(filePath);
1656
+ let replacedOldVersion = false;
1657
+ if (existing !== void 0) {
1658
+ if (existing.contentHash === contentHash || existing.contentHash === null) {
1659
+ if (existing.contentHash === null) await store.updateSourceContentHash(existing.id, contentHash);
1660
+ onProgress?.({
1661
+ stage: "completed",
1662
+ message: "内容未变化,已跳过"
1663
+ });
1664
+ return {
1665
+ documentId: null,
1666
+ chunkCount: 0,
1667
+ eventCount: 0,
1668
+ entityCount: 0,
1669
+ outcome: "unchanged"
1670
+ };
1671
+ }
1529
1672
  onProgress?.({
1530
- stage: "error",
1531
- message: "文件无有效内容"
1673
+ stage: "chunking",
1674
+ message: `检测到内容变更,替换旧版本: ${existing.name}`
1532
1675
  });
1533
- throw new Error("文件无有效内容可摄入");
1676
+ await store.deleteSource(existing.id);
1677
+ replacedOldVersion = true;
1534
1678
  }
1679
+ const sections = chunkContent(content, getFileExtension(filePath));
1535
1680
  store.beginTransaction();
1536
1681
  try {
1682
+ if (sections.length === 0) {
1683
+ onProgress?.({
1684
+ stage: "error",
1685
+ message: "文件无有效内容"
1686
+ });
1687
+ throw new Error("文件无有效内容可摄入");
1688
+ }
1537
1689
  const fileName = basename(filePath);
1538
1690
  const source = await store.createSource({
1539
1691
  name: fileName,
1540
1692
  filePath,
1541
- description: null
1693
+ description: null,
1694
+ contentHash
1542
1695
  });
1543
1696
  const document = await store.createDocument({
1544
1697
  sourceId: source.id,
@@ -1687,10 +1840,15 @@ async function ingestFile(store, llm, filePath, onProgress) {
1687
1840
  documentId: document.id,
1688
1841
  chunkCount: chunks.length,
1689
1842
  eventCount: events.length,
1690
- entityCount: uniqueEntities.length
1843
+ entityCount: uniqueEntities.length,
1844
+ outcome: replacedOldVersion ? "updated" : "created"
1691
1845
  };
1692
1846
  } catch (error) {
1693
1847
  store.rollbackTransaction();
1848
+ if (replacedOldVersion) {
1849
+ const message = error instanceof Error ? error.message : String(error);
1850
+ throw new Error(`旧版本已删除、新版本摄入失败,请重新摄入该文件: ${filePath}(${message})`);
1851
+ }
1694
1852
  throw error;
1695
1853
  }
1696
1854
  }
@@ -1709,6 +1867,9 @@ async function ingestDirectory(store, llm, dirPath, onProgress) {
1709
1867
  if (files.length === 0) throw new Error(`目录下没有支持的文件 (.md, .markdown, .txt): ${dirPath}`);
1710
1868
  const errors = [];
1711
1869
  let succeeded = 0;
1870
+ let created = 0;
1871
+ let updated = 0;
1872
+ let unchanged = 0;
1712
1873
  let totalChunks = 0;
1713
1874
  let totalEvents = 0;
1714
1875
  let totalEntities = 0;
@@ -1724,6 +1885,9 @@ async function ingestDirectory(store, llm, dirPath, onProgress) {
1724
1885
  try {
1725
1886
  const result = await ingestFile(store, llm, filePath, fileProgress);
1726
1887
  succeeded += 1;
1888
+ if (result.outcome === "created") created += 1;
1889
+ else if (result.outcome === "updated") updated += 1;
1890
+ else unchanged += 1;
1727
1891
  totalChunks += result.chunkCount;
1728
1892
  totalEvents += result.eventCount;
1729
1893
  totalEntities += result.entityCount;
@@ -1741,11 +1905,14 @@ async function ingestDirectory(store, llm, dirPath, onProgress) {
1741
1905
  }
1742
1906
  onProgress?.({
1743
1907
  stage: "completed",
1744
- message: `批量摄入完成:${succeeded}/${files.length} 个文件成功,${totalChunks} chunks, ${totalEvents} events, ${totalEntities} entities`
1908
+ message: `批量摄入完成:新增 ${created} · 更新 ${updated} · 未变化 ${unchanged} · 失败 ${errors.length}(${totalChunks} chunks, ${totalEvents} events, ${totalEntities} entities)`
1745
1909
  });
1746
1910
  return {
1747
1911
  succeeded,
1748
1912
  failed: errors.length,
1913
+ created,
1914
+ updated,
1915
+ unchanged,
1749
1916
  totalChunks,
1750
1917
  totalEvents,
1751
1918
  totalEntities,
@@ -1820,6 +1987,18 @@ async function multiSearchWithTrace(store, llm, query, options = {}) {
1820
1987
  }
1821
1988
  };
1822
1989
  }
1990
+ const recordedModel = await store.getMeta(EMBEDDING_MODEL_META_KEY);
1991
+ if (recordedModel !== null && recordedModel !== engine.modelName) {
1992
+ fallbackReason = `embedding model mismatch (library: ${recordedModel}, current: ${engine.modelName}); used FTS5 keyword fallback`;
1993
+ return {
1994
+ results: await ftsFallback(store, query, topK),
1995
+ trace: {
1996
+ steps,
1997
+ rerankedEventTitles,
1998
+ fallbackReason
1999
+ }
2000
+ };
2001
+ }
1823
2002
  const queryEmbeddings = await timed(onStep, "queryEmbedding", "把用户问题转成向量,用于召回相关事件和切片。", async () => engine.embedBatch([query]));
1824
2003
  if (queryEmbeddings === null || queryEmbeddings.length === 0) {
1825
2004
  fallbackReason = "query embedding returned null; used FTS5 fallback";
@@ -302,6 +302,21 @@ const dictionaries = {
302
302
  "knowledge.menu_title": "SAG知识库管理",
303
303
  "knowledge.menu_hint": "选择操作(esc 退出)",
304
304
  "knowledge.op_failed": "操作失败: {msg}",
305
+ "knowledge.loading_model": "加载向量模型中...",
306
+ "knowledge.model_missing": "尚未下载向量模型,请先在 /knowledge 菜单选择「下载向量模型」",
307
+ "knowledge.model_load_failed": "向量模型加载失败,本次未摄入任何内容",
308
+ "knowledge.vector_coverage": "向量嵌入: {embedded}/{total} chunks",
309
+ "knowledge.reembed": "重新嵌入",
310
+ "knowledge.reembed_desc": "对指定文档重算全部向量(不重新抽取,不消耗 LLM)",
311
+ "knowledge.reembed_pick": "选择要重新嵌入的知识",
312
+ "knowledge.reembed_confirm_name": "重新嵌入「{name}」?",
313
+ "knowledge.reembed_confirm_desc": "将重算该文档所有 chunks/events/entities 向量",
314
+ "knowledge.reembed_done": "重新嵌入完成",
315
+ "knowledge.reembed_summary": "chunks: {chunks}, events: {events}, entities: {entities}, relations: {relations}",
316
+ "knowledge.reembed_none": "知识库为空,无可重新嵌入的内容",
317
+ "knowledge.ingest_unchanged": "内容未变化,已跳过",
318
+ "knowledge.ingest_updated": "已替换旧版本,重新摄入完成",
319
+ "knowledge.sync_counts": "新增 {created} · 更新 {updated} · 未变化 {unchanged}",
305
320
  "init.select_title": "选择 AGENTS.md 生成位置",
306
321
  "init.select_hint": "当前目录:{currentDir} · 项目根目录:{projectRoot}",
307
322
  "init.current_dir": "当前目录",
@@ -1018,12 +1033,6 @@ const dictionaries = {
1018
1033
  "prompts.wire_openai_responses_desc": "OpenAI Responses API(推理模型)",
1019
1034
  "prompts.wire_anthropic": "Anthropic 协议",
1020
1035
  "prompts.wire_anthropic_desc": "Messages API 兼容的服务商",
1021
- "prompts.thinking_off": "关闭思考",
1022
- "prompts.thinking_low": "低强度思考",
1023
- "prompts.thinking_medium": "中强度思考",
1024
- "prompts.thinking_high": "高强度思考",
1025
- "prompts.thinking_xhigh": "超高强度思考",
1026
- "prompts.thinking_max": "最大强度思考",
1027
1036
  "prompts.image_off": "关闭识图",
1028
1037
  "prompts.image_off_desc": "模型不支持图片输入时请选择此项",
1029
1038
  "prompts.image_on": "开启识图",
@@ -1199,7 +1208,11 @@ const dictionaries = {
1199
1208
  "kw.embedding_ready": "向量模型已就绪",
1200
1209
  "kw.embedding_not_downloaded": "向量模型未下载(选择「下载向量模型」手动下载)",
1201
1210
  "kw.embedding_data_intact": " —— 你的知识数据仍然存在,下载模型后即可正常使用",
1202
- "kw.embedding_already_installed": "向量模型已安装,无需重新下载"
1211
+ "kw.embedding_already_installed": "向量模型已安装,无需重新下载",
1212
+ "kw.setup_description": "检测到向量模型尚未下载。下载后 /knowledge 知识库与 /memory 记忆将启用语义检索(模型约 95 MB,只需下载一次)。",
1213
+ "kw.setup_download": "立即下载",
1214
+ "kw.setup_skip": "暂不下载",
1215
+ "kw.setup_hint": "↑↓ 选择 · Enter 确认 · Esc 跳过"
1203
1216
  },
1204
1217
  en: {
1205
1218
  "status.not_set": "Not set",
@@ -1404,6 +1417,21 @@ const dictionaries = {
1404
1417
  "knowledge.menu_title": "SAG Knowledge Base",
1405
1418
  "knowledge.menu_hint": "Select an action (Esc to exit)",
1406
1419
  "knowledge.op_failed": "Operation failed: {msg}",
1420
+ "knowledge.loading_model": "Loading vector model...",
1421
+ "knowledge.model_missing": "Vector model not downloaded yet. Select \"Download vector model\" in the /knowledge menu first.",
1422
+ "knowledge.model_load_failed": "Vector model failed to load. Nothing was ingested.",
1423
+ "knowledge.vector_coverage": "Vectors: {embedded}/{total} chunks embedded",
1424
+ "knowledge.reembed": "Re-embed",
1425
+ "knowledge.reembed_desc": "Recompute all vectors for one document (no re-extraction, no LLM cost)",
1426
+ "knowledge.reembed_pick": "Pick knowledge to re-embed",
1427
+ "knowledge.reembed_confirm_name": "Re-embed \"{name}\"?",
1428
+ "knowledge.reembed_confirm_desc": "All chunk/event/entity vectors of this document will be recomputed",
1429
+ "knowledge.reembed_done": "Re-embed completed",
1430
+ "knowledge.reembed_summary": "chunks: {chunks}, events: {events}, entities: {entities}, relations: {relations}",
1431
+ "knowledge.reembed_none": "Knowledge base is empty; nothing to re-embed",
1432
+ "knowledge.ingest_unchanged": "Content unchanged, skipped",
1433
+ "knowledge.ingest_updated": "Previous version replaced; re-ingestion completed",
1434
+ "knowledge.sync_counts": "new: {created} · updated: {updated} · unchanged: {unchanged}",
1407
1435
  "init.select_title": "Select AGENTS.md location",
1408
1436
  "init.select_hint": "Current: {currentDir} · Project root: {projectRoot}",
1409
1437
  "init.current_dir": "Current directory",
@@ -2120,12 +2148,6 @@ const dictionaries = {
2120
2148
  "prompts.wire_openai_responses_desc": "OpenAI Responses API (reasoning models)",
2121
2149
  "prompts.wire_anthropic": "Anthropic protocol",
2122
2150
  "prompts.wire_anthropic_desc": "Messages API compatible providers",
2123
- "prompts.thinking_off": "Thinking off",
2124
- "prompts.thinking_low": "Low thinking",
2125
- "prompts.thinking_medium": "Medium thinking",
2126
- "prompts.thinking_high": "High thinking",
2127
- "prompts.thinking_xhigh": "Extra-high thinking",
2128
- "prompts.thinking_max": "Max thinking",
2129
2151
  "prompts.image_off": "Image off",
2130
2152
  "prompts.image_off_desc": "Select this when the model does not support image input",
2131
2153
  "prompts.image_on": "Image on",
@@ -2301,7 +2323,11 @@ const dictionaries = {
2301
2323
  "kw.embedding_ready": "Vector model ready",
2302
2324
  "kw.embedding_not_downloaded": "Vector model not downloaded (select Download vector model)",
2303
2325
  "kw.embedding_data_intact": " — your knowledge data is still there; download the model to use it again",
2304
- "kw.embedding_already_installed": "Vector model already installed, no need to re-download"
2326
+ "kw.embedding_already_installed": "Vector model already installed, no need to re-download",
2327
+ "kw.setup_description": "The vector model is not downloaded yet. Once downloaded, /knowledge and /memory gain semantic retrieval (about 95 MB, one-time download).",
2328
+ "kw.setup_download": "Download now",
2329
+ "kw.setup_skip": "Not now",
2330
+ "kw.setup_hint": "↑↓ select · Enter confirm · Esc skip"
2305
2331
  }
2306
2332
  };
2307
2333
  function detectSystemLocale() {
@@ -3,5 +3,5 @@ import { fileURLToPath as __cjsShimFileURLToPath } from 'node:url';
3
3
  import { dirname as __cjsShimDirname } from 'node:path';
4
4
  const __filename = __cjsShimFileURLToPath(import.meta.url);
5
5
  const __dirname = __cjsShimDirname(__filename);
6
- import { t as TextInputDialogComponent } from "./text-input-dialog-B0yHChBj.mjs";
6
+ import { t as TextInputDialogComponent } from "./text-input-dialog-BUXAKycx.mjs";
7
7
  export { TextInputDialogComponent };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scream-code",
3
- "version": "0.14.8",
3
+ "version": "0.15.0",
4
4
  "description": "A terminal-native AI agent for builders",
5
5
  "license": "MIT",
6
6
  "author": "ScreamCli",
@@ -46,6 +46,7 @@
46
46
  "dev:prod": "node dist/main.mjs",
47
47
  "web:build": "pnpm exec vite build src/web/frontend",
48
48
  "web:dev": "pnpm exec vite dev src/web/frontend",
49
+ "web:typecheck": "vue-tsc --noEmit -p src/web/frontend/tsconfig.json",
49
50
  "clean": "rm -rf dist",
50
51
  "typecheck": "tsc -p tsconfig.json --noEmit",
51
52
  "test": "pnpm -w run build:packages && vitest run",
@@ -64,6 +65,7 @@
64
65
  "fastembed": "^2.1.0",
65
66
  "marked": "^15.0.7",
66
67
  "mupdf": "^1.28.0",
68
+ "qrcode": "^1.5.4",
67
69
  "semver": "^7.7.4",
68
70
  "shiki": "^3.2.1",
69
71
  "smol-toml": "^1.6.1",
@@ -78,12 +80,16 @@
78
80
  "@scream-code/knowledge": "workspace:*",
79
81
  "@scream-code/memory": "workspace:*",
80
82
  "@scream-code/scream-code-sdk": "workspace:^",
83
+ "@types/qrcode": "^1.5.6",
81
84
  "@types/semver": "^7.7.0",
82
85
  "@types/ws": "^8.18.1",
83
86
  "@vitejs/plugin-vue": "^5.2.4",
87
+ "@vue/test-utils": "^2.5.0",
88
+ "jsdom": "^24.1.3",
84
89
  "tsx": "^4.21.0",
85
90
  "vite": "^6.3.5",
86
- "vue": "^3.5.13"
91
+ "vue": "^3.5.13",
92
+ "vue-tsc": "^3.3.11"
87
93
  },
88
94
  "engines": {
89
95
  "node": ">=22.19.0"