manifest 6.0.0 → 7.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.
package/CONTRACT.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  The SDK talks to the configured Manifest API using `Authorization: Bearer <project key>` and `User-Agent: mnfst-node/<version>`.
4
4
 
5
+ ## Handshake
6
+
7
+ `POST /v1/hello` announces an install — `{"runtime":"node-22.0.0"}` — authorized with the same bearer key. A `200` confirms the key; `401`, or a `403` whose body is not `{"error":"project_disabled"}`, rejects it. The `200` body may carry project and activity context, all optional:
8
+
9
+ ```json
10
+ {"project": {"name": "Find Concierge"}, "requests": 12}
11
+ ```
12
+
13
+ `manifest doctor` reads `project` (also accepting `projectName`, `project_name` or a bare `name`) and `requests` (also accepting `requestCount`, `requestsCount`, `requests_count`, `request_count`, or an object with `total`) to print the project and to warn when no request has arrived yet. Missing fields simply hide those lines.
14
+
15
+ A capture may carry a top-level `"synthetic": true`, which `manifest doctor --send-test` sets. Synthetic captures exist only to prove the pipeline end to end and must be excluded from statistics.
16
+
5
17
  ## Capture
6
18
 
7
19
  `POST /v1/heal` receives:
@@ -15,13 +27,13 @@ The SDK talks to the configured Manifest API using `Authorization: Bearer <proje
15
27
  }
16
28
  ```
17
29
 
18
- Credential filtering and body limits are described in the README. Capture gates live in `runtime.ts`; the server owns repair policy.
30
+ Any 4xx response is captured except 401, 402, 403 and 429; those and every 5xx pass through untouched, because auth, billing, rate limiting and server faults are not repaired by editing the request. JSON bodies and `application/x-www-form-urlencoded` bodies are sent as structured JSON values. Credential filtering and body limits are described in the README. Capture gates live in `runtime.ts`; the server owns repair policy.
19
31
 
20
32
  A successful heal response may contain `status: patched|unverified`, `healAttemptId`, `operations` and `healedRequest` with `url`, `headers` or `body`. Only these two statuses authorize a retry. No patch, malformed responses and unavailable service return the original error response. HTTP 403 with `{"error":"project_disabled"}` suppresses healing for five minutes.
21
33
 
22
34
  ## Apply
23
35
 
24
- A healed URL replaces the URL only within the original origin. Headers set or replace case-insensitively; null removes a header. Content length is recalculated. Objects merge using the server's healed body as the authoritative copy of fields sent to the server; withheld local credential fields are restored. Non-object JSON replaces the body. Incomplete request or error captures are not retried.
36
+ A healed URL replaces the URL only within the original origin. Headers set or replace case-insensitively; null removes a header. Content length is recalculated. Objects merge using the server's healed body as the authoritative copy of fields sent to the server; withheld local credential fields are restored. Non-object JSON replaces the body. Form-urlencoded retries use the original encoding and are re-encoded from the parsed structure, so repeated keys return as indexed keys; a non-object healed body is not retried for them. A healed body that merges to nothing is sent as no body at all: GET, HEAD, DELETE and OPTIONS retry bodyless, and any other method is not retried. GET and HEAD never carry a body on a retry. Incomplete or malformed request captures and incomplete error captures are not retried.
25
37
 
26
38
  Each captured failure permits one retry. A retry response, including another failure, is returned to the caller. A transport failure returns the original response. Successful response streams are not eagerly consumed.
27
39
 
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MNFST, Inc
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,52 +1,93 @@
1
+ <div align="center">
2
+
3
+ ![Manifest SDK Architecture](./docs/github-sdk.png)
4
+
1
5
  # Manifest for Node.js
2
6
 
7
+ **Turn 🔴 4xx API errors into 🟢 2xx in real time.**
8
+
3
9
  [![CI](https://github.com/mnfst/manifest-node/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/mnfst/manifest-node/actions/workflows/ci.yml)
10
+ [![npm version](https://img.shields.io/npm/v/manifest?label=npm)](https://www.npmjs.com/package/manifest)
11
+ [![npm downloads](https://img.shields.io/npm/dm/manifest?label=npm%20downloads)](https://www.npmjs.com/package/manifest)
4
12
 
5
- Repair failed JSON API requests automatically. Works with Node's built-in `fetch`, for everyday APIs and LLMs alike.
13
+ </div>
6
14
 
7
- ```js
8
- import { manifest } from 'manifest';
15
+ ---
9
16
 
10
- manifest();
11
- // Keep making your API calls as usual.
12
- ```
17
+ ## What is Manifest
13
18
 
14
- Your API rejects a request → Manifest finds a repair → the SDK retries once, locally.
19
+ Manifest is a self-healing layer that fixes and retries failed API requests on the fly.
15
20
 
16
- ## Setup
21
+ * 🎯 **Fix failures automatically** before they impact your users.
22
+ * 🔔 **Get notified of root causes** so you can fix them permanently.
23
+ * 🔌 **Works across your stack** with internal APIs, external services, and agent tools.
24
+
25
+ ## How it works
26
+
27
+ ![How Manifest heals a failed request: a 400 reaches Manifest, drops to a patch from the knowledge base or the healing agents, and is retried once, returning a 200 OK](./docs/sdk-flow-diagram.png)
17
28
 
18
- ### 1. Install
29
+ ## Prerequisites
19
30
 
20
- Requires **Node.js 22+**:
31
+ - <a href="https://nodejs.org/en/download/" target="_blank">Node.js 22</a> or higher
32
+
33
+ ## Get started
34
+
35
+ ### Start with your agent
36
+
37
+ ```
38
+ "Install Manifest in this app: https://dashboard.manifest.build/prompt-node.md"
39
+ ```
40
+
41
+ [Read the prompt →](https://dashboard.manifest.build/prompt-node.md)
42
+
43
+ The prompt adds the one-line install to your entry file and stops to let you paste your key.
44
+
45
+ ### Start with code
21
46
 
22
47
  ```sh
23
48
  npm install manifest
24
49
  ```
25
50
 
26
- JavaScript, TypeScript, ESM and CommonJS are supported; there are no runtime dependencies.
51
+ ```js
52
+ import { manifest } from 'manifest';
27
53
 
28
- ### 2. Connect your project
54
+ manifest(); // Once, at startup.
55
+ // Keep making your API calls as usual.
56
+ ```
57
+
58
+ TypeScript, ESM and CommonJS. Zero dependencies.
59
+
60
+ ## Setup
29
61
 
30
- Create a project in your Manifest dashboard and copy the project key shown during setup. In **Project Settings**, turn **Autofix** on to enable repairs.
62
+ 1. Create a project in your [Manifest dashboard](https://dashboard.manifest.build) and copy its project key.
63
+ 2. Set the key as an environment variable:
31
64
 
32
65
  ```sh
33
66
  export MNFST_KEY='your-project-key'
34
67
  ```
35
68
 
36
- The SDK defaults to `https://api.manifest.build`. For a local app running on port 5310, also set:
69
+ Call `manifest()` from the first import of the file that starts your app, before any client is constructed. Some clients keep the `fetch` they saw when they were built, and a client built at import time runs before your call. Where that happens, or where the start command is not yours to change, preload the SDK instead:
70
+
71
+ ```sh
72
+ node -r manifest/register app.js
73
+ ```
74
+
75
+ Self-healing is enabled by default in your project settings.
76
+
77
+ Verify the install from your project directory:
37
78
 
38
79
  ```sh
39
- export MNFST_URL='http://127.0.0.1:5310'
80
+ npx manifest doctor
40
81
  ```
41
82
 
42
- Your server must support the [SDK API contract](CONTRACT.md). The local app must already be running.
83
+ It resolves the installed SDK version, masks and validates the key against the handshake endpoint, checks that Manifest loads before your app, and prints the runtime coverage.
43
84
 
44
- ### 3. Initialize before your requests
85
+ ## Try it
45
86
 
46
- Call `manifest()` once at startup, before other libraries save a reference to `fetch`. Save this as `example.mjs`, replacing the example endpoint and payload with your own:
87
+ Send a request that would normally fail. Manifest catches it, repairs it, and retries:
47
88
 
48
89
  ```js
49
- import { manifest, flush } from 'manifest';
90
+ import { manifest } from 'manifest';
50
91
 
51
92
  manifest({
52
93
  onHeal(event) {
@@ -54,37 +95,16 @@ manifest({
54
95
  },
55
96
  });
56
97
 
57
- try {
58
- const response = await fetch('https://api.example.com/orders', {
59
- method: 'POST',
60
- headers: { 'content-type': 'application/json' },
61
- body: JSON.stringify({ limit: 500 }),
62
- });
63
- console.log(response.status, await response.text());
64
- } finally {
65
- await flush({ timeoutMs: 5000 });
66
- }
98
+ const res = await fetch('https://api.example.com/orders', {
99
+ method: 'POST',
100
+ headers: { 'content-type': 'application/json' },
101
+ body: JSON.stringify({ limit: 500 }), // Invalid? Manifest fixes it and retries.
102
+ });
103
+ console.log(res.status); // See the 200 OK response.
67
104
  ```
68
105
 
69
- Run it with `node example.mjs`. For an API that rejects `limit: 500` and has a matching repair, Manifest can retry with a valid limit. Repairs depend on the API error and available patches.
70
-
71
- CommonJS uses `const { manifest, flush } = require('manifest')`.
72
-
73
- ## Check that it works
74
-
75
- Send a JSON request that your test API rejects with **400, 404 or 422**. Check the failure in your project's dashboard and the `onHeal` callback for the repair result. A successful request alone does not contact Manifest. `flush()` lets a short script wait for outcome reports before exiting.
76
-
77
- ## What to expect
78
-
79
- - **One retry.** Manifest returns a repair; the SDK sends the corrected request directly to your API.
80
- - **Original error if healing is unavailable.** A heal call can add up to 60 seconds. If a retry returns an HTTP response, that response reaches your application.
81
- - **Built-in fetch.** Browser JavaScript, default Axios, `node:http` and separately imported fetch implementations are not intercepted.
82
- - **Retry semantics still matter.** Use idempotency keys where needed; a repeated request can repeat side effects.
83
-
84
- ## Privacy
85
-
86
- Manifest receives failed request URLs, headers, JSON bodies and error responses. Known credential fields are masked or withheld, but nested secrets, prompts and business data can still be sent. Enable it only for traffic you permit your Manifest server to process and store.
106
+ Check your [Manifest dashboard](https://dashboard.manifest.build) to see all repairs and insights.
87
107
 
88
108
  ## More
89
109
 
90
- [Configuration, limits & development](docs/guide.md) · [API contract](CONTRACT.md) · [Python SDK](https://github.com/mnfst/manifest-python)
110
+ [Configuration, limits & development](docs/guide.md) · [API contract](CONTRACT.md) · [Python SDK](https://github.com/mnfst/manifest-python) · [Website](https://manifest.build)
package/dist/bin.cjs ADDED
@@ -0,0 +1,410 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/cli.ts
27
+ var import_node_module = require("module");
28
+ var import_node_fs = require("fs");
29
+ var import_promises = require("fs/promises");
30
+ var import_node_path = __toESM(require("path"), 1);
31
+
32
+ // src/wire.ts
33
+ var isObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
34
+
35
+ // src/api.ts
36
+ var VERSION = "7.1.0";
37
+
38
+ // src/cli.ts
39
+ var DEFAULT_URL = "https://api.manifest.build";
40
+ var REQUEST_TIMEOUT_MS = 1e4;
41
+ var symbols = {
42
+ ok: "\u2705",
43
+ fail: "\u274C",
44
+ warn: "\u26A0\uFE0F",
45
+ skip: "\u2796"
46
+ };
47
+ function maskKey(key) {
48
+ const tail = key.slice(-4);
49
+ if (key.length <= tail.length + 2) return "\u2026";
50
+ const prefix = /^(?:mnfst(?:_[a-z]+)*_)/i.exec(key)?.[0] ?? key.slice(0, 4);
51
+ const head = prefix.length + tail.length < key.length ? prefix : "";
52
+ return `${head}\u2026${tail}`;
53
+ }
54
+ function normalizeBase(raw) {
55
+ try {
56
+ const url = new URL(raw);
57
+ if (!["https:", "http:"].includes(url.protocol) || url.username || url.password || url.search || url.hash)
58
+ return null;
59
+ if (!url.pathname.endsWith("/")) url.pathname += "/";
60
+ return url.toString();
61
+ } catch {
62
+ return null;
63
+ }
64
+ }
65
+ async function readJson(response) {
66
+ try {
67
+ const text = await response.text();
68
+ return text.length > 1e6 ? null : JSON.parse(text);
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
73
+ function projectName(body) {
74
+ if (!isObject(body)) return void 0;
75
+ const candidate = body.project ?? body.projectName ?? body.project_name ?? body.name;
76
+ if (typeof candidate === "string" && candidate.trim())
77
+ return candidate.trim();
78
+ if (isObject(candidate) && typeof candidate.name === "string" && candidate.name.trim())
79
+ return candidate.name.trim();
80
+ return void 0;
81
+ }
82
+ function requestCount(body) {
83
+ if (!isObject(body)) return void 0;
84
+ const candidate = body.requests ?? body.requestCount ?? body.requestsCount ?? body.requests_count ?? body.request_count;
85
+ if (typeof candidate === "number") return candidate;
86
+ if (isObject(candidate) && typeof candidate.total === "number")
87
+ return candidate.total;
88
+ return void 0;
89
+ }
90
+ function headers(key) {
91
+ return {
92
+ authorization: `Bearer ${key}`,
93
+ "content-type": "application/json",
94
+ "user-agent": `mnfst-node/${VERSION}`
95
+ };
96
+ }
97
+ async function probe(url, key, doFetch) {
98
+ const controller = new AbortController();
99
+ const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
100
+ try {
101
+ const response = await doFetch(new URL("v1/hello", url), {
102
+ method: "POST",
103
+ headers: headers(key),
104
+ // `probe` marks this a key check, not a boot: the server answers but
105
+ // records no install. Without it, running doctor from a laptop makes
106
+ // the dashboard claim the app is connected — while doctor is printing
107
+ // "manifest is not installed in this project" two lines above.
108
+ body: JSON.stringify({ probe: true }),
109
+ signal: controller.signal,
110
+ redirect: "error"
111
+ });
112
+ const body = await readJson(response);
113
+ if (response.status === 200)
114
+ return {
115
+ kind: "valid",
116
+ project: projectName(body),
117
+ requests: requestCount(body)
118
+ };
119
+ if (response.status === 401)
120
+ return { kind: "invalid", detail: "the key was rejected (401)" };
121
+ if (response.status === 403)
122
+ return {
123
+ kind: "invalid",
124
+ detail: isObject(body) && body.error === "project_disabled" ? "the key is valid but the project is disabled (403)" : "the key was rejected (403)"
125
+ };
126
+ return {
127
+ kind: "error",
128
+ detail: `unexpected response (${response.status})`
129
+ };
130
+ } catch (error) {
131
+ const timedOut = error instanceof Error && error.name === "AbortError";
132
+ return {
133
+ kind: "error",
134
+ detail: `${timedOut ? "timed out" : "could not be reached"} at ${url}`
135
+ };
136
+ } finally {
137
+ clearTimeout(timer);
138
+ }
139
+ }
140
+ function installedVersion(cwd) {
141
+ try {
142
+ const entry = (0, import_node_module.createRequire)(import_node_path.default.join(cwd, "package.json")).resolve(
143
+ "manifest"
144
+ );
145
+ let dir = import_node_path.default.dirname(entry);
146
+ while (true) {
147
+ const manifest = import_node_path.default.join(dir, "package.json");
148
+ if ((0, import_node_fs.existsSync)(manifest)) {
149
+ const pkg = JSON.parse((0, import_node_fs.readFileSync)(manifest, "utf8"));
150
+ if (pkg.name === "manifest" && pkg.version) return pkg.version;
151
+ }
152
+ const parent = import_node_path.default.dirname(dir);
153
+ if (parent === dir) return void 0;
154
+ dir = parent;
155
+ }
156
+ } catch {
157
+ return void 0;
158
+ }
159
+ }
160
+ async function nearestPackage(cwd) {
161
+ let dir = import_node_path.default.resolve(cwd);
162
+ while (true) {
163
+ const file = import_node_path.default.join(dir, "package.json");
164
+ if ((0, import_node_fs.existsSync)(file)) {
165
+ try {
166
+ return {
167
+ dir,
168
+ pkg: JSON.parse(await (0, import_promises.readFile)(file, "utf8"))
169
+ };
170
+ } catch {
171
+ return void 0;
172
+ }
173
+ }
174
+ const parent = import_node_path.default.dirname(dir);
175
+ if (parent === dir) return void 0;
176
+ dir = parent;
177
+ }
178
+ }
179
+ var instrumentationFiles = [
180
+ "instrumentation.ts",
181
+ "instrumentation.js",
182
+ "instrumentation.mjs",
183
+ "src/instrumentation.ts",
184
+ "src/instrumentation.js",
185
+ "src/instrumentation.mjs"
186
+ ];
187
+ async function loadCheck(cwd) {
188
+ const label = "Loads before your app";
189
+ const found = await nearestPackage(cwd);
190
+ if (!found)
191
+ return {
192
+ label,
193
+ status: "warn",
194
+ detail: "no package.json found; cannot tell how Manifest loads"
195
+ };
196
+ const scripts = isObject(found.pkg.scripts) ? found.pkg.scripts : {};
197
+ const values = Object.values(scripts).filter(
198
+ (value) => typeof value === "string"
199
+ );
200
+ if (values.some((value) => value.includes("manifest/register")))
201
+ return {
202
+ label,
203
+ status: "ok",
204
+ detail: "a script preloads manifest/register"
205
+ };
206
+ const dependencies = {
207
+ ...isObject(found.pkg.dependencies) ? found.pkg.dependencies : {},
208
+ ...isObject(found.pkg.devDependencies) ? found.pkg.devDependencies : {}
209
+ };
210
+ if ("next" in dependencies) {
211
+ for (const relative of instrumentationFiles) {
212
+ const file = import_node_path.default.join(found.dir, relative);
213
+ if (!(0, import_node_fs.existsSync)(file)) continue;
214
+ if (/manifest/.test(await (0, import_promises.readFile)(file, "utf8")))
215
+ return {
216
+ label,
217
+ status: "ok",
218
+ detail: `${relative} installs Manifest before the app runs`
219
+ };
220
+ }
221
+ return {
222
+ label,
223
+ status: "fail",
224
+ detail: 'package.json "start" relies on Next.js with no NODE_OPTIONS and no instrumentation file \u2014 Manifest will not load'
225
+ };
226
+ }
227
+ const start = scripts.start;
228
+ if (typeof start === "string" && start.trim())
229
+ return {
230
+ label,
231
+ status: "warn",
232
+ detail: "cannot tell from here whether manifest() runs; check Requests received"
233
+ };
234
+ return {
235
+ label,
236
+ status: "warn",
237
+ detail: "no start script found; cannot confirm Manifest loads before your app"
238
+ };
239
+ }
240
+ function sdkCheck(cwd, injected) {
241
+ const label = "SDK installed";
242
+ const version = injected ?? installedVersion(cwd);
243
+ if (!version)
244
+ return {
245
+ label,
246
+ status: "fail",
247
+ detail: "manifest is not installed in this project"
248
+ };
249
+ return { label, status: "ok", detail: `manifest ${version}` };
250
+ }
251
+ async function runDoctor(options = {}) {
252
+ const cwd = options.cwd ?? process.cwd();
253
+ const env = options.env ?? process.env;
254
+ const doFetch = options.fetch ?? globalThis.fetch;
255
+ const checks = [sdkCheck(cwd, options.sdkVersion)];
256
+ const rawUrl = options.url ?? env.MNFST_URL ?? DEFAULT_URL;
257
+ const url = normalizeBase(rawUrl);
258
+ const key = env.MNFST_KEY;
259
+ if (!key) {
260
+ checks.push({
261
+ label: "MNFST_KEY set",
262
+ status: "fail",
263
+ detail: "MNFST_KEY is not set"
264
+ });
265
+ } else {
266
+ checks.push({ label: "MNFST_KEY set", status: "ok", detail: maskKey(key) });
267
+ }
268
+ let requests;
269
+ if (!url) {
270
+ checks.push({
271
+ label: "Key valid",
272
+ status: "fail",
273
+ detail: `invalid Manifest URL: ${rawUrl}`
274
+ });
275
+ } else if (!key) {
276
+ checks.push({ label: "Key valid", status: "skip", detail: "" });
277
+ } else {
278
+ const result = await probe(url, key, doFetch);
279
+ if (result.kind === "valid") {
280
+ requests = result.requests;
281
+ checks.push({
282
+ label: "Key valid",
283
+ status: "ok",
284
+ detail: result.project ? `project "${result.project}"` : "the server accepted the key"
285
+ });
286
+ } else {
287
+ checks.push({
288
+ label: "Key valid",
289
+ status: "fail",
290
+ detail: result.detail
291
+ });
292
+ }
293
+ }
294
+ checks.push(await loadCheck(cwd));
295
+ if (requests !== void 0) {
296
+ checks.push({
297
+ label: "Requests received",
298
+ status: requests > 0 ? "ok" : "warn",
299
+ detail: requests > 0 ? `${requests} request${requests === 1 ? "" : "s"} received` : "no requests received yet"
300
+ });
301
+ }
302
+ return { checks, ok: checks.every((check) => check.status !== "fail") };
303
+ }
304
+ function wrap(text, width) {
305
+ if (!text) return [""];
306
+ const lines = [];
307
+ let line = "";
308
+ for (const word of text.split(" ")) {
309
+ if (!line) line = word;
310
+ else if (line.length + 1 + word.length <= width) line += ` ${word}`;
311
+ else {
312
+ lines.push(line);
313
+ line = word;
314
+ }
315
+ }
316
+ if (line) lines.push(line);
317
+ return lines;
318
+ }
319
+ function render(report) {
320
+ const width = Math.max(
321
+ 20,
322
+ ...report.checks.map((check) => check.label.length)
323
+ );
324
+ const lines = [];
325
+ for (const check of report.checks) {
326
+ const left = ` ${symbols[check.status]} ${check.label.padEnd(width)}`;
327
+ const detail = wrap(check.detail, Math.max(24, 96 - left.length - 2));
328
+ lines.push(`${left} ${detail[0] ?? ""}`.trimEnd());
329
+ for (const extra of detail.slice(1))
330
+ lines.push(`${" ".repeat(left.length + 2)}${extra}`);
331
+ }
332
+ lines.push("");
333
+ lines.push("Runtime coverage");
334
+ lines.push(
335
+ " Node.js runtime fetch, http.request, https.request, http.get are patched"
336
+ );
337
+ lines.push(
338
+ " Edge runtime not supported \u2014 middleware.ts and Edge route handlers are never covered"
339
+ );
340
+ return `${lines.join("\n")}
341
+ `;
342
+ }
343
+ function parseArgs(argv) {
344
+ const args = { help: false, version: false };
345
+ for (let index = 0; index < argv.length; index++) {
346
+ const arg = argv[index];
347
+ if (arg === "-h" || arg === "--help") args.help = true;
348
+ else if (arg === "-v" || arg === "--version") args.version = true;
349
+ else if (arg === "--url") {
350
+ const value = argv[++index];
351
+ if (!value) throw new Error("--url needs a value");
352
+ args.url = value;
353
+ } else if (arg.startsWith("--url=")) args.url = arg.slice("--url=".length);
354
+ else if (arg.startsWith("-")) throw new Error(`unknown option: ${arg}`);
355
+ else if (args.command === void 0) args.command = arg;
356
+ else throw new Error(`unexpected argument: ${arg}`);
357
+ }
358
+ return args;
359
+ }
360
+ var HELP = `Usage: manifest <command> [options]
361
+
362
+ Commands
363
+ doctor Check that this project is wired up to Manifest
364
+
365
+ Options
366
+ --url <url> Manifest API base URL (default: $MNFST_URL)
367
+ -h, --help Show this help
368
+ -v, --version Print the SDK version
369
+
370
+ Examples
371
+ npx manifest doctor
372
+ npx manifest doctor --url=https://api.manifest.build
373
+ `;
374
+ async function main(argv = process.argv.slice(2)) {
375
+ let args;
376
+ try {
377
+ args = parseArgs(argv);
378
+ } catch (error) {
379
+ console.error(error instanceof Error ? error.message : String(error));
380
+ console.error(HELP);
381
+ return 1;
382
+ }
383
+ if (args.version) {
384
+ console.log(VERSION);
385
+ return 0;
386
+ }
387
+ if (args.help || !args.command) {
388
+ console.log(HELP);
389
+ return 0;
390
+ }
391
+ if (args.command !== "doctor") {
392
+ console.error(`Unknown command: ${args.command}`);
393
+ console.error(HELP);
394
+ return 1;
395
+ }
396
+ const report = await runDoctor({ url: args.url });
397
+ process.stdout.write(render(report));
398
+ return report.ok ? 0 : 1;
399
+ }
400
+
401
+ // src/bin.ts
402
+ main().then(
403
+ (code) => {
404
+ process.exitCode = code;
405
+ },
406
+ (error) => {
407
+ console.error(error instanceof Error ? error.message : String(error));
408
+ process.exitCode = 1;
409
+ }
410
+ );
package/dist/bin.d.cts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/bin.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node