@backburner/cli 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 (202) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +329 -0
  3. package/dist/src/agents/antigravity-launch.js +162 -0
  4. package/dist/src/agents/antigravity.js +251 -0
  5. package/dist/src/agents/claude-launch.js +117 -0
  6. package/dist/src/agents/claude.js +271 -0
  7. package/dist/src/agents/codex-launch.js +82 -0
  8. package/dist/src/agents/codex.js +261 -0
  9. package/dist/src/agents/composite-runner.js +13 -0
  10. package/dist/src/agents/extraction.js +442 -0
  11. package/dist/src/agents/failure-feedback.js +132 -0
  12. package/dist/src/agents/gemini-launch.js +96 -0
  13. package/dist/src/agents/gemini.js +392 -0
  14. package/dist/src/agents/job-agent-assignments.js +180 -0
  15. package/dist/src/agents/lifecycle-feedback.js +147 -0
  16. package/dist/src/agents/limit-detector.js +83 -0
  17. package/dist/src/agents/opencode-launch.js +138 -0
  18. package/dist/src/agents/opencode.js +156 -0
  19. package/dist/src/agents/plan-breakdown-feedback.js +97 -0
  20. package/dist/src/agents/pool.js +168 -0
  21. package/dist/src/agents/prompt.js +462 -0
  22. package/dist/src/agents/provider-sessions.js +105 -0
  23. package/dist/src/agents/review-feedback.js +255 -0
  24. package/dist/src/agents/selection.js +113 -0
  25. package/dist/src/agents/service.js +1087 -0
  26. package/dist/src/agents/types.js +1 -0
  27. package/dist/src/agents/wave-assessment-feedback.js +69 -0
  28. package/dist/src/attention/derive.js +337 -0
  29. package/dist/src/capabilities/index.js +2 -0
  30. package/dist/src/capabilities/projection.js +183 -0
  31. package/dist/src/capabilities/types.js +1 -0
  32. package/dist/src/capabilities/validator.js +87 -0
  33. package/dist/src/cli/commands/broker-smoke.js +96 -0
  34. package/dist/src/cli/commands/broker.js +56 -0
  35. package/dist/src/cli/commands/init.js +4 -0
  36. package/dist/src/cli/commands/journal.js +43 -0
  37. package/dist/src/cli/commands/run-loop.js +56 -0
  38. package/dist/src/cli/commands/run.js +1241 -0
  39. package/dist/src/cli/commands/tui.js +187 -0
  40. package/dist/src/cli/dispatcher.js +51 -0
  41. package/dist/src/cli/errors.js +2 -0
  42. package/dist/src/cli/init.js +92 -0
  43. package/dist/src/cli/options.js +86 -0
  44. package/dist/src/cli/output.js +231 -0
  45. package/dist/src/cli/run-journal.js +342 -0
  46. package/dist/src/cli/run-types.js +1 -0
  47. package/dist/src/cli/run.js +32 -0
  48. package/dist/src/cli/runtime/dispatch-lifecycle.js +772 -0
  49. package/dist/src/cli/runtime/provider-runtime-types.js +1 -0
  50. package/dist/src/cli/runtime/provider-runtime.js +152 -0
  51. package/dist/src/cli/runtime/provider-tools.js +73 -0
  52. package/dist/src/cli/runtime/providers/antigravity.js +26 -0
  53. package/dist/src/cli/runtime/providers/claude.js +20 -0
  54. package/dist/src/cli/runtime/providers/codex.js +20 -0
  55. package/dist/src/cli/runtime/providers/gemini.js +20 -0
  56. package/dist/src/cli/runtime/providers/opencode.js +20 -0
  57. package/dist/src/cli/runtime/run-context.js +131 -0
  58. package/dist/src/cli/tui/index.js +3 -0
  59. package/dist/src/cli/tui/screen.js +388 -0
  60. package/dist/src/cli/tui/tracker.js +558 -0
  61. package/dist/src/cli/tui/types.js +1 -0
  62. package/dist/src/cli/tui/views.js +1006 -0
  63. package/dist/src/config/loader.js +136 -0
  64. package/dist/src/config/schemas.js +403 -0
  65. package/dist/src/config/types.js +1 -0
  66. package/dist/src/context/types.js +331 -0
  67. package/dist/src/domain/entities.js +1 -0
  68. package/dist/src/git/backburner-git-tool-service.js +680 -0
  69. package/dist/src/git/gitops-lifecycle.js +56 -0
  70. package/dist/src/git/merge-prep-workspace.js +357 -0
  71. package/dist/src/git/types.js +484 -0
  72. package/dist/src/github/broker-model-content.js +215 -0
  73. package/dist/src/github/broker-server.js +237 -0
  74. package/dist/src/github/broker.js +638 -0
  75. package/dist/src/github/composed.js +53 -0
  76. package/dist/src/github/gateway.js +478 -0
  77. package/dist/src/github/normalize.js +222 -0
  78. package/dist/src/github/pr-classification.js +73 -0
  79. package/dist/src/github/scoped-broker.js +35 -0
  80. package/dist/src/github/security.js +126 -0
  81. package/dist/src/github/service.js +332 -0
  82. package/dist/src/github/types.js +1 -0
  83. package/dist/src/github/utils.js +16 -0
  84. package/dist/src/journal/index.js +4 -0
  85. package/dist/src/journal/reader.js +62 -0
  86. package/dist/src/journal/renderer.js +50 -0
  87. package/dist/src/journal/service.js +72 -0
  88. package/dist/src/journal/types.js +1 -0
  89. package/dist/src/journal/writer.js +19 -0
  90. package/dist/src/mcps/bridge.js +166 -0
  91. package/dist/src/mcps/index.js +5 -0
  92. package/dist/src/mcps/reconcile.js +113 -0
  93. package/dist/src/mcps/smithery-client.js +186 -0
  94. package/dist/src/mcps/smithery.js +164 -0
  95. package/dist/src/mcps/types.js +1 -0
  96. package/dist/src/memory/json-store.js +71 -0
  97. package/dist/src/memory/prompt.js +16 -0
  98. package/dist/src/memory/scope.js +31 -0
  99. package/dist/src/memory/store.js +1 -0
  100. package/dist/src/memory/tool-service.js +142 -0
  101. package/dist/src/memory/types.js +12 -0
  102. package/dist/src/onboarding/config-writer.js +96 -0
  103. package/dist/src/onboarding/engine.js +86 -0
  104. package/dist/src/onboarding/prompt.js +102 -0
  105. package/dist/src/onboarding/services/provider-discovery.js +58 -0
  106. package/dist/src/onboarding/services/repo-discovery.js +246 -0
  107. package/dist/src/onboarding/steps/check-github-auth.js +32 -0
  108. package/dist/src/onboarding/steps/check-requirements.js +34 -0
  109. package/dist/src/onboarding/steps/configure-models.js +105 -0
  110. package/dist/src/onboarding/steps/configure-paths.js +33 -0
  111. package/dist/src/onboarding/steps/discover-providers.js +38 -0
  112. package/dist/src/onboarding/steps/discover-repos.js +39 -0
  113. package/dist/src/onboarding/steps/generate-config.js +106 -0
  114. package/dist/src/onboarding/steps/readiness-validation.js +158 -0
  115. package/dist/src/onboarding/steps/select-repos.js +76 -0
  116. package/dist/src/onboarding/steps/show-summary.js +97 -0
  117. package/dist/src/onboarding/steps/welcome.js +27 -0
  118. package/dist/src/onboarding/types.js +1 -0
  119. package/dist/src/onboarding/validators/git-access.js +63 -0
  120. package/dist/src/onboarding/validators/github-auth.js +85 -0
  121. package/dist/src/onboarding/validators/tool-checker.js +153 -0
  122. package/dist/src/onboarding/validators/workspace.js +83 -0
  123. package/dist/src/prompts/resolver.js +106 -0
  124. package/dist/src/retrospectives/candidates.js +224 -0
  125. package/dist/src/retrospectives/derive.js +169 -0
  126. package/dist/src/retrospectives/ingest.js +321 -0
  127. package/dist/src/retrospectives/management-prs.js +380 -0
  128. package/dist/src/retrospectives/proposals.js +199 -0
  129. package/dist/src/retrospectives/scopes.js +272 -0
  130. package/dist/src/retrospectives/scoring.js +171 -0
  131. package/dist/src/retrospectives/types.js +1 -0
  132. package/dist/src/state/loader.js +473 -0
  133. package/dist/src/state/types.js +15 -0
  134. package/dist/src/tasks/derivation/builders.js +70 -0
  135. package/dist/src/tasks/derivation/handlers/comment-response.js +169 -0
  136. package/dist/src/tasks/derivation/handlers/implement-plan.js +103 -0
  137. package/dist/src/tasks/derivation/handlers/index.js +54 -0
  138. package/dist/src/tasks/derivation/handlers/packet-pr-control.js +92 -0
  139. package/dist/src/tasks/derivation/handlers/packet-worktree.js +29 -0
  140. package/dist/src/tasks/derivation/handlers/plan-breakdown.js +45 -0
  141. package/dist/src/tasks/derivation/handlers/prepare-for-merge.js +91 -0
  142. package/dist/src/tasks/derivation/handlers/product-discovery.js +36 -0
  143. package/dist/src/tasks/derivation/handlers/retry-failed-task.js +125 -0
  144. package/dist/src/tasks/derivation/handlers/review-pr.js +28 -0
  145. package/dist/src/tasks/derivation/handlers/shared.js +24 -0
  146. package/dist/src/tasks/derivation/handlers/sync-parent-branch.js +100 -0
  147. package/dist/src/tasks/derivation/handlers/wave-assessment.js +223 -0
  148. package/dist/src/tasks/derivation/handlers/write-plan.js +101 -0
  149. package/dist/src/tasks/derivation/handlers/write-product-spec.js +49 -0
  150. package/dist/src/tasks/derivation/runner.js +18 -0
  151. package/dist/src/tasks/derivation/types.js +1 -0
  152. package/dist/src/tasks/derive.js +810 -0
  153. package/dist/src/tasks/mcp-skip.js +10 -0
  154. package/dist/src/tasks/retry.js +10 -0
  155. package/dist/src/tasks/task.js +184 -0
  156. package/dist/src/tasks/types.js +1 -0
  157. package/dist/src/utils/command-runner.js +192 -0
  158. package/dist/src/utils/json.js +38 -0
  159. package/dist/src/utils/logger.js +43 -0
  160. package/dist/src/utils/paths.js +38 -0
  161. package/dist/src/utils/slug.js +10 -0
  162. package/dist/src/workflows/registry.js +174 -0
  163. package/dist/src/workflows/triage.js +90 -0
  164. package/dist/src/workflows/workstream-machine/derive-events.js +121 -0
  165. package/dist/src/workflows/workstream-machine/evaluate.js +24 -0
  166. package/dist/src/workflows/workstream-machine/events.js +1 -0
  167. package/dist/src/workflows/workstream-machine/machine.js +355 -0
  168. package/dist/src/workflows/workstream-machine/selectors.js +19 -0
  169. package/dist/src/workflows/workstream-machine/types.js +1 -0
  170. package/dist/src/workstreams/branch-sync.js +87 -0
  171. package/dist/src/workstreams/derive.js +629 -0
  172. package/dist/src/workstreams/mcp-blockers.js +420 -0
  173. package/dist/src/workstreams/packet-candidates.js +42 -0
  174. package/dist/src/workstreams/packet-lifecycle-feedback.js +60 -0
  175. package/dist/src/workstreams/packet-lifecycle-report.js +199 -0
  176. package/dist/src/workstreams/packet-plan.js +597 -0
  177. package/dist/src/workstreams/packet-projections.js +190 -0
  178. package/dist/src/workstreams/parent-branch-sync-action.js +312 -0
  179. package/dist/src/workstreams/parent-branch-sync.js +148 -0
  180. package/dist/src/workstreams/phases.js +4 -0
  181. package/dist/src/workstreams/plan-breakdown-generator/export.js +19 -0
  182. package/dist/src/workstreams/plan-breakdown-generator/index.js +6 -0
  183. package/dist/src/workstreams/plan-breakdown-generator/inspect.js +50 -0
  184. package/dist/src/workstreams/plan-breakdown-generator/patch.js +139 -0
  185. package/dist/src/workstreams/plan-breakdown-generator/session.js +19 -0
  186. package/dist/src/workstreams/plan-breakdown-generator/store.js +27 -0
  187. package/dist/src/workstreams/plan-breakdown-generator/tools.js +407 -0
  188. package/dist/src/workstreams/plan-breakdown-generator/types.js +6 -0
  189. package/dist/src/workstreams/plan-breakdown-generator/validate.js +212 -0
  190. package/dist/src/workstreams/plan-breakdown-schema.js +175 -0
  191. package/dist/src/workstreams/utils.js +15 -0
  192. package/dist/src/workstreams/wave-assessment/checkpoint.js +172 -0
  193. package/dist/src/workstreams/wave-assessment/index.js +3 -0
  194. package/dist/src/workstreams/wave-assessment/session.js +84 -0
  195. package/dist/src/workstreams/wave-assessment/store.js +51 -0
  196. package/dist/src/workstreams/wave-assessment/tools.js +419 -0
  197. package/dist/src/workstreams/wave-assessment/types.js +1 -0
  198. package/dist/src/workstreams/wave-assessment/validate.js +78 -0
  199. package/dist/src/worktrees/service.js +839 -0
  200. package/dist/src/worktrees/workspace-ownership.js +34 -0
  201. package/dist/src/worktrees/workspace-recovery.js +177 -0
  202. package/package.json +61 -0
@@ -0,0 +1,50 @@
1
+ const SEVERITY_PREFIX = {
2
+ info: " ",
3
+ warning: "! ",
4
+ error: "✗ "
5
+ };
6
+ export function renderJournalEntries(entries, opts = {}) {
7
+ if (opts.json) {
8
+ return JSON.stringify(entries, null, 2) + "\n";
9
+ }
10
+ if (entries.length === 0) {
11
+ return "No journal entries found.\n";
12
+ }
13
+ return (entries
14
+ .map((e) => {
15
+ const prefix = SEVERITY_PREFIX[e.severity] ?? " ";
16
+ const ts = e.timestamp.replace("T", " ").replace(/\.\d+Z$/, "Z");
17
+ const scope = e.repoId ? `[${e.repoId}]` : `[${e.scope}:${e.scopeId.slice(0, 8)}]`;
18
+ const base = `${ts} ${prefix}${scope} ${e.summary}`;
19
+ if (!opts.verbose) {
20
+ return base;
21
+ }
22
+ const lines = [base];
23
+ lines.push(` category: ${e.category}`);
24
+ if (e.cycleId)
25
+ lines.push(` cycle: ${e.cycleId}`);
26
+ if (e.workstreamId)
27
+ lines.push(` ws: ${e.workstreamId}`);
28
+ if (e.executionId)
29
+ lines.push(` exec: ${e.executionId}`);
30
+ if (e.noActionReason)
31
+ lines.push(` reason: ${e.noActionReason}`);
32
+ if (e.expectedNextAction)
33
+ lines.push(` next: ${e.expectedNextAction}`);
34
+ if (e.stateTransition) {
35
+ lines.push(` from: ${e.stateTransition.from}`);
36
+ lines.push(` to: ${e.stateTransition.to}`);
37
+ if (e.stateTransition.trigger) {
38
+ lines.push(` trigger: ${e.stateTransition.trigger}`);
39
+ }
40
+ }
41
+ if (e.observedState) {
42
+ lines.push(` observed: ${JSON.stringify(e.observedState)}`);
43
+ }
44
+ if (e.derivedState) {
45
+ lines.push(` derived: ${JSON.stringify(e.derivedState)}`);
46
+ }
47
+ return lines.join("\n");
48
+ })
49
+ .join("\n") + "\n");
50
+ }
@@ -0,0 +1,72 @@
1
+ import { JournalWriter } from "./writer.js";
2
+ import { JournalReader } from "./reader.js";
3
+ export class JournalService {
4
+ writer;
5
+ reader;
6
+ constructor(journalDir) {
7
+ this.writer = new JournalWriter(journalDir);
8
+ this.reader = new JournalReader(journalDir);
9
+ }
10
+ async write(entry) {
11
+ return this.writer.write(entry);
12
+ }
13
+ async query(q = {}) {
14
+ return this.reader.query(q);
15
+ }
16
+ async recordNoAction(opts) {
17
+ return this.write({
18
+ ...(opts.cycleId !== undefined ? { cycleId: opts.cycleId } : {}),
19
+ scope: opts.scope,
20
+ scopeId: opts.scopeId,
21
+ ...(opts.repoId !== undefined ? { repoId: opts.repoId } : {}),
22
+ ...(opts.workstreamId !== undefined ? { workstreamId: opts.workstreamId } : {}),
23
+ ...(opts.issueNumber !== undefined ? { issueNumber: opts.issueNumber } : {}),
24
+ ...(opts.prNumber !== undefined ? { prNumber: opts.prNumber } : {}),
25
+ category: "no_action",
26
+ severity: "info",
27
+ summary: opts.reason,
28
+ noActionReason: opts.reason,
29
+ ...(opts.observedState !== undefined ? { observedState: opts.observedState } : {}),
30
+ ...(opts.derivedState !== undefined ? { derivedState: opts.derivedState } : {})
31
+ });
32
+ }
33
+ async recordStateTransition(opts) {
34
+ return this.write({
35
+ ...(opts.cycleId !== undefined ? { cycleId: opts.cycleId } : {}),
36
+ scope: opts.scope,
37
+ scopeId: opts.scopeId,
38
+ ...(opts.repoId !== undefined ? { repoId: opts.repoId } : {}),
39
+ ...(opts.workstreamId !== undefined ? { workstreamId: opts.workstreamId } : {}),
40
+ ...(opts.issueNumber !== undefined ? { issueNumber: opts.issueNumber } : {}),
41
+ ...(opts.prNumber !== undefined ? { prNumber: opts.prNumber } : {}),
42
+ category: "state_transition",
43
+ severity: "info",
44
+ summary: opts.summary,
45
+ stateTransition: {
46
+ from: opts.from,
47
+ to: opts.to,
48
+ ...(opts.trigger !== undefined ? { trigger: opts.trigger } : {})
49
+ },
50
+ ...(opts.observedState !== undefined ? { observedState: opts.observedState } : {}),
51
+ ...(opts.derivedState !== undefined ? { derivedState: opts.derivedState } : {}),
52
+ ...(opts.expectedNextAction !== undefined ? { expectedNextAction: opts.expectedNextAction } : {})
53
+ });
54
+ }
55
+ async recordBlocked(opts) {
56
+ return this.write({
57
+ ...(opts.cycleId !== undefined ? { cycleId: opts.cycleId } : {}),
58
+ scope: opts.scope,
59
+ scopeId: opts.scopeId,
60
+ ...(opts.repoId !== undefined ? { repoId: opts.repoId } : {}),
61
+ ...(opts.workstreamId !== undefined ? { workstreamId: opts.workstreamId } : {}),
62
+ ...(opts.issueNumber !== undefined ? { issueNumber: opts.issueNumber } : {}),
63
+ ...(opts.prNumber !== undefined ? { prNumber: opts.prNumber } : {}),
64
+ category: "blocked",
65
+ severity: "warning",
66
+ summary: opts.reason,
67
+ noActionReason: opts.reason,
68
+ ...(opts.observedState !== undefined ? { observedState: opts.observedState } : {}),
69
+ ...(opts.derivedState !== undefined ? { derivedState: opts.derivedState } : {})
70
+ });
71
+ }
72
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import { appendFile, mkdir } from "node:fs/promises";
2
+ import { randomUUID } from "node:crypto";
3
+ import * as path from "node:path";
4
+ export class JournalWriter {
5
+ journalPath;
6
+ constructor(journalDir) {
7
+ this.journalPath = path.join(journalDir, "journal.jsonl");
8
+ }
9
+ async write(entry) {
10
+ const full = {
11
+ id: randomUUID(),
12
+ timestamp: new Date().toISOString(),
13
+ ...entry
14
+ };
15
+ await mkdir(path.dirname(this.journalPath), { recursive: true });
16
+ await appendFile(this.journalPath, JSON.stringify(full) + "\n", "utf8");
17
+ return full;
18
+ }
19
+ }
@@ -0,0 +1,166 @@
1
+ import Smithery from "@smithery/api";
2
+ export class SmitheryConnectionToolsClient {
3
+ input;
4
+ constructor(input) {
5
+ this.input = input;
6
+ }
7
+ async listTools() {
8
+ const response = await this.input.client.connections.tools.list(this.input.connectionId, { namespace: this.input.namespace });
9
+ return response.tools.map((tool) => toBrokerTool(tool));
10
+ }
11
+ async getTool(name) {
12
+ try {
13
+ return toBrokerTool(await this.input.client.connections.tools.get(name, {
14
+ namespace: this.input.namespace,
15
+ connectionId: this.input.connectionId
16
+ }));
17
+ }
18
+ catch {
19
+ return undefined;
20
+ }
21
+ }
22
+ async callTool(name, rawArguments) {
23
+ try {
24
+ const body = normalizeToolArguments(rawArguments);
25
+ const result = await this.input.client.connections.tools.call(name, {
26
+ namespace: this.input.namespace,
27
+ connectionId: this.input.connectionId,
28
+ body
29
+ });
30
+ return {
31
+ ok: true,
32
+ tool: name,
33
+ result,
34
+ summary: `Smithery MCP tool "${name}" succeeded.`
35
+ };
36
+ }
37
+ catch (err) {
38
+ return {
39
+ ok: false,
40
+ tool: name,
41
+ error: {
42
+ code: "smithery_tool_call_failed",
43
+ message: err instanceof Error ? err.message : String(err)
44
+ }
45
+ };
46
+ }
47
+ }
48
+ }
49
+ function toBrokerTool(tool) {
50
+ return {
51
+ name: tool.name,
52
+ title: tool.title ?? tool.name,
53
+ description: tool.description ?? `Smithery MCP tool ${tool.name}`,
54
+ inputSchema: tool.inputSchema ?? { type: "object", properties: {} }
55
+ };
56
+ }
57
+ export class ReadyMcpBridgeService {
58
+ handle;
59
+ client;
60
+ namespace;
61
+ cachedTools;
62
+ constructor(handle, client, allowedPublicToolNames) {
63
+ this.handle = handle;
64
+ this.client = client;
65
+ this.namespace = handle;
66
+ this.allowedLocalToolNames = allowedPublicToolNames
67
+ ? new Set(allowedPublicToolNames
68
+ .filter((name) => name.startsWith(`${handle}.`))
69
+ .filter((name) => !name.endsWith(".*"))
70
+ .map((name) => name.slice(handle.length + 1)))
71
+ : undefined;
72
+ }
73
+ allowedLocalToolNames;
74
+ listTools() {
75
+ return (this.cachedTools ?? [])
76
+ .filter((tool) => this.isToolAllowed(tool.name))
77
+ .map((tool) => ({
78
+ ...tool,
79
+ name: tool.name
80
+ }))
81
+ .sort((left, right) => left.name.localeCompare(right.name));
82
+ }
83
+ async initialize() {
84
+ try {
85
+ this.cachedTools = await this.client.listTools();
86
+ }
87
+ catch (err) {
88
+ if (this.allowedLocalToolNames === undefined || this.client.getTool === undefined) {
89
+ throw err;
90
+ }
91
+ const tools = [];
92
+ for (const toolName of [...this.allowedLocalToolNames].sort((left, right) => left.localeCompare(right))) {
93
+ const tool = await this.client.getTool(toolName);
94
+ if (tool) {
95
+ tools.push(tool);
96
+ }
97
+ }
98
+ if (tools.length === 0) {
99
+ throw err;
100
+ }
101
+ this.cachedTools = tools;
102
+ }
103
+ }
104
+ async callTool(name, rawArguments) {
105
+ if (!this.isToolAllowed(name)) {
106
+ return {
107
+ ok: false,
108
+ tool: name,
109
+ error: {
110
+ code: "tool_not_found",
111
+ message: `MCP bridge tool "${this.handle}.${name}" is not enabled for this run`
112
+ }
113
+ };
114
+ }
115
+ return await this.client.callTool(name, rawArguments);
116
+ }
117
+ isToolAllowed(localToolName) {
118
+ return (this.allowedLocalToolNames === undefined ||
119
+ this.allowedLocalToolNames.has(localToolName));
120
+ }
121
+ }
122
+ export function createReadyMcpBridgeService(input) {
123
+ if (input.status.status !== "ready") {
124
+ return undefined;
125
+ }
126
+ return new ReadyMcpBridgeService(input.status.handle, input.client, input.allowedPublicToolNames);
127
+ }
128
+ export function createReadyMcpBridgeServices(inputs) {
129
+ return [...inputs]
130
+ .sort((left, right) => left.status.handle.localeCompare(right.status.handle))
131
+ .map((input) => createReadyMcpBridgeService(input))
132
+ .filter((service) => service !== undefined);
133
+ }
134
+ export async function createSmitheryMcpBridgeServicesFromStatuses(statuses, runtimeConfig, client, allowedPublicToolNames) {
135
+ if (!runtimeConfig.apiKey && !client) {
136
+ return [];
137
+ }
138
+ const sdkClient = client ?? new Smithery({ apiKey: runtimeConfig.apiKey });
139
+ const services = [];
140
+ for (const status of [...statuses].sort((left, right) => left.handle.localeCompare(right.handle))) {
141
+ if (status.status !== "ready") {
142
+ continue;
143
+ }
144
+ const namespace = status.connection?.namespace;
145
+ const connectionId = status.connection?.providerConnectionId;
146
+ if (!namespace || !connectionId) {
147
+ continue;
148
+ }
149
+ const service = new ReadyMcpBridgeService(status.handle, new SmitheryConnectionToolsClient({ namespace, connectionId, client: sdkClient }), allowedPublicToolNames);
150
+ await service.initialize();
151
+ services.push(service);
152
+ }
153
+ return services;
154
+ }
155
+ export function projectReadyMcpBridge(service, status) {
156
+ return {
157
+ handle: status.handle,
158
+ tools: service.listTools().map((tool) => tool.name)
159
+ };
160
+ }
161
+ function normalizeToolArguments(rawArguments) {
162
+ if (rawArguments && typeof rawArguments === "object" && !Array.isArray(rawArguments)) {
163
+ return rawArguments;
164
+ }
165
+ return {};
166
+ }
@@ -0,0 +1,5 @@
1
+ export { SmitheryMcpResolver } from "./smithery.js";
2
+ export { createSmitheryMcpResolverFromEnv } from "./smithery.js";
3
+ export { SmitherySdkClient } from "./smithery-client.js";
4
+ export { ReadyMcpBridgeService, createReadyMcpBridgeService, createReadyMcpBridgeServices, createSmitheryMcpBridgeServicesFromStatuses, SmitheryConnectionToolsClient, projectReadyMcpBridge } from "./bridge.js";
5
+ export { reconcileMcpStatus } from "./reconcile.js";
@@ -0,0 +1,113 @@
1
+ export function reconcileMcpStatus(dependencies, previous, resolverResults, now) {
2
+ const previousByHandle = new Map(previous.map((r) => [r.handle, r]));
3
+ const currentHandles = new Set(dependencies.map((d) => d.handle));
4
+ const resultByHandle = new Map(resolverResults.map((r) => [r.handle, r]));
5
+ const added = [];
6
+ const updated = [];
7
+ const unchanged = [];
8
+ const removed = [];
9
+ // Drop records for handles no longer in dependencies.
10
+ for (const prev of previous) {
11
+ if (!currentHandles.has(prev.handle)) {
12
+ removed.push(prev.handle);
13
+ }
14
+ }
15
+ const records = [];
16
+ for (const dep of dependencies) {
17
+ const resolved = resultByHandle.get(dep.handle);
18
+ const prev = previousByHandle.get(dep.handle);
19
+ if (!resolved) {
20
+ // No resolver result for this handle — carry forward previous if any.
21
+ if (prev) {
22
+ records.push(prev);
23
+ unchanged.push(dep.handle);
24
+ }
25
+ continue;
26
+ }
27
+ const next = resolvedMcpToStatusRecord(resolved, now);
28
+ if (!prev) {
29
+ added.push(dep.handle);
30
+ }
31
+ else if (prev.status === next.status &&
32
+ prev.actionRequired?.reason === next.actionRequired?.reason &&
33
+ prev.connection?.providerConnectionId === next.connection?.providerConnectionId &&
34
+ prev.connection?.readinessProof === next.connection?.readinessProof) {
35
+ // Status, action_required reason, and connection identity/proof are unchanged — still update lastCheckedAt and message.
36
+ unchanged.push(dep.handle);
37
+ records.push({ ...next });
38
+ continue;
39
+ }
40
+ else {
41
+ updated.push(dep.handle);
42
+ }
43
+ records.push(next);
44
+ }
45
+ // Sort deterministically by handle.
46
+ records.sort((a, b) => a.handle.localeCompare(b.handle));
47
+ return { records, added, updated, unchanged, removed };
48
+ }
49
+ function resolvedMcpToStatusRecord(resolved, now) {
50
+ const record = {
51
+ handle: resolved.handle,
52
+ providerId: resolved.provider,
53
+ providerMcpId: resolved.providerMcpId,
54
+ status: resolved.status,
55
+ lastCheckedAt: now,
56
+ };
57
+ if (resolved.message) {
58
+ record.message = resolved.message;
59
+ }
60
+ if (resolved.actionRequired) {
61
+ // Only persist the reason, user-visible message, safe fields list.
62
+ // Do NOT persist URLs unless they are explicitly safe (non-secret, non-short-lived).
63
+ // For V1, omit URL persistence to stay conservative.
64
+ record.actionRequired = {
65
+ reason: resolved.actionRequired.reason,
66
+ ...(resolved.actionRequired.message
67
+ ? { message: resolved.actionRequired.message }
68
+ : {}),
69
+ ...(resolved.actionRequired.fields?.length
70
+ ? { fields: [...resolved.actionRequired.fields] }
71
+ : {}),
72
+ };
73
+ }
74
+ if (resolved.connection) {
75
+ // Only persist non-secret connection metadata.
76
+ // mcpUrl is intentionally omitted: Smithery bundleUrl/deploymentUrl may embed
77
+ // short-lived auth tokens. transport (e.g. "http", "streamable-http") is safe.
78
+ record.connection = {
79
+ ...(resolved.connection.localConnectionIdentity
80
+ ? { localConnectionIdentity: resolved.connection.localConnectionIdentity }
81
+ : {}),
82
+ ...(resolved.connection.providerConnectionId
83
+ ? { providerConnectionId: resolved.connection.providerConnectionId }
84
+ : {}),
85
+ ...(resolved.connection.namespace
86
+ ? { namespace: resolved.connection.namespace }
87
+ : {}),
88
+ ...(resolved.connection.transport
89
+ ? { transport: resolved.connection.transport }
90
+ : {}),
91
+ ...(resolved.status === "ready" && resolved.connection.readiness === "connected"
92
+ ? { readinessProof: "connected", readinessCheckedAt: now }
93
+ : {}),
94
+ };
95
+ }
96
+ if (resolved.discovered) {
97
+ // Store discovery summary (counts) rather than full tool lists for now.
98
+ // Full projection is out of scope for this issue.
99
+ record.discovered = {
100
+ ...(resolved.discovered.name ? { name: resolved.discovered.name } : {}),
101
+ ...(resolved.discovered.tools?.length !== undefined
102
+ ? { toolCount: resolved.discovered.tools?.length ?? 0 }
103
+ : {}),
104
+ ...(resolved.discovered.resources?.length !== undefined
105
+ ? { resourceCount: resolved.discovered.resources?.length ?? 0 }
106
+ : {}),
107
+ ...(resolved.discovered.prompts?.length !== undefined
108
+ ? { promptCount: resolved.discovered.prompts?.length ?? 0 }
109
+ : {}),
110
+ };
111
+ }
112
+ return record;
113
+ }
@@ -0,0 +1,186 @@
1
+ import Smithery from "@smithery/api";
2
+ function isSmitherySdkClientShape(candidate) {
3
+ if (!candidate || typeof candidate !== "object") {
4
+ return false;
5
+ }
6
+ const maybe = candidate;
7
+ return typeof maybe.servers?.get === "function" && typeof maybe.connections?.get === "function" && typeof maybe.connections?.set === "function";
8
+ }
9
+ export class SmitherySdkClient {
10
+ runtimeConfig;
11
+ fetchFn;
12
+ sdkClient;
13
+ constructor(runtimeConfig, clientOrFetch, sdkClient) {
14
+ this.runtimeConfig = runtimeConfig;
15
+ this.fetchFn = typeof clientOrFetch === "function" ? clientOrFetch : fetch;
16
+ if (isSmitherySdkClientShape(clientOrFetch)) {
17
+ this.sdkClient = clientOrFetch;
18
+ }
19
+ else if (sdkClient) {
20
+ this.sdkClient = sdkClient;
21
+ }
22
+ }
23
+ async getServerByExactId(smitheryId) {
24
+ if (!this.runtimeConfig.apiKey) {
25
+ return {
26
+ status: "error",
27
+ message: "Missing Smithery API key.",
28
+ };
29
+ }
30
+ try {
31
+ const server = await this.getClient().servers.get(smitheryId);
32
+ // Check for required configuration
33
+ const configProperties = new Set();
34
+ if (Array.isArray(server.connections)) {
35
+ for (const conn of server.connections) {
36
+ if (conn.configSchema?.properties) {
37
+ for (const key of Object.keys(conn.configSchema.properties)) {
38
+ configProperties.add(key);
39
+ }
40
+ }
41
+ }
42
+ }
43
+ const actionRequired = configProperties.size > 0
44
+ ? {
45
+ reason: "config_required",
46
+ message: `Configuration required for Smithery server "${smitheryId}".`,
47
+ fields: Array.from(configProperties),
48
+ }
49
+ : undefined;
50
+ // Map connection info if available. We prefer hosted HTTP connections.
51
+ const httpConn = server.connections?.find((c) => c.type === "http");
52
+ const connection = httpConn
53
+ ? {
54
+ transport: "http",
55
+ mcpUrl: httpConn.deploymentUrl ||
56
+ httpConn.bundleUrl ||
57
+ server.deploymentUrl ||
58
+ undefined,
59
+ readiness: "unknown",
60
+ }
61
+ : undefined;
62
+ return {
63
+ status: "found",
64
+ server: {
65
+ name: server.displayName || server.qualifiedName,
66
+ tools: server.tools ?? undefined,
67
+ resources: server.resources ?? undefined,
68
+ prompts: server.prompts ?? undefined,
69
+ },
70
+ actionRequired,
71
+ connection,
72
+ };
73
+ }
74
+ catch (err) {
75
+ if (err.status === 404) {
76
+ return {
77
+ status: "not_found",
78
+ message: `Smithery server "${smitheryId}" not found.`,
79
+ };
80
+ }
81
+ if (err.status === 401 || err.status === 403) {
82
+ return {
83
+ status: "error",
84
+ message: `Smithery API key is missing, invalid, or unauthorized (HTTP ${err.status}). Check the SMITHERY_API_KEY runtime configuration.`,
85
+ };
86
+ }
87
+ return {
88
+ status: "error",
89
+ message: err.message || String(err),
90
+ };
91
+ }
92
+ }
93
+ async getConnection(namespace, connectionId) {
94
+ if (!this.runtimeConfig.apiKey) {
95
+ return { status: "error", message: "Missing Smithery API key." };
96
+ }
97
+ try {
98
+ const connection = await this.getClient().connections.get(connectionId, { namespace });
99
+ return this.mapConnectionData(connection);
100
+ }
101
+ catch (err) {
102
+ if (err.status === 404) {
103
+ return {
104
+ status: "not_found",
105
+ message: `Connection "${connectionId}" not found in namespace "${namespace}".`,
106
+ };
107
+ }
108
+ if (err.status === 401 || err.status === 403) {
109
+ return {
110
+ status: "error",
111
+ message: `Smithery API key is missing, invalid, or unauthorized (HTTP ${err.status}). Check the SMITHERY_API_KEY runtime configuration.`,
112
+ };
113
+ }
114
+ return { status: "error", message: err.message || String(err) };
115
+ }
116
+ }
117
+ async createOrUpdateConnection(input) {
118
+ if (!this.runtimeConfig.apiKey) {
119
+ return { status: "error", message: "Missing Smithery API key." };
120
+ }
121
+ try {
122
+ const connection = await this.getClient().connections.set(input.connectionId, {
123
+ namespace: input.namespace,
124
+ server: input.smitheryId,
125
+ name: input.handle,
126
+ ...(input.transport ? { transport: input.transport } : {}),
127
+ metadata: {
128
+ backburner: "backburner",
129
+ backburnerHandle: input.handle,
130
+ providerMcpId: input.smitheryId,
131
+ localConnectionIdentity: input.connectionId,
132
+ },
133
+ });
134
+ return this.mapConnectionData(connection);
135
+ }
136
+ catch (err) {
137
+ if (err.status === 401 || err.status === 403) {
138
+ return {
139
+ status: "error",
140
+ message: `Smithery API key is missing, invalid, or unauthorized (HTTP ${err.status}). Check the SMITHERY_API_KEY runtime configuration.`,
141
+ };
142
+ }
143
+ return { status: "error", message: err.message || String(err) };
144
+ }
145
+ }
146
+ mapConnectionData(data) {
147
+ const state = data.status?.state;
148
+ let status = "error";
149
+ if (state === "connected")
150
+ status = "connected";
151
+ else if (state === "auth_required")
152
+ status = "auth_required";
153
+ else if (state === "input_required")
154
+ status = "input_required";
155
+ else if (state === "disconnected")
156
+ status = "disconnected";
157
+ const rawMetadata = data.metadata;
158
+ const metadata = rawMetadata && typeof rawMetadata === "object" && !Array.isArray(rawMetadata)
159
+ ? rawMetadata
160
+ : undefined;
161
+ return {
162
+ status,
163
+ message: data.status?.message,
164
+ connectionId: data.connectionId,
165
+ namespace: data.namespace,
166
+ transport: data.transport ?? undefined,
167
+ mcpUrl: data.mcpUrl || undefined,
168
+ setupUrl: data.status?.setupUrl || data.status?.url,
169
+ fields: data.status?.fields,
170
+ ...(metadata ? { metadata } : {}),
171
+ };
172
+ }
173
+ getClient() {
174
+ if (this.sdkClient) {
175
+ return this.sdkClient;
176
+ }
177
+ if (!this.runtimeConfig.apiKey) {
178
+ throw new Error("Missing Smithery API key.");
179
+ }
180
+ this.sdkClient = new Smithery({
181
+ apiKey: this.runtimeConfig.apiKey,
182
+ fetch: this.fetchFn,
183
+ });
184
+ return this.sdkClient;
185
+ }
186
+ }