@jam-mcp/server 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.
Files changed (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +65 -0
  3. package/dist/adapters/cache/noop-cache.d.ts +7 -0
  4. package/dist/adapters/cache/noop-cache.js +12 -0
  5. package/dist/adapters/credentials/composite.d.ts +30 -0
  6. package/dist/adapters/credentials/composite.js +79 -0
  7. package/dist/adapters/credentials/process-env.d.ts +13 -0
  8. package/dist/adapters/credentials/process-env.js +17 -0
  9. package/dist/adapters/credentials/secret-store.d.ts +69 -0
  10. package/dist/adapters/credentials/secret-store.js +259 -0
  11. package/dist/adapters/credentials/windows-user-env.d.ts +20 -0
  12. package/dist/adapters/credentials/windows-user-env.js +51 -0
  13. package/dist/adapters/jira-cloud/adf-to-text.d.ts +12 -0
  14. package/dist/adapters/jira-cloud/adf-to-text.js +151 -0
  15. package/dist/adapters/jira-cloud/jira-client.d.ts +32 -0
  16. package/dist/adapters/jira-cloud/jira-client.js +137 -0
  17. package/dist/adapters/jira-cloud/jira-read.adapter.d.ts +13 -0
  18. package/dist/adapters/jira-cloud/jira-read.adapter.js +97 -0
  19. package/dist/adapters/jira-cloud/jira-write.adapter.d.ts +12 -0
  20. package/dist/adapters/jira-cloud/jira-write.adapter.js +17 -0
  21. package/dist/adapters/jira-cloud/mapper.d.ts +34 -0
  22. package/dist/adapters/jira-cloud/mapper.js +143 -0
  23. package/dist/adapters/telemetry/console-telemetry.d.ts +13 -0
  24. package/dist/adapters/telemetry/console-telemetry.js +31 -0
  25. package/dist/application/get-full-issue-context.d.ts +18 -0
  26. package/dist/application/get-full-issue-context.js +120 -0
  27. package/dist/application/get-issue-context.d.ts +19 -0
  28. package/dist/application/get-issue-context.js +79 -0
  29. package/dist/application/search-issues.d.ts +22 -0
  30. package/dist/application/search-issues.js +93 -0
  31. package/dist/bootstrap/boot-health-gate.d.ts +25 -0
  32. package/dist/bootstrap/boot-health-gate.js +137 -0
  33. package/dist/bootstrap/bootstrap-orchestrator.d.ts +15 -0
  34. package/dist/bootstrap/bootstrap-orchestrator.js +14 -0
  35. package/dist/bootstrap/host-mcp.d.ts +54 -0
  36. package/dist/bootstrap/host-mcp.js +98 -0
  37. package/dist/bootstrap/jira-projects.d.ts +16 -0
  38. package/dist/bootstrap/jira-projects.js +24 -0
  39. package/dist/bootstrap/mcp-config-merger.d.ts +58 -0
  40. package/dist/bootstrap/mcp-config-merger.js +119 -0
  41. package/dist/bootstrap/migration-target.d.ts +60 -0
  42. package/dist/bootstrap/migration-target.js +80 -0
  43. package/dist/bootstrap/project-bindings.d.ts +48 -0
  44. package/dist/bootstrap/project-bindings.js +112 -0
  45. package/dist/bootstrap/project-config-bootstrapper.d.ts +37 -0
  46. package/dist/bootstrap/project-config-bootstrapper.js +69 -0
  47. package/dist/bootstrap/project-config-resolver.d.ts +53 -0
  48. package/dist/bootstrap/project-config-resolver.js +59 -0
  49. package/dist/bootstrap/project-root-resolver.d.ts +22 -0
  50. package/dist/bootstrap/project-root-resolver.js +43 -0
  51. package/dist/bootstrap/setup-apply.d.ts +31 -0
  52. package/dist/bootstrap/setup-apply.js +61 -0
  53. package/dist/bootstrap/setup-plan.d.ts +102 -0
  54. package/dist/bootstrap/setup-plan.js +224 -0
  55. package/dist/bootstrap/setup-state.d.ts +73 -0
  56. package/dist/bootstrap/setup-state.js +89 -0
  57. package/dist/bootstrap/workspace-identity.d.ts +52 -0
  58. package/dist/bootstrap/workspace-identity.js +114 -0
  59. package/dist/cli/agent-api.d.ts +63 -0
  60. package/dist/cli/agent-api.js +149 -0
  61. package/dist/cli/auth.d.ts +39 -0
  62. package/dist/cli/auth.js +201 -0
  63. package/dist/cli/doctor.d.ts +7 -0
  64. package/dist/cli/doctor.js +31 -0
  65. package/dist/cli/runtime.d.ts +16 -0
  66. package/dist/cli/runtime.js +97 -0
  67. package/dist/cli/serve.d.ts +7 -0
  68. package/dist/cli/serve.js +23 -0
  69. package/dist/cli/setup-wizard.d.ts +37 -0
  70. package/dist/cli/setup-wizard.js +338 -0
  71. package/dist/cli/setup.d.ts +26 -0
  72. package/dist/cli/setup.js +214 -0
  73. package/dist/cli/ui.d.ts +118 -0
  74. package/dist/cli/ui.js +338 -0
  75. package/dist/cli-entry.d.ts +7 -0
  76. package/dist/cli-entry.js +124 -0
  77. package/dist/config/load-config.d.ts +18 -0
  78. package/dist/config/load-config.js +48 -0
  79. package/dist/config/schema.d.ts +36 -0
  80. package/dist/config/schema.js +74 -0
  81. package/dist/deps.d.ts +50 -0
  82. package/dist/deps.js +39 -0
  83. package/dist/domain/completeness.d.ts +63 -0
  84. package/dist/domain/completeness.js +20 -0
  85. package/dist/domain/context.d.ts +38 -0
  86. package/dist/domain/context.js +1 -0
  87. package/dist/domain/errors.d.ts +27 -0
  88. package/dist/domain/errors.js +49 -0
  89. package/dist/domain/issue.d.ts +35 -0
  90. package/dist/domain/issue.js +1 -0
  91. package/dist/index.d.ts +2 -0
  92. package/dist/index.js +17 -0
  93. package/dist/mcp/create-server.d.ts +9 -0
  94. package/dist/mcp/create-server.js +32 -0
  95. package/dist/mcp/tool-result.d.ts +10 -0
  96. package/dist/mcp/tool-result.js +33 -0
  97. package/dist/mcp/tools/jira-context.tool.d.ts +3 -0
  98. package/dist/mcp/tools/jira-context.tool.js +25 -0
  99. package/dist/mcp/tools/jira-full.tool.d.ts +3 -0
  100. package/dist/mcp/tools/jira-full.tool.js +27 -0
  101. package/dist/mcp/tools/jira-search.tool.d.ts +3 -0
  102. package/dist/mcp/tools/jira-search.tool.js +31 -0
  103. package/dist/policy/completeness-policy.d.ts +19 -0
  104. package/dist/policy/completeness-policy.js +34 -0
  105. package/dist/policy/consistency-policy.d.ts +13 -0
  106. package/dist/policy/consistency-policy.js +6 -0
  107. package/dist/policy/field-policy.d.ts +11 -0
  108. package/dist/policy/field-policy.js +26 -0
  109. package/dist/policy/output-budget-policy.d.ts +23 -0
  110. package/dist/policy/output-budget-policy.js +96 -0
  111. package/dist/policy/pagination-policy.d.ts +15 -0
  112. package/dist/policy/pagination-policy.js +13 -0
  113. package/dist/ports/cache.port.d.ts +5 -0
  114. package/dist/ports/cache.port.js +1 -0
  115. package/dist/ports/credentials.port.d.ts +27 -0
  116. package/dist/ports/credentials.port.js +1 -0
  117. package/dist/ports/jira-read.port.d.ts +73 -0
  118. package/dist/ports/jira-read.port.js +1 -0
  119. package/dist/ports/jira-write.port.d.ts +12 -0
  120. package/dist/ports/jira-write.port.js +1 -0
  121. package/dist/ports/telemetry.port.d.ts +13 -0
  122. package/dist/ports/telemetry.port.js +1 -0
  123. package/package.json +69 -0
@@ -0,0 +1,201 @@
1
+ import { CompositeCredentialProvider } from "../adapters/credentials/composite.js";
2
+ import { SecretStoreUnavailableError, resolveSecretStore, secretStoreDisabled, } from "../adapters/credentials/secret-store.js";
3
+ import { ProjectConfigSchema } from "../config/schema.js";
4
+ import { toJamError } from "../domain/errors.js";
5
+ import { Ui } from "./ui.js";
6
+ const TOKEN_URL = "https://id.atlassian.com/manage-profile/security/api-tokens";
7
+ // A sentence, not a command: JAM does not know whether this shell wants
8
+ // `export`, `set` or `$env:`, and printing one of them as if it were runnable
9
+ // is worse than describing what has to end up in the environment. Matches the
10
+ // wording the secret store already uses for the same situation.
11
+ const ENV_HINT = "Set JIRA_BASE_URL, JIRA_EMAIL and JIRA_API_TOKEN instead.";
12
+ /**
13
+ * Bounded rather than open: a run whose input has stopped producing usable
14
+ * answers has to end, and a login that spins forever on a script or a pipe is
15
+ * worse than one that gives up and says why.
16
+ */
17
+ const MAX_PROMPT_ATTEMPTS = 3;
18
+ /**
19
+ * Re-ask one step until its answer parses.
20
+ *
21
+ * Losing an entire login to one mistyped URL is not a safety property, it is
22
+ * just the step forgetting what it was asking for. The retry stays inside the
23
+ * step so callers still see a single exit code.
24
+ */
25
+ async function askUntilValid(ask, parse, onInvalid) {
26
+ for (let attempt = 1; attempt <= MAX_PROMPT_ATTEMPTS; attempt++) {
27
+ const parsed = parse(await ask());
28
+ if (parsed !== undefined)
29
+ return parsed;
30
+ onInvalid();
31
+ }
32
+ return undefined;
33
+ }
34
+ function giveUp(ui, what) {
35
+ ui.line();
36
+ ui.failure(`No usable ${what} after ${MAX_PROMPT_ATTEMPTS} attempts`);
37
+ ui.next(ENV_HINT);
38
+ return 1;
39
+ }
40
+ /**
41
+ * A provider caches its answer for its own lifetime, so the effective source
42
+ * after a write can only be observed through a new one.
43
+ */
44
+ function freshPort() {
45
+ return new CompositeCredentialProvider();
46
+ }
47
+ export async function authLoginCommand(options = {}) {
48
+ const ui = options.ui ?? new Ui();
49
+ const store = "store" in options ? options.store : resolveSecretStore();
50
+ const readBack = options.readBack ?? freshPort;
51
+ ui.section("Authentication");
52
+ if (!store) {
53
+ // Disabled and absent are different problems with different fixes, and
54
+ // saying "no store" when one was switched off sends the user hunting.
55
+ if (secretStoreDisabled()) {
56
+ ui.failure("Secret store disabled by JAM_DISABLE_SECRET_STORE");
57
+ ui.line(" That variable is for isolated test sandboxes. Unset it and run this again.");
58
+ ui.next(ENV_HINT);
59
+ return 1;
60
+ }
61
+ ui.failure("No usable secret store was found on this system");
62
+ ui.line(" JAM stores credentials where the operating system holds them for you,");
63
+ ui.line(" so an editor launched from a Dock or Start menu can still read them.");
64
+ ui.next(ENV_HINT);
65
+ return 1;
66
+ }
67
+ const existing = readBack().describe();
68
+ // Any page from their Jira site, pasted whole. Nobody should have to know
69
+ // what an origin is, or strip a path by hand, to log in.
70
+ const baseUrl = await askUntilValid(() => ui.prompt("Paste your Jira URL", ENV_HINT, existing.baseUrl ?? undefined), toJiraOrigin, () => {
71
+ ui.failure("That does not look like a Jira URL");
72
+ ui.line(" Paste any page URL from your Jira site.");
73
+ });
74
+ if (!baseUrl)
75
+ return giveUp(ui, "Jira URL");
76
+ // Checked at its own step: an empty email used to surface only after the
77
+ // token had been typed, which asks for a secret in order to reject the line
78
+ // before it.
79
+ const email = await askUntilValid(() => ui.prompt("Atlassian account email", ENV_HINT, existing.email ?? undefined), (raw) => raw.trim() || undefined, () => ui.failure("An Atlassian account email is required"));
80
+ if (!email)
81
+ return giveUp(ui, "Atlassian account email");
82
+ ui.line(` Create a token at ${TOKEN_URL}`);
83
+ const apiToken = await ui.secret("Atlassian API token", ENV_HINT);
84
+ if (!apiToken) {
85
+ ui.failure("An API token is required");
86
+ ui.next(ENV_HINT);
87
+ return 1;
88
+ }
89
+ const values = { baseUrl, email, apiToken };
90
+ // Verified before it is stored. A rejected token written to the keychain is
91
+ // the worst outcome available here: every later command fails, and the thing
92
+ // that is wrong looks like the thing that was just fixed.
93
+ const verify = options.verify ?? ((v) => verifyAgainstJira(ui, v));
94
+ const failure = await verify(values);
95
+ if (failure) {
96
+ ui.line();
97
+ ui.failure("Jira rejected these credentials");
98
+ ui.line(` ${failure}`);
99
+ ui.line(" Nothing was stored.");
100
+ return 1;
101
+ }
102
+ try {
103
+ store.write(values);
104
+ }
105
+ catch (err) {
106
+ ui.failure("Could not store the credentials");
107
+ ui.line(` ${err instanceof Error ? err.message : String(err)}`);
108
+ if (err instanceof SecretStoreUnavailableError)
109
+ ui.next(err.remedy);
110
+ return 1;
111
+ }
112
+ ui.success("Authentication stored", `${email} · ${baseUrl} (${store.label})`);
113
+ reportOverride(ui, readBack());
114
+ return 0;
115
+ }
116
+ export function authLogoutCommand(options = {}) {
117
+ const ui = options.ui ?? new Ui();
118
+ const store = "store" in options ? options.store : resolveSecretStore();
119
+ const readBack = options.readBack ?? freshPort;
120
+ ui.section("Authentication");
121
+ if (!store) {
122
+ ui.warn("No secret store on this system, so there is nothing stored to remove");
123
+ reportRemaining(ui, readBack());
124
+ return 0;
125
+ }
126
+ store.clear();
127
+ ui.success("Removed the stored credentials");
128
+ reportRemaining(ui, readBack());
129
+ return 0;
130
+ }
131
+ /**
132
+ * Warn when an exported variable shadows what was just stored.
133
+ *
134
+ * The chain merges per field, so this is not "are all three exported" - one
135
+ * stale `export` in a shell profile is enough to make part of the stored
136
+ * credential unreachable, and the resulting split shows up as "mixed".
137
+ */
138
+ function reportOverride(ui, port) {
139
+ const source = port.describe().source;
140
+ if (source === "secret-store")
141
+ return;
142
+ if (source === "mixed") {
143
+ ui.warn("Current JIRA_* environment variables override part of the stored credentials");
144
+ }
145
+ else {
146
+ ui.warn("Current JIRA_* environment variables override the stored credentials");
147
+ }
148
+ ui.line(` Effective source: ${source}`);
149
+ ui.line(" Unset them to use what was just stored.");
150
+ }
151
+ /** After a logout, say plainly whether anything still authenticates JAM. */
152
+ function reportRemaining(ui, port) {
153
+ const described = port.describe();
154
+ const stillResolves = Boolean(described.baseUrl && described.email && described.hasToken);
155
+ if (!stillResolves && described.source === "none") {
156
+ ui.line(" JAM is no longer authenticated.");
157
+ return;
158
+ }
159
+ ui.warn(stillResolves
160
+ ? "Jira credentials still resolve from outside the secret store"
161
+ : "Part of a Jira credential still resolves from outside the secret store");
162
+ ui.line(` Effective source: ${described.source}`);
163
+ ui.line(" Unset JIRA_BASE_URL, JIRA_EMAIL and JIRA_API_TOKEN to finish logging out.");
164
+ }
165
+ /** undefined when Jira accepted the credentials; otherwise the reason. */
166
+ async function verifyAgainstJira(ui, values) {
167
+ const port = {
168
+ load: () => values,
169
+ describe: () => ({ baseUrl: values.baseUrl, email: values.email, hasToken: true, source: "process" }),
170
+ };
171
+ try {
172
+ const { JiraCloudReadAdapter } = await import("../adapters/jira-cloud/jira-read.adapter.js");
173
+ const jira = new JiraCloudReadAdapter(port, ProjectConfigSchema.parse({}));
174
+ const me = await ui.spin("Checking Jira access...", () => jira.getCurrentUser());
175
+ ui.success("Jira accepted the credentials", me.displayName ?? me.emailAddress ?? me.accountId);
176
+ return undefined;
177
+ }
178
+ catch (err) {
179
+ const jamError = toJamError(err);
180
+ return `${jamError.code}: ${jamError.message}`;
181
+ }
182
+ }
183
+ /**
184
+ * The origin of any URL from the user's Jira site, or undefined.
185
+ *
186
+ * Parsed rather than trimmed, because a pathname is the failure that actually
187
+ * happens: someone copies the project page URL, and JiraClient resolves each
188
+ * REST path *relative* to the base, so `/jira/software/c/projects/KEY/summary`
189
+ * gets the API path appended to it. Atlassian answers that with its HTML shell
190
+ * at 200, and the only symptom is "Jira returned a non-JSON response".
191
+ */
192
+ export function toJiraOrigin(input) {
193
+ let url;
194
+ try {
195
+ url = new URL(input.trim());
196
+ }
197
+ catch {
198
+ return undefined;
199
+ }
200
+ return url.protocol === "http:" || url.protocol === "https:" ? url.origin : undefined;
201
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * `jam doctor` exists to answer one question fast: is this a Jira problem, a
3
+ * credential problem, or a local setup problem? It is read-only - it never
4
+ * bootstraps a missing project.yaml (that is `jam setup`'s job) - and runs the
5
+ * "full" health gate, including live Jira connectivity.
6
+ */
7
+ export declare function doctor(cwd?: string): Promise<number>;
@@ -0,0 +1,31 @@
1
+ import { buildDeps } from "../deps.js";
2
+ import { toJamError } from "../domain/errors.js";
3
+ import { runHealthGate } from "../bootstrap/boot-health-gate.js";
4
+ /**
5
+ * `jam doctor` exists to answer one question fast: is this a Jira problem, a
6
+ * credential problem, or a local setup problem? It is read-only - it never
7
+ * bootstraps a missing project.yaml (that is `jam setup`'s job) - and runs the
8
+ * "full" health gate, including live Jira connectivity.
9
+ */
10
+ export async function doctor(cwd) {
11
+ let deps;
12
+ try {
13
+ // Optional fallback: doctor reports what `jam serve` would run with -
14
+ // including a key that comes from this user's binding rather than from a
15
+ // file in the repository - and never refuses to load.
16
+ deps = await buildDeps({ cwd, keyFallback: "optional" });
17
+ }
18
+ catch (err) {
19
+ process.stdout.write(`[FAIL] Project config - ${toJamError(err).message}\n`);
20
+ return 1;
21
+ }
22
+ const gate = await runHealthGate(deps, "full");
23
+ for (const check of gate.checks) {
24
+ process.stdout.write(`${check.ok ? "[OK] " : "[FAIL]"} ${check.name}${check.detail ? ` - ${check.detail}` : ""}\n`);
25
+ }
26
+ const failed = gate.checks.filter((c) => !c.ok);
27
+ process.stdout.write(failed.length === 0
28
+ ? `\nAll ${gate.checks.length} checks passed.\n`
29
+ : `\n${failed.length} of ${gate.checks.length} checks failed: ${failed.map((f) => f.name).join(", ")}\n`);
30
+ return gate.passed ? 0 : 1;
31
+ }
@@ -0,0 +1,16 @@
1
+ export type RuntimeCommandOptions = {
2
+ /** Injected by tests; defaults to the real user home. */
3
+ home?: string;
4
+ json?: boolean;
5
+ };
6
+ /**
7
+ * `jam runtime` - inspect or change which JAM build this machine runs.
8
+ *
9
+ * The config module is imported from the launcher rather than reimplemented
10
+ * here: the launcher reads this file on every MCP start, so a second writer
11
+ * with its own idea of the format is how the two drift apart.
12
+ *
13
+ * This command touches ~/.jam/config.yaml only. It never writes to a project.
14
+ */
15
+ export declare function showRuntime(options?: RuntimeCommandOptions): number;
16
+ export declare function useRuntime(mode: string | undefined, source: string | undefined, options?: RuntimeCommandOptions): number;
@@ -0,0 +1,97 @@
1
+ import { readRuntimeConfig, resolveRuntime, runtimeConfigPath, writeRuntimeConfig, LauncherError, } from "@jam-mcp/launcher";
2
+ /**
3
+ * `jam runtime` - inspect or change which JAM build this machine runs.
4
+ *
5
+ * The config module is imported from the launcher rather than reimplemented
6
+ * here: the launcher reads this file on every MCP start, so a second writer
7
+ * with its own idea of the format is how the two drift apart.
8
+ *
9
+ * This command touches ~/.jam/config.yaml only. It never writes to a project.
10
+ */
11
+ export function showRuntime(options = {}) {
12
+ const config = readRuntimeConfig(options.home);
13
+ if (!config) {
14
+ if (options.json) {
15
+ writeJson({ status: "not_configured", code: "JAM_RUNTIME_CONFIG_MISSING" });
16
+ return 1;
17
+ }
18
+ process.stdout.write("No JAM runtime is configured for this user.\n");
19
+ process.stdout.write("\nRun:\n jam runtime use package\n");
20
+ return 1;
21
+ }
22
+ let resolved;
23
+ let error;
24
+ try {
25
+ resolved = resolveRuntime(config);
26
+ }
27
+ catch (err) {
28
+ error = err instanceof Error ? err.message : String(err);
29
+ }
30
+ if (options.json) {
31
+ writeJson({
32
+ status: resolved ? "configured" : "invalid",
33
+ mode: config.runtime.mode,
34
+ ...(config.runtime.mode === "development" ? { source: config.runtime.source } : {}),
35
+ ...(resolved ? { version: resolved.version } : {}),
36
+ ...(error ? { error } : {}),
37
+ configPath: runtimeConfigPath(options.home),
38
+ });
39
+ return resolved ? 0 : 1;
40
+ }
41
+ process.stdout.write(`Runtime: ${config.runtime.mode}\n`);
42
+ if (config.runtime.mode === "development") {
43
+ process.stdout.write(`Source: ${config.runtime.source}\n`);
44
+ }
45
+ if (resolved) {
46
+ process.stdout.write(`Version: ${resolved.version}\n`);
47
+ }
48
+ else {
49
+ process.stdout.write(`\n[FAIL] ${error}\n`);
50
+ }
51
+ process.stdout.write(`Config: ${runtimeConfigPath(options.home)}\n`);
52
+ return resolved ? 0 : 1;
53
+ }
54
+ export function useRuntime(mode, source, options = {}) {
55
+ if (mode === "package") {
56
+ const path = writeRuntimeConfig({ version: 1, runtime: { mode: "package" } }, options.home);
57
+ report({ mode: "package", path }, options);
58
+ return 0;
59
+ }
60
+ if (mode === "development") {
61
+ if (!source) {
62
+ process.stderr.write("Usage: jam runtime use development <path-to-jam-checkout>\n");
63
+ return 1;
64
+ }
65
+ // Validate before persisting, so a bad path fails here rather than on the
66
+ // next MCP start where the error surfaces without context.
67
+ try {
68
+ resolveRuntime({ version: 1, runtime: { mode: "development", source } });
69
+ }
70
+ catch (err) {
71
+ if (err instanceof LauncherError) {
72
+ process.stderr.write(`[jam] ${err.code}: ${err.message}\n`);
73
+ if (err.nextCommand)
74
+ process.stderr.write(`\nRun:\n ${err.nextCommand}\n`);
75
+ }
76
+ else {
77
+ process.stderr.write(`[jam] ${err instanceof Error ? err.message : String(err)}\n`);
78
+ }
79
+ return 1;
80
+ }
81
+ const path = writeRuntimeConfig({ version: 1, runtime: { mode: "development", source } }, options.home);
82
+ report({ mode: "development", source, path }, options);
83
+ return 0;
84
+ }
85
+ process.stderr.write("Usage: jam runtime use package | jam runtime use development <path>\n");
86
+ return 1;
87
+ }
88
+ function report(result, options) {
89
+ if (options.json) {
90
+ writeJson({ status: "configured", ...result, configPath: result.path });
91
+ return;
92
+ }
93
+ process.stdout.write(`Runtime set to ${result.mode}${result.source ? ` (${result.source})` : ""}.\n`);
94
+ }
95
+ function writeJson(payload) {
96
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
97
+ }
@@ -0,0 +1,7 @@
1
+ import { type BootstrapForServeOptions } from "../bootstrap/bootstrap-orchestrator.js";
2
+ /**
3
+ * stdout is reserved for the MCP protocol - every diagnostic here goes to
4
+ * stderr. A failed boot gate means the server never calls `connect()`: a
5
+ * half-started MCP is worse than a clear failure message and a non-zero exit.
6
+ */
7
+ export declare function serve(options?: BootstrapForServeOptions): Promise<number>;
@@ -0,0 +1,23 @@
1
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2
+ import { bootstrapForServe } from "../bootstrap/bootstrap-orchestrator.js";
3
+ import { createServer } from "../mcp/create-server.js";
4
+ /**
5
+ * stdout is reserved for the MCP protocol - every diagnostic here goes to
6
+ * stderr. A failed boot gate means the server never calls `connect()`: a
7
+ * half-started MCP is worse than a clear failure message and a non-zero exit.
8
+ */
9
+ export async function serve(options = {}) {
10
+ const { deps, gate } = await bootstrapForServe(options);
11
+ if (!gate.passed) {
12
+ process.stderr.write("[jam] boot check failed - MCP server not started:\n");
13
+ for (const check of gate.checks.filter((c) => c.fatal && !c.ok)) {
14
+ process.stderr.write(` [FAIL] ${check.name}${check.detail ? ` - ${check.detail}` : ""}\n`);
15
+ }
16
+ process.stderr.write("Run `jam setup` to fix configuration, or `jam doctor` for the full diagnosis.\n");
17
+ return 1;
18
+ }
19
+ const server = createServer(deps);
20
+ await server.connect(new StdioServerTransport());
21
+ process.stderr.write(`[jam] serving on stdio (project=${deps.config.project.key || "unset"}, config=${deps.configPath ?? `none (key from ${deps.keySource ?? "defaults"})`})\n`);
22
+ return -1; // stays alive on the stdio transport
23
+ }
@@ -0,0 +1,37 @@
1
+ import { type SetupPlan } from "../bootstrap/setup-plan.js";
2
+ import type { CredentialPort } from "../ports/credentials.port.js";
3
+ import type { JiraReadPort } from "../ports/jira-read.port.js";
4
+ import type { AuthOptions } from "./auth.js";
5
+ import type { HostRunner } from "../bootstrap/host-mcp.js";
6
+ import { Ui } from "./ui.js";
7
+ export type WizardOptions = {
8
+ cwd?: string;
9
+ home?: string;
10
+ explicitKey?: string;
11
+ /** `--shared`: adopt JAM for the team by writing into the repository. */
12
+ shared?: boolean;
13
+ /** Injected by tests so no test ever registers JAM with a real host. */
14
+ runHost?: HostRunner;
15
+ migrate?: boolean;
16
+ ui?: Ui;
17
+ /**
18
+ * Injected by tests. Without these the wizard reads the machine it runs on -
19
+ * the real keychain through `detectSetupState`, the real Jira through
20
+ * `listVisibleProjects` and the health gate, and the real `JAM_PROJECT_KEY`
21
+ * through the plan. Production passes none of them and behaves as before.
22
+ */
23
+ credentials?: CredentialPort;
24
+ jira?: JiraReadPort;
25
+ env?: NodeJS.ProcessEnv;
26
+ auth?: AuthOptions;
27
+ };
28
+ /**
29
+ * `jam setup`, the human path.
30
+ *
31
+ * Runs the same detect -> plan -> apply -> verify core the agent API uses; the
32
+ * only difference is presentation and the ability to ask. Anything that can be
33
+ * determined is determined - the wizard never asks a question it already knows
34
+ * the answer to, and never re-walks steps that are already done.
35
+ */
36
+ export declare function runSetupWizard(options?: WizardOptions): Promise<number>;
37
+ export type { SetupPlan };