portable-agent-layer 0.46.0 → 0.47.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/README.md +1 -0
- package/assets/skills/create-skill/SKILL.md +39 -36
- package/assets/skills/playwright/tools/shot.ts +18 -8
- package/package.json +1 -1
- package/src/cli/index.ts +8 -0
- package/src/cli/skill.ts +40 -0
- package/src/targets/lib.ts +40 -0
package/README.md
CHANGED
|
@@ -85,6 +85,7 @@ pal cli status # check your setup
|
|
|
85
85
|
| `pal cli migrate` | Run pending data migrations (non-destructive) |
|
|
86
86
|
| `pal cli usage` | Summarize token usage and estimated cost |
|
|
87
87
|
| `pal cli knowledge` | Query & manage the knowledge store (search, graph, stats, hubs, find, show, add, ls, ingest) |
|
|
88
|
+
| `pal cli skill link <name>` | Link a personal `~/.pal/skills/<name>/` into every installed agent so it is discoverable |
|
|
88
89
|
|
|
89
90
|
### Target flags
|
|
90
91
|
|
|
@@ -1,69 +1,72 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: create-skill
|
|
3
|
-
description:
|
|
3
|
+
description: Create a new personal skill for this user, scaffolded into their own ~/.pal/skills/ and linked into every installed agent. Use when the user asks to create a skill, add a capability, build a custom command, or "make a skill that…".
|
|
4
4
|
argument-hint: <skill name> <skill description>
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
# Create a personal skill
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
This scaffolds a personal skill into the user's own `~/.pal/skills/<name>/` and links it into every installed agent so it is immediately discoverable.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
## What makes a good skill
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
1. **Pointed at the assistant, not the user.** A skill is *instructions you (the assistant) follow*. Write in second person addressing yourself ("read X", "run Y", "output Z"). Prescriptive verbs, deterministic flow — not a tutorial or marketing blurb.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
2. **One skill, one job.** A skill describes a single workflow. If it needs branches like "for case A do X, for case B do Y," it is two skills.
|
|
16
|
+
|
|
17
|
+
3. **Concise and concrete.** Assume the model is already smart — only add what it doesn't already know. Every step has a verb and an object; no "as needed" or "appropriately." Keep the SKILL.md body well under 500 lines; push long reference material into sibling files linked one level deep.
|
|
18
|
+
|
|
19
|
+
A personal skill **may** contain this user's own context — their paths, project names, preferences, conventions. That is the point of a personal skill.
|
|
16
20
|
|
|
17
21
|
## Skill anatomy
|
|
18
22
|
|
|
19
23
|
```markdown
|
|
20
24
|
---
|
|
21
25
|
name: <slug> # the slash-command name; lowercase-kebab
|
|
22
|
-
description: <
|
|
26
|
+
description: <what it does + WHEN to invoke> # the dispatcher matches on this
|
|
23
27
|
argument-hint: <args> # optional; how the user passes input
|
|
24
28
|
---
|
|
25
29
|
|
|
26
30
|
## Overview / Workflow
|
|
27
31
|
|
|
28
|
-
Numbered steps the assistant follows on invocation.
|
|
29
|
-
read this file, run this command, ask this question, output this format.
|
|
32
|
+
Numbered, concrete steps the assistant follows on invocation.
|
|
30
33
|
|
|
31
34
|
## Output format
|
|
32
35
|
|
|
33
|
-
Exactly what the assistant returns
|
|
34
|
-
caller will parse it; specify tone if the caller is a human.
|
|
36
|
+
Exactly what the assistant returns — structure if it will be parsed, tone if a human reads it.
|
|
35
37
|
|
|
36
38
|
## When to use / Do NOT use
|
|
37
39
|
|
|
38
|
-
Two short lists
|
|
39
|
-
that would otherwise also match the user's request.
|
|
40
|
+
Two short lists; the "do not" list disambiguates this skill from neighbours.
|
|
40
41
|
```
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
-
|
|
59
|
-
|
|
60
|
-
-
|
|
61
|
-
-
|
|
62
|
-
-
|
|
43
|
+
The `description` should state **both what the skill does and when to invoke it**, in third person, with the trigger terms a model would match on. A vague description ("helps with documents") will not trigger reliably.
|
|
44
|
+
|
|
45
|
+
## Workflow when invoked with `<name> <description>`
|
|
46
|
+
|
|
47
|
+
1. Validate the name: lowercase-kebab, no spaces, not colliding with an existing skill (check `~/.pal/skills/` and the active skill list).
|
|
48
|
+
2. Confirm the trigger with the user if the description is ambiguous about *when* the skill should fire.
|
|
49
|
+
3. Create the directory and SKILL.md at the user's PAL home:
|
|
50
|
+
```bash
|
|
51
|
+
mkdir -p ~/.pal/skills/<name>
|
|
52
|
+
```
|
|
53
|
+
Write `~/.pal/skills/<name>/SKILL.md` populated from the anatomy above (frontmatter + Workflow + Output format + When to use).
|
|
54
|
+
4. If the skill needs runtime tooling, scaffold a `tools/` subdir alongside SKILL.md and write the scripts there.
|
|
55
|
+
5. Link the new skill into every installed agent so it is discoverable:
|
|
56
|
+
```bash
|
|
57
|
+
pal cli skill link <name>
|
|
58
|
+
```
|
|
59
|
+
This creates the per-skill discovery symlink in each installed agent's skills directory (Claude Code, Cursor, Copilot, Codex); opencode discovers it automatically via `~/.pal/skills/`.
|
|
60
|
+
6. Validate before declaring done:
|
|
61
|
+
- **Trigger clarity** — could a model decide *not* to invoke this from the description alone? If so, tighten it.
|
|
62
|
+
- **Step concreteness** — every step has a verb and an object.
|
|
63
|
+
- **Output specification** — the caller knows what they get back.
|
|
64
|
+
- **Scope discipline** — one skill, one job.
|
|
63
65
|
|
|
64
66
|
## Output format
|
|
65
67
|
|
|
66
68
|
After scaffolding, return:
|
|
67
|
-
- The path of the created `SKILL.md
|
|
69
|
+
- The path of the created `SKILL.md` (under `~/.pal/skills/<name>/`).
|
|
70
|
+
- The agents it was linked into (from the `pal cli skill link` output).
|
|
68
71
|
- A 2-3 sentence summary of the trigger and workflow.
|
|
69
|
-
-
|
|
72
|
+
- How to invoke it (its `/<name>` slash command or trigger phrase).
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// node --experimental-strip-types ~/.pal/skills/playwright/tools/shot.ts <url> [opts]
|
|
14
14
|
|
|
15
15
|
import { spawnSync } from "node:child_process";
|
|
16
|
-
import { existsSync } from "node:fs";
|
|
16
|
+
import { existsSync, mkdtempSync, rmSync } from "node:fs";
|
|
17
17
|
import { tmpdir } from "node:os";
|
|
18
18
|
import { join, resolve } from "node:path";
|
|
19
19
|
import { chooseTier, parseArgs, type ShotOptions } from "./shot-lib.ts";
|
|
@@ -27,13 +27,23 @@ function playwrightCliAvailable(): boolean {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
function runViaCli(opts: ShotOptions, out: string): boolean {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
// playwright-cli writes a `.playwright-cli/` session dir (logs + page snapshots)
|
|
31
|
+
// into its cwd. Run it inside a throwaway temp dir so nothing lands in the user's
|
|
32
|
+
// repo, then remove it. `out` is already absolute, so the screenshot is unaffected.
|
|
33
|
+
// Cross-platform: os.tmpdir + fs.mkdtemp/rm — no shell, no platform-specific paths.
|
|
34
|
+
const work = mkdtempSync(join(tmpdir(), "pal-pwcli-"));
|
|
35
|
+
const run = (args: string[], quiet = false) =>
|
|
36
|
+
spawnSync("playwright-cli", args, { stdio: quiet ? "ignore" : "inherit", cwd: work });
|
|
37
|
+
try {
|
|
38
|
+
if (run(["open", opts.url]).status !== 0) return false;
|
|
39
|
+
const args = ["screenshot", `--filename=${out}`];
|
|
40
|
+
if (opts.selector) args.push(opts.selector);
|
|
41
|
+
const shot = run(args);
|
|
42
|
+
run(["close"], true);
|
|
43
|
+
return shot.status === 0 && existsSync(out);
|
|
44
|
+
} finally {
|
|
45
|
+
rmSync(work, { recursive: true, force: true });
|
|
46
|
+
}
|
|
37
47
|
}
|
|
38
48
|
|
|
39
49
|
// "unavailable" = the engine itself can't run (package or Chromium missing) → MCP fallback.
|
package/package.json
CHANGED
package/src/cli/index.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* status Show current PAL configuration
|
|
17
17
|
* doctor Check prerequisites and system health
|
|
18
18
|
* usage Summarize token usage and cost
|
|
19
|
+
* skill link <name> Link a personal ~/.pal/skills/<name>/ into installed agents
|
|
19
20
|
*/
|
|
20
21
|
|
|
21
22
|
import { spawnSync } from "node:child_process";
|
|
@@ -192,6 +193,12 @@ async function runCli(command: string | undefined, args: string[]) {
|
|
|
192
193
|
if (code !== 0) process.exit(code);
|
|
193
194
|
break;
|
|
194
195
|
}
|
|
196
|
+
case "skill": {
|
|
197
|
+
const { runSkill } = await import("./skill");
|
|
198
|
+
const code = await runSkill(args);
|
|
199
|
+
if (code !== 0) process.exit(code);
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
195
202
|
case "--help":
|
|
196
203
|
case "-h":
|
|
197
204
|
case "help":
|
|
@@ -234,6 +241,7 @@ function showHelp() {
|
|
|
234
241
|
pal cli usage Summarize token usage and cost
|
|
235
242
|
pal cli knowledge <sub> [args] Query & manage the knowledge store
|
|
236
243
|
(search · graph · stats · hubs · find · show · add · ls)
|
|
244
|
+
pal cli skill link <name> Link a personal ~/.pal/skills/<name>/ into installed agents
|
|
237
245
|
|
|
238
246
|
Environment:
|
|
239
247
|
PAL_HOME Override user state directory (default: ~/.pal or repo root)
|
package/src/cli/skill.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pal cli skill — manage personal skills under ~/.pal/skills/.
|
|
3
|
+
*
|
|
4
|
+
* pal cli skill link <name> Link an existing ~/.pal/skills/<name>/ into
|
|
5
|
+
* every installed agent so it is discoverable.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { linkPersonalSkill, log } from "../targets/lib";
|
|
9
|
+
|
|
10
|
+
export async function runSkill(args: string[]): Promise<number> {
|
|
11
|
+
const [sub, name] = args;
|
|
12
|
+
|
|
13
|
+
if (sub === "link") {
|
|
14
|
+
if (!name) {
|
|
15
|
+
log.error("Usage: pal cli skill link <name>");
|
|
16
|
+
return 1;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const linked = linkPersonalSkill(name);
|
|
20
|
+
if (linked.length === 0) {
|
|
21
|
+
log.warn(
|
|
22
|
+
`'${name}' linked to no per-skill agents (none installed). ` +
|
|
23
|
+
"It is still discoverable by opencode via ~/.pal/skills/."
|
|
24
|
+
);
|
|
25
|
+
} else {
|
|
26
|
+
log.success(
|
|
27
|
+
`Linked '${name}' into: ${linked.join(", ")} ` +
|
|
28
|
+
"(opencode: auto via ~/.pal/skills/)"
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
return 0;
|
|
32
|
+
} catch (e) {
|
|
33
|
+
log.error(e instanceof Error ? e.message : String(e));
|
|
34
|
+
return 1;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
log.error("Usage: pal cli skill link <name>");
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
package/src/targets/lib.ts
CHANGED
|
@@ -506,6 +506,46 @@ export function copySkills(claudeSkillsDir: string): number {
|
|
|
506
506
|
return count;
|
|
507
507
|
}
|
|
508
508
|
|
|
509
|
+
/**
|
|
510
|
+
* Agent skills directories that need a per-skill discovery link.
|
|
511
|
+
*
|
|
512
|
+
* opencode is intentionally absent: it discovers the whole ~/.pal/skills/ tree
|
|
513
|
+
* via the ~/.agents/skills → ~/.pal/skills symlink, so any personal skill is
|
|
514
|
+
* picked up without a per-skill link.
|
|
515
|
+
*/
|
|
516
|
+
function perSkillAgentDirs(): { agent: string; dir: string }[] {
|
|
517
|
+
return [
|
|
518
|
+
{ agent: "claude", dir: resolve(platform.claudeDir(), "skills") },
|
|
519
|
+
{ agent: "cursor", dir: resolve(platform.cursorDir(), "skills") },
|
|
520
|
+
{ agent: "copilot", dir: resolve(platform.copilotDir(), "skills") },
|
|
521
|
+
{ agent: "codex", dir: resolve(platform.codexDir(), "skills") },
|
|
522
|
+
];
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Link a personal skill that already lives at ~/.pal/skills/<name>/ into every
|
|
527
|
+
* installed agent's skills directory so the agent discovers it. Mirrors the
|
|
528
|
+
* per-skill discovery link copySkills() creates for shipped skills.
|
|
529
|
+
*
|
|
530
|
+
* An agent counts as installed when its skills directory already exists.
|
|
531
|
+
* Returns the agents a discovery link was created for (opencode excluded — it
|
|
532
|
+
* is covered by the whole-dir ~/.agents/skills link).
|
|
533
|
+
*/
|
|
534
|
+
export function linkPersonalSkill(name: string): string[] {
|
|
535
|
+
const palLink = resolve(PAL_SKILLS_DIR, name);
|
|
536
|
+
if (!existsSync(resolve(palLink, "SKILL.md"))) {
|
|
537
|
+
throw new Error(`No skill found at ${palLink}/SKILL.md`);
|
|
538
|
+
}
|
|
539
|
+
const linkType = process.platform === "win32" ? "junction" : "dir";
|
|
540
|
+
const linked: string[] = [];
|
|
541
|
+
for (const { agent, dir } of perSkillAgentDirs()) {
|
|
542
|
+
if (!existsSync(dir)) continue; // agent not installed
|
|
543
|
+
ensureSymlink(resolve(dir, name), palLink, linkType);
|
|
544
|
+
linked.push(agent);
|
|
545
|
+
}
|
|
546
|
+
return linked;
|
|
547
|
+
}
|
|
548
|
+
|
|
509
549
|
/** Create or update a symlink/junction, replacing any non-symlink entry. */
|
|
510
550
|
function ensureSymlink(link: string, target: string, type: "dir" | "junction"): void {
|
|
511
551
|
try {
|