@netmind/arena-cli 0.21.0 → 0.25.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 +2 -0
- package/dist/index.js +623 -113
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { readFileSync as readFileSync9 } from "fs";
|
|
5
|
-
import { Command as
|
|
5
|
+
import { Command as Command27 } from "commander";
|
|
6
6
|
|
|
7
7
|
// src/diag.ts
|
|
8
8
|
import { appendFileSync } from "fs";
|
|
@@ -326,9 +326,9 @@ function charCount(value) {
|
|
|
326
326
|
return String(value).length;
|
|
327
327
|
}
|
|
328
328
|
}
|
|
329
|
-
async function api(
|
|
329
|
+
async function api(path2, opts = {}) {
|
|
330
330
|
const { method = "GET", body, auth = false } = opts;
|
|
331
|
-
const url = `${getApiUrl()}${
|
|
331
|
+
const url = `${getApiUrl()}${path2}`;
|
|
332
332
|
const headers = {
|
|
333
333
|
"User-Agent": `arena-cli/${CLI_VERSION}`,
|
|
334
334
|
"X-Arena-Cli-Version": CLI_VERSION
|
|
@@ -369,7 +369,7 @@ async function api(path, opts = {}) {
|
|
|
369
369
|
emitApiDiag({
|
|
370
370
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
371
371
|
method,
|
|
372
|
-
path,
|
|
372
|
+
path: path2,
|
|
373
373
|
status: res.status,
|
|
374
374
|
latencyMs: Date.now() - startedAt,
|
|
375
375
|
reqChars: charCount(requestBody),
|
|
@@ -684,13 +684,13 @@ function ensureDir2(dir) {
|
|
|
684
684
|
mkdirSync3(dir, { recursive: true });
|
|
685
685
|
}
|
|
686
686
|
}
|
|
687
|
-
function writeJson(
|
|
687
|
+
function writeJson(path2, data) {
|
|
688
688
|
ensureCacheDir();
|
|
689
|
-
writeFileSync3(
|
|
689
|
+
writeFileSync3(path2, JSON.stringify(data, null, 2) + "\n");
|
|
690
690
|
}
|
|
691
|
-
function readJson(
|
|
691
|
+
function readJson(path2) {
|
|
692
692
|
try {
|
|
693
|
-
return JSON.parse(readFileSync4(
|
|
693
|
+
return JSON.parse(readFileSync4(path2, "utf-8"));
|
|
694
694
|
} catch {
|
|
695
695
|
return null;
|
|
696
696
|
}
|
|
@@ -1581,9 +1581,152 @@ Champion: ${res.champion.agentName} (${res.champion.returnPct}%)`);
|
|
|
1581
1581
|
});
|
|
1582
1582
|
var gameCmd = new Command5("game").description("Interact with a live competition").addCommand(stateCmd).addCommand(actCmd).addCommand(leaderboardCmd).addCommand(recapCmd).addCommand(gameCronCmd);
|
|
1583
1583
|
|
|
1584
|
-
// src/commands/
|
|
1584
|
+
// src/commands/bet.ts
|
|
1585
1585
|
import { Command as Command6 } from "commander";
|
|
1586
|
-
|
|
1586
|
+
async function loadMarket(id) {
|
|
1587
|
+
const res = await api(`/competitions/${id}`);
|
|
1588
|
+
const comp = res.data ?? res;
|
|
1589
|
+
const market = comp.bettingMarket;
|
|
1590
|
+
if (!market) {
|
|
1591
|
+
throw new Error("This competition is not a betting market.");
|
|
1592
|
+
}
|
|
1593
|
+
return market;
|
|
1594
|
+
}
|
|
1595
|
+
function toSmallestUnits(amount, decimals) {
|
|
1596
|
+
const [whole = "0", frac = ""] = String(amount).split(".");
|
|
1597
|
+
if (frac.length > decimals) {
|
|
1598
|
+
throw new Error(
|
|
1599
|
+
`Amount ${amount} has more than ${decimals} decimal places, which this token cannot represent.`
|
|
1600
|
+
);
|
|
1601
|
+
}
|
|
1602
|
+
const padded = (whole + frac.padEnd(decimals, "0")).replace(/^0+(?=\d)/, "");
|
|
1603
|
+
return padded === "" ? "0" : padded;
|
|
1604
|
+
}
|
|
1605
|
+
function parseStake(raw) {
|
|
1606
|
+
const amount = Number(raw);
|
|
1607
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
1608
|
+
return { error: "--amount must be a positive number" };
|
|
1609
|
+
}
|
|
1610
|
+
if (!Number.isInteger(amount)) {
|
|
1611
|
+
return {
|
|
1612
|
+
error: `--amount must be a whole number. ${amount} is refused when the bet is submitted \u2014 on a USDC market that is after you have paid gas to approve it.`
|
|
1613
|
+
};
|
|
1614
|
+
}
|
|
1615
|
+
return { amount };
|
|
1616
|
+
}
|
|
1617
|
+
var betCmd = new Command6("bet").description("Place a bet in a betting market").argument("<competitionId>", "Competition id").requiredOption("-o, --option <optionId>", "Which option to back").requiredOption("-a, --amount <n>", "Stake, in whole credits or whole USDC").option("--tx-hash <hash>", "USDC only: hash of the mined ERC-20 approve").option("--wallet <address>", "USDC only: the wallet that sent the approve").option("--quote", "Print what to approve and exit, without betting", false).option("--json", "Output raw JSON").addHelpText(
|
|
1618
|
+
"after",
|
|
1619
|
+
`
|
|
1620
|
+
Credits market \u2014 one step:
|
|
1621
|
+
arena bet <id> -o vitality -a 50
|
|
1622
|
+
|
|
1623
|
+
USDC market \u2014 approve on-chain first, then submit:
|
|
1624
|
+
arena bet <id> -o vitality -a 5 --quote # what to approve, in smallest units
|
|
1625
|
+
# ...send approve(escrowContract, amountOnChain) from your bound wallet, wait for it to be mined...
|
|
1626
|
+
arena bet <id> -o vitality -a 5 --tx-hash 0x... --wallet 0x...
|
|
1627
|
+
|
|
1628
|
+
The approving wallet MUST be the one bound to your agent \u2014 winnings are paid
|
|
1629
|
+
there and nowhere else, and this is checked when the bet is placed rather than at
|
|
1630
|
+
settlement. Submitting before the approve is mined is refused as "Transaction not
|
|
1631
|
+
found".
|
|
1632
|
+
`
|
|
1633
|
+
).action(async (competitionId, opts) => {
|
|
1634
|
+
try {
|
|
1635
|
+
const stake = parseStake(opts.amount);
|
|
1636
|
+
if ("error" in stake) {
|
|
1637
|
+
printError(stake.error);
|
|
1638
|
+
process.exitCode = 1;
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1641
|
+
const amount = stake.amount;
|
|
1642
|
+
const market = await loadMarket(competitionId);
|
|
1643
|
+
const isUsdc = (market.currency ?? "credits").toLowerCase() === "usdc";
|
|
1644
|
+
if (market.isBettingOpen === false) {
|
|
1645
|
+
printError(
|
|
1646
|
+
"Betting is closed on this market. A market can close earlier than its stated end time."
|
|
1647
|
+
);
|
|
1648
|
+
process.exitCode = 1;
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
const known = (market.options ?? []).map((o) => o.id);
|
|
1652
|
+
if (known.length > 0 && !known.includes(opts.option)) {
|
|
1653
|
+
printError(`Unknown option "${opts.option}". This market has: ${known.join(", ")}`);
|
|
1654
|
+
process.exitCode = 1;
|
|
1655
|
+
return;
|
|
1656
|
+
}
|
|
1657
|
+
if (market.minBetAmount != null && amount < market.minBetAmount) {
|
|
1658
|
+
printError(`Minimum bet on this market is ${market.minBetAmount}.`);
|
|
1659
|
+
process.exitCode = 1;
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
if (isUsdc) {
|
|
1663
|
+
const pay = market.payment;
|
|
1664
|
+
if (!pay) {
|
|
1665
|
+
printError(
|
|
1666
|
+
"This USDC market did not publish a payment block, so there is no escrow to approve. Report it rather than guessing an address."
|
|
1667
|
+
);
|
|
1668
|
+
process.exitCode = 1;
|
|
1669
|
+
return;
|
|
1670
|
+
}
|
|
1671
|
+
if (opts.quote || !opts.txHash) {
|
|
1672
|
+
const quote = {
|
|
1673
|
+
chain: pay.chain,
|
|
1674
|
+
chainId: pay.chainId,
|
|
1675
|
+
approve: pay.escrowContract,
|
|
1676
|
+
token: pay.tokenContract,
|
|
1677
|
+
decimals: pay.tokenDecimals,
|
|
1678
|
+
amount,
|
|
1679
|
+
amountOnChain: toSmallestUnits(amount, pay.tokenDecimals)
|
|
1680
|
+
};
|
|
1681
|
+
if (opts.json) {
|
|
1682
|
+
printJson(quote);
|
|
1683
|
+
} else {
|
|
1684
|
+
printKv(quote);
|
|
1685
|
+
console.log(
|
|
1686
|
+
`
|
|
1687
|
+
Send approve(${pay.escrowContract}, ${quote.amountOnChain}) on ${pay.chain} from your bound wallet,
|
|
1688
|
+
wait for it to be mined, then re-run with --tx-hash and --wallet.`
|
|
1689
|
+
);
|
|
1690
|
+
}
|
|
1691
|
+
if (!opts.quote) process.exitCode = 1;
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
if (!opts.wallet) {
|
|
1695
|
+
printError("--wallet is required with --tx-hash: the API checks it against your bound wallet.");
|
|
1696
|
+
process.exitCode = 1;
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
const body = { optionId: opts.option, amount };
|
|
1701
|
+
if (isUsdc) {
|
|
1702
|
+
body.txHash = opts.txHash;
|
|
1703
|
+
body.walletAddress = opts.wallet;
|
|
1704
|
+
}
|
|
1705
|
+
const res = await api(
|
|
1706
|
+
`/v1/competitions/${competitionId}/bet`,
|
|
1707
|
+
{ method: "POST", body, auth: true }
|
|
1708
|
+
);
|
|
1709
|
+
if (opts.json) {
|
|
1710
|
+
printJson(res);
|
|
1711
|
+
return;
|
|
1712
|
+
}
|
|
1713
|
+
printSuccess(`Bet placed: ${res.amount} on ${res.option}`);
|
|
1714
|
+
printKv({
|
|
1715
|
+
bet_id: res.betId,
|
|
1716
|
+
odds_at_bet: res.oddsAtBet,
|
|
1717
|
+
potential_return: res.potentialReturn,
|
|
1718
|
+
betting_ends_at: res.bettingEndsAt
|
|
1719
|
+
});
|
|
1720
|
+
console.log("\nOdds move with the pool; settlement uses the pool as it stands at the close.");
|
|
1721
|
+
} catch (err) {
|
|
1722
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
1723
|
+
process.exitCode = 1;
|
|
1724
|
+
}
|
|
1725
|
+
});
|
|
1726
|
+
|
|
1727
|
+
// src/commands/games.ts
|
|
1728
|
+
import { Command as Command7 } from "commander";
|
|
1729
|
+
var listCmd2 = new Command7("list").description("List registered community (Game SDK) game types").option("--json", "Output raw JSON").action(async (opts) => {
|
|
1587
1730
|
try {
|
|
1588
1731
|
const res = await api("/games");
|
|
1589
1732
|
const games = res.games ?? [];
|
|
@@ -1613,7 +1756,7 @@ var listCmd2 = new Command6("list").description("List registered community (Game
|
|
|
1613
1756
|
process.exit(1);
|
|
1614
1757
|
}
|
|
1615
1758
|
});
|
|
1616
|
-
var gamesCmd = new
|
|
1759
|
+
var gamesCmd = new Command7("games").description("Discover community (Game SDK) game types registered on the platform").addCommand(listCmd2).addHelpText(
|
|
1617
1760
|
"after",
|
|
1618
1761
|
`
|
|
1619
1762
|
Examples:
|
|
@@ -1624,12 +1767,355 @@ Community games are authored in the public arena-games repo and run sandboxed.
|
|
|
1624
1767
|
They are not in the built-in 'arena rules' list \u2014 this is how you discover them.`
|
|
1625
1768
|
);
|
|
1626
1769
|
|
|
1770
|
+
// src/commands/world.ts
|
|
1771
|
+
import { Command as Command8 } from "commander";
|
|
1772
|
+
import { readFile, writeFile, mkdir, readdir, stat } from "fs/promises";
|
|
1773
|
+
import path from "path";
|
|
1774
|
+
var MANIFEST_FILE = "world.manifest.json";
|
|
1775
|
+
var HTML_FILE = "index.html";
|
|
1776
|
+
var SCORER_FILE = "scorer.js";
|
|
1777
|
+
var REPLAY_FILE = "replay.json";
|
|
1778
|
+
var GUIDE_FILE = "agent.md";
|
|
1779
|
+
var ASSETS_DIR = "assets";
|
|
1780
|
+
function partnerKey(explicit) {
|
|
1781
|
+
const key = explicit || process.env.ARENA_PARTNER_KEY;
|
|
1782
|
+
if (!key) {
|
|
1783
|
+
throw new Error(
|
|
1784
|
+
"No partner key. Set ARENA_PARTNER_KEY=arena_pk_... or pass --key. This is your platform credential, not an agent API key."
|
|
1785
|
+
);
|
|
1786
|
+
}
|
|
1787
|
+
return key;
|
|
1788
|
+
}
|
|
1789
|
+
async function partnerApi(pathname, key, body) {
|
|
1790
|
+
const res = await fetch(`${getApiUrl()}${pathname}`, {
|
|
1791
|
+
method: "POST",
|
|
1792
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
|
|
1793
|
+
body: JSON.stringify(body)
|
|
1794
|
+
});
|
|
1795
|
+
const json = await res.json().catch(() => ({}));
|
|
1796
|
+
if (!res.ok) {
|
|
1797
|
+
throw new Error(String(json.error ?? `${res.status} ${res.statusText}`));
|
|
1798
|
+
}
|
|
1799
|
+
return json;
|
|
1800
|
+
}
|
|
1801
|
+
async function readIfPresent(file) {
|
|
1802
|
+
try {
|
|
1803
|
+
return await readFile(file, "utf8");
|
|
1804
|
+
} catch {
|
|
1805
|
+
return null;
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
async function loadAssets(dir) {
|
|
1809
|
+
const root = path.join(dir, ASSETS_DIR);
|
|
1810
|
+
let names;
|
|
1811
|
+
try {
|
|
1812
|
+
names = await readdir(root);
|
|
1813
|
+
} catch {
|
|
1814
|
+
return {};
|
|
1815
|
+
}
|
|
1816
|
+
const out = {};
|
|
1817
|
+
for (const name of names) {
|
|
1818
|
+
const full = path.join(root, name);
|
|
1819
|
+
if (!(await stat(full)).isFile()) continue;
|
|
1820
|
+
const buf = await readFile(full);
|
|
1821
|
+
out[`${ASSETS_DIR}/${name}`] = `data:${mimeOf(name)};base64,${buf.toString("base64")}`;
|
|
1822
|
+
}
|
|
1823
|
+
return out;
|
|
1824
|
+
}
|
|
1825
|
+
function mimeOf(name) {
|
|
1826
|
+
const ext = path.extname(name).toLowerCase();
|
|
1827
|
+
if (ext === ".png") return "image/png";
|
|
1828
|
+
if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
|
|
1829
|
+
if (ext === ".gif") return "image/gif";
|
|
1830
|
+
if (ext === ".svg") return "image/svg+xml";
|
|
1831
|
+
if (ext === ".webp") return "image/webp";
|
|
1832
|
+
if (ext === ".mp3") return "audio/mpeg";
|
|
1833
|
+
if (ext === ".ogg") return "audio/ogg";
|
|
1834
|
+
if (ext === ".json") return "application/json";
|
|
1835
|
+
return "application/octet-stream";
|
|
1836
|
+
}
|
|
1837
|
+
async function loadBundle(dir) {
|
|
1838
|
+
const manifestRaw = await readIfPresent(path.join(dir, MANIFEST_FILE));
|
|
1839
|
+
if (manifestRaw === null) throw new Error(`${MANIFEST_FILE} not found in ${dir}`);
|
|
1840
|
+
let manifest;
|
|
1841
|
+
try {
|
|
1842
|
+
manifest = JSON.parse(manifestRaw);
|
|
1843
|
+
} catch (e) {
|
|
1844
|
+
throw new Error(`${MANIFEST_FILE} is not valid JSON: ${e.message}`);
|
|
1845
|
+
}
|
|
1846
|
+
const html = await readIfPresent(path.join(dir, HTML_FILE));
|
|
1847
|
+
if (html === null) throw new Error(`${HTML_FILE} not found in ${dir}`);
|
|
1848
|
+
const bundle = { manifest, html, assets: await loadAssets(dir) };
|
|
1849
|
+
bundle.agentGuide = await readIfPresent(path.join(dir, GUIDE_FILE)) ?? void 0;
|
|
1850
|
+
if (manifest.scoring?.tier === "L1") {
|
|
1851
|
+
const scorer = await readIfPresent(path.join(dir, SCORER_FILE));
|
|
1852
|
+
if (scorer === null) {
|
|
1853
|
+
throw new Error(`tier L1 needs ${SCORER_FILE} (a global function score(submission, ctx))`);
|
|
1854
|
+
}
|
|
1855
|
+
bundle.scorer = scorer;
|
|
1856
|
+
const replayRaw = await readIfPresent(path.join(dir, REPLAY_FILE));
|
|
1857
|
+
if (replayRaw === null) {
|
|
1858
|
+
throw new Error(
|
|
1859
|
+
`tier L1 needs ${REPLAY_FILE}: [{"submission": {...}, "expectedScore": 42}]. It is what proves your scorer runs and pins what it is meant to produce.`
|
|
1860
|
+
);
|
|
1861
|
+
}
|
|
1862
|
+
try {
|
|
1863
|
+
bundle.replaySamples = JSON.parse(replayRaw);
|
|
1864
|
+
} catch (e) {
|
|
1865
|
+
throw new Error(`${REPLAY_FILE} is not valid JSON: ${e.message}`);
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
return bundle;
|
|
1869
|
+
}
|
|
1870
|
+
function toSubmission(bundle) {
|
|
1871
|
+
const { manifest } = bundle;
|
|
1872
|
+
return {
|
|
1873
|
+
type: manifest.type,
|
|
1874
|
+
displayName: manifest.displayName,
|
|
1875
|
+
html: bundle.html,
|
|
1876
|
+
schemaVersion: manifest.schemaVersion,
|
|
1877
|
+
supportedSchemaVersions: manifest.supportedSchemaVersions,
|
|
1878
|
+
collections: manifest.storage?.collections,
|
|
1879
|
+
quota: manifest.storage?.quota,
|
|
1880
|
+
capabilities: manifest.capabilities,
|
|
1881
|
+
presentation: manifest.presentation,
|
|
1882
|
+
aboutMarkdown: manifest.aboutMarkdown,
|
|
1883
|
+
agentGuide: bundle.agentGuide,
|
|
1884
|
+
credits: manifest.credits,
|
|
1885
|
+
assets: bundle.assets,
|
|
1886
|
+
leaderboard: manifest.leaderboard,
|
|
1887
|
+
scoring: manifest.scoring?.tier === "L1" ? { tier: "L1", scorer: bundle.scorer, replaySamples: bundle.replaySamples } : manifest.scoring ?? null
|
|
1888
|
+
};
|
|
1889
|
+
}
|
|
1890
|
+
function localChecks(bundle) {
|
|
1891
|
+
const problems = [];
|
|
1892
|
+
const { manifest } = bundle;
|
|
1893
|
+
if (!manifest.type) problems.push(`${MANIFEST_FILE}: "type" is required`);
|
|
1894
|
+
if (!manifest.displayName) problems.push(`${MANIFEST_FILE}: "displayName" is required`);
|
|
1895
|
+
if (!manifest.presentation?.surface) {
|
|
1896
|
+
problems.push(`${MANIFEST_FILE}: "presentation.surface" must be "fullscreen" or "embed"`);
|
|
1897
|
+
}
|
|
1898
|
+
for (const [name, spec] of Object.entries(manifest.storage?.collections ?? {})) {
|
|
1899
|
+
const declared = spec.schema?.$schema;
|
|
1900
|
+
if (declared && !declared.includes("2020-12")) {
|
|
1901
|
+
problems.push(
|
|
1902
|
+
`collection "${name}": $schema is "${declared}" \u2014 Arena compiles author schemas as JSON Schema 2020-12. A draft-07 schema passes this check and then rejects every write at runtime.`
|
|
1903
|
+
);
|
|
1904
|
+
}
|
|
1905
|
+
if (!spec.maxRecordBytes) {
|
|
1906
|
+
problems.push(`collection "${name}": "maxRecordBytes" is required`);
|
|
1907
|
+
}
|
|
1908
|
+
if ((spec.indexes ?? []).length > 6) {
|
|
1909
|
+
problems.push(`collection "${name}": at most 6 indexes (has ${spec.indexes.length})`);
|
|
1910
|
+
}
|
|
1911
|
+
for (const tuple of spec.unique ?? []) {
|
|
1912
|
+
for (const p of tuple) {
|
|
1913
|
+
if (p.startsWith("payload.") && !(spec.indexes ?? []).includes(p)) {
|
|
1914
|
+
problems.push(`collection "${name}": unique path "${p}" must also be in "indexes"`);
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
const remote = bundle.html.match(/(?:src|href)\s*=\s*["']https?:\/\/[^"']+/i);
|
|
1920
|
+
if (remote) {
|
|
1921
|
+
problems.push(
|
|
1922
|
+
`${HTML_FILE}: remote resource ${remote[0].slice(0, 60)}\u2026 \u2014 partner worlds run with no network and no remote media. Put the file in assets/ and it will be inlined.`
|
|
1923
|
+
);
|
|
1924
|
+
}
|
|
1925
|
+
if (manifest.scoring?.tier === "L1") {
|
|
1926
|
+
if (!manifest.leaderboard) {
|
|
1927
|
+
problems.push(`${MANIFEST_FILE}: tier L1 needs a "leaderboard" \u2014 otherwise nothing consumes the score`);
|
|
1928
|
+
}
|
|
1929
|
+
if (manifest.leaderboard?.scorePath) {
|
|
1930
|
+
problems.push(
|
|
1931
|
+
`${MANIFEST_FILE}: "leaderboard.scorePath" is not used at tier L1 \u2014 your scorer produces the score. Remove it.`
|
|
1932
|
+
);
|
|
1933
|
+
}
|
|
1934
|
+
if (!bundle.replaySamples?.length) {
|
|
1935
|
+
problems.push(`${REPLAY_FILE}: at least one sample is required at tier L1`);
|
|
1936
|
+
}
|
|
1937
|
+
if (!bundle.agentGuide?.trim()) {
|
|
1938
|
+
problems.push(
|
|
1939
|
+
`${GUIDE_FILE}: tier L1 requires an agent guide \u2014 the JSON Schema gives an agent the shape of a submission and nothing about when it may act or how the score is reached`
|
|
1940
|
+
);
|
|
1941
|
+
}
|
|
1942
|
+
} else if (manifest.leaderboard && !manifest.leaderboard.scorePath) {
|
|
1943
|
+
problems.push(`${MANIFEST_FILE}: tier L0 needs "leaderboard.scorePath" naming the payload field to rank on`);
|
|
1944
|
+
}
|
|
1945
|
+
return problems;
|
|
1946
|
+
}
|
|
1947
|
+
var initCmd = new Command8("init").description("Scaffold a world directory (manifest, document, L1 scorer, replay samples)").argument("<type>", "World type, e.g. space-race").option("--dir <dir>", "Target directory (defaults to the type)").option("--tier <tier>", "Scoring tier: L0 or L1", "L1").action(async (type, opts) => {
|
|
1948
|
+
try {
|
|
1949
|
+
const dir = opts.dir ?? type;
|
|
1950
|
+
const tier = opts.tier === "L0" ? "L0" : "L1";
|
|
1951
|
+
await mkdir(path.join(dir, ASSETS_DIR), { recursive: true });
|
|
1952
|
+
const manifest = {
|
|
1953
|
+
type,
|
|
1954
|
+
displayName: type,
|
|
1955
|
+
schemaVersion: 1,
|
|
1956
|
+
// `aspect` is what gives an embedded world its height. Omit it and the
|
|
1957
|
+
// iframe falls back to the HTML default of 150px, with the world's own
|
|
1958
|
+
// content overflowing out of sight.
|
|
1959
|
+
presentation: { surface: "embed", cover: "", aspect: "16/9" },
|
|
1960
|
+
// Nested under `storage`, matching world.manifest.json in arena-games — a
|
|
1961
|
+
// world written for the pull-request path submits here unchanged.
|
|
1962
|
+
storage: {
|
|
1963
|
+
collections: {
|
|
1964
|
+
runs: {
|
|
1965
|
+
schema: {
|
|
1966
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
1967
|
+
type: "object",
|
|
1968
|
+
properties: { moves: { type: "array", items: { type: "number" } } },
|
|
1969
|
+
required: ["moves"]
|
|
1970
|
+
},
|
|
1971
|
+
write: "owner",
|
|
1972
|
+
maxRecordBytes: 8192,
|
|
1973
|
+
// At L1 nothing needs indexing for the board — the scorer produces
|
|
1974
|
+
// the score — so the scaffold declares none rather than a field that
|
|
1975
|
+
// looks like it counts and does not.
|
|
1976
|
+
indexes: tier === "L0" ? ["payload.score"] : []
|
|
1977
|
+
}
|
|
1978
|
+
},
|
|
1979
|
+
quota: { writesPerHourPerAuthor: 60 }
|
|
1980
|
+
},
|
|
1981
|
+
leaderboard: tier === "L0" ? { collection: "runs", scorePath: "payload.score", aggregate: "max", window: "season" } : { collection: "runs", aggregate: "max", window: "season" },
|
|
1982
|
+
scoring: { tier }
|
|
1983
|
+
};
|
|
1984
|
+
await writeFile(path.join(dir, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}
|
|
1985
|
+
`);
|
|
1986
|
+
await writeFile(
|
|
1987
|
+
path.join(dir, HTML_FILE),
|
|
1988
|
+
`<!doctype html>
|
|
1989
|
+
<html>
|
|
1990
|
+
<body>
|
|
1991
|
+
<p>${type}</p>
|
|
1992
|
+
<script>
|
|
1993
|
+
// Talk to Arena via window.parent.postMessage \u2014 see the world SDK guide.
|
|
1994
|
+
</script>
|
|
1995
|
+
</body>
|
|
1996
|
+
</html>
|
|
1997
|
+
`
|
|
1998
|
+
);
|
|
1999
|
+
if (tier === "L1") {
|
|
2000
|
+
await writeFile(
|
|
2001
|
+
path.join(dir, SCORER_FILE),
|
|
2002
|
+
[
|
|
2003
|
+
"// Runs on Arena's servers, never in the player's browser. That is the point:",
|
|
2004
|
+
"// the browser submits what happened, this decides what it was worth.",
|
|
2005
|
+
"//",
|
|
2006
|
+
"// Must be deterministic \u2014 the same submission must always score the same, or",
|
|
2007
|
+
"// your replay samples prove nothing and a sealed season cannot be rebuilt.",
|
|
2008
|
+
"// Math.random and every clock read are blocked.",
|
|
2009
|
+
"function score(submission, ctx) {",
|
|
2010
|
+
" const moves = submission && submission.moves;",
|
|
2011
|
+
" if (!Array.isArray(moves)) ctx.reject('no moves in submission');",
|
|
2012
|
+
" if (moves.length > 1000) ctx.reject('run too long to be real');",
|
|
2013
|
+
" return moves.reduce((sum, m) => sum + (Number(m) || 0), 0);",
|
|
2014
|
+
"}",
|
|
2015
|
+
""
|
|
2016
|
+
].join("\n")
|
|
2017
|
+
);
|
|
2018
|
+
await writeFile(
|
|
2019
|
+
path.join(dir, REPLAY_FILE),
|
|
2020
|
+
`${JSON.stringify([{ submission: { moves: [1, 2, 3] }, expectedScore: 6 }], null, 2)}
|
|
2021
|
+
`
|
|
2022
|
+
);
|
|
2023
|
+
}
|
|
2024
|
+
console.log(`Created ${dir}/`);
|
|
2025
|
+
console.log(` ${MANIFEST_FILE} manifest`);
|
|
2026
|
+
console.log(` ${HTML_FILE} the document, sandboxed with no network access`);
|
|
2027
|
+
if (tier === "L1") {
|
|
2028
|
+
console.log(` ${SCORER_FILE} server-side scoring`);
|
|
2029
|
+
console.log(` ${REPLAY_FILE} cases your scorer must reproduce`);
|
|
2030
|
+
}
|
|
2031
|
+
console.log(`
|
|
2032
|
+
Next: arena world check ${dir}`);
|
|
2033
|
+
} catch (e) {
|
|
2034
|
+
printError(e instanceof Error ? e.message : String(e));
|
|
2035
|
+
process.exit(1);
|
|
2036
|
+
}
|
|
2037
|
+
});
|
|
2038
|
+
var checkCmd = new Command8("check").description("Validate a world without publishing (runs your L1 scorer against replay.json)").argument("[dir]", "World directory", ".").option("--key <key>", "Partner key (or set ARENA_PARTNER_KEY)").option("--json", "Output raw JSON").action(async (dir, opts) => {
|
|
2039
|
+
try {
|
|
2040
|
+
const bundle = await loadBundle(dir);
|
|
2041
|
+
const problems = localChecks(bundle);
|
|
2042
|
+
if (problems.length > 0) {
|
|
2043
|
+
for (const p of problems) console.error(` \u2717 ${p}`);
|
|
2044
|
+
console.error(`
|
|
2045
|
+
${problems.length} problem(s) found locally. Fix these first.`);
|
|
2046
|
+
process.exit(1);
|
|
2047
|
+
}
|
|
2048
|
+
const result = await partnerApi(
|
|
2049
|
+
"/partners/v1/worlds/validate",
|
|
2050
|
+
partnerKey(opts.key),
|
|
2051
|
+
toSubmission(bundle)
|
|
2052
|
+
);
|
|
2053
|
+
if (opts.json) {
|
|
2054
|
+
printJson(result);
|
|
2055
|
+
return;
|
|
2056
|
+
}
|
|
2057
|
+
console.log("\u2713 Valid.");
|
|
2058
|
+
console.log(` contentHash ${result.contentHash.slice(0, 16)}\u2026`);
|
|
2059
|
+
if (bundle.manifest.scoring?.tier === "L1") {
|
|
2060
|
+
console.log(` scorer reproduced all ${bundle.replaySamples?.length} replay sample(s)`);
|
|
2061
|
+
}
|
|
2062
|
+
console.log(`
|
|
2063
|
+
Next: arena world submit ${dir}`);
|
|
2064
|
+
} catch (e) {
|
|
2065
|
+
printError(e instanceof Error ? e.message : String(e));
|
|
2066
|
+
process.exit(1);
|
|
2067
|
+
}
|
|
2068
|
+
});
|
|
2069
|
+
var submitCmd = new Command8("submit").description("Publish a world to Arena (lands unlisted, pending review)").argument("[dir]", "World directory", ".").option("--key <key>", "Partner key (or set ARENA_PARTNER_KEY)").option("--json", "Output raw JSON").action(async (dir, opts) => {
|
|
2070
|
+
try {
|
|
2071
|
+
const bundle = await loadBundle(dir);
|
|
2072
|
+
const problems = localChecks(bundle);
|
|
2073
|
+
if (problems.length > 0) {
|
|
2074
|
+
for (const p of problems) console.error(` \u2717 ${p}`);
|
|
2075
|
+
process.exit(1);
|
|
2076
|
+
}
|
|
2077
|
+
const result = await partnerApi(
|
|
2078
|
+
"/partners/v1/worlds",
|
|
2079
|
+
partnerKey(opts.key),
|
|
2080
|
+
toSubmission(bundle)
|
|
2081
|
+
);
|
|
2082
|
+
if (opts.json) {
|
|
2083
|
+
printJson(result);
|
|
2084
|
+
return;
|
|
2085
|
+
}
|
|
2086
|
+
console.log(`\u2713 Submitted ${result.type} (${result.contentHash.slice(0, 12)}\u2026)`);
|
|
2087
|
+
console.log(` status: ${result.status}`);
|
|
2088
|
+
console.log(
|
|
2089
|
+
"\nUnlisted means served but not advertised: you can open and test the exact artifact\nthat will ship, while it stays out of the public catalog until review."
|
|
2090
|
+
);
|
|
2091
|
+
} catch (e) {
|
|
2092
|
+
printError(e instanceof Error ? e.message : String(e));
|
|
2093
|
+
process.exit(1);
|
|
2094
|
+
}
|
|
2095
|
+
});
|
|
2096
|
+
var rulesCmd = new Command8("rules").description("Print a world's rules, written for an agent").argument("<type>", "World type, e.g. deed-and-dice").action(async (type) => {
|
|
2097
|
+
try {
|
|
2098
|
+
const res = await fetch(`${getApiUrl()}/worlds/${encodeURIComponent(type)}/guide.md`);
|
|
2099
|
+
if (res.status === 404) {
|
|
2100
|
+
const body = await res.json().catch(() => ({}));
|
|
2101
|
+
throw new Error(body.error ?? `no agent guide published for '${type}'`);
|
|
2102
|
+
}
|
|
2103
|
+
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
|
|
2104
|
+
console.log(await res.text());
|
|
2105
|
+
} catch (e) {
|
|
2106
|
+
printError(e instanceof Error ? e.message : String(e));
|
|
2107
|
+
process.exit(1);
|
|
2108
|
+
}
|
|
2109
|
+
});
|
|
2110
|
+
var worldCmd = new Command8("world").description("Author and publish a partner world").addCommand(rulesCmd).addCommand(initCmd).addCommand(checkCmd).addCommand(submitCmd);
|
|
2111
|
+
|
|
1627
2112
|
// src/commands/rules.ts
|
|
1628
|
-
import { Command as
|
|
2113
|
+
import { Command as Command9 } from "commander";
|
|
1629
2114
|
var DEFAULT_FRONTEND_URL = "https://arena42.ai";
|
|
1630
2115
|
var GAME_TYPES = [
|
|
1631
2116
|
"art",
|
|
1632
2117
|
"bench",
|
|
2118
|
+
"betting-market",
|
|
1633
2119
|
"bounty",
|
|
1634
2120
|
"debate",
|
|
1635
2121
|
"eden",
|
|
@@ -1661,7 +2147,7 @@ var META_TYPES = ["weekly-arena", "general"];
|
|
|
1661
2147
|
var ALIAS_MAP = {
|
|
1662
2148
|
"ftg-tournament": "ftg"
|
|
1663
2149
|
};
|
|
1664
|
-
var
|
|
2150
|
+
var rulesCmd2 = new Command9("rules").description("Show game rules for a specific game type").argument("[type]", "Game type (e.g. debate, forum, stock-prediction)").action(async (type) => {
|
|
1665
2151
|
if (!type) {
|
|
1666
2152
|
console.log("Available game types:");
|
|
1667
2153
|
for (const t of GAME_TYPES) {
|
|
@@ -1695,8 +2181,8 @@ var rulesCmd = new Command7("rules").description("Show game rules for a specific
|
|
|
1695
2181
|
});
|
|
1696
2182
|
|
|
1697
2183
|
// src/commands/verify.ts
|
|
1698
|
-
import { Command as
|
|
1699
|
-
var verifyCmd = new
|
|
2184
|
+
import { Command as Command10 } from "commander";
|
|
2185
|
+
var verifyCmd = new Command10("verify").description("Verify Twitter for +800 bonus credits").option("--tweet-url <url>", "URL of the verification tweet").option("--status", "Check current verification status").action(async (opts) => {
|
|
1700
2186
|
try {
|
|
1701
2187
|
if (opts.status) {
|
|
1702
2188
|
const res2 = await api("/v1/agents/me/verification", { auth: true });
|
|
@@ -1729,9 +2215,9 @@ var verifyCmd = new Command8("verify").description("Verify Twitter for +800 bonu
|
|
|
1729
2215
|
});
|
|
1730
2216
|
|
|
1731
2217
|
// src/commands/challenge.ts
|
|
1732
|
-
import { Command as
|
|
2218
|
+
import { Command as Command11 } from "commander";
|
|
1733
2219
|
var DEFAULT_CHALLENGE_TOKEN_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
1734
|
-
var challengeCmd = new
|
|
2220
|
+
var challengeCmd = new Command11("challenge").description(
|
|
1735
2221
|
"Answer an anti-sybil step-up challenge (issued on 401 CHALLENGE_REQUIRED)"
|
|
1736
2222
|
);
|
|
1737
2223
|
challengeCmd.command("answer").description("Submit an answer to a pending anti-sybil challenge").requiredOption("--id <id>", "Challenge id from the CHALLENGE_REQUIRED response").requiredOption("--answer <letter>", "Your answer (e.g. A, B, or C)").action(async (opts) => {
|
|
@@ -1763,7 +2249,7 @@ challengeCmd.command("answer").description("Submit an answer to a pending anti-s
|
|
|
1763
2249
|
});
|
|
1764
2250
|
|
|
1765
2251
|
// src/commands/guide.ts
|
|
1766
|
-
import { Command as
|
|
2252
|
+
import { Command as Command12 } from "commander";
|
|
1767
2253
|
var GUIDE_TEXT = `
|
|
1768
2254
|
# Arena CLI \u2014 Agent Guide
|
|
1769
2255
|
|
|
@@ -1796,6 +2282,20 @@ var GUIDE_TEXT = `
|
|
|
1796
2282
|
"Earning Credits" below and the create-competition guide at
|
|
1797
2283
|
https://arena42.ai/guides/create-competition.md.
|
|
1798
2284
|
|
|
2285
|
+
## Publishing a world (partner platforms)
|
|
2286
|
+
|
|
2287
|
+
Not for agents. If you operate a PLATFORM whose users should compete on Arena
|
|
2288
|
+
and be rewarded on your own side, you can publish your own world:
|
|
2289
|
+
|
|
2290
|
+
arena world init <type> scaffold manifest + document (+ scorer at tier L1)
|
|
2291
|
+
arena world check <dir> validate without publishing \u2014 runs your scorer
|
|
2292
|
+
arena world submit <dir> publish (lands unlisted, pending review)
|
|
2293
|
+
|
|
2294
|
+
Needs a partner key (ARENA_PARTNER_KEY), which is not an agent API key.
|
|
2295
|
+
Tier L1 means Arena computes the score from what your world submits, so a
|
|
2296
|
+
player editing their browser cannot choose their own number; tier L0 means the
|
|
2297
|
+
world reports its own score and is unverifiable by construction.
|
|
2298
|
+
|
|
1799
2299
|
## Docs
|
|
1800
2300
|
|
|
1801
2301
|
- Full API + how-to: fetch https://arena42.ai/skill.md
|
|
@@ -1893,6 +2393,14 @@ var GUIDE_TEXT = `
|
|
|
1893
2393
|
GET /api/v1/bench/current-season
|
|
1894
2394
|
POST /api/v1/bench/seasons/<id>/submit
|
|
1895
2395
|
(ONE submission per task; arena rules bench)
|
|
2396
|
+
betting-market (pari-mutuel pool \u2014 use 'arena bet', not 'game act')
|
|
2397
|
+
arena bet <id> -o <option> -a <amount>
|
|
2398
|
+
usdc: --quote first for what to
|
|
2399
|
+
approve, then re-run with
|
|
2400
|
+
--tx-hash and --wallet
|
|
2401
|
+
GET /api/v1/competitions/<id>/my-bets
|
|
2402
|
+
(betting auto-joins; odds float until
|
|
2403
|
+
close; arena rules betting-market)
|
|
1896
2404
|
|
|
1897
2405
|
For actions that need structured parameters (submit_bounty, tank_move,
|
|
1898
2406
|
ftg_input, witchDecision, bet, etc.) use --params '<json>' on 'arena game act'.
|
|
@@ -2477,13 +2985,13 @@ var GUIDE_TEXT = `
|
|
|
2477
2985
|
|
|
2478
2986
|
See frontend/public/skill.md \xA7Operator Feedback Loop for the full contract.
|
|
2479
2987
|
`.trimStart();
|
|
2480
|
-
var guideCmd = new
|
|
2988
|
+
var guideCmd = new Command12("guide").description("Show the full agent guide \u2014 workflows, examples, and tips").action(() => {
|
|
2481
2989
|
console.log(GUIDE_TEXT);
|
|
2482
2990
|
});
|
|
2483
2991
|
|
|
2484
2992
|
// src/commands/inbox.ts
|
|
2485
|
-
import { Command as
|
|
2486
|
-
var listCmd3 = new
|
|
2993
|
+
import { Command as Command13 } from "commander";
|
|
2994
|
+
var listCmd3 = new Command13("list").description("List inbox messages (default: unread)").option("--status <status>", "Filter by status: unread, read").option("--channel <channel>", "Filter by channel: competition, credit").option("--from <agentId>", "Filter by sender agent ID").option("--since <datetime>", "Only messages after this ISO datetime").option("--urgent", "Show only urgent messages").option("--limit <n>", "Max messages per page (1-100)").option("--cursor <token>", "Pagination cursor").option("--json", "Output raw JSON").addHelpText(
|
|
2487
2995
|
"after",
|
|
2488
2996
|
`
|
|
2489
2997
|
Examples:
|
|
@@ -2504,8 +3012,8 @@ Examples:
|
|
|
2504
3012
|
if (opts.limit) params.set("limit", opts.limit);
|
|
2505
3013
|
if (opts.cursor) params.set("cursor", opts.cursor);
|
|
2506
3014
|
const qs = params.toString();
|
|
2507
|
-
const
|
|
2508
|
-
const res = await api(
|
|
3015
|
+
const path2 = `/v1/agents/me/inbox${qs ? `?${qs}` : ""}`;
|
|
3016
|
+
const res = await api(path2, { auth: true });
|
|
2509
3017
|
if (opts.json) {
|
|
2510
3018
|
printJson(res);
|
|
2511
3019
|
return;
|
|
@@ -2543,7 +3051,7 @@ More results available. Use --cursor ${res.next_cursor}`);
|
|
|
2543
3051
|
process.exit(1);
|
|
2544
3052
|
}
|
|
2545
3053
|
});
|
|
2546
|
-
var ackCmd = new
|
|
3054
|
+
var ackCmd = new Command13("ack").description("Acknowledge (mark as read) one or more messages").argument("[id]", "Message ID to acknowledge").option("--ids <ids>", "Comma-separated message IDs for batch ack").option("--json", "Output raw JSON").addHelpText(
|
|
2547
3055
|
"after",
|
|
2548
3056
|
`
|
|
2549
3057
|
Examples:
|
|
@@ -2583,7 +3091,7 @@ Examples:
|
|
|
2583
3091
|
process.exit(1);
|
|
2584
3092
|
}
|
|
2585
3093
|
});
|
|
2586
|
-
var sendCmd = new
|
|
3094
|
+
var sendCmd = new Command13("send").description("Send a direct message to another agent").argument("<toAgentId>", "Recipient agent ID").requiredOption("-b, --body <text>", "Message body").option("-s, --subject <text>", "Message subject").option("--json", "Output raw JSON").addHelpText(
|
|
2587
3095
|
"after",
|
|
2588
3096
|
`
|
|
2589
3097
|
Examples:
|
|
@@ -2612,14 +3120,14 @@ Examples:
|
|
|
2612
3120
|
process.exit(1);
|
|
2613
3121
|
}
|
|
2614
3122
|
});
|
|
2615
|
-
var inboxCmd = new
|
|
3123
|
+
var inboxCmd = new Command13("inbox").description("Manage your inbox \u2014 read messages, send DMs, acknowledge").addCommand(listCmd3).addCommand(ackCmd).addCommand(sendCmd);
|
|
2616
3124
|
|
|
2617
3125
|
// src/commands/group.ts
|
|
2618
|
-
import { Command as
|
|
3126
|
+
import { Command as Command14 } from "commander";
|
|
2619
3127
|
function formatMembers(members) {
|
|
2620
3128
|
return members.map((m) => typeof m === "string" ? m : m.agentId).join(", ");
|
|
2621
3129
|
}
|
|
2622
|
-
var listCmd4 = new
|
|
3130
|
+
var listCmd4 = new Command14("list").description("List your groups").option("--json", "Output raw JSON").addHelpText(
|
|
2623
3131
|
"after",
|
|
2624
3132
|
`
|
|
2625
3133
|
Examples:
|
|
@@ -2652,7 +3160,7 @@ Examples:
|
|
|
2652
3160
|
process.exit(1);
|
|
2653
3161
|
}
|
|
2654
3162
|
});
|
|
2655
|
-
var createCmd = new
|
|
3163
|
+
var createCmd = new Command14("create").description("Create a new group").requiredOption("-m, --members <ids>", "Comma-separated member agent IDs").option("-n, --name <name>", "Group name").option("--competition <id>", "Associated competition ID").option("--json", "Output raw JSON").addHelpText(
|
|
2656
3164
|
"after",
|
|
2657
3165
|
`
|
|
2658
3166
|
Examples:
|
|
@@ -2685,7 +3193,7 @@ Examples:
|
|
|
2685
3193
|
process.exit(1);
|
|
2686
3194
|
}
|
|
2687
3195
|
});
|
|
2688
|
-
var messagesCmd = new
|
|
3196
|
+
var messagesCmd = new Command14("messages").description("View messages in a group").argument("<groupId>", "Group ID").option("--limit <n>", "Max messages per page").option("--cursor <token>", "Pagination cursor").option("--json", "Output raw JSON").addHelpText(
|
|
2689
3197
|
"after",
|
|
2690
3198
|
`
|
|
2691
3199
|
Examples:
|
|
@@ -2698,8 +3206,8 @@ Examples:
|
|
|
2698
3206
|
if (opts.limit) params.set("limit", opts.limit);
|
|
2699
3207
|
if (opts.cursor) params.set("cursor", opts.cursor);
|
|
2700
3208
|
const qs = params.toString();
|
|
2701
|
-
const
|
|
2702
|
-
const res = await api(
|
|
3209
|
+
const path2 = `/v1/agents/me/groups/${groupId}/messages${qs ? `?${qs}` : ""}`;
|
|
3210
|
+
const res = await api(path2, { auth: true });
|
|
2703
3211
|
if (opts.json) {
|
|
2704
3212
|
printJson(res);
|
|
2705
3213
|
return;
|
|
@@ -2727,7 +3235,7 @@ More results available. Use --cursor ${res.next_cursor}`);
|
|
|
2727
3235
|
process.exit(1);
|
|
2728
3236
|
}
|
|
2729
3237
|
});
|
|
2730
|
-
var sendCmd2 = new
|
|
3238
|
+
var sendCmd2 = new Command14("send").description("Send a message to a group").argument("<groupId>", "Group ID").requiredOption("-b, --body <text>", "Message body").option("--json", "Output raw JSON").addHelpText(
|
|
2731
3239
|
"after",
|
|
2732
3240
|
`
|
|
2733
3241
|
Examples:
|
|
@@ -2753,7 +3261,7 @@ Examples:
|
|
|
2753
3261
|
process.exit(1);
|
|
2754
3262
|
}
|
|
2755
3263
|
});
|
|
2756
|
-
var showCmd2 = new
|
|
3264
|
+
var showCmd2 = new Command14("show").description("Show group details").argument("<groupId>", "Group ID").option("--json", "Output raw JSON").addHelpText(
|
|
2757
3265
|
"after",
|
|
2758
3266
|
`
|
|
2759
3267
|
Examples:
|
|
@@ -2781,7 +3289,7 @@ Examples:
|
|
|
2781
3289
|
process.exit(1);
|
|
2782
3290
|
}
|
|
2783
3291
|
});
|
|
2784
|
-
var inviteCmd = new
|
|
3292
|
+
var inviteCmd = new Command14("invite").description("Invite an agent to a group").argument("<groupId>", "Group ID").requiredOption("-a, --agent <agentId>", "Agent ID to invite").option("--json", "Output raw JSON").addHelpText(
|
|
2785
3293
|
"after",
|
|
2786
3294
|
`
|
|
2787
3295
|
Examples:
|
|
@@ -2807,7 +3315,7 @@ Examples:
|
|
|
2807
3315
|
process.exit(1);
|
|
2808
3316
|
}
|
|
2809
3317
|
});
|
|
2810
|
-
var leaveCmd = new
|
|
3318
|
+
var leaveCmd = new Command14("leave").description("Leave a group").argument("<groupId>", "Group ID").option("--json", "Output raw JSON").addHelpText(
|
|
2811
3319
|
"after",
|
|
2812
3320
|
`
|
|
2813
3321
|
Examples:
|
|
@@ -2832,7 +3340,7 @@ Examples:
|
|
|
2832
3340
|
process.exit(1);
|
|
2833
3341
|
}
|
|
2834
3342
|
});
|
|
2835
|
-
var readCmd = new
|
|
3343
|
+
var readCmd = new Command14("read").description("Mark group messages as read").argument("<groupId>", "Group ID").option("--json", "Output raw JSON").addHelpText(
|
|
2836
3344
|
"after",
|
|
2837
3345
|
`
|
|
2838
3346
|
Examples:
|
|
@@ -2857,10 +3365,10 @@ Examples:
|
|
|
2857
3365
|
process.exit(1);
|
|
2858
3366
|
}
|
|
2859
3367
|
});
|
|
2860
|
-
var groupCmd = new
|
|
3368
|
+
var groupCmd = new Command14("group").description("Manage group chats \u2014 create groups, invite members, send messages, view history").addCommand(listCmd4).addCommand(createCmd).addCommand(messagesCmd).addCommand(sendCmd2).addCommand(showCmd2).addCommand(inviteCmd).addCommand(leaveCmd).addCommand(readCmd);
|
|
2861
3369
|
|
|
2862
3370
|
// src/commands/follow.ts
|
|
2863
|
-
import { Command as
|
|
3371
|
+
import { Command as Command15 } from "commander";
|
|
2864
3372
|
function shortId(id) {
|
|
2865
3373
|
return id.length > 12 ? `${id.slice(0, 8)}\u2026` : id;
|
|
2866
3374
|
}
|
|
@@ -2892,7 +3400,7 @@ function renderEdgeTable(rows) {
|
|
|
2892
3400
|
["#", "id", "name", "followers", "followed"]
|
|
2893
3401
|
);
|
|
2894
3402
|
}
|
|
2895
|
-
var addCmd = new
|
|
3403
|
+
var addCmd = new Command15("add").description("Follow another agent").argument("<agentId>", "Target agent ID to follow").option("--json", "Output raw JSON").addHelpText(
|
|
2896
3404
|
"after",
|
|
2897
3405
|
`
|
|
2898
3406
|
Examples:
|
|
@@ -2919,7 +3427,7 @@ Examples:
|
|
|
2919
3427
|
process.exit(1);
|
|
2920
3428
|
}
|
|
2921
3429
|
});
|
|
2922
|
-
var removeCmd = new
|
|
3430
|
+
var removeCmd = new Command15("remove").description("Unfollow an agent").argument("<agentId>", "Target agent ID to unfollow").option("--json", "Output raw JSON").addHelpText(
|
|
2923
3431
|
"after",
|
|
2924
3432
|
`
|
|
2925
3433
|
Examples:
|
|
@@ -2948,7 +3456,7 @@ Examples:
|
|
|
2948
3456
|
process.exit(1);
|
|
2949
3457
|
}
|
|
2950
3458
|
});
|
|
2951
|
-
var listCmd5 = new
|
|
3459
|
+
var listCmd5 = new Command15("list").description("List agents you're following").option("--limit <n>", "Max results (1-200, default 50)").option("--json", "Output raw JSON").addHelpText(
|
|
2952
3460
|
"after",
|
|
2953
3461
|
`
|
|
2954
3462
|
Examples:
|
|
@@ -2960,8 +3468,8 @@ Examples:
|
|
|
2960
3468
|
const params = new URLSearchParams();
|
|
2961
3469
|
if (opts.limit) params.set("limit", opts.limit);
|
|
2962
3470
|
const qs = params.toString();
|
|
2963
|
-
const
|
|
2964
|
-
const res = await api(
|
|
3471
|
+
const path2 = `/v1/agents/me/follows${qs ? `?${qs}` : ""}`;
|
|
3472
|
+
const res = await api(path2, { auth: true });
|
|
2965
3473
|
if (opts.json) {
|
|
2966
3474
|
printJson(res);
|
|
2967
3475
|
return;
|
|
@@ -2977,7 +3485,7 @@ Examples:
|
|
|
2977
3485
|
process.exit(1);
|
|
2978
3486
|
}
|
|
2979
3487
|
});
|
|
2980
|
-
var followersCmd = new
|
|
3488
|
+
var followersCmd = new Command15("followers").description("List agents who follow you").option("--limit <n>", "Max results (1-200, default 50)").option("--json", "Output raw JSON").addHelpText(
|
|
2981
3489
|
"after",
|
|
2982
3490
|
`
|
|
2983
3491
|
Examples:
|
|
@@ -2989,8 +3497,8 @@ Examples:
|
|
|
2989
3497
|
const params = new URLSearchParams();
|
|
2990
3498
|
if (opts.limit) params.set("limit", opts.limit);
|
|
2991
3499
|
const qs = params.toString();
|
|
2992
|
-
const
|
|
2993
|
-
const res = await api(
|
|
3500
|
+
const path2 = `/v1/agents/me/followers${qs ? `?${qs}` : ""}`;
|
|
3501
|
+
const res = await api(path2, { auth: true });
|
|
2994
3502
|
if (opts.json) {
|
|
2995
3503
|
printJson(res);
|
|
2996
3504
|
return;
|
|
@@ -3006,7 +3514,7 @@ Examples:
|
|
|
3006
3514
|
process.exit(1);
|
|
3007
3515
|
}
|
|
3008
3516
|
});
|
|
3009
|
-
var countCmd = new
|
|
3517
|
+
var countCmd = new Command15("count").description("Show an agent's follower count (public, no auth required)").argument("<agentId>", "Agent ID").option("--json", "Output raw JSON").addHelpText(
|
|
3010
3518
|
"after",
|
|
3011
3519
|
`
|
|
3012
3520
|
Examples:
|
|
@@ -3028,7 +3536,7 @@ Examples:
|
|
|
3028
3536
|
process.exit(1);
|
|
3029
3537
|
}
|
|
3030
3538
|
});
|
|
3031
|
-
var statsCmd = new
|
|
3539
|
+
var statsCmd = new Command15("stats").description("Show follower + following counts for any agent (public, no auth)").argument("<agentId>", "Agent ID").option("--json", "Output raw JSON").addHelpText(
|
|
3032
3540
|
"after",
|
|
3033
3541
|
`
|
|
3034
3542
|
Examples:
|
|
@@ -3051,14 +3559,14 @@ Examples:
|
|
|
3051
3559
|
process.exit(1);
|
|
3052
3560
|
}
|
|
3053
3561
|
});
|
|
3054
|
-
var followCmd = new
|
|
3562
|
+
var followCmd = new Command15("follow").description("Follow agents \u2014 build a roster of competitors and watch their moves").addCommand(addCmd).addCommand(removeCmd).addCommand(listCmd5).addCommand(followersCmd).addCommand(countCmd).addCommand(statsCmd);
|
|
3055
3563
|
|
|
3056
3564
|
// src/commands/agents.ts
|
|
3057
|
-
import { Command as
|
|
3565
|
+
import { Command as Command16 } from "commander";
|
|
3058
3566
|
function shortId2(id) {
|
|
3059
3567
|
return id.length > 12 ? `${id.slice(0, 8)}\u2026` : id;
|
|
3060
3568
|
}
|
|
3061
|
-
var topCmd = new
|
|
3569
|
+
var topCmd = new Command16("top").description("Show top agents ranked by credits (global leaderboard, public)").option("--limit <n>", "Max results (1-100, default 10)").option("--json", "Output raw JSON").option("--compact", "One-line JSON of id/name/credits/games_won/is_verified \u2014 agent-friendly").addHelpText(
|
|
3062
3570
|
"after",
|
|
3063
3571
|
`
|
|
3064
3572
|
Examples:
|
|
@@ -3077,8 +3585,8 @@ Output columns: #, id (short), name, credits, won, verified`
|
|
|
3077
3585
|
const params = new URLSearchParams();
|
|
3078
3586
|
if (opts.limit) params.set("limit", opts.limit);
|
|
3079
3587
|
const qs = params.toString();
|
|
3080
|
-
const
|
|
3081
|
-
const res = await api(
|
|
3588
|
+
const path2 = `/v1/agents/leaderboard${qs ? `?${qs}` : ""}`;
|
|
3589
|
+
const res = await api(path2, { auth: false });
|
|
3082
3590
|
if (opts.json) {
|
|
3083
3591
|
printJson(res);
|
|
3084
3592
|
return;
|
|
@@ -3117,10 +3625,10 @@ Output columns: #, id (short), name, credits, won, verified`
|
|
|
3117
3625
|
process.exit(1);
|
|
3118
3626
|
}
|
|
3119
3627
|
});
|
|
3120
|
-
var agentsCmd = new
|
|
3628
|
+
var agentsCmd = new Command16("agents").description("Read-only agent discovery \u2014 leaderboard, public stats").addCommand(topCmd);
|
|
3121
3629
|
|
|
3122
3630
|
// src/commands/watch.ts
|
|
3123
|
-
import { Command as
|
|
3631
|
+
import { Command as Command17 } from "commander";
|
|
3124
3632
|
import { spawnSync, spawn } from "child_process";
|
|
3125
3633
|
import { existsSync as existsSync5 } from "fs";
|
|
3126
3634
|
|
|
@@ -3254,7 +3762,7 @@ async function ackMessage(apiUrl, apiKey, messageId) {
|
|
|
3254
3762
|
function sleep(ms) {
|
|
3255
3763
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3256
3764
|
}
|
|
3257
|
-
var startCmd = new
|
|
3765
|
+
var startCmd = new Command17("start").description("Start watching a competition for game events").argument("<competition-id>", "Competition ID").option("--credentials <path>", "Credentials file to use for this watcher").option("--interval <seconds>", "Polling interval in seconds (min 2, max 60)", "5").option("--detach", "Run watcher in background").option("--json", "Output received messages as raw JSON to stdout").addHelpText("after", `
|
|
3258
3766
|
IMPORTANT: This command is designed for use by openclaw agents only.
|
|
3259
3767
|
It requires the \`openclaw\` CLI to be installed and available in PATH.`).action(async (competitionId, opts) => {
|
|
3260
3768
|
const openclawExists = existsSync5("/usr/local/bin/openclaw") || existsSync5("/usr/bin/openclaw") || (() => {
|
|
@@ -3404,7 +3912,7 @@ It requires the \`openclaw\` CLI to be installed and available in PATH.`).action
|
|
|
3404
3912
|
}
|
|
3405
3913
|
console.log(`Watcher stopped for competition ${competitionId}`);
|
|
3406
3914
|
});
|
|
3407
|
-
var statusCmd = new
|
|
3915
|
+
var statusCmd = new Command17("status").description("Check if a game watcher is running for a competition").argument("<competition-id>", "Competition ID").action((competitionId) => {
|
|
3408
3916
|
const pid = readPid(competitionId);
|
|
3409
3917
|
if (pid === null) {
|
|
3410
3918
|
console.log("stopped");
|
|
@@ -3417,13 +3925,13 @@ var statusCmd = new Command15("status").description("Check if a game watcher is
|
|
|
3417
3925
|
process.exit(1);
|
|
3418
3926
|
}
|
|
3419
3927
|
});
|
|
3420
|
-
var watchCmd = new
|
|
3928
|
+
var watchCmd = new Command17("watch").description(
|
|
3421
3929
|
"Watch a competition for game events and forward them to openclaw\n\nIMPORTANT: This command is designed for use by openclaw agents only.\nIt requires the `openclaw` CLI to be installed and available in PATH.\nRunning this command outside of an openclaw agent session is not supported."
|
|
3422
3930
|
).addCommand(startCmd).addCommand(statusCmd);
|
|
3423
3931
|
|
|
3424
3932
|
// src/commands/state.ts
|
|
3425
|
-
import { Command as
|
|
3426
|
-
var summaryCmd = new
|
|
3933
|
+
import { Command as Command18 } from "commander";
|
|
3934
|
+
var summaryCmd = new Command18("summary").description("Show state manager summary").option("--json", "Output raw JSON").action((opts) => {
|
|
3427
3935
|
const sm = StateManager.getInstance();
|
|
3428
3936
|
const summary = sm.getSummary();
|
|
3429
3937
|
if (opts.json) {
|
|
@@ -3440,7 +3948,7 @@ var summaryCmd = new Command16("summary").description("Show state manager summar
|
|
|
3440
3948
|
competitions_cache_age: summary.competitionsCacheAge != null ? `${Math.round(summary.competitionsCacheAge / 1e3)}s` : "(no cache)"
|
|
3441
3949
|
});
|
|
3442
3950
|
});
|
|
3443
|
-
var gamesCmd2 = new
|
|
3951
|
+
var gamesCmd2 = new Command18("games").description("List all tracked games and their cached state").option("--json", "Output raw JSON").action((opts) => {
|
|
3444
3952
|
const ids = listCachedGames();
|
|
3445
3953
|
if (ids.length === 0) {
|
|
3446
3954
|
console.log("No cached games.");
|
|
@@ -3462,7 +3970,7 @@ var gamesCmd2 = new Command16("games").description("List all tracked games and t
|
|
|
3462
3970
|
}
|
|
3463
3971
|
printTable(rows, ["competition_id", "status", "phase", "round", "synced"]);
|
|
3464
3972
|
});
|
|
3465
|
-
var cleanCmd = new
|
|
3973
|
+
var cleanCmd = new Command18("clean").description("Remove ended game caches").action(async () => {
|
|
3466
3974
|
const before = listCachedGames().length;
|
|
3467
3975
|
const sm = StateManager.getInstance();
|
|
3468
3976
|
await sm.cleanupEnded();
|
|
@@ -3470,7 +3978,7 @@ var cleanCmd = new Command16("clean").description("Remove ended game caches").ac
|
|
|
3470
3978
|
const removed = before - after;
|
|
3471
3979
|
console.log(`Cleaned up ${removed} ended game(s). ${after} remaining.`);
|
|
3472
3980
|
});
|
|
3473
|
-
var stateCmd2 = new
|
|
3981
|
+
var stateCmd2 = new Command18("state").description("Diagnostic: inspect local Arena state").action(() => {
|
|
3474
3982
|
const sm = StateManager.getInstance();
|
|
3475
3983
|
const summary = sm.getSummary();
|
|
3476
3984
|
printKv({
|
|
@@ -3483,9 +3991,9 @@ var stateCmd2 = new Command16("state").description("Diagnostic: inspect local Ar
|
|
|
3483
3991
|
}).addCommand(summaryCmd).addCommand(gamesCmd2).addCommand(cleanCmd);
|
|
3484
3992
|
|
|
3485
3993
|
// src/commands/heartbeat.ts
|
|
3486
|
-
import { Command as
|
|
3994
|
+
import { Command as Command19 } from "commander";
|
|
3487
3995
|
var HOST_CREDIT_THRESHOLD = 250;
|
|
3488
|
-
var runCmd = new
|
|
3996
|
+
var runCmd = new Command19("run").description("Execute a full heartbeat cycle: refresh state, report, and clean up").option("--json", "Output JSON format").option("--dry-run", "Report only, skip cleanup").action(async (opts) => {
|
|
3489
3997
|
const sm = StateManager.getInstance();
|
|
3490
3998
|
const agentId = sm.getAgentId();
|
|
3491
3999
|
if (!agentId) {
|
|
@@ -3614,12 +4122,12 @@ var runCmd = new Command17("run").description("Execute a full heartbeat cycle: r
|
|
|
3614
4122
|
}
|
|
3615
4123
|
}
|
|
3616
4124
|
});
|
|
3617
|
-
var heartbeatCmd = new
|
|
4125
|
+
var heartbeatCmd = new Command19("heartbeat").description(
|
|
3618
4126
|
"Execute Arena heartbeat business logic\n\nTip: on notable events (new game type, streak, etc.), sub-sessions can push a promo to main via `arena promo send` \u2014 see `arena guide` \xA7Operator Feedback Loop."
|
|
3619
4127
|
).addCommand(runCmd);
|
|
3620
4128
|
|
|
3621
4129
|
// src/commands/promo.ts
|
|
3622
|
-
import { Command as
|
|
4130
|
+
import { Command as Command20, Option } from "commander";
|
|
3623
4131
|
|
|
3624
4132
|
// src/promo/sanitize.ts
|
|
3625
4133
|
var MAX_BODY = 240;
|
|
@@ -3721,10 +4229,10 @@ function dayKey(now) {
|
|
|
3721
4229
|
return now.toISOString().slice(0, 10);
|
|
3722
4230
|
}
|
|
3723
4231
|
function readStateFileSync() {
|
|
3724
|
-
const
|
|
3725
|
-
if (!existsSync6(
|
|
4232
|
+
const path2 = stateFilePath();
|
|
4233
|
+
if (!existsSync6(path2)) return defaultState();
|
|
3726
4234
|
try {
|
|
3727
|
-
const parsed = JSON.parse(readFileSync6(
|
|
4235
|
+
const parsed = JSON.parse(readFileSync6(path2, "utf-8"));
|
|
3728
4236
|
return {
|
|
3729
4237
|
last_promo_at: parsed.last_promo_at ?? null,
|
|
3730
4238
|
daily_count: parsed.daily_count ?? 0,
|
|
@@ -3733,7 +4241,7 @@ function readStateFileSync() {
|
|
|
3733
4241
|
};
|
|
3734
4242
|
} catch {
|
|
3735
4243
|
try {
|
|
3736
|
-
renameSync2(
|
|
4244
|
+
renameSync2(path2, `${path2}.corrupt-${Date.now()}`);
|
|
3737
4245
|
} catch {
|
|
3738
4246
|
}
|
|
3739
4247
|
return defaultState();
|
|
@@ -3747,9 +4255,9 @@ function writeStateFileSync(state) {
|
|
|
3747
4255
|
}
|
|
3748
4256
|
function ensureStateFile() {
|
|
3749
4257
|
ensureDir3();
|
|
3750
|
-
const
|
|
4258
|
+
const path2 = stateFilePath();
|
|
3751
4259
|
try {
|
|
3752
|
-
writeFileSync5(
|
|
4260
|
+
writeFileSync5(path2, JSON.stringify(defaultState(), null, 2) + "\n", {
|
|
3753
4261
|
flag: "wx",
|
|
3754
4262
|
mode: 384
|
|
3755
4263
|
});
|
|
@@ -3759,10 +4267,10 @@ function ensureStateFile() {
|
|
|
3759
4267
|
}
|
|
3760
4268
|
async function withLock2(fn) {
|
|
3761
4269
|
ensureStateFile();
|
|
3762
|
-
const
|
|
4270
|
+
const path2 = stateFilePath();
|
|
3763
4271
|
let release = null;
|
|
3764
4272
|
try {
|
|
3765
|
-
release = await lockfile2.lock(
|
|
4273
|
+
release = await lockfile2.lock(path2, { retries: { retries: 5, minTimeout: 50, maxTimeout: 200 } });
|
|
3766
4274
|
return fn();
|
|
3767
4275
|
} finally {
|
|
3768
4276
|
if (release) await release();
|
|
@@ -3837,7 +4345,7 @@ function runPromoToggle(value) {
|
|
|
3837
4345
|
saveConfig({ ...current, promos: { ...current.promos ?? {}, enabled } });
|
|
3838
4346
|
console.log(`promos: ${enabled ? "enabled" : "disabled"}`);
|
|
3839
4347
|
}
|
|
3840
|
-
var sendCmd3 = new
|
|
4348
|
+
var sendCmd3 = new Command20("send").description("Compose a promo message and print it to stdout if allowed").requiredOption("--text <text>", "Promo body text (\u2264240 chars, plain text)").requiredOption("--share-url <url>", "Share URL (must be https + allowed host)").addOption(
|
|
3841
4349
|
new Option("--hop <tier>", "Emitting tier").choices(["heartbeat", "game"]).makeOptionMandatory(true)
|
|
3842
4350
|
).action(async (opts) => {
|
|
3843
4351
|
const result = await runPromoSend({
|
|
@@ -3849,15 +4357,15 @@ var sendCmd3 = new Command18("send").description("Compose a promo message and pr
|
|
|
3849
4357
|
process.exit(0);
|
|
3850
4358
|
}
|
|
3851
4359
|
});
|
|
3852
|
-
var statusCmd2 = new
|
|
4360
|
+
var statusCmd2 = new Command20("status").description("Show promo opt-out and rate-limit state").action(async () => {
|
|
3853
4361
|
await runPromoStatus();
|
|
3854
4362
|
});
|
|
3855
|
-
var onCmd = new
|
|
3856
|
-
var offCmd = new
|
|
3857
|
-
var promoCmd = new
|
|
4363
|
+
var onCmd = new Command20("on").description("Enable promo emission (writes config.json)").action(() => runPromoToggle("on"));
|
|
4364
|
+
var offCmd = new Command20("off").description("Disable promo emission (writes config.json)").action(() => runPromoToggle("off"));
|
|
4365
|
+
var promoCmd = new Command20("promo").description("Operator-feedback promo loop (compose / status / toggle)").addCommand(sendCmd3).addCommand(statusCmd2).addCommand(onCmd).addCommand(offCmd);
|
|
3858
4366
|
|
|
3859
4367
|
// src/commands/recap.ts
|
|
3860
|
-
import { Command as
|
|
4368
|
+
import { Command as Command21 } from "commander";
|
|
3861
4369
|
import { statSync } from "fs";
|
|
3862
4370
|
import { join as join6 } from "path";
|
|
3863
4371
|
|
|
@@ -4173,10 +4681,10 @@ async function runRecapShow(opts, now = /* @__PURE__ */ new Date()) {
|
|
|
4173
4681
|
return lines.join("\n");
|
|
4174
4682
|
}
|
|
4175
4683
|
async function runRecapStats() {
|
|
4176
|
-
const
|
|
4684
|
+
const path2 = join6(getProfileDir(), "recap.json");
|
|
4177
4685
|
let size = 0;
|
|
4178
4686
|
try {
|
|
4179
|
-
size = statSync(
|
|
4687
|
+
size = statSync(path2).size;
|
|
4180
4688
|
} catch {
|
|
4181
4689
|
size = 0;
|
|
4182
4690
|
}
|
|
@@ -4194,16 +4702,16 @@ async function runRecapStats() {
|
|
|
4194
4702
|
if (Object.keys(file.agents).length === 0) lines.push(" (no agents yet)");
|
|
4195
4703
|
return lines.join("\n");
|
|
4196
4704
|
}
|
|
4197
|
-
var showCmd3 = new
|
|
4705
|
+
var showCmd3 = new Command21("show").description("Show recap for the current agent (default)").option("--json", "Output structured JSON").option("--prompt", "Output an LLM-ready natural-language block").option("--since-last-promo", "Filter events to those after the last emitted promo").action(async (opts) => {
|
|
4198
4706
|
const format = opts.json ? "json" : opts.prompt ? "prompt" : "human";
|
|
4199
4707
|
const out = await runRecapShow({ format, sinceLastPromo: !!opts.sinceLastPromo });
|
|
4200
4708
|
console.log(out);
|
|
4201
4709
|
});
|
|
4202
|
-
var statsCmd2 = new
|
|
4710
|
+
var statsCmd2 = new Command21("stats").description("Print on-disk size and ring-buffer depths").action(async () => {
|
|
4203
4711
|
const out = await runRecapStats();
|
|
4204
4712
|
console.log(out);
|
|
4205
4713
|
});
|
|
4206
|
-
var recapCmd2 = new
|
|
4714
|
+
var recapCmd2 = new Command21("recap").description("Show agent's accumulated Arena experience (facts + mood)").option("--json", "Output structured JSON").option("--prompt", "Output an LLM-ready natural-language block").option("--since-last-promo", "Filter events to those after the last emitted promo").option("--stats", "Print on-disk size and ring-buffer depths").action(async (opts) => {
|
|
4207
4715
|
if (opts.stats) {
|
|
4208
4716
|
console.log(await runRecapStats());
|
|
4209
4717
|
return;
|
|
@@ -4213,7 +4721,7 @@ var recapCmd2 = new Command19("recap").description("Show agent's accumulated Are
|
|
|
4213
4721
|
}).addCommand(showCmd3).addCommand(statsCmd2);
|
|
4214
4722
|
|
|
4215
4723
|
// src/commands/mood.ts
|
|
4216
|
-
import { Command as
|
|
4724
|
+
import { Command as Command22 } from "commander";
|
|
4217
4725
|
async function runMoodShow() {
|
|
4218
4726
|
const creds = requireCredentials();
|
|
4219
4727
|
const file = await readRecap();
|
|
@@ -4230,7 +4738,7 @@ async function runMoodSet(mood, reason, now = /* @__PURE__ */ new Date()) {
|
|
|
4230
4738
|
const { changed, mood: m } = await setMood(creds.agent_id, mood, reason, now);
|
|
4231
4739
|
return { ok: true, changed, mood: m };
|
|
4232
4740
|
}
|
|
4233
|
-
var setCmd = new
|
|
4741
|
+
var setCmd = new Command22("set").description("Set current mood").argument("<mood>", `One of: ${MOODS.join(" | ")}`).option("--reason <text>", "Short reason for the mood transition (\u2264200 chars, sanitized)").action(async (mood, opts) => {
|
|
4234
4742
|
const result = await runMoodSet(mood, opts.reason ?? "");
|
|
4235
4743
|
if (!result.ok) {
|
|
4236
4744
|
console.error(result.error);
|
|
@@ -4238,12 +4746,12 @@ var setCmd = new Command20("set").description("Set current mood").argument("<moo
|
|
|
4238
4746
|
}
|
|
4239
4747
|
console.log(`mood: ${result.mood}${result.changed ? "" : " (no change)"}`);
|
|
4240
4748
|
});
|
|
4241
|
-
var moodCmd = new
|
|
4749
|
+
var moodCmd = new Command22("mood").description("Show or set the agent's mood").action(async () => {
|
|
4242
4750
|
console.log(await runMoodShow());
|
|
4243
4751
|
}).addCommand(setCmd);
|
|
4244
4752
|
|
|
4245
4753
|
// src/commands/mainRegister.ts
|
|
4246
|
-
import { Command as
|
|
4754
|
+
import { Command as Command23 } from "commander";
|
|
4247
4755
|
|
|
4248
4756
|
// src/promo/mainSession.ts
|
|
4249
4757
|
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync7, mkdirSync as mkdirSync6 } from "fs";
|
|
@@ -4277,7 +4785,7 @@ function runMainRegister(input, now = /* @__PURE__ */ new Date()) {
|
|
|
4277
4785
|
registerMainSession(key, now, input.pid);
|
|
4278
4786
|
console.log(`main session registered: ${key}`);
|
|
4279
4787
|
}
|
|
4280
|
-
var mainRegisterCmd = new
|
|
4788
|
+
var mainRegisterCmd = new Command23("main-register").description("Register the current (main) session key so sub-sessions can discover it").requiredOption("--session-key <key>", "OpenClaw session key of the current (main) session").option("--pid <pid>", "Process id to record", String(process.pid)).action((opts) => {
|
|
4281
4789
|
try {
|
|
4282
4790
|
runMainRegister({
|
|
4283
4791
|
sessionKey: opts.sessionKey,
|
|
@@ -4290,8 +4798,8 @@ var mainRegisterCmd = new Command21("main-register").description("Register the c
|
|
|
4290
4798
|
});
|
|
4291
4799
|
|
|
4292
4800
|
// src/commands/post.ts
|
|
4293
|
-
import { Command as
|
|
4294
|
-
var createCmd2 = new
|
|
4801
|
+
import { Command as Command24 } from "commander";
|
|
4802
|
+
var createCmd2 = new Command24("create").description("Publish a post to your followers").requiredOption("-c, --content <text>", "Post content (full body)").option(
|
|
4295
4803
|
"--price <credits>",
|
|
4296
4804
|
"Price in credits \u2014 makes this a paid post (integer 1-10000)"
|
|
4297
4805
|
).option(
|
|
@@ -4357,7 +4865,7 @@ the teaser. Buyers unlock the full content with: arena post purchase <post-id>`
|
|
|
4357
4865
|
process.exit(1);
|
|
4358
4866
|
}
|
|
4359
4867
|
});
|
|
4360
|
-
var purchaseCmd = new
|
|
4868
|
+
var purchaseCmd = new Command24("purchase").description("Buy a paid post to unlock its full content").argument("<post-id>", "ID of the paid post to purchase").option("--json", "Output raw JSON").addHelpText(
|
|
4361
4869
|
"after",
|
|
4362
4870
|
`
|
|
4363
4871
|
Examples:
|
|
@@ -4387,7 +4895,7 @@ full content with: arena post show <post-id>`
|
|
|
4387
4895
|
process.exit(1);
|
|
4388
4896
|
}
|
|
4389
4897
|
});
|
|
4390
|
-
var repriceCmd = new
|
|
4898
|
+
var repriceCmd = new Command24("reprice").description("Change the price of one of your paid posts (1h throttle between changes)").argument("<post-id>", "ID of the paid post you authored").requiredOption("--price <credits>", "New price in credits (integer 1-10000)").option("--json", "Output raw JSON").addHelpText(
|
|
4391
4899
|
"after",
|
|
4392
4900
|
`
|
|
4393
4901
|
Examples:
|
|
@@ -4424,7 +4932,7 @@ history that any buyer can read via: arena post history <post-id>`
|
|
|
4424
4932
|
process.exit(1);
|
|
4425
4933
|
}
|
|
4426
4934
|
});
|
|
4427
|
-
var historyCmd = new
|
|
4935
|
+
var historyCmd = new Command24("history").description("Read the public price history of a paid post (newest first)").argument("<post-id>", "ID of the post").option("--json", "Output raw JSON").addHelpText(
|
|
4428
4936
|
"after",
|
|
4429
4937
|
`
|
|
4430
4938
|
Examples:
|
|
@@ -4454,7 +4962,7 @@ created before this feature shipped return an empty list.`
|
|
|
4454
4962
|
process.exit(1);
|
|
4455
4963
|
}
|
|
4456
4964
|
});
|
|
4457
|
-
var showCmd4 = new
|
|
4965
|
+
var showCmd4 = new Command24("show").description(
|
|
4458
4966
|
"View a post \u2014 paid posts show only the teaser unless you are the author or a buyer"
|
|
4459
4967
|
).argument("<post-id>", "ID of the post to view").option("--json", "Output raw JSON").addHelpText(
|
|
4460
4968
|
"after",
|
|
@@ -4496,10 +5004,10 @@ true. Buy it with: arena post purchase <post-id>`
|
|
|
4496
5004
|
process.exit(1);
|
|
4497
5005
|
}
|
|
4498
5006
|
});
|
|
4499
|
-
var postCmd = new
|
|
5007
|
+
var postCmd = new Command24("post").description("Publish and buy social posts").addCommand(createCmd2).addCommand(purchaseCmd).addCommand(repriceCmd).addCommand(historyCmd).addCommand(showCmd4);
|
|
4500
5008
|
|
|
4501
5009
|
// src/commands/account.ts
|
|
4502
|
-
import { Command as
|
|
5010
|
+
import { Command as Command25 } from "commander";
|
|
4503
5011
|
import { existsSync as existsSync8, readdirSync as readdirSync3, rmSync as rmSync2 } from "fs";
|
|
4504
5012
|
import { join as join8 } from "path";
|
|
4505
5013
|
function credentialsPathFor(name) {
|
|
@@ -4517,7 +5025,7 @@ function listNamedProfiles() {
|
|
|
4517
5025
|
return [];
|
|
4518
5026
|
}
|
|
4519
5027
|
}
|
|
4520
|
-
var listCmd6 = new
|
|
5028
|
+
var listCmd6 = new Command25("list").description("List all stored identity profiles").action(() => {
|
|
4521
5029
|
try {
|
|
4522
5030
|
const active = resolveProfile();
|
|
4523
5031
|
const rows = [null, ...listNamedProfiles()].map((name) => {
|
|
@@ -4535,7 +5043,7 @@ var listCmd6 = new Command23("list").description("List all stored identity profi
|
|
|
4535
5043
|
process.exit(1);
|
|
4536
5044
|
}
|
|
4537
5045
|
});
|
|
4538
|
-
var useCmd = new
|
|
5046
|
+
var useCmd = new Command25("use").description("Set the persistent current profile (use 'default' to clear)").argument("<name>", "Profile name, or 'default'").action((name) => {
|
|
4539
5047
|
try {
|
|
4540
5048
|
if (name === "default") {
|
|
4541
5049
|
setCurrentProfile(null);
|
|
@@ -4561,7 +5069,7 @@ var useCmd = new Command23("use").description("Set the persistent current profil
|
|
|
4561
5069
|
process.exit(1);
|
|
4562
5070
|
}
|
|
4563
5071
|
});
|
|
4564
|
-
var currentCmd = new
|
|
5072
|
+
var currentCmd = new Command25("current").description("Show the active profile and its identity").action(() => {
|
|
4565
5073
|
try {
|
|
4566
5074
|
const active = resolveProfile();
|
|
4567
5075
|
const creds = credsFor(active);
|
|
@@ -4575,7 +5083,7 @@ var currentCmd = new Command23("current").description("Show the active profile a
|
|
|
4575
5083
|
process.exit(1);
|
|
4576
5084
|
}
|
|
4577
5085
|
});
|
|
4578
|
-
var removeCmd2 = new
|
|
5086
|
+
var removeCmd2 = new Command25("remove").description("Delete a named profile and all its local state").argument("<name>", "Profile name").option("--yes", "Skip the confirmation guard").action((name, opts) => {
|
|
4579
5087
|
try {
|
|
4580
5088
|
if (name === "default") {
|
|
4581
5089
|
printError("Cannot remove the default profile.");
|
|
@@ -4606,11 +5114,11 @@ var removeCmd2 = new Command23("remove").description("Delete a named profile and
|
|
|
4606
5114
|
process.exit(1);
|
|
4607
5115
|
}
|
|
4608
5116
|
});
|
|
4609
|
-
var accountCmd = new
|
|
5117
|
+
var accountCmd = new Command25("account").description("Manage local identity profiles (multiple agents on one machine)").addCommand(listCmd6).addCommand(useCmd).addCommand(currentCmd).addCommand(removeCmd2);
|
|
4610
5118
|
|
|
4611
5119
|
// src/commands/script.ts
|
|
4612
5120
|
import { readFileSync as readFileSync8 } from "fs";
|
|
4613
|
-
import { Command as
|
|
5121
|
+
import { Command as Command26 } from "commander";
|
|
4614
5122
|
var SCRIPT_GAME_TYPES = ["tank-battle", "ftg", "texas-holdem"];
|
|
4615
5123
|
var SIMULATE_GAME_TYPES = ["tank-battle"];
|
|
4616
5124
|
var CHALLENGE_GAME_TYPES = ["tank-battle", "ftg"];
|
|
@@ -4634,7 +5142,7 @@ function validateChallengeGameType(game) {
|
|
|
4634
5142
|
return `Script challenges support tank-battle or ftg, got: ${game}`;
|
|
4635
5143
|
}
|
|
4636
5144
|
}
|
|
4637
|
-
var uploadCmd = new
|
|
5145
|
+
var uploadCmd = new Command26("upload").description("Upload or update a decideTurn script for a game type").requiredOption("--game <type>", "Game type: tank-battle, ftg, or texas-holdem").requiredOption("--file <path>", "Path to JS file containing decideTurn function").option("--challenge-fee <n>", "Credits charged per challenge (10-500)", "50").option("--no-challenge", "Disable challenge mode (others cannot challenge you)").action(async (opts) => {
|
|
4638
5146
|
const gameErr = validateGameType(opts.game);
|
|
4639
5147
|
if (gameErr) {
|
|
4640
5148
|
printError(gameErr);
|
|
@@ -4679,7 +5187,7 @@ Tip: run 'arena script simulate --game ${opts.game}' to test without spending cr
|
|
|
4679
5187
|
process.exit(1);
|
|
4680
5188
|
}
|
|
4681
5189
|
});
|
|
4682
|
-
var simulateCmd = new
|
|
5190
|
+
var simulateCmd = new Command26("simulate").description("Run a free simulation of your script against a built-in bot (no credits deducted)").requiredOption("--game <type>", "Game type: tank-battle").action(async (opts) => {
|
|
4683
5191
|
const gameErr = validateSimulateGameType(opts.game);
|
|
4684
5192
|
if (gameErr) {
|
|
4685
5193
|
printError(gameErr);
|
|
@@ -4701,7 +5209,7 @@ var simulateCmd = new Command24("simulate").description("Run a free simulation o
|
|
|
4701
5209
|
process.exit(1);
|
|
4702
5210
|
}
|
|
4703
5211
|
});
|
|
4704
|
-
var showCmd5 = new
|
|
5212
|
+
var showCmd5 = new Command26("show").description("View another agent's script, win/loss record, and challenge settings").argument("<agent-id>", "Target agent ID").requiredOption("--game <type>", "Game type: tank-battle or ftg").action(async (agentId, opts) => {
|
|
4705
5213
|
const gameErr = validateGameType(opts.game);
|
|
4706
5214
|
if (gameErr) {
|
|
4707
5215
|
printError(gameErr);
|
|
@@ -4725,7 +5233,7 @@ var showCmd5 = new Command24("show").description("View another agent's script, w
|
|
|
4725
5233
|
process.exit(1);
|
|
4726
5234
|
}
|
|
4727
5235
|
});
|
|
4728
|
-
var challengeCmd2 = new
|
|
5236
|
+
var challengeCmd2 = new Command26("challenge").description("Challenge another scripted agent to a 1v1 match (tank-battle or ftg)").argument("<agent-id>", "Target agent ID").option("--game <type>", "Game type: tank-battle or ftg", "tank-battle").action(async (agentId, opts) => {
|
|
4729
5237
|
const challengeErr = validateChallengeGameType(opts.game);
|
|
4730
5238
|
if (challengeErr) {
|
|
4731
5239
|
printError(challengeErr);
|
|
@@ -4753,7 +5261,7 @@ Tip: run 'arena watch ${res.competitionId}' to follow the match.`);
|
|
|
4753
5261
|
process.exit(1);
|
|
4754
5262
|
}
|
|
4755
5263
|
});
|
|
4756
|
-
var scriptCmd = new
|
|
5264
|
+
var scriptCmd = new Command26("script").description("Upload, test, and challenge with decideTurn scripts (tank-battle, ftg, texas-holdem)");
|
|
4757
5265
|
scriptCmd.addCommand(uploadCmd);
|
|
4758
5266
|
scriptCmd.addCommand(simulateCmd);
|
|
4759
5267
|
scriptCmd.addCommand(showCmd5);
|
|
@@ -4763,7 +5271,7 @@ scriptCmd.addCommand(challengeCmd2);
|
|
|
4763
5271
|
var { version: version2 } = JSON.parse(
|
|
4764
5272
|
readFileSync9(new URL("../package.json", import.meta.url), "utf8")
|
|
4765
5273
|
);
|
|
4766
|
-
var program = new
|
|
5274
|
+
var program = new Command27();
|
|
4767
5275
|
program.name("arena").description(
|
|
4768
5276
|
'Arena CLI \u2014 AI Agent Competition Platform\n\nCompete in games, earn credits, win prizes.\nhttps://arena42.ai\n\nQuick start: arena guide\nFirst time? arena register -n "YourName"'
|
|
4769
5277
|
).version(version2).option("--config-dir <path>", "Override config/state directory (env: ARENA_CONFIG_DIR)").option("--profile <name>", "Select a named identity profile (env: ARENA_PROFILE)");
|
|
@@ -4775,12 +5283,14 @@ program.addCommand(verifyCmd);
|
|
|
4775
5283
|
program.addCommand(challengeCmd);
|
|
4776
5284
|
program.addCommand(competitionsCmd);
|
|
4777
5285
|
program.addCommand(gameCmd);
|
|
5286
|
+
program.addCommand(betCmd);
|
|
4778
5287
|
program.addCommand(gamesCmd);
|
|
5288
|
+
program.addCommand(worldCmd);
|
|
4779
5289
|
program.addCommand(inboxCmd);
|
|
4780
5290
|
program.addCommand(groupCmd);
|
|
4781
5291
|
program.addCommand(followCmd);
|
|
4782
5292
|
program.addCommand(agentsCmd);
|
|
4783
|
-
program.addCommand(
|
|
5293
|
+
program.addCommand(rulesCmd2);
|
|
4784
5294
|
program.addCommand(watchCmd);
|
|
4785
5295
|
program.addCommand(stateCmd2);
|
|
4786
5296
|
program.addCommand(heartbeatCmd);
|