@marina-cloud/cli 0.0.4 → 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 +26 -3
  2. package/dist/marina.mjs +2718 -435
  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,64 +120,6 @@ import {
16
120
  } from "node:fs";
17
121
  import { homedir } from "node:os";
18
122
  import { join } from "node:path";
19
-
20
- // package.json
21
- var package_default = {
22
- name: "@marina-cloud/cli",
23
- version: "0.0.4",
24
- description: "Command-line client for Marina Cloud",
25
- homepage: "https://github.com/marina-hq/marina#readme",
26
- bugs: {
27
- url: "https://github.com/marina-hq/marina/issues"
28
- },
29
- license: "Apache-2.0",
30
- repository: {
31
- type: "git",
32
- url: "git+https://github.com/marina-hq/marina.git",
33
- directory: "packages/cli"
34
- },
35
- bin: {
36
- marina: "dist/marina.mjs"
37
- },
38
- files: [
39
- "dist"
40
- ],
41
- type: "module",
42
- publishConfig: {
43
- access: "public",
44
- provenance: true
45
- },
46
- scripts: {
47
- 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',
48
- typecheck: "tsc --noEmit",
49
- test: "pnpm build && node --experimental-strip-types --test src/*.test.ts",
50
- prepack: "pnpm build"
51
- },
52
- devDependencies: {
53
- "@types/node": "^26.2.0",
54
- esbuild: "^0.25.0",
55
- fflate: "^0.8.2",
56
- typescript: "^5.9.0"
57
- },
58
- engines: {
59
- node: ">=22"
60
- }
61
- };
62
-
63
- // src/identity.ts
64
- var CLI_VERSION = package_default.version;
65
- var cliRequestHeaders = () => ({
66
- "user-agent": `marina-cli/${CLI_VERSION}`,
67
- "x-marina-client": "cli",
68
- "x-marina-client-version": CLI_VERSION
69
- });
70
-
71
- // src/config.ts
72
- var MARINA_HOME = process.env.MARINA_HOME ?? join(homedir(), ".marina");
73
- var PROFILE = join(MARINA_HOME, "profile");
74
- var PRODUCTION_CONTROL_PLANE = "https://marina.cloud";
75
- var CONTROL_PLANE_CACHE_MS = 60 * 60 * 1e3;
76
- var resolvedControlPlane = null;
77
123
  function normalizeApiUrl(value) {
78
124
  const url = new URL(value);
79
125
  if (url.protocol !== "https:" && url.protocol !== "http:") {
@@ -87,9 +133,6 @@ function normalizeApiUrl(value) {
87
133
  }
88
134
  return url.origin;
89
135
  }
90
- var controlPlaneHost = () => normalizeApiUrl(
91
- process.env.MARINA_CONTROL_PLANE ?? readProfile().control_plane_host ?? PRODUCTION_CONTROL_PLANE
92
- );
93
136
  function cachedControlPlane(host = controlPlaneHost()) {
94
137
  const cached = readProfile().control_plane;
95
138
  if (!cached || cached.host !== host) return null;
@@ -101,15 +144,6 @@ function cachedControlPlane(host = controlPlaneHost()) {
101
144
  return null;
102
145
  }
103
146
  }
104
- var apiUrl = () => normalizeApiUrl(
105
- process.env.MARINA_API ?? resolvedControlPlane?.api_url ?? cachedControlPlane()?.api_url ?? controlPlaneHost()
106
- );
107
- var dashboardUrl = () => {
108
- if (process.env.MARINA_DASHBOARD) return normalizeApiUrl(process.env.MARINA_DASHBOARD);
109
- return normalizeApiUrl(
110
- resolvedControlPlane?.dashboard_url ?? cachedControlPlane()?.dashboard_url ?? controlPlaneHost()
111
- );
112
- };
113
147
  async function resolveControlPlane() {
114
148
  const host = controlPlaneHost();
115
149
  const cached = cachedControlPlane(host);
@@ -161,7 +195,6 @@ async function resolveControlPlane() {
161
195
  function latestCliVersion() {
162
196
  return resolvedControlPlane?.latest_cli_version ?? cachedControlPlane()?.latest_cli_version ?? null;
163
197
  }
164
- var profilePath = () => PROFILE;
165
198
  function readProfile() {
166
199
  try {
167
200
  return JSON.parse(readFileSync(PROFILE, "utf8"));
@@ -195,145 +228,2304 @@ function clearToken() {
195
228
  }
196
229
  return;
197
230
  }
198
- writeProfile(rest);
231
+ writeProfile(rest);
232
+ }
233
+ function readLink(dir) {
234
+ try {
235
+ return JSON.parse(readFileSync(join(dir, LINK), "utf8"));
236
+ } catch {
237
+ return null;
238
+ }
239
+ }
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}`);
199
2247
  }
200
- var LINK = ".marina/project.json";
201
- function readLink(dir) {
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;
202
2254
  try {
203
- return JSON.parse(readFileSync(join(dir, LINK), "utf8"));
2255
+ value = JSON.parse(readFileSync6(path, "utf8"));
204
2256
  } catch {
205
- return null;
2257
+ fail("is not valid JSON");
206
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
+ };
207
2307
  }
208
- function writeLink(dir, link) {
209
- mkdirSync(join(dir, ".marina"), { recursive: true });
210
- writeFileSync(join(dir, LINK), `${JSON.stringify(link, null, 2)}
211
- `);
212
- }
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
+ });
213
2316
 
214
- // src/api.ts
215
- var LOGIN_EXCHANGE_TIMEOUT_MS = 15e3;
216
- var ApiError = class extends Error {
217
- code;
218
- status;
219
- constructor(code, message, status2) {
220
- super(message);
221
- this.code = code;
222
- 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
+ };
223
2409
  }
224
- };
225
- 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) {
226
2420
  const token = getToken();
227
- if (!token) throw new ApiError("unauthenticated", "not signed in \u2014 run `marina setup`", 401);
228
- const res = await fetch(`${apiUrl()}${path}`, {
229
- ...init,
230
- headers: { ...cliRequestHeaders(), authorization: `Bearer ${token}`, ...init?.headers }
231
- });
232
- const body = await res.json().catch(() => ({}));
233
- if (!res.ok) {
234
- throw new ApiError(
235
- body.error?.code ?? "error",
236
- body.error?.message ?? `request failed (${String(res.status)})`,
237
- res.status
238
- );
2421
+ if (!token) {
2422
+ throw new Error("not signed in \u2014 run `marina setup` first");
239
2423
  }
240
- return body;
241
- }
242
- async function exchangeCliLogin(code, codeVerifier) {
243
- let res;
244
- try {
245
- res = await fetch(`${apiUrl()}/cli/auth/exchange`, {
246
- method: "POST",
247
- headers: { ...cliRequestHeaders(), "content-type": "application/json" },
248
- body: JSON.stringify({ code, code_verifier: codeVerifier }),
249
- signal: AbortSignal.timeout(LOGIN_EXCHANGE_TIMEOUT_MS)
250
- });
251
- } catch (error) {
252
- if (error.name === "TimeoutError") {
253
- throw new ApiError(
254
- "login_timeout",
255
- "the login confirmation request timed out after 15 seconds \u2014 run `marina setup` to try again",
256
- 408
257
- );
258
- }
259
- 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");
260
2429
  }
261
- const body = await res.json().catch(() => ({}));
262
- if (!res.ok || !body.token) {
263
- throw new ApiError(
264
- body.error?.code ?? "login_failed",
265
- body.error?.message ?? `login exchange failed (${String(res.status)})`,
266
- 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
+ )
267
2441
  );
268
2442
  }
269
- return body.token;
270
- }
271
- async function startDeploy(zip, name, app) {
272
- const form = new FormData();
273
- form.set(
274
- "code",
275
- new Blob([new Uint8Array(zip).buffer], { type: "application/zip" }),
276
- "upload.zip"
277
- );
278
- form.set("name", name);
279
- form.set("source", "cli");
280
- if (app) form.set("app", app);
281
- const res = await request("/v1/deploys", {
282
- method: "POST",
283
- body: form
284
- });
285
- return res.deploy;
286
- }
287
- async function pollDeploy(id, onProgress = () => void 0) {
288
- for (; ; ) {
289
- const { deploy: deploy2 } = await request(`/v1/deploys/${id}`);
290
- onProgress(deploy2);
291
- if (deploy2.status !== "queued" && deploy2.status !== "building") return deploy2;
292
- 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
+ );
293
2453
  }
294
- }
295
- async function listApps() {
296
- const res = await request("/v1/apps");
297
- return res.apps;
298
- }
299
- var getApp = (idOrSlug) => request(`/v1/apps/${encodeURIComponent(idOrSlug)}`);
300
- async function getAppUrl(idOrSlug) {
301
- return (await getApp(idOrSlug)).url;
302
- }
303
- async function listVersions(app) {
304
- const res = await request(
305
- `/v1/apps/${encodeURIComponent(app)}/versions`
306
- );
307
- return res.versions;
308
- }
309
- async function listDeploys(app, limit = 10) {
310
- const res = await request(
311
- `/v1/apps/${encodeURIComponent(app)}/deploys?limit=${String(limit)}`
312
- );
313
- return res.deploys;
314
- }
315
- async function restoreVersion(versionId) {
316
- const res = await request(`/v1/versions/${versionId}/restore`, {
317
- 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 }
318
2467
  });
319
- return res.version;
320
- }
321
- async function listEnv(app) {
322
- const res = await request(`/v1/apps/${encodeURIComponent(app)}/env`);
323
- return res.env;
324
- }
325
- async function setEnv(app, key, value, secret) {
326
- await request(`/v1/apps/${encodeURIComponent(app)}/env/${encodeURIComponent(key)}`, {
327
- method: "PUT",
328
- headers: { "content-type": "application/json" },
329
- 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
+ }
330
2483
  });
331
- }
332
- async function deleteEnv(app, key) {
333
- await request(`/v1/apps/${encodeURIComponent(app)}/env/${encodeURIComponent(key)}`, {
334
- 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);
335
2504
  });
336
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";
337
2529
 
338
2530
  // ../../node_modules/.pnpm/fflate@0.8.3/node_modules/fflate/esm/index.mjs
339
2531
  import { createRequire } from "module";
@@ -422,13 +2614,13 @@ var fdeb = new u8([
422
2614
  var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
423
2615
  var freb = function(eb, start) {
424
2616
  var b = new u16(31);
425
- for (var i = 0; i < 31; ++i) {
426
- b[i] = start += 1 << eb[i - 1];
2617
+ for (var i2 = 0; i2 < 31; ++i2) {
2618
+ b[i2] = start += 1 << eb[i2 - 1];
427
2619
  }
428
2620
  var r = new i32(b[30]);
429
- for (var i = 1; i < 30; ++i) {
430
- for (var j = b[i]; j < b[i + 1]; ++j) {
431
- 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;
432
2624
  }
433
2625
  }
434
2626
  return { b, r };
@@ -451,25 +2643,25 @@ var x;
451
2643
  var i;
452
2644
  var hMap = (function(cd, mb, r) {
453
2645
  var s = cd.length;
454
- var i = 0;
2646
+ var i2 = 0;
455
2647
  var l = new u16(mb);
456
- for (; i < s; ++i) {
457
- if (cd[i])
458
- ++l[cd[i] - 1];
2648
+ for (; i2 < s; ++i2) {
2649
+ if (cd[i2])
2650
+ ++l[cd[i2] - 1];
459
2651
  }
460
2652
  var le = new u16(mb);
461
- for (i = 1; i < mb; ++i) {
462
- 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;
463
2655
  }
464
2656
  var co;
465
2657
  if (r) {
466
2658
  co = new u16(1 << mb);
467
2659
  var rvb = 15 - mb;
468
- for (i = 0; i < s; ++i) {
469
- if (cd[i]) {
470
- var sv = i << 4 | cd[i];
471
- var r_1 = mb - cd[i];
472
- 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;
473
2665
  for (var m = v | (1 << r_1) - 1; v <= m; ++v) {
474
2666
  co[rev[v] >> rvb] = sv;
475
2667
  }
@@ -477,9 +2669,9 @@ var hMap = (function(cd, mb, r) {
477
2669
  }
478
2670
  } else {
479
2671
  co = new u16(s);
480
- for (i = 0; i < s; ++i) {
481
- if (cd[i]) {
482
- 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];
483
2675
  }
484
2676
  }
485
2677
  }
@@ -556,9 +2748,9 @@ var wbits16 = function(d, p, v) {
556
2748
  };
557
2749
  var hTree = function(d, mb) {
558
2750
  var t = [];
559
- for (var i = 0; i < d.length; ++i) {
560
- if (d[i])
561
- 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] });
562
2754
  }
563
2755
  var s = t.length;
564
2756
  var t2 = t.slice();
@@ -573,28 +2765,28 @@ var hTree = function(d, mb) {
573
2765
  return a.f - b.f;
574
2766
  });
575
2767
  t.push({ s: -1, f: 25001 });
576
- 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;
577
2769
  t[0] = { s: -1, f: l.f + r.f, l, r };
578
2770
  while (i1 != s - 1) {
579
- l = t[t[i0].f < t[i2].f ? i0++ : i2++];
580
- 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++];
581
2773
  t[i1++] = { s: -1, f: l.f + r.f, l, r };
582
2774
  }
583
2775
  var maxSym = t2[0].s;
584
- for (var i = 1; i < s; ++i) {
585
- if (t2[i].s > maxSym)
586
- maxSym = t2[i].s;
2776
+ for (var i2 = 1; i2 < s; ++i2) {
2777
+ if (t2[i2].s > maxSym)
2778
+ maxSym = t2[i2].s;
587
2779
  }
588
2780
  var tr = new u16(maxSym + 1);
589
2781
  var mbt = ln(t[i1 - 1], tr, 0);
590
2782
  if (mbt > mb) {
591
- var i = 0, dt = 0;
2783
+ var i2 = 0, dt = 0;
592
2784
  var lft = mbt - mb, cst = 1 << lft;
593
2785
  t2.sort(function(a, b) {
594
2786
  return tr[b.s] - tr[a.s] || a.f - b.f;
595
2787
  });
596
- for (; i < s; ++i) {
597
- var i2_1 = t2[i].s;
2788
+ for (; i2 < s; ++i2) {
2789
+ var i2_1 = t2[i2].s;
598
2790
  if (tr[i2_1] > mb) {
599
2791
  dt += cst - (1 << mbt - tr[i2_1]);
600
2792
  tr[i2_1] = mb;
@@ -603,14 +2795,14 @@ var hTree = function(d, mb) {
603
2795
  }
604
2796
  dt >>= lft;
605
2797
  while (dt > 0) {
606
- var i2_2 = t2[i].s;
2798
+ var i2_2 = t2[i2].s;
607
2799
  if (tr[i2_2] < mb)
608
2800
  dt -= 1 << mb - tr[i2_2]++ - 1;
609
2801
  else
610
- ++i;
2802
+ ++i2;
611
2803
  }
612
- for (; i >= 0 && dt; --i) {
613
- var i2_3 = t2[i].s;
2804
+ for (; i2 >= 0 && dt; --i2) {
2805
+ var i2_3 = t2[i2].s;
614
2806
  if (tr[i2_3] == mb) {
615
2807
  --tr[i2_3];
616
2808
  ++dt;
@@ -632,8 +2824,8 @@ var lc = function(c) {
632
2824
  var w = function(v) {
633
2825
  cl[cli++] = v;
634
2826
  };
635
- for (var i = 1; i <= s; ++i) {
636
- if (c[i] == cln && i != s)
2827
+ for (var i2 = 1; i2 <= s; ++i2) {
2828
+ if (c[i2] == cln && i2 != s)
637
2829
  ++cls;
638
2830
  else {
639
2831
  if (!cln && cls > 2) {
@@ -653,15 +2845,15 @@ var lc = function(c) {
653
2845
  while (cls--)
654
2846
  w(cln);
655
2847
  cls = 1;
656
- cln = c[i];
2848
+ cln = c[i2];
657
2849
  }
658
2850
  }
659
2851
  return { c: cl.subarray(0, cli), n: s };
660
2852
  };
661
2853
  var clen = function(cf, cl) {
662
2854
  var l = 0;
663
- for (var i = 0; i < cl.length; ++i)
664
- l += cf[i] * cl[i];
2855
+ for (var i2 = 0; i2 < cl.length; ++i2)
2856
+ l += cf[i2] * cl[i2];
665
2857
  return l;
666
2858
  };
667
2859
  var wfblk = function(out, pos, dat) {
@@ -671,8 +2863,8 @@ var wfblk = function(out, pos, dat) {
671
2863
  out[o + 1] = s >> 8;
672
2864
  out[o + 2] = out[o] ^ 255;
673
2865
  out[o + 3] = out[o + 1] ^ 255;
674
- for (var i = 0; i < s; ++i)
675
- out[o + i + 4] = dat[i];
2866
+ for (var i2 = 0; i2 < s; ++i2)
2867
+ out[o + i2 + 4] = dat[i2];
676
2868
  return (o + 4 + s) * 8;
677
2869
  };
678
2870
  var wblk = function(dat, out, final, syms, lf, df, eb, li, bs, bl, p) {
@@ -683,10 +2875,10 @@ var wblk = function(dat, out, final, syms, lf, df, eb, li, bs, bl, p) {
683
2875
  var _c = lc(dlt), lclt = _c.c, nlc = _c.n;
684
2876
  var _d = lc(ddt), lcdt = _d.c, ndc = _d.n;
685
2877
  var lcfreq = new u16(19);
686
- for (var i = 0; i < lclt.length; ++i)
687
- ++lcfreq[lclt[i] & 31];
688
- for (var i = 0; i < lcdt.length; ++i)
689
- ++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];
690
2882
  var _e = hTree(lcfreq, 7), lct = _e.t, mlcb = _e.l;
691
2883
  var nlcc = 19;
692
2884
  for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc)
@@ -705,24 +2897,24 @@ var wblk = function(dat, out, final, syms, lf, df, eb, li, bs, bl, p) {
705
2897
  wbits(out, p + 5, ndc - 1);
706
2898
  wbits(out, p + 10, nlcc - 4);
707
2899
  p += 14;
708
- for (var i = 0; i < nlcc; ++i)
709
- 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]]);
710
2902
  p += 3 * nlcc;
711
2903
  var lcts = [lclt, lcdt];
712
2904
  for (var it = 0; it < 2; ++it) {
713
2905
  var clct = lcts[it];
714
- for (var i = 0; i < clct.length; ++i) {
715
- var len = clct[i] & 31;
2906
+ for (var i2 = 0; i2 < clct.length; ++i2) {
2907
+ var len = clct[i2] & 31;
716
2908
  wbits(out, p, llm[len]), p += lct[len];
717
2909
  if (len > 15)
718
- wbits(out, p, clct[i] >> 5 & 127), p += clct[i] >> 12;
2910
+ wbits(out, p, clct[i2] >> 5 & 127), p += clct[i2] >> 12;
719
2911
  }
720
2912
  }
721
2913
  } else {
722
2914
  lm = flm, ll = flt, dm = fdm, dl = fdt;
723
2915
  }
724
- for (var i = 0; i < li; ++i) {
725
- var sym = syms[i];
2916
+ for (var i2 = 0; i2 < li; ++i2) {
2917
+ var sym = syms[i2];
726
2918
  if (sym > 255) {
727
2919
  var len = sym >> 18 & 31;
728
2920
  wbits16(out, p, lm[len + 257]), p += ll[len + 257];
@@ -755,36 +2947,36 @@ var dflt = function(dat, lvl, plvl, pre, post, st) {
755
2947
  var msk_1 = (1 << plvl) - 1;
756
2948
  var prev = st.p || new u16(32768), head = st.h || new u16(msk_1 + 1);
757
2949
  var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1;
758
- var hsh = function(i2) {
759
- 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;
760
2952
  };
761
2953
  var syms = new i32(25e3);
762
2954
  var lf = new u16(288), df = new u16(32);
763
- var lc_1 = 0, eb = 0, i = st.i || 0, li = 0, wi = st.w || 0, bs = 0;
764
- for (; i + 2 < s; ++i) {
765
- var hv = hsh(i);
766
- 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];
767
2959
  prev[imod] = pimod;
768
2960
  head[hv] = imod;
769
- if (wi <= i) {
770
- var rem = s - i;
2961
+ if (wi <= i2) {
2962
+ var rem = s - i2;
771
2963
  if ((lc_1 > 7e3 || li > 24576) && (rem > 423 || !lst)) {
772
- pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i - bs, pos);
773
- 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;
774
2966
  for (var j = 0; j < 286; ++j)
775
2967
  lf[j] = 0;
776
2968
  for (var j = 0; j < 30; ++j)
777
2969
  df[j] = 0;
778
2970
  }
779
2971
  var l = 2, d = 0, ch_1 = c, dif = imod - pimod & 32767;
780
- if (rem > 2 && hv == hsh(i - dif)) {
2972
+ if (rem > 2 && hv == hsh(i2 - dif)) {
781
2973
  var maxn = Math.min(n, rem) - 1;
782
- var maxd = Math.min(32767, i);
2974
+ var maxd = Math.min(32767, i2);
783
2975
  var ml = Math.min(258, rem);
784
2976
  while (dif <= maxd && --ch_1 && imod != pimod) {
785
- if (dat[i + l] == dat[i + l - dif]) {
2977
+ if (dat[i2 + l] == dat[i2 + l - dif]) {
786
2978
  var nl = 0;
787
- for (; nl < ml && dat[i + nl] == dat[i + nl - dif]; ++nl)
2979
+ for (; nl < ml && dat[i2 + nl] == dat[i2 + nl - dif]; ++nl)
788
2980
  ;
789
2981
  if (nl > l) {
790
2982
  l = nl, d = dif;
@@ -793,7 +2985,7 @@ var dflt = function(dat, lvl, plvl, pre, post, st) {
793
2985
  var mmd = Math.min(dif, nl - 2);
794
2986
  var md = 0;
795
2987
  for (var j = 0; j < mmd; ++j) {
796
- var ti = i - dif + j & 32767;
2988
+ var ti = i2 - dif + j & 32767;
797
2989
  var pti = prev[ti];
798
2990
  var cd = ti - pti & 32767;
799
2991
  if (cd > md)
@@ -811,32 +3003,32 @@ var dflt = function(dat, lvl, plvl, pre, post, st) {
811
3003
  eb += fleb[lin] + fdeb[din];
812
3004
  ++lf[257 + lin];
813
3005
  ++df[din];
814
- wi = i + l;
3006
+ wi = i2 + l;
815
3007
  ++lc_1;
816
3008
  } else {
817
- syms[li++] = dat[i];
818
- ++lf[dat[i]];
3009
+ syms[li++] = dat[i2];
3010
+ ++lf[dat[i2]];
819
3011
  }
820
3012
  }
821
3013
  }
822
- for (i = Math.max(i, wi); i < s; ++i) {
823
- syms[li++] = dat[i];
824
- ++lf[dat[i]];
3014
+ for (i2 = Math.max(i2, wi); i2 < s; ++i2) {
3015
+ syms[li++] = dat[i2];
3016
+ ++lf[dat[i2]];
825
3017
  }
826
- 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);
827
3019
  if (!lst) {
828
3020
  st.r = pos & 7 | w[pos / 8 | 0] << 3;
829
3021
  pos -= 7;
830
- 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;
831
3023
  }
832
3024
  } else {
833
- for (var i = st.w || 0; i < s + lst; i += 65535) {
834
- var e = i + 65535;
3025
+ for (var i2 = st.w || 0; i2 < s + lst; i2 += 65535) {
3026
+ var e = i2 + 65535;
835
3027
  if (e >= s) {
836
3028
  w[pos / 8 | 0] = lst;
837
3029
  e = s;
838
3030
  }
839
- pos = wfblk(w, pos + 1, dat.subarray(i, e));
3031
+ pos = wfblk(w, pos + 1, dat.subarray(i2, e));
840
3032
  }
841
3033
  st.i = s;
842
3034
  }
@@ -844,11 +3036,11 @@ var dflt = function(dat, lvl, plvl, pre, post, st) {
844
3036
  };
845
3037
  var crct = /* @__PURE__ */ (function() {
846
3038
  var t = new Int32Array(256);
847
- for (var i = 0; i < 256; ++i) {
848
- var c = i, k = 9;
3039
+ for (var i2 = 0; i2 < 256; ++i2) {
3040
+ var c = i2, k = 9;
849
3041
  while (--k)
850
3042
  c = (c & 1 && -306674912) ^ c >>> 1;
851
- t[i] = c;
3043
+ t[i2] = c;
852
3044
  }
853
3045
  return t;
854
3046
  })();
@@ -857,8 +3049,8 @@ var crc = function() {
857
3049
  return {
858
3050
  p: function(d) {
859
3051
  var cr = c;
860
- for (var i = 0; i < d.length; ++i)
861
- 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;
862
3054
  c = cr;
863
3055
  },
864
3056
  d: function() {
@@ -919,8 +3111,8 @@ try {
919
3111
  function strToU8(str, latin1) {
920
3112
  if (latin1) {
921
3113
  var ar_1 = new u8(str.length);
922
- for (var i = 0; i < str.length; ++i)
923
- ar_1[i] = str.charCodeAt(i);
3114
+ for (var i2 = 0; i2 < str.length; ++i2)
3115
+ ar_1[i2] = str.charCodeAt(i2);
924
3116
  return ar_1;
925
3117
  }
926
3118
  if (te)
@@ -931,19 +3123,19 @@ function strToU8(str, latin1) {
931
3123
  var w = function(v) {
932
3124
  ar[ai++] = v;
933
3125
  };
934
- for (var i = 0; i < l; ++i) {
3126
+ for (var i2 = 0; i2 < l; ++i2) {
935
3127
  if (ai + 5 > ar.length) {
936
- var n = new u8(ai + 8 + (l - i << 1));
3128
+ var n = new u8(ai + 8 + (l - i2 << 1));
937
3129
  n.set(ar);
938
3130
  ar = n;
939
3131
  }
940
- var c = str.charCodeAt(i);
3132
+ var c = str.charCodeAt(i2);
941
3133
  if (c < 128 || latin1)
942
3134
  w(c);
943
3135
  else if (c < 2048)
944
3136
  w(192 | c >> 6), w(128 | c & 63);
945
3137
  else if (c > 55295 && c < 57344)
946
- 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);
947
3139
  else
948
3140
  w(224 | c >> 12), w(128 | c >> 6 & 63), w(128 | c & 63);
949
3141
  }
@@ -1040,8 +3232,8 @@ function zipSync(data, opts) {
1040
3232
  tot += 76 + 2 * (s + exl) + (ms || 0) + l;
1041
3233
  }
1042
3234
  var out = new u8(tot + 22), oe = o, cdl = tot - o;
1043
- for (var i = 0; i < files.length; ++i) {
1044
- var f = files[i];
3235
+ for (var i2 = 0; i2 < files.length; ++i2) {
3236
+ var f = files[i2];
1045
3237
  wzh(out, f.o, f, f.f, f.u, f.c.length);
1046
3238
  var badd = 30 + f.f.length + exfl(f.extra);
1047
3239
  out.set(f.c, f.o + badd);
@@ -1535,8 +3727,7 @@ header {
1535
3727
  var DEMO_MANIFEST = {
1536
3728
  schema: 1,
1537
3729
  name: "Hello Marina",
1538
- icon: "\u26F5",
1539
- type: "static"
3730
+ icon: "\u26F5"
1540
3731
  };
1541
3732
 
1542
3733
  // src/demo.ts
@@ -1561,20 +3752,24 @@ function packDemo() {
1561
3752
  }
1562
3753
 
1563
3754
  // src/login.ts
3755
+ init_config();
3756
+ init_api();
1564
3757
  import { createHash, randomBytes } from "node:crypto";
1565
3758
  import { spawn } from "node:child_process";
1566
3759
  import { createServer } from "node:http";
1567
3760
  import { hostname } from "node:os";
1568
- var LOGIN_TIMEOUT_MS = 5 * 6e4;
3761
+ var LOGIN_TIMEOUT_MS = 15 * 6e4;
1569
3762
  var PROGRESS_INTERVAL_MS = 1e3;
1570
3763
  var page = (title, message) => `<!doctype html>
1571
- <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">
1572
3765
  <title>${title}</title><style>
1573
- body{margin:0;display:grid;place-items:center;min-height:100vh;font:15px/1.5 system-ui,sans-serif;color:#142238;background:#faf9f7}
1574
- 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)}
1575
3770
  </style></head><body><main><h1>${title}</h1><p>${message}</p></main></body></html>`;
1576
- var closeLoginServer = (server) => new Promise((resolve2) => {
1577
- server.close(() => resolve2());
3771
+ var closeLoginServer = (server) => new Promise((resolve4) => {
3772
+ server.close(() => resolve4());
1578
3773
  server.closeAllConnections();
1579
3774
  });
1580
3775
  function openBrowser(url) {
@@ -1592,7 +3787,7 @@ function timeoutLabel(timeoutMs) {
1592
3787
  return `${String(seconds)} second${seconds === 1 ? "" : "s"}`;
1593
3788
  }
1594
3789
  function waitForLoginCallback(server, expectedState, onProgress, timeoutMs = LOGIN_TIMEOUT_MS) {
1595
- return new Promise((resolve2, reject) => {
3790
+ return new Promise((resolve4, reject) => {
1596
3791
  const startedAt = Date.now();
1597
3792
  const waiting = () => {
1598
3793
  const elapsedMs = Date.now() - startedAt;
@@ -1649,7 +3844,7 @@ function waitForLoginCallback(server, expectedState, onProgress, timeoutMs = LOG
1649
3844
  response.writeHead(200);
1650
3845
  response.end(
1651
3846
  page("Marina CLI is signed in", "You can close this window and return to the terminal."),
1652
- () => resolve2(code)
3847
+ () => resolve4(code)
1653
3848
  );
1654
3849
  });
1655
3850
  });
@@ -1659,9 +3854,9 @@ async function loginWithBrowser(onOpen, onProgress = () => void 0) {
1659
3854
  const challenge = createHash("sha256").update(verifier).digest("base64url");
1660
3855
  const state = randomBytes(32).toString("base64url");
1661
3856
  const server = createServer();
1662
- await new Promise((resolve2, reject) => {
3857
+ await new Promise((resolve4, reject) => {
1663
3858
  server.once("error", reject);
1664
- server.listen(0, "127.0.0.1", resolve2);
3859
+ server.listen(0, "127.0.0.1", resolve4);
1665
3860
  });
1666
3861
  try {
1667
3862
  const address = server.address();
@@ -1685,65 +3880,18 @@ async function loginWithBrowser(onOpen, onProgress = () => void 0) {
1685
3880
  }
1686
3881
  }
1687
3882
 
1688
- // src/output.ts
1689
- var json = false;
1690
- var setJsonMode = (on) => {
1691
- json = on;
1692
- };
1693
- var isJsonMode = () => json;
1694
- var bold = (s) => `\x1B[1m${s}\x1B[22m`;
1695
- var dim = (s) => `\x1B[2m${s}\x1B[22m`;
1696
- var red = (s) => `\x1B[31m${s}\x1B[39m`;
1697
- var green = (s) => `\x1B[32m${s}\x1B[39m`;
1698
- function say(line = "") {
1699
- if (json) console.error(line);
1700
- else console.log(line);
1701
- }
1702
- function note(line) {
1703
- console.error(line);
1704
- }
1705
- function createProgress() {
1706
- let active = false;
1707
- let lastPhase = null;
1708
- let frame = 0;
1709
- const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1710
- const interactive = process.stderr.isTTY === true && !json;
1711
- return {
1712
- update(phase, line) {
1713
- if (interactive) {
1714
- process.stderr.write(`\r\x1B[2K${frames[frame++ % frames.length]} ${line}`);
1715
- active = true;
1716
- } else if (phase !== lastPhase) {
1717
- console.error(json ? JSON.stringify({ type: "progress", phase, message: line }) : line);
1718
- }
1719
- lastPhase = phase;
1720
- },
1721
- clear() {
1722
- if (active) process.stderr.write("\r\x1B[2K");
1723
- active = false;
1724
- }
1725
- };
1726
- }
1727
- function result(payload) {
1728
- if (json) console.log(JSON.stringify({ schema_version: 1, ok: true, ...payload }, null, 2));
1729
- }
1730
- function failure(code, message, extra = {}) {
1731
- if (json)
1732
- console.log(
1733
- JSON.stringify({ schema_version: 1, ok: false, error: { code, message, ...extra } }, null, 2)
1734
- );
1735
- else {
1736
- console.error(`${red(code)} ${message}`);
1737
- }
1738
- }
3883
+ // src/index.ts
3884
+ init_output();
1739
3885
 
1740
3886
  // src/pack.ts
1741
- import { lstatSync, readFileSync as readFileSync2, readdirSync } from "node:fs";
1742
- 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);
1743
3890
  var EXCLUDED_DIRS = /* @__PURE__ */ new Set([
1744
3891
  ".git",
1745
3892
  ".hg",
1746
3893
  ".svn",
3894
+ ".marina",
1747
3895
  "node_modules",
1748
3896
  "bower_components",
1749
3897
  ".venv",
@@ -1759,23 +3907,74 @@ var EXCLUDED_DIRS = /* @__PURE__ */ new Set([
1759
3907
  "coverage",
1760
3908
  ".idea",
1761
3909
  ".vscode",
1762
- ".marina",
1763
3910
  "__MACOSX"
1764
3911
  ]);
1765
3912
  var EXCLUDED_FILES = /* @__PURE__ */ new Set([".DS_Store"]);
1766
3913
  var MAX_TOTAL = 100 * 1024 * 1024;
1767
- 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
+ }
1768
3952
  function pack(dir) {
1769
3953
  const files = {};
1770
3954
  const skippedSecrets = [];
1771
3955
  let totalBytes = 0;
1772
- 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
+ }
1773
3969
  for (const entry of readdirSync(current)) {
1774
3970
  const full = join2(current, entry);
1775
3971
  const stat = lstatSync(full);
1776
3972
  if (stat.isSymbolicLink()) continue;
3973
+ const relativePath = relative(dir, full).replaceAll("\\", "/");
1777
3974
  if (stat.isDirectory()) {
1778
- if (!EXCLUDED_DIRS.has(entry)) walk(full);
3975
+ if (!EXCLUDED_DIRS.has(entry) && !ignoredBy(rules, relativePath, true)) {
3976
+ walk(full, rules);
3977
+ }
1779
3978
  continue;
1780
3979
  }
1781
3980
  if (EXCLUDED_FILES.has(entry)) continue;
@@ -1783,27 +3982,28 @@ function pack(dir) {
1783
3982
  skippedSecrets.push(relative(dir, full));
1784
3983
  continue;
1785
3984
  }
3985
+ if (ignoredBy(rules, relativePath, false)) continue;
1786
3986
  totalBytes += stat.size;
1787
3987
  if (totalBytes > MAX_TOTAL) {
1788
3988
  throw new Error(
1789
3989
  `this directory is over ${String(MAX_TOTAL / 1024 / 1024)} MB unpacked \u2014 trim it before deploying`
1790
3990
  );
1791
3991
  }
1792
- files[relative(dir, full).replaceAll("\\", "/")] = readFileSync2(full);
3992
+ files[relativePath] = readFileSync2(full);
1793
3993
  }
1794
3994
  };
1795
- walk(dir);
3995
+ walk(dir, []);
1796
3996
  const fileCount = Object.keys(files).length;
1797
3997
  if (fileCount === 0) throw new Error("nothing to deploy \u2014 this directory is empty");
1798
3998
  return { zip: zipSync(files), fileCount, totalBytes, skippedSecrets };
1799
3999
  }
1800
4000
 
1801
4001
  // src/manifest.ts
1802
- import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
1803
- 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";
1804
4004
  function readManifest(dir) {
1805
4005
  const path = join3(dir, "marina.json");
1806
- if (!existsSync2(path)) return null;
4006
+ if (!existsSync3(path)) return null;
1807
4007
  let value;
1808
4008
  try {
1809
4009
  value = JSON.parse(readFileSync3(path, "utf8"));
@@ -1824,7 +4024,7 @@ function readManifest(dir) {
1824
4024
  throw new Error('marina.json "icon" must be an emoji');
1825
4025
  }
1826
4026
  if (manifest.type !== void 0 && manifest.type !== "static" && manifest.type !== "dynamic") {
1827
- 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');
1828
4028
  }
1829
4029
  return manifest;
1830
4030
  }
@@ -1839,16 +4039,16 @@ function resolveAppName(dir, flag, manifest = readManifest(dir), linkedName) {
1839
4039
  } catch {
1840
4040
  }
1841
4041
  if (linkedName?.trim()) return linkedName.trim();
1842
- return basename(dir);
4042
+ return basename2(dir);
1843
4043
  }
1844
4044
 
1845
4045
  // src/skills.ts
1846
- 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";
1847
4047
  import { homedir as homedir2 } from "node:os";
1848
4048
  import { join as join4 } from "node:path";
1849
4049
 
1850
4050
  // ../../skills/marina-deploy/SKILL.md
1851
- 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";
1852
4052
 
1853
4053
  // ../../skills/marina-deploy/agents/openai.yaml
1854
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';
@@ -1870,7 +4070,7 @@ function targets(agent) {
1870
4070
  }));
1871
4071
  }
1872
4072
  if (agent === "codex" || agent === "claude") return [{ agent, home: agentHomes[agent] }];
1873
- 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 }));
1874
4074
  }
1875
4075
  function install(target, update) {
1876
4076
  const directory = join4(target.home, "skills", "marina-deploy");
@@ -1907,11 +4107,13 @@ function installSkills(agent) {
1907
4107
  }
1908
4108
 
1909
4109
  // src/update.ts
4110
+ init_package();
4111
+ init_config();
1910
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));
1911
4114
  function isNewerVersion(candidate, current) {
1912
- const parse = (version) => version.replace(/^v/, "").split("-", 1)[0].split(".").map((part) => Number.parseInt(part, 10));
1913
- const left = parse(candidate);
1914
- const right = parse(current);
4115
+ const left = parseVersion(candidate);
4116
+ const right = parseVersion(current);
1915
4117
  if (left.some(Number.isNaN) || right.some(Number.isNaN)) return false;
1916
4118
  for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
1917
4119
  const difference = (left[index] ?? 0) - (right[index] ?? 0);
@@ -1941,7 +4143,7 @@ async function availableUpdate() {
1941
4143
  // src/index.ts
1942
4144
  var HELP = `${bold("marina")} \u2014 deploy internal apps
1943
4145
 
1944
- marina setup sign in with Clerk in your browser
4146
+ marina setup sign in through Marina in your browser
1945
4147
  --skills install the Marina deployment skill
1946
4148
  --deploy-demo deploy Hello Marina after signing in
1947
4149
  marina logout remove the saved credential
@@ -1954,11 +4156,17 @@ var HELP = `${bold("marina")} \u2014 deploy internal apps
1954
4156
  --app <slug> deploy into an existing app
1955
4157
  marina status [--app <slug>] what is live, and how the last deploy went
1956
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
1957
4164
  marina versions [--app <slug>] version history
1958
4165
  marina rollback [--to <hash>] publish a previous version again
1959
- marina secrets list env vars and secrets for the app
1960
- marina secrets set K=V set one (--plain for a non-secret var)
1961
- 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
1962
4170
  marina list apps in your workspace
1963
4171
  marina open open this project's app
1964
4172
 
@@ -1966,7 +4174,7 @@ var HELP = `${bold("marina")} \u2014 deploy internal apps
1966
4174
  var ANSI_ESCAPE = new RegExp(`${String.fromCodePoint(27)}\\[[0-9;]*m`, "g");
1967
4175
  var shouldCheckForUpdates = false;
1968
4176
  function targetApp(flag) {
1969
- const linked = readLink(resolve("."))?.app;
4177
+ const linked = readLink(resolve3("."))?.app;
1970
4178
  const app = flag ?? linked;
1971
4179
  if (!app) {
1972
4180
  failure("no_app", "no app here \u2014 pass --app <slug>, or deploy from a linked project");
@@ -1975,6 +4183,15 @@ function targetApp(flag) {
1975
4183
  return app;
1976
4184
  }
1977
4185
  var shortDigest = (digest) => digest?.slice(7, 14) ?? "\u2014";
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
+ }
1978
4195
  async function deploy(dirArg, flags, emitResult = true) {
1979
4196
  let dir = null;
1980
4197
  let link = null;
@@ -1989,20 +4206,32 @@ async function deploy(dirArg, flags, emitResult = true) {
1989
4206
  packed = packDemo();
1990
4207
  name = flags.name?.trim() || DEMO_MANIFEST.name;
1991
4208
  } else {
1992
- dir = resolve(dirArg ?? ".");
4209
+ dir = resolve3(dirArg ?? ".");
1993
4210
  link = readLink(dir);
1994
4211
  target = flags.app ?? link?.app;
1995
4212
  const manifest = readManifest(dir);
1996
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
+ }
1997
4225
  packed = pack(dir);
1998
4226
  }
1999
- 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`));
2000
4229
  const progress = createProgress();
2001
4230
  const size = `${String(packed.fileCount)} files, ${String(Math.round(packed.totalBytes / 1024))} KB`;
2002
4231
  progress.update("uploading", `Uploading ${name} (${size})`);
2003
4232
  let started;
2004
4233
  try {
2005
- started = await startDeploy(packed.zip, name, target);
4234
+ started = await startDeploy(packed.zip, name, target, link?.base_revision);
2006
4235
  } catch (error) {
2007
4236
  progress.clear();
2008
4237
  if (error instanceof ApiError && error.code === "not_found" && link) {
@@ -2020,32 +4249,43 @@ async function deploy(dirArg, flags, emitResult = true) {
2020
4249
  deployed = await pollDeploy(started.id, (current) => {
2021
4250
  if (current.status !== "queued" && current.status !== "building") return;
2022
4251
  const elapsed = Math.max(1, Math.round((Date.now() - deployedAt) / 1e3));
2023
- 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)`;
2024
4253
  progress.update(current.status, message);
2025
4254
  });
2026
4255
  } finally {
2027
4256
  progress.clear();
2028
4257
  }
2029
4258
  if (deployed.status === "refused" && deployed.refusal) {
2030
- say(`${red("refused")} ${deployed.refusal.message}`);
2031
- if (deployed.refusal.action) say(` ${deployed.refusal.action}`);
2032
- if (deployed.build_log) {
2033
- say(dim("---- build log (tail) ----"));
2034
- say(dim(deployed.build_log.split("\n").slice(-20).join("\n")));
2035
- }
2036
4259
  failure("refused", deployed.refusal.message, {
4260
+ deploy_id: deployed.id,
2037
4261
  refusal: deployed.refusal,
2038
- build_log: deployed.build_log
4262
+ build_log: deployed.build_log,
4263
+ build_phase: deployed.build_phase,
4264
+ build_message: deployed.build_message
2039
4265
  });
4266
+ if (!isJsonMode() && deployed.refusal.action) {
4267
+ note(` ${terminalSafeText(deployed.refusal.action)}`);
4268
+ }
4269
+ deployAttemptHint(deployed.id, deployed.build_log !== null);
2040
4270
  process.exit(3);
2041
4271
  }
2042
4272
  if (deployed.status === "failed") {
2043
- 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);
2044
4280
  process.exit(1);
2045
4281
  }
2046
4282
  const slug = deployed.app_slug ?? "";
2047
- if (dir && (!link || link.app !== slug || link.name !== name)) {
2048
- 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
+ });
2049
4289
  }
2050
4290
  const published = deployed.version_state === "published";
2051
4291
  say(
@@ -2067,11 +4307,13 @@ async function deploy(dirArg, flags, emitResult = true) {
2067
4307
  const payload = {
2068
4308
  command: "deploy",
2069
4309
  status: "succeeded",
4310
+ deploy_id: deployed.id,
2070
4311
  app: slug,
2071
4312
  version: deployed.version_number,
2072
4313
  state: deployed.version_state,
2073
4314
  url: deployed.url,
2074
4315
  version_url: deployed.version_url,
4316
+ build_log: deployed.build_log,
2075
4317
  preparation: deployed.preparation,
2076
4318
  live: published,
2077
4319
  demo: dirArg === "demo"
@@ -2081,15 +4323,16 @@ async function deploy(dirArg, flags, emitResult = true) {
2081
4323
  }
2082
4324
  async function status(appFlag) {
2083
4325
  const app = targetApp(appFlag);
2084
- const [detail, deploys2] = await Promise.all([getApp(app), listDeploys(app, 1)]);
2085
- const last = deploys2[0];
4326
+ const [detail, history] = await Promise.all([getApp(app), listDeploys(app, 1)]);
4327
+ const last = history[0];
2086
4328
  say(`${bold(detail.app.name)} ${dim(`(${detail.app.slug})`)}`);
2087
4329
  say(` ${detail.app.current_version_id ? green(detail.app.status) : dim("nothing published")}`);
2088
4330
  if (detail.app.current_version_id) say(` ${detail.url}`);
2089
4331
  if (detail.app.archived_at) say(` ${red("archived")} \u2014 it isn't serving`);
2090
4332
  if (last) {
2091
4333
  const outcome = last.status === "succeeded" ? green("succeeded") : last.status === "queued" || last.status === "building" ? last.status : red(last.status);
2092
- 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}` : ""}`);
2093
4336
  }
2094
4337
  result({
2095
4338
  command: "status",
@@ -2102,17 +4345,55 @@ async function status(appFlag) {
2102
4345
  last_deploy: last ?? null
2103
4346
  });
2104
4347
  }
2105
- 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
+ }
2106
4360
  const app = targetApp(appFlag);
2107
4361
  const rows = await listDeploys(app);
2108
4362
  if (rows.length === 0) say("no deploys yet");
2109
4363
  for (const row of rows) {
2110
4364
  const mark = row.status === "succeeded" ? green("ok") : red(row.status);
2111
- const why = row.refusal ? ` \u2014 ${row.refusal.message}` : row.error ? ` \u2014 ${row.error}` : "";
2112
- 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}`);
2113
4368
  }
4369
+ const failedWithLog = rows.find((row) => row.status !== "succeeded" && row.build_log);
4370
+ if (failedWithLog) deployAttemptHint(failedWithLog.id, true);
2114
4371
  result({ command: "deploys", app, deploys: rows });
2115
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
+ }
2116
4397
  async function versions(appFlag) {
2117
4398
  const app = targetApp(appFlag);
2118
4399
  const [rows, detail] = await Promise.all([listVersions(app), getApp(app)]);
@@ -2154,40 +4435,6 @@ async function rollback(appFlag, to) {
2154
4435
  url: detail.url
2155
4436
  });
2156
4437
  }
2157
- async function secrets(appFlag, argv, plain) {
2158
- const app = targetApp(appFlag);
2159
- const [action, argument] = argv;
2160
- if (action === "set") {
2161
- const eq = argument?.indexOf("=") ?? -1;
2162
- if (!argument || eq < 1) {
2163
- failure("invalid_input", "usage: marina secrets set KEY=value");
2164
- process.exit(1);
2165
- }
2166
- const key = argument.slice(0, eq);
2167
- await setEnv(app, key, argument.slice(eq + 1), !plain);
2168
- say(`${green("ok")} set ${bold(key)}${plain ? "" : dim(" (secret)")}`);
2169
- result({ command: "secrets.set", app, key, kind: plain ? "plain" : "secret" });
2170
- return;
2171
- }
2172
- if (action === "rm") {
2173
- if (!argument) {
2174
- failure("invalid_input", "usage: marina secrets rm KEY");
2175
- process.exit(1);
2176
- }
2177
- await deleteEnv(app, argument);
2178
- say(`${green("ok")} removed ${bold(argument)}`);
2179
- result({ command: "secrets.rm", app, key: argument, removed: true });
2180
- return;
2181
- }
2182
- const rows = await listEnv(app);
2183
- if (rows.length === 0) say("nothing set");
2184
- for (const row of rows) {
2185
- say(
2186
- `${bold(row.key)} ${row.kind === "secret" ? dim("secret \u2014 write-only") : row.value ?? ""}`
2187
- );
2188
- }
2189
- result({ command: "secrets.list", app, env: rows });
2190
- }
2191
4438
  async function main() {
2192
4439
  setJsonMode(process.argv.slice(2).includes("--json"));
2193
4440
  const { positionals, values } = parseArgs({
@@ -2196,16 +4443,21 @@ async function main() {
2196
4443
  name: { type: "string" },
2197
4444
  app: { type: "string" },
2198
4445
  to: { type: "string" },
2199
- plain: { type: "boolean" },
4446
+ level: { type: "string" },
4447
+ limit: { type: "string" },
4448
+ cursor: { type: "string" },
2200
4449
  agent: { type: "string" },
4450
+ port: { type: "string" },
4451
+ schedules: { type: "boolean" },
4452
+ dir: { type: "string" },
2201
4453
  skills: { type: "boolean" },
2202
4454
  "deploy-demo": { type: "boolean" },
2203
4455
  json: { type: "boolean" },
2204
4456
  help: { type: "boolean", short: "h" }
2205
4457
  }
2206
4458
  });
2207
- setJsonMode(values.json === true);
2208
4459
  const command = positionals[0];
4460
+ setJsonMode(values.json === true || command === "logs");
2209
4461
  if (values.help || !command) {
2210
4462
  say(HELP);
2211
4463
  result({ command: "help", usage: HELP.replaceAll(ANSI_ESCAPE, "") });
@@ -2276,6 +4528,20 @@ async function main() {
2276
4528
  });
2277
4529
  return;
2278
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
+ }
2279
4545
  case "profile": {
2280
4546
  const profile = readProfile();
2281
4547
  const source = process.env.MARINA_TOKEN ? "environment" : profile.token ? "profile" : null;
@@ -2302,6 +4568,23 @@ async function main() {
2302
4568
  result({ command: "skills.install", skills: installed });
2303
4569
  return;
2304
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
+ }
2305
4588
  case "deploy":
2306
4589
  await deploy(positionals[1], values);
2307
4590
  return;
@@ -2310,7 +4593,10 @@ async function main() {
2310
4593
  return;
2311
4594
  case "deploys":
2312
4595
  case "deployments":
2313
- await deploys(values.app);
4596
+ await deploys(values.app, positionals[1]);
4597
+ return;
4598
+ case "logs":
4599
+ await logs(values.app, values);
2314
4600
  return;
2315
4601
  case "versions":
2316
4602
  await versions(values.app);
@@ -2318,9 +4604,6 @@ async function main() {
2318
4604
  case "rollback":
2319
4605
  await rollback(values.app, values.to);
2320
4606
  return;
2321
- case "secrets":
2322
- await secrets(values.app, positionals.slice(1), values.plain === true);
2323
- return;
2324
4607
  case "list": {
2325
4608
  const apps = await listApps();
2326
4609
  if (apps.length === 0) say("no apps yet \u2014 `marina deploy` one");