@callmeradical/augy 0.3.0 → 0.5.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,109 @@
1
+ import {
2
+ AGENTS,
3
+ agentSkillPath,
4
+ detectInstalledAgents
5
+ } from "./chunk-V5GA2ZHU.js";
6
+ import {
7
+ createSkillRecord,
8
+ getSkill,
9
+ readRegistry,
10
+ upsertSkill,
11
+ writeRegistry
12
+ } from "./chunk-JM4VVAHN.js";
13
+
14
+ // src/commands/author.ts
15
+ import { intro, outro, multiselect, isCancel } from "@clack/prompts";
16
+ import chalk from "chalk";
17
+ import { mkdir, writeFile } from "fs/promises";
18
+ import { join } from "path";
19
+ import { spawn } from "child_process";
20
+ function skillTemplate(name) {
21
+ return `# Skill: ${name}
22
+
23
+ ## Overview
24
+
25
+ <!-- Describe what this skill does and when to use it. -->
26
+
27
+ ## Workflow
28
+
29
+ <!-- Step-by-step instructions for the agent. -->
30
+
31
+ 1.
32
+
33
+ ## Notes
34
+
35
+ <!-- Tips, caveats, or references. -->
36
+ `;
37
+ }
38
+ async function authorNewCommand(name, opts = {}) {
39
+ intro(chalk.bold("augy") + chalk.dim(` \u2014 author new ${name}`));
40
+ const registry = await readRegistry();
41
+ if (getSkill(registry, name)) {
42
+ throw new Error(`Skill "${name}" already exists in the registry. Use \`augy author edit ${name}\` to open it.`);
43
+ }
44
+ const available = detectInstalledAgents();
45
+ if (!available.length) {
46
+ throw new Error("No agents detected. Install an agent first.");
47
+ }
48
+ const selected = available.length === 1 ? [available[0].id] : await multiselect({
49
+ message: "Install for which agents?",
50
+ options: available.map((a) => ({ value: a.id, label: a.name })),
51
+ initialValues: available.map((a) => a.id)
52
+ });
53
+ if (isCancel(selected)) {
54
+ console.log(chalk.dim("Cancelled."));
55
+ process.exit(0);
56
+ }
57
+ const targetAgents = AGENTS.filter((a) => selected.includes(a.id));
58
+ const agentPaths = {};
59
+ for (const agent of targetAgents) {
60
+ const dest = agentSkillPath(agent, name);
61
+ agentPaths[agent.id] = dest;
62
+ await mkdir(dest, { recursive: true });
63
+ await writeFile(join(dest, "SKILL.md"), skillTemplate(name), "utf8");
64
+ }
65
+ const record = createSkillRecord({
66
+ name,
67
+ source: "",
68
+ gigetSource: "",
69
+ sha: "unversioned",
70
+ agentIds: targetAgents.map((a) => a.id),
71
+ agentPaths
72
+ });
73
+ upsertSkill(registry, record);
74
+ await writeRegistry(registry);
75
+ const firstPath = agentPaths[targetAgents[0].id];
76
+ outro(
77
+ `${chalk.green("\u2713")} ${chalk.cyan(name)} created
78
+ ` + chalk.dim(` ${firstPath}
79
+ `) + chalk.dim(" Run `augy home push` when ready to back it up.")
80
+ );
81
+ if (!opts.noEdit) {
82
+ openInEditor(firstPath);
83
+ }
84
+ }
85
+ async function authorEditCommand(name) {
86
+ const registry = await readRegistry();
87
+ const skill = getSkill(registry, name);
88
+ if (!skill) {
89
+ throw new Error(`Skill "${name}" not found in registry. Run \`augy list\` to see installed skills.`);
90
+ }
91
+ const agentPath = Object.values(skill.agents).find((a) => a.active)?.path;
92
+ if (!agentPath) {
93
+ throw new Error(`No active agent path found for "${name}".`);
94
+ }
95
+ console.log(chalk.dim(`Opening ${agentPath}\u2026`));
96
+ openInEditor(agentPath);
97
+ }
98
+ function openInEditor(path) {
99
+ const editor = process.env["EDITOR"] ?? process.env["VISUAL"] ?? "vi";
100
+ const child = spawn(editor, [path], {
101
+ stdio: "inherit",
102
+ detached: true
103
+ });
104
+ child.unref();
105
+ }
106
+ export {
107
+ authorEditCommand,
108
+ authorNewCommand
109
+ };
@@ -416,6 +416,7 @@ async function filterableMultiselect(opts) {
416
416
  const allOptions = opts.options.map((o) => ({ value: o.value, label: o.label, hint: o.hint }));
417
417
  const initialValues = opts.options.filter((o) => o.selected).map((o) => o.value);
418
418
  let filterText = "";
419
+ let filterMode = false;
419
420
  let matchCount = allOptions.length;
420
421
  const pageSize = opts.pageSize ?? Math.min(14, process2.stdout.rows - 6);
421
422
  const prompt = new wD({
@@ -438,11 +439,11 @@ ${headerPrefix} ${opts.message}` + chalk.dim(` (${allOptions.length} total)
438
439
  ${chalk.gray(S_BAR)}
439
440
  `;
440
441
  }
441
- const cursor = filterText.length > 0 ? chalk.inverse(" ") : chalk.inverse(chalk.hidden("_"));
442
442
  const noMatch = matchCount === 0 && filterText.length > 0;
443
443
  const matchLabel = noMatch ? chalk.red("no matches") : filterText ? chalk.dim(`${matchCount} matched`) : chalk.dim(`${allOptions.length} skills`);
444
- const hint = chalk.dim("space select \xB7 a all \xB7 enter confirm");
445
- const filterLine = `${chalk.cyan(S_BAR)} ` + chalk.dim("/ ") + (filterText ? chalk.white(filterText) : chalk.dim("type to filter")) + cursor + ` ${matchLabel} ${hint}`;
444
+ const hint = filterMode ? chalk.dim("esc to clear \xB7 enter confirm") : chalk.dim("/ filter \xB7 a all \xB7 space select \xB7 enter confirm");
445
+ const filterDisplay = filterMode ? chalk.white(filterText) + chalk.inverse(" ") : filterText ? chalk.dim("/ ") + chalk.white(filterText) + chalk.dim(" (/ to edit)") : chalk.dim("/ to filter");
446
+ const filterLine = `${filterMode ? chalk.cyan(S_BAR) : chalk.gray(S_BAR)} ` + filterDisplay + ` ${matchLabel} ${hint}`;
446
447
  const visibleOptions = paginate(
447
448
  this.options,
448
449
  this.cursor,
@@ -471,19 +472,47 @@ ${chalk.cyan(S_BAR_END)}
471
472
  }
472
473
  });
473
474
  prompt.on("key", (key) => {
474
- if (key === "\b" || key === "\x7F" || key === "backspace") {
475
- filterText = filterText.slice(0, -1);
476
- } else if (key && key.length === 1 && key.charCodeAt(0) >= 32) {
477
- if (key === " ") return;
478
- filterText += key;
479
- } else {
475
+ const p = prompt;
476
+ if (filterMode) {
477
+ if (key === "\x1B") {
478
+ filterMode = false;
479
+ filterText = "";
480
+ } else if (key === "\b" || key === "\x7F" || key === "backspace") {
481
+ if (filterText.length > 0) {
482
+ filterText = filterText.slice(0, -1);
483
+ } else {
484
+ filterMode = false;
485
+ }
486
+ } else if (key === "/") {
487
+ filterMode = false;
488
+ return;
489
+ } else if (key && key.length === 1 && key.charCodeAt(0) >= 32 && key !== " ") {
490
+ filterText += key;
491
+ } else {
492
+ return;
493
+ }
494
+ const filtered = filterText ? allOptions.filter((o) => o.label.toLowerCase().includes(filterText.toLowerCase())) : allOptions;
495
+ matchCount = filtered.length;
496
+ p.options = filtered;
497
+ if (p.cursor >= filtered.length) p.cursor = Math.max(0, filtered.length - 1);
498
+ return;
499
+ }
500
+ if (key === "/") {
501
+ filterMode = true;
480
502
  return;
481
503
  }
482
- const filtered = filterText ? allOptions.filter((o) => o.label.toLowerCase().includes(filterText.toLowerCase())) : allOptions;
483
- matchCount = filtered.length;
484
- prompt.options = filtered;
485
- if (prompt.cursor >= filtered.length) {
486
- prompt.cursor = Math.max(0, filtered.length - 1);
504
+ if (key === "a") {
505
+ const visibleValues = p.options.map((o) => o.value);
506
+ const currentSelected = Array.isArray(p.value) ? p.value : [];
507
+ const allVisible = visibleValues.every((v2) => currentSelected.includes(v2));
508
+ if (allVisible) {
509
+ p.value = currentSelected.filter((v2) => !visibleValues.includes(v2));
510
+ } else {
511
+ const union = [...currentSelected];
512
+ for (const v2 of visibleValues) if (!union.includes(v2)) union.push(v2);
513
+ p.value = union;
514
+ }
515
+ return;
487
516
  }
488
517
  });
489
518
  const result = await prompt.prompt();
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ var pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf8")
10
10
  var program = new Command();
11
11
  program.name("augy").description("Homebrew for AI agent skills \u2014 install, version, update, rollback").version(pkg.version);
12
12
  program.command("install [url]").description("Install skills from a GitHub URL or owner/repo[/path]").option("-a, --agent <agents...>", "Target agent(s): opencode, claude, codex").action(async (url, opts) => {
13
- const { installCommand } = await import("./install-7ZWHGCFN.js");
13
+ const { installCommand } = await import("./install-27YDQ46H.js");
14
14
  await installCommand(url, opts ?? {});
15
15
  });
16
16
  program.command("update [skill]").description("Check for upstream changes and upgrade installed skills").action(async (skill) => {
@@ -36,7 +36,7 @@ program.command("sync [path]").description("Install/update skills from an augy.j
36
36
  await syncCommand(path, opts ?? {});
37
37
  });
38
38
  program.command("scan").description("Find skills installed outside augy and optionally import them into the registry").action(async () => {
39
- const { scanCommand } = await import("./scan-CC4IYZ35.js");
39
+ const { scanCommand } = await import("./scan-6HFU774V.js");
40
40
  await scanCommand();
41
41
  });
42
42
  program.command("info <skill>").description("Show full metadata, version history, and description for an installed skill").action(async (skill) => {
@@ -78,6 +78,15 @@ program.command("pin <skill>").description("Pin a skill so it is skipped during
78
78
  program.command("unpin <skill>").description("Allow a pinned skill to receive updates again").action(async (skill) => {
79
79
  await setPinned(skill, false);
80
80
  });
81
+ var author = program.command("author").description("Create and edit self-authored skills");
82
+ author.command("new <name>").description("Scaffold a new skill and open it in $EDITOR").option("--no-edit", "Skip opening the editor after creating").action(async (name, opts) => {
83
+ const { authorNewCommand } = await import("./author-G46NJ3OW.js");
84
+ await authorNewCommand(name, { noEdit: !opts.edit });
85
+ });
86
+ author.command("edit <name>").description("Open an existing skill in $EDITOR").action(async (name) => {
87
+ const { authorEditCommand } = await import("./author-G46NJ3OW.js");
88
+ await authorEditCommand(name);
89
+ });
81
90
  var home = program.command("home").description("Manage a personal GitHub repo for backing up your skills manifest");
82
91
  home.command("set <repo>").description("Set the home repo e.g. augy home set alice/my-skills").option("--path <file>", "Manifest path within the repo (default: augy.json)").option("--skills-path <dir>", "Dir for authored skills in the repo (default: skills)").action(async (repo, opts) => {
83
92
  const { homeSetCommand } = await import("./home-TWK3PMCL.js");
@@ -96,7 +105,7 @@ home.command("show").description("Show the current home repo configuration").act
96
105
  await homeShowCommand();
97
106
  });
98
107
  program.action(async () => {
99
- const { installCommand } = await import("./install-7ZWHGCFN.js");
108
+ const { installCommand } = await import("./install-27YDQ46H.js");
100
109
  await installCommand();
101
110
  });
102
111
  program.parse();
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  filterableMultiselect
3
- } from "./chunk-RBF4Q6N4.js";
3
+ } from "./chunk-A3USXURD.js";
4
4
  import {
5
5
  resolveSkillFromTaps
6
6
  } from "./chunk-HZSFPII7.js";
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-KCQY4IDO.js";
5
5
  import {
6
6
  filterableMultiselect
7
- } from "./chunk-RBF4Q6N4.js";
7
+ } from "./chunk-A3USXURD.js";
8
8
  import {
9
9
  resolveSkillFromTaps
10
10
  } from "./chunk-HZSFPII7.js";
@@ -52,6 +52,7 @@ async function scanCommand() {
52
52
  untracked.push({ name, agents });
53
53
  }
54
54
  }
55
+ const additionalInstalls = findAdditionalAgentInstalls(onDisk, registry);
55
56
  const tracked = listSkills(registry);
56
57
  if (!untracked.length) {
57
58
  outro(chalk.green("All installed skills are already tracked by augy."));
@@ -224,6 +225,36 @@ async function scanCommand() {
224
225
  outro(
225
226
  importedCount > 0 ? chalk.green(`${importedCount} skill(s) imported into augy`) : chalk.dim("No skills imported.")
226
227
  );
228
+ if (additionalInstalls.length) {
229
+ console.log(
230
+ `
231
+ ${chalk.bold("Additional installs found")}` + chalk.dim(` (${additionalInstalls.length} skill(s) installed in new agent(s))`) + "\n"
232
+ );
233
+ for (const { skillName, newAgents } of additionalInstalls) {
234
+ console.log(` ${chalk.cyan.bold(skillName)}`);
235
+ for (const { agent, path } of newAgents) {
236
+ console.log(` ${chalk.bold(agent.name.padEnd(10))} ${chalk.dim(tildefy(path))}`);
237
+ }
238
+ }
239
+ console.log();
240
+ const doRegister = await confirm({
241
+ message: "Register these additional agent paths in the registry?"
242
+ });
243
+ if (!isCancel(doRegister) && doRegister) {
244
+ let registered = 0;
245
+ for (const { skillName, newAgents } of additionalInstalls) {
246
+ const skill = getSkill(registry, skillName);
247
+ for (const { agent, path } of newAgents) {
248
+ skill.agents[agent.id] = { path, active: true };
249
+ registered++;
250
+ }
251
+ registry.skills[skillName] = skill;
252
+ await writeRegistry(registry);
253
+ }
254
+ console.log(chalk.green(`
255
+ \u2713 Registered ${registered} additional path(s)`));
256
+ }
257
+ }
227
258
  }
228
259
  function printDetected(sk) {
229
260
  const p = sk.provenance;
@@ -309,6 +340,18 @@ async function readSkillDescription(skillPath) {
309
340
  }
310
341
  return void 0;
311
342
  }
343
+ function findAdditionalAgentInstalls(onDisk, registry) {
344
+ const result = [];
345
+ for (const [name, agents] of onDisk) {
346
+ const tracked = getSkill(registry, name);
347
+ if (!tracked) continue;
348
+ const newAgents = agents.filter((a) => !tracked.agents[a.agent.id]);
349
+ if (newAgents.length) {
350
+ result.push({ skillName: name, newAgents });
351
+ }
352
+ }
353
+ return result;
354
+ }
312
355
  function tildefy(p) {
313
356
  const home = homedir();
314
357
  return p.startsWith(home) ? "~" + p.slice(home.length) : p;
@@ -324,5 +367,6 @@ async function tryResolveTapSource(registry, name) {
324
367
  }
325
368
  }
326
369
  export {
370
+ findAdditionalAgentInstalls,
327
371
  scanCommand
328
372
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@callmeradical/augy",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Homebrew for AI agent skills — install, version, update, rollback",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -25,6 +25,7 @@
25
25
  ],
26
26
  "files": [
27
27
  "dist/**/*",
28
+ "skills/**/*",
28
29
  "README.md",
29
30
  "LICENSE"
30
31
  ],
@@ -0,0 +1,81 @@
1
+ # Skill: author
2
+
3
+ A meta-skill for authoring new augy skills. Use this when the user wants
4
+ to create a new skill for their AI agents.
5
+
6
+ ## What is an augy skill?
7
+
8
+ A skill is a directory containing a `SKILL.md` file. That file is loaded
9
+ by an AI agent as context — giving it specialised knowledge, workflows,
10
+ or tool integrations for a specific task.
11
+
12
+ Skills are installed per-agent:
13
+ ```
14
+ ~/.opencode/skills/<name>/SKILL.md
15
+ ~/.claude/skills/<name>/SKILL.md
16
+ ~/.codex/skills/<name>/SKILL.md
17
+ ```
18
+
19
+ ## Workflow
20
+
21
+ ### 1. Scaffold the skill
22
+
23
+ ```sh
24
+ augy author new <name>
25
+ ```
26
+
27
+ This creates the skill directory for your installed agents, registers it
28
+ in the augy registry, and opens it in `$EDITOR`.
29
+
30
+ ### 2. Write the SKILL.md
31
+
32
+ A good SKILL.md has:
33
+
34
+ - **One-line description** — what this skill does and when to invoke it
35
+ - **Trigger phrases** — phrases the user says that should activate this skill
36
+ - **Workflow** — numbered steps the agent follows
37
+ - **Examples** — concrete inputs and expected outputs
38
+ - **References** — links to docs, files, or other skills
39
+
40
+ Keep it focused. One skill = one capability. If it grows beyond ~150 lines,
41
+ consider splitting it.
42
+
43
+ ### 3. Test it
44
+
45
+ Load the skill into your agent and try the trigger phrases. Check that the
46
+ agent follows the workflow correctly.
47
+
48
+ ### 4. Back it up
49
+
50
+ ```sh
51
+ augy home push
52
+ ```
53
+
54
+ This commits the skill to your home repo so it's available on all your machines.
55
+
56
+ ## SKILL.md template
57
+
58
+ ```markdown
59
+ # Skill: <name>
60
+
61
+ <One-sentence description of what this skill does.>
62
+ Trigger on: <phrase 1>, <phrase 2>, <phrase 3>.
63
+
64
+ ## Workflow
65
+
66
+ 1. <First step>
67
+ 2. <Second step>
68
+ 3. <Third step>
69
+
70
+ ## Notes
71
+
72
+ <!-- Tips, caveats, edge cases -->
73
+ ```
74
+
75
+ ## Tips
76
+
77
+ - **Verb-first steps**: "Run the tests", not "Tests should be run"
78
+ - **Concrete over abstract**: Show exact commands, not just concepts
79
+ - **Fail states**: Tell the agent what to do when things go wrong
80
+ - **Scope**: If you find yourself writing "unless X, in which case Y", that's
81
+ a second skill