@wirecat/cli-core 0.504.0 → 0.505.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 CHANGED
@@ -7,6 +7,19 @@ shape of this file.
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 0.505.0 — 31.10.2026
11
+
12
+ ### Added
13
+
14
+ - **`appDoctorCommand` takes `--online`, and a messenger's and an app's `doctor` now say one thing.** Both use the
15
+ summary "the state this installation is in, without connecting unless --online" and the option text "also connect
16
+ once and read the account; sends nothing" (one constant, `doctor-text.ts`). Without the flag the answer is as before
17
+ and nothing is called. With it, `online` joins the answer: `{ ok, duration_ms, version: { current, latest, newer } }`,
18
+ plus the fields of the new `AppDoctorOptions.online(env)` hook, where a CLI checks its own service (a login, a folder
19
+ read); a throw there is `ok: false` with `error_code` and `error`, never a failed command. `version` comes from npm
20
+ (the `latestVersion` that `upgrade` uses) when the CLI passes the new `AppDoctorOptions.packageName`; without it that
21
+ check is left out. The command's effects are now `REMOTE_READ`, as the messenger's.
22
+
10
23
  ## 0.504.0 — 31.10.2026
11
24
 
12
25
  ### Added
@@ -9,6 +9,15 @@ export interface AppDoctorOptions {
9
9
  oauthClient?: (env: NodeJS.ProcessEnv) => {
10
10
  client: "wirecat" | "own";
11
11
  };
12
+ /** The npm package, so `--online` can say whether a newer version is published; without it that check is left out. */
13
+ packageName?: string;
14
+ /**
15
+ * What `--online` checks of the service: a login, a folder read. Its fields join the `online` answer, and `ok: false`
16
+ * (or a throw, reported as `error_code` and `error`) makes the whole check fail without failing the command.
17
+ */
18
+ online?: (env: NodeJS.ProcessEnv) => Promise<{
19
+ ok: boolean;
20
+ } & Record<string, unknown>>;
12
21
  }
13
22
  /**
14
23
  * Run when everything else is broken, so a problem in the config file or the OAuth setting is a field, never a throw.
@@ -103,5 +112,9 @@ export declare const appDoctorReport: (app: AppIdentity, configuration: Configur
103
112
  };
104
113
  fixes: string[] | never[];
105
114
  };
106
- /** `doctor` for a CLI that is not a messenger: gcal, drive, zoom. A messenger's is `doctorCommand(messenger)`. */
115
+ /**
116
+ * `doctor` for a CLI that is not a messenger: gcal, drive, zoom. A messenger's is `doctorCommand(messenger)`, and both
117
+ * say the same thing: the state from disk, and only with `--online` a call out. Here that call is `options.online`
118
+ * and, with `options.packageName`, whether npm has a newer version.
119
+ */
107
120
  export declare const appDoctorCommand: (app: AppIdentity, configuration: Configuration, options?: AppDoctorOptions) => Command;
@@ -2,15 +2,48 @@ import { existsSync } from "node:fs";
2
2
  import { dirname, resolve } from "node:path";
3
3
  import { CliError, configFilePath, resolvePaths } from "@wirecat/cli-base";
4
4
  import { annotate } from "@wirecat/cli-base/commands";
5
+ import { isNewer, latestVersion } from "@wirecat/cli-base/update";
5
6
  import { Command } from "commander";
6
7
  import { envName, readOnlyLockOf } from "../ports/app.js";
7
8
  import { storePath } from "../store/path.js";
8
9
  import { environmentOf, outputFor } from "./context.js";
9
- import { LOCAL_READ } from "./effects.js";
10
+ import { DOCTOR_ONLINE_OPTION, DOCTOR_SUMMARY } from "./doctor-text.js";
11
+ import { REMOTE_READ } from "./effects.js";
10
12
  import { itemOf, renderAnswer } from "./envelope.js";
11
13
  import { privateFiles } from "./messenger/private-files.js";
12
14
  import { unknownPermissionIssues } from "./permission-keys.js";
13
15
  import { profileFree } from "./profile.js";
16
+ const onlineCheck = async (app, env, fetcher, options) => {
17
+ const started = Date.now();
18
+ let service = { ok: true };
19
+ if (options.online)
20
+ try {
21
+ service = await options.online(env);
22
+ }
23
+ catch (error) {
24
+ service = {
25
+ ok: false,
26
+ error_code: error instanceof CliError ? error.code : "generic_failure",
27
+ error: error instanceof Error ? error.message : String(error),
28
+ };
29
+ }
30
+ const latest = options.packageName
31
+ ? await latestVersion(options.packageName, fetcher, { timeoutMs: 3000 })
32
+ : undefined;
33
+ return {
34
+ ...service,
35
+ duration_ms: Date.now() - started,
36
+ ...(options.packageName
37
+ ? {
38
+ version: {
39
+ current: app.version,
40
+ latest: latest ?? null,
41
+ newer: latest !== undefined && isNewer(latest, app.version),
42
+ },
43
+ }
44
+ : {}),
45
+ };
46
+ };
14
47
  const failure = (error) => {
15
48
  if (error instanceof CliError)
16
49
  return error.message;
@@ -85,17 +118,26 @@ export const appDoctorReport = (app, configuration, env, command, options = {})
85
118
  };
86
119
  }
87
120
  };
88
- /** `doctor` for a CLI that is not a messenger: gcal, drive, zoom. A messenger's is `doctorCommand(messenger)`. */
121
+ /**
122
+ * `doctor` for a CLI that is not a messenger: gcal, drive, zoom. A messenger's is `doctorCommand(messenger)`, and both
123
+ * say the same thing: the state from disk, and only with `--online` a call out. Here that call is `options.online`
124
+ * and, with `options.packageName`, whether npm has a newer version.
125
+ */
89
126
  export const appDoctorCommand = (app, configuration, options = {}) => profileFree(annotate(new Command("doctor"), {
90
- effects: LOCAL_READ,
127
+ effects: REMOTE_READ,
91
128
  examples: [`${app.command} doctor`, `${app.command} doctor --json`],
92
129
  }))
93
- .description("check this installation: version, paths, config, profiles, store; no keyring or network")
94
- .action(function () {
95
- const env = environmentOf(this).env ?? process.env;
130
+ .description(DOCTOR_SUMMARY)
131
+ .option("--online", DOCTOR_ONLINE_OPTION)
132
+ .action(async function () {
133
+ const environment = environmentOf(this);
134
+ const env = environment.env ?? process.env;
96
135
  const { report, fixes } = appDoctorReport(app, configuration, env, this, options);
136
+ const online = this.opts().online
137
+ ? await onlineCheck(app, env, environment.fetch ?? fetch, options)
138
+ : undefined;
97
139
  const output = outputFor(this);
98
- renderAnswer(this, output, itemOf(report));
140
+ renderAnswer(this, output, itemOf(online ? { ...report, online } : report));
99
141
  for (const fix of fixes)
100
142
  output.renderer.note(`to make it private: ${fix}`);
101
143
  });
@@ -0,0 +1,2 @@
1
+ export declare const DOCTOR_SUMMARY = "the state this installation is in, without connecting unless --online";
2
+ export declare const DOCTOR_ONLINE_OPTION = "also connect once and read the account; sends nothing";
@@ -0,0 +1,2 @@
1
+ export const DOCTOR_SUMMARY = "the state this installation is in, without connecting unless --online";
2
+ export const DOCTOR_ONLINE_OPTION = "also connect once and read the account; sends nothing";
@@ -10,6 +10,7 @@ import { FloodMemory, floodPathFor } from "../../sends/flood.js";
10
10
  import { SendJournal, sendsPathFor } from "../../sends/journal.js";
11
11
  import { storePath } from "../../store/path.js";
12
12
  import { baseContext, environmentOf, outputFor } from "../context.js";
13
+ import { DOCTOR_ONLINE_OPTION, DOCTOR_SUMMARY } from "../doctor-text.js";
13
14
  import { destroysNothing, LOCAL_WRITE, NO_EFFECTS, REMOTE_READ } from "../effects.js";
14
15
  import { itemOf, renderAnswer, snakeKeys } from "../envelope.js";
15
16
  import { unknownPermissionIssues } from "../permission-keys.js";
@@ -28,8 +29,8 @@ import { storeSummary } from "./store-maintenance-command.js";
28
29
  */
29
30
  export const doctorCommand = (messenger) => {
30
31
  const command = annotate(new Command("doctor"), { effects: REMOTE_READ })
31
- .description("the state this installation is in, without connecting unless --online")
32
- .option("--online", "also connect once and read the account; sends nothing")
32
+ .description(DOCTOR_SUMMARY)
33
+ .option("--online", DOCTOR_ONLINE_OPTION)
33
34
  .action(async function () {
34
35
  const { online } = this.opts();
35
36
  const { report, renderer, fixes = [] } = await diagnose(this, messenger);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wirecat/cli-core",
3
- "version": "0.504.0",
3
+ "version": "0.505.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",