attachpage 0.1.0 → 0.1.2
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 +1 -1
- package/README.md +13 -3
- package/bin/attachpage.mjs +177 -8
- package/package.json +9 -4
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Send a website like an attachment. Point it at a folder with an `index.html`; get a link.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Sign in once with your Google or Microsoft account:
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
+
npx attachpage connect # add AttachPage to Claude Code, Codex, Cursor, VS Code, Gemini CLI
|
|
8
9
|
npx attachpage login # approve in your browser, once
|
|
9
10
|
npx attachpage ./dist # publish (private by default)
|
|
10
11
|
npx attachpage send ./dist --to client@example.com --message "Here's the new version"
|
|
@@ -15,8 +16,8 @@ npx attachpage ./dist --site <pageId> --notify "Applied your feedback"
|
|
|
15
16
|
Pages are private by default: only the people you send them to can open them, each with a
|
|
16
17
|
personal link you can revoke. Recipients can comment on the page and approve it; `feedback`
|
|
17
18
|
lists what they said with the element they pointed at, which is handy for agents. Add `--public`
|
|
18
|
-
to make a page open to anyone with the link.
|
|
19
|
-
|
|
19
|
+
to make a page open to anyone with the link. Without signing in, `npx attachpage ./dist` publishes a
|
|
20
|
+
public page that expires after 48 hours and prints a link to keep it.
|
|
20
21
|
|
|
21
22
|
- No dependencies, Node 18+.
|
|
22
23
|
- Your files are uploaded exactly as they are. Nothing is built, bundled or rewritten.
|
|
@@ -48,3 +49,12 @@ Setup for Claude, ChatGPT, Cursor, VS Code and Codex: https://attachpage.com/age
|
|
|
48
49
|
Agent instructions: https://attachpage.com/skill.md.
|
|
49
50
|
|
|
50
51
|
Support: support@attachpage.com. MIT licensed, by JP Technology Services.
|
|
52
|
+
|
|
53
|
+
## Connect your AI apps
|
|
54
|
+
|
|
55
|
+
`npx attachpage connect` adds the AttachPage MCP server (`https://attachpage.com/mcp`) to the AI apps
|
|
56
|
+
it finds: Claude Code, OpenAI Codex and Gemini CLI through their own commands, and Cursor and VS Code
|
|
57
|
+
through their user config files (backed up first; existing entries and files it cannot parse are left
|
|
58
|
+
alone). `--dry-run` shows what it would change; `--client cursor,vscode` limits it to some apps. Each
|
|
59
|
+
app asks you to sign in to AttachPage the first time you use it.
|
|
60
|
+
|
package/bin/attachpage.mjs
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* AttachPage command line: publish a folder, send it to people, read their feedback.
|
|
4
4
|
*
|
|
5
|
-
* npx attachpage ./dist publish
|
|
5
|
+
* npx attachpage ./dist publish (signed in: private; signed out: public for 48 h)
|
|
6
6
|
* npx attachpage send ./dist --to a@b.com publish privately and email personal links
|
|
7
7
|
* npx attachpage login sign in from the terminal (approve in your browser)
|
|
8
8
|
* npx attachpage feedback <siteId> what recipients said (open items)
|
|
9
|
+
* npx attachpage connect add AttachPage to the AI apps on this computer
|
|
9
10
|
*
|
|
10
11
|
* No dependencies. Credentials: ATTACHPAGE_API_KEY, or the key `login` saved in the operating system's
|
|
11
12
|
* password store (or in ~/.attachpage/credentials with `login --store file`).
|
|
@@ -15,7 +16,7 @@ import { chmod, mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/pr
|
|
|
15
16
|
import { homedir } from "node:os";
|
|
16
17
|
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
17
18
|
|
|
18
|
-
const VERSION = "0.1.
|
|
19
|
+
const VERSION = "0.1.1";
|
|
19
20
|
const CREDENTIALS = process.env.ATTACHPAGE_CREDENTIALS_FILE || join(homedir(), ".attachpage", "credentials");
|
|
20
21
|
const SKIP_DIRS = new Set([".git", "node_modules", ".svn", ".hg"]);
|
|
21
22
|
const SKIP_FILES = new Set([".DS_Store", "Thumbs.db", "desktop.ini"]);
|
|
@@ -404,9 +405,7 @@ async function publish({ send = false } = {}) {
|
|
|
404
405
|
const anon = await api(origin, "POST", "/api/v1/anonymous/publish", {
|
|
405
406
|
body: { siteName: name, slug, files: manifest },
|
|
406
407
|
}).catch((e) =>
|
|
407
|
-
fail(
|
|
408
|
-
e.status === 403 ? `${e.message}\nSign in with an invited account: npx attachpage login` : e.message,
|
|
409
|
-
),
|
|
408
|
+
fail(e.status === 403 ? `${e.message}\nSign in to publish: npx attachpage login` : e.message),
|
|
410
409
|
);
|
|
411
410
|
await uploadAll(anon.targets, files, anon.uploadToken, origin);
|
|
412
411
|
const fin = await api(origin, "POST", anon.next, { key: anon.uploadToken, body: {} });
|
|
@@ -562,17 +561,186 @@ async function whoami() {
|
|
|
562
561
|
});
|
|
563
562
|
}
|
|
564
563
|
|
|
564
|
+
// ---------- connect ----------
|
|
565
|
+
//
|
|
566
|
+
// Add the AttachPage MCP server to the AI apps on this computer. Apps with their own command line
|
|
567
|
+
// (Claude Code, Codex, Gemini CLI) are set up through it; Cursor and VS Code through their user
|
|
568
|
+
// config file, backed up first. Existing AttachPage entries are left alone, and a file that cannot be
|
|
569
|
+
// read as plain JSON is never rewritten (the snippet to paste is printed instead).
|
|
570
|
+
|
|
571
|
+
const CONNECT_HOME = process.env.ATTACHPAGE_CONNECT_HOME || homedir();
|
|
572
|
+
|
|
573
|
+
/** Run an app's own CLI. Windows npm shims are .cmd files, which need a shell; every argument is a
|
|
574
|
+
* fixed token or the validated server URL, so nothing needs quoting. */
|
|
575
|
+
function runCli(bin, args) {
|
|
576
|
+
return new Promise((done) => {
|
|
577
|
+
let child;
|
|
578
|
+
try {
|
|
579
|
+
child = spawn(bin, args, {
|
|
580
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
581
|
+
shell: process.platform === "win32",
|
|
582
|
+
windowsHide: true,
|
|
583
|
+
});
|
|
584
|
+
} catch {
|
|
585
|
+
return done({ code: -1, missing: true, out: "" });
|
|
586
|
+
}
|
|
587
|
+
let out = "";
|
|
588
|
+
child.stdout.on("data", (d) => {
|
|
589
|
+
out += d;
|
|
590
|
+
});
|
|
591
|
+
child.stderr.on("data", (d) => {
|
|
592
|
+
out += d;
|
|
593
|
+
});
|
|
594
|
+
child.on("error", () => done({ code: -1, missing: true, out }));
|
|
595
|
+
child.on("close", (code) =>
|
|
596
|
+
done({ code: code ?? -1, missing: code === 9009 || /not recognized|not found/i.test(out), out }),
|
|
597
|
+
);
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
async function readJsonFile(file) {
|
|
602
|
+
try {
|
|
603
|
+
return { ok: true, data: JSON.parse(await readFile(file, "utf8")) };
|
|
604
|
+
} catch (e) {
|
|
605
|
+
return e.code === "ENOENT" ? { ok: true, data: {} } : { ok: false };
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
async function dirExists(dir) {
|
|
610
|
+
try {
|
|
611
|
+
return (await stat(dir)).isDirectory();
|
|
612
|
+
} catch {
|
|
613
|
+
return false;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/** Add `entry` under `root[key].attachpage` in a JSON config file. */
|
|
618
|
+
async function addToJsonConfig(file, key, entry, dryRun) {
|
|
619
|
+
const read = await readJsonFile(file);
|
|
620
|
+
if (!read.ok)
|
|
621
|
+
return {
|
|
622
|
+
status: "manual",
|
|
623
|
+
note: `could not read ${file} as plain JSON; add this yourself:\n${JSON.stringify({ [key]: { attachpage: entry } }, null, 2)}`,
|
|
624
|
+
};
|
|
625
|
+
const data = read.data && typeof read.data === "object" ? read.data : {};
|
|
626
|
+
if (data[key]?.attachpage) return { status: "already" };
|
|
627
|
+
if (dryRun) return { status: "would-add", note: file };
|
|
628
|
+
try {
|
|
629
|
+
const existing = await readFile(file, "utf8");
|
|
630
|
+
await writeFile(`${file}.bak-attachpage`, existing, { flag: "wx" }).catch(() => {});
|
|
631
|
+
} catch {
|
|
632
|
+
/* new file */
|
|
633
|
+
}
|
|
634
|
+
data[key] = { ...(data[key] ?? {}), attachpage: entry };
|
|
635
|
+
await mkdir(dirname(file), { recursive: true });
|
|
636
|
+
await writeFile(file, `${JSON.stringify(data, null, 2)}\n`);
|
|
637
|
+
return { status: "added", note: file };
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function vscodeUserDir() {
|
|
641
|
+
if (process.platform === "win32")
|
|
642
|
+
return join(process.env.APPDATA || join(CONNECT_HOME, "AppData", "Roaming"), "Code", "User");
|
|
643
|
+
if (process.platform === "darwin")
|
|
644
|
+
return join(CONNECT_HOME, "Library", "Application Support", "Code", "User");
|
|
645
|
+
return join(CONNECT_HOME, ".config", "Code", "User");
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
async function connect() {
|
|
649
|
+
const { origin } = await readCredentials();
|
|
650
|
+
if (!SAFE.test(origin)) fail("That --origin cannot be used here.");
|
|
651
|
+
const url = `${origin}/mcp`;
|
|
652
|
+
const dryRun = flags.has("dry-run");
|
|
653
|
+
const only = typeof flags.get("client") === "string" ? String(flags.get("client")).split(",") : null;
|
|
654
|
+
const viaCli = [
|
|
655
|
+
{
|
|
656
|
+
id: "claude-code",
|
|
657
|
+
name: "Claude Code",
|
|
658
|
+
bin: "claude",
|
|
659
|
+
args: ["mcp", "add", "--transport", "http", "--scope", "user", "attachpage", url],
|
|
660
|
+
next: "In Claude Code, run /mcp and sign in to AttachPage.",
|
|
661
|
+
},
|
|
662
|
+
{
|
|
663
|
+
id: "codex",
|
|
664
|
+
name: "OpenAI Codex",
|
|
665
|
+
bin: "codex",
|
|
666
|
+
args: ["mcp", "add", "attachpage", "--url", url],
|
|
667
|
+
next: "Run: codex mcp login attachpage",
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
id: "gemini",
|
|
671
|
+
name: "Gemini CLI",
|
|
672
|
+
bin: "gemini",
|
|
673
|
+
args: ["mcp", "add", "--transport", "http", "attachpage", url],
|
|
674
|
+
next: "In Gemini CLI, run /mcp auth attachpage.",
|
|
675
|
+
},
|
|
676
|
+
];
|
|
677
|
+
const results = [];
|
|
678
|
+
for (const c of viaCli) {
|
|
679
|
+
if (only && !only.includes(c.id)) continue;
|
|
680
|
+
const probe = await runCli(c.bin, ["--version"]);
|
|
681
|
+
if (probe.missing || probe.code !== 0) {
|
|
682
|
+
results.push({ app: c.name, status: "not-found" });
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
if (dryRun) {
|
|
686
|
+
results.push({ app: c.name, status: "would-add", note: `${c.bin} ${c.args.join(" ")}` });
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
const r = await runCli(c.bin, c.args);
|
|
690
|
+
if (r.code === 0) results.push({ app: c.name, status: "added", note: c.next });
|
|
691
|
+
else if (/already exists|already configured|already added/i.test(r.out))
|
|
692
|
+
results.push({ app: c.name, status: "already" });
|
|
693
|
+
else results.push({ app: c.name, status: "failed", note: r.out.trim().split("\n").slice(-1)[0] });
|
|
694
|
+
}
|
|
695
|
+
if (!only || only.includes("cursor")) {
|
|
696
|
+
const dir = join(CONNECT_HOME, ".cursor");
|
|
697
|
+
results.push(
|
|
698
|
+
(await dirExists(dir))
|
|
699
|
+
? { app: "Cursor", ...(await addToJsonConfig(join(dir, "mcp.json"), "mcpServers", { url }, dryRun)) }
|
|
700
|
+
: { app: "Cursor", status: "not-found" },
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
if (!only || only.includes("vscode")) {
|
|
704
|
+
const dir = vscodeUserDir();
|
|
705
|
+
results.push(
|
|
706
|
+
(await dirExists(dir))
|
|
707
|
+
? {
|
|
708
|
+
app: "VS Code",
|
|
709
|
+
...(await addToJsonConfig(join(dir, "mcp.json"), "servers", { type: "http", url }, dryRun)),
|
|
710
|
+
}
|
|
711
|
+
: { app: "VS Code", status: "not-found" },
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
const label = {
|
|
715
|
+
added: "added",
|
|
716
|
+
already: "already set up",
|
|
717
|
+
"would-add": "would add",
|
|
718
|
+
"not-found": "not installed",
|
|
719
|
+
manual: "needs a manual step",
|
|
720
|
+
failed: "failed",
|
|
721
|
+
};
|
|
722
|
+
const lines = results.map((r) => ` ${r.app.padEnd(14)} ${label[r.status]}${r.note ? `: ${r.note}` : ""}`);
|
|
723
|
+
lines.push(
|
|
724
|
+
"",
|
|
725
|
+
`Server: ${url}`,
|
|
726
|
+
"Each app asks you to sign in to AttachPage the first time you use it.",
|
|
727
|
+
"Claude and ChatGPT on the web: Settings > Connectors > Add custom connector, with the server above.",
|
|
728
|
+
);
|
|
729
|
+
out(lines.join("\n"), { server: url, dryRun, results });
|
|
730
|
+
}
|
|
731
|
+
|
|
565
732
|
const HELP = `attachpage ${VERSION}: send websites like attachments
|
|
566
733
|
|
|
567
734
|
Usage
|
|
568
735
|
attachpage <folder> [--name "Q3 report"] [--slug q3-report] [--public] [--site <id> [--notify "note"]]
|
|
569
736
|
attachpage send <folder> --to a@b.com,c@d.com [--message "..."] [--expires 7]
|
|
570
737
|
attachpage login [--no-browser] [--store file] | logout | whoami
|
|
738
|
+
attachpage connect [--client claude-code,codex,gemini,cursor,vscode] [--dry-run]
|
|
571
739
|
attachpage feedback <siteId> [--all]
|
|
572
740
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
741
|
+
Sign in once with \`attachpage login\` (Google or Microsoft account). Signed-in pages are private by
|
|
742
|
+
default: only the people you send them to can open them. Without signing in, a folder is published
|
|
743
|
+
as a public page that expires in 48 hours, with a link to keep it.
|
|
576
744
|
Options: --json for machine-readable output, --origin to use another server.
|
|
577
745
|
\`login\` keeps the key in your system's password store (Windows Credential Manager, macOS Keychain,
|
|
578
746
|
or the Linux keyring); --store file keeps it in ~/.attachpage/credentials instead.`;
|
|
@@ -584,6 +752,7 @@ try {
|
|
|
584
752
|
else if (command === "login") await login();
|
|
585
753
|
else if (command === "logout") await logout();
|
|
586
754
|
else if (command === "whoami") await whoami();
|
|
755
|
+
else if (command === "connect") await connect();
|
|
587
756
|
else if (command === "feedback") await feedback();
|
|
588
757
|
else if (command === "send") await publish({ send: true });
|
|
589
758
|
else await publish();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "attachpage",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Send websites like attachments: publish a folder, email personal links, read feedback. Command line for AttachPage
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Send websites like attachments: publish a folder, email personal links, read feedback. Command line for AttachPage.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"publish",
|
|
7
7
|
"static-site",
|
|
@@ -12,11 +12,16 @@
|
|
|
12
12
|
"attachpage"
|
|
13
13
|
],
|
|
14
14
|
"homepage": "https://attachpage.com",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/colossalbreacker/attachpage-skill.git",
|
|
18
|
+
"directory": "cli"
|
|
19
|
+
},
|
|
15
20
|
"bugs": {
|
|
16
21
|
"email": "support@attachpage.com"
|
|
17
22
|
},
|
|
18
23
|
"license": "MIT",
|
|
19
|
-
"author": "JP Technology Services (https://jptechnologyservices.com)",
|
|
24
|
+
"author": "JP Technology Services LLC (https://jptechnologyservices.com)",
|
|
20
25
|
"type": "module",
|
|
21
26
|
"bin": {
|
|
22
27
|
"attachpage": "bin/attachpage.mjs"
|
|
@@ -25,7 +30,7 @@
|
|
|
25
30
|
"bin/attachpage.mjs"
|
|
26
31
|
],
|
|
27
32
|
"scripts": {
|
|
28
|
-
"test": "node --test test/credentials.test.mjs"
|
|
33
|
+
"test": "node --test test/credentials.test.mjs test/connect.test.mjs"
|
|
29
34
|
},
|
|
30
35
|
"engines": {
|
|
31
36
|
"node": ">=18"
|