@dxos/plugin-registry 0.9.1-staging.ee54ba693a → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/dist/lib/RegistryPlugin.mjs +15 -0
  2. package/dist/lib/RegistryPlugin.mjs.map +1 -0
  3. package/dist/lib/RegistryPlugin.node.mjs +770 -0
  4. package/dist/lib/RegistryPlugin.node.mjs.map +1 -0
  5. package/dist/lib/RegistryPlugin.workerd.mjs +8 -0
  6. package/dist/lib/RegistryPlugin.workerd.mjs.map +1 -0
  7. package/dist/lib/capabilities.mjs +11 -0
  8. package/dist/lib/capabilities.mjs.map +1 -0
  9. package/dist/lib/chunk-BaseRegistryArticle.mjs +103 -0
  10. package/dist/lib/chunk-BaseRegistryArticle.mjs.map +1 -0
  11. package/dist/lib/chunk-LoadPluginDialog.mjs +2 -0
  12. package/dist/lib/chunk-PluginArticle.mjs +87 -0
  13. package/dist/lib/chunk-PluginArticle.mjs.map +1 -0
  14. package/dist/lib/chunk-PublicRegistryArticle.mjs +152 -0
  15. package/dist/lib/chunk-PublicRegistryArticle.mjs.map +1 -0
  16. package/dist/lib/chunk-RegistryArticle.mjs +29 -0
  17. package/dist/lib/chunk-RegistryArticle.mjs.map +1 -0
  18. package/dist/lib/chunk-RegistrySettingsContainer.mjs +37 -0
  19. package/dist/lib/chunk-RegistrySettingsContainer.mjs.map +1 -0
  20. package/dist/lib/chunk-app-graph-builder.mjs +202 -0
  21. package/dist/lib/chunk-app-graph-builder.mjs.map +1 -0
  22. package/dist/lib/chunk-categories.mjs +23 -0
  23. package/dist/lib/chunk-categories.mjs.map +1 -0
  24. package/dist/lib/chunk-components.mjs +537 -0
  25. package/dist/lib/chunk-components.mjs.map +1 -0
  26. package/dist/lib/chunk-containers.mjs +80 -0
  27. package/dist/lib/chunk-containers.mjs.map +1 -0
  28. package/dist/lib/chunk-dev-plugin-loader.mjs +40 -0
  29. package/dist/lib/chunk-dev-plugin-loader.mjs.map +1 -0
  30. package/dist/lib/chunk-hooks.mjs +345 -0
  31. package/dist/lib/chunk-hooks.mjs.map +1 -0
  32. package/dist/lib/chunk-meta.mjs +40 -0
  33. package/dist/lib/chunk-meta.mjs.map +1 -0
  34. package/dist/lib/chunk-open-plugin-registry.mjs +12 -0
  35. package/dist/lib/chunk-open-plugin-registry.mjs.map +1 -0
  36. package/dist/lib/chunk-operation-handler.mjs +11 -0
  37. package/dist/lib/chunk-operation-handler.mjs.map +1 -0
  38. package/dist/lib/chunk-react-surface.mjs +109 -0
  39. package/dist/lib/chunk-react-surface.mjs.map +1 -0
  40. package/dist/lib/chunk-settings.mjs +24 -0
  41. package/dist/lib/chunk-settings.mjs.map +1 -0
  42. package/dist/lib/chunk-storage.mjs +36 -0
  43. package/dist/lib/chunk-storage.mjs.map +1 -0
  44. package/dist/lib/components.mjs +2 -0
  45. package/dist/lib/containers.mjs +2 -0
  46. package/dist/lib/index.mjs +3 -0
  47. package/dist/lib/meta.mjs +2 -0
  48. package/dist/lib/operations.mjs +7 -0
  49. package/dist/lib/operations.mjs.map +1 -0
  50. package/dist/lib/plugin.mjs +9 -0
  51. package/dist/lib/plugin.mjs.map +1 -0
  52. package/dist/lib/translations.mjs +66 -0
  53. package/dist/lib/translations.mjs.map +1 -0
  54. package/dist/lib/types.mjs +29 -0
  55. package/dist/lib/types.mjs.map +1 -0
  56. package/dist/types/src/capabilities/RegistryCategoryArticle.d.ts +11 -0
  57. package/dist/types/src/capabilities/RegistryCategoryArticle.d.ts.map +1 -0
  58. package/dist/types/src/capabilities/app-graph-builder.d.ts +1 -1
  59. package/dist/types/src/capabilities/dev-plugin-loader.d.ts +1 -1
  60. package/dist/types/src/capabilities/operation-handler.d.ts +1 -1
  61. package/dist/types/src/capabilities/react-surface.d.ts +1 -1
  62. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  63. package/dist/types/src/capabilities/settings.d.ts +1 -1
  64. package/dist/types/src/categories.d.ts.map +1 -1
  65. package/dist/types/src/categories.test.d.ts +2 -0
  66. package/dist/types/src/categories.test.d.ts.map +1 -0
  67. package/dist/types/src/commands/registry/util.d.ts.map +1 -1
  68. package/dist/types/src/components/PluginDetail/PluginDetail.d.ts +1 -1
  69. package/dist/types/src/components/PluginDetail/PluginDetail.d.ts.map +1 -1
  70. package/dist/types/src/components/PluginDetail/PluginDetail.stories.d.ts +1 -1
  71. package/dist/types/src/containers/BaseRegistryArticle/BaseRegistryArticle.d.ts +1 -1
  72. package/dist/types/src/containers/BaseRegistryArticle/BaseRegistryArticle.d.ts.map +1 -1
  73. package/dist/types/src/containers/LoadPluginDialog/LoadPluginDialog.d.ts +4 -1
  74. package/dist/types/src/containers/LoadPluginDialog/LoadPluginDialog.d.ts.map +1 -1
  75. package/dist/types/src/containers/PluginArticle/PluginArticle.d.ts +4 -1
  76. package/dist/types/src/containers/PluginArticle/PluginArticle.d.ts.map +1 -1
  77. package/dist/types/src/containers/PublicRegistryArticle/PublicRegistryArticle.d.ts +1 -1
  78. package/dist/types/src/containers/PublicRegistryArticle/PublicRegistryArticle.d.ts.map +1 -1
  79. package/dist/types/src/containers/RegistryArticle/RegistryArticle.d.ts +1 -1
  80. package/dist/types/src/containers/RegistryArticle/RegistryArticle.d.ts.map +1 -1
  81. package/dist/types/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.d.ts +4 -7
  82. package/dist/types/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.d.ts.map +1 -1
  83. package/dist/types/src/hooks/usePluginActions.d.ts.map +1 -1
  84. package/dist/types/src/meta.d.ts +0 -3
  85. package/dist/types/src/meta.d.ts.map +1 -1
  86. package/dist/types/src/operations/open-plugin-registry.d.ts.map +1 -1
  87. package/dist/types/src/types.d.ts +2 -2
  88. package/dist/types/src/types.d.ts.map +1 -1
  89. package/dist/types/tsconfig.tsbuildinfo +1 -1
  90. package/package.json +38 -37
  91. package/src/capabilities/RegistryCategoryArticle.tsx +46 -0
  92. package/src/capabilities/app-graph-builder.ts +80 -74
  93. package/src/capabilities/react-surface.ts +85 -0
  94. package/src/categories.test.ts +45 -0
  95. package/src/categories.ts +10 -7
  96. package/src/commands/registry/util.ts +1 -1
  97. package/src/components/PluginList/PluginItem.tsx +5 -3
  98. package/src/components/PluginList/PluginList.stories.tsx +1 -1
  99. package/src/components/RegistrySettings/RegistrySettings.tsx +2 -2
  100. package/src/containers/BaseRegistryArticle/BaseRegistryArticle.tsx +2 -2
  101. package/src/containers/LoadPluginDialog/LoadPluginDialog.tsx +2 -0
  102. package/src/containers/PluginArticle/PluginArticle.tsx +5 -1
  103. package/src/containers/RegistrySettingsContainer/RegistrySettingsContainer.tsx +2 -0
  104. package/src/hooks/usePluginActions.ts +8 -4
  105. package/src/meta.ts +5 -8
  106. package/src/operations/open-plugin-registry.ts +2 -2
  107. package/src/types.ts +2 -1
  108. package/dist/lib/neutral/RegistryPlugin.mjs +0 -27
  109. package/dist/lib/neutral/RegistryPlugin.mjs.map +0 -7
  110. package/dist/lib/neutral/RegistryPlugin.node.mjs +0 -844
  111. package/dist/lib/neutral/RegistryPlugin.node.mjs.map +0 -7
  112. package/dist/lib/neutral/RegistryPlugin.workerd.mjs +0 -10
  113. package/dist/lib/neutral/RegistryPlugin.workerd.mjs.map +0 -7
  114. package/dist/lib/neutral/chunk-XK2JMH3H.mjs +0 -33
  115. package/dist/lib/neutral/chunk-XK2JMH3H.mjs.map +0 -7
  116. package/dist/lib/neutral/chunk-ZY37XLVF.mjs +0 -46
  117. package/dist/lib/neutral/chunk-ZY37XLVF.mjs.map +0 -7
  118. package/dist/lib/neutral/index.mjs +0 -25
  119. package/dist/lib/neutral/index.mjs.map +0 -7
  120. package/dist/lib/neutral/meta.json +0 -1
  121. package/dist/lib/neutral/open-plugin-registry-ID7LV3SF.mjs +0 -15
  122. package/dist/lib/neutral/open-plugin-registry-ID7LV3SF.mjs.map +0 -7
  123. package/dist/lib/neutral/plugin.mjs +0 -18
  124. package/dist/lib/neutral/plugin.mjs.map +0 -7
  125. package/dist/lib/neutral/translations.mjs +0 -72
  126. package/dist/lib/neutral/translations.mjs.map +0 -7
  127. package/src/capabilities/react-surface.tsx +0 -123
@@ -0,0 +1,770 @@
1
+ import { n as saveEnabledPlugins } from "./chunk-storage.mjs";
2
+ import { Plugin } from "@dxos/app-framework";
3
+ import { AppPlugin, AppSpace } from "@dxos/app-toolkit";
4
+ import * as FileSystem from "@effect/platform/FileSystem";
5
+ import * as Path from "@effect/platform/Path";
6
+ import * as Effect from "effect/Effect";
7
+ import * as Schema from "effect/Schema";
8
+ import { meta } from "#meta";
9
+ import * as Command from "@effect/cli/Command";
10
+ import * as Args from "@effect/cli/Args";
11
+ import * as Console from "effect/Console";
12
+ import { CommandConfig, FormBuilder, printList } from "@dxos/cli-util";
13
+ import { invariant } from "@dxos/invariant";
14
+ import * as Options from "@effect/cli/Options";
15
+ import * as PlatformCommand from "@effect/platform/Command";
16
+ import * as FetchHttpClient from "@effect/platform/FetchHttpClient";
17
+ import * as Function from "effect/Function";
18
+ import * as Option from "effect/Option";
19
+ import { findDxConfigFile, loadDxConfig } from "@dxos/app-framework/vite-plugin";
20
+ import { ClientService } from "@dxos/client";
21
+ import { Context } from "@dxos/context";
22
+ import { EdgeHttpClient } from "@dxos/edge-client";
23
+ import { ALL_NSIDS, Config2, NSID } from "@dxos/protocols";
24
+ import * as HttpClient from "@effect/platform/HttpClient";
25
+ import * as HttpClientRequest from "@effect/platform/HttpClientRequest";
26
+ import * as Config from "effect/Config";
27
+ import { Filter } from "@dxos/echo";
28
+ import { AccessToken } from "@dxos/link";
29
+ //#region src/commands/plugin/disable.ts
30
+ var __dxlog_file$2 = "/__w/dxos/dxos/packages/plugins/plugin-registry/src/commands/plugin/disable.ts";
31
+ var handler$2 = Effect.fn(function* ({ id }) {
32
+ const { json, profile } = yield* CommandConfig;
33
+ const manager = yield* Plugin.Service;
34
+ const plugin = manager.getPlugins().find((p) => p.meta.profile.key === id);
35
+ invariant(plugin, `Plugin not found: ${id}`, {
36
+ "~LogMeta": "~LogMeta",
37
+ F: __dxlog_file$2,
38
+ L: 22,
39
+ S: this,
40
+ A: ["plugin", "`Plugin not found: ${id}`"]
41
+ });
42
+ invariant(!manager.getCore().includes(id), `Cannot disable core plugin: ${id}`, {
43
+ "~LogMeta": "~LogMeta",
44
+ F: __dxlog_file$2,
45
+ L: 25,
46
+ S: this,
47
+ A: ["!core.includes(id)", "`Cannot disable core plugin: ${id}`"]
48
+ });
49
+ invariant(yield* manager.disable(id), `Failed to disable plugin: ${id}`, {
50
+ "~LogMeta": "~LogMeta",
51
+ F: __dxlog_file$2,
52
+ L: 28,
53
+ S: this,
54
+ A: ["disabled", "`Failed to disable plugin: ${id}`"]
55
+ });
56
+ yield* saveEnabledPlugins({
57
+ profile,
58
+ enabled: [...manager.getEnabled()]
59
+ });
60
+ if (json) yield* Console.log(JSON.stringify({
61
+ id,
62
+ enabled: false
63
+ }, null, 2));
64
+ else yield* Console.log(`Plugin "${plugin.meta.profile.name ?? id}" disabled.`);
65
+ });
66
+ var disable = Command.make("disable", { id: Args.text({ name: "id" }).pipe(Args.withDescription("The ID of the plugin to disable.")) }, handler$2).pipe(Command.withDescription("Disable a plugin."));
67
+ //#endregion
68
+ //#region src/commands/plugin/enable.ts
69
+ var __dxlog_file$1 = "/__w/dxos/dxos/packages/plugins/plugin-registry/src/commands/plugin/enable.ts";
70
+ var handler$1 = Effect.fn(function* ({ id }) {
71
+ const { json, profile } = yield* CommandConfig;
72
+ const manager = yield* Plugin.Service;
73
+ const plugin = manager.getPlugins().find((p) => p.meta.profile.key === id);
74
+ invariant(plugin, `Plugin not found: ${id}`, {
75
+ "~LogMeta": "~LogMeta",
76
+ F: __dxlog_file$1,
77
+ L: 22,
78
+ S: this,
79
+ A: ["plugin", "`Plugin not found: ${id}`"]
80
+ });
81
+ invariant(yield* manager.enable(id), `Failed to enable plugin: ${id}`, {
82
+ "~LogMeta": "~LogMeta",
83
+ F: __dxlog_file$1,
84
+ L: 25,
85
+ S: this,
86
+ A: ["enabled", "`Failed to enable plugin: ${id}`"]
87
+ });
88
+ yield* saveEnabledPlugins({
89
+ profile,
90
+ enabled: [...manager.getEnabled()]
91
+ });
92
+ if (json) yield* Console.log(JSON.stringify({
93
+ id,
94
+ enabled: true
95
+ }, null, 2));
96
+ else yield* Console.log(`Plugin "${plugin.meta.profile.name ?? id}" enabled.`);
97
+ });
98
+ var enable = Command.make("enable", { id: Args.text({ name: "id" }).pipe(Args.withDescription("The ID of the plugin to enable.")) }, handler$1).pipe(Command.withDescription("Enable a plugin."));
99
+ //#endregion
100
+ //#region src/commands/plugin/util.ts
101
+ /**
102
+ * Pretty prints a plugin with ANSI colors.
103
+ */
104
+ var printPlugin = (plugin) => {
105
+ const status = plugin.core ? "core" : plugin.enabled ? "enabled" : "disabled";
106
+ return FormBuilder.make({ title: plugin.name }).pipe(FormBuilder.set("id", plugin.id), FormBuilder.set("status", status), FormBuilder.build);
107
+ };
108
+ //#endregion
109
+ //#region src/commands/plugin/list.ts
110
+ var handler = Effect.fn(function* () {
111
+ const { json } = yield* CommandConfig;
112
+ const manager = yield* Plugin.Service;
113
+ const plugins = manager.getPlugins();
114
+ const enabled = manager.getEnabled();
115
+ const core = manager.getCore();
116
+ const formattedPlugins = plugins.map((plugin) => {
117
+ const isEnabled = enabled.includes(plugin.meta.profile.key);
118
+ const isCore = core.includes(plugin.meta.profile.key);
119
+ return {
120
+ id: plugin.meta.profile.key,
121
+ name: plugin.meta.profile.name ?? plugin.meta.profile.key,
122
+ enabled: isEnabled,
123
+ core: isCore
124
+ };
125
+ });
126
+ if (json) yield* Console.log(JSON.stringify(formattedPlugins, null, 2));
127
+ else {
128
+ const formatted = formattedPlugins.map(printPlugin);
129
+ yield* Console.log(printList(formatted));
130
+ }
131
+ });
132
+ var list = Command.make("list", {}, handler).pipe(Command.withDescription("List all available plugins."));
133
+ //#endregion
134
+ //#region src/commands/plugin/index.ts
135
+ var plugin = Command.make("plugin").pipe(Command.withDescription("Manage plugins."), Command.withSubcommands([
136
+ enable,
137
+ disable,
138
+ list
139
+ ]));
140
+ //#endregion
141
+ //#region src/commands/registry/util.ts
142
+ /**
143
+ * Shared helpers for the `dx registry *` commands.
144
+ *
145
+ * These commands write `org.dxos.experimental.*` records to a publisher's PDS via AT Protocol
146
+ * XRPC. Auth has two modes (see {@link resolveSession}):
147
+ *
148
+ * - **Personal-space (default):** when logged in (`dx account login`), the publisher's atproto
149
+ * session lives in their personal space as the "Atmosphere" `AccessToken`. Record writes are
150
+ * signed by Edge's DPoP proxy (`/atproto/proxy`) — no app password required.
151
+ * - **App password (fallback):** explicit `--handle` / `--app-password` (or `$ATPROTO_HANDLE` /
152
+ * `$ATPROTO_APP_PASSWORD`) authenticate directly against the PDS with a session token.
153
+ */
154
+ var ATMOSPHERE_SOURCE = "atproto.local";
155
+ var BSKY_PUBLIC_API = "https://public.api.bsky.app/xrpc";
156
+ var PLC_DIRECTORY = "https://plc.directory";
157
+ var CreateSessionResponseSchema = Schema.Struct({
158
+ did: Schema.String,
159
+ handle: Schema.String,
160
+ accessJwt: Schema.String,
161
+ refreshJwt: Schema.String
162
+ });
163
+ var ResolveHandleResponseSchema = Schema.Struct({ did: Schema.String });
164
+ var DidServiceSchema = Schema.Struct({
165
+ id: Schema.String,
166
+ type: Schema.optional(Schema.String),
167
+ serviceEndpoint: Schema.Unknown
168
+ });
169
+ var DidDocumentSchema = Schema.Struct({ service: Schema.optional(Schema.Array(DidServiceSchema)) });
170
+ var PutRecordResponseSchema = Schema.Struct({
171
+ uri: Schema.String,
172
+ cid: Schema.String
173
+ });
174
+ var ListRecordsEntrySchema = Schema.Struct({
175
+ uri: Schema.String,
176
+ cid: Schema.String,
177
+ value: Schema.Unknown
178
+ });
179
+ var ListRecordsResponseSchema = Schema.Struct({
180
+ cursor: Schema.optional(Schema.String),
181
+ records: Schema.Array(ListRecordsEntrySchema)
182
+ });
183
+ var decodeJson = (schema) => (response) => Effect.flatMap(response.json, Schema.decodeUnknown(schema));
184
+ /** GET an XRPC endpoint and decode the JSON response. */
185
+ var xrpcGet = (url, query, schema) => Effect.gen(function* () {
186
+ const client = yield* HttpClient.HttpClient;
187
+ return yield* HttpClientRequest.get(url).pipe(HttpClientRequest.setUrlParams(query), client.execute, Effect.flatMap(decodeJson(schema)), Effect.scoped);
188
+ });
189
+ /** POST a JSON body to an XRPC endpoint and decode the JSON response. */
190
+ var xrpcPost = (url, body, schema, headers = {}) => Effect.gen(function* () {
191
+ const client = yield* HttpClient.HttpClient;
192
+ const response = yield* HttpClientRequest.post(url).pipe(HttpClientRequest.setHeaders(headers), HttpClientRequest.bodyJson(body), Effect.flatMap((req) => client.execute(req)), Effect.scoped);
193
+ if (schema === void 0) return;
194
+ return yield* decodeJson(schema)(response);
195
+ });
196
+ /** Resolve a handle (e.g. `alice.bsky.social`) to a DID via the public XRPC. */
197
+ var resolveHandle = (handle) => xrpcGet(`${BSKY_PUBLIC_API}/com.atproto.identity.resolveHandle`, { handle }, ResolveHandleResponseSchema).pipe(Effect.map((response) => response.did));
198
+ /** Resolve a DID to its PDS service endpoint via the PLC directory (or did:web well-known). */
199
+ var resolvePds = (did) => Effect.gen(function* () {
200
+ let doc;
201
+ if (did.startsWith("did:plc:")) doc = yield* xrpcGet(`${PLC_DIRECTORY}/${did}`, {}, DidDocumentSchema);
202
+ else if (did.startsWith("did:web:")) {
203
+ const [host, ...segments] = did.slice(8).split(":").map((part) => decodeURIComponent(part));
204
+ doc = yield* xrpcGet(`https://${host}${segments.length > 0 ? `/${segments.join("/")}/did.json` : "/.well-known/did.json"}`, {}, DidDocumentSchema);
205
+ } else return yield* Effect.fail(/* @__PURE__ */ new Error(`Unsupported DID method: ${did}`));
206
+ const endpoint = (doc.service?.find((entry) => entry.id === "#atproto_pds" || entry.type === "AtprotoPersonalDataServer"))?.serviceEndpoint;
207
+ if (typeof endpoint !== "string" || endpoint.length === 0) return yield* Effect.fail(/* @__PURE__ */ new Error(`PDS endpoint missing in DID document for ${did}`));
208
+ return endpoint;
209
+ });
210
+ /** Resolve a handle or DID to its `{ did, pdsBaseUrl }`. */
211
+ var resolveRepo = (handleOrDid) => Effect.gen(function* () {
212
+ const did = handleOrDid.startsWith("did:") ? handleOrDid : yield* resolveHandle(handleOrDid);
213
+ return {
214
+ did,
215
+ pdsBaseUrl: yield* resolvePds(did)
216
+ };
217
+ });
218
+ /**
219
+ * App-password session — call `com.atproto.server.createSession` against the caller's resolved
220
+ * PDS, then return the access JWT, DID, and PDS endpoint needed for subsequent writes.
221
+ */
222
+ var createSession = (handle, appPassword) => Effect.gen(function* () {
223
+ const { did, pdsBaseUrl } = yield* resolveRepo(handle);
224
+ const response = yield* xrpcPost(`${pdsBaseUrl}/xrpc/com.atproto.server.createSession`, {
225
+ identifier: handle,
226
+ password: appPassword
227
+ }, CreateSessionResponseSchema);
228
+ return {
229
+ mode: "app-password",
230
+ did: response.did,
231
+ handle: response.handle,
232
+ accessJwt: response.accessJwt,
233
+ pdsBaseUrl
234
+ };
235
+ });
236
+ /**
237
+ * Resolve an authenticated {@link Session} for the registry commands, in precedence order:
238
+ *
239
+ * 1. Explicit `--handle` + `--app-password` (or `$ATPROTO_HANDLE` / `$ATPROTO_APP_PASSWORD`) →
240
+ * app-password session (works without a DXOS identity).
241
+ * 2. The logged-in identity's personal-space "Atmosphere" `AccessToken` → DPoP-proxy session.
242
+ * 3. A context-specific error guiding the user to log in / connect an integration / pass creds.
243
+ */
244
+ var resolveSession = (options) => Effect.gen(function* () {
245
+ const handle = options.handle ?? Option.getOrUndefined(yield* Config.option(Config.string("ATPROTO_HANDLE")));
246
+ const appPassword = options.appPassword ?? Option.getOrUndefined(yield* Config.option(Config.string("ATPROTO_APP_PASSWORD")));
247
+ if (handle && appPassword) return yield* createSession(handle, appPassword);
248
+ const fromPersonalSpace = yield* resolvePersonalSpaceSession(options.client);
249
+ if (fromPersonalSpace) return fromPersonalSpace;
250
+ if (options.client.halo.identity.get()) return yield* Effect.fail(/* @__PURE__ */ new Error("No atproto integration connected. Connect one with `dx integration add`, or pass --handle/--app-password. (`dx account login` cannot add an integration to an existing identity.)"));
251
+ return yield* Effect.fail(/* @__PURE__ */ new Error("No DXOS identity. Run `dx account login` first, or pass --handle/--app-password."));
252
+ });
253
+ /**
254
+ * Build a DPoP-proxy session from the personal space's "Atmosphere" `AccessToken`, or `undefined`
255
+ * if the user isn't logged in or hasn't connected an atproto integration.
256
+ */
257
+ var resolvePersonalSpaceSession = (client) => Effect.gen(function* () {
258
+ const space = AppSpace.getPersonalSpace(client);
259
+ if (!space) return;
260
+ const token = (yield* Effect.promise(() => space.db.query(Filter.type(AccessToken.AccessToken)).run())).find((object) => object.source === ATMOSPHERE_SOURCE && !!object.account && !!object.token);
261
+ if (!token?.account) return;
262
+ const { did, pdsBaseUrl } = yield* resolveRepo(token.account);
263
+ return {
264
+ mode: "dpop-proxy",
265
+ did,
266
+ handle: token.account,
267
+ pdsBaseUrl,
268
+ edgeBaseUrl: client.edge.http.baseUrl,
269
+ spaceId: space.id,
270
+ accessTokenId: token.id,
271
+ token: token.token,
272
+ authHeader: client.edge.http._authHeader
273
+ };
274
+ });
275
+ /**
276
+ * Proxy an XRPC call through Edge's `/atproto/proxy`, which attaches the DPoP proof signed with
277
+ * the key Edge stored at OAuth time. The proxy forwards `request` verbatim and returns the
278
+ * upstream PDS response body, so callers decode the same schema as the direct path.
279
+ */
280
+ var proxyCall = (session, params, schema) => {
281
+ const query = params.query ? `?${new URLSearchParams(params.query).toString()}` : "";
282
+ const innerHeaders = {
283
+ Accept: "application/json",
284
+ Authorization: `DPoP ${session.token}`
285
+ };
286
+ if (params.jsonBody) innerHeaders["Content-Type"] = "application/json";
287
+ const outerHeaders = {};
288
+ if (session.authHeader) outerHeaders.Authorization = session.authHeader;
289
+ return xrpcPost(`${session.edgeBaseUrl.replace(/\/$/, "")}/atproto/proxy`, {
290
+ spaceId: session.spaceId,
291
+ accessTokenId: session.accessTokenId,
292
+ request: {
293
+ endpoint: `${session.pdsBaseUrl}/xrpc/${params.xrpcMethod}${query}`,
294
+ method: params.method,
295
+ headers: innerHeaders,
296
+ body: params.jsonBody ? JSON.stringify(params.jsonBody) : null
297
+ }
298
+ }, schema, outerHeaders);
299
+ };
300
+ /** Write a record at `com.atproto.repo.putRecord` (idempotent on rkey). */
301
+ var putRecord = (session, collection, rkey, record) => {
302
+ const body = {
303
+ repo: session.did,
304
+ collection,
305
+ rkey,
306
+ record: {
307
+ $type: collection,
308
+ ...record
309
+ }
310
+ };
311
+ return session.mode === "app-password" ? xrpcPost(`${session.pdsBaseUrl}/xrpc/com.atproto.repo.putRecord`, body, PutRecordResponseSchema, { Authorization: `Bearer ${session.accessJwt}` }) : proxyCall(session, {
312
+ xrpcMethod: "com.atproto.repo.putRecord",
313
+ method: "POST",
314
+ jsonBody: body
315
+ }, PutRecordResponseSchema);
316
+ };
317
+ /** Delete a record at `com.atproto.repo.deleteRecord`. */
318
+ var deleteRecord = (session, collection, rkey) => {
319
+ const body = {
320
+ repo: session.did,
321
+ collection,
322
+ rkey
323
+ };
324
+ return session.mode === "app-password" ? xrpcPost(`${session.pdsBaseUrl}/xrpc/com.atproto.repo.deleteRecord`, body, void 0, { Authorization: `Bearer ${session.accessJwt}` }) : proxyCall(session, {
325
+ xrpcMethod: "com.atproto.repo.deleteRecord",
326
+ method: "POST",
327
+ jsonBody: body
328
+ }, void 0);
329
+ };
330
+ /**
331
+ * List records in a single collection on the authenticated repo. Paging is left to the caller (the
332
+ * four registry collections are tiny in practice); the cursor is surfaced on the return.
333
+ */
334
+ var listRecords = (session, collection, options = {}) => {
335
+ const query = {
336
+ repo: session.did,
337
+ collection,
338
+ ...options.limit !== void 0 ? { limit: String(options.limit) } : {},
339
+ ...options.cursor !== void 0 ? { cursor: options.cursor } : {}
340
+ };
341
+ return session.mode === "app-password" ? xrpcGet(`${session.pdsBaseUrl}/xrpc/com.atproto.repo.listRecords`, query, ListRecordsResponseSchema) : proxyCall(session, {
342
+ xrpcMethod: "com.atproto.repo.listRecords",
343
+ method: "GET",
344
+ query
345
+ }, ListRecordsResponseSchema);
346
+ };
347
+ var AUTH_OPTION_DESCRIPTIONS = {
348
+ handle: "AT Protocol handle (e.g. alice.bsky.social). Defaults to the logged-in identity; falls back to $ATPROTO_HANDLE.",
349
+ appPassword: "AT Protocol app password (https://bsky.app/settings/app-passwords). Falls back to $ATPROTO_APP_PASSWORD. Not needed when logged in via `dx account login`."
350
+ };
351
+ //#endregion
352
+ //#region src/commands/registry/publish.ts
353
+ var __dxlog_file = "/__w/dxos/dxos/packages/plugins/plugin-registry/src/commands/registry/publish.ts";
354
+ /** Manifest emitted by the build (subset consumed here). Extends `Config2.Plugin` with build-time fields. */
355
+ var ManifestSchema = Schema.Struct({
356
+ ...Config2.Plugin.fields,
357
+ version: Schema.String.pipe(Schema.nonEmptyString()),
358
+ dependencies: Schema.optional(Schema.Record({
359
+ key: Schema.String,
360
+ value: Schema.String
361
+ }))
362
+ });
363
+ var ensureTrailingSlash = (url) => url.endsWith("/") ? url : `${url}/`;
364
+ var sha256Base64 = async (bytes) => {
365
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
366
+ return Buffer.from(new Uint8Array(digest)).toString("base64");
367
+ };
368
+ /**
369
+ * `dx registry publish` — config-driven build + publish.
370
+ *
371
+ * Reads the build/publish orchestration from `dx.config.ts`, runs the declared build command,
372
+ * reads the emitted `manifest.json`, hosts the bundle (default: upload the build
373
+ * output to the DXOS edge registry; override with `publish.assetBaseUrl` to point
374
+ * at your own already-hosted directory), then writes the `plugin.profile` and
375
+ * `plugin.release` records to the authenticated publisher's PDS. Release
376
+ * integrity is anchored by `manifestHash` (sha256 of `manifest.json`) in the
377
+ * signed release record.
378
+ */
379
+ var publish = Command.make("publish", {
380
+ handle: Options.text("handle").pipe(Options.withDescription(AUTH_OPTION_DESCRIPTIONS.handle), Options.optional),
381
+ appPassword: Options.text("app-password").pipe(Options.withDescription(AUTH_OPTION_DESCRIPTIONS.appPassword), Options.optional),
382
+ dir: Options.text("dir").pipe(Options.withDescription("Project directory containing dx.config.ts. Defaults to the current directory."), Options.withDefault(".")),
383
+ noBuild: Options.boolean("no-build").pipe(Options.withDescription("Skip running the build command (publish a pre-built dist).")),
384
+ assetBaseUrl: Options.text("asset-base-url").pipe(Options.withDescription("Skip upload and point the release at an already-hosted bundle directory."), Options.optional),
385
+ edgeUrl: Options.text("edge-url").pipe(Options.withDescription("Edge base URL for bundle upload (e.g. http://localhost:8787). Bypasses profile config; auth is skipped (requires WORKER_ENV=dev on the server)."), Options.optional)
386
+ }, (options) => Function.pipe(Effect.gen(function* () {
387
+ const fs = yield* FileSystem.FileSystem;
388
+ const path = yield* Path.Path;
389
+ const dir = path.resolve(options.dir);
390
+ const configFile = findDxConfigFile(dir);
391
+ if (!configFile) return yield* Effect.fail(/* @__PURE__ */ new Error(`No dx.config.ts found in ${dir}.`));
392
+ const config = yield* Effect.tryPromise({
393
+ try: () => loadDxConfig(configFile),
394
+ catch: (error) => /* @__PURE__ */ new Error(`Failed to load dx.config.ts in ${dir}: ${error}`)
395
+ });
396
+ const buildCommand = config.publish?.buildCommand;
397
+ if (!options.noBuild && buildCommand) {
398
+ yield* Console.log(`Building: ${buildCommand}`);
399
+ const binDir = path.join(dir, "node_modules", ".bin");
400
+ const exitCode = yield* PlatformCommand.make("sh", "-c", `export PATH="${binDir}:$PATH"; ${buildCommand}`).pipe(PlatformCommand.workingDirectory(dir), PlatformCommand.stdout("inherit"), PlatformCommand.stderr("inherit"), PlatformCommand.exitCode);
401
+ if (exitCode !== 0) return yield* Effect.fail(/* @__PURE__ */ new Error(`Build failed (exit ${exitCode}): ${buildCommand}`));
402
+ }
403
+ const outdir = path.join(dir, config.publish?.outputDirectory ?? "dist");
404
+ const manifestPath = path.join(outdir, "manifest.json");
405
+ if (!(yield* fs.exists(manifestPath))) return yield* Effect.fail(/* @__PURE__ */ new Error(`manifest.json not found in ${outdir}. Did the build run?`));
406
+ const manifestRaw = yield* fs.readFileString(manifestPath);
407
+ const manifest = yield* Schema.decodeUnknown(ManifestSchema)(JSON.parse(manifestRaw));
408
+ const key = manifest.key;
409
+ const version = manifest.version;
410
+ const manifestHash = `sha256-${yield* Effect.promise(() => sha256Base64(new TextEncoder().encode(manifestRaw)))}`;
411
+ const assetBaseUrl = Option.getOrUndefined(options.assetBaseUrl) ?? config.publish?.assetBaseUrl;
412
+ let moduleUrl;
413
+ if (assetBaseUrl) {
414
+ moduleUrl = new URL("manifest.json", ensureTrailingSlash(assetBaseUrl)).toString();
415
+ yield* Console.log(`Self-hosted: ${moduleUrl}`);
416
+ } else {
417
+ const explicitEdgeUrl = Option.getOrUndefined(options.edgeUrl);
418
+ if (explicitEdgeUrl) moduleUrl = yield* uploadBundleDirect({
419
+ http: new EdgeHttpClient(explicitEdgeUrl),
420
+ key,
421
+ version,
422
+ outdir
423
+ });
424
+ else {
425
+ const client = yield* ClientService;
426
+ moduleUrl = yield* uploadBundle({
427
+ client,
428
+ key,
429
+ version,
430
+ outdir,
431
+ auth: !!client.halo.identity.get()
432
+ });
433
+ }
434
+ yield* Console.log(`Uploaded: ${moduleUrl}`);
435
+ }
436
+ const client = yield* ClientService;
437
+ const session = yield* resolveSession({
438
+ handle: Option.getOrUndefined(options.handle),
439
+ appPassword: Option.getOrUndefined(options.appPassword),
440
+ client
441
+ });
442
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
443
+ const profile = {
444
+ key,
445
+ name: manifest.name,
446
+ createdAt
447
+ };
448
+ if (manifest.description !== void 0) profile.description = manifest.description;
449
+ if (manifest.homePage !== void 0) profile.homePage = manifest.homePage;
450
+ if (manifest.source !== void 0) profile.source = manifest.source;
451
+ if (manifest.icon !== void 0) profile.icon = manifest.icon;
452
+ if (manifest.tags && manifest.tags.length > 0) profile.tags = manifest.tags;
453
+ if (manifest.screenshots && manifest.screenshots.length > 0) profile.screenshots = manifest.screenshots;
454
+ if (manifest.dependsOn?.length) profile.dependsOn = manifest.dependsOn;
455
+ if (manifest.spec !== void 0) profile.spec = manifest.spec;
456
+ const profileResult = yield* putRecord(session, NSID.PluginProfile, key, profile);
457
+ yield* Console.log(`Profile ${profileResult.uri}`);
458
+ const releaseResult = yield* putRecord(session, NSID.PluginRelease, `${key}:${version}`, {
459
+ pluginKey: key,
460
+ version,
461
+ moduleUrl,
462
+ manifestHash,
463
+ createdAt,
464
+ ...manifest.dependencies ? { dependencies: manifest.dependencies } : {}
465
+ });
466
+ yield* Console.log(`Release ${releaseResult.uri}`);
467
+ }), Effect.provide(FetchHttpClient.layer))).pipe(Command.withDescription("Build, host, and publish the plugin in the current directory to the registry."));
468
+ /**
469
+ * Upload the build output to the edge registry via the authenticated edge client.
470
+ * The edge gates `/registry/upload` on the caller's hub identity (verifiable
471
+ * presentation; bypassed for local dev). Release integrity is anchored by the
472
+ * signed record's `manifestHash`, not the transport. Returns the canonical
473
+ * `moduleUrl` (the hosted `manifest.json`), targeting the client's configured edge.
474
+ */
475
+ var uploadBundle = ({ client, key, version, outdir, auth = true }) => Effect.gen(function* () {
476
+ const fs = yield* FileSystem.FileSystem;
477
+ const path = yield* Path.Path;
478
+ const entries = yield* fs.readDirectory(outdir, { recursive: true });
479
+ const files = [];
480
+ for (const entry of entries) {
481
+ const full = path.join(outdir, entry);
482
+ if ((yield* fs.stat(full)).type !== "File") continue;
483
+ const bytes = yield* fs.readFile(full);
484
+ files.push({
485
+ path: entry.split(path.sep).join("/"),
486
+ content: Buffer.from(bytes).toString("base64")
487
+ });
488
+ }
489
+ const { moduleUrl } = yield* Effect.tryPromise(() => client.edge.http.uploadPluginBundle(Context.default(void 0, {
490
+ "~LogMeta": "~LogMeta",
491
+ F: __dxlog_file,
492
+ L: 238
493
+ }), {
494
+ slug: key,
495
+ version,
496
+ files
497
+ }, { auth }));
498
+ return moduleUrl;
499
+ });
500
+ /**
501
+ * Upload using a standalone EdgeHttpClient (no DXOS profile required).
502
+ * Auth is skipped — the server must have WORKER_ENV=dev to accept unauthenticated uploads.
503
+ */
504
+ var uploadBundleDirect = ({ http, key, version, outdir }) => Effect.gen(function* () {
505
+ const fs = yield* FileSystem.FileSystem;
506
+ const path = yield* Path.Path;
507
+ const entries = yield* fs.readDirectory(outdir, { recursive: true });
508
+ const files = [];
509
+ for (const entry of entries) {
510
+ const full = path.join(outdir, entry);
511
+ if ((yield* fs.stat(full)).type !== "File") continue;
512
+ const bytes = yield* fs.readFile(full);
513
+ files.push({
514
+ path: entry.split(path.sep).join("/"),
515
+ content: Buffer.from(bytes).toString("base64")
516
+ });
517
+ }
518
+ const { moduleUrl } = yield* Effect.tryPromise(() => http.uploadPluginBundle(Context.default(void 0, {
519
+ "~LogMeta": "~LogMeta",
520
+ F: __dxlog_file,
521
+ L: 275
522
+ }), {
523
+ slug: key,
524
+ version,
525
+ files
526
+ }, { auth: false }));
527
+ return moduleUrl;
528
+ });
529
+ //#endregion
530
+ //#region src/commands/registry/publish-package.ts
531
+ /**
532
+ * `dx registry publish-package` — publishes both the mutable
533
+ * `plugin.profile` (rkey = key) and a `plugin.release`
534
+ * (rkey = `<key>:<version>`) record to the authenticated user's PDS.
535
+ *
536
+ * The two writes are independent `putRecord` (upsert) calls; if the second fails the
537
+ * first is left in place (the indexer treats a profile with no releases as
538
+ * present-but-unpublishable, so a half-success is benign). Releases are conceptually
539
+ * single-write per version but not enforced — see docs/registry-spec.md (Integrity).
540
+ */
541
+ var publishPackage = Command.make("publish-package", {
542
+ handle: Options.text("handle").pipe(Options.withDescription(AUTH_OPTION_DESCRIPTIONS.handle), Options.optional),
543
+ appPassword: Options.text("app-password").pipe(Options.withDescription(AUTH_OPTION_DESCRIPTIONS.appPassword), Options.optional),
544
+ key: Options.text("key").pipe(Options.withDescription("Plugin key — a reverse-domain NSID (e.g. org.dxos.plugin.excalidraw); also the profile record rkey.")),
545
+ name: Options.text("name").pipe(Options.withDescription("Human-readable package name.")),
546
+ description: Options.text("description").pipe(Options.withDescription("Short description shown in the registry."), Options.withDefault("")),
547
+ version: Options.text("version").pipe(Options.withDescription("Semver version (no build metadata). The release rkey is `<key>:<version>`.")),
548
+ moduleUrl: Options.text("module-url").pipe(Options.withDescription("HTTPS URL to the bundle or manifest for this release.")),
549
+ homepage: Options.text("homepage").pipe(Options.withDescription("Homepage URL."), Options.optional),
550
+ source: Options.text("source").pipe(Options.withDescription("Source repository URL."), Options.optional),
551
+ tag: Options.text("tag").pipe(Options.withDescription("Tag (repeatable). Categorizes the package for discovery."), Options.repeated),
552
+ iconKey: Options.text("icon-key").pipe(Options.withDescription("Icon identifier (e.g. ph--sparkle--regular) resolvable by @ch-ui/icons."), Options.optional),
553
+ iconHue: Options.text("icon-hue").pipe(Options.withDescription("Icon palette hue (e.g. blue)."), Options.optional),
554
+ manifestHash: Options.text("manifest-hash").pipe(Options.withDescription("Optional content hash for the bundle/manifest."), Options.optional)
555
+ }, (options) => Function.pipe(Effect.gen(function* () {
556
+ const client = yield* ClientService;
557
+ const session = yield* resolveSession({
558
+ handle: Option.getOrUndefined(options.handle),
559
+ appPassword: Option.getOrUndefined(options.appPassword),
560
+ client
561
+ });
562
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
563
+ const homepage = Option.getOrUndefined(options.homepage);
564
+ const source = Option.getOrUndefined(options.source);
565
+ const iconKey = Option.getOrUndefined(options.iconKey);
566
+ const iconHue = Option.getOrUndefined(options.iconHue);
567
+ const icon = iconKey !== void 0 ? {
568
+ key: iconKey,
569
+ ...iconHue !== void 0 && { hue: iconHue }
570
+ } : void 0;
571
+ const profile = {
572
+ key: options.key,
573
+ name: options.name,
574
+ ...options.description.length > 0 && { description: options.description },
575
+ ...homepage !== void 0 && { homePage: homepage },
576
+ ...source !== void 0 && { source },
577
+ ...options.tag.length > 0 && { tags: options.tag },
578
+ ...icon !== void 0 && { icon },
579
+ createdAt
580
+ };
581
+ const profileResult = yield* putRecord(session, NSID.PluginProfile, options.key, profile);
582
+ yield* Console.log(`Profile ${profileResult.uri}`);
583
+ const manifestHash = Option.getOrUndefined(options.manifestHash);
584
+ const release = {
585
+ pluginKey: options.key,
586
+ version: options.version,
587
+ moduleUrl: options.moduleUrl,
588
+ createdAt,
589
+ ...manifestHash !== void 0 && { manifestHash }
590
+ };
591
+ const releaseResult = yield* putRecord(session, NSID.PluginRelease, `${options.key}:${options.version}`, release);
592
+ yield* Console.log(`Release ${releaseResult.uri}`);
593
+ }), Effect.provide(FetchHttpClient.layer))).pipe(Command.withDescription("Publish a package profile + release to the authenticated user's PDS."));
594
+ //#endregion
595
+ //#region src/commands/registry/publish-publisher.ts
596
+ /**
597
+ * `dx registry publish-publisher` — publishes the authenticated user's own
598
+ * `org.dxos.experimental.publisher.profile` record (rkey = `self`).
599
+ *
600
+ * This is metadata about the publisher (the human/org), shown alongside their
601
+ * packages once a verifier has attested to their identity.
602
+ */
603
+ var publishPublisher = Command.make("publish-publisher", {
604
+ handle: Options.text("handle").pipe(Options.withDescription(AUTH_OPTION_DESCRIPTIONS.handle), Options.optional),
605
+ appPassword: Options.text("app-password").pipe(Options.withDescription(AUTH_OPTION_DESCRIPTIONS.appPassword), Options.optional),
606
+ displayName: Options.text("display-name").pipe(Options.withDescription("Publisher display name (the human/org name shown in UIs).")),
607
+ bio: Options.text("bio").pipe(Options.withDescription("Short bio."), Options.optional),
608
+ homepageUrl: Options.text("homepage-url").pipe(Options.withDescription("Publisher homepage URL."), Options.optional),
609
+ contact: Options.text("contact").pipe(Options.withDescription("Contact (email, handle, etc.)."), Options.optional)
610
+ }, (options) => Function.pipe(Effect.gen(function* () {
611
+ const client = yield* ClientService;
612
+ const session = yield* resolveSession({
613
+ handle: Option.getOrUndefined(options.handle),
614
+ appPassword: Option.getOrUndefined(options.appPassword),
615
+ client
616
+ });
617
+ const record = { displayName: options.displayName };
618
+ const bio = Option.getOrUndefined(options.bio);
619
+ if (bio !== void 0) record.bio = bio;
620
+ const homepageUrl = Option.getOrUndefined(options.homepageUrl);
621
+ if (homepageUrl !== void 0) record.homepageUrl = homepageUrl;
622
+ const contact = Option.getOrUndefined(options.contact);
623
+ if (contact !== void 0) record.contact = contact;
624
+ const result = yield* putRecord(session, NSID.PublisherProfile, "self", record);
625
+ yield* Console.log(`Publisher ${result.uri}`);
626
+ }), Effect.provide(FetchHttpClient.layer))).pipe(Command.withDescription("Publish the authenticated user's publisher.profile record (rkey = self)."));
627
+ //#endregion
628
+ //#region src/commands/registry/records.ts
629
+ /**
630
+ * `dx registry records` — lists all `org.dxos.experimental.*` records on the
631
+ * authenticated user's repo. Useful for double-checking what's been published
632
+ * and what the registry indexer is about to ingest.
633
+ */
634
+ var records = Command.make("records", {
635
+ handle: Options.text("handle").pipe(Options.withDescription(AUTH_OPTION_DESCRIPTIONS.handle), Options.optional),
636
+ appPassword: Options.text("app-password").pipe(Options.withDescription(AUTH_OPTION_DESCRIPTIONS.appPassword), Options.optional)
637
+ }, (options) => Function.pipe(Effect.gen(function* () {
638
+ const { json } = yield* CommandConfig;
639
+ const client = yield* ClientService;
640
+ const session = yield* resolveSession({
641
+ handle: Option.getOrUndefined(options.handle),
642
+ appPassword: Option.getOrUndefined(options.appPassword),
643
+ client
644
+ });
645
+ const results = yield* Effect.all(ALL_NSIDS.map((collection) => listRecords(session, collection).pipe(Effect.map(({ records: entries }) => ({
646
+ collection,
647
+ records: entries
648
+ })), Effect.catchAll(() => Effect.succeed({
649
+ collection,
650
+ records: []
651
+ })))), { concurrency: "unbounded" });
652
+ if (json) {
653
+ yield* Console.log(JSON.stringify({
654
+ did: session.did,
655
+ results
656
+ }, null, 2));
657
+ return;
658
+ }
659
+ yield* Console.log(`Repo: ${session.did} (${session.handle})`);
660
+ for (const { collection, records: entries } of results) {
661
+ yield* Console.log(`\n${collection} (${entries.length})`);
662
+ for (const entry of entries) yield* Console.log(` ${entry.uri}`);
663
+ }
664
+ }), Effect.provide(FetchHttpClient.layer))).pipe(Command.withDescription("List org.dxos.experimental.* records on the authenticated user's repo."));
665
+ //#endregion
666
+ //#region src/commands/registry/unpublish.ts
667
+ var rkeyOf = (uri) => uri.split("/").pop() ?? "";
668
+ /**
669
+ * `dx registry unpublish` — removes a plugin from the authenticated user's PDS:
670
+ * deletes the `plugin.profile` (rkey = key) and every `plugin.release`
671
+ * (rkey = `<key>:<version>`). The registry indexer unindexes on its next sweep.
672
+ * Hosted bundles in edge R2 are immutable and are not removed.
673
+ */
674
+ var unpublish = Command.make("unpublish", {
675
+ handle: Options.text("handle").pipe(Options.withDescription(AUTH_OPTION_DESCRIPTIONS.handle), Options.optional),
676
+ appPassword: Options.text("app-password").pipe(Options.withDescription(AUTH_OPTION_DESCRIPTIONS.appPassword), Options.optional),
677
+ key: Options.text("key").pipe(Options.withDescription("Plugin key (the profile rkey). Removes its profile and all releases."))
678
+ }, (options) => Function.pipe(Effect.gen(function* () {
679
+ const client = yield* ClientService;
680
+ const session = yield* resolveSession({
681
+ handle: Option.getOrUndefined(options.handle),
682
+ appPassword: Option.getOrUndefined(options.appPassword),
683
+ client
684
+ });
685
+ const key = options.key;
686
+ let cursor;
687
+ let deletedReleases = 0;
688
+ do {
689
+ const releases = yield* listRecords(session, NSID.PluginRelease, {
690
+ limit: 100,
691
+ cursor
692
+ });
693
+ cursor = releases.cursor;
694
+ for (const record of releases.records) {
695
+ const rkey = rkeyOf(record.uri);
696
+ if (rkey.startsWith(`${key}:`)) {
697
+ yield* deleteRecord(session, NSID.PluginRelease, rkey);
698
+ yield* Console.log(`Deleted release ${rkey}`);
699
+ deletedReleases += 1;
700
+ }
701
+ }
702
+ } while (cursor !== void 0);
703
+ yield* deleteRecord(session, NSID.PluginProfile, key);
704
+ yield* Console.log(`Deleted profile ${key} (${deletedReleases} release(s) removed)`);
705
+ }), Effect.provide(FetchHttpClient.layer))).pipe(Command.withDescription("Remove a package (profile + releases) from the authenticated user's PDS."));
706
+ //#endregion
707
+ //#region src/commands/registry/verify.ts
708
+ /**
709
+ * `dx registry verify` — publishes a `org.dxos.experimental.publisher.verification`
710
+ * record on the authenticated repo, with rkey = subject DID. Subject is given as a
711
+ * handle or DID; handles are resolved through the public XRPC.
712
+ *
713
+ * Verification records are public and anyone may author them. The registry indexer only
714
+ * honors records authored by the configured verifier (`REGISTRY_CURATOR_DID`), so this
715
+ * command only affects discovery when run with that verifier's credentials.
716
+ */
717
+ var verify = Command.make("verify", {
718
+ handle: Options.text("handle").pipe(Options.withDescription(AUTH_OPTION_DESCRIPTIONS.handle), Options.optional),
719
+ appPassword: Options.text("app-password").pipe(Options.withDescription(AUTH_OPTION_DESCRIPTIONS.appPassword), Options.optional),
720
+ subject: Options.text("subject").pipe(Options.withDescription("Handle or DID of the publisher being verified.")),
721
+ subjectHandle: Options.text("subject-handle").pipe(Options.withDescription("Optional human-readable handle to record for the verified subject (defaults to --subject if it is a handle)."), Options.optional),
722
+ displayName: Options.text("display-name").pipe(Options.withDescription("Display name to record for the verified subject."))
723
+ }, (options) => Function.pipe(Effect.gen(function* () {
724
+ const client = yield* ClientService;
725
+ const session = yield* resolveSession({
726
+ handle: Option.getOrUndefined(options.handle),
727
+ appPassword: Option.getOrUndefined(options.appPassword),
728
+ client
729
+ });
730
+ const subjectDid = options.subject.startsWith("did:") ? options.subject : yield* resolveHandle(options.subject);
731
+ const subjectHandle = Option.getOrUndefined(options.subjectHandle) ?? (options.subject.startsWith("did:") ? "" : options.subject);
732
+ const record = {
733
+ subject: subjectDid,
734
+ ...subjectHandle ? { handle: subjectHandle } : {},
735
+ displayName: options.displayName,
736
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
737
+ };
738
+ const result = yield* putRecord(session, NSID.PublisherVerification, subjectDid, record);
739
+ yield* Console.log(`Verified ${subjectDid} -> ${result.uri}`);
740
+ }), Effect.provide(FetchHttpClient.layer))).pipe(Command.withDescription("Write a publisher.verification record (indexed only if authored by the configured verifier, REGISTRY_CURATOR_DID)."));
741
+ //#endregion
742
+ //#region src/commands/registry/index.ts
743
+ /**
744
+ * `dx registry *` — commands for publishing and inspecting records that the
745
+ * DXOS plugin registry indexes from AT Protocol. Auth uses an App Password
746
+ * (per-user secret minted at https://bsky.app/settings/app-passwords); OAuth
747
+ * support is a follow-up.
748
+ *
749
+ * - `publish` — config-driven: build from dx.yml, host the bundle, write records.
750
+ * - `publish-package` — low-level: write profile + release records from flags.
751
+ * - `publish-publisher` — write the publisher's own profile record.
752
+ * - `unpublish` — remove a package (profile + releases) from your PDS.
753
+ * - `verify` — write a publisher.verification record (indexed only from the configured verifier).
754
+ * - `records` — list everything the authenticated repo has published under `org.dxos.experimental.*`.
755
+ */
756
+ var registry = Command.make("registry").pipe(Command.withDescription("Publish to and inspect the AT Protocol-backed DXOS plugin registry."), Command.withSubcommands([
757
+ publish,
758
+ publishPackage,
759
+ publishPublisher,
760
+ unpublish,
761
+ verify,
762
+ records
763
+ ]));
764
+ //#endregion
765
+ //#region src/RegistryPlugin.node.ts
766
+ var RegistryPlugin = Plugin.define(meta).pipe(AppPlugin.addCommandModule({ commands: [plugin, registry] }), Plugin.make);
767
+ //#endregion
768
+ export { RegistryPlugin, RegistryPlugin as default };
769
+
770
+ //# sourceMappingURL=RegistryPlugin.node.mjs.map