@askthew/mcp-plugin 0.4.4 → 0.4.5
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 +18 -0
- package/dist/cli.js +86 -1
- package/dist/cli.test.js +51 -0
- package/dist/index.js +1 -1
- package/dist/index.test.js +3 -3
- package/dist/lib/upgrade-nudge.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,6 +45,24 @@ Free install is local-first: it generates `~/.askthew/identity.json`, writes MCP
|
|
|
45
45
|
|
|
46
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
|
+
## Refresh vs Uninstall
|
|
49
|
+
|
|
50
|
+
Use `refresh` when you want the latest installed MCP config and instruction blocks without touching local identity or local data:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx -y --prefer-online @askthew/mcp-plugin@latest refresh --host claude_code
|
|
54
|
+
```
|
|
55
|
+
|
|
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.
|
|
57
|
+
|
|
58
|
+
Use `uninstall` when you want to remove the plugin from a host:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npx -y @askthew/mcp-plugin@latest uninstall --host claude_code --keep-local-data --keep-auth
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Without `--keep-local-data`, uninstall removes `~/.askthew`; without `--keep-auth`, it removes the local install identity. Uninstall does not clear npm's cache.
|
|
65
|
+
|
|
48
66
|
## Workspace Install
|
|
49
67
|
|
|
50
68
|
Create a workspace token in Ask The W at `/decisions/settings/connectors`, then run the installer from your coding agent or terminal. Treat the token like a password; anyone with it can write compact source signals into that workspace.
|
package/dist/cli.js
CHANGED
|
@@ -22,6 +22,7 @@ function usage() {
|
|
|
22
22
|
" askthew-mcp",
|
|
23
23
|
" 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]",
|
|
24
24
|
" askthew-mcp install --host <claude_code|codex|cursor> --free [--email <email>] [--api-url <url>] [--server-name <name>]",
|
|
25
|
+
" askthew-mcp refresh --host <claude_code|codex|cursor> [--free] [--token <install-token>] [--api-url <url>] [--server-name <name>] [--dry-run]",
|
|
25
26
|
" askthew-mcp uninstall --host <claude_code|codex|cursor> [--server-name <name>] [--dry-run] [--keep-local-data] [--keep-auth] [--keep-agent-instructions]",
|
|
26
27
|
" askthew-mcp identify --email <email> [--no-telemetry]",
|
|
27
28
|
" askthew-mcp identity status",
|
|
@@ -132,6 +133,16 @@ function parseInstallArgs(argv) {
|
|
|
132
133
|
function normalizeInstallToken(token) {
|
|
133
134
|
return String(token ?? "").trim().replace(/^['"]/, "").replace(/['"]$/, "");
|
|
134
135
|
}
|
|
136
|
+
function packageVersion() {
|
|
137
|
+
try {
|
|
138
|
+
const packagePath = fileURLToPath(new URL("../package.json", import.meta.url));
|
|
139
|
+
const parsed = JSON.parse(fs.readFileSync(packagePath, "utf8"));
|
|
140
|
+
return typeof parsed.version === "string" ? parsed.version : "unknown";
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return "unknown";
|
|
144
|
+
}
|
|
145
|
+
}
|
|
135
146
|
function detectLoginEmail() {
|
|
136
147
|
for (const value of [
|
|
137
148
|
process.env.ASKTHEW_EMAIL,
|
|
@@ -229,6 +240,10 @@ async function main() {
|
|
|
229
240
|
}
|
|
230
241
|
return;
|
|
231
242
|
}
|
|
243
|
+
if (command === "refresh") {
|
|
244
|
+
await runRefreshCommand(argv);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
232
247
|
if (command === "identify") {
|
|
233
248
|
await runIdentifyCommand(argv);
|
|
234
249
|
return;
|
|
@@ -270,7 +285,7 @@ async function main() {
|
|
|
270
285
|
return;
|
|
271
286
|
}
|
|
272
287
|
if (command === "upgrade") {
|
|
273
|
-
console.log("Open https://askthew.com/
|
|
288
|
+
console.log("Open https://askthew.com/plugin to upgrade, then run `askthew-mcp upgrade --finalize`.");
|
|
274
289
|
if (argv.includes("--finalize")) {
|
|
275
290
|
console.log("Finalize will rewrite host config after a paid workspace token is available in the web app.");
|
|
276
291
|
}
|
|
@@ -353,6 +368,76 @@ async function runUninstallCommand(argv) {
|
|
|
353
368
|
console.log(config.json);
|
|
354
369
|
}
|
|
355
370
|
}
|
|
371
|
+
async function runRefreshCommand(argv) {
|
|
372
|
+
const parsed = parseInstallArgs(["--free", ...argv]);
|
|
373
|
+
const isPaidRefresh = Boolean(parsed.token && !argv.includes("--free"));
|
|
374
|
+
const options = {
|
|
375
|
+
...parsed,
|
|
376
|
+
free: !isPaidRefresh,
|
|
377
|
+
};
|
|
378
|
+
const existingIdentity = loadLocalIdentity();
|
|
379
|
+
let freeIdentity = existingIdentity;
|
|
380
|
+
if (options.free && !options.dryRun && !freeIdentity) {
|
|
381
|
+
freeIdentity = ensureLocalIdentity({
|
|
382
|
+
emailClaim: options.email,
|
|
383
|
+
apiUrl: options.apiUrl,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
const uninstall = uninstallHostConfig({
|
|
387
|
+
hostType: options.hostType,
|
|
388
|
+
serverName: options.serverName,
|
|
389
|
+
dryRun: options.dryRun,
|
|
390
|
+
});
|
|
391
|
+
const removedInstructions = uninstallBehaviorInstructions({
|
|
392
|
+
hostType: options.hostType,
|
|
393
|
+
dryRun: options.dryRun,
|
|
394
|
+
});
|
|
395
|
+
const install = installHostConfig(options);
|
|
396
|
+
const installedInstructions = options.installAgentInstructions
|
|
397
|
+
? installBehaviorInstructions({
|
|
398
|
+
hostType: options.hostType,
|
|
399
|
+
dryRun: options.dryRun,
|
|
400
|
+
})
|
|
401
|
+
: null;
|
|
402
|
+
if (options.free && freeIdentity && !options.dryRun) {
|
|
403
|
+
await tryRegisterFreeInstall({
|
|
404
|
+
identity: freeIdentity,
|
|
405
|
+
deviceLabel: options.clientLabel ?? `${options.hostType} free refresh`,
|
|
406
|
+
repo: {
|
|
407
|
+
repoName: process.env.ASKTHEW_REPO_NAME,
|
|
408
|
+
repoRoot: process.env.ASKTHEW_REPO_ROOT,
|
|
409
|
+
hostType: options.hostType,
|
|
410
|
+
},
|
|
411
|
+
options: { apiUrl: options.apiUrl },
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
console.log(options.dryRun ? "Ask The W plugin refresh dry run complete." : "Ask The W plugin refresh complete.");
|
|
415
|
+
console.log(`Plugin package version: ${packageVersion()}`);
|
|
416
|
+
console.log(`Settings path: ${install.settingsPath}`);
|
|
417
|
+
console.log(`Removed instructions: ${removedInstructions.paths.join(", ") || "none"}`);
|
|
418
|
+
if (installedInstructions) {
|
|
419
|
+
console.log(`Installed instructions: ${installedInstructions.paths.join(", ") || installedInstructions.path}`);
|
|
420
|
+
}
|
|
421
|
+
if (options.free) {
|
|
422
|
+
console.log(freeIdentity
|
|
423
|
+
? `Local identity preserved: ${freeIdentity.installId}`
|
|
424
|
+
: "Local identity would be created on a non-dry-run refresh.");
|
|
425
|
+
console.log("Local data preserved: ~/.askthew/store.sqlite");
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
const heartbeatSent = install.wroteFile
|
|
429
|
+
? await sendInstallHeartbeat(options).catch(() => false)
|
|
430
|
+
: false;
|
|
431
|
+
console.log(heartbeatSent ? "Paid workspace heartbeat sent." : "Paid workspace heartbeat not sent yet.");
|
|
432
|
+
}
|
|
433
|
+
console.log(`Next step: ${install.nextStep}`);
|
|
434
|
+
if (options.dryRun) {
|
|
435
|
+
console.log("");
|
|
436
|
+
console.log(uninstall.json);
|
|
437
|
+
console.log("");
|
|
438
|
+
console.log(install.json);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
356
441
|
function argValue(argv, name) {
|
|
357
442
|
const index = argv.indexOf(name);
|
|
358
443
|
return index >= 0 ? argv[index + 1] : undefined;
|
package/dist/cli.test.js
CHANGED
|
@@ -138,6 +138,57 @@ test("free install ignores stale legacy credentials and writes local identity",
|
|
|
138
138
|
fs.rmSync(fixture.root, { recursive: true, force: true });
|
|
139
139
|
}
|
|
140
140
|
});
|
|
141
|
+
test("refresh rewrites host config and instructions while preserving local identity and data", () => {
|
|
142
|
+
const fixture = makeFixture();
|
|
143
|
+
try {
|
|
144
|
+
const install = runCli({
|
|
145
|
+
args: ["install", "--host", "claude_code", "--free", "--email", "founder@example.com", "--api-url", "http://127.0.0.1:9"],
|
|
146
|
+
cwd: fixture.project,
|
|
147
|
+
home: fixture.home,
|
|
148
|
+
dataDir: fixture.dataDir,
|
|
149
|
+
});
|
|
150
|
+
assert.equal(install.status, 0, install.stderr);
|
|
151
|
+
const beforeIdentity = JSON.parse(fs.readFileSync(path.join(fixture.dataDir, "identity.json"), "utf8"));
|
|
152
|
+
fs.writeFileSync(path.join(fixture.dataDir, "store.sqlite"), "keep me", "utf8");
|
|
153
|
+
const refresh = runCli({
|
|
154
|
+
args: ["refresh", "--host", "claude_code", "--api-url", "http://127.0.0.1:9"],
|
|
155
|
+
cwd: fixture.project,
|
|
156
|
+
home: fixture.home,
|
|
157
|
+
dataDir: fixture.dataDir,
|
|
158
|
+
});
|
|
159
|
+
assert.equal(refresh.status, 0, refresh.stderr);
|
|
160
|
+
assert.match(refresh.stdout, /plugin refresh complete/);
|
|
161
|
+
assert.match(refresh.stdout, /Local identity preserved/);
|
|
162
|
+
assert.match(refresh.stdout, /Plugin package version:/);
|
|
163
|
+
const afterIdentity = JSON.parse(fs.readFileSync(path.join(fixture.dataDir, "identity.json"), "utf8"));
|
|
164
|
+
assert.equal(afterIdentity.installId, beforeIdentity.installId);
|
|
165
|
+
assert.equal(afterIdentity.emailClaim, "founder@example.com");
|
|
166
|
+
assert.equal(fs.readFileSync(path.join(fixture.dataDir, "store.sqlite"), "utf8"), "keep me");
|
|
167
|
+
assert.match(fs.readFileSync(path.join(fixture.project, "CLAUDE.md"), "utf8"), /ASKTHEW_PLUGIN_INSTRUCTIONS_START/);
|
|
168
|
+
assert.match(fs.readFileSync(path.join(fixture.project, "AGENTS.md"), "utf8"), /ASKTHEW_PLUGIN_INSTRUCTIONS_START/);
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
fs.rmSync(fixture.root, { recursive: true, force: true });
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
test("refresh dry-run does not create a local identity", () => {
|
|
175
|
+
const fixture = makeFixture();
|
|
176
|
+
try {
|
|
177
|
+
const refresh = runCli({
|
|
178
|
+
args: ["refresh", "--host", "codex", "--dry-run"],
|
|
179
|
+
cwd: fixture.project,
|
|
180
|
+
home: fixture.home,
|
|
181
|
+
dataDir: fixture.dataDir,
|
|
182
|
+
});
|
|
183
|
+
assert.equal(refresh.status, 0, refresh.stderr);
|
|
184
|
+
assert.match(refresh.stdout, /refresh dry run complete/);
|
|
185
|
+
assert.equal(fs.existsSync(path.join(fixture.dataDir, "identity.json")), false);
|
|
186
|
+
assert.equal(fs.existsSync(path.join(fixture.home, ".codex", "config.toml")), false);
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
fs.rmSync(fixture.root, { recursive: true, force: true });
|
|
190
|
+
}
|
|
191
|
+
});
|
|
141
192
|
test("auth status reports missing local identity without pending-code guidance", () => {
|
|
142
193
|
const fixture = makeFixture();
|
|
143
194
|
try {
|
package/dist/index.js
CHANGED
|
@@ -1278,7 +1278,7 @@ export function createAskTheWMcpServer(options = {}) {
|
|
|
1278
1278
|
extra: {
|
|
1279
1279
|
tool: "review_session",
|
|
1280
1280
|
limit: 3,
|
|
1281
|
-
upgradeUrl: "https://askthew.com/
|
|
1281
|
+
upgradeUrl: "https://askthew.com/plugin?utm_source=mcp-plugin&utm_medium=tool-nudge&utm_campaign=mcp-free&tool=review_session",
|
|
1282
1282
|
cta: "Upgrade to review more than three sessions in the workspace dashboard.",
|
|
1283
1283
|
},
|
|
1284
1284
|
});
|
package/dist/index.test.js
CHANGED
|
@@ -743,7 +743,7 @@ test("free decisions, recap, coach, and promote return human-readable compact ou
|
|
|
743
743
|
assert.equal(response.ok, false);
|
|
744
744
|
assert.equal(response.code, "free_tier_paid_feature");
|
|
745
745
|
assert.equal(response.tool, "coach");
|
|
746
|
-
assert.match(response.upgradeUrl, /askthew\.com\/
|
|
746
|
+
assert.match(response.upgradeUrl, /askthew\.com\/plugin/);
|
|
747
747
|
}
|
|
748
748
|
});
|
|
749
749
|
});
|
|
@@ -910,7 +910,7 @@ test("free review_session markdown is capped and json is cursor-paginated with a
|
|
|
910
910
|
assert.equal(capped.ok, false);
|
|
911
911
|
assert.equal(capped.code, "free_tier_limit");
|
|
912
912
|
assert.equal(capped.limit, 3);
|
|
913
|
-
assert.match(capped.upgradeUrl, /askthew\.com\/
|
|
913
|
+
assert.match(capped.upgradeUrl, /askthew\.com\/plugin/);
|
|
914
914
|
});
|
|
915
915
|
});
|
|
916
916
|
test("paid tools return canonical paywall envelope before transport in free mode", async () => {
|
|
@@ -943,7 +943,7 @@ test("paid tools return canonical paywall envelope before transport in free mode
|
|
|
943
943
|
assert.equal(response.ok, false, toolName);
|
|
944
944
|
assert.equal(response.code, "free_tier_paid_feature", toolName);
|
|
945
945
|
assert.equal(response.tool, toolName, toolName);
|
|
946
|
-
assert.match(response.upgradeUrl, /askthew\.com\/
|
|
946
|
+
assert.match(response.upgradeUrl, /askthew\.com\/plugin/, toolName);
|
|
947
947
|
assert.equal(response.supportEmail, "support@askthew.com", toolName);
|
|
948
948
|
}
|
|
949
949
|
});
|
|
@@ -20,7 +20,7 @@ export function paidFeatureNudge(tool) {
|
|
|
20
20
|
tool,
|
|
21
21
|
message: `${feature.label} ${feature.verb} a paid feature. See ${PRICING_URL}.`,
|
|
22
22
|
pricingUrl: PRICING_URL,
|
|
23
|
-
upgradeUrl: `https://askthew.com/
|
|
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
25
|
cta: "Run: npx @askthew/mcp-plugin upgrade",
|
|
26
26
|
};
|