@dxos/plugin-file 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 (174) hide show
  1. package/PLUGIN.mdl +86 -77
  2. package/dist/lib/FilePlugin.mjs +37 -0
  3. package/dist/lib/FilePlugin.mjs.map +1 -0
  4. package/dist/lib/FilePlugin.node.mjs +12 -0
  5. package/dist/lib/FilePlugin.node.mjs.map +1 -0
  6. package/dist/lib/capabilities/node.mjs +8 -0
  7. package/dist/lib/capabilities/node.mjs.map +1 -0
  8. package/dist/lib/capabilities.mjs +15 -0
  9. package/dist/lib/capabilities.mjs.map +1 -0
  10. package/dist/lib/chunk-FileArticle.mjs +62 -0
  11. package/dist/lib/chunk-FileArticle.mjs.map +1 -0
  12. package/dist/lib/chunk-create-object.mjs +28 -0
  13. package/dist/lib/chunk-create-object.mjs.map +1 -0
  14. package/dist/lib/chunk-create.mjs +78 -0
  15. package/dist/lib/chunk-create.mjs.map +1 -0
  16. package/dist/lib/chunk-edge-backend.mjs +18 -0
  17. package/dist/lib/chunk-edge-backend.mjs.map +1 -0
  18. package/dist/lib/chunk-file-uploader.mjs +37 -0
  19. package/dist/lib/chunk-file-uploader.mjs.map +1 -0
  20. package/dist/lib/chunk-inline-backend.mjs +19 -0
  21. package/dist/lib/chunk-inline-backend.mjs.map +1 -0
  22. package/dist/lib/chunk-markdown-extension.mjs +182 -0
  23. package/dist/lib/chunk-markdown-extension.mjs.map +1 -0
  24. package/dist/lib/chunk-meta.mjs +44 -0
  25. package/dist/lib/chunk-meta.mjs.map +1 -0
  26. package/dist/lib/chunk-operation-handler.mjs +11 -0
  27. package/dist/lib/chunk-operation-handler.mjs.map +1 -0
  28. package/dist/lib/chunk-react-surface.mjs +55 -0
  29. package/dist/lib/chunk-react-surface.mjs.map +1 -0
  30. package/dist/lib/chunk-read.mjs +32 -0
  31. package/dist/lib/chunk-read.mjs.map +1 -0
  32. package/dist/lib/chunk-settings.mjs +23 -0
  33. package/dist/lib/chunk-settings.mjs.map +1 -0
  34. package/dist/lib/chunk-skill-definition.mjs +10 -0
  35. package/dist/lib/chunk-skill-definition.mjs.map +1 -0
  36. package/dist/lib/chunk-skills.mjs +28 -0
  37. package/dist/lib/chunk-skills.mjs.map +1 -0
  38. package/dist/lib/chunk-types.mjs +98 -0
  39. package/dist/lib/chunk-types.mjs.map +1 -0
  40. package/dist/lib/components.mjs +57 -0
  41. package/dist/lib/components.mjs.map +1 -0
  42. package/dist/lib/containers.mjs +56 -0
  43. package/dist/lib/containers.mjs.map +1 -0
  44. package/dist/lib/index.mjs +4 -0
  45. package/dist/lib/meta.mjs +2 -0
  46. package/dist/lib/operations.mjs +7 -0
  47. package/dist/lib/operations.mjs.map +1 -0
  48. package/dist/lib/plugin.mjs +9 -0
  49. package/dist/lib/plugin.mjs.map +1 -0
  50. package/dist/lib/skills.mjs +2 -0
  51. package/dist/lib/translations.mjs +30 -0
  52. package/dist/lib/translations.mjs.map +1 -0
  53. package/dist/lib/types.mjs +2 -0
  54. package/dist/types/src/FilePlugin.d.ts.map +1 -1
  55. package/dist/types/src/FilePlugin.node.d.ts.map +1 -1
  56. package/dist/types/src/capabilities/FileUploadField.d.ts +12 -0
  57. package/dist/types/src/capabilities/FileUploadField.d.ts.map +1 -0
  58. package/dist/types/src/capabilities/create-object.d.ts +1 -1
  59. package/dist/types/src/capabilities/edge-backend.d.ts +5 -0
  60. package/dist/types/src/capabilities/edge-backend.d.ts.map +1 -0
  61. package/dist/types/src/capabilities/file-uploader.d.ts +1 -1
  62. package/dist/types/src/capabilities/file-uploader.d.ts.map +1 -1
  63. package/dist/types/src/capabilities/index.d.ts +1 -0
  64. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  65. package/dist/types/src/capabilities/inline-backend.d.ts +2 -2
  66. package/dist/types/src/capabilities/inline-backend.d.ts.map +1 -1
  67. package/dist/types/src/capabilities/markdown-extension.d.ts +1 -1
  68. package/dist/types/src/capabilities/node.d.ts +5 -0
  69. package/dist/types/src/capabilities/node.d.ts.map +1 -0
  70. package/dist/types/src/capabilities/operation-handler.d.ts +1 -1
  71. package/dist/types/src/capabilities/react-surface.d.ts +1 -1
  72. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  73. package/dist/types/src/capabilities/settings.d.ts +1 -1
  74. package/dist/types/src/components/FileInput/FileInput.d.ts.map +1 -1
  75. package/dist/types/src/components/FilePreview/FilePreview.d.ts +1 -1
  76. package/dist/types/src/components/FilePreview/FilePreview.d.ts.map +1 -1
  77. package/dist/types/src/containers/FileArticle/FileArticle.d.ts +4 -1
  78. package/dist/types/src/containers/FileArticle/FileArticle.d.ts.map +1 -1
  79. package/dist/types/src/containers/FileSettings/FileSettings.d.ts +5 -3
  80. package/dist/types/src/containers/FileSettings/FileSettings.d.ts.map +1 -1
  81. package/dist/types/src/extensions/image.d.ts +0 -2
  82. package/dist/types/src/extensions/image.d.ts.map +1 -1
  83. package/dist/types/src/operations/create.d.ts +14 -5
  84. package/dist/types/src/operations/create.d.ts.map +1 -1
  85. package/dist/types/src/operations/read.d.ts.map +1 -1
  86. package/dist/types/src/types/FileCapabilities.d.ts +10 -28
  87. package/dist/types/src/types/FileCapabilities.d.ts.map +1 -1
  88. package/dist/types/src/types/Settings.d.ts +1 -1
  89. package/dist/types/src/types/Settings.d.ts.map +1 -1
  90. package/dist/types/src/types/limits.d.ts +0 -1
  91. package/dist/types/src/types/limits.d.ts.map +1 -1
  92. package/dist/types/tsconfig.tsbuildinfo +1 -1
  93. package/dx.config.ts +10 -6
  94. package/package.json +45 -41
  95. package/src/FilePlugin.node.ts +2 -1
  96. package/src/FilePlugin.tsx +8 -1
  97. package/src/capabilities/FileUploadField.tsx +37 -0
  98. package/src/capabilities/edge-backend.ts +27 -0
  99. package/src/capabilities/file-uploader.ts +6 -4
  100. package/src/capabilities/index.ts +1 -0
  101. package/src/capabilities/inline-backend.ts +4 -21
  102. package/src/capabilities/markdown-extension.ts +3 -8
  103. package/src/capabilities/node.ts +16 -0
  104. package/src/capabilities/react-surface.ts +46 -0
  105. package/src/components/FileInput/FileInput.tsx +3 -2
  106. package/src/containers/FileArticle/FileArticle.tsx +49 -42
  107. package/src/containers/FileSettings/FileSettings.tsx +19 -14
  108. package/src/extensions/image.tsx +49 -28
  109. package/src/operations/create.test.ts +122 -32
  110. package/src/operations/create.ts +44 -24
  111. package/src/operations/read.ts +24 -6
  112. package/src/types/FileCapabilities.ts +10 -31
  113. package/src/types/Settings.ts +1 -1
  114. package/src/types/limits.ts +0 -2
  115. package/dist/lib/neutral/FileArticle-YU6NN3KN.mjs +0 -80
  116. package/dist/lib/neutral/FileArticle-YU6NN3KN.mjs.map +0 -7
  117. package/dist/lib/neutral/FilePlugin.mjs +0 -58
  118. package/dist/lib/neutral/FilePlugin.mjs.map +0 -7
  119. package/dist/lib/neutral/FilePlugin.node.mjs +0 -23
  120. package/dist/lib/neutral/FilePlugin.node.mjs.map +0 -7
  121. package/dist/lib/neutral/capabilities/index.mjs +0 -23
  122. package/dist/lib/neutral/capabilities/index.mjs.map +0 -7
  123. package/dist/lib/neutral/chunk-DKGTSUUH.mjs +0 -8
  124. package/dist/lib/neutral/chunk-DKGTSUUH.mjs.map +0 -7
  125. package/dist/lib/neutral/chunk-GMYSEHV4.mjs +0 -40
  126. package/dist/lib/neutral/chunk-GMYSEHV4.mjs.map +0 -7
  127. package/dist/lib/neutral/chunk-J5LGTIGS.mjs +0 -10
  128. package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +0 -7
  129. package/dist/lib/neutral/chunk-LBZ4MCLI.mjs +0 -45
  130. package/dist/lib/neutral/chunk-LBZ4MCLI.mjs.map +0 -7
  131. package/dist/lib/neutral/chunk-YODPX62X.mjs +0 -136
  132. package/dist/lib/neutral/chunk-YODPX62X.mjs.map +0 -7
  133. package/dist/lib/neutral/chunk-ZUXUTRAJ.mjs +0 -80
  134. package/dist/lib/neutral/chunk-ZUXUTRAJ.mjs.map +0 -7
  135. package/dist/lib/neutral/components/index.mjs +0 -74
  136. package/dist/lib/neutral/components/index.mjs.map +0 -7
  137. package/dist/lib/neutral/containers/index.mjs +0 -59
  138. package/dist/lib/neutral/containers/index.mjs.map +0 -7
  139. package/dist/lib/neutral/create-IUUG7RI6.mjs +0 -17
  140. package/dist/lib/neutral/create-IUUG7RI6.mjs.map +0 -7
  141. package/dist/lib/neutral/create-object-V667G4ZL.mjs +0 -30
  142. package/dist/lib/neutral/create-object-V667G4ZL.mjs.map +0 -7
  143. package/dist/lib/neutral/file-uploader-BCJH6TUL.mjs +0 -41
  144. package/dist/lib/neutral/file-uploader-BCJH6TUL.mjs.map +0 -7
  145. package/dist/lib/neutral/index.mjs +0 -32
  146. package/dist/lib/neutral/index.mjs.map +0 -7
  147. package/dist/lib/neutral/inline-backend-FGGGDYRB.mjs +0 -37
  148. package/dist/lib/neutral/inline-backend-FGGGDYRB.mjs.map +0 -7
  149. package/dist/lib/neutral/markdown-extension-RA5YNMTW.mjs +0 -230
  150. package/dist/lib/neutral/markdown-extension-RA5YNMTW.mjs.map +0 -7
  151. package/dist/lib/neutral/meta.json +0 -1
  152. package/dist/lib/neutral/meta.mjs +0 -8
  153. package/dist/lib/neutral/meta.mjs.map +0 -7
  154. package/dist/lib/neutral/operation-handler-CPDANWGH.mjs +0 -13
  155. package/dist/lib/neutral/operation-handler-CPDANWGH.mjs.map +0 -7
  156. package/dist/lib/neutral/operations/index.mjs +0 -8
  157. package/dist/lib/neutral/operations/index.mjs.map +0 -7
  158. package/dist/lib/neutral/plugin.mjs +0 -16
  159. package/dist/lib/neutral/plugin.mjs.map +0 -7
  160. package/dist/lib/neutral/react-surface-5BXKSHD2.mjs +0 -60
  161. package/dist/lib/neutral/react-surface-5BXKSHD2.mjs.map +0 -7
  162. package/dist/lib/neutral/read-HG6CSWKR.mjs +0 -28
  163. package/dist/lib/neutral/read-HG6CSWKR.mjs.map +0 -7
  164. package/dist/lib/neutral/settings-RR3RESL2.mjs +0 -28
  165. package/dist/lib/neutral/settings-RR3RESL2.mjs.map +0 -7
  166. package/dist/lib/neutral/skill-definition-YHBGWBUS.mjs +0 -15
  167. package/dist/lib/neutral/skill-definition-YHBGWBUS.mjs.map +0 -7
  168. package/dist/lib/neutral/skills/index.mjs +0 -11
  169. package/dist/lib/neutral/skills/index.mjs.map +0 -7
  170. package/dist/lib/neutral/translations.mjs +0 -36
  171. package/dist/lib/neutral/translations.mjs.map +0 -7
  172. package/dist/lib/neutral/types/index.mjs +0 -22
  173. package/dist/lib/neutral/types/index.mjs.map +0 -7
  174. package/src/capabilities/react-surface.tsx +0 -61
@@ -0,0 +1,78 @@
1
+ import { c as SettingsAtom, d as isAcceptedMimeType, o as Backend, r as Create, t as File } from "./chunk-types.mjs";
2
+ import { Operation } from "@dxos/compute";
3
+ import { Capabilities, Capability } from "@dxos/app-framework";
4
+ import { Blob, Database } from "@dxos/echo";
5
+ import * as Effect from "effect/Effect";
6
+ //#region src/operations/create.ts
7
+ var UnsupportedFileTypeError = class extends Error {
8
+ type;
9
+ constructor(type) {
10
+ super(`Unsupported file type: ${type}`);
11
+ this.type = type;
12
+ this.name = "UnsupportedFileTypeError";
13
+ }
14
+ };
15
+ var FileTooLargeError = class extends Error {
16
+ size;
17
+ limit;
18
+ constructor(size, limit = Blob.MAX_INLINE_SIZE) {
19
+ super(`File is too large: ${size} bytes (limit: ${limit} bytes)`);
20
+ this.size = size;
21
+ this.limit = limit;
22
+ this.name = "FileTooLargeError";
23
+ }
24
+ };
25
+ var NoBackendError = class extends Error {
26
+ constructor() {
27
+ super("No file storage backend is registered.");
28
+ this.name = "NoBackendError";
29
+ }
30
+ };
31
+ var FileReadError = class extends Error {
32
+ constructor(cause) {
33
+ super("Failed to read file contents.");
34
+ this.name = "FileReadError";
35
+ this.cause = cause;
36
+ }
37
+ };
38
+ /**
39
+ * Resolves the storage name to force for an upload:
40
+ * - the storage name the user explicitly configured in Settings.backend, if it matches a
41
+ * registered {@link FileCapabilities.Backend} descriptor
42
+ * - otherwise `undefined`, leaving `File.fromBytes`'s `storage` option unset so it falls through
43
+ * to the Blob registry's own configured default (edge when configured, inline otherwise)
44
+ * instead of hardcoding `inline`
45
+ *
46
+ * Still requires at least one descriptor to be registered, as a sanity check that the plugin's
47
+ * settings UI has something to show.
48
+ */
49
+ var resolveActiveStorage = Effect.gen(function* () {
50
+ const backends = yield* Capability.getAll(Backend);
51
+ if (backends.length === 0) return yield* Effect.fail(new NoBackendError());
52
+ const settingsAtomOpt = yield* Capability.get(SettingsAtom).pipe(Effect.option);
53
+ const registryOpt = yield* Capability.get(Capabilities.AtomRegistry).pipe(Effect.option);
54
+ if (settingsAtomOpt._tag === "Some" && registryOpt._tag === "Some") {
55
+ const settings = registryOpt.value.get(settingsAtomOpt.value);
56
+ if (settings.backend) {
57
+ const match = backends.find((b) => b.storage === settings.backend);
58
+ if (match) return match.storage;
59
+ }
60
+ }
61
+ });
62
+ var handler = Create.pipe(Operation.withHandler(Effect.fnUntraced(function* ({ file, db }) {
63
+ if (!isAcceptedMimeType(file.type)) return yield* Effect.fail(new UnsupportedFileTypeError(file.type));
64
+ const storage = yield* resolveActiveStorage;
65
+ const bytes = new Uint8Array(yield* Effect.tryPromise({
66
+ try: () => file.arrayBuffer(),
67
+ catch: (error) => new FileReadError(error)
68
+ }));
69
+ return { object: yield* File.fromBytes(bytes, {
70
+ name: file.name,
71
+ type: file.type,
72
+ storage
73
+ }).pipe(Effect.provide(Database.layer(db)), Effect.catchTag("BlobTooLargeError", () => Effect.fail(new FileTooLargeError(bytes.byteLength)))) };
74
+ })));
75
+ //#endregion
76
+ export { FileReadError, FileTooLargeError, NoBackendError, UnsupportedFileTypeError, handler as default, resolveActiveStorage };
77
+
78
+ //# sourceMappingURL=chunk-create.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-create.mjs","names":[],"sources":["../../src/operations/create.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport { Capabilities, Capability } from '@dxos/app-framework';\nimport { Operation } from '@dxos/compute';\nimport { Blob, Database } from '@dxos/echo';\n\nimport { File, FileCapabilities, FileOperation, Settings, isAcceptedMimeType } from '../types';\n\nexport class UnsupportedFileTypeError extends Error {\n constructor(public readonly type: string) {\n super(`Unsupported file type: ${type}`);\n this.name = 'UnsupportedFileTypeError';\n }\n}\n\nexport class FileTooLargeError extends Error {\n constructor(\n public readonly size: number,\n public readonly limit: number = Blob.MAX_INLINE_SIZE,\n ) {\n super(`File is too large: ${size} bytes (limit: ${limit} bytes)`);\n this.name = 'FileTooLargeError';\n }\n}\n\nexport class NoBackendError extends Error {\n constructor() {\n super('No file storage backend is registered.');\n this.name = 'NoBackendError';\n }\n}\n\nexport class FileReadError extends Error {\n constructor(cause: unknown) {\n super('Failed to read file contents.');\n this.name = 'FileReadError';\n this.cause = cause;\n }\n}\n\n/**\n * Resolves the storage name to force for an upload:\n * - the storage name the user explicitly configured in Settings.backend, if it matches a\n * registered {@link FileCapabilities.Backend} descriptor\n * - otherwise `undefined`, leaving `File.fromBytes`'s `storage` option unset so it falls through\n * to the Blob registry's own configured default (edge when configured, inline otherwise)\n * instead of hardcoding `inline`\n *\n * Still requires at least one descriptor to be registered, as a sanity check that the plugin's\n * settings UI has something to show.\n */\nexport const resolveActiveStorage = Effect.gen(function* () {\n const backends = yield* Capability.getAll(FileCapabilities.Backend);\n if (backends.length === 0) {\n return yield* Effect.fail(new NoBackendError());\n }\n\n const settingsAtomOpt = yield* Capability.get(FileCapabilities.SettingsAtom).pipe(Effect.option);\n const registryOpt = yield* Capability.get(Capabilities.AtomRegistry).pipe(Effect.option);\n\n if (settingsAtomOpt._tag === 'Some' && registryOpt._tag === 'Some') {\n const settings = registryOpt.value.get(settingsAtomOpt.value) as Settings.Settings;\n if (settings.backend) {\n const match = backends.find((b) => b.storage === settings.backend);\n if (match) {\n return match.storage;\n }\n }\n }\n\n return undefined;\n});\n\nconst handler: Operation.WithHandler<typeof FileOperation.Create> = FileOperation.Create.pipe(\n Operation.withHandler(\n Effect.fnUntraced(function* ({ file, db }) {\n // Validate before hitting the backend so the contract is consistent regardless of backend.\n if (!isAcceptedMimeType(file.type)) {\n return yield* Effect.fail(new UnsupportedFileTypeError(file.type));\n }\n const storage = yield* resolveActiveStorage;\n const bytes = new Uint8Array(\n yield* Effect.tryPromise({\n try: () => file.arrayBuffer(),\n catch: (error) => new FileReadError(error),\n }),\n );\n // The size cap only applies to `inline` storage — `Blob.fromBytes` enforces it internally;\n // other backends scale beyond it.\n const object = yield* File.fromBytes(bytes, {\n name: file.name,\n type: file.type,\n storage,\n }).pipe(\n Effect.provide(Database.layer(db)),\n Effect.catchTag('BlobTooLargeError', () => Effect.fail(new FileTooLargeError(bytes.byteLength))),\n );\n return { object };\n }),\n ),\n);\n\nexport default handler;\n"],"mappings":";;;;;;AAYA,IAAa,2BAAb,cAA8C,MAAM;CACtB;CAA5B,YAAY,MAA8B;EACxC,MAAM,0BAA0B,MAAM;EADZ,KAAA,OAAA;EAE1B,KAAK,OAAO;CACd;AACF;AAEA,IAAa,oBAAb,cAAuC,MAAM;CAEzB;CACA;CAFlB,YACE,MACA,QAAgC,KAAK,iBACrC;EACA,MAAM,sBAAsB,KAAK,iBAAiB,MAAM,QAAQ;EAHhD,KAAA,OAAA;EACA,KAAA,QAAA;EAGhB,KAAK,OAAO;CACd;AACF;AAEA,IAAa,iBAAb,cAAoC,MAAM;CACxC,cAAc;EACZ,MAAM,wCAAwC;EAC9C,KAAK,OAAO;CACd;AACF;AAEA,IAAa,gBAAb,cAAmC,MAAM;CACvC,YAAY,OAAgB;EAC1B,MAAM,+BAA+B;EACrC,KAAK,OAAO;EACZ,KAAK,QAAQ;CACf;AACF;;;;;;;;;;;;AAaA,IAAa,uBAAuB,OAAO,IAAI,aAAa;CAC1D,MAAM,WAAW,OAAO,WAAW,OAAO,OAAwB;CAClE,IAAI,SAAS,WAAW,GACtB,OAAO,OAAO,OAAO,KAAK,IAAI,eAAe,CAAC;CAGhD,MAAM,kBAAkB,OAAO,WAAW,IAAI,YAA6B,CAAC,CAAC,KAAK,OAAO,MAAM;CAC/F,MAAM,cAAc,OAAO,WAAW,IAAI,aAAa,YAAY,CAAC,CAAC,KAAK,OAAO,MAAM;CAEvF,IAAI,gBAAgB,SAAS,UAAU,YAAY,SAAS,QAAQ;EAClE,MAAM,WAAW,YAAY,MAAM,IAAI,gBAAgB,KAAK;EAC5D,IAAI,SAAS,SAAS;GACpB,MAAM,QAAQ,SAAS,MAAM,MAAM,EAAE,YAAY,SAAS,OAAO;GACjE,IAAI,OACF,OAAO,MAAM;EAEjB;CACF;AAGF,CAAC;AAED,IAAM,UAAA,OAAmF,KACvF,UAAU,YACR,OAAO,WAAW,WAAW,EAAE,MAAM,MAAM;CAEzC,IAAI,CAAC,mBAAmB,KAAK,IAAI,GAC/B,OAAO,OAAO,OAAO,KAAK,IAAI,yBAAyB,KAAK,IAAI,CAAC;CAEnE,MAAM,UAAU,OAAO;CACvB,MAAM,QAAQ,IAAI,WAChB,OAAO,OAAO,WAAW;EACvB,WAAW,KAAK,YAAY;EAC5B,QAAQ,UAAU,IAAI,cAAc,KAAK;CAC3C,CAAC,CACH;CAWA,OAAO,EAAE,QAAA,OARa,KAAK,UAAU,OAAO;EAC1C,MAAM,KAAK;EACX,MAAM,KAAK;EACX;CACF,CAAC,CAAC,CAAC,KACD,OAAO,QAAQ,SAAS,MAAM,EAAE,CAAC,GACjC,OAAO,SAAS,2BAA2B,OAAO,KAAK,IAAI,kBAAkB,MAAM,UAAU,CAAC,CAAC,CACjG,EACgB;AAClB,CAAC,CACH,CACF"}
@@ -0,0 +1,18 @@
1
+ import { Capabilities, Capability } from "@dxos/app-framework";
2
+ import { Blob } from "@dxos/echo";
3
+ import { ClientCapabilities } from "@dxos/plugin-client";
4
+ import { FileCapabilities } from "#types";
5
+ import * as Effect from "effect/Effect";
6
+ //#region src/capabilities/edge-backend.ts
7
+ var edge_backend_default = Capability.makeModule(Effect.fnUntraced(function* () {
8
+ if (!(yield* Capability.get(ClientCapabilities.Client)).config.values.runtime?.services?.edge?.url) return Capability.contributes(Capabilities.Null, null);
9
+ return Capability.contributes(FileCapabilities.Backend, {
10
+ name: "Edge",
11
+ description: "Store files on the DXOS edge network. Scales beyond the inline size cap.",
12
+ storage: Blob.Storage.edge
13
+ });
14
+ }));
15
+ //#endregion
16
+ export { edge_backend_default as default };
17
+
18
+ //# sourceMappingURL=chunk-edge-backend.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-edge-backend.mjs","names":[],"sources":["../../src/capabilities/edge-backend.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport { Capabilities, Capability } from '@dxos/app-framework';\nimport { Blob } from '@dxos/echo';\nimport { ClientCapabilities } from '@dxos/plugin-client';\n\nimport { FileCapabilities } from '#types';\n\nexport default Capability.makeModule(\n Effect.fnUntraced(function* () {\n const client = yield* Capability.get(ClientCapabilities.Client);\n const edgeUrl = client.config.values.runtime?.services?.edge?.url;\n if (!edgeUrl) {\n return Capability.contributes(Capabilities.Null, null);\n }\n\n return Capability.contributes(FileCapabilities.Backend, {\n name: 'Edge',\n description: 'Store files on the DXOS edge network. Scales beyond the inline size cap.',\n storage: Blob.Storage.edge,\n });\n }),\n);\n"],"mappings":";;;;;;AAYA,IAAA,uBAAe,WAAW,WACxB,OAAO,WAAW,aAAa;CAG7B,IAAI,EADY,OADM,WAAW,IAAI,mBAAmB,MAAM,EAAA,CACvC,OAAO,OAAO,SAAS,UAAU,MAAM,KAE5D,OAAO,WAAW,YAAY,aAAa,MAAM,IAAI;CAGvD,OAAO,WAAW,YAAY,iBAAiB,SAAS;EACtD,MAAM;EACN,aAAa;EACb,SAAS,KAAK,QAAQ;CACxB,CAAC;AACH,CAAC,CACH"}
@@ -0,0 +1,37 @@
1
+ import { Capabilities, Capability } from "@dxos/app-framework";
2
+ import { Blob, Database } from "@dxos/echo";
3
+ import { AppCapabilities } from "@dxos/app-toolkit";
4
+ import { FileOperation } from "#types";
5
+ import { EffectEx } from "@dxos/effect";
6
+ import * as Effect from "effect/Effect";
7
+ import { SpaceOperation } from "@dxos/plugin-space";
8
+ import * as Option from "effect/Option";
9
+ //#region src/capabilities/file-uploader.ts
10
+ var file_uploader_default = Capability.makeModule(Effect.fnUntraced(function* () {
11
+ const capabilities = yield* Capability.Service;
12
+ return Capability.contributes(AppCapabilities.FileUploader, (db, file) => {
13
+ const { invoke } = capabilities.get(Capabilities.OperationInvoker);
14
+ const program = Effect.gen(function* () {
15
+ const { object } = yield* invoke(FileOperation.Create, {
16
+ db,
17
+ file
18
+ });
19
+ yield* invoke(SpaceOperation.AddObject, {
20
+ target: db,
21
+ object
22
+ });
23
+ const blob = yield* Database.load(object.data);
24
+ const urlOption = yield* Blob.url(blob).pipe(Effect.provide(Database.layer(db)));
25
+ return {
26
+ name: object.name ?? file.name,
27
+ type: blob.type ?? "application/octet-stream",
28
+ ...Option.isSome(urlOption) ? { url: urlOption.value } : {}
29
+ };
30
+ });
31
+ return EffectEx.runAndForwardErrors(program);
32
+ });
33
+ }));
34
+ //#endregion
35
+ export { file_uploader_default as default };
36
+
37
+ //# sourceMappingURL=chunk-file-uploader.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-file-uploader.mjs","names":[],"sources":["../../src/capabilities/file-uploader.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\nimport * as Option from 'effect/Option';\n\nimport { Capabilities, Capability } from '@dxos/app-framework';\nimport { AppCapabilities } from '@dxos/app-toolkit';\nimport { Blob, Database } from '@dxos/echo';\nimport { EffectEx } from '@dxos/effect';\nimport { SpaceOperation } from '@dxos/plugin-space';\n\nimport { FileOperation } from '#types';\n\nexport default Capability.makeModule(\n Effect.fnUntraced(function* () {\n const capabilities = yield* Capability.Service;\n\n return Capability.contributes(AppCapabilities.FileUploader, (db, file) => {\n const { invoke } = capabilities.get(Capabilities.OperationInvoker);\n const program = Effect.gen(function* () {\n const { object } = yield* invoke(FileOperation.Create, { db, file });\n yield* invoke(SpaceOperation.AddObject, { target: db, object });\n const blob = yield* Database.load(object.data);\n const urlOption = yield* Blob.url(blob).pipe(Effect.provide(Database.layer(db)));\n return {\n name: object.name ?? file.name,\n type: blob.type ?? 'application/octet-stream',\n ...(Option.isSome(urlOption) ? { url: urlOption.value } : {}),\n };\n });\n\n return EffectEx.runAndForwardErrors(program);\n });\n }),\n);\n"],"mappings":";;;;;;;;;AAeA,IAAA,wBAAe,WAAW,WACxB,OAAO,WAAW,aAAa;CAC7B,MAAM,eAAe,OAAO,WAAW;CAEvC,OAAO,WAAW,YAAY,gBAAgB,eAAe,IAAI,SAAS;EACxE,MAAM,EAAE,WAAW,aAAa,IAAI,aAAa,gBAAgB;EACjE,MAAM,UAAU,OAAO,IAAI,aAAa;GACtC,MAAM,EAAE,WAAW,OAAO,OAAO,cAAc,QAAQ;IAAE;IAAI;GAAK,CAAC;GACnE,OAAO,OAAO,eAAe,WAAW;IAAE,QAAQ;IAAI;GAAO,CAAC;GAC9D,MAAM,OAAO,OAAO,SAAS,KAAK,OAAO,IAAI;GAC7C,MAAM,YAAY,OAAO,KAAK,IAAI,IAAI,CAAC,CAAC,KAAK,OAAO,QAAQ,SAAS,MAAM,EAAE,CAAC,CAAC;GAC/E,OAAO;IACL,MAAM,OAAO,QAAQ,KAAK;IAC1B,MAAM,KAAK,QAAQ;IACnB,GAAI,OAAO,OAAO,SAAS,IAAI,EAAE,KAAK,UAAU,MAAM,IAAI,CAAC;GAC7D;EACF,CAAC;EAED,OAAO,SAAS,oBAAoB,OAAO;CAC7C,CAAC;AACH,CAAC,CACH"}
@@ -0,0 +1,19 @@
1
+ import { Capability } from "@dxos/app-framework";
2
+ import { Blob } from "@dxos/echo";
3
+ import { FileCapabilities } from "#types";
4
+ import * as Effect from "effect/Effect";
5
+ //#region src/capabilities/inline-backend.ts
6
+ /**
7
+ * Inline backend descriptor: file bytes are stored on the ECHO object itself.
8
+ * Exported standalone for direct testing.
9
+ */
10
+ var inlineBackend = {
11
+ name: "Inline (ECHO)",
12
+ description: "Store the file bytes directly inside the ECHO document. Capped at 4MB; images, videos, and PDFs only.",
13
+ storage: Blob.Storage.inline
14
+ };
15
+ var inline_backend_default = Capability.makeModule(() => Effect.succeed(Capability.contributes(FileCapabilities.Backend, inlineBackend)));
16
+ //#endregion
17
+ export { inline_backend_default as default, inlineBackend };
18
+
19
+ //# sourceMappingURL=chunk-inline-backend.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-inline-backend.mjs","names":[],"sources":["../../src/capabilities/inline-backend.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport { Capability } from '@dxos/app-framework';\nimport { Blob } from '@dxos/echo';\n\nimport { FileCapabilities } from '#types';\n\n/**\n * Inline backend descriptor: file bytes are stored on the ECHO object itself.\n * Exported standalone for direct testing.\n */\nexport const inlineBackend: FileCapabilities.Backend = {\n name: 'Inline (ECHO)',\n description: 'Store the file bytes directly inside the ECHO document. Capped at 4MB; images, videos, and PDFs only.',\n storage: Blob.Storage.inline,\n};\n\nexport default Capability.makeModule(() =>\n Effect.succeed(Capability.contributes(FileCapabilities.Backend, inlineBackend)),\n);\n"],"mappings":";;;;;;;;;AAeA,IAAa,gBAA0C;CACrD,MAAM;CACN,aAAa;CACb,SAAS,KAAK,QAAQ;AACxB;AAEA,IAAA,yBAAe,WAAW,iBACxB,OAAO,QAAQ,WAAW,YAAY,iBAAiB,SAAS,aAAa,CAAC,CAChF"}
@@ -0,0 +1,182 @@
1
+ import { Capability } from "@dxos/app-framework";
2
+ import { Blob, Database, Filter, Obj } from "@dxos/echo";
3
+ import { File } from "#types";
4
+ import "react";
5
+ import { EffectEx } from "@dxos/effect";
6
+ import { Status, ThemeProvider, defaultTx } from "@dxos/react-ui";
7
+ import { jsx } from "react/jsx-runtime";
8
+ import * as Effect from "effect/Effect";
9
+ import * as Option from "effect/Option";
10
+ import { MarkdownCapabilities } from "@dxos/plugin-markdown/types";
11
+ import { getSpace } from "@dxos/react-client/echo";
12
+ import { syntaxTree } from "@codemirror/language";
13
+ import { StateField } from "@codemirror/state";
14
+ import { Decoration, EditorView, WidgetType } from "@codemirror/view";
15
+ import { createRoot } from "react-dom/client";
16
+ import { EID } from "@dxos/keys";
17
+ import { focusField } from "@dxos/ui-editor";
18
+ //#region src/extensions/image.tsx
19
+ var WAIT_UNTIL_LOADER = 1500;
20
+ /**
21
+ * Decorate `![](echo:...)` references that resolve to File objects with inline image previews.
22
+ */
23
+ var image = (options) => {
24
+ const blobUrlCache = {};
25
+ const preloaded = /* @__PURE__ */ new Set();
26
+ const preload = (url) => {
27
+ if (!preloaded.has(url)) {
28
+ const img = document.createElement("img");
29
+ img.src = url;
30
+ preloaded.add(url);
31
+ }
32
+ };
33
+ return [StateField.define({
34
+ create: (state) => Decoration.set(buildDecorations({
35
+ from: 0,
36
+ to: state.doc.length,
37
+ state,
38
+ blobUrlCache,
39
+ preload,
40
+ options
41
+ })),
42
+ update: (value, tr) => {
43
+ if (!tr.docChanged && !tr.selection) return value;
44
+ const cursor = tr.state.selection.main.head;
45
+ const oldCursor = tr.changes.mapPos(tr.startState.selection.main.head);
46
+ let from = Math.min(cursor, oldCursor);
47
+ let to = Math.max(cursor, oldCursor);
48
+ tr.changes.iterChangedRanges((_fromA, _toA, fromB, toB) => {
49
+ from = Math.min(from, fromB);
50
+ to = Math.max(to, toB);
51
+ });
52
+ from = tr.state.doc.lineAt(from).from;
53
+ to = tr.state.doc.lineAt(to).to;
54
+ return value.map(tr.changes).update({
55
+ filterFrom: from,
56
+ filterTo: to,
57
+ filter: () => false,
58
+ add: buildDecorations({
59
+ from,
60
+ to,
61
+ state: tr.state,
62
+ blobUrlCache,
63
+ preload,
64
+ options
65
+ })
66
+ });
67
+ },
68
+ provide: (field) => EditorView.decorations.from(field)
69
+ })];
70
+ };
71
+ var buildDecorations = ({ state, from, to, blobUrlCache, preload, options: { space } }) => {
72
+ const decorations = [];
73
+ const cursor = state.selection.main.head;
74
+ syntaxTree(state).iterate({
75
+ enter: (node) => {
76
+ if (node.name !== "Image") return;
77
+ const urlNode = node.node.getChild("URL");
78
+ if (!urlNode) return;
79
+ const hide = state.readOnly || cursor < node.from || cursor > node.to;
80
+ const urlText = state.sliceDoc(urlNode.from, urlNode.to);
81
+ if (!EID.isEID(urlText)) return;
82
+ let echoUri;
83
+ let echoSpaceId;
84
+ try {
85
+ const parsed = EID.parse(urlText);
86
+ echoUri = EID.getEntityId(parsed);
87
+ echoSpaceId = EID.getSpaceId(parsed);
88
+ } catch {
89
+ return;
90
+ }
91
+ if (!echoUri) return;
92
+ if (echoSpaceId && echoSpaceId !== space.id) return;
93
+ const cacheKey = urlText;
94
+ const blobUrlPromise = blobUrlCache[cacheKey] ?? (async () => {
95
+ const matched = await space.db.query(Filter.id(echoUri)).first();
96
+ if (!matched || !Obj.instanceOf(File.File, matched)) return;
97
+ const url = await EffectEx.runPromise(Effect.gen(function* () {
98
+ const blob = yield* Database.load(matched.data);
99
+ const urlOption = yield* Blob.url(blob);
100
+ if (Option.isSome(urlOption)) return urlOption.value;
101
+ const bytes = yield* Blob.read(blob);
102
+ return URL.createObjectURL(new globalThis.Blob([bytes], { type: blob.type }));
103
+ }).pipe(Effect.provide(Database.layer(space.db)), Effect.catchAll(() => Effect.succeed(void 0))));
104
+ if (!url) return;
105
+ const previous = blobUrlCache[cacheKey];
106
+ if (previous?.startsWith("blob:")) URL.revokeObjectURL(previous);
107
+ blobUrlCache[cacheKey] = url;
108
+ preload(url);
109
+ return url;
110
+ })();
111
+ decorations.push(Decoration.replace({
112
+ block: true,
113
+ widget: new DxnImageWidget(urlText, blobUrlPromise)
114
+ }).range(hide ? node.from : node.to, node.to));
115
+ },
116
+ from,
117
+ to
118
+ });
119
+ return decorations;
120
+ };
121
+ var createImg = (view, url) => {
122
+ const img = document.createElement("img");
123
+ img.setAttribute("src", url);
124
+ img.setAttribute("class", "cm-image-with-loader");
125
+ if (view.state.field(focusField)) img.onload = () => img.classList.add("cm-loaded-image");
126
+ else img.classList.add("cm-loaded-image");
127
+ return img;
128
+ };
129
+ var DxnImageWidget = class extends WidgetType {
130
+ _dxnUrl;
131
+ _url;
132
+ constructor(_dxnUrl, _url) {
133
+ super();
134
+ this._dxnUrl = _dxnUrl;
135
+ this._url = _url;
136
+ }
137
+ eq(other) {
138
+ return this._dxnUrl === other._dxnUrl;
139
+ }
140
+ toDOM(view) {
141
+ if (typeof this._url === "string") return createImg(view, this._url);
142
+ const widget = document.createElement("div");
143
+ widget.className = "cm-image-wrapper";
144
+ const loader = document.createElement("div");
145
+ loader.className = "mx-auto transition-opacity";
146
+ let loaderAdded = false;
147
+ const timeout = setTimeout(() => {
148
+ createRoot(loader).render(/* @__PURE__ */ jsx(ThemeProvider, {
149
+ tx: defaultTx,
150
+ children: /* @__PURE__ */ jsx(Status, { indeterminate: true })
151
+ }));
152
+ widget.appendChild(loader);
153
+ loaderAdded = true;
154
+ }, WAIT_UNTIL_LOADER);
155
+ Promise.resolve(this._url).then((url) => {
156
+ clearTimeout(timeout);
157
+ if (loaderAdded) widget.removeChild(loader);
158
+ if (url) widget.appendChild(createImg(view, url));
159
+ }).catch(() => {
160
+ clearTimeout(timeout);
161
+ if (loaderAdded) widget.removeChild(loader);
162
+ });
163
+ return widget;
164
+ }
165
+ };
166
+ //#endregion
167
+ //#region src/capabilities/markdown-extension.ts
168
+ var markdown_extension_default = Capability.makeModule(() => Effect.sync(() => {
169
+ const provider = ({ document, viewMode }) => {
170
+ if (viewMode === "source") return;
171
+ if (document) {
172
+ const space = getSpace(document);
173
+ if (!space) return;
174
+ return [image({ space })];
175
+ }
176
+ };
177
+ return Capability.contributes(MarkdownCapabilities.ExtensionProvider, [provider]);
178
+ }));
179
+ //#endregion
180
+ export { markdown_extension_default as default };
181
+
182
+ //# sourceMappingURL=chunk-markdown-extension.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-markdown-extension.mjs","names":[],"sources":["../../src/extensions/image.tsx","../../src/capabilities/markdown-extension.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport { syntaxTree } from '@codemirror/language';\nimport { type EditorState, type Extension, type Range, StateField } from '@codemirror/state';\nimport { Decoration, EditorView, WidgetType } from '@codemirror/view';\nimport * as Effect from 'effect/Effect';\nimport * as Option from 'effect/Option';\nimport React from 'react';\nimport { createRoot } from 'react-dom/client';\n\nimport { Blob, Database, Filter, Obj } from '@dxos/echo';\nimport { EffectEx } from '@dxos/effect';\nimport { EID } from '@dxos/keys';\nimport { type Space } from '@dxos/react-client/echo';\nimport { Status, ThemeProvider } from '@dxos/react-ui';\nimport { defaultTx } from '@dxos/react-ui';\nimport { focusField } from '@dxos/ui-editor';\nimport { type MaybePromise } from '@dxos/util';\n\nimport { File } from '#types';\n\nconst WAIT_UNTIL_LOADER = 1500;\n\nexport type ImageOptions = {\n space: Space;\n};\n\n/**\n * Decorate `![](echo:...)` references that resolve to File objects with inline image previews.\n */\nexport const image = (options: ImageOptions): Extension[] => {\n const blobUrlCache: Record<string, string> = {};\n const preloaded = new Set<string>();\n\n const preload = (url: string) => {\n if (!preloaded.has(url)) {\n const img = document.createElement('img');\n img.src = url;\n preloaded.add(url);\n }\n };\n\n return [\n StateField.define({\n create: (state) =>\n Decoration.set(buildDecorations({ from: 0, to: state.doc.length, state, blobUrlCache, preload, options })),\n update: (value, tr) => {\n if (!tr.docChanged && !tr.selection) {\n return value;\n }\n\n const cursor = tr.state.selection.main.head;\n const oldCursor = tr.changes.mapPos(tr.startState.selection.main.head);\n let from = Math.min(cursor, oldCursor);\n let to = Math.max(cursor, oldCursor);\n tr.changes.iterChangedRanges((_fromA, _toA, fromB, toB) => {\n from = Math.min(from, fromB);\n to = Math.max(to, toB);\n });\n\n from = tr.state.doc.lineAt(from).from;\n to = tr.state.doc.lineAt(to).to;\n\n return value.map(tr.changes).update({\n filterFrom: from,\n filterTo: to,\n filter: () => false,\n add: buildDecorations({ from, to, state: tr.state, blobUrlCache, preload, options }),\n });\n },\n provide: (field) => EditorView.decorations.from(field),\n }),\n ];\n};\n\nconst buildDecorations = ({\n state,\n from,\n to,\n blobUrlCache,\n preload,\n options: { space },\n}: {\n state: EditorState;\n from: number;\n to: number;\n blobUrlCache: Record<string, string>;\n preload: (url: string) => void;\n options: ImageOptions;\n}) => {\n const decorations: Range<Decoration>[] = [];\n const cursor = state.selection.main.head;\n syntaxTree(state).iterate({\n enter: (node) => {\n if (node.name !== 'Image') {\n return;\n }\n\n const urlNode = node.node.getChild('URL');\n if (!urlNode) {\n return;\n }\n\n const hide = state.readOnly || cursor < node.from || cursor > node.to;\n const urlText = state.sliceDoc(urlNode.from, urlNode.to);\n if (!EID.isEID(urlText)) {\n return;\n }\n\n let echoUri: string | undefined;\n let echoSpaceId: string | undefined;\n try {\n const parsed = EID.parse(urlText);\n echoUri = EID.getEntityId(parsed);\n echoSpaceId = EID.getSpaceId(parsed);\n } catch {\n return;\n }\n if (!echoUri) {\n return;\n }\n\n // Skip references to objects in other spaces.\n if (echoSpaceId && echoSpaceId !== space.id) {\n return;\n }\n\n const cacheKey = urlText;\n const cached = blobUrlCache[cacheKey];\n // Skip the DB query and Blob resolution entirely once a URL is cached — otherwise every\n // decoration rebuild (e.g. on cursor movement) re-fetches and, on the object-URL fallback\n // path, mints and leaks a new `blob:` URL even though the cached one is still valid.\n const blobUrlPromise =\n cached ??\n (async () => {\n const matched = await space.db.query(Filter.id(echoUri!)).first();\n if (!matched || !Obj.instanceOf(File.File, matched)) {\n return undefined;\n }\n\n const url = await EffectEx.runPromise(\n Effect.gen(function* () {\n const blob = yield* Database.load(matched.data);\n const urlOption = yield* Blob.url(blob);\n if (Option.isSome(urlOption)) {\n return urlOption.value;\n }\n const bytes = yield* Blob.read(blob);\n // `Uint8Array` is generic over `ArrayBufferLike` (incl. `SharedArrayBuffer`) while\n // DOM's `BlobPart` only covers `ArrayBuffer`-backed views — a gap between the DOM lib\n // types and the TS standard lib, not fixable by typing `bytes` differently.\n return URL.createObjectURL(new globalThis.Blob([bytes as BlobPart], { type: blob.type }));\n }).pipe(\n Effect.provide(Database.layer(space.db)),\n Effect.catchAll(() => Effect.succeed(undefined)),\n ),\n );\n if (!url) {\n return undefined;\n }\n // Only object URLs we minted via `createObjectURL` above need revoking — `Blob.url()`\n // results (data:/https: URLs) aren't owned by this cache.\n const previous = blobUrlCache[cacheKey];\n if (previous?.startsWith('blob:')) {\n URL.revokeObjectURL(previous);\n }\n blobUrlCache[cacheKey] = url;\n preload(url);\n return url;\n })();\n\n decorations.push(\n Decoration.replace({\n block: true,\n widget: new DxnImageWidget(urlText, blobUrlPromise),\n }).range(hide ? node.from : node.to, node.to),\n );\n },\n from,\n to,\n });\n\n return decorations;\n};\n\nconst createImg = (view: EditorView, url: string) => {\n const img = document.createElement('img');\n img.setAttribute('src', url);\n img.setAttribute('class', 'cm-image-with-loader');\n if (view.state.field(focusField)) {\n img.onload = () => img.classList.add('cm-loaded-image');\n } else {\n img.classList.add('cm-loaded-image');\n }\n return img;\n};\n\nclass DxnImageWidget extends WidgetType {\n constructor(\n readonly _dxnUrl: string,\n readonly _url: MaybePromise<string | undefined>,\n ) {\n super();\n }\n\n override eq(other: this) {\n return this._dxnUrl === other._dxnUrl;\n }\n\n override toDOM(view: EditorView) {\n if (typeof this._url === 'string') {\n return createImg(view, this._url);\n }\n\n const widget = document.createElement('div');\n widget.className = 'cm-image-wrapper';\n const loader = document.createElement('div');\n loader.className = 'mx-auto transition-opacity';\n let loaderAdded = false;\n\n const timeout = setTimeout(() => {\n const root = createRoot(loader);\n root.render(\n <ThemeProvider tx={defaultTx}>\n <Status indeterminate />\n </ThemeProvider>,\n );\n widget.appendChild(loader);\n loaderAdded = true;\n }, WAIT_UNTIL_LOADER);\n\n void Promise.resolve(this._url)\n .then((url) => {\n clearTimeout(timeout);\n if (loaderAdded) {\n widget.removeChild(loader);\n }\n if (url) {\n widget.appendChild(createImg(view, url));\n }\n })\n .catch(() => {\n clearTimeout(timeout);\n if (loaderAdded) {\n widget.removeChild(loader);\n }\n });\n\n return widget;\n }\n}\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport { Capability } from '@dxos/app-framework';\nimport { MarkdownCapabilities, type MarkdownExtensionProvider } from '@dxos/plugin-markdown/types';\nimport { getSpace } from '@dxos/react-client/echo';\n\nimport { image } from '../extensions';\n\nexport default Capability.makeModule(() =>\n Effect.sync(() => {\n const provider: MarkdownExtensionProvider = ({ document, viewMode }) => {\n if (viewMode === 'source') {\n return undefined;\n }\n\n if (document) {\n const space = getSpace(document);\n if (!space) {\n return undefined;\n }\n return [image({ space })];\n }\n\n return undefined;\n };\n\n return Capability.contributes(MarkdownCapabilities.ExtensionProvider, [provider]);\n }),\n);\n"],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import { trim } from "@dxos/util";
2
+ import { Plugin } from "@dxos/app-framework";
3
+ import { Config2 } from "@dxos/app-framework/config";
4
+ //#region dx.config.ts
5
+ var dx_config_default = Config2.make({ plugin: {
6
+ key: "org.dxos.plugin.file",
7
+ name: "File",
8
+ author: "DXOS",
9
+ description: trim`
10
+ FilePlugin enables DXOS Composer to store and preview binary files — images, videos, and PDFs — as
11
+ first-class ECHO objects. Uploaded bytes are routed through a pluggable backend system: the built-in
12
+ inline backend serialises the file directly onto the ECHO document, while external backends (e.g. WNFS)
13
+ can be contributed by other plugins.
14
+
15
+ Accepted formats are images (jpg, jpeg, png, gif, webp, svg), videos (mp4, webm, mov), and PDFs,
16
+ up to a 4 MB limit per file for the inline backend (other backends scale beyond it). The preview
17
+ surface renders the appropriate HTML element for each MIME class and surfaces inside article,
18
+ section, and slide roles.
19
+
20
+ A File object's \`data\` field is a reference to a \`Blob\` ECHO object (from \`@dxos/echo\`), which
21
+ abstracts where bytes actually live. \`Blob.url\`/\`Blob.read\` resolve a renderable URL or the raw
22
+ bytes regardless of backend, keeping the File object schema backend-agnostic without a separate
23
+ URL-resolver capability. A companion Markdown extension allows file objects to be embedded directly
24
+ in Markdown documents.
25
+
26
+ Backend selection is user-configurable in the plugin settings panel, which lists all registered
27
+ backends with their descriptions. Selecting a backend persists the choice to the ECHO-backed settings
28
+ atom so the preference is shared across devices.
29
+ `,
30
+ icon: {
31
+ key: "ph--file--regular",
32
+ hue: "indigo"
33
+ },
34
+ source: "https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-file",
35
+ spec: "PLUGIN.mdl",
36
+ tags: ["alpha"]
37
+ } });
38
+ //#endregion
39
+ //#region src/meta.ts
40
+ var meta = Plugin.getMetaFromConfig(dx_config_default);
41
+ //#endregion
42
+ export { meta as t };
43
+
44
+ //# sourceMappingURL=chunk-meta.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-meta.mjs","names":[],"sources":["../../dx.config.ts","../../src/meta.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport { Config2 } from '@dxos/app-framework/config';\nimport { trim } from '@dxos/util';\n\nexport default Config2.make({\n plugin: {\n key: 'org.dxos.plugin.file',\n name: 'File',\n author: 'DXOS',\n description: trim`\n FilePlugin enables DXOS Composer to store and preview binary files — images, videos, and PDFs — as\n first-class ECHO objects. Uploaded bytes are routed through a pluggable backend system: the built-in\n inline backend serialises the file directly onto the ECHO document, while external backends (e.g. WNFS)\n can be contributed by other plugins.\n\n Accepted formats are images (jpg, jpeg, png, gif, webp, svg), videos (mp4, webm, mov), and PDFs,\n up to a 4 MB limit per file for the inline backend (other backends scale beyond it). The preview\n surface renders the appropriate HTML element for each MIME class and surfaces inside article,\n section, and slide roles.\n\n A File object's \\`data\\` field is a reference to a \\`Blob\\` ECHO object (from \\`@dxos/echo\\`), which\n abstracts where bytes actually live. \\`Blob.url\\`/\\`Blob.read\\` resolve a renderable URL or the raw\n bytes regardless of backend, keeping the File object schema backend-agnostic without a separate\n URL-resolver capability. A companion Markdown extension allows file objects to be embedded directly\n in Markdown documents.\n\n Backend selection is user-configurable in the plugin settings panel, which lists all registered\n backends with their descriptions. Selecting a backend persists the choice to the ECHO-backed settings\n atom so the preference is shared across devices.\n `,\n icon: { key: 'ph--file--regular', hue: 'indigo' },\n source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-file',\n spec: 'PLUGIN.mdl',\n tags: ['alpha'],\n },\n});\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { Plugin } from '@dxos/app-framework';\n\nimport config from '../dx.config';\n\nexport const meta = Plugin.getMetaFromConfig(config);\n"],"mappings":";;;;AAOA,IAAA,oBAAe,QAAQ,KAAK,EAC1B,QAAQ;CACN,KAAK;CACL,MAAM;CACN,QAAQ;CACR,aAAa,IAAI;;;;;;;;;;;;;;;;;;;;;CAqBjB,MAAM;EAAE,KAAK;EAAqB,KAAK;CAAS;CAChD,QAAQ;CACR,MAAM;CACN,MAAM,CAAC,OAAO;AAChB,EACF,CAAC;;;AC9BD,IAAa,OAAO,OAAO,kBAAkB,iBAAM"}
@@ -0,0 +1,11 @@
1
+ import { Capabilities, Capability } from "@dxos/app-framework";
2
+ import * as Effect from "effect/Effect";
3
+ import { FileOperationHandlerSet } from "#operations";
4
+ //#region src/capabilities/operation-handler.ts
5
+ var operation_handler_default = Capability.makeModule(Effect.fnUntraced(function* () {
6
+ return Capability.contributes(Capabilities.OperationHandler, FileOperationHandlerSet);
7
+ }));
8
+ //#endregion
9
+ export { operation_handler_default as default };
10
+
11
+ //# sourceMappingURL=chunk-operation-handler.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-operation-handler.mjs","names":[],"sources":["../../src/capabilities/operation-handler.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport { Capabilities, Capability } from '@dxos/app-framework';\nimport type { OperationHandlerSet } from '@dxos/compute';\n\nimport { FileOperationHandlerSet } from '#operations';\n\nexport default Capability.makeModule<OperationHandlerSet.OperationHandlerSet>(\n Effect.fnUntraced(function* () {\n return Capability.contributes(Capabilities.OperationHandler, FileOperationHandlerSet);\n }),\n);\n"],"mappings":";;;;AAWA,IAAA,4BAAe,WAAW,WACxB,OAAO,WAAW,aAAa;CAC7B,OAAO,WAAW,YAAY,aAAa,kBAAkB,uBAAuB;AACtF,CAAC,CACH"}
@@ -0,0 +1,55 @@
1
+ import { meta } from "#meta";
2
+ import { Capabilities, Capability } from "@dxos/app-framework";
3
+ import { File, FileAction } from "#types";
4
+ import { useCallback } from "react";
5
+ import { SchemaEx } from "@dxos/effect";
6
+ import { jsx } from "react/jsx-runtime";
7
+ import { Surface } from "@dxos/app-framework/ui";
8
+ import * as Effect from "effect/Effect";
9
+ import { AppSurface } from "@dxos/app-toolkit/ui";
10
+ import { FileArticle, FileSettings } from "#containers";
11
+ import { FileInput } from "#components";
12
+ //#region src/capabilities/FileUploadField.tsx
13
+ /**
14
+ * Form field that uploads a file for an upload-annotated property. It consumes the whole surface
15
+ * envelope, so it is registered without a `props` mapper.
16
+ */
17
+ var FileUploadField = ({ data, onValueChange }) => {
18
+ const ast = data.fieldPropertyAst;
19
+ const handleChange = useCallback((file) => {
20
+ if (ast) onValueChange?.(ast, file);
21
+ }, [ast, onValueChange]);
22
+ if (!ast) return null;
23
+ return /* @__PURE__ */ jsx(FileInput, {
24
+ schema: data.schema,
25
+ onChange: handleChange
26
+ });
27
+ };
28
+ //#endregion
29
+ //#region src/capabilities/react-surface.ts
30
+ var react_surface_default = Capability.makeModule(() => Effect.succeed(Capability.contributes(Capabilities.ReactSurface, [
31
+ Surface.create({
32
+ id: "article",
33
+ filter: AppSurface.oneOf(AppSurface.object(AppSurface.Article, File.File), AppSurface.object(AppSurface.Section, File.File), AppSurface.object(AppSurface.Slide, File.File)),
34
+ component: FileArticle,
35
+ props: ({ role, data: { subject } }) => ({
36
+ role,
37
+ subject
38
+ })
39
+ }),
40
+ Surface.create({
41
+ id: "createForm",
42
+ filter: AppSurface.formInputBySchema((ast) => !!SchemaEx.findAnnotation(ast, FileAction.UploadAnnotationId)),
43
+ component: FileUploadField
44
+ }),
45
+ Surface.create({
46
+ id: "pluginSettings",
47
+ filter: AppSurface.settings(AppSurface.Article, meta.profile.key),
48
+ component: FileSettings,
49
+ props: ({ data: { subject } }) => ({ subject })
50
+ })
51
+ ])));
52
+ //#endregion
53
+ export { react_surface_default as default };
54
+
55
+ //# sourceMappingURL=chunk-react-surface.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-react-surface.mjs","names":[],"sources":["../../src/capabilities/FileUploadField.tsx","../../src/capabilities/react-surface.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { useCallback } from 'react';\n\nimport { type Surface } from '@dxos/app-framework/ui';\nimport { type AppSurface } from '@dxos/app-toolkit/ui';\nimport { type FormFieldRendererProps } from '@dxos/react-ui-form';\n\nimport { FileInput } from '#components';\n\n/** The form renderer's own props ride alongside `data` on the surface envelope. */\nexport type FileUploadFieldProps = Surface.ComponentProps<AppSurface.FormInputData> &\n Pick<FormFieldRendererProps<File>, 'onValueChange'>;\n\n/**\n * Form field that uploads a file for an upload-annotated property. It consumes the whole surface\n * envelope, so it is registered without a `props` mapper.\n */\nexport const FileUploadField = ({ data, onValueChange }: FileUploadFieldProps) => {\n const ast = data.fieldPropertyAst;\n const handleChange = useCallback(\n (file: File) => {\n if (ast) {\n onValueChange?.(ast, file);\n }\n },\n [ast, onValueChange],\n );\n\n if (!ast) {\n return null;\n }\n\n return <FileInput schema={data.schema} onChange={handleChange} />;\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport { Capabilities, Capability } from '@dxos/app-framework';\nimport { Surface } from '@dxos/app-framework/ui';\nimport { AppSurface } from '@dxos/app-toolkit/ui';\nimport { SchemaEx } from '@dxos/effect';\n\nimport { FileArticle, FileSettings } from '#containers';\nimport { meta } from '#meta';\nimport { File, FileAction } from '#types';\n\nimport { FileUploadField } from './FileUploadField';\n\nexport default Capability.makeModule(() =>\n Effect.succeed(\n Capability.contributes(Capabilities.ReactSurface, [\n Surface.create({\n id: 'article',\n filter: AppSurface.oneOf(\n AppSurface.object(AppSurface.Article, File.File),\n AppSurface.object(AppSurface.Section, File.File),\n AppSurface.object(AppSurface.Slide, File.File),\n ),\n component: FileArticle,\n props: ({ role, data: { subject } }) => ({ role, subject }),\n }),\n Surface.create({\n id: 'createForm',\n filter: AppSurface.formInputBySchema(\n (ast) => !!SchemaEx.findAnnotation<Record<string, string[]>>(ast, FileAction.UploadAnnotationId),\n ),\n component: FileUploadField,\n }),\n Surface.create({\n id: 'pluginSettings',\n filter: AppSurface.settings(AppSurface.Article, meta.profile.key),\n component: FileSettings,\n props: ({ data: { subject } }) => ({ subject }),\n }),\n ]),\n ),\n);\n"],"mappings":";;;;;;;;;;;;;;;;AAoBA,IAAa,mBAAmB,EAAE,MAAM,oBAA0C;CAChF,MAAM,MAAM,KAAK;CACjB,MAAM,eAAe,aAClB,SAAe;EACd,IAAI,KACF,gBAAgB,KAAK,IAAI;CAE7B,GACA,CAAC,KAAK,aAAa,CACrB;CAEA,IAAI,CAAC,KACH,OAAO;CAGT,OAAO,oBAAC,WAAD;EAAW,QAAQ,KAAK;EAAQ,UAAU;CAAe,CAAA;AAClE;;;ACnBA,IAAA,wBAAe,WAAW,iBACxB,OAAO,QACL,WAAW,YAAY,aAAa,cAAc;CAChD,QAAQ,OAAO;EACb,IAAI;EACJ,QAAQ,WAAW,MACjB,WAAW,OAAO,WAAW,SAAS,KAAK,IAAI,GAC/C,WAAW,OAAO,WAAW,SAAS,KAAK,IAAI,GAC/C,WAAW,OAAO,WAAW,OAAO,KAAK,IAAI,CAC/C;EACA,WAAW;EACX,QAAQ,EAAE,MAAM,MAAM,EAAE,iBAAiB;GAAE;GAAM;EAAQ;CAC3D,CAAC;CACD,QAAQ,OAAO;EACb,IAAI;EACJ,QAAQ,WAAW,mBAChB,QAAQ,CAAC,CAAC,SAAS,eAAyC,KAAK,WAAW,kBAAkB,CACjG;EACA,WAAW;CACb,CAAC;CACD,QAAQ,OAAO;EACb,IAAI;EACJ,QAAQ,WAAW,SAAS,WAAW,SAAS,KAAK,QAAQ,GAAG;EAChE,WAAW;EACX,QAAQ,EAAE,MAAM,EAAE,iBAAiB,EAAE,QAAQ;CAC/C,CAAC;AACH,CAAC,CACH,CACF"}
@@ -0,0 +1,32 @@
1
+ import { a as Read } from "./chunk-types.mjs";
2
+ import { Operation } from "@dxos/compute";
3
+ import { ContentBlock } from "@dxos/types";
4
+ import { Blob, Database } from "@dxos/echo";
5
+ import * as Effect from "effect/Effect";
6
+ import * as Option from "effect/Option";
7
+ //#region src/operations/read.ts
8
+ var BASE64_CHUNK_SIZE = 32768;
9
+ var bytesToBase64 = (bytes) => {
10
+ let binary = "";
11
+ for (let offset = 0; offset < bytes.length; offset += BASE64_CHUNK_SIZE) binary += String.fromCharCode(...bytes.subarray(offset, offset + BASE64_CHUNK_SIZE));
12
+ return btoa(binary);
13
+ };
14
+ var handler = Read.pipe(Operation.withHandler(Effect.fn(function* ({ file }) {
15
+ const obj = yield* Database.load(file);
16
+ const blob = yield* Database.load(obj.data);
17
+ const type = blob.type ?? "application/octet-stream";
18
+ const urlOption = yield* Blob.url(blob);
19
+ const url = yield* Option.match(urlOption, {
20
+ onSome: Effect.succeed,
21
+ onNone: () => Blob.read(blob).pipe(Effect.map((bytes) => `data:${type};base64,${bytesToBase64(bytes)}`))
22
+ });
23
+ return ContentBlock.ContentBlockResult.make({ content: [ContentBlock.File.make({
24
+ url,
25
+ name: obj.name,
26
+ mediaType: type
27
+ })] });
28
+ })));
29
+ //#endregion
30
+ export { handler as default };
31
+
32
+ //# sourceMappingURL=chunk-read.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-read.mjs","names":[],"sources":["../../src/operations/read.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\nimport * as Option from 'effect/Option';\n\nimport { Operation } from '@dxos/compute';\nimport { Blob, Database } from '@dxos/echo';\nimport { ContentBlock } from '@dxos/types';\n\nimport { FileOperation } from '../types';\n\nconst BASE64_CHUNK_SIZE = 0x8000;\n\n// `String.fromCharCode(...bytes)` blows the call stack / argument limit for large arrays, so the\n// bytes are base64-encoded in chunks. `Buffer` isn't available in browser bundles.\nconst bytesToBase64 = (bytes: Uint8Array): string => {\n let binary = '';\n for (let offset = 0; offset < bytes.length; offset += BASE64_CHUNK_SIZE) {\n binary += String.fromCharCode(...bytes.subarray(offset, offset + BASE64_CHUNK_SIZE));\n }\n return btoa(binary);\n};\n\nconst handler: Operation.WithHandler<typeof FileOperation.Read> = FileOperation.Read.pipe(\n Operation.withHandler(\n Effect.fn(function* ({ file }) {\n const obj = yield* Database.load(file);\n const blob = yield* Database.load(obj.data);\n const type = blob.type ?? 'application/octet-stream';\n const urlOption = yield* Blob.url(blob);\n const url = yield* Option.match(urlOption, {\n onSome: Effect.succeed,\n // No renderable URL from the backend (e.g. external storage without `getUrl`) — fall back\n // to reading the bytes directly and encoding them as a `data:` URL.\n onNone: () => Blob.read(blob).pipe(Effect.map((bytes) => `data:${type};base64,${bytesToBase64(bytes)}`)),\n });\n\n return ContentBlock.ContentBlockResult.make({\n content: [\n ContentBlock.File.make({\n url,\n name: obj.name,\n mediaType: type,\n }),\n ],\n });\n }),\n ),\n);\n\nexport default handler;\n"],"mappings":";;;;;;;AAaA,IAAM,oBAAoB;AAI1B,IAAM,iBAAiB,UAA8B;CACnD,IAAI,SAAS;CACb,KAAK,IAAI,SAAS,GAAG,SAAS,MAAM,QAAQ,UAAU,mBACpD,UAAU,OAAO,aAAa,GAAG,MAAM,SAAS,QAAQ,SAAS,iBAAiB,CAAC;CAErF,OAAO,KAAK,MAAM;AACpB;AAEA,IAAM,UAAA,KAA+E,KACnF,UAAU,YACR,OAAO,GAAG,WAAW,EAAE,QAAQ;CAC7B,MAAM,MAAM,OAAO,SAAS,KAAK,IAAI;CACrC,MAAM,OAAO,OAAO,SAAS,KAAK,IAAI,IAAI;CAC1C,MAAM,OAAO,KAAK,QAAQ;CAC1B,MAAM,YAAY,OAAO,KAAK,IAAI,IAAI;CACtC,MAAM,MAAM,OAAO,OAAO,MAAM,WAAW;EACzC,QAAQ,OAAO;EAGf,cAAc,KAAK,KAAK,IAAI,CAAC,CAAC,KAAK,OAAO,KAAK,UAAU,QAAQ,KAAK,UAAU,cAAc,KAAK,GAAG,CAAC;CACzG,CAAC;CAED,OAAO,aAAa,mBAAmB,KAAK,EAC1C,SAAS,CACP,aAAa,KAAK,KAAK;EACrB;EACA,MAAM,IAAI;EACV,WAAW;CACb,CAAC,CACH,EACF,CAAC;AACH,CAAC,CACH,CACF"}
@@ -0,0 +1,23 @@
1
+ import { meta } from "#meta";
2
+ import { Capability } from "@dxos/app-framework";
3
+ import { AppCapabilities } from "@dxos/app-toolkit";
4
+ import { FileCapabilities, Settings } from "#types";
5
+ import { createKvsStore } from "@dxos/effect";
6
+ import * as Effect from "effect/Effect";
7
+ //#region src/capabilities/settings.ts
8
+ var settings_default = Capability.makeModule(() => Effect.sync(() => {
9
+ const settingsAtom = createKvsStore({
10
+ key: meta.profile.key,
11
+ schema: Settings.Settings,
12
+ defaultValue: () => ({})
13
+ });
14
+ return [Capability.contributes(FileCapabilities.SettingsAtom, settingsAtom), Capability.contributes(AppCapabilities.Settings, {
15
+ prefix: meta.profile.key,
16
+ schema: Settings.Settings,
17
+ atom: settingsAtom
18
+ })];
19
+ }));
20
+ //#endregion
21
+ export { settings_default as default };
22
+
23
+ //# sourceMappingURL=chunk-settings.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-settings.mjs","names":[],"sources":["../../src/capabilities/settings.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport { Capability } from '@dxos/app-framework';\nimport { AppCapabilities } from '@dxos/app-toolkit';\nimport { createKvsStore } from '@dxos/effect';\n\nimport { meta } from '#meta';\nimport { FileCapabilities, Settings } from '#types';\n\nexport default Capability.makeModule(() =>\n Effect.sync(() => {\n const settingsAtom = createKvsStore({\n key: meta.profile.key,\n schema: Settings.Settings,\n defaultValue: () => ({}),\n });\n\n return [\n Capability.contributes(FileCapabilities.SettingsAtom, settingsAtom),\n Capability.contributes(AppCapabilities.Settings, {\n prefix: meta.profile.key,\n schema: Settings.Settings,\n atom: settingsAtom,\n }),\n ];\n }),\n);\n"],"mappings":";;;;;;;AAaA,IAAA,mBAAe,WAAW,iBACxB,OAAO,WAAW;CAChB,MAAM,eAAe,eAAe;EAClC,KAAK,KAAK,QAAQ;EAClB,QAAQ,SAAS;EACjB,qBAAqB,CAAC;CACxB,CAAC;CAED,OAAO,CACL,WAAW,YAAY,iBAAiB,cAAc,YAAY,GAClE,WAAW,YAAY,gBAAgB,UAAU;EAC/C,QAAQ,KAAK,QAAQ;EACrB,QAAQ,SAAS;EACjB,MAAM;CACR,CAAC,CACH;AACF,CAAC,CACH"}
@@ -0,0 +1,10 @@
1
+ import { Capability } from "@dxos/app-framework";
2
+ import { AppCapabilities } from "@dxos/app-toolkit";
3
+ import * as Effect from "effect/Effect";
4
+ import { FileSkill } from "#skills";
5
+ //#region src/capabilities/skill-definition.ts
6
+ var skillDefinition = () => Effect.succeed([Capability.contributes(AppCapabilities.SkillDefinition, FileSkill)]);
7
+ //#endregion
8
+ export { skillDefinition as default };
9
+
10
+ //# sourceMappingURL=chunk-skill-definition.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-skill-definition.mjs","names":[],"sources":["../../src/capabilities/skill-definition.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport { Capability } from '@dxos/app-framework';\nimport { AppCapabilities } from '@dxos/app-toolkit';\n\nimport { FileSkill } from '#skills';\n\nconst skillDefinition = () => Effect.succeed([Capability.contributes(AppCapabilities.SkillDefinition, FileSkill)]);\n\nexport default skillDefinition;\n"],"mappings":";;;;;AAWA,IAAM,wBAAwB,OAAO,QAAQ,CAAC,WAAW,YAAY,gBAAgB,iBAAiB,SAAS,CAAC,CAAC"}