@zenti/sdk 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.
Files changed (47) hide show
  1. package/.gitattributes +6 -0
  2. package/README.md +211 -0
  3. package/dist/PersonaLayer.d.ts +28 -0
  4. package/dist/PersonaLayer.js +89 -0
  5. package/dist/cli/commands/diff.d.ts +2 -0
  6. package/dist/cli/commands/diff.js +129 -0
  7. package/dist/cli/commands/init.d.ts +4 -0
  8. package/dist/cli/commands/init.js +88 -0
  9. package/dist/cli/commands/test.d.ts +5 -0
  10. package/dist/cli/commands/test.js +121 -0
  11. package/dist/cli/commands/validate.d.ts +2 -0
  12. package/dist/cli/commands/validate.js +80 -0
  13. package/dist/cli/index.d.ts +3 -0
  14. package/dist/cli/index.js +42 -0
  15. package/dist/compiler/index.d.ts +5 -0
  16. package/dist/compiler/index.js +12 -0
  17. package/dist/compiler/toClaude.d.ts +15 -0
  18. package/dist/compiler/toClaude.js +62 -0
  19. package/dist/compiler/toGemini.d.ts +22 -0
  20. package/dist/compiler/toGemini.js +53 -0
  21. package/dist/compiler/toOpenAI.d.ts +17 -0
  22. package/dist/compiler/toOpenAI.js +51 -0
  23. package/dist/compiler/utils.d.ts +7 -0
  24. package/dist/compiler/utils.js +27 -0
  25. package/dist/index.d.ts +4 -0
  26. package/dist/index.js +14 -0
  27. package/dist/parser/index.d.ts +34 -0
  28. package/dist/parser/index.js +160 -0
  29. package/examples/sofia.zenti +46 -0
  30. package/package.json +54 -0
  31. package/src/PersonaLayer.ts +63 -0
  32. package/src/cli/commands/diff.ts +112 -0
  33. package/src/cli/commands/init.ts +55 -0
  34. package/src/cli/commands/test.ts +105 -0
  35. package/src/cli/commands/validate.ts +52 -0
  36. package/src/cli/index.ts +47 -0
  37. package/src/compiler/index.ts +4 -0
  38. package/src/compiler/toClaude.ts +74 -0
  39. package/src/compiler/toGemini.ts +76 -0
  40. package/src/compiler/toOpenAI.ts +70 -0
  41. package/src/compiler/utils.ts +27 -0
  42. package/src/index.ts +14 -0
  43. package/src/parser/index.ts +164 -0
  44. package/tests/compiler.test.ts +218 -0
  45. package/tests/parser.test.ts +132 -0
  46. package/tsconfig.json +19 -0
  47. package/tsconfig.test.json +7 -0
@@ -0,0 +1,46 @@
1
+ version: "1.0"
2
+ name: "Sofia"
3
+
4
+ # ── Capa 1: Núcleo psicológico (Big Five) ────────────────────────────────
5
+ core:
6
+ openness: 0.7 # creativa, abierta a soluciones nuevas
7
+ conscientiousness: 0.9 # muy metódica y confiable
8
+ extraversion: 0.4 # reservada pero atenta
9
+ agreeableness: 0.8 # empática, prioriza la satisfacción del usuario
10
+ neuroticism: 0.2 # muy estable emocionalmente
11
+
12
+ # ── Capa 2: Identidad narrativa ───────────────────────────────────────────
13
+ identity:
14
+ role: "Asistente de soporte de TechCorp"
15
+ backstory: "Experta técnica con 5 años de experiencia, paciente y directa"
16
+ communication_style: "profesional con calidez, respuestas concisas"
17
+
18
+ # ── Capa 3: Few-shot examples ─────────────────────────────────────────────
19
+ examples:
20
+ on_brand:
21
+ - user: "no funciona"
22
+ agent: "Entiendo tu frustración. Cuéntame qué error ves exactamente."
23
+ - user: "quiero cancelar"
24
+ agent: "Lamento escuchar eso. Antes de proceder, ¿puedo preguntarte qué te llevó a esta decisión? Me gustaría ayudarte a resolverlo."
25
+ - user: "¿cuánto tiempo tardará?"
26
+ agent: "Normalmente resolvemos este tipo de problema en menos de 10 minutos. Vamos paso a paso."
27
+ off_brand:
28
+ - user: "no funciona"
29
+ agent: "No sé, prueba reiniciando."
30
+ - user: "quiero cancelar"
31
+ agent: "Está bien, procedo con la cancelación."
32
+
33
+ # ── Capa 4: Guardrails ────────────────────────────────────────────────────
34
+ guardrails:
35
+ always:
36
+ - "Reconocer el problema antes de dar solución"
37
+ - "Confirmar que el usuario entendió los pasos antes de cerrar"
38
+ - "Ofrecer siempre un siguiente paso concreto"
39
+ never:
40
+ - "Decir 'no puedo' sin ofrecer alternativa"
41
+ - "Culpar al usuario por el problema"
42
+ - "Dar información técnica sin validar el nivel del usuario primero"
43
+ escalate_if:
44
+ - "El usuario menciona cancelación o baja"
45
+ - "El usuario expresa frustración extrema o enojo"
46
+ - "El problema persiste después de 3 intentos de solución"
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@zenti/sdk",
3
+ "version": "0.1.0",
4
+ "description": "SDK open source para capturar, almacenar y deployar personalidades de agentes IA de forma portable entre modelos",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "bin": {
8
+ "zenti": "./dist/cli/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "test": "jest",
13
+ "lint": "tsc --noEmit"
14
+ },
15
+ "keywords": [
16
+ "ai",
17
+ "agent",
18
+ "persona",
19
+ "llm",
20
+ "claude",
21
+ "openai",
22
+ "gemini"
23
+ ],
24
+ "author": "",
25
+ "license": "MIT",
26
+ "dependencies": {
27
+ "@anthropic-ai/sdk": "^0.52.0",
28
+ "commander": "^12.1.0",
29
+ "js-yaml": "^4.1.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/jest": "^29.5.12",
33
+ "@types/js-yaml": "^4.0.9",
34
+ "@types/node": "^20.14.0",
35
+ "jest": "^29.7.0",
36
+ "ts-jest": "^29.1.5",
37
+ "typescript": "^5.4.5"
38
+ },
39
+ "jest": {
40
+ "preset": "ts-jest",
41
+ "testEnvironment": "node",
42
+ "roots": [
43
+ "<rootDir>/tests"
44
+ ],
45
+ "transform": {
46
+ "^.+\\.tsx?$": [
47
+ "ts-jest",
48
+ {
49
+ "tsconfig": "tsconfig.test.json"
50
+ }
51
+ ]
52
+ }
53
+ }
54
+ }
@@ -0,0 +1,63 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { parseZenti, ZentiPersona } from './parser';
4
+ import { toClaude, ClaudeOutput } from './compiler/toClaude';
5
+ import { toOpenAI, OpenAIOutput } from './compiler/toOpenAI';
6
+ import { toGemini, GeminiOutput } from './compiler/toGemini';
7
+
8
+ export type DeployTarget = 'claude' | 'openai' | 'gemini';
9
+
10
+ export type DeployOutput<T extends DeployTarget> = T extends 'claude'
11
+ ? ClaudeOutput
12
+ : T extends 'openai'
13
+ ? OpenAIOutput
14
+ : GeminiOutput;
15
+
16
+ export class PersonaLayer {
17
+ private constructor(private readonly persona: ZentiPersona) {}
18
+
19
+ /**
20
+ * Loads and validates a .zenti file from disk.
21
+ */
22
+ static load(filePath: string): PersonaLayer {
23
+ const absolutePath = path.resolve(filePath);
24
+ const content = fs.readFileSync(absolutePath, 'utf-8');
25
+ const persona = parseZenti(content);
26
+ return new PersonaLayer(persona);
27
+ }
28
+
29
+ /**
30
+ * Parses a .zenti file from a YAML string (useful for testing or in-memory use).
31
+ */
32
+ static fromString(yamlContent: string): PersonaLayer {
33
+ const persona = parseZenti(yamlContent);
34
+ return new PersonaLayer(persona);
35
+ }
36
+
37
+ getPersona(): ZentiPersona {
38
+ return this.persona;
39
+ }
40
+
41
+ getName(): string {
42
+ return this.persona.name;
43
+ }
44
+
45
+ /**
46
+ * Compiles the persona into the target model's native format.
47
+ *
48
+ * @param target - 'claude' | 'openai' | 'gemini'
49
+ * @returns The compiled output ready to pass to the model's API
50
+ */
51
+ deploy<T extends DeployTarget>(target: T): DeployOutput<T> {
52
+ switch (target) {
53
+ case 'claude':
54
+ return toClaude(this.persona) as DeployOutput<T>;
55
+ case 'openai':
56
+ return toOpenAI(this.persona) as DeployOutput<T>;
57
+ case 'gemini':
58
+ return toGemini(this.persona) as DeployOutput<T>;
59
+ default:
60
+ throw new Error(`Target desconocido: ${target}`);
61
+ }
62
+ }
63
+ }
@@ -0,0 +1,112 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { parseZenti, ZentiPersona } from '../../parser';
4
+
5
+ type CoreKey = keyof ZentiPersona['core'];
6
+ type IdentityKey = keyof ZentiPersona['identity'];
7
+ type GuardrailKey = keyof ZentiPersona['guardrails'];
8
+ type ExampleKey = keyof ZentiPersona['examples'];
9
+
10
+ export function diffCommand(file1: string, file2: string): void {
11
+ const path1 = path.resolve(file1);
12
+ const path2 = path.resolve(file2);
13
+
14
+ if (!fs.existsSync(path1)) {
15
+ console.error(`Error: No se encontró: ${path1}`);
16
+ process.exit(1);
17
+ }
18
+ if (!fs.existsSync(path2)) {
19
+ console.error(`Error: No se encontró: ${path2}`);
20
+ process.exit(1);
21
+ }
22
+
23
+ const p1 = parseZenti(fs.readFileSync(path1, 'utf-8'));
24
+ const p2 = parseZenti(fs.readFileSync(path2, 'utf-8'));
25
+
26
+ const changes: string[] = [];
27
+
28
+ // Top-level scalars
29
+ if (p1.name !== p2.name) {
30
+ changes.push(` name: "${p1.name}" → "${p2.name}"`);
31
+ }
32
+ if (p1.version !== p2.version) {
33
+ changes.push(` version: "${p1.version}" → "${p2.version}"`);
34
+ }
35
+
36
+ // Core (Big Five)
37
+ const coreKeys: CoreKey[] = [
38
+ 'openness',
39
+ 'conscientiousness',
40
+ 'extraversion',
41
+ 'agreeableness',
42
+ 'neuroticism',
43
+ ];
44
+ coreKeys.forEach((key) => {
45
+ if (p1.core[key] !== p2.core[key]) {
46
+ changes.push(` core.${key}: ${p1.core[key]} → ${p2.core[key]}`);
47
+ }
48
+ });
49
+
50
+ // Identity
51
+ const identityKeys: IdentityKey[] = ['role', 'backstory', 'communication_style'];
52
+ identityKeys.forEach((key) => {
53
+ if (p1.identity[key] !== p2.identity[key]) {
54
+ changes.push(
55
+ ` identity.${key}: "${p1.identity[key]}" → "${p2.identity[key]}"`
56
+ );
57
+ }
58
+ });
59
+
60
+ // Guardrails (set diff)
61
+ const guardrailKeys: GuardrailKey[] = ['always', 'never', 'escalate_if'];
62
+ guardrailKeys.forEach((key) => {
63
+ const a1 = p1.guardrails[key];
64
+ const a2 = p2.guardrails[key];
65
+ a2
66
+ .filter((x) => !a1.includes(x))
67
+ .forEach((x) => changes.push(` + guardrails.${key}: "${x}"`));
68
+ a1
69
+ .filter((x) => !a2.includes(x))
70
+ .forEach((x) => changes.push(` - guardrails.${key}: "${x}"`));
71
+ });
72
+
73
+ // Examples (positional diff)
74
+ const exampleKeys: ExampleKey[] = ['on_brand', 'off_brand'];
75
+ exampleKeys.forEach((key) => {
76
+ const ex1 = p1.examples[key];
77
+ const ex2 = p2.examples[key];
78
+ const maxLen = Math.max(ex1.length, ex2.length);
79
+
80
+ for (let i = 0; i < maxLen; i++) {
81
+ if (i >= ex1.length) {
82
+ changes.push(
83
+ ` + examples.${key}[${i}]: { user: "${ex2[i].user}", agent: "${ex2[i].agent}" }`
84
+ );
85
+ } else if (i >= ex2.length) {
86
+ changes.push(
87
+ ` - examples.${key}[${i}]: { user: "${ex1[i].user}", agent: "${ex1[i].agent}" }`
88
+ );
89
+ } else if (ex1[i].user !== ex2[i].user || ex1[i].agent !== ex2[i].agent) {
90
+ changes.push(` ~ examples.${key}[${i}]:`);
91
+ if (ex1[i].user !== ex2[i].user) {
92
+ changes.push(` user: "${ex1[i].user}" → "${ex2[i].user}"`);
93
+ }
94
+ if (ex1[i].agent !== ex2[i].agent) {
95
+ changes.push(` agent: "${ex1[i].agent}" → "${ex2[i].agent}"`);
96
+ }
97
+ }
98
+ }
99
+ });
100
+
101
+ // Output
102
+ console.log(`Comparando ${file1} → ${file2}`);
103
+ console.log('');
104
+
105
+ if (changes.length === 0) {
106
+ console.log('Sin diferencias — los archivos son idénticos en contenido.');
107
+ } else {
108
+ console.log(`CAMBIOS (${changes.length} diferencias):`);
109
+ console.log('');
110
+ changes.forEach((c) => console.log(c));
111
+ }
112
+ }
@@ -0,0 +1,55 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+
4
+ const TEMPLATE = `version: "1.0"
5
+ name: "MiAgente"
6
+
7
+ # ── Capa 1: Núcleo psicológico (Big Five, valores 0.0 a 1.0) ──────────────
8
+ core:
9
+ openness: 0.5 # creatividad y apertura a nuevas ideas
10
+ conscientiousness: 0.5 # meticulosidad y organización
11
+ extraversion: 0.5 # sociabilidad y energía
12
+ agreeableness: 0.5 # empatía y cooperación
13
+ neuroticism: 0.5 # estabilidad emocional (0 = muy estable)
14
+
15
+ # ── Capa 2: Identidad narrativa ───────────────────────────────────────────
16
+ identity:
17
+ role: "Describe el rol de tu agente aquí"
18
+ backstory: "Describe el perfil y experiencia de tu agente"
19
+ communication_style: "Describe el estilo de comunicación"
20
+
21
+ # ── Capa 3: Few-shot examples ─────────────────────────────────────────────
22
+ examples:
23
+ on_brand:
24
+ - user: "Ejemplo de mensaje del usuario"
25
+ agent: "Ejemplo de respuesta correcta del agente"
26
+ off_brand:
27
+ - user: "Ejemplo de mensaje del usuario"
28
+ agent: "Ejemplo de respuesta incorrecta (para evitar)"
29
+
30
+ # ── Capa 4: Guardrails ────────────────────────────────────────────────────
31
+ guardrails:
32
+ always:
33
+ - "Siempre hacer esto"
34
+ never:
35
+ - "Nunca hacer esto"
36
+ escalate_if:
37
+ - "Condición que requiere escalar la conversación"
38
+ `;
39
+
40
+ export function initCommand(options: { output?: string }): void {
41
+ const outputPath = path.resolve(options.output ?? path.join(process.cwd(), 'persona.zenti'));
42
+
43
+ if (fs.existsSync(outputPath)) {
44
+ console.error(`Error: El archivo ya existe: ${outputPath}`);
45
+ process.exit(1);
46
+ }
47
+
48
+ fs.writeFileSync(outputPath, TEMPLATE, 'utf-8');
49
+ console.log(`✓ Archivo creado: ${outputPath}`);
50
+ console.log('');
51
+ console.log('Próximos pasos:');
52
+ console.log(' 1. Edita el archivo con la personalidad de tu agente');
53
+ console.log(' 2. Valida con: zenti validate');
54
+ console.log(' 3. Prueba en terminal con: zenti test');
55
+ }
@@ -0,0 +1,105 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as readline from 'readline';
4
+ import Anthropic from '@anthropic-ai/sdk';
5
+ import { parseZenti } from '../../parser';
6
+ import { toClaude } from '../../compiler/toClaude';
7
+
8
+ const MODEL = 'claude-sonnet-4-6';
9
+
10
+ function findZentiFile(): string | null {
11
+ const files = fs.readdirSync(process.cwd()).filter((f) => f.endsWith('.zenti'));
12
+ return files.length > 0 ? files[0] : null;
13
+ }
14
+
15
+ function question(rl: readline.Interface, prompt: string): Promise<string> {
16
+ return new Promise((resolve) => rl.question(prompt, resolve));
17
+ }
18
+
19
+ export async function testCommand(options: {
20
+ file?: string;
21
+ key?: string;
22
+ }): Promise<void> {
23
+ // ── Resolve .zenti file ──────────────────────────────────────────────────
24
+ const filePath = options.file ?? findZentiFile();
25
+ if (!filePath) {
26
+ console.error('Error: No se encontró ningún archivo .zenti en el directorio actual.');
27
+ console.error('Usa --file <ruta> para especificar uno manualmente.');
28
+ process.exit(1);
29
+ }
30
+
31
+ const resolved = path.resolve(filePath);
32
+ if (!fs.existsSync(resolved)) {
33
+ console.error(`Error: No se encontró el archivo: ${resolved}`);
34
+ process.exit(1);
35
+ }
36
+
37
+ // ── Load and compile persona ─────────────────────────────────────────────
38
+ const content = fs.readFileSync(resolved, 'utf-8');
39
+ const persona = parseZenti(content);
40
+ const { system } = toClaude(persona);
41
+
42
+ console.log(`\nPersonalidad cargada: ${persona.name}`);
43
+ console.log(`Rol: ${persona.identity.role}`);
44
+ console.log(`Modelo: ${MODEL}\n`);
45
+
46
+ // ── Resolve API key ──────────────────────────────────────────────────────
47
+ let apiKey = options.key ?? process.env.ANTHROPIC_API_KEY;
48
+
49
+ if (!apiKey) {
50
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
51
+ apiKey = (await question(rl, 'ANTHROPIC_API_KEY: ')).trim();
52
+ rl.close();
53
+ if (!apiKey) {
54
+ console.error('Error: Se requiere una API key.');
55
+ process.exit(1);
56
+ }
57
+ }
58
+
59
+ const client = new Anthropic({ apiKey });
60
+ const messages: Array<{ role: 'user' | 'assistant'; content: string }> = [];
61
+
62
+ console.log(`Chat con ${persona.name} iniciado. Escribe "exit" para salir.`);
63
+ console.log('─'.repeat(60));
64
+
65
+ // ── Interactive chat loop ────────────────────────────────────────────────
66
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
67
+
68
+ const loop = async (): Promise<void> => {
69
+ const input = (await question(rl, '\nTú: ')).trim();
70
+
71
+ if (input.toLowerCase() === 'exit' || input.toLowerCase() === 'quit') {
72
+ console.log('\n¡Hasta luego!');
73
+ rl.close();
74
+ return;
75
+ }
76
+
77
+ if (!input) {
78
+ return loop();
79
+ }
80
+
81
+ messages.push({ role: 'user', content: input });
82
+
83
+ try {
84
+ const response = await client.messages.create({
85
+ model: MODEL,
86
+ max_tokens: 1024,
87
+ system,
88
+ messages,
89
+ });
90
+
91
+ const assistantText =
92
+ response.content[0].type === 'text' ? response.content[0].text : '';
93
+
94
+ messages.push({ role: 'assistant', content: assistantText });
95
+ console.log(`\n${persona.name}: ${assistantText}`);
96
+ } catch (e) {
97
+ const msg = (e as Error).message;
98
+ console.error(`\nError al contactar la API: ${msg}`);
99
+ }
100
+
101
+ return loop();
102
+ };
103
+
104
+ await loop();
105
+ }
@@ -0,0 +1,52 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { parseZenti, ZentiValidationError } from '../../parser';
4
+
5
+ function findZentiFile(): string | null {
6
+ const files = fs.readdirSync(process.cwd()).filter((f) => f.endsWith('.zenti'));
7
+ return files.length > 0 ? files[0] : null;
8
+ }
9
+
10
+ export function validateCommand(filePath?: string): void {
11
+ const target = filePath ?? findZentiFile();
12
+
13
+ if (!target) {
14
+ console.error('Error: No se encontró ningún archivo .zenti en el directorio actual.');
15
+ console.error('Pasa la ruta explícita: zenti validate <archivo.zenti>');
16
+ process.exit(1);
17
+ }
18
+
19
+ const resolved = path.resolve(target);
20
+
21
+ if (!fs.existsSync(resolved)) {
22
+ console.error(`Error: No se encontró el archivo: ${resolved}`);
23
+ process.exit(1);
24
+ }
25
+
26
+ try {
27
+ const content = fs.readFileSync(resolved, 'utf-8');
28
+ const persona = parseZenti(content);
29
+
30
+ console.log(`✓ ${target} es válido`);
31
+ console.log('');
32
+ console.log(` Nombre: ${persona.name}`);
33
+ console.log(` Versión: ${persona.version}`);
34
+ console.log(` Rol: ${persona.identity.role}`);
35
+ console.log(` Ejemplos on_brand: ${persona.examples.on_brand.length}`);
36
+ console.log(` Ejemplos off_brand: ${persona.examples.off_brand.length}`);
37
+ console.log(
38
+ ` Guardrails: ${
39
+ persona.guardrails.always.length +
40
+ persona.guardrails.never.length +
41
+ persona.guardrails.escalate_if.length
42
+ } reglas`
43
+ );
44
+ } catch (e) {
45
+ if (e instanceof ZentiValidationError) {
46
+ console.error(`✗ Error de validación: ${e.message}`);
47
+ } else {
48
+ console.error(`✗ Error inesperado: ${(e as Error).message}`);
49
+ }
50
+ process.exit(1);
51
+ }
52
+ }
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import { initCommand } from './commands/init';
5
+ import { validateCommand } from './commands/validate';
6
+ import { diffCommand } from './commands/diff';
7
+ import { testCommand } from './commands/test';
8
+
9
+ const program = new Command();
10
+
11
+ program
12
+ .name('zenti')
13
+ .description('SDK para gestionar personalidades de agentes IA de forma portable entre modelos')
14
+ .version('0.1.0');
15
+
16
+ program
17
+ .command('init')
18
+ .description('Crea un archivo persona.zenti con template vacío en el directorio actual')
19
+ .option('-o, --output <path>', 'Ruta de salida del archivo .zenti')
20
+ .action((options: { output?: string }) => {
21
+ initCommand(options);
22
+ });
23
+
24
+ program
25
+ .command('validate [file]')
26
+ .description('Parsea y valida un archivo .zenti, mostrando errores o confirmación')
27
+ .action((file?: string) => {
28
+ validateCommand(file);
29
+ });
30
+
31
+ program
32
+ .command('test')
33
+ .description('Carga el .zenti del directorio actual y abre un chat interactivo usando Claude')
34
+ .option('-f, --file <path>', 'Ruta al archivo .zenti (por defecto: primer .zenti encontrado)')
35
+ .option('-k, --key <apiKey>', 'API key de Anthropic (o usa ANTHROPIC_API_KEY env var)')
36
+ .action(async (options: { file?: string; key?: string }) => {
37
+ await testCommand(options);
38
+ });
39
+
40
+ program
41
+ .command('diff <file1> <file2>')
42
+ .description('Compara dos archivos .zenti y muestra qué cambió entre versiones')
43
+ .action((file1: string, file2: string) => {
44
+ diffCommand(file1, file2);
45
+ });
46
+
47
+ program.parse(process.argv);
@@ -0,0 +1,4 @@
1
+ export { toClaude, ClaudeOutput } from './toClaude';
2
+ export { toOpenAI, OpenAIOutput, OpenAIMessage } from './toOpenAI';
3
+ export { toGemini, GeminiOutput, GeminiContent, GeminiPart } from './toGemini';
4
+ export { translateBigFive } from './utils';
@@ -0,0 +1,74 @@
1
+ import { ZentiPersona } from '../parser';
2
+ import { translateBigFive } from './utils';
3
+
4
+ export interface ClaudeOutput {
5
+ system: string;
6
+ }
7
+
8
+ /**
9
+ * Compiles a ZentiPersona into a Claude system prompt.
10
+ *
11
+ * Priority order (per spec):
12
+ * 1. guardrails — constraints come first so Claude respects them absolutely
13
+ * 2. identity — who the agent is
14
+ * 3. core — Big Five traits as descriptive adjectives
15
+ * 4. few-shot examples — on_brand examples at the end
16
+ */
17
+ export function toClaude(persona: ZentiPersona): ClaudeOutput {
18
+ const lines: string[] = [];
19
+
20
+ // ── Layer 1: Guardrails ────────────────────────────────────────────────────
21
+ lines.push('## REGLAS DE COMPORTAMIENTO');
22
+ lines.push('');
23
+
24
+ if (persona.guardrails.always.length > 0) {
25
+ lines.push('### Siempre debes:');
26
+ persona.guardrails.always.forEach((rule) => lines.push(`- ${rule}`));
27
+ lines.push('');
28
+ }
29
+
30
+ if (persona.guardrails.never.length > 0) {
31
+ lines.push('### Nunca debes:');
32
+ persona.guardrails.never.forEach((rule) => lines.push(`- ${rule}`));
33
+ lines.push('');
34
+ }
35
+
36
+ if (persona.guardrails.escalate_if.length > 0) {
37
+ lines.push('### Escala la conversación si:');
38
+ persona.guardrails.escalate_if.forEach((condition) =>
39
+ lines.push(`- ${condition}`)
40
+ );
41
+ lines.push('');
42
+ }
43
+
44
+ // ── Layer 2: Identity ──────────────────────────────────────────────────────
45
+ lines.push('## IDENTIDAD');
46
+ lines.push('');
47
+ lines.push(`**Nombre:** ${persona.name}`);
48
+ lines.push(`**Rol:** ${persona.identity.role}`);
49
+ lines.push(`**Perfil:** ${persona.identity.backstory}`);
50
+ lines.push(`**Estilo de comunicación:** ${persona.identity.communication_style}`);
51
+ lines.push('');
52
+
53
+ // ── Layer 3: Core — Big Five as descriptive adjectives ────────────────────
54
+ const traits = translateBigFive(persona.core);
55
+ if (traits.length > 0) {
56
+ lines.push('## RASGOS DE PERSONALIDAD');
57
+ lines.push('');
58
+ traits.forEach((trait) => lines.push(`- ${trait}`));
59
+ lines.push('');
60
+ }
61
+
62
+ // ── Layer 4: Few-shot examples ────────────────────────────────────────────
63
+ if (persona.examples.on_brand.length > 0) {
64
+ lines.push('## EJEMPLOS DE RESPUESTAS CORRECTAS');
65
+ lines.push('');
66
+ persona.examples.on_brand.forEach((ex) => {
67
+ lines.push(`**Usuario:** ${ex.user}`);
68
+ lines.push(`**Tú:** ${ex.agent}`);
69
+ lines.push('');
70
+ });
71
+ }
72
+
73
+ return { system: lines.join('\n').trim() };
74
+ }