@lumerahq/cli 0.29.1 → 0.30.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/dist/{chunk-QPVRRJFJ.js → chunk-FLFEF2BN.js} +1 -1
- package/dist/{chunk-KC3VBP6R.js → chunk-LVIIWE44.js} +1 -1
- package/dist/{chunk-FHHWIV4C.js → chunk-U3ZLIXGD.js} +32 -0
- package/dist/{chunk-7ZTCXKII.js → chunk-YMWDIAUG.js} +1 -1
- package/dist/{deps-IYUORT55.js → deps-NHVL4DMD.js} +2 -2
- package/dist/{dev-OJEV76DA.js → dev-IWTA5G5M.js} +3 -3
- package/dist/{functions-KP2NO4RC.js → functions-55J6E2RF.js} +1 -1
- package/dist/index.js +14 -14
- package/dist/{init-ZR2SLJ3K.js → init-I4FECTEL.js} +1 -1
- package/dist/{project-OPA5UUMQ.js → project-6POVRBZL.js} +2 -2
- package/dist/{register-HRLBT4FI.js → register-OBQZPOGC.js} +1 -1
- package/dist/{resources-P4VA2JPE.js → resources-5YWEX6DG.js} +510 -62
- package/dist/{run-WHVUVIYB.js → run-SLLDX3RF.js} +1 -1
- package/package.json +1 -1
- package/templates/default/platform/skills/.gitkeep +0 -0
- package/templates/default/pyproject.toml +1 -1
|
@@ -4,7 +4,7 @@ import { existsSync } from "fs";
|
|
|
4
4
|
import { join, resolve } from "path";
|
|
5
5
|
import pc from "picocolors";
|
|
6
6
|
var SUPPORTED_SUBCOMMANDS = /* @__PURE__ */ new Set(["list", "inspect", "invoke", "test"]);
|
|
7
|
-
var FUNCTIONS_SDK_REQUIREMENT = "lumera[functions]>=0.
|
|
7
|
+
var FUNCTIONS_SDK_REQUIREMENT = "lumera[functions]>=0.33.0,<0.34.0";
|
|
8
8
|
var FUNCTIONS_RUNNER_PROTOCOL_VERSION = 1;
|
|
9
9
|
function errorMessage(error, fallback) {
|
|
10
10
|
return error instanceof Error && error.message ? error.message : fallback;
|
|
@@ -299,6 +299,38 @@ var ApiClient = class {
|
|
|
299
299
|
method: "DELETE"
|
|
300
300
|
});
|
|
301
301
|
}
|
|
302
|
+
// Project skills
|
|
303
|
+
async listAgentSkills(opts) {
|
|
304
|
+
const params = new URLSearchParams({ limit: "100" });
|
|
305
|
+
if (opts?.project_id) params.set("project_id", opts.project_id);
|
|
306
|
+
if (opts?.include_global) params.set("include_global", "true");
|
|
307
|
+
const all = [];
|
|
308
|
+
let offset = 0;
|
|
309
|
+
for (; ; ) {
|
|
310
|
+
params.set("offset", String(offset));
|
|
311
|
+
const result = await this.request(`/api/lm_agent_skills?${params}`);
|
|
312
|
+
const skills = result.skills || [];
|
|
313
|
+
all.push(...skills);
|
|
314
|
+
if (!result.has_more || skills.length === 0) break;
|
|
315
|
+
offset += skills.length;
|
|
316
|
+
}
|
|
317
|
+
return all;
|
|
318
|
+
}
|
|
319
|
+
async createAgentSkill(def) {
|
|
320
|
+
return this.request("/api/lm_agent_skills", {
|
|
321
|
+
method: "POST",
|
|
322
|
+
body: JSON.stringify(def)
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
async updateAgentSkill(id, def) {
|
|
326
|
+
return this.request(`/api/lm_agent_skills/${id}`, {
|
|
327
|
+
method: "PATCH",
|
|
328
|
+
body: JSON.stringify(def)
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
async deleteAgentSkill(id) {
|
|
332
|
+
await this.request(`/api/lm_agent_skills/${id}`, { method: "DELETE" });
|
|
333
|
+
}
|
|
302
334
|
// Resolve a list of refs (slugs or IDs) to skill records. Chunks
|
|
303
335
|
// requests so callers don't have to think about the server-side
|
|
304
336
|
// per-request cap.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
deps,
|
|
3
3
|
syncDeps
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-YMWDIAUG.js";
|
|
5
5
|
import "./chunk-2CR762KB.js";
|
|
6
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-U3ZLIXGD.js";
|
|
7
7
|
import "./chunk-JLVVHTBY.js";
|
|
8
8
|
import "./chunk-FJFIWC7G.js";
|
|
9
9
|
import "./chunk-PNKVD2UK.js";
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
dev
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-LVIIWE44.js";
|
|
4
4
|
import {
|
|
5
5
|
syncDeps
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-YMWDIAUG.js";
|
|
7
7
|
import {
|
|
8
8
|
loadEnv
|
|
9
9
|
} from "./chunk-2CR762KB.js";
|
|
10
10
|
import {
|
|
11
11
|
createApiClient
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-U3ZLIXGD.js";
|
|
13
13
|
import {
|
|
14
14
|
findProjectRoot,
|
|
15
15
|
getApiUrl,
|
package/dist/index.js
CHANGED
|
@@ -240,49 +240,49 @@ async function main() {
|
|
|
240
240
|
switch (command) {
|
|
241
241
|
// Resource commands
|
|
242
242
|
case "plan":
|
|
243
|
-
await import("./resources-
|
|
243
|
+
await import("./resources-5YWEX6DG.js").then((m) => m.plan(args.slice(1)));
|
|
244
244
|
break;
|
|
245
245
|
case "apply":
|
|
246
|
-
await import("./resources-
|
|
246
|
+
await import("./resources-5YWEX6DG.js").then((m) => m.apply(args.slice(1)));
|
|
247
247
|
break;
|
|
248
248
|
case "pull":
|
|
249
|
-
await import("./resources-
|
|
249
|
+
await import("./resources-5YWEX6DG.js").then((m) => m.pull(args.slice(1)));
|
|
250
250
|
break;
|
|
251
251
|
case "destroy":
|
|
252
|
-
await import("./resources-
|
|
252
|
+
await import("./resources-5YWEX6DG.js").then((m) => m.destroy(args.slice(1)));
|
|
253
253
|
break;
|
|
254
254
|
case "list":
|
|
255
|
-
await import("./resources-
|
|
255
|
+
await import("./resources-5YWEX6DG.js").then((m) => m.list(args.slice(1)));
|
|
256
256
|
break;
|
|
257
257
|
case "show":
|
|
258
|
-
await import("./resources-
|
|
258
|
+
await import("./resources-5YWEX6DG.js").then((m) => m.show(args.slice(1)));
|
|
259
259
|
break;
|
|
260
260
|
case "diff":
|
|
261
|
-
await import("./resources-
|
|
261
|
+
await import("./resources-5YWEX6DG.js").then((m) => m.diff(args.slice(1)));
|
|
262
262
|
break;
|
|
263
263
|
// Development
|
|
264
264
|
case "dev":
|
|
265
|
-
await import("./dev-
|
|
265
|
+
await import("./dev-IWTA5G5M.js").then((m) => m.dev(args.slice(1)));
|
|
266
266
|
break;
|
|
267
267
|
case "run":
|
|
268
|
-
await import("./run-
|
|
268
|
+
await import("./run-SLLDX3RF.js").then((m) => m.run(args.slice(1)));
|
|
269
269
|
break;
|
|
270
270
|
case "functions":
|
|
271
|
-
await import("./functions-
|
|
271
|
+
await import("./functions-55J6E2RF.js").then(
|
|
272
272
|
(m) => m.functions(subcommand, args.slice(2))
|
|
273
273
|
);
|
|
274
274
|
break;
|
|
275
275
|
case "project":
|
|
276
|
-
await import("./project-
|
|
276
|
+
await import("./project-6POVRBZL.js").then(
|
|
277
277
|
(m) => m.project(subcommand, args.slice(2))
|
|
278
278
|
);
|
|
279
279
|
break;
|
|
280
280
|
// Project
|
|
281
281
|
case "init":
|
|
282
|
-
await import("./init-
|
|
282
|
+
await import("./init-I4FECTEL.js").then((m) => m.init(args.slice(1)));
|
|
283
283
|
break;
|
|
284
284
|
case "register":
|
|
285
|
-
await import("./register-
|
|
285
|
+
await import("./register-OBQZPOGC.js").then((m) => m.register(args.slice(1)));
|
|
286
286
|
break;
|
|
287
287
|
case "templates":
|
|
288
288
|
await import("./templates-LNUOTNLN.js").then((m) => m.templates(subcommand, args.slice(2)));
|
|
@@ -299,7 +299,7 @@ async function main() {
|
|
|
299
299
|
break;
|
|
300
300
|
// Dependencies
|
|
301
301
|
case "deps":
|
|
302
|
-
await import("./deps-
|
|
302
|
+
await import("./deps-NHVL4DMD.js").then((m) => m.deps(args.slice(1)));
|
|
303
303
|
break;
|
|
304
304
|
// Feature flags (read the in-sandbox snapshot)
|
|
305
305
|
case "flags":
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
findFunctionsProjectRoot
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-FLFEF2BN.js";
|
|
4
4
|
import "./chunk-PNKVD2UK.js";
|
|
5
5
|
|
|
6
6
|
// src/commands/project.ts
|
|
7
7
|
import { spawn } from "child_process";
|
|
8
8
|
import pc from "picocolors";
|
|
9
|
-
var RELEASE_SDK_REQUIREMENT = "lumera[release]>=0.
|
|
9
|
+
var RELEASE_SDK_REQUIREMENT = "lumera[release]>=0.33.0,<0.34.0";
|
|
10
10
|
var RELEASE_PROTOCOL_VERSION = 1;
|
|
11
11
|
var RELEASE_COMMANDS = /* @__PURE__ */ new Set([
|
|
12
12
|
"build",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
deploy
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-LVIIWE44.js";
|
|
4
4
|
import {
|
|
5
5
|
syncDeps
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-YMWDIAUG.js";
|
|
7
7
|
import {
|
|
8
8
|
loadEnv
|
|
9
9
|
} from "./chunk-2CR762KB.js";
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
createApiClient,
|
|
13
13
|
isApiErrorStatus,
|
|
14
14
|
validateCollectionNameLength
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-U3ZLIXGD.js";
|
|
16
16
|
import {
|
|
17
17
|
findProjectRoot,
|
|
18
18
|
getApiUrl,
|
|
@@ -1287,6 +1287,8 @@ ${pc2.dim("Resources:")}
|
|
|
1287
1287
|
hooks Plan only hooks
|
|
1288
1288
|
agents Plan only agents
|
|
1289
1289
|
agents/<name> Plan single agent
|
|
1290
|
+
skills Plan only project skills
|
|
1291
|
+
skills/<slug> Plan single project skill
|
|
1290
1292
|
mailboxes Plan only mailboxes
|
|
1291
1293
|
mailboxes/<slug> Plan single mailbox
|
|
1292
1294
|
app Plan app deployment
|
|
@@ -1315,6 +1317,8 @@ ${pc2.dim("Resources:")}
|
|
|
1315
1317
|
hooks Apply only hooks
|
|
1316
1318
|
agents Apply only agents
|
|
1317
1319
|
agents/<name> Apply single agent
|
|
1320
|
+
skills Apply only project skills
|
|
1321
|
+
skills/<slug> Apply single project skill
|
|
1318
1322
|
mailboxes Apply only mailboxes
|
|
1319
1323
|
mailboxes/<slug> Apply single mailbox
|
|
1320
1324
|
app Deploy the frontend app
|
|
@@ -1359,6 +1363,8 @@ ${pc2.dim("Resources:")}
|
|
|
1359
1363
|
hooks Pull only hooks
|
|
1360
1364
|
agents Pull only agents
|
|
1361
1365
|
agents/<name> Pull single agent
|
|
1366
|
+
skills Pull only project skills
|
|
1367
|
+
skills/<slug> Pull single project skill
|
|
1362
1368
|
mailboxes Pull only mailboxes
|
|
1363
1369
|
mailboxes/<slug> Pull single mailbox
|
|
1364
1370
|
|
|
@@ -1386,6 +1392,8 @@ ${pc2.dim("Resources:")}
|
|
|
1386
1392
|
hooks Destroy only hooks
|
|
1387
1393
|
agents Destroy only agents
|
|
1388
1394
|
agents/<name> Destroy single agent
|
|
1395
|
+
skills Destroy only project skills
|
|
1396
|
+
skills/<slug> Destroy single project skill
|
|
1389
1397
|
app Delete app registration
|
|
1390
1398
|
|
|
1391
1399
|
${pc2.dim("Options:")}
|
|
@@ -1412,6 +1420,7 @@ ${pc2.dim("Types:")}
|
|
|
1412
1420
|
automations List only automations
|
|
1413
1421
|
hooks List only hooks
|
|
1414
1422
|
agents List only agents
|
|
1423
|
+
skills List only project skills
|
|
1415
1424
|
mailboxes List only mailboxes
|
|
1416
1425
|
|
|
1417
1426
|
${pc2.dim("Options:")}
|
|
@@ -1436,6 +1445,7 @@ ${pc2.dim("Resources:")}
|
|
|
1436
1445
|
automations/<name> Show automation details
|
|
1437
1446
|
hooks/<name> Show hook details
|
|
1438
1447
|
agents/<name> Show agent details
|
|
1448
|
+
skills/<slug> Show project skill details
|
|
1439
1449
|
mailboxes/<slug> Show mailbox details
|
|
1440
1450
|
app Show app details
|
|
1441
1451
|
|
|
@@ -1452,7 +1462,7 @@ function parseResource(resourcePath) {
|
|
|
1452
1462
|
const parts = resourcePath.split("/");
|
|
1453
1463
|
const type = parts[0];
|
|
1454
1464
|
const name = parts.slice(1).join("/") || null;
|
|
1455
|
-
const validTypes = ["collections", "automations", "hooks", "agents", "mailboxes", "app"];
|
|
1465
|
+
const validTypes = ["collections", "automations", "hooks", "agents", "skills", "mailboxes", "app"];
|
|
1456
1466
|
if (!validTypes.includes(type)) {
|
|
1457
1467
|
console.log(pc2.red(` Unknown resource type "${type}". Valid types: ${validTypes.join(", ")}`));
|
|
1458
1468
|
process.exit(1);
|
|
@@ -2475,6 +2485,246 @@ ${hook.script.split("\n").map((line) => " " + line).join("\n")}
|
|
|
2475
2485
|
console.log(pc2.green(" \u2713"), `${hook.name} \u2192 hooks/${fileName}`);
|
|
2476
2486
|
}
|
|
2477
2487
|
}
|
|
2488
|
+
var SKILL_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
2489
|
+
var SKILL_NAME_MAX_LENGTH = 100;
|
|
2490
|
+
var SKILL_SLUG_MAX_LENGTH = 100;
|
|
2491
|
+
var SKILL_SUMMARY_MAX_LENGTH = 500;
|
|
2492
|
+
function loadLocalSkills(platformDir, filterName) {
|
|
2493
|
+
const skillsDir = join2(platformDir, "skills");
|
|
2494
|
+
if (!existsSync2(skillsDir)) return [];
|
|
2495
|
+
const skills = [];
|
|
2496
|
+
const errors = [];
|
|
2497
|
+
for (const entry of readdirSync2(skillsDir, { withFileTypes: true })) {
|
|
2498
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
2499
|
+
if (filterName && entry.name !== filterName) continue;
|
|
2500
|
+
if (!SKILL_SLUG_RE.test(entry.name)) {
|
|
2501
|
+
errors.push(`${entry.name}: directory name must be a lowercase slug`);
|
|
2502
|
+
continue;
|
|
2503
|
+
}
|
|
2504
|
+
const path = join2(skillsDir, entry.name, "SKILL.md");
|
|
2505
|
+
if (!existsSync2(path)) {
|
|
2506
|
+
errors.push(`${entry.name}: missing SKILL.md`);
|
|
2507
|
+
continue;
|
|
2508
|
+
}
|
|
2509
|
+
const content = readFileSync2(path, "utf-8");
|
|
2510
|
+
const separator = /\r?\n---\r?\n/.exec(content);
|
|
2511
|
+
const parts = separator ? [content.slice(0, separator.index), content.slice(separator.index + separator[0].length)] : [content];
|
|
2512
|
+
if (parts.length !== 2) {
|
|
2513
|
+
errors.push(`${entry.name}: SKILL.md must separate the summary and instructions with ---`);
|
|
2514
|
+
continue;
|
|
2515
|
+
}
|
|
2516
|
+
const headerLines = parts[0].trim().split(/\r?\n/);
|
|
2517
|
+
const titleLine = headerLines[0]?.trim().startsWith("# ") ? headerLines[0] : "";
|
|
2518
|
+
const name = titleLine.trim().slice(2).trim();
|
|
2519
|
+
const summaryLines = [];
|
|
2520
|
+
for (const line of headerLines.slice(1)) {
|
|
2521
|
+
const trimmed = line.trim();
|
|
2522
|
+
if (summaryLines.length === 0 && trimmed === "") continue;
|
|
2523
|
+
if (summaryLines.length > 0 && trimmed === "") break;
|
|
2524
|
+
summaryLines.push(trimmed);
|
|
2525
|
+
}
|
|
2526
|
+
const summary = summaryLines.join(" ");
|
|
2527
|
+
const instructions = parts[1].trim();
|
|
2528
|
+
if (!name) errors.push(`${entry.name}: SKILL.md must start with a # Title`);
|
|
2529
|
+
if (name.length > SKILL_NAME_MAX_LENGTH) errors.push(`${entry.name}: title must be at most ${SKILL_NAME_MAX_LENGTH} characters`);
|
|
2530
|
+
if (!summary) errors.push(`${entry.name}: SKILL.md must include a one-paragraph summary`);
|
|
2531
|
+
if (summary.length > SKILL_SUMMARY_MAX_LENGTH) errors.push(`${entry.name}: summary must be at most ${SKILL_SUMMARY_MAX_LENGTH} characters`);
|
|
2532
|
+
if (!instructions) errors.push(`${entry.name}: SKILL.md must include instructions after ---`);
|
|
2533
|
+
if (name && name.length <= SKILL_NAME_MAX_LENGTH && summary && summary.length <= SKILL_SUMMARY_MAX_LENGTH && instructions) {
|
|
2534
|
+
skills.push({ slug: entry.name, name, summary, instructions });
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
if (errors.length > 0) {
|
|
2538
|
+
console.log(pc2.red(" Skill errors:"));
|
|
2539
|
+
for (const error3 of errors) console.log(pc2.red(` \u2717 ${error3}`));
|
|
2540
|
+
throw new Error(`Found ${errors.length} skill error(s)`);
|
|
2541
|
+
}
|
|
2542
|
+
return skills;
|
|
2543
|
+
}
|
|
2544
|
+
function requireProjectSkillsProjectID(projectId) {
|
|
2545
|
+
if (!projectId) {
|
|
2546
|
+
throw new Error("Project skills require lumera.project_id in package.json. Register the project before applying skills.");
|
|
2547
|
+
}
|
|
2548
|
+
return projectId;
|
|
2549
|
+
}
|
|
2550
|
+
function skillMarkdown(skill) {
|
|
2551
|
+
return `# ${skill.name}
|
|
2552
|
+
|
|
2553
|
+
${skill.summary}
|
|
2554
|
+
|
|
2555
|
+
---
|
|
2556
|
+
|
|
2557
|
+
${skill.instructions}
|
|
2558
|
+
`;
|
|
2559
|
+
}
|
|
2560
|
+
function scopedProjectSkillSlug(appName, localSlug) {
|
|
2561
|
+
return appName ? `${appName}:${localSlug}` : localSlug;
|
|
2562
|
+
}
|
|
2563
|
+
function localProjectSkillSlug(appName, storedSlug) {
|
|
2564
|
+
const prefix = appName ? `${appName}:` : "";
|
|
2565
|
+
return prefix && storedSlug.startsWith(prefix) ? storedSlug.slice(prefix.length) : storedSlug;
|
|
2566
|
+
}
|
|
2567
|
+
function remoteSkillsByLocalSlug(remoteSkills, appName) {
|
|
2568
|
+
const byLocalSlug = /* @__PURE__ */ new Map();
|
|
2569
|
+
for (const remote of remoteSkills) {
|
|
2570
|
+
const localSlug = localProjectSkillSlug(appName, remote.slug);
|
|
2571
|
+
const existing = byLocalSlug.get(localSlug);
|
|
2572
|
+
if (!existing || remote.slug === scopedProjectSkillSlug(appName, localSlug)) {
|
|
2573
|
+
byLocalSlug.set(localSlug, remote);
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
return byLocalSlug;
|
|
2577
|
+
}
|
|
2578
|
+
function projectSkillRefCandidates(refs, appName) {
|
|
2579
|
+
return [...new Set(refs.flatMap((ref) => appName && SKILL_SLUG_RE.test(ref) ? [scopedProjectSkillSlug(appName, ref), ref] : [ref]))];
|
|
2580
|
+
}
|
|
2581
|
+
async function resolveAgentSkillRefs(api, refs, projectId, appName, localSourceSkillSlugs = /* @__PURE__ */ new Set()) {
|
|
2582
|
+
const refToId = /* @__PURE__ */ new Map();
|
|
2583
|
+
const skillIdToSlug = /* @__PURE__ */ new Map();
|
|
2584
|
+
const blockedRefs = /* @__PURE__ */ new Set();
|
|
2585
|
+
const outsideProjectRefs = /* @__PURE__ */ new Set();
|
|
2586
|
+
if (refs.length === 0) return { refToId, skillIdToSlug, blockedRefs, outsideProjectRefs };
|
|
2587
|
+
const skills = await api.lookupAgentSkills(projectSkillRefCandidates(refs, appName));
|
|
2588
|
+
const byID = new Map(skills.map((skill) => [skill.id, skill]));
|
|
2589
|
+
const bySlug = new Map(skills.map((skill) => [skill.slug, skill]));
|
|
2590
|
+
const projectByLocalSlug = /* @__PURE__ */ new Map();
|
|
2591
|
+
for (const skill of skills) {
|
|
2592
|
+
const inRuntimeProject = !!projectId && skill.project_id === projectId;
|
|
2593
|
+
const inScope = !skill.project_id || inRuntimeProject;
|
|
2594
|
+
if (inScope && isAvailableToCustomAgents(skill)) {
|
|
2595
|
+
skillIdToSlug.set(skill.id, inRuntimeProject ? localProjectSkillSlug(appName, skill.slug) : skill.slug);
|
|
2596
|
+
}
|
|
2597
|
+
if (!inRuntimeProject) continue;
|
|
2598
|
+
const localSlug = localProjectSkillSlug(appName, skill.slug);
|
|
2599
|
+
const existing = projectByLocalSlug.get(localSlug);
|
|
2600
|
+
if (!existing || skill.slug === scopedProjectSkillSlug(appName, localSlug)) {
|
|
2601
|
+
projectByLocalSlug.set(localSlug, skill);
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
for (const ref of refs) {
|
|
2605
|
+
let selected = byID.get(ref);
|
|
2606
|
+
if (!selected && SKILL_SLUG_RE.test(ref)) {
|
|
2607
|
+
selected = projectByLocalSlug.get(ref);
|
|
2608
|
+
if (!selected && localSourceSkillSlugs.has(ref)) {
|
|
2609
|
+
continue;
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
selected ||= bySlug.get(ref);
|
|
2613
|
+
if (!selected) continue;
|
|
2614
|
+
if (selected.project_id && selected.project_id !== projectId) {
|
|
2615
|
+
outsideProjectRefs.add(ref);
|
|
2616
|
+
continue;
|
|
2617
|
+
}
|
|
2618
|
+
if (!isAvailableToCustomAgents(selected)) {
|
|
2619
|
+
blockedRefs.add(ref);
|
|
2620
|
+
continue;
|
|
2621
|
+
}
|
|
2622
|
+
refToId.set(ref, selected.id);
|
|
2623
|
+
}
|
|
2624
|
+
return { refToId, skillIdToSlug, blockedRefs, outsideProjectRefs };
|
|
2625
|
+
}
|
|
2626
|
+
function validateProjectSkillStorageSlugs(localSkills, appName) {
|
|
2627
|
+
for (const skill of localSkills) {
|
|
2628
|
+
const storedSlug = scopedProjectSkillSlug(appName, skill.slug);
|
|
2629
|
+
if (storedSlug.length > SKILL_SLUG_MAX_LENGTH) {
|
|
2630
|
+
throw new Error(`Project skill slug "${storedSlug}" must be at most ${SKILL_SLUG_MAX_LENGTH} characters including the project prefix`);
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
async function planSkills(api, localSkills, projectId, appName) {
|
|
2635
|
+
if (localSkills.length === 0) return [];
|
|
2636
|
+
validateProjectSkillStorageSlugs(localSkills, appName);
|
|
2637
|
+
const scopedProjectID = requireProjectSkillsProjectID(projectId);
|
|
2638
|
+
const remoteSkills = await api.listAgentSkills({ project_id: scopedProjectID });
|
|
2639
|
+
const remoteBySlug = remoteSkillsByLocalSlug(remoteSkills, appName);
|
|
2640
|
+
const changes = [];
|
|
2641
|
+
for (const skill of localSkills) {
|
|
2642
|
+
const remote = remoteBySlug.get(skill.slug);
|
|
2643
|
+
if (!remote) {
|
|
2644
|
+
changes.push({ type: "create", resource: "skill", id: skill.slug, name: skill.name });
|
|
2645
|
+
continue;
|
|
2646
|
+
}
|
|
2647
|
+
const changed = [];
|
|
2648
|
+
if (remote.name !== skill.name) changed.push("name");
|
|
2649
|
+
if ((remote.summary || "").trim() !== skill.summary.trim()) changed.push("summary");
|
|
2650
|
+
if ((remote.instructions || "").trim() !== skill.instructions.trim()) changed.push("instructions");
|
|
2651
|
+
if (changed.length > 0) {
|
|
2652
|
+
changes.push({
|
|
2653
|
+
type: "update",
|
|
2654
|
+
resource: "skill",
|
|
2655
|
+
id: skill.slug,
|
|
2656
|
+
name: skill.name,
|
|
2657
|
+
details: `changed: ${changed.join(", ")}`,
|
|
2658
|
+
textDiffs: changed.includes("instructions") || changed.includes("summary") ? [{ field: "SKILL.md", oldText: skillMarkdown({ slug: skill.slug, name: remote.name, summary: remote.summary || "", instructions: remote.instructions || "" }), newText: skillMarkdown(skill) }] : void 0
|
|
2659
|
+
});
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
return changes;
|
|
2663
|
+
}
|
|
2664
|
+
async function applySkills(api, localSkills, projectId, appName) {
|
|
2665
|
+
if (localSkills.length === 0) return 0;
|
|
2666
|
+
validateProjectSkillStorageSlugs(localSkills, appName);
|
|
2667
|
+
const scopedProjectID = requireProjectSkillsProjectID(projectId);
|
|
2668
|
+
let errors = 0;
|
|
2669
|
+
const remoteSkills = await api.listAgentSkills({ project_id: scopedProjectID });
|
|
2670
|
+
const remoteBySlug = remoteSkillsByLocalSlug(remoteSkills, appName);
|
|
2671
|
+
for (const skill of localSkills) {
|
|
2672
|
+
const remote = remoteBySlug.get(skill.slug);
|
|
2673
|
+
try {
|
|
2674
|
+
if (remote) {
|
|
2675
|
+
await api.updateAgentSkill(remote.id, {
|
|
2676
|
+
name: skill.name,
|
|
2677
|
+
summary: skill.summary,
|
|
2678
|
+
instructions: skill.instructions
|
|
2679
|
+
});
|
|
2680
|
+
console.log(pc2.green(" \u2713"), `${skill.name} (updated)`);
|
|
2681
|
+
} else {
|
|
2682
|
+
await api.createAgentSkill({
|
|
2683
|
+
...skill,
|
|
2684
|
+
slug: scopedProjectSkillSlug(appName, skill.slug),
|
|
2685
|
+
project_id: scopedProjectID
|
|
2686
|
+
});
|
|
2687
|
+
console.log(pc2.green(" \u2713"), `${skill.name} (created)`);
|
|
2688
|
+
}
|
|
2689
|
+
} catch (error3) {
|
|
2690
|
+
console.log(pc2.red(" \u2717"), `${skill.name}: ${error3}`);
|
|
2691
|
+
errors++;
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
return errors;
|
|
2695
|
+
}
|
|
2696
|
+
async function pullSkills(api, platformDir, filterName, projectId, appName) {
|
|
2697
|
+
if (!projectId) {
|
|
2698
|
+
console.log(pc2.dim(" Skipped \u2014 project not registered (run `lumera register`)"));
|
|
2699
|
+
return;
|
|
2700
|
+
}
|
|
2701
|
+
const scopedProjectID = requireProjectSkillsProjectID(projectId);
|
|
2702
|
+
const skillsDir = join2(platformDir, "skills");
|
|
2703
|
+
const remoteSkills = remoteSkillsByLocalSlug(
|
|
2704
|
+
await api.listAgentSkills({ project_id: scopedProjectID }),
|
|
2705
|
+
appName
|
|
2706
|
+
);
|
|
2707
|
+
for (const [localSlug, skill] of remoteSkills) {
|
|
2708
|
+
if (!SKILL_SLUG_RE.test(localSlug)) {
|
|
2709
|
+
console.log(pc2.yellow(` \u26A0 Skipping ${skill.name}: skill slug ${skill.slug} cannot be represented in platform/skills`));
|
|
2710
|
+
continue;
|
|
2711
|
+
}
|
|
2712
|
+
if (filterName && localSlug !== filterName && skill.slug !== filterName && skill.name !== filterName) continue;
|
|
2713
|
+
if (!(skill.summary || "").trim() || !(skill.instructions || "").trim()) {
|
|
2714
|
+
console.log(pc2.yellow(` \u26A0 Skipping ${skill.name}: source-managed skills require both summary and instructions`));
|
|
2715
|
+
continue;
|
|
2716
|
+
}
|
|
2717
|
+
const skillDir = join2(skillsDir, localSlug);
|
|
2718
|
+
mkdirSync(skillDir, { recursive: true });
|
|
2719
|
+
writeFileSync(join2(skillDir, "SKILL.md"), skillMarkdown({
|
|
2720
|
+
slug: localSlug,
|
|
2721
|
+
name: skill.name,
|
|
2722
|
+
summary: skill.summary || "",
|
|
2723
|
+
instructions: skill.instructions || ""
|
|
2724
|
+
}));
|
|
2725
|
+
console.log(pc2.green(" \u2713"), `${skill.name} \u2192 skills/${localSlug}/`);
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2478
2728
|
function loadLocalAgents(platformDir, filterName, appName) {
|
|
2479
2729
|
const agentsDir = join2(platformDir, "agents");
|
|
2480
2730
|
if (!existsSync2(agentsDir)) return [];
|
|
@@ -2545,45 +2795,47 @@ function loadLocalAgents(platformDir, filterName, appName) {
|
|
|
2545
2795
|
}
|
|
2546
2796
|
return agents;
|
|
2547
2797
|
}
|
|
2548
|
-
async function planAgents(api, localAgents, projectId) {
|
|
2798
|
+
async function planAgents(api, localAgents, projectId, localSourceSkillSlugs = /* @__PURE__ */ new Set(), pendingLocalSkillSlugs = /* @__PURE__ */ new Set(), appName) {
|
|
2549
2799
|
const changes = [];
|
|
2800
|
+
let validationErrors = 0;
|
|
2550
2801
|
const remoteAgents = await api.listAgents(projectId ? { project_id: projectId } : void 0);
|
|
2551
2802
|
const remoteByExternalId = new Map(
|
|
2552
2803
|
remoteAgents.filter((a) => a.external_id && !a.managed).map((a) => [a.external_id, a])
|
|
2553
2804
|
);
|
|
2554
|
-
const refToId = /* @__PURE__ */ new Map();
|
|
2555
|
-
const skillIdToSlug = /* @__PURE__ */ new Map();
|
|
2556
|
-
const blockedRefs = /* @__PURE__ */ new Set();
|
|
2557
2805
|
const planRefs = [
|
|
2558
2806
|
.../* @__PURE__ */ new Set([
|
|
2559
2807
|
...localAgents.flatMap((a) => a.agent.skills ?? []),
|
|
2560
2808
|
...remoteAgents.flatMap((a) => a.skill_ids ?? [])
|
|
2561
2809
|
])
|
|
2562
2810
|
];
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
blockedRefs.add(s.id);
|
|
2574
|
-
}
|
|
2575
|
-
}
|
|
2576
|
-
} catch (e) {
|
|
2577
|
-
console.log(pc2.yellow(` \u26A0 Could not resolve skills for plan: ${e}`));
|
|
2578
|
-
}
|
|
2811
|
+
let resolution = {
|
|
2812
|
+
refToId: /* @__PURE__ */ new Map(),
|
|
2813
|
+
skillIdToSlug: /* @__PURE__ */ new Map(),
|
|
2814
|
+
blockedRefs: /* @__PURE__ */ new Set(),
|
|
2815
|
+
outsideProjectRefs: /* @__PURE__ */ new Set()
|
|
2816
|
+
};
|
|
2817
|
+
try {
|
|
2818
|
+
resolution = await resolveAgentSkillRefs(api, planRefs, projectId, appName, localSourceSkillSlugs);
|
|
2819
|
+
} catch (e) {
|
|
2820
|
+
console.log(pc2.yellow(` \u26A0 Could not resolve skills for plan: ${e}`));
|
|
2579
2821
|
}
|
|
2822
|
+
const { refToId, skillIdToSlug, blockedRefs, outsideProjectRefs } = resolution;
|
|
2580
2823
|
for (const { agent, systemPrompt, policyScript } of localAgents) {
|
|
2581
2824
|
const remote = remoteByExternalId.get(agent.external_id);
|
|
2582
2825
|
for (const ref of agent.skills ?? []) {
|
|
2583
|
-
if (
|
|
2826
|
+
if (outsideProjectRefs.has(ref)) {
|
|
2827
|
+
console.log(pc2.red(` \u2717 Skill "${ref}" belongs to another project \u2014 apply will fail for this agent`));
|
|
2828
|
+
validationErrors++;
|
|
2829
|
+
} else if (blockedRefs.has(ref)) {
|
|
2584
2830
|
console.log(pc2.red(` \u2717 Skill "${ref}" is a Lumera platform skill, only available to Lumera-built agents \u2014 apply will fail for this agent`));
|
|
2585
|
-
|
|
2586
|
-
|
|
2831
|
+
validationErrors++;
|
|
2832
|
+
} else if (!refToId.has(ref) && !pendingLocalSkillSlugs.has(ref)) {
|
|
2833
|
+
if (localSourceSkillSlugs.has(ref)) {
|
|
2834
|
+
console.log(pc2.red(` \u2717 Skill "${ref}" exists in project source but is not deployed \u2014 run 'lumera apply skills' first or use 'lumera apply'`));
|
|
2835
|
+
} else {
|
|
2836
|
+
console.log(pc2.red(` \u2717 Skill "${ref}" not found \u2014 apply will fail for this agent`));
|
|
2837
|
+
}
|
|
2838
|
+
validationErrors++;
|
|
2587
2839
|
}
|
|
2588
2840
|
}
|
|
2589
2841
|
if (!remote) {
|
|
@@ -2600,12 +2852,14 @@ async function planAgents(api, localAgents, projectId) {
|
|
|
2600
2852
|
if ((remote.policy_enabled || false) !== (agent.policy_enabled || false)) diffs.push("policy_enabled");
|
|
2601
2853
|
if ((remote.policy_description || "") !== (agent.policy_description || "")) diffs.push("policy_description");
|
|
2602
2854
|
const localSkillIds = (agent.skills || []).map((s) => refToId.get(s)).filter((id) => !!id).sort();
|
|
2855
|
+
const pendingSkillSlugs = (agent.skills || []).filter((ref) => pendingLocalSkillSlugs.has(ref) && !refToId.has(ref));
|
|
2603
2856
|
const remoteSkillIds = [...remote.skill_ids || []].sort();
|
|
2604
|
-
if (localSkillIds.join(",") !== remoteSkillIds.join(",")) {
|
|
2857
|
+
if (localSkillIds.join(",") !== remoteSkillIds.join(",") || pendingSkillSlugs.length > 0) {
|
|
2605
2858
|
const addedSlugs = localSkillIds.filter((id) => !remoteSkillIds.includes(id)).map((id) => skillIdToSlug.get(id) || id);
|
|
2606
2859
|
const removedSlugs = remoteSkillIds.filter((id) => !localSkillIds.includes(id)).map((id) => skillIdToSlug.get(id) || id);
|
|
2607
2860
|
const parts = [];
|
|
2608
2861
|
if (addedSlugs.length) parts.push(`+${addedSlugs.join(", +")}`);
|
|
2862
|
+
if (pendingSkillSlugs.length) parts.push(`+${pendingSkillSlugs.join(", +")}`);
|
|
2609
2863
|
if (removedSlugs.length) parts.push(`-${removedSlugs.join(", -")}`);
|
|
2610
2864
|
diffs.push(`skills (${parts.join(", ")})`);
|
|
2611
2865
|
}
|
|
@@ -2621,64 +2875,71 @@ async function planAgents(api, localAgents, projectId) {
|
|
|
2621
2875
|
}
|
|
2622
2876
|
}
|
|
2623
2877
|
}
|
|
2624
|
-
return changes;
|
|
2878
|
+
return { changes, validationErrors };
|
|
2625
2879
|
}
|
|
2626
2880
|
var TAG_AVAILABLE_TO_CUSTOM_AGENTS = "available_to_custom_agents";
|
|
2627
2881
|
function isAvailableToCustomAgents(skill) {
|
|
2628
2882
|
if (!skill.managed) return true;
|
|
2629
2883
|
return (skill.tags ?? []).includes(TAG_AVAILABLE_TO_CUSTOM_AGENTS);
|
|
2630
2884
|
}
|
|
2631
|
-
async function applyAgents(api, localAgents, projectId) {
|
|
2885
|
+
async function applyAgents(api, localAgents, projectId, localSourceSkillSlugs = /* @__PURE__ */ new Set(), appName) {
|
|
2632
2886
|
let errors = 0;
|
|
2633
2887
|
const remoteAgents = await api.listAgents(projectId ? { project_id: projectId } : void 0);
|
|
2634
2888
|
const remoteByExternalId = new Map(
|
|
2635
2889
|
remoteAgents.filter((a) => a.external_id && !a.managed).map((a) => [a.external_id, a])
|
|
2636
2890
|
);
|
|
2637
|
-
const refToId = /* @__PURE__ */ new Map();
|
|
2638
|
-
const blockedRefs = /* @__PURE__ */ new Set();
|
|
2639
2891
|
const refs = [...new Set(localAgents.flatMap((a) => a.agent.skills ?? []))];
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
}
|
|
2651
|
-
}
|
|
2652
|
-
} catch (e) {
|
|
2653
|
-
console.log(pc2.yellow(` \u26A0 Could not resolve skills: ${e}`));
|
|
2654
|
-
}
|
|
2892
|
+
let resolution = {
|
|
2893
|
+
refToId: /* @__PURE__ */ new Map(),
|
|
2894
|
+
skillIdToSlug: /* @__PURE__ */ new Map(),
|
|
2895
|
+
blockedRefs: /* @__PURE__ */ new Set(),
|
|
2896
|
+
outsideProjectRefs: /* @__PURE__ */ new Set()
|
|
2897
|
+
};
|
|
2898
|
+
try {
|
|
2899
|
+
resolution = await resolveAgentSkillRefs(api, refs, projectId, appName, localSourceSkillSlugs);
|
|
2900
|
+
} catch (e) {
|
|
2901
|
+
console.log(pc2.yellow(` \u26A0 Could not resolve skills: ${e}`));
|
|
2655
2902
|
}
|
|
2903
|
+
const { refToId, blockedRefs, outsideProjectRefs } = resolution;
|
|
2656
2904
|
for (const { agent, systemPrompt, policyScript } of localAgents) {
|
|
2657
2905
|
const remote = remoteByExternalId.get(agent.external_id);
|
|
2658
2906
|
const skillIds = [];
|
|
2907
|
+
const outsideProjectForAgent = [];
|
|
2659
2908
|
const blockedForAgent = [];
|
|
2909
|
+
const undeployedLocalForAgent = [];
|
|
2660
2910
|
const notFoundForAgent = [];
|
|
2661
2911
|
if (agent.skills) {
|
|
2662
2912
|
for (const ref of agent.skills) {
|
|
2663
2913
|
const id = refToId.get(ref);
|
|
2664
2914
|
if (id) {
|
|
2665
2915
|
skillIds.push(id);
|
|
2916
|
+
} else if (outsideProjectRefs.has(ref)) {
|
|
2917
|
+
outsideProjectForAgent.push(ref);
|
|
2666
2918
|
} else if (blockedRefs.has(ref)) {
|
|
2667
2919
|
blockedForAgent.push(ref);
|
|
2920
|
+
} else if (localSourceSkillSlugs.has(ref)) {
|
|
2921
|
+
undeployedLocalForAgent.push(ref);
|
|
2668
2922
|
} else {
|
|
2669
2923
|
notFoundForAgent.push(ref);
|
|
2670
2924
|
}
|
|
2671
2925
|
}
|
|
2672
2926
|
}
|
|
2673
|
-
if (blockedForAgent.length > 0 || notFoundForAgent.length > 0) {
|
|
2927
|
+
if (outsideProjectForAgent.length > 0 || blockedForAgent.length > 0 || undeployedLocalForAgent.length > 0 || notFoundForAgent.length > 0) {
|
|
2674
2928
|
console.log(pc2.red(" \u2717"), `${agent.name}:`);
|
|
2929
|
+
if (outsideProjectForAgent.length > 0) {
|
|
2930
|
+
console.log(pc2.red(` skills outside the agent project: ${outsideProjectForAgent.join(", ")}`));
|
|
2931
|
+
}
|
|
2675
2932
|
if (blockedForAgent.length > 0) {
|
|
2676
2933
|
console.log(pc2.red(` skills not available for custom agents: ${blockedForAgent.join(", ")}`));
|
|
2677
2934
|
}
|
|
2935
|
+
if (undeployedLocalForAgent.length > 0) {
|
|
2936
|
+
console.log(pc2.red(` locally authored skills not deployed: ${undeployedLocalForAgent.join(", ")}`));
|
|
2937
|
+
console.log(pc2.dim(` run 'lumera apply skills' first, or use 'lumera apply' to deploy skills and agents together`));
|
|
2938
|
+
}
|
|
2678
2939
|
if (notFoundForAgent.length > 0) {
|
|
2679
2940
|
console.log(pc2.red(` skills not found: ${notFoundForAgent.join(", ")}`));
|
|
2941
|
+
console.log(pc2.dim(` pick skills from GET /api/skills (tags includes "available_to_custom_agents")`));
|
|
2680
2942
|
}
|
|
2681
|
-
console.log(pc2.dim(` pick skills from GET /api/skills (tags includes "available_to_custom_agents")`));
|
|
2682
2943
|
errors++;
|
|
2683
2944
|
continue;
|
|
2684
2945
|
}
|
|
@@ -2711,7 +2972,7 @@ async function applyAgents(api, localAgents, projectId) {
|
|
|
2711
2972
|
}
|
|
2712
2973
|
return errors;
|
|
2713
2974
|
}
|
|
2714
|
-
async function pullAgents(api, platformDir, filterName, projectId) {
|
|
2975
|
+
async function pullAgents(api, platformDir, filterName, projectId, appName) {
|
|
2715
2976
|
const agentsDir = join2(platformDir, "agents");
|
|
2716
2977
|
mkdirSync(agentsDir, { recursive: true });
|
|
2717
2978
|
const agents = await api.listAgents(projectId ? { project_id: projectId } : void 0);
|
|
@@ -2720,7 +2981,9 @@ async function pullAgents(api, platformDir, filterName, projectId) {
|
|
|
2720
2981
|
if (pullRefs.length > 0) {
|
|
2721
2982
|
try {
|
|
2722
2983
|
const skills = await api.lookupAgentSkills(pullRefs);
|
|
2723
|
-
for (const s of skills)
|
|
2984
|
+
for (const s of skills) {
|
|
2985
|
+
skillIdToSlug.set(s.id, s.project_id === projectId ? localProjectSkillSlug(appName, s.slug) : s.slug);
|
|
2986
|
+
}
|
|
2724
2987
|
} catch (e) {
|
|
2725
2988
|
console.log(pc2.yellow(` \u26A0 Could not resolve skills for pull: ${e}`));
|
|
2726
2989
|
console.log(pc2.yellow(` Pulled configs may omit skills \u2014 review before re-applying.`));
|
|
@@ -2875,6 +3138,28 @@ async function listResources(api, platformDir, filterType, appName, projectId) {
|
|
|
2875
3138
|
}
|
|
2876
3139
|
}
|
|
2877
3140
|
}
|
|
3141
|
+
if (!filterType || filterType === "skills") {
|
|
3142
|
+
const localSkills = loadLocalSkills(platformDir);
|
|
3143
|
+
if (localSkills.length > 0) requireProjectSkillsProjectID(projectId);
|
|
3144
|
+
const remoteSkills = projectId ? await api.listAgentSkills({ project_id: projectId }) : [];
|
|
3145
|
+
const remoteBySlug = remoteSkillsByLocalSlug(remoteSkills, appName);
|
|
3146
|
+
const localSlugs = new Set(localSkills.map((skill) => skill.slug));
|
|
3147
|
+
for (const skill of localSkills) {
|
|
3148
|
+
const remote = remoteBySlug.get(skill.slug);
|
|
3149
|
+
if (!remote) {
|
|
3150
|
+
results.push({ name: skill.name, type: "skills", status: "local-only" });
|
|
3151
|
+
} else if (remote.name !== skill.name || (remote.summary || "").trim() !== skill.summary.trim() || (remote.instructions || "").trim() !== skill.instructions.trim()) {
|
|
3152
|
+
results.push({ name: skill.name, type: "skills", status: "changed", details: "SKILL.md" });
|
|
3153
|
+
} else {
|
|
3154
|
+
results.push({ name: skill.name, type: "skills", status: "synced" });
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
for (const remote of remoteSkills) {
|
|
3158
|
+
if (!localSlugs.has(localProjectSkillSlug(appName, remote.slug))) {
|
|
3159
|
+
results.push({ name: remote.name, type: "skills", status: "remote-only" });
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
2878
3163
|
if (!filterType || filterType === "mailboxes") {
|
|
2879
3164
|
const localMailboxes = loadLocalMailboxes(platformDir);
|
|
2880
3165
|
const remoteMailboxes = await api.listMailboxes();
|
|
@@ -3022,6 +3307,20 @@ async function destroyResources(api, platformDir, resourceType, resourceName, sk
|
|
|
3022
3307
|
} catch {
|
|
3023
3308
|
}
|
|
3024
3309
|
}
|
|
3310
|
+
if (!resourceType || resourceType === "skills") {
|
|
3311
|
+
const localSkills = loadLocalSkills(platformDir, resourceName || void 0);
|
|
3312
|
+
if (localSkills.length > 0) {
|
|
3313
|
+
const scopedProjectID = requireProjectSkillsProjectID(projectId);
|
|
3314
|
+
const remoteSkills = await api.listAgentSkills({ project_id: scopedProjectID });
|
|
3315
|
+
const remoteBySlug = remoteSkillsByLocalSlug(remoteSkills, appName);
|
|
3316
|
+
for (const skill of localSkills) {
|
|
3317
|
+
const remote = remoteBySlug.get(skill.slug);
|
|
3318
|
+
if (remote) {
|
|
3319
|
+
toDelete.push({ type: "skill", id: skill.slug, name: skill.name, remoteId: remote.id });
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3025
3324
|
if (toDelete.length === 0) {
|
|
3026
3325
|
console.log(pc2.green(" \u2713 No resources found to delete"));
|
|
3027
3326
|
return;
|
|
@@ -3045,6 +3344,7 @@ async function destroyResources(api, platformDir, resourceType, resourceName, sk
|
|
|
3045
3344
|
}
|
|
3046
3345
|
}
|
|
3047
3346
|
const agents = toDelete.filter((r) => r.type === "agent");
|
|
3347
|
+
const skills = toDelete.filter((r) => r.type === "skill");
|
|
3048
3348
|
const hooks = toDelete.filter((r) => r.type === "hook");
|
|
3049
3349
|
const automations = toDelete.filter((r) => r.type === "automation");
|
|
3050
3350
|
const collections = toDelete.filter((r) => r.type === "collection");
|
|
@@ -3058,6 +3358,15 @@ async function destroyResources(api, platformDir, resourceType, resourceName, sk
|
|
|
3058
3358
|
errors++;
|
|
3059
3359
|
}
|
|
3060
3360
|
}
|
|
3361
|
+
for (const resource of skills) {
|
|
3362
|
+
try {
|
|
3363
|
+
await api.deleteAgentSkill(resource.remoteId);
|
|
3364
|
+
console.log(pc2.green(" \u2713"), `Deleted skill: ${resource.name}`);
|
|
3365
|
+
} catch (e) {
|
|
3366
|
+
console.log(pc2.red(" \u2717"), `Failed to delete skill ${resource.name}: ${e}`);
|
|
3367
|
+
errors++;
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3061
3370
|
for (const resource of hooks) {
|
|
3062
3371
|
try {
|
|
3063
3372
|
await api.deleteHook(resource.remoteId);
|
|
@@ -3314,6 +3623,31 @@ async function showResource(api, platformDir, resourceType, resourceName, appNam
|
|
|
3314
3623
|
console.log(` Skills: ${skills.join(", ")}`);
|
|
3315
3624
|
}
|
|
3316
3625
|
console.log();
|
|
3626
|
+
} else if (resourceType === "skills") {
|
|
3627
|
+
const localSkills = loadLocalSkills(platformDir, resourceName);
|
|
3628
|
+
if (localSkills.length > 0) requireProjectSkillsProjectID(projectId);
|
|
3629
|
+
const remoteSkills = projectId ? await api.listAgentSkills({ project_id: projectId }) : [];
|
|
3630
|
+
const local = localSkills[0];
|
|
3631
|
+
const remote = remoteSkillsByLocalSlug(remoteSkills, appName).get(resourceName) || remoteSkills.find((skill) => skill.name === resourceName);
|
|
3632
|
+
if (!local && !remote) {
|
|
3633
|
+
console.log(pc2.red(` Skill "${resourceName}" not found`));
|
|
3634
|
+
process.exit(1);
|
|
3635
|
+
}
|
|
3636
|
+
console.log();
|
|
3637
|
+
console.log(pc2.bold(` Skill: ${local?.name || remote?.name}`));
|
|
3638
|
+
console.log();
|
|
3639
|
+
if (local && remote) {
|
|
3640
|
+
const changed = local.name !== remote.name || local.summary.trim() !== (remote.summary || "").trim() || local.instructions.trim() !== (remote.instructions || "").trim();
|
|
3641
|
+
console.log(` Status: ${changed ? pc2.yellow("changed") : pc2.green("synced")}`);
|
|
3642
|
+
} else if (local) {
|
|
3643
|
+
console.log(` Status: ${pc2.yellow("local only")}`);
|
|
3644
|
+
} else {
|
|
3645
|
+
console.log(` Status: ${pc2.cyan("remote only")}`);
|
|
3646
|
+
}
|
|
3647
|
+
console.log(` Slug: ${local?.slug || (remote ? localProjectSkillSlug(appName, remote.slug) : "")}`);
|
|
3648
|
+
const summary = local?.summary || remote?.summary;
|
|
3649
|
+
if (summary) console.log(` Summary: ${summary}`);
|
|
3650
|
+
console.log();
|
|
3317
3651
|
} else if (resourceType === "mailboxes") {
|
|
3318
3652
|
const localMailboxes = loadLocalMailboxes(platformDir, resourceName);
|
|
3319
3653
|
const remoteMailboxes = await api.listMailboxes();
|
|
@@ -3401,6 +3735,7 @@ async function plan(args) {
|
|
|
3401
3735
|
}
|
|
3402
3736
|
await syncDeps(projectRoot, { ignorePermissionDenied: true });
|
|
3403
3737
|
const allChanges = [];
|
|
3738
|
+
let planValidationErrors = 0;
|
|
3404
3739
|
let collections;
|
|
3405
3740
|
try {
|
|
3406
3741
|
const remoteCollections = await api.listCollections();
|
|
@@ -3430,11 +3765,19 @@ async function plan(args) {
|
|
|
3430
3765
|
allChanges.push(...changes);
|
|
3431
3766
|
}
|
|
3432
3767
|
}
|
|
3768
|
+
const localSkills = !type || type === "skills" ? loadLocalSkills(platformDir, name || void 0) : [];
|
|
3769
|
+
const sourceSkillsForAgentResolution = type === "agents" ? loadLocalSkills(platformDir) : localSkills;
|
|
3770
|
+
const localSourceSkillSlugs = new Set(sourceSkillsForAgentResolution.map((skill) => skill.slug));
|
|
3771
|
+
const pendingLocalSkillSlugs = new Set(localSkills.map((skill) => skill.slug));
|
|
3772
|
+
if (localSkills.length > 0) {
|
|
3773
|
+
allChanges.push(...await planSkills(api, localSkills, projectId, appName));
|
|
3774
|
+
}
|
|
3433
3775
|
if (!type || type === "agents") {
|
|
3434
3776
|
const localAgents = loadLocalAgents(platformDir, name || void 0, appName);
|
|
3435
3777
|
if (localAgents.length > 0) {
|
|
3436
|
-
const
|
|
3437
|
-
allChanges.push(...changes);
|
|
3778
|
+
const result = await planAgents(api, localAgents, projectId, localSourceSkillSlugs, pendingLocalSkillSlugs, appName);
|
|
3779
|
+
allChanges.push(...result.changes);
|
|
3780
|
+
planValidationErrors += result.validationErrors;
|
|
3438
3781
|
}
|
|
3439
3782
|
}
|
|
3440
3783
|
if (!type || type === "mailboxes") {
|
|
@@ -3447,7 +3790,14 @@ async function plan(args) {
|
|
|
3447
3790
|
const lintWarnings = await safeLint(projectRoot, localAutomations);
|
|
3448
3791
|
if (allChanges.length === 0) {
|
|
3449
3792
|
if (jsonOutput) {
|
|
3450
|
-
console.log(JSON.stringify({ changes: [], warnings: [], lintWarnings: serializeLintWarnings(lintWarnings, projectRoot) }));
|
|
3793
|
+
console.log(JSON.stringify({ changes: [], warnings: [], validationErrors: planValidationErrors, lintWarnings: serializeLintWarnings(lintWarnings, projectRoot) }));
|
|
3794
|
+
if (planValidationErrors > 0) process.exitCode = 1;
|
|
3795
|
+
return;
|
|
3796
|
+
}
|
|
3797
|
+
if (planValidationErrors > 0) {
|
|
3798
|
+
console.log(pc2.red(` \u2717 Plan has ${planValidationErrors} agent skill validation error${planValidationErrors === 1 ? "" : "s"}.`));
|
|
3799
|
+
console.log();
|
|
3800
|
+
process.exitCode = 1;
|
|
3451
3801
|
return;
|
|
3452
3802
|
}
|
|
3453
3803
|
console.log(pc2.green(" \u2713 No changes detected"));
|
|
@@ -3465,7 +3815,8 @@ async function plan(args) {
|
|
|
3465
3815
|
}
|
|
3466
3816
|
}
|
|
3467
3817
|
}
|
|
3468
|
-
console.log(JSON.stringify({ changes: allChanges, warnings, lintWarnings: serializeLintWarnings(lintWarnings, projectRoot) }));
|
|
3818
|
+
console.log(JSON.stringify({ changes: allChanges, warnings, validationErrors: planValidationErrors, lintWarnings: serializeLintWarnings(lintWarnings, projectRoot) }));
|
|
3819
|
+
if (planValidationErrors > 0) process.exitCode = 1;
|
|
3469
3820
|
return;
|
|
3470
3821
|
}
|
|
3471
3822
|
console.log(pc2.bold(" Changes:"));
|
|
@@ -3506,7 +3857,12 @@ async function plan(args) {
|
|
|
3506
3857
|
}
|
|
3507
3858
|
console.log();
|
|
3508
3859
|
safePrintLint(lintWarnings, projectRoot);
|
|
3509
|
-
|
|
3860
|
+
if (planValidationErrors > 0) {
|
|
3861
|
+
console.log(pc2.red(` \u2717 Fix ${planValidationErrors} agent skill validation error${planValidationErrors === 1 ? "" : "s"} before applying.`));
|
|
3862
|
+
process.exitCode = 1;
|
|
3863
|
+
} else {
|
|
3864
|
+
console.log(pc2.dim(` Run 'lumera apply' to apply these changes.`));
|
|
3865
|
+
}
|
|
3510
3866
|
console.log();
|
|
3511
3867
|
}
|
|
3512
3868
|
async function apply(args) {
|
|
@@ -3553,24 +3909,40 @@ async function apply(args) {
|
|
|
3553
3909
|
collections = /* @__PURE__ */ new Map();
|
|
3554
3910
|
}
|
|
3555
3911
|
const allChanges = [];
|
|
3912
|
+
let planValidationErrors = 0;
|
|
3556
3913
|
const localCollections = !type || type === "collections" ? loadLocalCollections(platformDir, name || void 0) : [];
|
|
3557
3914
|
const localAutomations = !type || type === "automations" ? loadLocalAutomations(platformDir, name || void 0, appName) : [];
|
|
3558
3915
|
const localHooks = !type || type === "hooks" ? loadLocalHooks(platformDir, name || void 0, appName) : [];
|
|
3916
|
+
const localSkills = !type || type === "skills" ? loadLocalSkills(platformDir, name || void 0) : [];
|
|
3917
|
+
const sourceSkillsForAgentResolution = type === "agents" ? loadLocalSkills(platformDir) : localSkills;
|
|
3918
|
+
const localSourceSkillSlugs = new Set(sourceSkillsForAgentResolution.map((skill) => skill.slug));
|
|
3919
|
+
const pendingLocalSkillSlugs = new Set(localSkills.map((skill) => skill.slug));
|
|
3559
3920
|
const localAgents = !type || type === "agents" ? loadLocalAgents(platformDir, name || void 0, appName) : [];
|
|
3560
3921
|
const localMailboxes = !type || type === "mailboxes" ? loadLocalMailboxes(platformDir, name || void 0) : [];
|
|
3561
3922
|
if (localCollections.length > 0) allChanges.push(...await planCollections(api, localCollections, appName, projectId));
|
|
3562
3923
|
if (localAutomations.length > 0) allChanges.push(...await planAutomations(api, localAutomations, projectId));
|
|
3563
3924
|
if (localHooks.length > 0) allChanges.push(...await planHooks(api, localHooks, collections, projectId));
|
|
3564
|
-
if (
|
|
3925
|
+
if (localSkills.length > 0) allChanges.push(...await planSkills(api, localSkills, projectId, appName));
|
|
3926
|
+
if (localAgents.length > 0) {
|
|
3927
|
+
const result = await planAgents(api, localAgents, projectId, localSourceSkillSlugs, pendingLocalSkillSlugs, appName);
|
|
3928
|
+
allChanges.push(...result.changes);
|
|
3929
|
+
planValidationErrors += result.validationErrors;
|
|
3930
|
+
}
|
|
3565
3931
|
if (localMailboxes.length > 0) allChanges.push(...await planMailboxes(api, localMailboxes));
|
|
3566
3932
|
if (name) {
|
|
3567
|
-
const hasLocal = localCollections.length > 0 || localAutomations.length > 0 || localHooks.length > 0 || localAgents.length > 0 || localMailboxes.length > 0;
|
|
3933
|
+
const hasLocal = localCollections.length > 0 || localAutomations.length > 0 || localHooks.length > 0 || localSkills.length > 0 || localAgents.length > 0 || localMailboxes.length > 0;
|
|
3568
3934
|
if (!hasLocal) {
|
|
3569
3935
|
console.log();
|
|
3570
3936
|
console.log(pc2.red(` Resource "${name}" not found locally`));
|
|
3571
3937
|
process.exit(1);
|
|
3572
3938
|
}
|
|
3573
3939
|
}
|
|
3940
|
+
if (planValidationErrors > 0) {
|
|
3941
|
+
console.log();
|
|
3942
|
+
console.log(pc2.red(` \u2717 Apply aborted: fix ${planValidationErrors} agent skill validation error${planValidationErrors === 1 ? "" : "s"} first.`));
|
|
3943
|
+
console.log();
|
|
3944
|
+
process.exit(1);
|
|
3945
|
+
}
|
|
3574
3946
|
let willDeployApp = false;
|
|
3575
3947
|
if (!type && !skipApp) {
|
|
3576
3948
|
try {
|
|
@@ -3651,9 +4023,14 @@ async function apply(args) {
|
|
|
3651
4023
|
totalErrors += await applyHooks(api, localHooks, collections, projectId);
|
|
3652
4024
|
console.log();
|
|
3653
4025
|
}
|
|
4026
|
+
if (localSkills.length > 0) {
|
|
4027
|
+
console.log(pc2.bold(" Skills:"));
|
|
4028
|
+
totalErrors += await applySkills(api, localSkills, projectId, appName);
|
|
4029
|
+
console.log();
|
|
4030
|
+
}
|
|
3654
4031
|
if (localAgents.length > 0) {
|
|
3655
4032
|
console.log(pc2.bold(" Agents:"));
|
|
3656
|
-
totalErrors += await applyAgents(api, localAgents, projectId);
|
|
4033
|
+
totalErrors += await applyAgents(api, localAgents, projectId, localSourceSkillSlugs, appName);
|
|
3657
4034
|
console.log();
|
|
3658
4035
|
}
|
|
3659
4036
|
if (localMailboxes.length > 0) {
|
|
@@ -3706,10 +4083,27 @@ async function pull(args) {
|
|
|
3706
4083
|
} catch {
|
|
3707
4084
|
collections = /* @__PURE__ */ new Map();
|
|
3708
4085
|
}
|
|
4086
|
+
if (projectId && (!type || type === "skills")) {
|
|
4087
|
+
const localSkills = loadLocalSkills(platformDir, name || void 0);
|
|
4088
|
+
if (localSkills.length > 0) {
|
|
4089
|
+
const changes = await planSkills(api, localSkills, projectId, appName);
|
|
4090
|
+
for (const change of changes) {
|
|
4091
|
+
if (change.type === "update") conflicts.push(`skills/${change.id}`);
|
|
4092
|
+
}
|
|
4093
|
+
}
|
|
4094
|
+
}
|
|
3709
4095
|
if (!type || type === "agents") {
|
|
3710
4096
|
const localAgents = loadLocalAgents(platformDir, name || void 0, appName);
|
|
3711
4097
|
if (localAgents.length > 0) {
|
|
3712
|
-
const
|
|
4098
|
+
const localSkillSlugs = new Set(loadLocalSkills(platformDir).map((skill) => skill.slug));
|
|
4099
|
+
const { changes } = await planAgents(
|
|
4100
|
+
api,
|
|
4101
|
+
localAgents,
|
|
4102
|
+
projectId,
|
|
4103
|
+
localSkillSlugs,
|
|
4104
|
+
localSkillSlugs,
|
|
4105
|
+
appName
|
|
4106
|
+
);
|
|
3713
4107
|
for (const c of changes) {
|
|
3714
4108
|
if (c.type === "update") conflicts.push(`agents/${c.name}`);
|
|
3715
4109
|
}
|
|
@@ -3770,9 +4164,14 @@ async function pull(args) {
|
|
|
3770
4164
|
await pullHooks(api, platformDir, name || void 0, appName, projectId);
|
|
3771
4165
|
console.log();
|
|
3772
4166
|
}
|
|
4167
|
+
if (!type || type === "skills") {
|
|
4168
|
+
console.log(pc2.bold(" Skills:"));
|
|
4169
|
+
await pullSkills(api, platformDir, name || void 0, projectId, appName);
|
|
4170
|
+
console.log();
|
|
4171
|
+
}
|
|
3773
4172
|
if (!type || type === "agents") {
|
|
3774
4173
|
console.log(pc2.bold(" Agents:"));
|
|
3775
|
-
await pullAgents(api, platformDir, name || void 0, projectId);
|
|
4174
|
+
await pullAgents(api, platformDir, name || void 0, projectId, appName);
|
|
3776
4175
|
console.log();
|
|
3777
4176
|
}
|
|
3778
4177
|
if (!type || type === "mailboxes") {
|
|
@@ -3924,6 +4323,7 @@ ${pc2.dim("Usage:")}
|
|
|
3924
4323
|
|
|
3925
4324
|
${pc2.dim("Resources:")}
|
|
3926
4325
|
agents/<name> Diff agent (system_prompt, policy_script)
|
|
4326
|
+
skills/<slug> Diff project skill instructions
|
|
3927
4327
|
automations/<name> Diff automation code
|
|
3928
4328
|
hooks/<name> Diff hook script
|
|
3929
4329
|
|
|
@@ -4072,23 +4472,71 @@ async function diff(args) {
|
|
|
4072
4472
|
} else if (scriptChanged) {
|
|
4073
4473
|
renderFullDiff(local.fileName, remote.script || "", local.script);
|
|
4074
4474
|
}
|
|
4475
|
+
} else if (type === "skills") {
|
|
4476
|
+
const localSkills = loadLocalSkills(platformDir, name);
|
|
4477
|
+
if (localSkills.length > 0) requireProjectSkillsProjectID(projectId);
|
|
4478
|
+
const remoteSkills = projectId ? await api.listAgentSkills({ project_id: projectId }) : [];
|
|
4479
|
+
const local = localSkills[0];
|
|
4480
|
+
const remote = remoteSkillsByLocalSlug(remoteSkills, appName).get(name) || remoteSkills.find((skill) => skill.name === name);
|
|
4481
|
+
if (!local && !remote) {
|
|
4482
|
+
console.log(pc2.red(` Skill "${name}" not found locally or remotely`));
|
|
4483
|
+
process.exit(1);
|
|
4484
|
+
}
|
|
4485
|
+
if (!local) {
|
|
4486
|
+
console.log(pc2.cyan(` Skill "${name}" exists only remotely`));
|
|
4487
|
+
process.exit(0);
|
|
4488
|
+
}
|
|
4489
|
+
if (!remote) {
|
|
4490
|
+
console.log(pc2.yellow(` Skill "${name}" exists only locally (not yet deployed)`));
|
|
4491
|
+
process.exit(0);
|
|
4492
|
+
}
|
|
4493
|
+
console.log(pc2.bold(` Skill: ${local.name}`));
|
|
4494
|
+
console.log();
|
|
4495
|
+
if (remote.name !== local.name) {
|
|
4496
|
+
console.log(` name: ${pc2.red(remote.name)} \u2192 ${pc2.green(local.name)}`);
|
|
4497
|
+
}
|
|
4498
|
+
if ((remote.summary || "").trim() !== local.summary.trim()) {
|
|
4499
|
+
console.log(` summary: ${pc2.red(remote.summary || "(empty)")} \u2192 ${pc2.green(local.summary)}`);
|
|
4500
|
+
}
|
|
4501
|
+
const remoteMarkdown = skillMarkdown({
|
|
4502
|
+
slug: localProjectSkillSlug(appName, remote.slug),
|
|
4503
|
+
name: remote.name,
|
|
4504
|
+
summary: remote.summary || "",
|
|
4505
|
+
instructions: remote.instructions || ""
|
|
4506
|
+
});
|
|
4507
|
+
const localMarkdown = skillMarkdown(local);
|
|
4508
|
+
if (remoteMarkdown === localMarkdown) {
|
|
4509
|
+
console.log(pc2.green(" \u2713 No changes"));
|
|
4510
|
+
} else {
|
|
4511
|
+
renderFullDiff("SKILL.md", remoteMarkdown, localMarkdown);
|
|
4512
|
+
}
|
|
4075
4513
|
} else {
|
|
4076
|
-
console.log(pc2.red(` Diff not supported for "${type}" \u2014 use agents, automations, or hooks`));
|
|
4514
|
+
console.log(pc2.red(` Diff not supported for "${type}" \u2014 use agents, skills, automations, or hooks`));
|
|
4077
4515
|
process.exit(1);
|
|
4078
4516
|
}
|
|
4079
4517
|
console.log();
|
|
4080
4518
|
}
|
|
4081
4519
|
export {
|
|
4082
4520
|
apply,
|
|
4521
|
+
applyAgents,
|
|
4083
4522
|
applyCollections,
|
|
4523
|
+
applySkills,
|
|
4084
4524
|
convertCollectionToApiFormat,
|
|
4085
4525
|
destroy,
|
|
4086
4526
|
diff,
|
|
4087
4527
|
list,
|
|
4088
4528
|
loadLocalCollections,
|
|
4529
|
+
loadLocalSkills,
|
|
4530
|
+
localProjectSkillSlug,
|
|
4089
4531
|
plan,
|
|
4532
|
+
planAgents,
|
|
4090
4533
|
planCollections,
|
|
4534
|
+
planSkills,
|
|
4535
|
+
projectSkillRefCandidates,
|
|
4091
4536
|
pull,
|
|
4092
4537
|
pullCollections,
|
|
4538
|
+
pullSkills,
|
|
4539
|
+
resolveAgentSkillRefs,
|
|
4540
|
+
scopedProjectSkillSlug,
|
|
4093
4541
|
show
|
|
4094
4542
|
};
|
package/package.json
CHANGED
|
File without changes
|