@wirecat/cli-core 0.527.0 → 0.528.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 +12 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1 -0
- package/dist/cli/messenger/doctor-command.js +7 -1
- package/dist/runs/provider-log.d.ts +22 -0
- package/dist/runs/provider-log.js +55 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.528.0 — 01.11.2026
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Every profile keeps its messenger library's warnings, and `doctor` counts the last day's.** A message the
|
|
15
|
+
library could not decrypt used to leave no trace unless the CLI's own log variable was set. `providerLogPathFor(app,
|
|
16
|
+
profile, env)` names `<state>/provider-log/<profile>.jsonl`; `openProviderLog(path)` makes it private (mode 600)
|
|
17
|
+
and keeps it under 1 MB with one older file, so an engine in another process can append to the path it is given;
|
|
18
|
+
`writeProviderWarning(path, kind, message)` appends from Node. A line is `{"time","level","kind"?,"message"}`, the
|
|
19
|
+
shape zerolog writes; `kind` is the adapter's tag. The adapter masks phone numbers, ids of people and text before
|
|
20
|
+
writing. `doctor` answers `provider_log: { path, last_day: { total, kinds } }` and checks the file is private.
|
|
21
|
+
|
|
10
22
|
## 0.527.0 — 01.11.2026
|
|
11
23
|
|
|
12
24
|
### Added
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export { NO_RULES, NO_TASKS, NOT_ALLOWED, ONLY_TASK, type Replied, type Replier,
|
|
|
23
23
|
export { repliesStatePathFor } from "../replies/state.js";
|
|
24
24
|
export { type CacheEvent, type DiagnosticEvent, type EventSink, providerErrorKey, type RequestEvent, renderEvent, type WarningEvent, } from "../runs/events.js";
|
|
25
25
|
export { CORE_LOG_EVENTS } from "../runs/log-events.js";
|
|
26
|
+
export { openProviderLog, PROVIDER_LOG_BYTES, type ProviderWarnings, providerLogPathFor, providerWarnings, writeProviderWarning, } from "../runs/provider-log.js";
|
|
26
27
|
export { crashOf, type Recording, type RecordingOptions, recorded, startRecording, wasSettled, } from "../runs/recording.js";
|
|
27
28
|
export { findRun, KEEP_RUNS_FOR_DAYS, listRuns, pruneRuns, type RunContext, type RunMetadata, readEvents, runsDirFor, runtime, startRun, } from "../runs/run.js";
|
|
28
29
|
export { runContextOf, secretInput } from "../runs/run-context.js";
|
package/dist/cli/index.js
CHANGED
|
@@ -19,6 +19,7 @@ export { NO_RULES, NO_TASKS, NOT_ALLOWED, ONLY_TASK, replyTo } from "../replies/
|
|
|
19
19
|
export { repliesStatePathFor } from "../replies/state.js";
|
|
20
20
|
export { providerErrorKey, renderEvent, } from "../runs/events.js";
|
|
21
21
|
export { CORE_LOG_EVENTS } from "../runs/log-events.js";
|
|
22
|
+
export { openProviderLog, PROVIDER_LOG_BYTES, providerLogPathFor, providerWarnings, writeProviderWarning, } from "../runs/provider-log.js";
|
|
22
23
|
export { crashOf, recorded, startRecording, wasSettled, } from "../runs/recording.js";
|
|
23
24
|
export { findRun, KEEP_RUNS_FOR_DAYS, listRuns, pruneRuns, readEvents, runsDirFor, runtime, startRun, } from "../runs/run.js";
|
|
24
25
|
export { runContextOf, secretInput } from "../runs/run-context.js";
|
|
@@ -5,6 +5,7 @@ import { annotate } from "@wirecat/cli-base/commands";
|
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
import { engineStates } from "../../attachments/extract.js";
|
|
7
7
|
import { providerErrorKey } from "../../runs/events.js";
|
|
8
|
+
import { providerLogPathFor, providerWarnings } from "../../runs/provider-log.js";
|
|
8
9
|
import { listRuns, runsDirFor, runtime } from "../../runs/run.js";
|
|
9
10
|
import { FloodMemory, floodPathFor } from "../../sends/flood.js";
|
|
10
11
|
import { SendJournal, sendsPathFor } from "../../sends/journal.js";
|
|
@@ -20,6 +21,7 @@ import { recalledAccount } from "./accounts.js";
|
|
|
20
21
|
import { messengerContext } from "./context.js";
|
|
21
22
|
import { privateFiles, withSqliteSidecars } from "./private-files.js";
|
|
22
23
|
import { buildReport, reportFileName } from "./report.js";
|
|
24
|
+
import { DAY_MS } from "./serve-members.js";
|
|
23
25
|
import { storeSummary } from "./store-maintenance-command.js";
|
|
24
26
|
/**
|
|
25
27
|
* The state this installation is in, read from disk and **never from the messenger unless
|
|
@@ -121,6 +123,7 @@ const rememberedOf = (report) => report.account?.remembered ?? undefined;
|
|
|
121
123
|
* What `doctor` answers, as data. A configuration that will not load is a field, not an exception:
|
|
122
124
|
* then there is no renderer from the settings, and the caller renders with the defaults.
|
|
123
125
|
*/
|
|
126
|
+
const providerLogState = ({ path, total, kinds }) => ({ path, last_day: { total, kinds } });
|
|
124
127
|
const diagnose = async (command, messenger) => {
|
|
125
128
|
const { app, provider } = messenger;
|
|
126
129
|
const env = environmentOf(command).env ?? process.env;
|
|
@@ -183,8 +186,9 @@ const diagnose = async (command, messenger) => {
|
|
|
183
186
|
const store = storePath(env);
|
|
184
187
|
const sends = sendsPathFor(app, profile, env);
|
|
185
188
|
const flood = floodPathFor(app, profile, env);
|
|
189
|
+
const providerLog = providerLogPathFor(app, profile, env);
|
|
186
190
|
const files = privateFiles({
|
|
187
|
-
files: [...own.files, ...withSqliteSidecars(store), sends, flood],
|
|
191
|
+
files: [...own.files, ...withSqliteSidecars(store), sends, flood, providerLog],
|
|
188
192
|
// A folder the owner chose with MESSAGING_STORE may rightly be shared, like the home folder itself.
|
|
189
193
|
dirs: [
|
|
190
194
|
...own.dirs,
|
|
@@ -192,6 +196,7 @@ const diagnose = async (command, messenger) => {
|
|
|
192
196
|
dirname(sends),
|
|
193
197
|
dirname(flood),
|
|
194
198
|
runsDirFor(app, env),
|
|
199
|
+
dirname(providerLog),
|
|
195
200
|
],
|
|
196
201
|
});
|
|
197
202
|
return {
|
|
@@ -209,6 +214,7 @@ const diagnose = async (command, messenger) => {
|
|
|
209
214
|
readers: engineStates(),
|
|
210
215
|
sends: sendsState(new SendJournal(sendsPathFor(app, profile, env))),
|
|
211
216
|
flood: floodState(new FloodMemory(flood)),
|
|
217
|
+
provider_log: providerLogState(providerWarnings(providerLog, new Date(Date.now() - DAY_MS))),
|
|
212
218
|
runs: { directory: runsDirFor(app, env), kept: listRuns(runsDirFor(app, env)).length },
|
|
213
219
|
// The messenger's own block keeps the keys it gives: some of them are file paths.
|
|
214
220
|
files: filesReport(files),
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AppIdentity } from "../ports/app.js";
|
|
2
|
+
/**
|
|
3
|
+
* The messenger library's warnings for one profile, always kept: a message it failed to decrypt is lost without a
|
|
4
|
+
* trace otherwise. One JSON object per line, `{"time","level","kind"?,"message"}` — the shape zerolog writes, so an
|
|
5
|
+
* engine in another process appends to the file directly. **No phone number, id of a person or message text**: the
|
|
6
|
+
* adapter or its engine masks before writing. `kind` is the adapter's tag (`decrypt`), counted by `doctor`.
|
|
7
|
+
*/
|
|
8
|
+
export declare const providerLogPathFor: (app: AppIdentity, profile: string, env?: NodeJS.ProcessEnv) => string;
|
|
9
|
+
export declare const PROVIDER_LOG_BYTES: number;
|
|
10
|
+
/**
|
|
11
|
+
* The file, private and under its cap, ready for an engine to append to. The cap is kept here rather than by the
|
|
12
|
+
* writer, because an engine in another language appends without knowing it: one older file is kept.
|
|
13
|
+
*/
|
|
14
|
+
export declare const openProviderLog: (path: string, maxBytes?: number) => string;
|
|
15
|
+
export declare const writeProviderWarning: (path: string, kind: string, message: string, now?: Date) => void;
|
|
16
|
+
export interface ProviderWarnings {
|
|
17
|
+
path: string;
|
|
18
|
+
total: number;
|
|
19
|
+
kinds: Record<string, number>;
|
|
20
|
+
}
|
|
21
|
+
/** Warnings since `since`, by kind; a line without one counts as `other`, a line that is not JSON is skipped. */
|
|
22
|
+
export declare const providerWarnings: (path: string, since: Date) => ProviderWarnings;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { appendFileSync, chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, statSync, } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { resolvePaths } from "@wirecat/cli-base";
|
|
4
|
+
/**
|
|
5
|
+
* The messenger library's warnings for one profile, always kept: a message it failed to decrypt is lost without a
|
|
6
|
+
* trace otherwise. One JSON object per line, `{"time","level","kind"?,"message"}` — the shape zerolog writes, so an
|
|
7
|
+
* engine in another process appends to the file directly. **No phone number, id of a person or message text**: the
|
|
8
|
+
* adapter or its engine masks before writing. `kind` is the adapter's tag (`decrypt`), counted by `doctor`.
|
|
9
|
+
*/
|
|
10
|
+
export const providerLogPathFor = (app, profile, env = process.env) => join(resolvePaths({ appName: app.appName, prefix: app.envPrefix, env }).state, "provider-log", `${profile}.jsonl`);
|
|
11
|
+
export const PROVIDER_LOG_BYTES = 1024 * 1024;
|
|
12
|
+
const previousOf = (path) => path.replace(/\.jsonl$/, ".1.jsonl");
|
|
13
|
+
/**
|
|
14
|
+
* The file, private and under its cap, ready for an engine to append to. The cap is kept here rather than by the
|
|
15
|
+
* writer, because an engine in another language appends without knowing it: one older file is kept.
|
|
16
|
+
*/
|
|
17
|
+
export const openProviderLog = (path, maxBytes = PROVIDER_LOG_BYTES) => {
|
|
18
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
19
|
+
if (existsSync(path) && statSync(path).size >= maxBytes)
|
|
20
|
+
renameSync(path, previousOf(path));
|
|
21
|
+
closeSync(openSync(path, "a", 0o600));
|
|
22
|
+
chmodSync(path, 0o600);
|
|
23
|
+
return path;
|
|
24
|
+
};
|
|
25
|
+
export const writeProviderWarning = (path, kind, message, now = new Date()) => {
|
|
26
|
+
openProviderLog(path);
|
|
27
|
+
appendFileSync(path, `${JSON.stringify({ time: now.toISOString(), level: "warn", kind, message })}\n`);
|
|
28
|
+
};
|
|
29
|
+
/** Warnings since `since`, by kind; a line without one counts as `other`, a line that is not JSON is skipped. */
|
|
30
|
+
export const providerWarnings = (path, since) => {
|
|
31
|
+
const kinds = {};
|
|
32
|
+
let total = 0;
|
|
33
|
+
for (const file of [previousOf(path), path]) {
|
|
34
|
+
if (!existsSync(file))
|
|
35
|
+
continue;
|
|
36
|
+
for (const line of readFileSync(file, "utf8").split("\n")) {
|
|
37
|
+
if (!line)
|
|
38
|
+
continue;
|
|
39
|
+
let entry;
|
|
40
|
+
try {
|
|
41
|
+
entry = JSON.parse(line);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const at = typeof entry.time === "string" ? Date.parse(entry.time) : Number.NaN;
|
|
47
|
+
if (!(at >= since.getTime()))
|
|
48
|
+
continue;
|
|
49
|
+
const kind = typeof entry.kind === "string" && entry.kind ? entry.kind : "other";
|
|
50
|
+
kinds[kind] = (kinds[kind] ?? 0) + 1;
|
|
51
|
+
total++;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return { path, total, kinds };
|
|
55
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.528.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",
|