@askthew/mcp-plugin 0.4.10 → 0.4.12
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 +43 -179
- package/dist/cli.d.ts +0 -6
- package/dist/cli.js +255 -723
- package/dist/cloud-client.d.ts +80 -0
- package/dist/cloud-client.js +150 -0
- package/dist/index.d.ts +20 -123
- package/dist/index.js +155 -1353
- package/dist/install.d.ts +33 -119
- package/dist/install.js +140 -614
- package/dist/lib/paths.d.ts +2 -2
- package/dist/lib/paths.js +6 -6
- package/dist/outbox.d.ts +32 -0
- package/dist/outbox.js +101 -0
- package/dist/redaction.d.ts +11 -0
- package/dist/redaction.js +52 -0
- package/package.json +2 -2
- package/dist/lib/cli-actions.d.ts +0 -28
- package/dist/lib/cli-actions.js +0 -104
- package/dist/lib/free-install-registration.d.ts +0 -27
- package/dist/lib/free-install-registration.js +0 -52
- package/dist/lib/free-tier-policy.d.ts +0 -22
- package/dist/lib/free-tier-policy.js +0 -52
- package/dist/lib/local-identity.d.ts +0 -44
- package/dist/lib/local-identity.js +0 -81
- package/dist/lib/local-store.d.ts +0 -130
- package/dist/lib/local-store.js +0 -606
- package/dist/lib/loopback-auth.d.ts +0 -8
- package/dist/lib/loopback-auth.js +0 -30
- package/dist/lib/telemetry.d.ts +0 -25
- package/dist/lib/telemetry.js +0 -155
- package/dist/lib/timeline-insights.d.ts +0 -23
- package/dist/lib/timeline-insights.js +0 -115
- package/dist/lib/tip-engine.d.ts +0 -18
- package/dist/lib/tip-engine.js +0 -237
- package/dist/lib/upgrade-nudge.d.ts +0 -19
- package/dist/lib/upgrade-nudge.js +0 -37
- package/dist/lib/upgrade-sync.d.ts +0 -38
- package/dist/lib/upgrade-sync.js +0 -60
package/dist/cli.js
CHANGED
|
@@ -1,783 +1,315 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { askTheWDataDir, ensureAskTheWDataDir, identityPath } from "./lib/paths.js";
|
|
9
|
-
import { loadCliCredentials } from "./lib/free-tier-policy.js";
|
|
10
|
-
import { describeFreeIdentity, tryRegisterFreeInstall } from "./lib/free-install-registration.js";
|
|
11
|
-
import { ensureLocalIdentity, loadLocalIdentity, publicIdentity } from "./lib/local-identity.js";
|
|
12
|
-
import { LocalStore } from "./lib/local-store.js";
|
|
13
|
-
import { buildTelemetryPayload } from "./lib/telemetry.js";
|
|
14
|
-
import { syncDryRun, uploadLocalStore } from "./lib/upgrade-sync.js";
|
|
15
|
-
import { installPreCommitHook, localScopeKey, preCommitDecisionGap, stagedFiles, writeWeeklyDigest, } from "./lib/cli-actions.js";
|
|
16
|
-
import { createHostConfigSnippet, defaultServerNameForTier, findInstallReceipts, formatInstallCommand, installBehaviorInstructions, installHostConfig, packageVersion, removeInstallReceipts, sendInstallHeartbeat, uninstallBehaviorInstructions, uninstallHostConfig, upgradePinnedHostConfig, writeInstallReceipt, } from "./install.js";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import prompts from "prompts";
|
|
4
|
+
import { AskTheWCloudClient, completeSignup, loadCloudToken, saveCloudToken, startSignup } from "./cloud-client.js";
|
|
5
|
+
import { Outbox } from "./outbox.js";
|
|
6
|
+
import { DEFAULT_SERVER_NAME, dataDirSummary, installHostConfig, packageVersion, writeBehaviorInstructions, writeInstallMetadata, } from "./install.js";
|
|
7
|
+
import { runStdioServer } from "./index.js";
|
|
17
8
|
function usage() {
|
|
18
9
|
return [
|
|
19
|
-
"Ask The W
|
|
10
|
+
"Ask The W MCP",
|
|
20
11
|
"",
|
|
21
12
|
"Usage:",
|
|
22
|
-
" askthew-mcp
|
|
13
|
+
" askthew-mcp",
|
|
14
|
+
" askthew-mcp install --host <claude_code|codex|cursor> [--bind <paid-bind-token>] [--skip-auth]",
|
|
15
|
+
" askthew-mcp bind [--allow-pending]",
|
|
16
|
+
" askthew-mcp token rotate|revoke",
|
|
17
|
+
" askthew-mcp export",
|
|
18
|
+
" askthew-mcp delete-me --confirm",
|
|
23
19
|
" askthew-mcp --version",
|
|
24
|
-
" askthew-mcp doctor [--server-entrypoint <path>]",
|
|
25
|
-
" askthew-mcp install --host <claude_code|codex|cursor> --token <install-token> --api-url <url> [--server-name <name>] [--client-id <id>] [--client-label <label>] [--server-entrypoint <path>] [--dry-run] [--no-agent-instructions]",
|
|
26
|
-
" askthew-mcp install --host <claude_code|codex|cursor> --free [--email <email>] [--api-url <url>] [--server-name <name>] [--server-entrypoint <path>] [--dry-run]",
|
|
27
|
-
" askthew-mcp refresh --host <claude_code|codex|cursor> [--free] [--token <install-token>] [--api-url <url>] [--server-name <name>] [--server-entrypoint <path>] [--dry-run]",
|
|
28
|
-
" askthew-mcp uninstall --host <claude_code|codex|cursor> [--server-name <name>] [--dry-run] [--keep-local-data] [--keep-auth] [--keep-agent-instructions]",
|
|
29
|
-
" askthew-mcp upgrade --host <claude_code|codex|cursor> [--server-name <name>] [--version <version>] [--dry-run]",
|
|
30
|
-
" askthew-mcp upgrade --browser",
|
|
31
|
-
" askthew-mcp identify --email <email> [--no-telemetry]",
|
|
32
|
-
" askthew-mcp identity status",
|
|
33
|
-
" askthew-mcp auth login --email <email> [--no-telemetry]",
|
|
34
|
-
" askthew-mcp auth logout | status",
|
|
35
|
-
" askthew-mcp telemetry status | opt-out | opt-in | preview",
|
|
36
|
-
" askthew-mcp local stats | reset --hard",
|
|
37
|
-
" askthew-mcp install-hook --pre-commit",
|
|
38
|
-
" askthew-mcp digest --weekly",
|
|
39
|
-
" askthew-mcp sync upload [--token <workspace-install-token>] [--dry-run]",
|
|
40
|
-
" askthew-mcp print-config --host <claude_code|codex|cursor> --token <install-token> --api-url <url> --server-name <name> [--client-id <id>] [--client-label <label>]",
|
|
41
20
|
].join("\n");
|
|
42
21
|
}
|
|
43
|
-
function
|
|
44
|
-
|
|
45
|
-
let clientId = process.env.ASKTHEW_CLIENT_ID?.trim() || "";
|
|
46
|
-
let clientLabel = process.env.ASKTHEW_CLIENT_LABEL?.trim() || "";
|
|
47
|
-
let token = normalizeInstallToken(process.env.ASKTHEW_INSTALL_TOKEN) || "";
|
|
48
|
-
let apiUrl = process.env.ASKTHEW_API_URL?.trim() || "";
|
|
49
|
-
let serverName = process.env.ASKTHEW_SERVER_NAME?.trim() || "";
|
|
50
|
-
let serverNameExplicit = Boolean(serverName);
|
|
51
|
-
let dryRun = false;
|
|
52
|
-
let installAgentInstructions = true;
|
|
53
|
-
let free = false;
|
|
54
|
-
let email = process.env.ASKTHEW_EMAIL?.trim() || "";
|
|
55
|
-
let serverEntrypoint = process.env.ASKTHEW_SERVER_ENTRYPOINT?.trim() || "";
|
|
22
|
+
function parseArgs(argv) {
|
|
23
|
+
const result = {};
|
|
56
24
|
for (let index = 0; index < argv.length; index += 1) {
|
|
57
|
-
const
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
if (argument === "--no-agent-instructions") {
|
|
63
|
-
installAgentInstructions = false;
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
if (argument === "--free") {
|
|
67
|
-
free = true;
|
|
25
|
+
const arg = argv[index];
|
|
26
|
+
if (!arg.startsWith("--")) {
|
|
27
|
+
result._ = [String(result._ ?? ""), arg].filter(Boolean).join(" ");
|
|
68
28
|
continue;
|
|
69
29
|
}
|
|
70
30
|
const next = argv[index + 1];
|
|
71
|
-
if (!next) {
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
if (argument === "--host") {
|
|
75
|
-
if (next !== "claude_code" && next !== "codex" && next !== "cursor") {
|
|
76
|
-
throw new Error(`Unsupported host "${next}". Expected claude_code, codex, or cursor.`);
|
|
77
|
-
}
|
|
78
|
-
hostType = next;
|
|
79
|
-
index += 1;
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
if (argument === "--client-id") {
|
|
83
|
-
clientId = next;
|
|
84
|
-
index += 1;
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
if (argument === "--client-label") {
|
|
88
|
-
clientLabel = next;
|
|
89
|
-
index += 1;
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
if (argument === "--token") {
|
|
93
|
-
token = normalizeInstallToken(next);
|
|
94
|
-
index += 1;
|
|
31
|
+
if (!next || next.startsWith("--")) {
|
|
32
|
+
result[arg] = true;
|
|
95
33
|
continue;
|
|
96
34
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
index += 1;
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
if (argument === "--server-name") {
|
|
103
|
-
serverName = next;
|
|
104
|
-
serverNameExplicit = true;
|
|
105
|
-
index += 1;
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
if (argument === "--email") {
|
|
109
|
-
email = next;
|
|
110
|
-
index += 1;
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
if (argument === "--server-entrypoint") {
|
|
114
|
-
serverEntrypoint = next;
|
|
115
|
-
index += 1;
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
throw new Error(`Unknown argument: ${argument}`);
|
|
119
|
-
}
|
|
120
|
-
if (!hostType) {
|
|
121
|
-
throw new Error("Missing required --host argument.");
|
|
122
|
-
}
|
|
123
|
-
if (!free && !token) {
|
|
124
|
-
throw new Error("Missing required --token argument.");
|
|
35
|
+
result[arg] = next;
|
|
36
|
+
index += 1;
|
|
125
37
|
}
|
|
126
|
-
|
|
127
|
-
apiUrl = "https://app.askthew.com";
|
|
128
|
-
}
|
|
129
|
-
if (!serverName) {
|
|
130
|
-
serverName = defaultServerNameForTier(free);
|
|
131
|
-
}
|
|
132
|
-
return {
|
|
133
|
-
hostType,
|
|
134
|
-
clientId: clientId || undefined,
|
|
135
|
-
clientLabel: clientLabel || undefined,
|
|
136
|
-
token,
|
|
137
|
-
apiUrl,
|
|
138
|
-
serverName,
|
|
139
|
-
serverNameExplicit,
|
|
140
|
-
dryRun,
|
|
141
|
-
installAgentInstructions,
|
|
142
|
-
free,
|
|
143
|
-
email: email || undefined,
|
|
144
|
-
serverEntrypoint: serverEntrypoint ? path.resolve(serverEntrypoint) : undefined,
|
|
145
|
-
};
|
|
38
|
+
return result;
|
|
146
39
|
}
|
|
147
|
-
function
|
|
148
|
-
|
|
40
|
+
function stringArg(args, name) {
|
|
41
|
+
const value = args[name];
|
|
42
|
+
return typeof value === "string" ? value.trim() : "";
|
|
149
43
|
}
|
|
150
|
-
function
|
|
151
|
-
|
|
152
|
-
process.env.ASKTHEW_EMAIL,
|
|
153
|
-
process.env.GIT_AUTHOR_EMAIL,
|
|
154
|
-
process.env.GIT_COMMITTER_EMAIL,
|
|
155
|
-
process.env.EMAIL,
|
|
156
|
-
]) {
|
|
157
|
-
const email = String(value ?? "").trim();
|
|
158
|
-
if (/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email))
|
|
159
|
-
return email;
|
|
160
|
-
}
|
|
161
|
-
try {
|
|
162
|
-
const email = execFileSync("git", ["config", "user.email"], {
|
|
163
|
-
encoding: "utf8",
|
|
164
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
165
|
-
}).trim();
|
|
166
|
-
if (/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email))
|
|
167
|
-
return email;
|
|
168
|
-
}
|
|
169
|
-
catch {
|
|
170
|
-
return "";
|
|
171
|
-
}
|
|
172
|
-
return "";
|
|
44
|
+
function boolArg(args, name) {
|
|
45
|
+
return args[name] === true;
|
|
173
46
|
}
|
|
174
|
-
function
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
: "askthew-mcp identify --email <your-email>";
|
|
47
|
+
function requireHost(value) {
|
|
48
|
+
if (value === "claude_code" || value === "codex" || value === "cursor")
|
|
49
|
+
return value;
|
|
50
|
+
throw new Error("Missing or invalid --host. Expected claude_code, codex, or cursor.");
|
|
179
51
|
}
|
|
180
|
-
function
|
|
181
|
-
return
|
|
52
|
+
function apiUrl(args) {
|
|
53
|
+
return stringArg(args, "--api-url") || process.env.ASKTHEW_API_URL?.trim() || "https://app.askthew.com";
|
|
182
54
|
}
|
|
183
|
-
async function
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
if (command === "doctor") {
|
|
194
|
-
await runDoctorCommand(argv);
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
if (command === "print-config") {
|
|
198
|
-
const options = parseInstallArgs(argv);
|
|
199
|
-
const snippet = createHostConfigSnippet(options);
|
|
200
|
-
console.log(snippet.json);
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
if (command === "install") {
|
|
204
|
-
console.error(`Downloading @askthew/mcp-plugin@${packageVersion()}...`);
|
|
205
|
-
const options = parseInstallArgs(argv);
|
|
206
|
-
let freeIdentity = null;
|
|
207
|
-
if (options.free && !options.dryRun) {
|
|
208
|
-
freeIdentity = ensureLocalIdentity({
|
|
209
|
-
emailClaim: installIdentityEmail(options.email),
|
|
210
|
-
apiUrl: options.apiUrl,
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
const result = installHostConfig(options);
|
|
214
|
-
const instructions = options.installAgentInstructions
|
|
215
|
-
? installBehaviorInstructions({
|
|
216
|
-
hostType: options.hostType,
|
|
217
|
-
dryRun: options.dryRun,
|
|
218
|
-
})
|
|
219
|
-
: null;
|
|
220
|
-
const receipt = result.wroteFile
|
|
221
|
-
? writeInstallReceipt({
|
|
222
|
-
hostType: options.hostType,
|
|
223
|
-
serverName: options.serverName,
|
|
224
|
-
settingsPath: result.settingsPath,
|
|
225
|
-
cwd: process.cwd(),
|
|
226
|
-
instructionPaths: instructions?.paths ?? [],
|
|
227
|
-
serverEntrypoint: options.serverEntrypoint,
|
|
228
|
-
packageVersion: packageVersion(),
|
|
229
|
-
})
|
|
230
|
-
: null;
|
|
231
|
-
const heartbeatSent = result.wroteFile && !options.free
|
|
232
|
-
? await sendInstallHeartbeat(options).catch(() => false)
|
|
233
|
-
: false;
|
|
234
|
-
console.log(result.wroteFile ? "Ask The W plugin install complete." : "Ask The W plugin dry run complete.");
|
|
235
|
-
console.log(`Settings path: ${result.settingsPath}`);
|
|
236
|
-
if (instructions) {
|
|
237
|
-
console.log(`${options.dryRun ? "Would update agent instructions" : "Agent instructions"}: ${instructions.paths?.join(", ") ?? instructions.path}`);
|
|
238
|
-
}
|
|
239
|
-
if (receipt) {
|
|
240
|
-
console.log(`Install receipt: ${receipt.hostType}/${receipt.serverName} at ${receipt.cwd}`);
|
|
241
|
-
}
|
|
242
|
-
else if (options.dryRun) {
|
|
243
|
-
console.log(`Would write install receipt: ${options.hostType}/${options.serverName} at ${process.cwd()}`);
|
|
244
|
-
}
|
|
245
|
-
console.log(`Install command: ${formatInstallCommand(options)}`);
|
|
246
|
-
if (result.wroteFile) {
|
|
247
|
-
if (freeIdentity) {
|
|
248
|
-
const registration = await tryRegisterFreeInstall({
|
|
249
|
-
identity: freeIdentity,
|
|
250
|
-
deviceLabel: options.clientLabel ?? `${options.hostType} free install`,
|
|
251
|
-
repo: {
|
|
252
|
-
repoName: process.env.ASKTHEW_REPO_NAME,
|
|
253
|
-
repoRoot: process.env.ASKTHEW_REPO_ROOT,
|
|
254
|
-
hostType: options.hostType,
|
|
255
|
-
},
|
|
256
|
-
options: { apiUrl: options.apiUrl },
|
|
257
|
-
});
|
|
258
|
-
console.log(registration.ok ? "Free install identity registered with Ask The W." : "Free install identity saved locally; cloud registration will retry later.");
|
|
259
|
-
}
|
|
260
|
-
console.log(options.free
|
|
261
|
-
? "Free local mode installed. Restart or reload your coding app; captures and decisions will write to ~/.askthew/store.sqlite."
|
|
262
|
-
: heartbeatSent
|
|
263
|
-
? "Ask The W install heartbeat sent. Refresh the app to confirm the plugin shows Installed."
|
|
264
|
-
: "Ask The W install heartbeat could not be sent yet. Restart or reload your coding app, then refresh Ask The W.");
|
|
265
|
-
}
|
|
266
|
-
console.log(`Next step: ${result.nextStep}`);
|
|
267
|
-
if (!result.wroteFile) {
|
|
268
|
-
console.log("");
|
|
269
|
-
console.log("Planned host config:");
|
|
270
|
-
console.log(result.json);
|
|
271
|
-
}
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
if (command === "refresh") {
|
|
275
|
-
await runRefreshCommand(argv);
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
if (command === "identify") {
|
|
279
|
-
await runIdentifyCommand(argv);
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
if (command === "identity") {
|
|
283
|
-
await runIdentityCommand(argv);
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
if (command === "uninstall") {
|
|
287
|
-
await runUninstallCommand(argv);
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
if (command === "auth") {
|
|
291
|
-
await runAuthCommand(argv);
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
if (command === "telemetry") {
|
|
295
|
-
await runTelemetryCommand(argv);
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
if (command === "local") {
|
|
299
|
-
await runLocalCommand(argv);
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
if (command === "install-hook") {
|
|
303
|
-
await runInstallHookCommand(argv);
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
if (command === "hook-check") {
|
|
307
|
-
await runHookCheckCommand(argv);
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
if (command === "digest") {
|
|
311
|
-
await runDigestCommand(argv);
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
if (command === "sync") {
|
|
315
|
-
await runSyncCommand(argv);
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
if (command === "upgrade") {
|
|
319
|
-
await runUpgradeCommand(argv);
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
if (command) {
|
|
323
|
-
throw new Error(`Unknown command "${command}".\n\n${usage()}`);
|
|
324
|
-
}
|
|
325
|
-
if (process.stdin.isTTY) {
|
|
326
|
-
console.log(usage());
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
const server = createAskTheWMcpServer();
|
|
330
|
-
const transport = new StdioServerTransport();
|
|
331
|
-
await server.connect(transport);
|
|
55
|
+
async function readEmail() {
|
|
56
|
+
const response = await prompts({
|
|
57
|
+
type: "text",
|
|
58
|
+
name: "email",
|
|
59
|
+
message: "Email for your Ask The W code",
|
|
60
|
+
validate: (value) => (/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value) ? true : "Enter a valid email."),
|
|
61
|
+
});
|
|
62
|
+
if (!response.email)
|
|
63
|
+
throw new Error("Signup cancelled.");
|
|
64
|
+
return String(response.email);
|
|
332
65
|
}
|
|
333
|
-
async function
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
let keepAuth = false;
|
|
340
|
-
let keepAgentInstructions = false;
|
|
341
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
342
|
-
const argument = argv[index];
|
|
343
|
-
if (argument === "--dry-run") {
|
|
344
|
-
dryRun = true;
|
|
345
|
-
continue;
|
|
346
|
-
}
|
|
347
|
-
if (argument === "--keep-local-data") {
|
|
348
|
-
keepLocalData = true;
|
|
349
|
-
continue;
|
|
350
|
-
}
|
|
351
|
-
if (argument === "--keep-auth") {
|
|
352
|
-
keepAuth = true;
|
|
353
|
-
continue;
|
|
354
|
-
}
|
|
355
|
-
if (argument === "--keep-agent-instructions") {
|
|
356
|
-
keepAgentInstructions = true;
|
|
357
|
-
continue;
|
|
358
|
-
}
|
|
359
|
-
const next = argv[index + 1];
|
|
360
|
-
if (!next)
|
|
361
|
-
throw new Error(`Missing value for ${argument}.`);
|
|
362
|
-
if (argument === "--host") {
|
|
363
|
-
if (next !== "claude_code" && next !== "codex" && next !== "cursor") {
|
|
364
|
-
throw new Error(`Unsupported host "${next}". Expected claude_code, codex, or cursor.`);
|
|
365
|
-
}
|
|
366
|
-
hostType = next;
|
|
367
|
-
index += 1;
|
|
368
|
-
continue;
|
|
369
|
-
}
|
|
370
|
-
if (argument === "--server-name") {
|
|
371
|
-
serverName = next;
|
|
372
|
-
serverNameExplicit = true;
|
|
373
|
-
index += 1;
|
|
374
|
-
continue;
|
|
375
|
-
}
|
|
376
|
-
throw new Error(`Unknown argument: ${argument}`);
|
|
377
|
-
}
|
|
378
|
-
if (!hostType) {
|
|
379
|
-
throw new Error("Usage: askthew-mcp uninstall --host <claude_code|codex|cursor> [--server-name <name>]");
|
|
380
|
-
}
|
|
381
|
-
const receipts = findInstallReceipts({ hostType, serverName });
|
|
382
|
-
const config = uninstallHostConfig({
|
|
383
|
-
hostType,
|
|
384
|
-
serverName: serverNameExplicit ? serverName : undefined,
|
|
385
|
-
dryRun,
|
|
386
|
-
cwds: Array.from(new Set([process.cwd(), ...receipts.map((receipt) => receipt.cwd)])),
|
|
66
|
+
async function readCode() {
|
|
67
|
+
const response = await prompts({
|
|
68
|
+
type: "text",
|
|
69
|
+
name: "code",
|
|
70
|
+
message: "Six-digit Ask The W code",
|
|
71
|
+
validate: (value) => (/^\d{6}$/.test(value) ? true : "Enter the 6-digit code."),
|
|
387
72
|
});
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
: instructionCwds.flatMap((cwd) => uninstallBehaviorInstructions({ hostType, dryRun, cwd }).paths);
|
|
392
|
-
const dataDir = askTheWDataDir();
|
|
393
|
-
const hadLocalData = fs.existsSync(dataDir);
|
|
394
|
-
const authFile = identityPath();
|
|
395
|
-
const hadAuth = fs.existsSync(authFile);
|
|
396
|
-
const removedReceipts = dryRun ? receipts.length : removeInstallReceipts({ hostType, serverName });
|
|
397
|
-
if (!keepLocalData && !dryRun) {
|
|
398
|
-
fs.rmSync(dataDir, { recursive: true, force: true });
|
|
399
|
-
}
|
|
400
|
-
if (!keepAuth && !dryRun) {
|
|
401
|
-
if (fs.existsSync(authFile))
|
|
402
|
-
fs.rmSync(authFile, { force: true });
|
|
403
|
-
}
|
|
404
|
-
console.log(dryRun ? "Ask The W plugin uninstall dry run complete." : "Ask The W plugin uninstall complete.");
|
|
405
|
-
console.log(`Settings path: ${config.settingsPath}`);
|
|
406
|
-
console.log(config.removedServer
|
|
407
|
-
? `${dryRun ? "Would remove" : "Removed"} MCP server "${config.removedServerName}".`
|
|
408
|
-
: config.foundConfigFile
|
|
409
|
-
? `No MCP server "${config.removedServerName}" found in host config.`
|
|
410
|
-
: "No host config file found.");
|
|
411
|
-
if (!keepAgentInstructions) {
|
|
412
|
-
console.log(instructionPaths.length > 0
|
|
413
|
-
? `Agent instructions ${dryRun ? "would be removed from" : "removed from"}: ${Array.from(new Set(instructionPaths)).join(", ")}`
|
|
414
|
-
: "No Ask The W agent instruction blocks found.");
|
|
415
|
-
}
|
|
416
|
-
console.log(removedReceipts > 0
|
|
417
|
-
? `${dryRun ? "Would remove" : "Removed"} ${removedReceipts} install receipt${removedReceipts === 1 ? "" : "s"}.`
|
|
418
|
-
: "No install receipts found.");
|
|
419
|
-
console.log(keepLocalData
|
|
420
|
-
? "Local data kept."
|
|
421
|
-
: hadLocalData
|
|
422
|
-
? `${dryRun ? "Local data would be removed" : "Local data removed"}.`
|
|
423
|
-
: "No local data directory found.");
|
|
424
|
-
console.log(keepAuth
|
|
425
|
-
? "Auth tokens kept."
|
|
426
|
-
: hadAuth
|
|
427
|
-
? `${dryRun ? "Local identity would be removed" : "Local identity removed"}.`
|
|
428
|
-
: "No local identity found.");
|
|
429
|
-
if (dryRun) {
|
|
430
|
-
console.log("");
|
|
431
|
-
console.log(config.json);
|
|
432
|
-
}
|
|
73
|
+
if (!response.code)
|
|
74
|
+
throw new Error("Signup cancelled.");
|
|
75
|
+
return String(response.code);
|
|
433
76
|
}
|
|
434
|
-
async function
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
: defaultServerNameForTier(!isPaidRefresh),
|
|
443
|
-
};
|
|
444
|
-
const existingIdentity = loadLocalIdentity();
|
|
445
|
-
let freeIdentity = existingIdentity;
|
|
446
|
-
if (options.free && !options.dryRun && !freeIdentity) {
|
|
447
|
-
freeIdentity = ensureLocalIdentity({
|
|
448
|
-
emailClaim: options.email,
|
|
449
|
-
apiUrl: options.apiUrl,
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
const uninstall = uninstallHostConfig({
|
|
453
|
-
hostType: options.hostType,
|
|
454
|
-
serverName: options.serverName,
|
|
455
|
-
dryRun: options.dryRun,
|
|
77
|
+
async function exchangePaidBindToken(input) {
|
|
78
|
+
const response = await fetch(`${input.apiUrl.replace(/\/+$/, "")}/api/v1/agent/paid/install-token/exchange`, {
|
|
79
|
+
method: "POST",
|
|
80
|
+
headers: { "Content-Type": "application/json" },
|
|
81
|
+
body: JSON.stringify({
|
|
82
|
+
paid_bind_token: input.paidBindToken,
|
|
83
|
+
client_hint: input.hostType,
|
|
84
|
+
}),
|
|
456
85
|
});
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
86
|
+
const body = await response.json().catch(() => ({}));
|
|
87
|
+
if (!response.ok || !body?.ok || !body.token || !body.install_id) {
|
|
88
|
+
throw new Error(String(body?.message ?? body?.code ?? "Paid install token exchange failed."));
|
|
89
|
+
}
|
|
90
|
+
saveCloudToken({
|
|
91
|
+
token: body.token,
|
|
92
|
+
install_id: body.install_id,
|
|
93
|
+
tier: "paid",
|
|
94
|
+
workspace_id: body.workspace_id ?? null,
|
|
95
|
+
token_purpose: "device",
|
|
96
|
+
api_url: input.apiUrl,
|
|
460
97
|
});
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
? writeInstallReceipt({
|
|
470
|
-
hostType: options.hostType,
|
|
471
|
-
serverName: options.serverName,
|
|
472
|
-
settingsPath: install.settingsPath,
|
|
473
|
-
cwd: process.cwd(),
|
|
474
|
-
instructionPaths: installedInstructions?.paths ?? [],
|
|
475
|
-
serverEntrypoint: options.serverEntrypoint,
|
|
476
|
-
packageVersion: packageVersion(),
|
|
477
|
-
})
|
|
478
|
-
: null;
|
|
479
|
-
if (options.free && freeIdentity && !options.dryRun) {
|
|
480
|
-
await tryRegisterFreeInstall({
|
|
481
|
-
identity: freeIdentity,
|
|
482
|
-
deviceLabel: options.clientLabel ?? `${options.hostType} free refresh`,
|
|
483
|
-
repo: {
|
|
484
|
-
repoName: process.env.ASKTHEW_REPO_NAME,
|
|
485
|
-
repoRoot: process.env.ASKTHEW_REPO_ROOT,
|
|
486
|
-
hostType: options.hostType,
|
|
487
|
-
},
|
|
488
|
-
options: { apiUrl: options.apiUrl },
|
|
98
|
+
return body;
|
|
99
|
+
}
|
|
100
|
+
async function authenticateInstall(input) {
|
|
101
|
+
if (input.bindToken) {
|
|
102
|
+
return exchangePaidBindToken({
|
|
103
|
+
paidBindToken: input.bindToken,
|
|
104
|
+
hostType: input.hostType,
|
|
105
|
+
apiUrl: input.apiUrl,
|
|
489
106
|
});
|
|
490
107
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
}
|
|
507
|
-
else {
|
|
508
|
-
const heartbeatSent = install.wroteFile
|
|
509
|
-
? await sendInstallHeartbeat(options).catch(() => false)
|
|
510
|
-
: false;
|
|
511
|
-
console.log(heartbeatSent ? "Paid workspace heartbeat sent." : "Paid workspace heartbeat not sent yet.");
|
|
512
|
-
}
|
|
513
|
-
console.log(`Next step: ${install.nextStep}`);
|
|
514
|
-
if (options.dryRun) {
|
|
515
|
-
console.log("");
|
|
516
|
-
console.log(uninstall.json);
|
|
517
|
-
console.log("");
|
|
518
|
-
console.log(install.json);
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
async function runDoctorCommand(argv) {
|
|
522
|
-
const serverEntrypoint = argValue(argv, "--server-entrypoint")?.trim();
|
|
523
|
-
const result = await runInitializeHandshake({
|
|
524
|
-
entrypoint: serverEntrypoint ? path.resolve(serverEntrypoint) : undefined,
|
|
108
|
+
const email = await readEmail();
|
|
109
|
+
const started = await startSignup({
|
|
110
|
+
email,
|
|
111
|
+
apiUrl: input.apiUrl,
|
|
112
|
+
clientHint: input.hostType,
|
|
113
|
+
});
|
|
114
|
+
if (!started?.ok)
|
|
115
|
+
throw new Error(String(started?.message ?? started?.code ?? "Could not send signup code."));
|
|
116
|
+
const code = await readCode();
|
|
117
|
+
const completed = await completeSignup({
|
|
118
|
+
email,
|
|
119
|
+
code,
|
|
120
|
+
apiUrl: input.apiUrl,
|
|
121
|
+
clientHint: input.hostType,
|
|
122
|
+
tokenPurpose: "device",
|
|
525
123
|
});
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
throw new Error(`MCP initialize version mismatch: package.json=${expectedVersion} serverInfo.version=${result.serverInfoVersion ?? "missing"}`);
|
|
124
|
+
if (!completed?.ok) {
|
|
125
|
+
throw new Error(String(completed?.message ?? completed?.code ?? "Could not verify signup code."));
|
|
529
126
|
}
|
|
530
|
-
|
|
531
|
-
console.log(`Package version: ${expectedVersion}`);
|
|
532
|
-
console.log(`serverInfo.version: ${result.serverInfoVersion}`);
|
|
127
|
+
return completed;
|
|
533
128
|
}
|
|
534
|
-
async function
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
129
|
+
async function installCommand(argv) {
|
|
130
|
+
const args = parseArgs(argv);
|
|
131
|
+
const hostType = requireHost(stringArg(args, "--host"));
|
|
132
|
+
const baseUrl = apiUrl(args);
|
|
133
|
+
const bindToken = stringArg(args, "--bind") || undefined;
|
|
134
|
+
const skipAuth = boolArg(args, "--skip-auth");
|
|
135
|
+
const dryRun = boolArg(args, "--dry-run");
|
|
136
|
+
let workspaceId;
|
|
137
|
+
if (!skipAuth && !dryRun) {
|
|
138
|
+
const auth = await authenticateInstall({ hostType, bindToken, apiUrl: baseUrl });
|
|
139
|
+
workspaceId = auth.workspace_id ?? null;
|
|
140
|
+
}
|
|
141
|
+
else if (!dryRun && !loadCloudToken()) {
|
|
142
|
+
throw new Error("--skip-auth requires an existing ~/.askthew/cloud-token.json.");
|
|
542
143
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
throw new Error(`Unsupported host "${next}". Expected claude_code, codex, or cursor.`);
|
|
559
|
-
}
|
|
560
|
-
hostType = next;
|
|
561
|
-
index += 1;
|
|
562
|
-
continue;
|
|
563
|
-
}
|
|
564
|
-
if (argument === "--server-name") {
|
|
565
|
-
serverName = next;
|
|
566
|
-
serverNameExplicit = true;
|
|
567
|
-
index += 1;
|
|
568
|
-
continue;
|
|
569
|
-
}
|
|
570
|
-
if (argument === "--version" || argument === "--pin") {
|
|
571
|
-
version = next;
|
|
572
|
-
index += 1;
|
|
573
|
-
continue;
|
|
144
|
+
try {
|
|
145
|
+
const config = installHostConfig({
|
|
146
|
+
hostType,
|
|
147
|
+
apiUrl: baseUrl,
|
|
148
|
+
serverName: DEFAULT_SERVER_NAME,
|
|
149
|
+
dryRun,
|
|
150
|
+
});
|
|
151
|
+
const instructions = writeBehaviorInstructions({ hostType, dryRun });
|
|
152
|
+
if (!dryRun) {
|
|
153
|
+
writeInstallMetadata({
|
|
154
|
+
hostType,
|
|
155
|
+
apiUrl: baseUrl,
|
|
156
|
+
tier: bindToken ? "paid" : "free",
|
|
157
|
+
workspaceId,
|
|
158
|
+
});
|
|
574
159
|
}
|
|
575
|
-
|
|
160
|
+
console.log(`Ask The W installed for ${hostType}.`);
|
|
161
|
+
console.log(`MCP config: ${config.settingsPath}`);
|
|
162
|
+
console.log(`Agent instructions: ${instructions.filePath}`);
|
|
163
|
+
console.log("Restart your coding agent so it loads the new MCP server and instructions.");
|
|
576
164
|
}
|
|
577
|
-
|
|
578
|
-
|
|
165
|
+
catch (error) {
|
|
166
|
+
const message = error instanceof Error ? error.message : "Unknown install failure.";
|
|
167
|
+
console.error(`Install reached auth, but writing local config failed: ${message}`);
|
|
168
|
+
console.error(`Your token is still saved under ${dataDirSummary()}.`);
|
|
169
|
+
console.error(`Retry local writes with: askthew-mcp install --host ${hostType} --skip-auth`);
|
|
170
|
+
process.exitCode = 1;
|
|
579
171
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
}
|
|
172
|
+
}
|
|
173
|
+
function openBrowser(url) {
|
|
174
|
+
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
175
|
+
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
176
|
+
try {
|
|
177
|
+
const child = spawn(command, args, { detached: true, stdio: "ignore" });
|
|
178
|
+
child.unref();
|
|
588
179
|
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
serverName: serverNameExplicit ? serverName : undefined,
|
|
592
|
-
});
|
|
593
|
-
const result = upgradePinnedHostConfig({
|
|
594
|
-
hostType,
|
|
595
|
-
serverName: serverNameExplicit ? serverName : undefined,
|
|
596
|
-
packageSpec,
|
|
597
|
-
dryRun,
|
|
598
|
-
cwds: Array.from(new Set([process.cwd(), ...receipts.map((receipt) => receipt.cwd)])),
|
|
599
|
-
});
|
|
600
|
-
console.log(dryRun ? "Ask The W plugin upgrade dry run complete." : "Ask The W plugin upgrade complete.");
|
|
601
|
-
console.log(`Settings path: ${result.settingsPath}`);
|
|
602
|
-
console.log(`Pinned package: ${result.packageSpec}`);
|
|
603
|
-
console.log(result.upgradedServer
|
|
604
|
-
? `${dryRun ? "Would update" : "Updated"} MCP server pin${result.upgradedServerNames.length ? `: ${result.upgradedServerNames.join(", ")}` : "."}`
|
|
605
|
-
: result.foundConfigFile
|
|
606
|
-
? "No Ask The W MCP server pins needed an update."
|
|
607
|
-
: "No host config file found.");
|
|
608
|
-
if (dryRun && result.json) {
|
|
609
|
-
console.log("");
|
|
610
|
-
console.log(result.json);
|
|
180
|
+
catch {
|
|
181
|
+
// Printing the URL is enough for headless terminals.
|
|
611
182
|
}
|
|
612
183
|
}
|
|
613
|
-
function
|
|
614
|
-
const
|
|
615
|
-
|
|
184
|
+
async function bindCommand(argv) {
|
|
185
|
+
const args = parseArgs(argv);
|
|
186
|
+
const allowPending = boolArg(args, "--allow-pending");
|
|
187
|
+
const client = new AskTheWCloudClient({ apiUrl: apiUrl(args) });
|
|
188
|
+
if (!client.hasToken())
|
|
189
|
+
throw new Error("Run askthew-mcp install first.");
|
|
190
|
+
const outbox = new Outbox();
|
|
191
|
+
if (outbox.pendingCount() > 0) {
|
|
192
|
+
await outbox.flush(client);
|
|
193
|
+
}
|
|
194
|
+
if (outbox.pendingCount() > 0 && !allowPending) {
|
|
195
|
+
const response = await prompts({
|
|
196
|
+
type: "confirm",
|
|
197
|
+
name: "ok",
|
|
198
|
+
message: `${outbox.pendingCount()} captures have not synced yet. Bind anyway?`,
|
|
199
|
+
initial: false,
|
|
200
|
+
});
|
|
201
|
+
if (!response.ok)
|
|
202
|
+
throw new Error("Bind cancelled while captures are pending.");
|
|
203
|
+
}
|
|
204
|
+
const started = await client.request("/paid/bind/start", { method: "POST", body: "{}" });
|
|
205
|
+
const body = started.body;
|
|
206
|
+
if (!started.ok || body.ok === false) {
|
|
207
|
+
throw new Error(String(body.message ?? body.code ?? "Could not start paid binding."));
|
|
208
|
+
}
|
|
209
|
+
const codeDisplay = String(body.code_display ?? "");
|
|
210
|
+
const url = String(body.url ?? "");
|
|
211
|
+
console.log(`Open this URL: ${url}`);
|
|
212
|
+
console.log(`Confirm this terminal code in the browser: ${codeDisplay}`);
|
|
213
|
+
if (url)
|
|
214
|
+
openBrowser(url);
|
|
215
|
+
for (let attempt = 0; attempt < 120; attempt += 1) {
|
|
216
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
217
|
+
const status = await client.request(`/paid/bind/status?code_display=${encodeURIComponent(codeDisplay)}`, { method: "GET" });
|
|
218
|
+
const statusBody = status.body;
|
|
219
|
+
if (status.ok && statusBody.status === "completed") {
|
|
220
|
+
console.log("Ask The W install is now bound to the workspace.");
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
throw new Error("Timed out waiting for workspace binding.");
|
|
616
225
|
}
|
|
617
|
-
|
|
618
|
-
const
|
|
619
|
-
const
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
const
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
226
|
+
async function tokenCommand(argv) {
|
|
227
|
+
const [action] = argv;
|
|
228
|
+
const client = new AskTheWCloudClient();
|
|
229
|
+
if (action === "rotate") {
|
|
230
|
+
const result = await client.request("/token/rotate", { method: "POST", body: "{}" });
|
|
231
|
+
const body = result.body;
|
|
232
|
+
if (!result.ok || body.ok === false)
|
|
233
|
+
throw new Error(String(body.message ?? body.code ?? "Token rotate failed."));
|
|
234
|
+
const tokenFile = loadCloudToken();
|
|
235
|
+
if (tokenFile && typeof body.token === "string") {
|
|
236
|
+
saveCloudToken({ ...tokenFile, token: body.token });
|
|
237
|
+
}
|
|
238
|
+
console.log("Ask The W token rotated. Restart your coding agent.");
|
|
626
239
|
return;
|
|
627
240
|
}
|
|
628
|
-
if (
|
|
629
|
-
const
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
241
|
+
if (action === "revoke") {
|
|
242
|
+
const result = await client.request("/token/revoke", { method: "POST", body: "{}" });
|
|
243
|
+
const body = result.body;
|
|
244
|
+
if (!result.ok || body.ok === false)
|
|
245
|
+
throw new Error(String(body.message ?? body.code ?? "Token revoke failed."));
|
|
246
|
+
console.log("Current Ask The W token revoked.");
|
|
633
247
|
return;
|
|
634
248
|
}
|
|
635
|
-
|
|
636
|
-
throw new Error("Usage: askthew-mcp auth login --email <email> [--no-telemetry] | askthew-mcp auth logout | status");
|
|
637
|
-
}
|
|
638
|
-
ensureAskTheWDataDir();
|
|
639
|
-
const email = argValue(argv, "--email")?.trim();
|
|
640
|
-
if (!email)
|
|
641
|
-
throw new Error("Missing --email.");
|
|
642
|
-
const noTelemetry = argv.includes("--no-telemetry");
|
|
643
|
-
const identity = ensureLocalIdentity({ emailClaim: email, telemetryOptOut: noTelemetry });
|
|
644
|
-
const registration = await registerInstall({
|
|
645
|
-
identity,
|
|
646
|
-
deviceLabel: "askthew-mcp",
|
|
647
|
-
});
|
|
648
|
-
log(`Local free install identified as ${identity.installId}.`);
|
|
649
|
-
log(`Email claim: ${identity.emailClaim ?? "none"} (unverified until upgrade).`);
|
|
650
|
-
log(`Claim code: ${identity.claimCode}`);
|
|
651
|
-
log(registration.ok ? "Registered install with Ask The W." : "Saved locally; cloud registration will retry later.");
|
|
652
|
-
log("No email code is required for free local capture.");
|
|
653
|
-
return;
|
|
249
|
+
throw new Error("Usage: askthew-mcp token rotate|revoke");
|
|
654
250
|
}
|
|
655
|
-
async function
|
|
656
|
-
const
|
|
657
|
-
const
|
|
658
|
-
if (!
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
deviceLabel: "askthew-mcp",
|
|
664
|
-
});
|
|
665
|
-
console.log(`Local free install identified as ${identity.installId}.`);
|
|
666
|
-
console.log(`Email claim: ${identity.emailClaim ?? "none"} (unverified until upgrade).`);
|
|
667
|
-
console.log(`Claim code: ${identity.claimCode}`);
|
|
668
|
-
console.log(registration.ok ? "Registered install with Ask The W." : "Saved locally; cloud registration will retry later.");
|
|
251
|
+
async function exportCommand() {
|
|
252
|
+
const client = new AskTheWCloudClient();
|
|
253
|
+
const result = await client.request("/export", { method: "GET" });
|
|
254
|
+
if (!result.ok) {
|
|
255
|
+
const body = result.body;
|
|
256
|
+
throw new Error(String(body.message ?? body.code ?? "Export failed."));
|
|
257
|
+
}
|
|
258
|
+
console.log(JSON.stringify(result.body, null, 2));
|
|
669
259
|
}
|
|
670
|
-
async function
|
|
671
|
-
const
|
|
672
|
-
if (
|
|
673
|
-
throw new Error("
|
|
674
|
-
}
|
|
675
|
-
const
|
|
676
|
-
if (
|
|
677
|
-
console.
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
260
|
+
async function deleteMeCommand(argv) {
|
|
261
|
+
const args = parseArgs(argv);
|
|
262
|
+
if (!boolArg(args, "--confirm")) {
|
|
263
|
+
throw new Error("Refusing to delete without --confirm.");
|
|
264
|
+
}
|
|
265
|
+
const tokenFile = loadCloudToken();
|
|
266
|
+
if (tokenFile?.tier === "paid") {
|
|
267
|
+
console.error("This install is workspace-bound. Install-scoped data will be deleted; workspace data is retained.");
|
|
268
|
+
}
|
|
269
|
+
const client = new AskTheWCloudClient();
|
|
270
|
+
const result = await client.request("/me", { method: "DELETE" });
|
|
271
|
+
const body = result.body;
|
|
272
|
+
if (!result.ok || body.ok === false)
|
|
273
|
+
throw new Error(String(body.message ?? body.code ?? "Delete failed."));
|
|
274
|
+
console.log(JSON.stringify(body, null, 2));
|
|
682
275
|
}
|
|
683
|
-
async function
|
|
684
|
-
const [
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
throw new Error("No local identity. Run `askthew-mcp identify --email <your-email>` first.");
|
|
688
|
-
if (subcommand === "status") {
|
|
689
|
-
console.log(`Telemetry: ${credentials.telemetryOptOut ? "off" : "on"}`);
|
|
276
|
+
async function main() {
|
|
277
|
+
const [command, ...argv] = process.argv.slice(2);
|
|
278
|
+
if (!command) {
|
|
279
|
+
await runStdioServer();
|
|
690
280
|
return;
|
|
691
281
|
}
|
|
692
|
-
if (
|
|
693
|
-
|
|
694
|
-
console.log(`Telemetry: ${subcommand === "opt-out" ? "off" : "on"}`);
|
|
282
|
+
if (command === "--help" || command === "-h" || command === "help") {
|
|
283
|
+
console.log(usage());
|
|
695
284
|
return;
|
|
696
285
|
}
|
|
697
|
-
if (
|
|
698
|
-
|
|
699
|
-
console.log(JSON.stringify(buildTelemetryPayload({ store, credentials }), null, 2));
|
|
286
|
+
if (command === "--version" || command === "-v" || command === "version") {
|
|
287
|
+
console.log(packageVersion());
|
|
700
288
|
return;
|
|
701
289
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
async function runLocalCommand(argv) {
|
|
705
|
-
const [subcommand, flag] = argv;
|
|
706
|
-
const store = LocalStore.open();
|
|
707
|
-
if (subcommand === "stats") {
|
|
708
|
-
console.log(JSON.stringify(store.stats(), null, 2));
|
|
290
|
+
if (command === "install") {
|
|
291
|
+
await installCommand(argv);
|
|
709
292
|
return;
|
|
710
293
|
}
|
|
711
|
-
if (
|
|
712
|
-
|
|
713
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
714
|
-
console.log("Local Ask The W data removed.");
|
|
294
|
+
if (command === "bind") {
|
|
295
|
+
await bindCommand(argv);
|
|
715
296
|
return;
|
|
716
297
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
if (!argv.includes("--pre-commit")) {
|
|
721
|
-
throw new Error("Usage: askthew-mcp install-hook --pre-commit");
|
|
722
|
-
}
|
|
723
|
-
const hookPath = installPreCommitHook();
|
|
724
|
-
console.log(`Ask The W pre-commit hook installed: ${hookPath}`);
|
|
725
|
-
}
|
|
726
|
-
async function runHookCheckCommand(argv) {
|
|
727
|
-
if (!argv.includes("--pre-commit")) {
|
|
728
|
-
throw new Error("Usage: askthew-mcp hook-check --pre-commit");
|
|
729
|
-
}
|
|
730
|
-
const store = LocalStore.open();
|
|
731
|
-
const gap = preCommitDecisionGap({ store, stagedFiles: stagedFiles(), scopeKey: localScopeKey() });
|
|
732
|
-
if (gap.missing) {
|
|
733
|
-
console.log('Ask The W: this change has no decision attached, draft one?');
|
|
734
|
-
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.");
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
async function runDigestCommand(argv) {
|
|
738
|
-
if (!argv.includes("--weekly")) {
|
|
739
|
-
throw new Error("Usage: askthew-mcp digest --weekly");
|
|
298
|
+
if (command === "token") {
|
|
299
|
+
await tokenCommand(argv);
|
|
300
|
+
return;
|
|
740
301
|
}
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
console.log(`Weekly decision digest written: ${filePath}`);
|
|
744
|
-
}
|
|
745
|
-
async function runSyncCommand(argv) {
|
|
746
|
-
if (argv[0] !== "upload")
|
|
747
|
-
throw new Error("Usage: askthew-mcp sync upload [--token <workspace-install-token>] [--dry-run]");
|
|
748
|
-
const credentials = loadCliCredentials();
|
|
749
|
-
if (!credentials)
|
|
750
|
-
throw new Error("No local identity. Run `askthew-mcp identify --email <your-email>` first.");
|
|
751
|
-
const store = LocalStore.open();
|
|
752
|
-
if (argv.includes("--dry-run")) {
|
|
753
|
-
console.log(JSON.stringify(syncDryRun(store), null, 2));
|
|
302
|
+
if (command === "export") {
|
|
303
|
+
await exportCommand();
|
|
754
304
|
return;
|
|
755
305
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
306
|
+
if (command === "delete-me") {
|
|
307
|
+
await deleteMeCommand(argv);
|
|
308
|
+
return;
|
|
759
309
|
}
|
|
760
|
-
|
|
761
|
-
}
|
|
762
|
-
const isDirectCliExecution = Boolean(process.argv[1]) &&
|
|
763
|
-
(() => {
|
|
764
|
-
const cliPath = fileURLToPath(import.meta.url);
|
|
765
|
-
const invokedPath = path.resolve(process.argv[1]);
|
|
766
|
-
try {
|
|
767
|
-
return fs.realpathSync(invokedPath) === fs.realpathSync(cliPath);
|
|
768
|
-
}
|
|
769
|
-
catch {
|
|
770
|
-
return invokedPath === cliPath;
|
|
771
|
-
}
|
|
772
|
-
})();
|
|
773
|
-
if (isDirectCliExecution) {
|
|
774
|
-
main().catch((error) => {
|
|
775
|
-
if (error instanceof Error) {
|
|
776
|
-
console.error(error.message);
|
|
777
|
-
}
|
|
778
|
-
else {
|
|
779
|
-
console.error("Ask The W plugin failed to start.", error);
|
|
780
|
-
}
|
|
781
|
-
process.exit(1);
|
|
782
|
-
});
|
|
310
|
+
throw new Error(`Unknown command: ${command}\n\n${usage()}`);
|
|
783
311
|
}
|
|
312
|
+
main().catch((error) => {
|
|
313
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
314
|
+
process.exitCode = 1;
|
|
315
|
+
});
|