@lumerahq/cli 0.28.0 → 0.30.0-dev.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-NIVQLH6V.js → chunk-FLFEF2BN.js} +20 -8
- 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-JUOVWQQH.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-2PYA24JV.js → project-6POVRBZL.js} +14 -4
- package/dist/{register-HRLBT4FI.js → register-OBQZPOGC.js} +1 -1
- package/dist/{resources-R53XKJL2.js → resources-YEOQ7YN6.js} +595 -67
- 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;
|
|
@@ -50,14 +50,18 @@ ${pc.dim("Usage:")}
|
|
|
50
50
|
lumera functions list
|
|
51
51
|
lumera functions inspect <function-id>
|
|
52
52
|
lumera functions invoke <function-id> --input <path|-> --local
|
|
53
|
+
lumera functions invoke <function-id> --input <path|-> --remote --project <project>
|
|
53
54
|
lumera functions test [-- <pytest-options>]
|
|
54
55
|
|
|
55
56
|
${pc.dim("Description:")}
|
|
56
|
-
Discover, inspect, invoke, and test configured Functions
|
|
57
|
-
|
|
57
|
+
Discover, inspect, invoke, and test configured Functions. Invocation can run
|
|
58
|
+
in a fresh local process or create a durable remote execution.
|
|
58
59
|
|
|
59
60
|
${pc.dim("Options:")}
|
|
60
|
-
--local
|
|
61
|
+
--local Run from the local project source
|
|
62
|
+
--remote Create a durable execution from the active release
|
|
63
|
+
--project <project> Project ID or external ID for remote invocation
|
|
64
|
+
--idempotency-key <k> Deduplicate matching remote invocation requests
|
|
61
65
|
--input <path|-> Read a JSON object from a project file or stdin (-)
|
|
62
66
|
--help, -h Show this help
|
|
63
67
|
|
|
@@ -65,6 +69,7 @@ ${pc.dim("Examples:")}
|
|
|
65
69
|
lumera functions list
|
|
66
70
|
lumera functions inspect invoices:approve
|
|
67
71
|
lumera functions invoke invoices:approve --input input.json --local
|
|
72
|
+
lumera functions invoke invoices:approve --input input.json --remote --project finance
|
|
68
73
|
cat input.json | lumera functions invoke invoices:approve --input - --local
|
|
69
74
|
lumera functions test
|
|
70
75
|
lumera functions test -- -k approve
|
|
@@ -74,10 +79,17 @@ function buildFunctionRunnerArgs(subcommand, args, projectRoot) {
|
|
|
74
79
|
if (!SUPPORTED_SUBCOMMANDS.has(subcommand)) {
|
|
75
80
|
throw new Error(`Unknown functions subcommand: ${subcommand}`);
|
|
76
81
|
}
|
|
77
|
-
if (subcommand === "invoke"
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
)
|
|
82
|
+
if (subcommand === "invoke") {
|
|
83
|
+
const local = args.includes("--local");
|
|
84
|
+
const remote = args.includes("--remote");
|
|
85
|
+
if (local === remote) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
"Pass exactly one of --local or --remote for Function invocation."
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
if (remote && !args.includes("--project")) {
|
|
91
|
+
throw new Error("--project is required for remote Function invocation.");
|
|
92
|
+
}
|
|
81
93
|
}
|
|
82
94
|
const runnerPrefix = ["run"];
|
|
83
95
|
if (existsSync(join(projectRoot, "uv.lock"))) {
|
|
@@ -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-YEOQ7YN6.js").then((m) => m.plan(args.slice(1)));
|
|
244
244
|
break;
|
|
245
245
|
case "apply":
|
|
246
|
-
await import("./resources-
|
|
246
|
+
await import("./resources-YEOQ7YN6.js").then((m) => m.apply(args.slice(1)));
|
|
247
247
|
break;
|
|
248
248
|
case "pull":
|
|
249
|
-
await import("./resources-
|
|
249
|
+
await import("./resources-YEOQ7YN6.js").then((m) => m.pull(args.slice(1)));
|
|
250
250
|
break;
|
|
251
251
|
case "destroy":
|
|
252
|
-
await import("./resources-
|
|
252
|
+
await import("./resources-YEOQ7YN6.js").then((m) => m.destroy(args.slice(1)));
|
|
253
253
|
break;
|
|
254
254
|
case "list":
|
|
255
|
-
await import("./resources-
|
|
255
|
+
await import("./resources-YEOQ7YN6.js").then((m) => m.list(args.slice(1)));
|
|
256
256
|
break;
|
|
257
257
|
case "show":
|
|
258
|
-
await import("./resources-
|
|
258
|
+
await import("./resources-YEOQ7YN6.js").then((m) => m.show(args.slice(1)));
|
|
259
259
|
break;
|
|
260
260
|
case "diff":
|
|
261
|
-
await import("./resources-
|
|
261
|
+
await import("./resources-YEOQ7YN6.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,14 +1,21 @@
|
|
|
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
|
-
var RELEASE_COMMANDS = /* @__PURE__ */ new Set([
|
|
11
|
+
var RELEASE_COMMANDS = /* @__PURE__ */ new Set([
|
|
12
|
+
"build",
|
|
13
|
+
"inspect",
|
|
14
|
+
"contract-test",
|
|
15
|
+
"deploy",
|
|
16
|
+
"stage",
|
|
17
|
+
"activate"
|
|
18
|
+
]);
|
|
12
19
|
function showHelp() {
|
|
13
20
|
console.log(`
|
|
14
21
|
${pc.dim("Usage:")}
|
|
@@ -16,10 +23,13 @@ ${pc.dim("Usage:")}
|
|
|
16
23
|
lumera project release inspect <archive> [--expected-digest <sha256>]
|
|
17
24
|
lumera project release contract-test <archive> [--mode api|worker|all]
|
|
18
25
|
lumera project release deploy <archive> --project <project-id>
|
|
26
|
+
lumera project release stage <release-id> --project <project-id> --expected-generation <n>
|
|
27
|
+
lumera project release activate <deployment-id> --project <project-id> --expected-generation <n>
|
|
19
28
|
|
|
20
29
|
${pc.dim("Description:")}
|
|
21
30
|
Build, verify, and contract-test immutable Python 3.13/Linux/ARM64 project
|
|
22
|
-
releases,
|
|
31
|
+
releases, upload an inactive resource, stage a healthy runtime, and activate
|
|
32
|
+
it atomically with an expected generation.
|
|
23
33
|
`);
|
|
24
34
|
}
|
|
25
35
|
function buildReleaseRunnerArgs(command, args, projectRoot) {
|
|
@@ -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,
|
|
@@ -93,10 +93,132 @@ var automationConfigRule = {
|
|
|
93
93
|
}
|
|
94
94
|
};
|
|
95
95
|
|
|
96
|
-
// src/lib/lint/rules/
|
|
96
|
+
// src/lib/lint/rules/automation-main-guard.ts
|
|
97
|
+
import { spawnSync } from "child_process";
|
|
98
|
+
var PYTHON_AST_SCRIPT = String.raw`
|
|
99
|
+
import ast
|
|
100
|
+
import json
|
|
101
|
+
import sys
|
|
102
|
+
|
|
103
|
+
source = sys.stdin.read()
|
|
104
|
+
try:
|
|
105
|
+
module = ast.parse(source)
|
|
106
|
+
except SyntaxError:
|
|
107
|
+
print("null")
|
|
108
|
+
raise SystemExit(0)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def is_name(node):
|
|
112
|
+
return isinstance(node, ast.Name) and node.id == "__name__"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def is_main_string(node):
|
|
116
|
+
if isinstance(node, ast.Str):
|
|
117
|
+
return node.s == "__main__"
|
|
118
|
+
return isinstance(node, ast.Constant) and node.value == "__main__"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def is_main_guard(node):
|
|
122
|
+
test = node.test
|
|
123
|
+
if not isinstance(test, ast.Compare):
|
|
124
|
+
return False
|
|
125
|
+
if len(test.ops) != 1 or not isinstance(test.ops[0], ast.Eq):
|
|
126
|
+
return False
|
|
127
|
+
if len(test.comparators) != 1:
|
|
128
|
+
return False
|
|
129
|
+
right = test.comparators[0]
|
|
130
|
+
return (is_name(test.left) and is_main_string(right)) or (
|
|
131
|
+
is_main_string(test.left) and is_name(right)
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
for statement in module.body:
|
|
136
|
+
if not isinstance(statement, ast.If) or not is_main_guard(statement):
|
|
137
|
+
continue
|
|
138
|
+
get_segment = getattr(ast, "get_source_segment", None)
|
|
139
|
+
condition = get_segment(source, statement.test) if get_segment else None
|
|
140
|
+
if not condition:
|
|
141
|
+
condition = '__name__ == "__main__"'
|
|
142
|
+
snippet = "if " + " ".join(condition.split()) + ":"
|
|
143
|
+
print(json.dumps({"line": statement.lineno, "snippet": snippet}))
|
|
144
|
+
break
|
|
145
|
+
else:
|
|
146
|
+
print("null")
|
|
147
|
+
`;
|
|
148
|
+
var cachedPython3;
|
|
149
|
+
function findPython3() {
|
|
150
|
+
if (cachedPython3 !== void 0) return cachedPython3;
|
|
151
|
+
for (const executable of ["python3", "python"]) {
|
|
152
|
+
const probe = spawnSync(executable, ["-I", "-S", "--version"], {
|
|
153
|
+
encoding: "utf8",
|
|
154
|
+
timeout: 2e3,
|
|
155
|
+
windowsHide: true
|
|
156
|
+
});
|
|
157
|
+
const version = `${probe.stdout ?? ""}${probe.stderr ?? ""}`.trim();
|
|
158
|
+
if (probe.status === 0 && /^Python 3(?:\.|\s|$)/.test(version)) {
|
|
159
|
+
cachedPython3 = executable;
|
|
160
|
+
return executable;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
cachedPython3 = null;
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
97
166
|
function isRecord2(value) {
|
|
98
167
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
99
168
|
}
|
|
169
|
+
function functionEntrypoint(config) {
|
|
170
|
+
if (!isRecord2(config) || !isRecord2(config.function)) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
const name = config.function.name;
|
|
174
|
+
return typeof name === "string" && name.trim() !== "" ? name.trim() : null;
|
|
175
|
+
}
|
|
176
|
+
function findTopLevelMainGuard(source) {
|
|
177
|
+
const python = findPython3();
|
|
178
|
+
if (python === null) return null;
|
|
179
|
+
const parsed = spawnSync(python, ["-I", "-S", "-c", PYTHON_AST_SCRIPT], {
|
|
180
|
+
input: source,
|
|
181
|
+
encoding: "utf8",
|
|
182
|
+
maxBuffer: 1024 * 1024,
|
|
183
|
+
timeout: 5e3,
|
|
184
|
+
windowsHide: true
|
|
185
|
+
});
|
|
186
|
+
if (parsed.status !== 0) return null;
|
|
187
|
+
try {
|
|
188
|
+
const value = JSON.parse(parsed.stdout.trim());
|
|
189
|
+
if (!isRecord2(value) || typeof value.line !== "number" || typeof value.snippet !== "string") {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
return { line: value.line, snippet: value.snippet };
|
|
193
|
+
} catch {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
var automationMainGuardRule = {
|
|
198
|
+
id: "automation-main-guard",
|
|
199
|
+
description: "Warns when a function automation contains a top-level __main__ guard.",
|
|
200
|
+
appliesTo: ["automation"],
|
|
201
|
+
check(target) {
|
|
202
|
+
const entrypoint = functionEntrypoint(target.config);
|
|
203
|
+
if (entrypoint === null) return [];
|
|
204
|
+
const guard = findTopLevelMainGuard(target.source);
|
|
205
|
+
if (guard === null) return [];
|
|
206
|
+
const warning2 = {
|
|
207
|
+
ruleId: "automation-main-guard",
|
|
208
|
+
target,
|
|
209
|
+
severity: "warning",
|
|
210
|
+
line: guard.line,
|
|
211
|
+
snippet: guard.snippet,
|
|
212
|
+
message: `Function automations must not contain a top-level __main__ guard. Lumera invokes "${entrypoint}" automatically; remove the guard.`
|
|
213
|
+
};
|
|
214
|
+
return [warning2];
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// src/lib/lint/rules/collection-schema.ts
|
|
219
|
+
function isRecord3(value) {
|
|
220
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
221
|
+
}
|
|
100
222
|
function error(target, message, snippet) {
|
|
101
223
|
return {
|
|
102
224
|
ruleId: "collection-schema",
|
|
@@ -118,7 +240,7 @@ var collectionSchemaRule = {
|
|
|
118
240
|
const message = err instanceof Error ? err.message : String(err);
|
|
119
241
|
return [error(target, `Invalid JSON: ${message}`)];
|
|
120
242
|
}
|
|
121
|
-
if (!
|
|
243
|
+
if (!isRecord3(parsed)) {
|
|
122
244
|
return [error(target, "Collection file must contain a JSON object.")];
|
|
123
245
|
}
|
|
124
246
|
const issues = [];
|
|
@@ -131,7 +253,7 @@ var collectionSchemaRule = {
|
|
|
131
253
|
const localFieldTypes = /* @__PURE__ */ new Map();
|
|
132
254
|
if (Array.isArray(fields)) {
|
|
133
255
|
for (const field of fields) {
|
|
134
|
-
if (
|
|
256
|
+
if (isRecord3(field) && typeof field.name === "string") {
|
|
135
257
|
localFieldTypes.set(field.name, field.type);
|
|
136
258
|
}
|
|
137
259
|
}
|
|
@@ -155,7 +277,7 @@ var collectionSchemaRule = {
|
|
|
155
277
|
} else {
|
|
156
278
|
for (let i = 0; i < fields.length; i++) {
|
|
157
279
|
const field = fields[i];
|
|
158
|
-
if (!
|
|
280
|
+
if (!isRecord3(field)) {
|
|
159
281
|
issues.push(error(target, `Field at index ${i} must be an object.`));
|
|
160
282
|
continue;
|
|
161
283
|
}
|
|
@@ -176,7 +298,7 @@ var collectionSchemaRule = {
|
|
|
176
298
|
} else {
|
|
177
299
|
for (let i = 0; i < indexes.length; i++) {
|
|
178
300
|
const index = indexes[i];
|
|
179
|
-
if (!
|
|
301
|
+
if (!isRecord3(index)) {
|
|
180
302
|
const hint = typeof index === "string" ? ' Indexes are objects like { "fields": ["title"], "unique": true }, not CREATE INDEX SQL strings.' : "";
|
|
181
303
|
issues.push(error(target, `Index at index ${i} must be an object.${hint}`));
|
|
182
304
|
continue;
|
|
@@ -193,7 +315,7 @@ var collectionSchemaRule = {
|
|
|
193
315
|
}
|
|
194
316
|
}
|
|
195
317
|
if (search !== void 0) {
|
|
196
|
-
if (!
|
|
318
|
+
if (!isRecord3(search)) {
|
|
197
319
|
issues.push(error(target, 'Optional field "search" must be an object when present.'));
|
|
198
320
|
} else {
|
|
199
321
|
const unknownSearchKeys = Object.keys(search).filter((key) => key !== "fulltext" && key !== "semantic");
|
|
@@ -205,14 +327,14 @@ var collectionSchemaRule = {
|
|
|
205
327
|
if (fulltext === void 0 && semantic === void 0) {
|
|
206
328
|
issues.push(error(target, '"search" must include "fulltext" or "semantic".'));
|
|
207
329
|
}
|
|
208
|
-
if (fulltext !== void 0 && !
|
|
330
|
+
if (fulltext !== void 0 && !isRecord3(fulltext)) {
|
|
209
331
|
issues.push(error(target, '"search.fulltext" must be an object when present.'));
|
|
210
|
-
} else if (
|
|
332
|
+
} else if (isRecord3(fulltext)) {
|
|
211
333
|
const allowed = /* @__PURE__ */ new Set(["fields", "language", "fuzzy_fields"]);
|
|
212
334
|
for (const key of Object.keys(fulltext).filter((key2) => !allowed.has(key2))) {
|
|
213
335
|
issues.push(error(target, `Unknown search.fulltext option "${key}".`));
|
|
214
336
|
}
|
|
215
|
-
if (!
|
|
337
|
+
if (!isRecord3(fulltext.fields) || Object.keys(fulltext.fields).length === 0) {
|
|
216
338
|
issues.push(error(target, '"search.fulltext.fields" must be a non-empty field-to-weight object.'));
|
|
217
339
|
} else {
|
|
218
340
|
if (Object.keys(fulltext.fields).length > 8) {
|
|
@@ -244,9 +366,9 @@ var collectionSchemaRule = {
|
|
|
244
366
|
}
|
|
245
367
|
}
|
|
246
368
|
}
|
|
247
|
-
if (semantic !== void 0 && !
|
|
369
|
+
if (semantic !== void 0 && !isRecord3(semantic)) {
|
|
248
370
|
issues.push(error(target, '"search.semantic" must be an object when present.'));
|
|
249
|
-
} else if (
|
|
371
|
+
} else if (isRecord3(semantic)) {
|
|
250
372
|
const allowed = /* @__PURE__ */ new Set(["fields", "model"]);
|
|
251
373
|
for (const key of Object.keys(semantic).filter((key2) => !allowed.has(key2))) {
|
|
252
374
|
issues.push(error(target, `Unknown search.semantic option "${key}".`));
|
|
@@ -653,7 +775,13 @@ var llmImportRule = {
|
|
|
653
775
|
};
|
|
654
776
|
|
|
655
777
|
// src/lib/lint/registry.ts
|
|
656
|
-
var ALL_RULES = [
|
|
778
|
+
var ALL_RULES = [
|
|
779
|
+
automationConfigRule,
|
|
780
|
+
automationMainGuardRule,
|
|
781
|
+
collectionSchemaRule,
|
|
782
|
+
hookVerifyRule,
|
|
783
|
+
llmImportRule
|
|
784
|
+
];
|
|
657
785
|
|
|
658
786
|
// src/lib/lint/format.ts
|
|
659
787
|
import { relative } from "path";
|
|
@@ -1159,6 +1287,8 @@ ${pc2.dim("Resources:")}
|
|
|
1159
1287
|
hooks Plan only hooks
|
|
1160
1288
|
agents Plan only agents
|
|
1161
1289
|
agents/<name> Plan single agent
|
|
1290
|
+
skills Plan only project skills
|
|
1291
|
+
skills/<slug> Plan single project skill
|
|
1162
1292
|
mailboxes Plan only mailboxes
|
|
1163
1293
|
mailboxes/<slug> Plan single mailbox
|
|
1164
1294
|
app Plan app deployment
|
|
@@ -1187,6 +1317,8 @@ ${pc2.dim("Resources:")}
|
|
|
1187
1317
|
hooks Apply only hooks
|
|
1188
1318
|
agents Apply only agents
|
|
1189
1319
|
agents/<name> Apply single agent
|
|
1320
|
+
skills Apply only project skills
|
|
1321
|
+
skills/<slug> Apply single project skill
|
|
1190
1322
|
mailboxes Apply only mailboxes
|
|
1191
1323
|
mailboxes/<slug> Apply single mailbox
|
|
1192
1324
|
app Deploy the frontend app
|
|
@@ -1231,6 +1363,8 @@ ${pc2.dim("Resources:")}
|
|
|
1231
1363
|
hooks Pull only hooks
|
|
1232
1364
|
agents Pull only agents
|
|
1233
1365
|
agents/<name> Pull single agent
|
|
1366
|
+
skills Pull only project skills
|
|
1367
|
+
skills/<slug> Pull single project skill
|
|
1234
1368
|
mailboxes Pull only mailboxes
|
|
1235
1369
|
mailboxes/<slug> Pull single mailbox
|
|
1236
1370
|
|
|
@@ -1258,6 +1392,8 @@ ${pc2.dim("Resources:")}
|
|
|
1258
1392
|
hooks Destroy only hooks
|
|
1259
1393
|
agents Destroy only agents
|
|
1260
1394
|
agents/<name> Destroy single agent
|
|
1395
|
+
skills Destroy only project skills
|
|
1396
|
+
skills/<slug> Destroy single project skill
|
|
1261
1397
|
app Delete app registration
|
|
1262
1398
|
|
|
1263
1399
|
${pc2.dim("Options:")}
|
|
@@ -1284,6 +1420,7 @@ ${pc2.dim("Types:")}
|
|
|
1284
1420
|
automations List only automations
|
|
1285
1421
|
hooks List only hooks
|
|
1286
1422
|
agents List only agents
|
|
1423
|
+
skills List only project skills
|
|
1287
1424
|
mailboxes List only mailboxes
|
|
1288
1425
|
|
|
1289
1426
|
${pc2.dim("Options:")}
|
|
@@ -1308,6 +1445,7 @@ ${pc2.dim("Resources:")}
|
|
|
1308
1445
|
automations/<name> Show automation details
|
|
1309
1446
|
hooks/<name> Show hook details
|
|
1310
1447
|
agents/<name> Show agent details
|
|
1448
|
+
skills/<slug> Show project skill details
|
|
1311
1449
|
mailboxes/<slug> Show mailbox details
|
|
1312
1450
|
app Show app details
|
|
1313
1451
|
|
|
@@ -1324,7 +1462,7 @@ function parseResource(resourcePath) {
|
|
|
1324
1462
|
const parts = resourcePath.split("/");
|
|
1325
1463
|
const type = parts[0];
|
|
1326
1464
|
const name = parts.slice(1).join("/") || null;
|
|
1327
|
-
const validTypes = ["collections", "automations", "hooks", "agents", "mailboxes", "app"];
|
|
1465
|
+
const validTypes = ["collections", "automations", "hooks", "agents", "skills", "mailboxes", "app"];
|
|
1328
1466
|
if (!validTypes.includes(type)) {
|
|
1329
1467
|
console.log(pc2.red(` Unknown resource type "${type}". Valid types: ${validTypes.join(", ")}`));
|
|
1330
1468
|
process.exit(1);
|
|
@@ -2347,6 +2485,246 @@ ${hook.script.split("\n").map((line) => " " + line).join("\n")}
|
|
|
2347
2485
|
console.log(pc2.green(" \u2713"), `${hook.name} \u2192 hooks/${fileName}`);
|
|
2348
2486
|
}
|
|
2349
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, pendingLocalSkillSlugs = /* @__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 && pendingLocalSkillSlugs.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
|
+
}
|
|
2350
2728
|
function loadLocalAgents(platformDir, filterName, appName) {
|
|
2351
2729
|
const agentsDir = join2(platformDir, "agents");
|
|
2352
2730
|
if (!existsSync2(agentsDir)) return [];
|
|
@@ -2417,44 +2795,38 @@ function loadLocalAgents(platformDir, filterName, appName) {
|
|
|
2417
2795
|
}
|
|
2418
2796
|
return agents;
|
|
2419
2797
|
}
|
|
2420
|
-
async function planAgents(api, localAgents, projectId) {
|
|
2798
|
+
async function planAgents(api, localAgents, projectId, localSkillSlugs = /* @__PURE__ */ new Set(), appName) {
|
|
2421
2799
|
const changes = [];
|
|
2422
2800
|
const remoteAgents = await api.listAgents(projectId ? { project_id: projectId } : void 0);
|
|
2423
2801
|
const remoteByExternalId = new Map(
|
|
2424
2802
|
remoteAgents.filter((a) => a.external_id && !a.managed).map((a) => [a.external_id, a])
|
|
2425
2803
|
);
|
|
2426
|
-
const refToId = /* @__PURE__ */ new Map();
|
|
2427
|
-
const skillIdToSlug = /* @__PURE__ */ new Map();
|
|
2428
|
-
const blockedRefs = /* @__PURE__ */ new Set();
|
|
2429
2804
|
const planRefs = [
|
|
2430
2805
|
.../* @__PURE__ */ new Set([
|
|
2431
2806
|
...localAgents.flatMap((a) => a.agent.skills ?? []),
|
|
2432
2807
|
...remoteAgents.flatMap((a) => a.skill_ids ?? [])
|
|
2433
2808
|
])
|
|
2434
2809
|
];
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
blockedRefs.add(s.id);
|
|
2446
|
-
}
|
|
2447
|
-
}
|
|
2448
|
-
} catch (e) {
|
|
2449
|
-
console.log(pc2.yellow(` \u26A0 Could not resolve skills for plan: ${e}`));
|
|
2450
|
-
}
|
|
2810
|
+
let resolution = {
|
|
2811
|
+
refToId: /* @__PURE__ */ new Map(),
|
|
2812
|
+
skillIdToSlug: /* @__PURE__ */ new Map(),
|
|
2813
|
+
blockedRefs: /* @__PURE__ */ new Set(),
|
|
2814
|
+
outsideProjectRefs: /* @__PURE__ */ new Set()
|
|
2815
|
+
};
|
|
2816
|
+
try {
|
|
2817
|
+
resolution = await resolveAgentSkillRefs(api, planRefs, projectId, appName, localSkillSlugs);
|
|
2818
|
+
} catch (e) {
|
|
2819
|
+
console.log(pc2.yellow(` \u26A0 Could not resolve skills for plan: ${e}`));
|
|
2451
2820
|
}
|
|
2821
|
+
const { refToId, skillIdToSlug, blockedRefs, outsideProjectRefs } = resolution;
|
|
2452
2822
|
for (const { agent, systemPrompt, policyScript } of localAgents) {
|
|
2453
2823
|
const remote = remoteByExternalId.get(agent.external_id);
|
|
2454
2824
|
for (const ref of agent.skills ?? []) {
|
|
2455
|
-
if (
|
|
2825
|
+
if (outsideProjectRefs.has(ref)) {
|
|
2826
|
+
console.log(pc2.red(` \u2717 Skill "${ref}" belongs to another project \u2014 apply will fail for this agent`));
|
|
2827
|
+
} else if (blockedRefs.has(ref)) {
|
|
2456
2828
|
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`));
|
|
2457
|
-
} else if (!refToId.has(ref)) {
|
|
2829
|
+
} else if (!refToId.has(ref) && !localSkillSlugs.has(ref)) {
|
|
2458
2830
|
console.log(pc2.red(` \u2717 Skill "${ref}" not found \u2014 apply will fail for this agent`));
|
|
2459
2831
|
}
|
|
2460
2832
|
}
|
|
@@ -2472,12 +2844,14 @@ async function planAgents(api, localAgents, projectId) {
|
|
|
2472
2844
|
if ((remote.policy_enabled || false) !== (agent.policy_enabled || false)) diffs.push("policy_enabled");
|
|
2473
2845
|
if ((remote.policy_description || "") !== (agent.policy_description || "")) diffs.push("policy_description");
|
|
2474
2846
|
const localSkillIds = (agent.skills || []).map((s) => refToId.get(s)).filter((id) => !!id).sort();
|
|
2847
|
+
const pendingLocalSkillSlugs = (agent.skills || []).filter((ref) => localSkillSlugs.has(ref) && !refToId.has(ref));
|
|
2475
2848
|
const remoteSkillIds = [...remote.skill_ids || []].sort();
|
|
2476
|
-
if (localSkillIds.join(",") !== remoteSkillIds.join(",")) {
|
|
2849
|
+
if (localSkillIds.join(",") !== remoteSkillIds.join(",") || pendingLocalSkillSlugs.length > 0) {
|
|
2477
2850
|
const addedSlugs = localSkillIds.filter((id) => !remoteSkillIds.includes(id)).map((id) => skillIdToSlug.get(id) || id);
|
|
2478
2851
|
const removedSlugs = remoteSkillIds.filter((id) => !localSkillIds.includes(id)).map((id) => skillIdToSlug.get(id) || id);
|
|
2479
2852
|
const parts = [];
|
|
2480
2853
|
if (addedSlugs.length) parts.push(`+${addedSlugs.join(", +")}`);
|
|
2854
|
+
if (pendingLocalSkillSlugs.length) parts.push(`+${pendingLocalSkillSlugs.join(", +")}`);
|
|
2481
2855
|
if (removedSlugs.length) parts.push(`-${removedSlugs.join(", -")}`);
|
|
2482
2856
|
diffs.push(`skills (${parts.join(", ")})`);
|
|
2483
2857
|
}
|
|
@@ -2500,34 +2874,29 @@ function isAvailableToCustomAgents(skill) {
|
|
|
2500
2874
|
if (!skill.managed) return true;
|
|
2501
2875
|
return (skill.tags ?? []).includes(TAG_AVAILABLE_TO_CUSTOM_AGENTS);
|
|
2502
2876
|
}
|
|
2503
|
-
async function applyAgents(api, localAgents, projectId) {
|
|
2877
|
+
async function applyAgents(api, localAgents, projectId, localSkillSlugs = /* @__PURE__ */ new Set(), appName) {
|
|
2504
2878
|
let errors = 0;
|
|
2505
2879
|
const remoteAgents = await api.listAgents(projectId ? { project_id: projectId } : void 0);
|
|
2506
2880
|
const remoteByExternalId = new Map(
|
|
2507
2881
|
remoteAgents.filter((a) => a.external_id && !a.managed).map((a) => [a.external_id, a])
|
|
2508
2882
|
);
|
|
2509
|
-
const refToId = /* @__PURE__ */ new Map();
|
|
2510
|
-
const blockedRefs = /* @__PURE__ */ new Set();
|
|
2511
2883
|
const refs = [...new Set(localAgents.flatMap((a) => a.agent.skills ?? []))];
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
}
|
|
2523
|
-
}
|
|
2524
|
-
} catch (e) {
|
|
2525
|
-
console.log(pc2.yellow(` \u26A0 Could not resolve skills: ${e}`));
|
|
2526
|
-
}
|
|
2884
|
+
let resolution = {
|
|
2885
|
+
refToId: /* @__PURE__ */ new Map(),
|
|
2886
|
+
skillIdToSlug: /* @__PURE__ */ new Map(),
|
|
2887
|
+
blockedRefs: /* @__PURE__ */ new Set(),
|
|
2888
|
+
outsideProjectRefs: /* @__PURE__ */ new Set()
|
|
2889
|
+
};
|
|
2890
|
+
try {
|
|
2891
|
+
resolution = await resolveAgentSkillRefs(api, refs, projectId, appName, localSkillSlugs);
|
|
2892
|
+
} catch (e) {
|
|
2893
|
+
console.log(pc2.yellow(` \u26A0 Could not resolve skills: ${e}`));
|
|
2527
2894
|
}
|
|
2895
|
+
const { refToId, blockedRefs, outsideProjectRefs } = resolution;
|
|
2528
2896
|
for (const { agent, systemPrompt, policyScript } of localAgents) {
|
|
2529
2897
|
const remote = remoteByExternalId.get(agent.external_id);
|
|
2530
2898
|
const skillIds = [];
|
|
2899
|
+
const outsideProjectForAgent = [];
|
|
2531
2900
|
const blockedForAgent = [];
|
|
2532
2901
|
const notFoundForAgent = [];
|
|
2533
2902
|
if (agent.skills) {
|
|
@@ -2535,6 +2904,8 @@ async function applyAgents(api, localAgents, projectId) {
|
|
|
2535
2904
|
const id = refToId.get(ref);
|
|
2536
2905
|
if (id) {
|
|
2537
2906
|
skillIds.push(id);
|
|
2907
|
+
} else if (outsideProjectRefs.has(ref)) {
|
|
2908
|
+
outsideProjectForAgent.push(ref);
|
|
2538
2909
|
} else if (blockedRefs.has(ref)) {
|
|
2539
2910
|
blockedForAgent.push(ref);
|
|
2540
2911
|
} else {
|
|
@@ -2542,8 +2913,11 @@ async function applyAgents(api, localAgents, projectId) {
|
|
|
2542
2913
|
}
|
|
2543
2914
|
}
|
|
2544
2915
|
}
|
|
2545
|
-
if (blockedForAgent.length > 0 || notFoundForAgent.length > 0) {
|
|
2916
|
+
if (outsideProjectForAgent.length > 0 || blockedForAgent.length > 0 || notFoundForAgent.length > 0) {
|
|
2546
2917
|
console.log(pc2.red(" \u2717"), `${agent.name}:`);
|
|
2918
|
+
if (outsideProjectForAgent.length > 0) {
|
|
2919
|
+
console.log(pc2.red(` skills outside the agent project: ${outsideProjectForAgent.join(", ")}`));
|
|
2920
|
+
}
|
|
2547
2921
|
if (blockedForAgent.length > 0) {
|
|
2548
2922
|
console.log(pc2.red(` skills not available for custom agents: ${blockedForAgent.join(", ")}`));
|
|
2549
2923
|
}
|
|
@@ -2583,7 +2957,7 @@ async function applyAgents(api, localAgents, projectId) {
|
|
|
2583
2957
|
}
|
|
2584
2958
|
return errors;
|
|
2585
2959
|
}
|
|
2586
|
-
async function pullAgents(api, platformDir, filterName, projectId) {
|
|
2960
|
+
async function pullAgents(api, platformDir, filterName, projectId, appName) {
|
|
2587
2961
|
const agentsDir = join2(platformDir, "agents");
|
|
2588
2962
|
mkdirSync(agentsDir, { recursive: true });
|
|
2589
2963
|
const agents = await api.listAgents(projectId ? { project_id: projectId } : void 0);
|
|
@@ -2592,7 +2966,9 @@ async function pullAgents(api, platformDir, filterName, projectId) {
|
|
|
2592
2966
|
if (pullRefs.length > 0) {
|
|
2593
2967
|
try {
|
|
2594
2968
|
const skills = await api.lookupAgentSkills(pullRefs);
|
|
2595
|
-
for (const s of skills)
|
|
2969
|
+
for (const s of skills) {
|
|
2970
|
+
skillIdToSlug.set(s.id, s.project_id === projectId ? localProjectSkillSlug(appName, s.slug) : s.slug);
|
|
2971
|
+
}
|
|
2596
2972
|
} catch (e) {
|
|
2597
2973
|
console.log(pc2.yellow(` \u26A0 Could not resolve skills for pull: ${e}`));
|
|
2598
2974
|
console.log(pc2.yellow(` Pulled configs may omit skills \u2014 review before re-applying.`));
|
|
@@ -2747,6 +3123,28 @@ async function listResources(api, platformDir, filterType, appName, projectId) {
|
|
|
2747
3123
|
}
|
|
2748
3124
|
}
|
|
2749
3125
|
}
|
|
3126
|
+
if (!filterType || filterType === "skills") {
|
|
3127
|
+
const localSkills = loadLocalSkills(platformDir);
|
|
3128
|
+
if (localSkills.length > 0) requireProjectSkillsProjectID(projectId);
|
|
3129
|
+
const remoteSkills = projectId ? await api.listAgentSkills({ project_id: projectId }) : [];
|
|
3130
|
+
const remoteBySlug = remoteSkillsByLocalSlug(remoteSkills, appName);
|
|
3131
|
+
const localSlugs = new Set(localSkills.map((skill) => skill.slug));
|
|
3132
|
+
for (const skill of localSkills) {
|
|
3133
|
+
const remote = remoteBySlug.get(skill.slug);
|
|
3134
|
+
if (!remote) {
|
|
3135
|
+
results.push({ name: skill.name, type: "skills", status: "local-only" });
|
|
3136
|
+
} else if (remote.name !== skill.name || (remote.summary || "").trim() !== skill.summary.trim() || (remote.instructions || "").trim() !== skill.instructions.trim()) {
|
|
3137
|
+
results.push({ name: skill.name, type: "skills", status: "changed", details: "SKILL.md" });
|
|
3138
|
+
} else {
|
|
3139
|
+
results.push({ name: skill.name, type: "skills", status: "synced" });
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
for (const remote of remoteSkills) {
|
|
3143
|
+
if (!localSlugs.has(localProjectSkillSlug(appName, remote.slug))) {
|
|
3144
|
+
results.push({ name: remote.name, type: "skills", status: "remote-only" });
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
2750
3148
|
if (!filterType || filterType === "mailboxes") {
|
|
2751
3149
|
const localMailboxes = loadLocalMailboxes(platformDir);
|
|
2752
3150
|
const remoteMailboxes = await api.listMailboxes();
|
|
@@ -2894,6 +3292,20 @@ async function destroyResources(api, platformDir, resourceType, resourceName, sk
|
|
|
2894
3292
|
} catch {
|
|
2895
3293
|
}
|
|
2896
3294
|
}
|
|
3295
|
+
if (!resourceType || resourceType === "skills") {
|
|
3296
|
+
const localSkills = loadLocalSkills(platformDir, resourceName || void 0);
|
|
3297
|
+
if (localSkills.length > 0) {
|
|
3298
|
+
const scopedProjectID = requireProjectSkillsProjectID(projectId);
|
|
3299
|
+
const remoteSkills = await api.listAgentSkills({ project_id: scopedProjectID });
|
|
3300
|
+
const remoteBySlug = remoteSkillsByLocalSlug(remoteSkills, appName);
|
|
3301
|
+
for (const skill of localSkills) {
|
|
3302
|
+
const remote = remoteBySlug.get(skill.slug);
|
|
3303
|
+
if (remote) {
|
|
3304
|
+
toDelete.push({ type: "skill", id: skill.slug, name: skill.name, remoteId: remote.id });
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
2897
3309
|
if (toDelete.length === 0) {
|
|
2898
3310
|
console.log(pc2.green(" \u2713 No resources found to delete"));
|
|
2899
3311
|
return;
|
|
@@ -2917,6 +3329,7 @@ async function destroyResources(api, platformDir, resourceType, resourceName, sk
|
|
|
2917
3329
|
}
|
|
2918
3330
|
}
|
|
2919
3331
|
const agents = toDelete.filter((r) => r.type === "agent");
|
|
3332
|
+
const skills = toDelete.filter((r) => r.type === "skill");
|
|
2920
3333
|
const hooks = toDelete.filter((r) => r.type === "hook");
|
|
2921
3334
|
const automations = toDelete.filter((r) => r.type === "automation");
|
|
2922
3335
|
const collections = toDelete.filter((r) => r.type === "collection");
|
|
@@ -2930,6 +3343,15 @@ async function destroyResources(api, platformDir, resourceType, resourceName, sk
|
|
|
2930
3343
|
errors++;
|
|
2931
3344
|
}
|
|
2932
3345
|
}
|
|
3346
|
+
for (const resource of skills) {
|
|
3347
|
+
try {
|
|
3348
|
+
await api.deleteAgentSkill(resource.remoteId);
|
|
3349
|
+
console.log(pc2.green(" \u2713"), `Deleted skill: ${resource.name}`);
|
|
3350
|
+
} catch (e) {
|
|
3351
|
+
console.log(pc2.red(" \u2717"), `Failed to delete skill ${resource.name}: ${e}`);
|
|
3352
|
+
errors++;
|
|
3353
|
+
}
|
|
3354
|
+
}
|
|
2933
3355
|
for (const resource of hooks) {
|
|
2934
3356
|
try {
|
|
2935
3357
|
await api.deleteHook(resource.remoteId);
|
|
@@ -3186,6 +3608,31 @@ async function showResource(api, platformDir, resourceType, resourceName, appNam
|
|
|
3186
3608
|
console.log(` Skills: ${skills.join(", ")}`);
|
|
3187
3609
|
}
|
|
3188
3610
|
console.log();
|
|
3611
|
+
} else if (resourceType === "skills") {
|
|
3612
|
+
const localSkills = loadLocalSkills(platformDir, resourceName);
|
|
3613
|
+
if (localSkills.length > 0) requireProjectSkillsProjectID(projectId);
|
|
3614
|
+
const remoteSkills = projectId ? await api.listAgentSkills({ project_id: projectId }) : [];
|
|
3615
|
+
const local = localSkills[0];
|
|
3616
|
+
const remote = remoteSkillsByLocalSlug(remoteSkills, appName).get(resourceName) || remoteSkills.find((skill) => skill.name === resourceName);
|
|
3617
|
+
if (!local && !remote) {
|
|
3618
|
+
console.log(pc2.red(` Skill "${resourceName}" not found`));
|
|
3619
|
+
process.exit(1);
|
|
3620
|
+
}
|
|
3621
|
+
console.log();
|
|
3622
|
+
console.log(pc2.bold(` Skill: ${local?.name || remote?.name}`));
|
|
3623
|
+
console.log();
|
|
3624
|
+
if (local && remote) {
|
|
3625
|
+
const changed = local.name !== remote.name || local.summary.trim() !== (remote.summary || "").trim() || local.instructions.trim() !== (remote.instructions || "").trim();
|
|
3626
|
+
console.log(` Status: ${changed ? pc2.yellow("changed") : pc2.green("synced")}`);
|
|
3627
|
+
} else if (local) {
|
|
3628
|
+
console.log(` Status: ${pc2.yellow("local only")}`);
|
|
3629
|
+
} else {
|
|
3630
|
+
console.log(` Status: ${pc2.cyan("remote only")}`);
|
|
3631
|
+
}
|
|
3632
|
+
console.log(` Slug: ${local?.slug || (remote ? localProjectSkillSlug(appName, remote.slug) : "")}`);
|
|
3633
|
+
const summary = local?.summary || remote?.summary;
|
|
3634
|
+
if (summary) console.log(` Summary: ${summary}`);
|
|
3635
|
+
console.log();
|
|
3189
3636
|
} else if (resourceType === "mailboxes") {
|
|
3190
3637
|
const localMailboxes = loadLocalMailboxes(platformDir, resourceName);
|
|
3191
3638
|
const remoteMailboxes = await api.listMailboxes();
|
|
@@ -3302,10 +3749,15 @@ async function plan(args) {
|
|
|
3302
3749
|
allChanges.push(...changes);
|
|
3303
3750
|
}
|
|
3304
3751
|
}
|
|
3752
|
+
const localSkills = !type || type === "skills" ? loadLocalSkills(platformDir, name || void 0) : [];
|
|
3753
|
+
const localSkillSlugs = new Set(!type || type === "skills" || type === "agents" ? (localSkills.length > 0 || type !== "agents" ? localSkills : loadLocalSkills(platformDir)).map((skill) => skill.slug) : []);
|
|
3754
|
+
if (localSkills.length > 0) {
|
|
3755
|
+
allChanges.push(...await planSkills(api, localSkills, projectId, appName));
|
|
3756
|
+
}
|
|
3305
3757
|
if (!type || type === "agents") {
|
|
3306
3758
|
const localAgents = loadLocalAgents(platformDir, name || void 0, appName);
|
|
3307
3759
|
if (localAgents.length > 0) {
|
|
3308
|
-
const changes = await planAgents(api, localAgents, projectId);
|
|
3760
|
+
const changes = await planAgents(api, localAgents, projectId, localSkillSlugs, appName);
|
|
3309
3761
|
allChanges.push(...changes);
|
|
3310
3762
|
}
|
|
3311
3763
|
}
|
|
@@ -3428,15 +3880,18 @@ async function apply(args) {
|
|
|
3428
3880
|
const localCollections = !type || type === "collections" ? loadLocalCollections(platformDir, name || void 0) : [];
|
|
3429
3881
|
const localAutomations = !type || type === "automations" ? loadLocalAutomations(platformDir, name || void 0, appName) : [];
|
|
3430
3882
|
const localHooks = !type || type === "hooks" ? loadLocalHooks(platformDir, name || void 0, appName) : [];
|
|
3883
|
+
const localSkills = !type || type === "skills" ? loadLocalSkills(platformDir, name || void 0) : [];
|
|
3884
|
+
const localSkillSlugs = new Set(!type || type === "skills" || type === "agents" ? (localSkills.length > 0 || type !== "agents" ? localSkills : loadLocalSkills(platformDir)).map((skill) => skill.slug) : []);
|
|
3431
3885
|
const localAgents = !type || type === "agents" ? loadLocalAgents(platformDir, name || void 0, appName) : [];
|
|
3432
3886
|
const localMailboxes = !type || type === "mailboxes" ? loadLocalMailboxes(platformDir, name || void 0) : [];
|
|
3433
3887
|
if (localCollections.length > 0) allChanges.push(...await planCollections(api, localCollections, appName, projectId));
|
|
3434
3888
|
if (localAutomations.length > 0) allChanges.push(...await planAutomations(api, localAutomations, projectId));
|
|
3435
3889
|
if (localHooks.length > 0) allChanges.push(...await planHooks(api, localHooks, collections, projectId));
|
|
3436
|
-
if (
|
|
3890
|
+
if (localSkills.length > 0) allChanges.push(...await planSkills(api, localSkills, projectId, appName));
|
|
3891
|
+
if (localAgents.length > 0) allChanges.push(...await planAgents(api, localAgents, projectId, localSkillSlugs, appName));
|
|
3437
3892
|
if (localMailboxes.length > 0) allChanges.push(...await planMailboxes(api, localMailboxes));
|
|
3438
3893
|
if (name) {
|
|
3439
|
-
const hasLocal = localCollections.length > 0 || localAutomations.length > 0 || localHooks.length > 0 || localAgents.length > 0 || localMailboxes.length > 0;
|
|
3894
|
+
const hasLocal = localCollections.length > 0 || localAutomations.length > 0 || localHooks.length > 0 || localSkills.length > 0 || localAgents.length > 0 || localMailboxes.length > 0;
|
|
3440
3895
|
if (!hasLocal) {
|
|
3441
3896
|
console.log();
|
|
3442
3897
|
console.log(pc2.red(` Resource "${name}" not found locally`));
|
|
@@ -3523,9 +3978,14 @@ async function apply(args) {
|
|
|
3523
3978
|
totalErrors += await applyHooks(api, localHooks, collections, projectId);
|
|
3524
3979
|
console.log();
|
|
3525
3980
|
}
|
|
3981
|
+
if (localSkills.length > 0) {
|
|
3982
|
+
console.log(pc2.bold(" Skills:"));
|
|
3983
|
+
totalErrors += await applySkills(api, localSkills, projectId, appName);
|
|
3984
|
+
console.log();
|
|
3985
|
+
}
|
|
3526
3986
|
if (localAgents.length > 0) {
|
|
3527
3987
|
console.log(pc2.bold(" Agents:"));
|
|
3528
|
-
totalErrors += await applyAgents(api, localAgents, projectId);
|
|
3988
|
+
totalErrors += await applyAgents(api, localAgents, projectId, localSkillSlugs, appName);
|
|
3529
3989
|
console.log();
|
|
3530
3990
|
}
|
|
3531
3991
|
if (localMailboxes.length > 0) {
|
|
@@ -3578,10 +4038,25 @@ async function pull(args) {
|
|
|
3578
4038
|
} catch {
|
|
3579
4039
|
collections = /* @__PURE__ */ new Map();
|
|
3580
4040
|
}
|
|
4041
|
+
if (projectId && (!type || type === "skills")) {
|
|
4042
|
+
const localSkills = loadLocalSkills(platformDir, name || void 0);
|
|
4043
|
+
if (localSkills.length > 0) {
|
|
4044
|
+
const changes = await planSkills(api, localSkills, projectId, appName);
|
|
4045
|
+
for (const change of changes) {
|
|
4046
|
+
if (change.type === "update") conflicts.push(`skills/${change.id}`);
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
}
|
|
3581
4050
|
if (!type || type === "agents") {
|
|
3582
4051
|
const localAgents = loadLocalAgents(platformDir, name || void 0, appName);
|
|
3583
4052
|
if (localAgents.length > 0) {
|
|
3584
|
-
const changes = await planAgents(
|
|
4053
|
+
const changes = await planAgents(
|
|
4054
|
+
api,
|
|
4055
|
+
localAgents,
|
|
4056
|
+
projectId,
|
|
4057
|
+
new Set(loadLocalSkills(platformDir).map((skill) => skill.slug)),
|
|
4058
|
+
appName
|
|
4059
|
+
);
|
|
3585
4060
|
for (const c of changes) {
|
|
3586
4061
|
if (c.type === "update") conflicts.push(`agents/${c.name}`);
|
|
3587
4062
|
}
|
|
@@ -3642,9 +4117,14 @@ async function pull(args) {
|
|
|
3642
4117
|
await pullHooks(api, platformDir, name || void 0, appName, projectId);
|
|
3643
4118
|
console.log();
|
|
3644
4119
|
}
|
|
4120
|
+
if (!type || type === "skills") {
|
|
4121
|
+
console.log(pc2.bold(" Skills:"));
|
|
4122
|
+
await pullSkills(api, platformDir, name || void 0, projectId, appName);
|
|
4123
|
+
console.log();
|
|
4124
|
+
}
|
|
3645
4125
|
if (!type || type === "agents") {
|
|
3646
4126
|
console.log(pc2.bold(" Agents:"));
|
|
3647
|
-
await pullAgents(api, platformDir, name || void 0, projectId);
|
|
4127
|
+
await pullAgents(api, platformDir, name || void 0, projectId, appName);
|
|
3648
4128
|
console.log();
|
|
3649
4129
|
}
|
|
3650
4130
|
if (!type || type === "mailboxes") {
|
|
@@ -3796,6 +4276,7 @@ ${pc2.dim("Usage:")}
|
|
|
3796
4276
|
|
|
3797
4277
|
${pc2.dim("Resources:")}
|
|
3798
4278
|
agents/<name> Diff agent (system_prompt, policy_script)
|
|
4279
|
+
skills/<slug> Diff project skill instructions
|
|
3799
4280
|
automations/<name> Diff automation code
|
|
3800
4281
|
hooks/<name> Diff hook script
|
|
3801
4282
|
|
|
@@ -3944,23 +4425,70 @@ async function diff(args) {
|
|
|
3944
4425
|
} else if (scriptChanged) {
|
|
3945
4426
|
renderFullDiff(local.fileName, remote.script || "", local.script);
|
|
3946
4427
|
}
|
|
4428
|
+
} else if (type === "skills") {
|
|
4429
|
+
const localSkills = loadLocalSkills(platformDir, name);
|
|
4430
|
+
if (localSkills.length > 0) requireProjectSkillsProjectID(projectId);
|
|
4431
|
+
const remoteSkills = projectId ? await api.listAgentSkills({ project_id: projectId }) : [];
|
|
4432
|
+
const local = localSkills[0];
|
|
4433
|
+
const remote = remoteSkillsByLocalSlug(remoteSkills, appName).get(name) || remoteSkills.find((skill) => skill.name === name);
|
|
4434
|
+
if (!local && !remote) {
|
|
4435
|
+
console.log(pc2.red(` Skill "${name}" not found locally or remotely`));
|
|
4436
|
+
process.exit(1);
|
|
4437
|
+
}
|
|
4438
|
+
if (!local) {
|
|
4439
|
+
console.log(pc2.cyan(` Skill "${name}" exists only remotely`));
|
|
4440
|
+
process.exit(0);
|
|
4441
|
+
}
|
|
4442
|
+
if (!remote) {
|
|
4443
|
+
console.log(pc2.yellow(` Skill "${name}" exists only locally (not yet deployed)`));
|
|
4444
|
+
process.exit(0);
|
|
4445
|
+
}
|
|
4446
|
+
console.log(pc2.bold(` Skill: ${local.name}`));
|
|
4447
|
+
console.log();
|
|
4448
|
+
if (remote.name !== local.name) {
|
|
4449
|
+
console.log(` name: ${pc2.red(remote.name)} \u2192 ${pc2.green(local.name)}`);
|
|
4450
|
+
}
|
|
4451
|
+
if ((remote.summary || "").trim() !== local.summary.trim()) {
|
|
4452
|
+
console.log(` summary: ${pc2.red(remote.summary || "(empty)")} \u2192 ${pc2.green(local.summary)}`);
|
|
4453
|
+
}
|
|
4454
|
+
const remoteMarkdown = skillMarkdown({
|
|
4455
|
+
slug: localProjectSkillSlug(appName, remote.slug),
|
|
4456
|
+
name: remote.name,
|
|
4457
|
+
summary: remote.summary || "",
|
|
4458
|
+
instructions: remote.instructions || ""
|
|
4459
|
+
});
|
|
4460
|
+
const localMarkdown = skillMarkdown(local);
|
|
4461
|
+
if (remoteMarkdown === localMarkdown) {
|
|
4462
|
+
console.log(pc2.green(" \u2713 No changes"));
|
|
4463
|
+
} else {
|
|
4464
|
+
renderFullDiff("SKILL.md", remoteMarkdown, localMarkdown);
|
|
4465
|
+
}
|
|
3947
4466
|
} else {
|
|
3948
|
-
console.log(pc2.red(` Diff not supported for "${type}" \u2014 use agents, automations, or hooks`));
|
|
4467
|
+
console.log(pc2.red(` Diff not supported for "${type}" \u2014 use agents, skills, automations, or hooks`));
|
|
3949
4468
|
process.exit(1);
|
|
3950
4469
|
}
|
|
3951
4470
|
console.log();
|
|
3952
4471
|
}
|
|
3953
4472
|
export {
|
|
3954
4473
|
apply,
|
|
4474
|
+
applyAgents,
|
|
3955
4475
|
applyCollections,
|
|
4476
|
+
applySkills,
|
|
3956
4477
|
convertCollectionToApiFormat,
|
|
3957
4478
|
destroy,
|
|
3958
4479
|
diff,
|
|
3959
4480
|
list,
|
|
3960
4481
|
loadLocalCollections,
|
|
4482
|
+
loadLocalSkills,
|
|
4483
|
+
localProjectSkillSlug,
|
|
3961
4484
|
plan,
|
|
3962
4485
|
planCollections,
|
|
4486
|
+
planSkills,
|
|
4487
|
+
projectSkillRefCandidates,
|
|
3963
4488
|
pull,
|
|
3964
4489
|
pullCollections,
|
|
4490
|
+
pullSkills,
|
|
4491
|
+
resolveAgentSkillRefs,
|
|
4492
|
+
scopedProjectSkillSlug,
|
|
3965
4493
|
show
|
|
3966
4494
|
};
|
package/package.json
CHANGED
|
File without changes
|