@supatent/skills 0.4.0 → 0.5.1
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/supatent-content-blog/SKILL.md +3 -0
- package/skills/supatent-content-landing/SKILL.md +3 -0
- package/skills/supatent-core/SKILL.md +14 -3
- package/skills/supatent-references/schema-reference.md +20 -1
- package/skills/supatent-references/workflow-reference.md +15 -1
- package/skills-codex/supatent-content-blog/SKILL.md +301 -0
- package/skills-codex/supatent-content-blog/blog-sections.md +99 -0
- package/skills-codex/supatent-content-landing/SKILL.md +387 -0
- package/skills-codex/supatent-content-landing/landing-sections.md +360 -0
- package/skills-codex/supatent-core/SKILL.md +247 -0
- package/skills-codex/supatent-references/schema-reference.md +308 -0
- package/skills-codex/supatent-references/workflow-reference.md +359 -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.1",
|
|
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
|
],
|
|
@@ -170,6 +170,7 @@ Generate a URL-friendly slug from the title (lowercase, hyphens, no special char
|
|
|
170
170
|
**Body content:**
|
|
171
171
|
- Default length: 1000-1500 words
|
|
172
172
|
- Format: markdown with headings (H2, H3), paragraphs, lists, and emphasis as the content demands
|
|
173
|
+
- For Supatent assets in the `body`, use slug links: ``, `[Link](asset-slug)`, or `<img src="asset-slug" ...>`
|
|
173
174
|
- SEO-conscious writing: include target keywords in the first paragraph, use semantic keyword variations throughout, structure headings around searchable concepts
|
|
174
175
|
- Content-driven structure: do NOT use a rigid template. Let the topic dictate whether the post needs numbered lists, narrative flow, comparison tables, or a mix.
|
|
175
176
|
- Match the tone and style the user selected in the interview
|
|
@@ -243,6 +244,7 @@ After writing, check `.supatent/.validation-status.json` for errors. Common JSON
|
|
|
243
244
|
|-------|-------|-----|
|
|
244
245
|
| `unsupported @type` | Using BlogPosting instead of Article | Change `@type` to `"Article"` |
|
|
245
246
|
| `image: expected uri format` | Using asset slug instead of URL | Remove `image` or use a full URL |
|
|
247
|
+
| `markdown references missing asset slug` | `body` contains an asset slug link with no matching local asset | Add/update `.supatent/assets/{slug}.{locale}.json` or change link target |
|
|
246
248
|
| `required property 'name' is missing` | Author object missing name | Add `"name"` to the author Person object |
|
|
247
249
|
|
|
248
250
|
## Multi-Locale Translation
|
|
@@ -298,3 +300,4 @@ Format the guidance as:
|
|
|
298
300
|
> Here are the images you will need for this post: [list with recommended sizes and format notes]
|
|
299
301
|
|
|
300
302
|
Remind the user that images can be uploaded via the Supatent dashboard (drag-and-drop) or via the CLI asset upload flow. After uploading, set the `cover-image` field value to the asset slug returned by the upload.
|
|
303
|
+
For in-body assets, embed with slug syntax (`` or `<img src="asset-slug" ...>`), not raw URLs.
|
|
@@ -240,6 +240,7 @@ Confirm these counts with the user during adaptive questions. Adjust if requeste
|
|
|
240
240
|
- If context is too vague for a section, ask the user for more detail rather than generating filler
|
|
241
241
|
- Match the tone preference from the interview throughout all sections
|
|
242
242
|
- `features-list` on pricing tiers: use markdown bullet list format, one feature per line
|
|
243
|
+
- In markdown fields, reference Supatent assets by slug: ``, `[Link](asset-slug)`, or `<img src="asset-slug" ...>`
|
|
243
244
|
- `is-featured` on pricing tiers: set `"true"` on the recommended/most popular tier, `"false"` on others
|
|
244
245
|
- Image fields: leave empty string `""` -- note images needed in the Image Guidance section
|
|
245
246
|
- CTA URLs: use the website URL from the interview if provided, otherwise use placeholder `#`
|
|
@@ -331,6 +332,7 @@ After writing, check `.supatent/.validation-status.json` for errors. Common JSON
|
|
|
331
332
|
|-------|-------|-----|
|
|
332
333
|
| `unsupported @type` | Using WebPage instead of Organization | Change `@type` to `"Organization"` |
|
|
333
334
|
| `image: expected uri format` | Using asset slug instead of URL | Remove `image` or use a full URL |
|
|
335
|
+
| `markdown references missing asset slug` | Markdown content references an unknown asset slug | Add/update `.supatent/assets/{slug}.{locale}.json` or change link target |
|
|
334
336
|
| `mainEntity: required` | FAQPage missing Question array | Add at least one Question to `mainEntity` |
|
|
335
337
|
|
|
336
338
|
## Multi-Locale Translation
|
|
@@ -384,3 +386,4 @@ After content generation, provide a section-by-section image checklist. Only inc
|
|
|
384
386
|
| Metadata | -- | -- | No images |
|
|
385
387
|
|
|
386
388
|
Images can be uploaded via the Supatent dashboard (drag-and-drop) or the CLI asset upload flow. After uploading, set the image field value to the asset slug returned by the upload.
|
|
389
|
+
For markdown fields, embed/link those assets with slug syntax (`` or `<img src="asset-slug" ...>`).
|
|
@@ -64,8 +64,9 @@ npm view @supatent/skills version 2>/dev/null
|
|
|
64
64
|
|------|-----------|--------------|
|
|
65
65
|
| `text` | `textInput`, `textarea` | Plain string |
|
|
66
66
|
| `number` | `numberInput` | Numeric value |
|
|
67
|
-
| `image` | `singleImage`, `multiImage` |
|
|
68
|
-
| `
|
|
67
|
+
| `image` | `singleImage`, `multiImage` | Asset slug string (`string`/`string[]`) or legacy object |
|
|
68
|
+
| `video` | `singleVideo`, `multiVideo` | Asset slug string (`string`/`string[]`) or legacy object |
|
|
69
|
+
| `markdown` | `markdownEditor` | Markdown string (supports asset slugs in links and image embeds) |
|
|
69
70
|
| `jsonLd` | `jsonLdEditor` | JSON-LD structured data object |
|
|
70
71
|
|
|
71
72
|
**Interface determines the UI editor**, not the storage format. For example, `text` with `textInput` gives a single-line input; `text` with `textarea` gives a multi-line editor.
|
|
@@ -169,6 +170,16 @@ Write a JSON file to `.supatent/content/{schemaSlug}/{itemSlug}.{locale}.json`:
|
|
|
169
170
|
|
|
170
171
|
Field keys must match the field slugs defined in the schema. Each locale gets its own file (e.g., `my-post.en.json`, `my-post.fr.json`).
|
|
171
172
|
|
|
173
|
+
### Markdown asset slug links
|
|
174
|
+
|
|
175
|
+
For markdown fields, use asset slugs directly when linking or embedding Supatent assets:
|
|
176
|
+
|
|
177
|
+
- ``
|
|
178
|
+
- `[Link text](asset-slug)`
|
|
179
|
+
- `<img src="asset-slug" width="1200" height="630" alt="...">`
|
|
180
|
+
|
|
181
|
+
Do not use file extensions or full API URLs when referencing Supatent assets in markdown. Missing slugs generate warning-only `markdown-asset` validation warnings.
|
|
182
|
+
|
|
172
183
|
### Validating
|
|
173
184
|
|
|
174
185
|
If dev mode is running, validation happens automatically on file save. Otherwise:
|
|
@@ -215,7 +226,7 @@ The `interface` value is not valid for the given `type`. Each field type only su
|
|
|
215
226
|
|
|
216
227
|
### "Unknown field type"
|
|
217
228
|
|
|
218
|
-
|
|
229
|
+
Six field types are supported: `text`, `number`, `image`, `video`, `markdown`, `jsonLd`. Check for typos or capitalization errors (`jsonLd` not `jsonld` or `JsonLd`).
|
|
219
230
|
|
|
220
231
|
### "Singleton must use default slug"
|
|
221
232
|
|
|
@@ -13,6 +13,8 @@ Every field in a schema has a `type` and an `interface`. The type determines the
|
|
|
13
13
|
| `number` | `numberInput` | `number` or `null` | Numeric input; `null` represents empty (distinct from `0`) |
|
|
14
14
|
| `image` | `singleImage` | `string` or `null` | Asset slug string (e.g., `"hero-image"`), or legacy object with `assetPath` |
|
|
15
15
|
| `image` | `multiImage` | `string[]` | Array of asset slug strings (e.g., `["hero", "banner"]`) |
|
|
16
|
+
| `video` | `singleVideo` | `string` or `null` | Asset slug string (e.g., `"promo-video"`), or legacy object with `assetPath` |
|
|
17
|
+
| `video` | `multiVideo` | `string[]` | Array of asset slug strings (e.g., `["intro-video", "demo-video"]`) |
|
|
16
18
|
| `markdown` | `markdownEditor` | `string` | Markdown-formatted text |
|
|
17
19
|
| `jsonLd` | `jsonLdEditor` | `object` | JSON-LD object with `@context` and `@type` |
|
|
18
20
|
|
|
@@ -24,6 +26,7 @@ Every field in a schema has a `type` and an `interface`. The type determines the
|
|
|
24
26
|
text -> textInput, textarea
|
|
25
27
|
number -> numberInput
|
|
26
28
|
image -> singleImage, multiImage
|
|
29
|
+
video -> singleVideo, multiVideo
|
|
27
30
|
markdown -> markdownEditor
|
|
28
31
|
jsonLd -> jsonLdEditor
|
|
29
32
|
```
|
|
@@ -75,7 +78,7 @@ Schema files live at `.supatent/schema/{slug}.json`. The complete structure:
|
|
|
75
78
|
| `slug` | Yes | `string` | Unique within the schema. Lowercase alphanumeric with hyphens. |
|
|
76
79
|
| `name` | Yes | `string` | Display name. Minimum 1 character. |
|
|
77
80
|
| `description` | No | `string` | Optional description of the field's purpose. |
|
|
78
|
-
| `type` | Yes | `string` | One of: `text`, `number`, `image`, `markdown`, `jsonLd` |
|
|
81
|
+
| `type` | Yes | `string` | One of: `text`, `number`, `image`, `video`, `markdown`, `jsonLd` |
|
|
79
82
|
| `interface` | Yes | `string` | Must be valid for the field's type (see compatibility map above). |
|
|
80
83
|
| `order` | Yes | `number` | Integer >= 0. Controls display order in the editor. |
|
|
81
84
|
|
|
@@ -163,9 +166,25 @@ The file is a flat JSON object where keys are field slugs from the schema:
|
|
|
163
166
|
| `number` | `number` or `null` | `42` or `null` |
|
|
164
167
|
| `image` (singleImage) | `string` (asset slug) or `null` | `"hero-image"` |
|
|
165
168
|
| `image` (multiImage) | `string[]` (asset slugs) | `["photo-1", "photo-2"]` |
|
|
169
|
+
| `video` (singleVideo) | `string` (asset slug) or `null` | `"promo-video"` |
|
|
170
|
+
| `video` (multiVideo) | `string[]` (asset slugs) | `["intro-video", "demo-video"]` |
|
|
166
171
|
| `markdown` | `string` | `"# Heading\n\nParagraph"` |
|
|
167
172
|
| `jsonLd` | `object` | `{ "@context": "https://schema.org", "@type": "Article" }` |
|
|
168
173
|
|
|
174
|
+
### Markdown Asset Slug Syntax
|
|
175
|
+
|
|
176
|
+
Markdown fields support direct asset slug references. Use these forms inside markdown content:
|
|
177
|
+
|
|
178
|
+
- Image embed (resolved to optimized image URL): ``
|
|
179
|
+
- Asset link (resolved to asset URL): `[Download file](asset-slug)`
|
|
180
|
+
- HTML image tag (resolved to optimized image URL): `<img src="asset-slug" width="1200" height="630" alt="Hero">`
|
|
181
|
+
|
|
182
|
+
Rules:
|
|
183
|
+
|
|
184
|
+
- Use raw slugs only (example: `hero-image`), not full URLs and not file extensions.
|
|
185
|
+
- Slugs must match `^[a-z0-9]+(?:-[a-z0-9]+)*$`.
|
|
186
|
+
- Missing slugs produce `markdown-asset` warnings (non-blocking).
|
|
187
|
+
|
|
169
188
|
### File Naming
|
|
170
189
|
|
|
171
190
|
- Collection: `{itemSlug}.{locale}.json` (e.g., `my-post.en.json`, `my-post.fr.json`)
|
|
@@ -173,6 +173,7 @@ All Supatent files live under the `.supatent/` directory in the project root:
|
|
|
173
173
|
- `text` -> `textInput`, `textarea`
|
|
174
174
|
- `number` -> `numberInput`
|
|
175
175
|
- `image` -> `singleImage`, `multiImage`
|
|
176
|
+
- `video` -> `singleVideo`, `multiVideo`
|
|
176
177
|
- `markdown` -> `markdownEditor`
|
|
177
178
|
- `jsonLd` -> `jsonLdEditor`
|
|
178
179
|
|
|
@@ -208,6 +209,19 @@ All Supatent files live under the `.supatent/` directory in the project root:
|
|
|
208
209
|
|
|
209
210
|
**Fix:** Add the missing field to the content file with the correct value type.
|
|
210
211
|
|
|
212
|
+
### Missing Markdown Asset Slug (Warning)
|
|
213
|
+
|
|
214
|
+
**Warning:** `Field 'body': markdown references missing asset slug 'hero-image'`
|
|
215
|
+
|
|
216
|
+
**Cause:** A markdown field references an asset slug that does not exist in local asset metadata. Checked forms include:
|
|
217
|
+
- ``
|
|
218
|
+
- `[text](asset-slug)`
|
|
219
|
+
- `<img src="asset-slug" ...>`
|
|
220
|
+
|
|
221
|
+
**Fix:** Create the asset metadata/file for that slug (for example `.supatent/assets/hero-image.en.json`) or update the markdown link to an existing slug.
|
|
222
|
+
|
|
223
|
+
**Agent note:** This is warning-only (`type: "markdown-asset"`). It appears in `supatent validate`, `supatent dev`, and pre-push validation, but does not block sync.
|
|
224
|
+
|
|
211
225
|
### JSON-LD Validation Errors
|
|
212
226
|
|
|
213
227
|
**Error:** `Field 'structured-data': @root: Missing required property "@context"` or `Missing required property "@type"`
|
|
@@ -333,7 +347,7 @@ The `.validation-status.json` file is written by `supatent validate` and `supate
|
|
|
333
347
|
**Key fields for AI agents:**
|
|
334
348
|
- `valid`: `true` if no errors (warnings are acceptable)
|
|
335
349
|
- `errors[]`: Each error includes `path`, `message`, and `help` (actionable fix instruction)
|
|
336
|
-
- `warnings[]`: Non-blocking issues (locale coverage, empty JSON-LD fields)
|
|
350
|
+
- `warnings[]`: Non-blocking issues (locale coverage, empty JSON-LD fields, missing markdown asset slugs)
|
|
337
351
|
- `trigger`: What caused this validation (`"file-change"`, `"manual"`, or `"startup"`)
|
|
338
352
|
|
|
339
353
|
**Workflow for AI agents:**
|