@wirecat/cli-core 0.516.0 → 0.517.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 +15 -0
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/messenger/projects-command.js +4 -3
- package/dist/cli/messenger/search-answers.d.ts +5 -0
- package/dist/cli/messenger/search-answers.js +14 -1
- package/dist/cli/project-evidence.d.ts +96 -0
- package/dist/cli/project-evidence.js +14 -5
- package/dist/services/archive.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,21 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.517.0 — 01.11.2026
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`withSyncCommands(found, sync)`** gives each linked chat of a stored project gathering its `syncCommand`: the
|
|
15
|
+
line the text names to sync a chat held in part, or null. tg's and max's `projects evidence --json` carry it as
|
|
16
|
+
`linked_chats[].sync_command`, so an agent reading JSON gets the command the text shows.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **A chat that reaches its first message is no longer "held from <date> only"** in `missing`, `next` and the
|
|
21
|
+
`projects evidence` text: it is "held from its first message, behind since <date>", or "with gaps".
|
|
22
|
+
- **`messages sync` progress says how far back it got as a date** ("back to 2026-09-27"), not as a message key, which
|
|
23
|
+
for MAX was a number of milliseconds.
|
|
24
|
+
|
|
10
25
|
## 0.516.0 — 01.11.2026
|
|
11
26
|
|
|
12
27
|
### Fixed
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -109,7 +109,7 @@ export { window, withPaging } from "./paging.js";
|
|
|
109
109
|
export { type CommandPermissions, commandPathKey, commandPermissions, knownBeside, knownPermissionKeys, type PermissionKeyOf, type PermissionKeySource, refuseUnknownPermissionKeys, unknownPermissionIssues, unknownPermissionKeys, } from "./permission-keys.js";
|
|
110
110
|
export { asFirstWord, commandWords, liftProfile, profileFree, refuseCommandName, rootOf, takesProfile, usableProfileName, } from "./profile.js";
|
|
111
111
|
export { createProgram, type ProgramDefinition, type ProgramOptions, type RunOptions, run } from "./program.js";
|
|
112
|
-
export { type ProjectTextHints, projectAnswerParts, projectCutoffTime, projectEvidenceText, projectWindowOf, } from "./project-evidence.js";
|
|
112
|
+
export { type ProjectTextHints, projectAnswerParts, projectCutoffTime, projectEvidenceText, projectWindowOf, withSyncCommands, } from "./project-evidence.js";
|
|
113
113
|
export { fieldsOf, projectFields } from "./result-fields.js";
|
|
114
114
|
export { type Config, type Configuration, count, first, flag, fromFile, type GlobalFlags, NONE_NAMED, namedProfile, type ProfileKind, plain, type ResolveOptions, type Settings, type SettingsExtension, type SettingsExtensionContext, type Source, settingsFor, } from "./settings.js";
|
|
115
115
|
export { type PermissionAnswer, readSetupAnswers, type SetupAnswers, setupPermissionCeiling } from "./setup-answers.js";
|
package/dist/cli/index.js
CHANGED
|
@@ -99,7 +99,7 @@ export { window, withPaging } from "./paging.js";
|
|
|
99
99
|
export { commandPathKey, commandPermissions, knownBeside, knownPermissionKeys, refuseUnknownPermissionKeys, unknownPermissionIssues, unknownPermissionKeys, } from "./permission-keys.js";
|
|
100
100
|
export { asFirstWord, commandWords, liftProfile, profileFree, refuseCommandName, rootOf, takesProfile, usableProfileName, } from "./profile.js";
|
|
101
101
|
export { createProgram, run } from "./program.js";
|
|
102
|
-
export { projectAnswerParts, projectCutoffTime, projectEvidenceText, projectWindowOf, } from "./project-evidence.js";
|
|
102
|
+
export { projectAnswerParts, projectCutoffTime, projectEvidenceText, projectWindowOf, withSyncCommands, } from "./project-evidence.js";
|
|
103
103
|
export { fieldsOf, projectFields } from "./result-fields.js";
|
|
104
104
|
export { count, first, flag, fromFile, NONE_NAMED, namedProfile, plain, settingsFor, } from "./settings.js";
|
|
105
105
|
export { readSetupAnswers, setupPermissionCeiling } from "./setup-answers.js";
|
|
@@ -7,7 +7,7 @@ import { gatherStoredProject } from "../../services/stored-project-gathering.js"
|
|
|
7
7
|
import { FETCH } from "../effects.js";
|
|
8
8
|
import { itemOf, renderAnswer, snakeKeys } from "../envelope.js";
|
|
9
9
|
import { positiveCount } from "../paging.js";
|
|
10
|
-
import { projectAnswerParts, projectCutoffTime, projectEvidenceText, projectWindowOf } from "../project-evidence.js";
|
|
10
|
+
import { projectAnswerParts, projectCutoffTime, projectEvidenceText, projectWindowOf, withSyncCommands, } from "../project-evidence.js";
|
|
11
11
|
import { lineOf } from "../steps.js";
|
|
12
12
|
import { messengerContext } from "./context.js";
|
|
13
13
|
const collect = (value, previous) => [...previous, value];
|
|
@@ -120,12 +120,13 @@ export const projectsCommand = (messenger) => {
|
|
|
120
120
|
: undefined;
|
|
121
121
|
const found = await context.withStore((store, account) => gather(store, account, fetcher));
|
|
122
122
|
const parts = projectAnswerParts(options.sinceTime, found);
|
|
123
|
-
|
|
123
|
+
const hints = {
|
|
124
124
|
sync: (one, since) => one.provider === messenger.provider
|
|
125
125
|
? lineOf(this, `projects evidence ${quote(project)} --since-time ${since} --sync-first`)
|
|
126
126
|
: null,
|
|
127
127
|
showDocument: "the notes app that imported it",
|
|
128
|
-
}
|
|
128
|
+
};
|
|
129
|
+
renderAnswer(this, context, itemOf(snakeKeys(withSyncCommands(found, hints.sync)), parts), () => projectEvidenceText(found, hints));
|
|
129
130
|
}));
|
|
130
131
|
return projects;
|
|
131
132
|
};
|
|
@@ -18,6 +18,11 @@ export type HitItem = InferOutput<(typeof ENTITIES)["hit"]>;
|
|
|
18
18
|
/** The page a `--page` asks for, as the offset the search services take. */
|
|
19
19
|
export declare const offsetOf: (page: number | undefined, limit: number) => number | undefined;
|
|
20
20
|
export declare const messageHitItem: (hit: FoundMessage, query?: string) => MessageHitItem;
|
|
21
|
+
/**
|
|
22
|
+
* What the store holds of a chat that is not complete, in words. A chat that reaches its first message is only
|
|
23
|
+
* behind or has gaps: saying "held from <date> only" would claim older history is missing.
|
|
24
|
+
*/
|
|
25
|
+
export declare const heldPhrase: ({ completeness, held }: Pick<ChatCoverage, "completeness" | "held">) => string;
|
|
21
26
|
/**
|
|
22
27
|
* What the archive lacked for this answer, and the step that would fill it. From a search's coverage, or from the
|
|
23
28
|
* chats an answer is about (`since` is the start of the window it was asked for).
|
|
@@ -96,6 +96,19 @@ const metaOf = (accounts, skipped = [], heldInPart = false) => ({
|
|
|
96
96
|
: {}),
|
|
97
97
|
});
|
|
98
98
|
const day = (at) => at?.slice(0, 10) ?? "no date";
|
|
99
|
+
/**
|
|
100
|
+
* What the store holds of a chat that is not complete, in words. A chat that reaches its first message is only
|
|
101
|
+
* behind or has gaps: saying "held from <date> only" would claim older history is missing.
|
|
102
|
+
*/
|
|
103
|
+
export const heldPhrase = ({ completeness, held }) => {
|
|
104
|
+
if (completeness.state === "unknown" || !held.oldestAt)
|
|
105
|
+
return "never fetched";
|
|
106
|
+
if (!completeness.reachesStart)
|
|
107
|
+
return `held from ${day(held.oldestAt)} only`;
|
|
108
|
+
if (completeness.gaps)
|
|
109
|
+
return "held from its first message, with gaps";
|
|
110
|
+
return `held from its first message, behind since ${day(held.newestAt)}`;
|
|
111
|
+
};
|
|
99
112
|
/**
|
|
100
113
|
* The steps for chats the store holds only in part. Bounded first, when the answer's window is known, then the whole
|
|
101
114
|
* chat; both name the first chat only, because a download is the owner's call. Counts and dates only, never text.
|
|
@@ -105,7 +118,7 @@ const chatGaps = (rows, since) => {
|
|
|
105
118
|
const [first] = open;
|
|
106
119
|
if (!first)
|
|
107
120
|
return { next: [], missing: [] };
|
|
108
|
-
const held =
|
|
121
|
+
const held = heldPhrase(first);
|
|
109
122
|
const steps = (args, why) => ({
|
|
110
123
|
...nextStep("messages.sync", { args: { chat: first.chatId, ...args } }),
|
|
111
124
|
why,
|
|
@@ -41,6 +41,102 @@ export declare const projectAnswerParts: (sinceTime: string, found: StoredProjec
|
|
|
41
41
|
complete: boolean;
|
|
42
42
|
};
|
|
43
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* The gathering with each linked chat's `syncCommand`: the line that syncs a chat held in part back to the window
|
|
46
|
+
* start, or null when it needs none or no command here can. JSON readers get what the text says per chat.
|
|
47
|
+
*/
|
|
48
|
+
export declare const withSyncCommands: (found: StoredProjectGathering, sync: ProjectTextHints["sync"]) => {
|
|
49
|
+
schemaVersion: 1;
|
|
50
|
+
runKey: string;
|
|
51
|
+
fingerprint: string;
|
|
52
|
+
resumed: boolean;
|
|
53
|
+
project: {
|
|
54
|
+
name: string;
|
|
55
|
+
key: string | null;
|
|
56
|
+
background: string | null;
|
|
57
|
+
aliases: string[];
|
|
58
|
+
};
|
|
59
|
+
window: {
|
|
60
|
+
after: number;
|
|
61
|
+
before: number;
|
|
62
|
+
asOf: number;
|
|
63
|
+
lookbackDays: number | null;
|
|
64
|
+
basis: "explicit" | "lookback";
|
|
65
|
+
timeBasis: "per-item-time-kind";
|
|
66
|
+
};
|
|
67
|
+
scope: {
|
|
68
|
+
origin: "all-connected" | "explicit";
|
|
69
|
+
sources: import("../context/evidence.js").ContextSource[];
|
|
70
|
+
};
|
|
71
|
+
lanes: import("../services/project-gathering-request.js").ProjectLane[];
|
|
72
|
+
fetch: import("../services/project-gathering-request.js").ProjectChatFetched[];
|
|
73
|
+
index: import("../services/project-gathering.js").ProjectIndexCatchUp;
|
|
74
|
+
units: {
|
|
75
|
+
lane: string;
|
|
76
|
+
source: number;
|
|
77
|
+
after: number;
|
|
78
|
+
before: number;
|
|
79
|
+
state: import("../services/project-gathering-request.js").ProjectUnitState;
|
|
80
|
+
pages: number;
|
|
81
|
+
issue: string | null;
|
|
82
|
+
}[];
|
|
83
|
+
items: import("../services/project-gathering.js").ProjectEvidenceItem[];
|
|
84
|
+
people: import("../services/project-gathering.js").ProjectEvidencePerson[];
|
|
85
|
+
peopleOmitted: number;
|
|
86
|
+
oversized: {
|
|
87
|
+
ref: string;
|
|
88
|
+
lanes: string[];
|
|
89
|
+
source: number;
|
|
90
|
+
bytes: number;
|
|
91
|
+
}[];
|
|
92
|
+
matchedAgain: {
|
|
93
|
+
ref: string;
|
|
94
|
+
lanes: string[];
|
|
95
|
+
}[];
|
|
96
|
+
usage: {
|
|
97
|
+
reads: number;
|
|
98
|
+
readBytes: number;
|
|
99
|
+
aroundBytes: number;
|
|
100
|
+
items: number;
|
|
101
|
+
elapsedMs: number;
|
|
102
|
+
};
|
|
103
|
+
totals: {
|
|
104
|
+
reads: number;
|
|
105
|
+
readBytes: number;
|
|
106
|
+
items: number;
|
|
107
|
+
invocations: number;
|
|
108
|
+
};
|
|
109
|
+
stopped: import("../services/project-gathering.js").ProjectGatheringStop;
|
|
110
|
+
complete: boolean;
|
|
111
|
+
completeness: "complete" | "partial" | "unknown";
|
|
112
|
+
checkpoint: string | null;
|
|
113
|
+
projectRef: string | null;
|
|
114
|
+
unreadSources: {
|
|
115
|
+
source: string;
|
|
116
|
+
kind: string;
|
|
117
|
+
reason?: "out-of-scope" | "linked-chat-limit" | "member-limit" | "no-identity" | "unknown-provider";
|
|
118
|
+
}[];
|
|
119
|
+
mentions: import("../services/project-gathering-mentions.js").EvidenceMentions;
|
|
120
|
+
linkedChats: {
|
|
121
|
+
provider: string;
|
|
122
|
+
account: string;
|
|
123
|
+
chat: string;
|
|
124
|
+
ref: string;
|
|
125
|
+
source: number | null;
|
|
126
|
+
lane: string | null;
|
|
127
|
+
state: "in-scope" | "out-of-scope" | "pending" | "unavailable";
|
|
128
|
+
title: string | null;
|
|
129
|
+
completeness: import("../store/exports.js").ChatCompleteness | null;
|
|
130
|
+
held: {
|
|
131
|
+
messages: number;
|
|
132
|
+
oldestAt: string | null;
|
|
133
|
+
newestAt: string | null;
|
|
134
|
+
} | null;
|
|
135
|
+
items?: number | undefined;
|
|
136
|
+
capped?: boolean | undefined;
|
|
137
|
+
syncCommand: string | null;
|
|
138
|
+
}[];
|
|
139
|
+
};
|
|
44
140
|
export interface ProjectTextHints {
|
|
45
141
|
/** The command that syncs one linked chat back to `since` (`YYYY-MM-DD`), or null when no command here can. */
|
|
46
142
|
sync: (chat: StoredProjectGathering["linkedChats"][number], since: string) => string | null;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import * as v from "valibot";
|
|
3
3
|
import { dateWindow, momentOf } from "../services/moment.js";
|
|
4
|
-
import { coverageGaps } from "./messenger/search-answers.js";
|
|
4
|
+
import { coverageGaps, heldPhrase } from "./messenger/search-answers.js";
|
|
5
5
|
/** Days back (`90d`) or a start date (`2026-09-01`); either way the run freezes its end, so a resume keeps it. */
|
|
6
6
|
export const projectWindowOf = (value) => {
|
|
7
7
|
const days = /^([1-9]\d{0,2})d$/.exec(value)?.[1];
|
|
@@ -47,6 +47,17 @@ export const projectAnswerParts = (sinceTime, found) => {
|
|
|
47
47
|
},
|
|
48
48
|
};
|
|
49
49
|
};
|
|
50
|
+
/**
|
|
51
|
+
* The gathering with each linked chat's `syncCommand`: the line that syncs a chat held in part back to the window
|
|
52
|
+
* start, or null when it needs none or no command here can. JSON readers get what the text says per chat.
|
|
53
|
+
*/
|
|
54
|
+
export const withSyncCommands = (found, sync) => ({
|
|
55
|
+
...found,
|
|
56
|
+
linkedChats: found.linkedChats.map((one) => ({
|
|
57
|
+
...one,
|
|
58
|
+
syncCommand: one.completeness === null || one.completeness.state === "complete" ? null : sync(one, day(found.window.after)),
|
|
59
|
+
})),
|
|
60
|
+
});
|
|
50
61
|
const LABELS = {
|
|
51
62
|
"person-only": " [person only]",
|
|
52
63
|
sender: " [sent by contact]",
|
|
@@ -120,13 +131,11 @@ export const projectEvidenceText = (found, hints) => {
|
|
|
120
131
|
for (const one of found.fetch)
|
|
121
132
|
lines.push(`${one.chat}: ${FETCH_STATES[one.state] ?? one.state} before reading` +
|
|
122
133
|
`${one.reason && one.state !== "skipped" ? ` (${one.reason})` : ""}`);
|
|
123
|
-
const held = (one) => one.completeness?.state === "unknown" || !one.held?.oldestAt
|
|
124
|
-
? "never fetched"
|
|
125
|
-
: `held from ${one.held.oldestAt.slice(0, 10)} only`;
|
|
126
134
|
for (const one of found.linkedChats)
|
|
127
135
|
if (one.completeness !== null && one.completeness.state !== "complete") {
|
|
128
136
|
const sync = hints.sync(one, day(found.window.after));
|
|
129
|
-
|
|
137
|
+
const held = one.held === null ? "never fetched" : heldPhrase({ completeness: one.completeness, held: one.held });
|
|
138
|
+
lines.push(`${one.title ?? one.ref}: ${held}${one.state === "pending" ? ", not read this run" : ""}` +
|
|
130
139
|
(sync ? ` — sync it: ${sync}` : ""));
|
|
131
140
|
}
|
|
132
141
|
const capped = found.linkedChats.filter((one) => one.capped);
|
package/dist/services/archive.js
CHANGED
|
@@ -308,7 +308,7 @@ export const fetchInto = async ({ history, store, account, fetching, limit, page
|
|
|
308
308
|
before = byTime
|
|
309
309
|
? new Date(boundary + (fresh > 0 && !(window && fetching.beforeInclusive) ? 1 : 0)).toISOString()
|
|
310
310
|
: String(boundary);
|
|
311
|
-
note(`${fetched} messages so far, back to ${
|
|
311
|
+
note(`${fetched} messages so far, back to ${fromAt.slice(0, 10)}`);
|
|
312
312
|
const wait = fetching.jitter ? pauseMs * (1 + Math.random()) : pauseMs;
|
|
313
313
|
await sleep(wait, undefined, { signal: stop }).catch(() => { });
|
|
314
314
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.517.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",
|