stuf-mcp 1.0.0 → 2.0.0

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.
package/README.md CHANGED
@@ -78,4 +78,4 @@ You can also configure via environment variables (skips pairing):
78
78
 
79
79
  ## License
80
80
 
81
- MIT
81
+ AGPL-3.0
package/crypto.js CHANGED
@@ -12,8 +12,9 @@ export async function setEncryptionKey(base64Key) {
12
12
  export async function encrypt(data) {
13
13
  if (!encryptionKey) throw new Error('Encryption key not set');
14
14
  const iv = crypto.getRandomValues(new Uint8Array(12));
15
- const encoded = new TextEncoder().encode(JSON.stringify(data));
16
- const ciphertext = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, encryptionKey, encoded);
15
+ // data is Uint8Array or Array — encrypt raw bytes
16
+ const input = data instanceof Uint8Array ? data : new Uint8Array(data);
17
+ const ciphertext = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, encryptionKey, input);
17
18
  const combined = new Uint8Array(iv.length + ciphertext.byteLength);
18
19
  combined.set(iv);
19
20
  combined.set(new Uint8Array(ciphertext), iv.length);
@@ -26,5 +27,5 @@ export async function decrypt(base64Data) {
26
27
  const iv = combined.slice(0, 12);
27
28
  const ciphertext = combined.slice(12);
28
29
  const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, encryptionKey, ciphertext);
29
- return JSON.parse(new TextDecoder().decode(decrypted));
30
+ return new Uint8Array(decrypted);
30
31
  }
package/index.js CHANGED
@@ -165,7 +165,7 @@ server.tool('delete_task', 'Delete a task', {
165
165
 
166
166
  const taskName = doc.todos[idx].name;
167
167
  await applyAndPush(d => {
168
- d.todos.deleteAt(idx);
168
+ d.todos.splice(idx, 1);
169
169
  });
170
170
 
171
171
  return { content: [{ type: 'text', text: `Deleted: ${taskName}` }] };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stuf-mcp",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
5
  "description": "MCP server for stuf — AI-powered task management",
6
6
  "main": "index.js",
@@ -26,14 +26,14 @@
26
26
  "claude"
27
27
  ],
28
28
  "author": "asbjornenge",
29
- "license": "MIT",
29
+ "license": "AGPL-3.0",
30
30
  "repository": {
31
31
  "type": "git",
32
32
  "url": "https://github.com/asbjornenge/stuf"
33
33
  },
34
34
  "dependencies": {
35
35
  "@modelcontextprotocol/sdk": "^1.0.0",
36
- "automerge": "^0.14.2",
36
+ "@automerge/automerge": "^3.0.0",
37
37
  "ws": "^8.18.0"
38
38
  }
39
39
  }
package/sync.js CHANGED
@@ -1,4 +1,4 @@
1
- import Automerge from 'automerge';
1
+ import * as Automerge from '@automerge/automerge';
2
2
  import WebSocket from 'ws';
3
3
  import { encrypt, decrypt } from './crypto.js';
4
4
 
@@ -35,7 +35,7 @@ export async function pullChanges() {
35
35
  for (const { data } of changes) {
36
36
  try {
37
37
  const change = await decrypt(data);
38
- doc = Automerge.applyChanges(doc, [change]);
38
+ [doc] = Automerge.applyChanges(doc, [change]);
39
39
  } catch (err) {
40
40
  console.warn(`Failed to apply change: ${err.message}`);
41
41
  }
@@ -53,8 +53,7 @@ export async function pullSnapshot() {
53
53
  const { data, seq } = await res.json();
54
54
  if (data) {
55
55
  const decrypted = await decrypt(data);
56
- const bytes = new Uint8Array(decrypted);
57
- doc = Automerge.load(bytes);
56
+ doc = Automerge.load(decrypted);
58
57
  lastSeq = seq || 0;
59
58
  }
60
59
  return doc;
@@ -63,11 +62,11 @@ export async function pullSnapshot() {
63
62
  export async function pushChanges(oldDoc, newDoc) {
64
63
  const changes = Automerge.getChanges(oldDoc, newDoc);
65
64
  if (changes.length === 0) return;
66
- const encrypted = await Promise.all(changes.map(c => encrypt(c)));
65
+ const encrypted = await Promise.all(changes.map(c => encrypt(Array.from(c))));
67
66
  const res = await fetch(`${serverUrl}/api/changes`, {
68
67
  method: 'POST',
69
68
  headers: headers(),
70
- body: JSON.stringify({ changes: encrypted })
69
+ body: JSON.stringify({ changes: encrypted, formatVersion: 3 })
71
70
  });
72
71
  if (!res.ok) throw new Error(`Push failed: ${res.status} ${await res.text()}`);
73
72
  const { lastSeq: newSeq } = await res.json();