@minhpnq1807/contextos 0.5.3 → 0.5.5

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,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.5
4
+
5
+ - Discovers all skill roots containing `SKILL.md` under global/project `.gemini`, `.codex`, and `.claude` directories before `ctx sync --skills`.
6
+ - Skips temporary/cache directories such as `.tmp`, `.git`, and `node_modules` while discovering skill roots.
7
+
8
+ ## 0.5.4
9
+
10
+ - Bridges legacy Antigravity skill directories (`~/.gemini/antigravity/skills` and `~/.gemini/antigravity-cli/skills`) into the skillshare source before `ctx sync --skills`.
11
+ - Reads custom `sources.skills` from `~/.config/skillshare/config.yaml` so ContextOS writes to the actual skillshare source path.
12
+
3
13
  ## 0.5.3
4
14
 
5
15
  - Skips project MCP imports whose command is an absolute path that does not exist, preventing placeholder paths such as `/home/user/.cargo/bin/mcp-rtk` from reaching Antigravity.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minhpnq1807/contextos",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
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": {
@@ -50,7 +50,22 @@ export function skillshareConfigDir({ home = os.homedir() } = {}) {
50
50
  }
51
51
 
52
52
  export function skillshareSourceDir({ home = os.homedir() } = {}) {
53
- return path.join(skillshareConfigDir({ home }), "skills");
53
+ return readSkillshareSourceDir({ home }) || path.join(skillshareConfigDir({ home }), "skills");
54
+ }
55
+
56
+ function readSkillshareSourceDir({ home = os.homedir() } = {}) {
57
+ const configPath = path.join(skillshareConfigDir({ home }), "config.yaml");
58
+ if (!fs.existsSync(configPath)) return null;
59
+ const content = fs.readFileSync(configPath, "utf8");
60
+ const match = content.match(/^\s{2}skills:\s*(.+?)\s*$/m);
61
+ if (!match) return null;
62
+ return expandHome(match[1].replace(/^["']|["']$/g, ""), home);
63
+ }
64
+
65
+ function expandHome(value, home) {
66
+ if (value === "~") return home;
67
+ if (value.startsWith("~/")) return path.join(home, value.slice(2));
68
+ return value;
54
69
  }
55
70
 
56
71
  export function checkSkillshareInstalled({ run = runCommand } = {}) {
@@ -107,7 +122,7 @@ export function detectExistingSkills({ cwd = process.cwd(), home = os.homedir()
107
122
  }
108
123
 
109
124
  function skillRoots({ cwd, home }) {
110
- return [
125
+ return uniquePaths([
111
126
  path.join(home, ".claude", "skills"),
112
127
  path.join(home, ".codex", "skills"),
113
128
  path.join(home, ".gemini", "antigravity", "skills"),
@@ -115,8 +130,67 @@ function skillRoots({ cwd, home }) {
115
130
  path.join(cwd, ".claude", "skills"),
116
131
  path.join(cwd, ".codex", "skills"),
117
132
  path.join(cwd, ".gemini", "antigravity", "skills"),
118
- path.join(cwd, ".gemini", "antigravity-cli", "skills")
119
- ];
133
+ path.join(cwd, ".gemini", "antigravity-cli", "skills"),
134
+ ...discoverSkillRoots({ cwd, home })
135
+ ]);
136
+ }
137
+
138
+ function antigravityLegacyRoots({ cwd, home }) {
139
+ return uniquePaths([
140
+ path.join(home, ".gemini", "antigravity", "skills"),
141
+ path.join(home, ".gemini", "antigravity-cli", "skills"),
142
+ path.join(cwd, ".gemini", "antigravity", "skills"),
143
+ path.join(cwd, ".gemini", "antigravity-cli", "skills"),
144
+ ...discoverSkillRoots({ cwd, home })
145
+ ]);
146
+ }
147
+
148
+ export function discoverSkillRoots({ cwd = process.cwd(), home = os.homedir() } = {}) {
149
+ const roots = [];
150
+ for (const base of [
151
+ path.join(home, ".gemini"),
152
+ path.join(home, ".codex"),
153
+ path.join(home, ".claude"),
154
+ path.join(cwd, ".gemini"),
155
+ path.join(cwd, ".codex"),
156
+ path.join(cwd, ".claude")
157
+ ]) {
158
+ findSkillRoots(base, 0, roots);
159
+ }
160
+ return uniquePaths(roots);
161
+ }
162
+
163
+ function findSkillRoots(directory, depth, roots) {
164
+ if (depth > 5) return;
165
+ let entries = [];
166
+ try {
167
+ entries = fs.readdirSync(directory, { withFileTypes: true });
168
+ } catch {
169
+ return;
170
+ }
171
+ if (entries.some((entry) => entry.isFile() && entry.name === "SKILL.md")) {
172
+ roots.push(path.dirname(directory));
173
+ return;
174
+ }
175
+ for (const entry of entries) {
176
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
177
+ if (entry.name === ".git" || entry.name === ".tmp" || entry.name === "node_modules") continue;
178
+ const fullPath = path.join(directory, entry.name);
179
+ if (entry.isSymbolicLink() && !safeStat(fullPath)?.isDirectory()) continue;
180
+ findSkillRoots(fullPath, depth + 1, roots);
181
+ }
182
+ }
183
+
184
+ function uniquePaths(paths) {
185
+ const seen = new Set();
186
+ const result = [];
187
+ for (const item of paths) {
188
+ const normalized = path.resolve(item);
189
+ if (seen.has(normalized)) continue;
190
+ seen.add(normalized);
191
+ result.push(item);
192
+ }
193
+ return result;
120
194
  }
121
195
 
122
196
  function countSkillFiles(root) {
@@ -158,6 +232,57 @@ function safeStat(filePath) {
158
232
  }
159
233
  }
160
234
 
235
+ function safeRealpath(filePath) {
236
+ try {
237
+ return fs.realpathSync(filePath);
238
+ } catch {
239
+ return null;
240
+ }
241
+ }
242
+
243
+ function copyDirectory(sourceDir, targetDir) {
244
+ fs.mkdirSync(targetDir, { recursive: true });
245
+ for (const entry of fs.readdirSync(sourceDir, { withFileTypes: true })) {
246
+ const source = path.join(sourceDir, entry.name);
247
+ const target = path.join(targetDir, entry.name);
248
+ if (entry.isDirectory()) {
249
+ copyDirectory(source, target);
250
+ } else if (entry.isSymbolicLink()) {
251
+ const link = fs.readlinkSync(source);
252
+ fs.symlinkSync(link, target);
253
+ } else if (entry.isFile()) {
254
+ fs.copyFileSync(source, target);
255
+ }
256
+ }
257
+ }
258
+
259
+ export function collectAntigravityLegacySkills({
260
+ cwd = process.cwd(),
261
+ home = os.homedir(),
262
+ sourceDir = skillshareSourceDir({ home }),
263
+ dryRun = false
264
+ } = {}) {
265
+ const sourceReal = safeRealpath(sourceDir);
266
+ const copied = [];
267
+ const skipped = [];
268
+ for (const root of antigravityLegacyRoots({ cwd, home })) {
269
+ const rootReal = safeRealpath(root);
270
+ if (!rootReal || rootReal === sourceReal) continue;
271
+ for (const skillFile of findSkillFiles(root)) {
272
+ const skillDir = path.dirname(skillFile);
273
+ const name = path.basename(skillDir);
274
+ const targetDir = path.join(sourceDir, name);
275
+ if (fs.existsSync(targetDir)) {
276
+ skipped.push(name);
277
+ continue;
278
+ }
279
+ if (!dryRun) copyDirectory(skillDir, targetDir);
280
+ copied.push(name);
281
+ }
282
+ }
283
+ return { copied: [...new Set(copied)], skipped: [...new Set(skipped)], sourceDir };
284
+ }
285
+
161
286
  export function isSkillshareInitialized({ home = os.homedir() } = {}) {
162
287
  return fs.existsSync(skillshareConfigDir({ home }));
163
288
  }
@@ -215,6 +340,16 @@ export async function syncSkills({
215
340
  }
216
341
  }
217
342
 
343
+ if (!options.noCollect) {
344
+ const legacy = collectAntigravityLegacySkills({ cwd, home, dryRun: options.dryRun });
345
+ if (legacy.copied.length || legacy.skipped.length) {
346
+ const value = options.dryRun
347
+ ? `dry-run (${legacy.copied.length} would copy, ${legacy.skipped.length} already present)`
348
+ : `✓ ${legacy.copied.length} copied, ${legacy.skipped.length} already present`;
349
+ logger(statusLine("Collecting Antigravity legacy skills...", value));
350
+ }
351
+ }
352
+
218
353
  const syncArgs = ["sync"];
219
354
  if (options.dryRun) syncArgs.push("--dry-run");
220
355
  if (options.agents.length) syncArgs.push("--agents", options.agents.join(","));