opentray 0.8.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Developer-facing OpenTray package.
4
4
 
5
- This is the package users install directly:
5
+ Install it directly in the application or service that owns the tray lifetime:
6
6
 
7
7
  ```bash
8
8
  pnpm add opentray
@@ -10,244 +10,96 @@ pnpm add opentray
10
10
 
11
11
  ## Role
12
12
 
13
- - Resolve or auto-start the OpenTray broker.
14
- - Expose `createSpace()`, default space resolution, and `createTray()`.
15
- - Route extension packages through public OpenTray contracts.
16
- - Resolve per-platform optional binary packages.
13
+ - Expose `createTray()` as the public creation entrypoint.
14
+ - Bind tray handles to the current runtime host context.
15
+ - Route official extension packages through public OpenTray contracts.
16
+ - Resolve platform runtime artifacts without exposing `Space`, `Surface`, or a public broker object.
17
17
 
18
18
  `packages/cli` is the only unscoped npm package in this monorepo.
19
19
 
20
- ## Caller identity and per-caller isolation
21
-
22
- Each host application gets its own broker process. OpenTray does not aggregate
23
- multiple callers onto one shared tray; a broker is pinned to exactly one caller
24
- session so its process is identifiable in the task manager and killing it only
25
- affects that one application.
26
-
27
- The SDK derives a caller label with this precedence:
28
-
29
- 1. An explicit label: `connectLocalBroker({ callerLabel: "myapp" })`.
30
- 2. `npm_package_name` when present.
31
- 3. The basename of the running script.
32
- 4. The neutral fallback `opentray` when nothing usable is available.
33
-
34
- The label is sanitized to a filesystem- and process-safe component and scopes
35
- the broker endpoint, runtime directory, ready file, and the process name, so two
36
- host applications using the same OpenTray version resolve distinct brokers and
37
- endpoints. A broker rejects a second incoming connection with a typed
38
- `OPENTRAY_BROKER_SINGLE_SESSION` error rather than silently sharing state.
39
-
40
-
41
- ## Example
42
-
43
- Run a protocol-only example that creates a space, creates a tray, dispatches an extension command, and prints each client frame:
44
-
45
- ```bash
46
- pnpm --filter opentray example:basic
47
- ```
48
-
49
- The top-level SDK now exposes the mainline broker-backed path directly:
20
+ ## Tray-First API
50
21
 
51
22
  ```ts
52
- import { createSpace, createTray, resolveDefaultSpace } from "opentray";
53
-
54
- const space = await createSpace({ id: "com.example.status", default: true });
55
- const tray = await space.createTray({
56
- trayId: "status",
57
- title: "Status",
58
- icon: { type: "file", path: "./assets/tray-icon.png" },
59
- menu: { items: [{ type: "item", id: 1, title: "Open", primaryEvent: true }] },
23
+ import { createTray } from "opentray";
24
+
25
+ const tray = await createTray({
26
+ id: "com.example.status",
27
+ icon: {
28
+ type: "file",
29
+ path: "./assets/tray-icon.png",
30
+ text: "Status",
31
+ "text-only": "Status",
32
+ },
33
+ tooltip: {
34
+ title: "Status",
35
+ description: "Background service is running",
36
+ },
37
+ menu: {
38
+ items: [{ type: "item", id: 1, title: "Open", primaryEvent: true }],
39
+ },
60
40
  });
61
41
 
62
- await tray.setTitle("Status: ready");
63
42
  tray.onMenuClick(({ itemId }) => {
64
43
  if (itemId === 1) {
65
- // Open a native menu action or a WebView surface.
44
+ // Open an app-owned window, command, or extension surface.
66
45
  }
67
46
  });
68
-
69
- const defaultSpace = await resolveDefaultSpace();
70
- await createTray(
71
- {
72
- trayId: "secondary",
73
- title: "Secondary",
74
- icon: { type: "file", path: "./assets/tray-icon.png" },
75
- },
76
- { space: defaultSpace.space }
77
- );
78
47
  ```
79
48
 
80
- `createTray()` resolves the broker default space when no explicit target is provided. If you already know the target, pass `space: defaultSpace.space` in the second argument instead of relying on default-space lookup.
49
+ Visible tray text belongs to `icon.text`, `icon["text-only"]`, or `icon["icon-text"].text`. The v0.9 API does not export `createSpace()`, `createSurface()`, `resolveDefaultSpace()`, or `TrayHandle.setTitle()`.
81
50
 
82
- Run the human-visible daemon tray example:
51
+ ## Runtime Ownership
83
52
 
84
- ```bash
85
- pnpm --filter opentray example:daemon-tray
86
- ```
53
+ OpenTray does not ask developers to create a public broker object. The application process or an application-owned background service imports `opentray`, calls `createTray()`, owns event handlers, and releases the tray when that process exits.
87
54
 
88
- Run the direct webview control demo:
55
+ Node platform packages carry the host-loadable runtime artifact at `runtime/opentray_runtime.node`. `opentray/node` exposes `runVisibleRuntimeHost()`, `loadOpenTrayRuntimeBinding()`, and `resolveInstalledRuntimeBindingPath()` for Node-specific host-loop ownership, runtime diagnostics, and packaging checks.
89
56
 
90
- ```bash
91
- pnpm --filter opentray example:webview-control
92
- pnpm --filter opentray example:webview-control -- --overlay
93
- pnpm --filter opentray example:webview-control -- --no-overlay
94
- ```
57
+ By default, `createTray()` routes through the visible Node runtime binding. The host main thread must run `runVisibleRuntimeHost()` while app logic runs in a worker or another app-owned execution source. This is a platform law, not a convenience wrapper: on macOS native tray creation must happen on the application main thread, and all native menu/tray events route back only to the live session that owns the tray.
95
58
 
96
- That demo opens a native WebView window immediately and puts the controls inside the page itself, so you can exercise frameless, background modes, keep-on-top, title, icon, screen, overlay, and navigation behavior without going through the tray menu first. The control demo enables the show-time `windowControlsOverlay` capability by default; use `-- --no-overlay` when you want to test the disabled path.
97
- Treat `example:webview-control` as the API exercise surface. It is useful for probing capabilities and events, but it is not the canonical recipe for a tray-anchored glass shell.
59
+ For protocol/session diagnostics, `createTray(options, { runtime: "headless-binding" })` routes through the Node binding without visible native state. Source-tree diagnostics may use `{ runtime: "local-broker" }`, but that transport is not the default package runtime.
98
60
 
99
- Application code mounts WebView as a tray capability. The first WebView command loads the native extension automatically; ordinary code does not need to send `load-ext` manually:
61
+ Runtime options may also carry app identity facts:
100
62
 
101
63
  ```ts
102
- import { WebviewExt } from "@opentray/ext-webview";
103
- import { createTray } from "opentray";
104
-
105
- const tray = (await createTray({
106
- trayId: "status",
107
- title: "Status",
108
- icon: { type: "file", path: "./tray-icon.png" },
109
- })).extend(WebviewExt);
110
-
111
- const panel = tray.createWebviewWindow({
112
- html: "<main>Status</main>",
113
- width: 360,
114
- height: 240,
64
+ await createTray(options, {
65
+ appId: "com.example.status",
66
+ appName: "Status",
115
67
  });
116
-
117
- await panel.show();
118
- ```
119
-
120
- For tray-anchored or screen-aware surfaces, use `WebviewPlacementKit` from `@opentray/ext-webview` instead of hand-authoring raw broker frames or creating a one-off panel abstraction.
121
-
122
- Run the placement demo when you want to review tray, screen, and edge-aware placement:
123
-
124
- ```bash
125
- cargo build -p opentray-bin -p opentray-ext-webview
126
- pnpm --filter opentray example:placement
127
68
  ```
128
69
 
129
- This demo follows the `skills/opentray` placement law: extension-owned WebView mounting, continuous `WebviewPlacementKit.watch()`, `applyOnce()`, tray/screen/edge anchors, and a page-owned frameless drag region.
70
+ The runtime host reports those facts as `appId` and `appName` in `runtime-host-health`; tray icon text, menu labels, and tooltip text remain projection data.
130
71
 
131
- Run the media query demo when you want to review responsive native window style and constraints:
132
-
133
- ```bash
134
- cargo build -p opentray-bin -p opentray-ext-webview
135
- pnpm --filter opentray example:mediaQuery
136
- ```
72
+ The `opentray/node` subpath exposes binding-resolution, visible host-loop, and binding-transport helpers. Source-tree diagnostics may still use the internal local broker transport, but that transport is not exported as a package contract.
137
73
 
138
- This demo focuses on `styleKit.apply(...)` and `mediaQueryKit.match(...)`. Keep it separate from placement debugging so size/style callbacks do not obscure placement behavior.
74
+ ## Examples
139
75
 
140
- Run the dedicated tray-panel demo:
76
+ Run a protocol-only example that creates a tray, dispatches an extension command, and prints each client frame:
141
77
 
142
78
  ```bash
143
- cargo build -p opentray-bin -p opentray-ext-webview
144
- pnpm --filter opentray example:tray-panel
145
- ```
146
-
147
- This demo is the canonical custom TrayPanel case: one `primaryEvent` tray item, backend `tray.getBounds()`, page `navigator.opentray.tray.getBounds()`, screen-aware repositioning, and a frameless glass panel with `keepOnTop`.
148
- It also follows the native-glass rule strictly: material background through `style.background`, no root HTML shell styling, and content padding only inside the page.
149
- On macOS it requests `{ kind: "platformMaterial", material: "hudWindow", state: "active" }` so the tray-launched material surface does not immediately fall back to the inactive grey AppKit appearance. On Windows it uses `background: "mica"` and `cornerPreference: "round"` instead of sending macOS-only style.
150
- For a step-by-step walkthrough of the examples and expected behavior, read [examples/EXAMPLE.md](./examples/EXAMPLE.md).
151
-
152
- ## Release Channels And Maturity
153
-
154
- OpenTray uses release channels and capability maturity together. Do not read a published platform package as proof that every visible runtime path is already stable.
155
-
156
- - `latest`: the stable package line
157
- - `alpha`: the prerelease/testing package line, installed as `npm i opentray@alpha`
158
-
159
- Current WebView truth:
160
-
161
- - macOS is the current `stable` human-visible acceptance path
162
- - Windows is the current `stable` WebView2-backed human-visible acceptance path for common bridge/window controls, background material/corner preferences, and native icon projection
163
- - Linux remains supported by the OpenTray core daemon/packages, but `@opentray/ext-webview` does not publish Linux native WebView packages
164
- - some requests are `unsupported by design`, such as asking the macOS runtime to apply a Windows-only style family
165
- - some results are `unavailable by context`, such as tray-bounds projection when the current session has no authoritative tray anchor
166
-
167
- When run from the repo worktree, the example automatically discovers `target/debug` or `target/release` `libopentray_ext_webview` and wires it through `OPENTRAY_EXT_PATH` before starting the daemon. The WebView mount still exercises the real generic extension loader, but example users do not author a manual `load-ext` request.
168
-
169
- The published CLI is intentionally small and only owns daemon lifecycle and health:
170
-
171
- ```bash
172
- opentray daemon health
173
- opentray daemon start
174
- opentray daemon stop
175
- opentray daemon restart
176
- ```
177
-
178
- Visual smoke is a real runtime activity, not a daemon CLI subcommand. It may auto-start or reuse the same-version daemon, write runtime coordination files under `$OPENTRAY_HOME/.opentray/<package-version>/runtime` or the user's home directory when `OPENTRAY_HOME` is unset, create a visible tray item, load native extension packages, and open real WebView/Lynx windows. Use the `opentray` skill or source-tree examples to orchestrate those checks, and run `opentray daemon stop` if you want immediate daemon cleanup or if a smoke process was interrupted.
179
-
180
- This example starts or reuses the same-version daemon automatically, creates a real tray through the public SDK, and prints broker-routed menu events. Use manual lifecycle commands only for operator/debug control:
181
-
182
- ```bash
183
- pnpm --filter opentray cli -- daemon start
184
- pnpm --filter opentray cli -- daemon stop
185
- pnpm --filter opentray cli -- daemon restart
186
- ```
187
-
188
- The menu intentionally declares only one plain item: `Open WebView` with `primaryEvent: true`. On macOS, that single primary item lets clicking the status item direct-trigger the normal `menuClick` event without opening a menu, so the example can behave like a one-action launcher for a WebView-built surface.
189
-
190
- On platforms that expose a direct tray activation gesture, the same item opens the WebView immediately while still remaining a normal native menu item on platforms or gestures that show a menu.
191
-
192
- The host side of that example can call `await tray.getBounds()` to read the current tray geometry before opening the window. The rendered page can also opt into `navigator.opentray.tray.getBounds()` when it needs the same tray anchor for layout.
193
-
194
- The opened WebView also enables the injected page bridge so the rendered page can call `navigator.window` / `navigator.opentrayWindow` and, for the demo only, opt into `window.close()` / `window.moveTo()` / `window.resizeTo()` overrides. Use the in-page buttons to verify `getCapabilities`, `getStyle`, `setStyle({ frameless })`, move, resize, close, and tray-bounds behavior visually instead of relying only on terminal logs.
195
-
196
- The Lynx example path uses the same generic extension loader but launches a real `OpenTrayLynxRuntime.app.zip` sidecar from `@opentray/ext-lynx-darwin-*`. It starts the requested `.lynx.bundle` in a fixed host shell, sets an initial title and icon, applies an explicit startup feature expression, and exposes `Show Window`, `Hide Window`, and `Quit Smoke` through tray-routed events. Inside the Lynx window, use the rendered controls to verify `getCapabilities`, `getStyle`, `getTitle`, `setTitle`, `getIcon`, `setIcon`, `navigator.screen.getScreenDetails()`, `resizeTo`, `moveTo`, frameless toggling, `window.resizeTo()`, `window.getScreenDetails()`, and close behavior visually. On macOS, the runtime Dock icon should no longer appear blank.
197
-
198
- For Lynx, `frameless` currently means borderless only. It does not imply a full-window drag region, because that would steal clicks from the page content. When isolating host-feature regressions, `--features "*,!frameless"` is the fastest way to keep the bridge on while removing the borderless shell from the test.
199
-
200
- When you need to validate a GitHub-built macOS artifact before publish, use the workspace launcher instead of hand-written `/tmp` scripts:
201
-
202
- ```bash
203
- pnpm run smoke:lynx -- --run <github-actions-run-id> --bundle packages/cli/assets/lynx-review/main.lynx.bundle
204
- pnpm run smoke:lynx -- --run <github-actions-run-id> --bundle packages/cli/assets/lynx-review/main.lynx.bundle --features "nativeWindowApi,bindWindowGlobals,nativeScreenApi,bindScreenGlobals"
205
- pnpm run smoke:lynx -- --run <github-actions-run-id> --bundle packages/cli/assets/lynx-review/main.lynx.bundle --features "*,!nativeScreenApi"
206
- ```
207
-
208
- The workspace review bundle is the full human acceptance surface. The separate `input-probe.lynx.bundle` is only a low-level diagnostic asset for isolating raw click/scroll/input delivery. The empty feature set is the baseline carrier check. Use it to validate the physical window baseline: click, scroll, input, red/yellow/green controls, and resize/move. Only after that baseline is healthy should you validate explicit startup feature sets on top of the same carrier.
209
-
210
- First-stage core daemon platform packages are published for macOS, Linux, and Windows. macOS and Windows are stable WebView human-visual acceptance paths for common window and bridge commands plus native material projection. Linux is not an official `@opentray/ext-webview` runtime target. Lynx is intentionally macOS-first for now and should fail honestly on other platforms instead of pretending the runtime exists.
211
-
212
- If you are validating the current prerelease branch before stable publication, install from the alpha channel and treat that as alpha evidence rather than stable evidence:
213
-
214
- ```bash
215
- npm i opentray@alpha
79
+ pnpm --filter opentray example:basic
216
80
  ```
217
81
 
218
- For local native smoke before npm publish, stage the current platform artifacts first and run source-tree examples:
82
+ Run the finite source-tree smoke matrix instead of relying on shell expansion for `example:*`:
219
83
 
220
84
  ```bash
221
- cargo build -p opentray-bin -p opentray-ext-webview -p opentray-ext-lynx
222
- bun run scripts/binaries/stage-local.ts --kind daemon --source target/debug/opentray
223
- bun run scripts/binaries/stage-local.ts --kind webview --source target/debug/libopentray_ext_webview.dylib
224
- bun run scripts/binaries/stage-local.ts --kind lynx --source target/debug/libopentray_ext_lynx.dylib
225
- bash scripts/release/build-lynx-runtime.sh /tmp/OpenTrayLynxRuntime.app.zip
226
- bun run scripts/binaries/stage-local.ts --kind lynx-runtime --source /tmp/OpenTrayLynxRuntime.app.zip
227
- OPENTRAY_EXAMPLE_WEBVIEW_SMOKE=1 pnpm --filter opentray example:daemon-tray
228
- pnpm --filter opentray example:daemon-lynx -- --bundle packages/cli/assets/lynx-review/main.lynx.bundle
85
+ pnpm --filter opentray example:matrix
86
+ pnpm --filter opentray example:matrix -- --row visible-binding
229
87
  ```
230
88
 
231
- During source-level daemon work, restart the daemon with the freshly built broker binary before testing tray behavior. Otherwise the CLI may reuse the already staged same-version daemon:
89
+ Run human-visible tray and extension examples from a source checkout:
232
90
 
233
91
  ```bash
234
- cargo build -p opentray-bin
235
- OPENTRAY_BROKER_BIN="$PWD/target/debug/opentray" pnpm --filter opentray cli -- daemon restart
236
- OPENTRAY_BROKER_BIN="$PWD/target/debug/opentray" OPENTRAY_EXAMPLE_WEBVIEW_SMOKE=1 pnpm --filter opentray example:daemon-tray
237
- ```
238
-
239
- The daemon exits automatically after 30 seconds with no connected clients. Set `OPENTRAY_DAEMON_IDLE_TIMEOUT_MS=0` to keep it alive during debugging, or provide another millisecond value for a custom idle release window.
240
-
241
- `OPENTRAY_HOME` should point at a home root, not the `.opentray` state directory itself. OpenTray always stores versioned state under `"$OPENTRAY_HOME/.opentray/<package-version>"`.
242
-
243
- To confirm the native runtime split on macOS after a release build:
92
+ cargo build -p opentray-runtime-node
93
+ pnpm --filter opentray build
94
+ bun run scripts/binaries/stage-local.ts --kind runtime --source target/debug/libopentray_runtime_node.dylib
95
+ OPENTRAY_EXAMPLE_EXIT_AFTER_MS=1500 pnpm --filter opentray example:visible-binding
244
96
 
245
- ```bash
246
- cargo build -p opentray-bin -p opentray-ext-webview -p opentray-ext-lynx --release
247
- wc -c target/release/opentray target/release/libopentray_ext_webview.dylib target/release/libopentray_ext_lynx.dylib
248
- otool -L target/release/opentray
249
- otool -L target/release/libopentray_ext_webview.dylib
250
- otool -L target/release/libopentray_ext_lynx.dylib
97
+ pnpm --filter opentray example:debug-runtime-tray
98
+ pnpm --filter opentray example:webview-control
99
+ pnpm --filter opentray example:tray-panel
100
+ pnpm --filter opentray example:placement
101
+ pnpm --filter opentray example:mediaQuery
102
+ pnpm --filter opentray example:debug-runtime-lynx -- --bundle packages/cli/assets/lynx-review/main.lynx.bundle
251
103
  ```
252
104
 
253
- The native `tray-icon` backend now normalizes `encoded` and `file` icon inputs into RGBA before native tray materialization. `rgba` still works when you already have pixel data, but ordinary app code can point at a PNG file instead of hand-building pixels.
105
+ The example matrix stages the generated Node runtime artifact before `visible-binding`, skips unsupported or missing native extension carrier artifacts with an explicit reason, and labels contributor-only extension rows as `extension-debug-runtime` coverage. The visible-binding example exercises the default package runtime on macOS and Windows. The debug-runtime examples exercise the contributor-only source-tree transport for extension and panel iteration. The public API they demonstrate is tray-first: application code creates trays directly and treats background/service lifecycle as application-owned.
package/dist/cli.d.mts CHANGED
@@ -1,16 +1,14 @@
1
- import { DaemonHealth } from "@opentray/spec";
2
-
3
1
  //#region src/cli.d.ts
4
2
  type CliCommand = {
5
- type: "daemon";
6
- action: "start" | "stop" | "restart" | "health";
7
- } | {
8
3
  type: "help";
4
+ exitCode: 0;
5
+ } | {
6
+ type: "unsupported";
7
+ command: string | undefined;
9
8
  };
10
9
  declare const parseCliCommand: (argv: string[]) => CliCommand;
11
10
  declare const runCli: (argv: string[]) => Promise<number>;
12
- declare const formatDaemonHealthOutput: (health: DaemonHealth) => string;
13
11
  declare const isCliEntrypoint: (argvEntryPath: string | undefined, modulePath: string) => boolean;
14
12
  //#endregion
15
- export { formatDaemonHealthOutput, isCliEntrypoint, parseCliCommand, runCli };
13
+ export { isCliEntrypoint, parseCliCommand, runCli };
16
14
  //# sourceMappingURL=cli.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.mts","names":[],"sources":["../src/cli.ts"],"mappings":";;;KAqBK,UAAA;EACC,IAAA;EAAgB,MAAA;AAAA;EAChB,IAAA;AAAA;AAAA,cAEO,eAAA,GAAmB,IAAA,eAAiB,UAehD;AAAA,cAEY,MAAA,GAAgB,IAAA,eAAiB,OAAO;AAAA,cAiFxC,wBAAA,GAA4B,MAAoB,EAAZ,YAAY;AAAA,cAoBhD,eAAA,GACX,aAAA,sBACA,UAAkB"}
1
+ {"version":3,"file":"cli.d.mts","names":[],"sources":["../src/cli.ts"],"mappings":";KAMK,UAAA;EACC,IAAA;EAAc,QAAA;AAAA;EACd,IAAA;EAAqB,OAAA;AAAA;AAAA,cAEd,eAAA,GAAmB,IAAA,eAAiB,UAMhD;AAAA,cAEY,MAAA,GAAgB,IAAA,eAAiB,OAAO;AAAA,cAuBxC,eAAA,GACX,aAAA,sBACA,UAAkB"}
package/dist/cli.mjs CHANGED
@@ -1,102 +1,35 @@
1
1
  #!/usr/bin/env node
2
- import { a as inspectDaemon, c as stopDaemon, i as createNodeDaemonDriver, n as resolveDaemonPaths, o as restartDaemon, r as readPackageVersion, s as startDaemon, t as connectLocalBroker } from "./local-broker-BMxjb-eO.mjs";
3
- import { homedir } from "node:os";
4
2
  import { fileURLToPath } from "node:url";
5
3
  import { realpathSync } from "node:fs";
6
4
  //#region src/cli.ts
7
- const packageJsonUrl = new URL("../package.json", import.meta.url);
8
5
  const cliModulePath = fileURLToPath(import.meta.url);
9
6
  const parseCliCommand = (argv) => {
10
- const [group, action] = argv;
11
- if (group !== "daemon") return { type: "help" };
12
- if (action === "start" || action === "stop" || action === "restart" || action === "health") return {
13
- type: "daemon",
14
- action
7
+ const [command] = argv;
8
+ if (command === void 0 || command === "--help" || command === "-h") return {
9
+ type: "help",
10
+ exitCode: 0
11
+ };
12
+ return {
13
+ type: "unsupported",
14
+ command
15
15
  };
16
- return { type: "help" };
17
16
  };
18
17
  const runCli = async (argv) => {
19
18
  const command = parseCliCommand(argv);
20
- const packageVersion = process.env.OPENTRAY_DAEMON_PACKAGE_VERSION ?? await readPackageVersion(packageJsonUrl);
21
- const paths = resolveDaemonPaths({
22
- homeDir: process.env.OPENTRAY_HOME ?? homedir(),
23
- packageVersion
24
- });
25
- if (command.type === "help") {
26
- printHelp();
27
- return 1;
28
- }
29
- const driver = createNodeDaemonDriver(cliModulePath);
30
- if (command.action === "start") {
31
- const result = await startDaemon({
32
- paths,
33
- driver
34
- });
35
- console.log(`opentray daemon ${result.status}: pid=${result.pid} endpoint=${result.paths.endpoint}`);
36
- return 0;
37
- }
38
- if (command.action === "stop") {
39
- const result = await stopDaemon({
40
- paths,
41
- driver
42
- });
43
- console.log(result.status === "stopped" ? `opentray daemon stopped: pid=${result.pid}` : "opentray daemon not running");
44
- return 0;
45
- }
46
- if (command.action === "restart") {
47
- const result = await restartDaemon({
48
- paths,
49
- driver
50
- });
51
- console.log(`opentray daemon ${result.status}: pid=${result.pid} endpoint=${result.paths.endpoint}`);
52
- return 0;
53
- }
54
- if (command.action === "health") {
55
- if ((await inspectDaemon({
56
- paths,
57
- driver
58
- })).status === "not-running") {
59
- console.log("opentray daemon not running");
60
- return 0;
61
- }
62
- const connection = await connectLocalBroker({
63
- autoStart: false,
64
- endpoint: paths.endpoint,
65
- homeDir: paths.homeDir,
66
- packageVersion
67
- });
68
- try {
69
- const response = await connection.request({
70
- type: "health",
71
- requestId: "opentray-daemon-health"
72
- });
73
- if (response.type !== "daemon-health") throw new Error(`expected daemon-health response, received ${response.type}`);
74
- console.log(formatDaemonHealthOutput(response.health));
75
- } finally {
76
- await connection.close();
77
- }
78
- return 0;
79
- }
80
- printHelp();
81
- return 1;
19
+ printHelp(command.type === "unsupported" ? command.command : void 0);
20
+ return command.type === "help" ? command.exitCode : 1;
82
21
  };
83
- const printHelp = () => {
84
- console.error("Usage: opentray daemon <start|stop|restart|health>");
85
- };
86
- const formatDaemonHealthOutput = (health) => {
87
- const lines = [
88
- "opentray daemon running",
89
- `pid: ${health.pid}`,
90
- `endpoint: ${health.endpoint}`,
91
- `packageVersion: ${health.packageVersion}`,
92
- `protocolVersion: ${health.protocolVersion}`,
93
- `sessions: ${health.sessionCount}`
94
- ];
95
- for (const session of health.sessions) {
96
- const internalLease = session.internalLeaseId ?? "(pending)";
97
- lines.push(`- sessionId=${session.sessionId} initialized=${session.initialized} internalLeaseId=${internalLease}`);
98
- }
99
- return lines.join("\n");
22
+ const printHelp = (unsupportedCommand) => {
23
+ if (unsupportedCommand !== void 0) console.error(`Unsupported opentray command: ${unsupportedCommand}`);
24
+ console.error([
25
+ "OpenTray v0.9 does not expose daemon lifecycle commands.",
26
+ "Create trays from an app-owned process with:",
27
+ "",
28
+ " import { createTray } from \"opentray\";",
29
+ "",
30
+ "Node runtime diagnostics live under the opentray/node binding helpers.",
31
+ "Source-tree visual diagnostics live in packages/cli/examples."
32
+ ].join("\n"));
100
33
  };
101
34
  const isCliEntrypoint = (argvEntryPath, modulePath) => {
102
35
  if (argvEntryPath === void 0) return false;
@@ -110,6 +43,6 @@ if (isCliEntrypoint(process.argv[1], cliModulePath)) runCli(process.argv.slice(2
110
43
  process.exitCode = code;
111
44
  });
112
45
  //#endregion
113
- export { formatDaemonHealthOutput, isCliEntrypoint, parseCliCommand, runCli };
46
+ export { isCliEntrypoint, parseCliCommand, runCli };
114
47
 
115
48
  //# sourceMappingURL=cli.mjs.map
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { realpathSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { fileURLToPath } from \"node:url\";\n\nimport type { DaemonHealth } from \"@opentray/spec\";\n\nimport {\n createNodeDaemonDriver,\n inspectDaemon,\n restartDaemon,\n startDaemon,\n stopDaemon,\n} from \"./daemon/lifecycle\";\nimport { readPackageVersion } from \"./daemon/package-version\";\nimport { resolveDaemonPaths } from \"./daemon/paths\";\nimport { connectLocalBroker } from \"./local-broker\";\n\nconst packageJsonUrl = new URL(\"../package.json\", import.meta.url);\nconst cliModulePath = fileURLToPath(import.meta.url);\n\ntype CliCommand =\n | { type: \"daemon\"; action: \"start\" | \"stop\" | \"restart\" | \"health\" }\n | { type: \"help\" };\n\nexport const parseCliCommand = (argv: string[]): CliCommand => {\n const [group, action] = argv;\n if (group !== \"daemon\") {\n return { type: \"help\" };\n }\n if (\n action === \"start\" ||\n action === \"stop\" ||\n action === \"restart\" ||\n action === \"health\"\n ) {\n return { type: \"daemon\", action };\n }\n\n return { type: \"help\" };\n};\n\nexport const runCli = async (argv: string[]): Promise<number> => {\n const command = parseCliCommand(argv);\n const packageVersion =\n process.env.OPENTRAY_DAEMON_PACKAGE_VERSION ??\n (await readPackageVersion(packageJsonUrl));\n const paths = resolveDaemonPaths({\n homeDir: process.env.OPENTRAY_HOME ?? homedir(),\n packageVersion,\n });\n\n if (command.type === \"help\") {\n printHelp();\n return 1;\n }\n\n const driver = createNodeDaemonDriver(cliModulePath);\n\n if (command.action === \"start\") {\n const result = await startDaemon({ paths, driver });\n console.log(\n `opentray daemon ${result.status}: pid=${result.pid} endpoint=${result.paths.endpoint}`\n );\n return 0;\n }\n\n if (command.action === \"stop\") {\n const result = await stopDaemon({ paths, driver });\n console.log(\n result.status === \"stopped\"\n ? `opentray daemon stopped: pid=${result.pid}`\n : \"opentray daemon not running\"\n );\n return 0;\n }\n\n if (command.action === \"restart\") {\n const result = await restartDaemon({ paths, driver });\n console.log(\n `opentray daemon ${result.status}: pid=${result.pid} endpoint=${result.paths.endpoint}`\n );\n return 0;\n }\n\n if (command.action === \"health\") {\n const inspected = await inspectDaemon({ paths, driver });\n if (inspected.status === \"not-running\") {\n console.log(\"opentray daemon not running\");\n return 0;\n }\n\n const connection = await connectLocalBroker({\n autoStart: false,\n endpoint: paths.endpoint,\n homeDir: paths.homeDir,\n packageVersion,\n });\n try {\n const response = await connection.request({\n type: \"health\",\n requestId: \"opentray-daemon-health\",\n });\n if (response.type !== \"daemon-health\") {\n throw new Error(\n `expected daemon-health response, received ${response.type}`\n );\n }\n console.log(formatDaemonHealthOutput(response.health));\n } finally {\n await connection.close();\n }\n return 0;\n }\n\n printHelp();\n return 1;\n};\n\nconst printHelp = (): void => {\n console.error(\"Usage: opentray daemon <start|stop|restart|health>\");\n};\n\nexport const formatDaemonHealthOutput = (health: DaemonHealth): string => {\n const lines = [\n \"opentray daemon running\",\n `pid: ${health.pid}`,\n `endpoint: ${health.endpoint}`,\n `packageVersion: ${health.packageVersion}`,\n `protocolVersion: ${health.protocolVersion}`,\n `sessions: ${health.sessionCount}`,\n ];\n\n for (const session of health.sessions) {\n const internalLease = session.internalLeaseId ?? \"(pending)\";\n lines.push(\n `- sessionId=${session.sessionId} initialized=${session.initialized} internalLeaseId=${internalLease}`\n );\n }\n\n return lines.join(\"\\n\");\n};\n\nexport const isCliEntrypoint = (\n argvEntryPath: string | undefined,\n modulePath: string\n): boolean => {\n if (argvEntryPath === undefined) return false;\n try {\n return realpathSync(argvEntryPath) === realpathSync(modulePath);\n } catch {\n return argvEntryPath === modulePath;\n }\n};\n\nif (isCliEntrypoint(process.argv[1], cliModulePath)) {\n runCli(process.argv.slice(2)).then((code) => {\n process.exitCode = code;\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { realpathSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\n\nconst cliModulePath = fileURLToPath(import.meta.url);\n\ntype CliCommand =\n | { type: \"help\"; exitCode: 0 }\n | { type: \"unsupported\"; command: string | undefined };\n\nexport const parseCliCommand = (argv: string[]): CliCommand => {\n const [command] = argv;\n if (command === undefined || command === \"--help\" || command === \"-h\") {\n return { type: \"help\", exitCode: 0 };\n }\n return { type: \"unsupported\", command };\n};\n\nexport const runCli = async (argv: string[]): Promise<number> => {\n const command = parseCliCommand(argv);\n printHelp(command.type === \"unsupported\" ? command.command : undefined);\n return command.type === \"help\" ? command.exitCode : 1;\n};\n\nconst printHelp = (unsupportedCommand?: string): void => {\n if (unsupportedCommand !== undefined) {\n console.error(`Unsupported opentray command: ${unsupportedCommand}`);\n }\n console.error(\n [\n \"OpenTray v0.9 does not expose daemon lifecycle commands.\",\n \"Create trays from an app-owned process with:\",\n \"\",\n ' import { createTray } from \"opentray\";',\n \"\",\n \"Node runtime diagnostics live under the opentray/node binding helpers.\",\n \"Source-tree visual diagnostics live in packages/cli/examples.\",\n ].join(\"\\n\")\n );\n};\n\nexport const isCliEntrypoint = (\n argvEntryPath: string | undefined,\n modulePath: string\n): boolean => {\n if (argvEntryPath === undefined) return false;\n try {\n return realpathSync(argvEntryPath) === realpathSync(modulePath);\n } catch {\n return argvEntryPath === modulePath;\n }\n};\n\nif (isCliEntrypoint(process.argv[1], cliModulePath)) {\n runCli(process.argv.slice(2)).then((code) => {\n process.exitCode = code;\n });\n}\n"],"mappings":";;;;AAIA,MAAM,gBAAgB,cAAc,OAAO,KAAK,GAAG;AAMnD,MAAa,mBAAmB,SAA+B;CAC7D,MAAM,CAAC,WAAW;CAClB,IAAI,YAAY,KAAA,KAAa,YAAY,YAAY,YAAY,MAC/D,OAAO;EAAE,MAAM;EAAQ,UAAU;CAAE;CAErC,OAAO;EAAE,MAAM;EAAe;CAAQ;AACxC;AAEA,MAAa,SAAS,OAAO,SAAoC;CAC/D,MAAM,UAAU,gBAAgB,IAAI;CACpC,UAAU,QAAQ,SAAS,gBAAgB,QAAQ,UAAU,KAAA,CAAS;CACtE,OAAO,QAAQ,SAAS,SAAS,QAAQ,WAAW;AACtD;AAEA,MAAM,aAAa,uBAAsC;CACvD,IAAI,uBAAuB,KAAA,GACzB,QAAQ,MAAM,iCAAiC,oBAAoB;CAErE,QAAQ,MACN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;CACF,EAAE,KAAK,IAAI,CACb;AACF;AAEA,MAAa,mBACX,eACA,eACY;CACZ,IAAI,kBAAkB,KAAA,GAAW,OAAO;CACxC,IAAI;EACF,OAAO,aAAa,aAAa,MAAM,aAAa,UAAU;CAChE,QAAQ;EACN,OAAO,kBAAkB;CAC3B;AACF;AAEA,IAAI,gBAAgB,QAAQ,KAAK,IAAI,aAAa,GAChD,OAAO,QAAQ,KAAK,MAAM,CAAC,CAAC,EAAE,MAAM,SAAS;CAC3C,QAAQ,WAAW;AACrB,CAAC"}
package/dist/index.d.mts CHANGED
@@ -1,23 +1,21 @@
1
- import { C as createTrayHandle, S as createSurfaceHandle, _ as TrayExtensionMountSpec, a as ExtensionLoadOptions, b as createInitFrame, c as OpenTrayEventSource, d as SpaceHandle, f as SurfaceHandle, g as TrayExtensionContext, h as TrayExtension, i as EventfulTrayHandle, l as OpenTrayEventfulClient, m as TrayEventType, n as CreateClientOptions, o as OpenTrayClient, p as TrayEventByType, r as EventfulSpaceHandle, s as OpenTrayConnection, t as BrokerEventFrame, u as OpenTrayTransport, v as TrayHandle, x as createSpaceHandle, y as createClient } from "./client-C27qmRfr.mjs";
2
- import { BrokerEndpointIdentity, BrokerEndpointIdentityOptions, PROTOCOL_VERSION, SpaceOptions, SpaceRef, TrayOptions, createBrokerEndpointIdentity, formatBrokerEndpointName, formatBrokerStateRoot, formatUnixSocketPath, formatWindowsPipeName, isSupportedProtocolVersion } from "@opentray/spec";
1
+ import { C as TrayHandle, E as createTrayHandle, S as TrayExtensionMountSpec, T as createInitFrame, _ as OpenTrayTransport, b as TrayExtension, d as EventfulTrayHandle, f as ExtensionLoadOptions, g as OpenTrayEventSource, h as OpenTrayEventFrame, m as OpenTrayConnection, n as OpenTrayRuntimeBinding, p as OpenTrayClient, u as CreateClientOptions, v as TrayEventByType, w as createClient, x as TrayExtensionContext, y as TrayEventType } from "./native-runtime-Cn8BaffH.mjs";
2
+ import { BrokerEndpointIdentity, BrokerEndpointIdentityOptions, PROTOCOL_VERSION, TrayOptions, createBrokerEndpointIdentity, formatBrokerEndpointName, formatBrokerStateRoot, formatUnixSocketPath, formatWindowsPipeName, isSupportedProtocolVersion } from "@opentray/spec";
3
3
 
4
4
  //#region src/sdk.d.ts
5
- interface BrokerConnectOptions {
5
+ type OpenTrayRuntimeMode = "visible-binding" | "local-broker" | "headless-binding";
6
+ interface OpenTrayRuntimeOptions {
7
+ runtime?: OpenTrayRuntimeMode;
6
8
  endpoint?: string;
7
9
  homeDir?: string;
8
10
  packageVersion?: string;
9
11
  protocolVersion?: number;
10
12
  clientVersion?: string;
13
+ appId?: string;
14
+ appName?: string;
11
15
  autoStart?: boolean;
16
+ binding?: OpenTrayRuntimeBinding;
12
17
  }
13
- interface CreateTrayOptions extends BrokerConnectOptions {
14
- space?: SpaceRef;
15
- }
16
- declare const createSpace: (options: SpaceOptions, brokerOptions?: BrokerConnectOptions) => Promise<EventfulSpaceHandle>;
17
- /** @deprecated Use `createSpace`. */
18
- declare const createSurface: (options: SpaceOptions, brokerOptions?: BrokerConnectOptions) => Promise<SurfaceHandle>;
19
- declare const resolveDefaultSpace: (brokerOptions?: BrokerConnectOptions) => Promise<EventfulSpaceHandle>;
20
- declare const createTray: (options: TrayOptions, brokerOptions?: CreateTrayOptions) => Promise<EventfulTrayHandle>;
18
+ declare const createTray: (options: TrayOptions, runtimeOptions?: OpenTrayRuntimeOptions) => Promise<EventfulTrayHandle>;
21
19
  //#endregion
22
- export { type BrokerConnectOptions, type BrokerEndpointIdentity, type BrokerEndpointIdentityOptions, type BrokerEventFrame, type CreateClientOptions, type CreateTrayOptions, type EventfulSpaceHandle, type EventfulTrayHandle, type ExtensionLoadOptions, type OpenTrayClient, type OpenTrayConnection, type OpenTrayEventSource, type OpenTrayEventfulClient, type OpenTrayTransport, PROTOCOL_VERSION, type SpaceHandle, type SurfaceHandle, type TrayEventByType, type TrayEventType, type TrayExtension, type TrayExtensionContext, type TrayExtensionMountSpec, type TrayHandle, createBrokerEndpointIdentity, createClient, createInitFrame, createSpace, createSpaceHandle, createSurface, createSurfaceHandle, createTray, createTrayHandle, formatBrokerEndpointName, formatBrokerStateRoot, formatUnixSocketPath, formatWindowsPipeName, isSupportedProtocolVersion, resolveDefaultSpace };
20
+ export { type BrokerEndpointIdentity, type BrokerEndpointIdentityOptions, type CreateClientOptions, type EventfulTrayHandle, type ExtensionLoadOptions, type OpenTrayClient, type OpenTrayConnection, type OpenTrayEventFrame, type OpenTrayEventSource, type OpenTrayRuntimeOptions, type OpenTrayTransport, PROTOCOL_VERSION, type TrayEventByType, type TrayEventType, type TrayExtension, type TrayExtensionContext, type TrayExtensionMountSpec, type TrayHandle, createBrokerEndpointIdentity, createClient, createInitFrame, createTray, createTrayHandle, formatBrokerEndpointName, formatBrokerStateRoot, formatUnixSocketPath, formatWindowsPipeName, isSupportedProtocolVersion };
23
21
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/sdk.ts"],"mappings":";;;;UAWiB,oBAAA;EACf,QAAA;EACA,OAAA;EACA,cAAA;EACA,eAAA;EACA,aAAA;EACA,SAAA;AAAA;AAAA,UAGe,iBAAA,SAA0B,oBAAoB;EAC7D,KAAA,GAAQ,QAAA;AAAA;AAAA,cAGG,WAAA,GACX,OAAA,EAAS,YAAA,EACT,aAAA,GAAe,oBAAA,KACd,OAAA,CAAQ,mBAAA;;cAME,aAAA,GACX,OAAA,EAAS,YAAA,EACT,aAAA,GAAe,oBAAA,KACd,OAAA,CAAQ,aAAA;AAAA,cAEE,mBAAA,GACX,aAAA,GAAe,oBAAA,KACd,OAAA,CAAQ,mBAAA;AAAA,cAKE,UAAA,GACX,OAAA,EAAS,WAAA,EACT,aAAA,GAAe,iBAAA,KACd,OAAA,CAAQ,kBAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/sdk.ts"],"mappings":";;;;KAOY,mBAAA;AAAA,UAKK,sBAAA;EACf,OAAA,GAAU,mBAAA;EACV,QAAA;EACA,OAAA;EACA,cAAA;EACA,eAAA;EACA,aAAA;EACA,KAAA;EACA,OAAA;EACA,SAAA;EACA,OAAA,GAAU,sBAAsB;AAAA;AAAA,cAGrB,UAAA,GACX,OAAA,EAAS,WAAA,EACT,cAAA,GAAgB,sBAAA,KACf,OAAA,CAAQ,kBAAA"}