@m-kopa/launchpad-cli 0.23.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 (191) hide show
  1. package/CHANGELOG.md +854 -0
  2. package/README.md +109 -0
  3. package/dist/auth/browser.d.ts +18 -0
  4. package/dist/auth/browser.d.ts.map +1 -0
  5. package/dist/auth/callback-server.d.ts +24 -0
  6. package/dist/auth/callback-server.d.ts.map +1 -0
  7. package/dist/auth/discovery.d.ts +25 -0
  8. package/dist/auth/discovery.d.ts.map +1 -0
  9. package/dist/auth/flow.d.ts +39 -0
  10. package/dist/auth/flow.d.ts.map +1 -0
  11. package/dist/auth/jwt.d.ts +27 -0
  12. package/dist/auth/jwt.d.ts.map +1 -0
  13. package/dist/auth/pkce.d.ts +26 -0
  14. package/dist/auth/pkce.d.ts.map +1 -0
  15. package/dist/auth/registration.d.ts +8 -0
  16. package/dist/auth/registration.d.ts.map +1 -0
  17. package/dist/auth/session.d.ts +54 -0
  18. package/dist/auth/session.d.ts.map +1 -0
  19. package/dist/auth/token.d.ts +37 -0
  20. package/dist/auth/token.d.ts.map +1 -0
  21. package/dist/bundle/cron-bundle.d.ts +77 -0
  22. package/dist/bundle/cron-bundle.d.ts.map +1 -0
  23. package/dist/bundle/cwd-walker.d.ts +43 -0
  24. package/dist/bundle/cwd-walker.d.ts.map +1 -0
  25. package/dist/bundle/orchestrate.d.ts +51 -0
  26. package/dist/bundle/orchestrate.d.ts.map +1 -0
  27. package/dist/bundle/upload.d.ts +66 -0
  28. package/dist/bundle/upload.d.ts.map +1 -0
  29. package/dist/cli.d.ts +3 -0
  30. package/dist/cli.d.ts.map +1 -0
  31. package/dist/cli.js +9757 -0
  32. package/dist/clone/git-init.d.ts +18 -0
  33. package/dist/clone/git-init.d.ts.map +1 -0
  34. package/dist/clone/tar-extract.d.ts +59 -0
  35. package/dist/clone/tar-extract.d.ts.map +1 -0
  36. package/dist/commands/apps.d.ts +14 -0
  37. package/dist/commands/apps.d.ts.map +1 -0
  38. package/dist/commands/channel-auth.d.ts +31 -0
  39. package/dist/commands/channel-auth.d.ts.map +1 -0
  40. package/dist/commands/clone.d.ts +3 -0
  41. package/dist/commands/clone.d.ts.map +1 -0
  42. package/dist/commands/create.d.ts +27 -0
  43. package/dist/commands/create.d.ts.map +1 -0
  44. package/dist/commands/deploy-flags.d.ts +75 -0
  45. package/dist/commands/deploy-flags.d.ts.map +1 -0
  46. package/dist/commands/deploy-modes.d.ts +59 -0
  47. package/dist/commands/deploy-modes.d.ts.map +1 -0
  48. package/dist/commands/deploy.d.ts +29 -0
  49. package/dist/commands/deploy.d.ts.map +1 -0
  50. package/dist/commands/destroy.d.ts +14 -0
  51. package/dist/commands/destroy.d.ts.map +1 -0
  52. package/dist/commands/envvars.d.ts +28 -0
  53. package/dist/commands/envvars.d.ts.map +1 -0
  54. package/dist/commands/generate.d.ts +3 -0
  55. package/dist/commands/generate.d.ts.map +1 -0
  56. package/dist/commands/groups-whoami.d.ts +3 -0
  57. package/dist/commands/groups-whoami.d.ts.map +1 -0
  58. package/dist/commands/groups.d.ts +3 -0
  59. package/dist/commands/groups.d.ts.map +1 -0
  60. package/dist/commands/init.d.ts +44 -0
  61. package/dist/commands/init.d.ts.map +1 -0
  62. package/dist/commands/login.d.ts +3 -0
  63. package/dist/commands/login.d.ts.map +1 -0
  64. package/dist/commands/logout.d.ts +3 -0
  65. package/dist/commands/logout.d.ts.map +1 -0
  66. package/dist/commands/logs.d.ts +16 -0
  67. package/dist/commands/logs.d.ts.map +1 -0
  68. package/dist/commands/merge.d.ts +29 -0
  69. package/dist/commands/merge.d.ts.map +1 -0
  70. package/dist/commands/plan.d.ts +3 -0
  71. package/dist/commands/plan.d.ts.map +1 -0
  72. package/dist/commands/pull.d.ts +12 -0
  73. package/dist/commands/pull.d.ts.map +1 -0
  74. package/dist/commands/review.d.ts +22 -0
  75. package/dist/commands/review.d.ts.map +1 -0
  76. package/dist/commands/rollback.d.ts +3 -0
  77. package/dist/commands/rollback.d.ts.map +1 -0
  78. package/dist/commands/secrets-template.d.ts +3 -0
  79. package/dist/commands/secrets-template.d.ts.map +1 -0
  80. package/dist/commands/secrets.d.ts +3 -0
  81. package/dist/commands/secrets.d.ts.map +1 -0
  82. package/dist/commands/skills.d.ts +13 -0
  83. package/dist/commands/skills.d.ts.map +1 -0
  84. package/dist/commands/status.d.ts +54 -0
  85. package/dist/commands/status.d.ts.map +1 -0
  86. package/dist/commands/update.d.ts +114 -0
  87. package/dist/commands/update.d.ts.map +1 -0
  88. package/dist/commands/validate.d.ts +3 -0
  89. package/dist/commands/validate.d.ts.map +1 -0
  90. package/dist/commands/whoami.d.ts +3 -0
  91. package/dist/commands/whoami.d.ts.map +1 -0
  92. package/dist/config.d.ts +11 -0
  93. package/dist/config.d.ts.map +1 -0
  94. package/dist/deploy/apply.d.ts +29 -0
  95. package/dist/deploy/apply.d.ts.map +1 -0
  96. package/dist/deploy/dry-run.d.ts +13 -0
  97. package/dist/deploy/dry-run.d.ts.map +1 -0
  98. package/dist/deploy/git-files.d.ts +33 -0
  99. package/dist/deploy/git-files.d.ts.map +1 -0
  100. package/dist/deploy/group-pin.d.ts +66 -0
  101. package/dist/deploy/group-pin.d.ts.map +1 -0
  102. package/dist/deploy/manifest-state.d.ts +20 -0
  103. package/dist/deploy/manifest-state.d.ts.map +1 -0
  104. package/dist/deploy/manifest-status.d.ts +11 -0
  105. package/dist/deploy/manifest-status.d.ts.map +1 -0
  106. package/dist/deploy/resolve.d.ts +53 -0
  107. package/dist/deploy/resolve.d.ts.map +1 -0
  108. package/dist/deploy/rollback.d.ts +23 -0
  109. package/dist/deploy/rollback.d.ts.map +1 -0
  110. package/dist/deploy/runner.d.ts +29 -0
  111. package/dist/deploy/runner.d.ts.map +1 -0
  112. package/dist/deploy/stage-exit-codes.d.ts +41 -0
  113. package/dist/deploy/stage-exit-codes.d.ts.map +1 -0
  114. package/dist/deploy/status-polling.d.ts +37 -0
  115. package/dist/deploy/status-polling.d.ts.map +1 -0
  116. package/dist/deploy/tar-pack.d.ts +22 -0
  117. package/dist/deploy/tar-pack.d.ts.map +1 -0
  118. package/dist/detect/index.d.ts +53 -0
  119. package/dist/detect/index.d.ts.map +1 -0
  120. package/dist/dispatcher.d.ts +30 -0
  121. package/dist/dispatcher.d.ts.map +1 -0
  122. package/dist/groups/client.d.ts +62 -0
  123. package/dist/groups/client.d.ts.map +1 -0
  124. package/dist/http/api-client.d.ts +33 -0
  125. package/dist/http/api-client.d.ts.map +1 -0
  126. package/dist/http/errors.d.ts +31 -0
  127. package/dist/http/errors.d.ts.map +1 -0
  128. package/dist/manifest/load.d.ts +38 -0
  129. package/dist/manifest/load.d.ts.map +1 -0
  130. package/dist/manifest/schema.d.ts +3 -0
  131. package/dist/manifest/schema.d.ts.map +1 -0
  132. package/dist/postinstall.d.ts +3 -0
  133. package/dist/postinstall.d.ts.map +1 -0
  134. package/dist/postinstall.js +37 -0
  135. package/dist/secrets/env-parse.d.ts +19 -0
  136. package/dist/secrets/env-parse.d.ts.map +1 -0
  137. package/dist/secrets/push.d.ts +13 -0
  138. package/dist/secrets/push.d.ts.map +1 -0
  139. package/dist/secrets/set.d.ts +19 -0
  140. package/dist/secrets/set.d.ts.map +1 -0
  141. package/dist/secrets/status.d.ts +19 -0
  142. package/dist/secrets/status.d.ts.map +1 -0
  143. package/dist/types/api.d.ts +112 -0
  144. package/dist/types/api.d.ts.map +1 -0
  145. package/dist/update-notifier.d.ts +69 -0
  146. package/dist/update-notifier.d.ts.map +1 -0
  147. package/dist/version.d.ts +2 -0
  148. package/dist/version.d.ts.map +1 -0
  149. package/package.json +62 -0
  150. package/skills/README.md +100 -0
  151. package/skills/_partials/shell-contract.md +42 -0
  152. package/skills/launchpad-content-pr/SKILL.md +255 -0
  153. package/skills/launchpad-deploy/SKILL.md +415 -0
  154. package/skills/launchpad-deploy-status/SKILL.md +231 -0
  155. package/skills/launchpad-destroy/SKILL.md +317 -0
  156. package/skills/launchpad-onboard/SKILL.md +179 -0
  157. package/skills/launchpad-status/SKILL.md +263 -0
  158. package/skills/marquee-share/README.md +155 -0
  159. package/skills/marquee-share/SKILL.md +94 -0
  160. package/skills/marquee-share/SYNC.md +27 -0
  161. package/skills/marquee-share/dist/cli.js +896 -0
  162. package/skills/marquee-share/eslint.config.mjs +71 -0
  163. package/skills/marquee-share/install.sh +103 -0
  164. package/skills/marquee-share/package-lock.json +3946 -0
  165. package/skills/marquee-share/package.json +30 -0
  166. package/skills/marquee-share/src/auth/PROVENANCE.md +103 -0
  167. package/skills/marquee-share/src/auth/browser.ts +75 -0
  168. package/skills/marquee-share/src/auth/callback-server.ts +171 -0
  169. package/skills/marquee-share/src/auth/discovery.ts +171 -0
  170. package/skills/marquee-share/src/auth/flow.ts +262 -0
  171. package/skills/marquee-share/src/auth/index.ts +171 -0
  172. package/skills/marquee-share/src/auth/jwt.ts +77 -0
  173. package/skills/marquee-share/src/auth/pkce.ts +79 -0
  174. package/skills/marquee-share/src/auth/registration.ts +87 -0
  175. package/skills/marquee-share/src/auth/session.ts +205 -0
  176. package/skills/marquee-share/src/auth/token.ts +162 -0
  177. package/skills/marquee-share/src/cli.ts +246 -0
  178. package/skills/marquee-share/src/config.ts +101 -0
  179. package/skills/marquee-share/src/render/template.ts +171 -0
  180. package/skills/marquee-share/src/upload/index.ts +11 -0
  181. package/skills/marquee-share/src/upload/upload.ts +191 -0
  182. package/skills/marquee-share/tests/cli.test.ts +281 -0
  183. package/skills/marquee-share/tests/config.test.ts +119 -0
  184. package/skills/marquee-share/tests/flow.test.ts +356 -0
  185. package/skills/marquee-share/tests/no-token-leak.test.ts +240 -0
  186. package/skills/marquee-share/tests/pkce.test.ts +121 -0
  187. package/skills/marquee-share/tests/session.test.ts +173 -0
  188. package/skills/marquee-share/tests/template.test.ts +170 -0
  189. package/skills/marquee-share/tests/upload.test.ts +311 -0
  190. package/skills/marquee-share/tsconfig.json +23 -0
  191. package/skills/marquee-share/vitest.config.ts +15 -0
@@ -0,0 +1,896 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import * as fs2 from "node:fs/promises";
5
+ import { pathToFileURL } from "node:url";
6
+
7
+ // src/config.ts
8
+ import * as os from "node:os";
9
+ import * as path from "node:path";
10
+ var DEFAULT_RESOURCE_URL = "https://marquee.launchpad.m-kopa.us";
11
+ function readOverride(value) {
12
+ if (value === undefined)
13
+ return;
14
+ const trimmed = value.trim();
15
+ return trimmed.length === 0 ? undefined : trimmed;
16
+ }
17
+ function isLoopbackHost(host) {
18
+ const h = host.toLowerCase();
19
+ return h === "localhost" || h.endsWith(".localhost") || h === "127.0.0.1" || h === "::1" || h === "[::1]";
20
+ }
21
+ function loadConfig(env = process.env) {
22
+ const resourceUrl = readOverride(env.MARQUEE_RESOURCE_URL)?.replace(/\/+$/, "") ?? DEFAULT_RESOURCE_URL;
23
+ let parsedResourceUrl;
24
+ try {
25
+ parsedResourceUrl = new URL(resourceUrl);
26
+ } catch {
27
+ throw new Error(`MARQUEE_RESOURCE_URL must be a valid absolute URL (got: ${resourceUrl})`);
28
+ }
29
+ if (parsedResourceUrl.protocol === "http:") {
30
+ if (!isLoopbackHost(parsedResourceUrl.hostname)) {
31
+ throw new Error(`MARQUEE_RESOURCE_URL must use https: for non-loopback hosts; ` + `http: is permitted only for localhost / 127.0.0.1 / ::1 ` + `(got: ${resourceUrl})`);
32
+ }
33
+ } else if (parsedResourceUrl.protocol !== "https:") {
34
+ throw new Error(`MARQUEE_RESOURCE_URL must use https: (or http: for loopback hosts); ` + `got: ${parsedResourceUrl.protocol}`);
35
+ }
36
+ const sessionPath = readOverride(env.MARQUEE_SESSION_PATH) ?? path.join(os.homedir(), ".marquee", "session.json");
37
+ return { resourceUrl, sessionPath };
38
+ }
39
+
40
+ // src/auth/flow.ts
41
+ import { randomBytes as randomBytes3 } from "node:crypto";
42
+
43
+ // src/auth/callback-server.ts
44
+ import {
45
+ createServer
46
+ } from "node:http";
47
+
48
+ class CallbackError extends Error {
49
+ code = "callback_error";
50
+ }
51
+ async function bindCallbackServer(expectedState) {
52
+ let resolveResult;
53
+ let rejectResult;
54
+ const result = new Promise((resolve, reject) => {
55
+ resolveResult = resolve;
56
+ rejectResult = reject;
57
+ });
58
+ const server = createServer((req, res) => {
59
+ handleRequest(req, res, expectedState, resolveResult, rejectResult);
60
+ });
61
+ await new Promise((resolve, reject) => {
62
+ server.once("error", reject);
63
+ server.listen(0, "127.0.0.1", () => resolve());
64
+ });
65
+ const addr = server.address();
66
+ if (addr === null || typeof addr === "string") {
67
+ server.close();
68
+ throw new CallbackError(`callback server: failed to bind`);
69
+ }
70
+ let closed = false;
71
+ const close = async () => {
72
+ if (closed)
73
+ return;
74
+ closed = true;
75
+ await new Promise((resolve) => server.close(() => resolve()));
76
+ };
77
+ const cancel = async (reason) => {
78
+ rejectResult(new CallbackError(`callback server cancelled: ${reason}`));
79
+ await close();
80
+ };
81
+ return { port: addr.port, result, close, cancel };
82
+ }
83
+ function handleRequest(req, res, expectedState, resolveResult, rejectResult) {
84
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
85
+ if (url.pathname !== "/callback") {
86
+ res.statusCode = 404;
87
+ res.setHeader("content-type", "text/plain");
88
+ res.end("not found");
89
+ return;
90
+ }
91
+ const error = url.searchParams.get("error");
92
+ if (error !== null) {
93
+ const desc = url.searchParams.get("error_description") ?? "";
94
+ res.statusCode = 400;
95
+ res.setHeader("content-type", "text/html");
96
+ res.end(htmlPage(`Authentication failed: ${error}. ${desc}`));
97
+ rejectResult(new CallbackError(`authorization endpoint returned error=${error}: ${desc}`));
98
+ return;
99
+ }
100
+ const code = url.searchParams.get("code");
101
+ const state = url.searchParams.get("state");
102
+ if (code === null || state === null) {
103
+ res.statusCode = 400;
104
+ res.setHeader("content-type", "text/plain");
105
+ res.end("missing code or state");
106
+ return;
107
+ }
108
+ if (state !== expectedState) {
109
+ res.statusCode = 400;
110
+ res.setHeader("content-type", "text/plain");
111
+ res.end("state mismatch");
112
+ rejectResult(new CallbackError(`OAuth callback state mismatch — possible CSRF, aborting`));
113
+ return;
114
+ }
115
+ res.statusCode = 200;
116
+ res.setHeader("content-type", "text/html");
117
+ res.end(htmlPage("Logged in. You can close this tab."));
118
+ resolveResult({ code, state });
119
+ }
120
+ function htmlPage(message) {
121
+ return `<!doctype html>
122
+ <html><head><meta charset="utf-8"><title>marquee</title>
123
+ <style>body{font:16px/1.5 system-ui,sans-serif;padding:3rem;color:#222}h1{font-size:1.2rem;margin:0 0 1rem}</style>
124
+ </head><body><h1>marquee</h1><p>${escapeHtml(message)}</p></body></html>`;
125
+ }
126
+ function escapeHtml(s) {
127
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
128
+ }
129
+
130
+ // src/auth/discovery.ts
131
+ class DiscoveryError extends Error {
132
+ code = "discovery_error";
133
+ }
134
+ async function discoverOauthEndpoints(resourceUrl, fetcher = fetch) {
135
+ const wellKnownUrl = `${resourceUrl}/.well-known/cloudflare-access-protected-resource/`;
136
+ const resourceMeta = await fetchJson(fetcher, wellKnownUrl);
137
+ if (!Array.isArray(resourceMeta.authorization_servers) || typeof resourceMeta.authorization_servers[0] !== "string") {
138
+ throw new DiscoveryError(`discovery: ${wellKnownUrl} returned no authorization_servers entry`);
139
+ }
140
+ const rawResource = typeof resourceMeta.resource === "string" ? resourceMeta.resource.trim() : "";
141
+ const resource = rawResource.length > 0 ? rawResource : resourceUrl;
142
+ const rawServer = resourceMeta.authorization_servers[0];
143
+ let parsed;
144
+ try {
145
+ parsed = new URL(rawServer);
146
+ } catch {
147
+ throw new DiscoveryError(`${wellKnownUrl}: authorization_servers[0] is not a valid URL: ${rawServer}`);
148
+ }
149
+ if (parsed.protocol !== "https:") {
150
+ throw new DiscoveryError(`${wellKnownUrl}: authorization_servers[0] is not https: ${rawServer}`);
151
+ }
152
+ const server = rawServer.replace(/\/+$/, "");
153
+ const serverUrl = `${server}/.well-known/oauth-authorization-server`;
154
+ const serverMeta = await fetchJson(fetcher, serverUrl);
155
+ const authorizationEndpoint = requireHttpsString(serverMeta, "authorization_endpoint", serverUrl);
156
+ const tokenEndpoint = requireHttpsString(serverMeta, "token_endpoint", serverUrl);
157
+ const registrationEndpoint = requireHttpsString(serverMeta, "registration_endpoint", serverUrl);
158
+ return {
159
+ authorizationEndpoint,
160
+ tokenEndpoint,
161
+ registrationEndpoint,
162
+ resource
163
+ };
164
+ }
165
+ async function fetchJson(fetcher, url) {
166
+ let res;
167
+ try {
168
+ res = await fetcher(url, { method: "GET" });
169
+ } catch (e) {
170
+ throw new DiscoveryError(`discovery: network error fetching ${url}: ${describe(e)}`);
171
+ }
172
+ if (!res.ok) {
173
+ throw new DiscoveryError(`discovery: ${url} returned HTTP ${res.status}`);
174
+ }
175
+ try {
176
+ return await res.json();
177
+ } catch (e) {
178
+ throw new DiscoveryError(`discovery: ${url} returned non-JSON body: ${describe(e)}`);
179
+ }
180
+ }
181
+ function requireHttpsString(obj, key, source) {
182
+ const v = obj[key];
183
+ if (typeof v !== "string") {
184
+ throw new DiscoveryError(`${source} missing string ${key}`);
185
+ }
186
+ if (!v.startsWith("https://")) {
187
+ throw new DiscoveryError(`${source} ${key} is not https: ${v}`);
188
+ }
189
+ return v;
190
+ }
191
+ function describe(e) {
192
+ return e instanceof Error ? e.message : String(e);
193
+ }
194
+
195
+ // src/auth/pkce.ts
196
+ import { randomBytes, createHash } from "node:crypto";
197
+ var MAX_PKCE_REGEN_ATTEMPTS = 32;
198
+
199
+ class PkceGenerationError extends Error {
200
+ code = "pkce_generation_failed";
201
+ }
202
+ function generatePkcePair(rng = (s) => new Uint8Array(randomBytes(s))) {
203
+ for (let attempt = 0;attempt < MAX_PKCE_REGEN_ATTEMPTS; attempt++) {
204
+ const verifierBytes = rng(32);
205
+ const verifier = base64Url(verifierBytes);
206
+ const challenge = sha256Base64Url(verifier);
207
+ if (/^[a-zA-Z0-9]/.test(challenge)) {
208
+ return { verifier, challenge };
209
+ }
210
+ }
211
+ throw new PkceGenerationError(`could not produce a Cloudflare-acceptable PKCE challenge after ${MAX_PKCE_REGEN_ATTEMPTS} attempts; check the RNG`);
212
+ }
213
+ function sha256Base64Url(verifier) {
214
+ const hash = createHash("sha256").update(verifier).digest();
215
+ return base64Url(hash);
216
+ }
217
+ function base64Url(bytes) {
218
+ const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes);
219
+ return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
220
+ }
221
+
222
+ // src/auth/registration.ts
223
+ class RegistrationError extends Error {
224
+ code = "registration_error";
225
+ }
226
+ async function registerClient(registrationEndpoint, redirectUri, fetcher = fetch) {
227
+ const body = {
228
+ client_name: "marquee-share-skill",
229
+ redirect_uris: [redirectUri],
230
+ token_endpoint_auth_method: "none",
231
+ grant_types: ["authorization_code", "refresh_token"],
232
+ response_types: ["code"]
233
+ };
234
+ let res;
235
+ try {
236
+ res = await fetcher(registrationEndpoint, {
237
+ method: "POST",
238
+ headers: {
239
+ "content-type": "application/json",
240
+ accept: "application/json"
241
+ },
242
+ body: JSON.stringify(body)
243
+ });
244
+ } catch (e) {
245
+ throw new RegistrationError(`registration: network error: ${describe2(e)}`);
246
+ }
247
+ if (!res.ok) {
248
+ const detail = await res.text().catch(() => "");
249
+ throw new RegistrationError(`registration: ${registrationEndpoint} returned HTTP ${res.status}: ${detail.slice(0, 200)}`);
250
+ }
251
+ let parsed;
252
+ try {
253
+ parsed = await res.json();
254
+ } catch (e) {
255
+ throw new RegistrationError(`registration: non-JSON response: ${describe2(e)}`);
256
+ }
257
+ if (parsed === null || typeof parsed !== "object" || typeof parsed.client_id !== "string") {
258
+ throw new RegistrationError(`registration: response missing string client_id`);
259
+ }
260
+ return { clientId: parsed.client_id };
261
+ }
262
+ function describe2(e) {
263
+ return e instanceof Error ? e.message : String(e);
264
+ }
265
+
266
+ // src/auth/token.ts
267
+ class TokenError extends Error {
268
+ code = "token_error";
269
+ httpStatus;
270
+ constructor(message, httpStatus) {
271
+ super(message);
272
+ this.name = "TokenError";
273
+ if (httpStatus !== undefined)
274
+ this.httpStatus = httpStatus;
275
+ }
276
+ }
277
+ async function exchangeCodeForTokens(params, fetcher = fetch) {
278
+ const form = new URLSearchParams({
279
+ grant_type: "authorization_code",
280
+ client_id: params.clientId,
281
+ code: params.code,
282
+ code_verifier: params.codeVerifier,
283
+ redirect_uri: params.redirectUri,
284
+ resource: params.resource
285
+ });
286
+ return postTokenForm(params.tokenEndpoint, form, fetcher);
287
+ }
288
+ async function refreshTokens(params, fetcher = fetch) {
289
+ const form = new URLSearchParams({
290
+ grant_type: "refresh_token",
291
+ client_id: params.clientId,
292
+ refresh_token: params.refreshToken,
293
+ resource: params.resource
294
+ });
295
+ return postTokenForm(params.tokenEndpoint, form, fetcher);
296
+ }
297
+ async function postTokenForm(tokenEndpoint, form, fetcher) {
298
+ let res;
299
+ try {
300
+ res = await fetcher(tokenEndpoint, {
301
+ method: "POST",
302
+ headers: {
303
+ "content-type": "application/x-www-form-urlencoded",
304
+ accept: "application/json"
305
+ },
306
+ body: form.toString()
307
+ });
308
+ } catch (e) {
309
+ throw new TokenError(`token endpoint: network error: ${describe3(e)}`);
310
+ }
311
+ if (!res.ok) {
312
+ const detail = await res.text().catch(() => "");
313
+ let oauthError = "";
314
+ try {
315
+ const parsed2 = JSON.parse(detail);
316
+ if (typeof parsed2.error === "string") {
317
+ oauthError = ` (${parsed2.error})`;
318
+ }
319
+ } catch {}
320
+ throw new TokenError(`token endpoint ${tokenEndpoint} returned HTTP ${res.status}${oauthError}`, res.status);
321
+ }
322
+ let parsed;
323
+ try {
324
+ parsed = await res.json();
325
+ } catch (e) {
326
+ throw new TokenError(`token endpoint: non-JSON response: ${describe3(e)}`);
327
+ }
328
+ if (parsed === null || typeof parsed !== "object") {
329
+ throw new TokenError(`token endpoint: response is not an object`);
330
+ }
331
+ const obj = parsed;
332
+ if (typeof obj.access_token !== "string") {
333
+ throw new TokenError(`token endpoint: response missing access_token`);
334
+ }
335
+ if (typeof obj.refresh_token !== "string") {
336
+ throw new TokenError(`token endpoint: response missing refresh_token`);
337
+ }
338
+ const expiresIn = obj.expires_in;
339
+ if (typeof expiresIn !== "number" || !Number.isFinite(expiresIn) || expiresIn <= 0) {
340
+ throw new TokenError(`token endpoint: response missing positive finite numeric expires_in (got ${String(expiresIn)})`);
341
+ }
342
+ return {
343
+ accessToken: obj.access_token,
344
+ refreshToken: obj.refresh_token,
345
+ expiresInSec: expiresIn
346
+ };
347
+ }
348
+ function describe3(e) {
349
+ return e instanceof Error ? e.message : String(e);
350
+ }
351
+
352
+ // src/auth/session.ts
353
+ import * as fs from "node:fs/promises";
354
+ import * as path2 from "node:path";
355
+ import { randomBytes as randomBytes2 } from "node:crypto";
356
+ var SESSION_VERSION = 1;
357
+
358
+ class SessionParseError extends Error {
359
+ code = "session_parse_error";
360
+ }
361
+ async function readSession(sessionPath) {
362
+ let raw;
363
+ try {
364
+ raw = await fs.readFile(sessionPath, "utf8");
365
+ } catch (e) {
366
+ if (isErrno(e) && e.code === "ENOENT")
367
+ return null;
368
+ throw e;
369
+ }
370
+ let parsed;
371
+ try {
372
+ parsed = JSON.parse(raw);
373
+ } catch (e) {
374
+ throw new SessionParseError(`session file at ${sessionPath} is not valid JSON: ${describe4(e)}`);
375
+ }
376
+ if (typeof parsed !== "object" || parsed === null) {
377
+ throw new SessionParseError(`session file at ${sessionPath} is not an object`);
378
+ }
379
+ const obj = parsed;
380
+ if (obj.version !== SESSION_VERSION) {
381
+ throw new SessionParseError(`unsupported session version ${String(obj.version)} at ${sessionPath}; expected ${SESSION_VERSION}`);
382
+ }
383
+ for (const k of [
384
+ "accessToken",
385
+ "refreshToken",
386
+ "clientId",
387
+ "tokenEndpoint",
388
+ "issuedAt"
389
+ ]) {
390
+ if (typeof obj[k] !== "string") {
391
+ throw new SessionParseError(`session file at ${sessionPath}: missing or non-string ${k}`);
392
+ }
393
+ }
394
+ if (typeof obj.accessTokenExpiresAt !== "number" || !Number.isFinite(obj.accessTokenExpiresAt) || obj.accessTokenExpiresAt <= 0) {
395
+ throw new SessionParseError(`session file at ${sessionPath}: missing or invalid accessTokenExpiresAt`);
396
+ }
397
+ if (obj.resource !== undefined && typeof obj.resource !== "string") {
398
+ throw new SessionParseError(`session file at ${sessionPath}: resource present but not a string`);
399
+ }
400
+ return obj;
401
+ }
402
+ async function writeSession(sessionPath, session) {
403
+ const dir = path2.dirname(sessionPath);
404
+ await fs.mkdir(dir, { recursive: true, mode: 448 });
405
+ try {
406
+ await fs.chmod(dir, 448);
407
+ } catch {}
408
+ const tmp = `${sessionPath}.${process.pid}.${Date.now()}.${randomBytes2(4).toString("hex")}.tmp`;
409
+ try {
410
+ await fs.writeFile(tmp, JSON.stringify(session, null, 2), {
411
+ encoding: "utf8",
412
+ mode: 384
413
+ });
414
+ await fs.rename(tmp, sessionPath);
415
+ } catch (e) {
416
+ await fs.unlink(tmp).catch(() => {
417
+ return;
418
+ });
419
+ throw e;
420
+ }
421
+ }
422
+ async function clearSession(sessionPath) {
423
+ try {
424
+ await fs.unlink(sessionPath);
425
+ return true;
426
+ } catch (e) {
427
+ if (isErrno(e) && e.code === "ENOENT")
428
+ return false;
429
+ throw e;
430
+ }
431
+ }
432
+ function isErrno(e) {
433
+ return e instanceof Error && typeof e.code === "string";
434
+ }
435
+ function describe4(e) {
436
+ return e instanceof Error ? e.message : String(e);
437
+ }
438
+
439
+ // src/auth/browser.ts
440
+ import { spawn } from "node:child_process";
441
+
442
+ class BrowserOpenError extends Error {
443
+ code = "browser_open_error";
444
+ }
445
+ async function openBrowser(url, platform = process.platform, spawner = spawn) {
446
+ const { command, args } = chooseOpener(platform, url);
447
+ return new Promise((resolve, reject) => {
448
+ const child = spawner(command, args, {
449
+ stdio: "ignore",
450
+ detached: true
451
+ });
452
+ child.once("error", (err) => {
453
+ reject(new BrowserOpenError(`could not open browser via ${command}: ${err.message}`));
454
+ });
455
+ child.once("spawn", () => {
456
+ child.unref();
457
+ resolve();
458
+ });
459
+ });
460
+ }
461
+ function chooseOpener(platform, url) {
462
+ switch (platform) {
463
+ case "darwin":
464
+ return { command: "open", args: [url] };
465
+ case "win32":
466
+ return { command: "cmd", args: ["/c", "start", "", url] };
467
+ default:
468
+ return { command: "xdg-open", args: [url] };
469
+ }
470
+ }
471
+
472
+ // src/auth/flow.ts
473
+ class LoginRequiredError extends Error {
474
+ code = "login_required";
475
+ }
476
+ var REFRESH_SKEW_MS = 30000;
477
+ var CALLBACK_TIMEOUT_MS = 5 * 60000;
478
+ async function login(opts) {
479
+ const fetcher = opts.fetcher ?? fetch;
480
+ const opener = opts.browserOpener ?? ((url) => openBrowser(url));
481
+ const endpoints = await discoverOauthEndpoints(opts.resourceUrl, fetcher);
482
+ const state = randomBytes3(16).toString("hex");
483
+ const server = await bindCallbackServer(state);
484
+ try {
485
+ const redirectUri = `http://127.0.0.1:${server.port}/callback`;
486
+ const reg = await registerClient(endpoints.registrationEndpoint, redirectUri, fetcher);
487
+ const pkce = generatePkcePair();
488
+ const authUrl = buildAuthorizationUrl({
489
+ authorizationEndpoint: endpoints.authorizationEndpoint,
490
+ clientId: reg.clientId,
491
+ redirectUri,
492
+ challenge: pkce.challenge,
493
+ state,
494
+ resource: endpoints.resource
495
+ });
496
+ opts.onAuthUrl?.(authUrl);
497
+ try {
498
+ await opener(authUrl);
499
+ } catch (e) {
500
+ opts.onAuthUrl?.(`(could not auto-open browser: ${describe5(e)} — copy the URL above into a browser instead)`);
501
+ }
502
+ let callbackTimer;
503
+ const callback = await Promise.race([
504
+ server.result,
505
+ new Promise((_, reject) => {
506
+ callbackTimer = setTimeout(() => reject(new LoginRequiredError("timed out waiting for the browser callback — run `marquee login` again")), CALLBACK_TIMEOUT_MS);
507
+ })
508
+ ]).finally(() => {
509
+ if (callbackTimer !== undefined)
510
+ clearTimeout(callbackTimer);
511
+ });
512
+ const tokens = await exchangeCodeForTokens({
513
+ tokenEndpoint: endpoints.tokenEndpoint,
514
+ clientId: reg.clientId,
515
+ code: callback.code,
516
+ codeVerifier: pkce.verifier,
517
+ redirectUri,
518
+ resource: endpoints.resource
519
+ }, fetcher);
520
+ const session = {
521
+ version: SESSION_VERSION,
522
+ accessToken: tokens.accessToken,
523
+ refreshToken: tokens.refreshToken,
524
+ accessTokenExpiresAt: Date.now() + tokens.expiresInSec * 1000,
525
+ clientId: reg.clientId,
526
+ tokenEndpoint: endpoints.tokenEndpoint,
527
+ resource: endpoints.resource,
528
+ issuedAt: new Date().toISOString()
529
+ };
530
+ await writeSession(opts.sessionPath, session);
531
+ return session;
532
+ } finally {
533
+ await server.close();
534
+ }
535
+ }
536
+ async function getValidAccessToken(sessionPath, fetcher = fetch, now = Date.now) {
537
+ const session = await readSession(sessionPath);
538
+ if (session === null) {
539
+ throw new LoginRequiredError("no session — run `marquee login`");
540
+ }
541
+ if (session.accessTokenExpiresAt - REFRESH_SKEW_MS > now()) {
542
+ return { accessToken: session.accessToken, session };
543
+ }
544
+ if (session.resource === undefined) {
545
+ throw new LoginRequiredError("session is missing the resource indicator — run `marquee login`");
546
+ }
547
+ let next;
548
+ try {
549
+ next = await refreshTokens({
550
+ tokenEndpoint: session.tokenEndpoint,
551
+ clientId: session.clientId,
552
+ refreshToken: session.refreshToken,
553
+ resource: session.resource
554
+ }, fetcher);
555
+ } catch (e) {
556
+ if (e instanceof TokenError && e.httpStatus !== undefined && e.httpStatus >= 400 && e.httpStatus < 500) {
557
+ throw new LoginRequiredError(`session expired — run \`marquee login\` (token endpoint said: ${e.message})`);
558
+ }
559
+ throw e;
560
+ }
561
+ const refreshed = {
562
+ ...session,
563
+ accessToken: next.accessToken,
564
+ refreshToken: next.refreshToken,
565
+ accessTokenExpiresAt: now() + next.expiresInSec * 1000,
566
+ issuedAt: new Date(now()).toISOString()
567
+ };
568
+ await writeSession(sessionPath, refreshed);
569
+ return { accessToken: refreshed.accessToken, session: refreshed };
570
+ }
571
+ function describe5(e) {
572
+ return e instanceof Error ? e.message : String(e);
573
+ }
574
+ function buildAuthorizationUrl(params) {
575
+ const u = new URL(params.authorizationEndpoint);
576
+ u.searchParams.set("client_id", params.clientId);
577
+ u.searchParams.set("redirect_uri", params.redirectUri);
578
+ u.searchParams.set("response_type", "code");
579
+ u.searchParams.set("code_challenge", params.challenge);
580
+ u.searchParams.set("code_challenge_method", "S256");
581
+ u.searchParams.set("state", params.state);
582
+ u.searchParams.set("resource", params.resource);
583
+ return u.toString();
584
+ }
585
+
586
+ // src/auth/index.ts
587
+ function resolveConfig(opts) {
588
+ return opts.config ?? loadConfig();
589
+ }
590
+ function diag(opts, line) {
591
+ (opts.onDiagnostic ?? ((l) => console.error(l)))(line);
592
+ }
593
+ async function login2(opts = {}) {
594
+ const cfg = resolveConfig(opts);
595
+ diag(opts, "Opening browser to authenticate with Cloudflare Access…");
596
+ diag(opts, "(if it doesn't open automatically, copy the URL below)");
597
+ const session = await login({
598
+ resourceUrl: cfg.resourceUrl,
599
+ sessionPath: cfg.sessionPath,
600
+ onAuthUrl: (url) => diag(opts, url),
601
+ ...opts.fetcher !== undefined ? { fetcher: opts.fetcher } : {},
602
+ ...opts.browserOpener !== undefined ? { browserOpener: opts.browserOpener } : {}
603
+ });
604
+ diag(opts, `Logged in. Session stored at ${cfg.sessionPath}`);
605
+ return session;
606
+ }
607
+ async function logout(opts = {}) {
608
+ const cfg = resolveConfig(opts);
609
+ const had = await clearSession(cfg.sessionPath);
610
+ diag(opts, had ? `Logged out. Session cleared at ${cfg.sessionPath}` : "Already logged out.");
611
+ return had;
612
+ }
613
+ async function getValidToken(opts = {}) {
614
+ const cfg = resolveConfig(opts);
615
+ const interactive = opts.interactive ?? true;
616
+ const fetcher = opts.fetcher ?? fetch;
617
+ const now = opts.now ?? Date.now;
618
+ try {
619
+ const { accessToken } = await getValidAccessToken(cfg.sessionPath, fetcher, now);
620
+ return accessToken;
621
+ } catch (e) {
622
+ if (e instanceof LoginRequiredError) {
623
+ if (!interactive) {
624
+ throw e;
625
+ }
626
+ diag(opts, "No valid Marquee session — starting a browser login…");
627
+ const session = await login2(opts);
628
+ return session.accessToken;
629
+ }
630
+ throw e;
631
+ }
632
+ }
633
+
634
+ // src/render/template.ts
635
+ var BRAND_GREEN = "#00b140";
636
+ var INK = "#1a1a1a";
637
+ var DEFAULT_TITLE = "Shared via Marquee";
638
+ function escapeHtml2(value) {
639
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
640
+ }
641
+ var STYLE = `
642
+ :root { --brand-green: ${BRAND_GREEN}; --ink: ${INK}; }
643
+ * { box-sizing: border-box; }
644
+ html, body { margin: 0; padding: 0; }
645
+ body {
646
+ background: #ffffff;
647
+ color: var(--ink);
648
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
649
+ Helvetica, Arial, sans-serif;
650
+ line-height: 1.6;
651
+ -webkit-text-size-adjust: 100%;
652
+ }
653
+ .marquee-shell {
654
+ max-width: 760px;
655
+ margin: 0 auto;
656
+ padding: 40px 24px 64px;
657
+ }
658
+ .marquee-header {
659
+ border-bottom: 3px solid var(--brand-green);
660
+ padding-bottom: 16px;
661
+ margin-bottom: 32px;
662
+ }
663
+ .marquee-eyebrow {
664
+ font-size: 12px;
665
+ font-weight: 700;
666
+ letter-spacing: 0.08em;
667
+ text-transform: uppercase;
668
+ color: var(--brand-green);
669
+ margin: 0 0 6px;
670
+ }
671
+ .marquee-title {
672
+ font-size: 28px;
673
+ font-weight: 700;
674
+ color: var(--ink);
675
+ margin: 0;
676
+ }
677
+ .marquee-content {
678
+ font-size: 16px;
679
+ }
680
+ .marquee-content a { color: var(--brand-green); }
681
+ .marquee-content pre,
682
+ .marquee-content code {
683
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
684
+ }
685
+ .marquee-content pre {
686
+ background: #f5f5f5;
687
+ border-left: 3px solid var(--brand-green);
688
+ padding: 12px 16px;
689
+ overflow-x: auto;
690
+ }
691
+ .marquee-footer {
692
+ margin-top: 48px;
693
+ padding-top: 16px;
694
+ border-top: 1px solid #e6e6e6;
695
+ font-size: 12px;
696
+ color: #6b6b6b;
697
+ }
698
+ `.trim();
699
+ function renderBrandedDocument(input) {
700
+ const safeTitle = escapeHtml2(input.title.trim().length > 0 ? input.title : DEFAULT_TITLE);
701
+ const shellPrefix = `<!DOCTYPE html>
702
+ ` + `<html lang="en">
703
+ ` + `<head>
704
+ ` + `<meta charset="utf-8">
705
+ ` + `<meta name="viewport" content="width=device-width, initial-scale=1">
706
+ ` + `<title>${safeTitle}</title>
707
+ ` + `<style>${STYLE}</style>
708
+ ` + `</head>
709
+ ` + `<body>
710
+ ` + `<main class="marquee-shell">
711
+ ` + `<header class="marquee-header">
712
+ ` + `<p class="marquee-eyebrow">M-KOPA</p>
713
+ ` + `<h1 class="marquee-title">${safeTitle}</h1>
714
+ ` + `</header>
715
+ ` + `<article class="marquee-content">
716
+ `;
717
+ const shellSuffix = `
718
+ </article>
719
+ ` + `<footer class="marquee-footer">Shared via Marquee</footer>
720
+ ` + `</main>
721
+ ` + `</body>
722
+ ` + `</html>
723
+ `;
724
+ return shellPrefix + input.contentHtml + shellSuffix;
725
+ }
726
+
727
+ // src/upload/upload.ts
728
+ var DEFAULT_UPLOAD_TIMEOUT_MS = 30000;
729
+
730
+ class MarqueeUploadError extends Error {
731
+ status;
732
+ constructor(message, status) {
733
+ super(message);
734
+ this.name = "MarqueeUploadError";
735
+ this.status = status;
736
+ }
737
+ }
738
+ async function shareToMarquee(input, options = {}) {
739
+ const config = options.config ?? loadConfig();
740
+ const fetcher = options.fetcher ?? fetch;
741
+ const tokenProvider = options.tokenProvider ?? (() => getValidToken());
742
+ const timeoutMs = options.timeoutMs ?? DEFAULT_UPLOAD_TIMEOUT_MS;
743
+ const document = renderBrandedDocument({
744
+ title: input.title,
745
+ contentHtml: input.contentHtml
746
+ });
747
+ const token = await tokenProvider();
748
+ const endpoint = `${config.resourceUrl}/api/uploads`;
749
+ let response;
750
+ const controller = new AbortController;
751
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
752
+ try {
753
+ response = await fetcher(endpoint, {
754
+ method: "POST",
755
+ headers: {
756
+ "content-type": "text/html; charset=utf-8",
757
+ authorization: `Bearer ${token}`
758
+ },
759
+ body: document,
760
+ signal: controller.signal
761
+ });
762
+ } catch (_e) {
763
+ throw new MarqueeUploadError("upload request failed (network error)", 0);
764
+ } finally {
765
+ clearTimeout(timer);
766
+ }
767
+ if (!response.ok) {
768
+ throw new MarqueeUploadError(`Marquee upload failed with HTTP ${response.status}`, response.status);
769
+ }
770
+ let parsed;
771
+ try {
772
+ parsed = await response.json();
773
+ } catch (_e) {
774
+ throw new MarqueeUploadError("Marquee upload returned a non-JSON response", response.status);
775
+ }
776
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
777
+ throw new MarqueeUploadError("Marquee upload response was not a JSON object", response.status);
778
+ }
779
+ const shape = parsed;
780
+ if (typeof shape.view_url !== "string" || shape.view_url.length === 0) {
781
+ throw new MarqueeUploadError("Marquee upload response did not include a view_url", response.status);
782
+ }
783
+ return {
784
+ viewUrl: shape.view_url,
785
+ ...typeof shape.id === "string" ? { id: shape.id } : {}
786
+ };
787
+ }
788
+ // src/cli.ts
789
+ var USAGE = `marquee-share — share an AI-chat result to Marquee.
790
+
791
+ Usage:
792
+ marquee-share login Authenticate with Marquee (one-time browser login).
793
+ marquee-share logout Clear the cached Marquee session.
794
+ marquee-share share [file] Wrap an HTML document, upload it, print the view URL.
795
+ Reads the document from [file], or from stdin if omitted.
796
+ --title <text> Title for the branded document (default: "Shared via Marquee").
797
+
798
+ The 'share' command prints ONLY the resulting view URL to stdout.`;
799
+ function parseShareArgs(args) {
800
+ let title = "Shared via Marquee";
801
+ let file;
802
+ for (let i = 0;i < args.length; i++) {
803
+ const arg = args[i];
804
+ if (arg === "--title") {
805
+ const value = args[i + 1];
806
+ if (value === undefined) {
807
+ throw new Error("--title requires a value");
808
+ }
809
+ title = value;
810
+ i++;
811
+ continue;
812
+ }
813
+ if (arg.startsWith("--title=")) {
814
+ title = arg.slice("--title=".length);
815
+ continue;
816
+ }
817
+ if (arg.startsWith("-")) {
818
+ throw new Error(`unknown option: ${arg}`);
819
+ }
820
+ if (file !== undefined) {
821
+ throw new Error("share accepts at most one file argument");
822
+ }
823
+ file = arg;
824
+ }
825
+ return file !== undefined ? { file, title } : { title };
826
+ }
827
+ async function runShare(deps, rest) {
828
+ const { file, title } = parseShareArgs(rest);
829
+ const contentHtml = file !== undefined ? await deps.readFile(file) : await deps.readStdin();
830
+ if (contentHtml.trim().length === 0) {
831
+ deps.stderr("error: no HTML content to share (input was empty)");
832
+ return 1;
833
+ }
834
+ const result = await deps.shareToMarquee({ title, contentHtml });
835
+ deps.stdout(result.viewUrl);
836
+ return 0;
837
+ }
838
+ async function run(deps) {
839
+ const [subcommand, ...rest] = deps.argv;
840
+ if (subcommand === undefined || subcommand === "--help" || subcommand === "-h") {
841
+ deps.stderr(USAGE);
842
+ return subcommand === undefined ? 1 : 0;
843
+ }
844
+ try {
845
+ switch (subcommand) {
846
+ case "login": {
847
+ await deps.login({ onDiagnostic: deps.stderr });
848
+ return 0;
849
+ }
850
+ case "logout": {
851
+ await deps.logout({ onDiagnostic: deps.stderr });
852
+ return 0;
853
+ }
854
+ case "share": {
855
+ return await runShare(deps, rest);
856
+ }
857
+ default: {
858
+ deps.stderr(`error: unknown command "${subcommand}"`);
859
+ deps.stderr(USAGE);
860
+ return 1;
861
+ }
862
+ }
863
+ } catch (e) {
864
+ const message = e instanceof Error ? e.message : String(e);
865
+ deps.stderr(`error: ${message}`);
866
+ return 1;
867
+ }
868
+ }
869
+ async function main() {
870
+ const code = await run({
871
+ argv: process.argv.slice(2),
872
+ stdout: (line) => process.stdout.write(`${line}
873
+ `),
874
+ stderr: (line) => process.stderr.write(`${line}
875
+ `),
876
+ readStdin: async () => {
877
+ const chunks = [];
878
+ for await (const chunk of process.stdin) {
879
+ chunks.push(Buffer.from(chunk));
880
+ }
881
+ return Buffer.concat(chunks).toString("utf8");
882
+ },
883
+ readFile: (path3) => fs2.readFile(path3, "utf8"),
884
+ login: login2,
885
+ logout,
886
+ shareToMarquee
887
+ });
888
+ process.exitCode = code;
889
+ }
890
+ var invokedDirectly = process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
891
+ if (invokedDirectly) {
892
+ main();
893
+ }
894
+ export {
895
+ run
896
+ };