@wirecat/cli-core 0.398.0 → 0.400.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 +106 -0
- package/dist/analysis/consents.d.ts +11 -6
- package/dist/analysis/consents.js +5 -2
- package/dist/cli/analysis-choice.d.ts +1 -0
- package/dist/cli/analysis-choice.js +3 -0
- package/dist/cli/bot/admins.js +14 -4
- package/dist/cli/bot/command.js +38 -16
- package/dist/cli/bot/examples.d.ts +3 -0
- package/dist/cli/bot/examples.js +2 -0
- package/dist/cli/bot/mcp.js +6 -2
- package/dist/cli/bot/messages.js +17 -6
- package/dist/cli/bot/moderation.js +16 -4
- package/dist/cli/bot/people.js +16 -4
- package/dist/cli/bot/setup.js +27 -8
- package/dist/cli/bot/watch.js +7 -1
- package/dist/cli/config-command.js +20 -1
- package/dist/cli/context.d.ts +2 -0
- package/dist/cli/context.js +1 -1
- package/dist/cli/effects.d.ts +4 -1
- package/dist/cli/effects.js +10 -2
- package/dist/cli/embedding-choice.d.ts +2 -1
- package/dist/cli/embedding-choice.js +4 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/json-fields.js +12 -1
- package/dist/cli/messenger/account-command.js +15 -6
- package/dist/cli/messenger/account-profile-query.d.ts +2 -0
- package/dist/cli/messenger/account-profile-query.js +13 -0
- package/dist/cli/messenger/account-sessions-command.js +1 -0
- package/dist/cli/messenger/admin-commands.js +30 -21
- package/dist/cli/messenger/admin-contacts-command.js +6 -3
- package/dist/cli/messenger/admin-folders-command.js +5 -5
- package/dist/cli/messenger/admin-moderation-command.js +4 -4
- package/dist/cli/messenger/admin-statistics-command.js +6 -14
- package/dist/cli/messenger/analysis-consents-query.d.ts +5 -0
- package/dist/cli/messenger/analysis-consents-query.js +20 -0
- package/dist/cli/messenger/archive-commands.js +3 -3
- package/dist/cli/messenger/attachments-command.js +3 -0
- package/dist/cli/messenger/backfill-command.js +5 -4
- package/dist/cli/messenger/catch-up-answers.d.ts +2 -1
- package/dist/cli/messenger/catch-up-answers.js +6 -2
- package/dist/cli/messenger/chat-queries.d.ts +1 -0
- package/dist/cli/messenger/chat-queries.js +12 -2
- package/dist/cli/messenger/chats-command.js +2 -2
- package/dist/cli/messenger/chats-documents-command.js +7 -5
- package/dist/cli/messenger/chats-members-command.js +7 -3
- package/dist/cli/messenger/chats-read-command.js +1 -1
- package/dist/cli/messenger/chats-stats-command.js +1 -1
- package/dist/cli/messenger/chats-tracking-command.js +14 -6
- package/dist/cli/messenger/contacts-command.js +11 -7
- package/dist/cli/messenger/context.d.ts +2 -0
- package/dist/cli/messenger/conversations-command.js +47 -29
- package/dist/cli/messenger/doctor-command.js +5 -1
- package/dist/cli/messenger/document-links-query.d.ts +2 -0
- package/dist/cli/messenger/document-links-query.js +9 -0
- package/dist/cli/messenger/download-command.js +1 -0
- package/dist/cli/messenger/flood-command.d.ts +2 -2
- package/dist/cli/messenger/flood-command.js +3 -3
- package/dist/cli/messenger/gaps-command.js +2 -2
- package/dist/cli/messenger/guard-commands.js +35 -13
- package/dist/cli/messenger/hearing-command.d.ts +1 -1
- package/dist/cli/messenger/hearing-command.js +1 -1
- package/dist/cli/messenger/images-command.js +1 -0
- package/dist/cli/messenger/inbox.js +6 -4
- package/dist/cli/messenger/media-archive-command.js +2 -0
- package/dist/cli/messenger/media-archive-move-command.js +1 -0
- package/dist/cli/messenger/media-archive-timer-command.js +1 -1
- package/dist/cli/messenger/mentions-command.js +15 -2
- package/dist/cli/messenger/messages-command.js +2 -0
- package/dist/cli/messenger/messages-delete-command.js +1 -0
- package/dist/cli/messenger/messages-edit-command.js +1 -0
- package/dist/cli/messenger/messages-evidence-command.js +3 -1
- package/dist/cli/messenger/messages-forward-command.js +1 -0
- package/dist/cli/messenger/messages-pin-command.js +3 -0
- package/dist/cli/messenger/messages-send-command.js +1 -0
- package/dist/cli/messenger/messages-stats-command.js +1 -1
- package/dist/cli/messenger/metadata-command.js +7 -2
- package/dist/cli/messenger/models-command.js +10 -2
- package/dist/cli/messenger/polls-command.js +4 -0
- package/dist/cli/messenger/private-people-command.js +8 -6
- package/dist/cli/messenger/rankings-command.js +6 -11
- package/dist/cli/messenger/reactions-command.js +2 -0
- package/dist/cli/messenger/recipients-query.d.ts +5 -0
- package/dist/cli/messenger/recipients-query.js +18 -0
- package/dist/cli/messenger/records-command.js +13 -6
- package/dist/cli/messenger/replies-edit-command.d.ts +1 -1
- package/dist/cli/messenger/replies-edit-command.js +13 -5
- package/dist/cli/messenger/retention-command.js +1 -1
- package/dist/cli/messenger/review.js +6 -4
- package/dist/cli/messenger/search-command.js +3 -1
- package/dist/cli/messenger/searches-command.js +6 -2
- package/dist/cli/messenger/sends-query.d.ts +5 -0
- package/dist/cli/messenger/sends-query.js +33 -0
- package/dist/cli/messenger/serve-command.js +1 -0
- package/dist/cli/messenger/serve-relay.d.ts +1 -0
- package/dist/cli/messenger/statistics-query.d.ts +14 -0
- package/dist/cli/messenger/statistics-query.js +143 -0
- package/dist/cli/messenger/stats-command.js +6 -6
- package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
- package/dist/cli/messenger/store-maintenance-command.js +10 -10
- package/dist/cli/messenger/tags-command.js +18 -6
- package/dist/cli/messenger/tasks-command.js +47 -8
- package/dist/cli/messenger/transcribe-command.js +2 -1
- package/dist/cli/messenger/watch-answers.d.ts +6 -0
- package/dist/cli/messenger/watch-answers.js +69 -0
- package/dist/cli/messenger/watch-command.d.ts +1 -1
- package/dist/cli/messenger/watch-command.js +56 -7
- package/dist/cli/program.js +5 -1
- package/dist/cli/runs/command.js +25 -38
- package/dist/cli/runs/query.d.ts +5 -0
- package/dist/cli/runs/query.js +32 -0
- package/dist/cli/runs/search-command.d.ts +4 -0
- package/dist/cli/runs/search-command.js +27 -0
- package/dist/cli/runs/search-query.d.ts +4 -0
- package/dist/cli/runs/search-query.js +50 -0
- package/dist/cli/runs/search.d.ts +2 -1
- package/dist/cli/runs/search.js +6 -6
- package/dist/domain/attachments.js +9 -1
- package/dist/domain/rankings-options.js +3 -3
- package/dist/entities/account-change.d.ts +89 -0
- package/dist/entities/account-change.js +20 -0
- package/dist/entities/account-profile.d.ts +7 -0
- package/dist/entities/account-profile.js +8 -0
- package/dist/entities/analysis-consent.d.ts +9 -0
- package/dist/entities/analysis-consent.js +4 -0
- package/dist/entities/attachment-results.d.ts +96 -0
- package/dist/entities/attachment-results.js +31 -0
- package/dist/entities/batch-result.d.ts +27 -0
- package/dist/entities/batch-result.js +18 -0
- package/dist/entities/chat-admin.d.ts +234 -0
- package/dist/entities/chat-admin.js +84 -0
- package/dist/entities/chat-change.d.ts +25 -0
- package/dist/entities/chat-change.js +11 -0
- package/dist/entities/chat-metadata.d.ts +34 -0
- package/dist/entities/chat-metadata.js +15 -0
- package/dist/entities/chat-moderation.d.ts +44 -0
- package/dist/entities/chat-moderation.js +37 -0
- package/dist/entities/chat-tracking.d.ts +26 -0
- package/dist/entities/chat-tracking.js +23 -0
- package/dist/entities/contact-activity.d.ts +52 -0
- package/dist/entities/contact-activity.js +42 -0
- package/dist/entities/contact-context.d.ts +1568 -0
- package/dist/entities/contact-context.js +191 -0
- package/dist/entities/contact-write.d.ts +22 -0
- package/dist/entities/contact-write.js +6 -0
- package/dist/entities/conversation-preparation.d.ts +63 -0
- package/dist/entities/conversation-preparation.js +23 -0
- package/dist/entities/conversation-results.d.ts +567 -0
- package/dist/entities/conversation-results.js +94 -0
- package/dist/entities/document-link.d.ts +13 -0
- package/dist/entities/document-link.js +11 -0
- package/dist/entities/extraction-report.d.ts +95 -0
- package/dist/entities/extraction-report.js +34 -0
- package/dist/entities/image-embedding.d.ts +9 -0
- package/dist/entities/image-embedding.js +10 -0
- package/dist/entities/index.d.ts +8006 -1928
- package/dist/entities/index.js +174 -3
- package/dist/entities/listener.d.ts +2150 -0
- package/dist/entities/listener.js +46 -0
- package/dist/entities/media-archive.d.ts +43 -0
- package/dist/entities/media-archive.js +32 -0
- package/dist/entities/meeting-task-proposal.d.ts +14 -0
- package/dist/entities/meeting-task-proposal.js +19 -0
- package/dist/entities/meeting.d.ts +1 -1
- package/dist/entities/meeting.js +1 -1
- package/dist/entities/mention.d.ts +63 -0
- package/dist/entities/mention.js +44 -0
- package/dist/entities/message-change.d.ts +19 -0
- package/dist/entities/message-change.js +6 -0
- package/dist/entities/message-context.d.ts +617 -0
- package/dist/entities/message-context.js +20 -0
- package/dist/entities/message-read.d.ts +73 -0
- package/dist/entities/message-read.js +58 -0
- package/dist/entities/message-transfer.d.ts +379 -0
- package/dist/entities/message-transfer.js +83 -0
- package/dist/entities/message-write.d.ts +191 -0
- package/dist/entities/message-write.js +8 -0
- package/dist/entities/model-key.d.ts +14 -0
- package/dist/entities/model-key.js +10 -0
- package/dist/entities/poll.d.ts +40 -0
- package/dist/entities/poll.js +16 -0
- package/dist/entities/private-contact.d.ts +10 -0
- package/dist/entities/private-contact.js +4 -0
- package/dist/entities/reaction-change.d.ts +7 -0
- package/dist/entities/reaction-change.js +8 -0
- package/dist/entities/recipient.d.ts +29 -0
- package/dist/entities/recipient.js +11 -0
- package/dist/entities/recorded-run.d.ts +103 -0
- package/dist/entities/recorded-run.js +44 -0
- package/dist/entities/saved-search.d.ts +4 -0
- package/dist/entities/saved-search.js +2 -0
- package/dist/entities/send-attempt.d.ts +31 -0
- package/dist/entities/send-attempt.js +29 -0
- package/dist/entities/store-maintenance.d.ts +319 -0
- package/dist/entities/store-maintenance.js +145 -0
- package/dist/entities/store-records.d.ts +442 -0
- package/dist/entities/store-records.js +99 -0
- package/dist/entities/tag-change.d.ts +74 -0
- package/dist/entities/tag-change.js +31 -0
- package/dist/entities/task-pair.d.ts +31 -0
- package/dist/entities/task-pair.js +23 -0
- package/dist/entities/task-results.d.ts +524 -0
- package/dist/entities/task-results.js +142 -0
- package/dist/errors/reasons.js +1 -0
- package/dist/mcp/agent.d.ts +3 -1
- package/dist/mcp/agent.js +9 -6
- package/dist/mcp/bot/server.d.ts +2 -0
- package/dist/mcp/bot/server.js +1 -0
- package/dist/mcp/prompts.js +8 -2
- package/dist/mcp/server.js +3 -1
- package/dist/mcp/tools/attachments.js +1 -0
- package/dist/meetings/cli/index.js +1 -1
- package/dist/meetings/cli/passage.js +1 -0
- package/dist/meetings/index.d.ts +2 -1
- package/dist/meetings/index.js +1 -0
- package/dist/meetings/links.d.ts +16 -0
- package/dist/meetings/mcp/index.d.ts +15 -15
- package/dist/meetings/read-answers.d.ts +167 -0
- package/dist/meetings/read-answers.js +183 -0
- package/dist/runs/run.d.ts +10 -2
- package/dist/runs/run.js +11 -3
- package/dist/sends/journal.d.ts +8 -1
- package/dist/sends/journal.js +9 -2
- package/dist/sends/permissions.d.ts +5 -1
- package/dist/sends/permissions.js +11 -1
- package/dist/sends/recipients.d.ts +8 -0
- package/dist/sends/recipients.js +9 -0
- package/dist/services/admin-statistics.js +3 -3
- package/dist/services/chat-documents.d.ts +1 -0
- package/dist/services/chat-documents.js +9 -3
- package/dist/services/chats.d.ts +1 -1
- package/dist/services/chats.js +5 -2
- package/dist/services/meeting-links.d.ts +2 -17
- package/dist/store/exports.d.ts +1 -1
- package/dist/store/exports.js +1 -1
- package/dist/store/messages.d.ts +3 -1
- package/dist/store/messages.js +2 -2
- package/dist/store/roster.d.ts +4 -1
- package/dist/store/roster.js +2 -2
- package/dist/store/sqlite/chats.d.ts +12 -0
- package/dist/store/sqlite/chats.js +1 -0
- package/dist/store/sqlite/document-links.d.ts +1 -1
- package/dist/store/sqlite/document-links.js +5 -5
- package/dist/store/sqlite/involvements.js +2 -1
- package/dist/store/sqlite/roster.d.ts +2 -1
- package/dist/store/sqlite/roster.js +3 -2
- package/package.json +3 -3
- package/parity.json +76 -108
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { annotate } from "@wirecat/cli-base/commands";
|
|
2
|
+
import { defineQuery, fieldsHelp, parseLucene, queryError, validateQuery, walkQuery, } from "@wirecat/cli-base/query";
|
|
3
|
+
import { QUERY_FIELDS } from "../../search/lucene/registry.js";
|
|
4
|
+
import { MESSAGE_PARSING } from "../../search/lucene/types.js";
|
|
5
|
+
import { LIST_QUERY_GUIDE, typedQuery } from "../list-query.js";
|
|
6
|
+
const FIELDS = {
|
|
7
|
+
"contacts-top": ["message_kind", "min_messages"],
|
|
8
|
+
"messages-top": ["message_kind"],
|
|
9
|
+
discussion: ["min_views", "max_replies"],
|
|
10
|
+
unanswered: ["older_than"],
|
|
11
|
+
responses: [],
|
|
12
|
+
};
|
|
13
|
+
const PATHS = {
|
|
14
|
+
"contacts-top": "stats contacts top",
|
|
15
|
+
"messages-top": "stats messages top",
|
|
16
|
+
discussion: "stats messages discussion",
|
|
17
|
+
unanswered: "stats messages unanswered",
|
|
18
|
+
responses: "stats contacts responses",
|
|
19
|
+
};
|
|
20
|
+
const FIELD_SPECS = [
|
|
21
|
+
{
|
|
22
|
+
name: "message_kind",
|
|
23
|
+
type: "enum",
|
|
24
|
+
values: ["all", "posts", "comments"],
|
|
25
|
+
operators: ["term", "phrase"],
|
|
26
|
+
example: "message_kind:posts",
|
|
27
|
+
placement: "and",
|
|
28
|
+
relation: "proven posts or comments selected before ranking",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: "min_messages",
|
|
32
|
+
type: "num",
|
|
33
|
+
operators: ["term"],
|
|
34
|
+
example: "min_messages:5",
|
|
35
|
+
placement: "and",
|
|
36
|
+
relation: "minimum selected messages per ranked author",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "min_views",
|
|
40
|
+
type: "num",
|
|
41
|
+
operators: ["term"],
|
|
42
|
+
example: "min_views:10",
|
|
43
|
+
placement: "and",
|
|
44
|
+
relation: "minimum known cumulative views of a discussion post",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "max_replies",
|
|
48
|
+
type: "num",
|
|
49
|
+
operators: ["term"],
|
|
50
|
+
example: "max_replies:0",
|
|
51
|
+
placement: "and",
|
|
52
|
+
relation: "maximum observed discussion replies",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "older_than",
|
|
56
|
+
type: "kw",
|
|
57
|
+
operators: ["term", "phrase"],
|
|
58
|
+
example: "older_than:24h",
|
|
59
|
+
placement: "and",
|
|
60
|
+
relation: "minimum age of an unanswered question; positive duration",
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
const fieldsFor = (report) => FIELD_SPECS.filter(({ name }) => FIELDS[report].includes(name));
|
|
64
|
+
export const withStatisticsQuery = (command, report) => {
|
|
65
|
+
const extra = fieldsFor(report);
|
|
66
|
+
const query = defineQuery({
|
|
67
|
+
entity: report.endsWith("top") ? "ranking" : "admin_report",
|
|
68
|
+
backend: "sql",
|
|
69
|
+
fields: [
|
|
70
|
+
...QUERY_FIELDS.map((field) => ({
|
|
71
|
+
...field,
|
|
72
|
+
example: field.name === "text" ? "text:invoice" : field.name === "topic" ? "topic:42" : field.example,
|
|
73
|
+
})),
|
|
74
|
+
...extra,
|
|
75
|
+
],
|
|
76
|
+
});
|
|
77
|
+
annotate(command, { query });
|
|
78
|
+
command.addHelpText("after", `\n${fieldsHelp({ fields: extra }, LIST_QUERY_GUIDE)}\nReport predicates are positive AND terms; the remaining query selects stored messages.\n`);
|
|
79
|
+
return command;
|
|
80
|
+
};
|
|
81
|
+
export const statisticsQuery = (words, report) => {
|
|
82
|
+
const source = typedQuery(words);
|
|
83
|
+
if (!source.trim())
|
|
84
|
+
return {};
|
|
85
|
+
const ast = parseLucene(source, MESSAGE_PARSING);
|
|
86
|
+
const reserved = new Set(FIELD_SPECS.map(({ name }) => name));
|
|
87
|
+
const hasReport = (node) => walkQuery(node).some(({ field }) => reserved.has(field));
|
|
88
|
+
if (!hasReport(ast.root))
|
|
89
|
+
return { text: source };
|
|
90
|
+
const context = { command: PATHS[report], guide: LIST_QUERY_GUIDE };
|
|
91
|
+
const values = new Map();
|
|
92
|
+
const original = (node) => {
|
|
93
|
+
const piece = source.slice(node.span.start, node.span.end);
|
|
94
|
+
if (node.kind === "boolean") {
|
|
95
|
+
const prefix = /(?:^|[\s(])([a-z_][a-z0-9_-]*)\s*:\s*$/i.exec(source.slice(0, node.span.start));
|
|
96
|
+
return prefix ? `${prefix[1]}:${piece}` : piece;
|
|
97
|
+
}
|
|
98
|
+
const named = /^([a-z_][a-z0-9_-]*)\s*[:<>]/i.exec(piece)?.[1];
|
|
99
|
+
return node.implicit || named?.toLowerCase() === node.field.toLowerCase() ? piece : `${node.field}:${piece}`;
|
|
100
|
+
};
|
|
101
|
+
const prune = (node) => {
|
|
102
|
+
if (!hasReport(node))
|
|
103
|
+
return original(node);
|
|
104
|
+
if (node.kind === "boolean") {
|
|
105
|
+
if (node.clauses.some(({ occur, node: child }) => occur === "should" || (occur === "must_not" && hasReport(child))))
|
|
106
|
+
queryError("query.unsupported-placement", node.span, "report predicates must be positive AND terms", context);
|
|
107
|
+
const kept = node.clauses
|
|
108
|
+
.map(({ occur, node: child }) => {
|
|
109
|
+
const part = prune(child);
|
|
110
|
+
return part === undefined ? undefined : occur === "must_not" ? `NOT (${part})` : part;
|
|
111
|
+
})
|
|
112
|
+
.filter((part) => part !== undefined);
|
|
113
|
+
return kept.length ? kept.map((part) => `(${part})`).join(" AND ") : undefined;
|
|
114
|
+
}
|
|
115
|
+
if (!FIELDS[report].includes(node.field))
|
|
116
|
+
queryError("query.unsupported-field", node.span, `${node.field} is not a filter for ${PATHS[report]}`, context);
|
|
117
|
+
validateQuery({ ...ast, root: node }, { entity: "statistics", backend: "sql", fields: fieldsFor(report) }, context);
|
|
118
|
+
if (values.has(node.field))
|
|
119
|
+
queryError("query.unsupported-placement", node.span, `${node.field} only once here`, context);
|
|
120
|
+
if (["min_messages", "min_views", "max_replies"].includes(node.field)) {
|
|
121
|
+
const value = Number(node.value);
|
|
122
|
+
if (!Number.isSafeInteger(value) || value < (node.field === "min_messages" ? 1 : 0))
|
|
123
|
+
queryError("query.invalid-number", node.span, `${node.field} takes a ${node.field === "min_messages" ? "positive" : "nonnegative"} integer`, context);
|
|
124
|
+
}
|
|
125
|
+
if (node.field === "older_than") {
|
|
126
|
+
const units = { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000, w: 604_800_000 };
|
|
127
|
+
const duration = Number(node.value.slice(0, -1)) * (units[node.value.at(-1) ?? ""] ?? 0);
|
|
128
|
+
if (!/^\d+(?:\.\d+)?[smhdw]$/.test(node.value) || !Number.isSafeInteger(duration) || duration <= 0)
|
|
129
|
+
queryError("query.invalid-duration", node.span, "older_than takes a positive duration such as 4h or 7d", context);
|
|
130
|
+
}
|
|
131
|
+
values.set(node.field, node.value);
|
|
132
|
+
return undefined;
|
|
133
|
+
};
|
|
134
|
+
const text = prune(ast.root);
|
|
135
|
+
return {
|
|
136
|
+
...(text ? { text } : {}),
|
|
137
|
+
...(values.has("message_kind") ? { messageKind: values.get("message_kind")?.toLowerCase() } : {}),
|
|
138
|
+
...(values.has("min_messages") ? { minMessages: Number(values.get("min_messages")) } : {}),
|
|
139
|
+
...(values.has("min_views") ? { minViews: Number(values.get("min_views")) } : {}),
|
|
140
|
+
...(values.has("max_replies") ? { maxReplies: Number(values.get("max_replies")) } : {}),
|
|
141
|
+
...(values.has("older_than") ? { olderThan: values.get("older_than") } : {}),
|
|
142
|
+
};
|
|
143
|
+
};
|
|
@@ -49,7 +49,7 @@ export const statsCommand = (messenger, loadRenderer = chartRenderer) => {
|
|
|
49
49
|
.description("a chart's data from a chat's statistics, and optionally a dark SVG or PNG image")
|
|
50
50
|
.argument("<chat>", messenger.chatArgument)
|
|
51
51
|
.option("--chart-kind <messages|active|membership>", "what to draw: messages, active authors, or joins and leaves", chartKindOf, "messages")
|
|
52
|
-
.option("--by <
|
|
52
|
+
.option("--by <group>", "one point per calendar day or week (weeks start on Monday)", chartPeriodOf, "day")
|
|
53
53
|
.option("--since-time <time>", `ISO 8601, or 2h / 1d ago; ${EVENTS_DAYS} days ago if not given`)
|
|
54
54
|
.option("--timezone <zone>", "the IANA timezone for calendar days")
|
|
55
55
|
.option("--output <file>", "write a dark image to a new .svg or .png file")
|
|
@@ -91,15 +91,15 @@ export const statsCommand = (messenger, loadRenderer = chartRenderer) => {
|
|
|
91
91
|
"chats retention": '"Book club"',
|
|
92
92
|
"chats show": '"Book club"',
|
|
93
93
|
"contacts evidence": '101 --selection "$selection" --component messages',
|
|
94
|
-
"contacts responses": '
|
|
95
|
-
"contacts top": '
|
|
94
|
+
"contacts responses": 'chat:"Project room"',
|
|
95
|
+
"contacts top": 'chat:"Book club" --limit 10',
|
|
96
96
|
"messages counters refresh": '--chat "Book club" --dry-run',
|
|
97
97
|
"messages counters show": '--chat "Book club" --limit 10',
|
|
98
|
-
"messages discussion": '
|
|
98
|
+
"messages discussion": 'chat:"Book club"',
|
|
99
99
|
"messages evidence": '"$message" --selection "$selection" --component reactions',
|
|
100
100
|
"messages show": '--chat "Book club" --by day',
|
|
101
|
-
"messages top": '
|
|
102
|
-
"messages unanswered": '
|
|
101
|
+
"messages top": 'chat:"Book club" --limit 10',
|
|
102
|
+
"messages unanswered": 'chat:"Project room"',
|
|
103
103
|
"tasks show": '--chat "Project room"',
|
|
104
104
|
};
|
|
105
105
|
for (const [path, args] of Object.entries(examples)) {
|
|
@@ -2,9 +2,9 @@ import { Command } from "commander";
|
|
|
2
2
|
import type { Messenger } from "./context.js";
|
|
3
3
|
/** `doctor`'s short summary of the store. Must answer for a broken file, so an error is a field. */
|
|
4
4
|
export declare const storeSummary: (env: NodeJS.ProcessEnv) => Promise<{
|
|
5
|
+
error?: undefined;
|
|
5
6
|
path: string;
|
|
6
7
|
exists: boolean;
|
|
7
|
-
error?: undefined;
|
|
8
8
|
} | {
|
|
9
9
|
path: string;
|
|
10
10
|
exists: boolean;
|
|
@@ -92,7 +92,7 @@ const refuseWhileServing = (command, messenger) => {
|
|
|
92
92
|
throw new CliError("validation_error", `${messenger.app.command} serve is running for ${serving.join(", ")} — ${serving.map((profile) => `\`${messenger.app.command} ${profile} server stop\``).join(", ")} first`);
|
|
93
93
|
}
|
|
94
94
|
};
|
|
95
|
-
const repairCommand = (messenger) => annotate(new Command("repair"), { effects: LOCAL_WRITE, mcp: false })
|
|
95
|
+
const repairCommand = (messenger) => annotate(new Command("repair"), { output: { entity: "store_repair" }, effects: LOCAL_WRITE, mcp: false })
|
|
96
96
|
.summary("repair table shapes while keeping the old tables")
|
|
97
97
|
.description("bring every table to this build's shape, deleting nothing: a table of the wrong shape is kept as a copy beside a new one")
|
|
98
98
|
.option("--dry-run", "say what it would do, and change nothing")
|
|
@@ -142,7 +142,7 @@ const notesOf = (report, command) => {
|
|
|
142
142
|
}
|
|
143
143
|
return notes;
|
|
144
144
|
};
|
|
145
|
-
const copiesCommand = () => new Command("copies").description("the tables `store repair` kept as copies").addCommand(annotate(new Command("delete"), { effects: LOCAL_WRITE, mcp: false })
|
|
145
|
+
const copiesCommand = () => new Command("copies").description("the tables `store repair` kept as copies").addCommand(annotate(new Command("delete"), { output: { entity: "store_copy_removal" }, effects: LOCAL_WRITE, mcp: false })
|
|
146
146
|
.description("delete one copy `store repair` kept, named exactly; refuses any other table")
|
|
147
147
|
.argument("<name>", "the copy's name, as `store repair` printed it")
|
|
148
148
|
.action(async function (name) {
|
|
@@ -152,7 +152,7 @@ const copiesCommand = () => new Command("copies").description("the tables `store
|
|
|
152
152
|
const deleted = await reading(path, (database) => deleteCopy(database, name));
|
|
153
153
|
result(this, { path, deleted });
|
|
154
154
|
}));
|
|
155
|
-
const infoCommand = () => annotate(new Command("info"), { effects: LOCAL_READ })
|
|
155
|
+
const infoCommand = () => annotate(new Command("info"), { output: { entity: "store_info" }, effects: LOCAL_READ })
|
|
156
156
|
.summary("the local archive's format, size and contents")
|
|
157
157
|
.description("the store file: where it is, its size, its schema and how many rows it holds; changes nothing")
|
|
158
158
|
.action(async function () {
|
|
@@ -182,7 +182,7 @@ const infoCommand = () => annotate(new Command("info"), { effects: LOCAL_READ })
|
|
|
182
182
|
* fetched" (NEED-399 A). Times are the chat's newest message by the messenger's list against the
|
|
183
183
|
* newest message held, not `sync_ranges`: those are in message ids, which a chat list does not give.
|
|
184
184
|
*/
|
|
185
|
-
const checkCommand = (messenger) => annotate(new Command("check"), { effects: LOCAL_READ })
|
|
185
|
+
const checkCommand = (messenger) => annotate(new Command("check"), { output: { entity: "store_check" }, effects: LOCAL_READ })
|
|
186
186
|
.summary("check the local archive for problems")
|
|
187
187
|
.description("whether the store is healthy — integrity, search indexes, disk, and which chats are behind")
|
|
188
188
|
.action(async function () {
|
|
@@ -362,7 +362,7 @@ const chatsBehind = (database) => database
|
|
|
362
362
|
held: row.held === null ? null : isoOf(row.held),
|
|
363
363
|
refreshed: isoOf(row.refreshed),
|
|
364
364
|
}));
|
|
365
|
-
const migrateCommand = (messenger) => annotate(new Command("migrate"), { effects: LOCAL_WRITE, mcp: false })
|
|
365
|
+
const migrateCommand = (messenger) => annotate(new Command("migrate"), { output: { entity: "store_migration" }, effects: LOCAL_WRITE, mcp: false })
|
|
366
366
|
.summary("migrate an older local archive")
|
|
367
367
|
.description("bring the store up to this build's schema, then normalize, index and stem the messages and notes stored before it")
|
|
368
368
|
.action(async function () {
|
|
@@ -431,7 +431,7 @@ const buildNoteIndex = (database, note, { reset = false } = {}) => {
|
|
|
431
431
|
note(`indexing ${pending} notes`);
|
|
432
432
|
return { notesIndexed: drainNoteIndex(database, stemmerCache()) };
|
|
433
433
|
};
|
|
434
|
-
const reindexCommand = (messenger) => destroysNothing(annotate(new Command("reindex"), { effects: LOCAL_WRITE }))
|
|
434
|
+
const reindexCommand = (messenger) => destroysNothing(annotate(new Command("reindex"), { output: { entity: "store_reindex" }, effects: LOCAL_WRITE }))
|
|
435
435
|
.summary("rebuild the local archive's search indexes")
|
|
436
436
|
.description("rebuild the word index, its typo vocabulary, the stems, the files' word index, the notes' indexes and who took part in what, from what is stored; loses nothing")
|
|
437
437
|
.action(async function () {
|
|
@@ -478,7 +478,7 @@ const vacuumInto = async (path, target) => {
|
|
|
478
478
|
}
|
|
479
479
|
};
|
|
480
480
|
const stampNow = () => new Date().toISOString().replace(/[:.]/g, "-");
|
|
481
|
-
const backupCommand = () => destroysNothing(annotate(new Command("backup"), { effects: LOCAL_WRITE, mcp: false }))
|
|
481
|
+
const backupCommand = () => destroysNothing(annotate(new Command("backup"), { output: { entity: "store_backup" }, effects: LOCAL_WRITE, mcp: false }))
|
|
482
482
|
.description("copy the store into a new file, while it is in use; never overwrites a file")
|
|
483
483
|
.argument("<file>", "the new file")
|
|
484
484
|
.option(...ENCRYPT_OPTION)
|
|
@@ -522,7 +522,7 @@ const backupCommand = () => destroysNothing(annotate(new Command("backup"), { ef
|
|
|
522
522
|
}
|
|
523
523
|
});
|
|
524
524
|
/** The only way back into a sealed file: the format is this tool's own. */
|
|
525
|
-
const decryptCommand = () => annotate(new Command("decrypt"), { effects: LOCAL_WRITE, mcp: false })
|
|
525
|
+
const decryptCommand = () => annotate(new Command("decrypt"), { output: { entity: "store_decrypt" }, effects: LOCAL_WRITE, mcp: false })
|
|
526
526
|
.description("open a file written with --encrypt into a new file; asks for its password")
|
|
527
527
|
.argument("<file>", "a file `store backup --encrypt` or `store export --encrypt` wrote")
|
|
528
528
|
.requiredOption("--output <file>", "the new file, readable only by you")
|
|
@@ -544,7 +544,7 @@ const decryptCommand = () => annotate(new Command("decrypt"), { effects: LOCAL_W
|
|
|
544
544
|
* process has the file open — it would go on writing to the file set aside — or while this CLI's
|
|
545
545
|
* `serve` runs: that opens the store on its first write, so until then it holds nothing to see.
|
|
546
546
|
*/
|
|
547
|
-
const restoreCommand = (messenger) => annotate(new Command("restore"), { effects: LOCAL_WRITE, mcp: false })
|
|
547
|
+
const restoreCommand = (messenger) => annotate(new Command("restore"), { output: { entity: "store_restore" }, effects: LOCAL_WRITE, mcp: false })
|
|
548
548
|
.summary("restore a backup while keeping the current store")
|
|
549
549
|
.description("put a backup in place of the store; the store it replaces is kept beside it, never deleted")
|
|
550
550
|
.argument("<file>", "a file `store backup` wrote; one written with --encrypt asks for its password")
|
|
@@ -661,7 +661,7 @@ const quiesce = async (path, messenger, then, warn, waitMs = STORE_LOCK_WAIT_MS)
|
|
|
661
661
|
* For a store this build cannot migrate: a backup beside it, then an empty store at this build's
|
|
662
662
|
* schema. The backup is made before anything is deleted, and its path is said before the delete.
|
|
663
663
|
*/
|
|
664
|
-
const resetCommand = (messenger) => annotate(new Command("reset"), { effects: LOCAL_WRITE, mcp: false })
|
|
664
|
+
const resetCommand = (messenger) => annotate(new Command("reset"), { output: { entity: "store_reset" }, effects: LOCAL_WRITE, mcp: false })
|
|
665
665
|
.summary("back up the local archive and replace it with an empty one")
|
|
666
666
|
.description("back the store up beside itself, then delete it and start an empty one at this build's schema; asks first, or --yes")
|
|
667
667
|
.option("--no-backup", "delete the store without backing it up first")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { annotate } from "@wirecat/cli-base/commands";
|
|
3
|
-
import { Command } from "commander";
|
|
3
|
+
import { Command, Option } from "commander";
|
|
4
4
|
import { TAGGING_QUERY } from "../../services/tags.js";
|
|
5
5
|
import { changesStore, FETCH, LOCAL_READ, LOCAL_WRITE } from "../effects.js";
|
|
6
6
|
import { itemOf, renderAnswer, snakeKeys } from "../envelope.js";
|
|
@@ -23,7 +23,7 @@ const describe = ({ type, chatId, personId, messageId, taskId }) => type === "ch
|
|
|
23
23
|
const line = (one) => `${one.tag} ${one.type} ${one.name ?? ""} (${one.target})`;
|
|
24
24
|
const sourceOf = (value) => {
|
|
25
25
|
if (value !== "manual" && value !== "auto")
|
|
26
|
-
throw new CliError("validation_error", "--
|
|
26
|
+
throw new CliError("validation_error", "--claim takes manual or auto");
|
|
27
27
|
return value;
|
|
28
28
|
};
|
|
29
29
|
/** The owner's labels, in the local store only: `tag:` in a search finds what they label. */
|
|
@@ -32,10 +32,14 @@ export const tagsCommand = (messenger) => {
|
|
|
32
32
|
const tags = new Command("tags")
|
|
33
33
|
.summary("tags on stored messages and other records")
|
|
34
34
|
.description("your own labels on chats, people, messages and tasks, kept in the local store and never sent; tag: in a search finds them");
|
|
35
|
-
changesStore(annotate(tags.command("auto"), {
|
|
35
|
+
changesStore(annotate(tags.command("auto"), {
|
|
36
|
+
output: { entity: "automatic_tags" },
|
|
37
|
+
effects: FETCH,
|
|
38
|
+
examples: [`${tool} tags auto --chat "Book club" --dry-run`],
|
|
39
|
+
}))
|
|
36
40
|
.description("derive local group/channel tags from cached metadata using keyword rules")
|
|
37
41
|
.option("--chat <chat>", "a stored group/channel; repeat to select several", (value, previous) => [...previous, value], [])
|
|
38
|
-
.option("--limit <
|
|
42
|
+
.option("--limit <n>", "process at most 1–500 chats", positiveCount("--limit"), 50)
|
|
39
43
|
.option("--sync-first", "read current descriptions from the messenger before classifying")
|
|
40
44
|
.option("--dry-run", "preview cached classification without changing the store")
|
|
41
45
|
.action(async function () {
|
|
@@ -47,6 +51,7 @@ export const tagsCommand = (messenger) => {
|
|
|
47
51
|
renderAnswer(this, context, itemOf(snakeKeys(done)));
|
|
48
52
|
});
|
|
49
53
|
targetOptions(annotate(tags.command("add"), {
|
|
54
|
+
output: { entity: "tag_change" },
|
|
50
55
|
effects: LOCAL_WRITE,
|
|
51
56
|
examples: [`${tool} tags add reading --chat "Book club"`],
|
|
52
57
|
}).description("put tags on one chat, person, message or task"), messenger, "tag").action(async function (given) {
|
|
@@ -59,15 +64,22 @@ export const tagsCommand = (messenger) => {
|
|
|
59
64
|
context.renderer.note(`already there: ${done.unchanged.join(", ")}`);
|
|
60
65
|
});
|
|
61
66
|
targetOptions(annotate(tags.command("remove"), {
|
|
67
|
+
output: { entity: "tag_change" },
|
|
62
68
|
effects: LOCAL_WRITE,
|
|
63
69
|
examples: [`${tool} tags remove reading --chat "Book club"`],
|
|
64
70
|
}).description("take tags off one chat, person, message or task"), messenger, "untag")
|
|
65
|
-
.
|
|
71
|
+
.addOption(new Option("--claim <claim>", "remove only this ownership claim: manual or auto")
|
|
72
|
+
.argParser(sourceOf)
|
|
73
|
+
.conflicts("source"))
|
|
74
|
+
.addOption(new Option("--source <manual|auto>", "compatibility alias for --claim")
|
|
75
|
+
.argParser(sourceOf)
|
|
76
|
+
.hideHelp()
|
|
77
|
+
.conflicts("claim"))
|
|
66
78
|
.action(async function (given) {
|
|
67
79
|
const context = messengerContext(this, messenger);
|
|
68
80
|
refuseLocalWrite(context, messenger.app.command, "tags.remove");
|
|
69
81
|
const target = this.opts();
|
|
70
|
-
const done = await context.withServices((services) => services.tags.remove(target, given, this.opts().source));
|
|
82
|
+
const done = await context.withServices((services) => services.tags.remove(target, given, this.opts().claim ?? this.opts().source));
|
|
71
83
|
renderAnswer(this, context, itemOf(snakeKeys(done)), () => done.removed.length ? `${describe(done.target)}: ${done.removed.join(", ")} removed\n` : "");
|
|
72
84
|
if (done.unchanged.length && context.format === "pretty")
|
|
73
85
|
context.renderer.note(`not there: ${done.unchanged.join(", ")}`);
|
|
@@ -152,6 +152,7 @@ export const tasksCommand = (messenger) => {
|
|
|
152
152
|
context.renderer.note("no tasks — review, inbox and messages sync add them as questions arrive");
|
|
153
153
|
});
|
|
154
154
|
planOptions(destroysNothing(annotate(tasks.command("add"), {
|
|
155
|
+
output: { entity: "task_created" },
|
|
155
156
|
effects: LOCAL_WRITE,
|
|
156
157
|
examples: [`${tool} personal tasks add note:101 --type promise --category documents --title "Review draft"`],
|
|
157
158
|
}))
|
|
@@ -185,6 +186,7 @@ export const tasksCommand = (messenger) => {
|
|
|
185
186
|
context.renderer.note(`${task.key ?? task.id} follows up ${followUpOf.key} (${followUpOf.status}), which stays closed: linked follow-up-of`);
|
|
186
187
|
});
|
|
187
188
|
planOptions(destroysNothing(annotate(tasks.command("plan"), {
|
|
189
|
+
output: { entity: "task_card" },
|
|
188
190
|
effects: LOCAL_WRITE,
|
|
189
191
|
examples: [`${tool} personal tasks plan DOC-12 --title "Review draft"`],
|
|
190
192
|
}))
|
|
@@ -201,6 +203,7 @@ export const tasksCommand = (messenger) => {
|
|
|
201
203
|
renderAnswer(this, context, itemOf(snakeKeys(planned)), () => `${card(planned)}\n`);
|
|
202
204
|
});
|
|
203
205
|
destroysNothing(annotate(tasks.command("link"), {
|
|
206
|
+
output: { entity: "task_links" },
|
|
204
207
|
effects: LOCAL_WRITE,
|
|
205
208
|
examples: [`${tool} personal tasks link DOC-12 https://example.com/draft`],
|
|
206
209
|
}))
|
|
@@ -223,7 +226,11 @@ export const tasksCommand = (messenger) => {
|
|
|
223
226
|
if (!("relations" in linked) && linked.review && context.format === "pretty")
|
|
224
227
|
context.renderer.note(`${linked.review} possible duplicate pair(s) went to tasks duplicates`);
|
|
225
228
|
});
|
|
226
|
-
annotate(tasks.command("show"), {
|
|
229
|
+
annotate(tasks.command("show"), {
|
|
230
|
+
output: { entity: "task_card" },
|
|
231
|
+
effects: LOCAL_READ,
|
|
232
|
+
examples: [`${tool} personal tasks show DOC-12`],
|
|
233
|
+
})
|
|
227
234
|
.description("one task: its plan and everything linked to it")
|
|
228
235
|
.argument("<task>", "the task's id or key, as tasks list shows it")
|
|
229
236
|
.action(async function (id) {
|
|
@@ -231,7 +238,11 @@ export const tasksCommand = (messenger) => {
|
|
|
231
238
|
const shown = await context.withServices((services) => services.tasks.show(id));
|
|
232
239
|
renderAnswer(this, context, itemOf(snakeKeys(shown)), () => `${card(shown)}\n`);
|
|
233
240
|
});
|
|
234
|
-
annotate(tasks.command("related"), {
|
|
241
|
+
annotate(tasks.command("related"), {
|
|
242
|
+
output: { entity: "task_similar" },
|
|
243
|
+
effects: LOCAL_READ,
|
|
244
|
+
examples: [`${tool} tasks related PR-12`],
|
|
245
|
+
})
|
|
235
246
|
.summary("tasks that may be the same job as this one, best first; merges nothing")
|
|
236
247
|
.description("tasks that may be the same job as this one, best first, each with a band — likely, maybe, different — and the " +
|
|
237
248
|
"reasons; merges nothing")
|
|
@@ -280,6 +291,7 @@ export const tasksCommand = (messenger) => {
|
|
|
280
291
|
renderAnswer(this, context, itemOf(snakeKeys(pair)), () => `${pair.decision} ${pairLine(pair)}\n`);
|
|
281
292
|
};
|
|
282
293
|
destroysNothing(annotate(duplicates.command("reject"), {
|
|
294
|
+
output: { entity: "task_pair" },
|
|
283
295
|
effects: LOCAL_WRITE,
|
|
284
296
|
examples: [`${tool} tasks duplicates reject PR-14 PR-12`],
|
|
285
297
|
}))
|
|
@@ -289,6 +301,7 @@ export const tasksCommand = (messenger) => {
|
|
|
289
301
|
.argument("<other-task>", "the task it was paired with")
|
|
290
302
|
.action(decided("rejected", "tasks.duplicates.reject"));
|
|
291
303
|
destroysNothing(annotate(duplicates.command("defer"), {
|
|
304
|
+
output: { entity: "task_pair" },
|
|
292
305
|
effects: LOCAL_WRITE,
|
|
293
306
|
examples: [`${tool} tasks duplicates defer PR-14 PR-12`],
|
|
294
307
|
}))
|
|
@@ -297,7 +310,11 @@ export const tasksCommand = (messenger) => {
|
|
|
297
310
|
.argument("<task>", "the task listed as the duplicate, by its id or key")
|
|
298
311
|
.argument("<other-task>", "the task it was paired with")
|
|
299
312
|
.action(decided("unsure", "tasks.duplicates.defer"));
|
|
300
|
-
destroysNothing(annotate(tasks.command("merge"), {
|
|
313
|
+
destroysNothing(annotate(tasks.command("merge"), {
|
|
314
|
+
output: { entity: "task_merge" },
|
|
315
|
+
effects: LOCAL_WRITE,
|
|
316
|
+
examples: [`${tool} tasks merge PR-14 --into PR-12`],
|
|
317
|
+
}))
|
|
301
318
|
.summary("one job seen twice: fold the duplicate into the task that stays")
|
|
302
319
|
.description("one job seen twice: link every message of the duplicate to the task and close the duplicate dismissed, " +
|
|
303
320
|
"duplicate-of the task; nothing is deleted, and tasks unmerge undoes it")
|
|
@@ -311,7 +328,11 @@ export const tasksCommand = (messenger) => {
|
|
|
311
328
|
renderAnswer(this, context, itemOf(snakeKeys(merged)), () => `${dup} merged into ${merged.task.key}; ${merged.linked} message(s) linked${toReview(merged.review)}\n` +
|
|
312
329
|
`${card(merged.task)}\n`);
|
|
313
330
|
});
|
|
314
|
-
destroysNothing(annotate(tasks.command("unmerge"), {
|
|
331
|
+
destroysNothing(annotate(tasks.command("unmerge"), {
|
|
332
|
+
output: { entity: "task_unmerge" },
|
|
333
|
+
effects: LOCAL_WRITE,
|
|
334
|
+
examples: [`${tool} tasks unmerge PR-14`],
|
|
335
|
+
}))
|
|
315
336
|
.summary("undo a merge: reopen the task and remove its duplicate-of relation")
|
|
316
337
|
.description("undo a merge: reopen the task and remove its duplicate-of relation; messages the merge linked stay linked")
|
|
317
338
|
.argument("<task>", "the merged task, by its id or key")
|
|
@@ -344,7 +365,11 @@ export const tasksCommand = (messenger) => {
|
|
|
344
365
|
}));
|
|
345
366
|
renderAnswer(this, context, itemOf(agendaItem(agenda)), () => `${agendaText(agenda)}\n`);
|
|
346
367
|
});
|
|
347
|
-
annotate(tasks.command("categories"), {
|
|
368
|
+
annotate(tasks.command("categories"), {
|
|
369
|
+
output: { entity: "task_categories" },
|
|
370
|
+
effects: LOCAL_READ,
|
|
371
|
+
examples: [`${tool} tasks categories`],
|
|
372
|
+
})
|
|
348
373
|
.summary("the categories a task takes, with what each means")
|
|
349
374
|
.description("the categories a task takes, with what each means and the words a rule picks it by")
|
|
350
375
|
.action(async function () {
|
|
@@ -365,6 +390,7 @@ export const tasksCommand = (messenger) => {
|
|
|
365
390
|
].join("\n"));
|
|
366
391
|
});
|
|
367
392
|
destroysNothing(annotate(tasks.command("scan"), {
|
|
393
|
+
output: { entity: "task_scan" },
|
|
368
394
|
effects: LOCAL_WRITE,
|
|
369
395
|
examples: [`${tool} tasks scan <chat>`, `${tool} tasks scan <chat> --last 300 --yes`],
|
|
370
396
|
}))
|
|
@@ -389,7 +415,11 @@ export const tasksCommand = (messenger) => {
|
|
|
389
415
|
}, { name: "tasks scan" });
|
|
390
416
|
renderAnswer(this, context, listOf("tasks scan", { items: passes.map(snakeKeys), limit: passes.length, has_more: false }), () => `${passes.map((pass) => applyText(pass, yes, tool)).join("\n")}\n`);
|
|
391
417
|
});
|
|
392
|
-
destroysNothing(annotate(tasks.command("claim"), {
|
|
418
|
+
destroysNothing(annotate(tasks.command("claim"), {
|
|
419
|
+
output: { entity: "task_card" },
|
|
420
|
+
effects: LOCAL_WRITE,
|
|
421
|
+
examples: [`${tool} tasks claim PR-12 --lease 2h`],
|
|
422
|
+
}))
|
|
393
423
|
.description("take an open task so no other agent works it at once; the claim lapses by itself")
|
|
394
424
|
.argument("<task>", "the task's id or key, as tasks list shows it")
|
|
395
425
|
.option("--lease <duration>", "how long the claim lasts: 30m, 2h (default 30m); claiming again extends it", durationOf("--lease"))
|
|
@@ -401,7 +431,11 @@ export const tasksCommand = (messenger) => {
|
|
|
401
431
|
const claimed = await context.withServices((services) => services.tasks.claim(id, holderOf(this), lease));
|
|
402
432
|
renderAnswer(this, context, itemOf(snakeKeys(claimed)), () => `${card(claimed)}\n`);
|
|
403
433
|
});
|
|
404
|
-
destroysNothing(annotate(tasks.command("release"), {
|
|
434
|
+
destroysNothing(annotate(tasks.command("release"), {
|
|
435
|
+
output: { entity: "task_card" },
|
|
436
|
+
effects: LOCAL_WRITE,
|
|
437
|
+
examples: [`${tool} tasks release PR-12`],
|
|
438
|
+
}))
|
|
405
439
|
.description("give back a task you claimed, before the claim lapses; the owner may release any")
|
|
406
440
|
.argument("<task>", "the task's id or key, as tasks list shows it")
|
|
407
441
|
.option("--holder <name>", HOLDER_HELP)
|
|
@@ -412,6 +446,7 @@ export const tasksCommand = (messenger) => {
|
|
|
412
446
|
renderAnswer(this, context, itemOf(snakeKeys(released)), () => `${card(released)}\n`);
|
|
413
447
|
});
|
|
414
448
|
destroysNothing(annotate(tasks.command("close"), {
|
|
449
|
+
output: { entity: "task_closed" },
|
|
415
450
|
effects: LOCAL_WRITE,
|
|
416
451
|
examples: [
|
|
417
452
|
`${tool} tasks close Q-15 --as done --certainty 0.9 --reason answered-in-chat`,
|
|
@@ -452,7 +487,11 @@ export const tasksCommand = (messenger) => {
|
|
|
452
487
|
context.renderer.note(`${closed.key ?? id} stays open: the close waits on the owner — ${tool} tasks close ${closed.key ?? id} ` +
|
|
453
488
|
`confirms it, ${tool} tasks reject ${closed.key ?? id} refuses it`);
|
|
454
489
|
});
|
|
455
|
-
annotate(tasks.command("reject"), {
|
|
490
|
+
annotate(tasks.command("reject"), {
|
|
491
|
+
output: { entity: "task_card" },
|
|
492
|
+
effects: LOCAL_WRITE,
|
|
493
|
+
examples: [`${tool} tasks reject Q-15`],
|
|
494
|
+
})
|
|
456
495
|
.summary("refuse an agent's close of a task: it stays open and the suggestion goes")
|
|
457
496
|
.description("refuse the close an agent suggested: the task stays open and leaves close:suggested; the agent may close it again")
|
|
458
497
|
.argument("<task>", "the task's id or key, as tasks list shows it")
|
|
@@ -9,6 +9,7 @@ import { heardAnswer } from "./message-answers.js";
|
|
|
9
9
|
import { keepRefreshedRefs } from "./refreshed-refs.js";
|
|
10
10
|
/** `messages transcribe`: a voice message as text, by the messenger or by a model on this machine. */
|
|
11
11
|
export const transcribeSubcommand = (messages, messenger) => annotate(messages.command("transcribe"), {
|
|
12
|
+
output: { entity: "message_transcript" },
|
|
12
13
|
examples: [`${messenger.app.command} personal messages transcribe "Book club" 101`],
|
|
13
14
|
effects: REMOTE_READ,
|
|
14
15
|
})
|
|
@@ -17,7 +18,7 @@ export const transcribeSubcommand = (messages, messenger) => annotate(messages.c
|
|
|
17
18
|
.argument("<chat>", messenger.chatArgument)
|
|
18
19
|
.argument("<message>", "the id of a voice message")
|
|
19
20
|
.option("--local", "use the model on this machine, never the messenger")
|
|
20
|
-
.option("--model <
|
|
21
|
+
.option("--model <model>", "which downloaded model; implies --local (`models audio list`)")
|
|
21
22
|
.action(async function (chat, messageId) {
|
|
22
23
|
const context = messengerContext(this, messenger);
|
|
23
24
|
const choice = {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type * as v from "valibot";
|
|
2
|
+
import type { MessageEvent, MessageHit } from "../../domain/models.js";
|
|
3
|
+
import type { messageEvent, watchedMessage } from "../../entities/listener.js";
|
|
4
|
+
import type { AccountKey } from "../../store/store.js";
|
|
5
|
+
export declare const watchedMessageItem: (value: MessageHit, account: AccountKey) => v.InferOutput<typeof watchedMessage>;
|
|
6
|
+
export declare const watchEventItem: (value: MessageEvent, account: AccountKey) => v.InferOutput<typeof messageEvent>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { chatEventAnswer, memberAnswer, pollVoteAnswer } from "./chat-answers.js";
|
|
2
|
+
import { messageItem } from "./message-items.js";
|
|
3
|
+
const observed = (value) => ({
|
|
4
|
+
value: value.value,
|
|
5
|
+
observed_at: value.observedAt,
|
|
6
|
+
source: value.source,
|
|
7
|
+
...(value.reactions === undefined ? {} : { reactions: value.reactions }),
|
|
8
|
+
});
|
|
9
|
+
export const watchedMessageItem = (value, account) => {
|
|
10
|
+
const counters = value.counterObservations;
|
|
11
|
+
return {
|
|
12
|
+
...messageItem(value, account),
|
|
13
|
+
...(value.starred === undefined ? {} : { starred: value.starred }),
|
|
14
|
+
...(counters === undefined
|
|
15
|
+
? {}
|
|
16
|
+
: {
|
|
17
|
+
counter_observations: {
|
|
18
|
+
...(counters.views === undefined ? {} : { views: observed(counters.views) }),
|
|
19
|
+
...(counters.reactions === undefined ? {} : { reactions: observed(counters.reactions) }),
|
|
20
|
+
...(counters.comments === undefined ? {} : { comments: observed(counters.comments) }),
|
|
21
|
+
},
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
const affected = (value) => ({
|
|
26
|
+
chat_id: value.chatId,
|
|
27
|
+
chat_title: value.chatTitle,
|
|
28
|
+
message_id: value.messageId,
|
|
29
|
+
});
|
|
30
|
+
export const watchEventItem = (value, account) => {
|
|
31
|
+
switch (value.event) {
|
|
32
|
+
case "message":
|
|
33
|
+
case "edit":
|
|
34
|
+
return { event: value.event, message: watchedMessageItem(value.message, account) };
|
|
35
|
+
case "delete":
|
|
36
|
+
return { event: value.event, chat_id: value.chatId, chat_title: value.chatTitle, message_id: value.messageId };
|
|
37
|
+
case "reaction":
|
|
38
|
+
return {
|
|
39
|
+
event: value.event,
|
|
40
|
+
...affected(value),
|
|
41
|
+
reactions: value.reactions,
|
|
42
|
+
...(value.counterObservation === undefined ? {} : { counter_observation: observed(value.counterObservation) }),
|
|
43
|
+
};
|
|
44
|
+
case "vote":
|
|
45
|
+
return { event: value.event, ...affected(value), vote: pollVoteAnswer(value.vote) };
|
|
46
|
+
case "person_reaction":
|
|
47
|
+
return {
|
|
48
|
+
event: value.event,
|
|
49
|
+
...affected(value),
|
|
50
|
+
reaction: {
|
|
51
|
+
person: memberAnswer(value.reaction.person),
|
|
52
|
+
own: value.reaction.own,
|
|
53
|
+
reaction: value.reaction.reaction,
|
|
54
|
+
reacted_at: value.reaction.reactedAt,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
case "star":
|
|
58
|
+
return { event: value.event, ...affected(value), starred: value.starred };
|
|
59
|
+
case "delivery":
|
|
60
|
+
return { event: value.event, ...affected(value), delivery: value.delivery, at: value.at };
|
|
61
|
+
case "chat_event":
|
|
62
|
+
return {
|
|
63
|
+
event: value.event,
|
|
64
|
+
chat_id: value.chatId,
|
|
65
|
+
chat_title: value.chatTitle,
|
|
66
|
+
change: chatEventAnswer(value.chatId, value.change),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
};
|
|
@@ -4,7 +4,7 @@ import type { MessengerAdapter } from "../../ports/messenger.js";
|
|
|
4
4
|
import { type Messenger, type MessengerContext } from "./context.js";
|
|
5
5
|
/**
|
|
6
6
|
* New messages as they arrive, until Ctrl-C or `--timeout` — both a normal end, exit 0: listening
|
|
7
|
-
* for a minute is a complete answer.
|
|
7
|
+
* for a minute is a complete answer. Canonical item records and one end record with `--jsonl`; `--json` is refused,
|
|
8
8
|
* because a stream is not one value. Nothing is marked read.
|
|
9
9
|
*/
|
|
10
10
|
export declare const watchCommand: (messenger: Messenger) => Command;
|