dshm-compat 0.0.0-stage → 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # Changelog
2
+
3
+ ## 0.1.1
4
+
5
+ - Tool results are now lossless JSON: `undefined`-valued fields are stripped at
6
+ the tool boundary. This fixes `compat_analyze` / `compat_plan` / `compat_scan`
7
+ failing with `value is not lossless JSON` on DSHM.
8
+ - Local tarball installation: `compat_install` (and `compat_convert` with
9
+ `install:true`) unpacks a `.tgz` into the owning DSH profile's `node_modules`,
10
+ records the dependency, and appends the bundle. This works around DSHM's host
11
+ installer rejecting local paths, `file:` and `http://` specs. A restart is
12
+ required, and the result says `restartRequired: true`.
13
+ - Local install preserves the tarball's file modes (needed for native worker
14
+ binaries such as `dsh-ops-worker`).
15
+ - Docs updated with the DSHM `pnpm view` limitation and the local install path.
16
+
17
+ ## 0.1.0
18
+
19
+ - Initial analyzer, S0/S1 converter, dependency preflight/vendoring, guest
20
+ probe/provision, install queue, and dsh-ops 0.3.x recipe.
package/INSTALL.md ADDED
@@ -0,0 +1,68 @@
1
+ # Installing dshm-compat
2
+
3
+ The artifact is a normal DSH bundle package: `dshm-compat-0.1.0.tgz`.
4
+ It is host-only, has no runtime dependencies, and adds eight agent tools.
5
+
6
+ ## A. DSHM (the tablet)
7
+
8
+ ### A1. npm or GitHub
9
+
10
+ Publish the package to npm (remove `"private": true` from `package.json`
11
+ first), or push this directory to a GitHub repository.
12
+
13
+ On DSHM builds without a shell, the official **Settings → Plugins → Add plugin**
14
+ input calls `pnpm view` before installing and fails with
15
+ `Command failed with EACCES: pnpm view ... spawn pnpm EACCES`. Use one of these
16
+ instead:
17
+
18
+ - ask the DSHM Agent to run `pnpm add dshm-compat@0.1.1` in its bash tool (the
19
+ bundled pnpm shim routes that command to the host install queue); or
20
+ - install from a GitHub repository URL; or
21
+ - after any working install, use the plugin's own `compat_install` tool for
22
+ npm/GitHub specs.
23
+
24
+ The plugin's local converted tarballs are installed by `compat_install` itself:
25
+ it unpacks them into `profiles/<profile>/node_modules/<name>`, records the
26
+ dependency and bundle, and asks for a DSHM restart. The host install queue does
27
+ not accept local paths, `file:` or `http://` specs.
28
+
29
+ ### A2. HTTPS tarball
30
+
31
+ Put `dshm-compat-0.1.0.tgz` on any HTTPS host (GitHub Release, Gitee Release,
32
+ object storage) and paste the `.tgz` URL into the same Add plugin input. DSHM's
33
+ installer accepts tarball specs; if the current market UI refuses a plain URL,
34
+ use A1.
35
+
36
+ ### A3. hdc / local path
37
+
38
+ If `hdc` is available and the app can read the path:
39
+
40
+ ```sh
41
+ hdc file send dshm-compat-0.1.0.tgz /data/storage/el2/base/haps/entry/files/dshm-compat-0.1.0.tgz
42
+ ```
43
+
44
+ Then enter the absolute path (or `file:/...`) in the Add plugin input. This
45
+ depends on the DSHM build's local-file support; A1 is safer.
46
+
47
+ ## B. Desktop DSH (for testing only)
48
+
49
+ ```powershell
50
+ & 'D:\安装\dsh-desktop\resources\runtime\cli\bin\dsh.cmd' plugin --profile desktop add 'D:\dsh\dsh-26-09-30\dshm-compat\dshm-compat-0.1.0.tgz'
51
+ ```
52
+
53
+ If the profile reports `ERR_PNPM_UNEXPECTED_VIRTUAL_STORE`, its `node_modules`
54
+ was migrated from another DSH home; repair that profile first or use a fresh
55
+ profile.
56
+
57
+ ## C. After installation
58
+
59
+ - The bundle row is `dshm-compat`; enable it if it is disabled.
60
+ - Ask the agent to run `compat_status`; it should return the data directory and
61
+ empty report list.
62
+ - Then run `compat_analyze`, `compat_plan`, `compat_convert`, `compat_install`.
63
+ - No market input interception is included; the tools are the entry point.
64
+
65
+ ## D. Uninstall
66
+
67
+ Remove `dshm-compat` from the plugin market/plugin manager. Its data lives under
68
+ `$DSH_HOME/dshm-compat` and is not removed automatically.
package/README.md CHANGED
@@ -1,3 +1,91 @@
1
- # Temporary Holding Version
1
+ # dshm-compat
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Analyze DSH plugins that cannot install or run on HarmonyOS DSHM, then convert
4
+ them through the least invasive strategy that can work.
5
+
6
+ ## Current phase
7
+
8
+ 0.1.1 fixes DSHM tool-result validation and adds local-tarball installation.
9
+ Phase 1 ships the analyzer, the S0/S1 converter, the dsh-ops recipe, guest
10
+ probing/provisioning, the DSHM install-queue transport, and a profile-local
11
+ fallback for artifacts the host queue rejects:
12
+
13
+ - `compat_analyze` downloads an npm/GitHub/local package and classifies it.
14
+ - `compat_plan` returns the concrete conversion plan without changing anything.
15
+ - `compat_scan` triages up to 50 specs in parallel and returns a strategy table.
16
+ - `compat_convert` rewrites an S0 package's manifest (foreign `os`/`cpu` gates,
17
+ install lifecycle scripts, client platform), vendors missing pure-JS runtime
18
+ dependencies into a flat `node_modules`, validates the resulting tarball, and
19
+ repacks it as a DSHM tarball; for S1 it removes a native dependency that the
20
+ DSHM core already provides. For `dsh-ops` it applies the 0.3.x recipe once an
21
+ AArch64 Linux worker is supplied via `workerPath`; without one it returns
22
+ `needs-native-build` with the exact cross-build command.
23
+ - `compat_guest_probe` reports the Alpine guest kernel/packages/tools.
24
+ - `compat_guest_provision` installs a reviewed allowlist of Alpine packages in
25
+ the guest (`confirm:true` required).
26
+ - `compat_status` lists stored reports and install attempts.
27
+ - `compat_install` installs an npm/GitHub spec through the host queue. Local
28
+ `.tgz` paths (and `file:`/`link:`) are unpacked directly into the owning DSH
29
+ profile and its bundle list, then need a DSHM restart; this is required
30
+ because the DSHM host queue rejects local specs.
31
+ - `compat_convert` with `install:true` uses the same local fallback for its
32
+ converted tarball.
33
+
34
+ The converter deliberately stops at the manifest/repack/vendor boundary:
35
+ source transpilation and native builds are later phases. A report saying
36
+ `S2`/`S3`/`S4` is returned as-is rather than being reported as a successful
37
+ conversion.
38
+
39
+ See [architecture](docs/ARCHITECTURE.md) and [conversion recipes](docs/RECIPES.md).
40
+
41
+ ## CLI
42
+
43
+ The same converter runs on a PC without installing the DSH plugin:
44
+
45
+ ```sh
46
+ node bin/dshm-compat.mjs analyze dsh-m
47
+ node bin/dshm-compat.mjs plan dsh-ops
48
+ node bin/dshm-compat.mjs convert dsh-cache-billing --no-vendor
49
+ node bin/dshm-compat.mjs convert dsh-ops --worker ./dsh-ops-worker
50
+ node bin/dshm-compat.mjs scan dsh-m dsh-ops some-plugin
51
+ ```
52
+
53
+ `convert` writes the tarball under `$DSH_HOME/dshm-compat/builds` (override with
54
+ `DSHM_COMPAT_DIR`).
55
+
56
+ ## Test
57
+
58
+ ```sh
59
+ node test/run.mjs
60
+ ```
61
+
62
+ The harness builds synthetic tarballs under `_tmp`, uses a fake DSHM core, and
63
+ never touches a real DSH profile.
64
+
65
+ Strategy codes:
66
+
67
+ | Code | Meaning |
68
+ |---|---|
69
+ | `S0` | pure JS/TS; code-level conversion on the host |
70
+ | `S1` | known native dependency with an OHOS/JS substitute |
71
+ | `S2` | native binary or shell command; guest Alpine sidecar |
72
+ | `S3` | source-available native; WASM or remote build |
73
+ | `S4` | unsupported, e.g. a win32-only binary with no source |
74
+
75
+ ## Install
76
+
77
+ Install the package directory as a DSH bundle in the target profile. On DSHM,
78
+ the package can be packed and submitted through the install queue once the
79
+ converter phase is in place.
80
+
81
+ ## Data
82
+
83
+ `$DSH_HOME/dshm-compat/`
84
+
85
+ - `reports/<fingerprint>.json` — full analyzer report.
86
+ - `state.json` — bounded report/install index.
87
+
88
+ ## Safety
89
+
90
+ The analyzer never executes install scripts. `compat_install` runs third-party
91
+ code only after the caller passes `confirm:true`.
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Standalone PC-side entry for dshm-compat.
4
+ *
5
+ * The same modules run inside DSHM, but a desktop checkout can convert a
6
+ * package and hand the tarball to the device. No dependencies are required.
7
+ */
8
+ import { analyze } from '../lib/analyze.js'
9
+ import { scan } from '../lib/batch.js'
10
+ import { convert } from '../lib/convert.js'
11
+ import { buildPlan } from '../lib/plan.js'
12
+ import { status } from '../lib/state.js'
13
+
14
+ const USAGE = `dshm-compat <command> [args]
15
+
16
+ commands:
17
+ analyze <spec> classify one npm/GitHub/local package
18
+ plan <spec> print the conversion plan
19
+ convert <spec> [--no-vendor] [--worker <path>]
20
+ write a converted tarball
21
+ scan <spec> [<spec> ...] classify several packages
22
+ status show the local compatibility data directory
23
+
24
+ spec: npm name[@version], https://github.com/owner/repo, or a local .tgz path
25
+ `
26
+
27
+ const args = process.argv.slice(2)
28
+ const command = args[0]
29
+ const rest = args.slice(1)
30
+
31
+ /** @param {string} message */
32
+ function fail(message) {
33
+ console.error(message)
34
+ process.exitCode = 1
35
+ }
36
+
37
+ if (command === undefined || command === '--help' || command === '-h') {
38
+ console.log(USAGE)
39
+ } else if (command === 'analyze') {
40
+ if (rest[0] === undefined) fail('analyze requires a spec')
41
+ else {
42
+ try {
43
+ const report = await analyze(rest[0], { textScan: true })
44
+ console.log(JSON.stringify(report, null, 2))
45
+ } catch (error) { fail(String(error?.message ?? error)) }
46
+ }
47
+ } else if (command === 'plan') {
48
+ if (rest[0] === undefined) fail('plan requires a spec')
49
+ else {
50
+ try {
51
+ const report = await analyze(rest[0], { textScan: true })
52
+ console.log(JSON.stringify(buildPlan(report), null, 2))
53
+ } catch (error) { fail(String(error?.message ?? error)) }
54
+ }
55
+ } else if (command === 'convert') {
56
+ if (rest[0] === undefined) fail('convert requires a spec')
57
+ else {
58
+ try {
59
+ const vendor = !rest.includes('--no-vendor')
60
+ const workerAt = rest.indexOf('--worker')
61
+ const workerPath = workerAt >= 0 ? rest[workerAt + 1] : undefined
62
+ const report = await analyze(rest[0], { textScan: false })
63
+ const result = await convert(rest[0], { report, vendor, workerPath })
64
+ console.log(JSON.stringify(result, null, 2))
65
+ if (result.status !== 'ok') process.exitCode = 1
66
+ } catch (error) { fail(String(error?.message ?? error)) }
67
+ }
68
+ } else if (command === 'scan') {
69
+ if (rest.length === 0) fail('scan requires at least one spec')
70
+ else {
71
+ try {
72
+ const result = await scan(rest)
73
+ console.log(JSON.stringify({
74
+ counts: result.counts,
75
+ items: result.items.map(({ report: _report, ...item }) => item),
76
+ }, null, 2))
77
+ } catch (error) { fail(String(error?.message ?? error)) }
78
+ }
79
+ } else if (command === 'status') {
80
+ console.log(JSON.stringify(status(), null, 2))
81
+ } else {
82
+ fail(`unknown command: ${command}\n\n${USAGE}`)
83
+ }
@@ -0,0 +1,6 @@
1
+ # dshm-compat: one host row. The row registers the compatibility analyzer and
2
+ # (in later phases) conversion/install tools. It never changes other rows.
3
+ - insert:
4
+ - id: dshm-compat
5
+ name: dshm-compat
6
+ config: {}
@@ -0,0 +1,92 @@
1
+ # dshm-compat architecture
2
+
3
+ `dshm-compat` is a DSH plugin that classifies and converts plugins that cannot
4
+ install or run on HarmonyOS DSHM. It is deliberately layered: a conversion that
5
+ would require a different runtime is reported as such, not disguised as a
6
+ manifest edit.
7
+
8
+ ## DSHM constraints that shape the design
9
+
10
+ 1. **No spawnable shell on the host.** The app sandbox rejects `execve` of
11
+ bundled tools. `dshm-vm-bash` redirects `bash -c` into an Alpine QEMU guest.
12
+ 2. **No host `dlopen` of profile native code.** `dshm-vm-bash`'s path notes say
13
+ `dlopen` only admits `/data/storage/el1/bundle/libs/arm64`; a `.node` placed
14
+ in a profile or plugin directory does not load. Native code must run in the
15
+ guest, or be WASM/pure JS.
16
+ 3. **The install path is a queue.** `$DSH_HOME/install-queue/<id>.req` is
17
+ consumed by the host's in-process installer, which writes `<id>.done` or
18
+ `<id>.fail` JSON. There is no package manager process to call.
19
+ 4. **The guest is a full Alpine 3.22 aarch64 userland** with 9p access to the
20
+ sandbox, QGA command execution, `apk`, and network through slirp. It is the
21
+ universal fallback for native code and shell commands.
22
+
23
+ ## Strategy layers
24
+
25
+ | Code | Layer | What runs where | Conversion |
26
+ |---|---|---|---|
27
+ | `S0` | code | host | rewrite manifest, repack; dependency vendoring and source builds later |
28
+ | `S1` | dependency | host | remove a native dependency that the DSHM core already provides as an OHOS port |
29
+ | `S2` | environment | Alpine guest | provision Node/npm/build tools, run or build the native artifact, proxy tools |
30
+ | `S3` | instruction | host/guest/remote | compile the addon to WASM with an N-API shim, or build/run remotely |
31
+ | `S4` | none | nowhere | report the blocking artifact honestly |
32
+
33
+ ## Pipeline
34
+
35
+ ```
36
+ spec ──► resolve (npm / GitHub / local .tgz)
37
+ ──► extract (ustar/pax, no dependency)
38
+ ──► analyze (manifest, native files, scripts, platform/Electron scan, core inventory)
39
+ ──► plan (S0..S4 + concrete steps)
40
+ ──► convert (S0/S1 now; S2+ is a plan)
41
+ ──► pack (ustar writer)
42
+ ──► install queue (DSHM only)
43
+ ```
44
+
45
+ ## Modules
46
+
47
+ | Module | Responsibility |
48
+ |---|---|
49
+ | `lib/fetch.js` | resolve npm specs, GitHub repos, local archives; bounded downloads |
50
+ | `lib/tar.js` | ustar/pax extraction with traversal and size guards |
51
+ | `lib/analyze.js` | classification evidence, strategy, confidence, next action |
52
+ | `lib/core.js` | locate `$DSH_HOME/cores/<version>/node_modules` and compare runtime dependencies |
53
+ | `lib/rules.js` | exact recipes for known packages (`dsh-ops`) |
54
+ | `lib/convert.js` | S0 manifest rewrite/repack and vendoring; S1 native-dependency removal |
55
+ | `lib/vendor.js` | flat `node_modules` vendoring for missing pure-JS dependencies |
56
+ | `lib/validate.js` | post-conversion manifest/entry/dependency validation |
57
+ | `lib/plan.js` | user-visible conversion plan for every strategy |
58
+ | `lib/guest.js` | read-only Alpine guest probe through `subprocess` |
59
+ | `lib/install.js` | DSHM install-queue producer with polling and cancellation |
60
+ | `lib/state.js` | bounded report/install index and atomic writes |
61
+ | `lib/pack.js` | dependency-free ustar/gzip writer |
62
+
63
+ ## Tool surface
64
+
65
+ | Tool | Side effects |
66
+ |---|---|
67
+ | `compat_analyze` | network download; writes a report under `$DSH_HOME/dshm-compat` |
68
+ | `compat_plan` | network download; writes a report; no package changes |
69
+ | `compat_scan` | network downloads; writes one report per successful spec |
70
+ | `compat_convert` | writes a converted tarball; optionally submits it to the install queue |
71
+ | `compat_guest_probe` | read-only guest command |
72
+ | `compat_guest_provision` | installs allowlisted Alpine packages in the guest |
73
+ | `compat_install` | writes one install-queue request; runs third-party code |
74
+ | `compat_status` | read-only |
75
+
76
+ ## Phase status
77
+
78
+ - **Phase 1 (done):** analyzer, batch triage, S0 manifest converter + flat
79
+ dependency vendoring + output validation, S1 manifest substitution,
80
+ dependency preflight, guest probe/provisioning, worker execution primitive,
81
+ dsh-ops 0.3.x recipe (external AArch64 worker), install queue, offline tests.
82
+ - **Phase 2 (next):** on-device guest verification, dsh-ops worker build, nested
83
+ version-conflict vendoring, sidecar RPC.
84
+ - **Phase 3:** WASM/remote fallback, market UI entry, compatibility registry
85
+ updates.
86
+
87
+ ## Non-goals
88
+
89
+ - Translating a win32-only binary into OHOS code.
90
+ - Loading profile `.node` files in the host.
91
+ - Running arbitrary `postinstall` scripts on the host.
92
+ - Claiming S2/S3/S4 conversions succeeded when only a plan exists.
@@ -0,0 +1,140 @@
1
+ # Conversion recipes
2
+
3
+ These are the concrete recipes behind `S1`/`S2` plans. They are intentionally
4
+ written as patches to the upstream package, not as claims that the current
5
+ converter executes them.
6
+
7
+ ## `dsh-ops` (Rust native worker)
8
+
9
+ Upstream: `T-Auto/dsh-ops`. The core is `native/` (Rust) and the published
10
+ worker is `runtime/<target>/dsh-ops-worker`.
11
+
12
+ ### 1. Support matrix
13
+
14
+ `lib/ptc/runtime.js` hard-codes:
15
+
16
+ ```js
17
+ const PLATFORMS = ['win32-x64', 'linux-x64', 'darwin-arm64']
18
+ ```
19
+
20
+ `resolveWorker()` checks this matrix **before** considering
21
+ `DSH_OPS_WORKER_BIN`, so a binary path override alone cannot unblock DSHM.
22
+
23
+ Patch:
24
+
25
+ - add `linux-arm64` to `PLATFORMS`;
26
+ - map an OHOS `process.platform` to `linux-arm64` before the matrix check;
27
+ - keep `workerName()` returning `dsh-ops-worker` on non-win32;
28
+ - look for `runtime/linux-arm64/dsh-ops-worker`.
29
+
30
+ Also update:
31
+
32
+ - `package.json` `os`/`cpu` (remove the gates or add `openharmony`/`arm64`);
33
+ - `dsh-plugin.json` `platforms`, `architectures`, and `targets` for each
34
+ `x-components` entry.
35
+
36
+ ### 2. Build the worker
37
+
38
+ From a Linux build host or CI:
39
+
40
+ ```sh
41
+ rustup target add aarch64-unknown-linux-musl
42
+ # either
43
+ cross build --release --locked --manifest-path native/Cargo.toml --target aarch64-unknown-linux-musl
44
+ # or
45
+ cargo zigbuild --release --locked --manifest-path native/Cargo.toml --target aarch64-unknown-linux-musl
46
+ ```
47
+
48
+ The Windows/macOS metadata crates are target-gated in `native/Cargo.toml`; the
49
+ portable dependencies are `cap-std`, `cap-fs-ext`, `fs2`, `rustix`, `sha2`,
50
+ `serde`, `serde_json`, `uuid`, `regex`.
51
+
52
+ Stage the result as `runtime/linux-arm64/dsh-ops-worker` with a matching
53
+ `provenance.json` (`version`, `target`, `executable`, `sha256`).
54
+
55
+ ### 3. Spawn through the guest
56
+
57
+ `lib/file/worker.js` and `lib/ptc/worker.js` call:
58
+
59
+ ```js
60
+ subprocess.spawn({ argv: [file], ... })
61
+ ```
62
+
63
+ `dshm-vm-bash` only redirects `bash -c`, so patch both call sites to:
64
+
65
+ ```js
66
+ argv: ['bash', '-c', 'exec "$0"', file]
67
+ ```
68
+
69
+ The worker path is under the profile's 9p share, so the guest sees the same
70
+ absolute path. If exec from 9p fails on a device, copy the worker to
71
+ `/tmp/dsh-ops-worker` once (hash-keyed) and execute that copy.
72
+
73
+ ### 4. Shell component
74
+
75
+ `lib/shells/resolve.js` treats every non-win32 host as POSIX and probes the
76
+ **host** for `/bin/bash`. DSHM's host has no bash. Patch the DSHM branch to
77
+ report the guest's `/bin/bash` as available; `ops_bash` then spawns
78
+ `bash -c`, which `dshm-vm-bash` routes into the guest.
79
+
80
+ Disable the PowerShell rung: the pwsh payload is win32-only.
81
+
82
+ ### 5. PTC vs file
83
+
84
+ `lib/ptc/worker.js` calls `sandbox.confine()` for non-`danger-full-access`
85
+ policies. If DSHM's sandbox provider cannot confine the worker, PTC tools fail
86
+ closed. The `file` component uses policy checks but no `sandbox.confine`, so
87
+ port `ops_*` file tools first and treat PTC as a later optimization.
88
+
89
+ ### 6. Verification
90
+
91
+ Run on the device:
92
+
93
+ - `ops_stat` on a workspace file;
94
+ - `ops_grep` over a small tree;
95
+ - `ops_write_file` then `ops_stat` (check the new revision);
96
+ - `ops_copy` / `ops_move` (9p rename/atomicity);
97
+ - a failure case with a read-only policy.
98
+
99
+ Watch for `cap-std`/`rustix` failures on 9p (`openat2`, `statx`, `renameat2`,
100
+ `flock`). If those fail, the fallback is a JS reimplementation of the worker
101
+ protocol rather than a guest port.
102
+
103
+ ## Common native dependencies
104
+
105
+ | Dependency | DSHM route |
106
+ |---|---|
107
+ | `sharp` | DSHM core ships an OpenHarmony port; remove the plugin's dependency so the core resolves it (S1) |
108
+ | `koffi` | same as `sharp` |
109
+ | `node-pty` | same as `sharp`; DSHM's terminal backend already uses it |
110
+ | `better-sqlite3` / `sqlite3` | replace calls with `node:sqlite` (Node 24) or run in the guest |
111
+ | `canvas` | no core port; guest or WASM |
112
+ | `esbuild` | `esbuild-wasm` on the host or `esbuild` in the guest |
113
+ | `bcrypt` | `bcryptjs` |
114
+ | `argon2` | `hash-wasm` |
115
+
116
+ ## Electron API shim
117
+
118
+ For host plugins that import `electron` only for platform glue:
119
+
120
+ | Electron | DSHM equivalent |
121
+ |---|---|
122
+ | `app.getPath(...)` | DSH home/paths service |
123
+ | `shell.openExternal` / `openPath` | `open-in-app` |
124
+ | `dialog.showOpenDialog` | directory picker |
125
+ | `clipboard` | ArkWeb clipboard API |
126
+ | `ipcMain` / `ipcRenderer` | DSH client connection / Cordis events |
127
+ | `BrowserWindow` | not shimmed; reimplement as a DSH client plugin or run remotely |
128
+
129
+ ## Shell commands
130
+
131
+ A plugin that calls `child_process` directly will not be redirected by
132
+ `dshm-vm-bash`. Two options:
133
+
134
+ 1. Rewrite the import in the bundled host code to DSH's `subprocess` service
135
+ (which routes `bash -c` to the guest).
136
+ 2. Run the whole plugin host half in the guest and proxy its tools over RPC.
137
+
138
+ The guest has `apk`; ffmpeg, git, and Python can be installed offline by
139
+ downloading the Alpine packages on the host, placing them in the 9p share, and
140
+ running `apk add --no-network`.
package/icon.svg ADDED
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="DSHM compatibility">
2
+ <rect x="3" y="3" width="58" height="58" rx="14" fill="#1f2937"/>
3
+ <path d="M18 22h12l5 6-5 6H18l5-6-5-6Z" fill="#60a5fa"/>
4
+ <path d="M34 30h12l-5 6 5 6H34l-5-6 5-6Z" fill="#f59e0b" opacity=".95"/>
5
+ <path d="M14 45h36" stroke="#e5e7eb" stroke-width="3" stroke-linecap="round"/>
6
+ </svg>