@marina-cloud/cli 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +67 -0
  2. package/dist/marina.mjs +2216 -0
  3. package/package.json +41 -0
@@ -0,0 +1,2216 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { resolve } from "node:path";
5
+ import { parseArgs } from "node:util";
6
+
7
+ // src/config.ts
8
+ import {
9
+ chmodSync,
10
+ existsSync,
11
+ mkdirSync,
12
+ readFileSync,
13
+ renameSync,
14
+ unlinkSync,
15
+ writeFileSync
16
+ } from "node:fs";
17
+ import { homedir } from "node:os";
18
+ import { join } from "node:path";
19
+ var MARINA_HOME = process.env.MARINA_HOME ?? join(homedir(), ".marina");
20
+ var PROFILE = join(MARINA_HOME, "profile");
21
+ var apiUrl = () => (process.env.MARINA_API ?? "https://v1.marina.cloud").replace(/\/$/, "");
22
+ var dashboardUrl = () => {
23
+ if (process.env.MARINA_DASHBOARD) return process.env.MARINA_DASHBOARD.replace(/\/$/, "");
24
+ const api = new URL(apiUrl());
25
+ return api.hostname === "localhost" || api.hostname === "127.0.0.1" ? "http://localhost:5173" : api.origin;
26
+ };
27
+ var profilePath = () => PROFILE;
28
+ function readProfile() {
29
+ try {
30
+ return JSON.parse(readFileSync(PROFILE, "utf8"));
31
+ } catch {
32
+ return {};
33
+ }
34
+ }
35
+ function writeProfile(profile) {
36
+ mkdirSync(MARINA_HOME, { recursive: true, mode: 448 });
37
+ const temporary = `${PROFILE}.${String(process.pid)}.tmp`;
38
+ writeFileSync(temporary, `${JSON.stringify(profile, null, 2)}
39
+ `, { mode: 384 });
40
+ renameSync(temporary, PROFILE);
41
+ chmodSync(PROFILE, 384);
42
+ }
43
+ function getToken() {
44
+ if (process.env.MARINA_TOKEN) return process.env.MARINA_TOKEN;
45
+ const profile = readProfile();
46
+ if (profile.api && profile.api !== apiUrl()) return null;
47
+ return profile.token ?? null;
48
+ }
49
+ function saveToken(token) {
50
+ writeProfile({ ...readProfile(), api: apiUrl(), token });
51
+ }
52
+ function clearToken() {
53
+ const { api: _api, token: _token, ...rest } = readProfile();
54
+ if (Object.keys(rest).length === 0) {
55
+ try {
56
+ unlinkSync(PROFILE);
57
+ } catch {
58
+ }
59
+ return;
60
+ }
61
+ writeProfile(rest);
62
+ }
63
+ var LINK = ".marina/project.json";
64
+ function readLink(dir) {
65
+ try {
66
+ return JSON.parse(readFileSync(join(dir, LINK), "utf8"));
67
+ } catch {
68
+ return null;
69
+ }
70
+ }
71
+ function writeLink(dir, link) {
72
+ mkdirSync(join(dir, ".marina"), { recursive: true });
73
+ writeFileSync(join(dir, LINK), `${JSON.stringify(link, null, 2)}
74
+ `);
75
+ }
76
+
77
+ // src/api.ts
78
+ var ApiError = class extends Error {
79
+ code;
80
+ status;
81
+ constructor(code, message, status2) {
82
+ super(message);
83
+ this.code = code;
84
+ this.status = status2;
85
+ }
86
+ };
87
+ async function request(path, init) {
88
+ const token = getToken();
89
+ if (!token) throw new ApiError("unauthenticated", "not signed in \u2014 run `marina setup`", 401);
90
+ const res = await fetch(`${apiUrl()}${path}`, {
91
+ ...init,
92
+ headers: { authorization: `Bearer ${token}`, ...init?.headers }
93
+ });
94
+ const body = await res.json().catch(() => ({}));
95
+ if (!res.ok) {
96
+ throw new ApiError(
97
+ body.error?.code ?? "error",
98
+ body.error?.message ?? `request failed (${String(res.status)})`,
99
+ res.status
100
+ );
101
+ }
102
+ return body;
103
+ }
104
+ async function exchangeCliLogin(code, codeVerifier) {
105
+ const res = await fetch(`${apiUrl()}/cli/auth/exchange`, {
106
+ method: "POST",
107
+ headers: { "content-type": "application/json" },
108
+ body: JSON.stringify({ code, code_verifier: codeVerifier })
109
+ });
110
+ const body = await res.json().catch(() => ({}));
111
+ if (!res.ok || !body.token) {
112
+ throw new ApiError(
113
+ body.error?.code ?? "login_failed",
114
+ body.error?.message ?? `login exchange failed (${String(res.status)})`,
115
+ res.status
116
+ );
117
+ }
118
+ return body.token;
119
+ }
120
+ async function startDeploy(zip, name, app) {
121
+ const form = new FormData();
122
+ form.set(
123
+ "code",
124
+ new Blob([new Uint8Array(zip).buffer], { type: "application/zip" }),
125
+ "upload.zip"
126
+ );
127
+ form.set("name", name);
128
+ form.set("source", "cli");
129
+ if (app) form.set("app", app);
130
+ const res = await request("/v1/deploys", {
131
+ method: "POST",
132
+ body: form
133
+ });
134
+ return res.deploy;
135
+ }
136
+ async function pollDeploy(id) {
137
+ for (; ; ) {
138
+ const { deploy: deploy2 } = await request(`/v1/deploys/${id}`);
139
+ if (deploy2.status !== "queued" && deploy2.status !== "building") return deploy2;
140
+ await new Promise((resolve2) => setTimeout(resolve2, 500));
141
+ }
142
+ }
143
+ async function listApps() {
144
+ const res = await request("/v1/apps");
145
+ return res.apps;
146
+ }
147
+ var getApp = (idOrSlug) => request(`/v1/apps/${encodeURIComponent(idOrSlug)}`);
148
+ async function getAppUrl(idOrSlug) {
149
+ return (await getApp(idOrSlug)).url;
150
+ }
151
+ async function listVersions(app) {
152
+ const res = await request(
153
+ `/v1/apps/${encodeURIComponent(app)}/versions`
154
+ );
155
+ return res.versions;
156
+ }
157
+ async function listDeploys(app, limit = 10) {
158
+ const res = await request(
159
+ `/v1/apps/${encodeURIComponent(app)}/deploys?limit=${String(limit)}`
160
+ );
161
+ return res.deploys;
162
+ }
163
+ async function restoreVersion(versionId) {
164
+ const res = await request(`/v1/versions/${versionId}/restore`, {
165
+ method: "POST"
166
+ });
167
+ return res.version;
168
+ }
169
+ async function listEnv(app) {
170
+ const res = await request(`/v1/apps/${encodeURIComponent(app)}/env`);
171
+ return res.env;
172
+ }
173
+ async function setEnv(app, key, value, secret) {
174
+ await request(`/v1/apps/${encodeURIComponent(app)}/env/${encodeURIComponent(key)}`, {
175
+ method: "PUT",
176
+ headers: { "content-type": "application/json" },
177
+ body: JSON.stringify({ value, kind: secret ? "secret" : "plain" })
178
+ });
179
+ }
180
+ async function deleteEnv(app, key) {
181
+ await request(`/v1/apps/${encodeURIComponent(app)}/env/${encodeURIComponent(key)}`, {
182
+ method: "DELETE"
183
+ });
184
+ }
185
+
186
+ // ../../node_modules/.pnpm/fflate@0.8.3/node_modules/fflate/esm/index.mjs
187
+ import { createRequire } from "module";
188
+ var require2 = createRequire("/");
189
+ var _a;
190
+ var Worker;
191
+ var isMarkedAsUntransferable;
192
+ try {
193
+ _a = require2("worker_threads"), Worker = _a.Worker, isMarkedAsUntransferable = _a.isMarkedAsUntransferable;
194
+ } catch (e) {
195
+ }
196
+ var u8 = Uint8Array;
197
+ var u16 = Uint16Array;
198
+ var i32 = Int32Array;
199
+ var fleb = new u8([
200
+ 0,
201
+ 0,
202
+ 0,
203
+ 0,
204
+ 0,
205
+ 0,
206
+ 0,
207
+ 0,
208
+ 1,
209
+ 1,
210
+ 1,
211
+ 1,
212
+ 2,
213
+ 2,
214
+ 2,
215
+ 2,
216
+ 3,
217
+ 3,
218
+ 3,
219
+ 3,
220
+ 4,
221
+ 4,
222
+ 4,
223
+ 4,
224
+ 5,
225
+ 5,
226
+ 5,
227
+ 5,
228
+ 0,
229
+ /* unused */
230
+ 0,
231
+ 0,
232
+ /* impossible */
233
+ 0
234
+ ]);
235
+ var fdeb = new u8([
236
+ 0,
237
+ 0,
238
+ 0,
239
+ 0,
240
+ 1,
241
+ 1,
242
+ 2,
243
+ 2,
244
+ 3,
245
+ 3,
246
+ 4,
247
+ 4,
248
+ 5,
249
+ 5,
250
+ 6,
251
+ 6,
252
+ 7,
253
+ 7,
254
+ 8,
255
+ 8,
256
+ 9,
257
+ 9,
258
+ 10,
259
+ 10,
260
+ 11,
261
+ 11,
262
+ 12,
263
+ 12,
264
+ 13,
265
+ 13,
266
+ /* unused */
267
+ 0,
268
+ 0
269
+ ]);
270
+ var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
271
+ var freb = function(eb, start) {
272
+ var b = new u16(31);
273
+ for (var i = 0; i < 31; ++i) {
274
+ b[i] = start += 1 << eb[i - 1];
275
+ }
276
+ var r = new i32(b[30]);
277
+ for (var i = 1; i < 30; ++i) {
278
+ for (var j = b[i]; j < b[i + 1]; ++j) {
279
+ r[j] = j - b[i] << 5 | i;
280
+ }
281
+ }
282
+ return { b, r };
283
+ };
284
+ var _a = freb(fleb, 2);
285
+ var fl = _a.b;
286
+ var revfl = _a.r;
287
+ fl[28] = 258, revfl[258] = 28;
288
+ var _b = freb(fdeb, 0);
289
+ var fd = _b.b;
290
+ var revfd = _b.r;
291
+ var rev = new u16(32768);
292
+ for (i = 0; i < 32768; ++i) {
293
+ x = (i & 43690) >> 1 | (i & 21845) << 1;
294
+ x = (x & 52428) >> 2 | (x & 13107) << 2;
295
+ x = (x & 61680) >> 4 | (x & 3855) << 4;
296
+ rev[i] = ((x & 65280) >> 8 | (x & 255) << 8) >> 1;
297
+ }
298
+ var x;
299
+ var i;
300
+ var hMap = (function(cd, mb, r) {
301
+ var s = cd.length;
302
+ var i = 0;
303
+ var l = new u16(mb);
304
+ for (; i < s; ++i) {
305
+ if (cd[i])
306
+ ++l[cd[i] - 1];
307
+ }
308
+ var le = new u16(mb);
309
+ for (i = 1; i < mb; ++i) {
310
+ le[i] = le[i - 1] + l[i - 1] << 1;
311
+ }
312
+ var co;
313
+ if (r) {
314
+ co = new u16(1 << mb);
315
+ var rvb = 15 - mb;
316
+ for (i = 0; i < s; ++i) {
317
+ if (cd[i]) {
318
+ var sv = i << 4 | cd[i];
319
+ var r_1 = mb - cd[i];
320
+ var v = le[cd[i] - 1]++ << r_1;
321
+ for (var m = v | (1 << r_1) - 1; v <= m; ++v) {
322
+ co[rev[v] >> rvb] = sv;
323
+ }
324
+ }
325
+ }
326
+ } else {
327
+ co = new u16(s);
328
+ for (i = 0; i < s; ++i) {
329
+ if (cd[i]) {
330
+ co[i] = rev[le[cd[i] - 1]++] >> 15 - cd[i];
331
+ }
332
+ }
333
+ }
334
+ return co;
335
+ });
336
+ var flt = new u8(288);
337
+ for (i = 0; i < 144; ++i)
338
+ flt[i] = 8;
339
+ var i;
340
+ for (i = 144; i < 256; ++i)
341
+ flt[i] = 9;
342
+ var i;
343
+ for (i = 256; i < 280; ++i)
344
+ flt[i] = 7;
345
+ var i;
346
+ for (i = 280; i < 288; ++i)
347
+ flt[i] = 8;
348
+ var i;
349
+ var fdt = new u8(32);
350
+ for (i = 0; i < 32; ++i)
351
+ fdt[i] = 5;
352
+ var i;
353
+ var flm = /* @__PURE__ */ hMap(flt, 9, 0);
354
+ var fdm = /* @__PURE__ */ hMap(fdt, 5, 0);
355
+ var shft = function(p) {
356
+ return (p + 7) / 8 | 0;
357
+ };
358
+ var slc = function(v, s, e) {
359
+ if (s == null || s < 0)
360
+ s = 0;
361
+ if (e == null || e > v.length)
362
+ e = v.length;
363
+ return new u8(v.subarray(s, e));
364
+ };
365
+ var ec = [
366
+ "unexpected EOF",
367
+ "invalid block type",
368
+ "invalid length/literal",
369
+ "invalid distance",
370
+ "stream finished",
371
+ "no stream handler",
372
+ ,
373
+ // determined by compression function
374
+ "no callback",
375
+ "invalid UTF-8 data",
376
+ "extra field too long",
377
+ "date not in range 1980-2099",
378
+ "filename too long",
379
+ "stream finishing",
380
+ "invalid zip data"
381
+ // determined by unknown compression method
382
+ ];
383
+ var err = function(ind, msg, nt) {
384
+ var e = new Error(msg || ec[ind]);
385
+ e.code = ind;
386
+ if (Error.captureStackTrace)
387
+ Error.captureStackTrace(e, err);
388
+ if (!nt)
389
+ throw e;
390
+ return e;
391
+ };
392
+ var wbits = function(d, p, v) {
393
+ v <<= p & 7;
394
+ var o = p / 8 | 0;
395
+ d[o] |= v;
396
+ d[o + 1] |= v >> 8;
397
+ };
398
+ var wbits16 = function(d, p, v) {
399
+ v <<= p & 7;
400
+ var o = p / 8 | 0;
401
+ d[o] |= v;
402
+ d[o + 1] |= v >> 8;
403
+ d[o + 2] |= v >> 16;
404
+ };
405
+ var hTree = function(d, mb) {
406
+ var t = [];
407
+ for (var i = 0; i < d.length; ++i) {
408
+ if (d[i])
409
+ t.push({ s: i, f: d[i] });
410
+ }
411
+ var s = t.length;
412
+ var t2 = t.slice();
413
+ if (!s)
414
+ return { t: et, l: 0 };
415
+ if (s == 1) {
416
+ var v = new u8(t[0].s + 1);
417
+ v[t[0].s] = 1;
418
+ return { t: v, l: 1 };
419
+ }
420
+ t.sort(function(a, b) {
421
+ return a.f - b.f;
422
+ });
423
+ t.push({ s: -1, f: 25001 });
424
+ var l = t[0], r = t[1], i0 = 0, i1 = 1, i2 = 2;
425
+ t[0] = { s: -1, f: l.f + r.f, l, r };
426
+ while (i1 != s - 1) {
427
+ l = t[t[i0].f < t[i2].f ? i0++ : i2++];
428
+ r = t[i0 != i1 && t[i0].f < t[i2].f ? i0++ : i2++];
429
+ t[i1++] = { s: -1, f: l.f + r.f, l, r };
430
+ }
431
+ var maxSym = t2[0].s;
432
+ for (var i = 1; i < s; ++i) {
433
+ if (t2[i].s > maxSym)
434
+ maxSym = t2[i].s;
435
+ }
436
+ var tr = new u16(maxSym + 1);
437
+ var mbt = ln(t[i1 - 1], tr, 0);
438
+ if (mbt > mb) {
439
+ var i = 0, dt = 0;
440
+ var lft = mbt - mb, cst = 1 << lft;
441
+ t2.sort(function(a, b) {
442
+ return tr[b.s] - tr[a.s] || a.f - b.f;
443
+ });
444
+ for (; i < s; ++i) {
445
+ var i2_1 = t2[i].s;
446
+ if (tr[i2_1] > mb) {
447
+ dt += cst - (1 << mbt - tr[i2_1]);
448
+ tr[i2_1] = mb;
449
+ } else
450
+ break;
451
+ }
452
+ dt >>= lft;
453
+ while (dt > 0) {
454
+ var i2_2 = t2[i].s;
455
+ if (tr[i2_2] < mb)
456
+ dt -= 1 << mb - tr[i2_2]++ - 1;
457
+ else
458
+ ++i;
459
+ }
460
+ for (; i >= 0 && dt; --i) {
461
+ var i2_3 = t2[i].s;
462
+ if (tr[i2_3] == mb) {
463
+ --tr[i2_3];
464
+ ++dt;
465
+ }
466
+ }
467
+ mbt = mb;
468
+ }
469
+ return { t: new u8(tr), l: mbt };
470
+ };
471
+ var ln = function(n, l, d) {
472
+ return n.s == -1 ? Math.max(ln(n.l, l, d + 1), ln(n.r, l, d + 1)) : l[n.s] = d;
473
+ };
474
+ var lc = function(c) {
475
+ var s = c.length;
476
+ while (s && !c[--s])
477
+ ;
478
+ var cl = new u16(++s);
479
+ var cli = 0, cln = c[0], cls = 1;
480
+ var w = function(v) {
481
+ cl[cli++] = v;
482
+ };
483
+ for (var i = 1; i <= s; ++i) {
484
+ if (c[i] == cln && i != s)
485
+ ++cls;
486
+ else {
487
+ if (!cln && cls > 2) {
488
+ for (; cls > 138; cls -= 138)
489
+ w(32754);
490
+ if (cls > 2) {
491
+ w(cls > 10 ? cls - 11 << 5 | 28690 : cls - 3 << 5 | 12305);
492
+ cls = 0;
493
+ }
494
+ } else if (cls > 3) {
495
+ w(cln), --cls;
496
+ for (; cls > 6; cls -= 6)
497
+ w(8304);
498
+ if (cls > 2)
499
+ w(cls - 3 << 5 | 8208), cls = 0;
500
+ }
501
+ while (cls--)
502
+ w(cln);
503
+ cls = 1;
504
+ cln = c[i];
505
+ }
506
+ }
507
+ return { c: cl.subarray(0, cli), n: s };
508
+ };
509
+ var clen = function(cf, cl) {
510
+ var l = 0;
511
+ for (var i = 0; i < cl.length; ++i)
512
+ l += cf[i] * cl[i];
513
+ return l;
514
+ };
515
+ var wfblk = function(out, pos, dat) {
516
+ var s = dat.length;
517
+ var o = shft(pos + 2);
518
+ out[o] = s & 255;
519
+ out[o + 1] = s >> 8;
520
+ out[o + 2] = out[o] ^ 255;
521
+ out[o + 3] = out[o + 1] ^ 255;
522
+ for (var i = 0; i < s; ++i)
523
+ out[o + i + 4] = dat[i];
524
+ return (o + 4 + s) * 8;
525
+ };
526
+ var wblk = function(dat, out, final, syms, lf, df, eb, li, bs, bl, p) {
527
+ wbits(out, p++, final);
528
+ ++lf[256];
529
+ var _a2 = hTree(lf, 15), dlt = _a2.t, mlb = _a2.l;
530
+ var _b2 = hTree(df, 15), ddt = _b2.t, mdb = _b2.l;
531
+ var _c = lc(dlt), lclt = _c.c, nlc = _c.n;
532
+ var _d = lc(ddt), lcdt = _d.c, ndc = _d.n;
533
+ var lcfreq = new u16(19);
534
+ for (var i = 0; i < lclt.length; ++i)
535
+ ++lcfreq[lclt[i] & 31];
536
+ for (var i = 0; i < lcdt.length; ++i)
537
+ ++lcfreq[lcdt[i] & 31];
538
+ var _e = hTree(lcfreq, 7), lct = _e.t, mlcb = _e.l;
539
+ var nlcc = 19;
540
+ for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc)
541
+ ;
542
+ var flen = bl + 5 << 3;
543
+ var ftlen = clen(lf, flt) + clen(df, fdt) + eb;
544
+ var dtlen = clen(lf, dlt) + clen(df, ddt) + eb + 14 + 3 * nlcc + clen(lcfreq, lct) + 2 * lcfreq[16] + 3 * lcfreq[17] + 7 * lcfreq[18];
545
+ if (bs >= 0 && flen <= ftlen && flen <= dtlen)
546
+ return wfblk(out, p, dat.subarray(bs, bs + bl));
547
+ var lm, ll, dm, dl;
548
+ wbits(out, p, 1 + (dtlen < ftlen)), p += 2;
549
+ if (dtlen < ftlen) {
550
+ lm = hMap(dlt, mlb, 0), ll = dlt, dm = hMap(ddt, mdb, 0), dl = ddt;
551
+ var llm = hMap(lct, mlcb, 0);
552
+ wbits(out, p, nlc - 257);
553
+ wbits(out, p + 5, ndc - 1);
554
+ wbits(out, p + 10, nlcc - 4);
555
+ p += 14;
556
+ for (var i = 0; i < nlcc; ++i)
557
+ wbits(out, p + 3 * i, lct[clim[i]]);
558
+ p += 3 * nlcc;
559
+ var lcts = [lclt, lcdt];
560
+ for (var it = 0; it < 2; ++it) {
561
+ var clct = lcts[it];
562
+ for (var i = 0; i < clct.length; ++i) {
563
+ var len = clct[i] & 31;
564
+ wbits(out, p, llm[len]), p += lct[len];
565
+ if (len > 15)
566
+ wbits(out, p, clct[i] >> 5 & 127), p += clct[i] >> 12;
567
+ }
568
+ }
569
+ } else {
570
+ lm = flm, ll = flt, dm = fdm, dl = fdt;
571
+ }
572
+ for (var i = 0; i < li; ++i) {
573
+ var sym = syms[i];
574
+ if (sym > 255) {
575
+ var len = sym >> 18 & 31;
576
+ wbits16(out, p, lm[len + 257]), p += ll[len + 257];
577
+ if (len > 7)
578
+ wbits(out, p, sym >> 23 & 31), p += fleb[len];
579
+ var dst = sym & 31;
580
+ wbits16(out, p, dm[dst]), p += dl[dst];
581
+ if (dst > 3)
582
+ wbits16(out, p, sym >> 5 & 8191), p += fdeb[dst];
583
+ } else {
584
+ wbits16(out, p, lm[sym]), p += ll[sym];
585
+ }
586
+ }
587
+ wbits16(out, p, lm[256]);
588
+ return p + ll[256];
589
+ };
590
+ var deo = /* @__PURE__ */ new i32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]);
591
+ var et = /* @__PURE__ */ new u8(0);
592
+ var dflt = function(dat, lvl, plvl, pre, post, st) {
593
+ var s = st.z || dat.length;
594
+ var o = new u8(pre + s + 5 * (1 + Math.ceil(s / 7e3)) + post);
595
+ var w = o.subarray(pre, o.length - post);
596
+ var lst = st.l;
597
+ var pos = (st.r || 0) & 7;
598
+ if (lvl) {
599
+ if (pos)
600
+ w[0] = st.r >> 3;
601
+ var opt = deo[lvl - 1];
602
+ var n = opt >> 13, c = opt & 8191;
603
+ var msk_1 = (1 << plvl) - 1;
604
+ var prev = st.p || new u16(32768), head = st.h || new u16(msk_1 + 1);
605
+ var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1;
606
+ var hsh = function(i2) {
607
+ return (dat[i2] ^ dat[i2 + 1] << bs1_1 ^ dat[i2 + 2] << bs2_1) & msk_1;
608
+ };
609
+ var syms = new i32(25e3);
610
+ var lf = new u16(288), df = new u16(32);
611
+ var lc_1 = 0, eb = 0, i = st.i || 0, li = 0, wi = st.w || 0, bs = 0;
612
+ for (; i + 2 < s; ++i) {
613
+ var hv = hsh(i);
614
+ var imod = i & 32767, pimod = head[hv];
615
+ prev[imod] = pimod;
616
+ head[hv] = imod;
617
+ if (wi <= i) {
618
+ var rem = s - i;
619
+ if ((lc_1 > 7e3 || li > 24576) && (rem > 423 || !lst)) {
620
+ pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i - bs, pos);
621
+ li = lc_1 = eb = 0, bs = i;
622
+ for (var j = 0; j < 286; ++j)
623
+ lf[j] = 0;
624
+ for (var j = 0; j < 30; ++j)
625
+ df[j] = 0;
626
+ }
627
+ var l = 2, d = 0, ch_1 = c, dif = imod - pimod & 32767;
628
+ if (rem > 2 && hv == hsh(i - dif)) {
629
+ var maxn = Math.min(n, rem) - 1;
630
+ var maxd = Math.min(32767, i);
631
+ var ml = Math.min(258, rem);
632
+ while (dif <= maxd && --ch_1 && imod != pimod) {
633
+ if (dat[i + l] == dat[i + l - dif]) {
634
+ var nl = 0;
635
+ for (; nl < ml && dat[i + nl] == dat[i + nl - dif]; ++nl)
636
+ ;
637
+ if (nl > l) {
638
+ l = nl, d = dif;
639
+ if (nl > maxn)
640
+ break;
641
+ var mmd = Math.min(dif, nl - 2);
642
+ var md = 0;
643
+ for (var j = 0; j < mmd; ++j) {
644
+ var ti = i - dif + j & 32767;
645
+ var pti = prev[ti];
646
+ var cd = ti - pti & 32767;
647
+ if (cd > md)
648
+ md = cd, pimod = ti;
649
+ }
650
+ }
651
+ }
652
+ imod = pimod, pimod = prev[imod];
653
+ dif += imod - pimod & 32767;
654
+ }
655
+ }
656
+ if (d) {
657
+ syms[li++] = 268435456 | revfl[l] << 18 | revfd[d];
658
+ var lin = revfl[l] & 31, din = revfd[d] & 31;
659
+ eb += fleb[lin] + fdeb[din];
660
+ ++lf[257 + lin];
661
+ ++df[din];
662
+ wi = i + l;
663
+ ++lc_1;
664
+ } else {
665
+ syms[li++] = dat[i];
666
+ ++lf[dat[i]];
667
+ }
668
+ }
669
+ }
670
+ for (i = Math.max(i, wi); i < s; ++i) {
671
+ syms[li++] = dat[i];
672
+ ++lf[dat[i]];
673
+ }
674
+ pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i - bs, pos);
675
+ if (!lst) {
676
+ st.r = pos & 7 | w[pos / 8 | 0] << 3;
677
+ pos -= 7;
678
+ st.h = head, st.p = prev, st.i = i, st.w = wi;
679
+ }
680
+ } else {
681
+ for (var i = st.w || 0; i < s + lst; i += 65535) {
682
+ var e = i + 65535;
683
+ if (e >= s) {
684
+ w[pos / 8 | 0] = lst;
685
+ e = s;
686
+ }
687
+ pos = wfblk(w, pos + 1, dat.subarray(i, e));
688
+ }
689
+ st.i = s;
690
+ }
691
+ return slc(o, 0, pre + shft(pos) + post);
692
+ };
693
+ var crct = /* @__PURE__ */ (function() {
694
+ var t = new Int32Array(256);
695
+ for (var i = 0; i < 256; ++i) {
696
+ var c = i, k = 9;
697
+ while (--k)
698
+ c = (c & 1 && -306674912) ^ c >>> 1;
699
+ t[i] = c;
700
+ }
701
+ return t;
702
+ })();
703
+ var crc = function() {
704
+ var c = -1;
705
+ return {
706
+ p: function(d) {
707
+ var cr = c;
708
+ for (var i = 0; i < d.length; ++i)
709
+ cr = crct[cr & 255 ^ d[i]] ^ cr >>> 8;
710
+ c = cr;
711
+ },
712
+ d: function() {
713
+ return ~c;
714
+ }
715
+ };
716
+ };
717
+ var dopt = function(dat, opt, pre, post, st) {
718
+ if (!st) {
719
+ st = { l: 1 };
720
+ if (opt.dictionary) {
721
+ var dict = opt.dictionary.subarray(-32768);
722
+ var newDat = new u8(dict.length + dat.length);
723
+ newDat.set(dict);
724
+ newDat.set(dat, dict.length);
725
+ dat = newDat;
726
+ st.w = dict.length;
727
+ }
728
+ }
729
+ return dflt(dat, opt.level == null ? 6 : opt.level, opt.mem == null ? st.l ? Math.ceil(Math.max(8, Math.min(13, Math.log(dat.length))) * 1.5) : 20 : 12 + opt.mem, pre, post, st);
730
+ };
731
+ var mrg = function(a, b) {
732
+ var o = {};
733
+ for (var k in a)
734
+ o[k] = a[k];
735
+ for (var k in b)
736
+ o[k] = b[k];
737
+ return o;
738
+ };
739
+ var wbytes = function(d, b, v) {
740
+ for (; v; ++b)
741
+ d[b] = v, v >>>= 8;
742
+ };
743
+ function deflateSync(data, opts) {
744
+ return dopt(data, opts || {}, 0, 0);
745
+ }
746
+ var fltn = function(d, p, t, o) {
747
+ for (var k in d) {
748
+ var val = d[k], n = p + k, op = o;
749
+ if (Array.isArray(val))
750
+ op = mrg(o, val[1]), val = val[0];
751
+ if (ArrayBuffer.isView(val))
752
+ t[n] = [val, op];
753
+ else {
754
+ t[n += "/"] = [new u8(0), op];
755
+ fltn(val, n, t, o);
756
+ }
757
+ }
758
+ };
759
+ var te = typeof TextEncoder != "undefined" && /* @__PURE__ */ new TextEncoder();
760
+ var td = typeof TextDecoder != "undefined" && /* @__PURE__ */ new TextDecoder();
761
+ var tds = 0;
762
+ try {
763
+ td.decode(et, { stream: true });
764
+ tds = 1;
765
+ } catch (e) {
766
+ }
767
+ function strToU8(str, latin1) {
768
+ if (latin1) {
769
+ var ar_1 = new u8(str.length);
770
+ for (var i = 0; i < str.length; ++i)
771
+ ar_1[i] = str.charCodeAt(i);
772
+ return ar_1;
773
+ }
774
+ if (te)
775
+ return te.encode(str);
776
+ var l = str.length;
777
+ var ar = new u8(str.length + (str.length >> 1));
778
+ var ai = 0;
779
+ var w = function(v) {
780
+ ar[ai++] = v;
781
+ };
782
+ for (var i = 0; i < l; ++i) {
783
+ if (ai + 5 > ar.length) {
784
+ var n = new u8(ai + 8 + (l - i << 1));
785
+ n.set(ar);
786
+ ar = n;
787
+ }
788
+ var c = str.charCodeAt(i);
789
+ if (c < 128 || latin1)
790
+ w(c);
791
+ else if (c < 2048)
792
+ w(192 | c >> 6), w(128 | c & 63);
793
+ else if (c > 55295 && c < 57344)
794
+ 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);
795
+ else
796
+ w(224 | c >> 12), w(128 | c >> 6 & 63), w(128 | c & 63);
797
+ }
798
+ return slc(ar, 0, ai);
799
+ }
800
+ var exfl = function(ex) {
801
+ var le = 0;
802
+ if (ex) {
803
+ for (var k in ex) {
804
+ var l = ex[k].length;
805
+ if (l > 65535)
806
+ err(9);
807
+ le += l + 4;
808
+ }
809
+ }
810
+ return le;
811
+ };
812
+ var wzh = function(d, b, f, fn, u, c, ce, co) {
813
+ var fl2 = fn.length, ex = f.extra, col = co && co.length;
814
+ var exl = exfl(ex);
815
+ wbytes(d, b, ce != null ? 33639248 : 67324752), b += 4;
816
+ if (ce != null)
817
+ d[b++] = 20, d[b++] = f.os;
818
+ d[b] = 20, b += 2;
819
+ d[b++] = f.flag << 1 | (c < 0 && 8), d[b++] = u && 8;
820
+ d[b++] = f.compression & 255, d[b++] = f.compression >> 8;
821
+ var dt = new Date(f.mtime == null ? Date.now() : f.mtime), y = dt.getFullYear() - 1980;
822
+ if (y < 0 || y > 119)
823
+ err(10);
824
+ wbytes(d, b, y << 25 | dt.getMonth() + 1 << 21 | dt.getDate() << 16 | dt.getHours() << 11 | dt.getMinutes() << 5 | dt.getSeconds() >> 1), b += 4;
825
+ if (c != -1) {
826
+ wbytes(d, b, f.crc);
827
+ wbytes(d, b + 4, c < 0 ? -c - 2 : c);
828
+ wbytes(d, b + 8, f.size);
829
+ }
830
+ wbytes(d, b + 12, fl2);
831
+ wbytes(d, b + 14, exl), b += 16;
832
+ if (ce != null) {
833
+ wbytes(d, b, col);
834
+ wbytes(d, b + 6, f.attrs);
835
+ wbytes(d, b + 10, ce), b += 14;
836
+ }
837
+ d.set(fn, b);
838
+ b += fl2;
839
+ if (exl) {
840
+ for (var k in ex) {
841
+ var exf = ex[k], l = exf.length;
842
+ wbytes(d, b, +k);
843
+ wbytes(d, b + 2, l);
844
+ d.set(exf, b + 4), b += 4 + l;
845
+ }
846
+ }
847
+ if (col)
848
+ d.set(co, b), b += col;
849
+ return b;
850
+ };
851
+ var wzf = function(o, b, c, d, e) {
852
+ wbytes(o, b, 101010256);
853
+ wbytes(o, b + 8, c);
854
+ wbytes(o, b + 10, c);
855
+ wbytes(o, b + 12, d);
856
+ wbytes(o, b + 16, e);
857
+ };
858
+ function zipSync(data, opts) {
859
+ if (!opts)
860
+ opts = {};
861
+ var r = {};
862
+ var files = [];
863
+ fltn(data, "", r, opts);
864
+ var o = 0;
865
+ var tot = 0;
866
+ for (var fn in r) {
867
+ var _a2 = r[fn], file = _a2[0], p = _a2[1];
868
+ var compression = p.level == 0 ? 0 : 8;
869
+ var f = strToU8(fn), s = f.length;
870
+ var com = p.comment, m = com && strToU8(com), ms = m && m.length;
871
+ var exl = exfl(p.extra);
872
+ if (s > 65535)
873
+ err(11);
874
+ var d = compression ? deflateSync(file, p) : file, l = d.length;
875
+ var c = crc();
876
+ c.p(file);
877
+ files.push(mrg(p, {
878
+ size: file.length,
879
+ crc: c.d(),
880
+ c: d,
881
+ f,
882
+ m,
883
+ u: s != fn.length || m && com.length != ms,
884
+ o,
885
+ compression
886
+ }));
887
+ o += 30 + s + exl + l;
888
+ tot += 76 + 2 * (s + exl) + (ms || 0) + l;
889
+ }
890
+ var out = new u8(tot + 22), oe = o, cdl = tot - o;
891
+ for (var i = 0; i < files.length; ++i) {
892
+ var f = files[i];
893
+ wzh(out, f.o, f, f.f, f.u, f.c.length);
894
+ var badd = 30 + f.f.length + exfl(f.extra);
895
+ out.set(f.c, f.o + badd);
896
+ wzh(out, o, f, f.f, f.u, f.c.length, f.o, f.m), o += 16 + badd + (f.m ? f.m.length : 0);
897
+ }
898
+ wzf(out, o, files.length, cdl, oe);
899
+ return out;
900
+ }
901
+
902
+ // demo/app.js.txt
903
+ var app_js_default = 'const canvas = document.querySelector("#canvas");\nconst originalPieces = [...document.querySelectorAll(".piece")];\nconst pieces = [...originalPieces];\nconst transformUI = document.querySelector("#transform-ui");\nconst transformHandle = document.querySelector("#transform-handle");\nconst initial = originalPieces.map((piece) => ({\n piece,\n x: getComputedStyle(piece).getPropertyValue("--x").trim(),\n y: getComputedStyle(piece).getPropertyValue("--y").trim(),\n rotation: getComputedStyle(piece).getPropertyValue("--rotation").trim(),\n scale: getComputedStyle(piece).getPropertyValue("--scale").trim() || "1",\n}));\nconst addedColors = ["#ff5e78", "#f03f30", "#0f7449", "#dfff3f", "#fff8e8", "#93cfff"];\nlet selectedPiece;\nlet handleAnchor;\nlet topLayer = 10;\nlet transformFrame;\n\nfunction numberStyle(piece, name, fallback = 0) {\n return Number.parseFloat(getComputedStyle(piece).getPropertyValue(name)) || fallback;\n}\n\nfunction place(piece, x, y, rotation) {\n piece.style.setProperty("--x", `${Math.max(3, Math.min(97, x))}%`);\n piece.style.setProperty("--y", `${Math.max(4, Math.min(96, y))}%`);\n piece.style.setProperty("--rotation", `${rotation}deg`);\n}\n\nfunction setScale(piece, scale) {\n const next = Math.max(0.45, Math.min(1.8, scale));\n piece.style.setProperty("--scale", `${next}`);\n}\n\nfunction updateTransformUI() {\n if (!selectedPiece || selectedPiece.hidden) return;\n const canvasBounds = canvas.getBoundingClientRect();\n const pieceBounds = selectedPiece.getBoundingClientRect();\n const centerX = pieceBounds.left - canvasBounds.left + pieceBounds.width / 2;\n const centerY = pieceBounds.top - canvasBounds.top + pieceBounds.height / 2;\n const defaultAnchor = { x: pieceBounds.width / 2, y: pieceBounds.height / 2 };\n const anchor = handleAnchor || defaultAnchor;\n const left = Math.max(20, Math.min(canvasBounds.width - 60, centerX + anchor.x));\n const top = Math.max(48, Math.min(canvasBounds.height - 48, centerY + anchor.y));\n transformUI.style.left = `${left}px`;\n transformUI.style.top = `${top}px`;\n}\n\nfunction queueTransformUIUpdate() {\n if (transformFrame) return;\n transformFrame = requestAnimationFrame(() => {\n transformFrame = undefined;\n updateTransformUI();\n });\n}\n\nfunction followPointer(event) {\n const canvasBounds = canvas.getBoundingClientRect();\n const left = Math.max(20, Math.min(canvasBounds.width - 60, event.clientX - canvasBounds.left));\n const top = Math.max(48, Math.min(canvasBounds.height - 48, event.clientY - canvasBounds.top));\n transformUI.style.left = `${left}px`;\n transformUI.style.top = `${top}px`;\n if (selectedPiece) {\n const pieceBounds = selectedPiece.getBoundingClientRect();\n const centerX = pieceBounds.left - canvasBounds.left + pieceBounds.width / 2;\n const centerY = pieceBounds.top - canvasBounds.top + pieceBounds.height / 2;\n handleAnchor = { x: left - centerX, y: top - centerY };\n }\n}\n\nfunction selectPiece(piece) {\n if (piece && piece === selectedPiece) {\n transformUI.hidden = false;\n queueTransformUIUpdate();\n return;\n }\n selectedPiece?.classList.remove("selected");\n selectedPiece = piece;\n handleAnchor = undefined;\n if (!piece) {\n transformUI.hidden = true;\n return;\n }\n piece.classList.add("selected");\n transformUI.hidden = false;\n requestAnimationFrame(updateTransformUI);\n}\n\nfunction makeInteractive(piece) {\n let activePointer;\n\n piece.addEventListener("pointerdown", (event) => {\n activePointer = event.pointerId;\n piece.setPointerCapture(activePointer);\n piece.classList.add("dragging");\n piece.style.zIndex = `${++topLayer}`;\n selectPiece(piece);\n });\n\n piece.addEventListener("pointermove", (event) => {\n if (event.pointerId !== activePointer) return;\n const bounds = canvas.getBoundingClientRect();\n const x = ((event.clientX - bounds.left) / bounds.width) * 100;\n const y = ((event.clientY - bounds.top) / bounds.height) * 100;\n place(piece, x, y, numberStyle(piece, "--rotation"));\n queueTransformUIUpdate();\n });\n\n const release = (event) => {\n if (event.pointerId !== activePointer) return;\n piece.classList.remove("dragging");\n activePointer = undefined;\n };\n piece.addEventListener("pointerup", release);\n piece.addEventListener("pointercancel", release);\n\n piece.addEventListener("dblclick", () => {\n piece.style.setProperty("--rotation", `${numberStyle(piece, "--rotation") + 45}deg`);\n updateTransformUI();\n });\n\n piece.addEventListener("keydown", (event) => {\n const moves = { ArrowLeft: [-2, 0], ArrowRight: [2, 0], ArrowUp: [0, -2], ArrowDown: [0, 2] };\n if (!moves[event.key]) return;\n event.preventDefault();\n const [dx, dy] = moves[event.key];\n place(piece, numberStyle(piece, "--x") + dx, numberStyle(piece, "--y") + dy, numberStyle(piece, "--rotation"));\n updateTransformUI();\n });\n}\n\noriginalPieces.forEach(makeInteractive);\n\ncanvas.addEventListener("pointerdown", (event) => {\n if (event.target === canvas) selectPiece(undefined);\n});\ndocument.querySelector("#add-piece").addEventListener("click", () => {\n const source = originalPieces[Math.floor(Math.random() * originalPieces.length)];\n const piece = source.cloneNode(true);\n piece.dataset.added = "true";\n piece.dataset.piece = `${source.dataset.piece}-copy`;\n piece.setAttribute("aria-label", `Added ${source.dataset.piece} cutout`);\n piece.classList.remove("selected", "dragging");\n piece.style.zIndex = `${++topLayer}`;\n piece.style.setProperty("--enter-delay", "0ms");\n piece.style.setProperty("--added-color", addedColors[Math.floor(Math.random() * addedColors.length)]);\n place(piece, 10 + Math.random() * 80, 12 + Math.random() * 76, -180 + Math.random() * 360);\n setScale(piece, 0.55 + Math.random() * 0.8);\n canvas.insertBefore(piece, transformUI);\n pieces.push(piece);\n makeInteractive(piece);\n selectPiece(piece);\n piece.focus();\n});\n\ntransformHandle.addEventListener("pointerdown", (event) => {\n if (!selectedPiece) return;\n event.preventDefault();\n event.stopPropagation();\n transformHandle.setPointerCapture(event.pointerId);\n selectedPiece.classList.add("transforming");\n\n const bounds = selectedPiece.getBoundingClientRect();\n const centerX = bounds.left + bounds.width / 2;\n const centerY = bounds.top + bounds.height / 2;\n const startDistance = Math.max(20, Math.hypot(event.clientX - centerX, event.clientY - centerY));\n const startScale = numberStyle(selectedPiece, "--scale", 1);\n let rotation = numberStyle(selectedPiece, "--rotation");\n let lastAngle = Math.atan2(event.clientY - centerY, event.clientX - centerX);\n\n const transform = (moveEvent) => {\n if (moveEvent.pointerId !== event.pointerId || !selectedPiece) return;\n const distance = Math.hypot(moveEvent.clientX - centerX, moveEvent.clientY - centerY);\n const angle = Math.atan2(moveEvent.clientY - centerY, moveEvent.clientX - centerX);\n let angleDelta = angle - lastAngle;\n if (angleDelta > Math.PI) angleDelta -= Math.PI * 2;\n if (angleDelta < -Math.PI) angleDelta += Math.PI * 2;\n rotation += (angleDelta * 180) / Math.PI;\n lastAngle = angle;\n setScale(selectedPiece, startScale * (distance / startDistance));\n selectedPiece.style.setProperty("--rotation", `${rotation}deg`);\n followPointer(moveEvent);\n };\n\n const finish = (upEvent) => {\n if (upEvent.pointerId !== event.pointerId) return;\n selectedPiece?.classList.remove("transforming");\n transformHandle.removeEventListener("pointermove", transform);\n transformHandle.removeEventListener("pointerup", finish);\n transformHandle.removeEventListener("pointercancel", finish);\n queueTransformUIUpdate();\n };\n\n transformHandle.addEventListener("pointermove", transform);\n transformHandle.addEventListener("pointerup", finish);\n transformHandle.addEventListener("pointercancel", finish);\n});\n\ntransformHandle.addEventListener("keydown", (event) => {\n if (!selectedPiece) return;\n if (event.key === "ArrowUp" || event.key === "ArrowDown") {\n event.preventDefault();\n setScale(selectedPiece, numberStyle(selectedPiece, "--scale", 1) + (event.key === "ArrowUp" ? 0.05 : -0.05));\n } else if (event.key === "ArrowLeft" || event.key === "ArrowRight") {\n event.preventDefault();\n selectedPiece.style.setProperty(\n "--rotation",\n `${numberStyle(selectedPiece, "--rotation") + (event.key === "ArrowRight" ? 5 : -5)}deg`,\n );\n } else {\n return;\n }\n requestAnimationFrame(updateTransformUI);\n});\n\ndocument.querySelector("#delete-piece").addEventListener("click", () => {\n if (!selectedPiece) return;\n const piece = selectedPiece;\n selectPiece(undefined);\n if (piece.dataset.added === "true") {\n pieces.splice(pieces.indexOf(piece), 1);\n piece.remove();\n return;\n }\n piece.hidden = true;\n});\n\ndocument.querySelector("#shuffle").addEventListener("click", () => {\n selectPiece(undefined);\n canvas.classList.add("shuffling");\n pieces\n .filter((piece) => !piece.hidden)\n .forEach((piece) => place(piece, 8 + Math.random() * 84, 12 + Math.random() * 76, -180 + Math.random() * 360));\n setTimeout(() => canvas.classList.remove("shuffling"), 850);\n});\n\nwindow.addEventListener("resize", updateTransformUI);\n\ndocument.querySelector("#reset").addEventListener("click", () => {\n canvas.classList.add("shuffling");\n selectPiece(undefined);\n for (let index = pieces.length - 1; index >= 0; index -= 1) {\n if (pieces[index].dataset.added === "true") {\n pieces[index].remove();\n pieces.splice(index, 1);\n }\n }\n initial.forEach(({ piece, x, y, rotation, scale }) => {\n piece.hidden = false;\n piece.style.setProperty("--x", x);\n piece.style.setProperty("--y", y);\n piece.style.setProperty("--rotation", rotation);\n piece.style.setProperty("--scale", scale);\n piece.style.removeProperty("z-index");\n });\n setTimeout(() => canvas.classList.remove("shuffling"), 850);\n});\n';
904
+
905
+ // demo/index.html
906
+ var demo_default = '<!doctype html>\n<html lang="en">\n <head>\n <meta charset="UTF-8" />\n <meta name="viewport" content="width=device-width, initial-scale=1.0" />\n <meta name="theme-color" content="#0966c7" />\n <title>Hello Marina</title>\n <link rel="stylesheet" href="styles.css" />\n </head>\n <body data-version="4">\n <main class="canvas" id="canvas" aria-label="Interactive cut-paper collage">\n <header>\n <span class="brand">Marina</span>\n <div class="controls">\n <button id="add-piece" type="button" aria-label="Add another piece" title="Add piece">\n <svg viewBox="0 0 24 24" aria-hidden="true">\n <path d="M12 5v14M5 12h14" />\n </svg>\n </button>\n <button id="shuffle" type="button" aria-label="Shuffle the collage" title="Shuffle">\n <svg viewBox="0 0 24 24" aria-hidden="true">\n <path\n d="M4 7h3.2c4.6 0 5.1 10 9.6 10H20m0 0-3-3m3 3-3 3M4 17h3.2c1.8 0 3-1.5 4-3.3M20 7h-3.2c-1.4 0-2.5.9-3.4 2.2M20 7l-3-3m3 3-3 3"\n />\n </svg>\n </button>\n <button id="reset" type="button" aria-label="Reset the collage" title="Reset">\n <svg viewBox="0 0 24 24" aria-hidden="true">\n <path d="M5.2 8.2A8 8 0 1 1 4 14m1.2-5.8V4m0 4.2H9.4" />\n </svg>\n </button>\n </div>\n </header>\n\n <button class="piece piece-sun" data-piece="sun" aria-label="Yellow sun cutout">\n <svg viewBox="0 0 200 200" aria-hidden="true"><circle cx="100" cy="100" r="98" /></svg>\n </button>\n\n <button class="piece piece-cape" data-piece="cape" aria-label="Coral cape cutout">\n <svg viewBox="0 0 420 360" aria-hidden="true">\n <path\n d="M16 352C55 257 116 203 171 167c66-43 98-94 113-155 58 41 98 91 120 150v190H16Z"\n />\n </svg>\n </button>\n\n <button class="piece piece-leaf" data-piece="leaf" aria-label="Green leaf cutout">\n <svg viewBox="0 0 250 360" aria-hidden="true">\n <path\n d="M118 358c-5-48-4-93 4-137-28 19-57 25-87 20 4-38 30-74 78-108-42-5-74-23-95-54 33-31 75-43 125-34C156 21 177 6 207 0c18 45 14 88-12 130 27-8 45-4 55 12-14 36-43 66-87 90-13 46-18 88-16 126h-29Z"\n />\n </svg>\n </button>\n\n <button class="piece piece-bloom" data-piece="bloom" aria-label="Pink flower cutout">\n <svg viewBox="0 0 260 260" aria-hidden="true">\n <path\n d="M128 87C105 30 67 7 44 26 21 44 38 83 91 111 29 111-5 137 10 165c16 28 60 14 105-28-17 58-1 98 31 96 31-2 36-48 5-100 48 38 91 40 98 9 8-31-28-55-88-57 50-36 60-79 31-91-29-13-54 19-64 93Z"\n />\n <circle class="bloom-center" cx="130" cy="120" r="23" />\n </svg>\n </button>\n\n <button class="piece piece-fan" data-piece="fan" aria-label="Cream fan cutout">\n <svg viewBox="0 0 310 300" aria-hidden="true">\n <path d="M154 286 12 154C49 75 92 27 154 3c62 24 105 72 142 151L154 286Z" />\n <path\n class="piece-line"\n d="M154 276V15m0 261L45 126m109 150 109-150m-109 150L91 55m63 221 63-221"\n />\n </svg>\n </button>\n\n <button class="piece piece-star" data-piece="star" aria-label="Yellow star cutout">\n <svg viewBox="0 0 260 260" aria-hidden="true">\n <path\n d="m130 0 21 82 63-57-31 80 77 5-74 31 54 63-71-39-10 82-16-80-66 51 38-74-80-13 81-23L60 40l69 43L130 0Z"\n />\n </svg>\n </button>\n\n <button class="piece piece-fish" data-piece="fish" aria-label="White fish cutout">\n <svg viewBox="0 0 330 180" aria-hidden="true">\n <path d="M12 92c63-74 165-94 245-32l61-45-17 74 17 75-62-43C173 185 72 164 12 92Z" />\n <circle class="fish-eye" cx="212" cy="72" r="8" />\n </svg>\n </button>\n\n <button class="piece piece-sail" data-piece="sail" aria-label="Sailboat cutout">\n <svg viewBox="0 0 250 330" aria-hidden="true">\n <path class="sail-mast" d="M129 17v242" />\n <path class="sail-main" d="M119 24 25 242h94V24Z" />\n <path class="sail-side" d="m139 72 82 170h-82V72Z" />\n <path class="sail-hull" d="M15 260h220l-37 52H54l-39-52Z" />\n </svg>\n </button>\n\n <button class="piece piece-ribbon" data-piece="ribbon" aria-label="Red ribbon cutout">\n <svg viewBox="0 0 300 430" aria-hidden="true">\n <path\n d="M37 421c15-76 60-125 135-147-79-14-129-53-150-117 60-26 119-17 176 28C168 119 177 59 225 4c52 43 69 101 51 174-12 50-54 93-125 130 41 16 81 53 119 111-65 18-120 1-164-51-7 15-13 33-18 53H37Z"\n />\n </svg>\n </button>\n\n <div class="transform-ui" id="transform-ui" hidden>\n <button\n class="transform-handle"\n id="transform-handle"\n type="button"\n aria-label="Drag to scale and rotate selected piece"\n >\n <svg viewBox="0 0 24 24" aria-hidden="true">\n <path d="M8 4H4v4m0-4 5 5m7 11h4v-4m0 4-5-5M20 8V4h-4m4 0-5 5M4 16v4h4m-4 0 5-5" />\n </svg>\n </button>\n <button\n class="delete-handle"\n id="delete-piece"\n type="button"\n aria-label="Delete selected piece"\n >\n <svg viewBox="0 0 24 24" aria-hidden="true">\n <path d="M5 7h14M9 7V4h6v3m-8 0 1 13h8l1-13M10 10v7m4-7v7" />\n </svg>\n </button>\n </div>\n\n <p class="hint">Drag the pieces</p>\n <div class="grain" aria-hidden="true"></div>\n </main>\n\n <script src="app.js"></script>\n </body>\n</html>\n';
907
+
908
+ // demo/styles.css
909
+ var styles_default = `:root {
910
+ --sea: #0966c7;
911
+ --deep-sea: #064990;
912
+ --coral: #ff5e78;
913
+ --red: #f03f30;
914
+ --leaf: #0f7449;
915
+ --acid: #dfff3f;
916
+ --cream: #fff8e8;
917
+ --sky: #93cfff;
918
+ color: var(--cream);
919
+ background: var(--sea);
920
+ font-family: Inter, "Helvetica Neue", ui-sans-serif, system-ui, sans-serif;
921
+ font-synthesis: none;
922
+ }
923
+
924
+ * {
925
+ box-sizing: border-box;
926
+ }
927
+
928
+ html,
929
+ body {
930
+ min-width: 320px;
931
+ min-height: 100%;
932
+ margin: 0;
933
+ overflow: hidden;
934
+ }
935
+
936
+ button {
937
+ font: inherit;
938
+ }
939
+
940
+ .canvas {
941
+ position: relative;
942
+ width: 100vw;
943
+ height: 100svh;
944
+ min-height: 560px;
945
+ overflow: hidden;
946
+ isolation: isolate;
947
+ background: var(--sea);
948
+ }
949
+
950
+ .canvas::before {
951
+ position: absolute;
952
+ z-index: -3;
953
+ inset: 0;
954
+ pointer-events: none;
955
+ content: "";
956
+ background:
957
+ radial-gradient(circle at 18% 12%, rgb(255 255 255 / 7%) 0 0.8px, transparent 1.1px),
958
+ linear-gradient(145deg, rgb(255 255 255 / 4%), transparent 45%);
959
+ background-size:
960
+ 6px 6px,
961
+ auto;
962
+ }
963
+
964
+ .grain {
965
+ position: absolute;
966
+ z-index: 50;
967
+ inset: 0;
968
+ pointer-events: none;
969
+ opacity: 0.22;
970
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.75' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.22'/%3E%3C/svg%3E");
971
+ mix-blend-mode: soft-light;
972
+ }
973
+
974
+ header {
975
+ position: absolute;
976
+ z-index: 100;
977
+ top: clamp(20px, 3.2vw, 44px);
978
+ right: clamp(20px, 3.2vw, 44px);
979
+ left: clamp(20px, 3.2vw, 44px);
980
+ display: flex;
981
+ justify-content: space-between;
982
+ align-items: center;
983
+ pointer-events: none;
984
+ animation: chrome-arrive 500ms ease-out 750ms both;
985
+ }
986
+
987
+ .brand {
988
+ color: rgb(255 248 232 / 92%);
989
+ font-size: 1.05rem;
990
+ font-weight: 720;
991
+ letter-spacing: -0.04em;
992
+ }
993
+
994
+ .controls {
995
+ display: flex;
996
+ gap: 8px;
997
+ pointer-events: auto;
998
+ }
999
+
1000
+ .controls button {
1001
+ display: grid;
1002
+ place-items: center;
1003
+ width: 42px;
1004
+ height: 42px;
1005
+ padding: 0;
1006
+ border: 1px solid rgb(255 248 232 / 44%);
1007
+ border-radius: 50%;
1008
+ color: var(--cream);
1009
+ background: rgb(4 58 128 / 15%);
1010
+ cursor: pointer;
1011
+ backdrop-filter: blur(5px);
1012
+ transition:
1013
+ color 150ms ease,
1014
+ background 150ms ease,
1015
+ transform 150ms ease;
1016
+ }
1017
+
1018
+ .controls button:hover {
1019
+ color: var(--deep-sea);
1020
+ background: var(--cream);
1021
+ transform: rotate(-5deg);
1022
+ }
1023
+
1024
+ .controls svg {
1025
+ width: 18px;
1026
+ fill: none;
1027
+ stroke: currentcolor;
1028
+ stroke-linecap: round;
1029
+ stroke-linejoin: round;
1030
+ stroke-width: 1.7;
1031
+ }
1032
+
1033
+ .controls button:focus-visible,
1034
+ .piece:focus-visible {
1035
+ outline: 3px solid var(--acid);
1036
+ outline-offset: 4px;
1037
+ }
1038
+
1039
+ .piece {
1040
+ --x: 50%;
1041
+ --y: 50%;
1042
+ --rotation: 0deg;
1043
+ --scale: 1;
1044
+ position: absolute;
1045
+ z-index: 5;
1046
+ top: var(--y);
1047
+ left: var(--x);
1048
+ width: 180px;
1049
+ padding: 0;
1050
+ border: 0;
1051
+ color: var(--cream);
1052
+ background: transparent;
1053
+ cursor: grab;
1054
+ touch-action: none;
1055
+ transform: translate(-50%, -50%) rotate(var(--rotation)) scale(var(--scale));
1056
+ transform-origin: center;
1057
+ transition:
1058
+ filter 160ms ease,
1059
+ transform 500ms cubic-bezier(0.22, 0.8, 0.25, 1);
1060
+ }
1061
+
1062
+ .piece svg {
1063
+ display: block;
1064
+ width: 100%;
1065
+ overflow: visible;
1066
+ fill: currentcolor;
1067
+ filter: drop-shadow(3px 8px 4px rgb(4 42 93 / 14%));
1068
+ transform-box: fill-box;
1069
+ transform-origin: center;
1070
+ animation: piece-arrive 760ms cubic-bezier(0.2, 0.8, 0.24, 1) var(--enter-delay, 0ms) both;
1071
+ }
1072
+
1073
+ .piece:hover {
1074
+ z-index: 30;
1075
+ filter: brightness(1.06) saturate(1.03);
1076
+ }
1077
+
1078
+ .piece.selected,
1079
+ .piece.dragging,
1080
+ .piece.transforming {
1081
+ z-index: 40;
1082
+ cursor: grabbing;
1083
+ transition: filter 160ms ease;
1084
+ filter: drop-shadow(8px 14px 8px rgb(3 31 74 / 20%));
1085
+ }
1086
+
1087
+ .piece.selected svg {
1088
+ filter: drop-shadow(0 0 0.8px rgb(255 248 232 / 95%)) drop-shadow(3px 8px 4px rgb(4 42 93 / 14%));
1089
+ }
1090
+
1091
+ .piece[data-added="true"] {
1092
+ color: var(--added-color);
1093
+ }
1094
+
1095
+ .piece[hidden] {
1096
+ display: none;
1097
+ }
1098
+
1099
+ .piece-sun {
1100
+ --enter-delay: 40ms;
1101
+ --x: 14%;
1102
+ --y: 24%;
1103
+ --rotation: -4deg;
1104
+ width: clamp(120px, 13vw, 210px);
1105
+ color: var(--acid);
1106
+ }
1107
+
1108
+ .piece-cape {
1109
+ --enter-delay: 100ms;
1110
+ --x: 89%;
1111
+ --y: 13%;
1112
+ --rotation: 13deg;
1113
+ width: clamp(360px, 39vw, 650px);
1114
+ color: var(--coral);
1115
+ }
1116
+
1117
+ .piece-leaf {
1118
+ --enter-delay: 460ms;
1119
+ --x: 12%;
1120
+ --y: 78%;
1121
+ --rotation: -22deg;
1122
+ width: clamp(150px, 17vw, 275px);
1123
+ color: var(--leaf);
1124
+ }
1125
+
1126
+ .piece-bloom {
1127
+ --enter-delay: 520ms;
1128
+ --x: 29%;
1129
+ --y: 86%;
1130
+ --rotation: 17deg;
1131
+ width: clamp(130px, 15vw, 230px);
1132
+ color: var(--red);
1133
+ }
1134
+
1135
+ .bloom-center {
1136
+ fill: var(--acid);
1137
+ }
1138
+
1139
+ .piece-fan {
1140
+ --enter-delay: 400ms;
1141
+ --x: 58%;
1142
+ --y: 69%;
1143
+ --rotation: -9deg;
1144
+ width: clamp(160px, 20vw, 320px);
1145
+ color: var(--cream);
1146
+ }
1147
+
1148
+ .piece-line {
1149
+ fill: none;
1150
+ stroke: var(--coral);
1151
+ stroke-width: 6px;
1152
+ }
1153
+
1154
+ .piece-star {
1155
+ --enter-delay: 280ms;
1156
+ --x: 71%;
1157
+ --y: 20%;
1158
+ --rotation: 11deg;
1159
+ width: clamp(90px, 10vw, 160px);
1160
+ color: var(--acid);
1161
+ }
1162
+
1163
+ .piece-fish {
1164
+ --enter-delay: 160ms;
1165
+ --x: 31%;
1166
+ --y: 37%;
1167
+ --rotation: -6deg;
1168
+ width: clamp(190px, 22vw, 350px);
1169
+ color: var(--sky);
1170
+ }
1171
+
1172
+ .fish-eye {
1173
+ fill: var(--deep-sea);
1174
+ }
1175
+
1176
+ .piece-sail {
1177
+ --enter-delay: 220ms;
1178
+ --x: 49%;
1179
+ --y: 30%;
1180
+ --rotation: 4deg;
1181
+ width: clamp(105px, 12vw, 185px);
1182
+ }
1183
+
1184
+ .sail-mast {
1185
+ fill: none;
1186
+ stroke: var(--deep-sea);
1187
+ stroke-width: 7px;
1188
+ }
1189
+
1190
+ .sail-main {
1191
+ fill: var(--cream);
1192
+ }
1193
+
1194
+ .sail-side {
1195
+ fill: var(--acid);
1196
+ }
1197
+
1198
+ .sail-hull {
1199
+ fill: var(--cream);
1200
+ }
1201
+
1202
+ .piece-ribbon {
1203
+ --enter-delay: 340ms;
1204
+ --x: 82%;
1205
+ --y: 77%;
1206
+ --rotation: 25deg;
1207
+ width: clamp(150px, 18vw, 285px);
1208
+ color: var(--red);
1209
+ }
1210
+
1211
+ .hint {
1212
+ position: absolute;
1213
+ z-index: 100;
1214
+ bottom: clamp(18px, 3vw, 38px);
1215
+ margin: 0;
1216
+ color: rgb(255 248 232 / 64%);
1217
+ font-size: 0.62rem;
1218
+ font-weight: 650;
1219
+ letter-spacing: 0.12em;
1220
+ text-transform: uppercase;
1221
+ pointer-events: none;
1222
+ }
1223
+
1224
+ .hint {
1225
+ right: clamp(20px, 3.2vw, 44px);
1226
+ animation: chrome-arrive 500ms ease-out 900ms both;
1227
+ }
1228
+
1229
+ .transform-ui {
1230
+ position: absolute;
1231
+ z-index: 120;
1232
+ display: flex;
1233
+ gap: 6px;
1234
+ align-items: center;
1235
+ pointer-events: none;
1236
+ transform: translate(-18px, -50%);
1237
+ }
1238
+
1239
+ .transform-ui[hidden] {
1240
+ display: none;
1241
+ }
1242
+
1243
+ .transform-ui button {
1244
+ display: grid;
1245
+ place-items: center;
1246
+ width: 36px;
1247
+ height: 36px;
1248
+ padding: 0;
1249
+ border: 1px solid rgb(255 248 232 / 55%);
1250
+ border-radius: 50%;
1251
+ color: var(--cream);
1252
+ background: rgb(4 58 128 / 70%);
1253
+ box-shadow: 0 7px 18px rgb(3 31 74 / 22%);
1254
+ cursor: pointer;
1255
+ pointer-events: auto;
1256
+ touch-action: none;
1257
+ backdrop-filter: blur(8px);
1258
+ }
1259
+
1260
+ .transform-ui button:hover {
1261
+ color: var(--deep-sea);
1262
+ background: var(--cream);
1263
+ }
1264
+
1265
+ .transform-ui .delete-handle {
1266
+ width: 30px;
1267
+ height: 30px;
1268
+ color: #ffd2cc;
1269
+ }
1270
+
1271
+ .transform-ui .delete-handle:hover {
1272
+ color: white;
1273
+ background: #d94737;
1274
+ }
1275
+
1276
+ .transform-ui svg {
1277
+ width: 16px;
1278
+ fill: none;
1279
+ stroke: currentcolor;
1280
+ stroke-linecap: round;
1281
+ stroke-linejoin: round;
1282
+ stroke-width: 1.7;
1283
+ }
1284
+
1285
+ .transform-ui button:focus-visible {
1286
+ outline: 2px solid var(--acid);
1287
+ outline-offset: 2px;
1288
+ }
1289
+
1290
+ .canvas.shuffling .piece {
1291
+ transition-duration: 800ms;
1292
+ }
1293
+
1294
+ @keyframes piece-arrive {
1295
+ 0% {
1296
+ opacity: 0;
1297
+ transform: translateY(48px) rotate(-8deg) scale(0.72);
1298
+ }
1299
+
1300
+ 68% {
1301
+ opacity: 1;
1302
+ transform: translateY(-4px) rotate(1.5deg) scale(1.025);
1303
+ }
1304
+
1305
+ 100% {
1306
+ opacity: 1;
1307
+ transform: translateY(0) rotate(0) scale(1);
1308
+ }
1309
+ }
1310
+
1311
+ @keyframes chrome-arrive {
1312
+ from {
1313
+ opacity: 0;
1314
+ transform: translateY(-7px);
1315
+ }
1316
+
1317
+ to {
1318
+ opacity: 1;
1319
+ transform: translateY(0);
1320
+ }
1321
+ }
1322
+
1323
+ @media (max-width: 700px) {
1324
+ .canvas {
1325
+ min-height: 500px;
1326
+ }
1327
+
1328
+ .piece-cape {
1329
+ --x: 98%;
1330
+ --y: 18%;
1331
+ width: 340px;
1332
+ }
1333
+
1334
+ .piece-sun {
1335
+ --x: 17%;
1336
+ --y: 18%;
1337
+ }
1338
+
1339
+ .piece-fish {
1340
+ --x: 29%;
1341
+ --y: 41%;
1342
+ }
1343
+
1344
+ .piece-sail {
1345
+ --x: 65%;
1346
+ --y: 38%;
1347
+ }
1348
+
1349
+ .piece-fan {
1350
+ --x: 52%;
1351
+ --y: 68%;
1352
+ }
1353
+
1354
+ .piece-leaf {
1355
+ --x: 8%;
1356
+ --y: 83%;
1357
+ }
1358
+
1359
+ .piece-bloom {
1360
+ --x: 29%;
1361
+ --y: 90%;
1362
+ }
1363
+
1364
+ .piece-ribbon {
1365
+ --x: 86%;
1366
+ --y: 84%;
1367
+ }
1368
+ }
1369
+
1370
+ @media (prefers-reduced-motion: reduce) {
1371
+ *,
1372
+ *::before,
1373
+ *::after {
1374
+ animation-duration: 0.01ms !important;
1375
+ animation-delay: 0ms !important;
1376
+ animation-iteration-count: 1 !important;
1377
+ transition-duration: 0.01ms !important;
1378
+ }
1379
+ }
1380
+ `;
1381
+
1382
+ // src/demo-manifest.ts
1383
+ var DEMO_MANIFEST = {
1384
+ schema: 1,
1385
+ name: "Hello Marina",
1386
+ icon: "\u26F5",
1387
+ type: "static"
1388
+ };
1389
+
1390
+ // src/demo.ts
1391
+ var encoder = new TextEncoder();
1392
+ function demoFiles() {
1393
+ return {
1394
+ "index.html": encoder.encode(demo_default),
1395
+ "styles.css": encoder.encode(styles_default),
1396
+ "app.js": encoder.encode(app_js_default),
1397
+ "marina.json": encoder.encode(`${JSON.stringify(DEMO_MANIFEST, null, 2)}
1398
+ `)
1399
+ };
1400
+ }
1401
+ function packDemo() {
1402
+ const files = demoFiles();
1403
+ return {
1404
+ zip: zipSync(files),
1405
+ fileCount: Object.keys(files).length,
1406
+ totalBytes: Object.values(files).reduce((total, file) => total + file.byteLength, 0),
1407
+ skippedSecrets: []
1408
+ };
1409
+ }
1410
+
1411
+ // src/login.ts
1412
+ import { createHash, randomBytes } from "node:crypto";
1413
+ import { spawn } from "node:child_process";
1414
+ import { createServer } from "node:http";
1415
+ import { hostname } from "node:os";
1416
+ var LOGIN_TIMEOUT_MS = 5 * 6e4;
1417
+ var page = (title, message) => `<!doctype html>
1418
+ <html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width">
1419
+ <title>${title}</title><style>
1420
+ body{margin:0;display:grid;place-items:center;min-height:100vh;font:15px/1.5 system-ui,sans-serif;color:#142238;background:#faf9f7}
1421
+ main{text-align:center;padding:32px}h1{font-size:22px;margin:0 0 6px}p{margin:0;color:#667085}
1422
+ </style></head><body><main><h1>${title}</h1><p>${message}</p></main></body></html>`;
1423
+ var closeServer = (server) => new Promise((resolve2) => server.close(() => resolve2()));
1424
+ function openBrowser(url) {
1425
+ const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "explorer.exe" : "xdg-open";
1426
+ const child = spawn(command, [url], { detached: true, stdio: "ignore" });
1427
+ child.on("error", () => void 0);
1428
+ child.unref();
1429
+ }
1430
+ function callbackCode(server, expectedState) {
1431
+ return new Promise((resolve2, reject) => {
1432
+ const timer = setTimeout(() => reject(new Error("browser login timed out")), LOGIN_TIMEOUT_MS);
1433
+ timer.unref();
1434
+ server.on("request", (request2, response) => {
1435
+ const url = new URL(request2.url ?? "/", "http://127.0.0.1");
1436
+ if (url.pathname !== "/callback") {
1437
+ response.writeHead(404).end();
1438
+ return;
1439
+ }
1440
+ response.setHeader("cache-control", "no-store");
1441
+ response.setHeader("content-type", "text/html; charset=utf-8");
1442
+ const state = url.searchParams.get("state");
1443
+ const code = url.searchParams.get("code");
1444
+ const error = url.searchParams.get("error");
1445
+ if (state !== expectedState) {
1446
+ response.writeHead(400);
1447
+ response.end(page("Login failed", "The callback did not match this CLI session."));
1448
+ return;
1449
+ }
1450
+ clearTimeout(timer);
1451
+ if (error || !code) {
1452
+ response.writeHead(400);
1453
+ response.end(
1454
+ page("Login cancelled", "You can close this window and return to the terminal."),
1455
+ () => reject(
1456
+ new Error(
1457
+ error === "access_denied" ? "browser login cancelled" : "browser login failed"
1458
+ )
1459
+ )
1460
+ );
1461
+ return;
1462
+ }
1463
+ response.writeHead(200);
1464
+ response.end(
1465
+ page("Marina CLI is signed in", "You can close this window and return to the terminal."),
1466
+ () => resolve2(code)
1467
+ );
1468
+ });
1469
+ });
1470
+ }
1471
+ async function loginWithBrowser(onOpen) {
1472
+ const verifier = randomBytes(32).toString("base64url");
1473
+ const challenge = createHash("sha256").update(verifier).digest("base64url");
1474
+ const state = randomBytes(32).toString("base64url");
1475
+ const server = createServer();
1476
+ await new Promise((resolve2, reject) => {
1477
+ server.once("error", reject);
1478
+ server.listen(0, "127.0.0.1", resolve2);
1479
+ });
1480
+ try {
1481
+ const address = server.address();
1482
+ if (!address || typeof address === "string") throw new Error("could not start login callback");
1483
+ const callback = `http://127.0.0.1:${String(address.port)}/callback`;
1484
+ const authorize = new URL("/cli/login", dashboardUrl());
1485
+ authorize.searchParams.set("callback_uri", callback);
1486
+ authorize.searchParams.set("code_challenge", challenge);
1487
+ authorize.searchParams.set("state", state);
1488
+ authorize.searchParams.set(
1489
+ "device_name",
1490
+ `Marina CLI on ${hostname().split(".")[0]}`.slice(0, 80)
1491
+ );
1492
+ const codePromise = callbackCode(server, state);
1493
+ onOpen(authorize.toString());
1494
+ openBrowser(authorize.toString());
1495
+ const code = await codePromise;
1496
+ return await exchangeCliLogin(code, verifier);
1497
+ } finally {
1498
+ await closeServer(server);
1499
+ }
1500
+ }
1501
+
1502
+ // src/output.ts
1503
+ var json = false;
1504
+ var setJsonMode = (on) => {
1505
+ json = on;
1506
+ };
1507
+ var isJsonMode = () => json;
1508
+ var bold = (s) => `\x1B[1m${s}\x1B[22m`;
1509
+ var dim = (s) => `\x1B[2m${s}\x1B[22m`;
1510
+ var red = (s) => `\x1B[31m${s}\x1B[39m`;
1511
+ var green = (s) => `\x1B[32m${s}\x1B[39m`;
1512
+ function say(line = "") {
1513
+ if (json) console.error(line);
1514
+ else console.log(line);
1515
+ }
1516
+ function note(line) {
1517
+ console.error(line);
1518
+ }
1519
+ function result(payload) {
1520
+ if (json) console.log(JSON.stringify({ schema_version: 1, ok: true, ...payload }, null, 2));
1521
+ }
1522
+ function failure(code, message, extra = {}) {
1523
+ if (json)
1524
+ console.log(
1525
+ JSON.stringify({ schema_version: 1, ok: false, error: { code, message, ...extra } }, null, 2)
1526
+ );
1527
+ else {
1528
+ console.error(`${red(code)} ${message}`);
1529
+ }
1530
+ }
1531
+
1532
+ // src/pack.ts
1533
+ import { lstatSync, readFileSync as readFileSync2, readdirSync } from "node:fs";
1534
+ import { join as join2, relative } from "node:path";
1535
+ var EXCLUDED_DIRS = /* @__PURE__ */ new Set([
1536
+ ".git",
1537
+ ".hg",
1538
+ ".svn",
1539
+ "node_modules",
1540
+ "bower_components",
1541
+ ".venv",
1542
+ "venv",
1543
+ "__pycache__",
1544
+ ".pytest_cache",
1545
+ ".cache",
1546
+ ".turbo",
1547
+ ".next",
1548
+ ".nuxt",
1549
+ ".svelte-kit",
1550
+ ".parcel-cache",
1551
+ "coverage",
1552
+ ".idea",
1553
+ ".vscode",
1554
+ ".marina",
1555
+ "__MACOSX"
1556
+ ]);
1557
+ var EXCLUDED_FILES = /* @__PURE__ */ new Set([".DS_Store"]);
1558
+ var MAX_TOTAL = 100 * 1024 * 1024;
1559
+ var SECRET_FILE = /^(\.env(\.[^/]*)?|.*\.pem|.*\.p12|.*\.pfx|id_rsa|id_dsa|id_ecdsa|id_ed25519|credentials\.json|service-account\.json)$/i;
1560
+ function pack(dir) {
1561
+ const files = {};
1562
+ const skippedSecrets = [];
1563
+ let totalBytes = 0;
1564
+ const walk = (current) => {
1565
+ for (const entry of readdirSync(current)) {
1566
+ const full = join2(current, entry);
1567
+ const stat = lstatSync(full);
1568
+ if (stat.isSymbolicLink()) continue;
1569
+ if (stat.isDirectory()) {
1570
+ if (!EXCLUDED_DIRS.has(entry)) walk(full);
1571
+ continue;
1572
+ }
1573
+ if (EXCLUDED_FILES.has(entry)) continue;
1574
+ if (SECRET_FILE.test(entry)) {
1575
+ skippedSecrets.push(relative(dir, full));
1576
+ continue;
1577
+ }
1578
+ totalBytes += stat.size;
1579
+ if (totalBytes > MAX_TOTAL) {
1580
+ throw new Error(
1581
+ `this directory is over ${String(MAX_TOTAL / 1024 / 1024)} MB unpacked \u2014 trim it before deploying`
1582
+ );
1583
+ }
1584
+ files[relative(dir, full).replaceAll("\\", "/")] = readFileSync2(full);
1585
+ }
1586
+ };
1587
+ walk(dir);
1588
+ const fileCount = Object.keys(files).length;
1589
+ if (fileCount === 0) throw new Error("nothing to deploy \u2014 this directory is empty");
1590
+ return { zip: zipSync(files), fileCount, totalBytes, skippedSecrets };
1591
+ }
1592
+
1593
+ // src/manifest.ts
1594
+ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
1595
+ import { basename, join as join3 } from "node:path";
1596
+ function readManifest(dir) {
1597
+ const path = join3(dir, "marina.json");
1598
+ if (!existsSync2(path)) return null;
1599
+ let value;
1600
+ try {
1601
+ value = JSON.parse(readFileSync3(path, "utf8"));
1602
+ } catch {
1603
+ throw new Error("marina.json is not valid JSON");
1604
+ }
1605
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1606
+ throw new Error("marina.json must contain an object");
1607
+ }
1608
+ const manifest = value;
1609
+ if (manifest.schema !== void 0 && manifest.schema !== 1) {
1610
+ throw new Error('marina.json "schema" must be 1');
1611
+ }
1612
+ if (manifest.name !== void 0 && (typeof manifest.name !== "string" || !manifest.name.trim() || manifest.name.length > 80)) {
1613
+ throw new Error('marina.json "name" must be between 1 and 80 characters');
1614
+ }
1615
+ if (manifest.icon !== void 0 && (typeof manifest.icon !== "string" || !manifest.icon || manifest.icon.length > 8)) {
1616
+ throw new Error('marina.json "icon" must be an emoji');
1617
+ }
1618
+ if (manifest.type !== void 0 && manifest.type !== "static" && manifest.type !== "dynamic") {
1619
+ throw new Error('marina.json "type" must be "static" or "dynamic"');
1620
+ }
1621
+ return manifest;
1622
+ }
1623
+ function resolveAppName(dir, flag, manifest = readManifest(dir), linkedName) {
1624
+ if (flag?.trim()) return flag.trim();
1625
+ if (manifest?.name?.trim()) return manifest.name.trim();
1626
+ try {
1627
+ const pkg = JSON.parse(readFileSync3(join3(dir, "package.json"), "utf8"));
1628
+ if (typeof pkg.name === "string" && pkg.name.trim()) {
1629
+ return pkg.name.trim().replace(/^@[^/]+\//, "");
1630
+ }
1631
+ } catch {
1632
+ }
1633
+ if (linkedName?.trim()) return linkedName.trim();
1634
+ return basename(dir);
1635
+ }
1636
+
1637
+ // src/skills.ts
1638
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
1639
+ import { homedir as homedir2 } from "node:os";
1640
+ import { join as join4 } from "node:path";
1641
+
1642
+ // ../../skills/marina-deploy/SKILL.md
1643
+ 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';
1644
+
1645
+ // ../../skills/marina-deploy/agents/openai.yaml
1646
+ 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';
1647
+
1648
+ // src/skills.ts
1649
+ var homes = () => ({
1650
+ codex: process.env.CODEX_HOME ?? join4(homedir2(), ".codex"),
1651
+ claude: process.env.CLAUDE_CONFIG_DIR ?? join4(homedir2(), ".claude")
1652
+ });
1653
+ function targets(agent) {
1654
+ const agentHomes = homes();
1655
+ if (agent && agent !== "all" && agent !== "codex" && agent !== "claude") {
1656
+ throw new Error("--agent must be codex, claude, or all");
1657
+ }
1658
+ if (agent === "all") {
1659
+ return Object.entries(agentHomes).map(([name, home]) => ({
1660
+ agent: name,
1661
+ home
1662
+ }));
1663
+ }
1664
+ if (agent === "codex" || agent === "claude") return [{ agent, home: agentHomes[agent] }];
1665
+ return Object.entries(agentHomes).filter(([, home]) => existsSync3(home)).map(([name, home]) => ({ agent: name, home }));
1666
+ }
1667
+ function install(target, update) {
1668
+ const directory = join4(target.home, "skills", "marina-deploy");
1669
+ const skillPath = join4(directory, "SKILL.md");
1670
+ const existing = (() => {
1671
+ try {
1672
+ return readFileSync4(skillPath, "utf8");
1673
+ } catch {
1674
+ return null;
1675
+ }
1676
+ })();
1677
+ if (existing === SKILL_default)
1678
+ return { agent: target.agent, path: directory, status: "current" };
1679
+ if (existing !== null && !update) return null;
1680
+ mkdirSync2(directory, { recursive: true });
1681
+ writeFileSync2(skillPath, SKILL_default);
1682
+ if (target.agent === "codex") {
1683
+ const agentsDirectory = join4(directory, "agents");
1684
+ mkdirSync2(agentsDirectory, { recursive: true });
1685
+ writeFileSync2(join4(agentsDirectory, "openai.yaml"), openai_default);
1686
+ }
1687
+ return {
1688
+ agent: target.agent,
1689
+ path: directory,
1690
+ status: existing === null ? "installed" : "updated"
1691
+ };
1692
+ }
1693
+ function autoInstallSkills() {
1694
+ const detectedTargets = targets();
1695
+ const installed = [];
1696
+ const updatesAvailable = [];
1697
+ for (const target of detectedTargets) {
1698
+ const result2 = install(target, false);
1699
+ if (result2) installed.push(result2);
1700
+ else updatesAvailable.push(target.agent);
1701
+ }
1702
+ return {
1703
+ detected: detectedTargets.map((target) => target.agent),
1704
+ installed,
1705
+ updatesAvailable
1706
+ };
1707
+ }
1708
+ function installSkills(agent) {
1709
+ const selected = targets(agent);
1710
+ if (selected.length === 0) {
1711
+ throw new Error("no Codex or Claude installation detected; pass --agent codex, claude, or all");
1712
+ }
1713
+ return selected.map((target) => install(target, true));
1714
+ }
1715
+
1716
+ // package.json
1717
+ var package_default = {
1718
+ name: "@marina-cloud/cli",
1719
+ version: "0.0.0",
1720
+ description: "Command-line client for Marina Cloud",
1721
+ homepage: "https://github.com/marina-hq/marina#readme",
1722
+ bugs: {
1723
+ url: "https://github.com/marina-hq/marina/issues"
1724
+ },
1725
+ license: "Apache-2.0",
1726
+ repository: {
1727
+ type: "git",
1728
+ url: "git+https://github.com/marina-hq/marina.git",
1729
+ directory: "packages/cli"
1730
+ },
1731
+ bin: {
1732
+ marina: "dist/marina.mjs"
1733
+ },
1734
+ files: [
1735
+ "dist"
1736
+ ],
1737
+ type: "module",
1738
+ publishConfig: {
1739
+ access: "public",
1740
+ provenance: false
1741
+ },
1742
+ scripts: {
1743
+ 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',
1744
+ typecheck: "tsc --noEmit",
1745
+ test: "pnpm build && node --experimental-strip-types --test src/*.test.ts",
1746
+ prepack: "pnpm build"
1747
+ },
1748
+ devDependencies: {
1749
+ "@types/node": "^26.2.0",
1750
+ esbuild: "^0.25.0",
1751
+ fflate: "^0.8.2",
1752
+ typescript: "^5.9.0"
1753
+ },
1754
+ engines: {
1755
+ node: ">=22"
1756
+ }
1757
+ };
1758
+
1759
+ // src/update.ts
1760
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
1761
+ var LATEST_URL = "https://registry.npmjs.org/@marina-cloud%2Fcli/latest";
1762
+ function isNewerVersion(candidate, current) {
1763
+ const parse = (version) => version.replace(/^v/, "").split("-", 1)[0].split(".").map((part) => Number.parseInt(part, 10));
1764
+ const left = parse(candidate);
1765
+ const right = parse(current);
1766
+ if (left.some(Number.isNaN) || right.some(Number.isNaN)) return false;
1767
+ for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
1768
+ const difference = (left[index] ?? 0) - (right[index] ?? 0);
1769
+ if (difference !== 0) return difference > 0;
1770
+ }
1771
+ return false;
1772
+ }
1773
+ async function availableUpdate() {
1774
+ if (process.env.MARINA_DISABLE_UPDATE_CHECK) return null;
1775
+ const current = package_default.version;
1776
+ const profile = readProfile();
1777
+ const checkedAt = profile.update ? Date.parse(profile.update.checked_at) : Number.NaN;
1778
+ let latest = profile.update?.latest;
1779
+ if (!latest || !Number.isFinite(checkedAt) || Date.now() - checkedAt >= CHECK_INTERVAL_MS) {
1780
+ try {
1781
+ const response = await fetch(LATEST_URL, {
1782
+ headers: { accept: "application/json" },
1783
+ signal: AbortSignal.timeout(1500)
1784
+ });
1785
+ if (response.ok) {
1786
+ const body = await response.json();
1787
+ if (body.version) {
1788
+ latest = body.version;
1789
+ writeProfile({
1790
+ ...readProfile(),
1791
+ update: { checked_at: (/* @__PURE__ */ new Date()).toISOString(), latest }
1792
+ });
1793
+ }
1794
+ }
1795
+ } catch {
1796
+ }
1797
+ }
1798
+ return latest && isNewerVersion(latest, current) ? {
1799
+ current,
1800
+ latest,
1801
+ command: "npm install -g @marina-cloud/cli@latest"
1802
+ } : null;
1803
+ }
1804
+
1805
+ // src/index.ts
1806
+ var HELP = `${bold("marina")} \u2014 deploy internal apps
1807
+
1808
+ marina setup sign in with Clerk in your browser
1809
+ --token mar_\u2026 save an existing API key (headless fallback)
1810
+ marina logout remove the saved credential
1811
+ marina profile show where this CLI is signed in
1812
+ marina skills install install or update the Marina deployment skill
1813
+ --agent <agent> codex, claude, or all (default: detected agents)
1814
+ marina deploy [dir] zip the directory and deploy it
1815
+ marina deploy demo deploy the bundled Hello Marina demo
1816
+ --name <name> override marina.json, package.json, or directory name
1817
+ --app <slug> deploy into an existing app
1818
+ marina status [--app <slug>] what is live, and how the last deploy went
1819
+ marina deploys [--app <slug>] recent deploy attempts, including refusals
1820
+ marina versions [--app <slug>] version history
1821
+ marina rollback [--to <hash>] publish a previous version again
1822
+ marina secrets list env vars and secrets for the app
1823
+ marina secrets set K=V set one (--plain for a non-secret var)
1824
+ marina secrets rm K remove one
1825
+ marina list apps in your workspace
1826
+ marina open open this project's app
1827
+
1828
+ ${dim("--json machine-readable result on stdout, progress on stderr")}
1829
+ ${dim(`API: ${apiUrl()} (override with MARINA_API)`)}`;
1830
+ var ANSI_ESCAPE = new RegExp(`${String.fromCodePoint(27)}\\[[0-9;]*m`, "g");
1831
+ var shouldCheckForUpdates = false;
1832
+ function targetApp(flag) {
1833
+ const linked = readLink(resolve("."))?.app;
1834
+ const app = flag ?? linked;
1835
+ if (!app) {
1836
+ failure("no_app", "no app here \u2014 pass --app <slug>, or deploy from a linked project");
1837
+ process.exit(1);
1838
+ }
1839
+ return app;
1840
+ }
1841
+ var shortDigest = (digest) => digest?.slice(7, 14) ?? "\u2014";
1842
+ async function deploy(dirArg, flags) {
1843
+ let dir = null;
1844
+ let link = null;
1845
+ let target;
1846
+ let name;
1847
+ let packed;
1848
+ if (dirArg === "demo") {
1849
+ if (flags.app) {
1850
+ failure("invalid_input", "the demo creates a new app; do not pass --app");
1851
+ process.exit(1);
1852
+ }
1853
+ packed = packDemo();
1854
+ name = flags.name?.trim() || DEMO_MANIFEST.name;
1855
+ } else {
1856
+ dir = resolve(dirArg ?? ".");
1857
+ link = readLink(dir);
1858
+ target = flags.app ?? link?.app;
1859
+ const manifest = readManifest(dir);
1860
+ name = resolveAppName(dir, flags.name, manifest, link?.name);
1861
+ packed = pack(dir);
1862
+ }
1863
+ for (const secret of packed.skippedSecrets) say(dim(`skipped ${secret} \u2014 secrets stay local`));
1864
+ say(
1865
+ `uploading ${bold(name)} ${dim(`(${String(packed.fileCount)} files, ${String(Math.round(packed.totalBytes / 1024))} KB)`)}`
1866
+ );
1867
+ let started;
1868
+ try {
1869
+ started = await startDeploy(packed.zip, name, target);
1870
+ } catch (error) {
1871
+ if (error instanceof ApiError && error.code === "not_found" && link) {
1872
+ failure(
1873
+ "link_stale",
1874
+ `this project is linked to "${link.app}", which no longer exists \u2014 delete .marina/project.json to deploy it as a new app`
1875
+ );
1876
+ process.exit(1);
1877
+ }
1878
+ throw error;
1879
+ }
1880
+ const deployed = await pollDeploy(started.id);
1881
+ if (deployed.status === "refused" && deployed.refusal) {
1882
+ say(`${red("refused")} ${deployed.refusal.message}`);
1883
+ if (deployed.refusal.action) say(` ${deployed.refusal.action}`);
1884
+ if (deployed.build_log) {
1885
+ say(dim("---- build log (tail) ----"));
1886
+ say(dim(deployed.build_log.split("\n").slice(-20).join("\n")));
1887
+ }
1888
+ failure("refused", deployed.refusal.message, {
1889
+ refusal: deployed.refusal,
1890
+ build_log: deployed.build_log
1891
+ });
1892
+ process.exit(3);
1893
+ }
1894
+ if (deployed.status === "failed") {
1895
+ failure("failed", deployed.error ?? "unknown error");
1896
+ process.exit(1);
1897
+ }
1898
+ const slug = deployed.app_slug ?? "";
1899
+ if (dir && (!link || link.app !== slug || link.name !== name)) {
1900
+ writeLink(dir, { app: slug, name });
1901
+ }
1902
+ const published = deployed.version_state === "published";
1903
+ say(
1904
+ `${green("ok")} ${bold(name)} v${String(deployed.version_number ?? "?")} ${published ? green("live") : `${deployed.version_state ?? ""} \u2014 awaiting review`}`
1905
+ );
1906
+ const prep = deployed.preparation;
1907
+ if (prep && prep.transformation !== "none") {
1908
+ say(` ${bold("Marina prepared this project:")} ${prep.summary}`);
1909
+ for (const change of prep.changes)
1910
+ say(` ${dim("changed")} ${change.path} \u2014 ${change.summary}`);
1911
+ for (const limitation of prep.limitations) say(` ${red("note")} ${limitation}`);
1912
+ }
1913
+ if (published) {
1914
+ if (deployed.url) say(` ${deployed.url}`);
1915
+ } else {
1916
+ if (deployed.version_url) say(` try this version: ${deployed.version_url}`);
1917
+ say(dim(" a publisher can approve it from the Marina inbox"));
1918
+ }
1919
+ result({
1920
+ command: "deploy",
1921
+ status: "succeeded",
1922
+ app: slug,
1923
+ version: deployed.version_number,
1924
+ state: deployed.version_state,
1925
+ url: deployed.url,
1926
+ version_url: deployed.version_url,
1927
+ preparation: deployed.preparation,
1928
+ live: published,
1929
+ demo: dirArg === "demo"
1930
+ });
1931
+ }
1932
+ async function status(appFlag) {
1933
+ const app = targetApp(appFlag);
1934
+ const [detail, deploys2] = await Promise.all([getApp(app), listDeploys(app, 1)]);
1935
+ const last = deploys2[0];
1936
+ say(`${bold(detail.app.name)} ${dim(`(${detail.app.slug})`)}`);
1937
+ say(` ${detail.app.current_version_id ? green(detail.app.status) : dim("nothing published")}`);
1938
+ if (detail.app.current_version_id) say(` ${detail.url}`);
1939
+ if (detail.app.archived_at) say(` ${red("archived")} \u2014 it isn't serving`);
1940
+ if (last) {
1941
+ const outcome = last.status === "succeeded" ? green("succeeded") : last.status === "queued" || last.status === "building" ? last.status : red(last.status);
1942
+ say(` last deploy: ${outcome}${last.refusal ? ` \u2014 ${last.refusal.message}` : ""}`);
1943
+ }
1944
+ result({
1945
+ command: "status",
1946
+ app: detail.app.slug,
1947
+ name: detail.app.name,
1948
+ status: detail.app.status,
1949
+ live: detail.app.current_version_id !== null,
1950
+ archived: detail.app.archived_at !== null,
1951
+ url: detail.url,
1952
+ last_deploy: last ?? null
1953
+ });
1954
+ }
1955
+ async function deploys(appFlag) {
1956
+ const app = targetApp(appFlag);
1957
+ const rows = await listDeploys(app);
1958
+ if (rows.length === 0) say("no deploys yet");
1959
+ for (const row of rows) {
1960
+ const mark = row.status === "succeeded" ? green("ok") : red(row.status);
1961
+ const why = row.refusal ? ` \u2014 ${row.refusal.message}` : row.error ? ` \u2014 ${row.error}` : "";
1962
+ say(`${mark} ${dim(row.created_at)} ${dim(`via ${row.source}`)}${why}`);
1963
+ }
1964
+ result({ command: "deploys", app, deploys: rows });
1965
+ }
1966
+ async function versions(appFlag) {
1967
+ const app = targetApp(appFlag);
1968
+ const [rows, detail] = await Promise.all([listVersions(app), getApp(app)]);
1969
+ for (const version of rows) {
1970
+ const current = version.id === detail.app.current_version_id;
1971
+ say(
1972
+ `${current ? green("current") : dim(shortDigest(version.artifact_digest))} v${String(version.number)} ${version.title} ${dim(version.state)}`
1973
+ );
1974
+ }
1975
+ result({
1976
+ command: "versions",
1977
+ app,
1978
+ current_version_id: detail.app.current_version_id,
1979
+ versions: rows
1980
+ });
1981
+ }
1982
+ async function rollback(appFlag, to) {
1983
+ const app = targetApp(appFlag);
1984
+ const [rows, detail] = await Promise.all([listVersions(app), getApp(app)]);
1985
+ const published = rows.filter((v) => v.state === "published");
1986
+ const target = to ? published.find((v) => shortDigest(v.artifact_digest) === to || v.artifact_digest === to) : published.find((v) => v.id !== detail.app.current_version_id);
1987
+ if (!target) {
1988
+ failure(
1989
+ "not_found",
1990
+ to ? `no published version matching ${to}` : "no earlier published version to roll back to"
1991
+ );
1992
+ process.exit(1);
1993
+ }
1994
+ const shipped = await restoreVersion(target.id);
1995
+ say(
1996
+ `${green("ok")} rolled back to ${shortDigest(target.artifact_digest)} \u2014 shipped as v${String(shipped.number)}`
1997
+ );
1998
+ say(` ${detail.url}`);
1999
+ result({
2000
+ command: "rollback",
2001
+ app,
2002
+ restored_from: target.artifact_digest,
2003
+ version: shipped.number,
2004
+ url: detail.url
2005
+ });
2006
+ }
2007
+ async function secrets(appFlag, argv, plain) {
2008
+ const app = targetApp(appFlag);
2009
+ const [action, argument] = argv;
2010
+ if (action === "set") {
2011
+ const eq = argument?.indexOf("=") ?? -1;
2012
+ if (!argument || eq < 1) {
2013
+ failure("invalid_input", "usage: marina secrets set KEY=value");
2014
+ process.exit(1);
2015
+ }
2016
+ const key = argument.slice(0, eq);
2017
+ await setEnv(app, key, argument.slice(eq + 1), !plain);
2018
+ say(`${green("ok")} set ${bold(key)}${plain ? "" : dim(" (secret)")}`);
2019
+ result({ command: "secrets.set", app, key, kind: plain ? "plain" : "secret" });
2020
+ return;
2021
+ }
2022
+ if (action === "rm") {
2023
+ if (!argument) {
2024
+ failure("invalid_input", "usage: marina secrets rm KEY");
2025
+ process.exit(1);
2026
+ }
2027
+ await deleteEnv(app, argument);
2028
+ say(`${green("ok")} removed ${bold(argument)}`);
2029
+ result({ command: "secrets.rm", app, key: argument, removed: true });
2030
+ return;
2031
+ }
2032
+ const rows = await listEnv(app);
2033
+ if (rows.length === 0) say("nothing set");
2034
+ for (const row of rows) {
2035
+ say(
2036
+ `${bold(row.key)} ${row.kind === "secret" ? dim("secret \u2014 write-only") : row.value ?? ""}`
2037
+ );
2038
+ }
2039
+ result({ command: "secrets.list", app, env: rows });
2040
+ }
2041
+ async function main() {
2042
+ setJsonMode(process.argv.slice(2).includes("--json"));
2043
+ const { positionals, values } = parseArgs({
2044
+ allowPositionals: true,
2045
+ options: {
2046
+ token: { type: "string" },
2047
+ name: { type: "string" },
2048
+ app: { type: "string" },
2049
+ to: { type: "string" },
2050
+ plain: { type: "boolean" },
2051
+ agent: { type: "string" },
2052
+ json: { type: "boolean" },
2053
+ help: { type: "boolean", short: "h" }
2054
+ }
2055
+ });
2056
+ setJsonMode(values.json === true);
2057
+ const command = positionals[0];
2058
+ if (values.help || !command) {
2059
+ say(HELP);
2060
+ result({ command: "help", usage: HELP.replaceAll(ANSI_ESCAPE, "") });
2061
+ return;
2062
+ }
2063
+ shouldCheckForUpdates = true;
2064
+ let skillBootstrap = null;
2065
+ if (command !== "skills") {
2066
+ try {
2067
+ skillBootstrap = autoInstallSkills();
2068
+ for (const installed of skillBootstrap.installed) {
2069
+ if (installed.status === "installed") {
2070
+ say(dim(`installed Marina skill for ${installed.agent}`));
2071
+ }
2072
+ }
2073
+ if (skillBootstrap.updatesAvailable.length > 0) {
2074
+ const agent = skillBootstrap.updatesAvailable.length > 1 ? "all" : skillBootstrap.updatesAvailable[0];
2075
+ say(
2076
+ dim(
2077
+ `Marina skill update available \u2014 run marina skills install --agent ${agent ?? "all"}`
2078
+ )
2079
+ );
2080
+ }
2081
+ } catch {
2082
+ say(dim("could not install the Marina agent skill; run marina skills install later"));
2083
+ }
2084
+ }
2085
+ switch (command) {
2086
+ case "setup":
2087
+ case "login": {
2088
+ const token = values.token ?? process.env.MARINA_TOKEN;
2089
+ if (token) {
2090
+ saveToken(token);
2091
+ } else {
2092
+ const signedIn = await loginWithBrowser((url) => {
2093
+ say("Opening your browser to sign in\u2026");
2094
+ say(dim(url));
2095
+ });
2096
+ saveToken(signedIn.token);
2097
+ }
2098
+ say(`${green("ok")} signed in ${dim(`(${apiUrl()})`)}`);
2099
+ if (skillBootstrap?.detected.length === 0) {
2100
+ say(
2101
+ dim("no Codex or Claude installation detected \u2014 use marina skills install --agent later")
2102
+ );
2103
+ }
2104
+ result({
2105
+ command: "setup",
2106
+ signed_in: true,
2107
+ api: apiUrl(),
2108
+ profile: profilePath(),
2109
+ skills: skillBootstrap?.installed ?? []
2110
+ });
2111
+ return;
2112
+ }
2113
+ case "logout": {
2114
+ clearToken();
2115
+ const environmentToken = Boolean(process.env.MARINA_TOKEN);
2116
+ say(
2117
+ environmentToken ? `${green("ok")} profile credential removed; MARINA_TOKEN is still set` : `${green("ok")} signed out`
2118
+ );
2119
+ result({
2120
+ command: "logout",
2121
+ signed_in: environmentToken,
2122
+ profile: profilePath(),
2123
+ environment_token: environmentToken
2124
+ });
2125
+ return;
2126
+ }
2127
+ case "profile": {
2128
+ const profile = readProfile();
2129
+ const source = process.env.MARINA_TOKEN ? "environment" : profile.token ? "profile" : null;
2130
+ say(`${getToken() ? green("signed in") : dim("signed out")} ${dim(`(${apiUrl()})`)}`);
2131
+ say(dim(profilePath()));
2132
+ result({
2133
+ command: "profile",
2134
+ signed_in: getToken() !== null,
2135
+ source,
2136
+ api: apiUrl(),
2137
+ profile: profilePath()
2138
+ });
2139
+ return;
2140
+ }
2141
+ case "skills": {
2142
+ if (positionals[1] !== "install") {
2143
+ failure("invalid_input", "usage: marina skills install [--agent codex|claude|all]");
2144
+ process.exit(1);
2145
+ }
2146
+ const installed = installSkills(values.agent);
2147
+ for (const skill of installed) {
2148
+ say(`${green("ok")} ${skill.status} Marina skill for ${skill.agent} ${dim(skill.path)}`);
2149
+ }
2150
+ result({ command: "skills.install", skills: installed });
2151
+ return;
2152
+ }
2153
+ case "deploy":
2154
+ await deploy(positionals[1], values);
2155
+ return;
2156
+ case "status":
2157
+ await status(values.app);
2158
+ return;
2159
+ case "deploys":
2160
+ case "deployments":
2161
+ await deploys(values.app);
2162
+ return;
2163
+ case "versions":
2164
+ await versions(values.app);
2165
+ return;
2166
+ case "rollback":
2167
+ await rollback(values.app, values.to);
2168
+ return;
2169
+ case "secrets":
2170
+ await secrets(values.app, positionals.slice(1), values.plain === true);
2171
+ return;
2172
+ case "list": {
2173
+ const apps = await listApps();
2174
+ if (apps.length === 0) say("no apps yet \u2014 `marina deploy` one");
2175
+ for (const app of apps) {
2176
+ say(
2177
+ `${app.emoji} ${bold(app.name)} ${dim(`(${app.slug})`)} ${app.status}${app.version === null ? "" : dim(` v${String(app.version)}`)} ${dim(app.url)}`
2178
+ );
2179
+ }
2180
+ result({ command: "list", apps });
2181
+ return;
2182
+ }
2183
+ case "open": {
2184
+ const app = targetApp(values.app);
2185
+ const url = await getAppUrl(app);
2186
+ if (!isJsonMode()) {
2187
+ const { spawn: spawn2 } = await import("node:child_process");
2188
+ spawn2(process.platform === "darwin" ? "open" : "xdg-open", [url], { detached: true });
2189
+ }
2190
+ say(url);
2191
+ result({ command: "open", app, url });
2192
+ return;
2193
+ }
2194
+ default:
2195
+ failure("unknown_command", `unknown command: ${command}`);
2196
+ if (!isJsonMode()) say(HELP);
2197
+ process.exit(1);
2198
+ }
2199
+ }
2200
+ async function run() {
2201
+ await main();
2202
+ if (!shouldCheckForUpdates) return;
2203
+ const update = await availableUpdate();
2204
+ if (update) {
2205
+ const message = `Marina CLI ${update.latest} is available (current ${update.current}) \u2014 ${update.command}`;
2206
+ note(isJsonMode() ? message : dim(message));
2207
+ }
2208
+ }
2209
+ run().catch((error) => {
2210
+ if (error instanceof ApiError) {
2211
+ failure(error.code, error.message);
2212
+ process.exit(error.status === 401 ? 2 : error.code === "invalid_input" ? 3 : 1);
2213
+ }
2214
+ failure("error", error.message);
2215
+ process.exit(1);
2216
+ });