@elgora/cli 7.4.0 → 7.5.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 +4 -0
- package/dist/cli.js +144 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,6 +21,10 @@ Or run one command without installing:
|
|
|
21
21
|
npx @elgora/cli --help
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
+
Every command refuses to start on a Node outside the `engines` range the
|
|
25
|
+
package declares, and says which range it needs. `--version` and `help` still
|
|
26
|
+
answer.
|
|
27
|
+
|
|
24
28
|
## Commands
|
|
25
29
|
|
|
26
30
|
- `poster:publish-fund` publishes approved `bounty_challenge.md` bytes and
|
package/dist/cli.js
CHANGED
|
@@ -1142,8 +1142,12 @@ async function openAesKeyForRecipient(input) {
|
|
|
1142
1142
|
}
|
|
1143
1143
|
}
|
|
1144
1144
|
if (!matched) {
|
|
1145
|
+
const wanted = [...new Set(wrappedKeys.map((key) => key.key_id))].sort();
|
|
1146
|
+
const configured = [
|
|
1147
|
+
...new Set(privateKeys.map((key) => key.key_id))
|
|
1148
|
+
].sort();
|
|
1145
1149
|
throw new Error(
|
|
1146
|
-
|
|
1150
|
+
`no matching Guardian private key for private artifact envelope: this envelope was sealed for key_id ${JSON.stringify(wanted)}, but ELGORA_GUARDIAN_PRIVATE_KEYS_JSON configures key_id ${JSON.stringify(configured)}. key_id must be this Guardian's own registered roster name (the name passed to roster:add-guardian), not its account address.`
|
|
1147
1151
|
);
|
|
1148
1152
|
}
|
|
1149
1153
|
throw new Error("matching Guardian private key could not open wrapped key", {
|
|
@@ -6902,6 +6906,70 @@ function resolveChainId(selector) {
|
|
|
6902
6906
|
);
|
|
6903
6907
|
}
|
|
6904
6908
|
|
|
6909
|
+
// src/version.ts
|
|
6910
|
+
var CLI_VERSION = true ? "7.5.0" : "0.0.0-dev";
|
|
6911
|
+
var CLI_NODE_ENGINES = true ? ">=24 <25" : ">=24 <25";
|
|
6912
|
+
|
|
6913
|
+
// src/node-engines.ts
|
|
6914
|
+
function unsupportedNodeMessage(running = process.versions.node, range = CLI_NODE_ENGINES) {
|
|
6915
|
+
if (satisfiesRange(running, range)) return null;
|
|
6916
|
+
return `elgora-cli needs Node ${range}; this is Node ${running}. Install a Node inside that range and run the command again. --version and help still answer.`;
|
|
6917
|
+
}
|
|
6918
|
+
function satisfiesRange(version, range) {
|
|
6919
|
+
const running = parseVersion(version);
|
|
6920
|
+
if (!running) return false;
|
|
6921
|
+
return range.split("||").some(
|
|
6922
|
+
(alternative) => alternative.trim().split(/\s+/).filter(Boolean).every((comparator) => satisfiesComparator(running, comparator))
|
|
6923
|
+
);
|
|
6924
|
+
}
|
|
6925
|
+
function satisfiesComparator(running, comparator) {
|
|
6926
|
+
const match = /^(>=|<=|>|<|=|\^|~)?v?(\d+)(?:\.(\d+|x|\*))?(?:\.(\d+|x|\*))?$/.exec(
|
|
6927
|
+
comparator
|
|
6928
|
+
);
|
|
6929
|
+
if (!match) return false;
|
|
6930
|
+
const operator = match[1] ?? "=";
|
|
6931
|
+
const major = Number(match[2]);
|
|
6932
|
+
const minorRaw = match[3];
|
|
6933
|
+
const patchRaw = match[4];
|
|
6934
|
+
const wildcard = minorRaw === void 0 || minorRaw === "x" || minorRaw === "*" || patchRaw === void 0 || patchRaw === "x" || patchRaw === "*";
|
|
6935
|
+
const bound = [
|
|
6936
|
+
major,
|
|
6937
|
+
minorRaw && !["x", "*"].includes(minorRaw) ? Number(minorRaw) : 0,
|
|
6938
|
+
patchRaw && !["x", "*"].includes(patchRaw) ? Number(patchRaw) : 0
|
|
6939
|
+
];
|
|
6940
|
+
const order = compareVersions(running, bound);
|
|
6941
|
+
switch (operator) {
|
|
6942
|
+
case ">":
|
|
6943
|
+
return order > 0;
|
|
6944
|
+
case ">=":
|
|
6945
|
+
return order >= 0;
|
|
6946
|
+
case "<":
|
|
6947
|
+
return order < 0;
|
|
6948
|
+
case "<=":
|
|
6949
|
+
return order <= 0;
|
|
6950
|
+
case "^":
|
|
6951
|
+
return order >= 0 && running[0] === major;
|
|
6952
|
+
case "~": {
|
|
6953
|
+
const anyMinor = minorRaw === void 0 || minorRaw === "x" || minorRaw === "*";
|
|
6954
|
+
return order >= 0 && running[0] === major && (anyMinor || running[1] === bound[1]);
|
|
6955
|
+
}
|
|
6956
|
+
default:
|
|
6957
|
+
return wildcard ? running[0] === major && (minorRaw === void 0 || ["x", "*"].includes(minorRaw) || running[1] === bound[1]) : order === 0;
|
|
6958
|
+
}
|
|
6959
|
+
}
|
|
6960
|
+
function parseVersion(version) {
|
|
6961
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)/.exec(version.trim());
|
|
6962
|
+
if (!match) return null;
|
|
6963
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
6964
|
+
}
|
|
6965
|
+
function compareVersions(left, right) {
|
|
6966
|
+
for (let index = 0; index < 3; index += 1) {
|
|
6967
|
+
const difference = (left[index] ?? 0) - (right[index] ?? 0);
|
|
6968
|
+
if (difference !== 0) return difference;
|
|
6969
|
+
}
|
|
6970
|
+
return 0;
|
|
6971
|
+
}
|
|
6972
|
+
|
|
6905
6973
|
// src/poster-open-winning-submission.ts
|
|
6906
6974
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
6907
6975
|
import path2 from "path";
|
|
@@ -7276,6 +7344,11 @@ function loadPublishFundConfig(env, apiBaseUrlOverride) {
|
|
|
7276
7344
|
};
|
|
7277
7345
|
}
|
|
7278
7346
|
|
|
7347
|
+
// src/required-env.ts
|
|
7348
|
+
function missingRequiredEnv(required4, env) {
|
|
7349
|
+
return required4.filter((envName) => !env[envName]?.trim());
|
|
7350
|
+
}
|
|
7351
|
+
|
|
7279
7352
|
// src/roster-add-guardian.ts
|
|
7280
7353
|
import { isAddress as isAddress4 } from "viem";
|
|
7281
7354
|
import { privateKeyToAccount as privateKeyToAccount9 } from "viem/accounts";
|
|
@@ -7312,6 +7385,11 @@ async function main10(env = process.env, argv = process.argv.slice(2)) {
|
|
|
7312
7385
|
`${args.guardianAccount} is already on the Guardian roster`
|
|
7313
7386
|
);
|
|
7314
7387
|
}
|
|
7388
|
+
if (roster.some((guardian) => guardian.name === args.name)) {
|
|
7389
|
+
throw new Error(
|
|
7390
|
+
`"${args.name}" is already the name of a Guardian on the roster; names must be unique`
|
|
7391
|
+
);
|
|
7392
|
+
}
|
|
7315
7393
|
const contractArgs = [
|
|
7316
7394
|
args.name,
|
|
7317
7395
|
args.guardianAccount,
|
|
@@ -7395,6 +7473,14 @@ function parseRosterAddGuardianArgs(argv) {
|
|
|
7395
7473
|
const guardianAccount = positional[1]?.trim();
|
|
7396
7474
|
const encryptionPublicKey = positional[2]?.trim();
|
|
7397
7475
|
if (!name) throw usageError2();
|
|
7476
|
+
if (!KEY_ID_RE.test(name)) {
|
|
7477
|
+
throw new Error(
|
|
7478
|
+
// A Solver's CLI seals private artifacts against this exact string as
|
|
7479
|
+
// the Guardian's key_id, so it can't contain spaces or punctuation the
|
|
7480
|
+
// wire format rejects — no display-only rendering happens in between.
|
|
7481
|
+
`name must match ${KEY_ID_RE.source} (it becomes this Guardian's private-artifact key_id)`
|
|
7482
|
+
);
|
|
7483
|
+
}
|
|
7398
7484
|
if (!guardianAccount || !isAddress4(guardianAccount)) {
|
|
7399
7485
|
throw new Error("guardian_account must be a valid EVM address");
|
|
7400
7486
|
}
|
|
@@ -7928,6 +8014,23 @@ function requirePinnedGuardianRosterHash(hubGuardianRosterHash, apiGuardianRoste
|
|
|
7928
8014
|
);
|
|
7929
8015
|
}
|
|
7930
8016
|
}
|
|
8017
|
+
function requireUsableGuardianNames(roster) {
|
|
8018
|
+
const seen = /* @__PURE__ */ new Map();
|
|
8019
|
+
for (const guardian of roster) {
|
|
8020
|
+
if (!KEY_ID_RE.test(guardian.name)) {
|
|
8021
|
+
throw new Error(
|
|
8022
|
+
`Guardian ${guardian.account} has roster name ${JSON.stringify(guardian.name)}, which cannot be used as a private-artifact key_id (must match ${KEY_ID_RE.source}). This roster cannot receive private submissions until its admin corrects the name.`
|
|
8023
|
+
);
|
|
8024
|
+
}
|
|
8025
|
+
const collidingAccount = seen.get(guardian.name);
|
|
8026
|
+
if (collidingAccount) {
|
|
8027
|
+
throw new Error(
|
|
8028
|
+
`Guardians ${collidingAccount} and ${guardian.account} share the roster name ${JSON.stringify(guardian.name)}. Names must be unique on the pinned roster so each Guardian's key_id resolves to exactly one recipient.`
|
|
8029
|
+
);
|
|
8030
|
+
}
|
|
8031
|
+
seen.set(guardian.name, guardian.account);
|
|
8032
|
+
}
|
|
8033
|
+
}
|
|
7931
8034
|
function requireMatchingSubmitTransaction(actual, expected) {
|
|
7932
8035
|
if (actual.chain_id !== expected.chain_id || actual.to.toLowerCase() !== expected.to.toLowerCase() || actual.value !== expected.value || actual.data.toLowerCase() !== expected.data.toLowerCase() || actual.function_name !== expected.function_name || actual.args.bounty_id !== expected.args.bounty_id || actual.args.submission_commitment.toLowerCase() !== expected.args.submission_commitment.toLowerCase() || actual.args.guardian_roster_hash.toLowerCase() !== expected.args.guardian_roster_hash.toLowerCase() || actual.args.extra_data !== expected.args.extra_data) {
|
|
7933
8036
|
throw new Error(
|
|
@@ -7949,9 +8052,10 @@ function requireOpenSubmittableBounty(bountyFacts, bountyId) {
|
|
|
7949
8052
|
async function buildSolverSubmissionEnvelope(input) {
|
|
7950
8053
|
const solver = input.solver.toLowerCase();
|
|
7951
8054
|
const bountyId = input.bountyId.toString();
|
|
8055
|
+
requireUsableGuardianNames(input.guardianRoster);
|
|
7952
8056
|
const guardianRecipients = input.guardianRoster.map(
|
|
7953
8057
|
(guardian) => ({
|
|
7954
|
-
key_id: guardian.
|
|
8058
|
+
key_id: guardian.name,
|
|
7955
8059
|
suite: PRIVATE_ARTIFACT_HPKE_SUITE,
|
|
7956
8060
|
public_key: guardian.encryptionPublicKey
|
|
7957
8061
|
})
|
|
@@ -8215,19 +8319,19 @@ function usageError4() {
|
|
|
8215
8319
|
return new Error(VERIFICATION_RECORD_USAGE);
|
|
8216
8320
|
}
|
|
8217
8321
|
|
|
8218
|
-
// src/version.ts
|
|
8219
|
-
var CLI_VERSION = true ? "7.4.0" : "0.0.0-dev";
|
|
8220
|
-
|
|
8221
8322
|
// src/cli.ts
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
quiet: true
|
|
8225
|
-
|
|
8226
|
-
})
|
|
8227
|
-
process.stderr.write(
|
|
8228
|
-
`Env: ${dotenvPath} (${dotenvResult.error ? "not found, using process env only" : "loaded"})
|
|
8323
|
+
if (process.env.DOTENV_CONFIG_PATH) {
|
|
8324
|
+
const dotenvPath = path4.resolve(process.env.DOTENV_CONFIG_PATH);
|
|
8325
|
+
const dotenvResult = loadDotenv({ path: dotenvPath, quiet: true });
|
|
8326
|
+
process.stderr.write(
|
|
8327
|
+
`Env: ${dotenvPath} (${dotenvResult.error ? `not found: ${dotenvResult.error.message}` : "loaded"})
|
|
8229
8328
|
`
|
|
8230
|
-
);
|
|
8329
|
+
);
|
|
8330
|
+
} else {
|
|
8331
|
+
process.stderr.write(
|
|
8332
|
+
"Env: DOTENV_CONFIG_PATH not set; reading process.env only\n"
|
|
8333
|
+
);
|
|
8334
|
+
}
|
|
8231
8335
|
var COMMANDS = {
|
|
8232
8336
|
"guardian:judgeable": main5,
|
|
8233
8337
|
"guardian:open": main3,
|
|
@@ -8342,7 +8446,7 @@ var COMMAND_HELP = {
|
|
|
8342
8446
|
"roster:add-guardian": {
|
|
8343
8447
|
usage: "elgora-cli roster:add-guardian [--roster-admin-address <address>]\n <name> <guardian_account> <encryption_public_key>",
|
|
8344
8448
|
title: "Append one Guardian to the live roster.",
|
|
8345
|
-
description: "Call ElgoraHub's guardianRosterAdmin-only addGuardian directly. Only\n future bounties see the new roster; open bounties keep judging\n against the hash pinned when they were created. With\n --roster-admin-address, the transaction is simulated and printed\n unsigned (to, data, value) for an external signer such as a Safe,\n instead of being sent. Run roster:prepare-guardian-proof,\n guardian:prove-roster-key, and roster:verify-guardian-proof against\n the proposed Guardian before ever calling this.",
|
|
8449
|
+
description: "Call ElgoraHub's guardianRosterAdmin-only addGuardian directly. Only\n future bounties see the new roster; open bounties keep judging\n against the hash pinned when they were created. With\n --roster-admin-address, the transaction is simulated and printed\n unsigned (to, data, value) for an external signer such as a Safe,\n instead of being sent. Run roster:prepare-guardian-proof,\n guardian:prove-roster-key, and roster:verify-guardian-proof against\n the proposed Guardian before ever calling this. name becomes this\n Guardian's key_id for every private submission a Solver seals\n afterward \u2014 it must be unique on the roster and is what this\n Guardian's own ELGORA_GUARDIAN_PRIVATE_KEYS_JSON entries must use as\n their key_id, not the account address.",
|
|
8346
8450
|
required: [],
|
|
8347
8451
|
defaulted: ["ELGORA_CHAIN_ID", "ELGORA_HUB_ADDRESS", "ELGORA_RPC_URL"]
|
|
8348
8452
|
},
|
|
@@ -8434,6 +8538,32 @@ async function run(rawArgv) {
|
|
|
8434
8538
|
printCommandHelpOrUsage(subcommand);
|
|
8435
8539
|
return;
|
|
8436
8540
|
}
|
|
8541
|
+
const nodeProblem = unsupportedNodeMessage();
|
|
8542
|
+
if (nodeProblem) {
|
|
8543
|
+
process.stderr.write(`${nodeProblem}
|
|
8544
|
+
`);
|
|
8545
|
+
process.exitCode = 1;
|
|
8546
|
+
return;
|
|
8547
|
+
}
|
|
8548
|
+
const missing = missingRequiredEnv(
|
|
8549
|
+
COMMAND_HELP[subcommand]?.required ?? [],
|
|
8550
|
+
env
|
|
8551
|
+
);
|
|
8552
|
+
if (missing.length > 0) {
|
|
8553
|
+
process.stderr.write(
|
|
8554
|
+
`${[
|
|
8555
|
+
`${subcommand} is missing required environment variable${missing.length > 1 ? "s" : ""}:`,
|
|
8556
|
+
...missing.map((envName) => ` ${envName}`),
|
|
8557
|
+
"",
|
|
8558
|
+
"This CLI reads only process.env (plus DOTENV_CONFIG_PATH if you set it)",
|
|
8559
|
+
"\u2014 it never assumes a secrets location. Provide these before invoking",
|
|
8560
|
+
`elgora-cli, or run \`elgora-cli help ${subcommand}\` for the full list.`
|
|
8561
|
+
].join("\n")}
|
|
8562
|
+
`
|
|
8563
|
+
);
|
|
8564
|
+
process.exitCode = 1;
|
|
8565
|
+
return;
|
|
8566
|
+
}
|
|
8437
8567
|
await command(env, rest);
|
|
8438
8568
|
}
|
|
8439
8569
|
function printUsage() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elgora/cli",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.5.0",
|
|
4
4
|
"description": "Command-line client for Elgora, a decentralized bounty marketplace for science. Lets Posters, Solvers, and Guardians interact with Elgora's public API and on-chain contracts.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|