@wirecat/cli-core 0.470.0 → 0.472.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.
Files changed (62) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/cli/app-doctor-command.d.ts +107 -0
  3. package/dist/cli/app-doctor-command.js +101 -0
  4. package/dist/cli/app-mcp-command.d.ts +23 -0
  5. package/dist/cli/app-mcp-command.js +49 -0
  6. package/dist/cli/index.d.ts +4 -2
  7. package/dist/cli/index.js +2 -1
  8. package/dist/cli/messenger/mcp-command.d.ts +29 -3
  9. package/dist/cli/messenger/mcp-command.js +59 -39
  10. package/dist/cli/shared-command-exports.d.ts +348 -0
  11. package/dist/cli/shared-command-exports.js +4 -0
  12. package/dist/errors/reasons.js +1 -0
  13. package/dist/mail/bulk.d.ts +7 -0
  14. package/dist/mail/bulk.js +3 -0
  15. package/dist/mail/facts.d.ts +9 -0
  16. package/dist/mail/facts.js +22 -0
  17. package/dist/mail/folders.d.ts +10 -0
  18. package/dist/mail/folders.js +13 -0
  19. package/dist/mail/gone.d.ts +20 -0
  20. package/dist/mail/gone.js +53 -0
  21. package/dist/mail/headers.d.ts +43 -0
  22. package/dist/mail/headers.js +88 -0
  23. package/dist/mail/index.d.ts +17 -0
  24. package/dist/mail/index.js +16 -0
  25. package/dist/mail/parse.d.ts +24 -0
  26. package/dist/mail/parse.js +79 -0
  27. package/dist/mail/presets.d.ts +30 -0
  28. package/dist/mail/presets.js +95 -0
  29. package/dist/mail/probe.d.ts +49 -0
  30. package/dist/mail/probe.js +81 -0
  31. package/dist/mail/resume.d.ts +61 -0
  32. package/dist/mail/resume.js +61 -0
  33. package/dist/mail/rule-input.d.ts +14 -0
  34. package/dist/mail/rule-input.js +32 -0
  35. package/dist/mail/rule-tags.d.ts +40 -0
  36. package/dist/mail/rule-tags.js +87 -0
  37. package/dist/mail/rules.d.ts +83 -0
  38. package/dist/mail/rules.js +108 -0
  39. package/dist/mail/score.d.ts +67 -0
  40. package/dist/mail/score.js +174 -0
  41. package/dist/mail/sender-scores.d.ts +30 -0
  42. package/dist/mail/sender-scores.js +96 -0
  43. package/dist/mail/source.d.ts +67 -0
  44. package/dist/mail/source.js +1 -0
  45. package/dist/mail/stored.d.ts +7 -0
  46. package/dist/mail/stored.js +36 -0
  47. package/dist/mail/sync.d.ts +91 -0
  48. package/dist/mail/sync.js +572 -0
  49. package/dist/mail/testing/contract.d.ts +20 -0
  50. package/dist/mail/testing/contract.js +106 -0
  51. package/dist/mail/testing/fake.d.ts +61 -0
  52. package/dist/mail/testing/fake.js +136 -0
  53. package/dist/mail/testing/index.d.ts +2 -0
  54. package/dist/mail/testing/index.js +2 -0
  55. package/dist/mcp/agent.js +14 -2
  56. package/dist/mcp/app-server.d.ts +42 -0
  57. package/dist/mcp/app-server.js +162 -0
  58. package/dist/mcp/instructions.d.ts +12 -0
  59. package/dist/mcp/instructions.js +23 -0
  60. package/dist/mcp/server.d.ts +4 -0
  61. package/dist/mcp/server.js +2 -2
  62. package/package.json +12 -1
@@ -66,13 +66,42 @@ export const servedProfiles = (self, messenger, { settings, env }, given, words)
66
66
  const withProfiles = (command) => command.option("--profiles <names|all>", "serve several profiles from one server instead of one: names, comma-separated, or all for every profile");
67
67
  /** Where `mcp serve --http` keeps the hashes of the tokens it issued — per profile, in the state folder. */
68
68
  export const httpTokenFile = (app, profile, env) => join(resolvePaths({ appName: app.appName, prefix: app.envPrefix, env }).state, "mcp-http", `${profile}.json`);
69
- const withFlags = (command) => command.option("--permission <key=level>", "make a permission stricter for this server only, like messages.send=deny; it never loosens the profile; repeat for more keys", (value, previous = []) => [...previous, value]);
70
- export const mcpCommand = (messenger) => {
71
- const { app } = messenger;
69
+ const withFlags = (command, example) => command.option("--permission <key=level>", `make a permission stricter for this server only, like ${example}; it never loosens the profile; repeat for more keys`, (value, previous = []) => [...previous, value]);
70
+ const messengerHost = (messenger) => ({
71
+ app: messenger.app,
72
+ name: messenger.name ?? messenger.app.command,
73
+ resolveSettings: messenger.resolveSettings,
74
+ ...(messenger.profileState ? { profileState: messenger.profileState } : {}),
75
+ context: (self, options) => messengerContext(self, messenger, options),
76
+ permissionExample: "messages.send=deny",
77
+ serveStdio: async (self, context) => {
78
+ // Loaded here, not at the top: every other command would otherwise pay for the SDK.
79
+ const { serveOverStdio } = await import("../../mcp/server.js");
80
+ await serveOverStdio(self, context, messenger, {});
81
+ },
82
+ serveHttp: async (self, context, http) => {
83
+ const { serveOverHttpUntilStopped } = await import("../../mcp/server.js");
84
+ await serveOverHttpUntilStopped(self, context, messenger, {}, http);
85
+ },
86
+ instructions: async (profiles) => {
87
+ const { instructions } = await import("../../mcp/instructions.js");
88
+ return instructions({
89
+ command: messenger.app.command,
90
+ name: messenger.name ?? messenger.app.command,
91
+ profile: profiles,
92
+ writes: [],
93
+ });
94
+ },
95
+ signedIn: (self, profile, env) => (messenger.accountOf?.(self) ?? recalledAccount(messenger.app, messenger.provider, profile, env)) !== undefined,
96
+ });
97
+ export const mcpCommand = (messenger) => mcpGroup(messengerHost(messenger));
98
+ export const mcpGroup = (host) => {
99
+ const { app } = host;
100
+ const flagged = (command) => withFlags(command, host.permissionExample);
72
101
  const command = annotate(new Command("mcp"), { effects: LOCAL_READ, mcp: false })
73
102
  .summary("serve this profile's commands to an AI agent, and set that up")
74
103
  .description(`serve this profile to an agent over MCP and set it up — \`claude mcp add ${app.command} -- ${app.command} <profile> mcp serve\``);
75
- command.addCommand(withProfiles(withFlags(annotate(new Command("serve"), {
104
+ command.addCommand(withProfiles(flagged(annotate(new Command("serve"), {
76
105
  effects: REMOTE_WRITE,
77
106
  lifetime: "persistent",
78
107
  mcp: false,
@@ -89,10 +118,10 @@ export const mcpCommand = (messenger) => {
89
118
  .option("--public-url <url>", "the tunnel's https address the browser apps use, e.g. https://<name>.ts.net or https://<name>.ts.net/tg")
90
119
  .action(async function () {
91
120
  const flags = this.optsWithGlobals();
92
- const context = messengerContext(this, messenger, { needsProfile: false });
93
- const served = servedProfiles(this, messenger, context, flags.profiles, "mcp serve");
121
+ const context = host.context(this, { needsProfile: false });
122
+ const served = servedProfiles(this, host, context, flags.profiles, "mcp serve");
94
123
  if ("set" in served)
95
- return serveProfiles(this, context, messenger, flags, served.set);
124
+ return serveProfiles(this, context, host, flags, served.set);
96
125
  const { profile } = served;
97
126
  if (flags.http) {
98
127
  const publicUrl = publicUrlOf(flags.publicUrl, {
@@ -100,9 +129,8 @@ export const mcpCommand = (messenger) => {
100
129
  reaches: `the browser apps reach ${app.command}`,
101
130
  path: true,
102
131
  });
103
- const { serveOverHttpUntilStopped } = await import("../../mcp/server.js");
104
132
  const tokenFile = httpTokenFile(app, profile, context.env);
105
- await serveOverHttpUntilStopped(this, context, messenger, {}, {
133
+ await host.serveHttp(this, context, {
106
134
  publicUrl,
107
135
  port: portOf(flags.port, DEFAULT_PORT),
108
136
  tokenFile,
@@ -111,9 +139,7 @@ export const mcpCommand = (messenger) => {
111
139
  });
112
140
  return;
113
141
  }
114
- // Loaded here, not at the top: every other command would otherwise pay for the SDK.
115
- const { serveOverStdio } = await import("../../mcp/server.js");
116
- await serveOverStdio(this, context, messenger, {});
142
+ await host.serveStdio(this, context);
117
143
  }));
118
144
  command.addCommand(withProfiles(annotate(new Command("revoke"), {
119
145
  effects: LOCAL_WRITE,
@@ -122,8 +148,8 @@ export const mcpCommand = (messenger) => {
122
148
  })
123
149
  .summary("forget every login given to a browser app")
124
150
  .description("forget every login `mcp serve --http` gave a browser app, of this profile or of the server `--profiles` names; each app must log in again")).action(async function () {
125
- const context = messengerContext(this, messenger, { needsProfile: false });
126
- const served = servedProfiles(this, messenger, context, this.opts().profiles, "mcp revoke");
151
+ const context = host.context(this, { needsProfile: false });
152
+ const served = servedProfiles(this, host, context, this.opts().profiles, "mcp revoke");
127
153
  const { revokeAll } = await import("../../mcp/http/oauth.js");
128
154
  revokeAll(httpTokenFile(app, "set" in served ? served.set.key : served.profile, context.env));
129
155
  renderAnswer(this, context, itemOf("set" in served
@@ -135,9 +161,9 @@ export const mcpCommand = (messenger) => {
135
161
  permission: true,
136
162
  profiles: true,
137
163
  target: (self) => {
138
- const context = messengerContext(self, messenger, { needsProfile: false });
164
+ const context = host.context(self, { needsProfile: false });
139
165
  const { env } = context;
140
- const served = servedProfiles(self, messenger, context, self.opts().profiles, `mcp server ${self.name()}`);
166
+ const served = servedProfiles(self, host, context, self.opts().profiles, `mcp server ${self.name()}`);
141
167
  if ("set" in served) {
142
168
  const { set } = served;
143
169
  return {
@@ -162,20 +188,20 @@ export const mcpCommand = (messenger) => {
162
188
  version: app.version,
163
189
  };
164
190
  },
165
- answer: (self, item, lines) => renderAnswer(self, messengerContext(self, messenger, { needsProfile: false }), itemOf(item), () => lines.join("\n")),
191
+ answer: (self, item, lines) => renderAnswer(self, host.context(self, { needsProfile: false }), itemOf(item), () => lines.join("\n")),
166
192
  system: (self) => environmentOf(self).system,
167
193
  }));
168
- command.addCommand(withProfiles(withFlags(annotate(new Command("config"), {
194
+ command.addCommand(withProfiles(flagged(annotate(new Command("config"), {
169
195
  effects: LOCAL_READ,
170
196
  examples: [`${app.command} personal mcp config`, `${app.command} mcp config --profiles all`],
171
197
  })
172
198
  .summary("print the local MCP configuration of this profile, or of those --profiles names")
173
199
  .description("print the mcpServers entry for Claude Desktop, Cursor and others, with full paths; writes nothing"))).action(async function () {
174
200
  const flags = this.optsWithGlobals();
175
- const context = messengerContext(this, messenger, { needsProfile: false });
201
+ const context = host.context(this, { needsProfile: false });
176
202
  const { env } = context;
177
203
  const given = environmentOf(this).mcp ?? {};
178
- const served = servedProfiles(this, messenger, context, flags.profiles, "mcp config");
204
+ const served = servedProfiles(this, host, context, flags.profiles, "mcp config");
179
205
  const entry = serverEntry(app, {
180
206
  ...("set" in served ? { profiles: served.set.option } : { profile: served.profile }),
181
207
  flags,
@@ -190,7 +216,7 @@ export const mcpCommand = (messenger) => {
190
216
  : [],
191
217
  }), () => JSON.stringify(entry.config, null, 2));
192
218
  }));
193
- command.addCommand(withFlags(grantsAuthority(annotate(new Command("add"), {
219
+ command.addCommand(flagged(grantsAuthority(annotate(new Command("add"), {
194
220
  effects: LOCAL_WRITE,
195
221
  examples: [`${app.command} personal mcp add codex --allow-writes`],
196
222
  }))
@@ -199,7 +225,7 @@ export const mcpCommand = (messenger) => {
199
225
  .option("--allow-writes", "acknowledge that this profile offers writing tools")).action(async function (client) {
200
226
  if (client !== "codex" && client !== "claude-code")
201
227
  throw new CliError("validation_error", "choose codex or claude-code");
202
- const context = messengerContext(this, messenger);
228
+ const context = host.context(this);
203
229
  const { settings, env } = context;
204
230
  const given = environmentOf(this).mcp ?? {};
205
231
  const configuration = serverEntry(app, {
@@ -224,11 +250,11 @@ export const mcpCommand = (messenger) => {
224
250
  throw new CliError("validation_error", error instanceof Error ? error.message : "MCP setup failed");
225
251
  }
226
252
  }));
227
- command.addCommand(withFlags(annotate(new Command("check"), {
253
+ command.addCommand(flagged(annotate(new Command("check"), {
228
254
  effects: LOCAL_READ,
229
255
  examples: [`${app.command} personal mcp check`],
230
256
  }).description("check this profile's local MCP handshake and tool list")).action(async function () {
231
- const context = messengerContext(this, messenger);
257
+ const context = host.context(this);
232
258
  const { settings, env } = context;
233
259
  const given = environmentOf(this).mcp ?? {};
234
260
  const configuration = serverEntry(app, {
@@ -247,8 +273,8 @@ export const mcpCommand = (messenger) => {
247
273
  const { tools, potentialWrites } = await probeStdio(entry, {
248
274
  start: stderr.start,
249
275
  });
250
- const account = messenger.accountOf?.(this) ?? recalledAccount(app, messenger.provider, settings.profile, env);
251
- if (!account) {
276
+ const signedIn = host.signedIn(this, settings.profile, env);
277
+ if (signedIn === false) {
252
278
  const setup = rootOf(this).commands.some((one) => one.name() === "setup")
253
279
  ? ` — log in with \`${app.command} ${settings.profile} setup\``
254
280
  : "";
@@ -256,9 +282,9 @@ export const mcpCommand = (messenger) => {
256
282
  `tools will fail${setup}`);
257
283
  }
258
284
  renderAnswer(this, context, itemOf({
259
- healthy: account !== undefined,
285
+ healthy: signedIn !== false,
260
286
  profile: settings.profile,
261
- signed_in: account !== undefined,
287
+ signed_in: signedIn ?? null,
262
288
  tools: tools.length,
263
289
  potential_writes: potentialWrites.length,
264
290
  }));
@@ -282,11 +308,11 @@ export const profileSetLabel = (set) => {
282
308
  * `<tool> <profile> mcp serve` (`src/mcp/all-profiles.ts`). Nothing picks a profile: each tool takes one, and the
283
309
  * agent is told to ask.
284
310
  */
285
- const serveProfiles = async (self, context, messenger, flags, set) => {
286
- const { app } = messenger;
311
+ const serveProfiles = async (self, context, host, flags, set) => {
312
+ const { app } = host;
287
313
  const { env } = context;
288
314
  const tokenFile = httpTokenFile(app, set.key, env);
289
- const state = (profile) => messenger.profileState?.(profile, env) ?? "signed-in";
315
+ const state = (profile) => host.profileState?.(profile, env) ?? "signed-in";
290
316
  const bots = set.option === EVERY_PROFILE ? [] : set.profiles().filter((profile) => state(profile) === "bot");
291
317
  if (bots.length > 0)
292
318
  throw new CliError("validation_error", `${bots.join(", ")} ${bots.length === 1 ? "is a bot profile" : "are bot profiles"}, served by \`${app.command} <profile> bot mcp serve\``);
@@ -298,22 +324,16 @@ const serveProfiles = async (self, context, messenger, flags, set) => {
298
324
  const execPath = given.execPath ?? process.execPath;
299
325
  const scriptPath = given.scriptPath ?? realpathSync(process.argv[1] ?? "");
300
326
  const tightened = (flags.permission ?? []).flatMap((entry) => ["--permission", entry]);
301
- const { instructions } = await import("../../mcp/instructions.js");
302
327
  const options = {
303
328
  command: app.command,
304
329
  version: app.version,
305
- name: messenger.name ?? app.command,
330
+ name: host.name,
306
331
  profiles,
307
332
  signedIn: (profile) => state(profile) === "signed-in",
308
333
  signIn: (profile) => `profile ${profile} is not signed in — run \`${app.command} ${profile} setup\`, or sign in from WireCat Desktop, ` +
309
334
  "then try again. If the owner did not choose this profile, ask which one.",
310
335
  child: (profile) => ({ command: execPath, args: [scriptPath, profile, "mcp", "serve", ...tightened], env }),
311
- instructions: instructions({
312
- command: app.command,
313
- name: messenger.name ?? app.command,
314
- profile: profiles(),
315
- writes: [],
316
- }),
336
+ instructions: await host.instructions(profiles()),
317
337
  note: (line) => context.renderer.note(line),
318
338
  };
319
339
  if (flags.http) {