@marina-cloud/cli 0.0.3 → 0.0.5

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 (3) hide show
  1. package/README.md +37 -7
  2. package/dist/marina.mjs +2822 -451
  3. package/package.json +10 -6
package/dist/marina.mjs CHANGED
@@ -1,8 +1,112 @@
1
1
  #!/usr/bin/env node
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res, err2) => function __init() {
9
+ if (err2) throw err2[0];
10
+ try {
11
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
12
+ } catch (e) {
13
+ throw err2 = [e], e;
14
+ }
15
+ };
16
+ var __commonJS = (cb, mod) => function __require() {
17
+ try {
18
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
19
+ } catch (e) {
20
+ throw mod = 0, e;
21
+ }
22
+ };
23
+ var __export = (target, all) => {
24
+ for (var name in all)
25
+ __defProp(target, name, { get: all[name], enumerable: true });
26
+ };
27
+ var __copyProps = (to, from, except, desc) => {
28
+ if (from && typeof from === "object" || typeof from === "function") {
29
+ for (let key of __getOwnPropNames(from))
30
+ if (!__hasOwnProp.call(to, key) && key !== except)
31
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
32
+ }
33
+ return to;
34
+ };
35
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
36
+ // If the importer is in node compatibility mode or this is not an ESM
37
+ // file that has been converted to a CommonJS file using a Babel-
38
+ // compatible transform (i.e. "__esModule" has not been set), then set
39
+ // "default" to the CommonJS "module.exports" for node compatibility.
40
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
41
+ mod
42
+ ));
2
43
 
3
- // src/index.ts
4
- import { resolve } from "node:path";
5
- import { parseArgs } from "node:util";
44
+ // package.json
45
+ var package_default;
46
+ var init_package = __esm({
47
+ "package.json"() {
48
+ package_default = {
49
+ name: "@marina-cloud/cli",
50
+ version: "0.0.5",
51
+ description: "Command-line client for Marina Cloud",
52
+ homepage: "https://github.com/marina-hq/marina#readme",
53
+ bugs: {
54
+ url: "https://github.com/marina-hq/marina/issues"
55
+ },
56
+ license: "Apache-2.0",
57
+ repository: {
58
+ type: "git",
59
+ url: "git+https://github.com/marina-hq/marina.git",
60
+ directory: "packages/cli"
61
+ },
62
+ bin: {
63
+ marina: "dist/marina.mjs"
64
+ },
65
+ files: [
66
+ "dist"
67
+ ],
68
+ type: "module",
69
+ publishConfig: {
70
+ access: "public",
71
+ provenance: true
72
+ },
73
+ scripts: {
74
+ build: 'esbuild src/index.ts --bundle --platform=node --target=node22 --format=esm --loader:.md=text --loader:.yaml=text --loader:.html=text --loader:.css=text --loader:.txt=text --external:@electric-sql/pglite --external:esbuild --outfile=dist/marina.mjs --banner:js="#!/usr/bin/env node" && chmod +x dist/marina.mjs',
75
+ typecheck: "tsc --noEmit",
76
+ test: "pnpm build && node --experimental-strip-types --test src/*.test.ts",
77
+ prepack: "pnpm build"
78
+ },
79
+ dependencies: {
80
+ "@electric-sql/pglite": "^0.3.6",
81
+ esbuild: "^0.28.2",
82
+ ignore: "^7.0.6"
83
+ },
84
+ devDependencies: {
85
+ "@types/node": "26.2.0",
86
+ fflate: "^0.8.3",
87
+ typescript: "7.0.2"
88
+ },
89
+ engines: {
90
+ node: ">=22"
91
+ }
92
+ };
93
+ }
94
+ });
95
+
96
+ // src/identity.ts
97
+ var CLI_VERSION, cliRequestHeaders;
98
+ var init_identity = __esm({
99
+ "src/identity.ts"() {
100
+ "use strict";
101
+ init_package();
102
+ CLI_VERSION = package_default.version;
103
+ cliRequestHeaders = () => ({
104
+ "user-agent": `marina-cli/${CLI_VERSION}`,
105
+ "x-marina-client": "cli",
106
+ "x-marina-client-version": CLI_VERSION
107
+ });
108
+ }
109
+ });
6
110
 
7
111
  // src/config.ts
8
112
  import {
@@ -16,15 +120,81 @@ import {
16
120
  } from "node:fs";
17
121
  import { homedir } from "node:os";
18
122
  import { join } from "node:path";
19
- var MARINA_HOME = process.env.MARINA_HOME ?? join(homedir(), ".marina");
20
- var PROFILE = join(MARINA_HOME, "profile");
21
- var apiUrl = () => (process.env.MARINA_API ?? "https://v1.marina.cloud").replace(/\/$/, "");
22
- var dashboardUrl = () => {
23
- if (process.env.MARINA_DASHBOARD) return process.env.MARINA_DASHBOARD.replace(/\/$/, "");
24
- const api = new URL(apiUrl());
25
- return api.hostname === "localhost" || api.hostname === "127.0.0.1" ? "http://localhost:5173" : api.origin;
26
- };
27
- var profilePath = () => PROFILE;
123
+ function normalizeApiUrl(value) {
124
+ const url = new URL(value);
125
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
126
+ throw new Error("the Marina control plane must use http or https");
127
+ }
128
+ if (url.username || url.password || url.search || url.hash || url.pathname !== "/") {
129
+ throw new Error("the Marina control plane must be an origin without a path");
130
+ }
131
+ if (url.protocol === "http:" && url.hostname !== "localhost" && url.hostname !== "127.0.0.1" && url.hostname !== "[::1]") {
132
+ throw new Error("the Marina control plane must use https unless it is local");
133
+ }
134
+ return url.origin;
135
+ }
136
+ function cachedControlPlane(host = controlPlaneHost()) {
137
+ const cached = readProfile().control_plane;
138
+ if (!cached || cached.host !== host) return null;
139
+ try {
140
+ normalizeApiUrl(cached.api_url);
141
+ normalizeApiUrl(cached.dashboard_url);
142
+ return cached;
143
+ } catch {
144
+ return null;
145
+ }
146
+ }
147
+ async function resolveControlPlane() {
148
+ const host = controlPlaneHost();
149
+ const cached = cachedControlPlane(host);
150
+ const checkedAt = cached ? Date.parse(cached.checked_at) : Number.NaN;
151
+ if (cached && Number.isFinite(checkedAt) && Date.now() - checkedAt < CONTROL_PLANE_CACHE_MS) {
152
+ resolvedControlPlane = {
153
+ api_url: cached.api_url,
154
+ dashboard_url: cached.dashboard_url,
155
+ latest_cli_version: cached.latest_cli_version
156
+ };
157
+ return;
158
+ }
159
+ if (process.env.MARINA_DISABLE_CONTROL_PLANE_DISCOVERY === "1") return;
160
+ try {
161
+ const response = await fetch(`${host}/.well-known/marina`, {
162
+ headers: { ...cliRequestHeaders(), accept: "application/json" },
163
+ signal: AbortSignal.timeout(3e3)
164
+ });
165
+ if (!response.ok)
166
+ throw new Error(`control-plane discovery failed (${String(response.status)})`);
167
+ const body = await response.json();
168
+ if (body.schema_version !== 1 || typeof body.api_url !== "string" || typeof body.dashboard_url !== "string") {
169
+ throw new Error("control-plane discovery returned an invalid response");
170
+ }
171
+ const discovered = {
172
+ api_url: normalizeApiUrl(body.api_url),
173
+ dashboard_url: normalizeApiUrl(body.dashboard_url),
174
+ latest_cli_version: typeof body.clients?.cli?.latest_version === "string" ? body.clients.cli.latest_version : null
175
+ };
176
+ resolvedControlPlane = discovered;
177
+ writeProfile({
178
+ ...readProfile(),
179
+ control_plane: {
180
+ host,
181
+ checked_at: (/* @__PURE__ */ new Date()).toISOString(),
182
+ ...discovered
183
+ }
184
+ });
185
+ } catch {
186
+ if (cached) {
187
+ resolvedControlPlane = {
188
+ api_url: cached.api_url,
189
+ dashboard_url: cached.dashboard_url,
190
+ latest_cli_version: cached.latest_cli_version
191
+ };
192
+ }
193
+ }
194
+ }
195
+ function latestCliVersion() {
196
+ return resolvedControlPlane?.latest_cli_version ?? cachedControlPlane()?.latest_cli_version ?? null;
197
+ }
28
198
  function readProfile() {
29
199
  try {
30
200
  return JSON.parse(readFileSync(PROFILE, "utf8"));
@@ -60,7 +230,6 @@ function clearToken() {
60
230
  }
61
231
  writeProfile(rest);
62
232
  }
63
- var LINK = ".marina/project.json";
64
233
  function readLink(dir) {
65
234
  try {
66
235
  return JSON.parse(readFileSync(join(dir, LINK), "utf8"));
@@ -68,135 +237,2295 @@ function readLink(dir) {
68
237
  return null;
69
238
  }
70
239
  }
71
- function writeLink(dir, link) {
72
- mkdirSync(join(dir, ".marina"), { recursive: true });
73
- writeFileSync(join(dir, LINK), `${JSON.stringify(link, null, 2)}
74
- `);
75
- }
240
+ function writeLink(dir, link) {
241
+ mkdirSync(join(dir, ".marina"), { recursive: true });
242
+ writeFileSync(join(dir, LINK), `${JSON.stringify(link, null, 2)}
243
+ `);
244
+ }
245
+ var MARINA_HOME, PROFILE, PRODUCTION_CONTROL_PLANE, CONTROL_PLANE_CACHE_MS, resolvedControlPlane, controlPlaneHost, apiUrl, dashboardUrl, profilePath, LINK;
246
+ var init_config = __esm({
247
+ "src/config.ts"() {
248
+ "use strict";
249
+ init_identity();
250
+ MARINA_HOME = process.env.MARINA_HOME ?? join(homedir(), ".marina");
251
+ PROFILE = join(MARINA_HOME, "profile");
252
+ PRODUCTION_CONTROL_PLANE = "https://marina.cloud";
253
+ CONTROL_PLANE_CACHE_MS = 60 * 60 * 1e3;
254
+ resolvedControlPlane = null;
255
+ controlPlaneHost = () => normalizeApiUrl(
256
+ process.env.MARINA_CONTROL_PLANE ?? readProfile().control_plane_host ?? PRODUCTION_CONTROL_PLANE
257
+ );
258
+ apiUrl = () => normalizeApiUrl(
259
+ process.env.MARINA_API ?? resolvedControlPlane?.api_url ?? cachedControlPlane()?.api_url ?? controlPlaneHost()
260
+ );
261
+ dashboardUrl = () => {
262
+ if (process.env.MARINA_DASHBOARD) return normalizeApiUrl(process.env.MARINA_DASHBOARD);
263
+ return normalizeApiUrl(
264
+ resolvedControlPlane?.dashboard_url ?? cachedControlPlane()?.dashboard_url ?? controlPlaneHost()
265
+ );
266
+ };
267
+ profilePath = () => PROFILE;
268
+ LINK = ".marina/project.json";
269
+ }
270
+ });
271
+
272
+ // src/api.ts
273
+ async function request(path, init) {
274
+ const token = getToken();
275
+ if (!token) throw new ApiError("unauthenticated", "not signed in \u2014 run `marina setup`", 401);
276
+ const res = await fetch(`${apiUrl()}${path}`, {
277
+ ...init,
278
+ headers: { ...cliRequestHeaders(), authorization: `Bearer ${token}`, ...init?.headers }
279
+ });
280
+ const body = await res.json().catch(() => ({}));
281
+ if (!res.ok) {
282
+ throw new ApiError(
283
+ body.error?.code ?? "error",
284
+ body.error?.message ?? `request failed (${String(res.status)})`,
285
+ res.status
286
+ );
287
+ }
288
+ return body;
289
+ }
290
+ async function me() {
291
+ return request("/v1/me");
292
+ }
293
+ async function exchangeCliLogin(code, codeVerifier) {
294
+ let res;
295
+ try {
296
+ res = await fetch(`${apiUrl()}/cli/auth/exchange`, {
297
+ method: "POST",
298
+ headers: { ...cliRequestHeaders(), "content-type": "application/json" },
299
+ body: JSON.stringify({ code, code_verifier: codeVerifier }),
300
+ signal: AbortSignal.timeout(LOGIN_EXCHANGE_TIMEOUT_MS)
301
+ });
302
+ } catch (error) {
303
+ if (error.name === "TimeoutError") {
304
+ throw new ApiError(
305
+ "login_timeout",
306
+ "the login confirmation request timed out after 15 seconds \u2014 run `marina setup` to try again",
307
+ 408
308
+ );
309
+ }
310
+ throw error;
311
+ }
312
+ const body = await res.json().catch(() => ({}));
313
+ if (!res.ok || !body.token) {
314
+ throw new ApiError(
315
+ body.error?.code ?? "login_failed",
316
+ body.error?.message ?? `login exchange failed (${String(res.status)})`,
317
+ res.status
318
+ );
319
+ }
320
+ return body.token;
321
+ }
322
+ async function startDeploy(zip, name, app, baseRevision) {
323
+ const form = new FormData();
324
+ form.set(
325
+ "code",
326
+ new Blob([new Uint8Array(zip).buffer], { type: "application/zip" }),
327
+ "upload.zip"
328
+ );
329
+ form.set("name", name);
330
+ form.set("source", "cli");
331
+ if (app) form.set("app", app);
332
+ if (baseRevision) form.set("base_revision", baseRevision);
333
+ const res = await request("/v1/deploys", {
334
+ method: "POST",
335
+ body: form
336
+ });
337
+ return res.deploy;
338
+ }
339
+ async function pollDeploy(id, onProgress = () => void 0) {
340
+ for (; ; ) {
341
+ const deploy2 = await getDeploy(id);
342
+ onProgress(deploy2);
343
+ if (deploy2.status !== "queued" && deploy2.status !== "building") return deploy2;
344
+ await new Promise((resolve4) => setTimeout(resolve4, 500));
345
+ }
346
+ }
347
+ async function getDeploy(id) {
348
+ const res = await request(`/v1/deploys/${encodeURIComponent(id)}`);
349
+ return res.deploy;
350
+ }
351
+ async function listApps() {
352
+ const res = await request("/v1/apps");
353
+ return res.apps;
354
+ }
355
+ async function getAppUrl(idOrSlug) {
356
+ return (await getApp(idOrSlug)).url;
357
+ }
358
+ async function listVersions(app) {
359
+ const res = await request(
360
+ `/v1/apps/${encodeURIComponent(app)}/versions`
361
+ );
362
+ return res.versions;
363
+ }
364
+ async function listDeploys(app, limit = 10) {
365
+ const res = await request(
366
+ `/v1/apps/${encodeURIComponent(app)}/deploys?limit=${String(limit)}`
367
+ );
368
+ return res.deploys;
369
+ }
370
+ async function listRuntimeLogs(app, options = {}) {
371
+ const query = new URLSearchParams();
372
+ query.set("limit", String(options.limit ?? 100));
373
+ if (options.cursor) query.set("cursor", options.cursor);
374
+ if (options.level) query.set("level", options.level);
375
+ const response = await request(
376
+ `/v1/apps/${encodeURIComponent(app)}/logs?${query.toString()}`
377
+ );
378
+ return { logs: response.logs, nextCursor: response.next_cursor };
379
+ }
380
+ async function restoreVersion(versionId) {
381
+ const res = await request(`/v1/versions/${versionId}/restore`, {
382
+ method: "POST"
383
+ });
384
+ return res.version;
385
+ }
386
+ var LOGIN_EXCHANGE_TIMEOUT_MS, ApiError, aiModels, getApp;
387
+ var init_api = __esm({
388
+ "src/api.ts"() {
389
+ "use strict";
390
+ init_config();
391
+ init_identity();
392
+ LOGIN_EXCHANGE_TIMEOUT_MS = 15e3;
393
+ ApiError = class extends Error {
394
+ code;
395
+ status;
396
+ constructor(code, message, status2) {
397
+ super(message);
398
+ this.code = code;
399
+ this.status = status2;
400
+ }
401
+ };
402
+ aiModels = () => request("/v1/ai/models");
403
+ getApp = (idOrSlug) => request(`/v1/apps/${encodeURIComponent(idOrSlug)}`);
404
+ }
405
+ });
406
+
407
+ // src/output.ts
408
+ function terminalSafeText(value) {
409
+ return Array.from(value, (character) => {
410
+ const code = character.codePointAt(0) ?? 0;
411
+ if (code >= 32 && (code < 127 || code > 159)) return character;
412
+ if (code === 9) return "\\t";
413
+ if (code === 10) return "\\n";
414
+ if (code === 13) return "\\r";
415
+ return `\\u{${code.toString(16).padStart(4, "0")}}`;
416
+ }).join("");
417
+ }
418
+ function terminalSafeJson(payload, space) {
419
+ return Array.from(JSON.stringify(payload, null, space), (character) => {
420
+ const code = character.codePointAt(0) ?? 0;
421
+ return code >= 127 && code <= 159 ? `\\u${code.toString(16).padStart(4, "0")}` : character;
422
+ }).join("");
423
+ }
424
+ function say(line = "") {
425
+ if (json) console.error(line);
426
+ else console.log(line);
427
+ }
428
+ function note(line) {
429
+ console.error(line);
430
+ }
431
+ function createProgress() {
432
+ let active = false;
433
+ let lastPhase = null;
434
+ let frame = 0;
435
+ const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
436
+ const interactive = process.stderr.isTTY === true && !json;
437
+ return {
438
+ update(phase, line) {
439
+ if (interactive) {
440
+ process.stderr.write(`\r\x1B[2K${frames[frame++ % frames.length]} ${line}`);
441
+ active = true;
442
+ } else if (phase !== lastPhase) {
443
+ console.error(json ? terminalSafeJson({ type: "progress", phase, message: line }) : line);
444
+ }
445
+ lastPhase = phase;
446
+ },
447
+ clear() {
448
+ if (active) process.stderr.write("\r\x1B[2K");
449
+ active = false;
450
+ }
451
+ };
452
+ }
453
+ function result(payload) {
454
+ if (json) console.log(terminalSafeJson({ schema_version: 1, ok: true, ...payload }, 2));
455
+ }
456
+ function failure(code, message, extra = {}) {
457
+ if (json)
458
+ console.log(
459
+ terminalSafeJson({ schema_version: 1, ok: false, error: { code, message, ...extra } }, 2)
460
+ );
461
+ else {
462
+ console.error(`${red(terminalSafeText(code))} ${terminalSafeText(message)}`);
463
+ }
464
+ }
465
+ var json, setJsonMode, isJsonMode, bold, dim, red, green;
466
+ var init_output = __esm({
467
+ "src/output.ts"() {
468
+ "use strict";
469
+ json = false;
470
+ setJsonMode = (on) => {
471
+ json = on;
472
+ };
473
+ isJsonMode = () => json;
474
+ bold = (s) => `\x1B[1m${s}\x1B[22m`;
475
+ dim = (s) => `\x1B[2m${s}\x1B[22m`;
476
+ red = (s) => `\x1B[31m${s}\x1B[39m`;
477
+ green = (s) => `\x1B[32m${s}\x1B[39m`;
478
+ }
479
+ });
480
+
481
+ // ../../node_modules/.pnpm/ignore@7.0.8/node_modules/ignore/index.js
482
+ var require_ignore = __commonJS({
483
+ "../../node_modules/.pnpm/ignore@7.0.8/node_modules/ignore/index.js"(exports, module) {
484
+ function makeArray(subject) {
485
+ return Array.isArray(subject) ? subject : [subject];
486
+ }
487
+ var UNDEFINED = void 0;
488
+ var EMPTY = "";
489
+ var SPACE = " ";
490
+ var ESCAPE = "\\";
491
+ var REGEX_LITERAL_SPECIAL = /[.*+?()[\]{}^$|\\/]/;
492
+ var REGEX_TEST_BLANK_LINE = /^ +$/;
493
+ var REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/;
494
+ var REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/;
495
+ var REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/;
496
+ var REGEX_SPLITALL_CRLF = /\r?\n/g;
497
+ var DOUBLE_SLASH = "//";
498
+ var SLASH_CODE = 47;
499
+ var DOT_CODE = 46;
500
+ var SLASH = "/";
501
+ var TMP_KEY_IGNORE = "node-ignore";
502
+ if (typeof Symbol !== "undefined") {
503
+ TMP_KEY_IGNORE = /* @__PURE__ */ Symbol.for("node-ignore");
504
+ }
505
+ var KEY_IGNORE = TMP_KEY_IGNORE;
506
+ var define = (object, key, value) => {
507
+ Object.defineProperty(object, key, { value });
508
+ return value;
509
+ };
510
+ var RETURN_FALSE = () => false;
511
+ var cleanRangeBackSlash = (slashes) => {
512
+ const { length } = slashes;
513
+ return slashes.slice(0, length - length % 2);
514
+ };
515
+ var POSIX_CLASSES = {
516
+ alnum: "0-9A-Za-z",
517
+ alpha: "A-Za-z",
518
+ blank: " \\t",
519
+ cntrl: "\\x00-\\x1f\\x7f",
520
+ digit: "0-9",
521
+ graph: "!-.0-~",
522
+ lower: "a-z",
523
+ print: " -.0-~",
524
+ punct: "!-.:-@\\[-`{-~",
525
+ // git's `sane-ctype.h` classifies \v and \f as control, not space,
526
+ // unlike C's `isspace`
527
+ space: " \\t\\n\\r",
528
+ upper: "A-Z",
529
+ xdigit: "0-9A-Fa-f"
530
+ };
531
+ var CLASS_MEMBERS_TO_ESCAPE = "\\]^-[";
532
+ var escapeMember = (char) => CLASS_MEMBERS_TO_ESCAPE.indexOf(char) < 0 ? char : ESCAPE + char;
533
+ var NON_SLASH = "(?!\\/)";
534
+ var classSource = (negated, body) => {
535
+ if (negated) {
536
+ return `[^\\/${body}]`;
537
+ }
538
+ const source = `[${body}]`;
539
+ return new RegExp(source).test("/") ? NON_SLASH + source : source;
540
+ };
541
+ var scanBracket = (pattern, start) => {
542
+ const { length } = pattern;
543
+ let index = start + 1;
544
+ let negated = EMPTY;
545
+ const lead = pattern[index];
546
+ if (lead === "!" || lead === "^") {
547
+ negated = "^";
548
+ index++;
549
+ }
550
+ let body = EMPTY;
551
+ let prev = EMPTY;
552
+ for (; ; ) {
553
+ const char = pattern[index];
554
+ if (char === UNDEFINED) {
555
+ return null;
556
+ }
557
+ if (char === ESCAPE) {
558
+ const escaped = pattern[index + 1];
559
+ if (escaped === UNDEFINED) {
560
+ return null;
561
+ }
562
+ body += escapeMember(escaped);
563
+ prev = escaped;
564
+ index++;
565
+ } else if (char === "-" && prev && index + 1 < length && pattern[index + 1] !== "]") {
566
+ index++;
567
+ let to = pattern[index];
568
+ if (to === ESCAPE) {
569
+ to = pattern[index += 1];
570
+ }
571
+ if (prev <= to) {
572
+ body += `-${escapeMember(to)}`;
573
+ }
574
+ prev = EMPTY;
575
+ } else if (char === "[" && pattern[index + 1] === ":") {
576
+ const nameStart = index + 2;
577
+ let end = nameStart;
578
+ while (end < length && pattern[end] !== "]") {
579
+ end++;
580
+ }
581
+ if (end === length) {
582
+ return null;
583
+ }
584
+ if (end > nameStart && pattern[end - 1] === ":") {
585
+ const expanded = POSIX_CLASSES[pattern.slice(nameStart, end - 1)];
586
+ if (expanded === UNDEFINED) {
587
+ return null;
588
+ }
589
+ body += expanded;
590
+ prev = EMPTY;
591
+ index = end;
592
+ } else {
593
+ body += escapeMember("[");
594
+ prev = "[";
595
+ index = nameStart - 2;
596
+ }
597
+ } else {
598
+ body += escapeMember(char);
599
+ prev = char;
600
+ }
601
+ index++;
602
+ if (pattern[index] === "]") {
603
+ return {
604
+ end: index,
605
+ source: classSource(negated, body)
606
+ };
607
+ }
608
+ }
609
+ };
610
+ var NEVER_MATCH = "[]";
611
+ var PLACEHOLDER = "\0";
612
+ var REGEX_RESTORE_PLACEHOLDER = new RegExp(
613
+ `${PLACEHOLDER}(\\d+)${PLACEHOLDER}`,
614
+ "g"
615
+ );
616
+ var TRAILING_WILDCARD = "\uE000";
617
+ var extractBrackets = (pattern) => {
618
+ const sources = [];
619
+ const hold = (source) => `${PLACEHOLDER}${sources.push(source) - 1}${PLACEHOLDER}`;
620
+ const { length } = pattern;
621
+ let out = EMPTY;
622
+ let index = 0;
623
+ while (index < length) {
624
+ const char = pattern[index];
625
+ if (char === ESCAPE) {
626
+ const escaped = pattern[index + 1];
627
+ if (escaped === "*" || escaped === "[" || escaped === SPACE || escaped === ESCAPE) {
628
+ out += pattern.slice(index, index + 2);
629
+ } else {
630
+ out += hold(
631
+ REGEX_LITERAL_SPECIAL.test(escaped) ? ESCAPE + escaped : escaped
632
+ );
633
+ }
634
+ index += 2;
635
+ } else if (char === PLACEHOLDER) {
636
+ out += hold(`[${PLACEHOLDER}]`);
637
+ index++;
638
+ } else if (char === "[") {
639
+ const scanned = scanBracket(pattern, index);
640
+ if (scanned === null) {
641
+ out += hold(NEVER_MATCH);
642
+ index = length;
643
+ } else {
644
+ out += hold(scanned.source);
645
+ index = scanned.end + 1;
646
+ }
647
+ } else {
648
+ out += char;
649
+ index++;
650
+ }
651
+ }
652
+ return {
653
+ source: out,
654
+ sources
655
+ };
656
+ };
657
+ var DIRECT = null;
658
+ var REGEX_INNER_SLASH = /\/(?!$)/;
659
+ var REPLACERS = [
660
+ [
661
+ // Remove BOM
662
+ // TODO:
663
+ // Other similar zero-width characters?
664
+ /^\uFEFF/,
665
+ () => EMPTY,
666
+ "\uFEFF"
667
+ ],
668
+ [
669
+ // A trailing line terminator, left on when a whole file's contents are
670
+ // added as one pattern rather than split into lines. git never sees one
671
+ // -- it reads a `.gitignore` line by line -- so it is not part of the
672
+ // pattern and is dropped here, apart from the trailing-space trimming,
673
+ // which follows git in touching spaces and nothing else.
674
+ /[\r\n]+$/,
675
+ () => EMPTY
676
+ ],
677
+ // > Trailing spaces are ignored unless they are quoted with backslash ("\")
678
+ [
679
+ // Only spaces, never tabs or other whitespace: git trims a trailing run
680
+ // of `' '` and nothing else (dir.c, `trim_trailing_spaces`, a single
681
+ // `case ' '`), so a pattern ending in a tab keeps it as a literal.
682
+ // (a\ ) -> (a )
683
+ // (a ) -> (a)
684
+ // (a ) -> (a)
685
+ // (a \ ) -> (a )
686
+ /((?:\\\\)*?)(\\? +)$/,
687
+ (_, m1, m2) => m1 + (m2.indexOf("\\") === 0 ? SPACE : EMPTY)
688
+ ],
689
+ // Replace (\ ) with ' '
690
+ // Only a space: an escaped tab or other whitespace is already a literal by
691
+ // the time it reaches here, and a bare tab must be left as one, not turned
692
+ // into a space.
693
+ // (\ ) -> ' '
694
+ // (\\ ) -> '\\ '
695
+ // (\\\ ) -> '\\ '
696
+ [
697
+ /(\\+?) /g,
698
+ (_, m1) => {
699
+ const { length } = m1;
700
+ return m1.slice(0, length - length % 2) + SPACE;
701
+ }
702
+ ],
703
+ // Escape metacharacters
704
+ // which is written down by users but means special for regular expressions.
705
+ // > There are 12 characters with special meanings:
706
+ // > - the backslash \,
707
+ // > - the caret ^,
708
+ // > - the dollar sign $,
709
+ // > - the period or dot .,
710
+ // > - the vertical bar or pipe symbol |,
711
+ // > - the question mark ?,
712
+ // > - the asterisk or star *,
713
+ // > - the plus sign +,
714
+ // > - the opening parenthesis (,
715
+ // > - the closing parenthesis ),
716
+ // > - and the opening square bracket [,
717
+ // > - the opening curly brace {,
718
+ // > These special characters are often called "metacharacters".
719
+ [
720
+ /[\\$.|*+(){^]/g,
721
+ (match) => `\\${match}`
722
+ ],
723
+ [
724
+ // > a question mark (?) matches a single character
725
+ /(?!\\)\?/g,
726
+ () => "[^/]",
727
+ "?"
728
+ ],
729
+ // leading slash
730
+ [
731
+ // > A leading slash matches the beginning of the pathname.
732
+ // > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".
733
+ // A leading slash matches the beginning of the pathname
734
+ /^\//,
735
+ () => "^",
736
+ SLASH
737
+ ],
738
+ // replace special metacharacter slash after the leading slash
739
+ [
740
+ /\//g,
741
+ () => "\\/",
742
+ SLASH
743
+ ],
744
+ [
745
+ // > A leading "**" followed by a slash means match in all directories.
746
+ // > For example, "**/foo" matches file or directory "foo" anywhere,
747
+ // > the same as pattern "foo".
748
+ // > "**/foo/bar" matches file or directory "bar" anywhere that is directly
749
+ // > under directory "foo".
750
+ // Notice that the '*'s have been replaced as '\\*'
751
+ /^\^*(?:\\\*\\\*\\\/)+/,
752
+ // '**/foo' <-> 'foo'
753
+ () => "^(?:.*\\/)?",
754
+ "*"
755
+ ],
756
+ // starting
757
+ [
758
+ // there will be no leading '/'
759
+ // (which has been replaced by section "leading slash")
760
+ // If starts with '**', adding a '^' to the regular expression also works
761
+ DIRECT,
762
+ (source, pattern) => {
763
+ if (!source || source[0] === "^") {
764
+ return source;
765
+ }
766
+ const anchor = !REGEX_INNER_SLASH.test(pattern) ? "(?:^|\\/)" : "^";
767
+ return anchor + source;
768
+ }
769
+ ],
770
+ // two globstars
771
+ [
772
+ // Use lookahead assertions so that we could match more than one `'/**'`
773
+ /\\\/\\\*\\\*(?=\\\/|$)/g,
774
+ // Zero, one or several directories
775
+ // should not use '*', or it will be replaced by the next replacer
776
+ // Check if it is not the last `'/**'`
777
+ (_, index, str) => index + 6 < str.length ? str.slice(index + 6) === "\\/" ? "(?:\\/[^\\/]+)+" : "(?:\\/[^\\/]+)*" : "\\/.+",
778
+ "*"
779
+ ],
780
+ // normal intermediate wildcards
781
+ [
782
+ // Never replace escaped '*'
783
+ // ignore rule '\*' will match the path '*'
784
+ // 'abc.*/' -> go
785
+ // 'abc.*' -> skip this rule,
786
+ // coz trailing single wildcard will be handed by [trailing wildcard]
787
+ /(^|[^\\]+)(\\\*)+(?=.+)/g,
788
+ // '*.js' matches '.js'
789
+ // '*.js' doesn't match 'abc'
790
+ (_, p1, p2) => {
791
+ const unescaped = p2.replace(/\\\*/g, "[^\\/]*");
792
+ return p1 + unescaped;
793
+ },
794
+ "*"
795
+ ],
796
+ // trailing wildcard, held apart from a literal star
797
+ [
798
+ // The step above leaves a trailing `*` alone, so a single `\*` is all that
799
+ // can be left at the end here. Whether it is a wildcard or a literal
800
+ // turns on the backslashes the user put in front of it: the escaper has
801
+ // since doubled every one, so what stands here is those `2N` doubled
802
+ // backslashes and then the star's own escape. An even number of the
803
+ // original `N` leaves the star unescaped -- a wildcard -- and an odd
804
+ // number escapes it -- a literal. This runs while the two are still
805
+ // distinct, before the unescape steps below collapse the literal onto
806
+ // the very `\*` a wildcard leaves behind.
807
+ /(^|[^\\])((?:\\\\)*)\\\*$/,
808
+ (match, p1, p2) => (
809
+ // `p2` holds the doubled user backslashes; half of them is `N`.
810
+ p2.length / 2 % 2 === 0 ? p1 + p2 + TRAILING_WILDCARD : match
811
+ ),
812
+ "*"
813
+ ],
814
+ [
815
+ // unescape, revert step 3 except for back slash
816
+ // For example, if a user escape a '\\*',
817
+ // after step 3, the result will be '\\\\\\*'
818
+ /\\\\\\(?=[$.|*+(){^])/g,
819
+ () => ESCAPE,
820
+ ESCAPE + ESCAPE
821
+ ],
822
+ [
823
+ // '\\\\' -> '\\'
824
+ /\\\\/g,
825
+ () => ESCAPE,
826
+ ESCAPE + ESCAPE
827
+ ],
828
+ [
829
+ // Every real bracket expression -- POSIX classes included -- has already
830
+ // been held aside by `extractBrackets`, so the only `[` left in the
831
+ // pattern is an escaped, literal one.
832
+ // `\` is escaped by step 3
833
+ /\\\[([^\]/]*?)(\\*)($|\])/g,
834
+ // '\\[bar]' -> '\\\\[bar\\]'
835
+ (match, range, endEscape, close) => `\\[${range}${cleanRangeBackSlash(endEscape)}${close}`,
836
+ "["
837
+ ],
838
+ // ending
839
+ [
840
+ // 'js' will not match 'js.'
841
+ // 'ab' will not match 'abc'
842
+ DIRECT,
843
+ // WTF!
844
+ // https://git-scm.com/docs/gitignore
845
+ // changes in [2.22.1](https://git-scm.com/docs/gitignore/2.22.1)
846
+ // which re-fixes #24, #38
847
+ // > If there is a separator at the end of the pattern then the pattern
848
+ // > will only match directories, otherwise the pattern can match both
849
+ // > files and directories.
850
+ // 'js*' will not match 'a.js'
851
+ // 'js/' will not match 'a.js'
852
+ // 'js' will match 'a.js' and 'a.js/'
853
+ (source) => {
854
+ const last = source[source.length - 1];
855
+ if (!last || last === TRAILING_WILDCARD) {
856
+ return source;
857
+ }
858
+ return last === SLASH ? `${source}$` : `${source}(?=$|\\/$)`;
859
+ }
860
+ ]
861
+ ];
862
+ var REGEX_REPLACE_TRAILING_WILDCARD = /(^|\\\/)?\uE000$/;
863
+ var MODE_IGNORE = "regex";
864
+ var MODE_CHECK_IGNORE = "checkRegex";
865
+ var UNDERSCORE = "_";
866
+ var TRAILING_WILD_CARD_REPLACERS = {
867
+ [MODE_IGNORE](_, p1) {
868
+ const prefix = p1 ? `${p1}[^/]+` : "[^/]*";
869
+ return `${prefix}(?=$|\\/$)`;
870
+ },
871
+ [MODE_CHECK_IGNORE](_, p1) {
872
+ const prefix = p1 ? `${p1}[^/]*` : "[^/]*";
873
+ return `${prefix}(?=$|\\/$)`;
874
+ }
875
+ };
876
+ var WILDCARD = "[^\\/]*";
877
+ var pinWildcards = (source) => {
878
+ if (source.indexOf(WILDCARD) < 0) {
879
+ return source;
880
+ }
881
+ const tokens = [];
882
+ const { length } = source;
883
+ let index = 0;
884
+ while (index < length) {
885
+ const char = source[index];
886
+ if (source.startsWith(WILDCARD, index)) {
887
+ tokens.push({ wildcard: true });
888
+ index += WILDCARD.length;
889
+ } else if (char === "[") {
890
+ let end = index + 1;
891
+ if (source[end] === "^") {
892
+ end++;
893
+ }
894
+ if (source[end] === "]") {
895
+ end++;
896
+ }
897
+ while (end < length && source[end] !== "]") {
898
+ end += source[end] === ESCAPE ? 2 : 1;
899
+ }
900
+ end++;
901
+ tokens.push({ single: source.slice(index, end) });
902
+ index = end;
903
+ } else if (char === ESCAPE) {
904
+ tokens.push({ single: source.slice(index, index + 2) });
905
+ index += 2;
906
+ } else if (char === "(") {
907
+ let depth = 0;
908
+ let end = index;
909
+ do {
910
+ if (source[end] === ESCAPE) {
911
+ end++;
912
+ } else if (source[end] === "(") {
913
+ depth++;
914
+ } else if (source[end] === ")") {
915
+ depth--;
916
+ }
917
+ end++;
918
+ } while (end < length && depth > 0);
919
+ if ("*+?".indexOf(source[end]) >= 0) {
920
+ end++;
921
+ }
922
+ tokens.push({ boundary: source.slice(index, end) });
923
+ index = end;
924
+ } else if (char === "^" || char === "$") {
925
+ tokens.push({ boundary: char });
926
+ index++;
927
+ } else {
928
+ tokens.push({ single: char });
929
+ index++;
930
+ }
931
+ }
932
+ let out = EMPTY;
933
+ let run2 = [];
934
+ const flush = () => {
935
+ let lastWildcard;
936
+ run2.forEach((token, at) => {
937
+ if (token.wildcard) {
938
+ lastWildcard = at;
939
+ }
940
+ });
941
+ run2.forEach((token, at) => {
942
+ if (!token.wildcard) {
943
+ out += token.single;
944
+ return;
945
+ }
946
+ out += at === lastWildcard ? WILDCARD : `(?:(?!${run2[at + 1].single})[^\\/])*`;
947
+ });
948
+ run2 = [];
949
+ };
950
+ tokens.forEach((token) => {
951
+ if (token.boundary === void 0) {
952
+ run2.push(token);
953
+ return;
954
+ }
955
+ flush();
956
+ out += token.boundary;
957
+ });
958
+ flush();
959
+ return out;
960
+ };
961
+ var makeRegexPrefix = (pattern) => {
962
+ const { source, sources } = extractBrackets(pattern);
963
+ const replaced = REPLACERS.reduce(
964
+ // A pass whose matcher finds nothing hands back the very string it was
965
+ // given, so asking first costs a search and saves a rewrite. Ten of the
966
+ // fifteen passes never fire for a typical .gitignore line, and between
967
+ // them they were 45% of this chain.
968
+ (prev, [matcher, replacer, required]) => {
969
+ if (matcher === DIRECT) {
970
+ return replacer(prev, pattern);
971
+ }
972
+ if (required !== UNDEFINED && prev.indexOf(required) < 0) {
973
+ return prev;
974
+ }
975
+ return matcher.test(prev) ? prev.replace(matcher, replacer.bind(pattern)) : prev;
976
+ },
977
+ source
978
+ );
979
+ return sources.length ? replaced.replace(
980
+ REGEX_RESTORE_PLACEHOLDER,
981
+ (match, index) => sources[index]
982
+ ) : replaced;
983
+ };
984
+ var matchesBasename = (body) => {
985
+ const index = body.indexOf(SLASH);
986
+ return index < 0 || index === body.length - 1;
987
+ };
988
+ var basenameOf = (path) => {
989
+ const end = path.length - 1;
990
+ const index = path.lastIndexOf(
991
+ SLASH,
992
+ path[end] === SLASH ? end - 1 : end
993
+ );
994
+ return index < 0 ? path : path.slice(index + 1);
995
+ };
996
+ var parentOf = (path) => {
997
+ if (path.charCodeAt(0) === SLASH_CODE || path.indexOf(DOUBLE_SLASH) >= 0) {
998
+ const slices = path.split(SLASH).filter(Boolean);
999
+ slices.pop();
1000
+ return slices.length ? slices.join(SLASH) + SLASH : EMPTY;
1001
+ }
1002
+ const end = path.length - 1;
1003
+ const cut = path.lastIndexOf(
1004
+ SLASH,
1005
+ path.charCodeAt(end) === SLASH_CODE ? end - 1 : end
1006
+ );
1007
+ return cut < 0 ? EMPTY : path.slice(0, cut + 1);
1008
+ };
1009
+ var isString = (subject) => typeof subject === "string";
1010
+ var checkPattern = (pattern) => pattern && isString(pattern) && !REGEX_TEST_BLANK_LINE.test(pattern) && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern) && pattern.indexOf("#") !== 0;
1011
+ var splitPattern = (pattern) => pattern.split(REGEX_SPLITALL_CRLF).filter(Boolean);
1012
+ var IgnoreRule = class {
1013
+ constructor(pattern, mark, body, ignoreCase, negative, prefix) {
1014
+ this.pattern = pattern;
1015
+ this.mark = mark;
1016
+ this.negative = negative;
1017
+ define(this, "body", body);
1018
+ define(this, "ignoreCase", ignoreCase);
1019
+ define(this, "regexPrefix", prefix);
1020
+ }
1021
+ // Worked out on first use and kept behind an own property, the way `regex`
1022
+ // caches itself in `_regex`. Deciding it in the constructor instead would
1023
+ // add a fourth `defineProperty` to every rule ever built, which cost 4% of
1024
+ // every compile -- including the compiles of rules that are never matched
1025
+ // against anything.
1026
+ get _basenameOnly() {
1027
+ return define(this, "_basenameOnly", matchesBasename(this.body));
1028
+ }
1029
+ get regex() {
1030
+ const key = UNDERSCORE + MODE_IGNORE;
1031
+ if (this[key]) {
1032
+ return this[key];
1033
+ }
1034
+ return this._make(MODE_IGNORE, key);
1035
+ }
1036
+ get checkRegex() {
1037
+ const key = UNDERSCORE + MODE_CHECK_IGNORE;
1038
+ if (this[key]) {
1039
+ return this[key];
1040
+ }
1041
+ return this._make(MODE_CHECK_IGNORE, key);
1042
+ }
1043
+ _make(mode, key) {
1044
+ const str = pinWildcards(this.regexPrefix.replace(
1045
+ REGEX_REPLACE_TRAILING_WILDCARD,
1046
+ // It does not need to bind pattern
1047
+ TRAILING_WILD_CARD_REPLACERS[mode]
1048
+ ));
1049
+ const regex = this.ignoreCase ? new RegExp(str, "i") : new RegExp(str);
1050
+ return define(this, key, regex);
1051
+ }
1052
+ };
1053
+ var createRule = ({
1054
+ pattern,
1055
+ mark
1056
+ }, ignoreCase) => {
1057
+ let negative = false;
1058
+ let body = pattern;
1059
+ if (body.indexOf("!") === 0) {
1060
+ negative = true;
1061
+ body = body.substr(1);
1062
+ }
1063
+ body = body.replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, "!").replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, "#");
1064
+ const regexPrefix = makeRegexPrefix(body);
1065
+ return new IgnoreRule(
1066
+ pattern,
1067
+ mark,
1068
+ body,
1069
+ ignoreCase,
1070
+ negative,
1071
+ regexPrefix
1072
+ );
1073
+ };
1074
+ var RuleManager = class {
1075
+ constructor(ignoreCase) {
1076
+ this._ignoreCase = ignoreCase;
1077
+ this._rules = [];
1078
+ this._basenameCount = 0;
1079
+ }
1080
+ _add(pattern) {
1081
+ if (pattern && pattern[KEY_IGNORE]) {
1082
+ this._rules = this._rules.concat(pattern._rules._rules);
1083
+ this._basenameCount += pattern._rules._basenameCount;
1084
+ this._added = true;
1085
+ return;
1086
+ }
1087
+ if (isString(pattern)) {
1088
+ pattern = {
1089
+ pattern
1090
+ };
1091
+ }
1092
+ if (checkPattern(pattern.pattern)) {
1093
+ const rule = createRule(pattern, this._ignoreCase);
1094
+ this._added = true;
1095
+ this._rules.push(rule);
1096
+ if (matchesBasename(rule.body)) {
1097
+ this._basenameCount++;
1098
+ }
1099
+ }
1100
+ }
1101
+ // @param {Array<string> | string | Ignore} pattern
1102
+ add(pattern) {
1103
+ this._added = false;
1104
+ makeArray(
1105
+ isString(pattern) ? splitPattern(pattern) : pattern
1106
+ ).forEach(this._add, this);
1107
+ return this._added;
1108
+ }
1109
+ // Test one single path without recursively checking parent directories
1110
+ //
1111
+ // - checkUnignored `boolean` whether should check if the path is unignored,
1112
+ // setting `checkUnignored` to `false` could reduce additional
1113
+ // path matching.
1114
+ // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
1115
+ // @returns {TestResult} true if a file is ignored
1116
+ test(path, checkUnignored, mode) {
1117
+ let ignored = false;
1118
+ let unignored = false;
1119
+ let matchedRule;
1120
+ const rules = this._rules;
1121
+ const { length } = rules;
1122
+ const shortcut = this._basenameCount * 2 >= length;
1123
+ const basename3 = shortcut ? basenameOf(path) : path;
1124
+ for (let index = 0; index < length; index++) {
1125
+ const rule = rules[index];
1126
+ const { negative } = rule;
1127
+ const skip = unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored;
1128
+ if (!skip && rule[mode].test(
1129
+ shortcut && rule._basenameOnly ? basename3 : path
1130
+ )) {
1131
+ ignored = !negative;
1132
+ unignored = negative;
1133
+ matchedRule = negative ? UNDEFINED : rule;
1134
+ }
1135
+ }
1136
+ const ret = {
1137
+ ignored,
1138
+ unignored
1139
+ };
1140
+ if (matchedRule) {
1141
+ ret.rule = matchedRule;
1142
+ }
1143
+ return ret;
1144
+ }
1145
+ };
1146
+ var throwError = (message, Ctor) => {
1147
+ throw new Ctor(message);
1148
+ };
1149
+ var checkPath = (path, originalPath, doThrow) => {
1150
+ if (!isString(path)) {
1151
+ return doThrow(
1152
+ `path must be a string, but got \`${originalPath}\``,
1153
+ TypeError
1154
+ );
1155
+ }
1156
+ if (!path) {
1157
+ return doThrow(`path must not be empty`, TypeError);
1158
+ }
1159
+ if (checkPath.isNotRelative(path)) {
1160
+ const r = "`path.relative()`d";
1161
+ return doThrow(
1162
+ `path should be a ${r} string, but got "${originalPath}"`,
1163
+ RangeError
1164
+ );
1165
+ }
1166
+ return true;
1167
+ };
1168
+ var isNotRelative = (path) => {
1169
+ const first = path.charCodeAt(0);
1170
+ if (first === SLASH_CODE) {
1171
+ return true;
1172
+ }
1173
+ if (first !== DOT_CODE) {
1174
+ return false;
1175
+ }
1176
+ if (path.length === 1) {
1177
+ return true;
1178
+ }
1179
+ const second = path.charCodeAt(1);
1180
+ if (second === SLASH_CODE) {
1181
+ return true;
1182
+ }
1183
+ if (second !== DOT_CODE) {
1184
+ return false;
1185
+ }
1186
+ return path.length === 2 || path.charCodeAt(2) === SLASH_CODE;
1187
+ };
1188
+ checkPath.isNotRelative = isNotRelative;
1189
+ checkPath.convert = (p) => p;
1190
+ var Ignore = class {
1191
+ constructor({
1192
+ ignorecase = true,
1193
+ ignoreCase = ignorecase,
1194
+ allowRelativePaths = false
1195
+ } = {}) {
1196
+ define(this, KEY_IGNORE, true);
1197
+ this._rules = new RuleManager(ignoreCase);
1198
+ this._strictPathCheck = !allowRelativePaths;
1199
+ this._initCache();
1200
+ }
1201
+ _initCache() {
1202
+ this._ignoreCache = /* @__PURE__ */ Object.create(null);
1203
+ this._testCache = /* @__PURE__ */ Object.create(null);
1204
+ }
1205
+ add(pattern) {
1206
+ if (this._rules.add(pattern)) {
1207
+ this._initCache();
1208
+ }
1209
+ return this;
1210
+ }
1211
+ // legacy
1212
+ addPattern(pattern) {
1213
+ return this.add(pattern);
1214
+ }
1215
+ // @returns {TestResult}
1216
+ _test(originalPath, cache, checkUnignored) {
1217
+ const path = originalPath && checkPath.convert(originalPath);
1218
+ checkPath(
1219
+ path,
1220
+ originalPath,
1221
+ this._strictPathCheck ? throwError : RETURN_FALSE
1222
+ );
1223
+ return this._t(path, cache, checkUnignored);
1224
+ }
1225
+ checkIgnore(path) {
1226
+ if (path.charCodeAt(path.length - 1) !== SLASH_CODE) {
1227
+ return this.test(path);
1228
+ }
1229
+ const parentPath = parentOf(path);
1230
+ if (parentPath) {
1231
+ const parent = this._t(parentPath, this._testCache, true);
1232
+ if (parent.ignored) {
1233
+ return parent;
1234
+ }
1235
+ }
1236
+ return this._rules.test(path, false, MODE_CHECK_IGNORE);
1237
+ }
1238
+ _t(path, cache, checkUnignored) {
1239
+ if (path in cache) {
1240
+ return cache[path];
1241
+ }
1242
+ const parentPath = parentOf(path);
1243
+ const parent = parentPath ? this._t(parentPath, cache, checkUnignored) : UNDEFINED;
1244
+ return cache[path] = parent && parent.ignored ? parent : this._rules.test(path, checkUnignored, MODE_IGNORE);
1245
+ }
1246
+ ignores(path) {
1247
+ return this._test(path, this._ignoreCache, false).ignored;
1248
+ }
1249
+ createFilter() {
1250
+ return (path) => !this.ignores(path);
1251
+ }
1252
+ filter(paths) {
1253
+ return makeArray(paths).filter(this.createFilter());
1254
+ }
1255
+ // @returns {TestResult}
1256
+ test(path) {
1257
+ return this._test(path, this._testCache, true);
1258
+ }
1259
+ };
1260
+ var factory = (options) => new Ignore(options);
1261
+ var isPathValid = (path) => checkPath(path && checkPath.convert(path), path, RETURN_FALSE);
1262
+ var setupWindows = () => {
1263
+ const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
1264
+ checkPath.convert = makePosix;
1265
+ const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
1266
+ checkPath.isNotRelative = (path) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path) || isNotRelative(path);
1267
+ };
1268
+ if (
1269
+ // Detect `process` so that it can run in browsers.
1270
+ typeof process !== "undefined" && process.platform === "win32"
1271
+ ) {
1272
+ setupWindows();
1273
+ }
1274
+ module.exports = factory;
1275
+ factory.default = factory;
1276
+ module.exports.isPathValid = isPathValid;
1277
+ define(module.exports, /* @__PURE__ */ Symbol.for("setupWindows"), setupWindows);
1278
+ }
1279
+ });
1280
+
1281
+ // src/dev/bridge.ts
1282
+ function mapErrorCode(code, status2) {
1283
+ if (code === "invalid_input") return "INVALID_INPUT";
1284
+ if (code === "not_found") return "NOT_FOUND";
1285
+ if (code === "conflict") return "CONFLICT";
1286
+ if (code === "forbidden") return "UNDECLARED";
1287
+ if (status2 === 401) return "UNDECLARED";
1288
+ return "UNAVAILABLE";
1289
+ }
1290
+ function devRuntimeBody(invoke, appId) {
1291
+ if (invoke.service === "ai") {
1292
+ return {
1293
+ service: "ai",
1294
+ args: invoke.input.args ?? {},
1295
+ ...appId ? { app_id: appId } : {}
1296
+ };
1297
+ }
1298
+ if (invoke.service === "capabilities") {
1299
+ return {
1300
+ service: "capabilities",
1301
+ capability: invoke.input.capability,
1302
+ args: invoke.input.args ?? {},
1303
+ ...invoke.input.requestId ? { request_id: invoke.input.requestId } : {}
1304
+ };
1305
+ }
1306
+ return {
1307
+ service: "connections",
1308
+ connector: invoke.input.connector,
1309
+ ...invoke.input.connection ? { connection: invoke.input.connection } : {},
1310
+ operation: invoke.input.operation,
1311
+ args: invoke.input.args ?? {},
1312
+ ...appId ? { app_id: appId } : {}
1313
+ };
1314
+ }
1315
+ async function bridgeInvoke(dependencies, invoke) {
1316
+ const fetcher = dependencies.fetcher ?? fetch;
1317
+ let response;
1318
+ try {
1319
+ response = await fetcher(`${dependencies.apiUrl}/v1/dev/runtime`, {
1320
+ method: "POST",
1321
+ headers: {
1322
+ authorization: `Bearer ${dependencies.token}`,
1323
+ "content-type": "application/json"
1324
+ },
1325
+ body: JSON.stringify(devRuntimeBody(invoke, dependencies.appId)),
1326
+ signal: AbortSignal.timeout(6e4)
1327
+ });
1328
+ } catch {
1329
+ throw new BridgeError({
1330
+ code: "UNAVAILABLE",
1331
+ message: "Marina is unreachable \u2014 bridged calls need a network connection",
1332
+ retryable: true
1333
+ });
1334
+ }
1335
+ let body;
1336
+ try {
1337
+ body = await response.json();
1338
+ } catch {
1339
+ throw new BridgeError({
1340
+ code: "UNAVAILABLE",
1341
+ message: "Marina returned an invalid response",
1342
+ retryable: true
1343
+ });
1344
+ }
1345
+ if (!response.ok) {
1346
+ const error = body.error ?? {};
1347
+ throw new BridgeError({
1348
+ code: mapErrorCode(error.code ?? "", response.status),
1349
+ message: error.message ?? `bridged call failed (${String(response.status)})`,
1350
+ retryable: response.status >= 500
1351
+ });
1352
+ }
1353
+ return body.value;
1354
+ }
1355
+ var BridgeError;
1356
+ var init_bridge = __esm({
1357
+ "src/dev/bridge.ts"() {
1358
+ "use strict";
1359
+ BridgeError = class extends Error {
1360
+ payload;
1361
+ constructor(payload) {
1362
+ super(payload.message);
1363
+ this.payload = payload;
1364
+ }
1365
+ };
1366
+ }
1367
+ });
1368
+
1369
+ // src/dev/binding.ts
1370
+ function undeclared(service, declaration) {
1371
+ return failure2(
1372
+ "UNDECLARED",
1373
+ `this app does not declare ${service} \u2014 add ${declaration} to marina.json`
1374
+ );
1375
+ }
1376
+ function createDevBinding(context) {
1377
+ return {
1378
+ async invoke(request2) {
1379
+ try {
1380
+ switch (request2.service) {
1381
+ case "storage": {
1382
+ if (!context.storage) return undeclared("storage", 'runtime.storage: "v1"');
1383
+ const input = request2.input;
1384
+ if (request2.operation === "put") return ok(context.storage.put(input));
1385
+ if (request2.operation === "get")
1386
+ return ok(context.storage.get(input.key));
1387
+ if (request2.operation === "delete") {
1388
+ context.storage.delete(input.key);
1389
+ return ok(null);
1390
+ }
1391
+ if (request2.operation === "list") return ok(context.storage.list(input));
1392
+ return failure2("INVALID_INPUT", `unsupported storage operation ${request2.operation}`);
1393
+ }
1394
+ case "db": {
1395
+ if (!context.database) return undeclared("the managed database", 'runtime.db: "v1"');
1396
+ if (request2.operation === "query") {
1397
+ const query = request2.input;
1398
+ return ok(await context.database.query(query.text, query.params));
1399
+ }
1400
+ if (request2.operation === "transaction") {
1401
+ const { queries } = request2.input;
1402
+ return ok(await context.database.transaction(queries));
1403
+ }
1404
+ return failure2("INVALID_INPUT", `unsupported db operation ${request2.operation}`);
1405
+ }
1406
+ case "ai": {
1407
+ if (!context.manifest.runtime.ai) return undeclared("ai", 'runtime.ai: "v1"');
1408
+ if (request2.operation !== "generate")
1409
+ return failure2("INVALID_INPUT", `unsupported ai operation ${request2.operation}`);
1410
+ return ok(
1411
+ await bridgeInvoke(context.bridge, {
1412
+ service: "ai",
1413
+ input: { args: request2.input }
1414
+ })
1415
+ );
1416
+ }
1417
+ case "jobs": {
1418
+ if (!context.jobs) return undeclared("background jobs", 'runtime.jobs: "v1"');
1419
+ if (request2.operation === "enqueue") {
1420
+ const input = request2.input;
1421
+ const status2 = context.jobs.enqueue(input);
1422
+ return ok({ id: status2.id, state: status2.state });
1423
+ }
1424
+ if (request2.operation === "get") {
1425
+ const { runId } = request2.input;
1426
+ const status2 = context.jobs.get(runId);
1427
+ return status2 ? ok(status2) : failure2("NOT_FOUND", "no such job run");
1428
+ }
1429
+ return failure2("INVALID_INPUT", `unsupported jobs operation ${request2.operation}`);
1430
+ }
1431
+ case "capabilities":
1432
+ case "connections":
1433
+ return ok(
1434
+ await bridgeInvoke(context.bridge, {
1435
+ service: request2.service,
1436
+ input: request2.input
1437
+ })
1438
+ );
1439
+ default:
1440
+ return failure2("UNDECLARED", `unsupported runtime service ${request2.service}`);
1441
+ }
1442
+ } catch (error) {
1443
+ if (error instanceof BridgeError) return { ok: false, error: error.payload };
1444
+ return failure2("INVALID_INPUT", error.message);
1445
+ }
1446
+ },
1447
+ async authorizeJob(request2) {
1448
+ return context.jobs ? context.jobs.authorizeJob(request2) : false;
1449
+ }
1450
+ };
1451
+ }
1452
+ var ok, failure2;
1453
+ var init_binding = __esm({
1454
+ "src/dev/binding.ts"() {
1455
+ "use strict";
1456
+ init_bridge();
1457
+ ok = (value) => ({ ok: true, value });
1458
+ failure2 = (code, message, retryable = false) => ({
1459
+ ok: false,
1460
+ error: { code, message, retryable }
1461
+ });
1462
+ }
1463
+ });
1464
+
1465
+ // src/dev/db.ts
1466
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "node:fs";
1467
+ import { join as join5 } from "node:path";
1468
+ function normalizeValue(value) {
1469
+ if (value === null || value === void 0) return null;
1470
+ if (typeof value === "string" || typeof value === "boolean") return value;
1471
+ if (typeof value === "number") return Number.isFinite(value) ? value : String(value);
1472
+ if (typeof value === "bigint") {
1473
+ return value >= BigInt(Number.MIN_SAFE_INTEGER) && value <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(value) : value.toString();
1474
+ }
1475
+ if (value instanceof Date) return value.toISOString();
1476
+ if (value instanceof Uint8Array) return Buffer.from(value).toString("base64");
1477
+ if (Array.isArray(value)) return value.map(normalizeValue);
1478
+ if (typeof value === "object") {
1479
+ return Object.fromEntries(
1480
+ Object.entries(value).map(([key, entry]) => [
1481
+ key,
1482
+ normalizeValue(entry)
1483
+ ])
1484
+ );
1485
+ }
1486
+ return String(value);
1487
+ }
1488
+ function normalizeRow(row) {
1489
+ if (!row || typeof row !== "object") return {};
1490
+ return Object.fromEntries(
1491
+ Object.entries(row).map(([key, value]) => [
1492
+ key,
1493
+ normalizeValue(value)
1494
+ ])
1495
+ );
1496
+ }
1497
+ var LocalDatabase;
1498
+ var init_db = __esm({
1499
+ "src/dev/db.ts"() {
1500
+ "use strict";
1501
+ LocalDatabase = class _LocalDatabase {
1502
+ db;
1503
+ constructor(db) {
1504
+ this.db = db;
1505
+ }
1506
+ static async open(dataDir) {
1507
+ let module;
1508
+ try {
1509
+ module = await import("@electric-sql/pglite");
1510
+ } catch {
1511
+ throw new Error(
1512
+ "the embedded Postgres could not load \u2014 reinstall the Marina CLI (@electric-sql/pglite is missing)"
1513
+ );
1514
+ }
1515
+ mkdirSync3(dataDir, { recursive: true });
1516
+ return new _LocalDatabase(new module.PGlite(dataDir));
1517
+ }
1518
+ /** Apply the app's marina/migrations in name order, once each — the same
1519
+ * files a deploy applies to the managed database. */
1520
+ async applyMigrations(projectDir) {
1521
+ const directory = join5(projectDir, "marina", "migrations");
1522
+ await this.db.exec(
1523
+ "create table if not exists marina_dev_migrations (name text primary key, applied_at timestamptz not null default now())"
1524
+ );
1525
+ if (!existsSync5(directory)) return [];
1526
+ const files = readdirSync2(directory).filter((file) => file.endsWith(".sql")).toSorted();
1527
+ const applied = [];
1528
+ for (const file of files) {
1529
+ const seen = await this.db.query("select 1 from marina_dev_migrations where name = $1", [
1530
+ file
1531
+ ]);
1532
+ if (seen.rows.length > 0) continue;
1533
+ const source = readFileSync5(join5(directory, file), "utf8");
1534
+ await this.db.transaction(async (tx) => {
1535
+ await tx.exec(source);
1536
+ await tx.query("insert into marina_dev_migrations (name) values ($1)", [file]);
1537
+ });
1538
+ applied.push(file);
1539
+ }
1540
+ return applied;
1541
+ }
1542
+ async query(text, params) {
1543
+ const result2 = await this.db.query(text, params);
1544
+ const rows = result2.rows.map(normalizeRow);
1545
+ return { rows, rowCount: rows.length > 0 ? rows.length : result2.affectedRows ?? 0 };
1546
+ }
1547
+ async transaction(queries) {
1548
+ return this.db.transaction(async (tx) => {
1549
+ const results = [];
1550
+ for (const query of queries) {
1551
+ const result2 = await tx.query(query.text, query.params);
1552
+ const rows = result2.rows.map(normalizeRow);
1553
+ results.push({
1554
+ rows,
1555
+ rowCount: rows.length > 0 ? rows.length : result2.affectedRows ?? 0
1556
+ });
1557
+ }
1558
+ return results;
1559
+ });
1560
+ }
1561
+ };
1562
+ }
1563
+ });
1564
+
1565
+ // src/dev/chrome.ts
1566
+ function escapeHtml(value) {
1567
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
1568
+ }
1569
+ function devChromeSnippet(input) {
1570
+ return `<div id="__marina-dev-chrome" style="position:fixed;right:12px;bottom:12px;z-index:2147483647;display:flex;align-items:center;gap:8px;background:#121316;color:#e5e7ea;border:1px solid #2a2e34;border-radius:8px;padding:6px 12px;font:500 12px/1.4 ui-sans-serif,system-ui;box-shadow:0 4px 16px rgb(0 0 0 / 0.25)">
1571
+ <span style="width:8px;height:8px;border-radius:99px;background:#e0a24a"></span>
1572
+ <span>${escapeHtml(input.appName)} \xB7 local dev \xB7 ${escapeHtml(input.userLabel)}</span>
1573
+ </div>`;
1574
+ }
1575
+ function injectDevChrome(html, snippet) {
1576
+ const marker = /<\/body\s*>/i.exec(html);
1577
+ if (!marker) return html + snippet;
1578
+ return html.slice(0, marker.index) + snippet + html.slice(marker.index);
1579
+ }
1580
+ var init_chrome = __esm({
1581
+ "src/dev/chrome.ts"() {
1582
+ "use strict";
1583
+ }
1584
+ });
1585
+
1586
+ // src/dev/embedded-runtime.ts
1587
+ var DEV_RUNTIME_FILES;
1588
+ var init_embedded_runtime = __esm({
1589
+ "src/dev/embedded-runtime.ts"() {
1590
+ "use strict";
1591
+ DEV_RUNTIME_FILES = {
1592
+ "ai.ts": 'import type {\n AIGenerateInput,\n AIGenerateResult,\n AIMessage,\n MarinaRuntimeBinding,\n} from "./contract.js";\nimport {\n MAX_AI_INPUT_BYTES,\n MAX_AI_MESSAGE_BYTES,\n MAX_AI_MESSAGES,\n MAX_AI_OUTPUT_TOKENS,\n} from "./contract.js";\nimport { MarinaRuntimeError } from "./error.js";\nimport { invokeRuntime } from "./invoke.js";\n\nconst encoder = new TextEncoder();\nconst MESSAGE_ROLES = new Set(["system", "user", "assistant"]);\n\nfunction invalid(message: string): never {\n throw new MarinaRuntimeError({ code: "INVALID_INPUT", message, retryable: false });\n}\n\n/** Validate the portable chat contract before any provider or substrate sees\n * application content. The broker repeats the same bounded checks. */\nexport function assertAIGenerateInput(value: AIGenerateInput): AIGenerateInput {\n if (!value || typeof value !== "object" || Array.isArray(value))\n invalid("AI generation input must be an object");\n if (typeof value.model !== "string" || value.model.length === 0 || value.model.length > 200)\n invalid("AI model must be fast, smart, or a model id from Marina\'s model library");\n if (!Array.isArray(value.messages) || value.messages.length === 0)\n invalid("AI generation requires at least one message");\n if (value.messages.length > MAX_AI_MESSAGES)\n invalid(`AI generation may contain at most ${String(MAX_AI_MESSAGES)} messages`);\n\n let inputBytes = 0;\n const messages: AIMessage[] = value.messages.map((message) => {\n if (!message || typeof message !== "object" || Array.isArray(message))\n invalid("AI messages must be objects");\n if (typeof message.role !== "string" || !MESSAGE_ROLES.has(message.role))\n invalid("AI message role must be system, user, or assistant");\n if (typeof message.content !== "string" || message.content.length === 0)\n invalid("AI message content must not be empty");\n if (/\\p{Cc}/u.test(message.content.replaceAll("\\n", "").replaceAll("\\t", "")))\n invalid("AI message content contains unsupported control characters");\n const bytes = encoder.encode(message.content).byteLength;\n if (bytes > MAX_AI_MESSAGE_BYTES)\n invalid(`AI messages must not exceed ${String(MAX_AI_MESSAGE_BYTES)} bytes each`);\n inputBytes += bytes;\n return { role: message.role, content: message.content };\n });\n if (inputBytes > MAX_AI_INPUT_BYTES)\n invalid(`AI generation input must not exceed ${String(MAX_AI_INPUT_BYTES)} bytes`);\n\n if (\n value.maxOutputTokens !== undefined &&\n (!Number.isSafeInteger(value.maxOutputTokens) ||\n value.maxOutputTokens < 1 ||\n value.maxOutputTokens > MAX_AI_OUTPUT_TOKENS)\n )\n invalid(`AI output tokens must be between 1 and ${String(MAX_AI_OUTPUT_TOKENS)}`);\n\n return {\n model: value.model,\n messages,\n ...(value.maxOutputTokens === undefined ? {} : { maxOutputTokens: value.maxOutputTokens }),\n };\n}\n\nexport interface MarinaAI {\n /** Generate bounded text using a Marina-managed model class. Provider names,\n * credentials, routing, and quota state never enter the app contract. */\n generate(input: AIGenerateInput): Promise<AIGenerateResult>;\n}\n\nexport function createAI(binding: MarinaRuntimeBinding): MarinaAI {\n return {\n async generate(input) {\n return invokeRuntime(binding, "ai", "generate", assertAIGenerateInput(input));\n },\n };\n}\n',
1593
+ "capabilities.ts": 'import type { DatabaseValue, MarinaRuntimeBinding } from "./contract.js";\nimport { MAX_CAPABILITY_INPUT_BYTES, MAX_CAPABILITY_SESSION_BYTES } from "./contract.js";\nimport { MarinaRuntimeError } from "./error.js";\nimport { invokeRuntime } from "./invoke.js";\n\nconst encoder = new TextEncoder();\nconst CAPABILITY_NAME = /^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$/;\nconst REQUEST_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\nfunction invalid(message: string): never {\n throw new MarinaRuntimeError({ code: "INVALID_INPUT", message, retryable: false });\n}\n\nfunction capabilityArgs(value: unknown): DatabaseValue {\n let serialized: string | undefined;\n try {\n serialized = JSON.stringify(value);\n } catch {\n invalid("capability arguments must be JSON compatible");\n }\n if (serialized === undefined) invalid("capability arguments must be JSON compatible");\n if (encoder.encode(serialized).byteLength > MAX_CAPABILITY_INPUT_BYTES)\n invalid(`capability arguments must not exceed ${String(MAX_CAPABILITY_INPUT_BYTES)} bytes`);\n return value as DatabaseValue;\n}\n\nexport interface CapabilityInvokeOptions {\n /** Stable idempotency identity for edit-effect Capability retries. */\n requestId?: string;\n}\n\nexport interface MarinaCapabilities {\n invoke<Result = unknown>(\n name: string,\n args?: DatabaseValue,\n options?: CapabilityInvokeOptions,\n ): Promise<Result>;\n}\n\nexport function createCapabilities(\n binding: MarinaRuntimeBinding,\n session?: string,\n): MarinaCapabilities {\n return {\n invoke<Result>(name: string, args: DatabaseValue = {}, options: CapabilityInvokeOptions = {}) {\n if (!session) {\n throw new MarinaRuntimeError({\n code: "UNAVAILABLE",\n message: "Capability calls require an authenticated app request",\n retryable: false,\n });\n }\n if (encoder.encode(session).byteLength > MAX_CAPABILITY_SESSION_BYTES)\n invalid("invalid Capability session");\n if (typeof name !== "string" || name.length > 200 || !CAPABILITY_NAME.test(name))\n invalid("invalid Capability name");\n if (options.requestId !== undefined && !REQUEST_ID.test(options.requestId))\n invalid("invalid Capability request id");\n return invokeRuntime<Result>(binding, "capabilities", "invoke", {\n session,\n capability: name,\n args: capabilityArgs(args),\n ...(options.requestId ? { requestId: options.requestId } : {}),\n });\n },\n };\n}\n',
1594
+ "connections.ts": 'import type { DatabaseValue, MarinaRuntimeBinding } from "./contract.js";\nimport { MAX_CAPABILITY_INPUT_BYTES, MAX_CAPABILITY_SESSION_BYTES } from "./contract.js";\nimport { MarinaRuntimeError } from "./error.js";\nimport { invokeRuntime } from "./invoke.js";\n\nconst encoder = new TextEncoder();\nconst SEGMENT = /^[a-z][a-z0-9_]{0,39}$/;\nconst OPERATION = /^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$/;\n\nfunction invalid(message: string): never {\n throw new MarinaRuntimeError({ code: "INVALID_INPUT", message, retryable: false });\n}\n\nexport interface ConnectionInvokeRequest {\n connector: string;\n /** Explicit connection id; optional when the app declares exactly one\n * connection of this connector. */\n connection?: string;\n operation: string;\n args?: DatabaseValue;\n}\n\nexport type MarinaConnectionOperationInput = { connection?: string } & Record<string, unknown>;\nexport type MarinaConnectionCall = (input?: MarinaConnectionOperationInput) => Promise<unknown>;\nexport type MarinaConnectionNamespace = MarinaConnectionCall & {\n readonly [segment: string]: MarinaConnectionNamespace;\n};\nexport type MarinaConnections = {\n invoke<Result = unknown>(request: ConnectionInvokeRequest): Promise<Result>;\n} & { readonly [connector: string]: MarinaConnectionNamespace };\n\nfunction boundedArgs(value: unknown): DatabaseValue {\n let serialized: string | undefined;\n try {\n serialized = JSON.stringify(value);\n } catch {\n invalid("connection operation input must be JSON compatible");\n }\n if (serialized === undefined) invalid("connection operation input must be JSON compatible");\n if (encoder.encode(serialized).byteLength > MAX_CAPABILITY_INPUT_BYTES)\n invalid(\n `connection operation input must not exceed ${String(MAX_CAPABILITY_INPUT_BYTES)} bytes`,\n );\n return value as DatabaseValue;\n}\n\n/**\n * `marina.connections.<connector>.<operation>(input)` \u2014 a dynamic path so the\n * runtime ships no connector catalog; the control plane owns validation and\n * authorization. `input.connection` names one of the app\'s declared\n * connections and is required only when the manifest declares several of the\n * same connector.\n */\nexport function createConnections(\n binding: MarinaRuntimeBinding,\n session?: string,\n): MarinaConnections {\n const invoke = <Result = unknown>(request: ConnectionInvokeRequest): Promise<Result> => {\n if (!session) {\n throw new MarinaRuntimeError({\n code: "UNAVAILABLE",\n message: "Connection calls require an authenticated app request",\n retryable: false,\n });\n }\n if (encoder.encode(session).byteLength > MAX_CAPABILITY_SESSION_BYTES)\n invalid("invalid connection session");\n if (typeof request.connector !== "string" || !SEGMENT.test(request.connector))\n invalid("invalid connector name");\n if (\n request.connection !== undefined &&\n (typeof request.connection !== "string" || !SEGMENT.test(request.connection))\n )\n invalid("invalid connection id");\n if (\n typeof request.operation !== "string" ||\n request.operation.length > 200 ||\n !OPERATION.test(request.operation)\n )\n invalid("invalid connection operation");\n return invokeRuntime<Result>(binding, "connections", "invoke", {\n session,\n connector: request.connector,\n ...(request.connection ? { connection: request.connection } : {}),\n operation: request.operation,\n args: boundedArgs(request.args ?? {}),\n });\n };\n\n const operationPath = (connector: string, segments: readonly string[]) => {\n const callable = (input: MarinaConnectionOperationInput = {}): Promise<unknown> => {\n if (segments.length < 2) invalid("call a connection operation such as orders.list");\n if (!input || typeof input !== "object" || Array.isArray(input))\n invalid("connection operation input must be an object");\n const { connection, ...args } = input;\n if (connection !== undefined && (typeof connection !== "string" || !SEGMENT.test(connection)))\n invalid("invalid connection id");\n return invoke({\n connector,\n ...(connection ? { connection } : {}),\n operation: segments.join("."),\n args: args as DatabaseValue,\n });\n };\n const path: MarinaConnectionNamespace = new Proxy(callable, {\n get(_target, property) {\n if (typeof property !== "string" || property === "then" || !SEGMENT.test(property))\n return undefined;\n return operationPath(connector, [...segments, property]);\n },\n }) as unknown as MarinaConnectionNamespace;\n return path;\n };\n\n return new Proxy(\n { invoke },\n {\n get(target, property) {\n if (property === "invoke") return target.invoke;\n if (typeof property !== "string" || property === "then" || !SEGMENT.test(property))\n return undefined;\n return operationPath(property, []);\n },\n },\n ) as MarinaConnections;\n}\n',
1595
+ "contract.ts": `/** The versioned provider-neutral protocol between an app and Marina. */
1596
+ export const MARINA_RUNTIME_PROTOCOL_VERSION = 1 as const;
1597
+ export const MAX_STORAGE_KEY_BYTES = 900;
1598
+ export const MAX_STORAGE_OBJECT_BYTES = 64 * 1024 * 1024;
1599
+ export const MAX_CAPABILITY_INPUT_BYTES = 256 * 1024;
1600
+ export const MAX_CAPABILITY_SESSION_BYTES = 4096;
1601
+ export const MAX_CAPABILITY_RESULT_BYTES = 32 * 1024 * 1024;
1602
+ export const MAX_DATABASE_QUERY_BYTES = 100 * 1024;
1603
+ export const MAX_DATABASE_PARAMETERS = 100;
1604
+ export const MAX_DATABASE_PARAMETER_BYTES = 1024 * 1024;
1605
+ export const MAX_DATABASE_TRANSACTION_QUERIES = 25;
1606
+ export const MAX_AI_MESSAGES = 64;
1607
+ export const MAX_AI_MESSAGE_BYTES = 32 * 1024;
1608
+ export const MAX_AI_INPUT_BYTES = 128 * 1024;
1609
+ export const MAX_AI_OUTPUT_TOKENS = 4096;
1610
+ export const MAX_JOB_INPUT_BYTES = 256 * 1024;
1611
+ export const MAX_JOB_VALUE_DEPTH = 32;
1612
+ export const MAX_JOB_INVOCATION_BYTES = MAX_JOB_INPUT_BYTES + 128 * 1024;
1613
+ export const MARINA_JOB_EXECUTION_PATH = "/.marina/runtime/jobs/execute";
1614
+
1615
+ /** Stable service identifiers carried by the broker protocol. */
1616
+ export type MarinaRuntimeService =
1617
+ | "storage"
1618
+ | "db"
1619
+ | "ai"
1620
+ | "jobs"
1621
+ | "capabilities"
1622
+ | "connections";
1623
+
1624
+ export type StorageOperation = "put" | "get" | "delete" | "list";
1625
+
1626
+ export interface MarinaRuntimeRequest {
1627
+ protocolVersion: typeof MARINA_RUNTIME_PROTOCOL_VERSION;
1628
+ /** Unique identity for this individual broker call. */
1629
+ requestId: string;
1630
+ service: MarinaRuntimeService;
1631
+ operation: string;
1632
+ input: unknown;
1633
+ }
1634
+
1635
+ export type MarinaRuntimeErrorCode =
1636
+ | "UNAVAILABLE"
1637
+ | "UNDECLARED"
1638
+ | "INVALID_INPUT"
1639
+ | "NOT_FOUND"
1640
+ | "QUOTA_EXCEEDED"
1641
+ | "CONFLICT"
1642
+ | "INTERNAL";
1643
+
1644
+ export interface MarinaRuntimeErrorPayload {
1645
+ code: MarinaRuntimeErrorCode;
1646
+ message: string;
1647
+ retryable: boolean;
1648
+ }
1649
+
1650
+ export type MarinaRuntimeResponse =
1651
+ | { ok: true; value: unknown }
1652
+ | { ok: false; error: MarinaRuntimeErrorPayload };
1653
+
1654
+ /**
1655
+ * The substrate supplies this object-capability. Applications never receive
1656
+ * the provider resources, credentials, or namespace identifiers behind it.
1657
+ */
1658
+ export interface MarinaRuntimeBinding {
1659
+ invoke(request: MarinaRuntimeRequest): Promise<MarinaRuntimeResponse>;
1660
+ /** Internal substrate callback used only by defineApp's job adapter. The
1661
+ * application receives no signing key or provider dispatch primitive. */
1662
+ authorizeJob?(request: MarinaJobAuthorizationRequest): Promise<boolean>;
1663
+ }
1664
+
1665
+ export type JobValue = DatabaseValue;
1666
+ export type JobTrigger = "enqueue" | "schedule";
1667
+ export type JobState = "queued" | "running" | "succeeded" | "failed";
1668
+
1669
+ export interface JobEnqueueInput {
1670
+ name: string;
1671
+ input: JobValue;
1672
+ dedupeKey?: string;
1673
+ }
1674
+
1675
+ export interface JobEnqueueResult {
1676
+ id: string;
1677
+ state: JobState;
1678
+ }
1679
+
1680
+ export interface JobStatus {
1681
+ id: string;
1682
+ name: string;
1683
+ state: JobState;
1684
+ attempts: number;
1685
+ createdAt: string;
1686
+ startedAt?: string;
1687
+ finishedAt?: string;
1688
+ }
1689
+
1690
+ export interface MarinaJobInvocation {
1691
+ runId: string;
1692
+ workspaceId: string;
1693
+ appId: string;
1694
+ artifactDigest: string;
1695
+ environment: "development" | "preview" | "production";
1696
+ jobName: string;
1697
+ handler: string;
1698
+ input: JobValue;
1699
+ attempt: number;
1700
+ trigger: JobTrigger;
1701
+ scheduledFor?: string;
1702
+ /** Opaque, run-bound delegation consumed by the runtime adapter. It is not
1703
+ * exposed to the application handler or job context. */
1704
+ capabilitySession?: string;
1705
+ }
1706
+
1707
+ export interface MarinaJobAuthorizationRequest {
1708
+ token: string;
1709
+ body: string;
1710
+ }
1711
+
1712
+ export interface StoragePutInput {
1713
+ key: string;
1714
+ body: string | Uint8Array | ArrayBuffer;
1715
+ contentType?: string;
1716
+ metadata?: Record<string, string>;
1717
+ }
1718
+
1719
+ export interface StorageGetInput {
1720
+ key: string;
1721
+ }
1722
+
1723
+ export interface StorageDeleteInput {
1724
+ key: string;
1725
+ }
1726
+
1727
+ export interface StorageListInput {
1728
+ prefix?: string;
1729
+ cursor?: string;
1730
+ limit?: number;
1731
+ }
1732
+
1733
+ export interface StorageObjectMetadata {
1734
+ key: string;
1735
+ size: number;
1736
+ etag: string;
1737
+ uploadedAt: string;
1738
+ contentType?: string;
1739
+ metadata: Record<string, string>;
1740
+ }
1741
+
1742
+ export interface StorageObject extends StorageObjectMetadata {
1743
+ body: Uint8Array;
1744
+ }
1745
+
1746
+ export interface StorageListResult {
1747
+ objects: StorageObjectMetadata[];
1748
+ cursor?: string;
1749
+ truncated: boolean;
1750
+ }
1751
+
1752
+ /** JSON-compatible values cross the provider-neutral broker boundary. Postgres
1753
+ * dates, numerics, JSON, and arrays therefore keep deterministic wire shapes
1754
+ * across Workers, local development, and future substrates. */
1755
+ export type DatabaseValue =
1756
+ | null
1757
+ | boolean
1758
+ | number
1759
+ | string
1760
+ | DatabaseValue[]
1761
+ | { [key: string]: DatabaseValue };
1762
+
1763
+ export type DatabaseRow = Record<string, DatabaseValue>;
1764
+
1765
+ export interface DatabaseQuery {
1766
+ text: string;
1767
+ params: DatabaseValue[];
1768
+ }
1769
+
1770
+ export interface DatabaseResult<Row extends DatabaseRow = DatabaseRow> {
1771
+ rows: Row[];
1772
+ rowCount: number;
1773
+ }
1774
+
1775
+ export type AIModelClass = "fast" | "smart";
1776
+ /** A stable class alias, or the id of a model from Marina's own model
1777
+ * library. The broker validates explicit ids against the library it offers;
1778
+ * provider model identifiers are never part of the app contract. */
1779
+ export type AIModel = AIModelClass | (string & {});
1780
+ export type AIMessageRole = "system" | "user" | "assistant";
1781
+
1782
+ export interface AIMessage {
1783
+ role: AIMessageRole;
1784
+ content: string;
1785
+ }
1786
+
1787
+ export interface AIGenerateInput {
1788
+ model: AIModel;
1789
+ messages: AIMessage[];
1790
+ maxOutputTokens?: number;
1791
+ }
1792
+
1793
+ export interface AIGenerateResult {
1794
+ text: string;
1795
+ }
1796
+
1797
+ /** Detect top-level Postgres transaction-control statements while ignoring
1798
+ * quoted values, identifiers, dollar bodies, and nested comments. Marina owns
1799
+ * the operation transaction on every substrate. */
1800
+ export function containsDatabaseTransactionControl(source: string): boolean {
1801
+ let executable = "";
1802
+ let index = 0;
1803
+ let blockDepth = 0;
1804
+ let state: "normal" | "single" | "double" | "line" | "block" | "dollar" = "normal";
1805
+ let dollar = "";
1806
+ while (index < source.length) {
1807
+ const current = source[index] ?? "";
1808
+ const next = source[index + 1] ?? "";
1809
+ if (state === "normal") {
1810
+ if (current === "-" && next === "-") {
1811
+ state = "line";
1812
+ executable += " ";
1813
+ index += 2;
1814
+ continue;
1815
+ }
1816
+ if (current === "/" && next === "*") {
1817
+ state = "block";
1818
+ blockDepth = 1;
1819
+ executable += " ";
1820
+ index += 2;
1821
+ continue;
1822
+ }
1823
+ if (current === "'") state = "single";
1824
+ else if (current === '"') state = "double";
1825
+ else if (current === "$") {
1826
+ const opener = /^\\$[A-Za-z_][A-Za-z0-9_]*\\$|^\\$\\$/.exec(source.slice(index))?.[0];
1827
+ if (opener) {
1828
+ state = "dollar";
1829
+ dollar = opener;
1830
+ executable += " ".repeat(opener.length);
1831
+ index += opener.length;
1832
+ continue;
1833
+ }
1834
+ }
1835
+ executable += state === "normal" ? current : " ";
1836
+ index++;
1837
+ continue;
1838
+ }
1839
+ if (state === "line") {
1840
+ if (current === "\\n") {
1841
+ state = "normal";
1842
+ executable += "\\n";
1843
+ } else executable += " ";
1844
+ index++;
1845
+ continue;
1846
+ }
1847
+ if (state === "block") {
1848
+ if (current === "/" && next === "*") {
1849
+ blockDepth++;
1850
+ executable += " ";
1851
+ index += 2;
1852
+ } else if (current === "*" && next === "/") {
1853
+ blockDepth--;
1854
+ executable += " ";
1855
+ index += 2;
1856
+ if (blockDepth === 0) state = "normal";
1857
+ } else {
1858
+ executable += current === "\\n" ? "\\n" : " ";
1859
+ index++;
1860
+ }
1861
+ continue;
1862
+ }
1863
+ if (state === "dollar") {
1864
+ if (source.startsWith(dollar, index)) {
1865
+ executable += " ".repeat(dollar.length);
1866
+ index += dollar.length;
1867
+ state = "normal";
1868
+ } else {
1869
+ executable += current === "\\n" ? "\\n" : " ";
1870
+ index++;
1871
+ }
1872
+ continue;
1873
+ }
1874
+ if (state === "single" && current === "'" && next === "'") {
1875
+ executable += " ";
1876
+ index += 2;
1877
+ continue;
1878
+ }
1879
+ if (state === "double" && current === '"' && next === '"') {
1880
+ executable += " ";
1881
+ index += 2;
1882
+ continue;
1883
+ }
1884
+ const closes =
1885
+ (state === "single" && current === "'") || (state === "double" && current === '"');
1886
+ executable += current === "\\n" ? "\\n" : " ";
1887
+ index++;
1888
+ if (closes) state = "normal";
1889
+ }
1890
+ return /(?:^|;)\\s*(?:abort\\b|begin\\b|start\\s+transaction\\b|commit\\b|end\\b|rollback\\b|savepoint\\b|release\\s+savepoint\\b|prepare\\s+transaction\\b|set\\s+transaction\\b|set\\s+session\\s+characteristics\\s+as\\s+transaction\\b)/i.test(
1891
+ executable,
1892
+ );
1893
+ }
1894
+
1895
+ export function isRuntimeResponse(value: unknown): value is MarinaRuntimeResponse {
1896
+ if (!value || typeof value !== "object") return false;
1897
+ const response = value as Partial<MarinaRuntimeResponse>;
1898
+ if (response.ok === true) return Object.hasOwn(response, "value");
1899
+ if (response.ok !== false || !response.error || typeof response.error !== "object") return false;
1900
+ return (
1901
+ typeof response.error.code === "string" &&
1902
+ typeof response.error.message === "string" &&
1903
+ typeof response.error.retryable === "boolean"
1904
+ );
1905
+ }
1906
+ `,
1907
+ "database.ts": 'import type {\n DatabaseQuery,\n DatabaseResult,\n DatabaseRow,\n MarinaRuntimeBinding,\n} from "./contract.js";\nimport {\n containsDatabaseTransactionControl,\n MAX_DATABASE_PARAMETER_BYTES,\n MAX_DATABASE_PARAMETERS,\n MAX_DATABASE_QUERY_BYTES,\n MAX_DATABASE_TRANSACTION_QUERIES,\n} from "./contract.js";\nimport { MarinaRuntimeError } from "./error.js";\nimport { invokeRuntime } from "./invoke.js";\n\nconst encoder = new TextEncoder();\nconst MAX_VALUE_DEPTH = 32;\n\nfunction invalid(message: string): never {\n throw new MarinaRuntimeError({ code: "INVALID_INPUT", message, retryable: false });\n}\n\nfunction assertJsonValue(value: unknown, depth = 0, seen = new Set<object>()): void {\n if (depth > MAX_VALUE_DEPTH) invalid("database parameters are nested too deeply");\n if (\n value === null ||\n typeof value === "string" ||\n typeof value === "boolean" ||\n (typeof value === "number" && Number.isFinite(value))\n )\n return;\n if (!value || typeof value !== "object")\n invalid("database parameters must contain only JSON-compatible values");\n if (seen.has(value)) invalid("database parameters must not contain cycles");\n seen.add(value);\n if (Array.isArray(value)) {\n for (const item of value) assertJsonValue(item, depth + 1, seen);\n } else {\n const prototype = Object.getPrototypeOf(value);\n if (prototype !== Object.prototype && prototype !== null)\n invalid("database parameters must contain only plain JSON objects");\n for (const item of Object.values(value)) assertJsonValue(item, depth + 1, seen);\n }\n seen.delete(value);\n}\n\nfunction jsonBytes(value: unknown): number {\n assertJsonValue(value);\n let serialized: string;\n try {\n serialized = JSON.stringify(value);\n } catch {\n invalid("database parameters must be JSON serializable");\n }\n if (serialized === undefined) invalid("database parameters must be JSON serializable");\n return encoder.encode(serialized).byteLength;\n}\n\n/** Validate one provider-neutral parameterized Postgres query. Runtime SQL is\n * app-authored, but values remain separate so callers never need to interpolate\n * request data into SQL text. The broker repeats these checks at its boundary. */\nexport function assertDatabaseQuery(value: DatabaseQuery): DatabaseQuery {\n if (!value || typeof value !== "object" || Array.isArray(value))\n invalid("database query must be an object");\n if (typeof value.text !== "string" || value.text.trim().length === 0)\n invalid("database query text must not be empty");\n if (value.text.includes("\\0")) invalid("database query text must not contain null bytes");\n if (containsDatabaseTransactionControl(value.text))\n invalid("database transactions are managed by the Marina runtime");\n if (encoder.encode(value.text).byteLength > MAX_DATABASE_QUERY_BYTES)\n invalid(`database query text must not exceed ${String(MAX_DATABASE_QUERY_BYTES)} bytes`);\n if (!Array.isArray(value.params)) invalid("database query parameters must be an array");\n if (value.params.length > MAX_DATABASE_PARAMETERS)\n invalid(`database queries may contain at most ${String(MAX_DATABASE_PARAMETERS)} parameters`);\n if (jsonBytes(value.params) > MAX_DATABASE_PARAMETER_BYTES)\n invalid(\n `database query parameters must not exceed ${String(MAX_DATABASE_PARAMETER_BYTES)} bytes`,\n );\n return value;\n}\n\nexport interface MarinaDatabase {\n /** Execute one parameterized statement against this app\'s private schema. */\n query<Row extends DatabaseRow = DatabaseRow>(\n text: string,\n params?: DatabaseQuery["params"],\n ): Promise<DatabaseResult<Row>>;\n\n /** Execute a bounded list of statements atomically. Interactive transaction\n * callbacks are intentionally absent so the contract works across edge and\n * future non-Cloudflare substrates. */\n transaction<Row extends DatabaseRow = DatabaseRow>(\n queries: readonly DatabaseQuery[],\n ): Promise<DatabaseResult<Row>[]>;\n}\n\nexport function createDatabase(binding: MarinaRuntimeBinding): MarinaDatabase {\n return {\n async query(text, params = []) {\n const query = assertDatabaseQuery({ text, params });\n return invokeRuntime(binding, "db", "query", query);\n },\n\n async transaction(queries) {\n if (!Array.isArray(queries) || queries.length === 0)\n invalid("database transaction must contain at least one query");\n if (queries.length > MAX_DATABASE_TRANSACTION_QUERIES)\n invalid(\n `database transactions may contain at most ${String(MAX_DATABASE_TRANSACTION_QUERIES)} queries`,\n );\n const validated = queries.map((query) => assertDatabaseQuery(query));\n return invokeRuntime(binding, "db", "transaction", { queries: validated });\n },\n };\n}\n',
1908
+ "error.ts": 'import type { MarinaRuntimeErrorCode, MarinaRuntimeErrorPayload } from "./contract.js";\n\nexport class MarinaRuntimeError extends Error {\n readonly code: MarinaRuntimeErrorCode;\n readonly retryable: boolean;\n\n constructor(payload: MarinaRuntimeErrorPayload) {\n super(payload.message);\n this.name = "MarinaRuntimeError";\n this.code = payload.code;\n this.retryable = payload.retryable;\n }\n}\n',
1909
+ "index.ts": 'import {\n MARINA_JOB_EXECUTION_PATH,\n MAX_JOB_INVOCATION_BYTES,\n type MarinaRuntimeBinding,\n} from "./contract.js";\nimport { MarinaRuntimeError } from "./error.js";\nimport { createStorage, type MarinaStorage } from "./storage.js";\nimport { createDatabase, type MarinaDatabase } from "./database.js";\nimport { createAI, type MarinaAI } from "./ai.js";\nimport { createJobs, type MarinaJobs } from "./jobs.js";\nimport { createCapabilities, type MarinaCapabilities } from "./capabilities.js";\nimport { createConnections, type MarinaConnections } from "./connections.js";\n\nexport type {\n MarinaRuntimeBinding,\n DatabaseQuery,\n DatabaseResult,\n DatabaseRow,\n DatabaseValue,\n AIGenerateInput,\n AIGenerateResult,\n AIMessage,\n AIMessageRole,\n AIModel,\n AIModelClass,\n JobEnqueueResult,\n JobState,\n JobStatus,\n JobTrigger,\n JobValue,\n MarinaJobInvocation,\n MarinaRuntimeErrorCode,\n MarinaRuntimeErrorPayload,\n MarinaRuntimeRequest,\n MarinaRuntimeResponse,\n StorageListResult,\n StorageObject,\n StorageObjectMetadata,\n} from "./contract.js";\nexport {\n containsDatabaseTransactionControl,\n MARINA_RUNTIME_PROTOCOL_VERSION,\n MAX_STORAGE_KEY_BYTES,\n MAX_STORAGE_OBJECT_BYTES,\n MAX_CAPABILITY_INPUT_BYTES,\n MAX_CAPABILITY_RESULT_BYTES,\n MAX_CAPABILITY_SESSION_BYTES,\n MAX_DATABASE_PARAMETER_BYTES,\n MAX_DATABASE_PARAMETERS,\n MAX_DATABASE_QUERY_BYTES,\n MAX_DATABASE_TRANSACTION_QUERIES,\n MAX_AI_INPUT_BYTES,\n MAX_AI_MESSAGE_BYTES,\n MAX_AI_MESSAGES,\n MAX_AI_OUTPUT_TOKENS,\n MAX_JOB_INPUT_BYTES,\n MAX_JOB_INVOCATION_BYTES,\n MAX_JOB_VALUE_DEPTH,\n MARINA_JOB_EXECUTION_PATH,\n} from "./contract.js";\nexport { MarinaRuntimeError } from "./error.js";\nexport type { MarinaStorage, StorageListOptions, StoragePutOptions } from "./storage.js";\nexport type { MarinaDatabase } from "./database.js";\nexport { assertAIGenerateInput } from "./ai.js";\nexport type { MarinaAI } from "./ai.js";\nexport { assertJobName, assertJobValue } from "./jobs.js";\nexport type { JobEnqueueOptions, MarinaJobs } from "./jobs.js";\nexport type { CapabilityInvokeOptions, MarinaCapabilities } from "./capabilities.js";\nexport type {\n ConnectionInvokeRequest,\n MarinaConnectionNamespace,\n MarinaConnectionOperationInput,\n MarinaConnections,\n} from "./connections.js";\n\nexport interface MarinaRuntime {\n storage: MarinaStorage;\n db: MarinaDatabase;\n ai: MarinaAI;\n jobs: MarinaJobs;\n capabilities: MarinaCapabilities;\n connections: MarinaConnections;\n}\n\nexport function createMarinaRuntime(binding: MarinaRuntimeBinding): MarinaRuntime {\n return createRuntime(binding);\n}\n\nfunction createRuntime(binding: MarinaRuntimeBinding, capabilitySession?: string): MarinaRuntime {\n return {\n storage: createStorage(binding),\n db: createDatabase(binding),\n ai: createAI(binding),\n jobs: createJobs(binding),\n capabilities: createCapabilities(binding, capabilitySession),\n connections: createConnections(binding, capabilitySession),\n };\n}\n\nexport interface MarinaAppContext {\n waitUntil(promise: Promise<unknown>): void;\n}\n\nexport interface MarinaApp {\n fetch(\n request: Request,\n marina: MarinaRuntime,\n context: MarinaAppContext,\n ): Response | Promise<Response>;\n jobs?: Record<string, MarinaJobHandler>;\n}\n\nexport interface MarinaPlatformApp {\n fetch(\n request: Request,\n environment: unknown,\n context: MarinaAppContext,\n ): Response | Promise<Response>;\n}\n\nconst platformApps = new WeakSet<object>();\n\nexport interface MarinaJobContext extends MarinaAppContext {\n runId: string;\n attempt: number;\n trigger: import("./contract.js").JobTrigger;\n scheduledFor?: string;\n}\n\nexport type MarinaJobHandler = (\n input: import("./contract.js").JobValue,\n marina: MarinaRuntime,\n context: MarinaJobContext,\n) => void | Promise<void>;\n\nasync function readBoundedJobBody(request: Request): Promise<string | null> {\n const declared = request.headers.get("content-length");\n if (declared !== null) {\n const bytes = Number(declared);\n if (Number.isFinite(bytes) && bytes > MAX_JOB_INVOCATION_BYTES) return null;\n }\n if (!request.body) return "";\n const reader = request.body.getReader();\n const chunks: Uint8Array[] = [];\n let size = 0;\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n size += value.byteLength;\n if (size > MAX_JOB_INVOCATION_BYTES) {\n await reader.cancel().catch(() => undefined);\n return null;\n }\n chunks.push(value);\n }\n const bytes = new Uint8Array(size);\n let offset = 0;\n for (const chunk of chunks) {\n bytes.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return new TextDecoder().decode(bytes);\n}\n\nasync function executeJobRequest(\n app: MarinaApp,\n request: Request,\n binding: MarinaRuntimeBinding,\n context: MarinaAppContext,\n): Promise<Response> {\n const token = request.headers.get("x-marina-job-token") ?? "";\n if (!binding.authorizeJob || !token) return new Response("Not found", { status: 404 });\n const body = await readBoundedJobBody(request);\n if (body === null) return new Response("Payload too large", { status: 413 });\n if (!(await binding.authorizeJob({ token, body })))\n return new Response("Not found", { status: 404 });\n let invocation: import("./contract.js").MarinaJobInvocation;\n try {\n invocation = JSON.parse(body) as import("./contract.js").MarinaJobInvocation;\n } catch {\n return new Response("Bad request", { status: 400 });\n }\n const handler = app.jobs?.[invocation.handler];\n if (typeof handler !== "function") return new Response("Job handler not found", { status: 404 });\n await handler(invocation.input, createRuntime(binding, invocation.capabilitySession), {\n ...context,\n runId: invocation.runId,\n attempt: invocation.attempt,\n trigger: invocation.trigger,\n ...(invocation.scheduledFor ? { scheduledFor: invocation.scheduledFor } : {}),\n });\n return new Response(null, { status: 204 });\n}\n\ninterface SubstrateEnvironment {\n MARINA?: unknown;\n}\n\nfunction runtimeBinding(environment: unknown): MarinaRuntimeBinding {\n const binding = (environment as SubstrateEnvironment | null)?.MARINA;\n if (!binding || typeof binding !== "object" || !("invoke" in binding)) {\n throw new MarinaRuntimeError({\n code: "UNAVAILABLE",\n message: "This app was not started with the Marina runtime binding",\n retryable: false,\n });\n }\n const invoke = (binding as { invoke?: unknown }).invoke;\n if (typeof invoke !== "function") {\n throw new MarinaRuntimeError({\n code: "UNAVAILABLE",\n message: "This app has an incompatible Marina runtime binding",\n retryable: false,\n });\n }\n return binding as MarinaRuntimeBinding;\n}\n\n/**\n * Define a portable Marina application. The returned adapter matches the\n * active substrate, while application code sees only Web APIs and Marina.\n */\nexport function defineApp(app: MarinaApp): MarinaPlatformApp {\n const adapter: MarinaPlatformApp = {\n fetch(request: Request, environment: unknown, context: MarinaAppContext) {\n const binding = runtimeBinding(environment);\n const url = new URL(request.url);\n if (request.method === "POST" && url.pathname === MARINA_JOB_EXECUTION_PATH) {\n return executeJobRequest(app, request, binding, context);\n }\n const capabilitySession = request.headers.get("x-platform-capability-session") ?? undefined;\n if (!capabilitySession) return app.fetch(request, createRuntime(binding), context);\n const headers = new Headers(request.headers);\n headers.delete("x-platform-capability-session");\n return app.fetch(\n new Request(request, { headers }),\n createRuntime(binding, capabilitySession),\n context,\n );\n },\n };\n platformApps.add(adapter);\n return adapter;\n}\n\n/** Internal build adapter. Application entrypoints may export the portable\n * app object directly; imports of defineApp remain compatible during the\n * transition without wrapping an already-adapted module twice. */\nexport function adaptApp(app: MarinaApp | MarinaPlatformApp): MarinaPlatformApp {\n if (!app || typeof app !== "object" || typeof app.fetch !== "function") {\n throw new TypeError("The Marina app entrypoint must export a fetch handler");\n }\n return platformApps.has(app) ? (app as MarinaPlatformApp) : defineApp(app as MarinaApp);\n}\n',
1910
+ "invoke.ts": 'import type {\n MarinaRuntimeBinding,\n MarinaRuntimeRequest,\n MarinaRuntimeService,\n} from "./contract.js";\nimport { MARINA_RUNTIME_PROTOCOL_VERSION, isRuntimeResponse } from "./contract.js";\nimport { MarinaRuntimeError } from "./error.js";\n\n/** Cross-service invocation behavior belongs to the Marina compatibility\n * layer. Individual capability clients supply only their stable service name,\n * operation, and validated provider-neutral input. */\nexport async function invokeRuntime<T>(\n binding: MarinaRuntimeBinding,\n service: MarinaRuntimeService,\n operation: MarinaRuntimeRequest["operation"],\n input: unknown,\n): Promise<T> {\n let response: unknown;\n try {\n response = await binding.invoke({\n protocolVersion: MARINA_RUNTIME_PROTOCOL_VERSION,\n requestId: crypto.randomUUID(),\n service,\n operation,\n input,\n });\n } catch (error) {\n if (error instanceof MarinaRuntimeError) throw error;\n throw new MarinaRuntimeError({\n code: "UNAVAILABLE",\n message: "Marina runtime is unavailable",\n retryable: true,\n });\n }\n if (!isRuntimeResponse(response)) {\n throw new MarinaRuntimeError({\n code: "INTERNAL",\n message: "Marina runtime returned an invalid response",\n retryable: false,\n });\n }\n if (!response.ok) throw new MarinaRuntimeError(response.error);\n return response.value as T;\n}\n',
1911
+ "jobs.ts": 'import type { JobEnqueueResult, JobStatus, JobValue, MarinaRuntimeBinding } from "./contract.js";\nimport { MAX_JOB_INPUT_BYTES, MAX_JOB_VALUE_DEPTH } from "./contract.js";\nimport { MarinaRuntimeError } from "./error.js";\nimport { invokeRuntime } from "./invoke.js";\n\nconst encoder = new TextEncoder();\nconst JOB_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/;\nconst RUN_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\nfunction invalid(message: string): never {\n throw new MarinaRuntimeError({ code: "INVALID_INPUT", message, retryable: false });\n}\n\nexport function assertJobName(value: unknown): string {\n if (typeof value !== "string" || !JOB_NAME.test(value)) invalid("invalid job name");\n return value as string;\n}\n\nexport function assertJobValue(value: unknown): JobValue {\n const seen = new Set<object>();\n const visit = (current: unknown, depth: number): void => {\n if (depth > MAX_JOB_VALUE_DEPTH) invalid("job input is nested too deeply");\n if (current === null || typeof current === "string" || typeof current === "boolean") return;\n if (typeof current === "number") {\n if (!Number.isFinite(current)) invalid("job input numbers must be finite");\n return;\n }\n if (!current || typeof current !== "object") invalid("job input must be JSON compatible");\n if (seen.has(current as object)) invalid("job input must not contain cycles");\n seen.add(current as object);\n if (Array.isArray(current)) {\n for (const item of current) visit(item, depth + 1);\n } else {\n if (Object.getPrototypeOf(current) !== Object.prototype)\n invalid("job input must contain plain objects");\n for (const [key, item] of Object.entries(current)) {\n if (key === "__proto__" || key === "constructor" || key === "prototype")\n invalid("job input contains an unsafe key");\n visit(item, depth + 1);\n }\n }\n seen.delete(current as object);\n };\n visit(value, 0);\n let serialized: string;\n try {\n serialized = JSON.stringify(value);\n } catch {\n invalid("job input must be JSON compatible");\n }\n if (serialized === undefined || encoder.encode(serialized).byteLength > MAX_JOB_INPUT_BYTES)\n invalid(`job input must not exceed ${String(MAX_JOB_INPUT_BYTES)} bytes`);\n return value as JobValue;\n}\n\nexport interface JobEnqueueOptions {\n dedupeKey?: string;\n}\n\nexport interface MarinaJobs {\n enqueue(name: string, input: JobValue, options?: JobEnqueueOptions): Promise<JobEnqueueResult>;\n get(runId: string): Promise<JobStatus>;\n}\n\nexport function createJobs(binding: MarinaRuntimeBinding): MarinaJobs {\n return {\n enqueue(name, input, options = {}) {\n const dedupeKey = options.dedupeKey;\n if (\n dedupeKey !== undefined &&\n (typeof dedupeKey !== "string" || dedupeKey.length < 1 || dedupeKey.length > 200)\n )\n invalid("job dedupe key must be between 1 and 200 characters");\n return invokeRuntime(binding, "jobs", "enqueue", {\n name: assertJobName(name),\n input: assertJobValue(input),\n ...(dedupeKey === undefined ? {} : { dedupeKey }),\n });\n },\n get(runId) {\n if (!RUN_ID.test(runId)) invalid("invalid job run id");\n return invokeRuntime(binding, "jobs", "get", { runId });\n },\n };\n}\n',
1912
+ "storage.ts": 'import type {\n MarinaRuntimeBinding,\n StorageListInput,\n StorageListResult,\n StorageObject,\n StorageObjectMetadata,\n StoragePutInput,\n} from "./contract.js";\nimport { MAX_STORAGE_KEY_BYTES, MAX_STORAGE_OBJECT_BYTES } from "./contract.js";\nimport { MarinaRuntimeError } from "./error.js";\nimport { invokeRuntime } from "./invoke.js";\n\nconst MAX_CONTENT_TYPE_LENGTH = 200;\nconst MAX_METADATA_ENTRIES = 32;\nconst MAX_METADATA_KEY_LENGTH = 128;\nconst MAX_METADATA_VALUE_LENGTH = 1024;\nconst MAX_LIST_LIMIT = 1000;\nconst encoder = new TextEncoder();\n\nfunction invalid(message: string): never {\n throw new MarinaRuntimeError({ code: "INVALID_INPUT", message, retryable: false });\n}\n\n/** Validate a logical app key. The broker adds the organization/app prefix. */\nexport function assertStorageKey(\n key: string,\n options: { allowEmpty?: boolean; allowTrailingSlash?: boolean } = {},\n): string {\n if (typeof key !== "string") invalid("storage key must be a string");\n if (key.length === 0) {\n if (options.allowEmpty) return key;\n invalid("storage key must not be empty");\n }\n if (encoder.encode(key).length > MAX_STORAGE_KEY_BYTES)\n invalid(`storage key must not exceed ${String(MAX_STORAGE_KEY_BYTES)} bytes`);\n if (key.startsWith("/")) invalid("storage key must be relative");\n if (key.endsWith("/") && !options.allowTrailingSlash)\n invalid("storage key must identify an object, not a directory");\n if (key.includes("\\\\")) invalid("storage key must use forward slashes");\n if (/\\p{Cc}/u.test(key)) invalid("storage key must not contain control characters");\n const segments = key.endsWith("/") ? key.slice(0, -1).split("/") : key.split("/");\n if (segments.some((segment) => segment === "" || segment === "." || segment === ".."))\n invalid("storage key contains an invalid path segment");\n return key;\n}\n\nfunction validateMetadata(metadata: Record<string, string> | undefined): Record<string, string> {\n if (!metadata) return {};\n const entries = Object.entries(metadata);\n if (entries.length > MAX_METADATA_ENTRIES)\n invalid(`storage metadata may contain at most ${String(MAX_METADATA_ENTRIES)} entries`);\n const normalized: Record<string, string> = {};\n for (const [key, value] of entries) {\n if (!key || key.length > MAX_METADATA_KEY_LENGTH)\n invalid(`storage metadata keys must be 1-${String(MAX_METADATA_KEY_LENGTH)} characters`);\n if (typeof value !== "string" || value.length > MAX_METADATA_VALUE_LENGTH)\n invalid(\n `storage metadata values must be strings no longer than ${String(MAX_METADATA_VALUE_LENGTH)} characters`,\n );\n Object.defineProperty(normalized, key, {\n value,\n enumerable: true,\n configurable: true,\n writable: true,\n });\n }\n return normalized;\n}\n\nexport interface StoragePutOptions {\n contentType?: string;\n metadata?: Record<string, string>;\n}\n\nexport interface StorageListOptions {\n prefix?: string;\n cursor?: string;\n limit?: number;\n}\n\nexport interface MarinaStorage {\n put(\n key: string,\n body: StoragePutInput["body"],\n options?: StoragePutOptions,\n ): Promise<StorageObjectMetadata>;\n get(key: string): Promise<StorageObject | null>;\n delete(key: string): Promise<void>;\n list(options?: StorageListOptions): Promise<StorageListResult>;\n}\n\nexport function createStorage(binding: MarinaRuntimeBinding): MarinaStorage {\n return {\n async put(key, body, options = {}) {\n assertStorageKey(key);\n if (\n typeof body !== "string" &&\n !(body instanceof Uint8Array) &&\n !(body instanceof ArrayBuffer)\n )\n invalid("storage body must be a string, Uint8Array, or ArrayBuffer");\n const bodyBytes =\n typeof body === "string"\n ? encoder.encode(body).byteLength\n : body instanceof Uint8Array\n ? body.byteLength\n : body.byteLength;\n if (bodyBytes > MAX_STORAGE_OBJECT_BYTES) {\n throw new MarinaRuntimeError({\n code: "QUOTA_EXCEEDED",\n message: `storage objects may not exceed ${String(MAX_STORAGE_OBJECT_BYTES)} bytes`,\n retryable: false,\n });\n }\n if (options.contentType && options.contentType.length > MAX_CONTENT_TYPE_LENGTH)\n invalid(\n `storage content type must not exceed ${String(MAX_CONTENT_TYPE_LENGTH)} characters`,\n );\n return invokeRuntime<StorageObjectMetadata>(binding, "storage", "put", {\n key,\n body,\n ...(options.contentType ? { contentType: options.contentType } : {}),\n metadata: validateMetadata(options.metadata),\n } satisfies StoragePutInput);\n },\n\n async get(key) {\n assertStorageKey(key);\n return invokeRuntime<StorageObject | null>(binding, "storage", "get", { key });\n },\n\n async delete(key) {\n assertStorageKey(key);\n await invokeRuntime<null>(binding, "storage", "delete", { key });\n },\n\n async list(options = {}) {\n const input: StorageListInput = {};\n if (options.prefix !== undefined)\n input.prefix = assertStorageKey(options.prefix, {\n allowEmpty: true,\n allowTrailingSlash: true,\n });\n if (options.cursor !== undefined) {\n if (!options.cursor) invalid("storage cursor must not be empty");\n input.cursor = options.cursor;\n }\n if (options.limit !== undefined) {\n if (\n !Number.isSafeInteger(options.limit) ||\n options.limit < 1 ||\n options.limit > MAX_LIST_LIMIT\n )\n invalid(`storage list limit must be between 1 and ${String(MAX_LIST_LIMIT)}`);\n input.limit = options.limit;\n }\n return invokeRuntime<StorageListResult>(binding, "storage", "list", input);\n },\n };\n}\n'
1913
+ };
1914
+ }
1915
+ });
1916
+
1917
+ // src/dev/host.ts
1918
+ import { createServer as createServer2 } from "node:http";
1919
+ import { mkdirSync as mkdirSync4, watch, writeFileSync as writeFileSync3 } from "node:fs";
1920
+ import { dirname as dirname2, join as join6, resolve } from "node:path";
1921
+ import { pathToFileURL } from "node:url";
1922
+ import { Readable } from "node:stream";
1923
+ function entrypointSource(projectDir, entrypoint) {
1924
+ return [
1925
+ `import app from ${JSON.stringify(resolve(projectDir, entrypoint))};`,
1926
+ ["import { adaptApp } ", ["fr", "om"].join(""), ' "./runtime/index.ts";'].join(""),
1927
+ "export default adaptApp(app);",
1928
+ ""
1929
+ ].join("\n");
1930
+ }
1931
+ async function bundle(options) {
1932
+ for (const [name, source] of Object.entries(DEV_RUNTIME_FILES)) {
1933
+ const path = join6(options.buildDir, "runtime", name);
1934
+ mkdirSync4(dirname2(path), { recursive: true });
1935
+ writeFileSync3(path, source);
1936
+ }
1937
+ const entry = join6(options.buildDir, "entrypoint.ts");
1938
+ writeFileSync3(entry, entrypointSource(options.projectDir, options.manifest.entrypoint));
1939
+ const outfile = join6(options.buildDir, "app.mjs");
1940
+ let esbuild;
1941
+ try {
1942
+ esbuild = await import("esbuild");
1943
+ } catch {
1944
+ throw new Error("esbuild could not load \u2014 reinstall the Marina CLI");
1945
+ }
1946
+ await esbuild.build({
1947
+ entryPoints: [entry],
1948
+ bundle: true,
1949
+ format: "esm",
1950
+ platform: "browser",
1951
+ conditions: ["workerd", "worker", "browser"],
1952
+ target: "es2022",
1953
+ external: ["cloudflare:*"],
1954
+ alias: {
1955
+ "@marina/runtime": join6(options.buildDir, "runtime", "index.ts"),
1956
+ "marina:runtime": join6(options.buildDir, "runtime", "index.ts")
1957
+ },
1958
+ absWorkingDir: options.projectDir,
1959
+ outfile,
1960
+ logLevel: "silent"
1961
+ });
1962
+ const module = await import(`${pathToFileURL(outfile).href}?v=${String(Date.now())}`);
1963
+ if (typeof module.default?.fetch !== "function") {
1964
+ throw new Error("the app entrypoint does not export a Marina app (use defineApp)");
1965
+ }
1966
+ return module.default;
1967
+ }
1968
+ function toRequest(req, port, identity) {
1969
+ const url = new URL(req.url ?? "/", `http://localhost:${String(port)}`);
1970
+ const headers = new Headers();
1971
+ for (const [name, value] of Object.entries(req.headers)) {
1972
+ if (typeof value === "string") headers.set(name, value);
1973
+ else if (Array.isArray(value)) headers.set(name, value.join(", "));
1974
+ }
1975
+ headers.set("x-platform-user-id", identity.userId);
1976
+ headers.set("x-platform-workspace-id", identity.workspaceId);
1977
+ headers.set("x-platform-capability-session", "marina-local-dev");
1978
+ const method = req.method ?? "GET";
1979
+ const body = method === "GET" || method === "HEAD" ? void 0 : Readable.toWeb(req);
1980
+ return new Request(url, {
1981
+ method,
1982
+ headers,
1983
+ ...body ? { body, duplex: "half" } : {}
1984
+ });
1985
+ }
1986
+ async function writeResponse(response, res, chrome) {
1987
+ const headers = {};
1988
+ response.headers.forEach((value, name) => {
1989
+ headers[name] = value;
1990
+ });
1991
+ const contentType = response.headers.get("content-type") ?? "";
1992
+ if (contentType.includes("text/html")) {
1993
+ const html = injectDevChrome(await response.text(), chrome);
1994
+ delete headers["content-length"];
1995
+ res.writeHead(response.status, headers);
1996
+ res.end(html);
1997
+ return;
1998
+ }
1999
+ res.writeHead(response.status, headers);
2000
+ if (!response.body) {
2001
+ res.end();
2002
+ return;
2003
+ }
2004
+ const reader = response.body.getReader();
2005
+ for (; ; ) {
2006
+ const next = await reader.read();
2007
+ if (next.done) break;
2008
+ res.write(next.value);
2009
+ }
2010
+ res.end();
2011
+ }
2012
+ async function startDevHost(options) {
2013
+ mkdirSync4(options.buildDir, { recursive: true });
2014
+ let app = await bundle(options);
2015
+ const chrome = devChromeSnippet({
2016
+ appName: options.identity.appName,
2017
+ userLabel: options.identity.userLabel
2018
+ });
2019
+ const fetchApp = (request2) => Promise.resolve(
2020
+ app.fetch(request2, { MARINA: options.binding }, { waitUntil: () => void 0 })
2021
+ );
2022
+ const server = createServer2((req, res) => {
2023
+ fetchApp(toRequest(req, options.port, options.identity)).then((response) => writeResponse(response, res, chrome)).catch((error) => {
2024
+ res.writeHead(500, { "content-type": "text/plain" });
2025
+ res.end(`marina dev: ${error.message}`);
2026
+ });
2027
+ });
2028
+ await new Promise((ready, failed) => {
2029
+ server.once("error", failed);
2030
+ server.listen(options.port, () => {
2031
+ ready();
2032
+ });
2033
+ });
2034
+ let rebuildTimer = null;
2035
+ const watchers = [];
2036
+ const scheduleRebuild = () => {
2037
+ if (rebuildTimer) clearTimeout(rebuildTimer);
2038
+ rebuildTimer = setTimeout(() => {
2039
+ bundle(options).then((next) => {
2040
+ app = next;
2041
+ options.log("reloaded");
2042
+ }).catch((error) => {
2043
+ options.log(`build failed \u2014 still serving the previous build: ${error.message}`);
2044
+ });
2045
+ }, 150);
2046
+ };
2047
+ try {
2048
+ const watcher = watch(options.projectDir, { recursive: true }, (_event, file) => {
2049
+ const name = String(file ?? "");
2050
+ if (name.startsWith(".marina") || name.startsWith("node_modules") || name.startsWith(".git")) {
2051
+ return;
2052
+ }
2053
+ scheduleRebuild();
2054
+ });
2055
+ watchers.push(watcher);
2056
+ } catch {
2057
+ options.log("file watching is unavailable \u2014 restart marina dev to pick up changes");
2058
+ }
2059
+ return {
2060
+ port: options.port,
2061
+ fetchApp,
2062
+ rebuild: async () => {
2063
+ app = await bundle(options);
2064
+ },
2065
+ close: async () => {
2066
+ for (const watcher of watchers) watcher.close();
2067
+ if (rebuildTimer) clearTimeout(rebuildTimer);
2068
+ await new Promise((done) => {
2069
+ server.close(() => {
2070
+ done();
2071
+ });
2072
+ });
2073
+ }
2074
+ };
2075
+ }
2076
+ var init_host = __esm({
2077
+ "src/dev/host.ts"() {
2078
+ "use strict";
2079
+ init_chrome();
2080
+ init_embedded_runtime();
2081
+ }
2082
+ });
2083
+
2084
+ // src/dev/cron.ts
2085
+ function matchesField(field, value, minimum, maximum) {
2086
+ return field.split(",").some((part) => {
2087
+ const step = /^(.+)\/(\d+)$/.exec(part);
2088
+ if (step?.[1] !== void 0 && step[2] !== void 0) {
2089
+ const size = Number(step[2]);
2090
+ if (!Number.isInteger(size) || size < 1) return false;
2091
+ const base = step[1];
2092
+ const [start, end] = base === "*" ? [minimum, maximum] : /^(\d+)-(\d+)$/.exec(base)?.slice(1).map(Number) ?? [Number(base), maximum];
2093
+ if (start === void 0 || Number.isNaN(start)) return false;
2094
+ return value >= start && value <= (end ?? maximum) && (value - start) % size === 0;
2095
+ }
2096
+ if (part === "*") return true;
2097
+ const range = /^(\d+)-(\d+)$/.exec(part);
2098
+ if (range) return value >= Number(range[1]) && value <= Number(range[2]);
2099
+ return Number(part) === value;
2100
+ });
2101
+ }
2102
+ function cronMatches(schedule, at) {
2103
+ const fields = schedule.trim().split(/\s+/);
2104
+ if (fields.length !== 5) return false;
2105
+ const [minute, hour, dayOfMonth, month, dayOfWeek] = fields;
2106
+ return matchesField(minute, at.getUTCMinutes(), 0, 59) && matchesField(hour, at.getUTCHours(), 0, 23) && matchesField(dayOfMonth, at.getUTCDate(), 1, 31) && matchesField(month, at.getUTCMonth() + 1, 1, 12) && matchesField(dayOfWeek, at.getUTCDay(), 0, 6);
2107
+ }
2108
+ var init_cron = __esm({
2109
+ "src/dev/cron.ts"() {
2110
+ "use strict";
2111
+ }
2112
+ });
2113
+
2114
+ // src/dev/jobs.ts
2115
+ import { randomUUID } from "node:crypto";
2116
+ var JOB_EXECUTION_PATH, DevJobRunner;
2117
+ var init_jobs = __esm({
2118
+ "src/dev/jobs.ts"() {
2119
+ "use strict";
2120
+ init_cron();
2121
+ JOB_EXECUTION_PATH = "/.marina/runtime/jobs/execute";
2122
+ DevJobRunner = class {
2123
+ runs = /* @__PURE__ */ new Map();
2124
+ deduped = /* @__PURE__ */ new Map();
2125
+ token = randomUUID();
2126
+ fetchApp = null;
2127
+ timer = null;
2128
+ lastScheduledMinute = "";
2129
+ manifest;
2130
+ identity;
2131
+ log;
2132
+ constructor(manifest, identity, log) {
2133
+ this.manifest = manifest;
2134
+ this.identity = identity;
2135
+ this.log = log;
2136
+ }
2137
+ attachApp(fetchApp) {
2138
+ this.fetchApp = fetchApp;
2139
+ }
2140
+ authorizeJob(request2) {
2141
+ void request2.body;
2142
+ return request2.token === this.token;
2143
+ }
2144
+ enqueue(input) {
2145
+ const definition = this.manifest.jobs[input.name];
2146
+ if (!definition) {
2147
+ throw new Error(`job "${input.name}" is not defined in marina.json`);
2148
+ }
2149
+ if (input.dedupeKey) {
2150
+ const existing = this.deduped.get(`${input.name}:${input.dedupeKey}`);
2151
+ const run2 = existing ? this.runs.get(existing) : void 0;
2152
+ if (run2 && (run2.state === "queued" || run2.state === "running")) return run2;
2153
+ }
2154
+ const status2 = {
2155
+ id: randomUUID(),
2156
+ name: input.name,
2157
+ state: "queued",
2158
+ attempts: 0,
2159
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2160
+ };
2161
+ this.runs.set(status2.id, status2);
2162
+ if (input.dedupeKey) this.deduped.set(`${input.name}:${input.dedupeKey}`, status2.id);
2163
+ void this.execute(status2, definition.handler, input.input, "enqueue");
2164
+ return { ...status2 };
2165
+ }
2166
+ get(id) {
2167
+ const run2 = this.runs.get(id);
2168
+ return run2 ? { ...run2 } : null;
2169
+ }
2170
+ async execute(status2, handler, input, trigger, scheduledFor) {
2171
+ const fetchApp = this.fetchApp;
2172
+ if (!fetchApp) {
2173
+ status2.state = "failed";
2174
+ status2.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
2175
+ return;
2176
+ }
2177
+ status2.state = "running";
2178
+ status2.attempts += 1;
2179
+ status2.startedAt = (/* @__PURE__ */ new Date()).toISOString();
2180
+ const invocation = {
2181
+ runId: status2.id,
2182
+ workspaceId: this.identity.workspaceId,
2183
+ appId: this.identity.appId,
2184
+ artifactDigest: `sha256:${"0".repeat(64)}`,
2185
+ environment: "development",
2186
+ jobName: status2.name,
2187
+ handler,
2188
+ input,
2189
+ attempt: status2.attempts,
2190
+ trigger,
2191
+ ...scheduledFor ? { scheduledFor } : {},
2192
+ capabilitySession: "marina-local-dev"
2193
+ };
2194
+ try {
2195
+ const response = await fetchApp(
2196
+ new Request(`http://marina.dev.local${JOB_EXECUTION_PATH}`, {
2197
+ method: "POST",
2198
+ headers: { "x-marina-job-token": this.token, "content-type": "application/json" },
2199
+ body: JSON.stringify(invocation)
2200
+ })
2201
+ );
2202
+ status2.state = response.ok ? "succeeded" : "failed";
2203
+ if (!response.ok) {
2204
+ this.log(`job ${status2.name} failed (${String(response.status)})`);
2205
+ }
2206
+ } catch (error) {
2207
+ status2.state = "failed";
2208
+ this.log(`job ${status2.name} threw: ${error.message}`);
2209
+ }
2210
+ status2.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
2211
+ }
2212
+ startSchedules() {
2213
+ if (this.timer) return;
2214
+ this.timer = setInterval(() => {
2215
+ const now = /* @__PURE__ */ new Date();
2216
+ const minute = now.toISOString().slice(0, 16);
2217
+ if (minute === this.lastScheduledMinute) return;
2218
+ this.lastScheduledMinute = minute;
2219
+ for (const [name, definition] of Object.entries(this.manifest.jobs)) {
2220
+ if (definition.schedule && cronMatches(definition.schedule, now)) {
2221
+ this.log(`schedule fired: ${name}`);
2222
+ try {
2223
+ const status2 = this.enqueue({ name, input: {} });
2224
+ const run2 = this.runs.get(status2.id);
2225
+ if (run2) run2.state = run2.state === "queued" ? "queued" : run2.state;
2226
+ } catch (error) {
2227
+ this.log(`schedule ${name} failed to enqueue: ${error.message}`);
2228
+ }
2229
+ }
2230
+ }
2231
+ }, 15e3);
2232
+ this.timer.unref?.();
2233
+ }
2234
+ stop() {
2235
+ if (this.timer) clearInterval(this.timer);
2236
+ this.timer = null;
2237
+ }
2238
+ };
2239
+ }
2240
+ });
2241
+
2242
+ // src/dev/manifest.ts
2243
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "node:fs";
2244
+ import { join as join7 } from "node:path";
2245
+ function fail(message) {
2246
+ throw new Error(`marina.json: ${message}`);
2247
+ }
2248
+ function readDevManifest(dir) {
2249
+ const path = join7(dir, "marina.json");
2250
+ if (!existsSync6(path)) {
2251
+ fail("not found \u2014 marina dev runs from a project with a marina.json");
2252
+ }
2253
+ let value;
2254
+ try {
2255
+ value = JSON.parse(readFileSync6(path, "utf8"));
2256
+ } catch {
2257
+ fail("is not valid JSON");
2258
+ }
2259
+ if (!value || typeof value !== "object" || Array.isArray(value)) fail("must contain an object");
2260
+ const manifest = value;
2261
+ if (typeof manifest.entrypoint !== "string" || manifest.entrypoint.length === 0) {
2262
+ fail('needs an "entrypoint" \u2014 static projects have no server to develop against');
2263
+ }
2264
+ const runtime = manifest.runtime ?? {};
2265
+ const capabilities = Array.isArray(manifest.capabilities) ? manifest.capabilities.filter((name) => typeof name === "string") : [];
2266
+ const connections = {};
2267
+ if (manifest.connections !== void 0) {
2268
+ if (!manifest.connections || typeof manifest.connections !== "object" || Array.isArray(manifest.connections)) {
2269
+ fail('"connections" must map connector names to declarations');
2270
+ }
2271
+ for (const [connector, declared] of Object.entries(manifest.connections)) {
2272
+ if (!CONNECTION_ID.test(connector)) fail(`"${connector}" is not a connector name`);
2273
+ if (!Array.isArray(declared)) fail(`connections.${connector} must be an array`);
2274
+ connections[connector] = declared.map((entry, index) => {
2275
+ const binding = entry;
2276
+ if (typeof binding.connection !== "string" || !CONNECTION_ID.test(binding.connection)) {
2277
+ fail(`connections.${connector}[${String(index)}] needs an explicit "connection" id`);
2278
+ }
2279
+ const operations = Array.isArray(binding.capabilities) ? binding.capabilities : [];
2280
+ if (!operations.every((op) => typeof op === "string" && NAME.test(op))) {
2281
+ fail(`connections.${connector}[${String(index)}] has an invalid operation name`);
2282
+ }
2283
+ return { connection: binding.connection, capabilities: operations };
2284
+ });
2285
+ }
2286
+ }
2287
+ const jobs = {};
2288
+ if (manifest.jobs && typeof manifest.jobs === "object" && !Array.isArray(manifest.jobs)) {
2289
+ for (const [name, definition] of Object.entries(manifest.jobs)) {
2290
+ const job = definition;
2291
+ if (typeof job.handler === "string") {
2292
+ jobs[name] = {
2293
+ handler: job.handler,
2294
+ ...typeof job.schedule === "string" ? { schedule: job.schedule } : {}
2295
+ };
2296
+ }
2297
+ }
2298
+ }
2299
+ return {
2300
+ ...typeof manifest.name === "string" ? { name: manifest.name } : {},
2301
+ entrypoint: manifest.entrypoint,
2302
+ runtime,
2303
+ capabilities,
2304
+ connections,
2305
+ jobs
2306
+ };
2307
+ }
2308
+ var CONNECTION_ID, NAME;
2309
+ var init_manifest = __esm({
2310
+ "src/dev/manifest.ts"() {
2311
+ "use strict";
2312
+ CONNECTION_ID = /^[a-z][a-z0-9_]{0,39}$/;
2313
+ NAME = /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/;
2314
+ }
2315
+ });
76
2316
 
77
- // src/api.ts
78
- var LOGIN_EXCHANGE_TIMEOUT_MS = 15e3;
79
- var ApiError = class extends Error {
80
- code;
81
- status;
82
- constructor(code, message, status2) {
83
- super(message);
84
- this.code = code;
85
- this.status = status2;
2317
+ // src/dev/storage.ts
2318
+ import { createHash as createHash2 } from "node:crypto";
2319
+ import { mkdirSync as mkdirSync5, readdirSync as readdirSync3, readFileSync as readFileSync7, rmSync, statSync, writeFileSync as writeFileSync4 } from "node:fs";
2320
+ import { dirname as dirname3, join as join8, normalize, sep } from "node:path";
2321
+ var MAX_LIST_LIMIT, LocalStorage;
2322
+ var init_storage = __esm({
2323
+ "src/dev/storage.ts"() {
2324
+ "use strict";
2325
+ MAX_LIST_LIMIT = 1e3;
2326
+ LocalStorage = class {
2327
+ objects;
2328
+ sidecars;
2329
+ constructor(root) {
2330
+ this.objects = join8(root, "objects");
2331
+ this.sidecars = join8(root, "meta");
2332
+ mkdirSync5(this.objects, { recursive: true });
2333
+ mkdirSync5(this.sidecars, { recursive: true });
2334
+ }
2335
+ /** Keys were validated by the runtime client; this guards the filesystem
2336
+ * anyway so a hand-crafted invoke cannot escape the store. */
2337
+ objectPath(base, key) {
2338
+ const path = normalize(join8(base, key));
2339
+ if (path !== base && !path.startsWith(base + sep)) {
2340
+ throw new Error("storage key escapes the local store");
2341
+ }
2342
+ return path;
2343
+ }
2344
+ put(input) {
2345
+ const body = typeof input.body === "string" ? Buffer.from(input.body, "utf8") : input.body instanceof ArrayBuffer ? Buffer.from(input.body) : Buffer.from(input.body);
2346
+ const sidecar = {
2347
+ ...input.contentType ? { contentType: input.contentType } : {},
2348
+ metadata: input.metadata ?? {},
2349
+ etag: createHash2("sha256").update(body).digest("hex").slice(0, 32),
2350
+ uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
2351
+ size: body.byteLength
2352
+ };
2353
+ const objectPath = this.objectPath(this.objects, input.key);
2354
+ mkdirSync5(dirname3(objectPath), { recursive: true });
2355
+ writeFileSync4(objectPath, body);
2356
+ const sidecarPath = this.objectPath(this.sidecars, `${input.key}.json`);
2357
+ mkdirSync5(dirname3(sidecarPath), { recursive: true });
2358
+ writeFileSync4(sidecarPath, JSON.stringify(sidecar));
2359
+ return { key: input.key, ...sidecar };
2360
+ }
2361
+ get(key) {
2362
+ const metadata = this.head(key);
2363
+ if (!metadata) return null;
2364
+ const body = readFileSync7(this.objectPath(this.objects, key));
2365
+ return { ...metadata, body: new Uint8Array(body) };
2366
+ }
2367
+ head(key) {
2368
+ try {
2369
+ const sidecar = JSON.parse(
2370
+ readFileSync7(this.objectPath(this.sidecars, `${key}.json`), "utf8")
2371
+ );
2372
+ return { key, ...sidecar };
2373
+ } catch {
2374
+ return null;
2375
+ }
2376
+ }
2377
+ delete(key) {
2378
+ rmSync(this.objectPath(this.objects, key), { force: true });
2379
+ rmSync(this.objectPath(this.sidecars, `${key}.json`), { force: true });
2380
+ }
2381
+ list(input = {}) {
2382
+ const limit = Math.min(input.limit ?? MAX_LIST_LIMIT, MAX_LIST_LIMIT);
2383
+ const keys = [];
2384
+ const walk = (dir, prefix) => {
2385
+ let entries;
2386
+ try {
2387
+ entries = readdirSync3(dir);
2388
+ } catch {
2389
+ return;
2390
+ }
2391
+ for (const entry of entries.toSorted()) {
2392
+ const path = join8(dir, entry);
2393
+ if (statSync(path).isDirectory()) walk(path, `${prefix}${entry}/`);
2394
+ else if (entry.endsWith(".json")) keys.push(`${prefix}${entry.slice(0, -5)}`);
2395
+ }
2396
+ };
2397
+ walk(this.sidecars, "");
2398
+ const filtered = keys.filter((key) => input.prefix ? key.startsWith(input.prefix) : true).filter((key) => input.cursor ? key > input.cursor : true);
2399
+ const page2 = filtered.slice(0, limit);
2400
+ const truncated = filtered.length > page2.length;
2401
+ const objects = page2.map((key) => this.head(key)).filter((object) => object !== null);
2402
+ return {
2403
+ objects,
2404
+ ...truncated && page2.length > 0 ? { cursor: page2[page2.length - 1] } : {},
2405
+ truncated
2406
+ };
2407
+ }
2408
+ };
86
2409
  }
87
- };
88
- async function request(path, init) {
2410
+ });
2411
+
2412
+ // src/dev/index.ts
2413
+ var dev_exports = {};
2414
+ __export(dev_exports, {
2415
+ runDev: () => runDev
2416
+ });
2417
+ import { existsSync as existsSync7 } from "node:fs";
2418
+ import { join as join9, resolve as resolve2 } from "node:path";
2419
+ async function runDev(options) {
89
2420
  const token = getToken();
90
- if (!token) throw new ApiError("unauthenticated", "not signed in \u2014 run `marina setup`", 401);
91
- const res = await fetch(`${apiUrl()}${path}`, {
92
- ...init,
93
- headers: { authorization: `Bearer ${token}`, ...init?.headers }
94
- });
95
- const body = await res.json().catch(() => ({}));
96
- if (!res.ok) {
97
- throw new ApiError(
98
- body.error?.code ?? "error",
99
- body.error?.message ?? `request failed (${String(res.status)})`,
100
- res.status
101
- );
2421
+ if (!token) {
2422
+ throw new Error("not signed in \u2014 run `marina setup` first");
102
2423
  }
103
- return body;
104
- }
105
- async function exchangeCliLogin(code, codeVerifier) {
106
- let res;
107
- try {
108
- res = await fetch(`${apiUrl()}/cli/auth/exchange`, {
109
- method: "POST",
110
- headers: { "content-type": "application/json" },
111
- body: JSON.stringify({ code, code_verifier: codeVerifier }),
112
- signal: AbortSignal.timeout(LOGIN_EXCHANGE_TIMEOUT_MS)
113
- });
114
- } catch (error) {
115
- if (error.name === "TimeoutError") {
116
- throw new ApiError(
117
- "login_timeout",
118
- "the login confirmation request timed out after 15 seconds \u2014 run `marina setup` to try again",
119
- 408
120
- );
121
- }
122
- throw error;
2424
+ const projectDir = resolve2(options.dir ?? ".");
2425
+ const manifest = readDevManifest(projectDir);
2426
+ const port = options.port ? Number(options.port) : DEFAULT_PORT;
2427
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
2428
+ throw new Error("--port must be a number between 1 and 65535");
123
2429
  }
124
- const body = await res.json().catch(() => ({}));
125
- if (!res.ok || !body.token) {
126
- throw new ApiError(
127
- body.error?.code ?? "login_failed",
128
- body.error?.message ?? `login exchange failed (${String(res.status)})`,
129
- res.status
2430
+ const identity = await me();
2431
+ const appName = manifest.name ?? "Marina app";
2432
+ say(`${bold("marina dev")} \xB7 ${appName}`);
2433
+ const declared = Object.entries(manifest.connections).flatMap(
2434
+ ([connector, bindings]) => bindings.map((binding2) => `${connector}/${binding2.connection}`)
2435
+ );
2436
+ if (declared.length > 0) {
2437
+ say(
2438
+ dim(
2439
+ `connections: ${declared.join(", ")} \u2014 bridged to ${apiUrl()} as ${identity.user.email}; grants are checked per call`
2440
+ )
130
2441
  );
131
2442
  }
132
- return body.token;
133
- }
134
- async function startDeploy(zip, name, app) {
135
- const form = new FormData();
136
- form.set(
137
- "code",
138
- new Blob([new Uint8Array(zip).buffer], { type: "application/zip" }),
139
- "upload.zip"
140
- );
141
- form.set("name", name);
142
- form.set("source", "cli");
143
- if (app) form.set("app", app);
144
- const res = await request("/v1/deploys", {
145
- method: "POST",
146
- body: form
147
- });
148
- return res.deploy;
149
- }
150
- async function pollDeploy(id, onProgress = () => void 0) {
151
- for (; ; ) {
152
- const { deploy: deploy2 } = await request(`/v1/deploys/${id}`);
153
- onProgress(deploy2);
154
- if (deploy2.status !== "queued" && deploy2.status !== "building") return deploy2;
155
- await new Promise((resolve2) => setTimeout(resolve2, 500));
2443
+ const devDir = join9(projectDir, ".marina", "dev");
2444
+ const storage = manifest.runtime.storage === "v1" ? new LocalStorage(join9(devDir, "storage")) : null;
2445
+ let database = null;
2446
+ if (manifest.runtime.db === "v1") {
2447
+ database = await LocalDatabase.open(join9(devDir, "db"));
2448
+ const applied = await database.applyMigrations(projectDir);
2449
+ const total = existsSync7(join9(projectDir, "marina", "migrations")) ? "" : " (no marina/migrations yet)";
2450
+ say(
2451
+ `${green("ok")} db \u2014 embedded Postgres ready, ${String(applied.length)} migrations applied${total}`
2452
+ );
156
2453
  }
157
- }
158
- async function listApps() {
159
- const res = await request("/v1/apps");
160
- return res.apps;
161
- }
162
- var getApp = (idOrSlug) => request(`/v1/apps/${encodeURIComponent(idOrSlug)}`);
163
- async function getAppUrl(idOrSlug) {
164
- return (await getApp(idOrSlug)).url;
165
- }
166
- async function listVersions(app) {
167
- const res = await request(
168
- `/v1/apps/${encodeURIComponent(app)}/versions`
169
- );
170
- return res.versions;
171
- }
172
- async function listDeploys(app, limit = 10) {
173
- const res = await request(
174
- `/v1/apps/${encodeURIComponent(app)}/deploys?limit=${String(limit)}`
175
- );
176
- return res.deploys;
177
- }
178
- async function restoreVersion(versionId) {
179
- const res = await request(`/v1/versions/${versionId}/restore`, {
180
- method: "POST"
2454
+ const jobs = manifest.runtime.jobs === "v1" ? new DevJobRunner(
2455
+ manifest,
2456
+ { workspaceId: identity.workspace.id, appId: "local-dev" },
2457
+ (line) => {
2458
+ say(dim(line));
2459
+ }
2460
+ ) : null;
2461
+ const binding = createDevBinding({
2462
+ manifest,
2463
+ storage,
2464
+ database,
2465
+ jobs,
2466
+ bridge: { apiUrl: apiUrl(), token }
181
2467
  });
182
- return res.version;
183
- }
184
- async function listEnv(app) {
185
- const res = await request(`/v1/apps/${encodeURIComponent(app)}/env`);
186
- return res.env;
187
- }
188
- async function setEnv(app, key, value, secret) {
189
- await request(`/v1/apps/${encodeURIComponent(app)}/env/${encodeURIComponent(key)}`, {
190
- method: "PUT",
191
- headers: { "content-type": "application/json" },
192
- body: JSON.stringify({ value, kind: secret ? "secret" : "plain" })
2468
+ const host = await startDevHost({
2469
+ projectDir,
2470
+ buildDir: join9(devDir, "build"),
2471
+ manifest,
2472
+ binding,
2473
+ port,
2474
+ identity: {
2475
+ userId: identity.user.id,
2476
+ workspaceId: identity.workspace.id,
2477
+ userLabel: identity.user.email,
2478
+ appName
2479
+ },
2480
+ log: (line) => {
2481
+ say(dim(line));
2482
+ }
193
2483
  });
194
- }
195
- async function deleteEnv(app, key) {
196
- await request(`/v1/apps/${encodeURIComponent(app)}/env/${encodeURIComponent(key)}`, {
197
- method: "DELETE"
2484
+ jobs?.attachApp(host.fetchApp);
2485
+ if (jobs && options.schedules) {
2486
+ jobs.startSchedules();
2487
+ say(dim("schedules: on"));
2488
+ } else if (jobs && Object.values(manifest.jobs).some((job) => job.schedule)) {
2489
+ say(dim("schedules: off \u2014 pass --schedules to run them locally"));
2490
+ }
2491
+ if (manifest.runtime.ai === "v1") {
2492
+ say(dim("marina.ai: bridged to Marina; usage is metered"));
2493
+ }
2494
+ say(
2495
+ `${green("\u2192")} http://localhost:${String(port)} ${dim(`(signed in as ${identity.user.email})`)}`
2496
+ );
2497
+ await new Promise((stop) => {
2498
+ const shutdown = () => {
2499
+ jobs?.stop();
2500
+ void host.close().then(stop);
2501
+ };
2502
+ process.once("SIGINT", shutdown);
2503
+ process.once("SIGTERM", shutdown);
198
2504
  });
199
2505
  }
2506
+ var DEFAULT_PORT;
2507
+ var init_dev = __esm({
2508
+ "src/dev/index.ts"() {
2509
+ "use strict";
2510
+ init_api();
2511
+ init_config();
2512
+ init_output();
2513
+ init_binding();
2514
+ init_db();
2515
+ init_host();
2516
+ init_jobs();
2517
+ init_manifest();
2518
+ init_storage();
2519
+ DEFAULT_PORT = 5990;
2520
+ }
2521
+ });
2522
+
2523
+ // src/index.ts
2524
+ init_api();
2525
+ init_api();
2526
+ init_config();
2527
+ import { resolve as resolve3 } from "node:path";
2528
+ import { parseArgs } from "node:util";
200
2529
 
201
2530
  // ../../node_modules/.pnpm/fflate@0.8.3/node_modules/fflate/esm/index.mjs
202
2531
  import { createRequire } from "module";
@@ -285,13 +2614,13 @@ var fdeb = new u8([
285
2614
  var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
286
2615
  var freb = function(eb, start) {
287
2616
  var b = new u16(31);
288
- for (var i = 0; i < 31; ++i) {
289
- b[i] = start += 1 << eb[i - 1];
2617
+ for (var i2 = 0; i2 < 31; ++i2) {
2618
+ b[i2] = start += 1 << eb[i2 - 1];
290
2619
  }
291
2620
  var r = new i32(b[30]);
292
- for (var i = 1; i < 30; ++i) {
293
- for (var j = b[i]; j < b[i + 1]; ++j) {
294
- r[j] = j - b[i] << 5 | i;
2621
+ for (var i2 = 1; i2 < 30; ++i2) {
2622
+ for (var j = b[i2]; j < b[i2 + 1]; ++j) {
2623
+ r[j] = j - b[i2] << 5 | i2;
295
2624
  }
296
2625
  }
297
2626
  return { b, r };
@@ -314,25 +2643,25 @@ var x;
314
2643
  var i;
315
2644
  var hMap = (function(cd, mb, r) {
316
2645
  var s = cd.length;
317
- var i = 0;
2646
+ var i2 = 0;
318
2647
  var l = new u16(mb);
319
- for (; i < s; ++i) {
320
- if (cd[i])
321
- ++l[cd[i] - 1];
2648
+ for (; i2 < s; ++i2) {
2649
+ if (cd[i2])
2650
+ ++l[cd[i2] - 1];
322
2651
  }
323
2652
  var le = new u16(mb);
324
- for (i = 1; i < mb; ++i) {
325
- le[i] = le[i - 1] + l[i - 1] << 1;
2653
+ for (i2 = 1; i2 < mb; ++i2) {
2654
+ le[i2] = le[i2 - 1] + l[i2 - 1] << 1;
326
2655
  }
327
2656
  var co;
328
2657
  if (r) {
329
2658
  co = new u16(1 << mb);
330
2659
  var rvb = 15 - mb;
331
- for (i = 0; i < s; ++i) {
332
- if (cd[i]) {
333
- var sv = i << 4 | cd[i];
334
- var r_1 = mb - cd[i];
335
- var v = le[cd[i] - 1]++ << r_1;
2660
+ for (i2 = 0; i2 < s; ++i2) {
2661
+ if (cd[i2]) {
2662
+ var sv = i2 << 4 | cd[i2];
2663
+ var r_1 = mb - cd[i2];
2664
+ var v = le[cd[i2] - 1]++ << r_1;
336
2665
  for (var m = v | (1 << r_1) - 1; v <= m; ++v) {
337
2666
  co[rev[v] >> rvb] = sv;
338
2667
  }
@@ -340,9 +2669,9 @@ var hMap = (function(cd, mb, r) {
340
2669
  }
341
2670
  } else {
342
2671
  co = new u16(s);
343
- for (i = 0; i < s; ++i) {
344
- if (cd[i]) {
345
- co[i] = rev[le[cd[i] - 1]++] >> 15 - cd[i];
2672
+ for (i2 = 0; i2 < s; ++i2) {
2673
+ if (cd[i2]) {
2674
+ co[i2] = rev[le[cd[i2] - 1]++] >> 15 - cd[i2];
346
2675
  }
347
2676
  }
348
2677
  }
@@ -419,9 +2748,9 @@ var wbits16 = function(d, p, v) {
419
2748
  };
420
2749
  var hTree = function(d, mb) {
421
2750
  var t = [];
422
- for (var i = 0; i < d.length; ++i) {
423
- if (d[i])
424
- t.push({ s: i, f: d[i] });
2751
+ for (var i2 = 0; i2 < d.length; ++i2) {
2752
+ if (d[i2])
2753
+ t.push({ s: i2, f: d[i2] });
425
2754
  }
426
2755
  var s = t.length;
427
2756
  var t2 = t.slice();
@@ -436,28 +2765,28 @@ var hTree = function(d, mb) {
436
2765
  return a.f - b.f;
437
2766
  });
438
2767
  t.push({ s: -1, f: 25001 });
439
- var l = t[0], r = t[1], i0 = 0, i1 = 1, i2 = 2;
2768
+ var l = t[0], r = t[1], i0 = 0, i1 = 1, i22 = 2;
440
2769
  t[0] = { s: -1, f: l.f + r.f, l, r };
441
2770
  while (i1 != s - 1) {
442
- l = t[t[i0].f < t[i2].f ? i0++ : i2++];
443
- r = t[i0 != i1 && t[i0].f < t[i2].f ? i0++ : i2++];
2771
+ l = t[t[i0].f < t[i22].f ? i0++ : i22++];
2772
+ r = t[i0 != i1 && t[i0].f < t[i22].f ? i0++ : i22++];
444
2773
  t[i1++] = { s: -1, f: l.f + r.f, l, r };
445
2774
  }
446
2775
  var maxSym = t2[0].s;
447
- for (var i = 1; i < s; ++i) {
448
- if (t2[i].s > maxSym)
449
- maxSym = t2[i].s;
2776
+ for (var i2 = 1; i2 < s; ++i2) {
2777
+ if (t2[i2].s > maxSym)
2778
+ maxSym = t2[i2].s;
450
2779
  }
451
2780
  var tr = new u16(maxSym + 1);
452
2781
  var mbt = ln(t[i1 - 1], tr, 0);
453
2782
  if (mbt > mb) {
454
- var i = 0, dt = 0;
2783
+ var i2 = 0, dt = 0;
455
2784
  var lft = mbt - mb, cst = 1 << lft;
456
2785
  t2.sort(function(a, b) {
457
2786
  return tr[b.s] - tr[a.s] || a.f - b.f;
458
2787
  });
459
- for (; i < s; ++i) {
460
- var i2_1 = t2[i].s;
2788
+ for (; i2 < s; ++i2) {
2789
+ var i2_1 = t2[i2].s;
461
2790
  if (tr[i2_1] > mb) {
462
2791
  dt += cst - (1 << mbt - tr[i2_1]);
463
2792
  tr[i2_1] = mb;
@@ -466,14 +2795,14 @@ var hTree = function(d, mb) {
466
2795
  }
467
2796
  dt >>= lft;
468
2797
  while (dt > 0) {
469
- var i2_2 = t2[i].s;
2798
+ var i2_2 = t2[i2].s;
470
2799
  if (tr[i2_2] < mb)
471
2800
  dt -= 1 << mb - tr[i2_2]++ - 1;
472
2801
  else
473
- ++i;
2802
+ ++i2;
474
2803
  }
475
- for (; i >= 0 && dt; --i) {
476
- var i2_3 = t2[i].s;
2804
+ for (; i2 >= 0 && dt; --i2) {
2805
+ var i2_3 = t2[i2].s;
477
2806
  if (tr[i2_3] == mb) {
478
2807
  --tr[i2_3];
479
2808
  ++dt;
@@ -495,8 +2824,8 @@ var lc = function(c) {
495
2824
  var w = function(v) {
496
2825
  cl[cli++] = v;
497
2826
  };
498
- for (var i = 1; i <= s; ++i) {
499
- if (c[i] == cln && i != s)
2827
+ for (var i2 = 1; i2 <= s; ++i2) {
2828
+ if (c[i2] == cln && i2 != s)
500
2829
  ++cls;
501
2830
  else {
502
2831
  if (!cln && cls > 2) {
@@ -516,15 +2845,15 @@ var lc = function(c) {
516
2845
  while (cls--)
517
2846
  w(cln);
518
2847
  cls = 1;
519
- cln = c[i];
2848
+ cln = c[i2];
520
2849
  }
521
2850
  }
522
2851
  return { c: cl.subarray(0, cli), n: s };
523
2852
  };
524
2853
  var clen = function(cf, cl) {
525
2854
  var l = 0;
526
- for (var i = 0; i < cl.length; ++i)
527
- l += cf[i] * cl[i];
2855
+ for (var i2 = 0; i2 < cl.length; ++i2)
2856
+ l += cf[i2] * cl[i2];
528
2857
  return l;
529
2858
  };
530
2859
  var wfblk = function(out, pos, dat) {
@@ -534,8 +2863,8 @@ var wfblk = function(out, pos, dat) {
534
2863
  out[o + 1] = s >> 8;
535
2864
  out[o + 2] = out[o] ^ 255;
536
2865
  out[o + 3] = out[o + 1] ^ 255;
537
- for (var i = 0; i < s; ++i)
538
- out[o + i + 4] = dat[i];
2866
+ for (var i2 = 0; i2 < s; ++i2)
2867
+ out[o + i2 + 4] = dat[i2];
539
2868
  return (o + 4 + s) * 8;
540
2869
  };
541
2870
  var wblk = function(dat, out, final, syms, lf, df, eb, li, bs, bl, p) {
@@ -546,10 +2875,10 @@ var wblk = function(dat, out, final, syms, lf, df, eb, li, bs, bl, p) {
546
2875
  var _c = lc(dlt), lclt = _c.c, nlc = _c.n;
547
2876
  var _d = lc(ddt), lcdt = _d.c, ndc = _d.n;
548
2877
  var lcfreq = new u16(19);
549
- for (var i = 0; i < lclt.length; ++i)
550
- ++lcfreq[lclt[i] & 31];
551
- for (var i = 0; i < lcdt.length; ++i)
552
- ++lcfreq[lcdt[i] & 31];
2878
+ for (var i2 = 0; i2 < lclt.length; ++i2)
2879
+ ++lcfreq[lclt[i2] & 31];
2880
+ for (var i2 = 0; i2 < lcdt.length; ++i2)
2881
+ ++lcfreq[lcdt[i2] & 31];
553
2882
  var _e = hTree(lcfreq, 7), lct = _e.t, mlcb = _e.l;
554
2883
  var nlcc = 19;
555
2884
  for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc)
@@ -568,24 +2897,24 @@ var wblk = function(dat, out, final, syms, lf, df, eb, li, bs, bl, p) {
568
2897
  wbits(out, p + 5, ndc - 1);
569
2898
  wbits(out, p + 10, nlcc - 4);
570
2899
  p += 14;
571
- for (var i = 0; i < nlcc; ++i)
572
- wbits(out, p + 3 * i, lct[clim[i]]);
2900
+ for (var i2 = 0; i2 < nlcc; ++i2)
2901
+ wbits(out, p + 3 * i2, lct[clim[i2]]);
573
2902
  p += 3 * nlcc;
574
2903
  var lcts = [lclt, lcdt];
575
2904
  for (var it = 0; it < 2; ++it) {
576
2905
  var clct = lcts[it];
577
- for (var i = 0; i < clct.length; ++i) {
578
- var len = clct[i] & 31;
2906
+ for (var i2 = 0; i2 < clct.length; ++i2) {
2907
+ var len = clct[i2] & 31;
579
2908
  wbits(out, p, llm[len]), p += lct[len];
580
2909
  if (len > 15)
581
- wbits(out, p, clct[i] >> 5 & 127), p += clct[i] >> 12;
2910
+ wbits(out, p, clct[i2] >> 5 & 127), p += clct[i2] >> 12;
582
2911
  }
583
2912
  }
584
2913
  } else {
585
2914
  lm = flm, ll = flt, dm = fdm, dl = fdt;
586
2915
  }
587
- for (var i = 0; i < li; ++i) {
588
- var sym = syms[i];
2916
+ for (var i2 = 0; i2 < li; ++i2) {
2917
+ var sym = syms[i2];
589
2918
  if (sym > 255) {
590
2919
  var len = sym >> 18 & 31;
591
2920
  wbits16(out, p, lm[len + 257]), p += ll[len + 257];
@@ -618,36 +2947,36 @@ var dflt = function(dat, lvl, plvl, pre, post, st) {
618
2947
  var msk_1 = (1 << plvl) - 1;
619
2948
  var prev = st.p || new u16(32768), head = st.h || new u16(msk_1 + 1);
620
2949
  var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1;
621
- var hsh = function(i2) {
622
- return (dat[i2] ^ dat[i2 + 1] << bs1_1 ^ dat[i2 + 2] << bs2_1) & msk_1;
2950
+ var hsh = function(i3) {
2951
+ return (dat[i3] ^ dat[i3 + 1] << bs1_1 ^ dat[i3 + 2] << bs2_1) & msk_1;
623
2952
  };
624
2953
  var syms = new i32(25e3);
625
2954
  var lf = new u16(288), df = new u16(32);
626
- var lc_1 = 0, eb = 0, i = st.i || 0, li = 0, wi = st.w || 0, bs = 0;
627
- for (; i + 2 < s; ++i) {
628
- var hv = hsh(i);
629
- var imod = i & 32767, pimod = head[hv];
2955
+ var lc_1 = 0, eb = 0, i2 = st.i || 0, li = 0, wi = st.w || 0, bs = 0;
2956
+ for (; i2 + 2 < s; ++i2) {
2957
+ var hv = hsh(i2);
2958
+ var imod = i2 & 32767, pimod = head[hv];
630
2959
  prev[imod] = pimod;
631
2960
  head[hv] = imod;
632
- if (wi <= i) {
633
- var rem = s - i;
2961
+ if (wi <= i2) {
2962
+ var rem = s - i2;
634
2963
  if ((lc_1 > 7e3 || li > 24576) && (rem > 423 || !lst)) {
635
- pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i - bs, pos);
636
- li = lc_1 = eb = 0, bs = i;
2964
+ pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i2 - bs, pos);
2965
+ li = lc_1 = eb = 0, bs = i2;
637
2966
  for (var j = 0; j < 286; ++j)
638
2967
  lf[j] = 0;
639
2968
  for (var j = 0; j < 30; ++j)
640
2969
  df[j] = 0;
641
2970
  }
642
2971
  var l = 2, d = 0, ch_1 = c, dif = imod - pimod & 32767;
643
- if (rem > 2 && hv == hsh(i - dif)) {
2972
+ if (rem > 2 && hv == hsh(i2 - dif)) {
644
2973
  var maxn = Math.min(n, rem) - 1;
645
- var maxd = Math.min(32767, i);
2974
+ var maxd = Math.min(32767, i2);
646
2975
  var ml = Math.min(258, rem);
647
2976
  while (dif <= maxd && --ch_1 && imod != pimod) {
648
- if (dat[i + l] == dat[i + l - dif]) {
2977
+ if (dat[i2 + l] == dat[i2 + l - dif]) {
649
2978
  var nl = 0;
650
- for (; nl < ml && dat[i + nl] == dat[i + nl - dif]; ++nl)
2979
+ for (; nl < ml && dat[i2 + nl] == dat[i2 + nl - dif]; ++nl)
651
2980
  ;
652
2981
  if (nl > l) {
653
2982
  l = nl, d = dif;
@@ -656,7 +2985,7 @@ var dflt = function(dat, lvl, plvl, pre, post, st) {
656
2985
  var mmd = Math.min(dif, nl - 2);
657
2986
  var md = 0;
658
2987
  for (var j = 0; j < mmd; ++j) {
659
- var ti = i - dif + j & 32767;
2988
+ var ti = i2 - dif + j & 32767;
660
2989
  var pti = prev[ti];
661
2990
  var cd = ti - pti & 32767;
662
2991
  if (cd > md)
@@ -674,32 +3003,32 @@ var dflt = function(dat, lvl, plvl, pre, post, st) {
674
3003
  eb += fleb[lin] + fdeb[din];
675
3004
  ++lf[257 + lin];
676
3005
  ++df[din];
677
- wi = i + l;
3006
+ wi = i2 + l;
678
3007
  ++lc_1;
679
3008
  } else {
680
- syms[li++] = dat[i];
681
- ++lf[dat[i]];
3009
+ syms[li++] = dat[i2];
3010
+ ++lf[dat[i2]];
682
3011
  }
683
3012
  }
684
3013
  }
685
- for (i = Math.max(i, wi); i < s; ++i) {
686
- syms[li++] = dat[i];
687
- ++lf[dat[i]];
3014
+ for (i2 = Math.max(i2, wi); i2 < s; ++i2) {
3015
+ syms[li++] = dat[i2];
3016
+ ++lf[dat[i2]];
688
3017
  }
689
- pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i - bs, pos);
3018
+ pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i2 - bs, pos);
690
3019
  if (!lst) {
691
3020
  st.r = pos & 7 | w[pos / 8 | 0] << 3;
692
3021
  pos -= 7;
693
- st.h = head, st.p = prev, st.i = i, st.w = wi;
3022
+ st.h = head, st.p = prev, st.i = i2, st.w = wi;
694
3023
  }
695
3024
  } else {
696
- for (var i = st.w || 0; i < s + lst; i += 65535) {
697
- var e = i + 65535;
3025
+ for (var i2 = st.w || 0; i2 < s + lst; i2 += 65535) {
3026
+ var e = i2 + 65535;
698
3027
  if (e >= s) {
699
3028
  w[pos / 8 | 0] = lst;
700
3029
  e = s;
701
3030
  }
702
- pos = wfblk(w, pos + 1, dat.subarray(i, e));
3031
+ pos = wfblk(w, pos + 1, dat.subarray(i2, e));
703
3032
  }
704
3033
  st.i = s;
705
3034
  }
@@ -707,11 +3036,11 @@ var dflt = function(dat, lvl, plvl, pre, post, st) {
707
3036
  };
708
3037
  var crct = /* @__PURE__ */ (function() {
709
3038
  var t = new Int32Array(256);
710
- for (var i = 0; i < 256; ++i) {
711
- var c = i, k = 9;
3039
+ for (var i2 = 0; i2 < 256; ++i2) {
3040
+ var c = i2, k = 9;
712
3041
  while (--k)
713
3042
  c = (c & 1 && -306674912) ^ c >>> 1;
714
- t[i] = c;
3043
+ t[i2] = c;
715
3044
  }
716
3045
  return t;
717
3046
  })();
@@ -720,8 +3049,8 @@ var crc = function() {
720
3049
  return {
721
3050
  p: function(d) {
722
3051
  var cr = c;
723
- for (var i = 0; i < d.length; ++i)
724
- cr = crct[cr & 255 ^ d[i]] ^ cr >>> 8;
3052
+ for (var i2 = 0; i2 < d.length; ++i2)
3053
+ cr = crct[cr & 255 ^ d[i2]] ^ cr >>> 8;
725
3054
  c = cr;
726
3055
  },
727
3056
  d: function() {
@@ -782,8 +3111,8 @@ try {
782
3111
  function strToU8(str, latin1) {
783
3112
  if (latin1) {
784
3113
  var ar_1 = new u8(str.length);
785
- for (var i = 0; i < str.length; ++i)
786
- ar_1[i] = str.charCodeAt(i);
3114
+ for (var i2 = 0; i2 < str.length; ++i2)
3115
+ ar_1[i2] = str.charCodeAt(i2);
787
3116
  return ar_1;
788
3117
  }
789
3118
  if (te)
@@ -794,19 +3123,19 @@ function strToU8(str, latin1) {
794
3123
  var w = function(v) {
795
3124
  ar[ai++] = v;
796
3125
  };
797
- for (var i = 0; i < l; ++i) {
3126
+ for (var i2 = 0; i2 < l; ++i2) {
798
3127
  if (ai + 5 > ar.length) {
799
- var n = new u8(ai + 8 + (l - i << 1));
3128
+ var n = new u8(ai + 8 + (l - i2 << 1));
800
3129
  n.set(ar);
801
3130
  ar = n;
802
3131
  }
803
- var c = str.charCodeAt(i);
3132
+ var c = str.charCodeAt(i2);
804
3133
  if (c < 128 || latin1)
805
3134
  w(c);
806
3135
  else if (c < 2048)
807
3136
  w(192 | c >> 6), w(128 | c & 63);
808
3137
  else if (c > 55295 && c < 57344)
809
- c = 65536 + (c & 1023 << 10) | str.charCodeAt(++i) & 1023, w(240 | c >> 18), w(128 | c >> 12 & 63), w(128 | c >> 6 & 63), w(128 | c & 63);
3138
+ c = 65536 + (c & 1023 << 10) | str.charCodeAt(++i2) & 1023, w(240 | c >> 18), w(128 | c >> 12 & 63), w(128 | c >> 6 & 63), w(128 | c & 63);
810
3139
  else
811
3140
  w(224 | c >> 12), w(128 | c >> 6 & 63), w(128 | c & 63);
812
3141
  }
@@ -903,8 +3232,8 @@ function zipSync(data, opts) {
903
3232
  tot += 76 + 2 * (s + exl) + (ms || 0) + l;
904
3233
  }
905
3234
  var out = new u8(tot + 22), oe = o, cdl = tot - o;
906
- for (var i = 0; i < files.length; ++i) {
907
- var f = files[i];
3235
+ for (var i2 = 0; i2 < files.length; ++i2) {
3236
+ var f = files[i2];
908
3237
  wzh(out, f.o, f, f.f, f.u, f.c.length);
909
3238
  var badd = 30 + f.f.length + exfl(f.extra);
910
3239
  out.set(f.c, f.o + badd);
@@ -1398,8 +3727,7 @@ header {
1398
3727
  var DEMO_MANIFEST = {
1399
3728
  schema: 1,
1400
3729
  name: "Hello Marina",
1401
- icon: "\u26F5",
1402
- type: "static"
3730
+ icon: "\u26F5"
1403
3731
  };
1404
3732
 
1405
3733
  // src/demo.ts
@@ -1424,20 +3752,24 @@ function packDemo() {
1424
3752
  }
1425
3753
 
1426
3754
  // src/login.ts
3755
+ init_config();
3756
+ init_api();
1427
3757
  import { createHash, randomBytes } from "node:crypto";
1428
3758
  import { spawn } from "node:child_process";
1429
3759
  import { createServer } from "node:http";
1430
3760
  import { hostname } from "node:os";
1431
- var LOGIN_TIMEOUT_MS = 5 * 6e4;
3761
+ var LOGIN_TIMEOUT_MS = 15 * 6e4;
1432
3762
  var PROGRESS_INTERVAL_MS = 1e3;
1433
3763
  var page = (title, message) => `<!doctype html>
1434
- <html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width">
3764
+ <html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><meta name="color-scheme" content="light dark">
1435
3765
  <title>${title}</title><style>
1436
- body{margin:0;display:grid;place-items:center;min-height:100vh;font:15px/1.5 system-ui,sans-serif;color:#142238;background:#faf9f7}
1437
- main{text-align:center;padding:32px}h1{font-size:22px;margin:0 0 6px}p{margin:0;color:#667085}
3766
+ :root{color-scheme:light;--text:#142238;--muted:#667085;--background:#faf9f7}
3767
+ @media(prefers-color-scheme:dark){:root{color-scheme:dark;--text:#f2f4f7;--muted:#98a2b3;--background:#101828}}
3768
+ body{margin:0;display:grid;place-items:center;min-height:100vh;font:15px/1.5 system-ui,sans-serif;color:var(--text);background:var(--background)}
3769
+ main{text-align:center;padding:32px}h1{font-size:22px;margin:0 0 6px}p{margin:0;color:var(--muted)}
1438
3770
  </style></head><body><main><h1>${title}</h1><p>${message}</p></main></body></html>`;
1439
- var closeLoginServer = (server) => new Promise((resolve2) => {
1440
- server.close(() => resolve2());
3771
+ var closeLoginServer = (server) => new Promise((resolve4) => {
3772
+ server.close(() => resolve4());
1441
3773
  server.closeAllConnections();
1442
3774
  });
1443
3775
  function openBrowser(url) {
@@ -1455,7 +3787,7 @@ function timeoutLabel(timeoutMs) {
1455
3787
  return `${String(seconds)} second${seconds === 1 ? "" : "s"}`;
1456
3788
  }
1457
3789
  function waitForLoginCallback(server, expectedState, onProgress, timeoutMs = LOGIN_TIMEOUT_MS) {
1458
- return new Promise((resolve2, reject) => {
3790
+ return new Promise((resolve4, reject) => {
1459
3791
  const startedAt = Date.now();
1460
3792
  const waiting = () => {
1461
3793
  const elapsedMs = Date.now() - startedAt;
@@ -1512,7 +3844,7 @@ function waitForLoginCallback(server, expectedState, onProgress, timeoutMs = LOG
1512
3844
  response.writeHead(200);
1513
3845
  response.end(
1514
3846
  page("Marina CLI is signed in", "You can close this window and return to the terminal."),
1515
- () => resolve2(code)
3847
+ () => resolve4(code)
1516
3848
  );
1517
3849
  });
1518
3850
  });
@@ -1522,9 +3854,9 @@ async function loginWithBrowser(onOpen, onProgress = () => void 0) {
1522
3854
  const challenge = createHash("sha256").update(verifier).digest("base64url");
1523
3855
  const state = randomBytes(32).toString("base64url");
1524
3856
  const server = createServer();
1525
- await new Promise((resolve2, reject) => {
3857
+ await new Promise((resolve4, reject) => {
1526
3858
  server.once("error", reject);
1527
- server.listen(0, "127.0.0.1", resolve2);
3859
+ server.listen(0, "127.0.0.1", resolve4);
1528
3860
  });
1529
3861
  try {
1530
3862
  const address = server.address();
@@ -1548,65 +3880,18 @@ async function loginWithBrowser(onOpen, onProgress = () => void 0) {
1548
3880
  }
1549
3881
  }
1550
3882
 
1551
- // src/output.ts
1552
- var json = false;
1553
- var setJsonMode = (on) => {
1554
- json = on;
1555
- };
1556
- var isJsonMode = () => json;
1557
- var bold = (s) => `\x1B[1m${s}\x1B[22m`;
1558
- var dim = (s) => `\x1B[2m${s}\x1B[22m`;
1559
- var red = (s) => `\x1B[31m${s}\x1B[39m`;
1560
- var green = (s) => `\x1B[32m${s}\x1B[39m`;
1561
- function say(line = "") {
1562
- if (json) console.error(line);
1563
- else console.log(line);
1564
- }
1565
- function note(line) {
1566
- console.error(line);
1567
- }
1568
- function createProgress() {
1569
- let active = false;
1570
- let lastPhase = null;
1571
- let frame = 0;
1572
- const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1573
- const interactive = process.stderr.isTTY === true && !json;
1574
- return {
1575
- update(phase, line) {
1576
- if (interactive) {
1577
- process.stderr.write(`\r\x1B[2K${frames[frame++ % frames.length]} ${line}`);
1578
- active = true;
1579
- } else if (phase !== lastPhase) {
1580
- console.error(json ? JSON.stringify({ type: "progress", phase, message: line }) : line);
1581
- }
1582
- lastPhase = phase;
1583
- },
1584
- clear() {
1585
- if (active) process.stderr.write("\r\x1B[2K");
1586
- active = false;
1587
- }
1588
- };
1589
- }
1590
- function result(payload) {
1591
- if (json) console.log(JSON.stringify({ schema_version: 1, ok: true, ...payload }, null, 2));
1592
- }
1593
- function failure(code, message, extra = {}) {
1594
- if (json)
1595
- console.log(
1596
- JSON.stringify({ schema_version: 1, ok: false, error: { code, message, ...extra } }, null, 2)
1597
- );
1598
- else {
1599
- console.error(`${red(code)} ${message}`);
1600
- }
1601
- }
3883
+ // src/index.ts
3884
+ init_output();
1602
3885
 
1603
3886
  // src/pack.ts
1604
- import { lstatSync, readFileSync as readFileSync2, readdirSync } from "node:fs";
1605
- import { join as join2, relative } from "node:path";
3887
+ import { existsSync as existsSync2, lstatSync, readFileSync as readFileSync2, readdirSync, realpathSync } from "node:fs";
3888
+ import { basename, dirname, join as join2, relative } from "node:path";
3889
+ var import_ignore = __toESM(require_ignore(), 1);
1606
3890
  var EXCLUDED_DIRS = /* @__PURE__ */ new Set([
1607
3891
  ".git",
1608
3892
  ".hg",
1609
3893
  ".svn",
3894
+ ".marina",
1610
3895
  "node_modules",
1611
3896
  "bower_components",
1612
3897
  ".venv",
@@ -1622,23 +3907,74 @@ var EXCLUDED_DIRS = /* @__PURE__ */ new Set([
1622
3907
  "coverage",
1623
3908
  ".idea",
1624
3909
  ".vscode",
1625
- ".marina",
1626
3910
  "__MACOSX"
1627
3911
  ]);
1628
3912
  var EXCLUDED_FILES = /* @__PURE__ */ new Set([".DS_Store"]);
1629
3913
  var MAX_TOTAL = 100 * 1024 * 1024;
1630
- var SECRET_FILE = /^(\.env(\.[^/]*)?|.*\.pem|.*\.p12|.*\.pfx|id_rsa|id_dsa|id_ecdsa|id_ed25519|credentials\.json|service-account\.json)$/i;
3914
+ var OUTPUT_DIR_NAMES = /* @__PURE__ */ new Set(["dist", "build", "out", ".output"]);
3915
+ function buildOutputParent(dir) {
3916
+ let real;
3917
+ try {
3918
+ real = realpathSync(dir);
3919
+ } catch {
3920
+ return null;
3921
+ }
3922
+ if (!OUTPUT_DIR_NAMES.has(basename(dir)) && !OUTPUT_DIR_NAMES.has(basename(real))) return null;
3923
+ if (!existsSync2(join2(real, "index.html"))) return null;
3924
+ if (existsSync2(join2(real, "package.json")) || existsSync2(join2(real, "marina.json"))) return null;
3925
+ for (const parent of /* @__PURE__ */ new Set([dirname(real), dirname(dir)])) {
3926
+ if (parent === real || parent === dir) continue;
3927
+ if (declaresBuild(parent)) return parent;
3928
+ }
3929
+ return null;
3930
+ }
3931
+ function declaresBuild(parent) {
3932
+ try {
3933
+ const parsed = JSON.parse(readFileSync2(join2(parent, "package.json"), "utf8"));
3934
+ const build = parsed.scripts?.build;
3935
+ return typeof build === "string" && build.trim().length > 0;
3936
+ } catch {
3937
+ return false;
3938
+ }
3939
+ }
3940
+ var SECRET_FILE = /^(\.env(\.[^/]*)?|\.npmrc|.*\.pem|.*\.p12|.*\.pfx|id_rsa|id_dsa|id_ecdsa|id_ed25519|credentials\.json|service-account\.json)$/i;
3941
+ function ignoredBy(rules, path, directory) {
3942
+ let ignored = false;
3943
+ for (const rule of rules) {
3944
+ const scopedPath = rule.base === "" ? path : path.startsWith(`${rule.base}/`) ? path.slice(rule.base.length + 1) : void 0;
3945
+ if (!scopedPath) continue;
3946
+ const result2 = rule.matcher.test(directory ? `${scopedPath}/` : scopedPath);
3947
+ if (result2.ignored) ignored = true;
3948
+ if (result2.unignored) ignored = false;
3949
+ }
3950
+ return ignored;
3951
+ }
1631
3952
  function pack(dir) {
1632
3953
  const files = {};
1633
3954
  const skippedSecrets = [];
1634
3955
  let totalBytes = 0;
1635
- const walk = (current) => {
3956
+ const walk = (current, inheritedRules) => {
3957
+ const currentRelative = relative(dir, current).replaceAll("\\", "/");
3958
+ const rules = [...inheritedRules];
3959
+ const gitignorePath = join2(current, ".gitignore");
3960
+ if (existsSync2(gitignorePath)) {
3961
+ const gitignoreStat = lstatSync(gitignorePath);
3962
+ if (gitignoreStat.isFile() && !gitignoreStat.isSymbolicLink()) {
3963
+ rules.push({
3964
+ base: currentRelative,
3965
+ matcher: (0, import_ignore.default)().add(readFileSync2(gitignorePath, "utf8"))
3966
+ });
3967
+ }
3968
+ }
1636
3969
  for (const entry of readdirSync(current)) {
1637
3970
  const full = join2(current, entry);
1638
3971
  const stat = lstatSync(full);
1639
3972
  if (stat.isSymbolicLink()) continue;
3973
+ const relativePath = relative(dir, full).replaceAll("\\", "/");
1640
3974
  if (stat.isDirectory()) {
1641
- if (!EXCLUDED_DIRS.has(entry)) walk(full);
3975
+ if (!EXCLUDED_DIRS.has(entry) && !ignoredBy(rules, relativePath, true)) {
3976
+ walk(full, rules);
3977
+ }
1642
3978
  continue;
1643
3979
  }
1644
3980
  if (EXCLUDED_FILES.has(entry)) continue;
@@ -1646,27 +3982,28 @@ function pack(dir) {
1646
3982
  skippedSecrets.push(relative(dir, full));
1647
3983
  continue;
1648
3984
  }
3985
+ if (ignoredBy(rules, relativePath, false)) continue;
1649
3986
  totalBytes += stat.size;
1650
3987
  if (totalBytes > MAX_TOTAL) {
1651
3988
  throw new Error(
1652
3989
  `this directory is over ${String(MAX_TOTAL / 1024 / 1024)} MB unpacked \u2014 trim it before deploying`
1653
3990
  );
1654
3991
  }
1655
- files[relative(dir, full).replaceAll("\\", "/")] = readFileSync2(full);
3992
+ files[relativePath] = readFileSync2(full);
1656
3993
  }
1657
3994
  };
1658
- walk(dir);
3995
+ walk(dir, []);
1659
3996
  const fileCount = Object.keys(files).length;
1660
3997
  if (fileCount === 0) throw new Error("nothing to deploy \u2014 this directory is empty");
1661
3998
  return { zip: zipSync(files), fileCount, totalBytes, skippedSecrets };
1662
3999
  }
1663
4000
 
1664
4001
  // src/manifest.ts
1665
- import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
1666
- import { basename, join as join3 } from "node:path";
4002
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
4003
+ import { basename as basename2, join as join3 } from "node:path";
1667
4004
  function readManifest(dir) {
1668
4005
  const path = join3(dir, "marina.json");
1669
- if (!existsSync2(path)) return null;
4006
+ if (!existsSync3(path)) return null;
1670
4007
  let value;
1671
4008
  try {
1672
4009
  value = JSON.parse(readFileSync3(path, "utf8"));
@@ -1687,7 +4024,7 @@ function readManifest(dir) {
1687
4024
  throw new Error('marina.json "icon" must be an emoji');
1688
4025
  }
1689
4026
  if (manifest.type !== void 0 && manifest.type !== "static" && manifest.type !== "dynamic") {
1690
- throw new Error('marina.json "type" must be "static" or "dynamic"');
4027
+ throw new Error('marina.json "type" is obsolete; remove it and use "entrypoint" if needed');
1691
4028
  }
1692
4029
  return manifest;
1693
4030
  }
@@ -1702,16 +4039,16 @@ function resolveAppName(dir, flag, manifest = readManifest(dir), linkedName) {
1702
4039
  } catch {
1703
4040
  }
1704
4041
  if (linkedName?.trim()) return linkedName.trim();
1705
- return basename(dir);
4042
+ return basename2(dir);
1706
4043
  }
1707
4044
 
1708
4045
  // src/skills.ts
1709
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
4046
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
1710
4047
  import { homedir as homedir2 } from "node:os";
1711
4048
  import { join as join4 } from "node:path";
1712
4049
 
1713
4050
  // ../../skills/marina-deploy/SKILL.md
1714
- var SKILL_default = '---\nname: marina-deploy\ndescription: Build, deploy, inspect, and recover applications on Marina Cloud with the Marina CLI. Use when a user asks to publish a project to Marina, create a first Marina demo, check whether a Marina deployment is live, diagnose a failed or refused deploy, inspect versions, or roll an app back.\n---\n\n# Marina Deploy\n\nUse the Marina CLI to deploy the user\'s project. Run commands with `--json` and\nfollow the returned result until the app is live.\n\n## Deploy\n\n1. Inspect the project and run its relevant local checks.\n2. Install the CLI if needed: `npm install -g @marina-cloud/cli`.\n3. If unauthenticated, run `marina setup` and let the user finish browser sign-in.\n4. Run `marina deploy --json` from the project directory.\n5. Return the live URL only when the result has `ok: true`, `state: "published"`,\n `live: true`, and a non-null `url`.\n\nIf it returns a failure or refusal, use the returned message and build log to\nfix the project, verify it locally, and retry. Never bypass a refusal or\ndescribe an unpublished or failed version as live.\n\n## Create a demo\n\nFor a first demo, run `marina deploy demo --json`. This deploys the bundled\nHello Marina collage app directly. Treat it like any other deploy: return its\nURL only after the result says it is published and live.\n\n## Inspect or recover\n\n```sh\nmarina status --app <slug> --json\nmarina deploys --app <slug> --json\nmarina versions --app <slug> --json\nmarina rollback --app <slug> --to <hash> --json\n```\n\nIdentify the intended published version before rolling back, then confirm the\nresult with `marina status --json`.\n';
4051
+ var SKILL_default = "---\nname: marina-deploy\ndescription: Build, deploy, inspect, and recover applications on Marina Cloud with the Marina CLI. Use when a user asks to publish a project to Marina, create a first Marina demo, check whether a Marina deployment is live, diagnose a failed or refused deploy, inspect versions, or roll an app back.\n---\n\n# Marina Deploy\n\nUse the Marina CLI to deploy the user's project. Run commands with `--json` and\nfollow the returned result until the app is live.\n\n## Deploy\n\n1. Inspect the project and run its relevant local checks.\n2. Install the CLI if needed: `npm install -g @marina-cloud/cli`.\n3. If unauthenticated, run `marina setup` and let the user finish browser sign-in.\n4. Run `marina deploy --json` from the project directory.\n5. Return the live URL only when the result has `ok: true`, `state: \"published\"`,\n `live: true`, and a non-null `url`.\n\nIf it returns a failure or refusal, use the returned message and build log to\nfix the project, verify it locally, and retry. Never bypass a refusal or\ndescribe an unpublished or failed version as live.\n\nMarina may recover an ambiguous or failed static build through its publishing\nagent. Inspect the returned `preparation` report and `build_log`: the agent can\nminimally repair and retry the build. Marina requires a successful build and\nmechanically verified output before publication.\n\nStatic source detection is deterministic: a non-empty `scripts.build` wins\nover root `index.html`; without a build script, root `index.html` is served\nas-is. Deploy the project root and let Marina build it. A finished output\ndirectory (for example, `marina deploy dist`) is refused when it belongs to a\nproject that declares a build; deploy that project's source instead.\n\nEvery accepted upload returns a `deploy_id`, including a first deploy refused\nbefore an app exists. Inspect that durable attempt with:\n\n```sh\nmarina deploys <deploy-id> --json\n```\n\nAn archive or security refusal may have a null `build_log` because no build was\nrun; the refusal and action are still preserved on the deploy attempt.\n\n## Develop locally\n\n`marina dev` runs the app on this machine with the production runtime\ncontract: local storage and an embedded Postgres (the app's\n`marina/migrations` apply on start), while `marina.capabilities` and\n`marina.connections` calls bridge to Marina under the signed-in user's\ndev-scoped grants. A denied bridged call names the missing grant \u2014 an\norganization admin adds it from the connection's Dev access control. Use\n`--port <port>` to change the listen port and `--schedules` to run scheduled\njobs locally. `marina.ai` bridges too; per-developer usage is metered.\n\n## Create a demo\n\nFor a first demo, run `marina deploy demo --json`. This deploys the bundled\nHello Marina collage app directly. Treat it like any other deploy: return its\nURL only after the result says it is published and live.\n\n## Inspect or recover\n\n```sh\nmarina status --app <slug> --json\nmarina deploys --app <slug> --json\nmarina deploys <deploy-id> --json\nmarina logs --app <slug>\nmarina versions --app <slug> --json\nmarina rollback --app <slug> --to <hash> --json\n```\n\n`marina logs` always returns structured JSON. Use it to inspect deployment\nevents, runtime invocations, console output, and exceptions. Follow\n`next_cursor` when older entries are needed.\n\nIdentify the intended published version before rolling back, then confirm the\nresult with `marina status --json`.\n";
1715
4052
 
1716
4053
  // ../../skills/marina-deploy/agents/openai.yaml
1717
4054
  var openai_default = 'interface:\n display_name: "Marina Deploy"\n short_description: "Build and deploy apps to Marina Cloud"\n default_prompt: "Use $marina-deploy to deploy this project to Marina Cloud."\n';
@@ -1733,7 +4070,7 @@ function targets(agent) {
1733
4070
  }));
1734
4071
  }
1735
4072
  if (agent === "codex" || agent === "claude") return [{ agent, home: agentHomes[agent] }];
1736
- return Object.entries(agentHomes).filter(([, home]) => existsSync3(home)).map(([name, home]) => ({ agent: name, home }));
4073
+ return Object.entries(agentHomes).filter(([, home]) => existsSync4(home)).map(([name, home]) => ({ agent: name, home }));
1737
4074
  }
1738
4075
  function install(target, update) {
1739
4076
  const directory = join4(target.home, "skills", "marina-deploy");
@@ -1769,56 +4106,14 @@ function installSkills(agent) {
1769
4106
  return selected.map((target) => install(target, true));
1770
4107
  }
1771
4108
 
1772
- // package.json
1773
- var package_default = {
1774
- name: "@marina-cloud/cli",
1775
- version: "0.0.3",
1776
- description: "Command-line client for Marina Cloud",
1777
- homepage: "https://github.com/marina-hq/marina#readme",
1778
- bugs: {
1779
- url: "https://github.com/marina-hq/marina/issues"
1780
- },
1781
- license: "Apache-2.0",
1782
- repository: {
1783
- type: "git",
1784
- url: "git+https://github.com/marina-hq/marina.git",
1785
- directory: "packages/cli"
1786
- },
1787
- bin: {
1788
- marina: "dist/marina.mjs"
1789
- },
1790
- files: [
1791
- "dist"
1792
- ],
1793
- type: "module",
1794
- publishConfig: {
1795
- access: "public",
1796
- provenance: true
1797
- },
1798
- scripts: {
1799
- build: 'esbuild src/index.ts --bundle --platform=node --target=node22 --format=esm --loader:.md=text --loader:.yaml=text --loader:.html=text --loader:.css=text --loader:.txt=text --outfile=dist/marina.mjs --banner:js="#!/usr/bin/env node" && chmod +x dist/marina.mjs',
1800
- typecheck: "tsc --noEmit",
1801
- test: "pnpm build && node --experimental-strip-types --test src/*.test.ts",
1802
- prepack: "pnpm build"
1803
- },
1804
- devDependencies: {
1805
- "@types/node": "^26.2.0",
1806
- esbuild: "^0.25.0",
1807
- fflate: "^0.8.2",
1808
- typescript: "^5.9.0"
1809
- },
1810
- engines: {
1811
- node: ">=22"
1812
- }
1813
- };
1814
-
1815
4109
  // src/update.ts
1816
- var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
1817
- var LATEST_URL = "https://registry.npmjs.org/@marina-cloud%2Fcli/latest";
4110
+ init_package();
4111
+ init_config();
4112
+ var NAG_INTERVAL_MS = 24 * 60 * 60 * 1e3;
4113
+ var parseVersion = (version) => version.replace(/^v/, "").split("-", 1)[0].split(".").map((part) => Number.parseInt(part, 10));
1818
4114
  function isNewerVersion(candidate, current) {
1819
- const parse = (version) => version.replace(/^v/, "").split("-", 1)[0].split(".").map((part) => Number.parseInt(part, 10));
1820
- const left = parse(candidate);
1821
- const right = parse(current);
4115
+ const left = parseVersion(candidate);
4116
+ const right = parseVersion(current);
1822
4117
  if (left.some(Number.isNaN) || right.some(Number.isNaN)) return false;
1823
4118
  for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
1824
4119
  const difference = (left[index] ?? 0) - (right[index] ?? 0);
@@ -1829,40 +4124,28 @@ function isNewerVersion(candidate, current) {
1829
4124
  async function availableUpdate() {
1830
4125
  if (process.env.MARINA_DISABLE_UPDATE_CHECK) return null;
1831
4126
  const current = package_default.version;
4127
+ const latest = latestCliVersion();
4128
+ if (!latest || !isNewerVersion(latest, current)) return null;
1832
4129
  const profile = readProfile();
1833
- const checkedAt = profile.update ? Date.parse(profile.update.checked_at) : Number.NaN;
1834
- let latest = profile.update?.latest;
1835
- if (!latest || !Number.isFinite(checkedAt) || Date.now() - checkedAt >= CHECK_INTERVAL_MS) {
1836
- try {
1837
- const response = await fetch(LATEST_URL, {
1838
- headers: { accept: "application/json" },
1839
- signal: AbortSignal.timeout(1500)
1840
- });
1841
- if (response.ok) {
1842
- const body = await response.json();
1843
- if (body.version) {
1844
- latest = body.version;
1845
- writeProfile({
1846
- ...readProfile(),
1847
- update: { checked_at: (/* @__PURE__ */ new Date()).toISOString(), latest }
1848
- });
1849
- }
1850
- }
1851
- } catch {
1852
- }
1853
- }
1854
- return latest && isNewerVersion(latest, current) ? {
4130
+ const notifiedAt = profile.update?.notified_at ? Date.parse(profile.update.notified_at) : Number.NaN;
4131
+ if (Number.isFinite(notifiedAt) && Date.now() - notifiedAt < NAG_INTERVAL_MS) return null;
4132
+ writeProfile({
4133
+ ...profile,
4134
+ update: { latest, notified_at: (/* @__PURE__ */ new Date()).toISOString() }
4135
+ });
4136
+ return {
1855
4137
  current,
1856
4138
  latest,
1857
4139
  command: "npm install -g @marina-cloud/cli@latest"
1858
- } : null;
4140
+ };
1859
4141
  }
1860
4142
 
1861
4143
  // src/index.ts
1862
4144
  var HELP = `${bold("marina")} \u2014 deploy internal apps
1863
4145
 
1864
- marina setup sign in with Clerk in your browser
1865
- --token mar_\u2026 save an existing API key (headless fallback)
4146
+ marina setup sign in through Marina in your browser
4147
+ --skills install the Marina deployment skill
4148
+ --deploy-demo deploy Hello Marina after signing in
1866
4149
  marina logout remove the saved credential
1867
4150
  marina profile show where this CLI is signed in
1868
4151
  marina skills install install or update the Marina deployment skill
@@ -1873,20 +4156,25 @@ var HELP = `${bold("marina")} \u2014 deploy internal apps
1873
4156
  --app <slug> deploy into an existing app
1874
4157
  marina status [--app <slug>] what is live, and how the last deploy went
1875
4158
  marina deploys [--app <slug>] recent deploy attempts, including refusals
4159
+ marina deploys <deploy-id> inspect one attempt, even before an app exists
4160
+ marina logs [--app <slug>] structured JSON runtime invocations, console output, and errors
4161
+ --level <level> debug, info, log, warn, or error
4162
+ --limit <count> 1\u2013200 entries (default: 100)
4163
+ --cursor <cursor> continue from a prior result
1876
4164
  marina versions [--app <slug>] version history
1877
4165
  marina rollback [--to <hash>] publish a previous version again
1878
- marina secrets list env vars and secrets for the app
1879
- marina secrets set K=V set one (--plain for a non-secret var)
1880
- marina secrets rm K remove one
4166
+ marina dev [dir] run this app locally against Marina
4167
+ --port <port> listen port (default: 5990)
4168
+ --schedules run scheduled jobs on their local timers
4169
+ marina ai models models marina.ai can generate with, and who pays
1881
4170
  marina list apps in your workspace
1882
4171
  marina open open this project's app
1883
4172
 
1884
- ${dim("--json machine-readable result on stdout, progress on stderr")}
1885
- ${dim(`API: ${apiUrl()} (override with MARINA_API)`)}`;
4173
+ ${dim("--json machine-readable result on stdout, progress on stderr")}`;
1886
4174
  var ANSI_ESCAPE = new RegExp(`${String.fromCodePoint(27)}\\[[0-9;]*m`, "g");
1887
4175
  var shouldCheckForUpdates = false;
1888
4176
  function targetApp(flag) {
1889
- const linked = readLink(resolve("."))?.app;
4177
+ const linked = readLink(resolve3("."))?.app;
1890
4178
  const app = flag ?? linked;
1891
4179
  if (!app) {
1892
4180
  failure("no_app", "no app here \u2014 pass --app <slug>, or deploy from a linked project");
@@ -1895,7 +4183,16 @@ function targetApp(flag) {
1895
4183
  return app;
1896
4184
  }
1897
4185
  var shortDigest = (digest) => digest?.slice(7, 14) ?? "\u2014";
1898
- async function deploy(dirArg, flags) {
4186
+ function deployAttemptHint(deployId, hasBuildLog) {
4187
+ if (isJsonMode()) return;
4188
+ const command = `marina deploys ${terminalSafeText(deployId)} --json`;
4189
+ note(
4190
+ dim(
4191
+ `${hasBuildLog ? "build log omitted from terminal; " : ""}retrieve structured JSON with: ${command}`
4192
+ )
4193
+ );
4194
+ }
4195
+ async function deploy(dirArg, flags, emitResult = true) {
1899
4196
  let dir = null;
1900
4197
  let link = null;
1901
4198
  let target;
@@ -1909,20 +4206,32 @@ async function deploy(dirArg, flags) {
1909
4206
  packed = packDemo();
1910
4207
  name = flags.name?.trim() || DEMO_MANIFEST.name;
1911
4208
  } else {
1912
- dir = resolve(dirArg ?? ".");
4209
+ dir = resolve3(dirArg ?? ".");
1913
4210
  link = readLink(dir);
1914
4211
  target = flags.app ?? link?.app;
1915
4212
  const manifest = readManifest(dir);
1916
4213
  name = resolveAppName(dir, flags.name, manifest, link?.name);
4214
+ const builtFrom = buildOutputParent(dir);
4215
+ if (builtFrom) {
4216
+ const refusal = {
4217
+ code: "prebuilt_output",
4218
+ message: `${dir} looks like the build output of ${builtFrom}, not source.`,
4219
+ action: "Deploy the project root; Marina runs the build itself."
4220
+ };
4221
+ failure("refused", refusal.message, { refusal });
4222
+ if (!isJsonMode()) note(` ${terminalSafeText(refusal.action)}`);
4223
+ process.exit(3);
4224
+ }
1917
4225
  packed = pack(dir);
1918
4226
  }
1919
- for (const secret of packed.skippedSecrets) say(dim(`skipped ${secret} \u2014 secrets stay local`));
4227
+ for (const secret of packed.skippedSecrets)
4228
+ say(dim(`skipped ${secret} \u2014 credential files are never deployed`));
1920
4229
  const progress = createProgress();
1921
4230
  const size = `${String(packed.fileCount)} files, ${String(Math.round(packed.totalBytes / 1024))} KB`;
1922
4231
  progress.update("uploading", `Uploading ${name} (${size})`);
1923
4232
  let started;
1924
4233
  try {
1925
- started = await startDeploy(packed.zip, name, target);
4234
+ started = await startDeploy(packed.zip, name, target, link?.base_revision);
1926
4235
  } catch (error) {
1927
4236
  progress.clear();
1928
4237
  if (error instanceof ApiError && error.code === "not_found" && link) {
@@ -1940,32 +4249,43 @@ async function deploy(dirArg, flags) {
1940
4249
  deployed = await pollDeploy(started.id, (current) => {
1941
4250
  if (current.status !== "queued" && current.status !== "building") return;
1942
4251
  const elapsed = Math.max(1, Math.round((Date.now() - deployedAt) / 1e3));
1943
- const message = current.status === "queued" ? `Waiting for a deploy worker (${String(elapsed)}s)` : `Building and verifying (${String(elapsed)}s)`;
4252
+ const message = current.build_message ? `${terminalSafeText(current.build_message)} (${String(elapsed)}s)` : current.status === "queued" ? `Waiting for a deploy worker (${String(elapsed)}s)` : `Building and verifying (${String(elapsed)}s)`;
1944
4253
  progress.update(current.status, message);
1945
4254
  });
1946
4255
  } finally {
1947
4256
  progress.clear();
1948
4257
  }
1949
4258
  if (deployed.status === "refused" && deployed.refusal) {
1950
- say(`${red("refused")} ${deployed.refusal.message}`);
1951
- if (deployed.refusal.action) say(` ${deployed.refusal.action}`);
1952
- if (deployed.build_log) {
1953
- say(dim("---- build log (tail) ----"));
1954
- say(dim(deployed.build_log.split("\n").slice(-20).join("\n")));
1955
- }
1956
4259
  failure("refused", deployed.refusal.message, {
4260
+ deploy_id: deployed.id,
1957
4261
  refusal: deployed.refusal,
1958
- build_log: deployed.build_log
4262
+ build_log: deployed.build_log,
4263
+ build_phase: deployed.build_phase,
4264
+ build_message: deployed.build_message
1959
4265
  });
4266
+ if (!isJsonMode() && deployed.refusal.action) {
4267
+ note(` ${terminalSafeText(deployed.refusal.action)}`);
4268
+ }
4269
+ deployAttemptHint(deployed.id, deployed.build_log !== null);
1960
4270
  process.exit(3);
1961
4271
  }
1962
4272
  if (deployed.status === "failed") {
1963
- failure("failed", deployed.error ?? "unknown error");
4273
+ failure("failed", deployed.error ?? "unknown error", {
4274
+ deploy_id: deployed.id,
4275
+ build_log: deployed.build_log,
4276
+ build_phase: deployed.build_phase,
4277
+ build_message: deployed.build_message
4278
+ });
4279
+ deployAttemptHint(deployed.id, deployed.build_log !== null);
1964
4280
  process.exit(1);
1965
4281
  }
1966
4282
  const slug = deployed.app_slug ?? "";
1967
- if (dir && (!link || link.app !== slug || link.name !== name)) {
1968
- writeLink(dir, { app: slug, name });
4283
+ if (dir && (!link || link.app !== slug || link.name !== name || link.base_revision !== (deployed.source_revision ?? void 0))) {
4284
+ writeLink(dir, {
4285
+ app: slug,
4286
+ name,
4287
+ ...deployed.source_revision ? { base_revision: deployed.source_revision } : {}
4288
+ });
1969
4289
  }
1970
4290
  const published = deployed.version_state === "published";
1971
4291
  say(
@@ -1984,30 +4304,35 @@ async function deploy(dirArg, flags) {
1984
4304
  if (deployed.version_url) say(` try this version: ${deployed.version_url}`);
1985
4305
  say(dim(" a publisher can approve it from the Marina inbox"));
1986
4306
  }
1987
- result({
4307
+ const payload = {
1988
4308
  command: "deploy",
1989
4309
  status: "succeeded",
4310
+ deploy_id: deployed.id,
1990
4311
  app: slug,
1991
4312
  version: deployed.version_number,
1992
4313
  state: deployed.version_state,
1993
4314
  url: deployed.url,
1994
4315
  version_url: deployed.version_url,
4316
+ build_log: deployed.build_log,
1995
4317
  preparation: deployed.preparation,
1996
4318
  live: published,
1997
4319
  demo: dirArg === "demo"
1998
- });
4320
+ };
4321
+ if (emitResult) result(payload);
4322
+ return payload;
1999
4323
  }
2000
4324
  async function status(appFlag) {
2001
4325
  const app = targetApp(appFlag);
2002
- const [detail, deploys2] = await Promise.all([getApp(app), listDeploys(app, 1)]);
2003
- const last = deploys2[0];
4326
+ const [detail, history] = await Promise.all([getApp(app), listDeploys(app, 1)]);
4327
+ const last = history[0];
2004
4328
  say(`${bold(detail.app.name)} ${dim(`(${detail.app.slug})`)}`);
2005
4329
  say(` ${detail.app.current_version_id ? green(detail.app.status) : dim("nothing published")}`);
2006
4330
  if (detail.app.current_version_id) say(` ${detail.url}`);
2007
4331
  if (detail.app.archived_at) say(` ${red("archived")} \u2014 it isn't serving`);
2008
4332
  if (last) {
2009
4333
  const outcome = last.status === "succeeded" ? green("succeeded") : last.status === "queued" || last.status === "building" ? last.status : red(last.status);
2010
- say(` last deploy: ${outcome}${last.refusal ? ` \u2014 ${last.refusal.message}` : ""}`);
4334
+ const version = last.version_number == null ? "" : ` v${String(last.version_number)}`;
4335
+ say(` last deploy: ${outcome}${version}${last.refusal ? ` \u2014 ${last.refusal.message}` : ""}`);
2011
4336
  }
2012
4337
  result({
2013
4338
  command: "status",
@@ -2020,17 +4345,55 @@ async function status(appFlag) {
2020
4345
  last_deploy: last ?? null
2021
4346
  });
2022
4347
  }
2023
- async function deploys(appFlag) {
4348
+ function deployOutcome(attempt) {
4349
+ return attempt.refusal?.message ?? attempt.error ?? attempt.status;
4350
+ }
4351
+ async function deploys(appFlag, deployId) {
4352
+ if (deployId) {
4353
+ const attempt = await getDeploy(deployId);
4354
+ const mark = attempt.status === "succeeded" ? green("ok") : red(attempt.status);
4355
+ say(`${mark} ${dim(attempt.id)} \u2014 ${terminalSafeText(deployOutcome(attempt))}`);
4356
+ if (attempt.build_log) deployAttemptHint(attempt.id, true);
4357
+ result({ command: "deploys.show", deploy: attempt });
4358
+ return;
4359
+ }
2024
4360
  const app = targetApp(appFlag);
2025
4361
  const rows = await listDeploys(app);
2026
4362
  if (rows.length === 0) say("no deploys yet");
2027
4363
  for (const row of rows) {
2028
4364
  const mark = row.status === "succeeded" ? green("ok") : red(row.status);
2029
- const why = row.refusal ? ` \u2014 ${row.refusal.message}` : row.error ? ` \u2014 ${row.error}` : "";
2030
- say(`${mark} ${dim(row.created_at)} ${dim(`via ${row.source}`)}${why}`);
4365
+ const why = row.refusal ? ` \u2014 ${terminalSafeText(row.refusal.message)}` : row.error ? ` \u2014 ${terminalSafeText(row.error)}` : "";
4366
+ const version = row.version_number == null ? "" : ` v${String(row.version_number)}`;
4367
+ say(`${mark}${version} ${dim(row.created_at)} ${dim(`via ${row.source}`)}${why}`);
2031
4368
  }
4369
+ const failedWithLog = rows.find((row) => row.status !== "succeeded" && row.build_log);
4370
+ if (failedWithLog) deployAttemptHint(failedWithLog.id, true);
2032
4371
  result({ command: "deploys", app, deploys: rows });
2033
4372
  }
4373
+ var LOG_LEVELS = /* @__PURE__ */ new Set(["debug", "info", "log", "warn", "error"]);
4374
+ async function logs(appFlag, flags) {
4375
+ const app = targetApp(appFlag);
4376
+ const parsedLimit = flags.limit === void 0 ? 100 : Number(flags.limit);
4377
+ if (!Number.isInteger(parsedLimit) || parsedLimit < 1 || parsedLimit > 200) {
4378
+ failure("invalid_input", "--limit must be an integer from 1 to 200");
4379
+ process.exit(1);
4380
+ }
4381
+ if (flags.level && !LOG_LEVELS.has(flags.level)) {
4382
+ failure("invalid_input", "--level must be debug, info, log, warn, or error");
4383
+ process.exit(1);
4384
+ }
4385
+ const response = await listRuntimeLogs(app, {
4386
+ limit: parsedLimit,
4387
+ cursor: flags.cursor,
4388
+ level: flags.level
4389
+ });
4390
+ result({
4391
+ command: "logs",
4392
+ app,
4393
+ logs: response.logs,
4394
+ next_cursor: response.nextCursor
4395
+ });
4396
+ }
2034
4397
  async function versions(appFlag) {
2035
4398
  const app = targetApp(appFlag);
2036
4399
  const [rows, detail] = await Promise.all([listVersions(app), getApp(app)]);
@@ -2072,70 +4435,40 @@ async function rollback(appFlag, to) {
2072
4435
  url: detail.url
2073
4436
  });
2074
4437
  }
2075
- async function secrets(appFlag, argv, plain) {
2076
- const app = targetApp(appFlag);
2077
- const [action, argument] = argv;
2078
- if (action === "set") {
2079
- const eq = argument?.indexOf("=") ?? -1;
2080
- if (!argument || eq < 1) {
2081
- failure("invalid_input", "usage: marina secrets set KEY=value");
2082
- process.exit(1);
2083
- }
2084
- const key = argument.slice(0, eq);
2085
- await setEnv(app, key, argument.slice(eq + 1), !plain);
2086
- say(`${green("ok")} set ${bold(key)}${plain ? "" : dim(" (secret)")}`);
2087
- result({ command: "secrets.set", app, key, kind: plain ? "plain" : "secret" });
2088
- return;
2089
- }
2090
- if (action === "rm") {
2091
- if (!argument) {
2092
- failure("invalid_input", "usage: marina secrets rm KEY");
2093
- process.exit(1);
2094
- }
2095
- await deleteEnv(app, argument);
2096
- say(`${green("ok")} removed ${bold(argument)}`);
2097
- result({ command: "secrets.rm", app, key: argument, removed: true });
2098
- return;
2099
- }
2100
- const rows = await listEnv(app);
2101
- if (rows.length === 0) say("nothing set");
2102
- for (const row of rows) {
2103
- say(
2104
- `${bold(row.key)} ${row.kind === "secret" ? dim("secret \u2014 write-only") : row.value ?? ""}`
2105
- );
2106
- }
2107
- result({ command: "secrets.list", app, env: rows });
2108
- }
2109
4438
  async function main() {
2110
4439
  setJsonMode(process.argv.slice(2).includes("--json"));
2111
4440
  const { positionals, values } = parseArgs({
2112
4441
  allowPositionals: true,
2113
4442
  options: {
2114
- token: { type: "string" },
2115
4443
  name: { type: "string" },
2116
4444
  app: { type: "string" },
2117
4445
  to: { type: "string" },
2118
- plain: { type: "boolean" },
4446
+ level: { type: "string" },
4447
+ limit: { type: "string" },
4448
+ cursor: { type: "string" },
2119
4449
  agent: { type: "string" },
4450
+ port: { type: "string" },
4451
+ schedules: { type: "boolean" },
4452
+ dir: { type: "string" },
4453
+ skills: { type: "boolean" },
4454
+ "deploy-demo": { type: "boolean" },
2120
4455
  json: { type: "boolean" },
2121
4456
  help: { type: "boolean", short: "h" }
2122
4457
  }
2123
4458
  });
2124
- setJsonMode(values.json === true);
2125
4459
  const command = positionals[0];
4460
+ setJsonMode(values.json === true || command === "logs");
2126
4461
  if (values.help || !command) {
2127
4462
  say(HELP);
2128
4463
  result({ command: "help", usage: HELP.replaceAll(ANSI_ESCAPE, "") });
2129
4464
  return;
2130
4465
  }
4466
+ await resolveControlPlane();
2131
4467
  shouldCheckForUpdates = true;
2132
4468
  switch (command) {
2133
4469
  case "setup":
2134
4470
  case "login": {
2135
- const token = values.token ?? process.env.MARINA_TOKEN;
2136
- if (token) {
2137
- saveToken(token);
2138
- } else {
4471
+ if (!process.env.MARINA_TOKEN) {
2139
4472
  let lastReportedRemaining = -1;
2140
4473
  const signedIn = await loginWithBrowser(
2141
4474
  (url) => {
@@ -2166,11 +4499,18 @@ async function main() {
2166
4499
  saveToken(signedIn.token);
2167
4500
  }
2168
4501
  say(`${green("ok")} signed in ${dim(`(${apiUrl()})`)}`);
4502
+ const installed = values.skills ? installSkills(values.agent) : void 0;
4503
+ for (const skill of installed ?? []) {
4504
+ say(`${green("ok")} ${skill.status} Marina skill for ${skill.agent} ${dim(skill.path)}`);
4505
+ }
4506
+ const demo = values["deploy-demo"] ? await deploy("demo", { name: values.name }, false) : void 0;
2169
4507
  result({
2170
4508
  command: "setup",
2171
4509
  signed_in: true,
2172
4510
  api: apiUrl(),
2173
- profile: profilePath()
4511
+ profile: profilePath(),
4512
+ ...installed ? { skills: installed } : {},
4513
+ ...demo ? { deploy: demo } : {}
2174
4514
  });
2175
4515
  return;
2176
4516
  }
@@ -2188,6 +4528,20 @@ async function main() {
2188
4528
  });
2189
4529
  return;
2190
4530
  }
4531
+ case "dev": {
4532
+ const { runDev: runDev2 } = await Promise.resolve().then(() => (init_dev(), dev_exports));
4533
+ try {
4534
+ await runDev2({
4535
+ dir: values.dir ?? positionals[1],
4536
+ port: values.port,
4537
+ schedules: values.schedules === true
4538
+ });
4539
+ } catch (error) {
4540
+ failure("dev_failed", error.message);
4541
+ process.exitCode = 1;
4542
+ }
4543
+ return;
4544
+ }
2191
4545
  case "profile": {
2192
4546
  const profile = readProfile();
2193
4547
  const source = process.env.MARINA_TOKEN ? "environment" : profile.token ? "profile" : null;
@@ -2214,6 +4568,23 @@ async function main() {
2214
4568
  result({ command: "skills.install", skills: installed });
2215
4569
  return;
2216
4570
  }
4571
+ case "ai": {
4572
+ if (positionals[1] !== "models") {
4573
+ failure("invalid_input", "usage: marina ai models");
4574
+ process.exit(1);
4575
+ }
4576
+ const view = await aiModels();
4577
+ say(`${bold("fast")} ${view.aliases.fast ?? dim("(broker unreachable)")}`);
4578
+ say(`${bold("smart")} ${view.aliases.smart ?? dim("(broker unreachable)")}`);
4579
+ if (view.models.length === 0) {
4580
+ say(dim("no library models offered on this deployment"));
4581
+ }
4582
+ for (const model of view.models) say(` ${model}`);
4583
+ const billing = view.billing === "workspace" ? "generations bill this workspace's OpenRouter account (BYOK)" : view.billing === "platform" ? "generations bill Marina's platform key \u2014 connect OpenRouter to use your own" : "no AI billing key: connect OpenRouter in Settings \u2192 Connections";
4584
+ say(dim(billing));
4585
+ result({ command: "ai.models", ...view });
4586
+ return;
4587
+ }
2217
4588
  case "deploy":
2218
4589
  await deploy(positionals[1], values);
2219
4590
  return;
@@ -2222,7 +4593,10 @@ async function main() {
2222
4593
  return;
2223
4594
  case "deploys":
2224
4595
  case "deployments":
2225
- await deploys(values.app);
4596
+ await deploys(values.app, positionals[1]);
4597
+ return;
4598
+ case "logs":
4599
+ await logs(values.app, values);
2226
4600
  return;
2227
4601
  case "versions":
2228
4602
  await versions(values.app);
@@ -2230,9 +4604,6 @@ async function main() {
2230
4604
  case "rollback":
2231
4605
  await rollback(values.app, values.to);
2232
4606
  return;
2233
- case "secrets":
2234
- await secrets(values.app, positionals.slice(1), values.plain === true);
2235
- return;
2236
4607
  case "list": {
2237
4608
  const apps = await listApps();
2238
4609
  if (apps.length === 0) say("no apps yet \u2014 `marina deploy` one");
@@ -2266,8 +4637,8 @@ async function run() {
2266
4637
  if (!shouldCheckForUpdates) return;
2267
4638
  const update = await availableUpdate();
2268
4639
  if (update) {
2269
- const message = `Marina CLI ${update.latest} is available (current ${update.current}) \u2014 ${update.command}`;
2270
- note(isJsonMode() ? message : dim(message));
4640
+ const detail = `Marina CLI ${update.latest} is available (current ${update.current}) \u2014 ${update.command}`;
4641
+ note(isJsonMode() ? detail : `${bold("Update available:")} ${detail}`);
2271
4642
  }
2272
4643
  }
2273
4644
  run().catch((error) => {