@askthew/mcp-plugin 0.4.5 → 0.4.7

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/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Connect a local coding agent to Ask The W. The fastest path is free and local-first:
4
4
 
5
5
  ```bash
6
- npx -y --prefer-online @askthew/mcp-plugin@latest install --host claude_code --free --email you@founder.com
6
+ npx -y --prefer-online --package @askthew/mcp-plugin@latest askthew-mcp install --host claude_code --free --email you@founder.com
7
7
  ```
8
8
 
9
9
  This captures decisions and session signals to `~/.askthew/store.sqlite` and lets your agent run `review_decisions`, `review_session`, `recap`, `coach`, and `promote_signal_to_decision` without onboarding into the web app.
@@ -35,7 +35,7 @@ Ask The W performs inference, linking, approval state, dedupe, and outcome updat
35
35
  ## Free Local Install
36
36
 
37
37
  ```bash
38
- npx -y --prefer-online @askthew/mcp-plugin@latest install \
38
+ npx -y --prefer-online --package @askthew/mcp-plugin@latest askthew-mcp install \
39
39
  --host claude_code \
40
40
  --free \
41
41
  --email you@founder.com
@@ -50,7 +50,7 @@ Telemetry is aggregate-only and opt-out. Ask The W does not receive code, file c
50
50
  Use `refresh` when you want the latest installed MCP config and instruction blocks without touching local identity or local data:
51
51
 
52
52
  ```bash
53
- npx -y --prefer-online @askthew/mcp-plugin@latest refresh --host claude_code
53
+ npx -y --prefer-online --package @askthew/mcp-plugin@latest askthew-mcp refresh --host claude_code
54
54
  ```
55
55
 
56
56
  `refresh` preserves `~/.askthew/identity.json` and `~/.askthew/store.sqlite`, reuses any existing email claim silently, rewrites the host MCP config, and rewrites the marked Ask The W blocks in `CLAUDE.md` and `AGENTS.md`. This is the recommended QA update path after a plugin publish.
@@ -72,7 +72,7 @@ Use setup tokens promptly. They expire after 24 hours if the connector never sen
72
72
  Codex:
73
73
 
74
74
  ```bash
75
- npx -y --prefer-online @askthew/mcp-plugin@latest install \
75
+ npx -y --prefer-online --package @askthew/mcp-plugin@latest askthew-mcp install \
76
76
  --host codex \
77
77
  --token "<ASKTHEW_INSTALL_TOKEN>" \
78
78
  --api-url "https://app.askthew.com/" \
@@ -82,7 +82,7 @@ npx -y --prefer-online @askthew/mcp-plugin@latest install \
82
82
  Claude Code:
83
83
 
84
84
  ```bash
85
- npx -y --prefer-online @askthew/mcp-plugin@latest install \
85
+ npx -y --prefer-online --package @askthew/mcp-plugin@latest askthew-mcp install \
86
86
  --host claude_code \
87
87
  --token "<ASKTHEW_INSTALL_TOKEN>" \
88
88
  --api-url "https://app.askthew.com/" \
@@ -92,7 +92,7 @@ npx -y --prefer-online @askthew/mcp-plugin@latest install \
92
92
  Cursor:
93
93
 
94
94
  ```bash
95
- npx -y --prefer-online @askthew/mcp-plugin@latest install \
95
+ npx -y --prefer-online --package @askthew/mcp-plugin@latest askthew-mcp install \
96
96
  --host cursor \
97
97
  --token "<ASKTHEW_INSTALL_TOKEN>" \
98
98
  --api-url "https://app.askthew.com/" \
@@ -130,7 +130,7 @@ Codex example:
130
130
  ```toml
131
131
  [mcp_servers.askthew]
132
132
  command = "npx"
133
- args = ["-y", "--prefer-online", "@askthew/mcp-plugin@latest"]
133
+ args = ["-y", "--prefer-online", "--package", "@askthew/mcp-plugin@latest", "askthew-mcp"]
134
134
  env = { ASKTHEW_INSTALL_TOKEN = "<ASKTHEW_INSTALL_TOKEN>", ASKTHEW_HOST_TYPE = "codex", ASKTHEW_API_URL = "https://app.askthew.com/", ASKTHEW_SERVER_NAME = "askthew" }
135
135
  ```
136
136
 
@@ -194,8 +194,8 @@ The plugin also exposes v1 API tools that map to the app's authenticated routes:
194
194
  Free PLG helpers:
195
195
 
196
196
  ```bash
197
- npx @askthew/mcp-plugin install-hook --pre-commit
198
- npx @askthew/mcp-plugin digest --weekly
197
+ npx -y --prefer-online --package @askthew/mcp-plugin@latest askthew-mcp install-hook --pre-commit
198
+ npx -y --prefer-online --package @askthew/mcp-plugin@latest askthew-mcp digest --weekly
199
199
  ```
200
200
 
201
201
  The hook prompts when staged files recently had implementation signals but no linked decision. The weekly digest writes `~/Documents/askthew-digest-YYYY-WW.md`.
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import path from "node:path";
5
5
  import { execFileSync } from "node:child_process";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { createAskTheWMcpServer } from "./index.js";
8
- import { ensureAskTheWDataDir, identityPath } from "./lib/paths.js";
8
+ import { askTheWDataDir, ensureAskTheWDataDir, identityPath } from "./lib/paths.js";
9
9
  import { loadCliCredentials } from "./lib/free-tier-policy.js";
10
10
  import { describeFreeIdentity, tryRegisterFreeInstall } from "./lib/free-install-registration.js";
11
11
  import { ensureLocalIdentity, loadLocalIdentity, publicIdentity } from "./lib/local-identity.js";
@@ -348,21 +348,39 @@ async function runUninstallCommand(argv) {
348
348
  const instructions = keepAgentInstructions
349
349
  ? null
350
350
  : uninstallBehaviorInstructions({ hostType, dryRun });
351
+ const dataDir = askTheWDataDir();
352
+ const hadLocalData = fs.existsSync(dataDir);
353
+ const authFile = identityPath();
354
+ const hadAuth = fs.existsSync(authFile);
351
355
  if (!keepLocalData && !dryRun) {
352
- fs.rmSync(ensureAskTheWDataDir(), { recursive: true, force: true });
356
+ fs.rmSync(dataDir, { recursive: true, force: true });
353
357
  }
354
358
  if (!keepAuth && !dryRun) {
355
- const file = identityPath();
356
- if (fs.existsSync(file))
357
- fs.rmSync(file, { force: true });
359
+ if (fs.existsSync(authFile))
360
+ fs.rmSync(authFile, { force: true });
358
361
  }
359
362
  console.log(dryRun ? "Ask The W plugin uninstall dry run complete." : "Ask The W plugin uninstall complete.");
360
363
  console.log(`Settings path: ${config.settingsPath}`);
364
+ console.log(config.removedServer
365
+ ? `${dryRun ? "Would remove" : "Removed"} MCP server "${config.removedServerName}".`
366
+ : config.foundConfigFile
367
+ ? `No MCP server "${config.removedServerName}" found in host config.`
368
+ : "No host config file found.");
361
369
  if (instructions) {
362
- console.log(`Agent instructions removed: ${instructions.paths.join(", ") || "none"}`);
363
- }
364
- console.log(keepLocalData ? "Local data kept." : "Local data removed.");
365
- console.log(keepAuth ? "Auth tokens kept." : "Auth tokens removed.");
370
+ console.log(instructions.paths.length > 0
371
+ ? `Agent instructions ${dryRun ? "would be removed from" : "removed from"}: ${instructions.paths.join(", ")}`
372
+ : "No Ask The W agent instruction blocks found.");
373
+ }
374
+ console.log(keepLocalData
375
+ ? "Local data kept."
376
+ : hadLocalData
377
+ ? `${dryRun ? "Local data would be removed" : "Local data removed"}.`
378
+ : "No local data directory found.");
379
+ console.log(keepAuth
380
+ ? "Auth tokens kept."
381
+ : hadAuth
382
+ ? `${dryRun ? "Local identity would be removed" : "Local identity removed"}.`
383
+ : "No local identity found.");
366
384
  if (dryRun) {
367
385
  console.log("");
368
386
  console.log(config.json);
@@ -559,7 +577,7 @@ async function runHookCheckCommand(argv) {
559
577
  const gap = preCommitDecisionGap({ store, stagedFiles: stagedFiles(), scopeKey: localScopeKey() });
560
578
  if (gap.missing) {
561
579
  console.log('Ask The W: this change has no decision attached, draft one?');
562
- console.log("Run: npx @askthew/mcp-plugin digest --weekly or ask your agent to call promote_signal_to_decision.");
580
+ console.log("Run: npx -y --prefer-online --package @askthew/mcp-plugin@latest askthew-mcp digest --weekly or ask your agent to call promote_signal_to_decision.");
563
581
  }
564
582
  }
565
583
  async function runDigestCommand(argv) {
@@ -587,7 +605,17 @@ async function runSyncCommand(argv) {
587
605
  }
588
606
  console.log(JSON.stringify(await uploadLocalStore({ store, credentials, syncToken }), null, 2));
589
607
  }
590
- const isDirectCliExecution = Boolean(process.argv[1]) && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
608
+ const isDirectCliExecution = Boolean(process.argv[1]) &&
609
+ (() => {
610
+ const cliPath = fileURLToPath(import.meta.url);
611
+ const invokedPath = path.resolve(process.argv[1]);
612
+ try {
613
+ return fs.realpathSync(invokedPath) === fs.realpathSync(cliPath);
614
+ }
615
+ catch {
616
+ return invokedPath === cliPath;
617
+ }
618
+ })();
591
619
  if (isDirectCliExecution) {
592
620
  main().catch((error) => {
593
621
  if (error instanceof Error) {
package/dist/index.js CHANGED
@@ -1,4 +1,8 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
2
6
  import { z } from "zod";
3
7
  import { resolvePluginScope } from "./scope.js";
4
8
  import { resolveMcpMode } from "./lib/free-tier-policy.js";
@@ -482,10 +486,10 @@ function loginCommandHint() {
482
486
  ]) {
483
487
  const email = String(value ?? "").trim();
484
488
  if (/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) {
485
- return `npx @askthew/mcp-plugin identify --email ${email}`;
489
+ return `npx -y --prefer-online --package @askthew/mcp-plugin@latest askthew-mcp identify --email ${email}`;
486
490
  }
487
491
  }
488
- return "npx @askthew/mcp-plugin identify --email <your-email>";
492
+ return "npx -y --prefer-online --package @askthew/mcp-plugin@latest askthew-mcp identify --email <your-email>";
489
493
  }
490
494
  async function sendStartupHeartbeat(options) {
491
495
  if (!hasServerIdentity(options?.credentials)) {
@@ -565,7 +569,7 @@ export function createAskTheWMcpServer(options = {}) {
565
569
  }
566
570
  const server = new McpServer({
567
571
  name: "Ask The W Coding Agent Connector",
568
- version: "0.4.0",
572
+ version: "0.4.7",
569
573
  });
570
574
  if (options.sendStartupHeartbeat !== false && mode.mode === "paid") {
571
575
  void sendStartupSignals(options);
@@ -1899,3 +1903,27 @@ function searchTrail(input) {
1899
1903
  nextCursor: matches.length >= limit ? matches.at(-1)?.createdAt ?? null : null,
1900
1904
  };
1901
1905
  }
1906
+ const isDirectIndexExecution = Boolean(process.argv[1]) &&
1907
+ (() => {
1908
+ const modulePath = fileURLToPath(import.meta.url);
1909
+ const invokedPath = path.resolve(process.argv[1]);
1910
+ try {
1911
+ return fs.realpathSync(invokedPath) === fs.realpathSync(modulePath);
1912
+ }
1913
+ catch {
1914
+ return invokedPath === modulePath;
1915
+ }
1916
+ })();
1917
+ if (isDirectIndexExecution) {
1918
+ const server = createAskTheWMcpServer();
1919
+ const transport = new StdioServerTransport();
1920
+ server.connect(transport).catch((error) => {
1921
+ if (error instanceof Error) {
1922
+ console.error(error.message);
1923
+ }
1924
+ else {
1925
+ console.error("Ask The W MCP server failed to start.", error);
1926
+ }
1927
+ process.exit(1);
1928
+ });
1929
+ }
package/dist/install.d.ts CHANGED
@@ -111,6 +111,8 @@ export declare function uninstallHostConfig(input: UninstallHostConfigInput): {
111
111
  settingsPath: string;
112
112
  json: string;
113
113
  removedServerName: string;
114
+ foundConfigFile: boolean;
115
+ removedServer: boolean;
114
116
  wroteFile: boolean;
115
117
  };
116
118
  export declare function sendInstallHeartbeat(input: HostConfigInput & {
package/dist/install.js CHANGED
@@ -21,7 +21,7 @@ export function createServerEntry(input) {
21
21
  const scope = resolvePluginScope(input.cwd ?? process.cwd());
22
22
  return {
23
23
  command: "npx",
24
- args: ["-y", "--prefer-online", "@askthew/mcp-plugin@latest"],
24
+ args: ["-y", "--prefer-online", "--package", "@askthew/mcp-plugin@latest", "askthew-mcp"],
25
25
  env: {
26
26
  ASKTHEW_API_URL: input.apiUrl,
27
27
  ...(input.free ? { ASKTHEW_FREE_MODE: "1" } : { ASKTHEW_INSTALL_TOKEN: input.token ?? "" }),
@@ -133,24 +133,23 @@ export function formatInstallCommand(input) {
133
133
  "npx",
134
134
  "-y",
135
135
  "--prefer-online",
136
+ "--package",
136
137
  "@askthew/mcp-plugin@latest",
138
+ "askthew-mcp",
137
139
  "install",
138
140
  "--host",
139
141
  input.hostType,
142
+ ...(input.free ? ["--free"] : ["--token", JSON.stringify(input.token ?? "")]),
140
143
  "--api-url",
141
144
  JSON.stringify(input.apiUrl),
142
145
  "--server-name",
143
146
  JSON.stringify(input.serverName),
144
147
  ];
145
148
  if (input.free) {
146
- parts.push("--free");
147
149
  if (input.email) {
148
150
  parts.push("--email", JSON.stringify(input.email));
149
151
  }
150
152
  }
151
- else {
152
- parts.splice(7, 0, "--token", JSON.stringify(input.token ?? ""));
153
- }
154
153
  return parts.join(" ");
155
154
  }
156
155
  export function verificationNextStep(hostType) {
@@ -219,9 +218,13 @@ export function uninstallHostConfig(input) {
219
218
  });
220
219
  const serverName = input.serverName?.trim() || "askthew";
221
220
  let json = "";
221
+ let foundConfigFile = false;
222
+ let removedServer = false;
222
223
  if (fs.existsSync(settingsPath)) {
224
+ foundConfigFile = true;
223
225
  const raw = fs.readFileSync(settingsPath, "utf8");
224
226
  if (input.hostType === "codex") {
227
+ removedServer = raw !== removeCodexTomlServer(raw, serverName) || (serverName !== "askthew" && raw !== removeCodexTomlServer(raw, "askthew"));
225
228
  json = removeCodexTomlServer(raw, serverName);
226
229
  if (serverName !== "askthew") {
227
230
  json = removeCodexTomlServer(json, "askthew");
@@ -236,6 +239,7 @@ export function uninstallHostConfig(input) {
236
239
  const existingProject = isRecord(existingProjects[cwd]) ? existingProjects[cwd] : {};
237
240
  const existingMcpServers = isRecord(existingProject.mcpServers) ? existingProject.mcpServers : {};
238
241
  const nextServers = { ...existingMcpServers };
242
+ removedServer = serverName in nextServers || (serverName !== "askthew" && "askthew" in nextServers);
239
243
  delete nextServers[serverName];
240
244
  if (serverName !== "askthew")
241
245
  delete nextServers.askthew;
@@ -253,6 +257,7 @@ export function uninstallHostConfig(input) {
253
257
  else {
254
258
  const existingMcpServers = isRecord(parsed.mcpServers) ? parsed.mcpServers : {};
255
259
  const nextServers = { ...existingMcpServers };
260
+ removedServer = serverName in nextServers || (serverName !== "askthew" && "askthew" in nextServers);
256
261
  delete nextServers[serverName];
257
262
  if (serverName !== "askthew")
258
263
  delete nextServers.askthew;
@@ -268,7 +273,9 @@ export function uninstallHostConfig(input) {
268
273
  settingsPath,
269
274
  json,
270
275
  removedServerName: serverName,
271
- wroteFile: !input.dryRun,
276
+ foundConfigFile,
277
+ removedServer,
278
+ wroteFile: !input.dryRun && foundConfigFile,
272
279
  };
273
280
  }
274
281
  export async function sendInstallHeartbeat(input) {
@@ -22,7 +22,7 @@ export function installPreCommitHook(input = {}) {
22
22
  const hook = [
23
23
  "#!/bin/sh",
24
24
  "# Ask The W pre-commit decision prompt",
25
- "npx -y --prefer-online @askthew/mcp-plugin@latest hook-check --pre-commit",
25
+ "npx -y --prefer-online --package @askthew/mcp-plugin@latest askthew-mcp hook-check --pre-commit",
26
26
  "",
27
27
  ].join("\n");
28
28
  fs.mkdirSync(path.dirname(hookPath), { recursive: true });
@@ -27,7 +27,7 @@ export function buildTelemetryPayload(input) {
27
27
  decisions: decisionStats(decisions),
28
28
  tools: input.toolUsage ?? {},
29
29
  plugin: {
30
- version: "0.4.0",
30
+ version: "0.4.7",
31
31
  platform: `${process.platform}-${process.arch}`,
32
32
  node: process.version.replace(/^v/, ""),
33
33
  },
@@ -22,7 +22,7 @@ export function paidFeatureNudge(tool) {
22
22
  pricingUrl: PRICING_URL,
23
23
  upgradeUrl: `https://askthew.com/plugin?utm_source=mcp-plugin&utm_medium=tool-nudge&utm_campaign=mcp-free&tool=${encodeURIComponent(tool)}`,
24
24
  supportEmail: SUPPORT_EMAIL,
25
- cta: "Run: npx @askthew/mcp-plugin upgrade",
25
+ cta: "Run: npx -y --prefer-online --package @askthew/mcp-plugin@latest askthew-mcp upgrade",
26
26
  };
27
27
  }
28
28
  export function toolJson(value) {
package/package.json CHANGED
@@ -1,18 +1,21 @@
1
1
  {
2
2
  "name": "@askthew/mcp-plugin",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "private": false,
5
5
  "description": "Ask The W plugin connector for local-first coding-agent decisions, signals, and review.",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
9
9
  "bin": {
10
- "askthew-mcp": "dist/cli.js"
10
+ "askthew-mcp": "dist/cli.js",
11
+ "mcp-plugin": "dist/cli.js"
11
12
  },
12
13
  "files": [
13
14
  "README.md",
14
15
  "dist/**/*.d.ts",
15
- "dist/**/*.js"
16
+ "dist/**/*.js",
17
+ "!dist/**/*.test.d.ts",
18
+ "!dist/**/*.test.js"
16
19
  ],
17
20
  "keywords": [
18
21
  "askthew",
@@ -1 +0,0 @@
1
- export {};
@@ -1,71 +0,0 @@
1
- import test from "node:test";
2
- import assert from "node:assert/strict";
3
- import { execFileSync } from "node:child_process";
4
- import fs from "node:fs";
5
- import os from "node:os";
6
- import path from "node:path";
7
- import { LocalStore } from "./lib/local-store.js";
8
- import { buildWeeklyDigest, installPreCommitHook, preCommitDecisionGap, writeWeeklyDigest, } from "./lib/cli-actions.js";
9
- test("pre-commit gap detects staged implementation updates without linked decisions", () => {
10
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-hook-store-"));
11
- const store = LocalStore.open({ path: path.join(dir, "store.sqlite") });
12
- const signal = store.insertSignal({
13
- sessionId: "s1",
14
- sequence: 1,
15
- kind: "implementation_update",
16
- summary: "Changed onboarding page.",
17
- filesTouched: ["apps/app/page.tsx"],
18
- commandsRun: [],
19
- });
20
- const gap = preCommitDecisionGap({
21
- store,
22
- stagedFiles: ["apps/app/page.tsx"],
23
- now: new Date(signal.capturedAt),
24
- });
25
- assert.equal(gap.missing, true);
26
- assert.deepEqual(gap.matchedSignals, [signal.id]);
27
- store.createDecision({
28
- rawContent: "Keep onboarding page copy direct.",
29
- sessionId: "s1",
30
- sourceSignalIds: [signal.id],
31
- });
32
- const resolved = preCommitDecisionGap({
33
- store,
34
- stagedFiles: ["apps/app/page.tsx"],
35
- now: new Date(signal.capturedAt),
36
- });
37
- assert.equal(resolved.missing, false);
38
- store.close();
39
- fs.rmSync(dir, { recursive: true, force: true });
40
- });
41
- test("pre-commit hook installer writes the documented inline prompt hook", () => {
42
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-hook-install-"));
43
- execFileSync("git", ["init"], { cwd: dir, stdio: "ignore" });
44
- const hookPath = installPreCommitHook({ cwd: dir });
45
- const hook = fs.readFileSync(hookPath, "utf8");
46
- assert.match(hook, /Ask The W pre-commit decision prompt/);
47
- assert.match(hook, /@askthew\/mcp-plugin@latest hook-check --pre-commit/);
48
- assert.equal((fs.statSync(hookPath).mode & 0o111) > 0, true);
49
- fs.rmSync(dir, { recursive: true, force: true });
50
- });
51
- test("weekly digest writes markdown with documented footer", () => {
52
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-digest-store-"));
53
- const outDir = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-digest-out-"));
54
- const store = LocalStore.open({ path: path.join(dir, "store.sqlite") });
55
- store.createDecision({
56
- rawContent: "Ship the free-tier polish.",
57
- sessionId: "s1",
58
- status: "committed",
59
- });
60
- const now = new Date("2026-05-06T12:00:00.000Z");
61
- const digest = buildWeeklyDigest({ store, now });
62
- assert.match(digest, /# Ask The W Weekly Decision Digest 2026-19/);
63
- assert.match(digest, /Ship the free-tier polish/);
64
- assert.match(digest, /_Captured by Ask The W\._/);
65
- const filePath = writeWeeklyDigest({ store, now, outputDir: outDir });
66
- assert.equal(path.basename(filePath), "askthew-digest-2026-19.md");
67
- assert.match(fs.readFileSync(filePath, "utf8"), /_Captured by Ask The W\._/);
68
- store.close();
69
- fs.rmSync(dir, { recursive: true, force: true });
70
- fs.rmSync(outDir, { recursive: true, force: true });
71
- });
@@ -1 +0,0 @@
1
- export {};