zero-config-cli-bridge 1.0.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 ADDED
@@ -0,0 +1,164 @@
1
+ # zero-config-cli-bridge
2
+
3
+ **Zero Setup. Zero API Keys. Just your local CLI.**
4
+
5
+ > Still copy-pasting `GITHUB_TOKEN` into `.env` files?
6
+ > Still wondering why your AI agent can't see your private repos?
7
+ > Stop. Your machine already has everything it needs.
8
+
9
+ `zero-config-cli-bridge` is an MCP (Model Context Protocol) server that exposes your **already-authenticated local CLI tools** directly to LLM agents — no API keys, no OAuth flows, no configuration.
10
+
11
+ If `gh issue list` works in your terminal, it works in Claude Desktop. That's it.
12
+
13
+ ---
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ # Option A: npx (no install required)
19
+ npx -y zero-config-cli-bridge
20
+
21
+ # Option B: global install
22
+ npm install -g zero-config-cli-bridge
23
+ ```
24
+
25
+ **Prerequisites:** `gh` CLI installed and authenticated (`gh auth login`)
26
+
27
+ ---
28
+
29
+ ## Claude Desktop Setup
30
+
31
+ Add to `~/AppData/Roaming/Claude/claude_desktop_config.json` (Windows) or
32
+ `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):
33
+
34
+ ```json
35
+ {
36
+ "mcpServers": {
37
+ "zero-config-cli-bridge": {
38
+ "command": "npx",
39
+ "args": ["-y", "zero-config-cli-bridge"]
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ Restart Claude Desktop. Done.
46
+
47
+ ---
48
+
49
+ ## What you can do
50
+
51
+ Ask Claude naturally:
52
+
53
+ - *"List the open issues in cli/cli"*
54
+ - *"Show me the last 20 PRs merged into main"*
55
+ - *"Which issues are labeled bug and unassigned?"*
56
+
57
+ Claude will call the MCP tools, hit your local `gh` binary with your existing authentication, and return real data — without ever seeing your token.
58
+
59
+ ---
60
+
61
+ ## Available Tools
62
+
63
+ | Tool | Command | Description |
64
+ |------|---------|-------------|
65
+ | `gh_issue_list` | `gh issue list` | List issues with filters |
66
+ | `gh_pr_list` | `gh pr list` | List pull requests with filters |
67
+
68
+ ### Arguments
69
+
70
+ **gh_issue_list**
71
+ | Argument | Type | Description |
72
+ |----------|------|-------------|
73
+ | `repo` | string | `OWNER/REPO` format. Omit to use current directory's remote. |
74
+ | `limit` | number | Max results (default: 30) |
75
+ | `state` | string | `open` / `closed` / `all` |
76
+ | `label` | string | Filter by label |
77
+ | `assignee` | string | Filter by assignee login |
78
+
79
+ **gh_pr_list**
80
+ | Argument | Type | Description |
81
+ |----------|------|-------------|
82
+ | `repo` | string | `OWNER/REPO` format |
83
+ | `limit` | number | Max results (default: 30) |
84
+ | `state` | string | `open` / `closed` / `merged` |
85
+ | `base` | string | Filter by base branch |
86
+ | `assignee` | string | Filter by assignee login |
87
+
88
+ ---
89
+
90
+ ## Security: Layered Defense
91
+
92
+ This server is **read-only by design**. Destructive operations are blocked at multiple layers:
93
+
94
+ ```
95
+ Request
96
+
97
+
98
+ ┌─────────────────────────────────────────────┐
99
+ │ Layer 1: LLM Self-Governance │
100
+ │ The tool schema explicitly describes │
101
+ │ read-only intent. Claude refuses mutating │
102
+ │ requests before calling the tool at all. │
103
+ └─────────────────┬───────────────────────────┘
104
+ │ (if bypassed)
105
+
106
+ ┌─────────────────────────────────────────────┐
107
+ │ Layer 2: Keyword Validator (security.ts) │
108
+ │ Blocks: create, delete, edit, rm, │
109
+ │ update, close, reopen, merge │
110
+ │ Returns: "Mutating commands are blocked" │
111
+ └─────────────────┬───────────────────────────┘
112
+ │ (if bypassed)
113
+
114
+ ┌─────────────────────────────────────────────┐
115
+ │ Layer 3: gh CLI Error │
116
+ │ Invalid commands fail at the gh binary │
117
+ │ level with a non-zero exit code. │
118
+ └─────────────────────────────────────────────┘
119
+ ```
120
+
121
+ **Verified in production:** TEST 3 on Claude Desktop confirmed Layer 1 blocked a `delete` injection attempt before the tool was even invoked.
122
+
123
+ ### Additional Safeguards
124
+
125
+ - **Timeout:** Processes are killed with `SIGKILL` after 3000ms — no hanging on interactive prompts
126
+ - **Output truncation:** stdout/stderr capped at 2000 characters to prevent context exhaustion. Long outputs append `...[Output truncated. Use grep/jq to filter]`
127
+ - **CI mode:** `CI=true` is injected to suppress interactive prompts
128
+
129
+ ---
130
+
131
+ ## How It Works
132
+
133
+ ```
134
+ Claude Desktop
135
+ │ MCP (stdio JSON-RPC)
136
+
137
+ zero-config-cli-bridge
138
+ │ child_process.spawn
139
+
140
+ gh CLI (your local binary)
141
+ │ uses ~/.config/gh/hosts.yml
142
+
143
+ GitHub API
144
+ ```
145
+
146
+ Your token never leaves your machine. The bridge just connects Claude to a process that was already authorized.
147
+
148
+ ---
149
+
150
+ ## Roadmap
151
+
152
+ - [ ] `gh issue view` / `gh pr view`
153
+ - [ ] `gh run list` / `gh release list`
154
+ - [ ] Dynamic schema generation from `gh --help`
155
+ - [ ] Support for `git`, `docker`, `kubectl`
156
+ - [ ] Configurable allow-list for additional read-only commands
157
+
158
+ PRs welcome.
159
+
160
+ ---
161
+
162
+ ## License
163
+
164
+ MIT
@@ -0,0 +1,7 @@
1
+ export interface ExecuteResult {
2
+ stdout: string;
3
+ stderr: string;
4
+ exitCode: number;
5
+ }
6
+ export declare function executeCommand(command: string): Promise<ExecuteResult>;
7
+ //# sourceMappingURL=executor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAWD,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAqCtE"}
@@ -0,0 +1,42 @@
1
+ import { spawn } from 'child_process';
2
+ const MAX_OUTPUT = 2000;
3
+ const TRUNCATION_MSG = '\n...[Output truncated. Use grep/jq to filter]';
4
+ const TIMEOUT_MS = 3000;
5
+ function truncate(output) {
6
+ if (output.length <= MAX_OUTPUT)
7
+ return output;
8
+ return output.slice(0, MAX_OUTPUT) + TRUNCATION_MSG;
9
+ }
10
+ export function executeCommand(command) {
11
+ return new Promise((resolve, reject) => {
12
+ const proc = spawn('sh', ['-c', command], {
13
+ env: { ...process.env, CI: 'true' },
14
+ stdio: ['ignore', 'pipe', 'pipe'],
15
+ });
16
+ let stdoutBuf = '';
17
+ let stderrBuf = '';
18
+ proc.stdout.on('data', (chunk) => {
19
+ stdoutBuf += chunk.toString();
20
+ });
21
+ proc.stderr.on('data', (chunk) => {
22
+ stderrBuf += chunk.toString();
23
+ });
24
+ const timer = setTimeout(() => {
25
+ proc.kill('SIGKILL');
26
+ reject(new Error(`Command timed out after ${TIMEOUT_MS}ms: ${command}`));
27
+ }, TIMEOUT_MS);
28
+ proc.on('close', (code) => {
29
+ clearTimeout(timer);
30
+ resolve({
31
+ stdout: truncate(stdoutBuf),
32
+ stderr: truncate(stderrBuf),
33
+ exitCode: code ?? 1,
34
+ });
35
+ });
36
+ proc.on('error', (err) => {
37
+ clearTimeout(timer);
38
+ reject(err);
39
+ });
40
+ });
41
+ }
42
+ //# sourceMappingURL=executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAQtC,MAAM,UAAU,GAAG,IAAI,CAAC;AACxB,MAAM,cAAc,GAAG,gDAAgD,CAAC;AACxE,MAAM,UAAU,GAAG,IAAI,CAAC;AAExB,SAAS,QAAQ,CAAC,MAAc;IAC9B,IAAI,MAAM,CAAC,MAAM,IAAI,UAAU;QAAE,OAAO,MAAM,CAAC;IAC/C,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,GAAG,cAAc,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE;YACxC,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE;YACnC,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;SAClC,CAAC,CAAC;QAEH,IAAI,SAAS,GAAG,EAAE,CAAC;QACnB,IAAI,SAAS,GAAG,EAAE,CAAC;QAEnB,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACvC,SAAS,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QAChC,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACvC,SAAS,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QAChC,CAAC,CAAC,CAAC;QAEH,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACrB,MAAM,CAAC,IAAI,KAAK,CAAC,2BAA2B,UAAU,OAAO,OAAO,EAAE,CAAC,CAAC,CAAC;QAC3E,CAAC,EAAE,UAAU,CAAC,CAAC;QAEf,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAmB,EAAE,EAAE;YACvC,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO,CAAC;gBACN,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC;gBAC3B,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC;gBAC3B,QAAQ,EAAE,IAAI,IAAI,CAAC;aACpB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE;YAC9B,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
+ import { executeCommand } from './executor.js';
6
+ import { validateReadOnly } from './security.js';
7
+ import { buildGhCommand, getToolDefinitions } from './schema.js';
8
+ const server = new Server({ name: 'zero-config-cli-bridge', version: '1.0.0' }, { capabilities: { tools: {} } });
9
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
10
+ return { tools: getToolDefinitions() };
11
+ });
12
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
13
+ const { name: toolName, arguments: rawArgs } = request.params;
14
+ const args = (rawArgs ?? {});
15
+ const command = buildGhCommand(toolName, args);
16
+ // Security check: block mutating commands
17
+ try {
18
+ validateReadOnly(command);
19
+ }
20
+ catch (err) {
21
+ const msg = err instanceof Error ? err.message : String(err);
22
+ return {
23
+ content: [{ type: 'text', text: msg }],
24
+ isError: true,
25
+ };
26
+ }
27
+ // Execute command
28
+ let result;
29
+ try {
30
+ result = await executeCommand(command);
31
+ }
32
+ catch (err) {
33
+ const msg = err instanceof Error ? err.message : String(err);
34
+ return {
35
+ content: [{ type: 'text', text: `Execution error: ${msg}` }],
36
+ isError: true,
37
+ };
38
+ }
39
+ const output = result.stdout ||
40
+ result.stderr ||
41
+ `(no output, exit code ${result.exitCode})`;
42
+ const isError = result.exitCode !== 0;
43
+ return {
44
+ content: [{ type: 'text', text: output }],
45
+ isError,
46
+ };
47
+ });
48
+ async function main() {
49
+ const transport = new StdioServerTransport();
50
+ await server.connect(transport);
51
+ // Server is running on stdio - no console output to avoid corrupting MCP protocol
52
+ }
53
+ main().catch((err) => {
54
+ process.stderr.write(`Fatal error: ${err}\n`);
55
+ process.exit(1);
56
+ });
57
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjE,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB,EAAE,IAAI,EAAE,wBAAwB,EAAE,OAAO,EAAE,OAAO,EAAE,EACpD,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAChC,CAAC;AAEF,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE;IAC1D,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,EAAE,CAAC;AACzC,CAAC,CAAC,CAAC;AAEH,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;IAChE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAC9D,MAAM,IAAI,GAAG,CAAC,OAAO,IAAI,EAAE,CAA4B,CAAC;IAExD,MAAM,OAAO,GAAG,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAE/C,0CAA0C;IAC1C,IAAI,CAAC;QACH,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC5B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;YACtC,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IAED,kBAAkB;IAClB,IAAI,MAAM,CAAC;IACX,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,oBAAoB,GAAG,EAAE,EAAE,CAAC;YAC5D,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GACV,MAAM,CAAC,MAAM;QACb,MAAM,CAAC,MAAM;QACb,yBAAyB,MAAM,CAAC,QAAQ,GAAG,CAAC;IAE9C,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,KAAK,CAAC,CAAC;IAEtC,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QACzC,OAAO;KACR,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,kFAAkF;AACpF,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,15 @@
1
+ export interface ToolDefinition {
2
+ name: string;
3
+ description: string;
4
+ inputSchema: {
5
+ type: 'object';
6
+ properties: Record<string, {
7
+ type: string;
8
+ description: string;
9
+ }>;
10
+ required?: string[];
11
+ };
12
+ }
13
+ export declare function getToolDefinitions(): ToolDefinition[];
14
+ export declare function buildGhCommand(toolName: string, args: Record<string, unknown>): string;
15
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ,CAAC;QACf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,WAAW,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAClE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;CACH;AAED,wBAAgB,kBAAkB,IAAI,cAAc,EAAE,CAqErD;AAED,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,CA+BR"}
package/dist/schema.js ADDED
@@ -0,0 +1,98 @@
1
+ export function getToolDefinitions() {
2
+ return [
3
+ {
4
+ name: 'gh_issue_list',
5
+ description: 'List GitHub issues for a repository using the local `gh` CLI. ' +
6
+ 'Requires gh to be installed and authenticated.',
7
+ inputSchema: {
8
+ type: 'object',
9
+ properties: {
10
+ repo: {
11
+ type: 'string',
12
+ description: 'Repository in OWNER/REPO format (e.g. "cli/cli"). ' +
13
+ 'If omitted, uses the current directory\'s git remote.',
14
+ },
15
+ limit: {
16
+ type: 'number',
17
+ description: 'Maximum number of issues to fetch (default: 30).',
18
+ },
19
+ state: {
20
+ type: 'string',
21
+ description: 'Filter by state: "open" (default), "closed", or "all".',
22
+ },
23
+ label: {
24
+ type: 'string',
25
+ description: 'Filter by label name.',
26
+ },
27
+ assignee: {
28
+ type: 'string',
29
+ description: 'Filter by assignee login.',
30
+ },
31
+ },
32
+ },
33
+ },
34
+ {
35
+ name: 'gh_pr_list',
36
+ description: 'List GitHub pull requests for a repository using the local `gh` CLI. ' +
37
+ 'Requires gh to be installed and authenticated.',
38
+ inputSchema: {
39
+ type: 'object',
40
+ properties: {
41
+ repo: {
42
+ type: 'string',
43
+ description: 'Repository in OWNER/REPO format (e.g. "cli/cli"). ' +
44
+ 'If omitted, uses the current directory\'s git remote.',
45
+ },
46
+ limit: {
47
+ type: 'number',
48
+ description: 'Maximum number of PRs to fetch (default: 30).',
49
+ },
50
+ state: {
51
+ type: 'string',
52
+ description: 'Filter by state: "open" (default), "closed", or "merged".',
53
+ },
54
+ base: {
55
+ type: 'string',
56
+ description: 'Filter by base branch name.',
57
+ },
58
+ assignee: {
59
+ type: 'string',
60
+ description: 'Filter by assignee login.',
61
+ },
62
+ },
63
+ },
64
+ },
65
+ ];
66
+ }
67
+ export function buildGhCommand(toolName, args) {
68
+ const parts = ['gh'];
69
+ if (toolName === 'gh_issue_list') {
70
+ parts.push('issue', 'list');
71
+ }
72
+ else if (toolName === 'gh_pr_list') {
73
+ parts.push('pr', 'list');
74
+ }
75
+ else {
76
+ throw new Error(`Unknown tool: ${toolName}`);
77
+ }
78
+ if (args['repo'] !== undefined) {
79
+ parts.push('--repo', String(args['repo']));
80
+ }
81
+ if (args['limit'] !== undefined) {
82
+ parts.push('--limit', String(args['limit']));
83
+ }
84
+ if (args['state'] !== undefined) {
85
+ parts.push('--state', String(args['state']));
86
+ }
87
+ if (toolName === 'gh_issue_list' && args['label'] !== undefined) {
88
+ parts.push('--label', String(args['label']));
89
+ }
90
+ if (args['assignee'] !== undefined) {
91
+ parts.push('--assignee', String(args['assignee']));
92
+ }
93
+ if (toolName === 'gh_pr_list' && args['base'] !== undefined) {
94
+ parts.push('--base', String(args['base']));
95
+ }
96
+ return parts.join(' ');
97
+ }
98
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function validateReadOnly(command: string): void;
2
+ //# sourceMappingURL=security.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../src/security.ts"],"names":[],"mappings":"AAWA,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAWtD"}
@@ -0,0 +1,21 @@
1
+ const BLOCKED_KEYWORDS = [
2
+ 'create',
3
+ 'delete',
4
+ 'edit',
5
+ 'rm',
6
+ 'update',
7
+ 'close',
8
+ 'reopen',
9
+ 'merge',
10
+ ];
11
+ export function validateReadOnly(command) {
12
+ const lower = command.toLowerCase();
13
+ for (const keyword of BLOCKED_KEYWORDS) {
14
+ // Match keyword as a whole word (surrounded by non-alphanumeric chars or at boundaries)
15
+ const pattern = new RegExp(`(?<![a-z0-9])${keyword}(?![a-z0-9])`);
16
+ if (pattern.test(lower)) {
17
+ throw new Error('Error: Mutating commands are blocked in MVP version. Use read-only commands.');
18
+ }
19
+ }
20
+ }
21
+ //# sourceMappingURL=security.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.js","sourceRoot":"","sources":["../src/security.ts"],"names":[],"mappings":"AAAA,MAAM,gBAAgB,GAAG;IACvB,QAAQ;IACR,QAAQ;IACR,MAAM;IACN,IAAI;IACJ,QAAQ;IACR,OAAO;IACP,QAAQ;IACR,OAAO;CACR,CAAC;AAEF,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IACpC,KAAK,MAAM,OAAO,IAAI,gBAAgB,EAAE,CAAC;QACvC,wFAAwF;QACxF,MAAM,OAAO,GAAG,IAAI,MAAM,CAAC,gBAAgB,OAAO,cAAc,CAAC,CAAC;QAClE,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CACb,8EAA8E,CAC/E,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "zero-config-cli-bridge",
3
+ "version": "1.0.0",
4
+ "description": "Zero Setup. Zero API Keys. Expose your local authenticated CLIs as MCP tools.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "zero-config-cli-bridge": "./dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc && node -e \"import('fs').then(({readFileSync,writeFileSync})=>{const p='dist/index.js';writeFileSync(p,'#!/usr/bin/env node\\n'+readFileSync(p,'utf8'))})\" && chmod +x dist/index.js",
17
+ "start": "node dist/index.js"
18
+ },
19
+ "keywords": [
20
+ "mcp",
21
+ "model-context-protocol",
22
+ "github",
23
+ "gh",
24
+ "cli",
25
+ "llm",
26
+ "claude",
27
+ "zero-config",
28
+ "agent",
29
+ "tool"
30
+ ],
31
+ "license": "MIT",
32
+ "engines": {
33
+ "node": ">=20.0.0"
34
+ },
35
+ "dependencies": {
36
+ "@modelcontextprotocol/sdk": "^1.0.0"
37
+ },
38
+ "devDependencies": {
39
+ "typescript": "^5.3.0",
40
+ "@types/node": "^20.0.0"
41
+ }
42
+ }