@danieltmn/tamnora 0.7.0 → 0.8.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/CHANGELOG.md +38 -0
- package/README.md +9 -0
- package/SECURITY.md +11 -6
- package/harness/packages/cli/src/commands/run.js +1 -1
- package/harness/packages/cli/src/commands/tui.js +1 -1
- package/harness/packages/core/src/agent/runAgent.js +3 -0
- package/harness/packages/core/src/git/index.js +177 -0
- package/harness/packages/core/src/index.js +2 -0
- package/harness/packages/core/src/procs/kill.js +95 -0
- package/harness/packages/core/test/procs.test.js +50 -0
- package/harness/packages/server/public/app.js +509 -75
- package/harness/packages/server/public/index.html +45 -1
- package/harness/packages/server/public/landing/index.html +5 -5
- package/harness/packages/server/public/markdown.js +43 -0
- package/harness/packages/server/public/styles.css +148 -22
- package/harness/packages/server/src/routes/chat.js +3 -0
- package/harness/packages/server/src/routes/git.js +43 -0
- package/harness/packages/server/src/routes/procs.js +14 -2
- package/harness/packages/server/src/routes/shell.js +23 -0
- package/harness/packages/server/src/server.js +4 -0
- package/harness/packages/server/test/markdown.test.js +33 -0
- package/harness/packages/server/test/server.test.js +107 -0
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { test } from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import http from 'node:http';
|
|
4
|
+
import { execFileSync } from 'node:child_process';
|
|
4
5
|
import { mkdtemp, mkdir, writeFile, readFile, stat } from 'node:fs/promises';
|
|
5
6
|
import { tmpdir } from 'node:os';
|
|
6
7
|
import path from 'node:path';
|
|
@@ -30,6 +31,21 @@ async function startServer(overrides = {}) {
|
|
|
30
31
|
return { instance, base: info.url };
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
const HAS_GIT = (() => {
|
|
35
|
+
try {
|
|
36
|
+
execFileSync('git', ['--version'], { stdio: 'ignore' });
|
|
37
|
+
return true;
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
})();
|
|
42
|
+
|
|
43
|
+
function gitInit(root) {
|
|
44
|
+
execFileSync('git', ['init', '-q'], { cwd: root });
|
|
45
|
+
execFileSync('git', ['config', 'user.email', 'test@tamnora.dev'], { cwd: root });
|
|
46
|
+
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: root });
|
|
47
|
+
}
|
|
48
|
+
|
|
33
49
|
test('serves health, providers and the console', async () => {
|
|
34
50
|
const { instance, base } = await startServer();
|
|
35
51
|
try {
|
|
@@ -49,6 +65,7 @@ test('serves health, providers and the console', async () => {
|
|
|
49
65
|
'id="settings-controls-slot"',
|
|
50
66
|
'id="slash-menu"',
|
|
51
67
|
'id="statusbar"',
|
|
68
|
+
'id="sb-activity"',
|
|
52
69
|
]) {
|
|
53
70
|
assert.ok(html.includes(hook), `falta ${hook}`);
|
|
54
71
|
}
|
|
@@ -569,6 +586,46 @@ test('detects, runs and stops dev processes', async () => {
|
|
|
569
586
|
}
|
|
570
587
|
});
|
|
571
588
|
|
|
589
|
+
test('runs a console shell command and kills a port', async () => {
|
|
590
|
+
const cwd = await mkdtemp(path.join(tmpdir(), 'tamnora-shell-'));
|
|
591
|
+
const { instance, base } = await startServer({ cwd });
|
|
592
|
+
const json = { 'Content-Type': 'application/json' };
|
|
593
|
+
try {
|
|
594
|
+
const shell = await fetch(`${base}/api/shell`, {
|
|
595
|
+
method: 'POST',
|
|
596
|
+
headers: json,
|
|
597
|
+
body: JSON.stringify({ command: 'echo hola-shell' }),
|
|
598
|
+
});
|
|
599
|
+
assert.equal(shell.status, 200);
|
|
600
|
+
const out = await shell.json();
|
|
601
|
+
assert.match(out.output, /hola-shell/);
|
|
602
|
+
|
|
603
|
+
const missing = await fetch(`${base}/api/shell`, {
|
|
604
|
+
method: 'POST',
|
|
605
|
+
headers: json,
|
|
606
|
+
body: JSON.stringify({ command: ' ' }),
|
|
607
|
+
});
|
|
608
|
+
assert.equal(missing.status, 400);
|
|
609
|
+
|
|
610
|
+
const badPort = await fetch(`${base}/api/procs/kill`, {
|
|
611
|
+
method: 'POST',
|
|
612
|
+
headers: json,
|
|
613
|
+
body: JSON.stringify({ port: 0 }),
|
|
614
|
+
});
|
|
615
|
+
assert.equal(badPort.status, 400);
|
|
616
|
+
|
|
617
|
+
const none = await fetch(`${base}/api/procs/kill`, {
|
|
618
|
+
method: 'POST',
|
|
619
|
+
headers: json,
|
|
620
|
+
body: JSON.stringify({ port: 47555 }),
|
|
621
|
+
});
|
|
622
|
+
assert.equal(none.status, 200);
|
|
623
|
+
assert.deepEqual((await none.json()).pids, []);
|
|
624
|
+
} finally {
|
|
625
|
+
await instance.close();
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
|
|
572
629
|
test('the agent can install a skill from chat (with approval)', async () => {
|
|
573
630
|
const calls = { n: 0 };
|
|
574
631
|
const toolProvider = {
|
|
@@ -774,6 +831,56 @@ test('lists and previews workspace files, confining to the root', async () => {
|
|
|
774
831
|
}
|
|
775
832
|
});
|
|
776
833
|
|
|
834
|
+
test('reports git status and per-file diffs', { skip: !HAS_GIT }, async () => {
|
|
835
|
+
const root = await mkdtemp(path.join(tmpdir(), 'tamnora-git-'));
|
|
836
|
+
gitInit(root);
|
|
837
|
+
await writeFile(path.join(root, 'app.js'), 'const a = 1;\n');
|
|
838
|
+
execFileSync('git', ['add', 'app.js'], { cwd: root });
|
|
839
|
+
execFileSync('git', ['commit', '-q', '-m', 'init'], { cwd: root });
|
|
840
|
+
await writeFile(path.join(root, 'app.js'), 'const a = 2;\n');
|
|
841
|
+
await writeFile(path.join(root, 'nuevo.txt'), 'hola\n');
|
|
842
|
+
|
|
843
|
+
const { instance, base } = await startServer();
|
|
844
|
+
try {
|
|
845
|
+
const status = await (
|
|
846
|
+
await fetch(`${base}/api/git/status?${new URLSearchParams({ cwd: root })}`)
|
|
847
|
+
).json();
|
|
848
|
+
assert.equal(status.isRepo, true);
|
|
849
|
+
assert.ok(status.branch);
|
|
850
|
+
assert.deepEqual(
|
|
851
|
+
status.entries.map((e) => e.path).sort(),
|
|
852
|
+
['app.js', 'nuevo.txt']
|
|
853
|
+
);
|
|
854
|
+
assert.equal(status.entries.find((e) => e.path === 'app.js').status, 'modified');
|
|
855
|
+
assert.equal(status.entries.find((e) => e.path === 'nuevo.txt').status, 'untracked');
|
|
856
|
+
|
|
857
|
+
const diff = await (
|
|
858
|
+
await fetch(`${base}/api/git/diff?${new URLSearchParams({ cwd: root, path: 'app.js' })}`)
|
|
859
|
+
).json();
|
|
860
|
+
assert.match(diff.diff, /-const a = 1;/);
|
|
861
|
+
assert.match(diff.diff, /\+const a = 2;/);
|
|
862
|
+
|
|
863
|
+
const untracked = await (
|
|
864
|
+
await fetch(`${base}/api/git/diff?${new URLSearchParams({ cwd: root, path: 'nuevo.txt' })}`)
|
|
865
|
+
).json();
|
|
866
|
+
assert.equal(untracked.untracked, true);
|
|
867
|
+
assert.match(untracked.diff, /\+hola/);
|
|
868
|
+
|
|
869
|
+
const outside = await fetch(
|
|
870
|
+
`${base}/api/git/diff?${new URLSearchParams({ cwd: root, path: '../fuera' })}`
|
|
871
|
+
);
|
|
872
|
+
assert.equal(outside.status, 400);
|
|
873
|
+
|
|
874
|
+
const plain = await mkdtemp(path.join(tmpdir(), 'tamnora-nogit-'));
|
|
875
|
+
const none = await (
|
|
876
|
+
await fetch(`${base}/api/git/status?${new URLSearchParams({ cwd: plain })}`)
|
|
877
|
+
).json();
|
|
878
|
+
assert.equal(none.isRepo, false);
|
|
879
|
+
} finally {
|
|
880
|
+
await instance.close();
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
|
|
777
884
|
test('previews an HTML file through a static server', async () => {
|
|
778
885
|
const root = await mkdtemp(path.join(tmpdir(), 'tamnora-html-'));
|
|
779
886
|
await writeFile(path.join(root, 'index.html'), '<h1>hola</h1>');
|
package/package.json
CHANGED