@callmeradical/augy 0.1.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,136 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __commonJS = (cb, mod) => function __require() {
8
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
19
+ // If the importer is in node compatibility mode or this is not an ESM
20
+ // file that has been converted to a CommonJS file using a Babel-
21
+ // compatible transform (i.e. "__esModule" has not been set), then set
22
+ // "default" to the CommonJS "module.exports" for node compatibility.
23
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
24
+ mod
25
+ ));
26
+
27
+ // src/registry.ts
28
+ import { homedir } from "os";
29
+ import { join } from "path";
30
+ import { mkdir, readFile, rename, writeFile } from "fs/promises";
31
+ import { existsSync } from "fs";
32
+ function augyHome() {
33
+ const env = process.env["AUGY_HOME"];
34
+ return env ?? join(homedir(), ".augy");
35
+ }
36
+ function registryPath() {
37
+ return join(augyHome(), "registry.json");
38
+ }
39
+ function versionsDir() {
40
+ return join(augyHome(), "versions");
41
+ }
42
+ function versionArchivePath(skillName, sha) {
43
+ return join(versionsDir(), skillName, sha);
44
+ }
45
+ var EMPTY_REGISTRY = { version: 1, taps: {}, skills: {} };
46
+ async function readRegistry() {
47
+ const p = registryPath();
48
+ if (!existsSync(p)) return structuredClone(EMPTY_REGISTRY);
49
+ try {
50
+ const raw = await readFile(p, "utf8");
51
+ const parsed = JSON.parse(raw);
52
+ return { ...structuredClone(EMPTY_REGISTRY), ...parsed };
53
+ } catch {
54
+ return structuredClone(EMPTY_REGISTRY);
55
+ }
56
+ }
57
+ async function writeRegistry(registry) {
58
+ const p = registryPath();
59
+ const tmp = `${p}.tmp`;
60
+ await mkdir(join(p, ".."), { recursive: true });
61
+ await writeFile(tmp, JSON.stringify(registry, null, 2) + "\n", "utf8");
62
+ await rename(tmp, p);
63
+ }
64
+ function shortSha(sha) {
65
+ return sha.slice(0, 7);
66
+ }
67
+ function getSkill(registry, name) {
68
+ return registry.skills[name];
69
+ }
70
+ function listSkills(registry) {
71
+ return Object.values(registry.skills);
72
+ }
73
+ function upsertSkill(registry, skill) {
74
+ registry.skills[skill.name] = skill;
75
+ }
76
+ function removeSkill(registry, name) {
77
+ delete registry.skills[name];
78
+ }
79
+ function createSkillRecord(opts) {
80
+ const now = (/* @__PURE__ */ new Date()).toISOString();
81
+ return {
82
+ name: opts.name,
83
+ source: opts.source,
84
+ gigetSource: opts.gigetSource,
85
+ sha: opts.sha,
86
+ shortSha: shortSha(opts.sha),
87
+ installedAt: now,
88
+ updatedAt: now,
89
+ pinned: false,
90
+ tap: opts.tap,
91
+ agents: Object.fromEntries(
92
+ opts.agentIds.map((id) => [
93
+ id,
94
+ { path: opts.agentPaths[id] ?? "", active: true }
95
+ ])
96
+ ),
97
+ history: []
98
+ };
99
+ }
100
+ function tapKey(owner, repo) {
101
+ return `${owner}/${repo}`;
102
+ }
103
+ function getTap(registry, key) {
104
+ return registry.taps[key];
105
+ }
106
+ function listTaps(registry) {
107
+ return Object.entries(registry.taps).map(([key, tap]) => ({ key, ...tap }));
108
+ }
109
+ function addTap(registry, tap) {
110
+ registry.taps[tapKey(tap.owner, tap.repo)] = tap;
111
+ }
112
+ function removeTap(registry, key) {
113
+ delete registry.taps[key];
114
+ }
115
+
116
+ export {
117
+ __commonJS,
118
+ __toESM,
119
+ augyHome,
120
+ registryPath,
121
+ versionsDir,
122
+ versionArchivePath,
123
+ readRegistry,
124
+ writeRegistry,
125
+ shortSha,
126
+ getSkill,
127
+ listSkills,
128
+ upsertSkill,
129
+ removeSkill,
130
+ createSkillRecord,
131
+ tapKey,
132
+ getTap,
133
+ listTaps,
134
+ addTap,
135
+ removeTap
136
+ };
@@ -0,0 +1,303 @@
1
+ import {
2
+ archiveExists
3
+ } from "./chunk-PX2LVUHV.js";
4
+ import {
5
+ discoverSkills,
6
+ parseGitHubUrl
7
+ } from "./chunk-EU54UQ4C.js";
8
+ import {
9
+ getSkill,
10
+ readRegistry,
11
+ shortSha,
12
+ versionArchivePath
13
+ } from "./chunk-ZW6ZKHTF.js";
14
+
15
+ // src/commands/diff.ts
16
+ import { cancel, intro, isCancel, outro, select, spinner } from "@clack/prompts";
17
+ import chalk2 from "chalk";
18
+ import { existsSync } from "fs";
19
+
20
+ // src/fileDiff.ts
21
+ import { readdir, readFile } from "fs/promises";
22
+ import { join, relative } from "path";
23
+ import { createPatch } from "diff";
24
+ import chalk from "chalk";
25
+ async function readLocalFiles(dir) {
26
+ const result = /* @__PURE__ */ new Map();
27
+ await walk(dir, dir, result);
28
+ return result;
29
+ }
30
+ async function walk(root, current, out) {
31
+ const entries = await readdir(current, { withFileTypes: true });
32
+ await Promise.all(
33
+ entries.map(async (entry) => {
34
+ const full = join(current, entry.name);
35
+ if (entry.isDirectory()) {
36
+ await walk(root, full, out);
37
+ } else if (entry.isFile()) {
38
+ const rel = relative(root, full);
39
+ const content = await readFile(full, "utf8").catch(() => "");
40
+ out.set(rel, content);
41
+ }
42
+ })
43
+ );
44
+ }
45
+ function apiHeaders() {
46
+ const token = process.env["GITHUB_TOKEN"];
47
+ return {
48
+ Accept: "application/vnd.github+json",
49
+ "X-GitHub-Api-Version": "2022-11-28",
50
+ ...token ? { Authorization: `Bearer ${token}` } : {}
51
+ };
52
+ }
53
+ async function ghFetch(url) {
54
+ const res = await fetch(url, { headers: apiHeaders() });
55
+ if (!res.ok) {
56
+ const body = await res.text().catch(() => "");
57
+ throw new Error(`GitHub API ${res.status}: ${url}
58
+ ${body}`);
59
+ }
60
+ return res.json();
61
+ }
62
+ async function fetchGitHubFiles(coords, commitSha) {
63
+ const { owner, repo, path: skillRepoPath } = coords;
64
+ const commit = await ghFetch(
65
+ `https://api.github.com/repos/${owner}/${repo}/git/commits/${commitSha}`
66
+ );
67
+ const rootTreeSha = commit.tree.sha;
68
+ const tree = await ghFetch(
69
+ `https://api.github.com/repos/${owner}/${repo}/git/trees/${rootTreeSha}?recursive=1`
70
+ );
71
+ const prefix = skillRepoPath ? `${skillRepoPath}/` : "";
72
+ const blobs = tree.tree.filter(
73
+ (item) => item.type === "blob" && item.path.startsWith(prefix)
74
+ );
75
+ const result = /* @__PURE__ */ new Map();
76
+ await Promise.all(
77
+ blobs.map(async (blob) => {
78
+ const data = await ghFetch(
79
+ `https://api.github.com/repos/${owner}/${repo}/git/blobs/${blob.sha}`
80
+ );
81
+ const content = data.encoding === "base64" ? Buffer.from(data.content.replace(/\n/g, ""), "base64").toString("utf8") : data.content;
82
+ const relPath = prefix ? blob.path.slice(prefix.length) : blob.path;
83
+ result.set(relPath, content);
84
+ })
85
+ );
86
+ return result;
87
+ }
88
+ function computeFileDiffs(oldFiles, newFiles) {
89
+ const allPaths = /* @__PURE__ */ new Set([...oldFiles.keys(), ...newFiles.keys()]);
90
+ const diffs = [];
91
+ for (const path of allPaths) {
92
+ const oldContent = oldFiles.get(path) ?? "";
93
+ const newContent = newFiles.get(path) ?? "";
94
+ const status = !oldFiles.has(path) ? "added" : !newFiles.has(path) ? "deleted" : oldContent === newContent ? "unchanged" : "modified";
95
+ if (status === "unchanged") continue;
96
+ const patch = createPatch(path, oldContent, newContent, void 0, void 0, {
97
+ context: 3
98
+ });
99
+ const { additions, deletions } = countChanges(patch);
100
+ diffs.push({ path, status, additions, deletions, patch, oldContent, newContent });
101
+ }
102
+ const order = {
103
+ modified: 0,
104
+ added: 1,
105
+ deleted: 2,
106
+ unchanged: 3
107
+ };
108
+ return diffs.sort((a, b) => order[a.status] - order[b.status]);
109
+ }
110
+ function countChanges(patch) {
111
+ let additions = 0;
112
+ let deletions = 0;
113
+ for (const line of patch.split("\n")) {
114
+ if (line.startsWith("+") && !line.startsWith("+++")) additions++;
115
+ else if (line.startsWith("-") && !line.startsWith("---")) deletions++;
116
+ }
117
+ return { additions, deletions };
118
+ }
119
+ function renderPatch(diff, oldLabel, newLabel) {
120
+ if (!diff.patch) return chalk.dim("(no changes)");
121
+ const lines = diff.patch.split("\n");
122
+ const rendered = [];
123
+ for (const line of lines) {
124
+ if (line.startsWith("---")) {
125
+ rendered.push(chalk.dim(`--- ${oldLabel}/${diff.path}`));
126
+ } else if (line.startsWith("+++")) {
127
+ rendered.push(chalk.dim(`+++ ${newLabel}/${diff.path}`));
128
+ } else if (line.startsWith("@@")) {
129
+ rendered.push(chalk.cyan(line));
130
+ } else if (line.startsWith("+")) {
131
+ rendered.push(chalk.green(line));
132
+ } else if (line.startsWith("-")) {
133
+ rendered.push(chalk.red(line));
134
+ } else if (line.startsWith("\\")) {
135
+ rendered.push(chalk.dim(line));
136
+ } else {
137
+ rendered.push(line);
138
+ }
139
+ }
140
+ return rendered.join("\n");
141
+ }
142
+ function diffBadge(diff) {
143
+ const parts = [];
144
+ if (diff.additions > 0) parts.push(chalk.green(`+${diff.additions}`));
145
+ if (diff.deletions > 0) parts.push(chalk.red(`-${diff.deletions}`));
146
+ return parts.join(" ") || chalk.dim("no changes");
147
+ }
148
+ function statusSigil(status) {
149
+ switch (status) {
150
+ case "modified":
151
+ return chalk.yellow("M");
152
+ case "added":
153
+ return chalk.green("A");
154
+ case "deleted":
155
+ return chalk.red("D");
156
+ default:
157
+ return " ";
158
+ }
159
+ }
160
+
161
+ // src/commands/diff.ts
162
+ async function diffCommand(skillName, sha1Arg, sha2Arg) {
163
+ intro(chalk2.bold("augy") + chalk2.dim(" \u2014 diff browser"));
164
+ const registry = await readRegistry();
165
+ const skill = getSkill(registry, skillName);
166
+ if (!skill) {
167
+ cancel(`Skill "${skillName}" not found. Run \`augy list\` to see installed skills.`);
168
+ process.exit(1);
169
+ }
170
+ let oldFiles;
171
+ let newFiles;
172
+ let oldLabel;
173
+ let newLabel;
174
+ if (sha1Arg && sha2Arg) {
175
+ ({ files: oldFiles, label: oldLabel } = await resolveLocalArchive(skillName, sha1Arg, skill.history));
176
+ ({ files: newFiles, label: newLabel } = await resolveLocalArchive(skillName, sha2Arg, skill.history));
177
+ } else if (sha1Arg) {
178
+ ({ files: oldFiles, label: oldLabel } = await resolveInstalledFiles(skill));
179
+ ({ files: newFiles, label: newLabel } = await resolveArbitraryRef(skill, sha1Arg));
180
+ } else {
181
+ ({ files: oldFiles, label: oldLabel } = await resolveInstalledFiles(skill));
182
+ ({ files: newFiles, label: newLabel } = await resolveUpstream(skill));
183
+ }
184
+ const diffs = computeFileDiffs(oldFiles, newFiles);
185
+ const changed = diffs.filter((d) => d.status !== "unchanged");
186
+ if (!changed.length) {
187
+ outro(chalk2.green(`No differences found between ${oldLabel} and ${newLabel}.`));
188
+ return;
189
+ }
190
+ printSummary(skillName, oldLabel, newLabel, changed);
191
+ await browseFiles(changed, oldLabel, newLabel);
192
+ outro(chalk2.dim("Done."));
193
+ }
194
+ async function resolveInstalledFiles(skill) {
195
+ const activeEntry = Object.entries(skill.agents).find(([, a]) => a.active);
196
+ if (!activeEntry) throw new Error("No active agent installation found.");
197
+ const [, install] = activeEntry;
198
+ if (!existsSync(install.path)) {
199
+ throw new Error(`Installed path does not exist: ${install.path}`);
200
+ }
201
+ const s = spinner();
202
+ s.start("Reading installed files\u2026");
203
+ const files = await readLocalFiles(install.path);
204
+ s.stop(`Read ${files.size} local file(s)`);
205
+ return { files, label: shortSha(skill.sha) + " (installed)" };
206
+ }
207
+ async function resolveUpstream(skill) {
208
+ const s = spinner();
209
+ s.start("Fetching upstream from GitHub\u2026");
210
+ const coords = parseGitHubUrl(skill.source);
211
+ const remote = await discoverSkills(coords);
212
+ const match = remote.find((r) => r.name === skill.name) ?? remote[0];
213
+ if (!match) throw new Error("Could not find skill on GitHub.");
214
+ const files = await fetchGitHubFiles(
215
+ { ...coords, path: match.repoPath },
216
+ match.sha
217
+ );
218
+ s.stop(`Fetched ${files.size} remote file(s) ${chalk2.dim("@" + shortSha(match.sha))}`);
219
+ const label = match.sha === skill.sha ? shortSha(match.sha) + " (upstream = installed)" : shortSha(match.sha) + " (upstream)";
220
+ return { files, label };
221
+ }
222
+ async function resolveArbitraryRef(skill, shaPrefix) {
223
+ const histEntry = skill.history.find((v) => v.sha.startsWith(shaPrefix));
224
+ const fullSha = histEntry?.sha ?? shaPrefix;
225
+ if (archiveExists(skill.name, fullSha)) {
226
+ const archivePath = versionArchivePath(skill.name, fullSha);
227
+ const s2 = spinner();
228
+ s2.start(`Reading local archive ${shortSha(fullSha)}\u2026`);
229
+ const files2 = await readLocalFiles(archivePath);
230
+ s2.stop(`Read ${files2.size} archived file(s)`);
231
+ return { files: files2, label: shortSha(fullSha) + " (archive)" };
232
+ }
233
+ const s = spinner();
234
+ s.start(`Fetching GitHub @ ${shortSha(fullSha)}\u2026`);
235
+ const coords = parseGitHubUrl(skill.source);
236
+ const remote = await discoverSkills(coords);
237
+ const match = remote.find((r) => r.name === skill.name) ?? remote[0];
238
+ if (!match) throw new Error("Could not resolve skill path on GitHub.");
239
+ const files = await fetchGitHubFiles(
240
+ { ...coords, path: match.repoPath },
241
+ fullSha
242
+ );
243
+ s.stop(`Fetched ${files.size} file(s) ${chalk2.dim("@" + shortSha(fullSha))}`);
244
+ return { files, label: shortSha(fullSha) };
245
+ }
246
+ async function resolveLocalArchive(skillName, shaPrefix, history) {
247
+ const histEntry = history.find((v) => v.sha.startsWith(shaPrefix));
248
+ const fullSha = histEntry?.sha ?? shaPrefix;
249
+ if (!archiveExists(skillName, fullSha)) {
250
+ throw new Error(
251
+ `No local archive for "${skillName}" @ ${shortSha(fullSha)}.
252
+ Available: ${history.map((v) => shortSha(v.sha)).join(", ")}`
253
+ );
254
+ }
255
+ const archivePath = versionArchivePath(skillName, fullSha);
256
+ const s = spinner();
257
+ s.start(`Reading archive ${shortSha(fullSha)}\u2026`);
258
+ const files = await readLocalFiles(archivePath);
259
+ s.stop(`Read ${files.size} archived file(s)`);
260
+ return { files, label: shortSha(fullSha) };
261
+ }
262
+ function printSummary(skillName, oldLabel, newLabel, diffs) {
263
+ const totalAdd = diffs.reduce((n, d) => n + d.additions, 0);
264
+ const totalDel = diffs.reduce((n, d) => n + d.deletions, 0);
265
+ console.log();
266
+ console.log(
267
+ ` ${chalk2.bold(skillName)} ` + chalk2.dim(oldLabel) + chalk2.dim(" \u2192 ") + chalk2.green(newLabel)
268
+ );
269
+ console.log(
270
+ ` ${chalk2.bold(String(diffs.length))} file(s) changed ` + chalk2.green(`+${totalAdd}`) + " " + chalk2.red(`-${totalDel}`)
271
+ );
272
+ console.log();
273
+ for (const d of diffs) {
274
+ const sigil = statusSigil(d.status);
275
+ const badge = diffBadge(d);
276
+ console.log(` ${sigil} ${chalk2.bold(d.path)} ${badge}`);
277
+ }
278
+ console.log();
279
+ }
280
+ var EXIT_SENTINEL = "__EXIT__";
281
+ async function browseFiles(diffs, oldLabel, newLabel) {
282
+ while (true) {
283
+ const chosen = await select({
284
+ message: "Select a file to inspect",
285
+ options: [
286
+ ...diffs.map((d) => ({
287
+ value: d.path,
288
+ label: `${statusSigil(d.status)} ${chalk2.bold(d.path)}`,
289
+ hint: diffBadge(d)
290
+ })),
291
+ { value: EXIT_SENTINEL, label: chalk2.dim("\u2500\u2500 done \u2500\u2500"), hint: "" }
292
+ ]
293
+ });
294
+ if (isCancel(chosen) || chosen === EXIT_SENTINEL) break;
295
+ const diff = diffs.find((d) => d.path === chosen);
296
+ console.log();
297
+ console.log(renderPatch(diff, oldLabel, newLabel));
298
+ console.log();
299
+ }
300
+ }
301
+ export {
302
+ diffCommand
303
+ };
package/dist/index.js ADDED
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+ import { fileURLToPath } from "url";
6
+ import { join, dirname } from "path";
7
+ import { readFileSync } from "fs";
8
+ var __dirname = dirname(fileURLToPath(import.meta.url));
9
+ var pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf8"));
10
+ var program = new Command();
11
+ program.name("augy").description("Homebrew for AI agent skills \u2014 install, version, update, rollback").version(pkg.version);
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-FPAGHWX2.js");
14
+ await installCommand(url, opts ?? {});
15
+ });
16
+ program.command("update [skill]").description("Check for upstream changes and upgrade installed skills").action(async (skill) => {
17
+ const { updateCommand } = await import("./update-A3PSROUK.js");
18
+ await updateCommand(skill);
19
+ });
20
+ program.command("list").description("Show all installed skills with version + agent info").option("--json", "Output raw JSON registry").action(async (opts) => {
21
+ const { listCommand } = await import("./list-UJX2MYXK.js");
22
+ await listCommand(opts ?? {});
23
+ });
24
+ program.command("diff <skill> [sha1] [sha2]").description(
25
+ "Browse file-level diffs for a skill\n augy diff <skill> installed \u2194 upstream HEAD\n augy diff <skill> <sha> installed \u2194 specific SHA (archive or GitHub)\n augy diff <skill> <sha1> <sha2> two local archives side-by-side"
26
+ ).action(async (skill, sha1, sha2) => {
27
+ const { diffCommand } = await import("./diff-KYSWUXMH.js");
28
+ await diffCommand(skill, sha1, sha2);
29
+ });
30
+ program.command("scan").description("Find skills installed outside augy and optionally import them into the registry").action(async () => {
31
+ const { scanCommand } = await import("./scan-DKN7YBT5.js");
32
+ await scanCommand();
33
+ });
34
+ program.command("info <skill>").description("Show full metadata, version history, and description for an installed skill").action(async (skill) => {
35
+ const { infoCommand } = await import("./info-7BO4LXXS.js");
36
+ await infoCommand(skill);
37
+ });
38
+ program.command("search [query]").description("Search all taps for available skills (optionally filter by name)").action(async (query) => {
39
+ const { searchCommand } = await import("./search-2V2U23KC.js");
40
+ await searchCommand(query);
41
+ });
42
+ var tap = program.command("tap").description("Manage trusted repos (taps) for skill name resolution");
43
+ tap.command("add <repo>").description("Register a tap e.g. augy tap add owner/repo").option("--path <skills-dir>", "Subdirectory where skills live (default: skills)").option("--description <text>", "Optional description").action(async (repo, opts) => {
44
+ const { tapAddCommand } = await import("./tap-3RD3XZ56.js");
45
+ await tapAddCommand(repo, opts);
46
+ });
47
+ tap.command("remove <repo>").description("Unregister a tap").action(async (repo) => {
48
+ const { tapRemoveCommand } = await import("./tap-3RD3XZ56.js");
49
+ await tapRemoveCommand(repo);
50
+ });
51
+ tap.command("list").description("List all registered taps").action(async () => {
52
+ const { tapListCommand } = await import("./tap-3RD3XZ56.js");
53
+ await tapListCommand();
54
+ });
55
+ program.command("set-source <skill> <url>").description("Attach a GitHub source URL to a skill imported without one").action(async (skill, url) => {
56
+ const { setSourceCommand } = await import("./set-source-TITL27N3.js");
57
+ await setSourceCommand(skill, url);
58
+ });
59
+ program.command("uninstall <skill>").description("Remove a skill from all agent paths and the registry").action(async (skill) => {
60
+ const { uninstallCommand } = await import("./uninstall-3C3XZZEC.js");
61
+ await uninstallCommand(skill);
62
+ });
63
+ program.command("rollback <skill> [sha]").description("Restore a skill to a previous archived version").action(async (skill, sha) => {
64
+ const { rollbackCommand } = await import("./rollback-WAZV5HNG.js");
65
+ await rollbackCommand(skill, sha);
66
+ });
67
+ program.command("pin <skill>").description("Pin a skill so it is skipped during `augy update`").action(async (skill) => {
68
+ await setPinned(skill, true);
69
+ });
70
+ program.command("unpin <skill>").description("Allow a pinned skill to receive updates again").action(async (skill) => {
71
+ await setPinned(skill, false);
72
+ });
73
+ program.action(async () => {
74
+ const { installCommand } = await import("./install-FPAGHWX2.js");
75
+ await installCommand();
76
+ });
77
+ program.parse();
78
+ async function setPinned(skillName, pinned) {
79
+ const chalk = (await import("chalk")).default;
80
+ const { readRegistry, writeRegistry, getSkill } = await import("./registry-QVCNZXBZ.js");
81
+ const registry = await readRegistry();
82
+ const skill = getSkill(registry, skillName);
83
+ if (!skill) {
84
+ console.error(chalk.red(`Skill "${skillName}" not found in registry.`));
85
+ process.exit(1);
86
+ }
87
+ skill.pinned = pinned;
88
+ registry.skills[skillName] = skill;
89
+ await writeRegistry(registry);
90
+ const action = pinned ? chalk.yellow("pinned") : chalk.green("unpinned");
91
+ console.log(`${chalk.cyan(skillName)} is now ${action}.`);
92
+ }
@@ -0,0 +1,119 @@
1
+ import {
2
+ archiveExists
3
+ } from "./chunk-PX2LVUHV.js";
4
+ import {
5
+ AGENTS
6
+ } from "./chunk-R2TJ3UDO.js";
7
+ import {
8
+ getSkill,
9
+ readRegistry,
10
+ shortSha
11
+ } from "./chunk-ZW6ZKHTF.js";
12
+
13
+ // src/commands/info.ts
14
+ import chalk from "chalk";
15
+ import { readFile } from "fs/promises";
16
+ import { existsSync } from "fs";
17
+ import { join } from "path";
18
+ async function infoCommand(nameArg) {
19
+ const registry = await readRegistry();
20
+ const skill = getSkill(registry, nameArg);
21
+ if (!skill) {
22
+ console.error(
23
+ chalk.red(`Skill "${nameArg}" not found.`) + chalk.dim(" Run `augy list` to see installed skills.")
24
+ );
25
+ process.exit(1);
26
+ }
27
+ const hr = chalk.dim("\u2500".repeat(50));
28
+ console.log();
29
+ console.log(` ${chalk.cyan.bold(skill.name)}` + (skill.label ? ` ${chalk.dim(skill.label)}` : ""));
30
+ console.log(` ${hr}`);
31
+ console.log(` ${label("source")} ${skill.source}`);
32
+ if (skill.tap) {
33
+ const tap = registry.taps[skill.tap];
34
+ const tapDesc = tap?.description ? ` ${chalk.dim("\u2014")} ${tap.description}` : "";
35
+ console.log(` ${label("tap")} ${chalk.cyan(skill.tap)}${tapDesc}`);
36
+ }
37
+ console.log(
38
+ ` ${label("sha")} ${chalk.green(skill.shortSha)}` + chalk.dim(` (full: ${skill.sha})`)
39
+ );
40
+ console.log(` ${label("installed")} ${fmtDate(skill.installedAt)}`);
41
+ if (skill.updatedAt !== skill.installedAt) {
42
+ console.log(` ${label("updated")} ${fmtDate(skill.updatedAt)}`);
43
+ }
44
+ console.log(
45
+ ` ${label("pinned")} ${skill.pinned ? chalk.yellow("yes (skipped during `augy update`)") : chalk.dim("no")}`
46
+ );
47
+ console.log();
48
+ console.log(` ${chalk.bold("Agents")}`);
49
+ const agentEntries = Object.entries(skill.agents);
50
+ if (!agentEntries.length) {
51
+ console.log(` ${chalk.dim(" (none)")}`);
52
+ } else {
53
+ for (const [agentId, install] of agentEntries) {
54
+ const agentDef = AGENTS.find((a) => a.id === agentId);
55
+ const agentName = agentDef?.name ?? agentId;
56
+ const exists = existsSync(install.path);
57
+ const pathStatus = exists ? chalk.dim(install.path) : chalk.red(install.path) + chalk.red(" (missing!)");
58
+ const active = install.active ? "" : chalk.dim(" [inactive]");
59
+ console.log(` ${chalk.bold(agentName.padEnd(10))} ${pathStatus}${active}`);
60
+ }
61
+ }
62
+ console.log();
63
+ console.log(` ${chalk.bold("Version history")}`);
64
+ if (!skill.history.length) {
65
+ console.log(` ${chalk.dim(" No previous versions")}`);
66
+ } else {
67
+ const entries = [...skill.history].reverse();
68
+ for (const v of entries) {
69
+ const archived = archiveExists(skill.name, v.sha);
70
+ const archiveTag = archived ? chalk.dim(" [archived]") : chalk.red(" [archive missing]");
71
+ console.log(
72
+ ` ${chalk.dim(shortSha(v.sha))} ${fmtDate(v.installedAt)}${archiveTag}`
73
+ );
74
+ }
75
+ }
76
+ const description = await readSkillDescription(skill);
77
+ if (description) {
78
+ console.log();
79
+ console.log(` ${chalk.bold("Description")}`);
80
+ for (const line of description) {
81
+ console.log(` ${chalk.dim("\u2502")} ${line}`);
82
+ }
83
+ }
84
+ console.log();
85
+ }
86
+ function label(text) {
87
+ return chalk.dim(text.padEnd(9));
88
+ }
89
+ function fmtDate(iso) {
90
+ return new Date(iso).toLocaleString();
91
+ }
92
+ async function readSkillDescription(skill) {
93
+ const activeEntry = Object.entries(skill.agents).find(([, a]) => a.active);
94
+ if (!activeEntry) return null;
95
+ const skillMdPath = join(activeEntry[1].path, "SKILL.md");
96
+ if (!existsSync(skillMdPath)) return null;
97
+ try {
98
+ const raw = await readFile(skillMdPath, "utf8");
99
+ const lines = raw.split("\n");
100
+ const content = [];
101
+ let skippedH1 = false;
102
+ for (const line of lines) {
103
+ if (!skippedH1 && line.startsWith("# ")) {
104
+ skippedH1 = true;
105
+ continue;
106
+ }
107
+ if (!content.length && !line.trim()) continue;
108
+ if (content.length > 0 && line.startsWith("#")) break;
109
+ if (line.trim()) content.push(line);
110
+ if (content.length >= 5) break;
111
+ }
112
+ return content.length ? content : null;
113
+ } catch {
114
+ return null;
115
+ }
116
+ }
117
+ export {
118
+ infoCommand
119
+ };