freshcontext-mcp 0.3.16 → 0.3.17

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.
@@ -1,196 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * freshcontext-validate
5
- * CLI validator for FreshContext-compatible responses
6
- * Spec: https://github.com/PrinceGabriel-lgtm/freshcontext-mcp/blob/main/FRESHCONTEXT_SPEC.md
7
- *
8
- * Usage:
9
- * node freshcontext-validate.js '<json_string>'
10
- * node freshcontext-validate.js --stdin
11
- * node freshcontext-validate.js --file <path>
12
- * node freshcontext-validate.js --help
13
- *
14
- * Compliance levels:
15
- * FreshContext-scored ★★★ — JSON form + numeric freshness_score
16
- * FreshContext-compatible ★★ — JSON/envelope with retrieved_at + confidence
17
- * FreshContext-aware ★ — retrieved_at only, no confidence
18
- * FAIL — missing required fields
19
- */
20
-
21
- const REQUIRED_JSON_FIELDS = ['source_url', 'retrieved_at', 'freshness_confidence', 'adapter'];
22
- const VALID_CONFIDENCE = ['high', 'medium', 'low'];
23
- const ENVELOPE_START = '[FRESHCONTEXT]';
24
- const ENVELOPE_END = '[/FRESHCONTEXT]';
25
- const REQUIRED_ENVELOPE_FIELDS = ['Source:', 'Published:', 'Retrieved:', 'Confidence:'];
26
-
27
- const g = s => `\x1b[32m${s}\x1b[0m`;
28
- const r = s => `\x1b[31m${s}\x1b[0m`;
29
- const y = s => `\x1b[33m${s}\x1b[0m`;
30
- const b = s => `\x1b[34m${s}\x1b[0m`;
31
- const d = s => `\x1b[2m${s}\x1b[0m`;
32
- const bold = s => `\x1b[1m${s}\x1b[0m`;
33
-
34
- const pass = msg => ({ ok: true, warn: false, msg: ` ${g('✓')} ${msg}` });
35
- const fail = msg => ({ ok: false, warn: false, msg: ` ${r('✕')} ${msg}` });
36
- const warn = msg => ({ ok: true, warn: true, msg: ` ${y('!')} ${msg}` });
37
-
38
- function validateJSON(obj) {
39
- const results = [];
40
- const fc = obj.freshcontext;
41
- if (!fc) return [fail('No "freshcontext" key found in JSON response')];
42
-
43
- for (const field of REQUIRED_JSON_FIELDS) {
44
- if (fc[field] === undefined || fc[field] === null) {
45
- results.push(fail(`Missing required field: freshcontext.${field}`));
46
- } else {
47
- results.push(pass(`freshcontext.${field} = ${d(String(fc[field]).slice(0, 80))}`));
48
- }
49
- }
50
-
51
- if (fc.retrieved_at) {
52
- const dt = new Date(fc.retrieved_at);
53
- if (isNaN(dt.getTime())) {
54
- results.push(fail(`retrieved_at is not valid ISO 8601: "${fc.retrieved_at}"`));
55
- } else {
56
- results.push(pass(`retrieved_at is valid ISO 8601`));
57
- }
58
- }
59
-
60
- if (fc.freshness_confidence && !VALID_CONFIDENCE.includes(fc.freshness_confidence)) {
61
- results.push(fail(`freshness_confidence must be high, medium, or low. Got: "${fc.freshness_confidence}"`));
62
- }
63
-
64
- if (fc.freshness_score !== undefined && fc.freshness_score !== null) {
65
- if (typeof fc.freshness_score !== 'number' || fc.freshness_score < 0 || fc.freshness_score > 100) {
66
- results.push(fail(`freshness_score must be 0-100. Got: ${fc.freshness_score}`));
67
- } else {
68
- const col = fc.freshness_score >= 70 ? g : fc.freshness_score >= 50 ? y : r;
69
- results.push(pass(`freshness_score: ${col(fc.freshness_score + '/100')}`));
70
- }
71
- } else {
72
- results.push(warn('freshness_score not present (optional — required for ★★★ level)'));
73
- }
74
-
75
- return results;
76
- }
77
-
78
- function validateEnvelope(text) {
79
- const results = [];
80
- if (!text.includes(ENVELOPE_START)) return [fail(`Missing opening tag: ${ENVELOPE_START}`)];
81
- if (!text.includes(ENVELOPE_END)) return [fail(`Missing closing tag: ${ENVELOPE_END}`)];
82
- results.push(pass('Envelope tags present'));
83
-
84
- const start = text.indexOf(ENVELOPE_START) + ENVELOPE_START.length;
85
- const end = text.indexOf(ENVELOPE_END);
86
- const envelope = text.slice(start, end);
87
-
88
- for (const field of REQUIRED_ENVELOPE_FIELDS) {
89
- if (!envelope.includes(field)) {
90
- results.push(fail(`Missing field: ${field}`));
91
- } else {
92
- const line = envelope.split('\n').find(l => l.trim().startsWith(field));
93
- results.push(pass(`${field.replace(':', '')} — ${d((line || '').trim().slice(0, 70))}`));
94
- }
95
- }
96
-
97
- const confLine = envelope.split('\n').find(l => l.trim().startsWith('Confidence:'));
98
- if (confLine) {
99
- const confVal = confLine.split(':')[1]?.trim().toLowerCase();
100
- if (!VALID_CONFIDENCE.includes(confVal)) {
101
- results.push(fail(`Confidence must be high, medium, or low. Got: "${confVal}"`));
102
- }
103
- }
104
-
105
- return results;
106
- }
107
-
108
- function complianceLevel(results, hasScore, mode) {
109
- const failed = results.filter(res => !res.ok).length;
110
- if (failed > 0) return { level: 'FAIL', colour: r };
111
- if (mode === 'json' && hasScore) return { level: 'FreshContext-scored \u2605\u2605\u2605', colour: g };
112
- return { level: 'FreshContext-compatible \u2605\u2605', colour: g };
113
- }
114
-
115
- function validateString(input, label) {
116
- console.log(`\n${bold('freshcontext-validate')} ${d('v1.0.0')}`);
117
- console.log(d('\u2500'.repeat(52)));
118
- console.log(`${b('Input:')} ${d(label)}\n`);
119
-
120
- let results = [];
121
- let hasScore = false;
122
- let mode = 'envelope';
123
-
124
- try {
125
- const parsed = JSON.parse(input);
126
- mode = 'json';
127
- console.log(`${d('Mode:')} ${b('JSON structured form')}\n`);
128
- results = validateJSON(parsed);
129
- hasScore = parsed?.freshcontext?.freshness_score !== undefined
130
- && parsed?.freshcontext?.freshness_score !== null;
131
- } catch {
132
- if (input.includes(ENVELOPE_START)) {
133
- console.log(`${d('Mode:')} ${b('Text envelope')}\n`);
134
- results = validateEnvelope(input);
135
- } else {
136
- console.log(r('✕ Input is neither valid JSON nor a FreshContext text envelope.\n'));
137
- console.log(d('Expected:'));
138
- console.log(` ${d('JSON:')} {"freshcontext": {"source_url": "...", "retrieved_at": "...", ...}}`);
139
- console.log(` ${d('Envelope:')} [FRESHCONTEXT]\\nSource: ...\\n[/FRESHCONTEXT]`);
140
- process.exit(1);
141
- }
142
- }
143
-
144
- results.forEach(res => console.log(res.msg));
145
-
146
- const passed = results.filter(res => res.ok && !res.warn).length;
147
- const failed = results.filter(res => !res.ok).length;
148
- const warned = results.filter(res => res.warn).length;
149
-
150
- const { level, colour } = complianceLevel(results, hasScore, mode);
151
-
152
- console.log(`\n${d('\u2500'.repeat(52))}`);
153
- console.log(`${d('Checks:')} ${g(passed + ' passed')}${warned ? ', ' + y(warned + ' warnings') : ''}${failed ? ', ' + r(failed + ' failed') : ''}`);
154
- console.log(`${d('Result:')} ${colour(bold(level))}\n`);
155
-
156
- if (failed > 0) process.exit(1);
157
- }
158
-
159
- const args = process.argv.slice(2);
160
-
161
- if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
162
- console.log(`
163
- ${bold('freshcontext-validate')} ${d('v1.0.0')}
164
- Validates FreshContext-compatible responses against the spec.
165
-
166
- ${bold('USAGE')}
167
- node freshcontext-validate.js ${d('<json_or_envelope_string>')}
168
- node freshcontext-validate.js --file ${d('<path>')}
169
- node freshcontext-validate.js --stdin
170
- echo '...' | node freshcontext-validate.js --stdin
171
-
172
- ${bold('COMPLIANCE LEVELS')}
173
- ${g('FreshContext-scored \u2605\u2605\u2605')} Full JSON form + numeric freshness_score
174
- ${g('FreshContext-compatible \u2605\u2605')} JSON/envelope with retrieved_at + confidence
175
- ${r('FAIL')} Missing required fields
176
-
177
- ${bold('SPEC')}
178
- https://freshcontext-site.pages.dev/spec.html
179
- https://github.com/PrinceGabriel-lgtm/freshcontext-mcp/blob/main/FRESHCONTEXT_SPEC.md
180
- `);
181
- process.exit(0);
182
- }
183
-
184
- if (args[0] === '--stdin') {
185
- let data = '';
186
- process.stdin.setEncoding('utf8');
187
- process.stdin.on('data', chunk => data += chunk);
188
- process.stdin.on('end', () => validateString(data.trim(), 'stdin'));
189
- } else if (args[0] === '--file') {
190
- const path = args[1];
191
- if (!path) { console.error(r('--file requires a path argument')); process.exit(1); }
192
- const fs = require('fs');
193
- validateString(fs.readFileSync(path, 'utf8').trim(), path);
194
- } else {
195
- validateString(args[0], 'inline input');
196
- }
package/time-check.ps1 DELETED
@@ -1,46 +0,0 @@
1
- # time-check.ps1 — Print a session header for Claude conversations
2
- # Usage: ./time-check.ps1
3
- # Then paste the output at the start of your message to Claude.
4
-
5
- $now = Get-Date
6
- $utc = $now.ToUniversalTime()
7
- $dayOfWeek = $now.DayOfWeek
8
- $weekNumber = (Get-Culture).Calendar.GetWeekOfYear($now, [System.Globalization.CalendarWeekRule]::FirstFourDayWeek, [DayOfWeek]::Monday)
9
-
10
- # Sun position approximation for Grootfontein (-19.57°, 18.12°)
11
- # Simple model: sunrise ~6:00, sunset ~18:30 (varies seasonally, close enough for vibes)
12
- $hour = $now.Hour
13
- $timeOfDay = switch ($hour) {
14
- {$_ -lt 5} { "deep night" }
15
- {$_ -lt 7} { "before dawn" }
16
- {$_ -lt 9} { "early morning" }
17
- {$_ -lt 12} { "morning" }
18
- {$_ -lt 14} { "midday" }
19
- {$_ -lt 17} { "afternoon" }
20
- {$_ -lt 19} { "early evening" }
21
- {$_ -lt 22} { "evening" }
22
- default { "late night" }
23
- }
24
-
25
- # US East Coast equivalent (CAT is UTC+2, ET is UTC-4 or UTC-5 depending on DST)
26
- # Simple approximation: CAT - 6h ≈ ET in summer, CAT - 7h ≈ ET in winter
27
- $etHour = ($hour - 6 + 24) % 24
28
- $etTime = "{0:D2}:{1:D2} ET" -f $etHour, $now.Minute
29
-
30
- # Output
31
- Write-Host ""
32
- Write-Host "═══════════════════════════════════════════" -ForegroundColor Cyan
33
- Write-Host " Session header for Claude (paste this in)" -ForegroundColor Cyan
34
- Write-Host "═══════════════════════════════════════════" -ForegroundColor Cyan
35
- Write-Host ""
36
- Write-Host "Local time: $($now.ToString('yyyy-MM-dd HH:mm')) CAT ($timeOfDay), $dayOfWeek" -ForegroundColor Green
37
- Write-Host "UTC: $($utc.ToString('yyyy-MM-dd HH:mm')) UTC" -ForegroundColor Gray
38
- Write-Host "ET (US): ~$etTime" -ForegroundColor Gray
39
- Write-Host "Week: Week $weekNumber of $($now.Year)" -ForegroundColor Gray
40
- Write-Host ""
41
-
42
- # Also copy to clipboard so you don't have to retype it
43
- $header = "[$($now.ToString('yyyy-MM-dd HH:mm')) CAT, $dayOfWeek $timeOfDay]"
44
- $header | Set-Clipboard
45
- Write-Host "→ Copied to clipboard: $header" -ForegroundColor Yellow
46
- Write-Host ""