@kb-labs/marketplace-entry 2.31.0 → 2.32.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.
@@ -1,10 +1,16 @@
1
1
  import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
2
2
 
3
+ interface LinkFlags {
4
+ scope?: string;
5
+ json?: boolean;
6
+ }
3
7
  interface LinkInput {
4
8
  argv?: string[];
9
+ flags?: LinkFlags;
5
10
  }
6
11
  declare const _default: _kb_labs_shared_command_kit.CommandHandlerV3<unknown, LinkInput, {
7
12
  id: string;
13
+ scope: string;
8
14
  }>;
9
15
 
10
16
  export { _default as default };
@@ -1,4 +1,6 @@
1
1
  import { defineCommand, useEnv } from '@kb-labs/sdk';
2
+ import * as path from 'path';
3
+ import { findProjectConfigRoot } from '@kb-labs/core-workspace';
2
4
 
3
5
  // src/commands/plugins/link.ts
4
6
  var DEFAULT_GATEWAY_URL = "http://127.0.0.1:4000";
@@ -12,8 +14,8 @@ function getBaseUrl() {
12
14
  const gateway = useEnv("KB_GATEWAY_URL") ?? DEFAULT_GATEWAY_URL;
13
15
  return `${gateway}${MARKETPLACE_PREFIX}`;
14
16
  }
15
- async function post(path, body) {
16
- const url = `${getBaseUrl()}${path}`;
17
+ async function post(path2, body) {
18
+ const url = `${getBaseUrl()}${path2}`;
17
19
  const controller = new AbortController();
18
20
  const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
19
21
  try {
@@ -25,18 +27,62 @@ async function post(path, body) {
25
27
  });
26
28
  if (!res.ok) {
27
29
  const text = await res.text();
28
- throw new Error(`Marketplace ${path} failed (${res.status}): ${text}`);
30
+ throw new Error(`Marketplace ${path2} failed (${res.status}): ${text}`);
29
31
  }
30
32
  return await res.json();
31
33
  } catch (err) {
32
34
  if (err.name === "AbortError") {
33
- throw new Error(`Marketplace ${path} timed out \u2014 is the marketplace service running? (kb-dev start marketplace)`);
35
+ throw new Error(`Marketplace ${path2} timed out \u2014 is the marketplace service running? (kb-dev start marketplace)`);
34
36
  }
35
37
  throw err;
36
38
  } finally {
37
39
  clearTimeout(timer);
38
40
  }
39
41
  }
42
+ var SCOPE_FLAG_CHOICES = ["platform", "project"];
43
+ async function resolveCliScope(cwd, flag) {
44
+ if (flag) {
45
+ assertMutatingScope(flag);
46
+ if (flag === "project") {
47
+ const projectRoot2 = await findProjectConfigRoot(cwd);
48
+ if (!projectRoot2) {
49
+ throw new CliScopeError(
50
+ "SCOPE_PROJECT_ROOT_NOT_FOUND",
51
+ `--scope=project requires a .kb/kb.config.{json,jsonc} ancestor of ${cwd} \u2014 none found.`
52
+ );
53
+ }
54
+ return { scope: "project", projectRoot: projectRoot2, reason: "flag" };
55
+ }
56
+ return { scope: "platform", reason: "flag" };
57
+ }
58
+ const projectRoot = await findProjectConfigRoot(cwd);
59
+ if (projectRoot) {
60
+ return { scope: "project", projectRoot, reason: "auto-detect" };
61
+ }
62
+ return { scope: "platform", reason: "fallback" };
63
+ }
64
+ function assertMutatingScope(value) {
65
+ if (!SCOPE_FLAG_CHOICES.includes(value)) {
66
+ throw new CliScopeError(
67
+ "SCOPE_INVALID",
68
+ `--scope must be one of: ${SCOPE_FLAG_CHOICES.join(", ")} (got ${JSON.stringify(value)})`
69
+ );
70
+ }
71
+ }
72
+ var CliScopeError = class extends Error {
73
+ code;
74
+ constructor(code, message) {
75
+ super(message);
76
+ this.code = code;
77
+ this.name = "CliScopeError";
78
+ }
79
+ };
80
+ function scopeBody(ctx) {
81
+ return {
82
+ scope: ctx.scope,
83
+ ...ctx.projectRoot ? { projectRoot: ctx.projectRoot } : {}
84
+ };
85
+ }
40
86
 
41
87
  // src/commands/plugins/link.ts
42
88
  var link_default = defineCommand({
@@ -47,11 +93,26 @@ var link_default = defineCommand({
47
93
  const pluginPath = input.argv?.[0];
48
94
  if (!pluginPath) {
49
95
  ctx.ui?.error?.("Specify a plugin path to link");
50
- return { exitCode: 1, result: { id: "" } };
96
+ return { exitCode: 1, result: { id: "", scope: "" } };
97
+ }
98
+ const flags = input.flags ?? input;
99
+ let scopeCtx;
100
+ try {
101
+ scopeCtx = await resolveCliScope(ctx.cwd, flags.scope);
102
+ } catch (err) {
103
+ if (err instanceof CliScopeError) {
104
+ ctx.ui?.error?.(err.message);
105
+ return { exitCode: 1, result: { id: "", scope: "" } };
106
+ }
107
+ throw err;
51
108
  }
52
- const result = await post(`/packages/${encodeURIComponent(pluginPath)}/link`, { path: pluginPath });
53
- ctx.ui?.success?.(`Linked ${result.id}`);
54
- return { exitCode: 0, result: { id: result.id } };
109
+ const absPath = path.resolve(ctx.cwd, pluginPath);
110
+ const result = await post(
111
+ `/packages/${encodeURIComponent(pluginPath)}/link`,
112
+ { path: absPath, ...scopeBody(scopeCtx) }
113
+ );
114
+ ctx.ui?.success?.(`Linked ${result.id} (${result.scope})`);
115
+ return { exitCode: 0, result: { id: result.id, scope: result.scope } };
55
116
  }
56
117
  }
57
118
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/http.ts","../../../src/commands/plugins/link.ts"],"names":[],"mappings":";;;AAMA,IAAM,mBAAA,GAAsB,uBAAA;AAC5B,IAAM,kBAAA,GAAqB,qBAAA;AAC3B,IAAM,gBAAA,GAAmB,GAAA;AAEzB,SAAS,UAAA,GAAqB;AAC5B,EAAA,MAAM,cAAA,GAAiB,OAAO,oBAAoB,CAAA;AAClD,EAAA,IAAI,cAAA,EAAgB;AAClB,IAAA,OAAO,CAAA,EAAG,cAAc,CAAA,EAAG,kBAAkB,CAAA,CAAA;AAAA,EAC/C;AACA,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,gBAAgB,CAAA,IAAK,mBAAA;AAC5C,EAAA,OAAO,CAAA,EAAG,OAAO,CAAA,EAAG,kBAAkB,CAAA,CAAA;AACxC;AAEA,eAAsB,IAAA,CAAkB,MAAc,IAAA,EAA2C;AAC/F,EAAA,MAAM,GAAA,GAAM,CAAA,EAAG,UAAA,EAAY,GAAG,IAAI,CAAA,CAAA;AAClC,EAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,EAAA,MAAM,QAAQ,UAAA,CAAW,MAAM,UAAA,CAAW,KAAA,IAAS,gBAAgB,CAAA;AACnE,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,MAC3B,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,MAC9C,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA;AAAA,MACzB,QAAQ,UAAA,CAAW;AAAA,KACpB,CAAA;AACD,IAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,MAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,EAAK;AAC5B,MAAA,MAAM,IAAI,MAAM,CAAA,YAAA,EAAe,IAAI,YAAY,GAAA,CAAI,MAAM,CAAA,GAAA,EAAM,IAAI,CAAA,CAAE,CAAA;AAAA,IACvE;AACA,IAAA,OAAO,MAAM,IAAI,IAAA,EAAK;AAAA,EACxB,SAAS,GAAA,EAAK;AACZ,IAAA,IAAK,GAAA,CAAc,SAAS,YAAA,EAAc;AACxC,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,YAAA,EAAe,IAAI,CAAA,gFAAA,CAA6E,CAAA;AAAA,IAClH;AACA,IAAA,MAAM,GAAA;AAAA,EACR,CAAA,SAAE;AACA,IAAA,YAAA,CAAa,KAAK,CAAA;AAAA,EACpB;AACF;;;ACtCA,IAAO,eAAQ,aAAA,CAAkD;AAAA,EAC/D,EAAA,EAAI,0BAAA;AAAA,EACJ,WAAA,EAAa,qCAAA;AAAA,EAEb,OAAA,EAAS;AAAA,IACP,MAAM,OAAA,CAAQ,GAAA,EAAsB,KAAA,EAA0D;AAC5F,MAAA,MAAM,UAAA,GAAa,KAAA,CAAM,IAAA,GAAO,CAAC,CAAA;AACjC,MAAA,IAAI,CAAC,UAAA,EAAY;AACf,QAAA,GAAA,CAAI,EAAA,EAAI,QAAQ,+BAA+B,CAAA;AAC/C,QAAA,OAAO,EAAE,QAAA,EAAU,CAAA,EAAG,QAAQ,EAAE,EAAA,EAAI,IAAG,EAAE;AAAA,MAC3C;AAEA,MAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAkC,CAAA,UAAA,EAAa,kBAAA,CAAmB,UAAU,CAAC,CAAA,KAAA,CAAA,EAAS,EAAE,IAAA,EAAM,UAAA,EAAY,CAAA;AAC/H,MAAA,GAAA,CAAI,EAAA,EAAI,OAAA,GAAU,CAAA,OAAA,EAAU,MAAA,CAAO,EAAE,CAAA,CAAE,CAAA;AACvC,MAAA,OAAO,EAAE,UAAU,CAAA,EAAG,MAAA,EAAQ,EAAE,EAAA,EAAI,MAAA,CAAO,IAAG,EAAE;AAAA,IAClD;AAAA;AAEJ,CAAC","file":"link.js","sourcesContent":["/**\n * HTTP client for marketplace service via Gateway.\n */\n\nimport { useEnv } from '@kb-labs/sdk';\n\nconst DEFAULT_GATEWAY_URL = 'http://127.0.0.1:4000';\nconst MARKETPLACE_PREFIX = '/api/v1/marketplace';\nconst FETCH_TIMEOUT_MS = 30_000;\n\nfunction getBaseUrl(): string {\n const marketplaceUrl = useEnv('KB_MARKETPLACE_URL');\n if (marketplaceUrl) {\n return `${marketplaceUrl}${MARKETPLACE_PREFIX}`;\n }\n const gateway = useEnv('KB_GATEWAY_URL') ?? DEFAULT_GATEWAY_URL;\n return `${gateway}${MARKETPLACE_PREFIX}`;\n}\n\nexport async function post<T = unknown>(path: string, body: Record<string, unknown>): Promise<T> {\n const url = `${getBaseUrl()}${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' },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Marketplace ${path} 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(`Marketplace ${path} timed out — is the marketplace service running? (kb-dev start marketplace)`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function patch<T = unknown>(path: string, body: Record<string, unknown>): Promise<T> {\n const url = `${getBaseUrl()}${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' },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Marketplace ${path} 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(`Marketplace ${path} timed out — is the marketplace service running? (kb-dev start marketplace)`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function del<T = unknown>(path: string, body?: Record<string, unknown>): Promise<T> {\n const url = `${getBaseUrl()}${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: 'DELETE',\n headers: body ? { 'Content-Type': 'application/json' } : {},\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Marketplace ${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(`Marketplace ${path} timed out — is the marketplace service running? (kb-dev start marketplace)`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function get<T = unknown>(path: string, params?: Record<string, string>): Promise<T> {\n const url = new URL(`${getBaseUrl()}${path}`);\n if (params) {\n for (const [k, v] of Object.entries(params)) {url.searchParams.set(k, v);}\n }\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const res = await fetch(url.toString(), { signal: controller.signal });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Marketplace ${path} 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(`Marketplace ${path} timed out — is the marketplace service running? (kb-dev start marketplace)`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n","import { defineCommand, type PluginContextV3, type CommandResult } from '@kb-labs/sdk';\nimport { post } from '../../http.js';\n\ninterface LinkInput { argv?: string[] }\n\nexport default defineCommand<unknown, LinkInput, { id: string }>({\n id: 'marketplace:plugins:link',\n description: 'Link a local plugin for development',\n\n handler: {\n async execute(ctx: PluginContextV3, input: LinkInput): Promise<CommandResult<{ id: string }>> {\n const pluginPath = input.argv?.[0];\n if (!pluginPath) {\n ctx.ui?.error?.('Specify a plugin path to link');\n return { exitCode: 1, result: { id: '' } };\n }\n // POST /packages first to install/register the local path, then link it\n const result = await post<{ ok: boolean; id: string }>(`/packages/${encodeURIComponent(pluginPath)}/link`, { path: pluginPath });\n ctx.ui?.success?.(`Linked ${result.id}`);\n return { exitCode: 0, result: { id: result.id } };\n },\n },\n});\n"]}
1
+ {"version":3,"sources":["../../../src/http.ts","../../../src/scope.ts","../../../src/commands/plugins/link.ts"],"names":["path","projectRoot"],"mappings":"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","file":"link.js","sourcesContent":["/**\n * HTTP client for marketplace service via Gateway.\n */\n\nimport { useEnv } from '@kb-labs/sdk';\n\nconst DEFAULT_GATEWAY_URL = 'http://127.0.0.1:4000';\nconst MARKETPLACE_PREFIX = '/api/v1/marketplace';\nconst FETCH_TIMEOUT_MS = 30_000;\n\nfunction getBaseUrl(): string {\n const marketplaceUrl = useEnv('KB_MARKETPLACE_URL');\n if (marketplaceUrl) {\n return `${marketplaceUrl}${MARKETPLACE_PREFIX}`;\n }\n const gateway = useEnv('KB_GATEWAY_URL') ?? DEFAULT_GATEWAY_URL;\n return `${gateway}${MARKETPLACE_PREFIX}`;\n}\n\nexport async function post<T = unknown>(path: string, body: Record<string, unknown>): Promise<T> {\n const url = `${getBaseUrl()}${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' },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Marketplace ${path} 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(`Marketplace ${path} timed out — is the marketplace service running? (kb-dev start marketplace)`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function patch<T = unknown>(path: string, body: Record<string, unknown>): Promise<T> {\n const url = `${getBaseUrl()}${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' },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Marketplace ${path} 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(`Marketplace ${path} timed out — is the marketplace service running? (kb-dev start marketplace)`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function del<T = unknown>(path: string, body?: Record<string, unknown>): Promise<T> {\n const url = `${getBaseUrl()}${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: 'DELETE',\n headers: body ? { 'Content-Type': 'application/json' } : {},\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Marketplace ${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(`Marketplace ${path} timed out — is the marketplace service running? (kb-dev start marketplace)`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function get<T = unknown>(path: string, params?: Record<string, string>): Promise<T> {\n const url = new URL(`${getBaseUrl()}${path}`);\n if (params) {\n for (const [k, v] of Object.entries(params)) {url.searchParams.set(k, v);}\n }\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const res = await fetch(url.toString(), { signal: controller.signal });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Marketplace ${path} 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(`Marketplace ${path} timed out — is the marketplace service running? (kb-dev start marketplace)`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n","/**\n * @module @kb-labs/marketplace-entry/scope\n *\n * Client-side scope resolution for marketplace CLI commands. Uses the\n * canonical `findProjectConfigRoot` helper from `@kb-labs/core-workspace`\n * so the detection rules (which filenames count, how walk-up stops) match\n * what the config loader and the marketplace daemon enforce.\n *\n * Rules:\n * - `project` is the default if cwd (or any ancestor up to the filesystem\n * root) contains `.kb/kb.config.{json,jsonc}`.\n * - Otherwise the default is `platform`.\n * - `--scope` always overrides detection.\n * - For scope=\"project\" we return the absolute projectRoot so the daemon\n * doesn't need to re-discover it.\n */\n\nimport { findProjectConfigRoot } from '@kb-labs/core-workspace';\nimport type { MarketplaceScope, MarketplaceQueryScope } from '@kb-labs/marketplace-contracts';\n\nexport const SCOPE_FLAG_CHOICES: readonly MarketplaceScope[] = ['platform', 'project'];\nexport const QUERY_SCOPE_FLAG_CHOICES: readonly MarketplaceQueryScope[] = ['platform', 'project', 'all'];\n\nexport interface ResolvedCliScope {\n scope: MarketplaceScope;\n projectRoot?: string;\n /** How the scope was determined — surfaces in --verbose logs. */\n reason: 'flag' | 'auto-detect' | 'fallback';\n}\n\nexport interface ResolvedCliQueryScope extends Omit<ResolvedCliScope, 'scope'> {\n scope: MarketplaceQueryScope;\n}\n\n/**\n * Resolve the effective scope for a mutating command (`link`, `unlink`,\n * `install`, ...). `flag` is the value of `--scope` (if supplied). The\n * helper never returns `'all'` for mutating commands — callers restrict\n * choices via `SCOPE_FLAG_CHOICES`.\n */\nexport async function resolveCliScope(\n cwd: string,\n flag: string | undefined,\n): Promise<ResolvedCliScope> {\n if (flag) {\n assertMutatingScope(flag);\n if (flag === 'project') {\n const projectRoot = await findProjectConfigRoot(cwd);\n if (!projectRoot) {\n throw new CliScopeError(\n 'SCOPE_PROJECT_ROOT_NOT_FOUND',\n `--scope=project requires a .kb/kb.config.{json,jsonc} ancestor of ${cwd} — none found.`,\n );\n }\n return { scope: 'project', projectRoot, reason: 'flag' };\n }\n return { scope: 'platform', reason: 'flag' };\n }\n\n const projectRoot = await findProjectConfigRoot(cwd);\n if (projectRoot) {\n return { scope: 'project', projectRoot, reason: 'auto-detect' };\n }\n return { scope: 'platform', reason: 'fallback' };\n}\n\n/**\n * Resolve the effective scope for a read-only command (`list`). Accepts\n * `'all'` as an explicit flag value.\n */\nexport async function resolveCliQueryScope(\n cwd: string,\n flag: string | undefined,\n): Promise<ResolvedCliQueryScope> {\n if (flag === 'all') {\n const projectRoot = await findProjectConfigRoot(cwd);\n return { scope: 'all', projectRoot, reason: 'flag' };\n }\n const base = await resolveCliScope(cwd, flag);\n return { scope: base.scope, projectRoot: base.projectRoot, reason: base.reason };\n}\n\nfunction assertMutatingScope(value: string): asserts value is MarketplaceScope {\n if (!SCOPE_FLAG_CHOICES.includes(value as MarketplaceScope)) {\n throw new CliScopeError(\n 'SCOPE_INVALID',\n `--scope must be one of: ${SCOPE_FLAG_CHOICES.join(', ')} (got ${JSON.stringify(value)})`,\n );\n }\n}\n\nexport class CliScopeError extends Error {\n readonly code: string;\n constructor(code: string, message: string) {\n super(message);\n this.code = code;\n this.name = 'CliScopeError';\n }\n}\n\n/**\n * Build the body-payload fragment expected by `parseMutatingScope` on the\n * API side: `{ scope, projectRoot? }`. Kept as a helper so every command\n * sends the same shape and nothing drifts out-of-sync with the server.\n */\nexport function scopeBody(ctx: ResolvedCliScope | ResolvedCliQueryScope): {\n scope: MarketplaceScope | MarketplaceQueryScope;\n projectRoot?: string;\n} {\n return {\n scope: ctx.scope,\n ...(ctx.projectRoot ? { projectRoot: ctx.projectRoot } : {}),\n };\n}\n","import { defineCommand, type PluginContextV3, type CommandResult } from '@kb-labs/sdk';\nimport * as path from 'node:path';\nimport { post } from '../../http.js';\nimport { resolveCliScope, scopeBody, CliScopeError } from '../../scope.js';\n\ninterface LinkFlags {\n scope?: string;\n json?: boolean;\n}\n\ninterface LinkInput {\n argv?: string[];\n flags?: LinkFlags;\n}\n\ninterface LinkResponse {\n id: string;\n version: string;\n primaryKind: string;\n provides: string[];\n packageRoot: string;\n scope: string;\n}\n\nexport default defineCommand<unknown, LinkInput, { id: string; scope: string }>({\n id: 'marketplace:plugins:link',\n description: 'Link a local plugin for development',\n\n handler: {\n async execute(ctx: PluginContextV3, input: LinkInput): Promise<CommandResult<{ id: string; scope: string }>> {\n const pluginPath = input.argv?.[0];\n if (!pluginPath) {\n ctx.ui?.error?.('Specify a plugin path to link');\n return { exitCode: 1, result: { id: '', scope: '' } };\n }\n const flags = (input.flags ?? input) as LinkFlags;\n\n let scopeCtx;\n try {\n scopeCtx = await resolveCliScope(ctx.cwd, flags.scope);\n } catch (err) {\n if (err instanceof CliScopeError) {\n ctx.ui?.error?.(err.message);\n return { exitCode: 1, result: { id: '', scope: '' } };\n }\n throw err;\n }\n\n // Resolve path against cwd so the daemon always receives an absolute\n // location. That keeps the server side from inheriting any ambient cwd.\n const absPath = path.resolve(ctx.cwd, pluginPath);\n\n const result = await post<LinkResponse>(\n `/packages/${encodeURIComponent(pluginPath)}/link`,\n { path: absPath, ...scopeBody(scopeCtx) },\n );\n\n ctx.ui?.success?.(`Linked ${result.id} (${result.scope})`);\n return { exitCode: 0, result: { id: result.id, scope: result.scope } };\n },\n },\n});\n"]}
@@ -2,6 +2,7 @@ import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
2
2
 
3
3
  interface ListFlags {
4
4
  json?: boolean;
5
+ scope?: string;
5
6
  }
6
7
  interface ListInput {
7
8
  argv?: string[];
@@ -14,6 +15,7 @@ interface EntryRow {
14
15
  primaryKind: string;
15
16
  provides: string[];
16
17
  enabled: boolean;
18
+ scope: 'platform' | 'project';
17
19
  }
18
20
  interface ListResultData {
19
21
  entries: EntryRow[];
@@ -1,4 +1,5 @@
1
1
  import { defineCommand, useEnv } from '@kb-labs/sdk';
2
+ import { findProjectConfigRoot } from '@kb-labs/core-workspace';
2
3
 
3
4
  // src/commands/plugins/list.ts
4
5
  var DEFAULT_GATEWAY_URL = "http://127.0.0.1:4000";
@@ -37,6 +38,52 @@ async function get(path, params) {
37
38
  clearTimeout(timer);
38
39
  }
39
40
  }
41
+ var SCOPE_FLAG_CHOICES = ["platform", "project"];
42
+ async function resolveCliScope(cwd, flag) {
43
+ if (flag) {
44
+ assertMutatingScope(flag);
45
+ if (flag === "project") {
46
+ const projectRoot2 = await findProjectConfigRoot(cwd);
47
+ if (!projectRoot2) {
48
+ throw new CliScopeError(
49
+ "SCOPE_PROJECT_ROOT_NOT_FOUND",
50
+ `--scope=project requires a .kb/kb.config.{json,jsonc} ancestor of ${cwd} \u2014 none found.`
51
+ );
52
+ }
53
+ return { scope: "project", projectRoot: projectRoot2, reason: "flag" };
54
+ }
55
+ return { scope: "platform", reason: "flag" };
56
+ }
57
+ const projectRoot = await findProjectConfigRoot(cwd);
58
+ if (projectRoot) {
59
+ return { scope: "project", projectRoot, reason: "auto-detect" };
60
+ }
61
+ return { scope: "platform", reason: "fallback" };
62
+ }
63
+ async function resolveCliQueryScope(cwd, flag) {
64
+ if (flag === "all") {
65
+ const projectRoot = await findProjectConfigRoot(cwd);
66
+ return { scope: "all", projectRoot, reason: "flag" };
67
+ }
68
+ const base = await resolveCliScope(cwd, flag);
69
+ return { scope: base.scope, projectRoot: base.projectRoot, reason: base.reason };
70
+ }
71
+ function assertMutatingScope(value) {
72
+ if (!SCOPE_FLAG_CHOICES.includes(value)) {
73
+ throw new CliScopeError(
74
+ "SCOPE_INVALID",
75
+ `--scope must be one of: ${SCOPE_FLAG_CHOICES.join(", ")} (got ${JSON.stringify(value)})`
76
+ );
77
+ }
78
+ }
79
+ var CliScopeError = class extends Error {
80
+ code;
81
+ constructor(code, message) {
82
+ super(message);
83
+ this.code = code;
84
+ this.name = "CliScopeError";
85
+ }
86
+ };
40
87
 
41
88
  // src/commands/plugins/list.ts
42
89
  var list_default = defineCommand({
@@ -45,18 +92,34 @@ var list_default = defineCommand({
45
92
  handler: {
46
93
  async execute(ctx, input) {
47
94
  const flags = input.flags ?? input;
48
- const data = await get("/packages", { kind: "plugin" });
95
+ let scopeCtx;
96
+ try {
97
+ scopeCtx = await resolveCliQueryScope(ctx.cwd, flags.scope);
98
+ } catch (err) {
99
+ if (err instanceof CliScopeError) {
100
+ ctx.ui?.error?.(err.message);
101
+ return { exitCode: 1, result: { entries: [], total: 0 } };
102
+ }
103
+ throw err;
104
+ }
105
+ const query = { kind: "plugin", scope: scopeCtx.scope };
106
+ if (scopeCtx.projectRoot) {
107
+ query.projectRoot = scopeCtx.projectRoot;
108
+ }
109
+ const data = await get("/packages", query);
49
110
  if (flags.json) {
50
111
  ctx.ui?.json?.(data);
51
112
  } else {
52
113
  const enabled = data.entries.filter((e) => e.enabled !== false).length;
53
114
  const disabled = data.total - enabled;
54
- ctx.ui?.success?.(`${data.total} plugins (${enabled} enabled, ${disabled} disabled)`, {
115
+ const showScopeColumn = scopeCtx.scope === "all";
116
+ ctx.ui?.success?.(`${data.total} plugins (${enabled} enabled, ${disabled} disabled) \u2014 scope=${scopeCtx.scope}`, {
55
117
  sections: [{
56
118
  header: "Plugins",
57
119
  items: data.entries.map((e) => {
58
120
  const icon = e.enabled !== false ? "\u2705" : "\u23F8";
59
- return `${icon} ${e.id} ${e.version} (${e.source})`;
121
+ const scopeTag = showScopeColumn ? ` [${e.scope}]` : "";
122
+ return `${icon} ${e.id} ${e.version} (${e.source})${scopeTag}`;
60
123
  })
61
124
  }]
62
125
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/http.ts","../../../src/commands/plugins/list.ts"],"names":[],"mappings":"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","file":"list.js","sourcesContent":["/**\n * HTTP client for marketplace service via Gateway.\n */\n\nimport { useEnv } from '@kb-labs/sdk';\n\nconst DEFAULT_GATEWAY_URL = 'http://127.0.0.1:4000';\nconst MARKETPLACE_PREFIX = '/api/v1/marketplace';\nconst FETCH_TIMEOUT_MS = 30_000;\n\nfunction getBaseUrl(): string {\n const marketplaceUrl = useEnv('KB_MARKETPLACE_URL');\n if (marketplaceUrl) {\n return `${marketplaceUrl}${MARKETPLACE_PREFIX}`;\n }\n const gateway = useEnv('KB_GATEWAY_URL') ?? DEFAULT_GATEWAY_URL;\n return `${gateway}${MARKETPLACE_PREFIX}`;\n}\n\nexport async function post<T = unknown>(path: string, body: Record<string, unknown>): Promise<T> {\n const url = `${getBaseUrl()}${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' },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Marketplace ${path} 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(`Marketplace ${path} timed out — is the marketplace service running? (kb-dev start marketplace)`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function patch<T = unknown>(path: string, body: Record<string, unknown>): Promise<T> {\n const url = `${getBaseUrl()}${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' },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Marketplace ${path} 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(`Marketplace ${path} timed out — is the marketplace service running? (kb-dev start marketplace)`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function del<T = unknown>(path: string, body?: Record<string, unknown>): Promise<T> {\n const url = `${getBaseUrl()}${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: 'DELETE',\n headers: body ? { 'Content-Type': 'application/json' } : {},\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Marketplace ${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(`Marketplace ${path} timed out — is the marketplace service running? (kb-dev start marketplace)`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function get<T = unknown>(path: string, params?: Record<string, string>): Promise<T> {\n const url = new URL(`${getBaseUrl()}${path}`);\n if (params) {\n for (const [k, v] of Object.entries(params)) {url.searchParams.set(k, v);}\n }\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const res = await fetch(url.toString(), { signal: controller.signal });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Marketplace ${path} 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(`Marketplace ${path} timed out — is the marketplace service running? (kb-dev start marketplace)`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n","import { defineCommand, type PluginContextV3, type CommandResult } from '@kb-labs/sdk';\nimport { get } from '../../http.js';\n\ninterface ListFlags { json?: boolean }\ninterface ListInput { argv?: string[]; flags?: ListFlags }\n\ninterface EntryRow {\n id: string;\n version: string;\n source: string;\n primaryKind: string;\n provides: string[];\n enabled: boolean;\n}\n\ninterface ListResultData {\n entries: EntryRow[];\n total: number;\n}\n\nexport default defineCommand<unknown, ListInput, ListResultData>({\n id: 'marketplace:plugins:list',\n description: 'List installed plugins',\n\n handler: {\n async execute(ctx: PluginContextV3, input: ListInput): Promise<CommandResult<ListResultData>> {\n const flags = (input.flags ?? input) as ListFlags;\n const data = await get<ListResultData>('/packages', { kind: 'plugin' });\n\n if (flags.json) {\n ctx.ui?.json?.(data);\n } else {\n const enabled = data.entries.filter(e => e.enabled !== false).length;\n const disabled = data.total - enabled;\n ctx.ui?.success?.(`${data.total} plugins (${enabled} enabled, ${disabled} disabled)`, {\n sections: [{\n header: 'Plugins',\n items: data.entries.map(e => {\n const icon = e.enabled !== false ? '✅' : '⏸';\n return `${icon} ${e.id} ${e.version} (${e.source})`;\n }),\n }],\n });\n }\n\n return { exitCode: 0, result: data };\n },\n },\n});\n"]}
1
+ {"version":3,"sources":["../../../src/http.ts","../../../src/scope.ts","../../../src/commands/plugins/list.ts"],"names":["projectRoot"],"mappings":"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","file":"list.js","sourcesContent":["/**\n * HTTP client for marketplace service via Gateway.\n */\n\nimport { useEnv } from '@kb-labs/sdk';\n\nconst DEFAULT_GATEWAY_URL = 'http://127.0.0.1:4000';\nconst MARKETPLACE_PREFIX = '/api/v1/marketplace';\nconst FETCH_TIMEOUT_MS = 30_000;\n\nfunction getBaseUrl(): string {\n const marketplaceUrl = useEnv('KB_MARKETPLACE_URL');\n if (marketplaceUrl) {\n return `${marketplaceUrl}${MARKETPLACE_PREFIX}`;\n }\n const gateway = useEnv('KB_GATEWAY_URL') ?? DEFAULT_GATEWAY_URL;\n return `${gateway}${MARKETPLACE_PREFIX}`;\n}\n\nexport async function post<T = unknown>(path: string, body: Record<string, unknown>): Promise<T> {\n const url = `${getBaseUrl()}${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' },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Marketplace ${path} 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(`Marketplace ${path} timed out — is the marketplace service running? (kb-dev start marketplace)`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function patch<T = unknown>(path: string, body: Record<string, unknown>): Promise<T> {\n const url = `${getBaseUrl()}${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' },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Marketplace ${path} 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(`Marketplace ${path} timed out — is the marketplace service running? (kb-dev start marketplace)`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function del<T = unknown>(path: string, body?: Record<string, unknown>): Promise<T> {\n const url = `${getBaseUrl()}${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: 'DELETE',\n headers: body ? { 'Content-Type': 'application/json' } : {},\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Marketplace ${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(`Marketplace ${path} timed out — is the marketplace service running? (kb-dev start marketplace)`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function get<T = unknown>(path: string, params?: Record<string, string>): Promise<T> {\n const url = new URL(`${getBaseUrl()}${path}`);\n if (params) {\n for (const [k, v] of Object.entries(params)) {url.searchParams.set(k, v);}\n }\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const res = await fetch(url.toString(), { signal: controller.signal });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Marketplace ${path} 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(`Marketplace ${path} timed out — is the marketplace service running? (kb-dev start marketplace)`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n","/**\n * @module @kb-labs/marketplace-entry/scope\n *\n * Client-side scope resolution for marketplace CLI commands. Uses the\n * canonical `findProjectConfigRoot` helper from `@kb-labs/core-workspace`\n * so the detection rules (which filenames count, how walk-up stops) match\n * what the config loader and the marketplace daemon enforce.\n *\n * Rules:\n * - `project` is the default if cwd (or any ancestor up to the filesystem\n * root) contains `.kb/kb.config.{json,jsonc}`.\n * - Otherwise the default is `platform`.\n * - `--scope` always overrides detection.\n * - For scope=\"project\" we return the absolute projectRoot so the daemon\n * doesn't need to re-discover it.\n */\n\nimport { findProjectConfigRoot } from '@kb-labs/core-workspace';\nimport type { MarketplaceScope, MarketplaceQueryScope } from '@kb-labs/marketplace-contracts';\n\nexport const SCOPE_FLAG_CHOICES: readonly MarketplaceScope[] = ['platform', 'project'];\nexport const QUERY_SCOPE_FLAG_CHOICES: readonly MarketplaceQueryScope[] = ['platform', 'project', 'all'];\n\nexport interface ResolvedCliScope {\n scope: MarketplaceScope;\n projectRoot?: string;\n /** How the scope was determined — surfaces in --verbose logs. */\n reason: 'flag' | 'auto-detect' | 'fallback';\n}\n\nexport interface ResolvedCliQueryScope extends Omit<ResolvedCliScope, 'scope'> {\n scope: MarketplaceQueryScope;\n}\n\n/**\n * Resolve the effective scope for a mutating command (`link`, `unlink`,\n * `install`, ...). `flag` is the value of `--scope` (if supplied). The\n * helper never returns `'all'` for mutating commands — callers restrict\n * choices via `SCOPE_FLAG_CHOICES`.\n */\nexport async function resolveCliScope(\n cwd: string,\n flag: string | undefined,\n): Promise<ResolvedCliScope> {\n if (flag) {\n assertMutatingScope(flag);\n if (flag === 'project') {\n const projectRoot = await findProjectConfigRoot(cwd);\n if (!projectRoot) {\n throw new CliScopeError(\n 'SCOPE_PROJECT_ROOT_NOT_FOUND',\n `--scope=project requires a .kb/kb.config.{json,jsonc} ancestor of ${cwd} — none found.`,\n );\n }\n return { scope: 'project', projectRoot, reason: 'flag' };\n }\n return { scope: 'platform', reason: 'flag' };\n }\n\n const projectRoot = await findProjectConfigRoot(cwd);\n if (projectRoot) {\n return { scope: 'project', projectRoot, reason: 'auto-detect' };\n }\n return { scope: 'platform', reason: 'fallback' };\n}\n\n/**\n * Resolve the effective scope for a read-only command (`list`). Accepts\n * `'all'` as an explicit flag value.\n */\nexport async function resolveCliQueryScope(\n cwd: string,\n flag: string | undefined,\n): Promise<ResolvedCliQueryScope> {\n if (flag === 'all') {\n const projectRoot = await findProjectConfigRoot(cwd);\n return { scope: 'all', projectRoot, reason: 'flag' };\n }\n const base = await resolveCliScope(cwd, flag);\n return { scope: base.scope, projectRoot: base.projectRoot, reason: base.reason };\n}\n\nfunction assertMutatingScope(value: string): asserts value is MarketplaceScope {\n if (!SCOPE_FLAG_CHOICES.includes(value as MarketplaceScope)) {\n throw new CliScopeError(\n 'SCOPE_INVALID',\n `--scope must be one of: ${SCOPE_FLAG_CHOICES.join(', ')} (got ${JSON.stringify(value)})`,\n );\n }\n}\n\nexport class CliScopeError extends Error {\n readonly code: string;\n constructor(code: string, message: string) {\n super(message);\n this.code = code;\n this.name = 'CliScopeError';\n }\n}\n\n/**\n * Build the body-payload fragment expected by `parseMutatingScope` on the\n * API side: `{ scope, projectRoot? }`. Kept as a helper so every command\n * sends the same shape and nothing drifts out-of-sync with the server.\n */\nexport function scopeBody(ctx: ResolvedCliScope | ResolvedCliQueryScope): {\n scope: MarketplaceScope | MarketplaceQueryScope;\n projectRoot?: string;\n} {\n return {\n scope: ctx.scope,\n ...(ctx.projectRoot ? { projectRoot: ctx.projectRoot } : {}),\n };\n}\n","import { defineCommand, type PluginContextV3, type CommandResult } from '@kb-labs/sdk';\nimport { get } from '../../http.js';\nimport { resolveCliQueryScope, CliScopeError } from '../../scope.js';\n\ninterface ListFlags {\n json?: boolean;\n scope?: string;\n}\ninterface ListInput { argv?: string[]; flags?: ListFlags }\n\ninterface EntryRow {\n id: string;\n version: string;\n source: string;\n primaryKind: string;\n provides: string[];\n enabled: boolean;\n scope: 'platform' | 'project';\n}\n\ninterface ListResultData {\n entries: EntryRow[];\n total: number;\n}\n\nexport default defineCommand<unknown, ListInput, ListResultData>({\n id: 'marketplace:plugins:list',\n description: 'List installed plugins',\n\n handler: {\n async execute(ctx: PluginContextV3, input: ListInput): Promise<CommandResult<ListResultData>> {\n const flags = (input.flags ?? input) as ListFlags;\n\n let scopeCtx;\n try {\n scopeCtx = await resolveCliQueryScope(ctx.cwd, flags.scope);\n } catch (err) {\n if (err instanceof CliScopeError) {\n ctx.ui?.error?.(err.message);\n return { exitCode: 1, result: { entries: [], total: 0 } };\n }\n throw err;\n }\n\n const query: Record<string, string> = { kind: 'plugin', scope: scopeCtx.scope };\n if (scopeCtx.projectRoot) {\n query.projectRoot = scopeCtx.projectRoot;\n }\n const data = await get<ListResultData>('/packages', query);\n\n if (flags.json) {\n ctx.ui?.json?.(data);\n } else {\n const enabled = data.entries.filter(e => e.enabled !== false).length;\n const disabled = data.total - enabled;\n const showScopeColumn = scopeCtx.scope === 'all';\n ctx.ui?.success?.(`${data.total} plugins (${enabled} enabled, ${disabled} disabled) — scope=${scopeCtx.scope}`, {\n sections: [{\n header: 'Plugins',\n items: data.entries.map(e => {\n const icon = e.enabled !== false ? '✅' : '⏸';\n const scopeTag = showScopeColumn ? ` [${e.scope}]` : '';\n return `${icon} ${e.id} ${e.version} (${e.source})${scopeTag}`;\n }),\n }],\n });\n }\n\n return { exitCode: 0, result: data };\n },\n },\n});\n"]}
@@ -1,10 +1,15 @@
1
1
  import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
2
2
 
3
+ interface UnlinkFlags {
4
+ scope?: string;
5
+ }
3
6
  interface UnlinkInput {
4
7
  argv?: string[];
8
+ flags?: UnlinkFlags;
5
9
  }
6
10
  declare const _default: _kb_labs_shared_command_kit.CommandHandlerV3<unknown, UnlinkInput, {
7
11
  packageId: string;
12
+ scope: string;
8
13
  }>;
9
14
 
10
15
  export { _default as default };
@@ -1,4 +1,5 @@
1
1
  import { defineCommand, useEnv } from '@kb-labs/sdk';
2
+ import { findProjectConfigRoot } from '@kb-labs/core-workspace';
2
3
 
3
4
  // src/commands/plugins/unlink.ts
4
5
  var DEFAULT_GATEWAY_URL = "http://127.0.0.1:4000";
@@ -40,6 +41,50 @@ async function del(path, body) {
40
41
  clearTimeout(timer);
41
42
  }
42
43
  }
44
+ var SCOPE_FLAG_CHOICES = ["platform", "project"];
45
+ async function resolveCliScope(cwd, flag) {
46
+ if (flag) {
47
+ assertMutatingScope(flag);
48
+ if (flag === "project") {
49
+ const projectRoot2 = await findProjectConfigRoot(cwd);
50
+ if (!projectRoot2) {
51
+ throw new CliScopeError(
52
+ "SCOPE_PROJECT_ROOT_NOT_FOUND",
53
+ `--scope=project requires a .kb/kb.config.{json,jsonc} ancestor of ${cwd} \u2014 none found.`
54
+ );
55
+ }
56
+ return { scope: "project", projectRoot: projectRoot2, reason: "flag" };
57
+ }
58
+ return { scope: "platform", reason: "flag" };
59
+ }
60
+ const projectRoot = await findProjectConfigRoot(cwd);
61
+ if (projectRoot) {
62
+ return { scope: "project", projectRoot, reason: "auto-detect" };
63
+ }
64
+ return { scope: "platform", reason: "fallback" };
65
+ }
66
+ function assertMutatingScope(value) {
67
+ if (!SCOPE_FLAG_CHOICES.includes(value)) {
68
+ throw new CliScopeError(
69
+ "SCOPE_INVALID",
70
+ `--scope must be one of: ${SCOPE_FLAG_CHOICES.join(", ")} (got ${JSON.stringify(value)})`
71
+ );
72
+ }
73
+ }
74
+ var CliScopeError = class extends Error {
75
+ code;
76
+ constructor(code, message) {
77
+ super(message);
78
+ this.code = code;
79
+ this.name = "CliScopeError";
80
+ }
81
+ };
82
+ function scopeBody(ctx) {
83
+ return {
84
+ scope: ctx.scope,
85
+ ...ctx.projectRoot ? { projectRoot: ctx.projectRoot } : {}
86
+ };
87
+ }
43
88
 
44
89
  // src/commands/plugins/unlink.ts
45
90
  var unlink_default = defineCommand({
@@ -50,11 +95,22 @@ var unlink_default = defineCommand({
50
95
  const packageId = input.argv?.[0];
51
96
  if (!packageId) {
52
97
  ctx.ui?.error?.("Specify a package ID to unlink");
53
- return { exitCode: 1, result: { packageId: "" } };
98
+ return { exitCode: 1, result: { packageId: "", scope: "" } };
99
+ }
100
+ const flags = input.flags ?? input;
101
+ let scopeCtx;
102
+ try {
103
+ scopeCtx = await resolveCliScope(ctx.cwd, flags.scope);
104
+ } catch (err) {
105
+ if (err instanceof CliScopeError) {
106
+ ctx.ui?.error?.(err.message);
107
+ return { exitCode: 1, result: { packageId: "", scope: "" } };
108
+ }
109
+ throw err;
54
110
  }
55
- await del(`/packages/${encodeURIComponent(packageId)}/link`);
56
- ctx.ui?.success?.(`Unlinked ${packageId}`);
57
- return { exitCode: 0, result: { packageId } };
111
+ await del(`/packages/${encodeURIComponent(packageId)}/link`, scopeBody(scopeCtx));
112
+ ctx.ui?.success?.(`Unlinked ${packageId} (${scopeCtx.scope})`);
113
+ return { exitCode: 0, result: { packageId, scope: scopeCtx.scope } };
58
114
  }
59
115
  }
60
116
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/http.ts","../../../src/commands/plugins/unlink.ts"],"names":[],"mappings":";;;AAMA,IAAM,mBAAA,GAAsB,uBAAA;AAC5B,IAAM,kBAAA,GAAqB,qBAAA;AAC3B,IAAM,gBAAA,GAAmB,GAAA;AAEzB,SAAS,UAAA,GAAqB;AAC5B,EAAA,MAAM,cAAA,GAAiB,OAAO,oBAAoB,CAAA;AAClD,EAAA,IAAI,cAAA,EAAgB;AAClB,IAAA,OAAO,CAAA,EAAG,cAAc,CAAA,EAAG,kBAAkB,CAAA,CAAA;AAAA,EAC/C;AACA,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,gBAAgB,CAAA,IAAK,mBAAA;AAC5C,EAAA,OAAO,CAAA,EAAG,OAAO,CAAA,EAAG,kBAAkB,CAAA,CAAA;AACxC;AAsDA,eAAsB,GAAA,CAAiB,MAAc,IAAA,EAA4C;AAC/F,EAAA,MAAM,GAAA,GAAM,CAAA,EAAG,UAAA,EAAY,GAAG,IAAI,CAAA,CAAA;AAClC,EAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,EAAA,MAAM,QAAQ,UAAA,CAAW,MAAM,UAAA,CAAW,KAAA,IAAS,gBAAgB,CAAA;AACnE,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,MAC3B,MAAA,EAAQ,QAAA;AAAA,MACR,SAAS,IAAA,GAAO,EAAE,cAAA,EAAgB,kBAAA,KAAuB,EAAC;AAAA,MAC1D,IAAA,EAAM,IAAA,GAAO,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA,GAAI,KAAA,CAAA;AAAA,MACpC,QAAQ,UAAA,CAAW;AAAA,KACpB,CAAA;AACD,IAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,MAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,EAAK;AAC5B,MAAA,MAAM,IAAI,MAAM,CAAA,YAAA,EAAe,IAAI,YAAY,GAAA,CAAI,MAAM,CAAA,GAAA,EAAM,IAAI,CAAA,CAAE,CAAA;AAAA,IACvE;AACA,IAAA,IAAI,GAAA,CAAI,WAAW,GAAA,EAAK;AAAE,MAAA,OAAO,KAAA,CAAA;AAAA,IAAgB;AACjD,IAAA,OAAO,MAAM,IAAI,IAAA,EAAK;AAAA,EACxB,SAAS,GAAA,EAAK;AACZ,IAAA,IAAK,GAAA,CAAc,SAAS,YAAA,EAAc;AACxC,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,YAAA,EAAe,IAAI,CAAA,gFAAA,CAA6E,CAAA;AAAA,IAClH;AACA,IAAA,MAAM,GAAA;AAAA,EACR,CAAA,SAAE;AACA,IAAA,YAAA,CAAa,KAAK,CAAA;AAAA,EACpB;AACF;;;AC3FA,IAAO,iBAAQ,aAAA,CAA2D;AAAA,EACxE,EAAA,EAAI,4BAAA;AAAA,EACJ,WAAA,EAAa,iBAAA;AAAA,EAEb,OAAA,EAAS;AAAA,IACP,MAAM,OAAA,CAAQ,GAAA,EAAsB,KAAA,EAAmE;AACrG,MAAA,MAAM,SAAA,GAAY,KAAA,CAAM,IAAA,GAAO,CAAC,CAAA;AAChC,MAAA,IAAI,CAAC,SAAA,EAAW;AACd,QAAA,GAAA,CAAI,EAAA,EAAI,QAAQ,gCAAgC,CAAA;AAChD,QAAA,OAAO,EAAE,QAAA,EAAU,CAAA,EAAG,QAAQ,EAAE,SAAA,EAAW,IAAG,EAAE;AAAA,MAClD;AACA,MAAA,MAAM,GAAA,CAAI,CAAA,UAAA,EAAa,kBAAA,CAAmB,SAAS,CAAC,CAAA,KAAA,CAAO,CAAA;AAC3D,MAAA,GAAA,CAAI,EAAA,EAAI,OAAA,GAAU,CAAA,SAAA,EAAY,SAAS,CAAA,CAAE,CAAA;AACzC,MAAA,OAAO,EAAE,QAAA,EAAU,CAAA,EAAG,MAAA,EAAQ,EAAE,WAAU,EAAE;AAAA,IAC9C;AAAA;AAEJ,CAAC","file":"unlink.js","sourcesContent":["/**\n * HTTP client for marketplace service via Gateway.\n */\n\nimport { useEnv } from '@kb-labs/sdk';\n\nconst DEFAULT_GATEWAY_URL = 'http://127.0.0.1:4000';\nconst MARKETPLACE_PREFIX = '/api/v1/marketplace';\nconst FETCH_TIMEOUT_MS = 30_000;\n\nfunction getBaseUrl(): string {\n const marketplaceUrl = useEnv('KB_MARKETPLACE_URL');\n if (marketplaceUrl) {\n return `${marketplaceUrl}${MARKETPLACE_PREFIX}`;\n }\n const gateway = useEnv('KB_GATEWAY_URL') ?? DEFAULT_GATEWAY_URL;\n return `${gateway}${MARKETPLACE_PREFIX}`;\n}\n\nexport async function post<T = unknown>(path: string, body: Record<string, unknown>): Promise<T> {\n const url = `${getBaseUrl()}${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' },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Marketplace ${path} 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(`Marketplace ${path} timed out — is the marketplace service running? (kb-dev start marketplace)`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function patch<T = unknown>(path: string, body: Record<string, unknown>): Promise<T> {\n const url = `${getBaseUrl()}${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' },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Marketplace ${path} 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(`Marketplace ${path} timed out — is the marketplace service running? (kb-dev start marketplace)`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function del<T = unknown>(path: string, body?: Record<string, unknown>): Promise<T> {\n const url = `${getBaseUrl()}${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: 'DELETE',\n headers: body ? { 'Content-Type': 'application/json' } : {},\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Marketplace ${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(`Marketplace ${path} timed out — is the marketplace service running? (kb-dev start marketplace)`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function get<T = unknown>(path: string, params?: Record<string, string>): Promise<T> {\n const url = new URL(`${getBaseUrl()}${path}`);\n if (params) {\n for (const [k, v] of Object.entries(params)) {url.searchParams.set(k, v);}\n }\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const res = await fetch(url.toString(), { signal: controller.signal });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Marketplace ${path} 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(`Marketplace ${path} timed out — is the marketplace service running? (kb-dev start marketplace)`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n","import { defineCommand, type PluginContextV3, type CommandResult } from '@kb-labs/sdk';\nimport { del } from '../../http.js';\n\ninterface UnlinkInput { argv?: string[] }\n\nexport default defineCommand<unknown, UnlinkInput, { packageId: string }>({\n id: 'marketplace:plugins:unlink',\n description: 'Unlink a plugin',\n\n handler: {\n async execute(ctx: PluginContextV3, input: UnlinkInput): Promise<CommandResult<{ packageId: string }>> {\n const packageId = input.argv?.[0];\n if (!packageId) {\n ctx.ui?.error?.('Specify a package ID to unlink');\n return { exitCode: 1, result: { packageId: '' } };\n }\n await del(`/packages/${encodeURIComponent(packageId)}/link`);\n ctx.ui?.success?.(`Unlinked ${packageId}`);\n return { exitCode: 0, result: { packageId } };\n },\n },\n});\n"]}
1
+ {"version":3,"sources":["../../../src/http.ts","../../../src/scope.ts","../../../src/commands/plugins/unlink.ts"],"names":["projectRoot"],"mappings":"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","file":"unlink.js","sourcesContent":["/**\n * HTTP client for marketplace service via Gateway.\n */\n\nimport { useEnv } from '@kb-labs/sdk';\n\nconst DEFAULT_GATEWAY_URL = 'http://127.0.0.1:4000';\nconst MARKETPLACE_PREFIX = '/api/v1/marketplace';\nconst FETCH_TIMEOUT_MS = 30_000;\n\nfunction getBaseUrl(): string {\n const marketplaceUrl = useEnv('KB_MARKETPLACE_URL');\n if (marketplaceUrl) {\n return `${marketplaceUrl}${MARKETPLACE_PREFIX}`;\n }\n const gateway = useEnv('KB_GATEWAY_URL') ?? DEFAULT_GATEWAY_URL;\n return `${gateway}${MARKETPLACE_PREFIX}`;\n}\n\nexport async function post<T = unknown>(path: string, body: Record<string, unknown>): Promise<T> {\n const url = `${getBaseUrl()}${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' },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Marketplace ${path} 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(`Marketplace ${path} timed out — is the marketplace service running? (kb-dev start marketplace)`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function patch<T = unknown>(path: string, body: Record<string, unknown>): Promise<T> {\n const url = `${getBaseUrl()}${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' },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Marketplace ${path} 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(`Marketplace ${path} timed out — is the marketplace service running? (kb-dev start marketplace)`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function del<T = unknown>(path: string, body?: Record<string, unknown>): Promise<T> {\n const url = `${getBaseUrl()}${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: 'DELETE',\n headers: body ? { 'Content-Type': 'application/json' } : {},\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Marketplace ${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(`Marketplace ${path} timed out — is the marketplace service running? (kb-dev start marketplace)`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function get<T = unknown>(path: string, params?: Record<string, string>): Promise<T> {\n const url = new URL(`${getBaseUrl()}${path}`);\n if (params) {\n for (const [k, v] of Object.entries(params)) {url.searchParams.set(k, v);}\n }\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const res = await fetch(url.toString(), { signal: controller.signal });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Marketplace ${path} 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(`Marketplace ${path} timed out — is the marketplace service running? (kb-dev start marketplace)`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n","/**\n * @module @kb-labs/marketplace-entry/scope\n *\n * Client-side scope resolution for marketplace CLI commands. Uses the\n * canonical `findProjectConfigRoot` helper from `@kb-labs/core-workspace`\n * so the detection rules (which filenames count, how walk-up stops) match\n * what the config loader and the marketplace daemon enforce.\n *\n * Rules:\n * - `project` is the default if cwd (or any ancestor up to the filesystem\n * root) contains `.kb/kb.config.{json,jsonc}`.\n * - Otherwise the default is `platform`.\n * - `--scope` always overrides detection.\n * - For scope=\"project\" we return the absolute projectRoot so the daemon\n * doesn't need to re-discover it.\n */\n\nimport { findProjectConfigRoot } from '@kb-labs/core-workspace';\nimport type { MarketplaceScope, MarketplaceQueryScope } from '@kb-labs/marketplace-contracts';\n\nexport const SCOPE_FLAG_CHOICES: readonly MarketplaceScope[] = ['platform', 'project'];\nexport const QUERY_SCOPE_FLAG_CHOICES: readonly MarketplaceQueryScope[] = ['platform', 'project', 'all'];\n\nexport interface ResolvedCliScope {\n scope: MarketplaceScope;\n projectRoot?: string;\n /** How the scope was determined — surfaces in --verbose logs. */\n reason: 'flag' | 'auto-detect' | 'fallback';\n}\n\nexport interface ResolvedCliQueryScope extends Omit<ResolvedCliScope, 'scope'> {\n scope: MarketplaceQueryScope;\n}\n\n/**\n * Resolve the effective scope for a mutating command (`link`, `unlink`,\n * `install`, ...). `flag` is the value of `--scope` (if supplied). The\n * helper never returns `'all'` for mutating commands — callers restrict\n * choices via `SCOPE_FLAG_CHOICES`.\n */\nexport async function resolveCliScope(\n cwd: string,\n flag: string | undefined,\n): Promise<ResolvedCliScope> {\n if (flag) {\n assertMutatingScope(flag);\n if (flag === 'project') {\n const projectRoot = await findProjectConfigRoot(cwd);\n if (!projectRoot) {\n throw new CliScopeError(\n 'SCOPE_PROJECT_ROOT_NOT_FOUND',\n `--scope=project requires a .kb/kb.config.{json,jsonc} ancestor of ${cwd} — none found.`,\n );\n }\n return { scope: 'project', projectRoot, reason: 'flag' };\n }\n return { scope: 'platform', reason: 'flag' };\n }\n\n const projectRoot = await findProjectConfigRoot(cwd);\n if (projectRoot) {\n return { scope: 'project', projectRoot, reason: 'auto-detect' };\n }\n return { scope: 'platform', reason: 'fallback' };\n}\n\n/**\n * Resolve the effective scope for a read-only command (`list`). Accepts\n * `'all'` as an explicit flag value.\n */\nexport async function resolveCliQueryScope(\n cwd: string,\n flag: string | undefined,\n): Promise<ResolvedCliQueryScope> {\n if (flag === 'all') {\n const projectRoot = await findProjectConfigRoot(cwd);\n return { scope: 'all', projectRoot, reason: 'flag' };\n }\n const base = await resolveCliScope(cwd, flag);\n return { scope: base.scope, projectRoot: base.projectRoot, reason: base.reason };\n}\n\nfunction assertMutatingScope(value: string): asserts value is MarketplaceScope {\n if (!SCOPE_FLAG_CHOICES.includes(value as MarketplaceScope)) {\n throw new CliScopeError(\n 'SCOPE_INVALID',\n `--scope must be one of: ${SCOPE_FLAG_CHOICES.join(', ')} (got ${JSON.stringify(value)})`,\n );\n }\n}\n\nexport class CliScopeError extends Error {\n readonly code: string;\n constructor(code: string, message: string) {\n super(message);\n this.code = code;\n this.name = 'CliScopeError';\n }\n}\n\n/**\n * Build the body-payload fragment expected by `parseMutatingScope` on the\n * API side: `{ scope, projectRoot? }`. Kept as a helper so every command\n * sends the same shape and nothing drifts out-of-sync with the server.\n */\nexport function scopeBody(ctx: ResolvedCliScope | ResolvedCliQueryScope): {\n scope: MarketplaceScope | MarketplaceQueryScope;\n projectRoot?: string;\n} {\n return {\n scope: ctx.scope,\n ...(ctx.projectRoot ? { projectRoot: ctx.projectRoot } : {}),\n };\n}\n","import { defineCommand, type PluginContextV3, type CommandResult } from '@kb-labs/sdk';\nimport { del } from '../../http.js';\nimport { resolveCliScope, scopeBody, CliScopeError } from '../../scope.js';\n\ninterface UnlinkFlags {\n scope?: string;\n}\n\ninterface UnlinkInput {\n argv?: string[];\n flags?: UnlinkFlags;\n}\n\nexport default defineCommand<unknown, UnlinkInput, { packageId: string; scope: string }>({\n id: 'marketplace:plugins:unlink',\n description: 'Unlink a plugin',\n\n handler: {\n async execute(ctx: PluginContextV3, input: UnlinkInput): Promise<CommandResult<{ packageId: string; scope: string }>> {\n const packageId = input.argv?.[0];\n if (!packageId) {\n ctx.ui?.error?.('Specify a package ID to unlink');\n return { exitCode: 1, result: { packageId: '', scope: '' } };\n }\n const flags = (input.flags ?? input) as UnlinkFlags;\n\n let scopeCtx;\n try {\n scopeCtx = await resolveCliScope(ctx.cwd, flags.scope);\n } catch (err) {\n if (err instanceof CliScopeError) {\n ctx.ui?.error?.(err.message);\n return { exitCode: 1, result: { packageId: '', scope: '' } };\n }\n throw err;\n }\n\n await del(`/packages/${encodeURIComponent(packageId)}/link`, scopeBody(scopeCtx));\n ctx.ui?.success?.(`Unlinked ${packageId} (${scopeCtx.scope})`);\n return { exitCode: 0, result: { packageId, scope: scopeCtx.scope } };\n },\n },\n});\n"]}
@@ -3,6 +3,7 @@ import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
3
3
  interface SyncFlags {
4
4
  'auto-enable'?: boolean;
5
5
  json?: boolean;
6
+ scope?: string;
6
7
  }
7
8
  interface SyncInput {
8
9
  argv?: string[];
@@ -1,4 +1,5 @@
1
1
  import { defineCommand, useEnv } from '@kb-labs/sdk';
2
+ import { findProjectConfigRoot } from '@kb-labs/core-workspace';
2
3
  import * as fs from 'fs/promises';
3
4
  import * as path from 'path';
4
5
 
@@ -39,6 +40,50 @@ async function post(path2, body) {
39
40
  clearTimeout(timer);
40
41
  }
41
42
  }
43
+ var SCOPE_FLAG_CHOICES = ["platform", "project"];
44
+ async function resolveCliScope(cwd, flag) {
45
+ if (flag) {
46
+ assertMutatingScope(flag);
47
+ if (flag === "project") {
48
+ const projectRoot2 = await findProjectConfigRoot(cwd);
49
+ if (!projectRoot2) {
50
+ throw new CliScopeError(
51
+ "SCOPE_PROJECT_ROOT_NOT_FOUND",
52
+ `--scope=project requires a .kb/kb.config.{json,jsonc} ancestor of ${cwd} \u2014 none found.`
53
+ );
54
+ }
55
+ return { scope: "project", projectRoot: projectRoot2, reason: "flag" };
56
+ }
57
+ return { scope: "platform", reason: "flag" };
58
+ }
59
+ const projectRoot = await findProjectConfigRoot(cwd);
60
+ if (projectRoot) {
61
+ return { scope: "project", projectRoot, reason: "auto-detect" };
62
+ }
63
+ return { scope: "platform", reason: "fallback" };
64
+ }
65
+ function assertMutatingScope(value) {
66
+ if (!SCOPE_FLAG_CHOICES.includes(value)) {
67
+ throw new CliScopeError(
68
+ "SCOPE_INVALID",
69
+ `--scope must be one of: ${SCOPE_FLAG_CHOICES.join(", ")} (got ${JSON.stringify(value)})`
70
+ );
71
+ }
72
+ }
73
+ var CliScopeError = class extends Error {
74
+ code;
75
+ constructor(code, message) {
76
+ super(message);
77
+ this.code = code;
78
+ this.name = "CliScopeError";
79
+ }
80
+ };
81
+ function scopeBody(ctx) {
82
+ return {
83
+ scope: ctx.scope,
84
+ ...ctx.projectRoot ? { projectRoot: ctx.projectRoot } : {}
85
+ };
86
+ }
42
87
  var sync_default = defineCommand({
43
88
  id: "marketplace:sync",
44
89
  description: "Sync workspace \u2014 scan for entities and populate lock",
@@ -46,23 +91,35 @@ var sync_default = defineCommand({
46
91
  async execute(ctx, input) {
47
92
  const flags = input.flags ?? input;
48
93
  const cwd = ctx.cwd ?? process.cwd();
49
- const syncConfig = await loadSyncConfig(cwd);
94
+ let scopeCtx;
95
+ try {
96
+ scopeCtx = await resolveCliScope(cwd, flags.scope);
97
+ } catch (err) {
98
+ if (err instanceof CliScopeError) {
99
+ ctx.ui?.error?.(err.message);
100
+ return { exitCode: 1, result: { added: [], skipped: [], total: 0 } };
101
+ }
102
+ throw err;
103
+ }
104
+ const configRoot = scopeCtx.scope === "project" && scopeCtx.projectRoot ? scopeCtx.projectRoot : cwd;
105
+ const syncConfig = await loadSyncConfig(configRoot);
50
106
  if (!syncConfig.include?.length) {
51
- ctx.ui?.error?.("No marketplace.sync.include in kb.config.json");
107
+ ctx.ui?.error?.(`No marketplace.sync.include in ${configRoot}/.kb/kb.config.{json,jsonc}`);
52
108
  return { exitCode: 1, result: { added: [], skipped: [], total: 0 } };
53
109
  }
54
110
  const isDev = (useEnv("NODE_ENV") ?? "development") === "development";
55
111
  const result = await post("/workspace/sync", {
56
112
  include: syncConfig.include,
57
113
  exclude: syncConfig.exclude,
58
- autoEnable: flags["auto-enable"] !== void 0 ? Boolean(flags["auto-enable"]) : isDev
114
+ autoEnable: flags["auto-enable"] !== void 0 ? Boolean(flags["auto-enable"]) : isDev,
115
+ ...scopeBody(scopeCtx)
59
116
  });
60
117
  if (flags.json) {
61
118
  ctx.ui?.json?.(result);
62
119
  } else if (result.added.length === 0) {
63
- ctx.ui?.info?.(`Lock is up to date (${result.total} entries)`);
120
+ ctx.ui?.info?.(`Lock is up to date \u2014 ${scopeCtx.scope} (${result.total} entries)`);
64
121
  } else {
65
- ctx.ui?.success?.(`Synced ${result.added.length} new entries (${result.total} total)`, {
122
+ ctx.ui?.success?.(`Synced ${result.added.length} new entries to ${scopeCtx.scope} (${result.total} total)`, {
66
123
  sections: [{
67
124
  header: "Added",
68
125
  items: result.added.map((e) => `+ ${e.id} (${e.primaryKind}) v${e.version}`)
@@ -73,17 +130,21 @@ var sync_default = defineCommand({
73
130
  }
74
131
  }
75
132
  });
76
- async function loadSyncConfig(cwd) {
77
- for (const p of [path.join(cwd, ".kb", "kb.config.json"), path.join(cwd, "kb.config.json")]) {
133
+ async function loadSyncConfig(root) {
134
+ for (const name of ["kb.config.jsonc", "kb.config.json"]) {
135
+ const p = path.join(root, ".kb", name);
78
136
  try {
79
137
  const raw = await fs.readFile(p, "utf-8");
80
- return JSON.parse(raw)?.marketplace?.sync ?? {};
138
+ return JSON.parse(stripJsonc(raw))?.marketplace?.sync ?? {};
81
139
  } catch {
82
140
  continue;
83
141
  }
84
142
  }
85
143
  return {};
86
144
  }
145
+ function stripJsonc(src) {
146
+ return src.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1").replace(/,(\s*[}\]])/g, "$1");
147
+ }
87
148
 
88
149
  export { sync_default as default };
89
150
  //# sourceMappingURL=sync.js.map