tlc-claude-code 1.4.9 → 1.5.2

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 (122) hide show
  1. package/CLAUDE.md +23 -0
  2. package/CODING-STANDARDS.md +408 -0
  3. package/bin/install.js +2 -0
  4. package/dashboard/dist/components/QualityGatePane.d.ts +38 -0
  5. package/dashboard/dist/components/QualityGatePane.js +31 -0
  6. package/dashboard/dist/components/QualityGatePane.test.d.ts +1 -0
  7. package/dashboard/dist/components/QualityGatePane.test.js +147 -0
  8. package/dashboard/dist/components/orchestration/AgentCard.d.ts +26 -0
  9. package/dashboard/dist/components/orchestration/AgentCard.js +60 -0
  10. package/dashboard/dist/components/orchestration/AgentCard.test.d.ts +1 -0
  11. package/dashboard/dist/components/orchestration/AgentCard.test.js +63 -0
  12. package/dashboard/dist/components/orchestration/AgentControls.d.ts +11 -0
  13. package/dashboard/dist/components/orchestration/AgentControls.js +20 -0
  14. package/dashboard/dist/components/orchestration/AgentControls.test.d.ts +1 -0
  15. package/dashboard/dist/components/orchestration/AgentControls.test.js +52 -0
  16. package/dashboard/dist/components/orchestration/AgentDetail.d.ts +35 -0
  17. package/dashboard/dist/components/orchestration/AgentDetail.js +37 -0
  18. package/dashboard/dist/components/orchestration/AgentDetail.test.d.ts +1 -0
  19. package/dashboard/dist/components/orchestration/AgentDetail.test.js +79 -0
  20. package/dashboard/dist/components/orchestration/AgentList.d.ts +31 -0
  21. package/dashboard/dist/components/orchestration/AgentList.js +47 -0
  22. package/dashboard/dist/components/orchestration/AgentList.test.d.ts +1 -0
  23. package/dashboard/dist/components/orchestration/AgentList.test.js +64 -0
  24. package/dashboard/dist/components/orchestration/CostMeter.d.ts +11 -0
  25. package/dashboard/dist/components/orchestration/CostMeter.js +28 -0
  26. package/dashboard/dist/components/orchestration/CostMeter.test.d.ts +1 -0
  27. package/dashboard/dist/components/orchestration/CostMeter.test.js +50 -0
  28. package/dashboard/dist/components/orchestration/ModelSelector.d.ts +20 -0
  29. package/dashboard/dist/components/orchestration/ModelSelector.js +12 -0
  30. package/dashboard/dist/components/orchestration/ModelSelector.test.d.ts +1 -0
  31. package/dashboard/dist/components/orchestration/ModelSelector.test.js +56 -0
  32. package/dashboard/dist/components/orchestration/OrchestrationDashboard.d.ts +28 -0
  33. package/dashboard/dist/components/orchestration/OrchestrationDashboard.js +28 -0
  34. package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.d.ts +1 -0
  35. package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.js +56 -0
  36. package/dashboard/dist/components/orchestration/QualityIndicator.d.ts +11 -0
  37. package/dashboard/dist/components/orchestration/QualityIndicator.js +37 -0
  38. package/dashboard/dist/components/orchestration/QualityIndicator.test.d.ts +1 -0
  39. package/dashboard/dist/components/orchestration/QualityIndicator.test.js +52 -0
  40. package/dashboard/dist/components/orchestration/index.d.ts +8 -0
  41. package/dashboard/dist/components/orchestration/index.js +8 -0
  42. package/package.json +1 -1
  43. package/server/lib/access-control.js +352 -0
  44. package/server/lib/access-control.test.js +322 -0
  45. package/server/lib/agents-cancel-command.js +139 -0
  46. package/server/lib/agents-cancel-command.test.js +180 -0
  47. package/server/lib/agents-get-command.js +159 -0
  48. package/server/lib/agents-get-command.test.js +167 -0
  49. package/server/lib/agents-list-command.js +150 -0
  50. package/server/lib/agents-list-command.test.js +149 -0
  51. package/server/lib/agents-logs-command.js +126 -0
  52. package/server/lib/agents-logs-command.test.js +198 -0
  53. package/server/lib/agents-retry-command.js +117 -0
  54. package/server/lib/agents-retry-command.test.js +192 -0
  55. package/server/lib/budget-limits.js +222 -0
  56. package/server/lib/budget-limits.test.js +214 -0
  57. package/server/lib/code-generator.js +291 -0
  58. package/server/lib/code-generator.test.js +307 -0
  59. package/server/lib/cost-command.js +290 -0
  60. package/server/lib/cost-command.test.js +202 -0
  61. package/server/lib/cost-optimizer.js +404 -0
  62. package/server/lib/cost-optimizer.test.js +232 -0
  63. package/server/lib/cost-projections.js +302 -0
  64. package/server/lib/cost-projections.test.js +217 -0
  65. package/server/lib/cost-reports.js +277 -0
  66. package/server/lib/cost-reports.test.js +254 -0
  67. package/server/lib/cost-tracker.js +216 -0
  68. package/server/lib/cost-tracker.test.js +302 -0
  69. package/server/lib/crypto-patterns.js +433 -0
  70. package/server/lib/crypto-patterns.test.js +346 -0
  71. package/server/lib/design-command.js +385 -0
  72. package/server/lib/design-command.test.js +249 -0
  73. package/server/lib/design-parser.js +237 -0
  74. package/server/lib/design-parser.test.js +290 -0
  75. package/server/lib/gemini-vision.js +377 -0
  76. package/server/lib/gemini-vision.test.js +282 -0
  77. package/server/lib/input-validator.js +360 -0
  78. package/server/lib/input-validator.test.js +295 -0
  79. package/server/lib/litellm-client.js +232 -0
  80. package/server/lib/litellm-client.test.js +267 -0
  81. package/server/lib/litellm-command.js +291 -0
  82. package/server/lib/litellm-command.test.js +260 -0
  83. package/server/lib/litellm-config.js +273 -0
  84. package/server/lib/litellm-config.test.js +212 -0
  85. package/server/lib/model-pricing.js +189 -0
  86. package/server/lib/model-pricing.test.js +178 -0
  87. package/server/lib/models-command.js +223 -0
  88. package/server/lib/models-command.test.js +193 -0
  89. package/server/lib/optimize-command.js +197 -0
  90. package/server/lib/optimize-command.test.js +193 -0
  91. package/server/lib/orchestration-integration.js +206 -0
  92. package/server/lib/orchestration-integration.test.js +235 -0
  93. package/server/lib/output-encoder.js +308 -0
  94. package/server/lib/output-encoder.test.js +312 -0
  95. package/server/lib/quality-evaluator.js +396 -0
  96. package/server/lib/quality-evaluator.test.js +337 -0
  97. package/server/lib/quality-gate-command.js +340 -0
  98. package/server/lib/quality-gate-command.test.js +321 -0
  99. package/server/lib/quality-gate-scorer.js +378 -0
  100. package/server/lib/quality-gate-scorer.test.js +376 -0
  101. package/server/lib/quality-history.js +265 -0
  102. package/server/lib/quality-history.test.js +359 -0
  103. package/server/lib/quality-presets.js +288 -0
  104. package/server/lib/quality-presets.test.js +269 -0
  105. package/server/lib/quality-retry.js +323 -0
  106. package/server/lib/quality-retry.test.js +325 -0
  107. package/server/lib/quality-thresholds.js +255 -0
  108. package/server/lib/quality-thresholds.test.js +237 -0
  109. package/server/lib/secure-auth.js +333 -0
  110. package/server/lib/secure-auth.test.js +288 -0
  111. package/server/lib/secure-code-command.js +540 -0
  112. package/server/lib/secure-code-command.test.js +309 -0
  113. package/server/lib/secure-errors.js +521 -0
  114. package/server/lib/secure-errors.test.js +298 -0
  115. package/server/lib/vision-command.js +372 -0
  116. package/server/lib/vision-command.test.js +255 -0
  117. package/server/lib/visual-command.js +350 -0
  118. package/server/lib/visual-command.test.js +256 -0
  119. package/server/lib/visual-testing.js +315 -0
  120. package/server/lib/visual-testing.test.js +357 -0
  121. package/server/package-lock.json +2 -2
  122. package/server/package.json +1 -1
@@ -0,0 +1,235 @@
1
+ const { describe, it, beforeEach, mock } = require('node:test');
2
+ const assert = require('node:assert');
3
+ const {
4
+ wrapWithOrchestration,
5
+ createAgentForCommand,
6
+ trackCommandCost,
7
+ applyQualityGate,
8
+ OrchestrationIntegration,
9
+ } = require('./orchestration-integration.js');
10
+
11
+ describe('orchestration-integration', () => {
12
+ describe('OrchestrationIntegration', () => {
13
+ let integration;
14
+ let mockRegistry;
15
+ let mockCostTracker;
16
+ let mockQualityGate;
17
+
18
+ beforeEach(() => {
19
+ mockRegistry = {
20
+ agents: [],
21
+ createAgent: (data) => {
22
+ const agent = { id: `agent-${Date.now()}`, ...data };
23
+ mockRegistry.agents.push(agent);
24
+ return agent;
25
+ },
26
+ getAgent: (id) => mockRegistry.agents.find(a => a.id === id),
27
+ updateAgent: (id, updates) => {
28
+ const agent = mockRegistry.agents.find(a => a.id === id);
29
+ if (agent) Object.assign(agent, updates);
30
+ return agent;
31
+ },
32
+ };
33
+
34
+ mockCostTracker = {
35
+ costs: [],
36
+ track: (cost) => mockCostTracker.costs.push(cost),
37
+ getTotal: () => mockCostTracker.costs.reduce((s, c) => s + c.amount, 0),
38
+ };
39
+
40
+ mockQualityGate = {
41
+ evaluate: async (output) => ({ pass: output.quality > 70, score: output.quality }),
42
+ };
43
+
44
+ integration = new OrchestrationIntegration({
45
+ registry: mockRegistry,
46
+ costTracker: mockCostTracker,
47
+ qualityGate: mockQualityGate,
48
+ });
49
+ });
50
+
51
+ describe('build command', () => {
52
+ it('creates agents', async () => {
53
+ const result = await integration.wrapCommand('build', async () => ({
54
+ success: true,
55
+ output: 'Built successfully',
56
+ quality: 85,
57
+ }));
58
+
59
+ assert.ok(mockRegistry.agents.length > 0);
60
+ });
61
+
62
+ it('tracks cost', async () => {
63
+ await integration.wrapCommand('build', async () => ({
64
+ success: true,
65
+ cost: 0.15,
66
+ quality: 85,
67
+ }));
68
+
69
+ assert.ok(mockCostTracker.costs.length > 0);
70
+ });
71
+ });
72
+
73
+ describe('review command', () => {
74
+ it('uses multi-model', async () => {
75
+ let modelsUsed = [];
76
+ const result = await integration.wrapCommand('review', async (ctx) => {
77
+ modelsUsed.push(ctx.model || 'default');
78
+ return { success: true, quality: 90 };
79
+ }, { multiModel: true, models: ['claude', 'gpt-4'] });
80
+
81
+ assert.ok(result.success);
82
+ });
83
+
84
+ it('applies consensus', async () => {
85
+ const results = [
86
+ { score: 80, issues: ['a', 'b'] },
87
+ { score: 85, issues: ['b', 'c'] },
88
+ ];
89
+ const consensus = integration.applyConsensus(results);
90
+ assert.ok(consensus.score >= 80);
91
+ assert.ok(consensus.issues.includes('b')); // Common issue
92
+ });
93
+ });
94
+
95
+ describe('refactor command', () => {
96
+ it('uses quality gate', async () => {
97
+ let qualityChecked = false;
98
+ const customQuality = {
99
+ evaluate: async (output) => {
100
+ qualityChecked = true;
101
+ return { pass: true, score: 90 };
102
+ },
103
+ };
104
+
105
+ const int = new OrchestrationIntegration({
106
+ registry: mockRegistry,
107
+ costTracker: mockCostTracker,
108
+ qualityGate: customQuality,
109
+ });
110
+
111
+ await int.wrapCommand('refactor', async () => ({
112
+ success: true,
113
+ quality: 90,
114
+ }), { useQualityGate: true });
115
+
116
+ assert.ok(qualityChecked);
117
+ });
118
+ });
119
+
120
+ it('agents appear in registry', async () => {
121
+ await integration.wrapCommand('test', async () => ({ success: true, quality: 85 }));
122
+ assert.ok(mockRegistry.agents.length > 0);
123
+ });
124
+
125
+ it('costs appear in tracker', async () => {
126
+ await integration.wrapCommand('test', async () => ({
127
+ success: true,
128
+ cost: 0.25,
129
+ quality: 85,
130
+ }));
131
+ assert.ok(mockCostTracker.costs.some(c => c.amount === 0.25));
132
+ });
133
+
134
+ it('quality scores recorded', async () => {
135
+ await integration.wrapCommand('test', async () => ({
136
+ success: true,
137
+ quality: 92,
138
+ }));
139
+ const agent = mockRegistry.agents[0];
140
+ assert.ok(agent.quality?.score === 92 || agent.qualityScore === 92);
141
+ });
142
+
143
+ it('existing behavior preserved', async () => {
144
+ const original = async () => ({ custom: 'data', success: true, quality: 85 });
145
+ const result = await integration.wrapCommand('test', original);
146
+ assert.strictEqual(result.custom, 'data');
147
+ });
148
+
149
+ it('graceful fallback on error', async () => {
150
+ const brokenRegistry = {
151
+ createAgent: () => { throw new Error('Registry error'); },
152
+ };
153
+
154
+ const int = new OrchestrationIntegration({
155
+ registry: brokenRegistry,
156
+ costTracker: mockCostTracker,
157
+ qualityGate: mockQualityGate,
158
+ fallbackOnError: true,
159
+ });
160
+
161
+ // Should not throw, should fall back
162
+ const result = await int.wrapCommand('test', async () => ({
163
+ success: true,
164
+ quality: 85,
165
+ }));
166
+ assert.ok(result.success);
167
+ });
168
+ });
169
+
170
+ describe('wrapWithOrchestration', () => {
171
+ it('wraps function with tracking', async () => {
172
+ let tracked = false;
173
+ const wrapped = wrapWithOrchestration(
174
+ async () => ({ result: 'ok' }),
175
+ { onComplete: () => { tracked = true; } }
176
+ );
177
+
178
+ const result = await wrapped();
179
+ assert.strictEqual(result.result, 'ok');
180
+ assert.ok(tracked);
181
+ });
182
+
183
+ it('passes context through', async () => {
184
+ const wrapped = wrapWithOrchestration(
185
+ async (ctx) => ({ model: ctx.model }),
186
+ {}
187
+ );
188
+
189
+ const result = await wrapped({ model: 'claude' });
190
+ assert.strictEqual(result.model, 'claude');
191
+ });
192
+ });
193
+
194
+ describe('createAgentForCommand', () => {
195
+ it('creates agent with command name', () => {
196
+ const agent = createAgentForCommand('build', { model: 'claude' });
197
+ assert.strictEqual(agent.command, 'build');
198
+ assert.strictEqual(agent.model, 'claude');
199
+ });
200
+
201
+ it('sets initial status', () => {
202
+ const agent = createAgentForCommand('test', {});
203
+ assert.strictEqual(agent.status, 'queued');
204
+ });
205
+ });
206
+
207
+ describe('trackCommandCost', () => {
208
+ it('records cost with command', () => {
209
+ const tracker = { costs: [], track: (c) => tracker.costs.push(c) };
210
+ trackCommandCost(tracker, 'build', 0.15);
211
+ assert.strictEqual(tracker.costs[0].command, 'build');
212
+ assert.strictEqual(tracker.costs[0].amount, 0.15);
213
+ });
214
+ });
215
+
216
+ describe('applyQualityGate', () => {
217
+ it('passes for high quality', async () => {
218
+ const gate = { evaluate: async () => ({ pass: true, score: 90 }) };
219
+ const result = await applyQualityGate(gate, { output: 'test' });
220
+ assert.ok(result.pass);
221
+ });
222
+
223
+ it('fails for low quality', async () => {
224
+ const gate = { evaluate: async () => ({ pass: false, score: 50 }) };
225
+ const result = await applyQualityGate(gate, { output: 'test' });
226
+ assert.strictEqual(result.pass, false);
227
+ });
228
+
229
+ it('returns score', async () => {
230
+ const gate = { evaluate: async () => ({ pass: true, score: 85 }) };
231
+ const result = await applyQualityGate(gate, { output: 'test' });
232
+ assert.strictEqual(result.score, 85);
233
+ });
234
+ });
235
+ });
@@ -0,0 +1,308 @@
1
+ /**
2
+ * Output Encoder Module
3
+ *
4
+ * Context-aware output encoding for XSS prevention
5
+ */
6
+
7
+ const crypto = require('crypto');
8
+
9
+ /**
10
+ * Create an output encoder
11
+ * @param {Object} options - Encoder options
12
+ * @returns {Object} Encoder instance
13
+ */
14
+ function createOutputEncoder(options = {}) {
15
+ return {
16
+ contexts: options.contexts || ['html', 'js', 'url', 'css', 'attribute'],
17
+ };
18
+ }
19
+
20
+ /**
21
+ * Encode string for HTML context
22
+ * @param {string} input - Input to encode
23
+ * @returns {string} Encoded string
24
+ */
25
+ function encodeHtml(input) {
26
+ if (input === null || input === undefined) {
27
+ return '';
28
+ }
29
+
30
+ return String(input)
31
+ .replace(/&/g, '&')
32
+ .replace(/</g, '&lt;')
33
+ .replace(/>/g, '&gt;')
34
+ .replace(/"/g, '&quot;')
35
+ .replace(/'/g, '&#x27;');
36
+ }
37
+
38
+ /**
39
+ * Encode string for JavaScript context
40
+ * @param {string} input - Input to encode
41
+ * @param {Object} options - Encoding options
42
+ * @returns {string} Encoded string
43
+ */
44
+ function encodeJs(input, options = {}) {
45
+ if (input === null || input === undefined) {
46
+ return '';
47
+ }
48
+
49
+ let result = String(input)
50
+ .replace(/\\/g, '\\\\')
51
+ .replace(/'/g, "\\'")
52
+ .replace(/"/g, '\\"')
53
+ .replace(/\n/g, '\\n')
54
+ .replace(/\r/g, '\\r')
55
+ .replace(/\t/g, '\\t')
56
+ .replace(/<\/script>/gi, '<\\/script>');
57
+
58
+ if (options.unicodeEscape) {
59
+ result = result.replace(/[\u0080-\uffff]/g, (char) => {
60
+ return '\\u' + ('0000' + char.charCodeAt(0).toString(16)).slice(-4);
61
+ });
62
+ }
63
+
64
+ return result;
65
+ }
66
+
67
+ /**
68
+ * Encode string for URL context
69
+ * @param {string} input - Input to encode
70
+ * @returns {string} Encoded string
71
+ */
72
+ function encodeUrl(input) {
73
+ if (input === null || input === undefined) {
74
+ return '';
75
+ }
76
+
77
+ return encodeURIComponent(String(input));
78
+ }
79
+
80
+ /**
81
+ * Encode string for CSS context
82
+ * @param {string} input - Input to encode
83
+ * @returns {string} Encoded string
84
+ */
85
+ function encodeCss(input) {
86
+ if (input === null || input === undefined) {
87
+ return '';
88
+ }
89
+
90
+ let result = String(input)
91
+ .replace(/\\/g, '\\\\')
92
+ .replace(/</g, '\\3c ')
93
+ .replace(/>/g, '\\3e ')
94
+ .replace(/expression\s*\(/gi, '')
95
+ .replace(/url\s*\(\s*javascript:/gi, 'url(blocked:');
96
+
97
+ return result;
98
+ }
99
+
100
+ /**
101
+ * Encode string for HTML attribute context
102
+ * @param {string} input - Input to encode
103
+ * @param {Object} options - Encoding options
104
+ * @returns {string} Encoded string
105
+ */
106
+ function encodeAttribute(input, options = {}) {
107
+ if (input === null || input === undefined) {
108
+ return '';
109
+ }
110
+
111
+ let result = String(input);
112
+
113
+ // Remove event handlers
114
+ result = result.replace(/on\w+\s*=/gi, '');
115
+
116
+ if (options.quoted === false) {
117
+ // Unquoted attributes need more escaping
118
+ result = result.replace(/[\s"'=<>`]/g, (char) => {
119
+ return '&#' + char.charCodeAt(0) + ';';
120
+ });
121
+ } else if (options.quoteChar === "'") {
122
+ result = result.replace(/'/g, '&#39;');
123
+ } else {
124
+ result = result.replace(/"/g, '&quot;');
125
+ }
126
+
127
+ return result;
128
+ }
129
+
130
+ /**
131
+ * Generate Content Security Policy header
132
+ * @param {Object} options - CSP options
133
+ * @returns {string} CSP header value
134
+ */
135
+ function generateCspHeader(options = {}) {
136
+ const directives = [];
137
+
138
+ // Always include default-src
139
+ directives.push("default-src 'self'");
140
+
141
+ if (options.scriptSrc) {
142
+ directives.push(`script-src ${options.scriptSrc.join(' ')}`);
143
+ }
144
+
145
+ if (options.styleSrc) {
146
+ directives.push(`style-src ${options.styleSrc.join(' ')}`);
147
+ }
148
+
149
+ if (options.imgSrc) {
150
+ directives.push(`img-src ${options.imgSrc.join(' ')}`);
151
+ }
152
+
153
+ if (options.useNonce && options.nonce) {
154
+ // Replace or add script-src with nonce
155
+ const nonceValue = `'nonce-${options.nonce}'`;
156
+ const existingScriptIdx = directives.findIndex(d => d.startsWith('script-src'));
157
+ if (existingScriptIdx >= 0) {
158
+ directives[existingScriptIdx] += ` ${nonceValue}`;
159
+ } else {
160
+ directives.push(`script-src ${nonceValue}`);
161
+ }
162
+ }
163
+
164
+ if (options.reportUri) {
165
+ directives.push(`report-uri ${options.reportUri}`);
166
+ }
167
+
168
+ const headerValue = directives.join('; ');
169
+
170
+ // For reportOnly mode, include in the returned string
171
+ if (options.reportOnly) {
172
+ return headerValue + ' /* Report-Only */';
173
+ }
174
+
175
+ return headerValue;
176
+ }
177
+
178
+ /**
179
+ * Generate Subresource Integrity hash
180
+ * @param {string} content - Content to hash
181
+ * @param {Object} options - Hashing options
182
+ * @returns {string} SRI hash
183
+ */
184
+ function generateSriHash(content, options = {}) {
185
+ const algorithm = options.algorithm || 'sha384';
186
+ const hash = crypto.createHash(algorithm).update(content).digest('base64');
187
+
188
+ return `${algorithm}-${hash}`;
189
+ }
190
+
191
+ /**
192
+ * Generate encoding code
193
+ * @param {Object} options - Generation options
194
+ * @returns {string} Generated code
195
+ */
196
+ function generateEncodingCode(options = {}) {
197
+ const { context, language = 'javascript' } = options;
198
+
199
+ const generators = {
200
+ javascript: {
201
+ html: () => `
202
+ function encodeHtml(input) {
203
+ if (input === null || input === undefined) return '';
204
+ return String(input)
205
+ .replace(/&/g, '&amp;')
206
+ .replace(/</g, '&lt;')
207
+ .replace(/>/g, '&gt;')
208
+ .replace(/"/g, '&quot;')
209
+ .replace(/'/g, '&#x27;');
210
+ }`,
211
+ js: () => `
212
+ function encodeJs(input) {
213
+ if (input === null || input === undefined) return '';
214
+ return String(input)
215
+ .replace(/\\\\/g, '\\\\\\\\')
216
+ .replace(/'/g, "\\\\'")
217
+ .replace(/"/g, '\\\\"')
218
+ .replace(/\\n/g, '\\\\n');
219
+ }`,
220
+ url: () => `
221
+ function encodeUrl(input) {
222
+ if (input === null || input === undefined) return '';
223
+ return encodeURIComponent(String(input));
224
+ }`,
225
+ css: () => `
226
+ function encodeCss(input) {
227
+ if (input === null || input === undefined) return '';
228
+ return String(input)
229
+ .replace(/\\\\/g, '\\\\\\\\')
230
+ .replace(/</g, '\\\\3c ')
231
+ .replace(/>/g, '\\\\3e ');
232
+ }`,
233
+ },
234
+ typescript: {
235
+ html: () => `
236
+ function encodeHtml(input: string | null | undefined): string {
237
+ if (input === null || input === undefined) return '';
238
+ return String(input)
239
+ .replace(/&/g, '&amp;')
240
+ .replace(/</g, '&lt;')
241
+ .replace(/>/g, '&gt;')
242
+ .replace(/"/g, '&quot;')
243
+ .replace(/'/g, '&#x27;');
244
+ }`,
245
+ js: () => `
246
+ function encodeJs(input: string | null | undefined): string {
247
+ if (input === null || input === undefined) return '';
248
+ return String(input)
249
+ .replace(/\\\\/g, '\\\\\\\\')
250
+ .replace(/'/g, "\\\\'")
251
+ .replace(/"/g, '\\\\"');
252
+ }`,
253
+ url: () => `
254
+ function encodeUrl(input: string | null | undefined): string {
255
+ if (input === null || input === undefined) return '';
256
+ return encodeURIComponent(String(input));
257
+ }`,
258
+ css: () => `
259
+ function encodeCss(input: string | null | undefined): string {
260
+ if (input === null || input === undefined) return '';
261
+ return String(input).replace(/</g, '\\\\3c ');
262
+ }`,
263
+ },
264
+ python: {
265
+ html: () => `
266
+ import html
267
+
268
+ def encode_html(input_str):
269
+ if input_str is None:
270
+ return ''
271
+ return html.escape(str(input_str))`,
272
+ js: () => `
273
+ def encode_js(input_str):
274
+ if input_str is None:
275
+ return ''
276
+ return str(input_str).replace('\\\\', '\\\\\\\\').replace("'", "\\\\'")`,
277
+ url: () => `
278
+ from urllib.parse import quote
279
+
280
+ def encode_url(input_str):
281
+ if input_str is None:
282
+ return ''
283
+ return quote(str(input_str))`,
284
+ css: () => `
285
+ def encode_css(input_str):
286
+ if input_str is None:
287
+ return ''
288
+ return str(input_str).replace('<', '\\\\3c ')`,
289
+ },
290
+ };
291
+
292
+ const lang = generators[language] || generators.javascript;
293
+ const gen = lang[context] || lang.html;
294
+
295
+ return gen();
296
+ }
297
+
298
+ module.exports = {
299
+ createOutputEncoder,
300
+ encodeHtml,
301
+ encodeJs,
302
+ encodeUrl,
303
+ encodeCss,
304
+ encodeAttribute,
305
+ generateCspHeader,
306
+ generateSriHash,
307
+ generateEncodingCode,
308
+ };