@danieltmn/tamnora 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 (123) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/LICENSE +21 -0
  3. package/README.md +130 -0
  4. package/SECURITY.md +31 -0
  5. package/harness/packages/cli/bin/tamnora.mjs +11 -0
  6. package/harness/packages/cli/package.json +10 -0
  7. package/harness/packages/cli/src/cli.js +90 -0
  8. package/harness/packages/cli/src/commands/autostart.js +121 -0
  9. package/harness/packages/cli/src/commands/bridge.js +274 -0
  10. package/harness/packages/cli/src/commands/doctor.js +73 -0
  11. package/harness/packages/cli/src/commands/eval.js +183 -0
  12. package/harness/packages/cli/src/commands/init.js +31 -0
  13. package/harness/packages/cli/src/commands/mcp.js +101 -0
  14. package/harness/packages/cli/src/commands/repl.js +172 -0
  15. package/harness/packages/cli/src/commands/run.js +165 -0
  16. package/harness/packages/cli/src/commands/serve.js +1 -0
  17. package/harness/packages/cli/src/commands/server.js +345 -0
  18. package/harness/packages/cli/src/commands/tui.js +110 -0
  19. package/harness/packages/cli/src/commands/update.js +76 -0
  20. package/harness/packages/cli/src/commands/user.js +135 -0
  21. package/harness/packages/cli/src/lib/core.js +2 -0
  22. package/harness/packages/cli/src/lib/daemon.js +153 -0
  23. package/harness/packages/cli/src/lib/qr.js +203 -0
  24. package/harness/packages/cli/src/lib/runtime.js +110 -0
  25. package/harness/packages/cli/src/lib/tunnel.js +113 -0
  26. package/harness/packages/cli/src/lib/util.js +24 -0
  27. package/harness/packages/cli/src/tui/app.js +302 -0
  28. package/harness/packages/cli/src/tui/keys.js +78 -0
  29. package/harness/packages/cli/src/tui/layout.js +130 -0
  30. package/harness/packages/cli/src/tui/screen.js +13 -0
  31. package/harness/packages/cli/src/ui/color.js +15 -0
  32. package/harness/packages/cli/test/release.test.js +34 -0
  33. package/harness/packages/cli/test/tui.test.js +99 -0
  34. package/harness/packages/cli/test/tunnel.test.js +20 -0
  35. package/harness/packages/core/package.json +14 -0
  36. package/harness/packages/core/src/agent/context.js +66 -0
  37. package/harness/packages/core/src/agent/modes.js +26 -0
  38. package/harness/packages/core/src/agent/runAgent.js +257 -0
  39. package/harness/packages/core/src/config/app.js +49 -0
  40. package/harness/packages/core/src/config/home.js +98 -0
  41. package/harness/packages/core/src/config/identity.js +41 -0
  42. package/harness/packages/core/src/config/index.js +69 -0
  43. package/harness/packages/core/src/config/providers.js +24 -0
  44. package/harness/packages/core/src/env.js +19 -0
  45. package/harness/packages/core/src/eval/dataset.js +47 -0
  46. package/harness/packages/core/src/eval/judge.js +39 -0
  47. package/harness/packages/core/src/eval/report.js +48 -0
  48. package/harness/packages/core/src/eval/runTask.js +71 -0
  49. package/harness/packages/core/src/eval/runner.js +150 -0
  50. package/harness/packages/core/src/eval/scorers.js +217 -0
  51. package/harness/packages/core/src/eval/task.js +59 -0
  52. package/harness/packages/core/src/index.js +72 -0
  53. package/harness/packages/core/src/mcp/common.js +82 -0
  54. package/harness/packages/core/src/mcp/http.js +149 -0
  55. package/harness/packages/core/src/mcp/index.js +167 -0
  56. package/harness/packages/core/src/mcp/stdio.js +218 -0
  57. package/harness/packages/core/src/providers/index.js +119 -0
  58. package/harness/packages/core/src/providers/ollama.js +76 -0
  59. package/harness/packages/core/src/providers/openaiCompatible.js +57 -0
  60. package/harness/packages/core/src/providers/openrouter.js +57 -0
  61. package/harness/packages/core/src/providers/presets.js +19 -0
  62. package/harness/packages/core/src/providers/resilience.js +74 -0
  63. package/harness/packages/core/src/providers/stream.js +200 -0
  64. package/harness/packages/core/src/providers/utils.js +66 -0
  65. package/harness/packages/core/src/storage/sqlite.js +453 -0
  66. package/harness/packages/core/src/tools/diff.js +90 -0
  67. package/harness/packages/core/src/tools/executors.js +277 -0
  68. package/harness/packages/core/src/tools/glob.js +67 -0
  69. package/harness/packages/core/src/tools/index.js +28 -0
  70. package/harness/packages/core/src/tools/paths.js +29 -0
  71. package/harness/packages/core/src/tools/permissions.js +69 -0
  72. package/harness/packages/core/src/tools/registry.js +44 -0
  73. package/harness/packages/core/src/tools/schema.js +177 -0
  74. package/harness/packages/core/test/agent.test.js +237 -0
  75. package/harness/packages/core/test/config.test.js +33 -0
  76. package/harness/packages/core/test/context.test.js +44 -0
  77. package/harness/packages/core/test/diff.test.js +20 -0
  78. package/harness/packages/core/test/eval.test.js +144 -0
  79. package/harness/packages/core/test/git-tools.test.js +44 -0
  80. package/harness/packages/core/test/home.test.js +62 -0
  81. package/harness/packages/core/test/mcp.test.js +158 -0
  82. package/harness/packages/core/test/providers.test.js +80 -0
  83. package/harness/packages/core/test/resilience.test.js +61 -0
  84. package/harness/packages/core/test/storage.test.js +55 -0
  85. package/harness/packages/core/test/stream.test.js +91 -0
  86. package/harness/packages/core/test/tools.test.js +96 -0
  87. package/harness/packages/server/bin/tamnora-server.mjs +11 -0
  88. package/harness/packages/server/package.json +10 -0
  89. package/harness/packages/server/public/app.js +2188 -0
  90. package/harness/packages/server/public/img/fondo-agentes.webp +0 -0
  91. package/harness/packages/server/public/index.html +368 -0
  92. package/harness/packages/server/public/landing/index.html +523 -0
  93. package/harness/packages/server/public/landing/landing.js +89 -0
  94. package/harness/packages/server/public/markdown.js +174 -0
  95. package/harness/packages/server/public/styles.css +923 -0
  96. package/harness/packages/server/src/auth.js +146 -0
  97. package/harness/packages/server/src/cli.js +61 -0
  98. package/harness/packages/server/src/index.js +1 -0
  99. package/harness/packages/server/src/lib/approvals.js +51 -0
  100. package/harness/packages/server/src/lib/core.js +2 -0
  101. package/harness/packages/server/src/lib/http.js +74 -0
  102. package/harness/packages/server/src/lib/modelCache.js +32 -0
  103. package/harness/packages/server/src/lib/prompt.js +48 -0
  104. package/harness/packages/server/src/lib/workspace.js +74 -0
  105. package/harness/packages/server/src/routes/auth.js +56 -0
  106. package/harness/packages/server/src/routes/chat.js +222 -0
  107. package/harness/packages/server/src/routes/eval.js +100 -0
  108. package/harness/packages/server/src/routes/files.js +117 -0
  109. package/harness/packages/server/src/routes/folders.js +80 -0
  110. package/harness/packages/server/src/routes/mcp.js +50 -0
  111. package/harness/packages/server/src/routes/models.js +26 -0
  112. package/harness/packages/server/src/routes/providers.js +135 -0
  113. package/harness/packages/server/src/routes/sessions.js +157 -0
  114. package/harness/packages/server/src/server.js +249 -0
  115. package/harness/packages/server/src/static.js +67 -0
  116. package/harness/packages/server/test/markdown.test.js +36 -0
  117. package/harness/packages/server/test/modelCache.test.js +72 -0
  118. package/harness/packages/server/test/server.test.js +809 -0
  119. package/harness/scripts/example-dataset.jsonl +4 -0
  120. package/harness/scripts/mcp-echo-server.mjs +108 -0
  121. package/harness/scripts/mock-provider.mjs +134 -0
  122. package/harness/tamnora.example.json +18 -0
  123. package/package.json +70 -0
@@ -0,0 +1,4 @@
1
+ {"id":"math","input":"¿Cuánto es 17 * 23? Respondé solo el número.","expect":{"equals":"391"}}
2
+ {"id":"write-file","input":"Creá el archivo saludo.txt con el texto \"hola\".","expect":[{"toolCalled":"write"},{"fileContains":{"path":"saludo.txt","value":"hola"}}]}
3
+ {"id":"json","input":"Devolvé JSON con las claves \"ok\" (true) y \"n\" (3).","expect":{"jsonSchema":{"type":"object","required":["ok","n"],"properties":{"ok":{"type":"boolean"},"n":{"type":"integer"}}}}}
4
+ {"id":"regex","input":"Escribí una oración que contenga un número.","expect":{"regex":"\\d"}}
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env node
2
+ // Minimal, zero-dependency MCP server over stdio, for testing the harness.
3
+ //
4
+ // Tools: `echo` (returns the text) and `add` (sums two numbers).
5
+ // Resources: `echo://welcome`. Prompts: `saludo` (arg `nombre`).
6
+ // Only JSON-RPC messages go to stdout; logs go to stderr.
7
+
8
+ import readline from 'node:readline';
9
+
10
+ const SERVER_INFO = { name: 'echo', version: '1.0.0' };
11
+
12
+ const RESOURCES = [{ uri: 'echo://welcome', name: 'Bienvenida', mimeType: 'text/plain' }];
13
+ const RESOURCE_TEXT = { 'echo://welcome': 'Hola desde el MCP echo.' };
14
+
15
+ const PROMPTS = [
16
+ {
17
+ name: 'saludo',
18
+ description: 'Genera un saludo para una persona.',
19
+ arguments: [{ name: 'nombre', description: 'Nombre', required: true }],
20
+ },
21
+ ];
22
+
23
+ const TOOLS = [
24
+ {
25
+ name: 'echo',
26
+ description: 'Devuelve el texto recibido, con el prefijo "echo: ".',
27
+ inputSchema: {
28
+ type: 'object',
29
+ properties: { text: { type: 'string', description: 'Texto a repetir' } },
30
+ required: ['text'],
31
+ },
32
+ },
33
+ {
34
+ name: 'add',
35
+ description: 'Suma dos números y devuelve el resultado.',
36
+ inputSchema: {
37
+ type: 'object',
38
+ properties: { a: { type: 'number' }, b: { type: 'number' } },
39
+ required: ['a', 'b'],
40
+ },
41
+ },
42
+ ];
43
+
44
+ function text(value) {
45
+ return { content: [{ type: 'text', text: String(value) }] };
46
+ }
47
+
48
+ function callTool(name, args = {}) {
49
+ if (name === 'echo') return text(`echo: ${args.text ?? ''}`);
50
+ if (name === 'add') return text(Number(args.a) + Number(args.b));
51
+ return { content: [{ type: 'text', text: `tool desconocida: ${name}` }], isError: true };
52
+ }
53
+
54
+ function handle(message) {
55
+ const { id, method, params } = message;
56
+ if (method === 'initialize') {
57
+ return {
58
+ jsonrpc: '2.0',
59
+ id,
60
+ result: {
61
+ protocolVersion: '2025-06-18',
62
+ capabilities: { tools: {}, resources: {}, prompts: {} },
63
+ serverInfo: SERVER_INFO,
64
+ },
65
+ };
66
+ }
67
+ if (method === 'notifications/initialized' || method === 'notifications/cancelled') return null;
68
+ if (method === 'ping') return { jsonrpc: '2.0', id, result: {} };
69
+ if (method === 'tools/list') return { jsonrpc: '2.0', id, result: { tools: TOOLS } };
70
+ if (method === 'tools/call') return { jsonrpc: '2.0', id, result: callTool(params?.name, params?.arguments) };
71
+ if (method === 'resources/list') return { jsonrpc: '2.0', id, result: { resources: RESOURCES } };
72
+ if (method === 'resources/read') {
73
+ const uri = params?.uri;
74
+ if (!(uri in RESOURCE_TEXT)) {
75
+ return { jsonrpc: '2.0', id, error: { code: -32602, message: `recurso desconocido: ${uri}` } };
76
+ }
77
+ return { jsonrpc: '2.0', id, result: { contents: [{ uri, mimeType: 'text/plain', text: RESOURCE_TEXT[uri] }] } };
78
+ }
79
+ if (method === 'prompts/list') return { jsonrpc: '2.0', id, result: { prompts: PROMPTS } };
80
+ if (method === 'prompts/get') {
81
+ const nombre = params?.arguments?.nombre ?? '';
82
+ return {
83
+ jsonrpc: '2.0',
84
+ id,
85
+ result: {
86
+ description: 'Saludo',
87
+ messages: [{ role: 'user', content: { type: 'text', text: `Hola ${nombre}!` } }],
88
+ },
89
+ };
90
+ }
91
+ if (id === undefined) return null;
92
+ return { jsonrpc: '2.0', id, error: { code: -32601, message: `método no soportado: ${method}` } };
93
+ }
94
+
95
+ const rl = readline.createInterface({ input: process.stdin });
96
+ process.stderr.write('mcp-echo-server listo\n');
97
+ rl.on('line', (line) => {
98
+ const trimmed = line.trim();
99
+ if (!trimmed) return;
100
+ let message;
101
+ try {
102
+ message = JSON.parse(trimmed);
103
+ } catch {
104
+ return;
105
+ }
106
+ const response = handle(message);
107
+ if (response) process.stdout.write(`${JSON.stringify(response)}\n`);
108
+ });
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env node
2
+ // Local mock provider for testing the Tamnora harness without network or keys.
3
+ //
4
+ // Speaks both OpenAI-compatible SSE (/v1/chat/completions) and Ollama NDJSON
5
+ // (/api/chat). Behavior:
6
+ // - "crea <archivo> con <texto>" → calls the `write` tool
7
+ // - anything else → echoes the prompt
8
+ //
9
+ // Usage:
10
+ // node scripts/mock-provider.mjs [--port 8790]
11
+
12
+ import http from 'node:http';
13
+
14
+ const args = process.argv.slice(2);
15
+ const portArgIndex = args.indexOf('--port');
16
+ const PORT = Number(portArgIndex >= 0 ? args[portArgIndex + 1] : process.env.MOCK_PORT ?? 8790);
17
+
18
+ function lastUserText(messages = []) {
19
+ for (let i = messages.length - 1; i >= 0; i--) {
20
+ if (messages[i].role === 'user') {
21
+ const content = messages[i].content;
22
+ return typeof content === 'string' ? content : JSON.stringify(content);
23
+ }
24
+ }
25
+ return '';
26
+ }
27
+
28
+ function decide(messages, tools) {
29
+ const user = lastUserText(messages);
30
+ const hasToolResult = messages.some((m) => m.role === 'tool');
31
+ if (hasToolResult) return { content: 'Listo. Ejecuté la herramienta.' };
32
+
33
+ const echoMatch = user.match(/eco\s+(.+)/i);
34
+ const echoTool = tools?.find((t) => /^mcp__.*__echo$/.test(t.function?.name));
35
+ if (echoMatch && echoTool) {
36
+ return { content: '', toolCall: { name: echoTool.function.name, args: { text: echoMatch[1].trim() } } };
37
+ }
38
+
39
+ const match = user.match(/cre(?:a|á|ar)\s+(\S+)\s+con\s+(.+)/i);
40
+ const canWrite = tools?.some((t) => t.function?.name === 'write');
41
+ if (match && canWrite) {
42
+ return {
43
+ content: '',
44
+ toolCall: { name: 'write', args: { path: match[1], content: match[2].trim() } },
45
+ };
46
+ }
47
+ return { content: `Echo: ${user}` };
48
+ }
49
+
50
+ function sendSse(res, { content, toolCall }) {
51
+ res.setHeader('Content-Type', 'text/event-stream');
52
+ res.setHeader('Cache-Control', 'no-cache');
53
+ res.setHeader('Connection', 'keep-alive');
54
+ if (toolCall) {
55
+ res.write(
56
+ `data: ${JSON.stringify({
57
+ choices: [
58
+ {
59
+ delta: {
60
+ tool_calls: [
61
+ {
62
+ index: 0,
63
+ id: 'call_1',
64
+ type: 'function',
65
+ function: { name: toolCall.name, arguments: JSON.stringify(toolCall.args) },
66
+ },
67
+ ],
68
+ },
69
+ },
70
+ ],
71
+ })}\n\n`
72
+ );
73
+ } else {
74
+ res.write(`data: ${JSON.stringify({ choices: [{ delta: { content } }] })}\n\n`);
75
+ }
76
+ res.write('data: [DONE]\n\n');
77
+ res.end();
78
+ }
79
+
80
+ function sendOllama(res, { content, toolCall }) {
81
+ res.setHeader('Content-Type', 'application/x-ndjson');
82
+ if (toolCall) {
83
+ res.write(
84
+ `${JSON.stringify({
85
+ message: { content: '', tool_calls: [{ function: { name: toolCall.name, arguments: toolCall.args } }] },
86
+ done: false,
87
+ })}\n`
88
+ );
89
+ } else {
90
+ res.write(`${JSON.stringify({ message: { content }, done: false })}\n`);
91
+ }
92
+ res.write(`${JSON.stringify({ done: true })}\n`);
93
+ res.end();
94
+ }
95
+
96
+ const server = http.createServer((req, res) => {
97
+ if (req.method === 'GET' && req.url.endsWith('/models')) {
98
+ res.setHeader('Content-Type', 'application/json');
99
+ res.end(
100
+ JSON.stringify({ data: [{ id: 'mock-agent', name: 'Mock Agent', architecture: { modality: 'text' } }] })
101
+ );
102
+ return;
103
+ }
104
+ if (req.method === 'GET' && req.url.endsWith('/api/tags')) {
105
+ res.setHeader('Content-Type', 'application/json');
106
+ res.end(JSON.stringify({ models: [{ name: 'mock-agent', details: { family: 'mock' } }] }));
107
+ return;
108
+ }
109
+ if (req.method === 'POST' && (req.url.endsWith('/chat/completions') || req.url.endsWith('/api/chat'))) {
110
+ let body = '';
111
+ req.on('data', (c) => (body += c));
112
+ req.on('end', () => {
113
+ let parsed = {};
114
+ try {
115
+ parsed = JSON.parse(body);
116
+ } catch {
117
+ // ignore
118
+ }
119
+ const decision = decide(parsed.messages ?? [], parsed.tools ?? []);
120
+ if (req.url.endsWith('/api/chat')) sendOllama(res, decision);
121
+ else sendSse(res, decision);
122
+ });
123
+ return;
124
+ }
125
+ res.statusCode = 404;
126
+ res.setHeader('Content-Type', 'application/json');
127
+ res.end('{}');
128
+ });
129
+
130
+ server.listen(PORT, '127.0.0.1', () => {
131
+ console.log(`Mock provider escuchando en http://127.0.0.1:${PORT}`);
132
+ console.log(` OpenAI-compatible base URL: http://127.0.0.1:${PORT}/v1`);
133
+ console.log(` Ollama base URL: http://127.0.0.1:${PORT}`);
134
+ });
@@ -0,0 +1,18 @@
1
+ {
2
+ "provider": "openrouter",
3
+ "model": "meta-llama/llama-3.3-8b-instruct:free",
4
+ "instructions": ["AGENTS.md"],
5
+ "maxRounds": 8,
6
+ "tools": [],
7
+ "permissions": {},
8
+ "mcp": [
9
+ {
10
+ "id": "echo",
11
+ "name": "Echo (demo stdio)",
12
+ "type": "stdio",
13
+ "command": "node",
14
+ "args": ["harness/scripts/mcp-echo-server.mjs"],
15
+ "enabled": true
16
+ }
17
+ ]
18
+ }
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@danieltmn/tamnora",
3
+ "version": "0.1.0",
4
+ "description": "Harness local para IAs: agente CLI-first con TUI, consola web y evaluacion. Node puro, sin dependencias.",
5
+ "type": "module",
6
+ "packageManager": "pnpm@11.25.0",
7
+ "author": "tamnora",
8
+ "license": "MIT",
9
+ "homepage": "https://github.com/tamnora/tamnora-harness#readme",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/tamnora/tamnora-harness.git"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/tamnora/tamnora-harness/issues"
16
+ },
17
+ "keywords": [
18
+ "tamnora",
19
+ "harness",
20
+ "ai",
21
+ "agent",
22
+ "cli",
23
+ "tui",
24
+ "llm",
25
+ "mcp",
26
+ "opencode",
27
+ "ollama"
28
+ ],
29
+ "bin": {
30
+ "tamnora": "harness/packages/cli/bin/tamnora.mjs",
31
+ "tamnora-server": "harness/packages/server/bin/tamnora-server.mjs"
32
+ },
33
+ "engines": {
34
+ "node": ">=22.5"
35
+ },
36
+ "files": [
37
+ "bin",
38
+ "harness/packages",
39
+ "harness/scripts",
40
+ "harness/tamnora.example.json",
41
+ "README.md",
42
+ "CHANGELOG.md",
43
+ "LICENSE",
44
+ "SECURITY.md"
45
+ ],
46
+ "scripts": {
47
+ "tamnora": "node --disable-warning=ExperimentalWarning harness/packages/cli/bin/tamnora.mjs",
48
+ "serve": "node --disable-warning=ExperimentalWarning harness/packages/server/bin/tamnora-server.mjs",
49
+ "mock": "node harness/scripts/mock-provider.mjs",
50
+ "test": "node --disable-warning=ExperimentalWarning --test \"harness/packages/*/test/*.test.js\"",
51
+ "lint": "eslint .",
52
+ "release": "node scripts/release.mjs",
53
+ "release:dry": "node scripts/release.mjs --dry-run",
54
+ "prepublishOnly": "npm test"
55
+ },
56
+ "release": {
57
+ "npmOwner": "danieltmn",
58
+ "gitRemote": "tamnora/tamnora-harness",
59
+ "gitOwner": "tamnora",
60
+ "branch": "main"
61
+ },
62
+ "publishConfig": {
63
+ "access": "public"
64
+ },
65
+ "devDependencies": {
66
+ "@eslint/js": "^9.39.4",
67
+ "eslint": "^9.39.4",
68
+ "globals": "^17.4.0"
69
+ }
70
+ }