@mcpbridgeapp/cli 0.0.0-stage → 0.1.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.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +346 -3
  3. package/dist/agents/broken.d.ts +24 -0
  4. package/dist/agents/broken.js +31 -0
  5. package/dist/agents/broken.js.map +1 -0
  6. package/dist/agents/cli.d.ts +47 -0
  7. package/dist/agents/cli.js +270 -0
  8. package/dist/agents/cli.js.map +1 -0
  9. package/dist/agents/health.d.ts +38 -0
  10. package/dist/agents/health.js +53 -0
  11. package/dist/agents/health.js.map +1 -0
  12. package/dist/agents/ollama.d.ts +27 -0
  13. package/dist/agents/ollama.js +118 -0
  14. package/dist/agents/ollama.js.map +1 -0
  15. package/dist/agents/openai.d.ts +35 -0
  16. package/dist/agents/openai.js +168 -0
  17. package/dist/agents/openai.js.map +1 -0
  18. package/dist/agents/outcome.d.ts +34 -0
  19. package/dist/agents/outcome.js +96 -0
  20. package/dist/agents/outcome.js.map +1 -0
  21. package/dist/agents/permissions.d.ts +44 -0
  22. package/dist/agents/permissions.js +68 -0
  23. package/dist/agents/permissions.js.map +1 -0
  24. package/dist/agents/registry.d.ts +56 -0
  25. package/dist/agents/registry.js +167 -0
  26. package/dist/agents/registry.js.map +1 -0
  27. package/dist/agents/shim.d.ts +35 -0
  28. package/dist/agents/shim.js +53 -0
  29. package/dist/agents/shim.js.map +1 -0
  30. package/dist/agents/types.d.ts +69 -0
  31. package/dist/agents/types.js +28 -0
  32. package/dist/agents/types.js.map +1 -0
  33. package/dist/config.d.ts +99 -0
  34. package/dist/config.js +78 -0
  35. package/dist/config.js.map +1 -0
  36. package/dist/connect.d.ts +35 -0
  37. package/dist/connect.js +124 -0
  38. package/dist/connect.js.map +1 -0
  39. package/dist/index.d.ts +2 -0
  40. package/dist/index.js +269 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/limit.d.ts +14 -0
  43. package/dist/limit.js +33 -0
  44. package/dist/limit.js.map +1 -0
  45. package/dist/protocol.d.ts +175 -0
  46. package/dist/protocol.js +86 -0
  47. package/dist/protocol.js.map +1 -0
  48. package/dist/secrets.d.ts +27 -0
  49. package/dist/secrets.js +53 -0
  50. package/dist/secrets.js.map +1 -0
  51. package/dist/server.d.ts +5 -0
  52. package/dist/server.js +97 -0
  53. package/dist/server.js.map +1 -0
  54. package/dist/tasks.d.ts +48 -0
  55. package/dist/tasks.js +103 -0
  56. package/dist/tasks.js.map +1 -0
  57. package/dist/tools.d.ts +25 -0
  58. package/dist/tools.js +71 -0
  59. package/dist/tools.js.map +1 -0
  60. package/dist/ui/page.d.ts +7 -0
  61. package/dist/ui/page.js +428 -0
  62. package/dist/ui/page.js.map +1 -0
  63. package/dist/ui/presets.d.ts +22 -0
  64. package/dist/ui/presets.js +57 -0
  65. package/dist/ui/presets.js.map +1 -0
  66. package/dist/ui/server.d.ts +38 -0
  67. package/dist/ui/server.js +567 -0
  68. package/dist/ui/server.js.map +1 -0
  69. package/package.json +95 -6
@@ -0,0 +1,167 @@
1
+ import { BrokenAgent } from './broken.js';
2
+ import { CliAgent } from './cli.js';
3
+ import { OllamaAgent } from './ollama.js';
4
+ import { OpenAiAgent } from './openai.js';
5
+ import { HealthStore } from './health.js';
6
+ import { classifyFailure, onlyAnnounced, selfReportedFailure } from './outcome.js';
7
+ import { failed } from './types.js';
8
+ export class AgentRegistry {
9
+ health;
10
+ agents = new Map();
11
+ /**
12
+ * The health store is passed in so it can outlive a registry: the control panel rebuilds
13
+ * the registry whenever the config changes, and what was learned must survive that.
14
+ */
15
+ misconfigured = new Map();
16
+ constructor(config, health = new HealthStore()) {
17
+ this.health = health;
18
+ for (const [name, agentConfig] of Object.entries(config.agents)) {
19
+ const agent = build(name.toLowerCase(), agentConfig, config.maxOutputChars);
20
+ if (agent instanceof BrokenAgent)
21
+ this.misconfigured.set(agent.name, agent.reason);
22
+ this.agents.set(name.toLowerCase(), tracked(agent, health));
23
+ }
24
+ }
25
+ /** Why an agent's configuration was rejected, or null if it was accepted. */
26
+ configError(name) {
27
+ return this.misconfigured.get(name.trim().toLowerCase()) ?? null;
28
+ }
29
+ find(name) {
30
+ return this.agents.get(name.trim().toLowerCase());
31
+ }
32
+ names() {
33
+ return [...this.agents.keys()].sort();
34
+ }
35
+ /** Probes every agent at once, since each probe is a filesystem or socket check. */
36
+ async statuses() {
37
+ const probed = await Promise.all([...this.agents.values()].map(async (agent) => {
38
+ const probe = await agent.probe();
39
+ const status = this.health.state(agent.name, probe.available);
40
+ const health = this.health.get(agent.name);
41
+ const failure = health?.lastFailure;
42
+ const remembered = status !== 'ready' && status !== 'unavailable' && failure
43
+ ? `${STATE_TEXT[status]} on the last call (${new Date(failure.at).toISOString()}): ${failure.message}${failure.retryAt ? ` -- it said to try again ${failure.retryAt}` : ''}`
44
+ : null;
45
+ const result = {
46
+ name: agent.name,
47
+ description: agent.description,
48
+ requires: agent.requires,
49
+ available: status === 'ready',
50
+ status,
51
+ readsFiles: agent.readsFiles,
52
+ writesFiles: agent.writesFiles,
53
+ runsCommands: agent.runsCommands,
54
+ timeoutSeconds: Math.round(agent.timeoutMs / 1000),
55
+ detail: remembered ?? probe.detail,
56
+ calls: health?.calls ?? 0,
57
+ };
58
+ if (failure) {
59
+ result.lastError = failure.message;
60
+ result.lastErrorAt = new Date(failure.at).toISOString();
61
+ if (failure.retryAt)
62
+ result.retryAt = failure.retryAt;
63
+ }
64
+ if (health?.lastSuccessAt)
65
+ result.lastSuccessAt = new Date(health.lastSuccessAt).toISOString();
66
+ return result;
67
+ }));
68
+ return probed.sort((a, b) => a.name.localeCompare(b.name));
69
+ }
70
+ }
71
+ const STATE_TEXT = {
72
+ ready: 'ready',
73
+ unavailable: 'unavailable',
74
+ 'quota-exhausted': 'usage limit reached',
75
+ 'sign-in-failed': 'sign-in failed',
76
+ 'directory-not-trusted': 'refused the working directory',
77
+ };
78
+ /**
79
+ * Every run goes through here, so whichever front door a call came in by, the result is
80
+ * read the same way: an agent that exited 0 but says it could not do the work is a
81
+ * failure, and every outcome is remembered for list_agents.
82
+ */
83
+ function tracked(agent, health) {
84
+ return {
85
+ get name() { return agent.name; },
86
+ get description() { return agent.description; },
87
+ get requires() { return agent.requires; },
88
+ get readsFiles() { return agent.readsFiles; },
89
+ get writesFiles() { return agent.writesFiles; },
90
+ get runsCommands() { return agent.runsCommands; },
91
+ get timeoutMs() { return agent.timeoutMs; },
92
+ probe: () => agent.probe(),
93
+ run: async (prompt, options) => {
94
+ // a used-up quota or a failed sign-in fails the same way next time: four tasks
95
+ // started together on one exhausted account should cost one failure, not four
96
+ const known = health.get(agent.name)?.lastFailure;
97
+ const state = health.state(agent.name, true);
98
+ if (!options.force && known && state !== 'ready') {
99
+ return failed(agent.name, `not started: ${agent.name} hit "${STATE_TEXT[state]}" on a call at ${new Date(known.at).toISOString()} ` +
100
+ `(${known.message})${known.retryAt ? `, and said to try again ${known.retryAt}` : ''}. ` +
101
+ 'Choose another agent; this one is tried again once that clears, or when its owner retests it.');
102
+ }
103
+ const result = await agent.run(prompt, options);
104
+ const judged = judge(agent, result);
105
+ health.record(agent.name, judged);
106
+ return judged;
107
+ },
108
+ };
109
+ }
110
+ function judge(agent, result) {
111
+ const name = agent.name;
112
+ if (result.succeeded) {
113
+ const admitted = selfReportedFailure(result.output);
114
+ if (admitted) {
115
+ return {
116
+ ...result,
117
+ succeeded: false,
118
+ diagnostics: `${name} exited normally, but its answer says it could not do the task: "${admitted}" ` +
119
+ 'Treat "output" as its explanation, not as a result.',
120
+ };
121
+ }
122
+ if (onlyAnnounced(result.output)) {
123
+ const limited = !agent.writesFiles || !agent.runsCommands;
124
+ return {
125
+ ...result,
126
+ succeeded: false,
127
+ diagnostics: `${name} exited normally, but its whole answer only says what it was about to do. ` +
128
+ (limited
129
+ ? 'It probably stopped at a step it was not allowed to take -- it is not allowed to ' +
130
+ `${[!agent.writesFiles ? 'change files' : '', !agent.runsCommands ? 'run commands' : ''].filter(Boolean).join(' or ')} ` +
131
+ 'here. Ask it only to read and report, or have its owner allow that step for this agent.'
132
+ : 'Ask again, or give it a smaller task.'),
133
+ };
134
+ }
135
+ }
136
+ if (classifyFailure(result.diagnostics).kind === 'trust') {
137
+ return {
138
+ ...result,
139
+ diagnostics: `${result.diagnostics}\n\nThis CLI refuses directories it has not been told to trust when it runs ` +
140
+ 'unattended. mcpbridgeapp does not trust them on its behalf, because the directory is chosen by ' +
141
+ 'whichever client made the call. If you trust the projects your clients send it to, set the ' +
142
+ 'variable the CLI names under this agent\'s "env" in config.json, e.g. ' +
143
+ '"env": { "GEMINI_CLI_TRUST_WORKSPACE": "true" } for gemini.',
144
+ };
145
+ }
146
+ return result;
147
+ }
148
+ /**
149
+ * A rejected config becomes a BrokenAgent rather than an exception: one typo should cost
150
+ * that agent, not the whole bridge and every other agent with it.
151
+ */
152
+ function build(name, config, maxOutputChars) {
153
+ try {
154
+ switch (config.type) {
155
+ case 'ollama':
156
+ return new OllamaAgent(name, config, maxOutputChars);
157
+ case 'openai':
158
+ return new OpenAiAgent(name, config, maxOutputChars);
159
+ default:
160
+ return new CliAgent(name, config, maxOutputChars);
161
+ }
162
+ }
163
+ catch (error) {
164
+ return new BrokenAgent(name, error.message);
165
+ }
166
+ }
167
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/agents/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * What a Windows batch shim actually invokes.
3
+ *
4
+ * @param command the executable to run instead of the shim
5
+ * @param prefix arguments that must come before the agent's own, e.g. the script path
6
+ * when the shim runs a .js file through node
7
+ */
8
+ export interface ShimTarget {
9
+ command: string;
10
+ prefix: string[];
11
+ }
12
+ /**
13
+ * Finds the real program behind an npm-generated .cmd shim.
14
+ *
15
+ * <p>Windows users get a shim for every npm-installed CLI, and a shim can only be run
16
+ * through a shell — which a prompt must never touch. Rather than refusing every such
17
+ * agent, which on Windows is all of them, the shim is read and the program it calls is
18
+ * run directly. The shim is only ever read, never executed.
19
+ *
20
+ * <p>npm writes the target as a quoted path built from %dp0%, the shim's own directory:
21
+ *
22
+ * <pre>
23
+ * "%dp0%\\node_modules\\@anthropic-ai\\claude-code\\bin\\claude.exe" %*
24
+ * </pre>
25
+ *
26
+ * and, for packages without a native binary, as a script passed to node:
27
+ *
28
+ * <pre>
29
+ * "%_prog%" "%dp0%\\node_modules\\some-cli\\cli.js" %*
30
+ * </pre>
31
+ *
32
+ * Both shapes are handled, and a target that is not on disk is ignored, so a shim this
33
+ * does not understand falls back to being refused rather than producing a wrong command.
34
+ */
35
+ export declare function parseShimTarget(content: string, shimDir: string, nodeExecutable: string, exists?: (path: string) => boolean): ShimTarget | null;
@@ -0,0 +1,53 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { win32 as windowsPath } from 'node:path';
3
+ const QUOTED_DP0 = /"([^"]*%dp0%[^"]*)"/gi;
4
+ const RUNNABLE = /\.(exe|com|js|cjs|mjs)$/i;
5
+ /**
6
+ * Finds the real program behind an npm-generated .cmd shim.
7
+ *
8
+ * <p>Windows users get a shim for every npm-installed CLI, and a shim can only be run
9
+ * through a shell — which a prompt must never touch. Rather than refusing every such
10
+ * agent, which on Windows is all of them, the shim is read and the program it calls is
11
+ * run directly. The shim is only ever read, never executed.
12
+ *
13
+ * <p>npm writes the target as a quoted path built from %dp0%, the shim's own directory:
14
+ *
15
+ * <pre>
16
+ * "%dp0%\\node_modules\\@anthropic-ai\\claude-code\\bin\\claude.exe" %*
17
+ * </pre>
18
+ *
19
+ * and, for packages without a native binary, as a script passed to node:
20
+ *
21
+ * <pre>
22
+ * "%_prog%" "%dp0%\\node_modules\\some-cli\\cli.js" %*
23
+ * </pre>
24
+ *
25
+ * Both shapes are handled, and a target that is not on disk is ignored, so a shim this
26
+ * does not understand falls back to being refused rather than producing a wrong command.
27
+ */
28
+ export function parseShimTarget(content, shimDir, nodeExecutable, exists = existsSync) {
29
+ const candidates = [];
30
+ for (const match of content.matchAll(QUOTED_DP0)) {
31
+ const raw = match[1];
32
+ if (!raw)
33
+ continue;
34
+ // win32 semantics explicitly: a shim is a Windows file, and the default path module
35
+ // would read "C:\\..." as a relative path everywhere else — including in these tests
36
+ const substituted = raw.replace(/%dp0%/gi, `${shimDir}\\`).replace(/\\\\+/g, '\\');
37
+ const resolved = windowsPath.isAbsolute(substituted)
38
+ ? windowsPath.normalize(substituted)
39
+ : windowsPath.normalize(windowsPath.join(shimDir, substituted));
40
+ if (RUNNABLE.test(resolved) && exists(resolved))
41
+ candidates.push(resolved);
42
+ }
43
+ if (candidates.length === 0)
44
+ return null;
45
+ // a native binary is run as itself; a script needs the node that is running us, not the
46
+ // bare "node" the shim would have looked up on PATH
47
+ const binary = candidates.find((path) => /\.(exe|com)$/i.test(path));
48
+ if (binary)
49
+ return { command: binary, prefix: [] };
50
+ const script = candidates[0];
51
+ return { command: nodeExecutable, prefix: [script] };
52
+ }
53
+ //# sourceMappingURL=shim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shim.js","sourceRoot":"","sources":["../../src/agents/shim.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,KAAK,IAAI,WAAW,EAAE,MAAM,WAAW,CAAC;AAcjD,MAAM,UAAU,GAAG,uBAAuB,CAAC;AAC3C,MAAM,QAAQ,GAAG,0BAA0B,CAAC;AAE5C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAe,EACf,OAAe,EACf,cAAsB,EACtB,SAAoC,UAAU;IAE9C,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QACjD,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACrB,IAAI,CAAC,GAAG;YAAE,SAAS;QACnB,oFAAoF;QACpF,qFAAqF;QACrF,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACnF,MAAM,QAAQ,GAAG,WAAW,CAAC,UAAU,CAAC,WAAW,CAAC;YAClD,CAAC,CAAC,WAAW,CAAC,SAAS,CAAC,WAAW,CAAC;YACpC,CAAC,CAAC,WAAW,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC;QAClE,IAAI,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC;YAAE,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC7E,CAAC;IACD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEzC,wFAAwF;IACxF,oDAAoD;IACpD,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACrE,IAAI,MAAM;QAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAEnD,MAAM,MAAM,GAAG,UAAU,CAAC,CAAC,CAAE,CAAC;IAC9B,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC;AACvD,CAAC"}
@@ -0,0 +1,69 @@
1
+ /** What a delegated task produced. Every agent kind returns this same shape. */
2
+ export interface AgentResult {
3
+ agent: string;
4
+ succeeded: boolean;
5
+ /** what the agent produced — the answer */
6
+ output: string;
7
+ /** why it failed, empty when it did not */
8
+ diagnostics: string;
9
+ /**
10
+ * Something the caller should know about a call that still succeeded — for instance
11
+ * that a workingDirectory was ignored because this agent cannot read files. Kept apart
12
+ * from diagnostics so "succeeded with a caveat" is not mistaken for a failure.
13
+ */
14
+ notes: string;
15
+ durationMs: number;
16
+ timedOut: boolean;
17
+ outputTruncated: boolean;
18
+ }
19
+ export interface RunOptions {
20
+ /** directory to run in, so the agent can see a project's files */
21
+ cwd?: string;
22
+ /** the caller's own bound; only ever shortens the agent's configured timeout */
23
+ timeoutMs?: number;
24
+ /** told each piece of output as it arrives, so a running task can show progress */
25
+ onOutput?: (chunk: string) => void;
26
+ /** run even though an earlier call hit a durable failure; for an explicit retry */
27
+ force?: boolean;
28
+ }
29
+ /** The configured limit, lowered to the caller's when the caller asked for less. */
30
+ export declare function effectiveTimeout(configured: number | undefined, options: RunOptions): number;
31
+ /**
32
+ * One agent the bridge can hand work to. A CLI and a local model look identical from
33
+ * here, which is what lets a caller treat them interchangeably.
34
+ */
35
+ export interface Agent {
36
+ readonly name: string;
37
+ readonly description: string;
38
+ /** the executable or endpoint that has to exist, reported by list_agents */
39
+ readonly requires: string;
40
+ /**
41
+ * Whether pointing this agent at a directory actually lets it read the files there. A
42
+ * coding CLI can; a model behind an HTTP API cannot, and the caller has to inline the
43
+ * context instead of assuming otherwise.
44
+ */
45
+ readonly readsFiles: boolean;
46
+ /** whether, as configured, it may create or change files in the working directory */
47
+ readonly writesFiles: boolean;
48
+ /** whether, as configured, it may run shell commands such as tests or builds */
49
+ readonly runsCommands: boolean;
50
+ /** the longest one call may run before it is stopped */
51
+ readonly timeoutMs: number;
52
+ /** whether it can actually be used right now */
53
+ probe(): Promise<{
54
+ available: boolean;
55
+ detail: string;
56
+ }>;
57
+ run(prompt: string, options: RunOptions): Promise<AgentResult>;
58
+ }
59
+ export declare function failed(agent: string, diagnostics: string, durationMs?: number, timedOut?: boolean): AgentResult;
60
+ /**
61
+ * Strips terminal escape sequences. A CLI writing colour, spinners or progress bars to
62
+ * stdout would otherwise have all of it land in the calling model's context as if it were
63
+ * part of the answer.
64
+ */
65
+ export declare function stripAnsi(text: string): string;
66
+ export declare function truncate(text: string, maxChars: number): {
67
+ text: string;
68
+ truncated: boolean;
69
+ };
@@ -0,0 +1,28 @@
1
+ /** The configured limit, lowered to the caller's when the caller asked for less. */
2
+ export function effectiveTimeout(configured, options) {
3
+ const base = configured ?? 300_000;
4
+ return options.timeoutMs && options.timeoutMs > 0 ? Math.min(base, options.timeoutMs) : base;
5
+ }
6
+ export function failed(agent, diagnostics, durationMs = 0, timedOut = false) {
7
+ return {
8
+ agent, succeeded: false, output: '', diagnostics, notes: '',
9
+ durationMs, timedOut, outputTruncated: false,
10
+ };
11
+ }
12
+ // eslint-disable-next-line no-control-regex
13
+ const ANSI = /[\u001B\u009B][[\]()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-PR-TZcf-nq-uy=><~]/g;
14
+ /**
15
+ * Strips terminal escape sequences. A CLI writing colour, spinners or progress bars to
16
+ * stdout would otherwise have all of it land in the calling model's context as if it were
17
+ * part of the answer.
18
+ */
19
+ export function stripAnsi(text) {
20
+ return text.replace(ANSI, '');
21
+ }
22
+ export function truncate(text, maxChars) {
23
+ const trimmed = stripAnsi(text).trim();
24
+ if (trimmed.length <= maxChars)
25
+ return { text: trimmed, truncated: false };
26
+ return { text: `${trimmed.slice(0, maxChars)}\n...[truncated]`, truncated: true };
27
+ }
28
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/agents/types.ts"],"names":[],"mappings":"AA8BA,oFAAoF;AACpF,MAAM,UAAU,gBAAgB,CAAC,UAA8B,EAAE,OAAmB;IAClF,MAAM,IAAI,GAAG,UAAU,IAAI,OAAO,CAAC;IACnC,OAAO,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC/F,CAAC;AA4BD,MAAM,UAAU,MAAM,CAAC,KAAa,EAAE,WAAmB,EAAE,UAAU,GAAG,CAAC,EAAE,QAAQ,GAAG,KAAK;IACzF,OAAO;QACL,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE;QAC3D,UAAU,EAAE,QAAQ,EAAE,eAAe,EAAE,KAAK;KAC7C,CAAC;AACJ,CAAC;AAED,4CAA4C;AAC5C,MAAM,IAAI,GAAG,mFAAmF,CAAC;AAEjG;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,QAAgB;IACrD,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;IACvC,IAAI,OAAO,CAAC,MAAM,IAAI,QAAQ;QAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IAC3E,OAAO,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,kBAAkB,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AACpF,CAAC"}
@@ -0,0 +1,99 @@
1
+ export interface CliAgentConfig {
2
+ type?: 'cli';
3
+ description?: string;
4
+ /** argv; needs one element containing {prompt} unless promptVia is "stdin" */
5
+ command: string[];
6
+ /**
7
+ * How the prompt reaches the agent. "argv" is subject to the operating system's
8
+ * argument-size limit, which a prompt carrying file contents passes easily — roughly
9
+ * 64KB on Linux and 32KB on Windows, after which the spawn fails outright. "stdin" has
10
+ * no such limit, and is right for any CLI that reads its prompt from a pipe.
11
+ */
12
+ promptVia?: 'argv' | 'stdin';
13
+ timeoutMs?: number;
14
+ /** default directory, overridden per call */
15
+ cwd?: string;
16
+ /** extra environment for the child, merged over this process's own */
17
+ env?: Record<string, string>;
18
+ /**
19
+ * What this agent may do unattended. Off by default: a client that can call the bridge
20
+ * should not be able to make an agent change files or run commands unless the owner
21
+ * chose that. For the CLIs the bridge knows (claude, codex, grok, gemini) these become
22
+ * that CLI's own flags.
23
+ */
24
+ allow?: {
25
+ edits?: boolean;
26
+ commands?: boolean;
27
+ trustDirectory?: boolean;
28
+ };
29
+ /** for a CLI the bridge does not know: what its configured command lets it do */
30
+ capabilities?: {
31
+ writesFiles?: boolean;
32
+ runsCommands?: boolean;
33
+ };
34
+ }
35
+ export interface OllamaAgentConfig {
36
+ type: 'ollama';
37
+ description?: string;
38
+ /** omit to use the first model Ollama reports as installed */
39
+ model?: string;
40
+ baseUrl?: string;
41
+ timeoutMs?: number;
42
+ }
43
+ /**
44
+ * A model behind an OpenAI-compatible /chat/completions endpoint. Almost every provider
45
+ * that is not Anthropic or Google exposes one, so this single shape reaches xAI (Grok),
46
+ * DeepSeek, Mistral, Groq, Together, OpenRouter, LM Studio, llama.cpp and vLLM — only the
47
+ * base URL, the model name and the key differ.
48
+ */
49
+ export interface OpenAiAgentConfig {
50
+ type: 'openai';
51
+ description?: string;
52
+ /** e.g. https://api.x.ai/v1 — the part before /chat/completions */
53
+ baseUrl: string;
54
+ /**
55
+ * The provider's model id. Left out until you know it: with a key set, probing lists
56
+ * what the endpoint offers, where the provider exposes a listing.
57
+ */
58
+ model?: string;
59
+ /**
60
+ * Name of the environment variable holding the key — not the key itself. Config files
61
+ * get copied into repos and pasted into issues; an environment variable does not.
62
+ */
63
+ apiKeyEnv?: string;
64
+ /** The key inline. Works, but prefer apiKeyEnv: this file is easy to leak. */
65
+ apiKey?: string;
66
+ /** extra headers some providers want, e.g. OpenRouter's attribution headers */
67
+ headers?: Record<string, string>;
68
+ maxOutputTokens?: number;
69
+ timeoutMs?: number;
70
+ }
71
+ export type AgentConfig = CliAgentConfig | OllamaAgentConfig | OpenAiAgentConfig;
72
+ export interface Config {
73
+ maxOutputChars: number;
74
+ /**
75
+ * How many delegations may run at once. Each one is a whole model doing real work, so
76
+ * an unbounded number of them saturates the machine and burns through the quota of
77
+ * whatever account is behind them.
78
+ */
79
+ maxConcurrent: number;
80
+ agents: Record<string, AgentConfig>;
81
+ }
82
+ /**
83
+ * Works with no configuration at all: the CLIs people already have, plus whatever Ollama
84
+ * is serving locally. An entry whose tool is not installed is simply reported as
85
+ * unavailable by list_agents, so shipping all four costs nothing.
86
+ *
87
+ * The flags are each tool's documented non-interactive mode, but they do change between
88
+ * releases — override any entry in the config file if yours differs.
89
+ */
90
+ export declare const DEFAULT_CONFIG: Config;
91
+ export declare function configPath(): string;
92
+ /**
93
+ * Agents from the file replace same-named defaults outright rather than merging field by
94
+ * field, so overriding a command cannot leave half of the old one behind.
95
+ */
96
+ export declare function loadConfig(path?: string): {
97
+ config: Config;
98
+ source: string;
99
+ };
package/dist/config.js ADDED
@@ -0,0 +1,78 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ /**
5
+ * Works with no configuration at all: the CLIs people already have, plus whatever Ollama
6
+ * is serving locally. An entry whose tool is not installed is simply reported as
7
+ * unavailable by list_agents, so shipping all four costs nothing.
8
+ *
9
+ * The flags are each tool's documented non-interactive mode, but they do change between
10
+ * releases — override any entry in the config file if yours differs.
11
+ */
12
+ export const DEFAULT_CONFIG = {
13
+ maxOutputChars: 60_000,
14
+ maxConcurrent: 2,
15
+ agents: {
16
+ claude: {
17
+ description: 'Claude Code — refactoring, tests, reading large codebases',
18
+ // verified: claude -p reads the prompt from stdin, which lifts the argv size limit
19
+ command: ['claude', '-p'],
20
+ promptVia: 'stdin',
21
+ timeoutMs: 300_000,
22
+ },
23
+ codex: {
24
+ description: 'OpenAI Codex CLI — algorithms and single-file problems',
25
+ command: ['codex', 'exec', '{prompt}'],
26
+ timeoutMs: 300_000,
27
+ },
28
+ gemini: {
29
+ description: 'Google Gemini CLI — large context, whole-repo questions',
30
+ command: ['gemini', '-p', '{prompt}'],
31
+ timeoutMs: 300_000,
32
+ },
33
+ antigravity: {
34
+ // Google withdrew Gemini CLI from individual accounts, pointing them at Antigravity
35
+ // instead; its CLI reaches the same models through that login, with no API key
36
+ description: 'Gemini models through the Antigravity CLI',
37
+ command: ['agy', '-p', '{prompt}'],
38
+ timeoutMs: 300_000,
39
+ },
40
+ ollama: {
41
+ type: 'ollama',
42
+ description: 'A model running locally in Ollama — no account, no network, always awake',
43
+ timeoutMs: 300_000,
44
+ },
45
+ },
46
+ };
47
+ export function configPath() {
48
+ return process.env.MCPBRIDGE_CONFIG ?? join(homedir(), '.config', 'mcpbridge', 'config.json');
49
+ }
50
+ /**
51
+ * Agents from the file replace same-named defaults outright rather than merging field by
52
+ * field, so overriding a command cannot leave half of the old one behind.
53
+ */
54
+ export function loadConfig(path = configPath()) {
55
+ let raw;
56
+ try {
57
+ raw = readFileSync(path, 'utf8');
58
+ }
59
+ catch {
60
+ return { config: DEFAULT_CONFIG, source: 'defaults (no config file)' };
61
+ }
62
+ let parsed;
63
+ try {
64
+ parsed = JSON.parse(raw);
65
+ }
66
+ catch (error) {
67
+ throw new Error(`${path} is not valid JSON: ${error.message}`);
68
+ }
69
+ return {
70
+ config: {
71
+ maxOutputChars: parsed.maxOutputChars ?? DEFAULT_CONFIG.maxOutputChars,
72
+ maxConcurrent: Math.max(1, parsed.maxConcurrent ?? DEFAULT_CONFIG.maxConcurrent),
73
+ agents: { ...DEFAULT_CONFIG.agents, ...(parsed.agents ?? {}) },
74
+ },
75
+ source: path,
76
+ };
77
+ }
78
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAiFjC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,cAAc,GAAW;IACpC,cAAc,EAAE,MAAM;IACtB,aAAa,EAAE,CAAC;IAChB,MAAM,EAAE;QACN,MAAM,EAAE;YACN,WAAW,EAAE,2DAA2D;YACxE,mFAAmF;YACnF,OAAO,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC;YACzB,SAAS,EAAE,OAAO;YAClB,SAAS,EAAE,OAAO;SACnB;QACD,KAAK,EAAE;YACL,WAAW,EAAE,wDAAwD;YACrE,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC;YACtC,SAAS,EAAE,OAAO;SACnB;QACD,MAAM,EAAE;YACN,WAAW,EAAE,yDAAyD;YACtE,OAAO,EAAE,CAAC,QAAQ,EAAE,IAAI,EAAE,UAAU,CAAC;YACrC,SAAS,EAAE,OAAO;SACnB;QACD,WAAW,EAAE;YACX,oFAAoF;YACpF,+EAA+E;YAC/E,WAAW,EAAE,2CAA2C;YACxD,OAAO,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,UAAU,CAAC;YAClC,SAAS,EAAE,OAAO;SACnB;QACD,MAAM,EAAE;YACN,IAAI,EAAE,QAAQ;YACd,WAAW,EAAE,0EAA0E;YACvF,SAAS,EAAE,OAAO;SACnB;KACF;CACF,CAAC;AAEF,MAAM,UAAU,UAAU;IACxB,OAAO,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,CAAC,CAAC;AAChG,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,IAAI,GAAG,UAAU,EAAE;IAC5C,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,2BAA2B,EAAE,CAAC;IACzE,CAAC;IAED,IAAI,MAAuB,CAAC;IAC5B,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAoB,CAAC;IAC9C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,uBAAwB,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;IAC5E,CAAC;IAED,OAAO;QACL,MAAM,EAAE;YACN,cAAc,EAAE,MAAM,CAAC,cAAc,IAAI,cAAc,CAAC,cAAc;YACtE,aAAa,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,aAAa,IAAI,cAAc,CAAC,aAAa,CAAC;YAChF,MAAM,EAAE,EAAE,GAAG,cAAc,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE;SAC/D;QACD,MAAM,EAAE,IAAI;KACb,CAAC;AACJ,CAAC"}
@@ -0,0 +1,35 @@
1
+ import { type ToolContext } from './tools.js';
2
+ export interface ConnectOptions {
3
+ /** the relay, as http(s):// or ws(s)://; a bare origin gets /agent appended */
4
+ relayUrl: string;
5
+ /** the agent token from the dashboard — identifies which account this machine serves */
6
+ token: string;
7
+ device?: string;
8
+ agentVersion: string;
9
+ context: ToolContext;
10
+ log?: (line: string) => void;
11
+ /** stop instead of reconnecting once the socket closes; for tests */
12
+ once?: boolean;
13
+ signal?: AbortSignal;
14
+ /** how often to push a fresh agent list while connected */
15
+ refreshMs?: number;
16
+ /** told when the relay welcomes this machine and when the session ends */
17
+ onState?: (state: {
18
+ connected: true;
19
+ device: string;
20
+ } | {
21
+ connected: false;
22
+ reason: string;
23
+ }) => void;
24
+ }
25
+ /** http -> ws, https -> wss; a URL with no path gets the relay's agent endpoint. */
26
+ export declare function toWebSocketUrl(relayUrl: string): string;
27
+ /**
28
+ * Keeps this machine connected to a relay and answers the calls it forwards.
29
+ *
30
+ * <p>The connection is outbound, so nothing here listens on a port and no firewall rule is
31
+ * needed. Each forwarded call runs the same executeTool as the local MCP server, so a
32
+ * delegation behaves identically whether it arrived from a client on this machine or from
33
+ * one on the other side of the relay.
34
+ */
35
+ export declare function connectToRelay(options: ConnectOptions): Promise<void>;