portable-agent-layer 0.57.1 → 0.59.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.
@@ -0,0 +1,54 @@
1
+ ---
2
+ name: skill-author
3
+ description: Authors a new personal PAL skill end-to-end — writes the SKILL.md (and any tools), links it into every installed agent, and runs the doctor. Invoked by the create-skill skill to delegate the creative authoring to a flagship model.
4
+
5
+ claude:
6
+ tools: Bash, Read, Write, Edit, Grep, Glob
7
+ model: fable
8
+
9
+ opencode:
10
+ mode: subagent
11
+ permission:
12
+ read: allow
13
+ write: allow
14
+ edit: allow
15
+ bash: allow
16
+
17
+ cursor:
18
+ model: inherit
19
+ readonly: false
20
+ is_background: false
21
+ ---
22
+
23
+ You author a single new personal skill for this user. You are handed a **skill name** and a **skill description**, and optionally hints about tooling or triggers.
24
+
25
+ ## Steps
26
+
27
+ 1. Read the authoring guide — it is the single source of truth for what a good skill is and the exact anatomy to follow:
28
+ ```bash
29
+ cat ~/.pal/skills/create-skill/authoring-guide.md
30
+ ```
31
+ 2. Validate the name: lowercase-kebab, no spaces, not colliding with an existing skill (check `~/.pal/skills/` and the active skill list). If it collides or is malformed, stop and report the conflict instead of overwriting.
32
+ 3. Create the skill and write its `SKILL.md`, populated per the guide's anatomy (frontmatter + Workflow + Output format + When to use):
33
+ ```bash
34
+ mkdir -p ~/.pal/skills/<name>
35
+ ```
36
+ 4. If the skill needs runtime tooling, scaffold a `tools/` subdir alongside `SKILL.md` and write the scripts there.
37
+ 5. Link the skill into every installed agent:
38
+ ```bash
39
+ pal cli skill link <name>
40
+ ```
41
+ 6. Run the doctor and fix every `✗` error it reports; weigh each `⚠`:
42
+ ```bash
43
+ pal cli skill doctor <name>
44
+ ```
45
+ A name/folder mismatch or a misnamed file makes the skill silently fail to load — never skip this.
46
+ 7. Hand-check what the doctor can't: trigger clarity, step concreteness (every step has a verb and an object), output specification, and scope discipline (one skill, one job).
47
+
48
+ ## Output
49
+
50
+ Return, concisely:
51
+ - The path of the created `SKILL.md` (under `~/.pal/skills/<name>/`).
52
+ - The agents it was linked into (from the `pal cli skill link` output).
53
+ - The doctor's final result (errors resolved).
54
+ - A 2-3 sentence summary of the trigger and workflow, and how to invoke it.
@@ -8,65 +8,28 @@ argument-hint: <skill name> <skill description>
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
- ## What makes a good skill
11
+ The quality rules and the exact skill anatomy live in one place — read them before writing anything:
12
12
 
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
-
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.
20
-
21
- ## Skill anatomy
22
-
23
- ```markdown
24
- ---
25
- name: <slug> # the slash-command name; lowercase-kebab
26
- description: <what it does + WHEN to invoke> # the dispatcher matches on this
27
- argument-hint: <args> # optional; how the user passes input
28
- ---
29
-
30
- ## Overview / Workflow
31
-
32
- Numbered, concrete steps the assistant follows on invocation.
33
-
34
- ## Output format
35
-
36
- Exactly what the assistant returns — structure if it will be parsed, tone if a human reads it.
37
-
38
- ## When to use / Do NOT use
39
-
40
- Two short lists; the "do not" list disambiguates this skill from neighbours.
13
+ ```bash
14
+ cat ~/.pal/skills/create-skill/authoring-guide.md
41
15
  ```
42
16
 
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
17
  ## Workflow when invoked with `<name> <description>`
46
18
 
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. Run the doctor and resolve every error it reports:
19
+ 1. Check whether a flagship authoring model is configured for the current agent:
61
20
  ```bash
62
- pal cli skill doctor <name>
21
+ pal cli skill author-model
63
22
  ```
64
- It checks the mechanical rules (folder/file-name match, name length/charset, description length, point-of-view, body length, reference depth). Fix all `✗` errors; weigh each `⚠` warning. A name/folder mismatch or a misnamed file makes the skill silently fail to load, so never skip this.
65
- 7. Validate the rest by hand the doctor can't judge these:
66
- - **Trigger clarity** could a model decide *not* to invoke this from the description alone? If so, tighten it.
67
- - **Step concreteness** every step has a verb and an object.
68
- - **Output specification** the caller knows what they get back.
69
- - **Scope discipline** one skill, one job.
23
+ 2. **If it prints a model** delegate the authoring to the `skill-author` subagent. That subagent is preconfigured to run on that flagship model; hand it the skill name, description, and any trigger/tooling hints, and let it write the SKILL.md, scaffold tools, run `pal cli skill link`, and run the doctor. Relay its result.
24
+ 3. **If it prints nothing** author the skill inline yourself:
25
+ - Read `authoring-guide.md` (above) and follow its anatomy.
26
+ - `mkdir -p ~/.pal/skills/<name>` and write `~/.pal/skills/<name>/SKILL.md`.
27
+ - If the skill needs runtime tooling, scaffold a `tools/` subdir and write the scripts there.
28
+ - Link it into every installed agent: `pal cli skill link <name>`.
29
+ - Run `pal cli skill doctor <name>` and fix every `✗`; weigh each `⚠`.
30
+ - Hand-check the items the doctor can't judge (see the guide's final section).
31
+
32
+ Either path: validate the name first (lowercase-kebab, no spaces, not colliding with an existing skill in `~/.pal/skills/` or the active skill list), and confirm the trigger with the user if the description is ambiguous about *when* the skill should fire.
70
33
 
71
34
  ## Output format
72
35
 
@@ -0,0 +1,48 @@
1
+ # Skill authoring guide
2
+
3
+ The canonical rules for authoring a personal PAL skill. Both the `create-skill`
4
+ dispatcher and the delegated `skill-author` subagent follow this file, so it is
5
+ the single source of truth for skill quality.
6
+
7
+ ## What makes a good skill
8
+
9
+ 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.
10
+
11
+ 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.
12
+
13
+ 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.
14
+
15
+ 4. **Self-contained and portable.** Everything the skill needs lives inside its own folder — `SKILL.md` plus a `tools/` subdir for scripts and any reference files — so it travels intact on export/import. Don't reach into sibling skills or reference files outside the folder. Prefer `$HOME`/`~` or an env var over a hardcoded absolute path like `/Users/you/…` so the skill still works on another machine; `pal cli skill doctor` warns (never errors) on machine-specific absolute paths.
16
+
17
+ A personal skill **may** contain this user's own context — their paths, project names, preferences, conventions. That is the point of a personal skill; portability (rule 4) is a preference, not a hard rule — a deliberate machine-specific mount is fine.
18
+
19
+ ## Skill anatomy
20
+
21
+ ```markdown
22
+ ---
23
+ name: <slug> # the slash-command name; lowercase-kebab
24
+ description: <what it does + WHEN to invoke> # the dispatcher matches on this
25
+ argument-hint: <args> # optional; how the user passes input
26
+ ---
27
+
28
+ ## Overview / Workflow
29
+
30
+ Numbered, concrete steps the assistant follows on invocation.
31
+
32
+ ## Output format
33
+
34
+ Exactly what the assistant returns — structure if it will be parsed, tone if a human reads it.
35
+
36
+ ## When to use / Do NOT use
37
+
38
+ Two short lists; the "do not" list disambiguates this skill from neighbours.
39
+ ```
40
+
41
+ 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.
42
+
43
+ ## Hand-checks the doctor can't judge
44
+
45
+ - **Trigger clarity** — could a model decide *not* to invoke this from the description alone? If so, tighten it.
46
+ - **Step concreteness** — every step has a verb and an object.
47
+ - **Output specification** — the caller knows what they get back.
48
+ - **Scope discipline** — one skill, one job.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "portable-agent-layer",
3
- "version": "0.57.1",
3
+ "version": "0.59.0",
4
4
  "description": "PAL — Portable Agent Layer: persistent personal context for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli/index.ts CHANGED
@@ -262,6 +262,7 @@ function showHelp() {
262
262
  (search · graph · stats · hubs · find · show · add · ls)
263
263
  pal cli skill link <name> Link a personal ~/.pal/skills/<name>/ into installed agents
264
264
  pal cli skill doctor <name> Evaluate a skill against the authoring best practices
265
+ pal cli skill author-model Print the flagship model that authors skills for the active agent
265
266
  pal cli debug [on|off] Enable/disable verbose hook debug logging (persisted)
266
267
 
267
268
  Environment:
package/src/cli/skill.ts CHANGED
@@ -5,9 +5,13 @@
5
5
  * every installed agent so it is discoverable.
6
6
  * pal cli skill doctor <name> Evaluate ~/.pal/skills/<name>/ against the
7
7
  * skill-authoring best practices.
8
+ * pal cli skill author-model Print the flagship model configured to author
9
+ * skills for the active agent (empty if none).
8
10
  */
9
11
 
10
12
  import { resolve } from "node:path";
13
+ import { getActiveAgent } from "../hooks/lib/agent";
14
+ import { flagshipAuthorModel } from "../hooks/lib/models";
11
15
  import { palHome } from "../hooks/lib/paths";
12
16
  import { linkPersonalSkill, log } from "../targets/lib";
13
17
  import { formatReport, lintSkill } from "../tools/skill-doctor";
@@ -15,6 +19,12 @@ import { formatReport, lintSkill } from "../tools/skill-doctor";
15
19
  export async function runSkill(args: string[]): Promise<number> {
16
20
  const [sub, name] = args;
17
21
 
22
+ if (sub === "author-model") {
23
+ const model = flagshipAuthorModel(getActiveAgent());
24
+ if (model) console.log(model);
25
+ return 0;
26
+ }
27
+
18
28
  if (sub === "doctor") {
19
29
  if (!name) {
20
30
  log.error("Usage: pal cli skill doctor <name>");
@@ -50,6 +60,6 @@ export async function runSkill(args: string[]): Promise<number> {
50
60
  }
51
61
  }
52
62
 
53
- log.error("Usage: pal cli skill <link|doctor> <name>");
63
+ log.error("Usage: pal cli skill <link|doctor|author-model> [name]");
54
64
  return 1;
55
65
  }
@@ -11,7 +11,7 @@
11
11
  * vars are used as secondary fallbacks for environments that forward them.
12
12
  */
13
13
 
14
- type AgentType = "claude" | "cursor" | "codex" | "copilot" | "opencode";
14
+ export type AgentType = "claude" | "cursor" | "codex" | "copilot" | "opencode";
15
15
 
16
16
  const KNOWN_AGENTS: ReadonlySet<AgentType> = new Set([
17
17
  "claude",
@@ -2,8 +2,29 @@
2
2
  * Single source of truth for model IDs and pricing.
3
3
  */
4
4
 
5
+ import type { AgentType } from "./agent";
6
+
5
7
  export const HAIKU_MODEL = "claude-haiku-4-5-20251001";
6
8
  export const SONNET_MODEL = "claude-sonnet-4-6";
9
+ export const FABLE_MODEL = "claude-fable-5";
10
+
11
+ /**
12
+ * Per-agent flagship model used to AUTHOR new skills (via `create-skill`).
13
+ *
14
+ * This is the single extension point for delegated skill authoring: an agent
15
+ * present here → `create-skill` spins up that agent's `skill-author` subagent on
16
+ * this model; an agent absent → `create-skill` authors inline. Add a provider by
17
+ * adding one entry (and a matching platform block in assets/agents/skill-author.md).
18
+ */
19
+ export const FLAGSHIP_AUTHOR_MODEL: Partial<Record<AgentType, string>> = {
20
+ claude: FABLE_MODEL,
21
+ // codex: "gpt-5.5", // enable once Codex ships a tool-capable flagship subagent
22
+ };
23
+
24
+ /** Flagship authoring model for an agent, or undefined if none is configured. */
25
+ export function flagshipAuthorModel(agent: AgentType): string | undefined {
26
+ return FLAGSHIP_AUTHOR_MODEL[agent];
27
+ }
7
28
 
8
29
  /** Pricing per million tokens (USD) — from https://platform.claude.com/docs/en/about-claude/pricing */
9
30
  export const MODEL_PRICING: Record<
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import { existsSync, readdirSync, readFileSync } from "node:fs";
13
- import { basename, resolve } from "node:path";
13
+ import { basename, extname, relative, resolve } from "node:path";
14
14
  import { palHome } from "../hooks/lib/paths";
15
15
 
16
16
  type Level = "pass" | "warn" | "error";
@@ -41,6 +41,44 @@ const MAX_NAME = 64;
41
41
  const MAX_DESCRIPTION = 1024;
42
42
  const MAX_BODY_LINES = 500;
43
43
 
44
+ /** File extensions worth scanning for hardcoded paths (SKILL.md + its scripts). */
45
+ const SCANNABLE_EXT = new Set([".md", ".ts", ".js", ".mjs", ".cjs", ".sh", ".py"]);
46
+
47
+ /** Machine/user-specific absolute paths that will not survive an export to
48
+ * another machine or user: POSIX home dirs and Windows user profiles. Portable
49
+ * forms ($HOME, ~, %USERPROFILE%, env vars) are deliberately not matched. */
50
+ const ABSOLUTE_PATH_RE =
51
+ /(?:\/(?:Users|home)\/[A-Za-z0-9._-]+|\/root\/[A-Za-z0-9._-]|[A-Za-z]:\\Users\\[A-Za-z0-9._-]+)/;
52
+
53
+ /** Collect SKILL.md and sibling script files, skipping vendored/VCS trees. */
54
+ function collectSkillFiles(skillDir: string): string[] {
55
+ const out: string[] = [];
56
+ const walk = (dir: string) => {
57
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
58
+ if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
59
+ const full = resolve(dir, entry.name);
60
+ if (entry.isDirectory()) walk(full);
61
+ else if (entry.isFile() && SCANNABLE_EXT.has(extname(entry.name))) out.push(full);
62
+ }
63
+ };
64
+ walk(skillDir);
65
+ return out;
66
+ }
67
+
68
+ /** Find machine-specific absolute paths across a skill's files. */
69
+ function findAbsolutePaths(skillDir: string): string[] {
70
+ const hits: string[] = [];
71
+ for (const file of collectSkillFiles(skillDir)) {
72
+ const rel = relative(skillDir, file).replaceAll("\\", "/");
73
+ const lines = readFileSync(file, "utf-8").split("\n");
74
+ for (let i = 0; i < lines.length; i++) {
75
+ const m = ABSOLUTE_PATH_RE.exec(lines[i]);
76
+ if (m) hits.push(`${rel}:${i + 1} → ${m[0]}`);
77
+ }
78
+ }
79
+ return hits;
80
+ }
81
+
44
82
  /** Split a SKILL.md into frontmatter fields and body. */
45
83
  function parseSkill(content: string): ParsedSkill {
46
84
  const parts = content.split(/^---\s*$/m);
@@ -238,6 +276,23 @@ export function lintSkill(skillDir: string): DoctorReport {
238
276
  ? add("warn", "paths", "Windows-style backslash path found — use forward slashes")
239
277
  : add("pass", "paths", "forward-slash paths");
240
278
 
279
+ // ── machine-specific absolute paths (portability) ──
280
+ // A personal skill MAY legitimately hardcode a machine-specific path (e.g. a
281
+ // cloud-mount vault), so this is a warning, never an error — it flags paths
282
+ // that will not survive being exported to another machine or user.
283
+ const absHits = findAbsolutePaths(skillDir);
284
+ if (absHits.length > 0) {
285
+ const shown = absHits.slice(0, 3).join("; ");
286
+ const more = absHits.length > 3 ? ` (+${absHits.length - 3} more)` : "";
287
+ add(
288
+ "warn",
289
+ "paths.absolute",
290
+ `hardcoded absolute path(s) that won't be portable across machines: ${shown}${more} — prefer $HOME/~ or an env var, or ignore if this is an intentional machine-specific mount`
291
+ );
292
+ } else {
293
+ add("pass", "paths.absolute", "no machine-specific absolute paths");
294
+ }
295
+
241
296
  const errors = findings.filter((f) => f.level === "error").length;
242
297
  const warnings = findings.filter((f) => f.level === "warn").length;
243
298
  return { dir: skillDir, name, findings, errors, warnings };