artifactsend 0.0.0-stage → 1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vela Partners
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,74 @@
1
- # Temporary Holding Version
1
+ # ArtifactSend: tracked links and data rooms for AI agents
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **Share a document, pitch deck, HTML page or Claude artifact as a link, and see who read it.** Each reader, how far they read, time per section or slide, clicks, copied text, and the questions they asked. A free DocSend alternative, built for agents: an MCP server, agent skills, a Claude Code plugin and a CLI.
4
+
5
+ - Website: https://artifactsend.com
6
+ - MCP server: `https://artifactsend.com/mcp` (Streamable HTTP, OAuth)
7
+ - Official MCP registry: `com.artifactsend/mcp`
8
+ - For agents: https://artifactsend.com/llms.txt
9
+
10
+ ## When an agent should use it
11
+
12
+ - "Share this with Dana and tell me when she reads it."
13
+ - "Send the deck to these five investors, one link each."
14
+ - "Publish this HTML page or Claude artifact as a private link."
15
+ - "Make a data room for the Series A with an NDA."
16
+ - "Who read the proposal? What did they ask?"
17
+ - "Read this artifactsend.com link someone sent me."
18
+
19
+ ## Install
20
+
21
+ | Where | How |
22
+ | --- | --- |
23
+ | Claude Code | `claude mcp add --transport http artifactsend https://artifactsend.com/mcp`, then `/mcp` to sign in |
24
+ | Every agent on this computer | `npx artifactsend setup` (Claude Code, Cursor, VS Code, Windsurf, Gemini CLI, Codex, plus the skills) |
25
+ | Skills | `npx skills add LLM-preneur/skills` |
26
+ | Claude Code plugin | `/plugin marketplace add LLM-preneur/skills`, then `/plugin install artifactsend@llm-preneur` |
27
+ | Cursor | [Add to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=artifactsend&config=eyJ1cmwiOiJodHRwczovL2FydGlmYWN0c2VuZC5jb20vbWNwIn0=) |
28
+ | VS Code | [Add to VS Code](https://insiders.vscode.dev/redirect/mcp/install?name=artifactsend&config=%7B%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A//artifactsend.com/mcp%22%7D) |
29
+ | Claude.ai | Settings, Connectors, Add custom connector, URL `https://artifactsend.com/mcp` |
30
+ | ChatGPT | Settings, Apps, Developer mode on, Create app, URL `https://artifactsend.com/mcp`, OAuth |
31
+ | Codex | `codex mcp add artifactsend --url https://artifactsend.com/mcp` |
32
+ | Gemini CLI | `gemini mcp add --transport http artifactsend https://artifactsend.com/mcp` |
33
+ | stdio-only clients | `npx -y artifactsend mcp` with `ARTIFACTSEND_TOKEN` set |
34
+
35
+ Sign in once with your email or Google. Links, readers and reports are free.
36
+
37
+ ## Skills
38
+
39
+ | Skill | Use it when |
40
+ | --- | --- |
41
+ | [share-with-tracking](https://github.com/LLM-preneur/skills/tree/main/skills/share-with-tracking/SKILL.md) | sharing a file or page and knowing who read it; one link per person; reading reports |
42
+ | [data-room](https://github.com/LLM-preneur/skills/tree/main/skills/data-room/SKILL.md) | several documents behind one link, with folders, an NDA and watermarks |
43
+ | [read-shared-link](https://github.com/LLM-preneur/skills/tree/main/skills/read-shared-link/SKILL.md) | someone sent you an artifactsend.com link |
44
+
45
+ ## MCP tools
46
+
47
+ **Share:** `send_artifact` (HTML, React, Markdown, PDF, Word, PowerPoint, Excel or a URL), `update_artifact`, `link_for_person`, `make_data_room`, `upload_file`, `get_artifact`, `update_link`.
48
+
49
+ **See who read it:** `list_links`, `link_report`, `reader_activity`, `recent_activity`, `room_report`, `list_questions`, `list_comments`, `website_visits`, `schedule_digest`.
50
+
51
+ **Read what others share with you:** `open_link`, `read_shared`, `ask_shared`, `message_shared_sender`, `shared_with_me`.
52
+
53
+ Reading tools are marked read-only. Tools that change things are marked so the app asks first.
54
+
55
+ ## CLI
56
+
57
+ ```bash
58
+ npx artifactsend setup # add ArtifactSend to your agents
59
+ export ARTIFACTSEND_TOKEN=as_... # from https://artifactsend.com/app/settings
60
+ npx artifactsend send deck.pdf --title "Seed deck" --access email
61
+ npx artifactsend send page.html --allow @acme.com --expires 14
62
+ npx artifactsend links
63
+ npx artifactsend report https://artifactsend.com/d/ab12cd34
64
+ ```
65
+
66
+ ## Readers and their AI
67
+
68
+ Readers open a link in the browser, or through their own AI. Every link also has its own MCP address (the link plus `/mcp`) that needs no key. By default a reader's AI gets the outline and only the parts that answer each question, so the sender sees every question and no full copy leaves. Readers are told what the sender sees, and never their chat or their AI's answers. Rules: https://artifactsend.com/ai-rules
69
+
70
+ ## Price
71
+
72
+ Free and unlimited for links, readers, doors, reports and data rooms. Sending and updating through MCP is free. AI answers to readers are paid per answer. https://artifactsend.com/pricing
73
+
74
+ Made by [Vela Partners](https://vela.partners). MIT licensed.
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env node
2
+ // ArtifactSend from the command line: add the MCP server to your agents, send a file as a tracked link,
3
+ // read who opened it, or bridge stdio-only MCP clients to https://artifactsend.com/mcp. No dependencies.
4
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, copyFileSync } from 'node:fs';
5
+ import { homedir, platform } from 'node:os';
6
+ import { join, dirname, basename, extname } from 'node:path';
7
+ import { spawnSync } from 'node:child_process';
8
+ import { createInterface } from 'node:readline';
9
+
10
+ const ORIGIN = (process.env.ARTIFACTSEND_URL || 'https://artifactsend.com').replace(/\/$/, '');
11
+ const MCP = ORIGIN + '/mcp';
12
+ const SKILLS = 'LLM-preneur/skills';
13
+ const VERSION = '1.0.0';
14
+ const [cmd, ...rest] = process.argv.slice(2);
15
+
16
+ const HELP = `ArtifactSend: the link that tells you who read it.
17
+
18
+ npx artifactsend setup Add ArtifactSend to Claude Code, Cursor, VS Code, Windsurf, Gemini CLI and Codex
19
+ npx artifactsend send <file> Share a file as a tracked link (HTML, Markdown, PDF, Word, PowerPoint, Excel, JSX)
20
+ --title "Seed deck" --access public|email|verified --allow @acme.com,dana@x.com
21
+ --expires 14 --folder Investors --ai questions|full|off --link <existing link to update>
22
+ npx artifactsend links Your links with views and last open
23
+ npx artifactsend report <link> Who read it, how far, time per section, what they asked
24
+ npx artifactsend mcp Run as a stdio MCP server (bridges to ${MCP})
25
+
26
+ send, links, report and mcp need ARTIFACTSEND_TOKEN (make one at ${ORIGIN}/app/settings).
27
+ In Claude Code you can skip the token: claude mcp add --transport http artifactsend ${MCP}, then /mcp to sign in.
28
+ Claude.ai and ChatGPT: add a custom connector with the URL ${MCP}.`;
29
+
30
+ const flags = (args) => {
31
+ const o = { _: [] };
32
+ for (let i = 0; i < args.length; i++) {
33
+ const a = args[i];
34
+ if (a.startsWith('--')) { const k = a.slice(2); const n = args[i + 1]; if (n === undefined || n.startsWith('--')) o[k] = true; else { o[k] = n; i++; } } else o._.push(a);
35
+ }
36
+ return o;
37
+ };
38
+
39
+ // ---------- setup ----------
40
+ const readJson = (f) => { if (!existsSync(f)) return {}; const t = readFileSync(f, 'utf8').trim(); return t ? JSON.parse(t) : {}; };
41
+ const writeJson = (f, o) => { mkdirSync(dirname(f), { recursive: true }); if (existsSync(f)) copyFileSync(f, f + '.bak'); writeFileSync(f, JSON.stringify(o, null, 2) + '\n'); };
42
+ const has = (bin) => spawnSync(platform() === 'win32' ? 'where' : 'which', [bin], { stdio: 'ignore' }).status === 0;
43
+
44
+ function addJson(label, file, key, entry, dirMustExist) {
45
+ if (!existsSync(dirMustExist)) return null;
46
+ let o;
47
+ try { o = readJson(file); } catch { return `${label}: skipped, ${file} is not plain JSON. Add ${JSON.stringify({ artifactsend: entry })} under "${key}" yourself.`; }
48
+ o[key] = o[key] || {};
49
+ if (o[key].artifactsend) return `${label}: already added`;
50
+ o[key].artifactsend = entry;
51
+ writeJson(file, o);
52
+ return `${label}: added to ${file}`;
53
+ }
54
+
55
+ function setup(opts) {
56
+ const h = homedir();
57
+ const out = [];
58
+ if (has('claude')) {
59
+ const r = spawnSync('claude', ['mcp', 'add', '--transport', 'http', '--scope', 'user', 'artifactsend', MCP], { encoding: 'utf8' });
60
+ const said = (r.stdout + r.stderr).toLowerCase();
61
+ out.push(r.status === 0 ? 'Claude Code: added. Run /mcp in Claude Code and choose artifactsend to sign in.' : said.includes('already exists') ? 'Claude Code: already added' : 'Claude Code: could not add (' + said.trim().split('\n')[0] + ')');
62
+ }
63
+ out.push(addJson('Cursor', join(h, '.cursor', 'mcp.json'), 'mcpServers', { url: MCP }, join(h, '.cursor')));
64
+ out.push(addJson('Windsurf', join(h, '.codeium', 'windsurf', 'mcp_config.json'), 'mcpServers', { serverUrl: MCP }, join(h, '.codeium', 'windsurf')));
65
+ out.push(addJson('Gemini CLI', join(h, '.gemini', 'settings.json'), 'mcpServers', { httpUrl: MCP }, join(h, '.gemini')));
66
+ const code = platform() === 'darwin' ? join(h, 'Library', 'Application Support', 'Code', 'User') : platform() === 'win32' ? join(process.env.APPDATA || '', 'Code', 'User') : join(h, '.config', 'Code', 'User');
67
+ out.push(addJson('VS Code', join(code, 'mcp.json'), 'servers', { type: 'http', url: MCP }, code));
68
+ const codex = join(h, '.codex');
69
+ if (existsSync(codex)) {
70
+ const f = join(codex, 'config.toml');
71
+ const t = existsSync(f) ? readFileSync(f, 'utf8') : '';
72
+ if (/^\[mcp_servers\.artifactsend\]/m.test(t)) out.push('Codex: already added');
73
+ else { if (t) copyFileSync(f, f + '.bak'); writeFileSync(f, t + (t && !t.endsWith('\n') ? '\n' : '') + `\n[mcp_servers.artifactsend]\nurl = "${MCP}"\n`); out.push(`Codex: added to ${f}`); }
74
+ }
75
+ const done = out.filter(Boolean);
76
+ if (!opts['no-skills']) {
77
+ const r = spawnSync('npx', ['-y', 'skills', 'add', SKILLS, '-g', '-y'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], shell: platform() === 'win32' });
78
+ done.push(r.status === 0 ? `Skills: installed ${SKILLS} (share-with-tracking, data-room, read-shared-link)` : `Skills: run npx skills add ${SKILLS} to install them`);
79
+ }
80
+ console.log(done.length ? done.join('\n') : 'No agents found on this computer.');
81
+ console.log(`\nClaude.ai and ChatGPT: add a custom connector with the URL ${MCP}\nThen ask your agent: "Share this as a tracked link and tell me who reads it."`);
82
+ }
83
+
84
+ // ---------- MCP over HTTP ----------
85
+ const token = () => {
86
+ const t = process.env.ARTIFACTSEND_TOKEN;
87
+ if (!t) { console.error(`Set ARTIFACTSEND_TOKEN first. Make one at ${ORIGIN}/app/settings`); process.exit(2); }
88
+ return t;
89
+ };
90
+ let rpcId = 0;
91
+ async function rpc(method, params, tk = token()) {
92
+ const r = await fetch(MCP, { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json, text/event-stream', Authorization: 'Bearer ' + tk, 'User-Agent': 'artifactsend-cli/' + VERSION }, body: JSON.stringify({ jsonrpc: '2.0', id: ++rpcId, method, params }) });
93
+ const text = await r.text();
94
+ let body;
95
+ try { body = JSON.parse(text.startsWith('event:') || text.startsWith('data:') ? text.split('\n').filter((l) => l.startsWith('data:')).pop().slice(5) : text); } catch { throw new Error(`HTTP ${r.status}: ${text.slice(0, 200)}`); }
96
+ if (body.error) throw new Error(body.error.message);
97
+ return body.result;
98
+ }
99
+ async function call(name, args) {
100
+ const res = await rpc('tools/call', { name, arguments: args });
101
+ const text = (res.content || []).map((c) => c.text || '').join('\n');
102
+ if (res.isError) throw new Error(text);
103
+ try { return JSON.parse(text); } catch { return text; }
104
+ }
105
+ const print = (v) => console.log(typeof v === 'string' ? v : JSON.stringify(v, null, 2));
106
+
107
+ const DOCS = new Set(['.pdf', '.doc', '.docx', '.ppt', '.pptx', '.xls', '.xlsx', '.key', '.odt', '.odp', '.ods']);
108
+ async function send(o) {
109
+ const file = o._[0];
110
+ if (!file) { console.error('Usage: npx artifactsend send <file> [--title ...]'); process.exit(2); }
111
+ const ext = extname(file).toLowerCase();
112
+ const a = {};
113
+ if (/^https?:\/\//.test(file)) a.url = file;
114
+ else if (DOCS.has(ext)) a.document = { name: basename(file), base64: readFileSync(file).toString('base64') };
115
+ else if (ext === '.md' || ext === '.markdown') a.markdown = readFileSync(file, 'utf8');
116
+ else if (ext === '.jsx' || ext === '.tsx') a.react = readFileSync(file, 'utf8');
117
+ else a.html = readFileSync(file, 'utf8');
118
+ if (o.title) a.title = o.title;
119
+ if (o.access) a.access = o.access;
120
+ if (o.allow) a.allow = String(o.allow).split(',').map((s) => s.trim()).filter(Boolean);
121
+ if (o.expires) a.expires_in_days = Number(o.expires);
122
+ if (o.folder) a.folder = o.folder;
123
+ if (o.ai) a.ai_reading = o.ai;
124
+ if (o.link) a.link = o.link;
125
+ if (o['no-notify']) a.notify = false;
126
+ const r = await call('send_artifact', a);
127
+ if (r && r.url) { console.log(r.url); if (r.settings) for (const [k, v] of Object.entries(r.settings)) if (v !== null && typeof v !== 'object') console.log(` ${k.replace(/_/g, ' ')}: ${v}`); }
128
+ else print(r);
129
+ }
130
+
131
+ // stdio <-> HTTP bridge, one JSON-RPC message per line.
132
+ async function bridge() {
133
+ const tk = token();
134
+ const rl = createInterface({ input: process.stdin });
135
+ let sid = null;
136
+ for await (const line of rl) {
137
+ if (!line.trim()) continue;
138
+ let msg;
139
+ try { msg = JSON.parse(line); } catch { continue; }
140
+ try {
141
+ const r = await fetch(MCP, { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json, text/event-stream', Authorization: 'Bearer ' + tk, 'User-Agent': 'artifactsend-cli/' + VERSION, ...(sid ? { 'Mcp-Session-Id': sid } : {}) }, body: line });
142
+ sid = r.headers.get('mcp-session-id') || sid;
143
+ const text = await r.text();
144
+ if (!text.trim()) continue;
145
+ const parts = text.startsWith('event:') || text.startsWith('data:') ? text.split('\n').filter((l) => l.startsWith('data:')).map((l) => l.slice(5).trim()) : [text];
146
+ for (const p of parts) process.stdout.write(JSON.stringify(JSON.parse(p)) + '\n');
147
+ } catch (e) {
148
+ if (msg.id !== undefined) process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id: msg.id, error: { code: -32603, message: String(e.message || e) } }) + '\n');
149
+ }
150
+ }
151
+ }
152
+
153
+ const o = flags(rest);
154
+ try {
155
+ if (cmd === 'setup' || cmd === 'init' || cmd === 'install') setup(o);
156
+ else if (cmd === 'send') await send(o);
157
+ else if (cmd === 'links') print(await call('list_links', o._[0] ? { query: o._.join(' ') } : {}));
158
+ else if (cmd === 'report') { if (!o._[0]) { console.error('Usage: npx artifactsend report <link>'); process.exit(2); } print(await call('link_report', { link: o._[0] })); }
159
+ else if (cmd === 'mcp') await bridge();
160
+ else if (cmd === '--version' || cmd === '-v') console.log(VERSION);
161
+ else console.log(HELP);
162
+ } catch (e) {
163
+ console.error(String(e.message || e));
164
+ process.exit(1);
165
+ }
package/package.json CHANGED
@@ -1,6 +1,48 @@
1
1
  {
2
2
  "name": "artifactsend",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "Share documents, decks, HTML pages and Claude artifacts as tracked links and data rooms. See who read them and what they asked. MCP server, agent skills and CLI.",
5
+ "mcpName": "com.artifactsend/mcp",
6
+ "type": "module",
7
+ "bin": {
8
+ "artifactsend": "bin/artifactsend.mjs"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "engines": {
16
+ "node": ">=18"
17
+ },
18
+ "keywords": [
19
+ "mcp",
20
+ "mcp-server",
21
+ "model-context-protocol",
22
+ "agent-skills",
23
+ "claude",
24
+ "claude-code",
25
+ "chatgpt",
26
+ "cursor",
27
+ "docsend",
28
+ "docsend-alternative",
29
+ "document-tracking",
30
+ "data-room",
31
+ "share-html",
32
+ "publish-html",
33
+ "artifacts",
34
+ "pitch-deck",
35
+ "analytics"
36
+ ],
37
+ "homepage": "https://artifactsend.com",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/LLM-preneur/skills.git",
41
+ "directory": "artifactsend"
42
+ },
43
+ "bugs": {
44
+ "url": "https://github.com/LLM-preneur/skills/issues"
45
+ },
46
+ "author": "Vela Partners (https://vela.partners)",
47
+ "license": "MIT"
48
+ }