@supatent/skills 0.4.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.
- package/README.md +34 -9
- package/bin/install.mjs +252 -43
- package/package.json +5 -3
- package/skills-codex/supatent-content-blog/SKILL.md +298 -0
- package/skills-codex/supatent-content-blog/blog-sections.md +99 -0
- package/skills-codex/supatent-content-landing/SKILL.md +384 -0
- package/skills-codex/supatent-content-landing/landing-sections.md +360 -0
- package/skills-codex/supatent-core/SKILL.md +236 -0
- package/skills-codex/supatent-references/schema-reference.md +289 -0
- package/skills-codex/supatent-references/workflow-reference.md +345 -0
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# @supatent/skills
|
|
2
2
|
|
|
3
|
-
Claude Code content authoring skills for [Supatent CMS](https://supatent.ai).
|
|
3
|
+
Claude Code and Codex CLI content authoring skills for [Supatent CMS](https://supatent.ai).
|
|
4
4
|
|
|
5
5
|
## What this does
|
|
6
6
|
|
|
7
|
-
Installs AI-powered content authoring skills
|
|
7
|
+
Installs AI-powered content authoring skills for Claude Code and Codex CLI. Once installed, agents can create and manage CMS content through natural conversation — blog posts, landing pages, and more.
|
|
8
8
|
|
|
9
9
|
## Quick start
|
|
10
10
|
|
|
@@ -12,15 +12,40 @@ Installs AI-powered content authoring skills into your Claude Code environment.
|
|
|
12
12
|
npx @supatent/skills
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
In interactive terminals, the installer prompts for a target:
|
|
16
|
+
|
|
17
|
+
- `claude`
|
|
18
|
+
- `codex`
|
|
19
|
+
- `both`
|
|
20
|
+
|
|
21
|
+
In non-interactive environments (CI), the default is `claude`.
|
|
22
|
+
|
|
23
|
+
### Explicit target selection
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Claude Code
|
|
27
|
+
npx @supatent/skills --target claude
|
|
28
|
+
|
|
29
|
+
# Codex CLI
|
|
30
|
+
npx @supatent/skills --target codex
|
|
31
|
+
|
|
32
|
+
# Both
|
|
33
|
+
npx @supatent/skills --target both
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Use `--codex-home` to override Codex install location (default: `$CODEX_HOME` or `~/.codex`):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx @supatent/skills --target codex --codex-home /path/to/codex-home
|
|
40
|
+
```
|
|
16
41
|
|
|
17
42
|
## Included skills
|
|
18
43
|
|
|
19
|
-
| Skill |
|
|
20
|
-
|
|
21
|
-
| Core | `/supatent:core` | Schema management, content operations, validation |
|
|
22
|
-
| Blog | `/supatent:blog` | Create and edit blog posts with structured fields |
|
|
23
|
-
| Landing | `/supatent:landing` | Build landing pages with configurable sections |
|
|
44
|
+
| Skill | Claude Code | Codex CLI | Description |
|
|
45
|
+
|-------|-------------|-----------|-------------|
|
|
46
|
+
| Core | `/supatent:core` | `supatent-core` | Schema management, content operations, validation |
|
|
47
|
+
| Blog | `/supatent:content-blog` | `supatent-content-blog` | Create and edit blog posts with structured fields |
|
|
48
|
+
| Landing | `/supatent:content-landing` | `supatent-content-landing` | Build landing pages with configurable sections |
|
|
24
49
|
|
|
25
50
|
## Updating
|
|
26
51
|
|
|
@@ -36,7 +61,7 @@ npx @supatent/skills --force
|
|
|
36
61
|
|
|
37
62
|
- Node.js >= 20
|
|
38
63
|
- A Supatent CMS project with `@supatent/cli` configured
|
|
39
|
-
- Claude Code
|
|
64
|
+
- Claude Code and/or Codex CLI
|
|
40
65
|
|
|
41
66
|
## License
|
|
42
67
|
|
package/bin/install.mjs
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// bin/install.mjs - Self-contained zero-dependency installer for @supatent/skills
|
|
4
|
-
// Copies bundled skill files to
|
|
4
|
+
// Copies bundled skill files to Claude Code and/or Codex CLI skill directories.
|
|
5
5
|
|
|
6
6
|
import { readFile, writeFile, mkdir, copyFile, readdir, stat } from 'node:fs/promises';
|
|
7
7
|
import { realpathSync } from 'node:fs';
|
|
8
8
|
import { createHash } from 'node:crypto';
|
|
9
9
|
import { join, dirname, relative, resolve } from 'node:path';
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { homedir } from 'node:os';
|
|
11
12
|
import { createInterface } from 'node:readline/promises';
|
|
12
13
|
|
|
13
14
|
// ---------------------------------------------------------------------------
|
|
@@ -16,8 +17,10 @@ import { createInterface } from 'node:readline/promises';
|
|
|
16
17
|
|
|
17
18
|
const __filename = fileURLToPath(import.meta.url);
|
|
18
19
|
const __dirname = dirname(__filename);
|
|
19
|
-
const
|
|
20
|
-
const
|
|
20
|
+
const CLAUDE_SKILLS_SOURCE_DIR = join(__dirname, '..', 'skills');
|
|
21
|
+
const CODEX_SKILLS_SOURCE_DIR = join(__dirname, '..', 'skills-codex');
|
|
22
|
+
const CLAUDE_TARGET_SUBDIR = join('.claude', 'skills');
|
|
23
|
+
const VALID_TARGETS = new Set(['claude', 'codex', 'both']);
|
|
21
24
|
|
|
22
25
|
// ---------------------------------------------------------------------------
|
|
23
26
|
// Exported helpers (for testability)
|
|
@@ -65,6 +68,109 @@ export async function confirm(message, { force = false } = {}) {
|
|
|
65
68
|
}
|
|
66
69
|
}
|
|
67
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Prompt install target when not explicitly provided.
|
|
73
|
+
* Defaults to "claude" when stdin is non-interactive.
|
|
74
|
+
*/
|
|
75
|
+
export async function promptInstallTarget({ force = false } = {}) {
|
|
76
|
+
if (force || !process.stdin.isTTY) return 'claude';
|
|
77
|
+
|
|
78
|
+
const rl = createInterface({
|
|
79
|
+
input: process.stdin,
|
|
80
|
+
output: process.stdout,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
console.log('Select installation target:');
|
|
85
|
+
console.log(' 1) Claude Code');
|
|
86
|
+
console.log(' 2) Codex CLI');
|
|
87
|
+
console.log(' 3) Both');
|
|
88
|
+
|
|
89
|
+
while (true) {
|
|
90
|
+
const answer = (await rl.question('Target [1/2/3] (default: 1): ')).trim().toLowerCase();
|
|
91
|
+
|
|
92
|
+
if (answer === '' || answer === '1' || answer === 'claude') {
|
|
93
|
+
return 'claude';
|
|
94
|
+
}
|
|
95
|
+
if (answer === '2' || answer === 'codex') {
|
|
96
|
+
return 'codex';
|
|
97
|
+
}
|
|
98
|
+
if (answer === '3' || answer === 'both') {
|
|
99
|
+
return 'both';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log('Invalid choice. Enter 1, 2, 3, claude, codex, or both.');
|
|
103
|
+
}
|
|
104
|
+
} finally {
|
|
105
|
+
rl.close();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Parse command line args.
|
|
111
|
+
*/
|
|
112
|
+
export function parseArgs(argv) {
|
|
113
|
+
const parsed = {
|
|
114
|
+
force: false,
|
|
115
|
+
help: false,
|
|
116
|
+
target: null,
|
|
117
|
+
codexHome: null,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
121
|
+
const arg = argv[i];
|
|
122
|
+
|
|
123
|
+
if (arg === '--force' || arg === '-f' || arg === '--yes' || arg === '-y') {
|
|
124
|
+
parsed.force = true;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (arg === '--help' || arg === '-h') {
|
|
129
|
+
parsed.help = true;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (arg === '--target') {
|
|
134
|
+
i += 1;
|
|
135
|
+
if (!argv[i]) {
|
|
136
|
+
throw new Error('Missing value for --target. Use: claude, codex, or both.');
|
|
137
|
+
}
|
|
138
|
+
parsed.target = argv[i];
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (arg.startsWith('--target=')) {
|
|
143
|
+
parsed.target = arg.slice('--target='.length);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (arg === '--codex-home') {
|
|
148
|
+
i += 1;
|
|
149
|
+
if (!argv[i]) {
|
|
150
|
+
throw new Error('Missing value for --codex-home.');
|
|
151
|
+
}
|
|
152
|
+
parsed.codexHome = argv[i];
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (arg.startsWith('--codex-home=')) {
|
|
157
|
+
parsed.codexHome = arg.slice('--codex-home='.length);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (parsed.target) {
|
|
165
|
+
parsed.target = parsed.target.toLowerCase();
|
|
166
|
+
if (!VALID_TARGETS.has(parsed.target)) {
|
|
167
|
+
throw new Error(`Invalid --target value: ${parsed.target}. Use: claude, codex, or both.`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return parsed;
|
|
172
|
+
}
|
|
173
|
+
|
|
68
174
|
/**
|
|
69
175
|
* Read and parse .manifest.json. Returns null if missing or invalid.
|
|
70
176
|
*/
|
|
@@ -161,60 +267,76 @@ export async function getVersion() {
|
|
|
161
267
|
return pkg.version;
|
|
162
268
|
}
|
|
163
269
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const force = args.includes('--force');
|
|
270
|
+
/**
|
|
271
|
+
* Resolve default Codex home.
|
|
272
|
+
*/
|
|
273
|
+
export function getDefaultCodexHome() {
|
|
274
|
+
return process.env.CODEX_HOME ? resolve(process.env.CODEX_HOME) : join(homedir(), '.codex');
|
|
275
|
+
}
|
|
171
276
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
277
|
+
function printHelp() {
|
|
278
|
+
console.log('Usage: npx @supatent/skills [options]');
|
|
279
|
+
console.log('');
|
|
280
|
+
console.log('Options:');
|
|
281
|
+
console.log(' --target <claude|codex|both> Installation target (prompts when omitted in TTY)');
|
|
282
|
+
console.log(' --codex-home <path> Override Codex home (default: $CODEX_HOME or ~/.codex)');
|
|
283
|
+
console.log(' -f, --force Auto-confirm prompts and overwrite user-modified files');
|
|
284
|
+
console.log(' -y, --yes Alias for --force');
|
|
285
|
+
console.log(' -h, --help Show this help');
|
|
286
|
+
}
|
|
177
287
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
// -----------------------------------------------------------------------
|
|
181
|
-
let claudeExists = false;
|
|
288
|
+
async function ensureDirectoryExists(dirPath, missingMessage, { force }) {
|
|
289
|
+
let exists = false;
|
|
182
290
|
try {
|
|
183
|
-
const s = await stat(
|
|
184
|
-
|
|
291
|
+
const s = await stat(dirPath);
|
|
292
|
+
exists = s.isDirectory();
|
|
185
293
|
} catch {
|
|
186
294
|
// does not exist
|
|
187
295
|
}
|
|
188
296
|
|
|
189
|
-
if (
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
console.log('Aborted.');
|
|
194
|
-
process.exitCode = 1;
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
await mkdir(claudeDir, { recursive: true });
|
|
297
|
+
if (exists) return true;
|
|
298
|
+
|
|
299
|
+
if (missingMessage) {
|
|
300
|
+
console.log(`\x1b[33m!\x1b[0m ${missingMessage}`);
|
|
198
301
|
}
|
|
199
302
|
|
|
303
|
+
const ok = await confirm(`Create ${dirPath} and continue?`, { force });
|
|
304
|
+
if (!ok) {
|
|
305
|
+
console.log('Aborted.');
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
await mkdir(dirPath, { recursive: true });
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async function installSkillBundle({
|
|
314
|
+
sourceDir,
|
|
315
|
+
targetDir,
|
|
316
|
+
manifestPath,
|
|
317
|
+
targetLabel,
|
|
318
|
+
startHint,
|
|
319
|
+
force,
|
|
320
|
+
version,
|
|
321
|
+
}) {
|
|
200
322
|
// -----------------------------------------------------------------------
|
|
201
|
-
// Step
|
|
323
|
+
// Step a: Build source map
|
|
202
324
|
// -----------------------------------------------------------------------
|
|
203
|
-
const sourceMap = await buildSourceMap(
|
|
325
|
+
const sourceMap = await buildSourceMap(sourceDir);
|
|
204
326
|
const sourceFiles = Object.keys(sourceMap);
|
|
205
327
|
|
|
206
328
|
// -----------------------------------------------------------------------
|
|
207
|
-
// Step
|
|
329
|
+
// Step b: Read existing manifest
|
|
208
330
|
// -----------------------------------------------------------------------
|
|
209
331
|
const manifest = await readManifest(manifestPath);
|
|
210
332
|
|
|
211
333
|
// -----------------------------------------------------------------------
|
|
212
|
-
// Step
|
|
334
|
+
// Step c: Compute diff
|
|
213
335
|
// -----------------------------------------------------------------------
|
|
214
336
|
const diff = await computeDiff(sourceMap, manifest, targetDir);
|
|
215
337
|
|
|
216
338
|
// -----------------------------------------------------------------------
|
|
217
|
-
// Step
|
|
339
|
+
// Step d: Handle user-modified files
|
|
218
340
|
// -----------------------------------------------------------------------
|
|
219
341
|
const filesToCopy = [...diff.newFiles, ...diff.changed];
|
|
220
342
|
const skippedFiles = [];
|
|
@@ -232,14 +354,14 @@ async function main() {
|
|
|
232
354
|
// Check if anything to do
|
|
233
355
|
// -----------------------------------------------------------------------
|
|
234
356
|
if (filesToCopy.length === 0 && skippedFiles.length === 0) {
|
|
235
|
-
console.log(`\x1b[32m\u2713\x1b[0m Already up to date (v${version})`);
|
|
357
|
+
console.log(`\x1b[32m\u2713\x1b[0m Already up to date (${targetLabel}, v${version})`);
|
|
236
358
|
console.log('');
|
|
237
|
-
console.log(
|
|
359
|
+
console.log(` ${startHint}`);
|
|
238
360
|
return;
|
|
239
361
|
}
|
|
240
362
|
|
|
241
363
|
// -----------------------------------------------------------------------
|
|
242
|
-
// Step
|
|
364
|
+
// Step e: Copy files
|
|
243
365
|
// -----------------------------------------------------------------------
|
|
244
366
|
const isFreshInstall = !manifest;
|
|
245
367
|
|
|
@@ -263,7 +385,7 @@ async function main() {
|
|
|
263
385
|
}
|
|
264
386
|
|
|
265
387
|
// -----------------------------------------------------------------------
|
|
266
|
-
// Step
|
|
388
|
+
// Step f: Write manifest
|
|
267
389
|
// -----------------------------------------------------------------------
|
|
268
390
|
const manifestFiles = {};
|
|
269
391
|
|
|
@@ -293,16 +415,103 @@ async function main() {
|
|
|
293
415
|
await writeFile(manifestPath, JSON.stringify(manifestData, null, 2) + '\n');
|
|
294
416
|
|
|
295
417
|
// -----------------------------------------------------------------------
|
|
296
|
-
// Step
|
|
418
|
+
// Step g: Print summary
|
|
297
419
|
// -----------------------------------------------------------------------
|
|
298
420
|
console.log('');
|
|
299
421
|
if (isFreshInstall) {
|
|
300
|
-
console.log(`\x1b[32m\u2713\x1b[0m Installed ${filesToCopy.length} files to ${
|
|
422
|
+
console.log(`\x1b[32m\u2713\x1b[0m Installed ${filesToCopy.length} files to ${targetDir} (${targetLabel}, v${version})`);
|
|
301
423
|
} else {
|
|
302
|
-
console.log(`\x1b[32m\u2713\x1b[0m Updated ${filesToCopy.length} file${filesToCopy.length === 1 ? '' : 's'} in ${
|
|
424
|
+
console.log(`\x1b[32m\u2713\x1b[0m Updated ${filesToCopy.length} file${filesToCopy.length === 1 ? '' : 's'} in ${targetDir} (${targetLabel}, v${version})`);
|
|
303
425
|
}
|
|
304
426
|
console.log('');
|
|
305
|
-
console.log(
|
|
427
|
+
console.log(` ${startHint}`);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async function installClaude({ cwd, force, version }) {
|
|
431
|
+
const claudeDir = join(cwd, '.claude');
|
|
432
|
+
const targetDir = join(cwd, CLAUDE_TARGET_SUBDIR);
|
|
433
|
+
const manifestPath = join(targetDir, 'supatent-core', '.manifest.json');
|
|
434
|
+
|
|
435
|
+
const ok = await ensureDirectoryExists(
|
|
436
|
+
claudeDir,
|
|
437
|
+
'No .claude/ directory found. This installer creates skill files for Claude Code.',
|
|
438
|
+
{ force },
|
|
439
|
+
);
|
|
440
|
+
if (!ok) {
|
|
441
|
+
process.exitCode = 1;
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
await installSkillBundle({
|
|
446
|
+
sourceDir: CLAUDE_SKILLS_SOURCE_DIR,
|
|
447
|
+
targetDir,
|
|
448
|
+
manifestPath,
|
|
449
|
+
targetLabel: 'Claude Code',
|
|
450
|
+
startHint: 'Run \x1b[36m/supatent:core\x1b[0m to get started',
|
|
451
|
+
force,
|
|
452
|
+
version,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
async function installCodex({ force, version, codexHomeOverride = null }) {
|
|
457
|
+
const codexHome = codexHomeOverride ? resolve(codexHomeOverride) : getDefaultCodexHome();
|
|
458
|
+
const targetDir = join(codexHome, 'skills');
|
|
459
|
+
const manifestPath = join(targetDir, 'supatent-core', '.manifest.json');
|
|
460
|
+
|
|
461
|
+
const ok = await ensureDirectoryExists(
|
|
462
|
+
codexHome,
|
|
463
|
+
`No Codex home directory found at ${codexHome}.`,
|
|
464
|
+
{ force },
|
|
465
|
+
);
|
|
466
|
+
if (!ok) {
|
|
467
|
+
process.exitCode = 1;
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
await installSkillBundle({
|
|
472
|
+
sourceDir: CODEX_SKILLS_SOURCE_DIR,
|
|
473
|
+
targetDir,
|
|
474
|
+
manifestPath,
|
|
475
|
+
targetLabel: 'Codex CLI',
|
|
476
|
+
startHint: 'Restart Codex CLI to pick up new skills',
|
|
477
|
+
force,
|
|
478
|
+
version,
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// ---------------------------------------------------------------------------
|
|
483
|
+
// Main installer flow
|
|
484
|
+
// ---------------------------------------------------------------------------
|
|
485
|
+
|
|
486
|
+
async function main() {
|
|
487
|
+
const args = parseArgs(process.argv.slice(2));
|
|
488
|
+
|
|
489
|
+
if (args.help) {
|
|
490
|
+
printHelp();
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const version = await getVersion();
|
|
495
|
+
const cwd = process.cwd();
|
|
496
|
+
const force = args.force;
|
|
497
|
+
|
|
498
|
+
const target = args.target || await promptInstallTarget({ force });
|
|
499
|
+
|
|
500
|
+
if (target === 'both') {
|
|
501
|
+
console.log('Installing Supatent skills for Claude Code...');
|
|
502
|
+
await installClaude({ cwd, force, version });
|
|
503
|
+
console.log('');
|
|
504
|
+
console.log('Installing Supatent skills for Codex CLI...');
|
|
505
|
+
await installCodex({ force, version, codexHomeOverride: args.codexHome });
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (target === 'codex') {
|
|
510
|
+
await installCodex({ force, version, codexHomeOverride: args.codexHome });
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
await installClaude({ cwd, force, version });
|
|
306
515
|
}
|
|
307
516
|
|
|
308
517
|
// ---------------------------------------------------------------------------
|
package/package.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supatent/skills",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Claude Code content authoring skills for Supatent CMS",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Claude Code and Codex CLI content authoring skills for Supatent CMS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"supatent-skills": "bin/install.mjs"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"bin",
|
|
11
|
-
"skills"
|
|
11
|
+
"skills",
|
|
12
|
+
"skills-codex"
|
|
12
13
|
],
|
|
13
14
|
"devDependencies": {
|
|
14
15
|
"eslint": "^9.16.0",
|
|
@@ -21,6 +22,7 @@
|
|
|
21
22
|
"supatent",
|
|
22
23
|
"cms",
|
|
23
24
|
"claude-code",
|
|
25
|
+
"codex-cli",
|
|
24
26
|
"skills",
|
|
25
27
|
"content-authoring"
|
|
26
28
|
],
|