@formstr/mcp 0.3.1 → 0.4.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 +97 -3
- package/dist/index.js +203 -41
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,9 +21,27 @@ npx -y @formstr/mcp login
|
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
Subcommands: `formstr-mcp login` · `formstr-mcp whoami` · `formstr-mcp accounts` ·
|
|
24
|
-
`formstr-mcp switch <npub>` · `formstr-mcp logout` · `formstr-mcp
|
|
25
|
-
`formstr-mcp` (run the stdio server, the default). Run
|
|
26
|
-
the full usage.
|
|
24
|
+
`formstr-mcp switch <npub>` · `formstr-mcp logout` · `formstr-mcp version` ·
|
|
25
|
+
`formstr-mcp help` · `formstr-mcp` (run the stdio server, the default). Run
|
|
26
|
+
`formstr-mcp help` (or `-h`) for the full usage.
|
|
27
|
+
|
|
28
|
+
## Version & updates
|
|
29
|
+
|
|
30
|
+
`formstr-mcp version` (or `-v` / `--version`) prints the installed version and checks the
|
|
31
|
+
npm registry for a newer release:
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
$ formstr-mcp version
|
|
35
|
+
@formstr/mcp 0.4.0
|
|
36
|
+
Update available: 0.5.0 (you have 0.4.0).
|
|
37
|
+
Upgrade: npm install -g @formstr/mcp@latest
|
|
38
|
+
Or just re-run via: npx -y @formstr/mcp@latest
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The update check is best-effort — if you're offline or the registry is unreachable it
|
|
42
|
+
prints the installed version and a note, never an error. If you run the server via
|
|
43
|
+
`npx -y @formstr/mcp` you already get the latest published version on each launch; pin a
|
|
44
|
+
version (`@formstr/mcp@0.4.0`) in your host config if you'd rather control upgrades.
|
|
27
45
|
|
|
28
46
|
## Sign-in
|
|
29
47
|
|
|
@@ -71,6 +89,82 @@ After `login`, no key belongs in the config:
|
|
|
71
89
|
Add `"--allow-writes"` to `args` to enable the gated (destructive/outward) tools, and
|
|
72
90
|
`"--relays", "wss://a,wss://b"` to override relays.
|
|
73
91
|
|
|
92
|
+
### Passing the ncryptsec passphrase
|
|
93
|
+
|
|
94
|
+
If your active account is an `ncryptsec` key (Create / Import login), the server needs its
|
|
95
|
+
passphrase to unlock at boot. An MCP host spawns the server with stdin wired to the
|
|
96
|
+
JSON-RPC channel, so it **can't prompt** — supply the passphrase through an `"env"` block in
|
|
97
|
+
the server entry of your MCP config (`mcp_config.json`, `claude_desktop_config.json`, Cursor's
|
|
98
|
+
`~/.cursor/mcp.json`, etc.):
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"mcpServers": {
|
|
103
|
+
"formstr": {
|
|
104
|
+
"command": "npx",
|
|
105
|
+
"args": ["-y", "@formstr/mcp"],
|
|
106
|
+
"env": {
|
|
107
|
+
"FORMSTR_MCP_NCRYPTSEC_PASSPHRASE": "your-passphrase-here"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The host hands that value to the server as an environment variable at startup — it never
|
|
115
|
+
enters the chat transcript. Each account has its own passphrase, so this unlocks whichever
|
|
116
|
+
one is **active** (set it with `formstr-mcp switch <npub>`).
|
|
117
|
+
|
|
118
|
+
> **Tip:** prefer not to keep a passphrase in a config file? Use a **NIP-46 (bunker)**
|
|
119
|
+
> account instead — it reconnects from its stored session and needs **no** passphrase, so
|
|
120
|
+
> the config can stay secret-free. Run `formstr-mcp switch <npub>` to a bunker account.
|
|
121
|
+
|
|
122
|
+
## Using with Ollama (local models)
|
|
123
|
+
|
|
124
|
+
Ollama isn't an MCP client — it just runs the model. To drive this server with a **local**
|
|
125
|
+
model you need an MCP **host** that uses Ollama as its backend. A good, actively-maintained
|
|
126
|
+
option is [**Goose**](https://block.github.io/goose/) (open-source agent by Block; CLI +
|
|
127
|
+
desktop), which has both first-class Ollama support and native stdio MCP extensions.
|
|
128
|
+
|
|
129
|
+
**1. Pull a tool-calling-capable model and start Ollama:**
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
ollama pull qwen2.5 # llama3.1 / 3.2, mistral, … also work — the model MUST support tools
|
|
133
|
+
ollama serve # serves the API on http://localhost:11434
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
A model **without** tool/function-calling support can chat but can't invoke this server's
|
|
137
|
+
tools (`list_forms`, `create_form`, …), so don't pick one of those.
|
|
138
|
+
|
|
139
|
+
**2. Sign in to formstr once** (stores your key in the keystore):
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
npx -y @formstr/mcp login
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**3. Point Goose at Ollama** — `goose configure` → _Configure Providers_ → **Ollama**, then
|
|
146
|
+
enter the host (`http://localhost:11434`) and pick your model.
|
|
147
|
+
|
|
148
|
+
**4. Add this server as an extension** — `goose configure` → _Add Extension_ → **Command-line
|
|
149
|
+
Extension**, then answer the prompts:
|
|
150
|
+
|
|
151
|
+
| Prompt | Value |
|
|
152
|
+
| --------------------- | ------------------------------------------------------------- |
|
|
153
|
+
| Name | `formstr` |
|
|
154
|
+
| Command | `npx -y @formstr/mcp` (add `--allow-writes` to enable writes) |
|
|
155
|
+
| Timeout (secs) | `300` |
|
|
156
|
+
| Environment variables | `FORMSTR_MCP_NCRYPTSEC_PASSPHRASE` = _your passphrase_ |
|
|
157
|
+
|
|
158
|
+
Goose passes that env var to the server when it spawns it (so it unlocks headlessly), and
|
|
159
|
+
saves the extension to `~/.config/goose/config.yaml`. Then just run `goose` (or `goose
|
|
160
|
+
session`) and ask it to work with your forms.
|
|
161
|
+
|
|
162
|
+
> **Bunker accounts need no passphrase** — skip the env-variable step and `formstr-mcp switch
|
|
163
|
+
<npub>` to a NIP-46 account (see the tip above); the extension then stores no secret.
|
|
164
|
+
|
|
165
|
+
The same approach works with any other Ollama-backed MCP host: point it at
|
|
166
|
+
`npx -y @formstr/mcp` and supply the passphrase through that host's env mechanism.
|
|
167
|
+
|
|
74
168
|
## Headless / unattended
|
|
75
169
|
|
|
76
170
|
Run `formstr-mcp login` once interactively to populate the keystore, then run the server
|
package/dist/index.js
CHANGED
|
@@ -24436,8 +24436,21 @@ async function loginNostrConnect(deps, log) {
|
|
|
24436
24436
|
perms: NIP46_PERMS
|
|
24437
24437
|
});
|
|
24438
24438
|
}
|
|
24439
|
-
async function doLogout(signer,
|
|
24440
|
-
|
|
24439
|
+
async function doLogout(signer, target) {
|
|
24440
|
+
if (target) {
|
|
24441
|
+
const match = findAccount(listAccounts(signer), target);
|
|
24442
|
+
if (!match) {
|
|
24443
|
+
throw new Error(
|
|
24444
|
+
`No stored account matching "${target}". Run \`formstr-mcp accounts\` to list them.`
|
|
24445
|
+
);
|
|
24446
|
+
}
|
|
24447
|
+
await signer.logout(match.pubkey);
|
|
24448
|
+
return match;
|
|
24449
|
+
}
|
|
24450
|
+
const active = signer.getActiveAccount();
|
|
24451
|
+
if (!active) return null;
|
|
24452
|
+
await signer.logout(active.pubkey);
|
|
24453
|
+
return active;
|
|
24441
24454
|
}
|
|
24442
24455
|
function whoami(signer) {
|
|
24443
24456
|
return signer.getActiveAccount();
|
|
@@ -27480,12 +27493,23 @@ async function unlockNcryptsec(signer, account, deps) {
|
|
|
27480
27493
|
}
|
|
27481
27494
|
|
|
27482
27495
|
// src/cli.ts
|
|
27483
|
-
var SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
27496
|
+
var SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
27497
|
+
"login",
|
|
27498
|
+
"logout",
|
|
27499
|
+
"whoami",
|
|
27500
|
+
"accounts",
|
|
27501
|
+
"switch",
|
|
27502
|
+
"help",
|
|
27503
|
+
"version"
|
|
27504
|
+
]);
|
|
27484
27505
|
function parseCli(argv) {
|
|
27485
27506
|
const rest = [...argv];
|
|
27486
27507
|
if (rest.includes("-h") || rest.includes("--help")) {
|
|
27487
27508
|
return { command: "help", allowWrites: false };
|
|
27488
27509
|
}
|
|
27510
|
+
if (rest.includes("-v") || rest.includes("--version")) {
|
|
27511
|
+
return { command: "version", allowWrites: false };
|
|
27512
|
+
}
|
|
27489
27513
|
let command = "run";
|
|
27490
27514
|
if (rest[0] && SUBCOMMANDS.has(rest[0])) {
|
|
27491
27515
|
command = rest.shift();
|
|
@@ -27521,11 +27545,12 @@ function helpText() {
|
|
|
27521
27545
|
"Commands:",
|
|
27522
27546
|
" run Run the stdio MCP server (default when no command is given).",
|
|
27523
27547
|
" login Sign in (create / import / bunker URI / QR) and store the key.",
|
|
27524
|
-
" logout [npub]
|
|
27548
|
+
" logout [npub|hex] Permanently remove a stored account from the keystore (defaults to active).",
|
|
27525
27549
|
" whoami Print the active account.",
|
|
27526
27550
|
" accounts List stored accounts ('*' marks the active one).",
|
|
27527
27551
|
" switch <npub> Set the active account (accepts an npub or hex pubkey).",
|
|
27528
27552
|
" help Show this help (also -h, --help).",
|
|
27553
|
+
" version Print the installed version and check for an update (also -v, --version).",
|
|
27529
27554
|
"",
|
|
27530
27555
|
"Flags:",
|
|
27531
27556
|
" --allow-writes Enable gated write tools (update / delete / share / submit).",
|
|
@@ -27534,7 +27559,28 @@ function helpText() {
|
|
|
27534
27559
|
"",
|
|
27535
27560
|
"Env:",
|
|
27536
27561
|
" FORMSTR_MCP_NCRYPTSEC_PASSPHRASE Unlock the active ncryptsec account at boot.",
|
|
27537
|
-
" FORMSTR_MCP_PASSPHRASE Encrypt the keystore file (keychain-less hosts)."
|
|
27562
|
+
" FORMSTR_MCP_PASSPHRASE Encrypt the keystore file (keychain-less hosts).",
|
|
27563
|
+
"",
|
|
27564
|
+
"Setting the passphrase in your MCP host config:",
|
|
27565
|
+
" When an MCP host (Claude Desktop/Code, Cursor, \u2026) spawns the server, stdin is the",
|
|
27566
|
+
" JSON-RPC channel, so it can't prompt for your ncryptsec passphrase. Pass it via an",
|
|
27567
|
+
' "env" block in the server entry of your MCP config (e.g. mcp_config.json /',
|
|
27568
|
+
" claude_desktop_config.json) so the host hands it to the server at startup:",
|
|
27569
|
+
"",
|
|
27570
|
+
" {",
|
|
27571
|
+
' "mcpServers": {',
|
|
27572
|
+
' "formstr": {',
|
|
27573
|
+
' "command": "npx",',
|
|
27574
|
+
' "args": ["-y", "@formstr/mcp"],',
|
|
27575
|
+
' "env": {',
|
|
27576
|
+
' "FORMSTR_MCP_NCRYPTSEC_PASSPHRASE": "your-passphrase-here"',
|
|
27577
|
+
" }",
|
|
27578
|
+
" }",
|
|
27579
|
+
" }",
|
|
27580
|
+
" }",
|
|
27581
|
+
"",
|
|
27582
|
+
" Tip: a NIP-46 (bunker) account needs no passphrase \u2014 `switch` to one and the config",
|
|
27583
|
+
' can stay secret-free. Add "--allow-writes" to args to enable gated write tools.'
|
|
27538
27584
|
].join("\n");
|
|
27539
27585
|
}
|
|
27540
27586
|
|
|
@@ -32418,19 +32464,27 @@ async function fetchMyForms() {
|
|
|
32418
32464
|
return summary;
|
|
32419
32465
|
}).filter((s) => s !== null);
|
|
32420
32466
|
}
|
|
32467
|
+
var MY_FORMS_LIST_READ_ATTEMPTS = 3;
|
|
32468
|
+
async function readMyFormsEntriesForWrite(signer, userPubkey, relays) {
|
|
32469
|
+
let listEvent = null;
|
|
32470
|
+
for (let attempt = 0; attempt < MY_FORMS_LIST_READ_ATTEMPTS; attempt++) {
|
|
32471
|
+
listEvent = await fetchLatestMyFormsEvent(relays, userPubkey);
|
|
32472
|
+
if (listEvent?.content) break;
|
|
32473
|
+
}
|
|
32474
|
+
if (!listEvent?.content) return [];
|
|
32475
|
+
try {
|
|
32476
|
+
return await decryptListEntries(signer, userPubkey, listEvent.content);
|
|
32477
|
+
} catch (err) {
|
|
32478
|
+
throw new Error(
|
|
32479
|
+
`Could not read your existing forms list to add this form, so it was left unchanged to avoid losing your other forms. The form itself was published \u2014 retry to add it. (${err instanceof Error ? err.message : String(err)})`
|
|
32480
|
+
);
|
|
32481
|
+
}
|
|
32482
|
+
}
|
|
32421
32483
|
async function appendToMyFormsList(formPubkey, formId, relay, signingKeyHex, viewKeyHex) {
|
|
32422
32484
|
const signer = await signerManager.getSigner();
|
|
32423
32485
|
const userPubkey = await signer.getPublicKey();
|
|
32424
32486
|
const relays = relayManager.getRelaysForModule("forms");
|
|
32425
|
-
|
|
32426
|
-
let entries = [];
|
|
32427
|
-
if (existing?.content) {
|
|
32428
|
-
try {
|
|
32429
|
-
entries = await decryptListEntries(signer, userPubkey, existing.content);
|
|
32430
|
-
} catch {
|
|
32431
|
-
entries = [];
|
|
32432
|
-
}
|
|
32433
|
-
}
|
|
32487
|
+
let entries = await readMyFormsEntriesForWrite(signer, userPubkey, relays);
|
|
32434
32488
|
entries = entries.map(
|
|
32435
32489
|
(e) => e[0] === "f" && e.length < 4 ? ["f", e[1] ?? "", e[2] ?? "", ""] : e
|
|
32436
32490
|
);
|
|
@@ -32614,9 +32668,13 @@ async function fetchFormSummaryFromRef(pubkey, formId) {
|
|
|
32614
32668
|
};
|
|
32615
32669
|
}
|
|
32616
32670
|
async function importForm(summary) {
|
|
32617
|
-
|
|
32618
|
-
|
|
32619
|
-
|
|
32671
|
+
await appendToMyFormsList(
|
|
32672
|
+
summary.pubkey,
|
|
32673
|
+
summary.id,
|
|
32674
|
+
summary.relay ?? "",
|
|
32675
|
+
summary.signingKey,
|
|
32676
|
+
summary.viewKey
|
|
32677
|
+
);
|
|
32620
32678
|
}
|
|
32621
32679
|
function parseFormEvent(event, decryptedRows) {
|
|
32622
32680
|
const merged = decryptedRows ? [...event.tags, ...decryptedRows] : event.tags;
|
|
@@ -34865,6 +34923,25 @@ function computeResults(responses) {
|
|
|
34865
34923
|
return { results, totalVotes: totalVoters };
|
|
34866
34924
|
}
|
|
34867
34925
|
|
|
34926
|
+
// ../agent/src/tools/pubkey.ts
|
|
34927
|
+
function normalizePubkey(value) {
|
|
34928
|
+
const trimmed = value.trim();
|
|
34929
|
+
if (!trimmed) return null;
|
|
34930
|
+
if (/^[0-9a-f]{64}$/i.test(trimmed)) return trimmed.toLowerCase();
|
|
34931
|
+
if (trimmed.startsWith("npub1")) {
|
|
34932
|
+
try {
|
|
34933
|
+
const decoded = nip19_exports.decode(trimmed);
|
|
34934
|
+
if (decoded.type === "npub") return decoded.data;
|
|
34935
|
+
} catch {
|
|
34936
|
+
}
|
|
34937
|
+
}
|
|
34938
|
+
return null;
|
|
34939
|
+
}
|
|
34940
|
+
function normalizePubkeyList(values) {
|
|
34941
|
+
if (!Array.isArray(values)) return [];
|
|
34942
|
+
return values.map((v) => typeof v === "string" ? normalizePubkey(v) : null).filter((p) => !!p);
|
|
34943
|
+
}
|
|
34944
|
+
|
|
34868
34945
|
// ../agent/src/tools/calendar.ts
|
|
34869
34946
|
var calendarTools = buildCalendarTools();
|
|
34870
34947
|
function buildCalendarTools() {
|
|
@@ -34904,7 +34981,7 @@ function buildCalendarTools() {
|
|
|
34904
34981
|
server.registerTool(
|
|
34905
34982
|
"create_calendar_event",
|
|
34906
34983
|
{
|
|
34907
|
-
description: "Schedule a calendar event. start/end are ISO 8601. Events default to PRIVATE (encrypted) and are linked into a calendar list \u2014 that is the only way they show on calendar.formstr.app, which renders only events referenced in a calendar list. Pass calendarId to choose which calendar; if omitted and the user already has calendars, this tool returns the list so you can ASK the user which one (then re-run with calendarId). Set isPrivate:false for a public, unencrypted event \u2014 note public events do NOT sync to calendar.formstr.app. participants receive NIP-59 invitations.",
|
|
34984
|
+
description: "Schedule a calendar event. start/end are ISO 8601. Events default to PRIVATE (encrypted) and are linked into a calendar list \u2014 that is the only way they show on calendar.formstr.app, which renders only events referenced in a calendar list. Pass calendarId to choose which calendar; if omitted and the user already has calendars, this tool returns the list so you can ASK the user which one (then re-run with calendarId). Set isPrivate:false for a public, unencrypted event \u2014 note public events do NOT sync to calendar.formstr.app. participants (npub or hex) receive NIP-59 invitations.",
|
|
34908
34985
|
inputSchema: {
|
|
34909
34986
|
title: external_exports.string(),
|
|
34910
34987
|
description: external_exports.string().optional(),
|
|
@@ -34944,7 +35021,9 @@ function buildCalendarTools() {
|
|
|
34944
35021
|
begin,
|
|
34945
35022
|
end,
|
|
34946
35023
|
location: args.location,
|
|
34947
|
-
|
|
35024
|
+
// Accept npub OR hex for each participant — the wire (["p"] tags, NIP-59
|
|
35025
|
+
// invitation wraps, relay-list query) needs hex, so convert here.
|
|
35026
|
+
participants: normalizePubkeyList(args.participants),
|
|
34948
35027
|
isPrivate,
|
|
34949
35028
|
calendarId: args.calendarId,
|
|
34950
35029
|
rrule: args.rrule,
|
|
@@ -34971,7 +35050,8 @@ function buildCalendarTools() {
|
|
|
34971
35050
|
inputSchema: { coordinate: external_exports.string() }
|
|
34972
35051
|
},
|
|
34973
35052
|
async ({ coordinate }) => {
|
|
34974
|
-
const
|
|
35053
|
+
const viewKey = await service_exports2.lookupEventViewKey(coordinate);
|
|
35054
|
+
const event = await service_exports2.fetchCalendarEventByCoordinate(coordinate, viewKey);
|
|
34975
35055
|
if (!event) return fail(`No event found for ${coordinate}.`, "NOT_FOUND");
|
|
34976
35056
|
return ok(`Event "${event.title}".`, {
|
|
34977
35057
|
event: {
|
|
@@ -35222,7 +35302,8 @@ function buildCalendarTools() {
|
|
|
35222
35302
|
`updates event ${args.coordinate}`
|
|
35223
35303
|
);
|
|
35224
35304
|
if (blocked) return blocked;
|
|
35225
|
-
const
|
|
35305
|
+
const viewKey = await service_exports2.lookupEventViewKey(args.coordinate);
|
|
35306
|
+
const existing = await service_exports2.fetchCalendarEventByCoordinate(args.coordinate, viewKey);
|
|
35226
35307
|
if (!existing) return fail(`No event found for ${args.coordinate}.`, "NOT_FOUND");
|
|
35227
35308
|
const draft = {
|
|
35228
35309
|
title: args.title ?? existing.title,
|
|
@@ -35264,7 +35345,8 @@ function buildCalendarTools() {
|
|
|
35264
35345
|
`attaches a form to ${coordinate}`
|
|
35265
35346
|
);
|
|
35266
35347
|
if (blocked) return blocked;
|
|
35267
|
-
const
|
|
35348
|
+
const viewKey = await service_exports2.lookupEventViewKey(coordinate);
|
|
35349
|
+
const existing = await service_exports2.fetchCalendarEventByCoordinate(coordinate, viewKey);
|
|
35268
35350
|
if (!existing) return fail(`No event found for ${coordinate}.`, "NOT_FOUND");
|
|
35269
35351
|
const draft = {
|
|
35270
35352
|
title: existing.title,
|
|
@@ -35543,23 +35625,6 @@ function buildDriveTools() {
|
|
|
35543
35625
|
}
|
|
35544
35626
|
|
|
35545
35627
|
// ../agent/src/tools/shared.ts
|
|
35546
|
-
function normalizePubkey(value) {
|
|
35547
|
-
const trimmed = value.trim();
|
|
35548
|
-
if (!trimmed) return null;
|
|
35549
|
-
if (/^[0-9a-f]{64}$/i.test(trimmed)) return trimmed.toLowerCase();
|
|
35550
|
-
if (trimmed.startsWith("npub1")) {
|
|
35551
|
-
try {
|
|
35552
|
-
const decoded = nip19_exports.decode(trimmed);
|
|
35553
|
-
if (decoded.type === "npub") return decoded.data;
|
|
35554
|
-
} catch {
|
|
35555
|
-
}
|
|
35556
|
-
}
|
|
35557
|
-
return null;
|
|
35558
|
-
}
|
|
35559
|
-
function normalizePubkeyList(values) {
|
|
35560
|
-
if (!Array.isArray(values)) return [];
|
|
35561
|
-
return values.map((v) => typeof v === "string" ? normalizePubkey(v) : null).filter((p) => !!p);
|
|
35562
|
-
}
|
|
35563
35628
|
var ANSWER_TYPES = new Set(Object.values(AnswerType));
|
|
35564
35629
|
var LEGACY_TYPE_ALIASES = {
|
|
35565
35630
|
multiChoiceGrid: "multipleChoiceGrid" /* multipleChoiceGrid */
|
|
@@ -46543,6 +46608,95 @@ async function startStdio(ctx) {
|
|
|
46543
46608
|
await server.connect(transport);
|
|
46544
46609
|
}
|
|
46545
46610
|
|
|
46611
|
+
// src/version.ts
|
|
46612
|
+
var import_node_fs2 = require("fs");
|
|
46613
|
+
var import_node_path2 = require("path");
|
|
46614
|
+
var PACKAGE_NAME = "@formstr/mcp";
|
|
46615
|
+
function readInstalledVersion() {
|
|
46616
|
+
try {
|
|
46617
|
+
const pkg = JSON.parse((0, import_node_fs2.readFileSync)((0, import_node_path2.join)(__dirname, "..", "package.json"), "utf8"));
|
|
46618
|
+
return pkg.version ?? "0.0.0";
|
|
46619
|
+
} catch {
|
|
46620
|
+
return "0.0.0";
|
|
46621
|
+
}
|
|
46622
|
+
}
|
|
46623
|
+
function parseSemver(version2) {
|
|
46624
|
+
const cleaned = version2.trim().replace(/^v/i, "").split("+")[0];
|
|
46625
|
+
const [core, pre] = cleaned.split("-", 2);
|
|
46626
|
+
const release = core.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
46627
|
+
while (release.length < 3) release.push(0);
|
|
46628
|
+
return { release, prerelease: pre ? pre.split(".") : [] };
|
|
46629
|
+
}
|
|
46630
|
+
function compareIdentifiers(a, b) {
|
|
46631
|
+
const na = /^\d+$/.test(a);
|
|
46632
|
+
const nb = /^\d+$/.test(b);
|
|
46633
|
+
if (na && nb) return Number(a) - Number(b) < 0 ? -1 : Number(a) === Number(b) ? 0 : 1;
|
|
46634
|
+
if (na) return -1;
|
|
46635
|
+
if (nb) return 1;
|
|
46636
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
46637
|
+
}
|
|
46638
|
+
function compareVersions(a, b) {
|
|
46639
|
+
const va = parseSemver(a);
|
|
46640
|
+
const vb = parseSemver(b);
|
|
46641
|
+
for (let i4 = 0; i4 < 3; i4++) {
|
|
46642
|
+
if (va.release[i4] !== vb.release[i4]) return va.release[i4] < vb.release[i4] ? -1 : 1;
|
|
46643
|
+
}
|
|
46644
|
+
if (va.prerelease.length === 0 && vb.prerelease.length === 0) return 0;
|
|
46645
|
+
if (va.prerelease.length === 0) return 1;
|
|
46646
|
+
if (vb.prerelease.length === 0) return -1;
|
|
46647
|
+
const len = Math.max(va.prerelease.length, vb.prerelease.length);
|
|
46648
|
+
for (let i4 = 0; i4 < len; i4++) {
|
|
46649
|
+
const ia = va.prerelease[i4];
|
|
46650
|
+
const ib = vb.prerelease[i4];
|
|
46651
|
+
if (ia === void 0) return -1;
|
|
46652
|
+
if (ib === void 0) return 1;
|
|
46653
|
+
const c = compareIdentifiers(ia, ib);
|
|
46654
|
+
if (c !== 0) return c < 0 ? -1 : 1;
|
|
46655
|
+
}
|
|
46656
|
+
return 0;
|
|
46657
|
+
}
|
|
46658
|
+
async function fetchLatestVersion(pkgName, opts = {}) {
|
|
46659
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
46660
|
+
const timeoutMs = opts.timeoutMs ?? 3e3;
|
|
46661
|
+
const controller = new AbortController();
|
|
46662
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
46663
|
+
try {
|
|
46664
|
+
const res = await fetchImpl(`https://registry.npmjs.org/${pkgName}/latest`, {
|
|
46665
|
+
signal: controller.signal,
|
|
46666
|
+
// The abbreviated metadata document is far smaller than the full packument.
|
|
46667
|
+
headers: { accept: "application/vnd.npm.install-v1+json" }
|
|
46668
|
+
});
|
|
46669
|
+
if (!res.ok) return null;
|
|
46670
|
+
const body = await res.json();
|
|
46671
|
+
return typeof body.version === "string" ? body.version : null;
|
|
46672
|
+
} catch {
|
|
46673
|
+
return null;
|
|
46674
|
+
} finally {
|
|
46675
|
+
clearTimeout(timer);
|
|
46676
|
+
}
|
|
46677
|
+
}
|
|
46678
|
+
function formatVersionReport(installed, latest) {
|
|
46679
|
+
const head = `${PACKAGE_NAME} ${installed}`;
|
|
46680
|
+
if (!latest) {
|
|
46681
|
+
return `${head}
|
|
46682
|
+
(could not check for updates \u2014 offline or registry unreachable)`;
|
|
46683
|
+
}
|
|
46684
|
+
const cmp = compareVersions(installed, latest);
|
|
46685
|
+
if (cmp < 0) {
|
|
46686
|
+
return [
|
|
46687
|
+
head,
|
|
46688
|
+
`Update available: ${latest} (you have ${installed}).`,
|
|
46689
|
+
`Upgrade: npm install -g ${PACKAGE_NAME}@latest`,
|
|
46690
|
+
`Or just re-run via: npx -y ${PACKAGE_NAME}@latest`
|
|
46691
|
+
].join("\n");
|
|
46692
|
+
}
|
|
46693
|
+
if (cmp > 0) {
|
|
46694
|
+
return `${head}
|
|
46695
|
+
(ahead of the latest published release ${latest})`;
|
|
46696
|
+
}
|
|
46697
|
+
return `${head} (latest)`;
|
|
46698
|
+
}
|
|
46699
|
+
|
|
46546
46700
|
// src/index.ts
|
|
46547
46701
|
async function runServer(cli) {
|
|
46548
46702
|
const cfg = resolveConfig(cli, process.env);
|
|
@@ -46593,8 +46747,10 @@ async function main() {
|
|
|
46593
46747
|
await runLogin(cli);
|
|
46594
46748
|
return cli.command;
|
|
46595
46749
|
case "logout": {
|
|
46596
|
-
await doLogout(await buildMcpSigner(), cli.account);
|
|
46597
|
-
console.error(
|
|
46750
|
+
const removed = await doLogout(await buildMcpSigner(), cli.target ?? cli.account);
|
|
46751
|
+
console.error(
|
|
46752
|
+
removed ? `formstr-mcp: removed ${removed.npub} (${removed.method}).` : "formstr-mcp: no account to remove."
|
|
46753
|
+
);
|
|
46598
46754
|
return cli.command;
|
|
46599
46755
|
}
|
|
46600
46756
|
case "whoami": {
|
|
@@ -46631,6 +46787,12 @@ async function main() {
|
|
|
46631
46787
|
case "help":
|
|
46632
46788
|
console.error(helpText());
|
|
46633
46789
|
return cli.command;
|
|
46790
|
+
case "version": {
|
|
46791
|
+
const installed = readInstalledVersion();
|
|
46792
|
+
const latest = await fetchLatestVersion(PACKAGE_NAME);
|
|
46793
|
+
console.log(formatVersionReport(installed, latest));
|
|
46794
|
+
return cli.command;
|
|
46795
|
+
}
|
|
46634
46796
|
case "run":
|
|
46635
46797
|
default:
|
|
46636
46798
|
await runServer(cli);
|