@wirecat/cli-core 0.586.0 → 0.588.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 +38 -0
- package/dist/calendar/cli/local-times.js +3 -13
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1 -0
- package/dist/cli/local-times.d.ts +16 -0
- package/dist/cli/local-times.js +65 -0
- package/dist/cli/runs/command.js +3 -2
- package/dist/domain/deals.d.ts +8 -6
- package/dist/domain/deals.js +7 -8
- package/dist/domain/ref-summaries.js +2 -0
- package/dist/domain/references.d.ts +1 -1
- package/dist/domain/references.js +2 -0
- package/dist/errors/reasons.js +6 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mail/cli/index.js +12 -11
- package/dist/mail/cli/review.js +10 -7
- package/dist/mail/read.d.ts +1 -1
- package/dist/mail/read.js +2 -2
- package/dist/services/proposals/registry.js +0 -2
- package/dist/store/index.d.ts +1 -1
- package/dist/store/sqlite/deals.d.ts +128 -29
- package/dist/store/sqlite/deals.js +270 -156
- package/dist/store/sqlite/entity-types.d.ts +3 -1
- package/dist/store/sqlite/entity-types.js +3 -1
- package/dist/store/sqlite/knowledge.js +3 -1
- package/dist/store/sqlite/migrations.generated.js +39 -11
- package/dist/store/sqlite/ref-summaries.js +11 -7
- package/dist/store/sqlite/schema.d.ts +600 -38
- package/dist/store/sqlite/schema.js +86 -14
- package/dist/store/sqlite/things.js +2 -0
- package/package.json +3 -3
- package/dist/services/proposals/kinds/deals.d.ts +0 -4
- package/dist/services/proposals/kinds/deals.js +0 -32
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,44 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.588.0 — 06.11.2026
|
|
11
|
+
|
|
12
|
+
### Changed — may break callers
|
|
13
|
+
|
|
14
|
+
- **Store version 52 is amended in place: deals and leads in their own tables, a stage history, and no review.**
|
|
15
|
+
0.585.0, 0.586.0 and 0.587.0 shipped an earlier shape of version 52, but no published CLI depends on them, so no real store
|
|
16
|
+
holds it; a store a test or a local build moved to 52 with them must be made again. Tables `deals` and `leads`
|
|
17
|
+
(refs `deal:<id>`, `lead:<id>`) replace `opportunities`; a lead may name the deal it became (`deal_id`). Pipelines
|
|
18
|
+
have a ref, `pipeline:<id>`, belong to the owner or to a CRM account (`account_id`, `external_id`, so two accounts
|
|
19
|
+
may each have "Sales"), and hold deals or leads (`kind`); stages have an `outcome` (`open`, `won`, `lost`) and an
|
|
20
|
+
`external_id`, and positions are not unique. Deals and leads keep the CRM's `account_id`, `external_id` and a board
|
|
21
|
+
`rank`. Triggers write every stage a deal or a lead enters to `pipeline_stage_changes`. The migration seeds one lead
|
|
22
|
+
pipeline, `Leads`, with the stages `new`, `contacted`, `qualified`, `won` and `lost`.
|
|
23
|
+
- **`store.deals` writes for the owner and his agents alike.** The `deal` proposal kind and the review columns are
|
|
24
|
+
gone; only the author is kept. `add`, `list` take `kind` (`deal` by default; a lead may leave out its stage when one
|
|
25
|
+
lead pipeline is stored). A pipeline is named by `pipeline:<id>` or by a name only one pipeline has; a shared name
|
|
26
|
+
is refused with the candidates (`pipeline.ambiguous`). Moving to a stage whose outcome is won or lost closes the row,
|
|
27
|
+
and `close` moves it to its pipeline's stage of that outcome when there is one. `store.deals.pipeline(ref)` reports
|
|
28
|
+
each stage's open rows, how many rows reached it and the conversion to the next stage, with won, lost and win rate.
|
|
29
|
+
|
|
30
|
+
## 0.587.0 — 06.11.2026
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- **`answerLocally` and `wallClock` in `@wirecat/cli-core/cli`.** `answerLocally` answers as `renderAnswer` does, and a
|
|
35
|
+
person reads each UTC time as `YYYY-MM-DD HH:MM` in the zone in use, with one `times in <zone>` line on stderr; a
|
|
36
|
+
`view` gets the formatter to build its text with. JSON, JSONL, `--compact` and agents keep UTC. `hitText` takes the
|
|
37
|
+
formatter as an optional third argument. mail can drop its own copy (`src/commands/times.ts`).
|
|
38
|
+
- **cli-core accepts `@wirecat/cli-base` 0.80 (peer range `>=0.76.0 <0.81.0`) and builds with 0.80.0**, which
|
|
39
|
+
adds the `--write <id>` flag mail's `messages restore` takes. The range was `<0.80.0`.
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- **`search messages`, `messages show`, `conversations list|show`, mail `review`, `runs list` and `runs show` show a
|
|
44
|
+
person times in the local zone** (WC-TIME-4), with a last line `times in <zone>`. They printed UTC ISO strings
|
|
45
|
+
such as `2026-10-03T09:00:00.000Z`. `search messages --timezone` decides the zone shown. JSON and storage keep UTC.
|
|
46
|
+
`runs list|show` is shared by every tool on cli-core; the mail screens reach mail and memo at their next bump.
|
|
47
|
+
|
|
10
48
|
## 0.586.0 — 06.11.2026
|
|
11
49
|
|
|
12
50
|
### Added
|
|
@@ -1,26 +1,16 @@
|
|
|
1
1
|
import { renderPretty } from "@wirecat/cli-base";
|
|
2
|
-
|
|
2
|
+
import { UTC_TIME, wallClock } from "../../cli/local-times.js";
|
|
3
3
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
4
|
/**
|
|
5
5
|
* A calendar answer as a person reads it at a terminal (WC-TIME-4): each UTC time on the wall clock of `zone`, an
|
|
6
6
|
* all-day event's times as its local dates, and a last line naming the zone. JSON keeps the UTC times.
|
|
7
7
|
*/
|
|
8
8
|
export const localTimesView = (value, zone, color) => {
|
|
9
|
-
const clock =
|
|
10
|
-
timeZone: zone,
|
|
11
|
-
year: "numeric",
|
|
12
|
-
month: "2-digit",
|
|
13
|
-
day: "2-digit",
|
|
14
|
-
hour: "2-digit",
|
|
15
|
-
minute: "2-digit",
|
|
16
|
-
hourCycle: "h23",
|
|
17
|
-
});
|
|
9
|
+
const clock = wallClock(zone);
|
|
18
10
|
let shown = false;
|
|
19
11
|
const local = (text, dateOnly) => {
|
|
20
|
-
const part = Object.fromEntries(clock.formatToParts(Date.parse(text)).map(({ type, value }) => [type, value]));
|
|
21
12
|
shown = true;
|
|
22
|
-
|
|
23
|
-
return dateOnly ? date : `${date} ${part.hour}:${part.minute}`;
|
|
13
|
+
return dateOnly ? clock.date(text) : clock.time(text);
|
|
24
14
|
};
|
|
25
15
|
const walk = (one, allDay = false) => {
|
|
26
16
|
if (typeof one === "string")
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -86,6 +86,7 @@ export { type HelpSection, inSection, SECTIONS } from "./help/sections.js";
|
|
|
86
86
|
export { bufferedInput, type InputPolicy, inputPolicy, MAX_BUFFERED_INPUT, MAX_SECRET_INPUT, provideInputPolicy, } from "./input-policy.js";
|
|
87
87
|
export { answerFields, checkJson, type EntityFields, entityFields, type JsonFieldsResolver, type JsonRequest, liftJson, } from "./json-fields.js";
|
|
88
88
|
export { commandPath, LIST_QUERY_GUIDE, listQuery, nativeField, nativeTermLists, nativeTerms, negatableOf, pagedList, pageOf, type QueryWords, queryArgument, queryHelp, queryTerms, searchText, termField, timeBounds, typedQuery, unfilteredQuery, withQuery, } from "./list-query.js";
|
|
89
|
+
export { answerLocally, type ShownTime, wallClock } from "./local-times.js";
|
|
89
90
|
export { MCP_HTTP_DEFAULT_PORT, type McpServerOptions, type McpServerTarget, mcpServerCommand, } from "./mcp-server-command.js";
|
|
90
91
|
export { formatMeetingBatch, prepareMeetingBatch } from "./meeting-batch.js";
|
|
91
92
|
export { meetingEvidenceItem, meetingPersonContextItem } from "./meeting-evidence-answer.js";
|
package/dist/cli/index.js
CHANGED
|
@@ -77,6 +77,7 @@ export { inSection, SECTIONS } from "./help/sections.js";
|
|
|
77
77
|
export { bufferedInput, inputPolicy, MAX_BUFFERED_INPUT, MAX_SECRET_INPUT, provideInputPolicy, } from "./input-policy.js";
|
|
78
78
|
export { answerFields, checkJson, entityFields, liftJson, } from "./json-fields.js";
|
|
79
79
|
export { commandPath, LIST_QUERY_GUIDE, listQuery, nativeField, nativeTermLists, nativeTerms, negatableOf, pagedList, pageOf, queryArgument, queryHelp, queryTerms, searchText, termField, timeBounds, typedQuery, unfilteredQuery, withQuery, } from "./list-query.js";
|
|
80
|
+
export { answerLocally, wallClock } from "./local-times.js";
|
|
80
81
|
export { MCP_HTTP_DEFAULT_PORT, mcpServerCommand, } from "./mcp-server-command.js";
|
|
81
82
|
export { formatMeetingBatch, prepareMeetingBatch } from "./meeting-batch.js";
|
|
82
83
|
export { meetingEvidenceItem, meetingPersonContextItem } from "./meeting-evidence-answer.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import { type AnswerOutput, type Body } from "./envelope.js";
|
|
3
|
+
export declare const UTC_TIME: RegExp;
|
|
4
|
+
/** `YYYY-MM-DD HH:MM` on the wall clock of `zone`, with no offset: the answer names the zone once. */
|
|
5
|
+
export declare const wallClock: (zone: string) => {
|
|
6
|
+
date: (iso: string) => string;
|
|
7
|
+
time: (iso: string) => string;
|
|
8
|
+
};
|
|
9
|
+
/** Turns a UTC time into the shown one; any other text comes back as it was. */
|
|
10
|
+
export type ShownTime = (value: string) => string;
|
|
11
|
+
/**
|
|
12
|
+
* Answers as `renderAnswer` does, and a person reads every UTC time in the zone in use (`zone`, else the computer's),
|
|
13
|
+
* with one line naming it (WC-TIME-4). A `view` builds its text with the `shown` it is given. JSON, JSONL, compact
|
|
14
|
+
* rows and agents keep UTC (WC-TIME-2), and `next` is left alone: its args run.
|
|
15
|
+
*/
|
|
16
|
+
export declare const answerLocally: <T>(command: Command, output: AnswerOutput, body: Body<T>, view?: (shown: ShownTime) => string, zone?: string) => void;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { timezoneOf } from "@wirecat/cli-base/query";
|
|
2
|
+
import { environmentOf } from "./context.js";
|
|
3
|
+
import { renderAnswer } from "./envelope.js";
|
|
4
|
+
export const UTC_TIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/u;
|
|
5
|
+
/** `YYYY-MM-DD HH:MM` on the wall clock of `zone`, with no offset: the answer names the zone once. */
|
|
6
|
+
export const wallClock = (zone) => {
|
|
7
|
+
const format = new Intl.DateTimeFormat("en-CA", {
|
|
8
|
+
timeZone: zone,
|
|
9
|
+
year: "numeric",
|
|
10
|
+
month: "2-digit",
|
|
11
|
+
day: "2-digit",
|
|
12
|
+
hour: "2-digit",
|
|
13
|
+
minute: "2-digit",
|
|
14
|
+
hourCycle: "h23",
|
|
15
|
+
});
|
|
16
|
+
const parts = (iso) => Object.fromEntries(format.formatToParts(Date.parse(iso)).map(({ type, value }) => [type, value]));
|
|
17
|
+
const date = (iso) => {
|
|
18
|
+
const part = parts(iso);
|
|
19
|
+
return `${part.year}-${part.month}-${part.day}`;
|
|
20
|
+
};
|
|
21
|
+
return {
|
|
22
|
+
date,
|
|
23
|
+
time: (iso) => {
|
|
24
|
+
const part = parts(iso);
|
|
25
|
+
return `${part.year}-${part.month}-${part.day} ${part.hour}:${part.minute}`;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
const localised = (value, shown) => {
|
|
30
|
+
if (typeof value === "string")
|
|
31
|
+
return shown(value);
|
|
32
|
+
if (Array.isArray(value))
|
|
33
|
+
return value.map((one) => localised(one, shown));
|
|
34
|
+
if (value !== null && typeof value === "object")
|
|
35
|
+
return Object.fromEntries(Object.entries(value).map(([key, one]) => [key, localised(one, shown)]));
|
|
36
|
+
return value;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Answers as `renderAnswer` does, and a person reads every UTC time in the zone in use (`zone`, else the computer's),
|
|
40
|
+
* with one line naming it (WC-TIME-4). A `view` builds its text with the `shown` it is given. JSON, JSONL, compact
|
|
41
|
+
* rows and agents keep UTC (WC-TIME-2), and `next` is left alone: its args run.
|
|
42
|
+
*/
|
|
43
|
+
export const answerLocally = (command, output, body, view, zone) => {
|
|
44
|
+
if (output.format !== "pretty" || output.compact || environmentOf(command).agent) {
|
|
45
|
+
renderAnswer(command, output, body, view && (() => view((value) => value)));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const inUse = timezoneOf(zone);
|
|
49
|
+
const clock = wallClock(inUse);
|
|
50
|
+
let seen = false;
|
|
51
|
+
const shown = (value) => {
|
|
52
|
+
if (!UTC_TIME.test(value))
|
|
53
|
+
return value;
|
|
54
|
+
seen = true;
|
|
55
|
+
return clock.time(value);
|
|
56
|
+
};
|
|
57
|
+
if (view)
|
|
58
|
+
renderAnswer(command, output, body, () => view(shown));
|
|
59
|
+
else
|
|
60
|
+
renderAnswer(command, output, ("items" in body
|
|
61
|
+
? { ...body, items: body.items.map((one) => localised(one, shown)) }
|
|
62
|
+
: { ...body, item: localised(body.item, shown) }));
|
|
63
|
+
if (seen)
|
|
64
|
+
output.renderer.note(`times in ${inUse}`);
|
|
65
|
+
};
|
package/dist/cli/runs/command.js
CHANGED
|
@@ -6,6 +6,7 @@ import { environmentOf, outputFor } from "../context.js";
|
|
|
6
6
|
import { LOCAL_READ, NO_EFFECTS } from "../effects.js";
|
|
7
7
|
import { itemOf, listOf, renderAnswer, snakeKeys } from "../envelope.js";
|
|
8
8
|
import { typedQuery, withQuery } from "../list-query.js";
|
|
9
|
+
import { answerLocally } from "../local-times.js";
|
|
9
10
|
import { positiveCount } from "../paging.js";
|
|
10
11
|
import { lineOf } from "../steps.js";
|
|
11
12
|
import { RUNS_QUERY, runListFilter } from "./query.js";
|
|
@@ -33,7 +34,7 @@ export const runsCommand = (app) => {
|
|
|
33
34
|
const { renderer } = output;
|
|
34
35
|
const runs = listRuns(dir(this), { atLeast: page * limit + 1, filter });
|
|
35
36
|
const shown = runs.slice((page - 1) * limit, page * limit);
|
|
36
|
-
|
|
37
|
+
answerLocally(this, output, listOf("runs list", {
|
|
37
38
|
items: shown.map(snakeKeys),
|
|
38
39
|
limit,
|
|
39
40
|
page,
|
|
@@ -60,7 +61,7 @@ export const runsCommand = (app) => {
|
|
|
60
61
|
const { problems } = this.opts();
|
|
61
62
|
const shown = problems ? events.filter(notable) : events;
|
|
62
63
|
// Events are log records as written; only the run's own fields are renamed here.
|
|
63
|
-
|
|
64
|
+
answerLocally(this, output, itemOf({
|
|
64
65
|
...snakeKeys(found.metadata),
|
|
65
66
|
directory: found.dir,
|
|
66
67
|
event_count: events.length,
|
package/dist/domain/deals.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/** A pipeline holds deals or leads, never both; a deal and a lead are rows of their own tables. */
|
|
2
|
+
export declare const DEAL_KINDS: readonly ["deal", "lead"];
|
|
3
|
+
/** Whether a deal or a lead ended, kept apart from the stage it stands in. */
|
|
3
4
|
export declare const DEAL_STATUSES: readonly ["open", "won", "lost"];
|
|
5
|
+
/** What reaching a stage means, as a CRM marks it: still open, or won or lost. */
|
|
6
|
+
export declare const STAGE_OUTCOMES: readonly ["open", "won", "lost"];
|
|
4
7
|
/** The part a person plays on a deal: the owner's side runs it, the client's side talks or decides. */
|
|
5
8
|
export declare const DEAL_ROLES: readonly ["owner", "contact", "decision-maker"];
|
|
9
|
+
export type DealKind = (typeof DEAL_KINDS)[number];
|
|
6
10
|
export type DealStatus = (typeof DEAL_STATUSES)[number];
|
|
11
|
+
export type StageOutcome = DealStatus;
|
|
7
12
|
export type DealRole = (typeof DEAL_ROLES)[number];
|
|
8
13
|
export interface DealTitleFields {
|
|
9
14
|
id: number;
|
|
@@ -12,9 +17,6 @@ export interface DealTitleFields {
|
|
|
12
17
|
amount: number | null;
|
|
13
18
|
currency: string | null;
|
|
14
19
|
status: DealStatus;
|
|
15
|
-
review: FinanceReview;
|
|
16
20
|
}
|
|
17
|
-
/** "Site redesign · Proposal, 500 000.00 RUB": a ref summary's title, which `refLabel` prefixes. */
|
|
21
|
+
/** "Site redesign · Proposal, 500 000.00 RUB (won)": a ref summary's title, which `refLabel` prefixes. */
|
|
18
22
|
export declare const dealLine: (deal: DealTitleFields) => string;
|
|
19
|
-
/** "Deal Site redesign · Proposal, 500 000.00 RUB (won)". */
|
|
20
|
-
export declare const dealTitle: (deal: DealTitleFields) => string;
|
package/dist/domain/deals.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { moneyLabel } from "./money.js";
|
|
2
|
-
/**
|
|
2
|
+
/** A pipeline holds deals or leads, never both; a deal and a lead are rows of their own tables. */
|
|
3
|
+
export const DEAL_KINDS = ["deal", "lead"];
|
|
4
|
+
/** Whether a deal or a lead ended, kept apart from the stage it stands in. */
|
|
3
5
|
export const DEAL_STATUSES = ["open", "won", "lost"];
|
|
6
|
+
/** What reaching a stage means, as a CRM marks it: still open, or won or lost. */
|
|
7
|
+
export const STAGE_OUTCOMES = DEAL_STATUSES;
|
|
4
8
|
/** The part a person plays on a deal: the owner's side runs it, the client's side talks or decides. */
|
|
5
9
|
export const DEAL_ROLES = ["owner", "contact", "decision-maker"];
|
|
6
|
-
/** "Site redesign · Proposal, 500 000.00 RUB": a ref summary's title, which `refLabel` prefixes. */
|
|
10
|
+
/** "Site redesign · Proposal, 500 000.00 RUB (won)": a ref summary's title, which `refLabel` prefixes. */
|
|
7
11
|
export const dealLine = (deal) => {
|
|
8
12
|
const money = deal.amount === null || deal.currency === null ? "" : `, ${moneyLabel(deal.amount, deal.currency)}`;
|
|
9
|
-
|
|
10
|
-
.filter(Boolean)
|
|
11
|
-
.join(", ");
|
|
12
|
-
return `${deal.title} · ${deal.stage}${money}${notes ? ` (${notes})` : ""}`;
|
|
13
|
+
return `${deal.title} · ${deal.stage}${money}${deal.status === "open" ? "" : ` (${deal.status})`}`;
|
|
13
14
|
};
|
|
14
|
-
/** "Deal Site redesign · Proposal, 500 000.00 RUB (won)". */
|
|
15
|
-
export const dealTitle = (deal) => `Deal ${dealLine(deal)}`;
|
|
@@ -33,7 +33,7 @@ export type Reference = {
|
|
|
33
33
|
* Things named by their store id (a task also by its key or the task package's id). `entity` is the name
|
|
34
34
|
* before organizations and projects: it still parses, so an old file's reference resolves as not found.
|
|
35
35
|
*/
|
|
36
|
-
export declare const SIMPLE_REFERENCES: readonly ["note", "document", "person", "organization", "project", "task", "memory", "fact", "bot", "event", "invoice", "payment", "order", "deal", "entity"];
|
|
36
|
+
export declare const SIMPLE_REFERENCES: readonly ["note", "document", "person", "organization", "project", "task", "memory", "fact", "bot", "event", "invoice", "payment", "order", "deal", "lead", "pipeline", "entity"];
|
|
37
37
|
export type SimpleReference = (typeof SIMPLE_REFERENCES)[number];
|
|
38
38
|
export type RefKindName = Exclude<Reference["type"], "entity">;
|
|
39
39
|
export interface RefKindSpec<R extends Reference = Reference> {
|
package/dist/errors/reasons.js
CHANGED
|
@@ -203,14 +203,14 @@ const OTHERS = [
|
|
|
203
203
|
validation("money.amount", "the amount is not a decimal string in the currency's scale"),
|
|
204
204
|
validation("money.currency", "the currency is not one the store keeps money in"),
|
|
205
205
|
validation("money.quantity", "the quantity is not a decimal above 0"),
|
|
206
|
-
validation("deal.closed", "a
|
|
207
|
-
validation("deal.field", "a field of the deal is not valid"),
|
|
208
|
-
|
|
209
|
-
validation("deal.ref", "the reference names no deal"),
|
|
206
|
+
validation("deal.closed", "a deleted deal or lead does not move or close"),
|
|
207
|
+
validation("deal.field", "a field of the deal or lead is not valid"),
|
|
208
|
+
validation("deal.ref", "the reference names no deal or lead"),
|
|
210
209
|
validation("deal.stage", "no stage of that name, or a stage name more than one pipeline has"),
|
|
211
|
-
validation("pipeline.
|
|
210
|
+
validation("pipeline.ambiguous", "more than one pipeline has that name; name it by its pipeline: reference"),
|
|
211
|
+
validation("pipeline.duplicate", "a pipeline of that name and kind is already stored"),
|
|
212
212
|
validation("pipeline.field", "a pipeline needs a name and at least one stage, each named once"),
|
|
213
|
-
["pipeline.not-found", "not_found", "no stored pipeline has that name"],
|
|
213
|
+
["pipeline.not-found", "not_found", "no stored pipeline has that name or reference"],
|
|
214
214
|
validation("order.duplicate", "the shop's order with this number is already stored; change its status instead"),
|
|
215
215
|
validation("order.proposed", "the shop's order with this number already waits for the owner in a findings batch"),
|
|
216
216
|
validation("order.field", "a field of the order is not valid"),
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { bareCheck, type CheckWords, checkWords } from "./domain/checks.js";
|
|
2
2
|
export type { CounterField, CounterObservation, CounterObservations, CounterState } from "./domain/counters.js";
|
|
3
3
|
export { COUNTER_FIELDS, counterValue, observedCounters } from "./domain/counters.js";
|
|
4
|
-
export { DEAL_ROLES, DEAL_STATUSES, type DealRole, type DealStatus } from "./domain/deals.js";
|
|
4
|
+
export { DEAL_KINDS, DEAL_ROLES, DEAL_STATUSES, type DealKind, type DealRole, type DealStatus, STAGE_OUTCOMES, type StageOutcome, } from "./domain/deals.js";
|
|
5
5
|
export { FINANCE_REVIEWS, type FinanceReview, INVOICE_DIRECTIONS, INVOICE_STATUSES, type InvoiceDirection, type InvoiceStatus, MONEY_DIRECTIONS, type MoneyDirection, } from "./domain/finance.js";
|
|
6
6
|
export { type FormattedText, type HtmlFormatting, type MarkdownFormatting, type TextSpan, validateFormattedText, } from "./domain/formatting.js";
|
|
7
7
|
export { formatLocator, isLocator, type MessageLocator, parseLocator } from "./domain/locator.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { bareCheck, checkWords } from "./domain/checks.js";
|
|
2
2
|
export { COUNTER_FIELDS, counterValue, observedCounters } from "./domain/counters.js";
|
|
3
|
-
export { DEAL_ROLES, DEAL_STATUSES } from "./domain/deals.js";
|
|
3
|
+
export { DEAL_KINDS, DEAL_ROLES, DEAL_STATUSES, STAGE_OUTCOMES, } from "./domain/deals.js";
|
|
4
4
|
export { FINANCE_REVIEWS, INVOICE_DIRECTIONS, INVOICE_STATUSES, MONEY_DIRECTIONS, } from "./domain/finance.js";
|
|
5
5
|
export { validateFormattedText, } from "./domain/formatting.js";
|
|
6
6
|
export { formatLocator, isLocator, parseLocator } from "./domain/locator.js";
|
package/dist/mail/cli/index.js
CHANGED
|
@@ -6,8 +6,9 @@ import * as v from "valibot";
|
|
|
6
6
|
import { answerEntity } from "../../cli/answer-entity.js";
|
|
7
7
|
import { environmentOf, outputFor } from "../../cli/context.js";
|
|
8
8
|
import { FETCH, LOCAL_READ, REMOTE_READ } from "../../cli/effects.js";
|
|
9
|
-
import { itemOf, listOf,
|
|
9
|
+
import { itemOf, listOf, snakeKeys } from "../../cli/envelope.js";
|
|
10
10
|
import { listQuery, typedQuery, withQuery } from "../../cli/list-query.js";
|
|
11
|
+
import { answerLocally } from "../../cli/local-times.js";
|
|
11
12
|
import { messagesAnswer, offsetOf, shownOf } from "../../cli/messenger/search-answers.js";
|
|
12
13
|
import { backendRequest, noteServer, serverSearchOptions } from "../../cli/messenger/search-backend-options.js";
|
|
13
14
|
import { positiveCount } from "../../cli/paging.js";
|
|
@@ -193,7 +194,7 @@ Search guide: https://github.com/WireCatLabs/cli-core/blob/main/docs/search/quer
|
|
|
193
194
|
const issues = serverIssues(found.server);
|
|
194
195
|
if (issues.length)
|
|
195
196
|
answer.meta = { ...answer.meta, complete: false, issues: [...(answer.meta?.issues ?? []), ...issues] };
|
|
196
|
-
|
|
197
|
+
answerLocally(this, output, answer, (local) => {
|
|
197
198
|
if (!found.wordsReady)
|
|
198
199
|
output.renderer.note(`the word index is still being built — \`${mail.app.command} store migrate\` finishes it`);
|
|
199
200
|
noteServer(output, found.server);
|
|
@@ -204,12 +205,12 @@ Search guide: https://github.com/WireCatLabs/cli-core/blob/main/docs/search/quer
|
|
|
204
205
|
const shown = shownOf(found.items.length, (page - 1) * limit, found.total);
|
|
205
206
|
if (shown)
|
|
206
207
|
output.renderer.note(shown);
|
|
207
|
-
return found.items.map((item) => `${hitText(item, level)}\n`).join("");
|
|
208
|
-
});
|
|
208
|
+
return found.items.map((item) => `${hitText(item, level, local)}\n`).join("");
|
|
209
|
+
}, options.timezone);
|
|
209
210
|
});
|
|
210
211
|
return new Command("search").summary("search the stored mail").description("find stored emails").addCommand(leaf);
|
|
211
212
|
};
|
|
212
|
-
const shownText = (emails) => `${emails.map((email) => [hitText(email, "full"), attachmentsText(email)].filter(Boolean).join("\n")).join("\n\n")}\n`;
|
|
213
|
+
const shownText = (emails, local) => `${emails.map((email) => [hitText(email, "full", local), attachmentsText(email)].filter(Boolean).join("\n")).join("\n\n")}\n`;
|
|
213
214
|
const messagesCommand = (mail) => {
|
|
214
215
|
const messages = new Command("messages").summary("read stored emails").description("read the emails sync stored");
|
|
215
216
|
annotate(messages.command("show"), {
|
|
@@ -249,9 +250,9 @@ const messagesCommand = (mail) => {
|
|
|
249
250
|
});
|
|
250
251
|
const output = outputFor(this);
|
|
251
252
|
const shown = emails.map((email) => snakeKeys(atDetail(email)));
|
|
252
|
-
|
|
253
|
+
answerLocally(this, output, beforeN || afterN
|
|
253
254
|
? listOf("messages show", { items: shown, limit: Math.max(1, shown.length), has_more: false })
|
|
254
|
-
: itemOf(shown.find((_, at) => emails[at]?.anchor) ?? shown[0], unread ? { meta: { complete: false, issues: [unread] } } : undefined), () => shownText(emails));
|
|
255
|
+
: itemOf(shown.find((_, at) => emails[at]?.anchor) ?? shown[0], unread ? { meta: { complete: false, issues: [unread] } } : undefined), (local) => shownText(emails, local));
|
|
255
256
|
});
|
|
256
257
|
messages.addCommand(acknowledgeCommand(mail));
|
|
257
258
|
messages.addCommand(resetMarkCommand(mail));
|
|
@@ -315,14 +316,14 @@ const conversationsCommand = (mail) => {
|
|
|
315
316
|
last_email_at: thread.lastEmailAt === null ? null : new Date(thread.lastEmailAt).toISOString(),
|
|
316
317
|
untrusted: ["subject"],
|
|
317
318
|
}));
|
|
318
|
-
|
|
319
|
+
answerLocally(this, outputFor(this), listOf("conversations list", {
|
|
319
320
|
items,
|
|
320
321
|
limit,
|
|
321
322
|
has_more: threads.length > limit,
|
|
322
323
|
page,
|
|
323
324
|
...(typed.trim() ? { args: { query: typed } } : {}),
|
|
324
|
-
}), () => items
|
|
325
|
-
.map((one) => `${one.ref}\n ${one.last_email_at
|
|
325
|
+
}), (local) => items
|
|
326
|
+
.map((one) => `${one.ref}\n ${one.last_email_at === null ? "time unknown" : local(one.last_email_at)} ${one.emails_count} ${one.subject ?? ""}\n`)
|
|
326
327
|
.join(""));
|
|
327
328
|
});
|
|
328
329
|
answerEntity(list, "mail_thread", mailThread);
|
|
@@ -349,7 +350,7 @@ const conversationsCommand = (mail) => {
|
|
|
349
350
|
: hit));
|
|
350
351
|
});
|
|
351
352
|
const items = emails.map((email) => snakeKeys(atDetail(email)));
|
|
352
|
-
|
|
353
|
+
answerLocally(this, outputFor(this), listOf("conversations show", { items, limit: Math.max(1, items.length), has_more: false }), (local) => shownText(emails, local));
|
|
353
354
|
});
|
|
354
355
|
return conversations;
|
|
355
356
|
};
|
package/dist/mail/cli/review.js
CHANGED
|
@@ -5,8 +5,9 @@ import { defineSteps, MAX_NEXT_STEPS, stepOf, } from "@wirecat/cli-base/standard
|
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
import { environmentOf, outputFor } from "../../cli/context.js";
|
|
7
7
|
import { LOCAL_READ } from "../../cli/effects.js";
|
|
8
|
-
import { listOf
|
|
8
|
+
import { listOf } from "../../cli/envelope.js";
|
|
9
9
|
import { listQuery, nativeField, nativeTerms, queryHelp, typedQuery } from "../../cli/list-query.js";
|
|
10
|
+
import { answerLocally } from "../../cli/local-times.js";
|
|
10
11
|
import { pointsOf } from "../../cli/messenger/catch-up.js";
|
|
11
12
|
import { catchUpQuery, SUMMARY_UNTRUSTED, summaryOf } from "../../cli/messenger/catch-up-answers.js";
|
|
12
13
|
import { messageItem } from "../../cli/messenger/message-items.js";
|
|
@@ -231,11 +232,13 @@ const holderReview = async (command, mail, holder, query) => {
|
|
|
231
232
|
: []),
|
|
232
233
|
];
|
|
233
234
|
const output = outputFor(command);
|
|
234
|
-
|
|
235
|
+
answerLocally(command, output, answer, (local) => {
|
|
235
236
|
output.renderer.note(read.mark
|
|
236
237
|
? `${holder}: past ${read.mark.throughId}${emails.length ? `; findings key ${read.findingsKey}` : ""}; acknowledge with --cursor ${read.cursor}${read.more ? "; more after these" : ""}`
|
|
237
238
|
: `${holder} has no mark yet: it starts from now — acknowledge with --cursor ${read.cursor}`);
|
|
238
|
-
return emails
|
|
239
|
+
return emails
|
|
240
|
+
.map((hit) => `${hitText(hit, undefined, local)}${markText(marks.get(hit.id), hit.excerptOnly)}\n`)
|
|
241
|
+
.join("");
|
|
239
242
|
});
|
|
240
243
|
};
|
|
241
244
|
/**
|
|
@@ -311,7 +314,7 @@ an agent over MCP, and each scheduled run.`)
|
|
|
311
314
|
const answer = reviewAnswer(found, mailbox, saved !== undefined, stepsFor(mail.mount, mailbox));
|
|
312
315
|
if (meta)
|
|
313
316
|
answer.meta = { ...meta, ...answer.meta };
|
|
314
|
-
|
|
317
|
+
answerLocally(this, output, answer, (local) => {
|
|
315
318
|
const read = found.chats.flatMap((chat) => chat.messages);
|
|
316
319
|
if (found.unanswered || found.waiting)
|
|
317
320
|
output.renderer.note(`${[
|
|
@@ -319,16 +322,16 @@ an agent over MCP, and each scheduled run.`)
|
|
|
319
322
|
found.waiting ? `waiting ${found.waiting.olderThanHours}h or more` : "",
|
|
320
323
|
]
|
|
321
324
|
.filter(Boolean)
|
|
322
|
-
.join(", ")}, threads active from ${found.since}`);
|
|
325
|
+
.join(", ")}, threads active from ${local(found.since)}`);
|
|
323
326
|
else
|
|
324
|
-
output.renderer.note(`from ${found.since} to ${found.until}${found.complete ? "" : ", incomplete"}`);
|
|
327
|
+
output.renderer.note(`from ${local(found.since)} to ${local(found.until)}${found.complete ? "" : ", incomplete"}`);
|
|
325
328
|
if (found.quiet > 0)
|
|
326
329
|
output.renderer.note(`bulk:false left out ${found.quiet} threads`);
|
|
327
330
|
if (read.length === 0)
|
|
328
331
|
output.renderer.note("nothing new in the stored mail");
|
|
329
332
|
return read
|
|
330
333
|
.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp))
|
|
331
|
-
.map((hit) => `${hitText(hit)}${markText(hit.marks)}\n`)
|
|
334
|
+
.map((hit) => `${hitText(hit, undefined, local)}${markText(hit.marks)}\n`)
|
|
332
335
|
.join("");
|
|
333
336
|
});
|
|
334
337
|
saved?.save(found.checked ?? {}, found.complete ? { through: found.until } : {});
|
package/dist/mail/read.d.ts
CHANGED
|
@@ -54,7 +54,7 @@ export declare const showMail: (store: MessageStore, locator: string, { before,
|
|
|
54
54
|
*/
|
|
55
55
|
export declare const hitText: (item: StoredHit & {
|
|
56
56
|
excerpt?: string;
|
|
57
|
-
}, level?: "brief" | "full") => string;
|
|
57
|
+
}, level?: "brief" | "full", shown?: (time: string) => string) => string;
|
|
58
58
|
/** Each attachment of a shown email with its text indented under it, or why it has none. */
|
|
59
59
|
export declare const attachmentsText: (hit: ShownMail) => string;
|
|
60
60
|
export {};
|
package/dist/mail/read.js
CHANGED
|
@@ -82,7 +82,7 @@ const line = (text) => text.replace(/\s+/g, " ").trim();
|
|
|
82
82
|
* A found message or email for a reader: its reference first, then when and from whom, who else it went to and
|
|
83
83
|
* where it is filed, its subject or chat, and an excerpt — the whole text only at `full`.
|
|
84
84
|
*/
|
|
85
|
-
export const hitText = (item, level) => {
|
|
85
|
+
export const hitText = (item, level, shown = (time) => time) => {
|
|
86
86
|
const mail = item.email;
|
|
87
87
|
const names = (people) => people.map(({ name, address }) => line(name ?? address)).join(", ");
|
|
88
88
|
const between = mail
|
|
@@ -97,7 +97,7 @@ export const hitText = (item, level) => {
|
|
|
97
97
|
const who = line(item.senderName ?? item.senderId ?? "unknown");
|
|
98
98
|
return [
|
|
99
99
|
item.locator,
|
|
100
|
-
` ${item.timestamp} ${who}${level === "brief" || !between ? "" : ` ${between}`}`,
|
|
100
|
+
` ${shown(item.timestamp)} ${who}${level === "brief" || !between ? "" : ` ${between}`}`,
|
|
101
101
|
...(level === "brief" || !item.chatTitle ? [] : [` ${line(item.chatTitle)}`]),
|
|
102
102
|
` ${level === "full" ? item.text.replace(/\n/g, "\n ") : line(item.excerpt ?? item.text)}`,
|
|
103
103
|
].join("\n");
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { mayDecide } from "../../store/decisions.js";
|
|
3
3
|
import { actionKind } from "./kinds/action.js";
|
|
4
|
-
import { dealKind } from "./kinds/deals.js";
|
|
5
4
|
import { factKind } from "./kinds/fact.js";
|
|
6
5
|
import { invoiceKind, paymentKind } from "./kinds/finance.js";
|
|
7
6
|
import { findingsKind } from "./kinds/findings.js";
|
|
@@ -136,7 +135,6 @@ export const proposalRegistry = (store) => {
|
|
|
136
135
|
invoiceKind(store),
|
|
137
136
|
paymentKind(store),
|
|
138
137
|
orderKind(store),
|
|
139
|
-
dealKind(store),
|
|
140
138
|
organizationKind(store),
|
|
141
139
|
memoryKind(store),
|
|
142
140
|
findingsKind(store),
|
package/dist/store/index.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export type { BotUpdate, BotUpdateStore } from "./sqlite/bot-updates.js";
|
|
|
34
34
|
export type { CatalogActor, CatalogEntry, CatalogInput, CatalogQuery, CatalogStore } from "./sqlite/catalog.js";
|
|
35
35
|
export { CATALOG_QUERY } from "./sqlite/catalog.js";
|
|
36
36
|
export type { ChatMetadata } from "./sqlite/chats.js";
|
|
37
|
-
export type { Deal, DealFilter, DealInput, DealPerson, DealsStore, Pipeline, PipelineStage, } from "./sqlite/deals.js";
|
|
37
|
+
export type { Deal, DealFilter, DealInput, DealPerson, DealsStore, FunnelStage, Pipeline, PipelineInput, PipelineReport, PipelineStage, } from "./sqlite/deals.js";
|
|
38
38
|
export type { Email, EmailAddress, EmailAttachment, EmailFacts, EmailFilter, EmailInput, EmailInThread, EmailLocationInput, EmailRecipient, EmailRef, EmailThread, EmailVectorHit, FolderEmail, Mailbox, MailboxInput, MailFilter, MailStore, PartialEmail, SenderScore, ThreadDetails, ThreadSave, } from "./sqlite/emails.js";
|
|
39
39
|
export { EVENT_FIELDS, type EventChunkToEmbed, type EventHit, type EventQuery, type EventSearch, type NearestEvent, } from "./sqlite/event-search.js";
|
|
40
40
|
export type { EvidenceSource } from "./sqlite/evidence-sources.js";
|