@dopame/acde-linux-x64 0.0.0-stage → 0.0.10

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 (165) hide show
  1. package/LICENSE +8 -0
  2. package/bin/acde +66 -0
  3. package/bin/acde.cmd +45 -0
  4. package/bin/acdebin-dbg +4 -0
  5. package/bin/app/brand/art.mjs +53 -0
  6. package/bin/app/brand/brand.css +39 -0
  7. package/bin/app/brand/lockup.mjs +17 -0
  8. package/bin/app/brand/mark.mjs +124 -0
  9. package/bin/app/dist/broker.js +1 -0
  10. package/bin/app/dist/web.js +1 -0
  11. package/bin/app/greet/index.html +177 -0
  12. package/bin/app/greet/package.json +16 -0
  13. package/bin/app/package.json +35 -0
  14. package/lib/acde/LICENSE +8 -0
  15. package/lib/acde/dist/chunk-0n69bv5q.js +1 -0
  16. package/lib/acde/dist/chunk-1cw92x19.js +1 -0
  17. package/lib/acde/dist/chunk-458e14zb.js +1 -0
  18. package/lib/acde/dist/chunk-88mxgp54.js +1 -0
  19. package/lib/acde/dist/chunk-8dv09ven.js +1 -0
  20. package/lib/acde/dist/chunk-8fxsj5h6.js +1 -0
  21. package/lib/acde/dist/chunk-8pf3kk8v.js +1 -0
  22. package/lib/acde/dist/chunk-8r65bz1e.js +1 -0
  23. package/lib/acde/dist/chunk-9hpnkmxn.js +1 -0
  24. package/lib/acde/dist/chunk-a7w41xr9.js +1 -0
  25. package/lib/acde/dist/chunk-db7qqt8a.js +1 -0
  26. package/lib/acde/dist/chunk-e3rzr5fj.js +1 -0
  27. package/lib/acde/dist/chunk-ehms6xjp.js +1 -0
  28. package/lib/acde/dist/chunk-ggykhab6.js +1 -0
  29. package/lib/acde/dist/chunk-gt70e8h2.js +1 -0
  30. package/lib/acde/dist/chunk-hcf0xd5c.js +1 -0
  31. package/lib/acde/dist/chunk-hdrvz9sa.js +1 -0
  32. package/lib/acde/dist/chunk-jjq2mn94.js +1 -0
  33. package/lib/acde/dist/chunk-k8f4m94e.js +7 -0
  34. package/lib/acde/dist/chunk-mtgy1bm0.js +1 -0
  35. package/lib/acde/dist/chunk-mtw6p7kv.js +1 -0
  36. package/lib/acde/dist/chunk-nwkq7cpp.js +1 -0
  37. package/lib/acde/dist/chunk-nyr54266.js +1 -0
  38. package/lib/acde/dist/chunk-p0h5kspx.js +1 -0
  39. package/lib/acde/dist/chunk-pdj3h86e.js +1 -0
  40. package/lib/acde/dist/chunk-q9dbzac5.js +1 -0
  41. package/lib/acde/dist/chunk-qrhbrjzk.js +1 -0
  42. package/lib/acde/dist/chunk-s8p0y5ap.js +1 -0
  43. package/lib/acde/dist/chunk-t0xqxe00.js +1 -0
  44. package/lib/acde/dist/chunk-tgn5c2mw.js +1 -0
  45. package/lib/acde/dist/chunk-wmj6zwvn.js +1 -0
  46. package/lib/acde/dist/chunk-wnk81fkc.js +1 -0
  47. package/lib/acde/dist/chunk-x208z38w.js +1 -0
  48. package/lib/acde/dist/chunk-xq76vbg6.js +1 -0
  49. package/lib/acde/dist/chunk-xr9gh5x2.js +1 -0
  50. package/lib/acde/dist/chunk-yytnhz6e.js +1 -0
  51. package/lib/acde/dist/chunk-z0ts25s1.js +1 -0
  52. package/lib/acde/dist/chunk-zdyw1ckt.js +1 -0
  53. package/lib/acde/dist/cli.js +1 -0
  54. package/lib/acde/dist/driver/index.js +1 -0
  55. package/lib/acde/dist/e2e/index.js +1 -0
  56. package/lib/acde/dist/types/configs.d.ts +1 -0
  57. package/lib/acde/dist/types/driver/acde.d.ts +106 -0
  58. package/lib/acde/dist/types/driver/clock.d.ts +32 -0
  59. package/lib/acde/dist/types/driver/expect.d.ts +60 -0
  60. package/lib/acde/dist/types/driver/format.d.ts +16 -0
  61. package/lib/acde/dist/types/driver/index.d.ts +13 -0
  62. package/lib/acde/dist/types/driver/input.d.ts +75 -0
  63. package/lib/acde/dist/types/driver/locator.d.ts +151 -0
  64. package/lib/acde/dist/types/driver/logs.d.ts +26 -0
  65. package/lib/acde/dist/types/driver/png.d.ts +31 -0
  66. package/lib/acde/dist/types/driver/poll.d.ts +4 -0
  67. package/lib/acde/dist/types/driver/screenshot.d.ts +37 -0
  68. package/lib/acde/dist/types/driver/trace.d.ts +26 -0
  69. package/lib/acde/dist/types/driver/types.d.ts +213 -0
  70. package/lib/acde/dist/types/driver/video.d.ts +14 -0
  71. package/lib/acde/dist/types/driver/window.d.ts +77 -0
  72. package/lib/acde/dist/types/e2e/index.d.ts +78 -0
  73. package/lib/acde/dist/types/e2e/settings.d.ts +39 -0
  74. package/lib/acde/dist/types/ipc.d.ts +24 -0
  75. package/lib/acde/dist/types/paths.d.ts +5 -0
  76. package/lib/acde/dist/types/pm.d.ts +20 -0
  77. package/lib/acde/dist/types/runtime.d.ts +49 -0
  78. package/lib/acde/dist/types/sessions.d.ts +80 -0
  79. package/lib/acde/dist/types/sockets.d.ts +4 -0
  80. package/lib/acde/launcher.d.mts +25 -0
  81. package/lib/acde/launcher.mjs +1 -0
  82. package/lib/acde/package.json +31 -0
  83. package/lib/acde/skills/acde/SKILL.md +114 -0
  84. package/lib/acde/skills/acde/references/guide/agents.md +50 -0
  85. package/lib/acde/skills/acde/references/guide/assets.md +40 -0
  86. package/lib/acde/skills/acde/references/guide/backend.md +94 -0
  87. package/lib/acde/skills/acde/references/guide/capabilities.md +157 -0
  88. package/lib/acde/skills/acde/references/guide/config.md +73 -0
  89. package/lib/acde/skills/acde/references/guide/desktop.md +165 -0
  90. package/lib/acde/skills/acde/references/guide/dev.md +66 -0
  91. package/lib/acde/skills/acde/references/guide/doctor.md +144 -0
  92. package/lib/acde/skills/acde/references/guide/files.md +181 -0
  93. package/lib/acde/skills/acde/references/guide/frameworks.md +74 -0
  94. package/lib/acde/skills/acde/references/guide/install.md +34 -0
  95. package/lib/acde/skills/acde/references/guide/mcp.md +64 -0
  96. package/lib/acde/skills/acde/references/guide/open.md +107 -0
  97. package/lib/acde/skills/acde/references/guide/services.md +147 -0
  98. package/lib/acde/skills/acde/references/guide/testing.md +378 -0
  99. package/lib/acde/skills/acde/references/guide/tools.md +291 -0
  100. package/lib/acde/skills/acde/references/guide/window.md +167 -0
  101. package/lib/acde/skills/acde/references/reference/cli.md +221 -0
  102. package/lib/acde/skills/acde/references/reference/config.md +51 -0
  103. package/lib/acde/skills/acde/references/reference/env.md +44 -0
  104. package/lib/acde/skills/acde/references/reference/manifest.md +92 -0
  105. package/lib/acde/skills/acde/references/reference/modules.md +586 -0
  106. package/lib/acde/skills/acde/references/reference/platforms.md +23 -0
  107. package/lib/acde/skills/acde/references/reference/service.md +47 -0
  108. package/lib/acde/skills/acde/references/reference/testing.md +265 -0
  109. package/lib/acde/skills/acde/references/reference/web.md +94 -0
  110. package/lib/acde/skills/acde/references/start/first-app.md +87 -0
  111. package/lib/acde/skills/acde/references/start/install.md +232 -0
  112. package/lib/acde/skills/acde/references/start/project.md +77 -0
  113. package/lib/acde/templates/app/acde.config.jsonc +27 -0
  114. package/lib/acde/templates/app/gitignore +2 -0
  115. package/lib/acde/templates/app/index.html +22 -0
  116. package/lib/acde/templates/app/package.json +5 -0
  117. package/lib/acde/templates/app/src/i18n.js +26 -0
  118. package/lib/acde/templates/app/src/locales/en.js +4 -0
  119. package/lib/acde/templates/app/src/locales/zh.js +4 -0
  120. package/lib/acde/templates/app/src/main.js +5 -0
  121. package/lib/acde/templates/backend/server.ts +7 -0
  122. package/lib/acde/templates/backend/src/main.js +6 -0
  123. package/lib/acde/templates/desktop/acde.config.jsonc +17 -0
  124. package/lib/acde/templates/desktop/gitignore +2 -0
  125. package/lib/acde/templates/desktop/index.html +19 -0
  126. package/lib/acde/templates/desktop/package.json +5 -0
  127. package/lib/acde/templates/desktop/src/desktop.js +16 -0
  128. package/lib/acde/templates/desktop/src/i18n.js +26 -0
  129. package/lib/acde/templates/desktop/src/locales/en.js +4 -0
  130. package/lib/acde/templates/desktop/src/locales/zh.js +4 -0
  131. package/lib/acde/templates/framework/acde.config.jsonc +26 -0
  132. package/lib/acde-api/LICENSE +5 -0
  133. package/lib/acde-api/config.schema.json +563 -0
  134. package/lib/acde-api/package.json +51 -0
  135. package/lib/acde-api/src/app.d.ts +36 -0
  136. package/lib/acde-api/src/frame.d.ts +15 -0
  137. package/lib/acde-api/src/frame.js +1 -0
  138. package/lib/acde-api/src/index.d.ts +11 -0
  139. package/lib/acde-api/src/index.js +1 -0
  140. package/lib/acde-api/src/modules/app-modules.d.ts +81 -0
  141. package/lib/acde-api/src/modules/modules.d.ts +113 -0
  142. package/lib/acde-api/src/modules/shell-modules.d.ts +32 -0
  143. package/lib/acde-api/src/runtime/level.d.ts +1 -0
  144. package/lib/acde-api/src/runtime/level.js +1 -0
  145. package/lib/acde-api/src/runtime/load.d.ts +8 -0
  146. package/lib/acde-api/src/runtime/load.js +1 -0
  147. package/lib/acde-api/src/runtime/places.d.ts +3 -0
  148. package/lib/acde-api/src/runtime/places.js +1 -0
  149. package/lib/acde-api/src/runtime/platforms.d.ts +5 -0
  150. package/lib/acde-api/src/runtime/platforms.js +1 -0
  151. package/lib/acde-api/src/shell.d.ts +14 -0
  152. package/lib/acde-api/src/types/capabilities.d.ts +34 -0
  153. package/lib/acde-api/src/types/config.d.ts +55 -0
  154. package/lib/acde-api/src/types/events.d.ts +151 -0
  155. package/lib/acde-api/src/types/manifest.d.ts +83 -0
  156. package/lib/acde-api/src/types/office.d.ts +118 -0
  157. package/lib/acde-api/src/types/page.d.ts +1090 -0
  158. package/lib/acde-api/src/types/permissions.d.ts +50 -0
  159. package/lib/acde-api/src/types/service.d.ts +35 -0
  160. package/lib/acde-api/src/validate.d.ts +64 -0
  161. package/lib/acde-api/src/validate.js +1 -0
  162. package/lib/acde-api/src/vite.d.ts +3 -0
  163. package/lib/acde-api/src/vite.js +9 -0
  164. package/package.json +11 -4
  165. package/README.md +0 -3
@@ -0,0 +1,144 @@
1
+ # Check the setup
2
+
3
+ `acde doctor` checks what `acde dev` and `acde test` need on this machine and says how to fix what is wrong; `acde doctor --fix` fixes what acde may fix itself. Run it after installing, in a project you are about to work on, and whenever `acde dev`, `acde test` or the MCP `start` does not come up.
4
+
5
+ ```sh
6
+ acde doctor
7
+ ```
8
+
9
+ ```text
10
+ ok acde: 0.0.9 on Bun 1.4.4 (/Users/me/.acde/versions/0.0.9)
11
+ ok PATH: acde on PATH is this one (/Users/me/.acde/bin/acde)
12
+ ok Bun and Node.js: Bun 1.4+ at /Users/me/.bun/bin/bun; Node.js 22.3+ at /opt/homebrew/bin/node
13
+ ok acdert: acdert 0.0.9 (contract level 1) at /Users/me/.acde/versions/0.0.9/bin/acdert (installed next to acde)
14
+ ok contract level: level 1 on both sides (acdert 0.0.9, SDK 0.0.9)
15
+ ok host files: /Users/me/.acde/versions/0.0.9/bin/app/dist
16
+ ok gpu: Apple M3 Pro (Metal, integrated GPU): drew a test frame in 38 ms
17
+ ok acde dev sessions: none running
18
+ ok project: notes, app (acde.config.jsonc): acde check finds nothing wrong
19
+ ok platform: macos (desktop, standalone) has every required capability
20
+ ok dependencies: installed with bun (runs on Bun 1.4+), @dopame/acde-api 0.0.9
21
+ ok git: .acde/ (the build output) is ignored
22
+ ok agent skill: current in /Users/me/.agents/skills/acde, /Users/me/.claude/skills/acde
23
+ acde doctor: everything acde needs is here
24
+ ```
25
+
26
+ Each line is `<status> <check>: <what it found>`:
27
+
28
+ | Status | Meaning |
29
+ | --- | --- |
30
+ | `ok` | Fine |
31
+ | `warn` | Works, but something will get in the way; a `fix:` line follows |
32
+ | `error` | `acde dev` or `acde test` will not work; a `fix:` line follows when there is one |
33
+ | `skip` | Not checked: there is no project here, or the check needs one that failed |
34
+
35
+ The last line counts errors and warnings. The exit code is 1 when any check is an error, else 0. `acde doctor <dir>` checks the project in `<dir>` instead of the current directory (a directory with no acde project is then an error), and `--online` adds whether a newer acde is out.
36
+
37
+ ## What it checks
38
+
39
+ | Check | What it looks at |
40
+ | --- | --- |
41
+ | `acde` | The version of acde and the JavaScript runtime it runs on |
42
+ | `PATH` | Whether the `acde` on PATH is this one, not another install or checkout |
43
+ | `Bun and Node.js` | Bun 1.4+ and Node.js 22.3+ on PATH; `acde test` and projects that use bun need Bun |
44
+ | `acdert` | The runtime `acde dev` would run (`ACDERT`, else the one installed next to acde, else PATH), and whether it runs |
45
+ | `contract level` | Whether the runtime runs the contract level this acde builds, and whether it is the release this acde pairs with |
46
+ | `host files`, `gpu`, `display` | The runtime's own check, `acdert doctor`: its host files, the GPU (it draws a small frame and reads it back), and on Linux whether a host window can open |
47
+ | `acde dev sessions`, `approval` | The running `acde dev` sessions, and every camera or microphone prompt that waits for an answer |
48
+ | `project` | `acde check` of the project |
49
+ | `platform` | Whether this platform has every capability the project's apps require |
50
+ | `dependencies` | The project's dependencies are installed (also when a workspace keeps them in a parent directory), its runtime is on PATH, and its `@dopame/acde-api` (in `package.json` and installed) is the version this acde is built with; after 0.0.9, an older SDK is reported here once, fixed by `acde sdk update` (followed by the directory when `acde doctor` was given one) |
51
+ | `git` | Whether git ignores `.acde/` |
52
+ | `agent skill` | Whether the acde skill is installed for coding agents and current |
53
+ | `latest release` | With `--online`: whether a newer acde is out |
54
+
55
+ ## Common findings
56
+
57
+ No runtime:
58
+
59
+ ```text
60
+ error acdert: acde dev needs the Dopame runtime (acdert) and found none: no /Users/me/.acde/versions/0.0.9/bin/acdert, which the acde installer puts there: run the installer again, set ACDERT to an acdert binary, or put acdert on PATH
61
+ fix: run the installer again: curl -fsSL https://pkgs.dopame.ai/acde/install.sh | sh (it puts the acdert that pairs with acde next to it)
62
+ ```
63
+
64
+ A runtime that does not start reports why: a missing system library (`it exited with code 127: … error while loading shared libraries: …`), a file that is not executable (`EACCES`), or on macOS a binary that a download left in quarantine, with `xattr -d com.apple.quarantine <path>` as the fix.
65
+
66
+ A runtime from before `acdert doctor` cannot check its files and the GPU:
67
+
68
+ ```text
69
+ warn runtime check: acdert 0.0.8 cannot check itself (it has no acdert doctor), so its GPU and host files were not checked
70
+ ```
71
+
72
+ A runtime that cannot draw is an error of the `gpu` check, with the same reason `acde dev` would log at startup after `web layer unavailable:`. On Linux without the GPU's Vulkan driver:
73
+
74
+ ```text
75
+ error gpu: the system has a GPU (card0, kernel driver xe) but no usable Vulkan driver was found: install the GPU's Vulkan driver (for example vulkan-intel, vulkan-radeon, nvidia-utils) or point VK_DRIVER_FILES at its ICD (wgpu: No suitable graphics adapter found; …)
76
+ ```
77
+
78
+ Install the driver (see [The runtime](../start/install.md#the-runtime)); there is no software fallback. When it works, the line names the GPU, the graphics API and the driver, such as `ok gpu: Intel(R) Graphics (PTL) (Vulkan, integrated GPU, driver Intel open-source Mesa driver Mesa 26.2.5-arch3.1): drew a test frame in 41 ms`.
79
+
80
+ On Linux in a shell with no graphical session (over SSH, for example):
81
+
82
+ ```text
83
+ warn display: neither WAYLAND_DISPLAY nor DISPLAY is set, so acdert app and acdert desktop cannot open a host window here: they would try to start a session on the screen (DRM) instead
84
+ fix: run acde dev --headless, or run it inside a graphical session
85
+ ```
86
+
87
+ A camera or microphone prompt that waits:
88
+
89
+ ```text
90
+ warn approval: recorder waits for approval of microphone in acde dev 4242, so its getUserMedia waits until the prompt is answered
91
+ fix: acde allow (or acde deny): approving is the user's decision
92
+ ```
93
+
94
+ ## Fix what it can
95
+
96
+ `acde doctor --fix` runs the checks, prints them, then fixes what it can and checks again:
97
+
98
+ | Fix | When | Asks first |
99
+ | --- | --- | --- |
100
+ | `acde skills install` | The acde skill is not installed for any coding agent, or an installed one is outdated or broken | No |
101
+ | Add `.acde/` to the project's `.gitignore` | git does not ignore the build output | No |
102
+ | Remove and make again the sessions directory | It is a link or a file, not a directory | No |
103
+ | Run the installer of this acde version again (`install.sh --version <this version> --no-modify-path`, `install.ps1 -Version … -NoModifyPath` on Windows) | The runtime next to acde is missing, does not run, is of another contract level or release, or lacks its host files; only for an installed acde, and not when `ACDERT` names the runtime | Yes: it downloads the runtime and replaces the install, all or nothing, as the installer always does |
104
+ | The project's install (`bun install`, `npm install`, …) | Its dependencies are not installed | Yes: it downloads packages and runs their install scripts |
105
+ | `acde stop --session <pid>` | A running `acde dev` does not answer | Yes: it ends a process you started |
106
+
107
+ In a terminal it asks about each fix in the last column. Without a terminal (a script, a coding agent) it skips them and says so, `skipped: … (it asks first: pass --yes to allow it)`, unless it is given `--yes`. A coding agent passes `--yes` only when the user agreed.
108
+
109
+ It never touches what is the user's to decide: a camera or microphone prompt, a GPU driver or a system library, a binary in macOS quarantine, another acde on PATH, the project's code and config. Those keep their `fix:` line.
110
+
111
+ ```text
112
+ fixing: install the acde skill for the coding agents found here (acde skills install)
113
+ acde: skill linked to /Users/me/.agents/skills/acde (Cline, Codex, …)
114
+ fixed: install the acde skill for the coding agents found here (acde skills install)
115
+ skipped: install the project's dependencies (bun install in /Users/me/notes) (it asks first: pass --yes to allow it)
116
+ acde doctor: after fixing:
117
+ ok acde: 0.0.9 on Bun 1.4.4 (/Users/me/.acde/versions/0.0.9)
118
+ …
119
+ ```
120
+
121
+ ## acde dev points here
122
+
123
+ When `acde dev` finds no runtime, its error ends with `(acde doctor checks the whole setup)`. When the runtime exits before the session is ready, `acde dev` prints the runtime's log line and then:
124
+
125
+ ```text
126
+ acde: acdert exited (1) before it was ready; acde doctor checks the runtime, the GPU and this setup
127
+ ```
128
+
129
+ ## In scripts and agents
130
+
131
+ `acde doctor --json` prints one object:
132
+
133
+ ```json
134
+ {
135
+ "ok": false,
136
+ "checks": [
137
+ { "id": "acdert", "title": "acdert", "status": "error", "summary": "…", "fix": "…" }
138
+ ]
139
+ }
140
+ ```
141
+
142
+ `ok` is false when a check is an error. A check that `--fix` can repair carries `"repair": {"action", "target", "ask", "does"}` (`ask` is true for the fixes that ask first), and with `--fix` the object also has `"fixed": [{"repair", "result", "said"}]`, `result` being `fixed`, `failed` or `skipped`; the progress lines then go to stderr. The ids are `acde`, `path`, `runtimes`, `acdert`, `contract`, `runtime` (when the runtime cannot check itself), `runtime.host`, `runtime.gpu`, `runtime.display`, `sessions`, `approval`, `project`, `platform`, `dependencies`, `gitignore`, `skill` and `latest`; `fix` is left out when there is none. The MCP server has the same check as its `doctor` tool, and the fixes that do not ask as `doctor_fix` (it names the others and leaves them to the user); see [MCP](mcp.md).
143
+
144
+ The runtime's part also runs on its own, `acdert doctor` (or `acdert doctor --json`), with the same exit codes.
@@ -0,0 +1,181 @@
1
+ # Files and folders
2
+
3
+ There are three ways to read and write files:
4
+
5
+ 1. **Standard file pickers**: `showOpenFilePicker`, `showSaveFilePicker`, `showDirectoryPicker`, `<input type="file">`. No capability needed; you get whatever the user picks.
6
+ 2. **`acde:fs`**: files by "root": the app's own private folder, the shared folders it names (Downloads, Documents, ...), and folders the user picked. Suited to apps like file managers and editors. Requires the `fs` capability.
7
+ 3. **Private storage**: `navigator.storage.getDirectory()` (OPFS), IndexedDB, `localStorage`, for the app's own data.
8
+
9
+ An app works with handles and root ids, not with absolute paths; only the desktop and its bundled system apps see paths.
10
+
11
+ ## File pickers
12
+
13
+ They work just like in a browser:
14
+
15
+ ```js
16
+ const [handle] = await window.showOpenFilePicker({
17
+ types: [{ description: 'Markdown', accept: { 'text/markdown': ['.md'] } }],
18
+ })
19
+ const text = await (await handle.getFile()).text()
20
+ ```
21
+
22
+ - The dialog is the system's own (the open / save panel on macOS, the file dialog on Windows, the portal on Linux).
23
+ - Only this page can read and write the picked file.
24
+ - A folder picked with `showDirectoryPicker()` is also **granted to the app**: it shows up as a root in `acde:fs` (`source: 'granted'`) and is still there on the next launch. The default pick is read-only (the root has `mode: 'read_only'`); `showDirectoryPicker({ mode: 'readwrite' })` gives a writable one. Give it back with `fs.revoke(id)`.
25
+ - When the user cancels, the promise rejects with `AbortError`. A headless session has no dialog: a picker rejects with `AbortError` there too, unless a test answered it in advance (`chooseFiles`, `chooseFolder`, `chooseSaveFile`; see [Testing](testing.md)).
26
+
27
+ ## acde:fs
28
+
29
+ ```jsonc
30
+ { "capabilities": { "fs": true } }
31
+ ```
32
+
33
+ ```js
34
+ import { fs } from 'acde:fs'
35
+
36
+ const roots = await fs.roots()
37
+ // [{ id: 'private', label: '…', source: 'private', mode: 'writable', reason: null, exists: true }]
38
+
39
+ await fs.writeFile('private', 'notes/todo.md', '- buy milk\n', { mkdirs: true })
40
+
41
+ const { entries } = await fs.list('private', 'notes')
42
+ const { text, modifiedMs } = await fs.readText('private', 'notes/todo.md')
43
+ await fs.writeFile('private', 'notes/todo.md', text + '- a new item\n', { ifModifiedMs: modifiedMs })
44
+ ```
45
+
46
+ ### Roots
47
+
48
+ The first argument of every call is a root's **`id`**, and the second is a path under that root. A root's `id` is an opaque name, not a path: `fs.list('/home/me/Downloads')` is rejected.
49
+
50
+ There are three kinds of root:
51
+
52
+ | `source` | `id` | What it is | How the app gets it |
53
+ | --- | --- | --- | --- |
54
+ | `private` | `private` | The app's own data folder, created on first use | `"fs": true` (or any `fs` value) |
55
+ | `place` | The place's name, such as `downloads` | A shared folder of the user | Name it in `fs.roots` (see [Shared places](#shared-places)) |
56
+ | `granted` | `folder-` plus 16 hex digits | A folder the user picked | `showDirectoryPicker()` |
57
+
58
+ `roots()` returns one item per root the app can use now: `{id, label, source, mode, reason, exists}`, plus `place` (the place's name) for a shared place. `label` is a name to show the user (the folder's name for a picked folder). `mode` is `writable`, `read_only` (a folder picked without `mode: 'readwrite'`), or `unavailable`; when it is `unavailable`, `reason` says why (`the folder does not exist`) and `exists` is `false`.
59
+
60
+ Find a picked folder's root by the handle's name:
61
+
62
+ ```js
63
+ const handle = await window.showDirectoryPicker({ mode: 'readwrite' })
64
+ const picked = (await fs.roots()).find((root) => root.source === 'granted' && root.label === handle.name)
65
+ await fs.writeFile(picked.id, 'export.txt', 'hello')
66
+ ```
67
+
68
+ **Keep working data in `private`.** It needs no declaration beyond `fs` and no picker, it is always writable, and it is not a root of any other app. Use a shared place or a picked folder only for files the user should find outside the app. On disk the private folder is `$XDG_DATA_HOME/acde/files/<app id>/`, which is handy when debugging; the app itself only knows it as `private`.
69
+
70
+ ### Shared places
71
+
72
+ ```jsonc
73
+ { "capabilities": { "fs": { "roots": ["downloads", "documents"] } } }
74
+ ```
75
+
76
+ `fs.roots` lists shared places by name: `desktop`, `documents`, `downloads`, `music`, `pictures`, `videos`. Anything else fails `acde check`: `fs.roots "work" is not a shared place (desktop, documents, downloads, music, pictures, videos); any other folder is one the user picks (showDirectoryPicker() with "fs": true)`, and for a path, `fs.roots "~/Documents" is not a shared place: write "documents", the runtime finds that folder through the system (also when it was moved)`. `private` and picked folders come with every `fs` value and are not listed.
77
+
78
+ Declaring a place is what gives the app access to it; the user is not asked. Each named place is then in `roots()` as `{ id: 'downloads', source: 'place', place: 'downloads', … }` and its `id` works as the first argument: `fs.list('downloads')`. When the folder does not exist on this machine, the root has `mode: 'unavailable'` and calls on it reject with `ENOENT`.
79
+
80
+ ### Paths
81
+
82
+ The second argument is a **relative path** under the root, separated by `/`; `''` (or leaving it out in `list`) is the root itself. Absolute paths, and escaping the root with `..` or a symbolic link, are rejected.
83
+
84
+ ### Common calls
85
+
86
+ | Call | Returns |
87
+ | --- | --- |
88
+ | `roots()` | The roots the app can use now |
89
+ | `list(root, path?, hidden?)` | `{root, path, entries, truncated, skipped?}`: folders first, at most 5000 entries (`truncated`); hidden entries (a leading dot, on Windows also the hidden or system attribute) only with `hidden` `true`; `skipped` counts entries whose names are not valid Unicode |
90
+ | `stat(root, path)` | `{name, kind, size, modifiedMs, version, path}` (does not follow symbolic links) |
91
+ | `readText(root, path)` | `{text, modifiedMs, size, version}`, up to 64 MiB, `EILSEQ` if not UTF-8 |
92
+ | `readBytes(root, path)` | `{data: Uint8Array, modifiedMs, size, version}`, up to 64 MiB |
93
+ | `writeFile(root, path, data, options?)` | Writes text or bytes, up to 64 MiB; returns the entry with its new `version` |
94
+ | `readRange(root, path, offset, length, {ifVersion}?)` | `{data, offset, size, modifiedMs, version}`: part of a file, at most 16 MiB at a time; `size` is the whole file's |
95
+ | `mkdir(root, path, {recursive?})` | Creates a folder |
96
+ | `rename(root, from, to)` | Renames / moves inside one root |
97
+ | `delete(root, path)` | Deletes a file or an empty folder: `{status: 'deleted', path}` |
98
+ | `copy(fromRoot, fromPath, toRoot, toDir)` | Copies a file or a folder into the folder `toDir` of another root (or the same one); returns the new entry and its `path`. A taken name gets a number (`todo 2.md`) |
99
+ | `revoke(id)` | Gives back a folder the user picked |
100
+
101
+ To bring a file from a shared place or a picked folder into the app's own data, copy it: `await fs.copy('downloads', 'report.txt', 'private', '')`.
102
+
103
+ ### Safe writes
104
+
105
+ Options for `writeFile`:
106
+
107
+ - `create`: `'always'` (default, creates the file if missing), `'new'` (`EEXIST` if it already exists), `'never'` (`ENOENT` if it does not exist).
108
+ - `ifVersion`: skips the write and fails with `ECONFLICT` if the file is no longer the one you read. Every read returns a `version` (an opaque string that tells two states of the file apart, even two writes in one millisecond); pass it when you write, and you will never overwrite someone else's changes. `ifModifiedMs` does the same with the modification time; `ifVersion` is the safer of the two. Neither goes with `create: 'new'`.
109
+ - `mkdirs`: creates the parent folders first. Without it, writing into a folder that does not exist fails with `EINVAL`.
110
+
111
+ A write replacing a file goes to a temporary file in the same folder first, is fsynced, and then replaces the file atomically (keeping its permissions; on Windows its attributes and ACL), so a crash leaves either the old or the new content. When the folder does not let the app create a file, it writes into the original file instead and the returned entry has `inPlace: true`: that write is not crash-safe.
112
+
113
+ ### Large files
114
+
115
+ The whole-file calls stop at 64 MiB. For larger files use the standard handles: `getFile()` gives a `File` that reads on demand (`slice()`, `stream()`), and `createWritable()` writes at any position into a temporary file that `close()` puts in place atomically. `acde:fs` has the same as calls: `readRange()` above, and `openWrite(root, path, {keepExistingData, create, ifVersion, mkdirs}?)` → `{id, size}`, then `writeChunk(id, offset, data)` (at most 16 MiB a call), `truncateWrite(id, size)`, and `commitWrite(id)` (returns the entry with its `version`) or `abortWrite(id)`. With `ifVersion`, a change by someone else between the open and the commit is `ECONFLICT` and the file stays as it was. A write that is left alone for 10 minutes is given up.
116
+
117
+ ### Errors
118
+
119
+ On failure the promise rejects with an `Error` that has a `code` and a `status`:
120
+
121
+ | `code` | `status` | When |
122
+ | --- | --- | --- |
123
+ | `EINVAL` | 400 | Invalid arguments, an absolute path or one that escapes the root, a missing parent folder, wrong type, symbolic link |
124
+ | `EACCES` | 403 | The first argument is not one of the app's roots, the root is read-only, or the system does not allow it |
125
+ | `ENOENT` | 404 | Does not exist |
126
+ | `EEXIST` | 409 | Already exists |
127
+ | `ECONFLICT` | 409 | Changed or deleted by someone else since it was read (`ifVersion`, `ifModifiedMs`) |
128
+ | `ENOTEMPTY` | 409 | Deleting a non-empty folder |
129
+ | `EFBIG` | 413 | Exceeds the size limit |
130
+ | `EILSEQ` | 400 | `readText` read something that is not UTF-8 |
131
+ | `ENOSPC` | 507 | Disk full |
132
+ | `EIO` | 500 | Anything else |
133
+
134
+ ```js
135
+ try {
136
+ await fs.writeFile(root, path, text, { ifModifiedMs })
137
+ } catch (e) {
138
+ if (e.code === 'ECONFLICT') askToReload()
139
+ else throw e
140
+ }
141
+ ```
142
+
143
+ What the common mistakes look like:
144
+
145
+ | Mistake | Rejection |
146
+ | --- | --- |
147
+ | A path as the root: `fs.list('/tmp')` | `EACCES`: `this app may not use the files of /tmp (not one of its roots)` |
148
+ | A place that is not in `fs.roots`: `fs.list('downloads')` with `"fs": true` | `EACCES`: `this app may not use the files of downloads (not one of its roots)` |
149
+ | An absolute path as the second argument: `fs.readText('private', '/etc/hosts')` | `EINVAL`: `path must be relative to the workspace` |
150
+ | `..` out of the root: `fs.readText('private', '../x')` | `EINVAL`: `path escapes the workspace` |
151
+ | Writing into a folder that does not exist, without `mkdirs` | `EINVAL`: `parent directory is missing or outside the workspace` |
152
+ | Writing to a folder picked without `mode: 'readwrite'` | `EACCES`: `<folder> was picked read-only` |
153
+ | `fs.locate(path)` or `fs.openPath(root, path)` (they turn paths into roots and back) | `EACCES`: `fs.locate() works only in system apps that declare shared places` |
154
+
155
+ ## Dropped files
156
+
157
+ When files or folders are dragged from the host system into a window, the page receives the standard drag events, ending in `drop`, and `dataTransfer` carries the files the standard way. The page gets no path.
158
+
159
+ - Each dropped file or folder is an item of `dataTransfer.items` with `kind: 'file'`; `item.getAsFileSystemHandle()` resolves to a **read-only** `FileSystemFileHandle` or `FileSystemDirectoryHandle` (`createWritable()` rejects with `NotAllowedError`).
160
+ - Files up to 64 MiB are also in `dataTransfer.files`. Folders and larger files are not; use their handles.
161
+ - `dataTransfer.types` is `['Files']`; there is no `text/uri-list` or `text/plain` with the paths.
162
+
163
+ To keep a dropped file, copy its content into `private`:
164
+
165
+ ```js
166
+ import { fs } from 'acde:fs'
167
+
168
+ addEventListener('dragover', (event) => event.preventDefault())
169
+ addEventListener('drop', async (event) => {
170
+ event.preventDefault()
171
+ const items = [...event.dataTransfer.items].filter((item) => item.kind === 'file')
172
+ const handles = await Promise.all(items.map((item) => item.getAsFileSystemHandle()))
173
+ for (const handle of handles) {
174
+ if (handle.kind !== 'file') continue
175
+ const file = await handle.getFile()
176
+ await fs.writeFile('private', `inbox/${file.name}`, new Uint8Array(await file.arrayBuffer()), { mkdirs: true })
177
+ }
178
+ })
179
+ ```
180
+
181
+ A handle (from a drop, a picker, or an open request) can also be handed to another app; see [Opening apps](open.md#files-and-links).
@@ -0,0 +1,74 @@
1
+ # Frameworks
2
+
3
+ acde doesn't ask you to change your framework's config. At build time it recognizes the framework from your dependencies, runs **the framework's own build**, and puts the output into the package. The first match wins:
4
+
5
+ | Dependency | Build command | Output |
6
+ | --- | --- | --- |
7
+ | `next` | `next build` (standalone) | `.next/standalone` as the backend; static `out/` with `output: 'export'` |
8
+ | `nuxt` (or `nuxt3`) | `nuxt build` (`node-server` preset) | `.output/server/index.mjs` as the backend, `.output/public` |
9
+ | `@sveltejs/kit` | `vite build` | The server is wrapped as the backend; `client` is the static assets |
10
+ | `astro` | `astro build` | `dist/`; a backend if `dist/server/entry.mjs` exists |
11
+ | `@react-router/dev` / `@remix-run/dev` | `react-router build` / `remix vite:build` | A backend if `build/server/index.js` exists |
12
+ | `@angular/core` + `@angular/cli` | `ng build --configuration production --base-href /` | `dist/**/browser`; a backend if `server/server.mjs` exists |
13
+ | `vite` | Calls the project's Vite `build()` in process, reading its `vite.config` | Static pages |
14
+ | `react-scripts` | `react-scripts build` | `build/` |
15
+ | A `build` script in `package.json` | `bun --bun run build` when acde runs on Bun, otherwise `<pm> run build` | The first of `dist` / `build` / `out` / `www` / `.output/public` that contains HTML |
16
+
17
+ For server-rendered frameworks, pages are generated by the backend, and the manifest's `entry` is `/app/<id>/` (ending in `/`).
18
+
19
+ If there's no `node_modules`, acde runs `<pm> install` first.
20
+
21
+ To start a project with one of these frameworks, `acde new --template` has `next`, `nuxt`, `sveltekit` and `astro`, and the Vite templates `react`, `vue`, `svelte`, `solid`, `preact` and `vanilla`; see [First app](../start/first-app.md#create).
22
+
23
+ ## Saving in acde dev
24
+
25
+ `acde dev` runs the same production build after every save and **reloads the whole page**; it does not use the framework dev server or hot module replacement (HMR). Across the reload, acde restores form values, scroll position, focus and text selection, but **component state is lost**: a React or Preact `useState` counter, Vue `ref`s, Svelte and Solid signals, and any other value held only in memory start over. Keep state you want to survive a reload in `localStorage` or IndexedDB. See [Reloading](dev.md#reloading).
26
+
27
+ After 0.0.9, pages made by `acde new --template react` (and the other Vite templates) say "save to reload the page" where Vite's own templates say "save to test HMR".
28
+
29
+ ## Detection
30
+
31
+ Use `framework` in `acde.config.jsonc`:
32
+
33
+ - `auto` (default): detect using the table above.
34
+ - `next`, `nuxt`, `sveltekit`, `astro`, `react-router`, `remix`, `angular`, `vite`, `cra`, `script`: use this one.
35
+ - `none`: no framework; acde bundles `entry` itself.
36
+
37
+ When `entry` is set, acde doesn't pick up Vite (the built-in templates depend on Vite only so acde can bundle with it). To build with Vite, remove `entry` or set `"framework": "vite"`.
38
+
39
+ ## Without a framework
40
+
41
+ When no framework is recognized (or with `"framework": "none"`), acde bundles from `entry`:
42
+
43
+ - HTML entry: `base` is `/app/<id>/`, `acde:*` imports stay external, `NODE_ENV` is `"production"`, and `import.meta.env` works as in Vite (including `VITE_*` variables).
44
+ - Module entry: bundled into `<name>.js` with its exports kept; the default export is a `WindowApp`.
45
+
46
+ On Bun it uses `Bun.build`; on Node.js it uses the project's Vite (if it's missing, you're told to `add it with <pm> add -D vite`). The rules are the same on both.
47
+
48
+ ## Vite plugin
49
+
50
+ If you `import { fs } from 'acde:fs'` in a Vite project, the dev server (`vite dev`, in a regular browser) doesn't know about `acde:` modules. Add the SDK's plugin:
51
+
52
+ ```js
53
+ // vite.config.js
54
+ import acde from '@dopame/acde-api/vite'
55
+
56
+ export default {
57
+ plugins: [acde()],
58
+ }
59
+ ```
60
+
61
+ At build time, `acde:*` imports stay external. In the dev server they're replaced with a stub: calls reject with `only there when the app runs in acde`, and `on()` returns a no-op unsubscribe function. That way the same code still opens in a browser.
62
+
63
+ acde adds this plugin automatically when it builds a project with Vite.
64
+
65
+ ## acde: modules
66
+
67
+ If the same code needs to run both in a browser and in acde, use `load()`. It returns `null` when the module isn't available:
68
+
69
+ ```js
70
+ import { load } from '@dopame/acde-api'
71
+
72
+ const fs = await load('fs')
73
+ if (fs) console.log(await fs.roots())
74
+ ```
@@ -0,0 +1,34 @@
1
+ # Local install
2
+
3
+ `acde dev` hands your app to the runtime only temporarily. To keep the app in the Dopame desktop on this machine:
4
+
5
+ ```sh
6
+ acde install
7
+ ```
8
+
9
+ This runs `acde build`, then installs the package into `$XDG_DATA_HOME/acde/packages/<id>` (by default `~/.local/share/acde/packages/<id>`, on Windows `%USERPROFILE%\.local\share\acde\packages\<id>`). It copies to a temporary directory first and then swaps the whole directory in, so you never end up with half a package. A handwritten package (no config, with its own `acde` manifest in `package.json`) is installed as a symlink to the project.
10
+
11
+ Once installed, the next session you start (and `acde dev --desktop dopame`) will see it.
12
+
13
+ ## Package lookup
14
+
15
+ In this order; for a given id, the first one found wins:
16
+
17
+ 1. Directories in `ACDE_PATH` (this is how `acde dev` hands over its builds);
18
+ 2. `$XDG_DATA_HOME/acde/packages/*`;
19
+ 3. `$XDG_DATA_DIRS/acde/packages/*` (system-wide installs).
20
+
21
+ ## Uninstall
22
+
23
+ ```sh
24
+ acde uninstall # the installed packages: id and path
25
+ acde uninstall <id> # or the project directory it was installed from
26
+ ```
27
+
28
+ It says what it removes and asks first (`--yes` skips the question, and is needed outside a terminal). A copied package's directory is removed; for a handwritten package only the link is removed, never the project. A package that a running `acde dev` session uses is not removed: stop that session first.
29
+
30
+ The apps' data is kept: under `~/.local/share/acde/` their storage (`storage/app-<id>/`), backend data (`apps/<id>/`) and private files (`files/<id>/`), their folder grants (`~/.local/state/acde/grants/<id>.json`), and their entries in `~/.config/acde/permissions.json` (camera and microphone decisions) and `~/.local/state/acde/windows.json` (window places). After 0.0.9 it is kept unless you ask, and removing it takes all of these (other apps' entries in the shared files stay): in a terminal `acde uninstall` asks whether to remove it too; `--data` removes it and `--keep-data` keeps it without asking. With `--yes` alone it is kept, and the paths are printed. [acde uninstall](../reference/cli.md#acde-uninstall) lists the exit codes.
31
+
32
+ ## Publishing
33
+
34
+ An app store and `acde publish` are still in the works. For now, distributing an app means distributing the built package directory (`.acde/`); put it in any of the locations above.
@@ -0,0 +1,64 @@
1
+ # MCP
2
+
3
+ `acde mcp` is an [MCP](https://modelcontextprotocol.io) server over stdio. It gives AI agents the [debugging tools](tools.md) and more: an agent can start an `acde dev` session itself, look at the screen, click buttons, read the console, and take screenshots, so it can build and verify your app.
4
+
5
+ Agents can do all of this with the plain [debugging commands](tools.md) too, which need no setup; the [acde skill](agents.md) teaches them how. Use MCP when your agent works better with tools than with a shell.
6
+
7
+ ## Setup
8
+
9
+ Claude Code:
10
+
11
+ ```sh
12
+ claude mcp add acde -- acde mcp
13
+ ```
14
+
15
+ Other clients: the command is `acde mcp`, with no arguments (add `--session <pid>` to pin a particular session).
16
+
17
+ ## Tools
18
+
19
+ | Tool | What it does |
20
+ | --- | --- |
21
+ | `doctor` | Checks this machine for `acde dev`, as [`acde doctor`](doctor.md) does; call it first when `start` fails |
22
+ | `doctor_fix` | Checks, fixes what acde may fix without asking, and checks again, as `acde doctor --fix` does. The fixes that download, install or stop something it only names: the user runs `acde doctor --fix` in a terminal (it asks), or agrees to `acde doctor --fix --yes`. Its `dir` must be inside the working directory |
23
+ | `sessions` | Lists running `acde dev` sessions; can pin one |
24
+ | `start` / `stop` / `logs` | Starts its own `acde dev` session (for the project in the working directory by default), stops it, reads its output. After 0.0.9, when an `acde dev` already runs exactly that (the same projects, `desktop`, `app` and `headless`), `start` uses that session and says so (`attached to acde dev <pid>, which already runs …`); `stop` and `logs` do not reach a session started elsewhere. When one runs those projects some other way, `start` fails and names it (`… which is not what start asked for`): pin it with `sessions {use: <pid>}`, or stop it first. `start {force: true}` starts another one anyway |
25
+ | `webs` | Lists pages, and the camera or microphone prompts that wait |
26
+ | `permissions` / `answer` / `revoke` | Lists the camera and microphone prompts that wait and the decisions made, allows or denies a waiting prompt, takes back a decision; see [Camera and microphone](tools.md#camera-and-microphone) |
27
+ | `snapshot` | The screen as text: windows and the elements of the focused window, each with a ref |
28
+ | `act` / `run` | Performs one action on a ref / several in sequence, and reports what changed |
29
+ | `open` | Opens an app and waits for its window |
30
+ | `query` | Finds elements by CSS selector, hidden ones too; the refs work with `act` |
31
+ | `wait_for` | Waits for some text to appear (or disappear) |
32
+ | `screenshot` | Screenshot of one window or the whole screen |
33
+ | `eval` | Runs JS in a page |
34
+ | `console` / `network` | The pages' logs and requests |
35
+ | `emulate` | Switches light/dark mode |
36
+ | `choose_folder` / `choose_files` / `choose_save_file` | Answers the page's next file picker in advance |
37
+ | `route` / `unroute` | Fakes responses to the page's requests, or makes them fail |
38
+ | `pointer` | Moves the pointer to a screen point, as a real move (hover, screen edges) |
39
+ | `clipboard` | Reads and writes the clipboard |
40
+ | `input` | Sends raw mouse and keyboard events to a page, at page coordinates; `wait` (`ms`) holds a key or button in between (after 0.0.9) |
41
+ | `clock` | Takes over a page's time: installs a fake clock, pauses it, moves it forward |
42
+ | `dialog` | Answers the page's next `alert` / `confirm` / `prompt` in advance, and lists the dialogs it showed |
43
+ | `resize` | Resizes a window so that its page has the given size |
44
+
45
+ Which session is used: the pinned one; otherwise the one `start` launched; otherwise one picked by the working directory.
46
+
47
+ Declared capabilities need no answer; only the camera and the microphone ask the user. In 0.0.9 an app that declares one is asked when it starts, and its window waits for the answer; after 0.0.9 the user is asked the first time a page asks for one, and the page waits for the answer. `webs` lists such a prompt as a line, `recorder waits for approval of microphone: the answer tool allows or denies it`. `answer` takes `allow` (true or false). To allow, it must name the `app` and the `capability` that `permissions` lists, so the call states what is granted; to deny, the `prompt` id or the `app` and `capability` will do, and neither is needed when only one prompt waits. It stands in for the user's own answer, so the tool is marked as an action that needs confirmation: allow only what the user asked for or agreed to.
48
+
49
+ Closing the only window of a session that runs a single app (`act` with `close` on `w1`) ends that session, by design. The call succeeds and its answer says so, `closed w1; acdert exited with it, so acde dev 4242 ended`. The next call fails and says that the session stopped (`the acde dev started here (pid 4242) stopped; last output: …` for a session `start` ran, `acde dev 4242 stopped; no acde dev is running (…)` for one started elsewhere). Run `start` again to go on. Any other action during which the runtime goes away fails with `acdert closed the connection`; so does a `run` whose steps close that window, since the answer to its steps is lost, so close it with `act`.
50
+
51
+ `input`, `clock`, `dialog` and `resize` do for an agent what `app.mouse` / `app.keyboard`, `app.clock`, `app.answerDialog` and `app.resize` do in a test; see [Time, dialogs and page setup](testing.md#time-dialogs-and-page-setup). `clock` takes `action` (`install`, `pause_at`, `fast_forward`, `run_for`, `resume`, `set_fixed_time`, `set_system_time`, `state`) with a `time` (an ISO string or milliseconds since 1970) or a `duration` (milliseconds, `"mm:ss"` or `"hh:mm:ss"`). `dialog` without `accept` or `text` only lists.
52
+
53
+ A page's folder, file or save picker can be answered before the page opens it: call `choose_folder` with a `path` (relative paths resolve against the working directory), or with `cancel: true`, then the `act` that opens the picker. Each answer serves the next picker of that kind. In a session started with `start {headless: true}` there is no picker dialog, so a picker that was not answered first fails and the page sees it as cancelled. `choose_files` takes `paths`, `choose_save_file` a `path`.
54
+
55
+ ```json
56
+ { "name": "choose_folder", "arguments": { "path": "fixtures/project" } }
57
+ { "name": "act", "arguments": { "ref": "w1.4", "action": "click" } }
58
+ ```
59
+
60
+ On a machine without a display, call `start` with `headless: true`; `screenshot` and `snapshot` work the same. `eval` with `getComputedStyle` or `getBoundingClientRect` checks styles and layout, as in [Styles and layout](tools.md#styles-and-layout).
61
+
62
+ In a session with windows, `clipboard` touches **your system clipboard**, so the agent must pass `system: true` explicitly, and the tool is marked as an action that needs confirmation. Headless sessions have their own clipboard.
63
+
64
+ When the server's stdin closes or it receives a signal, it stops the `acde dev` session that `start` launched.
@@ -0,0 +1,107 @@
1
+ # Opening apps
2
+
3
+ ## Apps
4
+
5
+ ```jsonc
6
+ { "capabilities": { "open": ["viewer"] } }
7
+ ```
8
+
9
+ ```js
10
+ import { open } from 'acde:open'
11
+
12
+ const { window } = await open('viewer', { kind: 'show-image', image: '/photos/cat.png' })
13
+ ```
14
+
15
+ The second argument is a **request**: a structured-cloneable object with a `kind`, handed to the other app. `open('viewer')` without a request just opens the app (or brings its window to the front). Setting the `open` capability to `true` allows any app; a list allows only the apps listed.
16
+
17
+ The app being opened says which kinds of request it accepts in its config, and receives them through the `request` event:
18
+
19
+ ```jsonc
20
+ { "id": "viewer", "requests": ["show-image"] }
21
+ ```
22
+
23
+ ```js
24
+ import { window as self } from 'acde:window'
25
+
26
+ self.on('request', (request) => {
27
+ if (request.kind === 'show-image') showImage(request.image)
28
+ })
29
+ ```
30
+
31
+ `requests` lists the app's own request kinds (lowercase words such as `show-image`). The runtime checks only the `kind`; the receiving app checks the rest of the object. An app with no `requests` accepts none.
32
+
33
+ For a window opened for a request, the request is delivered once, to the first `request` handler only; a request that arrives before any handler is registered is held and delivered when one is. An app that is not `multiple` and already runs gets the request in its existing window.
34
+
35
+ `open(app, request, { parent, modal })` makes the new window a child of one of your own windows (`parent` is a window id, such as `self.id`), centered over it; `modal: true` keeps input away from the parent until the child closes.
36
+
37
+ What goes wrong:
38
+
39
+ | Call | Rejection (`status`, `code`) |
40
+ | --- | --- |
41
+ | `open('mail')` when `open` does not allow `mail` | 403 `forbidden`: `Notes may not use open mail (not in its manifest's capabilities)` |
42
+ | A request without `kind` | 400 `untyped_request`: `a request to viewer needs a kind` |
43
+ | A `kind` that is not in the target's `requests` | 409 `not_accepted`: `viewer does not accept other-kind requests (its manifest's requests)` |
44
+
45
+ To list the installed apps, use `acde:apps`; its `open` is the same function:
46
+
47
+ ```js
48
+ import { apps } from 'acde:apps'
49
+
50
+ const installed = await apps.list() // [{ id, label, icon, script }]
51
+ ```
52
+
53
+ ## Files and links
54
+
55
+ An app declares the file types and the link schemes it can open in its config:
56
+
57
+ ```jsonc
58
+ {
59
+ "id": "notes",
60
+ "mimeTypes": ["text/markdown", "text/plain"],
61
+ "schemes": ["mailto"]
62
+ }
63
+ ```
64
+
65
+ When the user double-clicks a `.md` file in the file manager, or another app asks for it to be opened, the system picks an app in this order:
66
+
67
+ 1. The default app the user has set (XDG MIME Applications spec, `mimeapps.list`; on Windows it is remembered per file extension);
68
+ 2. The first app whose `mimeTypes` lists the type (an exact type wins over `text/*`);
69
+ 3. A native program (Dopame OS / Linux), or the system's default program (the host window on macOS and Windows).
70
+
71
+ The chosen app receives a **system request**. A file arrives as `{ kind: 'open-file', handle }`, where `handle` is a standard `FileSystemFileHandle` (or a directory handle); a link arrives as `{ kind: 'open-link', url }`. The app gets no path:
72
+
73
+ ```js
74
+ self.on('request', async (request) => {
75
+ if (request.kind === 'open-file') showText(await (await request.handle.getFile()).text())
76
+ if (request.kind === 'open-link') compose(request.url)
77
+ })
78
+ ```
79
+
80
+ Apps that request `openFiles` can hand a file or a link to its default app themselves. Leave out the app id and the system picks:
81
+
82
+ ```js
83
+ import { open } from 'acde:open'
84
+
85
+ await open({ kind: 'open-file', handle }) // a handle from a picker, a drop, or a request
86
+ await open({ kind: 'open-link', url: 'mailto:someone@example.com' })
87
+ ```
88
+
89
+ The same order picks the app, so an app that lists the file's type in `mimeTypes` gets its own file back as a request (unless the user set another default app); there is no way to ask for the system's program instead. On macOS and Windows the system's default program takes only regular files of documents and media: `pdf`, images (`png`, `jpg`, `jpeg`, `gif`, `webp`, `bmp`, `tif`, `tiff`, `heic`, `heif`, `avif`, `ico`), audio and video (`mp3`, `wav`, `ogg`, `oga`, `flac`, `m4a`, `aac`, `opus`, `wma`, `mp4`, `m4v`, `webm`, `mov`, `mkv`, `avi`, `wmv`), `txt`, `md`, `csv`, `log`, `json`, and office documents (`docx`, `xlsx`, `pptx`, `odt`, `ods`, `odp`). A folder, an `.html` page or any other file is refused, and the promise rejects with a message such as `no app can open this kind of file (text/html): /…/index.html is not handed to the system: only documents and media open in system apps, not .html files`. `http` and `https` links open in the default browser.
90
+
91
+ With an app id (`open('viewer', { kind: 'open-file', handle })`) the request goes to that app; this needs `open` rather than `openFiles`, and is rejected with 409 `not_accepted` when the target does not declare the file's type in `mimeTypes` or the link's scheme in `schemes`. A read-only handle (from a drop) stays read-only for the receiver.
92
+
93
+ `acde:openPath` and `acde:handlers` (also `openFiles`) work with a path or a URI instead of a handle:
94
+
95
+ ```js
96
+ import { openPath } from 'acde:openPath'
97
+ import { handlers } from 'acde:handlers'
98
+
99
+ await openPath('/home/me/Notes/todo.md')
100
+ const { mime, association, handlers: list } = await handlers('/home/me/Notes/todo.md')
101
+ ```
102
+
103
+ They suit the desktop and system apps, which know paths. For an ordinary app the file must be inside one of its `acde:fs` roots, or the call is rejected with 404 `outside_roots`; an app that holds handles uses `open({ kind: 'open-file', handle })`.
104
+
105
+ Changing the default app for a type is `setDefault(association, app)` from `acde:setDefault`, a shell module for the desktop and system apps (`association` is the value `handlers()` returned).
106
+
107
+ File types are detected with shared-mime-info, so a dev session on macOS gets the same results as Dopame OS (for example, `.md` is `text/markdown`). On macOS this needs `brew install shared-mime-info`; without it, a one-line warning is printed. On Windows the types are the ones the system has registered for each file extension, and a name it knows no type for has the type `''`.