@askthew/mcp-plugin 0.2.8 → 0.4.2

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 (60) hide show
  1. package/README.md +65 -16
  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 +412 -18
  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 +59 -13
  13. package/dist/index.js +1736 -103
  14. package/dist/index.test.d.ts +1 -0
  15. package/dist/index.test.js +952 -0
  16. package/dist/install.d.ts +56 -1
  17. package/dist/install.js +171 -26
  18. package/dist/install.test.d.ts +1 -0
  19. package/dist/install.test.js +297 -0
  20. package/dist/lib/auth-magic-link.d.ts +22 -0
  21. package/dist/lib/auth-magic-link.js +43 -0
  22. package/dist/lib/auth-pending.d.ts +23 -0
  23. package/dist/lib/auth-pending.js +36 -0
  24. package/dist/lib/cli-actions.d.ts +28 -0
  25. package/dist/lib/cli-actions.js +104 -0
  26. package/dist/lib/free-install-registration.d.ts +27 -0
  27. package/dist/lib/free-install-registration.js +52 -0
  28. package/dist/lib/free-tier-policy.d.ts +23 -0
  29. package/dist/lib/free-tier-policy.js +68 -0
  30. package/dist/lib/local-identity.d.ts +44 -0
  31. package/dist/lib/local-identity.js +81 -0
  32. package/dist/lib/local-store.d.ts +130 -0
  33. package/dist/lib/local-store.js +595 -0
  34. package/dist/lib/loopback-auth.d.ts +8 -0
  35. package/dist/lib/loopback-auth.js +30 -0
  36. package/dist/lib/paths.d.ts +9 -0
  37. package/dist/lib/paths.js +50 -0
  38. package/dist/lib/telemetry.d.ts +25 -0
  39. package/dist/lib/telemetry.js +159 -0
  40. package/dist/lib/timeline-insights.d.ts +23 -0
  41. package/dist/lib/timeline-insights.js +115 -0
  42. package/dist/lib/tip-engine.d.ts +18 -0
  43. package/dist/lib/tip-engine.js +237 -0
  44. package/dist/lib/upgrade-nudge.d.ts +19 -0
  45. package/dist/lib/upgrade-nudge.js +37 -0
  46. package/dist/lib/upgrade-sync.d.ts +38 -0
  47. package/dist/lib/upgrade-sync.js +60 -0
  48. package/dist/local-identity.test.d.ts +1 -0
  49. package/dist/local-identity.test.js +29 -0
  50. package/dist/local-store.test.d.ts +1 -0
  51. package/dist/local-store.test.js +71 -0
  52. package/dist/scope.d.ts +1 -2
  53. package/dist/scope.js +56 -8
  54. package/dist/scope.test.d.ts +1 -0
  55. package/dist/scope.test.js +49 -0
  56. package/dist/timeline-insights.test.d.ts +1 -0
  57. package/dist/timeline-insights.test.js +85 -0
  58. package/dist/tip-engine.test.d.ts +1 -0
  59. package/dist/tip-engine.test.js +51 -0
  60. package/package.json +7 -10
package/dist/cli.js CHANGED
@@ -1,14 +1,40 @@
1
1
  #!/usr/bin/env node
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
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";
3
7
  import { createAskTheWMcpServer } from "./index.js";
4
- import { createHostConfigSnippet, formatInstallCommand, installBehaviorInstructions, installHostConfig, sendInstallHeartbeat, } from "./install.js";
8
+ import { clearPendingAuth, pendingAuth, pendingAuthForEmail } from "./lib/auth-pending.js";
9
+ import { verifyMagicLinkCode as verifyMagicLinkCodeDefault, } from "./lib/auth-magic-link.js";
10
+ import { credentialsPath, ensureAskTheWDataDir } from "./lib/paths.js";
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";
14
+ import { LocalStore } from "./lib/local-store.js";
15
+ import { buildTelemetryPayload } from "./lib/telemetry.js";
16
+ import { syncDryRun, uploadLocalStore } from "./lib/upgrade-sync.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";
5
19
  function usage() {
6
20
  return [
7
- "AskTheW Coding Agent Connector",
21
+ "Ask The W Coding Agent Connector",
8
22
  "",
9
23
  "Usage:",
10
24
  " askthew-mcp",
11
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]",
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>]",
32
+ " askthew-mcp auth logout | status",
33
+ " askthew-mcp telemetry status | opt-out | opt-in | preview",
34
+ " askthew-mcp local stats | reset --hard",
35
+ " askthew-mcp install-hook --pre-commit",
36
+ " askthew-mcp digest --weekly",
37
+ " askthew-mcp sync upload [--dry-run]",
12
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>]",
13
39
  ].join("\n");
14
40
  }
@@ -21,6 +47,8 @@ function parseInstallArgs(argv) {
21
47
  let serverName = process.env.ASKTHEW_SERVER_NAME?.trim() || "";
22
48
  let dryRun = false;
23
49
  let installAgentInstructions = true;
50
+ let free = false;
51
+ let email = process.env.ASKTHEW_EMAIL?.trim() || "";
24
52
  for (let index = 0; index < argv.length; index += 1) {
25
53
  const argument = argv[index];
26
54
  if (argument === "--dry-run") {
@@ -31,6 +59,10 @@ function parseInstallArgs(argv) {
31
59
  installAgentInstructions = false;
32
60
  continue;
33
61
  }
62
+ if (argument === "--free") {
63
+ free = true;
64
+ continue;
65
+ }
34
66
  const next = argv[index + 1];
35
67
  if (!next) {
36
68
  throw new Error(`Missing value for ${argument}.`);
@@ -68,19 +100,24 @@ function parseInstallArgs(argv) {
68
100
  index += 1;
69
101
  continue;
70
102
  }
103
+ if (argument === "--email") {
104
+ email = next;
105
+ index += 1;
106
+ continue;
107
+ }
71
108
  throw new Error(`Unknown argument: ${argument}`);
72
109
  }
73
110
  if (!hostType) {
74
111
  throw new Error("Missing required --host argument.");
75
112
  }
76
- if (!token) {
113
+ if (!free && !token) {
77
114
  throw new Error("Missing required --token argument.");
78
115
  }
79
116
  if (!apiUrl) {
80
- throw new Error("Missing required --api-url argument.");
117
+ apiUrl = "https://app.askthew.com";
81
118
  }
82
119
  if (!serverName) {
83
- throw new Error("Missing required --server-name argument.");
120
+ serverName = "askthew";
84
121
  }
85
122
  return {
86
123
  hostType,
@@ -91,11 +128,46 @@ function parseInstallArgs(argv) {
91
128
  serverName,
92
129
  dryRun,
93
130
  installAgentInstructions,
131
+ free,
132
+ email: email || undefined,
94
133
  };
95
134
  }
96
135
  function normalizeInstallToken(token) {
97
136
  return String(token ?? "").trim().replace(/^['"]/, "").replace(/['"]$/, "");
98
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
+ }
99
171
  async function main() {
100
172
  const [command, ...argv] = process.argv.slice(2);
101
173
  if (command === "--help" || command === "-h" || command === "help") {
@@ -110,6 +182,13 @@ async function main() {
110
182
  }
111
183
  if (command === "install") {
112
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
+ }
113
192
  const result = installHostConfig(options);
114
193
  const instructions = options.installAgentInstructions
115
194
  ? installBehaviorInstructions({
@@ -117,19 +196,34 @@ async function main() {
117
196
  dryRun: options.dryRun,
118
197
  })
119
198
  : null;
120
- const heartbeatSent = result.wroteFile
199
+ const heartbeatSent = result.wroteFile && !options.free
121
200
  ? await sendInstallHeartbeat(options).catch(() => false)
122
201
  : false;
123
- console.log(result.wroteFile ? "AskTheW plugin install complete." : "AskTheW plugin dry run complete.");
202
+ console.log(result.wroteFile ? "Ask The W plugin install complete." : "Ask The W plugin dry run complete.");
124
203
  console.log(`Settings path: ${result.settingsPath}`);
125
204
  if (instructions) {
126
- console.log(`Agent instructions: ${instructions.path}`);
205
+ console.log(`Agent instructions: ${instructions.paths?.join(", ") ?? instructions.path}`);
127
206
  }
128
207
  console.log(`Install command: ${formatInstallCommand(options)}`);
129
208
  if (result.wroteFile) {
130
- console.log(heartbeatSent
131
- ? "Ask The W setup check sent. Refresh the app to confirm the plugin shows Installed."
132
- : "Ask The W setup check could not be sent yet. Restart or reload your coding app, then refresh Ask The W.");
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
+ }
222
+ console.log(options.free
223
+ ? "Free local mode installed. Restart or reload your coding app; captures and decisions will write to ~/.askthew/store.sqlite."
224
+ : heartbeatSent
225
+ ? "Ask The W install heartbeat sent. Refresh the app to confirm the plugin shows Installed."
226
+ : "Ask The W install heartbeat could not be sent yet. Restart or reload your coding app, then refresh Ask The W.");
133
227
  }
134
228
  console.log(`Next step: ${result.nextStep}`);
135
229
  if (!result.wroteFile) {
@@ -138,6 +232,53 @@ async function main() {
138
232
  }
139
233
  return;
140
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
+ }
247
+ if (command === "auth") {
248
+ await runAuthCommand(argv);
249
+ return;
250
+ }
251
+ if (command === "telemetry") {
252
+ await runTelemetryCommand(argv);
253
+ return;
254
+ }
255
+ if (command === "local") {
256
+ await runLocalCommand(argv);
257
+ return;
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
+ }
271
+ if (command === "sync") {
272
+ await runSyncCommand(argv);
273
+ return;
274
+ }
275
+ if (command === "upgrade") {
276
+ console.log("Open https://askthew.com/mcp to upgrade, then run `askthew-mcp upgrade --finalize`.");
277
+ if (argv.includes("--finalize")) {
278
+ console.log("Finalize will rewrite host config after a paid workspace token is available in the web app.");
279
+ }
280
+ return;
281
+ }
141
282
  if (command) {
142
283
  throw new Error(`Unknown command "${command}".\n\n${usage()}`);
143
284
  }
@@ -145,12 +286,265 @@ async function main() {
145
286
  const transport = new StdioServerTransport();
146
287
  await server.connect(transport);
147
288
  }
148
- main().catch((error) => {
149
- if (error instanceof Error) {
150
- console.error(error.message);
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
+ }
359
+ function argValue(argv, name) {
360
+ const index = argv.indexOf(name);
361
+ return index >= 0 ? argv[index + 1] : undefined;
362
+ }
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;
367
+ const [subcommand] = argv;
368
+ if (subcommand === "status") {
369
+ const identity = loadLocalIdentity();
370
+ const credentials = loadCliCredentials();
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>\`.`);
378
+ return;
379
+ }
380
+ if (subcommand === "logout") {
381
+ const file = credentialsPath();
382
+ if (fs.existsSync(file))
383
+ fs.rmSync(file);
384
+ log("Logged out of Ask The W local free tier.");
385
+ return;
386
+ }
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>]");
151
389
  }
152
- else {
153
- console.error("AskTheW plugin failed to start.", error);
390
+ ensureAskTheWDataDir();
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;
154
413
  }
155
- process.exit(1);
156
- });
414
+ if (!email)
415
+ throw new Error("Missing --email.");
416
+ const noTelemetry = argv.includes("--no-telemetry");
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",
438
+ });
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.");
456
+ }
457
+ async function runTelemetryCommand(argv) {
458
+ const [subcommand] = argv;
459
+ const credentials = loadCliCredentials();
460
+ if (!credentials)
461
+ throw new Error("No local identity. Run `askthew-mcp identify --email <your-email>` first.");
462
+ if (subcommand === "status") {
463
+ console.log(`Telemetry: ${credentials.telemetryOptOut ? "off" : "on"}`);
464
+ return;
465
+ }
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
+ }
472
+ const next = { ...credentials, telemetryOptOut: subcommand === "opt-out" };
473
+ fs.writeFileSync(credentialsPath(), `${JSON.stringify(next, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
474
+ fs.chmodSync(credentialsPath(), 0o600);
475
+ console.log(`Telemetry: ${next.telemetryOptOut ? "off" : "on"}`);
476
+ return;
477
+ }
478
+ if (subcommand === "preview") {
479
+ const store = LocalStore.open();
480
+ console.log(JSON.stringify(buildTelemetryPayload({ store, credentials }), null, 2));
481
+ return;
482
+ }
483
+ throw new Error("Usage: askthew-mcp telemetry status | opt-out | opt-in | preview");
484
+ }
485
+ async function runLocalCommand(argv) {
486
+ const [subcommand, flag] = argv;
487
+ const store = LocalStore.open();
488
+ if (subcommand === "stats") {
489
+ console.log(JSON.stringify(store.stats(), null, 2));
490
+ return;
491
+ }
492
+ if (subcommand === "reset" && flag === "--hard") {
493
+ const dir = ensureAskTheWDataDir();
494
+ fs.rmSync(dir, { recursive: true, force: true });
495
+ console.log("Local Ask The W data removed.");
496
+ return;
497
+ }
498
+ throw new Error("Usage: askthew-mcp local stats | reset --hard");
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
+ }
526
+ async function runSyncCommand(argv) {
527
+ if (argv[0] !== "upload")
528
+ throw new Error("Usage: askthew-mcp sync upload [--dry-run]");
529
+ const credentials = loadCliCredentials();
530
+ if (!credentials)
531
+ throw new Error("No local identity. Run `askthew-mcp identify --email <your-email>` first.");
532
+ const store = LocalStore.open();
533
+ if (argv.includes("--dry-run")) {
534
+ console.log(JSON.stringify(syncDryRun(store), null, 2));
535
+ return;
536
+ }
537
+ console.log(JSON.stringify(await uploadLocalStore({ store, credentials }), null, 2));
538
+ }
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 {};