@catch-claw/openclaw-agentar 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.ts +500 -6
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -2
package/index.ts
CHANGED
|
@@ -54,6 +54,22 @@ type InstallResult = {
|
|
|
54
54
|
introduction?: string | null;
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
+
type AgentWorkspaceInfo = {
|
|
58
|
+
id: string;
|
|
59
|
+
name?: string;
|
|
60
|
+
workspace: string;
|
|
61
|
+
isDefault: boolean;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
type ExportResult = {
|
|
65
|
+
success: boolean;
|
|
66
|
+
agentName: string;
|
|
67
|
+
workspace: string;
|
|
68
|
+
outputPath: string;
|
|
69
|
+
files: string[];
|
|
70
|
+
includeMemory: boolean;
|
|
71
|
+
};
|
|
72
|
+
|
|
57
73
|
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
58
74
|
|
|
59
75
|
const DEFAULT_API_BASE_URL = "http://127.0.0.1:8080";
|
|
@@ -69,6 +85,7 @@ const WORKSPACE_FILES = [
|
|
|
69
85
|
"MEMORY.md",
|
|
70
86
|
];
|
|
71
87
|
const SKIP_FILES = ["AGENTS.md", "BOOTSTRAP.md"];
|
|
88
|
+
const EXPORT_SKIP_DIRS = [".git", ".openclaw", "__MACOSX", "memory"];
|
|
72
89
|
const INTRO_MESSAGE =
|
|
73
90
|
"请做一下自我介绍,简要说明你是谁、你擅长什么、你能帮用户做哪些事情。";
|
|
74
91
|
|
|
@@ -119,11 +136,26 @@ function box(text: string): void {
|
|
|
119
136
|
// ─── Core Utilities ──────────────────────────────────────────────────────────
|
|
120
137
|
|
|
121
138
|
function findOpenclawBin(): string {
|
|
139
|
+
const isWin = process.platform === "win32";
|
|
122
140
|
try {
|
|
123
|
-
return execSync("
|
|
141
|
+
return execSync(isWin ? "where openclaw" : "which openclaw", {
|
|
142
|
+
encoding: "utf-8",
|
|
143
|
+
})
|
|
144
|
+
.split(/\r?\n/)[0]
|
|
145
|
+
.trim();
|
|
124
146
|
} catch {
|
|
125
|
-
const
|
|
126
|
-
|
|
147
|
+
const fallbacks = isWin
|
|
148
|
+
? [
|
|
149
|
+
path.join(
|
|
150
|
+
process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"),
|
|
151
|
+
"pnpm",
|
|
152
|
+
"openclaw.cmd",
|
|
153
|
+
),
|
|
154
|
+
]
|
|
155
|
+
: [path.join(os.homedir(), ".local/share/pnpm/openclaw")];
|
|
156
|
+
for (const p of fallbacks) {
|
|
157
|
+
if (fs.existsSync(p)) return p;
|
|
158
|
+
}
|
|
127
159
|
throw new Error("openclaw CLI not found on PATH");
|
|
128
160
|
}
|
|
129
161
|
}
|
|
@@ -199,6 +231,187 @@ function resolveContentDir(extractDir: string): string {
|
|
|
199
231
|
return extractDir;
|
|
200
232
|
}
|
|
201
233
|
|
|
234
|
+
function discoverAgents(): AgentWorkspaceInfo[] {
|
|
235
|
+
const openclawBin = findOpenclawBin();
|
|
236
|
+
try {
|
|
237
|
+
const stdout = execSync(`"${openclawBin}" agents list --json`, {
|
|
238
|
+
encoding: "utf-8",
|
|
239
|
+
stdio: "pipe",
|
|
240
|
+
});
|
|
241
|
+
const parsed = JSON.parse(stdout);
|
|
242
|
+
const agents: AgentWorkspaceInfo[] = [];
|
|
243
|
+
|
|
244
|
+
// Handle both array format and wrapped format
|
|
245
|
+
const list = Array.isArray(parsed) ? parsed : parsed.agents ?? [];
|
|
246
|
+
for (const entry of list) {
|
|
247
|
+
agents.push({
|
|
248
|
+
id: String(entry.id ?? ""),
|
|
249
|
+
name: entry.name ?? entry.identityName,
|
|
250
|
+
workspace: String(entry.workspace ?? ""),
|
|
251
|
+
isDefault: Boolean(entry.isDefault),
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Ensure at least the default main agent is present
|
|
256
|
+
if (agents.length === 0) {
|
|
257
|
+
agents.push({
|
|
258
|
+
id: "main",
|
|
259
|
+
workspace: MAIN_WORKSPACE,
|
|
260
|
+
isDefault: true,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return agents;
|
|
265
|
+
} catch {
|
|
266
|
+
// Fallback: return default main agent if CLI fails
|
|
267
|
+
return [
|
|
268
|
+
{
|
|
269
|
+
id: "main",
|
|
270
|
+
workspace: MAIN_WORKSPACE,
|
|
271
|
+
isDefault: true,
|
|
272
|
+
},
|
|
273
|
+
];
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function collectExportFiles(
|
|
278
|
+
workspaceDir: string,
|
|
279
|
+
includeMemory: boolean,
|
|
280
|
+
): { tempDir: string; files: string[] } {
|
|
281
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "agentar-export-"));
|
|
282
|
+
const files: string[] = [];
|
|
283
|
+
|
|
284
|
+
for (const entry of fs.readdirSync(workspaceDir, { withFileTypes: true })) {
|
|
285
|
+
// 1. Skip metadata directories
|
|
286
|
+
if (entry.isDirectory() && EXPORT_SKIP_DIRS.includes(entry.name)) continue;
|
|
287
|
+
|
|
288
|
+
// 2. Skip skills/ (handled separately)
|
|
289
|
+
if (entry.name === "skills" && entry.isDirectory()) continue;
|
|
290
|
+
|
|
291
|
+
// 3. Skip files matching SKIP_FILES (AGENTS.md, BOOTSTRAP.md)
|
|
292
|
+
if (SKIP_FILES.includes(entry.name)) continue;
|
|
293
|
+
|
|
294
|
+
// 4. Conditionally skip MEMORY.md
|
|
295
|
+
if (entry.name === "MEMORY.md" && !includeMemory) continue;
|
|
296
|
+
|
|
297
|
+
// 5. Skip hidden files/directories
|
|
298
|
+
if (entry.name.startsWith(".")) continue;
|
|
299
|
+
|
|
300
|
+
// 6. Copy to temp directory
|
|
301
|
+
const src = path.join(workspaceDir, entry.name);
|
|
302
|
+
const dest = path.join(tempDir, entry.name);
|
|
303
|
+
if (entry.isDirectory()) {
|
|
304
|
+
fs.cpSync(src, dest, { recursive: true });
|
|
305
|
+
} else {
|
|
306
|
+
fs.copyFileSync(src, dest);
|
|
307
|
+
}
|
|
308
|
+
files.push(entry.name);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// 7. Handle skills/ separately
|
|
312
|
+
const skillsDir = path.join(workspaceDir, "skills");
|
|
313
|
+
if (fs.existsSync(skillsDir)) {
|
|
314
|
+
fs.cpSync(skillsDir, path.join(tempDir, "skills"), { recursive: true });
|
|
315
|
+
files.push("skills");
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return { tempDir, files };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// ─── Export Pipeline ────────────────────────────────────────────────────────
|
|
322
|
+
|
|
323
|
+
async function exportAgentar(opts: {
|
|
324
|
+
agentName: string;
|
|
325
|
+
outputPath?: string;
|
|
326
|
+
includeMemory?: boolean;
|
|
327
|
+
quiet?: boolean;
|
|
328
|
+
}): Promise<ExportResult> {
|
|
329
|
+
const { agentName, includeMemory = false, quiet } = opts;
|
|
330
|
+
const log = quiet ? () => {} : step;
|
|
331
|
+
const logDetail = quiet ? () => {} : detail;
|
|
332
|
+
const total = 3;
|
|
333
|
+
|
|
334
|
+
// [1/3] Validate workspace
|
|
335
|
+
log(1, total, "Validating workspace ...");
|
|
336
|
+
const agents = discoverAgents();
|
|
337
|
+
const agent = agents.find((a) => a.id === agentName);
|
|
338
|
+
if (!agent) {
|
|
339
|
+
const available = agents.map((a) => a.id).join(", ");
|
|
340
|
+
throw new Error(
|
|
341
|
+
`Agent "${agentName}" not found. Available agents: ${available}`,
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const workspaceDir = agent.workspace;
|
|
346
|
+
if (!fs.existsSync(workspaceDir)) {
|
|
347
|
+
throw new Error(
|
|
348
|
+
`Workspace directory does not exist: ${workspaceDir}`,
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
if (!fs.existsSync(path.join(workspaceDir, "SOUL.md"))) {
|
|
352
|
+
throw new Error(
|
|
353
|
+
`Invalid workspace: missing SOUL.md in ${workspaceDir}`,
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
logDetail("workspace", workspaceDir);
|
|
357
|
+
logDetail("SOUL.md", "found");
|
|
358
|
+
|
|
359
|
+
// [2/3] Collect files
|
|
360
|
+
log(2, total, "Collecting files ...");
|
|
361
|
+
const { tempDir, files } = collectExportFiles(workspaceDir, includeMemory);
|
|
362
|
+
|
|
363
|
+
// Preview collected content
|
|
364
|
+
const skillsDir = path.join(tempDir, "skills");
|
|
365
|
+
const skillCount = fs.existsSync(skillsDir)
|
|
366
|
+
? fs
|
|
367
|
+
.readdirSync(skillsDir, { withFileTypes: true })
|
|
368
|
+
.filter((e) => e.isDirectory()).length
|
|
369
|
+
: 0;
|
|
370
|
+
const filesDesc = files.filter((f) => f !== "skills").join(", ");
|
|
371
|
+
logDetail(
|
|
372
|
+
"files",
|
|
373
|
+
skillCount > 0
|
|
374
|
+
? `${filesDesc}, skills/ (${skillCount} skills)`
|
|
375
|
+
: filesDesc,
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
// [3/3] Create ZIP package
|
|
379
|
+
log(3, total, "Creating ZIP package ...");
|
|
380
|
+
const resolvedOutput = path.resolve(
|
|
381
|
+
opts.outputPath || `./${agentName}.zip`,
|
|
382
|
+
);
|
|
383
|
+
logDetail("output", resolvedOutput);
|
|
384
|
+
|
|
385
|
+
try {
|
|
386
|
+
if (process.platform === "win32") {
|
|
387
|
+
execSync(
|
|
388
|
+
`powershell -NoProfile -Command "Compress-Archive -Force -Path '${tempDir}\\*' -DestinationPath '${resolvedOutput}'"`,
|
|
389
|
+
{ encoding: "utf-8" },
|
|
390
|
+
);
|
|
391
|
+
} else {
|
|
392
|
+
execSync(`cd "${tempDir}" && zip -r -q "${resolvedOutput}" .`, {
|
|
393
|
+
encoding: "utf-8",
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
} catch (err) {
|
|
397
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
398
|
+
throw new Error(
|
|
399
|
+
`Failed to create ZIP: ${err instanceof Error ? err.message : String(err)}`,
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
404
|
+
|
|
405
|
+
return {
|
|
406
|
+
success: true,
|
|
407
|
+
agentName,
|
|
408
|
+
workspace: workspaceDir,
|
|
409
|
+
outputPath: resolvedOutput,
|
|
410
|
+
files,
|
|
411
|
+
includeMemory,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
202
415
|
// ─── Install Pipeline ────────────────────────────────────────────────────────
|
|
203
416
|
|
|
204
417
|
async function installAgentar(opts: {
|
|
@@ -257,9 +470,16 @@ async function installAgentar(opts: {
|
|
|
257
470
|
const extractDir = path.join(tmpDir, "extracted");
|
|
258
471
|
fs.mkdirSync(extractDir, { recursive: true });
|
|
259
472
|
try {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
473
|
+
if (process.platform === "win32") {
|
|
474
|
+
execSync(
|
|
475
|
+
`powershell -NoProfile -Command "Expand-Archive -Force -Path '${zipPath}' -DestinationPath '${extractDir}'"`,
|
|
476
|
+
{ encoding: "utf-8" },
|
|
477
|
+
);
|
|
478
|
+
} else {
|
|
479
|
+
execSync(`unzip -o -q "${zipPath}" -d "${extractDir}"`, {
|
|
480
|
+
encoding: "utf-8",
|
|
481
|
+
});
|
|
482
|
+
}
|
|
263
483
|
} catch {
|
|
264
484
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
265
485
|
throw new Error(`Failed to extract agentar zip for "${slug}"`);
|
|
@@ -476,6 +696,8 @@ export default function register(api: OpenClawPluginApi) {
|
|
|
476
696
|
"1. When user asks to install/switch agent persona, use the `agentar_install` tool.",
|
|
477
697
|
"2. Always confirm with the user before overwriting the main agent workspace.",
|
|
478
698
|
"3. When installing, ask if the user wants to overwrite the main agent or create a new one.",
|
|
699
|
+
"4. When user asks to export/share/package an agent, use the `agentar_export` tool.",
|
|
700
|
+
"5. Call agentar_export without parameters first to show available agents, then let user choose.",
|
|
479
701
|
].join("\n"),
|
|
480
702
|
}),
|
|
481
703
|
{ priority: 80 },
|
|
@@ -547,6 +769,78 @@ export default function register(api: OpenClawPluginApi) {
|
|
|
547
769
|
{ name: "agentar_install" },
|
|
548
770
|
);
|
|
549
771
|
|
|
772
|
+
// ── Tool: agentar_export ──────────────────────────────────────────────
|
|
773
|
+
|
|
774
|
+
api.registerTool(
|
|
775
|
+
() => ({
|
|
776
|
+
name: "agentar_export",
|
|
777
|
+
description:
|
|
778
|
+
"Export an agent workspace as a distributable ZIP package. Call without agentName to list available agents first. Returns agent list when agentName is omitted, or export result when agentName is provided.",
|
|
779
|
+
parameters: {
|
|
780
|
+
type: "object" as const,
|
|
781
|
+
properties: {
|
|
782
|
+
agentName: {
|
|
783
|
+
type: "string",
|
|
784
|
+
description:
|
|
785
|
+
"Agent ID to export (e.g. 'main'). Omit to list available agents.",
|
|
786
|
+
},
|
|
787
|
+
outputPath: {
|
|
788
|
+
type: "string",
|
|
789
|
+
description:
|
|
790
|
+
"Output ZIP file path. Defaults to ./<agentName>.zip",
|
|
791
|
+
},
|
|
792
|
+
includeMemory: {
|
|
793
|
+
type: "boolean",
|
|
794
|
+
description:
|
|
795
|
+
"Whether to include MEMORY.md in the export (default: false)",
|
|
796
|
+
},
|
|
797
|
+
},
|
|
798
|
+
required: [],
|
|
799
|
+
},
|
|
800
|
+
execute: async (params: Record<string, unknown>) => {
|
|
801
|
+
const agentName = params.agentName
|
|
802
|
+
? String(params.agentName)
|
|
803
|
+
: undefined;
|
|
804
|
+
const outputPath = params.outputPath
|
|
805
|
+
? String(params.outputPath)
|
|
806
|
+
: undefined;
|
|
807
|
+
const includeMemory = Boolean(params.includeMemory ?? false);
|
|
808
|
+
|
|
809
|
+
// No agentName → return available agents list
|
|
810
|
+
if (!agentName) {
|
|
811
|
+
try {
|
|
812
|
+
const agents = discoverAgents();
|
|
813
|
+
return {
|
|
814
|
+
agents: agents.map((a) => ({
|
|
815
|
+
...a,
|
|
816
|
+
exists: fs.existsSync(a.workspace),
|
|
817
|
+
})),
|
|
818
|
+
};
|
|
819
|
+
} catch (err) {
|
|
820
|
+
return {
|
|
821
|
+
error: `Failed to discover agents: ${err instanceof Error ? err.message : String(err)}`,
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// agentName provided → execute export
|
|
827
|
+
try {
|
|
828
|
+
return await exportAgentar({
|
|
829
|
+
agentName,
|
|
830
|
+
outputPath,
|
|
831
|
+
includeMemory,
|
|
832
|
+
quiet: true,
|
|
833
|
+
});
|
|
834
|
+
} catch (err) {
|
|
835
|
+
return {
|
|
836
|
+
error: `Export failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
},
|
|
840
|
+
}),
|
|
841
|
+
{ name: "agentar_export" },
|
|
842
|
+
);
|
|
843
|
+
|
|
550
844
|
// ── CLI: openclaw agentar ──────────────────────────────────────────────
|
|
551
845
|
|
|
552
846
|
api.registerCli(
|
|
@@ -744,6 +1038,206 @@ export default function register(api: OpenClawPluginApi) {
|
|
|
744
1038
|
}
|
|
745
1039
|
},
|
|
746
1040
|
);
|
|
1041
|
+
|
|
1042
|
+
// ── export ────────────────────────────────────────────────────────
|
|
1043
|
+
|
|
1044
|
+
agentarCmd
|
|
1045
|
+
.command("export")
|
|
1046
|
+
.description("Export an agent workspace as a ZIP package")
|
|
1047
|
+
.option("--agent <id>", "Agent ID to export")
|
|
1048
|
+
.option("-o, --output <path>", "Output ZIP file path")
|
|
1049
|
+
.option("--include-memory", "Include MEMORY.md in export")
|
|
1050
|
+
.option("--json", "Output result as JSON")
|
|
1051
|
+
.action(
|
|
1052
|
+
async (opts: {
|
|
1053
|
+
agent?: string;
|
|
1054
|
+
output?: string;
|
|
1055
|
+
includeMemory?: boolean;
|
|
1056
|
+
json?: boolean;
|
|
1057
|
+
}) => {
|
|
1058
|
+
try {
|
|
1059
|
+
// Discover available agents
|
|
1060
|
+
const agents = discoverAgents();
|
|
1061
|
+
|
|
1062
|
+
let selectedAgent: AgentWorkspaceInfo;
|
|
1063
|
+
|
|
1064
|
+
if (opts.agent) {
|
|
1065
|
+
// Non-interactive: use provided agent ID
|
|
1066
|
+
const found = agents.find((a) => a.id === opts.agent);
|
|
1067
|
+
if (!found) {
|
|
1068
|
+
const available = agents
|
|
1069
|
+
.map((a) => a.id)
|
|
1070
|
+
.join(", ");
|
|
1071
|
+
console.error(
|
|
1072
|
+
`Agent "${opts.agent}" not found. Available: ${available}`,
|
|
1073
|
+
);
|
|
1074
|
+
process.exit(1);
|
|
1075
|
+
}
|
|
1076
|
+
selectedAgent = found;
|
|
1077
|
+
} else {
|
|
1078
|
+
// Interactive: let user choose
|
|
1079
|
+
console.log("\n⟩ Discovering agents ...\n");
|
|
1080
|
+
console.log(" Available agents:");
|
|
1081
|
+
for (let i = 0; i < agents.length; i++) {
|
|
1082
|
+
const a = agents[i];
|
|
1083
|
+
const exists = fs.existsSync(a.workspace);
|
|
1084
|
+
const defaultTag = a.isDefault ? " [default]" : "";
|
|
1085
|
+
const notFound = exists ? "" : " (not found)";
|
|
1086
|
+
console.log(
|
|
1087
|
+
` [${i + 1}] ${a.id} (${a.workspace})${defaultTag}${notFound}`,
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
const readline = await import("node:readline");
|
|
1092
|
+
const rl = readline.createInterface({
|
|
1093
|
+
input: process.stdin,
|
|
1094
|
+
output: process.stdout,
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
const answer = await new Promise<string>((resolve) => {
|
|
1098
|
+
rl.question(
|
|
1099
|
+
`\n Select agent (default: 1): `,
|
|
1100
|
+
(ans: string) => {
|
|
1101
|
+
rl.close();
|
|
1102
|
+
resolve(ans.trim());
|
|
1103
|
+
},
|
|
1104
|
+
);
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
const idx = answer ? parseInt(answer, 10) - 1 : 0;
|
|
1108
|
+
if (idx < 0 || idx >= agents.length) {
|
|
1109
|
+
console.error("Invalid selection.");
|
|
1110
|
+
process.exit(1);
|
|
1111
|
+
}
|
|
1112
|
+
selectedAgent = agents[idx];
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
// Include memory?
|
|
1116
|
+
let includeMemory = opts.includeMemory ?? false;
|
|
1117
|
+
if (!opts.agent && !opts.includeMemory) {
|
|
1118
|
+
const readline = await import("node:readline");
|
|
1119
|
+
const rl = readline.createInterface({
|
|
1120
|
+
input: process.stdin,
|
|
1121
|
+
output: process.stdout,
|
|
1122
|
+
});
|
|
1123
|
+
const memAnswer = await new Promise<string>((resolve) => {
|
|
1124
|
+
rl.question(
|
|
1125
|
+
" Include MEMORY.md? (y/N): ",
|
|
1126
|
+
(ans: string) => {
|
|
1127
|
+
rl.close();
|
|
1128
|
+
resolve(ans.trim().toLowerCase());
|
|
1129
|
+
},
|
|
1130
|
+
);
|
|
1131
|
+
});
|
|
1132
|
+
includeMemory = memAnswer === "y" || memAnswer === "yes";
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// Output path
|
|
1136
|
+
let outputPath = opts.output;
|
|
1137
|
+
if (!opts.agent && !opts.output) {
|
|
1138
|
+
const defaultPath = `./${selectedAgent.id}.zip`;
|
|
1139
|
+
const readline = await import("node:readline");
|
|
1140
|
+
const rl = readline.createInterface({
|
|
1141
|
+
input: process.stdin,
|
|
1142
|
+
output: process.stdout,
|
|
1143
|
+
});
|
|
1144
|
+
const pathAnswer = await new Promise<string>((resolve) => {
|
|
1145
|
+
rl.question(
|
|
1146
|
+
` Output path (default: ${defaultPath}): `,
|
|
1147
|
+
(ans: string) => {
|
|
1148
|
+
rl.close();
|
|
1149
|
+
resolve(ans.trim());
|
|
1150
|
+
},
|
|
1151
|
+
);
|
|
1152
|
+
});
|
|
1153
|
+
outputPath = pathAnswer || defaultPath;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// Check if output file already exists (interactive mode)
|
|
1157
|
+
const resolvedOutput = path.resolve(
|
|
1158
|
+
outputPath || `./${selectedAgent.id}.zip`,
|
|
1159
|
+
);
|
|
1160
|
+
if (!opts.agent && fs.existsSync(resolvedOutput)) {
|
|
1161
|
+
const readline = await import("node:readline");
|
|
1162
|
+
const rl = readline.createInterface({
|
|
1163
|
+
input: process.stdin,
|
|
1164
|
+
output: process.stdout,
|
|
1165
|
+
});
|
|
1166
|
+
const overwrite = await new Promise<string>((resolve) => {
|
|
1167
|
+
rl.question(
|
|
1168
|
+
` File "${resolvedOutput}" already exists. Overwrite? (y/N): `,
|
|
1169
|
+
(ans: string) => {
|
|
1170
|
+
rl.close();
|
|
1171
|
+
resolve(ans.trim().toLowerCase());
|
|
1172
|
+
},
|
|
1173
|
+
);
|
|
1174
|
+
});
|
|
1175
|
+
if (overwrite !== "y" && overwrite !== "yes") {
|
|
1176
|
+
console.log(" Export cancelled.");
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
console.log(
|
|
1182
|
+
`\n⟩ Exporting agent "${selectedAgent.id}" ...`,
|
|
1183
|
+
);
|
|
1184
|
+
|
|
1185
|
+
const result = await exportAgentar({
|
|
1186
|
+
agentName: selectedAgent.id,
|
|
1187
|
+
outputPath,
|
|
1188
|
+
includeMemory,
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1191
|
+
if (opts.json) {
|
|
1192
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
success("Export complete");
|
|
1197
|
+
console.log(" Summary:");
|
|
1198
|
+
console.log(` agent ${result.agentName}`);
|
|
1199
|
+
console.log(` workspace ${result.workspace}`);
|
|
1200
|
+
console.log(` output ${result.outputPath}`);
|
|
1201
|
+
if (result.files.length > 0) {
|
|
1202
|
+
const skillsEntry = result.files.includes("skills");
|
|
1203
|
+
const nonSkillFiles = result.files.filter(
|
|
1204
|
+
(f) => f !== "skills",
|
|
1205
|
+
);
|
|
1206
|
+
let filesLine = nonSkillFiles.join(", ");
|
|
1207
|
+
if (skillsEntry) {
|
|
1208
|
+
const exportSkillsDir = path.join(
|
|
1209
|
+
result.workspace,
|
|
1210
|
+
"skills",
|
|
1211
|
+
);
|
|
1212
|
+
const skillCount = fs.existsSync(exportSkillsDir)
|
|
1213
|
+
? fs
|
|
1214
|
+
.readdirSync(exportSkillsDir, {
|
|
1215
|
+
withFileTypes: true,
|
|
1216
|
+
})
|
|
1217
|
+
.filter((e) => e.isDirectory()).length
|
|
1218
|
+
: 0;
|
|
1219
|
+
filesLine +=
|
|
1220
|
+
skillCount > 0
|
|
1221
|
+
? `, skills/ (${skillCount} skills)`
|
|
1222
|
+
: ", skills/";
|
|
1223
|
+
}
|
|
1224
|
+
console.log(` files ${filesLine}`);
|
|
1225
|
+
}
|
|
1226
|
+
if (result.includeMemory) {
|
|
1227
|
+
console.log(" memory included");
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
console.log(
|
|
1231
|
+
`\n Use "openclaw agentar install <slug>" with the exported ZIP to install elsewhere.\n`,
|
|
1232
|
+
);
|
|
1233
|
+
} catch (err) {
|
|
1234
|
+
console.error(
|
|
1235
|
+
`\nExport failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1236
|
+
);
|
|
1237
|
+
process.exit(1);
|
|
1238
|
+
}
|
|
1239
|
+
},
|
|
1240
|
+
);
|
|
747
1241
|
},
|
|
748
1242
|
{ commands: ["agentar"] },
|
|
749
1243
|
);
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@catch-claw/openclaw-agentar",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Search, download, and
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Search, download, install, and export agent templates (agentars) for OpenClaw",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"openclaw": {
|