@toon-protocol/client-mcp 0.12.2 → 0.13.1

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 CHANGED
@@ -274,7 +274,7 @@ receive a target-chain claim.
274
274
  results**, never private keys.
275
275
  - A single-instance PID lock prevents two daemons from racing the channel nonce
276
276
  watermark (which would corrupt the payment proof).
277
- - The control plane binds `127.0.0.1` only (no auth layer — it never leaves
277
+ - The control API binds `127.0.0.1` only (no auth layer — it never leaves
278
278
  loopback).
279
279
 
280
280
  ## Tests
@@ -2,7 +2,7 @@ import { createRequire as __cr } from 'module'; const require = __cr(import.meta
2
2
  import {
3
3
  ControlApiError,
4
4
  DaemonUnreachableError
5
- } from "./chunk-AYIP46A2.js";
5
+ } from "./chunk-UXCFHAUC.js";
6
6
 
7
7
  // ../views/dist/tool-names.js
8
8
  var PUBLISH_TOOL = "toon_publish_unsigned";
@@ -727,6 +727,7 @@ var EXAMPLE_VIEWSPECS = [
727
727
  ];
728
728
 
729
729
  // src/mcp-tools.ts
730
+ import { basename } from "path";
730
731
  var TOOL_DEFINITIONS = [
731
732
  {
732
733
  name: "toon_status",
@@ -824,6 +825,167 @@ var TOOL_DEFINITIONS = [
824
825
  additionalProperties: false
825
826
  }
826
827
  },
828
+ {
829
+ name: "toon_git_push",
830
+ description: "Pay-to-write: push a local git repository to TOON (NIP-34) \u2014 uploads the object delta to Arweave via the paid store and publishes the cumulative kind:30618 refs event (+ kind:30617 announcement on first push). The daemon identity is the repo owner. TWO-STEP, PAID + IRREVERSIBLE: ALWAYS call with dry_run:true first \u2014 it plans the push without paying and returns the itemized fee table (per-object upload fees + event fees). Quote estimate.totalFee to the user and get their explicit confirmation, THEN call again with confirm:true to execute; a push without confirm:true is rejected. Uploads and events are permanent and non-refundable (they cannot be unpublished).",
831
+ inputSchema: {
832
+ type: "object",
833
+ properties: {
834
+ repoPath: {
835
+ type: "string",
836
+ description: "Absolute path to the local git repository (worktree or .git dir) the daemon reads with git plumbing."
837
+ },
838
+ repoId: {
839
+ type: "string",
840
+ description: 'Repository identifier (NIP-34 `d` tag). Default: the basename of repoPath with any trailing `.git` stripped (so /repos/demo, /repos/demo/.git and /repos/demo.git all derive "demo").'
841
+ },
842
+ refspecs: {
843
+ type: "array",
844
+ items: { type: "string" },
845
+ description: 'Full refnames to push (e.g. ["refs/heads/main"]). Default: every local branch and tag.'
846
+ },
847
+ force: {
848
+ type: "boolean",
849
+ description: "Allow non-fast-forward updates (default false). A forced push abandons remote commits \u2014 get explicit user confirmation first."
850
+ },
851
+ relayUrls: {
852
+ type: "array",
853
+ items: { type: "string" },
854
+ description: "Relay URLs to read remote state from and publish to (default: the daemon's config-seeded relay)."
855
+ },
856
+ dry_run: {
857
+ type: "boolean",
858
+ description: "true \u2192 plan + price only (free, nothing is paid or published). Required first step before any real push."
859
+ },
860
+ confirm: {
861
+ type: "boolean",
862
+ description: "Must be literally true to execute the paid push \u2014 only after the user confirmed the dry_run fee quote."
863
+ }
864
+ },
865
+ required: ["repoPath"],
866
+ additionalProperties: false
867
+ }
868
+ },
869
+ {
870
+ name: "toon_git_issue",
871
+ description: "Pay-to-write: file a NIP-34 issue (kind:1621) against a TOON-hosted repo. One paid event publish. PAID + IRREVERSIBLE: quote the per-event fee (feePerEvent via toon_status / fee config) and confirm with the user before calling \u2014 events cannot be unpublished.",
872
+ inputSchema: {
873
+ type: "object",
874
+ properties: {
875
+ repoOwnerPubkey: {
876
+ type: "string",
877
+ description: "Repository owner's Nostr pubkey (64-char hex \u2014 the author of the kind:30617 announcement)."
878
+ },
879
+ repoId: {
880
+ type: "string",
881
+ description: "Repository identifier (NIP-34 `d` tag)."
882
+ },
883
+ title: { type: "string", description: "Issue title (`subject` tag)." },
884
+ body: { type: "string", description: "Issue body (Markdown)." },
885
+ labels: {
886
+ type: "array",
887
+ items: { type: "string" },
888
+ description: "Labels (`t` tags)."
889
+ }
890
+ },
891
+ required: ["repoOwnerPubkey", "repoId", "title", "body"],
892
+ additionalProperties: false
893
+ }
894
+ },
895
+ {
896
+ name: "toon_git_comment",
897
+ description: "Pay-to-write: comment (kind:1622) on a TOON-hosted issue or patch. One paid event publish. PAID + IRREVERSIBLE: quote the per-event fee (feePerEvent via toon_status / fee config) and confirm with the user before calling \u2014 events cannot be unpublished.",
898
+ inputSchema: {
899
+ type: "object",
900
+ properties: {
901
+ repoOwnerPubkey: {
902
+ type: "string",
903
+ description: "Repository owner's Nostr pubkey (64-char hex)."
904
+ },
905
+ repoId: {
906
+ type: "string",
907
+ description: "Repository identifier (NIP-34 `d` tag)."
908
+ },
909
+ rootEventId: {
910
+ type: "string",
911
+ description: "Event id of the issue or patch being commented on."
912
+ },
913
+ body: { type: "string", description: "Comment body (Markdown)." },
914
+ parentAuthorPubkey: {
915
+ type: "string",
916
+ description: "Pubkey of the TARGET event's author (NIP-34 `p` threading tag). Default: the repo owner."
917
+ },
918
+ marker: {
919
+ type: "string",
920
+ enum: ["root", "reply"],
921
+ description: "`e`-tag marker (default root: commenting directly on the issue/patch)."
922
+ }
923
+ },
924
+ required: ["repoOwnerPubkey", "repoId", "rootEventId", "body"],
925
+ additionalProperties: false
926
+ }
927
+ },
928
+ {
929
+ name: "toon_git_patch",
930
+ description: "Pay-to-write: submit a patch/PR (kind:1617, real `git format-patch` content) to a TOON-hosted repo. Supply EXACTLY ONE of patchText (literal format-patch output) or repoPath+range (the daemon runs format-patch locally). One paid event publish. PAID + IRREVERSIBLE: quote the per-event fee (feePerEvent via toon_status / fee config) and confirm with the user before calling \u2014 events cannot be unpublished.",
931
+ inputSchema: {
932
+ type: "object",
933
+ properties: {
934
+ repoOwnerPubkey: {
935
+ type: "string",
936
+ description: "Repository owner's Nostr pubkey (64-char hex)."
937
+ },
938
+ repoId: {
939
+ type: "string",
940
+ description: "Repository identifier (NIP-34 `d` tag)."
941
+ },
942
+ title: { type: "string", description: "Patch/PR title (`subject` tag)." },
943
+ patchText: {
944
+ type: "string",
945
+ description: "Literal `git format-patch` output. Mutually exclusive with repoPath+range."
946
+ },
947
+ repoPath: {
948
+ type: "string",
949
+ description: "Local repository to run format-patch in. Requires range."
950
+ },
951
+ range: {
952
+ type: "string",
953
+ description: "Revision range for format-patch (`<rev>`, `<rev>..<rev>`, `<rev>...<rev>`)."
954
+ },
955
+ branch: { type: "string", description: "Branch name for the `t` tag." }
956
+ },
957
+ required: ["repoOwnerPubkey", "repoId", "title"],
958
+ additionalProperties: false
959
+ }
960
+ },
961
+ {
962
+ name: "toon_git_status",
963
+ description: "Pay-to-write: set the status of a TOON-hosted issue or patch (kind:1630-1633: open/applied/closed/draft). One paid event publish. PAID + IRREVERSIBLE: quote the per-event fee (feePerEvent via toon_status / fee config) and confirm with the user before calling \u2014 events cannot be unpublished.",
964
+ inputSchema: {
965
+ type: "object",
966
+ properties: {
967
+ repoOwnerPubkey: {
968
+ type: "string",
969
+ description: "Repository owner's Nostr pubkey (64-char hex)."
970
+ },
971
+ repoId: {
972
+ type: "string",
973
+ description: "Repository identifier (NIP-34 `d` tag)."
974
+ },
975
+ targetEventId: {
976
+ type: "string",
977
+ description: "Event id of the issue/patch whose status is being set."
978
+ },
979
+ status: {
980
+ type: "string",
981
+ enum: ["open", "applied", "closed", "draft"],
982
+ description: "open \u2192 kind:1630, applied \u2192 1631, closed \u2192 1632, draft \u2192 1633."
983
+ }
984
+ },
985
+ required: ["repoOwnerPubkey", "repoId", "targetEventId", "status"],
986
+ additionalProperties: false
987
+ }
988
+ },
827
989
  {
828
990
  name: "toon_atoms",
829
991
  description: "REQUIRED first call before any toon_render \u2014 returns the atom allowlist (ids + the kinds each renders, props, write actions) plus example ViewSpecs, for composing a view to pass to toon_render. Never guess atom ids or kinds; always fetch them here first. This is how you build the user a generative UI for their journey.",
@@ -1197,6 +1359,14 @@ var TOOL_ANNOTATIONS = {
1197
1359
  toon_publish_unsigned: PAID_WRITE,
1198
1360
  toon_upload: PAID_WRITE,
1199
1361
  toon_swap: PAID_WRITE,
1362
+ // paid git writes (NIP-34): push uploads objects + publishes events; the
1363
+ // single-event tools each spend one channel claim. toon_git_push with
1364
+ // dry_run:true is a free estimate, but the tool as a whole is a paid write.
1365
+ toon_git_push: PAID_WRITE,
1366
+ toon_git_issue: PAID_WRITE,
1367
+ toon_git_comment: PAID_WRITE,
1368
+ toon_git_patch: PAID_WRITE,
1369
+ toon_git_status: PAID_WRITE,
1200
1370
  // on-chain channel ops
1201
1371
  toon_open_channel: { ...ONCHAIN_WRITE, idempotentHint: true },
1202
1372
  // returns the existing channel if open
@@ -1250,6 +1420,71 @@ async function dispatchTool(client, name, args) {
1250
1420
  return ok(
1251
1421
  await client.uploadMedia(args)
1252
1422
  );
1423
+ case "toon_git_push": {
1424
+ const req = {
1425
+ repoPath: String(args["repoPath"] ?? ""),
1426
+ repoId: typeof args["repoId"] === "string" && args["repoId"] !== "" ? args["repoId"] : defaultRepoId(String(args["repoPath"] ?? "")),
1427
+ ...Array.isArray(args["refspecs"]) ? { refspecs: args["refspecs"].map(String) } : {},
1428
+ ...typeof args["force"] === "boolean" ? { force: args["force"] } : {},
1429
+ ...Array.isArray(args["relayUrls"]) ? { relayUrls: args["relayUrls"].map(String) } : {}
1430
+ };
1431
+ if (args["dry_run"] === true) {
1432
+ const plan = await client.gitEstimate(req);
1433
+ return okStructured(
1434
+ JSON.stringify(compactPushPlan(plan)) + `
1435
+ Quote estimate.totalFee (base units) to the user and get explicit confirmation before pushing with confirm:true \u2014 the push is permanent and non-refundable.`,
1436
+ plan
1437
+ );
1438
+ }
1439
+ if (args["confirm"] !== true) {
1440
+ return err(
1441
+ `Refusing to push without confirm:true. Run toon_git_push with dry_run:true first (free), quote estimate.totalFee to the user, and only after their explicit confirmation call again with confirm:true. Pushes are PAID, permanent, and non-refundable.`
1442
+ );
1443
+ }
1444
+ const pushed = await client.gitPush({ ...req, confirm: true });
1445
+ return okStructured(
1446
+ JSON.stringify(compactPushResult(pushed)),
1447
+ pushed
1448
+ );
1449
+ }
1450
+ case "toon_git_issue":
1451
+ return ok(
1452
+ await client.gitIssue({
1453
+ repoAddr: gitRepoAddr(args),
1454
+ title: String(args["title"] ?? ""),
1455
+ body: String(args["body"] ?? ""),
1456
+ ...Array.isArray(args["labels"]) ? { labels: args["labels"].map(String) } : {}
1457
+ })
1458
+ );
1459
+ case "toon_git_comment":
1460
+ return ok(
1461
+ await client.gitComment({
1462
+ repoAddr: gitRepoAddr(args),
1463
+ rootEventId: String(args["rootEventId"] ?? ""),
1464
+ body: String(args["body"] ?? ""),
1465
+ ...typeof args["parentAuthorPubkey"] === "string" ? { parentAuthorPubkey: args["parentAuthorPubkey"] } : {},
1466
+ ...args["marker"] === "root" || args["marker"] === "reply" ? { marker: args["marker"] } : {}
1467
+ })
1468
+ );
1469
+ case "toon_git_patch": {
1470
+ const req = {
1471
+ repoAddr: gitRepoAddr(args),
1472
+ title: String(args["title"] ?? ""),
1473
+ ...typeof args["patchText"] === "string" ? { patchText: args["patchText"] } : {},
1474
+ ...typeof args["repoPath"] === "string" ? { repoPath: args["repoPath"] } : {},
1475
+ ...typeof args["range"] === "string" ? { range: args["range"] } : {},
1476
+ ...typeof args["branch"] === "string" ? { branch: args["branch"] } : {}
1477
+ };
1478
+ return ok(await client.gitPatch(req));
1479
+ }
1480
+ case "toon_git_status":
1481
+ return ok(
1482
+ await client.gitStatus({
1483
+ repoAddr: gitRepoAddr(args),
1484
+ targetEventId: String(args["targetEventId"] ?? ""),
1485
+ status: String(args["status"] ?? "")
1486
+ })
1487
+ );
1253
1488
  case "toon_atoms": {
1254
1489
  const atomsPayload = { atoms: ATOM_CATALOG, examples: EXAMPLE_VIEWSPECS };
1255
1490
  return okStructured(JSON.stringify(atomsPayload, null, 2), atomsPayload);
@@ -1386,7 +1621,7 @@ async function dispatchTool(client, name, args) {
1386
1621
  } catch (e) {
1387
1622
  if (e instanceof ControlApiError && e.status === 504 && name === "toon_balances") {
1388
1623
  return err(
1389
- `${e.detail ?? e.message} \u2014 the balances control plane (:8787 GET /balances) stalled reading on-chain balances; the relay and apex are unaffected. Retry shortly.`
1624
+ `${e.detail ?? e.message} \u2014 the balances control API (:8787 GET /balances) stalled reading on-chain balances; the relay and apex are unaffected. Retry shortly.`
1390
1625
  );
1391
1626
  }
1392
1627
  if (e instanceof ControlApiError && e.status === 504 && name === "toon_fund_wallet") {
@@ -1402,6 +1637,26 @@ async function dispatchTool(client, name, args) {
1402
1637
  if (e instanceof ControlApiError && e.status === 402) {
1403
1638
  return err(e.detail ?? e.message);
1404
1639
  }
1640
+ if (e instanceof ControlApiError && e.message === "non_fast_forward") {
1641
+ return err(
1642
+ JSON.stringify({
1643
+ error: "non_fast_forward",
1644
+ detail: e.detail,
1645
+ refs: e.data?.["refs"] ?? [],
1646
+ hint: "The remote moved since the last known state. Re-run toon_git_push with dry_run:true and force:true to preview the forced update, get explicit user confirmation (a forced push abandons the remote commits), then push with force:true + confirm:true."
1647
+ })
1648
+ );
1649
+ }
1650
+ if (e instanceof ControlApiError && e.message === "oversize_objects") {
1651
+ return err(
1652
+ JSON.stringify({
1653
+ error: "oversize_objects",
1654
+ detail: e.detail,
1655
+ objects: e.data?.["objects"] ?? [],
1656
+ hint: "These objects exceed the 95KB single-packet upload limit \u2014 a v1 hard error (paid blob storage for larger objects is the epic #222 follow-up, toon-client#235). Remove or shrink the listed paths (e.g. git filter-repo) and re-run the estimate."
1657
+ })
1658
+ );
1659
+ }
1405
1660
  if (e instanceof ControlApiError && e.retryable) {
1406
1661
  return err(
1407
1662
  `TOON client is still bootstrapping (the BTP session can take a few seconds) \u2014 retry shortly. (${e.message})`
@@ -1426,6 +1681,38 @@ function wrapList(key, res) {
1426
1681
  }
1427
1682
  return { [key]: [] };
1428
1683
  }
1684
+ function defaultRepoId(repoPath) {
1685
+ const trimmed = repoPath.replace(/[/\\]+$/, "");
1686
+ const base = basename(trimmed);
1687
+ if (base === ".git") {
1688
+ return basename(trimmed.slice(0, -".git".length).replace(/[/\\]+$/, ""));
1689
+ }
1690
+ return base.length > ".git".length && base.endsWith(".git") ? base.slice(0, -".git".length) : base;
1691
+ }
1692
+ function gitRepoAddr(args) {
1693
+ return {
1694
+ ownerPubkey: String(args["repoOwnerPubkey"] ?? ""),
1695
+ repoId: String(args["repoId"] ?? "")
1696
+ };
1697
+ }
1698
+ function compactPushPlan(plan) {
1699
+ const { objects, knownShaToTxId, newRefs, ...rest } = plan;
1700
+ return {
1701
+ ...rest,
1702
+ newRefCount: Object.keys(newRefs).length,
1703
+ plannedObjectCount: objects.length,
1704
+ knownOnArweaveCount: Object.keys(knownShaToTxId).length
1705
+ };
1706
+ }
1707
+ function compactPushResult(res) {
1708
+ const { uploads, arweaveMap, ...rest } = res;
1709
+ return {
1710
+ ...rest,
1711
+ uploadCount: uploads.length,
1712
+ skippedUploadCount: uploads.filter((u) => u.skipped).length,
1713
+ arweaveMapSize: Object.keys(arweaveMap).length
1714
+ };
1715
+ }
1429
1716
  function ok(data) {
1430
1717
  const result = {
1431
1718
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
@@ -1524,4 +1811,4 @@ export {
1524
1811
  TOOL_DEFINITIONS,
1525
1812
  dispatchTool
1526
1813
  };
1527
- //# sourceMappingURL=chunk-XVGREVEL.js.map
1814
+ //# sourceMappingURL=chunk-SL7UGVOC.js.map