@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.
- package/README.md +65 -16
- 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 +412 -18
- 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 +59 -13
- package/dist/index.js +1736 -103
- package/dist/index.test.d.ts +1 -0
- package/dist/index.test.js +952 -0
- package/dist/install.d.ts +56 -1
- package/dist/install.js +171 -26
- package/dist/install.test.d.ts +1 -0
- package/dist/install.test.js +297 -0
- package/dist/lib/auth-magic-link.d.ts +22 -0
- package/dist/lib/auth-magic-link.js +43 -0
- 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 +23 -0
- package/dist/lib/free-tier-policy.js +68 -0
- package/dist/lib/local-identity.d.ts +44 -0
- package/dist/lib/local-identity.js +81 -0
- package/dist/lib/local-store.d.ts +130 -0
- package/dist/lib/local-store.js +595 -0
- package/dist/lib/loopback-auth.d.ts +8 -0
- package/dist/lib/loopback-auth.js +30 -0
- package/dist/lib/paths.d.ts +9 -0
- package/dist/lib/paths.js +50 -0
- package/dist/lib/telemetry.d.ts +25 -0
- package/dist/lib/telemetry.js +159 -0
- package/dist/lib/timeline-insights.d.ts +23 -0
- package/dist/lib/timeline-insights.js +115 -0
- package/dist/lib/tip-engine.d.ts +18 -0
- package/dist/lib/tip-engine.js +237 -0
- package/dist/lib/upgrade-nudge.d.ts +19 -0
- package/dist/lib/upgrade-nudge.js +37 -0
- package/dist/lib/upgrade-sync.d.ts +38 -0
- package/dist/lib/upgrade-sync.js +60 -0
- package/dist/local-identity.test.d.ts +1 -0
- package/dist/local-identity.test.js +29 -0
- package/dist/local-store.test.d.ts +1 -0
- package/dist/local-store.test.js +71 -0
- package/dist/scope.d.ts +1 -2
- package/dist/scope.js +56 -8
- package/dist/scope.test.d.ts +1 -0
- package/dist/scope.test.js +49 -0
- package/dist/timeline-insights.test.d.ts +1 -0
- package/dist/timeline-insights.test.js +85 -0
- package/dist/tip-engine.test.d.ts +1 -0
- package/dist/tip-engine.test.js +51 -0
- 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 {
|
|
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
|
-
"
|
|
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
|
-
|
|
117
|
+
apiUrl = "https://app.askthew.com";
|
|
81
118
|
}
|
|
82
119
|
if (!serverName) {
|
|
83
|
-
|
|
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 ? "
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
153
|
-
|
|
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
|
-
|
|
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 {};
|