@pentoshi/clai 0.6.0 → 0.7.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 (116) hide show
  1. package/README.md +9 -17
  2. package/dist/agent/context-manager.d.ts +27 -0
  3. package/dist/agent/context-manager.js +75 -0
  4. package/dist/agent/context-manager.js.map +1 -0
  5. package/dist/agent/runner.d.ts +21 -1
  6. package/dist/agent/runner.js +176 -73
  7. package/dist/agent/runner.js.map +1 -1
  8. package/dist/commands/doctor.js +20 -2
  9. package/dist/commands/doctor.js.map +1 -1
  10. package/dist/commands/update.js +11 -2
  11. package/dist/commands/update.js.map +1 -1
  12. package/dist/index.js +156 -5
  13. package/dist/index.js.map +1 -1
  14. package/dist/llm/anthropic.js +29 -38
  15. package/dist/llm/anthropic.js.map +1 -1
  16. package/dist/llm/gemini.js +31 -40
  17. package/dist/llm/gemini.js.map +1 -1
  18. package/dist/llm/http.d.ts +21 -0
  19. package/dist/llm/http.js +140 -1
  20. package/dist/llm/http.js.map +1 -1
  21. package/dist/llm/ollama.js +18 -27
  22. package/dist/llm/ollama.js.map +1 -1
  23. package/dist/llm/router.d.ts +7 -0
  24. package/dist/llm/router.js +14 -23
  25. package/dist/llm/router.js.map +1 -1
  26. package/dist/modes/agent.d.ts +4 -2
  27. package/dist/modes/agent.js +2 -2
  28. package/dist/modes/agent.js.map +1 -1
  29. package/dist/modes/ask.js +3 -4
  30. package/dist/modes/ask.js.map +1 -1
  31. package/dist/os/pkgmgr.d.ts +7 -1
  32. package/dist/os/pkgmgr.js +97 -18
  33. package/dist/os/pkgmgr.js.map +1 -1
  34. package/dist/prompts/index.d.ts +7 -0
  35. package/dist/prompts/index.js +12 -4
  36. package/dist/prompts/index.js.map +1 -1
  37. package/dist/repl.d.ts +1 -0
  38. package/dist/repl.js +283 -43
  39. package/dist/repl.js.map +1 -1
  40. package/dist/safety/classifier.d.ts +5 -1
  41. package/dist/safety/classifier.js +244 -88
  42. package/dist/safety/classifier.js.map +1 -1
  43. package/dist/safety/patterns.d.ts +48 -1
  44. package/dist/safety/patterns.js +140 -7
  45. package/dist/safety/patterns.js.map +1 -1
  46. package/dist/store/config.d.ts +21 -3
  47. package/dist/store/config.js +28 -9
  48. package/dist/store/config.js.map +1 -1
  49. package/dist/store/history.d.ts +9 -0
  50. package/dist/store/history.js +58 -1
  51. package/dist/store/history.js.map +1 -1
  52. package/dist/store/keys.d.ts +2 -1
  53. package/dist/store/keys.js +7 -3
  54. package/dist/store/keys.js.map +1 -1
  55. package/dist/store/logs.d.ts +7 -0
  56. package/dist/store/logs.js +39 -1
  57. package/dist/store/logs.js.map +1 -1
  58. package/dist/store/project.d.ts +1 -0
  59. package/dist/store/project.js +34 -9
  60. package/dist/store/project.js.map +1 -1
  61. package/dist/store/scope.d.ts +29 -0
  62. package/dist/store/scope.js +113 -0
  63. package/dist/store/scope.js.map +1 -0
  64. package/dist/tools/fs.d.ts +6 -2
  65. package/dist/tools/fs.js +99 -87
  66. package/dist/tools/fs.js.map +1 -1
  67. package/dist/tools/http.d.ts +5 -3
  68. package/dist/tools/http.js +170 -31
  69. package/dist/tools/http.js.map +1 -1
  70. package/dist/tools/policies/output-policy.d.ts +13 -0
  71. package/dist/tools/policies/output-policy.js +56 -0
  72. package/dist/tools/policies/output-policy.js.map +1 -0
  73. package/dist/tools/reducers/ffuf.d.ts +6 -0
  74. package/dist/tools/reducers/ffuf.js +74 -0
  75. package/dist/tools/reducers/ffuf.js.map +1 -0
  76. package/dist/tools/reducers/generic.d.ts +2 -0
  77. package/dist/tools/reducers/generic.js +60 -0
  78. package/dist/tools/reducers/generic.js.map +1 -0
  79. package/dist/tools/reducers/gobuster.d.ts +2 -0
  80. package/dist/tools/reducers/gobuster.js +36 -0
  81. package/dist/tools/reducers/gobuster.js.map +1 -0
  82. package/dist/tools/reducers/httpx.d.ts +2 -0
  83. package/dist/tools/reducers/httpx.js +38 -0
  84. package/dist/tools/reducers/httpx.js.map +1 -0
  85. package/dist/tools/reducers/nmap.d.ts +7 -0
  86. package/dist/tools/reducers/nmap.js +82 -0
  87. package/dist/tools/reducers/nmap.js.map +1 -0
  88. package/dist/tools/reducers/nuclei.d.ts +2 -0
  89. package/dist/tools/reducers/nuclei.js +51 -0
  90. package/dist/tools/reducers/nuclei.js.map +1 -0
  91. package/dist/tools/reducers/sqlmap.d.ts +2 -0
  92. package/dist/tools/reducers/sqlmap.js +39 -0
  93. package/dist/tools/reducers/sqlmap.js.map +1 -0
  94. package/dist/tools/reducers/subdomains.d.ts +6 -0
  95. package/dist/tools/reducers/subdomains.js +31 -0
  96. package/dist/tools/reducers/subdomains.js.map +1 -0
  97. package/dist/tools/reducers/types.d.ts +14 -0
  98. package/dist/tools/reducers/types.js +2 -0
  99. package/dist/tools/reducers/types.js.map +1 -0
  100. package/dist/tools/registry.d.ts +1 -1
  101. package/dist/tools/registry.js +223 -79
  102. package/dist/tools/registry.js.map +1 -1
  103. package/dist/tools/shell.d.ts +45 -4
  104. package/dist/tools/shell.js +419 -88
  105. package/dist/tools/shell.js.map +1 -1
  106. package/dist/tools/validate.d.ts +37 -0
  107. package/dist/tools/validate.js +144 -0
  108. package/dist/tools/validate.js.map +1 -0
  109. package/dist/types.d.ts +7 -15
  110. package/dist/ui/keys.d.ts +21 -0
  111. package/dist/ui/keys.js +13 -0
  112. package/dist/ui/keys.js.map +1 -0
  113. package/dist/ui/output-pane.d.ts +31 -0
  114. package/dist/ui/output-pane.js +81 -0
  115. package/dist/ui/output-pane.js.map +1 -0
  116. package/package.json +1 -1
@@ -0,0 +1,144 @@
1
+ import net from "node:net";
2
+ const HOSTNAME_RE = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/i;
3
+ const SHORT_HOSTNAME_RE = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;
4
+ const SHELL_METACHAR_RE = /[\s;`$<>|&"'\\]/;
5
+ /**
6
+ * Parse a network target as IP, CIDR, or hostname. Throws on shell-injection
7
+ * attempts. The output is safe to pass to spawn() with `shell: false`.
8
+ */
9
+ export function parseHost(raw) {
10
+ const value = raw.trim();
11
+ if (!value) {
12
+ throw new Error(`Invalid host: empty value`);
13
+ }
14
+ if (SHELL_METACHAR_RE.test(value)) {
15
+ throw new Error(`Invalid host "${value}": contains shell metacharacters`);
16
+ }
17
+ // CIDR: <ip>/<bits>
18
+ if (value.includes("/")) {
19
+ const [addr, maskRaw] = value.split("/");
20
+ if (!addr || !maskRaw) {
21
+ throw new Error(`Invalid CIDR: ${value}`);
22
+ }
23
+ const mask = Number(maskRaw);
24
+ if (!Number.isInteger(mask)) {
25
+ throw new Error(`Invalid CIDR mask: ${maskRaw}`);
26
+ }
27
+ const family = net.isIP(addr);
28
+ if (family === 4) {
29
+ if (mask < 0 || mask > 32) {
30
+ throw new Error(`Invalid IPv4 CIDR mask: ${maskRaw}`);
31
+ }
32
+ return { kind: "cidr", value };
33
+ }
34
+ if (family === 6) {
35
+ if (mask < 0 || mask > 128) {
36
+ throw new Error(`Invalid IPv6 CIDR mask: ${maskRaw}`);
37
+ }
38
+ return { kind: "cidr", value };
39
+ }
40
+ throw new Error(`Invalid CIDR address: ${addr}`);
41
+ }
42
+ if (net.isIP(value)) {
43
+ return { kind: "ip", value };
44
+ }
45
+ if (HOSTNAME_RE.test(value) || SHORT_HOSTNAME_RE.test(value)) {
46
+ return { kind: "hostname", value: value.toLowerCase() };
47
+ }
48
+ throw new Error(`Invalid host: ${value}`);
49
+ }
50
+ /**
51
+ * Validate an nmap-compatible port specification. Accepts:
52
+ * - single port: "80"
53
+ * - csv: "80,443,8080"
54
+ * - ranges: "1-1000"
55
+ * - mixed: "22,80,443,8000-9000"
56
+ */
57
+ export function parsePortSpec(raw) {
58
+ const value = raw.trim();
59
+ if (!value)
60
+ throw new Error("Invalid port spec: empty");
61
+ if (SHELL_METACHAR_RE.test(value)) {
62
+ throw new Error(`Invalid port spec "${value}": shell metacharacters`);
63
+ }
64
+ if (!/^[\d,\-]+$/.test(value)) {
65
+ throw new Error(`Invalid port spec: ${value}`);
66
+ }
67
+ for (const part of value.split(",")) {
68
+ if (part.includes("-")) {
69
+ const [lo, hi] = part.split("-").map((n) => Number(n));
70
+ if (!Number.isInteger(lo) || !Number.isInteger(hi)) {
71
+ throw new Error(`Invalid port range: ${part}`);
72
+ }
73
+ if (lo === undefined || hi === undefined) {
74
+ throw new Error(`Invalid port range: ${part}`);
75
+ }
76
+ if (lo < 1 || lo > 65535 || hi < 1 || hi > 65535 || lo > hi) {
77
+ throw new Error(`Invalid port range: ${part}`);
78
+ }
79
+ }
80
+ else {
81
+ const n = Number(part);
82
+ if (!Number.isInteger(n) || n < 1 || n > 65535) {
83
+ throw new Error(`Invalid port: ${part}`);
84
+ }
85
+ }
86
+ }
87
+ return value;
88
+ }
89
+ const SAFE_SCRIPT_RE = /^[a-z0-9_-]+(?:,[a-z0-9_-]+)*$/i;
90
+ /** Convert a structured scan profile into safe argv for nmap. */
91
+ export function profileToNmapArgs(profile = {}) {
92
+ const args = [];
93
+ if (profile.scanType === "syn")
94
+ args.push("-sS");
95
+ else if (profile.scanType === "tcp")
96
+ args.push("-sT");
97
+ else if (profile.scanType === "ping")
98
+ args.push("-sn");
99
+ if (profile.udp || profile.scanType === "udp")
100
+ args.push("-sU");
101
+ if (profile.serviceDetect)
102
+ args.push("-sV");
103
+ if (profile.timing) {
104
+ if (!/^T[0-5]$/.test(profile.timing)) {
105
+ throw new Error(`Invalid timing template: ${profile.timing}`);
106
+ }
107
+ args.push(`-${profile.timing}`);
108
+ }
109
+ if (typeof profile.topPorts === "number") {
110
+ if (!Number.isInteger(profile.topPorts) ||
111
+ profile.topPorts < 1 ||
112
+ profile.topPorts > 65535) {
113
+ throw new Error(`Invalid topPorts: ${profile.topPorts}`);
114
+ }
115
+ args.push("--top-ports", String(profile.topPorts));
116
+ }
117
+ if (profile.scripts && profile.scripts.length > 0) {
118
+ const joined = profile.scripts.join(",");
119
+ if (!SAFE_SCRIPT_RE.test(joined)) {
120
+ throw new Error(`Invalid scripts list: ${joined}`);
121
+ }
122
+ args.push("--script", joined);
123
+ }
124
+ return args;
125
+ }
126
+ /**
127
+ * For backwards compatibility with the legacy `flags` string. We accept a
128
+ * whitespace-delimited list, but only if every token matches a strict
129
+ * safe pattern (no shell metacharacters, no leading `-` followed by anything
130
+ * we don't recognize as a benign flag).
131
+ */
132
+ export function parseLegacyFlags(raw) {
133
+ const value = raw.trim();
134
+ if (!value)
135
+ return [];
136
+ const tokens = value.split(/\s+/);
137
+ for (const token of tokens) {
138
+ if (!/^[A-Za-z0-9_./@:=,-]+$/.test(token)) {
139
+ throw new Error(`Invalid flag token: ${token}`);
140
+ }
141
+ }
142
+ return tokens;
143
+ }
144
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/tools/validate.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,UAAU,CAAC;AAS3B,MAAM,WAAW,GACf,wEAAwE,CAAC;AAC3E,MAAM,iBAAiB,GAAG,yCAAyC,CAAC;AACpE,MAAM,iBAAiB,GAAG,iBAAiB,CAAC;AAE5C;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IACzB,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAC/C,CAAC;IACD,IAAI,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,iBAAiB,KAAK,kCAAkC,CAAC,CAAC;IAC5E,CAAC;IACD,oBAAoB;IACpB,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACzC,IAAI,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,iBAAiB,KAAK,EAAE,CAAC,CAAC;QAC5C,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;QAC7B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,sBAAsB,OAAO,EAAE,CAAC,CAAC;QACnD,CAAC;QACD,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9B,IAAI,MAAM,KAAK,CAAC,EAAE,CAAC;YACjB,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,EAAE,EAAE,CAAC;gBAC1B,MAAM,IAAI,KAAK,CAAC,2BAA2B,OAAO,EAAE,CAAC,CAAC;YACxD,CAAC;YACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QACjC,CAAC;QACD,IAAI,MAAM,KAAK,CAAC,EAAE,CAAC;YACjB,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,GAAG,EAAE,CAAC;gBAC3B,MAAM,IAAI,KAAK,CAAC,2BAA2B,OAAO,EAAE,CAAC,CAAC;YACxD,CAAC;YACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QACjC,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,yBAAyB,IAAI,EAAE,CAAC,CAAC;IACnD,CAAC;IACD,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACpB,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IAC/B,CAAC;IACD,IAAI,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7D,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;IAC1D,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,iBAAiB,KAAK,EAAE,CAAC,CAAC;AAC5C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IACzB,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;IACxD,IAAI,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,sBAAsB,KAAK,yBAAyB,CAAC,CAAC;IACxE,CAAC;IACD,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,sBAAsB,KAAK,EAAE,CAAC,CAAC;IACjD,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACpC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YACvD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC;gBACnD,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,EAAE,CAAC,CAAC;YACjD,CAAC;YACD,IAAI,EAAE,KAAK,SAAS,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;gBACzC,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,EAAE,CAAC,CAAC;YACjD,CAAC;YACD,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC;gBAC5D,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,EAAE,CAAC,CAAC;YACjD,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;YACvB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC;gBAC/C,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC;YAC3C,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAcD,MAAM,cAAc,GAAG,iCAAiC,CAAC;AAEzD,iEAAiE;AACjE,MAAM,UAAU,iBAAiB,CAAC,UAAuB,EAAE;IACzD,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,IAAI,OAAO,CAAC,QAAQ,KAAK,KAAK;QAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;SAC5C,IAAI,OAAO,CAAC,QAAQ,KAAK,KAAK;QAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;SACjD,IAAI,OAAO,CAAC,QAAQ,KAAK,MAAM;QAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACvD,IAAI,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,QAAQ,KAAK,KAAK;QAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChE,IAAI,OAAO,CAAC,aAAa;QAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5C,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CAAC,4BAA4B,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAChE,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAClC,CAAC;IACD,IAAI,OAAO,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACzC,IACE,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC;YACnC,OAAO,CAAC,QAAQ,GAAG,CAAC;YACpB,OAAO,CAAC,QAAQ,GAAG,KAAK,EACxB,CAAC;YACD,MAAM,IAAI,KAAK,CAAC,qBAAqB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC3D,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IACrD,CAAC;IACD,IAAI,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClD,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACzC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACjC,MAAM,IAAI,KAAK,CAAC,yBAAyB,MAAM,EAAE,CAAC,CAAC;QACrD,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IACzB,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IACtB,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAClC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1C,MAAM,IAAI,KAAK,CAAC,uBAAuB,KAAK,EAAE,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
package/dist/types.d.ts CHANGED
@@ -39,26 +39,18 @@ export interface ToolCall {
39
39
  name: string;
40
40
  args: Record<string, unknown>;
41
41
  }
42
+ export interface ToolStats {
43
+ bytesRead: number;
44
+ bytesDropped: number;
45
+ linesRead: number;
46
+ elapsedMs: number;
47
+ captureLimitHit?: boolean | undefined;
48
+ }
42
49
  export interface ToolResult {
43
50
  ok: boolean;
44
51
  output: string;
45
52
  exitCode?: number | undefined;
46
53
  outputPath?: string | undefined;
47
54
  truncated?: boolean | undefined;
48
- modelContext?: string | undefined;
49
- summary?: string | undefined;
50
- artifacts?: ToolArtifact[] | undefined;
51
55
  stats?: ToolStats | undefined;
52
56
  }
53
- export interface ToolArtifact {
54
- path: string;
55
- kind: "raw" | "json" | "xml" | "report";
56
- redacted: boolean;
57
- }
58
- export interface ToolStats {
59
- bytesRead?: number | undefined;
60
- bytesShown?: number | undefined;
61
- bytesDropped?: number | undefined;
62
- linesRead?: number | undefined;
63
- elapsedMs?: number | undefined;
64
- }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Canonical keypress shapes used by clai. Centralized so the REPL,
3
+ * one-shot mode, and tests agree on what counts as "Ctrl+O" / "Ctrl+T" /
4
+ * "Ctrl+C" / "ESC" regardless of platform.
5
+ *
6
+ * Node's `readline.emitKeypressEvents` already normalizes the most
7
+ * common combinations across macOS / Linux / Windows terminals into the
8
+ * same `{ ctrl, meta, name, sequence }` shape, so we just need to be
9
+ * explicit about which fields matter.
10
+ */
11
+ export interface Keypress {
12
+ ctrl?: boolean | undefined;
13
+ meta?: boolean | undefined;
14
+ shift?: boolean | undefined;
15
+ name?: string | undefined;
16
+ sequence?: string | undefined;
17
+ }
18
+ export declare function isCtrlC(key: Keypress): boolean;
19
+ export declare function isCtrlT(key: Keypress): boolean;
20
+ export declare function isCtrlO(key: Keypress): boolean;
21
+ export declare function isEscape(key: Keypress): boolean;
@@ -0,0 +1,13 @@
1
+ export function isCtrlC(key) {
2
+ return Boolean(key.ctrl) && key.name === "c";
3
+ }
4
+ export function isCtrlT(key) {
5
+ return Boolean(key.ctrl) && key.name === "t";
6
+ }
7
+ export function isCtrlO(key) {
8
+ return Boolean(key.ctrl) && key.name === "o";
9
+ }
10
+ export function isEscape(key) {
11
+ return key.name === "escape";
12
+ }
13
+ //# sourceMappingURL=keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../../src/ui/keys.ts"],"names":[],"mappings":"AAkBA,MAAM,UAAU,OAAO,CAAC,GAAa;IACnC,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,GAAa;IACnC,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,GAAa;IACnC,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,GAAa;IACpC,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,31 @@
1
+ export interface OutputViewport {
2
+ /** Display name for the tool (e.g. "shell.exec", "nmap"). */
3
+ toolName: string;
4
+ /** Short args display (e.g. the command line). */
5
+ argsDisplay: string;
6
+ /** Path to the raw artifact file on disk (if present). */
7
+ artifactPath?: string | undefined;
8
+ /** AI-facing reduced summary shown to the user as well. */
9
+ summary: string;
10
+ /** Whether the user has expanded the full output via Ctrl+O / /output. */
11
+ expanded: boolean;
12
+ /** Timestamp the viewport was registered (for /output last). */
13
+ createdAt: number;
14
+ /** Unique id; users can refer to it via /output <id>. */
15
+ id: string;
16
+ }
17
+ export declare function newViewportId(toolName: string): string;
18
+ export declare function registerViewport(partial: Omit<OutputViewport, "id" | "expanded" | "createdAt"> & {
19
+ id?: string | undefined;
20
+ }): OutputViewport;
21
+ export declare function getViewport(id: string): OutputViewport | undefined;
22
+ export declare function getLastViewport(): OutputViewport | undefined;
23
+ export declare function listViewports(): OutputViewport[];
24
+ /**
25
+ * Toggle expansion and render either the full artifact (when expanding) or
26
+ * a collapse confirmation (when collapsing). Used both by the Ctrl+O key
27
+ * handler and the /output slash command so the two paths agree.
28
+ */
29
+ export declare function toggleViewport(id: string, write?: (chunk: string) => void): Promise<boolean>;
30
+ export declare function clearViewports(): void;
31
+ export declare function formatViewportHint(v: OutputViewport): string;
@@ -0,0 +1,81 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import chalk from "chalk";
3
+ let viewportCounter = 0;
4
+ const viewports = new Map();
5
+ let lastViewportId;
6
+ export function newViewportId(toolName) {
7
+ viewportCounter += 1;
8
+ return `${toolName.replace(/\W+/g, "-")}-${viewportCounter}`;
9
+ }
10
+ export function registerViewport(partial) {
11
+ const id = partial.id ?? newViewportId(partial.toolName);
12
+ const v = {
13
+ ...partial,
14
+ id,
15
+ expanded: false,
16
+ createdAt: Date.now(),
17
+ };
18
+ viewports.set(id, v);
19
+ lastViewportId = id;
20
+ return v;
21
+ }
22
+ export function getViewport(id) {
23
+ return viewports.get(id);
24
+ }
25
+ export function getLastViewport() {
26
+ if (!lastViewportId)
27
+ return undefined;
28
+ return viewports.get(lastViewportId);
29
+ }
30
+ export function listViewports() {
31
+ return [...viewports.values()].sort((a, b) => a.createdAt - b.createdAt);
32
+ }
33
+ /**
34
+ * Toggle expansion and render either the full artifact (when expanding) or
35
+ * a collapse confirmation (when collapsing). Used both by the Ctrl+O key
36
+ * handler and the /output slash command so the two paths agree.
37
+ */
38
+ export async function toggleViewport(id, write = (c) => process.stdout.write(c)) {
39
+ const v = viewports.get(id);
40
+ if (!v)
41
+ return false;
42
+ v.expanded = !v.expanded;
43
+ if (v.expanded) {
44
+ write(chalk.dim(`\n ── full output for ${v.toolName} (${v.argsDisplay}) ──\n`));
45
+ if (v.artifactPath) {
46
+ try {
47
+ const raw = await readFile(v.artifactPath, "utf8");
48
+ write(raw);
49
+ if (!raw.endsWith("\n"))
50
+ write("\n");
51
+ }
52
+ catch (error) {
53
+ write(chalk.yellow(` (could not read artifact: ${error instanceof Error ? error.message : String(error)})\n`));
54
+ }
55
+ }
56
+ else {
57
+ write(chalk.dim(" (no artifact file — only the summary is available)\n"));
58
+ }
59
+ write(chalk.dim(` ── summary still shown below; press Ctrl+O again to collapse ──\n`));
60
+ // Re-print summary so the user has it without scrolling.
61
+ write(`${v.summary}\n`);
62
+ }
63
+ else {
64
+ write(chalk.dim(`\n ── collapsed; press Ctrl+O to expand ──\n`));
65
+ }
66
+ return true;
67
+ }
68
+ export function clearViewports() {
69
+ viewports.clear();
70
+ lastViewportId = undefined;
71
+ }
72
+ export function formatViewportHint(v) {
73
+ const hints = [];
74
+ if (process.stdout.isTTY)
75
+ hints.push("Ctrl+O");
76
+ hints.push("/output last");
77
+ if (v.artifactPath)
78
+ hints.push(`/output ${v.id}`);
79
+ return chalk.dim(` ${hints.join(" or ")} to ${v.expanded ? "collapse" : "show full output"}${v.artifactPath ? ` (${v.artifactPath})` : ""}`);
80
+ }
81
+ //# sourceMappingURL=output-pane.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-pane.js","sourceRoot":"","sources":["../../src/ui/output-pane.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,KAAK,MAAM,OAAO,CAAC;AAmB1B,IAAI,eAAe,GAAG,CAAC,CAAC;AACxB,MAAM,SAAS,GAAG,IAAI,GAAG,EAA0B,CAAC;AACpD,IAAI,cAAkC,CAAC;AAEvC,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,eAAe,IAAI,CAAC,CAAC;IACrB,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,eAAe,EAAE,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,OAA4F;IAE5F,MAAM,EAAE,GAAG,OAAO,CAAC,EAAE,IAAI,aAAa,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzD,MAAM,CAAC,GAAmB;QACxB,GAAG,OAAO;QACV,EAAE;QACF,QAAQ,EAAE,KAAK;QACf,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;KACtB,CAAC;IACF,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IACrB,cAAc,GAAG,EAAE,CAAC;IACpB,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,EAAU;IACpC,OAAO,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,IAAI,CAAC,cAAc;QAAE,OAAO,SAAS,CAAC;IACtC,OAAO,SAAS,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC;AAC3E,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,EAAU,EACV,QAAiC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAE/D,MAAM,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC5B,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACrB,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC;IACzB,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;QACf,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,WAAW,QAAQ,CAAC,CAAC,CAAC;QACjF,IAAI,CAAC,CAAC,YAAY,EAAE,CAAC;YACnB,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,CAAC,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;gBACnD,KAAK,CAAC,GAAG,CAAC,CAAC;gBACX,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC;oBAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACvC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,KAAK,CACH,KAAK,CAAC,MAAM,CAAC,+BAA+B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CACzG,CAAC;YACJ,CAAC;QACH,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,wDAAwD,CAAC,CAAC,CAAC;QAC7E,CAAC;QACD,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,qEAAqE,CAAC,CAAC,CAAC;QACxF,yDAAyD;QACzD,KAAK,CAAC,GAAG,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC;IAC1B,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,+CAA+C,CAAC,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,SAAS,CAAC,KAAK,EAAE,CAAC;IAClB,cAAc,GAAG,SAAS,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,CAAiB;IAClD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK;QAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC/C,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC3B,IAAI,CAAC,CAAC,YAAY;QAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAClD,OAAO,KAAK,CAAC,GAAG,CACd,KAAK,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,kBAAkB,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAC5H,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pentoshi/clai",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "A fast, cross-platform AI CLI assistant with ask and agent modes for shell tasks, file operations, and cybersecurity workflows.",
5
5
  "type": "module",
6
6
  "license": "MIT",