@wirecat/cli-core 0.390.0 → 0.392.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 (76) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dist/cli/bot/command.js +33 -4
  3. package/dist/cli/bot/port.d.ts +35 -1
  4. package/dist/cli/bot/token.d.ts +8 -1
  5. package/dist/cli/bot/token.js +16 -3
  6. package/dist/cli/bot/watch.d.ts +2 -0
  7. package/dist/cli/bot/watch.js +54 -12
  8. package/dist/cli/bot/webhook-receiver.d.ts +22 -0
  9. package/dist/cli/bot/webhook-receiver.js +68 -0
  10. package/dist/cli/effects.d.ts +2 -0
  11. package/dist/cli/effects.js +8 -2
  12. package/dist/cli/index.d.ts +1 -1
  13. package/dist/cli/messenger/backfill-command.js +5 -1
  14. package/dist/cli/messenger/catch-up.d.ts +0 -19
  15. package/dist/cli/messenger/catch-up.js +0 -43
  16. package/dist/cli/messenger/context.js +6 -3
  17. package/dist/cli/messenger/conversations-command.js +4 -1
  18. package/dist/cli/messenger/download-command.js +4 -1
  19. package/dist/cli/messenger/inbox.js +6 -11
  20. package/dist/cli/messenger/lacks.d.ts +1 -0
  21. package/dist/cli/messenger/lacks.js +6 -1
  22. package/dist/cli/messenger/mcp-command.js +6 -28
  23. package/dist/cli/messenger/messages-command.js +17 -18
  24. package/dist/cli/messenger/messages-comments-command.js +4 -1
  25. package/dist/cli/messenger/messages-delete-command.js +4 -1
  26. package/dist/cli/messenger/messages-edit-command.js +4 -1
  27. package/dist/cli/messenger/messages-evidence-command.js +4 -1
  28. package/dist/cli/messenger/messages-forward-command.js +4 -1
  29. package/dist/cli/messenger/messages-link-command.js +4 -1
  30. package/dist/cli/messenger/messages-scheduled-command.js +4 -1
  31. package/dist/cli/messenger/messages-send-command.js +4 -1
  32. package/dist/cli/messenger/review.d.ts +1 -1
  33. package/dist/cli/messenger/review.js +5 -10
  34. package/dist/cli/messenger/tags-command.js +2 -2
  35. package/dist/cli/messenger/transcribe-command.js +4 -1
  36. package/dist/cli/settings.d.ts +0 -4
  37. package/dist/cli/settings.js +7 -5
  38. package/dist/domain/providers.js +2 -0
  39. package/dist/{mcp/http/node.d.ts → http/listen.d.ts} +3 -2
  40. package/dist/{mcp/http/node.js → http/listen.js} +5 -5
  41. package/dist/http/public-url.d.ts +10 -0
  42. package/dist/http/public-url.js +32 -0
  43. package/dist/mcp/http/serve.d.ts +1 -1
  44. package/dist/mcp/http/serve.js +1 -1
  45. package/dist/meetings/index.d.ts +2 -0
  46. package/dist/meetings/index.js +2 -0
  47. package/dist/meetings/links.d.ts +18 -0
  48. package/dist/meetings/links.js +1 -0
  49. package/dist/meetings/review.d.ts +45 -0
  50. package/dist/meetings/review.js +1 -0
  51. package/dist/search/lucene/registry.d.ts +5 -1
  52. package/dist/search/lucene/registry.js +10 -4
  53. package/dist/search/lucene/types.d.ts +8 -0
  54. package/dist/search/lucene/types.js +4 -0
  55. package/dist/search/question-plan.js +3 -3
  56. package/dist/services/excerpt.js +4 -1
  57. package/dist/services/index.d.ts +2 -0
  58. package/dist/services/index.js +2 -0
  59. package/dist/services/meeting-links.d.ts +33 -0
  60. package/dist/services/meeting-links.js +41 -0
  61. package/dist/services/meeting-review.d.ts +12 -0
  62. package/dist/services/meeting-review.js +8 -0
  63. package/dist/services/messages-search.js +2 -2
  64. package/dist/services/searches.js +3 -3
  65. package/dist/store/sqlite/lucene-fields.d.ts +10 -3
  66. package/dist/store/sqlite/lucene-fields.js +85 -13
  67. package/dist/store/sqlite/lucene.js +16 -11
  68. package/dist/store/sqlite/meeting-chat-lines.d.ts +3 -0
  69. package/dist/store/sqlite/meeting-chat-lines.js +34 -0
  70. package/dist/store/sqlite/meeting-review.d.ts +4 -0
  71. package/dist/store/sqlite/meeting-review.js +97 -0
  72. package/dist/store/sqlite/meetings.d.ts +2 -2
  73. package/dist/store/sqlite/meetings.js +4 -0
  74. package/dist/store/sqlite/mention-drain.js +3 -2
  75. package/package.json +1 -1
  76. package/parity.json +339 -118
@@ -4,7 +4,7 @@ import { isStemmed } from "../../search/lucene/resolved.js";
4
4
  import { exhausted, QUERY_LIMITS, searchError } from "../../search/lucene/types.js";
5
5
  import { normalize } from "../normalize.js";
6
6
  import { emailHitsByPk } from "./emails.js";
7
- import { combine, FIELD_COMPILERS, quoted, SPEECH, } from "./lucene-fields.js";
7
+ import { combine, FIELD_COMPILERS, ftsMatch, SPEECH, } from "./lucene-fields.js";
8
8
  import { hitsByPk } from "./search.js";
9
9
  import { read } from "./transaction.js";
10
10
  export { prefixOf } from "./lucene-fields.js";
@@ -83,11 +83,12 @@ const compileQuery = (context, execution, boundedAttachments = false) => {
83
83
  params: [match],
84
84
  });
85
85
  /** A bare word also matches a transcript, a file's text and a word of a file's name. */
86
- const everywhere = (node, text, fragment) => {
86
+ const everywhere = (node, fragment) => {
87
87
  if (!node.implicit || fragment.sql === "0")
88
88
  return fragment;
89
- const files = attachmentWords(quoted(text), "any");
90
- const elsewhere = { sql: `${files.sql} UNION ${namedSql}`, params: [...files.params, quoted(text)] };
89
+ const match = ftsMatch(node);
90
+ const files = attachmentWords(match, "any");
91
+ const elsewhere = { sql: `${files.sql} UNION ${namedSql}`, params: [...files.params, match] };
91
92
  return {
92
93
  ...fragment,
93
94
  sql: `(${fragment.sql} OR m.id IN (${elsewhere.sql}))`,
@@ -285,10 +286,10 @@ const compileQuery = (context, execution, boundedAttachments = false) => {
285
286
  }
286
287
  // Among hits the message text does not hold: a transcript, then a file's name, then a file's text. bm25 scores
287
288
  // of different indexes do not compare, and a long spreadsheet must not crowd out what was said.
288
- const places = [...fragments.values()].flatMap(({ node, fragment }) => fragment.elsewhere && fragment.fts ? [normalize(node.value)] : []);
289
+ const places = [...fragments.values()].flatMap(({ node, fragment }) => fragment.elsewhere && fragment.fts ? [ftsMatch(node)] : []);
289
290
  if (places.length && !execution.newest) {
290
291
  ranked.sql.push(`s(id, tier) AS MATERIALIZED (SELECT att.attachable_id, min(CASE WHEN ${SPEECH} THEN 1 ELSE 3 END) FROM attachments att WHERE att.attachable_type='${corpus.item}' AND att.id IN (SELECT rowid FROM attachment_words WHERE attachment_words MATCH ?) GROUP BY att.attachable_id UNION ALL SELECT attachable_id, 2 FROM (${namedSql}))`);
291
- ranked.params.push(places.map((text) => quoted(text)).join(" OR "), places.map((text) => quoted(text)).join(" OR "));
292
+ ranked.params.push(places.join(" OR "), places.join(" OR "));
292
293
  }
293
294
  const placeTier = places.length && !execution.newest ? "coalesce((SELECT min(tier) FROM s WHERE s.id=m.id), 4)," : "";
294
295
  if (exactTier) {
@@ -342,10 +343,10 @@ const compileQuery = (context, execution, boundedAttachments = false) => {
342
343
  if (!placeLeaves.length || !pks.length || found.length !== pks.length)
343
344
  return found;
344
345
  const keys = JSON.stringify(pks);
345
- const texts = placeLeaves.map(({ node }) => normalize(node.value)).filter((text) => /[\p{L}\p{N}]/u.test(text));
346
- if (!texts.length)
346
+ const wordy = placeLeaves.filter(({ node }) => /[\p{L}\p{N}]/u.test(normalize(node.value)));
347
+ if (!wordy.length)
347
348
  return found;
348
- const match = texts.map((text) => quoted(text)).join(" OR ");
349
+ const match = wordy.map(({ node }) => ftsMatch(node)).join(" OR ");
349
350
  const implicit = placeLeaves.some(({ node }) => node.implicit);
350
351
  const inText = new Set(implicit
351
352
  ? database
@@ -369,7 +370,6 @@ const compileQuery = (context, execution, boundedAttachments = false) => {
369
370
  .prepare(`SELECT att.attachable_id AS id, att.name AS name FROM attachments att WHERE att.attachable_type='${corpus.item}' AND att.attachable_id IN (SELECT value FROM json_each(?)) AND att.name IS NOT NULL ORDER BY att.position`)
370
371
  .all(keys)
371
372
  : [];
372
- const wanted = texts.map((text) => text.split(/[^\p{L}\p{N}]+/u).filter(Boolean));
373
373
  return found.map((hit, index) => {
374
374
  const pk = pks[index];
375
375
  const transcript = files.find((row) => Number(row.id) === pk && Number(row.speech) === 1);
@@ -378,7 +378,12 @@ const compileQuery = (context, execution, boundedAttachments = false) => {
378
378
  if (Number(row.id) !== pk)
379
379
  return false;
380
380
  const words = normalize(String(row.name)).split(/[^\p{L}\p{N}]+/u);
381
- return wanted.some((phrase) => words.some((_, at) => phrase.every((word, offset) => words[at + offset] === word)));
381
+ return wordy.some(({ node }) => {
382
+ const phrase = normalize(node.value).match(/[\p{L}\p{N}]+/gu) ?? [];
383
+ if (node.slop !== undefined)
384
+ return phrase.every((word) => words.includes(word));
385
+ return words.some((_, at) => phrase.every((word, offset) => words[at + offset] === word));
386
+ });
382
387
  });
383
388
  const place = inText.has(pk)
384
389
  ? ["text", hit.text]
@@ -0,0 +1,3 @@
1
+ import { type MeetingChatLinesStore } from "../../meetings/index.js";
2
+ import type { StoreContext } from "./open.js";
3
+ export declare const meetingChatLinesOver: ({ database }: StoreContext) => MeetingChatLinesStore;
@@ -0,0 +1,34 @@
1
+ import { CliError } from "@wirecat/cli-base";
2
+ import { CHAT_MESSAGES_READ } from "../../meetings/index.js";
3
+ import { checkMeetingCancelled } from "./meeting-operation.js";
4
+ import { positiveReadInteger } from "./meeting-reads.js";
5
+ import { meetingSelectionSql } from "./meeting-selection.js";
6
+ import { read } from "./transaction.js";
7
+ export const meetingChatLinesOver = ({ database }) => ({
8
+ async meetingChatLines(input) {
9
+ checkMeetingCancelled(input.signal);
10
+ positiveReadInteger(input.accountId, "accountId");
11
+ const most = input.maxMessages ?? CHAT_MESSAGES_READ;
12
+ if (!Number.isSafeInteger(most) || most < 1 || most > CHAT_MESSAGES_READ)
13
+ throw new CliError("validation_error", `read 1 through ${CHAT_MESSAGES_READ} chat messages`);
14
+ return read(database, () => {
15
+ const { clauses, params } = meetingSelectionSql(input);
16
+ const rows = database
17
+ .prepare(`SELECT c.meeting_id, m.account_id, m.title, c.sent_at, coalesce(c.sender_name, p.display_name) AS sender, c.text
18
+ FROM meetings m JOIN meeting_chat_messages c ON c.meeting_id = m.id
19
+ LEFT JOIN meeting_participants p ON p.id = c.sender_participant_id
20
+ WHERE ${clauses.join(" AND ")} ORDER BY c.sent_at, c.id LIMIT ?`)
21
+ .all(...params, most + 1);
22
+ if (rows.length > most)
23
+ throw new CliError("validation_error", `the selection has more than ${most} chat messages; narrow it`);
24
+ return rows.map((row) => ({
25
+ meetingId: Number(row.meeting_id),
26
+ accountId: Number(row.account_id),
27
+ title: row.title === null ? null : String(row.title),
28
+ sentAt: Number(row.sent_at),
29
+ sender: row.sender === null ? null : String(row.sender),
30
+ text: String(row.text),
31
+ }));
32
+ });
33
+ },
34
+ });
@@ -0,0 +1,4 @@
1
+ import { type MeetingReviewStore } from "../../meetings/index.js";
2
+ import type { StoreContext } from "./open.js";
3
+ /** Talk and words come from one transcript per meeting, its earliest current one, as in meeting stats. */
4
+ export declare const meetingReviewOver: ({ database }: StoreContext) => MeetingReviewStore;
@@ -0,0 +1,97 @@
1
+ import { CliError } from "@wirecat/cli-base";
2
+ import { REVIEW_LIMIT } from "../../meetings/index.js";
3
+ import { fromJson } from "./events.js";
4
+ import { checkMeetingCancelled } from "./meeting-operation.js";
5
+ import { positiveReadInteger } from "./meeting-reads.js";
6
+ import { meetingSelectionSql } from "./meeting-selection.js";
7
+ import { read } from "./transaction.js";
8
+ const number = (value) => (value === null || value === undefined ? null : Number(value));
9
+ const text = (value) => (value === null || value === undefined ? null : String(value));
10
+ /** Talk and words come from one transcript per meeting, its earliest current one, as in meeting stats. */
11
+ export const meetingReviewOver = ({ database }) => ({
12
+ async meetingReview(input) {
13
+ checkMeetingCancelled(input.signal);
14
+ positiveReadInteger(input.accountId, "accountId");
15
+ const limit = input.limit ?? REVIEW_LIMIT.default;
16
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > REVIEW_LIMIT.most)
17
+ throw new CliError("validation_error", `a review shows 1 through ${REVIEW_LIMIT.most} meetings`);
18
+ return read(database, () => {
19
+ const { clauses, params } = meetingSelectionSql(input);
20
+ const meetings = database
21
+ .prepare(`SELECT m.id, m.account_id, m.title, m.started_at, m.duration_ms, m.participants_count FROM meetings m
22
+ WHERE ${clauses.join(" AND ")} ORDER BY m.started_at DESC, m.id DESC LIMIT ?`)
23
+ .all(...params, limit + 1);
24
+ const shown = meetings.slice(0, limit);
25
+ const items = new Map();
26
+ for (const row of shown)
27
+ items.set(Number(row.id), {
28
+ meetingId: Number(row.id),
29
+ accountId: Number(row.account_id),
30
+ title: text(row.title),
31
+ startedAt: number(row.started_at),
32
+ durationMs: number(row.duration_ms),
33
+ participantsCount: number(row.participants_count),
34
+ projects: [],
35
+ transcript: null,
36
+ speakers: [],
37
+ summary: null,
38
+ });
39
+ if (items.size) {
40
+ const ids = [...items.keys()];
41
+ const marks = ids.map(() => "?").join(",");
42
+ checkMeetingCancelled(input.signal);
43
+ const talk = database
44
+ .prepare(`WITH chosen AS (SELECT min(t.id) AS id, t.meeting_id FROM meeting_transcripts t
45
+ WHERE t.meeting_id IN (${marks}) AND t.deleted_at IS NULL AND t.superseded_at IS NULL GROUP BY t.meeting_id)
46
+ SELECT c.meeting_id, coalesce(person.name, i.name, r.speaker_name, p.display_name) AS speaker,
47
+ count(*) AS lines, sum(max(r.end_ms - r.start_ms, 0)) AS talk_ms,
48
+ sum(length(trim(r.text)) - length(replace(trim(r.text), ' ', '')) + (length(trim(r.text)) > 0)) AS words
49
+ FROM chosen c JOIN meeting_transcript_rows r ON r.meeting_transcript_id = c.id
50
+ LEFT JOIN meeting_participants p ON p.id = r.speaker_participant_id
51
+ LEFT JOIN identities i ON i.id = p.identity_id
52
+ LEFT JOIN identity_links l ON l.identity_id = p.identity_id
53
+ LEFT JOIN persons person ON person.id = l.person_id
54
+ GROUP BY c.meeting_id, speaker ORDER BY c.meeting_id, talk_ms DESC`)
55
+ .all(...ids);
56
+ for (const row of talk) {
57
+ const item = items.get(Number(row.meeting_id));
58
+ item.transcript ??= { lines: 0, words: 0 };
59
+ item.transcript.lines += Number(row.lines);
60
+ item.transcript.words += Number(row.words);
61
+ if (item.speakers.length < 3)
62
+ item.speakers.push({ name: text(row.speaker), talkMs: Number(row.talk_ms) });
63
+ }
64
+ const transcribed = database
65
+ .prepare(`SELECT DISTINCT meeting_id FROM meeting_transcripts
66
+ WHERE meeting_id IN (${marks}) AND deleted_at IS NULL AND superseded_at IS NULL`)
67
+ .all(...ids);
68
+ for (const row of transcribed) {
69
+ const item = items.get(Number(row.meeting_id));
70
+ item.transcript ??= { lines: 0, words: 0 };
71
+ }
72
+ const summaries = database
73
+ .prepare(`SELECT meeting_id, source, title, overview, next_steps, doc_url FROM meeting_summaries
74
+ WHERE meeting_id IN (${marks}) ORDER BY coalesce(external_updated_at, updated_at) DESC, id DESC`)
75
+ .all(...ids);
76
+ for (const row of summaries) {
77
+ const item = items.get(Number(row.meeting_id));
78
+ item.summary ??= {
79
+ source: String(row.source),
80
+ title: text(row.title),
81
+ overview: text(row.overview),
82
+ nextSteps: fromJson(row.next_steps) ?? [],
83
+ url: text(row.doc_url),
84
+ };
85
+ }
86
+ const projects = database
87
+ .prepare(`SELECT l.from_id, p.name FROM links l JOIN projects p ON p.id = l.to_id
88
+ WHERE l.from_type = 'meeting' AND l.from_id IN (${marks}) AND l.to_type = 'project' AND l.kind = 'member-of'
89
+ AND l.anchor IS NULL AND l.confirmed = 1 AND p.account_id IS NULL AND p.deleted_at IS NULL ORDER BY p.name`)
90
+ .all(...ids);
91
+ for (const row of projects)
92
+ items.get(Number(row.from_id)).projects.push(String(row.name));
93
+ }
94
+ return { items: [...items.values()], hasMore: meetings.length > limit };
95
+ });
96
+ },
97
+ });
@@ -1,4 +1,4 @@
1
- import type { Attachment, MeetingContextStore, MeetingParticipantSearchStore, MeetingPreparationContextStore, MeetingPreparationKnowledgeStore, MeetingPreparationNotesStore, MeetingRelatedHistoryStore, MeetingSearchIndexStore, MeetingSearchStore, MeetingSeriesHistoryStore, MeetingStatsStore, MeetingStore, MeetingTranscriptReceiptStore, MeetingTranscriptStore, NewRecord } from "../../meetings/index.js";
1
+ import type { Attachment, MeetingChatLinesStore, MeetingContextStore, MeetingParticipantSearchStore, MeetingPreparationContextStore, MeetingPreparationKnowledgeStore, MeetingPreparationNotesStore, MeetingRelatedHistoryStore, MeetingReviewStore, MeetingSearchIndexStore, MeetingSearchStore, MeetingSeriesHistoryStore, MeetingStatsStore, MeetingStore, MeetingTranscriptReceiptStore, MeetingTranscriptStore, NewRecord } from "../../meetings/index.js";
2
2
  import type { CacheDatabase } from "../driver.js";
3
3
  import { type MeetingContextReadCapabilities, type MeetingReadCapabilities } from "./meeting-reads.js";
4
4
  import type { StoreContext } from "./open.js";
@@ -11,7 +11,7 @@ export declare const saveAttachment: (database: CacheDatabase, type: string, par
11
11
  export declare const drainMeetingIndex: (database: CacheDatabase, batch?: number) => number;
12
12
  /** Each word of the query, as a prefix, all required. */
13
13
  export declare const wordsQuery: (query: string) => string | null;
14
- export type StoredMeetings = MeetingStore & MeetingPreparationContextStore & MeetingPreparationKnowledgeStore & MeetingPreparationNotesStore & MeetingRelatedHistoryStore & MeetingSeriesHistoryStore & MeetingTranscriptStore & MeetingReadCapabilities & MeetingContextReadCapabilities & MeetingParticipantSearchStore & MeetingSearchStore & MeetingTranscriptReceiptStore & MeetingContextStore & MeetingSearchIndexStore & MeetingStatsStore;
14
+ export type StoredMeetings = MeetingStore & MeetingPreparationContextStore & MeetingPreparationKnowledgeStore & MeetingPreparationNotesStore & MeetingRelatedHistoryStore & MeetingSeriesHistoryStore & MeetingTranscriptStore & MeetingReadCapabilities & MeetingContextReadCapabilities & MeetingParticipantSearchStore & MeetingSearchStore & MeetingTranscriptReceiptStore & MeetingContextStore & MeetingSearchIndexStore & MeetingStatsStore & MeetingReviewStore & MeetingChatLinesStore;
15
15
  /** The `MeetingStore` of `src/meetings/` over the shared store. */
16
16
  export declare const meetingStoreOver: (context: StoreContext) => StoredMeetings;
17
17
  export {};
@@ -5,6 +5,7 @@ import * as eventQueries from "./events.js";
5
5
  import { fromJson, int, page, str, toJson } from "./events.js";
6
6
  import { folded } from "./folded.js";
7
7
  import { meetingIdentityPk } from "./identities.js";
8
+ import { meetingChatLinesOver } from "./meeting-chat-lines.js";
8
9
  import { meetingContextLinksOver } from "./meeting-context-links.js";
9
10
  import { meetingHitIndexOver } from "./meeting-hit-index.js";
10
11
  import { meetingHitSearchOver } from "./meeting-hit-search.js";
@@ -15,6 +16,7 @@ import { meetingPreparationKnowledgeOver } from "./meeting-preparation-knowledge
15
16
  import { meetingPreparationNotesOver } from "./meeting-preparation-notes.js";
16
17
  import { meetingReadsOver } from "./meeting-reads.js";
17
18
  import { meetingRelatedHistoryOver } from "./meeting-related-history.js";
19
+ import { meetingReviewOver } from "./meeting-review.js";
18
20
  import { meetingSeriesHistoryOver } from "./meeting-series-history.js";
19
21
  import { meetingStatsOver } from "./meeting-stats.js";
20
22
  import { invalidateMeetingStems } from "./meeting-stem-state.js";
@@ -573,6 +575,8 @@ export const meetingStoreOver = (context) => {
573
575
  ...meetingRelatedHistoryOver(context),
574
576
  ...meetingHitSearchOver(context),
575
577
  ...meetingStatsOver(context),
578
+ ...meetingReviewOver(context),
579
+ ...meetingChatLinesOver(context),
576
580
  ...meetingParticipantSearchOver(context),
577
581
  ...meetingHitIndexOver(context),
578
582
  ...meetingContextLinksOver(context),
@@ -1,6 +1,7 @@
1
1
  import { findMentions, MENTION_KINDS, MENTION_VERSIONS, oneTrackedLinkPerSite, telegramTarget, } from "../../domain/mentions.js";
2
2
  import { findNames, findUnknownNames, nameIndex } from "../../domain/person-names.js";
3
3
  import { createStemmer } from "../../search/stem.js";
4
+ import { speech } from "./lucene-fields.js";
4
5
  import { storePhoneCountries } from "./phone-countries.js";
5
6
  import { placeOf } from "./place-key.js";
6
7
  /** Mentions a store write finds before it commits: an older binary may have queued thousands. */
@@ -98,10 +99,10 @@ export const SOURCES = {
98
99
  attachment: {
99
100
  count: "SELECT count(*) AS n FROM attachments a JOIN messages m ON m.id = a.attachable_id " +
100
101
  "WHERE a.attachable_type = 'message' AND a.text IS NOT NULL AND a.text <> '' AND m.deleted_at IS NULL",
101
- page: "SELECT a.id, a.text, CASE WHEN coalesce(a.extractor, '') LIKE 'speech:%' THEN 'transcript' ELSE 'file_text' END AS field, " +
102
+ page: `SELECT a.id, a.text, CASE WHEN ${speech("a")} THEN 'transcript' ELSE 'file_text' END AS field, ` +
102
103
  "m.chat_id FROM attachments a JOIN messages m ON m.id = a.attachable_id WHERE a.id > ? AND a.attachable_type = 'message' " +
103
104
  "AND a.text IS NOT NULL AND a.text <> '' AND m.deleted_at IS NULL ORDER BY a.id LIMIT ?",
104
- one: "SELECT a.text, CASE WHEN coalesce(a.extractor, '') LIKE 'speech:%' THEN 'transcript' ELSE 'file_text' END AS field, " +
105
+ one: `SELECT a.text, CASE WHEN ${speech("a")} THEN 'transcript' ELSE 'file_text' END AS field, ` +
105
106
  "m.chat_id FROM attachments a JOIN messages m ON m.id = a.attachable_id WHERE a.id = ? AND a.attachable_type = 'message' " +
106
107
  "AND a.text IS NOT NULL AND m.deleted_at IS NULL",
107
108
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wirecat/cli-core",
3
- "version": "0.390.0",
3
+ "version": "0.392.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",