@nextclaw/server 0.6.4 → 0.6.6

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/index.d.ts CHANGED
@@ -57,6 +57,7 @@ type ProviderConnectionTestResult = {
57
57
  type ProviderAuthStartResult = {
58
58
  provider: string;
59
59
  kind: "device_code";
60
+ methodId?: string;
60
61
  sessionId: string;
61
62
  verificationUri: string;
62
63
  userCode: string;
@@ -64,6 +65,9 @@ type ProviderAuthStartResult = {
64
65
  intervalMs: number;
65
66
  note?: string;
66
67
  };
68
+ type ProviderAuthStartRequest = {
69
+ methodId?: string;
70
+ };
67
71
  type ProviderAuthPollRequest = {
68
72
  sessionId: string;
69
73
  };
@@ -296,6 +300,21 @@ type ChatCapabilitiesView = {
296
300
  stopSupported: boolean;
297
301
  stopReason?: string;
298
302
  };
303
+ type ChatCommandOptionView = {
304
+ name: string;
305
+ description: string;
306
+ type: "string" | "boolean" | "number";
307
+ required?: boolean;
308
+ };
309
+ type ChatCommandView = {
310
+ name: string;
311
+ description: string;
312
+ options?: ChatCommandOptionView[];
313
+ };
314
+ type ChatCommandsView = {
315
+ commands: ChatCommandView[];
316
+ total: number;
317
+ };
299
318
  type ChatTurnStopRequest = {
300
319
  runId: string;
301
320
  sessionKey?: string;
@@ -402,6 +421,18 @@ type ProviderSpecView = {
402
421
  en?: string;
403
422
  zh?: string;
404
423
  };
424
+ methods?: Array<{
425
+ id: string;
426
+ label?: {
427
+ en?: string;
428
+ zh?: string;
429
+ };
430
+ hint?: {
431
+ en?: string;
432
+ zh?: string;
433
+ };
434
+ }>;
435
+ defaultMethodId?: string;
405
436
  supportsCliImport?: boolean;
406
437
  };
407
438
  defaultModels?: string[];
@@ -491,7 +522,9 @@ type ConfigActionExecuteResult = {
491
522
  };
492
523
  type MarketplaceItemType = "plugin" | "skill";
493
524
  type MarketplaceSort = "relevance" | "updated";
494
- type MarketplaceInstallKind = "npm" | "clawhub" | "git" | "builtin";
525
+ type MarketplacePluginInstallKind = "npm";
526
+ type MarketplaceSkillInstallKind = "builtin" | "marketplace";
527
+ type MarketplaceInstallKind = MarketplacePluginInstallKind | MarketplaceSkillInstallKind;
495
528
  type MarketplaceInstallSpec = {
496
529
  kind: MarketplaceInstallKind;
497
530
  spec: string;
@@ -522,7 +555,7 @@ type MarketplaceSkillContentView = {
522
555
  slug: string;
523
556
  name: string;
524
557
  install: MarketplaceInstallSpec;
525
- source: "workspace" | "builtin" | "git" | "remote";
558
+ source: "builtin" | "marketplace" | "remote";
526
559
  raw: string;
527
560
  metadataRaw?: string;
528
561
  bodyRaw: string;
@@ -578,11 +611,9 @@ type MarketplaceInstalledView = {
578
611
  };
579
612
  type MarketplaceInstallSkillParams = {
580
613
  slug: string;
581
- kind?: MarketplaceInstallKind;
614
+ kind?: MarketplaceSkillInstallKind;
582
615
  skill?: string;
583
616
  installPath?: string;
584
- version?: string;
585
- registry?: string;
586
617
  force?: boolean;
587
618
  };
588
619
  type MarketplacePluginInstallRequest = {
@@ -592,11 +623,9 @@ type MarketplacePluginInstallRequest = {
592
623
  type MarketplaceSkillInstallRequest = {
593
624
  type?: "skill";
594
625
  spec: string;
595
- kind?: MarketplaceInstallKind;
626
+ kind?: MarketplaceSkillInstallKind;
596
627
  skill?: string;
597
628
  installPath?: string;
598
- version?: string;
599
- registry?: string;
600
629
  force?: boolean;
601
630
  };
602
631
  type MarketplacePluginInstallResult = {
@@ -679,6 +708,11 @@ type UiServerEvent = {
679
708
  payload: {
680
709
  run: ChatRunView;
681
710
  };
711
+ } | {
712
+ type: "session.updated";
713
+ payload: {
714
+ sessionKey: string;
715
+ };
682
716
  } | {
683
717
  type: "config.reload.started";
684
718
  payload?: Record<string, unknown>;
@@ -758,4 +792,4 @@ declare function deleteSession(configPath: string, key: string): boolean;
758
792
  declare function updateRuntime(configPath: string, patch: RuntimeConfigUpdate): Pick<ConfigView, "agents" | "bindings" | "session">;
759
793
  declare function updateSecrets(configPath: string, patch: SecretsConfigUpdate): SecretsView;
760
794
 
761
- export { type AgentBindingView, type AgentProfileView, type ApiError, type ApiResponse, type AppMetaView, type BindingPeerView, type ChannelSpecView, type ChatCapabilitiesView, type ChatRunListView, type ChatRunState, type ChatRunView, type ChatTurnRequest, type ChatTurnResult, type ChatTurnStopRequest, type ChatTurnStopResult, type ChatTurnStreamEvent, type ChatTurnView, type ConfigActionExecuteRequest, type ConfigActionExecuteResult, type ConfigActionManifest, type ConfigActionType, type ConfigMetaView, type ConfigSchemaResponse, type ConfigUiHint, type ConfigUiHints, type ConfigView, type CronActionResult, type CronEnableRequest, type CronJobStateView, type CronJobView, type CronListView, type CronPayloadView, type CronRunRequest, type CronScheduleView, type MarketplaceApiConfig, type MarketplaceInstallKind, type MarketplaceInstallSkillParams, type MarketplaceInstallSpec, type MarketplaceInstalledRecord, type MarketplaceInstalledView, type MarketplaceInstaller, type MarketplaceItemSummary, type MarketplaceItemType, type MarketplaceItemView, type MarketplaceListView, type MarketplaceLocalizedTextMap, type MarketplacePluginContentView, type MarketplacePluginInstallRequest, type MarketplacePluginInstallResult, type MarketplacePluginManageAction, type MarketplacePluginManageRequest, type MarketplacePluginManageResult, type MarketplaceRecommendationView, type MarketplaceSkillContentView, type MarketplaceSkillInstallRequest, type MarketplaceSkillInstallResult, type MarketplaceSkillManageAction, type MarketplaceSkillManageRequest, type MarketplaceSkillManageResult, type MarketplaceSort, type ProviderAuthImportResult, type ProviderAuthPollRequest, type ProviderAuthPollResult, type ProviderAuthStartResult, type ProviderConfigUpdate, type ProviderConfigView, type ProviderConnectionTestRequest, type ProviderConnectionTestResult, type ProviderCreateRequest, type ProviderCreateResult, type ProviderDeleteResult, type ProviderSpecView, type RuntimeConfigUpdate, type SecretProviderEnvView, type SecretProviderExecView, type SecretProviderFileView, type SecretProviderView, type SecretRefView, type SecretSourceView, type SecretsConfigUpdate, type SecretsView, type SessionConfigView, type SessionEntryView, type SessionEventView, type SessionHistoryView, type SessionMessageView, type SessionPatchUpdate, type SessionsListView, type UiChatRuntime, type UiServerEvent, type UiServerHandle, type UiServerOptions, buildConfigMeta, buildConfigSchemaView, buildConfigView, createCustomProvider, createUiRouter, deleteCustomProvider, deleteSession, executeConfigAction, getSessionHistory, listSessions, loadConfigOrDefault, patchSession, startUiServer, testProviderConnection, updateChannel, updateModel, updateProvider, updateRuntime, updateSecrets };
795
+ export { type AgentBindingView, type AgentProfileView, type ApiError, type ApiResponse, type AppMetaView, type BindingPeerView, type ChannelSpecView, type ChatCapabilitiesView, type ChatCommandOptionView, type ChatCommandView, type ChatCommandsView, type ChatRunListView, type ChatRunState, type ChatRunView, type ChatTurnRequest, type ChatTurnResult, type ChatTurnStopRequest, type ChatTurnStopResult, type ChatTurnStreamEvent, type ChatTurnView, type ConfigActionExecuteRequest, type ConfigActionExecuteResult, type ConfigActionManifest, type ConfigActionType, type ConfigMetaView, type ConfigSchemaResponse, type ConfigUiHint, type ConfigUiHints, type ConfigView, type CronActionResult, type CronEnableRequest, type CronJobStateView, type CronJobView, type CronListView, type CronPayloadView, type CronRunRequest, type CronScheduleView, type MarketplaceApiConfig, type MarketplaceInstallKind, type MarketplaceInstallSkillParams, type MarketplaceInstallSpec, type MarketplaceInstalledRecord, type MarketplaceInstalledView, type MarketplaceInstaller, type MarketplaceItemSummary, type MarketplaceItemType, type MarketplaceItemView, type MarketplaceListView, type MarketplaceLocalizedTextMap, type MarketplacePluginContentView, type MarketplacePluginInstallKind, type MarketplacePluginInstallRequest, type MarketplacePluginInstallResult, type MarketplacePluginManageAction, type MarketplacePluginManageRequest, type MarketplacePluginManageResult, type MarketplaceRecommendationView, type MarketplaceSkillContentView, type MarketplaceSkillInstallKind, type MarketplaceSkillInstallRequest, type MarketplaceSkillInstallResult, type MarketplaceSkillManageAction, type MarketplaceSkillManageRequest, type MarketplaceSkillManageResult, type MarketplaceSort, type ProviderAuthImportResult, type ProviderAuthPollRequest, type ProviderAuthPollResult, type ProviderAuthStartRequest, type ProviderAuthStartResult, type ProviderConfigUpdate, type ProviderConfigView, type ProviderConnectionTestRequest, type ProviderConnectionTestResult, type ProviderCreateRequest, type ProviderCreateResult, type ProviderDeleteResult, type ProviderSpecView, type RuntimeConfigUpdate, type SecretProviderEnvView, type SecretProviderExecView, type SecretProviderFileView, type SecretProviderView, type SecretRefView, type SecretSourceView, type SecretsConfigUpdate, type SecretsView, type SessionConfigView, type SessionEntryView, type SessionEventView, type SessionHistoryView, type SessionMessageView, type SessionPatchUpdate, type SessionsListView, type UiChatRuntime, type UiServerEvent, type UiServerHandle, type UiServerOptions, buildConfigMeta, buildConfigSchemaView, buildConfigView, createCustomProvider, createUiRouter, deleteCustomProvider, deleteSession, executeConfigAction, getSessionHistory, listSessions, loadConfigOrDefault, patchSession, startUiServer, testProviderConnection, updateChannel, updateModel, updateProvider, updateRuntime, updateSecrets };
package/dist/index.js CHANGED
@@ -5,12 +5,11 @@ import { cors } from "hono/cors";
5
5
  import { serve } from "@hono/node-server";
6
6
  import { WebSocketServer, WebSocket } from "ws";
7
7
  import { existsSync, readFileSync } from "fs";
8
- import { readFile as readFile3, stat } from "fs/promises";
8
+ import { readFile as readFile2, stat } from "fs/promises";
9
9
  import { join } from "path";
10
10
 
11
11
  // src/ui/router.ts
12
12
  import { Hono } from "hono";
13
- import { readFile as readFile2 } from "fs/promises";
14
13
  import * as NextclawCore from "@nextclaw/core";
15
14
  import { buildPluginStatusReport } from "@nextclaw/openclaw-compat";
16
15
 
@@ -29,7 +28,96 @@ import {
29
28
  SessionManager,
30
29
  getWorkspacePathFromConfig
31
30
  } from "@nextclaw/core";
31
+
32
+ // src/ui/provider-overrides.ts
32
33
  import { findBuiltinProviderByName, listBuiltinProviders } from "@nextclaw/runtime";
34
+ var MINIMAX_PORTAL_PROVIDER_SPEC = {
35
+ name: "minimax-portal",
36
+ keywords: ["minimax-portal", "minimax"],
37
+ envKey: "MINIMAX_PORTAL_TOKEN",
38
+ displayName: "MiniMax Portal",
39
+ modelPrefix: "minimax-portal",
40
+ litellmPrefix: "minimax-portal",
41
+ skipPrefixes: ["minimax-portal/"],
42
+ envExtras: [],
43
+ isGateway: false,
44
+ isLocal: false,
45
+ detectByKeyPrefix: "",
46
+ detectByBaseKeyword: "",
47
+ defaultApiBase: "https://api.minimax.io/v1",
48
+ defaultModels: ["minimax-portal/MiniMax-M2.5", "minimax-portal/MiniMax-M2.5-highspeed"],
49
+ stripModelPrefix: false,
50
+ modelOverrides: [],
51
+ logo: "minimax.svg",
52
+ apiBaseHelp: {
53
+ zh: "OAuth Global \u9ED8\u8BA4\u4F7F\u7528 https://api.minimax.io/v1\uFF1BOAuth \u4E2D\u56FD\u533A\u9ED8\u8BA4\u4F7F\u7528 https://api.minimaxi.com/v1\u3002",
54
+ en: "OAuth Global uses https://api.minimax.io/v1 by default; OAuth CN uses https://api.minimaxi.com/v1."
55
+ },
56
+ auth: {
57
+ kind: "device_code",
58
+ protocol: "minimax_user_code",
59
+ displayName: "MiniMax OAuth",
60
+ baseUrl: "https://api.minimax.io",
61
+ deviceCodePath: "/oauth/code",
62
+ tokenPath: "/oauth/token",
63
+ clientId: "78257093-7e40-4613-99e0-527b14b39113",
64
+ scope: "group_id profile model.completion",
65
+ grantType: "urn:ietf:params:oauth:grant-type:user_code",
66
+ usePkce: true,
67
+ defaultMethodId: "cn",
68
+ methods: [
69
+ {
70
+ id: "global",
71
+ label: {
72
+ zh: "Global\uFF08\u6D77\u5916\uFF09",
73
+ en: "Global"
74
+ },
75
+ hint: {
76
+ zh: "\u9002\u7528\u4E8E\u6D77\u5916\u7528\u6237\uFF0C\u9ED8\u8BA4 API Base \u4E3A https://api.minimax.io/v1\u3002",
77
+ en: "For international users. Default API base: https://api.minimax.io/v1."
78
+ },
79
+ baseUrl: "https://api.minimax.io",
80
+ defaultApiBase: "https://api.minimax.io/v1"
81
+ },
82
+ {
83
+ id: "cn",
84
+ label: {
85
+ zh: "\u4E2D\u56FD\u533A\uFF08CN\uFF09",
86
+ en: "China Mainland (CN)"
87
+ },
88
+ hint: {
89
+ zh: "\u9002\u7528\u4E8E\u4E2D\u56FD\u533A\u7528\u6237\uFF0C\u9ED8\u8BA4 API Base \u4E3A https://api.minimaxi.com/v1\u3002",
90
+ en: "For Mainland China users. Default API base: https://api.minimaxi.com/v1."
91
+ },
92
+ baseUrl: "https://api.minimaxi.com",
93
+ defaultApiBase: "https://api.minimaxi.com/v1"
94
+ }
95
+ ],
96
+ note: {
97
+ zh: "\u901A\u8FC7\u6D4F\u89C8\u5668\u5B8C\u6210 MiniMax OAuth \u6388\u6743\u540E\u5373\u53EF\u4F7F\u7528\uFF0C\u65E0\u9700\u624B\u52A8\u586B\u5199 API Key\u3002",
98
+ en: "Complete MiniMax OAuth in browser to use this provider without manually entering an API key."
99
+ }
100
+ }
101
+ };
102
+ var SERVER_BUILTIN_PROVIDER_OVERRIDES = [MINIMAX_PORTAL_PROVIDER_SPEC];
103
+ var SERVER_BUILTIN_PROVIDER_OVERRIDE_MAP = new Map(
104
+ SERVER_BUILTIN_PROVIDER_OVERRIDES.map((provider) => [provider.name, provider])
105
+ );
106
+ function listServerBuiltinProviders() {
107
+ const merged = /* @__PURE__ */ new Map();
108
+ for (const provider of listBuiltinProviders()) {
109
+ merged.set(provider.name, provider);
110
+ }
111
+ for (const provider of SERVER_BUILTIN_PROVIDER_OVERRIDES) {
112
+ merged.set(provider.name, provider);
113
+ }
114
+ return Array.from(merged.values());
115
+ }
116
+ function findServerBuiltinProviderByName(name) {
117
+ return SERVER_BUILTIN_PROVIDER_OVERRIDE_MAP.get(name) ?? findBuiltinProviderByName(name);
118
+ }
119
+
120
+ // src/ui/config.ts
33
121
  var MASK_MIN_LENGTH = 8;
34
122
  var EXTRA_SENSITIVE_PATH_PATTERNS = [/authorization/i, /cookie/i, /session/i, /bearer/i];
35
123
  var PREFERRED_PROVIDER_ORDER = [
@@ -47,7 +135,7 @@ var PREFERRED_PROVIDER_ORDER = [
47
135
  var PREFERRED_PROVIDER_ORDER_INDEX = new Map(
48
136
  PREFERRED_PROVIDER_ORDER.map((name, index) => [name, index])
49
137
  );
50
- var BUILTIN_PROVIDERS = listBuiltinProviders();
138
+ var BUILTIN_PROVIDERS = listServerBuiltinProviders();
51
139
  var BUILTIN_PROVIDER_NAMES = new Set(BUILTIN_PROVIDERS.map((spec) => spec.name));
52
140
  var CUSTOM_PROVIDER_WIRE_API_OPTIONS = ["auto", "chat", "responses"];
53
141
  var CUSTOM_PROVIDER_PREFIX = "custom-";
@@ -108,7 +196,7 @@ function ensureProviderConfig(config, providerName) {
108
196
  if (isCustomProviderName(providerName)) {
109
197
  return null;
110
198
  }
111
- const spec = findBuiltinProviderByName(providerName);
199
+ const spec = findServerBuiltinProviderByName(providerName);
112
200
  if (!spec) {
113
201
  return null;
114
202
  }
@@ -376,7 +464,7 @@ function buildConfigView(config) {
376
464
  const uiHints = buildUiHints(config);
377
465
  const providers = {};
378
466
  for (const [name, provider] of Object.entries(config.providers)) {
379
- const spec = findBuiltinProviderByName(name);
467
+ const spec = findServerBuiltinProviderByName(name);
380
468
  providers[name] = toProviderView(config, provider, name, uiHints, spec);
381
469
  }
382
470
  return {
@@ -425,6 +513,12 @@ function buildConfigMeta(config) {
425
513
  kind: spec.auth.kind,
426
514
  displayName: spec.auth.displayName,
427
515
  note: spec.auth.note,
516
+ methods: spec.auth.methods?.map((method) => ({
517
+ id: method.id,
518
+ label: method.label,
519
+ hint: method.hint
520
+ })),
521
+ defaultMethodId: spec.auth.defaultMethodId,
428
522
  supportsCliImport: Boolean(spec.auth.cliCredential)
429
523
  } : void 0,
430
524
  defaultModels: normalizeModelList(spec.defaultModels ?? []),
@@ -555,7 +649,7 @@ function updateProvider(configPath, providerName, patch) {
555
649
  if (!provider) {
556
650
  return null;
557
651
  }
558
- const spec = findBuiltinProviderByName(providerName);
652
+ const spec = findServerBuiltinProviderByName(providerName);
559
653
  const isCustom = isCustomProviderName(providerName);
560
654
  if (Object.prototype.hasOwnProperty.call(patch, "displayName") && isCustom) {
561
655
  provider.displayName = normalizeOptionalDisplayName(patch.displayName) ?? "";
@@ -690,7 +784,7 @@ async function testProviderConnection(configPath, providerName, patch) {
690
784
  if (!provider) {
691
785
  return null;
692
786
  }
693
- const spec = findBuiltinProviderByName(providerName);
787
+ const spec = findServerBuiltinProviderByName(providerName);
694
788
  const hasApiKeyPatch = Object.prototype.hasOwnProperty.call(patch, "apiKey");
695
789
  const providedApiKey = normalizeOptionalString(patch.apiKey);
696
790
  const currentApiKey = normalizeOptionalString(provider.apiKey);
@@ -1038,7 +1132,6 @@ import {
1038
1132
  loadConfig as loadConfig2,
1039
1133
  saveConfig as saveConfig2
1040
1134
  } from "@nextclaw/core";
1041
- import { findBuiltinProviderByName as findBuiltinProviderByName2 } from "@nextclaw/runtime";
1042
1135
  var authSessions = /* @__PURE__ */ new Map();
1043
1136
  var DEFAULT_AUTH_INTERVAL_MS = 2e3;
1044
1137
  var MAX_AUTH_INTERVAL_MS = 1e4;
@@ -1048,6 +1141,12 @@ function normalizePositiveInt(value, fallback) {
1048
1141
  }
1049
1142
  return Math.floor(value);
1050
1143
  }
1144
+ function normalizePositiveFloat(value) {
1145
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
1146
+ return null;
1147
+ }
1148
+ return value;
1149
+ }
1051
1150
  function toBase64Url(buffer) {
1052
1151
  return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
1053
1152
  }
@@ -1074,6 +1173,113 @@ function resolveDeviceCodeEndpoints(baseUrl, deviceCodePath, tokenPath) {
1074
1173
  function resolveAuthNote(params) {
1075
1174
  return params.zh ?? params.en;
1076
1175
  }
1176
+ function resolveLocalizedMethodLabel(method, fallbackId) {
1177
+ return method.label?.zh ?? method.label?.en ?? fallbackId;
1178
+ }
1179
+ function resolveLocalizedMethodHint(method) {
1180
+ return method.hint?.zh ?? method.hint?.en;
1181
+ }
1182
+ function normalizeMethodId(value) {
1183
+ if (typeof value !== "string") {
1184
+ return void 0;
1185
+ }
1186
+ const trimmed = value.trim();
1187
+ return trimmed.length > 0 ? trimmed : void 0;
1188
+ }
1189
+ function resolveAuthMethod(auth, requestedMethodId) {
1190
+ const protocol = auth.protocol ?? "rfc8628";
1191
+ const methods = (auth.methods ?? []).filter((entry) => normalizeMethodId(entry.id));
1192
+ const cleanRequestedMethodId = normalizeMethodId(requestedMethodId);
1193
+ if (methods.length === 0) {
1194
+ if (cleanRequestedMethodId) {
1195
+ throw new Error(`provider auth method is not supported: ${cleanRequestedMethodId}`);
1196
+ }
1197
+ return {
1198
+ protocol,
1199
+ baseUrl: auth.baseUrl,
1200
+ deviceCodePath: auth.deviceCodePath,
1201
+ tokenPath: auth.tokenPath,
1202
+ clientId: auth.clientId,
1203
+ scope: auth.scope,
1204
+ grantType: auth.grantType,
1205
+ usePkce: Boolean(auth.usePkce)
1206
+ };
1207
+ }
1208
+ let selectedMethod = methods.find((entry) => normalizeMethodId(entry.id) === cleanRequestedMethodId);
1209
+ if (!selectedMethod) {
1210
+ const fallbackMethodId = normalizeMethodId(auth.defaultMethodId) ?? normalizeMethodId(methods[0]?.id);
1211
+ selectedMethod = methods.find((entry) => normalizeMethodId(entry.id) === fallbackMethodId) ?? methods[0];
1212
+ }
1213
+ const methodId = normalizeMethodId(selectedMethod?.id);
1214
+ if (!selectedMethod || !methodId) {
1215
+ throw new Error("provider auth method is not configured");
1216
+ }
1217
+ if (cleanRequestedMethodId && methodId !== cleanRequestedMethodId) {
1218
+ throw new Error(`provider auth method is not supported: ${cleanRequestedMethodId}`);
1219
+ }
1220
+ return {
1221
+ id: methodId,
1222
+ protocol,
1223
+ baseUrl: selectedMethod.baseUrl ?? auth.baseUrl,
1224
+ deviceCodePath: selectedMethod.deviceCodePath ?? auth.deviceCodePath,
1225
+ tokenPath: selectedMethod.tokenPath ?? auth.tokenPath,
1226
+ clientId: selectedMethod.clientId ?? auth.clientId,
1227
+ scope: selectedMethod.scope ?? auth.scope,
1228
+ grantType: selectedMethod.grantType ?? auth.grantType,
1229
+ usePkce: selectedMethod.usePkce ?? Boolean(auth.usePkce),
1230
+ defaultApiBase: selectedMethod.defaultApiBase
1231
+ };
1232
+ }
1233
+ function parseExpiresAtMs(value, fallbackFromNowMs) {
1234
+ const normalized = normalizePositiveFloat(value);
1235
+ if (normalized === null) {
1236
+ return Date.now() + fallbackFromNowMs;
1237
+ }
1238
+ if (normalized >= 1e12) {
1239
+ return Math.floor(normalized);
1240
+ }
1241
+ if (normalized >= 1e9) {
1242
+ return Math.floor(normalized * 1e3);
1243
+ }
1244
+ return Date.now() + Math.floor(normalized * 1e3);
1245
+ }
1246
+ function parsePollIntervalMs(value, fallbackMs) {
1247
+ const normalized = normalizePositiveFloat(value);
1248
+ if (normalized === null) {
1249
+ return fallbackMs;
1250
+ }
1251
+ if (normalized <= 30) {
1252
+ return Math.floor(normalized * 1e3);
1253
+ }
1254
+ return Math.floor(normalized);
1255
+ }
1256
+ function buildMinimaxErrorMessage(payload, fallback) {
1257
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
1258
+ return fallback;
1259
+ }
1260
+ const record = payload;
1261
+ if (typeof record.error_description === "string" && record.error_description.trim()) {
1262
+ return record.error_description.trim();
1263
+ }
1264
+ if (typeof record.error === "string" && record.error.trim()) {
1265
+ return record.error.trim();
1266
+ }
1267
+ const baseMessage = record.base_resp?.status_msg;
1268
+ if (typeof baseMessage === "string" && baseMessage.trim()) {
1269
+ return baseMessage.trim();
1270
+ }
1271
+ return fallback;
1272
+ }
1273
+ function classifyMiniMaxErrorStatus(message) {
1274
+ const normalized = message.toLowerCase();
1275
+ if (normalized.includes("deny") || normalized.includes("rejected")) {
1276
+ return "denied";
1277
+ }
1278
+ if (normalized.includes("expired") || normalized.includes("timeout") || normalized.includes("timed out")) {
1279
+ return "expired";
1280
+ }
1281
+ return "error";
1282
+ }
1077
1283
  function resolveHomePath(inputPath) {
1078
1284
  const trimmed = inputPath.trim();
1079
1285
  if (!trimmed) {
@@ -1138,70 +1344,125 @@ function setProviderApiKey(params) {
1138
1344
  const next = ConfigSchema2.parse(config);
1139
1345
  saveConfig2(next, params.configPath);
1140
1346
  }
1141
- async function startProviderAuth(configPath, providerName) {
1347
+ async function startProviderAuth(configPath, providerName, options) {
1142
1348
  cleanupExpiredAuthSessions();
1143
- const spec = findBuiltinProviderByName2(providerName);
1349
+ const spec = findServerBuiltinProviderByName(providerName);
1144
1350
  if (!spec?.auth || spec.auth.kind !== "device_code") {
1145
1351
  return null;
1146
1352
  }
1353
+ const resolvedMethod = resolveAuthMethod(spec.auth, options?.methodId);
1147
1354
  const { deviceCodeEndpoint, tokenEndpoint } = resolveDeviceCodeEndpoints(
1148
- spec.auth.baseUrl,
1149
- spec.auth.deviceCodePath,
1150
- spec.auth.tokenPath
1355
+ resolvedMethod.baseUrl,
1356
+ resolvedMethod.deviceCodePath,
1357
+ resolvedMethod.tokenPath
1151
1358
  );
1152
- const pkce = spec.auth.usePkce ? buildPkce() : null;
1153
- const body = new URLSearchParams({
1154
- client_id: spec.auth.clientId,
1155
- scope: spec.auth.scope
1156
- });
1157
- if (pkce) {
1158
- body.set("code_challenge", pkce.challenge);
1159
- body.set("code_challenge_method", "S256");
1359
+ const pkce = resolvedMethod.usePkce ? buildPkce() : null;
1360
+ let authorizationCode = "";
1361
+ let tokenCodeField = "device_code";
1362
+ let userCode = "";
1363
+ let verificationUri = "";
1364
+ let intervalMs = DEFAULT_AUTH_INTERVAL_MS;
1365
+ let expiresAtMs = Date.now() + 6e5;
1366
+ if (resolvedMethod.protocol === "minimax_user_code") {
1367
+ if (!pkce) {
1368
+ throw new Error("MiniMax OAuth requires PKCE");
1369
+ }
1370
+ const state = toBase64Url(randomBytes(16));
1371
+ const body = new URLSearchParams({
1372
+ response_type: "code",
1373
+ client_id: resolvedMethod.clientId,
1374
+ scope: resolvedMethod.scope,
1375
+ code_challenge: pkce.challenge,
1376
+ code_challenge_method: "S256",
1377
+ state
1378
+ });
1379
+ const response = await fetch(deviceCodeEndpoint, {
1380
+ method: "POST",
1381
+ headers: {
1382
+ "Content-Type": "application/x-www-form-urlencoded",
1383
+ Accept: "application/json",
1384
+ "x-request-id": randomUUID()
1385
+ },
1386
+ body
1387
+ });
1388
+ const payload = await response.json().catch(() => ({}));
1389
+ if (!response.ok) {
1390
+ throw new Error(buildMinimaxErrorMessage(payload, response.statusText || "MiniMax OAuth start failed"));
1391
+ }
1392
+ if (payload.state && payload.state !== state) {
1393
+ throw new Error("MiniMax OAuth state mismatch");
1394
+ }
1395
+ authorizationCode = payload.user_code?.trim() ?? "";
1396
+ userCode = authorizationCode;
1397
+ verificationUri = payload.verification_uri?.trim() ?? "";
1398
+ if (!authorizationCode || !verificationUri) {
1399
+ throw new Error("provider auth payload is incomplete");
1400
+ }
1401
+ tokenCodeField = "user_code";
1402
+ intervalMs = Math.min(parsePollIntervalMs(payload.interval, DEFAULT_AUTH_INTERVAL_MS), MAX_AUTH_INTERVAL_MS);
1403
+ expiresAtMs = parseExpiresAtMs(payload.expired_in, 6e5);
1404
+ } else {
1405
+ const body = new URLSearchParams({
1406
+ client_id: resolvedMethod.clientId,
1407
+ scope: resolvedMethod.scope
1408
+ });
1409
+ if (pkce) {
1410
+ body.set("code_challenge", pkce.challenge);
1411
+ body.set("code_challenge_method", "S256");
1412
+ }
1413
+ const response = await fetch(deviceCodeEndpoint, {
1414
+ method: "POST",
1415
+ headers: {
1416
+ "Content-Type": "application/x-www-form-urlencoded",
1417
+ Accept: "application/json"
1418
+ },
1419
+ body
1420
+ });
1421
+ const payload = await response.json().catch(() => ({}));
1422
+ if (!response.ok) {
1423
+ const message = payload.error_description || payload.error || response.statusText || "device code auth failed";
1424
+ throw new Error(message);
1425
+ }
1426
+ authorizationCode = payload.device_code?.trim() ?? "";
1427
+ userCode = payload.user_code?.trim() ?? "";
1428
+ verificationUri = payload.verification_uri_complete?.trim() || payload.verification_uri?.trim() || "";
1429
+ if (!authorizationCode || !userCode || !verificationUri) {
1430
+ throw new Error("provider auth payload is incomplete");
1431
+ }
1432
+ intervalMs = normalizePositiveInt(payload.interval, DEFAULT_AUTH_INTERVAL_MS / 1e3) * 1e3;
1433
+ const expiresInSec = normalizePositiveInt(payload.expires_in, 600);
1434
+ expiresAtMs = Date.now() + expiresInSec * 1e3;
1160
1435
  }
1161
- const response = await fetch(deviceCodeEndpoint, {
1162
- method: "POST",
1163
- headers: {
1164
- "Content-Type": "application/x-www-form-urlencoded",
1165
- Accept: "application/json"
1166
- },
1167
- body
1168
- });
1169
- const payload = await response.json().catch(() => ({}));
1170
- if (!response.ok) {
1171
- const message = payload.error_description || payload.error || response.statusText || "device code auth failed";
1172
- throw new Error(message);
1173
- }
1174
- const deviceCode = payload.device_code?.trim() ?? "";
1175
- const userCode = payload.user_code?.trim() ?? "";
1176
- const verificationUri = payload.verification_uri_complete?.trim() || payload.verification_uri?.trim() || "";
1177
- if (!deviceCode || !userCode || !verificationUri) {
1178
- throw new Error("provider auth payload is incomplete");
1179
- }
1180
- const intervalMs = normalizePositiveInt(payload.interval, DEFAULT_AUTH_INTERVAL_MS / 1e3) * 1e3;
1181
- const expiresInSec = normalizePositiveInt(payload.expires_in, 600);
1182
- const expiresAtMs = Date.now() + expiresInSec * 1e3;
1183
1436
  const sessionId = randomUUID();
1184
1437
  authSessions.set(sessionId, {
1185
1438
  sessionId,
1186
1439
  provider: providerName,
1187
1440
  configPath,
1188
- deviceCode,
1441
+ authorizationCode,
1442
+ tokenCodeField,
1443
+ protocol: resolvedMethod.protocol,
1444
+ methodId: resolvedMethod.id,
1189
1445
  codeVerifier: pkce?.verifier,
1190
1446
  tokenEndpoint,
1191
- clientId: spec.auth.clientId,
1192
- grantType: spec.auth.grantType,
1447
+ clientId: resolvedMethod.clientId,
1448
+ grantType: resolvedMethod.grantType,
1449
+ defaultApiBase: resolvedMethod.defaultApiBase ?? spec.defaultApiBase,
1193
1450
  expiresAtMs,
1194
1451
  intervalMs
1195
1452
  });
1453
+ const methodConfig = (spec.auth.methods ?? []).find((entry) => normalizeMethodId(entry.id) === resolvedMethod.id);
1454
+ const methodLabel = methodConfig ? resolveLocalizedMethodLabel(methodConfig, resolvedMethod.id ?? "") : void 0;
1455
+ const methodHint = methodConfig ? resolveLocalizedMethodHint(methodConfig) : void 0;
1196
1456
  return {
1197
1457
  provider: providerName,
1198
1458
  kind: "device_code",
1459
+ methodId: resolvedMethod.id,
1199
1460
  sessionId,
1200
1461
  verificationUri,
1201
1462
  userCode,
1202
1463
  expiresAt: new Date(expiresAtMs).toISOString(),
1203
1464
  intervalMs,
1204
- note: resolveAuthNote(spec.auth.note ?? {})
1465
+ note: methodHint ?? methodLabel ?? resolveAuthNote(spec.auth.note ?? {})
1205
1466
  };
1206
1467
  }
1207
1468
  async function pollProviderAuth(params) {
@@ -1220,9 +1481,9 @@ async function pollProviderAuth(params) {
1220
1481
  }
1221
1482
  const body = new URLSearchParams({
1222
1483
  grant_type: session.grantType,
1223
- client_id: session.clientId,
1224
- device_code: session.deviceCode
1484
+ client_id: session.clientId
1225
1485
  });
1486
+ body.set(session.tokenCodeField, session.authorizationCode);
1226
1487
  if (session.codeVerifier) {
1227
1488
  body.set("code_verifier", session.codeVerifier);
1228
1489
  }
@@ -1234,17 +1495,47 @@ async function pollProviderAuth(params) {
1234
1495
  },
1235
1496
  body
1236
1497
  });
1237
- const payload = await response.json().catch(() => ({}));
1238
- if (!response.ok) {
1239
- const errorCode = payload.error?.trim().toLowerCase();
1240
- if (errorCode === "authorization_pending") {
1498
+ let accessToken = "";
1499
+ if (session.protocol === "minimax_user_code") {
1500
+ const raw = await response.text();
1501
+ let payload = {};
1502
+ if (raw) {
1503
+ try {
1504
+ payload = JSON.parse(raw);
1505
+ } catch {
1506
+ payload = {};
1507
+ }
1508
+ }
1509
+ if (!response.ok) {
1510
+ const message = buildMinimaxErrorMessage(payload, raw || response.statusText || "authorization failed");
1241
1511
  return {
1242
1512
  provider: params.providerName,
1243
- status: "pending",
1244
- nextPollMs: session.intervalMs
1513
+ status: "error",
1514
+ message
1245
1515
  };
1246
1516
  }
1247
- if (errorCode === "slow_down") {
1517
+ const status = payload.status?.trim().toLowerCase();
1518
+ if (status === "success") {
1519
+ accessToken = payload.access_token?.trim() ?? "";
1520
+ if (!accessToken) {
1521
+ return {
1522
+ provider: params.providerName,
1523
+ status: "error",
1524
+ message: "provider token response missing access token"
1525
+ };
1526
+ }
1527
+ } else if (status === "error") {
1528
+ const message = buildMinimaxErrorMessage(payload, "authorization failed");
1529
+ const classified = classifyMiniMaxErrorStatus(message);
1530
+ if (classified === "denied" || classified === "expired") {
1531
+ authSessions.delete(params.sessionId);
1532
+ }
1533
+ return {
1534
+ provider: params.providerName,
1535
+ status: classified,
1536
+ message
1537
+ };
1538
+ } else {
1248
1539
  const nextPollMs = Math.min(Math.floor(session.intervalMs * 1.5), MAX_AUTH_INTERVAL_MS);
1249
1540
  session.intervalMs = nextPollMs;
1250
1541
  authSessions.set(params.sessionId, session);
@@ -1254,42 +1545,63 @@ async function pollProviderAuth(params) {
1254
1545
  nextPollMs
1255
1546
  };
1256
1547
  }
1257
- if (errorCode === "access_denied") {
1258
- authSessions.delete(params.sessionId);
1548
+ } else {
1549
+ const payload = await response.json().catch(() => ({}));
1550
+ if (!response.ok) {
1551
+ const errorCode = payload.error?.trim().toLowerCase();
1552
+ if (errorCode === "authorization_pending") {
1553
+ return {
1554
+ provider: params.providerName,
1555
+ status: "pending",
1556
+ nextPollMs: session.intervalMs
1557
+ };
1558
+ }
1559
+ if (errorCode === "slow_down") {
1560
+ const nextPollMs = Math.min(Math.floor(session.intervalMs * 1.5), MAX_AUTH_INTERVAL_MS);
1561
+ session.intervalMs = nextPollMs;
1562
+ authSessions.set(params.sessionId, session);
1563
+ return {
1564
+ provider: params.providerName,
1565
+ status: "pending",
1566
+ nextPollMs
1567
+ };
1568
+ }
1569
+ if (errorCode === "access_denied") {
1570
+ authSessions.delete(params.sessionId);
1571
+ return {
1572
+ provider: params.providerName,
1573
+ status: "denied",
1574
+ message: payload.error_description || "authorization denied"
1575
+ };
1576
+ }
1577
+ if (errorCode === "expired_token") {
1578
+ authSessions.delete(params.sessionId);
1579
+ return {
1580
+ provider: params.providerName,
1581
+ status: "expired",
1582
+ message: payload.error_description || "authorization session expired"
1583
+ };
1584
+ }
1259
1585
  return {
1260
1586
  provider: params.providerName,
1261
- status: "denied",
1262
- message: payload.error_description || "authorization denied"
1587
+ status: "error",
1588
+ message: payload.error_description || payload.error || response.statusText || "authorization failed"
1263
1589
  };
1264
1590
  }
1265
- if (errorCode === "expired_token") {
1266
- authSessions.delete(params.sessionId);
1591
+ accessToken = payload.access_token?.trim() ?? "";
1592
+ if (!accessToken) {
1267
1593
  return {
1268
1594
  provider: params.providerName,
1269
- status: "expired",
1270
- message: payload.error_description || "authorization session expired"
1595
+ status: "error",
1596
+ message: "provider token response missing access token"
1271
1597
  };
1272
1598
  }
1273
- return {
1274
- provider: params.providerName,
1275
- status: "error",
1276
- message: payload.error_description || payload.error || response.statusText || "authorization failed"
1277
- };
1278
- }
1279
- const accessToken = payload.access_token?.trim();
1280
- if (!accessToken) {
1281
- return {
1282
- provider: params.providerName,
1283
- status: "error",
1284
- message: "provider token response missing access token"
1285
- };
1286
1599
  }
1287
- const spec = findBuiltinProviderByName2(params.providerName);
1288
1600
  setProviderApiKey({
1289
1601
  configPath: params.configPath,
1290
1602
  provider: params.providerName,
1291
1603
  accessToken,
1292
- defaultApiBase: spec?.defaultApiBase
1604
+ defaultApiBase: session.defaultApiBase
1293
1605
  });
1294
1606
  authSessions.delete(params.sessionId);
1295
1607
  return {
@@ -1298,7 +1610,7 @@ async function pollProviderAuth(params) {
1298
1610
  };
1299
1611
  }
1300
1612
  async function importProviderAuthFromCli(configPath, providerName) {
1301
- const spec = findBuiltinProviderByName2(providerName);
1613
+ const spec = findServerBuiltinProviderByName(providerName);
1302
1614
  if (!spec?.auth || spec.auth.kind !== "device_code" || !spec.auth.cliCredential) {
1303
1615
  return null;
1304
1616
  }
@@ -1999,98 +2311,23 @@ function isSupportedMarketplaceSkillItem(item, knownSkillNames) {
1999
2311
  if (item.type !== "skill") {
2000
2312
  return false;
2001
2313
  }
2002
- if (item.install.kind === "git") {
2314
+ if (item.install.kind === "marketplace") {
2003
2315
  return true;
2004
2316
  }
2005
2317
  return item.install.kind === "builtin" && knownSkillNames.has(item.install.spec);
2006
2318
  }
2007
- function splitMarkdownFrontmatter(raw) {
2008
- const normalized = raw.replace(/\r\n/g, "\n");
2009
- const match = normalized.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
2010
- if (!match) {
2011
- return { bodyRaw: normalized };
2012
- }
2013
- return {
2014
- metadataRaw: match[1]?.trim() || void 0,
2015
- bodyRaw: match[2] ?? ""
2016
- };
2017
- }
2018
- async function loadLocalSkillMarkdown(options, skillName) {
2019
- const config = loadConfigOrDefault(options.configPath);
2020
- const loader = createSkillsLoader(getWorkspacePathFromConfig3(config));
2021
- if (!loader) {
2022
- return null;
2023
- }
2024
- const skillInfo = loader.listSkills(false).find((skill) => skill.name === skillName);
2025
- if (!skillInfo) {
2026
- return null;
2027
- }
2028
- try {
2029
- const raw = await readFile2(skillInfo.path, "utf-8");
2030
- return {
2031
- raw,
2032
- source: skillInfo.source
2033
- };
2034
- } catch {
2035
- return null;
2036
- }
2037
- }
2038
- function parseGitSkillSpec(rawSpec) {
2039
- const spec = rawSpec.trim();
2040
- if (!spec) {
2041
- return null;
2042
- }
2043
- const segments = spec.split("/").filter(Boolean);
2044
- if (segments.length < 3) {
2045
- return null;
2046
- }
2047
- return {
2048
- owner: segments[0] ?? "",
2049
- repo: segments[1] ?? "",
2050
- skillPath: segments.slice(2).join("/")
2051
- };
2052
- }
2053
- async function fetchTextWithFallback(urls) {
2054
- for (const url of urls) {
2055
- try {
2056
- const response = await fetch(url, {
2057
- method: "GET",
2058
- headers: {
2059
- Accept: "text/plain, text/markdown, application/json"
2060
- }
2061
- });
2062
- if (!response.ok) {
2063
- continue;
2064
- }
2065
- const text = await response.text();
2066
- if (text.trim().length === 0) {
2067
- continue;
2068
- }
2069
- return { text, url };
2070
- } catch {
2319
+ function findUnsupportedSkillInstallKind(items) {
2320
+ for (const item of items) {
2321
+ if (item.type !== "skill") {
2071
2322
  continue;
2072
2323
  }
2324
+ const kind = item.install.kind;
2325
+ if (kind !== "builtin" && kind !== "marketplace") {
2326
+ return kind;
2327
+ }
2073
2328
  }
2074
2329
  return null;
2075
2330
  }
2076
- async function loadGitSkillMarkdownFromSpec(rawSpec) {
2077
- const parsed = parseGitSkillSpec(rawSpec);
2078
- if (!parsed) {
2079
- return null;
2080
- }
2081
- const candidates = [
2082
- `https://raw.githubusercontent.com/${parsed.owner}/${parsed.repo}/main/${parsed.skillPath}/SKILL.md`,
2083
- `https://raw.githubusercontent.com/${parsed.owner}/${parsed.repo}/master/${parsed.skillPath}/SKILL.md`
2084
- ];
2085
- const result = await fetchTextWithFallback(candidates);
2086
- if (!result) {
2087
- return null;
2088
- }
2089
- return {
2090
- raw: result.text,
2091
- sourceUrl: result.url
2092
- };
2093
- }
2094
2331
  async function loadPluginReadmeFromNpm(spec) {
2095
2332
  const encodedSpec = encodeURIComponent(spec);
2096
2333
  const registryUrl = `https://registry.npmjs.org/${encodedSpec}`;
@@ -2124,40 +2361,6 @@ async function loadPluginReadmeFromNpm(spec) {
2124
2361
  return null;
2125
2362
  }
2126
2363
  }
2127
- async function buildSkillContentView(options, item) {
2128
- const local = await loadLocalSkillMarkdown(options, item.install.spec);
2129
- if (local) {
2130
- const split = splitMarkdownFrontmatter(local.raw);
2131
- return {
2132
- type: "skill",
2133
- slug: item.slug,
2134
- name: item.name,
2135
- install: item.install,
2136
- source: local.source,
2137
- raw: local.raw,
2138
- metadataRaw: split.metadataRaw,
2139
- bodyRaw: split.bodyRaw
2140
- };
2141
- }
2142
- if (item.install.kind === "git") {
2143
- const remote = await loadGitSkillMarkdownFromSpec(item.install.spec);
2144
- if (remote) {
2145
- const split = splitMarkdownFrontmatter(remote.raw);
2146
- return {
2147
- type: "skill",
2148
- slug: item.slug,
2149
- name: item.name,
2150
- install: item.install,
2151
- source: "git",
2152
- raw: remote.raw,
2153
- metadataRaw: split.metadataRaw,
2154
- bodyRaw: split.bodyRaw,
2155
- sourceUrl: remote.sourceUrl
2156
- };
2157
- }
2158
- }
2159
- return null;
2160
- }
2161
2364
  async function buildPluginContentView(item) {
2162
2365
  if (item.install.kind === "npm") {
2163
2366
  const npm = await loadPluginReadmeFromNpm(item.install.spec);
@@ -2276,8 +2479,6 @@ async function installMarketplaceSkill(params) {
2276
2479
  kind: params.body.kind,
2277
2480
  skill: params.body.skill,
2278
2481
  installPath: params.body.installPath,
2279
- version: params.body.version,
2280
- registry: params.body.registry,
2281
2482
  force: params.body.force
2282
2483
  });
2283
2484
  params.options.publish({ type: "config.updated", payload: { path: "skills" } });
@@ -2504,8 +2705,16 @@ function registerSkillMarketplaceRoutes(app, options, marketplaceBaseUrl) {
2504
2705
  if (!result.ok) {
2505
2706
  return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
2506
2707
  }
2708
+ const normalizedItems = result.data.items.map((item) => normalizeMarketplaceItemForUi(sanitizeMarketplaceItem(item)));
2709
+ const unsupportedKind = findUnsupportedSkillInstallKind(normalizedItems);
2710
+ if (unsupportedKind) {
2711
+ return c.json(
2712
+ err("MARKETPLACE_CONTRACT_MISMATCH", `unsupported skill install kind from marketplace api: ${unsupportedKind}`),
2713
+ 502
2714
+ );
2715
+ }
2507
2716
  const knownSkillNames = collectKnownSkillNames(options);
2508
- const filteredItems = result.data.items.map((item) => normalizeMarketplaceItemForUi(sanitizeMarketplaceItem(item))).filter((item) => isSupportedMarketplaceSkillItem(item, knownSkillNames));
2717
+ const filteredItems = normalizedItems.filter((item) => isSupportedMarketplaceSkillItem(item, knownSkillNames));
2509
2718
  const pageSize = Math.min(100, toPositiveInt(query.pageSize, 20));
2510
2719
  const requestedPage = toPositiveInt(query.page, 1);
2511
2720
  const totalPages = filteredItems.length === 0 ? 0 : Math.ceil(filteredItems.length / pageSize);
@@ -2531,6 +2740,13 @@ function registerSkillMarketplaceRoutes(app, options, marketplaceBaseUrl) {
2531
2740
  }
2532
2741
  const knownSkillNames = collectKnownSkillNames(options);
2533
2742
  const sanitized = normalizeMarketplaceItemForUi(sanitizeMarketplaceItem(result.data));
2743
+ const unsupportedKind = findUnsupportedSkillInstallKind([sanitized]);
2744
+ if (unsupportedKind) {
2745
+ return c.json(
2746
+ err("MARKETPLACE_CONTRACT_MISMATCH", `unsupported skill install kind from marketplace api: ${unsupportedKind}`),
2747
+ 502
2748
+ );
2749
+ }
2534
2750
  if (!isSupportedMarketplaceSkillItem(sanitized, knownSkillNames)) {
2535
2751
  return c.json(err("NOT_FOUND", "marketplace item not supported by nextclaw"), 404);
2536
2752
  }
@@ -2547,14 +2763,24 @@ function registerSkillMarketplaceRoutes(app, options, marketplaceBaseUrl) {
2547
2763
  }
2548
2764
  const knownSkillNames = collectKnownSkillNames(options);
2549
2765
  const sanitized = normalizeMarketplaceItemForUi(sanitizeMarketplaceItem(result.data));
2766
+ const unsupportedKind = findUnsupportedSkillInstallKind([sanitized]);
2767
+ if (unsupportedKind) {
2768
+ return c.json(
2769
+ err("MARKETPLACE_CONTRACT_MISMATCH", `unsupported skill install kind from marketplace api: ${unsupportedKind}`),
2770
+ 502
2771
+ );
2772
+ }
2550
2773
  if (!isSupportedMarketplaceSkillItem(sanitized, knownSkillNames)) {
2551
2774
  return c.json(err("NOT_FOUND", "marketplace item not supported by nextclaw"), 404);
2552
2775
  }
2553
- const content = await buildSkillContentView(options, sanitized);
2554
- if (!content) {
2555
- return c.json(err("NOT_FOUND", "skill markdown content not found"), 404);
2776
+ const contentResult = await fetchMarketplaceData({
2777
+ baseUrl: marketplaceBaseUrl,
2778
+ path: `/api/v1/skills/items/${slug}/content`
2779
+ });
2780
+ if (!contentResult.ok) {
2781
+ return c.json(err("MARKETPLACE_UNAVAILABLE", contentResult.message), contentResult.status);
2556
2782
  }
2557
- return c.json(ok(content));
2783
+ return c.json(ok(contentResult.data));
2558
2784
  });
2559
2785
  app.post("/api/marketplace/skills/install", async (c) => {
2560
2786
  const body = await readJson(c.req.raw);
@@ -2727,8 +2953,20 @@ function createUiRouter(options) {
2727
2953
  });
2728
2954
  app.post("/api/config/providers/:provider/auth/start", async (c) => {
2729
2955
  const provider = c.req.param("provider");
2956
+ let payload = {};
2957
+ const rawBody = await c.req.raw.text();
2958
+ if (rawBody.trim().length > 0) {
2959
+ try {
2960
+ payload = JSON.parse(rawBody);
2961
+ } catch {
2962
+ return c.json(err("INVALID_BODY", "invalid json body"), 400);
2963
+ }
2964
+ }
2965
+ const methodId = typeof payload.methodId === "string" ? payload.methodId.trim() : void 0;
2730
2966
  try {
2731
- const result = await startProviderAuth(options.configPath, provider);
2967
+ const result = await startProviderAuth(options.configPath, provider, {
2968
+ methodId
2969
+ });
2732
2970
  if (!result) {
2733
2971
  return c.json(err("NOT_SUPPORTED", `provider auth is not supported: ${provider}`), 404);
2734
2972
  }
@@ -2814,6 +3052,31 @@ function createUiRouter(options) {
2814
3052
  return c.json(err("CHAT_RUNTIME_FAILED", String(error)), 500);
2815
3053
  }
2816
3054
  });
3055
+ app.get("/api/chat/commands", async (c) => {
3056
+ try {
3057
+ const config = loadConfigOrDefault(options.configPath);
3058
+ const registry = new NextclawCore.CommandRegistry(config);
3059
+ const commands = registry.listSlashCommands().map((command) => ({
3060
+ name: command.name,
3061
+ description: command.description,
3062
+ ...Array.isArray(command.options) && command.options.length > 0 ? {
3063
+ options: command.options.map((option) => ({
3064
+ name: option.name,
3065
+ description: option.description,
3066
+ type: option.type,
3067
+ ...option.required === true ? { required: true } : {}
3068
+ }))
3069
+ } : {}
3070
+ }));
3071
+ const payload = {
3072
+ commands,
3073
+ total: commands.length
3074
+ };
3075
+ return c.json(ok(payload));
3076
+ } catch (error) {
3077
+ return c.json(err("CHAT_COMMANDS_FAILED", String(error)), 500);
3078
+ }
3079
+ });
2817
3080
  app.post("/api/chat/turn", async (c) => {
2818
3081
  if (!options.chatRuntime) {
2819
3082
  return c.json(err("NOT_AVAILABLE", "chat runtime unavailable"), 503);
@@ -3419,7 +3682,7 @@ function startUiServer(options) {
3419
3682
  join,
3420
3683
  getContent: async (path) => {
3421
3684
  try {
3422
- return await readFile3(path);
3685
+ return await readFile2(path);
3423
3686
  } catch {
3424
3687
  return null;
3425
3688
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/server",
3
- "version": "0.6.4",
3
+ "version": "0.6.6",
4
4
  "private": false,
5
5
  "description": "Nextclaw UI/API server.",
6
6
  "type": "module",
@@ -15,11 +15,11 @@
15
15
  ],
16
16
  "dependencies": {
17
17
  "@hono/node-server": "^1.13.3",
18
- "@nextclaw/openclaw-compat": "^0.2.0",
19
- "@nextclaw/runtime": "^0.1.1",
18
+ "@nextclaw/runtime": "^0.1.2",
20
19
  "hono": "^4.6.2",
21
20
  "ws": "^8.18.0",
22
- "@nextclaw/core": "^0.7.1"
21
+ "@nextclaw/core": "^0.7.3",
22
+ "@nextclaw/openclaw-compat": "0.2.1"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/node": "^20.17.6",