@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.
- package/README.md +24 -13
- package/dist/auth-pending.test.d.ts +1 -0
- package/dist/auth-pending.test.js +56 -0
- package/dist/cli-actions.test.d.ts +1 -0
- package/dist/cli-actions.test.js +71 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +293 -37
- package/dist/cli.test.d.ts +1 -0
- package/dist/cli.test.js +274 -0
- package/dist/free-tier-policy.test.d.ts +1 -0
- package/dist/free-tier-policy.test.js +57 -0
- package/dist/index.d.ts +47 -13
- package/dist/index.js +1103 -106
- package/dist/index.test.js +609 -6
- package/dist/install.d.ts +40 -0
- package/dist/install.js +155 -18
- package/dist/install.test.js +62 -2
- package/dist/lib/auth-pending.d.ts +23 -0
- package/dist/lib/auth-pending.js +36 -0
- package/dist/lib/cli-actions.d.ts +28 -0
- package/dist/lib/cli-actions.js +104 -0
- package/dist/lib/free-install-registration.d.ts +27 -0
- package/dist/lib/free-install-registration.js +52 -0
- package/dist/lib/free-tier-policy.d.ts +5 -1
- package/dist/lib/free-tier-policy.js +16 -1
- package/dist/lib/local-identity.d.ts +44 -0
- package/dist/lib/local-identity.js +81 -0
- package/dist/lib/local-store.d.ts +33 -2
- package/dist/lib/local-store.js +191 -19
- package/dist/lib/paths.d.ts +2 -0
- package/dist/lib/paths.js +6 -0
- package/dist/lib/telemetry.js +28 -2
- package/dist/lib/timeline-insights.d.ts +23 -0
- package/dist/lib/timeline-insights.js +115 -0
- package/dist/lib/upgrade-nudge.d.ts +1 -1
- package/dist/lib/upgrade-nudge.js +8 -1
- package/dist/local-identity.test.d.ts +1 -0
- package/dist/local-identity.test.js +29 -0
- package/dist/local-store.test.js +34 -0
- package/dist/scope.d.ts +1 -1
- package/dist/scope.js +56 -2
- package/dist/scope.test.js +17 -0
- package/dist/timeline-insights.test.d.ts +1 -0
- package/dist/timeline-insights.test.js +85 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
# Ask The W
|
|
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 `
|
|
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,
|
|
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
|
|
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
|
-
|
|
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 {};
|