@akterjs/cli 0.0.0-stage → 0.1.1

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 (109) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +47 -2
  3. package/dist/Argument-D81dqizB.mjs +57 -0
  4. package/dist/Array-B4kuTQeW.mjs +2323 -0
  5. package/dist/Boolean-kILIvmhE.mjs +26 -0
  6. package/dist/ChildProcess-BCIwxPGc.mjs +577 -0
  7. package/dist/CliError-Cf5xqkkj.mjs +633 -0
  8. package/dist/Command-Dzq9KM-h.mjs +5135 -0
  9. package/dist/Config-B6OND9WR.mjs +854 -0
  10. package/dist/Console-BKHUkH6a.mjs +141 -0
  11. package/dist/Data-CeCULr0-.mjs +251 -0
  12. package/dist/Digest-CBd3uv5B.mjs +348 -0
  13. package/dist/Duration-EH9Vy4U6.mjs +791 -0
  14. package/dist/Effect-CKFpqbmw.mjs +12911 -0
  15. package/dist/FileSystem-4pPcEb_i.mjs +1366 -0
  16. package/dist/HttpClientError-BUeMWx70.mjs +303 -0
  17. package/dist/HttpClientResponse-D5ZKICVV.mjs +855 -0
  18. package/dist/Match-CGPLZYrV.mjs +434 -0
  19. package/dist/MutableHashMap-f8nW8AJR.mjs +376 -0
  20. package/dist/MutableHashSet-_HI572Ya.mjs +155 -0
  21. package/dist/MutableRef-D4Qv592C.mjs +107 -0
  22. package/dist/Number-BvkRkfc-.mjs +137 -0
  23. package/dist/Option-DkxSnBhp.mjs +3967 -0
  24. package/dist/Order-C1JC991I.mjs +481 -0
  25. package/dist/Path-Djdv_eF5.mjs +130 -0
  26. package/dist/Queue-CO5UQ6os.mjs +1394 -0
  27. package/dist/Redacted-RkJrG5Me.mjs +160 -0
  28. package/dist/Ref-BIPOxJNH.mjs +267 -0
  29. package/dist/Runtime-BInIAwK3.mjs +297 -0
  30. package/dist/Schema-MiKt5K1v.mjs +10999 -0
  31. package/dist/SecureRandom-B_U4ysO3.mjs +279 -0
  32. package/dist/Semaphore-DOVJ4ZGS.mjs +177 -0
  33. package/dist/Stdio-KylmOBX_.mjs +74 -0
  34. package/dist/Stream-bK9QIETN.mjs +4012 -0
  35. package/dist/String-q5dcsVe4.mjs +351 -0
  36. package/dist/TerminalCapabilities-w_44MAca.mjs +1769 -0
  37. package/dist/WebCrypto-ZI_GNVlw.mjs +33 -0
  38. package/dist/actor-call.service-CGM_vD9u.mjs +1269 -0
  39. package/dist/actor-client.adapter-Coem7gF1.mjs +3691 -0
  40. package/dist/akter-program.config-D8yb9xQP.mjs +7034 -0
  41. package/dist/akter.cli.mjs +5 -0
  42. package/dist/app-cli.config.mjs +49 -0
  43. package/dist/app-entry.pure.mjs +31 -0
  44. package/dist/archive/akter-archive.schema.mjs +65 -0
  45. package/dist/archive/akter-archive.service.mjs +309 -0
  46. package/dist/archive/archive-cli.config.mjs +45 -0
  47. package/dist/archive/archive-invalid.errors.mjs +5 -0
  48. package/dist/archive/archived-lineage.pure.mjs +13 -0
  49. package/dist/archive/tar.pure.mjs +291 -0
  50. package/dist/archive/zstd.adapter.mjs +46 -0
  51. package/dist/bench/bench-report.pure.mjs +120 -0
  52. package/dist/bench/bench-runner.pure.mjs +89 -0
  53. package/dist/bench/bench.service.mjs +301 -0
  54. package/dist/cli-runtime.layer-BqK2mmVL.mjs +1191 -0
  55. package/dist/cli-runtime.layer.mjs +1 -0
  56. package/dist/cloud-client.adapter-Cy1BjDbG.mjs +3186 -0
  57. package/dist/cloud-client.adapter-DOVMoYAj.mjs +304 -0
  58. package/dist/command/akter-commands.config.mjs +32 -0
  59. package/dist/command/akter-program.config.mjs +1 -0
  60. package/dist/credentials/credentials.service.mjs +226 -0
  61. package/dist/credentials/keychain.adapter.mjs +92 -0
  62. package/dist/credentials/login.config.mjs +251 -0
  63. package/dist/deploy/auth/api-attempt.service.mjs +30 -0
  64. package/dist/deploy/auth/token-refresh.errors.mjs +4 -0
  65. package/dist/deploy/build-bundle.adapter.mjs +133 -0
  66. package/dist/deploy/build-manifest.pure.mjs +44 -0
  67. package/dist/deploy/cloud-client.adapter.mjs +1 -0
  68. package/dist/deploy/cloud-url.pure.mjs +17 -0
  69. package/dist/deploy/deploy-failed.errors.mjs +17 -0
  70. package/dist/deploy/deploy.service.mjs +156 -0
  71. package/dist/deploy/request-failure.pure.mjs +87 -0
  72. package/dist/dev-runner.pure.mjs +30 -0
  73. package/dist/docs/doc-topics.pure.mjs +21 -0
  74. package/dist/docs/docs.config.mjs +1 -0
  75. package/dist/docs.config-D2amjsje.mjs +190 -0
  76. package/dist/docs.config-D3zzLzLR.mjs +190 -0
  77. package/dist/export/actor-export.service.mjs +267 -0
  78. package/dist/export/export-failed.errors.mjs +4 -0
  79. package/dist/export/export-failure.pure.mjs +18 -0
  80. package/dist/export/ordered-lines.pure.mjs +43 -0
  81. package/dist/export/ranged-download.pure.mjs +66 -0
  82. package/dist/export/snapshot-download.pure.mjs +86 -0
  83. package/dist/history-runner.pure.mjs +186 -0
  84. package/dist/history-since.pure.mjs +17 -0
  85. package/dist/hosted-runtime.layer-Cx3IPsEd.mjs +16358 -0
  86. package/dist/local/environment-cli.config.mjs +90 -0
  87. package/dist/local/environment-runner.pure.mjs +75 -0
  88. package/dist/local/local-cli.config.mjs +159 -0
  89. package/dist/logs/log-follow.config.mjs +21 -0
  90. package/dist/logs/log-follow.service.mjs +87 -0
  91. package/dist/logs/log-lines.pure.mjs +44 -0
  92. package/dist/migrate-runner.pure.mjs +28 -0
  93. package/dist/operation-input.pure-CS9iUhqK.mjs +1383 -0
  94. package/dist/operation-registry.pure-DVC8EDgj.mjs +283 -0
  95. package/dist/registry/actor-call.service.mjs +1 -0
  96. package/dist/registry/actor-commands.config.mjs +127 -0
  97. package/dist/registry/actor-refusal.pure.mjs +34 -0
  98. package/dist/registry/approval.service.mjs +80 -0
  99. package/dist/registry/cloud-commands.config.mjs +151 -0
  100. package/dist/registry/cloud-served.pure.mjs +31 -0
  101. package/dist/registry/operation-call.service.mjs +262 -0
  102. package/dist/registry/operation-input.pure.mjs +1 -0
  103. package/dist/registry/operation-output.pure.mjs +91 -0
  104. package/dist/registry/served-command.config.mjs +57 -0
  105. package/dist/runner-exited.errors.mjs +10 -0
  106. package/dist/terminal/cli-exit.errors.mjs +38 -0
  107. package/dist/terminal/cli-failure.pure.mjs +31 -0
  108. package/dist/terminal/confirm.adapter.mjs +27 -0
  109. package/package.json +61 -3
@@ -0,0 +1,304 @@
1
+ import { P as currentTimeMillis, S as prependUrl, f as bearerToken, r as fromWeb } from "./HttpClientResponse-D5ZKICVV.mjs";
2
+ import { A as ensuring, At as provideContext, B as flatMap, Dt as orElseSucceed, L as filterOrElse, Lt as retry, M as fail, T as context, Tt as option, U as fn, Vn as effect, W as fnUntraced, Xt as succeed, _a as pick, aa as Service, cn as tryPromise, d as asVoid, dt as map, en as sync, et as gen, ft as mapBoth, ht as mapError, ia as Reference, in as timeout, jt as provideService, o as andThen, p as callback, rt as interrupt, ua as getOrUndefined, xt as onError } from "./Effect-CKFpqbmw.mjs";
3
+ import { B as TaggedStruct, Qn as remove, Zn as merge, ir as toEpochMillis, mt as is, qn as fromInput, tt as decodeEffect, u as DateTimeUtcFromString } from "./Schema-MiKt5K1v.mjs";
4
+ import { S as none, _ as isSome, b as map$1, r as exists, w as some, x as match } from "./Option-DkxSnBhp.mjs";
5
+ import { i as value, n as make } from "./Redacted-RkJrG5Me.mjs";
6
+ import { o as set, r as make$1, s as update, t as get } from "./Ref-BIPOxJNH.mjs";
7
+ import { t as make$2 } from "./Semaphore-DOVJ4ZGS.mjs";
8
+ import { $ as toReadableStreamEffect, C as interruptWhen, d as ensuring$1, j as mkArrayBuffer } from "./Stream-bK9QIETN.mjs";
9
+ import { _ as mapRequestEffect, d as make$4, f as HttpClient, g as mapRequest, h as make$3, m as layerMergedContext, p as filterStatusOk, v as transformResponse } from "./cloud-client.adapter-Cy1BjDbG.mjs";
10
+ import { c as TransportError, i as HttpClientError, r as EncodeError, s as StatusCodeError } from "./HttpClientError-BUeMWx70.mjs";
11
+ import { DeployFailedError } from "./deploy/deploy-failed.errors.mjs";
12
+ import { ApiAttempt } from "./deploy/auth/api-attempt.service.mjs";
13
+ import { exchangeFailure, progressWait, retriedSignIn, signInRetries } from "./deploy/request-failure.pure.mjs";
14
+ import { signInFailure } from "./registry/operation-output.pure.mjs";
15
+ // ../../node_modules/.pnpm/effect@4.1.2_patch_hash=0e61176203bb1256381ce1c5b488d7951983ce38ff698647d3f4617383abbf07/node_modules/effect/dist/http/FetchHttpClient.js
16
+ /**
17
+ * Fetch-based implementation of the Effect HTTP client service.
18
+ *
19
+ * This module provides an `HttpClient` layer that executes requests through a
20
+ * Web Fetch API implementation. It is the transport to use in browsers, edge
21
+ * runtimes, and Node.js environments where `globalThis.fetch` is available, or
22
+ * anywhere a compatible fetch function can be supplied.
23
+ *
24
+ * Requests with an `Upgrade` header fail with `TransportError` before anything
25
+ * is sent because fetch cannot hand over the connection.
26
+ *
27
+ * @stability unstable
28
+ * @since 4.0.0
29
+ */
30
+ /**
31
+ * Context reference for the `fetch` implementation used by the fetch-based HTTP client.
32
+ *
33
+ * **Details**
34
+ *
35
+ * Defaults to `globalThis.fetch`.
36
+ *
37
+ * @stability unstable
38
+ * @category services
39
+ * @since 4.0.0
40
+ */
41
+ const Fetch = /*#__PURE__*/ Reference("effect/http/FetchHttpClient/Fetch", { defaultValue: () => globalThis.fetch });
42
+ /**
43
+ * Context reference controlling stream bodies when the runtime does not support streaming requests.
44
+ *
45
+ * **Details**
46
+ *
47
+ * Defaults to `"error"`, which fails with an `EncodeError` without sending the
48
+ * request. Set to `"buffer"` to collect the entire stream in memory and send it
49
+ * as a single body instead. Runtimes with streaming support always send the stream.
50
+ *
51
+ * **Gotchas**
52
+ *
53
+ * Buffering keeps the entire upload in memory and can exhaust memory for large
54
+ * or unbounded uploads. Support is detected once, on the first stream body sent,
55
+ * using the global `Request` implementation even when a custom `Fetch` is provided.
56
+ *
57
+ * @stability unstable
58
+ * @category services
59
+ * @since 4.0.0
60
+ */
61
+ const StreamBodyFallback = /*#__PURE__*/ Reference("effect/http/FetchHttpClient/StreamBodyFallback", { defaultValue: () => "error" });
62
+ /**
63
+ * Service that contains default fetch options for the fetch-based HTTP client.
64
+ *
65
+ * **When to use**
66
+ *
67
+ * Use to provide default credentials, cache, redirect, integrity, or other
68
+ * fetch options for outgoing HTTP requests.
69
+ *
70
+ * **Details**
71
+ *
72
+ * Request-specific method, headers, body, and abort signal are supplied by the client when a request is executed.
73
+ *
74
+ * @see {@link HttpClient.followRedirects} for following redirects in the client, which needs `{ redirect: "manual" }` here
75
+ *
76
+ * @stability unstable
77
+ * @category services
78
+ * @since 4.0.0
79
+ */
80
+ var RequestInit = class extends (/*#__PURE__*/ Service()("effect/http/FetchHttpClient/RequestInit")) {};
81
+ let supportsStreamBody;
82
+ const detectStreamBodySupport = () => {
83
+ try {
84
+ let duplexAccessed = false;
85
+ const body = new ReadableStream();
86
+ const request = new Request("https://example.com", {
87
+ method: "POST",
88
+ body,
89
+ get duplex() {
90
+ duplexAccessed = true;
91
+ return "half";
92
+ }
93
+ });
94
+ return (duplexAccessed || request.body === body) && !request.headers.has("Content-Type");
95
+ } catch {
96
+ return false;
97
+ }
98
+ };
99
+ /**
100
+ * Layer that provides an `HttpClient` implementation backed by the configured
101
+ * `Fetch` function.
102
+ *
103
+ * **When to use**
104
+ *
105
+ * Use when an Effect program should execute `HttpClient` requests through the
106
+ * platform `fetch` implementation, especially in browser, edge, or Node.js
107
+ * runtimes with `globalThis.fetch`.
108
+ *
109
+ * **Details**
110
+ *
111
+ * The layer uses the current `Fetch` reference and optional `RequestInit`
112
+ * service for each request. Request-specific method, headers, body, and abort
113
+ * signal are supplied by the client and override matching `RequestInit` fields.
114
+ *
115
+ * **Gotchas**
116
+ *
117
+ * Fetch behavior comes from the runtime's implementation, so CORS, cookies,
118
+ * redirects, abort handling, and streaming support can vary by platform.
119
+ * `fetch` follows redirects itself by default, so `HttpClient.followRedirects`
120
+ * has no effect unless `RequestInit` sets `redirect: "manual"`. Trace
121
+ * propagation is on by default, and a browser preflight fails unless the
122
+ * target allows `b3` and `traceparent`. Stream request bodies are sent as Web
123
+ * streams with `duplex: "half"` when the runtime supports it; see
124
+ * `StreamBodyFallback` for the unsupported case. Any `content-length` header is
125
+ * removed before calling `fetch`. Chrome passes the support check but rejects
126
+ * streaming uploads over HTTP/1.1, which surfaces as a `TransportError`, and
127
+ * browsers only deliver the response after the entire request body has been sent.
128
+ *
129
+ * @see {@link Fetch} for supplying the fetch implementation used by this layer
130
+ * @see {@link RequestInit} for default `RequestInit` options applied before request-specific fields
131
+ * @see {@link StreamBodyFallback} for opting into buffering on unsupported runtimes
132
+ * @see {@link HttpClient.TracerPropagationEnabled} for leaving `b3` and `traceparent` off a browser request
133
+ *
134
+ * @stability unstable
135
+ * @category layers
136
+ * @since 4.0.0
137
+ */
138
+ const layer = /*#__PURE__*/ layerMergedContext(/*#__PURE__*/ succeed(/* @__PURE__ */ make$3((request, url, signal, fiber) => {
139
+ if (request.headers.upgrade !== void 0) return fail(new HttpClientError({ reason: new TransportError({
140
+ request,
141
+ description: "fetch cannot send an HTTP upgrade request"
142
+ }) }));
143
+ const fetch = fiber.getRef(Fetch);
144
+ const options = getOrUndefined(fiber.context, RequestInit) ?? {};
145
+ let headers = options.headers ? merge(fromInput(options.headers), request.headers) : request.headers;
146
+ if (headers["content-length"]) headers = remove(headers, "content-length");
147
+ let uploadController;
148
+ const stopUpload = sync(() => uploadController?.abort());
149
+ const send = (body) => map(tryPromise({
150
+ try: () => fetch(url, {
151
+ ...options,
152
+ method: request.method,
153
+ headers,
154
+ body,
155
+ duplex: typeof ReadableStream !== "undefined" && body instanceof ReadableStream ? "half" : void 0,
156
+ signal: uploadController ? AbortSignal.any([signal, uploadController.signal]) : signal
157
+ }),
158
+ catch: (cause) => new HttpClientError({ reason: new TransportError({
159
+ request,
160
+ cause
161
+ }) })
162
+ }), (response) => {
163
+ const original = fromWeb(request, response);
164
+ if (!uploadController) return original;
165
+ if (response.body === null) uploadController.abort();
166
+ return Object.create(original, {
167
+ ...Object.fromEntries([
168
+ "arrayBuffer",
169
+ "text",
170
+ "json",
171
+ "formData",
172
+ "urlParamsBody"
173
+ ].map((key) => [key, { get: () => ensuring(original[key], stopUpload) }])),
174
+ stream: { get: () => ensuring$1(original.stream, stopUpload) }
175
+ });
176
+ }).pipe(onError(() => stopUpload));
177
+ switch (request.body._tag) {
178
+ case "Raw":
179
+ case "Uint8Array": return send(request.body.body);
180
+ case "FormData": return send(request.body.formData);
181
+ case "Stream":
182
+ supportsStreamBody ??= detectStreamBodySupport();
183
+ if (supportsStreamBody) {
184
+ uploadController = new AbortController();
185
+ const uploadSignal = AbortSignal.any([signal, uploadController.signal]);
186
+ const stopped = callback((resume) => {
187
+ const onAbort = () => resume(interrupt);
188
+ if (uploadSignal.aborted) {
189
+ onAbort();
190
+ return;
191
+ }
192
+ uploadSignal.addEventListener("abort", onAbort, { once: true });
193
+ return sync(() => uploadSignal.removeEventListener("abort", onAbort));
194
+ });
195
+ return flatMap(toReadableStreamEffect(interruptWhen(request.body.stream, stopped)), send);
196
+ }
197
+ if (fiber.getRef(StreamBodyFallback) === "error") return fail(new HttpClientError({ reason: new EncodeError({
198
+ request,
199
+ description: "Streaming request bodies are not supported in this runtime"
200
+ }) }));
201
+ return mkArrayBuffer(request.body.stream).pipe(mapError((cause) => new HttpClientError({ reason: new EncodeError({
202
+ request,
203
+ cause
204
+ }) })), flatMap(send));
205
+ }
206
+ return send(void 0);
207
+ })));
208
+ // src/deploy/cloud-client.adapter.ts
209
+ const sessionOf = fnUntraced(function* sessionOf(issued) {
210
+ const now = yield* currentTimeMillis;
211
+ const current = yield* decodeEffect(DateTimeUtcFromString)(issued.expiresAt).pipe(mapBoth({
212
+ onFailure: () => DeployFailedError.make({ message: "The API returned an invalid token expiry." }),
213
+ onSuccess: (expires) => ({
214
+ expiresAt: toEpochMillis(expires),
215
+ token: make(issued.token)
216
+ })
217
+ }));
218
+ if (current.expiresAt <= now) return yield* DeployFailedError.make({ message: "The API returned an expired token." });
219
+ return current;
220
+ });
221
+ const refreshMarginMillis = 3e4;
222
+ const oneExchange = 1;
223
+ const unauthorizedStatus = 401;
224
+ /** A refused token's expiry, so the next request exchanges the secret again, as a refresh. */
225
+ const refusedAt = 0;
226
+ const firstSuccess = 200;
227
+ const firstRedirect = 300;
228
+ const refusedStatus = (response) => response.text.pipe(orElseSucceed(() => ""), flatMap((description) => fail(new HttpClientError({ reason: new StatusCodeError({
229
+ description,
230
+ request: response.request,
231
+ response
232
+ }) }))));
233
+ const succeeded = (response) => response.status >= firstSuccess && response.status < firstRedirect;
234
+ const statusFirst = (client) => transformResponse(client, (responded) => filterOrElse(responded, succeeded, refusedStatus));
235
+ const transportOf = map(HttpClient, (http) => http.pipe(transformResponse(provideService(RequestInit, { redirect: "error" }))));
236
+ const anonymousClient = fn("anonymousClient")(function* anonymousClient(url) {
237
+ const transport = yield* transportOf;
238
+ return make$4(mapRequest(transport, prependUrl(url.href)));
239
+ });
240
+ /** The API's refusal of the bearer itself, answered before any handler runs, so sending the request again is safe. */
241
+ const isBearerRefusal = is(TaggedStruct("UnauthorizedError", {}));
242
+ const resignedWith = ({ session, token, transport }) => (response) => gen(function* resigned() {
243
+ const body = yield* option(response.json);
244
+ if (response.status !== unauthorizedStatus || !exists(body, isBearerRefusal)) return response;
245
+ yield* update(session, map$1((held) => ({
246
+ expiresAt: refusedAt,
247
+ token: held.token
248
+ })));
249
+ const bearer = yield* token;
250
+ return yield* transport.execute(bearerToken(response.request, bearer));
251
+ });
252
+ /** The token exchange within its wait; a first sign-in (`retrying`) retries on `signInRetries` while the failure may pass. */
253
+ const exchanged = (exchange, retrying) => exchange.pipe(timeout(progressWait), retry({
254
+ schedule: signInRetries,
255
+ while: (failure) => retrying && retriedSignIn(failure)
256
+ }));
257
+ const cloudClient = fn("cloudClient")(function* cloudClient({ apiKey, url }) {
258
+ const transport = yield* transportOf;
259
+ const base = mapRequest(transport, prependUrl(url.href));
260
+ const unsigned = make$4(filterStatusOk(base));
261
+ const session = yield* make$1(none());
262
+ const exchanging = yield* make$2(oneExchange);
263
+ const exchange = unsigned.tokensExchange({ payload: { secret: value(apiKey) } });
264
+ const token = gen(function* bearerToken() {
265
+ const now = yield* currentTimeMillis;
266
+ const cached = yield* get(session);
267
+ const refreshing = isSome(cached);
268
+ if (isSome(cached) && cached.value.expiresAt > now + refreshMarginMillis) return cached.value.token;
269
+ yield* match(cached, {
270
+ onNone: () => ApiAttempt.mark("initial"),
271
+ onSome: () => ApiAttempt.mark("refresh")
272
+ });
273
+ const issued = yield* mapError(exchanged(exchange, !refreshing), exchangeFailure(refreshing));
274
+ const current = yield* sessionOf(issued);
275
+ yield* set(session, some(current)).pipe(andThen(ApiAttempt.mark("request")));
276
+ return current.token;
277
+ }).pipe(exchanging.withPermits(oneExchange));
278
+ const signed = mapRequestEffect(base, (request) => map(token, (bearer) => bearerToken(request, bearer)));
279
+ const authenticated = transformResponse(signed, flatMap(resignedWith({
280
+ session,
281
+ token,
282
+ transport
283
+ })));
284
+ return {
285
+ anonymous: base,
286
+ bearer: token,
287
+ client: make$4(authenticated),
288
+ http: authenticated,
289
+ progress: make$4(authenticated.pipe(statusFirst)),
290
+ request: ApiAttempt.request,
291
+ signIn: asVoid(mapError(token, signInFailure)),
292
+ upload: filterStatusOk(transport)
293
+ };
294
+ });
295
+ var CloudClient = class extends Service()("@akterjs/cli/deploy/cloud-client.adapter/CloudClient", { make: gen(function* makeCloudClient() {
296
+ const context$1 = yield* context().pipe(map(pick(HttpClient)));
297
+ return {
298
+ anonymous: (url) => provideContext(anonymousClient(url), context$1),
299
+ connect: (options) => provideContext(cloudClient(options), context$1)
300
+ };
301
+ }) }) {
302
+ static layer = effect(this, this.make);
303
+ };
304
+ export { Fetch as n, layer as r, CloudClient as t };
@@ -0,0 +1,32 @@
1
+ import { a as withSubcommands, i as withDescription, t as make } from "../Command-Dzq9KM-h.mjs";
2
+ import { importApp } from "../archive/archive-cli.config.mjs";
3
+ import { bench, deploy, exportApp } from "../app-cli.config.mjs";
4
+ import { login, logout, signup } from "../credentials/login.config.mjs";
5
+ import { t as docs } from "../docs.config-D3zzLzLR.mjs";
6
+ import { localCommands } from "../local/environment-cli.config.mjs";
7
+ import { logCommands } from "../logs/log-follow.config.mjs";
8
+ import { actorCommands } from "../registry/actor-commands.config.mjs";
9
+ import { cloudCommands } from "../registry/cloud-commands.config.mjs";
10
+ // src/command/akter-commands.config.ts
11
+ const akterCommand = (operations) => {
12
+ const joined = {
13
+ ...actorCommands(operations),
14
+ ...logCommands
15
+ };
16
+ return make("akter").pipe(withDescription("Run Akter apps locally and operate Akter Cloud"), withSubcommands([
17
+ bench,
18
+ ...localCommands,
19
+ deploy,
20
+ exportApp,
21
+ importApp,
22
+ docs,
23
+ login,
24
+ logout,
25
+ signup,
26
+ ...cloudCommands({
27
+ joined,
28
+ operations
29
+ })
30
+ ]));
31
+ };
32
+ export { akterCommand };
@@ -0,0 +1 @@
1
+ export { t as runAkter } from "../akter-program.config-D8yb9xQP.mjs";
@@ -0,0 +1,226 @@
1
+ import { t as match } from "../Boolean-kILIvmhE.mjs";
2
+ import { a as option, i as String, r as Redacted } from "../Config-B6OND9WR.mjs";
3
+ import { At as provideContext, B as flatMap, Dt as orElseSucceed, Et as orDie, Kn as provide, M as fail, Q as fromOption, T as context, Tt as option$1, Vn as effect, W as fnUntraced, Xt as succeed, _n as when, aa as Service, dt as map, et as gen, f as cached, gn as void_, ht as mapError, tt as ignore } from "../Effect-CKFpqbmw.mjs";
4
+ import { t as FileSystem } from "../FileSystem-4pPcEb_i.mjs";
5
+ import { C as orElse, S as none, _ as isSome, a as firstSomeOf, b as map$1, d as fromUndefinedOr, f as getOrElse, h as isNone, i as filter, o as flatMap$1, w as some, x as match$1 } from "../Option-DkxSnBhp.mjs";
6
+ import { t as Path } from "../Path-Djdv_eF5.mjs";
7
+ import { i as value, n as make$1 } from "../Redacted-RkJrG5Me.mjs";
8
+ import { F as String$1, I as Struct, a as Boolean, an as optionalKey, pt as fromJsonString, st as encodeEffect, tt as decodeEffect, x as Literal } from "../Schema-MiKt5K1v.mjs";
9
+ import { s as isNonEmpty } from "../String-q5dcsVe4.mjs";
10
+ import { CliExitError } from "../terminal/cli-exit.errors.mjs";
11
+ import { Keychain } from "./keychain.adapter.mjs";
12
+ import { cloudUrl, defaultCloudUrl } from "../deploy/cloud-url.pure.mjs";
13
+ // src/credentials/credentials.service.ts
14
+ const LoginFile = Struct({
15
+ keychain: Boolean,
16
+ kind: Literal("apiKey"),
17
+ organization: optionalKey(String$1),
18
+ secret: optionalKey(String$1),
19
+ url: String$1
20
+ });
21
+ const LoginJson = fromJsonString(LoginFile);
22
+ const ownerOnlyFile = 384;
23
+ const ownerOnlyFolder = 448;
24
+ const fileName = "login.json";
25
+ const nonEmpty = (name) => option(String(name)).pipe(map(filter(isNonEmpty)), orElseSucceed(() => none()));
26
+ const apiKeyFromEnvironment = option(Redacted("AKTER_API_KEY")).pipe(map(filter((key) => isNonEmpty(value(key)))), orElseSucceed(() => none()));
27
+ const failure = (code, reason) => (message) => CliExitError.make({
28
+ code,
29
+ message,
30
+ reason
31
+ });
32
+ const signedOut = () => failure(CliExitError.codes.authentication, "NotSignedIn")("Not signed in. Set AKTER_API_KEY to an API key or agent key, or save one with: akter login --with-api-key < key.txt");
33
+ const unreadable = (file) => () => failure(CliExitError.codes.authentication, "LoginUnreadable")(`The saved login in ${file} can't be read. Run akter logout, then sign in again.`);
34
+ const originOf = (url) => cloudUrl(url).pipe(map((parsed) => some(parsed.origin)), orElseSucceed(() => none()));
35
+ const loginFile = fnUntraced(function* loginFile() {
36
+ const path = yield* Path;
37
+ const xdg = yield* nonEmpty("XDG_CONFIG_HOME");
38
+ const home = yield* nonEmpty("HOME");
39
+ const folder = orElse(xdg, () => map$1(home, (homeFolder) => path.join(homeFolder, ".config")));
40
+ return yield* match$1(folder, {
41
+ onNone: () => fail(failure(CliExitError.codes.authentication, "NoConfigFolder")("Neither XDG_CONFIG_HOME nor HOME is set, so there is no folder to keep a login in.")),
42
+ onSome: (found) => succeed(path.join(found, "akter", fileName))
43
+ });
44
+ });
45
+ const readLogin = fnUntraced(function* readLogin() {
46
+ const fileSystem = yield* FileSystem;
47
+ const located = yield* option$1(loginFile());
48
+ if (!(yield* match$1(located, {
49
+ onNone: () => succeed(false),
50
+ onSome: (file) => fileSystem.exists(file).pipe(orElseSucceed(() => false))
51
+ })) || isNone(located)) return none();
52
+ const file = located.value;
53
+ const text = yield* fileSystem.readFileString(file).pipe(mapError(unreadable(file)));
54
+ const login = yield* decodeEffect(LoginJson)(text).pipe(mapError(unreadable(file)));
55
+ return some(login);
56
+ });
57
+ const savedSecret = fnUntraced(function* savedSecret(saved) {
58
+ const login = yield* saved;
59
+ if (isNone(login)) return none();
60
+ if (!login.value.keychain) {
61
+ const secret = fromUndefinedOr(login.value.secret);
62
+ return map$1(secret, (value) => ({
63
+ secret: make$1(value),
64
+ source: "file"
65
+ }));
66
+ }
67
+ const keychain = yield* Keychain;
68
+ const origin = yield* originOf(login.value.url);
69
+ const found = yield* match$1(origin, {
70
+ onNone: () => succeed(none()),
71
+ onSome: keychain.find
72
+ });
73
+ return map$1(found, (value) => ({
74
+ secret: make$1(value),
75
+ source: "keychain"
76
+ }));
77
+ });
78
+ const elsewhere = (savedOrigin, url) => failure(CliExitError.codes.authentication, "LoginForOtherUrl")(`The saved login is for ${savedOrigin}, not ${url.origin}, so akter won't send its key there. Sign in for that URL with akter login --url ${url.origin}, or set AKTER_API_KEY.`);
79
+ /**
80
+ * A saved login's secret goes only to the origin it was saved for, so a --url or AKTER_CLOUD_URL that names another
81
+ * server, typed by mistake or planted in an agent's prompt, can't receive it.
82
+ */
83
+ const boundTo = fnUntraced(function* boundTo(saved, url) {
84
+ const login = yield* saved;
85
+ const file = yield* fromOption(login).pipe(mapError(signedOut));
86
+ const savedOrigin = yield* originOf(file.url);
87
+ if (isSome(savedOrigin) && savedOrigin.value !== url.origin) return yield* elsewhere(savedOrigin.value, url);
88
+ return yield* void_;
89
+ });
90
+ const otherThanEnvironment = (environmentOrigin, url) => failure(CliExitError.codes.usage, "ApiKeyForOtherUrl")(`AKTER_API_KEY is for AKTER_CLOUD_URL, ${environmentOrigin}, not ${url.origin}, so akter won't send it there. Drop --url, or set AKTER_CLOUD_URL to ${url.origin}.`);
91
+ /**
92
+ * AKTER_API_KEY goes only to AKTER_CLOUD_URL's origin when that is set, so a --url planted in an agent's prompt can't
93
+ * collect the key a script or agent runs with. With no AKTER_CLOUD_URL, the caller chose both the key and the URL.
94
+ */
95
+ const environmentBound = fnUntraced(function* environmentBound(url) {
96
+ const configured = yield* nonEmpty("AKTER_CLOUD_URL");
97
+ const origin = yield* flatMap(fromOption(configured), originOf).pipe(orElseSucceed(() => none()));
98
+ if (isSome(origin) && origin.value !== url.origin) return yield* otherThanEnvironment(origin.value, url);
99
+ return yield* void_;
100
+ });
101
+ /** AKTER_API_KEY first, so a script or an agent key wins over a person's saved login, which is bound to its origin. */
102
+ const credentialOf = fnUntraced(function* credentialOf(saved, url) {
103
+ const fromEnvironment = yield* apiKeyFromEnvironment;
104
+ if (isSome(fromEnvironment)) {
105
+ yield* environmentBound(url);
106
+ return {
107
+ secret: fromEnvironment.value,
108
+ source: "AKTER_API_KEY"
109
+ };
110
+ }
111
+ const stored = yield* savedSecret(saved);
112
+ const credential = yield* fromOption(stored).pipe(mapError(signedOut));
113
+ yield* boundTo(saved, url);
114
+ return credential;
115
+ });
116
+ const urlOf = fnUntraced(function* urlOf(saved, flag) {
117
+ const login = yield* saved;
118
+ const fromEnvironment = yield* nonEmpty("AKTER_CLOUD_URL");
119
+ const chosen = firstSomeOf([
120
+ flag,
121
+ fromEnvironment,
122
+ map$1(login, (file) => file.url)
123
+ ]);
124
+ return yield* cloudUrl(getOrElse(chosen, () => defaultCloudUrl)).pipe(mapError((invalid) => failure(CliExitError.codes.usage, "InvalidUrl")(invalid.message)));
125
+ });
126
+ const organizationOf = fnUntraced(function* organizationOf(saved, flag) {
127
+ const login = yield* saved;
128
+ const fromEnvironment = yield* nonEmpty("AKTER_ORGANIZATION");
129
+ const fromLogin = flatMap$1(login, (file) => fromUndefinedOr(file.organization));
130
+ return firstSomeOf([
131
+ flag,
132
+ fromEnvironment,
133
+ fromLogin
134
+ ]);
135
+ });
136
+ const targetOf = fnUntraced(function* targetOf(saved, flags) {
137
+ const url = yield* urlOf(saved, flags.url);
138
+ const organization = yield* organizationOf(saved, flags.organization);
139
+ const { secret } = yield* credentialOf(saved, url);
140
+ const missing = failure(CliExitError.codes.usage, "MissingOrganization")("Pass --organization or set AKTER_ORGANIZATION.");
141
+ return {
142
+ apiKey: secret,
143
+ organization: yield* fromOption(organization).pipe(mapError(() => missing)),
144
+ url: url.href
145
+ };
146
+ });
147
+ const recordOf = (login, inKeychain) => {
148
+ const record = {
149
+ keychain: inKeychain,
150
+ kind: "apiKey",
151
+ url: login.url.href
152
+ };
153
+ const named = match$1(login.organization, {
154
+ onNone: () => record,
155
+ onSome: (organization) => ({
156
+ ...record,
157
+ organization
158
+ })
159
+ });
160
+ if (inKeychain) return named;
161
+ return {
162
+ ...named,
163
+ secret: value(login.secret)
164
+ };
165
+ };
166
+ const writeOwnerOnly = fnUntraced(function* writeOwnerOnly(file, text) {
167
+ const fileSystem = yield* FileSystem;
168
+ const path = yield* Path;
169
+ const staged = `${file}.tmp`;
170
+ yield* fileSystem.makeDirectory(path.dirname(file), {
171
+ mode: ownerOnlyFolder,
172
+ recursive: true
173
+ });
174
+ yield* fileSystem.chmod(path.dirname(file), ownerOnlyFolder);
175
+ yield* fileSystem.writeFileString(staged, text, { mode: ownerOnlyFile });
176
+ yield* fileSystem.chmod(staged, ownerOnlyFile);
177
+ return yield* fileSystem.rename(staged, file);
178
+ });
179
+ const save = fnUntraced(function* save(login) {
180
+ const file = yield* loginFile();
181
+ const inKeychain = yield* (yield* Keychain).store(login.url.origin, value(login.secret));
182
+ const text = yield* encodeEffect(LoginJson)(recordOf(login, inKeychain)).pipe(orDie);
183
+ yield* writeOwnerOnly(file, `${text}\n`).pipe(mapError((cause) => failure(CliExitError.codes.failed, "LoginNotSaved")(`Could not save the login in ${file}: ${cause.message}`)));
184
+ return {
185
+ file,
186
+ store: match(inKeychain, {
187
+ onFalse: () => "file",
188
+ onTrue: () => "keychain"
189
+ })
190
+ };
191
+ });
192
+ const clear = fnUntraced(function* clear() {
193
+ const fileSystem = yield* FileSystem;
194
+ const keychain = yield* Keychain;
195
+ const login = yield* readLogin().pipe(orElseSucceed(() => none()));
196
+ const origin = yield* match$1(login, {
197
+ onNone: () => succeed(none()),
198
+ onSome: (record) => originOf(record.url)
199
+ });
200
+ const file = yield* loginFile();
201
+ const exists = yield* fileSystem.exists(file).pipe(orElseSucceed(() => false));
202
+ yield* match$1(origin, {
203
+ onNone: () => void_,
204
+ onSome: keychain.remove
205
+ });
206
+ yield* when(ignore(fileSystem.remove(file)), succeed(exists));
207
+ return exists;
208
+ });
209
+ const make = gen(function* makeCredentials() {
210
+ const context$1 = yield* context();
211
+ const provided = (effect) => provideContext(effect, context$1);
212
+ const saved = yield* readLogin().pipe(provided, cached);
213
+ return {
214
+ clear: provided(clear()),
215
+ /** The secret to sign in at `url` with; a saved login's secret only for the origin it was saved for. */
216
+ credentialFor: (url) => provided(credentialOf(saved, url)),
217
+ organization: (flag) => provided(organizationOf(saved, flag)),
218
+ save: (login) => provided(save(login)),
219
+ target: (flags) => provided(targetOf(saved, flags)),
220
+ url: (flag) => provided(urlOf(saved, flag))
221
+ };
222
+ });
223
+ var Credentials = class extends Service()("@akterjs/cli/credentials/credentials.service/Credentials", { make }) {
224
+ static layer = effect(this, this.make).pipe(provide(Keychain.layer));
225
+ };
226
+ export { Credentials };
@@ -0,0 +1,92 @@
1
+ import { j as map } from "../Array-B4kuTQeW.mjs";
2
+ import { Dt as orElseSucceed, Gt as scoped, K as forEach, Pt as reduce, Vn as effect, W as fnUntraced, Xt as succeed, a as all, aa as Service, dt as map$1, et as gen, in as timeout, jt as provideService } from "../Effect-CKFpqbmw.mjs";
3
+ import { S as none, _ as isSome, b as map$2, i as filter, w as some } from "../Option-DkxSnBhp.mjs";
4
+ import { M as mkString, T as make, V as runDrain, i as decodeText } from "../Stream-bK9QIETN.mjs";
5
+ import { f as replaceAll, s as isNonEmpty, y as trimEnd } from "../String-q5dcsVe4.mjs";
6
+ import { a as ChildProcessSpawner, n as make$1 } from "../ChildProcess-BCIwxPGc.mjs";
7
+ // src/credentials/keychain.adapter.ts
8
+ const service = "akter-cli";
9
+ const label = "Akter CLI";
10
+ const succeeded = 0;
11
+ const streams = 3;
12
+ const escapeQuote = replaceAll("\"", String.raw`\"`);
13
+ const escapeBackslash = replaceAll("\\", String.raw`\\`);
14
+ const quoted = (value) => `"${escapeQuote(escapeBackslash(value))}"`;
15
+ const encoder = new globalThis.TextEncoder();
16
+ const withInput = (command, args, text) => make$1(command, args, { stdin: make(encoder.encode(text)) });
17
+ const backends = [{
18
+ lookup: (account) => make$1("security", [
19
+ "find-generic-password",
20
+ "-s",
21
+ service,
22
+ "-a",
23
+ account,
24
+ "-w"
25
+ ]),
26
+ remove: (account) => make$1("security", [
27
+ "delete-generic-password",
28
+ "-s",
29
+ service,
30
+ "-a",
31
+ account
32
+ ]),
33
+ store: (account, secret) => withInput("security", ["-i"], `add-generic-password -U -s ${service} -a ${quoted(account)} -l ${quoted(label)} -w ${quoted(secret)}\n`)
34
+ }, {
35
+ lookup: (account) => make$1("secret-tool", [
36
+ "lookup",
37
+ "service",
38
+ service,
39
+ "account",
40
+ account
41
+ ]),
42
+ remove: (account) => make$1("secret-tool", [
43
+ "clear",
44
+ "service",
45
+ service,
46
+ "account",
47
+ account
48
+ ]),
49
+ store: (account, secret) => withInput("secret-tool", [
50
+ "store",
51
+ "--label",
52
+ label,
53
+ "service",
54
+ service,
55
+ "account",
56
+ account
57
+ ], secret)
58
+ }];
59
+ const run = fnUntraced(function* run(command) {
60
+ const handle = yield* (yield* ChildProcessSpawner).spawn(command);
61
+ const output = mkString(decodeText(handle.stdout));
62
+ const [text, code] = yield* all([
63
+ output,
64
+ handle.exitCode,
65
+ runDrain(handle.stderr)
66
+ ], { concurrency: streams });
67
+ return filter(some(text), () => code === succeeded);
68
+ }, scoped);
69
+ const attempt = (command) => run(command).pipe(timeout("10 seconds"), orElseSucceed(() => none()));
70
+ const firstFound = (account) => (found, backend) => {
71
+ if (isSome(found)) return succeed(found);
72
+ return map$1(attempt(backend.lookup(account)), (output) => filter(map$2(output, (text) => trimEnd(text)), (text) => isNonEmpty(text)));
73
+ };
74
+ const firstStored = (account, secret) => (stored, backend) => {
75
+ if (stored) return succeed(true);
76
+ return map$1(attempt(backend.store(account, secret)), isSome);
77
+ };
78
+ const find = (account) => reduce(backends, () => none(), firstFound(account));
79
+ const store = (account, secret) => reduce(backends, () => false, firstStored(account, secret));
80
+ const remove = (account) => forEach(map(backends, (backend) => backend.remove(account)), (command) => attempt(command), { discard: true });
81
+ var Keychain = class extends Service()("@akterjs/cli/credentials/keychain.adapter/Keychain", { make: gen(function* makeKeychain() {
82
+ const spawner = yield* ChildProcessSpawner;
83
+ const provide = provideService(ChildProcessSpawner, spawner);
84
+ return {
85
+ find: (account) => provide(find(account)),
86
+ remove: (account) => provide(remove(account)),
87
+ store: (account, secret) => provide(store(account, secret))
88
+ };
89
+ }) }) {
90
+ static layer = effect(this, this.make);
91
+ };
92
+ export { Keychain };