riskmodels 2.0.0 → 2.0.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 +5 -3
- package/dist/commands/decompose.d.ts +2 -0
- package/dist/commands/decompose.js +31 -0
- package/dist/commands/doctor.js +18 -8
- package/dist/commands/install.js +43 -16
- package/dist/commands/status.js +13 -4
- package/dist/commands/uninstall.js +18 -8
- package/dist/index.js +5 -1
- package/dist/lib/cli-human-diagnostics.d.ts +21 -0
- package/dist/lib/cli-human-diagnostics.js +49 -0
- package/dist/lib/cli-version.d.ts +4 -0
- package/dist/lib/cli-version.js +25 -0
- package/dist/lib/config.d.ts +2 -0
- package/dist/lib/config.js +13 -1
- package/dist/lib/global-json-hint.d.ts +4 -0
- package/dist/lib/global-json-hint.js +16 -0
- package/dist/lib/mcp-cli-human-output.d.ts +28 -0
- package/dist/lib/mcp-cli-human-output.js +224 -0
- package/dist/lib/path-display.d.ts +2 -0
- package/dist/lib/path-display.js +18 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,10 +8,12 @@ Command-line interface for [RiskModels](https://riskmodels.app): call the REST A
|
|
|
8
8
|
npm install -g riskmodels
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Agent-first installer
|
|
11
|
+
Agent-first installer (pin `@latest` so `npx` does not resolve a stale cached package):
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
|
|
14
|
+
# Prerequisites: Node.js LTS (includes npx). macOS/Homebrew: brew install node; others: https://nodejs.org
|
|
15
|
+
RISKMODELS_API_KEY=rm_agent_live_... npx -y riskmodels@latest install --dry-run # optional: inspect plan first
|
|
16
|
+
RISKMODELS_API_KEY=rm_agent_live_... npx -y riskmodels@latest install
|
|
15
17
|
```
|
|
16
18
|
|
|
17
19
|
Local development can run the same flow with:
|
|
@@ -101,4 +103,4 @@ Publishing (`npm publish`, version bumps, npm login / tokens) is **not** documen
|
|
|
101
103
|
|
|
102
104
|
**Rule of thumb:** run publish commands only from **`cli/`**; the repo root package is the Next.js portal, not this CLI.
|
|
103
105
|
|
|
104
|
-
The legacy `riskmodels-cli` npm package remains a backward-compatible published artifact. New releases from this directory target the unscoped `riskmodels` package
|
|
106
|
+
The legacy `riskmodels-cli` npm package remains a backward-compatible published artifact. New releases from this directory target the unscoped `riskmodels` package; use `npx -y riskmodels@latest install` for a deterministic one-shot MCP setup.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { loadConfig } from "../lib/config.js";
|
|
4
|
+
import { requireResolvedAuth } from "../lib/credentials.js";
|
|
5
|
+
import { apiFetchJson } from "../lib/api-client.js";
|
|
6
|
+
import { printResults } from "../lib/display.js";
|
|
7
|
+
export function decomposeCommand() {
|
|
8
|
+
return new Command("decompose")
|
|
9
|
+
.description("Four-layer decomposition + hedge map for one ticker (POST /decompose)")
|
|
10
|
+
.argument("<ticker>", "Ticker symbol, e.g. NVDA")
|
|
11
|
+
.action(async (ticker, _opts, cmd) => {
|
|
12
|
+
const json = cmd.optsWithGlobals().json ?? false;
|
|
13
|
+
const cfg = await loadConfig();
|
|
14
|
+
const auth = requireResolvedAuth(cfg, chalk.yellow);
|
|
15
|
+
if (!auth)
|
|
16
|
+
return;
|
|
17
|
+
const t = ticker.trim();
|
|
18
|
+
try {
|
|
19
|
+
const { body, costUsd } = await apiFetchJson(auth, "POST", "/decompose", {
|
|
20
|
+
jsonBody: { ticker: t },
|
|
21
|
+
});
|
|
22
|
+
printResults(body, json);
|
|
23
|
+
if (!json && costUsd)
|
|
24
|
+
console.log(chalk.dim(`Cost: $${costUsd}`));
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
console.error(chalk.red(e instanceof Error ? e.message : String(e)));
|
|
28
|
+
process.exitCode = 1;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -3,14 +3,17 @@ import { Command } from "commander";
|
|
|
3
3
|
import { configPath, loadConfig, maskSecret } from "../lib/config.js";
|
|
4
4
|
import { detectClients } from "../lib/mcp-config-paths.js";
|
|
5
5
|
import { printResults } from "../lib/display.js";
|
|
6
|
+
import { printDoctorHuman } from "../lib/cli-human-diagnostics.js";
|
|
7
|
+
import { warnIfGlobalJsonBeforeSubcommand } from "../lib/global-json-hint.js";
|
|
8
|
+
import { abbreviatePath } from "../lib/path-display.js";
|
|
6
9
|
function commandOk(command, args = ["--version"]) {
|
|
7
10
|
const result = spawnSync(command, args, { stdio: "ignore" });
|
|
8
11
|
return result.status === 0 || result.status === 1;
|
|
9
12
|
}
|
|
10
13
|
export function doctorCommand() {
|
|
11
14
|
return new Command("doctor")
|
|
12
|
-
.description("Run local diagnostics for RiskModels CLI and MCP install readiness")
|
|
13
|
-
.option("--json", "JSON
|
|
15
|
+
.description("Run local diagnostics for RiskModels CLI and MCP install readiness (default: readable summary; use --json for scripts)")
|
|
16
|
+
.option("--json", "Structured JSON instead of readable summary")
|
|
14
17
|
.action(async (opts, cmd) => {
|
|
15
18
|
const json = opts.json || cmd.optsWithGlobals().json || false;
|
|
16
19
|
const cfg = await loadConfig();
|
|
@@ -24,28 +27,35 @@ export function doctorCommand() {
|
|
|
24
27
|
{
|
|
25
28
|
id: "npx",
|
|
26
29
|
ok: commandOk("npx"),
|
|
27
|
-
detail: "Required
|
|
30
|
+
detail: "Required so MCP configs can run `npx -y @riskmodels/mcp` (stdio server from `riskmodels install`).",
|
|
28
31
|
},
|
|
29
32
|
{
|
|
30
33
|
id: "api_credentials",
|
|
31
34
|
ok: !!cfg?.apiKey || !!process.env.RISKMODELS_API_KEY || (!!cfg?.clientId && !!cfg?.clientSecret),
|
|
32
35
|
detail: cfg?.apiKey
|
|
33
|
-
? `Config API key ${maskSecret(cfg.apiKey)} in ${configPath()}`
|
|
36
|
+
? `Config API key ${maskSecret(cfg.apiKey)} in ${abbreviatePath(configPath())}`
|
|
34
37
|
: process.env.RISKMODELS_API_KEY
|
|
35
38
|
? "RISKMODELS_API_KEY is set in the environment."
|
|
36
|
-
: "No API key found. Get one at https://riskmodels.app/get-
|
|
39
|
+
: "No API key found. Get one at https://riskmodels.app/get-key",
|
|
37
40
|
},
|
|
38
41
|
{
|
|
39
42
|
id: "mcp_package_target",
|
|
40
43
|
ok: true,
|
|
41
|
-
detail: "
|
|
44
|
+
detail: "`riskmodels install` merges `npx -y @riskmodels/mcp` into detected client configs (stdio MCP).",
|
|
42
45
|
},
|
|
43
46
|
];
|
|
44
|
-
|
|
47
|
+
const payload = {
|
|
45
48
|
ok: checks.every((check) => check.ok),
|
|
46
49
|
checks,
|
|
47
50
|
clients,
|
|
48
51
|
note: "Run `riskmodels install --dry-run` to inspect config writes, or `riskmodels install` to write configs with backups and run a connection test.",
|
|
49
|
-
}
|
|
52
|
+
};
|
|
53
|
+
if (json) {
|
|
54
|
+
warnIfGlobalJsonBeforeSubcommand("doctor");
|
|
55
|
+
printResults(payload, json);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
printDoctorHuman(payload);
|
|
59
|
+
}
|
|
50
60
|
});
|
|
51
61
|
}
|
package/dist/commands/install.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import inquirer from "inquirer";
|
|
3
3
|
import chalk from "chalk";
|
|
4
|
-
import { configPath, DEFAULT_API_BASE, loadConfig } from "../lib/config.js";
|
|
4
|
+
import { configPath, DEFAULT_API_BASE, loadConfig, sharedRiskmodelsConfigExists, } from "../lib/config.js";
|
|
5
5
|
import { detectClients, selectedClients } from "../lib/mcp-config-paths.js";
|
|
6
6
|
import { buildInstallPlans, firstPrompt } from "../lib/mcp-install-plan.js";
|
|
7
7
|
import { printResults } from "../lib/display.js";
|
|
8
|
+
import { API_KEY_EMAIL_HINT, printInstallDryRunHuman, printInstallMissingKeyHuman, printInstallSuccessHuman, } from "../lib/mcp-cli-human-output.js";
|
|
8
9
|
import { redactSecret } from "../lib/redact.js";
|
|
9
10
|
import { installMcpConfig, writeSharedApiKey } from "../lib/mcp-config-writer.js";
|
|
10
11
|
import { apiRootFromUserBase } from "../lib/api-url.js";
|
|
11
12
|
import { apiFetchOptionalAuth } from "../lib/api-client.js";
|
|
13
|
+
import { warnIfGlobalJsonBeforeSubcommand } from "../lib/global-json-hint.js";
|
|
12
14
|
function envApiKey() {
|
|
13
15
|
return process.env.RISKMODELS_API_KEY?.trim() || undefined;
|
|
14
16
|
}
|
|
@@ -41,7 +43,8 @@ async function resolveApiKey(opts) {
|
|
|
41
43
|
if (opts.yes)
|
|
42
44
|
return { source: "missing" };
|
|
43
45
|
console.error(chalk.yellow("No RiskModels API key found."));
|
|
44
|
-
console.error(chalk.dim("Get a key: https://riskmodels.app/get-
|
|
46
|
+
console.error(chalk.dim("Get a key: https://riskmodels.app/get-key"));
|
|
47
|
+
console.error(chalk.dim(API_KEY_EMAIL_HINT));
|
|
45
48
|
const answer = await inquirer.prompt([
|
|
46
49
|
{
|
|
47
50
|
type: "password",
|
|
@@ -55,14 +58,14 @@ async function resolveApiKey(opts) {
|
|
|
55
58
|
}
|
|
56
59
|
export function installCommand() {
|
|
57
60
|
return new Command("install")
|
|
58
|
-
.description("Detect AI clients and install/register the RiskModels MCP server")
|
|
61
|
+
.description("Detect AI clients and install/register the RiskModels MCP server (default: friendly summary; pass --json for machine-readable)")
|
|
59
62
|
.option("--client <name>", "claude | cursor | codex | vscode")
|
|
60
63
|
.option("--all", "Target all detected clients")
|
|
61
64
|
.option("--api-key <key>", "RiskModels API key for one-shot setup")
|
|
62
65
|
.option("--dry-run", "Show planned config changes without writing")
|
|
63
66
|
.option("--yes", "Non-interactive mode")
|
|
64
67
|
.option("--embed-key", "Explicitly embed the API key in MCP config env (not recommended)")
|
|
65
|
-
.option("--json", "JSON output")
|
|
68
|
+
.option("--json", "Structured JSON instead of formatted text (matches historical output shape)")
|
|
66
69
|
.action(async (opts, cmd) => {
|
|
67
70
|
const json = opts.json || cmd.optsWithGlobals().json || false;
|
|
68
71
|
let clients;
|
|
@@ -96,20 +99,34 @@ export function installCommand() {
|
|
|
96
99
|
: "Install complete. Restart your MCP client, then try the first prompt.",
|
|
97
100
|
};
|
|
98
101
|
if (dryRun) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
+
if (json) {
|
|
103
|
+
warnIfGlobalJsonBeforeSubcommand("install");
|
|
104
|
+
printResults(output, json);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
printInstallDryRunHuman({
|
|
108
|
+
plans,
|
|
109
|
+
willStoreSharedKey: !!apiKey && source !== configPath(),
|
|
110
|
+
firstPrompt: firstPrompt(),
|
|
111
|
+
});
|
|
102
112
|
}
|
|
103
113
|
return;
|
|
104
114
|
}
|
|
105
115
|
if (!apiKey) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
116
|
+
if (json) {
|
|
117
|
+
warnIfGlobalJsonBeforeSubcommand("install");
|
|
118
|
+
printResults({
|
|
119
|
+
...output,
|
|
120
|
+
error: "No RiskModels API key found. Get one at https://riskmodels.app/get-key or pass --api-key.",
|
|
121
|
+
}, json);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
printInstallMissingKeyHuman();
|
|
125
|
+
}
|
|
110
126
|
process.exitCode = 1;
|
|
111
127
|
return;
|
|
112
128
|
}
|
|
129
|
+
const sharedBefore = await sharedRiskmodelsConfigExists();
|
|
113
130
|
const sharedConfigWrite = await writeSharedApiKey(apiKey, existingConfig?.apiBaseUrl ?? DEFAULT_API_BASE);
|
|
114
131
|
const writes = await Promise.all(detections.map((detection) => installMcpConfig(detection, {
|
|
115
132
|
apiKey,
|
|
@@ -123,14 +140,24 @@ export function installCommand() {
|
|
|
123
140
|
writes,
|
|
124
141
|
connectionTest: test,
|
|
125
142
|
};
|
|
126
|
-
|
|
143
|
+
if (json) {
|
|
144
|
+
warnIfGlobalJsonBeforeSubcommand("install");
|
|
145
|
+
printResults(result, json);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
printInstallSuccessHuman({
|
|
149
|
+
isFirstInstall: !sharedBefore,
|
|
150
|
+
writes,
|
|
151
|
+
sharedConfigWrite,
|
|
152
|
+
connectionTest: test,
|
|
153
|
+
firstPrompt: firstPrompt(),
|
|
154
|
+
hadErrors: writes.some((w) => w.action === "error") || !test.ok,
|
|
155
|
+
showClaudeCodeMcpTip: detections.some((d) => d.client === "claude" && d.commandAvailable),
|
|
156
|
+
});
|
|
157
|
+
}
|
|
127
158
|
if (writes.some((write) => write.action === "error") || !test.ok) {
|
|
128
159
|
process.exitCode = 1;
|
|
129
160
|
return;
|
|
130
161
|
}
|
|
131
|
-
if (!json) {
|
|
132
|
-
console.error(chalk.green("RiskModels MCP install completed with backups."));
|
|
133
|
-
console.error(chalk.green(`First prompt to try: "${firstPrompt()}"`));
|
|
134
|
-
}
|
|
135
162
|
});
|
|
136
163
|
}
|
package/dist/commands/status.js
CHANGED
|
@@ -2,24 +2,33 @@ import { Command } from "commander";
|
|
|
2
2
|
import { configPath, loadConfig, maskSecret } from "../lib/config.js";
|
|
3
3
|
import { detectClients, selectedClients } from "../lib/mcp-config-paths.js";
|
|
4
4
|
import { printResults } from "../lib/display.js";
|
|
5
|
+
import { printStatusHuman } from "../lib/cli-human-diagnostics.js";
|
|
6
|
+
import { warnIfGlobalJsonBeforeSubcommand } from "../lib/global-json-hint.js";
|
|
5
7
|
export function statusCommand() {
|
|
6
8
|
return new Command("status")
|
|
7
|
-
.description("Show RiskModels CLI and MCP client configuration status")
|
|
9
|
+
.description("Show RiskModels CLI and MCP client configuration status (default: readable summary; use --json for scripts)")
|
|
8
10
|
.option("--client <name>", "claude | cursor | codex | vscode")
|
|
9
11
|
.option("--all", "Check all supported clients")
|
|
10
|
-
.option("--json", "JSON
|
|
12
|
+
.option("--json", "Structured JSON instead of readable summary")
|
|
11
13
|
.action(async (opts, cmd) => {
|
|
12
14
|
const json = opts.json || cmd.optsWithGlobals().json || false;
|
|
13
15
|
const cfg = await loadConfig();
|
|
14
16
|
const clients = selectedClients({ client: opts.client, all: opts.all });
|
|
15
17
|
const detections = await detectClients(clients);
|
|
16
|
-
|
|
18
|
+
const payload = {
|
|
17
19
|
configPath: configPath(),
|
|
18
20
|
configFound: !!cfg,
|
|
19
21
|
apiBaseUrl: cfg?.apiBaseUrl ?? "https://riskmodels.app",
|
|
20
22
|
apiKey: maskSecret(cfg?.apiKey),
|
|
21
23
|
oauthConfigured: !!cfg?.clientId && !!cfg?.clientSecret,
|
|
22
24
|
mcpClients: detections,
|
|
23
|
-
}
|
|
25
|
+
};
|
|
26
|
+
if (json) {
|
|
27
|
+
warnIfGlobalJsonBeforeSubcommand("status");
|
|
28
|
+
printResults(payload, json);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
printStatusHuman(payload);
|
|
32
|
+
}
|
|
24
33
|
});
|
|
25
34
|
}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import chalk from "chalk";
|
|
3
2
|
import { detectClients, selectedClients } from "../lib/mcp-config-paths.js";
|
|
4
3
|
import { printResults } from "../lib/display.js";
|
|
4
|
+
import { printUninstallPlannedHuman, printUninstallSuccessHuman, } from "../lib/mcp-cli-human-output.js";
|
|
5
5
|
import { uninstallMcpConfig } from "../lib/mcp-config-writer.js";
|
|
6
|
+
import { warnIfGlobalJsonBeforeSubcommand } from "../lib/global-json-hint.js";
|
|
6
7
|
export function uninstallCommand() {
|
|
7
8
|
return new Command("uninstall")
|
|
8
|
-
.description("Remove the RiskModels MCP server from client configs")
|
|
9
|
+
.description("Remove the RiskModels MCP server from client configs (default: friendly summary; pass --json for machine-readable)")
|
|
9
10
|
.option("--client <name>", "claude | cursor | codex | vscode")
|
|
10
11
|
.option("--all", "Check all supported clients")
|
|
11
12
|
.option("--dry-run", "Show planned removals without writing")
|
|
12
|
-
.option("--json", "JSON output")
|
|
13
|
+
.option("--json", "Structured JSON instead of formatted text (matches historical output shape)")
|
|
13
14
|
.action(async (opts, cmd) => {
|
|
14
15
|
const json = opts.json || cmd.optsWithGlobals().json || false;
|
|
15
16
|
const clients = selectedClients({ client: opts.client, all: opts.all });
|
|
@@ -29,17 +30,26 @@ export function uninstallCommand() {
|
|
|
29
30
|
})),
|
|
30
31
|
};
|
|
31
32
|
if (dryRun) {
|
|
32
|
-
|
|
33
|
+
if (json) {
|
|
34
|
+
warnIfGlobalJsonBeforeSubcommand("uninstall");
|
|
35
|
+
printResults(output, json);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
printUninstallPlannedHuman(detections);
|
|
39
|
+
}
|
|
33
40
|
return;
|
|
34
41
|
}
|
|
35
42
|
const removals = await Promise.all(detections.map((detection) => uninstallMcpConfig(detection)));
|
|
36
|
-
|
|
43
|
+
if (json) {
|
|
44
|
+
warnIfGlobalJsonBeforeSubcommand("uninstall");
|
|
45
|
+
printResults({ ...output, removals }, json);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
printUninstallSuccessHuman(removals);
|
|
49
|
+
}
|
|
37
50
|
if (removals.some((removal) => removal.action === "error")) {
|
|
38
51
|
process.exitCode = 1;
|
|
39
52
|
return;
|
|
40
53
|
}
|
|
41
|
-
if (!json) {
|
|
42
|
-
console.error(chalk.green("RiskModels MCP uninstall completed with backups where files changed."));
|
|
43
|
-
}
|
|
44
54
|
});
|
|
45
55
|
}
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { mcpConfigCommand } from "./commands/mcp-config.js";
|
|
|
10
10
|
import { mcpServeCommand } from "./commands/mcp.js";
|
|
11
11
|
import { agentCommand } from "./commands/agent.js";
|
|
12
12
|
import { metricsCommand } from "./commands/metrics.js";
|
|
13
|
+
import { decomposeCommand } from "./commands/decompose.js";
|
|
13
14
|
import { batchCommand } from "./commands/batch.js";
|
|
14
15
|
import { portfolioCommand } from "./commands/portfolio.js";
|
|
15
16
|
import { tickersCommand } from "./commands/tickers.js";
|
|
@@ -28,15 +29,17 @@ const program = new Command();
|
|
|
28
29
|
program
|
|
29
30
|
.name("riskmodels")
|
|
30
31
|
.description("RiskModels CLI — REST API, SQL query, schema, billing, and agent manifests")
|
|
31
|
-
.version("2.0.
|
|
32
|
+
.version("2.0.2", "-V, --version", "output version")
|
|
32
33
|
.option("--json", "JSON output for supported commands (query, schema, balance, API calls, config list)")
|
|
33
34
|
.configureHelp({ sortSubcommands: true })
|
|
34
35
|
.addHelpText("after", `
|
|
35
36
|
${chalk.bold("Quick start")}
|
|
36
37
|
${chalk.dim("$")} riskmodels config init
|
|
37
38
|
${chalk.dim("$")} riskmodels install --dry-run
|
|
39
|
+
${chalk.dim("$")} riskmodels uninstall --dry-run
|
|
38
40
|
${chalk.dim("$")} riskmodels health
|
|
39
41
|
${chalk.dim("$")} riskmodels metrics NVDA
|
|
42
|
+
${chalk.dim("$")} riskmodels decompose NVDA
|
|
40
43
|
${chalk.dim("$")} riskmodels query ${chalk.green('"SELECT ticker FROM ticker_metadata LIMIT 3"')}
|
|
41
44
|
${chalk.dim("$")} riskmodels manifest --format anthropic
|
|
42
45
|
|
|
@@ -48,6 +51,7 @@ program.addCommand(doctorCommand());
|
|
|
48
51
|
program.addCommand(uninstallCommand());
|
|
49
52
|
program.addCommand(queryCommand());
|
|
50
53
|
program.addCommand(metricsCommand());
|
|
54
|
+
program.addCommand(decomposeCommand());
|
|
51
55
|
program.addCommand(batchCommand());
|
|
52
56
|
program.addCommand(portfolioCommand());
|
|
53
57
|
program.addCommand(returnsCommand());
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ClientDetection } from "./mcp-config-paths.js";
|
|
2
|
+
export type DoctorPayload = {
|
|
3
|
+
ok: boolean;
|
|
4
|
+
checks: {
|
|
5
|
+
id: string;
|
|
6
|
+
ok: boolean;
|
|
7
|
+
detail: string;
|
|
8
|
+
}[];
|
|
9
|
+
clients: ClientDetection[];
|
|
10
|
+
note: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function printDoctorHuman(payload: DoctorPayload): void;
|
|
13
|
+
export type StatusPayload = {
|
|
14
|
+
configPath: string;
|
|
15
|
+
configFound: boolean;
|
|
16
|
+
apiBaseUrl: string;
|
|
17
|
+
apiKey: string;
|
|
18
|
+
oauthConfigured: boolean;
|
|
19
|
+
mcpClients: ClientDetection[];
|
|
20
|
+
};
|
|
21
|
+
export declare function printStatusHuman(payload: StatusPayload): void;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { abbreviatePath } from "./path-display.js";
|
|
3
|
+
import { formatCliVersionLabel } from "./cli-version.js";
|
|
4
|
+
const BULLET = "•";
|
|
5
|
+
function logLine(line = "") {
|
|
6
|
+
console.log(line);
|
|
7
|
+
}
|
|
8
|
+
export function printDoctorHuman(payload) {
|
|
9
|
+
logLine("");
|
|
10
|
+
logLine(`${chalk.bold("riskmodels doctor")} ${chalk.dim(`(CLI ${formatCliVersionLabel()})`)}`);
|
|
11
|
+
logLine("");
|
|
12
|
+
logLine(payload.ok ? chalk.green.bold("All checks passed") : chalk.yellow.bold("Some checks need attention"));
|
|
13
|
+
logLine("");
|
|
14
|
+
logLine(chalk.bold("Checks:"));
|
|
15
|
+
for (const c of payload.checks) {
|
|
16
|
+
const icon = c.ok ? chalk.green("✓") : chalk.red("✗");
|
|
17
|
+
logLine(` ${icon} ${chalk.bold(c.id)}${c.detail ? chalk.dim(` — ${c.detail}`) : ""}`);
|
|
18
|
+
}
|
|
19
|
+
logLine("");
|
|
20
|
+
logLine(chalk.bold("MCP client detection:"));
|
|
21
|
+
for (const d of payload.clients) {
|
|
22
|
+
const p = d.configPath ? chalk.dim(` — ${abbreviatePath(d.configPath)}`) : "";
|
|
23
|
+
logLine(` ${BULLET} ${d.label} (${d.status}${d.mode !== "guidance" ? `, ${d.mode}` : ""})${p}`);
|
|
24
|
+
}
|
|
25
|
+
logLine("");
|
|
26
|
+
logLine(chalk.dim(payload.note));
|
|
27
|
+
logLine("");
|
|
28
|
+
}
|
|
29
|
+
export function printStatusHuman(payload) {
|
|
30
|
+
logLine("");
|
|
31
|
+
logLine(`${chalk.bold("riskmodels status")} ${chalk.dim(`(CLI ${formatCliVersionLabel()})`)}`);
|
|
32
|
+
logLine("");
|
|
33
|
+
const cfgDisp = abbreviatePath(payload.configPath);
|
|
34
|
+
logLine(`${chalk.bold("Shared config:")} ${cfgDisp}`);
|
|
35
|
+
if (cfgDisp !== payload.configPath) {
|
|
36
|
+
logLine(chalk.dim(` (full path: ${payload.configPath})`));
|
|
37
|
+
}
|
|
38
|
+
logLine(` ${BULLET} ${payload.configFound ? chalk.green("file present") : chalk.dim("file missing (run install or config init)")}`);
|
|
39
|
+
logLine(` ${BULLET} ${chalk.bold("apiBaseUrl")} ${chalk.dim(payload.apiBaseUrl)}`);
|
|
40
|
+
logLine(` ${BULLET} ${chalk.bold("API key")} ${payload.apiKey}`);
|
|
41
|
+
logLine(` ${BULLET} ${chalk.bold("OAuth")} ${payload.oauthConfigured ? chalk.green("configured") : chalk.dim("not configured")}`);
|
|
42
|
+
logLine("");
|
|
43
|
+
logLine(chalk.bold("MCP-related clients:"));
|
|
44
|
+
for (const d of payload.mcpClients) {
|
|
45
|
+
const p = d.configPath ? chalk.dim(` — ${abbreviatePath(d.configPath)}`) : "";
|
|
46
|
+
logLine(` ${BULLET} ${d.label}: ${d.status}${p}`);
|
|
47
|
+
}
|
|
48
|
+
logLine("");
|
|
49
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Resolved from sibling `cli/package.json` at runtime (`dist/` → repo `cli/`). */
|
|
2
|
+
export declare function getCliPackageVersion(): string;
|
|
3
|
+
/** e.g. `v2.0.2` for human-facing footers (package.json omits leading v). */
|
|
4
|
+
export declare function formatCliVersionLabel(): string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const pkgDir = dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
let cachedVersion = null;
|
|
6
|
+
/** Resolved from sibling `cli/package.json` at runtime (`dist/` → repo `cli/`). */
|
|
7
|
+
export function getCliPackageVersion() {
|
|
8
|
+
if (cachedVersion !== null)
|
|
9
|
+
return cachedVersion;
|
|
10
|
+
try {
|
|
11
|
+
const pkgPath = join(pkgDir, "..", "..", "package.json");
|
|
12
|
+
const raw = readFileSync(pkgPath, "utf8");
|
|
13
|
+
cachedVersion = JSON.parse(raw).version;
|
|
14
|
+
return cachedVersion;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
cachedVersion = "0.0.0";
|
|
18
|
+
return cachedVersion;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** e.g. `v2.0.2` for human-facing footers (package.json omits leading v). */
|
|
22
|
+
export function formatCliVersionLabel() {
|
|
23
|
+
const v = getCliPackageVersion();
|
|
24
|
+
return v.startsWith("v") ? v : `v${v}`;
|
|
25
|
+
}
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export interface RiskmodelsConfig {
|
|
|
13
13
|
}
|
|
14
14
|
export declare const DEFAULT_API_BASE = "https://riskmodels.app";
|
|
15
15
|
export declare function configPath(): string;
|
|
16
|
+
/** True when shared config file exists (used for MCP install welcome on first-ever setup). */
|
|
17
|
+
export declare function sharedRiskmodelsConfigExists(): Promise<boolean>;
|
|
16
18
|
export declare function loadConfig(): Promise<RiskmodelsConfig | null>;
|
|
17
19
|
export declare function saveConfig(cfg: RiskmodelsConfig): Promise<void>;
|
|
18
20
|
export declare function maskSecret(value: string | undefined, visible?: number): string;
|
package/dist/lib/config.js
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
1
|
+
import { mkdir, readFile, writeFile, stat } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
export const DEFAULT_API_BASE = "https://riskmodels.app";
|
|
5
5
|
export function configPath() {
|
|
6
6
|
return path.join(homedir(), ".config", "riskmodels", "config.json");
|
|
7
7
|
}
|
|
8
|
+
/** True when shared config file exists (used for MCP install welcome on first-ever setup). */
|
|
9
|
+
export async function sharedRiskmodelsConfigExists() {
|
|
10
|
+
try {
|
|
11
|
+
await stat(configPath());
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
if (err.code === "ENOENT")
|
|
16
|
+
return false;
|
|
17
|
+
throw err;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
8
20
|
export async function loadConfig() {
|
|
9
21
|
const p = configPath();
|
|
10
22
|
try {
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** True when `--json` appears in argv before the subcommand (parent-level flag). */
|
|
2
|
+
export declare function usedGlobalJsonBeforeSubcommand(subcommandName: string): boolean;
|
|
3
|
+
/** Explains JSON output when `--json` was set before the subcommand (stderr, dim). */
|
|
4
|
+
export declare function warnIfGlobalJsonBeforeSubcommand(subcommandName: string): void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
/** True when `--json` appears in argv before the subcommand (parent-level flag). */
|
|
3
|
+
export function usedGlobalJsonBeforeSubcommand(subcommandName) {
|
|
4
|
+
const args = process.argv.slice(2);
|
|
5
|
+
const jsonIdx = args.findIndex((a) => a === "--json" || a.startsWith("--json="));
|
|
6
|
+
const subIdx = args.indexOf(subcommandName);
|
|
7
|
+
if (jsonIdx === -1 || subIdx === -1)
|
|
8
|
+
return false;
|
|
9
|
+
return jsonIdx < subIdx;
|
|
10
|
+
}
|
|
11
|
+
/** Explains JSON output when `--json` was set before the subcommand (stderr, dim). */
|
|
12
|
+
export function warnIfGlobalJsonBeforeSubcommand(subcommandName) {
|
|
13
|
+
if (!usedGlobalJsonBeforeSubcommand(subcommandName))
|
|
14
|
+
return;
|
|
15
|
+
console.error(chalk.dim(`Note: JSON mode is on because --json appears before "${subcommandName}". Move --json after "${subcommandName}" when you want default human output.`));
|
|
16
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { SharedConfigWriteResult, ConfigWriteResult } from "./mcp-config-writer.js";
|
|
2
|
+
import type { InstallPlan } from "./mcp-install-plan.js";
|
|
3
|
+
import type { ClientDetection } from "./mcp-config-paths.js";
|
|
4
|
+
/** Shown wherever we direct users to obtain an API key (matches key-issued email sender). */
|
|
5
|
+
export declare const API_KEY_EMAIL_HINT = "If you requested a key already, check your inbox for mail from RiskModels (service@riskmodels.app).";
|
|
6
|
+
export declare function printInstallSuccessHuman(opts: {
|
|
7
|
+
isFirstInstall: boolean;
|
|
8
|
+
writes: ConfigWriteResult[];
|
|
9
|
+
sharedConfigWrite: SharedConfigWriteResult;
|
|
10
|
+
connectionTest: {
|
|
11
|
+
ok: boolean;
|
|
12
|
+
endpoint: string;
|
|
13
|
+
message: string;
|
|
14
|
+
};
|
|
15
|
+
firstPrompt: string;
|
|
16
|
+
hadErrors: boolean;
|
|
17
|
+
/** When Claude CLI is detected, suggest native `claude mcp` as an alternative. */
|
|
18
|
+
showClaudeCodeMcpTip?: boolean;
|
|
19
|
+
}): void;
|
|
20
|
+
export declare function printInstallDryRunHuman(opts: {
|
|
21
|
+
plans: InstallPlan[];
|
|
22
|
+
willStoreSharedKey?: boolean;
|
|
23
|
+
firstPrompt: string;
|
|
24
|
+
}): void;
|
|
25
|
+
export declare function printInstallMissingKeyHuman(): void;
|
|
26
|
+
export declare function printUninstallSuccessHuman(removals: ConfigWriteResult[]): void;
|
|
27
|
+
/** Human-readable detection list for uninstall dry-run (before removals computed). */
|
|
28
|
+
export declare function printUninstallPlannedHuman(detections: ClientDetection[]): void;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { platform } from "node:os";
|
|
3
|
+
import { configPath } from "./config.js";
|
|
4
|
+
import { formatCliVersionLabel } from "./cli-version.js";
|
|
5
|
+
import { abbreviatePath } from "./path-display.js";
|
|
6
|
+
const BULLET = "•";
|
|
7
|
+
/** Shown wherever we direct users to obtain an API key (matches key-issued email sender). */
|
|
8
|
+
export const API_KEY_EMAIL_HINT = "If you requested a key already, check your inbox for mail from RiskModels (service@riskmodels.app).";
|
|
9
|
+
function logRiskmodelsCliFooter(phase) {
|
|
10
|
+
logLine("");
|
|
11
|
+
logLine(chalk.bold("riskmodels CLI (still on PATH):"));
|
|
12
|
+
logLine(chalk.dim(phase === "dry-run"
|
|
13
|
+
? "Uninstalling MCP only edits AI client configs; it does not remove the riskmodels terminal command."
|
|
14
|
+
: 'The RiskModels MCP integration was removed from the configs above. The "riskmodels" terminal command was not.'));
|
|
15
|
+
logLine(chalk.dim("To remove the CLI, use what matches how you installed it:"));
|
|
16
|
+
if (platform() !== "win32") {
|
|
17
|
+
logLine(chalk.dim(` ${BULLET} Homebrew (macOS / Linux): brew uninstall riskmodels`));
|
|
18
|
+
}
|
|
19
|
+
logLine(chalk.dim(` ${BULLET} npm (any OS): npm uninstall -g riskmodels`));
|
|
20
|
+
if (platform() === "win32") {
|
|
21
|
+
logLine(chalk.dim(` ${BULLET} Package manager installs (winget / Chocolatey / Scoop / etc.): use that tool's uninstall`));
|
|
22
|
+
}
|
|
23
|
+
logLine("");
|
|
24
|
+
logLine(chalk.dim(`riskmodels CLI ${formatCliVersionLabel()}`));
|
|
25
|
+
}
|
|
26
|
+
function logLine(line = "") {
|
|
27
|
+
console.log(line);
|
|
28
|
+
}
|
|
29
|
+
function formatBackupBullets(paths) {
|
|
30
|
+
return paths
|
|
31
|
+
.filter(Boolean)
|
|
32
|
+
.sort()
|
|
33
|
+
.map((p) => `${BULLET} ${abbreviatePath(p)}`);
|
|
34
|
+
}
|
|
35
|
+
export function printInstallSuccessHuman(opts) {
|
|
36
|
+
const { isFirstInstall, writes, sharedConfigWrite, connectionTest, firstPrompt, hadErrors, showClaudeCodeMcpTip, } = opts;
|
|
37
|
+
if (isFirstInstall) {
|
|
38
|
+
logLine("");
|
|
39
|
+
logLine(chalk.bold("👋 Welcome to RiskModels MCP!"));
|
|
40
|
+
logLine("");
|
|
41
|
+
logLine("This one-time setup connects your AI coding tools (Claude, Cursor, etc.)");
|
|
42
|
+
logLine("to real-time portfolio risk snapshots, hedge ratios, and factor models.");
|
|
43
|
+
logLine("");
|
|
44
|
+
}
|
|
45
|
+
logLine("");
|
|
46
|
+
if (hadErrors) {
|
|
47
|
+
logLine(chalk.yellow.bold("⚠ RiskModels MCP install finished with errors"));
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
logLine(chalk.green.bold("✅ RiskModels MCP install completed"));
|
|
51
|
+
}
|
|
52
|
+
logLine("");
|
|
53
|
+
logLine(chalk.bold("Installed / updated in:"));
|
|
54
|
+
const installed = writes.filter((w) => w.action === "written");
|
|
55
|
+
if (installed.length === 0) {
|
|
56
|
+
logLine(` ${chalk.dim("(no client MCP configs were modified — check detection / permissions)")}`);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
for (const w of installed) {
|
|
60
|
+
logLine(` ${BULLET} ${w.label}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const skipped = writes.filter((w) => w.action === "skipped" && w.client !== "vscode");
|
|
64
|
+
if (skipped.length > 0) {
|
|
65
|
+
logLine("");
|
|
66
|
+
logLine(chalk.dim("Skipped (no change):"));
|
|
67
|
+
for (const w of skipped) {
|
|
68
|
+
logLine(` ${BULLET} ${w.label} — ${chalk.dim(w.message)}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const vsSkipped = writes.find((w) => w.action === "skipped" && w.client === "vscode");
|
|
72
|
+
if (vsSkipped) {
|
|
73
|
+
logLine("");
|
|
74
|
+
logLine(chalk.dim(`VS Code: ${vsSkipped.message}`));
|
|
75
|
+
}
|
|
76
|
+
const backupPaths = [];
|
|
77
|
+
if (sharedConfigWrite.backupPath)
|
|
78
|
+
backupPaths.push(sharedConfigWrite.backupPath);
|
|
79
|
+
for (const w of writes) {
|
|
80
|
+
if (w.backupPath)
|
|
81
|
+
backupPaths.push(w.backupPath);
|
|
82
|
+
}
|
|
83
|
+
logLine("");
|
|
84
|
+
logLine(chalk.bold("Backups created for safety:"));
|
|
85
|
+
if (backupPaths.length === 0) {
|
|
86
|
+
logLine(` ${chalk.dim("(none — configs were newly created)")}`);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
for (const line of formatBackupBullets(backupPaths))
|
|
90
|
+
logLine(` ${line}`);
|
|
91
|
+
}
|
|
92
|
+
logLine("");
|
|
93
|
+
if (connectionTest.ok) {
|
|
94
|
+
logLine(`${chalk.bold("Connection test:")} OK (${chalk.cyan(connectionTest.endpoint)})`);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
logLine(`${chalk.bold("Connection test:")} ${chalk.red("FAILED")} ${chalk.dim(`(${connectionTest.endpoint})`)}`);
|
|
98
|
+
logLine(chalk.dim(` ${connectionTest.message}`));
|
|
99
|
+
logLine(chalk.dim("Tip: check network/VPN, apiBaseUrl in shared config if non-default, corporate proxy, then run: riskmodels health"));
|
|
100
|
+
}
|
|
101
|
+
if (showClaudeCodeMcpTip) {
|
|
102
|
+
logLine("");
|
|
103
|
+
logLine("Claude Code users: you can also run");
|
|
104
|
+
logLine(chalk.dim(" claude mcp add riskmodels npx -y @riskmodels/mcp"));
|
|
105
|
+
}
|
|
106
|
+
if (!hadErrors) {
|
|
107
|
+
logLine("");
|
|
108
|
+
logLine(`${chalk.bold("Next step:")} Restart your AI clients, then try:`);
|
|
109
|
+
logLine("");
|
|
110
|
+
logLine(`"${firstPrompt}"`);
|
|
111
|
+
logLine("");
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
logLine("");
|
|
115
|
+
const failed = writes.filter((w) => w.action === "error");
|
|
116
|
+
if (failed.length > 0) {
|
|
117
|
+
logLine(chalk.bold("Errors:"));
|
|
118
|
+
for (const w of failed) {
|
|
119
|
+
logLine(` ${BULLET} ${w.label}: ${chalk.red(w.message)}`);
|
|
120
|
+
}
|
|
121
|
+
logLine("");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
logLine(chalk.dim(`riskmodels CLI ${formatCliVersionLabel()}`));
|
|
125
|
+
}
|
|
126
|
+
export function printInstallDryRunHuman(opts) {
|
|
127
|
+
logLine("");
|
|
128
|
+
logLine(chalk.bold("Dry run — planned RiskModels MCP install"));
|
|
129
|
+
logLine("");
|
|
130
|
+
logLine(chalk.bold("Clients to merge into:"));
|
|
131
|
+
for (const p of opts.plans) {
|
|
132
|
+
const pathPart = p.configPath ? chalk.dim(` — ${abbreviatePath(p.configPath)}`) : "";
|
|
133
|
+
logLine(` ${BULLET} ${p.label}${pathPart}`);
|
|
134
|
+
}
|
|
135
|
+
logLine("");
|
|
136
|
+
const cfgAbs = configPath();
|
|
137
|
+
const cfgShort = abbreviatePath(cfgAbs);
|
|
138
|
+
if (opts.willStoreSharedKey) {
|
|
139
|
+
logLine(chalk.dim(`A shared API key file would be created or updated at ${cfgShort}`));
|
|
140
|
+
if (cfgShort !== cfgAbs)
|
|
141
|
+
logLine(chalk.dim(` (full path: ${cfgAbs})`));
|
|
142
|
+
logLine(chalk.dim("(with backup if replacing)."));
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
logLine(chalk.dim("No API key resolved — rerun with --api-key or set RISKMODELS_API_KEY after obtaining a key."));
|
|
146
|
+
logLine(chalk.dim(API_KEY_EMAIL_HINT));
|
|
147
|
+
}
|
|
148
|
+
logLine("");
|
|
149
|
+
logLine(chalk.dim("Rerun without --dry-run after you are happy with this plan."));
|
|
150
|
+
logLine("");
|
|
151
|
+
logLine(`${chalk.bold("First prompt to paste into your AI client:")} "${opts.firstPrompt}"`);
|
|
152
|
+
logLine("");
|
|
153
|
+
logLine(chalk.dim(`riskmodels CLI ${formatCliVersionLabel()}`));
|
|
154
|
+
}
|
|
155
|
+
export function printInstallMissingKeyHuman() {
|
|
156
|
+
logLine("");
|
|
157
|
+
logLine(chalk.red.bold("✗ Missing RiskModels API key"));
|
|
158
|
+
logLine("");
|
|
159
|
+
logLine(`Get a key: ${chalk.cyan("https://riskmodels.app/get-key")}`);
|
|
160
|
+
logLine("");
|
|
161
|
+
logLine(chalk.dim(API_KEY_EMAIL_HINT));
|
|
162
|
+
logLine("");
|
|
163
|
+
logLine(chalk.dim("Pass --api-key, set RISKMODELS_API_KEY, or rerun without --yes to enter it interactively."));
|
|
164
|
+
logLine("");
|
|
165
|
+
logLine(chalk.dim(`riskmodels CLI ${formatCliVersionLabel()}`));
|
|
166
|
+
}
|
|
167
|
+
export function printUninstallSuccessHuman(removals) {
|
|
168
|
+
logLine("");
|
|
169
|
+
logLine(chalk.green.bold("✅ RiskModels MCP uninstall completed"));
|
|
170
|
+
logLine("");
|
|
171
|
+
logLine(chalk.bold("Updates to MCP configs:"));
|
|
172
|
+
logLine("");
|
|
173
|
+
for (const r of removals) {
|
|
174
|
+
const bullet = `${BULLET} ${r.label}`;
|
|
175
|
+
const pathSuffix = r.configPath !== undefined ? chalk.dim(` — ${abbreviatePath(r.configPath)}`) : "";
|
|
176
|
+
if (r.action === "written") {
|
|
177
|
+
logLine(` ${bullet}${pathSuffix}`);
|
|
178
|
+
logLine(chalk.green.dim(` ${r.message}`));
|
|
179
|
+
if (r.backupPath)
|
|
180
|
+
logLine(chalk.dim(` backup: ${abbreviatePath(r.backupPath)}`));
|
|
181
|
+
}
|
|
182
|
+
else if (r.action === "skipped") {
|
|
183
|
+
logLine(` ${bullet}${pathSuffix}`);
|
|
184
|
+
logLine(chalk.dim(` (skipped — ${r.message})`));
|
|
185
|
+
}
|
|
186
|
+
else if (r.action === "error") {
|
|
187
|
+
logLine(` ${bullet}${pathSuffix}`);
|
|
188
|
+
logLine(chalk.red(` error: ${r.message}`));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
logLine("");
|
|
192
|
+
const sharedAbs = configPath();
|
|
193
|
+
const sharedShort = abbreviatePath(sharedAbs);
|
|
194
|
+
logLine(`${chalk.bold("Shared API key / billing profile:")} unchanged ${chalk.dim(`(${sharedShort})`)}`);
|
|
195
|
+
if (sharedShort !== sharedAbs) {
|
|
196
|
+
logLine(chalk.dim(` (full path: ${sharedAbs})`));
|
|
197
|
+
}
|
|
198
|
+
logLine(chalk.dim("Only the riskmodels MCP server block was removed from each AI client config above; your key file was not modified."));
|
|
199
|
+
const backupPaths = removals.filter((r) => r.backupPath).map((r) => r.backupPath);
|
|
200
|
+
if (backupPaths.length > 0) {
|
|
201
|
+
logLine("");
|
|
202
|
+
logLine(chalk.bold("Backups stored:"));
|
|
203
|
+
for (const line of formatBackupBullets(backupPaths))
|
|
204
|
+
logLine(` ${line}`);
|
|
205
|
+
}
|
|
206
|
+
logRiskmodelsCliFooter("done");
|
|
207
|
+
}
|
|
208
|
+
/** Human-readable detection list for uninstall dry-run (before removals computed). */
|
|
209
|
+
export function printUninstallPlannedHuman(detections) {
|
|
210
|
+
logLine("");
|
|
211
|
+
logLine(chalk.bold("Dry run — RiskModels MCP would be inspected on:"));
|
|
212
|
+
logLine("");
|
|
213
|
+
for (const d of detections) {
|
|
214
|
+
const p = d.configPath ? chalk.dim(` — ${abbreviatePath(d.configPath)}`) : "";
|
|
215
|
+
logLine(` ${BULLET} ${d.label}${p}`);
|
|
216
|
+
}
|
|
217
|
+
logLine("");
|
|
218
|
+
const sharedAbs = configPath();
|
|
219
|
+
const sharedShort = abbreviatePath(sharedAbs);
|
|
220
|
+
logLine(chalk.dim(`Only the "riskmodels" MCP server block would be removed; ${sharedShort} would not be touched.`));
|
|
221
|
+
if (sharedShort !== sharedAbs)
|
|
222
|
+
logLine(chalk.dim(` (full path: ${sharedAbs})`));
|
|
223
|
+
logRiskmodelsCliFooter("dry-run");
|
|
224
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
/** Shorten home-prefixed paths for readability (~ on POSIX; Windows uses same tilde-in-shell convention in Git Bash). */
|
|
3
|
+
export function abbreviatePath(absolutePath) {
|
|
4
|
+
if (!absolutePath.trim())
|
|
5
|
+
return absolutePath;
|
|
6
|
+
const home = homedir();
|
|
7
|
+
if (absolutePath === home)
|
|
8
|
+
return "~";
|
|
9
|
+
const sep = absolutePath.includes("\\") ? "\\" : "/";
|
|
10
|
+
const homeNorm = home.replace(/\\/g, "/");
|
|
11
|
+
const pathNorm = absolutePath.replace(/\\/g, "/");
|
|
12
|
+
if (pathNorm === homeNorm || pathNorm === `${homeNorm}/`)
|
|
13
|
+
return "~";
|
|
14
|
+
if (pathNorm.startsWith(`${homeNorm}/`)) {
|
|
15
|
+
return `~/${pathNorm.slice(homeNorm.length + 1)}`;
|
|
16
|
+
}
|
|
17
|
+
return absolutePath;
|
|
18
|
+
}
|