amalgm 0.1.103 → 0.1.104

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 (56) hide show
  1. package/package.json +1 -1
  2. package/runtime/scripts/amalgm-mcp/lib/email-embeds.js +169 -81
  3. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-letter-c-00599c-ffffff.png +0 -0
  4. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-letter-c-5c6bc0-ffffff.png +0 -0
  5. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-letter-c-68217a-ffffff.png +0 -0
  6. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-letter-go-00add8-ffffff.png +0 -0
  7. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-letter-j-e76f00-ffffff.png +0 -0
  8. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-letter-js-f7df1e-1c1917.png +0 -0
  9. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-letter-kt-7f52ff-ffffff.png +0 -0
  10. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-letter-php-777bb4-ffffff.png +0 -0
  11. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-letter-rb-cc342d-ffffff.png +0 -0
  12. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-letter-rs-ce4a1f-ffffff.png +0 -0
  13. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-letter-s-cd6799-ffffff.png +0 -0
  14. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-letter-sw-f05138-ffffff.png +0 -0
  15. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-letter-ts-3178c6-ffffff.png +0 -0
  16. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-letter-v-42b883-ffffff.png +0 -0
  17. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-box-ffffff.png +0 -0
  18. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-braces-facc15.png +0 -0
  19. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-braces-fb923c.png +0 -0
  20. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-calendarclock-d6d3d1.png +0 -0
  21. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-database-60a5fa.png +0 -0
  22. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-externallink-ffffff.png +0 -0
  23. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-file-a8a29e.png +0 -0
  24. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-filearchive-facc15.png +0 -0
  25. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-fileimage-2dd4bf.png +0 -0
  26. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-fileimage-c084fc.png +0 -0
  27. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-filespreadsheet-4ade80.png +0 -0
  28. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-filetext-60a5fa.png +0 -0
  29. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-filetext-a8a29e.png +0 -0
  30. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-filetext-f87171.png +0 -0
  31. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-folder-d6d3d1.png +0 -0
  32. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-globe-d6d3d1.png +0 -0
  33. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-lock-facc15.png +0 -0
  34. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-messagesquare-ffffff.png +0 -0
  35. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-music-f472b6.png +0 -0
  36. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-package-38bdf8.png +0 -0
  37. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-package-facc15.png +0 -0
  38. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-settings-a8a29e.png +0 -0
  39. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-settings-d6d3d1.png +0 -0
  40. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-settings-f87171.png +0 -0
  41. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-terminal-4ade80.png +0 -0
  42. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-video-f472b6.png +0 -0
  43. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-zap-fbbf24.png +0 -0
  44. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-path-css3-1572b6.png +0 -0
  45. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-path-html5-e34f26.png +0 -0
  46. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-path-markdown-a8a29e.png +0 -0
  47. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-path-python-4b8bbe.png +0 -0
  48. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-react.png +0 -0
  49. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-skill.png +0 -0
  50. package/runtime/scripts/amalgm-mcp/lib/email-icons.js +53 -0
  51. package/runtime/scripts/amalgm-mcp/lib/email-md.js +18 -3
  52. package/runtime/scripts/amalgm-mcp/lib/file-icon-registry.json +995 -0
  53. package/runtime/scripts/amalgm-mcp/notify/email-media.js +16 -4
  54. package/runtime/scripts/amalgm-mcp/notify/index.js +6 -1
  55. package/runtime/scripts/amalgm-mcp/tests/email-embeds.test.js +97 -0
  56. package/runtime/scripts/amalgm-mcp/tests/email-media.test.js +19 -0
@@ -31,7 +31,19 @@ const MAX_VIDEO_BYTES = 20 * 1024 * 1024;
31
31
  const MAX_TOTAL_BYTES = 22 * 1024 * 1024; // proxy caps at 25MB; leave margin
32
32
  const MAX_ATTACHMENTS = 8;
33
33
 
34
- const MEDIA_LINE_RE = /^!\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)$/;
34
+ function parseMediaLine(line) {
35
+ const match = line.trim().match(/^!\[([^\]]*)\]\((.*)\)$/);
36
+ if (!match) return null;
37
+ let src = match[2].trim();
38
+ let title = null;
39
+ const titleMatch = src.match(/^(.*)\s+"([^"]*)"$/);
40
+ if (titleMatch) {
41
+ src = titleMatch[1].trim();
42
+ title = titleMatch[2];
43
+ }
44
+ if (!src) return null;
45
+ return { alt: match[1], src, title };
46
+ }
35
47
 
36
48
  /** Resolve a media src to an absolute path on this computer, or null. */
37
49
  function resolveLocalPath(src) {
@@ -133,9 +145,9 @@ function prepareEmailMedia(message) {
133
145
  };
134
146
 
135
147
  const lines = message.split('\n').map((line) => {
136
- const match = line.trim().match(MEDIA_LINE_RE);
137
- if (!match) return line;
138
- const [, alt, src, title] = match;
148
+ const media = parseMediaLine(line);
149
+ if (!media) return line;
150
+ const { alt, src, title } = media;
139
151
 
140
152
  if (/^(https?:|cid:|attached:|data:)/i.test(src)) return line;
141
153
  const filePath = resolveLocalPath(src);
@@ -9,6 +9,7 @@
9
9
  const { PROXY_BASE_URL, PROXY_TOKEN } = require('../config');
10
10
  const { ensureFreshProxyToken, readProxyToken } = require('../../proxy-token-store');
11
11
  const { formatNotificationEmail } = require('../lib/email-md');
12
+ const { prepareEmailIconAttachments } = require('../lib/email-icons');
12
13
  const { prepareEmailMedia } = require('./email-media');
13
14
  const { textResult, errorResult } = require('../lib/tool-result');
14
15
 
@@ -32,6 +33,10 @@ async function notifyUserViaProxy({ subject: customSubject, message, link, sessi
32
33
  // inline via cid:, videos as regular attachments with a poster card.
33
34
  const media = prepareEmailMedia(message);
34
35
  const html = formatNotificationEmail(media.message, link || null);
36
+ const attachments = [
37
+ ...media.attachments,
38
+ ...prepareEmailIconAttachments(html, media.attachments),
39
+ ];
35
40
 
36
41
  try {
37
42
  const res = await fetch(notifyUrl, {
@@ -44,7 +49,7 @@ async function notifyUserViaProxy({ subject: customSubject, message, link, sessi
44
49
  subject,
45
50
  html,
46
51
  text: message,
47
- ...(media.attachments.length ? { attachments: media.attachments } : {}),
52
+ ...(attachments.length ? { attachments } : {}),
48
53
  ...(sessionId ? { sessionId } : {}),
49
54
  }),
50
55
  });
@@ -0,0 +1,97 @@
1
+ 'use strict';
2
+
3
+ const assert = require('node:assert/strict');
4
+ const test = require('node:test');
5
+
6
+ const {
7
+ fileBasenameOf,
8
+ renderEntityEmbed,
9
+ renderMediaEmbed,
10
+ resolveFileEmbedIdentity,
11
+ } = require('../lib/email-embeds');
12
+ const { prepareEmailIconAttachments } = require('../lib/email-icons');
13
+ const { markdownToEmailHtml } = require('../lib/email-md');
14
+
15
+ test('file identity decodes local, amalgm, url, and attached sources', () => {
16
+ assert.equal(fileBasenameOf('/Users/me/Recordings/flower.webm'), 'flower.webm');
17
+ assert.equal(fileBasenameOf('amalgm://file/%2FUsers%2Fme%2Fsample.pdf'), 'sample.pdf');
18
+ assert.equal(fileBasenameOf('https://example.com/files/report%20final.pdf?token=abc'), 'report final.pdf');
19
+ assert.equal(fileBasenameOf('attached:qa-pass.webm'), 'qa-pass.webm');
20
+ });
21
+
22
+ test('attached video embed uses filename for the floating tab, not markdown alt', () => {
23
+ const html = renderMediaEmbed('QA recording', 'attached:qa-pass.webm', 'cid:poster-1');
24
+
25
+ assert.match(html, /qa-pass\.webm/);
26
+ assert.doesNotMatch(html, /QA recording/);
27
+ assert.match(html, /role="presentation" width="100%"/);
28
+ assert.match(html, /display:inline-table;height:32px/);
29
+ assert.match(html, /cid:amalgm-icon-lucide-video-f472b6/);
30
+ assert.match(html, /cid:amalgm-icon-lucide-externallink-ffffff/);
31
+ assert.doesNotMatch(html, /<svg/);
32
+ assert.doesNotMatch(html, /position:absolute/);
33
+ assert.match(html, /font-weight:400/);
34
+ assert.doesNotMatch(html, /\.\.\./);
35
+ assert.doesNotMatch(html, /▸/);
36
+ });
37
+
38
+ test('email icon cid references become inline png attachments', () => {
39
+ const html = renderMediaEmbed('QA recording', 'attached:qa-pass.webm', 'cid:poster-1');
40
+ const icons = prepareEmailIconAttachments(html, []);
41
+
42
+ assert.equal(icons.length, 2);
43
+ assert.equal(icons[0].contentType, 'image/png');
44
+ assert.deepEqual(
45
+ icons.map((attachment) => attachment.contentId).sort(),
46
+ ['amalgm-icon-lucide-externallink-ffffff', 'amalgm-icon-lucide-video-f472b6'],
47
+ );
48
+ });
49
+
50
+ test('long email embed tabs use faded title text instead of hard ellipsis', () => {
51
+ const html = renderMediaEmbed(
52
+ 'QA recording',
53
+ 'attached:amalgm-new-chat-flow-2026-06-12T05-57-32-525Z.webm',
54
+ 'cid:poster-1',
55
+ );
56
+
57
+ assert.match(html, /amalgm-new-c/);
58
+ assert.match(html, /rgba\(255,255,255,0\.75\)/);
59
+ assert.match(html, /font-weight:400/);
60
+ assert.doesNotMatch(html, /\.\.\./);
61
+ });
62
+
63
+ test('local file fallback uses chrome identity only, not email-specific metadata rows', () => {
64
+ const html = renderMediaEmbed('Context label', 'amalgm://file/%2Ftmp%2Fdocx-experiment-sandbox.docx', null);
65
+
66
+ assert.match(html, /docx-experiment-sandbox\.docx/);
67
+ assert.doesNotMatch(html, /Context label/);
68
+ assert.doesNotMatch(html, /On your computer/);
69
+ });
70
+
71
+ test('standalone media lines accept local file paths with spaces', () => {
72
+ const html = markdownToEmailHtml('![Context label](/tmp/amalgm repo/sample file.pdf)');
73
+
74
+ assert.match(html, /sample file\.pdf/);
75
+ assert.match(html, /display:inline-table;height:32px/);
76
+ assert.doesNotMatch(html, /Context label/);
77
+ });
78
+
79
+ test('full entity embed uses chrome identity only, not centered email-only body text', () => {
80
+ const html = renderEntityEmbed('full', 'app', 'scout');
81
+
82
+ assert.match(html, /scout/);
83
+ assert.doesNotMatch(html, /text-transform:uppercase/);
84
+ });
85
+
86
+ test('file identity prefers explicit filename over path and url', () => {
87
+ const identity = resolveFileEmbedIdentity({
88
+ name: 'renamed.mov',
89
+ path: '/tmp/original.webm',
90
+ url: 'https://example.com/original.webm',
91
+ mimeType: 'video/quicktime',
92
+ });
93
+
94
+ assert.equal(identity.label, 'renamed.mov');
95
+ assert.equal(identity.kind, 'video');
96
+ assert.equal(identity.mimeType, 'video/quicktime');
97
+ });
@@ -32,3 +32,22 @@ test('prepareEmailMedia attaches local video and prefers sibling animated GIF pr
32
32
  fs.rmSync(dir, { recursive: true, force: true });
33
33
  }
34
34
  });
35
+
36
+ test('prepareEmailMedia accepts absolute media paths with spaces', () => {
37
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'amalgm email media-'));
38
+ try {
39
+ const videoPath = path.join(dir, 'qa pass.webm');
40
+ const gifPath = path.join(dir, 'qa pass.preview.gif');
41
+ fs.writeFileSync(videoPath, Buffer.from('not really a webm but enough for extension detection'));
42
+ fs.writeFileSync(gifPath, Buffer.from('GIF89a'));
43
+
44
+ const result = prepareEmailMedia(`![QA recording](${videoPath})`);
45
+
46
+ assert.match(result.message, /!\[QA recording\]\(attached:qa%20pass\.webm "cid:poster-1"\)/);
47
+ assert.equal(result.attachments.length, 2);
48
+ assert.equal(result.attachments[0].filename, 'qa pass.webm');
49
+ assert.equal(result.attachments[1].filename, 'qa pass.preview.gif');
50
+ } finally {
51
+ fs.rmSync(dir, { recursive: true, force: true });
52
+ }
53
+ });