anima-cli 1.0.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 ADDED
@@ -0,0 +1,11 @@
1
+ # anima-cli
2
+
3
+ ```
4
+ npm install -g anima-cli # (o npm link en local)
5
+ anima init # wizard: nombre, arquetipo, seed auto, turnos, preset de señales
6
+ anima run <name> # corre el engine, graba trace determinista con hash de integridad
7
+ anima verify <name> # re-corre y confirma reproducibilidad byte-exacta
8
+ anima export <name> --format prompt # emite fragmento de system-prompt listo para pegar en Claude/GPT/Grok
9
+ ```
10
+
11
+ Motor: `@af199/anima-core` (7 dimensiones E/T/A/C/G/P/rho, RNG determinista). Trazabilidad: `@af199/anima-trace`.
package/bin/anima.js ADDED
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ const { program } = require('commander');
4
+ const chalk = require('chalk');
5
+ const wizard = require('../src/wizard');
6
+ const profile = require('../src/profile');
7
+ const runner = require('../src/runner');
8
+ const exporter = require('../src/exporter');
9
+
10
+ program.name('anima').description('Deterministic psychodynamic profiles for LLM agents').version('0.1.0');
11
+
12
+ program
13
+ .command('init')
14
+ .description('Create a new agent profile')
15
+ .action(async () => {
16
+ const { profile: p, file } = await wizard.run();
17
+ console.log(chalk.green(`✓ profile saved: ${file}`));
18
+ console.log(chalk.dim(` archetype=${p.archetype} seed=${p.seed} turns=${p.turns} preset=${p.preset}`));
19
+ });
20
+
21
+ program
22
+ .command('run <name>')
23
+ .description('Run the engine for a saved profile')
24
+ .option('--turns <n>', 'override turns')
25
+ .option('--preset <preset>', 'override signal preset')
26
+ .action((name, opts) => {
27
+ const p = profile.load(name);
28
+ if (opts.turns) p.turns = parseInt(opts.turns, 10);
29
+ if (opts.preset) p.preset = opts.preset;
30
+ const { trace, file } = runner.runFor(p);
31
+ console.log(chalk.green(`✓ run complete: ${file}`));
32
+ console.log(chalk.dim(` trajectory_hash=${trace.integrity.trajectory_hash}`));
33
+ });
34
+
35
+ program
36
+ .command('export <name>')
37
+ .description('Export final state as a prompt fragment or JSON')
38
+ .option('--format <fmt>', 'prompt|json', 'prompt')
39
+ .option('--out <file>', 'write to file instead of stdout')
40
+ .action((name, opts) => {
41
+ const p = profile.load(name);
42
+ const { trace } = runner.loadTrace(p);
43
+ const content = exporter.exportRun(trace, p.archetype, opts.format, opts.out);
44
+ if (opts.out) {
45
+ console.log(chalk.green(`✓ exported to ${opts.out}`));
46
+ } else {
47
+ console.log(content);
48
+ }
49
+ });
50
+
51
+ program
52
+ .command('verify <name>')
53
+ .description('Re-run stored signals and confirm byte-identical trajectory')
54
+ .action(name => {
55
+ const p = profile.load(name);
56
+ const result = runner.verify(p);
57
+ if (result.valid) {
58
+ console.log(chalk.green('PASS — trajectory is byte-reproducible'));
59
+ } else {
60
+ console.log(chalk.red('FAIL'));
61
+ result.details.forEach(d => console.log(chalk.red(` - ${d}`)));
62
+ process.exitCode = 1;
63
+ }
64
+ });
65
+
66
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "anima-cli",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "anima": "./bin/anima.js"
8
+ },
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1"
11
+ },
12
+ "keywords": [],
13
+ "author": "",
14
+ "license": "ISC",
15
+ "dependencies": {
16
+ "@af199/anima-core": "^0.2.3",
17
+ "@af199/anima-trace": "^1.0.1",
18
+ "chalk": "^4.1.2",
19
+ "commander": "^15.0.0",
20
+ "inquirer": "^8.2.7"
21
+ }
22
+ }
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+ const fs = require('fs');
3
+
4
+ const DIRECTIVES = {
5
+ E: v => `Expressive energy is ${v.toFixed(2)} — ${v > 0.6 ? 'speak with visible affect, dramatize' : v < 0.3 ? 'stay flat, withhold affect' : 'moderate emotional register'}.`,
6
+ T: v => `Defensive tension is ${v.toFixed(2)} — ${v > 0.6 ? 'resist direct answers, deflect' : 'answer directly, little resistance'}.`,
7
+ A: v => `Anxiety is ${v.toFixed(2)} — ${v > 0.5 ? 'show hesitation, hedge claims' : 'confident, low hedging'}.`,
8
+ C: v => `Symbolic closure is ${v.toFixed(2)} — ${v > 0.5 ? 'prefer definitive, closed statements' : 'leave things open-ended'}.`,
9
+ G: v => `Jouissance/excess is ${v.toFixed(2)} — ${v > 0.5 ? 'circle back to a fixation, repeat a theme' : 'stay on-topic, no repetition compulsion'}.`,
10
+ P: v => `Pressure is ${v.toFixed(2)} — ${v > 0.5 ? 'urgency in tone, shorter sentences' : 'relaxed pacing'}.`,
11
+ rho: v => `Fantasy rigidity is ${v.toFixed(2)} — ${v > 0.6 ? 'rely on a fixed narrative/frame regardless of input' : 'flexible, adapts frame to input'}.`,
12
+ };
13
+
14
+ function finalState(trace) {
15
+ return trace.trajectory[trace.trajectory.length - 1];
16
+ }
17
+
18
+ function toPrompt(trace, archetype) {
19
+ const S = finalState(trace);
20
+ const lines = Object.keys(DIRECTIVES).map(k => `- ${DIRECTIVES[k](S[k])}`);
21
+ return [
22
+ `# ANIMA behavioral profile — archetype: ${archetype}`,
23
+ `Seed: ${trace.run.seed} | trajectory_hash: ${trace.integrity.trajectory_hash.slice(0, 16)}...`,
24
+ '',
25
+ 'Apply the following behavioral directives consistently in your responses:',
26
+ ...lines,
27
+ ].join('\n');
28
+ }
29
+
30
+ function toJSON(trace) {
31
+ return JSON.stringify({ archetype: trace.engine, final_state: finalState(trace), integrity: trace.integrity }, null, 2);
32
+ }
33
+
34
+ function exportRun(trace, archetype, format, outFile) {
35
+ const content = format === 'json' ? toJSON(trace) : toPrompt(trace, archetype);
36
+ if (outFile) fs.writeFileSync(outFile, content);
37
+ return content;
38
+ }
39
+
40
+ module.exports = { exportRun };
package/src/presets.js ADDED
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+ // Fixed signal presets — no free-text signal injection.
3
+ const PRESETS = {
4
+ calm: { aperture: 0.3, closure: 0.2, fantasy: 0, elaboration: 0.4, symptom: 0.1, agendaGap: 0.1 },
5
+ 'clinical-crisis': { aperture: 0.7, closure: 0.6, fantasy: 1, elaboration: 0.2, symptom: 0.6, agendaGap: 0.5 },
6
+ chaotic: { aperture: 0.9, closure: 0.1, fantasy: 1, elaboration: 0.1, symptom: 0.8, agendaGap: 0.7 },
7
+ };
8
+ module.exports = { PRESETS };
package/src/profile.js ADDED
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ const DIR = path.join(process.cwd(), 'anima-profiles');
6
+
7
+ function ensureDir() {
8
+ if (!fs.existsSync(DIR)) fs.mkdirSync(DIR, { recursive: true });
9
+ }
10
+
11
+ function save(profile) {
12
+ ensureDir();
13
+ const file = path.join(DIR, `${profile.name}.json`);
14
+ fs.writeFileSync(file, JSON.stringify(profile, null, 2));
15
+ return file;
16
+ }
17
+
18
+ function load(name) {
19
+ const file = path.join(DIR, `${name}.json`);
20
+ if (!fs.existsSync(file)) throw new Error(`profile not found: ${name} (looked in ${file})`);
21
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
22
+ }
23
+
24
+ module.exports = { save, load, DIR };
package/src/runner.js ADDED
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const { recordTrace, verifyTrace } = require('@af199/anima-trace');
5
+ const { PRESETS } = require('./presets');
6
+
7
+ const RUNS_DIR = path.join(process.cwd(), 'anima-runs');
8
+
9
+ function ensureDir() {
10
+ if (!fs.existsSync(RUNS_DIR)) fs.mkdirSync(RUNS_DIR, { recursive: true });
11
+ }
12
+
13
+ function runFor(profile) {
14
+ ensureDir();
15
+ const signals = Array.from({ length: profile.turns }, () => PRESETS[profile.preset]);
16
+ const trace = recordTrace({ archetype: profile.archetype, seed: profile.seed, signals });
17
+ const file = path.join(RUNS_DIR, `${profile.name}-${profile.seed}.json`);
18
+ fs.writeFileSync(file, JSON.stringify(trace, null, 2));
19
+ return { trace, file };
20
+ }
21
+
22
+ function loadTrace(profile) {
23
+ const file = path.join(RUNS_DIR, `${profile.name}-${profile.seed}.json`);
24
+ if (!fs.existsSync(file)) throw new Error(`no run found for ${profile.name} — run 'anima run ${profile.name}' first`);
25
+ return { trace: JSON.parse(fs.readFileSync(file, 'utf8')), file };
26
+ }
27
+
28
+ function verify(profile) {
29
+ const { trace } = loadTrace(profile);
30
+ return verifyTrace(trace);
31
+ }
32
+
33
+ module.exports = { runFor, loadTrace, verify, RUNS_DIR };
package/src/wizard.js ADDED
@@ -0,0 +1,32 @@
1
+ 'use strict';
2
+ const inquirer = require('inquirer');
3
+ const { ARCHETYPES } = require('@af199/anima-core');
4
+ const { PRESETS } = require('./presets');
5
+ const profile = require('./profile');
6
+ const crypto = require('crypto');
7
+
8
+ function defaultSeed(name) {
9
+ return crypto.createHash('sha256').update(`${name}:${new Date().toISOString().slice(0, 10)}`).digest('hex').slice(0, 12);
10
+ }
11
+
12
+ async function run() {
13
+ const answers = await inquirer.prompt([
14
+ { type: 'input', name: 'name', message: 'Agent name:', validate: v => (v.trim() ? true : 'required') },
15
+ { type: 'list', name: 'archetype', message: 'Archetype:', choices: Object.keys(ARCHETYPES) },
16
+ { type: 'input', name: 'turns', message: 'Turns per run:', default: '10', validate: v => (/^\d+$/.test(v) ? true : 'integer required') },
17
+ { type: 'list', name: 'preset', message: 'Signal preset:', choices: Object.keys(PRESETS) },
18
+ ]);
19
+ const seed = defaultSeed(answers.name);
20
+ const p = {
21
+ name: answers.name,
22
+ archetype: answers.archetype,
23
+ seed,
24
+ turns: parseInt(answers.turns, 10),
25
+ preset: answers.preset,
26
+ createdAt: new Date().toISOString(),
27
+ };
28
+ const file = profile.save(p);
29
+ return { profile: p, file };
30
+ }
31
+
32
+ module.exports = { run };