@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/README.md CHANGED
@@ -1,21 +1,20 @@
1
- # Ask The W MCP Plugin
1
+ # Ask The W Plugin
2
2
 
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
7
- npx @askthew/mcp-plugin auth login --email you@founder.com
6
+ npx -y --prefer-online @askthew/mcp-plugin@latest install --host claude_code --free --email you@founder.com
8
7
  ```
9
8
 
10
- This captures decisions and session signals to `~/.askthew/store.sqlite` and lets your agent run `review_decisions`, `review_session`, and `analyze_session` without onboarding into the web app.
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.
11
10
 
12
- Founder-friendly promise: install from npm, magic-link login, then ask your coding agent to review the last session. You should see value in under 60 seconds.
11
+ Founder-friendly promise: install from npm, then ask your coding agent to review the last session. You should see value in under 60 seconds.
13
12
 
14
13
  This package runs a small MCP server that lets Codex, Claude Code, Cursor, and other MCP-capable tools send compact work-session signals to an Ask The W workspace.
15
14
 
16
15
  ## What It Does
17
16
 
18
- - Installs an Ask The W MCP server entry into a supported local client.
17
+ - Installs an Ask The W plugin server entry into a supported local client.
19
18
  - Preserves existing MCP servers and settings.
20
19
  - Adds marked project instructions so future coding-agent sessions know when to send Ask The W updates.
21
20
  - Free mode stores full-fidelity signals and decisions locally in SQLite.
@@ -38,12 +37,13 @@ Ask The W performs inference, linking, approval state, dedupe, and outcome updat
38
37
  ```bash
39
38
  npx -y --prefer-online @askthew/mcp-plugin@latest install \
40
39
  --host claude_code \
41
- --free
42
-
43
- npx @askthew/mcp-plugin auth login --email you@founder.com
40
+ --free \
41
+ --email you@founder.com
44
42
  ```
45
43
 
46
- Telemetry is aggregate-only and opt-out. Ask The W does not receive code, file contents, file paths, file names, command text, summaries, or decision content in free mode telemetry. We do tie aggregate counts, stack, and tool usage to your email for upgrade onboarding. Opt out with `--no-telemetry`, `ASKTHEW_TELEMETRY=off`, or `askthew-mcp telemetry opt-out`.
44
+ Free install is local-first: it generates `~/.askthew/identity.json`, writes MCP config and agent instructions immediately, and never requires an email code before local capture works. The email is an unverified claim used for upgrade onboarding only; data is keyed by the generated install ID, not email alone.
45
+
46
+ Telemetry is aggregate-only and opt-out. Ask The W does not receive code, file contents, file paths, file names, command text, summaries, or decision content in free mode telemetry. Aggregate summaries are signed by the local install identity and stored under the generated install ID. Opt out with `--no-telemetry`, `ASKTHEW_TELEMETRY=off`, or `askthew-mcp telemetry opt-out`.
47
47
 
48
48
  ## Workspace Install
49
49
 
@@ -85,7 +85,7 @@ After install, restart or reload your coding agent if needed. At the start of ev
85
85
 
86
86
  Claude Desktop and Cowork custom connectors use Ask The W's hosted Remote MCP URL instead of this local `npx` installer. In Ask The W, create a Claude Remote URL from the plugin source, then paste it into Claude's custom connector form as the Remote MCP server URL. The URL must be public HTTPS; `localhost`, `127.0.0.1`, LAN IPs, VPN-only hosts, and firewall-blocked servers will fail because Claude connects from Anthropic's cloud. Treat the URL like a password and rotate it if it leaks.
87
87
 
88
- The installer also adds safe, marked project instructions:
88
+ The installer also adds safe, marked project instructions to both markdown convention files plus any host-specific rule file:
89
89
 
90
90
  - Codex: `AGENTS.md`
91
91
  - Claude Code: `CLAUDE.md`
@@ -155,22 +155,33 @@ The session-signal tool remains the main automatic capture path.
155
155
  "evidence": [{ "role": "user | assistant | system", "excerpt": "string" }],
156
156
  "filesTouched": ["string"],
157
157
  "commandsRun": ["string"],
158
- "metadata": {}
158
+ "metadata": {},
159
+ "echo": "summary | full"
159
160
  }
160
161
  }
161
162
  ```
162
163
 
163
- Use compact summaries and short evidence excerpts. Do not send full transcripts.
164
+ Use compact summaries and short evidence excerpts. Do not send full transcripts. By default, write tools return compact responses; use `echo: "full"` only when you need the larger payload for debugging.
164
165
 
165
166
  The plugin also exposes v1 API tools that map to the app's authenticated routes:
166
167
 
167
168
  | Tool | Purpose |
168
169
  |---|---|
169
170
  | `list_decisions`, `get_decision`, `create_decision`, `update_decision`, `delete_decision` | Work with decision feed entries. |
171
+ | `review_session`, `recap`, `coach`, `promote_signal_to_decision`, `find_signal_by_summary` | Review local sessions, get session coaching, find recent evidence, and turn signals into decisions in free mode. |
170
172
  | `list_outcomes`, `get_outcome`, `list_outcome_signals`, `create_outcome`, `update_outcome`, `delete_outcome` | Work with outcomes and their linked signals. |
171
173
  | `get_north_star`, `update_north_star` | Read or update the workspace north star. API updates are allowed only for private workspaces. |
172
174
  | `list_signals`, `get_signal` | Read workspace signals. |
173
175
 
176
+ Free PLG helpers:
177
+
178
+ ```bash
179
+ npx @askthew/mcp-plugin install-hook --pre-commit
180
+ npx @askthew/mcp-plugin digest --weekly
181
+ ```
182
+
183
+ The hook prompts when staged files recently had implementation signals but no linked decision. The weekly digest writes `~/Documents/askthew-digest-YYYY-WW.md`.
184
+
174
185
  API mutations are text-only and are recorded back into the workspace signal feed. Decision and outcome deletes require a `confirmText` value that exactly matches the stored decision headline or outcome name after whitespace normalization. North star delete is not available through the API.
175
186
 
176
187
  ## Troubleshooting
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,56 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { clearPendingAuth, pendingAuth, pendingAuthForEmail, savePendingAuth } from "./lib/auth-pending.js";
7
+ import { configPath } from "./lib/paths.js";
8
+ function withTempEnv(fn) {
9
+ const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-auth-pending-"));
10
+ try {
11
+ return fn({ ASKTHEW_DATA_DIR: dataDir });
12
+ }
13
+ finally {
14
+ fs.rmSync(dataDir, { recursive: true, force: true });
15
+ }
16
+ }
17
+ test("pending auth stores and resolves the request id for the matching email", () => {
18
+ withTempEnv((env) => {
19
+ savePendingAuth({
20
+ email: "Founder@Example.com",
21
+ requestId: "request_1",
22
+ expiresAt: new Date(Date.now() + 60_000).toISOString(),
23
+ telemetryOptOut: true,
24
+ }, env);
25
+ const pending = pendingAuthForEmail("founder@example.com", env);
26
+ assert.equal(pending?.requestId, "request_1");
27
+ assert.equal(pending?.telemetryOptOut, true);
28
+ assert.equal(pendingAuth(env)?.email, "Founder@Example.com");
29
+ });
30
+ });
31
+ test("pending auth ignores other emails and clears expired requests", () => {
32
+ withTempEnv((env) => {
33
+ savePendingAuth({
34
+ email: "founder@example.com",
35
+ requestId: "request_1",
36
+ expiresAt: new Date(Date.now() - 1_000).toISOString(),
37
+ }, env);
38
+ assert.equal(pendingAuthForEmail("other@example.com", env), null);
39
+ assert.equal(pendingAuthForEmail("founder@example.com", env), null);
40
+ const config = JSON.parse(fs.readFileSync(configPath(env), "utf8"));
41
+ assert.equal("pendingAuth" in config, false);
42
+ });
43
+ });
44
+ test("pending auth clear keeps the config file private and removes only pending auth", () => {
45
+ withTempEnv((env) => {
46
+ savePendingAuth({
47
+ email: "founder@example.com",
48
+ requestId: "request_1",
49
+ expiresAt: new Date(Date.now() + 60_000).toISOString(),
50
+ }, env);
51
+ clearPendingAuth(env);
52
+ const config = JSON.parse(fs.readFileSync(configPath(env), "utf8"));
53
+ assert.equal("pendingAuth" in config, false);
54
+ assert.equal((fs.statSync(configPath(env)).mode & 0o777), 0o600);
55
+ });
56
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,71 @@
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
+ });
package/dist/cli.d.ts CHANGED
@@ -1,2 +1,11 @@
1
1
  #!/usr/bin/env node
2
+ import { requestMagicLinkCode as requestMagicLinkCodeDefault, verifyMagicLinkCode as verifyMagicLinkCodeDefault } from "./lib/auth-magic-link.js";
3
+ import { tryRegisterFreeInstall } from "./lib/free-install-registration.js";
4
+ type AuthCommandDeps = {
5
+ log?: (message: string) => void;
6
+ requestMagicLinkCode?: typeof requestMagicLinkCodeDefault;
7
+ verifyMagicLinkCode?: typeof verifyMagicLinkCodeDefault;
8
+ registerFreeInstall?: typeof tryRegisterFreeInstall;
9
+ };
10
+ export declare function runAuthCommand(argv: string[], deps?: AuthCommandDeps): Promise<void>;
2
11
  export {};