create-opentray 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +136 -133
- package/assets/create-opentray-logo.png +0 -0
- package/dist/bin-sZB5nWq0.mjs +2142 -0
- package/dist/bin-sZB5nWq0.mjs.map +1 -0
- package/dist/bin.d.mts +15 -2
- package/dist/bin.d.mts.map +1 -0
- package/dist/bin.mjs +1 -1
- package/dist/index.d.mts +62 -351
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -1
- package/dist/shell/assets/browse.js +1 -1
- package/dist/shell/assets/index.css +1 -1
- package/dist/shell/assets/index.js +7 -7
- package/dist/shell/assets/input.js +1 -1
- package/dist/shell/assets/main.js +21 -43
- package/dist/shell/assets/terminal-pane.js +1 -1
- package/dist/shell/browse.html +3 -0
- package/dist/shell/favicon-16.png +0 -0
- package/dist/shell/favicon-32.png +0 -0
- package/dist/shell/favicon-64.png +0 -0
- package/dist/shell/index.html +3 -0
- package/dist/shell/logo.png +0 -0
- package/dist/shell/terminal.html +3 -0
- package/dist/skill/SKILL.md +156 -0
- package/dist/skill/references/cli-reference.md +77 -0
- package/dist/skill/references/how-it-works.md +49 -0
- package/dist/webui/assets/browse.js +1 -1
- package/dist/webui/assets/index.css +1 -1
- package/dist/webui/assets/index.js +7 -7
- package/dist/webui/assets/input.js +1 -1
- package/dist/webui/assets/main.js +21 -43
- package/dist/webui/assets/terminal-pane.js +1 -1
- package/dist/webui/browse.html +3 -0
- package/dist/webui/favicon-16.png +0 -0
- package/dist/webui/favicon-32.png +0 -0
- package/dist/webui/favicon-64.png +0 -0
- package/dist/webui/index.html +3 -0
- package/dist/webui/logo.png +0 -0
- package/dist/webui/terminal.html +3 -0
- package/package.json +12 -6
- package/skill/SKILL.md +156 -0
- package/skill/references/cli-reference.md +77 -0
- package/skill/references/how-it-works.md +49 -0
- package/dist/bin-BT_kcLuP.mjs +0 -3823
- package/dist/bin-BT_kcLuP.mjs.map +0 -1
- package/dist/bin-CeFW1wxR.d.mts +0 -15
- package/dist/bin-CeFW1wxR.d.mts.map +0 -1
- package/dist/icon-codec-BQ3cGV1n.mjs +0 -13
- package/dist/icon-codec-BQ3cGV1n.mjs.map +0 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/ghostty-web.js","assets/index.js","assets/index.css"])))=>i.map(i=>d[i]);
|
|
2
|
-
import{c as
|
|
2
|
+
import{c as p,e as b,m as y,k as w,l as x}from"./index.js";const k=[["path",{d:"M12 19h8",key:"baeox8"}],["path",{d:"m4 17 6-6-6-6",key:"1yngyt"}]],L=p("terminal",k);function E(n){return b(n.defaultTagName??"div",n,n)}const S=x("group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",{variants:{variant:{default:"bg-primary text-primary-foreground [a]:hover:bg-primary/80",secondary:"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",destructive:"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",outline:"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",ghost:"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",link:"text-primary underline-offset-4 hover:underline"}},defaultVariants:{variant:"default"}});function N({className:n,variant:i="default",render:t,...c}){return E({defaultTagName:"span",props:y({className:w(S({variant:i}),n)},c),render:t,state:{slot:"badge",variant:i}})}const P="modulepreload",R=function(n){return"/"+n},v={},_=function(i,t,c){let u=Promise.resolve();if(t&&t.length>0){let f=function(e){return Promise.all(e.map(r=>Promise.resolve(r).then(d=>({status:"fulfilled",value:d}),d=>({status:"rejected",reason:d}))))};document.getElementsByTagName("link");const s=document.querySelector("meta[property=csp-nonce]"),o=s?.nonce||s?.getAttribute("nonce");u=f(t.map(e=>{if(e=R(e),e in v)return;v[e]=!0;const r=e.endsWith(".css"),d=r?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${e}"]${d}`))return;const a=document.createElement("link");if(a.rel=r?"stylesheet":P,r||(a.as="script"),a.crossOrigin="",a.href=e,o&&a.setAttribute("nonce",o),document.head.appendChild(a),r)return new Promise((m,h)=>{a.addEventListener("load",m),a.addEventListener("error",()=>h(new Error(`Unable to preload CSS for ${e}`)))})}))}function l(s){const o=new Event("vite:preloadError",{cancelable:!0});if(o.payload=s,window.dispatchEvent(o),!o.defaultPrevented)throw s}return u.then(s=>{for(const o of s||[])o.status==="rejected"&&l(o.reason);return i().catch(l)})};let g;const T=()=>{g??=(async()=>{const n=await _(()=>import("./ghostty-web.js"),__vite__mapDeps([0,1,2]));return await n.init(),n})()};async function A(n){let i;try{T(),i=await g}catch{return}const t=new i.Terminal({fontSize:13,theme:{background:"#05070b",foreground:"#b7c3d8",cursor:"#3d8bff",selectionBackground:"#2a3550"}});t.open(n);const c=new i.FitAddon;t.loadAddon(c),c.fit();const u=new Set,l=new Set,s=t.onData(e=>{for(const r of u)r(e)}),o=t.onResize(({cols:e,rows:r})=>{for(const d of l)d({cols:e,rows:r})}),f=new ResizeObserver(()=>{try{c.fit()}catch{}});return f.observe(n),{write:e=>t.write(e),reset:()=>t.reset(),fit:()=>c.fit(),focus:()=>n.querySelector("canvas")?.dispatchEvent(new Event("focus")),readState:()=>{const e=t.buffer.active,r=t.getSelectionPosition();return{cols:t.cols,rows:t.rows,cursorX:e.cursorX,cursorY:e.cursorY,...r===void 0?{}:{selection:{start:{x:r.start.x,y:r.start.y},end:{x:r.end.x,y:r.end.y}}}}},onData(e){return u.add(e),()=>u.delete(e)},onResize(e){return l.add(e),()=>l.delete(e)},dispose(){f.disconnect(),s.dispose(),o.dispose(),t.dispose()}}}export{N as B,L as T,_,A as c,T as p,E as u};
|
package/dist/shell/browse.html
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<link rel="icon" type="image/png" sizes="64x64" href="./favicon-64.png" />
|
|
7
|
+
<link rel="icon" type="image/png" sizes="32x32" href="./favicon-32.png" />
|
|
8
|
+
<link rel="icon" type="image/png" sizes="16x16" href="./favicon-16.png" />
|
|
6
9
|
<title>Browse</title>
|
|
7
10
|
<script type="module" crossorigin src="./assets/browse.js"></script>
|
|
8
11
|
<link rel="modulepreload" crossorigin href="./assets/index.js">
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/shell/index.html
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<link rel="icon" type="image/png" sizes="64x64" href="./favicon-64.png" />
|
|
7
|
+
<link rel="icon" type="image/png" sizes="32x32" href="./favicon-32.png" />
|
|
8
|
+
<link rel="icon" type="image/png" sizes="16x16" href="./favicon-16.png" />
|
|
6
9
|
<title>Terminal</title>
|
|
7
10
|
<script type="module" crossorigin src="./assets/terminal.js"></script>
|
|
8
11
|
<link rel="modulepreload" crossorigin href="./assets/index.js">
|
|
Binary file
|
package/dist/shell/terminal.html
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<link rel="icon" type="image/png" sizes="64x64" href="./favicon-64.png" />
|
|
7
|
+
<link rel="icon" type="image/png" sizes="32x32" href="./favicon-32.png" />
|
|
8
|
+
<link rel="icon" type="image/png" sizes="16x16" href="./favicon-16.png" />
|
|
6
9
|
<title>Terminal</title>
|
|
7
10
|
<script type="module" crossorigin src="./assets/terminal.js"></script>
|
|
8
11
|
<link rel="modulepreload" crossorigin href="./assets/index.js">
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create-opentray
|
|
3
|
+
description: create-opentray guide for turning any HTTP-serving start command into an OpenTray-hosted desktop application — WebUI wizard, non-interactive CLI creation, the v1 create-opentray.json configuration authority, the fixed ~/.opentray/create registry, application lifecycle (edit, copy, export, uninstall), icon handling including pixel-art smoothing, developer mode, and platform limitations. Use when scaffolding or managing applications created by create-opentray.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# create-opentray
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
`create-opentray` packages a start command that serves HTTP locally into an
|
|
11
|
+
OpenTray-hosted desktop application: a real tray icon, a webview window, and a
|
|
12
|
+
supervised child process. This skill explains how it works, how to drive it
|
|
13
|
+
non-interactively, and how to manage created applications.
|
|
14
|
+
|
|
15
|
+
Three adapter surfaces share one Core:
|
|
16
|
+
|
|
17
|
+
1. `create-opentray` (no arguments) and `create-opentray web` — the loopback
|
|
18
|
+
WebUI wizard. It can run your command once, discover the HTTP ports it
|
|
19
|
+
owns, and suggest a name/icon from the served page.
|
|
20
|
+
2. `create-opentray create …` — fully non-interactive creation. No browser,
|
|
21
|
+
no prompts, no sniffing: identity, icons, and the command vector are all
|
|
22
|
+
explicit.
|
|
23
|
+
3. `create-opentray app …` — manage registered applications: list, edit,
|
|
24
|
+
copy, export, uninstall.
|
|
25
|
+
|
|
26
|
+
## How OpenTray hosts an app (background)
|
|
27
|
+
|
|
28
|
+
OpenTray is a desktop status platform: your application calls `createTray()`
|
|
29
|
+
and owns its own foreground/background lifetime. A created application is a
|
|
30
|
+
small generated project whose entry (`main.mjs`):
|
|
31
|
+
|
|
32
|
+
- spawns your recorded start command as a supervised child,
|
|
33
|
+
- discovers the command's owned HTTP listening ports at runtime,
|
|
34
|
+
- hosts each verified port in an application-mode webview window, and
|
|
35
|
+
- owns the tray session with a Quit menu item.
|
|
36
|
+
|
|
37
|
+
The generated project depends only on published packages (`opentray`,
|
|
38
|
+
`@opentray/ext-webview`) — never on the create-opentray tool itself.
|
|
39
|
+
|
|
40
|
+
## The v1 configuration authority
|
|
41
|
+
|
|
42
|
+
Every registered application has exactly one editable desired-state document:
|
|
43
|
+
|
|
44
|
+
```text
|
|
45
|
+
~/.opentray/create/<encoded-app-id>/
|
|
46
|
+
create-opentray.json # the SOLE editable authority (schemaVersion: 1)
|
|
47
|
+
app/ # generated payload (managed dir, or a link to an external dir)
|
|
48
|
+
app-icon.<ext> # committed icon snapshots (referenced relatively)
|
|
49
|
+
tray-icon.<ext>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`create-opentray.json` records app identity and name, the exact command
|
|
53
|
+
vector (executable, args, cwd, env overlay), package-manager choice, icon
|
|
54
|
+
resource references with content hashes and provenance, icon-rendering
|
|
55
|
+
options (including `imageSmoothingEnabled`), window options, and
|
|
56
|
+
`developerMode`. Generated files are derived output — editing them is
|
|
57
|
+
overwritten on the next apply; edit the JSON instead.
|
|
58
|
+
|
|
59
|
+
Key rules:
|
|
60
|
+
|
|
61
|
+
- **appId is immutable.** Changing identity means `app copy` or a new
|
|
62
|
+
application, never an in-place rename.
|
|
63
|
+
- **The registry root is fixed**: `~/.opentray/create/`. It is never
|
|
64
|
+
redirected by a setting.
|
|
65
|
+
- **Snapshots are stable.** An icon fetched from a URL is committed locally
|
|
66
|
+
with its hash; later URL drift never changes an existing registration.
|
|
67
|
+
- **Legacy breaking boundary.** Projects identified only by the older
|
|
68
|
+
`opentray.app.json` marker are not discovered, listed, or migrated.
|
|
69
|
+
|
|
70
|
+
## Non-interactive creation
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
npx create-opentray create \
|
|
74
|
+
--app-id app.local.mytool \
|
|
75
|
+
--app-name "My Tool" \
|
|
76
|
+
--exec npm --arg run --arg dev \
|
|
77
|
+
--cwd /path/to/project \
|
|
78
|
+
--app-icon https://example.com/logo.png \
|
|
79
|
+
--pm npm
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- `--arg` is repeatable and each value is ONE exact argv element — never a
|
|
83
|
+
shell string. Metacharacters like `&&` stay literal.
|
|
84
|
+
- Icon sources may be local files, `http(s)` URLs, or `data:` URLs. The CLI
|
|
85
|
+
never scrapes names or favicons — everything is explicit.
|
|
86
|
+
- `--dry-run` prints the Core plan (effects, warnings, blocks) without
|
|
87
|
+
mutating anything.
|
|
88
|
+
- `--force` replaces only a VERIFIED create-opentray payload. It never
|
|
89
|
+
adopts or clears a user directory.
|
|
90
|
+
- `--no-image-smoothing` renders icons with nearest-neighbor sampling —
|
|
91
|
+
essential for low-resolution pixel art so enlarged icons keep hard edges.
|
|
92
|
+
- `--developer-mode` admits WebView DevTools in the generated app (default
|
|
93
|
+
off; it means nothing else).
|
|
94
|
+
|
|
95
|
+
## Application lifecycle
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
npx create-opentray app list
|
|
99
|
+
npx create-opentray app edit app.local.mytool --app-name "Renamed" --force
|
|
100
|
+
npx create-opentray app copy app.local.mytool --new-app-id app.local.mytool2
|
|
101
|
+
npx create-opentray app export app.local.mytool --format sh
|
|
102
|
+
npx create-opentray app uninstall app.local.mytool
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
- `app list` shows health status: `healthy`, `invalid-config`,
|
|
106
|
+
`incompatible-version`, `missing-payload`, `broken-link`, or `running`,
|
|
107
|
+
with both registration and payload paths.
|
|
108
|
+
- `app edit` applies flag patches over the committed document (omitted flags
|
|
109
|
+
never reset fields). A running instance blocks edits with a typed
|
|
110
|
+
`app_running` result until `--stop-running` is passed; `--restart`
|
|
111
|
+
relaunches after.
|
|
112
|
+
- `app uninstall` removes the registration. If the payload is a link to an
|
|
113
|
+
external directory, the target is RETAINED unless `--purge-target` is
|
|
114
|
+
explicit. Dock (macOS) and taskbar (Windows) pins must be removed manually.
|
|
115
|
+
- Live processes are only ever terminated when the recorded PID and its
|
|
116
|
+
ownership token/start fingerprint still match. A reused PID is refused,
|
|
117
|
+
never killed.
|
|
118
|
+
|
|
119
|
+
## Command and script export
|
|
120
|
+
|
|
121
|
+
`app export` emits a complete recreation artifact:
|
|
122
|
+
|
|
123
|
+
- `--format command` — the exact argv command line (one string).
|
|
124
|
+
- `--format sh` — a self-contained POSIX shell script; uploaded icon bytes
|
|
125
|
+
are embedded and reconstructed into a temp file before invocation.
|
|
126
|
+
- `--format ps1` — the PowerShell equivalent (CRLF line endings).
|
|
127
|
+
- `--output <file>` writes the script to a file instead of stdout.
|
|
128
|
+
- Direct command copy with embedded uploads requires `--force-copy`;
|
|
129
|
+
scripts are the default for uploaded resources.
|
|
130
|
+
- Any environment entry makes complete export require
|
|
131
|
+
`--acknowledge-env`. create-opentray never classifies which values are
|
|
132
|
+
sensitive — it treats every env-bearing export as needing one explicit
|
|
133
|
+
confirmation, and never echoes env values in ordinary output.
|
|
134
|
+
|
|
135
|
+
## Windows notes
|
|
136
|
+
|
|
137
|
+
- Directory links use junctions (or symlinks when permitted); if neither can
|
|
138
|
+
be created the operation fails with a typed error — it never silently
|
|
139
|
+
copies.
|
|
140
|
+
- Paths (drive letters, UNC, spaces) round-trip natively; the CLI never
|
|
141
|
+
shells through `/bin/sh` or reinterprets POSIX separators.
|
|
142
|
+
- Native Windows acceptance is validated on Windows agents; non-Windows
|
|
143
|
+
fixtures are preparatory evidence only.
|
|
144
|
+
|
|
145
|
+
## Skill commands
|
|
146
|
+
|
|
147
|
+
```sh
|
|
148
|
+
npx create-opentray skill # read this SKILL.md
|
|
149
|
+
npx create-opentray skill list # list packaged skill files
|
|
150
|
+
npx create-opentray skill list references # list one directory
|
|
151
|
+
npx create-opentray skill read SKILL.md # read any packaged file
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Skill access is read-only and contained: absolute paths, `..` traversal, NUL
|
|
155
|
+
bytes, and symlink escapes are rejected before any filesystem read, and
|
|
156
|
+
output is always the canonical English tree regardless of host locale.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# CLI reference
|
|
2
|
+
|
|
3
|
+
Complete option surface of the `create-opentray` yargs command tree. Every
|
|
4
|
+
command accepts `--json` for a machine-readable typed result on stdout
|
|
5
|
+
(progress and diagnostics then use stderr only).
|
|
6
|
+
|
|
7
|
+
## `create-opentray` / `create-opentray web`
|
|
8
|
+
|
|
9
|
+
Starts the loopback WebUI wizard. Options: `--port <n>` (bind a specific
|
|
10
|
+
loopback port), `--no-open` (do not launch the default browser).
|
|
11
|
+
|
|
12
|
+
## `create-opentray create`
|
|
13
|
+
|
|
14
|
+
Non-interactive creation. Options:
|
|
15
|
+
|
|
16
|
+
| Option | Meaning |
|
|
17
|
+
| ------ | ------- |
|
|
18
|
+
| `--config <file>` | base v1 document; explicit flags override named fields only |
|
|
19
|
+
| `--app-id <id>` | immutable reverse-dotted identity (required without `--config`) |
|
|
20
|
+
| `--app-name <name>` | display name (required without `--config`) |
|
|
21
|
+
| `--exec <executable>` | command executable |
|
|
22
|
+
| `--arg <value>` | one exact argv element (repeatable) |
|
|
23
|
+
| `--cwd <dir>` | command working directory (default: current) |
|
|
24
|
+
| `--env <KEY=VALUE>` | environment overlay entry (repeatable) |
|
|
25
|
+
| `--pm <npm\|pnpm\|bun>` | package manager for the generated project |
|
|
26
|
+
| `--app-icon <src>` | file path, http(s) URL, or data URL |
|
|
27
|
+
| `--tray-icon <src>` | same; default follows `--app-icon` |
|
|
28
|
+
| `--icon-background <black\|white\|transparent>` | composition background |
|
|
29
|
+
| `--icon-scale <0.5–0.95>` | foreground scale (default 0.8) |
|
|
30
|
+
| `--image-smoothing <bool>` | `false` = nearest-neighbor for pixel art (default true) |
|
|
31
|
+
| `--tray-template` | treat the tray source as a darwin template |
|
|
32
|
+
| `--developer-mode` | admit WebView DevTools (default false) |
|
|
33
|
+
| `--window <WxH>` | window size (default 1200x800) |
|
|
34
|
+
| `--force` | replace a VERIFIED existing payload |
|
|
35
|
+
| `--stop-running` | stop a verified running instance first |
|
|
36
|
+
| `--skip-install` | write the project without installing |
|
|
37
|
+
| `--dry-run` | print the Core plan without mutation |
|
|
38
|
+
|
|
39
|
+
## `create-opentray app list [--json]`
|
|
40
|
+
|
|
41
|
+
Health-classified registrations with registration/payload paths and link
|
|
42
|
+
evidence.
|
|
43
|
+
|
|
44
|
+
## `create-opentray app edit <app-id> [patches…]`
|
|
45
|
+
|
|
46
|
+
Same field options as `create`, plus `--restart`. Rejects appId changes
|
|
47
|
+
(use `app copy`). Requires `--stop-running` when a verified instance runs.
|
|
48
|
+
|
|
49
|
+
## `create-opentray app copy <app-id> --new-app-id <id> [--app-name <name>]`
|
|
50
|
+
|
|
51
|
+
Creates a new registration from an existing one. Snapshots are re-committed
|
|
52
|
+
under the new identity.
|
|
53
|
+
|
|
54
|
+
## `create-opentray app export <app-id> [--format command|sh|ps1]`
|
|
55
|
+
|
|
56
|
+
| Option | Meaning |
|
|
57
|
+
| ------ | ------- |
|
|
58
|
+
| `--format <command\|sh\|ps1>` | output shape (default command) |
|
|
59
|
+
| `--output <file>` | write a script to a file instead of stdout |
|
|
60
|
+
| `--force-copy` | allow embedded upload bytes in the direct command |
|
|
61
|
+
| `--acknowledge-env` | confirm complete output includes env values |
|
|
62
|
+
|
|
63
|
+
## `create-opentray app uninstall <app-id>`
|
|
64
|
+
|
|
65
|
+
| Option | Meaning |
|
|
66
|
+
| ------ | ------- |
|
|
67
|
+
| `--stop-running` | stop a verified running instance first |
|
|
68
|
+
| `--purge-target` | ALSO delete a linked external target after revalidation |
|
|
69
|
+
|
|
70
|
+
Exit always names the removed paths, whether the external target was
|
|
71
|
+
retained or deleted, and that OS pins are manual.
|
|
72
|
+
|
|
73
|
+
## `create-opentray skill [list|read]`
|
|
74
|
+
|
|
75
|
+
Read-only packaged skill access. `skill` reads `SKILL.md`; `skill list
|
|
76
|
+
[path]` lists logical entries; `skill read <path>` writes one file's exact
|
|
77
|
+
content. Paths are relative with `/` separators on every platform.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# How create-opentray works
|
|
2
|
+
|
|
3
|
+
## The creation pipeline
|
|
4
|
+
|
|
5
|
+
1. **Desired state** — CLI flags or the WebUI form compile into one v1
|
|
6
|
+
desired-state document. Both adapters share the same Core, so a WebUI
|
|
7
|
+
create and an equivalent CLI create produce the same plan.
|
|
8
|
+
2. **Plan** — Core performs every non-mutating check: identity, ownership of
|
|
9
|
+
an existing registration, resource sources, payload strategy, running
|
|
10
|
+
processes, and warnings. `--dry-run` prints exactly this.
|
|
11
|
+
3. **Apply** — Core commits icon snapshots (with hashes), writes
|
|
12
|
+
`create-opentray.json`, regenerates the payload transactionally into a
|
|
13
|
+
staging directory and swaps it in, installs dependencies, launches, and
|
|
14
|
+
records a runtime ownership record (PID + token + start fingerprint).
|
|
15
|
+
|
|
16
|
+
## What the generated app does at runtime
|
|
17
|
+
|
|
18
|
+
- Reads the frozen command vector from its derived config and spawns the
|
|
19
|
+
command with an absolute, PATH-independent executable.
|
|
20
|
+
- Continuously monitors the command's OWNED listening ports (ownership is
|
|
21
|
+
attributed through the process tree; foreign listeners such as browser
|
|
22
|
+
DevTools sockets are never adopted).
|
|
23
|
+
- Opens one application-mode webview window per verified port; a port that
|
|
24
|
+
stops listening marks its window detached.
|
|
25
|
+
- Publishes a tray with Quit; optional startup-terminal and address-bar
|
|
26
|
+
shells when configured.
|
|
27
|
+
|
|
28
|
+
## Force and ownership
|
|
29
|
+
|
|
30
|
+
`--force` is not "overwrite anything": it may only replace a payload whose
|
|
31
|
+
registration holds a valid matching v1 document. An unknown non-empty
|
|
32
|
+
directory is rejected with a typed error and nothing is deleted.
|
|
33
|
+
|
|
34
|
+
## Stop/restart safety
|
|
35
|
+
|
|
36
|
+
Generated apps record `runtime.json` (PID, unique token, process start
|
|
37
|
+
fingerprint). Stop commands verify the live process identity before any
|
|
38
|
+
termination; a PID reused by an unrelated process is refused with
|
|
39
|
+
`pid_reused`, and an unverifiable PID is never killed by name or appId.
|
|
40
|
+
|
|
41
|
+
## Uninstall semantics
|
|
42
|
+
|
|
43
|
+
- Managed payload: the registration envelope AND the payload directory are
|
|
44
|
+
removed.
|
|
45
|
+
- Linked payload: only the link and envelope are removed; the external
|
|
46
|
+
target is retained unless `--purge-target` revalidates identity and
|
|
47
|
+
explicitly authorizes deletion.
|
|
48
|
+
- macOS Dock pins and Windows taskbar pins are user-managed and must be
|
|
49
|
+
removed manually.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r as a,j as r,R as
|
|
1
|
+
import{r as a,j as r,R as C,a as S}from"./index.js";import{B as m,A as N,a as U,R as z,G as B,I}from"./input.js";const A=()=>typeof window<"u"&&"navigation"in window?window.navigation:void 0,v=s=>{const c=s.trim();if(c.length!==0)try{return new URL(/^https?:\/\//iu.test(c)?c:`http://${c}`).href}catch{return}},T=s=>`${location.pathname}?url=${encodeURIComponent(s)}`,D=()=>{const s=new URLSearchParams(location.search).get("url");return v(s??"")??"about:blank"};function P(){const s=a.useRef(D()),[c,x]=a.useState(s.current),[g,b]=a.useState(s.current),o=a.useRef([s.current]),t=a.useRef(0),i=a.useRef(A()),h=a.useRef(null),[$,y]=a.useState(0),d=a.useCallback(e=>{b(e),x(e)},[]);a.useEffect(()=>{const e=i.current;if(e===void 0)return;const n=u=>{!u.canIntercept||u.hashChange||u.intercept({handler:()=>{const p=new URLSearchParams(new URL(u.destination.url).search).get("url");if(p===null)return;const w=v(p);w!==void 0&&d(w)}})},l=()=>y(u=>u+1);return e.addEventListener("navigate",n),e.addEventListener("currententrychange",l),()=>{e.removeEventListener("navigate",n),e.removeEventListener("currententrychange",l)}},[]);const R=e=>{const n=i.current;if(n!==void 0)try{n.navigate(T(e));return}catch{}o.current=[...o.current.slice(0,t.current+1),e],t.current=o.current.length-1,d(e)},j=()=>{const e=i.current;if(e!==void 0&&e.currentEntry!==null&&e.currentEntry.index>0){const n=e.entries()[e.currentEntry.index-1];if(n!==void 0)try{e.traverseTo(n.key);return}catch{}}t.current>0&&(t.current-=1,d(o.current[t.current]))},E=()=>{const e=i.current;if(e!==void 0&&e.currentEntry!==null){const l=e.entries()[e.currentEntry.index+1];if(l!==void 0)try{e.traverseTo(l.key);return}catch{}}t.current<o.current.length-1&&(t.current+=1,d(o.current[t.current]))},f=i.current?.currentEntry??null,k=f!==null?f.index>0:t.current>0,L=f!==null?f.index<(i.current?.entries().length??1)-1:t.current<o.current.length-1;return r.jsxs("div",{className:"flex h-screen flex-col overflow-hidden",children:[r.jsxs("div",{className:"flex h-11 shrink-0 items-center gap-2 border-b border-border bg-card px-3",children:[r.jsx(m,{variant:"ghost",size:"icon-sm",disabled:!k,onClick:j,"aria-label":"后退",children:r.jsx(N,{})}),r.jsx(m,{variant:"ghost",size:"icon-sm",disabled:!L,onClick:E,"aria-label":"前进",children:r.jsx(U,{})}),r.jsx(m,{variant:"ghost",size:"icon-sm",onClick:()=>{h.current!==null&&(h.current.src=g)},"aria-label":"重新加载",children:r.jsx(z,{})}),r.jsx(B,{className:"size-4 shrink-0 text-muted-foreground"}),r.jsx(I,{className:"h-7 font-mono text-xs",value:c,placeholder:"输入 URL 跳转",onChange:e=>x(e.target.value),onKeyDown:e=>{if(e.key!=="Enter")return;const n=v(c);n!==void 0&&R(n)}})]}),r.jsx("iframe",{ref:h,title:"service",src:g,className:"min-h-0 flex-1 border-0 bg-white",sandbox:"allow-same-origin allow-scripts allow-forms"})]})}C.createRoot(document.getElementById("root")).render(r.jsx(S.StrictMode,{children:r.jsx(P,{})}));
|