@wirecat/cli-core 0.506.0 → 0.508.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 +59 -0
- package/dist/calendar/cli/answers.d.ts +5 -2
- package/dist/calendar/cli/answers.js +5 -2
- package/dist/calendar/cli/index.js +44 -33
- package/dist/calendar/reads.d.ts +2 -1
- package/dist/calendar/reads.js +4 -3
- package/dist/calendar/server-search.d.ts +31 -0
- package/dist/calendar/server-search.js +28 -0
- package/dist/cli/bot/token.js +2 -2
- package/dist/cli/messenger/backfill-command.js +26 -3
- package/dist/embeddings/keys.js +1 -1
- package/dist/mail/sync.d.ts +2 -0
- package/dist/mail/sync.js +20 -0
- package/dist/meetings/reads.d.ts +1 -0
- package/dist/oauth/keeper.js +2 -2
- package/dist/oauth/storage.d.ts +3 -2
- package/dist/oauth/storage.js +3 -3
- package/dist/services/archive.d.ts +6 -0
- package/dist/services/archive.js +4 -0
- package/dist/store/sqlite/transaction.d.ts +1 -0
- package/dist/store/sqlite/transaction.js +1 -1
- package/dist/terminal/sign-in-steps.js +2 -4
- package/dist/terminal/step-lines.d.ts +7 -0
- package/dist/terminal/step-lines.js +8 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,65 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.508.0 — 31.10.2026
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`events list --page <n>`: the next page of stored events.** `events list` said `hasMore` and had no way to read on;
|
|
15
|
+
it now takes `--page` from 1 with `--limit` events a page (100), and the answer's `page` is the number shown, so a
|
|
16
|
+
host's `listOf` can offer `page.next` (WC-OUT-5). `eventsAnswer` takes the page as a third argument.
|
|
17
|
+
- **`search events --backend both`** asks the store and the provider's own search together, where `--backend` is
|
|
18
|
+
offered (`deps.serverSearch`). The stored hits come first in the stored search's order, `found_by` gaining
|
|
19
|
+
`server` on each the provider also found; then the hits only the provider found, earliest first. One page, with no
|
|
20
|
+
`next_offset`; `has_more` when either side has more. It refuses `--offline`, `--cursor` and `--newest` as
|
|
21
|
+
`--backend server` does, and takes `--exact` for the stored side. `calendar_search_hit` allows `server` in a stored
|
|
22
|
+
hit's `found_by`. An occurrence the store keeps under another id than the provider's search names shows twice.
|
|
23
|
+
|
|
24
|
+
### Changed — may break callers
|
|
25
|
+
|
|
26
|
+
- **`events create --id` and `events update --following --id` are now `--operation-id`** (WC-CMD-10, WC-OUT-10): the
|
|
27
|
+
value is the `operation_id` the answer and an `outcome_unknown` already name. `--id` is refused as an unknown
|
|
28
|
+
option; errors name `--operation-id`. The calendar MCP tools keep their `id` argument.
|
|
29
|
+
- **`search events` no longer refuses `--backend both`** with `search.one-backend`; see Added.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- **`search events --cursor` help** said "a page's next_offset", a key the answer a host prints does not have; it now
|
|
34
|
+
says to go on from where the last page stopped, as `page.next` gives it.
|
|
35
|
+
|
|
36
|
+
- **A budgeted `syncMail` waits out a locked store.** When another process (memo's scheduled sync) holds the store's
|
|
37
|
+
write lock past its 5 s busy timeout, the sync used to fail with `database is locked`; it now waits 2 s, 4 s, ... (at
|
|
38
|
+
most 5 times, each a request of the budget) and reads the round again. When the budget has no room the run ends as
|
|
39
|
+
`partial`. `options.wait` lets a test skip the pause.
|
|
40
|
+
- **A bot token, a bot's named secret or an AI provider's key the owner sets again goes back to the keyring.** After
|
|
41
|
+
one keyring failure such a secret stayed in the private file for good, as cli-base 0.68.0 keeps a plain write where
|
|
42
|
+
the secret is. `BotTokenStore.write`, `writeSecret` and `embeddingKeys().write` now write with `{ signIn: true }`:
|
|
43
|
+
the keyring is tried and, once it takes the secret, the file copy is removed (WC-SECR-4). `writeKeyring` was
|
|
44
|
+
keyring-only already; the CAS key is only read here.
|
|
45
|
+
|
|
46
|
+
## 0.507.0 — 31.10.2026
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
- **`messages sync --json` prints its progress on stderr**, for a window to draw a bar instead of an endless one
|
|
51
|
+
(WireCat Desktop). One JSON line each, counts only — never a chat's id or title:
|
|
52
|
+
`{"event":"progress","step":"sync","chats":3,"chats_done":1,"fetched":250}`. With `--all`, one once the chat list
|
|
53
|
+
is read (`chats_done` 0), then one after each chat, those passed as already current included; for one chat, one per
|
|
54
|
+
page with `chats` and `chats_done` `null`. At most about two a second; the first and last of `--all` always come.
|
|
55
|
+
Only in machine output (`--json`, `--jsonl`, or stdout not a terminal) and not in a `--background` job's log;
|
|
56
|
+
stdout still carries the one answer. Archive services take an optional `onChat` for the same counts.
|
|
57
|
+
|
|
58
|
+
### Changed — may break callers
|
|
59
|
+
|
|
60
|
+
- **Needs `@wirecat/cli-base` 0.68.0** (peer range `>=0.68.0 <0.69.0`). cli-base's `Credentials` in `auto` now reads
|
|
61
|
+
a sign-in the private file holds before the keyring and keeps its refreshes there (WC-SECR-4).
|
|
62
|
+
|
|
63
|
+
### Fixed
|
|
64
|
+
|
|
65
|
+
- **A new OAuth sign-in moves a file sign-in back to the keyring.** `TokenStorage.write` takes `{ signIn: true }`, and
|
|
66
|
+
the keeper's `signIn` and `save` pass it: the keyring is tried again and, once it takes the sign-in, the file copy
|
|
67
|
+
is removed. A refresh stays where the sign-in is, so a locked keyring is not tried for it on every refresh.
|
|
68
|
+
|
|
10
69
|
## 0.506.0 — 31.10.2026
|
|
11
70
|
|
|
12
71
|
### Added
|
|
@@ -156,7 +156,10 @@ export declare const calendarEvent: v.StrictObjectSchema<{
|
|
|
156
156
|
}, undefined>, undefined>;
|
|
157
157
|
readonly untrusted: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
158
158
|
}, undefined>;
|
|
159
|
-
/**
|
|
159
|
+
/**
|
|
160
|
+
* A hit of the stored search (with `--backend both`, `server` too when the provider also found it), or one the
|
|
161
|
+
* provider's own search found and nothing stored.
|
|
162
|
+
*/
|
|
160
163
|
export declare const calendarSearchHit: v.UnionSchema<[v.StrictObjectSchema<{
|
|
161
164
|
readonly provider: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
162
165
|
readonly account: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
@@ -172,7 +175,7 @@ export declare const calendarSearchHit: v.UnionSchema<[v.StrictObjectSchema<{
|
|
|
172
175
|
readonly excerpt: v.StringSchema<undefined>;
|
|
173
176
|
readonly occurrences: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
174
177
|
readonly updated_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
|
|
175
|
-
readonly found_by: v.ArraySchema<v.PicklistSchema<["words", "meaning"], undefined>, undefined>;
|
|
178
|
+
readonly found_by: v.ArraySchema<v.PicklistSchema<["words", "meaning", "server"], undefined>, undefined>;
|
|
176
179
|
}, undefined>, v.StrictObjectSchema<{
|
|
177
180
|
readonly provider: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
178
181
|
readonly account: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
@@ -138,7 +138,10 @@ const hitFields = {
|
|
|
138
138
|
url: v.optional(text),
|
|
139
139
|
untrusted,
|
|
140
140
|
};
|
|
141
|
-
/**
|
|
141
|
+
/**
|
|
142
|
+
* A hit of the stored search (with `--backend both`, `server` too when the provider also found it), or one the
|
|
143
|
+
* provider's own search found and nothing stored.
|
|
144
|
+
*/
|
|
142
145
|
export const calendarSearchHit = v.union([
|
|
143
146
|
v.strictObject({
|
|
144
147
|
...hitFields,
|
|
@@ -146,7 +149,7 @@ export const calendarSearchHit = v.union([
|
|
|
146
149
|
excerpt: text,
|
|
147
150
|
occurrences: count,
|
|
148
151
|
updated_at: time,
|
|
149
|
-
found_by: v.array(v.picklist(["words", "meaning"])),
|
|
152
|
+
found_by: v.array(v.picklist(["words", "meaning", "server"])),
|
|
150
153
|
}),
|
|
151
154
|
v.strictObject({
|
|
152
155
|
...hitFields,
|
|
@@ -12,7 +12,7 @@ import { linkPeople } from "../../services/people.js";
|
|
|
12
12
|
import { BUSY_QUERY, BUSY_QUERY_NOTE, busyAnswer, busyQuery, workingHoursOf } from "../busy.js";
|
|
13
13
|
import { CALENDAR_UNTRUSTED_FIELDS, calendarIn, calendarItem, EVENTS_QUERY, EVENTS_QUERY_NOTE, eventAnswer, eventIdOf, eventOf, eventsAnswer, eventsFilter, peopleAnswer, resolveAnswer, reviewsAnswer, weekFilter, } from "../reads.js";
|
|
14
14
|
import { REPEAT_HELP } from "../recurrence.js";
|
|
15
|
-
import { serverSearchAnswer } from "../server-search.js";
|
|
15
|
+
import { bothSearchAnswer, serverSearchAnswer } from "../server-search.js";
|
|
16
16
|
import { calendarStatus, syncCalendars } from "../sync.js";
|
|
17
17
|
import { createEvent, deleteEvent, EVENT_RESPONSES, moveEvent, respondToEvent, updateEvent, } from "../writes.js";
|
|
18
18
|
import { CALENDARS_QUERY, declareCalendarAnswers, PEOPLE_QUERY } from "./answers.js";
|
|
@@ -201,8 +201,9 @@ const addCalendarCommandsDefinition = (program, deps) => {
|
|
|
201
201
|
}), command);
|
|
202
202
|
});
|
|
203
203
|
const events = annotate(program.command("events").description("the account's stored events"), { effects: GROUP });
|
|
204
|
-
example(withQuery(annotate(events.command("list"), { effects: LOCAL_READ }).description("list stored events, earliest first"), EVENTS_QUERY).addHelpText("after", EVENTS_QUERY_NOTE), "events list starts_at>=2026-11-02 starts_at<2026-11-07 --json", "events list ends_at>=today person:ana@example.com")
|
|
205
|
-
.option("--limit <n>", "events
|
|
204
|
+
example(withQuery(annotate(events.command("list"), { effects: LOCAL_READ }).description("list stored events, earliest first"), EVENTS_QUERY).addHelpText("after", EVENTS_QUERY_NOTE), "events list starts_at>=2026-11-02 starts_at<2026-11-07 --json", "events list ends_at>=today person:ana@example.com", "events list starts_at>=2026-11-02 --limit 50 --page 2")
|
|
205
|
+
.option("--limit <n>", "events per page, 1–1000", integer, 100)
|
|
206
|
+
.option("--page <n>", "which page, starting at 1", integer, 1)
|
|
206
207
|
.action(async (words, opts, command) => {
|
|
207
208
|
const filter = await eventsFilter(words, {
|
|
208
209
|
store: deps.store,
|
|
@@ -211,7 +212,7 @@ const addCalendarCommandsDefinition = (program, deps) => {
|
|
|
211
212
|
now: now(),
|
|
212
213
|
personOf: personId,
|
|
213
214
|
});
|
|
214
|
-
print(eachMarked(await eventsAnswer(deps.store, filter && { ...filter, limit: opts.limit }), EVENT_ITEM_UNTRUSTED), command);
|
|
215
|
+
print(eachMarked(await eventsAnswer(deps.store, filter && { ...filter, limit: opts.limit }, opts.page), EVENT_ITEM_UNTRUSTED), command);
|
|
215
216
|
});
|
|
216
217
|
example(annotate(events.command("show"), { effects: LOCAL_READ })
|
|
217
218
|
.description("one event: its calendar copies, attendees with their person, Drive files and history")
|
|
@@ -225,49 +226,59 @@ const addCalendarCommandsDefinition = (program, deps) => {
|
|
|
225
226
|
const search = example(annotate(searches.command("events"), { effects: deps.serverSearch ? LOCAL_OR_REMOTE_READ : LOCAL_READ })
|
|
226
227
|
.description(deps.serverSearch
|
|
227
228
|
? "find stored events by words, and by meaning when its model is downloaded; with --backend server, ask " +
|
|
228
|
-
"the provider instead"
|
|
229
|
+
"the provider instead, with --backend both, both"
|
|
229
230
|
: "find stored events by words, and by meaning when its model is downloaded")
|
|
230
|
-
.argument("<query...>", "what to find in titles, descriptions, places and attendees"), "search events roadmap planning --json", ...(deps.serverSearch
|
|
231
|
+
.argument("<query...>", "what to find in titles, descriptions, places and attendees"), "search events roadmap planning --json", ...(deps.serverSearch
|
|
232
|
+
? ["search events --backend server quarterly review", "search events --backend both quarterly review"]
|
|
233
|
+
: []))
|
|
231
234
|
.option("--limit <n>", "events per page", integer, 20)
|
|
232
|
-
.option("--cursor <cursor>", "
|
|
235
|
+
.option("--cursor <cursor>", "go on from where the last page stopped; page.next gives it", (text) => (text === "0" ? 0 : integer(text)), 0)
|
|
233
236
|
.option("--exact", "every word as written, no word stems")
|
|
234
237
|
.option("--newest", "latest start first instead of best first");
|
|
235
238
|
if (deps.serverSearch)
|
|
236
239
|
search.addOption(new Option("--backend <archive|server|both>", "where to search: archive, the local store, or server, the provider's own search over the synced calendars, " +
|
|
237
|
-
"earliest first"
|
|
240
|
+
"earliest first; both gives the store's hits, each the provider also found marked server, then the " +
|
|
241
|
+
"provider's other hits, one page with no cursor")
|
|
238
242
|
.choices(["archive", "server", "both"])
|
|
239
243
|
.default("archive"));
|
|
240
244
|
search.action(async (words, opts, command) => {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
245
|
+
const archived = () => searchEvents(messages("search"), words.join(" "), {
|
|
246
|
+
accounts: [deps.account],
|
|
247
|
+
limit: opts.limit,
|
|
248
|
+
offset: opts.cursor,
|
|
249
|
+
timezone: zone(),
|
|
250
|
+
command: program.name(),
|
|
251
|
+
...(opts.exact ? { exact: true } : {}),
|
|
252
|
+
...(opts.newest ? { newest: true } : {}),
|
|
253
|
+
});
|
|
254
|
+
if (opts.backend === "server" || opts.backend === "both") {
|
|
255
|
+
const flag = `search events --backend ${opts.backend}`;
|
|
246
256
|
if (command.optsWithGlobals().offline)
|
|
247
|
-
throw new CliError("permission_error",
|
|
257
|
+
throw new CliError("permission_error", `${flag} asks the provider; leave out --offline`, {
|
|
248
258
|
reason: "search.offline",
|
|
249
259
|
});
|
|
250
|
-
if (opts.
|
|
251
|
-
throw new CliError("validation_error",
|
|
252
|
-
|
|
260
|
+
if (opts.backend === "server" && opts.exact)
|
|
261
|
+
throw new CliError("validation_error", `${flag} takes no --exact: the provider matches words its own way`, {
|
|
262
|
+
reason: "search.server-options",
|
|
263
|
+
});
|
|
264
|
+
if (opts.cursor || opts.newest)
|
|
265
|
+
throw new CliError("validation_error", `${flag} takes neither --cursor nor --newest: the provider ranks and pages its own hits`, { reason: "search.server-options" });
|
|
266
|
+
const served = await serverSearchAnswer(source(), deps.store, {
|
|
253
267
|
accountId: deps.accountId,
|
|
254
268
|
account: deps.account,
|
|
255
269
|
query: words.join(" "),
|
|
256
270
|
limit: opts.limit,
|
|
257
271
|
});
|
|
258
|
-
|
|
272
|
+
if (opts.backend === "server")
|
|
273
|
+
print({ ...served, untrusted: SERVER_SEARCH_UNTRUSTED }, command);
|
|
274
|
+
else
|
|
275
|
+
print({
|
|
276
|
+
...(await bothSearchAnswer(deps.store, deps.accountId, await archived(), served, opts.limit)),
|
|
277
|
+
untrusted: SEARCH_UNTRUSTED,
|
|
278
|
+
}, command);
|
|
259
279
|
return;
|
|
260
280
|
}
|
|
261
|
-
|
|
262
|
-
accounts: [deps.account],
|
|
263
|
-
limit: opts.limit,
|
|
264
|
-
offset: opts.cursor,
|
|
265
|
-
timezone: zone(),
|
|
266
|
-
command: program.name(),
|
|
267
|
-
...(opts.exact ? { exact: true } : {}),
|
|
268
|
-
...(opts.newest ? { newest: true } : {}),
|
|
269
|
-
});
|
|
270
|
-
print({ ...found, untrusted: SEARCH_UNTRUSTED }, command);
|
|
281
|
+
print({ ...(await archived()), untrusted: SEARCH_UNTRUSTED }, command);
|
|
271
282
|
});
|
|
272
283
|
if (deps.writes)
|
|
273
284
|
addEventWrites(events, deps.writes, { deps, now, zone, print, example });
|
|
@@ -322,7 +333,7 @@ const addEventWrites = (events, writes, { deps, now, zone, print, example }) =>
|
|
|
322
333
|
zone: zone(),
|
|
323
334
|
names: {
|
|
324
335
|
notify: "--notify or --no-notify",
|
|
325
|
-
id: "--id",
|
|
336
|
+
id: "--operation-id",
|
|
326
337
|
calendar: "--calendar",
|
|
327
338
|
series: "--series",
|
|
328
339
|
following: "--following",
|
|
@@ -362,10 +373,10 @@ const addEventWrites = (events, writes, { deps, now, zone, print, example }) =>
|
|
|
362
373
|
.option("--calendar <id>", "the calendar, when the event is on more than one of the account's");
|
|
363
374
|
example(notify(fields(write("create"), false)
|
|
364
375
|
.description("add an event to a calendar; with --repeat, one that repeats")
|
|
365
|
-
.argument("<calendar>", "the calendar's id, or the provider's id")).option("--id <id>", "the
|
|
376
|
+
.argument("<calendar>", "the calendar's id, or the provider's id")).option("--operation-id <id>", "the operation_id a create that ended in outcome_unknown named: a retry finds that event"), "events create 3 --title 'Example planning' --start-time 2026-11-02T10:00 --end-time 2026-11-02T11:00", "events create 3 --title 'Example review' --start-date 2026-11-03 --end-date 2026-11-03 " +
|
|
366
377
|
"--attendee ana@example.com --no-notify", "events create 3 --title 'Example standup' --start-time 2026-11-02T09:30 --end-time 2026-11-02T09:45 " +
|
|
367
378
|
"--repeat 'FREQ=WEEKLY;BYDAY=MO,WE,FR;COUNT=12'").action(async (calendar, opts, command) => {
|
|
368
|
-
const { notify: choice, id, ...rest } = opts;
|
|
379
|
+
const { notify: choice, operationId: id, ...rest } = opts;
|
|
369
380
|
printWritten(await createEvent(context(command), calendar, fieldsOf(rest), {
|
|
370
381
|
...(choice === undefined ? {} : { notify: choice }),
|
|
371
382
|
...(id === undefined ? {} : { id }),
|
|
@@ -377,8 +388,8 @@ const addEventWrites = (events, writes, { deps, now, zone, print, example }) =>
|
|
|
377
388
|
.option("--series", "change the whole repeating event, not only this occurrence")
|
|
378
389
|
.option("--following", "change this occurrence and every later one: the repeating event ends before it and a new one starts " +
|
|
379
390
|
"with the change")
|
|
380
|
-
.option("--id <id>", "with --following, the
|
|
381
|
-
const { calendar: _calendar, series: _series, following: _following, notify: _notify, id, ...rest } = opts;
|
|
391
|
+
.option("--operation-id <id>", "with --following, the operation_id an outcome_unknown named: a retry finds that new series")), "events update event:42 --start-time 2026-11-02T14:00 --end-time 2026-11-02T15:00 --notify", "events update event:42 --series --repeat 'FREQ=WEEKLY;BYDAY=TU;UNTIL=20261231T230000Z' --no-notify", "events update event:42 --following --start-time 2026-11-09T10:00 --end-time 2026-11-09T10:30 --no-notify").action(async (reference, opts, command) => {
|
|
392
|
+
const { calendar: _calendar, series: _series, following: _following, notify: _notify, operationId: id, ...rest } = opts;
|
|
382
393
|
printWritten(await updateEvent(context(command), reference, fieldsOf(rest), {
|
|
383
394
|
...choicesOf(opts),
|
|
384
395
|
...(id === undefined ? {} : { id }),
|
package/dist/calendar/reads.d.ts
CHANGED
|
@@ -182,7 +182,8 @@ export declare const resolveAnswer: (store: EventStore, accountId: number, revie
|
|
|
182
182
|
status: "apart" | "linked" | "open";
|
|
183
183
|
chosen: string | null;
|
|
184
184
|
}>;
|
|
185
|
-
|
|
185
|
+
/** One page of the events, from 1: `filter.limit` events each. */
|
|
186
|
+
export declare const eventsAnswer: (store: EventStore, filter: EventFilter | null, number?: number) => Promise<{
|
|
186
187
|
limit: number;
|
|
187
188
|
hasMore: boolean;
|
|
188
189
|
items: {
|
package/dist/calendar/reads.js
CHANGED
|
@@ -331,10 +331,11 @@ const overlapsOf = async (store, rows, filter) => {
|
|
|
331
331
|
other.endsAt > event.startsAt),
|
|
332
332
|
]));
|
|
333
333
|
};
|
|
334
|
-
|
|
334
|
+
/** One page of the events, from 1: `filter.limit` events each. */
|
|
335
|
+
export const eventsAnswer = async (store, filter, number = 1) => {
|
|
335
336
|
if (filter === null)
|
|
336
337
|
return listed([]);
|
|
337
|
-
const { rows, limit, hasMore } = await page(store, filter);
|
|
338
|
+
const { rows, limit, hasMore } = await page(store, { ...filter, offset: (number - 1) * (filter.limit ?? 100) });
|
|
338
339
|
const changes = filter.changedSince === undefined ? null : await changesOf(store, rows, filter, filter.changedSince);
|
|
339
340
|
const overlaps = filter.overlapping ? await overlapsOf(store, rows, filter) : null;
|
|
340
341
|
return listed(rows.map((event) => {
|
|
@@ -350,5 +351,5 @@ export const eventsAnswer = async (store, filter) => {
|
|
|
350
351
|
: {}),
|
|
351
352
|
...(overlaps ? { overlaps: (overlaps.get(event.id) ?? []).map(({ id }) => `event:${id}`) } : {}),
|
|
352
353
|
};
|
|
353
|
-
}), { limit, hasMore });
|
|
354
|
+
}), { limit, hasMore, page: number });
|
|
354
355
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { EventStore } from "../meetings/index.js";
|
|
2
|
+
import type { EventsFound, FoundEvent } from "../services/events-search.js";
|
|
2
3
|
import type { AccountKey } from "../store/store.js";
|
|
3
4
|
import type { CalendarSource } from "./source.js";
|
|
4
5
|
/**
|
|
@@ -29,3 +30,33 @@ export declare const serverSearchAnswer: (source: CalendarSource, store: EventSt
|
|
|
29
30
|
}[];
|
|
30
31
|
has_more: boolean;
|
|
31
32
|
}>;
|
|
33
|
+
type FoundBy = FoundEvent["found_by"][number] | "server";
|
|
34
|
+
type ServerFound = Awaited<ReturnType<typeof serverSearchAnswer>>;
|
|
35
|
+
/**
|
|
36
|
+
* `--backend both`: the stored search's hits in its own order, each one the provider also found marked `server`, then
|
|
37
|
+
* what only the provider found, earliest first. One page and no cursor: the two sides rank and page differently.
|
|
38
|
+
* A provider hit is matched by its stored copy, so an occurrence the store keeps under another id shows twice.
|
|
39
|
+
*/
|
|
40
|
+
export declare const bothSearchAnswer: (store: EventStore, accountId: number, archived: EventsFound, served: ServerFound, limit: number) => Promise<{
|
|
41
|
+
query: string;
|
|
42
|
+
by: "both";
|
|
43
|
+
meaning_skipped?: string | undefined;
|
|
44
|
+
hits: ({
|
|
45
|
+
provider: string;
|
|
46
|
+
account: string;
|
|
47
|
+
calendar: string;
|
|
48
|
+
external_id: string;
|
|
49
|
+
title: string | null;
|
|
50
|
+
starts_at: string | null;
|
|
51
|
+
ends_at: string | null;
|
|
52
|
+
all_day: boolean;
|
|
53
|
+
status: "confirmed" | "tentative";
|
|
54
|
+
location: string | null;
|
|
55
|
+
url?: string | undefined;
|
|
56
|
+
found_by: "server"[];
|
|
57
|
+
} | (Omit<FoundEvent, "found_by"> & {
|
|
58
|
+
found_by: FoundBy[];
|
|
59
|
+
}))[];
|
|
60
|
+
has_more: boolean;
|
|
61
|
+
}>;
|
|
62
|
+
export {};
|
|
@@ -39,3 +39,31 @@ export const serverSearchAnswer = async (source, store, { accountId, account, qu
|
|
|
39
39
|
hits.sort((a, b) => (a.starts_at ?? "").localeCompare(b.starts_at ?? ""));
|
|
40
40
|
return { query, by: "server", hits: hits.slice(0, limit), has_more: more || hits.length > limit };
|
|
41
41
|
};
|
|
42
|
+
/**
|
|
43
|
+
* `--backend both`: the stored search's hits in its own order, each one the provider also found marked `server`, then
|
|
44
|
+
* what only the provider found, earliest first. One page and no cursor: the two sides rank and page differently.
|
|
45
|
+
* A provider hit is matched by its stored copy, so an occurrence the store keeps under another id shows twice.
|
|
46
|
+
*/
|
|
47
|
+
export const bothSearchAnswer = async (store, accountId, archived, served, limit) => {
|
|
48
|
+
const calendars = new Map((await store.calendars(accountId)).map((calendar) => [calendar.externalId, calendar]));
|
|
49
|
+
const hits = archived.hits.map((hit) => ({ ...hit }));
|
|
50
|
+
const byRef = new Map(hits.map((hit) => [hit.ref, hit]));
|
|
51
|
+
const onlyServer = [];
|
|
52
|
+
for (const hit of served.hits) {
|
|
53
|
+
const calendar = calendars.get(hit.calendar);
|
|
54
|
+
const entry = calendar ? await store.entry(calendar.id, hit.external_id) : null;
|
|
55
|
+
const held = entry?.eventId == null ? undefined : byRef.get(`event:${entry.eventId}`);
|
|
56
|
+
if (held)
|
|
57
|
+
held.found_by = [...held.found_by, "server"];
|
|
58
|
+
else
|
|
59
|
+
onlyServer.push(hit);
|
|
60
|
+
}
|
|
61
|
+
const merged = [...hits, ...onlyServer];
|
|
62
|
+
return {
|
|
63
|
+
query: archived.query,
|
|
64
|
+
by: "both",
|
|
65
|
+
...(archived.meaning_skipped === undefined ? {} : { meaning_skipped: archived.meaning_skipped }),
|
|
66
|
+
hits: merged.slice(0, limit),
|
|
67
|
+
has_more: archived.has_more || served.has_more || merged.length > limit,
|
|
68
|
+
};
|
|
69
|
+
};
|
package/dist/cli/bot/token.js
CHANGED
|
@@ -40,7 +40,7 @@ export class BotTokenStore {
|
|
|
40
40
|
return stored ? { token: stored.secret, source: stored.source } : undefined;
|
|
41
41
|
}
|
|
42
42
|
write(token) {
|
|
43
|
-
return this.#credentials.write(this.#account, token);
|
|
43
|
+
return this.#credentials.write(this.#account, token, { signIn: true });
|
|
44
44
|
}
|
|
45
45
|
readKeyring() {
|
|
46
46
|
const stored = this.readStored();
|
|
@@ -69,7 +69,7 @@ export class BotTokenStore {
|
|
|
69
69
|
return this.#secretOf(name).read(`${this.#account}:${name}`);
|
|
70
70
|
}
|
|
71
71
|
writeSecret(name, secret) {
|
|
72
|
-
return this.#secretOf(name).write(`${this.#account}:${name}`, secret);
|
|
72
|
+
return this.#secretOf(name).write(`${this.#account}:${name}`, secret, { signIn: true });
|
|
73
73
|
}
|
|
74
74
|
removeSecret(name) {
|
|
75
75
|
return this.#secretOf(name).remove(`${this.#account}:${name}`);
|
|
@@ -12,6 +12,7 @@ import { JOBS_QUERY, jobItems } from "../../services/job-query.js";
|
|
|
12
12
|
import { dateWindow } from "../../services/moment.js";
|
|
13
13
|
import { validateCatchUpBounds } from "../../services/search-catchup.js";
|
|
14
14
|
import { applyTaskRulesSinceLastPass } from "../../services/task-rules.js";
|
|
15
|
+
import { stepLines } from "../../terminal/step-lines.js";
|
|
15
16
|
import { environmentOf } from "../context.js";
|
|
16
17
|
import { destroysNothing, FETCH, LOCAL_READ, LOCAL_WRITE } from "../effects.js";
|
|
17
18
|
import { itemOf, listOf, renderAnswer, snakeKeys } from "../envelope.js";
|
|
@@ -41,7 +42,8 @@ export const syncCommand = (messenger) => {
|
|
|
41
42
|
cooperative: true,
|
|
42
43
|
})
|
|
43
44
|
.summary("fetch and save a chat's message history")
|
|
44
|
-
.description("bring a chat's history in the local store up to date, newest first; run it again to continue; --all syncs every chat"
|
|
45
|
+
.description("bring a chat's history in the local store up to date, newest first; run it again to continue; --all syncs every chat; " +
|
|
46
|
+
'with --json, stderr carries progress lines: {"event":"progress","step":"sync","chats","chats_done","fetched"}, counts only')
|
|
45
47
|
.argument("[chat]", messenger.chatArgument)
|
|
46
48
|
.option("--all", `every chat, most recently active first — what search needs; the last ${ALL_SINCE} unless --since-time or --last`)
|
|
47
49
|
.option("--limit <n>", `at most this many messages in this run, per chat with --all; ${fetching.maxPages * fetching.page} if not given`, wholeNumber)
|
|
@@ -139,6 +141,8 @@ export const syncCommand = (messenger) => {
|
|
|
139
141
|
const jobs = jobsDir(messenger.app, context.env);
|
|
140
142
|
const jobId = context.env[envName(messenger.app, "BACKFILL_JOB")];
|
|
141
143
|
const stop = stopOnSignal(this);
|
|
144
|
+
// A background job keeps its log as it was; its progress is the job file.
|
|
145
|
+
const progress = syncProgress(stepLines(context.streams.diagnostic, !jobId && context.format !== "pretty"));
|
|
142
146
|
try {
|
|
143
147
|
const options = {
|
|
144
148
|
limit,
|
|
@@ -149,10 +153,13 @@ export const syncCommand = (messenger) => {
|
|
|
149
153
|
...(last === undefined ? {} : { last }),
|
|
150
154
|
note: context.renderer.note,
|
|
151
155
|
stop: stop.signal,
|
|
152
|
-
onPage: (
|
|
156
|
+
onPage: (page) => {
|
|
153
157
|
if (jobId)
|
|
154
|
-
updateJob(jobs, jobId, { progress: { ...
|
|
158
|
+
updateJob(jobs, jobId, { progress: { ...page, at: new Date().toISOString() } });
|
|
159
|
+
if (chat !== undefined)
|
|
160
|
+
progress({ chats: null, chats_done: null, fetched: page.fetched });
|
|
155
161
|
},
|
|
162
|
+
onChat: ({ chats, done, fetched }) => progress({ chats, chats_done: done, fetched }, done === 0 || done === chats),
|
|
156
163
|
};
|
|
157
164
|
const result = await context.withServices((services) => chat === undefined ? services.archive.fetchAll(options) : services.archive.fetch(chat, options));
|
|
158
165
|
if (jobId)
|
|
@@ -197,6 +204,22 @@ export const syncCommand = (messenger) => {
|
|
|
197
204
|
}
|
|
198
205
|
});
|
|
199
206
|
};
|
|
207
|
+
/** At most this often a progress line, so a run of fast pages or of chats already current does not flood the window. */
|
|
208
|
+
const PROGRESS_EVERY_MS = 500;
|
|
209
|
+
/**
|
|
210
|
+
* `messages sync`'s progress for a window to draw a bar by: counts only, because the lines reach a desktop app and
|
|
211
|
+
* its logs — never a chat's id or title. `always` keeps the first and the last line of `--all` through the throttle.
|
|
212
|
+
*/
|
|
213
|
+
const syncProgress = (emit) => {
|
|
214
|
+
let last = Number.NEGATIVE_INFINITY;
|
|
215
|
+
return (counts, always = false) => {
|
|
216
|
+
const now = Date.now();
|
|
217
|
+
if (!always && now - last < PROGRESS_EVERY_MS)
|
|
218
|
+
return;
|
|
219
|
+
last = now;
|
|
220
|
+
emit({ event: "progress", step: "sync", ...counts });
|
|
221
|
+
};
|
|
222
|
+
};
|
|
200
223
|
/**
|
|
201
224
|
* What the sync stored, through the task rules (owner, 2026-10-14). A failure is noted, never the sync's failure:
|
|
202
225
|
* the messages are already kept, and the next sync's pass starts from the same stop point.
|
package/dist/embeddings/keys.js
CHANGED
|
@@ -33,7 +33,7 @@ export const embeddingKeys = (app, env = process.env, keyring) => {
|
|
|
33
33
|
: undefined;
|
|
34
34
|
return shared ? { key: shared, source: "env" } : undefined;
|
|
35
35
|
},
|
|
36
|
-
write: (provider, key) => credentials(provider).write(account(provider), key),
|
|
36
|
+
write: (provider, key) => credentials(provider).write(account(provider), key, { signIn: true }),
|
|
37
37
|
remove: (provider) => credentials(provider).remove(account(provider)),
|
|
38
38
|
};
|
|
39
39
|
};
|
package/dist/mail/sync.d.ts
CHANGED
|
@@ -101,5 +101,7 @@ export interface MailSyncOptions {
|
|
|
101
101
|
* is tried once more at the end of the run and, if it still fails, left for the next one. `max` defaults to no limit.
|
|
102
102
|
*/
|
|
103
103
|
budget?: RunBudget;
|
|
104
|
+
/** Pauses before a round is read again after the store was locked; tests pass one that does not wait. */
|
|
105
|
+
wait?: (ms: number) => Promise<void>;
|
|
104
106
|
}
|
|
105
107
|
export declare const syncMail: (source: MailSource, store: MessageStore, options: MailSyncOptions) => Promise<MailSyncResult>;
|
package/dist/mail/sync.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { setTimeout } from "node:timers/promises";
|
|
2
2
|
import { CliError } from "@wirecat/cli-base";
|
|
3
3
|
import { importEngine } from "../attachments/extract.js";
|
|
4
|
+
import { isBusy } from "../store/sqlite/transaction.js";
|
|
4
5
|
import { addressOf, FETCH_CHUNK, MAX_ATTACHMENT_CHARS, readAttachments } from "./attachments.js";
|
|
5
6
|
import { BULK, bulkBox } from "./bulk.js";
|
|
6
7
|
import { factsOf } from "./facts.js";
|
|
@@ -26,6 +27,8 @@ const RESUME_FROM = 5_000;
|
|
|
26
27
|
export { MAX_ATTACHMENT_CHARS } from "./attachments.js";
|
|
27
28
|
export { RETRYABLE_ATTACHMENTS } from "./retry.js";
|
|
28
29
|
const LIST_SLICE = 200;
|
|
30
|
+
const STORE_BUSY_TRIES = 5;
|
|
31
|
+
const STORE_BUSY_WAIT_MS = 2_000;
|
|
29
32
|
export const syncMail = async (source, store, options) => {
|
|
30
33
|
const { budget, index } = options;
|
|
31
34
|
if (budget === undefined || options.retryAttachments || options.account.mode === "mbox")
|
|
@@ -45,14 +48,31 @@ export const syncMail = async (source, store, options) => {
|
|
|
45
48
|
asked: 0,
|
|
46
49
|
};
|
|
47
50
|
const rounds = [];
|
|
51
|
+
let busyWaits = 0;
|
|
48
52
|
const goOn = async () => {
|
|
49
53
|
for (;;) {
|
|
50
54
|
plan.asked = 0;
|
|
51
55
|
plan.remaining = 0;
|
|
56
|
+
const triedBefore = plan.tried.size;
|
|
52
57
|
try {
|
|
53
58
|
rounds.push(await syncRound(source, store, { ...options, index: undefined, max: options.max ?? Number.POSITIVE_INFINITY }, plan));
|
|
54
59
|
}
|
|
55
60
|
catch (error) {
|
|
61
|
+
// Another process (memo's scheduled sync) held the store's write lock past its busy timeout. The slices this
|
|
62
|
+
// run committed stay; the round's messages are asked for again after a pause, while the budget has room.
|
|
63
|
+
if (isBusy(error) && busyWaits < STORE_BUSY_TRIES && !options.signal?.aborted) {
|
|
64
|
+
busyWaits++;
|
|
65
|
+
for (const key of [...plan.tried].slice(triedBefore))
|
|
66
|
+
plan.tried.delete(key);
|
|
67
|
+
const stop = budget.take();
|
|
68
|
+
if (stop === null) {
|
|
69
|
+
await (options.wait ?? setTimeout)(STORE_BUSY_WAIT_MS * busyWaits);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
plan.stopped = stop;
|
|
73
|
+
plan.remaining = Math.max(plan.remaining, 1);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
56
76
|
// A request cut at the deadline, or failed after the budget was spent, is the run's end, not its failure: what
|
|
57
77
|
// earlier rounds saved is kept and the next run goes on. While the budget has room the error is real.
|
|
58
78
|
const transient = error instanceof CliError && (error.code === "timeout" || error.code === "network_error");
|
package/dist/meetings/reads.d.ts
CHANGED
package/dist/oauth/keeper.js
CHANGED
|
@@ -137,13 +137,13 @@ export const tokenKeeper = (options) => {
|
|
|
137
137
|
const extra = (await accept?.(received)) ?? undefined;
|
|
138
138
|
await locked(async () => {
|
|
139
139
|
forget();
|
|
140
|
-
storage.write({ tokens: received, client, ...(extra ? { extra } : {}) });
|
|
140
|
+
storage.write({ tokens: received, client, ...(extra ? { extra } : {}) }, { signIn: true });
|
|
141
141
|
});
|
|
142
142
|
return { scopes: received.scopes };
|
|
143
143
|
},
|
|
144
144
|
save: (saved, extra) => locked(async () => {
|
|
145
145
|
forget();
|
|
146
|
-
storage.write({ tokens: saved, client: clientOf(storage.read()), ...(extra ? { extra } : {}) });
|
|
146
|
+
storage.write({ tokens: saved, client: clientOf(storage.read()), ...(extra ? { extra } : {}) }, { signIn: true });
|
|
147
147
|
}),
|
|
148
148
|
signOut: ({ revoke = false } = {}) => locked(async () => {
|
|
149
149
|
forget();
|
package/dist/oauth/storage.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CredentialSource, type CredentialsOptions } from "@wirecat/cli-base";
|
|
1
|
+
import { type CredentialSource, type CredentialsOptions, type WriteOptions } from "@wirecat/cli-base";
|
|
2
2
|
import type { OAuthClient, TokenSet } from "./provider.js";
|
|
3
3
|
export interface StoredTokens {
|
|
4
4
|
tokens: TokenSet;
|
|
@@ -29,7 +29,8 @@ export interface TokenStorage {
|
|
|
29
29
|
/** Throws when the entry cannot be saved, so a sign-in fails before the browser, not after consent. */
|
|
30
30
|
probe(): void;
|
|
31
31
|
read(): StoredTokens | undefined;
|
|
32
|
-
|
|
32
|
+
/** `signIn` moves an entry the file holds back to the keyring when it takes it (WC-SECR-4); a refresh leaves it. */
|
|
33
|
+
write(input: Omit<EncodeInput, "previous">, options?: WriteOptions): void;
|
|
33
34
|
remove(): CredentialSource[];
|
|
34
35
|
}
|
|
35
36
|
export declare const oauthAccount: (providerId: string, profile: string) => string;
|
package/dist/oauth/storage.js
CHANGED
|
@@ -162,9 +162,9 @@ export const credentialStorage = (options) => {
|
|
|
162
162
|
throw keyringFailure(error);
|
|
163
163
|
}
|
|
164
164
|
};
|
|
165
|
-
const save = (value) => {
|
|
165
|
+
const save = (value, writeOptions) => {
|
|
166
166
|
try {
|
|
167
|
-
credentials.write(account, value);
|
|
167
|
+
credentials.write(account, value, writeOptions);
|
|
168
168
|
}
|
|
169
169
|
catch (error) {
|
|
170
170
|
throw keyringFailure(error, value);
|
|
@@ -195,7 +195,7 @@ export const credentialStorage = (options) => {
|
|
|
195
195
|
const value = current();
|
|
196
196
|
return value === undefined ? undefined : codec.decode(value);
|
|
197
197
|
},
|
|
198
|
-
write: (input) => save(codec.encode({ ...input, previous: raw(account) })),
|
|
198
|
+
write: (input, writeOptions) => save(codec.encode({ ...input, previous: raw(account) }), writeOptions),
|
|
199
199
|
remove: () => {
|
|
200
200
|
try {
|
|
201
201
|
return credentials.remove(account);
|
|
@@ -39,6 +39,12 @@ export interface FetchOptions {
|
|
|
39
39
|
chatId: string;
|
|
40
40
|
oldest: number;
|
|
41
41
|
}) => void;
|
|
42
|
+
/** `fetchAll` only: once the chat list is read, then after each chat, current or not; counts across every chat. */
|
|
43
|
+
onChat?: (progress: {
|
|
44
|
+
chats: number;
|
|
45
|
+
done: number;
|
|
46
|
+
fetched: number;
|
|
47
|
+
}) => void;
|
|
42
48
|
}
|
|
43
49
|
/** A type, not an interface: a job keeps it as a plain record. */
|
|
44
50
|
export type Fetched = {
|
package/dist/services/archive.js
CHANGED
|
@@ -148,6 +148,8 @@ export const archiveService = (deps) => {
|
|
|
148
148
|
...options,
|
|
149
149
|
catchUp: catchingUp ? (options.catchUp ?? {}) : false,
|
|
150
150
|
});
|
|
151
|
+
const finished = () => options.onChat?.({ chats: chats.length, done: answer.items.length, fetched: answer.fetched });
|
|
152
|
+
finished();
|
|
151
153
|
for (const chat of chats) {
|
|
152
154
|
if (options.stop.aborted || batch.stopped) {
|
|
153
155
|
answer.stopped = true;
|
|
@@ -155,6 +157,7 @@ export const archiveService = (deps) => {
|
|
|
155
157
|
}
|
|
156
158
|
if (await upToDate(chat)) {
|
|
157
159
|
answer.items.push({ chat: chat.id, title: chat.title, fetched: 0, complete: true, current: true });
|
|
160
|
+
finished();
|
|
158
161
|
continue;
|
|
159
162
|
}
|
|
160
163
|
try {
|
|
@@ -183,6 +186,7 @@ export const archiveService = (deps) => {
|
|
|
183
186
|
const issue = batch.fail(chat.id, "history", error);
|
|
184
187
|
answer.items.push({ chat: chat.id, title: chat.title, fetched: 0, complete: false, error: issue.code, issue });
|
|
185
188
|
}
|
|
189
|
+
finished();
|
|
186
190
|
}
|
|
187
191
|
if (batch.failed > 0)
|
|
188
192
|
answer.batch = batch.result();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CacheDatabase } from "../driver.js";
|
|
2
|
+
export declare const isBusy: (error: unknown) => boolean;
|
|
2
3
|
/**
|
|
3
4
|
* One write transaction. The outermost takes the write lock at once (`BEGIN IMMEDIATE`): a deferred one that
|
|
4
5
|
* reads first fails with SQLITE_BUSY when another process writes in between, and tg, max, memo and zm share
|
|
@@ -3,7 +3,7 @@ import { drainMentions } from "./mention-drain.js";
|
|
|
3
3
|
import { drainStems, stemmerCache } from "./stems.js";
|
|
4
4
|
const SQLITE_BUSY = 5;
|
|
5
5
|
const pause = new Int32Array(new SharedArrayBuffer(4));
|
|
6
|
-
const isBusy = (error) => {
|
|
6
|
+
export const isBusy = (error) => {
|
|
7
7
|
const { errcode, errno } = (error ?? {});
|
|
8
8
|
const code = errcode ?? errno;
|
|
9
9
|
return typeof code === "number" && (code & 0xff) === SQLITE_BUSY;
|
|
@@ -2,6 +2,7 @@ import { StringDecoder } from "node:string_decoder";
|
|
|
2
2
|
import { CliError } from "@wirecat/cli-base";
|
|
3
3
|
import { Option } from "commander";
|
|
4
4
|
import { MAX_SECRET_INPUT } from "../cli/input-policy.js";
|
|
5
|
+
import { stepLines } from "./step-lines.js";
|
|
5
6
|
/**
|
|
6
7
|
* Opt-in, because a read nobody answers hangs: an agent running `session start` in a shell must get the refusal it
|
|
7
8
|
* gets today, and only a caller that holds stdin open and answers each `needs` line (WireCat Desktop) passes it.
|
|
@@ -10,10 +11,7 @@ export const answerStdinOption = () => new Option("--answer-stdin", "with --json
|
|
|
10
11
|
export const signInSteps = ({ diagnostic, json, answers, input = process.stdin }) => {
|
|
11
12
|
if (answers && !json)
|
|
12
13
|
throw new CliError("validation_error", "--answer-stdin needs --json");
|
|
13
|
-
const emit = (
|
|
14
|
-
if (json)
|
|
15
|
-
diagnostic(JSON.stringify(event));
|
|
16
|
-
};
|
|
14
|
+
const emit = stepLines(diagnostic, json);
|
|
17
15
|
const next = answers ? lineReader(input) : undefined;
|
|
18
16
|
return {
|
|
19
17
|
json,
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Machine-readable step lines: one JSON object per line on the diagnostic stream, only in `--json` / `--jsonl`, so
|
|
3
|
+
* stdout keeps its single answer and a person at a terminal sees nothing new. Sign-in and `messages sync` share it.
|
|
4
|
+
*/
|
|
5
|
+
export declare const stepLines: (diagnostic: (line: string) => void, json: boolean) => (event: Record<string, unknown> & {
|
|
6
|
+
event: string;
|
|
7
|
+
}) => void;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Machine-readable step lines: one JSON object per line on the diagnostic stream, only in `--json` / `--jsonl`, so
|
|
3
|
+
* stdout keeps its single answer and a person at a terminal sees nothing new. Sign-in and `messages sync` share it.
|
|
4
|
+
*/
|
|
5
|
+
export const stepLines = (diagnostic, json) => (event) => {
|
|
6
|
+
if (json)
|
|
7
|
+
diagnostic(JSON.stringify(event));
|
|
8
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.508.0",
|
|
4
4
|
"description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Viacheslav Ptsarev",
|
|
@@ -247,7 +247,7 @@
|
|
|
247
247
|
"@types/node": "^22.20.5",
|
|
248
248
|
"@types/pngjs": "6.0.5",
|
|
249
249
|
"@vitest/coverage-v8": "^5.0.5",
|
|
250
|
-
"@wirecat/cli-base": "0.
|
|
250
|
+
"@wirecat/cli-base": "0.68.0",
|
|
251
251
|
"@wirecat/cli-core-legacy-tags": "npm:@wirecat/cli-core@0.414.0",
|
|
252
252
|
"commander": "15.0.0",
|
|
253
253
|
"cspell": "10.4.0",
|
|
@@ -267,7 +267,7 @@
|
|
|
267
267
|
},
|
|
268
268
|
"peerDependencies": {
|
|
269
269
|
"@napi-rs/canvas": ">=0.1.69 <2",
|
|
270
|
-
"@wirecat/cli-base": ">=0.
|
|
270
|
+
"@wirecat/cli-base": ">=0.68.0 <0.69.0",
|
|
271
271
|
"commander": ">=15"
|
|
272
272
|
},
|
|
273
273
|
"bin": {
|