@wirecat/cli-core 0.419.0 → 0.421.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.
Files changed (126) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/dist/cli/bot/context.d.ts +3 -2
  3. package/dist/cli/bot/context.js +15 -0
  4. package/dist/cli/config-command.js +1 -2
  5. package/dist/cli/index.d.ts +0 -1
  6. package/dist/cli/index.js +0 -1
  7. package/dist/cli/messenger/context.js +3 -6
  8. package/dist/cli/permission-keys.d.ts +2 -0
  9. package/dist/cli/permission-keys.js +10 -3
  10. package/dist/cli/preview.d.ts +2 -2
  11. package/dist/cli/preview.js +4 -3
  12. package/dist/cli/program.js +4 -8
  13. package/dist/cli/settings.d.ts +0 -2
  14. package/dist/cli/settings.js +1 -5
  15. package/dist/domain/providers.d.ts +20 -2
  16. package/dist/domain/providers.js +23 -3
  17. package/dist/domain/tag-consolidation.d.ts +30 -1
  18. package/dist/entities/index.d.ts +4 -1
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.js +1 -1
  21. package/dist/mcp/surface.js +0 -4
  22. package/dist/mcp/tool.d.ts +1 -3
  23. package/dist/mcp/tool.js +1 -2
  24. package/dist/meetings/calendar-text.d.ts +13 -0
  25. package/dist/meetings/calendar-text.js +57 -0
  26. package/dist/meetings/event-store.d.ts +202 -0
  27. package/dist/meetings/event-store.js +1 -0
  28. package/dist/meetings/events.js +5 -1
  29. package/dist/meetings/index.d.ts +3 -0
  30. package/dist/meetings/index.js +2 -0
  31. package/dist/meetings/join-key.d.ts +7 -0
  32. package/dist/meetings/join-key.js +38 -0
  33. package/dist/meetings/mcp/index.d.ts +15 -15
  34. package/dist/meetings/model.d.ts +8 -2
  35. package/dist/meetings/read-answers.d.ts +6 -2
  36. package/dist/meetings/read-answers.js +9 -2
  37. package/dist/meetings/store.d.ts +2 -0
  38. package/dist/meetings/testing/event-store-contract.d.ts +10 -0
  39. package/dist/meetings/testing/event-store-contract.js +161 -0
  40. package/dist/meetings/testing/index.d.ts +1 -0
  41. package/dist/meetings/testing/index.js +1 -0
  42. package/dist/parity/manifest.js +0 -4
  43. package/dist/search/account-list.js +39 -7
  44. package/dist/sends/index.d.ts +1 -1
  45. package/dist/sends/index.js +1 -1
  46. package/dist/sends/permissions.d.ts +0 -13
  47. package/dist/sends/permissions.js +15 -142
  48. package/dist/services/agenda.js +5 -5
  49. package/dist/services/document-source.d.ts +7 -3
  50. package/dist/services/document-source.js +24 -12
  51. package/dist/services/documents/ingestion.d.ts +6 -1
  52. package/dist/services/documents/ingestion.js +4 -0
  53. package/dist/services/notes-search.js +3 -2
  54. package/dist/services/search-all.js +1 -0
  55. package/dist/services/task-tag-filters.d.ts +3 -0
  56. package/dist/services/task-tag-filters.js +8 -0
  57. package/dist/services/tasks.js +5 -9
  58. package/dist/store/document-transfer/index.js +12 -2
  59. package/dist/store/index.d.ts +2 -1
  60. package/dist/store/sqlite/accounts.js +1 -0
  61. package/dist/store/sqlite/calendar.d.ts +3 -0
  62. package/dist/store/sqlite/calendar.js +593 -0
  63. package/dist/store/sqlite/catalog.js +16 -5
  64. package/dist/store/sqlite/chat-rekey.js +1 -1
  65. package/dist/store/sqlite/chats.js +2 -2
  66. package/dist/store/sqlite/context-document-source.js +8 -4
  67. package/dist/store/sqlite/document-transfer/apply.js +24 -2
  68. package/dist/store/sqlite/document-transfer/collection.d.ts +19 -0
  69. package/dist/store/sqlite/document-transfer/collection.js +66 -0
  70. package/dist/store/sqlite/document-transfer/plan.js +15 -8
  71. package/dist/store/sqlite/document-transfer/snapshot.js +12 -7
  72. package/dist/store/sqlite/entity-types.d.ts +5 -1
  73. package/dist/store/sqlite/entity-types.js +5 -1
  74. package/dist/store/sqlite/events.d.ts +8 -2
  75. package/dist/store/sqlite/events.js +36 -12
  76. package/dist/store/sqlite/files.d.ts +93 -0
  77. package/dist/store/sqlite/files.js +293 -0
  78. package/dist/store/sqlite/folded.d.ts +3 -0
  79. package/dist/store/sqlite/folded.js +1 -0
  80. package/dist/store/sqlite/imported-documents.js +1 -1
  81. package/dist/store/sqlite/join-keys.d.ts +8 -0
  82. package/dist/store/sqlite/join-keys.js +27 -0
  83. package/dist/store/sqlite/knowledge.js +6 -6
  84. package/dist/store/sqlite/link-reviews.d.ts +9 -0
  85. package/dist/store/sqlite/link-reviews.js +12 -0
  86. package/dist/store/sqlite/lucene-fields.js +8 -6
  87. package/dist/store/sqlite/manifest.js +11 -0
  88. package/dist/store/sqlite/meeting-selection.js +1 -1
  89. package/dist/store/sqlite/meetings.js +6 -1
  90. package/dist/store/sqlite/migrations.generated.js +143 -0
  91. package/dist/store/sqlite/note-index.d.ts +8 -1
  92. package/dist/store/sqlite/note-index.js +14 -0
  93. package/dist/store/sqlite/note-search.js +6 -5
  94. package/dist/store/sqlite/notes.d.ts +18 -1
  95. package/dist/store/sqlite/notes.js +237 -40
  96. package/dist/store/sqlite/open-store.js +4 -0
  97. package/dist/store/sqlite/person-links.js +7 -2
  98. package/dist/store/sqlite/schema/canonical-tags.d.ts +87 -0
  99. package/dist/store/sqlite/schema/canonical-tags.js +8 -0
  100. package/dist/store/sqlite/schema/tag-consolidations.d.ts +270 -0
  101. package/dist/store/sqlite/schema/tag-consolidations.js +34 -0
  102. package/dist/store/sqlite/schema.d.ts +3589 -1634
  103. package/dist/store/sqlite/schema.js +191 -13
  104. package/dist/store/sqlite/tag-consolidation-apply.d.ts +3 -0
  105. package/dist/store/sqlite/tag-consolidation-apply.js +88 -0
  106. package/dist/store/sqlite/tag-consolidation-input.d.ts +16 -0
  107. package/dist/store/sqlite/tag-consolidation-input.js +52 -0
  108. package/dist/store/sqlite/tag-consolidation.js +51 -66
  109. package/dist/store/sqlite/tag-creation.js +17 -8
  110. package/dist/store/sqlite/tag-resolution.d.ts +5 -0
  111. package/dist/store/sqlite/tag-resolution.js +33 -0
  112. package/dist/store/sqlite/tags.d.ts +3 -1
  113. package/dist/store/sqlite/tags.js +70 -50
  114. package/dist/store/sqlite/tasks.d.ts +2 -0
  115. package/dist/store/sqlite/tasks.js +6 -1
  116. package/dist/store/sqlite/things.js +2 -1
  117. package/dist/store/sqlite/vocabulary.d.ts +1 -0
  118. package/dist/store/sqlite/vocabulary.js +30 -8
  119. package/dist/store/store.d.ts +6 -0
  120. package/dist/store/store.js +10 -0
  121. package/dist/store/tags.js +2 -2
  122. package/package.json +2 -1
  123. package/dist/cli/moved.d.ts +0 -18
  124. package/dist/cli/moved.js +0 -72
  125. package/dist/sends/permission-aliases.d.ts +0 -5
  126. package/dist/sends/permission-aliases.js +0 -50
@@ -1,7 +1,11 @@
1
1
  import type { Profiles } from "../search/account-list.js";
2
- import type { Note, NoteFolder } from "../store/index.js";
3
- /** Imported sources keep their remote edit time; indexing time still belongs to the local row. */
4
- export declare const documentSource: (note: Note, folders: NoteFolder[], profiles: Profiles) => {
2
+ import type { Note, StoredAccount, StoredFile } from "../store/index.js";
3
+ /**
4
+ * Where a hit came from. The document's preferred file says it: its account's provider, its link and its edit time.
5
+ * A Drive document under a notes folder (before version 31 moves it) is still known by its front matter.
6
+ * Indexing time belongs to the local row.
7
+ */
8
+ export declare const documentSource: (note: Note, accounts: readonly StoredAccount[], profiles: Profiles, file?: StoredFile) => {
5
9
  kind: "document" | "note";
6
10
  provider: string;
7
11
  account: string;
@@ -1,18 +1,30 @@
1
- /** Imported sources keep their remote edit time; indexing time still belongs to the local row. */
2
- export const documentSource = (note, folders, profiles) => {
1
+ import { isFolderProvider, kindIn } from "../domain/providers.js";
2
+ /**
3
+ * Where a hit came from. The document's preferred file says it: its account's provider, its link and its edit time.
4
+ * A Drive document under a notes folder (before version 31 moves it) is still known by its front matter.
5
+ * Indexing time belongs to the local row.
6
+ */
7
+ export const documentSource = (note, accounts, profiles, file) => {
3
8
  const metadata = note.frontMatter;
4
- const folder = folders.find(({ id }) => id === note.folderId);
5
- const remote = note.source === "file" && metadata?.source === "google-drive";
6
- const profile = remote && folder
7
- ? Object.entries(profiles.drive ?? {}).find(([, key]) => key.provider === "folder" && key.account === folder.name)?.[0]
9
+ const account = accounts.find(({ id }) => String(id) === note.folderId);
10
+ const remote = note.source === "file" &&
11
+ ((account !== undefined && kindIn(account.provider) === "documents") || metadata?.source === "google-drive");
12
+ const profile = remote && account
13
+ ? Object.entries(profiles)
14
+ .flatMap(([command, named]) => Object.entries(named).map(([name, key]) => ({ command, name, key })))
15
+ .find(({ key }) => (key.provider === account.provider && key.account === account.account) ||
16
+ (isFolderProvider(key.provider) && key.account === account.name))
8
17
  : undefined;
18
+ const url = file?.url ?? (remote && typeof metadata?.sourceUrl === "string" ? metadata.sourceUrl : undefined);
19
+ const modified = file?.externalUpdatedAt ??
20
+ (remote && typeof metadata?.modifiedAt === "string" && Number.isFinite(Date.parse(metadata.modifiedAt))
21
+ ? metadata.modifiedAt
22
+ : undefined);
9
23
  return {
10
24
  kind: remote ? "document" : "note",
11
- provider: profile ? "drive" : "notes",
12
- account: profile ?? note.folderId ?? "internal",
13
- ...(remote && typeof metadata?.sourceUrl === "string" ? { url: metadata.sourceUrl } : {}),
14
- updated_at: remote && typeof metadata?.modifiedAt === "string" && Number.isFinite(Date.parse(metadata.modifiedAt))
15
- ? metadata.modifiedAt
16
- : note.updatedAt,
25
+ provider: profile ? profile.command : "notes",
26
+ account: profile?.name ?? note.folderId ?? "internal",
27
+ ...(remote && url !== undefined ? { url } : {}),
28
+ updated_at: remote && modified !== undefined ? modified : note.updatedAt,
17
29
  };
18
30
  };
@@ -1,7 +1,12 @@
1
1
  import type { SourceContent, SourceFile } from "../../documents/model.js";
2
2
  import type { CatalogInput, CatalogQuery, MessageStore } from "../../store/index.js";
3
3
  export interface IngestionOptions<M extends SourceFile> {
4
- collectionName(principal: string): string;
4
+ /**
5
+ * The documents account's provider (`google-drive`): each principal's documents live under
6
+ * `{ provider, account: principal }`. Without it they live in a notes folder named `collectionName(principal)`.
7
+ */
8
+ provider?: string;
9
+ collectionName?(principal: string): string;
5
10
  externalId(id: string): string;
6
11
  catalogInput(file: M): CatalogInput;
7
12
  frontMatter(content: SourceContent<M>): Record<string, unknown>;
@@ -2,6 +2,10 @@ import { CliError } from "@wirecat/cli-base";
2
2
  import { walkQuery } from "@wirecat/cli-base/query";
3
3
  export const documentIngestion = (store, options) => {
4
4
  const folder = async (principal, create) => {
5
+ if (options.provider)
6
+ return store.notes.documentsAccount({ provider: options.provider, account: principal }, create);
7
+ if (!options.collectionName)
8
+ throw new CliError("configuration_error", "document ingestion needs a provider or a collection name");
5
9
  const name = options.collectionName(principal);
6
10
  const found = (await store.notes.folders()).find((item) => item.name === name);
7
11
  return found ?? (create ? await store.notes.addFolder({ name, format: "markdown" }) : undefined);
@@ -295,7 +295,7 @@ export const searchNotes = async (store, query, { limit = 20, offset = 0, tag, f
295
295
  const hasMore = slice ? matched.hasMore : ordered.length > window || matched.hasMore || titled.hasMore;
296
296
  const hits = [];
297
297
  const counts = new Map();
298
- const folders = await store.notes.folders();
298
+ const accounts = await store.accounts.storedAccounts();
299
299
  const profiles = await store.accounts.profiles();
300
300
  for (const [ref, { note, foundBy }] of page) {
301
301
  const links = (await store.notes.links({ from: ref })).map(({ to, targetText, kind, anchor }) => ({
@@ -310,7 +310,7 @@ export const searchNotes = async (store, query, { limit = 20, offset = 0, tag, f
310
310
  folder_id: note.folderId,
311
311
  path: note.path,
312
312
  title: note.title,
313
- ...documentSource(note, folders, profiles),
313
+ ...documentSource(note, accounts, profiles, note.source === "file" ? (await store.files.ofDocument(note.ref))[0] : undefined),
314
314
  excerpt: firstLine(note.text, words),
315
315
  links,
316
316
  found_by: foundBy,
@@ -374,6 +374,7 @@ export const searchNotesOf = async (store, account, text, { accounts: list, app,
374
374
  const held = [
375
375
  { provider: "notes", account: INTERNAL_NOTES },
376
376
  ...(await store.notes.folders()).map(({ id }) => ({ provider: "notes", account: id })),
377
+ ...(await store.notes.documentsAccounts()).map(({ id }) => ({ provider: "notes", account: id })),
377
378
  ];
378
379
  const named = list === undefined
379
380
  ? { accounts: held, skipped: [] }
@@ -109,6 +109,7 @@ export const searchAll = async (store, account, request, messenger = {}, searchM
109
109
  const read = chosen?.map(({ account }) => account) ?? [
110
110
  INTERNAL_NOTES,
111
111
  ...(await store.notes.folders()).map(({ id }) => id),
112
+ ...(await store.notes.documentsAccounts()).map(({ id }) => id),
112
113
  ];
113
114
  accounts.push(...read.map((account) => ({ resource, provider: "notes", account })));
114
115
  }
@@ -0,0 +1,3 @@
1
+ import { type QueryAst } from "@wirecat/cli-base/query";
2
+ import type { MessageStore, Where } from "../store/store.js";
3
+ export declare const taskTagFilters: (store: MessageStore, query: QueryAst | undefined) => Promise<Map<string, Where>>;
@@ -0,0 +1,8 @@
1
+ import { walkQuery } from "@wirecat/cli-base/query";
2
+ export const taskTagFilters = async (store, query) => {
3
+ const tags = new Map();
4
+ for (const node of query ? walkQuery(query.root) : [])
5
+ if (node.field === "tag" && !tags.has(node.value))
6
+ tags.set(node.value, await store.tasks.tagWhere(node.value));
7
+ return tags;
8
+ };
@@ -9,6 +9,7 @@ import { createTaskService, TASK_KINDS, TASK_STATES, } from "../tasks/index.js";
9
9
  import { storedChatId } from "./message-resolution.js";
10
10
  import { categorize, taskCategoryOf } from "./task-categories.js";
11
11
  import { taskAccount } from "./task-rules.js";
12
+ import { taskTagFilters } from "./task-tag-filters.js";
12
13
  export { TASK_KINDS, TASK_STATES };
13
14
  export const TASK_QUERY = {
14
15
  entity: "task",
@@ -68,17 +69,11 @@ export const TASK_PAIR_QUERY = {
68
69
  };
69
70
  const TIME = (sql) => ({ type: "time", sql });
70
71
  /** `chat:` names the chat as the messenger does; `groups` holds each name already resolved to its stored id. */
71
- const taskColumns = (groups) => ({
72
+ const taskColumns = (groups, tags) => ({
72
73
  id: { type: "kw", sql: "t.package_id" },
73
74
  key: { type: "kw", sql: "t.key" },
74
75
  category: { type: "kw", sql: "t.category" },
75
- tag: {
76
- where: (node) => ({
77
- sql: "EXISTS (SELECT 1 FROM taggings g JOIN tags n ON n.id = g.tag_id " +
78
- "WHERE g.taggable_type = 'task' AND g.taggable_id = t.id AND n.name = ?)",
79
- params: [node.value.trim().toLowerCase()],
80
- }),
81
- },
76
+ tag: { where: (node) => tags.get(node.value) ?? { sql: "0", params: [] } },
82
77
  title: { type: "kw", sql: "t.title_folded", folded: true },
83
78
  kind: { type: "enum", sql: "t.type" },
84
79
  state: { type: "enum", sql: "t.status" },
@@ -213,7 +208,8 @@ export const tasksService = (deps, categories) => {
213
208
  for (const node of query ? walkQuery(query.root) : [])
214
209
  if (node.field === "chat" && node.operator !== "wildcard" && !groups.has(node.value))
215
210
  groups.set(node.value, await storedChatId(deps.messenger, node.value, store, account, { includeLeft: true }));
216
- const where = query ? queryFilter(query, taskColumns(groups), { command: "tasks list" }) : undefined;
211
+ const tags = await taskTagFilters(store, query);
212
+ const where = query ? queryFilter(query, taskColumns(groups, tags), { command: "tasks list" }) : undefined;
217
213
  const rows = await store.tasks.query(taskAccount(account), where, { limit: limit + 1, offset });
218
214
  const shown = rows.slice(0, limit);
219
215
  return {
@@ -7,6 +7,7 @@ import { planTransfer } from "../sqlite/document-transfer/plan.js";
7
7
  import { transferSnapshot } from "../sqlite/document-transfer/snapshot.js";
8
8
  import { openSqlite } from "../sqlite/open.js";
9
9
  import { openStoreContext } from "../sqlite/open-store.js";
10
+ import { assertReadCompatible } from "../sqlite/read-compatibility.js";
10
11
  import { digest } from "./digest.js";
11
12
  const locations = (options) => {
12
13
  for (const value of [options.source, options.destination, options.collection])
@@ -51,8 +52,17 @@ const openSource = async (path) => {
51
52
  }
52
53
  };
53
54
  const openPreviewTarget = async (path) => {
54
- if (statSync(path, { throwIfNoEntry: false }))
55
- return openSource(path);
55
+ if (statSync(path, { throwIfNoEntry: false })) {
56
+ const opened = await openSource(path);
57
+ try {
58
+ assertReadCompatible(opened.database);
59
+ return opened;
60
+ }
61
+ catch (error) {
62
+ opened.database.close();
63
+ throw error;
64
+ }
65
+ }
56
66
  const target = await openSqlite(":memory:");
57
67
  try {
58
68
  migrate(target.database);
@@ -1,6 +1,6 @@
1
1
  export type { ContextEvidenceItem, ContextEvidencePage, ContextEvidenceReader, ContextEvidenceRequest, ContextEvidenceSender, ContextSource, } from "../context/evidence.js";
2
2
  export { FACT_STATUSES, FACT_TYPES, MEMORY_KINDS, MEMORY_SCOPES, MEMORY_STATUSES, TASK_TYPES, WORK_TASK_TYPES, } from "../domain/knowledge.js";
3
- export type { TagConsolidationOptions, TagConsolidationPreview, TagConsolidationStore, } from "../domain/tag-consolidation.js";
3
+ export type { TagConsolidationApplyOptions, TagConsolidationOptions, TagConsolidationPreview, TagConsolidationReceipt, TagConsolidationStore, } from "../domain/tag-consolidation.js";
4
4
  export type { TagRedirectIndex, TagRedirectResolution, TagRedirectSnapshot } from "../domain/tag-redirects.js";
5
5
  export { createTagRedirectIndex } from "../domain/tag-redirects.js";
6
6
  export { note } from "../entities/note.js";
@@ -31,6 +31,7 @@ export { CATALOG_QUERY } from "./sqlite/catalog.js";
31
31
  export type { ChatMetadata } from "./sqlite/chats.js";
32
32
  export type { Email, EmailAddress, EmailAttachment, EmailFacts, EmailFilter, EmailInput, EmailInThread, EmailRecipient, EmailRef, EmailThread, EmailVectorHit, Mailbox, MailboxInput, MailFilter, MailStore, SenderScore, ThreadDetails, ThreadSave, } from "./sqlite/emails.js";
33
33
  export type { Fact, FactInput, FactsStore } from "./sqlite/facts.js";
34
+ export type { FileListQuery, FilesStore, StoredFile } from "./sqlite/files.js";
34
35
  export { IMPORTED_DOCUMENT_QUERY, type ImportedDocuments } from "./sqlite/imported-documents.js";
35
36
  export type { Involvement, InvolvementStore } from "./sqlite/involvements.js";
36
37
  export type { Annotation, KnowledgeRelation, KnowledgeStore, KnowledgeTarget, LabelledType, Organization, Project, Reminder, } from "./sqlite/knowledge.js";
@@ -111,6 +111,7 @@ export const purgeAccount = ({ orm, database }, accountKey) => {
111
111
  orm.delete(accountIdentities).where(eq(accountIdentities.accountId, accountKey)).run();
112
112
  // The inbox project and its tasks are the owner's; only the tie to the account goes.
113
113
  orm.update(projects).set({ accountId: null }).where(eq(projects.accountId, accountKey)).run();
114
+ orm.run(sql `DELETE FROM files WHERE account_id = ${accountKey}`);
114
115
  orm.delete(accounts).where(eq(accounts.id, accountKey)).run();
115
116
  dropOrphanPeople(database, unseenBefore);
116
117
  orm.run(sql `DELETE FROM embeddings WHERE content_hash IN (SELECT value FROM json_each(${JSON.stringify(hashes)}))
@@ -0,0 +1,3 @@
1
+ import { type EventStore } from "../../meetings/index.js";
2
+ import type { StoreContext } from "./open.js";
3
+ export declare const eventStoreOver: (context: StoreContext) => EventStore;