update-base-config 0.1.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 +27 -0
- package/package.json +21 -0
- package/src/index.js +153 -0
- package/src/lib.js +144 -0
- package/templates/skills/ids/SKILL.md +40 -0
- package/templates/skills/ids/docs/SKILL_ID_MANAGEMENT.md +438 -0
- package/templates/skills/ids/docs/SKILL_INICIO.txt +124 -0
- package/templates/skills/ids/docs/SKILL_QUICK_START.md +260 -0
- package/templates/skills/ids/docs/SKILL_README.md +230 -0
- package/templates/skills/tailwind/SKILL.md +43 -0
- package/templates/skills/tailwind/docs/SKILL_TAILWIND_BEFORE_AFTER.md +796 -0
- package/templates/skills/tailwind/docs/SKILL_TAILWIND_COPILOT_PROMPT.md +295 -0
- package/templates/skills/tailwind/docs/SKILL_TAILWIND_CUSTOM.md +776 -0
- package/templates/skills/tailwind/docs/SKILL_TAILWIND_INDEX.md +315 -0
- package/templates/skills/tailwind/docs/SKILL_TAILWIND_QUICK_START.md +426 -0
- package/templates/skills/tailwind/docs/SKILL_TAILWIND_SUMMARY.md +436 -0
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# update-base-config
|
|
2
|
+
|
|
3
|
+
`update-base-config` is a small `npx` CLI that copies shared project-local AI agent skills from this repository into another repository.
|
|
4
|
+
|
|
5
|
+
## What it syncs
|
|
6
|
+
|
|
7
|
+
The CLI copies each skill from `templates/skills/` into `.agents/skills/`, `.claude/skills/`, `.opencode/skills/`, and `.codex/skills/`.
|
|
8
|
+
|
|
9
|
+
## Behavior
|
|
10
|
+
|
|
11
|
+
- Shows a summary before changing files
|
|
12
|
+
- Asks for confirmation by default
|
|
13
|
+
- Creates a backup before replacing an existing skill file
|
|
14
|
+
|
|
15
|
+
Backups are stored inside the target project under `.update-base-config-backups/<timestamp>/`.
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx update-base-config
|
|
21
|
+
npx update-base-config --dry-run
|
|
22
|
+
npx update-base-config --yes
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Develop the template
|
|
26
|
+
|
|
27
|
+
Edit every shared skill once inside `templates/skills/`. The CLI distributes that master source into each supported local agent directory.
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "update-base-config",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Install and update shared AI agent project configuration from a fixed template.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"update-base-config": "./src/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"src",
|
|
11
|
+
"templates",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "node --test"
|
|
16
|
+
},
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=18.17"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT"
|
|
21
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import process from 'node:process';
|
|
5
|
+
import { access } from 'node:fs/promises';
|
|
6
|
+
import { constants } from 'node:fs';
|
|
7
|
+
import readline from 'node:readline/promises';
|
|
8
|
+
import {
|
|
9
|
+
applyOperations,
|
|
10
|
+
buildBackupRoot,
|
|
11
|
+
formatSummary,
|
|
12
|
+
getDefaultSkillsRoot,
|
|
13
|
+
planSync,
|
|
14
|
+
summarizeOperations
|
|
15
|
+
} from './lib.js';
|
|
16
|
+
|
|
17
|
+
async function main() {
|
|
18
|
+
const args = parseArgs(process.argv.slice(2));
|
|
19
|
+
|
|
20
|
+
if (args.help) {
|
|
21
|
+
printHelp();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const targetRoot = path.resolve(args.targetRoot ?? process.cwd());
|
|
26
|
+
const skillsRoot = path.resolve(args.skillsRoot ?? getDefaultSkillsRoot());
|
|
27
|
+
|
|
28
|
+
await ensureReadableDirectory(skillsRoot, 'skills template');
|
|
29
|
+
await ensureReadableDirectory(targetRoot, 'target');
|
|
30
|
+
|
|
31
|
+
const operations = await planSync({ skillsRoot, targetRoot });
|
|
32
|
+
const actionable = operations.filter((operation) => operation.type !== 'skip');
|
|
33
|
+
|
|
34
|
+
console.log(formatSummary(operations, targetRoot));
|
|
35
|
+
|
|
36
|
+
if (actionable.length === 0) {
|
|
37
|
+
console.log('\nNo changes required.');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (args.dryRun) {
|
|
42
|
+
console.log('\nDry run only. No files were modified.');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!args.yes) {
|
|
47
|
+
const confirmed = await askForConfirmation();
|
|
48
|
+
if (!confirmed) {
|
|
49
|
+
console.log('Cancelled.');
|
|
50
|
+
process.exitCode = 1;
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const backupRoot = buildBackupRoot(targetRoot);
|
|
56
|
+
const result = await applyOperations({ operations, backupRoot });
|
|
57
|
+
const summary = summarizeOperations(operations);
|
|
58
|
+
|
|
59
|
+
console.log('');
|
|
60
|
+
console.log(`Applied ${summary.create + summary.merge + summary.replace} changes.`);
|
|
61
|
+
if (result.backupCreated) {
|
|
62
|
+
console.log(`Backups saved in ${backupRoot}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function parseArgs(argv) {
|
|
67
|
+
const args = {
|
|
68
|
+
yes: false,
|
|
69
|
+
dryRun: false,
|
|
70
|
+
help: false,
|
|
71
|
+
targetRoot: null,
|
|
72
|
+
skillsRoot: null
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
76
|
+
const token = argv[index];
|
|
77
|
+
|
|
78
|
+
if (token === '--yes' || token === '-y') {
|
|
79
|
+
args.yes = true;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (token === '--dry-run') {
|
|
84
|
+
args.dryRun = true;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (token === '--help' || token === '-h') {
|
|
89
|
+
args.help = true;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (token === '--target') {
|
|
94
|
+
args.targetRoot = argv[index + 1];
|
|
95
|
+
index += 1;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (token === '--skills-root') {
|
|
100
|
+
args.skillsRoot = argv[index + 1];
|
|
101
|
+
index += 1;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
throw new Error(`Unknown argument: ${token}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return args;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function printHelp() {
|
|
112
|
+
console.log(`update-base-config
|
|
113
|
+
|
|
114
|
+
Usage:
|
|
115
|
+
npx update-base-config
|
|
116
|
+
npx update-base-config --dry-run
|
|
117
|
+
npx update-base-config --yes
|
|
118
|
+
|
|
119
|
+
Options:
|
|
120
|
+
--yes, -y Apply without asking for confirmation
|
|
121
|
+
--dry-run Show the plan without writing files
|
|
122
|
+
--target <path> Override the target project directory
|
|
123
|
+
--skills-root Override the master skills directory
|
|
124
|
+
--help, -h Show this message
|
|
125
|
+
`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function ensureReadableDirectory(directoryPath, label) {
|
|
129
|
+
try {
|
|
130
|
+
await access(directoryPath, constants.R_OK);
|
|
131
|
+
} catch {
|
|
132
|
+
throw new Error(`Cannot read ${label} directory: ${directoryPath}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function askForConfirmation() {
|
|
137
|
+
const rl = readline.createInterface({
|
|
138
|
+
input: process.stdin,
|
|
139
|
+
output: process.stdout
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const answer = await rl.question('\nApply these changes? (y/N) ');
|
|
144
|
+
return answer.trim().toLowerCase() === 'y';
|
|
145
|
+
} finally {
|
|
146
|
+
rl.close();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
main().catch((error) => {
|
|
151
|
+
console.error(error.message);
|
|
152
|
+
process.exitCode = 1;
|
|
153
|
+
});
|
package/src/lib.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { mkdir, readFile, readdir, stat, writeFile, copyFile } from 'node:fs/promises';
|
|
3
|
+
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
|
|
8
|
+
export function getDefaultSkillsRoot() {
|
|
9
|
+
return path.resolve(__dirname, '..', 'templates', 'skills');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function collectFiles(rootDir) {
|
|
13
|
+
const entries = await readdir(rootDir, { withFileTypes: true });
|
|
14
|
+
const files = [];
|
|
15
|
+
|
|
16
|
+
for (const entry of entries) {
|
|
17
|
+
if (entry.name === '.DS_Store') {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const absolutePath = path.join(rootDir, entry.name);
|
|
22
|
+
|
|
23
|
+
if (entry.isDirectory()) {
|
|
24
|
+
files.push(...(await collectFiles(absolutePath)));
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (entry.isFile()) {
|
|
29
|
+
files.push(absolutePath);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return files.sort();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function planSync({ skillsRoot, targetRoot }) {
|
|
37
|
+
const skillFiles = await collectFiles(skillsRoot);
|
|
38
|
+
const operations = [];
|
|
39
|
+
|
|
40
|
+
for (const sourcePath of skillFiles) {
|
|
41
|
+
const skillPath = path.relative(skillsRoot, sourcePath);
|
|
42
|
+
|
|
43
|
+
for (const agentRoot of ['.agents', '.claude', '.opencode', '.codex']) {
|
|
44
|
+
const relativePath = path.join(agentRoot, 'skills', skillPath);
|
|
45
|
+
operations.push(...(await planFile({ sourcePath, relativePath, targetRoot })));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return operations;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function planFile({ sourcePath, relativePath, targetRoot }) {
|
|
53
|
+
const targetPath = path.join(targetRoot, relativePath);
|
|
54
|
+
const targetInfo = await readExistingFile(targetPath);
|
|
55
|
+
const sourceContent = await readFile(sourcePath, 'utf8');
|
|
56
|
+
|
|
57
|
+
if (!targetInfo.exists) {
|
|
58
|
+
return [{ type: 'create', relativePath, sourcePath, targetPath, content: sourceContent }];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (targetInfo.content === sourceContent) {
|
|
62
|
+
return [{ type: 'skip', relativePath, sourcePath, targetPath, reason: 'already up to date' }];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return [{ type: 'replace', relativePath, sourcePath, targetPath, content: sourceContent }];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function readExistingFile(filePath) {
|
|
69
|
+
try {
|
|
70
|
+
const fileStat = await stat(filePath);
|
|
71
|
+
if (!fileStat.isFile()) {
|
|
72
|
+
return { exists: false, content: null };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
exists: true,
|
|
77
|
+
content: await readFile(filePath, 'utf8')
|
|
78
|
+
};
|
|
79
|
+
} catch {
|
|
80
|
+
return { exists: false, content: null };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function summarizeOperations(operations) {
|
|
85
|
+
return operations.reduce(
|
|
86
|
+
(summary, operation) => {
|
|
87
|
+
summary[operation.type] = (summary[operation.type] ?? 0) + 1;
|
|
88
|
+
return summary;
|
|
89
|
+
},
|
|
90
|
+
{ create: 0, merge: 0, replace: 0, skip: 0 }
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function formatSummary(operations, targetRoot) {
|
|
95
|
+
const summary = summarizeOperations(operations);
|
|
96
|
+
const lines = [
|
|
97
|
+
`Target: ${targetRoot}`,
|
|
98
|
+
`Create: ${summary.create}`,
|
|
99
|
+
`Merge: ${summary.merge}`,
|
|
100
|
+
`Replace with backup: ${summary.replace}`,
|
|
101
|
+
`Skip: ${summary.skip}`,
|
|
102
|
+
'',
|
|
103
|
+
'Planned changes:'
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
for (const operation of operations) {
|
|
107
|
+
if (operation.type === 'skip') {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const detail = operation.type === 'merge' ? ` (${operation.mergeKind})` : '';
|
|
112
|
+
lines.push(`- ${operation.type.toUpperCase()} ${operation.relativePath}${detail}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return lines.join('\n');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function applyOperations({ operations, backupRoot }) {
|
|
119
|
+
let backupCreated = false;
|
|
120
|
+
|
|
121
|
+
for (const operation of operations) {
|
|
122
|
+
if (operation.type === 'skip') {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
await mkdir(path.dirname(operation.targetPath), { recursive: true });
|
|
127
|
+
|
|
128
|
+
if (operation.type === 'replace') {
|
|
129
|
+
const backupPath = path.join(backupRoot, operation.relativePath);
|
|
130
|
+
await mkdir(path.dirname(backupPath), { recursive: true });
|
|
131
|
+
await copyFile(operation.targetPath, backupPath);
|
|
132
|
+
backupCreated = true;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
await writeFile(operation.targetPath, operation.content, 'utf8');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return { backupCreated };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function buildBackupRoot(targetRoot, now = new Date()) {
|
|
142
|
+
const timestamp = now.toISOString().replace(/[:.]/g, '-');
|
|
143
|
+
return path.join(targetRoot, '.update-base-config-backups', timestamp);
|
|
144
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ids
|
|
3
|
+
description: Gestiona y valida IDs en elementos interactivos del proyecto m-personas con formato estándar para analytics y calidad.
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: m-personas
|
|
7
|
+
version: "1.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Skill: Gestión de IDs
|
|
11
|
+
|
|
12
|
+
Usa esta skill cuando necesites:
|
|
13
|
+
|
|
14
|
+
- Detectar elementos interactivos sin `id`
|
|
15
|
+
- Corregir IDs con formato inválido
|
|
16
|
+
- Aplicar auto-fix de ESLint para IDs
|
|
17
|
+
- Revisar el estándar `[PREFIX]_landing-[TIPO]-[CONTEXTO]`
|
|
18
|
+
|
|
19
|
+
## Documentación
|
|
20
|
+
|
|
21
|
+
- Inicio rápido: [docs/SKILL_QUICK_START.md](./docs/SKILL_QUICK_START.md)
|
|
22
|
+
- Guía general: [docs/SKILL_README.md](./docs/SKILL_README.md)
|
|
23
|
+
- Guía técnica: [docs/SKILL_ID_MANAGEMENT.md](./docs/SKILL_ID_MANAGEMENT.md)
|
|
24
|
+
- Resumen visual: [docs/SKILL_INICIO.txt](./docs/SKILL_INICIO.txt)
|
|
25
|
+
|
|
26
|
+
## Comandos útiles
|
|
27
|
+
|
|
28
|
+
- `npx eslint --fix src/`
|
|
29
|
+
- `node scripts/manageIds.js --analyze src/pages`
|
|
30
|
+
- `node scripts/manageIds.js --report`
|
|
31
|
+
|
|
32
|
+
## Notas de proyecto
|
|
33
|
+
|
|
34
|
+
- Prefijo esperado por defecto: `ppp_` (Banco Chile)
|
|
35
|
+
- En Banco Edwards: `pbec_`
|
|
36
|
+
- La validación es warning en desarrollo y error en producción
|
|
37
|
+
|
|
38
|
+
## Cómo resolverlo
|
|
39
|
+
|
|
40
|
+
Usa ids con el formato `[PREFIX]_[SECCION]-[TIPO]-[CONTEXTO]`, donde `[SECCION]` puede ser `landing`, `header` o `footer`; `link` para `<a>` y `btn` para `<button>`.
|