@askthew/mcp-plugin 0.4.7 → 0.4.8
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 +2 -0
- package/dist/cli.js +51 -10
- package/dist/index.d.ts +4 -0
- package/dist/index.js +88 -11
- package/dist/install.d.ts +26 -0
- package/dist/install.js +88 -12
- package/dist/lib/paths.d.ts +2 -0
- package/dist/lib/paths.js +14 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,6 +43,8 @@ npx -y --prefer-online --package @askthew/mcp-plugin@latest askthew-mcp install
|
|
|
43
43
|
|
|
44
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
45
|
|
|
46
|
+
Installer bookkeeping is separate from local capture data: install receipts live under `~/.local/state/askthew/install-receipts.json` by default, or `$XDG_STATE_HOME/askthew/install-receipts.json` when `XDG_STATE_HOME` is set. This lets `uninstall` remove host config and repo instruction blocks even after `~/.askthew` has been wiped.
|
|
47
|
+
|
|
46
48
|
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
49
|
|
|
48
50
|
## Refresh vs Uninstall
|
package/dist/cli.js
CHANGED
|
@@ -13,16 +13,16 @@ import { LocalStore } from "./lib/local-store.js";
|
|
|
13
13
|
import { buildTelemetryPayload } from "./lib/telemetry.js";
|
|
14
14
|
import { syncDryRun, uploadLocalStore } from "./lib/upgrade-sync.js";
|
|
15
15
|
import { installPreCommitHook, localScopeKey, preCommitDecisionGap, stagedFiles, writeWeeklyDigest, } from "./lib/cli-actions.js";
|
|
16
|
-
import { createHostConfigSnippet, formatInstallCommand, installBehaviorInstructions, installHostConfig, sendInstallHeartbeat, uninstallBehaviorInstructions, uninstallHostConfig, } from "./install.js";
|
|
16
|
+
import { createHostConfigSnippet, findInstallReceipts, formatInstallCommand, installBehaviorInstructions, installHostConfig, removeInstallReceipts, sendInstallHeartbeat, uninstallBehaviorInstructions, uninstallHostConfig, writeInstallReceipt, } from "./install.js";
|
|
17
17
|
function usage() {
|
|
18
18
|
return [
|
|
19
19
|
"Ask The W Coding Agent Connector",
|
|
20
20
|
"",
|
|
21
21
|
"Usage:",
|
|
22
22
|
" askthew-mcp",
|
|
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
|
-
" 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]",
|
|
23
|
+
" 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]",
|
|
24
|
+
" askthew-mcp install --host <claude_code|codex|cursor> --free [--email <email>] [--api-url <url>] [--server-name <name>] [--server-entrypoint <path>]",
|
|
25
|
+
" askthew-mcp refresh --host <claude_code|codex|cursor> [--free] [--token <install-token>] [--api-url <url>] [--server-name <name>] [--server-entrypoint <path>] [--dry-run]",
|
|
26
26
|
" askthew-mcp uninstall --host <claude_code|codex|cursor> [--server-name <name>] [--dry-run] [--keep-local-data] [--keep-auth] [--keep-agent-instructions]",
|
|
27
27
|
" askthew-mcp identify --email <email> [--no-telemetry]",
|
|
28
28
|
" askthew-mcp identity status",
|
|
@@ -47,6 +47,7 @@ function parseInstallArgs(argv) {
|
|
|
47
47
|
let installAgentInstructions = true;
|
|
48
48
|
let free = false;
|
|
49
49
|
let email = process.env.ASKTHEW_EMAIL?.trim() || "";
|
|
50
|
+
let serverEntrypoint = process.env.ASKTHEW_SERVER_ENTRYPOINT?.trim() || "";
|
|
50
51
|
for (let index = 0; index < argv.length; index += 1) {
|
|
51
52
|
const argument = argv[index];
|
|
52
53
|
if (argument === "--dry-run") {
|
|
@@ -103,6 +104,11 @@ function parseInstallArgs(argv) {
|
|
|
103
104
|
index += 1;
|
|
104
105
|
continue;
|
|
105
106
|
}
|
|
107
|
+
if (argument === "--server-entrypoint") {
|
|
108
|
+
serverEntrypoint = next;
|
|
109
|
+
index += 1;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
106
112
|
throw new Error(`Unknown argument: ${argument}`);
|
|
107
113
|
}
|
|
108
114
|
if (!hostType) {
|
|
@@ -128,6 +134,7 @@ function parseInstallArgs(argv) {
|
|
|
128
134
|
installAgentInstructions,
|
|
129
135
|
free,
|
|
130
136
|
email: email || undefined,
|
|
137
|
+
serverEntrypoint: serverEntrypoint ? path.resolve(serverEntrypoint) : undefined,
|
|
131
138
|
};
|
|
132
139
|
}
|
|
133
140
|
function normalizeInstallToken(token) {
|
|
@@ -204,6 +211,17 @@ async function main() {
|
|
|
204
211
|
dryRun: options.dryRun,
|
|
205
212
|
})
|
|
206
213
|
: null;
|
|
214
|
+
const receipt = result.wroteFile
|
|
215
|
+
? writeInstallReceipt({
|
|
216
|
+
hostType: options.hostType,
|
|
217
|
+
serverName: options.serverName,
|
|
218
|
+
settingsPath: result.settingsPath,
|
|
219
|
+
cwd: process.cwd(),
|
|
220
|
+
instructionPaths: instructions?.paths ?? [],
|
|
221
|
+
serverEntrypoint: options.serverEntrypoint,
|
|
222
|
+
packageVersion: packageVersion(),
|
|
223
|
+
})
|
|
224
|
+
: null;
|
|
207
225
|
const heartbeatSent = result.wroteFile && !options.free
|
|
208
226
|
? await sendInstallHeartbeat(options).catch(() => false)
|
|
209
227
|
: false;
|
|
@@ -212,6 +230,9 @@ async function main() {
|
|
|
212
230
|
if (instructions) {
|
|
213
231
|
console.log(`Agent instructions: ${instructions.paths?.join(", ") ?? instructions.path}`);
|
|
214
232
|
}
|
|
233
|
+
if (receipt) {
|
|
234
|
+
console.log(`Install receipt: ${receipt.hostType}/${receipt.serverName} at ${receipt.cwd}`);
|
|
235
|
+
}
|
|
215
236
|
console.log(`Install command: ${formatInstallCommand(options)}`);
|
|
216
237
|
if (result.wroteFile) {
|
|
217
238
|
if (freeIdentity) {
|
|
@@ -344,14 +365,17 @@ async function runUninstallCommand(argv) {
|
|
|
344
365
|
if (!hostType) {
|
|
345
366
|
throw new Error("Usage: askthew-mcp uninstall --host <claude_code|codex|cursor> [--server-name <name>]");
|
|
346
367
|
}
|
|
368
|
+
const receipts = findInstallReceipts({ hostType, serverName });
|
|
347
369
|
const config = uninstallHostConfig({ hostType, serverName, dryRun });
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
370
|
+
const instructionCwds = Array.from(new Set([process.cwd(), ...receipts.map((receipt) => receipt.cwd)]));
|
|
371
|
+
const instructionPaths = keepAgentInstructions
|
|
372
|
+
? []
|
|
373
|
+
: instructionCwds.flatMap((cwd) => uninstallBehaviorInstructions({ hostType, dryRun, cwd }).paths);
|
|
351
374
|
const dataDir = askTheWDataDir();
|
|
352
375
|
const hadLocalData = fs.existsSync(dataDir);
|
|
353
376
|
const authFile = identityPath();
|
|
354
377
|
const hadAuth = fs.existsSync(authFile);
|
|
378
|
+
const removedReceipts = dryRun ? receipts.length : removeInstallReceipts({ hostType, serverName });
|
|
355
379
|
if (!keepLocalData && !dryRun) {
|
|
356
380
|
fs.rmSync(dataDir, { recursive: true, force: true });
|
|
357
381
|
}
|
|
@@ -366,11 +390,14 @@ async function runUninstallCommand(argv) {
|
|
|
366
390
|
: config.foundConfigFile
|
|
367
391
|
? `No MCP server "${config.removedServerName}" found in host config.`
|
|
368
392
|
: "No host config file found.");
|
|
369
|
-
if (
|
|
370
|
-
console.log(
|
|
371
|
-
? `Agent instructions ${dryRun ? "would be removed from" : "removed from"}: ${
|
|
393
|
+
if (!keepAgentInstructions) {
|
|
394
|
+
console.log(instructionPaths.length > 0
|
|
395
|
+
? `Agent instructions ${dryRun ? "would be removed from" : "removed from"}: ${Array.from(new Set(instructionPaths)).join(", ")}`
|
|
372
396
|
: "No Ask The W agent instruction blocks found.");
|
|
373
397
|
}
|
|
398
|
+
console.log(removedReceipts > 0
|
|
399
|
+
? `${dryRun ? "Would remove" : "Removed"} ${removedReceipts} install receipt${removedReceipts === 1 ? "" : "s"}.`
|
|
400
|
+
: "No install receipts found.");
|
|
374
401
|
console.log(keepLocalData
|
|
375
402
|
? "Local data kept."
|
|
376
403
|
: hadLocalData
|
|
@@ -417,6 +444,17 @@ async function runRefreshCommand(argv) {
|
|
|
417
444
|
dryRun: options.dryRun,
|
|
418
445
|
})
|
|
419
446
|
: null;
|
|
447
|
+
const receipt = install.wroteFile
|
|
448
|
+
? writeInstallReceipt({
|
|
449
|
+
hostType: options.hostType,
|
|
450
|
+
serverName: options.serverName,
|
|
451
|
+
settingsPath: install.settingsPath,
|
|
452
|
+
cwd: process.cwd(),
|
|
453
|
+
instructionPaths: installedInstructions?.paths ?? [],
|
|
454
|
+
serverEntrypoint: options.serverEntrypoint,
|
|
455
|
+
packageVersion: packageVersion(),
|
|
456
|
+
})
|
|
457
|
+
: null;
|
|
420
458
|
if (options.free && freeIdentity && !options.dryRun) {
|
|
421
459
|
await tryRegisterFreeInstall({
|
|
422
460
|
identity: freeIdentity,
|
|
@@ -436,6 +474,9 @@ async function runRefreshCommand(argv) {
|
|
|
436
474
|
if (installedInstructions) {
|
|
437
475
|
console.log(`Installed instructions: ${installedInstructions.paths.join(", ") || installedInstructions.path}`);
|
|
438
476
|
}
|
|
477
|
+
if (receipt) {
|
|
478
|
+
console.log(`Install receipt: ${receipt.hostType}/${receipt.serverName} at ${receipt.cwd}`);
|
|
479
|
+
}
|
|
439
480
|
if (options.free) {
|
|
440
481
|
console.log(freeIdentity
|
|
441
482
|
? `Local identity preserved: ${freeIdentity.installId}`
|
package/dist/index.d.ts
CHANGED
|
@@ -158,4 +158,8 @@ export interface AskTheWMcpServerOptions {
|
|
|
158
158
|
}
|
|
159
159
|
export declare function normalizeInstallTokenInput(token: string | undefined): string;
|
|
160
160
|
export declare function createAskTheWMcpServer(options?: AskTheWMcpServerOptions): McpServer;
|
|
161
|
+
export declare function runInitializeHandshake(input?: {
|
|
162
|
+
entrypoint?: string;
|
|
163
|
+
timeoutMs?: number;
|
|
164
|
+
}): Promise<void>;
|
|
161
165
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
3
4
|
import fs from "node:fs";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
@@ -1914,16 +1915,92 @@ const isDirectIndexExecution = Boolean(process.argv[1]) &&
|
|
|
1914
1915
|
return invokedPath === modulePath;
|
|
1915
1916
|
}
|
|
1916
1917
|
})();
|
|
1917
|
-
|
|
1918
|
-
const
|
|
1919
|
-
const
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
}
|
|
1924
|
-
else {
|
|
1925
|
-
console.error("Ask The W MCP server failed to start.", error);
|
|
1926
|
-
}
|
|
1927
|
-
process.exit(1);
|
|
1918
|
+
export async function runInitializeHandshake(input = {}) {
|
|
1919
|
+
const modulePath = input.entrypoint ?? fileURLToPath(import.meta.url);
|
|
1920
|
+
const child = spawn(process.execPath, [modulePath], {
|
|
1921
|
+
cwd: process.cwd(),
|
|
1922
|
+
env: process.env,
|
|
1923
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1928
1924
|
});
|
|
1925
|
+
let stdout = "";
|
|
1926
|
+
let stderr = "";
|
|
1927
|
+
child.stdout.setEncoding("utf8");
|
|
1928
|
+
child.stderr.setEncoding("utf8");
|
|
1929
|
+
child.stdout.on("data", (chunk) => {
|
|
1930
|
+
stdout += chunk;
|
|
1931
|
+
});
|
|
1932
|
+
child.stderr.on("data", (chunk) => {
|
|
1933
|
+
stderr += chunk;
|
|
1934
|
+
});
|
|
1935
|
+
const initialize = {
|
|
1936
|
+
jsonrpc: "2.0",
|
|
1937
|
+
id: 1,
|
|
1938
|
+
method: "initialize",
|
|
1939
|
+
params: {
|
|
1940
|
+
protocolVersion: "2024-11-05",
|
|
1941
|
+
capabilities: {},
|
|
1942
|
+
clientInfo: { name: "askthew-initialize-handshake", version: "1.0.0" },
|
|
1943
|
+
},
|
|
1944
|
+
};
|
|
1945
|
+
child.stdin.write(`${JSON.stringify(initialize)}\n`);
|
|
1946
|
+
try {
|
|
1947
|
+
await new Promise((resolve, reject) => {
|
|
1948
|
+
const timeout = setTimeout(() => {
|
|
1949
|
+
reject(new Error(`Timed out waiting for initialize response. stdout=${stdout} stderr=${stderr}`));
|
|
1950
|
+
}, input.timeoutMs ?? 3000);
|
|
1951
|
+
const failOnExit = (code) => {
|
|
1952
|
+
clearTimeout(timeout);
|
|
1953
|
+
reject(new Error(`MCP stdio server exited before initialize. code=${code} stdout=${stdout} stderr=${stderr}`));
|
|
1954
|
+
};
|
|
1955
|
+
const check = () => {
|
|
1956
|
+
const lineEnd = stdout.indexOf("\n");
|
|
1957
|
+
if (lineEnd === -1)
|
|
1958
|
+
return;
|
|
1959
|
+
clearTimeout(timeout);
|
|
1960
|
+
child.off("exit", failOnExit);
|
|
1961
|
+
const response = JSON.parse(stdout.slice(0, lineEnd));
|
|
1962
|
+
if (response.id !== 1 || response.jsonrpc !== "2.0" || !response.result) {
|
|
1963
|
+
reject(new Error(`Unexpected initialize response: ${JSON.stringify(response)}`));
|
|
1964
|
+
return;
|
|
1965
|
+
}
|
|
1966
|
+
resolve();
|
|
1967
|
+
};
|
|
1968
|
+
child.stdout.on("data", check);
|
|
1969
|
+
child.once("exit", failOnExit);
|
|
1970
|
+
check();
|
|
1971
|
+
});
|
|
1972
|
+
}
|
|
1973
|
+
finally {
|
|
1974
|
+
child.kill();
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
if (isDirectIndexExecution) {
|
|
1978
|
+
if (process.argv[2] === "initialize-handshake") {
|
|
1979
|
+
runInitializeHandshake()
|
|
1980
|
+
.then(() => {
|
|
1981
|
+
console.log("Ask The W MCP initialize handshake succeeded.");
|
|
1982
|
+
})
|
|
1983
|
+
.catch((error) => {
|
|
1984
|
+
if (error instanceof Error) {
|
|
1985
|
+
console.error(error.message);
|
|
1986
|
+
}
|
|
1987
|
+
else {
|
|
1988
|
+
console.error("Ask The W MCP initialize handshake failed.", error);
|
|
1989
|
+
}
|
|
1990
|
+
process.exit(1);
|
|
1991
|
+
});
|
|
1992
|
+
}
|
|
1993
|
+
else {
|
|
1994
|
+
const server = createAskTheWMcpServer();
|
|
1995
|
+
const transport = new StdioServerTransport();
|
|
1996
|
+
server.connect(transport).catch((error) => {
|
|
1997
|
+
if (error instanceof Error) {
|
|
1998
|
+
console.error(error.message);
|
|
1999
|
+
}
|
|
2000
|
+
else {
|
|
2001
|
+
console.error("Ask The W MCP server failed to start.", error);
|
|
2002
|
+
}
|
|
2003
|
+
process.exit(1);
|
|
2004
|
+
});
|
|
2005
|
+
}
|
|
1929
2006
|
}
|
package/dist/install.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ interface HostConfigInput {
|
|
|
9
9
|
free?: boolean;
|
|
10
10
|
email?: string;
|
|
11
11
|
cwd?: string;
|
|
12
|
+
serverEntrypoint?: string;
|
|
12
13
|
}
|
|
13
14
|
interface InstallHostConfigInput extends HostConfigInput {
|
|
14
15
|
dryRun?: boolean;
|
|
@@ -22,6 +23,17 @@ interface UninstallHostConfigInput {
|
|
|
22
23
|
homeDirectory?: string;
|
|
23
24
|
cwd?: string;
|
|
24
25
|
}
|
|
26
|
+
export interface InstallReceipt {
|
|
27
|
+
hostType: SupportedHostType;
|
|
28
|
+
serverName: string;
|
|
29
|
+
settingsPath: string;
|
|
30
|
+
cwd: string;
|
|
31
|
+
instructionPaths: string[];
|
|
32
|
+
dataDir: string;
|
|
33
|
+
serverEntrypoint?: string;
|
|
34
|
+
installedAt: string;
|
|
35
|
+
packageVersion?: string;
|
|
36
|
+
}
|
|
25
37
|
export declare function resolveSettingsPath(input: {
|
|
26
38
|
hostType: SupportedHostType;
|
|
27
39
|
homeDirectory?: string;
|
|
@@ -107,6 +119,20 @@ export declare function installHostConfig(input: InstallHostConfigInput): {
|
|
|
107
119
|
wroteFile: boolean;
|
|
108
120
|
nextStep: string;
|
|
109
121
|
};
|
|
122
|
+
export declare function readInstallReceipts(env?: NodeJS.ProcessEnv): InstallReceipt[];
|
|
123
|
+
export declare function writeInstallReceipt(receipt: Omit<InstallReceipt, "dataDir" | "installedAt"> & {
|
|
124
|
+
dataDir?: string;
|
|
125
|
+
installedAt?: string;
|
|
126
|
+
}, env?: NodeJS.ProcessEnv): InstallReceipt;
|
|
127
|
+
export declare function findInstallReceipts(input: {
|
|
128
|
+
hostType: SupportedHostType;
|
|
129
|
+
serverName?: string;
|
|
130
|
+
}, env?: NodeJS.ProcessEnv): InstallReceipt[];
|
|
131
|
+
export declare function removeInstallReceipts(input: {
|
|
132
|
+
hostType: SupportedHostType;
|
|
133
|
+
serverName?: string;
|
|
134
|
+
cwd?: string;
|
|
135
|
+
}, env?: NodeJS.ProcessEnv): number;
|
|
110
136
|
export declare function uninstallHostConfig(input: UninstallHostConfigInput): {
|
|
111
137
|
settingsPath: string;
|
|
112
138
|
json: string;
|
package/dist/install.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { askTheWDataDir, installReceiptsPath, readJsonFile, writePrivateJson } from "./lib/paths.js";
|
|
4
5
|
import { resolvePluginScope } from "./scope.js";
|
|
5
6
|
const ASKTHEW_INSTRUCTIONS_START = "<!-- ASKTHEW_PLUGIN_INSTRUCTIONS_START -->";
|
|
6
7
|
const ASKTHEW_INSTRUCTIONS_END = "<!-- ASKTHEW_PLUGIN_INSTRUCTIONS_END -->";
|
|
8
|
+
const INSTALL_RECEIPTS_SCHEMA_VERSION = 1;
|
|
7
9
|
function isRecord(value) {
|
|
8
10
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9
11
|
}
|
|
@@ -19,21 +21,29 @@ export function resolveSettingsPath(input) {
|
|
|
19
21
|
}
|
|
20
22
|
export function createServerEntry(input) {
|
|
21
23
|
const scope = resolvePluginScope(input.cwd ?? process.cwd());
|
|
24
|
+
const env = {
|
|
25
|
+
ASKTHEW_API_URL: input.apiUrl,
|
|
26
|
+
...(input.free ? { ASKTHEW_FREE_MODE: "1" } : { ASKTHEW_INSTALL_TOKEN: input.token ?? "" }),
|
|
27
|
+
...(input.clientId ? { ASKTHEW_CLIENT_ID: input.clientId } : {}),
|
|
28
|
+
...(input.clientLabel ? { ASKTHEW_CLIENT_LABEL: input.clientLabel } : {}),
|
|
29
|
+
ASKTHEW_HOST_TYPE: input.hostType,
|
|
30
|
+
ASKTHEW_SERVER_NAME: input.serverName,
|
|
31
|
+
ASKTHEW_REPO_NAME: scope.repoName,
|
|
32
|
+
...(scope.repoRoot ? { ASKTHEW_REPO_ROOT: scope.repoRoot } : {}),
|
|
33
|
+
...(scope.appPath ? { ASKTHEW_APP_PATH: scope.appPath } : {}),
|
|
34
|
+
...(scope.serviceName ? { ASKTHEW_SERVICE_NAME: scope.serviceName } : {}),
|
|
35
|
+
};
|
|
36
|
+
if (input.serverEntrypoint) {
|
|
37
|
+
return {
|
|
38
|
+
command: "node",
|
|
39
|
+
args: [path.resolve(input.serverEntrypoint)],
|
|
40
|
+
env,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
22
43
|
return {
|
|
23
44
|
command: "npx",
|
|
24
45
|
args: ["-y", "--prefer-online", "--package", "@askthew/mcp-plugin@latest", "askthew-mcp"],
|
|
25
|
-
env
|
|
26
|
-
ASKTHEW_API_URL: input.apiUrl,
|
|
27
|
-
...(input.free ? { ASKTHEW_FREE_MODE: "1" } : { ASKTHEW_INSTALL_TOKEN: input.token ?? "" }),
|
|
28
|
-
...(input.clientId ? { ASKTHEW_CLIENT_ID: input.clientId } : {}),
|
|
29
|
-
...(input.clientLabel ? { ASKTHEW_CLIENT_LABEL: input.clientLabel } : {}),
|
|
30
|
-
ASKTHEW_HOST_TYPE: input.hostType,
|
|
31
|
-
ASKTHEW_SERVER_NAME: input.serverName,
|
|
32
|
-
ASKTHEW_REPO_NAME: scope.repoName,
|
|
33
|
-
...(scope.repoRoot ? { ASKTHEW_REPO_ROOT: scope.repoRoot } : {}),
|
|
34
|
-
...(scope.appPath ? { ASKTHEW_APP_PATH: scope.appPath } : {}),
|
|
35
|
-
...(scope.serviceName ? { ASKTHEW_SERVICE_NAME: scope.serviceName } : {}),
|
|
36
|
-
},
|
|
46
|
+
env,
|
|
37
47
|
};
|
|
38
48
|
}
|
|
39
49
|
export function createHostConfigSnippet(input) {
|
|
@@ -150,6 +160,9 @@ export function formatInstallCommand(input) {
|
|
|
150
160
|
parts.push("--email", JSON.stringify(input.email));
|
|
151
161
|
}
|
|
152
162
|
}
|
|
163
|
+
if (input.serverEntrypoint) {
|
|
164
|
+
parts.push("--server-entrypoint", JSON.stringify(input.serverEntrypoint));
|
|
165
|
+
}
|
|
153
166
|
return parts.join(" ");
|
|
154
167
|
}
|
|
155
168
|
export function verificationNextStep(hostType) {
|
|
@@ -188,6 +201,7 @@ export function installHostConfig(input) {
|
|
|
188
201
|
clientLabel: input.clientLabel,
|
|
189
202
|
free: input.free,
|
|
190
203
|
cwd: input.cwd,
|
|
204
|
+
serverEntrypoint: input.serverEntrypoint,
|
|
191
205
|
};
|
|
192
206
|
const json = input.hostType === "codex"
|
|
193
207
|
? mergeCodexSettings({
|
|
@@ -211,6 +225,68 @@ export function installHostConfig(input) {
|
|
|
211
225
|
nextStep: verificationNextStep(input.hostType),
|
|
212
226
|
};
|
|
213
227
|
}
|
|
228
|
+
function normalizeInstructionPaths(paths) {
|
|
229
|
+
return Array.from(new Set(paths.map((entry) => path.resolve(entry))));
|
|
230
|
+
}
|
|
231
|
+
function normalizeReceipt(receipt) {
|
|
232
|
+
return {
|
|
233
|
+
...receipt,
|
|
234
|
+
settingsPath: path.resolve(receipt.settingsPath),
|
|
235
|
+
cwd: path.resolve(receipt.cwd),
|
|
236
|
+
instructionPaths: normalizeInstructionPaths(receipt.instructionPaths ?? []),
|
|
237
|
+
dataDir: path.resolve(receipt.dataDir || askTheWDataDir()),
|
|
238
|
+
serverEntrypoint: receipt.serverEntrypoint ? path.resolve(receipt.serverEntrypoint) : undefined,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function receiptKey(receipt) {
|
|
242
|
+
return `${receipt.hostType}\u0000${receipt.serverName}\u0000${path.resolve(receipt.cwd)}`;
|
|
243
|
+
}
|
|
244
|
+
export function readInstallReceipts(env = process.env) {
|
|
245
|
+
const parsed = readJsonFile(installReceiptsPath(env));
|
|
246
|
+
if (!parsed || parsed.schemaVersion !== INSTALL_RECEIPTS_SCHEMA_VERSION || !Array.isArray(parsed.installs)) {
|
|
247
|
+
return [];
|
|
248
|
+
}
|
|
249
|
+
return parsed.installs
|
|
250
|
+
.filter((receipt) => Boolean(receipt?.hostType && receipt.serverName && receipt.settingsPath && receipt.cwd))
|
|
251
|
+
.map(normalizeReceipt);
|
|
252
|
+
}
|
|
253
|
+
export function writeInstallReceipt(receipt, env = process.env) {
|
|
254
|
+
const nextReceipt = normalizeReceipt({
|
|
255
|
+
...receipt,
|
|
256
|
+
dataDir: receipt.dataDir ?? askTheWDataDir(env),
|
|
257
|
+
installedAt: receipt.installedAt ?? new Date().toISOString(),
|
|
258
|
+
});
|
|
259
|
+
const receipts = readInstallReceipts(env).filter((entry) => receiptKey(entry) !== receiptKey(nextReceipt));
|
|
260
|
+
receipts.push(nextReceipt);
|
|
261
|
+
receipts.sort((left, right) => left.installedAt.localeCompare(right.installedAt));
|
|
262
|
+
writePrivateJson(installReceiptsPath(env), {
|
|
263
|
+
schemaVersion: INSTALL_RECEIPTS_SCHEMA_VERSION,
|
|
264
|
+
installs: receipts,
|
|
265
|
+
});
|
|
266
|
+
return nextReceipt;
|
|
267
|
+
}
|
|
268
|
+
export function findInstallReceipts(input, env = process.env) {
|
|
269
|
+
return readInstallReceipts(env).filter((receipt) => receipt.hostType === input.hostType &&
|
|
270
|
+
(!input.serverName || receipt.serverName === input.serverName));
|
|
271
|
+
}
|
|
272
|
+
export function removeInstallReceipts(input, env = process.env) {
|
|
273
|
+
const cwd = input.cwd ? path.resolve(input.cwd) : undefined;
|
|
274
|
+
const receipts = readInstallReceipts(env);
|
|
275
|
+
const next = receipts.filter((receipt) => {
|
|
276
|
+
if (receipt.hostType !== input.hostType)
|
|
277
|
+
return true;
|
|
278
|
+
if (input.serverName && receipt.serverName !== input.serverName)
|
|
279
|
+
return true;
|
|
280
|
+
if (cwd && receipt.cwd !== cwd)
|
|
281
|
+
return true;
|
|
282
|
+
return false;
|
|
283
|
+
});
|
|
284
|
+
writePrivateJson(installReceiptsPath(env), {
|
|
285
|
+
schemaVersion: INSTALL_RECEIPTS_SCHEMA_VERSION,
|
|
286
|
+
installs: next,
|
|
287
|
+
});
|
|
288
|
+
return receipts.length - next.length;
|
|
289
|
+
}
|
|
214
290
|
export function uninstallHostConfig(input) {
|
|
215
291
|
const settingsPath = resolveSettingsPath({
|
|
216
292
|
hostType: input.hostType,
|
package/dist/lib/paths.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export declare function askTheWDataDir(env?: NodeJS.ProcessEnv): string;
|
|
2
|
+
export declare function askTheWStateDir(env?: NodeJS.ProcessEnv): string;
|
|
2
3
|
export declare function ensureAskTheWDataDir(env?: NodeJS.ProcessEnv): string;
|
|
3
4
|
export declare function localStorePath(env?: NodeJS.ProcessEnv): string;
|
|
4
5
|
export declare function identityPath(env?: NodeJS.ProcessEnv): string;
|
|
5
6
|
export declare function configPath(env?: NodeJS.ProcessEnv): string;
|
|
6
7
|
export declare function jsonFallbackStorePath(env?: NodeJS.ProcessEnv): string;
|
|
8
|
+
export declare function installReceiptsPath(env?: NodeJS.ProcessEnv): string;
|
|
7
9
|
export declare function writePrivateJson(filePath: string, value: unknown): void;
|
|
8
10
|
export declare function readJsonFile<T>(filePath: string): T | null;
|
package/dist/lib/paths.js
CHANGED
|
@@ -12,6 +12,17 @@ export function askTheWDataDir(env = process.env) {
|
|
|
12
12
|
}
|
|
13
13
|
return path.join(os.homedir(), ".askthew");
|
|
14
14
|
}
|
|
15
|
+
export function askTheWStateDir(env = process.env) {
|
|
16
|
+
const explicit = env.ASKTHEW_STATE_DIR?.trim();
|
|
17
|
+
if (explicit) {
|
|
18
|
+
return path.resolve(explicit);
|
|
19
|
+
}
|
|
20
|
+
const xdgStateHome = env.XDG_STATE_HOME?.trim();
|
|
21
|
+
if (xdgStateHome) {
|
|
22
|
+
return path.join(path.resolve(xdgStateHome), "askthew");
|
|
23
|
+
}
|
|
24
|
+
return path.join(os.homedir(), ".local", "state", "askthew");
|
|
25
|
+
}
|
|
15
26
|
export function ensureAskTheWDataDir(env = process.env) {
|
|
16
27
|
const dir = askTheWDataDir(env);
|
|
17
28
|
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
@@ -29,6 +40,9 @@ export function configPath(env = process.env) {
|
|
|
29
40
|
export function jsonFallbackStorePath(env = process.env) {
|
|
30
41
|
return path.join(askTheWDataDir(env), "store.json");
|
|
31
42
|
}
|
|
43
|
+
export function installReceiptsPath(env = process.env) {
|
|
44
|
+
return path.join(askTheWStateDir(env), "install-receipts.json");
|
|
45
|
+
}
|
|
32
46
|
export function writePrivateJson(filePath, value) {
|
|
33
47
|
fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
34
48
|
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|