deadbyte-mcp 0.15.18 → 0.15.19

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 (55) hide show
  1. package/MANIFEST.SHA256 +54 -32
  2. package/PROVENANCE.json +13 -7
  3. package/README.txt +7 -7
  4. package/RELEASE.json +25 -6
  5. package/SBOM.cdx.json +3 -3
  6. package/TEST-MATRIX.json +26 -7
  7. package/controller/deadbyte-controller.ps1 +56 -1
  8. package/docs/COMMANDER_EXHAUSTIVE_SURFACE.json +247 -44
  9. package/docs/COMMANDER_REFERENCE_0_2_51.json +46 -0
  10. package/docs/R47-PROVENANCE.json +11 -0
  11. package/docs/R47-RELEASE.json +58 -0
  12. package/docs/plans/2026-09-27-desktop-commander-drop-in-parity.md +92 -0
  13. package/package.json +4 -2
  14. package/proof/commander-closure-20260927/CONTINUATION.json +43 -0
  15. package/proof/commander-closure-20260927/RESULT.json +30 -0
  16. package/proof/commander-closure-20260927/SEARCH-CONTINUATION.json +30 -0
  17. package/proof/commander-closure-20260927/XLS-REFERENCE-PROBE.json +13 -0
  18. package/scripts/deploy-r48.mjs +86 -0
  19. package/scripts/full-regression-r48.mjs +28 -0
  20. package/src/coding-search.mjs +65 -7
  21. package/src/commander-compat-contracts.mjs +10 -0
  22. package/src/commander-core-bridge.mjs +2 -2
  23. package/src/commander-extended-bridge-r47.mjs +116 -12
  24. package/src/commander-extended-runtime.mjs +33 -6
  25. package/src/commander-external-process-r48.mjs +125 -0
  26. package/src/commander-observability.mjs +66 -16
  27. package/src/commander-office-reader-r48.mjs +548 -0
  28. package/src/commander-pdf-r48.mjs +448 -0
  29. package/src/commander-ui-resources.mjs +26 -0
  30. package/src/commander-url-reader-r48.mjs +201 -0
  31. package/src/commander-wire-adapter-r47.mjs +595 -101
  32. package/src/deadbyte-cli.mjs +73 -8
  33. package/src/mcp-server.mjs +9 -2
  34. package/src/observation-output-contracts.mjs +2 -1
  35. package/src/observation-runtime.mjs +4 -2
  36. package/src/version.mjs +1 -1
  37. package/test/coding-search-r36.test.mjs +71 -0
  38. package/test/commander-core-bridge-r47.test.mjs +32 -3
  39. package/test/commander-exhaustive-surface-r47.test.mjs +5 -1
  40. package/test/commander-extended-runtime-r47.test.mjs +8 -0
  41. package/test/commander-extended-wire-r48.test.mjs +98 -0
  42. package/test/commander-external-process-r48.test.mjs +76 -0
  43. package/test/commander-office-reader-r48.test.mjs +152 -0
  44. package/test/commander-pdf-r48.test.mjs +103 -0
  45. package/test/commander-protocol-r47.test.mjs +23 -1
  46. package/test/commander-reference-contract-r48.test.mjs +26 -0
  47. package/test/commander-schema-differential-r48.test.mjs +88 -0
  48. package/test/commander-url-reader-r48.test.mjs +68 -0
  49. package/test/commander-wire-contract-r47.test.mjs +461 -41
  50. package/test/controller-r212.test.mjs +23 -2
  51. package/test/observation-runtime.test.mjs +4 -3
  52. package/test/prompts-resources.test.mjs +1 -1
  53. package/test/r47-release-contract.test.mjs +9 -8
  54. package/test/r48-release-contract.test.mjs +36 -0
  55. package/test/remote-session-cli.test.mjs +34 -1
@@ -0,0 +1,201 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { lookup as dnsLookup } from 'node:dns/promises';
3
+ import http from 'node:http';
4
+ import https from 'node:https';
5
+ import net from 'node:net';
6
+
7
+ const REDIRECTS=new Set([301,302,303,307,308]);
8
+ const DEFAULT_MAX_BYTES=8*1024*1024;
9
+ const DEFAULT_TIMEOUT_MS=15000;
10
+ const DEFAULT_MAX_REDIRECTS=5;
11
+
12
+ function sha256(bytes){return createHash('sha256').update(bytes).digest('hex');}
13
+ function ipv4Number(address){
14
+ const parts=address.split('.').map(Number);
15
+ if(parts.length!==4||parts.some(x=>!Number.isInteger(x)||x<0||x>255)) return null;
16
+ return (((parts[0]*256+parts[1])*256+parts[2])*256+parts[3])>>>0;
17
+ }
18
+ function inV4(value,cidrBase,prefix){
19
+ const base=ipv4Number(cidrBase); if(base===null) return false;
20
+ if(prefix===0) return true;
21
+ const mask=(0xffffffff << (32-prefix))>>>0;
22
+ return (value&mask)===(base&mask);
23
+ }
24
+ function ipv6BigInt(address){
25
+ let value=address.toLowerCase();
26
+ const zone=value.indexOf('%'); if(zone>=0) value=value.slice(0,zone);
27
+ if(value.includes('.')){
28
+ const lastColon=value.lastIndexOf(':');
29
+ const v4=ipv4Number(value.slice(lastColon+1));
30
+ if(v4===null) return null;
31
+ value=value.slice(0,lastColon)+':'+((v4>>>16)&0xffff).toString(16)+':'+(v4&0xffff).toString(16);
32
+ }
33
+ const halves=value.split('::');
34
+ if(halves.length>2) return null;
35
+ const left=halves[0]?halves[0].split(':'):[];
36
+ const right=halves.length===2&&halves[1]?halves[1].split(':'):[];
37
+ if(left.some(x=>!/^[0-9a-f]{1,4}$/.test(x))||right.some(x=>!/^[0-9a-f]{1,4}$/.test(x))) return null;
38
+ const missing=8-left.length-right.length;
39
+ if((halves.length===1&&missing!==0)||(halves.length===2&&missing<1)) return null;
40
+ const groups=[...left,...Array(Math.max(0,missing)).fill('0'),...right];
41
+ if(groups.length!==8) return null;
42
+ let out=0n;
43
+ for(const group of groups) out=(out<<16n)|BigInt(parseInt(group,16));
44
+ return out;
45
+ }
46
+ function v6In(value,base,prefix){
47
+ const b=ipv6BigInt(base); if(b===null) return false;
48
+ if(prefix===0) return true;
49
+ const shift=128n-BigInt(prefix);
50
+ return (value>>shift)===(b>>shift);
51
+ }
52
+
53
+ export function isPublicInternetAddress(address){
54
+ const family=net.isIP(address);
55
+ if(family===4){
56
+ const v=ipv4Number(address); if(v===null) return false;
57
+ const denied=[
58
+ ['0.0.0.0',8],['10.0.0.0',8],['100.64.0.0',10],['127.0.0.0',8],
59
+ ['169.254.0.0',16],['172.16.0.0',12],['192.0.0.0',24],['192.0.2.0',24],
60
+ ['192.88.99.0',24],['192.168.0.0',16],['198.18.0.0',15],['198.51.100.0',24],
61
+ ['203.0.113.0',24],['224.0.0.0',4],['240.0.0.0',4]
62
+ ];
63
+ return !denied.some(([base,prefix])=>inV4(v,base,prefix));
64
+ }
65
+ if(family===6){
66
+ const v=ipv6BigInt(address); if(v===null) return false;
67
+ if(v===0n||v===1n) return false;
68
+ if(v6In(v,'::ffff:0:0',96)){
69
+ const lower=Number(v&0xffffffffn)>>>0;
70
+ const mapped=[lower>>>24,(lower>>>16)&255,(lower>>>8)&255,lower&255].join('.');
71
+ return isPublicInternetAddress(mapped);
72
+ }
73
+ const denied=[
74
+ ['fc00::',7],['fe80::',10],['ff00::',8],['2001:db8::',32],['2001:10::',28]
75
+ ];
76
+ return !denied.some(([base,prefix])=>v6In(v,base,prefix));
77
+ }
78
+ return false;
79
+ }
80
+
81
+ async function defaultLookup(hostname){
82
+ if(net.isIP(hostname)) return [{address:hostname,family:net.isIP(hostname)}];
83
+ return dnsLookup(hostname,{all:true,verbatim:true});
84
+ }
85
+ function normalizeHeaders(headers){
86
+ const out={};
87
+ for(const [key,value] of Object.entries(headers??{})){
88
+ if(Array.isArray(value)) out[key.toLowerCase()]=value.join(', ');
89
+ else if(value!==undefined) out[key.toLowerCase()]=String(value);
90
+ }
91
+ return out;
92
+ }
93
+ function defaultRequestOnce({url,address,maxBytes,timeoutMs}){
94
+ return new Promise((resolve,reject)=>{
95
+ const client=url.protocol==='https:'?https:http;
96
+ let settled=false,total=0;
97
+ const chunks=[];
98
+ const finish=(fn,value)=>{if(settled)return;settled=true;clearTimeout(timer);fn(value);};
99
+ const pinnedLookup=(hostname,options,callback)=>{
100
+ const cb=typeof options==='function'?options:callback;
101
+ cb(null,address.address,address.family);
102
+ };
103
+ const req=client.request({
104
+ protocol:url.protocol,hostname:url.hostname,port:url.port||undefined,
105
+ method:'GET',path:url.pathname+url.search,
106
+ headers:{'Accept-Encoding':'identity','User-Agent':'DEADBYTE-MCP-Commander-Compat-R48'},
107
+ lookup:pinnedLookup
108
+ },res=>{
109
+ res.on('data',chunk=>{
110
+ if(settled)return;
111
+ total+=chunk.length;
112
+ if(total>maxBytes){
113
+ req.destroy();
114
+ finish(reject,new Error(`URL response exceeds ${maxBytes} byte limit`));
115
+ return;
116
+ }
117
+ chunks.push(Buffer.from(chunk));
118
+ });
119
+ res.once('end',()=>finish(resolve,{status:res.statusCode??0,headers:normalizeHeaders(res.headers),body:Buffer.concat(chunks)}));
120
+ res.once('error',error=>finish(reject,error));
121
+ });
122
+ req.once('error',error=>finish(reject,error));
123
+ const timer=setTimeout(()=>{req.destroy();finish(reject,new Error(`URL fetch timed out after ${timeoutMs}ms`));},timeoutMs);
124
+ timer.unref?.();
125
+ req.end();
126
+ });
127
+ }
128
+
129
+ function validateUrl(input){
130
+ let url;
131
+ try{url=new URL(input);}catch{throw new Error('URL is invalid');}
132
+ if(url.protocol!=='http:'&&url.protocol!=='https:') throw new Error('URL protocol must be http or https');
133
+ if(url.username||url.password) throw new Error('URL credentials are not allowed');
134
+ if(url.hostname.length<1||url.hostname.length>253) throw new Error('URL hostname invalid');
135
+ return url;
136
+ }
137
+ async function resolvePublic(hostname,lookupFn){
138
+ const rows=await lookupFn(hostname);
139
+ if(!Array.isArray(rows)||rows.length<1) throw new Error('URL hostname resolved to no addresses');
140
+ const normalized=rows.map(row=>({address:String(row.address??''),family:Number(row.family??net.isIP(String(row.address??'')))}));
141
+ if(normalized.some(row=>!isPublicInternetAddress(row.address))) throw new Error('URL hostname resolved to a non-public address');
142
+ return normalized[0];
143
+ }
144
+ function contentKind(contentType,url){
145
+ const mime=String(contentType??'text/plain').split(';',1)[0].trim().toLowerCase()||'text/plain';
146
+ if(mime==='application/pdf'||url.pathname.toLowerCase().endsWith('.pdf')) return {mime,kind:'pdf'};
147
+ if(mime.startsWith('image/')) return {mime,kind:'image'};
148
+ if(mime.startsWith('text/')||['application/json','application/xml','application/javascript','application/x-javascript','application/xhtml+xml'].includes(mime)) return {mime,kind:'text'};
149
+ return {mime,kind:'binary'};
150
+ }
151
+
152
+ export function createCommanderUrlReader({
153
+ maxBytes=DEFAULT_MAX_BYTES,
154
+ timeoutMs=DEFAULT_TIMEOUT_MS,
155
+ maxRedirects=DEFAULT_MAX_REDIRECTS,
156
+ lookupFn=defaultLookup,
157
+ requestOnceFn=defaultRequestOnce
158
+ }={}){
159
+ if(!Number.isInteger(maxBytes)||maxBytes<1||maxBytes>64*1024*1024) throw new Error('URL maxBytes out of range');
160
+ if(!Number.isInteger(timeoutMs)||timeoutMs<100||timeoutMs>120000) throw new Error('URL timeoutMs out of range');
161
+ if(!Number.isInteger(maxRedirects)||maxRedirects<0||maxRedirects>10) throw new Error('URL maxRedirects out of range');
162
+
163
+ return async function readCommanderUrl(input){
164
+ let url=validateUrl(input);
165
+ const visited=new Set();
166
+ for(let hop=0;hop<=maxRedirects;hop++){
167
+ if(visited.has(url.href)) throw new Error('URL redirect loop detected');
168
+ visited.add(url.href);
169
+ const address=await resolvePublic(url.hostname,lookupFn);
170
+ const response=await requestOnceFn({url,address,maxBytes,timeoutMs});
171
+ const status=Number(response?.status);
172
+ const headers=normalizeHeaders(response?.headers);
173
+ const body=Buffer.isBuffer(response?.body)?response.body:Buffer.from(response?.body??[]);
174
+ if(body.length>maxBytes) throw new Error(`URL response exceeds ${maxBytes} byte limit`);
175
+ if(REDIRECTS.has(status)){
176
+ const location=headers.location;
177
+ if(!location) throw new Error(`URL redirect ${status} missing Location header`);
178
+ if(hop===maxRedirects) throw new Error('URL redirect limit exceeded');
179
+ url=validateUrl(new URL(location,url).href);
180
+ continue;
181
+ }
182
+ if(status<200||status>=300) throw new Error(`URL HTTP status ${status}`);
183
+ const kind=contentKind(headers['content-type'],url);
184
+ if(kind.kind==='binary') throw new Error(`URL content type '${kind.mime}' is not a supported text, image, or PDF format`);
185
+ const digest=sha256(body);
186
+ if(kind.kind==='pdf'){
187
+ return {content:body.toString('base64'),mimeType:'application/pdf',isImage:false,isPdf:true,bytes:body.length,sha256:digest,url:url.href};
188
+ }
189
+ if(kind.kind==='image'){
190
+ return {content:body.toString('base64'),mimeType:kind.mime,isImage:true,isPdf:false,bytes:body.length,sha256:digest,url:url.href};
191
+ }
192
+ const decoder=new TextDecoder('utf-8',{fatal:true});
193
+ let content;
194
+ try{content=decoder.decode(body);}catch{throw new Error('URL text response is not valid UTF-8');}
195
+ return {content,mimeType:kind.mime,isImage:false,isPdf:false,bytes:body.length,sha256:digest,url:url.href};
196
+ }
197
+ throw new Error('URL redirect limit exceeded');
198
+ };
199
+ }
200
+
201
+ export const readCommanderUrl=createCommanderUrlReader();