@wirecat/cli-core 0.550.0 → 0.552.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 +235 -0
- package/README.md +1 -1
- package/THIRD_PARTY_NOTICES +41 -0
- package/dist/background/schedules.d.ts +12 -1
- package/dist/background/schedules.js +65 -24
- package/dist/background/units.d.ts +16 -9
- package/dist/background/units.js +26 -11
- package/dist/calendar/ics.d.ts +1 -1
- package/dist/calendar/ics.js +5 -2
- package/dist/calendar/windows-zones.generated.d.ts +1 -0
- package/dist/calendar/windows-zones.generated.js +146 -0
- package/dist/cli/compact.js +2 -0
- package/dist/cli/context.d.ts +2 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +3 -0
- package/dist/cli/meeting-findings-input.js +4 -3
- package/dist/cli/meeting-preparation-answer.d.ts +3 -0
- package/dist/cli/meeting-preparation-projects.d.ts +3 -0
- package/dist/cli/meeting-preparation-read.d.ts +3 -0
- package/dist/cli/messenger/backfill-command.js +28 -1
- package/dist/cli/messenger/context.js +1 -0
- package/dist/cli/messenger/history-gaps.js +4 -4
- package/dist/cli/messenger/mcp-command.js +1 -0
- package/dist/cli/messenger/messages-delete-command.js +7 -8
- package/dist/cli/messenger/projects-command.js +47 -2
- package/dist/cli/messenger/search-answers.js +8 -1
- package/dist/cli/messenger/serve-catch-up.d.ts +43 -0
- package/dist/cli/messenger/serve-catch-up.js +90 -0
- package/dist/cli/messenger/serve-command.js +54 -1
- package/dist/cli/messenger/server-command.d.ts +4 -3
- package/dist/cli/messenger/server-command.js +30 -12
- package/dist/cli/payment-query.d.ts +8 -0
- package/dist/cli/payment-query.js +27 -0
- package/dist/cli/program.js +9 -4
- package/dist/cli/project-status-rows.d.ts +8 -0
- package/dist/cli/project-status-rows.js +102 -0
- package/dist/cli/project-status.d.ts +132 -0
- package/dist/cli/project-status.js +248 -0
- package/dist/cli/schedule/schedule-command.js +5 -1
- package/dist/cli/steps.js +4 -1
- package/dist/domain/knowledge.d.ts +1 -1
- package/dist/domain/knowledge.js +1 -1
- package/dist/domain/status-words.d.ts +3 -0
- package/dist/domain/status-words.js +44 -0
- package/dist/entities/fact.d.ts +12 -1
- package/dist/entities/fact.js +13 -0
- package/dist/entities/hit.d.ts +5 -0
- package/dist/entities/hit.js +5 -0
- package/dist/entities/index.d.ts +45 -2
- package/dist/entities/listener.d.ts +6 -0
- package/dist/entities/listener.js +1 -0
- package/dist/entities/meeting-findings.d.ts +1 -1
- package/dist/entities/meeting-preparation.d.ts +3 -0
- package/dist/entities/message-transfer.d.ts +16 -0
- package/dist/entities/message-transfer.js +6 -0
- package/dist/errors/reasons.js +1 -0
- package/dist/mcp/app-server.js +1 -0
- package/dist/mcp/http/oauth.d.ts +2 -1
- package/dist/mcp/http/oauth.js +2 -2
- package/dist/mcp/http/serve.d.ts +2 -0
- package/dist/mcp/http/serve.js +2 -1
- package/dist/mcp/meeting-preparation.d.ts +3 -0
- package/dist/mcp/server.js +1 -0
- package/dist/mcp/surface.js +1 -1
- package/dist/meetings/answers.d.ts +5 -0
- package/dist/meetings/answers.js +8 -0
- package/dist/meetings/cli/index.js +14 -0
- package/dist/meetings/findings-rules.d.ts +14 -3
- package/dist/meetings/findings-rules.js +16 -2
- package/dist/meetings/index.d.ts +5 -3
- package/dist/meetings/index.js +3 -1
- package/dist/meetings/ingest.js +2 -1
- package/dist/meetings/language.d.ts +5 -0
- package/dist/meetings/language.js +57 -0
- package/dist/meetings/mcp/search.d.ts +3 -0
- package/dist/meetings/meeting-context.d.ts +2 -3
- package/dist/meetings/meeting-context.js +3 -4
- package/dist/meetings/payment.d.ts +27 -0
- package/dist/meetings/payment.js +81 -0
- package/dist/meetings/project-evidence-answer.d.ts +3 -0
- package/dist/meetings/result-items.d.ts +9 -0
- package/dist/meetings/result-items.js +7 -0
- package/dist/meetings/store.d.ts +22 -0
- package/dist/meetings/testing/memory.js +3 -0
- package/dist/meetings/transcription.d.ts +2 -0
- package/dist/meetings/transcription.js +5 -1
- package/dist/search/lucene/types.d.ts +7 -0
- package/dist/search/lucene/types.js +11 -1
- package/dist/services/archive.d.ts +13 -0
- package/dist/services/archive.js +56 -5
- package/dist/services/deps.d.ts +3 -0
- package/dist/services/history-limits.d.ts +2 -0
- package/dist/services/index.d.ts +4 -1
- package/dist/services/index.js +4 -1
- package/dist/services/job-query.d.ts +5 -2
- package/dist/services/job-query.js +5 -2
- package/dist/services/link-scorers/alias-hits.d.ts +7 -0
- package/dist/services/link-scorers/alias-hits.js +54 -0
- package/dist/services/link-scorers/chat-senders.d.ts +6 -0
- package/dist/services/link-scorers/chat-senders.js +56 -0
- package/dist/services/link-scorers/chat-title.d.ts +6 -0
- package/dist/services/link-scorers/chat-title.js +44 -0
- package/dist/services/link-scorers/email-domain.d.ts +6 -0
- package/dist/services/link-scorers/email-domain.js +64 -0
- package/dist/services/link-scorers/index.d.ts +9 -0
- package/dist/services/link-scorers/index.js +8 -0
- package/dist/services/link-scorers/mail-thread.d.ts +3 -0
- package/dist/services/link-scorers/mail-thread.js +74 -0
- package/dist/services/link-scorers/member-chats.d.ts +3 -0
- package/dist/services/link-scorers/member-chats.js +40 -0
- package/dist/services/link-scorers/member-dialog.d.ts +3 -0
- package/dist/services/link-scorers/member-dialog.js +34 -0
- package/dist/services/link-scorers/scan.d.ts +41 -0
- package/dist/services/link-scorers/scan.js +244 -0
- package/dist/services/link-scorers/shared.d.ts +26 -0
- package/dist/services/link-scorers/shared.js +48 -0
- package/dist/services/link-scorers/types.d.ts +77 -0
- package/dist/services/link-scorers/types.js +1 -0
- package/dist/services/messages-archive-search.js +3 -2
- package/dist/services/messages-combined.js +2 -2
- package/dist/services/messages-discovery.js +2 -2
- package/dist/services/notes-search.js +4 -5
- package/dist/services/project-status/index.d.ts +71 -0
- package/dist/services/project-status/index.js +196 -0
- package/dist/services/project-status/items.d.ts +10 -0
- package/dist/services/project-status/items.js +23 -0
- package/dist/services/project-status/signals/amounts.d.ts +20 -0
- package/dist/services/project-status/signals/amounts.js +91 -0
- package/dist/services/project-status/signals/children.d.ts +13 -0
- package/dist/services/project-status/signals/children.js +28 -0
- package/dist/services/project-status/signals/coverage.d.ts +13 -0
- package/dist/services/project-status/signals/coverage.js +30 -0
- package/dist/services/project-status/signals/index.d.ts +3 -0
- package/dist/services/project-status/signals/index.js +21 -0
- package/dist/services/project-status/signals/last-message.d.ts +4 -0
- package/dist/services/project-status/signals/last-message.js +22 -0
- package/dist/services/project-status/signals/payments.d.ts +9 -0
- package/dist/services/project-status/signals/payments.js +24 -0
- package/dist/services/project-status/signals/promises.d.ts +17 -0
- package/dist/services/project-status/signals/promises.js +109 -0
- package/dist/services/project-status/signals/questions.d.ts +8 -0
- package/dist/services/project-status/signals/questions.js +38 -0
- package/dist/services/project-status/signals/reply-next.d.ts +19 -0
- package/dist/services/project-status/signals/reply-next.js +75 -0
- package/dist/services/project-status/signals/topics.d.ts +10 -0
- package/dist/services/project-status/signals/topics.js +50 -0
- package/dist/services/project-status/types.d.ts +84 -0
- package/dist/services/project-status/types.js +1 -0
- package/dist/services/proposals/index.d.ts +1 -0
- package/dist/services/proposals/index.js +1 -0
- package/dist/services/proposals/kinds/findings.d.ts +3 -0
- package/dist/services/proposals/kinds/findings.js +8 -1
- package/dist/services/search-all-meetings.d.ts +3 -0
- package/dist/services/search-all-meetings.js +6 -1
- package/dist/speech/local.d.ts +2 -0
- package/dist/speech/local.js +8 -1
- package/dist/speech/models.d.ts +2 -0
- package/dist/speech/models.js +2 -0
- package/dist/store/decisions.d.ts +5 -1
- package/dist/store/decisions.js +5 -1
- package/dist/store/driver.d.ts +11 -0
- package/dist/store/driver.js +13 -0
- package/dist/store/drivers/node-sqlite.d.ts +1 -1
- package/dist/store/drivers/node-sqlite.js +3 -0
- package/dist/store/evidence-findings.js +1 -0
- package/dist/store/index.d.ts +2 -1
- package/dist/store/index.js +1 -0
- package/dist/store/project-transfer/request.d.ts +1 -1
- package/dist/store/project-transfer/schema.d.ts +2 -2
- package/dist/store/project-transfer/snapshot-model.d.ts +2 -2
- package/dist/store/sqlite/evidence-findings.js +5 -1
- package/dist/store/sqlite/facts.d.ts +13 -0
- package/dist/store/sqlite/facts.js +64 -6
- package/dist/store/sqlite/findings-core.js +14 -1
- package/dist/store/sqlite/knowledge.d.ts +16 -2
- package/dist/store/sqlite/knowledge.js +1 -0
- package/dist/store/sqlite/lucene.d.ts +1 -1
- package/dist/store/sqlite/lucene.js +33 -7
- package/dist/store/sqlite/meeting-hit-search.js +10 -3
- package/dist/store/sqlite/meeting-languages.d.ts +4 -0
- package/dist/store/sqlite/meeting-languages.js +36 -0
- package/dist/store/sqlite/meetings.d.ts +2 -1
- package/dist/store/sqlite/meetings.js +9 -3
- package/dist/store/sqlite/note-search.js +2 -2
- package/dist/store/sqlite/project-transfer/snapshot.d.ts +1 -1
- package/dist/store/sqlite/relation-proposals.d.ts +29 -2
- package/dist/store/sqlite/relation-proposals.js +75 -4
- package/dist/store/store.d.ts +1 -0
- package/package.json +3 -3
- package/parity.json +11 -0
|
@@ -202,6 +202,8 @@ export declare const messageSyncChat: v.StrictObjectSchema<{
|
|
|
202
202
|
readonly reached_since: v.OptionalSchema<v.LiteralSchema<true, undefined>, undefined>;
|
|
203
203
|
readonly reached_last: v.OptionalSchema<v.LiteralSchema<true, undefined>, undefined>;
|
|
204
204
|
readonly stopped: v.OptionalSchema<v.LiteralSchema<true, undefined>, undefined>;
|
|
205
|
+
/** The history limits stopped the read: a group too large to read, or the window's start reached. */
|
|
206
|
+
readonly limited: v.OptionalSchema<v.PicklistSchema<["group_too_large", "older_than_window"], undefined>, undefined>;
|
|
205
207
|
readonly tasks: v.OptionalSchema<v.StrictObjectSchema<{
|
|
206
208
|
readonly added: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
207
209
|
readonly closed: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
@@ -250,6 +252,12 @@ export declare const messageSyncAll: v.StrictObjectSchema<{
|
|
|
250
252
|
readonly retryable: v.BooleanSchema<undefined>;
|
|
251
253
|
readonly retry_after_ms: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
|
|
252
254
|
}, v.UnknownSchema, undefined>, undefined>;
|
|
255
|
+
readonly limited: v.OptionalSchema<v.PicklistSchema<["group_too_large", "older_than_window"], undefined>, undefined>;
|
|
256
|
+
}, undefined>, undefined>;
|
|
257
|
+
/** How many chats the history limits left unread (big groups) or read only to the window's start. */
|
|
258
|
+
readonly limited: v.OptionalSchema<v.StrictObjectSchema<{
|
|
259
|
+
readonly group_too_large: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
260
|
+
readonly older_than_window: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
253
261
|
}, undefined>, undefined>;
|
|
254
262
|
readonly tasks: v.OptionalSchema<v.StrictObjectSchema<{
|
|
255
263
|
readonly added: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
@@ -328,6 +336,8 @@ export declare const messageSync: v.UnionSchema<[v.StrictObjectSchema<{
|
|
|
328
336
|
readonly reached_since: v.OptionalSchema<v.LiteralSchema<true, undefined>, undefined>;
|
|
329
337
|
readonly reached_last: v.OptionalSchema<v.LiteralSchema<true, undefined>, undefined>;
|
|
330
338
|
readonly stopped: v.OptionalSchema<v.LiteralSchema<true, undefined>, undefined>;
|
|
339
|
+
/** The history limits stopped the read: a group too large to read, or the window's start reached. */
|
|
340
|
+
readonly limited: v.OptionalSchema<v.PicklistSchema<["group_too_large", "older_than_window"], undefined>, undefined>;
|
|
331
341
|
readonly tasks: v.OptionalSchema<v.StrictObjectSchema<{
|
|
332
342
|
readonly added: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
333
343
|
readonly closed: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
@@ -375,6 +385,12 @@ export declare const messageSync: v.UnionSchema<[v.StrictObjectSchema<{
|
|
|
375
385
|
readonly retryable: v.BooleanSchema<undefined>;
|
|
376
386
|
readonly retry_after_ms: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
|
|
377
387
|
}, v.UnknownSchema, undefined>, undefined>;
|
|
388
|
+
readonly limited: v.OptionalSchema<v.PicklistSchema<["group_too_large", "older_than_window"], undefined>, undefined>;
|
|
389
|
+
}, undefined>, undefined>;
|
|
390
|
+
/** How many chats the history limits left unread (big groups) or read only to the window's start. */
|
|
391
|
+
readonly limited: v.OptionalSchema<v.StrictObjectSchema<{
|
|
392
|
+
readonly group_too_large: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
393
|
+
readonly older_than_window: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
378
394
|
}, undefined>, undefined>;
|
|
379
395
|
readonly tasks: v.OptionalSchema<v.StrictObjectSchema<{
|
|
380
396
|
readonly added: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
@@ -46,6 +46,7 @@ const tasks = v.optional(v.strictObject({
|
|
|
46
46
|
held: v.optional(count),
|
|
47
47
|
held_chats: v.optional(count),
|
|
48
48
|
}));
|
|
49
|
+
const historyLimited = v.picklist(["group_too_large", "older_than_window"]);
|
|
49
50
|
export const messageSyncChat = v.strictObject({
|
|
50
51
|
issue: v.optional(actionableError),
|
|
51
52
|
resume: v.optional(v.strictObject({ before: v.optional(text) })),
|
|
@@ -59,6 +60,8 @@ export const messageSyncChat = v.strictObject({
|
|
|
59
60
|
reached_since: v.optional(v.literal(true)),
|
|
60
61
|
reached_last: v.optional(v.literal(true)),
|
|
61
62
|
stopped: v.optional(v.literal(true)),
|
|
63
|
+
/** The history limits stopped the read: a group too large to read, or the window's start reached. */
|
|
64
|
+
limited: v.optional(historyLimited),
|
|
62
65
|
tasks,
|
|
63
66
|
/** `--full-history`: the chat's pushed history is now kept in full; `false` when the store does not know the chat. */
|
|
64
67
|
history_included: v.optional(v.boolean()),
|
|
@@ -78,7 +81,10 @@ export const messageSyncAll = v.strictObject({
|
|
|
78
81
|
stopped: v.optional(v.literal(true)),
|
|
79
82
|
error: v.optional(text),
|
|
80
83
|
issue: v.optional(actionableError),
|
|
84
|
+
limited: v.optional(historyLimited),
|
|
81
85
|
})),
|
|
86
|
+
/** How many chats the history limits left unread (big groups) or read only to the window's start. */
|
|
87
|
+
limited: v.optional(v.strictObject({ group_too_large: count, older_than_window: count })),
|
|
82
88
|
tasks,
|
|
83
89
|
});
|
|
84
90
|
export const messageSyncStarted = v.strictObject({ job: id, pid: count, chat: text, log: text });
|
package/dist/errors/reasons.js
CHANGED
|
@@ -33,6 +33,7 @@ const SIGN_IN = [
|
|
|
33
33
|
["sign-in.client-file-invalid", "configuration_error", "the file is not a Desktop app OAuth client JSON"],
|
|
34
34
|
];
|
|
35
35
|
const OTHERS = [
|
|
36
|
+
["query.time-limit", "timeout", "the search took longer than its time limit; the query itself is valid"],
|
|
36
37
|
["model.offline", "permission_error", "downloading a model needs the network; --offline was requested"],
|
|
37
38
|
[
|
|
38
39
|
"contract.unacknowledged-override",
|
package/dist/mcp/app-server.js
CHANGED
|
@@ -144,6 +144,7 @@ export const serveAppOverHttp = async (command, context, options, http) => {
|
|
|
144
144
|
const listening = await serveOverHttp(build, {
|
|
145
145
|
...http,
|
|
146
146
|
appName: app.command,
|
|
147
|
+
reaches: `your ${options.name}`,
|
|
147
148
|
onCode: (code, expires) => context.renderer.note(context.env[mcpHttpMarker(app.envPrefix)] === undefined
|
|
148
149
|
? `login code for a new browser app: ${code} (until ${expires.toTimeString().slice(0, 5)}; a new one after each login)`
|
|
149
150
|
: `a new login code for a browser app: \`${app.command} ${context.settings.profile} mcp server status\``),
|
package/dist/mcp/http/oauth.d.ts
CHANGED
|
@@ -14,11 +14,12 @@ export interface OwnerLogin {
|
|
|
14
14
|
* the browser apps need dynamic registration — but nobody gets a token without the one-time code
|
|
15
15
|
* printed in the owner's terminal. Five wrong codes delay further attempts for thirty seconds.
|
|
16
16
|
*/
|
|
17
|
-
export declare const ownerLogin: ({ issuer, resource, file, appName, onCode, now, }: {
|
|
17
|
+
export declare const ownerLogin: ({ issuer, resource, file, appName, reaches, onCode, now, }: {
|
|
18
18
|
issuer: URL;
|
|
19
19
|
resource: URL;
|
|
20
20
|
file: string;
|
|
21
21
|
appName: string;
|
|
22
|
+
reaches?: string;
|
|
22
23
|
/** The code for the next login, for the owner's eyes only: their terminal, or the server's status file. */
|
|
23
24
|
onCode: (code: string, expires: Date) => void;
|
|
24
25
|
now?: () => number;
|
package/dist/mcp/http/oauth.js
CHANGED
|
@@ -51,7 +51,7 @@ const redirectAllowed = (uri) => {
|
|
|
51
51
|
* the browser apps need dynamic registration — but nobody gets a token without the one-time code
|
|
52
52
|
* printed in the owner's terminal. Five wrong codes delay further attempts for thirty seconds.
|
|
53
53
|
*/
|
|
54
|
-
export const ownerLogin = ({ issuer, resource, file, appName, onCode, now = () => Date.now(), }) => {
|
|
54
|
+
export const ownerLogin = ({ issuer, resource, file, appName, reaches = `your ${appName} data`, onCode, now = () => Date.now(), }) => {
|
|
55
55
|
const store = tokenFile(file);
|
|
56
56
|
const codes = new Map();
|
|
57
57
|
let login = { value: "", expires: 0 };
|
|
@@ -153,7 +153,7 @@ export const ownerLogin = ({ issuer, resource, file, appName, onCode, now = () =
|
|
|
153
153
|
.join("");
|
|
154
154
|
const who = escapeHtml(client.name ?? "an application");
|
|
155
155
|
const target = new URL(params.get("redirect_uri") ?? "");
|
|
156
|
-
const html = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${escapeHtml(appName)} login</title><style>body{font:16px system-ui,sans-serif;max-width:28rem;margin:3rem auto;padding:0 1rem;color:#1d2430}input[type=text]{font:20px ui-monospace,monospace;letter-spacing:.1em;width:100%;padding:.5rem;margin:.5rem 0 1rem;box-sizing:border-box}button{font:inherit;padding:.5rem 1rem}.error{color:#b42318}</style></head><body><h1>${escapeHtml(appName)}</h1><p>An app that calls itself <strong>${who}</strong> asks to reach
|
|
156
|
+
const html = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${escapeHtml(appName)} login</title><style>body{font:16px system-ui,sans-serif;max-width:28rem;margin:3rem auto;padding:0 1rem;color:#1d2430}input[type=text]{font:20px ui-monospace,monospace;letter-spacing:.1em;width:100%;padding:.5rem;margin:.5rem 0 1rem;box-sizing:border-box}button{font:inherit;padding:.5rem 1rem}.error{color:#b42318}</style></head><body><h1>${escapeHtml(appName)}</h1><p>An app that calls itself <strong>${who}</strong> asks to reach ${escapeHtml(reaches)} through <code>${escapeHtml(appName)} mcp serve --http</code>.</p><p>After you allow it, the login goes to <strong>${escapeHtml(target.host)}</strong>. Allow only if that is the app you are connecting, such as chatgpt.com or claude.ai.</p><p>Type the code shown in WireCat Desktop, or in the terminal where the server runs. If you did not start this login, close this page.</p>${message ? `<p class="error">${escapeHtml(message)}</p>` : ""}<form method="post" action="${escapeHtml(`${base}/authorize`)}">${hidden}<label>Login code<input type="text" name="login_code" autocomplete="off" autofocus required></label><button type="submit">Allow</button></form></body></html>`;
|
|
157
157
|
return new Response(html, {
|
|
158
158
|
status: message ? 400 : 200,
|
|
159
159
|
headers: {
|
package/dist/mcp/http/serve.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export interface HttpOptions {
|
|
|
13
13
|
/** Where the hashes of issued tokens and registered clients are kept. */
|
|
14
14
|
tokenFile: string;
|
|
15
15
|
appName: string;
|
|
16
|
+
/** What the browser login page says an app asks to reach: `your messages`, `your Google Calendar`. */
|
|
17
|
+
reaches?: string;
|
|
16
18
|
onCode: (code: string, expires: Date) => void;
|
|
17
19
|
onError?: (error: Error) => void;
|
|
18
20
|
now?: () => number;
|
package/dist/mcp/http/serve.js
CHANGED
|
@@ -91,7 +91,7 @@ const toolsIn = async (request) => {
|
|
|
91
91
|
* the owner's browser cannot reach the port through a rebound DNS name.
|
|
92
92
|
*/
|
|
93
93
|
export const serveOverHttp = async (build, options) => {
|
|
94
|
-
const { publicUrl, port, tokenFile, appName, onCode, onError, now, lockFile, version } = options;
|
|
94
|
+
const { publicUrl, port, tokenFile, appName, reaches, onCode, onError, now, lockFile, version } = options;
|
|
95
95
|
const lock = lockFile
|
|
96
96
|
? httpLock(lockFile, {
|
|
97
97
|
pid: process.pid,
|
|
@@ -114,6 +114,7 @@ export const serveOverHttp = async (build, options) => {
|
|
|
114
114
|
resource,
|
|
115
115
|
file: tokenFile,
|
|
116
116
|
appName,
|
|
117
|
+
...(reaches ? { reaches } : {}),
|
|
117
118
|
onCode: (code, expires) => {
|
|
118
119
|
lock?.update({ loginCode: code, loginCodeExpiresAt: expires.toISOString() });
|
|
119
120
|
onCode(code, expires);
|
|
@@ -376,6 +376,9 @@ export declare const callMeetingPreparationTool: (read: MeetingPreparationReader
|
|
|
376
376
|
id: string;
|
|
377
377
|
excerpt: string;
|
|
378
378
|
offset_ms: number | null;
|
|
379
|
+
language?: string | null | undefined;
|
|
380
|
+
line?: number | undefined;
|
|
381
|
+
lines_total?: number | undefined;
|
|
379
382
|
match?: "corrected" | "words" | undefined;
|
|
380
383
|
corrections?: {
|
|
381
384
|
from: string;
|
package/dist/mcp/server.js
CHANGED
|
@@ -417,6 +417,7 @@ export const serveOverHttpUntilStopped = async (command, context, messenger, opt
|
|
|
417
417
|
const listening = await serveOverHttp(build, {
|
|
418
418
|
...http,
|
|
419
419
|
appName,
|
|
420
|
+
reaches: "your messages",
|
|
420
421
|
// In the background its log is a file; the code goes only to the lock that `mcp server status` reads.
|
|
421
422
|
onCode: (code, expires) => context.renderer.note(context.env[mcpHttpMarker(appEnvPrefix)] === undefined
|
|
422
423
|
? `login code for a new browser app: ${code} (until ${expires.toTimeString().slice(0, 5)}; a new one after each login)`
|
package/dist/mcp/surface.js
CHANGED
|
@@ -46,7 +46,7 @@ const runnableOnly = (result, offered) => {
|
|
|
46
46
|
const body = result.structuredContent;
|
|
47
47
|
if (result.isError || !Array.isArray(body?.next))
|
|
48
48
|
return result;
|
|
49
|
-
const next = body.next.filter((step) => offered(step.command));
|
|
49
|
+
const next = body.next.filter((step) => step.command === undefined || offered(step.command));
|
|
50
50
|
const { next: _, ...rest } = body;
|
|
51
51
|
const kept = next.length > 0 ? { ...rest, next } : rest;
|
|
52
52
|
return {
|
|
@@ -15,6 +15,11 @@ export declare const meetingHit: v.StrictObjectSchema<{
|
|
|
15
15
|
readonly excerpt: v.StringSchema<undefined>;
|
|
16
16
|
/** How far into the meeting the passage is. */
|
|
17
17
|
readonly offset_ms: v.NullableSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
|
|
18
|
+
/** A transcript passage's transcript language (`ru`, `en`…), `null` when unknown. */
|
|
19
|
+
readonly language: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
20
|
+
/** A transcript passage's line, from 1, of `lines_total`. */
|
|
21
|
+
readonly line: v.OptionalSchema<v.SchemaWithPipe<readonly [v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, v.MinValueAction<number, 1, undefined>]>, undefined>;
|
|
22
|
+
readonly lines_total: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
|
|
18
23
|
/** `corrected`: a query word matched only a garbled form of itself, listed in `corrections`. */
|
|
19
24
|
readonly match: v.OptionalSchema<v.PicklistSchema<["words", "corrected"], undefined>, undefined>;
|
|
20
25
|
readonly corrections: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
|
package/dist/meetings/answers.js
CHANGED
|
@@ -16,6 +16,11 @@ export const meetingHit = v.strictObject({
|
|
|
16
16
|
excerpt: text,
|
|
17
17
|
/** How far into the meeting the passage is. */
|
|
18
18
|
offset_ms: v.nullable(count),
|
|
19
|
+
/** A transcript passage's transcript language (`ru`, `en`…), `null` when unknown. */
|
|
20
|
+
language: v.optional(v.nullable(text)),
|
|
21
|
+
/** A transcript passage's line, from 1, of `lines_total`. */
|
|
22
|
+
line: v.optional(v.pipe(count, v.minValue(1))),
|
|
23
|
+
lines_total: v.optional(count),
|
|
19
24
|
/** `corrected`: a query word matched only a garbled form of itself, listed in `corrections`. */
|
|
20
25
|
match: v.optional(v.picklist(["words", "corrected"])),
|
|
21
26
|
corrections: v.optional(v.array(v.strictObject({ from: text, to: v.array(text) }))),
|
|
@@ -190,6 +195,9 @@ export const meetingHitItem = (hit) => ({
|
|
|
190
195
|
id: String(hit.id),
|
|
191
196
|
excerpt: hit.text,
|
|
192
197
|
offset_ms: hit.startMs,
|
|
198
|
+
...(hit.language === undefined ? {} : { language: hit.language }),
|
|
199
|
+
...(hit.line === undefined ? {} : { line: hit.line }),
|
|
200
|
+
...(hit.linesTotal === undefined ? {} : { lines_total: hit.linesTotal }),
|
|
193
201
|
untrusted: hit.source === undefined ? ["excerpt"] : ["excerpt", "source.title"],
|
|
194
202
|
...(hit.match === undefined ? {} : { match: hit.match }),
|
|
195
203
|
...(hit.corrections === undefined ? {} : { corrections: hit.corrections }),
|
|
@@ -146,6 +146,20 @@ const addMeetingCommandsDefinition = (program, deps) => {
|
|
|
146
146
|
throw new MeetingError("validation_error", "file account differs from selected account");
|
|
147
147
|
print(await importFiles(files, deps.store, deps.now?.()), command);
|
|
148
148
|
});
|
|
149
|
+
output(annotate(group.command("repair"), {
|
|
150
|
+
effects: LOCAL_WRITE,
|
|
151
|
+
output: { entity: "meeting_repair" },
|
|
152
|
+
}).description("preview filling in what older builds left out of stored transcripts (their language); --yes stores it"))
|
|
153
|
+
.option("--yes", "store the detected languages; otherwise only preview")
|
|
154
|
+
.action(async (opts, command) => {
|
|
155
|
+
if (!deps.store.fillTranscriptLanguages)
|
|
156
|
+
throw new MeetingError("configuration_error", "this store cannot fill transcript languages");
|
|
157
|
+
print(await deps.store.fillTranscriptLanguages({
|
|
158
|
+
accountId: deps.accountId,
|
|
159
|
+
apply: opts.yes === true,
|
|
160
|
+
now: deps.now?.() ?? Date.now(),
|
|
161
|
+
}), command);
|
|
162
|
+
});
|
|
149
163
|
const eventList = queryWordsAfterOptions(annotate(program.command("events").description("the owner's stored events"), {
|
|
150
164
|
effects: { remote: "none", local: "none" },
|
|
151
165
|
}).command("list"))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FACT_TYPES, type Payment } from "./payment.js";
|
|
2
2
|
declare const TASK_TYPES: readonly ["bug", "feature", "chore"];
|
|
3
3
|
export interface FindingEvidence {
|
|
4
4
|
reference: string;
|
|
@@ -14,9 +14,18 @@ interface FindingBase {
|
|
|
14
14
|
}
|
|
15
15
|
export interface FactFinding extends FindingBase {
|
|
16
16
|
kind: "fact";
|
|
17
|
-
type: (typeof FACT_TYPES)[number]
|
|
17
|
+
type: Exclude<(typeof FACT_TYPES)[number], "payment">;
|
|
18
18
|
statement: string;
|
|
19
19
|
}
|
|
20
|
+
/** A payment an agent read; confirming it records a `payment` fact. A paid one supersedes the expected one. */
|
|
21
|
+
export interface PaymentFinding extends FindingBase {
|
|
22
|
+
kind: "payment";
|
|
23
|
+
/** The fact's full sentence; without one, the payment's title. */
|
|
24
|
+
statement?: string;
|
|
25
|
+
payment: Payment;
|
|
26
|
+
/** The `fact:` of the expected payment this one replaces, once it is paid or cancelled. */
|
|
27
|
+
supersedes?: string;
|
|
28
|
+
}
|
|
20
29
|
export interface TaskFinding extends FindingBase {
|
|
21
30
|
kind: "task";
|
|
22
31
|
type: (typeof TASK_TYPES)[number];
|
|
@@ -28,7 +37,7 @@ export interface TaskFinding extends FindingBase {
|
|
|
28
37
|
assignee?: string;
|
|
29
38
|
assigneeEvidence?: string[];
|
|
30
39
|
}
|
|
31
|
-
export type Finding = FactFinding | TaskFinding;
|
|
40
|
+
export type Finding = FactFinding | TaskFinding | PaymentFinding;
|
|
32
41
|
export interface FindingsInput {
|
|
33
42
|
schemaVersion: 1;
|
|
34
43
|
key: string;
|
|
@@ -59,6 +68,8 @@ export interface FindingsRules {
|
|
|
59
68
|
reference: (raw: string) => string;
|
|
60
69
|
/** A fact finding may name no project, and any finding may name a subject. */
|
|
61
70
|
anySubject?: boolean;
|
|
71
|
+
/** Payment findings are staged on a project's evidence, not from a meeting. */
|
|
72
|
+
payments?: boolean;
|
|
62
73
|
}
|
|
63
74
|
export declare const validateFindings: (input: FindingsInput, rules: FindingsRules) => FindingsInput;
|
|
64
75
|
export declare const findingFingerprint: ({ id: _id, ...finding }: Finding) => string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { CliError } from "@wirecat/cli-base";
|
|
3
|
-
import {
|
|
3
|
+
import { FACT_TYPES, validatePayment } from "./payment.js";
|
|
4
4
|
import { meetingTimestamp } from "./timestamp.js";
|
|
5
5
|
const TASK_TYPES = ["bug", "feature", "chore"];
|
|
6
6
|
const invalid = (message) => {
|
|
@@ -69,12 +69,26 @@ export const validateFindings = (input, rules) => {
|
|
|
69
69
|
};
|
|
70
70
|
if (finding.kind === "fact") {
|
|
71
71
|
fields(finding, ["id", "project", "subject", "evidence", "kind", "type", "statement"], rules.singular);
|
|
72
|
+
if (finding.type === "payment")
|
|
73
|
+
return invalid("a payment is a finding of kind payment");
|
|
72
74
|
if (!FACT_TYPES.includes(finding.type))
|
|
73
75
|
return invalid("unknown fact finding type");
|
|
74
76
|
return { ...base, kind: "fact", type: finding.type, statement: text(finding.statement, 2000) };
|
|
75
77
|
}
|
|
78
|
+
if (finding.kind === "payment" && rules.payments) {
|
|
79
|
+
fields(finding, ["id", "project", "subject", "evidence", "kind", "statement", "payment", "supersedes"], rules.singular);
|
|
80
|
+
if (finding.supersedes !== undefined && !/^fact:[1-9]\d*$/.test(String(finding.supersedes)))
|
|
81
|
+
return invalid("a payment finding supersedes a fact: reference");
|
|
82
|
+
return {
|
|
83
|
+
...base,
|
|
84
|
+
kind: "payment",
|
|
85
|
+
...(finding.statement === undefined ? {} : { statement: text(finding.statement, 2000) }),
|
|
86
|
+
payment: validatePayment(finding.payment),
|
|
87
|
+
...(finding.supersedes === undefined ? {} : { supersedes: finding.supersedes }),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
76
90
|
if (finding.kind !== "task")
|
|
77
|
-
return invalid(
|
|
91
|
+
return invalid(`a finding is a fact${rules.payments ? ", payment" : ""} or task`);
|
|
78
92
|
fields(finding, [
|
|
79
93
|
"id",
|
|
80
94
|
"project",
|
package/dist/meetings/index.d.ts
CHANGED
|
@@ -9,17 +9,19 @@ export { entryInWindow, eventChange } from "./event-store.js";
|
|
|
9
9
|
export { linkEvent } from "./events.js";
|
|
10
10
|
export { exportMeeting, type MeetingExport, type MeetingExportFormat, type MeetingExportOptions, serializeVtt, } from "./export.js";
|
|
11
11
|
export { type FindingEvidence, type FindingOutcome, findingHash, MEETING_FINDINGS_KIND, type MeetingFactFinding, type MeetingFinding, type MeetingFindingsInput, type MeetingFindingsReceipt, type MeetingTaskFinding, meetingFindingFingerprint, validateMeetingFindings, } from "./findings.js";
|
|
12
|
-
export { type Finding, type FindingsInput, type FindingsReceipt, type FindingsRules, findingFingerprint, validateFindings, } from "./findings-rules.js";
|
|
12
|
+
export { type Finding, type FindingsInput, type FindingsReceipt, type FindingsRules, findingFingerprint, type PaymentFinding, validateFindings, } from "./findings-rules.js";
|
|
13
13
|
export { liftMeetingFields, MEETING_HIT_QUERY, meetingWordsQuery, parseMeetingQuery } from "./hit-query.js";
|
|
14
14
|
export { type ImportFile, importFiles } from "./import.js";
|
|
15
15
|
export { MEETING_ISSUES, MEETING_QUERY, type MeetingIssue, type MeetingItem, type MeetingOwner, meeting, meetingIssuesOf, meetingItem, } from "./item.js";
|
|
16
16
|
export { joinKeyOf, joinUrlWithoutQuery } from "./join-key.js";
|
|
17
|
+
export { transcriptLanguage } from "./language.js";
|
|
17
18
|
export { CHAT_MESSAGES_READ, type MeetingChatLine, type MeetingChatLinesInput, type MeetingChatLinesStore, type MeetingLink, } from "./links.js";
|
|
18
|
-
export { createMeetingContext,
|
|
19
|
+
export { createMeetingContext, type MeetingContextAction, type MeetingContextChange, type MeetingContextOperation, type MeetingContextPorts, type MeetingContextReceipt, validateMeetingContextChange, } from "./meeting-context.js";
|
|
19
20
|
export { createMeetingManagement, dispatchMeetingMutation, type ManagedMeeting, type ManagedMeetingChange, type MeetingManagement, type MeetingManagementPorts, type MeetingMutationOptions, type MeetingMutationProposal, type MeetingMutationReceipt, type MeetingOperationOptions, type MeetingProviderContext, validateMeetingChange, } from "./meeting-management.js";
|
|
20
21
|
export type { Attachment, ChatLine, Event, EventSeries, JsonValue, Meeting, MeetingSeries, Metadata, Occurrence, Participant, RecordTimes, SourceChatLine, SourceMeeting, Summary, SummaryContent, Transcript, TranscriptLine, TranscriptRow, } from "./model.js";
|
|
21
22
|
export { type MeetingParticipantCandidate, type MeetingParticipantPage, type MeetingParticipantReader, type MeetingParticipantSearchOptions, type MeetingParticipantSearchStore, normalizeMeetingParticipantQuery, validateMeetingParticipantSearch, } from "./participant-search.js";
|
|
22
23
|
export { type MeetingContextCue, type MeetingHitContextOptions, type MeetingHitContextPacket, type MeetingPassageOutputFormat, type MeetingPassageReader, type MeetingPassageRequest, type MeetingPassageResult, meetingHitContextLimits, validateMeetingPassage, } from "./passage.js";
|
|
24
|
+
export { FACT_TYPES, PAYMENT_DIRECTIONS, PAYMENT_STATES, type Payment, paymentTitle, validatePayment, } from "./payment.js";
|
|
23
25
|
export { type MeetingPreparationContext, type MeetingPreparationContextRequest, type MeetingPreparationContextStore, type PreparationPage, validateMeetingPreparationContext, } from "./preparation-context.js";
|
|
24
26
|
export type { MeetingPreparationFact, MeetingPreparationKnowledgeRequest, MeetingPreparationKnowledgeStore, } from "./preparation-knowledge.js";
|
|
25
27
|
export type { MeetingPreparationNote, MeetingPreparationNotesSelection, MeetingPreparationNotesStore, } from "./preparation-notes.js";
|
|
@@ -50,7 +52,7 @@ export type { MeetingSeriesCandidate, MeetingSeriesInventoryPage, MeetingSeriesI
|
|
|
50
52
|
export { validateMeetingSeriesInventoryCursor } from "./series-inventory-validation.js";
|
|
51
53
|
export type { MeetingSource, SourceParticipant } from "./source.js";
|
|
52
54
|
export { aggregateMeetingStats, type MeetingSpeakerStats, type MeetingStats, type MeetingStatsInput, type MeetingStatsRow, type MeetingStatsSelection, type MeetingStatsStore, type MeetingWeekStats, } from "./stats.js";
|
|
53
|
-
export type { ChatInput, ContractCase, EventCandidateFilter, IdentityInput, MeetingDetails, MeetingFilter, MeetingHitSource, MeetingInput, MeetingSave, MeetingStore, NewRecord, ParticipantInput, SearchHit, TranscriptInput, TranscriptRowInput, } from "./store.js";
|
|
55
|
+
export type { ChatInput, ContractCase, EventCandidateFilter, IdentityInput, MeetingDetails, MeetingFilter, MeetingHitSource, MeetingInput, MeetingSave, MeetingStore, NewRecord, ParticipantInput, SearchHit, TranscriptInput, TranscriptLanguageFill, TranscriptRowInput, } from "./store.js";
|
|
54
56
|
export type { MeetingTranscriptReceiptStore, MeetingTranscriptStore, TranscriptAppend, TranscriptAppendReceipt, UnlinkedTranscriptInput, } from "./transcript-store.js";
|
|
55
57
|
export { parseTranscriptText, TranscriptTextError, type TranscriptTextHeader } from "./transcript-text.js";
|
|
56
58
|
export { type MeetingMediaDescriptor, type MeetingRecognitionSegment, type MeetingTranscriptionContext, type MeetingTranscriptionOptions, type MeetingTranscriptionReceipt, type MeetingTranscriptionSource, transcribeMeeting, } from "./transcription.js";
|
package/dist/meetings/index.js
CHANGED
|
@@ -11,11 +11,13 @@ export { liftMeetingFields, MEETING_HIT_QUERY, meetingWordsQuery, parseMeetingQu
|
|
|
11
11
|
export { importFiles } from "./import.js";
|
|
12
12
|
export { MEETING_ISSUES, MEETING_QUERY, meeting, meetingIssuesOf, meetingItem, } from "./item.js";
|
|
13
13
|
export { joinKeyOf, joinUrlWithoutQuery } from "./join-key.js";
|
|
14
|
+
export { transcriptLanguage } from "./language.js";
|
|
14
15
|
export { CHAT_MESSAGES_READ, } from "./links.js";
|
|
15
|
-
export { createMeetingContext,
|
|
16
|
+
export { createMeetingContext, validateMeetingContextChange, } from "./meeting-context.js";
|
|
16
17
|
export { createMeetingManagement, dispatchMeetingMutation, validateMeetingChange, } from "./meeting-management.js";
|
|
17
18
|
export { normalizeMeetingParticipantQuery, validateMeetingParticipantSearch, } from "./participant-search.js";
|
|
18
19
|
export { meetingHitContextLimits, validateMeetingPassage, } from "./passage.js";
|
|
20
|
+
export { FACT_TYPES, PAYMENT_DIRECTIONS, PAYMENT_STATES, paymentTitle, validatePayment, } from "./payment.js";
|
|
19
21
|
export { validateMeetingPreparationContext, } from "./preparation-context.js";
|
|
20
22
|
export { readMeetingProjectEvidence } from "./project-evidence.js";
|
|
21
23
|
export { meetingProjectEvidence, meetingProjectEvidenceAnswer, } from "./project-evidence-answer.js";
|
package/dist/meetings/ingest.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { transcriptLanguage } from "./language.js";
|
|
1
2
|
export const contentHash = async (text) => {
|
|
2
3
|
const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
|
|
3
4
|
return Array.from(new Uint8Array(bytes), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
@@ -11,7 +12,7 @@ export const speakerPosition = (name, participants) => {
|
|
|
11
12
|
export const transcriptInput = async (lines, participants, source, hashText = JSON.stringify(lines), format = null) => ({
|
|
12
13
|
source,
|
|
13
14
|
format,
|
|
14
|
-
language:
|
|
15
|
+
language: transcriptLanguage(lines.map((line) => line.text)),
|
|
15
16
|
contentHash: await contentHash(hashText),
|
|
16
17
|
externalCreatedAt: null,
|
|
17
18
|
metadata: null,
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The language most of a transcript is in, from its letters: Cyrillic is Russian (Ukrainian when its own letters
|
|
3
|
+
* show), Latin is decided by frequent words. `null` when the text is too short, mixed, or in neither script.
|
|
4
|
+
*/
|
|
5
|
+
export declare const transcriptLanguage: (texts: Iterable<string>) => string | null;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Words frequent in one Latin-script language and rare in the others here. Letters alone cannot tell Spanish from
|
|
3
|
+
* English, and calling a Spanish meeting English is worse than calling it nothing.
|
|
4
|
+
*/
|
|
5
|
+
const LATIN_WORDS = {
|
|
6
|
+
en: ["the", "and", "is", "that", "you", "this", "what", "have", "with", "are"],
|
|
7
|
+
es: ["el", "los", "las", "por", "pero", "para", "está", "muy", "también", "hay"],
|
|
8
|
+
de: ["der", "und", "ist", "nicht", "das", "ich", "wir", "mit", "auch", "ein"],
|
|
9
|
+
fr: ["le", "les", "est", "une", "pas", "je", "vous", "nous", "avec", "mais"],
|
|
10
|
+
it: ["gli", "della", "sono", "perché", "anche", "questo", "molto", "che", "di", "è"],
|
|
11
|
+
pt: ["não", "você", "isso", "muito", "então", "com", "uma", "também", "mas", "eu"],
|
|
12
|
+
};
|
|
13
|
+
const LATIN_LANGUAGE = new Map(Object.entries(LATIN_WORDS).flatMap(([language, words]) => words.map((word) => [word, language])));
|
|
14
|
+
/** Letters Ukrainian writes and Russian does not. */
|
|
15
|
+
const UKRAINIAN = /[іїєґ]/giu;
|
|
16
|
+
const CYRILLIC = /\p{Script=Cyrillic}/gu;
|
|
17
|
+
const LATIN = /\p{Script=Latin}/gu;
|
|
18
|
+
const MIN_LETTERS = 20;
|
|
19
|
+
/** A meeting in one language keeps a few lines of another; a share below this is a mixed meeting. */
|
|
20
|
+
const DOMINANT = 0.75;
|
|
21
|
+
const countOf = (text, pattern) => text.match(pattern)?.length ?? 0;
|
|
22
|
+
const latinLanguage = (texts) => {
|
|
23
|
+
const votes = new Map();
|
|
24
|
+
for (const text of texts)
|
|
25
|
+
for (const word of text.toLowerCase().split(/[^\p{L}]+/u)) {
|
|
26
|
+
const language = LATIN_LANGUAGE.get(word);
|
|
27
|
+
if (language)
|
|
28
|
+
votes.set(language, (votes.get(language) ?? 0) + 1);
|
|
29
|
+
}
|
|
30
|
+
const [first, second] = [...votes].sort((a, b) => b[1] - a[1]);
|
|
31
|
+
if (!first || first[1] < 3 || (second && first[1] < 2 * second[1]))
|
|
32
|
+
return null;
|
|
33
|
+
return first[0];
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* The language most of a transcript is in, from its letters: Cyrillic is Russian (Ukrainian when its own letters
|
|
37
|
+
* show), Latin is decided by frequent words. `null` when the text is too short, mixed, or in neither script.
|
|
38
|
+
*/
|
|
39
|
+
export const transcriptLanguage = (texts) => {
|
|
40
|
+
const all = [...texts];
|
|
41
|
+
let cyrillic = 0;
|
|
42
|
+
let latin = 0;
|
|
43
|
+
let ukrainian = 0;
|
|
44
|
+
for (const text of all) {
|
|
45
|
+
cyrillic += countOf(text, CYRILLIC);
|
|
46
|
+
latin += countOf(text, LATIN);
|
|
47
|
+
ukrainian += countOf(text, UKRAINIAN);
|
|
48
|
+
}
|
|
49
|
+
const letters = cyrillic + latin;
|
|
50
|
+
if (letters < MIN_LETTERS)
|
|
51
|
+
return null;
|
|
52
|
+
if (cyrillic / letters >= DOMINANT)
|
|
53
|
+
return ukrainian / cyrillic >= 0.01 ? "uk" : "ru";
|
|
54
|
+
if (latin / letters >= DOMINANT)
|
|
55
|
+
return latinLanguage(all);
|
|
56
|
+
return null;
|
|
57
|
+
};
|
|
@@ -139,6 +139,9 @@ export declare const callMeetingSearchTool: (search: MeetingHitSearch, input: un
|
|
|
139
139
|
id: string;
|
|
140
140
|
excerpt: string;
|
|
141
141
|
offset_ms: number | null;
|
|
142
|
+
language?: string | null | undefined;
|
|
143
|
+
line?: number | undefined;
|
|
144
|
+
lines_total?: number | undefined;
|
|
142
145
|
match?: "corrected" | "words" | undefined;
|
|
143
146
|
corrections?: {
|
|
144
147
|
from: string;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { type MeetingMutationOptions } from "./meeting-management.js";
|
|
2
|
+
import { FACT_TYPES } from "./payment.js";
|
|
2
3
|
import { type MeetingProviderScope } from "./provider.js";
|
|
3
|
-
/** Mirrors the store's fact types; the store checks the type again when it writes. */
|
|
4
|
-
export declare const MEETING_FACT_TYPES: readonly ["fact", "statement", "question", "decision", "promise"];
|
|
5
4
|
export type MeetingContextChange = {
|
|
6
5
|
action: "note.add";
|
|
7
6
|
text: string;
|
|
@@ -19,7 +18,7 @@ export type MeetingContextChange = {
|
|
|
19
18
|
} | {
|
|
20
19
|
action: "fact.add";
|
|
21
20
|
statement: string;
|
|
22
|
-
type: (typeof
|
|
21
|
+
type: (typeof FACT_TYPES)[number];
|
|
23
22
|
scope: "personal" | "work";
|
|
24
23
|
evidence: readonly string[];
|
|
25
24
|
subject?: string;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { dispatchMeetingMutation } from "./meeting-management.js";
|
|
3
|
+
import { FACT_TYPES } from "./payment.js";
|
|
3
4
|
import { snapshotMeetingScope } from "./provider.js";
|
|
4
|
-
/** Mirrors the store's fact types; the store checks the type again when it writes. */
|
|
5
|
-
export const MEETING_FACT_TYPES = ["fact", "statement", "question", "decision", "promise"];
|
|
6
5
|
const id = (value) => {
|
|
7
6
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1)
|
|
8
7
|
throw new CliError("validation_error", "meeting context identifiers must be positive integers");
|
|
@@ -63,8 +62,8 @@ export const validateMeetingContextChange = (change) => {
|
|
|
63
62
|
});
|
|
64
63
|
}
|
|
65
64
|
case "fact.add": {
|
|
66
|
-
if (!
|
|
67
|
-
throw new CliError("validation_error", `fact type (${
|
|
65
|
+
if (!FACT_TYPES.includes(change.type) || !["personal", "work"].includes(change.scope))
|
|
66
|
+
throw new CliError("validation_error", `fact type (${FACT_TYPES.join(", ")}) and explicit personal/work scope are required`);
|
|
68
67
|
return Object.freeze({
|
|
69
68
|
action: change.action,
|
|
70
69
|
statement: text(change.statement, 20_000),
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Every fact type the store takes; findings and meeting changes check against the same list. */
|
|
2
|
+
export declare const FACT_TYPES: readonly ["fact", "statement", "question", "decision", "promise", "payment"];
|
|
3
|
+
export declare const PAYMENT_DIRECTIONS: readonly ["to_us", "from_us"];
|
|
4
|
+
export declare const PAYMENT_STATES: readonly ["expected", "paid", "cancelled"];
|
|
5
|
+
/** A payment fact's fields, kept in `facts.metadata.payment`. */
|
|
6
|
+
export interface Payment {
|
|
7
|
+
/** A decimal as text with two places, "50000.00": money is never a float. */
|
|
8
|
+
amount: string;
|
|
9
|
+
/** ISO 4217, "RUB". */
|
|
10
|
+
currency: string;
|
|
11
|
+
direction: (typeof PAYMENT_DIRECTIONS)[number];
|
|
12
|
+
/** `person:`, `organization:` or `project:`; `null` when only a name is known. */
|
|
13
|
+
counterparty: string | null;
|
|
14
|
+
counterparty_name: string | null;
|
|
15
|
+
/** An ISO date. */
|
|
16
|
+
due_at: string | null;
|
|
17
|
+
/** A change of state is a new fact that supersedes this one, so the history and its evidence stay. */
|
|
18
|
+
state: (typeof PAYMENT_STATES)[number];
|
|
19
|
+
/** What it is for, short: "support, September". The fact's statement is the full sentence. */
|
|
20
|
+
item: string | null;
|
|
21
|
+
}
|
|
22
|
+
/** A payment as stored: the amount with two decimals, the currency upper case; refuses what is not one. */
|
|
23
|
+
export declare const validatePayment: (input: unknown) => Payment;
|
|
24
|
+
/** One line for a payment, as a list or an inbox shows it: "1 200 EUR to us from Ana, due 31.10 (expected)". */
|
|
25
|
+
export declare const paymentTitle: (fact: {
|
|
26
|
+
payment: Payment | null;
|
|
27
|
+
}) => string;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
/** Every fact type the store takes; findings and meeting changes check against the same list. */
|
|
3
|
+
export const FACT_TYPES = ["fact", "statement", "question", "decision", "promise", "payment"];
|
|
4
|
+
export const PAYMENT_DIRECTIONS = ["to_us", "from_us"];
|
|
5
|
+
export const PAYMENT_STATES = ["expected", "paid", "cancelled"];
|
|
6
|
+
const invalid = (message) => {
|
|
7
|
+
throw new CliError("validation_error", message);
|
|
8
|
+
};
|
|
9
|
+
const CURRENCIES = new Set(Intl.supportedValuesOf("currency"));
|
|
10
|
+
const AMOUNT = /^(\d{1,15})(?:\.(\d{1,2}))?$/;
|
|
11
|
+
const COUNTERPARTY = /^(person|organization|project):[1-9]\d*$/;
|
|
12
|
+
const amountOf = (value) => {
|
|
13
|
+
const match = AMOUNT.exec(typeof value === "number" ? String(value) : typeof value === "string" ? value.trim() : "");
|
|
14
|
+
if (!match)
|
|
15
|
+
return invalid("a payment's amount is a positive number with at most 2 decimals, such as 1200.50");
|
|
16
|
+
const amount = `${BigInt(match[1] ?? "0")}.${(match[2] ?? "").padEnd(2, "0")}`;
|
|
17
|
+
if (amount === "0.00")
|
|
18
|
+
return invalid("a payment's amount is more than 0");
|
|
19
|
+
return amount;
|
|
20
|
+
};
|
|
21
|
+
const dateOf = (value) => {
|
|
22
|
+
const date = typeof value === "string" ? value.trim() : "";
|
|
23
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(date) || new Date(`${date}T00:00:00Z`).toISOString().slice(0, 10) !== date)
|
|
24
|
+
return invalid("a payment's due date is an ISO date, such as 2026-10-31");
|
|
25
|
+
return date;
|
|
26
|
+
};
|
|
27
|
+
const optionalText = (value, what, limit) => {
|
|
28
|
+
if (value === undefined || value === null)
|
|
29
|
+
return null;
|
|
30
|
+
if (typeof value !== "string" || !value.trim() || value.length > limit || value.includes("\0"))
|
|
31
|
+
return invalid(`a payment's ${what} takes 1–${limit} characters`);
|
|
32
|
+
return value.trim();
|
|
33
|
+
};
|
|
34
|
+
const oneOf = (value, allowed, what) => {
|
|
35
|
+
if (typeof value !== "string" || !allowed.includes(value))
|
|
36
|
+
return invalid(`a payment's ${what} is one of ${allowed.join(", ")}`);
|
|
37
|
+
return value;
|
|
38
|
+
};
|
|
39
|
+
const FIELDS = ["amount", "currency", "direction", "counterparty", "counterparty_name", "due_at", "state", "item"];
|
|
40
|
+
/** A payment as stored: the amount with two decimals, the currency upper case; refuses what is not one. */
|
|
41
|
+
export const validatePayment = (input) => {
|
|
42
|
+
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
43
|
+
return invalid("a payment is an object");
|
|
44
|
+
const raw = input;
|
|
45
|
+
const unknown = Object.keys(raw).filter((key) => !FIELDS.includes(key));
|
|
46
|
+
if (unknown.length)
|
|
47
|
+
return invalid(`a payment has no field ${unknown.join(", ")}; its fields are ${FIELDS.join(", ")}`);
|
|
48
|
+
const currency = typeof raw.currency === "string" ? raw.currency.trim().toUpperCase() : "";
|
|
49
|
+
if (!/^[A-Z]{3}$/.test(currency) || !CURRENCIES.has(currency))
|
|
50
|
+
return invalid("a payment's currency is an ISO 4217 code, such as EUR or RUB");
|
|
51
|
+
const counterparty = optionalText(raw.counterparty, "counterparty", 80);
|
|
52
|
+
if (counterparty !== null && !COUNTERPARTY.test(counterparty))
|
|
53
|
+
return invalid("a payment's counterparty is a person:, organization: or project: reference");
|
|
54
|
+
return {
|
|
55
|
+
amount: amountOf(raw.amount),
|
|
56
|
+
currency,
|
|
57
|
+
direction: oneOf(raw.direction, PAYMENT_DIRECTIONS, "direction"),
|
|
58
|
+
counterparty,
|
|
59
|
+
counterparty_name: optionalText(raw.counterparty_name, "counterparty name", 200),
|
|
60
|
+
due_at: raw.due_at === undefined || raw.due_at === null ? null : dateOf(raw.due_at),
|
|
61
|
+
state: oneOf(raw.state, PAYMENT_STATES, "state"),
|
|
62
|
+
item: optionalText(raw.item, "item", 200),
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
const grouped = (amount) => {
|
|
66
|
+
const [whole = "0", cents = "00"] = amount.split(".");
|
|
67
|
+
const digits = whole.replace(/\B(?=(\d{3})+(?!\d))/g, " ");
|
|
68
|
+
return cents === "00" ? digits : `${digits}.${cents}`;
|
|
69
|
+
};
|
|
70
|
+
/** One line for a payment, as a list or an inbox shows it: "1 200 EUR to us from Ana, due 31.10 (expected)". */
|
|
71
|
+
export const paymentTitle = (fact) => {
|
|
72
|
+
const payment = fact.payment;
|
|
73
|
+
if (!payment)
|
|
74
|
+
return "";
|
|
75
|
+
const who = payment.counterparty_name ?? payment.counterparty;
|
|
76
|
+
const way = payment.direction === "to_us" ? "to us" : "from us";
|
|
77
|
+
const party = who === null ? "" : payment.direction === "to_us" ? ` from ${who}` : ` to ${who}`;
|
|
78
|
+
const due = payment.due_at === null ? "" : `, due ${payment.due_at.slice(8, 10)}.${payment.due_at.slice(5, 7)}`;
|
|
79
|
+
const item = payment.item === null ? "" : ` for ${payment.item}`;
|
|
80
|
+
return `${grouped(payment.amount)} ${payment.currency} ${way}${party}${item}${due} (${payment.state})`;
|
|
81
|
+
};
|