@kb-labs/marketplace-registry-entry 2.96.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 ADDED
@@ -0,0 +1,5 @@
1
+ # @kb-labs/marketplace-registry-entry
2
+
3
+ > Part of [KB Labs](https://github.com/KirillBaranov/kb-labs) ecosystem.
4
+
5
+ V3 plugin manifest and CLI commands for the marketplace registry plugin.
@@ -0,0 +1,12 @@
1
+ import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
2
+ import { CLIInput } from '@kb-labs/sdk';
3
+
4
+ interface DeprecateFlags {
5
+ message?: string;
6
+ json?: boolean;
7
+ }
8
+ declare const _default: _kb_labs_shared_command_kit.CommandHandlerV3<unknown, CLIInput<DeprecateFlags>, {
9
+ exitCode: number;
10
+ }>;
11
+
12
+ export { _default as default };
@@ -0,0 +1,146 @@
1
+ import { defineCommand, validationError, handleError, useEnv } from '@kb-labs/sdk';
2
+ import { readFile, writeFile, chmod } from 'fs/promises';
3
+ import { join } from 'path';
4
+ import { homedir } from 'os';
5
+
6
+ // src/commands/deprecate.ts
7
+ var CREDENTIALS_PATH = join(homedir(), ".kb", "credentials.json");
8
+ var REGISTRY_GATEWAY_PREFIX = "/api/v1/registry";
9
+ var FETCH_TIMEOUT_MS = 6e4;
10
+ var EXPIRY_BUFFER_MS = 6e4;
11
+ async function resolveAuth() {
12
+ const envToken = useEnv("KB_REGISTRY_TOKEN");
13
+ const envRegUrl = useEnv("KB_REGISTRY_URL");
14
+ const envGateway = useEnv("KB_GATEWAY_URL");
15
+ const envHandle = useEnv("KB_REGISTRY_AUTHOR_HANDLE");
16
+ if (envToken && envRegUrl) {
17
+ return { token: envToken, baseUrl: envRegUrl.replace(/\/$/, ""), handle: envHandle ?? "" };
18
+ }
19
+ if (envToken && envGateway) {
20
+ return {
21
+ token: envToken,
22
+ baseUrl: `${envGateway.replace(/\/$/, "")}${REGISTRY_GATEWAY_PREFIX}`,
23
+ handle: envHandle ?? ""
24
+ };
25
+ }
26
+ let creds;
27
+ try {
28
+ creds = JSON.parse(await readFile(CREDENTIALS_PATH, "utf-8"));
29
+ } catch {
30
+ throw new Error(
31
+ "Not authenticated. Run:\n kb auth create-service-account --gateway-url <url> --name <name> --namespace-id <ns>\n kb auth login --gateway-url <url> --client-id <id> --client-secret <secret>"
32
+ );
33
+ }
34
+ if (Date.now() >= creds.expiresAt - EXPIRY_BUFFER_MS) {
35
+ const res = await fetch(`${creds.gatewayUrl}/auth/refresh`, {
36
+ method: "POST",
37
+ headers: { "Content-Type": "application/json" },
38
+ body: JSON.stringify({ refreshToken: creds.refreshToken })
39
+ });
40
+ if (!res.ok) {
41
+ throw new Error("Session expired. Run: kb auth login --gateway-url <url> --client-id <id> --client-secret <secret>");
42
+ }
43
+ const refreshed = await res.json();
44
+ creds = {
45
+ ...creds,
46
+ accessToken: refreshed.accessToken,
47
+ refreshToken: refreshed.refreshToken,
48
+ expiresAt: Date.now() + refreshed.expiresIn * 1e3
49
+ };
50
+ await writeFile(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), "utf-8");
51
+ await chmod(CREDENTIALS_PATH, 384);
52
+ }
53
+ if (!creds.handle) {
54
+ throw new Error(
55
+ "Author handle not set in credentials. Re-run:\n kb auth login --gateway-url <url> --client-id <id> --client-secret <secret>"
56
+ );
57
+ }
58
+ const baseUrl = envRegUrl ? envRegUrl.replace(/\/$/, "") : `${creds.gatewayUrl.replace(/\/$/, "")}${REGISTRY_GATEWAY_PREFIX}`;
59
+ return { token: creds.accessToken, baseUrl, handle: creds.handle };
60
+ }
61
+ async function registryPost(path, body, token) {
62
+ const auth = await resolveAuth();
63
+ const url = `${auth.baseUrl}${path}`;
64
+ const controller = new AbortController();
65
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
66
+ try {
67
+ const res = await fetch(url, {
68
+ method: "POST",
69
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${auth.token}` },
70
+ body: JSON.stringify(body),
71
+ signal: controller.signal
72
+ });
73
+ if (!res.ok) {
74
+ const text = await res.text();
75
+ throw new Error(`Registry ${path} failed (${res.status}): ${text}`);
76
+ }
77
+ if (res.status === 204) {
78
+ return void 0;
79
+ }
80
+ return await res.json();
81
+ } catch (err) {
82
+ if (err.name === "AbortError") {
83
+ throw new Error("Registry request timed out. Is the gateway reachable?");
84
+ }
85
+ throw err;
86
+ } finally {
87
+ clearTimeout(timer);
88
+ }
89
+ }
90
+
91
+ // src/commands/deprecate.ts
92
+ var deprecate_default = defineCommand({
93
+ id: "marketplace:deprecate",
94
+ description: "Deprecate a package (removed from catalog, install shows warning)",
95
+ handler: {
96
+ async execute(ctx, input) {
97
+ const { flags = {}, argv = [] } = input;
98
+ const pkg = argv[0];
99
+ const t0 = Date.now();
100
+ if (!pkg) {
101
+ validationError(ctx, "Package spec is required", 'Usage: kb marketplace deprecate kb:handle/name [--message "reason"]', flags.json);
102
+ return { exitCode: 1 };
103
+ }
104
+ if (!pkg.startsWith("kb:")) {
105
+ validationError(ctx, "Package must be a kb: spec", "Example: kb:kirill/my-plugin", flags.json);
106
+ return { exitCode: 1 };
107
+ }
108
+ const sliced = pkg.slice(3);
109
+ const slashIdx = sliced.indexOf("/");
110
+ const handle = slashIdx > 0 ? sliced.slice(0, slashIdx) : "";
111
+ const name = slashIdx > 0 ? sliced.slice(slashIdx + 1) : "";
112
+ if (!handle || !name) {
113
+ validationError(ctx, "Invalid kb: spec \u2014 expected kb:handle/name", void 0, flags.json);
114
+ return { exitCode: 1 };
115
+ }
116
+ try {
117
+ await registryPost(`/packages/${handle}/${encodeURIComponent(name)}/deprecate`, { message: flags.message });
118
+ if (flags.json) {
119
+ ctx.ui?.json?.({ deprecated: true, pkg });
120
+ return { exitCode: 0 };
121
+ }
122
+ const items = [
123
+ "Hidden from the marketplace catalog",
124
+ "Existing installations still work but show a deprecation warning",
125
+ "New installs are allowed but show a warning"
126
+ ];
127
+ if (flags.message) {
128
+ items.push(`Message: ${flags.message}`);
129
+ }
130
+ ctx.ui?.success?.(`${pkg} deprecated`, {
131
+ sections: [{ header: "What this means", items }],
132
+ hint: "To block a specific version only, use kb marketplace yank instead",
133
+ timing: Date.now() - t0
134
+ });
135
+ return { exitCode: 0 };
136
+ } catch (err) {
137
+ handleError(ctx, err, flags.json);
138
+ return { exitCode: 1 };
139
+ }
140
+ }
141
+ }
142
+ });
143
+
144
+ export { deprecate_default as default };
145
+ //# sourceMappingURL=deprecate.js.map
146
+ //# sourceMappingURL=deprecate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/registry-http.ts","../../src/commands/deprecate.ts"],"names":[],"mappings":"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","file":"deprecate.js","sourcesContent":["/**\n * HTTP client for KB Labs Registry service.\n *\n * Credential provider chain (in priority order):\n * 1. KB_REGISTRY_URL + KB_REGISTRY_TOKEN — explicit registry URL (CI/CD, closed-loop mirror)\n * 2. KB_GATEWAY_URL + KB_REGISTRY_TOKEN — legacy CI/CD (derives registry URL from gateway)\n * 3. ~/.kb/credentials.json + KB_REGISTRY_URL — credentials file with explicit registry URL override\n * 4. ~/.kb/credentials.json fully — credentials file, derives registry URL from gatewayUrl\n * 5. Error — \"Run: kb auth login\"\n *\n * KB_REGISTRY_URL decouples the data layer (where the registry lives) from the execution\n * layer (which gateway runs the CLI command). Useful when the registry is behind a different\n * URL than the execution gateway — e.g. closed-loop mirrors or local dev pointing to prod registry.\n *\n * Token auto-refresh: if accessToken is within 60s of expiry, exchanges refreshToken\n * via POST /auth/refresh and overwrites ~/.kb/credentials.json (chmod 600).\n */\n\nimport { readFile, writeFile, chmod } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { useEnv } from '@kb-labs/sdk';\n\nconst CREDENTIALS_PATH = join(homedir(), '.kb', 'credentials.json');\nconst REGISTRY_GATEWAY_PREFIX = '/api/v1/registry';\nconst FETCH_TIMEOUT_MS = 60_000;\nconst EXPIRY_BUFFER_MS = 60_000;\n\ninterface ResolvedAuth {\n token: string;\n baseUrl: string; // full registry base URL including /api/v1/registry prefix\n handle: string;\n}\n\ninterface StoredCredentials {\n gatewayUrl: string;\n accessToken: string;\n refreshToken: string;\n expiresAt: number;\n handle?: string;\n namespaceId?: string;\n}\n\n/**\n * Resolve registry base URL from env vars only (used when a token is passed explicitly).\n * Priority: KB_REGISTRY_URL → KB_GATEWAY_URL → localhost fallback.\n */\nfunction resolveBaseUrlFromEnv(): string {\n const regUrl = useEnv('KB_REGISTRY_URL');\n const gateway = useEnv('KB_GATEWAY_URL');\n if (regUrl) { return regUrl.replace(/\\/$/, ''); }\n if (gateway) { return `${gateway.replace(/\\/$/, '')}${REGISTRY_GATEWAY_PREFIX}`; }\n return `http://127.0.0.1:4000${REGISTRY_GATEWAY_PREFIX}`;\n}\n\nasync function resolveAuth(): Promise<ResolvedAuth> {\n const envToken = useEnv('KB_REGISTRY_TOKEN');\n const envRegUrl = useEnv('KB_REGISTRY_URL'); // explicit registry base URL (data layer)\n const envGateway = useEnv('KB_GATEWAY_URL'); // legacy: derive registry URL from gateway\n const envHandle = useEnv('KB_REGISTRY_AUTHOR_HANDLE');\n\n // 1. Explicit registry URL + token (CI/CD, closed-loop mirror)\n if (envToken && envRegUrl) {\n return { token: envToken, baseUrl: envRegUrl.replace(/\\/$/, ''), handle: envHandle ?? '' };\n }\n\n // 2. Legacy: token + gateway URL → derive registry base URL\n if (envToken && envGateway) {\n return {\n token: envToken,\n baseUrl: `${envGateway.replace(/\\/$/, '')}${REGISTRY_GATEWAY_PREFIX}`,\n handle: envHandle ?? '',\n };\n }\n\n // 3–4. ~/.kb/credentials.json\n let creds: StoredCredentials;\n try {\n creds = JSON.parse(await readFile(CREDENTIALS_PATH, 'utf-8')) as StoredCredentials;\n } catch {\n throw new Error(\n 'Not authenticated. Run:\\n kb auth create-service-account --gateway-url <url> --name <name> --namespace-id <ns>\\n kb auth login --gateway-url <url> --client-id <id> --client-secret <secret>',\n );\n }\n\n // Auto-refresh if token is expired or close to expiry\n if (Date.now() >= creds.expiresAt - EXPIRY_BUFFER_MS) {\n const res = await fetch(`${creds.gatewayUrl}/auth/refresh`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ refreshToken: creds.refreshToken }),\n });\n if (!res.ok) {\n throw new Error('Session expired. Run: kb auth login --gateway-url <url> --client-id <id> --client-secret <secret>');\n }\n const refreshed = await res.json() as { accessToken: string; refreshToken: string; expiresIn: number };\n creds = {\n ...creds,\n accessToken: refreshed.accessToken,\n refreshToken: refreshed.refreshToken,\n expiresAt: Date.now() + refreshed.expiresIn * 1000,\n };\n await writeFile(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), 'utf-8');\n await chmod(CREDENTIALS_PATH, 0o600);\n }\n\n if (!creds.handle) {\n throw new Error(\n 'Author handle not set in credentials. Re-run:\\n kb auth login --gateway-url <url> --client-id <id> --client-secret <secret>',\n );\n }\n\n // 3. credentials + KB_REGISTRY_URL override (execution gateway ≠ registry URL)\n // 4. credentials fully (gatewayUrl used to derive registry base URL)\n const baseUrl = envRegUrl\n ? envRegUrl.replace(/\\/$/, '')\n : `${creds.gatewayUrl.replace(/\\/$/, '')}${REGISTRY_GATEWAY_PREFIX}`;\n\n return { token: creds.accessToken, baseUrl, handle: creds.handle };\n}\n\n/**\n * Resolve the author handle from the credential provider chain.\n * Used when the handle is needed explicitly (e.g. for URL path construction).\n */\nexport async function resolveRegistryHandle(): Promise<string> {\n const auth = await resolveAuth();\n return auth.handle;\n}\n\nexport async function registryPost<T = unknown>(\n path: string,\n body: Record<string, unknown>,\n token?: string,\n): Promise<T> {\n const auth = token\n ? { token, baseUrl: resolveBaseUrlFromEnv() }\n : await resolveAuth();\n\n const url = `${auth.baseUrl}${path}`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${auth.token}` },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Registry ${path} failed (${res.status}): ${text}`);\n }\n if (res.status === 204) { return undefined as T; }\n return await res.json() as T;\n } catch (err) {\n if ((err as Error).name === 'AbortError') {\n throw new Error('Registry request timed out. Is the gateway reachable?');\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function registryPostMultipart<T = unknown>(\n path: string,\n tarball: Buffer,\n fields: Record<string, string>,\n token?: string,\n authorHandle?: string,\n): Promise<T> {\n const auth = token\n ? {\n token,\n baseUrl: resolveBaseUrlFromEnv(),\n handle: authorHandle ?? useEnv('KB_REGISTRY_AUTHOR_HANDLE') ?? '',\n }\n : await resolveAuth();\n\n const url = `${auth.baseUrl}${path}`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const form = new FormData();\n form.append('tarball', new Blob([tarball], { type: 'application/octet-stream' }), 'package.tgz');\n for (const [k, v] of Object.entries(fields)) { form.append(k, v); }\n\n const handleHeader: Record<string, string> = auth.handle ? { 'X-Author-Handle': auth.handle } : {};\n\n const res = await fetch(url, {\n method: 'POST',\n headers: { Authorization: `Bearer ${auth.token}`, ...handleHeader },\n body: form,\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Registry publish failed (${res.status}): ${text}`);\n }\n return await res.json() as T;\n } catch (err) {\n if ((err as Error).name === 'AbortError') {\n throw new Error('Registry publish timed out.');\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function registryPatch<T = unknown>(\n path: string,\n body: Record<string, unknown>,\n token?: string,\n): Promise<T> {\n const auth = token\n ? { token, baseUrl: resolveBaseUrlFromEnv() }\n : await resolveAuth();\n\n const url = `${auth.baseUrl}${path}`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const res = await fetch(url, {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${auth.token}` },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Registry ${path} failed (${res.status}): ${text}`);\n }\n if (res.status === 204) { return undefined as T; }\n return await res.json() as T;\n } catch (err) {\n if ((err as Error).name === 'AbortError') {\n throw new Error('Registry request timed out.');\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n","import { defineCommand, handleError, validationError, type CLIInput, type PluginContextV3 } from '@kb-labs/sdk';\nimport { registryPost } from '../registry-http.js';\n\ninterface DeprecateFlags {\n message?: string;\n json?: boolean;\n}\n\nexport default defineCommand<unknown, CLIInput<DeprecateFlags>, { exitCode: number }>({\n id: 'marketplace:deprecate',\n description: 'Deprecate a package (removed from catalog, install shows warning)',\n\n handler: {\n async execute(ctx: PluginContextV3, input: CLIInput<DeprecateFlags>): Promise<{ exitCode: number }> {\n const { flags = {}, argv = [] } = input;\n const pkg = argv[0];\n const t0 = Date.now();\n\n if (!pkg) {\n validationError(ctx, 'Package spec is required', 'Usage: kb marketplace deprecate kb:handle/name [--message \"reason\"]', flags.json);\n return { exitCode: 1 };\n }\n\n if (!pkg.startsWith('kb:')) {\n validationError(ctx, 'Package must be a kb: spec', 'Example: kb:kirill/my-plugin', flags.json);\n return { exitCode: 1 };\n }\n\n const sliced = pkg.slice(3);\n const slashIdx = sliced.indexOf('/');\n const handle = slashIdx > 0 ? sliced.slice(0, slashIdx) : '';\n const name = slashIdx > 0 ? sliced.slice(slashIdx + 1) : '';\n if (!handle || !name) {\n validationError(ctx, 'Invalid kb: spec — expected kb:handle/name', undefined, flags.json);\n return { exitCode: 1 };\n }\n\n try {\n await registryPost(`/packages/${handle}/${encodeURIComponent(name)}/deprecate`, { message: flags.message });\n\n if (flags.json) {\n ctx.ui?.json?.({ deprecated: true, pkg });\n return { exitCode: 0 };\n }\n\n const items = [\n 'Hidden from the marketplace catalog',\n 'Existing installations still work but show a deprecation warning',\n 'New installs are allowed but show a warning',\n ];\n if (flags.message) {\n items.push(`Message: ${flags.message}`);\n }\n\n ctx.ui?.success?.(`${pkg} deprecated`, {\n sections: [{ header: 'What this means', items }],\n hint: 'To block a specific version only, use kb marketplace yank instead',\n timing: Date.now() - t0,\n });\n\n return { exitCode: 0 };\n } catch (err) {\n handleError(ctx, err, flags.json);\n return { exitCode: 1 };\n }\n },\n },\n});\n"]}
@@ -0,0 +1,14 @@
1
+ import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
2
+ import { CLIInput } from '@kb-labs/sdk';
3
+
4
+ interface PublishFlags {
5
+ private?: boolean;
6
+ path?: string;
7
+ metaOnly?: boolean;
8
+ json?: boolean;
9
+ }
10
+ declare const _default: _kb_labs_shared_command_kit.CommandHandlerV3<unknown, CLIInput<PublishFlags>, {
11
+ exitCode: number;
12
+ }>;
13
+
14
+ export { _default as default };
@@ -0,0 +1,268 @@
1
+ import * as path from 'path';
2
+ import { join } from 'path';
3
+ import * as fs from 'fs/promises';
4
+ import { readFile, writeFile, chmod } from 'fs/promises';
5
+ import { defineCommand, validationError, handleError, useEnv } from '@kb-labs/sdk';
6
+ import { homedir } from 'os';
7
+ import { execa } from 'execa';
8
+
9
+ // src/commands/publish.ts
10
+ var CREDENTIALS_PATH = join(homedir(), ".kb", "credentials.json");
11
+ var REGISTRY_GATEWAY_PREFIX = "/api/v1/registry";
12
+ var FETCH_TIMEOUT_MS = 6e4;
13
+ var EXPIRY_BUFFER_MS = 6e4;
14
+ async function resolveAuth() {
15
+ const envToken = useEnv("KB_REGISTRY_TOKEN");
16
+ const envRegUrl = useEnv("KB_REGISTRY_URL");
17
+ const envGateway = useEnv("KB_GATEWAY_URL");
18
+ const envHandle = useEnv("KB_REGISTRY_AUTHOR_HANDLE");
19
+ if (envToken && envRegUrl) {
20
+ return { token: envToken, baseUrl: envRegUrl.replace(/\/$/, ""), handle: envHandle ?? "" };
21
+ }
22
+ if (envToken && envGateway) {
23
+ return {
24
+ token: envToken,
25
+ baseUrl: `${envGateway.replace(/\/$/, "")}${REGISTRY_GATEWAY_PREFIX}`,
26
+ handle: envHandle ?? ""
27
+ };
28
+ }
29
+ let creds;
30
+ try {
31
+ creds = JSON.parse(await readFile(CREDENTIALS_PATH, "utf-8"));
32
+ } catch {
33
+ throw new Error(
34
+ "Not authenticated. Run:\n kb auth create-service-account --gateway-url <url> --name <name> --namespace-id <ns>\n kb auth login --gateway-url <url> --client-id <id> --client-secret <secret>"
35
+ );
36
+ }
37
+ if (Date.now() >= creds.expiresAt - EXPIRY_BUFFER_MS) {
38
+ const res = await fetch(`${creds.gatewayUrl}/auth/refresh`, {
39
+ method: "POST",
40
+ headers: { "Content-Type": "application/json" },
41
+ body: JSON.stringify({ refreshToken: creds.refreshToken })
42
+ });
43
+ if (!res.ok) {
44
+ throw new Error("Session expired. Run: kb auth login --gateway-url <url> --client-id <id> --client-secret <secret>");
45
+ }
46
+ const refreshed = await res.json();
47
+ creds = {
48
+ ...creds,
49
+ accessToken: refreshed.accessToken,
50
+ refreshToken: refreshed.refreshToken,
51
+ expiresAt: Date.now() + refreshed.expiresIn * 1e3
52
+ };
53
+ await writeFile(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), "utf-8");
54
+ await chmod(CREDENTIALS_PATH, 384);
55
+ }
56
+ if (!creds.handle) {
57
+ throw new Error(
58
+ "Author handle not set in credentials. Re-run:\n kb auth login --gateway-url <url> --client-id <id> --client-secret <secret>"
59
+ );
60
+ }
61
+ const baseUrl = envRegUrl ? envRegUrl.replace(/\/$/, "") : `${creds.gatewayUrl.replace(/\/$/, "")}${REGISTRY_GATEWAY_PREFIX}`;
62
+ return { token: creds.accessToken, baseUrl, handle: creds.handle };
63
+ }
64
+ async function resolveRegistryHandle() {
65
+ const auth = await resolveAuth();
66
+ return auth.handle;
67
+ }
68
+ async function registryPostMultipart(path2, tarball, fields, token, authorHandle) {
69
+ const auth = await resolveAuth();
70
+ const url = `${auth.baseUrl}${path2}`;
71
+ const controller = new AbortController();
72
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
73
+ try {
74
+ const form = new FormData();
75
+ form.append("tarball", new Blob([tarball], { type: "application/octet-stream" }), "package.tgz");
76
+ for (const [k, v] of Object.entries(fields)) {
77
+ form.append(k, v);
78
+ }
79
+ const handleHeader = auth.handle ? { "X-Author-Handle": auth.handle } : {};
80
+ const res = await fetch(url, {
81
+ method: "POST",
82
+ headers: { Authorization: `Bearer ${auth.token}`, ...handleHeader },
83
+ body: form,
84
+ signal: controller.signal
85
+ });
86
+ if (!res.ok) {
87
+ const text = await res.text();
88
+ throw new Error(`Registry publish failed (${res.status}): ${text}`);
89
+ }
90
+ return await res.json();
91
+ } catch (err) {
92
+ if (err.name === "AbortError") {
93
+ throw new Error("Registry publish timed out.");
94
+ }
95
+ throw err;
96
+ } finally {
97
+ clearTimeout(timer);
98
+ }
99
+ }
100
+ async function registryPatch(path2, body, token) {
101
+ const auth = await resolveAuth();
102
+ const url = `${auth.baseUrl}${path2}`;
103
+ const controller = new AbortController();
104
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
105
+ try {
106
+ const res = await fetch(url, {
107
+ method: "PATCH",
108
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${auth.token}` },
109
+ body: JSON.stringify(body),
110
+ signal: controller.signal
111
+ });
112
+ if (!res.ok) {
113
+ const text = await res.text();
114
+ throw new Error(`Registry ${path2} failed (${res.status}): ${text}`);
115
+ }
116
+ if (res.status === 204) {
117
+ return void 0;
118
+ }
119
+ return await res.json();
120
+ } catch (err) {
121
+ if (err.name === "AbortError") {
122
+ throw new Error("Registry request timed out.");
123
+ }
124
+ throw err;
125
+ } finally {
126
+ clearTimeout(timer);
127
+ }
128
+ }
129
+ async function packPlugin(dir) {
130
+ const { stdout } = await execa("pnpm", ["pack", "--pack-destination", "/tmp"], { cwd: dir });
131
+ const tarballPath = stdout.trim().split("\n").at(-1).trim();
132
+ const tarball = await fs.readFile(tarballPath);
133
+ await fs.unlink(tarballPath).catch(() => {
134
+ });
135
+ return tarball;
136
+ }
137
+
138
+ // src/commands/publish.ts
139
+ var publish_default = defineCommand({
140
+ id: "marketplace:publish",
141
+ description: "Publish plugin to KB Labs Registry",
142
+ handler: {
143
+ async execute(ctx, input) {
144
+ const { flags = {} } = input;
145
+ const pluginDir = path.resolve(ctx.cwd, flags.path ?? ".");
146
+ const visibility = flags.private ? "private" : "public";
147
+ const t0 = Date.now();
148
+ let pkgJson;
149
+ try {
150
+ const content = await fs.readFile(path.join(pluginDir, "package.json"), "utf-8");
151
+ pkgJson = JSON.parse(content);
152
+ } catch {
153
+ validationError(ctx, "Cannot read package.json in current directory", "Run this command from the plugin root directory", flags.json);
154
+ return { exitCode: 1 };
155
+ }
156
+ if (!pkgJson.name || !pkgJson.version) {
157
+ validationError(ctx, "package.json must have name and version fields", void 0, flags.json);
158
+ return { exitCode: 1 };
159
+ }
160
+ const kb = pkgJson["kb"];
161
+ if (!kb?.manifest && !kb?.adapter) {
162
+ validationError(
163
+ ctx,
164
+ "Not a KB Labs plugin or adapter",
165
+ "Add a kb.manifest or kb.adapter field to package.json",
166
+ flags.json
167
+ );
168
+ return { exitCode: 1 };
169
+ }
170
+ let readme;
171
+ for (const candidate of ["README.md", "README.mdx", "readme.md", "Readme.md"]) {
172
+ readme = await fs.readFile(path.join(pluginDir, candidate), "utf-8").catch(() => void 0);
173
+ if (readme) break;
174
+ }
175
+ const manifest = kb?.manifest ?? {};
176
+ const manifestCommands = Array.isArray(manifest["commands"]) ? manifest["commands"].map((c) => c.id ?? c.name ?? "").filter(Boolean) : void 0;
177
+ const meta = {
178
+ name: pkgJson.name,
179
+ version: pkgJson.version,
180
+ description: pkgJson.description,
181
+ author: pkgJson.author,
182
+ repository: pkgJson.repository,
183
+ keywords: pkgJson.keywords,
184
+ license: pkgJson.license,
185
+ homepage: pkgJson.homepage,
186
+ readme,
187
+ permissions: manifest["permissions"],
188
+ envVars: manifest["envVars"],
189
+ allowedHosts: manifest["allowedHosts"],
190
+ commands: manifestCommands
191
+ };
192
+ if (flags.metaOnly) {
193
+ try {
194
+ const handle = await resolveRegistryHandle();
195
+ await registryPatch(`/packages/${handle}/${meta.name}/meta`, meta);
196
+ ctx.ui?.success?.("Metadata updated", {
197
+ sections: [{ header: "Package", items: [`${meta.name} @ ${handle}`] }],
198
+ timing: Date.now() - t0
199
+ });
200
+ return { exitCode: 0 };
201
+ } catch (err) {
202
+ handleError(ctx, err, flags.json);
203
+ return { exitCode: 1 };
204
+ }
205
+ }
206
+ const primaryKind = kb?.adapter ? "adapter" : "plugin";
207
+ const t1 = Date.now();
208
+ let tarball;
209
+ let packMs;
210
+ try {
211
+ tarball = await packPlugin(pluginDir);
212
+ packMs = Date.now() - t1;
213
+ } catch (err) {
214
+ handleError(ctx, err, flags.json);
215
+ return { exitCode: 1 };
216
+ }
217
+ try {
218
+ const result = await registryPostMultipart(
219
+ "/packages/publish",
220
+ tarball,
221
+ { meta: JSON.stringify({ meta, visibility, primaryKind }) }
222
+ );
223
+ if (flags.json) {
224
+ ctx.ui?.json?.(result);
225
+ return { exitCode: 0 };
226
+ }
227
+ ctx.ui?.chain?.([
228
+ {
229
+ title: `Packed ${meta.name}@${meta.version}`,
230
+ timing: packMs
231
+ },
232
+ {
233
+ title: `${result.name}@${result.version} published`,
234
+ status: "success",
235
+ summary: {
236
+ "Visibility": result.visibility,
237
+ "Trust": result.trust === "trusted" ? "KB Labs Platform \u2713" : "community",
238
+ ...result.pageUrl ? { "Page": result.pageUrl } : {}
239
+ },
240
+ sections: [{ header: "Install", items: [{ text: result.installCommand, truncate: 500 }] }],
241
+ timing: Date.now() - t0
242
+ }
243
+ ]);
244
+ return { exitCode: 0 };
245
+ } catch (err) {
246
+ if (flags.json) {
247
+ handleError(ctx, err, true);
248
+ return { exitCode: 1 };
249
+ }
250
+ const message = err instanceof Error ? err.message : String(err);
251
+ ctx.ui?.chain?.([
252
+ { title: `Packed ${meta.name}@${meta.version}`, timing: packMs },
253
+ {
254
+ title: "Publish failed",
255
+ status: "error",
256
+ sections: [{ items: [message] }],
257
+ timing: Date.now() - t0
258
+ }
259
+ ]);
260
+ return { exitCode: 1 };
261
+ }
262
+ }
263
+ }
264
+ });
265
+
266
+ export { publish_default as default };
267
+ //# sourceMappingURL=publish.js.map
268
+ //# sourceMappingURL=publish.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/registry-http.ts","../../src/pack-plugin.ts","../../src/commands/publish.ts"],"names":["path","fs2"],"mappings":"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","file":"publish.js","sourcesContent":["/**\n * HTTP client for KB Labs Registry service.\n *\n * Credential provider chain (in priority order):\n * 1. KB_REGISTRY_URL + KB_REGISTRY_TOKEN — explicit registry URL (CI/CD, closed-loop mirror)\n * 2. KB_GATEWAY_URL + KB_REGISTRY_TOKEN — legacy CI/CD (derives registry URL from gateway)\n * 3. ~/.kb/credentials.json + KB_REGISTRY_URL — credentials file with explicit registry URL override\n * 4. ~/.kb/credentials.json fully — credentials file, derives registry URL from gatewayUrl\n * 5. Error — \"Run: kb auth login\"\n *\n * KB_REGISTRY_URL decouples the data layer (where the registry lives) from the execution\n * layer (which gateway runs the CLI command). Useful when the registry is behind a different\n * URL than the execution gateway — e.g. closed-loop mirrors or local dev pointing to prod registry.\n *\n * Token auto-refresh: if accessToken is within 60s of expiry, exchanges refreshToken\n * via POST /auth/refresh and overwrites ~/.kb/credentials.json (chmod 600).\n */\n\nimport { readFile, writeFile, chmod } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { useEnv } from '@kb-labs/sdk';\n\nconst CREDENTIALS_PATH = join(homedir(), '.kb', 'credentials.json');\nconst REGISTRY_GATEWAY_PREFIX = '/api/v1/registry';\nconst FETCH_TIMEOUT_MS = 60_000;\nconst EXPIRY_BUFFER_MS = 60_000;\n\ninterface ResolvedAuth {\n token: string;\n baseUrl: string; // full registry base URL including /api/v1/registry prefix\n handle: string;\n}\n\ninterface StoredCredentials {\n gatewayUrl: string;\n accessToken: string;\n refreshToken: string;\n expiresAt: number;\n handle?: string;\n namespaceId?: string;\n}\n\n/**\n * Resolve registry base URL from env vars only (used when a token is passed explicitly).\n * Priority: KB_REGISTRY_URL → KB_GATEWAY_URL → localhost fallback.\n */\nfunction resolveBaseUrlFromEnv(): string {\n const regUrl = useEnv('KB_REGISTRY_URL');\n const gateway = useEnv('KB_GATEWAY_URL');\n if (regUrl) { return regUrl.replace(/\\/$/, ''); }\n if (gateway) { return `${gateway.replace(/\\/$/, '')}${REGISTRY_GATEWAY_PREFIX}`; }\n return `http://127.0.0.1:4000${REGISTRY_GATEWAY_PREFIX}`;\n}\n\nasync function resolveAuth(): Promise<ResolvedAuth> {\n const envToken = useEnv('KB_REGISTRY_TOKEN');\n const envRegUrl = useEnv('KB_REGISTRY_URL'); // explicit registry base URL (data layer)\n const envGateway = useEnv('KB_GATEWAY_URL'); // legacy: derive registry URL from gateway\n const envHandle = useEnv('KB_REGISTRY_AUTHOR_HANDLE');\n\n // 1. Explicit registry URL + token (CI/CD, closed-loop mirror)\n if (envToken && envRegUrl) {\n return { token: envToken, baseUrl: envRegUrl.replace(/\\/$/, ''), handle: envHandle ?? '' };\n }\n\n // 2. Legacy: token + gateway URL → derive registry base URL\n if (envToken && envGateway) {\n return {\n token: envToken,\n baseUrl: `${envGateway.replace(/\\/$/, '')}${REGISTRY_GATEWAY_PREFIX}`,\n handle: envHandle ?? '',\n };\n }\n\n // 3–4. ~/.kb/credentials.json\n let creds: StoredCredentials;\n try {\n creds = JSON.parse(await readFile(CREDENTIALS_PATH, 'utf-8')) as StoredCredentials;\n } catch {\n throw new Error(\n 'Not authenticated. Run:\\n kb auth create-service-account --gateway-url <url> --name <name> --namespace-id <ns>\\n kb auth login --gateway-url <url> --client-id <id> --client-secret <secret>',\n );\n }\n\n // Auto-refresh if token is expired or close to expiry\n if (Date.now() >= creds.expiresAt - EXPIRY_BUFFER_MS) {\n const res = await fetch(`${creds.gatewayUrl}/auth/refresh`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ refreshToken: creds.refreshToken }),\n });\n if (!res.ok) {\n throw new Error('Session expired. Run: kb auth login --gateway-url <url> --client-id <id> --client-secret <secret>');\n }\n const refreshed = await res.json() as { accessToken: string; refreshToken: string; expiresIn: number };\n creds = {\n ...creds,\n accessToken: refreshed.accessToken,\n refreshToken: refreshed.refreshToken,\n expiresAt: Date.now() + refreshed.expiresIn * 1000,\n };\n await writeFile(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), 'utf-8');\n await chmod(CREDENTIALS_PATH, 0o600);\n }\n\n if (!creds.handle) {\n throw new Error(\n 'Author handle not set in credentials. Re-run:\\n kb auth login --gateway-url <url> --client-id <id> --client-secret <secret>',\n );\n }\n\n // 3. credentials + KB_REGISTRY_URL override (execution gateway ≠ registry URL)\n // 4. credentials fully (gatewayUrl used to derive registry base URL)\n const baseUrl = envRegUrl\n ? envRegUrl.replace(/\\/$/, '')\n : `${creds.gatewayUrl.replace(/\\/$/, '')}${REGISTRY_GATEWAY_PREFIX}`;\n\n return { token: creds.accessToken, baseUrl, handle: creds.handle };\n}\n\n/**\n * Resolve the author handle from the credential provider chain.\n * Used when the handle is needed explicitly (e.g. for URL path construction).\n */\nexport async function resolveRegistryHandle(): Promise<string> {\n const auth = await resolveAuth();\n return auth.handle;\n}\n\nexport async function registryPost<T = unknown>(\n path: string,\n body: Record<string, unknown>,\n token?: string,\n): Promise<T> {\n const auth = token\n ? { token, baseUrl: resolveBaseUrlFromEnv() }\n : await resolveAuth();\n\n const url = `${auth.baseUrl}${path}`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${auth.token}` },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Registry ${path} failed (${res.status}): ${text}`);\n }\n if (res.status === 204) { return undefined as T; }\n return await res.json() as T;\n } catch (err) {\n if ((err as Error).name === 'AbortError') {\n throw new Error('Registry request timed out. Is the gateway reachable?');\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function registryPostMultipart<T = unknown>(\n path: string,\n tarball: Buffer,\n fields: Record<string, string>,\n token?: string,\n authorHandle?: string,\n): Promise<T> {\n const auth = token\n ? {\n token,\n baseUrl: resolveBaseUrlFromEnv(),\n handle: authorHandle ?? useEnv('KB_REGISTRY_AUTHOR_HANDLE') ?? '',\n }\n : await resolveAuth();\n\n const url = `${auth.baseUrl}${path}`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const form = new FormData();\n form.append('tarball', new Blob([tarball], { type: 'application/octet-stream' }), 'package.tgz');\n for (const [k, v] of Object.entries(fields)) { form.append(k, v); }\n\n const handleHeader: Record<string, string> = auth.handle ? { 'X-Author-Handle': auth.handle } : {};\n\n const res = await fetch(url, {\n method: 'POST',\n headers: { Authorization: `Bearer ${auth.token}`, ...handleHeader },\n body: form,\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Registry publish failed (${res.status}): ${text}`);\n }\n return await res.json() as T;\n } catch (err) {\n if ((err as Error).name === 'AbortError') {\n throw new Error('Registry publish timed out.');\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function registryPatch<T = unknown>(\n path: string,\n body: Record<string, unknown>,\n token?: string,\n): Promise<T> {\n const auth = token\n ? { token, baseUrl: resolveBaseUrlFromEnv() }\n : await resolveAuth();\n\n const url = `${auth.baseUrl}${path}`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const res = await fetch(url, {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${auth.token}` },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Registry ${path} failed (${res.status}): ${text}`);\n }\n if (res.status === 204) { return undefined as T; }\n return await res.json() as T;\n } catch (err) {\n if ((err as Error).name === 'AbortError') {\n throw new Error('Registry request timed out.');\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n","import * as fs from 'node:fs/promises';\nimport { execa } from 'execa';\n\n/** Runs `pnpm pack` in the given directory and returns the tarball buffer. */\nexport async function packPlugin(dir: string): Promise<Buffer> {\n const { stdout } = await execa('pnpm', ['pack', '--pack-destination', '/tmp'], { cwd: dir });\n const tarballPath = stdout.trim().split('\\n').at(-1)!.trim();\n const tarball = await fs.readFile(tarballPath);\n await fs.unlink(tarballPath).catch(() => { /* ignore cleanup failure */ });\n return tarball;\n}\n","import * as path from 'node:path';\nimport * as fs from 'node:fs/promises';\nimport { defineCommand, handleError, validationError, type CLIInput, type PluginContextV3 } from '@kb-labs/sdk';\nimport { registryPostMultipart, registryPatch, resolveRegistryHandle } from '../registry-http.js';\nimport { packPlugin } from '../pack-plugin.js';\n\ninterface PublishFlags {\n private?: boolean;\n path?: string;\n metaOnly?: boolean;\n json?: boolean;\n}\n\ninterface PublishResultData {\n handle: string;\n name: string;\n version: string;\n visibility: string;\n trust: string;\n installCommand: string;\n pageUrl?: string;\n}\n\nexport default defineCommand<unknown, CLIInput<PublishFlags>, { exitCode: number }>({\n id: 'marketplace:publish',\n description: 'Publish plugin to KB Labs Registry',\n\n handler: {\n async execute(ctx: PluginContextV3, input: CLIInput<PublishFlags>): Promise<{ exitCode: number }> {\n const { flags = {} } = input;\n const pluginDir = path.resolve(ctx.cwd, flags.path ?? '.');\n const visibility = flags.private ? 'private' : 'public';\n const t0 = Date.now();\n\n let pkgJson: Record<string, unknown>;\n try {\n const content = await fs.readFile(path.join(pluginDir, 'package.json'), 'utf-8');\n pkgJson = JSON.parse(content) as Record<string, unknown>;\n } catch {\n validationError(ctx, 'Cannot read package.json in current directory', 'Run this command from the plugin root directory', flags.json);\n return { exitCode: 1 };\n }\n\n if (!pkgJson.name || !pkgJson.version) {\n validationError(ctx, 'package.json must have name and version fields', undefined, flags.json);\n return { exitCode: 1 };\n }\n\n const kb = pkgJson['kb'] as Record<string, unknown> | undefined;\n if (!kb?.manifest && !kb?.adapter) {\n validationError(\n ctx,\n 'Not a KB Labs plugin or adapter',\n 'Add a kb.manifest or kb.adapter field to package.json',\n flags.json,\n );\n return { exitCode: 1 };\n }\n\n // Read README (non-fatal)\n let readme: string | undefined;\n for (const candidate of ['README.md', 'README.mdx', 'readme.md', 'Readme.md']) {\n readme = await fs.readFile(path.join(pluginDir, candidate), 'utf-8').catch(() => undefined);\n if (readme) break;\n }\n\n // Extract manifest fields for the detail page\n const manifest = (kb?.manifest ?? {}) as Record<string, unknown>;\n const manifestCommands = Array.isArray(manifest['commands'])\n ? (manifest['commands'] as Array<{ id?: string; name?: string }>).map(c => c.id ?? c.name ?? '').filter(Boolean)\n : undefined;\n\n const meta = {\n name: pkgJson.name as string,\n version: pkgJson.version as string,\n description: pkgJson.description as string | undefined,\n author: pkgJson.author as unknown,\n repository: pkgJson.repository as unknown,\n keywords: pkgJson.keywords as string[] | undefined,\n license: pkgJson.license as string | undefined,\n homepage: pkgJson.homepage as string | undefined,\n readme,\n permissions: manifest['permissions'] as string[] | undefined,\n envVars: manifest['envVars'] as string[] | undefined,\n allowedHosts: manifest['allowedHosts'] as string[] | undefined,\n commands: manifestCommands,\n };\n\n if (flags.metaOnly) {\n try {\n const handle = await resolveRegistryHandle();\n await registryPatch(`/packages/${handle}/${meta.name}/meta`, meta);\n ctx.ui?.success?.('Metadata updated', {\n sections: [{ header: 'Package', items: [`${meta.name} @ ${handle}`] }],\n timing: Date.now() - t0,\n });\n return { exitCode: 0 };\n } catch (err) {\n handleError(ctx, err, flags.json);\n return { exitCode: 1 };\n }\n }\n\n const primaryKind = kb?.adapter ? 'adapter' : 'plugin';\n\n const t1 = Date.now();\n let tarball: Buffer;\n let packMs: number;\n try {\n tarball = await packPlugin(pluginDir);\n packMs = Date.now() - t1;\n } catch (err) {\n handleError(ctx, err, flags.json);\n return { exitCode: 1 };\n }\n\n try {\n const result = await registryPostMultipart<PublishResultData>(\n '/packages/publish',\n tarball,\n { meta: JSON.stringify({ meta, visibility, primaryKind }) },\n );\n\n if (flags.json) {\n ctx.ui?.json?.(result);\n return { exitCode: 0 };\n }\n\n ctx.ui?.chain?.([\n {\n title: `Packed ${meta.name}@${meta.version}`,\n timing: packMs,\n },\n {\n title: `${result.name}@${result.version} published`,\n status: 'success',\n summary: {\n 'Visibility': result.visibility,\n 'Trust': result.trust === 'trusted' ? 'KB Labs Platform ✓' : 'community',\n ...(result.pageUrl ? { 'Page': result.pageUrl } : {}),\n },\n sections: [{ header: 'Install', items: [{ text: result.installCommand, truncate: 500 }] }],\n timing: Date.now() - t0,\n },\n ]);\n\n return { exitCode: 0 };\n } catch (err) {\n if (flags.json) {\n handleError(ctx, err, true);\n return { exitCode: 1 };\n }\n const message = err instanceof Error ? err.message : String(err);\n ctx.ui?.chain?.([\n { title: `Packed ${meta.name}@${meta.version}`, timing: packMs },\n {\n title: 'Publish failed',\n status: 'error',\n sections: [{ items: [message] }],\n timing: Date.now() - t0,\n },\n ]);\n return { exitCode: 1 };\n }\n },\n },\n});\n\n"]}
@@ -0,0 +1,14 @@
1
+ import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
2
+ import { CLIInput } from '@kb-labs/sdk';
3
+
4
+ interface ShareFlags {
5
+ with?: string;
6
+ link?: boolean;
7
+ ttl?: string;
8
+ json?: boolean;
9
+ }
10
+ declare const _default: _kb_labs_shared_command_kit.CommandHandlerV3<unknown, CLIInput<ShareFlags>, {
11
+ exitCode: number;
12
+ }>;
13
+
14
+ export { _default as default };
@@ -0,0 +1,169 @@
1
+ import { defineCommand, validationError, handleError, useEnv } from '@kb-labs/sdk';
2
+ import { readFile, writeFile, chmod } from 'fs/promises';
3
+ import { join } from 'path';
4
+ import { homedir } from 'os';
5
+
6
+ // src/commands/share.ts
7
+ var CREDENTIALS_PATH = join(homedir(), ".kb", "credentials.json");
8
+ var REGISTRY_GATEWAY_PREFIX = "/api/v1/registry";
9
+ var FETCH_TIMEOUT_MS = 6e4;
10
+ var EXPIRY_BUFFER_MS = 6e4;
11
+ async function resolveAuth() {
12
+ const envToken = useEnv("KB_REGISTRY_TOKEN");
13
+ const envRegUrl = useEnv("KB_REGISTRY_URL");
14
+ const envGateway = useEnv("KB_GATEWAY_URL");
15
+ const envHandle = useEnv("KB_REGISTRY_AUTHOR_HANDLE");
16
+ if (envToken && envRegUrl) {
17
+ return { token: envToken, baseUrl: envRegUrl.replace(/\/$/, ""), handle: envHandle ?? "" };
18
+ }
19
+ if (envToken && envGateway) {
20
+ return {
21
+ token: envToken,
22
+ baseUrl: `${envGateway.replace(/\/$/, "")}${REGISTRY_GATEWAY_PREFIX}`,
23
+ handle: envHandle ?? ""
24
+ };
25
+ }
26
+ let creds;
27
+ try {
28
+ creds = JSON.parse(await readFile(CREDENTIALS_PATH, "utf-8"));
29
+ } catch {
30
+ throw new Error(
31
+ "Not authenticated. Run:\n kb auth create-service-account --gateway-url <url> --name <name> --namespace-id <ns>\n kb auth login --gateway-url <url> --client-id <id> --client-secret <secret>"
32
+ );
33
+ }
34
+ if (Date.now() >= creds.expiresAt - EXPIRY_BUFFER_MS) {
35
+ const res = await fetch(`${creds.gatewayUrl}/auth/refresh`, {
36
+ method: "POST",
37
+ headers: { "Content-Type": "application/json" },
38
+ body: JSON.stringify({ refreshToken: creds.refreshToken })
39
+ });
40
+ if (!res.ok) {
41
+ throw new Error("Session expired. Run: kb auth login --gateway-url <url> --client-id <id> --client-secret <secret>");
42
+ }
43
+ const refreshed = await res.json();
44
+ creds = {
45
+ ...creds,
46
+ accessToken: refreshed.accessToken,
47
+ refreshToken: refreshed.refreshToken,
48
+ expiresAt: Date.now() + refreshed.expiresIn * 1e3
49
+ };
50
+ await writeFile(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), "utf-8");
51
+ await chmod(CREDENTIALS_PATH, 384);
52
+ }
53
+ if (!creds.handle) {
54
+ throw new Error(
55
+ "Author handle not set in credentials. Re-run:\n kb auth login --gateway-url <url> --client-id <id> --client-secret <secret>"
56
+ );
57
+ }
58
+ const baseUrl = envRegUrl ? envRegUrl.replace(/\/$/, "") : `${creds.gatewayUrl.replace(/\/$/, "")}${REGISTRY_GATEWAY_PREFIX}`;
59
+ return { token: creds.accessToken, baseUrl, handle: creds.handle };
60
+ }
61
+ async function registryPost(path, body, token) {
62
+ const auth = await resolveAuth();
63
+ const url = `${auth.baseUrl}${path}`;
64
+ const controller = new AbortController();
65
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
66
+ try {
67
+ const res = await fetch(url, {
68
+ method: "POST",
69
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${auth.token}` },
70
+ body: JSON.stringify(body),
71
+ signal: controller.signal
72
+ });
73
+ if (!res.ok) {
74
+ const text = await res.text();
75
+ throw new Error(`Registry ${path} failed (${res.status}): ${text}`);
76
+ }
77
+ if (res.status === 204) {
78
+ return void 0;
79
+ }
80
+ return await res.json();
81
+ } catch (err) {
82
+ if (err.name === "AbortError") {
83
+ throw new Error("Registry request timed out. Is the gateway reachable?");
84
+ }
85
+ throw err;
86
+ } finally {
87
+ clearTimeout(timer);
88
+ }
89
+ }
90
+
91
+ // src/commands/share.ts
92
+ var share_default = defineCommand({
93
+ id: "marketplace:share",
94
+ description: "Share a private plugin (--with or --link)",
95
+ handler: {
96
+ async execute(ctx, input) {
97
+ const { flags = {}, argv = [] } = input;
98
+ const pkg = argv[0];
99
+ const t0 = Date.now();
100
+ if (!pkg) {
101
+ validationError(ctx, "Package spec is required", "Usage: kb marketplace share kb:handle/name [--with handle | --link]", flags.json);
102
+ return { exitCode: 1 };
103
+ }
104
+ if (!flags.with && !flags.link) {
105
+ validationError(ctx, "Specify --with <handle> to grant access, or --link to generate a share token", void 0, flags.json);
106
+ return { exitCode: 1 };
107
+ }
108
+ if (!pkg.startsWith("kb:")) {
109
+ validationError(ctx, "Package must be a kb: spec", "Example: kb:kirill/my-plugin", flags.json);
110
+ return { exitCode: 1 };
111
+ }
112
+ const sliced = pkg.slice(3);
113
+ const slashIdx = sliced.indexOf("/");
114
+ const handle = slashIdx > 0 ? sliced.slice(0, slashIdx) : "";
115
+ const name = slashIdx > 0 ? sliced.slice(slashIdx + 1) : "";
116
+ if (!handle || !name) {
117
+ validationError(ctx, "Invalid kb: spec \u2014 expected kb:handle/name", void 0, flags.json);
118
+ return { exitCode: 1 };
119
+ }
120
+ try {
121
+ if (flags.with) {
122
+ await registryPost(`/packages/${handle}/${encodeURIComponent(name)}/share/allowlist`, { namespaceId: flags.with });
123
+ if (flags.json) {
124
+ ctx.ui?.json?.({ added: flags.with, pkg });
125
+ return { exitCode: 0 };
126
+ }
127
+ ctx.ui?.success?.(`${flags.with} can now install ${pkg}`, {
128
+ sections: [
129
+ {
130
+ header: "Install command for them",
131
+ items: [{ text: `kb marketplace install ${pkg}`, truncate: 500 }]
132
+ }
133
+ ],
134
+ hint: "For one-time access without an account, use --link instead",
135
+ timing: Date.now() - t0
136
+ });
137
+ return { exitCode: 0 };
138
+ }
139
+ const ttlDays = flags.ttl ? parseInt(flags.ttl, 10) : 7;
140
+ const result = await registryPost(`/packages/${handle}/${encodeURIComponent(name)}/share/token`, { ttlDays });
141
+ if (flags.json) {
142
+ ctx.ui?.json?.(result);
143
+ return { exitCode: 0 };
144
+ }
145
+ const expiry = result.expiresAt ? new Date(result.expiresAt).toLocaleDateString() : `${ttlDays} days`;
146
+ ctx.ui?.success?.("Share link created", {
147
+ summary: {
148
+ "Token": result.token ?? "\u2013",
149
+ "Expires": expiry,
150
+ "Package": pkg
151
+ },
152
+ sections: [{
153
+ header: "Send this command to share",
154
+ items: [{ text: `kb marketplace install ${pkg} --token ${result.token}`, truncate: 500 }]
155
+ }],
156
+ timing: Date.now() - t0
157
+ });
158
+ return { exitCode: 0 };
159
+ } catch (err) {
160
+ handleError(ctx, err, flags.json);
161
+ return { exitCode: 1 };
162
+ }
163
+ }
164
+ }
165
+ });
166
+
167
+ export { share_default as default };
168
+ //# sourceMappingURL=share.js.map
169
+ //# sourceMappingURL=share.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/registry-http.ts","../../src/commands/share.ts"],"names":[],"mappings":"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","file":"share.js","sourcesContent":["/**\n * HTTP client for KB Labs Registry service.\n *\n * Credential provider chain (in priority order):\n * 1. KB_REGISTRY_URL + KB_REGISTRY_TOKEN — explicit registry URL (CI/CD, closed-loop mirror)\n * 2. KB_GATEWAY_URL + KB_REGISTRY_TOKEN — legacy CI/CD (derives registry URL from gateway)\n * 3. ~/.kb/credentials.json + KB_REGISTRY_URL — credentials file with explicit registry URL override\n * 4. ~/.kb/credentials.json fully — credentials file, derives registry URL from gatewayUrl\n * 5. Error — \"Run: kb auth login\"\n *\n * KB_REGISTRY_URL decouples the data layer (where the registry lives) from the execution\n * layer (which gateway runs the CLI command). Useful when the registry is behind a different\n * URL than the execution gateway — e.g. closed-loop mirrors or local dev pointing to prod registry.\n *\n * Token auto-refresh: if accessToken is within 60s of expiry, exchanges refreshToken\n * via POST /auth/refresh and overwrites ~/.kb/credentials.json (chmod 600).\n */\n\nimport { readFile, writeFile, chmod } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { useEnv } from '@kb-labs/sdk';\n\nconst CREDENTIALS_PATH = join(homedir(), '.kb', 'credentials.json');\nconst REGISTRY_GATEWAY_PREFIX = '/api/v1/registry';\nconst FETCH_TIMEOUT_MS = 60_000;\nconst EXPIRY_BUFFER_MS = 60_000;\n\ninterface ResolvedAuth {\n token: string;\n baseUrl: string; // full registry base URL including /api/v1/registry prefix\n handle: string;\n}\n\ninterface StoredCredentials {\n gatewayUrl: string;\n accessToken: string;\n refreshToken: string;\n expiresAt: number;\n handle?: string;\n namespaceId?: string;\n}\n\n/**\n * Resolve registry base URL from env vars only (used when a token is passed explicitly).\n * Priority: KB_REGISTRY_URL → KB_GATEWAY_URL → localhost fallback.\n */\nfunction resolveBaseUrlFromEnv(): string {\n const regUrl = useEnv('KB_REGISTRY_URL');\n const gateway = useEnv('KB_GATEWAY_URL');\n if (regUrl) { return regUrl.replace(/\\/$/, ''); }\n if (gateway) { return `${gateway.replace(/\\/$/, '')}${REGISTRY_GATEWAY_PREFIX}`; }\n return `http://127.0.0.1:4000${REGISTRY_GATEWAY_PREFIX}`;\n}\n\nasync function resolveAuth(): Promise<ResolvedAuth> {\n const envToken = useEnv('KB_REGISTRY_TOKEN');\n const envRegUrl = useEnv('KB_REGISTRY_URL'); // explicit registry base URL (data layer)\n const envGateway = useEnv('KB_GATEWAY_URL'); // legacy: derive registry URL from gateway\n const envHandle = useEnv('KB_REGISTRY_AUTHOR_HANDLE');\n\n // 1. Explicit registry URL + token (CI/CD, closed-loop mirror)\n if (envToken && envRegUrl) {\n return { token: envToken, baseUrl: envRegUrl.replace(/\\/$/, ''), handle: envHandle ?? '' };\n }\n\n // 2. Legacy: token + gateway URL → derive registry base URL\n if (envToken && envGateway) {\n return {\n token: envToken,\n baseUrl: `${envGateway.replace(/\\/$/, '')}${REGISTRY_GATEWAY_PREFIX}`,\n handle: envHandle ?? '',\n };\n }\n\n // 3–4. ~/.kb/credentials.json\n let creds: StoredCredentials;\n try {\n creds = JSON.parse(await readFile(CREDENTIALS_PATH, 'utf-8')) as StoredCredentials;\n } catch {\n throw new Error(\n 'Not authenticated. Run:\\n kb auth create-service-account --gateway-url <url> --name <name> --namespace-id <ns>\\n kb auth login --gateway-url <url> --client-id <id> --client-secret <secret>',\n );\n }\n\n // Auto-refresh if token is expired or close to expiry\n if (Date.now() >= creds.expiresAt - EXPIRY_BUFFER_MS) {\n const res = await fetch(`${creds.gatewayUrl}/auth/refresh`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ refreshToken: creds.refreshToken }),\n });\n if (!res.ok) {\n throw new Error('Session expired. Run: kb auth login --gateway-url <url> --client-id <id> --client-secret <secret>');\n }\n const refreshed = await res.json() as { accessToken: string; refreshToken: string; expiresIn: number };\n creds = {\n ...creds,\n accessToken: refreshed.accessToken,\n refreshToken: refreshed.refreshToken,\n expiresAt: Date.now() + refreshed.expiresIn * 1000,\n };\n await writeFile(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), 'utf-8');\n await chmod(CREDENTIALS_PATH, 0o600);\n }\n\n if (!creds.handle) {\n throw new Error(\n 'Author handle not set in credentials. Re-run:\\n kb auth login --gateway-url <url> --client-id <id> --client-secret <secret>',\n );\n }\n\n // 3. credentials + KB_REGISTRY_URL override (execution gateway ≠ registry URL)\n // 4. credentials fully (gatewayUrl used to derive registry base URL)\n const baseUrl = envRegUrl\n ? envRegUrl.replace(/\\/$/, '')\n : `${creds.gatewayUrl.replace(/\\/$/, '')}${REGISTRY_GATEWAY_PREFIX}`;\n\n return { token: creds.accessToken, baseUrl, handle: creds.handle };\n}\n\n/**\n * Resolve the author handle from the credential provider chain.\n * Used when the handle is needed explicitly (e.g. for URL path construction).\n */\nexport async function resolveRegistryHandle(): Promise<string> {\n const auth = await resolveAuth();\n return auth.handle;\n}\n\nexport async function registryPost<T = unknown>(\n path: string,\n body: Record<string, unknown>,\n token?: string,\n): Promise<T> {\n const auth = token\n ? { token, baseUrl: resolveBaseUrlFromEnv() }\n : await resolveAuth();\n\n const url = `${auth.baseUrl}${path}`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${auth.token}` },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Registry ${path} failed (${res.status}): ${text}`);\n }\n if (res.status === 204) { return undefined as T; }\n return await res.json() as T;\n } catch (err) {\n if ((err as Error).name === 'AbortError') {\n throw new Error('Registry request timed out. Is the gateway reachable?');\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function registryPostMultipart<T = unknown>(\n path: string,\n tarball: Buffer,\n fields: Record<string, string>,\n token?: string,\n authorHandle?: string,\n): Promise<T> {\n const auth = token\n ? {\n token,\n baseUrl: resolveBaseUrlFromEnv(),\n handle: authorHandle ?? useEnv('KB_REGISTRY_AUTHOR_HANDLE') ?? '',\n }\n : await resolveAuth();\n\n const url = `${auth.baseUrl}${path}`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const form = new FormData();\n form.append('tarball', new Blob([tarball], { type: 'application/octet-stream' }), 'package.tgz');\n for (const [k, v] of Object.entries(fields)) { form.append(k, v); }\n\n const handleHeader: Record<string, string> = auth.handle ? { 'X-Author-Handle': auth.handle } : {};\n\n const res = await fetch(url, {\n method: 'POST',\n headers: { Authorization: `Bearer ${auth.token}`, ...handleHeader },\n body: form,\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Registry publish failed (${res.status}): ${text}`);\n }\n return await res.json() as T;\n } catch (err) {\n if ((err as Error).name === 'AbortError') {\n throw new Error('Registry publish timed out.');\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function registryPatch<T = unknown>(\n path: string,\n body: Record<string, unknown>,\n token?: string,\n): Promise<T> {\n const auth = token\n ? { token, baseUrl: resolveBaseUrlFromEnv() }\n : await resolveAuth();\n\n const url = `${auth.baseUrl}${path}`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const res = await fetch(url, {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${auth.token}` },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Registry ${path} failed (${res.status}): ${text}`);\n }\n if (res.status === 204) { return undefined as T; }\n return await res.json() as T;\n } catch (err) {\n if ((err as Error).name === 'AbortError') {\n throw new Error('Registry request timed out.');\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n","import { defineCommand, handleError, validationError, type CLIInput, type PluginContextV3 } from '@kb-labs/sdk';\nimport { registryPost } from '../registry-http.js';\n\ninterface ShareFlags {\n with?: string;\n link?: boolean;\n ttl?: string;\n json?: boolean;\n}\n\ninterface ShareTokenResult {\n token?: string;\n pkg?: string;\n expiresAt?: string;\n}\n\nexport default defineCommand<unknown, CLIInput<ShareFlags>, { exitCode: number }>({\n id: 'marketplace:share',\n description: 'Share a private plugin (--with or --link)',\n\n handler: {\n async execute(ctx: PluginContextV3, input: CLIInput<ShareFlags>): Promise<{ exitCode: number }> {\n const { flags = {}, argv = [] } = input;\n const pkg = argv[0];\n const t0 = Date.now();\n\n if (!pkg) {\n validationError(ctx, 'Package spec is required', 'Usage: kb marketplace share kb:handle/name [--with handle | --link]', flags.json);\n return { exitCode: 1 };\n }\n\n if (!flags.with && !flags.link) {\n validationError(ctx, 'Specify --with <handle> to grant access, or --link to generate a share token', undefined, flags.json);\n return { exitCode: 1 };\n }\n\n if (!pkg.startsWith('kb:')) {\n validationError(ctx, 'Package must be a kb: spec', 'Example: kb:kirill/my-plugin', flags.json);\n return { exitCode: 1 };\n }\n\n const sliced = pkg.slice(3);\n const slashIdx = sliced.indexOf('/');\n const handle = slashIdx > 0 ? sliced.slice(0, slashIdx) : '';\n const name = slashIdx > 0 ? sliced.slice(slashIdx + 1) : '';\n if (!handle || !name) {\n validationError(ctx, 'Invalid kb: spec — expected kb:handle/name', undefined, flags.json);\n return { exitCode: 1 };\n }\n\n try {\n if (flags.with) {\n await registryPost(`/packages/${handle}/${encodeURIComponent(name)}/share/allowlist`, { namespaceId: flags.with });\n\n if (flags.json) {\n ctx.ui?.json?.({ added: flags.with, pkg });\n return { exitCode: 0 };\n }\n\n ctx.ui?.success?.(`${flags.with} can now install ${pkg}`, {\n sections: [\n {\n header: 'Install command for them',\n items: [{ text: `kb marketplace install ${pkg}`, truncate: 500 }],\n },\n ],\n hint: 'For one-time access without an account, use --link instead',\n timing: Date.now() - t0,\n });\n return { exitCode: 0 };\n }\n\n const ttlDays = flags.ttl ? parseInt(flags.ttl, 10) : 7;\n const result = await registryPost<ShareTokenResult>(`/packages/${handle}/${encodeURIComponent(name)}/share/token`, { ttlDays });\n\n if (flags.json) {\n ctx.ui?.json?.(result);\n return { exitCode: 0 };\n }\n\n const expiry = result.expiresAt\n ? new Date(result.expiresAt).toLocaleDateString()\n : `${ttlDays} days`;\n\n ctx.ui?.success?.('Share link created', {\n summary: {\n 'Token': result.token ?? '–',\n 'Expires': expiry,\n 'Package': pkg,\n },\n sections: [{\n header: 'Send this command to share',\n items: [{ text: `kb marketplace install ${pkg} --token ${result.token}`, truncate: 500 }],\n }],\n timing: Date.now() - t0,\n });\n\n return { exitCode: 0 };\n } catch (err) {\n handleError(ctx, err, flags.json);\n return { exitCode: 1 };\n }\n },\n },\n});\n"]}
@@ -0,0 +1,12 @@
1
+ import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
2
+ import { CLIInput } from '@kb-labs/sdk';
3
+
4
+ interface YankFlags {
5
+ reason?: string;
6
+ json?: boolean;
7
+ }
8
+ declare const _default: _kb_labs_shared_command_kit.CommandHandlerV3<unknown, CLIInput<YankFlags>, {
9
+ exitCode: number;
10
+ }>;
11
+
12
+ export { _default as default };
@@ -0,0 +1,145 @@
1
+ import { defineCommand, validationError, handleError, useEnv } from '@kb-labs/sdk';
2
+ import { readFile, writeFile, chmod } from 'fs/promises';
3
+ import { join } from 'path';
4
+ import { homedir } from 'os';
5
+
6
+ // src/commands/yank.ts
7
+ var CREDENTIALS_PATH = join(homedir(), ".kb", "credentials.json");
8
+ var REGISTRY_GATEWAY_PREFIX = "/api/v1/registry";
9
+ var FETCH_TIMEOUT_MS = 6e4;
10
+ var EXPIRY_BUFFER_MS = 6e4;
11
+ async function resolveAuth() {
12
+ const envToken = useEnv("KB_REGISTRY_TOKEN");
13
+ const envRegUrl = useEnv("KB_REGISTRY_URL");
14
+ const envGateway = useEnv("KB_GATEWAY_URL");
15
+ const envHandle = useEnv("KB_REGISTRY_AUTHOR_HANDLE");
16
+ if (envToken && envRegUrl) {
17
+ return { token: envToken, baseUrl: envRegUrl.replace(/\/$/, ""), handle: envHandle ?? "" };
18
+ }
19
+ if (envToken && envGateway) {
20
+ return {
21
+ token: envToken,
22
+ baseUrl: `${envGateway.replace(/\/$/, "")}${REGISTRY_GATEWAY_PREFIX}`,
23
+ handle: envHandle ?? ""
24
+ };
25
+ }
26
+ let creds;
27
+ try {
28
+ creds = JSON.parse(await readFile(CREDENTIALS_PATH, "utf-8"));
29
+ } catch {
30
+ throw new Error(
31
+ "Not authenticated. Run:\n kb auth create-service-account --gateway-url <url> --name <name> --namespace-id <ns>\n kb auth login --gateway-url <url> --client-id <id> --client-secret <secret>"
32
+ );
33
+ }
34
+ if (Date.now() >= creds.expiresAt - EXPIRY_BUFFER_MS) {
35
+ const res = await fetch(`${creds.gatewayUrl}/auth/refresh`, {
36
+ method: "POST",
37
+ headers: { "Content-Type": "application/json" },
38
+ body: JSON.stringify({ refreshToken: creds.refreshToken })
39
+ });
40
+ if (!res.ok) {
41
+ throw new Error("Session expired. Run: kb auth login --gateway-url <url> --client-id <id> --client-secret <secret>");
42
+ }
43
+ const refreshed = await res.json();
44
+ creds = {
45
+ ...creds,
46
+ accessToken: refreshed.accessToken,
47
+ refreshToken: refreshed.refreshToken,
48
+ expiresAt: Date.now() + refreshed.expiresIn * 1e3
49
+ };
50
+ await writeFile(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), "utf-8");
51
+ await chmod(CREDENTIALS_PATH, 384);
52
+ }
53
+ if (!creds.handle) {
54
+ throw new Error(
55
+ "Author handle not set in credentials. Re-run:\n kb auth login --gateway-url <url> --client-id <id> --client-secret <secret>"
56
+ );
57
+ }
58
+ const baseUrl = envRegUrl ? envRegUrl.replace(/\/$/, "") : `${creds.gatewayUrl.replace(/\/$/, "")}${REGISTRY_GATEWAY_PREFIX}`;
59
+ return { token: creds.accessToken, baseUrl, handle: creds.handle };
60
+ }
61
+ async function registryPost(path, body, token) {
62
+ const auth = await resolveAuth();
63
+ const url = `${auth.baseUrl}${path}`;
64
+ const controller = new AbortController();
65
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
66
+ try {
67
+ const res = await fetch(url, {
68
+ method: "POST",
69
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${auth.token}` },
70
+ body: JSON.stringify(body),
71
+ signal: controller.signal
72
+ });
73
+ if (!res.ok) {
74
+ const text = await res.text();
75
+ throw new Error(`Registry ${path} failed (${res.status}): ${text}`);
76
+ }
77
+ if (res.status === 204) {
78
+ return void 0;
79
+ }
80
+ return await res.json();
81
+ } catch (err) {
82
+ if (err.name === "AbortError") {
83
+ throw new Error("Registry request timed out. Is the gateway reachable?");
84
+ }
85
+ throw err;
86
+ } finally {
87
+ clearTimeout(timer);
88
+ }
89
+ }
90
+
91
+ // src/commands/yank.ts
92
+ var yank_default = defineCommand({
93
+ id: "marketplace:yank",
94
+ description: "Yank a specific version (soft-remove \u2014 tarball stays, fresh installs blocked)",
95
+ handler: {
96
+ async execute(ctx, input) {
97
+ const { flags = {}, argv = [] } = input;
98
+ const [pkg, version] = argv;
99
+ const t0 = Date.now();
100
+ if (!pkg || !version) {
101
+ validationError(ctx, "Package spec and version are required", "Usage: kb marketplace yank kb:handle/name <version>", flags.json);
102
+ return { exitCode: 1 };
103
+ }
104
+ if (!pkg.startsWith("kb:")) {
105
+ validationError(ctx, "Package must be a kb: spec", "Example: kb:kirill/my-plugin", flags.json);
106
+ return { exitCode: 1 };
107
+ }
108
+ const sliced = pkg.slice(3);
109
+ const slashIdx = sliced.indexOf("/");
110
+ const handle = slashIdx > 0 ? sliced.slice(0, slashIdx) : "";
111
+ const name = slashIdx > 0 ? sliced.slice(slashIdx + 1) : "";
112
+ if (!handle || !name) {
113
+ validationError(ctx, "Invalid kb: spec \u2014 expected kb:handle/name", void 0, flags.json);
114
+ return { exitCode: 1 };
115
+ }
116
+ try {
117
+ await registryPost(`/packages/${handle}/${encodeURIComponent(name)}/${version}/yank`, { reason: flags.reason });
118
+ if (flags.json) {
119
+ ctx.ui?.json?.({ yanked: true, pkg, version });
120
+ return { exitCode: 0 };
121
+ }
122
+ const items = [
123
+ "Fresh installs of this version are blocked",
124
+ "Existing installations continue to work"
125
+ ];
126
+ if (flags.reason) {
127
+ items.push(`Reason: ${flags.reason}`);
128
+ }
129
+ ctx.ui?.success?.(`${pkg}@${version} yanked`, {
130
+ sections: [{ header: "What this means", items }],
131
+ hint: "To yank all versions, deprecate the package instead",
132
+ timing: Date.now() - t0
133
+ });
134
+ return { exitCode: 0 };
135
+ } catch (err) {
136
+ handleError(ctx, err, flags.json);
137
+ return { exitCode: 1 };
138
+ }
139
+ }
140
+ }
141
+ });
142
+
143
+ export { yank_default as default };
144
+ //# sourceMappingURL=yank.js.map
145
+ //# sourceMappingURL=yank.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/registry-http.ts","../../src/commands/yank.ts"],"names":[],"mappings":"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","file":"yank.js","sourcesContent":["/**\n * HTTP client for KB Labs Registry service.\n *\n * Credential provider chain (in priority order):\n * 1. KB_REGISTRY_URL + KB_REGISTRY_TOKEN — explicit registry URL (CI/CD, closed-loop mirror)\n * 2. KB_GATEWAY_URL + KB_REGISTRY_TOKEN — legacy CI/CD (derives registry URL from gateway)\n * 3. ~/.kb/credentials.json + KB_REGISTRY_URL — credentials file with explicit registry URL override\n * 4. ~/.kb/credentials.json fully — credentials file, derives registry URL from gatewayUrl\n * 5. Error — \"Run: kb auth login\"\n *\n * KB_REGISTRY_URL decouples the data layer (where the registry lives) from the execution\n * layer (which gateway runs the CLI command). Useful when the registry is behind a different\n * URL than the execution gateway — e.g. closed-loop mirrors or local dev pointing to prod registry.\n *\n * Token auto-refresh: if accessToken is within 60s of expiry, exchanges refreshToken\n * via POST /auth/refresh and overwrites ~/.kb/credentials.json (chmod 600).\n */\n\nimport { readFile, writeFile, chmod } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { useEnv } from '@kb-labs/sdk';\n\nconst CREDENTIALS_PATH = join(homedir(), '.kb', 'credentials.json');\nconst REGISTRY_GATEWAY_PREFIX = '/api/v1/registry';\nconst FETCH_TIMEOUT_MS = 60_000;\nconst EXPIRY_BUFFER_MS = 60_000;\n\ninterface ResolvedAuth {\n token: string;\n baseUrl: string; // full registry base URL including /api/v1/registry prefix\n handle: string;\n}\n\ninterface StoredCredentials {\n gatewayUrl: string;\n accessToken: string;\n refreshToken: string;\n expiresAt: number;\n handle?: string;\n namespaceId?: string;\n}\n\n/**\n * Resolve registry base URL from env vars only (used when a token is passed explicitly).\n * Priority: KB_REGISTRY_URL → KB_GATEWAY_URL → localhost fallback.\n */\nfunction resolveBaseUrlFromEnv(): string {\n const regUrl = useEnv('KB_REGISTRY_URL');\n const gateway = useEnv('KB_GATEWAY_URL');\n if (regUrl) { return regUrl.replace(/\\/$/, ''); }\n if (gateway) { return `${gateway.replace(/\\/$/, '')}${REGISTRY_GATEWAY_PREFIX}`; }\n return `http://127.0.0.1:4000${REGISTRY_GATEWAY_PREFIX}`;\n}\n\nasync function resolveAuth(): Promise<ResolvedAuth> {\n const envToken = useEnv('KB_REGISTRY_TOKEN');\n const envRegUrl = useEnv('KB_REGISTRY_URL'); // explicit registry base URL (data layer)\n const envGateway = useEnv('KB_GATEWAY_URL'); // legacy: derive registry URL from gateway\n const envHandle = useEnv('KB_REGISTRY_AUTHOR_HANDLE');\n\n // 1. Explicit registry URL + token (CI/CD, closed-loop mirror)\n if (envToken && envRegUrl) {\n return { token: envToken, baseUrl: envRegUrl.replace(/\\/$/, ''), handle: envHandle ?? '' };\n }\n\n // 2. Legacy: token + gateway URL → derive registry base URL\n if (envToken && envGateway) {\n return {\n token: envToken,\n baseUrl: `${envGateway.replace(/\\/$/, '')}${REGISTRY_GATEWAY_PREFIX}`,\n handle: envHandle ?? '',\n };\n }\n\n // 3–4. ~/.kb/credentials.json\n let creds: StoredCredentials;\n try {\n creds = JSON.parse(await readFile(CREDENTIALS_PATH, 'utf-8')) as StoredCredentials;\n } catch {\n throw new Error(\n 'Not authenticated. Run:\\n kb auth create-service-account --gateway-url <url> --name <name> --namespace-id <ns>\\n kb auth login --gateway-url <url> --client-id <id> --client-secret <secret>',\n );\n }\n\n // Auto-refresh if token is expired or close to expiry\n if (Date.now() >= creds.expiresAt - EXPIRY_BUFFER_MS) {\n const res = await fetch(`${creds.gatewayUrl}/auth/refresh`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ refreshToken: creds.refreshToken }),\n });\n if (!res.ok) {\n throw new Error('Session expired. Run: kb auth login --gateway-url <url> --client-id <id> --client-secret <secret>');\n }\n const refreshed = await res.json() as { accessToken: string; refreshToken: string; expiresIn: number };\n creds = {\n ...creds,\n accessToken: refreshed.accessToken,\n refreshToken: refreshed.refreshToken,\n expiresAt: Date.now() + refreshed.expiresIn * 1000,\n };\n await writeFile(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), 'utf-8');\n await chmod(CREDENTIALS_PATH, 0o600);\n }\n\n if (!creds.handle) {\n throw new Error(\n 'Author handle not set in credentials. Re-run:\\n kb auth login --gateway-url <url> --client-id <id> --client-secret <secret>',\n );\n }\n\n // 3. credentials + KB_REGISTRY_URL override (execution gateway ≠ registry URL)\n // 4. credentials fully (gatewayUrl used to derive registry base URL)\n const baseUrl = envRegUrl\n ? envRegUrl.replace(/\\/$/, '')\n : `${creds.gatewayUrl.replace(/\\/$/, '')}${REGISTRY_GATEWAY_PREFIX}`;\n\n return { token: creds.accessToken, baseUrl, handle: creds.handle };\n}\n\n/**\n * Resolve the author handle from the credential provider chain.\n * Used when the handle is needed explicitly (e.g. for URL path construction).\n */\nexport async function resolveRegistryHandle(): Promise<string> {\n const auth = await resolveAuth();\n return auth.handle;\n}\n\nexport async function registryPost<T = unknown>(\n path: string,\n body: Record<string, unknown>,\n token?: string,\n): Promise<T> {\n const auth = token\n ? { token, baseUrl: resolveBaseUrlFromEnv() }\n : await resolveAuth();\n\n const url = `${auth.baseUrl}${path}`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${auth.token}` },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Registry ${path} failed (${res.status}): ${text}`);\n }\n if (res.status === 204) { return undefined as T; }\n return await res.json() as T;\n } catch (err) {\n if ((err as Error).name === 'AbortError') {\n throw new Error('Registry request timed out. Is the gateway reachable?');\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function registryPostMultipart<T = unknown>(\n path: string,\n tarball: Buffer,\n fields: Record<string, string>,\n token?: string,\n authorHandle?: string,\n): Promise<T> {\n const auth = token\n ? {\n token,\n baseUrl: resolveBaseUrlFromEnv(),\n handle: authorHandle ?? useEnv('KB_REGISTRY_AUTHOR_HANDLE') ?? '',\n }\n : await resolveAuth();\n\n const url = `${auth.baseUrl}${path}`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const form = new FormData();\n form.append('tarball', new Blob([tarball], { type: 'application/octet-stream' }), 'package.tgz');\n for (const [k, v] of Object.entries(fields)) { form.append(k, v); }\n\n const handleHeader: Record<string, string> = auth.handle ? { 'X-Author-Handle': auth.handle } : {};\n\n const res = await fetch(url, {\n method: 'POST',\n headers: { Authorization: `Bearer ${auth.token}`, ...handleHeader },\n body: form,\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Registry publish failed (${res.status}): ${text}`);\n }\n return await res.json() as T;\n } catch (err) {\n if ((err as Error).name === 'AbortError') {\n throw new Error('Registry publish timed out.');\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function registryPatch<T = unknown>(\n path: string,\n body: Record<string, unknown>,\n token?: string,\n): Promise<T> {\n const auth = token\n ? { token, baseUrl: resolveBaseUrlFromEnv() }\n : await resolveAuth();\n\n const url = `${auth.baseUrl}${path}`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const res = await fetch(url, {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${auth.token}` },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Registry ${path} failed (${res.status}): ${text}`);\n }\n if (res.status === 204) { return undefined as T; }\n return await res.json() as T;\n } catch (err) {\n if ((err as Error).name === 'AbortError') {\n throw new Error('Registry request timed out.');\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n","import { defineCommand, handleError, validationError, type CLIInput, type PluginContextV3 } from '@kb-labs/sdk';\nimport { registryPost } from '../registry-http.js';\n\ninterface YankFlags {\n reason?: string;\n json?: boolean;\n}\n\nexport default defineCommand<unknown, CLIInput<YankFlags>, { exitCode: number }>({\n id: 'marketplace:yank',\n description: 'Yank a specific version (soft-remove — tarball stays, fresh installs blocked)',\n\n handler: {\n async execute(ctx: PluginContextV3, input: CLIInput<YankFlags>): Promise<{ exitCode: number }> {\n const { flags = {}, argv = [] } = input;\n const [pkg, version] = argv;\n const t0 = Date.now();\n\n if (!pkg || !version) {\n validationError(ctx, 'Package spec and version are required', 'Usage: kb marketplace yank kb:handle/name <version>', flags.json);\n return { exitCode: 1 };\n }\n\n if (!pkg.startsWith('kb:')) {\n validationError(ctx, 'Package must be a kb: spec', 'Example: kb:kirill/my-plugin', flags.json);\n return { exitCode: 1 };\n }\n\n const sliced = pkg.slice(3);\n const slashIdx = sliced.indexOf('/');\n const handle = slashIdx > 0 ? sliced.slice(0, slashIdx) : '';\n const name = slashIdx > 0 ? sliced.slice(slashIdx + 1) : '';\n if (!handle || !name) {\n validationError(ctx, 'Invalid kb: spec — expected kb:handle/name', undefined, flags.json);\n return { exitCode: 1 };\n }\n\n try {\n await registryPost(`/packages/${handle}/${encodeURIComponent(name)}/${version}/yank`, { reason: flags.reason });\n\n if (flags.json) {\n ctx.ui?.json?.({ yanked: true, pkg, version });\n return { exitCode: 0 };\n }\n\n const items = [\n 'Fresh installs of this version are blocked',\n 'Existing installations continue to work',\n ];\n if (flags.reason) {\n items.push(`Reason: ${flags.reason}`);\n }\n\n ctx.ui?.success?.(`${pkg}@${version} yanked`, {\n sections: [{ header: 'What this means', items }],\n hint: 'To yank all versions, deprecate the package instead',\n timing: Date.now() - t0,\n });\n\n return { exitCode: 0 };\n } catch (err) {\n handleError(ctx, err, flags.json);\n return { exitCode: 1 };\n }\n },\n },\n});\n"]}
@@ -0,0 +1,2 @@
1
+ export { default as manifest } from './manifest.js';
2
+ import '@kb-labs/plugin-contracts';
package/dist/index.js ADDED
@@ -0,0 +1,58 @@
1
+ import { combinePermissions, kbPlatformPreset } from '@kb-labs/sdk';
2
+
3
+ // src/manifest.ts
4
+ var pluginPermissions = combinePermissions().with(kbPlatformPreset).withEnv(["KB_GATEWAY_URL", "KB_REGISTRY_URL", "KB_REGISTRY_TOKEN", "KB_REGISTRY_AUTHOR_HANDLE"]).build();
5
+ var manifest = {
6
+ schema: "kb.plugin/3",
7
+ id: "@kb-labs/marketplace-registry",
8
+ version: "0.1.0",
9
+ display: {
10
+ name: "Marketplace Registry",
11
+ description: "Publish and manage plugins in KB Labs Registry",
12
+ tags: ["marketplace", "registry", "publish"]
13
+ },
14
+ cli: {
15
+ groupMeta: [
16
+ { path: "marketplace", describe: "Marketplace plugin management" }
17
+ ],
18
+ commands: [
19
+ {
20
+ path: "marketplace publish",
21
+ category: "Registry",
22
+ operationType: "mutate",
23
+ describe: "Publish plugin to KB Labs Registry",
24
+ handler: "./commands/publish.js#default",
25
+ permissions: pluginPermissions
26
+ },
27
+ {
28
+ path: "marketplace share",
29
+ category: "Registry",
30
+ operationType: "mutate",
31
+ describe: "Share a private plugin (--with or --link)",
32
+ handler: "./commands/share.js#default",
33
+ permissions: pluginPermissions
34
+ },
35
+ {
36
+ path: "marketplace yank",
37
+ category: "Registry",
38
+ operationType: "mutate",
39
+ describe: "Yank a specific version",
40
+ handler: "./commands/yank.js#default",
41
+ permissions: pluginPermissions
42
+ },
43
+ {
44
+ path: "marketplace deprecate",
45
+ category: "Registry",
46
+ operationType: "mutate",
47
+ describe: "Deprecate a package",
48
+ handler: "./commands/deprecate.js#default",
49
+ permissions: pluginPermissions
50
+ }
51
+ ]
52
+ },
53
+ permissions: pluginPermissions
54
+ };
55
+
56
+ export { manifest };
57
+ //# sourceMappingURL=index.js.map
58
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/manifest.ts"],"names":[],"mappings":";;;AAEA,IAAM,iBAAA,GAAoB,kBAAA,EAAmB,CAC1C,IAAA,CAAK,gBAAgB,CAAA,CACrB,OAAA,CAAQ,CAAC,gBAAA,EAAkB,iBAAA,EAAmB,mBAAA,EAAqB,2BAA2B,CAAC,EAC/F,KAAA,EAAM;AAEF,IAAM,QAAA,GAAW;AAAA,EACtB,MAAA,EAAQ,aAAA;AAAA,EACR,EAAA,EAAI,+BAAA;AAAA,EACJ,OAAA,EAAS,OAAA;AAAA,EAET,OAAA,EAAS;AAAA,IACP,IAAA,EAAM,sBAAA;AAAA,IACN,WAAA,EAAa,gDAAA;AAAA,IACb,IAAA,EAAM,CAAC,aAAA,EAAe,UAAA,EAAY,SAAS;AAAA,GAC7C;AAAA,EAEA,GAAA,EAAK;AAAA,IACH,SAAA,EAAW;AAAA,MACT,EAAE,IAAA,EAAM,aAAA,EAAe,QAAA,EAAU,+BAAA;AAAgC,KACnE;AAAA,IACA,QAAA,EAAU;AAAA,MACR;AAAA,QACE,IAAA,EAAM,qBAAA;AAAA,QACN,QAAA,EAAU,UAAA;AAAA,QACV,aAAA,EAAe,QAAA;AAAA,QACf,QAAA,EAAU,oCAAA;AAAA,QACV,OAAA,EAAS,+BAAA;AAAA,QACT,WAAA,EAAa;AAAA,OACf;AAAA,MACA;AAAA,QACE,IAAA,EAAM,mBAAA;AAAA,QACN,QAAA,EAAU,UAAA;AAAA,QACV,aAAA,EAAe,QAAA;AAAA,QACf,QAAA,EAAU,2CAAA;AAAA,QACV,OAAA,EAAS,6BAAA;AAAA,QACT,WAAA,EAAa;AAAA,OACf;AAAA,MACA;AAAA,QACE,IAAA,EAAM,kBAAA;AAAA,QACN,QAAA,EAAU,UAAA;AAAA,QACV,aAAA,EAAe,QAAA;AAAA,QACf,QAAA,EAAU,yBAAA;AAAA,QACV,OAAA,EAAS,4BAAA;AAAA,QACT,WAAA,EAAa;AAAA,OACf;AAAA,MACA;AAAA,QACE,IAAA,EAAM,uBAAA;AAAA,QACN,QAAA,EAAU,UAAA;AAAA,QACV,aAAA,EAAe,QAAA;AAAA,QACf,QAAA,EAAU,qBAAA;AAAA,QACV,OAAA,EAAS,iCAAA;AAAA,QACT,WAAA,EAAa;AAAA;AACf;AACF,GACF;AAAA,EAEA,WAAA,EAAa;AACf","file":"index.js","sourcesContent":["import { combinePermissions, kbPlatformPreset } from '@kb-labs/sdk';\n\nconst pluginPermissions = combinePermissions()\n .with(kbPlatformPreset)\n .withEnv(['KB_GATEWAY_URL', 'KB_REGISTRY_URL', 'KB_REGISTRY_TOKEN', 'KB_REGISTRY_AUTHOR_HANDLE'])\n .build();\n\nexport const manifest = {\n schema: 'kb.plugin/3' as const,\n id: '@kb-labs/marketplace-registry',\n version: '0.1.0',\n\n display: {\n name: 'Marketplace Registry',\n description: 'Publish and manage plugins in KB Labs Registry',\n tags: ['marketplace', 'registry', 'publish'],\n },\n\n cli: {\n groupMeta: [\n { path: 'marketplace', describe: 'Marketplace plugin management' },\n ],\n commands: [\n {\n path: 'marketplace publish',\n category: 'Registry',\n operationType: 'mutate' as const,\n describe: 'Publish plugin to KB Labs Registry',\n handler: './commands/publish.js#default',\n permissions: pluginPermissions,\n },\n {\n path: 'marketplace share',\n category: 'Registry',\n operationType: 'mutate' as const,\n describe: 'Share a private plugin (--with or --link)',\n handler: './commands/share.js#default',\n permissions: pluginPermissions,\n },\n {\n path: 'marketplace yank',\n category: 'Registry',\n operationType: 'mutate' as const,\n describe: 'Yank a specific version',\n handler: './commands/yank.js#default',\n permissions: pluginPermissions,\n },\n {\n path: 'marketplace deprecate',\n category: 'Registry',\n operationType: 'mutate' as const,\n describe: 'Deprecate a package',\n handler: './commands/deprecate.js#default',\n permissions: pluginPermissions,\n },\n ],\n },\n\n permissions: pluginPermissions,\n};\n\nexport default manifest;\n"]}
@@ -0,0 +1,29 @@
1
+ import * as _kb_labs_plugin_contracts from '@kb-labs/plugin-contracts';
2
+
3
+ declare const manifest: {
4
+ schema: "kb.plugin/3";
5
+ id: string;
6
+ version: string;
7
+ display: {
8
+ name: string;
9
+ description: string;
10
+ tags: string[];
11
+ };
12
+ cli: {
13
+ groupMeta: {
14
+ path: string;
15
+ describe: string;
16
+ }[];
17
+ commands: {
18
+ path: string;
19
+ category: string;
20
+ operationType: "mutate";
21
+ describe: string;
22
+ handler: string;
23
+ permissions: _kb_labs_plugin_contracts.PermissionSpec;
24
+ }[];
25
+ };
26
+ permissions: _kb_labs_plugin_contracts.PermissionSpec;
27
+ };
28
+
29
+ export { manifest as default, manifest };
@@ -0,0 +1,59 @@
1
+ import { combinePermissions, kbPlatformPreset } from '@kb-labs/sdk';
2
+
3
+ // src/manifest.ts
4
+ var pluginPermissions = combinePermissions().with(kbPlatformPreset).withEnv(["KB_GATEWAY_URL", "KB_REGISTRY_URL", "KB_REGISTRY_TOKEN", "KB_REGISTRY_AUTHOR_HANDLE"]).build();
5
+ var manifest = {
6
+ schema: "kb.plugin/3",
7
+ id: "@kb-labs/marketplace-registry",
8
+ version: "0.1.0",
9
+ display: {
10
+ name: "Marketplace Registry",
11
+ description: "Publish and manage plugins in KB Labs Registry",
12
+ tags: ["marketplace", "registry", "publish"]
13
+ },
14
+ cli: {
15
+ groupMeta: [
16
+ { path: "marketplace", describe: "Marketplace plugin management" }
17
+ ],
18
+ commands: [
19
+ {
20
+ path: "marketplace publish",
21
+ category: "Registry",
22
+ operationType: "mutate",
23
+ describe: "Publish plugin to KB Labs Registry",
24
+ handler: "./commands/publish.js#default",
25
+ permissions: pluginPermissions
26
+ },
27
+ {
28
+ path: "marketplace share",
29
+ category: "Registry",
30
+ operationType: "mutate",
31
+ describe: "Share a private plugin (--with or --link)",
32
+ handler: "./commands/share.js#default",
33
+ permissions: pluginPermissions
34
+ },
35
+ {
36
+ path: "marketplace yank",
37
+ category: "Registry",
38
+ operationType: "mutate",
39
+ describe: "Yank a specific version",
40
+ handler: "./commands/yank.js#default",
41
+ permissions: pluginPermissions
42
+ },
43
+ {
44
+ path: "marketplace deprecate",
45
+ category: "Registry",
46
+ operationType: "mutate",
47
+ describe: "Deprecate a package",
48
+ handler: "./commands/deprecate.js#default",
49
+ permissions: pluginPermissions
50
+ }
51
+ ]
52
+ },
53
+ permissions: pluginPermissions
54
+ };
55
+ var manifest_default = manifest;
56
+
57
+ export { manifest_default as default, manifest };
58
+ //# sourceMappingURL=manifest.js.map
59
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/manifest.ts"],"names":[],"mappings":";;;AAEA,IAAM,iBAAA,GAAoB,kBAAA,EAAmB,CAC1C,IAAA,CAAK,gBAAgB,CAAA,CACrB,OAAA,CAAQ,CAAC,gBAAA,EAAkB,iBAAA,EAAmB,mBAAA,EAAqB,2BAA2B,CAAC,EAC/F,KAAA,EAAM;AAEF,IAAM,QAAA,GAAW;AAAA,EACtB,MAAA,EAAQ,aAAA;AAAA,EACR,EAAA,EAAI,+BAAA;AAAA,EACJ,OAAA,EAAS,OAAA;AAAA,EAET,OAAA,EAAS;AAAA,IACP,IAAA,EAAM,sBAAA;AAAA,IACN,WAAA,EAAa,gDAAA;AAAA,IACb,IAAA,EAAM,CAAC,aAAA,EAAe,UAAA,EAAY,SAAS;AAAA,GAC7C;AAAA,EAEA,GAAA,EAAK;AAAA,IACH,SAAA,EAAW;AAAA,MACT,EAAE,IAAA,EAAM,aAAA,EAAe,QAAA,EAAU,+BAAA;AAAgC,KACnE;AAAA,IACA,QAAA,EAAU;AAAA,MACR;AAAA,QACE,IAAA,EAAM,qBAAA;AAAA,QACN,QAAA,EAAU,UAAA;AAAA,QACV,aAAA,EAAe,QAAA;AAAA,QACf,QAAA,EAAU,oCAAA;AAAA,QACV,OAAA,EAAS,+BAAA;AAAA,QACT,WAAA,EAAa;AAAA,OACf;AAAA,MACA;AAAA,QACE,IAAA,EAAM,mBAAA;AAAA,QACN,QAAA,EAAU,UAAA;AAAA,QACV,aAAA,EAAe,QAAA;AAAA,QACf,QAAA,EAAU,2CAAA;AAAA,QACV,OAAA,EAAS,6BAAA;AAAA,QACT,WAAA,EAAa;AAAA,OACf;AAAA,MACA;AAAA,QACE,IAAA,EAAM,kBAAA;AAAA,QACN,QAAA,EAAU,UAAA;AAAA,QACV,aAAA,EAAe,QAAA;AAAA,QACf,QAAA,EAAU,yBAAA;AAAA,QACV,OAAA,EAAS,4BAAA;AAAA,QACT,WAAA,EAAa;AAAA,OACf;AAAA,MACA;AAAA,QACE,IAAA,EAAM,uBAAA;AAAA,QACN,QAAA,EAAU,UAAA;AAAA,QACV,aAAA,EAAe,QAAA;AAAA,QACf,QAAA,EAAU,qBAAA;AAAA,QACV,OAAA,EAAS,iCAAA;AAAA,QACT,WAAA,EAAa;AAAA;AACf;AACF,GACF;AAAA,EAEA,WAAA,EAAa;AACf;AAEA,IAAO,gBAAA,GAAQ","file":"manifest.js","sourcesContent":["import { combinePermissions, kbPlatformPreset } from '@kb-labs/sdk';\n\nconst pluginPermissions = combinePermissions()\n .with(kbPlatformPreset)\n .withEnv(['KB_GATEWAY_URL', 'KB_REGISTRY_URL', 'KB_REGISTRY_TOKEN', 'KB_REGISTRY_AUTHOR_HANDLE'])\n .build();\n\nexport const manifest = {\n schema: 'kb.plugin/3' as const,\n id: '@kb-labs/marketplace-registry',\n version: '0.1.0',\n\n display: {\n name: 'Marketplace Registry',\n description: 'Publish and manage plugins in KB Labs Registry',\n tags: ['marketplace', 'registry', 'publish'],\n },\n\n cli: {\n groupMeta: [\n { path: 'marketplace', describe: 'Marketplace plugin management' },\n ],\n commands: [\n {\n path: 'marketplace publish',\n category: 'Registry',\n operationType: 'mutate' as const,\n describe: 'Publish plugin to KB Labs Registry',\n handler: './commands/publish.js#default',\n permissions: pluginPermissions,\n },\n {\n path: 'marketplace share',\n category: 'Registry',\n operationType: 'mutate' as const,\n describe: 'Share a private plugin (--with or --link)',\n handler: './commands/share.js#default',\n permissions: pluginPermissions,\n },\n {\n path: 'marketplace yank',\n category: 'Registry',\n operationType: 'mutate' as const,\n describe: 'Yank a specific version',\n handler: './commands/yank.js#default',\n permissions: pluginPermissions,\n },\n {\n path: 'marketplace deprecate',\n category: 'Registry',\n operationType: 'mutate' as const,\n describe: 'Deprecate a package',\n handler: './commands/deprecate.js#default',\n permissions: pluginPermissions,\n },\n ],\n },\n\n permissions: pluginPermissions,\n};\n\nexport default manifest;\n"]}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@kb-labs/marketplace-registry-entry",
3
+ "description": "CLI commands for publishing and managing plugins in KB Labs Registry",
4
+ "version": "2.96.0",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./plugin-manifest": {
14
+ "import": "./dist/manifest.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "dependencies": {
21
+ "execa": "^9.5.3",
22
+ "@kb-labs/sdk": "2.21.2"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^24.3.3",
26
+ "tsup": "^8.5.0",
27
+ "typescript": "^5",
28
+ "vitest": "^3.2.6",
29
+ "@kb-labs/shared-testing-e2e": "2.96.0",
30
+ "@kb-labs/devkit": "2.96.0"
31
+ },
32
+ "engines": {
33
+ "node": ">=20.0.0",
34
+ "pnpm": ">=9.0.0"
35
+ },
36
+ "kb": {
37
+ "manifest": "./dist/manifest.js"
38
+ },
39
+ "sideEffects": false,
40
+ "scripts": {
41
+ "build": "tsup",
42
+ "clean": "rimraf dist",
43
+ "dev": "tsup --watch",
44
+ "lint": "eslint src --ext .ts",
45
+ "lint:fix": "eslint src --ext .ts --fix",
46
+ "test": "vitest run --passWithNoTests -c ../../vitest.config.ts",
47
+ "test:cli": "vitest run --config vitest.cli.config.ts --passWithNoTests",
48
+ "type-check": "tsc --noEmit"
49
+ }
50
+ }