delegation-layer 0.1.3 → 0.1.5
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 +29 -16
- package/install.sh +2 -2
- package/package.json +1 -1
- package/scripts/install-agent-integration.mjs +159 -40
- package/skills/agent-integration/SKILL.md +14 -11
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 \
|
|
@@ -56,7 +65,7 @@ npx --yes delegation-layer install \
|
|
|
56
65
|
```
|
|
57
66
|
|
|
58
67
|
The skill installer only installs guidance for your agent. It does not install
|
|
59
|
-
the CLI, change provider logins, or
|
|
68
|
+
the CLI, change provider logins, or run a task.
|
|
60
69
|
|
|
61
70
|
### Install the CLI
|
|
62
71
|
|
|
@@ -76,8 +85,9 @@ pnpm dlx delegation-layer install-cli
|
|
|
76
85
|
bunx --bun delegation-layer install-cli
|
|
77
86
|
```
|
|
78
87
|
|
|
79
|
-
The release installer verifies checksums and installs `delegate
|
|
80
|
-
`delegate-run` into `$HOME/.local/bin` by default.
|
|
88
|
+
The release installer verifies checksums and installs `delegate`,
|
|
89
|
+
`delegate-run`, `pueue`, and `pueued` into `$HOME/.local/bin` by default. The
|
|
90
|
+
CLI uses the bundled supervisor automatically. Set
|
|
81
91
|
`DELEGATION_LAYER_INSTALL_DIR` to use another directory.
|
|
82
92
|
|
|
83
93
|
Check the installation:
|
|
@@ -93,14 +103,17 @@ Normal users do not need Go or Node after choosing an installation method.
|
|
|
93
103
|
Dispatching a task requires:
|
|
94
104
|
|
|
95
105
|
- a released Delegation Layer CLI;
|
|
96
|
-
-
|
|
97
|
-
- a
|
|
98
|
-
or `opencode`;
|
|
106
|
+
- a compatible provider CLI with an available native login;
|
|
107
|
+
- a supported provider CLI such as `agy`, `codex`, `claude`, `pi`, or `opencode`;
|
|
99
108
|
- Darwin or Linux on amd64 or arm64 for the supplied release builds.
|
|
100
109
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
110
|
+
The release contains the supervisor client and daemon and starts a private
|
|
111
|
+
instance under the state root, recovering it when a control command finds it
|
|
112
|
+
stopped. An explicit `--pueue-config` or
|
|
113
|
+
`DELEGATE_PUEUE_CONFIG` can still select an existing compatible supervisor for
|
|
114
|
+
advanced integrations. Node.js 18 or newer is needed only for the npm-based
|
|
115
|
+
installers; it is not a runtime requirement for the released Go CLI. Provider
|
|
116
|
+
authentication remains native to each provider.
|
|
104
117
|
|
|
105
118
|
## Try one bounded task
|
|
106
119
|
|
|
@@ -110,7 +123,7 @@ Create a brief and a workspace outside the private state root:
|
|
|
110
123
|
mkdir -p "$HOME/delegation-workspace"
|
|
111
124
|
printf '%s\n' 'Inspect the repository and summarize the current build status.' > brief.txt
|
|
112
125
|
|
|
113
|
-
delegate --
|
|
126
|
+
delegate --root "$HOME/delegation-state" dispatch \
|
|
114
127
|
--provider codex:exec \
|
|
115
128
|
--brief "$PWD/brief.txt" \
|
|
116
129
|
--cwd "$HOME/delegation-workspace" \
|
package/install.sh
CHANGED
|
@@ -65,12 +65,12 @@ fi
|
|
|
65
65
|
|
|
66
66
|
tar -xzf "$ARCHIVE" -C "$TEMP_DIR" || fail "could not extract $ARCHIVE"
|
|
67
67
|
mkdir -p "$INSTALL_DIR"
|
|
68
|
-
for binary in delegate delegate-run; do
|
|
68
|
+
for binary in delegate delegate-run pueue pueued; do
|
|
69
69
|
[ -f "$TEMP_DIR/$binary" ] || fail "release archive does not contain $binary"
|
|
70
70
|
install -m 0755 "$TEMP_DIR/$binary" "$INSTALL_DIR/$binary"
|
|
71
71
|
done
|
|
72
72
|
|
|
73
|
-
printf 'Installed
|
|
73
|
+
printf 'Installed delegation-layer CLI and bundled supervisor %s in %s\n' "$VERSION" "$INSTALL_DIR"
|
|
74
74
|
case ":${PATH:-}:" in
|
|
75
75
|
*:"$INSTALL_DIR":*) ;;
|
|
76
76
|
*) printf 'Add %s to PATH if it is not already there.\n' "$INSTALL_DIR" ;;
|
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}`);
|
|
@@ -13,11 +13,12 @@ provider names, release versions, or native CLI syntax.
|
|
|
13
13
|
|
|
14
14
|
## Preconditions
|
|
15
15
|
|
|
16
|
-
The host is responsible for installing
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
The host is responsible for installing the CLI. This skill must not install,
|
|
17
|
+
build, upgrade, or repair it. It may check whether `delegate` is available and
|
|
18
|
+
create separate task and state directories that it is authorized to use. A
|
|
19
|
+
released CLI carries the compatible `pueue` and `pueued` supervisor binaries
|
|
20
|
+
and starts or recovers a private instance automatically; an explicit supervisor
|
|
21
|
+
config is only needed for an advanced integration.
|
|
21
22
|
Authentication is a separate live gate; do not invent a provider-specific
|
|
22
23
|
login check or classify a missing login as static incompatibility.
|
|
23
24
|
|
|
@@ -65,7 +66,6 @@ mkdir -p "${HOME}/delegation-workspace" "${HOME}/delegation-state"
|
|
|
65
66
|
printf '%s\n' 'Inspect the workspace and return a short summary.' > "${HOME}/delegation-brief.txt"
|
|
66
67
|
|
|
67
68
|
delegate --root "${HOME}/delegation-state" \
|
|
68
|
-
--pueue-config /absolute/path/to/private-pueue.yml \
|
|
69
69
|
dispatch \
|
|
70
70
|
--provider PROFILE \
|
|
71
71
|
--brief "${HOME}/delegation-brief.txt" \
|
|
@@ -75,11 +75,14 @@ delegate --root "${HOME}/delegation-state" \
|
|
|
75
75
|
--json
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
78
|
+
The default release owns the private supervisor lifecycle under the state root
|
|
79
|
+
and recovers its bundled daemon when control commands find it stopped. Collection
|
|
80
|
+
stays observational and does not start a supervisor. If an existing compatible
|
|
81
|
+
supervisor must be used, pass its absolute
|
|
82
|
+
`--pueue-config` path or set `DELEGATE_PUEUE_CONFIG`. If the CLI or provider
|
|
83
|
+
executable is unavailable, stop on the CLI's structured error. For live
|
|
84
|
+
acceptance, an unavailable authentication prerequisite is `blocked`; preserve
|
|
85
|
+
that evidence and never relabel it as
|
|
83
86
|
incompatible or passed.
|
|
84
87
|
|
|
85
88
|
Save the returned `task_id`. Admission records one immutable request and at
|