@wirecat/cli-core 0.492.0 → 0.493.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.
- package/CHANGELOG.md +33 -0
- package/dist/cli/messenger/messages-command.js +13 -1
- package/dist/cli/messenger/search-answers.d.ts +6 -2
- package/dist/cli/messenger/search-answers.js +43 -6
- package/dist/cli/messenger/tasks-command.js +15 -3
- package/dist/cli/steps.js +1 -0
- package/dist/entities/agenda.d.ts +42 -0
- package/dist/entities/agenda.js +10 -0
- package/dist/entities/index.d.ts +36 -0
- package/dist/services/agenda.d.ts +5 -0
- package/dist/services/agenda.js +25 -4
- package/dist/services/chat-coverage.d.ts +19 -0
- package/dist/services/chat-coverage.js +27 -0
- package/dist/services/search-all.d.ts +3 -0
- package/dist/services/search-all.js +4 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,39 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.493.0 — 31.10.2026
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`chatCoverage(store, account, chatIds)` says what the store holds of each chat.** Per chat, in the order asked:
|
|
15
|
+
`completeness` (`complete`, `partial` or `unknown`, up to date, gaps, start reached, last fetch) and `held` (messages,
|
|
16
|
+
oldest, newest), never text. It reads only the chats named, so a store of thousands of chats costs what the ids cost.
|
|
17
|
+
`coverageGaps` also takes a list of these rows and answers `missing` (`field: "history"`) and `next`: for the first
|
|
18
|
+
incomplete chat, `messages sync <chat> --since-time <start of the window>` when the answer has a window, then
|
|
19
|
+
`messages sync <chat>`. The step `messages.sync` takes `since_time`.
|
|
20
|
+
- **`messages list --offline` says when the store holds only part of the chat.** A chat that is `partial` or was never
|
|
21
|
+
fetched gives `meta.complete: false`, exit code 0, `missing` and `next`, as an offline read of 262 of 777 messages
|
|
22
|
+
must not pass for the whole chat. Online reads are unchanged.
|
|
23
|
+
- **`tasks agenda` says how much of each listed chat the store holds.** Each row of `owed`, `waiting` and `unsynced` has
|
|
24
|
+
`completeness` and `held`; a chat held only in part is marked `[partial]` or `[never fetched]` in the text, with one
|
|
25
|
+
line above the lists. The answer then has `meta.complete: false`, `missing` and `next` (bounded sync over the
|
|
26
|
+
`--within` window, then the whole chat).
|
|
27
|
+
- **`search all` names the chat to sync** when the store holds the chats its messages were searched in only in part:
|
|
28
|
+
`missing` (`field: "coverage"`) and `next`, from the same coverage `search messages` reports. `SearchAllFound` gains
|
|
29
|
+
`coverage`.
|
|
30
|
+
|
|
31
|
+
### Changed — may break callers
|
|
32
|
+
|
|
33
|
+
- **`tasks agenda --json` rows gain `completeness` and `held`**, and the answer may carry `meta.complete: false` with
|
|
34
|
+
`missing` and `next`; a caller that stops on anything but `meta.complete: true` now sees a store that holds chats in
|
|
35
|
+
part as incomplete. `tasks agenda` is declared `partial`, so the exit code stays 0. `AgendaChat` gains `coverage`
|
|
36
|
+
and `Agenda` gains `since`.
|
|
37
|
+
- **`search messages`, `search mail` and `search all` answer `meta.complete: false` when a chat searched is partial or
|
|
38
|
+
never fetched**, where they kept `true` and put the gap only in `missing` and `next`. Exit code stays 0 (the commands
|
|
39
|
+
are declared `partial`). A caller that read `meta.complete` as "no source failed" must read `missing` for the reason.
|
|
40
|
+
- **`messages list --offline --json` may carry `meta.complete: false`, `missing` and `next`** for a chat held in part.
|
|
41
|
+
`messages list` is declared `partial`: the exit code stays 0.
|
|
42
|
+
|
|
10
43
|
## 0.492.0 — 31.10.2026
|
|
11
44
|
|
|
12
45
|
### Added
|
|
@@ -5,7 +5,9 @@ import { isLocator, parseLocator } from "../../domain/locator.js";
|
|
|
5
5
|
import { SAVE_HELP } from "../../meetings/index.js";
|
|
6
6
|
import { renderMessages } from "../../render/messages.js";
|
|
7
7
|
import { renderThreadLinks } from "../../render/thread-context.js";
|
|
8
|
+
import { chatCoverage } from "../../services/chat-coverage.js";
|
|
8
9
|
import { readMail } from "../../services/mail-search.js";
|
|
10
|
+
import { storedChatId } from "../../services/message-resolution.js";
|
|
9
11
|
import { modelWith, spoken } from "../../speech/hearing.js";
|
|
10
12
|
import { REMOTE_READ } from "../effects.js";
|
|
11
13
|
import { itemOf, listOf, renderAnswer } from "../envelope.js";
|
|
@@ -30,6 +32,7 @@ import { pinCommand, starCommands, unpinCommand } from "./messages-pin-command.j
|
|
|
30
32
|
import { pressCommand } from "./messages-press-command.js";
|
|
31
33
|
import { scheduledCommand } from "./messages-scheduled-command.js";
|
|
32
34
|
import { sendCommand } from "./messages-send-command.js";
|
|
35
|
+
import { coverageGaps } from "./search-answers.js";
|
|
33
36
|
import { threadOptions, threadRequest } from "./thread-options.js";
|
|
34
37
|
import { transcribeSubcommand } from "./transcribe-command.js";
|
|
35
38
|
/** `messages`: reading, and sending through the guard. A CLI may add its own subcommands. */
|
|
@@ -40,6 +43,7 @@ export const messagesCommand = (messenger) => {
|
|
|
40
43
|
const list = annotate(messages.command("list"), {
|
|
41
44
|
examples: [`${messenger.app.command} personal messages list "Book club" --limit 20`],
|
|
42
45
|
effects: REMOTE_READ,
|
|
46
|
+
partial: true,
|
|
43
47
|
})
|
|
44
48
|
.description("a chat's messages, oldest to newest")
|
|
45
49
|
.addHelpText("after", SAVE_HELP)
|
|
@@ -68,6 +72,12 @@ export const messagesCommand = (messenger) => {
|
|
|
68
72
|
const next = page.hasMore
|
|
69
73
|
? cursorOf(window, { first: page.items[0]?.id, last: page.items.at(-1)?.id })
|
|
70
74
|
: undefined;
|
|
75
|
+
// Offline, the store is the whole answer: say how much of the chat it holds. Online the messenger is read.
|
|
76
|
+
const gaps = context.settings.offline
|
|
77
|
+
? coverageGaps(await context.withStore(async (store, account) => chatCoverage(store, account, [
|
|
78
|
+
await storedChatId(messenger, chat, store, account, { includeLeft: true }),
|
|
79
|
+
])))
|
|
80
|
+
: { next: [], missing: [] };
|
|
71
81
|
renderAnswer(this, context, listOf("messages list", {
|
|
72
82
|
items: messageItems(heardItems(page.items, hearing), reader),
|
|
73
83
|
limit,
|
|
@@ -75,7 +85,9 @@ export const messagesCommand = (messenger) => {
|
|
|
75
85
|
paging: "cursor",
|
|
76
86
|
...(next === undefined ? {} : { cursor: next }),
|
|
77
87
|
args: { chat, ...(query.length > 0 ? { query: query.join(" ") } : {}) },
|
|
78
|
-
|
|
88
|
+
next: gaps.next,
|
|
89
|
+
missing: [...hearingMissing(hearing, transcribe === true), ...gaps.missing],
|
|
90
|
+
...(gaps.missing.length > 0 ? { meta: { complete: false } } : {}),
|
|
79
91
|
}), () => renderMessages(spokenItems(page.items, hearing), viewOf(context, messenger)));
|
|
80
92
|
});
|
|
81
93
|
annotate(list, { output: { entity: "message" }, query: history });
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Missing, NextStep } from "@wirecat/cli-base/standards";
|
|
2
2
|
import type { InferOutput } from "valibot";
|
|
3
3
|
import type { ENTITIES } from "../../entities/index.js";
|
|
4
|
+
import type { ChatCoverage } from "../../services/chat-coverage.js";
|
|
4
5
|
import type { FoundMessage, SearchFound } from "../../services/messages.js";
|
|
5
6
|
import type { MeaningSkipped } from "../../services/messages-discovery.js";
|
|
6
7
|
import type { FoundNote, NotesFoundOf } from "../../services/notes-search.js";
|
|
@@ -16,8 +17,11 @@ export type HitItem = InferOutput<(typeof ENTITIES)["hit"]>;
|
|
|
16
17
|
/** The page a `--page` asks for, as the offset the search services take. */
|
|
17
18
|
export declare const offsetOf: (page: number | undefined, limit: number) => number | undefined;
|
|
18
19
|
export declare const messageHitItem: (hit: FoundMessage, query?: string) => MessageHitItem;
|
|
19
|
-
/**
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* What the archive lacked for this answer, and the step that would fill it. From a search's coverage, or from the
|
|
22
|
+
* chats an answer is about (`since` is the start of the window it was asked for).
|
|
23
|
+
*/
|
|
24
|
+
export declare const coverageGaps: (found: Pick<SearchFound, "coverage" | "refreshed"> | ChatCoverage[], shown?: number, since?: string) => {
|
|
21
25
|
next: NextStep[];
|
|
22
26
|
missing: Missing[];
|
|
23
27
|
};
|
|
@@ -88,15 +88,49 @@ export const messageHitItem = (hit, query = "") => {
|
|
|
88
88
|
};
|
|
89
89
|
const accountName = ({ provider, account }) => `${provider}:${account}`;
|
|
90
90
|
/** A source left out is an issue by its code; the sentence saying why goes to a person as a note, never here. */
|
|
91
|
-
const metaOf = (accounts, skipped = []) => ({
|
|
91
|
+
const metaOf = (accounts, skipped = [], heldInPart = false) => ({
|
|
92
92
|
accounts: accounts.map(accountName),
|
|
93
|
-
complete: skipped.length === 0,
|
|
93
|
+
complete: skipped.length === 0 && !heldInPart,
|
|
94
94
|
...(skipped.length
|
|
95
95
|
? { issues: skipped.map(({ source, resource, code }) => ({ code, source: source ?? resource ?? "" })) }
|
|
96
96
|
: {}),
|
|
97
97
|
});
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
const day = (at) => at?.slice(0, 10) ?? "no date";
|
|
99
|
+
/**
|
|
100
|
+
* The steps for chats the store holds only in part. Bounded first, when the answer's window is known, then the whole
|
|
101
|
+
* chat; both name the first chat only, because a download is the owner's call. Counts and dates only, never text.
|
|
102
|
+
*/
|
|
103
|
+
const chatGaps = (rows, since) => {
|
|
104
|
+
const open = rows.filter(({ completeness }) => completeness.state !== "complete");
|
|
105
|
+
const [first] = open;
|
|
106
|
+
if (!first)
|
|
107
|
+
return { next: [], missing: [] };
|
|
108
|
+
const held = first.completeness.state === "unknown" ? "never fetched" : `held from ${day(first.held.oldestAt)} only`;
|
|
109
|
+
const steps = (args, why) => ({
|
|
110
|
+
...nextStep("messages.sync", { args: { chat: first.chatId, ...args } }),
|
|
111
|
+
why,
|
|
112
|
+
});
|
|
113
|
+
return {
|
|
114
|
+
next: [
|
|
115
|
+
...(since === undefined ? [] : [steps({ since_time: since }, `${held}; this reads back to ${day(since)}`)]),
|
|
116
|
+
steps({}, "reads the chat back to its first message; a big group can take minutes"),
|
|
117
|
+
],
|
|
118
|
+
missing: [
|
|
119
|
+
{
|
|
120
|
+
field: "history",
|
|
121
|
+
why: `${open.length} of ${rows.length} chats hold only part of their history; the first is ${held}`,
|
|
122
|
+
step: "messages.sync",
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* What the archive lacked for this answer, and the step that would fill it. From a search's coverage, or from the
|
|
129
|
+
* chats an answer is about (`since` is the start of the window it was asked for).
|
|
130
|
+
*/
|
|
131
|
+
export const coverageGaps = (found, shown = 0, since) => {
|
|
132
|
+
if (Array.isArray(found))
|
|
133
|
+
return chatGaps(found, since);
|
|
100
134
|
const coverage = found.coverage;
|
|
101
135
|
const stale = found.refreshed?.complete === false;
|
|
102
136
|
if (!coverage?.next && !stale)
|
|
@@ -203,7 +237,7 @@ export const messagesAnswer = (command, found, { limit, page, again }, query = "
|
|
|
203
237
|
page: page ?? 1,
|
|
204
238
|
...(again ? { args: again } : {}),
|
|
205
239
|
...gapsOf(found),
|
|
206
|
-
meta: metaOf(accounts, found.skipped),
|
|
240
|
+
meta: metaOf(accounts, found.skipped, coverageGaps(found).missing.length > 0),
|
|
207
241
|
});
|
|
208
242
|
};
|
|
209
243
|
/**
|
|
@@ -272,6 +306,7 @@ export const allAnswer = (command, found, { limit, page, again }, exact = false)
|
|
|
272
306
|
const scan = found.meetings?.complete === false ? found.meetings : undefined;
|
|
273
307
|
const notes = meaningGap(found.notes?.meaningSkipped, exact);
|
|
274
308
|
const messages = messageMeaningGap(found.messages?.meaningSkipped, "found_by");
|
|
309
|
+
const archive = coverageGaps({ coverage: found.coverage }, found.items.length);
|
|
275
310
|
return listOf(command, {
|
|
276
311
|
items: found.items.map(allHitItem),
|
|
277
312
|
limit,
|
|
@@ -290,6 +325,7 @@ export const allAnswer = (command, found, { limit, page, again }, exact = false)
|
|
|
290
325
|
: []),
|
|
291
326
|
...notes.next,
|
|
292
327
|
...messages.next.filter((step) => !notes.next.some((asked) => JSON.stringify(asked) === JSON.stringify(step))),
|
|
328
|
+
...archive.next,
|
|
293
329
|
],
|
|
294
330
|
missing: [
|
|
295
331
|
...(scan
|
|
@@ -303,7 +339,8 @@ export const allAnswer = (command, found, { limit, page, again }, exact = false)
|
|
|
303
339
|
: []),
|
|
304
340
|
...notes.missing,
|
|
305
341
|
...messages.missing,
|
|
342
|
+
...archive.missing,
|
|
306
343
|
],
|
|
307
|
-
meta: metaOf(found.accounts, found.skipped),
|
|
344
|
+
meta: metaOf(found.accounts, found.skipped, archive.missing.length > 0),
|
|
308
345
|
});
|
|
309
346
|
};
|
|
@@ -15,6 +15,7 @@ import { itemOf, listOf, renderAnswer, snakeKeys } from "../envelope.js";
|
|
|
15
15
|
import { listQuery, pagedList, pageOf, queryTerms, typedQuery, withQuery } from "../list-query.js";
|
|
16
16
|
import { positiveCount } from "../paging.js";
|
|
17
17
|
import { messengerContext, refuseLocalWrite } from "./context.js";
|
|
18
|
+
import { coverageGaps } from "./search-answers.js";
|
|
18
19
|
import { refuseJsonl, taskStatsAnswer } from "./stats-answers.js";
|
|
19
20
|
const line = ({ id, key, state, kind, group, createdAt, message, note }) => [
|
|
20
21
|
key ?? id.slice(0, 8),
|
|
@@ -50,10 +51,16 @@ const applyText = (pass, yes, tool) => yes
|
|
|
50
51
|
...pass.tasks.map((task) => ` ${task.kind.padEnd(8)} ${(task.category ?? "—").padEnd(13)} ${task.source}`),
|
|
51
52
|
...(pass.tasks.length > 0 ? [` ${tool} tasks scan ${pass.chat} --yes opens them`] : []),
|
|
52
53
|
].join("\n");
|
|
54
|
+
const agendaChats = (agenda) => [...agenda.unsynced, ...agenda.owed, ...agenda.waiting];
|
|
55
|
+
const agendaGaps = (agenda) => coverageGaps(agendaChats(agenda).map(({ coverage }) => coverage), 0, agenda.since);
|
|
56
|
+
const STATE_MARK = { complete: "", partial: " [partial]", unknown: " [never fetched]" };
|
|
53
57
|
const agendaText = (agenda) => {
|
|
54
58
|
const chat = (one) => ` ${when(one.lastMessageAt)} ${one.title ?? one.chatId} ${one.lastMessage ?? one.chatId}` +
|
|
55
|
-
(one.openTasks.length > 0 ? ` (${one.openTasks.length} open)` : "")
|
|
59
|
+
(one.openTasks.length > 0 ? ` (${one.openTasks.length} open)` : "") +
|
|
60
|
+
STATE_MARK[one.coverage.completeness.state];
|
|
61
|
+
const [history] = agendaGaps(agenda).missing;
|
|
56
62
|
return [
|
|
63
|
+
...(history ? [`${history.why}: sync before trusting what is listed`] : []),
|
|
57
64
|
...(agenda.stale
|
|
58
65
|
? [`the store saved nothing since ${when(agenda.storedAt)}: sync before trusting quiet chats`]
|
|
59
66
|
: []),
|
|
@@ -356,7 +363,11 @@ export const tasksCommand = (messenger) => {
|
|
|
356
363
|
const unmerged = await context.withServices((services) => services.tasks.unmerge(dup, writerOf(this)));
|
|
357
364
|
renderAnswer(this, context, itemOf(snakeKeys(unmerged)), () => `${card(unmerged.task)}\n`);
|
|
358
365
|
});
|
|
359
|
-
withQuery(annotate(tasks.command("agenda"), {
|
|
366
|
+
withQuery(annotate(tasks.command("agenda"), {
|
|
367
|
+
effects: LOCAL_READ,
|
|
368
|
+
partial: true,
|
|
369
|
+
examples: [`${tool} personal tasks agenda`],
|
|
370
|
+
})
|
|
360
371
|
.summary("what needs a look today: tasks due, chats you owe or wait on, what you are owed")
|
|
361
372
|
.description("what needs a look today, from the local store and without message text: tasks overdue, due or to check, " +
|
|
362
373
|
"chats where you owe an answer, chats where you wait for one, open tasks someone else does"), AGENDA_QUERY)
|
|
@@ -377,7 +388,8 @@ export const tasksCommand = (messenger) => {
|
|
|
377
388
|
...(query ? { query } : {}),
|
|
378
389
|
holder: holderOf(this),
|
|
379
390
|
}));
|
|
380
|
-
|
|
391
|
+
const { next, missing } = agendaGaps(agenda);
|
|
392
|
+
renderAnswer(this, context, itemOf(agendaItem(agenda), { next, missing, ...(missing.length > 0 ? { meta: { complete: false } } : {}) }), () => `${agendaText(agenda)}\n`);
|
|
381
393
|
});
|
|
382
394
|
annotate(tasks.command("categories"), {
|
|
383
395
|
output: { entity: "task_categories" },
|
package/dist/cli/steps.js
CHANGED
|
@@ -14,6 +14,7 @@ export const STEPS = defineSteps([
|
|
|
14
14
|
args: [
|
|
15
15
|
{ name: "chat", required: false },
|
|
16
16
|
{ name: "all", required: false },
|
|
17
|
+
{ name: "since_time", required: false },
|
|
17
18
|
{ name: "background", required: false },
|
|
18
19
|
],
|
|
19
20
|
why: "the local copy is missing messages; syncing brings them in",
|
|
@@ -42,6 +42,20 @@ export declare const agenda: v.StrictObjectSchema<{
|
|
|
42
42
|
readonly last_from_owner: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
|
|
43
43
|
/** Open tasks whose source is in this chat. */
|
|
44
44
|
readonly open_tasks: v.ArraySchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>, undefined>;
|
|
45
|
+
/** `unknown` means nothing ever fetched the chat's history; `partial` that the store holds only part of it. */
|
|
46
|
+
readonly completeness: v.StrictObjectSchema<{
|
|
47
|
+
readonly state: v.PicklistSchema<["complete", "partial", "unknown"], undefined>;
|
|
48
|
+
readonly up_to_date: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
|
|
49
|
+
readonly gaps: v.BooleanSchema<undefined>;
|
|
50
|
+
readonly reaches_start: v.BooleanSchema<undefined>;
|
|
51
|
+
readonly fetched_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
52
|
+
}, undefined>;
|
|
53
|
+
/** What the store holds of the chat: the count and the span, never the text. */
|
|
54
|
+
readonly held: v.StrictObjectSchema<{
|
|
55
|
+
readonly messages: v.NumberSchema<undefined>;
|
|
56
|
+
readonly oldest_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
57
|
+
readonly newest_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
58
|
+
}, undefined>;
|
|
45
59
|
}, undefined>, undefined>;
|
|
46
60
|
/** The owner wrote last, and nobody answered. */
|
|
47
61
|
readonly waiting: v.ArraySchema<v.StrictObjectSchema<{
|
|
@@ -55,6 +69,20 @@ export declare const agenda: v.StrictObjectSchema<{
|
|
|
55
69
|
readonly last_from_owner: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
|
|
56
70
|
/** Open tasks whose source is in this chat. */
|
|
57
71
|
readonly open_tasks: v.ArraySchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>, undefined>;
|
|
72
|
+
/** `unknown` means nothing ever fetched the chat's history; `partial` that the store holds only part of it. */
|
|
73
|
+
readonly completeness: v.StrictObjectSchema<{
|
|
74
|
+
readonly state: v.PicklistSchema<["complete", "partial", "unknown"], undefined>;
|
|
75
|
+
readonly up_to_date: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
|
|
76
|
+
readonly gaps: v.BooleanSchema<undefined>;
|
|
77
|
+
readonly reaches_start: v.BooleanSchema<undefined>;
|
|
78
|
+
readonly fetched_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
79
|
+
}, undefined>;
|
|
80
|
+
/** What the store holds of the chat: the count and the span, never the text. */
|
|
81
|
+
readonly held: v.StrictObjectSchema<{
|
|
82
|
+
readonly messages: v.NumberSchema<undefined>;
|
|
83
|
+
readonly oldest_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
84
|
+
readonly newest_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
85
|
+
}, undefined>;
|
|
58
86
|
}, undefined>, undefined>;
|
|
59
87
|
/** The chat list says these moved and their newest message is not stored: read them first. */
|
|
60
88
|
readonly unsynced: v.ArraySchema<v.StrictObjectSchema<{
|
|
@@ -68,6 +96,20 @@ export declare const agenda: v.StrictObjectSchema<{
|
|
|
68
96
|
readonly last_from_owner: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
|
|
69
97
|
/** Open tasks whose source is in this chat. */
|
|
70
98
|
readonly open_tasks: v.ArraySchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>, undefined>;
|
|
99
|
+
/** `unknown` means nothing ever fetched the chat's history; `partial` that the store holds only part of it. */
|
|
100
|
+
readonly completeness: v.StrictObjectSchema<{
|
|
101
|
+
readonly state: v.PicklistSchema<["complete", "partial", "unknown"], undefined>;
|
|
102
|
+
readonly up_to_date: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
|
|
103
|
+
readonly gaps: v.BooleanSchema<undefined>;
|
|
104
|
+
readonly reaches_start: v.BooleanSchema<undefined>;
|
|
105
|
+
readonly fetched_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
106
|
+
}, undefined>;
|
|
107
|
+
/** What the store holds of the chat: the count and the span, never the text. */
|
|
108
|
+
readonly held: v.StrictObjectSchema<{
|
|
109
|
+
readonly messages: v.NumberSchema<undefined>;
|
|
110
|
+
readonly oldest_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
111
|
+
readonly newest_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
112
|
+
}, undefined>;
|
|
71
113
|
}, undefined>, undefined>;
|
|
72
114
|
/** Open tasks someone else does: what they owe the owner, oldest first. */
|
|
73
115
|
readonly assigned: v.ArraySchema<v.StrictObjectSchema<{
|
package/dist/entities/agenda.js
CHANGED
|
@@ -13,6 +13,16 @@ const agendaChat = v.strictObject({
|
|
|
13
13
|
last_from_owner: v.nullable(v.boolean()),
|
|
14
14
|
/** Open tasks whose source is in this chat. */
|
|
15
15
|
open_tasks: v.array(id),
|
|
16
|
+
/** `unknown` means nothing ever fetched the chat's history; `partial` that the store holds only part of it. */
|
|
17
|
+
completeness: v.strictObject({
|
|
18
|
+
state: v.picklist(["complete", "partial", "unknown"]),
|
|
19
|
+
up_to_date: v.nullable(v.boolean()),
|
|
20
|
+
gaps: v.boolean(),
|
|
21
|
+
reaches_start: v.boolean(),
|
|
22
|
+
fetched_at: v.nullable(time),
|
|
23
|
+
}),
|
|
24
|
+
/** What the store holds of the chat: the count and the span, never the text. */
|
|
25
|
+
held: v.strictObject({ messages: v.number(), oldest_at: v.nullable(time), newest_at: v.nullable(time) }),
|
|
16
26
|
});
|
|
17
27
|
/** What needs a look today, from the store alone and without one message's text. */
|
|
18
28
|
export const agenda = v.strictObject({
|
package/dist/entities/index.d.ts
CHANGED
|
@@ -9614,6 +9614,18 @@ export declare const ENTITIES: {
|
|
|
9614
9614
|
readonly last_message_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
|
|
9615
9615
|
readonly last_from_owner: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
|
|
9616
9616
|
readonly open_tasks: v.ArraySchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>, undefined>;
|
|
9617
|
+
readonly completeness: v.StrictObjectSchema<{
|
|
9618
|
+
readonly state: v.PicklistSchema<["complete", "partial", "unknown"], undefined>;
|
|
9619
|
+
readonly up_to_date: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
|
|
9620
|
+
readonly gaps: v.BooleanSchema<undefined>;
|
|
9621
|
+
readonly reaches_start: v.BooleanSchema<undefined>;
|
|
9622
|
+
readonly fetched_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
9623
|
+
}, undefined>;
|
|
9624
|
+
readonly held: v.StrictObjectSchema<{
|
|
9625
|
+
readonly messages: v.NumberSchema<undefined>;
|
|
9626
|
+
readonly oldest_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
9627
|
+
readonly newest_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
9628
|
+
}, undefined>;
|
|
9617
9629
|
}, undefined>, undefined>;
|
|
9618
9630
|
readonly waiting: v.ArraySchema<v.StrictObjectSchema<{
|
|
9619
9631
|
readonly chat: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
@@ -9623,6 +9635,18 @@ export declare const ENTITIES: {
|
|
|
9623
9635
|
readonly last_message_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
|
|
9624
9636
|
readonly last_from_owner: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
|
|
9625
9637
|
readonly open_tasks: v.ArraySchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>, undefined>;
|
|
9638
|
+
readonly completeness: v.StrictObjectSchema<{
|
|
9639
|
+
readonly state: v.PicklistSchema<["complete", "partial", "unknown"], undefined>;
|
|
9640
|
+
readonly up_to_date: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
|
|
9641
|
+
readonly gaps: v.BooleanSchema<undefined>;
|
|
9642
|
+
readonly reaches_start: v.BooleanSchema<undefined>;
|
|
9643
|
+
readonly fetched_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
9644
|
+
}, undefined>;
|
|
9645
|
+
readonly held: v.StrictObjectSchema<{
|
|
9646
|
+
readonly messages: v.NumberSchema<undefined>;
|
|
9647
|
+
readonly oldest_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
9648
|
+
readonly newest_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
9649
|
+
}, undefined>;
|
|
9626
9650
|
}, undefined>, undefined>;
|
|
9627
9651
|
readonly unsynced: v.ArraySchema<v.StrictObjectSchema<{
|
|
9628
9652
|
readonly chat: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
@@ -9632,6 +9656,18 @@ export declare const ENTITIES: {
|
|
|
9632
9656
|
readonly last_message_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
|
|
9633
9657
|
readonly last_from_owner: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
|
|
9634
9658
|
readonly open_tasks: v.ArraySchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>, undefined>;
|
|
9659
|
+
readonly completeness: v.StrictObjectSchema<{
|
|
9660
|
+
readonly state: v.PicklistSchema<["complete", "partial", "unknown"], undefined>;
|
|
9661
|
+
readonly up_to_date: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
|
|
9662
|
+
readonly gaps: v.BooleanSchema<undefined>;
|
|
9663
|
+
readonly reaches_start: v.BooleanSchema<undefined>;
|
|
9664
|
+
readonly fetched_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
9665
|
+
}, undefined>;
|
|
9666
|
+
readonly held: v.StrictObjectSchema<{
|
|
9667
|
+
readonly messages: v.NumberSchema<undefined>;
|
|
9668
|
+
readonly oldest_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
9669
|
+
readonly newest_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
9670
|
+
}, undefined>;
|
|
9635
9671
|
}, undefined>, undefined>;
|
|
9636
9672
|
readonly assigned: v.ArraySchema<v.StrictObjectSchema<{
|
|
9637
9673
|
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
@@ -2,6 +2,7 @@ import { type QueryAst } from "@wirecat/cli-base/query";
|
|
|
2
2
|
import type * as v from "valibot";
|
|
3
3
|
import { agenda } from "../entities/agenda.js";
|
|
4
4
|
import { type ChatActivity, type TaskDetails } from "../store/store.js";
|
|
5
|
+
import { type ChatCoverage } from "./chat-coverage.js";
|
|
5
6
|
import type { ServiceDeps } from "./deps.js";
|
|
6
7
|
export interface AgendaOptions {
|
|
7
8
|
/** ms: a task due within this is listed. */
|
|
@@ -33,6 +34,8 @@ export interface AgendaAssigned extends Pick<TaskDetails, "id" | "key" | "title"
|
|
|
33
34
|
assignees: string[];
|
|
34
35
|
}
|
|
35
36
|
export interface AgendaChat extends Omit<ChatActivity, "lastMessageId"> {
|
|
37
|
+
/** How much of the chat's history the store holds: a chat held from last week alone is not a quiet chat. */
|
|
38
|
+
coverage: ChatCoverage;
|
|
36
39
|
/** The newest message, as a locator to read around; never its text. `null` while it is not stored. */
|
|
37
40
|
lastMessage: string | null;
|
|
38
41
|
/** Open tasks whose source is in this chat. */
|
|
@@ -45,6 +48,8 @@ export interface AgendaChat extends Omit<ChatActivity, "lastMessageId"> {
|
|
|
45
48
|
*/
|
|
46
49
|
export interface Agenda {
|
|
47
50
|
at: string;
|
|
51
|
+
/** The start of the window the chats were read for (`within`). */
|
|
52
|
+
since: string;
|
|
48
53
|
account: string;
|
|
49
54
|
storedAt: string | null;
|
|
50
55
|
stale: boolean;
|
package/dist/services/agenda.js
CHANGED
|
@@ -3,6 +3,7 @@ import { formatLocator, isLocator, parseLocator } from "../domain/locator.js";
|
|
|
3
3
|
import { formatReference } from "../domain/references.js";
|
|
4
4
|
import { agenda } from "../entities/agenda.js";
|
|
5
5
|
import { AGENDA_COLUMNS, inboxKey, queryFilter, } from "../store/store.js";
|
|
6
|
+
import { chatCoverage } from "./chat-coverage.js";
|
|
6
7
|
import { CHAT_KINDS } from "./chats.js";
|
|
7
8
|
import { taskAccount } from "./task-rules.js";
|
|
8
9
|
import { taskTagFilters } from "./task-tag-filters.js";
|
|
@@ -102,21 +103,29 @@ export const agendaService = (deps, now = Date.now) => ({
|
|
|
102
103
|
}),
|
|
103
104
|
openTasks: open.filter((task) => chatOf(task.source) === chat.chatId).map((task) => task.id),
|
|
104
105
|
}));
|
|
106
|
+
const silentFor = (chat) => at - Date.parse(chat.lastMessageAt);
|
|
107
|
+
const shown = {
|
|
108
|
+
owed: chats.filter((chat) => chat.lastFromOwner === false && silentFor(chat) >= unanswered),
|
|
109
|
+
waiting: chats.filter((chat) => chat.lastFromOwner === true && silentFor(chat) >= waiting),
|
|
110
|
+
unsynced: chats.filter((chat) => chat.lastMessage === null),
|
|
111
|
+
};
|
|
112
|
+
const covered = new Map((await chatCoverage(store, account, Object.values(shown).flatMap((list) => list.map(({ chatId }) => chatId)))).map((one) => [one.chatId, one]));
|
|
113
|
+
const withCoverage = (list) => list.map((chat) => ({ ...chat, coverage: covered.get(chat.chatId) }));
|
|
105
114
|
const inList = new Set((await store.tasks.categories()).map(({ name }) => name));
|
|
106
115
|
const unlisted = new Map();
|
|
107
116
|
for (const { category } of open)
|
|
108
117
|
if (category && !inList.has(category))
|
|
109
118
|
unlisted.set(category, (unlisted.get(category) ?? 0) + 1);
|
|
110
|
-
const silentFor = (chat) => at - Date.parse(chat.lastMessageAt);
|
|
111
119
|
return {
|
|
112
120
|
at: new Date(at).toISOString(),
|
|
121
|
+
since: new Date(at - within).toISOString(),
|
|
113
122
|
account: taskAccount(account),
|
|
114
123
|
storedAt: activity.storedAt,
|
|
115
124
|
stale: activity.storedAt === null || at - Date.parse(activity.storedAt) > DAY,
|
|
116
125
|
tasks,
|
|
117
|
-
owed:
|
|
118
|
-
waiting:
|
|
119
|
-
unsynced:
|
|
126
|
+
owed: withCoverage(shown.owed),
|
|
127
|
+
waiting: withCoverage(shown.waiting),
|
|
128
|
+
unsynced: withCoverage(shown.unsynced),
|
|
120
129
|
assigned,
|
|
121
130
|
unlistedCategories: [...unlisted].sort().map(([name, openTasks]) => ({ name, openTasks })),
|
|
122
131
|
};
|
|
@@ -208,6 +217,18 @@ export const agendaItem = (shown) => {
|
|
|
208
217
|
last_message_at: one.lastMessageAt,
|
|
209
218
|
last_from_owner: one.lastFromOwner,
|
|
210
219
|
open_tasks: one.openTasks,
|
|
220
|
+
completeness: {
|
|
221
|
+
state: one.coverage.completeness.state,
|
|
222
|
+
up_to_date: one.coverage.completeness.upToDate,
|
|
223
|
+
gaps: one.coverage.completeness.gaps,
|
|
224
|
+
reaches_start: one.coverage.completeness.reachesStart,
|
|
225
|
+
fetched_at: one.coverage.completeness.fetchedAt,
|
|
226
|
+
},
|
|
227
|
+
held: {
|
|
228
|
+
messages: one.coverage.held.messages,
|
|
229
|
+
oldest_at: one.coverage.held.oldestAt,
|
|
230
|
+
newest_at: one.coverage.held.newestAt,
|
|
231
|
+
},
|
|
211
232
|
});
|
|
212
233
|
return {
|
|
213
234
|
at: shown.at,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Id } from "../domain/models.js";
|
|
2
|
+
import type { AccountKey, ChatCompleteness, MessageStore } from "../store/store.js";
|
|
3
|
+
/** What the store holds of one chat, and how far that goes: never message text. */
|
|
4
|
+
export interface ChatCoverage {
|
|
5
|
+
chatId: Id;
|
|
6
|
+
/** `null` while the store holds no message of the chat: titles come with the stats. */
|
|
7
|
+
title: string | null;
|
|
8
|
+
completeness: ChatCompleteness;
|
|
9
|
+
held: {
|
|
10
|
+
messages: number;
|
|
11
|
+
oldestAt: string | null;
|
|
12
|
+
newestAt: string | null;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Per chat, in the order asked: `chatCompleteness` with the span `chatStats` holds. Stats are read one chat at a time,
|
|
17
|
+
* because the account-wide read groups every message of every chat. A chat the store has no row for is `unknown`.
|
|
18
|
+
*/
|
|
19
|
+
export declare const chatCoverage: (store: Pick<MessageStore, "chats">, account: AccountKey, chatIds: Id[]) => Promise<ChatCoverage[]>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per chat, in the order asked: `chatCompleteness` with the span `chatStats` holds. Stats are read one chat at a time,
|
|
3
|
+
* because the account-wide read groups every message of every chat. A chat the store has no row for is `unknown`.
|
|
4
|
+
*/
|
|
5
|
+
export const chatCoverage = async (store, account, chatIds) => {
|
|
6
|
+
const ids = [...new Set(chatIds)];
|
|
7
|
+
if (ids.length === 0)
|
|
8
|
+
return [];
|
|
9
|
+
const found = new Map((await store.chats.chatCompleteness(account, ids)).map((one) => [one.chatId, one]));
|
|
10
|
+
return Promise.all(ids.map(async (chatId) => {
|
|
11
|
+
const [stats] = await store.chats.chatStats(account, chatId);
|
|
12
|
+
const completeness = found.get(chatId) ?? {
|
|
13
|
+
chatId,
|
|
14
|
+
state: "unknown",
|
|
15
|
+
upToDate: null,
|
|
16
|
+
gaps: false,
|
|
17
|
+
reachesStart: false,
|
|
18
|
+
fetchedAt: null,
|
|
19
|
+
};
|
|
20
|
+
return {
|
|
21
|
+
chatId,
|
|
22
|
+
title: stats?.title ?? null,
|
|
23
|
+
completeness,
|
|
24
|
+
held: { messages: stats?.messages ?? 0, oldestAt: stats?.oldestAt ?? null, newestAt: stats?.newestAt ?? null },
|
|
25
|
+
};
|
|
26
|
+
}));
|
|
27
|
+
};
|
|
@@ -4,6 +4,7 @@ import type { MessengerTraits } from "../ports/traits.js";
|
|
|
4
4
|
import type { AccountKey, MessageStore } from "../store/store.js";
|
|
5
5
|
import type { SearchFound, SearchQuery } from "./messages.js";
|
|
6
6
|
import { type MeaningLane, type MeaningSkipped } from "./messages-discovery.js";
|
|
7
|
+
import type { SearchCoverage } from "./messages-search.js";
|
|
7
8
|
import type { Backend, ServerOptions, ServerSearched } from "./server-search.js";
|
|
8
9
|
export declare const RESOURCES_SEARCHED: readonly ["messages", "mail", "notes", "events"];
|
|
9
10
|
export type SearchedResource = (typeof RESOURCES_SEARCHED)[number];
|
|
@@ -77,6 +78,8 @@ export interface SearchAllFound {
|
|
|
77
78
|
};
|
|
78
79
|
/** The messenger's server step for messages, as `search messages` reports it. */
|
|
79
80
|
server?: ServerSearched;
|
|
81
|
+
/** What the store held of the chats the messages were searched in; absent when messages were not searched. */
|
|
82
|
+
coverage?: SearchCoverage;
|
|
80
83
|
}
|
|
81
84
|
export interface SearchAllRequest {
|
|
82
85
|
text: string;
|
|
@@ -53,6 +53,7 @@ export const searchAll = async (store, account, request, messenger = {}, searchM
|
|
|
53
53
|
let events;
|
|
54
54
|
let messagesBy;
|
|
55
55
|
let server;
|
|
56
|
+
let coverage;
|
|
56
57
|
const messagesOf = async (kind) => {
|
|
57
58
|
const given = request.scope?.[kind];
|
|
58
59
|
const query = {
|
|
@@ -76,6 +77,8 @@ export const searchAll = async (store, account, request, messenger = {}, searchM
|
|
|
76
77
|
const found = kind === "messages" ? await searchMessages(query) : await searchStore(store, account, query, messenger);
|
|
77
78
|
if (found.server)
|
|
78
79
|
server = found.server;
|
|
80
|
+
if (kind === "messages" && found.coverage)
|
|
81
|
+
coverage = found.coverage;
|
|
79
82
|
hasMore ||= found.hasMore;
|
|
80
83
|
const read = found.coverage?.accounts ??
|
|
81
84
|
given ??
|
|
@@ -243,5 +246,6 @@ export const searchAll = async (store, account, request, messenger = {}, searchM
|
|
|
243
246
|
...(events === undefined ? {} : { events }),
|
|
244
247
|
...(messagesBy === undefined ? {} : { messages: messagesBy }),
|
|
245
248
|
...(server === undefined ? {} : { server }),
|
|
249
|
+
...(coverage === undefined ? {} : { coverage }),
|
|
246
250
|
};
|
|
247
251
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.493.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",
|