@mariozechner/pi-coding-agent 0.13.2 → 0.14.1

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 (49) hide show
  1. package/CHANGELOG.md +21 -1
  2. package/README.md +62 -0
  3. package/dist/compaction.d.ts.map +1 -1
  4. package/dist/compaction.js +5 -2
  5. package/dist/compaction.js.map +1 -1
  6. package/dist/export-html.d.ts.map +1 -1
  7. package/dist/export-html.js +52 -25
  8. package/dist/export-html.js.map +1 -1
  9. package/dist/main.d.ts.map +1 -1
  10. package/dist/main.js +120 -9
  11. package/dist/main.js.map +1 -1
  12. package/dist/messages.d.ts +43 -0
  13. package/dist/messages.d.ts.map +1 -0
  14. package/dist/messages.js +71 -0
  15. package/dist/messages.js.map +1 -0
  16. package/dist/model-config.d.ts.map +1 -1
  17. package/dist/model-config.js +9 -0
  18. package/dist/model-config.js.map +1 -1
  19. package/dist/settings-manager.d.ts +6 -3
  20. package/dist/settings-manager.d.ts.map +1 -1
  21. package/dist/settings-manager.js +7 -0
  22. package/dist/settings-manager.js.map +1 -1
  23. package/dist/shell.d.ts +16 -0
  24. package/dist/shell.d.ts.map +1 -0
  25. package/dist/shell.js +108 -0
  26. package/dist/shell.js.map +1 -0
  27. package/dist/theme/dark.json +4 -1
  28. package/dist/theme/light.json +4 -1
  29. package/dist/theme/theme-schema.json +28 -0
  30. package/dist/theme/theme.d.ts +3 -2
  31. package/dist/theme/theme.d.ts.map +1 -1
  32. package/dist/theme/theme.js +32 -3
  33. package/dist/theme/theme.js.map +1 -1
  34. package/dist/tools/bash.d.ts.map +1 -1
  35. package/dist/tools/bash.js +3 -107
  36. package/dist/tools/bash.js.map +1 -1
  37. package/dist/tui/bash-execution.d.ts +33 -0
  38. package/dist/tui/bash-execution.d.ts.map +1 -0
  39. package/dist/tui/bash-execution.js +134 -0
  40. package/dist/tui/bash-execution.js.map +1 -0
  41. package/dist/tui/tui-renderer.d.ts +7 -1
  42. package/dist/tui/tui-renderer.d.ts.map +1 -1
  43. package/dist/tui/tui-renderer.js +225 -10
  44. package/dist/tui/tui-renderer.js.map +1 -1
  45. package/package.json +4 -4
  46. package/dist/fuzzy.test.d.ts +0 -2
  47. package/dist/fuzzy.test.d.ts.map +0 -1
  48. package/dist/fuzzy.test.js +0 -76
  49. package/dist/fuzzy.test.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mariozechner/pi-coding-agent",
3
- "version": "0.13.2",
3
+ "version": "0.14.1",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -28,9 +28,9 @@
28
28
  "prepublishOnly": "npm run clean && npm run build"
29
29
  },
30
30
  "dependencies": {
31
- "@mariozechner/pi-agent-core": "^0.13.2",
32
- "@mariozechner/pi-ai": "^0.13.2",
33
- "@mariozechner/pi-tui": "^0.13.2",
31
+ "@mariozechner/pi-agent-core": "^0.14.1",
32
+ "@mariozechner/pi-ai": "^0.14.1",
33
+ "@mariozechner/pi-tui": "^0.14.1",
34
34
  "chalk": "^5.5.0",
35
35
  "diff": "^8.0.2",
36
36
  "glob": "^11.0.3"
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=fuzzy.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"fuzzy.test.d.ts","sourceRoot":"","sources":["../src/fuzzy.test.ts"],"names":[],"mappings":"","sourcesContent":["import { describe, expect, test } from \"vitest\";\nimport { fuzzyFilter, fuzzyMatch } from \"../src/fuzzy.js\";\n\ndescribe(\"fuzzyMatch\", () => {\n\ttest(\"empty query matches everything with score 0\", () => {\n\t\tconst result = fuzzyMatch(\"\", \"anything\");\n\t\texpect(result.matches).toBe(true);\n\t\texpect(result.score).toBe(0);\n\t});\n\n\ttest(\"query longer than text does not match\", () => {\n\t\tconst result = fuzzyMatch(\"longquery\", \"short\");\n\t\texpect(result.matches).toBe(false);\n\t});\n\n\ttest(\"exact match has good score\", () => {\n\t\tconst result = fuzzyMatch(\"test\", \"test\");\n\t\texpect(result.matches).toBe(true);\n\t\texpect(result.score).toBeLessThan(0); // Should be negative due to consecutive bonuses\n\t});\n\n\ttest(\"characters must appear in order\", () => {\n\t\tconst matchInOrder = fuzzyMatch(\"abc\", \"aXbXc\");\n\t\texpect(matchInOrder.matches).toBe(true);\n\n\t\tconst matchOutOfOrder = fuzzyMatch(\"abc\", \"cba\");\n\t\texpect(matchOutOfOrder.matches).toBe(false);\n\t});\n\n\ttest(\"case insensitive matching\", () => {\n\t\tconst result = fuzzyMatch(\"ABC\", \"abc\");\n\t\texpect(result.matches).toBe(true);\n\n\t\tconst result2 = fuzzyMatch(\"abc\", \"ABC\");\n\t\texpect(result2.matches).toBe(true);\n\t});\n\n\ttest(\"consecutive matches score better than scattered matches\", () => {\n\t\tconst consecutive = fuzzyMatch(\"foo\", \"foobar\");\n\t\tconst scattered = fuzzyMatch(\"foo\", \"f_o_o_bar\");\n\n\t\texpect(consecutive.matches).toBe(true);\n\t\texpect(scattered.matches).toBe(true);\n\t\texpect(consecutive.score).toBeLessThan(scattered.score);\n\t});\n\n\ttest(\"word boundary matches score better\", () => {\n\t\tconst atBoundary = fuzzyMatch(\"fb\", \"foo-bar\");\n\t\tconst notAtBoundary = fuzzyMatch(\"fb\", \"afbx\");\n\n\t\texpect(atBoundary.matches).toBe(true);\n\t\texpect(notAtBoundary.matches).toBe(true);\n\t\texpect(atBoundary.score).toBeLessThan(notAtBoundary.score);\n\t});\n});\n\ndescribe(\"fuzzyFilter\", () => {\n\ttest(\"empty query returns all items unchanged\", () => {\n\t\tconst items = [\"apple\", \"banana\", \"cherry\"];\n\t\tconst result = fuzzyFilter(items, \"\", (x) => x);\n\t\texpect(result).toEqual(items);\n\t});\n\n\ttest(\"filters out non-matching items\", () => {\n\t\tconst items = [\"apple\", \"banana\", \"cherry\"];\n\t\tconst result = fuzzyFilter(items, \"an\", (x) => x);\n\t\texpect(result).toContain(\"banana\");\n\t\texpect(result).not.toContain(\"apple\");\n\t\texpect(result).not.toContain(\"cherry\");\n\t});\n\n\ttest(\"sorts results by match quality\", () => {\n\t\tconst items = [\"a_p_p\", \"app\", \"application\"];\n\t\tconst result = fuzzyFilter(items, \"app\", (x) => x);\n\n\t\t// \"app\" should be first (exact consecutive match at start)\n\t\texpect(result[0]).toBe(\"app\");\n\t});\n\n\ttest(\"works with custom getText function\", () => {\n\t\tconst items = [\n\t\t\t{ name: \"foo\", id: 1 },\n\t\t\t{ name: \"bar\", id: 2 },\n\t\t\t{ name: \"foobar\", id: 3 },\n\t\t];\n\t\tconst result = fuzzyFilter(items, \"foo\", (item) => item.name);\n\n\t\texpect(result.length).toBe(2);\n\t\texpect(result.map((r) => r.name)).toContain(\"foo\");\n\t\texpect(result.map((r) => r.name)).toContain(\"foobar\");\n\t});\n});\n"]}
@@ -1,76 +0,0 @@
1
- import { describe, expect, test } from "vitest";
2
- import { fuzzyFilter, fuzzyMatch } from "../src/fuzzy.js";
3
- describe("fuzzyMatch", () => {
4
- test("empty query matches everything with score 0", () => {
5
- const result = fuzzyMatch("", "anything");
6
- expect(result.matches).toBe(true);
7
- expect(result.score).toBe(0);
8
- });
9
- test("query longer than text does not match", () => {
10
- const result = fuzzyMatch("longquery", "short");
11
- expect(result.matches).toBe(false);
12
- });
13
- test("exact match has good score", () => {
14
- const result = fuzzyMatch("test", "test");
15
- expect(result.matches).toBe(true);
16
- expect(result.score).toBeLessThan(0); // Should be negative due to consecutive bonuses
17
- });
18
- test("characters must appear in order", () => {
19
- const matchInOrder = fuzzyMatch("abc", "aXbXc");
20
- expect(matchInOrder.matches).toBe(true);
21
- const matchOutOfOrder = fuzzyMatch("abc", "cba");
22
- expect(matchOutOfOrder.matches).toBe(false);
23
- });
24
- test("case insensitive matching", () => {
25
- const result = fuzzyMatch("ABC", "abc");
26
- expect(result.matches).toBe(true);
27
- const result2 = fuzzyMatch("abc", "ABC");
28
- expect(result2.matches).toBe(true);
29
- });
30
- test("consecutive matches score better than scattered matches", () => {
31
- const consecutive = fuzzyMatch("foo", "foobar");
32
- const scattered = fuzzyMatch("foo", "f_o_o_bar");
33
- expect(consecutive.matches).toBe(true);
34
- expect(scattered.matches).toBe(true);
35
- expect(consecutive.score).toBeLessThan(scattered.score);
36
- });
37
- test("word boundary matches score better", () => {
38
- const atBoundary = fuzzyMatch("fb", "foo-bar");
39
- const notAtBoundary = fuzzyMatch("fb", "afbx");
40
- expect(atBoundary.matches).toBe(true);
41
- expect(notAtBoundary.matches).toBe(true);
42
- expect(atBoundary.score).toBeLessThan(notAtBoundary.score);
43
- });
44
- });
45
- describe("fuzzyFilter", () => {
46
- test("empty query returns all items unchanged", () => {
47
- const items = ["apple", "banana", "cherry"];
48
- const result = fuzzyFilter(items, "", (x) => x);
49
- expect(result).toEqual(items);
50
- });
51
- test("filters out non-matching items", () => {
52
- const items = ["apple", "banana", "cherry"];
53
- const result = fuzzyFilter(items, "an", (x) => x);
54
- expect(result).toContain("banana");
55
- expect(result).not.toContain("apple");
56
- expect(result).not.toContain("cherry");
57
- });
58
- test("sorts results by match quality", () => {
59
- const items = ["a_p_p", "app", "application"];
60
- const result = fuzzyFilter(items, "app", (x) => x);
61
- // "app" should be first (exact consecutive match at start)
62
- expect(result[0]).toBe("app");
63
- });
64
- test("works with custom getText function", () => {
65
- const items = [
66
- { name: "foo", id: 1 },
67
- { name: "bar", id: 2 },
68
- { name: "foobar", id: 3 },
69
- ];
70
- const result = fuzzyFilter(items, "foo", (item) => item.name);
71
- expect(result.length).toBe(2);
72
- expect(result.map((r) => r.name)).toContain("foo");
73
- expect(result.map((r) => r.name)).toContain("foobar");
74
- });
75
- });
76
- //# sourceMappingURL=fuzzy.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"fuzzy.test.js","sourceRoot":"","sources":["../src/fuzzy.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, expect, test } from \"vitest\";\nimport { fuzzyFilter, fuzzyMatch } from \"../src/fuzzy.js\";\n\ndescribe(\"fuzzyMatch\", () => {\n\ttest(\"empty query matches everything with score 0\", () => {\n\t\tconst result = fuzzyMatch(\"\", \"anything\");\n\t\texpect(result.matches).toBe(true);\n\t\texpect(result.score).toBe(0);\n\t});\n\n\ttest(\"query longer than text does not match\", () => {\n\t\tconst result = fuzzyMatch(\"longquery\", \"short\");\n\t\texpect(result.matches).toBe(false);\n\t});\n\n\ttest(\"exact match has good score\", () => {\n\t\tconst result = fuzzyMatch(\"test\", \"test\");\n\t\texpect(result.matches).toBe(true);\n\t\texpect(result.score).toBeLessThan(0); // Should be negative due to consecutive bonuses\n\t});\n\n\ttest(\"characters must appear in order\", () => {\n\t\tconst matchInOrder = fuzzyMatch(\"abc\", \"aXbXc\");\n\t\texpect(matchInOrder.matches).toBe(true);\n\n\t\tconst matchOutOfOrder = fuzzyMatch(\"abc\", \"cba\");\n\t\texpect(matchOutOfOrder.matches).toBe(false);\n\t});\n\n\ttest(\"case insensitive matching\", () => {\n\t\tconst result = fuzzyMatch(\"ABC\", \"abc\");\n\t\texpect(result.matches).toBe(true);\n\n\t\tconst result2 = fuzzyMatch(\"abc\", \"ABC\");\n\t\texpect(result2.matches).toBe(true);\n\t});\n\n\ttest(\"consecutive matches score better than scattered matches\", () => {\n\t\tconst consecutive = fuzzyMatch(\"foo\", \"foobar\");\n\t\tconst scattered = fuzzyMatch(\"foo\", \"f_o_o_bar\");\n\n\t\texpect(consecutive.matches).toBe(true);\n\t\texpect(scattered.matches).toBe(true);\n\t\texpect(consecutive.score).toBeLessThan(scattered.score);\n\t});\n\n\ttest(\"word boundary matches score better\", () => {\n\t\tconst atBoundary = fuzzyMatch(\"fb\", \"foo-bar\");\n\t\tconst notAtBoundary = fuzzyMatch(\"fb\", \"afbx\");\n\n\t\texpect(atBoundary.matches).toBe(true);\n\t\texpect(notAtBoundary.matches).toBe(true);\n\t\texpect(atBoundary.score).toBeLessThan(notAtBoundary.score);\n\t});\n});\n\ndescribe(\"fuzzyFilter\", () => {\n\ttest(\"empty query returns all items unchanged\", () => {\n\t\tconst items = [\"apple\", \"banana\", \"cherry\"];\n\t\tconst result = fuzzyFilter(items, \"\", (x) => x);\n\t\texpect(result).toEqual(items);\n\t});\n\n\ttest(\"filters out non-matching items\", () => {\n\t\tconst items = [\"apple\", \"banana\", \"cherry\"];\n\t\tconst result = fuzzyFilter(items, \"an\", (x) => x);\n\t\texpect(result).toContain(\"banana\");\n\t\texpect(result).not.toContain(\"apple\");\n\t\texpect(result).not.toContain(\"cherry\");\n\t});\n\n\ttest(\"sorts results by match quality\", () => {\n\t\tconst items = [\"a_p_p\", \"app\", \"application\"];\n\t\tconst result = fuzzyFilter(items, \"app\", (x) => x);\n\n\t\t// \"app\" should be first (exact consecutive match at start)\n\t\texpect(result[0]).toBe(\"app\");\n\t});\n\n\ttest(\"works with custom getText function\", () => {\n\t\tconst items = [\n\t\t\t{ name: \"foo\", id: 1 },\n\t\t\t{ name: \"bar\", id: 2 },\n\t\t\t{ name: \"foobar\", id: 3 },\n\t\t];\n\t\tconst result = fuzzyFilter(items, \"foo\", (item) => item.name);\n\n\t\texpect(result.length).toBe(2);\n\t\texpect(result.map((r) => r.name)).toContain(\"foo\");\n\t\texpect(result.map((r) => r.name)).toContain(\"foobar\");\n\t});\n});\n"]}