skein-cli 0.1.0-alpha.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 (250) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +180 -0
  3. package/bin/skein.mjs +7 -0
  4. package/dist/adapters/aider/conv-reader.d.ts +7 -0
  5. package/dist/adapters/aider/conv-reader.js +203 -0
  6. package/dist/adapters/aider/conv-reader.js.map +1 -0
  7. package/dist/adapters/aider/index.d.ts +2 -0
  8. package/dist/adapters/aider/index.js +123 -0
  9. package/dist/adapters/aider/index.js.map +1 -0
  10. package/dist/adapters/base.d.ts +71 -0
  11. package/dist/adapters/base.js +38 -0
  12. package/dist/adapters/base.js.map +1 -0
  13. package/dist/adapters/claude-code/conv-reader.d.ts +2 -0
  14. package/dist/adapters/claude-code/conv-reader.js +155 -0
  15. package/dist/adapters/claude-code/conv-reader.js.map +1 -0
  16. package/dist/adapters/claude-code/index.d.ts +3 -0
  17. package/dist/adapters/claude-code/index.js +517 -0
  18. package/dist/adapters/claude-code/index.js.map +1 -0
  19. package/dist/adapters/claude-desktop/index.d.ts +2 -0
  20. package/dist/adapters/claude-desktop/index.js +95 -0
  21. package/dist/adapters/claude-desktop/index.js.map +1 -0
  22. package/dist/adapters/codex/index.d.ts +2 -0
  23. package/dist/adapters/codex/index.js +472 -0
  24. package/dist/adapters/codex/index.js.map +1 -0
  25. package/dist/adapters/cursor/index.d.ts +2 -0
  26. package/dist/adapters/cursor/index.js +255 -0
  27. package/dist/adapters/cursor/index.js.map +1 -0
  28. package/dist/adapters/opencode/conv-reader.d.ts +3 -0
  29. package/dist/adapters/opencode/conv-reader.js +190 -0
  30. package/dist/adapters/opencode/conv-reader.js.map +1 -0
  31. package/dist/adapters/opencode/index.d.ts +2 -0
  32. package/dist/adapters/opencode/index.js +349 -0
  33. package/dist/adapters/opencode/index.js.map +1 -0
  34. package/dist/adapters/registry.d.ts +4 -0
  35. package/dist/adapters/registry.js +26 -0
  36. package/dist/adapters/registry.js.map +1 -0
  37. package/dist/cli.d.ts +3 -0
  38. package/dist/cli.js +54 -0
  39. package/dist/cli.js.map +1 -0
  40. package/dist/commands/conv.d.ts +2 -0
  41. package/dist/commands/conv.js +261 -0
  42. package/dist/commands/conv.js.map +1 -0
  43. package/dist/commands/doctor.d.ts +2 -0
  44. package/dist/commands/doctor.js +85 -0
  45. package/dist/commands/doctor.js.map +1 -0
  46. package/dist/commands/init.d.ts +2 -0
  47. package/dist/commands/init.js +38 -0
  48. package/dist/commands/init.js.map +1 -0
  49. package/dist/commands/memory.d.ts +2 -0
  50. package/dist/commands/memory.js +197 -0
  51. package/dist/commands/memory.js.map +1 -0
  52. package/dist/commands/migrate.d.ts +2 -0
  53. package/dist/commands/migrate.js +102 -0
  54. package/dist/commands/migrate.js.map +1 -0
  55. package/dist/commands/profile.d.ts +2 -0
  56. package/dist/commands/profile.js +183 -0
  57. package/dist/commands/profile.js.map +1 -0
  58. package/dist/commands/redact.d.ts +2 -0
  59. package/dist/commands/redact.js +73 -0
  60. package/dist/commands/redact.js.map +1 -0
  61. package/dist/commands/trace.d.ts +2 -0
  62. package/dist/commands/trace.js +181 -0
  63. package/dist/commands/trace.js.map +1 -0
  64. package/dist/commands/view.d.ts +13 -0
  65. package/dist/commands/view.js +184 -0
  66. package/dist/commands/view.js.map +1 -0
  67. package/dist/commands/watch.d.ts +6 -0
  68. package/dist/commands/watch.js +61 -0
  69. package/dist/commands/watch.js.map +1 -0
  70. package/dist/commands/wrap.d.ts +2 -0
  71. package/dist/commands/wrap.js +131 -0
  72. package/dist/commands/wrap.js.map +1 -0
  73. package/dist/conv/chatgpt-import.d.ts +33 -0
  74. package/dist/conv/chatgpt-import.js +145 -0
  75. package/dist/conv/chatgpt-import.js.map +1 -0
  76. package/dist/conv/cursor.d.ts +12 -0
  77. package/dist/conv/cursor.js +55 -0
  78. package/dist/conv/cursor.js.map +1 -0
  79. package/dist/conv/export.d.ts +40 -0
  80. package/dist/conv/export.js +215 -0
  81. package/dist/conv/export.js.map +1 -0
  82. package/dist/conv/replay/aider.d.ts +24 -0
  83. package/dist/conv/replay/aider.js +56 -0
  84. package/dist/conv/replay/aider.js.map +1 -0
  85. package/dist/conv/replay/claude-code.d.ts +38 -0
  86. package/dist/conv/replay/claude-code.js +80 -0
  87. package/dist/conv/replay/claude-code.js.map +1 -0
  88. package/dist/conv/replay/context.d.ts +25 -0
  89. package/dist/conv/replay/context.js +63 -0
  90. package/dist/conv/replay/context.js.map +1 -0
  91. package/dist/conv/replay/index.d.ts +22 -0
  92. package/dist/conv/replay/index.js +84 -0
  93. package/dist/conv/replay/index.js.map +1 -0
  94. package/dist/conv/search.d.ts +67 -0
  95. package/dist/conv/search.js +379 -0
  96. package/dist/conv/search.js.map +1 -0
  97. package/dist/conv/sink.d.ts +26 -0
  98. package/dist/conv/sink.js +76 -0
  99. package/dist/conv/sink.js.map +1 -0
  100. package/dist/conv/sources/aider.d.ts +13 -0
  101. package/dist/conv/sources/aider.js +95 -0
  102. package/dist/conv/sources/aider.js.map +1 -0
  103. package/dist/conv/sources/claude-code.d.ts +13 -0
  104. package/dist/conv/sources/claude-code.js +189 -0
  105. package/dist/conv/sources/claude-code.js.map +1 -0
  106. package/dist/conv/sources/codex.d.ts +15 -0
  107. package/dist/conv/sources/codex.js +175 -0
  108. package/dist/conv/sources/codex.js.map +1 -0
  109. package/dist/conv/sources/opencode.d.ts +12 -0
  110. package/dist/conv/sources/opencode.js +92 -0
  111. package/dist/conv/sources/opencode.js.map +1 -0
  112. package/dist/conv/store.d.ts +10 -0
  113. package/dist/conv/store.js +75 -0
  114. package/dist/conv/store.js.map +1 -0
  115. package/dist/conv/title.d.ts +9 -0
  116. package/dist/conv/title.js +74 -0
  117. package/dist/conv/title.js.map +1 -0
  118. package/dist/conv/watcher.d.ts +28 -0
  119. package/dist/conv/watcher.js +75 -0
  120. package/dist/conv/watcher.js.map +1 -0
  121. package/dist/conv/zip.d.ts +11 -0
  122. package/dist/conv/zip.js +82 -0
  123. package/dist/conv/zip.js.map +1 -0
  124. package/dist/hf/client.d.ts +41 -0
  125. package/dist/hf/client.js +87 -0
  126. package/dist/hf/client.js.map +1 -0
  127. package/dist/index.d.ts +5 -0
  128. package/dist/index.js +6 -0
  129. package/dist/index.js.map +1 -0
  130. package/dist/ir/index.d.ts +24 -0
  131. package/dist/ir/index.js +23 -0
  132. package/dist/ir/index.js.map +1 -0
  133. package/dist/ir/profile.d.ts +55 -0
  134. package/dist/ir/profile.js +177 -0
  135. package/dist/ir/profile.js.map +1 -0
  136. package/dist/memory/index.d.ts +41 -0
  137. package/dist/memory/index.js +109 -0
  138. package/dist/memory/index.js.map +1 -0
  139. package/dist/memory/search.d.ts +24 -0
  140. package/dist/memory/search.js +114 -0
  141. package/dist/memory/search.js.map +1 -0
  142. package/dist/memory/sinks/claude-mem.d.ts +13 -0
  143. package/dist/memory/sinks/claude-mem.js +100 -0
  144. package/dist/memory/sinks/claude-mem.js.map +1 -0
  145. package/dist/memory/sinks/codex.d.ts +12 -0
  146. package/dist/memory/sinks/codex.js +82 -0
  147. package/dist/memory/sinks/codex.js.map +1 -0
  148. package/dist/memory/sinks/context.d.ts +21 -0
  149. package/dist/memory/sinks/context.js +42 -0
  150. package/dist/memory/sinks/context.js.map +1 -0
  151. package/dist/memory/sources/chatgpt.d.ts +22 -0
  152. package/dist/memory/sources/chatgpt.js +98 -0
  153. package/dist/memory/sources/chatgpt.js.map +1 -0
  154. package/dist/memory/sources/claude-mem.d.ts +8 -0
  155. package/dist/memory/sources/claude-mem.js +104 -0
  156. package/dist/memory/sources/claude-mem.js.map +1 -0
  157. package/dist/memory/sources/codex.d.ts +8 -0
  158. package/dist/memory/sources/codex.js +77 -0
  159. package/dist/memory/sources/codex.js.map +1 -0
  160. package/dist/memory/store.d.ts +19 -0
  161. package/dist/memory/store.js +82 -0
  162. package/dist/memory/store.js.map +1 -0
  163. package/dist/proxy/http.d.ts +21 -0
  164. package/dist/proxy/http.js +205 -0
  165. package/dist/proxy/http.js.map +1 -0
  166. package/dist/proxy/recorder.d.ts +35 -0
  167. package/dist/proxy/recorder.js +221 -0
  168. package/dist/proxy/recorder.js.map +1 -0
  169. package/dist/proxy/streaming.d.ts +33 -0
  170. package/dist/proxy/streaming.js +185 -0
  171. package/dist/proxy/streaming.js.map +1 -0
  172. package/dist/redactor/entropy.d.ts +29 -0
  173. package/dist/redactor/entropy.js +98 -0
  174. package/dist/redactor/entropy.js.map +1 -0
  175. package/dist/redactor/index.d.ts +52 -0
  176. package/dist/redactor/index.js +152 -0
  177. package/dist/redactor/index.js.map +1 -0
  178. package/dist/redactor/ner.d.ts +53 -0
  179. package/dist/redactor/ner.js +97 -0
  180. package/dist/redactor/ner.js.map +1 -0
  181. package/dist/redactor/pii-patterns.d.ts +22 -0
  182. package/dist/redactor/pii-patterns.js +187 -0
  183. package/dist/redactor/pii-patterns.js.map +1 -0
  184. package/dist/redactor/secret-patterns.d.ts +27 -0
  185. package/dist/redactor/secret-patterns.js +475 -0
  186. package/dist/redactor/secret-patterns.js.map +1 -0
  187. package/dist/schema/conv.d.ts +698 -0
  188. package/dist/schema/conv.js +85 -0
  189. package/dist/schema/conv.js.map +1 -0
  190. package/dist/schema/index.d.ts +2 -0
  191. package/dist/schema/index.js +3 -0
  192. package/dist/schema/index.js.map +1 -0
  193. package/dist/schema/manifest.d.ts +1531 -0
  194. package/dist/schema/manifest.js +179 -0
  195. package/dist/schema/manifest.js.map +1 -0
  196. package/dist/schema/memory.d.ts +107 -0
  197. package/dist/schema/memory.js +45 -0
  198. package/dist/schema/memory.js.map +1 -0
  199. package/dist/schema/trace.d.ts +164 -0
  200. package/dist/schema/trace.js +89 -0
  201. package/dist/schema/trace.js.map +1 -0
  202. package/dist/trace/consent.d.ts +30 -0
  203. package/dist/trace/consent.js +60 -0
  204. package/dist/trace/consent.js.map +1 -0
  205. package/dist/trace/extract.d.ts +22 -0
  206. package/dist/trace/extract.js +168 -0
  207. package/dist/trace/extract.js.map +1 -0
  208. package/dist/trace/ml-pii.d.ts +33 -0
  209. package/dist/trace/ml-pii.js +35 -0
  210. package/dist/trace/ml-pii.js.map +1 -0
  211. package/dist/trace/push.d.ts +59 -0
  212. package/dist/trace/push.js +141 -0
  213. package/dist/trace/push.js.map +1 -0
  214. package/dist/trace/serialize.d.ts +23 -0
  215. package/dist/trace/serialize.js +67 -0
  216. package/dist/trace/serialize.js.map +1 -0
  217. package/dist/ui/banner.d.ts +2 -0
  218. package/dist/ui/banner.js +17 -0
  219. package/dist/ui/banner.js.map +1 -0
  220. package/dist/ui/box.d.ts +4 -0
  221. package/dist/ui/box.js +38 -0
  222. package/dist/ui/box.js.map +1 -0
  223. package/dist/ui/index.d.ts +5 -0
  224. package/dist/ui/index.js +5 -0
  225. package/dist/ui/index.js.map +1 -0
  226. package/dist/ui/spinner.d.ts +10 -0
  227. package/dist/ui/spinner.js +44 -0
  228. package/dist/ui/spinner.js.map +1 -0
  229. package/dist/ui/table.d.ts +9 -0
  230. package/dist/ui/table.js +55 -0
  231. package/dist/ui/table.js.map +1 -0
  232. package/dist/util/frontmatter.d.ts +6 -0
  233. package/dist/util/frontmatter.js +20 -0
  234. package/dist/util/frontmatter.js.map +1 -0
  235. package/dist/util/fs.d.ts +5 -0
  236. package/dist/util/fs.js +41 -0
  237. package/dist/util/fs.js.map +1 -0
  238. package/dist/util/ids.d.ts +3 -0
  239. package/dist/util/ids.js +16 -0
  240. package/dist/util/ids.js.map +1 -0
  241. package/dist/util/log.d.ts +13 -0
  242. package/dist/util/log.js +33 -0
  243. package/dist/util/log.js.map +1 -0
  244. package/dist/util/paths.d.ts +18 -0
  245. package/dist/util/paths.js +36 -0
  246. package/dist/util/paths.js.map +1 -0
  247. package/dist/version.d.ts +5 -0
  248. package/dist/version.js +32 -0
  249. package/dist/version.js.map +1 -0
  250. package/package.json +86 -0
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Aggregator that consumes SSE chunks from an upstream LLM provider and
3
+ * builds a single normalized assistant message with concatenated text and
4
+ * resolved tool_use blocks.
5
+ *
6
+ * Each provider has its own delta shape; the aggregator dispatches on
7
+ * provider at construction time.
8
+ */
9
+ export class StreamingAggregator {
10
+ provider;
11
+ buffer = '';
12
+ text = new Map();
13
+ toolUses = new Map();
14
+ inputTokens = 0;
15
+ outputTokens = 0;
16
+ model;
17
+ orderedIndices = [];
18
+ constructor(provider) {
19
+ this.provider = provider;
20
+ }
21
+ feed(chunk) {
22
+ this.buffer += chunk.toString('utf8');
23
+ let newlineIndex;
24
+ while ((newlineIndex = this.buffer.indexOf('\n')) !== -1) {
25
+ const line = this.buffer.slice(0, newlineIndex).trimEnd();
26
+ this.buffer = this.buffer.slice(newlineIndex + 1);
27
+ if (line.length > 0)
28
+ this.handleLine(line);
29
+ }
30
+ }
31
+ /** Call after the upstream stream has ended to flush any remaining buffered line. */
32
+ finish() {
33
+ const remainder = this.buffer.trim();
34
+ if (remainder.length > 0) {
35
+ this.handleLine(remainder);
36
+ this.buffer = '';
37
+ }
38
+ }
39
+ result() {
40
+ const content = [];
41
+ for (const idx of this.orderedIndices) {
42
+ const text = this.text.get(idx);
43
+ if (text !== undefined) {
44
+ content.push({ type: 'text', text });
45
+ continue;
46
+ }
47
+ const toolUse = this.toolUses.get(idx);
48
+ if (toolUse) {
49
+ content.push({
50
+ type: 'tool_use',
51
+ id: toolUse.id,
52
+ name: toolUse.name,
53
+ input: safeJson(toolUse.input) ?? {},
54
+ });
55
+ }
56
+ }
57
+ return {
58
+ content,
59
+ ...(this.inputTokens > 0 ? { tokensIn: this.inputTokens } : {}),
60
+ ...(this.outputTokens > 0 ? { tokensOut: this.outputTokens } : {}),
61
+ ...(this.model ? { model: this.model } : {}),
62
+ };
63
+ }
64
+ handleLine(line) {
65
+ if (!line.startsWith('data:'))
66
+ return;
67
+ const payload = line.slice('data:'.length).trim();
68
+ if (payload === '[DONE]')
69
+ return;
70
+ const event = safeJson(payload);
71
+ if (!event)
72
+ return;
73
+ if (this.provider === 'anthropic')
74
+ this.handleAnthropic(event);
75
+ else
76
+ this.handleOpenAi(event);
77
+ }
78
+ handleAnthropic(event) {
79
+ const type = event['type'];
80
+ if (type === 'message_start') {
81
+ const msg = event['message'];
82
+ if (typeof msg?.['model'] === 'string')
83
+ this.model = msg['model'];
84
+ const usage = msg?.['usage'];
85
+ if (typeof usage?.['input_tokens'] === 'number') {
86
+ this.inputTokens = usage['input_tokens'];
87
+ }
88
+ return;
89
+ }
90
+ if (type === 'content_block_start') {
91
+ const index = event['index'];
92
+ const block = event['content_block'];
93
+ if (block?.['type'] === 'text') {
94
+ this.text.set(index, '');
95
+ this.orderedIndices.push(index);
96
+ }
97
+ else if (block?.['type'] === 'tool_use') {
98
+ this.toolUses.set(index, {
99
+ id: String(block['id'] ?? `tu_${index}`),
100
+ name: String(block['name'] ?? ''),
101
+ input: '',
102
+ });
103
+ this.orderedIndices.push(index);
104
+ }
105
+ return;
106
+ }
107
+ if (type === 'content_block_delta') {
108
+ const index = event['index'];
109
+ const delta = event['delta'];
110
+ if (delta?.['type'] === 'text_delta') {
111
+ const current = this.text.get(index) ?? '';
112
+ this.text.set(index, current + String(delta['text'] ?? ''));
113
+ }
114
+ else if (delta?.['type'] === 'input_json_delta') {
115
+ const tu = this.toolUses.get(index);
116
+ if (tu)
117
+ tu.input += String(delta['partial_json'] ?? '');
118
+ }
119
+ return;
120
+ }
121
+ if (type === 'message_delta') {
122
+ const usage = event['usage'];
123
+ if (typeof usage?.['output_tokens'] === 'number') {
124
+ this.outputTokens = usage['output_tokens'];
125
+ }
126
+ }
127
+ }
128
+ handleOpenAi(event) {
129
+ if (typeof event['model'] === 'string')
130
+ this.model = event['model'];
131
+ const usage = event['usage'];
132
+ if (typeof usage?.['prompt_tokens'] === 'number')
133
+ this.inputTokens = usage['prompt_tokens'];
134
+ if (typeof usage?.['completion_tokens'] === 'number')
135
+ this.outputTokens = usage['completion_tokens'];
136
+ const choices = event['choices'];
137
+ if (!Array.isArray(choices))
138
+ return;
139
+ for (const choice of choices) {
140
+ const index = choice['index'] ?? 0;
141
+ const delta = choice['delta'];
142
+ if (!delta)
143
+ continue;
144
+ if (typeof delta['content'] === 'string') {
145
+ if (!this.text.has(index)) {
146
+ this.text.set(index, '');
147
+ this.orderedIndices.push(index);
148
+ }
149
+ this.text.set(index, this.text.get(index) + delta['content']);
150
+ }
151
+ const toolCalls = delta['tool_calls'];
152
+ if (Array.isArray(toolCalls)) {
153
+ for (const tc of toolCalls) {
154
+ const tcIndex = tc['index'] ?? 0;
155
+ const offsetIndex = 1000 + tcIndex;
156
+ const fn = tc['function'];
157
+ const existing = this.toolUses.get(offsetIndex);
158
+ if (!existing) {
159
+ this.toolUses.set(offsetIndex, {
160
+ id: String(tc['id'] ?? `tc_${tcIndex}`),
161
+ name: String(fn?.['name'] ?? existing?.['name'] ?? ''),
162
+ input: String(fn?.['arguments'] ?? ''),
163
+ });
164
+ this.orderedIndices.push(offsetIndex);
165
+ }
166
+ else {
167
+ if (fn?.['name'])
168
+ existing.name = String(fn['name']);
169
+ if (fn?.['arguments'])
170
+ existing.input += String(fn['arguments']);
171
+ }
172
+ }
173
+ }
174
+ }
175
+ }
176
+ }
177
+ function safeJson(raw) {
178
+ try {
179
+ return JSON.parse(raw);
180
+ }
181
+ catch {
182
+ return undefined;
183
+ }
184
+ }
185
+ //# sourceMappingURL=streaming.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streaming.js","sourceRoot":"","sources":["../../src/proxy/streaming.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Shannon-entropy based detector for high-entropy tokens that don't match
3
+ * a named regex but look like credentials. Heuristic — meant to be the
4
+ * last line of defense.
5
+ *
6
+ * Default thresholds tuned to balance precision and recall:
7
+ * - length ≥ 24 chars
8
+ * - entropy ≥ 4.5 bits/char (base64-ish or random hex)
9
+ * - alphabet limited to typical credential characters
10
+ * - skips URLs, paths, ISO timestamps, UUIDs, common nouns
11
+ */
12
+ export interface EntropyHit {
13
+ token: string;
14
+ entropy: number;
15
+ start: number;
16
+ end: number;
17
+ }
18
+ export interface EntropyOptions {
19
+ minLength?: number;
20
+ minEntropy?: number;
21
+ /** When true, replace each hit with `[REDACTED:high-entropy]`. */
22
+ replace?: boolean;
23
+ }
24
+ export declare function shannonEntropy(input: string): number;
25
+ export declare function findHighEntropyTokens(input: string, options?: EntropyOptions): EntropyHit[];
26
+ export declare function replaceHighEntropyTokens(input: string, options?: EntropyOptions): {
27
+ text: string;
28
+ hits: EntropyHit[];
29
+ };
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Shannon-entropy based detector for high-entropy tokens that don't match
3
+ * a named regex but look like credentials. Heuristic — meant to be the
4
+ * last line of defense.
5
+ *
6
+ * Default thresholds tuned to balance precision and recall:
7
+ * - length ≥ 24 chars
8
+ * - entropy ≥ 4.5 bits/char (base64-ish or random hex)
9
+ * - alphabet limited to typical credential characters
10
+ * - skips URLs, paths, ISO timestamps, UUIDs, common nouns
11
+ */
12
+ const DEFAULTS = {
13
+ minLength: 24,
14
+ minEntropy: 4.5,
15
+ };
16
+ const TOKEN_REGEX = /[A-Za-z0-9+/=_-]{16,}/g;
17
+ const URL_REGEX = /^[a-z]+:\/\//i;
18
+ const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
19
+ const SHA1_REGEX = /^[0-9a-f]{40}$/i;
20
+ const SHA256_REGEX = /^[0-9a-f]{64}$/i;
21
+ const ISO_TS_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}/;
22
+ const PATH_HINT = /\//;
23
+ export function shannonEntropy(input) {
24
+ if (input.length === 0)
25
+ return 0;
26
+ const counts = new Map();
27
+ for (const char of input)
28
+ counts.set(char, (counts.get(char) ?? 0) + 1);
29
+ let entropy = 0;
30
+ const len = input.length;
31
+ for (const count of counts.values()) {
32
+ const p = count / len;
33
+ entropy -= p * Math.log2(p);
34
+ }
35
+ return entropy;
36
+ }
37
+ export function findHighEntropyTokens(input, options = {}) {
38
+ const minLength = options.minLength ?? DEFAULTS.minLength;
39
+ const minEntropy = options.minEntropy ?? DEFAULTS.minEntropy;
40
+ const hits = [];
41
+ let match;
42
+ TOKEN_REGEX.lastIndex = 0;
43
+ while ((match = TOKEN_REGEX.exec(input))) {
44
+ const token = match[0];
45
+ if (token.length < minLength)
46
+ continue;
47
+ if (isStructuredId(token))
48
+ continue;
49
+ if (looksLikePathOrUrl(input, match.index, token))
50
+ continue;
51
+ const entropy = shannonEntropy(token);
52
+ if (entropy < minEntropy)
53
+ continue;
54
+ hits.push({
55
+ token,
56
+ entropy,
57
+ start: match.index,
58
+ end: match.index + token.length,
59
+ });
60
+ }
61
+ return hits;
62
+ }
63
+ export function replaceHighEntropyTokens(input, options = {}) {
64
+ const hits = findHighEntropyTokens(input, options);
65
+ if (hits.length === 0)
66
+ return { text: input, hits: [] };
67
+ let cursor = 0;
68
+ const parts = [];
69
+ for (const hit of hits) {
70
+ parts.push(input.slice(cursor, hit.start));
71
+ parts.push('[REDACTED:high-entropy]');
72
+ cursor = hit.end;
73
+ }
74
+ parts.push(input.slice(cursor));
75
+ return { text: parts.join(''), hits };
76
+ }
77
+ function isStructuredId(token) {
78
+ if (UUID_REGEX.test(token))
79
+ return true;
80
+ if (SHA1_REGEX.test(token))
81
+ return true;
82
+ if (SHA256_REGEX.test(token))
83
+ return true;
84
+ if (ISO_TS_REGEX.test(token))
85
+ return true;
86
+ return false;
87
+ }
88
+ function looksLikePathOrUrl(haystack, index, token) {
89
+ if (PATH_HINT.test(token))
90
+ return true;
91
+ const prefix = haystack.slice(Math.max(0, index - 8), index);
92
+ if (URL_REGEX.test(prefix.trim()))
93
+ return true;
94
+ if (/[\/.@]/.test(prefix.slice(-1)))
95
+ return true;
96
+ return false;
97
+ }
98
+ //# sourceMappingURL=entropy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entropy.js","sourceRoot":"","sources":["../../src/redactor/entropy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ export type Tier = 'secrets' | 'entropy' | 'pii-regex' | 'pii-ml';
2
+ export interface RedactionHit {
3
+ tier: Tier;
4
+ id: string;
5
+ category?: string;
6
+ start: number;
7
+ end: number;
8
+ match: string;
9
+ rationale?: string;
10
+ }
11
+ export interface RedactOptions {
12
+ tiers?: Partial<Record<Tier, boolean>>;
13
+ ml?: boolean;
14
+ entropy?: {
15
+ minLength?: number;
16
+ minEntropy?: number;
17
+ };
18
+ }
19
+ export interface RedactionResult {
20
+ text: string;
21
+ hits: RedactionHit[];
22
+ }
23
+ /**
24
+ * Apply the secrets tier (Gitleaks-style regex). Returns a fresh string
25
+ * with every match replaced by the pattern's replacement template.
26
+ */
27
+ export declare function redactSecrets(input: string): {
28
+ text: string;
29
+ hits: RedactionHit[];
30
+ };
31
+ /**
32
+ * Full tiered redact: secrets → entropy → PII regex → optional ML.
33
+ */
34
+ export declare function redact(input: string, options?: RedactOptions): Promise<RedactionResult>;
35
+ /**
36
+ * Synchronous redact — runs regex + entropy + PII regex tiers (no ML).
37
+ * Used by hot paths that can't await (proxy recorder, HF push).
38
+ */
39
+ export declare function redactText(input: string): {
40
+ text: string;
41
+ hits: {
42
+ pattern: string;
43
+ count: number;
44
+ }[];
45
+ };
46
+ export declare function redactObject<T>(value: T): T;
47
+ export declare function hasSecret(input: string): boolean;
48
+ export { SECRET_PATTERNS } from './secret-patterns.js';
49
+ export type { SecretPattern, Category as SecretCategory } from './secret-patterns.js';
50
+ export type { PiiHit, PiiCategory } from './pii-patterns.js';
51
+ export type { NerHit } from './ner.js';
52
+ export { loadNerPipeline, DEFAULT_NER_MODEL, lastNerError } from './ner.js';
@@ -0,0 +1,152 @@
1
+ import { SECRET_PATTERNS } from './secret-patterns.js';
2
+ import { findHighEntropyTokens, replaceHighEntropyTokens } from './entropy.js';
3
+ import { findPiiHits, redactPii } from './pii-patterns.js';
4
+ import { nerPiiAnalyze, redactNer } from './ner.js';
5
+ /**
6
+ * Apply the secrets tier (Gitleaks-style regex). Returns a fresh string
7
+ * with every match replaced by the pattern's replacement template.
8
+ */
9
+ export function redactSecrets(input) {
10
+ let text = input;
11
+ const hits = [];
12
+ for (const pattern of SECRET_PATTERNS) {
13
+ pattern.regex.lastIndex = 0;
14
+ const matches = [];
15
+ let match;
16
+ while ((match = pattern.regex.exec(text))) {
17
+ matches.push({ start: match.index, end: match.index + match[0].length, match: match[0] });
18
+ if (match[0].length === 0)
19
+ pattern.regex.lastIndex++;
20
+ }
21
+ if (matches.length === 0)
22
+ continue;
23
+ text = applyReplacement(text, pattern, matches);
24
+ for (const m of matches) {
25
+ hits.push({
26
+ tier: 'secrets',
27
+ id: pattern.id,
28
+ category: pattern.category,
29
+ start: m.start,
30
+ end: m.end,
31
+ match: m.match,
32
+ });
33
+ }
34
+ }
35
+ return { text, hits };
36
+ }
37
+ /**
38
+ * Full tiered redact: secrets → entropy → PII regex → optional ML.
39
+ */
40
+ export async function redact(input, options = {}) {
41
+ const tiers = {
42
+ secrets: options.tiers?.secrets !== false,
43
+ entropy: options.tiers?.entropy !== false,
44
+ 'pii-regex': options.tiers?.['pii-regex'] !== false,
45
+ 'pii-ml': options.ml === true && options.tiers?.['pii-ml'] !== false,
46
+ };
47
+ const hits = [];
48
+ let text = input;
49
+ if (tiers.secrets) {
50
+ const result = redactSecrets(text);
51
+ text = result.text;
52
+ hits.push(...result.hits);
53
+ }
54
+ if (tiers.entropy) {
55
+ const result = replaceHighEntropyTokens(text, options.entropy ?? {});
56
+ text = result.text;
57
+ for (const hit of result.hits) {
58
+ hits.push({
59
+ tier: 'entropy',
60
+ id: 'high-entropy',
61
+ start: hit.start,
62
+ end: hit.end,
63
+ match: hit.token,
64
+ rationale: `entropy=${hit.entropy.toFixed(2)} bits/char`,
65
+ });
66
+ }
67
+ }
68
+ if (tiers['pii-regex']) {
69
+ const result = redactPii(text);
70
+ text = result.text;
71
+ for (const hit of result.hits) {
72
+ hits.push({
73
+ tier: 'pii-regex',
74
+ id: hit.category,
75
+ category: hit.category,
76
+ start: hit.start,
77
+ end: hit.end,
78
+ match: hit.match,
79
+ ...(hit.rationale ? { rationale: hit.rationale } : {}),
80
+ });
81
+ }
82
+ }
83
+ if (tiers['pii-ml']) {
84
+ const nerHits = await nerPiiAnalyze(text);
85
+ const result = redactNer(text, nerHits);
86
+ text = result.text;
87
+ for (const hit of result.hits) {
88
+ hits.push({
89
+ tier: 'pii-ml',
90
+ id: hit.category,
91
+ category: hit.category,
92
+ start: hit.start,
93
+ end: hit.end,
94
+ match: hit.match,
95
+ rationale: `model score=${hit.score.toFixed(2)}`,
96
+ });
97
+ }
98
+ }
99
+ return { text, hits };
100
+ }
101
+ /**
102
+ * Synchronous redact — runs regex + entropy + PII regex tiers (no ML).
103
+ * Used by hot paths that can't await (proxy recorder, HF push).
104
+ */
105
+ export function redactText(input) {
106
+ const secrets = redactSecrets(input);
107
+ const entropy = replaceHighEntropyTokens(secrets.text);
108
+ const pii = redactPii(entropy.text);
109
+ const aggregated = new Map();
110
+ for (const hit of secrets.hits) {
111
+ aggregated.set(hit.id, (aggregated.get(hit.id) ?? 0) + 1);
112
+ }
113
+ for (const _ of entropy.hits) {
114
+ aggregated.set('high-entropy', (aggregated.get('high-entropy') ?? 0) + 1);
115
+ }
116
+ for (const hit of pii.hits) {
117
+ aggregated.set(hit.category, (aggregated.get(hit.category) ?? 0) + 1);
118
+ }
119
+ return {
120
+ text: pii.text,
121
+ hits: [...aggregated.entries()].map(([pattern, count]) => ({ pattern, count })),
122
+ };
123
+ }
124
+ export function redactObject(value) {
125
+ return JSON.parse(JSON.stringify(value, (_, v) => (typeof v === 'string' ? redactText(v).text : v)));
126
+ }
127
+ export function hasSecret(input) {
128
+ for (const pattern of SECRET_PATTERNS) {
129
+ pattern.regex.lastIndex = 0;
130
+ if (pattern.regex.test(input))
131
+ return true;
132
+ }
133
+ return findHighEntropyTokens(input).length > 0 || findPiiHits(input).length > 0;
134
+ }
135
+ function applyReplacement(text, pattern, matches) {
136
+ if (!pattern.replacement.includes('$')) {
137
+ let cursor = 0;
138
+ const parts = [];
139
+ for (const m of matches) {
140
+ parts.push(text.slice(cursor, m.start));
141
+ parts.push(pattern.replacement);
142
+ cursor = m.end;
143
+ }
144
+ parts.push(text.slice(cursor));
145
+ return parts.join('');
146
+ }
147
+ pattern.regex.lastIndex = 0;
148
+ return text.replace(pattern.regex, pattern.replacement);
149
+ }
150
+ export { SECRET_PATTERNS } from './secret-patterns.js';
151
+ export { loadNerPipeline, DEFAULT_NER_MODEL, lastNerError } from './ner.js';
152
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/redactor/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Optional ML-based PII detection via Transformers.js.
3
+ *
4
+ * Default model: `iiiorg/piiranha-v1-detect-personal-information` —
5
+ * a DeBERTa-v3 fine-tune released by Inferentia Internet Investigation
6
+ * that detects 17 PII categories and works well on English text. The
7
+ * model is lazy-loaded and cached in ~/.cache/huggingface/.
8
+ *
9
+ * `@huggingface/transformers` is declared as an OPTIONAL dependency.
10
+ * If onnxruntime-node fails to install (e.g. unsupported platform),
11
+ * the rest of Skein keeps working — calls to `nerPiiAnalyze` just
12
+ * return an empty hit list and the orchestrator logs a one-time
13
+ * notice.
14
+ *
15
+ * Set SKEIN_PII_MODEL to override the default.
16
+ */
17
+ export declare const DEFAULT_NER_MODEL: string;
18
+ export interface NerHit {
19
+ category: string;
20
+ start: number;
21
+ end: number;
22
+ match: string;
23
+ score: number;
24
+ }
25
+ interface TransformerPipeline {
26
+ (input: string, options?: Record<string, unknown>): Promise<Array<{
27
+ entity?: string;
28
+ entity_group?: string;
29
+ score: number;
30
+ start: number;
31
+ end: number;
32
+ word: string;
33
+ }>>;
34
+ }
35
+ /**
36
+ * Initialize the NER pipeline. Returns undefined if @huggingface/transformers
37
+ * is not installed or the model fails to load. Logs the reason exactly once.
38
+ */
39
+ export declare function loadNerPipeline(): Promise<TransformerPipeline | undefined>;
40
+ /**
41
+ * Run the configured PII NER over `input`. Returns hits with the
42
+ * model's score (0..1). Score threshold defaults to 0.5.
43
+ */
44
+ export declare function nerPiiAnalyze(input: string, minScore?: number): Promise<NerHit[]>;
45
+ export declare function lastNerError(): string | undefined;
46
+ /**
47
+ * Replace NER hits in the input with `[REDACTED:<category>]`.
48
+ */
49
+ export declare function redactNer(input: string, hits: NerHit[]): {
50
+ text: string;
51
+ hits: NerHit[];
52
+ };
53
+ export {};