Browse Source

init-parser-v0-1-0

master
Serge Shutov 2 weeks ago
commit
e4292bd497
  1. 4
      .gitignore
  2. 29
      README.md
  3. 3
      background.js
  4. 93
      content/parser.js
  5. 13
      manifest.json
  6. 11
      package.json
  7. 43
      test/parser.test.mjs

4
.gitignore vendored

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
node_modules/
.dsh-pro/
.vscode/
*.log

29
README.md

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
# dsh-memory
Unified Chrome extension: DSH tools (write/edit/bash etc) + persistent memory, in one app.
## Protocols supported
- DSH — classic JSON marker: [DSH]{"tool":...,"args":{...}}[/DSH]
- DSH_B64 — base64-encoded UTF-8 JSON: [DSH_B64]<base64>[/DSH_B64]
- DSH_FILE — raw file content, no escaping: [DSH_FILE path="..."]<raw>[/DSH_FILE]
(Real markers use U+27E6 / U+27E7 brackets.)
## Layout
- content/parser.js — extracts calls from chat text
- background.js — service worker
- manifest.json — MV3 manifest
## Test
npm test
Runs test/parser.test.mjs against all three marker types.
## Status
v0.1.0 — parser skeleton only. Bridge, UI, memory migration follow in separate iterations.

3
background.js

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
'use strict';
console.log('dsh-memory ready');

93
content/parser.js

@ -0,0 +1,93 @@ @@ -0,0 +1,93 @@
'use strict';
// DSH unified parser. Three markers:
// DSH - classic JSON
// DSH_B64 - base64 UTF-8 JSON
// DSH_FILE - raw file content
const L = String.fromCharCode(10214);
const R = String.fromCharCode(10215);
const BS = String.fromCharCode(92);
const Q = String.fromCharCode(34);
const OPEN_DSH = L + 'DSH' + R;
const CLOSE_DSH = L + '/DSH' + R;
const OPEN_B64 = L + 'DSH_B64' + R;
const CLOSE_B64 = L + '/DSH_B64' + R;
const OPEN_FILE = L + 'DSH_FILE';
const CLOSE_FILE = L + '/DSH_FILE' + R;
const B64_CLEAN = new RegExp('[^A-Za-z0-9+/=]', 'g');
function b64DecodeUtf8(b64) {
const clean = String(b64 || '').replace(B64_CLEAN, '');
const bin = atob(clean);
const bytes = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
return new TextDecoder('utf-8').decode(bytes);
}
function tryParseJson(s) {
try { return JSON.parse(s); } catch (e) { return null; }
}
function parseFileHeader(header) {
const pIdx = header.indexOf('path');
if (pIdx < 0) return null;
const eq = header.indexOf('=', pIdx);
if (eq < 0) return null;
const q1 = header.indexOf(Q, eq);
if (q1 < 0) return null;
const q2 = header.indexOf(Q, q1 + 1);
if (q2 < 0) return null;
return header.slice(q1 + 1, q2);
}
function parseOne(text, startIdx) {
if (text.startsWith(OPEN_FILE, startIdx)) {
const openEnd = text.indexOf(R, startIdx + OPEN_FILE.length);
if (openEnd < 0) return null;
const header = text.slice(startIdx + OPEN_FILE.length, openEnd);
const path = parseFileHeader(header);
if (!path) return null;
const closeIdx = text.indexOf(CLOSE_FILE, openEnd + 1);
if (closeIdx < 0) return null;
let content = text.slice(openEnd + 1, closeIdx);
if (content.charCodeAt(0) === 10) content = content.slice(1);
return { call: { tool: 'dsh_write', args: { path: path, content: content } }, nextIdx: closeIdx + CLOSE_FILE.length };
}
if (text.startsWith(OPEN_B64, startIdx)) {
const closeIdx = text.indexOf(CLOSE_B64, startIdx + OPEN_B64.length);
if (closeIdx < 0) return null;
const raw = text.slice(startIdx + OPEN_B64.length, closeIdx);
const json = b64DecodeUtf8(raw);
const obj = tryParseJson(json);
if (!obj || typeof obj !== 'object' || !obj.tool) return null;
return { call: obj, nextIdx: closeIdx + CLOSE_B64.length };
}
if (text.startsWith(OPEN_DSH, startIdx)) {
const closeIdx = text.indexOf(CLOSE_DSH, startIdx + OPEN_DSH.length);
if (closeIdx < 0) return null;
const raw = text.slice(startIdx + OPEN_DSH.length, closeIdx);
const obj = tryParseJson(raw);
if (!obj || typeof obj !== 'object' || !obj.tool) return null;
return { call: obj, nextIdx: closeIdx + CLOSE_DSH.length };
}
return null;
}
function extractCalls(text, maxCalls) {
const limit = Math.max(1, maxCalls | 0) || 3;
const calls = [];
let pos = 0;
while (calls.length < limit) {
const i = text.indexOf(L, pos);
if (i < 0) break;
const parsed = parseOne(text, i);
if (!parsed) { pos = i + L.length; continue; }
calls.push(parsed.call);
pos = parsed.nextIdx;
}
return { calls: calls, consumed: pos };
}
module.exports = { extractCalls: extractCalls, b64DecodeUtf8: b64DecodeUtf8 };

13
manifest.json

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
{
"manifest_version": 3,
"name": "DSH Memory (unified)",
"version": "0.1.0",
"description": "DSH tools + persistent memory, unified",
"permissions": ["storage", "scripting", "activeTab"],
"host_permissions": ["https://chat.deepseek.com/"],
"background": { "service_worker": "background.js" },
"content_scripts": [{
"matches": ["https://chat.deepseek.com/"],
"js": ["content/parser.js"]
}]
}

11
package.json

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
{
"name": "dsh-memory",
"version": "0.1.0",
"private": true,
"description": "Unified DSH tools + persistent memory extension",
"type": "commonjs",
"scripts": {
"test": "node test/parser.test.mjs"
},
"license": "MIT"
}

43
test/parser.test.mjs

@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
const mod = await import('node:module');
const require = mod.createRequire(import.meta.url);
const parser = require('../content/parser.js');
const L = String.fromCharCode(10214);
const R = String.fromCharCode(10215);
const LF = String.fromCharCode(10);
const Q = String.fromCharCode(34);
const LB = String.fromCharCode(123);
const RB = String.fromCharCode(125);
const BS = String.fromCharCode(92);
const LT = String.fromCharCode(60);
let pass = 0;
let fail = 0;
const ok = (name, cond) => cond ? (pass++, console.log('OK ' + name)) : (fail++, console.log('FAIL ' + name));
// Test 1: DSH JSON
const json1 = LB + Q + 'tool' + Q + ':' + Q + 'dsh_read' + Q + ',' + Q + 'args' + Q + ':' + LB + Q + 'path' + Q + ':' + Q + 'README.md' + Q + RB + RB;
const t1 = L + 'DSH' + R + json1 + L + '/DSH' + R;
const r1 = parser.extractCalls(t1, 3);
ok('DSH count', r1.calls.length === 1);
ok('DSH tool', !!r1.calls[0] ? r1.calls[0].tool === 'dsh_read' : false);
ok('DSH path', !!r1.calls[0] ? r1.calls[0].args.path === 'README.md' : false);
// Test 2: DSH_B64
const b64 = 'eyJ0b29sIjoiZHNoX3RyZWUiLCJhcmdzIjp7InBhdGgiOiIuIn19';
const t2 = L + 'DSH_B64' + R + b64 + L + '/DSH_B64' + R;
const r2 = parser.extractCalls(t2, 3);
ok('B64 count', r2.calls.length === 1);
ok('B64 tool', !!r2.calls[0] ? r2.calls[0].tool === 'dsh_tree' : false);
// Test 3: DSH_FILE
const phpBody = LT + '?php' + LF + 'namespace app' + BS + 'models;' + LF;
const t3 = L + 'DSH_FILE path=' + Q + 't.php' + Q + R + LF + phpBody + L + '/DSH_FILE' + R;
const r3 = parser.extractCalls(t3, 3);
ok('FILE count', r3.calls.length === 1);
ok('FILE path', !!r3.calls[0] ? r3.calls[0].args.path === 't.php' : false);
ok('FILE tool', !!r3.calls[0] ? r3.calls[0].tool === 'dsh_write' : false);
ok('FILE content', !!r3.calls[0] ? r3.calls[0].args.content.indexOf('namespace app' + BS + 'models') !== -1 : false);
console.log('passed ' + pass + ', failed ' + fail);
if (fail > 0) process.exit(1);
Loading…
Cancel
Save