@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.
- package/LICENSE +189 -0
- package/README.md +73 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +109 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/create.d.ts +6 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +212 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/dev.d.ts +2 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +191 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/doctor.d.ts +16 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +197 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/install-url.d.ts +20 -0
- package/dist/commands/install-url.d.ts.map +1 -0
- package/dist/commands/install-url.js +236 -0
- package/dist/commands/install-url.js.map +1 -0
- package/dist/commands/install.d.ts +13 -0
- package/dist/commands/install.d.ts.map +1 -0
- package/dist/commands/install.js +411 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/keygen.d.ts +13 -0
- package/dist/commands/keygen.d.ts.map +1 -0
- package/dist/commands/keygen.js +40 -0
- package/dist/commands/keygen.js.map +1 -0
- package/dist/commands/login.d.ts +21 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +224 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +17 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +70 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/pack.d.ts +12 -0
- package/dist/commands/pack.d.ts.map +1 -0
- package/dist/commands/pack.js +94 -0
- package/dist/commands/pack.js.map +1 -0
- package/dist/commands/protocol-register.d.ts +29 -0
- package/dist/commands/protocol-register.d.ts.map +1 -0
- package/dist/commands/protocol-register.js +231 -0
- package/dist/commands/protocol-register.js.map +1 -0
- package/dist/commands/publish.d.ts +17 -0
- package/dist/commands/publish.d.ts.map +1 -0
- package/dist/commands/publish.js +188 -0
- package/dist/commands/publish.js.map +1 -0
- package/dist/commands/token.d.ts +17 -0
- package/dist/commands/token.d.ts.map +1 -0
- package/dist/commands/token.js +110 -0
- package/dist/commands/token.js.map +1 -0
- package/dist/commands/whoami.d.ts +13 -0
- package/dist/commands/whoami.d.ts.map +1 -0
- package/dist/commands/whoami.js +77 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/broadcast.d.ts +41 -0
- package/dist/lib/broadcast.d.ts.map +1 -0
- package/dist/lib/broadcast.js +45 -0
- package/dist/lib/broadcast.js.map +1 -0
- package/dist/lib/host-adapter.d.ts +72 -0
- package/dist/lib/host-adapter.d.ts.map +1 -0
- package/dist/lib/host-adapter.js +193 -0
- package/dist/lib/host-adapter.js.map +1 -0
- package/dist/lib/host-detect.d.ts +60 -0
- package/dist/lib/host-detect.d.ts.map +1 -0
- package/dist/lib/host-detect.js +479 -0
- package/dist/lib/host-detect.js.map +1 -0
- package/dist/lib/oxp-url.d.ts +34 -0
- package/dist/lib/oxp-url.d.ts.map +1 -0
- package/dist/lib/oxp-url.js +74 -0
- package/dist/lib/oxp-url.js.map +1 -0
- package/dist/lib/vsx-install.d.ts +41 -0
- package/dist/lib/vsx-install.d.ts.map +1 -0
- package/dist/lib/vsx-install.js +74 -0
- package/dist/lib/vsx-install.js.map +1 -0
- package/dist/util.d.ts +10 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +64 -0
- package/dist/util.js.map +1 -0
- package/package.json +48 -0
- package/templates/hello-code/README.md +39 -0
- package/templates/hello-code/oxp.json +14 -0
- package/templates/hello-code/package.json +18 -0
- package/templates/hello-code/src/extension.ts +17 -0
- package/templates/hello-code/tsconfig.json +20 -0
- package/templates/hello-html/README.md +18 -0
- package/templates/hello-html/oxp.json +14 -0
- package/templates/hello-html/ui/index.html +43 -0
- package/templates/hello-rust/Cargo.toml +19 -0
- package/templates/hello-rust/README.md +50 -0
- package/templates/hello-rust/oxp.json +22 -0
- package/templates/hello-rust/src/lib.rs +111 -0
- package/templates/hello-rust/wit/deps/oxp-host/oxp-host.wit +136 -0
- package/templates/hello-rust/wit/extension.wit +71 -0
- package/templates/hello-tree/README.md +15 -0
- package/templates/hello-tree/oxp.json +14 -0
- package/templates/hello-tree/ui/tree.json +27 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VSX install adapter — handles `oxp install @publisher/slug` for entries
|
|
3
|
+
* that are mirrored from Open VSX rather than published natively to OXP.
|
|
4
|
+
*
|
|
5
|
+
* Strategy:
|
|
6
|
+
* 1. Caller pre-fetched `/api/v1/extensions/<publisher>/<slug>` and
|
|
7
|
+
* saw a `vsx` block in the response → call `installVsx()`.
|
|
8
|
+
* 2. We detect the user's VS Code-family IDEs (already done by the
|
|
9
|
+
* install command) and run `<cli> --install-extension <ns>.<name>`
|
|
10
|
+
* against each one. The IDE's own install path handles VSIX fetch,
|
|
11
|
+
* signature checks (or lack thereof), and registration.
|
|
12
|
+
* 3. We DO NOT touch the OXP shared host-store — VSX extensions are
|
|
13
|
+
* not OXP wasm components and have no oxp.json runtime contract.
|
|
14
|
+
*
|
|
15
|
+
* This keeps the CLI honest: VSX entries are explicitly delegated to
|
|
16
|
+
* the IDE, and we surface that in CLI output so the user knows what
|
|
17
|
+
* happened.
|
|
18
|
+
*/
|
|
19
|
+
import { spawnSync } from "node:child_process";
|
|
20
|
+
/**
|
|
21
|
+
* Run `<cliPath> --install-extension <namespace>.<name>` against every
|
|
22
|
+
* detected vscode-family host. JetBrains/Zed/Piye are reported as
|
|
23
|
+
* skipped since they have no compatible install command for VSIX.
|
|
24
|
+
*/
|
|
25
|
+
export function installVsx(target, hosts) {
|
|
26
|
+
const ext = `${target.namespace}.${target.name}`;
|
|
27
|
+
const out = [];
|
|
28
|
+
for (const host of hosts) {
|
|
29
|
+
if (host.family !== "vscode") {
|
|
30
|
+
out.push({
|
|
31
|
+
host,
|
|
32
|
+
status: "skipped",
|
|
33
|
+
reason: `family ${host.family} can't install VSIX via --install-extension`,
|
|
34
|
+
});
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (!host.cliPath) {
|
|
38
|
+
out.push({
|
|
39
|
+
host,
|
|
40
|
+
status: "skipped",
|
|
41
|
+
reason: "no CLI launcher on PATH",
|
|
42
|
+
});
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const res = spawnSync(host.cliPath, ["--install-extension", ext], {
|
|
46
|
+
encoding: "utf8",
|
|
47
|
+
// Cap runtime so a hung IDE process can't block the CLI forever.
|
|
48
|
+
timeout: 60_000,
|
|
49
|
+
});
|
|
50
|
+
const tail = (res.stdout || "") + (res.stderr || "");
|
|
51
|
+
const trimmed = tail.length > 400 ? tail.slice(-400) : tail;
|
|
52
|
+
if (res.error) {
|
|
53
|
+
out.push({
|
|
54
|
+
host,
|
|
55
|
+
status: "failed",
|
|
56
|
+
reason: res.error.message,
|
|
57
|
+
output: trimmed,
|
|
58
|
+
});
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (res.status !== 0) {
|
|
62
|
+
out.push({
|
|
63
|
+
host,
|
|
64
|
+
status: "failed",
|
|
65
|
+
reason: `${host.cliPath} exited with code ${res.status}`,
|
|
66
|
+
output: trimmed,
|
|
67
|
+
});
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
out.push({ host, status: "ok", output: trimmed });
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=vsx-install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vsx-install.js","sourceRoot":"","sources":["../../src/lib/vsx-install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAqB/C;;;;GAIG;AACH,MAAM,UAAU,UAAU,CACxB,MAAwB,EACxB,KAAqB;IAErB,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;IACjD,MAAM,GAAG,GAAuB,EAAE,CAAC;IACnC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC7B,GAAG,CAAC,IAAI,CAAC;gBACP,IAAI;gBACJ,MAAM,EAAE,SAAS;gBACjB,MAAM,EAAE,UAAU,IAAI,CAAC,MAAM,6CAA6C;aAC3E,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,GAAG,CAAC,IAAI,CAAC;gBACP,IAAI;gBACJ,MAAM,EAAE,SAAS;gBACjB,MAAM,EAAE,yBAAyB;aAClC,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QACD,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,qBAAqB,EAAE,GAAG,CAAC,EAAE;YAChE,QAAQ,EAAE,MAAM;YAChB,iEAAiE;YACjE,OAAO,EAAE,MAAM;SAChB,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;QACrD,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC5D,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;YACd,GAAG,CAAC,IAAI,CAAC;gBACP,IAAI;gBACJ,MAAM,EAAE,QAAQ;gBAChB,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,OAAO;gBACzB,MAAM,EAAE,OAAO;aAChB,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrB,GAAG,CAAC,IAAI,CAAC;gBACP,IAAI;gBACJ,MAAM,EAAE,QAAQ;gBAChB,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,qBAAqB,GAAG,CAAC,MAAM,EAAE;gBACxD,MAAM,EAAE,OAAO;aAChB,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
package/dist/util.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Shared helpers for OXP CLI commands. */
|
|
2
|
+
export declare function oxpHome(): string;
|
|
3
|
+
export declare function registryUrl(): string;
|
|
4
|
+
export declare function readCredentials(): Promise<string | null>;
|
|
5
|
+
export declare function writeCredentials(token: string): Promise<void>;
|
|
6
|
+
/** Find the project root (first ancestor containing oxp.json). */
|
|
7
|
+
export declare function findProjectRoot(start: string): Promise<string | null>;
|
|
8
|
+
export declare function info(msg: string): void;
|
|
9
|
+
export declare function fail(msg: string): never;
|
|
10
|
+
//# sourceMappingURL=util.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAM3C,wBAAgB,OAAO,IAAI,MAAM,CAEhC;AAKD,wBAAgB,WAAW,IAAI,MAAM,CAgBpC;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAO9D;AAED,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAKnE;AAED,kEAAkE;AAClE,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAa3E;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEtC;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAGvC"}
|
package/dist/util.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** Shared helpers for OXP CLI commands. */
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
export function oxpHome() {
|
|
6
|
+
return process.env.OXP_HOME ?? join(homedir(), ".oxp");
|
|
7
|
+
}
|
|
8
|
+
const DEFAULT_REGISTRY = "http://localhost:3000";
|
|
9
|
+
let warnedDefaultRegistry = false;
|
|
10
|
+
export function registryUrl() {
|
|
11
|
+
const fromEnv = process.env.OXP_REGISTRY;
|
|
12
|
+
if (fromEnv && fromEnv.trim().length > 0) {
|
|
13
|
+
return fromEnv.replace(/\/+$/, "");
|
|
14
|
+
}
|
|
15
|
+
// Localhost default is convenient for self-host / dev, but if you're trying
|
|
16
|
+
// to publish or install against the public registry it's almost always a
|
|
17
|
+
// mistake. Warn once per process so it's visible without becoming spammy.
|
|
18
|
+
if (!warnedDefaultRegistry) {
|
|
19
|
+
warnedDefaultRegistry = true;
|
|
20
|
+
process.stderr.write(`[oxp] OXP_REGISTRY not set — defaulting to ${DEFAULT_REGISTRY}. ` +
|
|
21
|
+
`Set OXP_REGISTRY=https://… to target a different host.\n`);
|
|
22
|
+
}
|
|
23
|
+
return DEFAULT_REGISTRY;
|
|
24
|
+
}
|
|
25
|
+
export async function readCredentials() {
|
|
26
|
+
try {
|
|
27
|
+
const buf = await fs.readFile(join(oxpHome(), "credentials"), "utf8");
|
|
28
|
+
return buf.trim() || null;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export async function writeCredentials(token) {
|
|
35
|
+
const dir = oxpHome();
|
|
36
|
+
await fs.mkdir(dir, { recursive: true, mode: 0o700 });
|
|
37
|
+
const path = join(dir, "credentials");
|
|
38
|
+
await fs.writeFile(path, token + "\n", { mode: 0o600 });
|
|
39
|
+
}
|
|
40
|
+
/** Find the project root (first ancestor containing oxp.json). */
|
|
41
|
+
export async function findProjectRoot(start) {
|
|
42
|
+
let cur = resolve(start);
|
|
43
|
+
while (true) {
|
|
44
|
+
try {
|
|
45
|
+
await fs.access(join(cur, "oxp.json"));
|
|
46
|
+
return cur;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// continue
|
|
50
|
+
}
|
|
51
|
+
const parent = dirname(cur);
|
|
52
|
+
if (parent === cur)
|
|
53
|
+
return null;
|
|
54
|
+
cur = parent;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export function info(msg) {
|
|
58
|
+
process.stdout.write(msg + "\n");
|
|
59
|
+
}
|
|
60
|
+
export function fail(msg) {
|
|
61
|
+
process.stderr.write(`oxp: ${msg}\n`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=util.js.map
|
package/dist/util.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@oxprotocol/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Command-line tool for the Open eXtensions Protocol.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"oxp": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./create": {
|
|
15
|
+
"types": "./dist/commands/create.d.ts",
|
|
16
|
+
"default": "./dist/commands/create.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"templates",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"chokidar": "^4.0.3",
|
|
29
|
+
"ws": "^8.18.0",
|
|
30
|
+
"@oxprotocol/bundle": "0.1.0",
|
|
31
|
+
"@oxprotocol/host-core": "0.1.0",
|
|
32
|
+
"@oxprotocol/wit": "0.1.0",
|
|
33
|
+
"@oxprotocol/types": "0.1.0",
|
|
34
|
+
"@oxprotocol/schema": "0.1.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^22.10.2",
|
|
38
|
+
"@types/ws": "^8.5.13",
|
|
39
|
+
"tsx": "^4.20.6",
|
|
40
|
+
"typescript": "5.9.3",
|
|
41
|
+
"vitest": "^2.1.8"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsc -p tsconfig.json && chmod +x dist/cli.js",
|
|
45
|
+
"dev": "tsx src/cli.ts",
|
|
46
|
+
"test": "vitest run --passWithNoTests"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# __DISPLAY_NAME__
|
|
2
|
+
|
|
3
|
+
OXP code extension scaffolded from the `hello-code` template.
|
|
4
|
+
|
|
5
|
+
## Develop
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pnpm install
|
|
9
|
+
pnpm dev # start the dev loop (watch + hot-reload)
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Publish
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
pnpm pack # build → dist/__SLUG__-0.0.1.oxp
|
|
16
|
+
oxp publish # upload to the registry
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Capabilities
|
|
20
|
+
|
|
21
|
+
Declare what your extension needs in `oxp.json` → `permissions`:
|
|
22
|
+
|
|
23
|
+
- `read-clipboard`
|
|
24
|
+
- `write-clipboard`
|
|
25
|
+
- `storage:local`
|
|
26
|
+
- `network:<domain>` (e.g. `network:api.example.com`, or `network:*`)
|
|
27
|
+
|
|
28
|
+
Use them via the `host` API:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { defineExtension, clipboard, net } from "@oxprotocol/sdk";
|
|
32
|
+
|
|
33
|
+
export default defineExtension({
|
|
34
|
+
async activate(host) {
|
|
35
|
+
const cb = clipboard(host);
|
|
36
|
+
await cb.write("hello");
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"specVersion": "1",
|
|
3
|
+
"id": "@__PUBLISHER__/__SLUG__",
|
|
4
|
+
"publisher": "__PUBLISHER__",
|
|
5
|
+
"version": "0.0.1",
|
|
6
|
+
"displayName": "__DISPLAY_NAME__",
|
|
7
|
+
"description": "An OXP code extension built with @oxprotocol/sdk.",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"categories": ["other"],
|
|
10
|
+
"engines": { "oxp": "^1.0.0" },
|
|
11
|
+
"main": { "entry": "dist/extension.js" },
|
|
12
|
+
"ui": { "preferredSurface": "panel" },
|
|
13
|
+
"permissions": []
|
|
14
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@__PUBLISHER__/__SLUG__",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc -p tsconfig.json",
|
|
8
|
+
"dev": "oxp dev",
|
|
9
|
+
"pack": "pnpm build && oxp pack",
|
|
10
|
+
"publish:oxp": "pnpm pack && oxp publish"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@oxprotocol/sdk": "^0.1.0"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"typescript": "^5.6.0"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineExtension } from "@oxprotocol/sdk";
|
|
2
|
+
|
|
3
|
+
export default defineExtension({
|
|
4
|
+
async activate(host) {
|
|
5
|
+
host.log("info", "hello from @__PUBLISHER__/__SLUG__");
|
|
6
|
+
host.renderHtml(`
|
|
7
|
+
<div style="font-family:system-ui;padding:2rem;color:#f8fafc;background:#060a13;min-height:100vh;display:grid;place-items:center">
|
|
8
|
+
<div style="border:1px solid rgba(125,211,252,.3);padding:2rem;border-radius:8px;text-align:center">
|
|
9
|
+
<h1 style="margin:0 0 .5rem;font-size:1.5rem">__DISPLAY_NAME__</h1>
|
|
10
|
+
<p style="margin:0;opacity:.6;font-family:ui-monospace,monospace;font-size:.75rem">
|
|
11
|
+
@__PUBLISHER__/__SLUG__
|
|
12
|
+
</p>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
`);
|
|
16
|
+
},
|
|
17
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ES2022",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"lib": [
|
|
7
|
+
"ES2022",
|
|
8
|
+
"DOM",
|
|
9
|
+
"WebWorker"
|
|
10
|
+
],
|
|
11
|
+
"strict": true,
|
|
12
|
+
"outDir": "dist",
|
|
13
|
+
"rootDir": "src",
|
|
14
|
+
"declaration": false,
|
|
15
|
+
"skipLibCheck": true
|
|
16
|
+
},
|
|
17
|
+
"include": [
|
|
18
|
+
"src"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# __DISPLAY_NAME__
|
|
2
|
+
|
|
3
|
+
A minimal OXP extension scaffolded by `oxp create`.
|
|
4
|
+
|
|
5
|
+
## Build
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
oxp pack
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Writes `dist/__SLUG__-<version>.oxp` and `dist/__SLUG__-<version>.sig.json`.
|
|
12
|
+
|
|
13
|
+
## Publish
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
oxp login # paste an API token from the registry
|
|
17
|
+
oxp publish dist/__SLUG__-*.oxp
|
|
18
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"specVersion": "1",
|
|
3
|
+
"id": "@__PUBLISHER__/__SLUG__",
|
|
4
|
+
"publisher": "__PUBLISHER__",
|
|
5
|
+
"version": "0.0.1",
|
|
6
|
+
"displayName": "__DISPLAY_NAME__",
|
|
7
|
+
"description": "Hello, OXP world.",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"categories": ["other"],
|
|
10
|
+
"engines": { "oxp": "^1.0.0" },
|
|
11
|
+
"main": { "ui": "ui/index.html" },
|
|
12
|
+
"ui": { "components": "oxp-ui-only", "preferredSurface": "panel" },
|
|
13
|
+
"permissions": []
|
|
14
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<title>__DISPLAY_NAME__</title>
|
|
6
|
+
<style>
|
|
7
|
+
:root {
|
|
8
|
+
color-scheme: dark light;
|
|
9
|
+
}
|
|
10
|
+
body {
|
|
11
|
+
margin: 0;
|
|
12
|
+
min-height: 100vh;
|
|
13
|
+
display: grid;
|
|
14
|
+
place-items: center;
|
|
15
|
+
font-family: -apple-system, system-ui, sans-serif;
|
|
16
|
+
background: #060a13;
|
|
17
|
+
color: #f8fafc;
|
|
18
|
+
}
|
|
19
|
+
.card {
|
|
20
|
+
border: 1px solid rgba(125, 211, 252, 0.3);
|
|
21
|
+
padding: 2rem 2.5rem;
|
|
22
|
+
border-radius: 8px;
|
|
23
|
+
text-align: center;
|
|
24
|
+
}
|
|
25
|
+
h1 {
|
|
26
|
+
margin: 0 0 0.5rem;
|
|
27
|
+
font-size: 1.75rem;
|
|
28
|
+
}
|
|
29
|
+
p {
|
|
30
|
+
margin: 0;
|
|
31
|
+
opacity: 0.6;
|
|
32
|
+
font-family: ui-monospace, monospace;
|
|
33
|
+
font-size: 0.75rem;
|
|
34
|
+
}
|
|
35
|
+
</style>
|
|
36
|
+
</head>
|
|
37
|
+
<body>
|
|
38
|
+
<div class="card">
|
|
39
|
+
<h1>Hello, OXP world.</h1>
|
|
40
|
+
<p>@__PUBLISHER__/__SLUG__</p>
|
|
41
|
+
</div>
|
|
42
|
+
</body>
|
|
43
|
+
</html>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "__SLUG_UNDERSCORED__"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
publish = false
|
|
6
|
+
|
|
7
|
+
[lib]
|
|
8
|
+
crate-type = ["cdylib"]
|
|
9
|
+
|
|
10
|
+
[dependencies]
|
|
11
|
+
wit-bindgen = "0.36"
|
|
12
|
+
|
|
13
|
+
[profile.release]
|
|
14
|
+
# These flags shrink the resulting .wasm dramatically. Hello-world
|
|
15
|
+
# components built with this profile land under 50 KB.
|
|
16
|
+
opt-level = "s"
|
|
17
|
+
lto = true
|
|
18
|
+
strip = true
|
|
19
|
+
codegen-units = 1
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# __DISPLAY_NAME__
|
|
2
|
+
|
|
3
|
+
A `component-v1` OXP extension written in Rust, targeting the
|
|
4
|
+
[WASI Preview 2](https://github.com/WebAssembly/WASI) component model
|
|
5
|
+
and the canonical `oxp:extension@0.1.0` world.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
rustup target add wasm32-wasip2
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Build & pack
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
oxp pack
|
|
17
|
+
# → runs `scripts.build` from oxp.json (cargo build + copy)
|
|
18
|
+
# → writes dist/__SLUG__-0.0.1.oxp
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
If you want to skip the build hook (e.g. you've built manually):
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
oxp pack --no-build
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## What does it do?
|
|
28
|
+
|
|
29
|
+
The `activate` lifecycle export logs a single line via the always-on
|
|
30
|
+
`oxp:host/log` interface. `deactivate` logs `"goodbye"`. That's the
|
|
31
|
+
whole extension — it's deliberately minimal so you can verify the
|
|
32
|
+
toolchain end-to-end before adding logic.
|
|
33
|
+
|
|
34
|
+
## Layout
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
oxp.json OXP manifest (component-v1, pins oxp:extension@0.1.0)
|
|
38
|
+
Cargo.toml Rust crate definition (cdylib)
|
|
39
|
+
src/lib.rs Component implementation (lifecycle + ui-handler + command-handler)
|
|
40
|
+
wit/ WIT contract — extension.wit + deps/oxp-host/oxp-host.wit
|
|
41
|
+
build/ Built .wasm artefact — packed into the bundle
|
|
42
|
+
dist/ Output of `oxp pack` (.oxp + signature); gitignored
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The `wit/` directory is shipped inline so the project builds without
|
|
46
|
+
any monorepo dependency. If the upstream `oxp:extension` world ever
|
|
47
|
+
changes you'll need to refresh the files and update `oxp.json#wit.sha256`
|
|
48
|
+
to match — `oxp create --template hello-rust` always emits the current
|
|
49
|
+
hash, so the easy fix is to scaffold a fresh project and copy the new
|
|
50
|
+
WIT files + sha across.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"specVersion": "1",
|
|
3
|
+
"id": "@__PUBLISHER__/__SLUG__",
|
|
4
|
+
"publisher": "__PUBLISHER__",
|
|
5
|
+
"version": "0.0.1",
|
|
6
|
+
"displayName": "__DISPLAY_NAME__",
|
|
7
|
+
"description": "OXP component-v1 extension written in Rust.",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"categories": ["other"],
|
|
10
|
+
"engines": { "oxp": "^1.0.0" },
|
|
11
|
+
"kind": "component-v1",
|
|
12
|
+
"main": { "wasm": "build/__SLUG_UNDERSCORED__.wasm" },
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "cargo build --release --target wasm32-wasip2 && mkdir -p build && cp target/wasm32-wasip2/release/__SLUG_UNDERSCORED__.wasm build/"
|
|
15
|
+
},
|
|
16
|
+
"permissions": [],
|
|
17
|
+
"wit": {
|
|
18
|
+
"package": "oxp:extension",
|
|
19
|
+
"version": "0.1.0",
|
|
20
|
+
"sha256": "__WIT_SHA__"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// Hello-world OXP extension component, written in Rust.
|
|
2
|
+
//
|
|
3
|
+
// Exercises:
|
|
4
|
+
// * activate / deactivate lifecycle exports
|
|
5
|
+
// * a single always-on host import: oxp:host/log
|
|
6
|
+
//
|
|
7
|
+
// To build:
|
|
8
|
+
// rustup target add wasm32-wasip2
|
|
9
|
+
// cargo build --release --target wasm32-wasip2
|
|
10
|
+
// mkdir -p build && cp target/wasm32-wasip2/release/__SLUG_UNDERSCORED__.wasm build/
|
|
11
|
+
// oxp pack
|
|
12
|
+
|
|
13
|
+
wit_bindgen::generate!({
|
|
14
|
+
world: "extension",
|
|
15
|
+
path: "wit",
|
|
16
|
+
generate_all,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
use exports::oxp::extension::command_handler::Guest as CommandHandlerGuest;
|
|
20
|
+
use exports::oxp::extension::lifecycle::{ActivateCtx, Guest as LifecycleGuest};
|
|
21
|
+
use exports::oxp::extension::ui_handler::{EventError, Guest as UiHandlerGuest};
|
|
22
|
+
use oxp::host::log::{log, Level};
|
|
23
|
+
|
|
24
|
+
struct Component;
|
|
25
|
+
|
|
26
|
+
impl LifecycleGuest for Component {
|
|
27
|
+
fn activate(ctx: ActivateCtx) -> Result<(), String> {
|
|
28
|
+
log(
|
|
29
|
+
Level::Info,
|
|
30
|
+
&format!(
|
|
31
|
+
"hello from {} v{} on host {} ({})",
|
|
32
|
+
ctx.extension_id, ctx.version, ctx.host, ctx.host_version
|
|
33
|
+
),
|
|
34
|
+
);
|
|
35
|
+
Ok(())
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fn deactivate() -> Result<(), String> {
|
|
39
|
+
log(Level::Info, "goodbye");
|
|
40
|
+
Ok(())
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
impl UiHandlerGuest for Component {
|
|
45
|
+
fn on_event(_event: Vec<u8>) -> Result<(), EventError> {
|
|
46
|
+
Ok(())
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
impl CommandHandlerGuest for Component {
|
|
51
|
+
fn on_command(id: String, args_json: String) -> Result<String, String> {
|
|
52
|
+
match id.as_str() {
|
|
53
|
+
// Replace this with your own command. The result must be valid JSON.
|
|
54
|
+
"hello.greet" => {
|
|
55
|
+
let name = parse_json_string_field(&args_json, "name")
|
|
56
|
+
.unwrap_or_else(|| "world".to_string());
|
|
57
|
+
Ok(format!("\"hello, {}!\"", json_escape(&name)))
|
|
58
|
+
}
|
|
59
|
+
other => Ok(format!("\"unhandled:{}\"", other)),
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Tiny zero-dependency JSON helpers so the template doesn't pull in serde
|
|
65
|
+
// just for one example. Replace with serde_json once your extension grows.
|
|
66
|
+
fn parse_json_string_field(json: &str, field: &str) -> Option<String> {
|
|
67
|
+
let needle = format!("\"{field}\"");
|
|
68
|
+
let start = json.find(&needle)? + needle.len();
|
|
69
|
+
let rest = &json[start..];
|
|
70
|
+
let colon = rest.find(':')? + 1;
|
|
71
|
+
let after_colon = &rest[colon..];
|
|
72
|
+
let q = after_colon.find('"')? + 1;
|
|
73
|
+
let body = &after_colon[q..];
|
|
74
|
+
let mut out = String::new();
|
|
75
|
+
let mut chars = body.chars();
|
|
76
|
+
while let Some(c) = chars.next() {
|
|
77
|
+
if c == '\\' {
|
|
78
|
+
match chars.next()? {
|
|
79
|
+
'n' => out.push('\n'),
|
|
80
|
+
't' => out.push('\t'),
|
|
81
|
+
'r' => out.push('\r'),
|
|
82
|
+
'"' => out.push('"'),
|
|
83
|
+
'\\' => out.push('\\'),
|
|
84
|
+
other => out.push(other),
|
|
85
|
+
}
|
|
86
|
+
} else if c == '"' {
|
|
87
|
+
return Some(out);
|
|
88
|
+
} else {
|
|
89
|
+
out.push(c);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
None
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
fn json_escape(s: &str) -> String {
|
|
96
|
+
let mut out = String::with_capacity(s.len());
|
|
97
|
+
for c in s.chars() {
|
|
98
|
+
match c {
|
|
99
|
+
'"' => out.push_str("\\\""),
|
|
100
|
+
'\\' => out.push_str("\\\\"),
|
|
101
|
+
'\n' => out.push_str("\\n"),
|
|
102
|
+
'\r' => out.push_str("\\r"),
|
|
103
|
+
'\t' => out.push_str("\\t"),
|
|
104
|
+
c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
|
|
105
|
+
c => out.push(c),
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
out
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export!(Component);
|