@oxprotocol/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/LICENSE +189 -0
  2. package/README.md +73 -0
  3. package/dist/cli.d.ts +15 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +109 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/commands/create.d.ts +6 -0
  8. package/dist/commands/create.d.ts.map +1 -0
  9. package/dist/commands/create.js +212 -0
  10. package/dist/commands/create.js.map +1 -0
  11. package/dist/commands/dev.d.ts +2 -0
  12. package/dist/commands/dev.d.ts.map +1 -0
  13. package/dist/commands/dev.js +191 -0
  14. package/dist/commands/dev.js.map +1 -0
  15. package/dist/commands/doctor.d.ts +16 -0
  16. package/dist/commands/doctor.d.ts.map +1 -0
  17. package/dist/commands/doctor.js +197 -0
  18. package/dist/commands/doctor.js.map +1 -0
  19. package/dist/commands/install-url.d.ts +20 -0
  20. package/dist/commands/install-url.d.ts.map +1 -0
  21. package/dist/commands/install-url.js +236 -0
  22. package/dist/commands/install-url.js.map +1 -0
  23. package/dist/commands/install.d.ts +13 -0
  24. package/dist/commands/install.d.ts.map +1 -0
  25. package/dist/commands/install.js +411 -0
  26. package/dist/commands/install.js.map +1 -0
  27. package/dist/commands/keygen.d.ts +13 -0
  28. package/dist/commands/keygen.d.ts.map +1 -0
  29. package/dist/commands/keygen.js +40 -0
  30. package/dist/commands/keygen.js.map +1 -0
  31. package/dist/commands/login.d.ts +21 -0
  32. package/dist/commands/login.d.ts.map +1 -0
  33. package/dist/commands/login.js +224 -0
  34. package/dist/commands/login.js.map +1 -0
  35. package/dist/commands/logout.d.ts +17 -0
  36. package/dist/commands/logout.d.ts.map +1 -0
  37. package/dist/commands/logout.js +70 -0
  38. package/dist/commands/logout.js.map +1 -0
  39. package/dist/commands/pack.d.ts +12 -0
  40. package/dist/commands/pack.d.ts.map +1 -0
  41. package/dist/commands/pack.js +94 -0
  42. package/dist/commands/pack.js.map +1 -0
  43. package/dist/commands/protocol-register.d.ts +29 -0
  44. package/dist/commands/protocol-register.d.ts.map +1 -0
  45. package/dist/commands/protocol-register.js +231 -0
  46. package/dist/commands/protocol-register.js.map +1 -0
  47. package/dist/commands/publish.d.ts +17 -0
  48. package/dist/commands/publish.d.ts.map +1 -0
  49. package/dist/commands/publish.js +188 -0
  50. package/dist/commands/publish.js.map +1 -0
  51. package/dist/commands/token.d.ts +17 -0
  52. package/dist/commands/token.d.ts.map +1 -0
  53. package/dist/commands/token.js +110 -0
  54. package/dist/commands/token.js.map +1 -0
  55. package/dist/commands/whoami.d.ts +13 -0
  56. package/dist/commands/whoami.d.ts.map +1 -0
  57. package/dist/commands/whoami.js +77 -0
  58. package/dist/commands/whoami.js.map +1 -0
  59. package/dist/index.d.ts +26 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +22 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/lib/broadcast.d.ts +41 -0
  64. package/dist/lib/broadcast.d.ts.map +1 -0
  65. package/dist/lib/broadcast.js +45 -0
  66. package/dist/lib/broadcast.js.map +1 -0
  67. package/dist/lib/host-adapter.d.ts +72 -0
  68. package/dist/lib/host-adapter.d.ts.map +1 -0
  69. package/dist/lib/host-adapter.js +193 -0
  70. package/dist/lib/host-adapter.js.map +1 -0
  71. package/dist/lib/host-detect.d.ts +60 -0
  72. package/dist/lib/host-detect.d.ts.map +1 -0
  73. package/dist/lib/host-detect.js +479 -0
  74. package/dist/lib/host-detect.js.map +1 -0
  75. package/dist/lib/oxp-url.d.ts +34 -0
  76. package/dist/lib/oxp-url.d.ts.map +1 -0
  77. package/dist/lib/oxp-url.js +74 -0
  78. package/dist/lib/oxp-url.js.map +1 -0
  79. package/dist/lib/vsx-install.d.ts +41 -0
  80. package/dist/lib/vsx-install.d.ts.map +1 -0
  81. package/dist/lib/vsx-install.js +74 -0
  82. package/dist/lib/vsx-install.js.map +1 -0
  83. package/dist/util.d.ts +10 -0
  84. package/dist/util.d.ts.map +1 -0
  85. package/dist/util.js +64 -0
  86. package/dist/util.js.map +1 -0
  87. package/package.json +48 -0
  88. package/templates/hello-code/README.md +39 -0
  89. package/templates/hello-code/oxp.json +14 -0
  90. package/templates/hello-code/package.json +18 -0
  91. package/templates/hello-code/src/extension.ts +17 -0
  92. package/templates/hello-code/tsconfig.json +20 -0
  93. package/templates/hello-html/README.md +18 -0
  94. package/templates/hello-html/oxp.json +14 -0
  95. package/templates/hello-html/ui/index.html +43 -0
  96. package/templates/hello-rust/Cargo.toml +19 -0
  97. package/templates/hello-rust/README.md +50 -0
  98. package/templates/hello-rust/oxp.json +22 -0
  99. package/templates/hello-rust/src/lib.rs +111 -0
  100. package/templates/hello-rust/wit/deps/oxp-host/oxp-host.wit +136 -0
  101. package/templates/hello-rust/wit/extension.wit +71 -0
  102. package/templates/hello-tree/README.md +15 -0
  103. package/templates/hello-tree/oxp.json +14 -0
  104. package/templates/hello-tree/ui/tree.json +27 -0
@@ -0,0 +1,236 @@
1
+ /**
2
+ * `oxp install-url <url>` — install a raw `.wasm` component from a URL into
3
+ * the shared host store, so any installed IDE host (VS Code, JetBrains)
4
+ * can list and activate it without re-downloading.
5
+ *
6
+ * Source schemes:
7
+ * https://…wasm always allowed
8
+ * file://…wasm always allowed
9
+ * http://…wasm only when --insecure-http (or host is localhost / 127.0.0.1 / ::1)
10
+ *
11
+ * Storage:
12
+ * $OXP_HOME/host-store/url-installs/<sha256>/bundle.wasm
13
+ * $OXP_HOME/host-store/url-installs/<sha256>/meta.json
14
+ *
15
+ * This sidesteps the registry / signature pipeline; the caller is trusting
16
+ * the URL. Hosts still gate activation with the permission prompt before
17
+ * loading the bytes into the runtime.
18
+ */
19
+ import * as path from "node:path";
20
+ import * as os from "node:os";
21
+ import { mkdir, mkdtemp, readFile, rm } from "node:fs/promises";
22
+ import { createHash } from "node:crypto";
23
+ import { fetchBundle, FetchBundleError, recordUrlInstall, listUrlInstalls, urlInstallRoot, } from "@oxprotocol/host-core";
24
+ import { unpackBundle } from "@oxprotocol/bundle";
25
+ import { fail, info, oxpHome } from "../util.js";
26
+ const HELP = `oxp install-url <url> Install an OXP extension from a URL
27
+
28
+ Accepts either:
29
+ - a raw .wasm component (Phase A; sha256 = wasm sha256), or
30
+ - a signed .oxp registry bundle (zstd-tar; the inner main.wasm is
31
+ extracted and stored, and the manifest's @publisher/slug@version
32
+ becomes the suggested id).
33
+
34
+ Arguments:
35
+ <url> https://, file:// (http:// requires --insecure-http
36
+ or a localhost host name)
37
+
38
+ Flags:
39
+ --list List previously URL-installed extensions and exit
40
+ --insecure-http Permit http:// URLs (auto-enabled for localhost)
41
+ --id <@scope/name> Override the suggested extension id
42
+ --json Emit a single JSON line on success
43
+
44
+ The wasm component is written to:
45
+ \$OXP_HOME/host-store/url-installs/<sha256>/
46
+
47
+ Installed IDEs detect new URL installs the next time their OXP runtime panel
48
+ is opened. Cancel-safe: SIGINT before write leaves the store untouched.
49
+ `;
50
+ function parseArgs(args) {
51
+ let url;
52
+ let list = false;
53
+ let json = false;
54
+ let insecureHttp = false;
55
+ let id;
56
+ for (let i = 0; i < args.length; i++) {
57
+ const a = args[i];
58
+ if (a === undefined)
59
+ continue;
60
+ if (a === "--list")
61
+ list = true;
62
+ else if (a === "--json")
63
+ json = true;
64
+ else if (a === "--insecure-http")
65
+ insecureHttp = true;
66
+ else if (a === "--id") {
67
+ const v = args[++i];
68
+ if (!v)
69
+ fail("--id requires a value");
70
+ id = v;
71
+ }
72
+ else if (a.startsWith("--id="))
73
+ id = a.slice("--id=".length);
74
+ else if (a === "-h" || a === "--help") {
75
+ process.stdout.write(HELP);
76
+ process.exit(0);
77
+ }
78
+ else if (a.startsWith("-"))
79
+ fail(`unknown flag: ${a}`);
80
+ else if (!url)
81
+ url = a;
82
+ else
83
+ fail(`unexpected argument: ${a}`);
84
+ }
85
+ return { url, list, json, insecureHttp, id };
86
+ }
87
+ function isLocalhost(u) {
88
+ return (u.hostname === "localhost" ||
89
+ u.hostname === "127.0.0.1" ||
90
+ u.hostname === "::1");
91
+ }
92
+ const WASM_MAGIC = [0x00, 0x61, 0x73, 0x6d];
93
+ const ZSTD_MAGIC = [0x28, 0xb5, 0x2f, 0xfd];
94
+ function startsWith(bytes, magic) {
95
+ if (bytes.length < magic.length)
96
+ return false;
97
+ for (let i = 0; i < magic.length; i++) {
98
+ if (bytes[i] !== magic[i])
99
+ return false;
100
+ }
101
+ return true;
102
+ }
103
+ function detectKind(bytes) {
104
+ if (startsWith(bytes, WASM_MAGIC))
105
+ return "wasm";
106
+ if (startsWith(bytes, ZSTD_MAGIC))
107
+ return "oxp";
108
+ return "unknown";
109
+ }
110
+ export async function installUrl(args) {
111
+ const opts = parseArgs(args);
112
+ const root = path.join(oxpHome(), "host-store");
113
+ if (opts.list) {
114
+ const records = await listUrlInstalls(root);
115
+ if (opts.json) {
116
+ process.stdout.write(JSON.stringify(records, null, 2) + "\n");
117
+ }
118
+ else if (records.length === 0) {
119
+ info(`(no URL installs yet — try: oxp install-url https://…wasm)`);
120
+ }
121
+ else {
122
+ info(`URL installs in ${urlInstallRoot(root)}:`);
123
+ for (const r of records) {
124
+ info(` ${r.suggestedId} ${r.sha256.slice(0, 12)}… ${r.size}b ${r.installedAt}`);
125
+ info(` ← ${r.sourceUrl}`);
126
+ }
127
+ }
128
+ return 0;
129
+ }
130
+ if (!opts.url) {
131
+ process.stderr.write(HELP);
132
+ return 2;
133
+ }
134
+ let parsedUrl;
135
+ try {
136
+ parsedUrl = new URL(opts.url);
137
+ }
138
+ catch {
139
+ fail(`not a valid URL: ${opts.url}`);
140
+ }
141
+ const allowInsecureHttp = opts.insecureHttp ||
142
+ (parsedUrl.protocol === "http:" && isLocalhost(parsedUrl));
143
+ await mkdir(root, { recursive: true });
144
+ const fetchCacheDir = path.join(oxpHome(), "cache", "url-installs");
145
+ await mkdir(fetchCacheDir, { recursive: true });
146
+ if (!opts.json)
147
+ info(`↓ fetching ${opts.url} …`);
148
+ let fetched;
149
+ try {
150
+ fetched = await fetchBundle(opts.url, {
151
+ cacheDir: fetchCacheDir,
152
+ allowInsecureHttp,
153
+ accept: "any",
154
+ onProgress: opts.json
155
+ ? undefined
156
+ : (received, total) => {
157
+ if (total != null && total > 0) {
158
+ const pct = Math.round((received / total) * 100);
159
+ process.stderr.write(`\r ${received}/${total} (${pct}%)`);
160
+ }
161
+ },
162
+ });
163
+ if (!opts.json)
164
+ process.stderr.write("\n");
165
+ }
166
+ catch (err) {
167
+ if (err instanceof FetchBundleError) {
168
+ fail(`fetch failed (${err.code}): ${err.message}`);
169
+ }
170
+ throw err;
171
+ }
172
+ // Detect what we got: raw .wasm component or .oxp (zstd-tar) registry bundle?
173
+ const rawBytes = await readFile(fetched.componentPath);
174
+ const kind = detectKind(rawBytes);
175
+ if (kind === "unknown") {
176
+ fail(`downloaded ${rawBytes.length} bytes but could not detect format (expected wasm or .oxp bundle)`);
177
+ }
178
+ let wasmBytes;
179
+ let suggestedId = opts.id;
180
+ let kindLabel;
181
+ if (kind === "wasm") {
182
+ wasmBytes = rawBytes;
183
+ kindLabel = "wasm";
184
+ }
185
+ else {
186
+ // .oxp bundle: unpack to a temp dir, locate manifest.main.wasm.
187
+ if (!opts.json)
188
+ info(`◇ unpacking .oxp bundle …`);
189
+ const tmp = await mkdtemp(path.join(os.tmpdir(), "oxp-install-"));
190
+ try {
191
+ const { manifest } = await unpackBundle(Buffer.from(rawBytes), tmp);
192
+ const wasmRel = manifest.main?.wasm;
193
+ if (!wasmRel) {
194
+ fail(`bundle ${manifest.id}@${manifest.version} has no main.wasm (declarative-only bundles are not yet installable via install-url)`);
195
+ }
196
+ wasmBytes = new Uint8Array(await readFile(path.join(tmp, wasmRel)));
197
+ // Prefer manifest's authoritative id over --id or @url/<basename>.
198
+ suggestedId = opts.id ?? `${manifest.id}@${manifest.version}`;
199
+ kindLabel = `bundle ${manifest.id}@${manifest.version}`;
200
+ }
201
+ finally {
202
+ await rm(tmp, { recursive: true, force: true });
203
+ }
204
+ }
205
+ // sha256 stored in the host-store entry is the *wasm* sha — that's the
206
+ // unit hosts load. The original `.oxp` URL is preserved as sourceUrl.
207
+ const wasmSha = createHash("sha256").update(wasmBytes).digest("hex");
208
+ const { dir, bundlePath, record } = await recordUrlInstall(root, Buffer.from(wasmBytes), {
209
+ sha256: wasmSha,
210
+ sourceUrl: fetched.sourceUrl,
211
+ suggestedId,
212
+ });
213
+ if (opts.json) {
214
+ process.stdout.write(JSON.stringify({
215
+ ok: true,
216
+ kind: kind === "oxp" ? "oxp" : "wasm",
217
+ suggestedId: record.suggestedId,
218
+ sha256: record.sha256,
219
+ size: record.size,
220
+ sourceUrl: record.sourceUrl,
221
+ bundlePath,
222
+ dir,
223
+ }) + "\n");
224
+ }
225
+ else {
226
+ info(`✓ installed ${record.suggestedId} (${kindLabel})`);
227
+ info(` sha256: ${record.sha256}`);
228
+ info(` size: ${record.size} bytes`);
229
+ info(` bundle: ${bundlePath}`);
230
+ info(``);
231
+ info(`Open the OXP runtime panel in VS Code or your JetBrains IDE`);
232
+ info(`to activate it (it will appear in the URL-installs list).`);
233
+ }
234
+ return 0;
235
+ }
236
+ //# sourceMappingURL=install-url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-url.js","sourceRoot":"","sources":["../../src/commands/install-url.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `oxp install <id>` — non-VS-Code install path.
3
+ *
4
+ * Drives the same `resolveAndVerify` + Phase A.4 prompt + `Store.install`
5
+ * pipeline that the VS Code host uses, so signature, WIT pin, TOFU
6
+ * pinning, and capability gating are identical.
7
+ *
8
+ * Install root: `$OXP_HOME/host-store/` (default `~/.oxp/host-store/`).
9
+ * The on-disk layout is identical to a real host's globalStorage so a
10
+ * Piye / vscode host pointed at the same root can read the install.
11
+ */
12
+ export declare function install(args: string[]): Promise<number>;
13
+ //# sourceMappingURL=install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/commands/install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AA0FH,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA8J7D"}
@@ -0,0 +1,411 @@
1
+ /**
2
+ * `oxp install <id>` — non-VS-Code install path.
3
+ *
4
+ * Drives the same `resolveAndVerify` + Phase A.4 prompt + `Store.install`
5
+ * pipeline that the VS Code host uses, so signature, WIT pin, TOFU
6
+ * pinning, and capability gating are identical.
7
+ *
8
+ * Install root: `$OXP_HOME/host-store/` (default `~/.oxp/host-store/`).
9
+ * The on-disk layout is identical to a real host's globalStorage so a
10
+ * Piye / vscode host pointed at the same root can read the install.
11
+ */
12
+ import { promises as fs } from "node:fs";
13
+ import * as path from "node:path";
14
+ import * as readline from "node:readline";
15
+ import { Store, Grants, installWithConsent, VerifyError, } from "@oxprotocol/host-core";
16
+ import { fail, info, oxpHome, registryUrl } from "../util.js";
17
+ import { detectHosts } from "../lib/host-detect.js";
18
+ import { ensureAdapters } from "../lib/host-adapter.js";
19
+ import { broadcast } from "../lib/broadcast.js";
20
+ import { parseOxpUrl, OxpUrlError } from "../lib/oxp-url.js";
21
+ import { installVsx } from "../lib/vsx-install.js";
22
+ function parseArgs(args) {
23
+ let id;
24
+ let yes = false;
25
+ let json = false;
26
+ let noDetect = false;
27
+ let noAdapter = false;
28
+ let fromUrl;
29
+ const hostFilter = [];
30
+ for (let i = 0; i < args.length; i++) {
31
+ const a = args[i];
32
+ if (a === undefined)
33
+ continue;
34
+ if (a === "-y" || a === "--yes")
35
+ yes = true;
36
+ else if (a === "--json")
37
+ json = true;
38
+ else if (a === "--no-detect")
39
+ noDetect = true;
40
+ else if (a === "--no-adapter")
41
+ noAdapter = true;
42
+ else if (a === "--host") {
43
+ const v = args[++i];
44
+ if (!v)
45
+ fail("--host requires a value");
46
+ hostFilter.push(v);
47
+ }
48
+ else if (a.startsWith("--host="))
49
+ hostFilter.push(a.slice("--host=".length));
50
+ else if (a === "--from") {
51
+ const v = args[++i];
52
+ if (!v)
53
+ fail("--from requires an oxp:// URL");
54
+ fromUrl = v;
55
+ }
56
+ else if (a.startsWith("--from="))
57
+ fromUrl = a.slice("--from=".length);
58
+ else if (a.startsWith("oxp://"))
59
+ fromUrl = a;
60
+ else if (a.startsWith("-"))
61
+ fail(`unknown flag: ${a}`);
62
+ else if (!id)
63
+ id = a;
64
+ else
65
+ fail(`unexpected argument: ${a}`);
66
+ }
67
+ return {
68
+ id,
69
+ yes,
70
+ json,
71
+ noDetect,
72
+ noAdapter,
73
+ hostFilter: hostFilter.length ? hostFilter : undefined,
74
+ fromUrl,
75
+ };
76
+ }
77
+ const HELP = `oxp install <id|oxp-url> Install an extension from the registry
78
+
79
+ Arguments:
80
+ <id> @publisher/slug
81
+ oxp://install/... Deep-link form (used by oxp.dev install buttons)
82
+
83
+ Flags:
84
+ --from <url> Install from an oxp:// URL
85
+ --host <id> Limit smart-detect to a specific host id (repeatable)
86
+ --no-detect Skip IDE detection (install to shared store only)
87
+ --no-adapter Don't auto-install missing host adapters
88
+ -y, --yes Approve every requested permission without prompting
89
+ --json Emit a single JSON line on success (machine-readable)
90
+ `;
91
+ export async function install(args) {
92
+ if (args[0] === "-h" || args[0] === "--help") {
93
+ process.stdout.write(HELP);
94
+ return 0;
95
+ }
96
+ const opts = parseArgs(args);
97
+ // oxp:// URL takes precedence — parse it and merge into opts.
98
+ if (opts.fromUrl) {
99
+ try {
100
+ const parsed = parseOxpUrl(opts.fromUrl);
101
+ if (!opts.id)
102
+ opts.id = parsed.id;
103
+ if (parsed.hosts && !opts.hostFilter)
104
+ opts.hostFilter = parsed.hosts;
105
+ }
106
+ catch (err) {
107
+ if (err instanceof OxpUrlError)
108
+ fail(err.message);
109
+ throw err;
110
+ }
111
+ }
112
+ if (!opts.id) {
113
+ process.stderr.write(HELP);
114
+ return 2;
115
+ }
116
+ if (!/^@[^/]+\/[^/]+$/.test(opts.id)) {
117
+ fail(`bad id ${opts.id} — expected @publisher/slug`);
118
+ }
119
+ const root = path.join(oxpHome(), "host-store");
120
+ await fs.mkdir(root, { recursive: true });
121
+ const hostFs = nodeHostFs();
122
+ const store = new Store(hostFs, root);
123
+ const grants = new Grants(hostFs, root);
124
+ // ── VSX-mirror short-circuit ────────────────────────────────────────────
125
+ // Ask the registry whether this id is an Open VSX mirror. If so, delegate
126
+ // to each detected IDE's `--install-extension` instead of running the
127
+ // wasm verify/install pipeline (VSX entries have no signed bundle).
128
+ const vsxMeta = await tryFetchVsxMeta(opts.id);
129
+ if (vsxMeta) {
130
+ let detected = [];
131
+ if (!opts.noDetect) {
132
+ detected = await detectHosts();
133
+ if (opts.hostFilter) {
134
+ detected = detected.filter((h) => opts.hostFilter.includes(h.id));
135
+ }
136
+ }
137
+ const reports = installVsx({
138
+ namespace: vsxMeta.namespace,
139
+ name: vsxMeta.name,
140
+ version: vsxMeta.version,
141
+ }, detected);
142
+ const okCount = reports.filter((r) => r.status === "ok").length;
143
+ const failed = reports.filter((r) => r.status === "failed");
144
+ if (opts.json) {
145
+ process.stdout.write(JSON.stringify({
146
+ ok: failed.length === 0,
147
+ kind: "vsx",
148
+ id: opts.id,
149
+ source: {
150
+ namespace: vsxMeta.namespace,
151
+ name: vsxMeta.name,
152
+ version: vsxMeta.version,
153
+ },
154
+ installed: okCount,
155
+ hosts: reports.map((r) => ({
156
+ id: r.host.id,
157
+ name: r.host.displayName,
158
+ status: r.status,
159
+ reason: r.reason,
160
+ })),
161
+ }) + "\n");
162
+ }
163
+ else {
164
+ info(`✓ VSX install: ${vsxMeta.namespace}.${vsxMeta.name}@${vsxMeta.version}`);
165
+ info(` source: open-vsx.org`);
166
+ printVsxSummary(reports);
167
+ if (okCount === 0 && reports.length > 0) {
168
+ info(` (no hosts received the extension — see reasons above)`);
169
+ }
170
+ if (reports.length === 0) {
171
+ info(` no VS Code-family IDE detected; nothing to install`);
172
+ }
173
+ }
174
+ return failed.length === 0 ? 0 : 1;
175
+ }
176
+ try {
177
+ const { record, prompted } = await installWithConsent({
178
+ registry: registryUrl(),
179
+ id: opts.id,
180
+ store,
181
+ grants,
182
+ prompt: opts.yes ? allowAllPrompt() : ttyPrompt(),
183
+ });
184
+ // Smart host detection — discover IDEs, ensure adapters, broadcast.
185
+ let detected = [];
186
+ let adapters = [];
187
+ if (!opts.noDetect) {
188
+ detected = await detectHosts();
189
+ if (opts.hostFilter) {
190
+ detected = detected.filter((h) => opts.hostFilter.includes(h.id));
191
+ }
192
+ adapters = await ensureAdapters(detected, {
193
+ reportOnly: opts.noAdapter,
194
+ });
195
+ }
196
+ await broadcast({
197
+ kind: "installed",
198
+ id: record.id,
199
+ version: record.version,
200
+ });
201
+ if (opts.json) {
202
+ process.stdout.write(JSON.stringify({
203
+ ok: true,
204
+ id: record.id,
205
+ version: record.version,
206
+ path: store.resourcePath(record),
207
+ prompted,
208
+ hosts: adapters.map((a) => ({
209
+ id: a.host.id,
210
+ name: a.host.displayName,
211
+ running: a.host.running,
212
+ adapter: a.status,
213
+ })),
214
+ }) + "\n");
215
+ }
216
+ else {
217
+ info(`✓ installed ${record.id}@${record.version}`);
218
+ info(` path: ${store.resourcePath(record)}`);
219
+ if (record.grantedPermissions?.length) {
220
+ info(` granted: ${record.grantedPermissions.join(", ")}`);
221
+ }
222
+ if (!opts.noDetect)
223
+ printHostSummary(adapters);
224
+ }
225
+ return 0;
226
+ }
227
+ catch (err) {
228
+ const code = err instanceof VerifyError ? err.code : "INSTALL_FAILED";
229
+ const msg = err.message;
230
+ if (opts.json) {
231
+ process.stdout.write(JSON.stringify({ ok: false, code, error: msg }) + "\n");
232
+ }
233
+ else {
234
+ process.stderr.write(`oxp: ${code}: ${msg}\n`);
235
+ }
236
+ return 1;
237
+ }
238
+ }
239
+ function printHostSummary(adapters) {
240
+ if (adapters.length === 0) {
241
+ info(` hosts: none detected (install lives in shared store only)`);
242
+ return;
243
+ }
244
+ info(` hosts:`);
245
+ for (const a of adapters) {
246
+ const runTag = a.host.running ? " (running)" : "";
247
+ let tail;
248
+ switch (a.status) {
249
+ case "present":
250
+ tail = "adapter ✓";
251
+ break;
252
+ case "installed":
253
+ tail = "adapter installed now";
254
+ break;
255
+ case "unavailable":
256
+ tail = `adapter pending (${a.reason})`;
257
+ break;
258
+ case "failed":
259
+ tail = `adapter install failed: ${a.error}`;
260
+ break;
261
+ case "unsupported":
262
+ tail = "adapter not yet built for this host";
263
+ break;
264
+ }
265
+ info(` - ${a.host.displayName}${runTag} — ${tail}`);
266
+ }
267
+ }
268
+ async function tryFetchVsxMeta(id) {
269
+ // id is `@publisher/slug` (validated upstream).
270
+ const m = /^@([^/]+)\/(.+)$/.exec(id);
271
+ if (!m)
272
+ return null;
273
+ const [, publisher, slug] = m;
274
+ const url = `${registryUrl()}/api/v1/extensions/${encodeURIComponent(publisher)}/${encodeURIComponent(slug)}`;
275
+ try {
276
+ const res = await fetch(url, {
277
+ headers: { accept: "application/json" },
278
+ });
279
+ if (!res.ok)
280
+ return null;
281
+ const j = (await res.json());
282
+ if (!j.vsx)
283
+ return null;
284
+ if (!j.vsx.namespace || !j.vsx.name)
285
+ return null;
286
+ return j.vsx;
287
+ }
288
+ catch {
289
+ // Network/DNS failure shouldn't poison the install — fall through to
290
+ // the normal native install path which has its own error handling.
291
+ return null;
292
+ }
293
+ }
294
+ function printVsxSummary(reports) {
295
+ if (reports.length === 0)
296
+ return;
297
+ info(` hosts:`);
298
+ for (const r of reports) {
299
+ const tag = r.status === "ok"
300
+ ? "installed ✓"
301
+ : r.status === "skipped"
302
+ ? `skipped (${r.reason ?? "unknown"})`
303
+ : `failed: ${r.reason ?? "unknown"}`;
304
+ info(` - ${r.host.displayName} — ${tag}`);
305
+ }
306
+ }
307
+ /* -------------------------------------------------------------------------- */
308
+ /* Node host-fs adapter */
309
+ /* -------------------------------------------------------------------------- */
310
+ function nodeHostFs() {
311
+ return {
312
+ async exists(p) {
313
+ try {
314
+ await fs.stat(p);
315
+ return true;
316
+ }
317
+ catch {
318
+ return false;
319
+ }
320
+ },
321
+ async mkdirp(p) {
322
+ await fs.mkdir(p, { recursive: true });
323
+ },
324
+ async readFile(p) {
325
+ return await fs.readFile(p);
326
+ },
327
+ async writeFile(p, bytes) {
328
+ await fs.mkdir(path.dirname(p), { recursive: true });
329
+ await fs.writeFile(p, bytes);
330
+ },
331
+ async rm(p) {
332
+ await fs.rm(p, { recursive: true, force: true });
333
+ },
334
+ join(...segments) {
335
+ return path.join(...segments);
336
+ },
337
+ };
338
+ }
339
+ /* -------------------------------------------------------------------------- */
340
+ /* Permission prompts */
341
+ /* -------------------------------------------------------------------------- */
342
+ function allowAllPrompt() {
343
+ // Explicit `--yes` flag — user has acknowledged unattended install.
344
+ // Still echoed so non-interactive logs show what was granted.
345
+ return async (req) => {
346
+ process.stderr.write(headerLines(req).join("\n") + "\n");
347
+ for (const it of req.items)
348
+ process.stderr.write(itemLine(it) + "\n");
349
+ process.stderr.write("(--yes) granting all\n");
350
+ return { kind: "grant", grantedRaw: req.items.map((i) => i.raw) };
351
+ };
352
+ }
353
+ function ttyPrompt() {
354
+ return async (req) => {
355
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
356
+ return {
357
+ kind: "deny",
358
+ reason: "non-interactive shell: re-run with --yes to grant every requested permission",
359
+ };
360
+ }
361
+ const lines = headerLines(req);
362
+ for (const l of lines)
363
+ process.stdout.write(l + "\n");
364
+ process.stdout.write("\nThis extension is requesting:\n");
365
+ for (const it of req.items)
366
+ process.stdout.write(" " + itemLine(it) + "\n");
367
+ const choice = await ask("\nApprove? [a]ll / [c]ustomize / [d]eny: ", "d");
368
+ const c = choice.trim().toLowerCase();
369
+ if (c === "a" || c === "all" || c === "y" || c === "yes") {
370
+ return { kind: "grant", grantedRaw: req.items.map((i) => i.raw) };
371
+ }
372
+ if (c === "c" || c === "customize") {
373
+ const grantedRaw = [];
374
+ for (const it of req.items) {
375
+ const ans = await ask(` ${it.raw} — grant? [y/N]: `, it.previouslyGranted ? "y" : "n");
376
+ if (ans.trim().toLowerCase().startsWith("y"))
377
+ grantedRaw.push(it.raw);
378
+ }
379
+ return { kind: "grant", grantedRaw };
380
+ }
381
+ return { kind: "deny", reason: "user denied at prompt" };
382
+ };
383
+ }
384
+ function headerLines(req) {
385
+ return [
386
+ "",
387
+ `OXP — install permission prompt`,
388
+ ` ${req.displayName} (${req.extensionId}@${req.version})`,
389
+ req.isUpgrade
390
+ ? ` ⚠ version upgrade — new permissions appeared since the last prompt`
391
+ : ` first-time install`,
392
+ ];
393
+ }
394
+ function itemLine(it) {
395
+ const tag = it.previouslyGranted ? " [previously granted]" : "";
396
+ const sens = `(${it.sensitivity})`;
397
+ return `${it.raw} ${sens} — ${it.description}${tag}`;
398
+ }
399
+ function ask(question, fallback) {
400
+ return new Promise((resolve) => {
401
+ const rl = readline.createInterface({
402
+ input: process.stdin,
403
+ output: process.stdout,
404
+ });
405
+ rl.question(question, (ans) => {
406
+ rl.close();
407
+ resolve(ans || fallback);
408
+ });
409
+ });
410
+ }
411
+ //# sourceMappingURL=install.js.map