manifest 7.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:
@@ -21,7 +33,7 @@ A successful heal response may contain `status: patched|unverified`, `healAttemp
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. 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. Incomplete or malformed request captures and incomplete 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,10 +1,48 @@
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)
4
10
  [![npm version](https://img.shields.io/npm/v/manifest?label=npm)](https://www.npmjs.com/package/manifest)
5
11
  [![npm downloads](https://img.shields.io/npm/dm/manifest?label=npm%20downloads)](https://www.npmjs.com/package/manifest)
6
12
 
7
- **An API rejects your request. Manifest fixes it and retries. You do nothing.**
13
+ </div>
14
+
15
+ ---
16
+
17
+ ## What is Manifest
18
+
19
+ Manifest is a self-healing layer that fixes and retries failed API requests on the fly.
20
+
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)
28
+
29
+ ## Prerequisites
30
+
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
8
46
 
9
47
  ```sh
10
48
  npm install manifest
@@ -13,31 +51,40 @@ npm install manifest
13
51
  ```js
14
52
  import { manifest } from 'manifest';
15
53
 
16
- manifest(); // once, at startup
54
+ manifest(); // Once, at startup.
17
55
  // Keep making your API calls as usual.
18
56
  ```
19
57
 
20
- Works with built-in `fetch`, `node:http`, `node:https` and Axios, for JSON and form-urlencoded bodies. Node.js 22+. TypeScript, ESM and CommonJS. Zero dependencies.
21
-
22
- ![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/healing-diagram.svg)
58
+ TypeScript, ESM and CommonJS. Zero dependencies.
23
59
 
24
60
  ## Setup
25
61
 
26
- 1. Create a project in your Manifest dashboard and copy its project key.
27
- 2. Turn on **Autofix** in **Project Settings**.
28
- 3. Set the key:
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:
29
64
 
30
65
  ```sh
31
66
  export MNFST_KEY='your-project-key'
32
67
  ```
33
68
 
34
- Call `manifest()` before any library grabs its own reference to `fetch` or `node:http`. To install before any of your modules run, preload it instead:
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:
35
78
 
36
79
  ```sh
37
- node --import manifest/register app.js
80
+ npx manifest doctor
38
81
  ```
39
82
 
40
- ## See it work
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.
84
+
85
+ ## Try it
86
+
87
+ Send a request that would normally fail. Manifest catches it, repairs it, and retries:
41
88
 
42
89
  ```js
43
90
  import { manifest } from 'manifest';
@@ -51,17 +98,13 @@ manifest({
51
98
  const res = await fetch('https://api.example.com/orders', {
52
99
  method: 'POST',
53
100
  headers: { 'content-type': 'application/json' },
54
- body: JSON.stringify({ limit: 500 }), // rejected? Manifest retries with a valid limit
101
+ body: JSON.stringify({ limit: 500 }), // Invalid? Manifest fixes it and retries.
55
102
  });
103
+ console.log(res.status); // See the 200 OK response.
56
104
  ```
57
105
 
58
- ## Good to know
59
-
60
- - **Retries repeat side effects.** Use idempotency keys on non-idempotent calls.
61
- - **A heal adds up to 60 s** to a failed request. Successful requests are untouched.
62
- - **Not intercepted:** browsers, HTTP/2, and directly imported `undici`.
63
- - **Privacy.** Failed URLs, headers, JSON or form-urlencoded bodies, and error responses are sent to Manifest. Known credentials are masked, but nested secrets and business data are not. Enable it only for traffic you allow Manifest to process.
106
+ Check your [Manifest dashboard](https://dashboard.manifest.build) to see all repairs and insights.
64
107
 
65
108
  ## More
66
109
 
67
- [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