@wirecat/cli-core 0.425.0 → 0.427.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 (39) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/cli/commands-command.js +1 -1
  3. package/dist/cli/effects.d.ts +4 -0
  4. package/dist/cli/effects.js +5 -1
  5. package/dist/cli/guide.d.ts +1 -1
  6. package/dist/cli/guide.js +33 -5
  7. package/dist/cli/help/command-help.js +12 -7
  8. package/dist/cli/help/first-screen.js +2 -2
  9. package/dist/cli/index.d.ts +3 -2
  10. package/dist/cli/index.js +3 -2
  11. package/dist/cli/meeting-preparation-command.js +2 -2
  12. package/dist/cli/meeting-series-inventory-command.js +2 -2
  13. package/dist/cli/messenger/models-command.js +3 -2
  14. package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
  15. package/dist/cli/messenger/store-maintenance-command.js +9 -47
  16. package/dist/cli/messenger/store-tables-command.d.ts +5 -0
  17. package/dist/cli/messenger/store-tables-command.js +59 -0
  18. package/dist/cli/profile.d.ts +7 -0
  19. package/dist/cli/profile.js +19 -0
  20. package/dist/cli/program.js +12 -11
  21. package/dist/cli/store-info.d.ts +12 -0
  22. package/dist/cli/store-info.js +72 -0
  23. package/dist/entities/index.d.ts +54 -5
  24. package/dist/entities/index.js +3 -1
  25. package/dist/entities/store-maintenance.d.ts +48 -4
  26. package/dist/entities/store-maintenance.js +16 -3
  27. package/dist/entities/store-table.d.ts +8 -0
  28. package/dist/entities/store-table.js +9 -0
  29. package/dist/meetings/mcp/index.d.ts +15 -15
  30. package/dist/store/compatibility.d.ts +28 -0
  31. package/dist/store/compatibility.js +41 -0
  32. package/dist/store/exports.d.ts +1 -0
  33. package/dist/store/exports.js +1 -0
  34. package/dist/store/sqlite/store-tables.d.ts +43 -0
  35. package/dist/store/sqlite/store-tables.js +111 -0
  36. package/dist/store/store.d.ts +1 -0
  37. package/dist/store/store.js +1 -0
  38. package/package.json +3 -3
  39. package/parity.json +12 -0
@@ -19,6 +19,7 @@ export { type AttachmentTextEntry, type AttachmentView, attachmentColumns, type
19
19
  export { pendingNormalization } from "./sqlite/backfill.js";
20
20
  export type { ChatRekey, ChatRekeys } from "./sqlite/chat-rekey.js";
21
21
  export { CHAT_COLUMNS, TRACKED_CHAT_COLUMNS, titlePart } from "./sqlite/chats.js";
22
+ export { storeClients } from "./sqlite/clients.js";
22
23
  export { CHAT_LIST_KEY, type ChatCompleteness, fetchedKey, historyStartKey } from "./sqlite/completeness.js";
23
24
  export { CONVERSATION_COLUMNS } from "./sqlite/conversations.js";
24
25
  export type { CounterTarget } from "./sqlite/counters.js";
@@ -3,6 +3,7 @@ export { accountGroupsOf } from "./sqlite/account-groups.js";
3
3
  export { attachmentColumns, resetAttachmentWords, } from "./sqlite/attachment-texts.js";
4
4
  export { pendingNormalization } from "./sqlite/backfill.js";
5
5
  export { CHAT_COLUMNS, TRACKED_CHAT_COLUMNS, titlePart } from "./sqlite/chats.js";
6
+ export { storeClients } from "./sqlite/clients.js";
6
7
  export { CHAT_LIST_KEY, fetchedKey, historyStartKey } from "./sqlite/completeness.js";
7
8
  export { CONVERSATION_COLUMNS } from "./sqlite/conversations.js";
8
9
  export { messageOfEmail, threadConversation } from "./sqlite/emails.js";
@@ -0,0 +1,43 @@
1
+ import type { TABLE_KINDS } from "../../entities/store-table.js";
2
+ import type { CacheDatabase } from "../driver.js";
3
+ export type TableKind = (typeof TABLE_KINDS)[number];
4
+ export interface StoreTable {
5
+ name: string;
6
+ kind: TableKind;
7
+ /** Counted for data and internal tables only: a search index's row count is a scan of the whole index. */
8
+ rows: number | null;
9
+ /** With sizes: the pages the table and its indexes hold; null for a search index, whose parts hold its pages. */
10
+ bytes?: number | null;
11
+ }
12
+ export interface TableFilter {
13
+ name?: string;
14
+ kind?: TableKind;
15
+ }
16
+ /** Read-only by the connection itself: listing tables must not set a journal mode or migrate the file. */
17
+ export declare const readStoreTables: (path: string, options?: {
18
+ filter?: TableFilter;
19
+ sizes?: boolean;
20
+ }) => Promise<StoreTable[]>;
21
+ export declare const storeTables: (database: CacheDatabase, { filter, sizes }?: {
22
+ filter?: TableFilter;
23
+ sizes?: boolean;
24
+ }) => StoreTable[];
25
+ /** Facts about the file itself, cheap enough for `store info`: no page is read beyond the header and the history. */
26
+ export declare const fileFacts: (database: CacheDatabase, path: string) => {
27
+ journalMode: string;
28
+ pages: {
29
+ size: number;
30
+ count: number;
31
+ free: number;
32
+ };
33
+ migrations: {
34
+ version: number;
35
+ minCompatible: number;
36
+ appliedAt: string;
37
+ }[];
38
+ backup: {
39
+ at: string;
40
+ file: string | null;
41
+ checked: string | null;
42
+ } | null;
43
+ };
@@ -0,0 +1,111 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { CliError } from "@wirecat/cli-base";
4
+ import { openSqlite } from "./open.js";
5
+ const BOOKKEEPING = ["schema_migrations", "store_settings"];
6
+ /** `PRAGMA table_list` names virtual and shadow tables itself, so a new search index needs no list kept here. */
7
+ const LISTED = `
8
+ SELECT name, CASE
9
+ WHEN type = 'virtual' THEN 'search_index'
10
+ WHEN type = 'shadow' THEN 'search_index_part'
11
+ WHEN name LIKE 'sqlite\\_%' ESCAPE '\\' OR name IN (${BOOKKEEPING.map((name) => `'${name}'`).join(", ")})
12
+ THEN 'internal'
13
+ ELSE 'data'
14
+ END AS kind
15
+ FROM pragma_table_list
16
+ WHERE schema = 'main' AND name <> 'sqlite_schema'`;
17
+ /** Read-only by the connection itself: listing tables must not set a journal mode or migrate the file. */
18
+ export const readStoreTables = async (path, options = {}) => {
19
+ const { database } = await openSqlite(path, { readOnly: true });
20
+ try {
21
+ return storeTables(database, options);
22
+ }
23
+ finally {
24
+ database.close();
25
+ }
26
+ };
27
+ export const storeTables = (database, { filter = {}, sizes = false } = {}) => {
28
+ const listed = database
29
+ .prepare(`SELECT name, kind FROM (${LISTED})
30
+ WHERE (? IS NULL OR name = ?) AND (? IS NULL OR kind = ?) ORDER BY name`)
31
+ .all(filter.name ?? null, filter.name ?? null, filter.kind ?? null, filter.kind ?? null)
32
+ .map((row) => ({ name: String(row.name), kind: String(row.kind) }));
33
+ const bytes = sizes ? pageBytes(database) : undefined;
34
+ return listed.map(({ name, kind }) => ({
35
+ name,
36
+ kind,
37
+ rows: kind === "data" || kind === "internal" ? rowCount(database, name) : null,
38
+ ...(bytes ? { bytes: kind === "search_index" ? null : (bytes.get(name) ?? 0) } : {}),
39
+ }));
40
+ };
41
+ const rowCount = (database, table) => Number(database.prepare(`SELECT count(*) AS n FROM "${table.replaceAll('"', '""')}"`).get()?.n ?? 0);
42
+ /** Every page of the file is read: about ten seconds for a 3 GB store whose pages are not in memory. */
43
+ const pageBytes = (database) => {
44
+ let rows;
45
+ try {
46
+ // Joined here, not in SQL: as the inner side of a join, dbstat walks the whole file once per outer row.
47
+ rows = database.prepare("SELECT name, pgsize FROM dbstat WHERE schema = 'main' AND aggregate = 1").all();
48
+ }
49
+ catch (error) {
50
+ if (!/no such table: dbstat/.test(String(error)))
51
+ throw error;
52
+ throw new CliError("configuration_error", "this SQLite cannot measure table sizes (it was built without dbstat) — run the command under Node instead of Bun");
53
+ }
54
+ const owners = new Map(database
55
+ .prepare("SELECT name, tbl_name FROM sqlite_schema WHERE type = 'index'")
56
+ .all()
57
+ .map((row) => [String(row.name), String(row.tbl_name)]));
58
+ const bytes = new Map();
59
+ for (const { name, pgsize } of rows) {
60
+ const owner = owners.get(String(name)) ?? String(name);
61
+ bytes.set(owner, (bytes.get(owner) ?? 0) + Number(pgsize));
62
+ }
63
+ return bytes;
64
+ };
65
+ /** Facts about the file itself, cheap enough for `store info`: no page is read beyond the header and the history. */
66
+ export const fileFacts = (database, path) => {
67
+ const pragma = (name) => database.prepare(`PRAGMA ${name}`).get()?.[name];
68
+ const tracked = database.prepare("SELECT 1 FROM sqlite_schema WHERE name = 'schema_migrations'").get();
69
+ return {
70
+ journalMode: String(pragma("journal_mode")),
71
+ pages: {
72
+ size: Number(pragma("page_size")),
73
+ count: Number(pragma("page_count")),
74
+ free: Number(pragma("freelist_count")),
75
+ },
76
+ migrations: tracked
77
+ ? database
78
+ .prepare("SELECT version, min_compatible, applied_at FROM schema_migrations ORDER BY version")
79
+ .all()
80
+ .map((row) => ({
81
+ version: Number(row.version),
82
+ minCompatible: Number(row.min_compatible),
83
+ appliedAt: new Date(Number(row.applied_at)).toISOString(),
84
+ }))
85
+ : [],
86
+ backup: lastBackup(path),
87
+ };
88
+ };
89
+ /**
90
+ * The marker the owner's backup job writes beside the store; cli-memo's `mail trash` already trusts it. Only its
91
+ * time, file name and check are passed on, and a marker that does not parse reads as none.
92
+ */
93
+ const lastBackup = (path) => {
94
+ const marker = join(dirname(path), "backups", "last.json");
95
+ if (!existsSync(marker))
96
+ return null;
97
+ try {
98
+ const { at, file, checked } = JSON.parse(readFileSync(marker, "utf8"));
99
+ const time = typeof at === "string" ? Date.parse(at) : Number.NaN;
100
+ if (Number.isNaN(time))
101
+ return null;
102
+ return {
103
+ at: new Date(time).toISOString(),
104
+ file: typeof file === "string" ? file : null,
105
+ checked: typeof checked === "string" ? checked : null,
106
+ };
107
+ }
108
+ catch {
109
+ return null;
110
+ }
111
+ };
@@ -106,5 +106,6 @@ export interface MessageStore {
106
106
  close(): Promise<void>;
107
107
  }
108
108
  export { BACKFILL_ON_OPEN, type StoreOptions } from "./sqlite/open-store.js";
109
+ export { fileFacts, readStoreTables, type TableKind } from "./sqlite/store-tables.js";
109
110
  export declare const openStore: (options?: StoreOptions) => Promise<MessageStore>;
110
111
  export * from "./exports.js";
@@ -41,6 +41,7 @@ import { syncStoreOver } from "./sync.js";
41
41
  import { tagsStoreOver } from "./tags.js";
42
42
  import { accountVectorsOver, vectorsStoreOver } from "./vectors.js";
43
43
  export { BACKFILL_ON_OPEN } from "./sqlite/open-store.js";
44
+ export { fileFacts, readStoreTables } from "./sqlite/store-tables.js";
44
45
  export const openStore = async (options = {}) => {
45
46
  const context = await openStoreContext(options);
46
47
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wirecat/cli-core",
3
- "version": "0.425.0",
3
+ "version": "0.427.0",
4
4
  "description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Viacheslav Ptsarev",
@@ -213,7 +213,7 @@
213
213
  "@types/node": "^22.20.4",
214
214
  "@types/pngjs": "6.0.5",
215
215
  "@vitest/coverage-v8": "^5.0.2",
216
- "@wirecat/cli-base": "0.53.0",
216
+ "@wirecat/cli-base": "0.56.0",
217
217
  "@wirecat/cli-core-legacy-tags": "npm:@wirecat/cli-core@0.414.0",
218
218
  "commander": "15.0.0",
219
219
  "cspell": "10.3.6",
@@ -234,7 +234,7 @@
234
234
  },
235
235
  "peerDependencies": {
236
236
  "@napi-rs/canvas": ">=0.1.69 <2",
237
- "@wirecat/cli-base": ">=0.53.0 <0.54.0",
237
+ "@wirecat/cli-base": ">=0.56.0 <0.57.0",
238
238
  "commander": ">=15"
239
239
  },
240
240
  "bin": {
package/parity.json CHANGED
@@ -4207,6 +4207,12 @@
4207
4207
  }
4208
4208
  },
4209
4209
  "skill show": {
4210
+ "in": "all",
4211
+ "options": {
4212
+ "--for": "all"
4213
+ }
4214
+ },
4215
+ "skill status": {
4210
4216
  "in": "all"
4211
4217
  },
4212
4218
  "stats": {
@@ -4614,6 +4620,12 @@
4614
4620
  "store status": {
4615
4621
  "in": "all"
4616
4622
  },
4623
+ "store tables": {
4624
+ "in": "all"
4625
+ },
4626
+ "store tables list": {
4627
+ "in": "all"
4628
+ },
4617
4629
  "tags": {
4618
4630
  "in": "all"
4619
4631
  },