@wirecat/cli-core 0.393.0 → 0.395.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 +63 -0
- package/dist/cli/config-command.js +5 -2
- package/dist/cli/messenger/account-command.js +8 -3
- package/dist/cli/messenger/account-sessions-command.js +7 -2
- package/dist/cli/messenger/archive-commands.js +28 -0
- package/dist/cli/messenger/contacts-command.js +29 -0
- package/dist/cli/messenger/conversation-examples.d.ts +2 -0
- package/dist/cli/messenger/conversation-examples.js +25 -0
- package/dist/cli/messenger/conversations-command.js +2 -0
- package/dist/cli/messenger/mcp-command.js +12 -3
- package/dist/cli/messenger/models-command.js +15 -6
- package/dist/cli/messenger/polls-command.js +20 -5
- package/dist/cli/messenger/records-command.js +10 -2
- package/dist/cli/messenger/search-command.js +12 -0
- package/dist/cli/messenger/server-command.js +19 -7
- package/dist/cli/messenger/stats-command.js +26 -0
- package/dist/cli/messenger/tasks-command.js +18 -6
- package/dist/cli/runs/command.js +11 -4
- package/dist/cli/skill-command.js +6 -1
- package/dist/entities/index.d.ts +1 -1
- package/dist/entities/saved-search.d.ts +1 -1
- package/dist/entities/saved-search.js +1 -1
- package/dist/errors/reasons.js +1 -0
- package/dist/meetings/cli/search.js +1 -1
- package/dist/meetings/hit-query.d.ts +10 -0
- package/dist/meetings/hit-query.js +85 -1
- package/dist/meetings/index.d.ts +1 -1
- package/dist/meetings/index.js +1 -1
- package/dist/sends/permissions.js +2 -1
- package/dist/services/index.d.ts +5 -2
- package/dist/services/index.js +3 -1
- package/dist/services/meeting-combined.d.ts +1 -1
- package/dist/services/meeting-combined.js +4 -3
- package/dist/services/meeting-hit-search.d.ts +1 -1
- package/dist/services/meeting-hit-search.js +4 -2
- package/dist/services/meeting-searches.d.ts +25 -0
- package/dist/services/meeting-searches.js +35 -0
- package/dist/services/people.d.ts +5 -0
- package/dist/services/people.js +2 -0
- package/dist/services/person-context.d.ts +12 -2
- package/dist/services/person-context.js +2 -2
- package/dist/services/person-preparation.d.ts +66 -0
- package/dist/services/person-preparation.js +126 -0
- package/dist/services/search-record.d.ts +1 -1
- package/dist/services/searches.d.ts +1 -0
- package/dist/services/searches.js +17 -4
- package/dist/store/exports.d.ts +1 -0
- package/dist/store/people.d.ts +6 -0
- package/dist/store/people.js +2 -0
- package/dist/store/searches.d.ts +5 -4
- package/dist/store/searches.js +4 -4
- package/dist/store/sqlite/person-preparation.d.ts +48 -0
- package/dist/store/sqlite/person-preparation.js +72 -0
- package/dist/store/sqlite/searches.d.ts +4 -4
- package/dist/store/sqlite/searches.js +21 -6
- package/package.json +1 -1
- package/parity.json +7 -0
|
@@ -130,7 +130,10 @@ export const tasksCommand = (messenger) => {
|
|
|
130
130
|
? "agent"
|
|
131
131
|
: "owner";
|
|
132
132
|
const tasks = new Command("tasks").description("what waits on you — unanswered questions, mentions, requests, promises — kept in the local store; review and serve add them");
|
|
133
|
-
withQuery(annotate(tasks.command("list"), {
|
|
133
|
+
withQuery(annotate(tasks.command("list"), {
|
|
134
|
+
effects: LOCAL_READ,
|
|
135
|
+
examples: [`${tool} personal tasks list --limit 10`],
|
|
136
|
+
}).description("tasks, oldest first, with their message or note source"), TASK_QUERY)
|
|
134
137
|
.option("--limit <n>", "how many", positiveCount("--limit"))
|
|
135
138
|
.action(async function (words) {
|
|
136
139
|
const context = messengerContext(this, messenger);
|
|
@@ -146,7 +149,10 @@ export const tasksCommand = (messenger) => {
|
|
|
146
149
|
if (found.items.length === 0 && context.format === "pretty")
|
|
147
150
|
context.renderer.note("no tasks — review, inbox and messages sync add them as questions arrive");
|
|
148
151
|
});
|
|
149
|
-
planOptions(destroysNothing(annotate(tasks.command("add"), {
|
|
152
|
+
planOptions(destroysNothing(annotate(tasks.command("add"), {
|
|
153
|
+
effects: LOCAL_WRITE,
|
|
154
|
+
examples: [`${tool} personal tasks add note:101 --type promise --category documents --title "Review draft"`],
|
|
155
|
+
}))
|
|
150
156
|
.description("add a task for a stored message or note — a promise, a request")
|
|
151
157
|
.argument("<message>", "a message locator, msg:<provider>/<account>/<chat>/<message>, or note:<id>")
|
|
152
158
|
.requiredOption("--type <type>", "the task's type: question, request, mention or promise", taskTypeOf)
|
|
@@ -176,7 +182,10 @@ export const tasksCommand = (messenger) => {
|
|
|
176
182
|
else if (followUpOf && context.format === "pretty")
|
|
177
183
|
context.renderer.note(`${task.key ?? task.id} follows up ${followUpOf.key} (${followUpOf.status}), which stays closed: linked follow-up-of`);
|
|
178
184
|
});
|
|
179
|
-
planOptions(destroysNothing(annotate(tasks.command("plan"), {
|
|
185
|
+
planOptions(destroysNothing(annotate(tasks.command("plan"), {
|
|
186
|
+
effects: LOCAL_WRITE,
|
|
187
|
+
examples: [`${tool} personal tasks plan DOC-12 --title "Review draft"`],
|
|
188
|
+
}))
|
|
180
189
|
.summary("set a task's title, description, due date, next check, category and assignee")
|
|
181
190
|
.description("set a task's title, description, due date, next check, category and assignee; nothing reaches the messenger")
|
|
182
191
|
.argument("<task>", "the task's id or key, as tasks list shows it")
|
|
@@ -189,7 +198,10 @@ export const tasksCommand = (messenger) => {
|
|
|
189
198
|
const planned = await context.withServices((services) => services.tasks.plan(id, plan, writerOf(this)));
|
|
190
199
|
renderAnswer(this, context, itemOf(snakeKeys(planned)), () => `${card(planned)}\n`);
|
|
191
200
|
});
|
|
192
|
-
destroysNothing(annotate(tasks.command("link"), {
|
|
201
|
+
destroysNothing(annotate(tasks.command("link"), {
|
|
202
|
+
effects: LOCAL_WRITE,
|
|
203
|
+
examples: [`${tool} personal tasks link DOC-12 https://example.com/draft`],
|
|
204
|
+
}))
|
|
193
205
|
.description("link a task to messages, chats, people or URLs — or to another task: task:<id>")
|
|
194
206
|
.argument("<task>", "the task's id or key, as tasks list shows it")
|
|
195
207
|
.argument("[targets...]", "msg: locators, person:<id>, document:<id>, note:<id>, a URL; or task:<id>, which links two project tasks")
|
|
@@ -209,7 +221,7 @@ export const tasksCommand = (messenger) => {
|
|
|
209
221
|
if (!("relations" in linked) && linked.review && context.format === "pretty")
|
|
210
222
|
context.renderer.note(`${linked.review} possible duplicate pair(s) went to tasks duplicates`);
|
|
211
223
|
});
|
|
212
|
-
annotate(tasks.command("show"), { effects: LOCAL_READ })
|
|
224
|
+
annotate(tasks.command("show"), { effects: LOCAL_READ, examples: [`${tool} personal tasks show DOC-12`] })
|
|
213
225
|
.description("one task: its plan and everything linked to it")
|
|
214
226
|
.argument("<task>", "the task's id or key, as tasks list shows it")
|
|
215
227
|
.action(async function (id) {
|
|
@@ -307,7 +319,7 @@ export const tasksCommand = (messenger) => {
|
|
|
307
319
|
const unmerged = await context.withServices((services) => services.tasks.unmerge(dup, writerOf(this)));
|
|
308
320
|
renderAnswer(this, context, itemOf(snakeKeys(unmerged)), () => `${card(unmerged.task)}\n`);
|
|
309
321
|
});
|
|
310
|
-
withQuery(annotate(tasks.command("agenda"), { effects: LOCAL_READ })
|
|
322
|
+
withQuery(annotate(tasks.command("agenda"), { effects: LOCAL_READ, examples: [`${tool} personal tasks agenda`] })
|
|
311
323
|
.summary("what needs a look today: tasks due, chats you owe or wait on, what you are owed")
|
|
312
324
|
.description("what needs a look today, from the local store and without message text: tasks overdue, due or to check, " +
|
|
313
325
|
"chats where you owe an answer, chats where you wait for one, open tasks someone else does"), AGENDA_QUERY)
|
package/dist/cli/runs/command.js
CHANGED
|
@@ -18,7 +18,11 @@ export const runsCommand = (app) => {
|
|
|
18
18
|
throw new CliError("not_found", `no recorded run "${id}" — \`${lineOf(from, "runs list")}\` shows them`);
|
|
19
19
|
return found;
|
|
20
20
|
};
|
|
21
|
-
annotate(command.command("list"), {
|
|
21
|
+
annotate(command.command("list"), {
|
|
22
|
+
effects: LOCAL_READ,
|
|
23
|
+
mcp: false,
|
|
24
|
+
examples: [`${app.command} runs list --limit 10`],
|
|
25
|
+
})
|
|
22
26
|
.description("recorded runs, newest first")
|
|
23
27
|
.option("--limit <n>", "how many to show", positiveCount("--limit"), 20)
|
|
24
28
|
.option("--page <n>", "which page, starting at 1", positiveCount("--page"), 1)
|
|
@@ -32,7 +36,7 @@ export const runsCommand = (app) => {
|
|
|
32
36
|
if (runs.length === 0)
|
|
33
37
|
renderer.note("nothing recorded — a run is kept with `--record`, or when it fails");
|
|
34
38
|
});
|
|
35
|
-
annotate(command.command("show"), { effects: LOCAL_READ, mcp: false })
|
|
39
|
+
annotate(command.command("show"), { effects: LOCAL_READ, mcp: false, examples: [`${app.command} runs show run-101`] })
|
|
36
40
|
.argument("<run-id>", `an id from \`${app.command} runs list\``)
|
|
37
41
|
.description("one run: what it was, how it ended, and every event it logged")
|
|
38
42
|
.option("--problems", "only failed responses, warnings and crashes")
|
|
@@ -53,7 +57,10 @@ export const runsCommand = (app) => {
|
|
|
53
57
|
if (shown.length < events.length)
|
|
54
58
|
renderer.note(`${events.length - shown.length} other events — \`${lineOf(this, `runs show ${id}`)}\` shows them all`);
|
|
55
59
|
});
|
|
56
|
-
annotate(command.command("search"), {
|
|
60
|
+
annotate(command.command("search"), {
|
|
61
|
+
effects: LOCAL_READ,
|
|
62
|
+
examples: [`${app.command} runs search timeout --limit 10`],
|
|
63
|
+
})
|
|
57
64
|
.description("search recorded diagnostic metadata and events; no message contents")
|
|
58
65
|
.argument("[query]", "case-insensitive literal diagnostic text")
|
|
59
66
|
.addOption(new Option("--status <status>", "run outcome").choices(["success", "failed", "partial", "running"]))
|
|
@@ -79,7 +86,7 @@ export const runsCommand = (app) => {
|
|
|
79
86
|
since_time: options.sinceTime,
|
|
80
87
|
}));
|
|
81
88
|
});
|
|
82
|
-
annotate(command.command("path"), { effects: NO_EFFECTS, mcp: false })
|
|
89
|
+
annotate(command.command("path"), { effects: NO_EFFECTS, mcp: false, examples: [`${app.command} runs path run-101`] })
|
|
83
90
|
.argument("<run-id>", `an id from \`${app.command} runs list\``)
|
|
84
91
|
.description("the directory holding one run")
|
|
85
92
|
.action(function (id) {
|
|
@@ -13,8 +13,13 @@ export const skillCommand = (app, skill) => {
|
|
|
13
13
|
}, SHARED_SKILLS);
|
|
14
14
|
// An installed skill is instructions every later agent follows.
|
|
15
15
|
const install = command.commands.find((sub) => sub.name() === "install");
|
|
16
|
-
if (install)
|
|
16
|
+
if (install) {
|
|
17
17
|
grantsAuthority(install);
|
|
18
|
+
annotate(install, { examples: [`${app.command} skill install --for agents`] });
|
|
19
|
+
}
|
|
20
|
+
const show = command.commands.find((sub) => sub.name() === "show");
|
|
21
|
+
if (show)
|
|
22
|
+
annotate(show, { examples: [`${app.command} skill show`, `${app.command} skill show follow-ups`] });
|
|
18
23
|
return annotate(command, { mcp: false });
|
|
19
24
|
};
|
|
20
25
|
/** Skills this package ships for every CLI on it, printed by `skill show <name>`: `skills/` beside `dist/` and `src/`. */
|
package/dist/entities/index.d.ts
CHANGED
|
@@ -2857,7 +2857,7 @@ export declare const ENTITIES: {
|
|
|
2857
2857
|
readonly saved_search: v.StrictObjectSchema<{
|
|
2858
2858
|
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
2859
2859
|
readonly name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
2860
|
-
readonly command: v.PicklistSchema<["search", "stats", "message-top", "author-top", "admin-statistics"], undefined>;
|
|
2860
|
+
readonly command: v.PicklistSchema<["search", "stats", "message-top", "author-top", "admin-statistics", "meetings-search"], undefined>;
|
|
2861
2861
|
readonly params: v.RecordSchema<v.StringSchema<undefined>, v.UnknownSchema, undefined>;
|
|
2862
2862
|
readonly language: v.StringSchema<undefined>;
|
|
2863
2863
|
readonly version: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
@@ -4,7 +4,7 @@ export declare const savedSearch: v.StrictObjectSchema<{
|
|
|
4
4
|
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
5
5
|
/** `null` for a run in the history. */
|
|
6
6
|
readonly name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
7
|
-
readonly command: v.PicklistSchema<["search", "stats", "message-top", "author-top", "admin-statistics"], undefined>;
|
|
7
|
+
readonly command: v.PicklistSchema<["search", "stats", "message-top", "author-top", "admin-statistics", "meetings-search"], undefined>;
|
|
8
8
|
/** The query and the options it runs with, as the command took them. */
|
|
9
9
|
readonly params: v.RecordSchema<v.StringSchema<undefined>, v.UnknownSchema, undefined>;
|
|
10
10
|
readonly language: v.StringSchema<undefined>;
|
|
@@ -5,7 +5,7 @@ export const savedSearch = v.strictObject({
|
|
|
5
5
|
id,
|
|
6
6
|
/** `null` for a run in the history. */
|
|
7
7
|
name: v.nullable(text),
|
|
8
|
-
command: v.picklist(["search", "stats", "message-top", "author-top", "admin-statistics"]),
|
|
8
|
+
command: v.picklist(["search", "stats", "message-top", "author-top", "admin-statistics", "meetings-search"]),
|
|
9
9
|
/** The query and the options it runs with, as the command took them. */
|
|
10
10
|
params: v.record(v.string(), v.unknown()),
|
|
11
11
|
language: text,
|
package/dist/errors/reasons.js
CHANGED
|
@@ -51,6 +51,7 @@ const OTHERS = [
|
|
|
51
51
|
validation("ranking.invalid", "the ranking is not valid"),
|
|
52
52
|
validation("selection.changed", "the stored selection changed meanwhile"),
|
|
53
53
|
validation("selection.invalid", "the selection is not valid"),
|
|
54
|
+
validation("search.kind", "the saved search belongs to another kind of search: messages or meetings"),
|
|
54
55
|
["selection.out-of-scope", "permission_error", "the selection reaches outside what this profile may read"],
|
|
55
56
|
validation("stemmer.invalid", "the stemmer is not valid for that script"),
|
|
56
57
|
validation("stemmer.newer", "the stems were built by a newer tool"),
|
|
@@ -35,7 +35,7 @@ export const createMeetingSearchCommand = (program) => {
|
|
|
35
35
|
const command = search
|
|
36
36
|
.command("meetings")
|
|
37
37
|
.description("find words in stored meetings, indexing new ones first")
|
|
38
|
-
.argument("<query>", 'words, OR, (groups), -word, "a phrase", "a b"~N (within N lines), exact:word, from:"Name" (who said the line); a word also matches its other forms and spellings speech recognition garbled')
|
|
38
|
+
.argument("<query>", 'words, OR, (groups), -word, "a phrase", "a b"~N (within N lines), exact:word, from:"Name" (who said the line); date>=30d, project:, tag:, topic: choose meetings; a word also matches its other forms and spellings speech recognition garbled')
|
|
39
39
|
.option("--meeting-id <id>", "only this stored meeting", integer)
|
|
40
40
|
.option("--identity-id <id>", "meetings with this participant identity; does not say who spoke a hit", integer)
|
|
41
41
|
.option("--person-id <id>", "meetings with a participant linked to this person", integer)
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import { type QueryAst } from "@wirecat/cli-base/query";
|
|
2
|
+
import type { MeetingHitSearchRequest } from "./search-store.js";
|
|
2
3
|
/** A transcript line holds a sentence or two; further than this is a different part of the meeting. */
|
|
3
4
|
export declare const MOST_LINES_APART = 20;
|
|
4
5
|
/** The messages query language over meeting text: the same fields, a narrower set of operators. */
|
|
5
6
|
export declare const MEETING_HIT_QUERY: import("@wirecat/cli-base/query").QueryRegistry;
|
|
7
|
+
/**
|
|
8
|
+
* `budget project:RES date>=30d` → the words, and the meetings to look in as filters. A meeting field must be
|
|
9
|
+
* joined to the words with AND and given once, in the query or as an option. Text the language refuses is left as
|
|
10
|
+
* typed, for the caller's own error or for a search by meaning.
|
|
11
|
+
*/
|
|
12
|
+
export declare const liftMeetingFields: (query: string, request: MeetingHitSearchRequest, now?: number) => {
|
|
13
|
+
query: string;
|
|
14
|
+
request: MeetingHitSearchRequest;
|
|
15
|
+
};
|
|
6
16
|
/** What a person typed: the CLI, MCP and batch queries. */
|
|
7
17
|
export declare const parseMeetingQuery: (query: string) => QueryAst;
|
|
8
18
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { dateRange, defineQuery, parseLucene, queryError, timezoneOf, validateQuery, } from "@wirecat/cli-base/query";
|
|
2
3
|
const context = {
|
|
3
4
|
command: "search meetings",
|
|
4
5
|
guide: "https://github.com/WireCatLabs/cli-core/blob/main/docs/search/query-language.md#встречи",
|
|
@@ -40,6 +41,89 @@ export const MEETING_HIT_QUERY = defineQuery({
|
|
|
40
41
|
},
|
|
41
42
|
],
|
|
42
43
|
});
|
|
44
|
+
/** Fields that choose meetings rather than lines: lifted out of the query into the search's own filters. */
|
|
45
|
+
const MEETING_FIELDS = defineQuery({
|
|
46
|
+
entity: "meeting_hit",
|
|
47
|
+
backend: "sql",
|
|
48
|
+
fields: [
|
|
49
|
+
...MEETING_HIT_QUERY.fields,
|
|
50
|
+
{
|
|
51
|
+
name: "date",
|
|
52
|
+
type: "time",
|
|
53
|
+
operators: ["term", "range"],
|
|
54
|
+
example: "date>=30d",
|
|
55
|
+
relation: "meetings that started then; the same as --since-time and --until-time",
|
|
56
|
+
},
|
|
57
|
+
...["project", "tag", "topic"].map((name) => ({
|
|
58
|
+
name,
|
|
59
|
+
type: "kw",
|
|
60
|
+
operators: words,
|
|
61
|
+
example: `${name}:"Example name"`,
|
|
62
|
+
relation: `meetings in this ${name}, by name or alias; the same as --${name}`,
|
|
63
|
+
})),
|
|
64
|
+
],
|
|
65
|
+
});
|
|
66
|
+
const LIFTED = new Set(["date", "project", "tag", "topic"]);
|
|
67
|
+
/**
|
|
68
|
+
* `budget project:RES date>=30d` → the words, and the meetings to look in as filters. A meeting field must be
|
|
69
|
+
* joined to the words with AND and given once, in the query or as an option. Text the language refuses is left as
|
|
70
|
+
* typed, for the caller's own error or for a search by meaning.
|
|
71
|
+
*/
|
|
72
|
+
export const liftMeetingFields = (query, request, now) => {
|
|
73
|
+
let ast;
|
|
74
|
+
try {
|
|
75
|
+
ast = validateQuery(parseLucene(query, { defaultField: "text", proximity: true, ...context }), MEETING_FIELDS, context);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (error instanceof CliError)
|
|
79
|
+
return { query, request };
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
const top = ast.root.kind === "boolean" ? ast.root.clauses : [{ occur: "must", node: ast.root }];
|
|
83
|
+
const lifted = top.filter((clause) => clause.node.kind === "predicate" && LIFTED.has(clause.node.field));
|
|
84
|
+
const misplaced = (node, nested) => node.kind === "predicate"
|
|
85
|
+
? nested && LIFTED.has(node.field)
|
|
86
|
+
? node
|
|
87
|
+
: undefined
|
|
88
|
+
: node.clauses.map((clause) => misplaced(clause.node, true)).find(Boolean);
|
|
89
|
+
const wrong = lifted.find((clause) => clause.occur !== "must")?.node ??
|
|
90
|
+
top.map((clause) => (clause.node.kind === "boolean" ? misplaced(clause.node, true) : undefined)).find(Boolean);
|
|
91
|
+
if (wrong)
|
|
92
|
+
queryError("query.unsupported-field", wrong.span, `${wrong.field}: chooses meetings; join it to the words with AND, outside OR, NOT and groups`, context);
|
|
93
|
+
if (!lifted.length)
|
|
94
|
+
return { query, request };
|
|
95
|
+
const next = { ...request };
|
|
96
|
+
const once = (name, flag) => {
|
|
97
|
+
if (next[name] !== undefined)
|
|
98
|
+
throw new CliError("validation_error", `give ${flag.replace(/^--/, "")} once: as ${flag} or in the query`);
|
|
99
|
+
};
|
|
100
|
+
for (const { node } of lifted) {
|
|
101
|
+
if (node.field === "date") {
|
|
102
|
+
once("since", "--since-time");
|
|
103
|
+
once("until", "--until-time");
|
|
104
|
+
const range = dateRange(node.value, node.operator === "range" ? (node.upper ?? "*") : node.value, node.lower_inclusive !== false, node.upper_inclusive !== false, timezoneOf(), node.span, now);
|
|
105
|
+
if (range.lower !== undefined)
|
|
106
|
+
next.since = range.lower + (range.lower_inclusive ? 0 : 1);
|
|
107
|
+
if (range.upper !== undefined)
|
|
108
|
+
next.until = range.upper - (range.upper_inclusive ? 0 : 1);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
const field = node.field;
|
|
112
|
+
once(field, `--${field}`);
|
|
113
|
+
next[field] = node.value;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
let rest = query;
|
|
117
|
+
for (const { node } of [...lifted].sort((a, b) => b.node.span.start - a.node.span.start))
|
|
118
|
+
rest = `${rest.slice(0, node.span.start)}${rest.slice(node.span.end)}`;
|
|
119
|
+
rest = rest
|
|
120
|
+
.replace(/^(?:\s*AND\b)+|(?:\bAND\s*)+$/g, "")
|
|
121
|
+
.replace(/\bAND(\s+AND\b)+/g, "AND")
|
|
122
|
+
.trim();
|
|
123
|
+
if (!rest)
|
|
124
|
+
throw new CliError("validation_error", "give words to find besides date:, project:, tag: and topic:");
|
|
125
|
+
return { query: rest, request: next };
|
|
126
|
+
};
|
|
43
127
|
/** What a person typed: the CLI, MCP and batch queries. */
|
|
44
128
|
export const parseMeetingQuery = (query) => validateQuery(parseLucene(query, { defaultField: "text", proximity: true, ...context }), MEETING_HIT_QUERY, context);
|
|
45
129
|
/**
|
package/dist/meetings/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { linkEvent } from "./events.js";
|
|
|
6
6
|
export { exportMeeting, type MeetingExport, type MeetingExportFormat, type MeetingExportOptions, serializeVtt, } from "./export.js";
|
|
7
7
|
export { type FindingEvidence, type FindingOutcome, findingHash, MEETING_FINDINGS_KIND, type MeetingFactFinding, type MeetingFinding, type MeetingFindingsInput, type MeetingFindingsReceipt, type MeetingTaskFinding, meetingFindingFingerprint, validateMeetingFindings, } from "./findings.js";
|
|
8
8
|
export { type Finding, type FindingsInput, type FindingsReceipt, type FindingsRules, findingFingerprint, validateFindings, } from "./findings-rules.js";
|
|
9
|
-
export { MEETING_HIT_QUERY, meetingWordsQuery, parseMeetingQuery } from "./hit-query.js";
|
|
9
|
+
export { liftMeetingFields, MEETING_HIT_QUERY, meetingWordsQuery, parseMeetingQuery } from "./hit-query.js";
|
|
10
10
|
export { type ImportFile, importFiles } from "./import.js";
|
|
11
11
|
export { MEETING_ISSUES, MEETING_QUERY, type MeetingIssue, type MeetingItem, type MeetingOwner, meeting, meetingIssuesOf, meetingItem, } from "./item.js";
|
|
12
12
|
export { CHAT_MESSAGES_READ, type MeetingChatLine, type MeetingChatLinesInput, type MeetingChatLinesStore, } from "./links.js";
|
package/dist/meetings/index.js
CHANGED
|
@@ -4,7 +4,7 @@ export { linkEvent } from "./events.js";
|
|
|
4
4
|
export { exportMeeting, serializeVtt, } from "./export.js";
|
|
5
5
|
export { findingHash, MEETING_FINDINGS_KIND, meetingFindingFingerprint, validateMeetingFindings, } from "./findings.js";
|
|
6
6
|
export { findingFingerprint, validateFindings, } from "./findings-rules.js";
|
|
7
|
-
export { MEETING_HIT_QUERY, meetingWordsQuery, parseMeetingQuery } from "./hit-query.js";
|
|
7
|
+
export { liftMeetingFields, MEETING_HIT_QUERY, meetingWordsQuery, parseMeetingQuery } from "./hit-query.js";
|
|
8
8
|
export { importFiles } from "./import.js";
|
|
9
9
|
export { MEETING_ISSUES, MEETING_QUERY, meeting, meetingIssuesOf, meetingItem, } from "./item.js";
|
|
10
10
|
export { CHAT_MESSAGES_READ, } from "./links.js";
|
|
@@ -450,7 +450,8 @@ export const keyForCommand = (path) => {
|
|
|
450
450
|
return path.join(".");
|
|
451
451
|
if (top === "messages" && next === "sync")
|
|
452
452
|
return "messages";
|
|
453
|
-
if (SHOW_MESSAGES.has(top) ||
|
|
453
|
+
if (SHOW_MESSAGES.has(top) ||
|
|
454
|
+
(top === "contacts" && (next === "context" || next === "prepare" || next === "timeline")))
|
|
454
455
|
return "messages";
|
|
455
456
|
if (RESOURCES.includes(top))
|
|
456
457
|
return path.join(".");
|
package/dist/services/index.d.ts
CHANGED
|
@@ -69,6 +69,7 @@ export { type MeetingLink, type MeetingLinksRequest, readMeetingLinks } from "./
|
|
|
69
69
|
export { createMeetingProjectCandidateReader } from "./meeting-project-candidates.js";
|
|
70
70
|
export { readMeetingProjectDiscovery } from "./meeting-project-discovery.js";
|
|
71
71
|
export { type MeetingReviewRequest, readMeetingReview } from "./meeting-review.js";
|
|
72
|
+
export { type MeetingSearchesService, type MeetingSearchParams, meetingSearchesService, type SavedMeetingSearch, } from "./meeting-searches.js";
|
|
72
73
|
export { type MeetingStatsRequest, readMeetingStats } from "./meeting-stats.js";
|
|
73
74
|
export { chatIdIn, storedChatId } from "./message-resolution.js";
|
|
74
75
|
export type { AroundWindow, FoundMessage, ListWindow, MessagesService, MessageTarget, Pinned, Reacted, SearchFound, SearchQuery, SendRequest, Starred, } from "./messages.js";
|
|
@@ -80,14 +81,16 @@ export type { ContactSync, PeopleService } from "./people.js";
|
|
|
80
81
|
export { peopleService, phoneOf } from "./people.js";
|
|
81
82
|
export type { ContextMessage, ContextOptions, NotReadReason, PersonContext, SharedChat, } from "./person-context.js";
|
|
82
83
|
export { CONTEXT_BYTES, CONTEXT_MESSAGES, identityIn, personContext } from "./person-context.js";
|
|
84
|
+
export type { PersonPreparation, WaitingDialog } from "./person-preparation.js";
|
|
85
|
+
export { PREPARE_BYTES, personPreparation } from "./person-preparation.js";
|
|
83
86
|
export type { PersonTimeline, Scope, TimelineItem, TimelineOptions } from "./person-timeline.js";
|
|
84
87
|
export { personTimeline, SCOPES, TIMELINE_ITEMS, TIMELINE_MAX } from "./person-timeline.js";
|
|
85
88
|
export type { RankedRow, RankingFound, RankingQuery, RankingsService } from "./rankings.js";
|
|
86
89
|
export { rankingsService } from "./rankings.js";
|
|
87
90
|
export type { SearchParams } from "./search-record.js";
|
|
88
91
|
export { searchRecordOf } from "./search-record.js";
|
|
89
|
-
export type { ResolvedSearch, SearchesService } from "./searches.js";
|
|
90
|
-
export { searchesService } from "./searches.js";
|
|
92
|
+
export type { ResolvedSearch, SavedSearchItem, SearchesService } from "./searches.js";
|
|
93
|
+
export { savedSearchItem, searchesService } from "./searches.js";
|
|
91
94
|
export type { TagsAdded, TagsRemoved, TagsService, TagTargetInput, TagTargetView } from "./tags.js";
|
|
92
95
|
export { tagsService } from "./tags.js";
|
|
93
96
|
export type { ClosedTask, TaskCard, TaskItem, TaskList, TaskListRequest, TasksService, TaskView, } from "./tasks.js";
|
package/dist/services/index.js
CHANGED
|
@@ -50,6 +50,7 @@ export { readMeetingLinks } from "./meeting-links.js";
|
|
|
50
50
|
export { createMeetingProjectCandidateReader } from "./meeting-project-candidates.js";
|
|
51
51
|
export { readMeetingProjectDiscovery } from "./meeting-project-discovery.js";
|
|
52
52
|
export { readMeetingReview } from "./meeting-review.js";
|
|
53
|
+
export { meetingSearchesService, } from "./meeting-searches.js";
|
|
53
54
|
export { readMeetingStats } from "./meeting-stats.js";
|
|
54
55
|
export { chatIdIn, storedChatId } from "./message-resolution.js";
|
|
55
56
|
export { DELETE_AT_ONCE, messagesService } from "./messages.js";
|
|
@@ -57,10 +58,11 @@ export { searchStore } from "./messages-archive-search.js";
|
|
|
57
58
|
export { moderationService } from "./moderation.js";
|
|
58
59
|
export { peopleService, phoneOf } from "./people.js";
|
|
59
60
|
export { CONTEXT_BYTES, CONTEXT_MESSAGES, identityIn, personContext } from "./person-context.js";
|
|
61
|
+
export { PREPARE_BYTES, personPreparation } from "./person-preparation.js";
|
|
60
62
|
export { personTimeline, SCOPES, TIMELINE_ITEMS, TIMELINE_MAX } from "./person-timeline.js";
|
|
61
63
|
export { rankingsService } from "./rankings.js";
|
|
62
64
|
export { searchRecordOf } from "./search-record.js";
|
|
63
|
-
export { searchesService } from "./searches.js";
|
|
65
|
+
export { savedSearchItem, searchesService } from "./searches.js";
|
|
64
66
|
export { tagsService } from "./tags.js";
|
|
65
67
|
export { closedStateOf, taskStateOf, tasksService, taskTypeOf, taskView } from "./tasks.js";
|
|
66
68
|
export { topicsService } from "./topics.js";
|
|
@@ -38,7 +38,7 @@ export interface MeetingCombinedHit {
|
|
|
38
38
|
*/
|
|
39
39
|
export declare const searchMeetingsCombined: (store: Pick<MessageStore, "meetings" | "meetingVectors"> & {
|
|
40
40
|
knowledge?: MessageStore["knowledge"];
|
|
41
|
-
}, embedder: MeetingEmbedder,
|
|
41
|
+
}, embedder: MeetingEmbedder, typed: string, request: MeetingCombinedRequest) => Promise<{
|
|
42
42
|
items: {
|
|
43
43
|
/** A transcript line to cite: the best word hit in the passage, else the passage's first line. */
|
|
44
44
|
ref: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { CliError } from "@wirecat/cli-base";
|
|
3
3
|
import { meaningFloor } from "../embeddings/models.js";
|
|
4
|
-
import { formatMeetingReference, parseMeetingReference, } from "../meetings/index.js";
|
|
4
|
+
import { formatMeetingReference, liftMeetingFields, parseMeetingReference, } from "../meetings/index.js";
|
|
5
5
|
import { questionTerms } from "../search/question-plan.js";
|
|
6
6
|
import { refreshMeetingIndex, resolveMeetingSelection } from "./meeting-hit-search.js";
|
|
7
7
|
import { validateVector } from "./meeting-semantic.js";
|
|
@@ -27,8 +27,9 @@ const cursorOf = (value, key) => {
|
|
|
27
27
|
* («что решили по цене?») searches its content words; other text is the meeting query language. A word hit inside
|
|
28
28
|
* a passage the meaning search found joins it, so one discussion found both ways ranks first.
|
|
29
29
|
*/
|
|
30
|
-
export const searchMeetingsCombined = async (store, embedder,
|
|
31
|
-
const { maxChunks, maxRows, maxTextBytes, ...
|
|
30
|
+
export const searchMeetingsCombined = async (store, embedder, typed, request) => {
|
|
31
|
+
const { maxChunks, maxRows, maxTextBytes, ...chosen } = request;
|
|
32
|
+
const { query, request: words } = liftMeetingFields(typed, chosen);
|
|
32
33
|
const { input, selection } = await resolveMeetingSelection(store, words);
|
|
33
34
|
const { after, limit = 20, maxReadBytes, ...filters } = input;
|
|
34
35
|
const terms = questionTerms(query);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type MeetingHitSearchRequest } from "../meetings/index.js";
|
|
2
2
|
import type { VocabularyKind } from "../store/index.js";
|
|
3
3
|
import type { MessageStore } from "../store/store.js";
|
|
4
4
|
export type { MeetingHitSearchRequest } from "../meetings/index.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { liftMeetingFields } from "../meetings/index.js";
|
|
2
3
|
/** Each batch commits on its own, so a search cut short by its deadline keeps the progress it made. */
|
|
3
4
|
export const refreshMeetingIndex = async (meetings, accountId, signal) => {
|
|
4
5
|
let before = Number.POSITIVE_INFINITY;
|
|
@@ -54,10 +55,11 @@ export const resolveMeetingSelection = async (store, request) => {
|
|
|
54
55
|
return { input, selection };
|
|
55
56
|
};
|
|
56
57
|
export const searchMeetingHits = async (store, query, request, options = {}) => {
|
|
57
|
-
const
|
|
58
|
+
const lifted = liftMeetingFields(query, request);
|
|
59
|
+
const { input, selection } = await resolveMeetingSelection(store, lifted.request);
|
|
58
60
|
if (options.refreshIndex !== false)
|
|
59
61
|
await refreshMeetingIndex(store.meetings, input.accountId, input.signal);
|
|
60
|
-
const result = await store.meetings.searchHits(query, input);
|
|
62
|
+
const result = await store.meetings.searchHits(lifted.query, input);
|
|
61
63
|
input.signal?.throwIfAborted();
|
|
62
64
|
return { ...result, selection };
|
|
63
65
|
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { SearchesStore, StoredSearch } from "../store/store.js";
|
|
2
|
+
/** A meeting search as `search meetings` takes it: the query and the options typed with it, already checked. */
|
|
3
|
+
export interface MeetingSearchParams {
|
|
4
|
+
query: string;
|
|
5
|
+
options: Readonly<Record<string, string | number | boolean>>;
|
|
6
|
+
}
|
|
7
|
+
export interface SavedMeetingSearch extends MeetingSearchParams {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string | null;
|
|
10
|
+
}
|
|
11
|
+
export interface MeetingSearchesService {
|
|
12
|
+
create(name: string, params: MeetingSearchParams, options?: {
|
|
13
|
+
replace?: boolean;
|
|
14
|
+
}): Promise<StoredSearch>;
|
|
15
|
+
list(): Promise<StoredSearch[]>;
|
|
16
|
+
show(reference: string): Promise<StoredSearch>;
|
|
17
|
+
delete(reference: string): Promise<StoredSearch>;
|
|
18
|
+
/** `--saved`: the stored query and options, counted as one more run. */
|
|
19
|
+
run(reference: string): Promise<SavedMeetingSearch>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Saved meeting searches, in the table every CLI on the store shares: only rows of their own kind are listed, and a
|
|
23
|
+
* saved message search is refused by name rather than run as a meeting one.
|
|
24
|
+
*/
|
|
25
|
+
export declare const meetingSearchesService: (searches: () => Promise<SearchesStore>) => MeetingSearchesService;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { CliError, singleLine } from "@wirecat/cli-base";
|
|
2
|
+
import { searchRecordOf } from "./search-record.js";
|
|
3
|
+
import { nameOf } from "./searches.js";
|
|
4
|
+
const KIND = ["meetings-search"];
|
|
5
|
+
/**
|
|
6
|
+
* Saved meeting searches, in the table every CLI on the store shares: only rows of their own kind are listed, and a
|
|
7
|
+
* saved message search is refused by name rather than run as a meeting one.
|
|
8
|
+
*/
|
|
9
|
+
export const meetingSearchesService = (searches) => {
|
|
10
|
+
const found = async (reference) => {
|
|
11
|
+
const search = await (await searches()).storedSearch(reference.trim().toLowerCase());
|
|
12
|
+
if (!search)
|
|
13
|
+
throw new CliError("not_found", `no saved search "${singleLine(reference)}"`);
|
|
14
|
+
if (search.command !== "meetings-search")
|
|
15
|
+
throw new CliError("validation_error", `"${singleLine(reference)}" is a saved search of messages, not meetings`, {
|
|
16
|
+
reason: "search.kind",
|
|
17
|
+
command: search.command,
|
|
18
|
+
});
|
|
19
|
+
return search;
|
|
20
|
+
};
|
|
21
|
+
const record = ({ query, options }) => searchRecordOf("meetings-search", { ...options, text: query });
|
|
22
|
+
return {
|
|
23
|
+
create: async (name, params, { replace = false } = {}) => (await searches()).saveSearch(nameOf(name), record(params), { replace }),
|
|
24
|
+
list: async () => (await searches()).savedSearches(KIND),
|
|
25
|
+
show: found,
|
|
26
|
+
delete: async (reference) => (await searches()).deleteSearch((await found(reference)).id),
|
|
27
|
+
run: async (reference) => {
|
|
28
|
+
const search = await found(reference);
|
|
29
|
+
const { text, ...options } = search.params;
|
|
30
|
+
const query = { query: String(text ?? ""), options };
|
|
31
|
+
await (await searches()).recordSearch(record(query), { saved: search.id });
|
|
32
|
+
return { id: search.id, name: search.name, ...query };
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
};
|
|
@@ -5,6 +5,7 @@ import type { PersonRecord } from "../store/store.js";
|
|
|
5
5
|
import type { PageWindow } from "./chats.js";
|
|
6
6
|
import { type ServiceDeps } from "./deps.js";
|
|
7
7
|
import { type ContextOptions, type Detail, type PersonContext, type PersonMessages } from "./person-context.js";
|
|
8
|
+
import { type PersonPreparation } from "./person-preparation.js";
|
|
8
9
|
import { type PersonTimeline, type TimelineOptions } from "./person-timeline.js";
|
|
9
10
|
export interface ContactSync {
|
|
10
11
|
added: number;
|
|
@@ -52,6 +53,10 @@ export interface PeopleService {
|
|
|
52
53
|
}>>;
|
|
53
54
|
/** What the store holds about them, across every messenger linked to them; never connects. */
|
|
54
55
|
context(person: string, options?: ContextOptions): Promise<PersonContext>;
|
|
56
|
+
/** `context` and what to know before talking to them: chats waiting on you, tasks, facts, notes, meetings. */
|
|
57
|
+
prepare(person: string, options?: ContextOptions & {
|
|
58
|
+
notes?: boolean;
|
|
59
|
+
}): Promise<PersonPreparation>;
|
|
55
60
|
/** Everything they took part in, from the store's involvement index; never connects. */
|
|
56
61
|
timeline(person: string, options?: TimelineOptions): Promise<PersonTimeline>;
|
|
57
62
|
/**
|
package/dist/services/people.js
CHANGED
|
@@ -8,6 +8,7 @@ import { newOperationId } from "../sends/send-id.js";
|
|
|
8
8
|
import { fromStore, storeIfOpen } from "./deps.js";
|
|
9
9
|
import { storedChatId } from "./message-resolution.js";
|
|
10
10
|
import { CHAT_MESSAGES, identityIn, personContext, personMessages, } from "./person-context.js";
|
|
11
|
+
import { personPreparation } from "./person-preparation.js";
|
|
11
12
|
import { personProfile } from "./person-profile.js";
|
|
12
13
|
import { personTimeline } from "./person-timeline.js";
|
|
13
14
|
export const peopleService = (deps) => {
|
|
@@ -75,6 +76,7 @@ export const peopleService = (deps) => {
|
|
|
75
76
|
},
|
|
76
77
|
profile: (person) => personProfile(deps, person),
|
|
77
78
|
context: async (person, options) => personContext(await deps.store(), await deps.account(), person, options),
|
|
79
|
+
prepare: async (person, options) => personPreparation(await deps.store(), await deps.account(), person, options),
|
|
78
80
|
timeline: async (person, options) => personTimeline(await deps.store(), await deps.account(), person, options),
|
|
79
81
|
messagesIn: async (person, { chats, limit, detail, fetch = false }) => {
|
|
80
82
|
const store = await deps.store();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ChatKind, Id, Provider } from "../domain/models.js";
|
|
2
|
-
import type { AccountKey, IdentityRef, MessageStore, PersonRecord } from "../store/store.js";
|
|
1
|
+
import type { ChatKind, Id, Message, Provider } from "../domain/models.js";
|
|
2
|
+
import type { AccountKey, IdentityRef, LinkedIdentity, MessageStore, PersonRecord } from "../store/store.js";
|
|
3
3
|
export declare const CONTEXT_MESSAGES = 10;
|
|
4
4
|
export declare const CONTEXT_BYTES: number;
|
|
5
5
|
export interface ContextMessage {
|
|
@@ -60,6 +60,16 @@ export interface ContextOptions {
|
|
|
60
60
|
/** ms: nothing older. */
|
|
61
61
|
since?: number;
|
|
62
62
|
}
|
|
63
|
+
export declare const toContext: (key: AccountKey, message: Message, chatTitle: string | null) => ContextMessage;
|
|
64
|
+
/**
|
|
65
|
+
* The accounts each identity is read in: for the messenger asked, only the account asked about — a
|
|
66
|
+
* second account of the same messenger is somebody else's view; for a linked messenger, every account
|
|
67
|
+
* of this store that saw that identity.
|
|
68
|
+
*/
|
|
69
|
+
export declare const readsOf: (record: PersonRecord, asked: AccountKey) => {
|
|
70
|
+
key: AccountKey;
|
|
71
|
+
identity: LinkedIdentity;
|
|
72
|
+
}[];
|
|
63
73
|
/**
|
|
64
74
|
* What the store knows about one person, across every identity linked to them. Reads the store
|
|
65
75
|
* only: nothing is fetched, nothing marked read. Identity is by id, never by name.
|
|
@@ -4,7 +4,7 @@ import { pickPerson } from "../resolve.js";
|
|
|
4
4
|
import { messageOfEmail } from "../store/store.js";
|
|
5
5
|
export const CONTEXT_MESSAGES = 10;
|
|
6
6
|
export const CONTEXT_BYTES = 64 * 1024;
|
|
7
|
-
const toContext = (key, message, chatTitle) => ({
|
|
7
|
+
export const toContext = (key, message, chatTitle) => ({
|
|
8
8
|
locator: formatLocator({ ...key, chat: message.chatId, message: message.id }),
|
|
9
9
|
provider: key.provider,
|
|
10
10
|
account: key.account,
|
|
@@ -49,7 +49,7 @@ const newestFirst = (a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp);
|
|
|
49
49
|
* second account of the same messenger is somebody else's view; for a linked messenger, every account
|
|
50
50
|
* of this store that saw that identity.
|
|
51
51
|
*/
|
|
52
|
-
const readsOf = (record, asked) => record.identities.flatMap((identity) => (identity.provider === asked.provider ? [asked.account] : identity.accounts)
|
|
52
|
+
export const readsOf = (record, asked) => record.identities.flatMap((identity) => (identity.provider === asked.provider ? [asked.account] : identity.accounts)
|
|
53
53
|
.filter((account) => identity.accounts.includes(account))
|
|
54
54
|
.map((account) => ({ key: { provider: identity.provider, account }, identity })));
|
|
55
55
|
/**
|