@agnishc/edb-compact-tools 0.10.8 → 0.10.9
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 +2 -0
- package/package.json +1 -1
- package/src/constants.test.ts +22 -0
- package/src/text.test.ts +124 -0
- package/src/tool-meta.test.ts +182 -0
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import * as constants from "./constants.js";
|
|
3
|
+
|
|
4
|
+
// ── constants ──────────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
describe("ANSI constants", () => {
|
|
7
|
+
it("exports ANSI_PURPLE and ANSI_RESET", () => {
|
|
8
|
+
expect(constants.ANSI_PURPLE).toMatch(/^\x1b\[38;5;\d+m$/);
|
|
9
|
+
expect(constants.ANSI_RESET).toBe("\x1b[0m");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("exports emoji arrays", () => {
|
|
13
|
+
expect(constants.USER_MESSAGE_EMOJIS.length).toBeGreaterThan(0);
|
|
14
|
+
expect(constants.ASSISTANT_MESSAGE_EMOJIS.length).toBeGreaterThan(0);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("exports OSC133 markers", () => {
|
|
18
|
+
expect(constants.OSC133_ZONE_START).toBe("\x1b]133;A\x07");
|
|
19
|
+
expect(constants.OSC133_ZONE_END).toBe("\x1b]133;B\x07");
|
|
20
|
+
expect(constants.OSC133_ZONE_FINAL).toBe("\x1b]133;C\x07");
|
|
21
|
+
});
|
|
22
|
+
});
|
package/src/text.test.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { clip, lineCount, oneLine, outputWasTruncated, previewLines, textContent } from "./text.js";
|
|
3
|
+
|
|
4
|
+
describe("oneLine", () => {
|
|
5
|
+
it("collapses whitespace", () => {
|
|
6
|
+
expect(oneLine("hello world")).toBe("hello world");
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("trims edges", () => {
|
|
10
|
+
expect(oneLine(" hello ")).toBe("hello");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("handles null and undefined as empty string", () => {
|
|
14
|
+
expect(oneLine(null)).toBe("");
|
|
15
|
+
expect(oneLine(undefined)).toBe("");
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("clip", () => {
|
|
20
|
+
it("passes through short text", () => {
|
|
21
|
+
expect(clip("hello")).toBe("hello");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("truncates long text with ellipsis", () => {
|
|
25
|
+
expect(clip("a".repeat(150))).toBe(`${"a".repeat(119)}…`);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("respects custom max", () => {
|
|
29
|
+
expect(clip("hello world", 5)).toBe("hell…");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("handles empty string", () => {
|
|
33
|
+
expect(clip("")).toBe("");
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("lineCount", () => {
|
|
38
|
+
it("counts lines", () => {
|
|
39
|
+
expect(lineCount("a\nb\nc")).toBe(3);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("handles CRLF", () => {
|
|
43
|
+
expect(lineCount("a\r\nb\r\nc")).toBe(3);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("returns 0 for empty", () => {
|
|
47
|
+
expect(lineCount("")).toBe(0);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("textContent", () => {
|
|
52
|
+
it("extracts text from content array", () => {
|
|
53
|
+
const result = {
|
|
54
|
+
content: [
|
|
55
|
+
{ type: "text", text: "hello" },
|
|
56
|
+
{ type: "text", text: "world" },
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
expect(textContent(result)).toBe("hello\nworld");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("filters non-text items", () => {
|
|
63
|
+
const result = {
|
|
64
|
+
content: [
|
|
65
|
+
{ type: "text", text: "hello" },
|
|
66
|
+
{ type: "image", text: "world" },
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
expect(textContent(result)).toBe("hello");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("handles missing content", () => {
|
|
73
|
+
expect(textContent({})).toBe("");
|
|
74
|
+
expect(textContent(null)).toBe("");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("joins multiple text items", () => {
|
|
78
|
+
const result = {
|
|
79
|
+
content: [
|
|
80
|
+
{ type: "text", text: "line1" },
|
|
81
|
+
{ type: "text", text: "line2" },
|
|
82
|
+
],
|
|
83
|
+
};
|
|
84
|
+
expect(textContent(result)).toBe("line1\nline2");
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe("outputWasTruncated", () => {
|
|
89
|
+
it("detects truncation markers", () => {
|
|
90
|
+
expect(outputWasTruncated("Output truncated")).toBe(true);
|
|
91
|
+
expect(outputWasTruncated("Full output saved to: /tmp/out.txt")).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("is case-insensitive", () => {
|
|
95
|
+
expect(outputWasTruncated("TRUNCATED")).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("returns false for clean output", () => {
|
|
99
|
+
expect(outputWasTruncated("hello world")).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("previewLines", () => {
|
|
104
|
+
it("takes head lines by default", () => {
|
|
105
|
+
const lines = "a\nb\nc\nd\ne".split("\n");
|
|
106
|
+
const text = lines.join("\n");
|
|
107
|
+
expect(previewLines(text, "head")).toEqual(["a", "b", "c", "d", "e"]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("takes tail lines when mode is tail", () => {
|
|
111
|
+
const text = "a\nb\nc\nd\ne";
|
|
112
|
+
expect(previewLines(text, "tail", 3)).toEqual(["c", "d", "e"]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("respects limit", () => {
|
|
116
|
+
const text = "a\nb\nc\nd\ne";
|
|
117
|
+
expect(previewLines(text, "head", 3)).toEqual(["a", "b", "c"]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("clips long lines", () => {
|
|
121
|
+
const text = "a".repeat(200);
|
|
122
|
+
expect(previewLines(text, "head", 1)[0]!.length).toBeLessThanOrEqual(121);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { callLabel, isSkillPath, purple, summaryFor, toolColor, toolIcon } from "./tool-meta.js";
|
|
3
|
+
|
|
4
|
+
describe("isSkillPath", () => {
|
|
5
|
+
it("detects .agents/skills paths", () => {
|
|
6
|
+
expect(isSkillPath(".agents/skills/my-skill/SKILL.md")).toBe(true);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("detects .pi/agent/skills paths", () => {
|
|
10
|
+
expect(isSkillPath(".pi/agent/skills/my-skill/SKILL.md")).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("returns false for regular paths", () => {
|
|
14
|
+
expect(isSkillPath("/Users/foo/project/src/index.ts")).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("returns false for non-string inputs", () => {
|
|
18
|
+
expect(isSkillPath(null)).toBe(false);
|
|
19
|
+
expect(isSkillPath(undefined)).toBe(false);
|
|
20
|
+
expect(isSkillPath(123)).toBe(false);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("purple", () => {
|
|
25
|
+
it("wraps text with ANSI purple codes", () => {
|
|
26
|
+
const result = purple("hello");
|
|
27
|
+
expect(result).toContain("hello");
|
|
28
|
+
expect(result).toContain("\x1b[38;5;141m");
|
|
29
|
+
expect(result).toContain("\x1b[0m");
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("toolColor", () => {
|
|
34
|
+
it("returns bashMode for bash", () => {
|
|
35
|
+
expect(toolColor("bash")).toBe("bashMode");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("returns toolTitle for read (non-skill)", () => {
|
|
39
|
+
expect(toolColor("read", { path: "/tmp/foo.ts" })).toBe("toolTitle");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("returns purple for read (skill file)", () => {
|
|
43
|
+
expect(toolColor("read", { path: ".agents/skills/my-skill/SKILL.md" })).toBe("purple");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("returns success for grep", () => {
|
|
47
|
+
expect(toolColor("grep")).toBe("success");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("returns accent for find", () => {
|
|
51
|
+
expect(toolColor("find")).toBe("accent");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("returns warning for ls", () => {
|
|
55
|
+
expect(toolColor("ls")).toBe("warning");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("returns toolDiffAdded for edit", () => {
|
|
59
|
+
expect(toolColor("edit")).toBe("toolDiffAdded");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("returns accent for write", () => {
|
|
63
|
+
expect(toolColor("write")).toBe("accent");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("returns accent for unknown tools", () => {
|
|
67
|
+
expect(toolColor("unknown_tool")).toBe("accent");
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("toolIcon", () => {
|
|
72
|
+
it("returns correct icon per tool", () => {
|
|
73
|
+
expect(toolIcon("bash")).toBe("⚙️");
|
|
74
|
+
expect(toolIcon("read")).toBe("📖");
|
|
75
|
+
expect(toolIcon("grep")).toBe("🔎");
|
|
76
|
+
expect(toolIcon("find")).toBe("🧭");
|
|
77
|
+
expect(toolIcon("ls")).toBe("📁");
|
|
78
|
+
expect(toolIcon("edit")).toBe("✏️");
|
|
79
|
+
expect(toolIcon("write")).toBe("📝");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("returns default icon for unknown tools", () => {
|
|
83
|
+
expect(toolIcon("unknown")).toBe("🧩");
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("callLabel", () => {
|
|
88
|
+
it("returns clipped command for bash", () => {
|
|
89
|
+
expect(callLabel("bash", { command: "ls -la" })).toBe("ls -la");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("truncates long bash commands", () => {
|
|
93
|
+
const long = `echo ${"x".repeat(150)}`;
|
|
94
|
+
const result = callLabel("bash", { command: long });
|
|
95
|
+
expect(result.length).toBeLessThanOrEqual(143);
|
|
96
|
+
expect(result.endsWith("…")).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("returns clipped path for read", () => {
|
|
100
|
+
expect(callLabel("read", { path: "/tmp/foo.ts" })).toBe("/tmp/foo.ts");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("formats grep with path", () => {
|
|
104
|
+
expect(callLabel("grep", { pattern: "TODO", path: "/src" })).toBe("TODO in /src");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("returns default for grep without path", () => {
|
|
108
|
+
expect(callLabel("grep", { pattern: "TODO" })).toBe("TODO in .");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("formats find with path", () => {
|
|
112
|
+
expect(callLabel("find", { path: "/tmp" })).toBe("/tmp");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("formats edit with replacement count", () => {
|
|
116
|
+
expect(callLabel("edit", { path: "foo.ts", edits: [{}, {}] })).toBe("foo.ts · 2 replacements");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("formats write with bytes", () => {
|
|
120
|
+
expect(callLabel("write", { path: "foo.ts", content: "hello world" })).toBe("foo.ts · 11 bytes");
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("summaryFor", () => {
|
|
125
|
+
it("formats bash exit code", () => {
|
|
126
|
+
const result = { content: [{ type: "text", text: "hello\nexit 0" }] };
|
|
127
|
+
expect(summaryFor("bash", result)).toMatch(/exit 0/);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("shows truncated marker when output was truncated", () => {
|
|
131
|
+
const result = { content: [{ type: "text", text: "hello\nOutput truncated" }] };
|
|
132
|
+
expect(summaryFor("bash", result)).toContain("truncated");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("formats read line count", () => {
|
|
136
|
+
const result = { content: [{ type: "text", text: "line1\nline2\nline3" }] };
|
|
137
|
+
expect(summaryFor("read", result)).toBe("3 lines");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("formats single line as singular", () => {
|
|
141
|
+
const result = { content: [{ type: "text", text: "line1" }] };
|
|
142
|
+
expect(summaryFor("read", result)).toBe("1 line");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("formats ls item count", () => {
|
|
146
|
+
const result = { content: [{ type: "text", text: "file1\nfile2" }] };
|
|
147
|
+
expect(summaryFor("ls", result)).toBe("2 items");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("formats edit diff stats", () => {
|
|
151
|
+
const result = {
|
|
152
|
+
content: [{ type: "text", text: "done" }],
|
|
153
|
+
details: { diff: "@@ -1,3 +1,4 @@\n+added1\n+added2\n-removed1\n-removed2" },
|
|
154
|
+
};
|
|
155
|
+
expect(summaryFor("edit", result)).toBe("+2 -2");
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("falls back to line count for edit without diff", () => {
|
|
159
|
+
const result = { content: [{ type: "text", text: "a\nb" }] };
|
|
160
|
+
expect(summaryFor("edit", result)).toBe("2 lines");
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("formats write line count", () => {
|
|
164
|
+
const result = { content: [{ type: "text", text: "line1\nline2" }] };
|
|
165
|
+
expect(summaryFor("write", result)).toBe("2 lines");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("formats grep result count", () => {
|
|
169
|
+
const result = { content: [{ type: "text", text: "match1\nmatch2\nmatch3" }] };
|
|
170
|
+
expect(summaryFor("grep", result)).toBe("3 results");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("formats find result count", () => {
|
|
174
|
+
const result = { content: [{ type: "text", text: "file1\nfile2\nfile3\nfile4" }] };
|
|
175
|
+
expect(summaryFor("find", result)).toBe("4 results");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("returns '1 result' for single result", () => {
|
|
179
|
+
const result = { content: [{ type: "text", text: "only" }] };
|
|
180
|
+
expect(summaryFor("grep", result)).toBe("1 result");
|
|
181
|
+
});
|
|
182
|
+
});
|