myceliumail 1.0.9 → 1.0.13
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/.mappersan/outbox.json +15 -0
- package/.spidersan/registry.json +39 -0
- package/CHANGELOG.md +29 -0
- package/CLAUDE.md +29 -0
- package/README.md +23 -2
- package/dist/bin/myceliumail.js +17 -1
- package/dist/bin/myceliumail.js.map +1 -1
- package/dist/commands/close.d.ts +9 -0
- package/dist/commands/close.d.ts.map +1 -0
- package/dist/commands/close.js +153 -0
- package/dist/commands/close.js.map +1 -0
- package/dist/commands/collab.d.ts +8 -0
- package/dist/commands/collab.d.ts.map +1 -0
- package/dist/commands/collab.js +112 -0
- package/dist/commands/collab.js.map +1 -0
- package/dist/commands/inbox.d.ts.map +1 -1
- package/dist/commands/inbox.js +105 -26
- package/dist/commands/inbox.js.map +1 -1
- package/dist/commands/tags.d.ts +6 -0
- package/dist/commands/tags.d.ts.map +1 -0
- package/dist/commands/tags.js +90 -0
- package/dist/commands/tags.js.map +1 -0
- package/dist/commands/wake.d.ts +9 -0
- package/dist/commands/wake.d.ts.map +1 -0
- package/dist/commands/wake.js +198 -0
- package/dist/commands/wake.js.map +1 -0
- package/dist/dashboard/public/app.js +117 -0
- package/dist/dashboard/public/index.html +63 -5
- package/dist/dashboard/routes.d.ts.map +1 -1
- package/dist/dashboard/routes.js +31 -2
- package/dist/dashboard/routes.js.map +1 -1
- package/dist/lib/update-check.d.ts.map +1 -1
- package/dist/lib/update-check.js +6 -4
- package/dist/lib/update-check.js.map +1 -1
- package/dist/lib/watson-digest.d.ts +40 -0
- package/dist/lib/watson-digest.d.ts.map +1 -0
- package/dist/lib/watson-digest.js +164 -0
- package/dist/lib/watson-digest.js.map +1 -0
- package/dist/storage/supabase.d.ts +4 -0
- package/dist/storage/supabase.d.ts.map +1 -1
- package/dist/storage/supabase.js +57 -0
- package/dist/storage/supabase.js.map +1 -1
- package/docs/COLLAB_mappersan_mycmail_setup.md +115 -0
- package/docs/COLLAB_wake_close_commands.md +518 -0
- package/docs/CROSS_TOOL_INTEGRATION_PLAN.md +246 -0
- package/docs/JSON_SCHEMA_WAKE_CLOSE.md +246 -0
- package/docs/MYCMAIL_QUICKSTART.md +103 -0
- package/docs/WAKE_AGENTS_SHARED_DOC.md +1215 -0
- package/mcp-server/README.md +75 -69
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +5 -1
- package/mcp-server/postinstall.js +14 -0
- package/mcp-server/src/server.ts +39 -0
- package/mobile-app/README.md +36 -0
- package/mobile-app/app/compose/page.tsx +140 -0
- package/mobile-app/app/favicon.ico +0 -0
- package/mobile-app/app/globals.css +26 -0
- package/mobile-app/app/layout.tsx +42 -0
- package/mobile-app/app/message/[id]/page.tsx +126 -0
- package/mobile-app/app/page.tsx +131 -0
- package/mobile-app/components/MessageCard.tsx +60 -0
- package/mobile-app/eslint.config.mjs +18 -0
- package/mobile-app/lib/supabase.ts +87 -0
- package/mobile-app/next.config.ts +7 -0
- package/mobile-app/package-lock.json +6674 -0
- package/mobile-app/package.json +27 -0
- package/mobile-app/postcss.config.mjs +7 -0
- package/mobile-app/public/file.svg +1 -0
- package/mobile-app/public/globe.svg +1 -0
- package/mobile-app/public/next.svg +1 -0
- package/mobile-app/public/vercel.svg +1 -0
- package/mobile-app/public/window.svg +1 -0
- package/mobile-app/tsconfig.json +34 -0
- package/package.json +2 -1
- package/postinstall.js +14 -0
- package/src/bin/myceliumail.ts +19 -1
- package/src/commands/close.ts +172 -0
- package/src/commands/collab.ts +125 -0
- package/src/commands/inbox.ts +120 -29
- package/src/commands/tags.ts +102 -0
- package/src/commands/wake.ts +228 -0
- package/src/dashboard/public/app.js +117 -0
- package/src/dashboard/public/index.html +63 -5
- package/src/dashboard/routes.ts +31 -2
- package/src/lib/update-check.ts +7 -4
- package/src/lib/watson-digest.ts +217 -0
- package/src/storage/supabase.ts +71 -0
- package/vscode-extension/README.md +107 -0
- package/vscode-extension/package-lock.json +1941 -0
- package/vscode-extension/package.json +117 -0
- package/vscode-extension/src/chatParticipant.ts +179 -0
- package/vscode-extension/src/extension.ts +262 -0
- package/vscode-extension/src/handlers.ts +265 -0
- package/vscode-extension/src/realtime.ts +302 -0
- package/vscode-extension/src/types.ts +41 -0
- package/vscode-extension/tsconfig.json +26 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mobile-app",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "next start",
|
|
9
|
+
"lint": "eslint"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@supabase/supabase-js": "^2.89.0",
|
|
13
|
+
"next": "16.1.1",
|
|
14
|
+
"react": "19.2.3",
|
|
15
|
+
"react-dom": "19.2.3"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@tailwindcss/postcss": "^4",
|
|
19
|
+
"@types/node": "^20",
|
|
20
|
+
"@types/react": "^19",
|
|
21
|
+
"@types/react-dom": "^19",
|
|
22
|
+
"eslint": "^9",
|
|
23
|
+
"eslint-config-next": "16.1.1",
|
|
24
|
+
"tailwindcss": "^4",
|
|
25
|
+
"typescript": "^5"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "react-jsx",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"plugins": [
|
|
17
|
+
{
|
|
18
|
+
"name": "next"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"paths": {
|
|
22
|
+
"@/*": ["./*"]
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"include": [
|
|
26
|
+
"next-env.d.ts",
|
|
27
|
+
"**/*.ts",
|
|
28
|
+
"**/*.tsx",
|
|
29
|
+
".next/types/**/*.ts",
|
|
30
|
+
".next/dev/types/**/*.ts",
|
|
31
|
+
"**/*.mts"
|
|
32
|
+
],
|
|
33
|
+
"exclude": ["node_modules"]
|
|
34
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "myceliumail",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.13",
|
|
4
4
|
"description": "End-to-End Encrypted Messaging for AI Agents",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "tsc",
|
|
13
13
|
"postbuild": "cp -r src/dashboard/public dist/dashboard/",
|
|
14
|
+
"postinstall": "node postinstall.js 2>/dev/null || true",
|
|
14
15
|
"dev": "tsc --watch",
|
|
15
16
|
"test": "vitest run",
|
|
16
17
|
"test:watch": "vitest",
|
package/postinstall.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Write to stderr so it shows even during npm install
|
|
3
|
+
process.stderr.write(`
|
|
4
|
+
\x1b[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m
|
|
5
|
+
\x1b[33m🍄 Thanks for installing Myceliumail!\x1b[0m
|
|
6
|
+
|
|
7
|
+
We'd love your feedback on your experience!
|
|
8
|
+
|
|
9
|
+
\x1b[32m📧 Beta testing & collaborations:\x1b[0m treebird@treebird.dev
|
|
10
|
+
\x1b[32m☕ Support the project:\x1b[0m https://buymeacoffee.com/tree.bird
|
|
11
|
+
|
|
12
|
+
This is early-stage software built in public. Your feedback helps!
|
|
13
|
+
\x1b[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m
|
|
14
|
+
`);
|
package/src/bin/myceliumail.ts
CHANGED
|
@@ -10,8 +10,16 @@
|
|
|
10
10
|
import 'dotenv/config';
|
|
11
11
|
|
|
12
12
|
import { Command } from 'commander';
|
|
13
|
+
import { readFileSync } from 'fs';
|
|
14
|
+
import { fileURLToPath } from 'url';
|
|
15
|
+
import { dirname, join } from 'path';
|
|
13
16
|
import { loadConfig } from '../lib/config.js';
|
|
14
17
|
|
|
18
|
+
// Get version from package.json
|
|
19
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
20
|
+
const __dirname = dirname(__filename);
|
|
21
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, '../../package.json'), 'utf-8'));
|
|
22
|
+
|
|
15
23
|
// Import commands
|
|
16
24
|
import { createKeygenCommand } from '../commands/keygen.js';
|
|
17
25
|
import { createKeysCommand } from '../commands/keys.js';
|
|
@@ -26,6 +34,10 @@ import { createWatchCommand } from '../commands/watch.js';
|
|
|
26
34
|
import { createExportCommand } from '../commands/export.js';
|
|
27
35
|
import { createStatusCommand } from '../commands/status.js';
|
|
28
36
|
import { createActivateCommand, createLicenseStatusCommand } from '../commands/activate.js';
|
|
37
|
+
import { createWakeCommand } from '../commands/wake.js';
|
|
38
|
+
import { createCloseCommand } from '../commands/close.js';
|
|
39
|
+
import { createTagsCommand } from '../commands/tags.js';
|
|
40
|
+
import { createCollabCommand } from '../commands/collab.js';
|
|
29
41
|
import { checkForUpdates } from '../lib/update-check.js';
|
|
30
42
|
|
|
31
43
|
const program = new Command();
|
|
@@ -33,7 +45,7 @@ const program = new Command();
|
|
|
33
45
|
program
|
|
34
46
|
.name('mycmail')
|
|
35
47
|
.description('🍄 Myceliumail - End-to-End Encrypted Messaging for AI Agents')
|
|
36
|
-
.version(
|
|
48
|
+
.version(pkg.version);
|
|
37
49
|
|
|
38
50
|
// Show current agent in help
|
|
39
51
|
const config = loadConfig();
|
|
@@ -56,6 +68,12 @@ program.addCommand(createWatchCommand());
|
|
|
56
68
|
program.addCommand(createExportCommand());
|
|
57
69
|
program.addCommand(createStatusCommand());
|
|
58
70
|
|
|
71
|
+
// Session lifecycle commands
|
|
72
|
+
program.addCommand(createWakeCommand());
|
|
73
|
+
program.addCommand(createCloseCommand());
|
|
74
|
+
program.addCommand(createTagsCommand());
|
|
75
|
+
program.addCommand(createCollabCommand());
|
|
76
|
+
|
|
59
77
|
// License management
|
|
60
78
|
program.addCommand(createActivateCommand());
|
|
61
79
|
program.addCommand(createLicenseStatusCommand());
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* close command - End a session properly
|
|
3
|
+
*
|
|
4
|
+
* Broadcasts signing-off message and updates session state.
|
|
5
|
+
* Designed for agent session lifecycle management.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
import { loadConfig } from '../lib/config.js';
|
|
10
|
+
import { generateDigest, sendDigestToWatsan } from '../lib/watson-digest.js';
|
|
11
|
+
import * as storage from '../storage/supabase.js';
|
|
12
|
+
import * as fs from 'fs';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
import * as os from 'os';
|
|
15
|
+
|
|
16
|
+
interface SessionData {
|
|
17
|
+
lastWake: string | null;
|
|
18
|
+
lastClose: string | null;
|
|
19
|
+
activeCollabs: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getSessionPath(): string {
|
|
23
|
+
return path.join(os.homedir(), '.mycmail', 'session.json');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function loadSession(): SessionData {
|
|
27
|
+
const sessionPath = getSessionPath();
|
|
28
|
+
try {
|
|
29
|
+
if (fs.existsSync(sessionPath)) {
|
|
30
|
+
return JSON.parse(fs.readFileSync(sessionPath, 'utf-8'));
|
|
31
|
+
}
|
|
32
|
+
} catch {
|
|
33
|
+
// Ignore errors, return default
|
|
34
|
+
}
|
|
35
|
+
return { lastWake: null, lastClose: null, activeCollabs: [] };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function saveSession(data: SessionData): void {
|
|
39
|
+
const sessionPath = getSessionPath();
|
|
40
|
+
const dir = path.dirname(sessionPath);
|
|
41
|
+
if (!fs.existsSync(dir)) {
|
|
42
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
fs.writeFileSync(sessionPath, JSON.stringify(data, null, 2));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createCloseCommand(): Command {
|
|
48
|
+
return new Command('close')
|
|
49
|
+
.description('End the current session - broadcast and update state')
|
|
50
|
+
.option('-m, --message <msg>', 'Custom sign-off message')
|
|
51
|
+
.option('--silent', 'No broadcast, just update state')
|
|
52
|
+
.option('--json', 'Output as JSON (for scripting)')
|
|
53
|
+
.option('-q, --quiet', 'Minimal output')
|
|
54
|
+
.option('--summary', 'Auto-generate session summary')
|
|
55
|
+
.option('--handoff <agent>', 'Tag agent for continuation')
|
|
56
|
+
.action(async (options) => {
|
|
57
|
+
const config = loadConfig();
|
|
58
|
+
const agentId = config.agentId;
|
|
59
|
+
|
|
60
|
+
if (agentId === 'anonymous') {
|
|
61
|
+
if (!options.quiet && !options.json) {
|
|
62
|
+
console.error('❌ Agent ID not configured.');
|
|
63
|
+
}
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const session = loadSession();
|
|
69
|
+
const closeTime = new Date().toISOString();
|
|
70
|
+
|
|
71
|
+
// Calculate session duration if we have wake time
|
|
72
|
+
let sessionDuration: string | null = null;
|
|
73
|
+
if (session.lastWake) {
|
|
74
|
+
const wakeTime = new Date(session.lastWake);
|
|
75
|
+
const now = new Date();
|
|
76
|
+
const diffMins = Math.floor((now.getTime() - wakeTime.getTime()) / 60000);
|
|
77
|
+
if (diffMins < 60) {
|
|
78
|
+
sessionDuration = `${diffMins} minutes`;
|
|
79
|
+
} else {
|
|
80
|
+
const hours = Math.floor(diffMins / 60);
|
|
81
|
+
const mins = diffMins % 60;
|
|
82
|
+
sessionDuration = `${hours}h ${mins}m`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Broadcast signing-off message (unless --silent)
|
|
87
|
+
let broadcastSent = false;
|
|
88
|
+
if (!options.silent) {
|
|
89
|
+
const message = options.message || 'Session complete';
|
|
90
|
+
const fullMessage = sessionDuration
|
|
91
|
+
? `${message} (${sessionDuration} session) - ${agentId} signing off`
|
|
92
|
+
: `${message} - ${agentId} signing off`;
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
// Get all known agents from storage (or use a simple list)
|
|
96
|
+
// For now, just log that we would broadcast
|
|
97
|
+
// In future: call broadcast function
|
|
98
|
+
if (!options.quiet && !options.json) {
|
|
99
|
+
console.log(`📢 Broadcast: "${fullMessage}"`);
|
|
100
|
+
}
|
|
101
|
+
broadcastSent = true;
|
|
102
|
+
} catch (broadcastError) {
|
|
103
|
+
// Broadcast failed, but continue with close
|
|
104
|
+
if (!options.quiet && !options.json) {
|
|
105
|
+
console.warn('⚠️ Broadcast failed, continuing with close');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Update session state
|
|
111
|
+
session.lastClose = closeTime;
|
|
112
|
+
saveSession(session);
|
|
113
|
+
|
|
114
|
+
// Output based on mode
|
|
115
|
+
if (options.json) {
|
|
116
|
+
const output = {
|
|
117
|
+
agentId,
|
|
118
|
+
closeTime,
|
|
119
|
+
sessionDuration,
|
|
120
|
+
broadcastSent,
|
|
121
|
+
handoff: options.handoff || null,
|
|
122
|
+
status: 'closed'
|
|
123
|
+
};
|
|
124
|
+
console.log(JSON.stringify(output, null, 2));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!options.quiet) {
|
|
129
|
+
console.log(`\n🌙 Session closing for ${agentId}\n`);
|
|
130
|
+
if (sessionDuration) {
|
|
131
|
+
console.log(`⏱️ Session duration: ${sessionDuration}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Show auto-summary if requested
|
|
135
|
+
if (options.summary) {
|
|
136
|
+
console.log('\n📝 Session Summary:');
|
|
137
|
+
console.log(' Messages sent this session');
|
|
138
|
+
if (session.activeCollabs.length > 0) {
|
|
139
|
+
console.log(` Active collabs: ${session.activeCollabs.join(', ')}`);
|
|
140
|
+
}
|
|
141
|
+
console.log(' (Full summary tracking coming soon)');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Show handoff if specified
|
|
145
|
+
if (options.handoff) {
|
|
146
|
+
console.log(`\n🤝 Handoff: Tagged ${options.handoff} for continuation`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
console.log('\n👋 Goodbye! See you next session.\n');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Send close digest to watson and wsan
|
|
153
|
+
if (!options.silent) {
|
|
154
|
+
try {
|
|
155
|
+
const closeDigest = await generateDigest(agentId, 'close', sessionDuration || undefined);
|
|
156
|
+
await sendDigestToWatsan(closeDigest);
|
|
157
|
+
if (!options.quiet && !options.json) {
|
|
158
|
+
console.log('📊 Digest sent to watson/wsan');
|
|
159
|
+
}
|
|
160
|
+
} catch {
|
|
161
|
+
// Silent fail
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
} catch (error) {
|
|
166
|
+
if (!options.json) {
|
|
167
|
+
console.error('❌ Close failed:', error);
|
|
168
|
+
}
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* collab command - Join or manage collaborative documents
|
|
3
|
+
*
|
|
4
|
+
* Allows mycmail to programmatically join birdsan-orchestrated collabs.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Command } from 'commander';
|
|
8
|
+
import { loadConfig } from '../lib/config.js';
|
|
9
|
+
import * as fs from 'fs';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
|
|
12
|
+
function addAgentSection(filepath: string, agentName: string, agentId: string, message: string): void {
|
|
13
|
+
let content = fs.readFileSync(filepath, 'utf-8');
|
|
14
|
+
|
|
15
|
+
const now = new Date();
|
|
16
|
+
const timestamp = now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
|
|
17
|
+
const date = now.toLocaleDateString('en-US', { month: 'numeric', day: 'numeric', year: 'numeric' });
|
|
18
|
+
|
|
19
|
+
// Find the placeholder section and add our response before it
|
|
20
|
+
const placeholder = '### [Agent responses will appear here]';
|
|
21
|
+
const agentSection = `### ${agentName} (${agentId}) - ${date} ${timestamp}
|
|
22
|
+
${message}
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
${placeholder}`;
|
|
27
|
+
|
|
28
|
+
if (content.includes(placeholder)) {
|
|
29
|
+
content = content.replace(placeholder, agentSection);
|
|
30
|
+
} else {
|
|
31
|
+
// Append at the end if no placeholder found
|
|
32
|
+
content += `\n\n---\n\n### ${agentName} (${agentId}) - ${date} ${timestamp}\n${message}\n`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Update the participant checkbox if present
|
|
36
|
+
const uncheckedPatterns = [
|
|
37
|
+
new RegExp(`- \\[ \\] \\*\\*${agentName}\\*\\* - Awaiting response`),
|
|
38
|
+
new RegExp(`- \\[ \\] \\*\\*${agentId}\\*\\* - Awaiting response`),
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
for (const pattern of uncheckedPatterns) {
|
|
42
|
+
content = content.replace(pattern, `- [x] **${agentName}** - Joined`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
fs.writeFileSync(filepath, content);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function createCollabCommand(): Command {
|
|
49
|
+
return new Command('collab')
|
|
50
|
+
.description('Join or manage collaborative documents')
|
|
51
|
+
.option('--join <filepath>', 'Join an existing collaboration document')
|
|
52
|
+
.option('-m, --message <message>', 'Custom message to add when joining')
|
|
53
|
+
.action(async (options) => {
|
|
54
|
+
const config = loadConfig();
|
|
55
|
+
const agentId = config.agentId;
|
|
56
|
+
const agentName = 'Myceliumail';
|
|
57
|
+
|
|
58
|
+
if (options.join) {
|
|
59
|
+
const filepath = options.join;
|
|
60
|
+
|
|
61
|
+
if (!fs.existsSync(filepath)) {
|
|
62
|
+
console.error(`\n❌ Collab file not found: ${filepath}\n`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log('\n🤝 Joining Collaboration\n');
|
|
67
|
+
console.log('─'.repeat(40));
|
|
68
|
+
console.log(`File: ${path.basename(filepath)}`);
|
|
69
|
+
console.log(`Agent: ${agentName} (${agentId})`);
|
|
70
|
+
|
|
71
|
+
const defaultMessage = `${agentName} joining the discussion!
|
|
72
|
+
|
|
73
|
+
As the communication backbone, here's my perspective:
|
|
74
|
+
|
|
75
|
+
**Key points:**
|
|
76
|
+
- Ready to facilitate message exchange between agents
|
|
77
|
+
- Can provide encryption/decryption support for sensitive discussions
|
|
78
|
+
- Monitoring network health for all participants
|
|
79
|
+
|
|
80
|
+
Looking forward to contributing!`;
|
|
81
|
+
|
|
82
|
+
const message = options.message || defaultMessage;
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
addAgentSection(filepath, agentName, agentId, message);
|
|
86
|
+
console.log(`\n✓ ${agentName} joined the collab!`);
|
|
87
|
+
console.log(` Added response to: ${filepath}`);
|
|
88
|
+
|
|
89
|
+
// Notify birdsan
|
|
90
|
+
try {
|
|
91
|
+
const { execSync } = await import('child_process');
|
|
92
|
+
execSync(`mycmail send bsan "Joined collab" --message "${agentName} has joined: ${path.basename(filepath)}" -p`, {
|
|
93
|
+
stdio: 'ignore'
|
|
94
|
+
});
|
|
95
|
+
console.log('✓ Notified birdsan');
|
|
96
|
+
} catch {
|
|
97
|
+
// Ignore notification errors
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error(`\n❌ Failed to join: ${(error as Error).message}\n`);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log('\n' + '─'.repeat(40) + '\n');
|
|
106
|
+
} else {
|
|
107
|
+
// Show help if no action specified
|
|
108
|
+
console.log('\n🤝 Myceliumail Collab\n');
|
|
109
|
+
console.log('─'.repeat(40));
|
|
110
|
+
console.log(`
|
|
111
|
+
Usage:
|
|
112
|
+
mycmail collab --join <filepath> Join a collaboration
|
|
113
|
+
|
|
114
|
+
Options:
|
|
115
|
+
--join <filepath> Path to the collab document
|
|
116
|
+
-m, --message <msg> Custom message to add
|
|
117
|
+
|
|
118
|
+
Examples:
|
|
119
|
+
mycmail collab --join ~/Dev/treebird-internal/collab/COLLAB_topic.md
|
|
120
|
+
mycmail collab --join ./collab.md -m "My thoughts on this..."
|
|
121
|
+
`);
|
|
122
|
+
console.log('─'.repeat(40) + '\n');
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
package/src/commands/inbox.ts
CHANGED
|
@@ -7,11 +7,63 @@ import { loadConfig } from '../lib/config.js';
|
|
|
7
7
|
import { loadKeyPair, decryptMessage } from '../lib/crypto.js';
|
|
8
8
|
import * as storage from '../storage/supabase.js';
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Extract hashtag from subject (e.g., "#wake-feature: Message" -> "wake-feature")
|
|
12
|
+
*/
|
|
13
|
+
function extractTag(subject: string | null): string | null {
|
|
14
|
+
if (!subject) return null;
|
|
15
|
+
const match = subject.match(/^#([a-zA-Z0-9_-]+):/);
|
|
16
|
+
return match ? match[1].toLowerCase() : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface DecryptedMessage {
|
|
20
|
+
original: any;
|
|
21
|
+
subject: string | null;
|
|
22
|
+
body: string | null;
|
|
23
|
+
tag: string | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Decrypt message subjects for filtering
|
|
28
|
+
*/
|
|
29
|
+
function decryptSubject(msg: any, keyPair: any): DecryptedMessage {
|
|
30
|
+
let subject = msg.subject;
|
|
31
|
+
let body = msg.body;
|
|
32
|
+
|
|
33
|
+
if (msg.encrypted && keyPair && msg.ciphertext && msg.nonce && msg.senderPublicKey) {
|
|
34
|
+
try {
|
|
35
|
+
const decrypted = decryptMessage({
|
|
36
|
+
ciphertext: msg.ciphertext,
|
|
37
|
+
nonce: msg.nonce,
|
|
38
|
+
senderPublicKey: msg.senderPublicKey,
|
|
39
|
+
}, keyPair);
|
|
40
|
+
|
|
41
|
+
if (decrypted) {
|
|
42
|
+
const parsed = JSON.parse(decrypted);
|
|
43
|
+
subject = parsed.subject || subject;
|
|
44
|
+
body = parsed.body || body;
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
// Keep original
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
original: msg,
|
|
53
|
+
subject,
|
|
54
|
+
body,
|
|
55
|
+
tag: extractTag(subject)
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
10
59
|
export function createInboxCommand(): Command {
|
|
11
60
|
return new Command('inbox')
|
|
12
61
|
.description('List incoming messages')
|
|
13
62
|
.option('-u, --unread', 'Show only unread messages')
|
|
14
63
|
.option('-l, --limit <n>', 'Limit number of messages', '10')
|
|
64
|
+
.option('-c, --count', 'Show only message count (for scripting)')
|
|
65
|
+
.option('-t, --tag <tag>', 'Filter by hashtag (e.g., --tag wake-feature)')
|
|
66
|
+
.option('--json', 'Output as JSON (for scripting)')
|
|
15
67
|
.action(async (options) => {
|
|
16
68
|
const config = loadConfig();
|
|
17
69
|
const agentId = config.agentId;
|
|
@@ -23,46 +75,85 @@ export function createInboxCommand(): Command {
|
|
|
23
75
|
}
|
|
24
76
|
|
|
25
77
|
try {
|
|
26
|
-
const
|
|
78
|
+
const rawMessages = await storage.getInbox(agentId, {
|
|
27
79
|
unreadOnly: options.unread,
|
|
28
|
-
limit: parseInt(options.limit, 10),
|
|
80
|
+
limit: parseInt(options.limit, 10) * (options.tag ? 10 : 1),
|
|
29
81
|
});
|
|
30
82
|
|
|
31
|
-
|
|
32
|
-
|
|
83
|
+
const keyPair = loadKeyPair(agentId);
|
|
84
|
+
|
|
85
|
+
// Decrypt all subjects first for proper filtering
|
|
86
|
+
let messages = rawMessages.map(m => decryptSubject(m, keyPair));
|
|
87
|
+
|
|
88
|
+
// Filter by tag if specified
|
|
89
|
+
if (options.tag) {
|
|
90
|
+
const targetTag = options.tag.toLowerCase().replace(/^#/, '');
|
|
91
|
+
messages = messages.filter(m => m.tag === targetTag);
|
|
92
|
+
messages = messages.slice(0, parseInt(options.limit, 10));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Count-only mode
|
|
96
|
+
if (options.count) {
|
|
97
|
+
const unreadCount = messages.filter(m => !m.original.read).length;
|
|
98
|
+
if (options.json) {
|
|
99
|
+
console.log(JSON.stringify({
|
|
100
|
+
total: messages.length,
|
|
101
|
+
unread: unreadCount,
|
|
102
|
+
agentId,
|
|
103
|
+
tag: options.tag || null
|
|
104
|
+
}));
|
|
105
|
+
} else {
|
|
106
|
+
console.log(`${unreadCount} unread`);
|
|
107
|
+
}
|
|
33
108
|
return;
|
|
34
109
|
}
|
|
35
110
|
|
|
36
|
-
|
|
111
|
+
// JSON mode
|
|
112
|
+
if (options.json) {
|
|
113
|
+
const output = {
|
|
114
|
+
agentId,
|
|
115
|
+
total: messages.length,
|
|
116
|
+
unread: messages.filter(m => !m.original.read).length,
|
|
117
|
+
tag: options.tag || null,
|
|
118
|
+
messages: messages.map(m => ({
|
|
119
|
+
id: m.original.id,
|
|
120
|
+
from: m.original.sender,
|
|
121
|
+
subject: m.subject,
|
|
122
|
+
tag: m.tag,
|
|
123
|
+
read: m.original.read,
|
|
124
|
+
encrypted: m.original.encrypted,
|
|
125
|
+
createdAt: m.original.createdAt.toISOString()
|
|
126
|
+
}))
|
|
127
|
+
};
|
|
128
|
+
console.log(JSON.stringify(output, null, 2));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
37
131
|
|
|
38
|
-
|
|
132
|
+
if (messages.length === 0) {
|
|
133
|
+
if (options.tag) {
|
|
134
|
+
console.log(`📭 No messages with tag #${options.tag}`);
|
|
135
|
+
} else {
|
|
136
|
+
console.log('📭 No messages');
|
|
137
|
+
}
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const tagInfo = options.tag ? ` [#${options.tag}]` : '';
|
|
142
|
+
console.log(`📬 Inbox for ${agentId}${tagInfo} (${messages.length} messages)\n`);
|
|
39
143
|
|
|
40
144
|
for (const msg of messages) {
|
|
41
|
-
const readMarker = msg.read ? ' ' : '● ';
|
|
42
|
-
const encryptedMarker = msg.encrypted ? '🔐 ' : '';
|
|
43
|
-
const date = msg.createdAt.toLocaleString();
|
|
44
|
-
|
|
45
|
-
let displaySubject = msg.subject;
|
|
46
|
-
|
|
47
|
-
//
|
|
48
|
-
if (msg.
|
|
49
|
-
|
|
50
|
-
const decrypted = decryptMessage({
|
|
51
|
-
ciphertext: msg.ciphertext,
|
|
52
|
-
nonce: msg.nonce,
|
|
53
|
-
senderPublicKey: msg.senderPublicKey,
|
|
54
|
-
}, keyPair);
|
|
55
|
-
|
|
56
|
-
if (decrypted) {
|
|
57
|
-
const parsed = JSON.parse(decrypted);
|
|
58
|
-
displaySubject = parsed.subject || '[Decrypted]';
|
|
59
|
-
}
|
|
60
|
-
} catch {
|
|
61
|
-
displaySubject = '[Encrypted]';
|
|
62
|
-
}
|
|
145
|
+
const readMarker = msg.original.read ? ' ' : '● ';
|
|
146
|
+
const encryptedMarker = msg.original.encrypted ? '🔐 ' : '';
|
|
147
|
+
const date = msg.original.createdAt.toLocaleString();
|
|
148
|
+
|
|
149
|
+
let displaySubject = msg.subject || '[No Subject]';
|
|
150
|
+
|
|
151
|
+
// Highlight tag in subject if present
|
|
152
|
+
if (msg.tag) {
|
|
153
|
+
displaySubject = displaySubject.replace(`#${msg.tag}:`, `[#${msg.tag}]`);
|
|
63
154
|
}
|
|
64
155
|
|
|
65
|
-
console.log(`${readMarker}${encryptedMarker}${msg.id.slice(0, 8)} | From: ${msg.sender} | ${displaySubject}`);
|
|
156
|
+
console.log(`${readMarker}${encryptedMarker}${msg.original.id.slice(0, 8)} | From: ${msg.original.sender} | ${displaySubject}`);
|
|
66
157
|
console.log(` ${date}`);
|
|
67
158
|
}
|
|
68
159
|
|