@maccesar/aiskills 1.10.0 → 1.12.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.
package/README.md CHANGED
@@ -40,7 +40,7 @@ claude # or gemini, or codex
40
40
 
41
41
  Installed files:
42
42
  - All skills to `~/.agents/skills/`
43
- - Platform symlinks in `~/.claude/skills/`, `~/.gemini/skills/`, or `~/.codex/skills/`
43
+ - Platform symlinks in `~/.claude/skills/` and `~/.gemini/skills/` (Codex CLI auto-discovers from `~/.agents/skills/` — no symlink needed)
44
44
 
45
45
  ### Which option should I use?
46
46
 
@@ -59,7 +59,7 @@ Installed files:
59
59
  | --------------------------------------------------------- | --------- | ------------------- |
60
60
  | [Claude Code](https://claude.ai/claude-code) | Supported | `~/.claude/skills/` |
61
61
  | [Gemini CLI](https://github.com/google-gemini/gemini-cli) | Supported | `~/.gemini/skills/` |
62
- | [Codex CLI](https://developers.openai.com/codex/cli/) | Supported | `~/.codex/skills/` |
62
+ | [Codex CLI](https://developers.openai.com/codex/cli/) | Supported | `~/.agents/skills/` (auto-discovered) |
63
63
 
64
64
  All three platforms use the same Agent Skills format: a `SKILL.md` file with YAML frontmatter that tells the assistant when to use the skill and what to do.
65
65
 
package/lib/cleanup.js CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  LEGACY_COMMANDS,
10
10
  getAgentsSkillsDir,
11
11
  getClaudeCommandsDir,
12
+ getCodexSkillsDir,
12
13
  } from './config.js';
13
14
  import { detectPlatforms } from './platform.js';
14
15
  import { existsSync, lstatSync, rmSync } from 'fs';
@@ -53,6 +54,14 @@ export function removeLegacySkillSymlinks(platformSkillsDir) {
53
54
  return removeSkillSymlinks(platformSkillsDir, { legacyOnly: true });
54
55
  }
55
56
 
57
+ // Codex reads skills from the canonical ~/.agents/skills/, so any aiskills-managed
58
+ // symlinks at ~/.codex/skills/ from earlier versions are redundant and should be
59
+ // cleaned up. Targets both active and legacy skill names.
60
+ export function removeCodexRedundantSymlinks(baseDir) {
61
+ const codexSkillsDir = getCodexSkillsDir(baseDir);
62
+ return removeSkillSymlinks(codexSkillsDir);
63
+ }
64
+
56
65
  export function removeSkills(baseDir, options = {}) {
57
66
  const skillsDir = getAgentsSkillsDir(baseDir);
58
67
  const skillList = getSkillList(options);
@@ -74,6 +83,8 @@ export function cleanupLegacyArtifacts(baseDir) {
74
83
  removeLegacySkillSymlinks(platform.skillsDir);
75
84
  }
76
85
 
86
+ removeCodexRedundantSymlinks(baseDir);
87
+
77
88
  if (baseDir) {
78
89
  removeSkills(undefined, { legacyOnly: true });
79
90
  removeCommands(undefined, { legacyOnly: true });
@@ -81,6 +92,7 @@ export function cleanupLegacyArtifacts(baseDir) {
81
92
  for (const platform of globalPlatforms) {
82
93
  removeLegacySkillSymlinks(platform.skillsDir);
83
94
  }
95
+ removeCodexRedundantSymlinks(undefined);
84
96
  }
85
97
  }
86
98
 
@@ -89,6 +101,7 @@ export default {
89
101
  getCommandList,
90
102
  removeSkillSymlinks,
91
103
  removeLegacySkillSymlinks,
104
+ removeCodexRedundantSymlinks,
92
105
  removeSkills,
93
106
  removeCommands,
94
107
  cleanupLegacyArtifacts,
@@ -86,7 +86,8 @@ export async function skillsCommand(options) {
86
86
 
87
87
  if (detectedPlatforms.length === 0 && !options.path && !isLocal) {
88
88
  console.log(chalk.yellow('No AI coding assistants detected globally.'));
89
- console.log('Install one of: Claude Code, Gemini CLI, or Codex CLI');
89
+ console.log('Install one of: Claude Code or Gemini CLI.');
90
+ console.log('(Codex CLI is supported via the canonical ~/.agents/skills/ — no platform-specific symlink needed.)');
90
91
  console.log('Or use: aiskills install --local');
91
92
  process.exit(1);
92
93
  }
package/lib/config.js CHANGED
@@ -68,12 +68,9 @@ export const getPlatforms = (baseDir = os.homedir()) => [
68
68
  skillsDir: getGeminiSkillsDir(baseDir),
69
69
  configDir: path.join(baseDir, '.gemini'),
70
70
  },
71
- {
72
- name: 'codex',
73
- displayName: 'Codex CLI',
74
- skillsDir: getCodexSkillsDir(baseDir),
75
- configDir: path.join(baseDir, '.codex'),
76
- },
71
+ // Codex CLI is intentionally omitted: it auto-discovers skills from the
72
+ // canonical ~/.agents/skills/ per the agentskills.io standard, so a
73
+ // platform-specific symlink at ~/.codex/skills/ would be redundant.
77
74
  ];
78
75
 
79
76
  // API configuration
package/lib/platform.js CHANGED
@@ -26,7 +26,7 @@ export function detectPlatforms(baseDir) {
26
26
 
27
27
  /**
28
28
  * Get platform by name
29
- * @param {string} name - Platform name (claude, gemini, codex)
29
+ * @param {string} name - Platform name (claude, gemini)
30
30
  * @param {string} baseDir - Optional base directory
31
31
  * @returns {Object|null} Platform object or null
32
32
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maccesar/aiskills",
3
- "version": "1.10.0",
3
+ "version": "1.12.0",
4
4
  "description": "AI coding assistant skills for Claude Code, Gemini CLI, and Codex CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -186,13 +186,13 @@
186
186
  // Update badge text
187
187
  viewBadge.textContent = currentView === 'mobile' ? 'Mobile' : 'WEB';
188
188
 
189
- // Update toggle icon: show the icon of the OTHER mode (what you'll switch to)
189
+ // Update toggle icon: show the icon of the CURRENT mode (matches the badge)
190
190
  if (currentView === 'mobile') {
191
- iconPhone.classList.add('hidden');
192
- iconMonitor.classList.remove('hidden');
193
- } else {
194
191
  iconPhone.classList.remove('hidden');
195
192
  iconMonitor.classList.add('hidden');
193
+ } else {
194
+ iconPhone.classList.add('hidden');
195
+ iconMonitor.classList.remove('hidden');
196
196
  }
197
197
  }
198
198
  applyViewMode();
@@ -219,6 +219,21 @@
219
219
  var frameMobile = document.getElementById('viewer-frame-mobile');
220
220
  var frameWeb = document.getElementById('viewer-frame-web');
221
221
 
222
+ // Inject scrollbar-hiding CSS into iframes after load (cosmetic, viewer-level only).
223
+ // Keeps source HTMLs untouched so they're clean for downstream handoff.
224
+ function injectScrollbarCSS(frame) {
225
+ try {
226
+ var doc = frame.contentDocument || frame.contentWindow.document;
227
+ if (!doc || doc.getElementById('__no-scrollbar-style')) return;
228
+ var style = doc.createElement('style');
229
+ style.id = '__no-scrollbar-style';
230
+ style.textContent = '*::-webkit-scrollbar{display:none!important}*{scrollbar-width:none!important;-ms-overflow-style:none!important}';
231
+ doc.head.appendChild(style);
232
+ } catch (e) { /* cross-origin or not yet ready */ }
233
+ }
234
+ frameMobile.addEventListener('load', function () { injectScrollbarCSS(frameMobile); });
235
+ frameWeb.addEventListener('load', function () { injectScrollbarCSS(frameWeb); });
236
+
222
237
  var screens = SCREENS;
223
238
  var currentIndex = 0;
224
239
  var VIEW_KEY = 'showcase-view-mode-' + '{{PROJECT_SLUG}}';
@@ -230,13 +245,13 @@
230
245
  root.classList.remove('view-mobile', 'view-web');
231
246
  root.classList.add('view-' + currentView);
232
247
 
233
- // Toggle icon shows what you'll switch TO
248
+ // Toggle icon shows the CURRENT mode (matches the badge)
234
249
  if (currentView === 'mobile') {
235
- iconPhone.classList.add('hidden');
236
- iconMonitor.classList.remove('hidden');
237
- } else {
238
250
  iconPhone.classList.remove('hidden');
239
251
  iconMonitor.classList.add('hidden');
252
+ } else {
253
+ iconPhone.classList.add('hidden');
254
+ iconMonitor.classList.remove('hidden');
240
255
  }
241
256
 
242
257
  // Recalc phone scaling when switching to mobile
@@ -106,6 +106,18 @@ def build(source_dir: str, project_type: str = None, project_name: str = None) -
106
106
  print("Error: no screens found (no zips or folders with code.html).", file=sys.stderr)
107
107
  sys.exit(1)
108
108
 
109
+ # Copy shared asset folders (images, fonts, css, js) referenced by relative paths in HTMLs.
110
+ # Searches in source/, then source.parent/ (project root). Skips if already copied.
111
+ for asset_name in ('images', 'fonts', 'css', 'js'):
112
+ for candidate_root in (source, source.parent):
113
+ candidate = candidate_root / asset_name
114
+ if candidate.is_dir():
115
+ dest = assets_dir / asset_name
116
+ if not dest.exists():
117
+ shutil.copytree(candidate, dest)
118
+ print(f" ✓ Copied shared {asset_name}/ from {candidate_root.name}/")
119
+ break
120
+
109
121
  # Enrich with DESIGN.md metadata
110
122
  screens = _enrich_screens(screens_raw, metadata, source)
111
123
 
@@ -160,6 +172,18 @@ def build_context(source_dir: str, project_type: str = None, project_name: str =
160
172
  print("Error: no screens found (no zips or folders with code.html).", file=sys.stderr)
161
173
  sys.exit(1)
162
174
 
175
+ # Copy shared asset folders (images, fonts, css, js) referenced by relative paths in HTMLs.
176
+ # Searches in source/, then source.parent/ (project root). Skips if already copied.
177
+ for asset_name in ('images', 'fonts', 'css', 'js'):
178
+ for candidate_root in (source, source.parent):
179
+ candidate = candidate_root / asset_name
180
+ if candidate.is_dir():
181
+ dest = assets_dir / asset_name
182
+ if not dest.exists():
183
+ shutil.copytree(candidate, dest)
184
+ print(f" ✓ Copied shared {asset_name}/ from {candidate_root.name}/")
185
+ break
186
+
163
187
  # Enrich with DESIGN.md metadata
164
188
  screens = _enrich_screens(screens_raw, metadata, source)
165
189
 
@@ -136,16 +136,11 @@ def _process_dir(dir_path: Path, slug: str, assets: Path) -> dict | None:
136
136
  }
137
137
 
138
138
 
139
- _NO_SCROLLBAR_CSS = '<style>*::-webkit-scrollbar{display:none!important}*{scrollbar-width:none!important;-ms-overflow-style:none!important}</style>'
140
-
141
139
  def _copy_html(src: Path, dst: Path) -> None:
142
- """Copy HTML injecting scrollbar-hiding CSS into <head>."""
143
- text = src.read_text(encoding="utf-8", errors="ignore")
144
- if "</head>" in text:
145
- text = text.replace("</head>", f"{_NO_SCROLLBAR_CSS}</head>", 1)
146
- elif "<body" in text:
147
- text = text.replace("<body", f"{_NO_SCROLLBAR_CSS}<body", 1)
148
- dst.write_text(text, encoding="utf-8")
140
+ """Copy HTML verbatim. Scrollbar-hiding CSS is now injected at viewer level
141
+ (see references/viewer.html), keeping the source HTML untouched so it can be
142
+ handed off cleanly to downstream tools (e.g., Laravel Blade conversion)."""
143
+ shutil.copy2(src, dst)
149
144
 
150
145
 
151
146
  def _find_file(root: Path, filename: str) -> Path | None: