@wirecat/cli-core 0.465.0 → 0.466.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 +21 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/messenger/account-command.js +3 -0
- package/dist/cli/messenger/context.d.ts +9 -0
- package/dist/cli/messenger/context.js +5 -3
- package/dist/cli/messenger/mcp-command.js +8 -1
- package/dist/cli/messenger/serve-relay.d.ts +1 -0
- package/dist/entities/account-profile.d.ts +2 -0
- package/dist/entities/account-profile.js +2 -0
- package/dist/entities/index.d.ts +1 -0
- package/dist/mcp/all-profiles.d.ts +10 -2
- package/dist/mcp/all-profiles.js +22 -5
- package/dist/mcp/http/oauth.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,27 @@ Notable changes to `@wirecat/cli-core` (`@wirecat/cli-messaging` up to 0.261.0,
|
|
|
5
5
|
minor version may break callers, and says how under "Changed — may break callers". `pnpm docs:check` checks the
|
|
6
6
|
shape of this file.
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## 0.466.0 — 27.10.2026
|
|
9
|
+
|
|
10
|
+
### Changed — may break callers
|
|
11
|
+
|
|
12
|
+
- **`account list` says whether each profile is signed in on this computer.** The `account_profile` entity gains an
|
|
13
|
+
optional `signed_in`, set when the messenger sets `Messenger.profileState`; a caller that checks items against an
|
|
14
|
+
older, strict copy of it refuses the new field.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **`<cli> mcp serve` with no profile marks the profiles that are not signed in.** A messenger sets the new optional
|
|
19
|
+
`Messenger.profileState(profile, env)` (`signed-in`, `signed-out` or `bot`), read from its own files without
|
|
20
|
+
connecting. The `profile` field keeps its `enum` and gains `oneOf` entries with a `title` per profile
|
|
21
|
+
(`default — not signed in`), as MCP's titled enums, and its description lists who is signed in. Bot profiles are left
|
|
22
|
+
out: they have their own server. A call for a profile that is not signed in is refused with how to sign it in
|
|
23
|
+
(`<cli> <profile> setup`, or WireCat Desktop) and starts nothing. Unset, every profile counts as signed in, as before.
|
|
24
|
+
- **The "no profile named" error marks the listed profiles that are not signed in** (`beta (not signed in)`), when the
|
|
25
|
+
messenger sets `profileState`.
|
|
26
|
+
- **The MCP login page says why a code that just worked is now wrong.** Each login code works once; after an app
|
|
27
|
+
logs in, the server shows a new one. The wrong-code message now says so and points to WireCat Desktop and
|
|
28
|
+
`<cli> mcp server status`, where the new code is.
|
|
9
29
|
|
|
10
30
|
## 0.465.0 — 27.10.2026
|
|
11
31
|
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -88,7 +88,7 @@ export { type MeetingSeriesInventoryReader, readMeetingSeriesInventory } from ".
|
|
|
88
88
|
export { accountFileFor, profilesHere, rememberAccount } from "./messenger/accounts.js";
|
|
89
89
|
export { terminalAsker } from "./messenger/ask.js";
|
|
90
90
|
export type { CompletionOptions } from "./messenger/complete-command.js";
|
|
91
|
-
export { type ConnectOptions, connectFor, type Messenger, type MessengerContext, messengerContext, type ReadConnection, type StoredMessages, } from "./messenger/context.js";
|
|
91
|
+
export { type ConnectOptions, connectFor, type Messenger, type MessengerContext, messengerContext, type ProfileState, type ReadConnection, type StoredMessages, } from "./messenger/context.js";
|
|
92
92
|
export { COMMAND_NEEDS } from "./messenger/lacks.js";
|
|
93
93
|
export { httpTokenFile, type McpEnvironment, serverEntry } from "./messenger/mcp-command.js";
|
|
94
94
|
export { type MessageItem, messageItem, type ReadMessage } from "./messenger/message-items.js";
|
|
@@ -45,6 +45,9 @@ export const accountCommand = (messenger) => {
|
|
|
45
45
|
current: profile === current,
|
|
46
46
|
account: key?.account ?? null,
|
|
47
47
|
name: key && store ? await store.accounts.accountName(key) : null,
|
|
48
|
+
...(messenger.profileState
|
|
49
|
+
? { signed_in: messenger.profileState(profile, context.env) !== "signed-out" }
|
|
50
|
+
: {}),
|
|
48
51
|
});
|
|
49
52
|
}
|
|
50
53
|
renderAnswer(this, context, wholeList("account list", items));
|
|
@@ -49,6 +49,7 @@ export interface LazyStore {
|
|
|
49
49
|
}
|
|
50
50
|
export declare const lazyStore: (env: NodeJS.ProcessEnv, command: string, version?: string) => LazyStore;
|
|
51
51
|
export declare const storedMessages: (provider: Provider, store: () => Promise<MessageStore>, account: () => Id | null | undefined) => StoredMessages;
|
|
52
|
+
export type ProfileState = "signed-in" | "signed-out" | "bot";
|
|
52
53
|
/** What one messenger CLI hands the shared commands. Everything else about it stays in its own code. */
|
|
53
54
|
export interface Messenger extends ServiceMessenger {
|
|
54
55
|
/**
|
|
@@ -65,6 +66,12 @@ export interface Messenger extends ServiceMessenger {
|
|
|
65
66
|
* Called inside `--timeout`; the context tracks and closes what it returns.
|
|
66
67
|
*/
|
|
67
68
|
connect: (command: Command, context: BaseContext, options?: ConnectOptions) => Promise<MessengerAdapter>;
|
|
69
|
+
/**
|
|
70
|
+
* A profile's state, read from the messenger's own files without connecting: `signed-in` has a session, `signed-out`
|
|
71
|
+
* has none yet, `bot` speaks for a bot. `<tool> mcp` with no profile offers the personal ones and marks those not
|
|
72
|
+
* signed in; `account list` and the profile errors say so too. Unset: every profile counts as signed in.
|
|
73
|
+
*/
|
|
74
|
+
profileState?: (profile: string, env: NodeJS.ProcessEnv) => ProfileState;
|
|
68
75
|
/** The help for a `<chat>` argument, in this messenger's words. */
|
|
69
76
|
chatArgument: string;
|
|
70
77
|
/** The group settings this messenger has, as `chats update` offers them; every one when unset. */
|
|
@@ -262,6 +269,8 @@ export declare const connectedFor: (command: Command, messenger: Pick<Messenger,
|
|
|
262
269
|
adapter: MessengerAdapter;
|
|
263
270
|
close: () => Promise<void>;
|
|
264
271
|
}>;
|
|
272
|
+
/** `default (not signed in)`: a profile as an error lists it among the choices. */
|
|
273
|
+
export declare const markedProfile: (messenger: Messenger, profile: string, env: NodeJS.ProcessEnv) => string;
|
|
265
274
|
/**
|
|
266
275
|
* A write to the local store alone. `deny` already stopped the command (`messengerContext`); `readonly`
|
|
267
276
|
* stops this write too. There is no question to put to the owner here, so `ask` refuses rather than
|
|
@@ -288,9 +288,11 @@ export const connectedFor = async (command, messenger, base, options) => {
|
|
|
288
288
|
}),
|
|
289
289
|
};
|
|
290
290
|
};
|
|
291
|
-
|
|
291
|
+
/** `default (not signed in)`: a profile as an error lists it among the choices. */
|
|
292
|
+
export const markedProfile = (messenger, profile, env) => messenger.profileState?.(profile, env) === "signed-out" ? `${profile} (not signed in)` : profile;
|
|
293
|
+
const unrecorded = (command, messenger, profile, { offline, profilesHere }, env) => {
|
|
292
294
|
if (!profilesHere.includes(profile)) {
|
|
293
|
-
return new CliError("not_found", `no profile named "${profile}" — profiles here: ${profilesHere.length > 0 ? profilesHere.join(", ") : "none"}`, { profiles: profilesHere });
|
|
295
|
+
return new CliError("not_found", `no profile named "${profile}" — profiles here: ${profilesHere.length > 0 ? profilesHere.map((one) => markedProfile(messenger, one, env)).join(", ") : "none"}`, { profiles: profilesHere });
|
|
294
296
|
}
|
|
295
297
|
const words = `${messenger.app.command} ${asFirstWord(profile)}`;
|
|
296
298
|
const setup = rootOf(command).commands.some((one) => one.name() === "setup") ? `\`${words}setup\`` : undefined;
|
|
@@ -361,7 +363,7 @@ export const messengerContext = (command, messenger, resolving = {}) => {
|
|
|
361
363
|
const accountFor = () => {
|
|
362
364
|
const account = messenger.accountOf?.(command) ?? recalledAccount(app, provider, profile, base.env);
|
|
363
365
|
if (!account)
|
|
364
|
-
throw unrecorded(command, messenger, profile, base.settings);
|
|
366
|
+
throw unrecorded(command, messenger, profile, base.settings, base.env);
|
|
365
367
|
return account;
|
|
366
368
|
};
|
|
367
369
|
const guard = messenger.guard?.(command, base.settings, warn) ??
|
|
@@ -222,7 +222,11 @@ const serveEveryProfile = async (self, context, messenger, flags) => {
|
|
|
222
222
|
const { app } = messenger;
|
|
223
223
|
const { env } = context;
|
|
224
224
|
const tokenFile = httpTokenFile(app, ALL_PROFILES, env);
|
|
225
|
-
const
|
|
225
|
+
const state = (profile) => messenger.profileState?.(profile, env) ?? "signed-in";
|
|
226
|
+
// Read again for each session, so a profile set up while the server runs appears. A bot has its own server.
|
|
227
|
+
const profiles = () => messenger
|
|
228
|
+
.resolveSettings(self.optsWithGlobals(), { env, needsProfile: false, readOnly: true })
|
|
229
|
+
.profilesHere.filter((profile) => state(profile) !== "bot");
|
|
226
230
|
if (profiles().length === 0)
|
|
227
231
|
throw new CliError("configuration_error", `no ${app.command} profile on this computer yet — set one up, then run \`${app.command} mcp serve\` again`);
|
|
228
232
|
const given = environmentOf(self).mcp ?? {};
|
|
@@ -235,6 +239,9 @@ const serveEveryProfile = async (self, context, messenger, flags) => {
|
|
|
235
239
|
version: app.version,
|
|
236
240
|
name: messenger.name ?? app.command,
|
|
237
241
|
profiles,
|
|
242
|
+
signedIn: (profile) => state(profile) === "signed-in",
|
|
243
|
+
signIn: (profile) => `profile ${profile} is not signed in — run \`${app.command} ${profile} setup\`, or sign in from WireCat Desktop, ` +
|
|
244
|
+
"then try again. If the owner did not choose this profile, ask which one.",
|
|
238
245
|
child: (profile) => ({ command: execPath, args: [scriptPath, profile, "mcp", "serve", ...tightened], env }),
|
|
239
246
|
instructions: instructions({
|
|
240
247
|
command: app.command,
|
|
@@ -39,6 +39,7 @@ export declare const serveRelay: (command: Command, messenger: Messenger) => {
|
|
|
39
39
|
connection?: "exclusive";
|
|
40
40
|
pace?: import("../../sends/pace.js").PaceRate;
|
|
41
41
|
resolveSettings: (flags: import("../settings.js").GlobalFlags, options?: import("../settings.js").ResolveOptions) => import("../settings.js").Settings;
|
|
42
|
+
profileState?: (profile: string, env: NodeJS.ProcessEnv) => import("./context.js").ProfileState;
|
|
42
43
|
chatArgument: string;
|
|
43
44
|
groupSettings?: readonly import("../../domain/models.js").GroupSettingKey[];
|
|
44
45
|
pollQuiz?: boolean;
|
|
@@ -4,4 +4,6 @@ export declare const accountProfile: v.StrictObjectSchema<{
|
|
|
4
4
|
readonly current: v.BooleanSchema<undefined>;
|
|
5
5
|
readonly account: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>, undefined>;
|
|
6
6
|
readonly name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
7
|
+
/** False for a profile with no session on this computer; absent when the messenger does not say. */
|
|
8
|
+
readonly signed_in: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
7
9
|
}, undefined>;
|
package/dist/entities/index.d.ts
CHANGED
|
@@ -4177,6 +4177,7 @@ export declare const ENTITIES: {
|
|
|
4177
4177
|
readonly current: v.BooleanSchema<undefined>;
|
|
4178
4178
|
readonly account: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>, undefined>;
|
|
4179
4179
|
readonly name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
4180
|
+
readonly signed_in: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
4180
4181
|
}, undefined>;
|
|
4181
4182
|
readonly organization: v.StrictObjectSchema<{
|
|
4182
4183
|
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
@@ -16,14 +16,22 @@ export interface AllProfilesOptions {
|
|
|
16
16
|
name: string;
|
|
17
17
|
/** Read again for each new session, so a profile set up while serving over HTTP shows up. */
|
|
18
18
|
profiles: () => readonly string[];
|
|
19
|
+
/** Whether a profile has a session now; unset, every one does. A call for one without is refused with `signIn`. */
|
|
20
|
+
signedIn?: (profile: string) => boolean;
|
|
21
|
+
/** How to sign a profile in, for the refusal of a call it cannot serve yet. */
|
|
22
|
+
signIn?: (profile: string) => string;
|
|
19
23
|
child: (profile: string) => ChildSpec;
|
|
20
24
|
instructions: string;
|
|
21
25
|
idleMs?: number;
|
|
22
26
|
/** A child's stderr, line by line; never this process's stdout, which carries the protocol. */
|
|
23
27
|
note?: (line: string) => void;
|
|
24
28
|
}
|
|
25
|
-
/**
|
|
26
|
-
|
|
29
|
+
/**
|
|
30
|
+
* The child's tool with the one field that picks the profile, required and without a default. Each profile carries
|
|
31
|
+
* its state as the title an app shows in its menu (`oneOf` with `const` and `title`, as MCP's titled enums), and the
|
|
32
|
+
* description repeats it for an app that shows only the description.
|
|
33
|
+
*/
|
|
34
|
+
export declare const withProfile: (tool: Tool, profiles: readonly string[], name: string, signedIn?: (profile: string) => boolean) => Tool;
|
|
27
35
|
/**
|
|
28
36
|
* **One server for every profile of a tool** (TASK-26): `<tool> mcp` shows the tools `<tool> <profile> mcp` shows,
|
|
29
37
|
* each with a required `profile`, and runs a call in that profile's own server, started as a child the first time
|
package/dist/mcp/all-profiles.js
CHANGED
|
@@ -138,9 +138,17 @@ const pool = (options) => {
|
|
|
138
138
|
};
|
|
139
139
|
return { use, close };
|
|
140
140
|
};
|
|
141
|
-
/**
|
|
142
|
-
|
|
141
|
+
/**
|
|
142
|
+
* The child's tool with the one field that picks the profile, required and without a default. Each profile carries
|
|
143
|
+
* its state as the title an app shows in its menu (`oneOf` with `const` and `title`, as MCP's titled enums), and the
|
|
144
|
+
* description repeats it for an app that shows only the description.
|
|
145
|
+
*/
|
|
146
|
+
export const withProfile = (tool, profiles, name, signedIn = () => true) => {
|
|
143
147
|
const schema = tool.inputSchema;
|
|
148
|
+
const out = profiles.filter((profile) => !signedIn(profile));
|
|
149
|
+
const states = out.length === 0
|
|
150
|
+
? ""
|
|
151
|
+
: ` Signed in: ${profiles.filter(signedIn).join(", ") || "none"} · Not signed in: ${out.join(", ")}.`;
|
|
144
152
|
return {
|
|
145
153
|
...tool,
|
|
146
154
|
inputSchema: {
|
|
@@ -149,7 +157,11 @@ export const withProfile = (tool, profiles, name) => {
|
|
|
149
157
|
profile: {
|
|
150
158
|
type: "string",
|
|
151
159
|
enum: [...profiles],
|
|
152
|
-
|
|
160
|
+
oneOf: profiles.map((profile) => ({
|
|
161
|
+
const: profile,
|
|
162
|
+
title: signedIn(profile) ? profile : `${profile} — not signed in`,
|
|
163
|
+
})),
|
|
164
|
+
description: `Which ${name} account, by its profile. If the owner did not name one, ask which — never guess.${states}`,
|
|
153
165
|
},
|
|
154
166
|
...schema.properties,
|
|
155
167
|
},
|
|
@@ -174,24 +186,29 @@ export const allProfilesServer = (options) => {
|
|
|
174
186
|
// list comes from whichever answers first and is kept.
|
|
175
187
|
const tools = (profiles) => {
|
|
176
188
|
listed ??= children
|
|
177
|
-
.use(profiles[0], (child) => child.request("tools/list", {}))
|
|
189
|
+
.use(profiles.find(signedIn) ?? profiles[0], (child) => child.request("tools/list", {}))
|
|
178
190
|
.then((answer) => (answer.tools ?? []));
|
|
179
191
|
listed.catch(() => {
|
|
180
192
|
listed = undefined;
|
|
181
193
|
});
|
|
182
194
|
return listed;
|
|
183
195
|
};
|
|
196
|
+
const signedIn = (profile) => options.signedIn?.(profile) ?? true;
|
|
184
197
|
const build = () => {
|
|
185
198
|
const profiles = [...options.profiles()].sort();
|
|
186
199
|
const server = new McpServer({ name: options.command, version: options.version }, { capabilities: { tools: {} }, instructions: options.instructions });
|
|
187
200
|
server.server.setRequestHandler("tools/list", async () => ({
|
|
188
|
-
tools: profiles.length === 0
|
|
201
|
+
tools: profiles.length === 0
|
|
202
|
+
? []
|
|
203
|
+
: (await tools(profiles)).map((one) => withProfile(one, profiles, options.name, signedIn)),
|
|
189
204
|
}));
|
|
190
205
|
server.server.setRequestHandler("tools/call", async (request) => {
|
|
191
206
|
const { name, arguments: given = {}, _meta } = request.params;
|
|
192
207
|
const { profile, ...rest } = given;
|
|
193
208
|
if (typeof profile !== "string" || !profiles.includes(profile))
|
|
194
209
|
return refusal(`${name} needs "profile", one of: ${profiles.join(", ")}. If the owner did not say which account, ask.`);
|
|
210
|
+
if (!signedIn(profile))
|
|
211
|
+
return refusal(options.signIn?.(profile) ?? `profile ${profile} is not signed in.`);
|
|
195
212
|
try {
|
|
196
213
|
return (await children.use(profile, (child) => child.request("tools/call", { name, arguments: rest, ...(_meta ? { _meta } : {}) })));
|
|
197
214
|
}
|
package/dist/mcp/http/oauth.js
CHANGED
|
@@ -201,7 +201,7 @@ export const ownerLogin = ({ issuer, resource, file, appName, onCode, now = () =
|
|
|
201
201
|
retryAt = now() + WRONG_CODE_WAIT_MS;
|
|
202
202
|
return throttled();
|
|
203
203
|
}
|
|
204
|
-
return page(params, client, `Wrong code. ${WRONG_TRIES - wrong} tries left.`);
|
|
204
|
+
return page(params, client, `Wrong code. Each code works for one login: after an app logs in, a new one appears in WireCat Desktop and in "${appName} mcp server status". ${WRONG_TRIES - wrong} tries left.`);
|
|
205
205
|
}
|
|
206
206
|
wrong = 0;
|
|
207
207
|
freshCode();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.466.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",
|