@minhpnq1807/contextos 0.5.40 → 0.5.42

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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.42
4
+
5
+ - **Setup fallback when no skills found:** During `ctx setup`, if `syncSkills` completes but `detectExistingSkills` finds zero skills on the machine, the CLI now automatically offers the community skill library installer. After successful install, `syncSkills` re-runs to distribute the newly installed skills to all selected agents.
6
+ - **Reusable `runCommunitySkillInstaller()`:** Extracted the fetch → select → install flow into a shared function used by both `ctx skills` and the `ctx setup` fallback. Returns the number of successfully installed sources for downstream branching.
7
+
8
+ ## 0.5.41
9
+
10
+ - **Interactive community skill installer (`ctx skills`):** Replaced the info-only display with a fully functional multi-select installer. Users can now toggle multiple community library sources with `Space`, confirm with `Enter`, and the CLI automatically runs the appropriate install command for each selected source (`npx`, `git clone`, etc.).
11
+ - **Compact selection UI with URL hints:** The skill source picker now shows a clean box-styled header and each option displays its GitHub URL as a dimmed sub-line hint, matching the `◇ / │` visual language used throughout contextOS.
12
+ - **Prefixed install output (`runPrefixed`):** All child-process output during installation (including interactive npx prompts like "Ok to proceed?") is piped through a line-by-line prefixer that prepends `│ ` to every line. This keeps the visual box style consistent and prevents raw command output from breaking the layout. `stdin` remains inherited so users can still answer interactive prompts.
13
+ - **`multiSelect` hint support:** The multi-select component now accepts an optional `hint` property on each option, rendered as a dimmed indented line below the label. Used for URLs but available for any contextual sub-text.
14
+ - **Library install metadata:** `skill-library.js` now exports `getInstallCommands(libraryId)` returning structured install info (command, verify step, type) for each library source, keeping install logic out of the main CLI.
15
+
3
16
  ## 0.5.40
4
17
 
5
18
  - **Update notifier:** `ctx` now checks npm for newer versions in the background (once per day, 3s timeout). If a newer release exists, a boxed notice is printed at the very end of any command: `Update available: 0.5.39 → 0.5.40`. Check result is cached in `$CONTEXTOS_DATA/.update-check.json` to avoid repeated network calls.
package/bin/ctx.js CHANGED
@@ -5,7 +5,7 @@ import path from "node:path";
5
5
  import readline from "node:readline/promises";
6
6
  import { stdin as input, stdout as output } from "node:process";
7
7
  import { fileURLToPath } from "node:url";
8
- import { execFileSync } from "node:child_process";
8
+ import { execFileSync, execSync, spawn } from "node:child_process";
9
9
 
10
10
  import { readAgentsChain } from "../plugins/ctx/lib/reader.js";
11
11
  import { filterActionableRules, parseRules, scoreRules } from "../plugins/ctx/lib/analyzer.js";
@@ -28,14 +28,153 @@ import { installCopilotHooks } from "../plugins/ctx/lib/copilot-hooks.js";
28
28
  import { installCopilotMcp } from "../plugins/ctx/lib/copilot-mcp.js";
29
29
  import { syncRules } from "../plugins/ctx/lib/ruler-sync.js";
30
30
  import { writeInnerGitignore, ensureRootGitignore } from "../plugins/ctx/lib/gitignore.js";
31
- import { syncSkills } from "../plugins/ctx/lib/skillshare-sync.js";
31
+ import { syncSkills, detectExistingSkills } from "../plugins/ctx/lib/skillshare-sync.js";
32
32
  import { scanSkills, warmSkillEmbeddings } from "../plugins/ctx/lib/skill-discoverer.js";
33
33
  import { parsePassthroughArgs, runPassthrough } from "../plugins/ctx/lib/passthrough.js";
34
34
  import { parseAgentList, parseSetupArgs, setupSummaryLines } from "../plugins/ctx/lib/setup-wizard.js";
35
35
  import { multiSelect } from "../plugins/ctx/lib/multi-select.js";
36
36
  import { syncWorkflows, warmWorkflowEmbeddings } from "../plugins/ctx/lib/workflow-discoverer.js";
37
37
  import { checkForUpdate } from "../plugins/ctx/lib/update-notifier.js";
38
- import { fetchSkillsForAgents, printSkillRecommendations, getAllLibraries } from "../plugins/ctx/lib/skill-library.js";
38
+ import { fetchSkillsForAgents, printSkillRecommendations, getAllLibraries, getInstallCommands } from "../plugins/ctx/lib/skill-library.js";
39
+
40
+ /**
41
+ * Run a shell command with all output lines prefixed by │
42
+ * Keeps the visual box style consistent during child-process output.
43
+ * stdin is inherited so interactive prompts (e.g. npx "Ok to proceed?") still work.
44
+ */
45
+ function runPrefixed(cmd) {
46
+ const DIM = "\x1B[2m";
47
+ const RST = "\x1B[0m";
48
+ const pfx = `${DIM}│${RST} `;
49
+ return new Promise((resolve, reject) => {
50
+ const child = spawn("sh", ["-c", cmd], { stdio: ["inherit", "pipe", "pipe"] });
51
+ function pipe(stream, target) {
52
+ let needPrefix = true;
53
+ stream.on("data", (buf) => {
54
+ const str = buf.toString();
55
+ let out = "";
56
+ for (const ch of str) {
57
+ if (needPrefix) { out += pfx; needPrefix = false; }
58
+ out += ch;
59
+ if (ch === "\n") needPrefix = true;
60
+ }
61
+ target.write(out);
62
+ });
63
+ }
64
+ pipe(child.stdout, process.stdout);
65
+ pipe(child.stderr, process.stderr);
66
+ child.on("close", (code) => code === 0 ? resolve() : reject(new Error(`exit code ${code}`)));
67
+ });
68
+ }
69
+
70
+ /**
71
+ * Interactive community skill library installer.
72
+ * Fetches library metadata, shows a multiSelect, and runs install commands.
73
+ * @param {string[]} agents - Agent names to filter libraries for.
74
+ * @returns {Promise<number>} Number of successfully installed sources.
75
+ */
76
+ async function runCommunitySkillInstaller(agents = []) {
77
+ const RESET = "\x1B[0m";
78
+ const DIM = "\x1B[2m";
79
+ const CYAN = "\x1B[36m";
80
+ const GREEN = "\x1B[32m";
81
+ const YELLOW = "\x1B[33m";
82
+ const BOLD = "\x1B[1m";
83
+
84
+ console.log("Fetching community skill libraries...\n");
85
+ const libraryResults = await fetchSkillsForAgents(agents, { dataDir: contextOSDataDir() });
86
+
87
+ const totalSkills = libraryResults.reduce((sum, r) => sum + r.count, 0);
88
+ if (totalSkills === 0) {
89
+ console.log("No skills found. Check your network connection or try --refresh.");
90
+ return 0;
91
+ }
92
+
93
+ // Compact header
94
+ console.log(`${CYAN}◇${RESET} ${BOLD}Community skill libraries available:${RESET}`);
95
+ console.log(`${DIM}│${RESET} Browse and install curated skills from the community.`);
96
+ console.log(`${DIM}│${RESET}`);
97
+
98
+ const allLibs = getAllLibraries();
99
+ const availableLibs = allLibs.filter((lib) => {
100
+ const result = libraryResults.find((r) => r.library.id === lib.id);
101
+ return result && result.count > 0;
102
+ });
103
+
104
+ if (availableLibs.length === 0) {
105
+ console.log("No installable libraries available.");
106
+ return 0;
107
+ }
108
+
109
+ const selectedSources = await multiSelect({
110
+ message: "Select skill sources to install:",
111
+ options: availableLibs.map((lib) => {
112
+ const result = libraryResults.find((r) => r.library.id === lib.id);
113
+ return {
114
+ label: `${lib.name} (${result?.count || 0} skills)`,
115
+ value: lib.id,
116
+ hint: lib.url,
117
+ selected: false
118
+ };
119
+ })
120
+ });
121
+
122
+ if (!selectedSources || selectedSources.length === 0) {
123
+ console.log(`\n${DIM}No sources selected.${RESET}`);
124
+ return 0;
125
+ }
126
+
127
+ // Install each selected source
128
+ let successCount = 0;
129
+ for (const libId of selectedSources) {
130
+ const lib = allLibs.find((l) => l.id === libId);
131
+ if (!lib) continue;
132
+
133
+ const installInfo = getInstallCommands(libId);
134
+ if (!installInfo) {
135
+ console.log(`${YELLOW}⚠${RESET} No install info for ${lib.name}. Visit: ${lib.url}`);
136
+ continue;
137
+ }
138
+
139
+ console.log("");
140
+ console.log(`${CYAN}◇${RESET} ${BOLD}Installing from ${lib.name}${RESET}`);
141
+
142
+ if (installInfo.type === "manual") {
143
+ console.log(`${DIM}│${RESET} ${installInfo.instructions}`);
144
+ console.log(`${DIM}│${RESET} ${DIM}URL: ${lib.url}${RESET}`);
145
+ continue;
146
+ }
147
+
148
+ const installCmd = installInfo.fullInstall;
149
+ if (installCmd) {
150
+ console.log(`${DIM}│${RESET} ${GREEN}$ ${installCmd}${RESET}`);
151
+ console.log(`${DIM}│${RESET}`);
152
+
153
+ try {
154
+ await runPrefixed(installCmd);
155
+ successCount++;
156
+
157
+ if (installInfo.verify) {
158
+ try { await runPrefixed(installInfo.verify); } catch { /* best-effort */ }
159
+ }
160
+ console.log(`${DIM}│${RESET}`);
161
+ console.log(`${GREEN}✔${RESET} ${lib.name} installed successfully.`);
162
+ } catch (err) {
163
+ console.error(`${YELLOW}⚠${RESET} Install failed for ${lib.name}. Try manually:`);
164
+ console.error(` ${installCmd}`);
165
+ console.error(` ${DIM}${err.message}${RESET}`);
166
+ }
167
+ }
168
+ }
169
+
170
+ // Summary
171
+ console.log("");
172
+ if (successCount > 0) {
173
+ console.log(`${GREEN}✔${RESET} ${BOLD}${successCount} source${successCount > 1 ? "s" : ""} installed.${RESET}`);
174
+ console.log(`${DIM}│${RESET} Restart your agent to pick up new skills.`);
175
+ }
176
+ return successCount;
177
+ }
39
178
 
40
179
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
41
180
  const rootDir = path.resolve(__dirname, "..");
@@ -596,7 +735,8 @@ async function setup({ args = [], cwd = process.cwd() } = {}) {
596
735
  const skillAgents = options.agents.map((agent) => agent === "agy" ? "antigravity" : agent).join(",");
597
736
  const syncArgs = ["--skills", "--agents", skillAgents];
598
737
  if (options.yes) syncArgs.push("--yes");
599
- await streamSetupOutput(() => syncSkills({
738
+
739
+ const doSyncSkills = async () => streamSetupOutput(() => syncSkills({
600
740
  cwd,
601
741
  args: syncArgs,
602
742
  rebuildSkillEmbeddings: async ({ cwd: skillCwd, sourceDir }) => warmSkillEmbeddings({
@@ -606,6 +746,25 @@ async function setup({ args = [], cwd = process.cwd() } = {}) {
606
746
  skills: scanSkills({ cwd: skillCwd, roots: [sourceDir] })
607
747
  })
608
748
  }));
749
+
750
+ await doSyncSkills();
751
+
752
+ // Fallback: if no skills were found, offer community library installer
753
+ const existing = detectExistingSkills({ cwd });
754
+ const totalExisting = existing.reduce((sum, e) => sum + e.count, 0);
755
+ if (totalExisting === 0) {
756
+ console.log("");
757
+ console.log(`${YELLOW}⚠${RESET} No skills found on this machine.`);
758
+ console.log(`${DIM}│${RESET} Install community skills to get started.`);
759
+ console.log("");
760
+
761
+ const installed = await runCommunitySkillInstaller(options.agents);
762
+ if (installed > 0) {
763
+ console.log("");
764
+ console.log("◇ Re-syncing skills after install...");
765
+ await doSyncSkills();
766
+ }
767
+ }
609
768
  }
610
769
 
611
770
  console.log("");
@@ -613,12 +772,6 @@ async function setup({ args = [], cwd = process.cwd() } = {}) {
613
772
  console.log("│ Next: restart/open your agent from this project directory.");
614
773
  console.log("│ Try: ctx debug -- \"Recheck authen flow\"");
615
774
  console.log("");
616
-
617
- // Recommend community skills based on selected agents
618
- try {
619
- const libraryResults = await fetchSkillsForAgents(options.agents, { dataDir: contextOSDataDir() });
620
- printSkillRecommendations(libraryResults);
621
- } catch { /* skill library is best-effort */ }
622
775
  }
623
776
 
624
777
  const args = process.argv.slice(2);
@@ -701,7 +854,7 @@ try {
701
854
  if (!task.trim()) throw new Error('Usage: ctx benchmark -- "task"');
702
855
  console.log(formatBenchmark(benchmarkWorkspace({ cwd: process.cwd(), task })));
703
856
  } else if (command === "skills") {
704
- // Browse community skill libraries
857
+ // Interactive community skill library selector + installer
705
858
  const agentsFlag = args.indexOf("--agents");
706
859
  const forceRefresh = args.includes("--refresh");
707
860
  let agents;
@@ -710,21 +863,19 @@ try {
710
863
  } else {
711
864
  agents = ["codex", "claude", "agy", "copilot"];
712
865
  }
713
- console.log("Fetching community skill libraries...\n");
714
- const libraryResults = await fetchSkillsForAgents(agents, {
715
- dataDir: contextOSDataDir()
716
- });
717
- printSkillRecommendations(libraryResults);
718
- console.log("");
719
- const totalSkills = libraryResults.reduce((sum, r) => sum + r.count, 0);
720
- if (totalSkills === 0) {
721
- console.log("No skills found. Check your network connection or try --refresh.");
722
- } else {
723
- console.log(`Total: ${totalSkills} skills across ${libraryResults.filter((r) => r.count > 0).length} libraries.`);
724
- console.log("");
725
- console.log("To install skills, visit the library URLs above or use:");
726
- console.log(" ctx sync --skills Sync skills across agents via skillshare");
866
+
867
+ const DIM = "\x1B[2m";
868
+ const RESET = "\x1B[0m";
869
+ const CYAN = "\x1B[36m";
870
+ const GREEN = "\x1B[32m";
871
+ const YELLOW = "\x1B[33m";
872
+ const BOLD = "\x1B[1m";
873
+
874
+ const installed = await runCommunitySkillInstaller(agents);
875
+ if (installed === 0) {
876
+ console.log(`\n${DIM}No installations were completed.${RESET}`);
727
877
  }
878
+ console.log("");
728
879
  } else if (command === "sync") {
729
880
  if (args.includes("--workflows")) {
730
881
  await syncWorkflows({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minhpnq1807/contextos",
3
- "version": "0.5.40",
3
+ "version": "0.5.42",
4
4
  "description": "Task-aware AGENTS.md context injection and compliance reporting for Codex, Claude Code, and Antigravity.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ctx",
3
- "version": "0.5.37",
3
+ "version": "0.5.41",
4
4
  "description": "Inject task-relevant AGENTS.md rules into Codex through plugin hooks.",
5
5
  "author": {
6
6
  "name": "ContextOS"
@@ -62,6 +62,9 @@ export function multiSelect({ message, options }) {
62
62
  const label = isCursor ? `${BOLD}${CYAN}${options[i].label}${RESET}` : options[i].label;
63
63
  const pointer = isCursor ? `${CYAN}❯${RESET}` : " ";
64
64
  lines.push(` ${pointer} ${checkbox} ${label}`);
65
+ if (options[i].hint) {
66
+ lines.push(` ${DIM}${options[i].hint}${RESET}`);
67
+ }
65
68
  }
66
69
  return lines;
67
70
  }
@@ -140,3 +143,113 @@ export function multiSelect({ message, options }) {
140
143
  draw();
141
144
  });
142
145
  }
146
+
147
+ /**
148
+ * Interactive single-select prompt (radio-button style).
149
+ *
150
+ * @param {{ message: string, options: Array<{label: string, value: string, disabled?: boolean, isHeader?: boolean}> }} config
151
+ * @returns {Promise<string|null>} selected value or null
152
+ */
153
+ export function singleSelect({ message, options }) {
154
+ return new Promise((resolve) => {
155
+ if (!process.stdin.isTTY) {
156
+ // Non-interactive: return first non-disabled
157
+ const first = options.find((o) => !o.disabled);
158
+ resolve(first ? first.value : null);
159
+ return;
160
+ }
161
+
162
+ // Filter to only selectable items, but keep the full list for rendering
163
+ const selectableIndices = options
164
+ .map((o, i) => (o.disabled ? -1 : i))
165
+ .filter((i) => i >= 0);
166
+ if (selectableIndices.length === 0) { resolve(null); return; }
167
+
168
+ let cursorIdx = 0; // index into selectableIndices
169
+ let cursor = selectableIndices[0]; // actual index in options
170
+
171
+ function render() {
172
+ const lines = [];
173
+ lines.push(`${CYAN}◇${RESET} ${message}`);
174
+ lines.push(`${DIM} Use ↑/↓ to navigate, Enter to select${RESET}`);
175
+ for (let i = 0; i < options.length; i++) {
176
+ const opt = options[i];
177
+ if (opt.disabled || opt.isHeader) {
178
+ lines.push(` ${DIM}${opt.label}${RESET}`);
179
+ continue;
180
+ }
181
+ const isCursor = i === cursor;
182
+ const radio = isCursor ? `${GREEN}◉${RESET}` : `${DIM}○${RESET}`;
183
+ const label = isCursor ? `${BOLD}${CYAN}${opt.label}${RESET}` : opt.label;
184
+ const pointer = isCursor ? `${CYAN}❯${RESET}` : " ";
185
+ lines.push(` ${pointer} ${radio} ${label}`);
186
+ }
187
+ return lines;
188
+ }
189
+
190
+ let prevLineCount = 0;
191
+
192
+ function draw() {
193
+ const lines = render();
194
+ if (prevLineCount > 0) {
195
+ process.stdout.write(`\x1B[${prevLineCount}A`);
196
+ for (let i = 0; i < prevLineCount; i++) {
197
+ process.stdout.write("\x1B[2K");
198
+ if (i < prevLineCount - 1) process.stdout.write("\x1B[1B");
199
+ }
200
+ process.stdout.write(`\x1B[${prevLineCount - 1}A`);
201
+ }
202
+ process.stdout.write(lines.join("\n") + "\n");
203
+ prevLineCount = lines.length;
204
+ }
205
+
206
+ process.stdout.write(HIDE_CURSOR);
207
+
208
+ const wasRaw = process.stdin.isRaw;
209
+ process.stdin.setRawMode(true);
210
+ process.stdin.resume();
211
+ process.stdin.setEncoding("utf8");
212
+
213
+ function cleanup() {
214
+ process.stdin.setRawMode(wasRaw || false);
215
+ process.stdin.pause();
216
+ process.stdin.removeListener("data", onData);
217
+ process.stdout.write(SHOW_CURSOR);
218
+ }
219
+
220
+ function onData(data) {
221
+ if (matchKey(data, KEYS.CTRL_C)) {
222
+ cleanup();
223
+ process.exit(0);
224
+ }
225
+
226
+ if (matchKey(data, KEYS.UP) || matchKey(data, KEYS.K)) {
227
+ cursorIdx = (cursorIdx - 1 + selectableIndices.length) % selectableIndices.length;
228
+ cursor = selectableIndices[cursorIdx];
229
+ draw();
230
+ } else if (matchKey(data, KEYS.DOWN) || matchKey(data, KEYS.J)) {
231
+ cursorIdx = (cursorIdx + 1) % selectableIndices.length;
232
+ cursor = selectableIndices[cursorIdx];
233
+ draw();
234
+ } else if (matchKey(data, KEYS.ENTER)) {
235
+ cleanup();
236
+ const selected = options[cursor];
237
+ // Clear and show result
238
+ if (prevLineCount > 0) {
239
+ process.stdout.write(`\x1B[${prevLineCount}A`);
240
+ for (let i = 0; i < prevLineCount; i++) {
241
+ process.stdout.write("\x1B[2K");
242
+ if (i < prevLineCount - 1) process.stdout.write("\x1B[1B");
243
+ }
244
+ process.stdout.write(`\x1B[${prevLineCount - 1}A`);
245
+ }
246
+ process.stdout.write(`${CYAN}◇${RESET} ${message}\n`);
247
+ process.stdout.write(`${DIM}│${RESET} ${GREEN}${selected.label}${RESET}\n`);
248
+ resolve(selected.value);
249
+ }
250
+ }
251
+
252
+ process.stdin.on("data", onData);
253
+ draw();
254
+ });
255
+ }
@@ -19,25 +19,64 @@ const SKILL_LIBRARIES = [
19
19
  url: "https://github.com/sickn33/antigravity-awesome-skills",
20
20
  rawReadmeUrl: "https://raw.githubusercontent.com/sickn33/antigravity-awesome-skills/main/README.md",
21
21
  agents: ["agy", "claude", "codex", "copilot"], // universal library
22
- description: "1,400+ agentic skills for all agents"
22
+ description: "1,400+ agentic skills for all agents",
23
+ install: {
24
+ type: "npx",
25
+ fullInstall: "npx antigravity-awesome-skills",
26
+ agentFlags: {
27
+ codex: "npx antigravity-awesome-skills --codex",
28
+ claude: "npx antigravity-awesome-skills --claude",
29
+ agy: "npx antigravity-awesome-skills --antigravity",
30
+ copilot: "npx antigravity-awesome-skills --cursor",
31
+ gemini: "npx antigravity-awesome-skills --gemini"
32
+ },
33
+ verify: 'test -d ~/.agents/skills && echo "Skills installed in ~/.agents/skills"'
34
+ }
23
35
  },
24
36
  {
25
37
  id: "awesome-claude",
26
38
  name: "Awesome Claude Skills",
27
39
  repo: "ComposioHQ/awesome-claude-skills",
28
40
  url: "https://github.com/ComposioHQ/awesome-claude-skills",
29
- rawReadmeUrl: "https://raw.githubusercontent.com/ComposioHQ/awesome-claude-skills/main/README.md",
41
+ rawReadmeUrl: "https://raw.githubusercontent.com/ComposioHQ/awesome-claude-skills/master/README.md",
30
42
  agents: ["claude"],
31
- description: "Curated Claude Code skills & workflows"
43
+ description: "Curated Claude Code skills & workflows",
44
+ install: {
45
+ type: "git-clone",
46
+ clone: "git clone https://github.com/ComposioHQ/awesome-claude-skills.git",
47
+ skillDir: "~/.claude/skills",
48
+ copyCommand: (skillName) =>
49
+ `cp -r awesome-claude-skills/${skillName} ~/.claude/skills/${skillName}`,
50
+ fullInstall: [
51
+ "git clone https://github.com/ComposioHQ/awesome-claude-skills.git",
52
+ "mkdir -p ~/.claude/skills",
53
+ 'for d in awesome-claude-skills/*/; do [ -f "$d/SKILL.md" ] && cp -r "$d" ~/.claude/skills/; done'
54
+ ].join(" && "),
55
+ verify: 'ls ~/.claude/skills/'
56
+ }
32
57
  },
33
58
  {
34
59
  id: "awesome-codex",
35
60
  name: "Awesome Codex Skills",
36
61
  repo: "ComposioHQ/awesome-codex-skills",
37
62
  url: "https://github.com/ComposioHQ/awesome-codex-skills",
38
- rawReadmeUrl: "https://raw.githubusercontent.com/ComposioHQ/awesome-codex-skills/main/README.md",
63
+ rawReadmeUrl: "https://raw.githubusercontent.com/ComposioHQ/awesome-codex-skills/master/README.md",
39
64
  agents: ["codex"],
40
- description: "Practical Codex CLI skills & automations"
65
+ description: "Practical Codex CLI skills & automations",
66
+ install: {
67
+ type: "git-clone-python",
68
+ clone: "git clone https://github.com/ComposioHQ/awesome-codex-skills.git",
69
+ skillDir: "~/.codex/skills",
70
+ installScript: (skillName) =>
71
+ `python awesome-codex-skills/skill-installer/scripts/install-skill-from-github.py --repo ComposioHQ/awesome-codex-skills --path ${skillName}`,
72
+ fullInstall: [
73
+ "git clone https://github.com/ComposioHQ/awesome-codex-skills.git",
74
+ "cd awesome-codex-skills",
75
+ "mkdir -p ~/.codex/skills",
76
+ 'for d in */; do [ -f "$d/SKILL.md" ] && [ "$d" != "skill-installer/" ] && cp -r "$d" ~/.codex/skills/; done'
77
+ ].join(" && "),
78
+ verify: 'ls ~/.codex/skills/'
79
+ }
41
80
  },
42
81
  {
43
82
  id: "awesome-copilot",
@@ -46,7 +85,13 @@ const SKILL_LIBRARIES = [
46
85
  url: "https://github.com/github/awesome-copilot",
47
86
  rawReadmeUrl: "https://raw.githubusercontent.com/github/awesome-copilot/main/README.md",
48
87
  agents: ["copilot"],
49
- description: "Community GitHub Copilot instructions & agents"
88
+ description: "Community GitHub Copilot instructions & agents",
89
+ install: {
90
+ type: "manual",
91
+ fullInstall: null,
92
+ instructions: "Browse the repository and copy relevant .instructions.md files to your .github/ directory.",
93
+ url: "https://github.com/github/awesome-copilot"
94
+ }
50
95
  }
51
96
  ];
52
97
 
@@ -288,3 +333,32 @@ export function printSkillRecommendations(libraryResults, { logger = console.log
288
333
  export function getAllLibraries() {
289
334
  return SKILL_LIBRARIES;
290
335
  }
336
+
337
+ /**
338
+ * Get install commands for a specific library.
339
+ * @param {string} libraryId
340
+ * @param {string} [agent] - optional agent to target
341
+ * @returns {{ fullInstall: string|null, agentInstall: string|null, verify: string|null, instructions: string|null, type: string }}
342
+ */
343
+ export function getInstallCommands(libraryId, agent) {
344
+ const lib = SKILL_LIBRARIES.find((l) => l.id === libraryId);
345
+ if (!lib || !lib.install) return null;
346
+
347
+ const inst = lib.install;
348
+ const result = {
349
+ type: inst.type,
350
+ fullInstall: typeof inst.fullInstall === "string" ? inst.fullInstall : null,
351
+ agentInstall: null,
352
+ verify: inst.verify || null,
353
+ instructions: inst.instructions || null,
354
+ url: inst.url || lib.url
355
+ };
356
+
357
+ // Agent-specific install (only for npx-based libs)
358
+ if (agent && inst.agentFlags) {
359
+ const normalized = agent.toLowerCase().replace("antigravity", "agy");
360
+ result.agentInstall = inst.agentFlags[normalized] || null;
361
+ }
362
+
363
+ return result;
364
+ }