@codeworksh/cli 0.0.1-dev.20260918042316 → 0.0.1-dev.20260922183613

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 (76) hide show
  1. package/README.md +94 -0
  2. package/add--i7mPR73.mjs +81 -0
  3. package/add--i7mPR73.mjs.map +1 -0
  4. package/{catalog-Cv1zoZ5S.mjs → catalog-DJxy_lne.mjs} +2 -2
  5. package/{catalog-Cv1zoZ5S.mjs.map → catalog-DJxy_lne.mjs.map} +1 -1
  6. package/check-Dq2WOgap.mjs +47 -0
  7. package/check-Dq2WOgap.mjs.map +1 -0
  8. package/commonjs-BDiLMfFF.mjs +160 -0
  9. package/commonjs-BDiLMfFF.mjs.map +1 -0
  10. package/definitions-Dfwyyv_K.mjs +3348 -0
  11. package/definitions-Dfwyyv_K.mjs.map +1 -0
  12. package/entries-Gfsq7U4P.mjs +48 -0
  13. package/entries-Gfsq7U4P.mjs.map +1 -0
  14. package/{error-zJGKAF4a.mjs → error-BrbT9_4g.mjs} +2470 -1853
  15. package/error-BrbT9_4g.mjs.map +1 -0
  16. package/{generate-DdscbX3w.mjs → generate-DrGYpD3l.mjs} +3 -3
  17. package/{generate-DdscbX3w.mjs.map → generate-DrGYpD3l.mjs.map} +1 -1
  18. package/{harness-BBTdWdhx.mjs → harness-Bgvrr0ZB.mjs} +2 -2
  19. package/{harness-BBTdWdhx.mjs.map → harness-Bgvrr0ZB.mjs.map} +1 -1
  20. package/index.d.mts +6 -1
  21. package/index.mjs +22 -6
  22. package/index.mjs.map +1 -1
  23. package/install-DoO4D3fk.mjs +53 -0
  24. package/install-DoO4D3fk.mjs.map +1 -0
  25. package/lib-BkWR53Bz.mjs +37659 -0
  26. package/lib-BkWR53Bz.mjs.map +1 -0
  27. package/lib-CyynPpjH.mjs +846 -0
  28. package/lib-CyynPpjH.mjs.map +1 -0
  29. package/lib-Dw7R2suc.mjs +16078 -0
  30. package/lib-Dw7R2suc.mjs.map +1 -0
  31. package/lib-JQygPqgO.mjs +10868 -0
  32. package/lib-JQygPqgO.mjs.map +1 -0
  33. package/link-B1zfETsS.mjs +40 -0
  34. package/link-B1zfETsS.mjs.map +1 -0
  35. package/list-3OR68HpY.mjs +76 -0
  36. package/list-3OR68HpY.mjs.map +1 -0
  37. package/{list-C6BrwuV-.mjs → list-CfPfmfe0.mjs} +4 -4
  38. package/{list-C6BrwuV-.mjs.map → list-CfPfmfe0.mjs.map} +1 -1
  39. package/login-GFV3Z9f2.mjs +45 -0
  40. package/login-GFV3Z9f2.mjs.map +1 -0
  41. package/logout-D17TQUfo.mjs +29 -0
  42. package/logout-D17TQUfo.mjs.map +1 -0
  43. package/p-map-C2WSUhyP.mjs +116 -0
  44. package/p-map-C2WSUhyP.mjs.map +1 -0
  45. package/package.json +12 -9
  46. package/{providers-fO-GJuXn.mjs → providers-DLXs9aWZ.mjs} +4 -4
  47. package/{providers-fO-GJuXn.mjs.map → providers-DLXs9aWZ.mjs.map} +1 -1
  48. package/refresh-BTNuP1zL.mjs +35 -0
  49. package/refresh-BTNuP1zL.mjs.map +1 -0
  50. package/reload-BzwsCBQq.mjs +26 -0
  51. package/reload-BzwsCBQq.mjs.map +1 -0
  52. package/remove-DgB3l5JL.mjs +36 -0
  53. package/remove-DgB3l5JL.mjs.map +1 -0
  54. package/{run-CgPOJ-wJ.mjs → run-BVo4XsL6.mjs} +11 -8
  55. package/run-BVo4XsL6.mjs.map +1 -0
  56. package/runtime-Dl621FOp.mjs +345 -0
  57. package/runtime-Dl621FOp.mjs.map +1 -0
  58. package/{serve-Du6luqoi.mjs → serve-Dpep18-A.mjs} +93 -17
  59. package/serve-Dpep18-A.mjs.map +1 -0
  60. package/settings-C52IrA72.mjs +215 -0
  61. package/settings-C52IrA72.mjs.map +1 -0
  62. package/shared-Uwjcofff.mjs +115 -0
  63. package/shared-Uwjcofff.mjs.map +1 -0
  64. package/status-DPYq2Sx4.mjs +31 -0
  65. package/status-DPYq2Sx4.mjs.map +1 -0
  66. package/type-defs-BZxrtlVD.mjs +538 -0
  67. package/type-defs-BZxrtlVD.mjs.map +1 -0
  68. package/update-CJrBbHwq.mjs +44 -0
  69. package/update-CJrBbHwq.mjs.map +1 -0
  70. package/valid-BWgW_TUl.mjs +354 -0
  71. package/valid-BWgW_TUl.mjs.map +1 -0
  72. package/error-zJGKAF4a.mjs.map +0 -1
  73. package/run-CgPOJ-wJ.mjs.map +0 -1
  74. package/runtime-DuL-GGrx.mjs +0 -163
  75. package/runtime-DuL-GGrx.mjs.map +0 -1
  76. package/serve-Du6luqoi.mjs.map +0 -1
@@ -1,13 +1,16 @@
1
- import { i as Cmd, t as handler } from "./runtime-DuL-GGrx.mjs";
2
- import { A as Service$2, C as create$1, D as resolve, E as register, F as Service$4, H as Prompt, I as SessionNotFoundError, L as Service$1, O as stop, R as SubscriptionOverflowError, S as relink, T as list, W as ID, _ as Core, b as create, g as Connected, h as Api, j as Service$3, k as layer$3, r as renderError, u as writeError, v as encode, w as drivers, x as get, y as attach } from "./error-zJGKAF4a.mjs";
3
- import { t as harnessOptions } from "./harness-BBTdWdhx.mjs";
1
+ import { $ as Service$5, A as resolve, C as get, D as drivers, E as create$1, M as layer$4, N as Service$7, O as list, Q as Service$3, S as create, T as relink, _ as OAuthError, b as encode, ct as SubscriptionOverflowError, d as writeError, g as Api, ht as schema_exports, i as renderError, it as SessionNotFoundError, j as stop, k as register, pt as Prompt, rt as Service$6, st as Service$2, v as Connected, vt as Service$4, w as link, x as attach, y as Core, yt as authFile } from "./error-BrbT9_4g.mjs";
2
+ import { i as Cmd, t as handler } from "./runtime-Dl621FOp.mjs";
3
+ import { t as harnessOptions } from "./harness-Bgvrr0ZB.mjs";
4
4
  import { Cause, Context, Effect, Layer, Option, Queue, Stream } from "effect";
5
+ import { summarizeGitHubCopilot, summarizeOpenAICodex } from "@codeworksh/aikit/oauth/summary";
5
6
  import { NodeHttpServer } from "@effect/platform-node";
7
+ import { GitHubCopilotOAuthClient, JsonGitHubCopilotAuthStorage } from "@codeworksh/aikit/oauth/github/copilot";
8
+ import { JsonOpenAICodexAuthStorage, OpenAICodexOAuthClient } from "@codeworksh/aikit/oauth/openai/codex";
6
9
  import { RpcSerialization, RpcServer } from "effect/unstable/rpc";
7
10
  import { HttpRouter, HttpServer } from "effect/unstable/http";
8
11
  import * as NetAddress from "effect/unstable/net/NetAddress";
9
12
  import { createServer } from "node:http";
10
- var Service = class extends Context.Service()("@codeworksh/cli/server/feed/Service") {};
13
+ var Service$1 = class extends Context.Service()("@codeworksh/cli/server/feed/Service") {};
11
14
  /**
12
15
  * Fan-out for `event.subscribe`. One listener on the event bus encodes each
13
16
  * event once and hands the same envelope to every client, rather than every
@@ -47,14 +50,69 @@ const make = Effect.fn("EventFeed.make")(function* (listen, options) {
47
50
  }));
48
51
  const unsubscribe = yield* listen(publish);
49
52
  yield* Effect.addFinalizer(() => unsubscribe);
50
- return Service.of({ subscribe: Effect.acquireRelease(Queue.dropping(capacity).pipe(Effect.tap((queue) => Effect.sync(() => subscribers.add(queue)))), (queue) => Effect.sync(() => subscribers.delete(queue)).pipe(Effect.andThen(Queue.shutdown(queue)))).pipe(Effect.map(Stream.fromQueue)) });
53
+ return Service$1.of({ subscribe: Effect.acquireRelease(Queue.dropping(capacity).pipe(Effect.tap((queue) => Effect.sync(() => subscribers.add(queue)))), (queue) => Effect.sync(() => subscribers.delete(queue)).pipe(Effect.andThen(Queue.shutdown(queue)))).pipe(Effect.map(Stream.fromQueue)) });
51
54
  });
52
- const layer$2 = Layer.effect(Service, Effect.gen(function* () {
53
- const events = yield* Service$1;
54
- const registered = yield* Service$2;
55
+ const layer$3 = Layer.effect(Service$1, Effect.gen(function* () {
56
+ const events = yield* Service$2;
57
+ const registered = yield* Service$3;
55
58
  return yield* make(events.listen, { registry: registered.get });
56
59
  }));
57
60
  //#endregion
61
+ //#region src/server/oauth.ts
62
+ /**
63
+ * Server-side OAuth credential storage.
64
+ *
65
+ * The CLI runs the interactive part of every login locally — a browser
66
+ * callback or a device code cannot travel over RPC — and sends the resulting
67
+ * credentials here, so this service only ever reads, writes, refreshes and
68
+ * clears the server home's `auth.json`.
69
+ */
70
+ var Service = class extends Context.Service()("@codeworksh/cli/server/oauth/Service") {};
71
+ const message = (cause) => cause instanceof Error && cause.message.trim().length > 0 ? cause.message : String(cause);
72
+ /**
73
+ * Re-exported so the server, the CLI and aikit all describe a login the same
74
+ * way; `Contract.OAuthInfo` is the wire shape of {@link OAuthSummary}.
75
+ */
76
+ const codexInfo = summarizeOpenAICodex;
77
+ const copilotInfo = summarizeGitHubCopilot;
78
+ const layer$2 = (options = {}) => Layer.effect(Service, Effect.gen(function* () {
79
+ const global = yield* Service$4;
80
+ const authFile$1 = authFile(options.home === void 0 ? void 0 : global.home);
81
+ const file = authFile$1 === void 0 ? {} : { path: authFile$1 };
82
+ const codex = new OpenAICodexOAuthClient({ storage: new JsonOpenAICodexAuthStorage(file) });
83
+ const copilot = new GitHubCopilotOAuthClient({ storage: new JsonGitHubCopilotAuthStorage(file) });
84
+ const attempt = (run) => Effect.tryPromise({
85
+ try: run,
86
+ catch: (cause) => new OAuthError({ message: message(cause) })
87
+ });
88
+ const optional = (run, info) => attempt(run).pipe(Effect.map((value) => value === void 0 ? null : info(value)));
89
+ return Service.of({
90
+ save: (wire) => {
91
+ if (wire.provider === "openai-codex") {
92
+ const credentials = {
93
+ access: wire.access,
94
+ refresh: wire.refresh,
95
+ expires: wire.expires,
96
+ accountId: wire.accountId
97
+ };
98
+ return attempt(() => codex.storage.set(credentials)).pipe(Effect.as(codexInfo(credentials)));
99
+ }
100
+ const credentials = {
101
+ access: wire.access,
102
+ refresh: wire.refresh,
103
+ expires: wire.expires,
104
+ ...wire.enterpriseUrl === void 0 ? {} : { enterpriseUrl: wire.enterpriseUrl },
105
+ ...wire.apiEndpoint === void 0 ? {} : { apiEndpoint: wire.apiEndpoint },
106
+ ...wire.availableModelIds === void 0 ? {} : { availableModelIds: [...wire.availableModelIds] }
107
+ };
108
+ return attempt(() => copilot.storage.set(credentials)).pipe(Effect.as(copilotInfo(credentials)));
109
+ },
110
+ status: (provider) => provider === "openai-codex" ? optional(() => codex.storage.get(), codexInfo) : optional(() => copilot.storage.get(), copilotInfo),
111
+ refresh: (provider) => provider === "openai-codex" ? optional(() => codex.getCredentials(), codexInfo) : optional(() => copilot.getCredentials(), copilotInfo),
112
+ logout: (provider) => attempt(() => provider === "openai-codex" ? codex.logout() : copilot.logout())
113
+ });
114
+ }));
115
+ //#endregion
58
116
  //#region src/server/handlers.ts
59
117
  const toSandboxInfo = (info) => ({
60
118
  id: info.id,
@@ -72,6 +130,7 @@ const toSessionInfo = (info) => ({
72
130
  id: info.id,
73
131
  title: info.title,
74
132
  directory: info.directory,
133
+ ...info.hostDir === void 0 ? {} : { hostDir: info.hostDir },
75
134
  ...info.sandbox === void 0 ? {} : { sandbox: toSandboxInfo(info.sandbox) }
76
135
  });
77
136
  const runtimeInput = (runtime) => ({
@@ -85,21 +144,31 @@ const sessionInfo = Effect.fn("Server.sessionInfo")(function* (sessionId) {
85
144
  });
86
145
  const releaseOnFailure = (selection) => selection?.created && selection.info !== void 0 ? stop(selection.info.id).pipe(Effect.catchCause(Effect.logError)) : Effect.void;
87
146
  const layer$1 = Api.toLayer(Effect.gen(function* () {
88
- const control = yield* Service$3;
89
- const feed = yield* Service;
90
- const sessions = yield* Service$4;
147
+ const control = yield* Service$5;
148
+ const feed = yield* Service$1;
149
+ const sessions = yield* Service$6;
150
+ const state = yield* Service$7;
151
+ const oauth = yield* Service;
91
152
  return Api.of({
92
- "session.create": Effect.fnUntraced(function* ({ title, directory, sandbox, runtime }) {
153
+ "auth.save": ({ credentials }) => oauth.save(credentials),
154
+ "auth.status": ({ provider }) => oauth.status(provider),
155
+ "auth.refresh": ({ provider }) => oauth.refresh(provider),
156
+ "auth.logout": ({ provider }) => oauth.logout(provider),
157
+ "session.create": Effect.fnUntraced(function* ({ title, directory, hostDir, sandbox, runtime }) {
93
158
  const selection = sandbox === void 0 ? void 0 : yield* resolve(sandbox);
94
159
  const selected = selection?.info;
95
160
  const handle = yield* create({
96
161
  ...title === void 0 ? {} : { title },
97
162
  ...directory === void 0 ? {} : { directory },
163
+ ...hostDir === void 0 ? {} : { hostDir },
98
164
  ...selected === void 0 ? {} : { sandbox: selected },
99
165
  ...runtimeInput(runtime)
100
166
  }).pipe(Effect.onError(() => releaseOnFailure(selection)));
101
167
  return toSessionInfo(yield* handle.info);
102
168
  }),
169
+ "plugin.reload": Effect.fnUntraced(function* () {
170
+ return yield* state.reload;
171
+ }),
103
172
  "session.list": Effect.fnUntraced(function* () {
104
173
  const rows = yield* sessions.list();
105
174
  return yield* Effect.forEach(rows, (row) => sessionInfo(row.id).pipe(Effect.orDie));
@@ -114,6 +183,13 @@ const layer$1 = Api.toLayer(Effect.gen(function* () {
114
183
  "session.info": Effect.fnUntraced(function* ({ sessionId }) {
115
184
  return yield* sessionInfo(sessionId);
116
185
  }),
186
+ "session.link": Effect.fnUntraced(function* ({ sessionId, hostDir }) {
187
+ const handle = yield* link({
188
+ sessionId,
189
+ hostDir: hostDir ?? null
190
+ });
191
+ return toSessionInfo(yield* handle.info);
192
+ }),
117
193
  "session.relink": Effect.fnUntraced(function* ({ sessionId, sandbox, directory }) {
118
194
  const selection = sandbox === void 0 ? void 0 : yield* resolve(sandbox);
119
195
  const selected = selection?.info;
@@ -164,7 +240,7 @@ const layer$1 = Api.toLayer(Effect.gen(function* () {
164
240
  yield* stop(sandboxId);
165
241
  }),
166
242
  "event.subscribe": () => Stream.unwrap(feed.subscribe.pipe(Effect.map((events) => Stream.make({
167
- id: ID.create(),
243
+ id: schema_exports.EventSchema.ID.create(),
168
244
  type: Connected.type,
169
245
  data: {}
170
246
  }).pipe(Stream.concat(events)))))
@@ -174,7 +250,7 @@ const layer$1 = Api.toLayer(Effect.gen(function* () {
174
250
  //#region src/server/server.ts
175
251
  const WsProtocol = RpcServer.layerProtocolWebsocket({ path: "/rpc" }).pipe(Layer.provide(HttpRouter.layer));
176
252
  const managedShutdown = Layer.unwrap(Effect.gen(function* () {
177
- const control = yield* Service$3;
253
+ const control = yield* Service$5;
178
254
  yield* Effect.addFinalizer(() => Effect.gen(function* () {
179
255
  yield* Effect.forEach(yield* control.active, (id) => control.interrupt(id, {
180
256
  reason: "shutdown",
@@ -197,10 +273,10 @@ const listenLog = Layer.unwrap(Effect.gen(function* () {
197
273
  return Layer.empty;
198
274
  }));
199
275
  const layer = (options) => {
200
- return RpcServer.layer(Api).pipe(Layer.provide(layer$1.pipe(Layer.provide(layer$2)))).pipe(Layer.provideMerge(WsProtocol), Layer.provide(HttpRouter.serve(WsProtocol, { disableListenLog: true })), Layer.provideMerge(managedShutdown)).pipe(Layer.provideMerge(listenLog), Layer.provideMerge(NodeHttpServer.layer(() => createServer(), {
276
+ return RpcServer.layer(Api).pipe(Layer.provide(layer$1.pipe(Layer.provide(layer$3)))).pipe(Layer.provideMerge(WsProtocol), Layer.provide(HttpRouter.serve(WsProtocol, { disableListenLog: true })), Layer.provideMerge(managedShutdown), Layer.provide(layer$2(options.harness.home === void 0 ? {} : { home: options.harness.home }))).pipe(Layer.provideMerge(listenLog), Layer.provideMerge(NodeHttpServer.layer(createServer, {
201
277
  host: options.host,
202
278
  port: options.port
203
- })), Layer.provide(RpcSerialization.layerNdjson), Layer.provide(layer$3(options.harness)));
279
+ })), Layer.provide(RpcSerialization.layerNdjson), Layer.provide(layer$4(options.harness)));
204
280
  };
205
281
  //#endregion
206
282
  //#region src/cli/cmd/handlers/serve.ts
@@ -217,4 +293,4 @@ var serve_default = handler(Cmd.commands.serve, Effect.fn("CLI.serve")(function*
217
293
  //#endregion
218
294
  export { serve_default as default };
219
295
 
220
- //# sourceMappingURL=serve-Du6luqoi.mjs.map
296
+ //# sourceMappingURL=serve-Dpep18-A.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve-Dpep18-A.mjs","names":["Service","Envelope.Core","Envelope.encode","Event.SubscriptionOverflowError","layer","Event.Service","EventRegistry.Service","layer","Global.Service","authFile","Global.authFile","Contract.OAuthError","Session.get","SessionStore.SessionNotFoundError","Sandbox.stop","layer","Control.Service","EventFeed.Service","SessionStore.Service","State.Service","OAuth.Service","Sandbox.resolve","Session.create","Session.attach","Session.link","Session.relink","Sandbox.drivers","Sandbox.list","Sandbox.create","Sandbox.register","EventSchema","Control.Service","Sandbox.list","Sandbox.stop","Contract.Api","EventFeed.layer","OAuth.layer","Harness.layer","Runtime.handler","Server.layer"],"sources":["../../src/server/feed.ts","../../src/server/oauth.ts","../../src/server/handlers.ts","../../src/server/server.ts","../../src/cli/cmd/handlers/serve.ts"],"sourcesContent":["import { Event, EventRegistry, EventSchema } from \"@codeworksh/harness/effect\";\nimport { Cause, Context, Effect, Layer, Queue, Scope, Stream } from \"effect\";\nimport { EncodingError, type EventEnvelope, Envelope } from \"./envelope.ts\";\n\nexport const SubscriberCapacity = 4096;\n\nexport type Error = Event.SubscriptionOverflowError | EncodingError;\n\nexport interface Interface {\n\t/** A live envelope stream for one client; releasing the scope unregisters it. */\n\treadonly subscribe: Effect.Effect<Stream.Stream<EventEnvelope, Error>, never, Scope.Scope>;\n}\n\nexport class Service extends Context.Service<Service, Interface>()(\"@codeworksh/cli/server/feed/Service\") {}\n\n/**\n * Fan-out for `event.subscribe`. One listener on the event bus encodes each\n * event once and hands the same envelope to every client, rather than every\n * client re-encoding the same payload.\n *\n * Failure is per-cause, not per-feed: a client that cannot keep up fails alone\n * with its own overflow, while an event that cannot be encoded is a server bug\n * that disconnects everyone currently attached -- the feed stays usable for\n * whoever connects next. Types outside the server's registry are dropped\n * before they can consume a client's capacity.\n */\nexport const make = Effect.fn(\"EventFeed.make\")(function* (\n\tlisten: (subscriber: Event.Subscriber) => Effect.Effect<Event.Unsubscribe>,\n\toptions?: { readonly capacity?: number; readonly registry?: Envelope.Registry },\n) {\n\tconst capacity = options?.capacity ?? SubscriberCapacity;\n\t// Whatever this server knows how to describe, plugin registrations included.\n\tconst registry = options?.registry ?? Envelope.Core;\n\tconst subscribers = new Set<Queue.Queue<EventEnvelope, Error>>();\n\n\tconst failAll = (error: Error) =>\n\t\tEffect.sync(() => {\n\t\t\tconst current = Array.from(subscribers);\n\t\t\tsubscribers.clear();\n\t\t\tfor (const subscriber of current) Queue.failCauseUnsafe(subscriber, Cause.fail(error));\n\t\t});\n\n\tconst deliver = Effect.fnUntraced(function* (event: EventSchema.Payload) {\n\t\tif (subscribers.size === 0) return;\n\t\t// Dropped here rather than after the queue: a burst of events this server\n\t\t// cannot describe must not spend a client's capacity on its way to nowhere.\n\t\tif (registry(event.type) === undefined) return;\n\t\tconst envelope = yield* Envelope.encode(event, registry);\n\t\tfor (const subscriber of subscribers) {\n\t\t\tif (Queue.offerUnsafe(subscriber, envelope)) continue;\n\t\t\tsubscribers.delete(subscriber);\n\t\t\tQueue.failCauseUnsafe(subscriber, Cause.fail(new Event.SubscriptionOverflowError({ capacity })));\n\t\t}\n\t});\n\n\tconst publish = (event: EventSchema.Payload) =>\n\t\tdeliver(event).pipe(\n\t\t\tEffect.catchTags({\n\t\t\t\tEventEncodingError: (error) =>\n\t\t\t\t\tEffect.logError(\"Failed to encode public event\", { type: error.type, cause: error.cause }).pipe(\n\t\t\t\t\t\tEffect.andThen(failAll(error)),\n\t\t\t\t\t),\n\t\t\t\t// The registry guard in `deliver` already returned for unknown types.\n\t\t\t\tUnknownEventTypeError: Effect.die,\n\t\t\t}),\n\t\t);\n\n\tconst unsubscribe = yield* listen(publish);\n\tyield* Effect.addFinalizer(() => unsubscribe);\n\n\treturn Service.of({\n\t\tsubscribe: Effect.acquireRelease(\n\t\t\tQueue.dropping<EventEnvelope, Error>(capacity).pipe(\n\t\t\t\tEffect.tap((queue) => Effect.sync(() => subscribers.add(queue))),\n\t\t\t),\n\t\t\t(queue) => Effect.sync(() => subscribers.delete(queue)).pipe(Effect.andThen(Queue.shutdown(queue))),\n\t\t).pipe(Effect.map(Stream.fromQueue)),\n\t});\n});\n\nexport const layer = Layer.effect(\n\tService,\n\tEffect.gen(function* () {\n\t\tconst events = yield* Event.Service;\n\t\tconst registered = yield* EventRegistry.Service;\n\t\treturn yield* make(events.listen, { registry: registered.get });\n\t}),\n);\n\nexport * as EventFeed from \"./feed.ts\";\n","/**\n * Server-side OAuth credential storage.\n *\n * The CLI runs the interactive part of every login locally — a browser\n * callback or a device code cannot travel over RPC — and sends the resulting\n * credentials here, so this service only ever reads, writes, refreshes and\n * clears the server home's `auth.json`.\n */\nimport {\n\tGitHubCopilotOAuthClient,\n\tJsonGitHubCopilotAuthStorage,\n\ttype GitHubCopilotOAuthCredentials,\n} from \"@codeworksh/aikit/oauth/github/copilot\";\nimport {\n\tJsonOpenAICodexAuthStorage,\n\tOpenAICodexOAuthClient,\n\ttype OpenAICodexOAuthCredentials,\n} from \"@codeworksh/aikit/oauth/openai/codex\";\nimport { summarizeGitHubCopilot, summarizeOpenAICodex } from \"@codeworksh/aikit/oauth/summary\";\nimport { Global } from \"@codeworksh/harness/effect\";\nimport { Context, Effect, Layer } from \"effect\";\nimport { Contract } from \"./contract.ts\";\n\ninterface Interface {\n\treadonly save: (credentials: Contract.OAuthCredentials) => Effect.Effect<Contract.OAuthInfo, Contract.OAuthError>;\n\treadonly status: (provider: Contract.OAuthProvider) => Effect.Effect<Contract.OAuthInfo | null, Contract.OAuthError>;\n\treadonly refresh: (\n\t\tprovider: Contract.OAuthProvider,\n\t) => Effect.Effect<Contract.OAuthInfo | null, Contract.OAuthError>;\n\treadonly logout: (provider: Contract.OAuthProvider) => Effect.Effect<void, Contract.OAuthError>;\n}\n\nexport class Service extends Context.Service<Service, Interface>()(\"@codeworksh/cli/server/oauth/Service\") {}\n\nconst message = (cause: unknown): string =>\n\tcause instanceof Error && cause.message.trim().length > 0 ? cause.message : String(cause);\n\n/**\n * Re-exported so the server, the CLI and aikit all describe a login the same\n * way; `Contract.OAuthInfo` is the wire shape of {@link OAuthSummary}.\n */\nexport const codexInfo = summarizeOpenAICodex;\nexport const copilotInfo = summarizeGitHubCopilot;\n\nexport const layer = (options: { readonly home?: string | undefined } = {}) =>\n\tLayer.effect(\n\t\tService,\n\t\tEffect.gen(function* () {\n\t\t\tconst global = yield* Global.Service;\n\t\t\tconst authFile = Global.authFile(options.home === undefined ? undefined : global.home);\n\t\t\tconst file = authFile === undefined ? {} : { path: authFile };\n\t\t\tconst codex = new OpenAICodexOAuthClient({ storage: new JsonOpenAICodexAuthStorage(file) });\n\t\t\tconst copilot = new GitHubCopilotOAuthClient({ storage: new JsonGitHubCopilotAuthStorage(file) });\n\t\t\tconst attempt = <A>(run: () => Promise<A>) =>\n\t\t\t\tEffect.tryPromise({\n\t\t\t\t\ttry: run,\n\t\t\t\t\tcatch: (cause) => new Contract.OAuthError({ message: message(cause) }),\n\t\t\t\t});\n\t\t\tconst optional = <A>(run: () => Promise<A | undefined>, info: (value: A) => Contract.OAuthInfo) =>\n\t\t\t\tattempt(run).pipe(Effect.map((value) => (value === undefined ? null : info(value))));\n\n\t\t\treturn Service.of({\n\t\t\t\t// The `provider` tag is routing, not a credential: it never reaches disk.\n\t\t\t\tsave: (wire) => {\n\t\t\t\t\tif (wire.provider === \"openai-codex\") {\n\t\t\t\t\t\tconst credentials: OpenAICodexOAuthCredentials = {\n\t\t\t\t\t\t\taccess: wire.access,\n\t\t\t\t\t\t\trefresh: wire.refresh,\n\t\t\t\t\t\t\texpires: wire.expires,\n\t\t\t\t\t\t\taccountId: wire.accountId,\n\t\t\t\t\t\t};\n\t\t\t\t\t\treturn attempt(() => codex.storage.set(credentials)).pipe(Effect.as(codexInfo(credentials)));\n\t\t\t\t\t}\n\t\t\t\t\tconst credentials: GitHubCopilotOAuthCredentials = {\n\t\t\t\t\t\taccess: wire.access,\n\t\t\t\t\t\trefresh: wire.refresh,\n\t\t\t\t\t\texpires: wire.expires,\n\t\t\t\t\t\t...(wire.enterpriseUrl === undefined ? {} : { enterpriseUrl: wire.enterpriseUrl }),\n\t\t\t\t\t\t...(wire.apiEndpoint === undefined ? {} : { apiEndpoint: wire.apiEndpoint }),\n\t\t\t\t\t\t...(wire.availableModelIds === undefined ? {} : { availableModelIds: [...wire.availableModelIds] }),\n\t\t\t\t\t};\n\t\t\t\t\treturn attempt(() => copilot.storage.set(credentials)).pipe(Effect.as(copilotInfo(credentials)));\n\t\t\t\t},\n\t\t\t\tstatus: (provider) =>\n\t\t\t\t\tprovider === \"openai-codex\"\n\t\t\t\t\t\t? optional(() => codex.storage.get(), codexInfo)\n\t\t\t\t\t\t: optional(() => copilot.storage.get(), copilotInfo),\n\t\t\t\t// GitHub OAuth tokens never expire, so a Copilot \"refresh\" is a read.\n\t\t\t\trefresh: (provider) =>\n\t\t\t\t\tprovider === \"openai-codex\"\n\t\t\t\t\t\t? optional(() => codex.getCredentials(), codexInfo)\n\t\t\t\t\t\t: optional(() => copilot.getCredentials(), copilotInfo),\n\t\t\t\tlogout: (provider) => attempt(() => (provider === \"openai-codex\" ? codex.logout() : copilot.logout())),\n\t\t\t});\n\t\t}),\n\t);\n\nexport * as OAuth from \"./oauth.ts\";\n","import { Control, EventSchema, PromptSchema, Sandbox, Session, SessionStore, State } from \"@codeworksh/harness/effect\";\nimport { Effect, Option, Stream } from \"effect\";\nimport { Contract, type RuntimeConfig, type SandboxInfo, type SessionInfo } from \"./contract.ts\";\nimport { Envelope } from \"./envelope.ts\";\nimport { EventFeed } from \"./feed.ts\";\nimport { OAuth } from \"./oauth.ts\";\n\nconst toSandboxInfo = (info: Sandbox.Info): SandboxInfo => ({\n\tid: info.id,\n\tdriver: info.driver,\n\tkind: info.kind,\n\tproviderResourceId: info.providerResourceId,\n\townership: info.ownership,\n\tstatus: info.status,\n\tusage: info.usage,\n\trefCount: info.refCount,\n\tcreatedAt: info.createdAt,\n\tupdatedAt: info.updatedAt,\n});\n\nconst toSessionInfo = (info: Session.Info): SessionInfo => ({\n\tid: info.id,\n\ttitle: info.title,\n\tdirectory: info.directory,\n\t...(info.hostDir === undefined ? {} : { hostDir: info.hostDir }),\n\t...(info.sandbox === undefined ? {} : { sandbox: toSandboxInfo(info.sandbox) }),\n});\n\n// Wire configs name only the keys they set, so `undefined` never reaches the\n// harness inputs under exactOptionalPropertyTypes.\nconst runtimeInput = (runtime: RuntimeConfig | undefined): Session.RuntimeInput => ({\n\t...(runtime?.model === undefined ? {} : { model: runtime.model }),\n\t...(runtime?.thinkingLevel === undefined ? {} : { thinkingLevel: runtime.thinkingLevel }),\n});\n\nconst sessionInfo = Effect.fn(\"Server.sessionInfo\")(function* (sessionId: Session.SessionSchema.ID) {\n\tconst found = yield* Session.get(sessionId);\n\tif (Option.isNone(found)) return yield* new SessionStore.SessionNotFoundError({ sessionId });\n\treturn toSessionInfo(yield* found.value.info);\n});\n\nconst releaseOnFailure = (selection: Effect.Success<ReturnType<typeof Sandbox.resolve>> | undefined) =>\n\tselection?.created && selection.info !== undefined\n\t\t? Sandbox.stop(selection.info.id).pipe(Effect.catchCause(Effect.logError))\n\t\t: Effect.void;\n\nexport const layer = Contract.Api.toLayer(\n\tEffect.gen(function* () {\n\t\tconst control = yield* Control.Service;\n\t\tconst feed = yield* EventFeed.Service;\n\t\tconst sessions = yield* SessionStore.Service;\n\t\tconst state = yield* State.Service;\n\t\tconst oauth = yield* OAuth.Service;\n\n\t\treturn Contract.Api.of({\n\t\t\t\"auth.save\": ({ credentials }) => oauth.save(credentials),\n\t\t\t\"auth.status\": ({ provider }) => oauth.status(provider),\n\t\t\t\"auth.refresh\": ({ provider }) => oauth.refresh(provider),\n\t\t\t\"auth.logout\": ({ provider }) => oauth.logout(provider),\n\t\t\t\"session.create\": Effect.fnUntraced(function* ({ title, directory, hostDir, sandbox, runtime }) {\n\t\t\t\tconst selection = sandbox === undefined ? undefined : yield* Sandbox.resolve(sandbox);\n\t\t\t\tconst selected = selection?.info;\n\t\t\t\tconst handle = yield* Session.create({\n\t\t\t\t\t...(title === undefined ? {} : { title }),\n\t\t\t\t\t...(directory === undefined ? {} : { directory }),\n\t\t\t\t\t// Passed through as given, never defaulted to the server's own directory: a\n\t\t\t\t\t// session the client did not place has no project, which is a normal state.\n\t\t\t\t\t...(hostDir === undefined ? {} : { hostDir }),\n\t\t\t\t\t...(selected === undefined ? {} : { sandbox: selected }),\n\t\t\t\t\t...runtimeInput(runtime),\n\t\t\t\t}).pipe(Effect.onError(() => releaseOnFailure(selection)));\n\t\t\t\treturn toSessionInfo(yield* handle.info);\n\t\t\t}),\n\t\t\t\"plugin.reload\": Effect.fnUntraced(function* () {\n\t\t\t\treturn yield* state.reload;\n\t\t\t}),\n\t\t\t\"session.list\": Effect.fnUntraced(function* () {\n\t\t\t\tconst rows = yield* sessions.list();\n\t\t\t\treturn yield* Effect.forEach(rows, (row) => sessionInfo(row.id).pipe(Effect.orDie));\n\t\t\t}),\n\t\t\t\"session.configure\": Effect.fnUntraced(function* ({ sessionId, runtime }) {\n\t\t\t\tconst handle = yield* Session.attach({ sessionId, ...runtimeInput(runtime) });\n\t\t\t\treturn toSessionInfo(yield* handle.info);\n\t\t\t}),\n\t\t\t\"session.info\": Effect.fnUntraced(function* ({ sessionId }) {\n\t\t\t\treturn yield* sessionInfo(sessionId);\n\t\t\t}),\n\t\t\t\"session.link\": Effect.fnUntraced(function* ({ sessionId, hostDir }) {\n\t\t\t\tconst handle = yield* Session.link({ sessionId, hostDir: hostDir ?? null });\n\t\t\t\treturn toSessionInfo(yield* handle.info);\n\t\t\t}),\n\t\t\t\"session.relink\": Effect.fnUntraced(function* ({ sessionId, sandbox, directory }) {\n\t\t\t\tconst selection = sandbox === undefined ? undefined : yield* Sandbox.resolve(sandbox);\n\t\t\t\tconst selected = selection?.info;\n\t\t\t\tconst handle = yield* Session.relink({\n\t\t\t\t\tsessionId,\n\t\t\t\t\t...(selected === undefined ? {} : { sandbox: selected }),\n\t\t\t\t\t...(directory === undefined ? {} : { directory }),\n\t\t\t\t}).pipe(Effect.onError(() => releaseOnFailure(selection)));\n\t\t\t\treturn toSessionInfo(yield* handle.info);\n\t\t\t}),\n\t\t\t\"session.prompt\": Effect.fnUntraced(function* ({ sessionId, text, delivery, id }) {\n\t\t\t\tyield* control.prompt({\n\t\t\t\t\tsessionId,\n\t\t\t\t\tprompt: PromptSchema.Prompt.make({ text }),\n\t\t\t\t\t...(delivery === undefined ? {} : { delivery }),\n\t\t\t\t\t...(id === undefined ? {} : { id }),\n\t\t\t\t});\n\t\t\t}),\n\t\t\t\"session.interrupt\": Effect.fnUntraced(function* ({ sessionId }) {\n\t\t\t\t// Ack only: the wire answers \"was there work to stop\", and the caller\n\t\t\t\t// uses `session.wait` when it needs cleanup to have finished.\n\t\t\t\treturn { interrupted: yield* control.interrupt(sessionId, { reason: \"user\" }) };\n\t\t\t}),\n\t\t\t\"session.message\": Effect.fnUntraced(function* ({ sessionId, messageId }) {\n\t\t\t\tconst found = yield* sessions.entry(messageId);\n\t\t\t\t// Scoped to the session the caller named: an entry id is global, and a\n\t\t\t\t// client should not learn about conversations it did not ask for.\n\t\t\t\treturn { found: Option.isSome(found) && found.value.entry.sessionId === sessionId };\n\t\t\t}),\n\t\t\t\"session.wait\": Effect.fnUntraced(function* ({ sessionId }) {\n\t\t\t\tyield* control.wait(sessionId);\n\t\t\t}),\n\t\t\t\"sandbox.drivers\": Effect.fnUntraced(function* () {\n\t\t\t\tconst drivers = yield* Sandbox.drivers();\n\t\t\t\treturn drivers.map(({ name, kind }) => ({ name, kind }));\n\t\t\t}),\n\t\t\t\"sandbox.list\": Effect.fnUntraced(function* () {\n\t\t\t\treturn (yield* Sandbox.list()).map(toSandboxInfo);\n\t\t\t}),\n\t\t\t\"sandbox.create\": Effect.fnUntraced(function* ({ driver }) {\n\t\t\t\treturn toSandboxInfo(yield* Sandbox.create({ driver }));\n\t\t\t}),\n\t\t\t\"sandbox.register\": Effect.fnUntraced(function* ({ driver, providerResourceId }) {\n\t\t\t\treturn toSandboxInfo(yield* Sandbox.register({ driver, providerResourceId }));\n\t\t\t}),\n\t\t\t\"sandbox.stop\": Effect.fnUntraced(function* ({ sandboxId }) {\n\t\t\t\tyield* Sandbox.stop(sandboxId);\n\t\t\t}),\n\t\t\t// Acquiring the feed subscription is the readiness barrier: `server.connected`\n\t\t\t// is only emitted once this client is registered, so a prompt admitted after\n\t\t\t// it cannot land in the gap.\n\t\t\t\"event.subscribe\": () =>\n\t\t\t\tStream.unwrap(\n\t\t\t\t\tfeed.subscribe.pipe(\n\t\t\t\t\t\tEffect.map((events) =>\n\t\t\t\t\t\t\tStream.make({ id: EventSchema.ID.create(), type: Envelope.Connected.type, data: {} }).pipe(\n\t\t\t\t\t\t\t\tStream.concat(events),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t});\n\t}),\n);\n\nexport * as Handlers from \"./handlers.ts\";\n","import { Control, Harness, Sandbox } from \"@codeworksh/harness/effect\";\nimport { NodeHttpServer } from \"@effect/platform-node\";\nimport { Effect, Layer } from \"effect\";\nimport { HttpRouter, HttpServer } from \"effect/unstable/http\";\nimport * as NetAddress from \"effect/unstable/net/NetAddress\";\nimport { RpcSerialization, RpcServer } from \"effect/unstable/rpc\";\n/* @effect-diagnostics nodeBuiltinImport:off -- the RPC listener needs a raw node server. */\nimport { createServer } from \"node:http\";\nimport { Contract } from \"./contract.ts\";\nimport { EventFeed } from \"./feed.ts\";\nimport { Handlers } from \"./handlers.ts\";\nimport { OAuth } from \"./oauth.ts\";\n\nconst WsProtocol = RpcServer.layerProtocolWebsocket({ path: \"/rpc\" }).pipe(Layer.provide(HttpRouter.layer));\n\n// Managed instances outlive any single RPC; the serve scope is what stops them.\nconst managedShutdown = Layer.unwrap(\n\tEffect.gen(function* () {\n\t\tconst control = yield* Control.Service;\n\t\tyield* Effect.addFinalizer(() =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\t// Release drain leases before stopping the server's managed sandboxes.\n\t\t\t\t// Both phases are best effort and bounded so a provider cannot prevent\n\t\t\t\t// process shutdown; an interrupted stop is persisted as faulted and can\n\t\t\t\t// recover the next time that sandbox is mounted.\n\t\t\t\tyield* Effect.forEach(\n\t\t\t\t\tyield* control.active,\n\t\t\t\t\t(id) =>\n\t\t\t\t\t\tcontrol\n\t\t\t\t\t\t\t.interrupt(id, { reason: \"shutdown\", awaitSettlement: true })\n\t\t\t\t\t\t\t.pipe(Effect.timeout(\"10 seconds\"), Effect.ignore),\n\t\t\t\t\t{ concurrency: \"unbounded\", discard: true },\n\t\t\t\t);\n\t\t\t\tconst infos = yield* Sandbox.list();\n\t\t\t\tyield* Effect.forEach(\n\t\t\t\t\tinfos.filter((info) => info.ownership === \"managed\"),\n\t\t\t\t\t(info) =>\n\t\t\t\t\t\tSandbox.stop(info.id).pipe(\n\t\t\t\t\t\t\tEffect.timeout(\"10 seconds\"),\n\t\t\t\t\t\t\tEffect.catchCause((cause) =>\n\t\t\t\t\t\t\t\tEffect.logError(\"Failed to stop managed sandbox during shutdown\", cause).pipe(\n\t\t\t\t\t\t\t\t\tEffect.annotateLogs({ sandboxInstanceId: info.id }),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t\t{ concurrency: \"unbounded\", discard: true },\n\t\t\t\t);\n\t\t\t}),\n\t\t);\n\t\treturn Layer.empty;\n\t}),\n);\n\nconst listenLog = Layer.unwrap(\n\tEffect.gen(function* () {\n\t\tconst server = yield* HttpServer.HttpServer;\n\t\tyield* Effect.log(`codework serve listening on ${NetAddress.formatUrlUnsafe(server.address, \"ws\")}/rpc`);\n\t\treturn Layer.empty;\n\t}),\n);\n\nexport const layer = (options: { host: string; port: number; harness: Harness.Options }) => {\n\tconst rpc = RpcServer.layer(Contract.Api).pipe(Layer.provide(Handlers.layer.pipe(Layer.provide(EventFeed.layer))));\n\tconst app = rpc.pipe(\n\t\tLayer.provideMerge(WsProtocol),\n\t\tLayer.provide(HttpRouter.serve(WsProtocol, { disableListenLog: true })),\n\t\tLayer.provideMerge(managedShutdown),\n\t\tLayer.provide(OAuth.layer(options.harness.home === undefined ? {} : { home: options.harness.home })),\n\t);\n\treturn app.pipe(\n\t\tLayer.provideMerge(listenLog),\n\t\tLayer.provideMerge(NodeHttpServer.layer(createServer, { host: options.host, port: options.port })),\n\t\tLayer.provide(RpcSerialization.layerNdjson),\n\t\tLayer.provide(Harness.layer(options.harness)),\n\t);\n};\n\nexport * as Server from \"./server.ts\";\n","import { Effect, Layer } from \"effect\";\nimport { Runtime } from \"../../../framework/runtime.ts\";\nimport { Server } from \"../../../server/server.ts\";\nimport { renderError } from \"../../error.ts\";\nimport { harnessOptions } from \"../../harness.ts\";\nimport { writeError } from \"../../output.ts\";\nimport { Cmd } from \"../cmd.ts\";\n\nexport default Runtime.handler(\n\tCmd.commands.serve,\n\tEffect.fn(\"CLI.serve\")(function* ({ host, port }) {\n\t\tconst shared = yield* Cmd.spec;\n\t\tyield* Layer.launch(Server.layer({ host, port, harness: harnessOptions(shared) })).pipe(\n\t\t\tEffect.catch((error) =>\n\t\t\t\twriteError(renderError(error)).pipe(\n\t\t\t\t\tEffect.andThen(\n\t\t\t\t\t\tEffect.sync(() => {\n\t\t\t\t\t\t\tprocess.exitCode = 1;\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t),\n\t\t);\n\t}),\n);\n"],"mappings":"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"}
@@ -0,0 +1,215 @@
1
+ import { B as inspect, F as paths, G as lock, H as required, I as projectRoot, K as parse$1, R as canonical, bt as resolve, t as InvalidInputError } from "./error-BrbT9_4g.mjs";
2
+ import { Effect, FileSystem, Option, Path } from "effect";
3
+ import { applyEdits, modify, parse } from "jsonc-parser";
4
+ //#region src/cli/cmd/handlers/plugin/settings.ts
5
+ /** The module an entry names: a bare loader string, or the `package` of a config object. */
6
+ const moduleOf = (entry) => {
7
+ if (typeof entry === "string") return entry;
8
+ if (typeof entry !== "object" || entry === null) return void 0;
9
+ const { package: located } = entry;
10
+ return typeof located === "string" ? located : void 0;
11
+ };
12
+ /** The plugin ID an entry configures, if it names one. */
13
+ const idOf = (entry) => {
14
+ if (typeof entry !== "object" || entry === null) return void 0;
15
+ const { plugin } = entry;
16
+ return typeof plugin === "string" ? plugin : void 0;
17
+ };
18
+ /**
19
+ * Every spelling a reference answers to, without touching the filesystem.
20
+ *
21
+ * `canonical` drops a version suffix and resolves a relative path against the file that declared
22
+ * it, so `@acme/x@1.2.0` and `@acme/x` are one name. The literal string is kept alongside it
23
+ * because a plugin ID is not a module reference and survives `canonical` unchanged.
24
+ */
25
+ const aliases = (reference, directory) => canonical(reference, directory).pipe(Effect.map((canonical) => [reference, canonical]), Effect.orElseSucceed(() => [reference]));
26
+ /** Every spelling `reference` answers to, computed once for a whole command. */
27
+ const spellings = (reference, directory) => Effect.map(aliases(reference, directory), (found) => new Set(found));
28
+ /**
29
+ * What each configured entry answers to.
30
+ *
31
+ * Comparing the string as written is not enough. `plugin add @acme/x@1.2.0` records a version the
32
+ * user will not repeat when removing it, and one plugin can be named three ways: by package, by
33
+ * path, or by the ID it declares -- a `{ "plugin": "<id>" }` configuration object sits next to the
34
+ * module entry that loaded it, and removing the plugin has to take both.
35
+ *
36
+ * The declared ID is only knowable by importing the module, so it is read from what is already
37
+ * installed: `resolveCached` reads a published package out of the harness cache and never runs an
38
+ * installer, so editing a settings file cannot fetch anything. An entry that does not resolve --
39
+ * uninstalled, moved, or broken -- keeps the names it was written with rather than failing the
40
+ * command, because an unusable plugin is exactly the one a user is trying to remove.
41
+ */
42
+ const identify = Effect.fn("CLI.plugin.identify")(function* (plugins, file, cache) {
43
+ const directory = (yield* Path.Path).dirname(file);
44
+ const entries = [];
45
+ for (const value of plugins) {
46
+ const answers = /* @__PURE__ */ new Set();
47
+ const id = idOf(value);
48
+ if (id !== void 0) answers.add(id);
49
+ const module = moduleOf(value);
50
+ if (module !== void 0) {
51
+ for (const alias of yield* aliases(module, directory)) answers.add(alias);
52
+ const declared = yield* inspect(module, {
53
+ cache,
54
+ hostDir: directory,
55
+ file,
56
+ install: required
57
+ }).pipe(Effect.option);
58
+ if (Option.isSome(declared)) {
59
+ answers.add(declared.value.id);
60
+ if (declared.value.name !== void 0) answers.add(declared.value.name);
61
+ }
62
+ }
63
+ entries.push({
64
+ value,
65
+ answers,
66
+ loads: typeof value === "string"
67
+ });
68
+ }
69
+ return entries;
70
+ });
71
+ /**
72
+ * What a local reference should be *written* as, which is rarely what was typed.
73
+ *
74
+ * A relative path on the command line is relative to `cwd`; in a settings file it is relative to
75
+ * that file's own directory. Those differ the moment the command runs anywhere but the directory
76
+ * holding the file, and writing the string verbatim produces a silently wrong entry:
77
+ *
78
+ * ```
79
+ * $ cd ~/workspace/app/packages/fubar
80
+ * $ codework plugin add ./plugins/x.ts # means packages/fubar/plugins/x.ts
81
+ * → the project is ~/workspace/app, so the entry lands in app/.codework/settings.jsonc
82
+ * → written verbatim, the harness loads app/.codework/plugins/x.ts ← a different file
83
+ * ```
84
+ *
85
+ * With both ends inside one project the entry is written relative to the file, which is portable
86
+ * to any checkout of that repository and therefore worth committing. Otherwise -- a `-g` write
87
+ * into `<home>`, a `--user-config-dir` elsewhere, or a target outside the project -- relative
88
+ * would be a lie, and an absolute path means the same thing read from anywhere.
89
+ *
90
+ * A package spec is returned untouched: it names no location.
91
+ */
92
+ const written = Effect.fn("CLI.plugin.written")(function* (reference, input) {
93
+ const nodePath = yield* Path.Path;
94
+ const target = yield* parse$1(reference, input.cwd).pipe(Effect.option);
95
+ if (Option.isNone(target) || target.value.kind !== "local") return reference;
96
+ const absolute = target.value.path;
97
+ const inside = (root, path) => path === root || path.startsWith(`${root}${nodePath.sep}`);
98
+ if (input.root !== void 0 && inside(input.root, input.file) && inside(input.root, absolute)) {
99
+ const relative = nodePath.relative(nodePath.dirname(input.file), absolute);
100
+ return relative.startsWith("..") ? relative : `./${relative}`;
101
+ }
102
+ return absolute;
103
+ });
104
+ /** `reference` selects `entry` when any spelling of one is a spelling of the other. */
105
+ const matches = (entry, spelled) => [...spelled].some((alias) => entry.answers.has(alias));
106
+ /** Every entry connected to a reference through a package spelling or declared plugin ID. */
107
+ const matching = (entries, spelled) => {
108
+ const answers = new Set(spelled);
109
+ const found = /* @__PURE__ */ new Set();
110
+ for (;;) {
111
+ const next = entries.filter((entry) => !found.has(entry) && [...entry.answers].some((answer) => answers.has(answer)));
112
+ if (next.length === 0) return found;
113
+ for (const entry of next) {
114
+ found.add(entry);
115
+ for (const answer of entry.answers) answers.add(answer);
116
+ }
117
+ }
118
+ };
119
+ const readPlugins = Effect.fn("CLI.plugin.readPlugins")(function* (path) {
120
+ const fs = yield* FileSystem.FileSystem;
121
+ const source = (yield* fs.exists(path)) ? yield* fs.readFileString(path) : "{}";
122
+ const errors = [];
123
+ const document = parse(source, errors, { allowTrailingComma: true });
124
+ if (errors.length > 0 || typeof document !== "object" || document === null || Array.isArray(document)) return yield* new InvalidInputError({ message: `${path} is not a valid settings file` });
125
+ const plugins = document.plugins;
126
+ if (plugins !== void 0 && !Array.isArray(plugins)) return yield* new InvalidInputError({ message: `${path}: "plugins" must be an array` });
127
+ return {
128
+ source,
129
+ plugins: plugins ?? []
130
+ };
131
+ });
132
+ const writePlugins = Effect.fn("CLI.plugin.writePlugins")(function* (path, source, plugins) {
133
+ const fs = yield* FileSystem.FileSystem;
134
+ const nodePath = yield* Path.Path;
135
+ const edited = applyEdits(source, modify(source, ["plugins"], plugins, { formattingOptions: {
136
+ tabSize: 2,
137
+ insertSpaces: false
138
+ } }));
139
+ const directory = nodePath.dirname(path);
140
+ yield* fs.makeDirectory(directory, { recursive: true });
141
+ const mode = (yield* fs.exists(path)) ? (yield* fs.stat(path)).mode & 511 : 384;
142
+ const temporary = yield* fs.makeTempFile({
143
+ directory,
144
+ prefix: `.${nodePath.basename(path)}.`,
145
+ suffix: ".tmp"
146
+ });
147
+ yield* fs.writeFileString(temporary, edited.endsWith("\n") ? edited : `${edited}\n`).pipe(Effect.andThen(fs.chmod(temporary, mode)), Effect.andThen(fs.rename(temporary, path)), Effect.ensuring(fs.remove(temporary, { force: true }).pipe(Effect.ignore)));
148
+ });
149
+ /**
150
+ * Serialize one settings read-modify-rename across CLI processes.
151
+ *
152
+ * The install or inspection that precedes an edit stays outside this scope; only the local edit is
153
+ * locked, so unrelated downloads do not wait behind one another.
154
+ */
155
+ const withPluginsLock = (path, edit) => Effect.gen(function* () {
156
+ const fs = yield* FileSystem.FileSystem;
157
+ const nodePath = yield* Path.Path;
158
+ yield* fs.makeDirectory(nodePath.dirname(path), { recursive: true });
159
+ yield* lock(`${path}.lock`, () => new InvalidInputError({ message: `timed out waiting to edit ${path}` }));
160
+ return yield* edit;
161
+ }).pipe(Effect.scoped);
162
+ /**
163
+ * Which file the edit lands in.
164
+ *
165
+ * A plugin belongs to a project by default — it is part of how that repository is worked on, so
166
+ * the entry belongs in a file the repository can commit. `--global` writes the user-wide file
167
+ * instead, the way `npm -g` installs outside a package. An explicit `--user-config-dir` outranks
168
+ * both, since naming a settings directory is already a decision about where settings live.
169
+ *
170
+ * The project is found the way the harness finds it: the nearest ancestor holding a `.codework`
171
+ * directory, so a command run from `packages/app` edits the repository's own file rather than
172
+ * creating a second one beside it. When no ancestor has one, the project begins here -- the
173
+ * directory is created and the caller is told, because that decision changes where every future
174
+ * plugin entry lands, and a mistyped `cd` is the way it goes wrong.
175
+ *
176
+ * There is nothing to warn about between layers: plugin entries from every layer accumulate, so a
177
+ * user-wide entry still applies inside a project that declares its own.
178
+ */
179
+ const resolveTarget = Effect.fn("CLI.plugin.resolveTarget")(function* (shared, global, from, create = true) {
180
+ const fs = yield* FileSystem.FileSystem;
181
+ const nodePath = yield* Path.Path;
182
+ const cwd = from ?? nodePath.resolve(".");
183
+ const home = yield* resolve(Option.isNone(shared.home) ? {} : { home: shared.home.value });
184
+ const root = global ? void 0 : yield* projectRoot(cwd, home.home);
185
+ const groups = paths({
186
+ home: home.home,
187
+ ...root === void 0 ? {} : { root },
188
+ from: cwd,
189
+ ...Option.isNone(shared.userConfigDir) ? {} : { custom: shared.userConfigDir.value }
190
+ });
191
+ const found = groups[Option.isSome(shared.userConfigDir) ? groups.length - 1 : global ? 0 : 1] ?? [];
192
+ if (found.length > 0) {
193
+ const existing = yield* Effect.findFirst(found, (candidate) => fs.exists(candidate));
194
+ return {
195
+ path: Option.getOrElse(existing, () => found[0]),
196
+ ...root === void 0 ? {} : { root }
197
+ };
198
+ }
199
+ const marker = nodePath.join(cwd, ".codework");
200
+ if (!create) return {
201
+ path: nodePath.join(marker, "settings.jsonc"),
202
+ created: marker,
203
+ root: cwd
204
+ };
205
+ yield* fs.makeDirectory(marker, { recursive: true });
206
+ return {
207
+ path: nodePath.join(marker, "settings.jsonc"),
208
+ created: marker,
209
+ root: cwd
210
+ };
211
+ });
212
+ //#endregion
213
+ export { resolveTarget as a, writePlugins as c, readPlugins as i, written as l, matches as n, spellings as o, matching as r, withPluginsLock as s, identify as t };
214
+
215
+ //# sourceMappingURL=settings-C52IrA72.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-C52IrA72.mjs","names":["Plugin.canonical","Plugin.inspect","Plugin.resolveCached","Plugin.parse","parseJsonc","Plugin.settingsLock","Global.resolve","Settings.projectRoot","Settings.paths"],"sources":["../../src/cli/cmd/handlers/plugin/settings.ts"],"sourcesContent":["import { Global, Plugin, Settings } from \"@codeworksh/harness/effect\";\nimport { Effect, FileSystem, Option, Path } from \"effect\";\nimport { applyEdits, modify, parse as parseJsonc, type ParseError } from \"jsonc-parser\";\nimport { InvalidInputError } from \"../../../error.ts\";\n\n/**\n * Reading and rewriting the `plugins` array of one settings file.\n *\n * Edits go through `jsonc-parser`, so a hand-written file keeps its comments, key order and\n * formatting: a plugin added by the CLI must not reformat a file its owner maintains. The write\n * is a temp file renamed over the original, so an interrupted run leaves the previous settings\n * intact rather than a half-written document.\n */\n\nexport interface Shared {\n\treadonly userConfigDir: Option.Option<string>;\n\treadonly home: Option.Option<string>;\n}\n\nexport interface Target {\n\treadonly path: string;\n\t/** Set when this command had to create the project directory, so the caller can say so. */\n\treadonly created?: string;\n\t/** The project this file belongs to, which a local entry is written relative to. */\n\treadonly root?: string;\n}\n\n/** The module an entry names: a bare loader string, or the `package` of a config object. */\nconst moduleOf = (entry: unknown): string | undefined => {\n\tif (typeof entry === \"string\") return entry;\n\tif (typeof entry !== \"object\" || entry === null) return undefined;\n\tconst { package: located } = entry as { package?: unknown };\n\treturn typeof located === \"string\" ? located : undefined;\n};\n\n/** The plugin ID an entry configures, if it names one. */\nconst idOf = (entry: unknown): string | undefined => {\n\tif (typeof entry !== \"object\" || entry === null) return undefined;\n\tconst { plugin } = entry as { plugin?: unknown };\n\treturn typeof plugin === \"string\" ? plugin : undefined;\n};\n\n/**\n * Every spelling a reference answers to, without touching the filesystem.\n *\n * `canonical` drops a version suffix and resolves a relative path against the file that declared\n * it, so `@acme/x@1.2.0` and `@acme/x` are one name. The literal string is kept alongside it\n * because a plugin ID is not a module reference and survives `canonical` unchanged.\n */\nconst aliases = (reference: string, directory: string): Effect.Effect<ReadonlyArray<string>> =>\n\tPlugin.canonical(reference, directory).pipe(\n\t\tEffect.map((canonical) => [reference, canonical] as ReadonlyArray<string>),\n\t\t// A reference that does not parse still answers to itself: a plugin ID is not a module\n\t\t// reference, and it has to keep matching an entry that names it.\n\t\tEffect.orElseSucceed(() => [reference] as ReadonlyArray<string>),\n\t);\n\n/** Every spelling `reference` answers to, computed once for a whole command. */\nexport const spellings = (reference: string, directory: string): Effect.Effect<ReadonlySet<string>> =>\n\tEffect.map(aliases(reference, directory), (found) => new Set(found));\n\nexport interface Entry {\n\treadonly value: unknown;\n\treadonly answers: ReadonlySet<string>;\n\treadonly loads: boolean;\n}\n\n/**\n * What each configured entry answers to.\n *\n * Comparing the string as written is not enough. `plugin add @acme/x@1.2.0` records a version the\n * user will not repeat when removing it, and one plugin can be named three ways: by package, by\n * path, or by the ID it declares -- a `{ \"plugin\": \"<id>\" }` configuration object sits next to the\n * module entry that loaded it, and removing the plugin has to take both.\n *\n * The declared ID is only knowable by importing the module, so it is read from what is already\n * installed: `resolveCached` reads a published package out of the harness cache and never runs an\n * installer, so editing a settings file cannot fetch anything. An entry that does not resolve --\n * uninstalled, moved, or broken -- keeps the names it was written with rather than failing the\n * command, because an unusable plugin is exactly the one a user is trying to remove.\n */\nexport const identify = Effect.fn(\"CLI.plugin.identify\")(function* (\n\tplugins: ReadonlyArray<unknown>,\n\tfile: string,\n\tcache: string,\n) {\n\tconst nodePath = yield* Path.Path;\n\tconst directory = nodePath.dirname(file);\n\tconst entries: Entry[] = [];\n\tfor (const value of plugins) {\n\t\tconst answers = new Set<string>();\n\t\tconst id = idOf(value);\n\t\tif (id !== undefined) answers.add(id);\n\t\tconst module = moduleOf(value);\n\t\tif (module !== undefined) {\n\t\t\tfor (const alias of yield* aliases(module, directory)) answers.add(alias);\n\t\t\tconst declared = yield* Plugin.inspect(module, {\n\t\t\t\tcache,\n\t\t\t\thostDir: directory,\n\t\t\t\tfile,\n\t\t\t\tinstall: Plugin.resolveCached,\n\t\t\t}).pipe(Effect.option);\n\t\t\tif (Option.isSome(declared)) {\n\t\t\t\tanswers.add(declared.value.id);\n\t\t\t\tif (declared.value.name !== undefined) answers.add(declared.value.name);\n\t\t\t}\n\t\t}\n\t\tentries.push({ value, answers, loads: typeof value === \"string\" });\n\t}\n\treturn entries as ReadonlyArray<Entry>;\n});\n\n/**\n * What a local reference should be *written* as, which is rarely what was typed.\n *\n * A relative path on the command line is relative to `cwd`; in a settings file it is relative to\n * that file's own directory. Those differ the moment the command runs anywhere but the directory\n * holding the file, and writing the string verbatim produces a silently wrong entry:\n *\n * ```\n * $ cd ~/workspace/app/packages/fubar\n * $ codework plugin add ./plugins/x.ts # means packages/fubar/plugins/x.ts\n * → the project is ~/workspace/app, so the entry lands in app/.codework/settings.jsonc\n * → written verbatim, the harness loads app/.codework/plugins/x.ts ← a different file\n * ```\n *\n * With both ends inside one project the entry is written relative to the file, which is portable\n * to any checkout of that repository and therefore worth committing. Otherwise -- a `-g` write\n * into `<home>`, a `--user-config-dir` elsewhere, or a target outside the project -- relative\n * would be a lie, and an absolute path means the same thing read from anywhere.\n *\n * A package spec is returned untouched: it names no location.\n */\nexport const written = Effect.fn(\"CLI.plugin.written\")(function* (\n\treference: string,\n\tinput: { readonly cwd: string; readonly file: string; readonly root: string | undefined },\n) {\n\tconst nodePath = yield* Path.Path;\n\tconst target = yield* Plugin.parse(reference, input.cwd).pipe(Effect.option);\n\tif (Option.isNone(target) || target.value.kind !== \"local\") return reference;\n\n\tconst absolute = target.value.path;\n\tconst inside = (root: string, path: string) => path === root || path.startsWith(`${root}${nodePath.sep}`);\n\tif (input.root !== undefined && inside(input.root, input.file) && inside(input.root, absolute)) {\n\t\tconst relative = nodePath.relative(nodePath.dirname(input.file), absolute);\n\t\treturn relative.startsWith(\"..\") ? relative : `./${relative}`;\n\t}\n\treturn absolute;\n});\n\n/** `reference` selects `entry` when any spelling of one is a spelling of the other. */\nexport const matches = (entry: Entry, spelled: ReadonlySet<string>) =>\n\t[...spelled].some((alias) => entry.answers.has(alias));\n\n/** Every entry connected to a reference through a package spelling or declared plugin ID. */\nexport const matching = (entries: ReadonlyArray<Entry>, spelled: ReadonlySet<string>) => {\n\tconst answers = new Set(spelled);\n\tconst found = new Set<Entry>();\n\tfor (;;) {\n\t\tconst next = entries.filter(\n\t\t\t(entry) => !found.has(entry) && [...entry.answers].some((answer) => answers.has(answer)),\n\t\t);\n\t\tif (next.length === 0) return found as ReadonlySet<Entry>;\n\t\tfor (const entry of next) {\n\t\t\tfound.add(entry);\n\t\t\tfor (const answer of entry.answers) answers.add(answer);\n\t\t}\n\t}\n};\n\nexport const readPlugins = Effect.fn(\"CLI.plugin.readPlugins\")(function* (path: string) {\n\tconst fs = yield* FileSystem.FileSystem;\n\tconst exists = yield* fs.exists(path);\n\tconst source = exists ? yield* fs.readFileString(path) : \"{}\";\n\tconst errors: ParseError[] = [];\n\tconst document: unknown = parseJsonc(source, errors, { allowTrailingComma: true });\n\tif (errors.length > 0 || typeof document !== \"object\" || document === null || Array.isArray(document)) {\n\t\treturn yield* new InvalidInputError({ message: `${path} is not a valid settings file` });\n\t}\n\tconst plugins = (document as { plugins?: unknown }).plugins;\n\tif (plugins !== undefined && !Array.isArray(plugins)) {\n\t\treturn yield* new InvalidInputError({ message: `${path}: \"plugins\" must be an array` });\n\t}\n\treturn { source, plugins: (plugins ?? []) as ReadonlyArray<unknown> };\n});\n\nexport const writePlugins = Effect.fn(\"CLI.plugin.writePlugins\")(function* (\n\tpath: string,\n\tsource: string,\n\tplugins: ReadonlyArray<unknown>,\n) {\n\tconst fs = yield* FileSystem.FileSystem;\n\tconst nodePath = yield* Path.Path;\n\tconst edited = applyEdits(\n\t\tsource,\n\t\tmodify(source, [\"plugins\"], plugins, { formattingOptions: { tabSize: 2, insertSpaces: false } }),\n\t);\n\tconst directory = nodePath.dirname(path);\n\tyield* fs.makeDirectory(directory, { recursive: true });\n\tconst mode = (yield* fs.exists(path)) ? (yield* fs.stat(path)).mode & 0o777 : 0o600;\n\tconst temporary = yield* fs.makeTempFile({ directory, prefix: `.${nodePath.basename(path)}.`, suffix: \".tmp\" });\n\tyield* fs\n\t\t.writeFileString(temporary, edited.endsWith(\"\\n\") ? edited : `${edited}\\n`)\n\t\t.pipe(\n\t\t\tEffect.andThen(fs.chmod(temporary, mode)),\n\t\t\tEffect.andThen(fs.rename(temporary, path)),\n\t\t\tEffect.ensuring(fs.remove(temporary, { force: true }).pipe(Effect.ignore)),\n\t\t);\n});\n\n/**\n * Serialize one settings read-modify-rename across CLI processes.\n *\n * The install or inspection that precedes an edit stays outside this scope; only the local edit is\n * locked, so unrelated downloads do not wait behind one another.\n */\nexport const withPluginsLock = <A, E, R>(path: string, edit: Effect.Effect<A, E, R>) =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst nodePath = yield* Path.Path;\n\t\tyield* fs.makeDirectory(nodePath.dirname(path), { recursive: true });\n\t\tyield* Plugin.settingsLock(\n\t\t\t`${path}.lock`,\n\t\t\t() => new InvalidInputError({ message: `timed out waiting to edit ${path}` }),\n\t\t);\n\t\treturn yield* edit;\n\t}).pipe(Effect.scoped);\n\n/**\n * Which file the edit lands in.\n *\n * A plugin belongs to a project by default — it is part of how that repository is worked on, so\n * the entry belongs in a file the repository can commit. `--global` writes the user-wide file\n * instead, the way `npm -g` installs outside a package. An explicit `--user-config-dir` outranks\n * both, since naming a settings directory is already a decision about where settings live.\n *\n * The project is found the way the harness finds it: the nearest ancestor holding a `.codework`\n * directory, so a command run from `packages/app` edits the repository's own file rather than\n * creating a second one beside it. When no ancestor has one, the project begins here -- the\n * directory is created and the caller is told, because that decision changes where every future\n * plugin entry lands, and a mistyped `cd` is the way it goes wrong.\n *\n * There is nothing to warn about between layers: plugin entries from every layer accumulate, so a\n * user-wide entry still applies inside a project that declares its own.\n */\nexport const resolveTarget = Effect.fn(\"CLI.plugin.resolveTarget\")(function* (\n\tshared: Shared,\n\tglobal: boolean,\n\t/**\n\t * Where to discover the project from, when it is not the shell's directory.\n\t *\n\t * A session's host directory is the only other honest answer: it is a declared host path. The\n\t * session's *space* is not, and neither is its project -- a `Project` is env-independent by\n\t * design, so it has no host directory to offer.\n\t */\n\tfrom?: string,\n\t/** Only `plugin add` may establish a project marker when no project exists yet. */\n\tcreate = true,\n) {\n\tconst fs = yield* FileSystem.FileSystem;\n\tconst nodePath = yield* Path.Path;\n\tconst cwd = from ?? nodePath.resolve(\".\");\n\tconst home = yield* Global.resolve(Option.isNone(shared.home) ? {} : { home: shared.home.value });\n\tconst root = global ? undefined : yield* Settings.projectRoot(cwd, home.home);\n\t// `paths` is ordered lowest priority first: user-wide, then project, then an explicit directory.\n\tconst groups = Settings.paths({\n\t\thome: home.home,\n\t\t...(root === undefined ? {} : { root }),\n\t\tfrom: cwd,\n\t\t...(Option.isNone(shared.userConfigDir) ? {} : { custom: shared.userConfigDir.value }),\n\t});\n\tconst chosen = Option.isSome(shared.userConfigDir) ? groups.length - 1 : global ? 0 : 1;\n\t// An empty group is the project layer saying there is no project above this directory. Then\n\t// this directory becomes one -- reported, not silent, because the marker shadows any outer\n\t// project from now on, so a `.codework/` created by a mistyped `cd` is worth spotting at once.\n\tconst found = groups[chosen] ?? [];\n\tif (found.length > 0) {\n\t\tconst existing = yield* Effect.findFirst(found, (candidate) => fs.exists(candidate));\n\t\treturn {\n\t\t\tpath: Option.getOrElse(existing, () => found[0]!),\n\t\t\t...(root === undefined ? {} : { root }),\n\t\t} satisfies Target;\n\t}\n\tconst marker = nodePath.join(cwd, \".codework\");\n\t// `created` is reported either way: with `create` unset the marker is named, not made, so a\n\t// caller can say what its write is about to establish without establishing it early.\n\tif (!create) return { path: nodePath.join(marker, \"settings.jsonc\"), created: marker, root: cwd } satisfies Target;\n\tyield* fs.makeDirectory(marker, { recursive: true });\n\treturn { path: nodePath.join(marker, \"settings.jsonc\"), created: marker, root: cwd } satisfies Target;\n});\n"],"mappings":"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"}
@@ -0,0 +1,115 @@
1
+ import { bt as resolve, p as layer, r as OAuthError, t as InvalidInputError } from "./error-BrbT9_4g.mjs";
2
+ import { i as Cmd } from "./runtime-Dl621FOp.mjs";
3
+ import { Effect, Option, Path } from "effect";
4
+ import { checkOAuthProvider, oauthProviderLabel, summarizeGitHubCopilot, summarizeOpenAICodex } from "@codeworksh/aikit/oauth/summary";
5
+ import { GitHubCopilotOAuthClient, JsonGitHubCopilotAuthStorage } from "@codeworksh/aikit/oauth/github/copilot";
6
+ import { JsonOpenAICodexAuthStorage, OpenAICodexOAuthClient } from "@codeworksh/aikit/oauth/openai/codex";
7
+ import { openBrowser, promptLine } from "@codeworksh/aikit/oauth/interactive";
8
+ //#region src/cli/cmd/handlers/auth/shared.ts
9
+ const announce = (info, browser) => {
10
+ process.stderr.write(`${info.instructions ?? "Complete authentication in your browser."}\n${info.userCode === void 0 ? "" : `Enter code: ${info.userCode}\n`}${info.url}\n`);
11
+ if (browser) openBrowser(info.url, (error) => void process.stderr.write(`Failed to open browser: ${error.message}\n`));
12
+ };
13
+ /** Holds nothing: a server's credentials live on the server, not on this disk. */
14
+ const transient = {
15
+ get: async () => void 0,
16
+ set: async () => {},
17
+ clear: async () => {}
18
+ };
19
+ const codexOps = (storage, path) => {
20
+ const client = new OpenAICodexOAuthClient({ storage });
21
+ return {
22
+ path,
23
+ login: async (options) => {
24
+ const credentials = await client.login({
25
+ device: options.device,
26
+ onAuth: (info) => announce(info, options.browser),
27
+ onProgress: (message) => void process.stderr.write(`${message}\n`),
28
+ onPrompt: (prompt) => promptLine(prompt.message)
29
+ });
30
+ return {
31
+ wire: {
32
+ provider: "openai-codex",
33
+ ...credentials
34
+ },
35
+ summary: summarizeOpenAICodex(credentials)
36
+ };
37
+ },
38
+ stored: async () => {
39
+ const credentials = await storage.get();
40
+ return credentials && summarizeOpenAICodex(credentials);
41
+ },
42
+ refreshed: async () => {
43
+ const credentials = await client.getCredentials();
44
+ return credentials && summarizeOpenAICodex(credentials);
45
+ },
46
+ logout: () => client.logout()
47
+ };
48
+ };
49
+ const copilotOps = (storage, path) => {
50
+ const client = new GitHubCopilotOAuthClient({ storage });
51
+ const read = async () => {
52
+ const credentials = await storage.get();
53
+ return credentials && summarizeGitHubCopilot(credentials);
54
+ };
55
+ return {
56
+ path,
57
+ login: async (options) => {
58
+ const credentials = await client.login({
59
+ ...Option.isSome(options.enterprise) && { enterpriseUrl: options.enterprise.value },
60
+ enableModels: options.enableModels,
61
+ onAuth: (info) => announce(info, options.browser),
62
+ onProgress: (message) => void process.stderr.write(`${message}\n`)
63
+ });
64
+ return {
65
+ wire: {
66
+ provider: "github-copilot",
67
+ ...credentials
68
+ },
69
+ summary: summarizeGitHubCopilot(credentials)
70
+ };
71
+ },
72
+ stored: read,
73
+ refreshed: read,
74
+ logout: () => client.logout()
75
+ };
76
+ };
77
+ const makeOps = (provider, options = {}) => {
78
+ const file = options.path === void 0 ? {} : { path: options.path };
79
+ if (provider === "openai-codex") {
80
+ const storage = options.persist === false ? void 0 : new JsonOpenAICodexAuthStorage(file);
81
+ return codexOps(storage ?? transient, storage?.path ?? "");
82
+ }
83
+ const storage = options.persist === false ? void 0 : new JsonGitHubCopilotAuthStorage(file);
84
+ return copilotOps(storage ?? transient, storage?.path ?? "");
85
+ };
86
+ const attempt = (message, run) => Effect.tryPromise({
87
+ try: run,
88
+ catch: (cause) => new OAuthError({
89
+ message,
90
+ cause
91
+ })
92
+ });
93
+ /** The provider named by the two mutually exclusive flags. */
94
+ const selectProvider = (flags) => {
95
+ const checked = checkOAuthProvider(flags);
96
+ return checked.ok ? Effect.succeed(checked.provider) : Effect.fail(new InvalidInputError({ message: checked.message }));
97
+ };
98
+ /** Reject the local-credential flags when the credentials live on a server. */
99
+ const assertServerScope = (authFile) => Effect.gen(function* () {
100
+ const shared = yield* Cmd.spec;
101
+ if (Option.isSome(authFile) || Option.isSome(shared.home)) return yield* new InvalidInputError({ message: "--auth-file and --home belong on codework serve when using --server" });
102
+ });
103
+ /** Credential operations against this machine's auth.json. */
104
+ const localOps = (provider, authFile) => Effect.gen(function* () {
105
+ const shared = yield* Cmd.spec;
106
+ const path = yield* Path.Path;
107
+ const file = Option.isSome(authFile) ? authFile.value : Option.isSome(shared.home) ? path.join((yield* resolve({ home: shared.home.value })).home, "aikit", "auth.json") : void 0;
108
+ return makeOps(provider, file === void 0 ? {} : { path: file });
109
+ });
110
+ /** Run an effect against the RPC server at `url`, discharging the client. */
111
+ const withServer = (url, program) => program.pipe(Effect.provide(layer(url)), Effect.scoped);
112
+ //#endregion
113
+ export { oauthProviderLabel as a, makeOps as i, attempt as n, selectProvider as o, localOps as r, withServer as s, assertServerScope as t };
114
+
115
+ //# sourceMappingURL=shared-Uwjcofff.mjs.map