@nullplatform/plugin 0.0.2

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 ADDED
@@ -0,0 +1,85 @@
1
+ <h2 align="center">
2
+ <a href="https://nullplatform.com" target="blank_">
3
+ <img height="100" alt="Nullplatform" src="https://nullplatform.com/favicon/android-chrome-192x192.png" />
4
+ </a>
5
+ <br>
6
+ <br>
7
+ @nullplatform/plugin
8
+ <br>
9
+ </h2>
10
+
11
+ TypeScript SDK for building nullplatform plugins. Handles gRPC transport (HashiCorp go-plugin protocol), action routing, `--describe`, and `--publish`.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ bun add @nullplatform/plugin
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ### Scope plugin
22
+
23
+ ```typescript
24
+ import { defineScope } from "@nullplatform/plugin/scope";
25
+
26
+ defineScope({
27
+ name: "kubernetes-k3d",
28
+ version: "0.0.1",
29
+ category: "containers",
30
+ provider: "kubernetes",
31
+
32
+ schema: {
33
+ type: "object",
34
+ properties: {
35
+ memory: { type: "string", default: "2Gi" },
36
+ },
37
+ },
38
+
39
+ actions: {
40
+ "create-scope": {
41
+ input: { type: "object" },
42
+ handler: async (notification, emit) => {
43
+ // provision infrastructure
44
+ return { domain: "app.k3d.local" };
45
+ },
46
+ },
47
+ },
48
+ });
49
+ ```
50
+
51
+ ### Simple plugin (custom command)
52
+
53
+ ```typescript
54
+ import { createPlugin, registerManifest } from "@nullplatform/plugin";
55
+
56
+ registerManifest({ name: "my-plugin", version: "0.0.1", command_types: ["custom"] });
57
+
58
+ createPlugin({
59
+ async execute(req) {
60
+ return { success: true, data: { message: "done" } };
61
+ },
62
+ }).start();
63
+ ```
64
+
65
+ ## Subpath exports
66
+
67
+ | Import | Description |
68
+ |---|---|
69
+ | `@nullplatform/plugin` | Core: createPlugin, registerManifest, types |
70
+ | `@nullplatform/plugin/scope` | defineScope API |
71
+ | `@nullplatform/plugin/schema` | JSON Schema type inference |
72
+ | `@nullplatform/plugin/testing` | Test harness: startPluginProcess, factories |
73
+ | `@nullplatform/plugin/workflow` | Workflow integration (StreamingExecutionObserver) |
74
+
75
+ ## CLI integration
76
+
77
+ Plugins are scaffolded with `np plugin init` and managed via mise tasks:
78
+
79
+ ```bash
80
+ mise run dev # local dev with hot reload
81
+ mise run dev:agent # dev with np-agent attached
82
+ mise run test # run tests
83
+ mise run build # compile to standalone binary
84
+ mise run publish # publish to platform
85
+ ```
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@nullplatform/plugin",
3
+ "version": "0.0.2",
4
+ "type": "module",
5
+ "main": "src/index.ts",
6
+ "types": "src/index.ts",
7
+ "exports": {
8
+ ".": "./src/index.ts",
9
+ "./schema": "./src/schema.ts",
10
+ "./scope": "./src/scope/index.ts",
11
+ "./testing": "./src/testing/index.ts",
12
+ "./workflow": "./src/workflow.ts"
13
+ },
14
+ "dependencies": {
15
+ "@grpc/grpc-js": "^1.13.0",
16
+ "@grpc/proto-loader": "^0.7.0",
17
+ "yaml": "^2.7.0"
18
+ },
19
+ "files": [
20
+ "src"
21
+ ],
22
+ "publishConfig": {
23
+ "registry": "https://registry.npmjs.org"
24
+ },
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/nullplatform/plugin-libraries.git",
28
+ "directory": "js"
29
+ },
30
+ "license": "MIT",
31
+ "peerDependencies": {
32
+ "@nullplatform/workflow-engine": ">=0.0.1 <1.0.0",
33
+ "@nullplatform/workflow-engine-in-memory": ">=0.0.1 <1.0.0",
34
+ "@nullplatform/workflow-types": ">=0.0.1 <1.0.0"
35
+ },
36
+ "peerDependenciesMeta": {
37
+ "@nullplatform/workflow-engine": {
38
+ "optional": true
39
+ },
40
+ "@nullplatform/workflow-engine-in-memory": {
41
+ "optional": true
42
+ },
43
+ "@nullplatform/workflow-types": {
44
+ "optional": true
45
+ }
46
+ },
47
+ "devDependencies": {
48
+ "@types/bun": "^1.2.0",
49
+ "json-schema-to-ts": "^3.1.1"
50
+ }
51
+ }
package/src/api.ts ADDED
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Platform API helpers — typed wrappers around nullplatform REST API.
3
+ *
4
+ * Generic helpers usable by any plugin type (scope, service, etc.).
5
+ * Replicates what `np service workflow build-context` does in the Go CLI.
6
+ *
7
+ * Usage:
8
+ * import { api } from "@nullplatform/plugin";
9
+ *
10
+ * const scope = await api.scope("12345");
11
+ * const deployment = await api.deployment("67890");
12
+ * const providers = await api.providers(scope.nrn, ["cloud-providers"]);
13
+ */
14
+
15
+ // ─── Configuration ───────────────────────────────────────────────────
16
+
17
+ const TIMEOUT_MS = 30_000;
18
+
19
+ function getConfig() {
20
+ const apiUrl = process.env.NP_API_URL ?? "https://api.nullplatform.com";
21
+ const apiKey = process.env.NULLPLATFORM_APIKEY ?? "";
22
+ if (!apiKey) {
23
+ throw new Error("NULLPLATFORM_APIKEY environment variable is not set");
24
+ }
25
+ return { apiUrl, apiKey };
26
+ }
27
+
28
+ async function fetchJson<T>(path: string, params?: Record<string, string>): Promise<T> {
29
+ const { apiUrl, apiKey } = getConfig();
30
+ const url = new URL(path, apiUrl);
31
+ if (params) {
32
+ for (const [k, v] of Object.entries(params)) {
33
+ if (v !== undefined && v !== "") url.searchParams.set(k, v);
34
+ }
35
+ }
36
+
37
+ const controller = new AbortController();
38
+ const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);
39
+
40
+ try {
41
+ const res = await fetch(url.toString(), {
42
+ headers: {
43
+ Authorization: `Bearer ${apiKey}`,
44
+ Accept: "application/json",
45
+ },
46
+ signal: controller.signal,
47
+ });
48
+
49
+ if (!res.ok) {
50
+ const body = await res.text().catch(() => "");
51
+ throw new Error(`API ${res.status} ${res.statusText}: ${path}${body ? ` — ${body}` : ""}`);
52
+ }
53
+
54
+ return res.json() as Promise<T>;
55
+ } finally {
56
+ clearTimeout(timeoutId);
57
+ }
58
+ }
59
+
60
+ // ─── Entity types ────────────────────────────────────────────────────
61
+
62
+ export interface ScopeInfo {
63
+ id: string;
64
+ slug: string;
65
+ nrn: string;
66
+ domain: string;
67
+ asset_name: string;
68
+ current_active_deployment: string;
69
+ capabilities: Record<string, unknown>;
70
+ dimensions: Record<string, string>;
71
+ [key: string]: unknown;
72
+ }
73
+
74
+ export interface DeploymentInfo {
75
+ id: string;
76
+ status: string;
77
+ release_id: string;
78
+ strategy_data: {
79
+ desired_switched_traffic?: number;
80
+ };
81
+ [key: string]: unknown;
82
+ }
83
+
84
+ export interface ApplicationInfo {
85
+ id: string;
86
+ slug: string;
87
+ [key: string]: unknown;
88
+ }
89
+
90
+ export interface NamespaceInfo {
91
+ id: string;
92
+ slug: string;
93
+ [key: string]: unknown;
94
+ }
95
+
96
+ export interface AccountInfo {
97
+ id: string;
98
+ slug: string;
99
+ [key: string]: unknown;
100
+ }
101
+
102
+ export interface AssetInfo {
103
+ type: string;
104
+ url: string;
105
+ [key: string]: unknown;
106
+ }
107
+
108
+ export interface ParameterResult {
109
+ variable: string;
110
+ values: Array<{ value: string }>;
111
+ }
112
+
113
+ export type ProvidersMap = Record<string, Record<string, unknown>>;
114
+
115
+ // ─── API functions ───────────────────────────────────────────────────
116
+
117
+ export async function scope(id: string): Promise<ScopeInfo> {
118
+ return fetchJson<ScopeInfo>(`/scope/${id}`);
119
+ }
120
+
121
+ export async function deployment(id: string): Promise<DeploymentInfo> {
122
+ return fetchJson<DeploymentInfo>(`/deployment/${id}`);
123
+ }
124
+
125
+ export async function application(id: string): Promise<ApplicationInfo> {
126
+ return fetchJson<ApplicationInfo>(`/application/${id}`);
127
+ }
128
+
129
+ export async function namespace(id: string): Promise<NamespaceInfo> {
130
+ return fetchJson<NamespaceInfo>(`/namespace/${id}`);
131
+ }
132
+
133
+ export async function account(id: string): Promise<AccountInfo> {
134
+ return fetchJson<AccountInfo>(`/account/${id}`);
135
+ }
136
+
137
+ /**
138
+ * Fetch asset by following the release → build → asset chain.
139
+ * Mirrors the Go CLI's fetchAndStoreAsset logic.
140
+ */
141
+ export async function asset(releaseId: string, assetName: string): Promise<AssetInfo> {
142
+ const release = await fetchJson<{ build_id: string }>(`/release/${releaseId}`);
143
+ const result = await fetchJson<{ results: AssetInfo[] }>("/asset", {
144
+ build_id: release.build_id,
145
+ name: assetName,
146
+ });
147
+
148
+ if (!result.results?.length) {
149
+ throw new Error(`No asset found for build_id=${release.build_id}, name=${assetName}`);
150
+ }
151
+
152
+ return result.results[0];
153
+ }
154
+
155
+ /**
156
+ * Fetch parameters for a scope by NRN.
157
+ * Mirrors --include-secrets flag from build-context.
158
+ */
159
+ export async function parameters(
160
+ nrn: string,
161
+ opts: { includeSecrets?: boolean } = {},
162
+ ): Promise<Record<string, string>> {
163
+ const result = await fetchJson<{ results: ParameterResult[] }>("/parameter", {
164
+ nrn,
165
+ show_secret_values: String(opts.includeSecrets ?? false),
166
+ interpolate: "true",
167
+ limit: "500",
168
+ });
169
+
170
+ const params: Record<string, string> = {};
171
+ for (const p of result.results ?? []) {
172
+ if (p.variable && p.values?.[0]?.value !== undefined) {
173
+ params[p.variable] = p.values[0].value;
174
+ }
175
+ }
176
+ return params;
177
+ }
178
+
179
+ /**
180
+ * Fetch providers by NRN and categories.
181
+ * Mirrors --provider-categories flag from build-context.
182
+ * Returns providers keyed by category slug with their attributes.
183
+ */
184
+ export async function providers(
185
+ nrn: string,
186
+ categories: string[],
187
+ dimensions?: Record<string, string>,
188
+ ): Promise<ProvidersMap> {
189
+ const dimensionFilter = dimensions
190
+ ? Object.entries(dimensions).map(([k, v]) => `${k}:${v}`).join(",")
191
+ : undefined;
192
+
193
+ const result = await fetchJson<{
194
+ results: Array<{ category: string; attributes: Record<string, unknown> }>;
195
+ }>("/provider", {
196
+ nrn,
197
+ categories: categories.join(","),
198
+ ...(dimensionFilter ? { dimensions: dimensionFilter } : {}),
199
+ });
200
+
201
+ const map: ProvidersMap = {};
202
+ for (const provider of result.results ?? []) {
203
+ map[provider.category] = provider.attributes ?? {};
204
+ }
205
+ return map;
206
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * createPlugin() — the public entry point for building np-agent plugins.
3
+ *
4
+ * Usage:
5
+ * createPlugin({
6
+ * async execute(req) {
7
+ * // do work
8
+ * return { success: true, data: { ... } };
9
+ * }
10
+ * }).start();
11
+ */
12
+
13
+ import type { PluginHandler, Plugin } from "./types";
14
+ import { loadManifest } from "./internal/manifest";
15
+ import { startGrpcServer } from "./internal/grpc-server";
16
+
17
+ export function createPlugin(handler: PluginHandler): Plugin {
18
+ return {
19
+ start() {
20
+ const manifest = loadManifest();
21
+ startGrpcServer(handler, manifest);
22
+ },
23
+ };
24
+ }
package/src/index.ts ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @nullplatform/plugin — gRPC transport for np-agent plugins.
3
+ *
4
+ * This SDK is ONLY the transport layer. It handles:
5
+ * - go-plugin handshake
6
+ * - gRPC Execute/Capabilities/Health/Version
7
+ * - Plugin manifest loading
8
+ *
9
+ * For progress tracking, use @nullplatform/workflow.
10
+ * For orchestration, use @nullplatform/workflow.
11
+ * They compose — a plugin CAN use the workflow SDK, but doesn't have to.
12
+ */
13
+
14
+ export { createPlugin } from "./create-plugin";
15
+ export { registerManifest } from "./internal/manifest";
16
+
17
+ export type {
18
+ PluginHandler,
19
+ Plugin,
20
+ ExecuteRequest,
21
+ ExecuteResult,
22
+ ExecuteOutput,
23
+ PluginManifest,
24
+ } from "./types";
25
+
26
+ export * as api from "./api";
27
+
28
+ export type { NpJSONSchema, NpKeywords } from "./schema";
29
+ export type { infer as InferSchema } from "./schema";