@assemblyline-agents/cli 5.0.9 → 6.0.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/authoring/skills/assembly-line-authoring/SKILL.md +24 -21
- package/dist/add.d.ts +2 -13
- package/dist/add.d.ts.map +1 -1
- package/dist/add.js +169 -478
- package/dist/add.js.map +1 -1
- package/dist/args.d.ts.map +1 -1
- package/dist/args.js +1 -2
- package/dist/args.js.map +1 -1
- package/dist/auth.d.ts +1 -2
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +38 -45
- package/dist/auth.js.map +1 -1
- package/dist/channels.d.ts +7 -30
- package/dist/channels.d.ts.map +1 -1
- package/dist/channels.js +21 -161
- package/dist/channels.js.map +1 -1
- package/dist/deploy-definition.d.ts +1 -8
- package/dist/deploy-definition.d.ts.map +1 -1
- package/dist/deploy-definition.js +18 -66
- package/dist/deploy-definition.js.map +1 -1
- package/dist/deploy-migrations.d.ts +15 -0
- package/dist/deploy-migrations.d.ts.map +1 -0
- package/dist/deploy-migrations.js +72 -0
- package/dist/deploy-migrations.js.map +1 -0
- package/dist/deploy-preflight.d.ts +6 -0
- package/dist/deploy-preflight.d.ts.map +1 -0
- package/dist/deploy-preflight.js +40 -0
- package/dist/deploy-preflight.js.map +1 -0
- package/dist/deploy-receipts.d.ts +9 -0
- package/dist/deploy-receipts.d.ts.map +1 -0
- package/dist/deploy-receipts.js +45 -0
- package/dist/deploy-receipts.js.map +1 -0
- package/dist/deploy-sandbox-environments.d.ts +6 -0
- package/dist/deploy-sandbox-environments.d.ts.map +1 -0
- package/dist/deploy-sandbox-environments.js +109 -0
- package/dist/deploy-sandbox-environments.js.map +1 -0
- package/dist/deploy-secrets.d.ts +21 -0
- package/dist/deploy-secrets.d.ts.map +1 -0
- package/dist/deploy-secrets.js +83 -0
- package/dist/deploy-secrets.js.map +1 -0
- package/dist/deploy.d.ts +5 -31
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +50 -636
- package/dist/deploy.js.map +1 -1
- package/dist/eval-case-runner.d.ts +1 -1
- package/dist/eval-case-runner.d.ts.map +1 -1
- package/dist/eval-case-runner.js.map +1 -1
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +41 -89
- package/dist/help.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +89 -32
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +1 -3
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +36 -39
- package/dist/init.js.map +1 -1
- package/dist/inspection.d.ts +44 -0
- package/dist/inspection.d.ts.map +1 -0
- package/dist/inspection.js +144 -0
- package/dist/inspection.js.map +1 -0
- package/dist/models.d.ts +2 -1
- package/dist/models.d.ts.map +1 -1
- package/dist/models.js +7 -13
- package/dist/models.js.map +1 -1
- package/dist/plugin-add.d.ts +36 -0
- package/dist/plugin-add.d.ts.map +1 -0
- package/dist/plugin-add.js +283 -0
- package/dist/plugin-add.js.map +1 -0
- package/dist/plugin-init.d.ts +2 -0
- package/dist/plugin-init.d.ts.map +1 -0
- package/dist/plugin-init.js +34 -0
- package/dist/plugin-init.js.map +1 -0
- package/dist/provider-deploy.d.ts +7 -0
- package/dist/provider-deploy.d.ts.map +1 -0
- package/dist/provider-deploy.js +163 -0
- package/dist/provider-deploy.js.map +1 -0
- package/dist/secrets.d.ts.map +1 -1
- package/dist/secrets.js +8 -28
- package/dist/secrets.js.map +1 -1
- package/package.json +9 -11
- package/dist/hosts.d.ts +0 -3
- package/dist/hosts.d.ts.map +0 -1
- package/dist/hosts.js +0 -60
- package/dist/hosts.js.map +0 -1
- package/dist/model-providers.d.ts +0 -5
- package/dist/model-providers.d.ts.map +0 -1
- package/dist/model-providers.js +0 -17
- package/dist/model-providers.js.map +0 -1
- package/dist/state.d.ts +0 -3
- package/dist/state.d.ts.map +0 -1
- package/dist/state.js +0 -79
- package/dist/state.js.map +0 -1
package/dist/add.js
CHANGED
|
@@ -1,501 +1,192 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
import { detectPackageManager } from "./pm.js";
|
|
7
|
-
const GATEWAY_ROLES = new Set(["deploy", "runtime", "state", "blob", "sandbox", "scheduler", "media", "secrets"]);
|
|
8
|
-
/** Channel scaffold helpers, verified against each channel package's exports. */
|
|
9
|
-
const CHANNEL_SCAFFOLDS = {
|
|
10
|
-
slack: { helper: "defineSlackChannel", packageName: "@assemblyline-agents/slack" },
|
|
11
|
-
discord: { helper: "defineDiscordChannel", packageName: "@assemblyline-agents/discord" },
|
|
12
|
-
telegram: { helper: "defineTelegramChannel", packageName: "@assemblyline-agents/telegram" },
|
|
13
|
-
teams: { helper: "defineTeamsChannel", packageName: "@assemblyline-agents/teams" }
|
|
14
|
-
};
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { isMap, isScalar, isSeq, parseDocument } from "yaml";
|
|
4
|
+
import { appendPluginSelection, runMdPluginAdd } from "./plugin-add.js";
|
|
5
|
+
/** Install, inspect, lock, and select a plugin using only its own declaration. */
|
|
15
6
|
export async function runAddCommand(options) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
const outcome = wireDeclaration(metadata, options.agentRoot, installDir);
|
|
31
|
-
for (const line of outcome.lines)
|
|
32
|
-
console.log(line);
|
|
33
|
-
for (const line of copyPluginSkills(metadata, options.agentRoot, installDir))
|
|
34
|
-
console.log(line);
|
|
35
|
-
printNextSteps(metadata, options.agentRoot, pm);
|
|
36
|
-
}
|
|
37
|
-
function normalizePm(pmOverride) {
|
|
38
|
-
if (pmOverride === undefined)
|
|
39
|
-
return undefined;
|
|
40
|
-
if (pmOverride === "pnpm" || pmOverride === "yarn" || pmOverride === "npm")
|
|
41
|
-
return pmOverride;
|
|
42
|
-
throw new Error(`Unknown package manager "${pmOverride}". Use --pm pnpm, yarn, or npm.`);
|
|
43
|
-
}
|
|
44
|
-
async function installPackage(options, pm, installDir, packageName) {
|
|
45
|
-
const installCommand = `${pm} add ${packageName}`;
|
|
46
|
-
if (!options.install) {
|
|
47
|
-
console.log(`Install (skipped by --no-install): ${installCommand}`);
|
|
7
|
+
if (!existsSync(join(options.agentRoot, "agent.md"))) {
|
|
8
|
+
throw new Error(`MD-first Assembly Line requires agent.md at ${options.agentRoot}.`);
|
|
9
|
+
}
|
|
10
|
+
const added = await runMdPluginAdd({
|
|
11
|
+
thing: options.thing,
|
|
12
|
+
agentRoot: options.agentRoot,
|
|
13
|
+
pmOverride: options.pmOverride,
|
|
14
|
+
install: options.install,
|
|
15
|
+
confirmUpgrade: options.confirmUpgrade === true,
|
|
16
|
+
selectCapability: false,
|
|
17
|
+
runCommand: options.runCommand
|
|
18
|
+
});
|
|
19
|
+
if (!added)
|
|
48
20
|
return;
|
|
21
|
+
const candidates = declaredSelections(added);
|
|
22
|
+
const requestedRole = normalizeRole(options.role);
|
|
23
|
+
const eligible = requestedRole ? candidates.filter((candidate) => candidate.role === requestedRole) : candidates;
|
|
24
|
+
const selected = chooseSelection(added.name, eligible, requestedRole);
|
|
25
|
+
if (selected.role === "capability") {
|
|
26
|
+
if (added.packageName)
|
|
27
|
+
appendPluginSelection(options.agentRoot, added.name);
|
|
28
|
+
else
|
|
29
|
+
console.log(`Local Agent Plugin ${added.name} is active by presence.`);
|
|
30
|
+
}
|
|
31
|
+
else if (selected.role === "media") {
|
|
32
|
+
console.log(`Locked ${added.name}. Select ${selected.name} under channels.<channel>.audio when overriding a channel default.`);
|
|
49
33
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (!
|
|
65
|
-
throw new Error(`"${kind}" has no ${role} provider. Roles for "${kind}": ${roleList(entries)}.`);
|
|
66
|
-
}
|
|
67
|
-
return match;
|
|
68
|
-
}
|
|
69
|
-
// Prefer installable entries so e.g. `add postgres` means the state package,
|
|
70
|
-
// not the package-less state-backed scheduler variant.
|
|
71
|
-
const installable = entries.filter((entry) => entry.packageName);
|
|
72
|
-
const pool = installable.length > 0 ? installable : entries;
|
|
73
|
-
const roles = [...new Set(pool.map((entry) => entry.role))];
|
|
74
|
-
if (roles.length > 1) {
|
|
75
|
-
// Without --role, `add` defaults to the agent-folder contribution — a
|
|
76
|
-
// channel scaffold first, then a connection or tool pack. Gateway slots
|
|
77
|
-
// (state, secrets, ...) offered alongside one stay reachable via --role.
|
|
78
|
-
for (const preferred of ["channel", "connection", "tools"]) {
|
|
79
|
-
const match = pool.find((entry) => entry.role === preferred);
|
|
80
|
-
if (match)
|
|
81
|
-
return match;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
const first = pool[0];
|
|
85
|
-
if (roles.length > 1 || !first) {
|
|
86
|
-
throw new Error(`"${kind}" provides multiple roles: ${roles.join(", ")}. Pass --role <role>, e.g. assembly-line add ${kind} --role ${roles[0]}.`);
|
|
87
|
-
}
|
|
88
|
-
return first;
|
|
89
|
-
}
|
|
90
|
-
function roleList(entries) {
|
|
91
|
-
return [...new Set(entries.map((entry) => entry.role))].sort().join(", ");
|
|
92
|
-
}
|
|
93
|
-
function kindsByRoleLines(all) {
|
|
94
|
-
const byRole = new Map();
|
|
95
|
-
for (const entry of all) {
|
|
96
|
-
const kinds = byRole.get(entry.role) ?? new Set();
|
|
97
|
-
kinds.add(entry.kind);
|
|
98
|
-
byRole.set(entry.role, kinds);
|
|
99
|
-
}
|
|
100
|
-
return [...byRole.entries()]
|
|
101
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
102
|
-
.map(([role, kinds]) => ` ${role}: ${[...kinds].sort().join(", ")}`);
|
|
103
|
-
}
|
|
104
|
-
async function metadataFromPackage(packageName, agentRoot, installDir, role) {
|
|
105
|
-
const imported = await importPackage(packageName, [agentRoot, installDir]);
|
|
106
|
-
const pluginModule = pluginModuleFromImport(imported);
|
|
107
|
-
const pluginEntries = [
|
|
108
|
-
...(pluginModule?.connections ?? []).map((connection) => ({
|
|
109
|
-
...connection,
|
|
110
|
-
packageName: connection.packageName ?? packageName
|
|
111
|
-
})),
|
|
112
|
-
...(pluginModule?.toolPacks ?? []).map((toolPack) => ({
|
|
113
|
-
...toolPack,
|
|
114
|
-
packageName: toolPack.packageName || packageName
|
|
115
|
-
}))
|
|
116
|
-
];
|
|
117
|
-
const providerModule = providerModuleFromImport(imported);
|
|
118
|
-
if (!providerModule && pluginEntries.length === 0) {
|
|
119
|
-
throw new Error(`Plugin package ${packageName} does not export assemblyLinePlugin or assemblyLineProvider, so its contributions cannot be determined.`);
|
|
120
|
-
}
|
|
121
|
-
const entries = [...pluginEntries, ...(providerModule?.providers ?? []).map((registration) => ({
|
|
122
|
-
...registration.metadata,
|
|
123
|
-
packageName: registration.metadata.packageName ?? packageName
|
|
124
|
-
}))];
|
|
125
|
-
if (role) {
|
|
126
|
-
const match = entries.find((entry) => entry.role === role);
|
|
127
|
-
if (!match) {
|
|
128
|
-
throw new Error(`Package ${packageName} has no ${role} provider. Available: ${entries.map((entry) => `${entry.role}:${entry.kind}`).join(", ")}.`);
|
|
129
|
-
}
|
|
130
|
-
return match;
|
|
131
|
-
}
|
|
132
|
-
// Without --role, plugin contributions (connections, tool packs) are what
|
|
133
|
-
// `add` scaffolds; provider registrations such as gateway secrets stores
|
|
134
|
-
// stay reachable through an explicit --role.
|
|
135
|
-
const candidates = pluginEntries.length > 0 ? pluginEntries : entries;
|
|
136
|
-
const roles = [...new Set(candidates.map((entry) => entry.role))];
|
|
137
|
-
const first = candidates[0];
|
|
138
|
-
if (roles.length > 1 || !first) {
|
|
139
|
-
throw new Error(`Package ${packageName} provides multiple roles: ${[...new Set(entries.map((entry) => entry.role))].join(", ")}. Pass --role <role>.`);
|
|
140
|
-
}
|
|
141
|
-
return first;
|
|
142
|
-
}
|
|
143
|
-
async function importPackage(packageName, startDirs) {
|
|
144
|
-
const errors = [];
|
|
145
|
-
const specifiers = [packageName];
|
|
146
|
-
for (const dir of startDirs) {
|
|
147
|
-
try {
|
|
148
|
-
const requireFrom = createRequire(pathToFileURL(join(dir, "package.json")).href);
|
|
149
|
-
specifiers.push(pathToFileURL(requireFrom.resolve(packageName)).href);
|
|
150
|
-
}
|
|
151
|
-
catch (error) {
|
|
152
|
-
errors.push(error instanceof Error ? error.message : String(error));
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
for (const specifier of [...new Set(specifiers)]) {
|
|
156
|
-
try {
|
|
157
|
-
return await import(specifier);
|
|
158
|
-
}
|
|
159
|
-
catch (error) {
|
|
160
|
-
errors.push(error instanceof Error ? error.message : String(error));
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
throw new Error(`Failed to load plugin package ${packageName}: ${errors.join("; ")}. Install it first (or rerun without --no-install).`);
|
|
164
|
-
}
|
|
165
|
-
function wireDeclaration(metadata, agentRoot, installDir) {
|
|
166
|
-
if (metadata.role === "tools")
|
|
167
|
-
return wireToolPack(agentRoot, metadata);
|
|
168
|
-
if (GATEWAY_ROLES.has(metadata.role))
|
|
169
|
-
return wireGatewaySlot(agentRoot, metadata.role, metadata.kind);
|
|
170
|
-
if (metadata.role === "channel")
|
|
171
|
-
return wireChannel(agentRoot, metadata.kind, metadata.packageName, installDir);
|
|
172
|
-
if (metadata.role === "connection") {
|
|
173
|
-
const plugin = asConnectionPluginMetadata(metadata) ?? connectionPluginMetadata(metadata.kind);
|
|
174
|
-
if (plugin)
|
|
175
|
-
return wireConnection(agentRoot, plugin);
|
|
176
|
-
}
|
|
177
|
-
return {
|
|
178
|
-
lines: [
|
|
179
|
-
`${metadata.role} providers are declared in ${metadata.role}s/*.ts, so nothing was wired automatically.`,
|
|
180
|
-
`Create ${metadata.role}s/${metadata.kind}.ts using the helper exported by ${metadata.packageName ?? "the plugin package"}.`
|
|
181
|
-
]
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
function wireToolPack(agentRoot, plugin) {
|
|
185
|
-
const lines = [];
|
|
186
|
-
const configPath = plugin.config ? safePluginAgentPath(plugin.config.path) : undefined;
|
|
187
|
-
if (plugin.config && configPath) {
|
|
188
|
-
const target = join(agentRoot, configPath);
|
|
189
|
-
if (existsSync(target)) {
|
|
190
|
-
lines.push(`${configPath} already exists. Leaving it unchanged.`);
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
mkdirSync(dirname(target), { recursive: true });
|
|
194
|
-
writeFileSync(target, [
|
|
195
|
-
`import { ${plugin.config.helper} } from ${JSON.stringify(plugin.packageName)};`,
|
|
196
|
-
"",
|
|
197
|
-
`export default ${plugin.config.helper}(${JSON.stringify(plugin.config.options ?? {}, null, 2)});`,
|
|
198
|
-
""
|
|
199
|
-
].join("\n"));
|
|
200
|
-
lines.push(`Created ${configPath} for developer-owned ${plugin.kind} configuration.`);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
mkdirSync(join(agentRoot, "tools"), { recursive: true });
|
|
204
|
-
for (const tool of plugin.tools) {
|
|
205
|
-
if (!/^[A-Za-z0-9_-]+$/u.test(tool.name)) {
|
|
206
|
-
throw new Error(`Tool pack ${plugin.kind} declares invalid tool name "${tool.name}".`);
|
|
207
|
-
}
|
|
208
|
-
const relativePath = `tools/${tool.name}.ts`;
|
|
209
|
-
const target = join(agentRoot, relativePath);
|
|
210
|
-
if (existsSync(target)) {
|
|
211
|
-
lines.push(`${relativePath} already exists. Leaving it unchanged.`);
|
|
34
|
+
else {
|
|
35
|
+
const changed = updateMarkdownSelection(options.agentRoot, selected);
|
|
36
|
+
console.log(changed
|
|
37
|
+
? `Updated agent.md ${selected.role} selection to ${selected.value}.`
|
|
38
|
+
: `agent.md already selects ${selected.value} for ${selected.role}.`);
|
|
39
|
+
}
|
|
40
|
+
printRequirements(selected);
|
|
41
|
+
}
|
|
42
|
+
function declaredSelections(plugin) {
|
|
43
|
+
const entry = plugin.entry;
|
|
44
|
+
if (!entry)
|
|
45
|
+
return [{ role: "capability", name: plugin.name, value: plugin.name }];
|
|
46
|
+
const selections = [];
|
|
47
|
+
for (const [name, raw] of Object.entries(entry.sections.providers)) {
|
|
48
|
+
if (!isRecord(raw) || typeof raw.role !== "string")
|
|
212
49
|
continue;
|
|
213
|
-
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
]
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
];
|
|
263
|
-
writeFileSync(path, [
|
|
264
|
-
`import { ${plugin.helper} } from "${plugin.packageName}";`,
|
|
265
|
-
"",
|
|
266
|
-
`export default ${plugin.helper}({`,
|
|
267
|
-
...connectionOptions,
|
|
268
|
-
...accessOptions,
|
|
269
|
-
"});",
|
|
270
|
-
""
|
|
271
|
-
].join("\n"));
|
|
272
|
-
const accessSummary = plugin.protocol === "credential" ? "" : " with reviewed tools enabled";
|
|
273
|
-
return { lines: [`Created ${relativePath} using ${plugin.helper}()${accessSummary}.`] };
|
|
274
|
-
}
|
|
275
|
-
export function copyPluginSkills(metadata, agentRoot, installDir) {
|
|
276
|
-
if (metadata.role !== "tools")
|
|
277
|
-
return [];
|
|
278
|
-
const packageName = metadata.packageName;
|
|
279
|
-
const skills = metadata.skills ?? [];
|
|
280
|
-
if (!packageName || skills.length === 0)
|
|
281
|
-
return [];
|
|
282
|
-
const packageRoot = resolvePluginPackageRoot(packageName, metadata.kind, [agentRoot, installDir]);
|
|
283
|
-
if (!packageRoot)
|
|
284
|
-
return skills.map((skill) => `Skill ${skill} will be available after ${packageName} is installed.`);
|
|
285
|
-
return skills.map((skill) => {
|
|
286
|
-
const target = join(agentRoot, "skills", skill);
|
|
287
|
-
if (existsSync(target)) {
|
|
288
|
-
return `skills/${skill}/ already exists. Leaving it unchanged.`;
|
|
289
|
-
}
|
|
290
|
-
const source = join(packageRoot, "skills", skill);
|
|
291
|
-
const bundleSkillNames = pluginBundleSkillNames(source);
|
|
292
|
-
if (!existsSync(join(source, "SKILL.md")) && bundleSkillNames.length === 0) {
|
|
293
|
-
return `Plugin ${packageName} does not include skills/${skill}/SKILL.md or a ${PLUGIN_MANIFEST_RELATIVE_PATH} skill plugin.`;
|
|
294
|
-
}
|
|
295
|
-
mkdirSync(dirname(target), { recursive: true });
|
|
296
|
-
cpSync(source, target, { recursive: true });
|
|
297
|
-
if (bundleSkillNames.length === 0)
|
|
298
|
-
return `Copied skills/${skill}/ from ${packageName}.`;
|
|
299
|
-
const collisions = bundleSkillNames.filter((name) => existsSync(join(agentRoot, "skills", name, "SKILL.md")));
|
|
300
|
-
const lines = [`Copied skill plugin skills/${skill}/ (${bundleSkillNames.length} skills) from ${packageName}.`];
|
|
301
|
-
for (const name of collisions) {
|
|
302
|
-
lines.push(`Warning: skill name "${name}" is contributed twice (skill plugin ${skill} and skills/${name}/); compilation will fail until one is removed.`);
|
|
303
|
-
}
|
|
304
|
-
return lines.join("\n");
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
/** Contributed skill names of a skill-plugin-shaped directory (empty for standalone skills). */
|
|
308
|
-
function pluginBundleSkillNames(source) {
|
|
309
|
-
const isBundle = existsSync(join(source, PLUGIN_MANIFEST_RELATIVE_PATH)) || existsSync(join(source, "skills"));
|
|
310
|
-
if (!isBundle || existsSync(join(source, "SKILL.md")))
|
|
311
|
-
return [];
|
|
312
|
-
const nested = join(source, "skills");
|
|
313
|
-
if (!existsSync(nested))
|
|
314
|
-
return [];
|
|
315
|
-
return readdirSync(nested)
|
|
316
|
-
.filter((entry) => existsSync(join(nested, entry, "SKILL.md")))
|
|
317
|
-
.sort();
|
|
318
|
-
}
|
|
319
|
-
function resolvePluginPackageRoot(packageName, kind, startDirs) {
|
|
320
|
-
for (const startDir of startDirs) {
|
|
321
|
-
try {
|
|
322
|
-
// Walk up from the resolved entry to the package.json whose name matches:
|
|
323
|
-
// nested manifests (dist/cjs/package.json shims) do not mark the root.
|
|
324
|
-
let current = dirname(createRequire(pathToFileURL(join(startDir, "package.json")).href).resolve(packageName));
|
|
325
|
-
while (current !== dirname(current)) {
|
|
326
|
-
const packageJson = join(current, "package.json");
|
|
327
|
-
if (existsSync(packageJson) && packageNameAtPath(packageJson) === packageName)
|
|
328
|
-
return current;
|
|
329
|
-
current = dirname(current);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
catch {
|
|
333
|
-
// Try the next project root, then the monorepo fallback used by local development.
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
const packagesDir = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
337
|
-
const workspaceCandidate = join(packagesDir, kind);
|
|
338
|
-
return existsSync(join(workspaceCandidate, "package.json")) ? workspaceCandidate : undefined;
|
|
339
|
-
}
|
|
340
|
-
function packageNameAtPath(packageJsonPath) {
|
|
50
|
+
const selectionName = typeof raw.name === "string" ? raw.name : name;
|
|
51
|
+
const metadata = contractMetadata(raw.contract, plugin.root);
|
|
52
|
+
selections.push({ role: raw.role, name: selectionName, value: selectionName, ...(metadata ? { metadata } : {}) });
|
|
53
|
+
}
|
|
54
|
+
for (const [name, raw] of Object.entries(entry.sections.channels)) {
|
|
55
|
+
selections.push({ role: "channel", name, value: name, ...(isRecord(raw) ? { metadata: raw } : {}) });
|
|
56
|
+
}
|
|
57
|
+
for (const [name, raw] of Object.entries(entry.sections.instrumentation)) {
|
|
58
|
+
selections.push({ role: "observability", name, value: name, ...(isRecord(raw) ? { metadata: raw } : {}) });
|
|
59
|
+
}
|
|
60
|
+
for (const [name, raw] of Object.entries(entry.sections.models)) {
|
|
61
|
+
if (!isRecord(raw) || typeof raw.defaultModel !== "string") {
|
|
62
|
+
throw new Error(`Model contribution ${plugin.name}.models.${name} must declare defaultModel for CLI selection.`);
|
|
63
|
+
}
|
|
64
|
+
const metadata = contractMetadata(raw.contract, plugin.root);
|
|
65
|
+
selections.push({ role: "model", name, value: raw.defaultModel, ...(metadata ? { metadata } : {}) });
|
|
66
|
+
}
|
|
67
|
+
if (hasCapabilitySurface(entry))
|
|
68
|
+
selections.push({ role: "capability", name: plugin.name, value: plugin.name });
|
|
69
|
+
return selections.length > 0 ? selections : [{ role: "capability", name: plugin.name, value: plugin.name }];
|
|
70
|
+
}
|
|
71
|
+
function chooseSelection(pluginName, candidates, requestedRole) {
|
|
72
|
+
if (candidates.length === 0) {
|
|
73
|
+
throw new Error(`Plugin ${pluginName} does not contribute role ${requestedRole}. Declared roles are in its ai.assemblyline entry.`);
|
|
74
|
+
}
|
|
75
|
+
if (candidates.length === 1)
|
|
76
|
+
return candidates[0];
|
|
77
|
+
const named = candidates.filter((candidate) => candidate.name === pluginName);
|
|
78
|
+
if (named.length === 1)
|
|
79
|
+
return named[0];
|
|
80
|
+
const roles = [...new Set(candidates.map((candidate) => candidate.role))].sort();
|
|
81
|
+
throw new Error(`Plugin ${pluginName} has multiple selectable contributions (${candidates.map((candidate) => `${candidate.role}:${candidate.name}`).join(", ")}). Pass --role ${roles.join("|")}.`);
|
|
82
|
+
}
|
|
83
|
+
function normalizeRole(role) {
|
|
84
|
+
if (role === "channel")
|
|
85
|
+
return "channel";
|
|
86
|
+
if (role === "connection" || role === "tools" || role === "plugin")
|
|
87
|
+
return "capability";
|
|
88
|
+
return role;
|
|
89
|
+
}
|
|
90
|
+
function hasCapabilitySurface(entry) {
|
|
91
|
+
return ["tools", "connections", "hooks", "lifecycle", "composition"]
|
|
92
|
+
.some((section) => Object.keys(entry.sections[section]).length > 0);
|
|
93
|
+
}
|
|
94
|
+
function contractMetadata(value, pluginRoot) {
|
|
95
|
+
if (isRecord(value))
|
|
96
|
+
return value;
|
|
97
|
+
if (typeof value !== "string" || !value.startsWith("./") || value.includes(".."))
|
|
98
|
+
return undefined;
|
|
341
99
|
try {
|
|
342
|
-
|
|
100
|
+
const parsed = JSON.parse(readFileSync(resolve(pluginRoot, value), "utf8"));
|
|
101
|
+
return isRecord(parsed) ? parsed : undefined;
|
|
343
102
|
}
|
|
344
103
|
catch {
|
|
345
104
|
return undefined;
|
|
346
105
|
}
|
|
347
106
|
}
|
|
348
|
-
function
|
|
349
|
-
const
|
|
350
|
-
return candidate.role === "connection" && typeof candidate.helper === "string"
|
|
351
|
-
&& (candidate.protocol === "credential" || candidate.protocol === "mcp" || candidate.protocol === "a2a" || candidate.protocol === "openapi" || candidate.protocol === "http" || candidate.protocol === "sdk" || candidate.protocol === "cli")
|
|
352
|
-
? candidate
|
|
353
|
-
: undefined;
|
|
354
|
-
}
|
|
355
|
-
function wireGatewaySlot(agentRoot, role, kind) {
|
|
356
|
-
const path = join(agentRoot, "gateway.ts");
|
|
357
|
-
const snippet = ` ${role}: adapter("${kind}"),`;
|
|
358
|
-
if (!existsSync(path)) {
|
|
359
|
-
writeFileSync(path, gatewayScaffoldSource({ [role]: kind }));
|
|
360
|
-
return { lines: [`Created gateway.ts with ${role}: adapter("${kind}").`] };
|
|
361
|
-
}
|
|
107
|
+
function updateMarkdownSelection(agentRoot, selection) {
|
|
108
|
+
const path = join(agentRoot, "agent.md");
|
|
362
109
|
const source = readFileSync(path, "utf8");
|
|
363
|
-
const
|
|
364
|
-
const
|
|
365
|
-
if (
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
if (
|
|
374
|
-
writeFileSync(path,
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
lines.push(`Slack app manifest template will be available after ${scaffold.packageName} is installed.`);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
return { lines, already: channelExists && manifestExists };
|
|
426
|
-
}
|
|
427
|
-
/** Never guess when the file shape is unexpected: print the exact snippet instead. */
|
|
428
|
-
function manualEditOutcome(file, callShape, snippet) {
|
|
429
|
-
return {
|
|
430
|
-
lines: [
|
|
431
|
-
`Could not confidently edit ${file}; paste this into the ${callShape} call yourself:`,
|
|
432
|
-
snippet
|
|
433
|
-
]
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
/**
|
|
437
|
-
* Inserts a property right after the config call's opening brace. Requires the
|
|
438
|
-
* `adapter` helper to be importable from the existing @assemblyline-agents/core import;
|
|
439
|
-
* returns undefined (caller prints the snippet) when either edit is unsafe.
|
|
440
|
-
*/
|
|
441
|
-
function insertProperty(source, openPattern, propertyLine) {
|
|
442
|
-
const withImport = ensureCoreImport(source, "adapter");
|
|
443
|
-
if (!withImport)
|
|
444
|
-
return undefined;
|
|
445
|
-
const match = openPattern.exec(withImport);
|
|
446
|
-
if (!match)
|
|
110
|
+
const frontmatter = splitMarkdownFrontmatter(source);
|
|
111
|
+
const document = parseDocument(frontmatter.yaml, { uniqueKeys: true, version: "1.2" });
|
|
112
|
+
if (document.errors.length > 0 || !isMap(document.contents)) {
|
|
113
|
+
throw new Error(`agent.md frontmatter is malformed: ${document.errors.map((error) => error.message).join("; ")}`);
|
|
114
|
+
}
|
|
115
|
+
const key = selection.role === "channel" ? "channels" : selection.role;
|
|
116
|
+
const pair = mappingPair(document, key);
|
|
117
|
+
const changed = selection.role === "channel"
|
|
118
|
+
? updateChannelNode(document, pair, selection.value)
|
|
119
|
+
: updateScalarNode(document, pair, key, selection.value);
|
|
120
|
+
if (changed)
|
|
121
|
+
writeFileSync(path, `---\n${String(document).trimEnd()}\n---${frontmatter.body}`);
|
|
122
|
+
return changed;
|
|
123
|
+
}
|
|
124
|
+
function updateScalarNode(document, pair, key, value) {
|
|
125
|
+
if (!pair) {
|
|
126
|
+
if (!isMap(document.contents))
|
|
127
|
+
throw new Error("agent.md frontmatter must be a mapping.");
|
|
128
|
+
document.contents.add({ key, value });
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
if (isScalar(pair.value) && pair.value.value === value)
|
|
132
|
+
return false;
|
|
133
|
+
pair.value = document.createNode(value);
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
function updateChannelNode(document, pair, name) {
|
|
137
|
+
if (!pair) {
|
|
138
|
+
if (!isMap(document.contents))
|
|
139
|
+
throw new Error("agent.md frontmatter must be a mapping.");
|
|
140
|
+
document.contents.add({ key: "channels", value: [name] });
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
if (isSeq(pair.value)) {
|
|
144
|
+
if (pair.value.items.some((item) => isScalar(item) && item.value === name))
|
|
145
|
+
return false;
|
|
146
|
+
pair.value.add(name);
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
if (isMap(pair.value)) {
|
|
150
|
+
if (pair.value.has(name))
|
|
151
|
+
return false;
|
|
152
|
+
pair.value.add({ key: name, value: document.createNode({}) });
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
throw new Error("agent.md channels must be a list or name-to-options mapping.");
|
|
156
|
+
}
|
|
157
|
+
function splitMarkdownFrontmatter(source) {
|
|
158
|
+
const opening = /^---[ \t]*\r?\n/u.exec(source);
|
|
159
|
+
if (!opening)
|
|
160
|
+
throw new Error("agent.md frontmatter is malformed.");
|
|
161
|
+
const remainder = source.slice(opening[0].length);
|
|
162
|
+
const closing = /^---[ \t]*(?:\r?\n|$)/mu.exec(remainder);
|
|
163
|
+
if (!closing || closing.index === undefined)
|
|
164
|
+
throw new Error("agent.md frontmatter is malformed.");
|
|
165
|
+
return { yaml: remainder.slice(0, closing.index), body: remainder.slice(closing.index + closing[0].length - (closing[0].endsWith("\n") ? 1 : 0)) };
|
|
166
|
+
}
|
|
167
|
+
function mappingPair(document, key) {
|
|
168
|
+
if (!isMap(document.contents))
|
|
447
169
|
return undefined;
|
|
448
|
-
|
|
449
|
-
return `${withImport.slice(0, index)}\n${propertyLine}${withImport.slice(index)}`;
|
|
170
|
+
return document.contents.items.find((pair) => isScalar(pair.key) && pair.key.value === key);
|
|
450
171
|
}
|
|
451
|
-
function
|
|
452
|
-
const
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
const names = match[1].split(",").map((name) => name.trim()).filter(Boolean);
|
|
457
|
-
if (names.includes(importName))
|
|
458
|
-
return source;
|
|
459
|
-
return source.replace(importPattern, `import { ${[...names, importName].sort().join(", ")} } from ${match[2]}${match[3]}${match[2]}`);
|
|
460
|
-
}
|
|
461
|
-
function printNextSteps(metadata, agentRoot, pm) {
|
|
462
|
-
const requiredConfig = "requiredConfig" in metadata ? metadata.requiredConfig ?? [] : [];
|
|
172
|
+
function printRequirements(selection) {
|
|
173
|
+
const requiredConfig = stringList(selection.metadata?.requiredConfig);
|
|
174
|
+
const optionalConfig = stringList(selection.metadata?.optionalConfig);
|
|
175
|
+
const requiredCredentials = stringList(selection.metadata?.requiredCredentials);
|
|
176
|
+
const optionalCredentials = stringList(selection.metadata?.optionalCredentials);
|
|
463
177
|
if (requiredConfig.length > 0)
|
|
464
178
|
console.log(`Set configuration: ${requiredConfig.join(", ")}`);
|
|
465
|
-
const optionalConfig = "optionalConfig" in metadata ? metadata.optionalConfig ?? [] : [];
|
|
466
179
|
if (optionalConfig.length > 0)
|
|
467
180
|
console.log(`Optional configuration: ${optionalConfig.join(", ")}`);
|
|
468
|
-
const requiredCredentials = "requiredCredentials" in metadata ? metadata.requiredCredentials ?? [] : [];
|
|
469
181
|
if (requiredCredentials.length > 0)
|
|
470
|
-
console.log(`
|
|
471
|
-
const optionalCredentials = "optionalCredentials" in metadata ? metadata.optionalCredentials ?? [] : [];
|
|
182
|
+
console.log(`Set credentials: ${requiredCredentials.join(", ")}`);
|
|
472
183
|
if (optionalCredentials.length > 0)
|
|
473
184
|
console.log(`Optional credentials: ${optionalCredentials.join(", ")}`);
|
|
474
|
-
for (const setup of metadata.setup ?? []) {
|
|
475
|
-
if (setup.required)
|
|
476
|
-
console.log(`Setup: ${setup.message}`);
|
|
477
|
-
}
|
|
478
|
-
const runner = pm === "pnpm" ? "pnpm assembly-line" : pm === "yarn" ? "yarn assembly-line" : "npx assembly-line";
|
|
479
|
-
console.log(`Run: ${runner} validate ${agentRoot}`);
|
|
480
185
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
state: "local",
|
|
487
|
-
blob: "local",
|
|
488
|
-
sandbox: "local",
|
|
489
|
-
scheduler: "local",
|
|
490
|
-
...overrides
|
|
491
|
-
};
|
|
492
|
-
return [
|
|
493
|
-
'import { adapter, defineGateway } from "@assemblyline-agents/core";',
|
|
494
|
-
"",
|
|
495
|
-
"export default defineGateway({",
|
|
496
|
-
Object.entries(slots).map(([role, kind]) => ` ${role}: adapter("${kind}")`).join(",\n"),
|
|
497
|
-
"});",
|
|
498
|
-
""
|
|
499
|
-
].join("\n");
|
|
186
|
+
function stringList(value) {
|
|
187
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
188
|
+
}
|
|
189
|
+
function isRecord(value) {
|
|
190
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
500
191
|
}
|
|
501
192
|
//# sourceMappingURL=add.js.map
|