delegation-layer 0.1.3 → 0.1.4
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/README.md +15 -6
- package/package.json +1 -1
- package/scripts/install-agent-integration.mjs +159 -40
package/README.md
CHANGED
|
@@ -36,17 +36,26 @@ already use instead of replacing them.
|
|
|
36
36
|
|
|
37
37
|
### Install the Agent Skill
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
The standard Agent Skills installer works across popular coding agents. From
|
|
40
|
+
the project where you want the skill available, run:
|
|
40
41
|
|
|
41
42
|
```sh
|
|
42
|
-
npx --yes delegation-layer
|
|
43
|
+
npx --yes skills add hishamkaram/delegation-layer --skill agent-integration
|
|
43
44
|
```
|
|
44
45
|
|
|
45
|
-
The
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
The `--skill agent-integration` selection matters because this repository also
|
|
47
|
+
contains maintainer skills. The installer detects supported agents and lets you
|
|
48
|
+
choose the project or global scope. Add `--global` to install for all projects,
|
|
49
|
+
or `--agent codex` (and similar agent names) to choose a specific harness.
|
|
50
|
+
|
|
51
|
+
If you want the Delegation Layer guided installer instead, run:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
npx --yes delegation-layer install
|
|
55
|
+
```
|
|
48
56
|
|
|
49
|
-
|
|
57
|
+
It provides an interactive scope and harness picker, with global installation
|
|
58
|
+
selected by default. For scripts or a choice without prompts:
|
|
50
59
|
|
|
51
60
|
```sh
|
|
52
61
|
npx --yes delegation-layer install \
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { accessSync, constants } from "node:fs";
|
|
4
4
|
import { copyFile, lstat, mkdir, readFile } from "node:fs/promises";
|
|
5
|
-
import {
|
|
5
|
+
import { emitKeypressEvents } from "node:readline";
|
|
6
6
|
import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
@@ -97,7 +97,7 @@ function usage(message, exitCode = 2) {
|
|
|
97
97
|
|
|
98
98
|
function help() {
|
|
99
99
|
console.log(
|
|
100
|
-
"Usage:\n delegation-layer install [--scope project|global] [--harness NAME[,NAME...]] [--yes] [--force]\n delegation-layer --target ABSOLUTE_SKILL_DIR [--force]\n delegation-layer install-cli [--version VERSION] [--install-dir ABSOLUTE_DIR]\n\nThe install command detects supported agent harnesses and installs the skill for a project or user.\nUse --harness universal when no specific harness is detected.",
|
|
100
|
+
"Usage:\n delegation-layer install [--scope project|global] [--harness NAME[,NAME...]] [--yes] [--force]\n delegation-layer --target ABSOLUTE_SKILL_DIR [--force]\n delegation-layer install-cli [--version VERSION] [--install-dir ABSOLUTE_DIR]\n\nThe install command detects supported agent harnesses and installs the skill for a project or user.\nGlobal scope is the default; use --scope project for a project-local install.\nUse --harness universal when no specific harness is detected.",
|
|
101
101
|
);
|
|
102
102
|
}
|
|
103
103
|
|
|
@@ -280,25 +280,123 @@ function printDetections(detections) {
|
|
|
280
280
|
});
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
-
function
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
.
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}
|
|
294
|
-
return detection.harness.name;
|
|
283
|
+
function renderPicker({ error, index, multiple, options, selected, title }) {
|
|
284
|
+
const lines = [title, ""];
|
|
285
|
+
options.forEach((option, optionIndex) => {
|
|
286
|
+
const current = optionIndex === index;
|
|
287
|
+
const marker = multiple ? (selected.has(option.value) ? "[x]" : "[ ]") : "";
|
|
288
|
+
const cursor = current ? "❯" : " ";
|
|
289
|
+
const color = current ? "\u001b[36m" : "";
|
|
290
|
+
const reset = current ? "\u001b[0m" : "";
|
|
291
|
+
lines.push(`${color}${cursor} ${marker ? `${marker} ` : ""}${option.label}${reset}`);
|
|
292
|
+
if (option.description) {
|
|
293
|
+
lines.push(` ${option.description}`);
|
|
295
294
|
}
|
|
296
|
-
|
|
297
|
-
|
|
295
|
+
});
|
|
296
|
+
lines.push("");
|
|
297
|
+
lines.push(multiple ? "↑↓ move Space select Enter continue a all n none q cancel" : "↑↓ move Enter select q cancel");
|
|
298
|
+
if (error) {
|
|
299
|
+
lines.push(`\u001b[31m${error}\u001b[0m`);
|
|
300
|
+
}
|
|
301
|
+
process.stdout.write(`\u001b[2J\u001b[H${lines.join("\n")}\n`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function pickOptions({ initialValues = [], multiple = false, options, title }) {
|
|
305
|
+
const input = process.stdin;
|
|
306
|
+
const output = process.stdout;
|
|
307
|
+
if (!input.isTTY || !output.isTTY || typeof input.setRawMode !== "function") {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
let index = 0;
|
|
312
|
+
const selected = new Set(initialValues);
|
|
313
|
+
|
|
314
|
+
return new Promise((resolve, reject) => {
|
|
315
|
+
let settled = false;
|
|
316
|
+
let error;
|
|
317
|
+
|
|
318
|
+
const cleanup = () => {
|
|
319
|
+
input.off("keypress", onKeypress);
|
|
320
|
+
try {
|
|
321
|
+
input.setRawMode(false);
|
|
322
|
+
} catch {
|
|
323
|
+
// The terminal may already be closed while handling cancellation.
|
|
324
|
+
}
|
|
325
|
+
input.pause();
|
|
326
|
+
output.write("\u001b[?25h\u001b[0m");
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
const finish = (value) => {
|
|
330
|
+
if (settled) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
settled = true;
|
|
334
|
+
cleanup();
|
|
335
|
+
resolve(value);
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const onKeypress = (character, key = {}) => {
|
|
339
|
+
if ((key.ctrl && key.name === "c") || key.name === "escape" || key.name === "q") {
|
|
340
|
+
finish(null);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (key.name === "up" || key.name === "k") {
|
|
344
|
+
index = (index + options.length - 1) % options.length;
|
|
345
|
+
error = undefined;
|
|
346
|
+
renderPicker({ error, index, multiple, options, selected, title });
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (key.name === "down" || key.name === "j") {
|
|
350
|
+
index = (index + 1) % options.length;
|
|
351
|
+
error = undefined;
|
|
352
|
+
renderPicker({ error, index, multiple, options, selected, title });
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
if (multiple && (key.name === "space" || character === " ")) {
|
|
356
|
+
const value = options[index].value;
|
|
357
|
+
if (selected.has(value)) {
|
|
358
|
+
selected.delete(value);
|
|
359
|
+
} else {
|
|
360
|
+
selected.add(value);
|
|
361
|
+
}
|
|
362
|
+
error = undefined;
|
|
363
|
+
renderPicker({ error, index, multiple, options, selected, title });
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (multiple && key.name === "a") {
|
|
367
|
+
options.forEach((option) => selected.add(option.value));
|
|
368
|
+
error = undefined;
|
|
369
|
+
renderPicker({ error, index, multiple, options, selected, title });
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (multiple && key.name === "n") {
|
|
373
|
+
selected.clear();
|
|
374
|
+
error = undefined;
|
|
375
|
+
renderPicker({ error, index, multiple, options, selected, title });
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (key.name === "return" || key.name === "enter") {
|
|
379
|
+
if (multiple && selected.size === 0) {
|
|
380
|
+
error = "Select at least one harness, or press q to cancel.";
|
|
381
|
+
renderPicker({ error, index, multiple, options, selected, title });
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
finish(multiple ? options.filter((option) => selected.has(option.value)).map((option) => option.value) : options[index].value);
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
emitKeypressEvents(input);
|
|
390
|
+
input.setRawMode(true);
|
|
391
|
+
input.resume();
|
|
392
|
+
input.on("keypress", onKeypress);
|
|
393
|
+
output.write("\u001b[?25l");
|
|
394
|
+
renderPicker({ error, index, multiple, options, selected, title });
|
|
395
|
+
} catch (pickerError) {
|
|
396
|
+
cleanup();
|
|
397
|
+
reject(pickerError);
|
|
298
398
|
}
|
|
299
|
-
return token;
|
|
300
399
|
});
|
|
301
|
-
return [...new Set(names)];
|
|
302
400
|
}
|
|
303
401
|
|
|
304
402
|
async function chooseInstallOptions({ force, harnessNames, scope, yes }) {
|
|
@@ -309,28 +407,45 @@ async function chooseInstallOptions({ force, harnessNames, scope, yes }) {
|
|
|
309
407
|
const canPrompt = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
310
408
|
const needsPrompt = !yes && !harnessNames && !scope && canPrompt;
|
|
311
409
|
|
|
312
|
-
if (!harnessNames) {
|
|
410
|
+
if (!harnessNames && !needsPrompt) {
|
|
313
411
|
printDetections(detections);
|
|
314
412
|
}
|
|
315
413
|
if (!canPrompt && !yes && !harnessNames && !scope) {
|
|
316
|
-
console.log("No interactive terminal detected; using
|
|
414
|
+
console.log("No interactive terminal detected; using global scope and detected harnesses.");
|
|
317
415
|
}
|
|
318
416
|
|
|
319
417
|
if (needsPrompt) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
418
|
+
scope = await pickOptions({
|
|
419
|
+
initialValues: ["global"],
|
|
420
|
+
options: [
|
|
421
|
+
{
|
|
422
|
+
value: "global",
|
|
423
|
+
label: "Global",
|
|
424
|
+
description: `available to your agents from ${home}`,
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
value: "project",
|
|
428
|
+
label: "Project",
|
|
429
|
+
description: `available to this project from ${cwd}`,
|
|
430
|
+
},
|
|
431
|
+
],
|
|
432
|
+
title: "Where should the agent skill be installed?",
|
|
433
|
+
});
|
|
434
|
+
if (!scope) {
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
harnessNames = await pickOptions({
|
|
438
|
+
initialValues: defaults,
|
|
439
|
+
multiple: true,
|
|
440
|
+
options: detections.map(({ harness, reasons }) => ({
|
|
441
|
+
value: harness.name,
|
|
442
|
+
label: `${harness.label}${formatDetected(reasons)}`,
|
|
443
|
+
description: targetFor(harness, scope, cwd, home),
|
|
444
|
+
})),
|
|
445
|
+
title: "Which harnesses should use the skill?",
|
|
446
|
+
});
|
|
447
|
+
if (!harnessNames) {
|
|
448
|
+
return null;
|
|
334
449
|
}
|
|
335
450
|
}
|
|
336
451
|
|
|
@@ -339,7 +454,7 @@ async function chooseInstallOptions({ force, harnessNames, scope, yes }) {
|
|
|
339
454
|
force,
|
|
340
455
|
harnessNames: harnessNames ?? defaults,
|
|
341
456
|
home,
|
|
342
|
-
scope: scope ?? "
|
|
457
|
+
scope: scope ?? "global",
|
|
343
458
|
};
|
|
344
459
|
}
|
|
345
460
|
|
|
@@ -399,11 +514,15 @@ if (argumentsList[0] === "install-cli") {
|
|
|
399
514
|
if (options) {
|
|
400
515
|
try {
|
|
401
516
|
const selected = await chooseInstallOptions(options);
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
517
|
+
if (!selected) {
|
|
518
|
+
console.log("Installation cancelled.");
|
|
519
|
+
} else {
|
|
520
|
+
const targets = selected.harnessNames.map((name) => targetFor(harnessByName.get(name), selected.scope, selected.cwd, selected.home));
|
|
521
|
+
const uniqueTargets = [...new Set(targets)];
|
|
522
|
+
console.log(`Installing for ${selected.scope}: ${uniqueTargets.join(", ")}`);
|
|
523
|
+
for (const target of uniqueTargets) {
|
|
524
|
+
await install({ force: selected.force, target });
|
|
525
|
+
}
|
|
407
526
|
}
|
|
408
527
|
} catch (error) {
|
|
409
528
|
console.error(`Error: ${error.message}`);
|