nextclaw 0.9.14 → 0.9.16
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/cli/index.js +603 -426
- package/package.json +3 -3
- package/templates/USAGE.md +3 -2
- package/ui-dist/assets/{ChannelsList-DH5fzlPu.js → ChannelsList-DRqUf_f1.js} +1 -1
- package/ui-dist/assets/{ChatPage-BrLCnJSb.js → ChatPage-XNOO4MdH.js} +12 -12
- package/ui-dist/assets/{DocBrowser-DPQHJVsZ.js → DocBrowser-B_7djIEq.js} +1 -1
- package/ui-dist/assets/{LogoBadge-FEb4_vSq.js → LogoBadge-CEQerLPb.js} +1 -1
- package/ui-dist/assets/{MarketplacePage-BAVXYeZA.js → MarketplacePage-DBzY0jNC.js} +3 -3
- package/ui-dist/assets/{ModelConfig-BqPXe7nw.js → ModelConfig-BIcnKQ9L.js} +1 -1
- package/ui-dist/assets/{ProvidersList-vpKPuIxV.js → ProvidersList-K0TZvjGO.js} +1 -1
- package/ui-dist/assets/{RuntimeConfig-DTYSU4_d.js → RuntimeConfig-DlOB3OyD.js} +1 -1
- package/ui-dist/assets/{SecretsConfig-nNzs3YDm.js → SecretsConfig-Dl7ISZSg.js} +1 -1
- package/ui-dist/assets/{SessionsConfig-CHjeyqEQ.js → SessionsConfig-BQrPKyRq.js} +1 -1
- package/ui-dist/assets/{card-73MmEZi7.js → card-B-c2OgfM.js} +1 -1
- package/ui-dist/assets/{index-DI6BuShn.css → index-C_DhisNo.css} +1 -1
- package/ui-dist/assets/{index-CTLvVlk8.js → index-gXBW3ns8.js} +5 -5
- package/ui-dist/assets/{input-1MCMs6Yf.js → input-vcZIRBEV.js} +1 -1
- package/ui-dist/assets/{label-C4Q8RlBJ.js → label-DZa1Y6BS.js} +1 -1
- package/ui-dist/assets/{page-layout-CK0vcVmV.js → page-layout-DVoD2FFE.js} +1 -1
- package/ui-dist/assets/{session-run-status-BaNlKvi6.js → session-run-status-C0z5CFQq.js} +1 -1
- package/ui-dist/assets/{switch-Bf8w_cF1.js → switch-DNEnGqWo.js} +1 -1
- package/ui-dist/assets/{tabs-custom-B6Gw8gax.js → tabs-custom-B7PsWX6R.js} +1 -1
- package/ui-dist/assets/{useConfirmDialog-B5CZ4EDN.js → useConfirmDialog-PNI-ofd5.js} +1 -1
- package/ui-dist/index.html +2 -2
package/dist/cli/index.js
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
resolvePluginChannelMessageToolHints as resolvePluginChannelMessageToolHints2,
|
|
27
27
|
setPluginRuntimeBridge as setPluginRuntimeBridge2
|
|
28
28
|
} from "@nextclaw/openclaw-compat";
|
|
29
|
-
import { existsSync as existsSync10, mkdirSync as
|
|
29
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "fs";
|
|
30
30
|
import { join as join7, resolve as resolve9 } from "path";
|
|
31
31
|
import { createInterface as createInterface2 } from "readline";
|
|
32
32
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -185,15 +185,13 @@ function parseSessionKey(sessionKey) {
|
|
|
185
185
|
};
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
// src/cli/skills/
|
|
189
|
-
import {
|
|
190
|
-
import {
|
|
191
|
-
import {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
throw new Error("Skill slug is required.");
|
|
196
|
-
}
|
|
188
|
+
// src/cli/skills/marketplace.ts
|
|
189
|
+
import { cpSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
190
|
+
import { basename, dirname, isAbsolute, join, relative, resolve as resolve2 } from "path";
|
|
191
|
+
import { SkillsLoader } from "@nextclaw/core";
|
|
192
|
+
var DEFAULT_MARKETPLACE_API_BASE = "https://marketplace-api.nextclaw.io";
|
|
193
|
+
async function installMarketplaceSkill(options) {
|
|
194
|
+
const slug = validateSkillSlug(options.slug.trim(), "slug");
|
|
197
195
|
const workdir = resolve2(options.workdir);
|
|
198
196
|
if (!existsSync2(workdir)) {
|
|
199
197
|
throw new Error(`Workdir does not exist: ${workdir}`);
|
|
@@ -205,62 +203,256 @@ async function installClawHubSkill(options) {
|
|
|
205
203
|
if (existsSync2(skillFile)) {
|
|
206
204
|
return {
|
|
207
205
|
slug,
|
|
208
|
-
version: options.version,
|
|
209
|
-
registry: options.registry,
|
|
210
206
|
destinationDir,
|
|
211
|
-
alreadyInstalled: true
|
|
207
|
+
alreadyInstalled: true,
|
|
208
|
+
source: "marketplace"
|
|
212
209
|
};
|
|
213
210
|
}
|
|
214
211
|
throw new Error(`Skill directory already exists: ${destinationDir} (use --force)`);
|
|
215
212
|
}
|
|
216
|
-
const
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
213
|
+
const apiBase = resolveMarketplaceApiBase(options.apiBaseUrl);
|
|
214
|
+
const item = await fetchMarketplaceSkillItem(apiBase, slug);
|
|
215
|
+
if (item.install.kind === "builtin") {
|
|
216
|
+
if (existsSync2(destinationDir) && options.force) {
|
|
217
|
+
rmSync2(destinationDir, { recursive: true, force: true });
|
|
218
|
+
}
|
|
219
|
+
installBuiltinSkill(workdir, destinationDir, slug);
|
|
220
|
+
return {
|
|
221
|
+
slug,
|
|
222
|
+
destinationDir,
|
|
223
|
+
source: "builtin"
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
const filesPayload = await fetchMarketplaceSkillFiles(apiBase, slug);
|
|
227
|
+
if (existsSync2(destinationDir) && options.force) {
|
|
228
|
+
rmSync2(destinationDir, { recursive: true, force: true });
|
|
229
|
+
}
|
|
230
|
+
mkdirSync2(destinationDir, { recursive: true });
|
|
231
|
+
for (const file of filesPayload.files) {
|
|
232
|
+
const targetPath = resolve2(destinationDir, ...file.path.split("/"));
|
|
233
|
+
const rel = relative(destinationDir, targetPath);
|
|
234
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
235
|
+
throw new Error(`Invalid marketplace file path: ${file.path}`);
|
|
236
|
+
}
|
|
237
|
+
mkdirSync2(dirname(targetPath), { recursive: true });
|
|
238
|
+
writeFileSync2(targetPath, Buffer.from(file.contentBase64, "base64"));
|
|
224
239
|
}
|
|
225
|
-
if (
|
|
226
|
-
|
|
227
|
-
const stderr = result.stderr ? String(result.stderr).trim() : "";
|
|
228
|
-
const details = [stderr, stdout].filter(Boolean).join("\n");
|
|
229
|
-
throw new Error(details || `clawhub install failed with code ${result.status ?? 1}`);
|
|
240
|
+
if (!existsSync2(join(destinationDir, "SKILL.md"))) {
|
|
241
|
+
throw new Error(`Marketplace skill ${slug} does not include SKILL.md`);
|
|
230
242
|
}
|
|
231
243
|
return {
|
|
232
244
|
slug,
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
destinationDir
|
|
245
|
+
destinationDir,
|
|
246
|
+
source: "marketplace"
|
|
236
247
|
};
|
|
237
248
|
}
|
|
238
|
-
function
|
|
239
|
-
const
|
|
240
|
-
if (
|
|
241
|
-
|
|
249
|
+
async function publishMarketplaceSkill(options) {
|
|
250
|
+
const skillDir = resolve2(options.skillDir);
|
|
251
|
+
if (!existsSync2(skillDir)) {
|
|
252
|
+
throw new Error(`Skill directory not found: ${skillDir}`);
|
|
253
|
+
}
|
|
254
|
+
const files = collectFiles(skillDir);
|
|
255
|
+
if (!files.some((file) => file.path === "SKILL.md")) {
|
|
256
|
+
throw new Error(`Skill directory must include SKILL.md: ${skillDir}`);
|
|
257
|
+
}
|
|
258
|
+
const parsedFrontmatter = parseSkillFrontmatter(readFileSync2(join(skillDir, "SKILL.md"), "utf8"));
|
|
259
|
+
const slug = validateSkillSlug(options.slug?.trim() || basename(skillDir), "slug");
|
|
260
|
+
const name = options.name?.trim() || parsedFrontmatter.name || slug;
|
|
261
|
+
const description = options.description?.trim() || parsedFrontmatter.description;
|
|
262
|
+
const summary = options.summary?.trim() || parsedFrontmatter.summary || description || `${slug} skill`;
|
|
263
|
+
const author = options.author?.trim() || parsedFrontmatter.author || "nextclaw";
|
|
264
|
+
const tags = normalizeTags(options.tags && options.tags.length > 0 ? options.tags : parsedFrontmatter.tags);
|
|
265
|
+
const apiBase = resolveMarketplaceApiBase(options.apiBaseUrl);
|
|
266
|
+
const token = resolveMarketplaceAdminToken(options.token);
|
|
267
|
+
if (options.requireExisting) {
|
|
268
|
+
await fetchMarketplaceSkillItem(apiBase, slug);
|
|
269
|
+
}
|
|
270
|
+
const response = await fetch(`${apiBase}/api/v1/admin/skills/upsert`, {
|
|
271
|
+
method: "POST",
|
|
272
|
+
headers: {
|
|
273
|
+
"content-type": "application/json",
|
|
274
|
+
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
275
|
+
},
|
|
276
|
+
body: JSON.stringify({
|
|
277
|
+
slug,
|
|
278
|
+
name,
|
|
279
|
+
summary,
|
|
280
|
+
description,
|
|
281
|
+
author,
|
|
282
|
+
tags,
|
|
283
|
+
sourceRepo: options.sourceRepo?.trim() || void 0,
|
|
284
|
+
homepage: options.homepage?.trim() || void 0,
|
|
285
|
+
publishedAt: options.publishedAt?.trim() || void 0,
|
|
286
|
+
updatedAt: options.updatedAt?.trim() || void 0,
|
|
287
|
+
files
|
|
288
|
+
})
|
|
289
|
+
});
|
|
290
|
+
const payload = await readMarketplaceEnvelope(response);
|
|
291
|
+
if (!payload.ok || !payload.data) {
|
|
292
|
+
const message = payload.error?.message || `marketplace publish failed: HTTP ${response.status}`;
|
|
293
|
+
throw new Error(message);
|
|
242
294
|
}
|
|
243
|
-
|
|
244
|
-
|
|
295
|
+
return {
|
|
296
|
+
created: payload.data.created,
|
|
297
|
+
slug,
|
|
298
|
+
fileCount: payload.data.fileCount
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
function collectFiles(rootDir) {
|
|
302
|
+
const output = [];
|
|
303
|
+
const walk = (dir, prefix) => {
|
|
304
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
305
|
+
for (const entry of entries) {
|
|
306
|
+
const absolute = join(dir, entry.name);
|
|
307
|
+
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
308
|
+
if (entry.isDirectory()) {
|
|
309
|
+
walk(absolute, relativePath);
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
if (!entry.isFile()) {
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
const content = readFileSync2(absolute);
|
|
316
|
+
output.push({
|
|
317
|
+
path: relativePath,
|
|
318
|
+
contentBase64: content.toString("base64")
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
walk(rootDir, "");
|
|
323
|
+
return output;
|
|
324
|
+
}
|
|
325
|
+
function installBuiltinSkill(workdir, destinationDir, skillName) {
|
|
326
|
+
const loader = new SkillsLoader(workdir);
|
|
327
|
+
const builtin = loader.listSkills(false).find((skill) => skill.name === skillName && skill.source === "builtin");
|
|
328
|
+
if (!builtin) {
|
|
329
|
+
throw new Error(`Builtin skill not found in local core bundle: ${skillName}`);
|
|
245
330
|
}
|
|
246
|
-
|
|
247
|
-
|
|
331
|
+
cpSync(dirname(builtin.path), destinationDir, { recursive: true, force: true });
|
|
332
|
+
}
|
|
333
|
+
async function fetchMarketplaceSkillItem(apiBase, slug) {
|
|
334
|
+
const response = await fetch(`${apiBase}/api/v1/skills/items/${encodeURIComponent(slug)}`, {
|
|
335
|
+
headers: {
|
|
336
|
+
Accept: "application/json"
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
const payload = await readMarketplaceEnvelope(response);
|
|
340
|
+
if (!payload.ok || !payload.data) {
|
|
341
|
+
const message = payload.error?.message || `marketplace skill fetch failed: ${response.status}`;
|
|
342
|
+
throw new Error(message);
|
|
248
343
|
}
|
|
249
|
-
|
|
250
|
-
|
|
344
|
+
const kind = payload.data.install?.kind;
|
|
345
|
+
if (kind !== "builtin" && kind !== "marketplace") {
|
|
346
|
+
throw new Error(`Unsupported skill install kind from marketplace: ${String(kind)}`);
|
|
251
347
|
}
|
|
252
|
-
|
|
253
|
-
|
|
348
|
+
return {
|
|
349
|
+
install: {
|
|
350
|
+
kind
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
async function fetchMarketplaceSkillFiles(apiBase, slug) {
|
|
355
|
+
const response = await fetch(`${apiBase}/api/v1/skills/items/${encodeURIComponent(slug)}/files`, {
|
|
356
|
+
headers: {
|
|
357
|
+
Accept: "application/json"
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
const payload = await readMarketplaceEnvelope(response);
|
|
361
|
+
if (!payload.ok || !payload.data) {
|
|
362
|
+
const message = payload.error?.message || `marketplace skill file fetch failed: ${response.status}`;
|
|
363
|
+
throw new Error(message);
|
|
254
364
|
}
|
|
255
|
-
return
|
|
365
|
+
return payload.data;
|
|
366
|
+
}
|
|
367
|
+
async function readMarketplaceEnvelope(response) {
|
|
368
|
+
const raw = await response.text();
|
|
369
|
+
let payload;
|
|
370
|
+
try {
|
|
371
|
+
payload = raw.length > 0 ? JSON.parse(raw) : null;
|
|
372
|
+
} catch {
|
|
373
|
+
throw new Error(`Invalid marketplace response: ${response.status}`);
|
|
374
|
+
}
|
|
375
|
+
if (!isRecord(payload) || typeof payload.ok !== "boolean") {
|
|
376
|
+
throw new Error(`Invalid marketplace response shape: ${response.status}`);
|
|
377
|
+
}
|
|
378
|
+
return payload;
|
|
379
|
+
}
|
|
380
|
+
function resolveMarketplaceApiBase(explicitBase) {
|
|
381
|
+
const raw = explicitBase?.trim() || process.env.NEXTCLAW_MARKETPLACE_API_BASE?.trim() || DEFAULT_MARKETPLACE_API_BASE;
|
|
382
|
+
return raw.replace(/\/+$/, "");
|
|
383
|
+
}
|
|
384
|
+
function resolveMarketplaceAdminToken(explicitToken) {
|
|
385
|
+
const token = explicitToken?.trim() || process.env.NEXTCLAW_MARKETPLACE_ADMIN_TOKEN?.trim();
|
|
386
|
+
return token && token.length > 0 ? token : void 0;
|
|
387
|
+
}
|
|
388
|
+
function validateSkillSlug(raw, fieldName) {
|
|
389
|
+
if (!/^[A-Za-z0-9._-]+$/.test(raw)) {
|
|
390
|
+
throw new Error(`Invalid ${fieldName}: ${raw}`);
|
|
391
|
+
}
|
|
392
|
+
return raw;
|
|
393
|
+
}
|
|
394
|
+
function normalizeTags(rawTags) {
|
|
395
|
+
const seen = /* @__PURE__ */ new Set();
|
|
396
|
+
const output = [];
|
|
397
|
+
for (const rawTag of rawTags ?? []) {
|
|
398
|
+
const tag = rawTag.trim();
|
|
399
|
+
if (!tag || seen.has(tag)) {
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
seen.add(tag);
|
|
403
|
+
output.push(tag);
|
|
404
|
+
}
|
|
405
|
+
return output.length > 0 ? output : ["skill"];
|
|
406
|
+
}
|
|
407
|
+
function parseSkillFrontmatter(raw) {
|
|
408
|
+
const normalized = raw.replace(/\r\n/g, "\n");
|
|
409
|
+
const match = normalized.match(/^---\n([\s\S]*?)\n---/);
|
|
410
|
+
if (!match || !match[1]) {
|
|
411
|
+
return {};
|
|
412
|
+
}
|
|
413
|
+
const metadata = /* @__PURE__ */ new Map();
|
|
414
|
+
for (const line of match[1].split("\n")) {
|
|
415
|
+
const parsed = line.match(/^([A-Za-z0-9_-]+):\s*(.+)$/);
|
|
416
|
+
if (!parsed) {
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
const key = parsed[1]?.trim().toLowerCase();
|
|
420
|
+
const value = parsed[2]?.trim();
|
|
421
|
+
if (!key || !value) {
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
metadata.set(key, trimYamlString(value));
|
|
425
|
+
}
|
|
426
|
+
const rawTags = metadata.get("tags");
|
|
427
|
+
let tags;
|
|
428
|
+
if (rawTags) {
|
|
429
|
+
if (rawTags.startsWith("[") && rawTags.endsWith("]")) {
|
|
430
|
+
tags = rawTags.slice(1, -1).split(",").map((entry) => trimYamlString(entry)).filter(Boolean);
|
|
431
|
+
} else {
|
|
432
|
+
tags = rawTags.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return {
|
|
436
|
+
name: metadata.get("name"),
|
|
437
|
+
summary: metadata.get("summary"),
|
|
438
|
+
description: metadata.get("description"),
|
|
439
|
+
author: metadata.get("author"),
|
|
440
|
+
tags
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
function trimYamlString(raw) {
|
|
444
|
+
return raw.replace(/^['"]/, "").replace(/['"]$/, "").trim();
|
|
445
|
+
}
|
|
446
|
+
function isRecord(value) {
|
|
447
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
256
448
|
}
|
|
257
449
|
|
|
258
450
|
// src/cli/update/runner.ts
|
|
259
|
-
import { spawnSync
|
|
451
|
+
import { spawnSync } from "child_process";
|
|
260
452
|
import { resolve as resolve4 } from "path";
|
|
261
453
|
|
|
262
454
|
// src/cli/utils.ts
|
|
263
|
-
import { existsSync as existsSync3, mkdirSync as
|
|
455
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, rmSync as rmSync3 } from "fs";
|
|
264
456
|
import { join as join2, resolve as resolve3 } from "path";
|
|
265
457
|
import { spawn } from "child_process";
|
|
266
458
|
import { isIP } from "net";
|
|
@@ -319,7 +511,7 @@ function readServiceState() {
|
|
|
319
511
|
return null;
|
|
320
512
|
}
|
|
321
513
|
try {
|
|
322
|
-
const raw =
|
|
514
|
+
const raw = readFileSync3(path, "utf-8");
|
|
323
515
|
return JSON.parse(raw);
|
|
324
516
|
} catch {
|
|
325
517
|
return null;
|
|
@@ -327,13 +519,13 @@ function readServiceState() {
|
|
|
327
519
|
}
|
|
328
520
|
function writeServiceState(state) {
|
|
329
521
|
const path = resolveServiceStatePath();
|
|
330
|
-
|
|
331
|
-
|
|
522
|
+
mkdirSync3(resolve3(path, ".."), { recursive: true });
|
|
523
|
+
writeFileSync3(path, JSON.stringify(state, null, 2));
|
|
332
524
|
}
|
|
333
525
|
function clearServiceState() {
|
|
334
526
|
const path = resolveServiceStatePath();
|
|
335
527
|
if (existsSync3(path)) {
|
|
336
|
-
|
|
528
|
+
rmSync3(path, { force: true });
|
|
337
529
|
}
|
|
338
530
|
}
|
|
339
531
|
function resolveServiceStatePath() {
|
|
@@ -467,7 +659,7 @@ function resolveVersionFromPackageTree(startDir, expectedName) {
|
|
|
467
659
|
const pkgPath = join2(current, "package.json");
|
|
468
660
|
if (existsSync3(pkgPath)) {
|
|
469
661
|
try {
|
|
470
|
-
const raw =
|
|
662
|
+
const raw = readFileSync3(pkgPath, "utf-8");
|
|
471
663
|
const parsed = JSON.parse(raw);
|
|
472
664
|
if (typeof parsed.version === "string") {
|
|
473
665
|
if (!expectedName || parsed.name === expectedName) {
|
|
@@ -514,7 +706,7 @@ function runSelfUpdate(options = {}) {
|
|
|
514
706
|
return { cmd: process.env.SHELL || "sh", args: ["-c", command] };
|
|
515
707
|
};
|
|
516
708
|
const runStep = (cmd, args, cwd) => {
|
|
517
|
-
const result =
|
|
709
|
+
const result = spawnSync(cmd, args, {
|
|
518
710
|
cwd,
|
|
519
711
|
encoding: "utf-8",
|
|
520
712
|
timeout: timeoutMs,
|
|
@@ -1389,7 +1581,7 @@ var ConfigCommands = class {
|
|
|
1389
1581
|
};
|
|
1390
1582
|
|
|
1391
1583
|
// src/cli/commands/secrets.ts
|
|
1392
|
-
import { readFileSync as
|
|
1584
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
1393
1585
|
import {
|
|
1394
1586
|
buildReloadPlan as buildReloadPlan2,
|
|
1395
1587
|
diffConfigPaths as diffConfigPaths2,
|
|
@@ -1632,7 +1824,7 @@ var SecretsCommands = class {
|
|
|
1632
1824
|
nextConfig.secrets.enabled = false;
|
|
1633
1825
|
}
|
|
1634
1826
|
if (opts.file) {
|
|
1635
|
-
const raw =
|
|
1827
|
+
const raw = readFileSync4(opts.file, "utf-8");
|
|
1636
1828
|
const patch = parseApplyFile(raw);
|
|
1637
1829
|
if (patch.defaults) {
|
|
1638
1830
|
nextConfig.secrets.defaults = patch.defaults;
|
|
@@ -1712,7 +1904,7 @@ var SecretsCommands = class {
|
|
|
1712
1904
|
};
|
|
1713
1905
|
|
|
1714
1906
|
// src/cli/commands/channels.ts
|
|
1715
|
-
import { spawnSync as
|
|
1907
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
1716
1908
|
import { getWorkspacePath as getWorkspacePath2, loadConfig as loadConfig4, saveConfig as saveConfig4 } from "@nextclaw/core";
|
|
1717
1909
|
import { BUILTIN_CHANNEL_PLUGIN_IDS, builtinProviderIds as builtinProviderIds2 } from "@nextclaw/runtime";
|
|
1718
1910
|
import { buildPluginStatusReport as buildPluginStatusReport2, enablePluginInConfig as enablePluginInConfig2, getPluginChannelBindings } from "@nextclaw/openclaw-compat";
|
|
@@ -1762,7 +1954,7 @@ var ChannelCommands = class {
|
|
|
1762
1954
|
const bridgeDir = this.deps.getBridgeDir();
|
|
1763
1955
|
console.log(`${this.deps.logo} Starting bridge...`);
|
|
1764
1956
|
console.log("Scan the QR code to connect.\n");
|
|
1765
|
-
const result =
|
|
1957
|
+
const result = spawnSync2("npm", ["start"], { cwd: bridgeDir, stdio: "inherit" });
|
|
1766
1958
|
if (result.status !== 0) {
|
|
1767
1959
|
console.error(`Bridge failed: ${result.status ?? 1}`);
|
|
1768
1960
|
}
|
|
@@ -1903,7 +2095,7 @@ var CronCommands = class {
|
|
|
1903
2095
|
|
|
1904
2096
|
// src/cli/commands/diagnostics.ts
|
|
1905
2097
|
import { createServer as createNetServer } from "net";
|
|
1906
|
-
import { existsSync as existsSync5, readFileSync as
|
|
2098
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
1907
2099
|
import { resolve as resolve6 } from "path";
|
|
1908
2100
|
import {
|
|
1909
2101
|
APP_NAME,
|
|
@@ -2212,7 +2404,7 @@ var DiagnosticsCommands = class {
|
|
|
2212
2404
|
return [];
|
|
2213
2405
|
}
|
|
2214
2406
|
try {
|
|
2215
|
-
const lines =
|
|
2407
|
+
const lines = readFileSync5(path, "utf-8").split(/\r?\n/).filter(Boolean);
|
|
2216
2408
|
if (lines.length <= maxLines) {
|
|
2217
2409
|
return lines;
|
|
2218
2410
|
}
|
|
@@ -2252,9 +2444,8 @@ import {
|
|
|
2252
2444
|
stopPluginChannelGateways
|
|
2253
2445
|
} from "@nextclaw/openclaw-compat";
|
|
2254
2446
|
import { startUiServer } from "@nextclaw/server";
|
|
2255
|
-
import { appendFileSync, closeSync, cpSync, existsSync as existsSync8, mkdirSync as
|
|
2256
|
-
import { dirname
|
|
2257
|
-
import { tmpdir } from "os";
|
|
2447
|
+
import { appendFileSync, closeSync, cpSync as cpSync2, existsSync as existsSync8, mkdirSync as mkdirSync5, openSync, rmSync as rmSync4 } from "fs";
|
|
2448
|
+
import { dirname as dirname2, join as join5, resolve as resolve7 } from "path";
|
|
2258
2449
|
import { spawn as spawn2 } from "child_process";
|
|
2259
2450
|
import { request as httpRequest } from "http";
|
|
2260
2451
|
import { request as httpsRequest } from "https";
|
|
@@ -2263,7 +2454,7 @@ import chokidar from "chokidar";
|
|
|
2263
2454
|
|
|
2264
2455
|
// src/cli/gateway/controller.ts
|
|
2265
2456
|
import { createHash } from "crypto";
|
|
2266
|
-
import { existsSync as existsSync6, readFileSync as
|
|
2457
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
2267
2458
|
import {
|
|
2268
2459
|
buildConfigSchema,
|
|
2269
2460
|
ConfigSchema,
|
|
@@ -2276,7 +2467,7 @@ var readConfigSnapshot = (getConfigPath5) => {
|
|
|
2276
2467
|
let raw = "";
|
|
2277
2468
|
let parsed = {};
|
|
2278
2469
|
if (existsSync6(path)) {
|
|
2279
|
-
raw =
|
|
2470
|
+
raw = readFileSync6(path, "utf-8");
|
|
2280
2471
|
try {
|
|
2281
2472
|
parsed = JSON.parse(raw);
|
|
2282
2473
|
} catch {
|
|
@@ -2715,6 +2906,7 @@ var MissingProvider = class extends LLMProvider {
|
|
|
2715
2906
|
// src/cli/commands/agent-runtime-pool.ts
|
|
2716
2907
|
import {
|
|
2717
2908
|
NativeAgentEngine,
|
|
2909
|
+
CommandRegistry,
|
|
2718
2910
|
AgentRouteResolver,
|
|
2719
2911
|
getWorkspacePath as getWorkspacePath4,
|
|
2720
2912
|
parseAgentScopedSessionKey
|
|
@@ -2779,6 +2971,7 @@ var GatewayAgentRuntimePool = class {
|
|
|
2779
2971
|
runtimes = /* @__PURE__ */ new Map();
|
|
2780
2972
|
running = false;
|
|
2781
2973
|
defaultAgentId = "main";
|
|
2974
|
+
onSystemSessionUpdated = null;
|
|
2782
2975
|
get primaryAgentId() {
|
|
2783
2976
|
return this.defaultAgentId;
|
|
2784
2977
|
}
|
|
@@ -2795,6 +2988,9 @@ var GatewayAgentRuntimePool = class {
|
|
|
2795
2988
|
this.options.extensionRegistry = extensionRegistry;
|
|
2796
2989
|
this.rebuild(this.options.config);
|
|
2797
2990
|
}
|
|
2991
|
+
setSystemSessionUpdatedHandler(handler) {
|
|
2992
|
+
this.onSystemSessionUpdated = handler;
|
|
2993
|
+
}
|
|
2798
2994
|
async processDirect(params) {
|
|
2799
2995
|
const { message, route } = this.resolveDirectRoute({
|
|
2800
2996
|
content: params.content,
|
|
@@ -2804,6 +3000,14 @@ var GatewayAgentRuntimePool = class {
|
|
|
2804
3000
|
metadata: params.metadata,
|
|
2805
3001
|
agentId: params.agentId
|
|
2806
3002
|
});
|
|
3003
|
+
const commandResult = await this.executeDirectCommand(params.content, {
|
|
3004
|
+
channel: message.channel,
|
|
3005
|
+
chatId: message.chatId,
|
|
3006
|
+
sessionKey: route.sessionKey
|
|
3007
|
+
});
|
|
3008
|
+
if (commandResult) {
|
|
3009
|
+
return commandResult;
|
|
3010
|
+
}
|
|
2807
3011
|
const runtime2 = this.resolveRuntime(route.agentId);
|
|
2808
3012
|
return runtime2.engine.processDirect({
|
|
2809
3013
|
content: params.content,
|
|
@@ -2816,6 +3020,42 @@ var GatewayAgentRuntimePool = class {
|
|
|
2816
3020
|
onSessionEvent: params.onSessionEvent
|
|
2817
3021
|
});
|
|
2818
3022
|
}
|
|
3023
|
+
async executeDirectCommand(rawContent, ctx) {
|
|
3024
|
+
const trimmed = rawContent.trim();
|
|
3025
|
+
if (!trimmed.startsWith("/")) {
|
|
3026
|
+
return null;
|
|
3027
|
+
}
|
|
3028
|
+
const registry = new CommandRegistry(this.options.config, this.options.sessionManager);
|
|
3029
|
+
const executeText = registry.executeText;
|
|
3030
|
+
if (typeof executeText === "function") {
|
|
3031
|
+
const result2 = await executeText.call(registry, rawContent, {
|
|
3032
|
+
channel: ctx.channel,
|
|
3033
|
+
chatId: ctx.chatId,
|
|
3034
|
+
senderId: "user",
|
|
3035
|
+
sessionKey: ctx.sessionKey
|
|
3036
|
+
});
|
|
3037
|
+
return result2?.content ?? null;
|
|
3038
|
+
}
|
|
3039
|
+
const commandRaw = trimmed.slice(1).trim();
|
|
3040
|
+
if (!commandRaw) {
|
|
3041
|
+
return null;
|
|
3042
|
+
}
|
|
3043
|
+
const [nameToken, ...restTokens] = commandRaw.split(/\s+/);
|
|
3044
|
+
const commandName = nameToken.trim().toLowerCase();
|
|
3045
|
+
if (!commandName) {
|
|
3046
|
+
return null;
|
|
3047
|
+
}
|
|
3048
|
+
const commandTail = restTokens.join(" ").trim();
|
|
3049
|
+
const specs = registry.listSlashCommands();
|
|
3050
|
+
const args = parseCommandArgsFromText(commandName, commandTail, specs);
|
|
3051
|
+
const result = await registry.execute(commandName, args, {
|
|
3052
|
+
channel: ctx.channel,
|
|
3053
|
+
chatId: ctx.chatId,
|
|
3054
|
+
senderId: "user",
|
|
3055
|
+
sessionKey: ctx.sessionKey
|
|
3056
|
+
});
|
|
3057
|
+
return result?.content ?? null;
|
|
3058
|
+
}
|
|
2819
3059
|
supportsTurnAbort(params) {
|
|
2820
3060
|
const { route } = this.resolveDirectRoute({
|
|
2821
3061
|
content: "",
|
|
@@ -2856,6 +3096,12 @@ var GatewayAgentRuntimePool = class {
|
|
|
2856
3096
|
sessionKey: route.sessionKey,
|
|
2857
3097
|
publishResponse: true
|
|
2858
3098
|
});
|
|
3099
|
+
if (message.channel === "system") {
|
|
3100
|
+
this.onSystemSessionUpdated?.({
|
|
3101
|
+
sessionKey: route.sessionKey,
|
|
3102
|
+
message
|
|
3103
|
+
});
|
|
3104
|
+
}
|
|
2859
3105
|
} catch (error) {
|
|
2860
3106
|
await this.options.bus.publishOutbound({
|
|
2861
3107
|
channel: message.channel,
|
|
@@ -2988,9 +3234,57 @@ var GatewayAgentRuntimePool = class {
|
|
|
2988
3234
|
this.runtimes = nextRuntimes;
|
|
2989
3235
|
}
|
|
2990
3236
|
};
|
|
3237
|
+
function parseCommandArgsFromText(commandName, rawTail, specs) {
|
|
3238
|
+
if (!rawTail) {
|
|
3239
|
+
return {};
|
|
3240
|
+
}
|
|
3241
|
+
const command = specs.find((item) => item.name.trim().toLowerCase() === commandName);
|
|
3242
|
+
const options = command?.options;
|
|
3243
|
+
if (!options || options.length === 0) {
|
|
3244
|
+
return {};
|
|
3245
|
+
}
|
|
3246
|
+
const tokens = rawTail.split(/\s+/).filter(Boolean);
|
|
3247
|
+
const args = {};
|
|
3248
|
+
let cursor = 0;
|
|
3249
|
+
for (let i = 0; i < options.length; i += 1) {
|
|
3250
|
+
if (cursor >= tokens.length) {
|
|
3251
|
+
break;
|
|
3252
|
+
}
|
|
3253
|
+
const option = options[i];
|
|
3254
|
+
const isLastOption = i === options.length - 1;
|
|
3255
|
+
const rawValue = isLastOption ? tokens.slice(cursor).join(" ") : tokens[cursor];
|
|
3256
|
+
cursor += isLastOption ? tokens.length - cursor : 1;
|
|
3257
|
+
const parsedValue = parseCommandOptionValue(option.type, rawValue);
|
|
3258
|
+
if (parsedValue !== void 0) {
|
|
3259
|
+
args[option.name] = parsedValue;
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
return args;
|
|
3263
|
+
}
|
|
3264
|
+
function parseCommandOptionValue(type, rawValue) {
|
|
3265
|
+
const value = rawValue.trim();
|
|
3266
|
+
if (!value) {
|
|
3267
|
+
return void 0;
|
|
3268
|
+
}
|
|
3269
|
+
if (type === "number") {
|
|
3270
|
+
const parsed = Number(value);
|
|
3271
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
3272
|
+
}
|
|
3273
|
+
if (type === "boolean") {
|
|
3274
|
+
const lowered = value.toLowerCase();
|
|
3275
|
+
if (["1", "true", "yes", "on"].includes(lowered)) {
|
|
3276
|
+
return true;
|
|
3277
|
+
}
|
|
3278
|
+
if (["0", "false", "no", "off"].includes(lowered)) {
|
|
3279
|
+
return false;
|
|
3280
|
+
}
|
|
3281
|
+
return void 0;
|
|
3282
|
+
}
|
|
3283
|
+
return value;
|
|
3284
|
+
}
|
|
2991
3285
|
|
|
2992
3286
|
// src/cli/commands/ui-chat-run-coordinator.ts
|
|
2993
|
-
import { existsSync as existsSync7, mkdirSync as
|
|
3287
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
2994
3288
|
import { join as join4 } from "path";
|
|
2995
3289
|
import {
|
|
2996
3290
|
getDataDir as getDataDir5,
|
|
@@ -3004,7 +3298,7 @@ function createRunId() {
|
|
|
3004
3298
|
const rand = Math.random().toString(36).slice(2, 10);
|
|
3005
3299
|
return `run-${now}-${rand}`;
|
|
3006
3300
|
}
|
|
3007
|
-
function
|
|
3301
|
+
function isRecord2(value) {
|
|
3008
3302
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3009
3303
|
}
|
|
3010
3304
|
function readOptionalString(value) {
|
|
@@ -3035,7 +3329,7 @@ function cloneEvent(event) {
|
|
|
3035
3329
|
var UiChatRunCoordinator = class {
|
|
3036
3330
|
constructor(options) {
|
|
3037
3331
|
this.options = options;
|
|
3038
|
-
|
|
3332
|
+
mkdirSync4(RUNS_DIR, { recursive: true });
|
|
3039
3333
|
this.loadPersistedRuns();
|
|
3040
3334
|
}
|
|
3041
3335
|
runs = /* @__PURE__ */ new Map();
|
|
@@ -3172,7 +3466,7 @@ var UiChatRunCoordinator = class {
|
|
|
3172
3466
|
const parsedAgentId = parseAgentScopedSessionKey2(sessionKey)?.agentId;
|
|
3173
3467
|
const agentId = explicitAgentId ?? readOptionalString(parsedAgentId);
|
|
3174
3468
|
const model = readOptionalString(input.model);
|
|
3175
|
-
const metadata =
|
|
3469
|
+
const metadata = isRecord2(input.metadata) ? { ...input.metadata } : {};
|
|
3176
3470
|
if (model) {
|
|
3177
3471
|
metadata.model = model;
|
|
3178
3472
|
}
|
|
@@ -3414,20 +3708,20 @@ var UiChatRunCoordinator = class {
|
|
|
3414
3708
|
...typeof run.reply === "string" ? { reply: run.reply } : {},
|
|
3415
3709
|
events: run.events
|
|
3416
3710
|
};
|
|
3417
|
-
|
|
3711
|
+
writeFileSync4(this.getRunPath(run.runId), `${JSON.stringify(persisted, null, 2)}
|
|
3418
3712
|
`);
|
|
3419
3713
|
}
|
|
3420
3714
|
loadPersistedRuns() {
|
|
3421
3715
|
if (!existsSync7(RUNS_DIR)) {
|
|
3422
3716
|
return;
|
|
3423
3717
|
}
|
|
3424
|
-
for (const entry of
|
|
3718
|
+
for (const entry of readdirSync2(RUNS_DIR, { withFileTypes: true })) {
|
|
3425
3719
|
if (!entry.isFile() || !entry.name.endsWith(".json")) {
|
|
3426
3720
|
continue;
|
|
3427
3721
|
}
|
|
3428
3722
|
const path = join4(RUNS_DIR, entry.name);
|
|
3429
3723
|
try {
|
|
3430
|
-
const parsed = JSON.parse(
|
|
3724
|
+
const parsed = JSON.parse(readFileSync7(path, "utf-8"));
|
|
3431
3725
|
const runId = readOptionalString(parsed.runId);
|
|
3432
3726
|
const sessionKey = readOptionalString(parsed.sessionKey);
|
|
3433
3727
|
if (!runId || !sessionKey) {
|
|
@@ -3508,6 +3802,56 @@ function createSkillsLoader(workspace) {
|
|
|
3508
3802
|
}
|
|
3509
3803
|
return new ctor(workspace);
|
|
3510
3804
|
}
|
|
3805
|
+
function containsAbsoluteFsPath(line) {
|
|
3806
|
+
const normalized = line.trim();
|
|
3807
|
+
if (!normalized) {
|
|
3808
|
+
return false;
|
|
3809
|
+
}
|
|
3810
|
+
const lowered = normalized.toLowerCase();
|
|
3811
|
+
if (lowered.includes("http://") || lowered.includes("https://")) {
|
|
3812
|
+
return false;
|
|
3813
|
+
}
|
|
3814
|
+
if (/^[A-Za-z]:\\/.test(normalized)) {
|
|
3815
|
+
return true;
|
|
3816
|
+
}
|
|
3817
|
+
return /(?:^|\s)(?:~\/|\/[^\s]+)/.test(normalized);
|
|
3818
|
+
}
|
|
3819
|
+
function pickUserFacingCommandSummary(output, fallback) {
|
|
3820
|
+
const lines = output.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
3821
|
+
if (lines.length === 0) {
|
|
3822
|
+
return fallback;
|
|
3823
|
+
}
|
|
3824
|
+
const visibleLines = lines.filter((line) => {
|
|
3825
|
+
if (/^(path|install path|source path|destination|location)\s*:/i.test(line)) {
|
|
3826
|
+
return false;
|
|
3827
|
+
}
|
|
3828
|
+
if (containsAbsoluteFsPath(line)) {
|
|
3829
|
+
return false;
|
|
3830
|
+
}
|
|
3831
|
+
return true;
|
|
3832
|
+
});
|
|
3833
|
+
if (visibleLines.length === 0) {
|
|
3834
|
+
return fallback;
|
|
3835
|
+
}
|
|
3836
|
+
const preferred = [...visibleLines].reverse().find(
|
|
3837
|
+
(line) => /\b(installed|enabled|disabled|uninstalled|published|updated|already installed|removed)\b/i.test(line)
|
|
3838
|
+
);
|
|
3839
|
+
return preferred ?? visibleLines[visibleLines.length - 1] ?? fallback;
|
|
3840
|
+
}
|
|
3841
|
+
function buildMarketplaceSkillInstallArgs(params) {
|
|
3842
|
+
const args = ["skills", "install", params.slug, "--workdir", params.workspace];
|
|
3843
|
+
if (params.force) {
|
|
3844
|
+
args.push("--force");
|
|
3845
|
+
}
|
|
3846
|
+
return args;
|
|
3847
|
+
}
|
|
3848
|
+
function resolveCliSubcommandEntry(params) {
|
|
3849
|
+
const argvEntry = params.argvEntry?.trim();
|
|
3850
|
+
if (argvEntry) {
|
|
3851
|
+
return resolve7(argvEntry);
|
|
3852
|
+
}
|
|
3853
|
+
return fileURLToPath2(new URL("../index.js", params.importMetaUrl));
|
|
3854
|
+
}
|
|
3511
3855
|
var ServiceCommands = class {
|
|
3512
3856
|
constructor(deps) {
|
|
3513
3857
|
this.deps = deps;
|
|
@@ -3911,7 +4255,7 @@ var ServiceCommands = class {
|
|
|
3911
4255
|
}
|
|
3912
4256
|
const logPath = resolveServiceLogPath();
|
|
3913
4257
|
const logDir = resolve7(logPath, "..");
|
|
3914
|
-
|
|
4258
|
+
mkdirSync5(logDir, { recursive: true });
|
|
3915
4259
|
const logFd = openSync(logPath, "a");
|
|
3916
4260
|
const readinessTimeoutMs = this.resolveStartupTimeoutMs(options.startupTimeoutMs);
|
|
3917
4261
|
const quickPhaseTimeoutMs = Math.min(8e3, readinessTimeoutMs);
|
|
@@ -4216,6 +4560,21 @@ var ServiceCommands = class {
|
|
|
4216
4560
|
...params.model ? { model: params.model } : {}
|
|
4217
4561
|
});
|
|
4218
4562
|
let publishUiEvent = null;
|
|
4563
|
+
runtimePool.setSystemSessionUpdatedHandler(({ sessionKey, message }) => {
|
|
4564
|
+
if (!publishUiEvent) {
|
|
4565
|
+
return;
|
|
4566
|
+
}
|
|
4567
|
+
const isUiRoute = message.chatId.startsWith("ui:");
|
|
4568
|
+
if (!isUiRoute) {
|
|
4569
|
+
return;
|
|
4570
|
+
}
|
|
4571
|
+
publishUiEvent({
|
|
4572
|
+
type: "session.updated",
|
|
4573
|
+
payload: {
|
|
4574
|
+
sessionKey
|
|
4575
|
+
}
|
|
4576
|
+
});
|
|
4577
|
+
});
|
|
4219
4578
|
const runCoordinator = new UiChatRunCoordinator({
|
|
4220
4579
|
runtimePool,
|
|
4221
4580
|
sessionManager,
|
|
@@ -4311,8 +4670,8 @@ var ServiceCommands = class {
|
|
|
4311
4670
|
}
|
|
4312
4671
|
async installMarketplacePlugin(spec) {
|
|
4313
4672
|
const output = await this.runCliSubcommand(["plugins", "install", spec]);
|
|
4314
|
-
const summary =
|
|
4315
|
-
return { message: summary
|
|
4673
|
+
const summary = pickUserFacingCommandSummary(output, `Installed plugin: ${spec}`);
|
|
4674
|
+
return { message: summary };
|
|
4316
4675
|
}
|
|
4317
4676
|
async installMarketplaceSkill(params) {
|
|
4318
4677
|
if (params.kind === "builtin") {
|
|
@@ -4322,23 +4681,19 @@ var ServiceCommands = class {
|
|
|
4322
4681
|
}
|
|
4323
4682
|
return result;
|
|
4324
4683
|
}
|
|
4325
|
-
if (params.kind
|
|
4326
|
-
|
|
4327
|
-
}
|
|
4328
|
-
const args = ["skills", "install", params.slug];
|
|
4329
|
-
if (params.version) {
|
|
4330
|
-
args.push("--version", params.version);
|
|
4331
|
-
}
|
|
4332
|
-
if (params.registry) {
|
|
4333
|
-
args.push("--registry", params.registry);
|
|
4334
|
-
}
|
|
4335
|
-
if (params.force) {
|
|
4336
|
-
args.push("--force");
|
|
4684
|
+
if (params.kind && params.kind !== "marketplace") {
|
|
4685
|
+
throw new Error(`Unsupported marketplace skill kind: ${params.kind}`);
|
|
4337
4686
|
}
|
|
4687
|
+
const workspace = getWorkspacePath5(loadConfig6().agents.defaults.workspace);
|
|
4688
|
+
const args = buildMarketplaceSkillInstallArgs({
|
|
4689
|
+
slug: params.slug,
|
|
4690
|
+
workspace,
|
|
4691
|
+
force: params.force
|
|
4692
|
+
});
|
|
4338
4693
|
try {
|
|
4339
4694
|
const output = await this.runCliSubcommand(args);
|
|
4340
|
-
const summary =
|
|
4341
|
-
return { message: summary
|
|
4695
|
+
const summary = pickUserFacingCommandSummary(output, `Installed skill: ${params.slug}`);
|
|
4696
|
+
return { message: summary };
|
|
4342
4697
|
} catch (error) {
|
|
4343
4698
|
const fallback = this.installBuiltinMarketplaceSkill(params.slug, params.force);
|
|
4344
4699
|
if (!fallback) {
|
|
@@ -4347,258 +4702,20 @@ var ServiceCommands = class {
|
|
|
4347
4702
|
return fallback;
|
|
4348
4703
|
}
|
|
4349
4704
|
}
|
|
4350
|
-
async installGitMarketplaceSkill(params) {
|
|
4351
|
-
const source = params.slug.trim();
|
|
4352
|
-
if (!source) {
|
|
4353
|
-
throw new Error("Git skill source is required");
|
|
4354
|
-
}
|
|
4355
|
-
const workspace = getWorkspacePath5(loadConfig6().agents.defaults.workspace);
|
|
4356
|
-
const skillName = this.resolveGitSkillName(params.skill, source);
|
|
4357
|
-
const destination = this.resolveSkillInstallPath(workspace, params.installPath, skillName);
|
|
4358
|
-
const destinationSkillFile = join5(destination, "SKILL.md");
|
|
4359
|
-
if (existsSync8(destinationSkillFile) && !params.force) {
|
|
4360
|
-
return {
|
|
4361
|
-
message: `${skillName} is already installed`,
|
|
4362
|
-
output: destination
|
|
4363
|
-
};
|
|
4364
|
-
}
|
|
4365
|
-
if (existsSync8(destination) && !params.force) {
|
|
4366
|
-
throw new Error(`Skill install path already exists: ${destination} (use force to overwrite)`);
|
|
4367
|
-
}
|
|
4368
|
-
if (existsSync8(destination) && params.force) {
|
|
4369
|
-
rmSync3(destination, { recursive: true, force: true });
|
|
4370
|
-
}
|
|
4371
|
-
const materialized = await this.materializeMarketplaceGitSkillSource({ source, skillName });
|
|
4372
|
-
try {
|
|
4373
|
-
const installSkillFile = join5(materialized.skillDir, "SKILL.md");
|
|
4374
|
-
if (!existsSync8(installSkillFile)) {
|
|
4375
|
-
throw new Error(`git skill source does not contain SKILL.md for ${skillName}`);
|
|
4376
|
-
}
|
|
4377
|
-
mkdirSync4(dirname(destination), { recursive: true });
|
|
4378
|
-
cpSync(materialized.skillDir, destination, { recursive: true, force: true });
|
|
4379
|
-
return {
|
|
4380
|
-
message: `Installed skill: ${skillName}`,
|
|
4381
|
-
output: [
|
|
4382
|
-
`Source: ${source}`,
|
|
4383
|
-
`Materialized skill: ${materialized.skillDir}`,
|
|
4384
|
-
`Workspace target: ${destination}`,
|
|
4385
|
-
materialized.commandOutput
|
|
4386
|
-
].filter(Boolean).join("\n")
|
|
4387
|
-
};
|
|
4388
|
-
} finally {
|
|
4389
|
-
rmSync3(materialized.tempRoot, { recursive: true, force: true });
|
|
4390
|
-
}
|
|
4391
|
-
}
|
|
4392
|
-
async materializeMarketplaceGitSkillSource(params) {
|
|
4393
|
-
const parsed = this.parseMarketplaceGitSkillSource(params.source);
|
|
4394
|
-
const gitPath = this.resolveGitExecutablePath();
|
|
4395
|
-
const fallbackNotes = [];
|
|
4396
|
-
if (gitPath) {
|
|
4397
|
-
try {
|
|
4398
|
-
return await this.materializeMarketplaceGitSkillViaGit({ gitPath, parsed });
|
|
4399
|
-
} catch (error) {
|
|
4400
|
-
fallbackNotes.push(`Git fast path failed: ${String(error)}`);
|
|
4401
|
-
}
|
|
4402
|
-
} else {
|
|
4403
|
-
fallbackNotes.push("Git fast path unavailable: git executable not found");
|
|
4404
|
-
}
|
|
4405
|
-
const httpResult = await this.materializeMarketplaceGitSkillViaGithubApi(parsed);
|
|
4406
|
-
return {
|
|
4407
|
-
...httpResult,
|
|
4408
|
-
commandOutput: [...fallbackNotes, httpResult.commandOutput].filter(Boolean).join("\n")
|
|
4409
|
-
};
|
|
4410
|
-
}
|
|
4411
|
-
resolveGitExecutablePath() {
|
|
4412
|
-
return findExecutableOnPath("git");
|
|
4413
|
-
}
|
|
4414
|
-
async materializeMarketplaceGitSkillViaGit(params) {
|
|
4415
|
-
const tempRoot = mkdtempSync(join5(tmpdir(), "nextclaw-marketplace-skill-"));
|
|
4416
|
-
const repoDir = join5(tempRoot, "repo");
|
|
4417
|
-
try {
|
|
4418
|
-
const cloneArgs = ["clone", "--depth", "1", "--filter=blob:none", "--sparse"];
|
|
4419
|
-
if (params.parsed.ref) {
|
|
4420
|
-
cloneArgs.push("--branch", params.parsed.ref);
|
|
4421
|
-
}
|
|
4422
|
-
cloneArgs.push(params.parsed.repoUrl, repoDir);
|
|
4423
|
-
const cloneResult = await this.runCommand(params.gitPath, cloneArgs, {
|
|
4424
|
-
cwd: tempRoot,
|
|
4425
|
-
timeoutMs: 18e4
|
|
4426
|
-
});
|
|
4427
|
-
const sparseResult = await this.runCommand(params.gitPath, ["-C", repoDir, "sparse-checkout", "set", params.parsed.skillPath], {
|
|
4428
|
-
cwd: tempRoot,
|
|
4429
|
-
timeoutMs: 6e4
|
|
4430
|
-
});
|
|
4431
|
-
const skillDir = join5(repoDir, params.parsed.skillPath);
|
|
4432
|
-
return {
|
|
4433
|
-
tempRoot,
|
|
4434
|
-
skillDir,
|
|
4435
|
-
commandOutput: [
|
|
4436
|
-
"Installer path: git-sparse",
|
|
4437
|
-
`Git repository: ${params.parsed.repoUrl}`,
|
|
4438
|
-
`Git path: ${params.parsed.skillPath}`,
|
|
4439
|
-
this.mergeCommandOutput(cloneResult.stdout, cloneResult.stderr),
|
|
4440
|
-
this.mergeCommandOutput(sparseResult.stdout, sparseResult.stderr)
|
|
4441
|
-
].filter(Boolean).join("\n")
|
|
4442
|
-
};
|
|
4443
|
-
} catch (error) {
|
|
4444
|
-
rmSync3(tempRoot, { recursive: true, force: true });
|
|
4445
|
-
throw error;
|
|
4446
|
-
}
|
|
4447
|
-
}
|
|
4448
|
-
async materializeMarketplaceGitSkillViaGithubApi(parsed) {
|
|
4449
|
-
const tempRoot = mkdtempSync(join5(tmpdir(), "nextclaw-marketplace-skill-"));
|
|
4450
|
-
const skillDir = join5(tempRoot, "skill");
|
|
4451
|
-
const downloadedFiles = [];
|
|
4452
|
-
try {
|
|
4453
|
-
mkdirSync4(skillDir, { recursive: true });
|
|
4454
|
-
await this.downloadGithubDirectoryToPath({
|
|
4455
|
-
parsed,
|
|
4456
|
-
remotePath: parsed.skillPath,
|
|
4457
|
-
localDir: skillDir,
|
|
4458
|
-
relativePrefix: "",
|
|
4459
|
-
downloadedFiles
|
|
4460
|
-
});
|
|
4461
|
-
return {
|
|
4462
|
-
tempRoot,
|
|
4463
|
-
skillDir,
|
|
4464
|
-
commandOutput: [
|
|
4465
|
-
"Installer path: github-http",
|
|
4466
|
-
`Git repository: ${parsed.repoUrl}`,
|
|
4467
|
-
`Git path: ${parsed.skillPath}`,
|
|
4468
|
-
`Downloaded files: ${downloadedFiles.length}`
|
|
4469
|
-
].join("\n")
|
|
4470
|
-
};
|
|
4471
|
-
} catch (error) {
|
|
4472
|
-
rmSync3(tempRoot, { recursive: true, force: true });
|
|
4473
|
-
throw error;
|
|
4474
|
-
}
|
|
4475
|
-
}
|
|
4476
|
-
async downloadGithubDirectoryToPath(params) {
|
|
4477
|
-
const encodedPath = params.remotePath.split("/").filter(Boolean).map((segment) => encodeURIComponent(segment)).join("/");
|
|
4478
|
-
const apiUrl = new URL(`https://api.github.com/repos/${params.parsed.owner}/${params.parsed.repo}/contents/${encodedPath}`);
|
|
4479
|
-
if (params.parsed.ref) {
|
|
4480
|
-
apiUrl.searchParams.set("ref", params.parsed.ref);
|
|
4481
|
-
}
|
|
4482
|
-
const payload = await this.fetchJsonWithHeaders(apiUrl.toString(), {
|
|
4483
|
-
Accept: "application/vnd.github+json",
|
|
4484
|
-
"User-Agent": `${APP_NAME2}/${getPackageVersion()}`
|
|
4485
|
-
});
|
|
4486
|
-
if (!Array.isArray(payload)) {
|
|
4487
|
-
throw new Error(`GitHub path is not a directory: ${params.remotePath}`);
|
|
4488
|
-
}
|
|
4489
|
-
for (const entry of payload) {
|
|
4490
|
-
if (!entry || typeof entry !== "object") {
|
|
4491
|
-
continue;
|
|
4492
|
-
}
|
|
4493
|
-
const item = entry;
|
|
4494
|
-
const itemName = typeof item.name === "string" ? item.name.trim() : "";
|
|
4495
|
-
const itemPath = typeof item.path === "string" ? item.path.trim() : "";
|
|
4496
|
-
if (!itemName || !itemPath) {
|
|
4497
|
-
continue;
|
|
4498
|
-
}
|
|
4499
|
-
if (item.type === "dir") {
|
|
4500
|
-
const childLocalDir = join5(params.localDir, itemName);
|
|
4501
|
-
mkdirSync4(childLocalDir, { recursive: true });
|
|
4502
|
-
await this.downloadGithubDirectoryToPath({
|
|
4503
|
-
parsed: params.parsed,
|
|
4504
|
-
remotePath: itemPath,
|
|
4505
|
-
localDir: childLocalDir,
|
|
4506
|
-
relativePrefix: params.relativePrefix ? `${params.relativePrefix}/${itemName}` : itemName,
|
|
4507
|
-
downloadedFiles: params.downloadedFiles
|
|
4508
|
-
});
|
|
4509
|
-
continue;
|
|
4510
|
-
}
|
|
4511
|
-
if (item.type !== "file") {
|
|
4512
|
-
throw new Error(`Unsupported GitHub skill entry type: ${item.type ?? "unknown"}`);
|
|
4513
|
-
}
|
|
4514
|
-
if (!item.download_url) {
|
|
4515
|
-
throw new Error(`GitHub skill file missing download_url: ${itemPath}`);
|
|
4516
|
-
}
|
|
4517
|
-
const content = await this.fetchBinaryWithHeaders(item.download_url, {
|
|
4518
|
-
"User-Agent": `${APP_NAME2}/${getPackageVersion()}`
|
|
4519
|
-
});
|
|
4520
|
-
const localFile = join5(params.localDir, itemName);
|
|
4521
|
-
writeFileSync4(localFile, content);
|
|
4522
|
-
params.downloadedFiles.push(params.relativePrefix ? `${params.relativePrefix}/${itemName}` : itemName);
|
|
4523
|
-
}
|
|
4524
|
-
}
|
|
4525
|
-
async fetchJsonWithHeaders(url, headers) {
|
|
4526
|
-
const response = await fetch(url, { headers });
|
|
4527
|
-
if (!response.ok) {
|
|
4528
|
-
throw new Error(`HTTP ${response.status} when fetching ${url}`);
|
|
4529
|
-
}
|
|
4530
|
-
return await response.json();
|
|
4531
|
-
}
|
|
4532
|
-
async fetchBinaryWithHeaders(url, headers) {
|
|
4533
|
-
const response = await fetch(url, { headers });
|
|
4534
|
-
if (!response.ok) {
|
|
4535
|
-
throw new Error(`HTTP ${response.status} when downloading ${url}`);
|
|
4536
|
-
}
|
|
4537
|
-
const bytes = await response.arrayBuffer();
|
|
4538
|
-
return Buffer.from(bytes);
|
|
4539
|
-
}
|
|
4540
|
-
parseMarketplaceGitSkillSource(source) {
|
|
4541
|
-
const trimmed = source.trim();
|
|
4542
|
-
if (!trimmed) {
|
|
4543
|
-
throw new Error("Git skill source is required");
|
|
4544
|
-
}
|
|
4545
|
-
if (trimmed.includes("://")) {
|
|
4546
|
-
const parsedUrl = new URL(trimmed);
|
|
4547
|
-
if (parsedUrl.hostname !== "github.com") {
|
|
4548
|
-
throw new Error(`Unsupported git skill source host: ${parsedUrl.hostname}`);
|
|
4549
|
-
}
|
|
4550
|
-
const parts2 = parsedUrl.pathname.split("/").filter(Boolean);
|
|
4551
|
-
if (parts2.length < 5 || parts2[2] !== "tree" && parts2[2] !== "blob") {
|
|
4552
|
-
throw new Error(`Unsupported GitHub skill source URL: ${source}`);
|
|
4553
|
-
}
|
|
4554
|
-
const [owner2, repoRaw, , ref2, ...pathParts2] = parts2;
|
|
4555
|
-
const repo2 = repoRaw.replace(/\.git$/i, "");
|
|
4556
|
-
if (!owner2 || !repo2 || !ref2 || pathParts2.length === 0) {
|
|
4557
|
-
throw new Error(`Unsupported GitHub skill source URL: ${source}`);
|
|
4558
|
-
}
|
|
4559
|
-
return {
|
|
4560
|
-
owner: owner2,
|
|
4561
|
-
repo: repo2,
|
|
4562
|
-
repoUrl: `https://github.com/${owner2}/${repo2}.git`,
|
|
4563
|
-
skillPath: pathParts2.join("/"),
|
|
4564
|
-
ref: ref2
|
|
4565
|
-
};
|
|
4566
|
-
}
|
|
4567
|
-
const parts = trimmed.split("/").filter(Boolean);
|
|
4568
|
-
if (parts.length < 3) {
|
|
4569
|
-
throw new Error(`Unsupported git skill source: ${source}`);
|
|
4570
|
-
}
|
|
4571
|
-
const owner = parts[0] ?? "";
|
|
4572
|
-
const repoWithOptionalRef = parts[1] ?? "";
|
|
4573
|
-
const pathParts = parts.slice(2);
|
|
4574
|
-
const atIndex = repoWithOptionalRef.indexOf("@");
|
|
4575
|
-
const repo = (atIndex >= 0 ? repoWithOptionalRef.slice(0, atIndex) : repoWithOptionalRef).replace(/\.git$/i, "");
|
|
4576
|
-
const ref = atIndex >= 0 ? repoWithOptionalRef.slice(atIndex + 1) : void 0;
|
|
4577
|
-
if (!owner || !repo || pathParts.length === 0) {
|
|
4578
|
-
throw new Error(`Unsupported git skill source: ${source}`);
|
|
4579
|
-
}
|
|
4580
|
-
return {
|
|
4581
|
-
owner,
|
|
4582
|
-
repo,
|
|
4583
|
-
repoUrl: `https://github.com/${owner}/${repo}.git`,
|
|
4584
|
-
skillPath: pathParts.join("/"),
|
|
4585
|
-
ref: ref && ref.trim().length > 0 ? ref.trim() : void 0
|
|
4586
|
-
};
|
|
4587
|
-
}
|
|
4588
4705
|
async enableMarketplacePlugin(id) {
|
|
4589
4706
|
const output = await this.runCliSubcommand(["plugins", "enable", id]);
|
|
4590
|
-
const summary =
|
|
4591
|
-
return { message: summary
|
|
4707
|
+
const summary = pickUserFacingCommandSummary(output, `Enabled plugin: ${id}`);
|
|
4708
|
+
return { message: summary };
|
|
4592
4709
|
}
|
|
4593
4710
|
async disableMarketplacePlugin(id) {
|
|
4594
4711
|
const output = await this.runCliSubcommand(["plugins", "disable", id]);
|
|
4595
|
-
const summary =
|
|
4596
|
-
return { message: summary
|
|
4712
|
+
const summary = pickUserFacingCommandSummary(output, `Disabled plugin: ${id}`);
|
|
4713
|
+
return { message: summary };
|
|
4597
4714
|
}
|
|
4598
4715
|
async uninstallMarketplacePlugin(id) {
|
|
4599
4716
|
const output = await this.runCliSubcommand(["plugins", "uninstall", id, "--force"]);
|
|
4600
|
-
const summary =
|
|
4601
|
-
return { message: summary
|
|
4717
|
+
const summary = pickUserFacingCommandSummary(output, `Uninstalled plugin: ${id}`);
|
|
4718
|
+
return { message: summary };
|
|
4602
4719
|
}
|
|
4603
4720
|
async uninstallMarketplaceSkill(slug) {
|
|
4604
4721
|
const workspace = getWorkspacePath5(loadConfig6().agents.defaults.workspace);
|
|
@@ -4606,10 +4723,9 @@ var ServiceCommands = class {
|
|
|
4606
4723
|
if (!existsSync8(targetDir)) {
|
|
4607
4724
|
throw new Error(`Skill not installed in workspace: ${slug}`);
|
|
4608
4725
|
}
|
|
4609
|
-
|
|
4726
|
+
rmSync4(targetDir, { recursive: true, force: true });
|
|
4610
4727
|
return {
|
|
4611
|
-
message: `Uninstalled skill: ${slug}
|
|
4612
|
-
output: `Removed ${targetDir}`
|
|
4728
|
+
message: `Uninstalled skill: ${slug}`
|
|
4613
4729
|
};
|
|
4614
4730
|
}
|
|
4615
4731
|
installBuiltinMarketplaceSkill(slug, force) {
|
|
@@ -4618,8 +4734,7 @@ var ServiceCommands = class {
|
|
|
4618
4734
|
const destinationSkillFile = join5(destination, "SKILL.md");
|
|
4619
4735
|
if (existsSync8(destinationSkillFile) && !force) {
|
|
4620
4736
|
return {
|
|
4621
|
-
message: `${slug} is already installed
|
|
4622
|
-
output: destination
|
|
4737
|
+
message: `${slug} is already installed`
|
|
4623
4738
|
};
|
|
4624
4739
|
}
|
|
4625
4740
|
const loader = createSkillsLoader(workspace);
|
|
@@ -4627,77 +4742,31 @@ var ServiceCommands = class {
|
|
|
4627
4742
|
if (!builtin) {
|
|
4628
4743
|
if (existsSync8(destinationSkillFile)) {
|
|
4629
4744
|
return {
|
|
4630
|
-
message: `${slug} is already installed
|
|
4631
|
-
output: destination
|
|
4745
|
+
message: `${slug} is already installed`
|
|
4632
4746
|
};
|
|
4633
4747
|
}
|
|
4634
4748
|
return null;
|
|
4635
4749
|
}
|
|
4636
|
-
|
|
4637
|
-
|
|
4750
|
+
mkdirSync5(join5(workspace, "skills"), { recursive: true });
|
|
4751
|
+
cpSync2(dirname2(builtin.path), destination, { recursive: true, force: true });
|
|
4638
4752
|
return {
|
|
4639
|
-
message: `Installed skill: ${slug}
|
|
4640
|
-
output: `Copied builtin skill to ${destination}`
|
|
4753
|
+
message: `Installed skill: ${slug}`
|
|
4641
4754
|
};
|
|
4642
4755
|
}
|
|
4643
|
-
resolveGitSkillName(skill, source) {
|
|
4644
|
-
const fromRequest = typeof skill === "string" ? skill.trim() : "";
|
|
4645
|
-
if (fromRequest) {
|
|
4646
|
-
return this.validateSkillName(fromRequest);
|
|
4647
|
-
}
|
|
4648
|
-
const normalizedSource = source.replace(/[?#].*$/, "").replace(/\/+$/, "");
|
|
4649
|
-
const parts = normalizedSource.split("/").filter(Boolean);
|
|
4650
|
-
const inferred = parts.length > 0 ? parts[parts.length - 1] : "";
|
|
4651
|
-
if (!inferred) {
|
|
4652
|
-
throw new Error("Git skill install requires a specific skill name");
|
|
4653
|
-
}
|
|
4654
|
-
return this.validateSkillName(inferred);
|
|
4655
|
-
}
|
|
4656
|
-
validateSkillName(skillName) {
|
|
4657
|
-
if (!/^[A-Za-z0-9._-]+$/.test(skillName)) {
|
|
4658
|
-
throw new Error(`Invalid skill name: ${skillName}`);
|
|
4659
|
-
}
|
|
4660
|
-
return skillName;
|
|
4661
|
-
}
|
|
4662
|
-
resolveSkillInstallPath(workspace, installPath, skillName) {
|
|
4663
|
-
const requested = typeof installPath === "string" && installPath.trim().length > 0 ? installPath.trim() : join5("skills", skillName);
|
|
4664
|
-
if (isAbsolute2(requested)) {
|
|
4665
|
-
throw new Error("installPath must be relative to workspace");
|
|
4666
|
-
}
|
|
4667
|
-
const destination = resolve7(workspace, requested);
|
|
4668
|
-
const rel = relative(workspace, destination);
|
|
4669
|
-
if (rel.startsWith("..") || isAbsolute2(rel)) {
|
|
4670
|
-
throw new Error("installPath escapes workspace");
|
|
4671
|
-
}
|
|
4672
|
-
return destination;
|
|
4673
|
-
}
|
|
4674
4756
|
mergeCommandOutput(stdout, stderr) {
|
|
4675
4757
|
return `${stdout}
|
|
4676
4758
|
${stderr}`.trim();
|
|
4677
4759
|
}
|
|
4678
4760
|
runCliSubcommand(args, timeoutMs = 18e4) {
|
|
4679
|
-
const cliEntry =
|
|
4761
|
+
const cliEntry = resolveCliSubcommandEntry({
|
|
4762
|
+
argvEntry: process.argv[1],
|
|
4763
|
+
importMetaUrl: import.meta.url
|
|
4764
|
+
});
|
|
4680
4765
|
return this.runCommand(process.execPath, [...process.execArgv, cliEntry, ...args], {
|
|
4681
4766
|
cwd: process.cwd(),
|
|
4682
4767
|
timeoutMs
|
|
4683
4768
|
}).then((result) => this.mergeCommandOutput(result.stdout, result.stderr));
|
|
4684
4769
|
}
|
|
4685
|
-
async runCommandWithFallback(commandCandidates, args, options = {}) {
|
|
4686
|
-
let lastError = null;
|
|
4687
|
-
for (const command of commandCandidates) {
|
|
4688
|
-
try {
|
|
4689
|
-
return await this.runCommand(command, args, options);
|
|
4690
|
-
} catch (error) {
|
|
4691
|
-
const message = String(error);
|
|
4692
|
-
lastError = error instanceof Error ? error : new Error(message);
|
|
4693
|
-
if (message.startsWith("failed to start command:")) {
|
|
4694
|
-
continue;
|
|
4695
|
-
}
|
|
4696
|
-
throw error;
|
|
4697
|
-
}
|
|
4698
|
-
}
|
|
4699
|
-
throw lastError ?? new Error("failed to start command");
|
|
4700
|
-
}
|
|
4701
4770
|
runCommand(command, args, options = {}) {
|
|
4702
4771
|
const timeoutMs = options.timeoutMs ?? 18e4;
|
|
4703
4772
|
return new Promise((resolvePromise, rejectPromise) => {
|
|
@@ -4735,19 +4804,15 @@ ${stderr}`.trim();
|
|
|
4735
4804
|
});
|
|
4736
4805
|
});
|
|
4737
4806
|
}
|
|
4738
|
-
pickLastOutputLine(output) {
|
|
4739
|
-
const lines = output.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
4740
|
-
return lines.length > 0 ? lines[lines.length - 1] : null;
|
|
4741
|
-
}
|
|
4742
4807
|
};
|
|
4743
4808
|
|
|
4744
4809
|
// src/cli/workspace.ts
|
|
4745
|
-
import { cpSync as
|
|
4810
|
+
import { cpSync as cpSync3, existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync8, readdirSync as readdirSync3, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
4746
4811
|
import { createRequire } from "module";
|
|
4747
|
-
import { dirname as
|
|
4812
|
+
import { dirname as dirname3, join as join6, resolve as resolve8 } from "path";
|
|
4748
4813
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4749
4814
|
import { APP_NAME as APP_NAME3, getDataDir as getDataDir7 } from "@nextclaw/core";
|
|
4750
|
-
import { spawnSync as
|
|
4815
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
4751
4816
|
var WorkspaceManager = class {
|
|
4752
4817
|
constructor(logo) {
|
|
4753
4818
|
this.logo = logo;
|
|
@@ -4783,20 +4848,20 @@ var WorkspaceManager = class {
|
|
|
4783
4848
|
console.warn(`Warning: Template file missing: ${templatePath}`);
|
|
4784
4849
|
continue;
|
|
4785
4850
|
}
|
|
4786
|
-
const raw =
|
|
4851
|
+
const raw = readFileSync8(templatePath, "utf-8");
|
|
4787
4852
|
const content = raw.replace(/\$\{APP_NAME\}/g, APP_NAME3);
|
|
4788
|
-
|
|
4853
|
+
mkdirSync6(dirname3(filePath), { recursive: true });
|
|
4789
4854
|
writeFileSync5(filePath, content);
|
|
4790
4855
|
created.push(entry.target);
|
|
4791
4856
|
}
|
|
4792
4857
|
const memoryDir = join6(workspace, "memory");
|
|
4793
4858
|
if (!existsSync9(memoryDir)) {
|
|
4794
|
-
|
|
4859
|
+
mkdirSync6(memoryDir, { recursive: true });
|
|
4795
4860
|
created.push(join6("memory", ""));
|
|
4796
4861
|
}
|
|
4797
4862
|
const skillsDir = join6(workspace, "skills");
|
|
4798
4863
|
if (!existsSync9(skillsDir)) {
|
|
4799
|
-
|
|
4864
|
+
mkdirSync6(skillsDir, { recursive: true });
|
|
4800
4865
|
created.push(join6("skills", ""));
|
|
4801
4866
|
}
|
|
4802
4867
|
const seeded = this.seedBuiltinSkills(skillsDir, { force });
|
|
@@ -4812,7 +4877,7 @@ var WorkspaceManager = class {
|
|
|
4812
4877
|
}
|
|
4813
4878
|
const force = Boolean(options.force);
|
|
4814
4879
|
let seeded = 0;
|
|
4815
|
-
for (const entry of
|
|
4880
|
+
for (const entry of readdirSync3(sourceDir, { withFileTypes: true })) {
|
|
4816
4881
|
if (!entry.isDirectory()) {
|
|
4817
4882
|
continue;
|
|
4818
4883
|
}
|
|
@@ -4824,7 +4889,7 @@ var WorkspaceManager = class {
|
|
|
4824
4889
|
if (!force && existsSync9(dest)) {
|
|
4825
4890
|
continue;
|
|
4826
4891
|
}
|
|
4827
|
-
|
|
4892
|
+
cpSync3(src, dest, { recursive: true, force: true });
|
|
4828
4893
|
seeded += 1;
|
|
4829
4894
|
}
|
|
4830
4895
|
return seeded;
|
|
@@ -4833,7 +4898,7 @@ var WorkspaceManager = class {
|
|
|
4833
4898
|
try {
|
|
4834
4899
|
const require2 = createRequire(import.meta.url);
|
|
4835
4900
|
const entry = require2.resolve("@nextclaw/core");
|
|
4836
|
-
const pkgRoot = resolve8(
|
|
4901
|
+
const pkgRoot = resolve8(dirname3(entry), "..");
|
|
4837
4902
|
const distSkills = join6(pkgRoot, "dist", "skills");
|
|
4838
4903
|
if (existsSync9(distSkills)) {
|
|
4839
4904
|
return distSkills;
|
|
@@ -4886,15 +4951,15 @@ var WorkspaceManager = class {
|
|
|
4886
4951
|
process.exit(1);
|
|
4887
4952
|
}
|
|
4888
4953
|
console.log(`${this.logo} Setting up bridge...`);
|
|
4889
|
-
|
|
4954
|
+
mkdirSync6(resolve8(userBridge, ".."), { recursive: true });
|
|
4890
4955
|
if (existsSync9(userBridge)) {
|
|
4891
|
-
|
|
4956
|
+
rmSync5(userBridge, { recursive: true, force: true });
|
|
4892
4957
|
}
|
|
4893
|
-
|
|
4958
|
+
cpSync3(source, userBridge, {
|
|
4894
4959
|
recursive: true,
|
|
4895
4960
|
filter: (src) => !src.includes("node_modules") && !src.includes("dist")
|
|
4896
4961
|
});
|
|
4897
|
-
const install =
|
|
4962
|
+
const install = spawnSync3("npm", ["install"], { cwd: userBridge, stdio: "pipe" });
|
|
4898
4963
|
if (install.status !== 0) {
|
|
4899
4964
|
console.error(`Bridge install failed: ${install.status ?? 1}`);
|
|
4900
4965
|
if (install.stderr) {
|
|
@@ -4902,7 +4967,7 @@ var WorkspaceManager = class {
|
|
|
4902
4967
|
}
|
|
4903
4968
|
process.exit(1);
|
|
4904
4969
|
}
|
|
4905
|
-
const build =
|
|
4970
|
+
const build = spawnSync3("npm", ["run", "build"], { cwd: userBridge, stdio: "pipe" });
|
|
4906
4971
|
if (build.status !== 0) {
|
|
4907
4972
|
console.error(`Bridge build failed: ${build.status ?? 1}`);
|
|
4908
4973
|
if (build.stderr) {
|
|
@@ -4919,6 +4984,12 @@ var WorkspaceManager = class {
|
|
|
4919
4984
|
var LOGO = "\u{1F916}";
|
|
4920
4985
|
var EXIT_COMMANDS = /* @__PURE__ */ new Set(["exit", "quit", "/exit", "/quit", ":q"]);
|
|
4921
4986
|
var FORCED_PUBLIC_UI_HOST = "0.0.0.0";
|
|
4987
|
+
function resolveSkillsInstallWorkdir(params) {
|
|
4988
|
+
if (params.explicitWorkdir) {
|
|
4989
|
+
return expandHome2(params.explicitWorkdir);
|
|
4990
|
+
}
|
|
4991
|
+
return getWorkspacePath6(params.configuredWorkspace);
|
|
4992
|
+
}
|
|
4922
4993
|
var CliRuntime = class {
|
|
4923
4994
|
logo;
|
|
4924
4995
|
restartCoordinator;
|
|
@@ -5155,7 +5226,7 @@ var CliRuntime = class {
|
|
|
5155
5226
|
const workspaceSetting = config2.agents.defaults.workspace;
|
|
5156
5227
|
const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ? join7(getDataDir8(), DEFAULT_WORKSPACE_DIR) : expandHome2(workspaceSetting);
|
|
5157
5228
|
const workspaceExisted = existsSync10(workspacePath);
|
|
5158
|
-
|
|
5229
|
+
mkdirSync7(workspacePath, { recursive: true });
|
|
5159
5230
|
const templateResult = this.workspaceManager.createWorkspaceTemplates(
|
|
5160
5231
|
workspacePath,
|
|
5161
5232
|
{ force }
|
|
@@ -5184,6 +5255,76 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
5184
5255
|
);
|
|
5185
5256
|
}
|
|
5186
5257
|
}
|
|
5258
|
+
async login(opts = {}) {
|
|
5259
|
+
await this.init({ source: "login", auto: true });
|
|
5260
|
+
const configPath = getConfigPath4();
|
|
5261
|
+
const config2 = loadConfig7(configPath);
|
|
5262
|
+
const providers = config2.providers;
|
|
5263
|
+
const nextclawProvider = providers.nextclaw ?? {
|
|
5264
|
+
displayName: "",
|
|
5265
|
+
apiKey: "",
|
|
5266
|
+
apiBase: null,
|
|
5267
|
+
extraHeaders: null,
|
|
5268
|
+
wireApi: "auto",
|
|
5269
|
+
models: []
|
|
5270
|
+
};
|
|
5271
|
+
const configuredApiBase = typeof nextclawProvider.apiBase === "string" && nextclawProvider.apiBase.trim().length > 0 ? nextclawProvider.apiBase.trim() : "https://ai-gateway-api.nextclaw.io/v1";
|
|
5272
|
+
const requestedApiBase = typeof opts.apiBase === "string" && opts.apiBase.trim().length > 0 ? opts.apiBase.trim() : configuredApiBase;
|
|
5273
|
+
const platformBase = requestedApiBase.replace(/\/v1\/?$/i, "");
|
|
5274
|
+
const v1Base = `${platformBase}/v1`;
|
|
5275
|
+
let email = typeof opts.email === "string" ? opts.email.trim() : "";
|
|
5276
|
+
let password = typeof opts.password === "string" ? opts.password : "";
|
|
5277
|
+
if (!email || !password) {
|
|
5278
|
+
const rl = createInterface2({
|
|
5279
|
+
input: process.stdin,
|
|
5280
|
+
output: process.stdout
|
|
5281
|
+
});
|
|
5282
|
+
try {
|
|
5283
|
+
if (!email) {
|
|
5284
|
+
email = (await prompt(rl, "Email: ")).trim();
|
|
5285
|
+
}
|
|
5286
|
+
if (!password) {
|
|
5287
|
+
password = await prompt(rl, "Password: ");
|
|
5288
|
+
}
|
|
5289
|
+
} finally {
|
|
5290
|
+
rl.close();
|
|
5291
|
+
}
|
|
5292
|
+
}
|
|
5293
|
+
if (!email || !password) {
|
|
5294
|
+
throw new Error("Email and password are required.");
|
|
5295
|
+
}
|
|
5296
|
+
const endpoint = opts.register ? `${platformBase}/platform/auth/register` : `${platformBase}/platform/auth/login`;
|
|
5297
|
+
const response = await fetch(endpoint, {
|
|
5298
|
+
method: "POST",
|
|
5299
|
+
headers: {
|
|
5300
|
+
"Content-Type": "application/json"
|
|
5301
|
+
},
|
|
5302
|
+
body: JSON.stringify({ email, password })
|
|
5303
|
+
});
|
|
5304
|
+
const raw = await response.text();
|
|
5305
|
+
let parsed = null;
|
|
5306
|
+
try {
|
|
5307
|
+
parsed = JSON.parse(raw);
|
|
5308
|
+
} catch {
|
|
5309
|
+
parsed = null;
|
|
5310
|
+
}
|
|
5311
|
+
if (!response.ok) {
|
|
5312
|
+
const maybeMessage = typeof parsed === "object" && parsed && "error" in parsed && typeof parsed.error?.message === "string" ? parsed.error.message : raw || `Request failed (${response.status})`;
|
|
5313
|
+
throw new Error(maybeMessage);
|
|
5314
|
+
}
|
|
5315
|
+
const token = typeof parsed === "object" && parsed && "data" in parsed && typeof parsed.data?.token === "string" ? parsed.data.token : "";
|
|
5316
|
+
const role = typeof parsed === "object" && parsed && "data" in parsed && typeof parsed.data?.user?.role === "string" ? parsed.data.user.role : "user";
|
|
5317
|
+
if (!token) {
|
|
5318
|
+
throw new Error("Login succeeded but token is missing.");
|
|
5319
|
+
}
|
|
5320
|
+
nextclawProvider.apiBase = v1Base;
|
|
5321
|
+
nextclawProvider.apiKey = token;
|
|
5322
|
+
providers.nextclaw = nextclawProvider;
|
|
5323
|
+
saveConfig6(config2, configPath);
|
|
5324
|
+
console.log(`\u2713 Logged in to NextClaw platform (${platformBase})`);
|
|
5325
|
+
console.log(`\u2713 Account: ${email} (${role})`);
|
|
5326
|
+
console.log(`\u2713 Token saved into providers.nextclaw.apiKey`);
|
|
5327
|
+
}
|
|
5187
5328
|
async gateway(opts) {
|
|
5188
5329
|
const uiOverrides = {
|
|
5189
5330
|
host: FORCED_PUBLIC_UI_HOST
|
|
@@ -5344,8 +5485,8 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
5344
5485
|
);
|
|
5345
5486
|
const historyFile = join7(getDataDir8(), "history", "cli_history");
|
|
5346
5487
|
const historyDir = resolve9(historyFile, "..");
|
|
5347
|
-
|
|
5348
|
-
const history = existsSync10(historyFile) ?
|
|
5488
|
+
mkdirSync7(historyDir, { recursive: true });
|
|
5489
|
+
const history = existsSync10(historyFile) ? readFileSync9(historyFile, "utf-8").split("\n").filter(Boolean) : [];
|
|
5349
5490
|
const rl = createInterface2({
|
|
5350
5491
|
input: process.stdin,
|
|
5351
5492
|
output: process.stdout
|
|
@@ -5500,26 +5641,63 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
5500
5641
|
await this.diagnosticsCommands.doctor(opts);
|
|
5501
5642
|
}
|
|
5502
5643
|
async skillsInstall(options) {
|
|
5503
|
-
const
|
|
5504
|
-
const
|
|
5644
|
+
const config2 = loadConfig7();
|
|
5645
|
+
const workdir = resolveSkillsInstallWorkdir({
|
|
5646
|
+
explicitWorkdir: options.workdir,
|
|
5647
|
+
configuredWorkspace: config2.agents.defaults.workspace
|
|
5648
|
+
});
|
|
5649
|
+
const result = await installMarketplaceSkill({
|
|
5505
5650
|
slug: options.slug,
|
|
5506
|
-
version: options.version,
|
|
5507
|
-
registry: options.registry,
|
|
5508
5651
|
workdir,
|
|
5509
5652
|
dir: options.dir,
|
|
5510
|
-
force: options.force
|
|
5653
|
+
force: options.force,
|
|
5654
|
+
apiBaseUrl: options.apiBaseUrl
|
|
5511
5655
|
});
|
|
5512
|
-
const versionLabel = result.version ?? "latest";
|
|
5513
5656
|
if (result.alreadyInstalled) {
|
|
5514
5657
|
console.log(`\u2713 ${result.slug} is already installed`);
|
|
5515
5658
|
} else {
|
|
5516
|
-
console.log(`\u2713 Installed ${result.slug}
|
|
5517
|
-
}
|
|
5518
|
-
if (result.registry) {
|
|
5519
|
-
console.log(` Registry: ${result.registry}`);
|
|
5659
|
+
console.log(`\u2713 Installed ${result.slug} (${result.source})`);
|
|
5520
5660
|
}
|
|
5521
5661
|
console.log(` Path: ${result.destinationDir}`);
|
|
5522
5662
|
}
|
|
5663
|
+
async skillsPublish(options) {
|
|
5664
|
+
const result = await publishMarketplaceSkill({
|
|
5665
|
+
skillDir: expandHome2(options.dir),
|
|
5666
|
+
slug: options.slug,
|
|
5667
|
+
name: options.name,
|
|
5668
|
+
summary: options.summary,
|
|
5669
|
+
description: options.description,
|
|
5670
|
+
author: options.author,
|
|
5671
|
+
tags: options.tag,
|
|
5672
|
+
sourceRepo: options.sourceRepo,
|
|
5673
|
+
homepage: options.homepage,
|
|
5674
|
+
publishedAt: options.publishedAt,
|
|
5675
|
+
updatedAt: options.updatedAt,
|
|
5676
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
5677
|
+
token: options.token
|
|
5678
|
+
});
|
|
5679
|
+
console.log(result.created ? `\u2713 Published new skill: ${result.slug}` : `\u2713 Updated skill: ${result.slug}`);
|
|
5680
|
+
console.log(` Files: ${result.fileCount}`);
|
|
5681
|
+
}
|
|
5682
|
+
async skillsUpdate(options) {
|
|
5683
|
+
const result = await publishMarketplaceSkill({
|
|
5684
|
+
skillDir: expandHome2(options.dir),
|
|
5685
|
+
slug: options.slug,
|
|
5686
|
+
name: options.name,
|
|
5687
|
+
summary: options.summary,
|
|
5688
|
+
description: options.description,
|
|
5689
|
+
author: options.author,
|
|
5690
|
+
tags: options.tag,
|
|
5691
|
+
sourceRepo: options.sourceRepo,
|
|
5692
|
+
homepage: options.homepage,
|
|
5693
|
+
updatedAt: options.updatedAt,
|
|
5694
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
5695
|
+
token: options.token,
|
|
5696
|
+
requireExisting: true
|
|
5697
|
+
});
|
|
5698
|
+
console.log(`\u2713 Updated skill: ${result.slug}`);
|
|
5699
|
+
console.log(` Files: ${result.fileCount}`);
|
|
5700
|
+
}
|
|
5523
5701
|
};
|
|
5524
5702
|
|
|
5525
5703
|
// src/cli/index.ts
|
|
@@ -5528,6 +5706,7 @@ var runtime = new CliRuntime({ logo: LOGO });
|
|
|
5528
5706
|
program.name(APP_NAME5).description(`${LOGO} ${APP_NAME5} - ${APP_TAGLINE}`).version(getPackageVersion(), "-v, --version", "show version");
|
|
5529
5707
|
program.command("onboard").description(`Initialize ${APP_NAME5} configuration and workspace`).action(async () => runtime.onboard());
|
|
5530
5708
|
program.command("init").description(`Initialize ${APP_NAME5} configuration and workspace`).option("-f, --force", "Overwrite existing template files").action(async (opts) => runtime.init({ force: Boolean(opts.force) }));
|
|
5709
|
+
program.command("login").description("Login to NextClaw platform and save token into providers.nextclaw.apiKey").option("--api-base <url>", "Platform API base (supports /v1 suffix)").option("--email <email>", "Login email").option("--password <password>", "Login password").option("--register", "Register first, then login", false).action(async (opts) => runtime.login(opts));
|
|
5531
5710
|
program.command("gateway").description(`Start the ${APP_NAME5} gateway`).option("-p, --port <port>", "Gateway port", "18790").option("-v, --verbose", "Verbose output", false).option("--ui", "Enable UI server", false).option("--ui-port <port>", "UI port").option("--ui-open", "Open browser when UI starts", false).action(async (opts) => runtime.gateway(opts));
|
|
5532
5711
|
program.command("ui").description(`Start the ${APP_NAME5} UI with gateway`).option("--port <port>", "UI port").option("--no-open", "Disable opening browser").action(async (opts) => runtime.ui(opts));
|
|
5533
5712
|
program.command("start").description(`Start the ${APP_NAME5} gateway + UI in the background`).option("--ui-port <port>", "UI port").option("--start-timeout <ms>", "Maximum wait time for startup readiness in milliseconds").option("--open", "Open browser after start", false).action(async (opts) => runtime.start(opts));
|
|
@@ -5536,13 +5715,11 @@ program.command("serve").description(`Run the ${APP_NAME5} gateway + UI in the f
|
|
|
5536
5715
|
program.command("stop").description(`Stop the ${APP_NAME5} background service`).action(async () => runtime.stop());
|
|
5537
5716
|
program.command("agent").description("Interact with the agent directly").option("-m, --message <message>", "Message to send to the agent").option("-s, --session <session>", "Session ID", "cli:default").option("--model <model>", "Session model override for this run").option("--no-markdown", "Disable Markdown rendering").action(async (opts) => runtime.agent(opts));
|
|
5538
5717
|
program.command("update").description(`Update ${APP_NAME5}`).option("--timeout <ms>", "Update command timeout in milliseconds").action(async (opts) => runtime.update(opts));
|
|
5539
|
-
var registerClawHubInstall = (cmd) => {
|
|
5540
|
-
cmd.command("install <slug>").description("Install a skill from ClawHub").option("--version <version>", "Skill version (default: latest)").option("--registry <url>", "ClawHub registry base URL").option("--workdir <dir>", "Workspace directory to install into").option("--dir <dir>", "Skills directory name (default: skills)").option("-f, --force", "Overwrite existing skill files", false).action(async (slug, opts) => runtime.skillsInstall({ slug, ...opts }));
|
|
5541
|
-
};
|
|
5542
5718
|
var skills = program.command("skills").description("Manage skills");
|
|
5543
|
-
|
|
5544
|
-
var
|
|
5545
|
-
|
|
5719
|
+
skills.command("install <slug>").description("Install a skill from NextClaw marketplace").option("--api-base <url>", "Marketplace API base URL").option("--workdir <dir>", "Workspace directory to install into").option("--dir <dir>", "Skills directory name (default: skills)").option("-f, --force", "Overwrite existing skill files", false).action(async (slug, opts) => runtime.skillsInstall({ slug, ...opts, apiBaseUrl: opts.apiBase }));
|
|
5720
|
+
var withRepeatableTag = (value, previous = []) => [...previous, value];
|
|
5721
|
+
skills.command("publish <dir>").description("Upload or create a skill in marketplace").option("--slug <slug>", "Skill slug (default: directory name)").option("--name <name>", "Skill display name").option("--summary <summary>", "Skill summary").option("--description <description>", "Skill description").option("--author <author>", "Skill author").option("--tag <tag>", "Skill tag (repeatable)", withRepeatableTag, []).option("--source-repo <url>", "Source repository URL").option("--homepage <url>", "Homepage URL").option("--published-at <datetime>", "Published time (ISO datetime)").option("--updated-at <datetime>", "Updated time (ISO datetime)").option("--api-base <url>", "Marketplace API base URL").option("--token <token>", "Marketplace admin token").action(async (dir, opts) => runtime.skillsPublish({ dir, ...opts, apiBaseUrl: opts.apiBase }));
|
|
5722
|
+
skills.command("update <dir>").description("Update an existing skill in marketplace").option("--slug <slug>", "Skill slug (default: directory name)").option("--name <name>", "Skill display name").option("--summary <summary>", "Skill summary").option("--description <description>", "Skill description").option("--author <author>", "Skill author").option("--tag <tag>", "Skill tag (repeatable)", withRepeatableTag, []).option("--source-repo <url>", "Source repository URL").option("--homepage <url>", "Homepage URL").option("--updated-at <datetime>", "Updated time (ISO datetime)").option("--api-base <url>", "Marketplace API base URL").option("--token <token>", "Marketplace admin token").action(async (dir, opts) => runtime.skillsUpdate({ dir, ...opts, apiBaseUrl: opts.apiBase }));
|
|
5546
5723
|
var plugins = program.command("plugins").description("Manage OpenClaw-compatible plugins");
|
|
5547
5724
|
plugins.command("list").description("List discovered plugins").option("--json", "Print JSON").option("--enabled", "Only show enabled plugins", false).option("--verbose", "Show detailed entries", false).action((opts) => runtime.pluginsList(opts));
|
|
5548
5725
|
plugins.command("info <id>").description("Show plugin details").option("--json", "Print JSON").action((id, opts) => runtime.pluginsInfo(id, opts));
|