@muxoai/platform 1.4.3 → 1.4.5

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/dist/policy.js CHANGED
@@ -1436,6 +1436,65 @@ export function buildLlmPolicyAndGovernanceBrief(policy = DEFAULT_WORKSPACE_POLI
1436
1436
  prompt: "Remember our default GitHub repo is muxo-ai/muxo",
1437
1437
  },
1438
1438
  },
1439
+ {
1440
+ intent: "Run a turnkey or custom Temporal Saga playbook (or pass plain English in prompt)",
1441
+ mcpTool: "get_run_context",
1442
+ arguments: {
1443
+ action: "run_playbook",
1444
+ slug: "fullstack-cloud-bootstrap",
1445
+ prompt: "Run the fullstack cloud bootstrap playbook for acme",
1446
+ },
1447
+ },
1448
+ {
1449
+ intent: "Create a custom multi-step Saga playbook in plain English",
1450
+ mcpTool: "get_run_context",
1451
+ arguments: {
1452
+ action: "create_playbook",
1453
+ prompt: "Create playbook daily-growth-check that runs instant.seo.audit and app.slack.send_message",
1454
+ },
1455
+ },
1456
+ {
1457
+ intent: "Rollback or signal a Temporal workflow Saga in plain English",
1458
+ mcpTool: "get_run_context",
1459
+ arguments: {
1460
+ action: "rollback_workflow",
1461
+ workflow_id: "latest",
1462
+ prompt: "Rollback the latest workflow because staging healthcheck failed",
1463
+ },
1464
+ },
1465
+ {
1466
+ intent: "Time-travel or k-hop BFS traverse the Graphiti Bi-Temporal Knowledge Graph",
1467
+ mcpTool: "get_run_context",
1468
+ arguments: {
1469
+ action: "memory_graph",
1470
+ as_of: "2026-10-20T00:00:00Z",
1471
+ focal_entity: "workspace",
1472
+ max_hops: 2,
1473
+ },
1474
+ },
1475
+ {
1476
+ intent: "Compile or evaluate AWS Cedar 4.0 & OPA Rego v1 Policy-as-Code",
1477
+ mcpTool: "get_run_context",
1478
+ arguments: {
1479
+ action: "cedar_policy",
1480
+ tool_slug: "resource.turso.database",
1481
+ input: { query: "SELECT 1" },
1482
+ },
1483
+ },
1484
+ {
1485
+ intent: "Export OpenTelemetry GenAI 1.29.0 spans, Langfuse batch, or FinOps ROI",
1486
+ mcpTool: "get_run_context",
1487
+ arguments: { action: "roi_analytics" },
1488
+ },
1489
+ {
1490
+ intent: "One-shot configure a turnkey harness preset (startup-builder, strict-production, read-only-auditor, finops-byok-zero-cost)",
1491
+ mcpTool: "get_run_context",
1492
+ arguments: {
1493
+ action: "configure_harness",
1494
+ preset: "strict-production",
1495
+ harness: "claude-code",
1496
+ },
1497
+ },
1439
1498
  ],
1440
1499
  };
1441
1500
  }
@@ -1,5 +1,6 @@
1
1
  import vm from "node:vm";
2
2
  import { Composio } from "@composio/core";
3
+ import { callComposioMcpGatewayRpc } from "../composio-catalog.js";
3
4
  const INSTANT_TOOLS = [
4
5
  {
5
6
  slug: "instant.web.search",
@@ -1024,44 +1025,61 @@ export class ComposioInstantAdapter {
1024
1025
  };
1025
1026
  let evalResult = null;
1026
1027
  let executionEngine = "cloudflare_sandbox_vm_isolate";
1027
- try {
1028
- const sandboxContext = vm.createContext({
1029
- Math,
1030
- Date,
1031
- JSON,
1032
- Array,
1033
- Object,
1034
- String,
1035
- Number,
1036
- Boolean,
1037
- RegExp,
1038
- Map,
1039
- Set,
1040
- Buffer: {
1041
- from: (str, enc) => Buffer.from(str, enc),
1042
- },
1043
- env: Object.freeze({ ...vaultBindings }),
1044
- console: {
1045
- log: (...args) => logs.push(redactSecrets(args.map((a) => String(a)).join(" "))),
1046
- info: (...args) => logs.push(redactSecrets(args.map((a) => String(a)).join(" "))),
1047
- },
1048
- });
1049
- const wrappedCode = code.includes("return ") && !code.trim().startsWith("(")
1050
- ? `(() => { ${code} })()`
1051
- : code;
1052
- const rawOut = vm.runInContext(wrappedCode, sandboxContext, {
1053
- timeout: 1500,
1028
+ if (input.useRemoteWorkbench === true && this.config.composioApiKey) {
1029
+ const gwRes = await callComposioMcpGatewayRpc("COMPOSIO_REMOTE_WORKBENCH", {
1030
+ code_to_execute: code,
1031
+ thought: String(input.thought || "Muxo governed workbench execution"),
1032
+ }, {
1033
+ apiKey: this.config.composioApiKey,
1034
+ gatewayUrl: this.config.composioMcpGatewayUrl,
1035
+ isReadOnly: false,
1036
+ timeoutMs: 8000,
1054
1037
  });
1055
- evalResult =
1056
- typeof rawOut === "string"
1057
- ? redactSecrets(rawOut)
1058
- : rawOut !== undefined
1059
- ? JSON.parse(redactSecrets(JSON.stringify(rawOut)))
1060
- : null;
1038
+ if (gwRes.ok && gwRes.data) {
1039
+ executionEngine = "composio_remote_workbench";
1040
+ evalResult = JSON.parse(redactSecrets(JSON.stringify(gwRes.data)));
1041
+ }
1061
1042
  }
1062
- catch (err) {
1063
- executionEngine = "isolated_workbench_ast_guard";
1064
- evalResult = redactSecrets(String(err?.message || "Completed expression analysis"));
1043
+ if (evalResult === null) {
1044
+ try {
1045
+ const sandboxContext = vm.createContext({
1046
+ Math,
1047
+ Date,
1048
+ JSON,
1049
+ Array,
1050
+ Object,
1051
+ String,
1052
+ Number,
1053
+ Boolean,
1054
+ RegExp,
1055
+ Map,
1056
+ Set,
1057
+ Buffer: {
1058
+ from: (str, enc) => Buffer.from(str, enc),
1059
+ },
1060
+ env: Object.freeze({ ...vaultBindings }),
1061
+ console: {
1062
+ log: (...args) => logs.push(redactSecrets(args.map((a) => String(a)).join(" "))),
1063
+ info: (...args) => logs.push(redactSecrets(args.map((a) => String(a)).join(" "))),
1064
+ },
1065
+ });
1066
+ const wrappedCode = code.includes("return ") && !code.trim().startsWith("(")
1067
+ ? `(() => { ${code} })()`
1068
+ : code;
1069
+ const rawOut = vm.runInContext(wrappedCode, sandboxContext, {
1070
+ timeout: 1500,
1071
+ });
1072
+ evalResult =
1073
+ typeof rawOut === "string"
1074
+ ? redactSecrets(rawOut)
1075
+ : rawOut !== undefined
1076
+ ? JSON.parse(redactSecrets(JSON.stringify(rawOut)))
1077
+ : null;
1078
+ }
1079
+ catch (err) {
1080
+ executionEngine = "isolated_workbench_ast_guard";
1081
+ evalResult = redactSecrets(String(err?.message || "Completed expression analysis"));
1082
+ }
1065
1083
  }
1066
1084
  return {
1067
1085
  provider: "composio_instant",
@@ -1,7 +1,7 @@
1
1
  import { Composio } from "@composio/core";
2
2
  import type { MuxoConfig } from "../config.js";
3
3
  import type { MuxoTool, RiskLevel } from "../types.js";
4
- import { parseComposioWebhookEnvelope, type ComposioToolkitMetadata, type ComposioTriggerEventRecord, type ComposioTriggerSubscription, type ComposioTriggerType, type ComposioWebhookEventType } from "../composio-catalog.js";
4
+ import { parseComposioWebhookEnvelope, type ComposioMcpGatewayMetaTool, type ComposioMcpGatewayResult, type ComposioToolkitMetadata, type ComposioTriggerEventRecord, type ComposioTriggerSubscription, type ComposioTriggerType, type ComposioWebhookEventType } from "../composio-catalog.js";
5
5
  export type { ComposioToolkitMetadata, ComposioTriggerEventRecord, ComposioTriggerSubscription, ComposioTriggerType, ComposioWebhookEventType, };
6
6
  export declare class ComposioSdkAdapter {
7
7
  private readonly config;
@@ -16,12 +16,20 @@ export declare class ComposioSdkAdapter {
16
16
  private get headers();
17
17
  private getHeaders;
18
18
  getComposioClient(composioApiKey?: string | null): Composio | null;
19
+ callComposioMcpGateway(metaTool: ComposioMcpGatewayMetaTool | string, toolArgs: Record<string, unknown>, options?: {
20
+ workspaceId?: string;
21
+ composioApiKey?: string | null;
22
+ gatewayUrl?: string | null;
23
+ isReadOnly?: boolean;
24
+ timeoutMs?: number;
25
+ }): Promise<ComposioMcpGatewayResult>;
19
26
  getSdkInfo(composioApiKey?: string | null): {
20
27
  sdkPackage: "@composio/core";
21
28
  sdkVersion: string;
22
29
  configured: boolean;
23
30
  usingCustomerByok: boolean;
24
31
  baseUrl: string;
32
+ mcpGatewayUrl: string;
25
33
  curatedToolCount: number;
26
34
  cachedToolCount: number;
27
35
  capabilities: string[];
@@ -158,5 +166,6 @@ export declare class ComposioSdkAdapter {
158
166
  input: Record<string, unknown>;
159
167
  connectedAccountId?: string | null;
160
168
  composioApiKey?: string | null;
169
+ mcpGatewayUrl?: string | null;
161
170
  }): Promise<Record<string, unknown>>;
162
171
  }
@@ -1,6 +1,6 @@
1
1
  import { Composio } from "@composio/core";
2
2
  import { matchAppToolSlug } from "../mcp-schema.js";
3
- import { composioAuthConfigScopes, COMPOSIO_V3_WEBHOOK_EVENTS, ensureOAuthCallbackParams, ENTERPRISE_TOOLKITS_CATALOG, filterComposioToolkits, getComposioTriggerType, getSampleActionSchema, isStripeProjectsExclusiveProvider, listComposioTriggerTypes, parseComposioWebhookEnvelope, verifyComposioWebhookSignature, wrapMuxoConnectUrl, } from "../composio-catalog.js";
3
+ import { callComposioMcpGatewayRpc, canUseComposioMcpGatewayForExecution, composioAuthConfigScopes, COMPOSIO_V3_WEBHOOK_EVENTS, ensureOAuthCallbackParams, ENTERPRISE_TOOLKITS_CATALOG, filterComposioToolkits, getComposioTriggerType, getSampleActionSchema, isStripeProjectsExclusiveProvider, listComposioTriggerTypes, parseComposioWebhookEnvelope, verifyComposioWebhookSignature, wrapMuxoConnectUrl, } from "../composio-catalog.js";
4
4
  const CURATED_APP_TOOLS = [
5
5
  {
6
6
  slug: "app.github.list_repos",
@@ -468,6 +468,20 @@ export class ComposioSdkAdapter {
468
468
  return null;
469
469
  }
470
470
  }
471
+ async callComposioMcpGateway(metaTool, toolArgs, options) {
472
+ const apiKey = this.resolveApiKey(options?.composioApiKey);
473
+ const gatewayUrl = options?.gatewayUrl ||
474
+ this.config.composioMcpGatewayUrl ||
475
+ "https://connect.composio.dev/mcp";
476
+ return callComposioMcpGatewayRpc(metaTool, toolArgs, {
477
+ apiKey,
478
+ gatewayUrl,
479
+ workspaceId: options?.workspaceId,
480
+ usingCustomerByok: Boolean(options?.composioApiKey && options.composioApiKey.trim()),
481
+ isReadOnly: options?.isReadOnly,
482
+ timeoutMs: options?.timeoutMs,
483
+ });
484
+ }
471
485
  getSdkInfo(composioApiKey) {
472
486
  const apiKey = this.resolveApiKey(composioApiKey);
473
487
  return {
@@ -476,6 +490,7 @@ export class ComposioSdkAdapter {
476
490
  configured: Boolean(apiKey),
477
491
  usingCustomerByok: Boolean(composioApiKey && composioApiKey.trim()),
478
492
  baseUrl: this.config.composioBaseUrl,
493
+ mcpGatewayUrl: this.config.composioMcpGatewayUrl || "https://connect.composio.dev/mcp",
479
494
  curatedToolCount: CURATED_APP_TOOLS.length,
480
495
  cachedToolCount: this.liveToolsCache.size,
481
496
  capabilities: [
@@ -491,6 +506,11 @@ export class ComposioSdkAdapter {
491
506
  "triggers.create",
492
507
  "triggers.subscribe",
493
508
  "triggers.verifyWebhook",
509
+ "mcpGateway.COMPOSIO_SEARCH_TOOLS",
510
+ "mcpGateway.COMPOSIO_GET_TOOL_SCHEMAS",
511
+ "mcpGateway.COMPOSIO_MANAGE_CONNECTIONS",
512
+ "mcpGateway.COMPOSIO_MULTI_EXECUTE_TOOL",
513
+ "mcpGateway.COMPOSIO_REMOTE_WORKBENCH",
494
514
  ],
495
515
  };
496
516
  }
@@ -901,6 +921,33 @@ export class ComposioSdkAdapter {
901
921
  merged.set(tool.slug, tool);
902
922
  }
903
923
  }
924
+ if (merged.size === 0 &&
925
+ q &&
926
+ canUseComposioMcpGatewayForExecution({
927
+ apiKey,
928
+ gatewayUrl: this.config.composioMcpGatewayUrl,
929
+ usingCustomerByok: Boolean(composioApiKey),
930
+ isReadOnly: false,
931
+ })) {
932
+ const gwSearch = await this.callComposioMcpGateway("COMPOSIO_SEARCH_TOOLS", {
933
+ queries: [
934
+ {
935
+ use_case: tk ? `${tk}: ${q}` : q,
936
+ ...(tk ? { known_fields: `toolkit:${tk}` } : {}),
937
+ },
938
+ ],
939
+ session: { generate_id: true },
940
+ }, { composioApiKey, isReadOnly: true, timeoutMs: 3500 });
941
+ const gwTools = Array.isArray(gwSearch.data?.tools)
942
+ ? gwSearch.data?.tools
943
+ : [];
944
+ for (const gwItem of gwTools) {
945
+ const tool = this.mapRawComposioItemToMuxoTool(gwItem);
946
+ if (tool && !merged.has(tool.slug)) {
947
+ merged.set(tool.slug, tool);
948
+ }
949
+ }
950
+ }
904
951
  return Array.from(merged.values()).slice(0, limit);
905
952
  }
906
953
  catch {
@@ -935,7 +982,23 @@ export class ComposioSdkAdapter {
935
982
  }
936
983
  }
937
984
  catch {
938
- // Fall through to searchTools
985
+ // Fall through to MCP Gateway schema lookup / searchTools
986
+ }
987
+ }
988
+ if (canUseComposioMcpGatewayForExecution({
989
+ apiKey,
990
+ gatewayUrl: this.config.composioMcpGatewayUrl,
991
+ usingCustomerByok: Boolean(composioApiKey),
992
+ isReadOnly: false,
993
+ })) {
994
+ const gwSchema = await this.callComposioMcpGateway("COMPOSIO_GET_TOOL_SCHEMAS", { tool_slugs: [upstreamCandidate] }, { composioApiKey, isReadOnly: true, timeoutMs: 3000 });
995
+ const schemaList = Array.isArray(gwSchema.data?.tools)
996
+ ? gwSchema.data?.tools
997
+ : [];
998
+ if (schemaList[0]) {
999
+ const mapped = this.mapRawComposioItemToMuxoTool(schemaList[0]);
1000
+ if (mapped)
1001
+ return mapped;
939
1002
  }
940
1003
  }
941
1004
  }
@@ -1331,10 +1394,57 @@ export class ComposioSdkAdapter {
1331
1394
  if (!effectiveApiKey) {
1332
1395
  return buildConnectedAccountFallback();
1333
1396
  }
1334
- // 1. Official @composio/core SDK tool execution
1335
- const sdk = this.getComposioClient(params.composioApiKey);
1397
+ const isReadOnly = params.tool.riskLevel === "read";
1398
+ const gatewayUrl = params.mcpGatewayUrl ||
1399
+ this.config.composioMcpGatewayUrl ||
1400
+ "https://connect.composio.dev/mcp";
1401
+ const preferMcpGateway = Boolean(params.mcpGatewayUrl) ||
1402
+ effectiveApiKey.startsWith("ck_") ||
1403
+ gatewayUrl.includes("mcp.composio.dev/t/");
1404
+ let writeTierAttempted = false;
1405
+ // Tier 1: Upstream Composio MCP Gateway (when BYOK consumer/team gateway is configured, or read-only)
1406
+ if (preferMcpGateway &&
1407
+ canUseComposioMcpGatewayForExecution({
1408
+ apiKey: effectiveApiKey,
1409
+ gatewayUrl,
1410
+ usingCustomerByok: Boolean(params.composioApiKey),
1411
+ isReadOnly,
1412
+ })) {
1413
+ if (!isReadOnly)
1414
+ writeTierAttempted = true;
1415
+ const gwExec = await this.callComposioMcpGateway("COMPOSIO_MULTI_EXECUTE_TOOL", {
1416
+ tools: [
1417
+ {
1418
+ tool_slug: params.tool.upstreamSlug,
1419
+ arguments: params.input,
1420
+ },
1421
+ ],
1422
+ sync_response_to_workbench: false,
1423
+ }, {
1424
+ workspaceId: params.workspaceId,
1425
+ composioApiKey: params.composioApiKey,
1426
+ gatewayUrl,
1427
+ isReadOnly,
1428
+ timeoutMs: 20000,
1429
+ });
1430
+ if (gwExec.ok && gwExec.data) {
1431
+ return {
1432
+ provider: "composio_sdk",
1433
+ sdk: "composio_mcp_gateway",
1434
+ gatewayUrl: gwExec.gatewayUrl,
1435
+ tool: params.tool.slug,
1436
+ upstreamTool: params.tool.upstreamSlug,
1437
+ connectedAccountId: params.connectedAccountId,
1438
+ data: gwExec.data,
1439
+ };
1440
+ }
1441
+ }
1442
+ // Tier 2: Official @composio/core SDK tool execution (skip if a non-idempotent write was already attempted)
1443
+ const sdk = !writeTierAttempted ? this.getComposioClient(params.composioApiKey) : null;
1336
1444
  if (sdk) {
1337
1445
  try {
1446
+ if (!isReadOnly)
1447
+ writeTierAttempted = true;
1338
1448
  const sdkExec = await Promise.race([
1339
1449
  sdk.tools.execute(params.tool.upstreamSlug, {
1340
1450
  userId: params.workspaceId,
@@ -1359,36 +1469,39 @@ export class ComposioSdkAdapter {
1359
1469
  }
1360
1470
  }
1361
1471
  catch {
1362
- // Fall through to direct v3 REST execute
1472
+ // Only fall through to REST v3 for read-only calls; never double-submit writes
1363
1473
  }
1364
1474
  }
1365
- const body = {
1366
- user_id: params.workspaceId,
1367
- arguments: params.input,
1368
- };
1369
- if (params.connectedAccountId && params.connectedAccountId.startsWith("ca_")) {
1370
- body.connected_account_id = params.connectedAccountId;
1371
- }
1372
- try {
1373
- const res = await fetch(`${this.config.composioBaseUrl}/tools/execute/${encodeURIComponent(params.tool.upstreamSlug)}`, {
1374
- method: "POST",
1375
- headers: this.getHeaders(params.composioApiKey),
1376
- body: JSON.stringify(body),
1377
- signal: AbortSignal.timeout(25000),
1378
- });
1379
- const payload = (await res.json().catch(() => ({})));
1380
- if (res.ok && !payload.error && payload.successful !== false) {
1381
- return {
1382
- provider: "composio_sdk",
1383
- tool: params.tool.slug,
1384
- upstreamTool: params.tool.upstreamSlug,
1385
- connectedAccountId: params.connectedAccountId,
1386
- data: payload.data ?? payload,
1387
- };
1475
+ // Tier 3: Composio v3 REST API (skip if a non-idempotent write was already attempted in Tier 1/2)
1476
+ if (!writeTierAttempted) {
1477
+ const body = {
1478
+ user_id: params.workspaceId,
1479
+ arguments: params.input,
1480
+ };
1481
+ if (params.connectedAccountId && params.connectedAccountId.startsWith("ca_")) {
1482
+ body.connected_account_id = params.connectedAccountId;
1483
+ }
1484
+ try {
1485
+ const res = await fetch(`${this.config.composioBaseUrl}/tools/execute/${encodeURIComponent(params.tool.upstreamSlug)}`, {
1486
+ method: "POST",
1487
+ headers: this.getHeaders(params.composioApiKey),
1488
+ body: JSON.stringify(body),
1489
+ signal: AbortSignal.timeout(25000),
1490
+ });
1491
+ const payload = (await res.json().catch(() => ({})));
1492
+ if (res.ok && !payload.error && payload.successful !== false) {
1493
+ return {
1494
+ provider: "composio_sdk",
1495
+ tool: params.tool.slug,
1496
+ upstreamTool: params.tool.upstreamSlug,
1497
+ connectedAccountId: params.connectedAccountId,
1498
+ data: payload.data ?? payload,
1499
+ };
1500
+ }
1501
+ }
1502
+ catch {
1503
+ // Fall through to connected account execution result when callback handoff was completed via Muxo /oauth/callback
1388
1504
  }
1389
- }
1390
- catch {
1391
- // Fall through to connected account execution result when callback handoff was completed via Muxo /oauth/callback
1392
1505
  }
1393
1506
  return buildConnectedAccountFallback();
1394
1507
  }
package/dist/sdk.d.ts CHANGED
@@ -246,6 +246,7 @@ export declare class MuxoClient {
246
246
  run: ExecutionRun;
247
247
  receipt: ExecutionReceipt;
248
248
  }>;
249
+ reconcileRun(runOrPlanId: string): Promise<Record<string, unknown>>;
249
250
  exec(toolSlug: string, input?: Record<string, unknown>, options?: {
250
251
  reason?: string;
251
252
  approve?: boolean;
@@ -969,6 +970,24 @@ export declare class MuxoClient {
969
970
  }): Promise<Record<string, unknown>>;
970
971
  getOtelTraces(limit?: number): Promise<Record<string, unknown>>;
971
972
  listPlaybooks(): Promise<Record<string, unknown>>;
973
+ createPlaybook(params: {
974
+ prompt?: string;
975
+ slug?: string;
976
+ name?: string;
977
+ steps?: Array<string | Record<string, unknown>>;
978
+ [key: string]: unknown;
979
+ }): Promise<Record<string, unknown>>;
972
980
  runPlaybook(slug: string, input?: Record<string, unknown>): Promise<Record<string, unknown>>;
981
+ configureHarness(params?: {
982
+ preset?: string;
983
+ harness?: string;
984
+ prompt?: string;
985
+ }): Promise<Record<string, unknown>>;
973
986
  getRoiAnalytics(): Promise<Record<string, unknown>>;
987
+ listSkills(filter?: {
988
+ category?: string;
989
+ provider?: string;
990
+ query?: string;
991
+ }): Promise<Record<string, unknown>>;
992
+ getSkill(slug: string): Promise<Record<string, unknown>>;
974
993
  }
package/dist/sdk.js CHANGED
@@ -189,6 +189,9 @@ export class MuxoClient {
189
189
  body: reason ? { reason } : {},
190
190
  });
191
191
  }
192
+ async reconcileRun(runOrPlanId) {
193
+ return this.request(`/api/v1/runs/${encodeURIComponent(runOrPlanId)}/reconcile`);
194
+ }
192
195
  async exec(toolSlug, input = {}, options = {}) {
193
196
  const planned = await this.planAction({
194
197
  toolSlug,
@@ -787,13 +790,42 @@ export class MuxoClient {
787
790
  async listPlaybooks() {
788
791
  return this.request("/api/v1/playbooks");
789
792
  }
793
+ async createPlaybook(params) {
794
+ return this.request("/api/v1/playbooks", {
795
+ method: "POST",
796
+ body: params,
797
+ });
798
+ }
790
799
  async runPlaybook(slug, input) {
791
800
  return this.request(`/api/v1/playbooks/${encodeURIComponent(slug)}/run`, {
792
801
  method: "POST",
793
802
  body: { input: input || {} },
794
803
  });
795
804
  }
805
+ async configureHarness(params) {
806
+ if (!params || Object.keys(params).length === 0) {
807
+ return this.request("/api/v1/harness/configure");
808
+ }
809
+ return this.request("/api/v1/harness/configure", {
810
+ method: "POST",
811
+ body: params,
812
+ });
813
+ }
796
814
  async getRoiAnalytics() {
797
815
  return this.request("/api/v1/analytics/roi");
798
816
  }
817
+ async listSkills(filter) {
818
+ const qs = new URLSearchParams();
819
+ if (filter?.category)
820
+ qs.set("category", filter.category);
821
+ if (filter?.provider)
822
+ qs.set("provider", filter.provider);
823
+ if (filter?.query)
824
+ qs.set("q", filter.query);
825
+ const suffix = qs.toString() ? `?${qs.toString()}` : "";
826
+ return this.request(`/api/v1/skills${suffix}`);
827
+ }
828
+ async getSkill(slug) {
829
+ return this.request(`/api/v1/skills/${encodeURIComponent(slug)}`);
830
+ }
799
831
  }