driggsby 0.1.41 → 0.2.0
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 +59 -0
- package/bin/driggsby.js +2 -0
- package/dist/api/base-url.js +31 -0
- package/dist/api/network-error.js +55 -0
- package/dist/args-deploy.js +94 -0
- package/dist/args-init.js +29 -0
- package/dist/args-mcp-setup.js +258 -0
- package/dist/args-shared.js +28 -0
- package/dist/args.js +177 -0
- package/dist/clap-suggestions.js +66 -0
- package/dist/cli-error.js +12 -0
- package/dist/credentials/clear-outcome.js +1 -0
- package/dist/credentials/file-store.js +77 -0
- package/dist/credentials/keychain.js +54 -0
- package/dist/credentials/secret-tool.js +48 -0
- package/dist/credentials/store.js +164 -0
- package/dist/credentials/tool-runner.js +55 -0
- package/dist/deploy/api-session.js +85 -0
- package/dist/deploy/deploy-command.js +126 -0
- package/dist/deploy/rollback-command.js +100 -0
- package/dist/deploy/versions-command.js +97 -0
- package/dist/dev/dev-command.js +106 -0
- package/dist/dev/dev-servers.js +274 -0
- package/dist/dev/host-page.js +147 -0
- package/dist/dev/mcp-broker.js +149 -0
- package/dist/dev/static-files.js +104 -0
- package/dist/dev/tool-allowlist.js +18 -0
- package/dist/dev/watcher.js +76 -0
- package/dist/help.js +180 -0
- package/dist/init/init-command.js +114 -0
- package/dist/init/templates.js +271 -0
- package/dist/login/claim-client.js +151 -0
- package/dist/login/login.js +146 -0
- package/dist/login/logout.js +37 -0
- package/dist/login/open-url.js +79 -0
- package/dist/main.js +57 -0
- package/dist/mcp-setup/classify.js +37 -0
- package/dist/mcp-setup/client-command.js +133 -0
- package/dist/mcp-setup/commands.js +53 -0
- package/dist/mcp-setup/known-client.js +38 -0
- package/dist/mcp-setup/setup.js +137 -0
- package/dist/mcp-setup/spawn-plan.js +91 -0
- package/dist/mcp-setup/text.js +83 -0
- package/dist/terminal-text.js +19 -0
- package/dist/version.js +6 -0
- package/package.json +22 -41
- package/bin/driggsby +0 -10
- package/install.js +0 -159
- package/lib/artifacts.js +0 -49
- package/lib/process.js +0 -65
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {} from "./known-client.js";
|
|
2
|
+
export const DRIGGSBY_MCP_URL = "https://app.driggsby.com/mcp";
|
|
3
|
+
export function buildInstallerCommand(client, scope) {
|
|
4
|
+
switch (client) {
|
|
5
|
+
case "claude-code":
|
|
6
|
+
return {
|
|
7
|
+
program: "claude",
|
|
8
|
+
args: [
|
|
9
|
+
"mcp",
|
|
10
|
+
"add",
|
|
11
|
+
"--transport",
|
|
12
|
+
"http",
|
|
13
|
+
"-s",
|
|
14
|
+
scope ?? "user",
|
|
15
|
+
"driggsby",
|
|
16
|
+
DRIGGSBY_MCP_URL,
|
|
17
|
+
],
|
|
18
|
+
};
|
|
19
|
+
case "codex":
|
|
20
|
+
return {
|
|
21
|
+
program: "codex",
|
|
22
|
+
args: ["mcp", "add", "driggsby", "--url", DRIGGSBY_MCP_URL],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function buildInspectorCommand(client) {
|
|
27
|
+
switch (client) {
|
|
28
|
+
case "claude-code":
|
|
29
|
+
return { program: "claude", args: ["mcp", "get", "driggsby"] };
|
|
30
|
+
case "codex":
|
|
31
|
+
return { program: "codex", args: ["mcp", "get", "driggsby", "--json"] };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function buildScopedRemoverCommand(client, scope) {
|
|
35
|
+
switch (client) {
|
|
36
|
+
case "claude-code":
|
|
37
|
+
return {
|
|
38
|
+
program: "claude",
|
|
39
|
+
args: ["mcp", "remove", "driggsby", "-s", scope ?? "user"],
|
|
40
|
+
};
|
|
41
|
+
case "codex":
|
|
42
|
+
return { program: "codex", args: ["mcp", "remove", "driggsby"] };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function renderShellCommand(command) {
|
|
46
|
+
return [command.program, ...command.args].map(shellQuote).join(" ");
|
|
47
|
+
}
|
|
48
|
+
function shellQuote(value) {
|
|
49
|
+
if (/^[A-Za-z0-9\-_./=]*$/.test(value)) {
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
53
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { CliError } from "../cli-error.js";
|
|
2
|
+
import { sanitizeForTerminal, trimLikeRust } from "../terminal-text.js";
|
|
3
|
+
const SUPPORTED_CLIENTS_NOTE = "Supported clients:\n claude-code\n codex\n other";
|
|
4
|
+
export function displayName(client) {
|
|
5
|
+
switch (client) {
|
|
6
|
+
case "claude-code":
|
|
7
|
+
return "Claude Code";
|
|
8
|
+
case "codex":
|
|
9
|
+
return "Codex";
|
|
10
|
+
case "other":
|
|
11
|
+
return "Other MCP client";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
// Rust-set trimming plus ASCII-only lowercasing, matching the Rust CLI's
|
|
15
|
+
// str::trim + to_ascii_lowercase: a non-ASCII uppercase letter stays as-is
|
|
16
|
+
// and fails the lookup below.
|
|
17
|
+
export function canonicalizeClientId(input) {
|
|
18
|
+
return trimLikeRust(input).replace(/[A-Z]/g, (letter) => letter.toLowerCase());
|
|
19
|
+
}
|
|
20
|
+
export function parseClient(value) {
|
|
21
|
+
const canonical = canonicalizeClientId(value);
|
|
22
|
+
if (canonical === "") {
|
|
23
|
+
throw new CliError(`Client is required.\n\n${SUPPORTED_CLIENTS_NOTE}`, 1);
|
|
24
|
+
}
|
|
25
|
+
if (canonical === "claude-code" || canonical === "codex" || canonical === "other") {
|
|
26
|
+
return canonical;
|
|
27
|
+
}
|
|
28
|
+
throw new CliError(`Unsupported client: ${sanitizeForTerminal(canonical)}\n\n${SUPPORTED_CLIENTS_NOTE}`, 1);
|
|
29
|
+
}
|
|
30
|
+
export function validateMcpScope(client, scope) {
|
|
31
|
+
if (scope === undefined || client === "claude-code") {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
throw new CliError("-s is supported only for Claude Code.", 1);
|
|
35
|
+
}
|
|
36
|
+
export function cliMcpClient(client) {
|
|
37
|
+
return client === "other" ? null : client;
|
|
38
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { createInterface } from "node:readline/promises";
|
|
2
|
+
import { CliError } from "../cli-error.js";
|
|
3
|
+
import { trimLikeRust } from "../terminal-text.js";
|
|
4
|
+
import { runClientCommand } from "./client-command.js";
|
|
5
|
+
import { classifyExistingMcpConfig } from "./classify.js";
|
|
6
|
+
import { buildInspectorCommand, buildInstallerCommand, buildScopedRemoverCommand, } from "./commands.js";
|
|
7
|
+
import { cliMcpClient, displayName, parseClient, validateMcpScope, } from "./known-client.js";
|
|
8
|
+
import { alreadySetupBlock, autoSetupFailureBlock, existingConfigDiffersBlock, manualCommandBlock, otherClientInstructionsBlock, successBlock, } from "./text.js";
|
|
9
|
+
export async function runMcpSetup(options) {
|
|
10
|
+
const client = await resolveClient(options.client);
|
|
11
|
+
validateMcpScope(client, options.scope);
|
|
12
|
+
const installClient = cliMcpClient(client);
|
|
13
|
+
if (installClient === null) {
|
|
14
|
+
write(otherClientInstructionsBlock());
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const installer = buildInstallerCommand(installClient, options.scope);
|
|
18
|
+
if (options.print) {
|
|
19
|
+
write(manualCommandBlock(client, installer));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
write(`Checking for Driggsby in ${displayName(client)} MCP config...\n`);
|
|
23
|
+
if (await handleExistingConfig(client, installClient, options.scope, installer)) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
write(`Adding Driggsby to ${displayName(client)} MCP config...\n`);
|
|
27
|
+
const result = await runClientCommand(installer, streamConfigOutput(client));
|
|
28
|
+
switch (result.kind) {
|
|
29
|
+
case "output":
|
|
30
|
+
if (result.output.succeeded) {
|
|
31
|
+
write(successBlock(client, codexCompletedLogin(result.output)));
|
|
32
|
+
}
|
|
33
|
+
else if (commandReportsExistingConfig(result.output)) {
|
|
34
|
+
const remover = buildScopedRemoverCommand(installClient, options.scope);
|
|
35
|
+
write(existingConfigDiffersBlock(client, remover, installer));
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
write(autoSetupFailureBlock(client, "The client command returned an error.", installer));
|
|
39
|
+
}
|
|
40
|
+
return;
|
|
41
|
+
case "not-found":
|
|
42
|
+
write(autoSetupFailureBlock(client, `${displayName(client)} is not installed or not on PATH.`, installer));
|
|
43
|
+
return;
|
|
44
|
+
case "spawn-error":
|
|
45
|
+
write(autoSetupFailureBlock(client, "Could not start the client command.", installer));
|
|
46
|
+
return;
|
|
47
|
+
case "timed-out":
|
|
48
|
+
write(autoSetupFailureBlock(client, "The client command timed out.", installer));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function resolveClient(requested) {
|
|
53
|
+
if (requested !== undefined) {
|
|
54
|
+
return parseClient(requested);
|
|
55
|
+
}
|
|
56
|
+
return promptForClient();
|
|
57
|
+
}
|
|
58
|
+
function defaultPromptStreams() {
|
|
59
|
+
return {
|
|
60
|
+
input: process.stdin,
|
|
61
|
+
output: process.stdout,
|
|
62
|
+
isInteractive: process.stdin.isTTY,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export async function promptForClient(streams = defaultPromptStreams()) {
|
|
66
|
+
if (!streams.isInteractive) {
|
|
67
|
+
throw new CliError("Pass a client name.\n\nExamples:\n npx driggsby@latest mcp setup claude-code\n npx driggsby@latest mcp setup codex\n npx driggsby@latest mcp setup other", 1);
|
|
68
|
+
}
|
|
69
|
+
streams.output.write("Which client are you setting up?\n\n 1. Claude Code\n 2. Codex\n 3. Other\n\n");
|
|
70
|
+
// terminal: false keeps the prompt a plain write (no cursor-control
|
|
71
|
+
// escapes); the TTY's own canonical mode handles echo, as in the original.
|
|
72
|
+
const readline = createInterface({
|
|
73
|
+
input: streams.input,
|
|
74
|
+
output: streams.output,
|
|
75
|
+
terminal: false,
|
|
76
|
+
});
|
|
77
|
+
// EOF (Ctrl+D) never settles readline's question(); it only closes the
|
|
78
|
+
// interface. Race the two so EOF becomes "no valid choice", exactly what
|
|
79
|
+
// the original did with a zero-byte read.
|
|
80
|
+
const closedAsEmptyChoice = new Promise((resolve) => {
|
|
81
|
+
readline.once("close", () => {
|
|
82
|
+
resolve("");
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
let choice;
|
|
86
|
+
try {
|
|
87
|
+
choice = trimLikeRust(await Promise.race([readline.question("Choose 1-3: "), closedAsEmptyChoice]));
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
readline.close();
|
|
91
|
+
}
|
|
92
|
+
switch (choice) {
|
|
93
|
+
case "1":
|
|
94
|
+
return "claude-code";
|
|
95
|
+
case "2":
|
|
96
|
+
return "codex";
|
|
97
|
+
case "3":
|
|
98
|
+
return "other";
|
|
99
|
+
default:
|
|
100
|
+
throw new CliError("Choose 1, 2, or 3.", 1);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Returns true when an existing config entry fully handled the run (already
|
|
104
|
+
// set up, or differs with remediation printed). Probe failures fall through
|
|
105
|
+
// to a normal install attempt.
|
|
106
|
+
async function handleExistingConfig(client, installClient, scope, installer) {
|
|
107
|
+
const result = await runClientCommand(buildInspectorCommand(installClient), false);
|
|
108
|
+
if (result.kind !== "output") {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
switch (classifyExistingMcpConfig(installClient, scope, result.output)) {
|
|
112
|
+
case "matches":
|
|
113
|
+
write(alreadySetupBlock(client));
|
|
114
|
+
return true;
|
|
115
|
+
case "differs":
|
|
116
|
+
write(existingConfigDiffersBlock(client, buildScopedRemoverCommand(installClient, scope), installer));
|
|
117
|
+
return true;
|
|
118
|
+
case "missing":
|
|
119
|
+
case "unknown":
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Only Codex streams: its MCP add performs an interactive OAuth login
|
|
124
|
+
// in-band, and swallowing that output makes setup look hung.
|
|
125
|
+
function streamConfigOutput(client) {
|
|
126
|
+
return client === "codex";
|
|
127
|
+
}
|
|
128
|
+
function commandReportsExistingConfig(output) {
|
|
129
|
+
return output.stdout.includes("already exists") || output.stderr.includes("already exists");
|
|
130
|
+
}
|
|
131
|
+
function codexCompletedLogin(output) {
|
|
132
|
+
return (output.stdout.includes("Successfully logged in.") ||
|
|
133
|
+
output.stderr.includes("Successfully logged in."));
|
|
134
|
+
}
|
|
135
|
+
function write(text) {
|
|
136
|
+
process.stdout.write(text);
|
|
137
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Turns an McpConfigCommand into something Node's spawn can run on every
|
|
2
|
+
// platform WITHOUT a shell. On POSIX the command runs as-is. On Windows,
|
|
3
|
+
// client CLIs are usually npm-installed .cmd shims, which Node (rightly)
|
|
4
|
+
// refuses to spawn directly — so we resolve the program ourselves against
|
|
5
|
+
// PATH + PATHEXT (never the current directory, which Windows shells would
|
|
6
|
+
// search first) and run shims through an explicit, absolute cmd.exe
|
|
7
|
+
// invocation with our own quoting. Real commands only ever carry fixed
|
|
8
|
+
// constant arguments (see commands.ts); nothing user-controlled may ever
|
|
9
|
+
// flow into a spawn plan.
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import { isAbsolute, join } from "node:path";
|
|
12
|
+
import {} from "./commands.js";
|
|
13
|
+
// null means the program does not exist on PATH (Windows only — POSIX lets
|
|
14
|
+
// spawn report ENOENT itself). platform and lookup are injectable so the
|
|
15
|
+
// Windows branch — including the absolute cmd.exe composition — stays
|
|
16
|
+
// unit-testable on every CI runner.
|
|
17
|
+
export function planSpawn(command, platform = process.platform, lookup) {
|
|
18
|
+
if (platform !== "win32") {
|
|
19
|
+
return { program: command.program, args: command.args, windowsVerbatimArguments: false };
|
|
20
|
+
}
|
|
21
|
+
const resolved = resolveWindowsProgram(command.program, lookup ?? defaultLookup());
|
|
22
|
+
if (resolved === null) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
if (resolved.kind === "direct") {
|
|
26
|
+
return { program: resolved.path, args: command.args, windowsVerbatimArguments: false };
|
|
27
|
+
}
|
|
28
|
+
const commandLine = [resolved.path, ...command.args].map(quoteForCmd).join(" ");
|
|
29
|
+
return {
|
|
30
|
+
// Absolute path: a bare "cmd.exe" would let Windows' legacy CreateProcess
|
|
31
|
+
// lookup find a planted cmd.exe in the current directory first.
|
|
32
|
+
program: join(process.env.SystemRoot ?? "C:\\Windows", "System32", "cmd.exe"),
|
|
33
|
+
args: ["/d", "/s", "/c", `"${commandLine}"`],
|
|
34
|
+
windowsVerbatimArguments: true,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function defaultLookup() {
|
|
38
|
+
return {
|
|
39
|
+
path: process.env.PATH ?? "",
|
|
40
|
+
pathext: process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD",
|
|
41
|
+
exists: existsSync,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function kindForPath(candidate) {
|
|
45
|
+
return /\.(cmd|bat)$/i.test(candidate) ? "shim" : "direct";
|
|
46
|
+
}
|
|
47
|
+
export function resolveWindowsProgram(program, lookup = defaultLookup()) {
|
|
48
|
+
// An explicit path (absolute, or containing a separator) is used as given.
|
|
49
|
+
if (isAbsolute(program) || program.includes("\\") || program.includes("/")) {
|
|
50
|
+
return lookup.exists(program) ? { kind: kindForPath(program), path: program } : null;
|
|
51
|
+
}
|
|
52
|
+
// Windows PATH semantics regardless of the host running the tests: split
|
|
53
|
+
// on ";" (a POSIX-style ":" would cut drive letters like C: in half).
|
|
54
|
+
const pathDirectories = lookup.path
|
|
55
|
+
.split(";")
|
|
56
|
+
// Real Windows PATHs sometimes carry quoted segments; strip the quotes so
|
|
57
|
+
// the directory still resolves.
|
|
58
|
+
.map((directory) => directory.replaceAll('"', ""))
|
|
59
|
+
.filter((directory) => directory !== "");
|
|
60
|
+
const extensions = lookup.pathext.split(";").filter((extension) => extension !== "");
|
|
61
|
+
const hasExtension = /\.[^\\/.]+$/.test(program);
|
|
62
|
+
for (const directory of pathDirectories) {
|
|
63
|
+
if (hasExtension) {
|
|
64
|
+
const exact = join(directory, program);
|
|
65
|
+
if (lookup.exists(exact)) {
|
|
66
|
+
return { kind: kindForPath(exact), path: exact };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const extension of extensions) {
|
|
70
|
+
const candidate = join(directory, program + extension);
|
|
71
|
+
if (lookup.exists(candidate)) {
|
|
72
|
+
return { kind: kindForPath(candidate), path: candidate };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
// Quoting for the cmd.exe /s /c command line: plain tokens pass through,
|
|
79
|
+
// anything else is wrapped in double quotes with embedded quotes doubled
|
|
80
|
+
// (the cmd convention) and trailing backslashes doubled (so a path ending
|
|
81
|
+
// in \ cannot escape the closing quote for the child's own parser). The
|
|
82
|
+
// safe set covers every constant argument the CLI actually runs, including
|
|
83
|
+
// the MCP URL; only fixed constants and PATH-resolved program paths may
|
|
84
|
+
// reach this function.
|
|
85
|
+
export function quoteForCmd(value) {
|
|
86
|
+
if (/^[A-Za-z0-9_\-.:\\/=]+$/.test(value)) {
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
89
|
+
const escaped = value.replaceAll('"', '""').replace(/(\\+)$/, "$1$1");
|
|
90
|
+
return `"${escaped}"`;
|
|
91
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Every user-facing mcp-setup text block, byte-compatible with the original
|
|
2
|
+
// Rust CLI output (pinned by the fixture parity tests). Terminal command
|
|
3
|
+
// suggestions are raw and copy-pasteable — never wrapped in backticks.
|
|
4
|
+
import { DRIGGSBY_MCP_URL, renderShellCommand } from "./commands.js";
|
|
5
|
+
import { displayName } from "./known-client.js";
|
|
6
|
+
export function nextStepLines(client, completedLogin) {
|
|
7
|
+
switch (client) {
|
|
8
|
+
case "claude-code":
|
|
9
|
+
return [" Open Claude Code, run /mcp, and authenticate Driggsby to get started."];
|
|
10
|
+
case "codex":
|
|
11
|
+
if (completedLogin) {
|
|
12
|
+
return [" Open Codex and ask it to use Driggsby."];
|
|
13
|
+
}
|
|
14
|
+
return [
|
|
15
|
+
" Complete the Driggsby sign-in in the browser window opened by Codex.",
|
|
16
|
+
" If no browser window opened, run:",
|
|
17
|
+
" codex mcp login driggsby",
|
|
18
|
+
];
|
|
19
|
+
case "other":
|
|
20
|
+
return otherClientInstructionLines();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function alreadySetupNextStepLines(client) {
|
|
24
|
+
switch (client) {
|
|
25
|
+
case "claude-code":
|
|
26
|
+
return [" Open Claude Code, run /mcp, and authenticate Driggsby if prompted."];
|
|
27
|
+
case "codex":
|
|
28
|
+
return [
|
|
29
|
+
" Open Codex and ask it to use Driggsby.",
|
|
30
|
+
" If Codex asks you to sign in, run:",
|
|
31
|
+
" codex mcp login driggsby",
|
|
32
|
+
];
|
|
33
|
+
case "other":
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function otherClientInstructionLines() {
|
|
38
|
+
return [
|
|
39
|
+
" Add a remote MCP server named driggsby.",
|
|
40
|
+
" Set its URL to https://app.driggsby.com/mcp.",
|
|
41
|
+
" Choose OAuth authentication if the client asks.",
|
|
42
|
+
" Complete the Driggsby browser sign-in when prompted.",
|
|
43
|
+
"",
|
|
44
|
+
"Requirement:",
|
|
45
|
+
" The MCP client must support OAuth-based MCP authentication.",
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
export function successBlock(client, completedLogin) {
|
|
49
|
+
return (`${displayName(client)} is set up.\n\nDriggsby MCP URL:\n ${DRIGGSBY_MCP_URL}\n\nNext:\n` +
|
|
50
|
+
joinLines(nextStepLines(client, completedLogin)));
|
|
51
|
+
}
|
|
52
|
+
export function alreadySetupBlock(client) {
|
|
53
|
+
return (`Driggsby is already set up in ${displayName(client)} MCP config.\n\n` +
|
|
54
|
+
`Driggsby MCP URL:\n ${DRIGGSBY_MCP_URL}\n\nNext:\n` +
|
|
55
|
+
joinLines(alreadySetupNextStepLines(client)));
|
|
56
|
+
}
|
|
57
|
+
export function existingConfigDiffersBlock(client, remover, installer) {
|
|
58
|
+
return (`Driggsby already exists in ${displayName(client)} MCP config, ` +
|
|
59
|
+
`but it does not match the expected Driggsby setup.\n\n` +
|
|
60
|
+
`Expected Driggsby MCP URL:\n ${DRIGGSBY_MCP_URL}\n\n` +
|
|
61
|
+
`To replace the existing entry, run:\n` +
|
|
62
|
+
` ${renderShellCommand(remover)}\n` +
|
|
63
|
+
` ${renderShellCommand(installer)}\n`);
|
|
64
|
+
}
|
|
65
|
+
export function manualCommandBlock(client, installer) {
|
|
66
|
+
return (`Run this command to add Driggsby to ${displayName(client)}:\n\n` +
|
|
67
|
+
` ${renderShellCommand(installer)}\n\n` +
|
|
68
|
+
`After adding it:\n` +
|
|
69
|
+
joinLines(nextStepLines(client, false)));
|
|
70
|
+
}
|
|
71
|
+
export function autoSetupFailureBlock(client, reason, installer) {
|
|
72
|
+
return (`Could not add Driggsby to ${displayName(client)}: ${reason}\n\n` +
|
|
73
|
+
manualCommandBlock(client, installer));
|
|
74
|
+
}
|
|
75
|
+
export function otherClientInstructionsBlock() {
|
|
76
|
+
return (`Driggsby currently supports OAuth-based remote MCP only.\n\n` +
|
|
77
|
+
`Driggsby OAuth MCP URL:\n ${DRIGGSBY_MCP_URL}\n\n` +
|
|
78
|
+
`For another MCP client or agent:\n` +
|
|
79
|
+
joinLines(otherClientInstructionLines()));
|
|
80
|
+
}
|
|
81
|
+
function joinLines(lines) {
|
|
82
|
+
return lines.map((line) => `${line}\n`).join("");
|
|
83
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Terminal-output text helpers. The sanitizer and prose wrapper live in
|
|
2
|
+
// @driggsby/deploy (the zero-dependency package this CLI already depends
|
|
3
|
+
// on) and are re-exported here so every caller keeps one import path. See
|
|
4
|
+
// the sanitizer's comment there for what it strips and for the deliberate
|
|
5
|
+
// boundary of that defense: it stops terminal corruption and hidden text,
|
|
6
|
+
// and callers print untrusted values quoted and length-capped so they read
|
|
7
|
+
// as names, not as the CLI's own voice.
|
|
8
|
+
export { capForTerminal, quotedForTerminal, sanitizeForTerminal, wrapProse } from "@driggsby/deploy";
|
|
9
|
+
// Rust's str::trim() trims the Unicode White_Space set, which differs from
|
|
10
|
+
// JavaScript's String.prototype.trim at exactly two code points: Rust trims
|
|
11
|
+
// U+0085 (NEL) and does NOT trim U+FEFF (ZWNBSP/BOM); JS does the opposite.
|
|
12
|
+
// Client-id parsing must match the retired Rust binary byte-for-byte, so
|
|
13
|
+
// trim against Rust's set explicitly.
|
|
14
|
+
const RUST_WHITESPACE_CLASS = "[\\t\\n\\v\\f\\r \\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]";
|
|
15
|
+
const LEADING_RUST_WHITESPACE = new RegExp(`^${RUST_WHITESPACE_CLASS}+`);
|
|
16
|
+
const TRAILING_RUST_WHITESPACE = new RegExp(`${RUST_WHITESPACE_CLASS}+$`);
|
|
17
|
+
export function trimLikeRust(value) {
|
|
18
|
+
return value.replace(LEADING_RUST_WHITESPACE, "").replace(TRAILING_RUST_WHITESPACE, "");
|
|
19
|
+
}
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
// Read the version from this package's own manifest so it can never drift
|
|
3
|
+
// from what npm published. Resolved relative to this module, which sits one
|
|
4
|
+
// directory below the package root both in src/ (tests) and dist/ (shipped).
|
|
5
|
+
const manifest = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
6
|
+
export const VERSION = manifest.version;
|
package/package.json
CHANGED
|
@@ -1,53 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "driggsby",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Set up and build on Driggsby, the MCP server for personal finance.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
|
-
"repository":
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/driggsby/driggsby.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://driggsby.com",
|
|
7
11
|
"type": "module",
|
|
8
12
|
"bin": {
|
|
9
|
-
"driggsby": "bin/driggsby"
|
|
10
|
-
},
|
|
11
|
-
"scripts": {
|
|
12
|
-
"postinstall": "node ./install.js"
|
|
13
|
-
},
|
|
14
|
-
"engines": {
|
|
15
|
-
"node": ">=18",
|
|
16
|
-
"npm": ">=9"
|
|
13
|
+
"driggsby": "bin/driggsby.js"
|
|
17
14
|
},
|
|
18
15
|
"files": [
|
|
19
16
|
"bin",
|
|
17
|
+
"dist",
|
|
20
18
|
"LICENSE",
|
|
21
|
-
"README.md"
|
|
22
|
-
"install.js",
|
|
23
|
-
"lib",
|
|
24
|
-
"package.json"
|
|
19
|
+
"README.md"
|
|
25
20
|
],
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
},
|
|
39
|
-
"x86_64-apple-darwin": {
|
|
40
|
-
"artifactName": "driggsby-x86_64-apple-darwin.tar.xz",
|
|
41
|
-
"binaryPath": "driggsby"
|
|
42
|
-
},
|
|
43
|
-
"aarch64-unknown-linux-gnu": {
|
|
44
|
-
"artifactName": "driggsby-aarch64-unknown-linux-gnu.tar.xz",
|
|
45
|
-
"binaryPath": "driggsby"
|
|
46
|
-
},
|
|
47
|
-
"x86_64-unknown-linux-gnu": {
|
|
48
|
-
"artifactName": "driggsby-x86_64-unknown-linux-gnu.tar.xz",
|
|
49
|
-
"binaryPath": "driggsby"
|
|
50
|
-
}
|
|
51
|
-
}
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "npm run build --prefix ../sdk && tsc -p ../deploy/tsconfig.build.json && tsc -p tsconfig.build.json",
|
|
23
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
24
|
+
"pretest": "npm run build",
|
|
25
|
+
"test": "node --test \"src/**/*.test.ts\""
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=18"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@driggsby/deploy": "0.2.0",
|
|
32
|
+
"@driggsby/sdk": "0.2.0"
|
|
52
33
|
}
|
|
53
34
|
}
|
package/bin/driggsby
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { readPackageJson, resolvePlatform, unsupportedPlatformMessage, } from "../lib/artifacts.js";
|
|
3
|
-
const packageJson = readPackageJson();
|
|
4
|
-
const resolution = resolvePlatform(packageJson);
|
|
5
|
-
if (!resolution.platform) {
|
|
6
|
-
console.error(unsupportedPlatformMessage(resolution));
|
|
7
|
-
process.exit(1);
|
|
8
|
-
}
|
|
9
|
-
console.error("Driggsby native binary is not active. Reinstall with npm scripts enabled, or rerun with npm_config_foreground_scripts=true to see installer output.");
|
|
10
|
-
process.exit(1);
|