@askthew/mcp-plugin 0.4.0 → 0.4.3

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 (45) hide show
  1. package/README.md +24 -13
  2. package/dist/auth-pending.test.d.ts +1 -0
  3. package/dist/auth-pending.test.js +56 -0
  4. package/dist/cli-actions.test.d.ts +1 -0
  5. package/dist/cli-actions.test.js +71 -0
  6. package/dist/cli.d.ts +9 -0
  7. package/dist/cli.js +293 -37
  8. package/dist/cli.test.d.ts +1 -0
  9. package/dist/cli.test.js +274 -0
  10. package/dist/free-tier-policy.test.d.ts +1 -0
  11. package/dist/free-tier-policy.test.js +57 -0
  12. package/dist/index.d.ts +47 -13
  13. package/dist/index.js +1103 -106
  14. package/dist/index.test.js +609 -6
  15. package/dist/install.d.ts +40 -0
  16. package/dist/install.js +155 -18
  17. package/dist/install.test.js +62 -2
  18. package/dist/lib/auth-pending.d.ts +23 -0
  19. package/dist/lib/auth-pending.js +36 -0
  20. package/dist/lib/cli-actions.d.ts +28 -0
  21. package/dist/lib/cli-actions.js +104 -0
  22. package/dist/lib/free-install-registration.d.ts +27 -0
  23. package/dist/lib/free-install-registration.js +52 -0
  24. package/dist/lib/free-tier-policy.d.ts +5 -1
  25. package/dist/lib/free-tier-policy.js +16 -1
  26. package/dist/lib/local-identity.d.ts +44 -0
  27. package/dist/lib/local-identity.js +81 -0
  28. package/dist/lib/local-store.d.ts +33 -2
  29. package/dist/lib/local-store.js +191 -19
  30. package/dist/lib/paths.d.ts +2 -0
  31. package/dist/lib/paths.js +6 -0
  32. package/dist/lib/telemetry.js +28 -2
  33. package/dist/lib/timeline-insights.d.ts +23 -0
  34. package/dist/lib/timeline-insights.js +115 -0
  35. package/dist/lib/upgrade-nudge.d.ts +1 -1
  36. package/dist/lib/upgrade-nudge.js +8 -1
  37. package/dist/local-identity.test.d.ts +1 -0
  38. package/dist/local-identity.test.js +29 -0
  39. package/dist/local-store.test.js +34 -0
  40. package/dist/scope.d.ts +1 -1
  41. package/dist/scope.js +56 -2
  42. package/dist/scope.test.js +17 -0
  43. package/dist/timeline-insights.test.d.ts +1 -0
  44. package/dist/timeline-insights.test.js +85 -0
  45. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -1,14 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
3
  import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { execFileSync } from "node:child_process";
6
+ import { fileURLToPath } from "node:url";
4
7
  import { createAskTheWMcpServer } from "./index.js";
5
- import { requestMagicLinkCode, verifyMagicLinkCode } from "./lib/auth-magic-link.js";
8
+ import { clearPendingAuth, pendingAuth, pendingAuthForEmail } from "./lib/auth-pending.js";
9
+ import { verifyMagicLinkCode as verifyMagicLinkCodeDefault, } from "./lib/auth-magic-link.js";
6
10
  import { credentialsPath, ensureAskTheWDataDir } from "./lib/paths.js";
7
11
  import { loadCliCredentials } from "./lib/free-tier-policy.js";
12
+ import { describeFreeIdentity, tryRegisterFreeInstall } from "./lib/free-install-registration.js";
13
+ import { ensureLocalIdentity, loadLocalIdentity, publicIdentity } from "./lib/local-identity.js";
8
14
  import { LocalStore } from "./lib/local-store.js";
9
15
  import { buildTelemetryPayload } from "./lib/telemetry.js";
10
16
  import { syncDryRun, uploadLocalStore } from "./lib/upgrade-sync.js";
11
- import { createHostConfigSnippet, formatInstallCommand, installBehaviorInstructions, installHostConfig, sendInstallHeartbeat, } from "./install.js";
17
+ import { installPreCommitHook, localScopeKey, preCommitDecisionGap, stagedFiles, writeWeeklyDigest, } from "./lib/cli-actions.js";
18
+ import { createHostConfigSnippet, formatInstallCommand, installBehaviorInstructions, installHostConfig, sendInstallHeartbeat, uninstallBehaviorInstructions, uninstallHostConfig, } from "./install.js";
12
19
  function usage() {
13
20
  return [
14
21
  "Ask The W Coding Agent Connector",
@@ -16,11 +23,17 @@ function usage() {
16
23
  "Usage:",
17
24
  " askthew-mcp",
18
25
  " askthew-mcp install --host <claude_code|codex|cursor> --token <install-token> --api-url <url> --server-name <name> [--client-id <id>] [--client-label <label>] [--dry-run] [--no-agent-instructions]",
19
- " askthew-mcp install --host <claude_code|codex|cursor> --free [--api-url <url>] [--server-name <name>]",
20
- " askthew-mcp auth login --email <email> [--code <code>] [--no-telemetry]",
26
+ " askthew-mcp install --host <claude_code|codex|cursor> --free [--email <email>] [--api-url <url>] [--server-name <name>]",
27
+ " askthew-mcp uninstall --host <claude_code|codex|cursor> [--server-name <name>] [--dry-run] [--keep-local-data] [--keep-auth] [--keep-agent-instructions]",
28
+ " askthew-mcp identify --email <email> [--no-telemetry]",
29
+ " askthew-mcp identity status",
30
+ " askthew-mcp auth login --email <email> [--no-telemetry]",
31
+ " askthew-mcp auth verify --code <code> [--email <email>]",
21
32
  " askthew-mcp auth logout | status",
22
33
  " askthew-mcp telemetry status | opt-out | opt-in | preview",
23
34
  " askthew-mcp local stats | reset --hard",
35
+ " askthew-mcp install-hook --pre-commit",
36
+ " askthew-mcp digest --weekly",
24
37
  " askthew-mcp sync upload [--dry-run]",
25
38
  " askthew-mcp print-config --host <claude_code|codex|cursor> --token <install-token> --api-url <url> --server-name <name> [--client-id <id>] [--client-label <label>]",
26
39
  ].join("\n");
@@ -35,6 +48,7 @@ function parseInstallArgs(argv) {
35
48
  let dryRun = false;
36
49
  let installAgentInstructions = true;
37
50
  let free = false;
51
+ let email = process.env.ASKTHEW_EMAIL?.trim() || "";
38
52
  for (let index = 0; index < argv.length; index += 1) {
39
53
  const argument = argv[index];
40
54
  if (argument === "--dry-run") {
@@ -86,6 +100,11 @@ function parseInstallArgs(argv) {
86
100
  index += 1;
87
101
  continue;
88
102
  }
103
+ if (argument === "--email") {
104
+ email = next;
105
+ index += 1;
106
+ continue;
107
+ }
89
108
  throw new Error(`Unknown argument: ${argument}`);
90
109
  }
91
110
  if (!hostType) {
@@ -110,11 +129,45 @@ function parseInstallArgs(argv) {
110
129
  dryRun,
111
130
  installAgentInstructions,
112
131
  free,
132
+ email: email || undefined,
113
133
  };
114
134
  }
115
135
  function normalizeInstallToken(token) {
116
136
  return String(token ?? "").trim().replace(/^['"]/, "").replace(/['"]$/, "");
117
137
  }
138
+ function detectLoginEmail() {
139
+ for (const value of [
140
+ process.env.ASKTHEW_EMAIL,
141
+ process.env.GIT_AUTHOR_EMAIL,
142
+ process.env.GIT_COMMITTER_EMAIL,
143
+ process.env.EMAIL,
144
+ ]) {
145
+ const email = String(value ?? "").trim();
146
+ if (/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email))
147
+ return email;
148
+ }
149
+ try {
150
+ const email = execFileSync("git", ["config", "user.email"], {
151
+ encoding: "utf8",
152
+ stdio: ["ignore", "pipe", "ignore"],
153
+ }).trim();
154
+ if (/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email))
155
+ return email;
156
+ }
157
+ catch {
158
+ return "";
159
+ }
160
+ return "";
161
+ }
162
+ function loginCommandHint() {
163
+ const email = detectLoginEmail();
164
+ return email
165
+ ? `askthew-mcp identify --email ${email}`
166
+ : "askthew-mcp identify --email <your-email>";
167
+ }
168
+ function installIdentityEmail(optionsEmail) {
169
+ return optionsEmail?.trim() || detectLoginEmail() || undefined;
170
+ }
118
171
  async function main() {
119
172
  const [command, ...argv] = process.argv.slice(2);
120
173
  if (command === "--help" || command === "-h" || command === "help") {
@@ -129,6 +182,13 @@ async function main() {
129
182
  }
130
183
  if (command === "install") {
131
184
  const options = parseInstallArgs(argv);
185
+ let freeIdentity = null;
186
+ if (options.free && !options.dryRun) {
187
+ freeIdentity = ensureLocalIdentity({
188
+ emailClaim: installIdentityEmail(options.email),
189
+ apiUrl: options.apiUrl,
190
+ });
191
+ }
132
192
  const result = installHostConfig(options);
133
193
  const instructions = options.installAgentInstructions
134
194
  ? installBehaviorInstructions({
@@ -142,12 +202,25 @@ async function main() {
142
202
  console.log(result.wroteFile ? "Ask The W plugin install complete." : "Ask The W plugin dry run complete.");
143
203
  console.log(`Settings path: ${result.settingsPath}`);
144
204
  if (instructions) {
145
- console.log(`Agent instructions: ${instructions.path}`);
205
+ console.log(`Agent instructions: ${instructions.paths?.join(", ") ?? instructions.path}`);
146
206
  }
147
207
  console.log(`Install command: ${formatInstallCommand(options)}`);
148
208
  if (result.wroteFile) {
209
+ if (freeIdentity) {
210
+ const registration = await tryRegisterFreeInstall({
211
+ identity: freeIdentity,
212
+ deviceLabel: options.clientLabel ?? `${options.hostType} free install`,
213
+ repo: {
214
+ repoName: process.env.ASKTHEW_REPO_NAME,
215
+ repoRoot: process.env.ASKTHEW_REPO_ROOT,
216
+ hostType: options.hostType,
217
+ },
218
+ options: { apiUrl: options.apiUrl },
219
+ });
220
+ console.log(registration.ok ? "Free install identity registered with Ask The W." : "Free install identity saved locally; cloud registration will retry later.");
221
+ }
149
222
  console.log(options.free
150
- ? "Free local mode installed. Next step: run `askthew-mcp auth login --email you@example.com`."
223
+ ? "Free local mode installed. Restart or reload your coding app; captures and decisions will write to ~/.askthew/store.sqlite."
151
224
  : heartbeatSent
152
225
  ? "Ask The W install heartbeat sent. Refresh the app to confirm the plugin shows Installed."
153
226
  : "Ask The W install heartbeat could not be sent yet. Restart or reload your coding app, then refresh Ask The W.");
@@ -159,6 +232,18 @@ async function main() {
159
232
  }
160
233
  return;
161
234
  }
235
+ if (command === "identify") {
236
+ await runIdentifyCommand(argv);
237
+ return;
238
+ }
239
+ if (command === "identity") {
240
+ await runIdentityCommand(argv);
241
+ return;
242
+ }
243
+ if (command === "uninstall") {
244
+ await runUninstallCommand(argv);
245
+ return;
246
+ }
162
247
  if (command === "auth") {
163
248
  await runAuthCommand(argv);
164
249
  return;
@@ -171,6 +256,18 @@ async function main() {
171
256
  await runLocalCommand(argv);
172
257
  return;
173
258
  }
259
+ if (command === "install-hook") {
260
+ await runInstallHookCommand(argv);
261
+ return;
262
+ }
263
+ if (command === "hook-check") {
264
+ await runHookCheckCommand(argv);
265
+ return;
266
+ }
267
+ if (command === "digest") {
268
+ await runDigestCommand(argv);
269
+ return;
270
+ }
174
271
  if (command === "sync") {
175
272
  await runSyncCommand(argv);
176
273
  return;
@@ -189,59 +286,189 @@ async function main() {
189
286
  const transport = new StdioServerTransport();
190
287
  await server.connect(transport);
191
288
  }
289
+ async function runUninstallCommand(argv) {
290
+ let hostType;
291
+ let serverName = process.env.ASKTHEW_SERVER_NAME?.trim() || "askthew";
292
+ let dryRun = false;
293
+ let keepLocalData = false;
294
+ let keepAuth = false;
295
+ let keepAgentInstructions = false;
296
+ for (let index = 0; index < argv.length; index += 1) {
297
+ const argument = argv[index];
298
+ if (argument === "--dry-run") {
299
+ dryRun = true;
300
+ continue;
301
+ }
302
+ if (argument === "--keep-local-data") {
303
+ keepLocalData = true;
304
+ continue;
305
+ }
306
+ if (argument === "--keep-auth") {
307
+ keepAuth = true;
308
+ continue;
309
+ }
310
+ if (argument === "--keep-agent-instructions") {
311
+ keepAgentInstructions = true;
312
+ continue;
313
+ }
314
+ const next = argv[index + 1];
315
+ if (!next)
316
+ throw new Error(`Missing value for ${argument}.`);
317
+ if (argument === "--host") {
318
+ if (next !== "claude_code" && next !== "codex" && next !== "cursor") {
319
+ throw new Error(`Unsupported host "${next}". Expected claude_code, codex, or cursor.`);
320
+ }
321
+ hostType = next;
322
+ index += 1;
323
+ continue;
324
+ }
325
+ if (argument === "--server-name") {
326
+ serverName = next;
327
+ index += 1;
328
+ continue;
329
+ }
330
+ throw new Error(`Unknown argument: ${argument}`);
331
+ }
332
+ if (!hostType) {
333
+ throw new Error("Usage: askthew-mcp uninstall --host <claude_code|codex|cursor> [--server-name <name>]");
334
+ }
335
+ const config = uninstallHostConfig({ hostType, serverName, dryRun });
336
+ const instructions = keepAgentInstructions
337
+ ? null
338
+ : uninstallBehaviorInstructions({ hostType, dryRun });
339
+ if (!keepLocalData && !dryRun) {
340
+ fs.rmSync(ensureAskTheWDataDir(), { recursive: true, force: true });
341
+ }
342
+ if (!keepAuth && !dryRun) {
343
+ const file = credentialsPath();
344
+ if (fs.existsSync(file))
345
+ fs.rmSync(file, { force: true });
346
+ }
347
+ console.log(dryRun ? "Ask The W plugin uninstall dry run complete." : "Ask The W plugin uninstall complete.");
348
+ console.log(`Settings path: ${config.settingsPath}`);
349
+ if (instructions) {
350
+ console.log(`Agent instructions removed: ${instructions.paths.join(", ") || "none"}`);
351
+ }
352
+ console.log(keepLocalData ? "Local data kept." : "Local data removed.");
353
+ console.log(keepAuth ? "Auth tokens kept." : "Auth tokens removed.");
354
+ if (dryRun) {
355
+ console.log("");
356
+ console.log(config.json);
357
+ }
358
+ }
192
359
  function argValue(argv, name) {
193
360
  const index = argv.indexOf(name);
194
361
  return index >= 0 ? argv[index + 1] : undefined;
195
362
  }
196
- async function runAuthCommand(argv) {
363
+ export async function runAuthCommand(argv, deps = {}) {
364
+ const log = deps.log ?? console.log;
365
+ const verifyCode = deps.verifyMagicLinkCode ?? verifyMagicLinkCodeDefault;
366
+ const registerInstall = deps.registerFreeInstall ?? tryRegisterFreeInstall;
197
367
  const [subcommand] = argv;
198
368
  if (subcommand === "status") {
369
+ const identity = loadLocalIdentity();
199
370
  const credentials = loadCliCredentials();
200
- console.log(credentials
201
- ? `Logged in as ${credentials.email ?? credentials.userId}. Telemetry: ${credentials.telemetryOptOut ? "off" : "on"}`
202
- : "Not logged in. Run `askthew-mcp auth login --email you@example.com`.");
371
+ log(identity
372
+ ? `Identified local free install ${identity.installId}${identity.emailClaim ? ` with email claim ${identity.emailClaim}` : ""}. Email claim is unverified until upgrade.`
373
+ : credentials
374
+ ? `Logged in as ${credentials.email ?? credentials.userId}. Telemetry: ${credentials.telemetryOptOut ? "off" : "on"}`
375
+ : pendingAuth()
376
+ ? `Not logged in. Pending code for ${pendingAuth()?.email}. Run \`askthew-mcp auth verify --code <6-digit-code>\`.`
377
+ : `No local identity yet. Run \`askthew-mcp identify --email <your-email>\`, or install with \`--free --email <your-email>\`.`);
203
378
  return;
204
379
  }
205
380
  if (subcommand === "logout") {
206
381
  const file = credentialsPath();
207
382
  if (fs.existsSync(file))
208
383
  fs.rmSync(file);
209
- console.log("Logged out of Ask The W local free tier.");
384
+ log("Logged out of Ask The W local free tier.");
210
385
  return;
211
386
  }
212
- if (subcommand !== "login") {
213
- throw new Error("Usage: askthew-mcp auth login --email <email> [--code <code>] [--no-telemetry]");
387
+ if (subcommand !== "login" && subcommand !== "verify") {
388
+ throw new Error("Usage: askthew-mcp auth login --email <email> [--no-telemetry] | askthew-mcp auth verify --code <code> [--email <email>]");
214
389
  }
215
390
  ensureAskTheWDataDir();
216
391
  const email = argValue(argv, "--email")?.trim();
392
+ const code = argValue(argv, "--code")?.trim();
393
+ if (subcommand === "verify" || code) {
394
+ if (!code)
395
+ throw new Error("Missing --code.");
396
+ const pending = email ? pendingAuthForEmail(email) : pendingAuth();
397
+ if (!pending) {
398
+ throw new Error(email
399
+ ? `No pending Ask The W login request for ${email}. Run \`askthew-mcp auth login --email ${email}\` first.`
400
+ : "No pending Ask The W login request. Run `askthew-mcp auth login --email <email>` first.");
401
+ }
402
+ if (subcommand === "login") {
403
+ log("Using the pending Ask The W login request. Next time, run `askthew-mcp auth verify --code <6-digit-code>`.");
404
+ }
405
+ const credentials = await verifyCode({
406
+ requestId: pending.requestId,
407
+ code,
408
+ telemetryOptOut: pending.telemetryOptOut,
409
+ });
410
+ clearPendingAuth();
411
+ log(`Logged in. Account status: ${credentials.accountStatus}. Credentials stored with mode 0600.`);
412
+ return;
413
+ }
217
414
  if (!email)
218
415
  throw new Error("Missing --email.");
219
416
  const noTelemetry = argv.includes("--no-telemetry");
220
- const requested = await requestMagicLinkCode({ email, deviceLabel: "askthew-mcp" });
221
- console.log(`Code sent to ${email}. It expires at ${requested.expiresAt}.`);
222
- if (requested.devCode)
223
- console.log(`Dev code: ${requested.devCode}`);
224
- const code = argValue(argv, "--code")?.trim() ?? requested.devCode;
225
- if (!code) {
226
- throw new Error("Re-run with --code <6-digit-code> after checking your email.");
227
- }
228
- const credentials = await verifyMagicLinkCode({
229
- requestId: requested.requestId,
230
- code,
231
- telemetryOptOut: noTelemetry,
417
+ const identity = ensureLocalIdentity({ emailClaim: email, telemetryOptOut: noTelemetry });
418
+ const registration = await registerInstall({
419
+ identity,
420
+ deviceLabel: "askthew-mcp",
421
+ });
422
+ log(`Local free install identified as ${identity.installId}.`);
423
+ log(`Email claim: ${identity.emailClaim ?? "none"} (unverified until upgrade).`);
424
+ log(`Claim code: ${identity.claimCode}`);
425
+ log(registration.ok ? "Registered install with Ask The W." : "Saved locally; cloud registration will retry later.");
426
+ log("No email code is required for free local capture.");
427
+ return;
428
+ }
429
+ async function runIdentifyCommand(argv) {
430
+ const email = argValue(argv, "--email")?.trim() || detectLoginEmail();
431
+ const noTelemetry = argv.includes("--no-telemetry");
432
+ if (!email)
433
+ throw new Error("Missing --email.");
434
+ const identity = ensureLocalIdentity({ emailClaim: email, telemetryOptOut: noTelemetry });
435
+ const registration = await tryRegisterFreeInstall({
436
+ identity,
437
+ deviceLabel: "askthew-mcp",
232
438
  });
233
- console.log(`Logged in. Account status: ${credentials.accountStatus}. Credentials stored with mode 0600.`);
439
+ console.log(`Local free install identified as ${identity.installId}.`);
440
+ console.log(`Email claim: ${identity.emailClaim ?? "none"} (unverified until upgrade).`);
441
+ console.log(`Claim code: ${identity.claimCode}`);
442
+ console.log(registration.ok ? "Registered install with Ask The W." : "Saved locally; cloud registration will retry later.");
443
+ }
444
+ async function runIdentityCommand(argv) {
445
+ const [subcommand] = argv;
446
+ if (subcommand !== "status") {
447
+ throw new Error("Usage: askthew-mcp identity status");
448
+ }
449
+ const identity = loadLocalIdentity();
450
+ if (!identity) {
451
+ console.log("No local free install identity yet.");
452
+ return;
453
+ }
454
+ console.log(describeFreeIdentity(publicIdentity(identity)));
455
+ console.log("Email claims are attribution only; upgrade/import requires local possession proof.");
234
456
  }
235
457
  async function runTelemetryCommand(argv) {
236
458
  const [subcommand] = argv;
237
459
  const credentials = loadCliCredentials();
238
460
  if (!credentials)
239
- throw new Error("Not logged in. Run `askthew-mcp auth login` first.");
461
+ throw new Error("No local identity. Run `askthew-mcp identify --email <your-email>` first.");
240
462
  if (subcommand === "status") {
241
463
  console.log(`Telemetry: ${credentials.telemetryOptOut ? "off" : "on"}`);
242
464
  return;
243
465
  }
244
466
  if (subcommand === "opt-out" || subcommand === "opt-in") {
467
+ if (credentials.identityKind === "local_install" && credentials.localIdentity) {
468
+ ensureLocalIdentity({ telemetryOptOut: subcommand === "opt-out" });
469
+ console.log(`Telemetry: ${subcommand === "opt-out" ? "off" : "on"}`);
470
+ return;
471
+ }
245
472
  const next = { ...credentials, telemetryOptOut: subcommand === "opt-out" };
246
473
  fs.writeFileSync(credentialsPath(), `${JSON.stringify(next, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
247
474
  fs.chmodSync(credentialsPath(), 0o600);
@@ -270,12 +497,38 @@ async function runLocalCommand(argv) {
270
497
  }
271
498
  throw new Error("Usage: askthew-mcp local stats | reset --hard");
272
499
  }
500
+ async function runInstallHookCommand(argv) {
501
+ if (!argv.includes("--pre-commit")) {
502
+ throw new Error("Usage: askthew-mcp install-hook --pre-commit");
503
+ }
504
+ const hookPath = installPreCommitHook();
505
+ console.log(`Ask The W pre-commit hook installed: ${hookPath}`);
506
+ }
507
+ async function runHookCheckCommand(argv) {
508
+ if (!argv.includes("--pre-commit")) {
509
+ throw new Error("Usage: askthew-mcp hook-check --pre-commit");
510
+ }
511
+ const store = LocalStore.open();
512
+ const gap = preCommitDecisionGap({ store, stagedFiles: stagedFiles(), scopeKey: localScopeKey() });
513
+ if (gap.missing) {
514
+ console.log('Ask The W: this change has no decision attached, draft one?');
515
+ console.log("Run: npx @askthew/mcp-plugin digest --weekly or ask your agent to call promote_signal_to_decision.");
516
+ }
517
+ }
518
+ async function runDigestCommand(argv) {
519
+ if (!argv.includes("--weekly")) {
520
+ throw new Error("Usage: askthew-mcp digest --weekly");
521
+ }
522
+ const store = LocalStore.open();
523
+ const filePath = writeWeeklyDigest({ store });
524
+ console.log(`Weekly decision digest written: ${filePath}`);
525
+ }
273
526
  async function runSyncCommand(argv) {
274
527
  if (argv[0] !== "upload")
275
528
  throw new Error("Usage: askthew-mcp sync upload [--dry-run]");
276
529
  const credentials = loadCliCredentials();
277
530
  if (!credentials)
278
- throw new Error("Not logged in. Run `askthew-mcp auth login` first.");
531
+ throw new Error("No local identity. Run `askthew-mcp identify --email <your-email>` first.");
279
532
  const store = LocalStore.open();
280
533
  if (argv.includes("--dry-run")) {
281
534
  console.log(JSON.stringify(syncDryRun(store), null, 2));
@@ -283,12 +536,15 @@ async function runSyncCommand(argv) {
283
536
  }
284
537
  console.log(JSON.stringify(await uploadLocalStore({ store, credentials }), null, 2));
285
538
  }
286
- main().catch((error) => {
287
- if (error instanceof Error) {
288
- console.error(error.message);
289
- }
290
- else {
291
- console.error("Ask The W plugin failed to start.", error);
292
- }
293
- process.exit(1);
294
- });
539
+ const isDirectCliExecution = Boolean(process.argv[1]) && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
540
+ if (isDirectCliExecution) {
541
+ main().catch((error) => {
542
+ if (error instanceof Error) {
543
+ console.error(error.message);
544
+ }
545
+ else {
546
+ console.error("Ask The W plugin failed to start.", error);
547
+ }
548
+ process.exit(1);
549
+ });
550
+ }
@@ -0,0 +1 @@
1
+ export {};