@ixo/asks-mcp 1.6.0-dev.3 → 1.6.0-dev.4
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 +2 -0
- package/dist/asks-mcp.mjs +122 -42
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -87,6 +87,8 @@ naming what is missing; nothing is posted and nothing is spent. `my_status` and
|
|
|
87
87
|
|
|
88
88
|
On first run the server makes a key in `$OPENHANDS_HOME/<network>/key.json` (by default `~/.openhands/testnet/key.json`), readable only by you. The server refuses to use it if anyone else can read the file, anyone else can write to its directory, or either is a symlink. The mnemonic never leaves the server: no tool returns it and nothing logs it. The spend ledger (`spend.json`), the account it acts for (`principal.json`) and open-call drafts (`calls/`) sit beside it.
|
|
89
89
|
|
|
90
|
+
On Windows the server verifies the actual ACL using Windows PowerShell, because Node's POSIX mode bits cannot express Windows permissions. The current Windows user must have Full control; only that user, SYSTEM and the built-in Administrators group may have access. Inherited private permissions are accepted. The server does not rewrite Windows ACLs, and refuses to read a key when it cannot verify them. For a refusal, check the directory and file under **Properties > Security**, or choose an `OPENHANDS_HOME` that already has private permissions. No permission-check bypass is available.
|
|
91
|
+
|
|
90
92
|
Spending tools preview first and need the exact total confirmed; daily caps are counted on disk, so restarting the client does not reset them. Earnings go only to `OPENHANDS_HOME_ADDRESS`.
|
|
91
93
|
|
|
92
94
|
## The hosted server instead
|
package/dist/asks-mcp.mjs
CHANGED
|
@@ -8058,7 +8058,14 @@ function reportProblems(raw, requirement, fileIds = []) {
|
|
|
8058
8058
|
}
|
|
8059
8059
|
function readReport(envelope) {
|
|
8060
8060
|
const doc = obj(envelope);
|
|
8061
|
-
|
|
8061
|
+
let report = obj(doc?.report) ?? (doc?.kind === REPORT_KIND ? doc : null);
|
|
8062
|
+
if (!report && typeof doc?.deliverable === "string" && doc.deliverable.length <= REPORT_LIMITS.bytes) {
|
|
8063
|
+
try {
|
|
8064
|
+
const pasted = obj(JSON.parse(doc.deliverable));
|
|
8065
|
+
if (pasted?.kind === REPORT_KIND && obj(pasted.summary)) report = pasted;
|
|
8066
|
+
} catch {
|
|
8067
|
+
}
|
|
8068
|
+
}
|
|
8062
8069
|
if (!report) return null;
|
|
8063
8070
|
const summary = obj(report.summary) ?? {};
|
|
8064
8071
|
const read9 = {
|
|
@@ -8090,6 +8097,16 @@ function readReport(envelope) {
|
|
|
8090
8097
|
sections: Object.entries(obj(report.sections) ?? {}).map(([title, value]) => ({ title, text: String(value) }))
|
|
8091
8098
|
};
|
|
8092
8099
|
const files = (Array.isArray(doc?.files) ? doc.files : []).map(obj).filter((one) => one !== null && typeof one.id === "string" && typeof one.url === "string" && STORED.test(one.url)).slice(0, 24).map((one) => ({ id: String(one.id), url: String(one.url), sha256: String(one.sha256 ?? ""), mediaType: String(one.mediaType ?? "application/octet-stream"), name: String(one.name ?? one.id) }));
|
|
8100
|
+
for (const field2 of Object.values(doc ?? {})) {
|
|
8101
|
+
if (!Array.isArray(field2)) continue;
|
|
8102
|
+
for (const value of field2) {
|
|
8103
|
+
const file3 = obj(value);
|
|
8104
|
+
if (!file3 || !isStoredFile(file3.content) || files.some((one) => one.url === file3.content) || files.length >= 24) continue;
|
|
8105
|
+
let id = `attachment-${files.length + 1}`;
|
|
8106
|
+
while (files.some((one) => one.id === id)) id += "-file";
|
|
8107
|
+
files.push({ id, url: file3.content, sha256: "", mediaType: str(file3.type) ?? "application/octet-stream", name: str(file3.name) ?? "Attachment" });
|
|
8108
|
+
}
|
|
8109
|
+
}
|
|
8093
8110
|
return { doc: read9, files };
|
|
8094
8111
|
}
|
|
8095
8112
|
function formAnswersOf(envelope) {
|
|
@@ -30395,6 +30412,73 @@ var init_site = __esm({
|
|
|
30395
30412
|
}
|
|
30396
30413
|
});
|
|
30397
30414
|
|
|
30415
|
+
// src/keyPermissions.ts
|
|
30416
|
+
import { spawnSync } from "node:child_process";
|
|
30417
|
+
import { lstatSync } from "node:fs";
|
|
30418
|
+
import { win32 } from "node:path";
|
|
30419
|
+
function assertWindowsPrivatePath(path, run2 = spawnSync) {
|
|
30420
|
+
const root = process.env.SystemRoot;
|
|
30421
|
+
if (!root || !win32.isAbsolute(root)) throw new Error(`Cannot verify Windows permissions for ${path}: SystemRoot is unavailable. No key was read.`);
|
|
30422
|
+
const powershellHome = win32.join(root, "System32", "WindowsPowerShell", "v1.0");
|
|
30423
|
+
const env = { ...process.env };
|
|
30424
|
+
for (const name8 of Object.keys(env)) if (/^(psmodulepath|openhands_acl_path)$/i.test(name8)) delete env[name8];
|
|
30425
|
+
const result = run2(win32.join(powershellHome, "powershell.exe"), [
|
|
30426
|
+
"-NoLogo",
|
|
30427
|
+
"-NoProfile",
|
|
30428
|
+
"-NonInteractive",
|
|
30429
|
+
"-EncodedCommand",
|
|
30430
|
+
Buffer.from(CHECK_ACL, "utf16le").toString("base64")
|
|
30431
|
+
], {
|
|
30432
|
+
env: { ...env, PSModulePath: win32.join(powershellHome, "Modules"), OPENHANDS_ACL_PATH: path },
|
|
30433
|
+
timeout: 3e4,
|
|
30434
|
+
windowsHide: true,
|
|
30435
|
+
stdio: "ignore"
|
|
30436
|
+
});
|
|
30437
|
+
if (result.error || result.signal || result.status !== 0) {
|
|
30438
|
+
const why = result.error?.code === "ETIMEDOUT" ? "inspection timed out" : `inspection exit ${result.status ?? "unavailable"}`;
|
|
30439
|
+
throw new Error(`Cannot verify private Windows permissions for ${path} (${why}). Only your Windows account, SYSTEM and Administrators may have access, and your account must have Full control. Check Properties > Security, or choose a private OPENHANDS_HOME. No key was read.`);
|
|
30440
|
+
}
|
|
30441
|
+
}
|
|
30442
|
+
function assertSafe(path, what, platform = process.platform, run2) {
|
|
30443
|
+
const stat = lstatSync(path);
|
|
30444
|
+
if (stat.isSymbolicLink()) throw new Error(`${path} is a symlink. A key that lives where another program decides is not a key we will use.`);
|
|
30445
|
+
if (what === "file" ? !stat.isFile() : !stat.isDirectory()) throw new Error(`${path} is not a ${what}.`);
|
|
30446
|
+
if (platform === "win32") {
|
|
30447
|
+
assertWindowsPrivatePath(path, run2);
|
|
30448
|
+
return;
|
|
30449
|
+
}
|
|
30450
|
+
const mode = stat.mode & 511;
|
|
30451
|
+
const tooOpen = what === "file" ? mode & 63 : mode & 18;
|
|
30452
|
+
if (tooOpen) throw new Error(`${path} is readable or writable by others (mode ${mode.toString(8)}). Run: chmod ${what === "file" ? "600" : "700"} ${path}`);
|
|
30453
|
+
}
|
|
30454
|
+
var CHECK_ACL;
|
|
30455
|
+
var init_keyPermissions = __esm({
|
|
30456
|
+
"src/keyPermissions.ts"() {
|
|
30457
|
+
"use strict";
|
|
30458
|
+
CHECK_ACL = String.raw`
|
|
30459
|
+
$ErrorActionPreference = 'Stop'
|
|
30460
|
+
try {
|
|
30461
|
+
$item = Get-Item -LiteralPath $env:OPENHANDS_ACL_PATH -Force
|
|
30462
|
+
if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { exit 2 }
|
|
30463
|
+
$acl = Get-Acl -LiteralPath $env:OPENHANDS_ACL_PATH
|
|
30464
|
+
$me = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
|
30465
|
+
$trusted = @($me, 'S-1-5-18', 'S-1-5-32-544')
|
|
30466
|
+
if ($trusted -notcontains $acl.GetOwner([Security.Principal.SecurityIdentifier]).Value) { exit 3 }
|
|
30467
|
+
$mine = $false
|
|
30468
|
+
foreach ($rule in $acl.GetAccessRules($true, $true, [Security.Principal.SecurityIdentifier])) {
|
|
30469
|
+
if (($rule.PropagationFlags -band [Security.AccessControl.PropagationFlags]::InheritOnly) -ne 0) { continue }
|
|
30470
|
+
if ($rule.AccessControlType -ne [Security.AccessControl.AccessControlType]::Allow) { continue }
|
|
30471
|
+
if ($trusted -notcontains $rule.IdentityReference.Value) { exit 3 }
|
|
30472
|
+
if ($rule.IdentityReference.Value -eq $me -and
|
|
30473
|
+
($rule.FileSystemRights -band [Security.AccessControl.FileSystemRights]::FullControl) -eq [Security.AccessControl.FileSystemRights]::FullControl) { $mine = $true }
|
|
30474
|
+
}
|
|
30475
|
+
if (-not $mine) { exit 3 }
|
|
30476
|
+
exit 0
|
|
30477
|
+
} catch { exit 4 }
|
|
30478
|
+
`;
|
|
30479
|
+
}
|
|
30480
|
+
});
|
|
30481
|
+
|
|
30398
30482
|
// ../../node_modules/.pnpm/cosmjs-types@0.11.0/node_modules/cosmjs-types/utf8.js
|
|
30399
30483
|
var require_utf82 = __commonJS({
|
|
30400
30484
|
"../../node_modules/.pnpm/cosmjs-types@0.11.0/node_modules/cosmjs-types/utf8.js"(exports) {
|
|
@@ -49621,26 +49705,14 @@ __export(keys_exports, {
|
|
|
49621
49705
|
signer: () => signer,
|
|
49622
49706
|
writePin: () => writePin
|
|
49623
49707
|
});
|
|
49624
|
-
import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
49708
|
+
import { chmodSync, existsSync, lstatSync as lstatSync2, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
49625
49709
|
import { homedir } from "node:os";
|
|
49626
49710
|
import { join as join3 } from "node:path";
|
|
49627
|
-
function assertSafe(path, what) {
|
|
49628
|
-
const stat = lstatSync(path);
|
|
49629
|
-
if (stat.isSymbolicLink()) {
|
|
49630
|
-
throw new Error(`${path} is a symlink. A key that lives where another program decides is not a key we will use.`);
|
|
49631
|
-
}
|
|
49632
|
-
const mode = stat.mode & 511;
|
|
49633
|
-
const tooOpen = what === "file" ? mode & 63 : mode & 18;
|
|
49634
|
-
if (tooOpen) {
|
|
49635
|
-
throw new Error(
|
|
49636
|
-
`${path} is readable or writable by others (mode ${mode.toString(8)}). Run: chmod ${what === "file" ? "600" : "700"} ${path}`
|
|
49637
|
-
);
|
|
49638
|
-
}
|
|
49639
|
-
}
|
|
49640
49711
|
function ensureDir() {
|
|
49641
49712
|
const dir = DIR();
|
|
49642
49713
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 448 });
|
|
49643
|
-
|
|
49714
|
+
if (lstatSync2(dir).isSymbolicLink()) throw new Error(`${dir} is a symlink.`);
|
|
49715
|
+
if (process.platform !== "win32") chmodSync(dir, 448);
|
|
49644
49716
|
assertSafe(dir, "dir");
|
|
49645
49717
|
}
|
|
49646
49718
|
async function loadKey() {
|
|
@@ -49667,7 +49739,7 @@ async function loadKey() {
|
|
|
49667
49739
|
const [account] = await wallet.getAccounts();
|
|
49668
49740
|
const made = { mnemonic: wallet.mnemonic, address: account.address };
|
|
49669
49741
|
writeFileSync(KEY_FILE2, JSON.stringify(made, null, 2), { mode: 384 });
|
|
49670
|
-
chmodSync(KEY_FILE2, 384);
|
|
49742
|
+
if (process.platform !== "win32") chmodSync(KEY_FILE2, 384);
|
|
49671
49743
|
assertSafe(KEY_FILE2, "file");
|
|
49672
49744
|
cached2.set(KEY_FILE2, made);
|
|
49673
49745
|
return made;
|
|
@@ -49691,6 +49763,10 @@ async function signer() {
|
|
|
49691
49763
|
function readPin() {
|
|
49692
49764
|
const PIN_FILE = pinFile();
|
|
49693
49765
|
if (!existsSync(PIN_FILE)) return null;
|
|
49766
|
+
const { ino, mtimeMs, ctimeMs } = lstatSync2(PIN_FILE);
|
|
49767
|
+
const stamp = `${ino}:${mtimeMs}:${ctimeMs}`;
|
|
49768
|
+
const seen2 = pins.get(PIN_FILE);
|
|
49769
|
+
if (seen2?.stamp === stamp) return seen2.pin;
|
|
49694
49770
|
assertSafe(PIN_FILE, "file");
|
|
49695
49771
|
let parsed;
|
|
49696
49772
|
try {
|
|
@@ -49701,7 +49777,9 @@ function readPin() {
|
|
|
49701
49777
|
if (typeof parsed.principal !== "string" || !PRINCIPAL.test(parsed.principal) || typeof parsed.agent !== "string") {
|
|
49702
49778
|
throw new Error(`${PIN_FILE} does not name an account. Remove it and call connect_account again.`);
|
|
49703
49779
|
}
|
|
49704
|
-
|
|
49780
|
+
const pin = { principal: parsed.principal, agent: parsed.agent, pinnedAt: typeof parsed.pinnedAt === "string" ? parsed.pinnedAt : "", ...parsed.signed === true ? { signed: true } : {} };
|
|
49781
|
+
pins.set(PIN_FILE, { stamp, pin });
|
|
49782
|
+
return pin;
|
|
49705
49783
|
}
|
|
49706
49784
|
function writePin(pin) {
|
|
49707
49785
|
if (!PRINCIPAL.test(pin.principal)) throw new Error("that is not an ixo account address");
|
|
@@ -49709,17 +49787,18 @@ function writePin(pin) {
|
|
|
49709
49787
|
const PIN_FILE = pinFile();
|
|
49710
49788
|
const temporary = `${PIN_FILE}.tmp`;
|
|
49711
49789
|
writeFileSync(temporary, JSON.stringify(pin, null, 2), { mode: 384 });
|
|
49712
|
-
chmodSync(temporary, 384);
|
|
49790
|
+
if (process.platform !== "win32") chmodSync(temporary, 384);
|
|
49713
49791
|
renameSync(temporary, PIN_FILE);
|
|
49714
49792
|
assertSafe(PIN_FILE, "file");
|
|
49715
49793
|
}
|
|
49716
49794
|
function clearPin() {
|
|
49717
49795
|
rmSync(pinFile(), { force: true });
|
|
49718
49796
|
}
|
|
49719
|
-
var DIR, KEY_FILE, cached2, keyLocation, pinFile, PRINCIPAL, pinLocation;
|
|
49797
|
+
var DIR, KEY_FILE, cached2, keyLocation, pinFile, PRINCIPAL, pins, pinLocation;
|
|
49720
49798
|
var init_keys = __esm({
|
|
49721
49799
|
"src/keys.ts"() {
|
|
49722
49800
|
"use strict";
|
|
49801
|
+
init_keyPermissions();
|
|
49723
49802
|
init_net();
|
|
49724
49803
|
DIR = () => join3(process.env.OPENHANDS_HOME || join3(homedir(), ".openhands"), currentNetwork2());
|
|
49725
49804
|
KEY_FILE = () => join3(DIR(), "key.json");
|
|
@@ -49727,6 +49806,7 @@ var init_keys = __esm({
|
|
|
49727
49806
|
keyLocation = () => KEY_FILE();
|
|
49728
49807
|
pinFile = () => join3(DIR(), "principal.json");
|
|
49729
49808
|
PRINCIPAL = /^(ixo1[a-z0-9]{38,58}|self)$/;
|
|
49809
|
+
pins = /* @__PURE__ */ new Map();
|
|
49730
49810
|
pinLocation = () => pinFile();
|
|
49731
49811
|
}
|
|
49732
49812
|
});
|
|
@@ -259464,8 +259544,8 @@ function noteIgnored(agent, pinned, others, log) {
|
|
|
259464
259544
|
for (const one of fresh2) told.add(`${agent}:${pinned}:${one}`);
|
|
259465
259545
|
log(`asks-mcp: ignoring ${fresh2.length === 1 ? "a connection" : "connections"} to this key from ${fresh2.join(", ")}: it is pinned to ${pinned === SELF ? "itself" : pinned}, and only connect_account changes that.`);
|
|
259466
259546
|
}
|
|
259467
|
-
async function actingFor(agent, reads2 = chainReads,
|
|
259468
|
-
const pinned = pinOf(agent,
|
|
259547
|
+
async function actingFor(agent, reads2 = chainReads, pins2 = keyPins) {
|
|
259548
|
+
const pinned = pinOf(agent, pins2);
|
|
259469
259549
|
if (!pinned || pinned === SELF) return null;
|
|
259470
259550
|
let found;
|
|
259471
259551
|
try {
|
|
@@ -259475,14 +259555,14 @@ async function actingFor(agent, reads2 = chainReads, pins = keyPins) {
|
|
|
259475
259555
|
}
|
|
259476
259556
|
return found ? actingOf(found) : null;
|
|
259477
259557
|
}
|
|
259478
|
-
async function actingOrWhy(agent, reads2 = chainReads,
|
|
259558
|
+
async function actingOrWhy(agent, reads2 = chainReads, pins2 = keyPins, log = stderr) {
|
|
259479
259559
|
let acting = null;
|
|
259480
259560
|
try {
|
|
259481
|
-
acting = await actingFor(agent, reads2,
|
|
259561
|
+
acting = await actingFor(agent, reads2, pins2);
|
|
259482
259562
|
} catch (error54) {
|
|
259483
259563
|
return { acting: null, problem: error54 instanceof Error ? error54.message : String(error54), candidates: [] };
|
|
259484
259564
|
}
|
|
259485
|
-
const pinned = pinOf(agent,
|
|
259565
|
+
const pinned = pinOf(agent, pins2);
|
|
259486
259566
|
let standing;
|
|
259487
259567
|
try {
|
|
259488
259568
|
standing = (await reads2.connectionsTo(agent)).map((one) => one.principal);
|
|
@@ -259499,16 +259579,16 @@ function candidateWords(candidates) {
|
|
|
259499
259579
|
if (!candidates.length) return null;
|
|
259500
259580
|
return `Not acting for anyone: ${candidates.length === 1 ? "an account has" : `${candidates.length} accounts have`} connected this key (${list5(candidates)}), and none has been chosen. Anybody can grant to a public address, so ask the person which is theirs and call connect_account with principal set to it (or principal: "self" to stay as this key and stop being asked).`;
|
|
259501
259581
|
}
|
|
259502
|
-
async function connectAs(agent, choice, reads2 = chainReads,
|
|
259503
|
-
const pinned = pinOf(agent,
|
|
259582
|
+
async function connectAs(agent, choice, reads2 = chainReads, pins2 = keyPins, now3 = /* @__PURE__ */ new Date(), log = stderr) {
|
|
259583
|
+
const pinned = pinOf(agent, pins2);
|
|
259504
259584
|
if (choice === SELF) {
|
|
259505
|
-
if (pinned !== SELF)
|
|
259585
|
+
if (pinned !== SELF) pins2.write({ principal: SELF, agent, pinnedAt: now3.toISOString() });
|
|
259506
259586
|
return { kind: "self", pinnedNow: pinned !== SELF };
|
|
259507
259587
|
}
|
|
259508
259588
|
if (choice) {
|
|
259509
259589
|
const found = await reads2.connectionFor(agent, choice);
|
|
259510
259590
|
if (!found) return { kind: "not-granted", principal: choice };
|
|
259511
|
-
if (pinned !== choice)
|
|
259591
|
+
if (pinned !== choice) pins2.write({ principal: choice, agent, pinnedAt: now3.toISOString() });
|
|
259512
259592
|
const others = (await reads2.connectionsTo(agent)).map((one) => one.principal).filter((one) => one !== choice);
|
|
259513
259593
|
return { kind: "connected", acting: actingOf(found), pinnedNow: pinned !== choice, others };
|
|
259514
259594
|
}
|
|
@@ -259522,13 +259602,13 @@ async function connectAs(agent, choice, reads2 = chainReads, pins = keyPins, now
|
|
|
259522
259602
|
const standing = (await reads2.connectionsTo(agent)).map((one) => one.principal);
|
|
259523
259603
|
return standing.length ? { kind: "choose", principals: standing } : { kind: "none" };
|
|
259524
259604
|
}
|
|
259525
|
-
function markSigned(agent, principal2,
|
|
259526
|
-
const pin =
|
|
259527
|
-
if (pin && pin.agent === agent && pin.principal === principal2 && !pin.signed)
|
|
259605
|
+
function markSigned(agent, principal2, pins2 = keyPins) {
|
|
259606
|
+
const pin = pins2.read();
|
|
259607
|
+
if (pin && pin.agent === agent && pin.principal === principal2 && !pin.signed) pins2.write({ ...pin, signed: true });
|
|
259528
259608
|
}
|
|
259529
|
-
function disconnect(
|
|
259530
|
-
const pin =
|
|
259531
|
-
|
|
259609
|
+
function disconnect(pins2 = keyPins) {
|
|
259610
|
+
const pin = pins2.read();
|
|
259611
|
+
pins2.clear();
|
|
259532
259612
|
return pin;
|
|
259533
259613
|
}
|
|
259534
259614
|
function actingWords(acting, face) {
|
|
@@ -259604,8 +259684,8 @@ var init_connect = __esm({
|
|
|
259604
259684
|
SELF = "self";
|
|
259605
259685
|
told = /* @__PURE__ */ new Set();
|
|
259606
259686
|
stderr = (line2) => console.error(line2);
|
|
259607
|
-
pinOf = (agent,
|
|
259608
|
-
const pin =
|
|
259687
|
+
pinOf = (agent, pins2) => {
|
|
259688
|
+
const pin = pins2.read();
|
|
259609
259689
|
return pin && pin.agent === agent ? pin.principal : null;
|
|
259610
259690
|
};
|
|
259611
259691
|
feeAllowanceSpent = (error54) => /codespace:?\s*feegrant|fee limit exceeded|fee allowance/i.test(error54 instanceof Error ? error54.message : String(error54));
|
|
@@ -291941,7 +292021,7 @@ import { randomBytes as randomBytes7 } from "node:crypto";
|
|
|
291941
292021
|
// package.json
|
|
291942
292022
|
var package_default = {
|
|
291943
292023
|
name: "@ixo/asks-mcp",
|
|
291944
|
-
version: "1.6.0-dev.
|
|
292024
|
+
version: "1.6.0-dev.4",
|
|
291945
292025
|
description: "The Openhands Network MCP server: read the board, answer asks, post work and get paid on the ixo chain, with a key of its own on your machine.",
|
|
291946
292026
|
license: "Apache-2.0",
|
|
291947
292027
|
repository: {
|
|
@@ -296091,7 +296171,7 @@ ${settings}`
|
|
|
296091
296171
|
confirm: external_exports.string().optional().describe("The exact total from the preview. Omit to preview.")
|
|
296092
296172
|
}
|
|
296093
296173
|
},
|
|
296094
|
-
async ({ name: name8, pins, pin_pay, currency, days, metres, confirm }) => {
|
|
296174
|
+
async ({ name: name8, pins: pins2, pin_pay, currency, days, metres, confirm }) => {
|
|
296095
296175
|
const acting = await principal2();
|
|
296096
296176
|
const coin = await coinFor(currency, acting);
|
|
296097
296177
|
if (typeof coin !== "string") return text4(`Refused: ${coin.refused}. Nothing was opened and nothing was spent.`);
|
|
@@ -296112,8 +296192,8 @@ ${settings}`
|
|
|
296112
296192
|
terms.via = "mcp";
|
|
296113
296193
|
const unread = await unreadable(terms);
|
|
296114
296194
|
if (unread) return text4(unread);
|
|
296115
|
-
const { total } = escrowTotal(pin_pay,
|
|
296116
|
-
const preview = `${face(total, coinSymbol)} to open "${name8}". ${
|
|
296195
|
+
const { total } = escrowTotal(pin_pay, pins2, legBase, coinSymbol, coin === FEE_DENOM);
|
|
296196
|
+
const preview = `${face(total, coinSymbol)} to open "${name8}". ${pins2} ${pins2 === 1 ? "report" : "reports"} at ${face(pin_pay, coinSymbol)} each, plus ${feeWords(legBase)} to whoever checks each one. Mapping stays open ${open} days, and two pins closer than ${apart} m are the same mess`;
|
|
296117
296197
|
const guard = spendGuard({ bucket: "organise", task: `campaign ${tag}`, total, ...confirm ? { confirm } : {}, what: preview, symbol: coinSymbol });
|
|
296118
296198
|
if (guard) {
|
|
296119
296199
|
return text4(
|
|
@@ -296138,7 +296218,7 @@ Mapping pays for the report. What happens to a mapped pin afterwards is a separa
|
|
|
296138
296218
|
...await postedAs(acting),
|
|
296139
296219
|
title: terms.task,
|
|
296140
296220
|
amountUixo: baseFromPay2(pin_pay),
|
|
296141
|
-
quota:
|
|
296221
|
+
quota: pins2,
|
|
296142
296222
|
days: open,
|
|
296143
296223
|
denom: coin,
|
|
296144
296224
|
terms,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ixo/asks-mcp",
|
|
3
|
-
"version": "1.6.0-dev.
|
|
3
|
+
"version": "1.6.0-dev.4",
|
|
4
4
|
"description": "The Openhands Network MCP server: read the board, answer asks, post work and get paid on the ixo chain, with a key of its own on your machine.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|